Commit Graph
210 Commits
Author SHA1 Message Date
Bastien Chanot 80ccdafe0e chore(config): untrack the vendored graphify skill, prune the project-local settings override
graphify: `graphify claude install` (install-plugins.sh STEP graphify)
writes SKILL.md, references/ and .graphify_version straight into the repo,
because ~/.claude/skills is a symlink to skills/. Every `pipx upgrade
graphifyy` therefore dirtied the tree and cost a `chore(graphify): sync
vendored skill X -> Y` commit. Now gitignored and untracked; a fresh clone
gets them back from `make plugin`. test-prompts.json is hand-written for
darwin and stays tracked. The accepted trade-off, documented in CLAUDE.md,
is that an upstream release can change the skill's prompt with no diff to
review.

settings.local.json (gitignored, so not in this commit) went from 14.6 KB
to 6.2 KB. It was a near-complete shadow copy of the global settings at a
higher precedence tier, which hid its own drift until the global moved.
Two entries were actively defeating BDR-090, merged an hour earlier:

  - local `deny` still carried rsync / kill -9 / killall / pkill, the four
    rules deliberately moved out of global deny. deny wins across sources,
    so autoMode.soft_deny was a dead letter in this repo.
  - local `allow` carried `sed *`, `cp *` and `python3 -`. An allow rule
    short-circuits the classifier, punching a hole through the same
    soft_deny rules.

deny and ask are dropped whole (102 and 27 of their entries duplicated the
global; ask gates nothing under defaultMode auto). allow went 185 -> 98:
81 duplicates plus six policy conflicts, the three above and
Read(//home/bchanot/**), WebSearch, and a leftover command-injection test
payload that had been allowlisted verbatim. Every non-permissions key was
a verbatim copy of the global, including a hooks block whose only original
entry pointed at hooks/config-protection.sh, a script that exists nowhere.
2026-09-15 19:55:09 +02:00
Bastien Chanot 6cd26bc3fa chore(memory): BDR-090, LRN-153, journal — autoMode tier rebuild
BDR-090 records why the ask tier was abandoned rather than repopulated,
the three alternatives rejected, and the deliberate caveat that the
guardrail hard_deny bars removing a deny entry but not adding one.

LRN-153 records the two traps the block carries: every autoMode list is
a full replacement without "$defaults", and a user-scope block reaches
every project on the machine.

TODO also logs F1-F3, found but not fixed: .claude/settings.local.json
is a 14.6 KB shadow copy of the global settings at higher precedence,
including a PreToolUse hook whose script does not exist.
2026-09-15 19:44:26 +02:00
Bastien Chanot a0876a2976 chore(memory): BDR-088/089, LRN-150/151/152, EVAL-029 — gstack Playwright lib 2026-09-15 16:49:23 +02:00
Bastien Chanot 8d5d154c28 chore(memory): LRN-149 — background_tasks gates the turn-end signal 2026-09-10 03:03:22 +02:00
Bastien Chanot 2c0439a0a8 chore(memory): LRN-148 — pre-flight terminal test; LRN-147 mechanism too narrow 2026-09-03 02:22:24 +02:00
Bastien Chanot a627201bee chore(memory): LRN-147 — restored terminals never instrumented by OSC ext 2026-09-03 02:00:22 +02:00
Bastien Chanot 6aca40a810 chore(memory): BDR-087 + LRN-146 + BLK-020 — capitalize 2026-09-03 00:28:45 +02:00
Bastien Chanot 069a73338a chore(todo): reconcile 2026-09-01 — T4 gate ticked, T6 residuals corrected, Makefile item re-verified open 2026-09-01 16:54:15 +02:00
Bastien Chanot c4e6ef1e2b chore(memory): BLK-019 notify-attention bell silent (VS Code client default) 2026-09-01 16:30:39 +02:00
Bastien Chanot f08c3ab51c chore(memory): LRN-145 terminalSequence pattern + journal 2026-09-01 2026-09-01 15:32:51 +02:00
Bastien Chanot a51a65e1d5 chore(memory): LRN-143 index row — re-escape pipes (sed a-command unescaped them) 2026-08-26 23:01:16 +02:00
Bastien Chanot a15854aa87 chore(memory): EVAL-028 + LRN-143/144 + BDR-086 + journal — darwin run capitalized; TODO round-count corrected 2026-08-26 23:00:41 +02:00
Bastien Chanot 7f457f09fd docs(darwin): result card PNG 2026-08-26 23:00:41 +02:00
Bastien Chanot 12823181d1 chore(darwin): Phase 3 — optimization report + TODO T5/T6 ticked 2026-08-26 22:45:03 +02:00
Bastien Chanot af002ba235 chore(darwin): T3 baseline — 54 rows, mean 83.4, 13 candidates <80 2026-08-26 11:15:03 +02:00
Bastien Chanot afec610dc8 chore(darwin): T2 gate passed — prompts reused, dim8 on candidates, threshold 80 2026-08-26 10:43:47 +02:00
Bastien Chanot a871ce5acb feat(darwin): Phase 0.5 — test-prompts for 7 promptless skills + campaign plan 2026-08-25 20:23:46 +02:00
Bastien Chanot 7f16213456 chore(reconcile): TODO vs real — 3 open-but-done ticked, Makefile rescoped, C1 note corrected
Oracles: merge 5ec7bfa (user-writing-web-rules), BDR-065 Amendment +
LRN-138 in registry bodies, darwin-skill present + T6c green + make
test exit 0, Makefile :31 glob fixed / :57 profiles still 5/10,
seo-geo-deprescription merged 5488c48.
2026-08-25 20:12:40 +02:00
Bastien Chanot dab25636c8 chore(memory): BDR-085 + journal + TODO — user permanent rules integrated 2026-08-25 19:48:06 +02:00
Bastien Chanot b9aa9ba2e6 chore(memory): TODO — T4 verified, merge pending human gate 2026-08-24 14:19:08 +02:00
Bastien Chanot 543b0c811a feat(tour): multi-project parallel fan-out — one runner per repo
Two or more project paths dispatch one general-purpose runner per repo,
all in a single message, instead of processing repos one by one. The
runner inherits the session model — no pin, it carries tour's reflection
(fix decisions, convergence) — and every agent inside keeps its defined
tier (security-auditor sonnet, Phase B opus, doc-syncer sonnet). A dead
or mute runner becomes an explicit RUNNER FAILED summary row; the gated
capitalize offer stays in the main loop, never in a runner.

Bounded LRN-083 derogation recorded in BDR-084: the per-project fix loop
moves into its runner, but nothing a runner decides touches shared state
— independent repos, per-repo chore branches, branches left unmerged for
human review exactly as inline. Mechanics proven before building: nested
probe, 3 sub-agent windows all overlapping, 9.1s vs ~18s sequential.

Census §12: 6 locks, flip-tested. Single-project path unchanged.
2026-08-24 14:18:59 +02:00
Bastien Chanot 4ededc75ab chore(memory): TODO — plan tour-parallel (T1-T4) 2026-08-24 14:17:25 +02:00
Bastien Chanot 763d0022bf chore(memory): TODO — W9 human merge signal + Palier 3 trigger (won't-build-now) 2026-08-24 13:44:01 +02:00
Bastien Chanot 33f5356c82 feat(gates): wire GATE 0 into the four orchestrator skill restatements
The include is authoritative, but feat/bugfix/ship-feature/init-project
each restate the verify loop inline — an orchestrator following the
restatement alone would have skipped the floor. Each now carries the
GATE 0 bullet ahead of GATE 1 (4 new structure locks, flip-tested).
The contract-interview weight table stops promising a hotfix oracle
nothing executes: hotfix runs no floor, the hotfixer runs the suite
itself. CHANGELOG extended with the wiring + the RED result.
2026-08-24 13:36:32 +02:00
Bastien Chanot abb4ea7650 chore(memory): EVAL-027 — contract-gates behavioral RED 16/16 conformant 2026-08-24 13:26:39 +02:00
Bastien Chanot bfac4d4522 chore(memory): BDR-083 + LRN-141/142 + journal + CHANGELOG + TODO W0-W8
BDR-083 records what was taken from unlazy and, more usefully, what was
refused and why. LRN-141: an external skill's machinery encodes its threat
model, not yours — take the invariants, refuse the machinery. LRN-142:
structure locks are fixed-string, so reflowing a doctrine paragraph reds
them; fix the doc, not the lock.
2026-08-24 13:12:38 +02:00
Bastien Chanot 325962e080 chore(memory): BDR-082 + LRN-140 + journal + CHANGELOG + TODO C1 done (seo/geo de-prescription) 2026-08-02 17:28:07 +02:00
Bastien Chanot 9681b468e1 test(census): seo/geo agent⇄dispatcher contract locks (C1 P1, pre-reword)
71 locks, flip-proven (7 scratch mutations → 7 FAILs): judge verdict
grammar, FIX BUNDLE + READY-TO-APPLY sentinel, signals handoff, ALL
STEP headers (interiors included, conf#5), collect report, bundle item
fields parsed by L1 appliers, score labels (BDR-010/LRN-011), scoring
blocks, trajectory, envelope keys. Locks existing state — reword
commits must keep this green.

+ plan v3 (challenged 3 lenses FATAL/FATAL/CONCERNS + 1 confirmation
pass FATAL(9), every BLOCKER closed by a named change, §5bis record)
+ directive-language inventory annex (analyzer report).
2026-08-02 01:10:47 +02:00
Bastien Chanot 7047adfe77 chore(memory): reconcile TODO — opus5 branch merged (709cf9b), add Claude 5 follow-on chantiers C1-C4 2026-07-30 13:41:37 +02:00
Bastien Chanot 550b39043e chore(memory): BDR-081 + LRN-139 + journal + CHANGELOG + plan (opus5 tuning)
Capitalizes the Claude-5-family config recalibration: decision record,
trait-inversion learning (LRN-030 superseded premise, #80988 injections,
no-effort-hold trap), journal line, CHANGELOG Unreleased entries, and the
challenged plan (3 blind Opus 5 lenses, synthesis in §5bis).
2026-07-30 13:02:55 +02:00
Bastien Chanot 78a25aeb5e chore(memory): BDR-065 amendment (auto-purge coded) + LRN-138 + journal
BDR-065 delete-side now automated (lib/gitflow.sh _gitflow_purge_transient).
LRN-138: gitignore != delete for run-time artifacts read from disk — use
commit-during-run + auto-delete at the integration boundary. TODO checked,
journal line.
2026-07-22 15:12:28 +02:00
Bastien Chanot 1ef6e6e694 chore(memory): BDR-080 bug routing inversion + journal line 2026-07-21 01:15:06 +02:00
Bastien Chanot 33f9529b9e chore(memory): BLK-018 classifier-blocked finish span + v1.3.1 journal line 2026-07-21 00:27:39 +02:00
Bastien Chanot 533fcc841e chore(memory): journal — README rebuild session 2026-07-20 22:17:48 +02:00
Bastien Chanot dc90aae9bd Merge chore/purge-transient-docs into develop
# Conflicts:
#	.claude/tasks/TODO.md
2026-07-20 17:07:11 +02:00
Bastien Chanot e75ea79ae6 chore(tasks): reconcile 2026-07-20 — 4 stale claims corrected + pending-gates section
- seo/geo STATUS: H1+C1 were done (url-guard, sitemap verb) and the branch
  merged (92301fe) + shipped v1.2.0 — 'NEXT'/'nothing merged' lines stale
- ctx7 + opus-pin sections: 'NO merge' notes stale (8ee7d19, 17fbe51 both
  shipped v1.2.0)
- f1c9c474 transcript decision moot: auto-rotated (cleanupPeriodDays=7)
- new open items: 2 unmerged branches + Makefile help-text fix
2026-07-20 16:46:42 +02:00
Bastien Chanot 8008d8233c feat(profile): set symmetric on managed externals + MCPs (BDR-079)
- MANAGED_EXTERNALS (emil-design-eng, frontend-design,
  design-motion-principles, impeccable) + MANAGED_MCPS (magic):
  cmd_set now trims both when the profile does not list them —
  design leftovers no longer survive a 'set backend'
- cmd_set refactored to 4 symmetric trim helpers; nothing outside
  the MANAGED_* allowlists is ever auto-toggled (darwin-skill manual)
- enable_skill external: from-source fallback (ln -sf
  skills-external/<name>), mirrors toggle-external.sh
- stale usage() NOTE + SKILL.md updated to the both-ways reality
- hermetic test: 16 checks, fixture repo + fake claude shim (gstack
  on-demand, from-source, park/restore, magic add/remove, non-managed
  untouched); shellcheck + full make test green
2026-07-20 14:47:53 +02:00
Bastien Chanot b3a03fd974 chore(memory): journal — v1.2.0 cut + doc pass 2026-07-20 14:24:39 +02:00
Bastien Chanot b7026e4bda feat(ctx7): coverage extension — fast-libs single source + reminder hook + executor briefs (BDR-078)
- lib/fast-libs.sh: detect/cache-status verbs, JS+Python manifests,
  7-day cache freshness, LC_ALL=C sort — replaces 3 hardcoded lists
  (ship-feature 0c, init-project 5c, onboard 3.5)
- hooks/ctx7-reminder.sh: once-per-session UserPromptSubmit nudge when
  the project carries fast-libs and .ctx7-cache/ is missing/stale
- find-docs: before-writing-code trigger + cache-first rule; dist is
  machine-owned (gitignored) so the durable patch lives in
  install-plugins.sh STEP ctx7 (idempotent, grep-guarded)
- feater/bugfixer briefs: fast-lib docs rule (fresh cache read, else
  2-topic ctx7 fetch, else NOTES cache miss + proceed)
- tests: lib/tests/fast-libs.test.sh (11 checks); shellcheck + full
  make test green (review-guards 5/0)
2026-07-20 10:45:06 +02:00
Bastien Chanot 07253e093c feat(doctrine): W6 — prose sweep + BDR-077 + LRN-137 + plan execution notes
LRN-113 whole-surface sweep: client-handover x2 + commit-change prose
repointed to the two-mode reality; code-cleaner/status historic notes
kept (accurate). Memory: BDR-077 (full architecture), LRN-137
(mode-based re-tiering + fail-safe pin rule), journal. Plan carries
as-built EXECUTION NOTES.
2026-07-19 23:44:24 +02:00
Bastien Chanot 1c2d30dbf0 chore(tasks): model-tiering v2 — analysis + challenged plan v3 + TODO reconcile
Plan challenged by 3 blind lenses + 1 confirmation pass (1 BLOCKER closed
by fable-dispatch spike, 6 MAJORs + 8 MINORs fixed by named changes, 0
deferred). TODO: seo-geo-integrity 'UNMERGED' note was stale (92301fe
already in develop) — corrected.
2026-07-19 19:51:06 +02:00
Bastien Chanot 3eaf31ca09 chore(memory): BDR-076 + journal + TODO — opus-pin dispatched judgment agents 2026-07-19 17:38:58 +02:00
Bastien Chanot 727a41ad71 chore(memory): BDR-075 amendment (hotfix included) + journal — Option B + behavioral smoke 2026-07-18 23:08:53 +02:00
Bastien Chanot 2aa95636ee chore(memory): BDR-074 BDR-075 EVAL-026 LRN-136 — config-protection removal + plan-challenge phase 2026-07-17 22:54:12 +02:00
Bastien Chanot a391be4906 chore(memory): LRN-134 LRN-135 — capitalize 2026-07-17 20:16:50 +02:00
Bastien Chanot 0564afcb3c chore(memory): journal — content_quality shipped, both easy picks done 2026-07-17 19:07:10 +02:00
Bastien Chanot fb0b587240 chore(memory): journal — schema_gen shipped, gap-revisit note 2026-07-17 14:31:26 +02:00
Bastien Chanot f96206ff21 chore(memory): BDR-070..073 LRN-131..133 BLK-017 EVAL-025 — capitalize 2026-07-17 13:46:17 +02:00
Bastien Chanot d6b8edc8ea fix(seo): B1 KILLED — Common Crawl backlinks measured, not assumed
The plan said Common Crawl was the free backlink source and the 70/100 cap
was therefore mandatory. Measured before building, and both premises die.

HEAD against data.commoncrawl.org, live:
  cc-main-2026-feb-mar-apr-domain-edges.txt.gz    17.3 GB   gzipped
  cc-main-2026-feb-mar-apr-domain-ranks.txt.gz     2.3 GB
  cc-main-2026-feb-mar-apr-domain-vertices.txt.gz  879 MB

Finding one domain's inbound links means scanning the edges file end to end,
per audit. That is not slow, it is non-viable — and abusive toward a
nonprofit serving the data free.

Worse, the reference implementation everyone points at
(claude-seo scripts/commoncrawl_graph.py:169) does this:

    max_compressed_bytes = 500 * 1024 * 1024   # 500 MiB safety cap
    if total_downloaded > max_compressed_bytes: break

500 MiB of 17.3 GB is **2.9% of the edges file**, which is sorted by source
ID — so it reads an arbitrary slice of source domains and reports whatever
backlinks happened to be in it, as a backlink profile, capped at "70/100
health". Nothing in the output says 3%. That is a random sample wearing a
measurement's clothes: the exact failure class this branch exists to remove,
and I was one step from copying it.

B2 dies with B1: nothing left to cap.

CONSEQUENCE, and it is the point: I1's narrowed Off-page axis — brand
mentions only, backlinks + authority declared unauditable in §14 — is the
FINAL state, not a placeholder waiting for data. Corrected my own I1 text,
which pointed at Common Crawl as the "nearest free source": that sends a
future reader into a 17 GB dead end. The §14 line now records what was
measured and why no number beats a fabricated one.

Also corrects the B3 note, whose follow-on ("so Common Crawl is the only free
source") was wrong for the same reason. The only free viable backlink source
is Bing's GetUrlLinks — first-party only, never a competitor, and blocked on
the client's Bing account. That raises W2's value; it does not unblock it.

Verified: full suite green, seo-data 144 pass / 0 fail.
2026-07-17 13:17:18 +02:00
Bastien Chanot 7d6aa09faf feat(lib): H1 — url-guard, shell-injection + local-target refusal before curl
Prerequisite for C1, which is why this moved up from AXE 5. Today $DOMAIN is
typed by the operator and interpolated into ~10 curls (seo-analyzer.md:254+,
geo-analyzer.md:248+) — self-inflicted risk. The sitemap crawl changes the
threat model completely: URLs then come from the TARGET'S OWN SERVER, so a
remote file's bytes reach a shell.

The severe hazard is injection, not SSRF. Those curls quote with ", inside
which $ and backtick still execute, and ~/.claude/.env holds
GOOGLE_OAUTH_CLIENT_SECRET + CRUX_API_KEY. A <loc> of
`https://x/$(cat ${HOME}/.claude/.env)` reads the vault into a request. The
test suite asserts exactly that payload is refused.

Code, not prose: a markdown instruction does not stop an injection. Mirrors
the house pattern (fetch.sh:25 _label_safe) — whole-string allowlist, C
locale, POSIX case: newline-proof, locale-independent, no grep pitfall.
Allowlist over denylist per CLAUDE.md.

Covers: shell metacharacters; scheme (http/https only — no file:, gopher:);
literal loopback/private/link-local/metadata/.local; userinfo authority
confusion (https://trusted.com@127.0.0.1/ hits .0.0.1, not trusted.com).

NOT covered, stated in the header rather than left silent: DNS-level SSRF. A
public hostname resolving to a private address passes. Closing it needs
resolve-then-pin at the HTTP layer; shell curl cannot without a TOCTOU
window. Proportionate to the threat model — this runs on a workstation
auditing the operator's own client sites.

Wired at all three entry points: both agents' STEP 4 domain assignment, and
the W3 sameAs loop (whose URLs come from the audited repo, not the operator).
Refused sameAs rows report as REFUSED rather than vanish — neither dead nor
live, and an unguardable sameAs is itself a finding.

Note: writing the test file tripped the config-protection hook (test suite is
a guarded quality-gate). Used the documented one-shot sentinel with a reason
rather than working around the gate; it was consumed as designed.

Verified: 47 new assertions PASS / 0 FAIL, picked up by make test; full suite
green; shellcheck clean on lib/url-guard.sh (the sole remaining hit in the
health-stack glob is pre-existing, lib/gitflow-test.sh:242); guard dogfooded
against the real zenquality.fr domain (accepted) and the real exfil payload
(refused, exit 2).
2026-07-17 09:25:34 +02:00