@open-product-primer/cli 1.0.0 → 1.1.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.
@@ -59,20 +59,32 @@ function initCommand() {
59
59
  console.log(chalk_1.default.green('✓') + ' OpenSpec detected');
60
60
  if (graphify.detected)
61
61
  console.log(chalk_1.default.green('✓') + ' Graphify detected');
62
+ console.log('');
63
+ const okfEnabled = await (0, install_agent_1.promptOkfFrontmatter)();
62
64
  const primerDir = path.join(projectRoot, 'oprim');
63
65
  (0, scaffold_1.ensureDir)(path.join(primerDir, 'decisions'));
64
66
  (0, scaffold_1.ensureDir)(path.join(primerDir, 'bets'));
65
67
  (0, scaffold_1.ensureDir)(path.join(primerDir, 'reviews'));
66
68
  (0, scaffold_1.ensureDir)(path.join(primerDir, 'templates'));
67
69
  (0, scaffold_1.ensureDir)(path.join(primerDir, 'scripts'));
68
- const configWritten = (0, scaffold_1.writeFileIfAbsent)(path.join(primerDir, 'config.yaml'), (0, templates_1.configTemplate)(projectName, openspec.detected, graphify.detected));
70
+ const configWritten = (0, scaffold_1.writeFileIfAbsent)(path.join(primerDir, 'config.yaml'), (0, templates_1.configTemplate)(projectName, openspec.detected, graphify.detected, okfEnabled));
69
71
  const sequenceWritten = (0, scaffold_1.writeFileIfAbsent)(path.join(primerDir, 'sequence.yaml'), templates_1.sequenceTemplate);
70
- (0, scaffold_1.writeFile)(path.join(primerDir, 'templates', 'pdr.md'), templates_1.pdrTemplate);
71
- (0, scaffold_1.writeFile)(path.join(primerDir, 'templates', 'bet-decision.md'), templates_1.betDecisionTemplate);
72
+ const pdrContent = okfEnabled ? (0, templates_1.okfFrontmatter)('pdr', '<Decision title>') + templates_1.pdrTemplate : templates_1.pdrTemplate;
73
+ const betContent = okfEnabled
74
+ ? (0, templates_1.okfFrontmatter)('bet-decision', '<Bet title>') + templates_1.betDecisionTemplate
75
+ : templates_1.betDecisionTemplate;
76
+ const kpiContent = okfEnabled
77
+ ? (0, templates_1.okfFrontmatter)('kpi-review', 'KPI Review: BET-XXX') + templates_1.kpiReviewTemplate
78
+ : templates_1.kpiReviewTemplate;
79
+ (0, scaffold_1.writeFile)(path.join(primerDir, 'templates', 'pdr.md'), pdrContent);
80
+ (0, scaffold_1.writeFile)(path.join(primerDir, 'templates', 'bet-decision.md'), betContent);
72
81
  (0, scaffold_1.writeFile)(path.join(primerDir, 'templates', 'criteria.yaml'), templates_1.criteriaTemplate);
73
- (0, scaffold_1.writeFile)(path.join(primerDir, 'templates', 'kpi-review.md'), templates_1.kpiReviewTemplate);
82
+ (0, scaffold_1.writeFile)(path.join(primerDir, 'templates', 'kpi-review.md'), kpiContent);
74
83
  (0, scaffold_1.writeFile)(path.join(primerDir, 'templates', 'discovery.md'), templates_1.discoveryTemplate);
75
84
  (0, scaffold_1.writeFile)(path.join(primerDir, 'scripts', 'generate-sequence-view.js'), templates_1.sequenceViewScriptTemplate);
85
+ if (okfEnabled) {
86
+ (0, scaffold_1.writeFile)(path.join(primerDir, 'index.md'), (0, templates_1.indexTemplate)(projectName));
87
+ }
76
88
  (0, scaffold_1.writeFileIfAbsent)(path.join(primerDir, 'decisions', '.gitkeep'), '');
77
89
  (0, scaffold_1.writeFileIfAbsent)(path.join(primerDir, 'bets', '.gitkeep'), '');
78
90
  (0, scaffold_1.writeFileIfAbsent)(path.join(primerDir, 'reviews', '.gitkeep'), '');
@@ -83,6 +95,7 @@ function initCommand() {
83
95
  console.log(' ' + chalk_1.default.gray('oprim/sequence.yaml') + ' — ' + sequenceStatus);
84
96
  console.log(' ' + chalk_1.default.gray('oprim/templates/') + ' — refreshed');
85
97
  console.log(' ' + chalk_1.default.gray('oprim/scripts/') + ' — refreshed');
98
+ console.log(' ' + chalk_1.default.gray('OKF frontmatter') + ' — ' + (okfEnabled ? 'enabled' : 'disabled'));
86
99
  // ── Agent selection ───────────────────────────────────────────────────────
87
100
  let selectedAgents;
88
101
  const flaggedAgents = opts.agent;
@@ -51,6 +51,10 @@ function updateCommand() {
51
51
  .action(async () => {
52
52
  const projectRoot = process.cwd();
53
53
  const configAgents = (0, detect_1.readAgentsFromConfig)(projectRoot);
54
+ // okf.enabled is persisted at init time; update reads it (no re-prompt) since
55
+ // already-scaffolded oprim/templates/*.md files are never rewritten here.
56
+ const okfEnabled = (0, detect_1.readOkfEnabledFromConfig)(projectRoot);
57
+ console.log(chalk_1.default.dim(`OKF frontmatter: ${okfEnabled ? 'enabled' : 'disabled'} (persisted from init)`));
54
58
  const primerDir = path.join(projectRoot, 'oprim');
55
59
  (0, scaffold_1.ensureDir)(path.join(primerDir, 'scripts'));
56
60
  (0, scaffold_1.writeFile)(path.join(primerDir, 'scripts', 'generate-sequence-view.js'), templates_1.sequenceViewScriptTemplate);
@@ -7,5 +7,6 @@ export declare function detectGraphify(projectRoot: string): {
7
7
  graphDir: string | null;
8
8
  };
9
9
  export declare function readAgentsFromConfig(projectRoot: string): string[] | null;
10
+ export declare function readOkfEnabledFromConfig(projectRoot: string): boolean;
10
11
  export declare function detectAvailableAgents(projectRoot: string): string[];
11
12
  export declare function writeAgentsToConfig(agents: string[], projectRoot: string): void;
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.detectOpenSpec = detectOpenSpec;
37
37
  exports.detectGraphify = detectGraphify;
38
38
  exports.readAgentsFromConfig = readAgentsFromConfig;
39
+ exports.readOkfEnabledFromConfig = readOkfEnabledFromConfig;
39
40
  exports.detectAvailableAgents = detectAvailableAgents;
40
41
  exports.writeAgentsToConfig = writeAgentsToConfig;
41
42
  const fs = __importStar(require("fs"));
@@ -62,6 +63,15 @@ function readAgentsFromConfig(projectRoot) {
62
63
  return null;
63
64
  return agents;
64
65
  }
66
+ function readOkfEnabledFromConfig(projectRoot) {
67
+ const configPath = path.join(projectRoot, 'oprim', 'config.yaml');
68
+ if (!fs.existsSync(configPath))
69
+ return false;
70
+ const content = fs.readFileSync(configPath, 'utf-8');
71
+ const config = yaml.load(content);
72
+ const okf = config?.['okf'];
73
+ return Boolean(okf?.['enabled']);
74
+ }
65
75
  function detectAvailableAgents(projectRoot) {
66
76
  const detected = [];
67
77
  if (fs.existsSync(path.join(projectRoot, '.claude')))
@@ -3,6 +3,7 @@ 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
5
  export declare function promptPdrSurfacing(): Promise<boolean>;
6
+ export declare function promptOkfFrontmatter(): Promise<boolean>;
6
7
  export declare function installAgentSkills(agent: Agent, projectRoot: string, framework?: string, pdrSurfacing?: boolean): void;
7
8
  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
9
  export declare const CLAUDE_SKILLS: Record<string, string>;
@@ -40,6 +40,7 @@ exports.CURSOR_COMMANDS = exports.CURSOR_SKILLS = exports.POOLSIDE_SKILLS = expo
40
40
  exports.promptFrameworkSelection = promptFrameworkSelection;
41
41
  exports.promptAgentSelection = promptAgentSelection;
42
42
  exports.promptPdrSurfacing = promptPdrSurfacing;
43
+ exports.promptOkfFrontmatter = promptOkfFrontmatter;
43
44
  exports.installAgentSkills = installAgentSkills;
44
45
  exports.writeAgentInstructionFile = writeAgentInstructionFile;
45
46
  exports.codexInstructions = codexInstructions;
@@ -96,6 +97,13 @@ async function promptPdrSurfacing() {
96
97
  const { confirm } = await Promise.resolve().then(() => __importStar(require('@inquirer/prompts')));
97
98
  return confirm({ message: 'Enable proactive PDR surfacing in skills? (y/N)', default: false });
98
99
  }
100
+ async function promptOkfFrontmatter() {
101
+ const { confirm } = await Promise.resolve().then(() => __importStar(require('@inquirer/prompts')));
102
+ return confirm({
103
+ message: 'Enable OKF (Open Knowledge Format) frontmatter on scaffolded artifacts? (y/N)',
104
+ default: false,
105
+ });
106
+ }
99
107
  function installAgentSkills(agent, projectRoot, framework = 'openspec', pdrSurfacing = false) {
100
108
  if (agent === 'claude') {
101
109
  const claudeDir = path.join(projectRoot, '.claude');
@@ -372,7 +380,12 @@ Ask: Context (what forced this decision), Decision (clear statement), Alternativ
372
380
  ### 4. Check for supersession
373
381
  Ask: "Does this supersede an existing PDR? If so, which ID? (Enter to skip)"
374
382
 
383
+ ### 4b. Check for OKF frontmatter
384
+ Read \`oprim/templates/pdr.md\`. If it begins with a YAML frontmatter block (\`---\` ... \`---\`), this workspace has OKF frontmatter enabled. Ask for a one-line description and comma-separated tags (subject-area keywords). Prepare a frontmatter block with \`type: pdr\`, \`title: <title>\`, \`description: <description>\`, \`tags: [<tags>]\`, \`timestamp: <today's date, ISO 8601>\`, to prepend in step 5.
385
+ If no frontmatter block is found in the template, skip this step — write the file with no frontmatter, matching current behavior.
386
+
375
387
  ### 5. Write the PDR file
388
+ Prepend the frontmatter block from step 4b, if one was prepared.
376
389
  \`\`\`
377
390
  # PDR-NNN: <title>
378
391
 
@@ -461,7 +474,12 @@ Then ask about each of the four risk dimensions (Low / Medium / High + short rat
461
474
  - "**Feasibility risk**: Can we build this with our current skills, time, and technology? (Low / Medium / High — and why?)"
462
475
  - "**Business viability risk**: Does this solution work for the business (revenue, legal, ops)? (Low / Medium / High — and why?)"
463
476
 
477
+ ### 4b. Check for OKF frontmatter
478
+ Read \`oprim/templates/bet-decision.md\`. If it begins with a YAML frontmatter block (\`---\` ... \`---\`), this workspace has OKF frontmatter enabled. Ask for a one-line description and comma-separated tags (subject-area keywords). Prepare a frontmatter block with \`type: bet-decision\`, \`title: <title>\`, \`description: <description>\`, \`tags: [<tags>]\`, \`timestamp: <today's date, ISO 8601>\`, to prepend in step 5.
479
+ If no frontmatter block is found in the template, skip this step — write the file with no frontmatter, matching current behavior.
480
+
464
481
  ### 5. Write oprim/bets/BET-NNN-<slug>/bet-decision.md
482
+ Prepend the frontmatter block from step 4b, if one was prepared.
465
483
  \`\`\`
466
484
  # Decision: BET-NNN <title>
467
485
  <!-- Naming tip: verb + object [for context] — e.g. "Improve bet naming for scannability" not "Naming" -->
@@ -820,7 +838,12 @@ Ask: reviewer name, decision quality notes.
820
838
  ### 5. Output path
821
839
  \`oprim/reviews/YYYY-MM-DD-BET-NNN-kpi.md\` (today's date)
822
840
 
841
+ ### 5b. Check for OKF frontmatter
842
+ Read \`oprim/templates/kpi-review.md\`. If it begins with a YAML frontmatter block (\`---\` ... \`---\`), this workspace has OKF frontmatter enabled. Ask for a one-line description and comma-separated tags (derived from the reviewed bet's subject area). Prepare a frontmatter block with \`type: kpi-review\`, \`title: <bet ID and title>\`, \`description: <description>\`, \`tags: [<tags>]\`, \`timestamp: <review date, ISO 8601>\`, to prepend in step 6.
843
+ If no frontmatter block is found in the template, skip this step — write the file with no frontmatter, matching current behavior.
844
+
823
845
  ### 6. Write the review file
846
+ Prepend the frontmatter block from step 5b, if one was prepared.
824
847
  \`\`\`markdown
825
848
  # KPI Review: BET-NNN
826
849
 
@@ -1,4 +1,6 @@
1
- export declare function configTemplate(projectName: string, openspecEnabled: boolean, graphifyEnabled: boolean): string;
1
+ export declare function configTemplate(projectName: string, openspecEnabled: boolean, graphifyEnabled: boolean, okfEnabled: boolean): string;
2
+ export declare function okfFrontmatter(type: string, titleHint: string): string;
3
+ export declare function indexTemplate(projectName: string): string;
2
4
  export declare const sequenceTemplate = "wip_limits:\n now: 2\n\nnow: []\nnext: []\nlater: []\nbacklog: []\n";
3
5
  export declare const pdrTemplate = "# PDR-XXX: <Decision title>\n\n## Status\nProposed | Accepted | Deprecated | Superseded by PDR-YYY\n\n## Context\n<What forced this decision?>\n\n## Decision\n<Clear statement of what is decided>\n\n## Alternatives considered\n- <Alternative A and why rejected>\n- <Alternative B and why rejected>\n\n## Consequences\n- Positive: <...>\n- Trade-offs: <...>\n- Follow-ups: <...>\n\n## Evidence\n- <Research link>\n- <Data link>\n\n## Related\n- Bets: <BET-IDs>\n- OpenSpec: <change paths>\n- Supersedes: <PDR-ID or none>\n";
4
6
  export declare const betDecisionTemplate = "# Decision: BET-XXX <Bet title>\n<!-- Naming tip: verb + object [for context] \u2014 e.g. \"Improve bet naming for scannability\" not \"Naming\" -->\n\n## Status\n- Decision: Build now | Defer | Kill\n- Date: YYYY-MM-DD\n- Owner: <name>\n- Review date: YYYY-MM-DD\n\n## Why now\n- <prioritization rationale>\n\n## Alternatives considered\n- <alternative + reason>\n\n## Expected outcomes\n- <metric: baseline -> target in timeframe>\n\n## Kill criteria / rollback trigger\n- <condition and action>\n\n## Links\n- PDRs: <PDR-IDs>\n- OpenSpec change: <path once promoted>\n";
@@ -2,7 +2,9 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.kpiReviewTemplate = exports.sequenceViewScriptTemplate = exports.discoveryTemplate = exports.criteriaTemplate = exports.betDecisionTemplate = exports.pdrTemplate = exports.sequenceTemplate = void 0;
4
4
  exports.configTemplate = configTemplate;
5
- function configTemplate(projectName, openspecEnabled, graphifyEnabled) {
5
+ exports.okfFrontmatter = okfFrontmatter;
6
+ exports.indexTemplate = indexTemplate;
7
+ function configTemplate(projectName, openspecEnabled, graphifyEnabled, okfEnabled) {
6
8
  return `version: 1
7
9
  project:
8
10
  name: "${projectName}"
@@ -14,6 +16,8 @@ integrations:
14
16
  graphify:
15
17
  enabled: ${graphifyEnabled}
16
18
  graph_dir: graphify-out
19
+ okf:
20
+ enabled: ${okfEnabled}
17
21
  measurement:
18
22
  amplitude:
19
23
  enabled: false
@@ -25,6 +29,29 @@ sequencing:
25
29
  now: 2
26
30
  `;
27
31
  }
32
+ // OKF (Open Knowledge Format) — https://github.com/GoogleCloudPlatform/okf
33
+ function okfFrontmatter(type, titleHint) {
34
+ return `---
35
+ type: ${type}
36
+ title: "${titleHint}"
37
+ description: "<one-line summary>"
38
+ tags: []
39
+ timestamp: YYYY-MM-DDTHH:MM:SSZ
40
+ ---
41
+
42
+ `;
43
+ }
44
+ function indexTemplate(projectName) {
45
+ return `${okfFrontmatter('index', `${projectName} — oprim workspace`)}# ${projectName} — oprim workspace
46
+
47
+ OKF (Open Knowledge Format) bundle entrypoint for this oprim workspace.
48
+
49
+ ## Contents
50
+ - [Bets](./bets/) — product bet decisions
51
+ - [Decisions](./decisions/) — Product Decision Records (PDRs)
52
+ - [Reviews](./reviews/) — KPI reviews
53
+ `;
54
+ }
28
55
  exports.sequenceTemplate = `wip_limits:
29
56
  now: 2
30
57
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-product-primer/cli",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Open Product Primer CLI — product decisions, sequencing, and KPI tracking for repositories",
5
5
  "keywords": [
6
6
  "product",