sourcebook 0.3.0 → 0.5.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.
@@ -1,4 +1,4 @@
1
- import { groupByCategory, hasCommands, categorizeFindings, enforceTokenBudget, } from "./shared.js";
1
+ import { groupByCategory, hasCommands, categorizeFindings, enforceTokenBudget, buildQuickReference, getModePriorities, } from "./shared.js";
2
2
  /**
3
3
  * Generate a CLAUDE.md file from scan results.
4
4
  *
@@ -10,6 +10,7 @@ import { groupByCategory, hasCommands, categorizeFindings, enforceTokenBudget, }
10
10
  */
11
11
  export function generateClaude(scan, budget) {
12
12
  const { critical, important, supplementary } = categorizeFindings(scan.findings);
13
+ const priorities = getModePriorities(scan.repoMode);
13
14
  const sections = [];
14
15
  // ============================================
15
16
  // BEGINNING: Most critical info goes here
@@ -23,7 +24,12 @@ export function generateClaude(scan, budget) {
23
24
  "",
24
25
  ].join("\n");
25
26
  sections.push({ key: "header", content: header, priority: 100 });
26
- // Commands first -- most immediately actionable
27
+ // Quick Reference the "30-second handoff" (highest priority in app mode)
28
+ const quickRef = buildQuickReference(scan.findings);
29
+ if (quickRef) {
30
+ sections.push({ key: "quick_reference", content: quickRef, priority: priorities.quick_reference });
31
+ }
32
+ // Commands — most immediately actionable
27
33
  if (hasCommands(scan.commands)) {
28
34
  const lines = ["## Commands", ""];
29
35
  if (scan.commands.dev)
@@ -49,7 +55,7 @@ export function generateClaude(scan, budget) {
49
55
  lines.push(`- **${finding.category}:** ${finding.description}`);
50
56
  }
51
57
  lines.push("");
52
- sections.push({ key: "critical", content: lines.join("\n"), priority: 90 });
58
+ sections.push({ key: "critical", content: lines.join("\n"), priority: priorities.critical });
53
59
  }
54
60
  // ============================================
55
61
  // MIDDLE: Less critical but useful info
@@ -58,7 +64,7 @@ export function generateClaude(scan, budget) {
58
64
  // Stack (brief)
59
65
  if (scan.frameworks.length > 0) {
60
66
  const content = ["## Stack", "", scan.frameworks.join(", "), ""].join("\n");
61
- sections.push({ key: "stack", content, priority: 50 });
67
+ sections.push({ key: "stack", content, priority: priorities.stack });
62
68
  }
63
69
  // Key directories (only non-obvious ones)
64
70
  if (Object.keys(scan.structure.directories).length > 0) {
@@ -69,7 +75,7 @@ export function generateClaude(scan, budget) {
69
75
  lines.push(`- \`${dir}/\` — ${purpose}`);
70
76
  }
71
77
  lines.push("");
72
- sections.push({ key: "structure", content: lines.join("\n"), priority: 40 });
78
+ sections.push({ key: "structure", content: lines.join("\n"), priority: priorities.structure });
73
79
  }
74
80
  }
75
81
  // Core modules (from PageRank)
@@ -80,7 +86,7 @@ export function generateClaude(scan, budget) {
80
86
  lines.push(`- \`${file}\``);
81
87
  }
82
88
  lines.push("");
83
- sections.push({ key: "core_modules", content: lines.join("\n"), priority: 60 });
89
+ sections.push({ key: "core_modules", content: lines.join("\n"), priority: priorities.core_modules });
84
90
  }
85
91
  // Important findings (high confidence, non-critical)
86
92
  if (important.length > 0) {
@@ -98,7 +104,7 @@ export function generateClaude(scan, budget) {
98
104
  }
99
105
  }
100
106
  lines.push("");
101
- sections.push({ key: "conventions", content: lines.join("\n"), priority: 30 });
107
+ sections.push({ key: "conventions", content: lines.join("\n"), priority: priorities.conventions });
102
108
  }
103
109
  // Supplementary findings (medium confidence)
104
110
  if (supplementary.length > 0) {
@@ -116,7 +122,7 @@ export function generateClaude(scan, budget) {
116
122
  }
117
123
  }
118
124
  lines.push("");
119
- sections.push({ key: "supplementary", content: lines.join("\n"), priority: 20 });
125
+ sections.push({ key: "supplementary", content: lines.join("\n"), priority: priorities.supplementary });
120
126
  }
121
127
  // ============================================
122
128
  // END: Important reminders go here
@@ -27,6 +27,18 @@ export declare function categorizeFindings(findings: Finding[]): {
27
27
  * 6. Supplementary findings (drop first)
28
28
  * 7. Footer/manual section (always keep — end of context = high retention)
29
29
  */
30
+ /**
31
+ * Build a Quick Reference section from dominant pattern findings.
32
+ * This is the "30-second senior engineer handoff" — the single most
33
+ * actionable section in the output.
34
+ */
35
+ export declare function buildQuickReference(findings: Finding[]): string | null;
36
+ /**
37
+ * Get priority adjustments based on repo mode.
38
+ * App repos: boost dominant patterns, demote structural.
39
+ * Library repos: boost structural, demote patterns.
40
+ */
41
+ export declare function getModePriorities(repoMode?: "app" | "library" | "monorepo"): Record<string, number>;
30
42
  export declare function enforceTokenBudget(sections: {
31
43
  key: string;
32
44
  content: string;
@@ -10,6 +10,7 @@ const CRITICAL_CATEGORIES = new Set([
10
10
  "Git history",
11
11
  "Commit conventions",
12
12
  "Anti-patterns",
13
+ "Critical constraints",
13
14
  ]);
14
15
  const CRITICAL_KEYWORDS = [
15
16
  "breaking", "blast radius", "deprecated", "don't", "must",
@@ -62,6 +63,113 @@ export function categorizeFindings(findings) {
62
63
  * 6. Supplementary findings (drop first)
63
64
  * 7. Footer/manual section (always keep — end of context = high retention)
64
65
  */
66
+ /**
67
+ * Build a Quick Reference section from dominant pattern findings.
68
+ * This is the "30-second senior engineer handoff" — the single most
69
+ * actionable section in the output.
70
+ */
71
+ export function buildQuickReference(findings) {
72
+ const patterns = findings.filter((f) => f.category === "Dominant patterns");
73
+ if (patterns.length < 2)
74
+ return null;
75
+ const lines = ["## Quick Reference", ""];
76
+ for (const p of patterns) {
77
+ // Extract a short label from the description
78
+ const desc = p.description;
79
+ let label = "";
80
+ let value = desc;
81
+ if (desc.includes("internationalization") || desc.includes("i18n") || desc.includes("translation")) {
82
+ label = "i18n";
83
+ }
84
+ else if (desc.includes("route") || desc.includes("endpoint") || desc.includes("API")) {
85
+ label = "routing";
86
+ }
87
+ else if (desc.includes("validation") || desc.includes("schema") || desc.includes("Zod") || desc.includes("Pydantic")) {
88
+ label = "validation";
89
+ }
90
+ else if ((desc.includes("auth") || desc.includes("Auth") || desc.includes("session")) && !desc.includes("integration")) {
91
+ label = "auth";
92
+ }
93
+ else if (desc.includes("Test") || desc.includes("test")) {
94
+ label = "testing";
95
+ }
96
+ else if (desc.includes("Tailwind") || desc.includes("styled") || desc.includes("CSS")) {
97
+ label = "styling";
98
+ }
99
+ else if (desc.includes("database") || desc.includes("Database") || desc.includes("Prisma") || desc.includes("ORM")) {
100
+ label = "database";
101
+ }
102
+ else if (desc.includes("fetching") || desc.includes("Query") || desc.includes("SWR")) {
103
+ label = "data fetching";
104
+ }
105
+ else if (desc.includes("Route definitions") || desc.includes("Add new endpoints")) {
106
+ label = "routes";
107
+ }
108
+ else if (desc.includes("integration") || desc.includes("Third-party")) {
109
+ label = "integrations";
110
+ }
111
+ else if (desc.includes("components") || desc.includes("UI")) {
112
+ label = "components";
113
+ }
114
+ else if (desc.includes("Generated") || desc.includes("generated") || desc.includes("DO NOT")) {
115
+ label = "generated";
116
+ }
117
+ else {
118
+ continue; // Skip findings we can't label cleanly
119
+ }
120
+ // Compress the description to a short actionable line
121
+ const short = desc
122
+ .replace(/\. Follow this pattern.*$/, "")
123
+ .replace(/\. This is the project's standard.*$/, "")
124
+ .replace(/\. Each integration has.*$/, "");
125
+ lines.push(`- **${label}:** ${short}`);
126
+ }
127
+ if (lines.length <= 2)
128
+ return null; // Nothing useful
129
+ // Deduplicate by label — keep only first occurrence of each
130
+ const seen = new Set();
131
+ const deduped = [lines[0], lines[1]]; // Keep header
132
+ for (let i = 2; i < lines.length; i++) {
133
+ const labelMatch = lines[i].match(/^\- \*\*(\w[\w\s]*)\:\*\*/);
134
+ if (labelMatch) {
135
+ const lbl = labelMatch[1];
136
+ if (seen.has(lbl))
137
+ continue;
138
+ seen.add(lbl);
139
+ }
140
+ deduped.push(lines[i]);
141
+ }
142
+ deduped.push("");
143
+ return deduped.join("\n");
144
+ }
145
+ /**
146
+ * Get priority adjustments based on repo mode.
147
+ * App repos: boost dominant patterns, demote structural.
148
+ * Library repos: boost structural, demote patterns.
149
+ */
150
+ export function getModePriorities(repoMode) {
151
+ if (repoMode === "library") {
152
+ return {
153
+ quick_reference: 85,
154
+ critical: 92,
155
+ core_modules: 90,
156
+ conventions: 80,
157
+ stack: 50,
158
+ structure: 40,
159
+ supplementary: 25,
160
+ };
161
+ }
162
+ // Default: app mode (boost patterns, Quick Reference highest)
163
+ return {
164
+ quick_reference: 96,
165
+ critical: 92,
166
+ core_modules: 50,
167
+ conventions: 85,
168
+ stack: 45,
169
+ structure: 60,
170
+ supplementary: 20,
171
+ };
172
+ }
65
173
  export function enforceTokenBudget(sections, budget) {
66
174
  // Sort by priority descending (highest priority = keep)
67
175
  const sorted = [...sections].sort((a, b) => b.priority - a.priority);
@@ -51,6 +51,8 @@ export async function scanProject(dir) {
51
51
  ...gitAnalysis.findings,
52
52
  ...graphAnalysis.findings,
53
53
  ];
54
+ // Detect repo mode for prioritization
55
+ const repoMode = detectRepoMode(dir, files, frameworks.map((f) => f.name));
54
56
  return {
55
57
  dir,
56
58
  files,
@@ -60,8 +62,29 @@ export async function scanProject(dir) {
60
62
  structure,
61
63
  findings,
62
64
  rankedFiles: graphAnalysis.rankedFiles,
65
+ repoMode,
63
66
  };
64
67
  }
68
+ function detectRepoMode(dir, files, frameworks) {
69
+ // Monorepo detection
70
+ const hasWorkspaces = files.some((f) => f === "pnpm-workspace.yaml" || f === "lerna.json" || f === "nx.json");
71
+ if (hasWorkspaces)
72
+ return "monorepo";
73
+ // Library detection
74
+ const hasPublishConfig = files.some((f) => f === "setup.py" || f === "pyproject.toml" || f === "setup.cfg");
75
+ const hasSrcLib = files.some((f) => f.startsWith("src/lib/") || f.startsWith("lib/"));
76
+ const hasExportsField = false; // would need to read package.json, but frameworks already detected
77
+ const isLibraryFramework = frameworks.some((f) => ["FastAPI", "Flask", "Django", "Hono", "Express"].includes(f));
78
+ // If it has a setup.py/pyproject.toml AND no app/ or pages/ → library
79
+ const hasAppDirs = files.some((f) => f.startsWith("app/") || f.startsWith("pages/") || f.startsWith("src/app/") || f.startsWith("src/pages/"));
80
+ const hasComponents = files.some((f) => f.includes("/components/"));
81
+ if (hasPublishConfig && !hasAppDirs && !hasComponents)
82
+ return "library";
83
+ if (hasSrcLib && !hasAppDirs && !hasComponents && !isLibraryFramework)
84
+ return "library";
85
+ // Default: app
86
+ return "app";
87
+ }
65
88
  function detectLanguages(files) {
66
89
  const extMap = {
67
90
  ".ts": "TypeScript",