agents/security-auditor.md: fresh read-only-on-code SAST gate. Pinned
rulesets p/security-audit + p/secrets + p/owasp-top-ten (owasp REQUIRED —
measured: the 2-ruleset baseline misses SQLi + path-traversal entirely on
realistic Flask code), never --config auto, never auto login (BDR-048).
Severity map: secrets ERROR → CRITICAL, other ERROR → HIGH (block),
WARNING/INFO → reported. gate mode (diff, no Write) vs audit mode (Write
only to REPORT, rule-locked). DEGRADED (semgrep absent) still runs the
checklist and still blocks — never a vacuous pass (LRN-048). Anti-gaming:
a new un-gated nosemgrep suppression is BLOCKING. PROOF mandatory, mute
auditor never a PASS, blind (no iteration history), blocks HIGH/CRITICAL
only (LRN-047).
Grafts: onboard STEP 6 L3a dispatches it in audit mode (report
.onboard-audit/semgrep.md) in BOTH gstack branches — complement to cso
(cso is a gstack submodule, unmodifiable); synthesis picks it up via the
existing .onboard-audit/ sweep. audit-delta security axis runs the SAST
pass first, folds findings into the existing gate/fix/re-verify flow.
lib/tests/security-auditor.test.sh: 28 structure locks green, shellcheck
clean. Behavioral dogfood (fresh agents on a planted fixture):
BLOCK(9) on the vuln commit (2 secrets→CRITICAL, semgrep+checklist
complementarity — checklist caught the 6 semgrep missed off-context);
BLOCK(1) on a new nosemgrep suppression (understood semgrep's 0 was the
mask); DEGRADED → BLOCK(7) on grep-detectable secrets with semgrep hidden.
FP measured on real repos (faunosteo, game): owasp adds only hygiene
findings, contained by diff-scoping.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpphkdTosUzokBDNG7PToS
lib/contract-interview.md: mandatory upstream passage for all orchestrators.
Verbatim REQUEST (immutable), proportional questions (complete request =
zero, max 3 one batch), testable criteria + file scope, written to disk
immediately (.claude/tasks/contracts/<date>-<slug>-<HHMM>.md — a context-only
contract dies at compaction). Lifecycle: enrichment only at human gates
([gated] marker, scope micro-gate), supersedes for re-scope, aborted runs
deleted or committed status:aborted — never left dirty. Hand-off = path,
not restatement.
agents/verifier.md: fresh read-only verifier. Reads the contract from disk,
renders VERIFY — VERDICT: CONFORME | ECARTS(n) | ERROR. Blind: never
receives iteration history. PROOF line mandatory (LRN-048), UNVERIFIABLE
never MET, mute verifier never a PASS (retry once fresh, 2nd structural
failure = human escalation). Orchestrator protocol documented in-file
(max 3 iterations, re-verify request before security).
lib/tests/contract-verifier.test.sh: 31 deterministic structure locks on
the load-bearing doctrine clauses — green, shellcheck clean.
Behavioral dogfood (2 fresh subagents on a planted fixture): gap case →
ECARTS(2) exactly as planted (NOT-MET located + out-of-scope flagged);
conform case with injected fake iteration history → CONFORME, noise
ignored, real python spot-check as evidence. Both outputs parse-clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpphkdTosUzokBDNG7PToS
The 07-02 tightening left bare tokens common in non-UI talk (design, component, theme, transition, frontend, palette) -> ~6 false-fires/session during the ECC config audit. Dropped them; dashboard now word-boundary matched (kills the ecc_dashboard.py filename match, keeps 'admin dashboard'); kept animation; added 'front-end design' bigram. Each fire now logs time+token+excerpt to a light file so 're-firing?' is measured, not argued. Regression test 18/18, shellcheck clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpphkdTosUzokBDNG7PToS
Blocks Edit/Write to guardrails (settings.json + .claude/settings*, lib/gitflow.sh, .githooks/*, doctor.sh, hooks/*.sh self-guard, lib/tests/*, lint) so a gate can't be weakened to pass an error. Bypass = one-shot sentinel .claude/.config-edit-ok (non-empty reason, logged+consumed), not an env-var. Adaptation from the ECC second-look (BDR-047 corrob): own bash idiom, not ECC's Node dispatcher. shellcheck clean, test 20/20.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpphkdTosUzokBDNG7PToS
gitflow_finish ignored its <type> <name> args and always merged the
checked-out branch — `finish bugfix audit-bugs` run from
feature/audit-tokens merged the wrong branch (audit UX trap, 2026-07-02).
Args are now an optional safety ASSERTION: if present and != current
branch, refuse loudly (rc 2) instead of merging the wrong thing. No args
= unchanged (the only real caller, SKILL.md:36, passes none). +7 T12
regression assertions.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpphkdTosUzokBDNG7PToS
Measured: 6 agent descriptions = ~2.2k tokens injected EVERY session
(the single largest plugin contributor) for a toolkit useful only when
reviewing PRs. enabledPlugins → false; removed from full+backend
profiles (BDR-017 caveat already accepts full excluding rarely-used
items); audit.profile KEEPS it = profile reactivation channel.
Per-PR-session: claude plugin enable pr-review-toolkit@claude-code-plugins
(or bash lib/profile.sh apply audit); a later 'profile set full'
re-disables it via the MANAGED_PLUGINS lifecycle.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016zA3Qh2Q1QpcGXzXxKeDHR
$MEM/../skills resolved to .claude/skills/ (the LRN-042 parasite, removed
2026-06-30 by make plugin Step 8.5), not the real skills/. Green at build
time only because the parasite still existed — green-for-wrong-reason
(LRN-077 class); red ever since. Suite back to 20/20.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016zA3Qh2Q1QpcGXzXxKeDHR
rtk lives at ~/.cargo/bin but the hand-managed .bashrc lost the cargo
line: command -v failed in hook AND tool shell, so the hook no-op'd with
a stderr warn on every Bash call — input compression silently OFF.
- Resolve RTK_BIN by probing known install dirs (LRN-036 class).
- Substitute the ABSOLUTE path at the rewrite head: a bare 'rtk …'
exits 127 in the tool shell, whose PATH the hook cannot fix (proven).
- Compound rewrites carrying further bare rtk segments pass through
unrewritten: quoted text (commit messages) makes a global substitution
unsafe — lose compression, never emit a command that 127s (proven:
a commit chain 127'd mid-flow).
- detect_rtk probes the same dirs so the banner reports capability.
- Re-pinned .rtk-hook.sha256: the rtk BINARY verifies the hook against
it at execution time and refuses a modified hook — the pin is live
machinery, not a vestige; coupling documented in the header.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016zA3Qh2Q1QpcGXzXxKeDHR
Standalone /capitalize /close /prune-memory /reconcile no longer lean on the .claude/** hook exemption when run on main/develop: the aiguillage branches them to chore/* off develop before writing. New chore type (base develop, finish->develop) added to the lib; hook unchanged (chore/* non-protected). Closes the leak where standalone memory work (memory IS the work, no code branch to follow) landed direct on a protected base. 64/64 gitflow-test green, shellcheck clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RNaYKPEkjH1jbgoX1TwKMX
/release-candidate cuts a release by orchestrating the existing gitflow
release mechanic (start from develop; finish fan-out main+develop+delete)
and adding the one piece the lib lacks: the version tag.
- skills/release-candidate/SKILL.md: thin orchestrator — preconditions →
gitflow start release → prep (version.txt + CHANGELOG, breaking doc'd) →
run-tests gate → human WHEN-to-release gate → gitflow finish → git tag -a
vX.Y.Z (in the skill, lib untouched) → push (gated).
- lib/tests/run-release-candidate.sh: throwaway-repo flow replay. RC_TAG=0
reds the tag (gitflow fans out but never tags); RC_TAG=1 → 5/5.
- CLAUDE.md: Skill routing line. CHANGELOG [Unreleased]: /reconcile +
/release-candidate under Added (so the eventual v4.0.0 captures them).
Tag scheme vX.Y.Z continues the version.txt/CHANGELOG lineage. writing-skills TDD.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C6bUdvHnajCNzgVQefZowj
/reconcile confronts declarative sources (TODO checkboxes, registry
statuses, ## Index) against real git/fs state and surfaces the gaps,
in 4 categories + contradiction candidates.
- lib/reconcile.sh: engine — body-only enumeration (never the Index),
git/fs oracles, BLK last-block-wins status, lexical deferral sweep,
contradiction candidates, pure reconcile_verdict kernel.
- lib/tests/run-reconcile.sh + fixtures (neutral-named): 20/20;
recursive-coherence T1 reds if the engine reads the Index (teeth).
- skills/reconcile/SKILL.md: thin orchestration + A/B/C write-back gate,
honest limits (lexical deferrals, contradictions surfaced not asserted).
- CLAUDE.md: Skill routing line.
Founding principle: never trust a declarative source as an oracle — the
skill practices what it preaches (tested). Built via writing-skills TDD.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C6bUdvHnajCNzgVQefZowj
(1) lib/doc-shape.sh — deterministic shape oracle re-checks each LLM-classified
MINOR patch before the silent auto-commit; a shape-suspect patch (added heading
/ oversize / new file / non-doc) escalates to the EXISTING SIGNIFICANT gate.
Genuine MINOR still auto-commits (zero friction, BDR-036 preserved). A structural
floor under the LLM call, not a semantic SIGNIFICANT-detector (BDR-040).
(2) lib/doc-commit.sh — guard the commit itself: a rejected git commit (pre-commit
hook / protected branch / signing) now fails loud with exit 5 + empty stdout,
instead of a false "committed" + the previous HEAD's hash + exit 0 that left docs
silently uncommitted (LRN-071, 3rd occurrence of the swallowed-commit pattern
after LRN-066 and LRN-068/BLK-012).
Wired doc-syncer STEP A4 (whole-set escalation: no=revert all, select=keep subset)
+ doc-commit.md (rc-5 consumer row + ACKNOWLEDGMENTS coherence).
TDD RED->GREEN: run-doc-commit.sh 32/32, run-doc-shape.sh 19/19, behavioral C/D.
shellcheck clean. Branch-guard (3) deferred.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C6bUdvHnajCNzgVQefZowj
lib/tests/run-doc-behavioral.md — in-vivo whole-chain check (twin of run-behavioral.md for memory). Scenario A: doc-syncer patches a public doc, the include commits it surgically with dangling code present (coupled + surgical). Scenario B: a forbidden .claude/ path in PATCHED_FILES → helper refuses (rc 4), nothing half-committed, offender named (fail-closed + loud). Complements the 28-assertion deterministic suite (run-doc-commit.sh).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ho5EQCFTSvYamuRtVZpp2d
doc-syncer now emits PATCHED_FILES — every public-doc file created/modified this run, ONE PATH PER LINE — in both STEP 9 OUTPUT (full audit) and AUTO MODE STEP A4 (the path orchestrators call). NONE stays silent (no line → doc-commit sees empty → no-ops). Additive: detection/patching logic and the `auto-mode scope:` input contract are unchanged → callers unaffected.
Separator contract, producer↔consumer aligned + proven: newline is doc-syncer's OUTPUT format (paths carry no newlines); the agent splits on newline and passes EACH path as a SEPARATE argv element to lib/doc-commit.sh. The helper takes argv (no in-band separator) → a path with spaces survives as one argument. lib/doc-commit.md spells this out (never flatten to a space-joined string + re-split, which would mis-split a spaced path the helper then silently drops). New test T7 PROVES it on real git: 'docs/My Guide.md' → committed as one file (28/28, shellcheck clean).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ho5EQCFTSvYamuRtVZpp2d
lib/doc-commit.md — TAIL of the doc-sync step, twin of capitalize-commit.md. Tells an orchestrator how to commit ONLY the docs doc-syncer patched (PATCHED_FILES), surgically, BEFORE FINISH so they reach the merge/PR.
Centerpiece: a report-by-(rc, hash) table covering ALL four exits — rc 0 visible surface (files + agent-composed change summary, NOT a bare count), rc 0/empty no-op, rc 3 unsafe-state manual fallback, and rc 4 SCOPE VIOLATION relayed LOUDLY as an upstream BDR-022 anomaly (name the offender, do not swallow or hand-commit the rest — the refusal IS the alarm).
Attribution locked in 3 places: the rc 0 <one-line> summary comes from the AGENT (in-thread patch context), the helper returns only the hash — otherwise the surface degenerates to the bare count that the gate-replacement was meant to avoid. Two conscious acknowledgments inline: MINOR non-gated-by-choice (surface replaces gate); partial init-project fix linking BLK-010.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ho5EQCFTSvYamuRtVZpp2d
Shared include lib/analyze-before-plan.md (two-pass on '## <PREFIX>-' headings, disposition-not-reading invariant, guarded no-op). Wired into the dev flows: ship-feature STEP 0d (analyzer code+memory, INPUT INJECTION into brainstorm/plan + STEP 3 reconciliation gate), bugfix STEP 2.5 (blockers-first), feat STEP 0.6 (decisions-first, MINI-PLAN names in-force or states none), hotfix opt-in. analyzer gains a RELATED MEMORY output section pointing at the include (DRY). init-project / onboard no-op by construction (guarded scan on absent/empty registries).
Mirror of the coupled-capitalize write-after (BDR-034): read-before / write-after bookend.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ho5EQCFTSvYamuRtVZpp2d
Shared snippet referenced by every dev flow's capitalize step (design-gate.md
pattern). Tail of capitalize: commit the already-approved memory via
memory-commit.sh, surgically. Documents the stdout hash contract, the 2-hash
non-confusion (memory commit vs code-commit anchored in entries), the orchestrator
ordering (before FINISH), and what it must NOT do.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W9sqAwZxBMZSynZoVrEJhd
commit_memory now routes diagnostics to stderr and prints ONLY the memory-commit
short hash to stdout, so the capitalize-commit include can report it. Proven:
- T6: commit→hash (matches independent rev-parse), no-op→empty, unsafe→empty+exit3.
- T7: double run creates exactly one commit (real run, not by construction).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W9sqAwZxBMZSynZoVrEJhd
Foundation for the coupled-capitalize invariant (Frame 2): commit ONLY
.claude/memory + .claude/tasks, never `git add -A`. Safety lives in the
pathspec because automation removes the human diff review.
Proven on real git behavior, not assumed:
- T1/T2: dangling code (untracked or pre-staged) never embarked.
- T2-bis: `git commit -- pathspec` takes the working tree, not a stale index.
- T3 idempotent, T4 fail-closed on broken state, T5 TODO.md in scope.
_changed_paths filters to paths with real changes: `git commit -- pathspec`
aborts the whole commit on a no-match pathspec (e.g. empty .claude/tasks),
unlike `git add` which tolerates it.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W9sqAwZxBMZSynZoVrEJhd
Add §4 to lib/design-gate.md: when a non-trivial design task hits a motion
signal (animation/transition/hover/motion/animate, now in §DETECTION) and the
stack is motion-eligible with no anim lib installed, surface a single-line
suggestion for the recommended `motion` package. Suggest-only (install on
explicit consent), non-blocking, stateless (always one line, no marker; calls
the helper, no 3rd copy of the lib list).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W9sqAwZxBMZSynZoVrEJhd
Clearer scoped name for the W3C + WCAG skill. Updated: folder (git mv),
frontmatter name, H1 title, command refs, CLAUDE.md routing, 6 profiles
(functional — activate the skill by folder name), cross-refs in
harden/seo/depth-matrix/client-handover, agent dispatch refs, README +
USAGE tables.
Confidentiality: the client-deliverable leak-guard regex
(client-handover-writer.md) now matches BOTH /web-validate and legacy
/validate, so older client docs stay covered.
Left intentionally: validator-analyzer agent name (lockstep with
subagent_type + registry), .validate-cache/ + VALIDATE.md (audit-file
family {SEO,GEO,HARDEN,CSO,VALIDATE}.md), .claude/ history (append-only),
CHANGELOG old entry (added a new "renamed" entry instead). NL trigger
keywords kept so "validate" still routes here. Third-party html-validate
untouched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W9sqAwZxBMZSynZoVrEJhd
gstack stays OFF by default (no per-skill symlink in skills/, zero context
cost). enable_skill now gains a gstack branch: a skill absent from skills/
and skills-disabled/ but present in the skills-external/gstack submodule is
symlinked in on demand when a profile lists it; disable_gstack_not_in()
parks it again on an unrelated profile.
This makes `set full` (which lists 35 gstack skills) work without 35 bogus
"missing — try: bash link.sh" warnings, without abandoning the OFF-by-default
policy. The old remedy message was wrong (link.sh never creates gstack
skills) and is replaced with submodule-aware messages.
Refs BDR-030.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0169vjUD1sP9Nx4ZiCa8wvAw
The "unknown -> exit 11" path triggers when `command -v claude` fails. Root
cause is NOT the interactive alias (claude->dtach_claude) not surviving the
subshell — that's true but harmless: the real binary is on the inherited PATH,
so `command -v` finds it in a normal `bash script.sh` (proven: toggle-external
and the gate both resolve claude). The actual lever is PATH carrying the nvm
node bin. A skill/hook that shells the gate out with a sanitized PATH, or a node
upgrade moving the version-pinned nvm path, loses it.
ensure_claude_on_path(): if `command -v claude` already resolves, do nothing;
else probe known install dirs (~/.claude/local, ~/.local/bin, /usr/local/bin)
and the nvm glob, prepending the bin dir — which carries BOTH claude and its
node runtime (claude's shebang needs node, same dir). nvm keeps old versions
after an upgrade, so pick the newest that ships claude via sort -V, not the
first glob match. If nothing resolves, command -v still fails -> unknown ->
exit 11 (fail-visible net stays).
Verified: shellcheck clean; normal PATH -> READY exit 0 (function returns early,
no regression); PATH=/usr/bin:/bin (sanitized hook) -> now resolves claude via
the nvm glob and reports REAL magic state (READY exit 0), where before the fix
it was exit 11 unknown.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When `claude` is unreachable, the plugin/MCP checks (magic, ui-ux-pro-max —
the most important design tools) return `unknown`. The old verdict folded that
into a silent `READY` exit 0 — a fail-OPEN: the gate granted "proceed" exactly
when it had verified nothing. Contradicts the fail-closed-on-uncertainty rule.
Now fail-VISIBLE (not strict fail-closed — claude can be merely slow, false
blocks would get the gate ignored): a third outcome READY BUT UNVERIFIED with a
distinct exit 11. It proceeds, but says so loudly and names the unchecked tools,
telling the user to confirm with `claude mcp list` / `claude plugin list`. The
distinct non-zero code also stops a naive `if gate; then proceed` shell caller
from silently passing. Also covers the non-reproducible "transient claude absent
after apply" flake seen in live testing.
design-gate.md §DECISION updated: exit-code line + a real branch-3 state for 11.
Verified: shellcheck clean; reachable+active -> READY exit 0; claude off PATH ->
READY BUT UNVERIFIED exit 11 naming magic+ui-ux-pro-max; magic off -> INCOMPLETE
exit 10 (trip branch intact after the restructure).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move the real secret out of the git tree: the key lives in ~/.claude/.env
(outside the repo), and link.sh symlinks repo/.env -> ~/.claude/.env so
`source "$REPO/.env"` resolves transparently. The secret never enters git —
not as content (it's a link) and not by accident (gitignored).
link.sh: add link_env() — verify ~/.claude/.env exists + has MAGIC_API_KEY
(warn, never create/copy the secret), then create repo/.env -> ~/.claude/.env.
Defensive + idempotent: links only when repo/.env is absent or already the
right symlink; a residual REAL repo/.env is left untouched with a migrate hint
(never clobbered, so the secret can't be destroyed).
.gitignore: harden .env -> .env + .env.* + !.env.example (covers .env.local,
.env.bak, .env.save; keeps the template tracked).
Messages point at ~/.claude/.env (the canonical edit location) instead of the
ambiguous $REPO/.env: design-tool-gate.sh gate output, design-gate.md
(branch 3 + IMPORTANT), toggle-external.sh, install-plugins.sh.
Verified: shellcheck clean (link.sh, toggle-external.sh, design-tool-gate.sh);
link.sh created the symlink (1 change, idempotent re-run); repo/.env absent
from git status; magic-off path still exits 10 with the ~/.claude/.env hint.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
design-tool-gate.sh: deterministic design-toolchain state check. Reads the
design-core tools from design.profile's `# GATE-BLOCK:` allowlist + their
types via `profile.sh show design --plain` (claude-free parse contract),
checks each on its own channel (skill symlink / claude plugin list / claude
mcp list / command -v). Never reads disabledMcpServers. Exit 0 ready · 10
incomplete · 2 error.
Remedy is always a profile (/profile design), never an atomic tool toggle —
the profile system stays the single source of truth for activation. magic is
required-but-manual: it TRIPS the gate (not advisory) and the output names
the MAGIC_API_KEY step. Non-design tools bundled in the profile (browse,
plan-*, design-shotgun, graphify) are excluded from the trip via GATE-BLOCK,
so the gate fires only on real design tools.
design-gate.md: §DECISION rewritten profile-based (tier → run script → branch
on 3 groups), replacing the old atomic "ask user to activate ui-ux-pro-max".
§DETECTION unchanged. design.profile: add the `# GATE-BLOCK:` allowlist
(8 design-core tools); it is a comment, so read_profile/--plain are unaffected.
Verified: shellcheck clean; magic-off (real design profile) → exit 10 +
API-key line; all active → exit 0; browse-off (non-GATE-BLOCK) → exit 0,
no trip.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
list: ITEMS column with compact per-category counts (e.g. 12s·1p·1m·1c),
left block aligned; full descriptions kept (CLI output, not 80-strict).
show: grouped by type (gstack/external/personal/plugin/mcp/cli) with
status; redundant per-line TYPE column and plugin marketplace suffix dropped.
show --plain: parsable "type<TAB>name" list, no status, zero claude calls —
the contract for the upcoming design gate. All derived from .profile files.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X3e8LaH2vymmxyh36h3jFU
A gstack submodule bump added `spec` (v1.47) and the iOS device-farm
5-skill set (v1.43) but gstack `./setup` was never re-run, leaving all 6
as source-only — unlinked, invisible to Claude and untouched by
reset/`gstack on`.
- Link `spec` only, surgically: skills/spec/SKILL.md symlink matching
gstack setup's per-skill pattern (real dir + absolute SKILL.md link).
Platform-agnostic, useful planning skill. Added to full + web-full
profiles (must be in the active `full` profile or `set full` would
re-disable it).
- iOS skills deliberately NOT linked: this is a Linux host, and the iOS
device-farm needs a Mac daemon + Tailscale + physical iOS devices.
Linking them would add 5 dead skills plus passive token cost.
- Complete the .gitignore gstack allowlist: add the 12 entries missing
from it — `spec`, the 5 `ios-*` (so they stay ignored if a future
setup on a Mac materializes them), and 6 already-parked skills
(document-generate, landing-report, scrape, setup-gbrain, skillify,
sync-gbrain). The parked ones matter because `gstack on` moves parked
skills into skills/, where any allowlist gap becomes untracked git
noise. Also drop the stale `skills/checkpoint` entry (renamed to
context-save).
Verified: `profile show full`/`web-full` report spec enabled; the
source-vs-allowlist drift check is now empty; profile.sh still parses.
The gstack submodule pointer bump itself is left uncommitted (separate
pending work).
Memory: BLK-007 -> resolved; LRN-025 (gitignore allowlist must cover all
toggleable skills incl. parked, else enabling one = git noise).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Centralize gstack toggling in the `profile` command without losing the
active-profile label.
- `gstack on` re-enables ALL parked gstack skills (moves
skills-disabled/gstack__* back) but does NOT touch .active-profile,
so the user layers full gstack on top of their current profile and
the statusline label is preserved. Unlike `reset`, which clears the
label to "none".
- `gstack off` disables gstack skills not listed in the active profile;
errors cleanly when no profile is active (needs one to know what to
keep).
Refactor (behavior-preserving): extract three shared helpers
`enable_all_gstack`, `disable_gstack_not_in`, `parked_gstack_count` and
rewire `cmd_reset` + `cmd_set` to reuse them instead of duplicating the
symlink-toggle loops. Wire `gstack` into main() dispatch, usage(), and the
header usage block.
Docs: SKILL.md argument-hint, examples, and output-policy updated. The
generic `make profile cmd="gstack on"` target already covers Make usage.
Verified: shellcheck CLEAN, `bash -n` OK, 6-case test (help, bad-action,
off-with-no-profile, on, off-trim, on-cycle) with final assertion that the
live symlink state was restored exactly to its pre-test value.
Memory: capitalize BDR-018 (decision), LRN-024 (DRY helper-extraction
pattern), BLK-007 (6 gstack source skills ios-*/spec unlinked post
submodule bump — open follow-up), EVAL-002 (self-eval, false "full.profile
bug" flag corrected pre-edit). Backfill index drift: BDR-017, BLK-005/006.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
OCR/image-based PDF pipeline: convert pages to PNGs, read with Claude
Vision (bypasses unreliable OCR text layer), translate with cross-page
glossary consistency, reconstruct faithful HTML via /design-html.
5 steps: deps check → page images + assets → style analysis →
page-by-page read+translate → HTML reconstruction → visual QA.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
frontend-design (Anthropic's anti-AI-slop skill) was installed but not
referenced in the plugin-advisor decision table, recommended sets, or
conditional rules — nor in the design-gate detection logic.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Source: anthropic-agent-skills plugin cache → skills-external/frontend-design/
Auto-install via install-plugins.sh step 8b, auto-update via update-all.sh
step 7.1, symlinked by link.sh (refactored to loop over EXTERNAL_SKILLS).
Added to profiles: design, web, web-full, full.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
`bash lib/profile.sh current` is 12s+ — far too slow to call from the
statusline hook (runs on every keystroke). Add a one-line cache file at
`<repo>/.active-profile`, written by `cmd_apply` and `cmd_reset`. The
statusline reads the file directly with a single `head -n1`, no
sub-shell into `profile.sh`.
Behavior:
- `bash lib/profile.sh set <name>` (which ends in `cmd_apply`) and
`bash lib/profile.sh apply <name>` both write `<name>` to
`<repo>/.active-profile`.
- `bash lib/profile.sh reset` writes the literal `none`.
- Statusline inserts the cached profile name between the plan segment
and the context-bar segment, e.g.
`Opus 4.7 | claude (master) | Max | full | ████░░░░░░ 42% | 3m`.
- Missing or empty cache → statusline shows `?`.
Cache file is gitignored — it tracks runtime state, not source.
Co-Authored-By: Claude <noreply@anthropic.com>
`lib/profile.sh:43` set `REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"`.
Default `cd` preserves symlinks (logical pathname), so when the script is
invoked via the `~/.claude/lib/profile.sh` symlink, `$REPO` resolves to
`/home/bchanot-ubuntu/.claude` instead of the real repo path. `$SKILLS_DIR`
still works because `~/.claude/skills` is itself a symlink to the repo's
`skills/`. But `$DISABLED_DIR` ends up at `~/.claude/skills-disabled` (a real
sibling directory containing only stale npx-skill symlinks) while the actual
disabled gstack skills sit at `<repo>/skills-disabled`.
Symptom: `bash "$HOME/.claude/lib/profile.sh" current` returns
`none (all gstack skills enabled — no profile set)` even when a profile is
applied — because `find $DISABLED_DIR -name 'gstack__*'` returns 0.
Adding `-P` to `cd` forces physical-path resolution so `$REPO` always points
to the real repo regardless of how the script was invoked. `cmd_current`
now correctly reports `full (100% match, 14 gstack skills disabled)`.
Co-Authored-By: Claude <noreply@anthropic.com>
gstack upstream renamed the `checkpoint` skill to `context-save`
(it was shadowed by Claude Code's native /checkpoint rewind alias).
`/profile set full|dev|backend|web|web-full` was emitting
`⚠ missing: checkpoint — try: bash link.sh`, but link.sh only
materializes symlinks for skills that exist upstream — it cannot
resurrect a renamed skill, so the suggested next step was a dead
end.
Replace `checkpoint` → `context-save` in all 5 affected profiles
so the warning goes away and the new skill is enabled when the
profile is applied.
CLAUDE.md routing rule (line 193) also updated locally but left
uncommitted because the file carries an unrelated in-progress
graphify section rewrite.
Co-Authored-By: Claude <noreply@anthropic.com>
Superset of web-full + dev + audit + plan tools — covers brainstorm →
design → architecture review → scaffold → implement → ship → audit
pipeline needed by /init-project to produce a real MVP in one session.
Also renames cmd_current "no-profile" sentinel from "full" to "none"
to avoid collision with the new profile name.
Co-Authored-By: Claude <noreply@anthropic.com>
`enable <tool>` for npx/external skills (darwin-skill, find-skills,
emil-design-eng) only handled two states: symlink in skills-disabled/
(move) and symlink in skills/ (already enabled). Missed the state
right after `make plugin` where the source dir exists at
~/.agents/skills/<tool> but no symlink has been created yet — first
run errored "not installed — run: make plugin" misleadingly.
Add a third branch: when the resolved source dir exists, create the
symlink in place. Resolve source path per tool (skills-external for
emil-design-eng, ~/.agents/skills for darwin-skill/find-skills). Error
message now names the path checked so the caller can verify install
vs symlink state without rereading the script.
Co-Authored-By: Claude <noreply@anthropic.com>
Ship lib/profile.sh + 9 profiles in lib/profiles/. A profile is a
plain-text file listing items + types (gstack | personal | external |
plugin@<marketplace> | mcp | cli). `profile set <name>` enables the
listed items and disables the rest:
- gstack/personal/external skills: symlink toggle skills/ ↔
skills-disabled/ (gstack__<name> prefix to avoid collisions; no
prefix for personal/external).
- plugins typed `plugin@<marketplace>`: actually toggled via
`claude plugin enable|disable <name>@<marketplace>`. Allowlist:
MANAGED_PLUGINS = ui-ux-pro-max, plugin-dev, pr-review-toolkit.
Denylist: PROTECTED_PLUGINS = caveman, security-guidance,
superpowers (always-on, never disabled even if absent from a
profile).
- mcp magic: delegated to lib/toggle-external.sh which already
handles the MAGIC_API_KEY env lookup. Other MCPs stay advisory.
- cli (rtk, gsd, ctx7, graphify): status-only, never auto-installed.
Profiles shipped:
web public website work — frontend + content + light dev
seo SEO + GEO + W3C audit (search/AI indexability + a11y)
web-full production website end-to-end (web ∪ seo ∪ qa-only/canary)
backend backend / API / system dev — no design, no SEO
design visual QA, design systems, mockups, polish
dev daily code work — features, fixes, refactor, ship
qa site testing, perf, canary, validation
audit comprehensive audit — security + SEO + perf + health
minimal strip all gstack skills (quiet session)
Commands:
profile list / show <name> / current / apply <name> / set <name> /
reset / diff <a> <b>
`current` heuristic returns "full" when nothing is disabled, otherwise
picks the profile with the highest available-ratio (counts both
"enabled" and "installed" — the latter for CLIs). Tiebreaker: larger
profile total wins, so web-full beats web at a 100% tie.
`reset` re-enables every gstack skill but does NOT touch plugins —
the user re-enables a managed plugin manually or via `apply <profile>`.
This is documented in the trailing info line.
Integration:
- skills/profile/SKILL.md — `/profile` slash command, lists profiles,
documents the per-type mechanism, points at lib/profile.sh.
- agents/plugin-advisor.md — DETECT phase calls `profile current`,
OUTPUT adds a PROFILE line, and TOGGLING EXTERNAL TOOLS gains a
"Skill profiles" section with a signal → profile recommendation
table.
- lib/toggle-external.sh — header pointer to profile.sh for fine-
grained activation (toggle-external still owns whole-gstack and
magic-MCP toggles).
- Makefile — `make profile cmd="set <name>"`, profile-list,
profile-current, profile-reset.
Tested end-to-end: `set web` enables ui-ux-pro-max + magic; `set seo`
disables ui-ux-pro-max; `set minimal` disables ui-ux-pro-max but
spares always-on plugins; `reset` restores all 64 skills; shellcheck
clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires JuliusBrussee/caveman into the always-on tier alongside
security-guidance and superpowers. Caveman compresses Claude's output
tokens (~75%) by speaking like a caveman while keeping technical
substance. Three layers:
1. Plugin (caveman@caveman, marketplace JuliusBrussee/caveman)
— adds /caveman, /caveman-commit, /caveman-review, /caveman-stats,
/caveman-help, /cavecrew, /compress + 3 cavecrew agents +
SessionStart/UserPromptSubmit hooks from the plugin path.
2. Standalone hooks (statusline + stats badge) deployed by
caveman's own hooks/install.sh into ~/.claude/hooks/. Paths in
settings.json normalized to ~/.claude/hooks/... so this user's
home dir doesn't leak across machines.
3. caveman-shrink MCP proxy — NOT auto-registered. The bare proxy
fails health checks because it requires an upstream MCP server
to wrap. install-plugins.sh STEP 5.5 prints a snippet showing how
to register a wrapped entry (e.g. caveman-shrink-fs) when the user
decides which upstream to compress.
New helper enable_plugin() for explicit always-on activation —
'claude plugin install' only copies into cache, doesn't write
enabledPlugins. Idempotent via Python json check.
doctor.sh adds detect_caveman / detect_caveman_hooks / detect_caveman_shrink
checks plus a 300t passive-cost adder. update-all.sh refreshes hook
files via the upstream installer's --force mode.
.gitignore covers caveman runtime files materialized into hooks/
because ~/.claude/hooks is symlinked to this repo.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>