create-team-foundry 1.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +128 -39
  2. package/dist/index.js +1118 -42
  3. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import fs3 from "fs/promises";
5
- import path3 from "path";
4
+ import fs4 from "fs/promises";
5
+ import path4 from "path";
6
6
  import { outro as outro2, log, confirm } from "@clack/prompts";
7
7
 
8
8
  // src/prompts.ts
@@ -20,7 +20,8 @@ async function runPrompts() {
20
20
  options: [
21
21
  { value: "claude", label: "Claude Code" },
22
22
  { value: "gemini", label: "Gemini CLI" },
23
- { value: "both", label: "Both" }
23
+ { value: "cursor", label: "Cursor" },
24
+ { value: "both", label: "Multiple (Claude Code + Gemini CLI)" }
24
25
  ]
25
26
  });
26
27
  cancelIfNeeded(tool);
@@ -28,7 +29,7 @@ async function runPrompts() {
28
29
  message: "Team size?",
29
30
  options: [
30
31
  { value: "solo", label: "1\u20133 people (solo profile \u2014 7 files)" },
31
- { value: "full", label: "4\u201315 people (full profile \u2014 19 files)" }
32
+ { value: "full", label: "4\u201315 people (full profile \u2014 20 files)" }
32
33
  ]
33
34
  });
34
35
  cancelIfNeeded(profile);
@@ -41,18 +42,34 @@ async function runPrompts() {
41
42
  ]
42
43
  });
43
44
  cancelIfNeeded(repoVisibility);
45
+ let federated;
46
+ if (profile === "full") {
47
+ const federatedAnswer = await select({
48
+ message: "Context layout?",
49
+ options: [
50
+ { value: "flat", label: "Flat (one root CLAUDE.md \u2014 simpler, recommended for most teams)" },
51
+ { value: "federated", label: "Federated (CLAUDE.md per folder \u2014 for larger teams, 8+ people)" }
52
+ ]
53
+ });
54
+ cancelIfNeeded(federatedAnswer);
55
+ federated = federatedAnswer === "federated";
56
+ }
44
57
  const ingestion = await select({
45
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)",
46
59
  options: [
47
- { value: "local", label: "Local folder (exported docs on disk)" },
48
- { value: "mcp", label: "MCP source (Notion, Confluence, Google Drive)" },
49
- { 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)" },
50
67
  { value: "skip", label: "Skip (start fresh)" }
51
68
  ]
52
69
  });
53
70
  cancelIfNeeded(ingestion);
54
71
  let ingestionPath;
55
- if (ingestion === "local") {
72
+ if (ingestion === "local" || ingestion === "repo+local") {
56
73
  const rawPath = await text({
57
74
  message: "Path to the folder containing your docs?",
58
75
  placeholder: "./docs or /Users/you/exports",
@@ -68,7 +85,8 @@ async function runPrompts() {
68
85
  profile,
69
86
  repoVisibility,
70
87
  ingestion,
71
- ingestionPath
88
+ ingestionPath,
89
+ federated
72
90
  };
73
91
  }
74
92
 
@@ -76,14 +94,148 @@ async function runPrompts() {
76
94
  import fs from "fs/promises";
77
95
  import path from "path";
78
96
 
97
+ // src/templates/federated/product-claude.ts
98
+ function federatedProductTemplate(ctx) {
99
+ return `---
100
+ purpose: Routing context for the product/ folder \u2014 read before answering product questions
101
+ read_when: Any question about outcomes, customers, roadmap, strategy, assumptions, or risks
102
+ last_updated: ${ctx.date}
103
+ ---
104
+
105
+ # Product context
106
+
107
+ This folder contains the team's product thinking. Read the relevant file before answering.
108
+
109
+ | Topic | File |
110
+ |---|---|
111
+ | Vision and north star metric | \`north-star.md\` |
112
+ | This quarter's outcomes | \`outcomes.md\` |
113
+ | Named customers and personas | \`customers.md\` |
114
+ | What we're building now / next / later | \`now-next-later.md\` |
115
+ | Strategic logic and guiding policy | \`strategy.md\` |
116
+ | Open assumptions and untested bets | \`assumptions.md\` |
117
+ | Key product risks | \`risks.md\` |
118
+
119
+ Files with recent \`last_updated\` dates are more reliable than older ones. If asked about freshness, note if a file hasn't been updated in 60+ days.
120
+ `;
121
+ }
122
+
123
+ // src/templates/federated/team-claude.ts
124
+ function federatedTeamTemplate(ctx) {
125
+ return `---
126
+ purpose: Routing context for the team/ folder \u2014 read before answering team questions
127
+ read_when: Any question about who owns decisions, how the team works, or AI tool usage
128
+ last_updated: ${ctx.date}
129
+ ---
130
+
131
+ # Team context
132
+
133
+ This folder describes how the product trio works and how the team operates.
134
+
135
+ | Topic | File |
136
+ |---|---|
137
+ | Who's on the trio and how decisions are made | \`trio.md\` |
138
+ | Ceremonies, DoD, working norms | \`working-agreement.md\` |
139
+ | How this team uses AI tools | \`ai-practices.md\` |
140
+
141
+ When answering questions about ownership or process, read \`trio.md\` first. It reflects how the team actually operates, not an org chart.
142
+ `;
143
+ }
144
+
145
+ // src/templates/federated/engineering-claude.ts
146
+ function federatedEngineeringTemplate(ctx) {
147
+ return `---
148
+ purpose: Routing context for the engineering/ folder \u2014 read before answering technical questions
149
+ read_when: Any question about the stack, conventions, tech debt, or past architecture decisions
150
+ last_updated: ${ctx.date}
151
+ ---
152
+
153
+ # Engineering context
154
+
155
+ This folder contains the team's technical context. Read before writing or reviewing code.
156
+
157
+ | Topic | File |
158
+ |---|---|
159
+ | Stack, conventions, deployment | \`stack.md\` |
160
+ | Quality stance and tech debt policy | \`quality-bar.md\` |
161
+ | Past architecture decisions | \`decisions/\` |
162
+
163
+ Always read \`stack.md\` before writing code for this repo \u2014 it contains the conventions and the non-obvious choices that would surprise an outsider. Read the relevant ADR in \`decisions/\` before making a technical decision that's already been decided.
164
+ `;
165
+ }
166
+
167
+ // src/templates/federated/design-claude.ts
168
+ function federatedDesignTemplate(ctx) {
169
+ return `---
170
+ purpose: Routing context for the design/ folder \u2014 read before answering design questions
171
+ read_when: Any question about UI copy, visual decisions, accessibility, or tone of voice
172
+ last_updated: ${ctx.date}
173
+ ---
174
+
175
+ # Design context
176
+
177
+ This folder contains the team's design principles and standards.
178
+
179
+ | Topic | File |
180
+ |---|---|
181
+ | Design principles, tone of voice, accessibility | \`principles.md\` |
182
+
183
+ Read \`principles.md\` before writing UI copy, reviewing designs, or making visual decisions. The tone and accessibility standards here apply to all customer-facing work.
184
+ `;
185
+ }
186
+
187
+ // src/templates/federated/data-claude.ts
188
+ function federatedDataTemplate(ctx) {
189
+ return `---
190
+ purpose: Routing context for the data/ folder \u2014 read before discussing metrics
191
+ read_when: Any question about metrics, success criteria, or what numbers mean
192
+ last_updated: ${ctx.date}
193
+ ---
194
+
195
+ # Data context
196
+
197
+ This folder contains metric definitions for this team.
198
+
199
+ | Topic | File |
200
+ |---|---|
201
+ | Metric definitions, ownership, data sources | \`metrics.md\` |
202
+
203
+ Read \`metrics.md\` before citing any metric or writing success criteria in a spec. If a metric isn't defined here, it doesn't have an agreed definition \u2014 flag that rather than inventing one.
204
+ `;
205
+ }
206
+
207
+ // src/templates/federated/context-claude.ts
208
+ function federatedContextTemplate(ctx) {
209
+ return `---
210
+ purpose: Routing context for the context/ folder \u2014 read before using domain terminology
211
+ read_when: Any question involving domain terms, acronyms, or stakeholder communication
212
+ last_updated: ${ctx.date}
213
+ ---
214
+
215
+ # Domain context
216
+
217
+ This folder contains the team's shared vocabulary and stakeholder map.
218
+
219
+ | Topic | File |
220
+ |---|---|
221
+ | Domain terms and acronyms | \`glossary.md\` |
222
+ | Stakeholders and what they care about | \`stakeholders.md\` |
223
+
224
+ Read \`glossary.md\` when writing specs, copy, or anything using domain-specific terms \u2014 especially terms flagged as "used inconsistently." Read \`stakeholders.md\` before drafting any communication intended for a specific stakeholder.
225
+ `;
226
+ }
227
+
79
228
  // src/templates/root-claude.ts
80
229
  function rootClaudeTemplate(ctx) {
81
230
  return `---
82
231
  purpose: Identity, routing map, and coach activation \u2014 read at the start of every session
83
232
  read_when: Every Claude Code session in this repo \u2014 this is the root instruction file
84
233
  last_updated: ${ctx.date}
234
+ owner:
85
235
  ---
86
236
 
237
+ <!-- See AGENTS.md for cross-tool context routing (OpenAI Codex and other agents). -->
238
+
87
239
  # CLAUDE.md
88
240
 
89
241
  This repo uses **team-foundry** \u2014 structured files that give you real team context.
@@ -157,8 +309,11 @@ function rootGeminiTemplate(ctx) {
157
309
  purpose: Identity, routing map, and coach activation \u2014 read at the start of every session
158
310
  read_when: Every Gemini CLI session in this repo \u2014 this is the root instruction file
159
311
  last_updated: ${ctx.date}
312
+ owner:
160
313
  ---
161
314
 
315
+ <!-- See AGENTS.md for cross-tool context routing (OpenAI Codex and other agents). -->
316
+
162
317
  # GEMINI.md
163
318
 
164
319
  This repo uses **team-foundry** \u2014 structured files that give you real team context.
@@ -226,6 +381,85 @@ it notices something relevant to your current work. You can also invoke it direc
226
381
  `;
227
382
  }
228
383
 
384
+ // src/templates/root-cursor.ts
385
+ function rootCursorTemplate(ctx) {
386
+ return `---
387
+ purpose: Identity, routing map, and coach activation \u2014 read at the start of every session
388
+ read_when: Every Cursor session in this repo \u2014 this is the root instruction file
389
+ last_updated: ${ctx.date}
390
+ owner:
391
+ alwaysApply: true
392
+ ---
393
+
394
+ <!-- See AGENTS.md for cross-tool context routing (OpenAI Codex and other agents). -->
395
+
396
+ # team-foundry
397
+
398
+ This repo uses **team-foundry** \u2014 structured files that give you real team context.
399
+ Read this file first. It tells you where to find everything and how to activate the coach.
400
+
401
+ <!-- GAP: The onboarding interview hasn't run yet.
402
+ When the user says "Let's set up our team-foundry" or similar, do this:
403
+ 1. Read GETTING_STARTED.md for context on what to expect
404
+ 2. Load .team-foundry/coach.md \u2014 it contains the interview sequence
405
+ 3. Begin the onboarding interview as described there
406
+ Do not improvise the interview. Follow the sequence in coach.md. -->
407
+
408
+ ## Who we are
409
+
410
+ <!-- Filled in during the onboarding interview. -->
411
+
412
+ ## Routing map
413
+
414
+ When the user's question relates to any of the following, read the corresponding file
415
+ before answering. Files with recent \`last_updated\` dates are more reliable than older ones.
416
+
417
+ | Topic | File |
418
+ |---|---|
419
+ | Who we are / what this product does | team-foundry.mdc \u2014 "Who we are" section (this file) |
420
+ | What success looks like / vision | \`team-foundry/product/north-star.md\` |
421
+ | What we're working toward this quarter | \`team-foundry/product/outcomes.md\` |
422
+ | Who our customers are | \`team-foundry/product/customers.md\` |
423
+ | What we're building now / next / later | \`team-foundry/product/now-next-later.md\` |
424
+ | Strategic logic and guiding policy | \`team-foundry/product/strategy.md\` |
425
+ | Open assumptions and untested bets | \`team-foundry/product/assumptions.md\` |
426
+ | Key product risks | \`team-foundry/product/risks.md\` |
427
+ | How the product trio works | \`team-foundry/team/trio.md\` |
428
+ | Team norms, DoD, ceremonies | \`team-foundry/team/working-agreement.md\` |
429
+ | How we use AI tools | \`team-foundry/team/ai-practices.md\` |
430
+ | Tech stack and conventions | \`team-foundry/engineering/stack.md\` |
431
+ | Quality stance and tech debt policy | \`team-foundry/engineering/quality-bar.md\` |
432
+ | Past architecture decisions | \`team-foundry/engineering/decisions/\` |
433
+ | Design principles and tone | \`team-foundry/design/principles.md\` |
434
+ | Metric definitions | \`team-foundry/data/metrics.md\` |
435
+ | Domain terms and acronyms | \`team-foundry/context/glossary.md\` |
436
+ | Stakeholders and what they care about | \`team-foundry/context/stakeholders.md\` |
437
+
438
+ ## Coach
439
+
440
+ The team-foundry coach keeps these files honest over time. It runs automatically when
441
+ it notices something relevant to your current work. You can also invoke it directly:
442
+
443
+ | What to say | What happens |
444
+ |---|---|
445
+ | "Let's set up our team-foundry" | Runs the onboarding interview (first time only) |
446
+ | "let's do a team-foundry review" | Full audit \u2014 all files checked, findings listed |
447
+ | "coach mode" | Same as above |
448
+ | "review our [outcomes / customers / stack / etc.]" | Targeted review of one file |
449
+ | "what's missing from team-foundry?" | Lists gaps across all files |
450
+ | "run the weekly team-foundry review" | Weekly check-in, top 3 issues surfaced |
451
+
452
+ <!-- AI instructions:
453
+ - Normal coding sessions: do NOT load coach.md. Use the routing map above to load
454
+ specific files only when directly relevant to the user's question.
455
+ - Explicit mode / Scheduled mode / onboarding: load .team-foundry/coach.md in full
456
+ before activating any mode. Triggered only by the phrases in the table above.
457
+ - Inline mode nudges: if you notice a clear gap in a team-foundry file while answering
458
+ a normal question, surface it in one sentence \u2014 without loading the full coach.md.
459
+ Keep it brief and non-blocking. Do not coach unprompted on back-to-back messages. -->
460
+ `;
461
+ }
462
+
229
463
  // src/templates/getting-started.ts
230
464
  function gettingStartedTemplate(ctx) {
231
465
  const toolName = ctx.tool === "gemini" ? "Gemini CLI" : ctx.tool === "both" ? "Claude Code or Gemini CLI" : "Claude Code";
@@ -315,8 +549,14 @@ You can delete this file once the onboarding interview is complete.
315
549
  // src/templates/coach.ts
316
550
  function coachTemplate(ctx) {
317
551
  const isSolo = ctx.profile === "solo";
318
- const questionCount = isSolo ? "10" : "18\u201325";
552
+ const questionCount = isSolo ? "9" : "18\u201325";
319
553
  const timeEstimate = isSolo ? "15\u201320 minutes" : "25\u201335 minutes";
554
+ const featureQueryFullSteps = isSolo ? "" : `3. Read \`team-foundry/product/now-next-later.md\` \u2014 find the feature's current status (Now / Next / Later / shipped)
555
+ 4. Read \`team-foundry/product/assumptions.md\` \u2014 find any assumptions linked to this feature
556
+ 5. Read \`team-foundry/engineering/decisions/\` \u2014 find any ADRs related to how it's being built
557
+ `;
558
+ const featureQuerySynthesis = isSolo ? "why it's being built (outcome + customer evidence)." : "why it's being built (outcome + customer evidence), current status, open bets (assumptions), and any relevant technical decisions.";
559
+ const featureQueryIndexNote = isSolo ? "Your solo profile covers outcomes, customers, north-star, and stack \u2014 feature queries draw from those." : "For full profile teams, this covers status, rationale, customer evidence, open bets, and decisions.";
320
560
  return `---
321
561
  purpose: Full coach playbook \u2014 loaded on demand to preserve token budget
322
562
  read_when: When the user triggers coach mode (explicit, inline, scheduled review, or onboarding interview)
@@ -336,6 +576,67 @@ You are a mirror, not a template pack. The files in this repo are the team's own
336
576
  thinking. Your role is to reflect it back to them accurately, including the parts
337
577
  that have gone stale or were never written down.
338
578
 
579
+ ## Reality observation
580
+
581
+ This is the authoritative protocol for reading git activity. It supersedes any git-reading
582
+ instructions elsewhere in this file (including the "Why this nudge?" block and Behavior 5).
583
+ Those sections define what to surface \u2014 this section defines how to gather the evidence.
584
+
585
+ **When to run:**
586
+ - **Explicit and scheduled modes:** Run all four steps before any behavior fires.
587
+ - **Inline mode:** Run Steps 1\u20132 only when an inline trigger fires, scoped to the specific
588
+ file being evaluated. Do not run a full git audit on every inline response \u2014 that
589
+ contradicts the token-budget framing of this file.
590
+
591
+ **Step 1 \u2014 Read recent commits.**
592
+
593
+ \`\`\`
594
+ git log --oneline --since="30 days ago"
595
+ \`\`\`
596
+
597
+ This returns all commits regardless of merge strategy. Extract:
598
+ - Total commit count in the window
599
+ - Commit messages that describe shipped features, decisions, or major changes
600
+
601
+ **Merge strategy note:** \`git log --merges\` returns zero results on repos that use
602
+ squash-merge or rebase-merge (GitHub's common defaults). Do not use it as the primary
603
+ activity signal. Instead, use total commit count from the regular log. If commit volume
604
+ is high (>10 commits) but \`--merges\` returns zero, conclude "squash or rebase merge
605
+ strategy \u2014 using commit messages as activity proxy" and proceed accordingly.
606
+
607
+ **Step 2 \u2014 Check \`last_updated\` dates in team-foundry files.**
608
+
609
+ For each file being evaluated, note its \`last_updated\` frontmatter date. Calculate:
610
+ - Days between \`last_updated\` and today
611
+ - Commits since that date: \`git log --oneline --since="<last_updated date>"\`
612
+ - If the log returns no results, treat activity as zero and skip the high-activity threshold.
613
+
614
+ **Step 3 \u2014 Build the observed reality summary (internal, not shown to user).**
615
+
616
+ Synthesize into a mental model carried through the rest of the session:
617
+ - What shipped recently (from commit messages)
618
+ - Which team-foundry files haven't been updated since shipping started
619
+ - Activity level: high (>3 commits since last update) or low (\u22643 commits since last update).
620
+ High activity means push harder on drift. Low activity means lighter touch.
621
+
622
+ **Step 4 \u2014 Use this in every finding.**
623
+
624
+ When surfacing drift, always cite the observed reality explicitly. This is the single format
625
+ for "Why this nudge?" across all behaviors:
626
+
627
+ > "[N] commits have merged since outcomes.md was last updated ([X] days ago). Based on
628
+ > recent commit messages \u2014 [list 2-3 relevant ones] \u2014 it looks like [what changed].
629
+ > outcomes.md doesn't reflect this yet."
630
+
631
+ **Fallback when git is unavailable:** If the tool doesn't have shell access, fall back to
632
+ \`last_updated\` dates only and note: "I don't have access to git history \u2014 using
633
+ \`last_updated\` dates as the staleness signal."
634
+
635
+ **What counts as high activity:** More than 3 commits since the file was last updated.
636
+ This is the single threshold used throughout this file.
637
+
638
+ ---
639
+
339
640
  ## Activation modes
340
641
 
341
642
  You have three activation modes. Read which one applies and behave accordingly.
@@ -524,6 +825,31 @@ order and **name the conflict explicitly** rather than silently picking one:
524
825
  Say: "I see a conflict between [file A] and [file B]. Based on the context priority
525
826
  order, I'm going with [file A] \u2014 but you may want to reconcile these."
526
827
 
828
+ When running any coaching behavior, also load \`.team-foundry/team-lessons.md\` if it exists.
829
+ Apply Active rules from that file alongside built-in behaviors \u2014 they carry equal weight.
830
+
831
+ ## Feature queries
832
+
833
+ **Applies in explicit and scheduled modes.** In inline mode, treat feature questions as
834
+ potential B5 (reality drift) triggers rather than running this full multi-file read.
835
+
836
+ When the user asks about a specific feature \u2014 "tell me about X," "what's the status of Y,"
837
+ "what do we know about Z," "has X shipped?," "where are we on X?," "who owns X?," or any
838
+ close variant \u2014 read the following files in order:
839
+
840
+ 1. Read \`team-foundry/product/outcomes.md\` \u2014 find which outcome this feature supports (the why)
841
+ 2. Read \`team-foundry/product/customers.md\` \u2014 find customer quotes or personas that motivated it
842
+ ${featureQueryFullSteps}
843
+ For each file above: if the file doesn't exist on disk, skip it silently. If it exists
844
+ but doesn't mention the feature, note that gap explicitly \u2014 don't invent connections.
845
+
846
+ Synthesize into a single response: ${featureQuerySynthesis}
847
+
848
+ If the feature doesn't appear in any file, say: "I couldn't find [X] in any team-foundry
849
+ file \u2014 it may be undocumented or tracked under a different name. Want me to help capture it?"
850
+
851
+ The team-foundry files are the index. ${featureQueryIndexNote}
852
+
527
853
  ---
528
854
 
529
855
  ## Behaviors
@@ -537,6 +863,11 @@ For every finding: name it specifically (cite the file and the exact content),
537
863
  explain why it matters in one sentence, offer to draft the fix. Never list a finding
538
864
  without a proposed next step.
539
865
 
866
+ **"Why this nudge?" \u2014 required for every drift finding.** Use the evidence gathered in the
867
+ Reality observation section above. Always be specific \u2014 a team that understands why a nudge
868
+ fired is far more likely to act on it. Never say "this looks stale" without citing the
869
+ commit count, day delta, and (if blank) missing owner.
870
+
540
871
  ---
541
872
 
542
873
  ### Behavior 1: Outputs framed as outcomes
@@ -1094,6 +1425,61 @@ as a deliberate strategy update. If item should be removed: flag only \u2014 do
1094
1425
  the current strategy.md guiding policy explicitly excludes \u2014 or when strategy.md has no
1095
1426
  Guiding Policy filled in.
1096
1427
 
1428
+ ### Behavior 17: Team-specific lesson capture
1429
+
1430
+ **Severity:** Informational \u2014 surfaced as an offer, never a blocker.
1431
+
1432
+ **Trigger condition:** The user's message contains a recurring-pattern signal:
1433
+ - "we keep doing X"
1434
+ - "this is the third time we've had this problem"
1435
+ - "we always confuse Y with Z"
1436
+ - "every time we [situation], we [result]"
1437
+ - Any close variant signaling a pattern the team has noticed about itself.
1438
+
1439
+ **What to say:**
1440
+ > "Sounds like a recurring pattern. Want me to add a coaching rule to \`.team-foundry/team-lessons.md\`
1441
+ > so I watch for this on future reviews?"
1442
+
1443
+ **If the user confirms:**
1444
+ 1. Draft the rule in this format:
1445
+ \`- [date] [concise rule] \u2014 [brief context]\`
1446
+ 2. Ask: "Does this capture it, or want to edit the wording?"
1447
+ 3. After confirmation, write it to the Active rules section of \`.team-foundry/team-lessons.md\`.
1448
+ If the file doesn't exist, create it with this structure:
1449
+
1450
+ \`\`\`markdown
1451
+ ---
1452
+ purpose: Team-specific coaching rules learned from this team's patterns
1453
+ read_when: Coach runs any coaching behavior
1454
+ last_updated: [date]
1455
+ ---
1456
+
1457
+ # Team lessons
1458
+
1459
+ Rules this specific team has accumulated for their coach.
1460
+ Added when the team flags a recurring issue they want the coach to watch for.
1461
+
1462
+ ## Active rules
1463
+
1464
+ - [date] [rule] \u2014 [context]
1465
+
1466
+ ## Retired rules
1467
+
1468
+ <!-- Move rules here when no longer relevant, with the date retired. -->
1469
+ \`\`\`
1470
+
1471
+ **Rule retirement:** When a team says a rule is no longer relevant ("we fixed that," "we changed process"),
1472
+ offer to retire it: move it from Active rules to Retired rules with today's date prepended.
1473
+
1474
+ **Loading instruction:** When running any coaching behavior (inline, explicit, or scheduled),
1475
+ check if \`.team-foundry/team-lessons.md\` exists. If it does, load it and apply Active rules
1476
+ with equal weight to built-in behaviors, scoped to this team's context.
1477
+
1478
+ **What not to do:** Do not proactively suggest adding rules unless the user explicitly names a pattern.
1479
+ This behavior is listener-only \u2014 it waits for the signal, it does not fish for it.
1480
+
1481
+ ---
1482
+
1097
1483
  ## Quarterly retrospective
1098
1484
 
1099
1485
  ### Trigger
@@ -1223,13 +1609,120 @@ Adjustments are soft \u2014 they change when you surface a behavior, not whether
1223
1609
  its protocol. They reset after 30 days or when the team addresses the gap.
1224
1610
 
1225
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?
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
1226
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
+ ` : ""}
1227
1720
  ## Onboarding interview
1228
1721
 
1229
1722
  **Triggered by:** The user says "Let's set up our team-foundry," "run the onboarding
1230
1723
  interview," or any close variant. Also triggered on first load if GETTING_STARTED.md
1231
1724
  still exists and the "Who we are" section in the root file is empty.
1232
- ${ctx.ingestion === "mcp" ? `
1725
+ ${ctx.ingestion === "mcp" || ctx.ingestion === "repo+mcp" ? `
1233
1726
  **Existing docs \u2014 MCP source:** The user indicated they have docs in a connected MCP
1234
1727
  source (Notion, Confluence, or Google Drive). Before asking any questions, query their
1235
1728
  connected MCP servers, then follow the shared ingestion reference below.
@@ -1272,7 +1765,7 @@ Apply medium confidence to all content from undated or old docs.
1272
1765
  Then apply Steps 2\u20134 from the **Shared ingestion reference** section below.
1273
1766
  ` : ctx.ingestionPath ? `
1274
1767
  **Existing docs \u2014 local folder:** The user indicated they have docs to ingest at
1275
- \`${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,`}
1276
1769
  then follow the shared ingestion reference below.
1277
1770
 
1278
1771
  **Step 1 \u2014 Stale doc check.** Before reading content, check each file for dates.
@@ -1282,7 +1775,7 @@ If a file has no date fields, or all dates are older than 6 months, flag it:
1282
1775
  Apply medium confidence to all content from undated or old files.
1283
1776
 
1284
1777
  Then apply Steps 2\u20134 from the **Shared ingestion reference** section below.
1285
- ` : ctx.ingestion === "paste" ? `
1778
+ ` : ctx.ingestion === "paste" || ctx.ingestion === "repo+paste" ? `
1286
1779
  **Existing docs \u2014 paste content:** The user indicated they have docs to share by
1287
1780
  pasting. Before starting the interview, say:
1288
1781
 
@@ -1311,7 +1804,7 @@ Wait for the user to confirm before proceeding.
1311
1804
 
1312
1805
  Then apply Steps 2\u20134 from the **Shared ingestion reference** section below.
1313
1806
  ` : ""}
1314
- ${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" ? `
1315
1808
  ### Shared ingestion reference
1316
1809
 
1317
1810
  **Step 2 \u2014 Map content to files.** Route what you find to the right team-foundry file:
@@ -1491,11 +1984,11 @@ Example answers:
1491
1984
 
1492
1985
  ---
1493
1986
 
1494
- ### Theme 3: Measurement
1987
+ ${isSolo ? "" : `### Theme 3: Measurement
1495
1988
 
1496
1989
  *Files written: data/metrics.md*
1497
1990
 
1498
- **Q${isSolo ? "5" : "7"}. What are the 3\u20135 numbers you actually look at to know if the product is healthy?**
1991
+ **Q7. What are the 3\u20135 numbers you actually look at to know if the product is healthy?**
1499
1992
  *Why it matters: data/metrics.md is read whenever the AI is asked about product performance.
1500
1993
  Undefined metrics cause disagreements \u2014 two people reading the same number and reaching different conclusions.*
1501
1994
 
@@ -1506,7 +1999,7 @@ Example answers:
1506
1999
  - "Listing-to-sale rate \u2014 % of active listings that get bought within 30 days, from our DB."
1507
2000
  - "P1 bug count \u2014 open bugs tagged P1 in Linear, reviewed Monday mornings."
1508
2001
 
1509
- *After the answer: write each metric as a definition block to data/metrics.md.*
2002
+ *After the answer: write each metric as a definition block to data/metrics.md.*`}
1510
2003
 
1511
2004
  ---
1512
2005
 
@@ -1514,7 +2007,7 @@ Example answers:
1514
2007
 
1515
2008
  *Files written: product/customers.md*
1516
2009
 
1517
- **Q${isSolo ? "6" : "8"}. Name three customers you've spoken to directly.**
2010
+ **Q${isSolo ? "5" : "8"}. Name three customers you've spoken to directly.**
1518
2011
  *Why it matters: customers.md is read whenever the AI helps with prioritization, specs,
1519
2012
  or discovery. Generic personas don't resolve real disagreements. Named customers do.*
1520
2013
 
@@ -1576,7 +2069,7 @@ Example answers:
1576
2069
 
1577
2070
  *After the answer: write to engineering/quality-bar.md (tech debt stance).*`}
1578
2071
 
1579
- **Q${isSolo ? "7" : "12"}. What does "shipped" mean on your team?**
2072
+ **Q${isSolo ? "6" : "12"}. What does "shipped" mean on your team?**
1580
2073
  *Why it matters: misaligned definitions of done cause the most common sprint friction.
1581
2074
  Writing it down means the argument happens once, not every week.*
1582
2075
 
@@ -1658,7 +2151,7 @@ Example answers:
1658
2151
 
1659
2152
  *Files written: engineering/stack.md*
1660
2153
 
1661
- **Q${isSolo ? "8" : "18"}. What's the tech stack, and what would surprise an incoming engineer?**
2154
+ **Q${isSolo ? "7" : "18"}. What's the tech stack, and what would surprise an incoming engineer?**
1662
2155
  *Why it matters: stack.md is read every time the AI helps write or review code.
1663
2156
  The "what would surprise" framing surfaces the non-obvious conventions.*
1664
2157
 
@@ -1691,7 +2184,7 @@ If no: note that the decisions/ folder is ready when one comes up.
1691
2184
 
1692
2185
  *Files written: context/glossary.md${isSolo ? "" : ", context/stakeholders.md"}*
1693
2186
 
1694
- **Q${isSolo ? "9" : "21"}. What words does your team use that would confuse an outsider?**
2187
+ **Q${isSolo ? "8" : "21"}. What words does your team use that would confuse an outsider?**
1695
2188
  *Why it matters: glossary.md is read when the AI writes specs, reviews code, or
1696
2189
  discusses product strategy. Shared vocabulary prevents the AI from guessing at meaning.*
1697
2190
 
@@ -1712,7 +2205,7 @@ For each stakeholder: name/role, what they really care about, how they prefer to
1712
2205
 
1713
2206
  *After the answer: write to context/stakeholders.md.*`}
1714
2207
 
1715
- **Q${isSolo ? "10" : "23"}. Are there any terms your team uses inconsistently with each other?**
2208
+ **Q${isSolo ? "9" : "23"}. Are there any terms your team uses inconsistently with each other?**
1716
2209
  *Why it matters: inconsistent internal vocabulary is a reliable source of meeting friction.
1717
2210
  Naming it here gives the AI a flag to raise when it notices the inconsistency.*
1718
2211
 
@@ -1752,6 +2245,7 @@ function northStarTemplate(ctx) {
1752
2245
  purpose: The single metric that best captures whether we're creating the value we intend to create
1753
2246
  read_when: Setting quarterly direction, evaluating big bets, writing OKRs, onboarding new team members
1754
2247
  last_updated: ${ctx.date}
2248
+ owner:
1755
2249
  ---
1756
2250
 
1757
2251
  # North Star
@@ -1805,6 +2299,7 @@ function outcomesTemplate(ctx) {
1805
2299
  purpose: Current quarter outcomes \u2014 the changes in customer behavior that define success this quarter
1806
2300
  read_when: Prioritizing work, writing specs, deciding what to build next, evaluating tradeoffs
1807
2301
  last_updated: ${ctx.date}
2302
+ owner:
1808
2303
  ---
1809
2304
 
1810
2305
  # Outcomes
@@ -1848,6 +2343,7 @@ function customersTemplate(ctx) {
1848
2343
  purpose: Named customers, personas, jobs to be done, and direct quotes from real conversations
1849
2344
  read_when: Writing specs, prioritizing features, evaluating tradeoffs, any time you're guessing what customers want
1850
2345
  last_updated: ${ctx.date}
2346
+ owner:
1851
2347
  ---
1852
2348
 
1853
2349
  # Customers
@@ -1901,6 +2397,7 @@ function nowNextLaterTemplate(ctx) {
1901
2397
  purpose: What we're building now, what we're committed to next, and what's directional
1902
2398
  read_when: Sprint planning, stakeholder updates, evaluating new requests, prioritization discussions
1903
2399
  last_updated: ${ctx.date}
2400
+ owner:
1904
2401
  ---
1905
2402
 
1906
2403
  # Now / Next / Later
@@ -1960,6 +2457,7 @@ function assumptionsTemplate(ctx) {
1960
2457
  purpose: Open assumptions and untested beliefs \u2014 the bets the team is currently making
1961
2458
  read_when: Designing discovery work, scoping experiments, retros, any time a decision feels risky
1962
2459
  last_updated: ${ctx.date}
2460
+ owner:
1963
2461
  ---
1964
2462
 
1965
2463
  # Assumptions
@@ -2035,6 +2533,7 @@ function risksTemplate(ctx) {
2035
2533
  purpose: The four product risks \u2014 tracked so they don't become surprises at launch
2036
2534
  read_when: Scoping new features, go/no-go decisions, discovery planning, quarterly reviews
2037
2535
  last_updated: ${ctx.date}
2536
+ owner:
2038
2537
  ---
2039
2538
 
2040
2539
  # Risks
@@ -2094,6 +2593,7 @@ function trioTemplate(ctx) {
2094
2593
  purpose: The product trio \u2014 who owns what decisions and how the three roles work together
2095
2594
  read_when: Escalations, onboarding, clarifying ownership, any "who decides this?" conversation
2096
2595
  last_updated: ${ctx.date}
2596
+ owner:
2097
2597
  ---
2098
2598
 
2099
2599
  # Team Trio
@@ -2115,11 +2615,17 @@ last_updated: ${ctx.date}
2115
2615
 
2116
2616
  ## Members
2117
2617
 
2118
- | Role | Person | Focus area |
2119
- |---|---|---|
2120
- | Product Manager | <!-- name --> | What to build and why |
2121
- | Engineering Lead | <!-- name --> | How to build it, tech debt, architecture |
2122
- | Design Lead | <!-- name --> | UX, flows, visual quality |
2618
+ | Role | Person | GitHub (optional) | Focus area |
2619
+ |---|---|---|---|
2620
+ | Product Manager | <!-- name --> | <!-- @handle --> | What to build and why |
2621
+ | Engineering Lead | <!-- name --> | <!-- @handle --> | How to build it, tech debt, architecture |
2622
+ | Design Lead | <!-- name --> | <!-- @handle --> | UX, flows, visual quality |
2623
+
2624
+ <!-- Optional additional fields per member (add inline or as a sub-list):
2625
+ - slack: U0C3D4E5F (Slack member ID for @mentions)
2626
+ - linear: user ID (for issue assignment lookups)
2627
+ Fill in what's known; leave blank what isn't. These fields let AI tools
2628
+ take cross-platform actions when relevant. -->
2123
2629
 
2124
2630
  ## How we make decisions
2125
2631
 
@@ -2151,6 +2657,7 @@ function workingAgreementTemplate(ctx) {
2151
2657
  purpose: Definition of done, definition of ready, ceremonies, and team norms \u2014 the honest version
2152
2658
  read_when: Code review, sprint planning, retrospectives, any "this isn't how we said we'd work" moment
2153
2659
  last_updated: ${ctx.date}
2660
+ owner:
2154
2661
  ---
2155
2662
 
2156
2663
  # Working Agreement
@@ -2221,6 +2728,7 @@ function aiPracticesTemplate(ctx) {
2221
2728
  purpose: How this team uses AI tools \u2014 what's working, what we've decided not to do, and our norms
2222
2729
  read_when: Onboarding engineers, evaluating new AI tooling, retrospectives on AI-assisted work
2223
2730
  last_updated: ${ctx.date}
2731
+ owner:
2224
2732
  ---
2225
2733
 
2226
2734
  # AI Practices
@@ -2275,6 +2783,7 @@ function stackTemplate(ctx) {
2275
2783
  purpose: Tech stack, conventions, deployment pipeline, and local dev setup
2276
2784
  read_when: Writing code, reviewing PRs, evaluating new dependencies, onboarding engineers
2277
2785
  last_updated: ${ctx.date}
2786
+ owner:
2278
2787
  ---
2279
2788
 
2280
2789
  # Engineering Stack
@@ -2324,6 +2833,7 @@ function qualityBarTemplate(ctx) {
2324
2833
  purpose: The team's honest stance on tech debt, bugs, and what "shipped" actually means
2325
2834
  read_when: Code review, sprint planning, evaluating shortcuts, any quality-vs-speed conversation
2326
2835
  last_updated: ${ctx.date}
2836
+ owner:
2327
2837
  ---
2328
2838
 
2329
2839
  # Quality Bar
@@ -2394,6 +2904,7 @@ function decisionsReadmeTemplate(ctx) {
2394
2904
  purpose: Index and template for architecture decision records (ADRs)
2395
2905
  read_when: Evaluating architectural choices, understanding why the codebase looks the way it does
2396
2906
  last_updated: ${ctx.date}
2907
+ owner:
2397
2908
  ---
2398
2909
 
2399
2910
  # Architecture Decisions
@@ -2450,6 +2961,7 @@ function principlesTemplate(ctx) {
2450
2961
  purpose: Design principles, tone of voice, and accessibility stance
2451
2962
  read_when: Designing new features, writing copy, reviewing designs, evaluating UX tradeoffs
2452
2963
  last_updated: ${ctx.date}
2964
+ owner:
2453
2965
  ---
2454
2966
 
2455
2967
  # Design Principles
@@ -2507,6 +3019,7 @@ function metricsTemplate(ctx) {
2507
3019
  purpose: Metric definitions, ownership, and data sources \u2014 so the team means the same thing
2508
3020
  read_when: Building dashboards, writing OKRs, reviewing product health, debugging data discrepancies
2509
3021
  last_updated: ${ctx.date}
3022
+ owner:
2510
3023
  ---
2511
3024
 
2512
3025
  # Metrics
@@ -2553,6 +3066,7 @@ function glossaryTemplate(ctx) {
2553
3066
  purpose: Domain terms, acronyms, and jargon specific to this team and product
2554
3067
  read_when: Onboarding, writing specs, any time a term feels ambiguous or overloaded
2555
3068
  last_updated: ${ctx.date}
3069
+ owner:
2556
3070
  ---
2557
3071
 
2558
3072
  # Glossary
@@ -2594,6 +3108,7 @@ function stakeholdersTemplate(ctx) {
2594
3108
  purpose: Who cares about this product, what they care about, and how the team works with them
2595
3109
  read_when: Stakeholder updates, go/no-go decisions, escalations, quarterly planning
2596
3110
  last_updated: ${ctx.date}
3111
+ owner:
2597
3112
  ---
2598
3113
 
2599
3114
  # Stakeholders
@@ -2637,6 +3152,7 @@ function strategyTemplate(ctx) {
2637
3152
  purpose: The strategic logic connecting our north-star gap to what we're building. Read before adding anything to the roadmap.
2638
3153
  read_when: Roadmap planning, evaluating new feature requests, quarterly retrospective, when a new item is proposed for Now or Next, when a new team member is onboarding
2639
3154
  last_updated: ${ctx.date}
3155
+ owner:
2640
3156
  ---
2641
3157
 
2642
3158
  # Strategy
@@ -2698,7 +3214,77 @@ last_updated: ${ctx.date}
2698
3214
  `;
2699
3215
  }
2700
3216
 
3217
+ // src/templates/root-agents.ts
3218
+ function rootAgentsTemplate(ctx) {
3219
+ const isSolo = ctx.profile === "solo";
3220
+ return `---
3221
+ purpose: Entry point for AI agents \u2014 routes to team-foundry context files
3222
+ read_when: always
3223
+ last_updated: ${ctx.date}
3224
+ owner:
3225
+ ---
3226
+
3227
+ # Agents
3228
+
3229
+ This repo uses **team-foundry** \u2014 structured files that give AI tools real team context.
3230
+ Use this file to orient yourself and find the right files before answering.
3231
+
3232
+ ## Project overview
3233
+
3234
+ <!-- GAP: The project overview hasn't been filled in yet. Run the onboarding interview to populate it. -->
3235
+
3236
+ ## Where to find context
3237
+
3238
+ | Topic | Path |
3239
+ |---|---|
3240
+ | Vision and north star metric | \`team-foundry/product/north-star.md\` |
3241
+ | This quarter's outcomes | \`team-foundry/product/outcomes.md\` |
3242
+ | Who our customers are | \`team-foundry/product/customers.md\` |
3243
+ | Tech stack and conventions | \`team-foundry/engineering/stack.md\` |${isSolo ? "" : `
3244
+ | Current roadmap | \`team-foundry/product/now-next-later.md\` |
3245
+ | Strategic logic | \`team-foundry/product/strategy.md\` |
3246
+ | Open assumptions | \`team-foundry/product/assumptions.md\` |
3247
+ | Key risks | \`team-foundry/product/risks.md\` |
3248
+ | Team norms and DoD | \`team-foundry/team/working-agreement.md\` |
3249
+ | How we use AI tools | \`team-foundry/team/ai-practices.md\` |
3250
+ | Quality stance | \`team-foundry/engineering/quality-bar.md\` |
3251
+ | Architecture decisions | \`team-foundry/engineering/decisions/\` |
3252
+ | Design principles | \`team-foundry/design/principles.md\` |
3253
+ | Metric definitions | \`team-foundry/data/metrics.md\` |
3254
+ | Domain terms | \`team-foundry/context/glossary.md\` |
3255
+ | Stakeholders | \`team-foundry/context/stakeholders.md\` |`}
3256
+
3257
+ ## Setup
3258
+
3259
+ See \`team-foundry/engineering/stack.md\` for the tech stack, local setup steps, and environment requirements.
3260
+
3261
+ ## Code conventions
3262
+
3263
+ See \`team-foundry/engineering/stack.md\` for language and framework conventions.${isSolo ? "" : `
3264
+ See \`team-foundry/engineering/quality-bar.md\` for the quality bar, bug triage policy, and definition of done.`}
3265
+
3266
+ ## Areas of caution
3267
+
3268
+ - **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.
3269
+ - **Private folder** \u2014 \`team-foundry/private/\` is gitignored. Do not read from or write to it.${isSolo ? "" : `
3270
+ - **ADR commitments** \u2014 architecture decisions in \`team-foundry/engineering/decisions/\` represent committed choices. Treat them as constraints unless the user explicitly opens a discussion.`}
3271
+
3272
+ ## Working with the team-foundry coach
3273
+
3274
+ 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.
3275
+
3276
+ **Draft-then-confirm rule:** the coach always shows a proposed file change and waits for confirmation before writing. Silence is not confirmation.
3277
+ ${isSolo ? "" : `
3278
+ ## Glossary
3279
+
3280
+ See \`team-foundry/context/glossary.md\` for domain terms, acronyms, and known naming inconsistencies between teams.
3281
+ `}`;
3282
+ }
3283
+
2701
3284
  // src/scaffold.ts
3285
+ var ALWAYS_ROOT_ENTRIES = [
3286
+ { relativePath: "AGENTS.md", content: rootAgentsTemplate }
3287
+ ];
2702
3288
  var SOLO_ENTRIES = [
2703
3289
  { relativePath: "GETTING_STARTED.md", content: gettingStartedTemplate },
2704
3290
  { relativePath: ".team-foundry/coach.md", content: coachTemplate },
@@ -2725,6 +3311,14 @@ var FULL_ONLY_ENTRIES = [
2725
3311
  { relativePath: "team-foundry/context/stakeholders.md", content: stakeholdersTemplate },
2726
3312
  { relativePath: "team-foundry/product/strategy.md", content: strategyTemplate }
2727
3313
  ];
3314
+ var FEDERATED_ENTRIES = [
3315
+ { relativePath: "team-foundry/product/CLAUDE.md", content: federatedProductTemplate },
3316
+ { relativePath: "team-foundry/team/CLAUDE.md", content: federatedTeamTemplate },
3317
+ { relativePath: "team-foundry/engineering/CLAUDE.md", content: federatedEngineeringTemplate },
3318
+ { relativePath: "team-foundry/design/CLAUDE.md", content: federatedDesignTemplate },
3319
+ { relativePath: "team-foundry/data/CLAUDE.md", content: federatedDataTemplate },
3320
+ { relativePath: "team-foundry/context/CLAUDE.md", content: federatedContextTemplate }
3321
+ ];
2728
3322
  function rootEntries(tool) {
2729
3323
  if (tool === "claude") {
2730
3324
  return [{ relativePath: "CLAUDE.md", content: rootClaudeTemplate }];
@@ -2732,18 +3326,23 @@ function rootEntries(tool) {
2732
3326
  if (tool === "gemini") {
2733
3327
  return [{ relativePath: "GEMINI.md", content: rootGeminiTemplate }];
2734
3328
  }
3329
+ if (tool === "cursor") {
3330
+ return [{ relativePath: ".cursor/rules/team-foundry.mdc", content: rootCursorTemplate }];
3331
+ }
2735
3332
  return [
2736
3333
  { relativePath: "CLAUDE.md", content: rootClaudeTemplate },
2737
3334
  { relativePath: "GEMINI.md", content: rootGeminiTemplate }
2738
3335
  ];
2739
3336
  }
2740
3337
  async function scaffold(options) {
2741
- const { targetDir, profile, tool, repoVisibility, date, ingestionPath, ingestion } = options;
3338
+ const { targetDir, profile, tool, repoVisibility, date, ingestionPath, ingestion, federated } = options;
2742
3339
  const ctx = { profile, tool, repoVisibility, date, ingestionPath, ingestion };
2743
3340
  const entries = [
3341
+ ...ALWAYS_ROOT_ENTRIES,
2744
3342
  ...rootEntries(tool),
2745
3343
  ...SOLO_ENTRIES,
2746
- ...profile === "full" ? FULL_ONLY_ENTRIES : []
3344
+ ...profile === "full" ? FULL_ONLY_ENTRIES : [],
3345
+ ...profile === "full" && federated ? FEDERATED_ENTRIES : []
2747
3346
  ];
2748
3347
  for (const entry of entries) {
2749
3348
  const fullPath = path.join(targetDir, entry.relativePath);
@@ -2778,10 +3377,428 @@ async function writeGitignore(targetDir) {
2778
3377
  `, "utf-8");
2779
3378
  }
2780
3379
 
3380
+ // src/status.ts
3381
+ import fs3 from "fs/promises";
3382
+ import path3 from "path";
3383
+ import { spawnSync } from "child_process";
3384
+
3385
+ // src/link-checker.ts
3386
+ import { readFile } from "fs/promises";
3387
+ var SEVERITY = {
3388
+ missing: 3,
3389
+ "outcome-metric": 3,
3390
+ "now-assumption": 3,
3391
+ "assumption-outcome": 3,
3392
+ stale: 2,
3393
+ empty: 2
3394
+ };
3395
+ function recencyFactor(prs) {
3396
+ if (prs > 3) return 2;
3397
+ if (prs > 0) return 1;
3398
+ return 0;
3399
+ }
3400
+ function escapeRe(s) {
3401
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3402
+ }
3403
+ function actionForHealth(health, file) {
3404
+ if (health === "missing") return `Run \`npx create-team-foundry\` to scaffold ${file}`;
3405
+ if (health === "empty") return `Fill in ${file} with real content (remove stub placeholder)`;
3406
+ return `Update last_updated in ${file} and review content for accuracy`;
3407
+ }
3408
+ function actionForLink(type, item, file) {
3409
+ if (type === "outcome-metric") return `Define "${item}" in data/metrics.md with formula, source, window, owner`;
3410
+ if (type === "now-assumption") return `Add assumption reference to "${item}" in ${file} or link it in assumptions.md`;
3411
+ return `Add cross-reference between "${item}" and a related outcome or assumption`;
3412
+ }
3413
+ function rankFindings(healthFindings, linkFindings) {
3414
+ const candidates = [];
3415
+ for (const h of healthFindings) {
3416
+ const severity = SEVERITY[h.health] ?? 1;
3417
+ const score = severity * 3 + recencyFactor(h.prs);
3418
+ candidates.push({
3419
+ item: h.file,
3420
+ file: h.file,
3421
+ detail: `File is ${h.health}: ${h.file.replace("team-foundry/", "")}`,
3422
+ score,
3423
+ action: actionForHealth(h.health, h.file)
3424
+ });
3425
+ }
3426
+ for (const l of linkFindings) {
3427
+ const severity = SEVERITY[l.type] ?? 1;
3428
+ const score = severity * 3;
3429
+ candidates.push({
3430
+ item: l.item,
3431
+ file: l.file,
3432
+ detail: l.detail,
3433
+ score,
3434
+ action: actionForLink(l.type, l.item, l.file)
3435
+ });
3436
+ }
3437
+ candidates.sort((a, b) => {
3438
+ if (b.score !== a.score) return b.score - a.score;
3439
+ return a.file.localeCompare(b.file);
3440
+ });
3441
+ return candidates.slice(0, 3);
3442
+ }
3443
+ function extractHeadings(content, section) {
3444
+ if (!content.trim()) return [];
3445
+ let source = content;
3446
+ if (section) {
3447
+ const sectionRe = new RegExp(
3448
+ `(?:^|\\n)###\\s+${escapeRe(section)}\\s*\\n([\\s\\S]*?)(?=\\n###|$)`
3449
+ );
3450
+ const match = source.match(sectionRe);
3451
+ source = match ? match[1] : "";
3452
+ }
3453
+ const headings = [];
3454
+ for (const line of source.split("\n")) {
3455
+ const m = line.match(/^##\s+(.+)$/);
3456
+ if (m) headings.push(m[1].trim());
3457
+ }
3458
+ return headings;
3459
+ }
3460
+ function extractSectionBodies(content) {
3461
+ const result = {};
3462
+ const lines = content.split("\n");
3463
+ let currentHeading = null;
3464
+ const bodyLines = [];
3465
+ for (const line of lines) {
3466
+ const m = line.match(/^##\s+(.+)$/);
3467
+ if (m) {
3468
+ if (currentHeading !== null) result[currentHeading] = bodyLines.join("\n").trim();
3469
+ currentHeading = m[1].trim();
3470
+ bodyLines.length = 0;
3471
+ } else if (currentHeading !== null) {
3472
+ bodyLines.push(line);
3473
+ }
3474
+ }
3475
+ if (currentHeading !== null) result[currentHeading] = bodyLines.join("\n").trim();
3476
+ return result;
3477
+ }
3478
+ function checkOutcomeMetricLinks(outcomesContent, northStarContent, metricsContent) {
3479
+ const defined = new Set(extractHeadings(metricsContent));
3480
+ if (defined.size === 0) return [];
3481
+ const findings = [];
3482
+ const fromOutcomes = extractHeadings(outcomesContent, "Key Metrics");
3483
+ const fromNorthStar = extractHeadings(northStarContent, "Key Metrics");
3484
+ for (const heading of fromOutcomes) {
3485
+ if (!defined.has(heading)) {
3486
+ findings.push({
3487
+ type: "outcome-metric",
3488
+ file: "team-foundry/product/outcomes.md",
3489
+ item: heading,
3490
+ detail: `"${heading}" in outcomes.md#Key Metrics is not defined in data/metrics.md`
3491
+ });
3492
+ }
3493
+ }
3494
+ for (const heading of fromNorthStar) {
3495
+ if (!defined.has(heading)) {
3496
+ findings.push({
3497
+ type: "outcome-metric",
3498
+ file: "team-foundry/product/north-star.md",
3499
+ item: heading,
3500
+ detail: `"${heading}" in north-star.md#Key Metrics is not defined in data/metrics.md`
3501
+ });
3502
+ }
3503
+ }
3504
+ return findings;
3505
+ }
3506
+ function checkNowAssumptionLinks(nowNextLaterContent, assumptionsContent) {
3507
+ if (!nowNextLaterContent.trim()) return [];
3508
+ const assumptionHeadings = extractHeadings(assumptionsContent);
3509
+ if (assumptionHeadings.length === 0) return [];
3510
+ const nowItems = extractHeadings(nowNextLaterContent, "Now");
3511
+ const bodies = extractSectionBodies(nowNextLaterContent);
3512
+ const findings = [];
3513
+ for (const item of nowItems) {
3514
+ const body = bodies[item] ?? "";
3515
+ const referencesAssumption = assumptionHeadings.some((a) => body.includes(a));
3516
+ if (!referencesAssumption) {
3517
+ findings.push({
3518
+ type: "now-assumption",
3519
+ file: "team-foundry/product/now-next-later.md",
3520
+ item,
3521
+ detail: `Now item "${item}" has no linked assumption in product/assumptions.md`
3522
+ });
3523
+ }
3524
+ }
3525
+ return findings;
3526
+ }
3527
+ function checkAssumptionOutcomeReciprocity(assumptionsContent, outcomesContent) {
3528
+ if (!assumptionsContent.trim() || !outcomesContent.trim()) return [];
3529
+ const outcomeHeadings = extractHeadings(outcomesContent);
3530
+ const assumptionHeadings = extractHeadings(assumptionsContent);
3531
+ const assumptionBodies = extractSectionBodies(assumptionsContent);
3532
+ const outcomeBodies = extractSectionBodies(outcomesContent);
3533
+ const findings = [];
3534
+ for (const assumption of assumptionHeadings) {
3535
+ const body = assumptionBodies[assumption] ?? "";
3536
+ if (!outcomeHeadings.some((o) => body.includes(o))) {
3537
+ findings.push({
3538
+ type: "assumption-outcome",
3539
+ file: "team-foundry/product/assumptions.md",
3540
+ item: assumption,
3541
+ detail: `Assumption "${assumption}" does not reference any outcome`
3542
+ });
3543
+ }
3544
+ }
3545
+ for (const outcome of outcomeHeadings) {
3546
+ const body = outcomeBodies[outcome] ?? "";
3547
+ if (!assumptionHeadings.some((a) => body.includes(a))) {
3548
+ findings.push({
3549
+ type: "assumption-outcome",
3550
+ file: "team-foundry/product/outcomes.md",
3551
+ item: outcome,
3552
+ detail: `Outcome "${outcome}" does not reference any assumption`
3553
+ });
3554
+ }
3555
+ }
3556
+ return findings;
3557
+ }
3558
+ async function readOptional(filePath) {
3559
+ try {
3560
+ return await readFile(filePath, "utf-8");
3561
+ } catch {
3562
+ return "";
3563
+ }
3564
+ }
3565
+ async function runLinkChecks(targetDir) {
3566
+ const p = (rel) => `${targetDir}/${rel}`;
3567
+ const [outcomes, northStar, metrics, nowNext, assumptions] = await Promise.all([
3568
+ readOptional(p("team-foundry/product/outcomes.md")),
3569
+ readOptional(p("team-foundry/product/north-star.md")),
3570
+ readOptional(p("team-foundry/data/metrics.md")),
3571
+ readOptional(p("team-foundry/product/now-next-later.md")),
3572
+ readOptional(p("team-foundry/product/assumptions.md"))
3573
+ ]);
3574
+ return [
3575
+ ...checkOutcomeMetricLinks(outcomes, northStar, metrics),
3576
+ ...checkNowAssumptionLinks(nowNext, assumptions),
3577
+ ...checkAssumptionOutcomeReciprocity(assumptions, outcomes)
3578
+ ];
3579
+ }
3580
+
3581
+ // src/status.ts
3582
+ var SOLO_FILES = [
3583
+ "team-foundry/product/north-star.md",
3584
+ "team-foundry/product/outcomes.md",
3585
+ "team-foundry/product/customers.md",
3586
+ "team-foundry/engineering/stack.md"
3587
+ ];
3588
+ var FULL_ONLY_FILES = [
3589
+ "team-foundry/product/now-next-later.md",
3590
+ "team-foundry/product/assumptions.md",
3591
+ "team-foundry/product/risks.md",
3592
+ "team-foundry/product/strategy.md",
3593
+ "team-foundry/team/trio.md",
3594
+ "team-foundry/team/working-agreement.md",
3595
+ "team-foundry/team/ai-practices.md",
3596
+ "team-foundry/engineering/quality-bar.md",
3597
+ "team-foundry/design/principles.md",
3598
+ "team-foundry/data/metrics.md",
3599
+ "team-foundry/context/glossary.md",
3600
+ "team-foundry/context/stakeholders.md"
3601
+ ];
3602
+ var ALL_FILES = [...SOLO_FILES, ...FULL_ONLY_FILES];
3603
+ var STALE_DAYS = 45;
3604
+ function parseFrontmatter(content) {
3605
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
3606
+ if (!match) return {};
3607
+ const result = {};
3608
+ for (const line of match[1].split("\n")) {
3609
+ const idx = line.indexOf(":");
3610
+ if (idx === -1) continue;
3611
+ const key = line.slice(0, idx).trim();
3612
+ const value = line.slice(idx + 1).trim();
3613
+ result[key] = value;
3614
+ }
3615
+ return result;
3616
+ }
3617
+ function daysSince(dateStr) {
3618
+ const then = new Date(dateStr).getTime();
3619
+ if (isNaN(then)) return null;
3620
+ return Math.floor((Date.now() - then) / 864e5);
3621
+ }
3622
+ function prsSinceDate(targetDir, dateStr) {
3623
+ try {
3624
+ const since = new Date(dateStr).toISOString();
3625
+ if (isNaN(new Date(dateStr).getTime())) return null;
3626
+ const result = spawnSync(
3627
+ "git",
3628
+ ["-C", targetDir, "log", "--oneline", "--merges", `--since=${since}`],
3629
+ { encoding: "utf-8", timeout: 5e3 }
3630
+ );
3631
+ if (result.status !== 0) return null;
3632
+ return result.stdout.trim().split("\n").filter(Boolean).length;
3633
+ } catch {
3634
+ return null;
3635
+ }
3636
+ }
3637
+ function isEffectivelyEmpty(body) {
3638
+ const stripped = body.replace(/<!--[\s\S]*?-->/g, "").replace(/^#+\s.*$/gm, "").trim();
3639
+ return stripped.length < 30;
3640
+ }
3641
+ async function analyzeFile(targetDir, relativePath) {
3642
+ const fullPath = path3.join(targetDir, relativePath);
3643
+ let exists = false;
3644
+ let isEmpty = false;
3645
+ let lastUpdated = null;
3646
+ let owner = null;
3647
+ let daysSinceUpdate = null;
3648
+ let prsSince = null;
3649
+ try {
3650
+ const content = await fs3.readFile(fullPath, "utf-8");
3651
+ exists = true;
3652
+ const body = content.replace(/^---[\s\S]*?---\n?/, "").trim();
3653
+ isEmpty = isEffectivelyEmpty(body);
3654
+ const fm = parseFrontmatter(content);
3655
+ if (fm["last_updated"]) {
3656
+ lastUpdated = fm["last_updated"];
3657
+ daysSinceUpdate = daysSince(lastUpdated);
3658
+ prsSince = prsSinceDate(targetDir, lastUpdated);
3659
+ }
3660
+ owner = fm["owner"] || null;
3661
+ } catch {
3662
+ }
3663
+ let health = "ok";
3664
+ if (!exists) health = "missing";
3665
+ else if (isEmpty) health = "empty";
3666
+ else if (daysSinceUpdate !== null && daysSinceUpdate >= STALE_DAYS) health = "stale";
3667
+ return {
3668
+ relativePath,
3669
+ lastUpdated,
3670
+ owner,
3671
+ daysSinceUpdate,
3672
+ prsSinceUpdate: prsSince,
3673
+ exists,
3674
+ isEmpty,
3675
+ health
3676
+ };
3677
+ }
3678
+ function healthIcon(health) {
3679
+ if (health === "ok") return "\u2713";
3680
+ if (health === "stale") return "~";
3681
+ if (health === "empty") return "\u25CB";
3682
+ return "\u2717";
3683
+ }
3684
+ function formatRow(s) {
3685
+ const icon = healthIcon(s.health);
3686
+ const rawName = s.relativePath.replace("team-foundry/", "");
3687
+ const name = rawName.slice(0, 42).padEnd(42);
3688
+ const updated = s.lastUpdated ?? "\u2014";
3689
+ const age = s.daysSinceUpdate !== null ? `${s.daysSinceUpdate}d` : "\u2014";
3690
+ const prs = s.prsSinceUpdate !== null ? `${s.prsSinceUpdate} PRs` : "\u2014";
3691
+ const owner = s.owner || "\u2014";
3692
+ return ` ${icon} ${name} ${updated.padEnd(12)} ${age.padEnd(6)} ${prs.padEnd(8)} ${owner}`;
3693
+ }
3694
+ function whyNudge(s) {
3695
+ const parts = [];
3696
+ if (s.daysSinceUpdate !== null) parts.push(`${s.daysSinceUpdate} days since last update`);
3697
+ if (s.prsSinceUpdate !== null && s.prsSinceUpdate > 0)
3698
+ parts.push(`${s.prsSinceUpdate} PRs shipped since then`);
3699
+ if (!s.owner) parts.push("no owner set");
3700
+ return parts.join(", ");
3701
+ }
3702
+ async function runStatus(targetDir) {
3703
+ const fullProfileFile = path3.join(targetDir, "team-foundry/team/trio.md");
3704
+ let isFullProfile = false;
3705
+ try {
3706
+ await fs3.access(fullProfileFile);
3707
+ isFullProfile = true;
3708
+ } catch {
3709
+ }
3710
+ const filesToCheck = isFullProfile ? ALL_FILES : SOLO_FILES;
3711
+ const results = await Promise.all(filesToCheck.map((f) => analyzeFile(targetDir, f)));
3712
+ const ok = results.filter((r) => r.health === "ok");
3713
+ const stale = results.filter((r) => r.health === "stale");
3714
+ const empty = results.filter((r) => r.health === "empty");
3715
+ const missing = results.filter((r) => r.health === "missing");
3716
+ console.log("\n team-foundry status\n");
3717
+ console.log(` ${"File".padEnd(44)} ${"Last updated".padEnd(12)} ${"Age".padEnd(6)} ${"PRs".padEnd(8)} Owner`);
3718
+ console.log(` ${"\u2500".repeat(90)}`);
3719
+ for (const s of results) console.log(formatRow(s));
3720
+ console.log();
3721
+ console.log(` \u2713 ${ok.length} current ~ ${stale.length} stale \u25CB ${empty.length} empty \u2717 ${missing.length} missing
3722
+ `);
3723
+ let linkFindings = [];
3724
+ if (isFullProfile) {
3725
+ linkFindings = await runLinkChecks(targetDir);
3726
+ if (linkFindings.length > 0) {
3727
+ console.log(` Link Integrity`);
3728
+ console.log(` ${"\u2500".repeat(60)}`);
3729
+ const byType = {};
3730
+ for (const f of linkFindings) {
3731
+ (byType[f.type] ??= []).push(f);
3732
+ }
3733
+ const typeLabels = {
3734
+ "outcome-metric": "Outcome references undefined metric",
3735
+ "now-assumption": "Now item missing linked assumption",
3736
+ "assumption-outcome": "Assumption/outcome unlinked"
3737
+ };
3738
+ for (const [type, items] of Object.entries(byType)) {
3739
+ console.log(`
3740
+ ! ${typeLabels[type] ?? type}`);
3741
+ for (const item of items) {
3742
+ console.log(` ${item.file.replace("team-foundry/", "")} \u2192 ${item.detail}`);
3743
+ }
3744
+ }
3745
+ console.log();
3746
+ }
3747
+ }
3748
+ const healthForRanking = results.filter((r) => r.health !== "ok").map((r) => ({
3749
+ file: r.relativePath,
3750
+ health: r.health,
3751
+ prs: r.prsSinceUpdate ?? 0
3752
+ }));
3753
+ const top3 = rankFindings(healthForRanking, linkFindings);
3754
+ console.log(` Top 3 Fix Suggestions`);
3755
+ console.log(` ${"\u2500".repeat(60)}`);
3756
+ if (top3.length === 0) {
3757
+ console.log(" No critical drift detected this week.\n");
3758
+ } else {
3759
+ for (let i = 0; i < top3.length; i++) {
3760
+ const s = top3[i];
3761
+ console.log(`
3762
+ ${i + 1}) ${s.detail}`);
3763
+ console.log(` In: ${s.file.replace("team-foundry/", "")}`);
3764
+ console.log(` Action: ${s.action}`);
3765
+ }
3766
+ console.log();
3767
+ }
3768
+ if (stale.length > 0) {
3769
+ console.log(" Stale files \u2014 why this nudge:\n");
3770
+ for (const s of stale) {
3771
+ console.log(` ~ ${s.relativePath.replace("team-foundry/", "")}`);
3772
+ console.log(` ${whyNudge(s)}
3773
+ `);
3774
+ }
3775
+ }
3776
+ if (empty.length > 0) {
3777
+ console.log(" Empty files \u2014 not yet filled in:\n");
3778
+ for (const s of empty) {
3779
+ console.log(` \u25CB ${s.relativePath.replace("team-foundry/", "")}`);
3780
+ }
3781
+ console.log();
3782
+ }
3783
+ if (missing.length > 0) {
3784
+ console.log(" Missing files \u2014 run `npx create-team-foundry` to scaffold:\n");
3785
+ for (const s of missing) {
3786
+ console.log(` \u2717 ${s.relativePath.replace("team-foundry/", "")}`);
3787
+ }
3788
+ console.log();
3789
+ }
3790
+ const noOwner = results.filter((r) => r.exists && !r.owner);
3791
+ if (noOwner.length > 0) {
3792
+ console.log(` ${noOwner.length} file(s) have no owner set. Add \`owner: <name>\` to their frontmatter.
3793
+ `);
3794
+ }
3795
+ }
3796
+
2781
3797
  // src/index.ts
2782
3798
  var TOOL_LABEL = {
2783
3799
  claude: "Claude Code",
2784
3800
  gemini: "Gemini CLI",
3801
+ cursor: "Cursor",
2785
3802
  both: "Claude Code or Gemini CLI"
2786
3803
  };
2787
3804
  var PASTE_PLACEHOLDER = `# Paste your existing docs here
@@ -2799,16 +3816,16 @@ You can paste multiple documents \u2014 just separate them with a heading like:
2799
3816
  When you're done, save this file and start the onboarding interview.
2800
3817
  `;
2801
3818
  async function checkDirectory(targetDir) {
2802
- const prdPath = path3.join(targetDir, "team-foundry-prd-v2.md");
2803
- const scaffoldPath = path3.join(targetDir, "src", "scaffold.ts");
3819
+ const prdPath = path4.join(targetDir, "team-foundry-prd-v2.md");
3820
+ const scaffoldPath = path4.join(targetDir, "src", "scaffold.ts");
2804
3821
  let isSourceRepo = false;
2805
3822
  try {
2806
- await fs3.access(prdPath);
3823
+ await fs4.access(prdPath);
2807
3824
  isSourceRepo = true;
2808
3825
  } catch {
2809
3826
  }
2810
3827
  try {
2811
- await fs3.access(scaffoldPath);
3828
+ await fs4.access(scaffoldPath);
2812
3829
  isSourceRepo = true;
2813
3830
  } catch {
2814
3831
  }
@@ -2818,17 +3835,17 @@ async function checkDirectory(targetDir) {
2818
3835
  );
2819
3836
  process.exit(1);
2820
3837
  }
2821
- const pkgPath = path3.join(targetDir, "package.json");
2822
- const srcPath = path3.join(targetDir, "src");
3838
+ const pkgPath = path4.join(targetDir, "package.json");
3839
+ const srcPath = path4.join(targetDir, "src");
2823
3840
  let hasPkg = false;
2824
3841
  let hasSrc = false;
2825
3842
  try {
2826
- await fs3.access(pkgPath);
3843
+ await fs4.access(pkgPath);
2827
3844
  hasPkg = true;
2828
3845
  } catch {
2829
3846
  }
2830
3847
  try {
2831
- await fs3.access(srcPath);
3848
+ await fs4.access(srcPath);
2832
3849
  hasSrc = true;
2833
3850
  } catch {
2834
3851
  }
@@ -2845,22 +3862,81 @@ async function checkDirectory(targetDir) {
2845
3862
  }
2846
3863
  async function main() {
2847
3864
  const targetDir = process.cwd();
3865
+ if (process.argv[2] === "status") {
3866
+ await runStatus(targetDir);
3867
+ return;
3868
+ }
2848
3869
  await checkDirectory(targetDir);
2849
3870
  const answers = await runPrompts();
2850
3871
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2851
3872
  await scaffold({ ...answers, targetDir, date });
2852
3873
  await writeGitignore(targetDir);
2853
- if (answers.ingestion === "paste") {
2854
- const pastePath = path3.join(targetDir, ".team-foundry", "paste-content.md");
3874
+ if (answers.ingestion === "paste" || answers.ingestion === "repo+paste") {
3875
+ const pastePath = path4.join(targetDir, ".team-foundry", "paste-content.md");
2855
3876
  try {
2856
- await fs3.access(pastePath);
3877
+ await fs4.access(pastePath);
2857
3878
  } catch {
2858
- await fs3.writeFile(pastePath, PASTE_PLACEHOLDER, "utf-8");
3879
+ await fs4.writeFile(pastePath, PASTE_PLACEHOLDER, "utf-8");
2859
3880
  }
2860
3881
  }
2861
3882
  const tool = TOOL_LABEL[answers.tool];
2862
3883
  let ingestionNote;
2863
- if (answers.ingestion === "paste") {
3884
+ if (answers.ingestion === "repo") {
3885
+ ingestionNote = `
3886
+ Next steps:
3887
+
3888
+ 1. cd ${targetDir}
3889
+
3890
+ 2. Open ${tool} and say:
3891
+
3892
+ "Let's set up our team-foundry."
3893
+
3894
+ The AI will read your repo (README, package.json, git history, GitHub
3895
+ PRs/issues) and pre-fill answers before asking questions.
3896
+ `;
3897
+ } else if (answers.ingestion === "repo+local") {
3898
+ ingestionNote = `
3899
+ Next steps:
3900
+
3901
+ 1. cd ${targetDir}
3902
+
3903
+ 2. Open ${tool} and say:
3904
+
3905
+ "Let's set up our team-foundry."
3906
+
3907
+ The AI will read your repo first, then supplement with the docs in
3908
+ ${answers.ingestionPath ?? "[your docs folder]"}.
3909
+ `;
3910
+ } else if (answers.ingestion === "repo+mcp") {
3911
+ ingestionNote = `
3912
+ Next steps:
3913
+
3914
+ 1. In ${tool} settings, connect your MCP server
3915
+ (Notion, Confluence, or Google Drive) if you haven't already.
3916
+
3917
+ 2. cd ${targetDir}
3918
+
3919
+ 3. Open ${tool} and say:
3920
+
3921
+ "Let's set up our team-foundry."
3922
+
3923
+ The AI will read your repo first, then supplement from your MCP source.
3924
+ `;
3925
+ } else if (answers.ingestion === "repo+paste") {
3926
+ ingestionNote = `
3927
+ Next steps:
3928
+
3929
+ 1. Open .team-foundry/paste-content.md and paste in your existing docs
3930
+ (strategy, roadmaps, customer research). Save the file.
3931
+
3932
+ 2. cd ${targetDir}
3933
+
3934
+ 3. Open ${tool} and say:
3935
+
3936
+ "Let's set up our team-foundry. I've added docs to
3937
+ paste-content.md \u2014 use them to supplement the repo scan."
3938
+ `;
3939
+ } else if (answers.ingestion === "paste") {
2864
3940
  ingestionNote = `
2865
3941
  Next steps:
2866
3942