agent-bober 0.7.0 → 0.8.1

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 (60) hide show
  1. package/README.md +64 -2
  2. package/dist/cli/commands/init.d.ts.map +1 -1
  3. package/dist/cli/commands/init.js +176 -1
  4. package/dist/cli/commands/init.js.map +1 -1
  5. package/dist/discovery/config-generator.d.ts +28 -0
  6. package/dist/discovery/config-generator.d.ts.map +1 -0
  7. package/dist/discovery/config-generator.js +225 -0
  8. package/dist/discovery/config-generator.js.map +1 -0
  9. package/dist/discovery/index.d.ts +20 -0
  10. package/dist/discovery/index.d.ts.map +1 -0
  11. package/dist/discovery/index.js +19 -0
  12. package/dist/discovery/index.js.map +1 -0
  13. package/dist/discovery/scanner.d.ts +17 -0
  14. package/dist/discovery/scanner.d.ts.map +1 -0
  15. package/dist/discovery/scanner.js +120 -0
  16. package/dist/discovery/scanner.js.map +1 -0
  17. package/dist/discovery/scanners/ci-checks.d.ts +10 -0
  18. package/dist/discovery/scanners/ci-checks.d.ts.map +1 -0
  19. package/dist/discovery/scanners/ci-checks.js +169 -0
  20. package/dist/discovery/scanners/ci-checks.js.map +1 -0
  21. package/dist/discovery/scanners/code-conventions.d.ts +12 -0
  22. package/dist/discovery/scanners/code-conventions.d.ts.map +1 -0
  23. package/dist/discovery/scanners/code-conventions.js +216 -0
  24. package/dist/discovery/scanners/code-conventions.js.map +1 -0
  25. package/dist/discovery/scanners/documentation.d.ts +17 -0
  26. package/dist/discovery/scanners/documentation.d.ts.map +1 -0
  27. package/dist/discovery/scanners/documentation.js +92 -0
  28. package/dist/discovery/scanners/documentation.js.map +1 -0
  29. package/dist/discovery/scanners/git-conventions.d.ts +11 -0
  30. package/dist/discovery/scanners/git-conventions.d.ts.map +1 -0
  31. package/dist/discovery/scanners/git-conventions.js +128 -0
  32. package/dist/discovery/scanners/git-conventions.js.map +1 -0
  33. package/dist/discovery/scanners/package-scripts.d.ts +9 -0
  34. package/dist/discovery/scanners/package-scripts.d.ts.map +1 -0
  35. package/dist/discovery/scanners/package-scripts.js +112 -0
  36. package/dist/discovery/scanners/package-scripts.js.map +1 -0
  37. package/dist/discovery/scanners/test-conventions.d.ts +9 -0
  38. package/dist/discovery/scanners/test-conventions.d.ts.map +1 -0
  39. package/dist/discovery/scanners/test-conventions.js +231 -0
  40. package/dist/discovery/scanners/test-conventions.js.map +1 -0
  41. package/dist/discovery/synthesizer.d.ts +30 -0
  42. package/dist/discovery/synthesizer.d.ts.map +1 -0
  43. package/dist/discovery/synthesizer.js +348 -0
  44. package/dist/discovery/synthesizer.js.map +1 -0
  45. package/dist/discovery/types.d.ts +160 -0
  46. package/dist/discovery/types.d.ts.map +1 -0
  47. package/dist/discovery/types.js +9 -0
  48. package/dist/discovery/types.js.map +1 -0
  49. package/dist/index.d.ts +4 -0
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +4 -0
  52. package/dist/index.js.map +1 -1
  53. package/dist/mcp/tools/init.d.ts.map +1 -1
  54. package/dist/mcp/tools/init.js +102 -1
  55. package/dist/mcp/tools/init.js.map +1 -1
  56. package/package.json +2 -2
  57. package/skills/bober.eval/SKILL.md +4 -0
  58. package/skills/bober.principles/SKILL.md +36 -3
  59. package/skills/bober.run/SKILL.md +12 -0
  60. package/skills/bober.sprint/SKILL.md +8 -0
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Scanner: Code Conventions
3
+ *
4
+ * Samples up to 20 source files per file category and detects:
5
+ * - File naming patterns (camelCase / kebab-case / PascalCase / snake_case)
6
+ * - Import style (relative vs absolute/alias, .js extensions)
7
+ * - Export patterns (named vs default)
8
+ * - TypeScript patterns (any, ts-ignore, enum, interface, type)
9
+ */
10
+ import { readFile } from "node:fs/promises";
11
+ import { basename, extname } from "node:path";
12
+ import { glob } from "glob";
13
+ // ── Constants ─────────────────────────────────────────────────────
14
+ /** Directories to skip when scanning. */
15
+ const IGNORE_DIRS = [
16
+ "node_modules",
17
+ "dist",
18
+ ".git",
19
+ ".bober",
20
+ "build",
21
+ "coverage",
22
+ ".next",
23
+ "__pycache__",
24
+ ".turbo",
25
+ ".cache",
26
+ "out",
27
+ ".vercel",
28
+ ];
29
+ const MAX_FILES_PER_CATEGORY = 20;
30
+ // ── File naming detection ─────────────────────────────────────────
31
+ const KEBAB_CASE_RE = /^[a-z][a-z0-9]*(-[a-z0-9]+)+$/;
32
+ const PASCAL_CASE_RE = /^[A-Z][a-zA-Z0-9]*$/;
33
+ const CAMEL_CASE_RE = /^[a-z][a-zA-Z0-9]*$/;
34
+ const SNAKE_CASE_RE = /^[a-z][a-z0-9]*(_[a-z0-9]+)+$/;
35
+ function detectFileNaming(name) {
36
+ if (KEBAB_CASE_RE.test(name))
37
+ return "kebab-case";
38
+ if (PASCAL_CASE_RE.test(name))
39
+ return "PascalCase";
40
+ if (SNAKE_CASE_RE.test(name))
41
+ return "snake_case";
42
+ if (CAMEL_CASE_RE.test(name))
43
+ return "camelCase";
44
+ return "mixed";
45
+ }
46
+ function analyzeFileNaming(filePaths) {
47
+ const counts = {
48
+ camelCase: 0,
49
+ "kebab-case": 0,
50
+ PascalCase: 0,
51
+ snake_case: 0,
52
+ mixed: 0,
53
+ };
54
+ for (const filePath of filePaths) {
55
+ const name = basename(filePath, extname(filePath));
56
+ // Remove test suffixes for more accurate naming analysis
57
+ const cleanName = name.replace(/\.(test|spec)$/, "");
58
+ // Also handle multi-dot names like "foo.test"
59
+ const baseName = cleanName.split(".")[0] ?? cleanName;
60
+ if (baseName) {
61
+ const style = detectFileNaming(baseName);
62
+ counts[style]++;
63
+ }
64
+ }
65
+ // Find dominant style (excluding "mixed")
66
+ let dominant = "mixed";
67
+ let maxCount = 0;
68
+ for (const [style, count] of Object.entries(counts)) {
69
+ if (style !== "mixed" && count > maxCount) {
70
+ dominant = style;
71
+ maxCount = count;
72
+ }
73
+ }
74
+ return { dominant, counts };
75
+ }
76
+ // ── Import style detection ────────────────────────────────────────
77
+ const RELATIVE_RE = /^\.\.?\//;
78
+ const JS_EXT_RE = /\.js['"]?\s*$/;
79
+ function analyzeImportStyle(contents) {
80
+ let relativeCount = 0;
81
+ let absoluteCount = 0;
82
+ let jsExtCount = 0;
83
+ let totalImports = 0;
84
+ const examples = [];
85
+ for (const content of contents) {
86
+ const importLines = content.match(/^import\s+.+$/gm) ?? [];
87
+ for (const line of importLines) {
88
+ const match = /from\s+['"]([^'"]+)['"]/.exec(line);
89
+ if (!match)
90
+ continue;
91
+ const importPath = match[1];
92
+ if (!importPath)
93
+ continue;
94
+ totalImports++;
95
+ if (RELATIVE_RE.test(importPath)) {
96
+ relativeCount++;
97
+ }
98
+ else {
99
+ absoluteCount++;
100
+ }
101
+ if (JS_EXT_RE.test(importPath)) {
102
+ jsExtCount++;
103
+ }
104
+ if (examples.length < 5) {
105
+ examples.push(line.trim());
106
+ }
107
+ }
108
+ }
109
+ return {
110
+ relativeCount,
111
+ absoluteCount,
112
+ usesJsExtensions: totalImports > 0 && jsExtCount / totalImports >= 0.3,
113
+ examples,
114
+ };
115
+ }
116
+ // ── Export style detection ────────────────────────────────────────
117
+ const NAMED_EXPORT_RE = /^export\s+(const|function|class|interface|type|enum|let|var)\s/gm;
118
+ const DEFAULT_EXPORT_RE = /^export\s+default\s/gm;
119
+ function analyzeExportStyle(contents) {
120
+ let namedExportCount = 0;
121
+ let defaultExportCount = 0;
122
+ for (const content of contents) {
123
+ const named = content.match(NAMED_EXPORT_RE);
124
+ const defaults = content.match(DEFAULT_EXPORT_RE);
125
+ namedExportCount += named?.length ?? 0;
126
+ defaultExportCount += defaults?.length ?? 0;
127
+ }
128
+ let dominant;
129
+ const total = namedExportCount + defaultExportCount;
130
+ if (total === 0) {
131
+ dominant = "mixed";
132
+ }
133
+ else if (namedExportCount / total >= 0.7) {
134
+ dominant = "named";
135
+ }
136
+ else if (defaultExportCount / total >= 0.7) {
137
+ dominant = "default";
138
+ }
139
+ else {
140
+ dominant = "mixed";
141
+ }
142
+ return { namedExportCount, defaultExportCount, dominant };
143
+ }
144
+ // ── TypeScript patterns detection ─────────────────────────────────
145
+ function analyzeTypeScriptPatterns(contents) {
146
+ let anyCount = 0;
147
+ let tsIgnoreCount = 0;
148
+ let enumCount = 0;
149
+ let interfaceCount = 0;
150
+ let typeAliasCount = 0;
151
+ for (const content of contents) {
152
+ // Count `: any` usages (avoid counting "anyCount" variable names etc)
153
+ const anyMatches = content.match(/:\s*any[\s;,)>]/g);
154
+ anyCount += anyMatches?.length ?? 0;
155
+ const tsIgnoreMatches = content.match(/@ts-(ignore|expect-error)/g);
156
+ tsIgnoreCount += tsIgnoreMatches?.length ?? 0;
157
+ const enumMatches = content.match(/\benum\s+\w+/g);
158
+ enumCount += enumMatches?.length ?? 0;
159
+ const interfaceMatches = content.match(/\binterface\s+\w+/g);
160
+ interfaceCount += interfaceMatches?.length ?? 0;
161
+ const typeAliasMatches = content.match(/\btype\s+\w+\s*=/g);
162
+ typeAliasCount += typeAliasMatches?.length ?? 0;
163
+ }
164
+ return { anyCount, tsIgnoreCount, enumCount, interfaceCount, typeAliasCount };
165
+ }
166
+ // ── Main scanner ──────────────────────────────────────────────────
167
+ export async function scanCodeConventions(projectRoot) {
168
+ const ignore = IGNORE_DIRS.map((d) => `**/${d}/**`);
169
+ // Gather TypeScript/JavaScript files
170
+ let tsFiles;
171
+ let jsFiles;
172
+ try {
173
+ tsFiles = await glob("**/*.{ts,tsx}", {
174
+ cwd: projectRoot,
175
+ ignore,
176
+ absolute: true,
177
+ });
178
+ jsFiles = await glob("**/*.{js,jsx}", {
179
+ cwd: projectRoot,
180
+ ignore,
181
+ absolute: true,
182
+ });
183
+ }
184
+ catch {
185
+ return null;
186
+ }
187
+ // Exclude test files from naming analysis (but include in content analysis)
188
+ const sourceFiles = [...tsFiles, ...jsFiles]
189
+ .sort()
190
+ .slice(0, MAX_FILES_PER_CATEGORY * 2);
191
+ if (sourceFiles.length === 0) {
192
+ return null;
193
+ }
194
+ // Read file contents
195
+ const contents = [];
196
+ for (const filePath of sourceFiles.slice(0, MAX_FILES_PER_CATEGORY * 2)) {
197
+ try {
198
+ const content = await readFile(filePath, "utf-8");
199
+ contents.push(content);
200
+ }
201
+ catch {
202
+ // Skip unreadable files
203
+ }
204
+ }
205
+ const isTypeScript = tsFiles.length > 0;
206
+ return {
207
+ filesSampled: sourceFiles.length,
208
+ fileNaming: analyzeFileNaming(sourceFiles),
209
+ importStyle: analyzeImportStyle(contents),
210
+ exportStyle: analyzeExportStyle(contents),
211
+ typescriptPatterns: isTypeScript
212
+ ? analyzeTypeScriptPatterns(contents)
213
+ : null,
214
+ };
215
+ }
216
+ //# sourceMappingURL=code-conventions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-conventions.js","sourceRoot":"","sources":["../../../src/discovery/scanners/code-conventions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Scanner: Documentation
3
+ *
4
+ * Reads key documentation files from the project root and stores
5
+ * their content truncated to 2000 characters each.
6
+ *
7
+ * Files scanned:
8
+ * - README.md
9
+ * - CONTRIBUTING.md
10
+ * - CLAUDE.md
11
+ * - .cursorrules
12
+ * - .github/PULL_REQUEST_TEMPLATE.md
13
+ * - docs/**\/*.md
14
+ */
15
+ import type { DocumentationReport } from "../types.js";
16
+ export declare function scanDocumentation(projectRoot: string): Promise<DocumentationReport>;
17
+ //# sourceMappingURL=documentation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"documentation.d.ts","sourceRoot":"","sources":["../../../src/discovery/scanners/documentation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAMH,OAAO,KAAK,EAAE,mBAAmB,EAAW,MAAM,aAAa,CAAC;AAyChE,wBAAsB,iBAAiB,CACrC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,mBAAmB,CAAC,CA6C9B"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Scanner: Documentation
3
+ *
4
+ * Reads key documentation files from the project root and stores
5
+ * their content truncated to 2000 characters each.
6
+ *
7
+ * Files scanned:
8
+ * - README.md
9
+ * - CONTRIBUTING.md
10
+ * - CLAUDE.md
11
+ * - .cursorrules
12
+ * - .github/PULL_REQUEST_TEMPLATE.md
13
+ * - docs/**\/*.md
14
+ */
15
+ import { readFile } from "node:fs/promises";
16
+ import { join, relative } from "node:path";
17
+ import { glob } from "glob";
18
+ import { fileExists } from "../../utils/fs.js";
19
+ // ── Constants ─────────────────────────────────────────────────────
20
+ const MAX_CONTENT_LENGTH = 2000;
21
+ /** Fixed documentation files to always attempt reading. */
22
+ const FIXED_DOC_FILES = [
23
+ "README.md",
24
+ "CONTRIBUTING.md",
25
+ "CLAUDE.md",
26
+ ".cursorrules",
27
+ ".github/PULL_REQUEST_TEMPLATE.md",
28
+ ];
29
+ // ── Helpers ───────────────────────────────────────────────────────
30
+ async function readDocFile(projectRoot, relPath) {
31
+ const fullPath = join(projectRoot, relPath);
32
+ if (!(await fileExists(fullPath))) {
33
+ return null;
34
+ }
35
+ try {
36
+ const raw = await readFile(fullPath, "utf-8");
37
+ const truncated = raw.length > MAX_CONTENT_LENGTH;
38
+ return {
39
+ path: relPath,
40
+ content: truncated ? raw.slice(0, MAX_CONTENT_LENGTH) : raw,
41
+ truncated,
42
+ };
43
+ }
44
+ catch {
45
+ return null;
46
+ }
47
+ }
48
+ // ── Main scanner ──────────────────────────────────────────────────
49
+ export async function scanDocumentation(projectRoot) {
50
+ const files = [];
51
+ const seenPaths = new Set();
52
+ // Read fixed doc files first
53
+ for (const relPath of FIXED_DOC_FILES) {
54
+ const docFile = await readDocFile(projectRoot, relPath);
55
+ if (docFile) {
56
+ files.push(docFile);
57
+ seenPaths.add(relPath);
58
+ }
59
+ }
60
+ // Scan docs/**/*.md
61
+ try {
62
+ const docsGlob = await glob("docs/**/*.md", {
63
+ cwd: projectRoot,
64
+ absolute: true,
65
+ });
66
+ // Sort alphabetically for deterministic ordering
67
+ const sortedDocs = docsGlob.sort();
68
+ for (const fullPath of sortedDocs) {
69
+ const relPath = relative(projectRoot, fullPath);
70
+ if (seenPaths.has(relPath))
71
+ continue;
72
+ try {
73
+ const raw = await readFile(fullPath, "utf-8");
74
+ const truncated = raw.length > MAX_CONTENT_LENGTH;
75
+ files.push({
76
+ path: relPath,
77
+ content: truncated ? raw.slice(0, MAX_CONTENT_LENGTH) : raw,
78
+ truncated,
79
+ });
80
+ seenPaths.add(relPath);
81
+ }
82
+ catch {
83
+ // Skip unreadable files
84
+ }
85
+ }
86
+ }
87
+ catch {
88
+ // docs/ directory may not exist — that's fine
89
+ }
90
+ return { files };
91
+ }
92
+ //# sourceMappingURL=documentation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"documentation.js","sourceRoot":"","sources":["../../../src/discovery/scanners/documentation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Scanner: Git Conventions
3
+ *
4
+ * Analyzes git history and branches to detect:
5
+ * - Commit message format (conventional commits, custom prefixes)
6
+ * - Branch naming patterns
7
+ * - Merge strategy
8
+ */
9
+ import type { GitConventionsReport } from "../types.js";
10
+ export declare function scanGitConventions(projectRoot: string): Promise<GitConventionsReport | null>;
11
+ //# sourceMappingURL=git-conventions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-conventions.d.ts","sourceRoot":"","sources":["../../../src/discovery/scanners/git-conventions.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AA8ExD,wBAAsB,kBAAkB,CACtC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAkEtC"}
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Scanner: Git Conventions
3
+ *
4
+ * Analyzes git history and branches to detect:
5
+ * - Commit message format (conventional commits, custom prefixes)
6
+ * - Branch naming patterns
7
+ * - Merge strategy
8
+ */
9
+ import { join } from "node:path";
10
+ import { execa } from "execa";
11
+ import { fileExists } from "../../utils/fs.js";
12
+ // ── Commit message analysis ───────────────────────────────────────
13
+ const CONVENTIONAL_COMMIT_RE = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?:/i;
14
+ const MERGE_COMMIT_RE = /^Merge (branch|pull request|remote)/i;
15
+ /**
16
+ * Extracts the prefix/type from a commit message.
17
+ * Returns e.g. "feat:", "bober(sprint-1):", "JIRA-123:".
18
+ */
19
+ function extractPrefix(message) {
20
+ // Conventional commits: feat(scope): or feat:
21
+ const conventional = /^[a-zA-Z]+(\([^)]+\))?:/.exec(message);
22
+ if (conventional)
23
+ return conventional[0];
24
+ // Ticket prefix: JIRA-123, ABC-456
25
+ const ticket = /^[A-Z]+-\d+/.exec(message);
26
+ if (ticket)
27
+ return ticket[0];
28
+ return null;
29
+ }
30
+ function detectMostCommonPrefix(messages) {
31
+ const prefixCounts = new Map();
32
+ for (const msg of messages) {
33
+ const prefix = extractPrefix(msg);
34
+ if (prefix) {
35
+ prefixCounts.set(prefix, (prefixCounts.get(prefix) ?? 0) + 1);
36
+ }
37
+ }
38
+ if (prefixCounts.size === 0)
39
+ return null;
40
+ let topPrefix = "";
41
+ let topCount = 0;
42
+ for (const [prefix, count] of prefixCounts) {
43
+ if (count > topCount) {
44
+ topPrefix = prefix;
45
+ topCount = count;
46
+ }
47
+ }
48
+ return topPrefix || null;
49
+ }
50
+ // ── Branch pattern analysis ───────────────────────────────────────
51
+ /**
52
+ * Groups branch names into patterns like "feature/*", "bober/*", etc.
53
+ */
54
+ function detectBranchPatterns(branches) {
55
+ const prefixCounts = new Map();
56
+ for (const branch of branches) {
57
+ // Strip remote prefix (remotes/origin/)
58
+ const clean = branch.replace(/^remotes\/[^/]+\//, "").trim();
59
+ if (!clean || clean === "HEAD")
60
+ continue;
61
+ const slashIdx = clean.indexOf("/");
62
+ if (slashIdx > 0) {
63
+ const prefix = clean.substring(0, slashIdx);
64
+ prefixCounts.set(prefix + "/*", (prefixCounts.get(prefix + "/*") ?? 0) + 1);
65
+ }
66
+ }
67
+ // Return patterns with at least 1 occurrence, sorted by frequency
68
+ return Array.from(prefixCounts.entries())
69
+ .filter(([, count]) => count >= 1)
70
+ .sort((a, b) => b[1] - a[1])
71
+ .map(([pattern]) => pattern);
72
+ }
73
+ // ── Main scanner ──────────────────────────────────────────────────
74
+ export async function scanGitConventions(projectRoot) {
75
+ // Check for .git directory
76
+ const gitDir = join(projectRoot, ".git");
77
+ if (!(await fileExists(gitDir))) {
78
+ return null;
79
+ }
80
+ // Fetch git log
81
+ let recentMessages;
82
+ try {
83
+ const { stdout } = await execa("git", ["log", "--oneline", "-50"], { cwd: projectRoot });
84
+ recentMessages = stdout
85
+ .split("\n")
86
+ .map((line) => {
87
+ // Remove the short hash prefix (first 7-8 chars)
88
+ const spaceIdx = line.indexOf(" ");
89
+ return spaceIdx > 0 ? line.substring(spaceIdx + 1).trim() : line.trim();
90
+ })
91
+ .filter((msg) => msg.length > 0);
92
+ }
93
+ catch {
94
+ // Git command failed (no commits, etc.)
95
+ return null;
96
+ }
97
+ // Fetch branches
98
+ let branches = [];
99
+ try {
100
+ const { stdout } = await execa("git", ["branch", "-a"], { cwd: projectRoot });
101
+ branches = stdout
102
+ .split("\n")
103
+ .map((b) => b.replace(/^\*?\s+/, "").trim())
104
+ .filter((b) => b.length > 0);
105
+ }
106
+ catch {
107
+ // Non-fatal
108
+ }
109
+ // Analyze commit messages
110
+ const mergeCommits = recentMessages.filter((m) => MERGE_COMMIT_RE.test(m));
111
+ const nonMergeMessages = recentMessages.filter((m) => !MERGE_COMMIT_RE.test(m));
112
+ const conventionalCount = nonMergeMessages.filter((m) => CONVENTIONAL_COMMIT_RE.test(m)).length;
113
+ const usesConventionalCommits = nonMergeMessages.length > 0 &&
114
+ conventionalCount / nonMergeMessages.length >= 0.5;
115
+ const mergeCommitRatio = recentMessages.length > 0
116
+ ? mergeCommits.length / recentMessages.length
117
+ : 0;
118
+ return {
119
+ usesConventionalCommits,
120
+ mostCommonPrefix: detectMostCommonPrefix(nonMergeMessages),
121
+ recentMessages,
122
+ branchPatterns: detectBranchPatterns(branches),
123
+ branches,
124
+ hasLinearHistory: mergeCommitRatio < 0.05,
125
+ mergeCommitRatio,
126
+ };
127
+ }
128
+ //# sourceMappingURL=git-conventions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-conventions.js","sourceRoot":"","sources":["../../../src/discovery/scanners/git-conventions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Scanner: Package Scripts
3
+ *
4
+ * Reads package.json scripts, maps them to bober command categories,
5
+ * and detects the package manager from lockfiles.
6
+ */
7
+ import type { PackageScriptsReport } from "../types.js";
8
+ export declare function scanPackageScripts(projectRoot: string): Promise<PackageScriptsReport | null>;
9
+ //# sourceMappingURL=package-scripts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package-scripts.d.ts","sourceRoot":"","sources":["../../../src/discovery/scanners/package-scripts.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EACV,oBAAoB,EAGrB,MAAM,aAAa,CAAC;AAqGrB,wBAAsB,kBAAkB,CACtC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CA8BtC"}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Scanner: Package Scripts
3
+ *
4
+ * Reads package.json scripts, maps them to bober command categories,
5
+ * and detects the package manager from lockfiles.
6
+ */
7
+ import { readFile } from "node:fs/promises";
8
+ import { join } from "node:path";
9
+ import { fileExists } from "../../utils/fs.js";
10
+ async function detectPackageManager(projectRoot) {
11
+ if (await fileExists(join(projectRoot, "bun.lockb")))
12
+ return "bun";
13
+ if (await fileExists(join(projectRoot, "pnpm-lock.yaml")))
14
+ return "pnpm";
15
+ if (await fileExists(join(projectRoot, "yarn.lock")))
16
+ return "yarn";
17
+ if (await fileExists(join(projectRoot, "package-lock.json")))
18
+ return "npm";
19
+ // Fallback: if package.json exists, assume npm
20
+ if (await fileExists(join(projectRoot, "package.json")))
21
+ return "npm";
22
+ return null;
23
+ }
24
+ function buildRunCommand(pm, scriptName) {
25
+ switch (pm) {
26
+ case "yarn":
27
+ return `yarn ${scriptName}`;
28
+ case "pnpm":
29
+ return `pnpm run ${scriptName}`;
30
+ case "bun":
31
+ return `bun run ${scriptName}`;
32
+ case "npm":
33
+ default:
34
+ return `npm run ${scriptName}`;
35
+ }
36
+ }
37
+ // ── Category matching ─────────────────────────────────────────────
38
+ /**
39
+ * Keywords used to identify bober command categories.
40
+ * Checked against the script name (key in package.json "scripts").
41
+ */
42
+ const CATEGORY_PATTERNS = [
43
+ {
44
+ category: "build",
45
+ patterns: [/^build$/, /^compile$/, /^tsc$/, /^bundle$/],
46
+ },
47
+ {
48
+ category: "test",
49
+ patterns: [/^test$/, /^test:run$/, /^vitest$/, /^jest$/, /^mocha$/],
50
+ },
51
+ {
52
+ category: "lint",
53
+ patterns: [/^lint$/, /^lint:fix$/, /^eslint$/, /^tslint$/],
54
+ },
55
+ {
56
+ category: "typecheck",
57
+ patterns: [/^typecheck$/, /^type-check$/, /^types$/, /^tsc:check$/],
58
+ },
59
+ {
60
+ category: "dev",
61
+ patterns: [/^dev$/, /^start$/, /^serve$/, /^watch$/],
62
+ },
63
+ {
64
+ category: "install",
65
+ patterns: [/^prepare$/, /^postinstall$/, /^setup$/],
66
+ },
67
+ ];
68
+ function categorizeScript(scriptName, command, pm) {
69
+ const normalizedName = scriptName.toLowerCase();
70
+ for (const { category, patterns } of CATEGORY_PATTERNS) {
71
+ if (patterns.some((p) => p.test(normalizedName))) {
72
+ return {
73
+ category,
74
+ mapping: {
75
+ scriptName,
76
+ command,
77
+ runCommand: buildRunCommand(pm, scriptName),
78
+ },
79
+ };
80
+ }
81
+ }
82
+ return null;
83
+ }
84
+ export async function scanPackageScripts(projectRoot) {
85
+ const pkgPath = join(projectRoot, "package.json");
86
+ if (!(await fileExists(pkgPath))) {
87
+ return null;
88
+ }
89
+ let pkg;
90
+ try {
91
+ const raw = await readFile(pkgPath, "utf-8");
92
+ pkg = JSON.parse(raw);
93
+ }
94
+ catch {
95
+ return null;
96
+ }
97
+ const pm = await detectPackageManager(projectRoot);
98
+ const allScripts = pkg.scripts ?? {};
99
+ const categorized = {};
100
+ for (const [scriptName, command] of Object.entries(allScripts)) {
101
+ const result = categorizeScript(scriptName, command, pm);
102
+ if (result && !(result.category in categorized)) {
103
+ categorized[result.category] = result.mapping;
104
+ }
105
+ }
106
+ return {
107
+ packageManager: pm,
108
+ allScripts,
109
+ categorized,
110
+ };
111
+ }
112
+ //# sourceMappingURL=package-scripts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"package-scripts.js","sourceRoot":"","sources":["../../../src/discovery/scanners/package-scripts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Scanner: Test Conventions
3
+ *
4
+ * Detects test framework, file naming patterns, directory structure,
5
+ * mocking library, and coverage configuration.
6
+ */
7
+ import type { TestConventionsReport } from "../types.js";
8
+ export declare function scanTestConventions(projectRoot: string): Promise<TestConventionsReport | null>;
9
+ //# sourceMappingURL=test-conventions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-conventions.d.ts","sourceRoot":"","sources":["../../../src/discovery/scanners/test-conventions.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,KAAK,EACV,qBAAqB,EAGtB,MAAM,aAAa,CAAC;AAoOrB,wBAAsB,mBAAmB,CACvC,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CA8BvC"}