@open-product-primer/cli 0.12.0 → 0.14.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.
@@ -43,6 +43,7 @@ const fs = __importStar(require("fs"));
43
43
  const chalk_1 = __importDefault(require("chalk"));
44
44
  const detect_1 = require("../lib/detect");
45
45
  const measure_1 = require("../lib/measure");
46
+ const integrity_1 = require("../lib/integrity");
46
47
  const AGENT_DIRS = {
47
48
  claude: '.claude',
48
49
  cursor: '.cursor',
@@ -204,6 +205,10 @@ function doctorCommand() {
204
205
  });
205
206
  }
206
207
  }
208
+ // ── Sequence integrity checks ─────────────────────────────────────────────
209
+ (0, integrity_1.checkSequenceIntegrity)(projectRoot, checks);
210
+ // ── Skill version drift checks ────────────────────────────────────────────
211
+ (0, integrity_1.checkSkillVersionDrift)(projectRoot, checks);
207
212
  // ── Agent environment checks ──────────────────────────────────────────────
208
213
  const configAgents = (0, detect_1.readAgentsFromConfig)(projectRoot);
209
214
  if (configAgents !== null) {
@@ -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);
@@ -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(),
@@ -1127,3 +1153,6 @@ function codexInstructions() {
1127
1153
  function geminiInstructions() {
1128
1154
  return oprimWorkflowsInline();
1129
1155
  }
1156
+ function poolsideInstructions() {
1157
+ return oprimWorkflowsInline();
1158
+ }
@@ -0,0 +1,8 @@
1
+ export interface Check {
2
+ name: string;
3
+ pass: boolean;
4
+ note?: string;
5
+ required: boolean;
6
+ }
7
+ export declare function checkSequenceIntegrity(projectRoot: string, checks: Check[]): void;
8
+ export declare function checkSkillVersionDrift(projectRoot: string, checks: Check[]): void;
@@ -0,0 +1,115 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.checkSequenceIntegrity = checkSequenceIntegrity;
37
+ exports.checkSkillVersionDrift = checkSkillVersionDrift;
38
+ const path = __importStar(require("path"));
39
+ const fs = __importStar(require("fs"));
40
+ const yaml = __importStar(require("js-yaml"));
41
+ const install_agent_1 = require("./install-agent");
42
+ function checkSequenceIntegrity(projectRoot, checks) {
43
+ const sequencePath = path.join(projectRoot, 'oprim', 'sequence.yaml');
44
+ if (!fs.existsSync(sequencePath))
45
+ return;
46
+ let seq;
47
+ try {
48
+ seq = yaml.load(fs.readFileSync(sequencePath, 'utf-8'));
49
+ }
50
+ catch {
51
+ checks.push({
52
+ name: 'sequence: could not parse sequence.yaml',
53
+ pass: false,
54
+ note: 'Fix YAML syntax errors in oprim/sequence.yaml',
55
+ required: false,
56
+ });
57
+ return;
58
+ }
59
+ const lanes = [seq.now, seq.next, seq.later, seq.backlog].filter((l) => Array.isArray(l));
60
+ const allIds = new Set(lanes.flat().map((e) => e.id));
61
+ const wipLimit = seq.wip_limits?.now;
62
+ const nowCount = seq.now?.length ?? 0;
63
+ if (wipLimit !== undefined && nowCount > wipLimit) {
64
+ checks.push({
65
+ name: `sequence: WIP limit exceeded (${nowCount} active, limit ${wipLimit})`,
66
+ pass: false,
67
+ note: 'Move items out of now: to respect wip_limits.now',
68
+ required: false,
69
+ });
70
+ }
71
+ for (const lane of lanes) {
72
+ for (const entry of lane) {
73
+ for (const ref of entry.blocked_by ?? []) {
74
+ if (!allIds.has(ref)) {
75
+ checks.push({
76
+ name: `sequence: dangling blocked_by reference ${ref}`,
77
+ pass: false,
78
+ note: `${entry.id} references ${ref} which is not in the sequence`,
79
+ required: false,
80
+ });
81
+ }
82
+ }
83
+ for (const ref of entry.unlocks ?? []) {
84
+ if (!allIds.has(ref)) {
85
+ checks.push({
86
+ name: `sequence: dangling unlocks reference ${ref}`,
87
+ pass: false,
88
+ note: `${entry.id} references ${ref} which is not in the sequence`,
89
+ required: false,
90
+ });
91
+ }
92
+ }
93
+ }
94
+ }
95
+ }
96
+ function checkSkillVersionDrift(projectRoot, checks) {
97
+ const skillsDir = path.join(projectRoot, '.claude', 'skills');
98
+ if (!fs.existsSync(skillsDir))
99
+ return;
100
+ for (const [name, bundledContent] of Object.entries(install_agent_1.CLAUDE_SKILLS)) {
101
+ const skillPath = path.join(skillsDir, name, 'SKILL.md');
102
+ if (!fs.existsSync(skillPath))
103
+ continue;
104
+ const installed = fs.readFileSync(skillPath, 'utf-8');
105
+ const stripped = installed.replace('\n\n' + install_agent_1.OPRIM_CONTEXT_SKILL_STEP + '\n\n', '\n\n');
106
+ if (stripped !== bundledContent) {
107
+ checks.push({
108
+ name: `agent: Claude skill ${name} is out of date`,
109
+ pass: false,
110
+ note: "Run 'oprim update' to refresh",
111
+ required: false,
112
+ });
113
+ }
114
+ }
115
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-product-primer/cli",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "Open Product Primer CLI — product decisions, sequencing, and KPI tracking for repositories",
5
5
  "keywords": [
6
6
  "product",