create-team-foundry 2.1.2 → 3.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.
Files changed (3) hide show
  1. package/README.md +81 -23
  2. package/dist/index.js +969 -47
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,12 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import fs4 from "fs/promises";
5
- import path4 from "path";
6
- import { outro as outro2, log, confirm } from "@clack/prompts";
4
+ import fs5 from "fs/promises";
5
+ import path5 from "path";
6
+ import { outro as outro3, log as log2, confirm as confirm2, select as select2, isCancel as isCancel2 } from "@clack/prompts";
7
7
 
8
8
  // src/prompts.ts
9
9
  import { intro, select, text, outro, isCancel } from "@clack/prompts";
10
+ function questionCount(profile, federated, ingestion) {
11
+ if (profile === void 0) return 5;
12
+ const hasLocalPath = ingestion === "local" || ingestion === "repo+local";
13
+ if (profile === "solo") return hasLocalPath ? 5 : 4;
14
+ const base = federated ? 6 : 5;
15
+ return hasLocalPath ? base + 1 : base;
16
+ }
10
17
  function cancelIfNeeded(value) {
11
18
  if (isCancel(value)) {
12
19
  outro("Cancelled.");
@@ -17,6 +24,7 @@ async function runPrompts() {
17
24
  intro("create-team-foundry");
18
25
  const tool = await select({
19
26
  message: "Which AI tool does your team use?",
27
+ hint: "1 of ~5",
20
28
  options: [
21
29
  { value: "claude", label: "Claude Code" },
22
30
  { value: "gemini", label: "Gemini CLI" },
@@ -27,14 +35,18 @@ async function runPrompts() {
27
35
  cancelIfNeeded(tool);
28
36
  const profile = await select({
29
37
  message: "Team size?",
38
+ hint: "2 of ~5",
30
39
  options: [
31
40
  { value: "solo", label: "1\u20133 people (solo profile \u2014 7 files)" },
32
41
  { value: "full", label: "4\u201315 people (full profile \u2014 20 files)" }
33
42
  ]
34
43
  });
35
44
  cancelIfNeeded(profile);
45
+ const profileTyped = profile;
46
+ const totalNoFed = questionCount(profileTyped, false, void 0);
36
47
  const repoVisibility = await select({
37
48
  message: "Is this repo public, internal-only, or private?",
49
+ hint: `3 of ${totalNoFed}`,
38
50
  options: [
39
51
  { value: "public", label: "Public (GitHub public, open source)" },
40
52
  { value: "internal", label: "Internal (company-private, not public)" },
@@ -46,6 +58,7 @@ async function runPrompts() {
46
58
  if (profile === "full") {
47
59
  const federatedAnswer = await select({
48
60
  message: "Context layout?",
61
+ hint: "4 of ?",
49
62
  options: [
50
63
  { value: "flat", label: "Flat (one root CLAUDE.md \u2014 simpler, recommended for most teams)" },
51
64
  { value: "federated", label: "Federated (CLAUDE.md per folder \u2014 for larger teams, 8+ people)" }
@@ -54,8 +67,11 @@ async function runPrompts() {
54
67
  cancelIfNeeded(federatedAnswer);
55
68
  federated = federatedAnswer === "federated";
56
69
  }
70
+ const federatedResolved = federated ?? false;
71
+ const ingestionQ = profileTyped === "solo" ? 4 : 5;
57
72
  const ingestion = await select({
58
73
  message: "Do you have existing docs to ingest?\n (Strategy docs, old roadmaps, customer research \u2014 the interview uses them to pre-populate answers)",
74
+ hint: `${ingestionQ} of ${questionCount(profileTyped, federatedResolved, void 0)}`,
59
75
  options: [
60
76
  { value: "repo", label: "Repo signals only (README, package.json, git history, GitHub PRs/issues)" },
61
77
  { value: "repo+local", label: "Repo + local docs folder (repo signals + point me at a folder)" },
@@ -70,8 +86,10 @@ async function runPrompts() {
70
86
  cancelIfNeeded(ingestion);
71
87
  let ingestionPath;
72
88
  if (ingestion === "local" || ingestion === "repo+local") {
89
+ const total = questionCount(profileTyped, federatedResolved, ingestion);
73
90
  const rawPath = await text({
74
91
  message: "Path to the folder containing your docs?",
92
+ hint: `${total} of ${total}`,
75
93
  placeholder: "./docs or /Users/you/exports",
76
94
  validate: (value) => {
77
95
  if (!value.trim()) return "Please enter a path.";
@@ -225,6 +243,328 @@ Read \`glossary.md\` when writing specs, copy, or anything using domain-specific
225
243
  `;
226
244
  }
227
245
 
246
+ // src/templates/skills/team-foundry-intro.ts
247
+ function introSkillTemplate(_ctx) {
248
+ return `---
249
+ description: Team and product overview \u2014 great for onboarding a new session or new team member
250
+ ---
251
+
252
+ # /team-foundry-intro
253
+
254
+ You have been asked to orient yourself to this team's product context.
255
+
256
+ ## What to do
257
+
258
+ Read the following files in order. After reading each one, note what you learned and what questions it raises.
259
+
260
+ 1. **Who we are and why we exist**
261
+ - \`team-foundry/product/north-star.md\` \u2014 mission, long-term vision, success metric
262
+ - \`team-foundry/product/customers.md\` \u2014 who we serve, validated segments, pain points
263
+
264
+ 2. **What we're working toward**
265
+ - \`team-foundry/product/outcomes.md\` \u2014 current cycle outcomes and their status
266
+ - \`team-foundry/product/strategy.md\` \u2014 how we're choosing to win (if present)
267
+
268
+ 3. **How we work**
269
+ - \`team-foundry/team/trio.md\` \u2014 PM, engineering lead, design lead (if present)
270
+ - \`team-foundry/team/working-agreement.md\` \u2014 norms and expectations (if present)
271
+
272
+ 4. **What we're building on**
273
+ - \`team-foundry/engineering/stack.md\` \u2014 tech stack, key constraints
274
+ - \`team-foundry/engineering/quality-bar.md\` \u2014 definition of done (if present)
275
+
276
+ 5. **Shared language**
277
+ - \`team-foundry/context/glossary.md\` \u2014 domain terms (if present)
278
+
279
+ ## After reading
280
+
281
+ Summarize what you know in three parts:
282
+
283
+ **This team exists to:** [one sentence from north-star]
284
+
285
+ **Right now they're focused on:** [current outcomes in plain language]
286
+
287
+ **Key constraints I should keep in mind:** [stack, quality bar, working agreement norms]
288
+
289
+ Then ask: "Is there anything outdated or missing from these files before we start?"
290
+
291
+ ## Files that may not exist
292
+
293
+ For solo profile setups, only a subset of these files is present. Skip any that are missing \u2014 do not report them as errors.
294
+ `;
295
+ }
296
+
297
+ // src/templates/skills/team-foundry-status.ts
298
+ function statusSkillTemplate(_ctx) {
299
+ return `---
300
+ description: Current sprint/cycle status \u2014 what's on track, what's at risk, what's blocked
301
+ ---
302
+
303
+ # /team-foundry-status
304
+
305
+ You have been asked for a status read on this team's current work.
306
+
307
+ ## What to do
308
+
309
+ Read these files:
310
+
311
+ - \`team-foundry/product/outcomes.md\` \u2014 current cycle outcomes and their validation status
312
+ - \`team-foundry/product/now-next-later.md\` \u2014 what's in flight, what's queued (if present)
313
+ - \`team-foundry/product/assumptions.md\` \u2014 open assumptions that could invalidate work (if present)
314
+ - \`team-foundry/product/risks.md\` \u2014 known risks and their mitigations (if present)
315
+
316
+ ## Report format
317
+
318
+ Produce a status report in this structure:
319
+
320
+ ### On track
321
+ [Outcomes or work items that are validated and progressing. Cite the source field or inline evidence if present.]
322
+
323
+ ### At risk
324
+ [Work that lacks validation, has an open assumption underneath it, or has a known risk with no mitigation. Be specific about what's missing.]
325
+
326
+ ### Blocked
327
+ [Anything explicitly marked blocked or waiting on a dependency.]
328
+
329
+ ### Gaps I noticed
330
+ [Files that exist but have placeholder content. Sections that are empty. Frontmatter with no \`last_validated\` date on items marked Validated. Claims in a Validated section with no \`source:\` reference.]
331
+
332
+ ## Tone
333
+
334
+ Be direct. "No validated outcomes this cycle" is a useful status. "Everything looks great" when files have placeholders is not.
335
+
336
+ If the files are mostly empty or stubbed, say: "The team-foundry files exist but haven't been filled in yet. The highest-value thing right now is completing [most important missing section]."
337
+ `;
338
+ }
339
+
340
+ // src/templates/skills/team-foundry-review.ts
341
+ function reviewSkillTemplate(_ctx) {
342
+ return `---
343
+ description: Full team-foundry audit by the coach \u2014 all files, findings by severity
344
+ ---
345
+
346
+ # /team-foundry-review
347
+
348
+ You have been asked to audit this team's team-foundry files and act as a diagnostic coach.
349
+
350
+ ## What to do
351
+
352
+ Read every team-foundry file that exists:
353
+
354
+ - \`team-foundry/product/\` \u2014 all files
355
+ - \`team-foundry/team/\` \u2014 all files (if present)
356
+ - \`team-foundry/engineering/\` \u2014 all files
357
+ - \`team-foundry/design/\` \u2014 all files (if present)
358
+ - \`team-foundry/data/\` \u2014 all files (if present)
359
+ - \`team-foundry/context/\` \u2014 all files (if present)
360
+ - \`.team-foundry/coach.md\` \u2014 coaching playbook (if present)
361
+
362
+ ## Audit findings format
363
+
364
+ Group findings by severity:
365
+
366
+ ### Critical \u2014 acting on bad information
367
+ [Claims marked Validated with no \`source:\` reference or inline evidence. Outcomes with no signal. Decisions that contradict each other. These are the cases where the AI is most likely to give wrong advice.]
368
+
369
+ ### Important \u2014 gaps that limit usefulness
370
+ [Files with placeholder content. Missing sections for the profile. Stale \`last_validated\` dates (>90 days). Outcomes with no owner. Assumptions with no test plan.]
371
+
372
+ ### Suggestions \u2014 would strengthen the context
373
+ [Things that would make AI answers more specific: adding a source to a hypothesis, filling in a glossary term, adding a stakeholder. Low urgency.]
374
+
375
+ ## After findings
376
+
377
+ Recommend the top 3 actions in priority order. For each one, say:
378
+ - What to do (one sentence)
379
+ - Which file to update
380
+ - Why it matters for AI accuracy
381
+
382
+ ## Tone
383
+
384
+ Name gaps directly. "outcomes.md has four outcomes, three of which are placeholders with no signal" is useful. Softening findings makes the audit pointless.
385
+ `;
386
+ }
387
+
388
+ // src/templates/skills/team-foundry-capture.ts
389
+ function captureSkillTemplate(_ctx) {
390
+ return `---
391
+ description: Capture what was learned in this session into the right team-foundry file
392
+ ---
393
+
394
+ # /team-foundry-capture
395
+
396
+ You have been asked to capture what was learned or decided in this session into the team's team-foundry files.
397
+
398
+ ## What to do
399
+
400
+ **Do not write any file until you have shown the proposed content and received explicit confirmation.**
401
+
402
+ ### Step 1 \u2014 identify what's worth keeping
403
+
404
+ Review this conversation for:
405
+
406
+ - **Validated claims**: something that was confirmed by data, user research, or a real event (e.g., "we tested X and users did Y")
407
+ - **Decisions**: something the team chose to do or not do, and why
408
+ - **Invalidated assumptions**: something the team believed that turned out to be wrong
409
+ - **New risks or blockers**: something that emerged that could affect the work
410
+ - **Glossary terms**: domain language that came up and should be defined for the AI
411
+
412
+ Ignore: process discussions, brainstorming that went nowhere, anything already in the files.
413
+
414
+ ### Step 2 \u2014 map to the right file
415
+
416
+ | What you learned | Where it goes |
417
+ |------------------|---------------|
418
+ | An outcome is now validated | \`team-foundry/product/outcomes.md\` \u2014 move to Validated section, add source |
419
+ | A customer segment was confirmed | \`team-foundry/product/customers.md\` \u2014 add to Validated with source |
420
+ | An assumption was tested | \`team-foundry/product/assumptions.md\` \u2014 update status, add result |
421
+ | A key decision was made | \`team-foundry/engineering/decisions/\` \u2014 new ADR or update existing |
422
+ | A new risk surfaced | \`team-foundry/product/risks.md\` \u2014 add with impact and mitigation |
423
+ | A term needs defining | \`team-foundry/context/glossary.md\` \u2014 add entry |
424
+ | A metric moved | \`team-foundry/data/metrics.md\` \u2014 update with date and source |
425
+
426
+ ### Step 3 \u2014 write the update
427
+
428
+ For each piece of learning, draft the update text and show it to the team before writing. Include:
429
+
430
+ - The claim or decision in plain language
431
+ - The source or evidence (who said it, what data, what date)
432
+ - For validated items: update \`last_validated:\` in the frontmatter if stale
433
+
434
+ ### Step 4 \u2014 confirm and write
435
+
436
+ Show the proposed changes. Ask: "Should I write these to the files?"
437
+
438
+ Do not write anything without confirmation.
439
+ `;
440
+ }
441
+
442
+ // src/templates/skills/team-foundry-decision.ts
443
+ function decisionSkillTemplate(_ctx) {
444
+ return `---
445
+ description: Draft a new architecture decision record (ADR) from the current conversation
446
+ ---
447
+
448
+ # /team-foundry-decision
449
+
450
+ You have been asked to draft an Architecture Decision Record (ADR) from this conversation.
451
+
452
+ ## What to do
453
+
454
+ **Do not write any file until you have shown the proposed content and received explicit confirmation.**
455
+
456
+ ### Step 1 \u2014 extract the decision
457
+
458
+ From the conversation, identify:
459
+
460
+ - **The decision**: what was chosen (one sentence)
461
+ - **The context**: what problem were we solving, what constraints existed
462
+ - **The options considered**: what else was on the table (even if briefly)
463
+ - **The rationale**: why this option over the others
464
+ - **The consequences**: what becomes easier, what becomes harder, what we're committing to
465
+
466
+ If any of these are unclear, ask before drafting.
467
+
468
+ ### Step 2 \u2014 draft the ADR
469
+
470
+ Use this format:
471
+
472
+ \`\`\`markdown
473
+ ---
474
+ title: [Short descriptive title]
475
+ date: [today's date]
476
+ status: Proposed
477
+ ---
478
+
479
+ ## Context
480
+ [What situation or problem led to this decision. What constraints were in play.]
481
+
482
+ ## Decision
483
+ [The choice made, stated as a clear action or commitment.]
484
+
485
+ ## Options considered
486
+ - **[Option A]**: [Brief description and why it was considered]
487
+ - **[Option B]**: [Brief description and why it was considered]
488
+
489
+ ## Rationale
490
+ [Why this option over the others. What evidence or reasoning tipped it.]
491
+
492
+ ## Consequences
493
+ - What becomes easier: [...]
494
+ - What becomes harder: [...]
495
+ - What we're committing to: [...]
496
+ \`\`\`
497
+
498
+ ### Step 3 \u2014 pick the file name
499
+
500
+ ADRs go in \`team-foundry/engineering/decisions/\`. Use the format \`NNN-short-title.md\` where NNN is the next sequential number. List the files in \`team-foundry/engineering/decisions/\` to find the highest existing number, then use that number + 1. If the directory is empty or does not exist, start at \`001\`.
501
+
502
+ ### Step 4 \u2014 confirm and write
503
+
504
+ Show the draft. Ask: "Should I write this to \`team-foundry/engineering/decisions/[filename].md\`?"
505
+
506
+ Do not write without confirmation.
507
+ `;
508
+ }
509
+
510
+ // src/templates/skills/team-foundry-feature.ts
511
+ function featureSkillTemplate(_ctx) {
512
+ return `---
513
+ description: Synthesize everything team-foundry knows about a specific feature or work item
514
+ ---
515
+
516
+ # /team-foundry-feature
517
+
518
+ You have been asked to synthesize the full team context for a specific feature or work item.
519
+
520
+ ## What to do
521
+
522
+ Ask (or infer from the conversation): **what feature or work item are we focusing on?**
523
+
524
+ Then read the team-foundry files for everything relevant to it:
525
+
526
+ ### Product context
527
+ - \`team-foundry/product/outcomes.md\` \u2014 which outcome(s) does this feature serve?
528
+ - \`team-foundry/product/customers.md\` \u2014 which customer segment is this for?
529
+ - \`team-foundry/product/now-next-later.md\` \u2014 where does this sit in the roadmap? (if present)
530
+ - \`team-foundry/product/assumptions.md\` \u2014 what assumptions is this feature resting on? (if present)
531
+
532
+ ### Design and quality
533
+ - \`team-foundry/design/principles.md\` \u2014 design constraints that apply (if present)
534
+ - \`team-foundry/engineering/quality-bar.md\` \u2014 definition of done (if present)
535
+
536
+ ### Prior decisions
537
+ - \`team-foundry/engineering/decisions/\` \u2014 any ADRs that affect this feature's approach
538
+
539
+ ### Domain context
540
+ - \`team-foundry/context/glossary.md\` \u2014 terms relevant to this feature (if present)
541
+
542
+ ## Output format
543
+
544
+ Produce a feature brief:
545
+
546
+ **Feature:** [name]
547
+
548
+ **Why this matters:** [which validated outcome it moves, for which customer segment]
549
+
550
+ **Assumptions underneath it:** [open assumptions from assumptions.md that this feature depends on]
551
+
552
+ **Design constraints:** [relevant principles or quality bar items]
553
+
554
+ **Prior decisions that shape the approach:** [relevant ADRs]
555
+
556
+ **What the AI should keep in mind while working on this:** [anything that would change how code is written, what trade-offs to make, what not to do]
557
+
558
+ ## If files are sparse
559
+
560
+ If the team-foundry files don't have enough to answer the questions above, say so explicitly:
561
+
562
+ "I can see this feature targets [X], but there's no validated customer segment in customers.md to confirm who it's for. Before we build, it's worth checking whether [assumption] holds."
563
+
564
+ Don't invent context. Surface the gap instead.
565
+ `;
566
+ }
567
+
228
568
  // src/templates/root-claude.ts
229
569
  function rootClaudeTemplate(ctx) {
230
570
  return `---
@@ -276,7 +616,8 @@ before answering. Files with recent \`last_updated\` dates are more reliable tha
276
616
  | Design principles and tone | \`team-foundry/design/principles.md\` |
277
617
  | Metric definitions | \`team-foundry/data/metrics.md\` |
278
618
  | Domain terms and acronyms | \`team-foundry/context/glossary.md\` |
279
- | Stakeholders and what they care about | \`team-foundry/context/stakeholders.md\` |
619
+ | Stakeholders and what they care about | \`team-foundry/context/stakeholders.md\` |${ctx.profile === "full" ? `
620
+ | Which source wins when context conflicts | \`.team-foundry/hierarchy.md\` |` : ""}
280
621
 
281
622
  ## Coach
282
623
 
@@ -300,6 +641,19 @@ it notices something relevant to your current work. You can also invoke it direc
300
641
  - Inline mode nudges: if you notice a clear gap in a team-foundry file while answering
301
642
  a normal question, surface it in one sentence \u2014 without loading the full coach.md.
302
643
  Keep it brief and non-blocking. Do not coach unprompted on back-to-back messages. -->
644
+
645
+ ## Skills
646
+
647
+ Pre-built Claude Code skills are in \`.claude/skills/\`. Invoke them with a slash command:
648
+
649
+ | Skill | What it does |
650
+ |---|---|
651
+ | \`/team-foundry-intro\` | Orient to the team \u2014 reads all context files, produces a summary |
652
+ | \`/team-foundry-status\` | Status read \u2014 what's on track, at risk, or blocked this cycle |
653
+ | \`/team-foundry-review\` | Full audit \u2014 all files checked, findings by severity |
654
+ | \`/team-foundry-capture\` | Capture what was learned in this session into the right file |
655
+ | \`/team-foundry-decision\` | Draft an ADR from the current conversation |
656
+ | \`/team-foundry-feature\` | Synthesize everything team-foundry knows about a specific feature |
303
657
  `;
304
658
  }
305
659
 
@@ -354,7 +708,8 @@ before answering. Files with recent \`last_updated\` dates are more reliable tha
354
708
  | Design principles and tone | \`team-foundry/design/principles.md\` |
355
709
  | Metric definitions | \`team-foundry/data/metrics.md\` |
356
710
  | Domain terms and acronyms | \`team-foundry/context/glossary.md\` |
357
- | Stakeholders and what they care about | \`team-foundry/context/stakeholders.md\` |
711
+ | Stakeholders and what they care about | \`team-foundry/context/stakeholders.md\` |${ctx.profile === "full" ? `
712
+ | Which source wins when context conflicts | \`.team-foundry/hierarchy.md\` |` : ""}
358
713
 
359
714
  ## Coach
360
715
 
@@ -463,7 +818,7 @@ it notices something relevant to your current work. You can also invoke it direc
463
818
  // src/templates/getting-started.ts
464
819
  function gettingStartedTemplate(ctx) {
465
820
  const toolName = ctx.tool === "gemini" ? "Gemini CLI" : ctx.tool === "both" ? "Claude Code or Gemini CLI" : "Claude Code";
466
- const questionCount = ctx.profile === "solo" ? "10" : "18\u201325";
821
+ const questionCount2 = ctx.profile === "solo" ? "10" : "18\u201325";
467
822
  const fileCount = ctx.profile === "solo" ? "7" : "19";
468
823
  const ingestionNote = ctx.ingestionPath ? `
469
824
  > **Tip:** Before saying the phrase above, tell ${toolName}:
@@ -485,7 +840,7 @@ You've scaffolded ${fileCount} files. They're mostly empty. One thing to do now:
485
840
 
486
841
  > Open this project in **${toolName}** and say: **"Let's set up our team-foundry."**
487
842
  ${ingestionNote}
488
- The AI will walk you through a setup conversation \u2014 ${questionCount} questions, about 30 minutes.
843
+ The AI will walk you through a setup conversation \u2014 ${questionCount2} questions, about 30 minutes.
489
844
  By the end, most files will be meaningfully populated.
490
845
 
491
846
  ## What the setup covers
@@ -549,7 +904,7 @@ You can delete this file once the onboarding interview is complete.
549
904
  // src/templates/coach.ts
550
905
  function coachTemplate(ctx) {
551
906
  const isSolo = ctx.profile === "solo";
552
- const questionCount = isSolo ? "9" : "18\u201325";
907
+ const questionCount2 = isSolo ? "9" : "18\u201325";
553
908
  const timeEstimate = isSolo ? "15\u201320 minutes" : "25\u201335 minutes";
554
909
  const featureQueryFullSteps = isSolo ? "" : `3. Read \`team-foundry/product/now-next-later.md\` \u2014 find the feature's current status (Now / Next / Later / shipped)
555
910
  4. Read \`team-foundry/product/assumptions.md\` \u2014 find any assumptions linked to this feature
@@ -671,7 +1026,7 @@ context of their actual work.
671
1026
  **How to behave:**
672
1027
  - Run all active coaching behaviors in priority order: B1 (outputs-vs-outcomes) \u2192
673
1028
  B2 (customer staleness) \u2192 B3 (stale assumptions) \u2192 B4 (decisions without rationale) \u2192
674
- ... \u2192 B12 (MCP suggestions) \u2192 discovery and strategy behaviors
1029
+ ... \u2192 B12 (MCP suggestions) \u2192 discovery and strategy behaviors \u2192 B18 (unsourced claims, full only)
675
1030
  - For each issue found: name it specifically (cite the file and exact content),
676
1031
  explain why it matters in one sentence, offer to draft the fix
677
1032
  - Group findings by severity: blockers (things actively misleading the AI or the team)
@@ -814,7 +1169,12 @@ window (inline mode) or until the next explicit review (explicit/scheduled mode)
814
1169
 
815
1170
  ## Context priority
816
1171
 
817
- When two team-foundry files appear to contradict each other, resolve using this
1172
+ ${!isSolo ? `Before reconciling any conflicting signals, read \`.team-foundry/hierarchy.md\`.
1173
+ It defines the authoritative precedence order across file types, data sources, expertise levels,
1174
+ and version authority. Apply it. Do not average conflicting claims \u2014 name the conflict and state
1175
+ which source wins.
1176
+
1177
+ ` : ""}When two team-foundry files appear to contradict each other, resolve using this
818
1178
  order and **name the conflict explicitly** rather than silently picking one:
819
1179
 
820
1180
  1. \`north-star.md\` \u2014 destination, never overridden
@@ -854,7 +1214,7 @@ The team-foundry files are the index. ${featureQueryIndexNote}
854
1214
 
855
1215
  ## Behaviors
856
1216
 
857
- Behaviors run in priority order (B1\u2192B12, then discovery and strategy behaviors). In explicit mode, run all of them.
1217
+ Behaviors run in priority order (B1\u2192B12, then discovery and strategy behaviors; B18\u2013B20 full profile only). In explicit mode, run all of them.
858
1218
  In inline mode, run only the highest-priority behavior whose inline trigger condition
859
1219
  is met for the user's current question. If multiple triggers apply, pick the
860
1220
  highest-priority one \u2014 do not surface multiple behaviors in a single inline nudge.
@@ -1475,12 +1835,98 @@ offer to retire it: move it from Active rules to Retired rules with today's date
1475
1835
  check if \`.team-foundry/team-lessons.md\` exists. If it does, load it and apply Active rules
1476
1836
  with equal weight to built-in behaviors, scoped to this team's context.
1477
1837
 
1838
+ **Knowledge routing:** B17 handles team process patterns only \u2014 things that should shape future coaching.
1839
+ For other types of learning surfaced in a session, route to the right file:
1840
+
1841
+ | What was learned | Where it goes | How |
1842
+ |---|---|---|
1843
+ | A team process pattern | \`.team-foundry/team-lessons.md\` | B17 (this behavior) |
1844
+ | A validated claim or customer insight | \`team-foundry/product/outcomes.md\` or \`customers.md\` | Offer \`/team-foundry-capture\` (B20) |
1845
+ | An architectural decision | \`team-foundry/engineering/decisions/\` (full profile only) | Offer \`/team-foundry-decision\` |
1846
+ | A tested assumption | \`team-foundry/product/assumptions.md\` (full profile only) | Offer \`/team-foundry-capture\` (B20) |
1847
+ | A new risk | \`team-foundry/product/risks.md\` (full profile only) | Offer \`/team-foundry-capture\` (B20) |
1848
+
1849
+ Do not mix routing: if the user names a pattern, use B17. If the session surfaces validated data, use B20.
1850
+
1478
1851
  **What not to do:** Do not proactively suggest adding rules unless the user explicitly names a pattern.
1479
1852
  This behavior is listener-only \u2014 it waits for the signal, it does not fish for it.
1480
1853
 
1481
1854
  ---
1482
1855
 
1483
- ## Quarterly retrospective
1856
+ ${!isSolo ? `### Behavior 18: Unsourced quantitative claim
1857
+
1858
+ **Severity:** Low \u2014 flag once, do not repeat in the same conversation.
1859
+
1860
+ **Trigger condition:** A team-foundry file contains a number, percentage, or currency figure
1861
+ (e.g. "62% win rate", "saves 4 hours/week", "$1.2M ARR") without either:
1862
+ - a \`source:\` frontmatter field that is not \`~\`, or
1863
+ - an inline source reference in the form \`(source, date)\` near the claim.
1864
+
1865
+ **What to say:**
1866
+ > "I noticed a quantitative claim in [filename]: '[the claim]'. I don't see a source for it.
1867
+ > If this comes from data, add a \`source:\` value to the frontmatter or an inline note like
1868
+ > \`(source, date)\` next to the figure. If it's an estimate or assumption, consider moving
1869
+ > it to assumptions.md so the team knows it hasn't been validated."
1870
+
1871
+ **What not to do:**
1872
+ - Do not flag every file on every review \u2014 one pass per file per session.
1873
+ - Do not treat the absence of a source as an error; it's a signal to investigate, not a blocker.
1874
+ - Do not ask for sources on qualitative statements (opinions, framings, examples).
1875
+
1876
+ ---
1877
+
1878
+ ### Behavior 19: Validated entry without evidence
1879
+
1880
+ **Severity:** Medium \u2014 the word "Validated" makes an implicit promise to readers.
1881
+
1882
+ **Trigger condition:** An entry appears under a validated section in any of the three target files
1883
+ but contains no source reference \u2014 neither an inline \`(source, date)\` near the entry, nor a
1884
+ populated \`source:\` frontmatter field in the same file.
1885
+
1886
+ The validated section headers are file-specific:
1887
+ - \`customers.md\` \u2192 \`## Validated\`
1888
+ - \`outcomes.md\` \u2192 \`## Validated outcomes\`
1889
+ - \`now-next-later.md\` \u2192 \`## Now (validated outcomes)\`
1890
+
1891
+ **What to say:**
1892
+ > "[filename] has an entry in the validated section that doesn't cite evidence:
1893
+ > '[the entry]'. Validated sections carry an implicit promise \u2014 readers trust them more.
1894
+ > If this is backed by data, add a source reference inline \`(source, date)\` or in the
1895
+ > file's \`source:\` frontmatter. If it's still an assumption, move it to the hypothesized
1896
+ > section so the team reads it with the right level of trust."
1897
+
1898
+ **What not to do:**
1899
+ - Do not flag entries in hypothesized sections \u2014 those explicitly signal unvalidated beliefs.
1900
+ - Do not demand a specific source format \u2014 any inline note or frontmatter value is sufficient.
1901
+ - Do not block work; surface the gap and offer to help relocate the entry if the team prefers.
1902
+
1903
+ ---
1904
+
1905
+ ### Behavior 20: Session-end knowledge capture
1906
+
1907
+ **Severity:** Low \u2014 this is an offer, not a finding.
1908
+
1909
+ **Trigger condition:** The session included at least one of:
1910
+ - A claim that was confirmed or invalidated by data, research, or a real event
1911
+ - A decision the team made (what to build, what to cut, what approach to take)
1912
+ - A risk or blocker that surfaced and was not previously documented
1913
+
1914
+ Do not trigger if the session was purely a coding or debugging session with no product/team learning.
1915
+
1916
+ **What to say:**
1917
+ > "Before we close \u2014 this session surfaced [brief summary: e.g., 'a validated customer segment' / 'a decision about X' / 'a new risk around Y']. Want me to run \`/team-foundry-capture\` to save it to the right files? It takes about two minutes and keeps the context current for future sessions."
1918
+
1919
+ Only proceed after the user explicitly confirms. The conversation-as-update confirmation rules apply \u2014 silence or an ambiguous response is not a yes.
1920
+
1921
+ **What not to do:**
1922
+ - Do not trigger on every session \u2014 only when there is something clearly worth preserving.
1923
+ - Do not run \`/team-foundry-capture\` without the team saying yes.
1924
+ - Do not summarize the entire conversation \u2014 name the specific learnable item only.
1925
+ - Do not offer if the user has already run \`/team-foundry-capture\` in this session.
1926
+
1927
+ ---
1928
+
1929
+ ` : ""}## Quarterly retrospective
1484
1930
 
1485
1931
  ### Trigger
1486
1932
 
@@ -1685,10 +2131,21 @@ fields. If the user says "looks good," proceed immediately to Step 4.
1685
2131
 
1686
2132
  ### Step 4 \u2014 Write files
1687
2133
 
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.
2134
+ Write **all** team-foundry files that have at least one confirmed signal. Do not skip
2135
+ files just because they were not mentioned in the pre-fill summary \u2014 if the repo scan
2136
+ gave you data for a file, populate it now.
2137
+
2138
+ Signal-to-file mapping (use this to decide what to write):
2139
+ - Stack (language, framework, dependencies from manifest file) \u2192 \`team-foundry/engineering/stack.md\`
2140
+ - Product name + what it does (README) \u2192 \`team-foundry/product/north-star.md\` (name field)
2141
+ - Recent commit messages \u2192 \`team-foundry/product/outcomes.md\` (inferred focus areas \u2014 frame as customer outcomes, not shipped features)
2142
+ - Open issues / PRs \u2192 \`team-foundry/product/assumptions.md\` (open bets, if full profile)
2143
+ - \`team-foundry/product/customers.md\` \u2014 no repo signal; write gap marker, surface in Step 5
2144
+ - Root instruction file \u2014 always write/update with product name and stack summary
2145
+
2146
+ For any field with no signal and no user-provided answer, write a \`<!-- GAP: ... -->\`
2147
+ marker. Follow the conversation-as-update protocol \u2014 show each file draft and wait
2148
+ for confirmation before writing.
1692
2149
 
1693
2150
  **No silent writes.** Even high-confidence pre-filled answers require explicit
1694
2151
  confirmation before being written to disk. "Looks good" or "yes" is confirmation.
@@ -1887,7 +2344,7 @@ outdated. Every answer needs the user's confirmation before it becomes a file.
1887
2344
 
1888
2345
  **Opening framing** (say this verbatim \u2014 the question count, time estimate, and file-writing detail are load-bearing):
1889
2346
 
1890
- > "We're going to set up your team-foundry \u2014 ${questionCount} questions across
2347
+ > "We're going to set up your team-foundry \u2014 ${questionCount2} questions across
1891
2348
  > 9 themes. Each answer goes directly into a file as we go,
1892
2349
  > so you'll see the files populate in real time. You can skip anything you don't
1893
2350
  > have an answer to right now \u2014 I'll mark it as a gap instead of leaving it blank.
@@ -2232,8 +2689,8 @@ After the last question, do the following:
2232
2689
  \u2192 north star (grounds the coach) \u2192 stack (engineering context).
2233
2690
  Wait for the user to say yes or choose a different file. If they say yes, proceed
2234
2691
  directly to the interview questions for that file as defined in the interview section
2235
- above \u2014 do not re-run the full interview. If they decline, proceed to step 4.
2236
- If all files are populated, skip this offer and proceed directly to step 4.
2692
+ above \u2014 do not re-run the full interview. If they decline, proceed to the "Offer the coach" step below.
2693
+ If all files are populated, skip this offer and proceed directly to the "Offer the coach" step below.
2237
2694
 
2238
2695
  4. **Offer the coach.** End with:
2239
2696
  > "Your team-foundry is set up. You can ask me to review it any time by saying
@@ -2306,7 +2763,9 @@ function outcomesTemplate(ctx) {
2306
2763
  purpose: Current quarter outcomes \u2014 the changes in customer behavior that define success this quarter
2307
2764
  read_when: Prioritizing work, writing specs, deciding what to build next, evaluating tradeoffs
2308
2765
  last_updated: ${ctx.date}
2309
- owner:
2766
+ last_validated: ~
2767
+ source: ~
2768
+ owner:
2310
2769
  ---
2311
2770
 
2312
2771
  # Outcomes
@@ -2325,7 +2784,24 @@ owner:
2325
2784
  "Write your outcomes in the form 'we want X to change for Y customer segment.'
2326
2785
  What does winning this quarter look like for your customers, not your roadmap?" -->
2327
2786
 
2328
- ## This quarter
2787
+ ${ctx.profile === "full" ? `## Validated outcomes
2788
+
2789
+ <!-- Outcomes confirmed by data: cohort analysis, retention curves, usage metrics, or direct
2790
+ customer evidence. Each entry needs a source \u2014 inline (source, date) or in the frontmatter.
2791
+ If you can't name the evidence, move it to Hypothesized.
2792
+
2793
+ Format: outcome statement + evidence reference + date confirmed.
2794
+ Example: "New sellers list their first item within 48h of signup (cohort data, Q1 2026)" -->
2795
+
2796
+ ## Hypothesized outcomes
2797
+
2798
+ <!-- Outcomes you believe are worth pursuing but haven't yet validated with data.
2799
+ State the assumption and what would confirm it.
2800
+
2801
+ Format: outcome statement + assumption + validation signal.
2802
+ Example: "Ops managers close reconciliation in <30 min \u2014 assumed from 3 sales calls.
2803
+ Validate: measure time-on-task for 5 ops managers over one cycle." -->
2804
+ ` : `## This quarter
2329
2805
 
2330
2806
  <!-- List 2\u20134 outcome statements. Each should be falsifiable \u2014 you'll know at quarter-end
2331
2807
  whether it happened.
@@ -2340,6 +2816,7 @@ owner:
2340
2816
  - "Ship the new dashboard" (feature, not behavior change)
2341
2817
  - "Complete the API integration" (milestone, not customer outcome)
2342
2818
  - "Improve retention" (direction, not a measurable change) -->
2819
+ `}
2343
2820
  `;
2344
2821
  }
2345
2822
 
@@ -2350,7 +2827,9 @@ function customersTemplate(ctx) {
2350
2827
  purpose: Named customers, personas, jobs to be done, and direct quotes from real conversations
2351
2828
  read_when: Writing specs, prioritizing features, evaluating tradeoffs, any time you're guessing what customers want
2352
2829
  last_updated: ${ctx.date}
2353
- owner:
2830
+ last_validated: ~
2831
+ source: ~
2832
+ owner:
2354
2833
  ---
2355
2834
 
2356
2835
  # Customers
@@ -2374,7 +2853,40 @@ ${visibilityNote}
2374
2853
  "Name three customers you've spoken to directly. For each: what did you learn,
2375
2854
  and when was the last time you talked to them?" -->
2376
2855
 
2377
- ## Personas
2856
+ ${ctx.profile === "full" ? `## Validated
2857
+
2858
+ <!-- Customers confirmed by cohort data, win-rate analysis, or direct evidence.
2859
+ Every entry here needs a source \u2014 inline (source, date) or in the file's source: frontmatter.
2860
+ If you can't name the evidence, move the entry to Hypothesized.
2861
+
2862
+ Format per entry:
2863
+ ### [Segment name \u2014 e.g. "IT consultancies, 20\u201350 staff"]
2864
+ **Win rate / evidence:** [e.g. "62% win rate (cohort data, Q4 2025)"]
2865
+ **Job to be done:** When [situation], I want to [motivation], so I can [expected outcome].
2866
+ **Last direct contact:** YYYY-MM-DD
2867
+ **Quote:** "[Something a real customer said]" -->
2868
+
2869
+ ## Hypothesized
2870
+
2871
+ <!-- Customers you believe are a fit but haven't validated with data.
2872
+ State the assumption explicitly and what would confirm it.
2873
+
2874
+ Format per entry:
2875
+ ### [Segment name]
2876
+ **Why we believe this:** [reasoning \u2014 pattern from sales calls, founder intuition, etc.]
2877
+ **What would validate it:** [specific signal \u2014 cohort analysis, X wins in segment, etc.]
2878
+ **Last direct contact:** YYYY-MM-DD (or "never") -->
2879
+
2880
+ ## Anti-ICP
2881
+
2882
+ <!-- Explicitly who this product is NOT for \u2014 with reasoning.
2883
+ Anti-ICP entries prevent scope creep and help the AI avoid suggesting work
2884
+ that serves the wrong customer. Be explicit about why they're excluded.
2885
+
2886
+ Format per entry:
2887
+ ### [Segment name]
2888
+ **Why excluded:** [specific reason \u2014 wrong pain, wrong scale, misaligned incentives, etc.] -->
2889
+ ` : `## Personas
2378
2890
 
2379
2891
  <!-- For each persona below:
2380
2892
  - Give them a name or a specific role (not a label like "power user")
@@ -2395,6 +2907,7 @@ ${visibilityNote}
2395
2907
  **Quote:** "[Something they actually said]"
2396
2908
  **What we learned:** [The non-obvious thing \u2014 the thing that would surprise an outsider]
2397
2909
  -->
2910
+ `}
2398
2911
  `;
2399
2912
  }
2400
2913
 
@@ -2404,7 +2917,9 @@ function nowNextLaterTemplate(ctx) {
2404
2917
  purpose: What we're building now, what we're committed to next, and what's directional
2405
2918
  read_when: Sprint planning, stakeholder updates, evaluating new requests, prioritization discussions
2406
2919
  last_updated: ${ctx.date}
2407
- owner:
2920
+ last_validated: ~
2921
+ source: ~
2922
+ owner:
2408
2923
  ---
2409
2924
 
2410
2925
  # Now / Next / Later
@@ -2431,7 +2946,39 @@ owner:
2431
2946
  What have you committed to doing after that?
2432
2947
  What's directional but not yet committed?" -->
2433
2948
 
2434
- ## Now
2949
+ ${ctx.profile === "full" ? `## Now (validated outcomes)
2950
+
2951
+ <!-- Active work connected to a validated outcome \u2014 something backed by data or direct
2952
+ customer evidence. Each item has an outcome reference and a source.
2953
+
2954
+ Format: item \u2192 outcome reference | evidence | owner | started date.
2955
+ Example:
2956
+ - **Self-serve report fix flow** \u2192 "ops managers close reconciliation in <30 min"
2957
+ Evidence: usability study (March 2026) | Owner: [name] | Started: [date] -->
2958
+
2959
+ ## Now (hypothesized outcomes)
2960
+
2961
+ <!-- Active work connected to a hypothesized outcome \u2014 believed to matter but not yet
2962
+ validated. These are bets, not commitments. The outcome link should be explicit so the
2963
+ AI can flag it when the hypothesis is later confirmed or invalidated.
2964
+
2965
+ Format: item \u2192 hypothesis | what would validate | owner.
2966
+ Example:
2967
+ - **Onboarding redesign** \u2192 hypothesis: "new sellers list first item within 48h"
2968
+ Validate: track time-to-first-listing for next cohort | Owner: [name] -->
2969
+
2970
+ ## Next
2971
+
2972
+ <!-- Committed work, sequenced. Whether linked to a validated or hypothesized outcome,
2973
+ the commitment is real \u2014 there's a reason this follows from what's in Now.
2974
+
2975
+ If everything in Next could plausibly be first, it isn't sequenced \u2014 it's a list. -->
2976
+
2977
+ ## Later
2978
+
2979
+ <!-- Directional bets. Not scheduled, not promised. Label each with its outcome hypothesis
2980
+ so the AI can flag it if that hypothesis is validated or invalidated before you get here. -->
2981
+ ` : `## Now
2435
2982
 
2436
2983
  <!-- Active work. For each item: what it is, which outcome it serves, who owns it.
2437
2984
 
@@ -2455,6 +3002,7 @@ owner:
2455
3002
 
2456
3003
  It's okay for "later" to be short. A short, honest "later" is better than a long,
2457
3004
  wishful one. -->
3005
+ `}
2458
3006
  `;
2459
3007
  }
2460
3008
 
@@ -2464,7 +3012,9 @@ function assumptionsTemplate(ctx) {
2464
3012
  purpose: Open assumptions and untested beliefs \u2014 the bets the team is currently making
2465
3013
  read_when: Designing discovery work, scoping experiments, retros, any time a decision feels risky
2466
3014
  last_updated: ${ctx.date}
2467
- owner:
3015
+ last_validated: ~
3016
+ source: ~
3017
+ owner:
2468
3018
  ---
2469
3019
 
2470
3020
  # Assumptions
@@ -3026,7 +3576,9 @@ function metricsTemplate(ctx) {
3026
3576
  purpose: Metric definitions, ownership, and data sources \u2014 so the team means the same thing
3027
3577
  read_when: Building dashboards, writing OKRs, reviewing product health, debugging data discrepancies
3028
3578
  last_updated: ${ctx.date}
3029
- owner:
3579
+ last_validated: ~
3580
+ source: ~
3581
+ owner:
3030
3582
  ---
3031
3583
 
3032
3584
  # Metrics
@@ -3276,6 +3828,13 @@ See \`team-foundry/engineering/quality-bar.md\` for the quality bar, bug triage
3276
3828
  - **Private folder** \u2014 \`team-foundry/private/\` is gitignored. Do not read from or write to it.${isSolo ? "" : `
3277
3829
  - **ADR commitments** \u2014 architecture decisions in \`team-foundry/engineering/decisions/\` represent committed choices. Treat them as constraints unless the user explicitly opens a discussion.`}
3278
3830
 
3831
+ ## Claude Code skills
3832
+
3833
+ If you are running as Claude Code, pre-built skills are available in \`.claude/skills/\`:
3834
+ \`/team-foundry-intro\`, \`/team-foundry-status\`, \`/team-foundry-review\`,
3835
+ \`/team-foundry-capture\`, \`/team-foundry-decision\`, \`/team-foundry-feature\`.
3836
+ See CLAUDE.md for the full skill table.
3837
+
3279
3838
  ## Working with the team-foundry coach
3280
3839
 
3281
3840
  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.
@@ -3288,6 +3847,165 @@ See \`team-foundry/context/glossary.md\` for domain terms, acronyms, and known n
3288
3847
  `}`;
3289
3848
  }
3290
3849
 
3850
+ // src/templates/hierarchy.ts
3851
+ function hierarchyTemplate(ctx) {
3852
+ return `---
3853
+ purpose: Trust precedence rules the coach applies when context conflicts across files
3854
+ read_when: Before reconciling conflicting signals; when claims in different files disagree
3855
+ last_updated: ${ctx.date}
3856
+ owner: team
3857
+ ---
3858
+
3859
+ # Context Hierarchy
3860
+
3861
+ When signals conflict across team-foundry files, apply these precedence rules in order.
3862
+ Do not average conflicting data \u2014 name the conflict and state which source wins and why.
3863
+
3864
+ ## File Precedence
3865
+
3866
+ 1. **Constitution** \u2014 CLAUDE.md / GEMINI.md (always-loaded rules and routing)
3867
+ 2. **Context files** \u2014 team-foundry/ files (the substance of what the team knows)
3868
+ 3. **Memory / notes** \u2014 session notes, conversation history
3869
+ 4. **Project logs** \u2014 git log, CI output, issue trackers
3870
+
3871
+ *When a context file contradicts a memory note, the context file wins.*
3872
+
3873
+ ## Data Source Precedence
3874
+
3875
+ 1. **Measured live data** \u2014 dashboards, analytics, cohort exports with a date
3876
+ 2. **Own assessment** \u2014 team's direct analysis with reasoning
3877
+ 3. **Own notes** \u2014 meeting notes, interview summaries
3878
+ 4. **Automatic notes** \u2014 AI-generated summaries, auto-transcripts
3879
+ 5. **Customer claims** \u2014 what customers say (without corroborating data)
3880
+
3881
+ *When cohort data disagrees with a customer claim, cohort data wins.*
3882
+ *Always note which source tier a claim comes from.*
3883
+
3884
+ ## Expertise Precedence
3885
+
3886
+ 1. **Domain expert** \u2014 someone with direct, validated experience in this area
3887
+ 2. **Team** \u2014 collective judgment based on what the team has observed
3888
+ 3. **AI general knowledge** \u2014 the model's training knowledge (no team-specific context)
3889
+ 4. **Web search** \u2014 external sources with no team context
3890
+
3891
+ *When your domain expert's view conflicts with AI general knowledge, the expert wins.*
3892
+
3893
+ ## Version Authority
3894
+
3895
+ - **Working docs** (team-foundry/ files, CLAUDE.md): latest commit wins
3896
+ - **Research** (customer interviews, experiments, market data): newer does not supersede older \u2014
3897
+ it adds a data point. A 2026 interview does not erase a 2025 cohort result; it adds signal.
3898
+ Surface both and name the gap.
3899
+
3900
+ ## How to Apply
3901
+
3902
+ When you see conflicting signals:
3903
+ 1. Name the conflict explicitly \u2014 do not silently pick one
3904
+ 2. State which source wins per the hierarchy above
3905
+ 3. Note what evidence would resolve the conflict
3906
+ `;
3907
+ }
3908
+
3909
+ // src/templates/instructions/hooks.ts
3910
+ function hooksTemplate(ctx) {
3911
+ return `---
3912
+ purpose: Enforced behavioral instructions \u2014 the AI must follow these before acting
3913
+ read_when: Before editing any team-foundry file; before using external tools or paid APIs
3914
+ last_updated: ${ctx.date}
3915
+ owner: team
3916
+ ---
3917
+
3918
+ # Hooks
3919
+
3920
+ These are markdown conventions, not executable scripts. They define behaviors the AI
3921
+ must treat as enforced \u2014 not suggestions to weigh against convenience.
3922
+
3923
+ In v3.1, an optional \`--with-hooks\` flag will generate real Claude Code hook scripts
3924
+ wired to \`.claude/settings.json\`. For now, treat this file as the enforcement layer.
3925
+
3926
+ ---
3927
+
3928
+ ## Before reconciling conflicting signals
3929
+
3930
+ Read \`.team-foundry/hierarchy.md\` first.
3931
+ Do not average conflicting claims. Name the conflict, state which source wins per the
3932
+ hierarchy, and note what evidence would resolve it.
3933
+
3934
+ **Never do:** silently pick the more recent or more confident-sounding claim without
3935
+ stating which hierarchy tier it comes from.
3936
+
3937
+ ---
3938
+
3939
+ ## Before editing any team-foundry file
3940
+
3941
+ Follow the conversation-as-update protocol in \`.team-foundry/coach.md\`.
3942
+
3943
+ 1. Surface the finding and name the gap (diagnosis message)
3944
+ 2. Draft the proposed change in a separate message
3945
+ 3. Wait for explicit confirmation before writing
3946
+
3947
+ **Never do:** edit team-foundry files without showing the draft and receiving
3948
+ a clear "yes", "looks good", "go ahead", or equivalent. Silence is not confirmation.
3949
+
3950
+ ---
3951
+
3952
+ ## Before using paid tools or external MCPs
3953
+
3954
+ Ask first. Paid tools include: web search APIs, external MCP servers billed per call,
3955
+ AI image generation, or any tool that incurs a cost outside this session.
3956
+
3957
+ State: what tool you want to use, why, and what the alternative is if the user declines.
3958
+
3959
+ **Never do:** trigger a paid or external tool call without the user's explicit approval
3960
+ in the current session.
3961
+ `;
3962
+ }
3963
+
3964
+ // src/templates/instructions/rules.ts
3965
+ function rulesTemplate(ctx) {
3966
+ return `---
3967
+ purpose: Always-loaded behavioral rules \u2014 kept minimal so they actually get followed
3968
+ read_when: Every coach session (explicit, scheduled, inline). Load before any coaching action.
3969
+ last_updated: ${ctx.date}
3970
+ owner: team
3971
+ ---
3972
+
3973
+ # Rules
3974
+
3975
+ Behavioral rules for the team-foundry coach. These are always loaded \u2014 keep this file
3976
+ minimal. If a rule applies to fewer than 80% of sessions, move it to a reference file
3977
+ and add a pointer here instead.
3978
+
3979
+ ---
3980
+
3981
+ 1. **Diagnostic-first.** Name the gap before suggesting a fix. Do not open with advice.
3982
+ "Your outcomes look like outputs" before "Here's how to reframe them."
3983
+
3984
+ 2. **Source every quantitative claim.** Any number, percentage, or currency figure in a
3985
+ team-foundry file needs a \`source:\` frontmatter value or an inline \`(source, date)\`.
3986
+ Flag unsourced claims \u2014 do not silently accept them.
3987
+
3988
+ 3. **Separate validated from hypothesized.** A claim in a Validated section without a
3989
+ \`source:\` reference or inline \`(source, date)\` is a hypothesis. Say so. \`last_validated:\`
3990
+ tracks staleness \u2014 it does not substitute for a source.
3991
+
3992
+ 4. **No silent writes.** Follow the conversation-as-update protocol. Draft \u2192 confirm \u2192
3993
+ write. Silence is not confirmation.
3994
+
3995
+ 5. **Hell-yes standard.** Every file, coaching nudge, and onboarding question must be
3996
+ obviously essential or it gets cut. Flag padding \u2014 don't add to it.
3997
+
3998
+ 6. **One nudge per response in inline mode.** Surface the single most important gap.
3999
+ Do not stack multiple coaching observations in the same inline message.
4000
+
4001
+ 7. **Respect nudge memory.** If the team has declined a finding in the last 7 days,
4002
+ do not resurface it unless they explicitly ask.
4003
+
4004
+ 8. **Never block work.** Coaching is observational. Note the gap, offer to help, then
4005
+ let the team proceed. Do not gate work on a coaching fix.
4006
+ `;
4007
+ }
4008
+
3291
4009
  // src/scaffold.ts
3292
4010
  var ALWAYS_ROOT_ENTRIES = [
3293
4011
  { relativePath: "AGENTS.md", content: rootAgentsTemplate }
@@ -3316,7 +4034,10 @@ var FULL_ONLY_ENTRIES = [
3316
4034
  { relativePath: "team-foundry/data/metrics.md", content: metricsTemplate },
3317
4035
  { relativePath: "team-foundry/context/glossary.md", content: glossaryTemplate },
3318
4036
  { relativePath: "team-foundry/context/stakeholders.md", content: stakeholdersTemplate },
3319
- { relativePath: "team-foundry/product/strategy.md", content: strategyTemplate }
4037
+ { relativePath: "team-foundry/product/strategy.md", content: strategyTemplate },
4038
+ { relativePath: ".team-foundry/hierarchy.md", content: hierarchyTemplate },
4039
+ { relativePath: ".team-foundry/instructions/hooks.md", content: hooksTemplate },
4040
+ { relativePath: ".team-foundry/instructions/rules.md", content: rulesTemplate }
3320
4041
  ];
3321
4042
  var FEDERATED_ENTRIES = [
3322
4043
  { relativePath: "team-foundry/product/CLAUDE.md", content: federatedProductTemplate },
@@ -3326,6 +4047,14 @@ var FEDERATED_ENTRIES = [
3326
4047
  { relativePath: "team-foundry/data/CLAUDE.md", content: federatedDataTemplate },
3327
4048
  { relativePath: "team-foundry/context/CLAUDE.md", content: federatedContextTemplate }
3328
4049
  ];
4050
+ var CLAUDE_SKILLS_ENTRIES = [
4051
+ { relativePath: ".claude/skills/team-foundry-intro.md", content: introSkillTemplate },
4052
+ { relativePath: ".claude/skills/team-foundry-status.md", content: statusSkillTemplate },
4053
+ { relativePath: ".claude/skills/team-foundry-review.md", content: reviewSkillTemplate },
4054
+ { relativePath: ".claude/skills/team-foundry-capture.md", content: captureSkillTemplate },
4055
+ { relativePath: ".claude/skills/team-foundry-decision.md", content: decisionSkillTemplate },
4056
+ { relativePath: ".claude/skills/team-foundry-feature.md", content: featureSkillTemplate }
4057
+ ];
3329
4058
  function rootEntries(tool) {
3330
4059
  if (tool === "claude") {
3331
4060
  return [{ relativePath: "CLAUDE.md", content: rootClaudeTemplate }];
@@ -3344,13 +4073,16 @@ function rootEntries(tool) {
3344
4073
  async function scaffold(options) {
3345
4074
  const { targetDir, profile, tool, repoVisibility, date, ingestionPath, ingestion, federated } = options;
3346
4075
  const ctx = { profile, tool, repoVisibility, date, ingestionPath, ingestion };
4076
+ const includesSkills = tool === "claude" || tool === "both";
3347
4077
  const entries = [
3348
4078
  ...ALWAYS_ROOT_ENTRIES,
3349
4079
  ...rootEntries(tool),
3350
4080
  ...SOLO_ENTRIES,
3351
4081
  ...profile === "full" ? FULL_ONLY_ENTRIES : [],
3352
- ...profile === "full" && federated ? FEDERATED_ENTRIES : []
4082
+ ...profile === "full" && federated ? FEDERATED_ENTRIES : [],
4083
+ ...includesSkills ? CLAUDE_SKILLS_ENTRIES : []
3353
4084
  ];
4085
+ const written = [];
3354
4086
  for (const entry of entries) {
3355
4087
  const fullPath = path.join(targetDir, entry.relativePath);
3356
4088
  const dir = path.dirname(fullPath);
@@ -3361,7 +4093,23 @@ async function scaffold(options) {
3361
4093
  } catch {
3362
4094
  }
3363
4095
  await fs.writeFile(fullPath, entry.content(ctx), "utf-8");
4096
+ written.push(entry.relativePath);
3364
4097
  }
4098
+ return written;
4099
+ }
4100
+ function gitAddCommand(tool) {
4101
+ const toolFiles = [];
4102
+ if (tool === "claude" || tool === "both") toolFiles.push("CLAUDE.md", ".claude/");
4103
+ if (tool === "gemini" || tool === "both") toolFiles.push("GEMINI.md");
4104
+ if (tool === "cursor") toolFiles.push(".cursor/");
4105
+ const paths = [
4106
+ "team-foundry/",
4107
+ ".team-foundry/",
4108
+ "AGENTS.md",
4109
+ "GETTING_STARTED.md",
4110
+ ...toolFiles
4111
+ ].join(" ");
4112
+ return `git add ${paths} && git commit -m "Add team-foundry"`;
3365
4113
  }
3366
4114
 
3367
4115
  // src/gitignore.ts
@@ -3801,7 +4549,131 @@ async function runStatus(targetDir) {
3801
4549
  }
3802
4550
  }
3803
4551
 
4552
+ // src/migrate.ts
4553
+ import fs4 from "fs/promises";
4554
+ import path4 from "path";
4555
+ import { confirm, log, outro as outro2 } from "@clack/prompts";
4556
+ async function detectTool(targetDir) {
4557
+ const checks = [
4558
+ [".cursor/rules/team-foundry.mdc", "cursor"],
4559
+ ["GEMINI.md", "gemini"],
4560
+ ["CLAUDE.md", "claude"]
4561
+ ];
4562
+ for (const [relPath, tool] of checks) {
4563
+ try {
4564
+ await fs4.access(path4.join(targetDir, relPath));
4565
+ return tool;
4566
+ } catch {
4567
+ }
4568
+ }
4569
+ return "claude";
4570
+ }
4571
+ async function detectMigrateState(targetDir) {
4572
+ const tfDir = path4.join(targetDir, ".team-foundry");
4573
+ try {
4574
+ await fs4.access(tfDir);
4575
+ } catch {
4576
+ return "none";
4577
+ }
4578
+ try {
4579
+ await fs4.access(path4.join(tfDir, "hierarchy.md"));
4580
+ return "v3";
4581
+ } catch {
4582
+ return "v2";
4583
+ }
4584
+ }
4585
+ async function writeIfAbsent(filePath, content) {
4586
+ await fs4.mkdir(path4.dirname(filePath), { recursive: true });
4587
+ try {
4588
+ await fs4.writeFile(filePath, content, { flag: "wx", encoding: "utf-8" });
4589
+ } catch (err) {
4590
+ if (err.code !== "EEXIST") throw err;
4591
+ }
4592
+ }
4593
+ async function appendFrontmatterFields(filePath) {
4594
+ let content;
4595
+ try {
4596
+ content = await fs4.readFile(filePath, "utf-8");
4597
+ } catch {
4598
+ return;
4599
+ }
4600
+ if (!content.startsWith("---")) return;
4601
+ const closeIdx = content.indexOf("\n---", 3);
4602
+ if (closeIdx === -1) return;
4603
+ const frontmatter = content.slice(0, closeIdx);
4604
+ const rest = content.slice(closeIdx);
4605
+ let updated = frontmatter;
4606
+ if (!frontmatter.includes("last_validated:")) {
4607
+ updated += "\nlast_validated: ~";
4608
+ }
4609
+ if (!frontmatter.includes("source:")) {
4610
+ updated += "\nsource: ~";
4611
+ }
4612
+ if (updated === frontmatter) return;
4613
+ await fs4.writeFile(filePath, updated + rest, "utf-8");
4614
+ }
4615
+ var DATA_HEAVY_FILES = [
4616
+ "team-foundry/product/outcomes.md",
4617
+ "team-foundry/product/customers.md",
4618
+ "team-foundry/data/metrics.md",
4619
+ "team-foundry/product/assumptions.md",
4620
+ "team-foundry/product/now-next-later.md"
4621
+ ];
4622
+ async function runMigrate(targetDir) {
4623
+ const state = await detectMigrateState(targetDir);
4624
+ if (state === "none") {
4625
+ log.error("No existing team-foundry found. Run npx create-team-foundry to scaffold v3.");
4626
+ process.exit(1);
4627
+ }
4628
+ if (state === "v3") {
4629
+ outro2("Already on v3. Nothing to migrate.");
4630
+ return;
4631
+ }
4632
+ log.info(
4633
+ "v2 team-foundry detected. Migration will add:\n\n .team-foundry/hierarchy.md\n .team-foundry/instructions/hooks.md\n .team-foundry/instructions/rules.md\n\nAnd append source: / last_validated: to frontmatter of:\n\n" + DATA_HEAVY_FILES.map((f) => ` ${f}`).join("\n") + "\n\nExisting files are never overwritten."
4634
+ );
4635
+ const ok = await confirm({ message: "Proceed with migration?" });
4636
+ if (!ok) {
4637
+ outro2("Migration cancelled. No files were changed.");
4638
+ return;
4639
+ }
4640
+ const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
4641
+ const tool = await detectTool(targetDir);
4642
+ const ctx = {
4643
+ profile: "full",
4644
+ tool,
4645
+ repoVisibility: "internal",
4646
+ date
4647
+ };
4648
+ const tfDir = path4.join(targetDir, ".team-foundry");
4649
+ await writeIfAbsent(path4.join(tfDir, "hierarchy.md"), hierarchyTemplate(ctx));
4650
+ await writeIfAbsent(path4.join(tfDir, "instructions", "hooks.md"), hooksTemplate(ctx));
4651
+ await writeIfAbsent(path4.join(tfDir, "instructions", "rules.md"), rulesTemplate(ctx));
4652
+ for (const relPath of DATA_HEAVY_FILES) {
4653
+ const filePath = path4.join(targetDir, relPath);
4654
+ await appendFrontmatterFields(filePath);
4655
+ }
4656
+ outro2(
4657
+ `Migration complete.
4658
+
4659
+ New files added to .team-foundry/. Your existing content is untouched.
4660
+
4661
+ Open your AI tool and say: "Read .team-foundry/hierarchy.md and let's review
4662
+ what changed in v3."`
4663
+ );
4664
+ }
4665
+
3804
4666
  // src/index.ts
4667
+ function groupByFolder(paths) {
4668
+ const groups = {};
4669
+ for (const p of paths) {
4670
+ const parts = p.split("/");
4671
+ const folder = parts.length > 1 ? parts[0] : ".";
4672
+ const file = parts.length > 1 ? parts.slice(1).join("/") : p;
4673
+ (groups[folder] ??= []).push(file);
4674
+ }
4675
+ return groups;
4676
+ }
3805
4677
  var TOOL_LABEL = {
3806
4678
  claude: "Claude Code",
3807
4679
  gemini: "Gemini CLI",
@@ -3823,69 +4695,119 @@ You can paste multiple documents \u2014 just separate them with a heading like:
3823
4695
  When you're done, save this file and start the onboarding interview.
3824
4696
  `;
3825
4697
  async function checkDirectory(targetDir) {
3826
- const prdPath = path4.join(targetDir, "team-foundry-prd-v2.md");
3827
- const scaffoldPath = path4.join(targetDir, "src", "scaffold.ts");
4698
+ const prdPath = path5.join(targetDir, "team-foundry-prd-v2.md");
4699
+ const scaffoldPath = path5.join(targetDir, "src", "scaffold.ts");
3828
4700
  let isSourceRepo = false;
3829
4701
  try {
3830
- await fs4.access(prdPath);
4702
+ await fs5.access(prdPath);
3831
4703
  isSourceRepo = true;
3832
4704
  } catch {
3833
4705
  }
3834
4706
  try {
3835
- await fs4.access(scaffoldPath);
4707
+ await fs5.access(scaffoldPath);
3836
4708
  isSourceRepo = true;
3837
4709
  } catch {
3838
4710
  }
3839
4711
  if (isSourceRepo) {
3840
- log.error(
4712
+ log2.error(
3841
4713
  "You're running create-team-foundry inside the team-foundry source repo.\nThis will overwrite development files.\n\ncd to your product repo first, then run this command again."
3842
4714
  );
3843
4715
  process.exit(1);
3844
4716
  }
3845
- const pkgPath = path4.join(targetDir, "package.json");
3846
- const srcPath = path4.join(targetDir, "src");
4717
+ const pkgPath = path5.join(targetDir, "package.json");
4718
+ const srcPath = path5.join(targetDir, "src");
3847
4719
  let hasPkg = false;
3848
4720
  let hasSrc = false;
3849
4721
  try {
3850
- await fs4.access(pkgPath);
4722
+ await fs5.access(pkgPath);
3851
4723
  hasPkg = true;
3852
4724
  } catch {
3853
4725
  }
3854
4726
  try {
3855
- await fs4.access(srcPath);
4727
+ await fs5.access(srcPath);
3856
4728
  hasSrc = true;
3857
4729
  } catch {
3858
4730
  }
3859
4731
  if (hasPkg && hasSrc) {
3860
- log.warn(
4732
+ log2.warn(
3861
4733
  "This directory has a package.json and src/ \u2014 it looks like a Node.js project.\nteam-foundry works best in your product repo, not inside a library or CLI repo.\nIf this is the right place, continue. Otherwise Ctrl-C and cd to your product repo."
3862
4734
  );
3863
- const ok = await confirm({ message: "Continue anyway?" });
4735
+ const ok = await confirm2({ message: "Continue anyway?" });
3864
4736
  if (!ok) {
3865
- outro2("Cancelled. cd to your product repo and try again.");
4737
+ outro3("Cancelled. cd to your product repo and try again.");
3866
4738
  process.exit(0);
3867
4739
  }
3868
4740
  }
3869
4741
  }
4742
+ async function checkExistingInstall(targetDir) {
4743
+ try {
4744
+ await fs5.access(path5.join(targetDir, "team-foundry"));
4745
+ } catch {
4746
+ return null;
4747
+ }
4748
+ log2.warn(
4749
+ "team-foundry is already set up in this directory.\nWhat would you like to do?"
4750
+ );
4751
+ const choice = await select2({
4752
+ message: "Choose an option:",
4753
+ options: [
4754
+ { value: "status", label: "Run status \u2014 see which files are stale or missing" },
4755
+ { value: "migrate", label: "Run migrate \u2014 upgrade to the latest profile" },
4756
+ { value: "continue", label: "Continue anyway \u2014 re-run setup (adds any missing files)" }
4757
+ ]
4758
+ });
4759
+ if (isCancel2(choice)) {
4760
+ outro3("Cancelled.");
4761
+ process.exit(0);
4762
+ }
4763
+ return choice;
4764
+ }
3870
4765
  async function main() {
3871
4766
  const targetDir = process.cwd();
3872
4767
  if (process.argv[2] === "status") {
3873
4768
  await runStatus(targetDir);
3874
4769
  return;
3875
4770
  }
4771
+ if (process.argv[2] === "migrate") {
4772
+ await runMigrate(targetDir);
4773
+ return;
4774
+ }
3876
4775
  await checkDirectory(targetDir);
4776
+ const installChoice = await checkExistingInstall(targetDir);
4777
+ if (installChoice === "status") {
4778
+ await runStatus(targetDir);
4779
+ return;
4780
+ }
4781
+ if (installChoice === "migrate") {
4782
+ await runMigrate(targetDir);
4783
+ return;
4784
+ }
3877
4785
  const answers = await runPrompts();
3878
4786
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
3879
- await scaffold({ ...answers, targetDir, date });
4787
+ const writtenPaths = await scaffold({ ...answers, targetDir, date });
3880
4788
  await writeGitignore(targetDir);
3881
4789
  if (answers.ingestion === "paste" || answers.ingestion === "repo+paste") {
3882
- const pastePath = path4.join(targetDir, ".team-foundry", "paste-content.md");
4790
+ const pastePath = path5.join(targetDir, ".team-foundry", "paste-content.md");
3883
4791
  try {
3884
- await fs4.access(pastePath);
4792
+ await fs5.access(pastePath);
3885
4793
  } catch {
3886
- await fs4.writeFile(pastePath, PASTE_PLACEHOLDER, "utf-8");
4794
+ await fs5.writeFile(pastePath, PASTE_PLACEHOLDER, "utf-8");
4795
+ writtenPaths.push(".team-foundry/paste-content.md");
3887
4796
  }
3888
4797
  }
4798
+ if (writtenPaths.length > 0) {
4799
+ const grouped = groupByFolder(writtenPaths);
4800
+ const lines = ["", "Files created:"];
4801
+ for (const [folder, files] of Object.entries(grouped)) {
4802
+ lines.push(` ${folder}/`);
4803
+ for (const file of files) lines.push(` ${file}`);
4804
+ }
4805
+ lines.push("", "Commit when ready:");
4806
+ lines.push(` ${gitAddCommand(answers.tool)}`);
4807
+ log2.info(lines.join("\n"));
4808
+ } else {
4809
+ log2.info("No new files created \u2014 all files already exist.");
4810
+ }
3889
4811
  const tool = TOOL_LABEL[answers.tool];
3890
4812
  let ingestionNote;
3891
4813
  if (answers.ingestion === "repo") {
@@ -3998,7 +4920,7 @@ Next steps:
3998
4920
  You can add existing docs later by editing .team-foundry/paste-content.md.
3999
4921
  `;
4000
4922
  }
4001
- outro2(
4923
+ outro3(
4002
4924
  `Done! Your files are in:
4003
4925
 
4004
4926
  ${targetDir}
@@ -4010,6 +4932,6 @@ team commits to, so everyone's AI tool gets the same context.`
4010
4932
  );
4011
4933
  }
4012
4934
  main().catch((err) => {
4013
- log.error(err instanceof Error ? err.message : String(err));
4935
+ log2.error(err instanceof Error ? err.message : String(err));
4014
4936
  process.exit(1);
4015
4937
  });