@dreki-gg/pi-plan-mode 0.10.1 → 0.13.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.
- package/CHANGELOG.md +24 -0
- package/extensions/plan-mode/__tests__/package-skills.test.ts +47 -0
- package/extensions/plan-mode/__tests__/prompts.test.ts +38 -0
- package/extensions/plan-mode/constants.ts +1 -0
- package/extensions/plan-mode/index.ts +26 -3
- package/extensions/plan-mode/plan-storage.ts +13 -0
- package/extensions/plan-mode/prompts.ts +12 -7
- package/extensions/plan-mode/tools/submit-plan.ts +10 -16
- package/extensions/plan-mode/tools/update-step.ts +1 -0
- package/extensions/plan-mode/types.ts +1 -2
- package/package.json +5 -1
- package/skills/technical-options/SKILL.md +89 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# @dreki-gg/pi-plan-mode
|
|
2
2
|
|
|
3
|
+
## 0.13.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Replace context+risks with HANDOFF.md and step summaries. submit_plan now writes a HANDOFF.md alongside plan.json. Completion message shows an actual summary of changes from step notes instead of just a checklist. Executor is prompted to always include notes summarizing what was done.
|
|
8
|
+
|
|
9
|
+
## 0.12.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Include `skills/` directory in package files so the bundled technical-options skill is published.
|
|
14
|
+
|
|
15
|
+
## 0.12.0
|
|
16
|
+
|
|
17
|
+
### Minor Changes
|
|
18
|
+
|
|
19
|
+
- Bundle `technical-options` skill inside the package (installable via `pi.skills`). The planner prompt now explicitly tells the agent to generate proposals itself and only delegate voting to subagents, keeping the planner visible as the main agent.
|
|
20
|
+
|
|
21
|
+
## 0.11.0
|
|
22
|
+
|
|
23
|
+
### Minor Changes
|
|
24
|
+
|
|
25
|
+
- Integrate technical-options skill into plan mode: add `subagent` to plan-phase tools and nudge the planner to use structured proposal evaluation when facing significant design decisions with multiple viable approaches.
|
|
26
|
+
|
|
3
27
|
## 0.10.1
|
|
4
28
|
|
|
5
29
|
### Patch Changes
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
const PKG_ROOT = join(import.meta.dir, '..', '..', '..');
|
|
6
|
+
const PKG_JSON = JSON.parse(readFileSync(join(PKG_ROOT, 'package.json'), 'utf-8'));
|
|
7
|
+
|
|
8
|
+
describe('package.json pi manifest', () => {
|
|
9
|
+
test('declares skills directory', () => {
|
|
10
|
+
expect(PKG_JSON.pi?.skills).toBeDefined();
|
|
11
|
+
expect(PKG_JSON.pi.skills).toContain('./skills');
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe('bundled technical-options skill', () => {
|
|
16
|
+
const skillDir = join(PKG_ROOT, 'skills', 'technical-options');
|
|
17
|
+
const skillFile = join(skillDir, 'SKILL.md');
|
|
18
|
+
|
|
19
|
+
test('SKILL.md exists', () => {
|
|
20
|
+
expect(existsSync(skillFile)).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('has valid frontmatter with name and description', () => {
|
|
24
|
+
const content = readFileSync(skillFile, 'utf-8');
|
|
25
|
+
expect(content).toMatch(/^---\n/);
|
|
26
|
+
expect(content).toMatch(/name:\s*technical-options/);
|
|
27
|
+
expect(content).toMatch(/description:/);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('description does not contain overly broad triggers', () => {
|
|
31
|
+
const content = readFileSync(skillFile, 'utf-8');
|
|
32
|
+
// These were flagged during review as too broad for routing
|
|
33
|
+
expect(content).not.toMatch(/description:.*help me decide/i);
|
|
34
|
+
expect(content).not.toMatch(/description:.*what are my options/i);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('skill content mentions parallel voting agents', () => {
|
|
38
|
+
const content = readFileSync(skillFile, 'utf-8');
|
|
39
|
+
expect(content).toContain('voting');
|
|
40
|
+
expect(content).toContain('parallel');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('skill content includes adversarial framing challenge step', () => {
|
|
44
|
+
const content = readFileSync(skillFile, 'utf-8');
|
|
45
|
+
expect(content).toMatch(/challenge.*framing|framing.*challenge/i);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { buildPlanModePrompt } from '../prompts.js';
|
|
3
|
+
import { PLAN_TOOLS } from '../constants.js';
|
|
4
|
+
|
|
5
|
+
describe('buildPlanModePrompt', () => {
|
|
6
|
+
const prompt = buildPlanModePrompt();
|
|
7
|
+
|
|
8
|
+
test('mentions technical-options skill for significant decisions', () => {
|
|
9
|
+
expect(prompt).toContain('technical-options');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test('mentions handoff instead of context and risks', () => {
|
|
13
|
+
expect(prompt).toContain('handoff');
|
|
14
|
+
expect(prompt).not.toContain('- risks:');
|
|
15
|
+
expect(prompt).not.toContain('- context:');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('tells planner to do proposal generation itself, not delegate', () => {
|
|
19
|
+
// The planner should generate proposals as the main agent, only using
|
|
20
|
+
// subagents for voting/evaluation — not delegating the entire workflow
|
|
21
|
+
expect(prompt).toMatch(/you.*generat|generat.*yourself|do this yourself/i);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('mentions subagent is available for voting only', () => {
|
|
25
|
+
// Should clarify subagent is for evaluation, not for the whole workflow
|
|
26
|
+
expect(prompt).toMatch(/subagent|voting|evaluat/i);
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe('PLAN_TOOLS', () => {
|
|
31
|
+
test('includes subagent for voting workflows', () => {
|
|
32
|
+
expect(PLAN_TOOLS).toContain('subagent');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('includes search_skills for skill discovery', () => {
|
|
36
|
+
expect(PLAN_TOOLS).toContain('search_skills');
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -217,11 +217,34 @@ export default function planMode(pi: ExtensionAPI): void {
|
|
|
217
217
|
await updatePlansManifest(state.planDir.replace(/^\.plans\//, ''), 'done', state.plan.title);
|
|
218
218
|
await savePlanToDisk(state.planDir, state.plan);
|
|
219
219
|
}
|
|
220
|
-
const
|
|
221
|
-
|
|
220
|
+
const done = state.plan.steps.filter((s) => s.status === 'done').length;
|
|
221
|
+
const skipped = state.plan.steps.filter((s) => s.status === 'skipped').length;
|
|
222
|
+
const total = state.plan.steps.length;
|
|
223
|
+
const stats = skipped > 0
|
|
224
|
+
? `${done}/${total} done, ${skipped} skipped`
|
|
225
|
+
: `${done}/${total} done`;
|
|
226
|
+
|
|
227
|
+
// Build a summary of what was actually done from step notes
|
|
228
|
+
const changeSummary = state.plan.steps
|
|
229
|
+
.map((s, i) => {
|
|
230
|
+
const icon = s.status === 'done' ? '✓' : '⊘';
|
|
231
|
+
const label = `${i + 1}. ${icon} ${s.description}`;
|
|
232
|
+
return s.notes ? `${label}\n ${s.notes}` : label;
|
|
233
|
+
})
|
|
222
234
|
.join('\n');
|
|
235
|
+
|
|
236
|
+
const summary = [
|
|
237
|
+
`**Plan Complete!** ✓ — ${state.plan.title}`,
|
|
238
|
+
'',
|
|
239
|
+
`> ${stats}`,
|
|
240
|
+
'',
|
|
241
|
+
'## Summary',
|
|
242
|
+
'',
|
|
243
|
+
changeSummary,
|
|
244
|
+
].join('\n');
|
|
245
|
+
|
|
223
246
|
pi.sendMessage(
|
|
224
|
-
{ customType: 'plan-complete', content:
|
|
247
|
+
{ customType: 'plan-complete', content: summary, display: true },
|
|
225
248
|
{ triggerTurn: false },
|
|
226
249
|
);
|
|
227
250
|
|
|
@@ -48,6 +48,19 @@ export async function readAndClearExecPending(): Promise<{ planDir: string; conf
|
|
|
48
48
|
return undefined;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
export async function saveHandoff(planDir: string, content: string): Promise<void> {
|
|
52
|
+
await mkdir(planDir, { recursive: true });
|
|
53
|
+
await writeFile(`${planDir}/HANDOFF.md`, content, 'utf-8');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function loadHandoff(planDir: string): Promise<string | undefined> {
|
|
57
|
+
try {
|
|
58
|
+
return await readFile(`${planDir}/HANDOFF.md`, 'utf-8');
|
|
59
|
+
} catch {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
51
64
|
export async function updatePlansManifest(
|
|
52
65
|
planName: string,
|
|
53
66
|
status: 'in-progress' | 'done',
|
|
@@ -21,12 +21,17 @@ Your task:
|
|
|
21
21
|
When you are ready to finalize the plan, call the submit_plan tool with:
|
|
22
22
|
- name: a short kebab-case name (e.g. "add-auth-middleware")
|
|
23
23
|
- title: a human-readable plan title
|
|
24
|
-
-
|
|
24
|
+
- handoff: a markdown document that explains:
|
|
25
|
+
1. **What** change is being made and **why** it matters
|
|
26
|
+
2. The approach and key design decisions
|
|
27
|
+
3. Relevant codebase context: file paths, APIs, patterns, constraints, and gotchas
|
|
28
|
+
This document is saved as HANDOFF.md and must be clear to both human reviewers and executor agents.
|
|
25
29
|
- steps: an array of steps, each with a short description (≤60 chars for display) and detailed implementation instructions
|
|
26
|
-
- risks: any open questions, assumptions, or concerns
|
|
27
30
|
|
|
28
31
|
Do NOT attempt to make product code changes — only analyze and plan.
|
|
29
|
-
Do NOT write files manually — use submit_plan to finalize the plan
|
|
32
|
+
Do NOT write files manually — use submit_plan to finalize the plan.
|
|
33
|
+
|
|
34
|
+
When facing a significant technical decision with multiple viable approaches (architecture, API design, implementation strategy), use the technical-options skill: you generate the competing proposals yourself, then use the subagent tool to fan out voting agents for evaluation. Do not delegate the entire workflow to a subagent — you are the planner, you drive the process.`;
|
|
30
35
|
}
|
|
31
36
|
|
|
32
37
|
export function buildExecutionPrompt(plan: PlanData): string | undefined {
|
|
@@ -48,7 +53,7 @@ You are executing a structured plan. Your ONLY job is to implement the plan step
|
|
|
48
53
|
|
|
49
54
|
Rules:
|
|
50
55
|
- Work on ONE step at a time, starting with step ${currentStep.num}
|
|
51
|
-
- After completing each step, IMMEDIATELY call update_step to mark it done
|
|
56
|
+
- After completing each step, IMMEDIATELY call update_step to mark it done with notes summarizing what you changed (files modified, key decisions)
|
|
52
57
|
- Do NOT run diagnostics, linters, test suites, or skills unless a step explicitly asks for it
|
|
53
58
|
- Do NOT explore the codebase beyond what the current step requires
|
|
54
59
|
- Do NOT deviate from the plan — if something seems wrong, call update_step with status "blocked"
|
|
@@ -57,11 +62,11 @@ Rules:
|
|
|
57
62
|
Step ${currentStep.num}: ${currentStep.description}
|
|
58
63
|
Details: ${currentStep.details}
|
|
59
64
|
|
|
60
|
-
##
|
|
61
|
-
${plan.
|
|
65
|
+
## Handoff
|
|
66
|
+
${plan.handoff}
|
|
62
67
|
|
|
63
68
|
## All remaining steps
|
|
64
69
|
${stepList}
|
|
65
70
|
|
|
66
|
-
Start with step ${currentStep.num} NOW. When done, call update_step(step=${currentStep.num}, status="done").`;
|
|
71
|
+
Start with step ${currentStep.num} NOW. When done, call update_step(step=${currentStep.num}, status="done", notes="<brief summary of what you did>").`;
|
|
67
72
|
}
|
|
@@ -10,6 +10,7 @@ import { Text } from '@earendil-works/pi-tui';
|
|
|
10
10
|
import { Type } from 'typebox';
|
|
11
11
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
12
12
|
import { readPlansJson, serializePlansJson } from '../plans-json.js';
|
|
13
|
+
import { saveHandoff } from '../plan-storage.js';
|
|
13
14
|
import { toKebabCase } from '../utils.js';
|
|
14
15
|
import type { PlanData, PlanStep } from '../types.js';
|
|
15
16
|
|
|
@@ -22,24 +23,24 @@ export function registerSubmitPlanTool(pi: ExtensionAPI, callbacks: SubmitPlanCa
|
|
|
22
23
|
name: 'submit_plan',
|
|
23
24
|
label: 'Submit Plan',
|
|
24
25
|
description:
|
|
25
|
-
'Submit a structured plan with a title,
|
|
26
|
+
'Submit a structured plan with a title, handoff document, and numbered steps. ' +
|
|
26
27
|
'Each step has a short description (for progress display) and detailed implementation instructions. ' +
|
|
27
|
-
'This finalizes the plan
|
|
28
|
+
'This finalizes the plan, writes .plans/<name>/plan.json and .plans/<name>/HANDOFF.md.',
|
|
28
29
|
promptSnippet:
|
|
29
|
-
'Submit a structured plan with title,
|
|
30
|
+
'Submit a structured plan with title, handoff (what/why/context), and steps',
|
|
30
31
|
promptGuidelines: [
|
|
31
32
|
'Call submit_plan once when you have finished analyzing the codebase and are ready to finalize your plan.',
|
|
32
33
|
'Each submit_plan step description should be a short label (≤60 chars). Put full implementation instructions in the details field.',
|
|
33
|
-
'The submit_plan
|
|
34
|
+
'The submit_plan handoff field is a markdown document explaining what change is being made, why it matters, and all relevant codebase context (file paths, APIs, patterns, constraints). It must be thorough enough that both a human reviewer and an executor agent with zero prior context can understand the plan.',
|
|
34
35
|
],
|
|
35
36
|
parameters: Type.Object({
|
|
36
37
|
name: Type.String({
|
|
37
38
|
description: 'Short kebab-case name for the plan (e.g. "add-auth-middleware")',
|
|
38
39
|
}),
|
|
39
40
|
title: Type.String({ description: 'Human-readable plan title' }),
|
|
40
|
-
|
|
41
|
+
handoff: Type.String({
|
|
41
42
|
description:
|
|
42
|
-
'
|
|
43
|
+
'Markdown content for HANDOFF.md — explains what change is being made, why, and includes relevant codebase context for the executor',
|
|
43
44
|
}),
|
|
44
45
|
steps: Type.Array(
|
|
45
46
|
Type.Object({
|
|
@@ -52,7 +53,6 @@ export function registerSubmitPlanTool(pi: ExtensionAPI, callbacks: SubmitPlanCa
|
|
|
52
53
|
}),
|
|
53
54
|
{ minItems: 1 },
|
|
54
55
|
),
|
|
55
|
-
risks: Type.String({ description: 'Open questions, assumptions, and risks' }),
|
|
56
56
|
}),
|
|
57
57
|
|
|
58
58
|
async execute(_toolCallId, params) {
|
|
@@ -67,14 +67,14 @@ export function registerSubmitPlanTool(pi: ExtensionAPI, callbacks: SubmitPlanCa
|
|
|
67
67
|
|
|
68
68
|
const plan: PlanData = {
|
|
69
69
|
title: params.title,
|
|
70
|
-
|
|
70
|
+
handoff: params.handoff,
|
|
71
71
|
steps,
|
|
72
|
-
risks: params.risks,
|
|
73
72
|
};
|
|
74
73
|
|
|
75
|
-
// Write plan.json
|
|
74
|
+
// Write plan.json and HANDOFF.md
|
|
76
75
|
await mkdir(planDir, { recursive: true });
|
|
77
76
|
await writeFile(`${planDir}/plan.json`, JSON.stringify(plan, null, 2) + '\n', 'utf-8');
|
|
77
|
+
await saveHandoff(planDir, params.handoff);
|
|
78
78
|
|
|
79
79
|
// Update plans.json manifest
|
|
80
80
|
const manifest = await readPlansJson();
|
|
@@ -132,12 +132,6 @@ export function registerSubmitPlanTool(pi: ExtensionAPI, callbacks: SubmitPlanCa
|
|
|
132
132
|
lines.push(` ${num} ${step.description}`);
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
// Risks
|
|
136
|
-
if (plan.risks) {
|
|
137
|
-
lines.push('');
|
|
138
|
-
lines.push(theme.fg('warning', '⚠ Risks: ') + theme.fg('dim', plan.risks));
|
|
139
|
-
}
|
|
140
|
-
|
|
141
135
|
return new Text(lines.join('\n'), 0, 0);
|
|
142
136
|
},
|
|
143
137
|
});
|
|
@@ -27,6 +27,7 @@ export function registerUpdateStepTool(pi: ExtensionAPI, callbacks: UpdateStepCa
|
|
|
27
27
|
promptSnippet: 'Mark a plan step as done, skipped, or blocked',
|
|
28
28
|
promptGuidelines: [
|
|
29
29
|
'Call update_step after completing each plan step to mark it done before moving to the next.',
|
|
30
|
+
'Always include notes when calling update_step — summarize what was done (files changed, key decisions made) for done steps, why it was unnecessary for skipped steps, or what went wrong for blocked steps.',
|
|
30
31
|
'Use update_step with status "skipped" if a step is unnecessary after inspecting the code.',
|
|
31
32
|
'Use update_step with status "blocked" and explain the reason in notes if a step cannot be completed — execution will pause for user input.',
|
|
32
33
|
],
|
|
@@ -11,9 +11,8 @@ export interface PlanStep {
|
|
|
11
11
|
|
|
12
12
|
export interface PlanData {
|
|
13
13
|
title: string;
|
|
14
|
-
|
|
14
|
+
handoff: string;
|
|
15
15
|
steps: PlanStep[];
|
|
16
|
-
risks: string;
|
|
17
16
|
}
|
|
18
17
|
|
|
19
18
|
export type ThinkingLevel = 'off' | 'minimal' | 'low' | 'medium' | 'high' | 'xhigh';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dreki-gg/pi-plan-mode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Two-phase planning workflow for pi — plan with claude-opus-4-6:medium, execute with gpt-5.5:low, with .plans/ file-based handoff",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package"
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
20
|
"extensions",
|
|
21
|
+
"skills",
|
|
21
22
|
"bin",
|
|
22
23
|
"README.md",
|
|
23
24
|
"CHANGELOG.md",
|
|
@@ -33,6 +34,9 @@
|
|
|
33
34
|
"pi": {
|
|
34
35
|
"extensions": [
|
|
35
36
|
"./extensions/plan-mode"
|
|
37
|
+
],
|
|
38
|
+
"skills": [
|
|
39
|
+
"./skills"
|
|
36
40
|
]
|
|
37
41
|
},
|
|
38
42
|
"dependencies": {
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: technical-options
|
|
3
|
+
description: Generate and rank competing options for a technical decision using parallel evaluators. Use when the user wants a structured comparison of implementation approaches, architecture alternatives, or engineering tradeoffs before choosing one. Not for binary yes/no or pure preference decisions.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Technical Options
|
|
7
|
+
|
|
8
|
+
Generate competing proposals for a technical decision, challenge the framing, then fan out to parallel voting agents for ranking.
|
|
9
|
+
|
|
10
|
+
## Process
|
|
11
|
+
|
|
12
|
+
### 1. Understand the problem
|
|
13
|
+
|
|
14
|
+
- Read relevant code and context
|
|
15
|
+
- Identify the core constraint or failure mode driving the decision
|
|
16
|
+
- Note available APIs, tools, and integration points
|
|
17
|
+
|
|
18
|
+
### 2. Research (optional)
|
|
19
|
+
|
|
20
|
+
Use `web_search` + `web_visit` if external patterns would help. Skip for purely internal decisions. If those tools are unavailable, proceed from repo context only.
|
|
21
|
+
|
|
22
|
+
### 3. Generate 3–5 proposals
|
|
23
|
+
|
|
24
|
+
Each proposal needs:
|
|
25
|
+
- **Letter** (A–E) and a short memorable name
|
|
26
|
+
- **One-paragraph description** with the key mechanism
|
|
27
|
+
- **Pros and cons** — honest tradeoffs, not sales pitches
|
|
28
|
+
|
|
29
|
+
Rules:
|
|
30
|
+
- Span the solution space — don't cluster around one idea
|
|
31
|
+
- Include a simplest-possible option and a most-robust option
|
|
32
|
+
- Include an unconventional or contrarian option when possible
|
|
33
|
+
- 3 proposals for small choices, 4–5 for architectural decisions
|
|
34
|
+
|
|
35
|
+
### 4. Challenge the framing
|
|
36
|
+
|
|
37
|
+
Before voting, spawn one `advisor` agent (or equivalent) with this task:
|
|
38
|
+
|
|
39
|
+
> "Here are N proposals for [problem]. What important constraint, framing assumption, or missing approach is absent? If you find a materially distinct option the proposals don't cover, describe it. If the slate is well-shaped, say so."
|
|
40
|
+
|
|
41
|
+
Only amend the proposal slate if the challenger surfaces a genuinely distinct option. Do not add variations of existing proposals.
|
|
42
|
+
|
|
43
|
+
### 5. Fan out to 3 voting agents
|
|
44
|
+
|
|
45
|
+
Use `subagent` in parallel mode with 3 tasks. Each voter gets identical proposals but a different evaluation preamble. The lenses are the contract; agent names are flexible — use `advisor`, `reviewer`, or whatever is available:
|
|
46
|
+
|
|
47
|
+
**Lens 1 — Pragmatic engineer:**
|
|
48
|
+
> "You value shipping speed and simplicity. You penalize over-engineering. A complex solution must justify its complexity with concrete failure modes the simple one can't handle."
|
|
49
|
+
|
|
50
|
+
**Lens 2 — Reliability engineer:**
|
|
51
|
+
> "You value robustness, crash recovery, and correctness over speed. You penalize approaches that work in happy paths but have edge-case blind spots. If two are equally correct, prefer the one that fails louder."
|
|
52
|
+
|
|
53
|
+
**Lens 3 — Maintainability reviewer:**
|
|
54
|
+
> "You value clean abstractions, testability, and long-term ownership cost. You penalize approaches that fight the framework or create implicit coupling. The best solution is one a new team member understands in 5 minutes."
|
|
55
|
+
|
|
56
|
+
Each voter must output a strict ranking:
|
|
57
|
+
```
|
|
58
|
+
1st: [Letter] — [reason]
|
|
59
|
+
2nd: [Letter] — [reason]
|
|
60
|
+
...
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Scale to 5 voters only for high-stakes decisions (new system boundaries, irreversible migrations, public API design).
|
|
64
|
+
|
|
65
|
+
### 6. Tally and present
|
|
66
|
+
|
|
67
|
+
**Scoring**: Borda count — 1st = N points, 2nd = N-1, ..., last = 1.
|
|
68
|
+
|
|
69
|
+
**Tiebreaker**: Most 1st-place votes. If still tied, present the split to the user honestly and let them decide — do NOT force a winner.
|
|
70
|
+
|
|
71
|
+
Present in this order:
|
|
72
|
+
1. **Problem framing** (one sentence)
|
|
73
|
+
2. **Proposals** (A–E with names)
|
|
74
|
+
3. **Vote table** with per-voter rankings and scores
|
|
75
|
+
4. **Winner** with consensus reasoning
|
|
76
|
+
5. **Dissent** — where voters disagreed and why
|
|
77
|
+
6. **Recommendation** — winner, plus any elements worth borrowing from runner-up
|
|
78
|
+
7. **Question to user** — proceed, combine, or reconsider?
|
|
79
|
+
|
|
80
|
+
### 7. Confirm with user
|
|
81
|
+
|
|
82
|
+
Do NOT auto-implement. Ask whether to proceed with the winner, combine elements, or reconsider.
|
|
83
|
+
|
|
84
|
+
## When NOT to use this
|
|
85
|
+
|
|
86
|
+
- Binary yes/no decisions — just discuss pros/cons
|
|
87
|
+
- Decisions with an obviously correct answer — just do it
|
|
88
|
+
- Pure preference decisions with no technical tradeoffs — ask the user
|
|
89
|
+
- Reversible decisions where trying the simplest option first is cheap
|