Commit graph

22 commits

Author SHA1 Message Date
e6b734c4fb docs(catalog): re-measure badge coverage at pinned refs (27 gated / 15 ungated across 12 plugins)
The 2026-08-02 reading (24 gated / 15 ungated across 7 of 11 plugins) predated
repo-standard entering the catalog and repo-mailbox badging its two remaining
axes. Re-measured 2026-08-04 at every pinned ref with the gate's own parsers:
42 catalog axis-claims, 27 badge-covered, 15 badge-less across 7 of 12 plugins,
source=ref for 12/12. The two readings reconcile exactly (24+3, 15-2+2).

Two premisses corrected while measuring:

- graceful-handoff has NOT lost its tests/hooks badges. Both badge-removing
  commits are unreleased (after v3.1.0), so at the pinned ref the catalog's
  "30 tests" is gated. Recorded at pickStatSource as the opposite failure
  direction to the llm-security case: reading the worktree misreports COVERAGE,
  not just values, and neither example alone would have caught it.

- the "251 selftest checks" correction is not settled: CLAUDE.md said 370,
  check-versions.mjs said 374, and repo-mailbox's own README at v0.19.0 implies
  390. Moot for the gate (badged and green at v0.20.2) and not re-measured, so
  the disagreement is flagged rather than laundered into one number.

Badge-less VALUES were not re-audited this session and do not need to be: only
two refs have moved since their audit, so all 15 stand measured at the ref they
are pinned to. Re-measurement is triggered by a ref moving, not by the calendar.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PtnSwCLpUR3Hk8SHkPoxX4
2026-08-04 21:43:52 +02:00
780f86ec9f fix(check-versions): mirror stats against the pinned ref, not the working tree [skip-docs]
Found live minutes after shipping fc95cbd: llm-security went ERROR on scanners
(23 vs 22) and tests (2013 vs 2034). The catalog was RIGHT and the gate was
wrong. llm-security had committed past its v7.8.3 tag without bumping the
version, and the gate was reading the sibling working tree — but the catalog
documents what INSTALLS, and `ref: v7.8.3` still installs 23/2013.

Stat badges are now read with `git show <ref>:README.md`, falling back to the
working tree only when the ref cannot be read (a ref resolving to nothing is
already its own ERROR, so the fallback cannot hide a dangling ref). The
version-badge check is unchanged and still reads the working tree: that one is
about the plugin's internal consistency, not about what the catalog promises.

This also corrects a stat I got wrong in fc95cbd. I had moved config-audit from
1410 to 1441 tests off the working tree; at the pinned v5.13.0 the badge says
1398. 1441 is unreleased. The catalog now says 1398 — what installs.

[skip-docs]: CLAUDE.md carries the rule and the "check `git show <ref>:README.md`
before believing the working tree" instruction; README.md changes by one number
because the gate was wrong about it.

Tests 117 -> 120 (+3, all regression). Gate green at 11 OK / 0 WARN / 0 ERROR.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDSiMcgLMpEETwtkc86Nym
2026-08-02 21:26:27 +02:00
c6c82b358e docs(okf-check): body-text emitters are now ZERO — okr migrated too
029ffb8 (this session, hours ago) said one emitter remained. okr migrated the
same day, so that is already stale. Both emitters are gone:
- linkedin-studio at its bc47c18 (scaffold.ts)
- okr at its 1da9dd8 — okf-index.mjs now emits okf_version via writeFrontmatter
  ahead of the `# heading`; only okf_layout stays in body text.

Verified read-only against okr e662196 rather than taken from their notice.

The fallback stays, but its justification changed with them: it now covers
bundles already written in the old form, not emitters still producing it. okr
made its own checker tolerant (049259d) BEFORE moving the emitter (1da9dd8), so
nothing forces a simultaneous migration and the parity signature does not split.
Re-measured after the change: check-okf-parity 9/9 green, okf-check 25/25.

Removal condition restated honestly: only once every bundle in the wild has been
rewritten — which nothing tracks today.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDSiMcgLMpEETwtkc86Nym
2026-08-02 21:22:05 +02:00
fc95cbd300 feat(check-versions): gate the catalog's stat lines against plugin badges [skip-docs]
[skip-docs]: the user-facing doc here is CLAUDE.md, not README.md. This is a
maintainer-side consistency gate — the catalog landing page should not describe
it — and CLAUDE.md gains a full paragraph covering the rule, the measured
badge-less gap, and the never-hand-edit-a-stat-line instruction. README.md
changes by exactly one number because the gate found it wrong.

The catalog restates each plugin's counts in a per-plugin stat line, and those
numbers rot silently: nothing compared them to anything. Measured across all 11
plugin READMEs today, config-audit's line claimed 1410 tests while the plugin's
own badge said 1441.

Rule is PER-AXIS, not per-plugin. For each number on a catalog stat line, if the
plugin carries a shields badge for that axis, they must agree (ERROR otherwise);
if it carries no such badge, the axis is skipped. This was measured, not assumed:
21 axis-pairs are badge-covered, but 14 axes across 8 of the 11 plugins are
badge-less (voyage 4, ms-ai-architect 3, repo-mailbox 2, ai-psychosis 2,
linkedin-studio 1, claude-design 1, graceful-handoff 1). A per-plugin exception
list — the original sketch, scoped around voyage alone — would have had to name 8
of 11 repos and be hand-edited for every new axis.

Stated plainly rather than hidden: those 14 axes stay ungated. repo-mailbox's two
were 6 and 251 against a true 8 and 374 (fixed in e9054f8).

Parsing is measured against the real corpus: shields' `--` literal-hyphen escape,
`_`/`%20` spaces, zero as a real count, non-numeric badges (version/platform/
license) excluded, parenthetical asides read as their own axis ("5 skills (389
docs)"), doc/docs/references/reference_docs/knowledge_docs folded onto one axis,
and `N+` read as a lower bound.

Only the catalog's config-audit number changed; the plugin badge is the source.
Tests 106 -> 117 (+11). Gate green at 11 OK / 0 WARN / 0 ERROR.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDSiMcgLMpEETwtkc86Nym
2026-08-02 21:12:50 +02:00
029ffb878d docs(okf-check): correct the body-text fallback rationale — 1 emitter, not 2
linkedin-studio migrated its bundle-root okf_version from body text into the
root index.md frontmatter block (their bc47c18, which is their HEAD). The
comment justifying the body-text fallback still claimed two live emitters.

Verified against ground truth rather than taken from the notice:
- linkedin-studio scaffold.ts now emits okf_version 0.1 inside the frontmatter
  block (measured 2026-08-02 at bc47c18).
- okr scripts/okf-index.mjs:204 still pushes it as body text, after the
  `# heading` line with no `---` delimiters (measured 2026-08-02 at d634385).

So the fallback stays — it is still load-bearing for okr, and check-okf-parity
still compares this value against okr's live checker. Only the count and the
removal condition change. Comment-only; 106/106 tests green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDSiMcgLMpEETwtkc86Nym
2026-08-02 21:02:19 +02:00
c4b776e4c6 fix(okf-check): required and recommended fields are read at top level only
The frontmatter reader matched `^\s*<key>:` with the m flag — indentation-
agnostic, so a block-form nested entry satisfied a top-level lookup. Measured
against okf/SPEC.md at frozen 3fcbb9f, this is a field confusion, not a near
miss: :467-469 names `resource`, `sources[].resource`, `executor.resource` and
`attester.resource` as DISTINCT fields. Top-level `resource` is the URI of the
asset a concept describes (:196); `sources[].resource` is the material it
derives from (:302). `sources` entries carry their own `title` and `type` too.

The consequence was not confined to warnings. Measured before the fix, a
concept with NO top-level `type:` and a `sources[].type` reported "0 files
without type: / OK: valid OKF bundle" — a false negative on §4.1's only
always-required field. `untyped` IS in the parity signature
(check-okf-parity.mjs:73-76), but okr vendors the same regex, so both impls
were blind identically and the gate stayed green while both were wrong.

Anchoring the key at column 0 fixes it. Flow-form never had the bug: in
`sources: [{ id: s1, resource: fixture }]` the nested key is mid-line, so `^`
cannot match it — measured against llm-ingestion-okf's v0.2 golden bundle
(6e0a7c0, read-only), which warns about `resource` and `description` both
before and after.

The divergence from okr is deliberate and is NOT okr lagging. Their reader is
SHARED, and the nested match is documented as load-bearing for their injector
(lib/frontmatter.mjs:7-9 -> inject:69) — while the same module backs their
scripts/okf-check.mjs:101, which needs the opposite. Pinned as parity fixture
`red-nested-key` (catalog FAILs on the nested type, okr passes it), so the
split is a running red/green signal instead of a note. It flips to `agree`
only if okr scopes the checker's reader without touching inject.

Correcting two premises carried in from the previous session, both measured:
- The reader was NOT flat/top-level-only. It read nested keys, so the suspected
  false POSITIVE on `resource` was actually a false NEGATIVE, opposite sign.
- "No v0.2 bundle exists" held for our own corpora and emitters only.
  llm-ingestion-okf ships a v0.2 golden bundle, where the previous commit's
  version-conditional list has real effect — and behaves correctly there.

docs/okf-second-brain/spec.md is untouched deliberately: it makes no claim
about key scope, so gate and convention do not disagree here.

Tests 103 -> 106 (okf-check 22 -> 25), parity 9/9 -> 10/10. All six suites
green; check-versions 11 OK / 0 WARN / 0 ERROR.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RGZGiDPYcHUMSDCVJavRhp
2026-08-01 19:57:29 +02:00
fa3c5d8482 fix(okf-check): recommended last-change field follows the bundle's okf_version
The gate carried a flat RECOMMENDED list ending in `timestamp`. Upstream retired
that field in v0.2: "`timestamp` is superseded by `generated.at`" (okf/SPEC.md
§13.1:802-803, read at frozen 3fcbb9f), one of the version's two breaking
changes — while :804 still lets a consumer "fall back to a legacy `timestamp`
when `generated` is absent".

A version-unconditional list cannot serve both readings. It either nags a
correct v0.2 bundle about a retired field, or goes silent about a field v0.1
still wants. So the list is now chosen by the bundle root's own okf_version:
< 0.2 (or absent/unshaped) keeps `timestamp`, >= 0.2 asks for `generated`.
Absence gets the legacy floor deliberately — §3 echoes a missing marker rather
than failing it, so it still needs a defined list.

Measured, not assumed:
- Every fixture in both corpora and both live emitters (okr, linkedin-studio)
  still write `okf_version: 0.1`, so this changes NO verdict today. It is
  written now because the upstream reading is fresh and pinned to a commit.
- The parity signature is conceptCount|untyped|okfVersion|okfVersionAccepted
  (check-okf-parity.mjs:73-76) — warnings are not in it, so diverging from
  okr's list here cannot red the parity gate. Confirmed: 9/9 fixtures pass.
- The compare is component-wise, NOT parseFloat: okf_version is version-SHAPED,
  and parseFloat('0.10') is 0.1, which would sort 0.10 before 0.2 and hand a
  newer bundle the retired field. Guarded by its own test.

spec §4 is updated in the same commit — a gate and the convention it enforces
must not disagree about which field they want.

Tests 98 -> 103 (okf-check 17 -> 22). All six suites green; check-versions
11 OK / 0 WARN / 0 ERROR.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0135YZBcCDvH3BgG5yEtuHCA
2026-07-31 21:27:31 +02:00
1ca27f6507 feat(okf): §6 placement follows upstream — okf_version in root frontmatter (spec 0.2 -> 0.3)
Upstream (~/repos/_okf-upstream @ 3fcbb9f, okf/SPEC.md) is unambiguous: a bundle-root
index.md MAY carry okf_version in a FRONTMATTER block, "the only place frontmatter is
permitted in an index.md" (§8:509-510 + §12:773-775). Through 0.2 this convention said
the opposite — no frontmatter, marker in body text. The divergence was OURS against
upstream, so 0.3 removes it rather than documenting it. Operator ruling, 2026-07-31.

Measured before deciding (date pinned to every number):
  - emitters: okr (body, okf-index.mjs:204) · linkedin-studio (body, scaffold.ts:38)
    · commons (FRONTMATTER, examples/nav-golden-*/bundle/index.md)
  - this repo already carried BOTH forms, both green: okf-parity-corpus (9, body) and
    nav-golden-corpus (4, frontmatter, byte-exact from commons @ b641741). Invisible
    because the marker regex was unanchored /m, i.e. placement-blind.

THE READER IS TRANSITIONAL BY DESIGN, NOT BY OMISSION. check-okf-parity.mjs:36-39 runs
okr's LIVE checker and compares conceptCount|untyped|okfVersion|okfVersionAccepted. A
frontmatter-only reader reports null here and 0.1 there, splitting the signature on all
9 body-text fixtures — a red gate produced by a doc change, with no bundle having become
less conformant. So both placements are read, one is canonical, and placement is
DECLARED, NOT ENFORCED — the same shape §3 presence has carried since 07-23.

QUOTING WAS UPSTREAM'S OWN FORM, NOT A CORNER CASE. §12:773 is the only place in the
upstream spec showing the key with a value, and it is quoted: okf_version: "0.2". The
0.2 gate ran the shape regex on the RAW string and failed upstream's canonical example.
Fixed by unquoting BEFORE the shape check. Implemented by reusing okf-frontmatter.mjs's
existing get(), which already unquoted (lines 22-26) — no new parsing code.

okf_layout STAYS IN BODY TEXT (§12). Upstream's exception is enumerated to one key, so
the block exists by upstream's leave and for upstream's key; our own extension marker
stays outside it. Asymmetric on purpose: it survives either construction of upstream's
parenthesis — a question okr has flagged as open and we do not own.

MEASURED CONSEQUENCE, REPORTED NOT HIDDEN: okr v1.8.2 fixed their PRODUCER, not their
checker (rootMarkers/pick returns the raw string). On a quoted fixture the two impls now
diverge on the VALUE — catalog 0.2 vs okr "0.2" — measured directly via evaluateBundle.
No existing gate is affected because the corpus carries no quoted fixture, which also
means the parity gate's green does NOT cover the quoting axis: "not run", not "as
expected". The shared unquote-before-compare decision has to reach okr's CHECKER too.

Verification: suite 90 -> 98/98 (8 new; written red first — 6 genuinely failed, 2 were
regression guards already correct). Parity 9/9, red-marker-layout still diverging per
its manifest. nav-golden 2/2. git status test/ clean: NO fixture migrated — the 9
body-text fixtures are now the coverage for the transitional path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FRhqgd8hws7SxT3mC4Lm4U
2026-07-31 17:36:15 +02:00
2efe98ba02 fix(rollup): discover git worktrees - .git tested for existence, not directory-ness
The catalog half of a shared gap that was previously PINNED rather than fixed.
This reader of ~/repos tested `.git` with a directory check, so a plain
`git worktree add ~/repos/feature-x` produced a depth-1 sibling that can carry
its own STATE.md and was dropped SILENTLY - no warning, no count,
indistinguishable from a repo with nothing to say. A submodule fails the same
way for the same reason.

It was pinned, not fixed, because diverging would break the very
same-name-from-both-readers invariant discoverRepos exists to hold. ~/repos
answered YES to the gated question and reported their half landed. Verified
here rather than taken on report: board.sh's Discovery block tests `-e`, and
has done since board.sh was introduced (repo-mailbox 61e224c, first released
v0.9.0) - so the released reader has no `-d` era at all, and only this side
was ever the outlier. The two move together from here - change one, change the
other.

THE ACCEPTANCE TRAP, MEASURED RATHER THAN ASSUMED. This changes NO number
against real ~/repos. Old and new builders were run back to back against the
live tree and diffed: stdout and stderr byte-identical, both exit 0. Directly
measured why: 0 directories at depth 1 or 2 currently carry .git as a file.
A number standing still is not ambiguous here, it is the ONLY possible outcome,
and it is why a fixture is not the best way to test this but the only way.

FIXTURE GATED BEFORE BEHAVIOUR. The worktree fixture uses a real `git worktree
add`, not a hand-written `.git` file - faking it would assert against our guess
at git's on-disk format instead of against git. A separate test asserts the
fixture itself: .git exists, is a file, is not a directory. Without it, the day
git stops writing worktree .git as a file the behaviour tests would go green
while measuring nothing, and a green run cannot distinguish that from success.
The gate says which one it was, and says the premise of the -e rule is gone
rather than inviting a test tweak.

Two behaviour tests: a worktree at depth 1, and one under a polyrepo container
at depth 2 - the same rule at both depths board.sh walks.

Suite 90/90 across the six files (rollup 31 -> 34). check-versions 11 OK,
0 WARN, 0 ERROR. Real run unchanged at 8 repos, 17 markers, Output B 0 lines,
1 V6 warning, exit 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KSX2v1m7Fz22BKrGJZuUpQ
2026-07-31 15:48:41 +02:00
1eabad1d27 feat(rollup): V6 marker-loss gate + depth-1+2 discovery matching board.sh
Two measured defects in build-rollup-register.mjs, reported by ~/repos and
independently re-measured here with the producer's own parser as instrument.

D1 - discovery saw only depth 1, so 5 markers across 3 marketplace repos were
invisible (11 STATE.md files live at depth 2). discoverRepos now mirrors
board.sh:63-89 EXACTLY: a dir that is itself a git repo contributes itself and
is NOT descended into; a non-repo dir holding git repos is a polyrepo container
contributing its children. Identity stays the RAW BASENAME (board.sh:100) so
this builder, board.sh and the coord mailboxes name a repo alike.

Not descending into a git repo is what makes the known nested-name trap
(claude-code-100x/claude-code-100x, both git repos) structurally unreachable
rather than merely absent - blanket depth-2, as proposed, would have reached it.
A collision gate still fails HIGH on two STATE-bearing dirs resolving to one
name, gated on CONTRIBUTING repos so a same-named dir without STATE.md cannot
stop the build. Measured today: 0 collisions across 25 state-bearing dirs.

D2 - parseMarkerLine returns null for two DIFFERENT facts ("not a marker" /
"a marker that failed to parse"), so a malformed marker vanished with exit 0.
V4/V5 only ever gated the carrier; nothing gated marker loss. New V6 reports a
line whose topic and KNOWN status token are both present but which does not
parse, naming file + line number. A warning, not a failure: a typo in another
repo's LOCAL state file must be visible, but is not this builder's business to
fail on. Unknown tokens stay "not a marker", keeping prose out of the channel.
A repo with only losses is still discovered - skipping it would re-open the
exact silent channel V6 closes.

Acceptance against real ~/repos: 15 markers (was 10, +5 exactly as predicted),
2 V6 warnings naming the two measured "--" lines, 0 collisions, exit 0.
Test fixtures are synthetic: marker prose is Output-A material (LOCAL-ONLY),
so quoting a real one into this public repo would be the same
durability-vs-publicity conflation the builder header warns about.

Suite 87/87 (was 78, +9). check-versions 11 OK / 0 WARN / 0 ERROR.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CPHvLhn2U3j2XD3Cn8zeYa
2026-07-26 15:20:50 +02:00
6a72b26985 feat(okf): enforce §3 okf_version shape, bump convention 0.1 -> 0.2
The 2026-07-23 reservation said enforcement waits until "the emitters have
migrated". Measured rather than assumed, and the emitter set turned out to be
TWO, not three:

  okr @0059da7       okf_version: 0.1 + okf_layout: kb-layout-2026-06. Migrated.
  linkedin-studio    scaffold.ts:38 -> okf_version: 0.1. Never carried a layout
                     string; has no okf_layout and needs none.
  ms-ai-architect    NOT an emitter. Zero index.md in the repo; okf_version
                     appears in one planning doc. The status table already said
                     "designed, not built" -- only the word "emitters" implied it.

Swept the whole marketplace plus the sibling consumer repos: every live
okf_version value is 0.1. The flip is a no-op today and locks the invariant.

The gate checks SHAPE (/^\d+(\.\d+)*$/), never membership: the upstream value
set is Google's (spec §12), so a bundle targeting a newer version passes. A
membership check would be the convention claiming a set it says it does not own
-- the over-reach class this round has corrected three times. Presence stays
unenforced (reported, not failed) as a separate §3 MUST.

Convention bumped 0.1 -> 0.2 by this log's own criterion: the set of conforming
bundles changed (the spec explicitly said a layout string "conforms today", and
rollout rule 6 defines conformance as gate output). The §12 per-plugin re-check
is pre-measured as finding zero violations.

Also closes a parity blind spot found while landing this: check-okf-parity
compared the okf_version VALUE, which is identical exactly when two impls
disagree about whether it is acceptable -- a false "agree" on the axis the gate
exists to watch. The signature now carries a boolean shape verdict, with
red-marker-layout as the committed red proof. Its "diverge" expectation encodes
okr's lag (they still pure-echo) and flips to "agree" when they mirror it.

And a correction the round earned: portfolio-optimiser enumerated their own
bundle surface and it is four, not three. The dormant fourth
(reference_domain.py:49 -> package data at :70) has no flag form at all, so our
published claim that the re-measurement surfaced "one" unreported entry was
itself one-of-two. Sharpens the standing rule: a search shaped like one entry
type cannot see another; install-vs-fixture is one instance, not the class.

Suite 73 -> 78. coord: PO replied, okr + llm-ingestion-okf notified (the latter
under the standing promise to flag any okf-check.mjs change).
2026-07-25 20:32:30 +02:00
325727523d test(okf): consume nav-golden fixtures + thin STEG 0 corpus gate
Consume commons' nav-golden fixture class byte-exact from
portfolio-optimiser-commons @ b641741 (nav-golden-hierarchy positive +
nav-golden-escape boundary) into test/nav-golden-corpus/ and wire a thin
gate scripts/check-nav-golden.mjs (+ .test.mjs, 10 tests).

Scope (operator decision, thin): the catalog is the convention owner and
holds no consumer (null konsument), and the shared retrieval skill is
deferred (spec.md §10 Stage 3). So the gate does NOT run a read-context
navigator and does NOT assert the goldens byte-exact. It asserts only what
the catalog owns: each fixture bundle/ is a conformant OKF bundle under the
existing §3 checkBundle (every concept typed + root okf_version), and the
committed expected-read-context.md golden is present and non-empty. Byte-exact
navigator conformance stays a consumer concern (okr STEG 4 / Stage 3 skill).

Fixtures pinned byte-exact (.gitattributes -text); manifest carries source
repo+SHA per the vendoring-provenance rule. Naming: the stale "§5 pts 1-5"
read-context label maps to the settled method-spec §3 Step 1 (commons 9801d35);
no open §5 dependency. commons confirmed b641741 complete + goldens derived
from §3 Step 1 (via coord).

Verified: check-nav-golden 10/10; gate CLI exit 0 (2 fixtures PASS); full
suite 73/73 across the six test files.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NeK9hkxrU9wFPBYGYnSV1V
2026-07-24 20:35:44 +02:00
ba844976e9 feat(catalog): add state roll-up register builder (STEG 0 wiring)
Wire the two-output roll-up register per the ratified D2 contract
(~/.claude/coord/register.md; commons c66ccc3 + status vocab fe6b998),
resolving the AS#5 gate the coord notice handed catalog.

- Output A: rich LOCAL roll-up grepped from ~/repos/*/STATE.md markers.
- Output B: public status-token index, a projection of A, opt-in via a
  committed .rollup-carrier; absent/private modes excluded by design.
- V4 public-safety gate: a carrier line must be exactly "topic: <token>"
  (no prose, no em-dash, known token) or it is blocked from B.
- V5 drift-warn: committed-carrier status != STATE status -> warn; STATE wins.

Pure-function core + thin ~/repos CLI shell, zero deps, catalog script
pattern. 22/22 tests over the ratified adversarial axes. Commits no
carrier/output (AS#5 discipline: catalog is public-mirror class -> mode absent).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NeK9hkxrU9wFPBYGYnSV1V
2026-07-24 01:46:53 +02:00
09521f8f2d test(okf): add running n-way per-file parity gate + adversarial corpus (STEG 3)
Make OKF §3 checker parity a RUNNING per-file red/green signal instead of a
stale one-shot claim. The catalog and okr checkers provably diverge on the same
input (catalog walks everything + no BOM/CRLF norm; okr skips innboks/dot-dirs +
normalizes); this gate turns that divergence into a monitored, tested fact.

- scripts/check-okf-parity.mjs: n-way runner, zero-dep. Normalizes each impl's
  existing checkBundle(root) return to {conceptCount, untyped[], okfVersion} and
  compares per file over default read-mode. Impl registry is availability-guarded
  so a catalog-only checkout degrades gracefully; a documented subprocess seam
  awaits llm-ingestion-okf's Python checker (fase 2) + Node port (fase 4).
- test/okf-parity-corpus/: committed byte-exact adversarial corpus + manifest.
  Red-proof fixtures (expected: diverge) the gate MUST go red on — byte axis
  (BOM+CRLF) and tree axis (innboks, dot-dir) each carry one; canon = both.
  .gitattributes -text pins the BOM/CRLF bytes. Green fixtures pin agreement
  incl. a SHARED gap (both descend into node_modules).
- check-okf-parity.test.mjs: 8/8. Corpus assertions + byte-tracking red-proof +
  runtime-materialized symlink/NFC-NFD meta-tests + graceful-skip seam.

Honest scope: only 2 impls expose a runnable per-file checker today, so the gate
runs 2-way and is architected n-way — no faked third impl, no cross-repo code.
spec §7 + log.md updated (no self-certified parity; fixes stay upstream-first).

Verified: parity 8/8, CLI exit 0; regression 33/33; check-versions 10 OK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NeK9hkxrU9wFPBYGYnSV1V
2026-07-24 01:23:46 +02:00
548a9abab3 docs(okf): retract false okr verdict-parity claim + fold in 7 distilled notes
The shared checker (scripts/okf-check.mjs) was lifted from okr once (c06e4d7,
2026-06-29) and never updated; okr hardened its checker afterward (skip
innboks/dot-dirs + scoped checkBundle @ 3b45be7, 2026-06-30; BOM/CRLF
normalization @ 482effb, 2026-07-17). The two have diverged and are NOT
verdict-identical. Retract the "byte-identical / verdict parity is verified"
claim everywhere it stood (spec.md section 7, log.md incl. a handed-out
conformance flag, and both script headers), replacing it with a precise, dated
provenance note. Establishing parity is tracked separate work
(check-okf-parity.mjs); fixes go upstream-first (drift is two-way). No code
behavior changed. Also folds the round's 7 distilled architecture notes into
log.md. Tests 33/33.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 22:05:13 +02:00
c06e4d7b55 feat(catalog): shared OKF conformance checker + wire as cross-plugin acceptance gate
Lift okr's reference okf-check.mjs into the catalog as the single cross-plugin
acceptance gate for the OKF-compatible second-brain form (spec §3):

- scripts/okf-check.mjs (+ vendored okf-frontmatter.mjs): verdict logic
  byte-identical to okr's reference impl, English output, zero deps, self-contained.
- scripts/okf-check.test.mjs: 5 self-contained tests (temp-dir bundles).
- spec §7 + §14: the shared checker now lives here; only TS/mjs reconciliation
  remains Stage-3, not required for the gate. log.md protocol §6: conformance is
  verified by the gate, not asserted — a plugin moves to 🟢 only after passing it.

Verified: 33/33 catalog tests green (canonical glob form); verdict parity with
okr's checker on okr fixtures (positive + negative missing-type); a scaffolded
linkedin-studio brain/ validates clean (exit 0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012GqEHp4uDiivfrAUjw4BdE
2026-06-29 10:27:05 +02:00
2e1d206ab3 feat(catalog): gate catalog README labels against ref + close the drift
The catalog README's per-plugin `vX.Y.Z` labels were an UNGUARDED surface:
check-versions validated each plugin's OWN README badge, never the catalog
README's labels, so they drifted (config-audit shown v5.5.0 while pinned to
v5.7.0; voyage v5.1.1 while pinned to v5.6.0) — the doc misstated what
`claude plugin install` actually resolves.

Close the class, same pattern as the ref surface:
- check-versions.mjs: new ERROR rule "catalog README label == catalog ref"
  via pure extractCatalogLabel() (matches the /open/<name>) heading, takes the
  first `vX.Y.Z`, ignores a trailing lang/flag badge). No legitimate transient
  state lets label != ref, so ERROR (not WARN). +5 tests.
- release-plugin.mjs: on --write, bump the README label atomically with the ref
  via pure reconcileReadmeLabel() and git add README.md on --commit, so future
  releases keep label and ref in lock-step. +4 tests.
- README.md: reconcile the 2 stale labels (config-audit -> v5.7.0,
  voyage -> v5.6.0). Gate now 9 OK / 1 WARN / 0 ERROR.
- CLAUDE.md: doctrine updated to document the new gate rule + atomic label bump.

ms-ai-architect stays WARN by decision (1.16.0 is unreleased WIP, never tagged).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cm2RxKbomdLqjiWGcwCCPi
2026-06-22 13:38:23 +02:00
9b1838f1d7 feat(catalog): release-plugin.mjs — atomic catalog-ref release helper
The polyrepo split made a plugin release a TWO-repo act: tag the plugin repo AND
bump the catalog `ref`. The second step is manual and easily forgotten — that
drift just stranded linkedin-studio on v0.4.0 while its plugin.json moved to
0.5.0. This adds the canonical release path that makes the catalog side
impossible to do wrong.

scripts/release-plugin.mjs:
- Pure planner planRelease() — given the catalog + observed plugin state +
  target version, computes verdict (READY/NOOP/BLOCKED), the bumped marketplace
  object, and the commit subject. REFUSES (BLOCKED) unless plugin.json == README
  badge == target AND the vX.Y.Z tag exists, so a READY plan is check-versions-
  green by construction. Reuses normalizeVersion/classifyPlugin from
  check-versions.mjs (no duplicated rules).
- I/O shell: dry-run by default; --write bumps the ref + re-runs the gate;
  --commit/--push apply; --create-tag mints+pushes a missing plugin tag first.
- 10/10 unit tests (scripts/release-plugin.test.mjs); check-versions 9/9 still green.
- Dogfooded: linkedin-studio → NOOP (already pinned v0.5.0); ms-ai-architect →
  BLOCKED (v1.16.0 in plugin.json was never tagged — refuses to publish it).

Also: fix the stale LinkedIn Studio README label v0.4.0 -> v0.5.0 (loose end of
the v0.5.0 release), and document the canonical release path in CLAUDE.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 19:40:47 +02:00
a8d44e6d3f feat(scripts): add marketplace version-consistency gate
scripts/check-versions.mjs cross-checks every plugin in marketplace.json against
its sibling repo:
- catalog ref must resolve to a real git tag    → ERROR (dangling = install breaks)
- plugin.json version == README version-badge    → ERROR (internal corruption)
- catalog ref == plugin.json version             → WARN (catalog lags / unreleased bump)
- sibling repo missing                           → SKIP

Exit 1 on any ERROR; --strict also fails on WARN. Pure classifier covered by
check-versions.test.mjs (9 tests, node --test). Documented in CLAUDE.md
catalog-maintenance. Run before committing any ref change.

Current run: 8 OK, 2 WARN (linkedin-studio, ms-ai-architect — catalog ref lags an
untagged plugin.json bump), 0 ERROR.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ter3E2JSi1Khgmuf2kady8
2026-06-19 23:19:22 +02:00
e84dffd2b0 chore(marketplace): thin catalog to manifest + docs (polyrepo migration complete) 2026-06-18 10:00:47 +02:00
653db4785e feat(migration): sync-design-system --source + --check for DS re-home [skip-docs] 2026-06-17 12:34:01 +02:00
Kjell Tore Guttormsen
f4aa1ed58f feat(marketplace): add sync-design-system.mjs script
Vendors shared/playground-design-system/ into a plugin's
playground/vendor/playground-design-system/ tree so each plugin stays
standalone (no marketplace-rot dependency at runtime).

Features:
- Generates MANIFEST.json with SHA-256 per file, source commit hash, sync date
- Drift detection: refuses overwrite if vendored file changed since last sync
- --force flag to override drift
- Injects "DO NOT EDIT" header into copied CSS files
- Pure Node.js, zero npm deps (uses fs.cp from Node 16.7+)

Usage: node scripts/sync-design-system.mjs <plugin-name> [--force]
2026-05-03 12:24:23 +02:00