popilot 0.2.0 → 0.2.1
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/LICENSE +21 -0
- package/adapters/claude-code/.claude/commands/start.md +38 -10
- package/lib/setup-wizard.mjs +72 -4
- package/package.json +5 -2
- package/scaffold/.context/project.yaml.example +11 -1
- package/scaffold/spec-site/package-lock.json +1522 -0
- package/scripts/lint.mjs +56 -0
- package/scripts/typecheck.mjs +24 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yoonjae Song
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -19,14 +19,14 @@ ARGUMENTS: $ARGUMENTS
|
|
|
19
19
|
|
|
20
20
|
## Execution Steps
|
|
21
21
|
|
|
22
|
-
### -1. Setup
|
|
22
|
+
### -1. Setup + Ambiguity Gate Check ⭐
|
|
23
23
|
|
|
24
24
|
**First**, check whether the `.context/project.yaml` file exists.
|
|
25
25
|
|
|
26
26
|
```
|
|
27
27
|
project.yaml exists?
|
|
28
|
-
├──
|
|
29
|
-
└──
|
|
28
|
+
├── No → Start Setup Wizard
|
|
29
|
+
└── Yes → Evaluate ambiguity gate before normal flow
|
|
30
30
|
```
|
|
31
31
|
|
|
32
32
|
#### Setup Wizard Start
|
|
@@ -54,13 +54,10 @@ If `.context/project.yaml` does not exist, treat this as a first run and start t
|
|
|
54
54
|
- Communication style, work style preferences
|
|
55
55
|
- → Generate `user-context.yaml`
|
|
56
56
|
|
|
57
|
-
4. **Phase 2:
|
|
58
|
-
-
|
|
59
|
-
-
|
|
60
|
-
-
|
|
61
|
-
- What's validated / what needs validation
|
|
62
|
-
- Research unknown areas together via WebSearch
|
|
63
|
-
- → Generate `project.yaml`
|
|
57
|
+
4. **Phase 2: Project Skeleton Setup**
|
|
58
|
+
- Fill minimal project defaults and metadata gate fields
|
|
59
|
+
- Keep deep interview for ambiguity-gate phase
|
|
60
|
+
- → Generate initial `project.yaml`
|
|
64
61
|
|
|
65
62
|
5. **Phase 3: Sensitive Information Guidance** (🎩 Oscar)
|
|
66
63
|
- Suggest creating `.secrets.yaml` template
|
|
@@ -76,6 +73,37 @@ Generated files:
|
|
|
76
73
|
What would you like to start working on?
|
|
77
74
|
```
|
|
78
75
|
|
|
76
|
+
#### Ambiguity Gate (mandatory before normal `/start`)
|
|
77
|
+
|
|
78
|
+
If `.context/project.yaml` exists, read `_meta` and evaluate:
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
meta = _meta ?? {};
|
|
82
|
+
needsDeepInterview = meta.needs_deep_interview === true;
|
|
83
|
+
ambiguityScoreRaw = meta.ambiguity_score ?? (needsDeepInterview ? 1 : 0);
|
|
84
|
+
ambiguityScoreParsed = Number(ambiguityScoreRaw);
|
|
85
|
+
ambiguityScore = Number.isFinite(ambiguityScoreParsed) ? ambiguityScoreParsed : 1;
|
|
86
|
+
|
|
87
|
+
if (needsDeepInterview || ambiguityScore >= 0.6) {
|
|
88
|
+
// Gate is open: run deep interview first
|
|
89
|
+
} else {
|
|
90
|
+
// Gate is closed: proceed to step 0
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
If `_meta` is missing or malformed, treat it as **gate open** (safe default).
|
|
95
|
+
If gate is open, **do not proceed to step 0/session selection yet**.
|
|
96
|
+
Run the deep project interview first (🎯 Simon-led), then update `.context/project.yaml`:
|
|
97
|
+
|
|
98
|
+
```yaml
|
|
99
|
+
_meta:
|
|
100
|
+
needs_deep_interview: false
|
|
101
|
+
ambiguity_score: <updated numeric score>
|
|
102
|
+
last_interview: "<ISO8601>"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Only after this metadata update should `/start` continue with normal session flow.
|
|
106
|
+
|
|
79
107
|
---
|
|
80
108
|
|
|
81
109
|
### 0. Load Secret Variables (Required)
|
package/lib/setup-wizard.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Interactive setup wizard — terminal interview → project.yaml + related files.
|
|
3
3
|
*
|
|
4
4
|
* Collects only essential config. AI deep interview is deferred to Claude Code
|
|
5
|
-
* via `_meta.needs_deep_interview
|
|
5
|
+
* via metadata gate fields (`_meta.needs_deep_interview`, `_meta.ambiguity_score`).
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { readdir, readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
@@ -16,6 +16,7 @@ import { parse as parseYaml, stringify as stringifyYaml } from './yaml-lite.mjs'
|
|
|
16
16
|
* @param {string} targetDir - Project root (scaffold already copied)
|
|
17
17
|
* @param {object} [opts]
|
|
18
18
|
* @param {import('node:readline/promises').Interface} [opts.rl] - Inject readline for testing
|
|
19
|
+
* @param {string|null} [opts.platform] - Optional adapter/platform name
|
|
19
20
|
* @returns {Promise<void>}
|
|
20
21
|
*/
|
|
21
22
|
export async function runSetupWizard(targetDir, opts = {}) {
|
|
@@ -232,14 +233,76 @@ async function collectObjectList(rl, question) {
|
|
|
232
233
|
return items;
|
|
233
234
|
}
|
|
234
235
|
|
|
236
|
+
// ── Ambiguity score ─────────────────────────────────────
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Calculate ambiguity score from project.yaml content.
|
|
240
|
+
* Returns 0.0 (clear) to 1.0 (high ambiguity).
|
|
241
|
+
*
|
|
242
|
+
* Note: fresh setup defaults are usually high ambiguity because deep-interview
|
|
243
|
+
* fields are empty, but this may be below 1.0 if users fill optional basics
|
|
244
|
+
* (for example `project.tagline`) during setup.
|
|
245
|
+
*
|
|
246
|
+
* @param {object} yaml
|
|
247
|
+
* @returns {number}
|
|
248
|
+
*/
|
|
249
|
+
export function calculateAmbiguityScore(yaml) {
|
|
250
|
+
const root = (yaml && typeof yaml === 'object') ? yaml : {};
|
|
251
|
+
|
|
252
|
+
const checks = [
|
|
253
|
+
// Basic clarity
|
|
254
|
+
{ weight: 0.1, filled: !!root.project?.tagline },
|
|
255
|
+
|
|
256
|
+
// Problem & market
|
|
257
|
+
{ weight: 0.15, filled: !!root.problem?.core },
|
|
258
|
+
{ weight: 0.1, filled: !!root.problem?.target },
|
|
259
|
+
{ weight: 0.05, filled: root.problem?.alternatives?.length > 0 },
|
|
260
|
+
{ weight: 0.05, filled: !!root.problem?.timing },
|
|
261
|
+
|
|
262
|
+
// Solution
|
|
263
|
+
{ weight: 0.15, filled: !!root.solution?.approach },
|
|
264
|
+
{ weight: 0.08, filled: !!root.solution?.differentiation },
|
|
265
|
+
{ weight: 0.05, filled: root.solution?.outcome?.length > 0 },
|
|
266
|
+
|
|
267
|
+
// Current state
|
|
268
|
+
{ weight: 0.1, filled: !!root.current_state?.stage },
|
|
269
|
+
{ weight: 0.07, filled: !!root.current_state?.focus },
|
|
270
|
+
{ weight: 0.05, filled: !!root.current_state?.next_milestone },
|
|
271
|
+
|
|
272
|
+
// Validation
|
|
273
|
+
{ weight: 0.05, filled: root.validation?.confirmed?.length > 0 },
|
|
274
|
+
];
|
|
275
|
+
|
|
276
|
+
let totalWeight = 0;
|
|
277
|
+
let filledWeight = 0;
|
|
278
|
+
|
|
279
|
+
for (const { weight, filled } of checks) {
|
|
280
|
+
totalWeight += weight;
|
|
281
|
+
if (filled) filledWeight += weight;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return Math.round((1 - filledWeight / totalWeight) * 100) / 100;
|
|
285
|
+
}
|
|
286
|
+
|
|
235
287
|
// ── project.yaml builder ────────────────────────────────
|
|
236
288
|
|
|
289
|
+
export const ALL_INTEGRATION_PROVIDERS = [
|
|
290
|
+
'ga4',
|
|
291
|
+
'mixpanel',
|
|
292
|
+
'notion',
|
|
293
|
+
'linear',
|
|
294
|
+
'channel_io',
|
|
295
|
+
'intercom',
|
|
296
|
+
'prod_db',
|
|
297
|
+
'notebooklm',
|
|
298
|
+
'corti',
|
|
299
|
+
];
|
|
300
|
+
|
|
237
301
|
function buildProjectYaml({ projectName, tagline, projectType, domains, devScope, integrations, platform }) {
|
|
238
302
|
// Build the full integrations block with all known providers
|
|
239
|
-
const allProviders = ['ga4', 'mixpanel', 'notion', 'linear', 'channel_io', 'intercom', 'prod_db', 'notebooklm', 'corti'];
|
|
240
303
|
const integrationsBlock = {};
|
|
241
304
|
|
|
242
|
-
for (const id of
|
|
305
|
+
for (const id of ALL_INTEGRATION_PROVIDERS) {
|
|
243
306
|
if (integrations[id]) {
|
|
244
307
|
integrationsBlock[id] = integrations[id];
|
|
245
308
|
} else {
|
|
@@ -247,7 +310,7 @@ function buildProjectYaml({ projectName, tagline, projectType, domains, devScope
|
|
|
247
310
|
}
|
|
248
311
|
}
|
|
249
312
|
|
|
250
|
-
|
|
313
|
+
const yaml = {
|
|
251
314
|
project: {
|
|
252
315
|
name: projectName,
|
|
253
316
|
tagline: tagline || '',
|
|
@@ -298,11 +361,16 @@ function buildProjectYaml({ projectName, tagline, projectType, domains, devScope
|
|
|
298
361
|
created_at: new Date().toISOString(),
|
|
299
362
|
created_by: 'popilot init',
|
|
300
363
|
needs_deep_interview: true,
|
|
364
|
+
ambiguity_score: 0,
|
|
301
365
|
last_interview: null,
|
|
302
366
|
version: '1.0.0',
|
|
303
367
|
...(platform ? { platform } : {}),
|
|
304
368
|
},
|
|
305
369
|
};
|
|
370
|
+
|
|
371
|
+
yaml._meta.ambiguity_score = calculateAmbiguityScore(yaml);
|
|
372
|
+
|
|
373
|
+
return yaml;
|
|
306
374
|
}
|
|
307
375
|
|
|
308
376
|
// ── Helpers ─────────────────────────────────────────────
|
package/package.json
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "popilot",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Multi-agent PO/PM system scaffold for Claude Code",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"popilot": "
|
|
7
|
+
"popilot": "bin/cli.mjs"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
+
"lint": "node scripts/lint.mjs",
|
|
11
|
+
"typecheck": "node scripts/typecheck.mjs",
|
|
10
12
|
"test": "node --test test/*.test.mjs"
|
|
11
13
|
},
|
|
12
14
|
"files": [
|
|
13
15
|
"bin/",
|
|
14
16
|
"lib/",
|
|
17
|
+
"scripts/",
|
|
15
18
|
"scaffold/",
|
|
16
19
|
"adapters/",
|
|
17
20
|
"README.md"
|
|
@@ -63,12 +63,20 @@ operations:
|
|
|
63
63
|
ga4:
|
|
64
64
|
enabled: false
|
|
65
65
|
property_id: "" # {{integrations.ga4.property_id}}
|
|
66
|
+
mixpanel:
|
|
67
|
+
enabled: false
|
|
68
|
+
project_id: ""
|
|
66
69
|
notion:
|
|
67
70
|
enabled: false
|
|
68
71
|
workspace: ""
|
|
69
72
|
daily_page_id: ""
|
|
73
|
+
linear:
|
|
74
|
+
enabled: false
|
|
75
|
+
team_id: ""
|
|
70
76
|
channel_io:
|
|
71
77
|
enabled: false
|
|
78
|
+
intercom:
|
|
79
|
+
enabled: false
|
|
72
80
|
prod_db:
|
|
73
81
|
enabled: false
|
|
74
82
|
mcp_name: "" # {{integrations.prod_db.mcp_name}}
|
|
@@ -97,6 +105,8 @@ operations:
|
|
|
97
105
|
# ============================================================
|
|
98
106
|
_meta:
|
|
99
107
|
created_at: ""
|
|
100
|
-
created_by: "
|
|
108
|
+
created_by: "popilot init"
|
|
109
|
+
needs_deep_interview: true
|
|
110
|
+
ambiguity_score: 1.0
|
|
101
111
|
last_interview: null
|
|
102
112
|
version: "1.0.0"
|