Skip to content

fix(deps): unpin revoked eslint 10.4.1 → ^10.4.0 + sync root lockfile (unblocks #673) - #676

Merged
artyhoo merged 1 commit into
stagingfrom
chore/regen-lockfile-presets
Jun 22, 2026
Merged

fix(deps): unpin revoked eslint 10.4.1 → ^10.4.0 + sync root lockfile (unblocks #673)#676
artyhoo merged 1 commit into
stagingfrom
chore/regen-lockfile-presets

Conversation

@artyhoo

@artyhoo artyhoo commented Jun 22, 2026

Copy link
Copy Markdown
Owner

Problem

`eslint@10.4.1` (pinned in `packages/core/package.json`) was unpublished from npm — version-API returns 404, though the tarball is still CDN-served. Therefore any root-level `npm install` / `npm ci` / `--package-lock-only` failed with `ETARGET No matching version for eslint@^10.4.1`, leaving the root lockfile unable to regenerate. As a result the new workspace packages `preset-react-native` (#646) and `preset-react-spa` (#661) were never added to `package-lock.json`.

`guard-liveness-fullsweep` is the only required check that runs root `npm ci` and it triggers only on `pull_request → branches: [main]`, so the drift surfaced just now on promote PR #673 — red on `Missing: @rules-as-tests/preset-react-native@0.1.0 from lock file`. Other jobs use `npm install` (lenient) and masked it.

Fix

  • `packages/core/package.json`: `eslint ^10.4.1 → ^10.4.0` (latest existing in the 10.4 line; API stable vs 10.4.1).
  • Regenerated root `package-lock.json` → `preset-react-native`/`preset-react-spa` + their deps now present (458 → 770 resolved pkgs; the bulk is the expo/metro tree react-native legitimately pulls).

Verification (local)

  • `npm ci` → rc=0 (lockfile ↔ package.json consistent — the exact check guard runs).
  • `npm --prefix packages/core run guard-liveness:fullsweep` → GREEN (v1 ESLint 8/0, v1.5 cmd 3/0, v3 structural pass; `FULLSWEEP_JSON={"failed":false}`).
  • `lockfileVersion` stays 3 (node20/npm10 CI-compatible); linux-x64 bindings present in lock.
  • pre-push: `packages/core` suite 226/226 passed.

Why this unblocks #673

Once merged to `staging`, #673 (staging→main) picks up the synced lockfile; guard's root `npm ci` clears the structural sync-check.

§1.7 Forward-check: complies with `no-paid-llm-in-ci` (zero API calls); this is a dependency-pin + mechanical lockfile sync, not a discipline-bearing artifact. Backward-check: codifies the fix for revoked-eslint drift; touches no rule/principle — change is `packages/core/package.json:48` + lockfile only.

Prior-art: skipped — dependency version-pin fix (revoked eslint 10.4.1 → existing 10.4.0), no new capability introduced.

… (preset-react-native/spa)

eslint 10.4.1 was unpublished from the npm registry (version-API → 404; tarball
still served by the CDN), so any root-level `npm install` / `npm ci` /
`--package-lock-only` failed with ETARGET, leaving the root lockfile unable to
regenerate. Consequently the new workspace packages preset-react-native (#646)
and preset-react-spa (#661) were never added to package-lock.json.

guard-liveness-fullsweep (pull_request → main only) is the one required check
that runs `npm ci` on the root workspace, so the drift surfaced only on promote
PR #673 — red on "Missing: @rules-as-tests/preset-react-native from lock file".

Fix: pin packages/core eslint to ^10.4.0 (latest existing in the 10.4 line; API
stable vs 10.4.1) and regenerate the root lockfile. Verified locally: npm ci
rc=0; guard-liveness:fullsweep GREEN (v1 ESLint 8/0, v1.5 cmd 3/0, v3 structural
pass); lockfileVersion stays 3 (node20/npm10 CI-compatible); linux-x64 bindings
present; package count 458→770 (preset deps).

Prior-art: skipped — dependency version-pin fix (revoked eslint 10.4.1 → existing 10.4.0) plus mechanical lockfile sync; no new capability introduced.
@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatedtypescript-eslint@​8.62.0 ⏵ 8.59.2100 +11007498100

View full report

@socket-security

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: npm @typescript-eslint/eslint-plugin is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: package-lock.jsonnpm/typescript-eslint@8.59.2npm/eslint-config-expo@56.0.4npm/@react-native/eslint-config@0.86.0npm/@typescript-eslint/eslint-plugin@8.59.2

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@typescript-eslint/eslint-plugin@8.59.2. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm string.prototype.trimend is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: package-lock.jsonnpm/eslint-config-expo@56.0.4npm/eslint-plugin-jsx-a11y@6.10.2npm/eslint-plugin-react-native-a11y@3.5.1npm/eslint-plugin-react@7.37.5npm/string.prototype.trimend@1.0.9

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/string.prototype.trimend@1.0.9. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@artyhoo
artyhoo merged commit 632a363 into staging Jun 22, 2026
44 checks passed
artyhoo added a commit that referenced this pull request Jun 22, 2026
…g release) (#673)

* fix(hooks): pre-push delivery completeness on pnpm monorepo (#635, #636) (#638)

* fix(hooks): probe tsx runtime in pre-push dispatcher, degrade to bash fallback instead of crash (#636)

* fix(install): refresh ships hooks/package.json type:module marker in do_refresh (#635)

* fix(templates): shipped CI templates pass the framework's own zizmor gate + regression guard + honest brownfield hint (#637) (#639)

* fix(templates): harden shipped CI workflows to pass zizmor (artipacked + unpinned-uses + template-injection) (#637)

Both shipped CI templates failed the framework's own zizmor pre-push gate,
blocking a fresh consumer on the framework's own scaffolding. Apply three
hardening transforms to each:
- artipacked: persist-credentials: false on every actions/checkout step
- unpinned-uses: SHA-pin every action uses: (KEEP the # vX.Y.Z comment)
- template-injection: use runner GITHUB_BASE_REF env in the Stryker run-block
  instead of interpolating ${{ github.base_ref }} into shell

The actions/cache key: interpolation of github.base_ref is left unchanged
(not a shell context — zizmor only flags the run:-block one).

Prior-art: skipped — shipped-template security hardening (persist-credentials + SHA-pin + injection fix), no new capability

* fix(pre-push): guard shipped CI templates against zizmor drift + honest brownfield fix hint (#637)

Extend the pre-push zizmor gate (and the audit-self.yml CI backstop) to also
scan the three shipped CI templates, so they cannot silently drift past the
gate again after cab1a0e fixed them. The template guard uses an INVERTED
existsSync direction vs sections 3c/3d: it fires in the maintainer repo (where
the templates exist) and no-ops on consumers (who receive the rendered
.github/workflows/ci.yml, not templates/).

Also add an HONEST brownfield fix hint to every zizmor failure via a new
optional requireTool(failHint) param. The hint spells out that
`zizmor --fix=all` auto-fixes only artipacked + template-injection and that
unpinned-uses is NOT auto-fixable (needs SHA-pinning) — a "just run --fix" hint
would mislead the consumer.

Prior-art: skipped — extend existing zizmor gate to shipped templates + brownfield fix hint, no new capability

* fix(install): guarantee tsx resolves from workspace root for the pre-push TS hook (#636) (#640)

GH #636 fix (a) — the install-time half, complementing #638's runtime degradation half.

The pre-push dispatcher runs `node --import tsx/esm <root>/packages/core/hooks/pre-push.ts`
from the repo ROOT, so tsx must resolve THERE. tsx is in CORE_DEVDEPS, but on a pnpm monorepo
a tsx living in a sub-package is NOT hoisted to the root, so the TS hook silently degrades to
the bash fallback (#638 made that degradation graceful instead of a crash). This closes the gap
at install time.

New install.sh step (§8b, after the §8 dep-install, before §6b-bis-L2 R2 wiring — so on --full
the bulk install has already landed tsx via -w and the probe is a no-op, and tsx is ready before
R2 wiring needs it). Reuses the #638 dispatcher probe expression and the existing detect_pm SSOT:

  tsx already resolvable from root   -> nothing
  tsx missing, --full                -> install at the workspace root, silently
  tsx missing, interactive (tty)     -> offer [y/N]; on yes install, EVEN without --full
  tsx missing, refused / non-tty     -> WARN: pre-push runs REDUCED (critical-only); print the
                                        exact PM-aware enabling command

tsx ONLY (the hook runtime) — ts-morph / R2 untouched. Idempotent: the probe short-circuits when
tsx already resolves. PM-aware command is built once as the SSOT for both the install and the warn
(no #two-prompts-drift). No new dependency (tsx already in CORE_DEVDEPS).

Test: tests/install-sh/gh-636-ensure-tsx-root.test.sh (fake PM on PATH, real node probe, python
pty for the interactive arm; NO network). Covers --full silent install, interactive-yes (no --full),
non-interactive WARN, PM/workspace command derivation, and a load-bearing paired-negative (tsx
present -> no warn/install). Wired into audit-self.yml (meta-all-wired gate).

Closes #636.

* fix(tests): portable in-place sed in c1-wiring (BSD/GNU sed parity) (#641)

GNU-style `sed -i 's/…/…/' file` errors on BSD sed (macOS): the substitute
expression is consumed as the backup-suffix arg, the file path is parsed as a
sed command, the command fails, and the file is never mutated. This made the
C1-548-neg paired-negative arm VACUOUS on macOS (spurious FAIL=1) while Linux
CI (GNU sed) stayed green and hid it.

Replace with a POSIX temp-file + mv in-place edit — identical behaviour on
both seds, no OS branching.

Verified: macOS PASS=12 FAIL=0 (was 11/1). Test-only; install.sh and
deps-hash-check.sh behaviour untouched.

* fix(install): resolve ts-morph from consumer cwd in R2 Layer-2 wirer (#642)

Resolve ts-morph from the consumer cwd (createRequire + pathToFileURL) in the R2 Layer-2 wirer; declare ts-morph@^24.0.0 as a packages/core devDep (matches setup.sh:336 / SSOT #131, fixes the latent undeclared audit-r4.ts:11 import); add fail-first Fixture X (in-pattern with f16/f17).

Prior-art: prior-art-evaluations.md#131 (ts-morph REUSE — declaring an already-imported engine; SSOT pre-cleared, no new capability introduced)

Closes #642

* docs(orchestrator): preserve #642 R2-wirer-fix kickoff (Phase -1 audit trail) (#645)

Audit-trail for the #642 fix (shipped via #643, merged to staging as e1bb9c4):
the orchestration kickoff capturing the 3-round Phase -1 cold-review reasoning
(B->A CI-strategy flip away from a runtime-registry-install anti-pattern; the
ts-morph ^24 vs npm-latest-28 version-drift catch) and the TDD cross-checkout
fixture design. The fix itself already landed; this preserves the design record
per the project's kickoff-tracking convention. Passes principle-12 citation
(ai-laziness-traps + T-642-A/B/C domain traps).

Prior-art: skipped — docs only (orchestration kickoff audit-trail under .claude/orchestrator-prompts/, not under packages/, no dependency), no new capability

* docs(#646): multi-stack hybrid design + react-spa R-phase (#648)

* docs(specs): multi-stack hybrid design (epic #646)

Records the hybrid decision resolving the Stage-1 preset-vs-principle fork
surfaced by the reviewer pass on #646: Stage 1 curated react/react-native
presets now + Stage 2 generate-path as committed next umbrella, with a
binding sequencing-commitment (phase-5 v2-trigger research-patch at Stage 1
close) so the bridge cannot strand 3 hand-maintained presets.

Prior-art: skipped — design spec doc, no new capability (brainstorm output for epic #646).

* docs(research): react-spa rule prior-art R-phase + SSOT #136-141 (#646 Stage 1)

R-phase verdicts gating react-spa capability commits: A1-A3 (eslint-plugin-react /
react-hooks v6 / jsx-a11y) ADOPT (own-stack backfill); B (no-business-logic) ADAPT
not BUILD (boundary REUSE); C (require-error-boundary) T16 split BUILD-presence +
ADOPT-usage. Primary-plugin fork resolved to eslint-plugin-react (dual-impl
discipline); @eslint-react WATCHLIST (#141). DeepWiki down → WebSearch-grounded,
re-probe flagged before B/C commits.

Prior-art: skipped — R-phase research-patch + SSOT register entries, no new capability (these verdicts gate future capability commits per build-first-reuse-default §3).

* chore(#646): re-trigger CI on corrected PR-body §1.7 headings

No content change — forces a fresh SHA so the stale failed §1.7 check-run
(from a re-run on the pre-fix PR body) detaches from PR head.

Prior-art: skipped — empty re-trigger commit, no capability, no file change.

* docs(research): react-native rule prior-art R-phase + SSOT #142-148 (#646 Stage 1) (#650)

* docs(research): react-native rule prior-art R-phase + SSOT #142-148 (#646 Stage 1)

R-phase verdicts for the react-native preset (epic #646 Stage 1, sibling of the
react-spa R-phase in #648). All three issue BUILD hypotheses resolve to
ADAPT/ADOPT/REJECT — zero clean BUILDs survive the prior-art consult:

- no-web-only-globals          -> ADAPT  (REUSE no-restricted-globals + eslint-config-expo globals; BUILD-residue YAGNI)
- require-stylesheet-create    -> ADOPT  (eslint-plugin-react-native/no-inline-styles)
- prefer-flashlist-over-flatlist -> REJECT the BUILD (no upstream rule; perf-opinion not a correctness invariant; 3-way FlatList/FlashList/LegendList landscape)

ADOPT layer: eslint-config-expo (Expo baseline; forbids web globals), @react-native
+ @callstack configs (bare-RN baseline), eslint-plugin-react-native (RN style rules,
WATCHLIST low-maint), eslint-plugin-react-native-a11y (T16: RN a11y != jsx-a11y,
supersedes #138 for RN).

DeepWiki was UP (vs the react-spa sibling, where it was down) -> negative-existence
claims are source-repo-verified. SSOT rows #142-148 continue after #648's #136-141.

Depends on #648 (design spec + #136-141, branch claude/hopeful-clarke-6517e0, OPEN).
Merge #648 first; the prior-art-evaluations.md tail then reorders to

Prior-art: skipped -- R-phase research-patch; records ADOPT/ADAPT/REJECT verdicts + SSOT rows, builds no capability artifact.

* docs(research): flag eslint-config-expo web-globals claim for I-phase verify (#646 rn R-phase)

Adds a §5 open-item: the eslint-config-expo window:false / globals.browser-off
claim (candidate B / SSOT #146) is DeepWiki-sourced, not file-verified in this
R-phase; the core no-restricted-globals denylist is the independent fallback.

(Lands on a fresh SHA -> clears the stale §1.7 check run frozen against the
pre-rebase PR body; the gate snapshots github.event.pull_request.body at
opened-event time, so the original opened-run stays red on the old SHA.)

Prior-art: skipped -- R-phase research-patch addendum; records a verify-item, builds no capability.

* docs(ssot): #146 — surface expo-globals verify caveat (consistency with R-phase §5)

SSOT row #146 (no-web-only-globals / ADAPT) now carries the same I-phase
verify-item just added to the research-patch §5: the eslint-config-expo
window:false claim is DeepWiki-sourced, not file-verified; no-restricted-globals
is the independent fallback. Keeps the SSOT row honest about its unverified leg.

(Touches prior-art-evaluations.md -> re-runs the full path-filtered CI suite,
incl. the §1.7 gate, on a fresh SHA — the prior docs-only addendum skipped them.)

Prior-art: skipped -- SSOT honesty edit; adds a verify caveat to an existing row, builds no capability.

* docs(research): correct falsified web-globals claim in react-native R-phase SSOT #142/#146 (#646 Stage 1) (#651)

The react-native R-phase (#650, merged) recorded that `eslint-config-expo`
"sets window:false and forbids globals.browser web-only globals -> closes
candidate B (no-web-only-globals) for free on the Expo path". That claim was
DeepWiki-prose, flagged by the patch's own §5 open-item-4 as "not file-verified".

File-verified now (the open-item's intended I-phase check):
- gh expo/expo:packages/eslint-config-expo/flat/default.js:23,38 — the globals
  block SPREADS `...globals.browser` and sets `window: false`, ships no
  `no-restricted-globals`.
- ESLint globals docs — `false` ≡ "readonly" ≡ a DEFINED/readable global;
  only `"off"` disables. So `window: false` whitelists, it does not forbid.

=> The config WHITELISTS web-only globals (the opposite of forbidding) and does
NOT close candidate B. This is the same `whitelists ≠ forbids` error the patch
already caught for #144's `eslint-plugin-react-native` env claim — latent in the
expo claim only because it was never file-verified. DeepWiki even returned two
opposite answers on the semantics across probes (reliable for source contents,
not for ESLint semantics — §1.10 spec-over-prose).

Verdicts unchanged: ADOPT #142, ADAPT #146. Corrected the load-bearing
consequence — the Expo baseline does NOT close candidate B; core
`no-restricted-globals` is the PRIMARY forbid mechanism, not a fallback.
Discharges react-native R-phase §5 open-item-4. Brings #142/#146 into agreement
with the already-correct #144 row.

Prior-art: skipped — docs-only correction of a falsified prior-art claim in #142/#146 (web-globals semantics: expo whitelists, not forbids); no new capability, no dep, no code.

* fix(recovery): restore #642/#635/#636/#645 reverted by #648 staged-index contamination (#653)

* fix(recovery): restore #642/#635/#636/#645 reverted by #648 staged-index contamination

PR #648 (merge 691f44f) shipped 3 legit docs (multi-stack hybrid design +
react-spa R-phase + SSOT), but its `--allow-empty` CI re-trigger sub-commit
captured 16 foreign staged-WIP files, silently reverting merged fixes:
- #642 — R2 wirer cwd-anchored ts-morph resolution (wire-eslint-r2.ts back to bare import)
- #635 — hooks type:module refresh (install.sh do_refresh + pre-push.ts)
- #636 — tsx-at-root probe (install.sh §8b) + DELETED both harness tests (gh-636-*.test.sh)
- #645 — the #642 R2-wirer kickoff (deleted)
plus package.json/lock dropping ts-morph, CI templates, husky hooks.
No restorative commit followed → contamination live on staging.

Restores all 16 contaminated files from dd05105 (parent of 691f44f, the
pre-contamination state). #650/#651 touched none of the 16 (docs-only), so this
is a clean reversal; the 3 legit docs from #648 and #650/#651 research are
untouched. Mirrors the contamination exactly: +817 lines = the 817 it deleted.

Verified: wire-eslint-r2.test.ts vitest passes; #642 createRequire, #636
tsx-at-root + both gh-636 tests, #645 kickoff all confirmed restored.

Prior-art: skipped — recovery of an accidental revert (staged-index contamination), no new capability; the package.json ts-morph line is restoration of a pre-existing dependency removed by the contamination, not a new dependency.

* ci(zizmor): pin zizmor==1.25.2 — unpinned install + 1.26.1 bump red-ed all PRs

`pip install zizmor` (unpinned) installed 1.26.1, which tightened the
adhoc-packages audit and surfaced 5 pre-existing LOW findings in our own
self-CI workflows (audit-self.yml npm install --prefix / git init;
framework-self-template-render.yml:35) that 1.25.2 did not flag. ci-success
`needs: zizmor` → every fresh PR went red (recovery #653 included), with no
code cause. Pin to the last known-green 1.25.2 for deterministic CI; a
zizmor bump is now a deliberate edit, not a silent CI break.

The 1.26.1 adhoc-packages findings (5 LOW, self-CI only — not shipped supply
chain) are deferred to a separate triage (suppress-config vs fix), off the
critical path here.

Prior-art: skipped — CI tooling version pin (one-line determinism fix), no new capability, no new dependency.

* fix(install): R2 Layer-2 wirer registers the plugin so the wired config loads (#644) (#647)

* docs(spec): R2 Layer-2 wirer plugin-registration fix design (#644)

* docs(plan): R2 Layer-2 wirer plugin-registration fix implementation plan (#644)

* docs(plan): address Phase -1 cold-review — BLOCKER eslint resolve via package.json + 2 MAJOR + P2 assertion (#644)

* feat(install): R2 wirer supports self-contained (plugin-registering) variant (#644)

* feat(install): compute relative customRules import path for self-contained wire (#644)

* feat(install): resolveAndWire orchestrates bare→self-contained via injected probe (#644)

* feat(install): probeViaEslint default probe resolves eslint from consumer cwd (#644)

* feat(install): apply-mode wiring goes through resolveAndWire probe loop (#644)

* test(install): W3 loadability gate — wired config must load in ESLint (#644)

P1 (plugin-less base) + P2 (plugin-registering base) run the wirer in apply mode and
assert eslint --print-config LOADS (rc 0). Red-then-green verified: pre-fix wirer makes
P1 fail with the exact 'could not find plugin' error on eslint 9.39.4 (confirms v9 parity,
spec R1). _mk_consumer sets type:module (the .ts plugin-import precondition Layer-1 already
needs) + installs ts-morph (wirer resolves it from cwd). Fixture X switched to --diff to
isolate its ts-morph cross-checkout concern (#642) from the new apply-mode probe (#644).

* fix(install): manual R2 snippets register the plugin (self-contained) (#644)

* test(install): correct the false 'print-config in consumer-pipeline' comment (#644)

* fix(install): load .ts eslint config on Node 20 via tsx loader (#644)

The shipped eslint config (.mjs) imports the eslint-rules-local/index.ts barrel; on the
shipped .nvmrc (Node 20, no native type-stripping) plain eslint fails with
ERR_UNKNOWN_FILE_EXTENSION — breaking both the R2 wirer's self-contained escalation AND
Layer-1's own lint. Preserve the consumer's Node choice (don't force a bump): run eslint
through the tsx loader (already a CORE_DEVDEP + the pre-push-hook pattern; build-first-reuse).
- probeViaEslint: node --import tsx (when tsx resolvable; else plain node, Node>=22.18).
- consumer lint/lint:fix scripts (install.sh + setup.sh): NODE_OPTIONS=--import tsx.
- W3 harness: _mk_consumer installs tsx; P1/P2 validate via the loader.
Verified on simulated Node 20 (--no-experimental-strip-types): loader -> rc 0, plain -> ERR.

* docs(#646): I-phase kickoffs for react-spa + react-native presets (Stage 1) (#655)

Two parallel-dispatch I-phase kickoffs for the merged #646 Stage 1 R-phase
(curated bridge). Each is self-contained for an autonomous aif worker.

- react-spa: 3 ADOPT (eslint-plugin-react/hooks-v6/jsx-a11y/boundaries) + 1
  BUILD (require-error-boundary presence-check, #140 — upstream covers usage
  not presence; AST-over-grep, narrow in-file scope per SSOT #115 precedent).
- react-native: Expo-only (orchestrator scope decision 2026-06-22), 4 ADOPT +
  1 ADAPT (no-restricted-globals denylist #146 — Expo baseline whitelists web
  globals per #651) + 1 REJECT (FlashList #148). Zero from-scratch BUILD = a
  build-first-reuse success per design-spec section 8.
- Both fence off install.sh / packages/core/principles/* / prior-art-evaluations.md
  / preset-next-15-canonical (install+principle wiring is a separate follow-up
  task) -> no egress collision between the two parallel workers.
- Phase -1 cold-reviewed (2x Opus, read-only): all load-bearing SSOT claims
  #136-148 + the #651 whitelist->denylist correction verified TRUE; MAJOR/MINOR
  findings folded in (peerDeps, package.json mirror, egress-guards, T-MS test cases).

* docs(#646): revise react-native kickoff to ship BOTH Expo + bare-RN baselines (#656)

Operator override (2026-06-22): Expo-only -> both baselines. The project goal
is multi-stack + a factory for any stack (README#why-this-exists); covering the
Expo AND bare-RN consumer up front serves that goal directly, weighted over the
earlier YAGNI lean.

- Ship eslint-config-expo/flat (#142) AND @react-native/eslint-config (#143) as
  two selectable baselines over a shared rn-common layer (style/a11y/no-restricted
  -globals denylist). Shipping #143 resolves the open DECISION-NEEDED the R-phase
  explicitly left to the operator (reviewer-discipline §2 path) — not a contradiction.
- ZERO custom rules unchanged (build-first-reuse success); two-baseline = maximal REUSE.
- Phase -1 cold-reviewed (1x Opus): GO, 0 BLOCKER/0 MAJOR; #143 ship-vs-swap
  consistency verified against R-phase + SSOT; egress fences still airtight; T-gate
  4 distinct T-numbers. 1 MINOR folded in (denylist is a separate rule key, fires
  additively — not a key-collision to 'win').

* docs(654): kickoff for ci-adhoc-install-fix umbrella (option 2b) (#657)

Dispatch input for aif autonomous execution of GH issue #654 option 2 (fix the
5 zizmor adhoc-packages findings via npm ci + re-pin zizmor 1.26.1 + internalise
unpinned-CI-tool-install as a Class-A rule with an earliest-channel pre-push check).

Kickoff passed Phase -1 (2x Opus round 1 caught 3 BLOCKER + 5 MAJOR; round 2 GO).
Must reach staging before aif sees it (kickoff-staging-placement.md).

* docs(#646): integration kickoff — install.sh wiring + principle 05/09 (Stage 1 Phase B) (#658)

Single integration task owning the SHARED files both presets need: install.sh
arg-parser/guard/menu/auto-detect/SHIPPED_DOCS/copy-blocks/dev-deps, plus
principle 09 (REQUIRED_HEADER_DOCS + drift-test count 19->25) and principle 05
(manifest-rendered-vs-static, determined by grep on the render-rules provenance marker).

- Binding dispatch dependency: ONLY after BOTH preset PRs merge to staging (copy-blocks
  reference real preset files; principle 09 registers real RULES.md/ARCHITECTURE.md).
- Phase -1 cold-reviewed (1x Opus, REVISE->GO round 2): 3 MAJOR fixed (SPA eslint
  basename = eslint.config.react.mjs not -spa suffix; RN ships no playwright.config.ts;
  principle-05 rendered-vs-static via deterministic grep) + 4 MINOR (count->25 explicit,
  guard message, detect order RN-before-spa, don't-touch-react-next-blocks). M3 grep
  marker independently re-verified against next RULES.md:13.

* docs(#646): stage-2-generate-path umbrella kickoff (Stage 2) (#659)

* docs(#646): Stage 2 generate-path brainstorm spec

Records the brainstorm feasibility framing for epic #646 Stage 2 (LLM
generate-path). Corrects the Stage-1 design-doc claim that L4 does not
exist (packages/core/validator/ ships gates 1/2/4/6, Phase 7); reframes
Stage 2 as swap-curated-inputs-for-LLM at L2/L3 with L4/L5 unchanged.
Lands reasoned recommendations on both strategic forks (entry point =
L3-live-first vs existing L4; scope = Path A only) plus the mandatory
build-vs-reuse gate (aif-evolve / OMC) before any live-LLM build.

Status: proposed — pending operator ratification. Not a kickoff.

Prior-art: skipped — docs-only brainstorm spec, no new capability (no dependency, no code under packages/).

* docs(#646): stage-2-generate-path umbrella kickoff (Stage 2)

Operationalises the 2026-06-22 brainstorm spec into a dispatch-ready umbrella kickoff for #646 Stage 2 (the LLM generate-path). Stage 0 = build-vs-reuse R-phase (aif-evolve / OMC) is BLOCKING — no L2/L3 code lands before its verdict. Reconstructed onto staging cleanly, without the cross-umbrella plugin commits that had tangled the original brainstorm branch.

Prior-art: skipped — docs-only umbrella kickoff, no new capability (no dependency, no code under packages/).

* style(#646): prettier-format stage-2 generate-path spec

Hooks were bypassed on the original brainstorm commit (3da9578, core.hooksPath=/dev/null); this applies the prettier pass it missed — italic-emphasis normalisation (*x* → _x_) and table-column padding. No content change.

Prior-art: skipped — prettier formatting only, no new capability.

---------

Co-authored-by: Art <devartyhoo@gmail.com>

* CC plugin packaging (S0–S1 + umbrella) — superpowers-style hybrid (#660)

* docs(plugin): CC plugin packaging spec + plan + umbrella kickoff

Spec/plan/kickoff for packaging rules-as-tests-aif as a CC-first plugin
(in-repo marketplace, soft/hard layer split, hybrid install seam,
recursive self-test). Kickoff carries ai-laziness-traps T-enumeration
(7 canonical + 2 domain) per ai-laziness-traps.md §3.

* feat(plugin): plugin.json + in-repo marketplace + run-hook.cmd skeleton (S1)

Task 1 of the plugin-packaging umbrella. In-repo marketplace source
verified as relative path string ("./plugin") against CC docs.
run-hook.cmd adopted from obra/superpowers, audited (T13): its missing-arg
guard is Windows-only — Unix path exits non-zero, asserted accordingly.
Both acceptance tests green (manifest-valid 5/5, run-hook 2/2).

Prior-art: skipped — new files outside packages/, no new dependency; not a
capability commit per CLAUDE.md mechanical definition. run-hook.cmd is an
ADOPT from superpowers (MIT), recorded for the S0 SSOT entry.

* docs(plugin): REVISE round after Phase -1 cold-review — dispatch-ready kickoff

Phase -1 adversarial cold-review (CLAUDE.md meta-orchestrator self-review
obligation) returned REVISE; one round applied:
- S0 SSOT entries #149-152 (superpowers ADOPT, run-hook ADOPT-audited, using-* ADAPT, CC-docs REFERENCE) - closes the S1 trailer forward-reference (B1).
- kickoff records current state: S0+S1 DONE (63b344c, fa37b2b), dispatch starts S2 (B1/B2).
- hook count 15 -> 14, all 14 enumerated in plan Task 2 (B3).
- compliance-verifier: stop claiming 'per extension.json'; S4 adds it (B4).
- principle 21 -> full filenames (M1); dispatcher/pipeline paths (M3); tool-bootstrapping OUT of v1 (m1).

Prior-art: prior-art-evaluations.md#149-152 (S0 SSOT entries; superpowers plugin/run-hook ADOPT, using-superpowers ADAPT, CC docs REFERENCE).

* fix(plugin): restore 654 kickoff erroneously deleted by S0 commit

Commit 63b344c (S0 spec+plan+kickoff) deleted .claude/orchestrator-prompts/654-ci-adhoc-install-fix/kickoff.md — a foreign file of the active 654 ci-adhoc-install-fix umbrella (present on origin/staging, blob c9019eb). Likely a stale staged-index artifact at branch-creation time. Restored byte-for-byte from origin/staging so this PR does not drive-by-delete another umbrella's kickoff. No plugin-packaging content affected.

Prior-art: skipped — doc restoration, no new capability or dependency.

---------

Co-authored-by: Art <devartyhoo@gmail.com>

* feat(preset-react-spa): React-19 SPA preset (#646 Stage 1) (#661)

* docs(plugin): CC plugin packaging spec + plan + umbrella kickoff

Spec/plan/kickoff for packaging rules-as-tests-aif as a CC-first plugin
(in-repo marketplace, soft/hard layer split, hybrid install seam,
recursive self-test). Kickoff carries ai-laziness-traps T-enumeration
(7 canonical + 2 domain) per ai-laziness-traps.md §3.

* feat(plugin): plugin.json + in-repo marketplace + run-hook.cmd skeleton (S1)

Task 1 of the plugin-packaging umbrella. In-repo marketplace source
verified as relative path string ("./plugin") against CC docs.
run-hook.cmd adopted from obra/superpowers, audited (T13): its missing-arg
guard is Windows-only — Unix path exits non-zero, asserted accordingly.
Both acceptance tests green (manifest-valid 5/5, run-hook 2/2).

Prior-art: skipped — new files outside packages/, no new dependency; not a
capability commit per CLAUDE.md mechanical definition. run-hook.cmd is an
ADOPT from superpowers (MIT), recorded for the S0 SSOT entry.

* feat(preset-react-spa): React-19 SPA preset — 3 ADOPT + require-error-boundary (#646 Stage 1)

Built autonomously by aif task 547d9114; harvested selectively (preset dir only —
excluded base-contamination: hook-mods, foreign kickoff deletions, root lock).

- 3 ADOPT: eslint-plugin-react (#136), react-hooks v6 (#137), jsx-a11y (#138),
  eslint-plugin-boundaries (#139) — wired in self-contained eslint.config.react.mjs (no @next).
- 1 BUILD: require-error-boundary (#140) — real AST rule (ESLintUtils.RuleCreator +
  AST_NODE_TYPES JSX visitor, not string-grep) + paired valid/invalid test (principle 02).
- verify-before-harvest: 14 preset files present, out-of-scope clean (no install.sh/
  principles/prior-art edits), AST + paired-test sanity passed.
- install.sh wiring + principle 05/09 registration = separate integration task (#658).

Prior-art: prior-art-evaluations.md#136/#137/#138/#139 (ADOPT upstream react lint plugins) + #140 (BUILD require-error-boundary, presence-gap confirmed, no upstream analog) + #141 (WATCHLIST @eslint-react). SSOT rows recorded in merged R-phase #648.

---------

Co-authored-by: Art <devartyhoo@gmail.com>

* feat(preset-react-native): Expo + bare-RN preset, both baselines (#646 Stage 1) (#662)

* docs(plugin): CC plugin packaging spec + plan + umbrella kickoff

Spec/plan/kickoff for packaging rules-as-tests-aif as a CC-first plugin
(in-repo marketplace, soft/hard layer split, hybrid install seam,
recursive self-test). Kickoff carries ai-laziness-traps T-enumeration
(7 canonical + 2 domain) per ai-laziness-traps.md §3.

* feat(plugin): plugin.json + in-repo marketplace + run-hook.cmd skeleton (S1)

Task 1 of the plugin-packaging umbrella. In-repo marketplace source
verified as relative path string ("./plugin") against CC docs.
run-hook.cmd adopted from obra/superpowers, audited (T13): its missing-arg
guard is Windows-only — Unix path exits non-zero, asserted accordingly.
Both acceptance tests green (manifest-valid 5/5, run-hook 2/2).

Prior-art: skipped — new files outside packages/, no new dependency; not a
capability commit per CLAUDE.md mechanical definition. run-hook.cmd is an
ADOPT from superpowers (MIT), recorded for the S0 SSOT entry.

* feat(preset-react-native): Expo + bare-RN preset, both baselines (#646 Stage 1)

Built autonomously by aif task 808f858a (rebuilt for both-baselines per operator override);
harvested selectively (preset dir only, excluding base-contamination + preset-local lock).

- BOTH baselines: eslint-config-expo/flat (#142, Expo) + @react-native/eslint-config (#143,
  bare-RN) over a shared eslint.config.rn-common.mjs layer.
- ADOPT plugins: eslint-plugin-react-native (#144/#147), react-native-a11y (#145).
- ADAPT: no-restricted-globals denylist (#146) — present in all three configs (forbids web
  globals the Expo baseline whitelists, per #651 correction).
- REJECT: FlashList custom rule (#148). ZERO eslint-rules/ (build-first-reuse success).
- No playwright.config.ts (RN web-only, correctly omitted).
- verify-before-harvest: both baseline configs + rn-common + denylist present; eslint-rules
  absent as designed; out-of-scope clean.
- install.sh wiring + principle 05/09 = separate integration task (#658).

Prior-art: prior-art-evaluations.md#142/#143 (ADOPT Expo + bare-RN baselines) + #144/#145/#147 (ADOPT react-native lint plugins) + #146 (ADAPT no-restricted-globals denylist) + #148 (REJECT FlashList BUILD). SSOT rows recorded in merged R-phase #650/#651.

---------

Co-authored-by: Art <devartyhoo@gmail.com>

* feat(plugin): S2 — relocate shippable hooks (per-hook path-class audit) (#664)

* feat(plugin): relocate shippable hooks to plugin payload (path-class audit) (S2)

Per-hook triage of all 14 .claude/hooks/*.sh (docs/superpowers/plans/plugin-hook-triage.md): SHIP=2, INTERNAL=12. Each hook's self-declared @dual-pair/@cc-only-rationale marker is the primary evidence. Relocate inject-matching-rule into plugin/hooks/ as an extensionless, context-agnostic script: every path it touches is project-data, resolved via ${CLAUDE_PROJECT_DIR} (T-PLUG-A — no blanket env swap; a swap to ${CLAUDE_PLUGIN_ROOT} would make it read the plugin's non-existent rules instead of the consumer's). hooks.json wires PostToolUse→inject-matching-rule via run-hook.cmd; SessionStart→session-start deferred to S3. inject-session-bootstrap→session-start, also S3. check-doc-authority stays INTERNAL (needs the framework TS toolchain); deps-hash-check already ships via install.sh.

Verified: tests/plugin/hook-paths.test.sh 6/6; throwaway-consumer smoke proves the hook fires on a matching glob, reads the CONSUMER's .claude/rules via CLAUDE_PROJECT_DIR, and no-ops on non-match.

Prior-art: skipped — relocation of an own hook into the plugin payload, no new external dependency or capability.

* test(plugin): harden hook-paths assertion (d) vs T-PLUG-A inverse mis-rooting (S2 cold-review)

Phase -1 cold-review CONCERN: assertion (d) only checked that CLAUDE_PROJECT_DIR appears somewhere in the file, so a hook rooting consumer .claude/rules at ${CLAUDE_PLUGIN_ROOT} while mentioning CLAUDE_PROJECT_DIR elsewhere would falsely pass. Added a negative arm rejecting CLAUDE_PLUGIN_ROOT-rooted .claude/rules. Verified: real hook still 6/6; a constructed inverse-bug fixture now FAILs; clean after removal.

Prior-art: skipped — test hardening only, no new external dependency or capability.

---------

Co-authored-by: Art <devartyhoo@gmail.com>

* fix(654): zizmor adhoc-packages findings + ci-tool-pinning rule (option 2b) (#663)

Fix the 5 zizmor adhoc-packages findings via npm ci + re-pin zizmor 1.26.1 +
internalise unpinned-CI-tool-install as Class-A ci-tool-pinning.md (pre-push gate
+ paired-negative test, principle-09 registered). aif autonomous task e8528798 +
orchestrator cold-QA rework (B1 SSOT #149->#153 staging-collision, M1 narrow Rule B).

Prior-art: prior-art-evaluations.md#153 (zizmor adhoc-packages REFERENCE + bare-run tool-pin BUILD; HYBRID).
§1.7: forward — no-paid-llm-in-ci (deterministic scan), build-first-reuse (REUSE zizmor + BUILD bare-run slice), doc-authority (Class-A + principle-09). backward — codifies 2026-06-22 unpinned-zizmor incident (audit-self.yml:741). See packages/core/hooks/checks/unpinned-tool-install.ts:1.

* feat(plugin): using-rules-as-tests bootstrap + shippable skills (S3) (#665)

Author plugin/skills/using-rules-as-tests/SKILL.md (ADAPT of superpowers using-superpowers — their mechanism: invoke-skills-before-responding + the instruction-priority ladder; our content: the rules-as-tests skill set + the soft/hard honesty boundary). Add plugin/hooks/session-start (extensionless, @cc-only-rationale, self-resolves dir, ${CLAUDE_PLUGIN_ROOT}) that injects the bootstrap so the skill auto-triggers without a manual Skill invocation. Copy the consumer-facing rules-as-tests skill (SKILL.md + 5 references) into plugin/skills/. Wire SessionStart→session-start in hooks.json. v1 ships only rules-as-tests + the bootstrap (per extension.json); tool-bootstrapping deferred.

Verified: tests/plugin/bootstrap.test.sh 10/10 (session-start emits the bootstrap naming the skill, the CLAUDE.md/AGENTS.md instruction ladder, and the /install-enforcement boundary; both shipped skills carry valid frontmatter); hook-paths 10/10 (session-start extensionless+marked+wired); principle 09 + 22 green.

Prior-art: prior-art-evaluations.md#151 (superpowers using-superpowers — ADAPT: their SessionStart-bootstrap mechanism, our content).

Co-authored-by: Art <devartyhoo@gmail.com>

* chore(654): umbrella done.md (closure convention) (#666)

Co-authored-by: Art <devartyhoo@gmail.com>

* feat(plugin): S4 — ship consumer-facing agents + principle 21 plugin-tree arm (#667)

* feat(plugin): ship consumer-facing agent subset + principle 21 plugin-tree arm (S4)

Triage (T-PLUG-B): SHIP review-sidecar, living-docs-auditor, compliance-verifier (consumer-facing per extension.json + the Artifact Ownership Contract); INTERNAL aif-init, manual-rule-liveness-prober, memory-codification-auditor, orchestrator-worker-discipline, shipped-agent-liveness-prober. Copy the 3 SHIP agents verbatim into plugin/agents/ (byte-identical to the agents/ source they mirror). Register compliance-verifier in extension.json. Extend principle 21 with collectPluginAgentFiles() + arm (h) so the plugin's shipped agents are guarded by the same #551 tool-name-validity gate as agents/*.md (non-vacuity floor ≥3; the formal manifest-integrity self-test lands in S6).

Verified: principle 21 10/10 (arm (h) red with 0 agents → green after copy); principle 09 green (agents carry their REQUIRED_HEADER_DOCS doc-authority headers); plugin bash suite 4/4.

Prior-art: skipped — test arm extension for an existing capability + verbatim relocation of own agents; no new external dependency.

* test(plugin): relabel plugin-tree arm (h)→(j) to de-dup the label (S4 cold-review nit)

Prior-art: skipped — cosmetic test-label fix, no new dependency or capability.

---------

Co-authored-by: Art <devartyhoo@gmail.com>

* docs(stage-2-generate-path): Stage 0 close — land prior-art research-patch + SSOT #154#168 (#668)

Make the Stage-0 reuse-gate research-patch visible to dispatch (which reads from staging): the patch was untracked on a feature branch, so /pipeline + aif dispatch could not see it.

- Add the research-patch under docs/meta-factory/research-patches/ (was untracked).
- Append 15 SSOT entries (#154#168) for candidates 3–17 (ESLint RuleTester, Semgrep, OpenRewrite, GritQL, config-inspector, antfu/Sheriff, Biome/oxlint, OPA/Checkov, SonarQube, CEGIS, Reflexion, compiler self-hosting, Renovate, Nx); cross-link #1/#50/#66/#105 (Last-reviewed bumped, verdicts unchanged).
- Mark Stage 0 discharged in the kickoff + acceptance criterion met.

Verdict: /aif-evolve = REFERENCE -> the L3-live synthesizer stays BUILD; Oh My ClaudeCode = REJECT.

Landing-time CI-compliance fixes on the as-authored patch: add the principle-10 <!-- scope:... --> first-line annotation; collapse compound verdict cells to single principle-11-recognized tokens (dual nature preserved in each rationale). All 25 principle suites green (219 tests).

Prior-art: skipped — Stage-0 R-phase research-patch + SSOT append (#154-#168) + kickoff discharge; doc-only, no new capability or dependency.

Co-authored-by: Art <devartyhoo@gmail.com>

* feat(plugin): /install-enforcement fetch-and-wire seam (S5, Option C) (#669)

Hybrid seam reaching the HARD enforcement layer (git hooks + CI). Option C (maintainer decision 2026-06-22, overrides spec §6 bundle): rather than bundle ~2MB of install.sh's payload into plugin/install/ (which would duplicate the plugin's own skills/agents + carry the not-shipped dispatcher/pipeline + risk repo-wide CI scans), the thin plugin/install/fetch-and-wire.sh FETCHES the project's own official install.sh (pinned to plugin version; RAT_INSTALL_SOURCE override for forks/offline/tests) and runs it verbatim against $CLAUDE_PROJECT_DIR. Default dry-run (writes nothing); --apply does the real run. commands/install-enforcement.md orchestrates consent (dry-run → show → [y/N] → --apply); installing-enforcement/SKILL.md documents WHEN to wire + the honest soft/hard boundary (T16). The thin-reimplemented-wirer alternative was rejected (would discard install.sh's battle-hardened wiring, fixes #532/#533/#635). Spec §6 + plan Task 5 updated to reflect the decision; plan S6 step updated (no bundled install.sh to hash → assert fetch-and-wire version pin === plugin.json).

Verified offline (RAT_INSTALL_SOURCE=repo root): tests/plugin/install-seam.test.sh 9/9 — dry-run writes nothing+exits 0, --apply creates .husky/pre-commit+pre-push in a throwaway consumer, unknown flag exits 2; full plugin suite 5/5; principle 09 green.

Prior-art: skipped — thin seam under plugin/ (no new dependency, no packages/ code); reuses the project's own install.sh verbatim per companion-install-principle.md (install via the official top-level installer).

Co-authored-by: Art <devartyhoo@gmail.com>

* test(plugin): S6 — principle 24 plugin-manifest integrity + paired-negative (T15) (#670)

* test(plugin): principle 24 — plugin manifest integrity + paired-negative (S6)

Recursive self-test (T15): the packaging enforces its own integrity. checkPluginIntegrity() is a pure function (plugin dir + manifest dir → violations) run on BOTH the real tree (arm a, 0 violations) and a deliberately-broken fixture (arm b — tests/fixtures/plugin-broken-manifest trips V2 version-drift + V3 header-less skill + V6 dangling hook target), the principle-02 paired-negative discipline that makes the gate non-tautological. Plus real-tree arms: version pin (fetch-and-wire RAT_PLUGIN_VERSION === plugin.json.version), drift guards (plugin/agents/*.md byte-identical to agents/ source; plugin/hooks/inject-matching-rule shares the source's @dual-pair anchor + glob_match core), and T15 self-application. Added Authoritative-for headers to the two new plugin skills so V3 passes. plugin doc-authority is owned HERE (V3 for skills; the byte-identical drift guard covers plugin/agents headers transitively via principle-09-enforced agents/ sources) rather than by extending principle 09 — closes the S4 cold-review gap without touching the shared principle.

Verified: principle 24 6/6 (real-tree green, fixture red-detected); full principle suite 26 files / 226 tests; plugin bash suite 5/5.

Prior-art: skipped — test addition (principle self-test) for the plugin-packaging capability already SSOT'd at #149-152; no new dependency or external capability.

* test(plugin): harden principle 24 arm (e) — byte-identical core drift guard (S6 cold-review)

Cold-review CONCERN: arm (e) only asserted @dual-pair + glob_match() string presence, so a regression INSIDE glob_match's body would not be caught. Now extracts the matcher+injection core (from glob_match() to EOF — the only legit divergence is the relocation above it) and asserts byte-equality between plugin/hooks/inject-matching-rule and the source. Verified: real tree green; a mutation inside the glob_match region now goes RED.

Prior-art: skipped — test hardening only, no new dependency or capability.

---------

Co-authored-by: Art <devartyhoo@gmail.com>

* feat(plugin): OpenCode degradation adapter (S7) (#671)

.opencode/INSTALL.md — fetch-and-follow adapter pointing OpenCode at the shared plugin/skills/ body (via opencode.json skills.paths). Documents the ONE accepted off-CC degradation: SessionStart auto-injection of the using-rules-as-tests bootstrap does not fire off-CC → read it on demand (a documented degradation per dual-implementation-discipline §3, NOT a portability gap — the skill content is fully portable). Tool mapping (CC Skill→OpenCode skill tool, Task→@mention, etc.); the hard layer routes through the portable plugin/install/fetch-and-wire.sh seam (same honest soft/hard boundary). extension.json gains a $plugin-packaging note reconciling it with the plugin payload (source paths kept — install.sh + principle 21 read them; not repointed). AGENTS.md cross-harness layer is referenced from the adapter, not duplicated.

Verified: tests/plugin/opencode-adapter.test.sh 7/7; full plugin bash suite 6/6; markdownlint 0.

Prior-art: skipped — docs + a small acceptance test, no new dependency or capability (ADAPT of superpowers' .opencode fetch-and-follow pattern, SSOT-adjacent).

Co-authored-by: Art <devartyhoo@gmail.com>

* feat(plugin): S8 — README per-harness install + fetch ref → main (umbrella finale) (#672)

* feat(plugin): README per-harness install + fetch ref → main (S8)

README 'As a Claude Code plugin (per-harness)' section: the marketplace-add/install flow, skills auto-trigger, the honest soft/hard boundary (T16 — the plugin never wires git/CI; /rules-as-tests:install-enforcement does, opt-in), the OpenCode path + accepted degradation, and plugin-vs-./setup. §Why this exists untouched (read-only per the Artifact Ownership Contract).

fetch-and-wire.sh: decouple RAT_INSTALL_REF from the plugin version → default 'main'. Evidence: the framework's latest release tag v0.3.0 is 2026-05-06, staging is 72 files / +17551 ahead in install.sh + payload (incl. the hardening fixes #531/#551/#635/#636 the S5 cold-review valued); origin/main (2026-06-18) carries the current installer (only 65 lines behind staging). Pinning to v<plugin-version> would ship a 6-week-stale install.sh. Plugin keeps its own 0.1.0 version line; a reproducible per-plugin release tag is a future maintainer action. Spec §6 updated.

Verified: composite e2e on a throwaway consumer — SessionStart bootstrap fires, inject-matching-rule reads consumer rules, /install-enforcement seam dry-run writes nothing + --apply wires .husky pre-commit/pre-push + CI, manifest version-parity True; all 6 plugin bash tests + principle 21/24/09 (39) green; README markdownlint 0.

Prior-art: skipped — docs + a config-default change under plugin/, no new dependency or capability.

* docs(plugin): umbrella closure — plugin-packaging done.md (S8)

CLAUDE.md Umbrella closure schema: Final PR #672, all S0–S8 merged to staging. Records the stage-PR map + 3 maintainer-owned residuals (live /plugin install unproven-from-automation, install.sh-vs-plugin agent divergence, reproducible-tag option).

Prior-art: skipped — closure doc, no new dependency or capability.

---------

Co-authored-by: Art <devartyhoo@gmail.com>

* test(validator): L4 adversarial hardening — tautology/never-firing/conflict fixtures (paired-negative) (#674)

Adds three adversarial SynthesisPlan fixture pairs (bad + valid) that
exercise the exact reject surfaces of Gates 2, 4, and 6:

- gate-tautology.adversarial.test.ts (T1): no-restricted-imports forbidding
  'react' fires on negative-corpus/unrelated.tsx (Gate 4 fail); paired with
  a lodash-forbidding rule that does NOT fire on the corpus (Gate 4 pass).

- gate-rule-tester.adversarial.test.ts (T2): comment-only input that never
  triggers no-restricted-imports (Gate 2 fail); paired with a lodash-import
  input that correctly fires (Gate 2 pass).

- snapshot.adversarial.test.ts + expected-adv-*.json (T4): full validate()
  aggregate snapshots for each bad fixture confirm ok:false and the right
  gate routes to 'fail'. Separate from existing self/next-16 snapshots.

T3 (Gate 6 conflict): adversarial orphan-ref + snippet-drop cases are
already present in gate-conflict.test.ts:52-99; no new file needed.
Gate 6 snippet-drop is additionally covered via snapshot.adversarial.test.ts
using a no-debugger rule absent from eslintConfigSnippet: '{}' (clean Gate
2 + 4 pass, Gate 6 fail).

Gate source files (gate-*.ts, validate.ts) are UNCHANGED — hardening
adds tests/fixtures only. validator suite: 45 tests, all pass.

Prior-art: prior-art-evaluations.md#154–#168 (Stage-0 SSOT for L4 generate-path hardening — adversarial corpus design patterns, no upstream production-grade paired-negative harness for ESLint-rule validation existed at SSOT survey time).

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(deps): unpin revoked eslint 10.4.1 → ^10.4.0 + sync root lockfile (preset-react-native/spa) (#676)

eslint 10.4.1 was unpublished from the npm registry (version-API → 404; tarball
still served by the CDN), so any root-level `npm install` / `npm ci` /
`--package-lock-only` failed with ETARGET, leaving the root lockfile unable to
regenerate. Consequently the new workspace packages preset-react-native (#646)
and preset-react-spa (#661) were never added to package-lock.json.

guard-liveness-fullsweep (pull_request → main only) is the one required check
that runs `npm ci` on the root workspace, so the drift surfaced only on promote
PR #673 — red on "Missing: @rules-as-tests/preset-react-native from lock file".

Fix: pin packages/core eslint to ^10.4.0 (latest existing in the 10.4 line; API
stable vs 10.4.1) and regenerate the root lockfile. Verified locally: npm ci
rc=0; guard-liveness:fullsweep GREEN (v1 ESLint 8/0, v1.5 cmd 3/0, v3 structural
pass); lockfileVersion stays 3 (node20/npm10 CI-compatible); linux-x64 bindings
present; package count 458→770 (preset deps).

Prior-art: skipped — dependency version-pin fix (revoked eslint 10.4.1 → existing 10.4.0) plus mechanical lockfile sync; no new capability introduced.

Co-authored-by: Art <devartyhoo@gmail.com>

---------

Co-authored-by: Art <devartyhoo@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
artyhoo added a commit that referenced this pull request Jun 22, 2026
)

* fix(hooks): pre-push delivery completeness on pnpm monorepo (#635, #636) (#638)

* fix(hooks): probe tsx runtime in pre-push dispatcher, degrade to bash fallback instead of crash (#636)

* fix(install): refresh ships hooks/package.json type:module marker in do_refresh (#635)

* fix(templates): shipped CI templates pass the framework's own zizmor gate + regression guard + honest brownfield hint (#637) (#639)

* fix(templates): harden shipped CI workflows to pass zizmor (artipacked + unpinned-uses + template-injection) (#637)

Both shipped CI templates failed the framework's own zizmor pre-push gate,
blocking a fresh consumer on the framework's own scaffolding. Apply three
hardening transforms to each:
- artipacked: persist-credentials: false on every actions/checkout step
- unpinned-uses: SHA-pin every action uses: (KEEP the # vX.Y.Z comment)
- template-injection: use runner GITHUB_BASE_REF env in the Stryker run-block
  instead of interpolating ${{ github.base_ref }} into shell

The actions/cache key: interpolation of github.base_ref is left unchanged
(not a shell context — zizmor only flags the run:-block one).

Prior-art: skipped — shipped-template security hardening (persist-credentials + SHA-pin + injection fix), no new capability

* fix(pre-push): guard shipped CI templates against zizmor drift + honest brownfield fix hint (#637)

Extend the pre-push zizmor gate (and the audit-self.yml CI backstop) to also
scan the three shipped CI templates, so they cannot silently drift past the
gate again after cab1a0e fixed them. The template guard uses an INVERTED
existsSync direction vs sections 3c/3d: it fires in the maintainer repo (where
the templates exist) and no-ops on consumers (who receive the rendered
.github/workflows/ci.yml, not templates/).

Also add an HONEST brownfield fix hint to every zizmor failure via a new
optional requireTool(failHint) param. The hint spells out that
`zizmor --fix=all` auto-fixes only artipacked + template-injection and that
unpinned-uses is NOT auto-fixable (needs SHA-pinning) — a "just run --fix" hint
would mislead the consumer.

Prior-art: skipped — extend existing zizmor gate to shipped templates + brownfield fix hint, no new capability

* fix(install): guarantee tsx resolves from workspace root for the pre-push TS hook (#636) (#640)

GH #636 fix (a) — the install-time half, complementing #638's runtime degradation half.

The pre-push dispatcher runs `node --import tsx/esm <root>/packages/core/hooks/pre-push.ts`
from the repo ROOT, so tsx must resolve THERE. tsx is in CORE_DEVDEPS, but on a pnpm monorepo
a tsx living in a sub-package is NOT hoisted to the root, so the TS hook silently degrades to
the bash fallback (#638 made that degradation graceful instead of a crash). This closes the gap
at install time.

New install.sh step (§8b, after the §8 dep-install, before §6b-bis-L2 R2 wiring — so on --full
the bulk install has already landed tsx via -w and the probe is a no-op, and tsx is ready before
R2 wiring needs it). Reuses the #638 dispatcher probe expression and the existing detect_pm SSOT:

  tsx already resolvable from root   -> nothing
  tsx missing, --full                -> install at the workspace root, silently
  tsx missing, interactive (tty)     -> offer [y/N]; on yes install, EVEN without --full
  tsx missing, refused / non-tty     -> WARN: pre-push runs REDUCED (critical-only); print the
                                        exact PM-aware enabling command

tsx ONLY (the hook runtime) — ts-morph / R2 untouched. Idempotent: the probe short-circuits when
tsx already resolves. PM-aware command is built once as the SSOT for both the install and the warn
(no #two-prompts-drift). No new dependency (tsx already in CORE_DEVDEPS).

Test: tests/install-sh/gh-636-ensure-tsx-root.test.sh (fake PM on PATH, real node probe, python
pty for the interactive arm; NO network). Covers --full silent install, interactive-yes (no --full),
non-interactive WARN, PM/workspace command derivation, and a load-bearing paired-negative (tsx
present -> no warn/install). Wired into audit-self.yml (meta-all-wired gate).

Closes #636.

* fix(tests): portable in-place sed in c1-wiring (BSD/GNU sed parity) (#641)

GNU-style `sed -i 's/…/…/' file` errors on BSD sed (macOS): the substitute
expression is consumed as the backup-suffix arg, the file path is parsed as a
sed command, the command fails, and the file is never mutated. This made the
C1-548-neg paired-negative arm VACUOUS on macOS (spurious FAIL=1) while Linux
CI (GNU sed) stayed green and hid it.

Replace with a POSIX temp-file + mv in-place edit — identical behaviour on
both seds, no OS branching.

Verified: macOS PASS=12 FAIL=0 (was 11/1). Test-only; install.sh and
deps-hash-check.sh behaviour untouched.

* fix(install): resolve ts-morph from consumer cwd in R2 Layer-2 wirer (#642)

Resolve ts-morph from the consumer cwd (createRequire + pathToFileURL) in the R2 Layer-2 wirer; declare ts-morph@^24.0.0 as a packages/core devDep (matches setup.sh:336 / SSOT #131, fixes the latent undeclared audit-r4.ts:11 import); add fail-first Fixture X (in-pattern with f16/f17).

Prior-art: prior-art-evaluations.md#131 (ts-morph REUSE — declaring an already-imported engine; SSOT pre-cleared, no new capability introduced)

Closes #642

* docs(orchestrator): preserve #642 R2-wirer-fix kickoff (Phase -1 audit trail) (#645)

Audit-trail for the #642 fix (shipped via #643, merged to staging as e1bb9c4):
the orchestration kickoff capturing the 3-round Phase -1 cold-review reasoning
(B->A CI-strategy flip away from a runtime-registry-install anti-pattern; the
ts-morph ^24 vs npm-latest-28 version-drift catch) and the TDD cross-checkout
fixture design. The fix itself already landed; this preserves the design record
per the project's kickoff-tracking convention. Passes principle-12 citation
(ai-laziness-traps + T-642-A/B/C domain traps).

Prior-art: skipped — docs only (orchestration kickoff audit-trail under .claude/orchestrator-prompts/, not under packages/, no dependency), no new capability

* docs(#646): multi-stack hybrid design + react-spa R-phase (#648)

* docs(specs): multi-stack hybrid design (epic #646)

Records the hybrid decision resolving the Stage-1 preset-vs-principle fork
surfaced by the reviewer pass on #646: Stage 1 curated react/react-native
presets now + Stage 2 generate-path as committed next umbrella, with a
binding sequencing-commitment (phase-5 v2-trigger research-patch at Stage 1
close) so the bridge cannot strand 3 hand-maintained presets.

Prior-art: skipped — design spec doc, no new capability (brainstorm output for epic #646).

* docs(research): react-spa rule prior-art R-phase + SSOT #136-141 (#646 Stage 1)

R-phase verdicts gating react-spa capability commits: A1-A3 (eslint-plugin-react /
react-hooks v6 / jsx-a11y) ADOPT (own-stack backfill); B (no-business-logic) ADAPT
not BUILD (boundary REUSE); C (require-error-boundary) T16 split BUILD-presence +
ADOPT-usage. Primary-plugin fork resolved to eslint-plugin-react (dual-impl
discipline); @eslint-react WATCHLIST (#141). DeepWiki down → WebSearch-grounded,
re-probe flagged before B/C commits.

Prior-art: skipped — R-phase research-patch + SSOT register entries, no new capability (these verdicts gate future capability commits per build-first-reuse-default §3).

* chore(#646): re-trigger CI on corrected PR-body §1.7 headings

No content change — forces a fresh SHA so the stale failed §1.7 check-run
(from a re-run on the pre-fix PR body) detaches from PR head.

Prior-art: skipped — empty re-trigger commit, no capability, no file change.

* docs(research): react-native rule prior-art R-phase + SSOT #142-148 (#646 Stage 1) (#650)

* docs(research): react-native rule prior-art R-phase + SSOT #142-148 (#646 Stage 1)

R-phase verdicts for the react-native preset (epic #646 Stage 1, sibling of the
react-spa R-phase in #648). All three issue BUILD hypotheses resolve to
ADAPT/ADOPT/REJECT — zero clean BUILDs survive the prior-art consult:

- no-web-only-globals          -> ADAPT  (REUSE no-restricted-globals + eslint-config-expo globals; BUILD-residue YAGNI)
- require-stylesheet-create    -> ADOPT  (eslint-plugin-react-native/no-inline-styles)
- prefer-flashlist-over-flatlist -> REJECT the BUILD (no upstream rule; perf-opinion not a correctness invariant; 3-way FlatList/FlashList/LegendList landscape)

ADOPT layer: eslint-config-expo (Expo baseline; forbids web globals), @react-native
+ @callstack configs (bare-RN baseline), eslint-plugin-react-native (RN style rules,
WATCHLIST low-maint), eslint-plugin-react-native-a11y (T16: RN a11y != jsx-a11y,
supersedes #138 for RN).

DeepWiki was UP (vs the react-spa sibling, where it was down) -> negative-existence
claims are source-repo-verified. SSOT rows #142-148 continue after #648's #136-141.

Depends on #648 (design spec + #136-141, branch claude/hopeful-clarke-6517e0, OPEN).
Merge #648 first; the prior-art-evaluations.md tail then reorders to

Prior-art: skipped -- R-phase research-patch; records ADOPT/ADAPT/REJECT verdicts + SSOT rows, builds no capability artifact.

* docs(research): flag eslint-config-expo web-globals claim for I-phase verify (#646 rn R-phase)

Adds a §5 open-item: the eslint-config-expo window:false / globals.browser-off
claim (candidate B / SSOT #146) is DeepWiki-sourced, not file-verified in this
R-phase; the core no-restricted-globals denylist is the independent fallback.

(Lands on a fresh SHA -> clears the stale §1.7 check run frozen against the
pre-rebase PR body; the gate snapshots github.event.pull_request.body at
opened-event time, so the original opened-run stays red on the old SHA.)

Prior-art: skipped -- R-phase research-patch addendum; records a verify-item, builds no capability.

* docs(ssot): #146 — surface expo-globals verify caveat (consistency with R-phase §5)

SSOT row #146 (no-web-only-globals / ADAPT) now carries the same I-phase
verify-item just added to the research-patch §5: the eslint-config-expo
window:false claim is DeepWiki-sourced, not file-verified; no-restricted-globals
is the independent fallback. Keeps the SSOT row honest about its unverified leg.

(Touches prior-art-evaluations.md -> re-runs the full path-filtered CI suite,
incl. the §1.7 gate, on a fresh SHA — the prior docs-only addendum skipped them.)

Prior-art: skipped -- SSOT honesty edit; adds a verify caveat to an existing row, builds no capability.

* docs(research): correct falsified web-globals claim in react-native R-phase SSOT #142/#146 (#646 Stage 1) (#651)

The react-native R-phase (#650, merged) recorded that `eslint-config-expo`
"sets window:false and forbids globals.browser web-only globals -> closes
candidate B (no-web-only-globals) for free on the Expo path". That claim was
DeepWiki-prose, flagged by the patch's own §5 open-item-4 as "not file-verified".

File-verified now (the open-item's intended I-phase check):
- gh expo/expo:packages/eslint-config-expo/flat/default.js:23,38 — the globals
  block SPREADS `...globals.browser` and sets `window: false`, ships no
  `no-restricted-globals`.
- ESLint globals docs — `false` ≡ "readonly" ≡ a DEFINED/readable global;
  only `"off"` disables. So `window: false` whitelists, it does not forbid.

=> The config WHITELISTS web-only globals (the opposite of forbidding) and does
NOT close candidate B. This is the same `whitelists ≠ forbids` error the patch
already caught for #144's `eslint-plugin-react-native` env claim — latent in the
expo claim only because it was never file-verified. DeepWiki even returned two
opposite answers on the semantics across probes (reliable for source contents,
not for ESLint semantics — §1.10 spec-over-prose).

Verdicts unchanged: ADOPT #142, ADAPT #146. Corrected the load-bearing
consequence — the Expo baseline does NOT close candidate B; core
`no-restricted-globals` is the PRIMARY forbid mechanism, not a fallback.
Discharges react-native R-phase §5 open-item-4. Brings #142/#146 into agreement
with the already-correct #144 row.

Prior-art: skipped — docs-only correction of a falsified prior-art claim in #142/#146 (web-globals semantics: expo whitelists, not forbids); no new capability, no dep, no code.

* fix(recovery): restore #642/#635/#636/#645 reverted by #648 staged-index contamination (#653)

* fix(recovery): restore #642/#635/#636/#645 reverted by #648 staged-index contamination

PR #648 (merge 691f44f) shipped 3 legit docs (multi-stack hybrid design +
react-spa R-phase + SSOT), but its `--allow-empty` CI re-trigger sub-commit
captured 16 foreign staged-WIP files, silently reverting merged fixes:
- #642 — R2 wirer cwd-anchored ts-morph resolution (wire-eslint-r2.ts back to bare import)
- #635 — hooks type:module refresh (install.sh do_refresh + pre-push.ts)
- #636 — tsx-at-root probe (install.sh §8b) + DELETED both harness tests (gh-636-*.test.sh)
- #645 — the #642 R2-wirer kickoff (deleted)
plus package.json/lock dropping ts-morph, CI templates, husky hooks.
No restorative commit followed → contamination live on staging.

Restores all 16 contaminated files from dd05105 (parent of 691f44f, the
pre-contamination state). #650/#651 touched none of the 16 (docs-only), so this
is a clean reversal; the 3 legit docs from #648 and #650/#651 research are
untouched. Mirrors the contamination exactly: +817 lines = the 817 it deleted.

Verified: wire-eslint-r2.test.ts vitest passes; #642 createRequire, #636
tsx-at-root + both gh-636 tests, #645 kickoff all confirmed restored.

Prior-art: skipped — recovery of an accidental revert (staged-index contamination), no new capability; the package.json ts-morph line is restoration of a pre-existing dependency removed by the contamination, not a new dependency.

* ci(zizmor): pin zizmor==1.25.2 — unpinned install + 1.26.1 bump red-ed all PRs

`pip install zizmor` (unpinned) installed 1.26.1, which tightened the
adhoc-packages audit and surfaced 5 pre-existing LOW findings in our own
self-CI workflows (audit-self.yml npm install --prefix / git init;
framework-self-template-render.yml:35) that 1.25.2 did not flag. ci-success
`needs: zizmor` → every fresh PR went red (recovery #653 included), with no
code cause. Pin to the last known-green 1.25.2 for deterministic CI; a
zizmor bump is now a deliberate edit, not a silent CI break.

The 1.26.1 adhoc-packages findings (5 LOW, self-CI only — not shipped supply
chain) are deferred to a separate triage (suppress-config vs fix), off the
critical path here.

Prior-art: skipped — CI tooling version pin (one-line determinism fix), no new capability, no new dependency.

* fix(install): R2 Layer-2 wirer registers the plugin so the wired config loads (#644) (#647)

* docs(spec): R2 Layer-2 wirer plugin-registration fix design (#644)

* docs(plan): R2 Layer-2 wirer plugin-registration fix implementation plan (#644)

* docs(plan): address Phase -1 cold-review — BLOCKER eslint resolve via package.json + 2 MAJOR + P2 assertion (#644)

* feat(install): R2 wirer supports self-contained (plugin-registering) variant (#644)

* feat(install): compute relative customRules import path for self-contained wire (#644)

* feat(install): resolveAndWire orchestrates bare→self-contained via injected probe (#644)

* feat(install): probeViaEslint default probe resolves eslint from consumer cwd (#644)

* feat(install): apply-mode wiring goes through resolveAndWire probe loop (#644)

* test(install): W3 loadability gate — wired config must load in ESLint (#644)

P1 (plugin-less base) + P2 (plugin-registering base) run the wirer in apply mode and
assert eslint --print-config LOADS (rc 0). Red-then-green verified: pre-fix wirer makes
P1 fail with the exact 'could not find plugin' error on eslint 9.39.4 (confirms v9 parity,
spec R1). _mk_consumer sets type:module (the .ts plugin-import precondition Layer-1 already
needs) + installs ts-morph (wirer resolves it from cwd). Fixture X switched to --diff to
isolate its ts-morph cross-checkout concern (#642) from the new apply-mode probe (#644).

* fix(install): manual R2 snippets register the plugin (self-contained) (#644)

* test(install): correct the false 'print-config in consumer-pipeline' comment (#644)

* fix(install): load .ts eslint config on Node 20 via tsx loader (#644)

The shipped eslint config (.mjs) imports the eslint-rules-local/index.ts barrel; on the
shipped .nvmrc (Node 20, no native type-stripping) plain eslint fails with
ERR_UNKNOWN_FILE_EXTENSION — breaking both the R2 wirer's self-contained escalation AND
Layer-1's own lint. Preserve the consumer's Node choice (don't force a bump): run eslint
through the tsx loader (already a CORE_DEVDEP + the pre-push-hook pattern; build-first-reuse).
- probeViaEslint: node --import tsx (when tsx resolvable; else plain node, Node>=22.18).
- consumer lint/lint:fix scripts (install.sh + setup.sh): NODE_OPTIONS=--import tsx.
- W3 harness: _mk_consumer installs tsx; P1/P2 validate via the loader.
Verified on simulated Node 20 (--no-experimental-strip-types): loader -> rc 0, plain -> ERR.

* docs(#646): I-phase kickoffs for react-spa + react-native presets (Stage 1) (#655)

Two parallel-dispatch I-phase kickoffs for the merged #646 Stage 1 R-phase
(curated bridge). Each is self-contained for an autonomous aif worker.

- react-spa: 3 ADOPT (eslint-plugin-react/hooks-v6/jsx-a11y/boundaries) + 1
  BUILD (require-error-boundary presence-check, #140 — upstream covers usage
  not presence; AST-over-grep, narrow in-file scope per SSOT #115 precedent).
- react-native: Expo-only (orchestrator scope decision 2026-06-22), 4 ADOPT +
  1 ADAPT (no-restricted-globals denylist #146 — Expo baseline whitelists web
  globals per #651) + 1 REJECT (FlashList #148). Zero from-scratch BUILD = a
  build-first-reuse success per design-spec section 8.
- Both fence off install.sh / packages/core/principles/* / prior-art-evaluations.md
  / preset-next-15-canonical (install+principle wiring is a separate follow-up
  task) -> no egress collision between the two parallel workers.
- Phase -1 cold-reviewed (2x Opus, read-only): all load-bearing SSOT claims
  #136-148 + the #651 whitelist->denylist correction verified TRUE; MAJOR/MINOR
  findings folded in (peerDeps, package.json mirror, egress-guards, T-MS test cases).

* docs(#646): revise react-native kickoff to ship BOTH Expo + bare-RN baselines (#656)

Operator override (2026-06-22): Expo-only -> both baselines. The project goal
is multi-stack + a factory for any stack (README#why-this-exists); covering the
Expo AND bare-RN consumer up front serves that goal directly, weighted over the
earlier YAGNI lean.

- Ship eslint-config-expo/flat (#142) AND @react-native/eslint-config (#143) as
  two selectable baselines over a shared rn-common layer (style/a11y/no-restricted
  -globals denylist). Shipping #143 resolves the open DECISION-NEEDED the R-phase
  explicitly left to the operator (reviewer-discipline §2 path) — not a contradiction.
- ZERO custom rules unchanged (build-first-reuse success); two-baseline = maximal REUSE.
- Phase -1 cold-reviewed (1x Opus): GO, 0 BLOCKER/0 MAJOR; #143 ship-vs-swap
  consistency verified against R-phase + SSOT; egress fences still airtight; T-gate
  4 distinct T-numbers. 1 MINOR folded in (denylist is a separate rule key, fires
  additively — not a key-collision to 'win').

* docs(654): kickoff for ci-adhoc-install-fix umbrella (option 2b) (#657)

Dispatch input for aif autonomous execution of GH issue #654 option 2 (fix the
5 zizmor adhoc-packages findings via npm ci + re-pin zizmor 1.26.1 + internalise
unpinned-CI-tool-install as a Class-A rule with an earliest-channel pre-push check).

Kickoff passed Phase -1 (2x Opus round 1 caught 3 BLOCKER + 5 MAJOR; round 2 GO).
Must reach staging before aif sees it (kickoff-staging-placement.md).

* docs(#646): integration kickoff — install.sh wiring + principle 05/09 (Stage 1 Phase B) (#658)

Single integration task owning the SHARED files both presets need: install.sh
arg-parser/guard/menu/auto-detect/SHIPPED_DOCS/copy-blocks/dev-deps, plus
principle 09 (REQUIRED_HEADER_DOCS + drift-test count 19->25) and principle 05
(manifest-rendered-vs-static, determined by grep on the render-rules provenance marker).

- Binding dispatch dependency: ONLY after BOTH preset PRs merge to staging (copy-blocks
  reference real preset files; principle 09 registers real RULES.md/ARCHITECTURE.md).
- Phase -1 cold-reviewed (1x Opus, REVISE->GO round 2): 3 MAJOR fixed (SPA eslint
  basename = eslint.config.react.mjs not -spa suffix; RN ships no playwright.config.ts;
  principle-05 rendered-vs-static via deterministic grep) + 4 MINOR (count->25 explicit,
  guard message, detect order RN-before-spa, don't-touch-react-next-blocks). M3 grep
  marker independently re-verified against next RULES.md:13.

* docs(#646): stage-2-generate-path umbrella kickoff (Stage 2) (#659)

* docs(#646): Stage 2 generate-path brainstorm spec

Records the brainstorm feasibility framing for epic #646 Stage 2 (LLM
generate-path). Corrects the Stage-1 design-doc claim that L4 does not
exist (packages/core/validator/ ships gates 1/2/4/6, Phase 7); reframes
Stage 2 as swap-curated-inputs-for-LLM at L2/L3 with L4/L5 unchanged.
Lands reasoned recommendations on both strategic forks (entry point =
L3-live-first vs existing L4; scope = Path A only) plus the mandatory
build-vs-reuse gate (aif-evolve / OMC) before any live-LLM build.

Status: proposed — pending operator ratification. Not a kickoff.

Prior-art: skipped — docs-only brainstorm spec, no new capability (no dependency, no code under packages/).

* docs(#646): stage-2-generate-path umbrella kickoff (Stage 2)

Operationalises the 2026-06-22 brainstorm spec into a dispatch-ready umbrella kickoff for #646 Stage 2 (the LLM generate-path). Stage 0 = build-vs-reuse R-phase (aif-evolve / OMC) is BLOCKING — no L2/L3 code lands before its verdict. Reconstructed onto staging cleanly, without the cross-umbrella plugin commits that had tangled the original brainstorm branch.

Prior-art: skipped — docs-only umbrella kickoff, no new capability (no dependency, no code under packages/).

* style(#646): prettier-format stage-2 generate-path spec

Hooks were bypassed on the original brainstorm commit (3da9578, core.hooksPath=/dev/null); this applies the prettier pass it missed — italic-emphasis normalisation (*x* → _x_) and table-column padding. No content change.

Prior-art: skipped — prettier formatting only, no new capability.

---------

Co-authored-by: Art <devartyhoo@gmail.com>

* CC plugin packaging (S0–S1 + umbrella) — superpowers-style hybrid (#660)

* docs(plugin): CC plugin packaging spec + plan + umbrella kickoff

Spec/plan/kickoff for packaging rules-as-tests-aif as a CC-first plugin
(in-repo marketplace, soft/hard layer split, hybrid install seam,
recursive self-test). Kickoff carries ai-laziness-traps T-enumeration
(7 canonical + 2 domain) per ai-laziness-traps.md §3.

* feat(plugin): plugin.json + in-repo marketplace + run-hook.cmd skeleton (S1)

Task 1 of the plugin-packaging umbrella. In-repo marketplace source
verified as relative path string ("./plugin") against CC docs.
run-hook.cmd adopted from obra/superpowers, audited (T13): its missing-arg
guard is Windows-only — Unix path exits non-zero, asserted accordingly.
Both acceptance tests green (manifest-valid 5/5, run-hook 2/2).

Prior-art: skipped — new files outside packages/, no new dependency; not a
capability commit per CLAUDE.md mechanical definition. run-hook.cmd is an
ADOPT from superpowers (MIT), recorded for the S0 SSOT entry.

* docs(plugin): REVISE round after Phase -1 cold-review — dispatch-ready kickoff

Phase -1 adversarial cold-review (CLAUDE.md meta-orchestrator self-review
obligation) returned REVISE; one round applied:
- S0 SSOT entries #149-152 (superpowers ADOPT, run-hook ADOPT-audited, using-* ADAPT, CC-docs REFERENCE) - closes the S1 trailer forward-reference (B1).
- kickoff records current state: S0+S1 DONE (63b344c, fa37b2b), dispatch starts S2 (B1/B2).
- hook count 15 -> 14, all 14 enumerated in plan Task 2 (B3).
- compliance-verifier: stop claiming 'per extension.json'; S4 adds it (B4).
- principle 21 -> full filenames (M1); dispatcher/pipeline paths (M3); tool-bootstrapping OUT of v1 (m1).

Prior-art: prior-art-evaluations.md#149-152 (S0 SSOT entries; superpowers plugin/run-hook ADOPT, using-superpowers ADAPT, CC docs REFERENCE).

* fix(plugin): restore 654 kickoff erroneously deleted by S0 commit

Commit 63b344c (S0 spec+plan+kickoff) deleted .claude/orchestrator-prompts/654-ci-adhoc-install-fix/kickoff.md — a foreign file of the active 654 ci-adhoc-install-fix umbrella (present on origin/staging, blob c9019eb). Likely a stale staged-index artifact at branch-creation time. Restored byte-for-byte from origin/staging so this PR does not drive-by-delete another umbrella's kickoff. No plugin-packaging content affected.

Prior-art: skipped — doc restoration, no new capability or dependency.

---------

Co-authored-by: Art <devartyhoo@gmail.com>

* feat(preset-react-spa): React-19 SPA preset (#646 Stage 1) (#661)

* docs(plugin): CC plugin packaging spec + plan + umbrella kickoff

Spec/plan/kickoff for packaging rules-as-tests-aif as a CC-first plugin
(in-repo marketplace, soft/hard layer split, hybrid install seam,
recursive self-test). Kickoff carries ai-laziness-traps T-enumeration
(7 canonical + 2 domain) per ai-laziness-traps.md §3.

* feat(plugin): plugin.json + in-repo marketplace + run-hook.cmd skeleton (S1)

Task 1 of the plugin-packaging umbrella. In-repo marketplace source
verified as relative path string ("./plugin") against CC docs.
run-hook.cmd adopted from obra/superpowers, audited (T13): its missing-arg
guard is Windows-only — Unix path exits non-zero, asserted accordingly.
Both acceptance tests green (manifest-valid 5/5, run-hook 2/2).

Prior-art: skipped — new files outside packages/, no new dependency; not a
capability commit per CLAUDE.md mechanical definition. run-hook.cmd is an
ADOPT from superpowers (MIT), recorded for the S0 SSOT entry.

* feat(preset-react-spa): React-19 SPA preset — 3 ADOPT + require-error-boundary (#646 Stage 1)

Built autonomously by aif task 547d9114; harvested selectively (preset dir only —
excluded base-contamination: hook-mods, foreign kickoff deletions, root lock).

- 3 ADOPT: eslint-plugin-react (#136), react-hooks v6 (#137), jsx-a11y (#138),
  eslint-plugin-boundaries (#139) — wired in self-contained eslint.config.react.mjs (no @next).
- 1 BUILD: require-error-boundary (#140) — real AST rule (ESLintUtils.RuleCreator +
  AST_NODE_TYPES JSX visitor, not string-grep) + paired valid/invalid test (principle 02).
- verify-before-harvest: 14 preset files present, out-of-scope clean (no install.sh/
  principles/prior-art edits), AST + paired-test sanity passed.
- install.sh wiring + principle 05/09 registration = separate integration task (#658).

Prior-art: prior-art-evaluations.md#136/#137/#138/#139 (ADOPT upstream react lint plugins) + #140 (BUILD require-error-boundary, presence-gap confirmed, no upstream analog) + #141 (WATCHLIST @eslint-react). SSOT rows recorded in merged R-phase #648.

---------

Co-authored-by: Art <devartyhoo@gmail.com>

* feat(preset-react-native): Expo + bare-RN preset, both baselines (#646 Stage 1) (#662)

* docs(plugin): CC plugin packaging spec + plan + umbrella kickoff

Spec/plan/kickoff for packaging rules-as-tests-aif as a CC-first plugin
(in-repo marketplace, soft/hard layer split, hybrid install seam,
recursive self-test). Kickoff carries ai-laziness-traps T-enumeration
(7 canonical + 2 domain) per ai-laziness-traps.md §3.

* feat(plugin): plugin.json + in-repo marketplace + run-hook.cmd skeleton (S1)

Task 1 of the plugin-packaging umbrella. In-repo marketplace source
verified as relative path string ("./plugin") against CC docs.
run-hook.cmd adopted from obra/superpowers, audited (T13): its missing-arg
guard is Windows-only — Unix path exits non-zero, asserted accordingly.
Both acceptance tests green (manifest-valid 5/5, run-hook 2/2).

Prior-art: skipped — new files outside packages/, no new dependency; not a
capability commit per CLAUDE.md mechanical definition. run-hook.cmd is an
ADOPT from superpowers (MIT), recorded for the S0 SSOT entry.

* feat(preset-react-native): Expo + bare-RN preset, both baselines (#646 Stage 1)

Built autonomously by aif task 808f858a (rebuilt for both-baselines per operator override);
harvested selectively (preset dir only, excluding base-contamination + preset-local lock).

- BOTH baselines: eslint-config-expo/flat (#142, Expo) + @react-native/eslint-config (#143,
  bare-RN) over a shared eslint.config.rn-common.mjs layer.
- ADOPT plugins: eslint-plugin-react-native (#144/#147), react-native-a11y (#145).
- ADAPT: no-restricted-globals denylist (#146) — present in all three configs (forbids web
  globals the Expo baseline whitelists, per #651 correction).
- REJECT: FlashList custom rule (#148). ZERO eslint-rules/ (build-first-reuse success).
- No playwright.config.ts (RN web-only, correctly omitted).
- verify-before-harvest: both baseline configs + rn-common + denylist present; eslint-rules
  absent as designed; out-of-scope clean.
- install.sh wiring + principle 05/09 = separate integration task (#658).

Prior-art: prior-art-evaluations.md#142/#143 (ADOPT Expo + bare-RN baselines) + #144/#145/#147 (ADOPT react-native lint plugins) + #146 (ADAPT no-restricted-globals denylist) + #148 (REJECT FlashList BUILD). SSOT rows recorded in merged R-phase #650/#651.

---------

Co-authored-by: Art <devartyhoo@gmail.com>

* feat(plugin): S2 — relocate shippable hooks (per-hook path-class audit) (#664)

* feat(plugin): relocate shippable hooks to plugin payload (path-class audit) (S2)

Per-hook triage of all 14 .claude/hooks/*.sh (docs/superpowers/plans/plugin-hook-triage.md): SHIP=2, INTERNAL=12. Each hook's self-declared @dual-pair/@cc-only-rationale marker is the primary evidence. Relocate inject-matching-rule into plugin/hooks/ as an extensionless, context-agnostic script: every path it touches is project-data, resolved via ${CLAUDE_PROJECT_DIR} (T-PLUG-A — no blanket env swap; a swap to ${CLAUDE_PLUGIN_ROOT} would make it read the plugin's non-existent rules instead of the consumer's). hooks.json wires PostToolUse→inject-matching-rule via run-hook.cmd; SessionStart→session-start deferred to S3. inject-session-bootstrap→session-start, also S3. check-doc-authority stays INTERNAL (needs the framework TS toolchain); deps-hash-check already ships via install.sh.

Verified: tests/plugin/hook-paths.test.sh 6/6; throwaway-consumer smoke proves the hook fires on a matching glob, reads the CONSUMER's .claude/rules via CLAUDE_PROJECT_DIR, and no-ops on non-match.

Prior-art: skipped — relocation of an own hook into the plugin payload, no new external dependency or capability.

* test(plugin): harden hook-paths assertion (d) vs T-PLUG-A inverse mis-rooting (S2 cold-review)

Phase -1 cold-review CONCERN: assertion (d) only checked that CLAUDE_PROJECT_DIR appears somewhere in the file, so a hook rooting consumer .claude/rules at ${CLAUDE_PLUGIN_ROOT} while mentioning CLAUDE_PROJECT_DIR elsewhere would falsely pass. Added a negative arm rejecting CLAUDE_PLUGIN_ROOT-rooted .claude/rules. Verified: real hook still 6/6; a constructed inverse-bug fixture now FAILs; clean after removal.

Prior-art: skipped — test hardening only, no new external dependency or capability.

---------

Co-authored-by: Art <devartyhoo@gmail.com>

* fix(654): zizmor adhoc-packages findings + ci-tool-pinning rule (option 2b) (#663)

Fix the 5 zizmor adhoc-packages findings via npm ci + re-pin zizmor 1.26.1 +
internalise unpinned-CI-tool-install as Class-A ci-tool-pinning.md (pre-push gate
+ paired-negative test, principle-09 registered). aif autonomous task e8528798 +
orchestrator cold-QA rework (B1 SSOT #149->#153 staging-collision, M1 narrow Rule B).

Prior-art: prior-art-evaluations.md#153 (zizmor adhoc-packages REFERENCE + bare-run tool-pin BUILD; HYBRID).
§1.7: forward — no-paid-llm-in-ci (deterministic scan), build-first-reuse (REUSE zizmor + BUILD bare-run slice), doc-authority (Class-A + principle-09). backward — codifies 2026-06-22 unpinned-zizmor incident (audit-self.yml:741). See packages/core/hooks/checks/unpinned-tool-install.ts:1.

* feat(plugin): using-rules-as-tests bootstrap + shippable skills (S3) (#665)

Author plugin/skills/using-rules-as-tests/SKILL.md (ADAPT of superpowers using-superpowers — their mechanism: invoke-skills-before-responding + the instruction-priority ladder; our content: the rules-as-tests skill set + the soft/hard honesty boundary). Add plugin/hooks/session-start (extensionless, @cc-only-rationale, self-resolves dir, ${CLAUDE_PLUGIN_ROOT}) that injects the bootstrap so the skill auto-triggers without a manual Skill invocation. Copy the consumer-facing rules-as-tests skill (SKILL.md + 5 references) into plugin/skills/. Wire SessionStart→session-start in hooks.json. v1 ships only rules-as-tests + the bootstrap (per extension.json); tool-bootstrapping deferred.

Verified: tests/plugin/bootstrap.test.sh 10/10 (session-start emits the bootstrap naming the skill, the CLAUDE.md/AGENTS.md instruction ladder, and the /install-enforcement boundary; both shipped skills carry valid frontmatter); hook-paths 10/10 (session-start extensionless+marked+wired); principle 09 + 22 green.

Prior-art: prior-art-evaluations.md#151 (superpowers using-superpowers — ADAPT: their SessionStart-bootstrap mechanism, our content).

Co-authored-by: Art <devartyhoo@gmail.com>

* chore(654): umbrella done.md (closure convention) (#666)

Co-authored-by: Art <devartyhoo@gmail.com>

* feat(plugin): S4 — ship consumer-facing agents + principle 21 plugin-tree arm (#667)

* feat(plugin): ship consumer-facing agent subset + principle 21 plugin-tree arm (S4)

Triage (T-PLUG-B): SHIP review-sidecar, living-docs-auditor, compliance-verifier (consumer-facing per extension.json + the Artifact Ownership Contract); INTERNAL aif-init, manual-rule-liveness-prober, memory-codification-auditor, orchestrator-worker-discipline, shipped-agent-liveness-prober. Copy the 3 SHIP agents verbatim into plugin/agents/ (byte-identical to the agents/ source they mirror). Register compliance-verifier in extension.json. Extend principle 21 with collectPluginAgentFiles() + arm (h) so the plugin's shipped agents are guarded by the same #551 tool-name-validity gate as agents/*.md (non-vacuity floor ≥3; the formal manifest-integrity self-test lands in S6).

Verified: principle 21 10/10 (arm (h) red with 0 agents → green after copy); principle 09 green (agents carry their REQUIRED_HEADER_DOCS doc-authority headers); plugin bash suite 4/4.

Prior-art: skipped — test arm extension for an existing capability + verbatim relocation of own agents; no new external dependency.

* test(plugin): relabel plugin-tree arm (h)→(j) to de-dup the label (S4 cold-review nit)

Prior-art: skipped — cosmetic test-label fix, no new dependency or capability.

---------

Co-authored-by: Art <devartyhoo@gmail.com>

* docs(stage-2-generate-path): Stage 0 close — land prior-art research-patch + SSOT #154#168 (#668)

Make the Stage-0 reuse-gate research-patch visible to dispatch (which reads from staging): the patch was untracked on a feature branch, so /pipeline + aif dispatch could not see it.

- Add the research-patch under docs/meta-factory/research-patches/ (was untracked).
- Append 15 SSOT entries (#154#168) for candidates 3–17 (ESLint RuleTester, Semgrep, OpenRewrite, GritQL, config-inspector, antfu/Sheriff, Biome/oxlint, OPA/Checkov, SonarQube, CEGIS, Reflexion, compiler self-hosting, Renovate, Nx); cross-link #1/#50/#66/#105 (Last-reviewed bumped, verdicts unchanged).
- Mark Stage 0 discharged in the kickoff + acceptance criterion met.

Verdict: /aif-evolve = REFERENCE -> the L3-live synthesizer stays BUILD; Oh My ClaudeCode = REJECT.

Landing-time CI-compliance fixes on the as-authored patch: add the principle-10 <!-- scope:... --> first-line annotation; collapse compound verdict cells to single principle-11-recognized tokens (dual nature preserved in each rationale). All 25 principle suites green (219 tests).

Prior-art: skipped — Stage-0 R-phase research-patch + SSOT append (#154-#168) + kickoff discharge; doc-only, no new capability or dependency.

Co-authored-by: Art <devartyhoo@gmail.com>

* feat(plugin): /install-enforcement fetch-and-wire seam (S5, Option C) (#669)

Hybrid seam reaching the HARD enforcement layer (git hooks + CI). Option C (maintainer decision 2026-06-22, overrides spec §6 bundle): rather than bundle ~2MB of install.sh's payload into plugin/install/ (which would duplicate the plugin's own skills/agents + carry the not-shipped dispatcher/pipeline + risk repo-wide CI scans), the thin plugin/install/fetch-and-wire.sh FETCHES the project's own official install.sh (pinned to plugin version; RAT_INSTALL_SOURCE override for forks/offline/tests) and runs it verbatim against $CLAUDE_PROJECT_DIR. Default dry-run (writes nothing); --apply does the real run. commands/install-enforcement.md orchestrates consent (dry-run → show → [y/N] → --apply); installing-enforcement/SKILL.md documents WHEN to wire + the honest soft/hard boundary (T16). The thin-reimplemented-wirer alternative was rejected (would discard install.sh's battle-hardened wiring, fixes #532/#533/#635). Spec §6 + plan Task 5 updated to reflect the decision; plan S6 step updated (no bundled install.sh to hash → assert fetch-and-wire version pin === plugin.json).

Verified offline (RAT_INSTALL_SOURCE=repo root): tests/plugin/install-seam.test.sh 9/9 — dry-run writes nothing+exits 0, --apply creates .husky/pre-commit+pre-push in a throwaway consumer, unknown flag exits 2; full plugin suite 5/5; principle 09 green.

Prior-art: skipped — thin seam under plugin/ (no new dependency, no packages/ code); reuses the project's own install.sh verbatim per companion-install-principle.md (install via the official top-level installer).

Co-authored-by: Art <devartyhoo@gmail.com>

* test(plugin): S6 — principle 24 plugin-manifest integrity + paired-negative (T15) (#670)

* test(plugin): principle 24 — plugin manifest integrity + paired-negative (S6)

Recursive self-test (T15): the packaging enforces its own integrity. checkPluginIntegrity() is a pure function (plugin dir + manifest dir → violations) run on BOTH the real tree (arm a, 0 violations) and a deliberately-broken fixture (arm b — tests/fixtures/plugin-broken-manifest trips V2 version-drift + V3 header-less skill + V6 dangling hook target), the principle-02 paired-negative discipline that makes the gate non-tautological. Plus real-tree arms: version pin (fetch-and-wire RAT_PLUGIN_VERSION === plugin.json.version), drift guards (plugin/agents/*.md byte-identical to agents/ source; plugin/hooks/inject-matching-rule shares the source's @dual-pair anchor + glob_match core), and T15 self-application. Added Authoritative-for headers to the two new plugin skills so V3 passes. plugin doc-authority is owned HERE (V3 for skills; the byte-identical drift guard covers plugin/agents headers transitively via principle-09-enforced agents/ sources) rather than by extending principle 09 — closes the S4 cold-review gap without touching the shared principle.

Verified: principle 24 6/6 (real-tree green, fixture red-detected); full principle suite 26 files / 226 tests; plugin bash suite 5/5.

Prior-art: skipped — test addition (principle self-test) for the plugin-packaging capability already SSOT'd at #149-152; no new dependency or external capability.

* test(plugin): harden principle 24 arm (e) — byte-identical core drift guard (S6 cold-review)

Cold-review CONCERN: arm (e) only asserted @dual-pair + glob_match() string presence, so a regression INSIDE glob_match's body would not be caught. Now extracts the matcher+injection core (from glob_match() to EOF — the only legit divergence is the relocation above it) and asserts byte-equality between plugin/hooks/inject-matching-rule and the source. Verified: real tree green; a mutation inside the glob_match region now goes RED.

Prior-art: skipped — test hardening only, no new dependency or capability.

---------

Co-authored-by: Art <devartyhoo@gmail.com>

* feat(plugin): OpenCode degradation adapter (S7) (#671)

.opencode/INSTALL.md — fetch-and-follow adapter pointing OpenCode at the shared plugin/skills/ body (via opencode.json skills.paths). Documents the ONE accepted off-CC degradation: SessionStart auto-injection of the using-rules-as-tests bootstrap does not fire off-CC → read it on demand (a documented degradation per dual-implementation-discipline §3, NOT a portability gap — the skill content is fully portable). Tool mapping (CC Skill→OpenCode skill tool, Task→@mention, etc.); the hard layer routes through the portable plugin/install/fetch-and-wire.sh seam (same honest soft/hard boundary). extension.json gains a $plugin-packaging note reconciling it with the plugin payload (source paths kept — install.sh + principle 21 read them; not repointed). AGENTS.md cross-harness layer is referenced from the adapter, not duplicated.

Verified: tests/plugin/opencode-adapter.test.sh 7/7; full plugin bash suite 6/6; markdownlint 0.

Prior-art: skipped — docs + a small acceptance test, no new dependency or capability (ADAPT of superpowers' .opencode fetch-and-follow pattern, SSOT-adjacent).

Co-authored-by: Art <devartyhoo@gmail.com>

* feat(plugin): S8 — README per-harness install + fetch ref → main (umbrella finale) (#672)

* feat(plugin): README per-harness install + fetch ref → main (S8)

README 'As a Claude Code plugin (per-harness)' section: the marketplace-add/install flow, skills auto-trigger, the honest soft/hard boundary (T16 — the plugin never wires git/CI; /rules-as-tests:install-enforcement does, opt-in), the OpenCode path + accepted degradation, and plugin-vs-./setup. §Why this exists untouched (read-only per the Artifact Ownership Contract).

fetch-and-wire.sh: decouple RAT_INSTALL_REF from the plugin version → default 'main'. Evidence: the framework's latest release tag v0.3.0 is 2026-05-06, staging is 72 files / +17551 ahead in install.sh + payload (incl. the hardening fixes #531/#551/#635/#636 the S5 cold-review valued); origin/main (2026-06-18) carries the current installer (only 65 lines behind staging). Pinning to v<plugin-version> would ship a 6-week-stale install.sh. Plugin keeps its own 0.1.0 version line; a reproducible per-plugin release tag is a future maintainer action. Spec §6 updated.

Verified: composite e2e on a throwaway consumer — SessionStart bootstrap fires, inject-matching-rule reads consumer rules, /install-enforcement seam dry-run writes nothing + --apply wires .husky pre-commit/pre-push + CI, manifest version-parity True; all 6 plugin bash tests + principle 21/24/09 (39) green; README markdownlint 0.

Prior-art: skipped — docs + a config-default change under plugin/, no new dependency or capability.

* docs(plugin): umbrella closure — plugin-packaging done.md (S8)

CLAUDE.md Umbrella closure schema: Final PR #672, all S0–S8 merged to staging. Records the stage-PR map + 3 maintainer-owned residuals (live /plugin install unproven-from-automation, install.sh-vs-plugin agent divergence, reproducible-tag option).

Prior-art: skipped — closure doc, no new dependency or capability.

---------

Co-authored-by: Art <devartyhoo@gmail.com>

* test(validator): L4 adversarial hardening — tautology/never-firing/conflict fixtures (paired-negative) (#674)

Adds three adversarial SynthesisPlan fixture pairs (bad + valid) that
exercise the exact reject surfaces of Gates 2, 4, and 6:

- gate-tautology.adversarial.test.ts (T1): no-restricted-imports forbidding
  'react' fires on negative-corpus/unrelated.tsx (Gate 4 fail); paired with
  a lodash-forbidding rule that does NOT fire on the corpus (Gate 4 pass).

- gate-rule-tester.adversarial.test.ts (T2): comment-only input that never
  triggers no-restricted-imports (Gate 2 fail); paired with a lodash-import
  input that correctly fires (Gate 2 pass).

- snapshot.adversarial.test.ts + expected-adv-*.json (T4): full validate()
  aggregate snapshots for each bad fixture confirm ok:false and the right
  gate routes to 'fail'. Separate from existing self/next-16 snapshots.

T3 (Gate 6 conflict): adversarial orphan-ref + snippet-drop cases are
already present in gate-conflict.test.ts:52-99; no new file needed.
Gate 6 snippet-drop is additionally covered via snapshot.adversarial.test.ts
using a no-debugger rule absent from eslintConfigSnippet: '{}' (clean Gate
2 + 4 pass, Gate 6 fail).

Gate source files (gate-*.ts, validate.ts) are UNCHANGED — hardening
adds tests/fixtures only. validator suite: 45 tests, all pass.

Prior-art: prior-art-evaluations.md#154–#168 (Stage-0 SSOT for L4 generate-path hardening — adversarial corpus design patterns, no upstream production-grade paired-negative harness for ESLint-rule validation existed at SSOT survey time).

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(deps): unpin revoked eslint 10.4.1 → ^10.4.0 + sync root lockfile (preset-react-native/spa) (#676)

eslint 10.4.1 was unpublished from the npm registry (version-API → 404; tarball
still served by the CDN), so any root-level `npm install` / `npm ci` /
`--package-lock-only` failed with ETARGET, leaving the root lockfile unable to
regenerate. Consequently the new workspace packages preset-react-native (#646)
and preset-react-spa (#661) were never added to package-lock.json.

guard-liveness-fullsweep (pull_request → main only) is the one required check
that runs `npm ci` on the root workspace, so the drift surfaced only on promote
PR #673 — red on "Missing: @rules-as-tests/preset-react-native from lock file".

Fix: pin packages/core eslint to ^10.4.0 (latest existing in the 10.4 line; API
stable vs 10.4.1) and regenerate the root lockfile. Verified locally: npm ci
rc=0; guard-liveness:fullsweep GREEN (v1 ESLint 8/0, v1.5 cmd 3/0, v3 structural
pass); lockfileVersion stays 3 (node20/npm10 CI-compatible); linux-x64 bindings
present; package count 458→770 (preset deps).

Prior-art: skipped — dependency version-pin fix (revoked eslint 10.4.1 → existing 10.4.0) plus mechanical lockfile sync; no new capability introduced.

Co-authored-by: Art <devartyhoo@gmail.com>

* fix(dispatcher): classify aif plan_ready + review as RUNNING in monitor-classify (#675)

monitor-classify.sh fell through `plan_ready` and `review` to the `*)` arm,
emitting UNKNOWN:<status>. dispatcher SKILL.md §2.2 branches only on
RUNNING:* / DONE:* / PARKED:* / ERROR:* prefixes, so an UNKNOWN:* result is
unhandled and the single-poll-per-turn loop stalls/misfires — and every aif task
passes through plan_ready (backlog→planning→plan_ready→implementing→review→done).

Map plan_ready and review to RUNNING, mirroring the canonical
mapAifStatusToTaskStatus (packages/runtime-bridge/src/aifWsStatus.ts:82), which
classifies plan_ready / implementing / review all as 'running'. blocked_external
stays intercepted by the is_parked check (PARKED); genuinely-unknown future
statuses still surface as UNKNOWN (catch-all preserved).

Tests (packages/core/skills/dispatcher/monitor.test.ts): adds positive cases
(plan_ready→RUNNING, review→RUNNING, backlog→RUNNING) and a paired-negative
regression-guard block asserting running states never classify as UNKNOWN. 16/16
green; verified RED (4 failing) before the fix.

Live-hit 2026-06-22: a poller dispatching stage-2-generate-path Stage 1 exited
prematurely on UNKNOWN:plan_ready, treating a running task as terminal.

Prior-art: skipped — bug fix + test additions for an existing capability, no new dependency/module/capability surface.

Co-authored-by: Art <devartyhoo@gmail.com>

---------

Co-authored-by: Art <devartyhoo@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
artyhoo added a commit that referenced this pull request Jun 22, 2026
… 10.4.0 (#679)

#676 synced the root lockfile + preset-react-native/spa + packages/core/package.json (^10.4.1 → ^10.4.0) but missed regenerating packages/core/package-lock.json, which stayed resolved to eslint 10.5.0 — a version unpublished from the npm registry (packument 404; `npm view eslint@10.5.0` → E404, latest = 10.4.0). The resolved tarball is still CDN-served (HTTP 200), so `npm ci --prefix packages/core` in audit-self.yml / framework-self-template-render.yml has not broken yet — but it is a time-bomb: once npm drops the dead 10.5.0 blob, every cold-cache `npm ci` against this lock fails ETARGET, and any `npm install` silently re-resolves to 10.4.0 and rewrites the lock.

Fix: re-resolve eslint against the current manifest → 10.4.0. Lock spec aligned with package.json (^10.4.0); node_modules/eslint version + resolved + integrity updated; @eslint/plugin-kit range 0.7.2 → 0.7.1 (10.4.0's requirement, satisfied by existing 0.7.2 resolution).

Verified: `npm ci --prefix packages/core --dry-run` rc=0, 'add eslint 10.4.0'; no eslint-10.4.1 / eslint-10.5.0 references remain.

Prior-art: skipped — dependency version-pin fix (revoked eslint 10.5.0 → existing 10.4.0) plus mechanical lockfile sync; no new capability introduced.

Co-authored-by: Art <devartyhoo@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant