@rune-kit/rune 2.18.1 → 2.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +31 -12
  2. package/compiler/__tests__/comprehension.test.js +916 -0
  3. package/compiler/__tests__/governance-collector.test.js +376 -0
  4. package/compiler/__tests__/setup.test.js +5 -0
  5. package/compiler/analytics.js +5 -0
  6. package/compiler/bin/rune.js +165 -1
  7. package/compiler/comprehension-client.js +2348 -0
  8. package/compiler/comprehension.js +1254 -0
  9. package/compiler/governance-collector.js +382 -0
  10. package/compiler/schemas/comprehension.schema.json +87 -0
  11. package/compiler/schemas/governance.schema.json +78 -0
  12. package/compiler/transforms/branding.js +1 -1
  13. package/hooks/context-watch/index.cjs +24 -13
  14. package/hooks/hooks.json +2 -2
  15. package/hooks/lib/context-key.cjs +37 -0
  16. package/hooks/metrics-collector/index.cjs +37 -8
  17. package/hooks/pre-tool-guard/index.cjs +44 -0
  18. package/hooks/session-start/index.cjs +4 -10
  19. package/package.json +1 -1
  20. package/skills/adversary/SKILL.md +36 -3
  21. package/skills/adversary/references/cross-model-escalation.md +85 -0
  22. package/skills/adversary/references/reasoning-modes.md +95 -0
  23. package/skills/autopsy/SKILL.md +41 -0
  24. package/skills/ba/SKILL.md +106 -27
  25. package/skills/ba/references/ears-format.md +91 -0
  26. package/skills/brainstorm/SKILL.md +32 -7
  27. package/skills/completion-gate/SKILL.md +21 -10
  28. package/skills/converge/SKILL.md +178 -0
  29. package/skills/converge/references/eval-fixtures.md +59 -0
  30. package/skills/cook/SKILL.md +41 -6
  31. package/skills/debug/SKILL.md +4 -2
  32. package/skills/deploy/SKILL.md +23 -2
  33. package/skills/deploy/references/observability.md +146 -0
  34. package/skills/design/SKILL.md +15 -1
  35. package/skills/graft/SKILL.md +24 -8
  36. package/skills/onboard/SKILL.md +45 -0
  37. package/skills/perf/SKILL.md +4 -1
  38. package/skills/plan/SKILL.md +70 -7
  39. package/skills/plan/references/boundary-artifacts.md +127 -0
  40. package/skills/plan/references/plan-templates.md +28 -9
  41. package/skills/plan/references/vertical-slice.md +8 -6
  42. package/skills/preflight/SKILL.md +9 -3
  43. package/skills/review/SKILL.md +4 -2
  44. package/skills/skill-router/SKILL.md +2 -0
  45. package/skills/test/SKILL.md +15 -8
  46. package/skills/verification/SKILL.md +39 -7
@@ -0,0 +1,37 @@
1
+ 'use strict';
2
+
3
+ // Shared state-file keying for Rune context hooks (rune-pulse, context-sense,
4
+ // context-inject).
5
+ //
6
+ // State files are keyed by the Claude Code session_id, which Claude Code passes
7
+ // on stdin to the statusline AND to every PreToolUse / UserPromptSubmit hook in
8
+ // the same session. Because the key is identical across all of them, the hooks
9
+ // always read the SAME pulse file the statusline wrote — and because it is
10
+ // unique per session, counters reset automatically when a new session starts.
11
+ //
12
+ // process.cwd() was the previous key. It silently differed between the
13
+ // statusline and the hook processes (e.g. different launch directories), so the
14
+ // hooks could never find the statusline's real-percentage pulse file and fell
15
+ // back to a stale, never-reset tool-call counter — reporting ~100% "compact
16
+ // now" while the real context was a fraction of that.
17
+ //
18
+ // Falls back to a cwd hash only when session_id is unavailable (older Claude
19
+ // Code builds that don't pass it), which preserves the legacy behavior instead
20
+ // of crashing.
21
+
22
+ const os = require('node:os');
23
+ const path = require('node:path');
24
+
25
+ function resolveStateKey(sessionId, cwd) {
26
+ if (typeof sessionId === 'string') {
27
+ const safe = sessionId.replace(/[^a-zA-Z0-9_-]/g, '').slice(0, 36);
28
+ if (safe) return `s_${safe}`;
29
+ }
30
+ return `cwd_${Buffer.from(cwd || process.cwd()).toString('base64url').slice(0, 16)}`;
31
+ }
32
+
33
+ function stateFile(prefix, sessionId, cwd) {
34
+ return path.join(os.tmpdir(), `${prefix}-${resolveStateKey(sessionId, cwd)}.json`);
35
+ }
36
+
37
+ module.exports = { resolveStateKey, stateFile };
@@ -1,5 +1,6 @@
1
1
  // Rune Metrics Collector Hook
2
- // PostToolUse on Skill — captures skill invocations for H3 mesh analytics
2
+ // PostToolUse on Skill|Task|Agent — captures skill invocations for H3 mesh analytics
3
+ // (skills run both via the Skill tool and as rune:* subagents via Task/Agent)
3
4
  // Append-only JSONL to tmpdir. Flushed to .rune/metrics/ at session end.
4
5
  // Async: true — never blocks skill execution.
5
6
  //
@@ -8,11 +9,11 @@
8
9
  const fs = require('fs');
9
10
  const path = require('path');
10
11
  const os = require('os');
12
+ const { resolveStateKey, stateFile } = require('../lib/context-key.cjs');
11
13
 
12
- const cwd = process.cwd();
13
- const hash = Buffer.from(cwd).toString('base64url').slice(0, 16);
14
- const metricsFile = path.join(os.tmpdir(), `rune-metrics-${hash}.jsonl`);
15
- const watchFile = path.join(os.tmpdir(), `rune-context-watch-${hash}.json`);
14
+ // metricsFile + watchFile are keyed by the Claude Code session_id (parsed from
15
+ // stdin in the handler) so they reset per session and match the session-keyed
16
+ // context-watch counter. See lib/context-key.cjs.
16
17
 
17
18
  // Read stdin JSON to get tool input (Claude Code passes hook data via stdin)
18
19
  let stdinData = '';
@@ -20,13 +21,37 @@ process.stdin.setEncoding('utf-8');
20
21
  process.stdin.on('data', chunk => { stdinData += chunk; });
21
22
  process.stdin.on('end', () => {
22
23
  let skillName = 'unknown';
24
+ let claudeSessionId; // raw Claude Code session_id — used to key temp files
23
25
 
24
26
  try {
25
27
  const hookData = JSON.parse(stdinData);
26
- // PostToolUse stdin: { tool: "Skill", tool_input: { skill: "rune:cook", ... }, tool_result: ... }
28
+ claudeSessionId = hookData.session_id;
29
+ // PostToolUse stdin covers TWO invocation paths:
30
+ // Skill tool: { tool: "Skill", tool_input: { skill: "rune:cook", ... } }
31
+ // Task tool: { tool: "Task", tool_input: { subagent_type: "rune:cook", ... } }
32
+ // In Rune most skills run as subagents via Task, so the old Skill-only
33
+ // capture left skills_used[] empty. Capture both.
34
+ const toolName = hookData.tool || hookData.tool_name || '';
27
35
  const toolInput = hookData.tool_input || {};
28
- const raw = toolInput.skill || toolInput.name || '';
29
- skillName = raw.replace(/^rune:/, '') || 'unknown';
36
+ const fromSkill = () => (toolInput.skill || toolInput.name || '').replace(/^rune:/, '') || 'unknown';
37
+ // Subagent path — only count Rune skills (rune:*); skip generic agents
38
+ // (general-purpose, Explore, claude, statusline-setup, …) which are not skills.
39
+ const fromSubagent = () => {
40
+ const sub = toolInput.subagent_type || '';
41
+ return /^rune:/.test(sub) ? sub.replace(/^rune:/, '') : 'unknown';
42
+ };
43
+ // Branch on TOOL NAME first (authoritative), so a Skill invocation is never
44
+ // mis-routed by an incidental subagent_type field. Fall back to field-sniffing
45
+ // only when the tool name is absent.
46
+ if (toolName === 'Skill') {
47
+ skillName = fromSkill();
48
+ } else if (toolName === 'Task' || toolName === 'Agent') {
49
+ skillName = fromSubagent();
50
+ } else if (toolInput.skill || toolInput.name) {
51
+ skillName = fromSkill();
52
+ } else {
53
+ skillName = fromSubagent();
54
+ }
30
55
  } catch {
31
56
  // Fallback: try env var for backwards compatibility
32
57
  const toolInput = process.env.CLAUDE_TOOL_INPUT || '';
@@ -44,6 +69,10 @@ process.stdin.on('end', () => {
44
69
  const now = Date.now();
45
70
  const ts = new Date(now).toISOString();
46
71
 
72
+ // Session-keyed temp files (match the session-keyed context-watch counter).
73
+ const metricsFile = path.join(os.tmpdir(), `rune-metrics-${resolveStateKey(claudeSessionId)}.jsonl`);
74
+ const watchFile = stateFile('rune-context-watch', claudeSessionId);
75
+
47
76
  // Read session ID from context-watch state (shared tmpdir file)
48
77
  let sessionId = null;
49
78
  try {
@@ -7,10 +7,49 @@
7
7
  // - Per-project .rune/privacy.json config (custom patterns + overrides)
8
8
  // - Three tiers: ALLOW (silent), WARN (log), BLOCK (exit code 2)
9
9
  // - Content-aware: scans first bytes for secret patterns on WARN files
10
+ //
11
+ // Gate-outcome capture:
12
+ // - BLOCK events are appended to .rune/metrics/gate-outcomes.jsonl (best-effort)
13
+ // - passed/bypassed outcomes are NOT captured here (not observable at this layer)
10
14
 
11
15
  const fs = require('fs');
12
16
  const path = require('path');
13
17
 
18
+ /**
19
+ * Append a structured gate-outcome record to .rune/metrics/gate-outcomes.jsonl
20
+ * relative to process.cwd(). Best-effort: any error is silently swallowed so
21
+ * that capture NEVER affects the BLOCK decision or exit code.
22
+ *
23
+ * @param {string} gate - Gate name (e.g. "privacy-mesh")
24
+ * @param {string} outcome - "blocked" (only outcome captured here)
25
+ * @param {string} detail - Short human-readable reason
26
+ */
27
+ function appendGateOutcome(gate, outcome, detail) {
28
+ try {
29
+ const outDir = path.join(process.cwd(), '.rune', 'metrics');
30
+ fs.mkdirSync(outDir, { recursive: true });
31
+ const outFile = path.join(outDir, 'gate-outcomes.jsonl');
32
+ const entry = JSON.stringify({
33
+ ts: new Date().toISOString(),
34
+ gate,
35
+ outcome,
36
+ detail: String(detail).slice(0, 200), // cap length; no PII in detail
37
+ });
38
+ fs.appendFileSync(outFile, entry + '\n');
39
+
40
+ // Bound file to last 500 lines so it never grows unbounded.
41
+ // Synchronous read-trim-write, fully inside this try/catch — NEVER throws,
42
+ // NEVER affects the BLOCK decision or exit code.
43
+ const raw = fs.readFileSync(outFile, 'utf-8');
44
+ const lines = raw.split('\n').filter(Boolean);
45
+ if (lines.length > 500) {
46
+ fs.writeFileSync(outFile, lines.slice(-500).join('\n') + '\n');
47
+ }
48
+ } catch {
49
+ // Capture is best-effort. NEVER throw, NEVER affect exit code.
50
+ }
51
+ }
52
+
14
53
  // Read tool_input from Claude Code hook stdin
15
54
  let input = '';
16
55
  process.stdin.setEncoding('utf-8');
@@ -87,6 +126,11 @@ process.stdin.on('end', () => {
87
126
 
88
127
  const isBlocked = blockPatterns.some((p) => p.test(basename) || p.test(normalized));
89
128
  if (isBlocked) {
129
+ // Append block outcome BEFORE printing/exiting — fail-safe, never affects exit code.
130
+ // Only "blocked" is captured here; "passed" and "bypassed" are not observable
131
+ // at this layer and intentionally remain uncaptured (see GAP-1 in governance-collector.js).
132
+ appendGateOutcome('privacy-mesh', 'blocked', `file matched BLOCK-tier pattern: ${basename}`);
133
+
90
134
  console.log(`\n🚫 [Rune privacy-mesh] BLOCKED: ${filePath}`);
91
135
  console.log(' This file matches a BLOCK-tier pattern (private keys, certificates).');
92
136
  console.log(' Override: add path to .rune/privacy.json "allow" list if intentional.\n');
@@ -8,16 +8,10 @@ const os = require('os');
8
8
  const cwd = process.cwd();
9
9
  const runeDir = path.join(cwd, '.rune');
10
10
 
11
- // Initialize fresh session state (shared between context-watch and metrics-collector)
12
- const hash = Buffer.from(cwd).toString('base64url').slice(0, 16);
13
- const counterFile = path.join(os.tmpdir(), `rune-context-watch-${hash}.json`);
14
- const now = new Date().toISOString();
15
- const sessionId = `s-${now.slice(0, 10).replace(/-/g, '')}-${now.slice(11, 19).replace(/:/g, '')}`;
16
- try {
17
- fs.writeFileSync(counterFile, JSON.stringify({
18
- count: 0, lastWarning: 0, sessionStart: now, sessionId, toolCounts: {}
19
- }));
20
- } catch { /* non-critical */ }
11
+ // The context-watch counter is now keyed by the Claude Code session_id (see
12
+ // hooks/lib/context-key.cjs), so each new session starts with a fresh counter
13
+ // automatically no explicit reset is needed here. (Previously this block
14
+ // reset a cwd-keyed file, which bled across sessions in the same directory.)
21
15
 
22
16
  if (fs.existsSync(runeDir)) {
23
17
  const stateFiles = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rune-kit/rune",
3
- "version": "2.18.1",
3
+ "version": "2.21.0",
4
4
  "description": "64-skill mesh for AI coding assistants — runtime auto-discipline via native hooks (Claude/Cursor/Windsurf/Antigravity), 5-layer architecture, 215+ connections, multi-platform compiler. v2.17 adds quarantine L3 for prompt-injection advisory on untrusted external content.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -3,7 +3,7 @@ name: adversary
3
3
  description: "Pre-implementation red-team analysis. Use when a plan is high-risk, critical path, or expensive to reverse. Challenges plans before code is written — finds edge cases, security holes, scalability bottlenecks, error propagation risks, and integration conflicts. Catches flaws at plan time (10x cheaper than post-implementation)."
4
4
  metadata:
5
5
  author: runedev
6
- version: "0.2.0"
6
+ version: "0.4.0"
7
7
  layer: L2
8
8
  model: opus
9
9
  group: quality
@@ -68,6 +68,31 @@ Every finding MUST reference the specific plan section, file, or assumption it c
68
68
  3. Use `scout` to identify existing code files that the plan will touch or depend on
69
69
  4. Identify the plan's core assumptions — what MUST be true for this plan to work?
70
70
 
71
+ ### Step 0.5: Steelman + Pick a Reasoning Lens
72
+ <MUST-READ path="references/reasoning-modes.md" trigger="before challenging any plan — to steelman the thesis and select the reasoning lens per dimension"/>
73
+
74
+ Before attacking, **steelman the plan's core thesis** — restate it in its strongest
75
+ form (strip weak framing, supply the strongest implied evidence, name what's genuinely
76
+ good). Attacking a weak paraphrase produces findings the author dismisses with "that's
77
+ not what I meant." The steelman also seeds the Strength Notes section (Step 6).
78
+
79
+ The 5 dimensions below answer *what* to attack. The 5 **reasoning modes** answer *how*:
80
+
81
+ | Mode | Core question | Reach for when |
82
+ |------|---------------|----------------|
83
+ | **Red Team** (default) | "How would someone break/exploit/game this?" | auth, payment, user data, public input, perverse incentives. NB: this is *persona framing* (who attacks, their capability/motivation) — it composes with Step 2's attack-surface inventory, not a duplicate of it |
84
+ | **Pre-mortem** | "It's 6 months out and this failed — why?" | migrations, infra, architecture, cascading failure |
85
+ | **Evidence Audit** | "Does the evidence actually support this claim?" | benchmark/"X is faster"/capacity-number justifications |
86
+ | **Dialectic** | "What's the strongest case for the opposite choice?" | tech/vendor/architecture one-way-door decisions |
87
+ | **Socratic** | "What is this plan taking for granted?" | vague scope, consensus-driven plans, thin specs |
88
+
89
+ Default to **Red Team**. Switch or add a second lens when the plan's shape calls for it
90
+ (signal→mode table + mode mechanics in `references/reasoning-modes.md`). State which lens
91
+ you applied per dimension — don't ask the user to pick. Dialectic's synthesis usually
92
+ produces concrete remediations (likely HARDEN), but maps to REVISE if it exposes a
93
+ structural flaw in the chosen approach; Socratic's surfaced assumptions and Pre-mortem's
94
+ narratives become findings.
95
+
71
96
  ### Step 1: Edge Case Analysis
72
97
 
73
98
  Challenge the plan's handling of boundary conditions.
@@ -199,6 +224,7 @@ After reporting:
199
224
  ## Adversary Report: [feature/plan name]
200
225
  - **Plan analyzed**: [path to plan file]
201
226
  - **Dimensions checked**: [which of the 5 were relevant]
227
+ - **Reasoning lens**: [Red Team | Pre-mortem | Evidence Audit | Dialectic | Socratic — and why]
202
228
  - **Findings**: [count by severity]
203
229
  - **Verdict**: REVISE | HARDEN | PROCEED
204
230
 
@@ -250,7 +276,7 @@ Trigger: plan involves auth, crypto, payment, or user data handling.
250
276
 
251
277
  1. **Pre-bundle gate** — emit `context.preview` to `context-engine` first; abort if action=block
252
278
  2. **Build context bundle** — see `references/context-bundle-format.md` for exact format
253
- 3. **Dispatch** — emit `oracle.dispatched` signal; route via `session-bridge` detach if target model is opus-class (non-blocking)
279
+ 3. **Dispatch** — emit `oracle.dispatched` signal; route via `session-bridge` detach if target model is opus-class (non-blocking). **For genuine architectural independence** (security-critical logic, irreversible ops, a loop the in-mesh model cannot break), prefer a **different-architecture external CLI** (Gemini/Codex) over another Claude instance — a same-family reviewer shares blind spots with the author. Invoke it via the safe transport in `references/cross-model-escalation.md`: explicit per-call user authorization, read-only sandbox, stdin not inline args, binary pre-flight. Never auto-invoke an external CLI in non-interactive contexts — skip and announce.
254
280
  4. **Wait for response** — synchronous if model is sonnet-class, polled via `.rune/oracle-pending/<id>.json` if opus-class
255
281
  5. **Validate response** — every claim MUST cite file:line. Strip + warn on uncited claims (`oracle.failed` if all claims uncited)
256
282
  6. **Emit response** — `oracle.response` carries the validated diagnosis, consumed by `debug`/`fix` to override or refine their current hypothesis
@@ -281,7 +307,7 @@ Trigger: plan involves auth, crypto, payment, or user data handling.
281
307
 
282
308
  Replies failing this contract are rejected — `oracle.failed` emitted, primary agent continues without second opinion.
283
309
 
284
- See `references/oracle-mode.md` for the full protocol and integration with `debug`/`fix`.
310
+ See `references/oracle-mode.md` for the full protocol and integration with `debug`/`fix`. See `references/cross-model-escalation.md` for the safe external-CLI transport when the second opinion should come from a different model architecture.
285
311
 
286
312
  ## Constraints
287
313
 
@@ -292,6 +318,7 @@ See `references/oracle-mode.md` for the full protocol and integration with `debu
292
318
  5. MUST use concrete attack scenarios, not vague warnings ("could be a problem" is NOT a finding)
293
319
  6. MUST NOT block on MEDIUM/LOW findings — only CRITICAL and HIGH trigger REVISE verdict
294
320
  7. MUST include Strength Notes — adversary finds weaknesses AND acknowledges what's well-designed
321
+ 7b. SHOULD steelman the plan's core thesis before challenging it (Step 0.5) — a challenge that only lands against a weaker paraphrase is a weak finding, not a CRITICAL/HIGH. If steelmanning reveals a dimension is genuinely solid, record "No findings after steelman" for that dimension — that satisfies the per-dimension HARD-GATE
295
322
  8. (oracle-mode) MUST emit `context.preview` BEFORE building the bundle — abort if context-engine action=block
296
323
  9. (oracle-mode) MUST validate every Oracle reply citation against the provided files — reject uncited claims as `oracle.failed`
297
324
 
@@ -307,6 +334,8 @@ See `references/oracle-mode.md` for the full protocol and integration with `debu
307
334
  | Failure Mode | Severity | Mitigation |
308
335
  |---|---|---|
309
336
  | Over-challenging — nitpicking every line of the plan | HIGH | Rigor filter: only findings you can justify with specific references. Skip theoretical 3+ condition chains |
337
+ | Strawmanning — attacking a weaker version of the plan than what's written | HIGH | Step 0.5: steelman the thesis first; a challenge that only lands against the weak reading is dropped |
338
+ | One-lens tunnel vision — red-teaming a decision that needed dialectic/evidence-audit | MEDIUM | Step 0.5 signal→mode table: match the reasoning lens to the plan's shape, state which lens you applied |
310
339
  | False security alarms — flagging secure patterns as vulnerable | HIGH | Call sentinel for validation before reporting security findings as CRITICAL |
311
340
  | Analysis paralysis — too many findings block all progress | MEDIUM | Max 3 CRITICAL + 5 HIGH. If more found, consolidate or prioritize top impact |
312
341
  | Missing context — challenging plan without understanding existing codebase | HIGH | Step 0 MUST load existing code context via scout before challenging |
@@ -315,9 +344,13 @@ See `references/oracle-mode.md` for the full protocol and integration with `debu
315
344
  | (oracle-mode) Bundle exceeds token cap — caller didn't prune | HIGH | Caller MUST run `context.preview` first; adversary fails fast with `oracle.failed` instead of silently truncating signal |
316
345
  | (oracle-mode) Oracle reply has no citations — model improvised | CRITICAL | Reject reply with `oracle.failed`. Primary agent continues without second opinion (better than acting on hallucination) |
317
346
  | (oracle-mode) Loop: oracle reply triggers another `agent.stuck` | HIGH | Cap at 1 oracle dispatch per primary-agent stuck cycle. Subsequent stucks must escalate to user |
347
+ | (cross-model) External CLI invoked without authorization or in non-interactive run | CRITICAL | Per-call user authorization required; non-interactive → skip + announce. See `references/cross-model-escalation.md` |
348
+ | (cross-model) Bundle interpolated into shell args — embedded `$(...)` executes | CRITICAL | Always pass via stdin from a temp file; read-only sandbox. Never inline `-p "<bundle>"` |
349
+ | (cross-model) Rubber-stamping the external reviewer's verdict | MEDIUM | Reply is data, not ruling — reconcile against the artifact; classify each finding |
318
350
 
319
351
  ## Done When
320
352
 
353
+ - Plan thesis steelmanned before challenging (Step 0.5) — reasoning lens(es) selected and stated
321
354
  - All relevant dimensions analyzed (minimum: edge cases + security + integration)
322
355
  - Every finding references specific plan section or codebase file
323
356
  - Security-sensitive plans escalated to sentinel (or confirmed not security-relevant)
@@ -0,0 +1,85 @@
1
+ # Cross-Model Escalation (External CLI Transport)
2
+
3
+ Oracle-mode's "Dispatch" step (oracle-mode.md §3) is abstract about *how* the bundle reaches a second model. This reference is the concrete, safe transport when that second model is a **locally-installed CLI of a different architecture** (Gemini CLI, Codex CLI, etc.) rather than an in-mesh Anthropic model.
4
+
5
+ ## Why a different architecture
6
+
7
+ A same-family reviewer shares blind spots with the original author — the same training distribution produces the same wrong intuitions. A colder, different-architecture model is the highest-value second opinion precisely because it fails *differently*. When escalating, **prefer a non-Anthropic CLI over another Claude instance** for the genuine-independence case (security-critical logic, irreversible operations, a loop the in-mesh model cannot break).
8
+
9
+ This does not replace oracle-mode's in-mesh dispatch — it is the option for when "fresh context" is not enough and you want "fresh *architecture*".
10
+
11
+ ## Safety properties (all load-bearing)
12
+
13
+ ### 1. Explicit per-invocation authorization
14
+
15
+ **Never invoke an external CLI without the user's explicit yes — every time.** Authorization is per-call, not per-session: the artifact, prompt, and flags change between calls. "User said yes once" does not license the next invocation. In interactive sessions, *offer* the escalation and let the user decide; in non-interactive contexts (CI, `/loop`, scheduled runs) **skip and announce the skip** — never auto-invoke.
16
+
17
+ ### 2. Read-only sandbox
18
+
19
+ The bundle may itself contain instructions (intentional or accidental prompt injection) that an agentic CLI would otherwise execute against the workspace. Always invoke in a read-only mode:
20
+
21
+ ```bash
22
+ # Codex — read-only sandbox keeps the CLI from writing to your workspace:
23
+ codex exec --sandbox read-only -C <repo-path> - < /tmp/cross-model-prompt.md
24
+
25
+ # Gemini — '--approval-mode plan' is read-only; '-p ""' triggers non-interactive,
26
+ # prompt read from stdin:
27
+ gemini --approval-mode plan -p "" < /tmp/cross-model-prompt.md
28
+ ```
29
+
30
+ Verify the exact flags against the installed version — CLI syntax drifts across releases. Confirm the invocation with the user before running it.
31
+
32
+ ### 3. stdin, never inline args
33
+
34
+ Code, markdown, and review prompts routinely contain backticks, `$(...)`, and quote characters. Interpolating the bundle into a shell-quoted argument will either truncate the prompt or **execute embedded shell**. Write the full prompt to a temp file and pipe via stdin (`< /tmp/...`). Never `-p "…<bundle>…"`.
35
+
36
+ ### 4. Pre-flight the binary
37
+
38
+ `which` passing is not enough — a stale or broken binary can pass `which` and fail on real input:
39
+
40
+ 1. `which gemini` / `which codex` — exists in PATH?
41
+ 2. `gemini --version` / `codex --version` — actually runs?
42
+ 3. Confirm required auth/env (API keys) is present.
43
+
44
+ If any step fails: surface the failure explicitly, offer (run manually / try another tool / skip). **Do not silently fall back** to the in-mesh model — the user should know cross-model did not happen.
45
+
46
+ ## What to pass
47
+
48
+ Pass **ARTIFACT + CONTRACT + adversarial prompt only**. Do NOT pass your own conclusion or hypothesis — handing the reviewer your answer biases it toward agreement. The reviewer must independently decide whether the artifact satisfies the contract.
49
+
50
+ Adversarial framing (framing decides the answer):
51
+
52
+ ```
53
+ Adversarial review. Find what is wrong with this artifact. Assume the author
54
+ is overconfident. Look for: unstated assumptions, unhandled edge cases, hidden
55
+ coupling or shared state, contract violations, broken conventions, failure
56
+ modes under unexpected input.
57
+
58
+ Do NOT validate. Do NOT summarize. Find issues, or state explicitly that you
59
+ cannot find any after thorough examination.
60
+
61
+ ARTIFACT: <paste>
62
+ CONTRACT: <paste>
63
+ ```
64
+
65
+ ## Reconcile: output is data, not verdict
66
+
67
+ The external model's reply is **input to your judgement, not a ruling**. A different-architecture reviewer can be wrong precisely because it lacks your context. Re-read the artifact against each finding and classify (first match wins):
68
+
69
+ 1. **Contract misread** — the reviewer flagged something because the CONTRACT you gave was unclear. Fix the contract, not the code.
70
+ 2. **Valid + actionable** — real issue, change the artifact.
71
+ 3. **Valid trade-off** — real but cost of fixing exceeds cost of accepting; document it so the user sees the call.
72
+ 4. **Noise** — correct under context the reviewer didn't have; note it, move on.
73
+
74
+ Rubber-stamping the external reviewer is the same failure as ignoring it. If across 2+ cycles the reviewer surfaced substantive findings and you classified *zero* as actionable, you are validating, not doubting — stop and escalate to the user.
75
+
76
+ ## Checklist
77
+
78
+ - [ ] User explicitly authorized THIS invocation (or: non-interactive → skipped + announced)
79
+ - [ ] Binary pre-flighted (PATH + version + auth)
80
+ - [ ] Read-only sandbox flag set
81
+ - [ ] Prompt delivered via stdin from a temp file — never inline-interpolated
82
+ - [ ] Passed ARTIFACT + CONTRACT only — not your conclusion
83
+ - [ ] Prompt was adversarial ("find issues"), not validating ("is it good")
84
+ - [ ] Findings reconciled against the artifact (classified, not rubber-stamped)
85
+ - [ ] If the CLI was missing/errored → surfaced, not silently swallowed
@@ -0,0 +1,95 @@
1
+ # Reasoning Modes — 5 Lenses for Challenging a Plan
2
+
3
+ adversary's 5 **dimensions** (edge cases, security, scalability, error propagation,
4
+ integration) answer *what* to attack. These 5 **reasoning modes** answer *how* to
5
+ attack — the cognitive lens you bring to each dimension. They are orthogonal: any
6
+ dimension can be examined through any lens, and the right lens depends on the plan's
7
+ shape and the kind of weakness most likely to hide in it.
8
+
9
+ Default lens is **Red Team** (adversary's native mode). Escalate or switch when the
10
+ signals below fire. You do not need user input to pick a lens — infer it from the plan
11
+ and the dimension, state which lens you applied, and move on.
12
+
13
+ ## The 5 Modes
14
+
15
+ | Mode | Core question | Best for | Output shape |
16
+ |------|---------------|----------|--------------|
17
+ | **Red Team** | "If someone wanted to break, exploit, or game this, how?" | Security, abuse, perverse incentives, business-logic loopholes | Adversary persona → attack vector → defense (the *persona framing*, distinct from oracle-mode's cross-model second-opinion dispatch and from Step 2's attack-surface inventory — Red Team supplies the WHO, Step 2 the WHERE) |
18
+ | **Pre-mortem** | "It's 6 months out and this failed. Why?" | Architecture, infra, migrations, anything with cascading failure | Specific failure narrative → consequence chain → early-warning sign → mitigation |
19
+ | **Evidence Audit** | "Does the evidence actually support this claim?" | Plans justified by benchmarks, "X is faster", capacity assumptions | Extracted claim → falsification criterion → evidence-quality verdict |
20
+ | **Dialectic** | "What's the strongest case for the opposite choice?" | Technology/architecture decisions, "we chose X over Y" | Steelmanned thesis → strongest antithesis → synthesis (a better plan) |
21
+ | **Socratic** | "What is this plan taking for granted?" | Vague scope, consensus-driven plans, unexamined "obviously" | Probing questions grouped by theme — surfaces hidden assumptions |
22
+
23
+ ## Picking the lens (signal → mode)
24
+
25
+ | Signal in the plan | Switch to |
26
+ |--------------------|-----------|
27
+ | Auth / payment / user-data / multi-tenant / public input | **Red Team** |
28
+ | DB migration, service mesh, infra change, "at scale it will…" | **Pre-mortem** |
29
+ | "benchmarks show", "X is faster", "this saves N hours", capacity numbers | **Evidence Audit** |
30
+ | "we picked X over Y", framework/vendor choice, one-way-door decision | **Dialectic** |
31
+ | Vague terms ("scalable", "real-time", "robust"), "everyone agrees", thin spec | **Socratic** |
32
+
33
+ Domain defaults when no single signal dominates: Security → Red Team · Infra/Architecture
34
+ → Pre-mortem · Data/Analytics → Evidence Audit · Business/strategy → Dialectic ·
35
+ Product/UX → Socratic.
36
+
37
+ ## Steelman First (applies to every mode)
38
+
39
+ Before challenging, restate the plan's core thesis in its **strongest** form. An
40
+ adversary that attacks a weak paraphrase of the plan produces findings the author can
41
+ dismiss ("that's not what I meant"). Steelmanning earns the right to challenge.
42
+
43
+ 1. **Strip weak framing** — reduce the plan to its core claim ("microservices" →
44
+ "independent deploy + scale of components accelerates 4 teams on different release cycles").
45
+ 2. **Supply the strongest evidence** the plan implied but didn't state.
46
+ 3. **Name what's genuinely good** — this becomes the Strength Notes section.
47
+ 4. Then attack *that* version. If your challenge only lands against a weaker reading,
48
+ it isn't a finding.
49
+
50
+ Checklist: Have I made the position stronger, not weaker? Would the author recognize
51
+ this as their view (or better)? Am I attacking this version, not an easier one?
52
+
53
+ ## Mode mechanics (just enough to apply)
54
+
55
+ **Red Team** — Build a *specific* adversary persona (role, motivation, capability,
56
+ access, constraints), not a generic "attacker". Specific personas → actionable vectors.
57
+ E-commerce → fraudster (coupon abuse, fake returns); SaaS → free-tier abuser
58
+ (multi-accounting, rate-limit evasion); API → scraper. Map each persona's vector to the
59
+ plan's entry points, rank by likelihood × impact, propose a concrete defense.
60
+
61
+ **Pre-mortem** — Assume failure as the starting point (bypasses optimism bias). A
62
+ narrative must be specific: a named trigger, a number/threshold, the chain of events,
63
+ who's affected, and the underlying wrong assumption. "It didn't scale" is not a
64
+ narrative; "at 50K concurrent users the connection pool exhausted, cascading timeouts
65
+ tripped the circuit breaker, rejecting all requests for 4 min at peak" is. Trace ≥2
66
+ orders of consequence. Identify the early-warning sign — the thing you'd see *before*
67
+ the failure.
68
+
69
+ **Evidence Audit** — Extract the plan's claims (causal / predictive / comparative /
70
+ quantitative — often implicit). For each, write the falsification criterion: what
71
+ observation would prove it false? Then judge evidence quality (pilot vs production,
72
+ sample representativeness, correlation vs causation) and surface competing explanations.
73
+ "Migrating to K8s cuts deploy time 60%" hides three claims: pilot is representative, K8s
74
+ is the *cause*, 60% persists at scale. Audit each.
75
+
76
+ **Dialectic** — Build the antithesis by asking "if a smart, informed person disagreed,
77
+ what's their best argument?" Sources: opposing trade-off, hidden cost, an alternative
78
+ that solves the same problem cheaper ("modular monolith gets 80% at 20% cost"),
79
+ precedent ("Company X reverted after 2 years"), an unserved stakeholder. Don't stop at
80
+ the clash — drive to **synthesis**: a revised plan incorporating the best of both. This
81
+ maps directly to adversary's HARDEN verdict.
82
+
83
+ **Socratic** — Ask, don't argue. Categories: definitional ("when you say 'scalable', 10x
84
+ or 1000x?"), evidential ("what data shows users want this?"), logical ("does caching
85
+ *necessarily* improve UX?"), perspective-shifting ("how would the on-call engineer see
86
+ this architecture?"), consequential ("what does this look like in 2 years?"). Each good
87
+ question creates an "I hadn't thought about that" — surfaced assumptions become findings.
88
+
89
+ ## Sequencing (optional)
90
+
91
+ Some plans benefit from two lenses in order: Dialectic → Pre-mortem (decide the
92
+ approach, then stress its failure modes); Evidence Audit → Socratic (test the claims,
93
+ then probe what's still assumed); Red Team → Pre-mortem (find the exploit, then trace
94
+ its blast radius). Don't run more than two — adversary's job is a sharp report, not an
95
+ exhaustive philosophy seminar.
@@ -174,6 +174,45 @@ Generated: [date]
174
174
 
175
175
  Call `rune:journal` to record that autopsy ran, the overall health score, and the surgery queue.
176
176
 
177
+ ### Step 5b — Emit comprehension.json
178
+
179
+ Write `.rune/comprehension.json` conforming to `compiler/schemas/comprehension.schema.json`.
180
+ This is ADDITIVE — do not modify RESCUE-REPORT.md or any other output.
181
+
182
+ Populate from the module analysis already completed in Steps 1–4:
183
+
184
+ ```json
185
+ {
186
+ "project": "<project name>",
187
+ "generated_at": "<ISO 8601 timestamp>",
188
+ "source": "autopsy",
189
+ "health_score": <overall score 0-100 from Step 3>,
190
+ "layers": [
191
+ { "id": "<layer-id>", "name": "<human name>", "color": "<code|service|data|domain|docs|infra|concept>" }
192
+ ],
193
+ "domains": [],
194
+ "modules": [
195
+ {
196
+ "id": "<relative file path>",
197
+ "name": "<module name>",
198
+ "layer": "<layer id>",
199
+ "type": "file",
200
+ "complexity": "<simple|moderate|complex — map from health score: 80+=simple, 60-79=moderate, <60=complex>",
201
+ "files": 1,
202
+ "summary": "<one-line health finding — e.g. 'Score 42/100 — high cyclomatic complexity, no tests'>"
203
+ }
204
+ ],
205
+ "edges": []
206
+ }
207
+ ```
208
+
209
+ Rules:
210
+ - `modules[]` — include ALL modules scored in Step 3 (this is the full health inventory, not just entry points).
211
+ - `layers[]` — derive from the project's architectural layers detected by scout.
212
+ - `health_score` — MUST be the weighted overall score computed in Step 3, not an estimate.
213
+ - `edges[]` — leave empty; autopsy does not trace cross-file dependencies (that is the visualizer's job).
214
+ - Overwrite any existing `.rune/comprehension.json` — this is a generated emit, not human-written content.
215
+
177
216
  ### Step 6 — Report
178
217
 
179
218
  Output a summary of the findings:
@@ -268,6 +307,7 @@ Known failure modes for this skill. Check these before declaring done.
268
307
  - All major modules scored across all 6 dimensions
269
308
  - Git archaeology run (hotspots, stale files, dead code candidates identified)
270
309
  - RESCUE-REPORT.md written to project root with Mermaid dependency diagram
310
+ - .rune/comprehension.json written with health_score populated + all scored modules in modules[] conforming to comprehension.schema.json
271
311
  - journal called with health score and surgery queue
272
312
  - Autopsy Report emitted with overall health tier and top-3 issues
273
313
 
@@ -279,6 +319,7 @@ Known failure modes for this skill. Check these before declaring done.
279
319
  | RESCUE-REPORT.md | Markdown + Mermaid | project root |
280
320
  | Surgery queue (priority order) | Ordered list | RESCUE-REPORT.md |
281
321
  | Git archaeology findings | Bash output + summary | inline |
322
+ | Comprehension graph | JSON | `.rune/comprehension.json` |
282
323
  | Journal entry | Text | via `journal` L3 |
283
324
 
284
325
  ## Cost Profile