create-team-foundry 2.0.0 → 2.1.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.
Files changed (3) hide show
  1. package/README.md +117 -62
  2. package/dist/index.js +508 -14
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -1,120 +1,175 @@
1
1
  # team-foundry
2
2
 
3
- **A Context Engine for product teams.**
3
+ **Make your AI coding outputs align with your product reality.**
4
4
 
5
- team-foundry scaffolds the shared brain your AI coding tools read from outcomes, customers, decisions, quality bar in a repo every team member commits to. Every AI tool, every team member, the same context. Automatically.
5
+ Your AI tool gives better answers when it knows what the product is for, who the customers are, and what quality means. team-foundry puts that context in your shared repo where every AI tool on every teammate's machine reads the same files from the same repo.
6
6
 
7
- ```
7
+ ```bash
8
8
  npx create-team-foundry
9
9
  ```
10
10
 
11
- Answer 4 questions. Files appear in your repo. That's it.
11
+ **Run in your shared repo. Then say: `"Let's set up our team-foundry."`**
12
+ If your repo already has a README and commit history, setup takes about 1 minute — the AI reads your repo and pre-fills the answers. Starting fresh takes 15–25 minutes. Either way, your AI starts using the context immediately.
12
13
 
13
- team-foundry is agent-agnostic by design. The context files are the product. The AI tool reading them is a replaceable component.
14
+ → **[See what it looks like when populated](example/)** — a fully filled-in team-foundry for Clearline, a fictional 8-person B2B SaaS team. Open `example/` in Claude Code or Cursor and ask anything.
14
15
 
15
- **Run this in your shared product/engineering repo** the one the whole team commits to. Running it in a personal repo works for solo use but misses the point for teams.
16
+ **[team-foundry.com](https://team-foundry.com)** · If this helps your team, [a star helps others find it](https://github.com/tomershahar/team-foundry).
16
17
 
17
18
  ---
18
19
 
19
- ## See what populated looks like
20
+ ## The problem
20
21
 
21
- **[example/](example/)** — a fully populated team-foundry for Clearline, a fictional 8-person B2B SaaS team. Clone this repo, open `example/` in Claude Code, and ask the AI anything about the team. This is what your team-foundry can look like after the setup conversation.
22
+ **Before team-foundry:**
23
+ > You ask the AI to help prioritize a sprint. It gives solid generic advice — but doesn't know your north star metric, hasn't seen your open assumptions, and has no idea that your top customer churned last month.
22
24
 
23
- ## What it does
25
+ **After team-foundry:**
26
+ > The AI references your outcomes, flags an assumption that's been untested for 45 days, and notes that two roadmap items haven't been updated since 8 PRs shipped. It offers to draft the fixes. You confirm.
24
27
 
25
- Most teams use AI tools for code. The AI gives better answers when it knows what the product is for, who the customers are, and what quality means to this team.
28
+ That context used to live in someone's head, a Notion page nobody reads, or a wiki 6 months stale. team-foundry puts it in your shared repo where every AI tool reads it every session.
26
29
 
27
- team-foundry puts that context in your shared repo — not in a wiki, not in Notion, not in someone's head. Every team member's AI tool reads the same files every session, automatically.
30
+ ---
28
31
 
29
- It also installs a coach: a set of behaviors that notice when your files go stale, when your roadmap outpaces your assumptions, or when your strategy has drifted from your execution. The coach flags gaps inline while you work. You confirm; it writes.
32
+ ```bash
33
+ npx create-team-foundry
34
+ ```
30
35
 
31
- ## What gets created
36
+ **Try it in 2 minutes.** Answer a few questions. Files appear. Open your AI tool. Done.
32
37
 
33
- **Solo profile (1–3 people):** 7 files. Root instruction file, getting started guide, coach playbook, north star, outcomes, customers, stack.
38
+ ---
34
39
 
35
- **Full profile (4–15 people):** 20 files. Everything above plus strategy, roadmap, assumptions, risks, trio, working agreement, AI practices, quality bar, decisions log, design principles, metrics, glossary, stakeholders.
40
+ ## Set up once. Everyone gets the same context.
36
41
 
37
- Every file has YAML frontmatter (`purpose`, `read_when`, `last_updated`) so the AI knows when to load it and why.
42
+ **No cloud. No sync service. No extra accounts.** team-foundry uses your existing repo as the shared space.
38
43
 
39
- ## The honest note
44
+ **One person sets it up**
45
+ Run `npx create-team-foundry` in your shared repo. The CLI scaffolds a `team-foundry/` folder, generates the right tool file (`CLAUDE.md`, `GEMINI.md`, or `.cursor/rules/`), and commits everything to git.
40
46
 
41
- team-foundry enables the work of building a shared understanding of your product. It doesn't replace it. If your team isn't willing to have the hard conversations about outcomes, customers, and quality — no tool fixes that. What team-foundry does is make those conversations easier to start, and easier to keep current.
47
+ **Everyone else pulls**
48
+ Teammates `git pull`. They now have the same team-foundry files locally. Their Claude Code, Cursor, or Gemini CLI reads from the same files yours does. No installs, no logins, no setup.
42
49
 
43
- ## Shared floor, individual ceiling
50
+ **Updates flow through git**
51
+ When someone updates a file — or the coach drafts an update they confirm — it's a normal git commit. Push, pull, review in PRs. Everyone stays in sync the same way they already sync code.
44
52
 
45
- team-foundry sets a shared baseline of context that every team member's AI tool reads from. Outcomes, customers, decisions, quality bar, glossary — the things everyone should be grounded in.
53
+ ```
54
+ ┌────────────────────────┐
55
+ │ Your shared repo │
56
+ │ (GitHub / GitLab) │
57
+ │ │
58
+ │ team-foundry/ │
59
+ │ ├─ outcomes.md │
60
+ │ ├─ customers.md │
61
+ │ ├─ decisions/ │
62
+ │ └─ ... │
63
+ └───────────┬────────────┘
64
+
65
+ git pull / push (no other sync)
66
+
67
+ ┌─────────────────┼─────────────────┐
68
+ ▼ ▼ ▼
69
+ ┌──────────┐ ┌──────────┐ ┌──────────┐
70
+ │ PM │ │ Engineer │ │ Designer │
71
+ │ Claude │ │ Cursor │ │ Gemini │
72
+ │ Code │ │ │ │ CLI │
73
+ └──────────┘ └──────────┘ └──────────┘
74
+ ```
46
75
 
47
- It does not cap what individuals do above that baseline. A senior PM can add their own prompts, their own discovery framework, their own depth. An engineer can bring their own AI practices. A designer can layer on top.
76
+ *Your repo is the shared space. Git is the sync. team-foundry adds the structure and the coach.*
48
77
 
49
- The floor is shared. The ceiling is individual. That's the point.
78
+ ---
50
79
 
51
- ## What drift looks like
80
+ ## What gets created
52
81
 
53
- The coach catches specific, named patterns. These are the most common:
82
+ **Solo profile (1–3 people):** 7 files, ~1 minute with repo scan / ~15 minutes fresh.
83
+ **Full profile (4–15 people):** 20 files, ~1 minute with repo scan / ~25 minutes fresh.
54
84
 
55
- | Pattern | What it is | Example |
85
+ | Profile | Files | Includes |
56
86
  |---|---|---|
57
- | **Output-as-outcome drift** | An outcome that's actually a shipped feature | `outcomes.md` says "ship the new dashboard" instead of "reduce time-to-insight for SMB analysts" |
58
- | **Assumption fossilization** | A core assumption logged long ago, never revisited, still driving decisions | `assumptions.md` lists "users want faster checkout" dated 94 days ago. Three roadmap items cite it. No one's tested it. |
59
- | **Customer ghost syndrome** | A persona with no direct team contact in 60+ days, while roadmap items still claim to serve them | Enterprise persona last interviewed in February. Three Q2 features built "for enterprise." |
60
- | **Metric ambiguity** | A metric cited across files without an agreed definition | "Active user" in `outcomes.md` and `metrics.md` with no shared definition. PM means weekly; engineer means daily. |
61
- | **Decision amnesia** | A previous ADR rejecting an approach is invisible to a discussion heading the same direction | Q1 ADR rejects microservices migration. Q3 discussion reopens it with no reference to what changed. |
62
- | **Output roadmap disguised as strategy** | `strategy.md` guiding policy is all "yes" — doesn't name what the team is deliberately not pursuing | "We will be the best tool for product teams." No "we will not build X for Y." |
63
- | **Build-trap signal** | An item moves into "Now" with no linked experiment and no validated assumption | "Add collaborative editing" moves to Now. No assumption linked. Last validation: none. |
64
-
65
- ## Strategy in Git
87
+ | Solo | 7 | Root instruction file (CLAUDE.md/GEMINI.md), getting started guide, coach playbook, north star, outcomes, customers, stack |
88
+ | Full | 20 | Everything above + strategy, roadmap, assumptions, risks, trio, working agreement, AI practices, quality bar, decisions log, design principles, metrics, glossary, stakeholders |
89
+ | Full (federated) | 26 | Everything above + per-folder routing files for multi-instance setups |
66
90
 
67
- Putting strategy and product context in a repo might feel wrong. A few things worth knowing:
91
+ Every file has YAML frontmatter (`purpose`, `read_when`, `last_updated`, `owner`) so the AI knows when to load it and why.
68
92
 
69
- - The `.gitignore` template excludes `team-foundry/private/` by default — put sensitive material there
70
- - During setup you choose repo visibility (public / internal / private) and the coach adjusts its language accordingly
71
- - Nothing in the generated files requires sensitive data — personas use first names, metrics use your definitions, decisions record rationale not revenue
93
+ ## Supported tools
72
94
 
73
- If your repo is public and you're concerned, use the solo profile and keep customers.md to job roles rather than names.
95
+ | Tool | File generated |
96
+ |---|---|
97
+ | Claude Code | `CLAUDE.md` |
98
+ | Gemini CLI | `GEMINI.md` |
99
+ | Cursor | `.cursor/rules/team-foundry.mdc` |
100
+ | OpenAI Codex / generic agents | `AGENTS.md` |
101
+ | Both (Claude + Gemini) | `CLAUDE.md` + `GEMINI.md` |
74
102
 
75
103
  ## The coach
76
104
 
77
- The coach lives in `.team-foundry/coach.md` and is loaded on demand to preserve token budget. It runs in three modes:
105
+ After setup, the coach watches your files for drift while you work. It runs in three modes:
78
106
 
79
- - **Inline** — silent by default. If the AI notices a clear gap in a team-foundry file while answering a normal question, it surfaces it in one sentence and moves on. It does not interrupt coding sessions.
80
- - **Explicit** — triggered by "let's do a team-foundry review," runs a full audit across all files
81
- - **Scheduled** — proactive weekly check-in, surfaces top 3 findings
107
+ - **Inline** — silent by default. Surfaces one sentence when a gap is directly relevant to what you're working on. Never interrupts.
108
+ - **Explicit** — `"let's do a team-foundry review"`. Full audit, all files, findings by severity.
109
+ - **Scheduled** — weekly check-in, top 3 findings, offers to draft fixes.
82
110
 
83
- The coach never writes without your confirmation.
111
+ **The coach never writes without your confirmation.**
84
112
 
85
- ### Triggering the coach
113
+ ### What it catches
86
114
 
87
- Say any of these to your AI tool:
115
+ | Pattern | Example |
116
+ |---|---|
117
+ | **Output-as-outcome drift** | `outcomes.md` says "ship the dashboard" instead of "reduce time-to-insight for SMB analysts" |
118
+ | **Assumption fossilization** | Core assumption logged 94 days ago, never retested, still driving three roadmap items |
119
+ | **Customer ghost syndrome** | Enterprise persona last interviewed in February. Three Q2 features built "for enterprise." |
120
+ | **Decision amnesia** | Q1 ADR rejects microservices. Q3 discussion reopens it with no reference to what changed. |
121
+ | **Reality drift** | 8 PRs shipped since `outcomes.md` was last updated. Coach cites the commit messages. |
122
+ | **Build-trap signal** | "Add collaborative editing" moves to Now with no linked assumption and no validation. |
123
+
124
+ Every finding cites the specific file, the specific content, and the evidence. Not "this looks stale."
125
+
126
+ ### Triggering the coach
88
127
 
89
128
  | What to say | What happens |
90
129
  |---|---|
91
- | `"let's do a team-foundry review"` | Full audit — all files checked, findings listed |
92
- | `"coach mode"` | Same as above |
130
+ | `"let's do a team-foundry review"` | Full audit — all files, findings by severity |
93
131
  | `"review our outcomes"` | Targeted review of one file |
94
- | `"what's missing from team-foundry?"` | Lists gaps across all files |
95
- | `"run the weekly team-foundry review"` | Weekly check-in, top 3 issues |
132
+ | `"tell me about feature X"` | Synthesizes status, rationale, customer evidence, open bets |
133
+ | `"run the weekly review"` | Top 3 issues, draft fixes offered |
96
134
 
97
- ### Turning off inline nudges
135
+ ## Status command
98
136
 
99
- If you find the inline nudges too frequent, add this to your `CLAUDE.md` (or `GEMINI.md`):
100
-
101
- ```
102
- ## team-foundry coach
103
- inline-nudges: off
137
+ ```bash
138
+ npx create-team-foundry status
104
139
  ```
105
140
 
106
- The coach will then only run when you explicitly ask for it.
141
+ Health table across all your files: last updated, days since update, PRs shipped since then, owner, health classification (ok / stale / empty / missing). Link integrity checks flag outcomes with no linked assumption, Now items with no validated bet, and metrics referenced but not defined.
107
142
 
108
- ## Requirements
143
+ ---
109
144
 
110
- - Node 18+
111
- - Claude Code, Gemini CLI, or any AI tool that reads files from your repo as context
145
+ ```bash
146
+ npx create-team-foundry
147
+ ```
148
+
149
+ **Run in your shared repo. Then say: `"Let's set up our team-foundry."`**
150
+
151
+ ---
112
152
 
113
153
  ## What's next
114
154
 
115
- team-foundry v1 supports Claude Code and Gemini CLI. Cursor is the highest-priority v2 addition — it has the largest active user base of any AI coding tool and fits the same file-based context model.
155
+ **v2.x**
156
+ - `--json` output for `status` — pipe findings into CI or dashboards
157
+ - `--strict` mode — fail CI when critical drift is detected
158
+ - MCP server — expose team-foundry context as a tool for agents that don't read files natively
159
+
160
+ **v3 (exploring)**
161
+ - Cross-repo federation — one team-foundry for a platform team read by multiple product repos
162
+ - Status webhooks — post weekly drift report to Slack without leaving the terminal
163
+ - Team onboarding flow — guided interview for new team members joining an existing team-foundry
164
+
165
+ Have a use case or feature idea? [Open an issue](https://github.com/tomershahar/team-foundry/issues).
166
+
167
+ ---
168
+
169
+ ## Requirements
116
170
 
117
- v2 shortlist: Cursor support and scheduled coach runs via cron.
171
+ - Node 18+
172
+ - Claude Code, Gemini CLI, Cursor, or any AI tool that reads files from your repo
118
173
 
119
174
  ## Contributing
120
175
 
package/dist/index.js CHANGED
@@ -29,7 +29,7 @@ async function runPrompts() {
29
29
  message: "Team size?",
30
30
  options: [
31
31
  { value: "solo", label: "1\u20133 people (solo profile \u2014 7 files)" },
32
- { value: "full", label: "4\u201315 people (full profile \u2014 19 files)" }
32
+ { value: "full", label: "4\u201315 people (full profile \u2014 20 files)" }
33
33
  ]
34
34
  });
35
35
  cancelIfNeeded(profile);
@@ -57,15 +57,19 @@ async function runPrompts() {
57
57
  const ingestion = await select({
58
58
  message: "Do you have existing docs to ingest?\n (Strategy docs, old roadmaps, customer research \u2014 the interview uses them to pre-populate answers)",
59
59
  options: [
60
- { value: "local", label: "Local folder (exported docs on disk)" },
61
- { value: "mcp", label: "MCP source (Notion, Confluence, Google Drive)" },
62
- { value: "paste", label: "Paste content (we'll create a file for you to fill in)" },
60
+ { value: "repo", label: "Repo signals only (README, package.json, git history, GitHub PRs/issues)" },
61
+ { value: "repo+local", label: "Repo + local docs folder (repo signals + point me at a folder)" },
62
+ { value: "repo+mcp", label: "Repo + MCP source (repo signals + Notion, Confluence, Google Drive)" },
63
+ { value: "repo+paste", label: "Repo + paste content (repo signals + paste docs into paste-content.md)" },
64
+ { value: "local", label: "Local docs folder only (no repo scan)" },
65
+ { value: "mcp", label: "MCP source only (no repo scan)" },
66
+ { value: "paste", label: "Paste content only (no repo scan)" },
63
67
  { value: "skip", label: "Skip (start fresh)" }
64
68
  ]
65
69
  });
66
70
  cancelIfNeeded(ingestion);
67
71
  let ingestionPath;
68
- if (ingestion === "local") {
72
+ if (ingestion === "local" || ingestion === "repo+local") {
69
73
  const rawPath = await text({
70
74
  message: "Path to the folder containing your docs?",
71
75
  placeholder: "./docs or /Users/you/exports",
@@ -227,8 +231,11 @@ function rootClaudeTemplate(ctx) {
227
231
  purpose: Identity, routing map, and coach activation \u2014 read at the start of every session
228
232
  read_when: Every Claude Code session in this repo \u2014 this is the root instruction file
229
233
  last_updated: ${ctx.date}
234
+ owner:
230
235
  ---
231
236
 
237
+ <!-- See AGENTS.md for cross-tool context routing (OpenAI Codex and other agents). -->
238
+
232
239
  # CLAUDE.md
233
240
 
234
241
  This repo uses **team-foundry** \u2014 structured files that give you real team context.
@@ -302,8 +309,11 @@ function rootGeminiTemplate(ctx) {
302
309
  purpose: Identity, routing map, and coach activation \u2014 read at the start of every session
303
310
  read_when: Every Gemini CLI session in this repo \u2014 this is the root instruction file
304
311
  last_updated: ${ctx.date}
312
+ owner:
305
313
  ---
306
314
 
315
+ <!-- See AGENTS.md for cross-tool context routing (OpenAI Codex and other agents). -->
316
+
307
317
  # GEMINI.md
308
318
 
309
319
  This repo uses **team-foundry** \u2014 structured files that give you real team context.
@@ -377,9 +387,12 @@ function rootCursorTemplate(ctx) {
377
387
  purpose: Identity, routing map, and coach activation \u2014 read at the start of every session
378
388
  read_when: Every Cursor session in this repo \u2014 this is the root instruction file
379
389
  last_updated: ${ctx.date}
390
+ owner:
380
391
  alwaysApply: true
381
392
  ---
382
393
 
394
+ <!-- See AGENTS.md for cross-tool context routing (OpenAI Codex and other agents). -->
395
+
383
396
  # team-foundry
384
397
 
385
398
  This repo uses **team-foundry** \u2014 structured files that give you real team context.
@@ -1596,13 +1609,120 @@ Adjustments are soft \u2014 they change when you surface a behavior, not whether
1596
1609
  its protocol. They reset after 30 days or when the team addresses the gap.
1597
1610
 
1598
1611
  ---
1612
+ ${ctx.ingestion?.startsWith("repo") ? `
1613
+ ## Repo auto-ingestion
1614
+
1615
+ **When this runs:** At the start of every onboarding interview when ingestion mode is
1616
+ \`repo\`, \`repo+local\`, \`repo+mcp\`, or \`repo+paste\`. Execute Steps 1\u20135 silently
1617
+ before saying anything to the user.
1618
+
1619
+ ### Source priority
1620
+
1621
+ Read sources in this order. Highest-priority source wins per field \u2014 lower sources
1622
+ only fill gaps the higher ones leave empty.
1623
+
1624
+ | Priority | Source | What it supplies |
1625
+ |---|---|---|
1626
+ | 1 | Existing \`team-foundry/\` files | Everything \u2014 highest priority, re-run aware |
1627
+ | 2 | \`package.json\` / \`pyproject.toml\` / \`Cargo.toml\` | Product name, stack, language, deps |
1628
+ | 3 | \`README.md\` | Product description, what it does, who it's for |
1629
+ | 4 | Git log (last 30 commits) | Contributors, cadence, recent focus |
1630
+ | 5 | ADRs in \`docs/\`, \`decisions/\`, or \`engineering/decisions/\` | Architecture decisions |
1631
+ | 6 | GitHub PRs and issues via \`gh\` CLI (optional) | Current work, open problems |
1632
+ | 7 | Top-level folder structure | Repo layout, monorepo signals |
1633
+
1634
+ ### Step 1 \u2014 Silent read
1635
+
1636
+ Read all available sources silently. Do not stream output during the read.
1637
+
1638
+ **GitHub signals (priority 6):** Run \`gh issue list\` and \`gh pr list\` to read open
1639
+ issues and recent PRs. If \`gh\` is not installed, not authenticated, or does not respond
1640
+ within 10 seconds, fall back to local repo signals only and note
1641
+ "GitHub signals unavailable \u2014 using local repo only" in the summary. Never block on
1642
+ GitHub \u2014 the flow continues regardless.
1643
+
1644
+ **Re-run detection:** Check whether \`team-foundry/\` files already exist and have
1645
+ content beyond gap markers. If yes, treat them as highest-priority source and skip to
1646
+ the re-run flow in Step 2b.
1647
+
1648
+ ### Step 2 \u2014 Pre-fill summary
1649
+
1650
+ Present a single structured summary. Do not ask questions yet.
1651
+
1652
+ > Here's what I found in your repo. Tell me what's wrong and I'll fix it before writing
1653
+ > anything.
1654
+ >
1655
+ > **Product:** [name \u2014 from package.json]
1656
+ > **What it does:** [1\u20132 sentences \u2014 from README, first paragraph]
1657
+ > **Stack:** [language/framework \u2014 from package.json + README]
1658
+ > **Team:** [contributor count from git log \u2014 challenge me]${ctx.profile === "full" ? `
1659
+ > **Recent focus:** [inferred from last 30 commit messages \u2014 challenge me]
1660
+ > **Open assumptions I spotted:** [from open issues / PRs if available]
1661
+ > **Decisions already made:** [from ADRs if found]` : `
1662
+ > **Recent focus:** [inferred from last 30 commit messages \u2014 challenge me]`}
1663
+ >
1664
+ > What's missing or wrong? (say "looks good" to proceed, or correct anything above)
1665
+
1666
+ Per-field source attribution rules:
1667
+ - Fields read verbatim from a file: show \`(from manifest file)\`, \`(from README)\` etc. Use the actual filename (e.g. \`package.json\`, \`pyproject.toml\`, \`Cargo.toml\`) when it is unambiguous.
1668
+ - Fields inferred (e.g. team size estimated from contributor count): append \`\u2014 challenge me\`
1669
+ - Fields where no signal was found: omit the field from the summary
1670
+
1671
+ ### Step 2b \u2014 Re-run flow (only when existing team-foundry/ files found)
1672
+
1673
+ If existing \`team-foundry/\` files are populated beyond gap markers, say instead:
1674
+
1675
+ > You've already run the onboarding interview. Here's what's currently populated.
1676
+ > What's changed since then?
1599
1677
 
1678
+ Show current values from the files. Only update fields the user says have changed.
1679
+ Leave confirmed fields untouched.
1680
+
1681
+ ### Step 3 \u2014 One-message correction
1682
+
1683
+ Wait for the user's response. Apply any corrections they give. Do not re-ask confirmed
1684
+ fields. If the user says "looks good," proceed immediately to Step 4.
1685
+
1686
+ ### Step 4 \u2014 Write files
1687
+
1688
+ Write all team-foundry files using confirmed data. For any field with no signal and
1689
+ no user-provided answer, write a \`<!-- GAP: ... -->\` marker. Follow the
1690
+ conversation-as-update protocol \u2014 show each file draft and wait for confirmation
1691
+ before writing.
1692
+
1693
+ **No silent writes.** Even high-confidence pre-filled answers require explicit
1694
+ confirmation before being written to disk. "Looks good" or "yes" is confirmation.
1695
+ Silence is not.
1696
+
1697
+ ### Step 5 \u2014 Gap nudge
1698
+
1699
+ After writing, surface the top 3 gaps:
1700
+
1701
+ > Here's what still needs filling in \u2014 these are the most important:
1702
+ > 1. [Gap 1 \u2014 file and field]
1703
+ > 2. [Gap 2 \u2014 file and field]
1704
+ > 3. [Gap 3 \u2014 file and field]
1705
+ >
1706
+ > Want to fill any of these in now?
1707
+
1708
+ ### No-signal fallback
1709
+
1710
+ If the repo has no \`README.md\`, no \`package.json\`, and fewer than 5 git commits,
1711
+ say:
1712
+
1713
+ > I couldn't find enough signals in this repo to pre-fill anything.
1714
+ > I'll ask you directly \u2014 this takes about ${timeEstimate}.
1715
+
1716
+ Then proceed with the standard interview sequence below, skipping Steps 1\u20135.
1717
+
1718
+ ---
1719
+ ` : ""}
1600
1720
  ## Onboarding interview
1601
1721
 
1602
1722
  **Triggered by:** The user says "Let's set up our team-foundry," "run the onboarding
1603
1723
  interview," or any close variant. Also triggered on first load if GETTING_STARTED.md
1604
1724
  still exists and the "Who we are" section in the root file is empty.
1605
- ${ctx.ingestion === "mcp" ? `
1725
+ ${ctx.ingestion === "mcp" || ctx.ingestion === "repo+mcp" ? `
1606
1726
  **Existing docs \u2014 MCP source:** The user indicated they have docs in a connected MCP
1607
1727
  source (Notion, Confluence, or Google Drive). Before asking any questions, query their
1608
1728
  connected MCP servers, then follow the shared ingestion reference below.
@@ -1645,7 +1765,7 @@ Apply medium confidence to all content from undated or old docs.
1645
1765
  Then apply Steps 2\u20134 from the **Shared ingestion reference** section below.
1646
1766
  ` : ctx.ingestionPath ? `
1647
1767
  **Existing docs \u2014 local folder:** The user indicated they have docs to ingest at
1648
- \`${ctx.ingestionPath}\`. Before asking any questions, read all files in that folder,
1768
+ \`${ctx.ingestionPath}\`.${ctx.ingestion === "repo+local" ? ` Repo signals have already been processed in the Repo auto-ingestion section above. Now also read this local folder as a supplementary source \u2014 use it to fill gaps the repo signals could not supply.` : ` Before asking any questions, read all files in that folder,`}
1649
1769
  then follow the shared ingestion reference below.
1650
1770
 
1651
1771
  **Step 1 \u2014 Stale doc check.** Before reading content, check each file for dates.
@@ -1655,7 +1775,7 @@ If a file has no date fields, or all dates are older than 6 months, flag it:
1655
1775
  Apply medium confidence to all content from undated or old files.
1656
1776
 
1657
1777
  Then apply Steps 2\u20134 from the **Shared ingestion reference** section below.
1658
- ` : ctx.ingestion === "paste" ? `
1778
+ ` : ctx.ingestion === "paste" || ctx.ingestion === "repo+paste" ? `
1659
1779
  **Existing docs \u2014 paste content:** The user indicated they have docs to share by
1660
1780
  pasting. Before starting the interview, say:
1661
1781
 
@@ -1684,7 +1804,7 @@ Wait for the user to confirm before proceeding.
1684
1804
 
1685
1805
  Then apply Steps 2\u20134 from the **Shared ingestion reference** section below.
1686
1806
  ` : ""}
1687
- ${ctx.ingestionPath || ctx.ingestion === "mcp" || ctx.ingestion === "paste" ? `
1807
+ ${ctx.ingestionPath || ctx.ingestion === "repo+local" || ctx.ingestion === "mcp" || ctx.ingestion === "paste" || ctx.ingestion === "repo+mcp" || ctx.ingestion === "repo+paste" ? `
1688
1808
  ### Shared ingestion reference
1689
1809
 
1690
1810
  **Step 2 \u2014 Map content to files.** Route what you find to the right team-foundry file:
@@ -2104,9 +2224,14 @@ After the last question, do the following:
2104
2224
  2. **List what's still a gap.** Name each empty or partially-filled file and the specific
2105
2225
  missing piece. Don't apologize for the gaps \u2014 state them neutrally.
2106
2226
 
2107
- 3. **Suggest one next action.** The single most valuable thing the team could do to improve
2108
- their team-foundry right now. Usually: fill the most important gap, or schedule a
2109
- customer conversation if customers.md is thin.
2227
+ 3. **Suggest one next action and offer to start it now.** Name the single most valuable
2228
+ gap and immediately offer to fill it:
2229
+ > "Want to start with [most valuable gap]? That's usually the highest-leverage first
2230
+ > fill \u2014 everything else references it."
2231
+ Default priority: customers (shapes all other files) \u2192 outcomes (most time-sensitive)
2232
+ \u2192 north star (grounds the coach) \u2192 stack (engineering context).
2233
+ Wait for the user to say yes or choose a different file. If they say yes, begin the
2234
+ relevant questions immediately without re-running the full interview.
2110
2235
 
2111
2236
  4. **Offer the coach.** End with:
2112
2237
  > "Your team-foundry is set up. You can ask me to review it any time by saying
@@ -3094,7 +3219,77 @@ owner:
3094
3219
  `;
3095
3220
  }
3096
3221
 
3222
+ // src/templates/root-agents.ts
3223
+ function rootAgentsTemplate(ctx) {
3224
+ const isSolo = ctx.profile === "solo";
3225
+ return `---
3226
+ purpose: Entry point for AI agents \u2014 routes to team-foundry context files
3227
+ read_when: always
3228
+ last_updated: ${ctx.date}
3229
+ owner:
3230
+ ---
3231
+
3232
+ # Agents
3233
+
3234
+ This repo uses **team-foundry** \u2014 structured files that give AI tools real team context.
3235
+ Use this file to orient yourself and find the right files before answering.
3236
+
3237
+ ## Project overview
3238
+
3239
+ <!-- GAP: The project overview hasn't been filled in yet. Run the onboarding interview to populate it. -->
3240
+
3241
+ ## Where to find context
3242
+
3243
+ | Topic | Path |
3244
+ |---|---|
3245
+ | Vision and north star metric | \`team-foundry/product/north-star.md\` |
3246
+ | This quarter's outcomes | \`team-foundry/product/outcomes.md\` |
3247
+ | Who our customers are | \`team-foundry/product/customers.md\` |
3248
+ | Tech stack and conventions | \`team-foundry/engineering/stack.md\` |${isSolo ? "" : `
3249
+ | Current roadmap | \`team-foundry/product/now-next-later.md\` |
3250
+ | Strategic logic | \`team-foundry/product/strategy.md\` |
3251
+ | Open assumptions | \`team-foundry/product/assumptions.md\` |
3252
+ | Key risks | \`team-foundry/product/risks.md\` |
3253
+ | Team norms and DoD | \`team-foundry/team/working-agreement.md\` |
3254
+ | How we use AI tools | \`team-foundry/team/ai-practices.md\` |
3255
+ | Quality stance | \`team-foundry/engineering/quality-bar.md\` |
3256
+ | Architecture decisions | \`team-foundry/engineering/decisions/\` |
3257
+ | Design principles | \`team-foundry/design/principles.md\` |
3258
+ | Metric definitions | \`team-foundry/data/metrics.md\` |
3259
+ | Domain terms | \`team-foundry/context/glossary.md\` |
3260
+ | Stakeholders | \`team-foundry/context/stakeholders.md\` |`}
3261
+
3262
+ ## Setup
3263
+
3264
+ See \`team-foundry/engineering/stack.md\` for the tech stack, local setup steps, and environment requirements.
3265
+
3266
+ ## Code conventions
3267
+
3268
+ See \`team-foundry/engineering/stack.md\` for language and framework conventions.${isSolo ? "" : `
3269
+ See \`team-foundry/engineering/quality-bar.md\` for the quality bar, bug triage policy, and definition of done.`}
3270
+
3271
+ ## Areas of caution
3272
+
3273
+ - **Silent edits** \u2014 the team-foundry coach never writes files without the user's explicit confirmation. Do not write to \`team-foundry/\` files without confirmation.
3274
+ - **Private folder** \u2014 \`team-foundry/private/\` is gitignored. Do not read from or write to it.${isSolo ? "" : `
3275
+ - **ADR commitments** \u2014 architecture decisions in \`team-foundry/engineering/decisions/\` represent committed choices. Treat them as constraints unless the user explicitly opens a discussion.`}
3276
+
3277
+ ## Working with the team-foundry coach
3278
+
3279
+ The coach runs in three modes \u2014 inline (always on, surfaces one-sentence nudges), explicit (triggered by "coach mode" or "let's do a team-foundry review"), and scheduled (weekly check-in, top 3 findings). See CLAUDE.md or GEMINI.md for the full trigger phrase table and loading instructions.
3280
+
3281
+ **Draft-then-confirm rule:** the coach always shows a proposed file change and waits for confirmation before writing. Silence is not confirmation.
3282
+ ${isSolo ? "" : `
3283
+ ## Glossary
3284
+
3285
+ See \`team-foundry/context/glossary.md\` for domain terms, acronyms, and known naming inconsistencies between teams.
3286
+ `}`;
3287
+ }
3288
+
3097
3289
  // src/scaffold.ts
3290
+ var ALWAYS_ROOT_ENTRIES = [
3291
+ { relativePath: "AGENTS.md", content: rootAgentsTemplate }
3292
+ ];
3098
3293
  var SOLO_ENTRIES = [
3099
3294
  { relativePath: "GETTING_STARTED.md", content: gettingStartedTemplate },
3100
3295
  { relativePath: ".team-foundry/coach.md", content: coachTemplate },
@@ -3148,6 +3343,7 @@ async function scaffold(options) {
3148
3343
  const { targetDir, profile, tool, repoVisibility, date, ingestionPath, ingestion, federated } = options;
3149
3344
  const ctx = { profile, tool, repoVisibility, date, ingestionPath, ingestion };
3150
3345
  const entries = [
3346
+ ...ALWAYS_ROOT_ENTRIES,
3151
3347
  ...rootEntries(tool),
3152
3348
  ...SOLO_ENTRIES,
3153
3349
  ...profile === "full" ? FULL_ONLY_ENTRIES : [],
@@ -3190,6 +3386,204 @@ async function writeGitignore(targetDir) {
3190
3386
  import fs3 from "fs/promises";
3191
3387
  import path3 from "path";
3192
3388
  import { spawnSync } from "child_process";
3389
+
3390
+ // src/link-checker.ts
3391
+ import { readFile } from "fs/promises";
3392
+ var SEVERITY = {
3393
+ missing: 3,
3394
+ "outcome-metric": 3,
3395
+ "now-assumption": 3,
3396
+ "assumption-outcome": 3,
3397
+ stale: 2,
3398
+ empty: 2
3399
+ };
3400
+ function recencyFactor(prs) {
3401
+ if (prs > 3) return 2;
3402
+ if (prs > 0) return 1;
3403
+ return 0;
3404
+ }
3405
+ function escapeRe(s) {
3406
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3407
+ }
3408
+ function actionForHealth(health, file) {
3409
+ if (health === "missing") return `Run \`npx create-team-foundry\` to scaffold ${file}`;
3410
+ if (health === "empty") return `Fill in ${file} with real content (remove stub placeholder)`;
3411
+ return `Update last_updated in ${file} and review content for accuracy`;
3412
+ }
3413
+ function actionForLink(type, item, file) {
3414
+ if (type === "outcome-metric") return `Define "${item}" in data/metrics.md with formula, source, window, owner`;
3415
+ if (type === "now-assumption") return `Add assumption reference to "${item}" in ${file} or link it in assumptions.md`;
3416
+ return `Add cross-reference between "${item}" and a related outcome or assumption`;
3417
+ }
3418
+ function rankFindings(healthFindings, linkFindings) {
3419
+ const candidates = [];
3420
+ for (const h of healthFindings) {
3421
+ const severity = SEVERITY[h.health] ?? 1;
3422
+ const score = severity * 3 + recencyFactor(h.prs);
3423
+ candidates.push({
3424
+ item: h.file,
3425
+ file: h.file,
3426
+ detail: `File is ${h.health}: ${h.file.replace("team-foundry/", "")}`,
3427
+ score,
3428
+ action: actionForHealth(h.health, h.file)
3429
+ });
3430
+ }
3431
+ for (const l of linkFindings) {
3432
+ const severity = SEVERITY[l.type] ?? 1;
3433
+ const score = severity * 3;
3434
+ candidates.push({
3435
+ item: l.item,
3436
+ file: l.file,
3437
+ detail: l.detail,
3438
+ score,
3439
+ action: actionForLink(l.type, l.item, l.file)
3440
+ });
3441
+ }
3442
+ candidates.sort((a, b) => {
3443
+ if (b.score !== a.score) return b.score - a.score;
3444
+ return a.file.localeCompare(b.file);
3445
+ });
3446
+ return candidates.slice(0, 3);
3447
+ }
3448
+ function extractHeadings(content, section) {
3449
+ if (!content.trim()) return [];
3450
+ let source = content;
3451
+ if (section) {
3452
+ const sectionRe = new RegExp(
3453
+ `(?:^|\\n)###\\s+${escapeRe(section)}\\s*\\n([\\s\\S]*?)(?=\\n###|$)`
3454
+ );
3455
+ const match = source.match(sectionRe);
3456
+ source = match ? match[1] : "";
3457
+ }
3458
+ const headings = [];
3459
+ for (const line of source.split("\n")) {
3460
+ const m = line.match(/^##\s+(.+)$/);
3461
+ if (m) headings.push(m[1].trim());
3462
+ }
3463
+ return headings;
3464
+ }
3465
+ function extractSectionBodies(content) {
3466
+ const result = {};
3467
+ const lines = content.split("\n");
3468
+ let currentHeading = null;
3469
+ const bodyLines = [];
3470
+ for (const line of lines) {
3471
+ const m = line.match(/^##\s+(.+)$/);
3472
+ if (m) {
3473
+ if (currentHeading !== null) result[currentHeading] = bodyLines.join("\n").trim();
3474
+ currentHeading = m[1].trim();
3475
+ bodyLines.length = 0;
3476
+ } else if (currentHeading !== null) {
3477
+ bodyLines.push(line);
3478
+ }
3479
+ }
3480
+ if (currentHeading !== null) result[currentHeading] = bodyLines.join("\n").trim();
3481
+ return result;
3482
+ }
3483
+ function checkOutcomeMetricLinks(outcomesContent, northStarContent, metricsContent) {
3484
+ const defined = new Set(extractHeadings(metricsContent));
3485
+ if (defined.size === 0) return [];
3486
+ const findings = [];
3487
+ const fromOutcomes = extractHeadings(outcomesContent, "Key Metrics");
3488
+ const fromNorthStar = extractHeadings(northStarContent, "Key Metrics");
3489
+ for (const heading of fromOutcomes) {
3490
+ if (!defined.has(heading)) {
3491
+ findings.push({
3492
+ type: "outcome-metric",
3493
+ file: "team-foundry/product/outcomes.md",
3494
+ item: heading,
3495
+ detail: `"${heading}" in outcomes.md#Key Metrics is not defined in data/metrics.md`
3496
+ });
3497
+ }
3498
+ }
3499
+ for (const heading of fromNorthStar) {
3500
+ if (!defined.has(heading)) {
3501
+ findings.push({
3502
+ type: "outcome-metric",
3503
+ file: "team-foundry/product/north-star.md",
3504
+ item: heading,
3505
+ detail: `"${heading}" in north-star.md#Key Metrics is not defined in data/metrics.md`
3506
+ });
3507
+ }
3508
+ }
3509
+ return findings;
3510
+ }
3511
+ function checkNowAssumptionLinks(nowNextLaterContent, assumptionsContent) {
3512
+ if (!nowNextLaterContent.trim()) return [];
3513
+ const assumptionHeadings = extractHeadings(assumptionsContent);
3514
+ if (assumptionHeadings.length === 0) return [];
3515
+ const nowItems = extractHeadings(nowNextLaterContent, "Now");
3516
+ const bodies = extractSectionBodies(nowNextLaterContent);
3517
+ const findings = [];
3518
+ for (const item of nowItems) {
3519
+ const body = bodies[item] ?? "";
3520
+ const referencesAssumption = assumptionHeadings.some((a) => body.includes(a));
3521
+ if (!referencesAssumption) {
3522
+ findings.push({
3523
+ type: "now-assumption",
3524
+ file: "team-foundry/product/now-next-later.md",
3525
+ item,
3526
+ detail: `Now item "${item}" has no linked assumption in product/assumptions.md`
3527
+ });
3528
+ }
3529
+ }
3530
+ return findings;
3531
+ }
3532
+ function checkAssumptionOutcomeReciprocity(assumptionsContent, outcomesContent) {
3533
+ if (!assumptionsContent.trim() || !outcomesContent.trim()) return [];
3534
+ const outcomeHeadings = extractHeadings(outcomesContent);
3535
+ const assumptionHeadings = extractHeadings(assumptionsContent);
3536
+ const assumptionBodies = extractSectionBodies(assumptionsContent);
3537
+ const outcomeBodies = extractSectionBodies(outcomesContent);
3538
+ const findings = [];
3539
+ for (const assumption of assumptionHeadings) {
3540
+ const body = assumptionBodies[assumption] ?? "";
3541
+ if (!outcomeHeadings.some((o) => body.includes(o))) {
3542
+ findings.push({
3543
+ type: "assumption-outcome",
3544
+ file: "team-foundry/product/assumptions.md",
3545
+ item: assumption,
3546
+ detail: `Assumption "${assumption}" does not reference any outcome`
3547
+ });
3548
+ }
3549
+ }
3550
+ for (const outcome of outcomeHeadings) {
3551
+ const body = outcomeBodies[outcome] ?? "";
3552
+ if (!assumptionHeadings.some((a) => body.includes(a))) {
3553
+ findings.push({
3554
+ type: "assumption-outcome",
3555
+ file: "team-foundry/product/outcomes.md",
3556
+ item: outcome,
3557
+ detail: `Outcome "${outcome}" does not reference any assumption`
3558
+ });
3559
+ }
3560
+ }
3561
+ return findings;
3562
+ }
3563
+ async function readOptional(filePath) {
3564
+ try {
3565
+ return await readFile(filePath, "utf-8");
3566
+ } catch {
3567
+ return "";
3568
+ }
3569
+ }
3570
+ async function runLinkChecks(targetDir) {
3571
+ const p = (rel) => `${targetDir}/${rel}`;
3572
+ const [outcomes, northStar, metrics, nowNext, assumptions] = await Promise.all([
3573
+ readOptional(p("team-foundry/product/outcomes.md")),
3574
+ readOptional(p("team-foundry/product/north-star.md")),
3575
+ readOptional(p("team-foundry/data/metrics.md")),
3576
+ readOptional(p("team-foundry/product/now-next-later.md")),
3577
+ readOptional(p("team-foundry/product/assumptions.md"))
3578
+ ]);
3579
+ return [
3580
+ ...checkOutcomeMetricLinks(outcomes, northStar, metrics),
3581
+ ...checkNowAssumptionLinks(nowNext, assumptions),
3582
+ ...checkAssumptionOutcomeReciprocity(assumptions, outcomes)
3583
+ ];
3584
+ }
3585
+
3586
+ // src/status.ts
3193
3587
  var SOLO_FILES = [
3194
3588
  "team-foundry/product/north-star.md",
3195
3589
  "team-foundry/product/outcomes.md",
@@ -3331,6 +3725,51 @@ async function runStatus(targetDir) {
3331
3725
  console.log();
3332
3726
  console.log(` \u2713 ${ok.length} current ~ ${stale.length} stale \u25CB ${empty.length} empty \u2717 ${missing.length} missing
3333
3727
  `);
3728
+ let linkFindings = [];
3729
+ if (isFullProfile) {
3730
+ linkFindings = await runLinkChecks(targetDir);
3731
+ if (linkFindings.length > 0) {
3732
+ console.log(` Link Integrity`);
3733
+ console.log(` ${"\u2500".repeat(60)}`);
3734
+ const byType = {};
3735
+ for (const f of linkFindings) {
3736
+ (byType[f.type] ??= []).push(f);
3737
+ }
3738
+ const typeLabels = {
3739
+ "outcome-metric": "Outcome references undefined metric",
3740
+ "now-assumption": "Now item missing linked assumption",
3741
+ "assumption-outcome": "Assumption/outcome unlinked"
3742
+ };
3743
+ for (const [type, items] of Object.entries(byType)) {
3744
+ console.log(`
3745
+ ! ${typeLabels[type] ?? type}`);
3746
+ for (const item of items) {
3747
+ console.log(` ${item.file.replace("team-foundry/", "")} \u2192 ${item.detail}`);
3748
+ }
3749
+ }
3750
+ console.log();
3751
+ }
3752
+ }
3753
+ const healthForRanking = results.filter((r) => r.health !== "ok").map((r) => ({
3754
+ file: r.relativePath,
3755
+ health: r.health,
3756
+ prs: r.prsSinceUpdate ?? 0
3757
+ }));
3758
+ const top3 = rankFindings(healthForRanking, linkFindings);
3759
+ console.log(` Top 3 Fix Suggestions`);
3760
+ console.log(` ${"\u2500".repeat(60)}`);
3761
+ if (top3.length === 0) {
3762
+ console.log(" No critical drift detected this week.\n");
3763
+ } else {
3764
+ for (let i = 0; i < top3.length; i++) {
3765
+ const s = top3[i];
3766
+ console.log(`
3767
+ ${i + 1}) ${s.detail}`);
3768
+ console.log(` In: ${s.file.replace("team-foundry/", "")}`);
3769
+ console.log(` Action: ${s.action}`);
3770
+ }
3771
+ console.log();
3772
+ }
3334
3773
  if (stale.length > 0) {
3335
3774
  console.log(" Stale files \u2014 why this nudge:\n");
3336
3775
  for (const s of stale) {
@@ -3437,7 +3876,7 @@ async function main() {
3437
3876
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
3438
3877
  await scaffold({ ...answers, targetDir, date });
3439
3878
  await writeGitignore(targetDir);
3440
- if (answers.ingestion === "paste") {
3879
+ if (answers.ingestion === "paste" || answers.ingestion === "repo+paste") {
3441
3880
  const pastePath = path4.join(targetDir, ".team-foundry", "paste-content.md");
3442
3881
  try {
3443
3882
  await fs4.access(pastePath);
@@ -3447,7 +3886,62 @@ async function main() {
3447
3886
  }
3448
3887
  const tool = TOOL_LABEL[answers.tool];
3449
3888
  let ingestionNote;
3450
- if (answers.ingestion === "paste") {
3889
+ if (answers.ingestion === "repo") {
3890
+ ingestionNote = `
3891
+ Next steps:
3892
+
3893
+ 1. cd ${targetDir}
3894
+
3895
+ 2. Open ${tool} and say:
3896
+
3897
+ "Let's set up our team-foundry."
3898
+
3899
+ The AI will read your repo (README, package.json, git history, GitHub
3900
+ PRs/issues) and pre-fill answers before asking questions.
3901
+ `;
3902
+ } else if (answers.ingestion === "repo+local") {
3903
+ ingestionNote = `
3904
+ Next steps:
3905
+
3906
+ 1. cd ${targetDir}
3907
+
3908
+ 2. Open ${tool} and say:
3909
+
3910
+ "Let's set up our team-foundry."
3911
+
3912
+ The AI will read your repo first, then supplement with the docs in
3913
+ ${answers.ingestionPath ?? "[your docs folder]"}.
3914
+ `;
3915
+ } else if (answers.ingestion === "repo+mcp") {
3916
+ ingestionNote = `
3917
+ Next steps:
3918
+
3919
+ 1. In ${tool} settings, connect your MCP server
3920
+ (Notion, Confluence, or Google Drive) if you haven't already.
3921
+
3922
+ 2. cd ${targetDir}
3923
+
3924
+ 3. Open ${tool} and say:
3925
+
3926
+ "Let's set up our team-foundry."
3927
+
3928
+ The AI will read your repo first, then supplement from your MCP source.
3929
+ `;
3930
+ } else if (answers.ingestion === "repo+paste") {
3931
+ ingestionNote = `
3932
+ Next steps:
3933
+
3934
+ 1. Open .team-foundry/paste-content.md and paste in your existing docs
3935
+ (strategy, roadmaps, customer research). Save the file.
3936
+
3937
+ 2. cd ${targetDir}
3938
+
3939
+ 3. Open ${tool} and say:
3940
+
3941
+ "Let's set up our team-foundry. I've added docs to
3942
+ paste-content.md \u2014 use them to supplement the repo scan."
3943
+ `;
3944
+ } else if (answers.ingestion === "paste") {
3451
3945
  ingestionNote = `
3452
3946
  Next steps:
3453
3947
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "create-team-foundry",
3
- "version": "2.0.0",
4
- "description": "Scaffold a team-foundry into any repo — structured context files for Claude Code and Gemini CLI",
3
+ "version": "2.1.1",
4
+ "description": "Scaffold a team-foundry into any repo — structured context files for Claude Code, Gemini CLI, and Cursor",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "bin": {