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 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 Check (First Run Detection)
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
- ├── Yes Normal flow (proceed to step 0)
29
- └── No Start Setup Wizard
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: In-depth Project Interview** (🎯 Simon deployed)
58
- - Core problem, target customers, market
59
- - Solution, differentiators, expected outcomes
60
- - Current stage, uncertainties, milestones
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)
@@ -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: true`.
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 allProviders) {
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
- return {
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.0",
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": "./bin/cli.mjs"
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: "Oscar (Setup Wizard)"
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"