Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .claude/commands/dream.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ $ARGUMENTS
`.claude/commands/*.md` has a `description` and a row in the index; **flag**
(report, don't auto-remove) any index rows for commands that no longer exist and
any near-duplicate commands.
2b. Health-check the **workflow diagrams** (`docs/workflow-graphs.md`) against
`.claude/workflows/*.js`: for each workflow, confirm the diagram's node labels,
model tier (🟢/🔵/⚪/▫️), and edges (parallel/pipeline/gates) match the script's
`CRITICS`/`LENSES`/`DIMENSIONS`/`CATEGORIES` arrays and `FANOUT_MODEL`/`JUDGE_MODEL`
usage; **report (don't auto-fix)** any diagram that has drifted from its script.
3. Auto-fix only additive/clerical issues (broken links, a missing index row, a
missing `description`). Everything else — removing a dead index row, deleting a
command, merging duplicates, or removing/rewriting a fact — is **reported only**
Expand Down
8 changes: 7 additions & 1 deletion .claude/commands/improve-skills.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,18 @@ $ARGUMENTS
instruction-following risk, safety/gating, consistency/DRY, workflow DSL, routing)
runs in its own context, in parallel; a *separate* skeptic adversarially verifies
every blocker/important finding. Invoking Workflow here is expected.
2. The workflow returns `{ scope, targets, counts, applyReady, needsDesign, niceToHaves, refuted }`.
2. The workflow returns `{ scope, targets, budgetStop, counts, applyReady, needsDesign, niceToHaves, refuted }`.
Render `applyReady` and `needsDesign` as a matrix and list `niceToHaves` separately:

| Lens | Severity | File | Finding | Evidence | Proposed edit | Edit verified safe? |
|---|---|---|---|---|---|---|

**If `budgetStop` is true**, tell the user up front that the adversarial verify phase
was skipped due to the token-budget floor: every `needsDesign` item in this run is
**UNVERIFIED** (not merely design-pending — neither its reality nor its edit-safety
was checked), `applyReady` is empty by design, and a follow-up `/improve-skills` pass
should re-run verification before anything is applied.

3. **Confirmation gate (hard rule).** This command may propose edits to core prompts
but must **never apply them without explicit confirmation** — the same rule
`/dream` follows for creating/rewriting skills. Present the proposed edits and ask
Expand Down
44 changes: 44 additions & 0 deletions .claude/memory/log.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,47 @@ change), `rotate` (archived old entries). Format: `## [YYYY-MM-DD] <op> | <summa
- Lesson: a self-improving system MUST add its own prompt surface (`.claude/`,
`CLAUDE.md`) to every autonomous denylist — otherwise the loop can edit the rules
that constrain it. The meta-critic caught this in its own machinery on the first run.

## [2026-06-29] ingest | Graph-engineering upgrades (model tiering, budget, diagrams)
- Distilled from a "graph engineering" post (Slate/Random Labs). Our `Workflow` tool
already IS a graph runtime (nodes=agents, edges=hand-offs, parallel/pipeline, scoped
failure); three real gaps were closed:
- **Model tiering** across all 6 workflows: fan-out nodes (critics, evidence, gaps,
E2E cases, meta-critics) → `model: 'sonnet'`; gates/judgment (verifiers, status
verifier, synthesis) → `model: 'opus'`; overridable via `args.models`. The
`loop-iteration` implementer stays on the session model (never downgrade a
code-writing node). Cuts fan-out cost, keeps quality at the decision points.
- **Budget hard-stops**: `improve-skills` skips the verify fan-out below a token
floor (findings → `needsDesign`, unverified, never auto-applied); `loop-iteration`
escalates all items below its floor instead of failing mid-fix. Guarded on
`budget.total` (null ⇒ no cap).
- **Diagram-first**: `docs/workflow-graphs.md` draws every workflow's node/edge graph
(Mermaid) with the per-node model tier — the "see the graph before you run" view.
- Filed the tiering + soft/hard-budget + diagram-drift conventions in topics/workflow.md.
- Not adopted (out of scope): the post's quant/hedge-fund framing and the standalone
Slate runtime — we already have the graph runtime; the trading claims overstate what
orchestration solves (data quality, costs, overfitting remain the hard part).

## [2026-06-29] ingest | Gate on the tiering change caught a denylist bypass (blocker)
- Ran `/improve-skills` (21 agents, on the NEW tiering — Sonnet fan-out + Opus verify)
over the graph-engineering diff: 1 blocker + 7 important confirmed, 7 refuted. Fixes:
- **Blocker — `loop-iteration` denylist bypass:** the "hard, deterministic" gate
checked the implementer's SELF-REPORTED `changedFiles`, but the caller applies the
real `diff`. An under-reported file list touching a denylisted path (`.claude/`,
`.env`) would slip past. Fixed by parsing paths from the actual diff
(`parseDiffPaths`) and gating on the UNION of diff-paths + changedFiles. Unit-tested:
the attack (changedFiles omits `.claude/loop.md` while the diff edits it) now
escalates. This is the same "give the checker the real artifact, not the self-report"
lesson — it applies to the deterministic gate too, not just the LLM verifier.
- **args normalization** ported to the other 5 workflows (the JSON-string parse lived
only in `improve-skills`), rewiring every `args.X` read to the normalized `a` so the
`args.models` tiering override doesn't silently no-op.
- **`workflow-dsl` lens** now also checks model-tiering + budget-guard fail-safety;
**`consistency-dry` lens** + `/dream lint` now check `docs/workflow-graphs.md` for
drift vs the scripts; `docs/workflow-graphs.md` added to `improve-skills`
DEFAULT_TARGETS (the drift check was promised but not wired).
- **`improve-skills.md`** now documents `budgetStop` and instructs disclosing a
skipped-verification run (needsDesign items are UNVERIFIED, not design-pending).
- Lesson: a deterministic gate is only as trustworthy as the input it reads — derive the
checked paths from the artifact that is actually applied (the diff), never from a
parallel self-report. The maker/checker "real artifact" rule extends to code gates.
20 changes: 20 additions & 0 deletions .claude/memory/topics/workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,26 @@
missing), and give a maker/checker checker the **real artifact** (the diff), not
the maker's self-report. The DSL auto-caps concurrency (~min(16, cores-2)), so a
large fan-out won't spawn unbounded agents — no manual limit needed.
- **Model tiering (graph-engineering): fan-out → fast tier, gate → strong tier.**
High-volume, individually-low-stakes fan-out nodes (critics, per-story evidence,
per-dimension gaps, per-category E2E cases) run `model: 'sonnet'`; the high-stakes
gates/judgment (adversarial verifiers, status verifier, synthesis) run
`model: 'opus'`. Define `FANOUT_MODEL`/`JUDGE_MODEL` constants (default `sonnet`/
`opus`, overridable via `args.models = { fanout, judge }`) and pass `model:` per
`agent()`. **Exception:** a node that writes real code (the `loop-iteration`
implementer) inherits the session model — never silently downgrade a code-writing
node. This cuts cost on the wide fan-out and keeps quality where the decision is made.
- **Budget as soft + hard control.** For big fan-outs, guard on `budget.total` (it is
`null` when no target was set — without the guard `budget.remaining()` is `Infinity`
and a loop never stops). Hard-stop pattern: if `budget.total && budget.remaining()
< FLOOR`, skip the expensive stage instead of failing mid-run — `loop-iteration`
escalates all items (`budgetStopped`), `improve-skills` skips the verify fan-out and
routes findings to `needsDesign` (unverified ⇒ never auto-applied). Keep the fail-safe
direction: a budget stop must never *approve* unverified work.
- **Diagram-first:** every workflow's node/edge graph is drawn in
`docs/workflow-graphs.md` (Mermaid), with the model tier per node. Update the diagram
when you add a node, edge, or change a tier — `/improve-skills` and `/dream lint`
flag drift between a diagram and its script.
- A command's `description` also lets Claude **auto-invoke** it. Add
`disable-model-invocation: true` to heavy/code-changing commands that should be
manual-only.
Expand Down
2 changes: 2 additions & 0 deletions .claude/skills-index.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ catalog so both you and Claude can route to the right command.
> `scan-docs` (`/scan-project-docs`), `gap-analysis` (`/story-gap-analysis`),
> `e2e-design` (`/multi-agent-e2e`), `loop-iteration` (`/loop` L2/L3),
> `improve-skills` (`/improve-skills` — meta-critic over our own prompts/workflows).
> Pass `args.models = { fanout, judge }` when invoking any of these to override the
> default `sonnet`/`opus` model tiering (e.g. for cost control on a large fan-out).

| Command | When to use | Args |
|---|---|---|
Expand Down
28 changes: 22 additions & 6 deletions .claude/workflows/critic-panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,32 @@ export const meta = {
],
}

// Normalize args: the Workflow tool may hand args as an object, a plain string, or a
// JSON-encoded string. Parse the JSON-string case so structured fields (models,
// priorEvidence, uiInScope) resolve instead of silently no-opping.
let a = args
if (typeof a === 'string') {
const s = a.trim()
if (s.startsWith('{') || s.startsWith('[')) {
try { a = JSON.parse(s) } catch { /* keep the string as a plain focus note */ }
}
}

// What to review (focus) + how deep, passed via the Workflow `args`.
const focus =
(args && (args.focus || (typeof args === 'string' ? args : null))) ||
(a && (a.focus || (typeof a === 'string' ? a : null))) ||
'the current app/flow and the changed files'
// Optional: evidence the Lead already gathered (test/lint/build output, browser
// findings, git diff). Critics verify against it instead of re-deriving from scratch.
const priorEvidence = (args && args.priorEvidence) || null
const priorEvidence = (a && a.priorEvidence) || null
// Optional: skip the 5 UI/browser critics for non-UI (backend/docs/config) changes.
// Defaults to including them (safe). Callers pass `uiInScope: false` to save fan-out.
const uiInScope = !(args && args.uiInScope === false)
const uiInScope = !(a && a.uiInScope === false)

// Model tiering (graph-engineering): high-volume fan-out on the fast tier, the
// high-stakes adversarial gate on the strong tier. Override via args.models.
const FANOUT_MODEL = (a && a.models && a.models.fanout) || 'sonnet'
const JUDGE_MODEL = (a && a.models && a.models.judge) || 'opus'

// The standard critic roster. `ui: true` marks browser-facing critics.
const ALL_CRITICS = [
Expand Down Expand Up @@ -70,10 +86,10 @@ const VERDICT_SCHEMA = {
// the full set before deduping and verifying).
phase('Review')
const reviews = await parallel(
CRITICS.map((c) => () =>
CRITICS.map((c, i) => () =>
agent(
`You are the **${c.label}** for this project. Read CLAUDE.md and the relevant memory topic page(s), inspect ${focus} (read the code; if a localhost flow is in scope, drive it via Playwright/Chrome MCP and check console + network), then review strictly through your lens:\n${c.lens}\n${priorEvidence ? `\nEvidence already gathered by the Lead — verify against it instead of re-deriving from scratch:\n${typeof priorEvidence === 'string' ? priorEvidence : JSON.stringify(priorEvidence)}\n` : ''}\nReturn only findings you can back with concrete evidence. An empty list is a valid, honest answer.`,
{ label: `critic:${c.key}`, phase: 'Review', schema: FINDINGS_SCHEMA },
{ label: `critic:${c.key}-${i}`, phase: 'Review', schema: FINDINGS_SCHEMA, model: FANOUT_MODEL },
).then((r) => ({ critic: c.key, label: c.label, findings: (r && r.findings) || [] })),
),
)
Expand Down Expand Up @@ -107,7 +123,7 @@ const verified = await parallel(
toVerify.map((f, i) => () =>
agent(
`Adversarially verify this ${f.severity} finding from the ${f.criticLabel}. Try to REFUTE it by inspecting the actual code/flow — default to real:false if the evidence does not hold up.\n\nTitle: ${f.title}\nEvidence: ${f.evidence}\nRecommendation: ${f.recommendation}`,
{ label: `verify:${f.critic}:${i}`, phase: 'Verify', schema: VERDICT_SCHEMA },
{ label: `verify:${f.critic}:${i}`, phase: 'Verify', schema: VERDICT_SCHEMA, model: JUDGE_MODEL },
).then((v) => ({
...f,
// Fail safe: a missing/failed verdict must NOT confirm the finding.
Expand Down
21 changes: 18 additions & 3 deletions .claude/workflows/e2e-design.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,22 @@ export const meta = {
phases: [{ title: 'Design', detail: 'one agent per path category enumerates test cases in parallel' }],
}

const flow = (args && (args.flow || (typeof args === 'string' ? args : null))) || 'the target flow'
// Normalize args: object, plain string, or JSON-encoded string. Parse the JSON case
// so structured fields (models) resolve instead of silently no-opping.
let a = args
if (typeof a === 'string') {
const s = a.trim()
if (s.startsWith('{') || s.startsWith('[')) {
try { a = JSON.parse(s) } catch { /* keep the string as a plain flow note */ }
}
}

const flow = (a && (a.flow || (typeof a === 'string' ? a : null))) || 'the target flow'

// Model tiering (graph-engineering): this workflow is pure fan-out enumeration
// (the dedup is plain code, not an agent), so every node runs on the fast tier.
// Override via args.models.fanout.
const FANOUT_MODEL = (a && a.models && a.models.fanout) || 'sonnet'

const CATEGORIES = [
{ key: 'happy', prompt: 'the happy path(s) a user follows when everything works' },
Expand Down Expand Up @@ -34,10 +49,10 @@ const CASES_SCHEMA = {

phase('Design')
const results = await parallel(
CATEGORIES.map((c) => () =>
CATEGORIES.map((c, i) => () =>
agent(
`Enumerate concrete Playwright E2E test cases for "${flow}" covering ${c.prompt}. Inspect the routes/components and existing specs first. Each case needs: title, steps, and the visible expected outcome to assert.`,
{ label: `e2e:${c.key}`, phase: 'Design', schema: CASES_SCHEMA },
{ label: `e2e:${c.key}-${i}`, phase: 'Design', schema: CASES_SCHEMA, model: FANOUT_MODEL },
).then((r) => ((r && r.cases) || []).map((x) => ({ ...x, category: c.key }))),
),
)
Expand Down
23 changes: 19 additions & 4 deletions .claude/workflows/gap-analysis.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,22 @@ export const meta = {
],
}

const scope = (args && (args.scope || (typeof args === 'string' ? args : null))) || 'the whole project'
// Normalize args: object, plain string, or JSON-encoded string. Parse the JSON case
// so structured fields (models) resolve instead of silently no-opping.
let a = args
if (typeof a === 'string') {
const s = a.trim()
if (s.startsWith('{') || s.startsWith('[')) {
try { a = JSON.parse(s) } catch { /* keep the string as a plain scope note */ }
}
}

const scope = (a && (a.scope || (typeof a === 'string' ? a : null))) || 'the whole project'

// Model tiering (graph-engineering): per-dimension gap discovery on the fast tier,
// the synthesis/prioritization judgment on the strong tier. Override via args.models.
const FANOUT_MODEL = (a && a.models && a.models.fanout) || 'sonnet'
const JUDGE_MODEL = (a && a.models && a.models.judge) || 'opus'

const DIMENSIONS = [
{ key: 'docs-not-impl', prompt: 'Requirements in docs (docs/prd.md, docs/stories/*, README) with NO implementation evidence in the codebase.' },
Expand Down Expand Up @@ -52,10 +67,10 @@ const ORDER_SCHEMA = {

phase('Dimensions')
const results = await parallel(
DIMENSIONS.map((d) => () =>
DIMENSIONS.map((d, i) => () =>
agent(
`Analyze the gap for "${scope}" along this dimension by reading docs/, src/, e2e/, and the story map:\n${d.prompt}\nReturn only gaps backed by concrete references; an empty list is a valid, honest answer.`,
{ label: `gap:${d.key}`, phase: 'Dimensions', schema: GAP_SCHEMA },
{ label: `gap:${d.key}-${i}`, phase: 'Dimensions', schema: GAP_SCHEMA, model: FANOUT_MODEL },
).then((r) => ({ dimension: d.key, gaps: (r && r.gaps) || [] })),
),
)
Expand All @@ -71,7 +86,7 @@ log(`${total} gaps across ${DIMENSIONS.length} dimensions`)
phase('Synthesize')
const synth = await agent(
`Given these gaps grouped by dimension, propose the recommended NEXT-implementation order (highest-leverage first), each with a one-line "why". Be concrete.\n\n${JSON.stringify(byDimension)}`,
{ label: 'synthesize', phase: 'Synthesize', schema: ORDER_SCHEMA },
{ label: 'synthesize', phase: 'Synthesize', schema: ORDER_SCHEMA, model: JUDGE_MODEL },
)

return { scope, totalGaps: total, byDimension, recommendedOrder: (synth && synth.order) || [] }
Loading
Loading