@open-product-primer/cli 0.11.0 → 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/dist/cli.js +2 -0
- package/dist/commands/init.js +1 -1
- package/dist/commands/ovw.d.ts +2 -0
- package/dist/commands/ovw.js +263 -0
- package/dist/lib/detect.js +2 -0
- package/dist/lib/install-agent.d.ts +3 -1
- package/dist/lib/install-agent.js +50 -2
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -10,6 +10,7 @@ const update_1 = require("./commands/update");
|
|
|
10
10
|
const doctor_1 = require("./commands/doctor");
|
|
11
11
|
const migrate_1 = require("./commands/migrate");
|
|
12
12
|
const measure_1 = require("./commands/measure");
|
|
13
|
+
const ovw_1 = require("./commands/ovw");
|
|
13
14
|
const package_json_1 = __importDefault(require("../package.json"));
|
|
14
15
|
const program = new commander_1.Command();
|
|
15
16
|
program
|
|
@@ -21,4 +22,5 @@ program.addCommand((0, update_1.updateCommand)());
|
|
|
21
22
|
program.addCommand((0, doctor_1.doctorCommand)());
|
|
22
23
|
program.addCommand((0, migrate_1.migrateCommand)());
|
|
23
24
|
program.addCommand((0, measure_1.measureCommand)());
|
|
25
|
+
program.addCommand((0, ovw_1.ovwCommand)());
|
|
24
26
|
program.parse();
|
package/dist/commands/init.js
CHANGED
|
@@ -48,7 +48,7 @@ function initCommand() {
|
|
|
48
48
|
return new commander_1.Command('init')
|
|
49
49
|
.description('Initialize oprim in the current repository')
|
|
50
50
|
.option('--name <name>', 'project name (defaults to directory name)')
|
|
51
|
-
.option('--agent <name>', 'AI agent to install skills for (repeatable; supported: claude, cursor)', (val, prev) => [...prev, val], [])
|
|
51
|
+
.option('--agent <name>', 'AI agent to install skills for (repeatable; supported: claude, cursor, codex, gemini, poolside)', (val, prev) => [...prev, val], [])
|
|
52
52
|
.action(async (opts) => {
|
|
53
53
|
const projectRoot = process.cwd();
|
|
54
54
|
const projectName = opts.name ?? path.basename(projectRoot);
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.ovwCommand = ovwCommand;
|
|
40
|
+
const commander_1 = require("commander");
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const fs = __importStar(require("fs"));
|
|
43
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
44
|
+
const yaml = __importStar(require("js-yaml"));
|
|
45
|
+
function resolveBetDecisionPath(betsDir, betId) {
|
|
46
|
+
if (!fs.existsSync(betsDir))
|
|
47
|
+
return null;
|
|
48
|
+
const entries = fs.readdirSync(betsDir, { withFileTypes: true });
|
|
49
|
+
for (const entry of entries) {
|
|
50
|
+
if (entry.isDirectory() && entry.name.startsWith(betId)) {
|
|
51
|
+
const candidate = path.join(betsDir, entry.name, 'bet-decision.md');
|
|
52
|
+
if (fs.existsSync(candidate))
|
|
53
|
+
return candidate;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
function extractDoorType(content) {
|
|
59
|
+
if (/\[x\]\s*2-way door/i.test(content))
|
|
60
|
+
return '2-way';
|
|
61
|
+
if (/\[x\]\s*1-way door/i.test(content))
|
|
62
|
+
return '1-way';
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
function extractRiskLevel(content, label) {
|
|
66
|
+
const match = content.match(new RegExp(`\\*\\*${label}\\*\\*:\\s*(Low|Medium|High)`, 'i'));
|
|
67
|
+
if (!match)
|
|
68
|
+
return null;
|
|
69
|
+
const raw = match[1];
|
|
70
|
+
if (raw.toLowerCase() === 'low')
|
|
71
|
+
return 'Low';
|
|
72
|
+
if (raw.toLowerCase() === 'medium')
|
|
73
|
+
return 'Medium';
|
|
74
|
+
if (raw.toLowerCase() === 'high')
|
|
75
|
+
return 'High';
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
function extractRiskProfile(content) {
|
|
79
|
+
return {
|
|
80
|
+
value: extractRiskLevel(content, 'Value risk'),
|
|
81
|
+
usability: extractRiskLevel(content, 'Usability risk'),
|
|
82
|
+
feasibility: extractRiskLevel(content, 'Feasibility risk'),
|
|
83
|
+
viability: extractRiskLevel(content, 'Business viability risk'),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function loadBetMeta(betsDir, betId) {
|
|
87
|
+
const decisionPath = resolveBetDecisionPath(betsDir, betId);
|
|
88
|
+
if (!decisionPath)
|
|
89
|
+
return { doorType: null, risks: null };
|
|
90
|
+
try {
|
|
91
|
+
const content = fs.readFileSync(decisionPath, 'utf-8');
|
|
92
|
+
return {
|
|
93
|
+
doorType: extractDoorType(content),
|
|
94
|
+
risks: extractRiskProfile(content),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return { doorType: null, risks: null };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function abbreviate(level) {
|
|
102
|
+
if (level === 'Low')
|
|
103
|
+
return 'L';
|
|
104
|
+
if (level === 'Medium')
|
|
105
|
+
return 'M';
|
|
106
|
+
if (level === 'High')
|
|
107
|
+
return 'H';
|
|
108
|
+
return '?';
|
|
109
|
+
}
|
|
110
|
+
function renderInlineMeta(meta) {
|
|
111
|
+
if (!meta.doorType && !meta.risks)
|
|
112
|
+
return '[risk: unknown]';
|
|
113
|
+
const door = meta.doorType ?? '?-way';
|
|
114
|
+
if (!meta.risks)
|
|
115
|
+
return `[${door} | risk: unknown]`;
|
|
116
|
+
const { value, usability, feasibility, viability } = meta.risks;
|
|
117
|
+
return `[${door} | value:${abbreviate(value)} usability:${abbreviate(usability)} feasibility:${abbreviate(feasibility)} viability:${abbreviate(viability)}]`;
|
|
118
|
+
}
|
|
119
|
+
function renderBlockers(bet) {
|
|
120
|
+
if (!bet.blocked_by || bet.blocked_by.length === 0)
|
|
121
|
+
return '';
|
|
122
|
+
return ` [blocked by: ${bet.blocked_by.join(', ')}]`;
|
|
123
|
+
}
|
|
124
|
+
function renderLane(label, bets, betsDir, showMeta) {
|
|
125
|
+
console.log(chalk_1.default.bold.cyan(`\n── ${label.toUpperCase()} ──`));
|
|
126
|
+
if (bets.length === 0) {
|
|
127
|
+
console.log(' (empty)');
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
for (const bet of bets) {
|
|
131
|
+
if (showMeta) {
|
|
132
|
+
const meta = loadBetMeta(betsDir, bet.id);
|
|
133
|
+
const inline = renderInlineMeta(meta);
|
|
134
|
+
const blockers = renderBlockers(bet);
|
|
135
|
+
console.log(` ${bet.id} ${bet.title} ${inline}${blockers}`);
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
const blockers = renderBlockers(bet);
|
|
139
|
+
console.log(` ${bet.id} ${bet.title}${blockers}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function isElevated(level) {
|
|
144
|
+
return level === 'Medium' || level === 'High';
|
|
145
|
+
}
|
|
146
|
+
function buildAdvisory(board, betsDir) {
|
|
147
|
+
const nudges = [];
|
|
148
|
+
const now = board.now ?? [];
|
|
149
|
+
const next = board.next ?? [];
|
|
150
|
+
// Board-shape rules
|
|
151
|
+
if (now.length === 0) {
|
|
152
|
+
nudges.push('Now lane is empty — consider promoting a bet from next');
|
|
153
|
+
}
|
|
154
|
+
if (now.length > 3) {
|
|
155
|
+
nudges.push(`Focus risk: now lane has ${now.length} active bets — consider narrowing`);
|
|
156
|
+
}
|
|
157
|
+
for (const bet of now) {
|
|
158
|
+
const blockers = bet.blocked_by ?? [];
|
|
159
|
+
const inFlightIds = new Set([...now.map((b) => b.id), ...next.map((b) => b.id)]);
|
|
160
|
+
for (const blocker of blockers) {
|
|
161
|
+
if (!inFlightIds.has(blocker)) {
|
|
162
|
+
nudges.push(`${bet.id} may be stuck — its blocker ${blocker} is not in a flight lane`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// Door-type sequencing rules
|
|
167
|
+
const inFlightIds = new Set([...now.map((b) => b.id), ...next.map((b) => b.id)]);
|
|
168
|
+
const metaCache = new Map();
|
|
169
|
+
const getMeta = (betId) => {
|
|
170
|
+
if (!metaCache.has(betId))
|
|
171
|
+
metaCache.set(betId, loadBetMeta(betsDir, betId));
|
|
172
|
+
return metaCache.get(betId);
|
|
173
|
+
};
|
|
174
|
+
const nowMetas = now.map((b) => ({ bet: b, meta: getMeta(b.id) }));
|
|
175
|
+
const nextMetas = next.map((b) => ({ bet: b, meta: getMeta(b.id) }));
|
|
176
|
+
const inFlightMetas = [...nowMetas, ...nextMetas];
|
|
177
|
+
// 2-way bets in flight that unlock 1-way bets (used to check unrisker presence)
|
|
178
|
+
const twowayUnlocks = new Set();
|
|
179
|
+
for (const { bet, meta } of inFlightMetas) {
|
|
180
|
+
if (meta.doorType === '2-way') {
|
|
181
|
+
for (const unlockedId of bet.unlocks ?? [])
|
|
182
|
+
twowayUnlocks.add(unlockedId);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const allNow1Way = nowMetas.length > 0 && nowMetas.every(({ meta }) => meta.doorType === '1-way');
|
|
186
|
+
if (allNow1Way) {
|
|
187
|
+
nudges.push('Your now lane has no 2-way door bets — you are in full-commitment mode with no reversible fallback');
|
|
188
|
+
}
|
|
189
|
+
for (const { bet, meta } of nowMetas) {
|
|
190
|
+
if (meta.doorType === '1-way' && !twowayUnlocks.has(bet.id)) {
|
|
191
|
+
nudges.push(`${bet.id} is a 1-way door with no 2-way door unrisker in flight — consider sequencing a reversible spike first`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// 2-way door blocked by a 1-way door (inverted order)
|
|
195
|
+
for (const { bet, meta } of inFlightMetas) {
|
|
196
|
+
if (meta.doorType === '2-way') {
|
|
197
|
+
for (const blockerId of bet.blocked_by ?? []) {
|
|
198
|
+
const blockerMeta = getMeta(blockerId);
|
|
199
|
+
if (blockerMeta.doorType === '1-way') {
|
|
200
|
+
nudges.push(`${bet.id} (2-way) is blocked by ${blockerId} (1-way) — order may be inverted; 2-way doors should precede 1-way doors`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// Risk advisory rules for now lane
|
|
206
|
+
for (const { bet, meta } of nowMetas) {
|
|
207
|
+
if (!meta.risks)
|
|
208
|
+
continue;
|
|
209
|
+
const { value, usability, feasibility, viability } = meta.risks;
|
|
210
|
+
if (isElevated(value) && meta.doorType === '1-way') {
|
|
211
|
+
nudges.push(`${bet.id}: high commitment with unvalidated value — consider a 2-way door discovery bet first`);
|
|
212
|
+
}
|
|
213
|
+
if (isElevated(feasibility)) {
|
|
214
|
+
nudges.push(`${bet.id}: feasibility risk is elevated — ensure a spike or prototype is in plan before full build`);
|
|
215
|
+
}
|
|
216
|
+
if (isElevated(usability)) {
|
|
217
|
+
nudges.push(`${bet.id}: usability risk is elevated — plan for user testing before shipping`);
|
|
218
|
+
}
|
|
219
|
+
if (isElevated(viability)) {
|
|
220
|
+
nudges.push(`${bet.id}: business viability risk is elevated — align with stakeholders before committing`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return nudges;
|
|
224
|
+
}
|
|
225
|
+
function ovwCommand() {
|
|
226
|
+
return new commander_1.Command('ovw')
|
|
227
|
+
.description('Show the sequencing board with door type, risk profile, and advisory guidance')
|
|
228
|
+
.action(() => {
|
|
229
|
+
const projectRoot = process.cwd();
|
|
230
|
+
const sequencePath = path.join(projectRoot, 'oprim', 'sequence.yaml');
|
|
231
|
+
const betsDir = path.join(projectRoot, 'oprim', 'bets');
|
|
232
|
+
if (!fs.existsSync(sequencePath)) {
|
|
233
|
+
console.error("No oprim/sequence.yaml found — run 'oprim init' first");
|
|
234
|
+
process.exit(1);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
let board;
|
|
238
|
+
try {
|
|
239
|
+
board = yaml.load(fs.readFileSync(sequencePath, 'utf-8')) ?? {};
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
console.error('Failed to parse oprim/sequence.yaml');
|
|
243
|
+
process.exit(1);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const lanes = [
|
|
247
|
+
{ key: 'now', label: 'now', showMeta: true },
|
|
248
|
+
{ key: 'next', label: 'next', showMeta: true },
|
|
249
|
+
{ key: 'later', label: 'later', showMeta: false },
|
|
250
|
+
{ key: 'backlog', label: 'backlog', showMeta: false },
|
|
251
|
+
];
|
|
252
|
+
for (const { key, label, showMeta } of lanes) {
|
|
253
|
+
renderLane(label, board[key] ?? [], betsDir, showMeta);
|
|
254
|
+
}
|
|
255
|
+
const nudges = buildAdvisory(board, betsDir);
|
|
256
|
+
if (nudges.length > 0) {
|
|
257
|
+
console.log(chalk_1.default.bold.yellow('\n── ADVISORY ──'));
|
|
258
|
+
for (const nudge of nudges) {
|
|
259
|
+
console.log(` • ${nudge}`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
}
|
package/dist/lib/detect.js
CHANGED
|
@@ -72,6 +72,8 @@ function detectAvailableAgents(projectRoot) {
|
|
|
72
72
|
detected.push('codex');
|
|
73
73
|
if (fs.existsSync(path.join(projectRoot, 'GEMINI.md')))
|
|
74
74
|
detected.push('gemini');
|
|
75
|
+
if (fs.existsSync(path.join(projectRoot, '.poolside')))
|
|
76
|
+
detected.push('poolside');
|
|
75
77
|
return detected;
|
|
76
78
|
}
|
|
77
79
|
function writeAgentsToConfig(agents, projectRoot) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type Agent = 'claude' | 'cursor' | 'codex' | 'gemini';
|
|
1
|
+
export type Agent = 'claude' | 'cursor' | 'codex' | 'gemini' | 'poolside';
|
|
2
2
|
export declare const SUPPORTED_AGENTS: readonly Agent[];
|
|
3
3
|
export declare function promptFrameworkSelection(projectRoot: string): Promise<string>;
|
|
4
4
|
export declare function promptAgentSelection(projectRoot: string): Promise<string[]>;
|
|
@@ -7,8 +7,10 @@ export declare function installAgentSkills(agent: Agent, projectRoot: string, fr
|
|
|
7
7
|
export declare const OPRIM_CONTEXT_SKILL_STEP = "## Step 0: Check relevant product decisions\nInvoke the `oprim:context` skill using the Skill tool. If matching PDRs are surfaced, review them before proceeding. If no PDRs match or `oprim/decisions/` is empty, the skill exits silently \u2014 continue to Step 1 immediately.";
|
|
8
8
|
export declare const CLAUDE_SKILLS: Record<string, string>;
|
|
9
9
|
export declare const CLAUDE_COMMANDS: Record<string, string>;
|
|
10
|
+
export declare const POOLSIDE_SKILLS: Record<string, string>;
|
|
10
11
|
export declare const CURSOR_SKILLS: Record<string, string>;
|
|
11
12
|
export declare const CURSOR_COMMANDS: Record<string, string>;
|
|
12
13
|
export declare function writeAgentInstructionFile(filePath: string, section: string): void;
|
|
13
14
|
export declare function codexInstructions(): string;
|
|
14
15
|
export declare function geminiInstructions(): string;
|
|
16
|
+
export declare function poolsideInstructions(): string;
|
|
@@ -36,7 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.CURSOR_COMMANDS = exports.CURSOR_SKILLS = exports.CLAUDE_COMMANDS = exports.CLAUDE_SKILLS = exports.OPRIM_CONTEXT_SKILL_STEP = exports.SUPPORTED_AGENTS = void 0;
|
|
39
|
+
exports.CURSOR_COMMANDS = exports.CURSOR_SKILLS = exports.POOLSIDE_SKILLS = exports.CLAUDE_COMMANDS = exports.CLAUDE_SKILLS = exports.OPRIM_CONTEXT_SKILL_STEP = exports.SUPPORTED_AGENTS = void 0;
|
|
40
40
|
exports.promptFrameworkSelection = promptFrameworkSelection;
|
|
41
41
|
exports.promptAgentSelection = promptAgentSelection;
|
|
42
42
|
exports.promptPdrSurfacing = promptPdrSurfacing;
|
|
@@ -44,12 +44,13 @@ exports.installAgentSkills = installAgentSkills;
|
|
|
44
44
|
exports.writeAgentInstructionFile = writeAgentInstructionFile;
|
|
45
45
|
exports.codexInstructions = codexInstructions;
|
|
46
46
|
exports.geminiInstructions = geminiInstructions;
|
|
47
|
+
exports.poolsideInstructions = poolsideInstructions;
|
|
47
48
|
const path = __importStar(require("path"));
|
|
48
49
|
const fs = __importStar(require("fs"));
|
|
49
50
|
const chalk_1 = __importDefault(require("chalk"));
|
|
50
51
|
const scaffold_1 = require("./scaffold");
|
|
51
52
|
const detect_1 = require("./detect");
|
|
52
|
-
exports.SUPPORTED_AGENTS = ['claude', 'cursor', 'codex', 'gemini'];
|
|
53
|
+
exports.SUPPORTED_AGENTS = ['claude', 'cursor', 'codex', 'gemini', 'poolside'];
|
|
53
54
|
async function promptFrameworkSelection(projectRoot) {
|
|
54
55
|
const configPath = path.join(projectRoot, '.claude', 'hooks', 'config.json');
|
|
55
56
|
if (fs.existsSync(configPath)) {
|
|
@@ -87,6 +88,7 @@ async function promptAgentSelection(projectRoot) {
|
|
|
87
88
|
{ name: 'Cursor', value: 'cursor', checked: detected.includes('cursor') },
|
|
88
89
|
{ name: 'Codex', value: 'codex', checked: detected.includes('codex') },
|
|
89
90
|
{ name: 'Gemini CLI', value: 'gemini', checked: detected.includes('gemini') },
|
|
91
|
+
{ name: 'Poolside', value: 'poolside', checked: detected.includes('poolside') },
|
|
90
92
|
],
|
|
91
93
|
});
|
|
92
94
|
}
|
|
@@ -170,6 +172,21 @@ function installAgentSkills(agent, projectRoot, framework = 'openspec', pdrSurfa
|
|
|
170
172
|
console.log(chalk_1.default.dim(' .claude/ created — Claude Code will discover these files automatically.'));
|
|
171
173
|
}
|
|
172
174
|
}
|
|
175
|
+
else if (agent === 'poolside') {
|
|
176
|
+
const poolsideDir = path.join(projectRoot, '.poolside');
|
|
177
|
+
const dirCreated = !fs.existsSync(poolsideDir);
|
|
178
|
+
const skillsBase = path.join(poolsideDir, 'skills');
|
|
179
|
+
for (const [name, content] of Object.entries(exports.POOLSIDE_SKILLS)) {
|
|
180
|
+
(0, scaffold_1.writeFile)(path.join(skillsBase, name, 'SKILL.md'), content);
|
|
181
|
+
console.log(chalk_1.default.green('✓') + ` .poolside/skills/${name}/SKILL.md`);
|
|
182
|
+
}
|
|
183
|
+
const agentsFile = path.join(projectRoot, 'AGENTS.md');
|
|
184
|
+
writeAgentInstructionFile(agentsFile, poolsideInstructions());
|
|
185
|
+
console.log(chalk_1.default.green('✓') + ' AGENTS.md (oprim section written)');
|
|
186
|
+
if (dirCreated) {
|
|
187
|
+
console.log(chalk_1.default.dim(' .poolside/ created — Poolside will discover these files automatically.'));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
173
190
|
else if (agent === 'codex') {
|
|
174
191
|
const agentsFile = path.join(projectRoot, 'AGENTS.md');
|
|
175
192
|
writeAgentInstructionFile(agentsFile, codexInstructions());
|
|
@@ -280,6 +297,15 @@ exports.CLAUDE_COMMANDS = {
|
|
|
280
297
|
'sequence.md': claudeWrapper('OPRIM: Sequence', 'Validate and update the primer sequencing board', sequenceContent()),
|
|
281
298
|
'archive.md': claudeWrapper('OPRIM: Archive', 'Archive a completed bet — move it out of the active board', archiveCommandContent()),
|
|
282
299
|
};
|
|
300
|
+
// ─── Poolside skill playbooks ─────────────────────────────────────────────────
|
|
301
|
+
exports.POOLSIDE_SKILLS = {
|
|
302
|
+
'oprim-pdr': pdrSkill(),
|
|
303
|
+
'oprim-bet': betSkill(),
|
|
304
|
+
'oprim-criteria': criteriaSkill(),
|
|
305
|
+
'oprim-review': reviewSkill(),
|
|
306
|
+
'oprim-archive': archiveSkill(),
|
|
307
|
+
'oprim-sequence': oprimSequenceSkill(),
|
|
308
|
+
};
|
|
283
309
|
// ─── Cursor skill playbooks ───────────────────────────────────────────────────
|
|
284
310
|
exports.CURSOR_SKILLS = {
|
|
285
311
|
'oprim-pdr': pdrSkill(),
|
|
@@ -426,6 +452,15 @@ If \`oprim/sequence.yaml\` not found: report and stop — advise \`oprim init\`.
|
|
|
426
452
|
### 4. Gather content
|
|
427
453
|
Ask: Decision (Build now / Defer / Kill, default Build now), Owner, Review date (YYYY-MM-DD), Why now, Alternatives considered, Expected outcomes (metric: baseline → target in timeframe), Kill criteria / rollback trigger, PDR links (optional).
|
|
428
454
|
|
|
455
|
+
Then ask about reversibility:
|
|
456
|
+
- "Is this a **2-way door** (reversible — easy to undo, safe to try) or a **1-way door** (hard to reverse — requires high confidence)?"
|
|
457
|
+
|
|
458
|
+
Then ask about each of the four risk dimensions (Low / Medium / High + short rationale):
|
|
459
|
+
- "**Value risk**: Will users/customers actually use or buy this? (Low / Medium / High — and why?)"
|
|
460
|
+
- "**Usability risk**: Can users figure out how to use it without help? (Low / Medium / High — and why?)"
|
|
461
|
+
- "**Feasibility risk**: Can we build this with our current skills, time, and technology? (Low / Medium / High — and why?)"
|
|
462
|
+
- "**Business viability risk**: Does this solution work for the business (revenue, legal, ops)? (Low / Medium / High — and why?)"
|
|
463
|
+
|
|
429
464
|
### 5. Write oprim/bets/BET-NNN-<slug>/bet-decision.md
|
|
430
465
|
\`\`\`
|
|
431
466
|
# Decision: BET-NNN <title>
|
|
@@ -437,6 +472,16 @@ Ask: Decision (Build now / Defer / Kill, default Build now), Owner, Review date
|
|
|
437
472
|
- Owner: <owner>
|
|
438
473
|
- Review date: <review date>
|
|
439
474
|
|
|
475
|
+
## Door type
|
|
476
|
+
- [<x if 2-way>] 2-way door (reversible — safe to try, easy to undo)
|
|
477
|
+
- [<x if 1-way>] 1-way door (hard to reverse — requires higher confidence before committing)
|
|
478
|
+
|
|
479
|
+
## Risk profile
|
|
480
|
+
- **Value risk**: <Low / Medium / High> — <rationale>
|
|
481
|
+
- **Usability risk**: <Low / Medium / High> — <rationale>
|
|
482
|
+
- **Feasibility risk**: <Low / Medium / High> — <rationale>
|
|
483
|
+
- **Business viability risk**: <Low / Medium / High> — <rationale>
|
|
484
|
+
|
|
440
485
|
## Why now
|
|
441
486
|
<why-now as bullet list>
|
|
442
487
|
|
|
@@ -1108,3 +1153,6 @@ function codexInstructions() {
|
|
|
1108
1153
|
function geminiInstructions() {
|
|
1109
1154
|
return oprimWorkflowsInline();
|
|
1110
1155
|
}
|
|
1156
|
+
function poolsideInstructions() {
|
|
1157
|
+
return oprimWorkflowsInline();
|
|
1158
|
+
}
|