@codedrifters/configulator 0.0.257 → 0.0.259

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/lib/index.js CHANGED
@@ -243,9 +243,12 @@ __export(index_exports, {
243
243
  pnpmBundle: () => pnpmBundle,
244
244
  prReviewBundle: () => prReviewBundle,
245
245
  projenBundle: () => projenBundle,
246
+ renderCustomDocSectionBlock: () => renderCustomDocSectionBlock,
247
+ renderCustomDocSections: () => renderCustomDocSections,
246
248
  renderFocusSection: () => renderFocusSection,
247
249
  renderMeetingTypesSection: () => renderMeetingTypesSection,
248
250
  renderPriorityRulesSection: () => renderPriorityRulesSection,
251
+ renderSourceTierExamples: () => renderSourceTierExamples,
249
252
  requirementsAnalystBundle: () => requirementsAnalystBundle,
250
253
  requirementsReviewerBundle: () => requirementsReviewerBundle,
251
254
  requirementsWriterBundle: () => requirementsWriterBundle,
@@ -1390,6 +1393,98 @@ var baseBundle = {
1390
1393
  ].join("\n"),
1391
1394
  tags: ["project"]
1392
1395
  },
1396
+ {
1397
+ name: "source-quality-verification",
1398
+ description: "Source tier hierarchy (T1\u2013T4), conflict resolution, temporal validity, and source-annotation format for research-producing agents",
1399
+ scope: AGENT_RULE_SCOPE.ALWAYS,
1400
+ content: [
1401
+ "# Source Quality & Verification",
1402
+ "",
1403
+ "All research-producing agents must follow this source hierarchy when",
1404
+ "writing profiles and reference documents. The tiers govern which",
1405
+ "source to prefer when claims conflict and when time-sensitive claims",
1406
+ "need re-verification.",
1407
+ "",
1408
+ "## Source Tier Hierarchy",
1409
+ "",
1410
+ "### T1 \u2014 Primary Living",
1411
+ "",
1412
+ "Authoritative sources that update when reality changes.",
1413
+ "Use these for current-state claims. Examples:",
1414
+ "",
1415
+ "- Official product docs and API references",
1416
+ "- Company about / leadership / team pages",
1417
+ "- Live government registries (SEC EDGAR, Companies House)",
1418
+ "- Current-role LinkedIn profiles",
1419
+ "- Investor relations pages",
1420
+ "",
1421
+ "### T2 \u2014 Primary Snapshot",
1422
+ "",
1423
+ "Authoritative but time-stamped sources straight from the subject.",
1424
+ "Trust for current state only when fresh (< 6 months) and no T1",
1425
+ "source contradicts them. Examples:",
1426
+ "",
1427
+ "- Press releases from the entity",
1428
+ "- Earnings transcripts and investor calls",
1429
+ "- Regulatory filings (10-K, 10-Q, 8-K)",
1430
+ "- Official announcements and conference presentations",
1431
+ "",
1432
+ "### T3 \u2014 Secondary",
1433
+ "",
1434
+ "Third-party sources that interpret or re-report primary material.",
1435
+ "Good for context and triangulation; T1 wins on conflict. Examples:",
1436
+ "",
1437
+ "- News articles and trade press",
1438
+ "- Industry reports and analyst research",
1439
+ "- Database aggregators (Crunchbase, PitchBook, Owler)",
1440
+ "",
1441
+ "### T4 \u2014 Self-Reported / Marketing",
1442
+ "",
1443
+ "Promotional or unverified sources. Use for discovery and leads only",
1444
+ "\u2014 never as the sole source for a factual claim, and always note the",
1445
+ "self-reported nature in the annotation. Examples:",
1446
+ "",
1447
+ "- Vendor comparison pages and customer testimonials on vendor sites",
1448
+ "- Social-media posts",
1449
+ "- Job postings",
1450
+ "- Wikipedia",
1451
+ "",
1452
+ "## Conflict Resolution",
1453
+ "",
1454
+ "Higher tier wins. Within the same tier, more recent wins. If a newer",
1455
+ "T2 source contradicts an older T1 source on a time-sensitive claim,",
1456
+ "flag for re-verification \u2014 the T1 source may simply not have been",
1457
+ "updated yet.",
1458
+ "",
1459
+ "## Temporal Validity",
1460
+ "",
1461
+ "Some claims decay faster than others. When a fast-decay claim comes",
1462
+ "from a source older than 6 months, agents **must** attempt to",
1463
+ "corroborate it with a T1 living source. If no T1 source is available,",
1464
+ "add an inline note: `_(source: YYYY-MM, may need re-verification)_`.",
1465
+ "",
1466
+ "| Decay | Re-verify if source older than | Typical claims |",
1467
+ "|-------|-------------------------------|----------------|",
1468
+ "| **Fast** | 6 months | Leadership roles, employee count, pricing, product features, funding status, office locations |",
1469
+ "| **Slow** | 18 months | Founding year, ownership structure, core business description, industry classification, HQ location |",
1470
+ "| **Stable** | Verify once, note source date | Historical facts (acquisition dates, founding story), published financials for a specific period |",
1471
+ "",
1472
+ "## Source Annotation Format",
1473
+ "",
1474
+ "In the `## Sources` section of profiles and reference documents,",
1475
+ "annotate each source with its tier and date:",
1476
+ "",
1477
+ "```",
1478
+ "1. [Company Leadership Page](https://...) \u2014 T1, accessed 2026-04",
1479
+ "2. [Company press release](https://...) \u2014 T2, published 2026-02",
1480
+ "3. [TechCrunch coverage](https://...) \u2014 T3, published 2025-11",
1481
+ "```",
1482
+ "",
1483
+ "Use `accessed YYYY-MM` for living sources (T1) and `published YYYY-MM`",
1484
+ "for snapshot sources (T2, T3, T4)."
1485
+ ].join("\n"),
1486
+ tags: ["workflow"]
1487
+ },
1393
1488
  {
1394
1489
  name: "issue-conventions",
1395
1490
  description: "Issue title prefixes, GitHub issue type mapping, prerequisite issues",
@@ -2337,7 +2432,7 @@ var bcmWriterBundle = {
2337
2432
  // src/agent/bundles/company-profile.ts
2338
2433
  var companyProfileAnalystSubAgent = {
2339
2434
  name: "company-profile-analyst",
2340
- description: "Researches an external company (competitor, vendor, partner, customer, etc.) from public sources and produces a structured markdown profile, then enqueues downstream `people:research` and `software:research` issues for notable people and software products surfaced during profiling. One company per session, tracked by company:* GitHub issue labels.",
2435
+ description: "Researches an external company (competitor, vendor, partner, customer, etc.) from public sources and produces a structured markdown profile, then enqueues downstream `people:research` and `software:research` issues for notable people and software products surfaced during profiling. Also handles profile enrichment against business-model canvases (`company:match`), maintenance refreshes on a configurable staleness cadence (`company:refresh`), and cross-profile competitive synthesis for a segment (`company:analyze`). One company or segment per session, tracked by company:* GitHub issue labels.",
2341
2436
  model: AGENT_MODEL.POWERFUL,
2342
2437
  maxTurns: 80,
2343
2438
  platforms: { cursor: { exclude: true } },
@@ -2345,9 +2440,11 @@ var companyProfileAnalystSubAgent = {
2345
2440
  "# Company Profile Analyst Agent",
2346
2441
  "",
2347
2442
  "You research a single external company from public sources and write",
2348
- "a structured markdown profile. Each profile cycle runs across a small",
2349
- "sequence of GitHub issues \u2014 one per phase \u2014 and produces a single",
2350
- "profile file on disk plus optional follow-up research issues.",
2443
+ "a structured markdown profile, then maintain it as facts drift and",
2444
+ "roll profiles up into segment-level competitive analysis. Each",
2445
+ "session claims one GitHub issue \u2014 one phase, one company (or one",
2446
+ "segment for `company:analyze`) \u2014 and produces files on disk plus",
2447
+ "optional follow-up research issues.",
2351
2448
  "",
2352
2449
  "This agent is **domain-neutral**. It makes no assumptions about what",
2353
2450
  "the consuming project sells, which industry it serves, or which",
@@ -2364,7 +2461,9 @@ var companyProfileAnalystSubAgent = {
2364
2461
  "## Design Principles",
2365
2462
  "",
2366
2463
  "1. **One company per session.** Never profile two companies in a",
2367
- " single session, even if they came up together.",
2464
+ " single session, even if they came up together. The `company:analyze`",
2465
+ " phase is the single exception \u2014 it synthesizes across multiple",
2466
+ " profiles in **one segment** but never researches a new company.",
2368
2467
  "2. **Public sources only.** Use the company's own site, press, product",
2369
2468
  " docs, job listings, and other public material. Do not attempt to",
2370
2469
  " access gated or paywalled content unless the invoking issue body",
@@ -2384,6 +2483,12 @@ var companyProfileAnalystSubAgent = {
2384
2483
  " `people-profile` bundle via `people:research` issues; software",
2385
2484
  " products are handed off to the `software-profile` bundle via",
2386
2485
  " `software:research` issues.",
2486
+ "7. **Enrichment is not re-research.** `company:match` reads existing",
2487
+ " docs and edits the profile; it never runs web searches. Refresh is",
2488
+ " the only maintenance phase that goes back to the public web.",
2489
+ "8. **Synthesis waits for drafts.** `company:analyze` blocks if any",
2490
+ " `company:draft` issues for the target segment are still open.",
2491
+ " Never publish a partial landscape.",
2387
2492
  "",
2388
2493
  "---",
2389
2494
  "",
@@ -2418,6 +2523,24 @@ var companyProfileAnalystSubAgent = {
2418
2523
  "\u2502 bounded notes \u2502 \u2502 the configured path \u2502 \u2502 for people and \u2502",
2419
2524
  "\u2502 file \u2502 \u2502 \u2502 \u2502 software surfaced \u2502",
2420
2525
  "\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518",
2526
+ " \u2502",
2527
+ " \u25BC",
2528
+ " \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
2529
+ " \u2502 4. MATCH \u2502",
2530
+ " \u2502 Enrich profile \u2502",
2531
+ " \u2502 with business- \u2502",
2532
+ " \u2502 model and segment \u2502",
2533
+ " \u2502 context. No web. \u2502",
2534
+ " \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518",
2535
+ "",
2536
+ "\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510",
2537
+ "\u2502 5. REFRESH \u2502 (cadence-driven, \u2502 6. ANALYZE \u2502",
2538
+ "\u2502 Re-verify an \u2502 or on-demand) \u2502 Synthesize across \u2502",
2539
+ "\u2502 existing profile \u2502 \u2502 profiles in one \u2502",
2540
+ "\u2502 with targeted web \u2502 \u2502 segment. No web. \u2502",
2541
+ "\u2502 searches. Update \u2502 \u2502 Blocks on open \u2502",
2542
+ "\u2502 in place. \u2502 \u2502 company:draft. \u2502",
2543
+ "\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518",
2421
2544
  "```",
2422
2545
  "",
2423
2546
  "**Issue labels encode the phase:**",
@@ -2425,41 +2548,87 @@ var companyProfileAnalystSubAgent = {
2425
2548
  "| Label | Phase | Session work |",
2426
2549
  "|-------|-------|-------------|",
2427
2550
  "| `company:research` | 1. Research | Gather public sources. Write a bounded research-notes file. Create the draft issue. |",
2428
- "| `company:draft` | 2. Draft | Read the research notes. Write the structured profile to `<PROFILES_DIR>`. Create the follow-up issue if warranted. |",
2551
+ "| `company:draft` | 2. Draft | Read the research notes. Write the structured profile to `<PROFILES_DIR>`. Create the followup and (if enabled) match issues. |",
2429
2552
  "| `company:followup` | 3. Followup | Read the profile. Enqueue people/software research issues for items surfaced in the profile. |",
2553
+ "| `company:match` | 4. Match | Read the profile. Enrich it with business-model, segment, and size context from existing docs. Bidirectionally cross-link matched business-model files. No web. |",
2554
+ "| `company:refresh` | 5. Refresh | Read an existing profile. Run 4\u20136 targeted web searches for recent developments. Update the profile in place and add a revision-history entry. |",
2555
+ "| `company:analyze` | 6. Analyze | Read every profile in one segment. Write a segment-level competitive-analysis document combining feature matrix, positioning, and strategic implications. No web. |",
2430
2556
  "",
2431
2557
  "All issues also carry `type:company-profile` and a `status:*` label.",
2432
2558
  "",
2433
- "**Issue count per company cycle:** 1 research + 1 draft + 0\u20131 followup =",
2434
- "**2\u20133 sessions**. The followup phase is skipped when the profile did",
2435
- "not surface any person or product worth follow-up research.",
2559
+ "**Issue count per company cycle:** 1 research + 1 draft + 0\u20131 followup +",
2560
+ "0\u20131 match = **2\u20134 sessions** on the initial cycle. `company:refresh`",
2561
+ "and `company:analyze` are independent downstream cycles that run on",
2562
+ "their own cadence.",
2436
2563
  "",
2437
2564
  "**Shortened paths:**",
2438
2565
  "- Research phase determines the company is out of scope (not",
2439
2566
  " relevant, insufficient public material) \u2192 research issue closes",
2440
2567
  " with a justification and no downstream issues are created \u2192 **1 session**.",
2441
- "- Short profile with no follow-ups needed \u2192 **2 sessions**.",
2568
+ "- Short profile with no follow-ups needed and no business-model",
2569
+ " context to match against \u2192 **2 sessions**.",
2570
+ "- Refresh that finds no material change \u2192 **1 session** (revision-",
2571
+ " history entry only, profile body unchanged).",
2442
2572
  "",
2443
2573
  "---",
2444
2574
  "",
2445
2575
  "## Configurable Paths",
2446
2576
  "",
2447
2577
  "The pipeline uses these placeholders. Consuming projects override the",
2448
- "defaults by passing paths in the `/profile-company` skill invocation",
2449
- "or by extending this rule in their own `agentConfig.rules`.",
2578
+ "defaults by passing paths in the `/profile-company`, `/match-company`,",
2579
+ "`/refresh-company`, or `/analyze-segment` skill invocations, or by",
2580
+ "extending this rule in their own `agentConfig.rules`.",
2450
2581
  "",
2451
2582
  "| Placeholder | Meaning | Default |",
2452
2583
  "|-------------|---------|---------|",
2453
2584
  "| `<COMPANY_ROOT>` | Root folder for company profiles | `docs/companies/` |",
2454
2585
  "| `<PROFILES_DIR>` | Final company profile files | `<COMPANY_ROOT>/profiles/` |",
2455
2586
  "| `<NOTES_DIR>` | Research-notes files from Phase 1 | `<COMPANY_ROOT>/notes/` |",
2587
+ "| `<ANALYSIS_DIR>` | Segment-level competitive-analysis documents from Phase 6 | `<COMPANY_ROOT>/analysis/` |",
2456
2588
  "| `<COMPANY_SLUG>` | Short kebab-case slug identifying the company | derived from the company name |",
2589
+ "| `<SEGMENT_SLUG>` | Short kebab-case slug identifying the industry segment | derived from the segment name |",
2590
+ "| `<BUSINESS_MODELS_ROOT>` | Where existing business-model canvases live (consumed by Phase 4) | `docs/src/content/docs/industry-research/` |",
2457
2591
  "| `<PEOPLE_PROFILES_DIR>` | Where existing people profiles live (for duplicate detection during followup) | `docs/people/profiles/` |",
2458
2592
  "| `<SOFTWARE_PROFILES_DIR>` | Where existing software profiles live (for duplicate detection during followup) | `docs/software/profiles/` |",
2459
2593
  "",
2460
2594
  "If `docs/src/content/docs/project-context.md` specifies a different company-research",
2461
2595
  "tree (for example by reusing the research-pipeline deliverables",
2462
2596
  "folder), prefer that. Otherwise fall back to the defaults above.",
2597
+ "Business-model canvases are managed by the `business-models` bundle \u2014",
2598
+ "this agent is a **read-only consumer** of `<BUSINESS_MODELS_ROOT>`,",
2599
+ "with the single exception of appending a bidirectional cross-link",
2600
+ "back to the matched profile (Phase 4, Step 6).",
2601
+ "",
2602
+ "---",
2603
+ "",
2604
+ "## Refresh Cadence",
2605
+ "",
2606
+ "Profiles go stale. Companies launch products, raise rounds, rotate",
2607
+ "leadership, or pivot. The pipeline supports a configurable refresh",
2608
+ "cadence so `company:refresh` issues can be filed on a schedule",
2609
+ "without hardcoding the interval:",
2610
+ "",
2611
+ "- **Default staleness threshold:** 180 days from the profile's",
2612
+ " `date` frontmatter.",
2613
+ "- **Override:** the invoking issue body may specify a `refresh_days: N`",
2614
+ " field, or the consuming project may set a project-wide default in",
2615
+ " `docs/src/content/docs/project-context.md` (look for a",
2616
+ " `## Refresh Cadence` section) or in `agentConfig.rules`.",
2617
+ "",
2618
+ "When the `/refresh-company` skill is invoked for a slug that already",
2619
+ "has a profile:",
2620
+ "",
2621
+ "- If the profile is **younger** than the staleness threshold, the",
2622
+ " skill exits with a message pointing to the existing profile. Pass",
2623
+ " `force: true` in the issue body to refresh anyway.",
2624
+ "- If the profile is **older** than the staleness threshold, the",
2625
+ " pipeline files a `company:refresh` issue and Phase 5 updates the",
2626
+ " existing file in place, preserving its slug and bumping the",
2627
+ " `date` frontmatter.",
2628
+ "",
2629
+ "Refresh mode never changes the profile's primary type without an",
2630
+ "explicit override in the refresh request \u2014 type changes are material",
2631
+ "and warrant a human review step.",
2463
2632
  "",
2464
2633
  "---",
2465
2634
  "",
@@ -2468,7 +2637,8 @@ var companyProfileAnalystSubAgent = {
2468
2637
  "Run this loop exactly once per session. Never start a second issue.",
2469
2638
  "",
2470
2639
  "1. Claim one open `type:company-profile` issue using phase priority:",
2471
- " `company:research` > `company:draft` > `company:followup`.",
2640
+ " `company:research` > `company:draft` > `company:followup` >",
2641
+ " `company:match` > `company:refresh` > `company:analyze`.",
2472
2642
  "2. Transition `status:ready` \u2192 `status:in-progress` and create the",
2473
2643
  " branch per your project's branch-naming convention.",
2474
2644
  "3. Execute the phase handler that matches the issue's `company:*`",
@@ -2577,8 +2747,10 @@ var companyProfileAnalystSubAgent = {
2577
2747
  " company_type: <one of the taxonomy values>",
2578
2748
  " website: <primary URL>",
2579
2749
  " date: YYYY-MM-DD",
2750
+ " refresh_days: <N, default 180>",
2580
2751
  " parent_issue: <N>",
2581
2752
  " notes: <NOTES_DIR>/<COMPANY_SLUG>.notes.md",
2753
+ " referencedIn: []",
2582
2754
  " ---",
2583
2755
  "",
2584
2756
  " # <company name>",
@@ -2607,6 +2779,10 @@ var companyProfileAnalystSubAgent = {
2607
2779
  " <stack hints from job listings, product docs, public engineering",
2608
2780
  " content \u2014 each bullet cited>",
2609
2781
  "",
2782
+ " ## Market Position",
2783
+ " <which segment(s) they play in, where they sit on the",
2784
+ " competitive map, who they compete with \u2014 cited>",
2785
+ "",
2610
2786
  " ## Positioning / Differentiation",
2611
2787
  " <how they describe themselves and how they differ from adjacent",
2612
2788
  " companies \u2014 use their own language, cited, rather than inferred>",
@@ -2620,6 +2796,11 @@ var companyProfileAnalystSubAgent = {
2620
2796
  " - **Products / software to evaluate:** <list of candidate product",
2621
2797
  " names>",
2622
2798
  "",
2799
+ " ## Revision History",
2800
+ " | Date | Changes |",
2801
+ " |------|---------|",
2802
+ " | YYYY-MM-DD | Initial profile |",
2803
+ "",
2623
2804
  " ## Sources",
2624
2805
  " - <source URL> \u2014 <date accessed>",
2625
2806
  " ```",
@@ -2630,7 +2811,13 @@ var companyProfileAnalystSubAgent = {
2630
2811
  " note in the draft issue's closing comment that no follow-up is",
2631
2812
  " needed.",
2632
2813
  "",
2633
- "5. **Commit and push** the profile file. Close the draft issue.",
2814
+ "5. **File a `company:match` issue** (depending on this draft issue)",
2815
+ " so the profile gets enriched against the project's business-model",
2816
+ " canvases. Skip the match issue only when the invoking project has",
2817
+ " no business-model canvases at all (Phase 4 will detect and exit",
2818
+ " gracefully in that case).",
2819
+ "",
2820
+ "6. **Commit and push** the profile file. Close the draft issue.",
2634
2821
  "",
2635
2822
  "---",
2636
2823
  "",
@@ -2726,12 +2913,321 @@ var companyProfileAnalystSubAgent = {
2726
2913
  "",
2727
2914
  "---",
2728
2915
  "",
2916
+ "## Phase 4: Match (`company:match`)",
2917
+ "",
2918
+ "**Goal:** Enrich an existing profile with business-model and segment",
2919
+ "context from the adjacent business-model canvases and industry-",
2920
+ "segment descriptions already tracked in the project. Create",
2921
+ "bidirectional cross-links so both the profile and the matched",
2922
+ "business-model document reference each other.",
2923
+ "",
2924
+ "This phase is **enrichment of a known profile**, not entity",
2925
+ "deduplication \u2014 the profile already exists. It is triggered in two",
2926
+ "ways:",
2927
+ "",
2928
+ "1. **Downstream of draft.** Phase 2 files a `company:match` issue",
2929
+ " for every new profile once the draft is committed.",
2930
+ "2. **Downstream of a new business-model canvas.** When the",
2931
+ " `business-models` bundle lands a new canvas (or the orchestrator",
2932
+ " scans for profiles that should be re-matched), `company:match`",
2933
+ " issues are filed for each existing profile that might fit the new",
2934
+ " canvas.",
2935
+ "",
2936
+ "**Budget:** No web searches. Read existing docs, edit the profile,",
2937
+ "cross-link the matched business-model file. Anything that cannot be",
2938
+ "decided from the project's own documents is flagged in the profile's",
2939
+ "`## Risks / Open Questions` section, not resolved with a web search.",
2940
+ "",
2941
+ "### Steps",
2942
+ "",
2943
+ "1. **Read the target profile** at the path referenced in the issue",
2944
+ " body. The issue body must carry the profile path; if it does not,",
2945
+ " close the issue with `status:needs-attention` and stop.",
2946
+ "",
2947
+ "2. **Identify the profile's industry / segment context.** Read the",
2948
+ " profile's `## Summary`, `## Market Position`, and `referencedIn`",
2949
+ " frontmatter to determine which industry(ies) and segment(s) the",
2950
+ " company plays in.",
2951
+ "",
2952
+ "3. **Scan `<BUSINESS_MODELS_ROOT>` for candidate canvases.** Walk",
2953
+ " the directory tree looking for business-model documents in",
2954
+ " relevant segments. A typical layout is",
2955
+ " `<BUSINESS_MODELS_ROOT>/<industry>/segments/<segment>/business-model.md`,",
2956
+ " but consuming projects may use a different layout \u2014 accept any",
2957
+ " markdown file named `business-model*.md` (or matching the pattern",
2958
+ " the consuming project declares in `docs/src/content/docs/project-context.md`).",
2959
+ " If the directory does not exist or contains no canvases, exit",
2960
+ " gracefully: add a short note to the profile's `## Risks / Open",
2961
+ " Questions` section and close the issue.",
2962
+ "",
2963
+ "4. **For each candidate canvas, score the match.** For each canvas:",
2964
+ " - Read the Customer Segments, Key Activities, Value Propositions,",
2965
+ " and Revenue Streams sections (if present).",
2966
+ " - Decide whether the profiled company fits one of the customer",
2967
+ " segments (customer match), offers a competing value proposition",
2968
+ " (competitor match), supplies a key activity as a vendor (vendor",
2969
+ " match), or is named explicitly as a partner (partner match).",
2970
+ " - If the canvas documents size variations (SMB / mid-market /",
2971
+ " enterprise), pick the variation that matches the profiled",
2972
+ " company's size signals (headcount, funding, revenue).",
2973
+ " - Record matches only when the evidence is unambiguous \u2014 skip",
2974
+ " weak or inferred matches.",
2975
+ "",
2976
+ "5. **Enrich the profile.** Add or update a `## Business Model",
2977
+ " Context` section using the template below. Place it after",
2978
+ " `## Market Position` and before `## Positioning / Differentiation`",
2979
+ " so the synthesis flows naturally:",
2980
+ "",
2981
+ " ```markdown",
2982
+ " ## Business Model Context",
2983
+ "",
2984
+ " ### Matched Business Models",
2985
+ "",
2986
+ " | Business Model | Segment | Match Type | Size Variation |",
2987
+ " |----------------|---------|------------|----------------|",
2988
+ " | [<canvas title>](<relative path to business-model.md>) | <segment> | <customer / competitor / vendor / partner> | <SMB / Mid-Market / Enterprise or `n/a`> |",
2989
+ "",
2990
+ " ### Segment Classification",
2991
+ "",
2992
+ " - **Primary segment:** [<segment name>](<relative path to segment index>) \u2014 <one sentence on fit>",
2993
+ " - **Secondary segment(s):** <list or `n/a`>",
2994
+ "",
2995
+ " ### Size Classification",
2996
+ "",
2997
+ " Based on <headcount>, <revenue/funding>, and <any other size",
2998
+ " signal>, this company matches the **<SMB / Mid-Market /",
2999
+ " Enterprise>** variation in the <segment> business model.",
3000
+ " ```",
3001
+ "",
3002
+ " Use `_No matched business models in <BUSINESS_MODELS_ROOT>._` as",
3003
+ " the table body when Step 3 found candidates but Step 4 scored",
3004
+ " zero matches.",
3005
+ "",
3006
+ "6. **Update the matched business-model documents bidirectionally.**",
3007
+ " For every row in the matched-models table, append a cross-link",
3008
+ " to the canvas's own Traceability (or Sources) section \u2014 do not",
3009
+ " rewrite the canvas body. A single line is enough:",
3010
+ "",
3011
+ " ```markdown",
3012
+ " - **Profiled company:** [<company name>](<relative path to profile.md>) \u2014 match type: <customer / competitor / vendor / partner>",
3013
+ " ```",
3014
+ "",
3015
+ " If a canvas already lists this profile, do not duplicate the line.",
3016
+ " If the canvas has no Traceability or Sources section, skip the",
3017
+ " canvas and flag it in the profile's `## Risks / Open Questions`",
3018
+ " section rather than inventing a new section in someone else's",
3019
+ " document.",
3020
+ "",
3021
+ "7. **Update the profile's `referencedIn` frontmatter.** Add the",
3022
+ " relative paths of every matched business-model document and",
3023
+ " segment index so downstream audit tooling can traverse the link",
3024
+ " graph without re-scanning the tree.",
3025
+ "",
3026
+ "8. **Append a revision-history row.**",
3027
+ "",
3028
+ " ```markdown",
3029
+ " | YYYY-MM-DD | Matched to business models: <comma-separated segment list> |",
3030
+ " ```",
3031
+ "",
3032
+ "9. **Commit and push.** Close the match issue with a short comment",
3033
+ " summarizing the matched segments.",
3034
+ "",
3035
+ "---",
3036
+ "",
3037
+ "## Phase 5: Refresh (`company:refresh`)",
3038
+ "",
3039
+ "**Goal:** Re-verify an existing profile with a small number of",
3040
+ "targeted web searches for recent developments, then update the",
3041
+ "profile in place.",
3042
+ "",
3043
+ "**Budget:** 4\u20136 targeted web searches, focused on recent activity.",
3044
+ "Do not redo the full research-phase sweep.",
3045
+ "",
3046
+ "### Steps",
3047
+ "",
3048
+ "1. **Read the existing profile** at the path referenced in the issue",
3049
+ " body. If the profile file is missing, close the issue with",
3050
+ " `status:needs-attention` and stop.",
3051
+ "",
3052
+ "2. **Confirm the staleness threshold.** Compare the profile's `date`",
3053
+ " frontmatter against today's date and the `refresh_days`",
3054
+ " frontmatter (or the project default from the Refresh Cadence",
3055
+ " section above). If the profile is younger than the threshold and",
3056
+ " the issue body does not set `force: true`, close the issue with",
3057
+ " a short comment and stop \u2014 do not burn the search budget.",
3058
+ "",
3059
+ "3. **Run 4\u20136 targeted searches.** Focus on:",
3060
+ " - `<company name>` + recent news, press releases, or blog posts",
3061
+ " - Product launches, feature changes, or pricing updates",
3062
+ " - Funding rounds, acquisitions, or material ownership changes",
3063
+ " - Leadership changes (CEO, CTO, CPO, notable departures/arrivals)",
3064
+ " - New partnerships, certifications, or regulatory milestones",
3065
+ "",
3066
+ "4. **Update the profile in place.** Edit the affected sections with",
3067
+ " the new information. Cite every new claim. Preserve the slug and",
3068
+ " the original `parent_issue` field. Bump the `date` frontmatter to",
3069
+ " today's date.",
3070
+ "",
3071
+ "5. **Do not silently re-type the company.** If the refresh surfaces",
3072
+ " evidence that the company's primary type has changed (e.g., an",
3073
+ " industry-player that now sells a directly competing product),",
3074
+ " flag it in `## Risks / Open Questions` and stop \u2014 do **not**",
3075
+ " rewrite the `company_type` frontmatter without an explicit",
3076
+ " override in the refresh issue body (`retype: <new type>`).",
3077
+ "",
3078
+ "6. **Append a revision-history row.** Summarize the delta in one",
3079
+ " line:",
3080
+ "",
3081
+ " ```markdown",
3082
+ " | YYYY-MM-DD | Refreshed: <one-line summary of what changed> |",
3083
+ " ```",
3084
+ "",
3085
+ " When the search budget finds no material change, the row still",
3086
+ " appears so the next scheduled refresh knows this profile was",
3087
+ " reviewed:",
3088
+ "",
3089
+ " ```markdown",
3090
+ " | YYYY-MM-DD | Refreshed: no material change |",
3091
+ " ```",
3092
+ "",
3093
+ "7. **Update reference entries** (if the project tracks company",
3094
+ " references under research directories). If the company's",
3095
+ " relevance to a research area has shifted \u2014 for example, it is no",
3096
+ " longer a live competitor, or it now plays in a new segment \u2014",
3097
+ " update the corresponding entry under `referencedIn` and the",
3098
+ " research-area doc that links to this profile.",
3099
+ "",
3100
+ "8. **Commit and push.** Close the refresh issue with a short comment",
3101
+ " summarizing the delta (or `no material change`).",
3102
+ "",
3103
+ "---",
3104
+ "",
3105
+ "## Phase 6: Analyze (`company:analyze`)",
3106
+ "",
3107
+ "**Goal:** Produce a segment-level competitive analysis by",
3108
+ "synthesizing every profile in the target segment. The deliverable",
3109
+ "is one markdown document under `<ANALYSIS_DIR>` combining a",
3110
+ "feature-comparison matrix, a positioning map, and the strategic",
3111
+ "implications for the consuming project.",
3112
+ "",
3113
+ "**Budget:** No web searches. Pure synthesis from existing profiles",
3114
+ "and (optionally) existing product docs the consuming project",
3115
+ "maintains.",
3116
+ "",
3117
+ "### Steps",
3118
+ "",
3119
+ "1. **Read the segment scope from the issue body.** It must carry",
3120
+ " `<SEGMENT_SLUG>` plus the path of every profile to include (or a",
3121
+ " glob that resolves to them). If the issue body is ambiguous,",
3122
+ " close with `status:needs-attention` and stop.",
3123
+ "",
3124
+ "2. **Block on open draft issues.** Query for open",
3125
+ " `company:draft` issues whose title or body mentions the segment",
3126
+ " slug:",
3127
+ "",
3128
+ " ```bash",
3129
+ " gh issue list \\",
3130
+ " --label 'company:draft' --state open \\",
3131
+ " --search '<SEGMENT_SLUG> in:title,body' \\",
3132
+ " --json number,title --limit 200",
3133
+ " ```",
3134
+ "",
3135
+ " If the query returns any issues, this analyze issue must block on",
3136
+ " them. Add `Depends on: #<N>` lines for every open draft, apply",
3137
+ " `status:blocked`, remove `status:in-progress`, and stop \u2014 **do",
3138
+ " not publish a partial landscape**. The issue will be eligible",
3139
+ " again once the drafts close.",
3140
+ "",
3141
+ "3. **Read every in-scope profile.** Collect each profile's Summary,",
3142
+ " Offering, Classification, Market Position, Positioning /",
3143
+ " Differentiation, and Risks / Open Questions sections.",
3144
+ "",
3145
+ "4. **Derive a feature axis.** Build the feature axis from the",
3146
+ " union of bullets across each profile's Offering section (and, if",
3147
+ " present, a project-maintained feature taxonomy under",
3148
+ " `<BUSINESS_MODELS_ROOT>`). Do **not** invent features not",
3149
+ " represented in at least one profile or the project's taxonomy.",
3150
+ "",
3151
+ "5. **Write the analysis document** to",
3152
+ " `<ANALYSIS_DIR>/<SEGMENT_SLUG>.md` using the template below. Use",
3153
+ " `_Not available in profiles._` in any cell a profile does not",
3154
+ " document. Never fabricate a `Yes/No` from a missing field.",
3155
+ "",
3156
+ " ```markdown",
3157
+ " ---",
3158
+ ' title: "<segment name> Competitive Analysis"',
3159
+ " slug: <SEGMENT_SLUG>",
3160
+ " segment: <segment name>",
3161
+ " date: YYYY-MM-DD",
3162
+ " status: draft",
3163
+ " profiles:",
3164
+ " - <relative path to profile 1>",
3165
+ " - <relative path to profile 2>",
3166
+ " ---",
3167
+ "",
3168
+ " # <segment name> Competitive Analysis",
3169
+ "",
3170
+ " ## Market Overview",
3171
+ " <landscape summary \u2014 total players, market structure, trends",
3172
+ " visible from the profiles>",
3173
+ "",
3174
+ " ## Feature Comparison Matrix",
3175
+ " | Feature | <Company A> | <Company B> | <Company C> |",
3176
+ " |---------|-------------|-------------|-------------|",
3177
+ " | <feature> | Yes / No / Partial | ... | ... |",
3178
+ "",
3179
+ " ## Pricing Comparison",
3180
+ " <pricing tiers across profiles, where disclosed>",
3181
+ "",
3182
+ " ## Positioning Map",
3183
+ " <where each player sits \u2014 by price vs. feature depth, or by",
3184
+ " segment focus. Textual description; link to an external diagram",
3185
+ " if the consuming project maintains one.>",
3186
+ "",
3187
+ " ## Strengths & Vulnerabilities",
3188
+ " | Company | Key Strength | Key Vulnerability |",
3189
+ " |---------|--------------|-------------------|",
3190
+ " | <company> | <strength> | <vulnerability> |",
3191
+ "",
3192
+ " ## Strategic Implications",
3193
+ " <which features matter most, where the consuming project can",
3194
+ " differentiate, what pricing the landscape supports, and which",
3195
+ " competitive threats to watch>",
3196
+ "",
3197
+ " ## Recommended Actions",
3198
+ " | Action | Priority | Rationale |",
3199
+ " |--------|----------|-----------|",
3200
+ " | <action> | High / Medium / Low | <why> |",
3201
+ "",
3202
+ " ## Sources",
3203
+ " - <relative path to profile 1>",
3204
+ " - <relative path to profile 2>",
3205
+ "",
3206
+ " ## Revision History",
3207
+ " | Date | Changes |",
3208
+ " |------|---------|",
3209
+ " | YYYY-MM-DD | Initial analysis |",
3210
+ " ```",
3211
+ "",
3212
+ "6. **Do not edit the underlying profiles.** `company:analyze` is a",
3213
+ " synthesis phase \u2014 profiles are **inputs only**. Any cross-link",
3214
+ " from a profile to the analysis document should be added by a",
3215
+ " follow-up `company:match` pass, not by this phase.",
3216
+ "",
3217
+ "7. **Commit and push.** Close the analyze issue with a short",
3218
+ " comment naming the analysis document path.",
3219
+ "",
3220
+ "---",
3221
+ "",
2729
3222
  "## Output Boundaries",
2730
3223
  "",
2731
3224
  "This agent writes **only** to:",
2732
3225
  "",
2733
3226
  "- `<NOTES_DIR>/` \u2014 research-notes files (Phase 1)",
2734
- "- `<PROFILES_DIR>/` \u2014 company profiles (Phase 2, updated in Phase 3)",
3227
+ "- `<PROFILES_DIR>/` \u2014 company profiles (Phase 2, updated in Phases",
3228
+ " 3, 4, and 5)",
3229
+ "- `<ANALYSIS_DIR>/` \u2014 segment-level competitive-analysis documents",
3230
+ " (Phase 6)",
2735
3231
  "",
2736
3232
  "In Phase 3, this agent also **creates `people:research` and",
2737
3233
  "`software:research` issues** for people and software products",
@@ -2740,18 +3236,33 @@ var companyProfileAnalystSubAgent = {
2740
3236
  "`people-profile-analyst` and `software-profile-analyst` agents, which",
2741
3237
  "pick up the issues this pipeline creates.",
2742
3238
  "",
2743
- "The pipeline produces **company profiles and notes only**. Deeper",
2744
- "research on people or products is delegated to downstream research",
2745
- "pipelines via `people:research` and `software:research` issues \u2014 this",
2746
- "agent never writes person profiles, software profiles, product",
2747
- "evaluations, or comparative analyses itself. Keep this boundary clean",
2748
- "so the company-profile pipeline stays generic.",
3239
+ "In Phase 4, this agent appends a single bidirectional cross-link",
3240
+ "line to matched business-model documents under",
3241
+ "`<BUSINESS_MODELS_ROOT>`. It never rewrites the canvas body, adds",
3242
+ "new canvas sections, or otherwise mutates business-model content \u2014",
3243
+ "that remains the responsibility of the `business-models` bundle.",
3244
+ "",
3245
+ "In Phase 5, this agent only updates an existing profile in place \u2014",
3246
+ "it never forks a new profile under a different slug.",
3247
+ "",
3248
+ "In Phase 6, this agent only writes a new segment-level analysis",
3249
+ "document. It never edits the underlying company profiles.",
3250
+ "",
3251
+ "The pipeline produces **company profiles, notes, and segment",
3252
+ "analyses**. Deeper research on people or products is delegated to",
3253
+ "downstream research pipelines via `people:research` and",
3254
+ "`software:research` issues \u2014 this agent never writes person",
3255
+ "profiles, software profiles, product evaluations, or",
3256
+ "requirement-style documents itself. Keep this boundary clean so",
3257
+ "the company-profile pipeline stays generic.",
2749
3258
  "",
2750
3259
  "---",
2751
3260
  "",
2752
3261
  "## Rules",
2753
3262
  "",
2754
3263
  "- **One company per session.** Never profile two companies back-to-back.",
3264
+ " `company:analyze` is the single exception \u2014 it synthesizes one",
3265
+ " segment per session, not one company.",
2755
3266
  "- **Persist before closing.** Every phase must write its output file",
2756
3267
  " before closing its issue.",
2757
3268
  "- **Cite everything.** Profile claims without source citations do not",
@@ -2772,10 +3283,24 @@ var companyProfileAnalystSubAgent = {
2772
3283
  " and products that are genuinely relevant to the framing of this",
2773
3284
  " profile. Do not open issues for every name or product mentioned in",
2774
3285
  " passing.",
2775
- "- **Produce profiles, not requirement or evaluation documents.** Do",
3286
+ "- **Produce profiles and analyses, not requirement documents.** Do",
2776
3287
  " not open `type:requirement` or formal evaluation issues from this",
2777
3288
  " pipeline. Follow-up research is scoped through `people:research`",
2778
- " and `software:research` only."
3289
+ " and `software:research` only.",
3290
+ "- **Match is read-mostly.** Phase 4 edits the target profile and",
3291
+ " appends one line to each matched business-model document. It never",
3292
+ " rewrites canvas bodies, never runs web searches, and never edits",
3293
+ " other profiles.",
3294
+ "- **Refresh respects the cadence.** Phase 5 exits early when the",
3295
+ " profile is younger than the staleness threshold and `force: true`",
3296
+ " is not set. It always appends a revision-history row so the next",
3297
+ " scheduled run knows the profile was reviewed.",
3298
+ "- **Analyze blocks on open drafts.** Phase 6 refuses to publish a",
3299
+ " partial landscape. If any `company:draft` issues for the target",
3300
+ " segment are still open, the analyze issue is marked",
3301
+ " `status:blocked` with `Depends on:` lines pointing at each draft.",
3302
+ "- **Refresh, don't fork.** When a profile exists and is past its",
3303
+ " cadence, update in place rather than creating a new slug."
2779
3304
  ].join("\n")
2780
3305
  };
2781
3306
  var profileCompanySkill = {
@@ -2820,8 +3345,8 @@ var profileCompanySkill = {
2820
3345
  " company name, selected type, framing, and any overrides.",
2821
3346
  "2. Execute Phase 1 (Research) of the company-profile-analyst agent.",
2822
3347
  "3. Phase 1 creates the `company:draft` issue. Phase 2 may create a",
2823
- " `company:followup` issue. Each downstream issue declares its",
2824
- " `Depends on:` predecessor.",
3348
+ " `company:followup` issue **and** a `company:match` issue. Each",
3349
+ " downstream issue declares its `Depends on:` predecessor.",
2825
3350
  "",
2826
3351
  "## Output",
2827
3352
  "",
@@ -2831,38 +3356,265 @@ var profileCompanySkill = {
2831
3356
  " (handed off to the `people-profile` bundle)",
2832
3357
  "- `software:research` issues for software products surfaced in the",
2833
3358
  " profile (handed off to the `software-profile` bundle)",
3359
+ "- A `company:match` issue so the profile is enriched against the",
3360
+ " project's business-model canvases",
2834
3361
  "- This pipeline produces **company profiles only** \u2014 it does not",
2835
3362
  " write person profiles, software profiles, product evaluations, or",
2836
- " comparative analyses itself."
3363
+ " comparative analyses itself. Segment-level analysis is produced by",
3364
+ " the separate `/analyze-segment` skill."
3365
+ ].join("\n")
3366
+ };
3367
+ var matchCompanySkill = {
3368
+ name: "match-company",
3369
+ description: "Kick off a company-profile match (enrichment) cycle. Creates a company:match issue for an existing profile and dispatches Phase 4 (Match) in the company-profile-analyst agent. Match enriches the profile with business-model, segment, and size context from existing docs \u2014 no web searches.",
3370
+ disableModelInvocation: true,
3371
+ userInvocable: true,
3372
+ context: "fork",
3373
+ agent: "company-profile-analyst",
3374
+ platforms: { cursor: { exclude: true } },
3375
+ instructions: [
3376
+ "# Match Company",
3377
+ "",
3378
+ "Enrich an existing company profile against the project's",
3379
+ "business-model canvases, industry segments, and size descriptions.",
3380
+ "Creates a `company:match` issue for the target profile and",
3381
+ "dispatches Phase 4 (Match) in the company-profile-analyst agent.",
3382
+ "",
3383
+ "**This is enrichment of a known profile, not entity",
3384
+ "deduplication** \u2014 the profile already exists and must be passed by",
3385
+ "path.",
3386
+ "",
3387
+ "## Usage",
3388
+ "",
3389
+ "/match-company <path-to-profile>",
3390
+ "",
3391
+ "Optional extensions in the issue body:",
3392
+ "- `business_models_root: <path>` \u2014 override `<BUSINESS_MODELS_ROOT>`",
3393
+ " if the project's business-model canvases do not live at the",
3394
+ " default location",
3395
+ "- `only: <comma-separated list of segment slugs>` \u2014 match only",
3396
+ " against the listed segments (useful when a new canvas triggers a",
3397
+ " re-match)",
3398
+ "",
3399
+ "## Default Paths",
3400
+ "",
3401
+ "If the project has no override in `docs/src/content/docs/project-context.md` or",
3402
+ "`agentConfig.rules`, the agent reads canvases from:",
3403
+ "",
3404
+ "- `docs/src/content/docs/industry-research/`",
3405
+ "",
3406
+ "## Budget",
3407
+ "",
3408
+ "- **No web searches.** Phase 4 reads existing docs and edits the",
3409
+ " profile + one cross-link per matched canvas. Anything that cannot",
3410
+ " be decided from existing docs is flagged in the profile's",
3411
+ " `## Risks / Open Questions` section.",
3412
+ "",
3413
+ "## Steps",
3414
+ "",
3415
+ "1. Create a `company:match` issue with `type:company-profile`,",
3416
+ " `priority:medium`, and `status:ready`. Body must include the",
3417
+ " profile path and any overrides.",
3418
+ "2. Execute Phase 4 (Match) of the company-profile-analyst agent.",
3419
+ "",
3420
+ "## Output",
3421
+ "",
3422
+ "- The profile gains a `## Business Model Context` section",
3423
+ "- Each matched business-model canvas gains a single cross-link line",
3424
+ " in its Traceability / Sources section",
3425
+ "- The profile's `referencedIn` frontmatter is updated with matched",
3426
+ " paths",
3427
+ "- A revision-history row summarizing the matched segments"
3428
+ ].join("\n")
3429
+ };
3430
+ var refreshCompanySkill = {
3431
+ name: "refresh-company",
3432
+ description: "Kick off a company-profile refresh cycle. Creates a company:refresh issue for an existing profile and dispatches Phase 5 (Refresh) in the company-profile-analyst agent. Refresh re-verifies the profile with 4\u20136 targeted web searches and updates it in place. Respects a configurable staleness threshold so profiles younger than the threshold exit early.",
3433
+ disableModelInvocation: true,
3434
+ userInvocable: true,
3435
+ context: "fork",
3436
+ agent: "company-profile-analyst",
3437
+ platforms: { cursor: { exclude: true } },
3438
+ instructions: [
3439
+ "# Refresh Company",
3440
+ "",
3441
+ "Re-verify an existing company profile with a small number of",
3442
+ "targeted web searches and update the profile in place. Creates a",
3443
+ "`company:refresh` issue for the target profile and dispatches",
3444
+ "Phase 5 (Refresh) in the company-profile-analyst agent.",
3445
+ "",
3446
+ "Refresh respects a configurable **staleness threshold** so scheduled",
3447
+ "refreshes do not burn search budget on profiles that are still",
3448
+ "fresh.",
3449
+ "",
3450
+ "## Usage",
3451
+ "",
3452
+ "/refresh-company <path-to-profile>",
3453
+ "",
3454
+ "Optional extensions in the issue body:",
3455
+ "- `refresh_days: <N>` \u2014 override the default 180-day staleness",
3456
+ " threshold for this run",
3457
+ "- `force: true` \u2014 refresh even if the profile is younger than the",
3458
+ " threshold",
3459
+ "- `retype: <taxonomy value>` \u2014 explicitly allow Phase 5 to change",
3460
+ " the profile's `company_type` when the refresh finds material",
3461
+ " evidence of a type change",
3462
+ "",
3463
+ "## Staleness Threshold",
3464
+ "",
3465
+ "Order of precedence (first match wins):",
3466
+ "",
3467
+ "1. `refresh_days` in the issue body (per-run override)",
3468
+ "2. `refresh_days` frontmatter on the profile itself",
3469
+ "3. Project-wide default declared under a `## Refresh Cadence`",
3470
+ " section in `docs/src/content/docs/project-context.md`",
3471
+ "4. Built-in default: **180 days**",
3472
+ "",
3473
+ "If the profile's `date` frontmatter plus the resolved threshold is",
3474
+ "in the future (i.e. the profile is still fresh) and `force: true`",
3475
+ "is not set, Phase 5 closes the refresh issue with a short comment",
3476
+ "and stops without running any searches.",
3477
+ "",
3478
+ "## Budget",
3479
+ "",
3480
+ "- **4\u20136 targeted web searches** focused on recent news, product",
3481
+ " launches, funding/acquisitions, leadership changes, and",
3482
+ " partnerships / certifications.",
3483
+ "",
3484
+ "## Steps",
3485
+ "",
3486
+ "1. Create a `company:refresh` issue with `type:company-profile`,",
3487
+ " `priority:medium`, and `status:ready`. Body must include the",
3488
+ " profile path and any overrides.",
3489
+ "2. Execute Phase 5 (Refresh) of the company-profile-analyst agent.",
3490
+ "",
3491
+ "## Output",
3492
+ "",
3493
+ "- The profile is updated in place (sections edited, `date` bumped,",
3494
+ " slug preserved)",
3495
+ "- A revision-history row summarizing the delta \u2014 or",
3496
+ " `Refreshed: no material change` when the search budget found",
3497
+ " nothing material",
3498
+ "- Reference entries updated if the company's relevance to a research",
3499
+ " area shifted"
3500
+ ].join("\n")
3501
+ };
3502
+ var analyzeSegmentSkill = {
3503
+ name: "analyze-segment",
3504
+ description: "Kick off a segment-level competitive-analysis cycle. Creates a company:analyze issue for the target segment and dispatches Phase 6 (Analyze) in the company-profile-analyst agent. Analyze synthesizes every profile in the segment into one competitive-analysis document. Blocks on open company:draft issues for the segment.",
3505
+ disableModelInvocation: true,
3506
+ userInvocable: true,
3507
+ context: "fork",
3508
+ agent: "company-profile-analyst",
3509
+ platforms: { cursor: { exclude: true } },
3510
+ instructions: [
3511
+ "# Analyze Segment",
3512
+ "",
3513
+ "Produce a segment-level competitive analysis by synthesizing every",
3514
+ "company profile in the target segment into one markdown document.",
3515
+ "Creates a `company:analyze` issue and dispatches Phase 6 (Analyze)",
3516
+ "in the company-profile-analyst agent.",
3517
+ "",
3518
+ "**This is pure synthesis** \u2014 no web searches, no new profile work.",
3519
+ "All inputs must already exist under the profiles directory before",
3520
+ "you run this skill.",
3521
+ "",
3522
+ "## Usage",
3523
+ "",
3524
+ "/analyze-segment <segment-slug>",
3525
+ "",
3526
+ "Optional extensions in the issue body:",
3527
+ "- `profiles: <list of paths>` \u2014 explicit list of profile files to",
3528
+ " include; otherwise the agent discovers profiles by scanning for",
3529
+ " the segment slug in each profile's `referencedIn` frontmatter",
3530
+ "- `analysis_dir: <path>` \u2014 override `<ANALYSIS_DIR>` if the project",
3531
+ " stores analyses somewhere other than the default",
3532
+ "",
3533
+ "## Default Paths",
3534
+ "",
3535
+ "If the project has no override in `docs/src/content/docs/project-context.md` or",
3536
+ "`agentConfig.rules`, outputs land under:",
3537
+ "",
3538
+ "- `docs/companies/analysis/<segment-slug>.md`",
3539
+ "",
3540
+ "## Blocking Rule",
3541
+ "",
3542
+ "Phase 6 refuses to publish a partial landscape. If any",
3543
+ "`company:draft` issues whose title or body mention",
3544
+ "`<SEGMENT_SLUG>` are still open, the analyze issue is marked",
3545
+ "`status:blocked` with `Depends on: #<N>` lines pointing at each",
3546
+ "open draft, `status:in-progress` is removed, and Phase 6 exits",
3547
+ "without writing the analysis. The issue becomes eligible again",
3548
+ "once the drafts close.",
3549
+ "",
3550
+ "## Budget",
3551
+ "",
3552
+ "- **No web searches.** Pure synthesis from existing profiles (and",
3553
+ " optionally the project's own product docs).",
3554
+ "",
3555
+ "## Steps",
3556
+ "",
3557
+ "1. Create a `company:analyze` issue with `type:company-profile`,",
3558
+ " `priority:medium`, and `status:ready`. Body must include the",
3559
+ " segment slug and any overrides.",
3560
+ "2. Execute Phase 6 (Analyze) of the company-profile-analyst agent.",
3561
+ "",
3562
+ "## Output",
3563
+ "",
3564
+ "- One markdown document at `<ANALYSIS_DIR>/<SEGMENT_SLUG>.md`",
3565
+ " combining feature-comparison matrix, pricing comparison,",
3566
+ " positioning map, strengths & vulnerabilities, strategic",
3567
+ " implications, and recommended actions \u2014 every claim sourced back",
3568
+ " to the underlying profiles",
3569
+ "- This skill does **not** edit the underlying profiles. Cross-",
3570
+ " linking a profile back to the analysis is the job of a",
3571
+ " subsequent `company:match` pass."
2837
3572
  ].join("\n")
2838
3573
  };
2839
3574
  var companyProfileBundle = {
2840
3575
  name: "company-profile",
2841
- description: "Company research and profiling pipeline: research, draft profile, followup. Enabled by default; domain-neutral; filesystem-durable between phases. Phase 3 (Followup) hands surfaced people and software products off to the `people-profile` and `software-profile` bundles via `people:research` and `software:research` issues.",
3576
+ description: "Company research and profiling pipeline: research, draft profile, followup, match, refresh, analyze. Enabled by default; domain-neutral; filesystem-durable between phases. Phase 3 (Followup) hands surfaced people and software products off to the `people-profile` and `software-profile` bundles via `people:research` and `software:research` issues. Phase 4 (Match) enriches profiles against business-model canvases. Phase 5 (Refresh) re-verifies profiles on a configurable staleness cadence. Phase 6 (Analyze) synthesizes profiles in a segment into a competitive-analysis document.",
2842
3577
  appliesWhen: () => true,
2843
3578
  rules: [
2844
3579
  {
2845
3580
  name: "company-profile-workflow",
2846
- description: "Describes the 3-phase company-profile pipeline, the company:* label taxonomy, and the boundary against downstream research agents.",
3581
+ description: "Describes the 6-phase company-profile pipeline, the company:* label taxonomy, and the boundary against downstream research agents.",
2847
3582
  scope: AGENT_RULE_SCOPE.ALWAYS,
2848
3583
  content: [
2849
3584
  "# Company Profile Workflow",
2850
3585
  "",
2851
3586
  "Use `/profile-company <company-name>` to kick off a company",
2852
- "research and profiling pipeline. The pipeline runs in up to 3",
2853
- "phases \u2014 research, draft, followup \u2014 each tracked by its own",
2854
- "GitHub issue labeled `company:research`, `company:draft`, or",
2855
- "`company:followup`. All issues carry `type:company-profile`.",
2856
- "",
2857
- "The pipeline produces **company profiles only**. Deeper research",
2858
- "on notable people and software products surfaced while profiling",
2859
- "is delegated to the `people-profile` and `software-profile`",
2860
- "bundles via `people:research` and `software:research` issues",
2861
- "opened during Phase 3 (Followup).",
3587
+ "research and profiling pipeline. The pipeline runs in up to 6",
3588
+ "phases \u2014 research, draft, followup, match, refresh, analyze \u2014",
3589
+ "each tracked by its own GitHub issue labeled `company:research`,",
3590
+ "`company:draft`, `company:followup`, `company:match`,",
3591
+ "`company:refresh`, or `company:analyze`. All issues carry",
3592
+ "`type:company-profile`.",
3593
+ "",
3594
+ "Three additional user-invocable skills drive the maintenance and",
3595
+ "synthesis phases independently:",
3596
+ "",
3597
+ "- `/match-company <path-to-profile>` \u2014 enrich an existing",
3598
+ " profile with business-model and segment context from the",
3599
+ " project's business-model canvases (Phase 4, no web searches).",
3600
+ "- `/refresh-company <path-to-profile>` \u2014 re-verify an existing",
3601
+ " profile with 4\u20136 targeted web searches and update it in place",
3602
+ " (Phase 5, respects a configurable staleness threshold).",
3603
+ "- `/analyze-segment <segment-slug>` \u2014 synthesize every profile",
3604
+ " in a segment into one competitive-analysis document (Phase 6,",
3605
+ " no web searches; blocks on open `company:draft` issues for",
3606
+ " the segment).",
3607
+ "",
3608
+ "The pipeline produces **company profiles, notes, and segment",
3609
+ "analyses**. Deeper research on notable people and software",
3610
+ "products surfaced while profiling is delegated to the",
3611
+ "`people-profile` and `software-profile` bundles via",
3612
+ "`people:research` and `software:research` issues opened during",
3613
+ "Phase 3 (Followup).",
2862
3614
  "",
2863
3615
  "See the `company-profile-analyst` agent definition for full",
2864
- "workflow details, default paths, the company-type taxonomy, and",
2865
- "phase-by-phase instructions."
3616
+ "workflow details, default paths, the company-type taxonomy, the",
3617
+ "staleness-threshold rules, and phase-by-phase instructions."
2866
3618
  ].join("\n"),
2867
3619
  platforms: {
2868
3620
  cursor: { exclude: true }
@@ -2870,7 +3622,12 @@ var companyProfileBundle = {
2870
3622
  tags: ["workflow"]
2871
3623
  }
2872
3624
  ],
2873
- skills: [profileCompanySkill],
3625
+ skills: [
3626
+ profileCompanySkill,
3627
+ matchCompanySkill,
3628
+ refreshCompanySkill,
3629
+ analyzeSegmentSkill
3630
+ ],
2874
3631
  subAgents: [companyProfileAnalystSubAgent],
2875
3632
  labels: [
2876
3633
  {
@@ -2892,6 +3649,21 @@ var companyProfileBundle = {
2892
3649
  name: "company:followup",
2893
3650
  color: "D4C5F9",
2894
3651
  description: "Phase 3: enqueue follow-up research issues for people and products surfaced in the profile"
3652
+ },
3653
+ {
3654
+ name: "company:match",
3655
+ color: "FBCA04",
3656
+ description: "Phase 4: enrich a company profile with business-model, segment, and size context from existing docs"
3657
+ },
3658
+ {
3659
+ name: "company:refresh",
3660
+ color: "F9D0C4",
3661
+ description: "Phase 5: re-verify an existing company profile with targeted web searches and update it in place"
3662
+ },
3663
+ {
3664
+ name: "company:analyze",
3665
+ color: "5319E7",
3666
+ description: "Phase 6: synthesize every company profile in a segment into a competitive-analysis document"
2895
3667
  }
2896
3668
  ]
2897
3669
  };
@@ -14583,6 +15355,141 @@ var vitestBundle = {
14583
15355
  }
14584
15356
  };
14585
15357
 
15358
+ // src/agent/bundles/features.ts
15359
+ var SOURCE_TIER_HEADINGS = [
15360
+ { key: "t1", heading: "### T1 \u2014 Primary Living" },
15361
+ { key: "t2", heading: "### T2 \u2014 Primary Snapshot" },
15362
+ { key: "t3", heading: "### T3 \u2014 Secondary" },
15363
+ { key: "t4", heading: "### T4 \u2014 Self-Reported / Marketing" }
15364
+ ];
15365
+ function renderSourceTierExamples(content, examples) {
15366
+ if (!examples) {
15367
+ return content;
15368
+ }
15369
+ const tierEntries = SOURCE_TIER_HEADINGS.flatMap(({ key, heading }) => {
15370
+ const list = examples[key];
15371
+ if (!list || list.length === 0) {
15372
+ return [];
15373
+ }
15374
+ return [{ heading, list }];
15375
+ });
15376
+ if (tierEntries.length === 0) {
15377
+ return content;
15378
+ }
15379
+ let updated = content;
15380
+ for (const { heading, list } of tierEntries) {
15381
+ const injection = [
15382
+ "",
15383
+ "**Project-specific examples:**",
15384
+ "",
15385
+ ...list.map((ex) => `- ${ex}`)
15386
+ ].join("\n");
15387
+ updated = insertAfterHeadingBlock(updated, heading, injection);
15388
+ }
15389
+ return updated;
15390
+ }
15391
+ function renderCustomDocSections(bundle, sections) {
15392
+ const matching = sections.filter((s) => s.bundleName === bundle.name);
15393
+ if (matching.length === 0) {
15394
+ return bundle;
15395
+ }
15396
+ let rules = bundle.rules;
15397
+ const currentAnchorByTarget = /* @__PURE__ */ new Map();
15398
+ for (const section of matching) {
15399
+ const injection = renderCustomDocSectionBlock(section);
15400
+ const seededAnchor = currentAnchorByTarget.get(section.afterSection);
15401
+ let targetIndex = -1;
15402
+ let targetHeading = "";
15403
+ for (let i = 0; i < rules.length; i += 1) {
15404
+ if (seededAnchor && rules[i].content.includes(seededAnchor)) {
15405
+ targetIndex = i;
15406
+ targetHeading = seededAnchor;
15407
+ break;
15408
+ }
15409
+ if (!seededAnchor) {
15410
+ const match = findHeadingForTitle(
15411
+ rules[i].content,
15412
+ section.afterSection
15413
+ );
15414
+ if (match) {
15415
+ targetIndex = i;
15416
+ targetHeading = match;
15417
+ break;
15418
+ }
15419
+ }
15420
+ }
15421
+ if (targetIndex === -1) {
15422
+ continue;
15423
+ }
15424
+ const targetRule = rules[targetIndex];
15425
+ const updatedRule = {
15426
+ ...targetRule,
15427
+ content: insertAfterHeadingBlock(
15428
+ targetRule.content,
15429
+ targetHeading,
15430
+ injection
15431
+ )
15432
+ };
15433
+ rules = [
15434
+ ...rules.slice(0, targetIndex),
15435
+ updatedRule,
15436
+ ...rules.slice(targetIndex + 1)
15437
+ ];
15438
+ currentAnchorByTarget.set(
15439
+ section.afterSection,
15440
+ `## ${section.sectionTitle}`
15441
+ );
15442
+ }
15443
+ if (rules === bundle.rules) {
15444
+ return bundle;
15445
+ }
15446
+ return { ...bundle, rules };
15447
+ }
15448
+ function renderCustomDocSectionBlock(section) {
15449
+ const body = section.body.replace(/[\r\n]+$/u, "");
15450
+ return ["", `## ${section.sectionTitle}`, "", body].join("\n");
15451
+ }
15452
+ function findHeadingForTitle(content, title) {
15453
+ const lines = content.split("\n");
15454
+ for (const line of lines) {
15455
+ const match = line.match(/^(#{1,6}) (.+)$/u);
15456
+ if (match && match[2] === title) {
15457
+ return line;
15458
+ }
15459
+ }
15460
+ return void 0;
15461
+ }
15462
+ function insertAfterHeadingBlock(content, heading, injection) {
15463
+ const lines = content.split("\n");
15464
+ const headingIndex = lines.findIndex((line) => line === heading);
15465
+ if (headingIndex === -1) {
15466
+ return content;
15467
+ }
15468
+ const headingLevel = countLeadingHashes(heading);
15469
+ let endIndex = lines.length - 1;
15470
+ for (let i = headingIndex + 1; i < lines.length; i += 1) {
15471
+ const nextLevel = countLeadingHashes(lines[i]);
15472
+ if (nextLevel > 0 && nextLevel <= headingLevel) {
15473
+ endIndex = i - 1;
15474
+ break;
15475
+ }
15476
+ }
15477
+ while (endIndex > headingIndex && lines[endIndex] === "") {
15478
+ endIndex -= 1;
15479
+ }
15480
+ const injectionLines = injection.split("\n");
15481
+ const next = [
15482
+ ...lines.slice(0, endIndex + 1),
15483
+ ...injectionLines,
15484
+ ...lines.slice(endIndex + 1)
15485
+ ];
15486
+ return next.join("\n");
15487
+ }
15488
+ function countLeadingHashes(line) {
15489
+ const match = line.match(/^(#{1,6}) /u);
15490
+ return match ? match[1].length : 0;
15491
+ }
15492
+
14586
15493
  // src/agent/bundles/focus.ts
14587
15494
  var DEFAULT_FOCUS_FILE_PATH = ".claude/focus.json";
14588
15495
  var DEFAULT_SCHEMA_VERSION = 1;
@@ -15553,6 +16460,19 @@ var AgentConfig = class _AgentConfig extends import_projen8.Component {
15553
16460
  const isAgentConfig = (c) => c instanceof _AgentConfig;
15554
16461
  return project.components.find(isAgentConfig);
15555
16462
  }
16463
+ /**
16464
+ * Returns `true` when at least one tier array on the supplied
16465
+ * `SourceTierExamples` is non-empty, signalling that the consuming
16466
+ * repo has opted into rendering the base bundle's
16467
+ * `source-quality-verification` rule. Returns `false` for
16468
+ * `undefined`, `{}`, or a fully-empty `{ t1: [], t2: [], t3: [], t4: [] }`.
16469
+ */
16470
+ static hasActiveTierExamples(examples) {
16471
+ if (!examples) {
16472
+ return false;
16473
+ }
16474
+ return (examples.t1?.length ?? 0) > 0 || (examples.t2?.length ?? 0) > 0 || (examples.t3?.length ?? 0) > 0 || (examples.t4?.length ?? 0) > 0;
16475
+ }
15556
16476
  /**
15557
16477
  * Merges default Claude permissions with bundle and user-supplied settings.
15558
16478
  *
@@ -15772,6 +16692,24 @@ ${section}`
15772
16692
  }
15773
16693
  }
15774
16694
  }
16695
+ const tierExamples = this.options.features?.sourceTierExamples;
16696
+ if (_AgentConfig.hasActiveTierExamples(tierExamples)) {
16697
+ const sourceRule = ruleMap.get("source-quality-verification");
16698
+ if (sourceRule) {
16699
+ const updated = renderSourceTierExamples(
16700
+ sourceRule.content,
16701
+ tierExamples
16702
+ );
16703
+ if (updated !== sourceRule.content) {
16704
+ ruleMap.set("source-quality-verification", {
16705
+ ...sourceRule,
16706
+ content: updated
16707
+ });
16708
+ }
16709
+ }
16710
+ } else {
16711
+ ruleMap.delete("source-quality-verification");
16712
+ }
15775
16713
  return [...ruleMap.values()].sort((a, b) => {
15776
16714
  if (a.name === "project-overview") return -1;
15777
16715
  if (b.name === "project-overview") return 1;
@@ -15784,19 +16722,24 @@ ${section}`
15784
16722
  /**
15785
16723
  * Return a bundle's rules with `filePatterns` narrowed to the projects
15786
16724
  * that actually matched the bundle's detection predicate (when the bundle
15787
- * provides `findApplicableProjects`). Rules with `ALWAYS` scope and rules
15788
- * on bundles that don't implement the hook are returned unchanged.
16725
+ * provides `findApplicableProjects`) and any project-specified
16726
+ * `features.customDocSections` entries injected into the matching rule
16727
+ * content. Rules with `ALWAYS` scope and rules on bundles that don't
16728
+ * implement the hook are returned with only the custom-section
16729
+ * transformation (if any) applied.
15789
16730
  */
15790
16731
  bundleRulesFor(bundle) {
15791
- if (!bundle.findApplicableProjects) {
15792
- return bundle.rules;
16732
+ const customSections = this.options.features?.customDocSections ?? [];
16733
+ const withCustomSections = customSections.length > 0 ? renderCustomDocSections(bundle, customSections) : bundle;
16734
+ if (!withCustomSections.findApplicableProjects) {
16735
+ return withCustomSections.rules;
15793
16736
  }
15794
- const detected = bundle.findApplicableProjects(this.project);
16737
+ const detected = withCustomSections.findApplicableProjects(this.project);
15795
16738
  if (detected.length === 0) {
15796
- return bundle.rules;
16739
+ return withCustomSections.rules;
15797
16740
  }
15798
16741
  const scoped = scopeFilePatternsToProjects(this.project, detected);
15799
- return bundle.rules.map(
16742
+ return withCustomSections.rules.map(
15800
16743
  (rule) => rule.scope === AGENT_RULE_SCOPE.FILE_PATTERN ? { ...rule, filePatterns: scoped } : rule
15801
16744
  );
15802
16745
  }
@@ -18583,9 +19526,12 @@ var TypeScriptConfig = class extends import_projen22.Component {
18583
19526
  pnpmBundle,
18584
19527
  prReviewBundle,
18585
19528
  projenBundle,
19529
+ renderCustomDocSectionBlock,
19530
+ renderCustomDocSections,
18586
19531
  renderFocusSection,
18587
19532
  renderMeetingTypesSection,
18588
19533
  renderPriorityRulesSection,
19534
+ renderSourceTierExamples,
18589
19535
  requirementsAnalystBundle,
18590
19536
  requirementsReviewerBundle,
18591
19537
  requirementsWriterBundle,