@boshu2/vibe-check 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +115 -0
- package/bin/vibe-check.js +2 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +90 -0
- package/dist/cli.js.map +1 -0
- package/dist/git.d.ts +4 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +64 -0
- package/dist/git.js.map +1 -0
- package/dist/metrics/flow.d.ts +3 -0
- package/dist/metrics/flow.d.ts.map +1 -0
- package/dist/metrics/flow.js +48 -0
- package/dist/metrics/flow.js.map +1 -0
- package/dist/metrics/index.d.ts +4 -0
- package/dist/metrics/index.d.ts.map +1 -0
- package/dist/metrics/index.js +156 -0
- package/dist/metrics/index.js.map +1 -0
- package/dist/metrics/rework.d.ts +3 -0
- package/dist/metrics/rework.d.ts.map +1 -0
- package/dist/metrics/rework.js +45 -0
- package/dist/metrics/rework.js.map +1 -0
- package/dist/metrics/spirals.d.ts +9 -0
- package/dist/metrics/spirals.d.ts.map +1 -0
- package/dist/metrics/spirals.js +153 -0
- package/dist/metrics/spirals.js.map +1 -0
- package/dist/metrics/trust.d.ts +3 -0
- package/dist/metrics/trust.d.ts.map +1 -0
- package/dist/metrics/trust.js +71 -0
- package/dist/metrics/trust.js.map +1 -0
- package/dist/metrics/velocity.d.ts +4 -0
- package/dist/metrics/velocity.d.ts.map +1 -0
- package/dist/metrics/velocity.js +77 -0
- package/dist/metrics/velocity.js.map +1 -0
- package/dist/output/index.d.ts +6 -0
- package/dist/output/index.d.ts.map +1 -0
- package/dist/output/index.js +25 -0
- package/dist/output/index.js.map +1 -0
- package/dist/output/json.d.ts +3 -0
- package/dist/output/json.d.ts.map +1 -0
- package/dist/output/json.js +52 -0
- package/dist/output/json.js.map +1 -0
- package/dist/output/markdown.d.ts +3 -0
- package/dist/output/markdown.d.ts.map +1 -0
- package/dist/output/markdown.js +90 -0
- package/dist/output/markdown.js.map +1 -0
- package/dist/output/terminal.d.ts +3 -0
- package/dist/output/terminal.d.ts.map +1 -0
- package/dist/output/terminal.js +90 -0
- package/dist/output/terminal.js.map +1 -0
- package/dist/types.d.ts +63 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +42 -0
- package/src/cli.ts +96 -0
- package/src/git.ts +72 -0
- package/src/metrics/flow.ts +53 -0
- package/src/metrics/index.ts +169 -0
- package/src/metrics/rework.ts +45 -0
- package/src/metrics/spirals.ts +173 -0
- package/src/metrics/trust.ts +80 -0
- package/src/metrics/velocity.ts +86 -0
- package/src/output/index.ts +20 -0
- package/src/output/json.ts +51 -0
- package/src/output/markdown.ts +111 -0
- package/src/output/terminal.ts +109 -0
- package/src/types.ts +68 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.formatTerminal = formatTerminal;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const date_fns_1 = require("date-fns");
|
|
9
|
+
function formatTerminal(result) {
|
|
10
|
+
const lines = [];
|
|
11
|
+
// Header
|
|
12
|
+
lines.push('');
|
|
13
|
+
lines.push(chalk_1.default.bold.cyan('='.repeat(64)));
|
|
14
|
+
lines.push(chalk_1.default.bold.cyan(' VIBE-CHECK RESULTS'));
|
|
15
|
+
lines.push(chalk_1.default.bold.cyan('='.repeat(64)));
|
|
16
|
+
// Period info
|
|
17
|
+
const fromStr = (0, date_fns_1.format)(result.period.from, 'MMM d, yyyy');
|
|
18
|
+
const toStr = (0, date_fns_1.format)(result.period.to, 'MMM d, yyyy');
|
|
19
|
+
lines.push('');
|
|
20
|
+
lines.push(chalk_1.default.gray(` Period: ${fromStr} - ${toStr} (${result.period.activeHours}h active)`));
|
|
21
|
+
lines.push(chalk_1.default.gray(` Commits: ${result.commits.total} total (${result.commits.feat} feat, ${result.commits.fix} fix, ${result.commits.docs} docs, ${result.commits.other} other)`));
|
|
22
|
+
// Metrics table
|
|
23
|
+
lines.push('');
|
|
24
|
+
lines.push(chalk_1.default.bold.white(' METRIC VALUE RATING'));
|
|
25
|
+
lines.push(chalk_1.default.gray(' ' + '-'.repeat(50)));
|
|
26
|
+
const metrics = [
|
|
27
|
+
{ name: 'Iteration Velocity', metric: result.metrics.iterationVelocity },
|
|
28
|
+
{ name: 'Rework Ratio', metric: result.metrics.reworkRatio },
|
|
29
|
+
{ name: 'Trust Pass Rate', metric: result.metrics.trustPassRate },
|
|
30
|
+
{ name: 'Debug Spiral Duration', metric: result.metrics.debugSpiralDuration },
|
|
31
|
+
{ name: 'Flow Efficiency', metric: result.metrics.flowEfficiency },
|
|
32
|
+
];
|
|
33
|
+
for (const { name, metric } of metrics) {
|
|
34
|
+
const valueStr = `${metric.value}${metric.unit}`.padEnd(10);
|
|
35
|
+
const ratingStr = formatRating(metric.rating);
|
|
36
|
+
lines.push(` ${name.padEnd(26)} ${valueStr} ${ratingStr}`);
|
|
37
|
+
}
|
|
38
|
+
// Overall rating
|
|
39
|
+
lines.push('');
|
|
40
|
+
lines.push(chalk_1.default.bold.cyan('-'.repeat(64)));
|
|
41
|
+
lines.push(` ${chalk_1.default.bold('OVERALL:')} ${formatOverallRating(result.overall)}`);
|
|
42
|
+
lines.push(chalk_1.default.bold.cyan('-'.repeat(64)));
|
|
43
|
+
// Debug spirals
|
|
44
|
+
if (result.fixChains.length > 0) {
|
|
45
|
+
lines.push('');
|
|
46
|
+
lines.push(chalk_1.default.bold.yellow(` DEBUG SPIRALS (${result.fixChains.length} detected):`));
|
|
47
|
+
for (const chain of result.fixChains) {
|
|
48
|
+
const patternStr = chain.pattern ? ` (${chain.pattern})` : '';
|
|
49
|
+
lines.push(chalk_1.default.yellow(` - ${chain.component}: ${chain.commits} commits, ${chain.duration}m${patternStr}`));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Patterns
|
|
53
|
+
if (result.patterns.total > 0) {
|
|
54
|
+
lines.push('');
|
|
55
|
+
lines.push(chalk_1.default.bold.magenta(' PATTERNS:'));
|
|
56
|
+
for (const [pattern, count] of Object.entries(result.patterns.categories)) {
|
|
57
|
+
const tracerNote = pattern !== 'OTHER' ? ' (tracer available)' : '';
|
|
58
|
+
lines.push(chalk_1.default.magenta(` - ${pattern}: ${count} fixes${tracerNote}`));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
lines.push('');
|
|
62
|
+
lines.push(chalk_1.default.bold.cyan('='.repeat(64)));
|
|
63
|
+
lines.push('');
|
|
64
|
+
return lines.join('\n');
|
|
65
|
+
}
|
|
66
|
+
function formatRating(rating) {
|
|
67
|
+
switch (rating) {
|
|
68
|
+
case 'elite':
|
|
69
|
+
return chalk_1.default.green.bold('ELITE');
|
|
70
|
+
case 'high':
|
|
71
|
+
return chalk_1.default.blue.bold('HIGH');
|
|
72
|
+
case 'medium':
|
|
73
|
+
return chalk_1.default.yellow.bold('MEDIUM');
|
|
74
|
+
case 'low':
|
|
75
|
+
return chalk_1.default.red.bold('LOW');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function formatOverallRating(rating) {
|
|
79
|
+
switch (rating) {
|
|
80
|
+
case 'ELITE':
|
|
81
|
+
return chalk_1.default.green.bold('ELITE');
|
|
82
|
+
case 'HIGH':
|
|
83
|
+
return chalk_1.default.blue.bold('HIGH');
|
|
84
|
+
case 'MEDIUM':
|
|
85
|
+
return chalk_1.default.yellow.bold('MEDIUM');
|
|
86
|
+
case 'LOW':
|
|
87
|
+
return chalk_1.default.red.bold('LOW');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=terminal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminal.js","sourceRoot":"","sources":["../../src/output/terminal.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export type Rating = 'elite' | 'high' | 'medium' | 'low';
|
|
2
|
+
export type OutputFormat = 'terminal' | 'json' | 'markdown';
|
|
3
|
+
export type OverallRating = 'ELITE' | 'HIGH' | 'MEDIUM' | 'LOW';
|
|
4
|
+
export interface Commit {
|
|
5
|
+
hash: string;
|
|
6
|
+
date: Date;
|
|
7
|
+
message: string;
|
|
8
|
+
type: 'feat' | 'fix' | 'docs' | 'chore' | 'refactor' | 'test' | 'style' | 'other';
|
|
9
|
+
scope: string | null;
|
|
10
|
+
author: string;
|
|
11
|
+
}
|
|
12
|
+
export interface MetricResult {
|
|
13
|
+
value: number;
|
|
14
|
+
unit: string;
|
|
15
|
+
rating: Rating;
|
|
16
|
+
description: string;
|
|
17
|
+
}
|
|
18
|
+
export interface FixChain {
|
|
19
|
+
component: string;
|
|
20
|
+
commits: number;
|
|
21
|
+
duration: number;
|
|
22
|
+
isSpiral: boolean;
|
|
23
|
+
pattern: string | null;
|
|
24
|
+
firstCommit: Date;
|
|
25
|
+
lastCommit: Date;
|
|
26
|
+
}
|
|
27
|
+
export interface PatternSummary {
|
|
28
|
+
categories: Record<string, number>;
|
|
29
|
+
total: number;
|
|
30
|
+
tracerAvailable: number;
|
|
31
|
+
}
|
|
32
|
+
export interface VibeCheckResult {
|
|
33
|
+
period: {
|
|
34
|
+
from: Date;
|
|
35
|
+
to: Date;
|
|
36
|
+
activeHours: number;
|
|
37
|
+
};
|
|
38
|
+
commits: {
|
|
39
|
+
total: number;
|
|
40
|
+
feat: number;
|
|
41
|
+
fix: number;
|
|
42
|
+
docs: number;
|
|
43
|
+
other: number;
|
|
44
|
+
};
|
|
45
|
+
metrics: {
|
|
46
|
+
iterationVelocity: MetricResult;
|
|
47
|
+
reworkRatio: MetricResult;
|
|
48
|
+
trustPassRate: MetricResult;
|
|
49
|
+
debugSpiralDuration: MetricResult;
|
|
50
|
+
flowEfficiency: MetricResult;
|
|
51
|
+
};
|
|
52
|
+
fixChains: FixChain[];
|
|
53
|
+
patterns: PatternSummary;
|
|
54
|
+
overall: OverallRating;
|
|
55
|
+
}
|
|
56
|
+
export interface CliOptions {
|
|
57
|
+
since?: string;
|
|
58
|
+
until?: string;
|
|
59
|
+
format: OutputFormat;
|
|
60
|
+
repo: string;
|
|
61
|
+
verbose: boolean;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;AACzD,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,MAAM,GAAG,UAAU,CAAC;AAC5D,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;AAEhE,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,IAAI,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;IAClF,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,WAAW,EAAE,IAAI,CAAC;IAClB,UAAU,EAAE,IAAI,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE;QACN,IAAI,EAAE,IAAI,CAAC;QACX,EAAE,EAAE,IAAI,CAAC;QACT,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,OAAO,EAAE;QACP,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;KACf,CAAC;IACF,OAAO,EAAE;QACP,iBAAiB,EAAE,YAAY,CAAC;QAChC,WAAW,EAAE,YAAY,CAAC;QAC1B,aAAa,EAAE,YAAY,CAAC;QAC5B,mBAAmB,EAAE,YAAY,CAAC;QAClC,cAAc,EAAE,YAAY,CAAC;KAC9B,CAAC;IACF,SAAS,EAAE,QAAQ,EAAE,CAAC;IACtB,QAAQ,EAAE,cAAc,CAAC;IACzB,OAAO,EAAE,aAAa,CAAC;CACxB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,YAAY,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;CAClB"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@boshu2/vibe-check",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Measure vibe coding effectiveness with git commit analysis",
|
|
5
|
+
"main": "dist/cli.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"vibe-check": "./bin/vibe-check.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"dev": "ts-node src/cli.ts",
|
|
12
|
+
"start": "node dist/cli.js",
|
|
13
|
+
"prepublishOnly": "npm run build"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"vibe-coding",
|
|
17
|
+
"git",
|
|
18
|
+
"metrics",
|
|
19
|
+
"developer-productivity",
|
|
20
|
+
"cli"
|
|
21
|
+
],
|
|
22
|
+
"author": "bodefuller",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://github.com/boshu2/vibe-check"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"chalk": "^4.1.2",
|
|
30
|
+
"commander": "^12.1.0",
|
|
31
|
+
"date-fns": "^3.6.0",
|
|
32
|
+
"simple-git": "^3.27.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^20.17.6",
|
|
36
|
+
"typescript": "^5.6.3",
|
|
37
|
+
"ts-node": "^10.9.2"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=18.0.0"
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { getCommits, isGitRepo } from './git';
|
|
6
|
+
import { analyzeCommits } from './metrics';
|
|
7
|
+
import { formatOutput } from './output';
|
|
8
|
+
import { OutputFormat } from './types';
|
|
9
|
+
|
|
10
|
+
const program = new Command();
|
|
11
|
+
|
|
12
|
+
program
|
|
13
|
+
.name('vibe-check')
|
|
14
|
+
.description('Measure vibe coding effectiveness with git commit analysis')
|
|
15
|
+
.version('1.0.0')
|
|
16
|
+
.option('--since <date>', 'Start date for analysis (e.g., "1 week ago", "2025-11-01")')
|
|
17
|
+
.option('--until <date>', 'End date for analysis (default: now)')
|
|
18
|
+
.option(
|
|
19
|
+
'-f, --format <type>',
|
|
20
|
+
'Output format: terminal, json, markdown',
|
|
21
|
+
'terminal'
|
|
22
|
+
)
|
|
23
|
+
.option('-r, --repo <path>', 'Repository path (default: current directory)', process.cwd())
|
|
24
|
+
.option('-v, --verbose', 'Show verbose output', false)
|
|
25
|
+
.action(async (options) => {
|
|
26
|
+
try {
|
|
27
|
+
const { since, until, format, repo, verbose } = options;
|
|
28
|
+
|
|
29
|
+
// Validate format
|
|
30
|
+
const validFormats: OutputFormat[] = ['terminal', 'json', 'markdown'];
|
|
31
|
+
if (!validFormats.includes(format)) {
|
|
32
|
+
console.error(chalk.red(`Invalid format: ${format}`));
|
|
33
|
+
console.error(chalk.gray(`Valid formats: ${validFormats.join(', ')}`));
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Check if repo is valid
|
|
38
|
+
if (!(await isGitRepo(repo))) {
|
|
39
|
+
console.error(chalk.red(`Not a git repository: ${repo}`));
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (verbose) {
|
|
44
|
+
console.error(chalk.gray(`Analyzing repository: ${repo}`));
|
|
45
|
+
if (since) console.error(chalk.gray(`Since: ${since}`));
|
|
46
|
+
if (until) console.error(chalk.gray(`Until: ${until}`));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Get commits
|
|
50
|
+
const commits = await getCommits(repo, since, until);
|
|
51
|
+
|
|
52
|
+
if (commits.length === 0) {
|
|
53
|
+
if (format === 'terminal') {
|
|
54
|
+
console.log(chalk.yellow('\nNo commits found in the specified range.\n'));
|
|
55
|
+
if (!since) {
|
|
56
|
+
console.log(chalk.gray('Try specifying a date range:'));
|
|
57
|
+
console.log(chalk.gray(' vibe-check --since "1 week ago"'));
|
|
58
|
+
console.log(chalk.gray(' vibe-check --since "2025-11-01"'));
|
|
59
|
+
}
|
|
60
|
+
} else if (format === 'json') {
|
|
61
|
+
console.log(JSON.stringify({ error: 'No commits found', commits: 0 }));
|
|
62
|
+
} else {
|
|
63
|
+
console.log('# Vibe-Check Report\n\nNo commits found in the specified range.');
|
|
64
|
+
}
|
|
65
|
+
process.exit(0);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (verbose) {
|
|
69
|
+
console.error(chalk.gray(`Found ${commits.length} commits`));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Analyze commits
|
|
73
|
+
const result = analyzeCommits(commits);
|
|
74
|
+
|
|
75
|
+
// Output result
|
|
76
|
+
const output = formatOutput(result, format as OutputFormat);
|
|
77
|
+
console.log(output);
|
|
78
|
+
|
|
79
|
+
// Exit with appropriate code based on overall rating
|
|
80
|
+
if (result.overall === 'LOW') {
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
} catch (error) {
|
|
84
|
+
if (error instanceof Error) {
|
|
85
|
+
console.error(chalk.red(`Error: ${error.message}`));
|
|
86
|
+
if (options.verbose) {
|
|
87
|
+
console.error(chalk.gray(error.stack));
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
console.error(chalk.red('An unexpected error occurred'));
|
|
91
|
+
}
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
program.parse();
|
package/src/git.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import simpleGit, { SimpleGit, LogResult, DefaultLogFields } from 'simple-git';
|
|
2
|
+
import { Commit } from './types';
|
|
3
|
+
|
|
4
|
+
const COMMIT_TYPES = ['feat', 'fix', 'docs', 'chore', 'refactor', 'test', 'style'] as const;
|
|
5
|
+
|
|
6
|
+
export async function getCommits(
|
|
7
|
+
repoPath: string,
|
|
8
|
+
since?: string,
|
|
9
|
+
until?: string
|
|
10
|
+
): Promise<Commit[]> {
|
|
11
|
+
const git: SimpleGit = simpleGit(repoPath);
|
|
12
|
+
|
|
13
|
+
// Build options for git log
|
|
14
|
+
const options: Record<string, string | number | boolean> = {};
|
|
15
|
+
|
|
16
|
+
if (since) {
|
|
17
|
+
options['--since'] = since;
|
|
18
|
+
}
|
|
19
|
+
if (until) {
|
|
20
|
+
options['--until'] = until;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const log: LogResult<DefaultLogFields> = await git.log(options);
|
|
25
|
+
|
|
26
|
+
return log.all.map((entry) => parseCommit(entry));
|
|
27
|
+
} catch (error) {
|
|
28
|
+
if (error instanceof Error) {
|
|
29
|
+
throw new Error(`Failed to read git log: ${error.message}`);
|
|
30
|
+
}
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function parseCommit(entry: DefaultLogFields): Commit {
|
|
36
|
+
const { hash, date, message, author_name } = entry;
|
|
37
|
+
|
|
38
|
+
// Parse conventional commit format: type(scope): description
|
|
39
|
+
const conventionalMatch = message.match(/^(\w+)(?:\(([^)]+)\))?:\s*(.+)/);
|
|
40
|
+
|
|
41
|
+
let type: Commit['type'] = 'other';
|
|
42
|
+
let scope: string | null = null;
|
|
43
|
+
|
|
44
|
+
if (conventionalMatch) {
|
|
45
|
+
const [, rawType, rawScope] = conventionalMatch;
|
|
46
|
+
const normalizedType = rawType.toLowerCase();
|
|
47
|
+
|
|
48
|
+
if (COMMIT_TYPES.includes(normalizedType as typeof COMMIT_TYPES[number])) {
|
|
49
|
+
type = normalizedType as Commit['type'];
|
|
50
|
+
}
|
|
51
|
+
scope = rawScope || null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
hash: hash.substring(0, 7),
|
|
56
|
+
date: new Date(date),
|
|
57
|
+
message: message.split('\n')[0], // First line only
|
|
58
|
+
type,
|
|
59
|
+
scope,
|
|
60
|
+
author: author_name,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function isGitRepo(repoPath: string): Promise<boolean> {
|
|
65
|
+
const git: SimpleGit = simpleGit(repoPath);
|
|
66
|
+
try {
|
|
67
|
+
await git.status();
|
|
68
|
+
return true;
|
|
69
|
+
} catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { FixChain, MetricResult, Rating } from '../types';
|
|
2
|
+
|
|
3
|
+
export function calculateFlowEfficiency(
|
|
4
|
+
activeMinutes: number,
|
|
5
|
+
spirals: FixChain[]
|
|
6
|
+
): MetricResult {
|
|
7
|
+
if (activeMinutes === 0) {
|
|
8
|
+
return {
|
|
9
|
+
value: 100,
|
|
10
|
+
unit: '%',
|
|
11
|
+
rating: 'elite',
|
|
12
|
+
description: 'No active time recorded',
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const spiralMinutes = spirals
|
|
17
|
+
.filter((s) => s.isSpiral)
|
|
18
|
+
.reduce((sum, s) => sum + s.duration, 0);
|
|
19
|
+
|
|
20
|
+
const efficiency = ((activeMinutes - spiralMinutes) / activeMinutes) * 100;
|
|
21
|
+
const clampedEfficiency = Math.max(0, Math.min(100, efficiency));
|
|
22
|
+
const rating = getRating(clampedEfficiency);
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
value: Math.round(clampedEfficiency),
|
|
26
|
+
unit: '%',
|
|
27
|
+
rating,
|
|
28
|
+
description: getDescription(rating, spiralMinutes),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getRating(efficiency: number): Rating {
|
|
33
|
+
if (efficiency > 90) return 'elite';
|
|
34
|
+
if (efficiency >= 75) return 'high';
|
|
35
|
+
if (efficiency >= 50) return 'medium';
|
|
36
|
+
return 'low';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function getDescription(rating: Rating, spiralMinutes: number): string {
|
|
40
|
+
const spiralText =
|
|
41
|
+
spiralMinutes > 0 ? `${spiralMinutes}m spent in debug spirals` : 'No debug spirals';
|
|
42
|
+
|
|
43
|
+
switch (rating) {
|
|
44
|
+
case 'elite':
|
|
45
|
+
return `${spiralText}. Excellent productive flow`;
|
|
46
|
+
case 'high':
|
|
47
|
+
return `${spiralText}. Good balance`;
|
|
48
|
+
case 'medium':
|
|
49
|
+
return `${spiralText}. Significant debugging overhead`;
|
|
50
|
+
case 'low':
|
|
51
|
+
return `${spiralText}. More debugging than building`;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { Commit, VibeCheckResult, OverallRating, Rating } from '../types';
|
|
2
|
+
import { calculateIterationVelocity, calculateActiveHours } from './velocity';
|
|
3
|
+
import { calculateReworkRatio } from './rework';
|
|
4
|
+
import { calculateTrustPassRate } from './trust';
|
|
5
|
+
import {
|
|
6
|
+
detectFixChains,
|
|
7
|
+
calculateDebugSpiralDuration,
|
|
8
|
+
calculatePatternSummary,
|
|
9
|
+
} from './spirals';
|
|
10
|
+
import { calculateFlowEfficiency } from './flow';
|
|
11
|
+
|
|
12
|
+
export function analyzeCommits(commits: Commit[]): VibeCheckResult {
|
|
13
|
+
if (commits.length === 0) {
|
|
14
|
+
return emptyResult();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Sort commits by date
|
|
18
|
+
const sorted = [...commits].sort((a, b) => a.date.getTime() - b.date.getTime());
|
|
19
|
+
const from = sorted[0].date;
|
|
20
|
+
const to = sorted[sorted.length - 1].date;
|
|
21
|
+
const activeHours = calculateActiveHours(sorted);
|
|
22
|
+
|
|
23
|
+
// Count commit types
|
|
24
|
+
const commitCounts = countCommitTypes(sorted);
|
|
25
|
+
|
|
26
|
+
// Detect fix chains
|
|
27
|
+
const fixChains = detectFixChains(sorted);
|
|
28
|
+
|
|
29
|
+
// Calculate all metrics
|
|
30
|
+
const iterationVelocity = calculateIterationVelocity(sorted);
|
|
31
|
+
const reworkRatio = calculateReworkRatio(sorted);
|
|
32
|
+
const trustPassRate = calculateTrustPassRate(sorted);
|
|
33
|
+
const debugSpiralDuration = calculateDebugSpiralDuration(fixChains);
|
|
34
|
+
const flowEfficiency = calculateFlowEfficiency(activeHours * 60, fixChains);
|
|
35
|
+
|
|
36
|
+
// Calculate pattern summary
|
|
37
|
+
const patterns = calculatePatternSummary(fixChains);
|
|
38
|
+
|
|
39
|
+
// Determine overall rating
|
|
40
|
+
const overall = calculateOverallRating([
|
|
41
|
+
iterationVelocity.rating,
|
|
42
|
+
reworkRatio.rating,
|
|
43
|
+
trustPassRate.rating,
|
|
44
|
+
debugSpiralDuration.rating,
|
|
45
|
+
flowEfficiency.rating,
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
period: {
|
|
50
|
+
from,
|
|
51
|
+
to,
|
|
52
|
+
activeHours: Math.round(activeHours * 10) / 10,
|
|
53
|
+
},
|
|
54
|
+
commits: commitCounts,
|
|
55
|
+
metrics: {
|
|
56
|
+
iterationVelocity,
|
|
57
|
+
reworkRatio,
|
|
58
|
+
trustPassRate,
|
|
59
|
+
debugSpiralDuration,
|
|
60
|
+
flowEfficiency,
|
|
61
|
+
},
|
|
62
|
+
fixChains,
|
|
63
|
+
patterns,
|
|
64
|
+
overall,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function countCommitTypes(commits: Commit[]): VibeCheckResult['commits'] {
|
|
69
|
+
const counts = {
|
|
70
|
+
total: commits.length,
|
|
71
|
+
feat: 0,
|
|
72
|
+
fix: 0,
|
|
73
|
+
docs: 0,
|
|
74
|
+
other: 0,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
for (const commit of commits) {
|
|
78
|
+
switch (commit.type) {
|
|
79
|
+
case 'feat':
|
|
80
|
+
counts.feat++;
|
|
81
|
+
break;
|
|
82
|
+
case 'fix':
|
|
83
|
+
counts.fix++;
|
|
84
|
+
break;
|
|
85
|
+
case 'docs':
|
|
86
|
+
counts.docs++;
|
|
87
|
+
break;
|
|
88
|
+
default:
|
|
89
|
+
counts.other++;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return counts;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function calculateOverallRating(ratings: Rating[]): OverallRating {
|
|
97
|
+
const scores: Record<Rating, number> = {
|
|
98
|
+
elite: 4,
|
|
99
|
+
high: 3,
|
|
100
|
+
medium: 2,
|
|
101
|
+
low: 1,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const avgScore =
|
|
105
|
+
ratings.reduce((sum, r) => sum + scores[r], 0) / ratings.length;
|
|
106
|
+
|
|
107
|
+
if (avgScore >= 3.5) return 'ELITE';
|
|
108
|
+
if (avgScore >= 2.5) return 'HIGH';
|
|
109
|
+
if (avgScore >= 1.5) return 'MEDIUM';
|
|
110
|
+
return 'LOW';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function emptyResult(): VibeCheckResult {
|
|
114
|
+
return {
|
|
115
|
+
period: {
|
|
116
|
+
from: new Date(),
|
|
117
|
+
to: new Date(),
|
|
118
|
+
activeHours: 0,
|
|
119
|
+
},
|
|
120
|
+
commits: {
|
|
121
|
+
total: 0,
|
|
122
|
+
feat: 0,
|
|
123
|
+
fix: 0,
|
|
124
|
+
docs: 0,
|
|
125
|
+
other: 0,
|
|
126
|
+
},
|
|
127
|
+
metrics: {
|
|
128
|
+
iterationVelocity: {
|
|
129
|
+
value: 0,
|
|
130
|
+
unit: 'commits/hour',
|
|
131
|
+
rating: 'low',
|
|
132
|
+
description: 'No commits found',
|
|
133
|
+
},
|
|
134
|
+
reworkRatio: {
|
|
135
|
+
value: 0,
|
|
136
|
+
unit: '%',
|
|
137
|
+
rating: 'elite',
|
|
138
|
+
description: 'No commits found',
|
|
139
|
+
},
|
|
140
|
+
trustPassRate: {
|
|
141
|
+
value: 100,
|
|
142
|
+
unit: '%',
|
|
143
|
+
rating: 'elite',
|
|
144
|
+
description: 'No commits found',
|
|
145
|
+
},
|
|
146
|
+
debugSpiralDuration: {
|
|
147
|
+
value: 0,
|
|
148
|
+
unit: 'min',
|
|
149
|
+
rating: 'elite',
|
|
150
|
+
description: 'No debug spirals detected',
|
|
151
|
+
},
|
|
152
|
+
flowEfficiency: {
|
|
153
|
+
value: 100,
|
|
154
|
+
unit: '%',
|
|
155
|
+
rating: 'elite',
|
|
156
|
+
description: 'No active time recorded',
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
fixChains: [],
|
|
160
|
+
patterns: {
|
|
161
|
+
categories: {},
|
|
162
|
+
total: 0,
|
|
163
|
+
tracerAvailable: 0,
|
|
164
|
+
},
|
|
165
|
+
overall: 'HIGH',
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export { calculateActiveHours } from './velocity';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Commit, MetricResult, Rating } from '../types';
|
|
2
|
+
|
|
3
|
+
export function calculateReworkRatio(commits: Commit[]): MetricResult {
|
|
4
|
+
if (commits.length === 0) {
|
|
5
|
+
return {
|
|
6
|
+
value: 0,
|
|
7
|
+
unit: '%',
|
|
8
|
+
rating: 'elite',
|
|
9
|
+
description: 'No commits found',
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const fixCommits = commits.filter((c) => c.type === 'fix').length;
|
|
14
|
+
const ratio = (fixCommits / commits.length) * 100;
|
|
15
|
+
const rating = getRating(ratio);
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
value: Math.round(ratio),
|
|
19
|
+
unit: '%',
|
|
20
|
+
rating,
|
|
21
|
+
description: getDescription(rating, fixCommits, commits.length),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getRating(ratio: number): Rating {
|
|
26
|
+
if (ratio < 30) return 'elite';
|
|
27
|
+
if (ratio < 50) return 'high';
|
|
28
|
+
if (ratio < 70) return 'medium';
|
|
29
|
+
return 'low';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getDescription(rating: Rating, fixes: number, total: number): string {
|
|
33
|
+
const fixText = `${fixes}/${total} commits are fixes`;
|
|
34
|
+
|
|
35
|
+
switch (rating) {
|
|
36
|
+
case 'elite':
|
|
37
|
+
return `${fixText}. Mostly forward progress`;
|
|
38
|
+
case 'high':
|
|
39
|
+
return `${fixText}. Normal for complex work`;
|
|
40
|
+
case 'medium':
|
|
41
|
+
return `${fixText}. Consider validating assumptions before coding`;
|
|
42
|
+
case 'low':
|
|
43
|
+
return `${fixText}. High rework, stop and reassess approach`;
|
|
44
|
+
}
|
|
45
|
+
}
|