@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.
- package/dist/commands/init.js +17 -4
- package/dist/commands/update.js +4 -0
- package/dist/lib/detect.d.ts +1 -0
- package/dist/lib/detect.js +10 -0
- package/dist/lib/install-agent.d.ts +1 -0
- package/dist/lib/install-agent.js +23 -0
- package/dist/lib/templates.d.ts +3 -1
- package/dist/lib/templates.js +28 -1
- package/package.json +1 -1
package/dist/commands/init.js
CHANGED
|
@@ -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,
|
|
71
|
-
|
|
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'),
|
|
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;
|
package/dist/commands/update.js
CHANGED
|
@@ -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);
|
package/dist/lib/detect.d.ts
CHANGED
|
@@ -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;
|
package/dist/lib/detect.js
CHANGED
|
@@ -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
|
|
package/dist/lib/templates.d.ts
CHANGED
|
@@ -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";
|
package/dist/lib/templates.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|