@ericrisco/rsc 1.0.10 → 1.0.11

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 (35) hide show
  1. package/README.md +15 -13
  2. package/manifest.json +133 -2
  3. package/package.json +3 -3
  4. package/scripts/consult.js +75 -42
  5. package/scripts/lib/default-skill-floor.js +11 -0
  6. package/scripts/lib/domains.js +3 -3
  7. package/scripts/lib/recommend.js +1 -0
  8. package/scripts/lib/text-rank.js +112 -0
  9. package/scripts/lib/ui.js +3 -3
  10. package/scripts/routing-audit.js +163 -0
  11. package/scripts/rsc.js +11 -10
  12. package/skills/author-skill/references/rsc-conventions.md +1 -1
  13. package/skills/bro/SKILL.md +68 -0
  14. package/skills/bro/evals/README.md +22 -0
  15. package/skills/bro/evals/cases.yaml +58 -0
  16. package/skills/decision-challenge/SKILL.md +113 -0
  17. package/skills/decision-challenge/evals/README.md +3 -0
  18. package/skills/decision-challenge/evals/cases.yaml +42 -0
  19. package/skills/deprecation/SKILL.md +123 -0
  20. package/skills/deprecation/evals/README.md +3 -0
  21. package/skills/deprecation/evals/cases.yaml +42 -0
  22. package/skills/idea-refinement/SKILL.md +121 -0
  23. package/skills/idea-refinement/evals/README.md +3 -0
  24. package/skills/idea-refinement/evals/cases.yaml +42 -0
  25. package/skills/observability/SKILL.md +6 -0
  26. package/skills/observability/evals/cases.yaml +2 -0
  27. package/skills/performance/SKILL.md +13 -0
  28. package/skills/performance/evals/cases.yaml +2 -0
  29. package/skills/simplify-code/SKILL.md +101 -0
  30. package/skills/simplify-code/evals/README.md +3 -0
  31. package/skills/simplify-code/evals/cases.yaml +42 -0
  32. package/skills/source-grounded-development/SKILL.md +108 -0
  33. package/skills/source-grounded-development/evals/README.md +3 -0
  34. package/skills/source-grounded-development/evals/cases.yaml +42 -0
  35. package/skills/suggest/evals/cases.yaml +1 -1
@@ -0,0 +1,163 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
4
+ import { dirname, join } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { createRanker } from './consult.js';
7
+ import { loadManifest } from './lib/manifest.js';
8
+ import { buildTextCorpus, descriptionCollisions } from './lib/text-rank.js';
9
+
10
+ const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
11
+ const FLOORS = {
12
+ rank1: 44,
13
+ top5: 70,
14
+ routedTop5: 85,
15
+ maxNegativeSelfRank1: 6,
16
+ maxCollision: 0.80,
17
+ };
18
+
19
+ function unquote(value) {
20
+ const trimmed = value.trim();
21
+ if (trimmed.startsWith('"')) return JSON.parse(trimmed);
22
+ return trimmed.replace(/^['"]|['"]$/g, '');
23
+ }
24
+
25
+ export function loadRoutingCases(root = ROOT) {
26
+ const cases = [];
27
+ const skillsRoot = join(root, 'skills');
28
+ for (const skill of readdirSync(skillsRoot).sort()) {
29
+ const file = join(skillsRoot, skill, 'evals', 'cases.yaml');
30
+ if (!existsSync(file)) continue;
31
+ const lines = readFileSync(file, 'utf8').split(/\r?\n/);
32
+ let section = '';
33
+ for (let index = 0; index < lines.length; index += 1) {
34
+ const line = lines[index];
35
+ if (/^should_trigger:/.test(line)) section = 'positive';
36
+ else if (/^should_not_trigger:/.test(line)) section = 'negative';
37
+ else if (/^capability:/.test(line)) section = '';
38
+ const prompt = line.match(/^ - prompt:\s*(".*")$/);
39
+ if (!prompt || !section) continue;
40
+
41
+ let routeTo = null;
42
+ if (section === 'negative') {
43
+ for (let next = index + 1; next < Math.min(lines.length, index + 8); next += 1) {
44
+ if (/^ - prompt:|^[A-Za-z_]+:/.test(lines[next])) break;
45
+ const route = lines[next].match(/^\s+route_to:\s*(.+?)\s*$/);
46
+ if (route) {
47
+ routeTo = unquote(route[1]);
48
+ break;
49
+ }
50
+ }
51
+ }
52
+ cases.push({ skill, section, prompt: unquote(prompt[1]), routeTo });
53
+ }
54
+ }
55
+ return cases;
56
+ }
57
+
58
+ function percentage(value, total) {
59
+ return total ? (100 * value) / total : 0;
60
+ }
61
+
62
+ function singleCatalogRoute(routeTo, ids) {
63
+ if (!routeTo || routeTo === 'none' || routeTo.startsWith('external:')) return null;
64
+ if (/[|,]/.test(routeTo) || routeTo.includes(' or ')) return null;
65
+ return ids.has(routeTo) ? routeTo : null;
66
+ }
67
+
68
+ export async function auditRouting(manifest, cases) {
69
+ const ids = new Set(manifest.skills.map(({ id }) => id));
70
+ const ranker = await createRanker(manifest);
71
+ const result = {
72
+ positives: 0,
73
+ rank1: 0,
74
+ top3: 0,
75
+ top5: 0,
76
+ negatives: 0,
77
+ negativeSelfRank1: 0,
78
+ routed: 0,
79
+ routedTop5: 0,
80
+ routeBeatsOwner: 0,
81
+ missedBySkill: new Map(),
82
+ };
83
+
84
+ try {
85
+ for (const row of cases) {
86
+ const ranked = ranker.rank(row.prompt).map(({ id }) => id);
87
+ if (row.section === 'positive') {
88
+ result.positives += 1;
89
+ const position = ranked.indexOf(row.skill);
90
+ if (position === 0) result.rank1 += 1;
91
+ if (position >= 0 && position < 3) result.top3 += 1;
92
+ if (position >= 0 && position < 5) result.top5 += 1;
93
+ else result.missedBySkill.set(row.skill, (result.missedBySkill.get(row.skill) || 0) + 1);
94
+ continue;
95
+ }
96
+
97
+ result.negatives += 1;
98
+ if (ranked[0] === row.skill) result.negativeSelfRank1 += 1;
99
+ const target = singleCatalogRoute(row.routeTo, ids);
100
+ if (!target) continue;
101
+ result.routed += 1;
102
+ const targetPosition = ranked.indexOf(target);
103
+ const ownerPosition = ranked.indexOf(row.skill);
104
+ if (targetPosition >= 0 && targetPosition < 5) result.routedTop5 += 1;
105
+ if (targetPosition >= 0 && (ownerPosition < 0 || targetPosition < ownerPosition)) {
106
+ result.routeBeatsOwner += 1;
107
+ }
108
+ }
109
+ } finally {
110
+ ranker.close();
111
+ }
112
+ return result;
113
+ }
114
+
115
+ async function main() {
116
+ const manifest = loadManifest();
117
+ const cases = loadRoutingCases();
118
+ const result = await auditRouting(manifest, cases);
119
+ const collisions = descriptionCollisions(buildTextCorpus(manifest.skills), 0.45);
120
+ const rates = {
121
+ rank1: percentage(result.rank1, result.positives),
122
+ top3: percentage(result.top3, result.positives),
123
+ top5: percentage(result.top5, result.positives),
124
+ negativeSelfRank1: percentage(result.negativeSelfRank1, result.negatives),
125
+ routedTop5: percentage(result.routedTop5, result.routed),
126
+ routeBeatsOwner: percentage(result.routeBeatsOwner, result.routed),
127
+ };
128
+
129
+ console.log(`routing-audit: ${manifest.skills.length} skills · ${cases.length} prompts`);
130
+ console.log(
131
+ `positive recall: rank-1 ${rates.rank1.toFixed(1)}% · top-3 ${rates.top3.toFixed(1)}% · top-5 ${rates.top5.toFixed(1)}%`,
132
+ );
133
+ console.log(
134
+ `negative owner-at-1: ${rates.negativeSelfRank1.toFixed(1)}% · declared route top-5 ${rates.routedTop5.toFixed(1)}% · route beats owner ${rates.routeBeatsOwner.toFixed(1)}%`,
135
+ );
136
+ console.log('highest description overlaps:');
137
+ for (const row of collisions.slice(0, 10)) {
138
+ console.log(` ${row.score.toFixed(2)} ${row.left} ↔ ${row.right}`);
139
+ }
140
+ const misses = [...result.missedBySkill].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
141
+ console.log(`largest top-5 gaps: ${misses.slice(0, 10).map(([id, count]) => `${id} (${count})`).join(', ')}`);
142
+
143
+ const failures = [];
144
+ if (rates.rank1 < FLOORS.rank1) failures.push(`rank-1 ${rates.rank1.toFixed(1)}% < ${FLOORS.rank1}%`);
145
+ if (rates.top5 < FLOORS.top5) failures.push(`top-5 ${rates.top5.toFixed(1)}% < ${FLOORS.top5}%`);
146
+ if (rates.routedTop5 < FLOORS.routedTop5) {
147
+ failures.push(`declared-route top-5 ${rates.routedTop5.toFixed(1)}% < ${FLOORS.routedTop5}%`);
148
+ }
149
+ if (rates.negativeSelfRank1 > FLOORS.maxNegativeSelfRank1) {
150
+ failures.push(`negative owner-at-1 ${rates.negativeSelfRank1.toFixed(1)}% > ${FLOORS.maxNegativeSelfRank1}%`);
151
+ }
152
+ if (collisions[0]?.score >= FLOORS.maxCollision) {
153
+ failures.push(`description collision ${collisions[0].score.toFixed(2)} >= ${FLOORS.maxCollision}`);
154
+ }
155
+
156
+ if (failures.length) {
157
+ console.error(`FAIL: ${failures.join('; ')}`);
158
+ process.exit(1);
159
+ }
160
+ console.log('PASS');
161
+ }
162
+
163
+ if (import.meta.url === `file://${process.argv[1]}`) main();
package/scripts/rsc.js CHANGED
@@ -12,6 +12,7 @@ import { audit, writeAuditReport } from './audit.js';
12
12
  import { DOMAINS } from './lib/domains.js';
13
13
  import { listBackups, restoreBackup } from './lib/backups.js';
14
14
  import { runUpgrade } from './lib/upgrade.js';
15
+ import { DEFAULT_SKILL_FLOOR, withDefaultSkillFloor } from './lib/default-skill-floor.js';
15
16
 
16
17
  const argv = process.argv.slice(2);
17
18
  const cmd = argv[0];
@@ -97,8 +98,8 @@ function printNextSteps(targets, ids) {
97
98
  say(' "sdd-init" then "I want <your idea>"');
98
99
  say(' → walks you specify → plan → tasks → implement → verify → ship.');
99
100
  }
100
- say(` ${n++}. From there, work in your own words. orient + suggest stay always-on:`);
101
- say(' they keep you oriented after each step and propose the missing skill (you confirm, it installs).');
101
+ say(` ${n++}. From there, work in your own words. orient + suggest stay always-on; bro is ready on request:`);
102
+ say(' they keep you oriented, propose missing skills, and rewrite in plain human language when asked.');
102
103
  say('\n Add something by hand anytime: npx @ericrisco/rsc add <skill>');
103
104
  say(' Browse the catalog / get picks: npx @ericrisco/rsc consult "whatever you need"');
104
105
  say('────────────────────────────────────────────────────────');
@@ -116,7 +117,7 @@ function printAgentHandoff() {
116
117
  say('AGENT, do this now:');
117
118
  say(' 1. Reload/restart this session so the new skills + hooks load');
118
119
  say(' (Claude Code: restart the session · Codex/AGENTS.md tools: next turn).');
119
- say(' 2. After reload you are EQUIPPED — orient + suggest are always-on.');
120
+ say(' 2. After reload you are EQUIPPED — orient + suggest are always-on; bro is installed.');
120
121
  say(' 3. Tell the user rsc is ready; they can start in their own words.');
121
122
  say(' Do NOT auto-start a task — wait for the user.');
122
123
  say('════════════════════════════════════════════════');
@@ -153,12 +154,12 @@ function printContextBudget(b) {
153
154
 
154
155
  async function wizard() {
155
156
  const m = loadManifest();
156
- await banner();
157
+ await banner(m.counts.skills);
157
158
  say(' the skill catalog for your assistant (Claude Code · Codex · Cursor · Gemini · Antigravity)\n');
158
159
  // Navigable loop: esc / "← Back" / "no" all return here instead of quitting.
159
160
  for (;;) {
160
161
  const choice = await select('What do you want to do?', [
161
- { key: 'base', label: 'Base install — the essentials (orient + suggest + harness + init)' },
162
+ { key: 'base', label: 'Base install — the essentials (orient + suggest + bro + harness + init)' },
162
163
  { key: 'sdd', label: 'Base + Spec-Driven Development — the specify → plan → implement → ship flow' },
163
164
  { key: 'manual', label: 'Pick skills by hand, by area' },
164
165
  ]);
@@ -173,9 +174,9 @@ async function wizard() {
173
174
  ids = picked;
174
175
  } else continue;
175
176
 
176
- // The floor is always installed: the compass + the detector.
177
- ids = [...new Set(['orient', 'suggest', ...ids])];
178
- if (ids.length <= 2 && choice !== 'base') {
177
+ // The floor is always installed: compass + detector + human-language pass.
178
+ ids = withDefaultSkillFloor(ids);
179
+ if (ids.length <= DEFAULT_SKILL_FLOOR.length && choice !== 'base') {
179
180
  say('\nNothing chosen — back to the menu.');
180
181
  continue;
181
182
  }
@@ -216,7 +217,7 @@ async function main() {
216
217
  if (argv[i].startsWith('--')) { i++; continue; }
217
218
  requested.push(argv[i]);
218
219
  }
219
- const ids = [...new Set(['orient', 'suggest', ...requested])];
220
+ const ids = withDefaultSkillFloor(requested);
220
221
  for (const t of targets) await applyInstall({ skillIds: ids, target: t });
221
222
  say(`✅ Installed for ${targets.join(', ')}: ${requested.join(', ')}`);
222
223
  return void say(' ↻ Reload/restart your assistant so the new skill activates.');
@@ -225,7 +226,7 @@ async function main() {
225
226
  const profile = flag('profile') || 'minimal';
226
227
  const without = argv.filter((a, i) => argv[i - 1] === '--without');
227
228
  let ids = skillsForProfile(loadManifest(), profile);
228
- ids = [...new Set(['orient', 'suggest', ...ids])].filter((id) => !without.includes(id));
229
+ ids = withDefaultSkillFloor(ids).filter((id) => !without.includes(id));
229
230
  for (const t of targets) await applyInstall({ skillIds: ids, target: t });
230
231
  say(`✅ Profile '${profile}' installed for ${targets.join(', ')} (${ids.length} skills)`);
231
232
  printAgentHandoff();
@@ -43,7 +43,7 @@ profiles: [core, full] # optional: named-profile membership
43
43
  - `tags` drive the FTS recommender — pick the words a user would actually type.
44
44
  - `recommends` must reference **real skill ids**; `build-manifest.js --validate`
45
45
  fails on a dangling id.
46
- - `profiles` is optional. `minimal` = the floor (`suggest`, `harness`, `init`);
46
+ - `profiles` is optional. `minimal` = the floor (`orient`, `suggest`, `bro`, `harness`, `init`);
47
47
  `core` = the SDD workflow; `full` = everything. Most stack skills set `[full]`
48
48
  or omit it (they are installed on demand, not by profile).
49
49
 
@@ -0,0 +1,68 @@
1
+ ---
2
+ name: bro
3
+ description: "Use whenever the user asks to make an answer or draft sound human, natural, plain-spoken or less AI-written — including a terse bro after the last response. Rewrites or drafts in the same language while preserving meaning, facts and channel. NOT a reusable brand voice (that is `brand-voice`), NOT translation (that is `translation-l10n`)."
4
+ tags: [bro, human-writing, humano, plain-language, natural-language, rewrite, no-jargon]
5
+ recommends: [brand-voice, technical-writing, translation-l10n]
6
+ profiles: [minimal, core, full]
7
+ origin: risco
8
+ ---
9
+
10
+ # bro — say it like a person
11
+
12
+ Make the words sound like someone with something real to say. Keep the substance; remove the
13
+ performance around it.
14
+
15
+ ## The contract
16
+
17
+ 1. **Find the source without ceremony.** A bare “bro”, “write like a human”, “hazlo más natural”,
18
+ or equivalent points to the assistant's last answer. If the user pasted text, rewrite that. If
19
+ they requested new copy, draft it directly. Ask for text only when no source exists.
20
+ 2. **Preserve the payload.** Keep names, facts, numbers, links, commitments, constraints,
21
+ uncertainty and necessary warnings. Do not improve the prose by inventing evidence, opinions,
22
+ anecdotes or certainty.
23
+ 3. **Match the room.** Keep the user's language and fit the audience, relationship and channel. A
24
+ natural legal notice is still precise; a natural Slack message can be loose. “Human” does not
25
+ automatically mean casual.
26
+ 4. **Cut the bot-shaped scaffolding.** Remove throat-clearing, needless summaries, repeated
27
+ conclusions, corporate filler, fake enthusiasm, abstract nouns and headings or bullets that do
28
+ not help the reader navigate.
29
+ 5. **Restore a real cadence.** Prefer concrete verbs, direct sentences and natural contractions.
30
+ Vary sentence length when it helps. Use the words a person in this context would actually choose,
31
+ without manufacturing slang, typos or choppy fragments.
32
+ 6. **Return the writing, not a review of the writing.** By default, output the rewritten or newly
33
+ drafted text with no “here is a more human version” preface. Explain the changes only if asked.
34
+
35
+ When the original contains code, commands, URLs, quoted terms or regulated wording, leave those
36
+ pieces exact unless the user explicitly asks to change them. Explain unavoidable jargon once in
37
+ plain language instead of deleting precision.
38
+
39
+ ## What “human” means here
40
+
41
+ | Aim | The result |
42
+ | --- | --- |
43
+ | Plain, not simplistic | Easy to follow without losing the idea |
44
+ | Concise, not incomplete | No padding; all load-bearing facts remain |
45
+ | Warm, not performative | Appropriate care without canned empathy |
46
+ | Conversational, not sloppy | Natural rhythm without fake mistakes |
47
+ | Specific, not decorated | Concrete nouns and verbs instead of hype |
48
+
49
+ ## Anti-patterns
50
+
51
+ | Anti-pattern | Why it fails | Do this instead |
52
+ | --- | --- | --- |
53
+ | Add typos, slang or filler to seem human | It performs humanity and can sound less credible | Match the actual relationship and channel |
54
+ | Replace technical terms with vague words | Readability bought with lost meaning is a defect | Keep or define the necessary term |
55
+ | Invent a personal story or strong opinion | A smoother fabrication is still a fabrication | Preserve only what the source supports |
56
+ | Use the same “casual” voice for every audience | A client email, README and condolence need different registers | Infer the room before choosing the cadence |
57
+ | Promise the result is undetectable as AI | No rewrite can honestly guarantee that | Promise clarity and naturalness, not detector outcomes |
58
+ | Announce and justify every edit | The preamble becomes the same friction the user asked to remove | Lead with the finished words |
59
+
60
+ ## Output boundary
61
+
62
+ Treat the rewritten text as an artifact: keep harness commentary outside it. If the task also
63
+ requires a brújula block, place that block after a clear separation so it cannot be copied into an
64
+ email, document or message by accident.
65
+
66
+ ## Orientación (siempre)
67
+
68
+ Cierra cada turno con el **bloque-brújula** (📍 dónde estás · ✅ qué hiciste · 🧭 por qué · ➡️ siguiente, terminando en pregunta), calibrado al dial de `02-DOCS/wiki/harness/user-profile.md`. **Nunca termines en seco.** Protocolo completo: skill `orient` → `skills/orient/references/orientation-contract.md`. (Defiere a `suggest` el "¿instalo la skill que falta?".)
@@ -0,0 +1,22 @@
1
+ # Evals — bro
2
+
3
+ These cases test two different claims: that human-language requests route to `bro` without
4
+ stealing adjacent writing jobs, and that the loaded skill produces a more natural result without
5
+ changing the payload.
6
+
7
+ ## Triggering
8
+
9
+ Load only `bro` and run every `should_trigger` and `should_not_trigger` prompt in 3–5 fresh
10
+ sessions. A positive passes when `bro` loads in a majority of trials. A negative passes when it
11
+ stays quiet; also check that the declared `route_to` skill is the genuine owner. Require at least
12
+ 90% accuracy across all cases.
13
+
14
+ ## Capability
15
+
16
+ Run the capability scenario in two fresh conditions: without `bro`, then with it. Use the same
17
+ source answer and one consistent grader. Score each `must_include` point over three trials. The
18
+ with-skill condition must satisfy at least 80% of the rubric and beat the baseline by at least 20
19
+ percentage points.
20
+
21
+ This is an LLM/human-judged A/B eval, not deterministic CI. Re-run it after changing either the
22
+ description or the body; routing and prose quality are both wording-sensitive.
@@ -0,0 +1,58 @@
1
+ skill: bro
2
+
3
+ # `bro` is the final-language pass: it rewrites an existing answer/draft or
4
+ # drafts requested text in a natural register. It does not define a reusable
5
+ # brand system, translate content, or choose a documentation/copy strategy.
6
+ should_trigger:
7
+ - prompt: "Bro, escribe como un humano."
8
+ why: "A terse bro-style instruction after an answer is the canonical request to rewrite the last assistant message naturally."
9
+
10
+ - prompt: "Rewrite your last answer so it sounds like a person, not a consultant."
11
+ why: "The user wants a human-language pass over the previous answer while preserving its content."
12
+
13
+ - prompt: "Haz este correo más natural y directo, pero no cambies las fechas ni los compromisos."
14
+ why: "Rewriting a supplied draft while preserving facts and commitments is the skill's core contract."
15
+
16
+ - prompt: "Això sona escrit per una IA. Fes-ho més humà i sense tanta cerimònia."
17
+ why: "Catalan request to remove AI-shaped formality tests multilingual semantic triggering."
18
+
19
+ - prompt: "Can you say that again in plain language without all the corporate jargon?"
20
+ why: "The symptom is jargon and artificial formality rather than the skill name, but the needed action is the same rewrite."
21
+
22
+ - prompt: "Escríbeme una respuesta breve y humana para decirle que llegaremos un día tarde."
23
+ why: "The user requests new text rather than a rewrite, but explicitly wants the same natural, truthful voice."
24
+
25
+ - prompt: "Keep all the technical details, just make it sound less robotic."
26
+ why: "Humanizing without deleting precision exercises the distinction between plain language and simplification."
27
+
28
+ should_not_trigger:
29
+ - prompt: "Define a reusable voice guide so every writer and channel sounds like our brand."
30
+ route_to: "brand-voice"
31
+ why: "A standing voice system is brand-voice; bro only performs a local writing pass."
32
+
33
+ - prompt: "Translate this onboarding email from English to Spanish and adapt the date and currency formats."
34
+ route_to: "translation-l10n"
35
+ why: "Translation and locale-aware adaptation are owned by translation-l10n, even when natural prose matters."
36
+
37
+ - prompt: "Turn this mixed README into a clean tutorial and API reference with runnable examples."
38
+ route_to: "technical-writing"
39
+ why: "Choosing documentation modes and restructuring technical docs is technical-writing, not a final tone pass."
40
+
41
+ - prompt: "Write the hero, value proposition and CTA for our pricing page."
42
+ route_to: "landing-copy"
43
+ why: "Conversion architecture and finished landing-page copy belong to landing-copy; bro may only polish its language afterward."
44
+
45
+ - prompt: "Rewrite this function to be simpler and remove the unnecessary abstractions."
46
+ route_to: "simplify-code"
47
+ why: "The requested source-code simplification is owned by simplify-code; 'rewrite' does not make it a prose task."
48
+
49
+ capability:
50
+ - scenario: "The previous assistant answer is a long Spanish status update full of headings, repeated summaries, corporate jargon and fake enthusiasm. Rewrite it for a client while preserving a June 30 deadline, a EUR 2,400 price, one unresolved risk, two URLs and the original uncertainty."
51
+ must_include:
52
+ - "preserves June 30, EUR 2,400, both URLs, the unresolved risk and the original uncertainty exactly"
53
+ - "uses natural Spanish appropriate for a client rather than automatically becoming slangy or over-casual"
54
+ - "removes needless headings, repeated conclusions, corporate filler and canned enthusiasm"
55
+ - "uses concrete verbs and readable sentence rhythm without adding typos or forced fragments"
56
+ - "does not invent evidence, anecdotes, opinions, commitments or certainty"
57
+ - "returns the rewritten message without a meta preface or an explanation of the edits"
58
+ - "keeps any harness orientation clearly outside the client-ready artifact"
@@ -0,0 +1,113 @@
1
+ ---
2
+ name: decision-challenge
3
+ description: "Use when a high-impact plan, migration or architecture claim needs a bounded adversarial challenge before commitment. Isolates claims from persuasion, attacks assumptions, reconciles doubts with evidence and emits proceed/hold/stop. NOT a finished-diff review (`review`/`code-review`), NOT SDD artifact consistency (`analyze`)."
4
+ tags: [adversarial-review, decision-quality, assumptions, risk, preflight]
5
+ recommends: [analyze, review, code-review, decision-records, security-scan]
6
+ profiles: [core, full]
7
+ origin: risco
8
+ ---
9
+
10
+ # decision-challenge — doubt with a stopping rule
11
+
12
+ Use this immediately before a consequential commitment: an irreversible migration, a production-auth change, a costly architecture bet, a launch exception, or a plan whose confidence is higher than its evidence. The purpose is to find the decision-changing doubt, not to perform scepticism forever.
13
+
14
+ This skill is read-only unless the user also asks to change the artifact. It emits a challenge record and a verdict. Finished code review stays with `../review/SKILL.md` or `../code-review/SKILL.md`; consistency across constitution/spec/plan/tasks stays with `../analyze/SKILL.md`.
15
+
16
+ ## One bounded cycle
17
+
18
+ ```text
19
+ MATERIALIZE → ISOLATE → ATTACK → TEST → RECONCILE → VERDICT
20
+ ```
21
+
22
+ ### 1. MATERIALIZE the decision
23
+
24
+ Do not challenge a cloud of conversation. Write the concrete packet:
25
+
26
+ - decision or claim being made;
27
+ - artifact and exact revision it applies to;
28
+ - constraints and invariants that must hold;
29
+ - evidence the author relies on;
30
+ - known unknowns;
31
+ - cost of a false positive (unnecessary stop) and false negative (unsafe proceed).
32
+
33
+ If the decision cannot be stated in one sentence, split it into claims. “The migration is safe” is not atomic; lock duration, dependency completeness, rollback time and consumer compatibility are separate claims.
34
+
35
+ ### 2. ISOLATE claim from persuasion
36
+
37
+ Create a compact challenge packet containing the artifact, contract, constraints and evidence — not the author’s confidence, status, prestige or preferred conclusion. This reduces anchoring.
38
+
39
+ A genuinely fresh pass is useful when the platform and user authorize one: another agent/model/context can receive only the packet. It is optional, never automatic, and never an excuse to leak data or broaden tool authority. When independent execution is unavailable, make the isolation explicit and challenge locally.
40
+
41
+ ### 3. ATTACK each material claim
42
+
43
+ For each claim, ask:
44
+
45
+ - What observation would make it false?
46
+ - Which dependency or consumer is missing from the inventory?
47
+ - Is the evidence direct, current and representative, or an analogy?
48
+ - What timing, ordering, concurrency or partial-failure path is assumed away?
49
+ - What privilege, data-quality or human handoff must work perfectly?
50
+ - If rollback is promised, has restoration time and data reconciliation been proved?
51
+ - Can the decision be made reversible or staged before accepting this risk?
52
+
53
+ Prefer one sharp counterexample over ten generic cautions. Rate doubts as blocker, material, or minor by consequence and likelihood; severity is not a count of how uncomfortable the question sounds.
54
+
55
+ ### 4. TEST instead of debating
56
+
57
+ Turn the strongest doubts into evidence requests: dependency query, row count, dry run, restore rehearsal, shadow traffic, contract test, load profile, permission audit, failure injection or consumer confirmation. Run safe read-only checks that are in scope. Do not mutate production or recruit external systems merely to settle the argument.
58
+
59
+ ### 5. RECONCILE claim by claim
60
+
61
+ Every doubt ends in one state:
62
+
63
+ - **verified** — direct evidence supports the claim;
64
+ - **refuted** — evidence contradicts it;
65
+ - **mitigated** — the plan changed so the original doubt no longer applies;
66
+ - **uncertain** — evidence is missing and consequence remains;
67
+ - **accepted risk** — an authorized owner explicitly accepts a bounded residual risk.
68
+
69
+ Record evidence, owner and next action. “Discussed” and “probably fine” are not states.
70
+
71
+ ### 6. VERDICT
72
+
73
+ - **PROCEED** — no blocker survives; material uncertainties have owners and safe bounds.
74
+ - **HOLD** — a decision-critical uncertainty can be resolved without abandoning the approach.
75
+ - **STOP / REDESIGN** — a claim is refuted or the remaining downside is outside the stated tolerance.
76
+
77
+ Name what would change a HOLD or STOP verdict. A verdict without an unlock condition teaches nothing.
78
+
79
+ ## Stopping rule
80
+
81
+ Run at most two cycles. A second cycle is allowed only after new evidence or a changed artifact. Stop earlier when a full pass produces no new blocker/material doubt. Rephrased uncertainty is not new evidence. If the decision remains uncertain after two cycles, keep the uncertainty in the verdict; do not manufacture confidence through repetition.
82
+
83
+ ## Challenge record
84
+
85
+ ```markdown
86
+ # Decision challenge — <decision> — <revision/date>
87
+
88
+ ## Contract and stakes
89
+ Decision · invariants · false-positive cost · false-negative cost.
90
+
91
+ ## Claims
92
+ | Claim | Evidence before | Doubt | Test/evidence | State | Owner |
93
+
94
+ ## Surviving risk
95
+ Blockers · material uncertainty · accepted residual risk.
96
+
97
+ ## Verdict
98
+ PROCEED / HOLD / STOP — reason — unlock condition.
99
+ ```
100
+
101
+ ## Anti-patterns
102
+
103
+ | Smell | Correction |
104
+ |---|---|
105
+ | Every reversible choice receives a panel | Reserve the skill for consequence, uncertainty or irreversibility. |
106
+ | The author’s explanation counts as independent evidence | Isolate claims and require an observation outside the persuasion. |
107
+ | Another model/agent is mandatory | Use fresh context only when authority, tooling and data boundaries allow it. |
108
+ | Executive pressure relabels a blocker as accepted risk | Require an accountable owner with authority over the consequence. |
109
+ | Domain controls are recreated generically | Route security to `security-scan` and AI quality experiments to `agent-eval`. |
110
+
111
+ ## Orientación (siempre)
112
+
113
+ Cierra cada turno con el **bloque-brújula** (📍 dónde estás · ✅ qué hiciste · 🧭 por qué · ➡️ siguiente, terminando en pregunta), calibrado al dial de `02-DOCS/wiki/harness/user-profile.md`. **Nunca termines en seco.** Protocolo completo: skill `orient` → `skills/orient/references/orientation-contract.md`. (Defiere a `suggest` el “¿instalo la skill que falta?”.)
@@ -0,0 +1,3 @@
1
+ # Evals — decision-challenge
2
+
3
+ Routing must reserve this workflow for consequential pre-commitment decisions, keeping finished code review, SDD consistency, security audits and AI experiments with their owners. The capability case checks atomic claims, isolation from persuasion, evidence-producing challenges, explicit reconciliation and a bounded verdict.
@@ -0,0 +1,42 @@
1
+ skill: decision-challenge
2
+
3
+ should_trigger:
4
+ - prompt: "Before the irreversible customer-ID migration, challenge every safety claim and give me proceed, hold, or stop."
5
+ why: "High-impact irreversible decision with explicit adversarial preflight."
6
+ - prompt: "No me creo nuestra propia confianza en este cambio de auth: separa las afirmaciones y busca qué las tumba."
7
+ why: "Spanish request to isolate and attack high-stakes claims."
8
+ - prompt: "Stress-test this architecture bet, especially the facts that would reverse the recommendation."
9
+ why: "A consequential choice needs decision-changing counterevidence."
10
+ - prompt: "Abans de signar el GO, qüestiona el pla sense deixar-te arrossegar per l'explicació de l'autor."
11
+ why: "Catalan request for an isolated adversarial launch decision."
12
+ - prompt: "The executive wants an exception to failed launch gates; build a challenge packet and reconcile the surviving risks."
13
+ why: "Authority pressure plus failed gates calls for evidence-bound verdict."
14
+ - prompt: "Our rollback claim is based on a backup; verify restore time, data reconciliation, and dependencies before we proceed."
15
+ why: "Specific migration claims need tests, not debate."
16
+
17
+ should_not_trigger:
18
+ - prompt: "Review this finished diff against the approved SDD spec and report findings."
19
+ route_to: review
20
+ why: "Post-verification SDD code judgment."
21
+ - prompt: "Review this standalone pull request; there is no spec or plan."
22
+ route_to: code-review
23
+ why: "Spec-less concrete diff review."
24
+ - prompt: "Cross-read the constitution, spec, plan and tasks for contradictions before implementation."
25
+ route_to: analyze
26
+ why: "Pre-code consistency gate across SDD artifacts."
27
+ - prompt: "Threat-model the upload endpoint and run the repository security scanners."
28
+ route_to: security-scan
29
+ why: "Security domain audit and tooling own the task."
30
+ - prompt: "Compare the new system prompt to the old one on a golden set and measure lift."
31
+ route_to: agent-eval
32
+ why: "Controlled AI-system evaluation, not generic decision challenge."
33
+
34
+ capability:
35
+ - scenario: "A team plans to replace integer customer IDs with UUIDs in one maintenance window and assumes foreign keys, external consumers, duration and last night's backup are all sufficient."
36
+ must_include:
37
+ - "Materializes separate claims for dependency coverage, row volume/duration, consumer compatibility, restore timing, and data reconciliation."
38
+ - "Creates an isolated challenge packet that excludes author confidence and treats the migration artifact/revision as fixed input."
39
+ - "Raises concrete counterexamples and converts the strongest doubts into read-only inventory, dry-run, restore-rehearsal, and consumer-evidence requests."
40
+ - "Reconciles each claim as verified, refuted, mitigated, uncertain, or explicitly accepted risk with evidence and owner."
41
+ - "Issues PROCEED, HOLD, or STOP with a clear reason and the evidence/change that would unlock a non-proceed verdict."
42
+ - "Applies the two-cycle stopping rule and does not require cross-model execution or mutate production without authority."