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.
- package/README.md +128 -39
- package/dist/index.js +1118 -42
- 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
|
|
5
|
-
import
|
|
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: "
|
|
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
|
|
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: "
|
|
48
|
-
{ value: "
|
|
49
|
-
{ value: "
|
|
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 ? "
|
|
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}
|
|
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
|
-
|
|
1987
|
+
${isSolo ? "" : `### Theme 3: Measurement
|
|
1495
1988
|
|
|
1496
1989
|
*Files written: data/metrics.md*
|
|
1497
1990
|
|
|
1498
|
-
**
|
|
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 ? "
|
|
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 ? "
|
|
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 ? "
|
|
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 ? "
|
|
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 ? "
|
|
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 =
|
|
2803
|
-
const scaffoldPath =
|
|
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
|
|
3823
|
+
await fs4.access(prdPath);
|
|
2807
3824
|
isSourceRepo = true;
|
|
2808
3825
|
} catch {
|
|
2809
3826
|
}
|
|
2810
3827
|
try {
|
|
2811
|
-
await
|
|
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 =
|
|
2822
|
-
const srcPath =
|
|
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
|
|
3843
|
+
await fs4.access(pkgPath);
|
|
2827
3844
|
hasPkg = true;
|
|
2828
3845
|
} catch {
|
|
2829
3846
|
}
|
|
2830
3847
|
try {
|
|
2831
|
-
await
|
|
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 =
|
|
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
|
|
3877
|
+
await fs4.access(pastePath);
|
|
2857
3878
|
} catch {
|
|
2858
|
-
await
|
|
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 === "
|
|
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
|
|