create-team-foundry 1.0.0 → 2.0.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 +40 -6
- package/dist/index.js +618 -31
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
# team-foundry
|
|
2
2
|
|
|
3
|
-
**
|
|
3
|
+
**A Context Engine for product teams.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
team-foundry scaffolds the shared brain your AI coding tools read from — outcomes, customers, decisions, quality bar — in a repo every team member commits to. Every AI tool, every team member, the same context. Automatically.
|
|
6
6
|
|
|
7
7
|
```
|
|
8
8
|
npx create-team-foundry
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Answer 4 questions. Files appear in your repo. That's it.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
team-foundry is agent-agnostic by design. The context files are the product. The AI tool reading them is a replaceable component.
|
|
14
|
+
|
|
15
|
+
**Run this in your shared product/engineering repo** — the one the whole team commits to. Running it in a personal repo works for solo use but misses the point for teams.
|
|
14
16
|
|
|
15
17
|
---
|
|
16
18
|
|
|
19
|
+
## See what populated looks like
|
|
20
|
+
|
|
21
|
+
→ **[example/](example/)** — a fully populated team-foundry for Clearline, a fictional 8-person B2B SaaS team. Clone this repo, open `example/` in Claude Code, and ask the AI anything about the team. This is what your team-foundry can look like after the setup conversation.
|
|
22
|
+
|
|
17
23
|
## What it does
|
|
18
24
|
|
|
19
25
|
Most teams use AI tools for code. The AI gives better answers when it knows what the product is for, who the customers are, and what quality means to this team.
|
|
@@ -24,9 +30,9 @@ It also installs a coach: a set of behaviors that notice when your files go stal
|
|
|
24
30
|
|
|
25
31
|
## What gets created
|
|
26
32
|
|
|
27
|
-
**Solo profile (1–3 people):** 7 files.
|
|
33
|
+
**Solo profile (1–3 people):** 7 files. Root instruction file, getting started guide, coach playbook, north star, outcomes, customers, stack.
|
|
28
34
|
|
|
29
|
-
**Full profile (4–15 people):**
|
|
35
|
+
**Full profile (4–15 people):** 20 files. Everything above plus strategy, roadmap, assumptions, risks, trio, working agreement, AI practices, quality bar, decisions log, design principles, metrics, glossary, stakeholders.
|
|
30
36
|
|
|
31
37
|
Every file has YAML frontmatter (`purpose`, `read_when`, `last_updated`) so the AI knows when to load it and why.
|
|
32
38
|
|
|
@@ -34,6 +40,28 @@ Every file has YAML frontmatter (`purpose`, `read_when`, `last_updated`) so the
|
|
|
34
40
|
|
|
35
41
|
team-foundry enables the work of building a shared understanding of your product. It doesn't replace it. If your team isn't willing to have the hard conversations about outcomes, customers, and quality — no tool fixes that. What team-foundry does is make those conversations easier to start, and easier to keep current.
|
|
36
42
|
|
|
43
|
+
## Shared floor, individual ceiling
|
|
44
|
+
|
|
45
|
+
team-foundry sets a shared baseline of context that every team member's AI tool reads from. Outcomes, customers, decisions, quality bar, glossary — the things everyone should be grounded in.
|
|
46
|
+
|
|
47
|
+
It does not cap what individuals do above that baseline. A senior PM can add their own prompts, their own discovery framework, their own depth. An engineer can bring their own AI practices. A designer can layer on top.
|
|
48
|
+
|
|
49
|
+
The floor is shared. The ceiling is individual. That's the point.
|
|
50
|
+
|
|
51
|
+
## What drift looks like
|
|
52
|
+
|
|
53
|
+
The coach catches specific, named patterns. These are the most common:
|
|
54
|
+
|
|
55
|
+
| Pattern | What it is | Example |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| **Output-as-outcome drift** | An outcome that's actually a shipped feature | `outcomes.md` says "ship the new dashboard" instead of "reduce time-to-insight for SMB analysts" |
|
|
58
|
+
| **Assumption fossilization** | A core assumption logged long ago, never revisited, still driving decisions | `assumptions.md` lists "users want faster checkout" dated 94 days ago. Three roadmap items cite it. No one's tested it. |
|
|
59
|
+
| **Customer ghost syndrome** | A persona with no direct team contact in 60+ days, while roadmap items still claim to serve them | Enterprise persona last interviewed in February. Three Q2 features built "for enterprise." |
|
|
60
|
+
| **Metric ambiguity** | A metric cited across files without an agreed definition | "Active user" in `outcomes.md` and `metrics.md` with no shared definition. PM means weekly; engineer means daily. |
|
|
61
|
+
| **Decision amnesia** | A previous ADR rejecting an approach is invisible to a discussion heading the same direction | Q1 ADR rejects microservices migration. Q3 discussion reopens it with no reference to what changed. |
|
|
62
|
+
| **Output roadmap disguised as strategy** | `strategy.md` guiding policy is all "yes" — doesn't name what the team is deliberately not pursuing | "We will be the best tool for product teams." No "we will not build X for Y." |
|
|
63
|
+
| **Build-trap signal** | An item moves into "Now" with no linked experiment and no validated assumption | "Add collaborative editing" moves to Now. No assumption linked. Last validation: none. |
|
|
64
|
+
|
|
37
65
|
## Strategy in Git
|
|
38
66
|
|
|
39
67
|
Putting strategy and product context in a repo might feel wrong. A few things worth knowing:
|
|
@@ -82,6 +110,12 @@ The coach will then only run when you explicitly ask for it.
|
|
|
82
110
|
- Node 18+
|
|
83
111
|
- Claude Code, Gemini CLI, or any AI tool that reads files from your repo as context
|
|
84
112
|
|
|
113
|
+
## What's next
|
|
114
|
+
|
|
115
|
+
team-foundry v1 supports Claude Code and Gemini CLI. Cursor is the highest-priority v2 addition — it has the largest active user base of any AI coding tool and fits the same file-based context model.
|
|
116
|
+
|
|
117
|
+
v2 shortlist: Cursor support and scheduled coach runs via cron.
|
|
118
|
+
|
|
85
119
|
## Contributing
|
|
86
120
|
|
|
87
121
|
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
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);
|
|
@@ -41,6 +42,18 @@ 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: [
|
|
@@ -68,7 +81,8 @@ async function runPrompts() {
|
|
|
68
81
|
profile,
|
|
69
82
|
repoVisibility,
|
|
70
83
|
ingestion,
|
|
71
|
-
ingestionPath
|
|
84
|
+
ingestionPath,
|
|
85
|
+
federated
|
|
72
86
|
};
|
|
73
87
|
}
|
|
74
88
|
|
|
@@ -76,6 +90,137 @@ async function runPrompts() {
|
|
|
76
90
|
import fs from "fs/promises";
|
|
77
91
|
import path from "path";
|
|
78
92
|
|
|
93
|
+
// src/templates/federated/product-claude.ts
|
|
94
|
+
function federatedProductTemplate(ctx) {
|
|
95
|
+
return `---
|
|
96
|
+
purpose: Routing context for the product/ folder \u2014 read before answering product questions
|
|
97
|
+
read_when: Any question about outcomes, customers, roadmap, strategy, assumptions, or risks
|
|
98
|
+
last_updated: ${ctx.date}
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
# Product context
|
|
102
|
+
|
|
103
|
+
This folder contains the team's product thinking. Read the relevant file before answering.
|
|
104
|
+
|
|
105
|
+
| Topic | File |
|
|
106
|
+
|---|---|
|
|
107
|
+
| Vision and north star metric | \`north-star.md\` |
|
|
108
|
+
| This quarter's outcomes | \`outcomes.md\` |
|
|
109
|
+
| Named customers and personas | \`customers.md\` |
|
|
110
|
+
| What we're building now / next / later | \`now-next-later.md\` |
|
|
111
|
+
| Strategic logic and guiding policy | \`strategy.md\` |
|
|
112
|
+
| Open assumptions and untested bets | \`assumptions.md\` |
|
|
113
|
+
| Key product risks | \`risks.md\` |
|
|
114
|
+
|
|
115
|
+
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.
|
|
116
|
+
`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// src/templates/federated/team-claude.ts
|
|
120
|
+
function federatedTeamTemplate(ctx) {
|
|
121
|
+
return `---
|
|
122
|
+
purpose: Routing context for the team/ folder \u2014 read before answering team questions
|
|
123
|
+
read_when: Any question about who owns decisions, how the team works, or AI tool usage
|
|
124
|
+
last_updated: ${ctx.date}
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
# Team context
|
|
128
|
+
|
|
129
|
+
This folder describes how the product trio works and how the team operates.
|
|
130
|
+
|
|
131
|
+
| Topic | File |
|
|
132
|
+
|---|---|
|
|
133
|
+
| Who's on the trio and how decisions are made | \`trio.md\` |
|
|
134
|
+
| Ceremonies, DoD, working norms | \`working-agreement.md\` |
|
|
135
|
+
| How this team uses AI tools | \`ai-practices.md\` |
|
|
136
|
+
|
|
137
|
+
When answering questions about ownership or process, read \`trio.md\` first. It reflects how the team actually operates, not an org chart.
|
|
138
|
+
`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/templates/federated/engineering-claude.ts
|
|
142
|
+
function federatedEngineeringTemplate(ctx) {
|
|
143
|
+
return `---
|
|
144
|
+
purpose: Routing context for the engineering/ folder \u2014 read before answering technical questions
|
|
145
|
+
read_when: Any question about the stack, conventions, tech debt, or past architecture decisions
|
|
146
|
+
last_updated: ${ctx.date}
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
# Engineering context
|
|
150
|
+
|
|
151
|
+
This folder contains the team's technical context. Read before writing or reviewing code.
|
|
152
|
+
|
|
153
|
+
| Topic | File |
|
|
154
|
+
|---|---|
|
|
155
|
+
| Stack, conventions, deployment | \`stack.md\` |
|
|
156
|
+
| Quality stance and tech debt policy | \`quality-bar.md\` |
|
|
157
|
+
| Past architecture decisions | \`decisions/\` |
|
|
158
|
+
|
|
159
|
+
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.
|
|
160
|
+
`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// src/templates/federated/design-claude.ts
|
|
164
|
+
function federatedDesignTemplate(ctx) {
|
|
165
|
+
return `---
|
|
166
|
+
purpose: Routing context for the design/ folder \u2014 read before answering design questions
|
|
167
|
+
read_when: Any question about UI copy, visual decisions, accessibility, or tone of voice
|
|
168
|
+
last_updated: ${ctx.date}
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
# Design context
|
|
172
|
+
|
|
173
|
+
This folder contains the team's design principles and standards.
|
|
174
|
+
|
|
175
|
+
| Topic | File |
|
|
176
|
+
|---|---|
|
|
177
|
+
| Design principles, tone of voice, accessibility | \`principles.md\` |
|
|
178
|
+
|
|
179
|
+
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.
|
|
180
|
+
`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/templates/federated/data-claude.ts
|
|
184
|
+
function federatedDataTemplate(ctx) {
|
|
185
|
+
return `---
|
|
186
|
+
purpose: Routing context for the data/ folder \u2014 read before discussing metrics
|
|
187
|
+
read_when: Any question about metrics, success criteria, or what numbers mean
|
|
188
|
+
last_updated: ${ctx.date}
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
# Data context
|
|
192
|
+
|
|
193
|
+
This folder contains metric definitions for this team.
|
|
194
|
+
|
|
195
|
+
| Topic | File |
|
|
196
|
+
|---|---|
|
|
197
|
+
| Metric definitions, ownership, data sources | \`metrics.md\` |
|
|
198
|
+
|
|
199
|
+
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.
|
|
200
|
+
`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// src/templates/federated/context-claude.ts
|
|
204
|
+
function federatedContextTemplate(ctx) {
|
|
205
|
+
return `---
|
|
206
|
+
purpose: Routing context for the context/ folder \u2014 read before using domain terminology
|
|
207
|
+
read_when: Any question involving domain terms, acronyms, or stakeholder communication
|
|
208
|
+
last_updated: ${ctx.date}
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
# Domain context
|
|
212
|
+
|
|
213
|
+
This folder contains the team's shared vocabulary and stakeholder map.
|
|
214
|
+
|
|
215
|
+
| Topic | File |
|
|
216
|
+
|---|---|
|
|
217
|
+
| Domain terms and acronyms | \`glossary.md\` |
|
|
218
|
+
| Stakeholders and what they care about | \`stakeholders.md\` |
|
|
219
|
+
|
|
220
|
+
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.
|
|
221
|
+
`;
|
|
222
|
+
}
|
|
223
|
+
|
|
79
224
|
// src/templates/root-claude.ts
|
|
80
225
|
function rootClaudeTemplate(ctx) {
|
|
81
226
|
return `---
|
|
@@ -226,6 +371,82 @@ it notices something relevant to your current work. You can also invoke it direc
|
|
|
226
371
|
`;
|
|
227
372
|
}
|
|
228
373
|
|
|
374
|
+
// src/templates/root-cursor.ts
|
|
375
|
+
function rootCursorTemplate(ctx) {
|
|
376
|
+
return `---
|
|
377
|
+
purpose: Identity, routing map, and coach activation \u2014 read at the start of every session
|
|
378
|
+
read_when: Every Cursor session in this repo \u2014 this is the root instruction file
|
|
379
|
+
last_updated: ${ctx.date}
|
|
380
|
+
alwaysApply: true
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
# team-foundry
|
|
384
|
+
|
|
385
|
+
This repo uses **team-foundry** \u2014 structured files that give you real team context.
|
|
386
|
+
Read this file first. It tells you where to find everything and how to activate the coach.
|
|
387
|
+
|
|
388
|
+
<!-- GAP: The onboarding interview hasn't run yet.
|
|
389
|
+
When the user says "Let's set up our team-foundry" or similar, do this:
|
|
390
|
+
1. Read GETTING_STARTED.md for context on what to expect
|
|
391
|
+
2. Load .team-foundry/coach.md \u2014 it contains the interview sequence
|
|
392
|
+
3. Begin the onboarding interview as described there
|
|
393
|
+
Do not improvise the interview. Follow the sequence in coach.md. -->
|
|
394
|
+
|
|
395
|
+
## Who we are
|
|
396
|
+
|
|
397
|
+
<!-- Filled in during the onboarding interview. -->
|
|
398
|
+
|
|
399
|
+
## Routing map
|
|
400
|
+
|
|
401
|
+
When the user's question relates to any of the following, read the corresponding file
|
|
402
|
+
before answering. Files with recent \`last_updated\` dates are more reliable than older ones.
|
|
403
|
+
|
|
404
|
+
| Topic | File |
|
|
405
|
+
|---|---|
|
|
406
|
+
| Who we are / what this product does | team-foundry.mdc \u2014 "Who we are" section (this file) |
|
|
407
|
+
| What success looks like / vision | \`team-foundry/product/north-star.md\` |
|
|
408
|
+
| What we're working toward this quarter | \`team-foundry/product/outcomes.md\` |
|
|
409
|
+
| Who our customers are | \`team-foundry/product/customers.md\` |
|
|
410
|
+
| What we're building now / next / later | \`team-foundry/product/now-next-later.md\` |
|
|
411
|
+
| Strategic logic and guiding policy | \`team-foundry/product/strategy.md\` |
|
|
412
|
+
| Open assumptions and untested bets | \`team-foundry/product/assumptions.md\` |
|
|
413
|
+
| Key product risks | \`team-foundry/product/risks.md\` |
|
|
414
|
+
| How the product trio works | \`team-foundry/team/trio.md\` |
|
|
415
|
+
| Team norms, DoD, ceremonies | \`team-foundry/team/working-agreement.md\` |
|
|
416
|
+
| How we use AI tools | \`team-foundry/team/ai-practices.md\` |
|
|
417
|
+
| Tech stack and conventions | \`team-foundry/engineering/stack.md\` |
|
|
418
|
+
| Quality stance and tech debt policy | \`team-foundry/engineering/quality-bar.md\` |
|
|
419
|
+
| Past architecture decisions | \`team-foundry/engineering/decisions/\` |
|
|
420
|
+
| Design principles and tone | \`team-foundry/design/principles.md\` |
|
|
421
|
+
| Metric definitions | \`team-foundry/data/metrics.md\` |
|
|
422
|
+
| Domain terms and acronyms | \`team-foundry/context/glossary.md\` |
|
|
423
|
+
| Stakeholders and what they care about | \`team-foundry/context/stakeholders.md\` |
|
|
424
|
+
|
|
425
|
+
## Coach
|
|
426
|
+
|
|
427
|
+
The team-foundry coach keeps these files honest over time. It runs automatically when
|
|
428
|
+
it notices something relevant to your current work. You can also invoke it directly:
|
|
429
|
+
|
|
430
|
+
| What to say | What happens |
|
|
431
|
+
|---|---|
|
|
432
|
+
| "Let's set up our team-foundry" | Runs the onboarding interview (first time only) |
|
|
433
|
+
| "let's do a team-foundry review" | Full audit \u2014 all files checked, findings listed |
|
|
434
|
+
| "coach mode" | Same as above |
|
|
435
|
+
| "review our [outcomes / customers / stack / etc.]" | Targeted review of one file |
|
|
436
|
+
| "what's missing from team-foundry?" | Lists gaps across all files |
|
|
437
|
+
| "run the weekly team-foundry review" | Weekly check-in, top 3 issues surfaced |
|
|
438
|
+
|
|
439
|
+
<!-- AI instructions:
|
|
440
|
+
- Normal coding sessions: do NOT load coach.md. Use the routing map above to load
|
|
441
|
+
specific files only when directly relevant to the user's question.
|
|
442
|
+
- Explicit mode / Scheduled mode / onboarding: load .team-foundry/coach.md in full
|
|
443
|
+
before activating any mode. Triggered only by the phrases in the table above.
|
|
444
|
+
- Inline mode nudges: if you notice a clear gap in a team-foundry file while answering
|
|
445
|
+
a normal question, surface it in one sentence \u2014 without loading the full coach.md.
|
|
446
|
+
Keep it brief and non-blocking. Do not coach unprompted on back-to-back messages. -->
|
|
447
|
+
`;
|
|
448
|
+
}
|
|
449
|
+
|
|
229
450
|
// src/templates/getting-started.ts
|
|
230
451
|
function gettingStartedTemplate(ctx) {
|
|
231
452
|
const toolName = ctx.tool === "gemini" ? "Gemini CLI" : ctx.tool === "both" ? "Claude Code or Gemini CLI" : "Claude Code";
|
|
@@ -315,8 +536,14 @@ You can delete this file once the onboarding interview is complete.
|
|
|
315
536
|
// src/templates/coach.ts
|
|
316
537
|
function coachTemplate(ctx) {
|
|
317
538
|
const isSolo = ctx.profile === "solo";
|
|
318
|
-
const questionCount = isSolo ? "
|
|
539
|
+
const questionCount = isSolo ? "9" : "18\u201325";
|
|
319
540
|
const timeEstimate = isSolo ? "15\u201320 minutes" : "25\u201335 minutes";
|
|
541
|
+
const featureQueryFullSteps = isSolo ? "" : `3. Read \`team-foundry/product/now-next-later.md\` \u2014 find the feature's current status (Now / Next / Later / shipped)
|
|
542
|
+
4. Read \`team-foundry/product/assumptions.md\` \u2014 find any assumptions linked to this feature
|
|
543
|
+
5. Read \`team-foundry/engineering/decisions/\` \u2014 find any ADRs related to how it's being built
|
|
544
|
+
`;
|
|
545
|
+
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.";
|
|
546
|
+
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
547
|
return `---
|
|
321
548
|
purpose: Full coach playbook \u2014 loaded on demand to preserve token budget
|
|
322
549
|
read_when: When the user triggers coach mode (explicit, inline, scheduled review, or onboarding interview)
|
|
@@ -336,6 +563,67 @@ You are a mirror, not a template pack. The files in this repo are the team's own
|
|
|
336
563
|
thinking. Your role is to reflect it back to them accurately, including the parts
|
|
337
564
|
that have gone stale or were never written down.
|
|
338
565
|
|
|
566
|
+
## Reality observation
|
|
567
|
+
|
|
568
|
+
This is the authoritative protocol for reading git activity. It supersedes any git-reading
|
|
569
|
+
instructions elsewhere in this file (including the "Why this nudge?" block and Behavior 5).
|
|
570
|
+
Those sections define what to surface \u2014 this section defines how to gather the evidence.
|
|
571
|
+
|
|
572
|
+
**When to run:**
|
|
573
|
+
- **Explicit and scheduled modes:** Run all four steps before any behavior fires.
|
|
574
|
+
- **Inline mode:** Run Steps 1\u20132 only when an inline trigger fires, scoped to the specific
|
|
575
|
+
file being evaluated. Do not run a full git audit on every inline response \u2014 that
|
|
576
|
+
contradicts the token-budget framing of this file.
|
|
577
|
+
|
|
578
|
+
**Step 1 \u2014 Read recent commits.**
|
|
579
|
+
|
|
580
|
+
\`\`\`
|
|
581
|
+
git log --oneline --since="30 days ago"
|
|
582
|
+
\`\`\`
|
|
583
|
+
|
|
584
|
+
This returns all commits regardless of merge strategy. Extract:
|
|
585
|
+
- Total commit count in the window
|
|
586
|
+
- Commit messages that describe shipped features, decisions, or major changes
|
|
587
|
+
|
|
588
|
+
**Merge strategy note:** \`git log --merges\` returns zero results on repos that use
|
|
589
|
+
squash-merge or rebase-merge (GitHub's common defaults). Do not use it as the primary
|
|
590
|
+
activity signal. Instead, use total commit count from the regular log. If commit volume
|
|
591
|
+
is high (>10 commits) but \`--merges\` returns zero, conclude "squash or rebase merge
|
|
592
|
+
strategy \u2014 using commit messages as activity proxy" and proceed accordingly.
|
|
593
|
+
|
|
594
|
+
**Step 2 \u2014 Check \`last_updated\` dates in team-foundry files.**
|
|
595
|
+
|
|
596
|
+
For each file being evaluated, note its \`last_updated\` frontmatter date. Calculate:
|
|
597
|
+
- Days between \`last_updated\` and today
|
|
598
|
+
- Commits since that date: \`git log --oneline --since="<last_updated date>"\`
|
|
599
|
+
- If the log returns no results, treat activity as zero and skip the high-activity threshold.
|
|
600
|
+
|
|
601
|
+
**Step 3 \u2014 Build the observed reality summary (internal, not shown to user).**
|
|
602
|
+
|
|
603
|
+
Synthesize into a mental model carried through the rest of the session:
|
|
604
|
+
- What shipped recently (from commit messages)
|
|
605
|
+
- Which team-foundry files haven't been updated since shipping started
|
|
606
|
+
- Activity level: high (>3 commits since last update) or low (\u22643 commits since last update).
|
|
607
|
+
High activity means push harder on drift. Low activity means lighter touch.
|
|
608
|
+
|
|
609
|
+
**Step 4 \u2014 Use this in every finding.**
|
|
610
|
+
|
|
611
|
+
When surfacing drift, always cite the observed reality explicitly. This is the single format
|
|
612
|
+
for "Why this nudge?" across all behaviors:
|
|
613
|
+
|
|
614
|
+
> "[N] commits have merged since outcomes.md was last updated ([X] days ago). Based on
|
|
615
|
+
> recent commit messages \u2014 [list 2-3 relevant ones] \u2014 it looks like [what changed].
|
|
616
|
+
> outcomes.md doesn't reflect this yet."
|
|
617
|
+
|
|
618
|
+
**Fallback when git is unavailable:** If the tool doesn't have shell access, fall back to
|
|
619
|
+
\`last_updated\` dates only and note: "I don't have access to git history \u2014 using
|
|
620
|
+
\`last_updated\` dates as the staleness signal."
|
|
621
|
+
|
|
622
|
+
**What counts as high activity:** More than 3 commits since the file was last updated.
|
|
623
|
+
This is the single threshold used throughout this file.
|
|
624
|
+
|
|
625
|
+
---
|
|
626
|
+
|
|
339
627
|
## Activation modes
|
|
340
628
|
|
|
341
629
|
You have three activation modes. Read which one applies and behave accordingly.
|
|
@@ -524,6 +812,31 @@ order and **name the conflict explicitly** rather than silently picking one:
|
|
|
524
812
|
Say: "I see a conflict between [file A] and [file B]. Based on the context priority
|
|
525
813
|
order, I'm going with [file A] \u2014 but you may want to reconcile these."
|
|
526
814
|
|
|
815
|
+
When running any coaching behavior, also load \`.team-foundry/team-lessons.md\` if it exists.
|
|
816
|
+
Apply Active rules from that file alongside built-in behaviors \u2014 they carry equal weight.
|
|
817
|
+
|
|
818
|
+
## Feature queries
|
|
819
|
+
|
|
820
|
+
**Applies in explicit and scheduled modes.** In inline mode, treat feature questions as
|
|
821
|
+
potential B5 (reality drift) triggers rather than running this full multi-file read.
|
|
822
|
+
|
|
823
|
+
When the user asks about a specific feature \u2014 "tell me about X," "what's the status of Y,"
|
|
824
|
+
"what do we know about Z," "has X shipped?," "where are we on X?," "who owns X?," or any
|
|
825
|
+
close variant \u2014 read the following files in order:
|
|
826
|
+
|
|
827
|
+
1. Read \`team-foundry/product/outcomes.md\` \u2014 find which outcome this feature supports (the why)
|
|
828
|
+
2. Read \`team-foundry/product/customers.md\` \u2014 find customer quotes or personas that motivated it
|
|
829
|
+
${featureQueryFullSteps}
|
|
830
|
+
For each file above: if the file doesn't exist on disk, skip it silently. If it exists
|
|
831
|
+
but doesn't mention the feature, note that gap explicitly \u2014 don't invent connections.
|
|
832
|
+
|
|
833
|
+
Synthesize into a single response: ${featureQuerySynthesis}
|
|
834
|
+
|
|
835
|
+
If the feature doesn't appear in any file, say: "I couldn't find [X] in any team-foundry
|
|
836
|
+
file \u2014 it may be undocumented or tracked under a different name. Want me to help capture it?"
|
|
837
|
+
|
|
838
|
+
The team-foundry files are the index. ${featureQueryIndexNote}
|
|
839
|
+
|
|
527
840
|
---
|
|
528
841
|
|
|
529
842
|
## Behaviors
|
|
@@ -537,6 +850,11 @@ For every finding: name it specifically (cite the file and the exact content),
|
|
|
537
850
|
explain why it matters in one sentence, offer to draft the fix. Never list a finding
|
|
538
851
|
without a proposed next step.
|
|
539
852
|
|
|
853
|
+
**"Why this nudge?" \u2014 required for every drift finding.** Use the evidence gathered in the
|
|
854
|
+
Reality observation section above. Always be specific \u2014 a team that understands why a nudge
|
|
855
|
+
fired is far more likely to act on it. Never say "this looks stale" without citing the
|
|
856
|
+
commit count, day delta, and (if blank) missing owner.
|
|
857
|
+
|
|
540
858
|
---
|
|
541
859
|
|
|
542
860
|
### Behavior 1: Outputs framed as outcomes
|
|
@@ -1094,6 +1412,61 @@ as a deliberate strategy update. If item should be removed: flag only \u2014 do
|
|
|
1094
1412
|
the current strategy.md guiding policy explicitly excludes \u2014 or when strategy.md has no
|
|
1095
1413
|
Guiding Policy filled in.
|
|
1096
1414
|
|
|
1415
|
+
### Behavior 17: Team-specific lesson capture
|
|
1416
|
+
|
|
1417
|
+
**Severity:** Informational \u2014 surfaced as an offer, never a blocker.
|
|
1418
|
+
|
|
1419
|
+
**Trigger condition:** The user's message contains a recurring-pattern signal:
|
|
1420
|
+
- "we keep doing X"
|
|
1421
|
+
- "this is the third time we've had this problem"
|
|
1422
|
+
- "we always confuse Y with Z"
|
|
1423
|
+
- "every time we [situation], we [result]"
|
|
1424
|
+
- Any close variant signaling a pattern the team has noticed about itself.
|
|
1425
|
+
|
|
1426
|
+
**What to say:**
|
|
1427
|
+
> "Sounds like a recurring pattern. Want me to add a coaching rule to \`.team-foundry/team-lessons.md\`
|
|
1428
|
+
> so I watch for this on future reviews?"
|
|
1429
|
+
|
|
1430
|
+
**If the user confirms:**
|
|
1431
|
+
1. Draft the rule in this format:
|
|
1432
|
+
\`- [date] [concise rule] \u2014 [brief context]\`
|
|
1433
|
+
2. Ask: "Does this capture it, or want to edit the wording?"
|
|
1434
|
+
3. After confirmation, write it to the Active rules section of \`.team-foundry/team-lessons.md\`.
|
|
1435
|
+
If the file doesn't exist, create it with this structure:
|
|
1436
|
+
|
|
1437
|
+
\`\`\`markdown
|
|
1438
|
+
---
|
|
1439
|
+
purpose: Team-specific coaching rules learned from this team's patterns
|
|
1440
|
+
read_when: Coach runs any coaching behavior
|
|
1441
|
+
last_updated: [date]
|
|
1442
|
+
---
|
|
1443
|
+
|
|
1444
|
+
# Team lessons
|
|
1445
|
+
|
|
1446
|
+
Rules this specific team has accumulated for their coach.
|
|
1447
|
+
Added when the team flags a recurring issue they want the coach to watch for.
|
|
1448
|
+
|
|
1449
|
+
## Active rules
|
|
1450
|
+
|
|
1451
|
+
- [date] [rule] \u2014 [context]
|
|
1452
|
+
|
|
1453
|
+
## Retired rules
|
|
1454
|
+
|
|
1455
|
+
<!-- Move rules here when no longer relevant, with the date retired. -->
|
|
1456
|
+
\`\`\`
|
|
1457
|
+
|
|
1458
|
+
**Rule retirement:** When a team says a rule is no longer relevant ("we fixed that," "we changed process"),
|
|
1459
|
+
offer to retire it: move it from Active rules to Retired rules with today's date prepended.
|
|
1460
|
+
|
|
1461
|
+
**Loading instruction:** When running any coaching behavior (inline, explicit, or scheduled),
|
|
1462
|
+
check if \`.team-foundry/team-lessons.md\` exists. If it does, load it and apply Active rules
|
|
1463
|
+
with equal weight to built-in behaviors, scoped to this team's context.
|
|
1464
|
+
|
|
1465
|
+
**What not to do:** Do not proactively suggest adding rules unless the user explicitly names a pattern.
|
|
1466
|
+
This behavior is listener-only \u2014 it waits for the signal, it does not fish for it.
|
|
1467
|
+
|
|
1468
|
+
---
|
|
1469
|
+
|
|
1097
1470
|
## Quarterly retrospective
|
|
1098
1471
|
|
|
1099
1472
|
### Trigger
|
|
@@ -1491,11 +1864,11 @@ Example answers:
|
|
|
1491
1864
|
|
|
1492
1865
|
---
|
|
1493
1866
|
|
|
1494
|
-
|
|
1867
|
+
${isSolo ? "" : `### Theme 3: Measurement
|
|
1495
1868
|
|
|
1496
1869
|
*Files written: data/metrics.md*
|
|
1497
1870
|
|
|
1498
|
-
**
|
|
1871
|
+
**Q7. What are the 3\u20135 numbers you actually look at to know if the product is healthy?**
|
|
1499
1872
|
*Why it matters: data/metrics.md is read whenever the AI is asked about product performance.
|
|
1500
1873
|
Undefined metrics cause disagreements \u2014 two people reading the same number and reaching different conclusions.*
|
|
1501
1874
|
|
|
@@ -1506,7 +1879,7 @@ Example answers:
|
|
|
1506
1879
|
- "Listing-to-sale rate \u2014 % of active listings that get bought within 30 days, from our DB."
|
|
1507
1880
|
- "P1 bug count \u2014 open bugs tagged P1 in Linear, reviewed Monday mornings."
|
|
1508
1881
|
|
|
1509
|
-
*After the answer: write each metric as a definition block to data/metrics.md
|
|
1882
|
+
*After the answer: write each metric as a definition block to data/metrics.md.*`}
|
|
1510
1883
|
|
|
1511
1884
|
---
|
|
1512
1885
|
|
|
@@ -1514,7 +1887,7 @@ Example answers:
|
|
|
1514
1887
|
|
|
1515
1888
|
*Files written: product/customers.md*
|
|
1516
1889
|
|
|
1517
|
-
**Q${isSolo ? "
|
|
1890
|
+
**Q${isSolo ? "5" : "8"}. Name three customers you've spoken to directly.**
|
|
1518
1891
|
*Why it matters: customers.md is read whenever the AI helps with prioritization, specs,
|
|
1519
1892
|
or discovery. Generic personas don't resolve real disagreements. Named customers do.*
|
|
1520
1893
|
|
|
@@ -1576,7 +1949,7 @@ Example answers:
|
|
|
1576
1949
|
|
|
1577
1950
|
*After the answer: write to engineering/quality-bar.md (tech debt stance).*`}
|
|
1578
1951
|
|
|
1579
|
-
**Q${isSolo ? "
|
|
1952
|
+
**Q${isSolo ? "6" : "12"}. What does "shipped" mean on your team?**
|
|
1580
1953
|
*Why it matters: misaligned definitions of done cause the most common sprint friction.
|
|
1581
1954
|
Writing it down means the argument happens once, not every week.*
|
|
1582
1955
|
|
|
@@ -1658,7 +2031,7 @@ Example answers:
|
|
|
1658
2031
|
|
|
1659
2032
|
*Files written: engineering/stack.md*
|
|
1660
2033
|
|
|
1661
|
-
**Q${isSolo ? "
|
|
2034
|
+
**Q${isSolo ? "7" : "18"}. What's the tech stack, and what would surprise an incoming engineer?**
|
|
1662
2035
|
*Why it matters: stack.md is read every time the AI helps write or review code.
|
|
1663
2036
|
The "what would surprise" framing surfaces the non-obvious conventions.*
|
|
1664
2037
|
|
|
@@ -1691,7 +2064,7 @@ If no: note that the decisions/ folder is ready when one comes up.
|
|
|
1691
2064
|
|
|
1692
2065
|
*Files written: context/glossary.md${isSolo ? "" : ", context/stakeholders.md"}*
|
|
1693
2066
|
|
|
1694
|
-
**Q${isSolo ? "
|
|
2067
|
+
**Q${isSolo ? "8" : "21"}. What words does your team use that would confuse an outsider?**
|
|
1695
2068
|
*Why it matters: glossary.md is read when the AI writes specs, reviews code, or
|
|
1696
2069
|
discusses product strategy. Shared vocabulary prevents the AI from guessing at meaning.*
|
|
1697
2070
|
|
|
@@ -1712,7 +2085,7 @@ For each stakeholder: name/role, what they really care about, how they prefer to
|
|
|
1712
2085
|
|
|
1713
2086
|
*After the answer: write to context/stakeholders.md.*`}
|
|
1714
2087
|
|
|
1715
|
-
**Q${isSolo ? "
|
|
2088
|
+
**Q${isSolo ? "9" : "23"}. Are there any terms your team uses inconsistently with each other?**
|
|
1716
2089
|
*Why it matters: inconsistent internal vocabulary is a reliable source of meeting friction.
|
|
1717
2090
|
Naming it here gives the AI a flag to raise when it notices the inconsistency.*
|
|
1718
2091
|
|
|
@@ -1752,6 +2125,7 @@ function northStarTemplate(ctx) {
|
|
|
1752
2125
|
purpose: The single metric that best captures whether we're creating the value we intend to create
|
|
1753
2126
|
read_when: Setting quarterly direction, evaluating big bets, writing OKRs, onboarding new team members
|
|
1754
2127
|
last_updated: ${ctx.date}
|
|
2128
|
+
owner:
|
|
1755
2129
|
---
|
|
1756
2130
|
|
|
1757
2131
|
# North Star
|
|
@@ -1805,6 +2179,7 @@ function outcomesTemplate(ctx) {
|
|
|
1805
2179
|
purpose: Current quarter outcomes \u2014 the changes in customer behavior that define success this quarter
|
|
1806
2180
|
read_when: Prioritizing work, writing specs, deciding what to build next, evaluating tradeoffs
|
|
1807
2181
|
last_updated: ${ctx.date}
|
|
2182
|
+
owner:
|
|
1808
2183
|
---
|
|
1809
2184
|
|
|
1810
2185
|
# Outcomes
|
|
@@ -1848,6 +2223,7 @@ function customersTemplate(ctx) {
|
|
|
1848
2223
|
purpose: Named customers, personas, jobs to be done, and direct quotes from real conversations
|
|
1849
2224
|
read_when: Writing specs, prioritizing features, evaluating tradeoffs, any time you're guessing what customers want
|
|
1850
2225
|
last_updated: ${ctx.date}
|
|
2226
|
+
owner:
|
|
1851
2227
|
---
|
|
1852
2228
|
|
|
1853
2229
|
# Customers
|
|
@@ -1901,6 +2277,7 @@ function nowNextLaterTemplate(ctx) {
|
|
|
1901
2277
|
purpose: What we're building now, what we're committed to next, and what's directional
|
|
1902
2278
|
read_when: Sprint planning, stakeholder updates, evaluating new requests, prioritization discussions
|
|
1903
2279
|
last_updated: ${ctx.date}
|
|
2280
|
+
owner:
|
|
1904
2281
|
---
|
|
1905
2282
|
|
|
1906
2283
|
# Now / Next / Later
|
|
@@ -1960,6 +2337,7 @@ function assumptionsTemplate(ctx) {
|
|
|
1960
2337
|
purpose: Open assumptions and untested beliefs \u2014 the bets the team is currently making
|
|
1961
2338
|
read_when: Designing discovery work, scoping experiments, retros, any time a decision feels risky
|
|
1962
2339
|
last_updated: ${ctx.date}
|
|
2340
|
+
owner:
|
|
1963
2341
|
---
|
|
1964
2342
|
|
|
1965
2343
|
# Assumptions
|
|
@@ -2035,6 +2413,7 @@ function risksTemplate(ctx) {
|
|
|
2035
2413
|
purpose: The four product risks \u2014 tracked so they don't become surprises at launch
|
|
2036
2414
|
read_when: Scoping new features, go/no-go decisions, discovery planning, quarterly reviews
|
|
2037
2415
|
last_updated: ${ctx.date}
|
|
2416
|
+
owner:
|
|
2038
2417
|
---
|
|
2039
2418
|
|
|
2040
2419
|
# Risks
|
|
@@ -2094,6 +2473,7 @@ function trioTemplate(ctx) {
|
|
|
2094
2473
|
purpose: The product trio \u2014 who owns what decisions and how the three roles work together
|
|
2095
2474
|
read_when: Escalations, onboarding, clarifying ownership, any "who decides this?" conversation
|
|
2096
2475
|
last_updated: ${ctx.date}
|
|
2476
|
+
owner:
|
|
2097
2477
|
---
|
|
2098
2478
|
|
|
2099
2479
|
# Team Trio
|
|
@@ -2115,11 +2495,17 @@ last_updated: ${ctx.date}
|
|
|
2115
2495
|
|
|
2116
2496
|
## Members
|
|
2117
2497
|
|
|
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 |
|
|
2498
|
+
| Role | Person | GitHub (optional) | Focus area |
|
|
2499
|
+
|---|---|---|---|
|
|
2500
|
+
| Product Manager | <!-- name --> | <!-- @handle --> | What to build and why |
|
|
2501
|
+
| Engineering Lead | <!-- name --> | <!-- @handle --> | How to build it, tech debt, architecture |
|
|
2502
|
+
| Design Lead | <!-- name --> | <!-- @handle --> | UX, flows, visual quality |
|
|
2503
|
+
|
|
2504
|
+
<!-- Optional additional fields per member (add inline or as a sub-list):
|
|
2505
|
+
- slack: U0C3D4E5F (Slack member ID for @mentions)
|
|
2506
|
+
- linear: user ID (for issue assignment lookups)
|
|
2507
|
+
Fill in what's known; leave blank what isn't. These fields let AI tools
|
|
2508
|
+
take cross-platform actions when relevant. -->
|
|
2123
2509
|
|
|
2124
2510
|
## How we make decisions
|
|
2125
2511
|
|
|
@@ -2151,6 +2537,7 @@ function workingAgreementTemplate(ctx) {
|
|
|
2151
2537
|
purpose: Definition of done, definition of ready, ceremonies, and team norms \u2014 the honest version
|
|
2152
2538
|
read_when: Code review, sprint planning, retrospectives, any "this isn't how we said we'd work" moment
|
|
2153
2539
|
last_updated: ${ctx.date}
|
|
2540
|
+
owner:
|
|
2154
2541
|
---
|
|
2155
2542
|
|
|
2156
2543
|
# Working Agreement
|
|
@@ -2221,6 +2608,7 @@ function aiPracticesTemplate(ctx) {
|
|
|
2221
2608
|
purpose: How this team uses AI tools \u2014 what's working, what we've decided not to do, and our norms
|
|
2222
2609
|
read_when: Onboarding engineers, evaluating new AI tooling, retrospectives on AI-assisted work
|
|
2223
2610
|
last_updated: ${ctx.date}
|
|
2611
|
+
owner:
|
|
2224
2612
|
---
|
|
2225
2613
|
|
|
2226
2614
|
# AI Practices
|
|
@@ -2275,6 +2663,7 @@ function stackTemplate(ctx) {
|
|
|
2275
2663
|
purpose: Tech stack, conventions, deployment pipeline, and local dev setup
|
|
2276
2664
|
read_when: Writing code, reviewing PRs, evaluating new dependencies, onboarding engineers
|
|
2277
2665
|
last_updated: ${ctx.date}
|
|
2666
|
+
owner:
|
|
2278
2667
|
---
|
|
2279
2668
|
|
|
2280
2669
|
# Engineering Stack
|
|
@@ -2324,6 +2713,7 @@ function qualityBarTemplate(ctx) {
|
|
|
2324
2713
|
purpose: The team's honest stance on tech debt, bugs, and what "shipped" actually means
|
|
2325
2714
|
read_when: Code review, sprint planning, evaluating shortcuts, any quality-vs-speed conversation
|
|
2326
2715
|
last_updated: ${ctx.date}
|
|
2716
|
+
owner:
|
|
2327
2717
|
---
|
|
2328
2718
|
|
|
2329
2719
|
# Quality Bar
|
|
@@ -2394,6 +2784,7 @@ function decisionsReadmeTemplate(ctx) {
|
|
|
2394
2784
|
purpose: Index and template for architecture decision records (ADRs)
|
|
2395
2785
|
read_when: Evaluating architectural choices, understanding why the codebase looks the way it does
|
|
2396
2786
|
last_updated: ${ctx.date}
|
|
2787
|
+
owner:
|
|
2397
2788
|
---
|
|
2398
2789
|
|
|
2399
2790
|
# Architecture Decisions
|
|
@@ -2450,6 +2841,7 @@ function principlesTemplate(ctx) {
|
|
|
2450
2841
|
purpose: Design principles, tone of voice, and accessibility stance
|
|
2451
2842
|
read_when: Designing new features, writing copy, reviewing designs, evaluating UX tradeoffs
|
|
2452
2843
|
last_updated: ${ctx.date}
|
|
2844
|
+
owner:
|
|
2453
2845
|
---
|
|
2454
2846
|
|
|
2455
2847
|
# Design Principles
|
|
@@ -2507,6 +2899,7 @@ function metricsTemplate(ctx) {
|
|
|
2507
2899
|
purpose: Metric definitions, ownership, and data sources \u2014 so the team means the same thing
|
|
2508
2900
|
read_when: Building dashboards, writing OKRs, reviewing product health, debugging data discrepancies
|
|
2509
2901
|
last_updated: ${ctx.date}
|
|
2902
|
+
owner:
|
|
2510
2903
|
---
|
|
2511
2904
|
|
|
2512
2905
|
# Metrics
|
|
@@ -2553,6 +2946,7 @@ function glossaryTemplate(ctx) {
|
|
|
2553
2946
|
purpose: Domain terms, acronyms, and jargon specific to this team and product
|
|
2554
2947
|
read_when: Onboarding, writing specs, any time a term feels ambiguous or overloaded
|
|
2555
2948
|
last_updated: ${ctx.date}
|
|
2949
|
+
owner:
|
|
2556
2950
|
---
|
|
2557
2951
|
|
|
2558
2952
|
# Glossary
|
|
@@ -2594,6 +2988,7 @@ function stakeholdersTemplate(ctx) {
|
|
|
2594
2988
|
purpose: Who cares about this product, what they care about, and how the team works with them
|
|
2595
2989
|
read_when: Stakeholder updates, go/no-go decisions, escalations, quarterly planning
|
|
2596
2990
|
last_updated: ${ctx.date}
|
|
2991
|
+
owner:
|
|
2597
2992
|
---
|
|
2598
2993
|
|
|
2599
2994
|
# Stakeholders
|
|
@@ -2637,6 +3032,7 @@ function strategyTemplate(ctx) {
|
|
|
2637
3032
|
purpose: The strategic logic connecting our north-star gap to what we're building. Read before adding anything to the roadmap.
|
|
2638
3033
|
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
3034
|
last_updated: ${ctx.date}
|
|
3035
|
+
owner:
|
|
2640
3036
|
---
|
|
2641
3037
|
|
|
2642
3038
|
# Strategy
|
|
@@ -2725,6 +3121,14 @@ var FULL_ONLY_ENTRIES = [
|
|
|
2725
3121
|
{ relativePath: "team-foundry/context/stakeholders.md", content: stakeholdersTemplate },
|
|
2726
3122
|
{ relativePath: "team-foundry/product/strategy.md", content: strategyTemplate }
|
|
2727
3123
|
];
|
|
3124
|
+
var FEDERATED_ENTRIES = [
|
|
3125
|
+
{ relativePath: "team-foundry/product/CLAUDE.md", content: federatedProductTemplate },
|
|
3126
|
+
{ relativePath: "team-foundry/team/CLAUDE.md", content: federatedTeamTemplate },
|
|
3127
|
+
{ relativePath: "team-foundry/engineering/CLAUDE.md", content: federatedEngineeringTemplate },
|
|
3128
|
+
{ relativePath: "team-foundry/design/CLAUDE.md", content: federatedDesignTemplate },
|
|
3129
|
+
{ relativePath: "team-foundry/data/CLAUDE.md", content: federatedDataTemplate },
|
|
3130
|
+
{ relativePath: "team-foundry/context/CLAUDE.md", content: federatedContextTemplate }
|
|
3131
|
+
];
|
|
2728
3132
|
function rootEntries(tool) {
|
|
2729
3133
|
if (tool === "claude") {
|
|
2730
3134
|
return [{ relativePath: "CLAUDE.md", content: rootClaudeTemplate }];
|
|
@@ -2732,18 +3136,22 @@ function rootEntries(tool) {
|
|
|
2732
3136
|
if (tool === "gemini") {
|
|
2733
3137
|
return [{ relativePath: "GEMINI.md", content: rootGeminiTemplate }];
|
|
2734
3138
|
}
|
|
3139
|
+
if (tool === "cursor") {
|
|
3140
|
+
return [{ relativePath: ".cursor/rules/team-foundry.mdc", content: rootCursorTemplate }];
|
|
3141
|
+
}
|
|
2735
3142
|
return [
|
|
2736
3143
|
{ relativePath: "CLAUDE.md", content: rootClaudeTemplate },
|
|
2737
3144
|
{ relativePath: "GEMINI.md", content: rootGeminiTemplate }
|
|
2738
3145
|
];
|
|
2739
3146
|
}
|
|
2740
3147
|
async function scaffold(options) {
|
|
2741
|
-
const { targetDir, profile, tool, repoVisibility, date, ingestionPath, ingestion } = options;
|
|
3148
|
+
const { targetDir, profile, tool, repoVisibility, date, ingestionPath, ingestion, federated } = options;
|
|
2742
3149
|
const ctx = { profile, tool, repoVisibility, date, ingestionPath, ingestion };
|
|
2743
3150
|
const entries = [
|
|
2744
3151
|
...rootEntries(tool),
|
|
2745
3152
|
...SOLO_ENTRIES,
|
|
2746
|
-
...profile === "full" ? FULL_ONLY_ENTRIES : []
|
|
3153
|
+
...profile === "full" ? FULL_ONLY_ENTRIES : [],
|
|
3154
|
+
...profile === "full" && federated ? FEDERATED_ENTRIES : []
|
|
2747
3155
|
];
|
|
2748
3156
|
for (const entry of entries) {
|
|
2749
3157
|
const fullPath = path.join(targetDir, entry.relativePath);
|
|
@@ -2778,10 +3186,185 @@ async function writeGitignore(targetDir) {
|
|
|
2778
3186
|
`, "utf-8");
|
|
2779
3187
|
}
|
|
2780
3188
|
|
|
3189
|
+
// src/status.ts
|
|
3190
|
+
import fs3 from "fs/promises";
|
|
3191
|
+
import path3 from "path";
|
|
3192
|
+
import { spawnSync } from "child_process";
|
|
3193
|
+
var SOLO_FILES = [
|
|
3194
|
+
"team-foundry/product/north-star.md",
|
|
3195
|
+
"team-foundry/product/outcomes.md",
|
|
3196
|
+
"team-foundry/product/customers.md",
|
|
3197
|
+
"team-foundry/engineering/stack.md"
|
|
3198
|
+
];
|
|
3199
|
+
var FULL_ONLY_FILES = [
|
|
3200
|
+
"team-foundry/product/now-next-later.md",
|
|
3201
|
+
"team-foundry/product/assumptions.md",
|
|
3202
|
+
"team-foundry/product/risks.md",
|
|
3203
|
+
"team-foundry/product/strategy.md",
|
|
3204
|
+
"team-foundry/team/trio.md",
|
|
3205
|
+
"team-foundry/team/working-agreement.md",
|
|
3206
|
+
"team-foundry/team/ai-practices.md",
|
|
3207
|
+
"team-foundry/engineering/quality-bar.md",
|
|
3208
|
+
"team-foundry/design/principles.md",
|
|
3209
|
+
"team-foundry/data/metrics.md",
|
|
3210
|
+
"team-foundry/context/glossary.md",
|
|
3211
|
+
"team-foundry/context/stakeholders.md"
|
|
3212
|
+
];
|
|
3213
|
+
var ALL_FILES = [...SOLO_FILES, ...FULL_ONLY_FILES];
|
|
3214
|
+
var STALE_DAYS = 45;
|
|
3215
|
+
function parseFrontmatter(content) {
|
|
3216
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
3217
|
+
if (!match) return {};
|
|
3218
|
+
const result = {};
|
|
3219
|
+
for (const line of match[1].split("\n")) {
|
|
3220
|
+
const idx = line.indexOf(":");
|
|
3221
|
+
if (idx === -1) continue;
|
|
3222
|
+
const key = line.slice(0, idx).trim();
|
|
3223
|
+
const value = line.slice(idx + 1).trim();
|
|
3224
|
+
result[key] = value;
|
|
3225
|
+
}
|
|
3226
|
+
return result;
|
|
3227
|
+
}
|
|
3228
|
+
function daysSince(dateStr) {
|
|
3229
|
+
const then = new Date(dateStr).getTime();
|
|
3230
|
+
if (isNaN(then)) return null;
|
|
3231
|
+
return Math.floor((Date.now() - then) / 864e5);
|
|
3232
|
+
}
|
|
3233
|
+
function prsSinceDate(targetDir, dateStr) {
|
|
3234
|
+
try {
|
|
3235
|
+
const since = new Date(dateStr).toISOString();
|
|
3236
|
+
if (isNaN(new Date(dateStr).getTime())) return null;
|
|
3237
|
+
const result = spawnSync(
|
|
3238
|
+
"git",
|
|
3239
|
+
["-C", targetDir, "log", "--oneline", "--merges", `--since=${since}`],
|
|
3240
|
+
{ encoding: "utf-8", timeout: 5e3 }
|
|
3241
|
+
);
|
|
3242
|
+
if (result.status !== 0) return null;
|
|
3243
|
+
return result.stdout.trim().split("\n").filter(Boolean).length;
|
|
3244
|
+
} catch {
|
|
3245
|
+
return null;
|
|
3246
|
+
}
|
|
3247
|
+
}
|
|
3248
|
+
function isEffectivelyEmpty(body) {
|
|
3249
|
+
const stripped = body.replace(/<!--[\s\S]*?-->/g, "").replace(/^#+\s.*$/gm, "").trim();
|
|
3250
|
+
return stripped.length < 30;
|
|
3251
|
+
}
|
|
3252
|
+
async function analyzeFile(targetDir, relativePath) {
|
|
3253
|
+
const fullPath = path3.join(targetDir, relativePath);
|
|
3254
|
+
let exists = false;
|
|
3255
|
+
let isEmpty = false;
|
|
3256
|
+
let lastUpdated = null;
|
|
3257
|
+
let owner = null;
|
|
3258
|
+
let daysSinceUpdate = null;
|
|
3259
|
+
let prsSince = null;
|
|
3260
|
+
try {
|
|
3261
|
+
const content = await fs3.readFile(fullPath, "utf-8");
|
|
3262
|
+
exists = true;
|
|
3263
|
+
const body = content.replace(/^---[\s\S]*?---\n?/, "").trim();
|
|
3264
|
+
isEmpty = isEffectivelyEmpty(body);
|
|
3265
|
+
const fm = parseFrontmatter(content);
|
|
3266
|
+
if (fm["last_updated"]) {
|
|
3267
|
+
lastUpdated = fm["last_updated"];
|
|
3268
|
+
daysSinceUpdate = daysSince(lastUpdated);
|
|
3269
|
+
prsSince = prsSinceDate(targetDir, lastUpdated);
|
|
3270
|
+
}
|
|
3271
|
+
owner = fm["owner"] || null;
|
|
3272
|
+
} catch {
|
|
3273
|
+
}
|
|
3274
|
+
let health = "ok";
|
|
3275
|
+
if (!exists) health = "missing";
|
|
3276
|
+
else if (isEmpty) health = "empty";
|
|
3277
|
+
else if (daysSinceUpdate !== null && daysSinceUpdate >= STALE_DAYS) health = "stale";
|
|
3278
|
+
return {
|
|
3279
|
+
relativePath,
|
|
3280
|
+
lastUpdated,
|
|
3281
|
+
owner,
|
|
3282
|
+
daysSinceUpdate,
|
|
3283
|
+
prsSinceUpdate: prsSince,
|
|
3284
|
+
exists,
|
|
3285
|
+
isEmpty,
|
|
3286
|
+
health
|
|
3287
|
+
};
|
|
3288
|
+
}
|
|
3289
|
+
function healthIcon(health) {
|
|
3290
|
+
if (health === "ok") return "\u2713";
|
|
3291
|
+
if (health === "stale") return "~";
|
|
3292
|
+
if (health === "empty") return "\u25CB";
|
|
3293
|
+
return "\u2717";
|
|
3294
|
+
}
|
|
3295
|
+
function formatRow(s) {
|
|
3296
|
+
const icon = healthIcon(s.health);
|
|
3297
|
+
const rawName = s.relativePath.replace("team-foundry/", "");
|
|
3298
|
+
const name = rawName.slice(0, 42).padEnd(42);
|
|
3299
|
+
const updated = s.lastUpdated ?? "\u2014";
|
|
3300
|
+
const age = s.daysSinceUpdate !== null ? `${s.daysSinceUpdate}d` : "\u2014";
|
|
3301
|
+
const prs = s.prsSinceUpdate !== null ? `${s.prsSinceUpdate} PRs` : "\u2014";
|
|
3302
|
+
const owner = s.owner || "\u2014";
|
|
3303
|
+
return ` ${icon} ${name} ${updated.padEnd(12)} ${age.padEnd(6)} ${prs.padEnd(8)} ${owner}`;
|
|
3304
|
+
}
|
|
3305
|
+
function whyNudge(s) {
|
|
3306
|
+
const parts = [];
|
|
3307
|
+
if (s.daysSinceUpdate !== null) parts.push(`${s.daysSinceUpdate} days since last update`);
|
|
3308
|
+
if (s.prsSinceUpdate !== null && s.prsSinceUpdate > 0)
|
|
3309
|
+
parts.push(`${s.prsSinceUpdate} PRs shipped since then`);
|
|
3310
|
+
if (!s.owner) parts.push("no owner set");
|
|
3311
|
+
return parts.join(", ");
|
|
3312
|
+
}
|
|
3313
|
+
async function runStatus(targetDir) {
|
|
3314
|
+
const fullProfileFile = path3.join(targetDir, "team-foundry/team/trio.md");
|
|
3315
|
+
let isFullProfile = false;
|
|
3316
|
+
try {
|
|
3317
|
+
await fs3.access(fullProfileFile);
|
|
3318
|
+
isFullProfile = true;
|
|
3319
|
+
} catch {
|
|
3320
|
+
}
|
|
3321
|
+
const filesToCheck = isFullProfile ? ALL_FILES : SOLO_FILES;
|
|
3322
|
+
const results = await Promise.all(filesToCheck.map((f) => analyzeFile(targetDir, f)));
|
|
3323
|
+
const ok = results.filter((r) => r.health === "ok");
|
|
3324
|
+
const stale = results.filter((r) => r.health === "stale");
|
|
3325
|
+
const empty = results.filter((r) => r.health === "empty");
|
|
3326
|
+
const missing = results.filter((r) => r.health === "missing");
|
|
3327
|
+
console.log("\n team-foundry status\n");
|
|
3328
|
+
console.log(` ${"File".padEnd(44)} ${"Last updated".padEnd(12)} ${"Age".padEnd(6)} ${"PRs".padEnd(8)} Owner`);
|
|
3329
|
+
console.log(` ${"\u2500".repeat(90)}`);
|
|
3330
|
+
for (const s of results) console.log(formatRow(s));
|
|
3331
|
+
console.log();
|
|
3332
|
+
console.log(` \u2713 ${ok.length} current ~ ${stale.length} stale \u25CB ${empty.length} empty \u2717 ${missing.length} missing
|
|
3333
|
+
`);
|
|
3334
|
+
if (stale.length > 0) {
|
|
3335
|
+
console.log(" Stale files \u2014 why this nudge:\n");
|
|
3336
|
+
for (const s of stale) {
|
|
3337
|
+
console.log(` ~ ${s.relativePath.replace("team-foundry/", "")}`);
|
|
3338
|
+
console.log(` ${whyNudge(s)}
|
|
3339
|
+
`);
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3342
|
+
if (empty.length > 0) {
|
|
3343
|
+
console.log(" Empty files \u2014 not yet filled in:\n");
|
|
3344
|
+
for (const s of empty) {
|
|
3345
|
+
console.log(` \u25CB ${s.relativePath.replace("team-foundry/", "")}`);
|
|
3346
|
+
}
|
|
3347
|
+
console.log();
|
|
3348
|
+
}
|
|
3349
|
+
if (missing.length > 0) {
|
|
3350
|
+
console.log(" Missing files \u2014 run `npx create-team-foundry` to scaffold:\n");
|
|
3351
|
+
for (const s of missing) {
|
|
3352
|
+
console.log(` \u2717 ${s.relativePath.replace("team-foundry/", "")}`);
|
|
3353
|
+
}
|
|
3354
|
+
console.log();
|
|
3355
|
+
}
|
|
3356
|
+
const noOwner = results.filter((r) => r.exists && !r.owner);
|
|
3357
|
+
if (noOwner.length > 0) {
|
|
3358
|
+
console.log(` ${noOwner.length} file(s) have no owner set. Add \`owner: <name>\` to their frontmatter.
|
|
3359
|
+
`);
|
|
3360
|
+
}
|
|
3361
|
+
}
|
|
3362
|
+
|
|
2781
3363
|
// src/index.ts
|
|
2782
3364
|
var TOOL_LABEL = {
|
|
2783
3365
|
claude: "Claude Code",
|
|
2784
3366
|
gemini: "Gemini CLI",
|
|
3367
|
+
cursor: "Cursor",
|
|
2785
3368
|
both: "Claude Code or Gemini CLI"
|
|
2786
3369
|
};
|
|
2787
3370
|
var PASTE_PLACEHOLDER = `# Paste your existing docs here
|
|
@@ -2799,16 +3382,16 @@ You can paste multiple documents \u2014 just separate them with a heading like:
|
|
|
2799
3382
|
When you're done, save this file and start the onboarding interview.
|
|
2800
3383
|
`;
|
|
2801
3384
|
async function checkDirectory(targetDir) {
|
|
2802
|
-
const prdPath =
|
|
2803
|
-
const scaffoldPath =
|
|
3385
|
+
const prdPath = path4.join(targetDir, "team-foundry-prd-v2.md");
|
|
3386
|
+
const scaffoldPath = path4.join(targetDir, "src", "scaffold.ts");
|
|
2804
3387
|
let isSourceRepo = false;
|
|
2805
3388
|
try {
|
|
2806
|
-
await
|
|
3389
|
+
await fs4.access(prdPath);
|
|
2807
3390
|
isSourceRepo = true;
|
|
2808
3391
|
} catch {
|
|
2809
3392
|
}
|
|
2810
3393
|
try {
|
|
2811
|
-
await
|
|
3394
|
+
await fs4.access(scaffoldPath);
|
|
2812
3395
|
isSourceRepo = true;
|
|
2813
3396
|
} catch {
|
|
2814
3397
|
}
|
|
@@ -2818,17 +3401,17 @@ async function checkDirectory(targetDir) {
|
|
|
2818
3401
|
);
|
|
2819
3402
|
process.exit(1);
|
|
2820
3403
|
}
|
|
2821
|
-
const pkgPath =
|
|
2822
|
-
const srcPath =
|
|
3404
|
+
const pkgPath = path4.join(targetDir, "package.json");
|
|
3405
|
+
const srcPath = path4.join(targetDir, "src");
|
|
2823
3406
|
let hasPkg = false;
|
|
2824
3407
|
let hasSrc = false;
|
|
2825
3408
|
try {
|
|
2826
|
-
await
|
|
3409
|
+
await fs4.access(pkgPath);
|
|
2827
3410
|
hasPkg = true;
|
|
2828
3411
|
} catch {
|
|
2829
3412
|
}
|
|
2830
3413
|
try {
|
|
2831
|
-
await
|
|
3414
|
+
await fs4.access(srcPath);
|
|
2832
3415
|
hasSrc = true;
|
|
2833
3416
|
} catch {
|
|
2834
3417
|
}
|
|
@@ -2845,17 +3428,21 @@ async function checkDirectory(targetDir) {
|
|
|
2845
3428
|
}
|
|
2846
3429
|
async function main() {
|
|
2847
3430
|
const targetDir = process.cwd();
|
|
3431
|
+
if (process.argv[2] === "status") {
|
|
3432
|
+
await runStatus(targetDir);
|
|
3433
|
+
return;
|
|
3434
|
+
}
|
|
2848
3435
|
await checkDirectory(targetDir);
|
|
2849
3436
|
const answers = await runPrompts();
|
|
2850
3437
|
const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
2851
3438
|
await scaffold({ ...answers, targetDir, date });
|
|
2852
3439
|
await writeGitignore(targetDir);
|
|
2853
3440
|
if (answers.ingestion === "paste") {
|
|
2854
|
-
const pastePath =
|
|
3441
|
+
const pastePath = path4.join(targetDir, ".team-foundry", "paste-content.md");
|
|
2855
3442
|
try {
|
|
2856
|
-
await
|
|
3443
|
+
await fs4.access(pastePath);
|
|
2857
3444
|
} catch {
|
|
2858
|
-
await
|
|
3445
|
+
await fs4.writeFile(pastePath, PASTE_PLACEHOLDER, "utf-8");
|
|
2859
3446
|
}
|
|
2860
3447
|
}
|
|
2861
3448
|
const tool = TOOL_LABEL[answers.tool];
|