@open-product-primer/cli 0.4.0 → 0.5.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.
@@ -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,4 +1,4 @@
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[]>;
@@ -7,3 +7,6 @@ export declare const CLAUDE_SKILLS: Record<string, string>;
7
7
  export declare const CLAUDE_COMMANDS: Record<string, string>;
8
8
  export declare const CURSOR_SKILLS: Record<string, string>;
9
9
  export declare const CURSOR_COMMANDS: Record<string, string>;
10
+ export declare function writeAgentInstructionFile(filePath: string, section: string): void;
11
+ export declare function codexInstructions(): string;
12
+ export declare function geminiInstructions(): string;
@@ -40,12 +40,15 @@ exports.CURSOR_COMMANDS = exports.CURSOR_SKILLS = exports.CLAUDE_COMMANDS = expo
40
40
  exports.promptFrameworkSelection = promptFrameworkSelection;
41
41
  exports.promptAgentSelection = promptAgentSelection;
42
42
  exports.installAgentSkills = installAgentSkills;
43
+ exports.writeAgentInstructionFile = writeAgentInstructionFile;
44
+ exports.codexInstructions = codexInstructions;
45
+ exports.geminiInstructions = geminiInstructions;
43
46
  const path = __importStar(require("path"));
44
47
  const fs = __importStar(require("fs"));
45
48
  const chalk_1 = __importDefault(require("chalk"));
46
49
  const scaffold_1 = require("./scaffold");
47
50
  const detect_1 = require("./detect");
48
- exports.SUPPORTED_AGENTS = ['claude', 'cursor'];
51
+ exports.SUPPORTED_AGENTS = ['claude', 'cursor', 'codex', 'gemini'];
49
52
  async function promptFrameworkSelection(projectRoot) {
50
53
  const configPath = path.join(projectRoot, '.claude', 'hooks', 'config.json');
51
54
  if (fs.existsSync(configPath)) {
@@ -81,6 +84,8 @@ async function promptAgentSelection(projectRoot) {
81
84
  choices: [
82
85
  { name: 'Claude Code', value: 'claude', checked: detected.includes('claude') },
83
86
  { name: 'Cursor', value: 'cursor', checked: detected.includes('cursor') },
87
+ { name: 'Codex', value: 'codex', checked: detected.includes('codex') },
88
+ { name: 'Gemini CLI', value: 'gemini', checked: detected.includes('gemini') },
84
89
  ],
85
90
  });
86
91
  }
@@ -131,6 +136,16 @@ function installAgentSkills(agent, projectRoot, framework = 'openspec') {
131
136
  console.log(chalk_1.default.dim(' .claude/ created — Claude Code will discover these files automatically.'));
132
137
  }
133
138
  }
139
+ else if (agent === 'codex') {
140
+ const agentsFile = path.join(projectRoot, 'AGENTS.md');
141
+ writeAgentInstructionFile(agentsFile, codexInstructions());
142
+ console.log(chalk_1.default.green('✓') + ' AGENTS.md (oprim section written)');
143
+ }
144
+ else if (agent === 'gemini') {
145
+ const geminiFile = path.join(projectRoot, 'GEMINI.md');
146
+ writeAgentInstructionFile(geminiFile, geminiInstructions());
147
+ console.log(chalk_1.default.green('✓') + ' GEMINI.md (oprim section written)');
148
+ }
134
149
  else if (agent === 'cursor') {
135
150
  const cursorDir = path.join(projectRoot, '.cursor');
136
151
  const dirCreated = !fs.existsSync(cursorDir);
@@ -290,7 +305,7 @@ After receiving the title, validate: if fewer than 4 words OR fewer than 25 char
290
305
  - If "y": proceed with the original title
291
306
 
292
307
  ### 2. Assign the next BET ID
293
- Scan \`oprim/bets/\` for directories matching \`BET-(\\d+)$\`. Extract all integers. Assign max+1, zero-padded to 3 digits. Default \`001\` if none.
308
+ Scan \`oprim/bets/\` for directories matching \`BET-(\\d+)\`. Extract all integers. Assign max+1, zero-padded to 3 digits. Default \`001\` if none.
294
309
 
295
310
  ### 3. Check sequence.yaml exists
296
311
  If \`oprim/sequence.yaml\` not found: report and stop — advise \`oprim init\`.
@@ -541,7 +556,7 @@ function pdrInlineContent() {
541
556
  return `Create a new PDR in \`oprim/decisions/\`. Scan for \`PDR-(\\d+)-\` to assign next ID (zero-padded, default 001). Gather: title, context, decision, alternatives, consequences, evidence, related bets/specs. Ask if superseding an existing PDR. Write \`oprim/decisions/PDR-NNN-<slug>.md\`. If superseding: update old PDR Status to "Superseded by PDR-NNN". Report what was created.`;
542
557
  }
543
558
  function betInlineContent() {
544
- return `Create a new bet in \`oprim/bets/\`. Before asking for the title, show: "Naming tip: verb + object [for context] — Good: 'Improve bet naming for scannability' / Bad: 'Naming'". Scan \`BET-(\\d+)$\` dirs for next ID (zero-padded, default 001). Check \`oprim/sequence.yaml\` exists (stop if not — advise oprim init). After receiving the title, validate: if fewer than 4 words OR fewer than 25 characters, warn "this title may be too vague", suggest a reformulation, and ask "Proceed anyway? (y/N)" — if "n", prompt for a revised title. Gather: decision (default Build now), owner, review date, why-now, alternatives, expected outcomes, kill criteria, PDR links. Write \`oprim/bets/BET-NNN/bet-decision.md\` with an inline naming tip comment in the header. Append entry to sequence.yaml backlog: \`{id, title, blocked_by: [], unlocks: [], requires_pdrs: []}\`. Then ask: "Do you want to scaffold a discovery.md now? (y/N)" — if "y", write \`oprim/bets/BET-NNN/discovery.md\` from the discovery template (sections: Problem Framing, User Research Signals, Competitive Context, Open Questions); if "n" or Enter, skip silently. Report what was created.`;
559
+ return `Create a new bet in \`oprim/bets/\`. Before asking for the title, show: "Naming tip: verb + object [for context] — Good: 'Improve bet naming for scannability' / Bad: 'Naming'". Scan \`BET-(\\d+)\` dirs for next ID (zero-padded, default 001). Check \`oprim/sequence.yaml\` exists (stop if not — advise oprim init). After receiving the title, validate: if fewer than 4 words OR fewer than 25 characters, warn "this title may be too vague", suggest a reformulation, and ask "Proceed anyway? (y/N)" — if "n", prompt for a revised title. Gather: decision (default Build now), owner, review date, why-now, alternatives, expected outcomes, kill criteria, PDR links. Write \`oprim/bets/BET-NNN/bet-decision.md\` with an inline naming tip comment in the header. Append entry to sequence.yaml backlog: \`{id, title, blocked_by: [], unlocks: [], requires_pdrs: []}\`. Then ask: "Do you want to scaffold a discovery.md now? (y/N)" — if "y", write \`oprim/bets/BET-NNN/discovery.md\` from the discovery template (sections: Problem Framing, User Research Signals, Competitive Context, Open Questions); if "n" or Enter, skip silently. Report what was created.`;
545
560
  }
546
561
  function criteriaInlineContent() {
547
562
  return `Add metrics to \`oprim/bets/BET-NNN/criteria.yaml\`. Verify bet dir exists. Gather: metric ID, name, baseline, target, timeframe, launch date, segment. Ask source type (amplitude or bigquery). Amplitude: event, aggregation, denominator_event. BigQuery: table, metric_column, filter, aggregation, denominator_query. If file exists: append to metrics list (never overwrite). If not: create. Ask if adding more metrics. Report what was created.`;
@@ -707,3 +722,92 @@ Validate the primer sequencing board and suggest rebalancing if needed.
707
722
  6. **Suggest moves** — recommend bets to defer to \`next\` or \`later\` to resolve violations
708
723
  `;
709
724
  }
725
+ // ─── Instruction-file helpers (Codex / Gemini CLI) ────────────────────────────
726
+ const OPRIM_START = '<!-- oprim:start -->';
727
+ const OPRIM_END = '<!-- oprim:end -->';
728
+ function writeAgentInstructionFile(filePath, section) {
729
+ const delimited = `${OPRIM_START}\n${section}\n${OPRIM_END}`;
730
+ if (!fs.existsSync(filePath)) {
731
+ fs.writeFileSync(filePath, delimited + '\n', 'utf-8');
732
+ return;
733
+ }
734
+ const existing = fs.readFileSync(filePath, 'utf-8');
735
+ const startIdx = existing.indexOf(OPRIM_START);
736
+ const endIdx = existing.indexOf(OPRIM_END);
737
+ if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
738
+ const before = existing.slice(0, startIdx);
739
+ const after = existing.slice(endIdx + OPRIM_END.length);
740
+ fs.writeFileSync(filePath, before + delimited + after, 'utf-8');
741
+ }
742
+ else {
743
+ const separator = existing.endsWith('\n') ? '\n' : '\n\n';
744
+ fs.writeFileSync(filePath, existing + separator + delimited + '\n', 'utf-8');
745
+ }
746
+ }
747
+ function oprimWorkflowsInline() {
748
+ return `
749
+ ## oprim workflows
750
+
751
+ ### Bet authoring (oprim-bet)
752
+ Create a new bet in \`oprim/bets/\` and register it on the sequencing board.
753
+
754
+ 1. Show naming tip: "verb + object [for context] — e.g. 'Improve bet naming for scannability'"
755
+ 2. Ask for the bet title. Validate: fewer than 4 words OR fewer than 25 chars → warn, suggest reformulation, ask "Proceed anyway? (y/N)".
756
+ 3. Assign next BET ID: scan \`oprim/bets/BET-(\\d+)\` dirs, max+1 zero-padded to 3 digits (default 001).
757
+ 4. Check \`oprim/sequence.yaml\` exists — stop if not, advise \`oprim init\`.
758
+ 5. Gather: decision (default Build now), owner, review date (YYYY-MM-DD), why now, alternatives, expected outcomes, kill criteria, PDR links.
759
+ 6. Write \`oprim/bets/BET-NNN/bet-decision.md\` with all fields.
760
+ 7. Append to \`oprim/sequence.yaml\` backlog: \`{id, title, blocked_by: [], unlocks: [], requires_pdrs: []}\`.
761
+ 8. Ask: "Scaffold a discovery.md now? (y/N)" — if "y", write \`oprim/bets/BET-NNN/discovery.md\`.
762
+ 9. Report what was created.
763
+
764
+ ### Criteria authoring (oprim-criteria)
765
+ Create or append to \`oprim/bets/BET-NNN/criteria.yaml\`.
766
+
767
+ 1. Ask which bet (e.g. BET-042). Verify dir exists.
768
+ 2. Gather: metric ID (snake_case), name, baseline, target, timeframe, launch date, segment.
769
+ 3. Ask source type (amplitude / bigquery).
770
+ - Amplitude: event, aggregation (unique_users/event_count/property_sum), denominator_event.
771
+ - BigQuery: table, metric_column, filter, aggregation, denominator_query.
772
+ 4. If file exists: append to \`metrics\` list (never overwrite). If not: create.
773
+ 5. Ask if adding more metrics.
774
+ 6. Report what was created.
775
+
776
+ ### PDR authoring (oprim-pdr)
777
+ Create a new Product Decision Record in \`oprim/decisions/\`.
778
+
779
+ 1. Ask for decision title.
780
+ 2. Assign next PDR ID: scan \`oprim/decisions/PDR-(\\d+)-\`, max+1 zero-padded to 3 digits (default 001).
781
+ 3. Gather: context, decision, alternatives, consequences, evidence, related bets/specs.
782
+ 4. Ask if superseding an existing PDR.
783
+ 5. Write \`oprim/decisions/PDR-NNN-<slug>.md\`. If superseding, update old PDR Status.
784
+ 6. Report what was created.
785
+
786
+ ### KPI review (oprim-review)
787
+ Create a KPI review artifact in \`oprim/reviews/\`.
788
+
789
+ 1. Ask which bet (e.g. BET-042).
790
+ 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.
791
+ 3. If no run result, ask for each metric's actual value.
792
+ 4. Status: actual >= target → hit; actual < target → missed; not provided → pending.
793
+ 5. Ask reviewer name and decision quality notes.
794
+ 6. Write \`oprim/reviews/YYYY-MM-DD-BET-NNN-kpi.md\`.
795
+ 7. Report what was created.
796
+
797
+ ### Bet archiving (oprim-archive)
798
+ Archive a completed bet.
799
+
800
+ 1. Ask for bet ID (accept bet-005, 005, 5, BET-005 — normalize to BET-NNN).
801
+ 2. Verify \`oprim/bets/BET-NNN/\` exists.
802
+ 3. Check \`oprim/sequence.yaml\` for entries where \`blocked_by\` or \`unlocks\` reference the target bet — warn if found, ask "Archive anyway? (y/N)".
803
+ 4. Move directory: \`oprim/bets/BET-NNN → oprim/bets/archived/BET-NNN\`.
804
+ 5. Remove the bet entry from \`oprim/sequence.yaml\`.
805
+ 6. Report what was done.
806
+ `;
807
+ }
808
+ function codexInstructions() {
809
+ return oprimWorkflowsInline();
810
+ }
811
+ function geminiInstructions() {
812
+ return oprimWorkflowsInline();
813
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-product-primer/cli",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Open Product Primer CLI — product decisions, sequencing, and KPI tracking for repositories",
5
5
  "keywords": [
6
6
  "product",