productkit 1.9.0 → 1.10.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.
Files changed (34) hide show
  1. package/README.md +25 -2
  2. package/package.json +6 -3
  3. package/src/cli.js +10 -1
  4. package/src/commands/check.js +2 -2
  5. package/src/commands/completion.js +26 -2
  6. package/src/commands/diff.js +4 -16
  7. package/src/commands/doctor.js +12 -4
  8. package/src/commands/export.js +169 -14
  9. package/src/commands/init.js +66 -6
  10. package/src/commands/list.js +17 -0
  11. package/src/commands/reset.js +1 -12
  12. package/src/commands/status.js +15 -12
  13. package/src/commands/update.js +42 -3
  14. package/src/commands/workspace.js +63 -0
  15. package/src/utils/fileUtils.js +57 -11
  16. package/templates/CLAUDE.md +29 -2
  17. package/templates/README.md +17 -0
  18. package/templates/commands/productkit.analyze.md +9 -0
  19. package/templates/commands/productkit.assumptions.md +9 -0
  20. package/templates/commands/productkit.audit.md +7 -0
  21. package/templates/commands/productkit.bootstrap.md +8 -1
  22. package/templates/commands/productkit.clarify.md +9 -0
  23. package/templates/commands/productkit.constitution.md +22 -1
  24. package/templates/commands/productkit.landscape.md +130 -0
  25. package/templates/commands/productkit.learn.md +80 -0
  26. package/templates/commands/productkit.prioritize.md +9 -0
  27. package/templates/commands/productkit.problem.md +10 -0
  28. package/templates/commands/productkit.solution.md +9 -0
  29. package/templates/commands/productkit.spec.md +9 -0
  30. package/templates/commands/productkit.stories.md +166 -0
  31. package/templates/commands/productkit.techreview.md +221 -0
  32. package/templates/commands/productkit.users.md +10 -0
  33. package/templates/commands/productkit.validate.md +18 -6
  34. package/templates/knowledge-README.md +33 -0
package/README.md CHANGED
@@ -43,7 +43,7 @@ productkit init my-project
43
43
  cd my-project
44
44
  ```
45
45
 
46
- This scaffolds a project with slash commands, a `CLAUDE.md` context file, and a `.productkit/` config directory.
46
+ This scaffolds a project with slash commands, a `CLAUDE.md` context file, a `knowledge/` directory for research files, and a `.productkit/` config directory.
47
47
 
48
48
  For existing projects:
49
49
 
@@ -58,6 +58,17 @@ To keep artifacts out of the project root (recommended for busy codebases):
58
58
  productkit init --existing --artifact-dir docs/product
59
59
  ```
60
60
 
61
+ To create a shared workspace for multi-project orgs:
62
+
63
+ ```bash
64
+ productkit workspace my-company
65
+ cd my-company
66
+ productkit init my-app
67
+ productkit init admin-dashboard
68
+ ```
69
+
70
+ The workspace holds shared `landscape.md` and `knowledge/` that all projects inside it inherit automatically. `init` auto-detects when it's run inside a workspace.
71
+
61
72
  ### 2. Open Claude Code
62
73
 
63
74
  ```bash
@@ -70,6 +81,7 @@ Each command starts a guided conversation. Claude asks questions, pushes back on
70
81
 
71
82
  | Step | Command | What it does | Output |
72
83
  |------|---------|-------------|--------|
84
+ | 0 | `/productkit.landscape` | Capture company, team, and domain landscape | `landscape.md` |
73
85
  | 1 | `/productkit.constitution` | Define product principles and values | `constitution.md` |
74
86
  | 2 | `/productkit.users` | Define target user personas through dialogue | `users.md` |
75
87
  | 3 | `/productkit.problem` | Frame the problem statement grounded in user research | `problem.md` |
@@ -82,8 +94,11 @@ Each command starts a guided conversation. Claude asks questions, pushes back on
82
94
  | — | `/productkit.analyze` | Run a consistency and completeness check | Analysis in chat |
83
95
  | — | `/productkit.bootstrap` | Auto-draft all artifacts from existing codebase | All missing artifacts |
84
96
  | — | `/productkit.audit` | Compare spec against codebase, surface gaps | `audit.md` |
97
+ | — | `/productkit.learn` | Index knowledge directory into a compact summary | `knowledge-index.md` |
98
+ | — | `/productkit.techreview` | Review spec against codebase, flag engineering questions | `techreview.md` |
99
+ | — | `/productkit.stories` | Break spec into user stories with acceptance criteria | `stories.md` |
85
100
 
86
- Commands build on each other — `/productkit.problem` reads your `users.md`, `/productkit.solution` reads your problem and users, and `/productkit.spec` synthesizes everything into a single document. You can run `/productkit.clarify` and `/productkit.analyze` at any stage to check your work.
101
+ Commands build on each other — every command reads `landscape.md` and `knowledge-index.md` for evidence, `/productkit.problem` reads your `users.md`, `/productkit.solution` reads your problem and users, and `/productkit.spec` synthesizes everything into a single document. You can run `/productkit.clarify` and `/productkit.analyze` at any stage to check your work.
87
102
 
88
103
  ### 4. Review your artifacts
89
104
 
@@ -91,6 +106,7 @@ After running the commands, your project contains:
91
106
 
92
107
  ```
93
108
  my-project/
109
+ ├── landscape.md # Company & domain landscape
94
110
  ├── constitution.md # Product principles
95
111
  ├── users.md # User personas
96
112
  ├── problem.md # Problem statement
@@ -100,6 +116,10 @@ my-project/
100
116
  ├── priorities.md # Ranked feature list
101
117
  ├── spec.md # Complete product spec
102
118
  ├── audit.md # Spec vs codebase audit (on demand)
119
+ ├── knowledge-index.md # Summary index of knowledge/ files
120
+ ├── knowledge/ # Raw research files (interviews, surveys, etc.)
121
+ ├── techreview.md # Technical feasibility review (on demand)
122
+ ├── stories.md # User stories by epic (on demand)
103
123
  ├── .productkit/config.json
104
124
  ├── .claude/commands/ # Slash command prompts
105
125
  ├── CLAUDE.md
@@ -118,10 +138,13 @@ These markdown files are your product foundation — share them with your team,
118
138
  | `productkit init <name>` | Scaffold a new project |
119
139
  | `productkit init --existing` | Add Product Kit to the current directory |
120
140
  | `productkit init --minimal` | Skip constitution, start with users/problem |
141
+ | `productkit init --mode <solo\|team>` | Set building mode (solo builder vs team with engineers) |
121
142
  | `productkit init --artifact-dir <dir>` | Store artifacts in a custom directory |
143
+ | `productkit workspace <name>` | Create a shared workspace for multi-project orgs |
122
144
  | `productkit status` | Show progress — which artifacts exist and what's next |
123
145
  | `productkit export` | Export all artifacts as a single combined markdown file |
124
146
  | `productkit export --output <file>` | Export to a custom filename |
147
+ | `productkit export --stories-csv` | Export stories as CSV for Jira/Linear/Shortcut import |
125
148
  | `productkit diff` | Show what changed in artifacts since last commit |
126
149
  | `productkit diff --staged` | Show staged artifact changes |
127
150
  | `productkit doctor` | Check project health (missing files, outdated commands) |
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "productkit",
3
- "version": "1.9.0",
3
+ "version": "1.10.0",
4
4
  "description": "Slash-command-driven product thinking toolkit for Claude Code",
5
5
  "main": "src/cli.js",
6
6
  "bin": {
7
- "productkit": "./src/cli.js"
7
+ "productkit": "src/cli.js"
8
8
  },
9
9
  "scripts": {
10
10
  "test": "node --test test/*.test.js"
@@ -22,12 +22,15 @@
22
22
  ],
23
23
  "repository": {
24
24
  "type": "git",
25
- "url": "https://github.com/iamquechua/product-kit.git"
25
+ "url": "git+https://github.com/iamquechua/product-kit.git"
26
26
  },
27
27
  "homepage": "https://github.com/iamquechua/product-kit#readme",
28
28
  "bugs": {
29
29
  "url": "https://github.com/iamquechua/product-kit/issues"
30
30
  },
31
+ "engines": {
32
+ "node": ">=18"
33
+ },
31
34
  "author": "Douno",
32
35
  "license": "MIT",
33
36
  "dependencies": {
package/src/cli.js CHANGED
@@ -12,13 +12,15 @@ const completionCommand = require('./commands/completion');
12
12
  const exportCommand = require('./commands/export');
13
13
  const diffCommand = require('./commands/diff');
14
14
  const doctorCommand = require('./commands/doctor');
15
+ const workspaceCommand = require('./commands/workspace');
15
16
 
17
+ const { version } = require('../package.json');
16
18
  const program = new Command();
17
19
 
18
20
  program
19
21
  .name('productkit')
20
22
  .description(chalk.cyan.bold('Product thinking toolkit for Claude Code'))
21
- .version('1.9.0');
23
+ .version(version);
22
24
 
23
25
  program
24
26
  .command('init [projectName]')
@@ -26,6 +28,7 @@ program
26
28
  .option('--existing', 'Add Product Kit to the current directory')
27
29
  .option('--minimal', 'Skip constitution, start with users/problem')
28
30
  .option('--artifact-dir <dir>', 'Directory for artifacts (default: project root)')
31
+ .option('--mode <mode>', 'Building mode: solo or team')
29
32
  .action(initCommand);
30
33
 
31
34
  program
@@ -64,6 +67,7 @@ program
64
67
  .command('export')
65
68
  .description('Export all artifacts as a single combined markdown file')
66
69
  .option('--output <file>', 'Output filename', 'export.md')
70
+ .option('--stories-csv', 'Export stories as CSV for Jira/Linear import')
67
71
  .action(exportCommand);
68
72
 
69
73
  program
@@ -77,6 +81,11 @@ program
77
81
  .description('Check project health (missing files, outdated commands, etc.)')
78
82
  .action(doctorCommand);
79
83
 
84
+ program
85
+ .command('workspace <name>')
86
+ .description('Create a shared workspace for multi-project orgs')
87
+ .action(workspaceCommand);
88
+
80
89
  program.parse(process.argv);
81
90
 
82
91
  if (process.argv.length === 2) {
@@ -1,9 +1,9 @@
1
1
  const chalk = require('chalk');
2
- const { execSync } = require('child_process');
2
+ const { execFileSync } = require('child_process');
3
3
 
4
4
  async function check() {
5
5
  try {
6
- execSync('claude --version', { stdio: 'ignore' });
6
+ execFileSync('claude', ['--version'], { stdio: 'ignore' });
7
7
  console.log(chalk.green('Claude Code is installed and available.'));
8
8
  } catch {
9
9
  console.log(chalk.red('Claude Code is not installed or not in PATH.'));
@@ -8,10 +8,14 @@ _productkit() {
8
8
  'init:Initialize a new product research project'
9
9
  'check:Verify Claude Code is installed and available'
10
10
  'status:Show which artifacts exist and what steps remain'
11
+ 'export:Export all artifacts as a single combined markdown file'
12
+ 'diff:Show what changed in artifacts since last commit'
13
+ 'doctor:Check project health'
11
14
  'update:Refresh slash commands to the latest version'
12
15
  'reset:Remove all artifacts and start over'
13
16
  'list:Show available slash commands with descriptions'
14
17
  'completion:Output shell completion script'
18
+ 'workspace:Create a shared workspace for multi-project orgs'
15
19
  )
16
20
 
17
21
  _arguments -C \\
@@ -27,8 +31,20 @@ _productkit() {
27
31
  init)
28
32
  _arguments \\
29
33
  '--existing[Add Product Kit to the current directory]' \\
34
+ '--minimal[Skip constitution]' \\
35
+ '--mode[Building mode (solo or team)]:mode:(solo team)' \\
36
+ '--artifact-dir[Store artifacts in a custom directory]:dir:_files -/' \\
30
37
  '1:project name:_files -/'
31
38
  ;;
39
+ export)
40
+ _arguments \\
41
+ '--output[Output filename]:file:_files' \\
42
+ '--stories-csv[Export stories as CSV for Jira/Linear import]'
43
+ ;;
44
+ diff)
45
+ _arguments \\
46
+ '--staged[Show staged changes instead of unstaged]'
47
+ ;;
32
48
  reset)
33
49
  _arguments \\
34
50
  '--force[Skip confirmation prompt]'
@@ -45,7 +61,7 @@ const BASH_COMPLETION = `_productkit() {
45
61
  COMPREPLY=()
46
62
  cur="\${COMP_WORDS[COMP_CWORD]}"
47
63
  prev="\${COMP_WORDS[COMP_CWORD-1]}"
48
- commands="init check status update reset list completion"
64
+ commands="init check status export diff doctor update reset list completion workspace"
49
65
 
50
66
  case "\${prev}" in
51
67
  productkit)
@@ -53,7 +69,15 @@ const BASH_COMPLETION = `_productkit() {
53
69
  return 0
54
70
  ;;
55
71
  init)
56
- COMPREPLY=( $(compgen -W "--existing" -- "\${cur}") )
72
+ COMPREPLY=( $(compgen -W "--existing --minimal --mode --artifact-dir" -- "\${cur}") )
73
+ return 0
74
+ ;;
75
+ export)
76
+ COMPREPLY=( $(compgen -W "--output --stories-csv" -- "\${cur}") )
77
+ return 0
78
+ ;;
79
+ diff)
80
+ COMPREPLY=( $(compgen -W "--staged" -- "\${cur}") )
57
81
  return 0
58
82
  ;;
59
83
  reset)
@@ -1,19 +1,8 @@
1
1
  const fs = require('fs-extra');
2
2
  const path = require('path');
3
3
  const chalk = require('chalk');
4
- const { execSync } = require('child_process');
5
- const { getArtifactDir } = require('../utils/fileUtils');
6
-
7
- const ARTIFACT_FILES = [
8
- 'constitution.md',
9
- 'users.md',
10
- 'problem.md',
11
- 'assumptions.md',
12
- 'validation.md',
13
- 'solution.md',
14
- 'priorities.md',
15
- 'spec.md',
16
- ];
4
+ const { execFileSync } = require('child_process');
5
+ const { getArtifactDir, ARTIFACT_FILES } = require('../utils/fileUtils');
17
6
 
18
7
  async function diff(options) {
19
8
  const root = process.cwd();
@@ -27,7 +16,7 @@ async function diff(options) {
27
16
 
28
17
  // Check if git is available
29
18
  try {
30
- execSync('git rev-parse --git-dir', { cwd: root, stdio: 'ignore' });
19
+ execFileSync('git', ['rev-parse', '--git-dir'], { cwd: root, stdio: 'ignore' });
31
20
  } catch {
32
21
  console.error(chalk.red('Not a git repository. The diff command requires git.'));
33
22
  process.exit(1);
@@ -45,11 +34,10 @@ async function diff(options) {
45
34
  }
46
35
 
47
36
  const gitArgs = options.staged ? ['diff', '--cached'] : ['diff'];
48
- const cmd = ['git', ...gitArgs, '--', ...existing].join(' ');
49
37
 
50
38
  let output;
51
39
  try {
52
- output = execSync(cmd, { cwd: root, encoding: 'utf-8' });
40
+ output = execFileSync('git', [...gitArgs, '--', ...existing], { cwd: root, encoding: 'utf-8' });
53
41
  } catch (err) {
54
42
  // git diff returns exit code 1 when there are differences in some configs
55
43
  output = err.stdout || '';
@@ -1,7 +1,7 @@
1
1
  const fs = require('fs-extra');
2
2
  const path = require('path');
3
3
  const chalk = require('chalk');
4
- const { execSync } = require('child_process');
4
+ const { execFileSync } = require('child_process');
5
5
 
6
6
  async function doctor() {
7
7
  const root = process.cwd();
@@ -45,12 +45,20 @@ async function doctor() {
45
45
 
46
46
  // 3. Check for expected command templates
47
47
  const templatesDir = path.join(__dirname, '..', '..', 'templates', 'commands');
48
- const expectedCommands = fs.readdirSync(templatesDir);
48
+ let expectedCommands;
49
+ try {
50
+ expectedCommands = fs.readdirSync(templatesDir);
51
+ } catch {
52
+ fail('Templates directory missing — Product Kit installation may be corrupted');
53
+ expectedCommands = [];
54
+ }
49
55
 
50
56
  // Account for minimal mode
51
57
  let config = {};
52
58
  try { config = fs.readJsonSync(configPath); } catch {}
53
- const skippable = config.minimal ? ['productkit.constitution.md'] : [];
59
+ // Landscape is workspace-only; constitution is skipped in minimal mode
60
+ const skippable = ['productkit.landscape.md'];
61
+ if (config.minimal) skippable.push('productkit.constitution.md');
54
62
 
55
63
  const missing = [];
56
64
  for (const cmd of expectedCommands) {
@@ -92,7 +100,7 @@ async function doctor() {
92
100
 
93
101
  // 5. Git initialized
94
102
  try {
95
- execSync('git rev-parse --git-dir', { cwd: root, stdio: 'ignore' });
103
+ execFileSync('git', ['rev-parse', '--git-dir'], { cwd: root, stdio: 'ignore' });
96
104
  pass('Git repository initialized');
97
105
  } catch {
98
106
  warn('No git repository — consider running git init');
@@ -1,18 +1,148 @@
1
1
  const fs = require('fs-extra');
2
2
  const path = require('path');
3
3
  const chalk = require('chalk');
4
- const { getArtifactDir } = require('../utils/fileUtils');
5
-
6
- const ARTIFACTS = [
7
- { file: 'constitution.md', label: 'Constitution' },
8
- { file: 'users.md', label: 'Users' },
9
- { file: 'problem.md', label: 'Problem' },
10
- { file: 'assumptions.md', label: 'Assumptions' },
11
- { file: 'validation.md', label: 'Validation' },
12
- { file: 'solution.md', label: 'Solution' },
13
- { file: 'priorities.md', label: 'Priorities' },
14
- { file: 'spec.md', label: 'Spec' },
15
- ];
4
+ const { getArtifactDir, getWorkspaceRoot, ARTIFACTS } = require('../utils/fileUtils');
5
+
6
+ function escapeCsvField(field) {
7
+ if (field == null) return '';
8
+ const str = String(field);
9
+ if (str.includes(',') || str.includes('"') || str.includes('\n')) {
10
+ return '"' + str.replace(/"/g, '""') + '"';
11
+ }
12
+ return str;
13
+ }
14
+
15
+ function parseStoriesCsv(content) {
16
+ const lines = content.split('\n');
17
+ // Detect mode from document headers produced by /productkit.stories
18
+ const isTeamMode = lines.some(l => l.trim() === '# User Stories');
19
+
20
+ if (isTeamMode) {
21
+ return parseTeamMode(lines);
22
+ }
23
+ return parseSoloMode(lines);
24
+ }
25
+
26
+ function parseTeamMode(lines) {
27
+ const headers = ['ID', 'Title', 'Epic', 'Priority', 'Estimate', 'Depends On', 'Acceptance Criteria', 'Definition of Done', 'Notes'];
28
+ const rows = [];
29
+ let currentEpic = '';
30
+ let currentRow = null;
31
+ let collectingAC = false;
32
+
33
+ for (const line of lines) {
34
+ const epicMatch = line.match(/^## Epic \d+:\s*(.+)/);
35
+ if (epicMatch) {
36
+ if (currentRow) rows.push(currentRow);
37
+ currentEpic = epicMatch[1].trim();
38
+ currentRow = null;
39
+ collectingAC = false;
40
+ continue;
41
+ }
42
+
43
+ const storyMatch = line.match(/^### (E\d+-S\d+):\s*(.*)/);
44
+ if (storyMatch) {
45
+ if (currentRow) rows.push(currentRow);
46
+ collectingAC = false;
47
+ currentRow = { ID: storyMatch[1], Title: storyMatch[2].trim(), Epic: currentEpic, Priority: '', Estimate: '', 'Depends On': '', 'Acceptance Criteria': [], 'Definition of Done': '', Notes: '' };
48
+ continue;
49
+ }
50
+
51
+ if (!currentRow) continue;
52
+
53
+ if (collectingAC) {
54
+ const checkMatch = line.match(/^\s*- \[[ x]\]\s*(.+)/);
55
+ if (checkMatch) {
56
+ currentRow['Acceptance Criteria'].push(checkMatch[1].trim());
57
+ continue;
58
+ }
59
+ collectingAC = false;
60
+ }
61
+
62
+ const titleMatch = line.match(/^- \*\*Title:\*\*\s*(.+)/);
63
+ if (titleMatch) { currentRow.Title = titleMatch[1].trim(); continue; }
64
+
65
+ const prioMatch = line.match(/^- \*\*Priority:\*\*\s*(.+)/);
66
+ if (prioMatch) { currentRow.Priority = prioMatch[1].trim(); continue; }
67
+
68
+ const estMatch = line.match(/^- \*\*Estimate:\*\*\s*(.+)/);
69
+ if (estMatch) { currentRow.Estimate = estMatch[1].trim(); continue; }
70
+
71
+ const depMatch = line.match(/^- \*\*Depends on:\*\*\s*(.+)/);
72
+ if (depMatch) { currentRow['Depends On'] = depMatch[1].trim(); continue; }
73
+
74
+ if (line.match(/^- \*\*Acceptance Criteria:\*\*/)) {
75
+ collectingAC = true;
76
+ continue;
77
+ }
78
+
79
+ const dodMatch = line.match(/^- \*\*Definition of Done:\*\*\s*(.+)/);
80
+ if (dodMatch) { currentRow['Definition of Done'] = dodMatch[1].trim(); continue; }
81
+
82
+ const notesMatch = line.match(/^- \*\*Notes:\*\*\s*(.+)/);
83
+ if (notesMatch) { currentRow.Notes = notesMatch[1].trim(); continue; }
84
+ }
85
+
86
+ if (currentRow) rows.push(currentRow);
87
+
88
+ const csvRows = rows.map(r => {
89
+ const ac = Array.isArray(r['Acceptance Criteria']) ? r['Acceptance Criteria'].join('; ') : r['Acceptance Criteria'];
90
+ return headers.map(h => escapeCsvField(h === 'Acceptance Criteria' ? ac : r[h])).join(',');
91
+ });
92
+
93
+ return [headers.join(','), ...csvRows].join('\n') + '\n';
94
+ }
95
+
96
+ function parseSoloMode(lines) {
97
+ const headers = ['ID', 'Task', 'Effort', 'Depends On', 'Done When', 'Watch Out For'];
98
+ const rows = [];
99
+ let currentRow = null;
100
+ let collectingDone = false;
101
+
102
+ for (const line of lines) {
103
+ const taskMatch = line.match(/^### (T\d+):\s*(.*)/);
104
+ if (taskMatch) {
105
+ if (currentRow) rows.push(currentRow);
106
+ collectingDone = false;
107
+ currentRow = { ID: taskMatch[1], Task: taskMatch[2].trim(), Effort: '', 'Depends On': '', 'Done When': [], 'Watch Out For': '' };
108
+ continue;
109
+ }
110
+
111
+ if (!currentRow) continue;
112
+
113
+ if (collectingDone) {
114
+ const checkMatch = line.match(/^\s*- \[[ x]\]\s*(.+)/);
115
+ if (checkMatch) {
116
+ currentRow['Done When'].push(checkMatch[1].trim());
117
+ continue;
118
+ }
119
+ collectingDone = false;
120
+ }
121
+
122
+ const effortMatch = line.match(/^- \*\*Effort:\*\*\s*(.+)/);
123
+ if (effortMatch) { currentRow.Effort = effortMatch[1].trim(); continue; }
124
+
125
+ const depMatch = line.match(/^- \*\*Depends on:\*\*\s*(.+)/);
126
+ if (depMatch) { currentRow['Depends On'] = depMatch[1].trim(); continue; }
127
+
128
+ if (line.match(/^- \*\*Done when:\*\*/)) {
129
+ collectingDone = true;
130
+ continue;
131
+ }
132
+
133
+ const watchMatch = line.match(/^- \*\*Watch out for:\*\*\s*(.+)/);
134
+ if (watchMatch) { currentRow['Watch Out For'] = watchMatch[1].trim(); continue; }
135
+ }
136
+
137
+ if (currentRow) rows.push(currentRow);
138
+
139
+ const csvRows = rows.map(r => {
140
+ const dw = Array.isArray(r['Done When']) ? r['Done When'].join('; ') : r['Done When'];
141
+ return headers.map(h => escapeCsvField(h === 'Done When' ? dw : r[h])).join(',');
142
+ });
143
+
144
+ return [headers.join(','), ...csvRows].join('\n') + '\n';
145
+ }
16
146
 
17
147
  async function exportCommand(options) {
18
148
  const root = process.cwd();
@@ -25,14 +155,38 @@ async function exportCommand(options) {
25
155
  }
26
156
 
27
157
  const artifactDir = getArtifactDir(root);
158
+
159
+ if (options.storiesCsv) {
160
+ const storiesPath = path.join(artifactDir, 'stories.md');
161
+ if (!fs.existsSync(storiesPath)) {
162
+ console.error(chalk.red('No stories.md found. Run /productkit.stories first.'));
163
+ process.exit(1);
164
+ }
165
+ const content = fs.readFileSync(storiesPath, 'utf-8');
166
+ const csv = parseStoriesCsv(content);
167
+ const outputFile = options.output === 'export.md' ? 'stories.csv' : options.output;
168
+ fs.writeFileSync(path.join(root, outputFile), csv);
169
+ console.log(chalk.green.bold(`Exported stories to ${outputFile}`));
170
+ return;
171
+ }
172
+
28
173
  const existing = ARTIFACTS.filter(a => fs.existsSync(path.join(artifactDir, a.file)));
29
174
 
30
- if (existing.length === 0) {
175
+ // Check for workspace landscape
176
+ const workspaceRoot = getWorkspaceRoot(root);
177
+ const hasLandscape = workspaceRoot && fs.existsSync(path.join(workspaceRoot, 'landscape.md'));
178
+
179
+ if (existing.length === 0 && !hasLandscape) {
31
180
  console.error(chalk.red('No artifacts found. Run some slash commands first.'));
32
181
  process.exit(1);
33
182
  }
34
183
 
35
184
  const sections = [];
185
+
186
+ if (hasLandscape) {
187
+ sections.push(fs.readFileSync(path.join(workspaceRoot, 'landscape.md'), 'utf-8'));
188
+ }
189
+
36
190
  for (const artifact of existing) {
37
191
  const content = fs.readFileSync(path.join(artifactDir, artifact.file), 'utf-8');
38
192
  sections.push(content);
@@ -45,7 +199,8 @@ async function exportCommand(options) {
45
199
  const outputFile = options.output || 'export.md';
46
200
  fs.writeFileSync(path.join(root, outputFile), combined);
47
201
 
48
- console.log(chalk.green.bold(`Exported ${existing.length} artifact(s) to ${outputFile}`));
202
+ const totalCount = existing.length + (hasLandscape ? 1 : 0);
203
+ console.log(chalk.green.bold(`Exported ${totalCount} artifact(s) to ${outputFile}`));
49
204
  }
50
205
 
51
206
  module.exports = exportCommand;
@@ -1,9 +1,29 @@
1
1
  const fs = require('fs-extra');
2
2
  const path = require('path');
3
3
  const chalk = require('chalk');
4
+ const readline = require('readline');
5
+
6
+ function promptMode() {
7
+ const rl = readline.createInterface({
8
+ input: process.stdin,
9
+ output: process.stdout,
10
+ });
11
+ return new Promise((resolve) => {
12
+ console.log();
13
+ console.log(chalk.cyan('How are you building this product?'));
14
+ console.log(' 1. Solo — I\'m building it myself');
15
+ console.log(' 2. Team — I\'m working with engineers/designers');
16
+ console.log();
17
+ rl.question('Choose (1 or 2): ', (answer) => {
18
+ rl.close();
19
+ resolve(answer.trim() === '2' ? 'team' : 'solo');
20
+ });
21
+ });
22
+ }
4
23
 
5
- function scaffold(projectRoot, projectName, minimal, artifactDir) {
24
+ function scaffold(projectRoot, projectName, minimal, artifactDir, mode) {
6
25
  const templatesDir = path.join(__dirname, '..', '..', 'templates');
26
+ const pkgVersion = require('../../package.json').version;
7
27
 
8
28
  // Create directories
9
29
  fs.ensureDirSync(path.join(projectRoot, '.productkit'));
@@ -11,9 +31,21 @@ function scaffold(projectRoot, projectName, minimal, artifactDir) {
11
31
 
12
32
  // Write config
13
33
  const config = {
14
- version: '1.0.0',
34
+ version: pkgVersion,
15
35
  created: new Date().toISOString(),
16
36
  };
37
+
38
+ // Detect workspace: check if parent has workspace config
39
+ const parentDir = path.dirname(projectRoot);
40
+ const parentConfigPath = path.join(parentDir, '.productkit', 'config.json');
41
+ try {
42
+ const parentConfig = fs.readJsonSync(parentConfigPath);
43
+ if (parentConfig.type === 'workspace') {
44
+ config.type = 'project';
45
+ config.workspace = '..';
46
+ }
47
+ } catch {}
48
+
17
49
  if (minimal) {
18
50
  config.minimal = true;
19
51
  }
@@ -21,6 +53,10 @@ function scaffold(projectRoot, projectName, minimal, artifactDir) {
21
53
  config.artifact_dir = artifactDir;
22
54
  fs.ensureDirSync(path.join(projectRoot, artifactDir));
23
55
  }
56
+ if (mode) {
57
+ config.mode = mode;
58
+ }
59
+ config.knowledge_dir = 'knowledge';
24
60
  fs.writeJsonSync(path.join(projectRoot, '.productkit', 'config.json'), config, { spaces: 2 });
25
61
 
26
62
  // Copy slash command templates
@@ -28,6 +64,8 @@ function scaffold(projectRoot, projectName, minimal, artifactDir) {
28
64
  const commandFiles = fs.readdirSync(commandsDir);
29
65
  for (const file of commandFiles) {
30
66
  if (minimal && file === 'productkit.constitution.md') continue;
67
+ // Landscape lives at workspace level, not project level
68
+ if (file === 'productkit.landscape.md') continue;
31
69
  fs.copyFileSync(
32
70
  path.join(commandsDir, file),
33
71
  path.join(projectRoot, '.claude', 'commands', file)
@@ -51,6 +89,14 @@ function scaffold(projectRoot, projectName, minimal, artifactDir) {
51
89
  fs.writeFileSync(path.join(projectRoot, 'README.md'), readme);
52
90
  }
53
91
 
92
+ // Create knowledge directory with README
93
+ const knowledgeDir = path.join(projectRoot, 'knowledge');
94
+ fs.ensureDirSync(knowledgeDir);
95
+ fs.copyFileSync(
96
+ path.join(templatesDir, 'knowledge-README.md'),
97
+ path.join(knowledgeDir, 'README.md')
98
+ );
99
+
54
100
  // Copy .gitignore (only for new projects)
55
101
  if (!fs.existsSync(path.join(projectRoot, '.gitignore'))) {
56
102
  fs.copyFileSync(
@@ -61,6 +107,20 @@ function scaffold(projectRoot, projectName, minimal, artifactDir) {
61
107
  }
62
108
 
63
109
  async function init(projectName, options) {
110
+ // Validate and resolve mode
111
+ let mode = options.mode;
112
+ if (mode && mode !== 'solo' && mode !== 'team') {
113
+ console.error(chalk.red('Error: --mode must be "solo" or "team"'));
114
+ process.exit(1);
115
+ }
116
+ if (!mode) {
117
+ if (process.stdin.isTTY) {
118
+ mode = await promptMode();
119
+ } else {
120
+ mode = 'solo';
121
+ }
122
+ }
123
+
64
124
  if (options.existing) {
65
125
  const projectRoot = process.cwd();
66
126
 
@@ -70,7 +130,7 @@ async function init(projectName, options) {
70
130
  }
71
131
 
72
132
  try {
73
- scaffold(projectRoot, path.basename(projectRoot), options.minimal, options.artifactDir);
133
+ scaffold(projectRoot, path.basename(projectRoot), options.minimal, options.artifactDir, mode);
74
134
 
75
135
  console.log(chalk.green.bold('Product Kit added to existing project!'));
76
136
  console.log();
@@ -98,12 +158,12 @@ async function init(projectName, options) {
98
158
  }
99
159
 
100
160
  try {
101
- scaffold(projectRoot, projectName, options.minimal, options.artifactDir);
161
+ scaffold(projectRoot, projectName, options.minimal, options.artifactDir, mode);
102
162
 
103
163
  // Init git repo
104
- const { execSync } = require('child_process');
164
+ const { execFileSync } = require('child_process');
105
165
  try {
106
- execSync('git init', { cwd: projectRoot, stdio: 'ignore' });
166
+ execFileSync('git', ['init'], { cwd: projectRoot, stdio: 'ignore' });
107
167
  } catch {
108
168
  // Git not available, skip
109
169
  }