@open-product-primer/cli 0.4.1 → 0.7.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.
@@ -189,6 +189,8 @@ function doctorCommand() {
189
189
  for (const entry of betEntries) {
190
190
  if (!entry.isDirectory())
191
191
  continue;
192
+ if (entry.name === 'archived')
193
+ continue;
192
194
  const betDir = path.join(betsDir, entry.name);
193
195
  const hasDecision = fs.existsSync(path.join(betDir, 'bet-decision.md'));
194
196
  if (!hasDecision)
@@ -113,12 +113,14 @@ function initCommand() {
113
113
  }
114
114
  else {
115
115
  let specFramework = 'openspec';
116
+ let pdrSurfacing = false;
116
117
  if (selectedAgents.includes('claude')) {
117
118
  specFramework = await (0, install_agent_1.promptFrameworkSelection)(projectRoot);
119
+ pdrSurfacing = await (0, install_agent_1.promptPdrSurfacing)();
118
120
  }
119
121
  console.log('\n' + chalk_1.default.bold('Installing agent skills...'));
120
122
  for (const agent of selectedAgents) {
121
- (0, install_agent_1.installAgentSkills)(agent, projectRoot, specFramework);
123
+ (0, install_agent_1.installAgentSkills)(agent, projectRoot, specFramework, pdrSurfacing);
122
124
  }
123
125
  console.log('\n' + chalk_1.default.green('✓') + ` Agent skills installed: ${selectedAgents.join(', ')}`);
124
126
  }
@@ -51,11 +51,13 @@ function updateCommand() {
51
51
  const configAgents = (0, detect_1.readAgentsFromConfig)(projectRoot);
52
52
  if (configAgents !== null && configAgents.length > 0) {
53
53
  let specFramework = 'openspec';
54
+ let pdrSurfacing = false;
54
55
  if (configAgents.includes('claude')) {
55
56
  specFramework = await (0, install_agent_1.promptFrameworkSelection)(projectRoot);
57
+ pdrSurfacing = await (0, install_agent_1.promptPdrSurfacing)();
56
58
  }
57
59
  for (const agent of configAgents) {
58
- (0, install_agent_1.installAgentSkills)(agent, projectRoot, specFramework);
60
+ (0, install_agent_1.installAgentSkills)(agent, projectRoot, specFramework, pdrSurfacing);
59
61
  }
60
62
  console.log(`\nAgent skills updated: ${configAgents.join(', ')}`);
61
63
  }
@@ -64,7 +66,8 @@ function updateCommand() {
64
66
  const legacyAgents = [];
65
67
  if (fs.existsSync(path.join(projectRoot, '.claude'))) {
66
68
  const specFramework = await (0, install_agent_1.promptFrameworkSelection)(projectRoot);
67
- (0, install_agent_1.installAgentSkills)('claude', projectRoot, specFramework);
69
+ const pdrSurfacing = await (0, install_agent_1.promptPdrSurfacing)();
70
+ (0, install_agent_1.installAgentSkills)('claude', projectRoot, specFramework, pdrSurfacing);
68
71
  legacyAgents.push('claude');
69
72
  }
70
73
  if (fs.existsSync(path.join(projectRoot, '.cursor'))) {
@@ -98,12 +101,14 @@ function updateCommand() {
98
101
  return;
99
102
  }
100
103
  let addSpecFramework = 'openspec';
104
+ let addPdrSurfacing = false;
101
105
  if (selected.includes('claude')) {
102
106
  addSpecFramework = await (0, install_agent_1.promptFrameworkSelection)(projectRoot);
107
+ addPdrSurfacing = await (0, install_agent_1.promptPdrSurfacing)();
103
108
  }
104
109
  console.log('\n' + chalk_1.default.bold('Installing agent skills...'));
105
110
  for (const agent of selected) {
106
- (0, install_agent_1.installAgentSkills)(agent, projectRoot, addSpecFramework);
111
+ (0, install_agent_1.installAgentSkills)(agent, projectRoot, addSpecFramework, addPdrSurfacing);
107
112
  }
108
113
  const merged = Array.from(new Set([...currentAgents, ...selected]));
109
114
  (0, detect_1.writeAgentsToConfig)(merged, projectRoot);
@@ -68,6 +68,10 @@ function detectAvailableAgents(projectRoot) {
68
68
  detected.push('claude');
69
69
  if (fs.existsSync(path.join(projectRoot, '.cursor')))
70
70
  detected.push('cursor');
71
+ if (fs.existsSync(path.join(projectRoot, 'AGENTS.md')))
72
+ detected.push('codex');
73
+ if (fs.existsSync(path.join(projectRoot, 'GEMINI.md')))
74
+ detected.push('gemini');
71
75
  return detected;
72
76
  }
73
77
  function writeAgentsToConfig(agents, projectRoot) {
@@ -1,9 +1,14 @@
1
- export type Agent = 'claude' | 'cursor';
1
+ export type Agent = 'claude' | 'cursor' | 'codex' | 'gemini';
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[]>;
5
- export declare function installAgentSkills(agent: Agent, projectRoot: string, framework?: string): void;
5
+ export declare function promptPdrSurfacing(): Promise<boolean>;
6
+ export declare function installAgentSkills(agent: Agent, projectRoot: string, framework?: string, pdrSurfacing?: boolean): void;
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.";
6
8
  export declare const CLAUDE_SKILLS: Record<string, string>;
7
9
  export declare const CLAUDE_COMMANDS: Record<string, string>;
8
10
  export declare const CURSOR_SKILLS: Record<string, string>;
9
11
  export declare const CURSOR_COMMANDS: Record<string, string>;
12
+ export declare function writeAgentInstructionFile(filePath: string, section: string): void;
13
+ export declare function codexInstructions(): string;
14
+ export declare function geminiInstructions(): string;
@@ -36,16 +36,20 @@ 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.SUPPORTED_AGENTS = void 0;
39
+ exports.CURSOR_COMMANDS = exports.CURSOR_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
+ exports.promptPdrSurfacing = promptPdrSurfacing;
42
43
  exports.installAgentSkills = installAgentSkills;
44
+ exports.writeAgentInstructionFile = writeAgentInstructionFile;
45
+ exports.codexInstructions = codexInstructions;
46
+ exports.geminiInstructions = geminiInstructions;
43
47
  const path = __importStar(require("path"));
44
48
  const fs = __importStar(require("fs"));
45
49
  const chalk_1 = __importDefault(require("chalk"));
46
50
  const scaffold_1 = require("./scaffold");
47
51
  const detect_1 = require("./detect");
48
- exports.SUPPORTED_AGENTS = ['claude', 'cursor'];
52
+ exports.SUPPORTED_AGENTS = ['claude', 'cursor', 'codex', 'gemini'];
49
53
  async function promptFrameworkSelection(projectRoot) {
50
54
  const configPath = path.join(projectRoot, '.claude', 'hooks', 'config.json');
51
55
  if (fs.existsSync(configPath)) {
@@ -81,18 +85,53 @@ async function promptAgentSelection(projectRoot) {
81
85
  choices: [
82
86
  { name: 'Claude Code', value: 'claude', checked: detected.includes('claude') },
83
87
  { name: 'Cursor', value: 'cursor', checked: detected.includes('cursor') },
88
+ { name: 'Codex', value: 'codex', checked: detected.includes('codex') },
89
+ { name: 'Gemini CLI', value: 'gemini', checked: detected.includes('gemini') },
84
90
  ],
85
91
  });
86
92
  }
87
- function installAgentSkills(agent, projectRoot, framework = 'openspec') {
93
+ async function promptPdrSurfacing() {
94
+ const { confirm } = await Promise.resolve().then(() => __importStar(require('@inquirer/prompts')));
95
+ return confirm({ message: 'Enable proactive PDR surfacing in skills? (y/N)', default: false });
96
+ }
97
+ function installAgentSkills(agent, projectRoot, framework = 'openspec', pdrSurfacing = false) {
88
98
  if (agent === 'claude') {
89
99
  const claudeDir = path.join(projectRoot, '.claude');
90
100
  const dirCreated = !fs.existsSync(claudeDir);
91
101
  const skillsBase = path.join(claudeDir, 'skills');
102
+ // oprim:context skill — install when opted in, remove when opted out
103
+ const contextSkillPath = path.join(skillsBase, 'oprim:context', 'SKILL.md');
104
+ if (pdrSurfacing) {
105
+ (0, scaffold_1.writeFile)(contextSkillPath, oprimContextSkill());
106
+ console.log(chalk_1.default.green('✓') + ' .claude/skills/oprim:context/SKILL.md');
107
+ }
108
+ else if (fs.existsSync(contextSkillPath)) {
109
+ fs.unlinkSync(contextSkillPath);
110
+ try {
111
+ fs.rmdirSync(path.dirname(contextSkillPath));
112
+ }
113
+ catch { /* not empty or already gone */ }
114
+ console.log(chalk_1.default.dim(' removed .claude/skills/oprim:context/SKILL.md'));
115
+ }
116
+ // oprim skills — prepend Step 0 when opted in
92
117
  for (const [name, content] of Object.entries(exports.CLAUDE_SKILLS)) {
93
- (0, scaffold_1.writeFile)(path.join(skillsBase, name, 'SKILL.md'), content);
118
+ const skillContent = pdrSurfacing ? withContextStep(content) : content;
119
+ (0, scaffold_1.writeFile)(path.join(skillsBase, name, 'SKILL.md'), skillContent);
94
120
  console.log(chalk_1.default.green('✓') + ` .claude/skills/${name}/SKILL.md`);
95
121
  }
122
+ // openspec skills — add/remove Step 0 in-place when they exist
123
+ for (const name of OPENSPEC_SKILL_NAMES) {
124
+ const skillFilePath = path.join(skillsBase, name, 'SKILL.md');
125
+ if (fs.existsSync(skillFilePath)) {
126
+ const current = fs.readFileSync(skillFilePath, 'utf-8');
127
+ const updated = pdrSurfacing ? addContextStepToFile(current) : removeContextStepFromFile(current);
128
+ if (updated !== current) {
129
+ fs.writeFileSync(skillFilePath, updated, 'utf-8');
130
+ console.log(chalk_1.default.green('✓') +
131
+ ` .claude/skills/${name}/SKILL.md (PDR surfacing ${pdrSurfacing ? 'enabled' : 'disabled'})`);
132
+ }
133
+ }
134
+ }
96
135
  const cmdsDir = path.join(claudeDir, 'commands', 'oprim');
97
136
  for (const [filename, content] of Object.entries(exports.CLAUDE_COMMANDS)) {
98
137
  (0, scaffold_1.writeFile)(path.join(cmdsDir, filename), content);
@@ -131,6 +170,16 @@ function installAgentSkills(agent, projectRoot, framework = 'openspec') {
131
170
  console.log(chalk_1.default.dim(' .claude/ created — Claude Code will discover these files automatically.'));
132
171
  }
133
172
  }
173
+ else if (agent === 'codex') {
174
+ const agentsFile = path.join(projectRoot, 'AGENTS.md');
175
+ writeAgentInstructionFile(agentsFile, codexInstructions());
176
+ console.log(chalk_1.default.green('✓') + ' AGENTS.md (oprim section written)');
177
+ }
178
+ else if (agent === 'gemini') {
179
+ const geminiFile = path.join(projectRoot, 'GEMINI.md');
180
+ writeAgentInstructionFile(geminiFile, geminiInstructions());
181
+ console.log(chalk_1.default.green('✓') + ' GEMINI.md (oprim section written)');
182
+ }
134
183
  else if (agent === 'cursor') {
135
184
  const cursorDir = path.join(projectRoot, '.cursor');
136
185
  const dirCreated = !fs.existsSync(cursorDir);
@@ -149,6 +198,73 @@ function installAgentSkills(agent, projectRoot, framework = 'openspec') {
149
198
  }
150
199
  }
151
200
  }
201
+ // ─── PDR context surfacing ────────────────────────────────────────────────────
202
+ const CTX_STEP_START = '<!-- oprim:context:start -->';
203
+ const CTX_STEP_END = '<!-- oprim:context:end -->';
204
+ exports.OPRIM_CONTEXT_SKILL_STEP = `## Step 0: Check relevant product decisions
205
+ Invoke 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 — continue to Step 1 immediately.`;
206
+ // Openspec skill names whose files are managed by the openspec CLI and modified in-place by oprim
207
+ const OPENSPEC_SKILL_NAMES = [
208
+ 'openspec-propose',
209
+ 'openspec-apply-change',
210
+ 'openspec-explore',
211
+ 'openspec-archive-change',
212
+ ];
213
+ // Insert Step 0 into an oprim skill string (written fresh each time — no markers needed)
214
+ function withContextStep(content) {
215
+ const lines = content.split('\n');
216
+ let closingDash = -1;
217
+ let dashCount = 0;
218
+ for (let i = 0; i < lines.length; i++) {
219
+ if (lines[i].trim() === '---') {
220
+ dashCount++;
221
+ if (dashCount === 2) {
222
+ closingDash = i;
223
+ break;
224
+ }
225
+ }
226
+ }
227
+ if (closingDash === -1)
228
+ return exports.OPRIM_CONTEXT_SKILL_STEP + '\n\n' + content;
229
+ const front = lines.slice(0, closingDash + 1).join('\n');
230
+ const body = lines.slice(closingDash + 1).join('\n').trimStart();
231
+ return `${front}\n\n${exports.OPRIM_CONTEXT_SKILL_STEP}\n\n${body}`;
232
+ }
233
+ // Idempotently add Step 0 to an openspec skill file (read-modify-write)
234
+ function addContextStepToFile(content) {
235
+ if (content.includes(CTX_STEP_START))
236
+ return content; // already present
237
+ const lines = content.split('\n');
238
+ let closingDash = -1;
239
+ let dashCount = 0;
240
+ for (let i = 0; i < lines.length; i++) {
241
+ if (lines[i].trim() === '---') {
242
+ dashCount++;
243
+ if (dashCount === 2) {
244
+ closingDash = i;
245
+ break;
246
+ }
247
+ }
248
+ }
249
+ const block = `${CTX_STEP_START}\n${exports.OPRIM_CONTEXT_SKILL_STEP}\n${CTX_STEP_END}`;
250
+ if (closingDash === -1)
251
+ return block + '\n\n' + content;
252
+ const front = lines.slice(0, closingDash + 1).join('\n');
253
+ const body = lines.slice(closingDash + 1).join('\n').trimStart();
254
+ return `${front}\n\n${block}\n\n${body}`;
255
+ }
256
+ // Remove Step 0 block from an openspec skill file (read-modify-write)
257
+ function removeContextStepFromFile(content) {
258
+ const s = content.indexOf(CTX_STEP_START);
259
+ if (s === -1)
260
+ return content;
261
+ const e = content.indexOf(CTX_STEP_END, s);
262
+ if (e === -1)
263
+ return content;
264
+ const before = content.slice(0, s).trimEnd();
265
+ const after = content.slice(e + CTX_STEP_END.length).replace(/^\n+/, '\n');
266
+ return before + after;
267
+ }
152
268
  // ─── Claude skill playbooks ───────────────────────────────────────────────────
153
269
  exports.CLAUDE_SKILLS = {
154
270
  'oprim-pdr': pdrSkill(),
@@ -471,6 +587,36 @@ The bet is preserved in full at the archive location.
471
587
  \`\`\`
472
588
  `;
473
589
  }
590
+ function oprimContextSkill() {
591
+ return `---
592
+ name: oprim:context
593
+ description: Surface relevant product decisions from oprim/decisions/ by keyword-matching against the current conversation — invoke at the start of any oprim or openspec workflow when PDR surfacing is enabled
594
+ ---
595
+
596
+ Scan \`oprim/decisions/\` and surface PDRs that match keywords from the current conversation.
597
+
598
+ ## Steps
599
+
600
+ ### 1. Check for decisions
601
+ Scan \`oprim/decisions/\` for files matching \`PDR-*.md\`. If the directory is empty or contains no PDR files, exit silently — produce no output and return immediately.
602
+
603
+ ### 2. Extract keywords
604
+ From the current conversation context, extract 3–10 topic keywords: bet IDs referenced (e.g. \`BET-007\`), capability names, filenames mentioned, subject-area nouns. Focus on the most specific and distinctive terms.
605
+
606
+ ### 3. Match PDRs
607
+ For each PDR file: read the filename and the first 25 lines (to capture title, status, and context). A PDR is relevant if any keyword appears in the filename, title (\`# PDR-NNN: ...\`), or body text (case-insensitive).
608
+
609
+ ### 4. Report or exit silently
610
+ If one or more PDRs match:
611
+
612
+ **Relevant product decisions:**
613
+ - PDR-NNN: <title> — <Status> (\`oprim/decisions/PDR-NNN-<slug>.md\`)
614
+
615
+ List each match on its own line, then return — the invoking skill continues to its next step.
616
+
617
+ If no PDRs match: exit silently — produce no output.
618
+ `;
619
+ }
474
620
  function archiveCommandContent() {
475
621
  return `Use the Skill tool to invoke the \`oprim-archive\` skill.`;
476
622
  }
@@ -707,3 +853,92 @@ Validate the primer sequencing board and suggest rebalancing if needed.
707
853
  6. **Suggest moves** — recommend bets to defer to \`next\` or \`later\` to resolve violations
708
854
  `;
709
855
  }
856
+ // ─── Instruction-file helpers (Codex / Gemini CLI) ────────────────────────────
857
+ const OPRIM_START = '<!-- oprim:start -->';
858
+ const OPRIM_END = '<!-- oprim:end -->';
859
+ function writeAgentInstructionFile(filePath, section) {
860
+ const delimited = `${OPRIM_START}\n${section}\n${OPRIM_END}`;
861
+ if (!fs.existsSync(filePath)) {
862
+ fs.writeFileSync(filePath, delimited + '\n', 'utf-8');
863
+ return;
864
+ }
865
+ const existing = fs.readFileSync(filePath, 'utf-8');
866
+ const startIdx = existing.indexOf(OPRIM_START);
867
+ const endIdx = existing.indexOf(OPRIM_END);
868
+ if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
869
+ const before = existing.slice(0, startIdx);
870
+ const after = existing.slice(endIdx + OPRIM_END.length);
871
+ fs.writeFileSync(filePath, before + delimited + after, 'utf-8');
872
+ }
873
+ else {
874
+ const separator = existing.endsWith('\n') ? '\n' : '\n\n';
875
+ fs.writeFileSync(filePath, existing + separator + delimited + '\n', 'utf-8');
876
+ }
877
+ }
878
+ function oprimWorkflowsInline() {
879
+ return `
880
+ ## oprim workflows
881
+
882
+ ### Bet authoring (oprim-bet)
883
+ Create a new bet in \`oprim/bets/\` and register it on the sequencing board.
884
+
885
+ 1. Show naming tip: "verb + object [for context] — e.g. 'Improve bet naming for scannability'"
886
+ 2. Ask for the bet title. Validate: fewer than 4 words OR fewer than 25 chars → warn, suggest reformulation, ask "Proceed anyway? (y/N)".
887
+ 3. Assign next BET ID: scan \`oprim/bets/BET-(\\d+)\` dirs, max+1 zero-padded to 3 digits (default 001).
888
+ 4. Check \`oprim/sequence.yaml\` exists — stop if not, advise \`oprim init\`.
889
+ 5. Gather: decision (default Build now), owner, review date (YYYY-MM-DD), why now, alternatives, expected outcomes, kill criteria, PDR links.
890
+ 6. Write \`oprim/bets/BET-NNN/bet-decision.md\` with all fields.
891
+ 7. Append to \`oprim/sequence.yaml\` backlog: \`{id, title, blocked_by: [], unlocks: [], requires_pdrs: []}\`.
892
+ 8. Ask: "Scaffold a discovery.md now? (y/N)" — if "y", write \`oprim/bets/BET-NNN/discovery.md\`.
893
+ 9. Report what was created.
894
+
895
+ ### Criteria authoring (oprim-criteria)
896
+ Create or append to \`oprim/bets/BET-NNN/criteria.yaml\`.
897
+
898
+ 1. Ask which bet (e.g. BET-042). Verify dir exists.
899
+ 2. Gather: metric ID (snake_case), name, baseline, target, timeframe, launch date, segment.
900
+ 3. Ask source type (amplitude / bigquery).
901
+ - Amplitude: event, aggregation (unique_users/event_count/property_sum), denominator_event.
902
+ - BigQuery: table, metric_column, filter, aggregation, denominator_query.
903
+ 4. If file exists: append to \`metrics\` list (never overwrite). If not: create.
904
+ 5. Ask if adding more metrics.
905
+ 6. Report what was created.
906
+
907
+ ### PDR authoring (oprim-pdr)
908
+ Create a new Product Decision Record in \`oprim/decisions/\`.
909
+
910
+ 1. Ask for decision title.
911
+ 2. Assign next PDR ID: scan \`oprim/decisions/PDR-(\\d+)-\`, max+1 zero-padded to 3 digits (default 001).
912
+ 3. Gather: context, decision, alternatives, consequences, evidence, related bets/specs.
913
+ 4. Ask if superseding an existing PDR.
914
+ 5. Write \`oprim/decisions/PDR-NNN-<slug>.md\`. If superseding, update old PDR Status.
915
+ 6. Report what was created.
916
+
917
+ ### KPI review (oprim-review)
918
+ Create a KPI review artifact in \`oprim/reviews/\`.
919
+
920
+ 1. Ask which bet (e.g. BET-042).
921
+ 2. Read \`oprim/bets/BET-NNN/criteria.yaml\` for pre-fill. Check \`oprim/bets/BET-NNN/measurements/\` for \`run-*.yaml\` — use most recent if present.
922
+ 3. If no run result, ask for each metric's actual value.
923
+ 4. Status: actual >= target → hit; actual < target → missed; not provided → pending.
924
+ 5. Ask reviewer name and decision quality notes.
925
+ 6. Write \`oprim/reviews/YYYY-MM-DD-BET-NNN-kpi.md\`.
926
+ 7. Report what was created.
927
+
928
+ ### Bet archiving (oprim-archive)
929
+ Archive a completed bet.
930
+
931
+ 1. Ask for bet ID (accept bet-005, 005, 5, BET-005 — normalize to BET-NNN).
932
+ 2. Verify \`oprim/bets/BET-NNN/\` exists.
933
+ 3. Check \`oprim/sequence.yaml\` for entries where \`blocked_by\` or \`unlocks\` reference the target bet — warn if found, ask "Archive anyway? (y/N)".
934
+ 4. Move directory: \`oprim/bets/BET-NNN → oprim/bets/archived/BET-NNN\`.
935
+ 5. Remove the bet entry from \`oprim/sequence.yaml\`.
936
+ 6. Report what was done.
937
+ `;
938
+ }
939
+ function codexInstructions() {
940
+ return oprimWorkflowsInline();
941
+ }
942
+ function geminiInstructions() {
943
+ return oprimWorkflowsInline();
944
+ }
@@ -230,10 +230,12 @@ async function runBigQueryMetric(sqlPath) {
230
230
  }
231
231
  // ─── Criteria scanner (used by doctor) ───────────────────────────────────────
232
232
  function scanCriteriaForSourceType(projectRoot, sourceType) {
233
- const betsDir = path.join(projectRoot, 'primer', 'bets');
233
+ const betsDir = path.join(projectRoot, 'oprim', 'bets');
234
234
  if (!fs.existsSync(betsDir))
235
235
  return false;
236
236
  for (const entry of fs.readdirSync(betsDir)) {
237
+ if (entry === 'archived')
238
+ continue;
237
239
  const criteriaPath = path.join(betsDir, entry, 'criteria.yaml');
238
240
  if (!fs.existsSync(criteriaPath))
239
241
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-product-primer/cli",
3
- "version": "0.4.1",
3
+ "version": "0.7.0",
4
4
  "description": "Open Product Primer CLI — product decisions, sequencing, and KPI tracking for repositories",
5
5
  "keywords": [
6
6
  "product",