@klitchevo/code-council 0.0.11 → 0.0.13
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 +42 -0
- package/dist/chunk-Y77R7523.js +142 -0
- package/dist/index.js +772 -75
- package/dist/tps-audit-GNK4VIKA.js +11 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -19,6 +19,7 @@ An MCP (Model Context Protocol) server that provides AI-powered code review usin
|
|
|
19
19
|
- 📋 **Plan Review** - Review implementation plans before writing code
|
|
20
20
|
- 📝 **Git Changes Review** - Review staged, unstaged, branch diffs, or specific commits
|
|
21
21
|
- 💬 **Council Discussions** - Multi-turn conversations with the AI council for deeper exploration
|
|
22
|
+
- 🏭 **TPS Audit** - Toyota Production System analysis for flow, waste, bottlenecks, and quality
|
|
22
23
|
- ⚡ **Parallel Execution** - All models run concurrently for fast results
|
|
23
24
|
|
|
24
25
|
## Quick Start
|
|
@@ -284,6 +285,46 @@ Use discuss_with_council with session_id=<id-from-previous-response> to ask: Can
|
|
|
284
285
|
- Rate limited to 10 requests per minute per session
|
|
285
286
|
- Context windowing keeps conversations efficient
|
|
286
287
|
|
|
288
|
+
### `tps_audit`
|
|
289
|
+
|
|
290
|
+
Analyze any codebase using Toyota Production System (TPS) principles. Generates beautiful HTML reports with scores for flow, waste, bottlenecks, and quality.
|
|
291
|
+
|
|
292
|
+
**Parameters:**
|
|
293
|
+
- `path` (optional): Path to repository root (auto-detects git root if not provided)
|
|
294
|
+
- `focus_areas` (optional): Specific areas to focus on (e.g., `["flow", "security", "performance"]`)
|
|
295
|
+
- `max_files` (optional): Maximum files to analyze (default: 50, max: 100)
|
|
296
|
+
- `file_types` (optional): File extensions to include (default: common source files)
|
|
297
|
+
- `include_sensitive` (optional): Include potentially sensitive files (default: false)
|
|
298
|
+
- `output_format` (optional): `html`, `markdown`, or `json` (default: `html`)
|
|
299
|
+
|
|
300
|
+
**Example usage in Claude:**
|
|
301
|
+
```
|
|
302
|
+
Use tps_audit to analyze this repository
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
```
|
|
306
|
+
Use tps_audit with output_format=markdown and focus_areas=["security", "performance"]
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
**What it analyzes:**
|
|
310
|
+
- **Flow**: How data and control flow through the system, entry points, pathways
|
|
311
|
+
- **Muda (Waste)**: The 7 wastes - defects, overproduction, waiting, transportation, inventory, motion, extra-processing
|
|
312
|
+
- **Bottlenecks**: Where flow is constrained, severity and impact
|
|
313
|
+
- **Jidoka**: Built-in quality, fail-fast patterns, error handling
|
|
314
|
+
- **Recommendations**: Prioritized improvements with effort/impact ratings
|
|
315
|
+
|
|
316
|
+
**Security features:**
|
|
317
|
+
- Automatically skips sensitive files (`.env`, credentials, keys, tokens)
|
|
318
|
+
- Scans file contents for embedded secrets (AWS keys, GitHub PATs, etc.)
|
|
319
|
+
- Validates paths to prevent directory traversal attacks
|
|
320
|
+
- Enforces size limits to prevent resource exhaustion
|
|
321
|
+
|
|
322
|
+
**Output:**
|
|
323
|
+
Reports are saved to `.code-council/` directory:
|
|
324
|
+
- `tps-audit.html` - Interactive styled report with glass-morphism dark theme
|
|
325
|
+
- `tps-audit.md` - Markdown version
|
|
326
|
+
- `tps-audit.json` - Raw JSON data
|
|
327
|
+
|
|
287
328
|
### `list_review_config`
|
|
288
329
|
|
|
289
330
|
Show which AI models are currently configured for each review type.
|
|
@@ -300,6 +341,7 @@ You can customize which AI models are used for reviews by setting environment va
|
|
|
300
341
|
- `BACKEND_REVIEW_MODELS` - Models for backend reviews
|
|
301
342
|
- `PLAN_REVIEW_MODELS` - Models for plan reviews
|
|
302
343
|
- `DISCUSSION_MODELS` - Models for council discussions
|
|
344
|
+
- `TPS_AUDIT_MODELS` - Models for TPS codebase audits
|
|
303
345
|
- `TEMPERATURE` - Control response randomness (0.0-2.0, default: 0.3)
|
|
304
346
|
- `MAX_TOKENS` - Maximum response tokens (default: 16384)
|
|
305
347
|
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// src/prompts/tps-audit.ts
|
|
2
|
+
var SYSTEM_PROMPT = `You are an expert Toyota Production System (TPS) consultant analyzing software codebases. Your role is to "walk the production line" - examining how code flows from input to output, identifying waste (muda), spotting bottlenecks, and suggesting continuous improvement (kaizen).
|
|
3
|
+
|
|
4
|
+
## TPS Principles for Software
|
|
5
|
+
|
|
6
|
+
### 1. FLOW (Nagare)
|
|
7
|
+
Analyze how data and control flow through the system:
|
|
8
|
+
- Identify entry points and exit points
|
|
9
|
+
- Map the critical paths
|
|
10
|
+
- Look for smooth, uninterrupted flow
|
|
11
|
+
- Identify where flow is blocked or redirected
|
|
12
|
+
- Check for single-piece flow vs batch processing
|
|
13
|
+
|
|
14
|
+
### 2. WASTE (Muda) - The 7 Wastes in Software
|
|
15
|
+
Identify instances of each waste type:
|
|
16
|
+
|
|
17
|
+
**Defects**: Bugs, error-prone code, missing validation
|
|
18
|
+
**Overproduction**: Features nobody uses, over-engineered solutions
|
|
19
|
+
**Waiting**: Blocking I/O, synchronous when async would work, slow tests
|
|
20
|
+
**Non-utilized Talent**: Manual tasks that could be automated, repetitive code
|
|
21
|
+
**Transportation**: Unnecessary data transformation, excessive API calls
|
|
22
|
+
**Inventory**: Dead code, unused imports/exports, stale dependencies
|
|
23
|
+
**Motion**: Complex navigation, scattered related code, poor organization
|
|
24
|
+
**Extra-processing**: Premature optimization, unnecessary abstraction layers
|
|
25
|
+
|
|
26
|
+
### 3. BOTTLENECKS
|
|
27
|
+
Identify constraints that limit throughput:
|
|
28
|
+
- Synchronous operations that block
|
|
29
|
+
- Single points of failure
|
|
30
|
+
- Resource contention
|
|
31
|
+
- N+1 queries or API calls
|
|
32
|
+
- Sequential operations that could be parallel
|
|
33
|
+
|
|
34
|
+
### 4. PULL vs PUSH
|
|
35
|
+
Evaluate if work is demand-driven:
|
|
36
|
+
- Lazy evaluation vs eager computation
|
|
37
|
+
- On-demand loading vs preloading everything
|
|
38
|
+
- Event-driven vs polling
|
|
39
|
+
- Streaming vs buffering all data
|
|
40
|
+
|
|
41
|
+
### 5. JIDOKA (Built-in Quality)
|
|
42
|
+
Assess quality mechanisms:
|
|
43
|
+
- Error handling and recovery
|
|
44
|
+
- Validation at boundaries
|
|
45
|
+
- Fail-fast patterns
|
|
46
|
+
- Type safety usage
|
|
47
|
+
- Test coverage signals
|
|
48
|
+
|
|
49
|
+
### 6. STANDARDIZATION
|
|
50
|
+
Look for consistency:
|
|
51
|
+
- Code style consistency
|
|
52
|
+
- Pattern usage consistency
|
|
53
|
+
- Error handling patterns
|
|
54
|
+
- Naming conventions
|
|
55
|
+
- File organization
|
|
56
|
+
|
|
57
|
+
## Scoring Guidelines
|
|
58
|
+
|
|
59
|
+
**Overall Score (0-100)**:
|
|
60
|
+
- 90-100: Exceptional flow, minimal waste, excellent quality
|
|
61
|
+
- 70-89: Good practices, some waste, room for improvement
|
|
62
|
+
- 50-69: Average, significant waste or flow issues
|
|
63
|
+
- 30-49: Poor flow, excessive waste, quality concerns
|
|
64
|
+
- 0-29: Critical issues, major redesign needed
|
|
65
|
+
|
|
66
|
+
**Flow Score**: How smoothly does data/control move through the system?
|
|
67
|
+
**Waste Score**: Higher = less waste (100 = no waste identified)
|
|
68
|
+
**Quality Score**: Built-in quality mechanisms, error handling, type safety
|
|
69
|
+
|
|
70
|
+
## Output Requirements
|
|
71
|
+
|
|
72
|
+
You MUST respond with valid JSON matching the TpsAnalysis interface. Do not include any text before or after the JSON.
|
|
73
|
+
|
|
74
|
+
Focus on:
|
|
75
|
+
1. Actionable findings with specific file/line references
|
|
76
|
+
2. Prioritized recommendations (quick wins first)
|
|
77
|
+
3. Concrete suggestions, not vague advice
|
|
78
|
+
4. Balanced assessment - acknowledge strengths too
|
|
79
|
+
5. Effort estimates for recommendations`;
|
|
80
|
+
function buildUserMessage(aggregatedContent, options) {
|
|
81
|
+
const parts = [];
|
|
82
|
+
if (options?.repoName) {
|
|
83
|
+
parts.push(`## Repository: ${options.repoName}`);
|
|
84
|
+
}
|
|
85
|
+
if (options?.focusAreas && options.focusAreas.length > 0) {
|
|
86
|
+
parts.push(
|
|
87
|
+
`## Focus Areas
|
|
88
|
+
Pay special attention to: ${options.focusAreas.join(", ")}`
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
if (options?.additionalContext) {
|
|
92
|
+
parts.push(`## Additional Context
|
|
93
|
+
${options.additionalContext}`);
|
|
94
|
+
}
|
|
95
|
+
parts.push(`## Codebase to Audit
|
|
96
|
+
|
|
97
|
+
Analyze this codebase using Toyota Production System principles. Walk the production line from entry points through to outputs. Identify waste, bottlenecks, and improvement opportunities.
|
|
98
|
+
|
|
99
|
+
${aggregatedContent}
|
|
100
|
+
|
|
101
|
+
## Response Format
|
|
102
|
+
|
|
103
|
+
Respond with ONLY valid JSON matching the TpsAnalysis interface. Include:
|
|
104
|
+
- Scores for overall, flow, waste, and quality (0-100)
|
|
105
|
+
- Flow analysis with entry points and pathways
|
|
106
|
+
- Specific bottlenecks with locations and suggestions
|
|
107
|
+
- Waste items categorized by the 7 types
|
|
108
|
+
- Jidoka (built-in quality) assessment
|
|
109
|
+
- Prioritized recommendations
|
|
110
|
+
- Summary with strengths, concerns, and quick wins
|
|
111
|
+
|
|
112
|
+
Your JSON response:`);
|
|
113
|
+
return parts.join("\n\n");
|
|
114
|
+
}
|
|
115
|
+
function parseTpsAnalysis(response) {
|
|
116
|
+
try {
|
|
117
|
+
let jsonStr = response.trim();
|
|
118
|
+
if (jsonStr.startsWith("```json")) {
|
|
119
|
+
jsonStr = jsonStr.slice(7);
|
|
120
|
+
} else if (jsonStr.startsWith("```")) {
|
|
121
|
+
jsonStr = jsonStr.slice(3);
|
|
122
|
+
}
|
|
123
|
+
if (jsonStr.endsWith("```")) {
|
|
124
|
+
jsonStr = jsonStr.slice(0, -3);
|
|
125
|
+
}
|
|
126
|
+
jsonStr = jsonStr.trim();
|
|
127
|
+
const parsed = JSON.parse(jsonStr);
|
|
128
|
+
if (typeof parsed.scores?.overall !== "number" || !Array.isArray(parsed.bottlenecks) || !Array.isArray(parsed.recommendations)) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
return parsed;
|
|
132
|
+
} catch {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export {
|
|
138
|
+
SYSTEM_PROMPT,
|
|
139
|
+
buildUserMessage,
|
|
140
|
+
parseTpsAnalysis
|
|
141
|
+
};
|
|
142
|
+
//# sourceMappingURL=chunk-Y77R7523.js.map
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
SYSTEM_PROMPT,
|
|
4
|
+
buildUserMessage
|
|
5
|
+
} from "./chunk-Y77R7523.js";
|
|
2
6
|
|
|
3
7
|
// src/index.ts
|
|
4
8
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -77,64 +81,10 @@ var DISCUSSION_MODELS = parseModels(
|
|
|
77
81
|
process.env.DISCUSSION_MODELS,
|
|
78
82
|
DEFAULT_MODELS
|
|
79
83
|
);
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
debugEnabled = process.env.DEBUG === "true";
|
|
85
|
-
log(level, message, context) {
|
|
86
|
-
const logEntry = {
|
|
87
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
88
|
-
level,
|
|
89
|
-
message,
|
|
90
|
-
...context
|
|
91
|
-
};
|
|
92
|
-
if (this.isDevelopment) {
|
|
93
|
-
const emoji = {
|
|
94
|
-
debug: "\u{1F50D}",
|
|
95
|
-
info: "\u2139\uFE0F",
|
|
96
|
-
warn: "\u26A0\uFE0F",
|
|
97
|
-
error: "\u274C"
|
|
98
|
-
}[level];
|
|
99
|
-
console.error(
|
|
100
|
-
`${emoji} [${level.toUpperCase()}] ${message}`,
|
|
101
|
-
context ? JSON.stringify(context, null, 2) : ""
|
|
102
|
-
);
|
|
103
|
-
} else {
|
|
104
|
-
console.error(JSON.stringify(logEntry));
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
debug(message, context) {
|
|
108
|
-
if (this.debugEnabled) {
|
|
109
|
-
this.log("debug", message, context);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
info(message, context) {
|
|
113
|
-
this.log("info", message, context);
|
|
114
|
-
}
|
|
115
|
-
warn(message, context) {
|
|
116
|
-
this.log("warn", message, context);
|
|
117
|
-
}
|
|
118
|
-
error(message, error, context) {
|
|
119
|
-
const errorContext = {
|
|
120
|
-
...context
|
|
121
|
-
};
|
|
122
|
-
if (error instanceof Error) {
|
|
123
|
-
errorContext.error = {
|
|
124
|
-
name: error.name,
|
|
125
|
-
message: error.message,
|
|
126
|
-
stack: error.stack
|
|
127
|
-
};
|
|
128
|
-
} else if (error) {
|
|
129
|
-
errorContext.error = error;
|
|
130
|
-
}
|
|
131
|
-
this.log("error", message, errorContext);
|
|
132
|
-
}
|
|
133
|
-
};
|
|
134
|
-
var logger = new Logger();
|
|
135
|
-
|
|
136
|
-
// src/review-client.ts
|
|
137
|
-
import { OpenRouter } from "@openrouter/sdk";
|
|
84
|
+
var TPS_AUDIT_MODELS = parseModels(
|
|
85
|
+
process.env.TPS_AUDIT_MODELS,
|
|
86
|
+
DEFAULT_MODELS
|
|
87
|
+
);
|
|
138
88
|
|
|
139
89
|
// src/errors.ts
|
|
140
90
|
var AppError = class extends Error {
|
|
@@ -198,9 +148,67 @@ function formatError(error) {
|
|
|
198
148
|
};
|
|
199
149
|
}
|
|
200
150
|
|
|
151
|
+
// src/logger.ts
|
|
152
|
+
var Logger = class {
|
|
153
|
+
isDevelopment = process.env.NODE_ENV === "development";
|
|
154
|
+
debugEnabled = process.env.DEBUG === "true";
|
|
155
|
+
log(level, message, context) {
|
|
156
|
+
const logEntry = {
|
|
157
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
158
|
+
level,
|
|
159
|
+
message,
|
|
160
|
+
...context
|
|
161
|
+
};
|
|
162
|
+
if (this.isDevelopment) {
|
|
163
|
+
const emoji = {
|
|
164
|
+
debug: "\u{1F50D}",
|
|
165
|
+
info: "\u2139\uFE0F",
|
|
166
|
+
warn: "\u26A0\uFE0F",
|
|
167
|
+
error: "\u274C"
|
|
168
|
+
}[level];
|
|
169
|
+
console.error(
|
|
170
|
+
`${emoji} [${level.toUpperCase()}] ${message}`,
|
|
171
|
+
context ? JSON.stringify(context, null, 2) : ""
|
|
172
|
+
);
|
|
173
|
+
} else {
|
|
174
|
+
console.error(JSON.stringify(logEntry));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
debug(message, context) {
|
|
178
|
+
if (this.debugEnabled) {
|
|
179
|
+
this.log("debug", message, context);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
info(message, context) {
|
|
183
|
+
this.log("info", message, context);
|
|
184
|
+
}
|
|
185
|
+
warn(message, context) {
|
|
186
|
+
this.log("warn", message, context);
|
|
187
|
+
}
|
|
188
|
+
error(message, error, context) {
|
|
189
|
+
const errorContext = {
|
|
190
|
+
...context
|
|
191
|
+
};
|
|
192
|
+
if (error instanceof Error) {
|
|
193
|
+
errorContext.error = {
|
|
194
|
+
name: error.name,
|
|
195
|
+
message: error.message,
|
|
196
|
+
stack: error.stack
|
|
197
|
+
};
|
|
198
|
+
} else if (error) {
|
|
199
|
+
errorContext.error = error;
|
|
200
|
+
}
|
|
201
|
+
this.log("error", message, errorContext);
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
var logger = new Logger();
|
|
205
|
+
|
|
206
|
+
// src/review-client.ts
|
|
207
|
+
import { OpenRouter } from "@openrouter/sdk";
|
|
208
|
+
|
|
201
209
|
// src/prompts/backend-review.ts
|
|
202
|
-
var
|
|
203
|
-
function
|
|
210
|
+
var SYSTEM_PROMPT2 = `You are an expert backend developer and security specialist. Review backend code for security, performance, and architecture.`;
|
|
211
|
+
function buildUserMessage2(code, reviewType = "full", language, context) {
|
|
204
212
|
const focusArea = getFocusArea(reviewType);
|
|
205
213
|
const languageContext = language ? `Language/Framework: ${language}
|
|
206
214
|
` : "";
|
|
@@ -227,7 +235,7 @@ function getFocusArea(reviewType) {
|
|
|
227
235
|
}
|
|
228
236
|
|
|
229
237
|
// src/prompts/code-review.ts
|
|
230
|
-
var
|
|
238
|
+
var SYSTEM_PROMPT3 = `You are an expert code reviewer. Analyze the code for:
|
|
231
239
|
- Code quality and best practices
|
|
232
240
|
- Potential bugs and edge cases
|
|
233
241
|
- Performance issues
|
|
@@ -235,7 +243,7 @@ var SYSTEM_PROMPT2 = `You are an expert code reviewer. Analyze the code for:
|
|
|
235
243
|
- Maintainability concerns
|
|
236
244
|
|
|
237
245
|
Provide specific, actionable feedback.`;
|
|
238
|
-
function
|
|
246
|
+
function buildUserMessage3(code, context) {
|
|
239
247
|
if (context) {
|
|
240
248
|
return `${context}
|
|
241
249
|
|
|
@@ -251,8 +259,8 @@ ${code}
|
|
|
251
259
|
}
|
|
252
260
|
|
|
253
261
|
// src/prompts/frontend-review.ts
|
|
254
|
-
var
|
|
255
|
-
function
|
|
262
|
+
var SYSTEM_PROMPT4 = `You are an expert frontend developer and UX specialist. Review frontend code for best practices.`;
|
|
263
|
+
function buildUserMessage4(code, reviewType = "full", framework, context) {
|
|
256
264
|
const focusArea = getFocusArea2(reviewType);
|
|
257
265
|
const frameworkContext = framework ? `Framework: ${framework}
|
|
258
266
|
` : "";
|
|
@@ -279,8 +287,8 @@ function getFocusArea2(reviewType) {
|
|
|
279
287
|
}
|
|
280
288
|
|
|
281
289
|
// src/prompts/plan-review.ts
|
|
282
|
-
var
|
|
283
|
-
function
|
|
290
|
+
var SYSTEM_PROMPT5 = `You are an expert software architect and project planner. Review implementation plans before code is written to catch issues early.`;
|
|
291
|
+
function buildUserMessage5(plan, reviewType = "full", context) {
|
|
284
292
|
const focusArea = getFocusArea3(reviewType);
|
|
285
293
|
const additionalContext = context ? `${context}
|
|
286
294
|
` : "";
|
|
@@ -380,17 +388,17 @@ var ReviewClient = class {
|
|
|
380
388
|
* @returns Array of review results from each model
|
|
381
389
|
*/
|
|
382
390
|
async reviewCode(code, models, context) {
|
|
383
|
-
const userMessage =
|
|
391
|
+
const userMessage = buildUserMessage3(code, context);
|
|
384
392
|
return executeInParallel(
|
|
385
393
|
models,
|
|
386
|
-
(model) => this.chat(model,
|
|
394
|
+
(model) => this.chat(model, SYSTEM_PROMPT3, userMessage)
|
|
387
395
|
);
|
|
388
396
|
}
|
|
389
397
|
/**
|
|
390
398
|
* Review frontend code for accessibility, performance, and UX
|
|
391
399
|
*/
|
|
392
400
|
async reviewFrontend(code, models, options) {
|
|
393
|
-
const userMessage =
|
|
401
|
+
const userMessage = buildUserMessage4(
|
|
394
402
|
code,
|
|
395
403
|
options?.reviewType || "full",
|
|
396
404
|
options?.framework,
|
|
@@ -398,14 +406,14 @@ var ReviewClient = class {
|
|
|
398
406
|
);
|
|
399
407
|
return executeInParallel(
|
|
400
408
|
models,
|
|
401
|
-
(model) => this.chat(model,
|
|
409
|
+
(model) => this.chat(model, SYSTEM_PROMPT4, userMessage)
|
|
402
410
|
);
|
|
403
411
|
}
|
|
404
412
|
/**
|
|
405
413
|
* Review backend code for security, performance, and architecture
|
|
406
414
|
*/
|
|
407
415
|
async reviewBackend(code, models, options) {
|
|
408
|
-
const userMessage =
|
|
416
|
+
const userMessage = buildUserMessage2(
|
|
409
417
|
code,
|
|
410
418
|
options?.reviewType || "full",
|
|
411
419
|
options?.language,
|
|
@@ -413,21 +421,21 @@ var ReviewClient = class {
|
|
|
413
421
|
);
|
|
414
422
|
return executeInParallel(
|
|
415
423
|
models,
|
|
416
|
-
(model) => this.chat(model,
|
|
424
|
+
(model) => this.chat(model, SYSTEM_PROMPT2, userMessage)
|
|
417
425
|
);
|
|
418
426
|
}
|
|
419
427
|
/**
|
|
420
428
|
* Review implementation plans before code is written
|
|
421
429
|
*/
|
|
422
430
|
async reviewPlan(plan, models, options) {
|
|
423
|
-
const userMessage =
|
|
431
|
+
const userMessage = buildUserMessage5(
|
|
424
432
|
plan,
|
|
425
433
|
options?.reviewType || "full",
|
|
426
434
|
options?.context
|
|
427
435
|
);
|
|
428
436
|
return executeInParallel(
|
|
429
437
|
models,
|
|
430
|
-
(model) => this.chat(model,
|
|
438
|
+
(model) => this.chat(model, SYSTEM_PROMPT5, userMessage)
|
|
431
439
|
);
|
|
432
440
|
}
|
|
433
441
|
/**
|
|
@@ -508,6 +516,30 @@ var ReviewClient = class {
|
|
|
508
516
|
return this.chatMultiTurn(model, messages);
|
|
509
517
|
});
|
|
510
518
|
}
|
|
519
|
+
/**
|
|
520
|
+
* Perform TPS (Toyota Production System) audit on aggregated codebase content
|
|
521
|
+
* Analyzes code for flow, waste, bottlenecks, and quality using TPS principles
|
|
522
|
+
*
|
|
523
|
+
* @param aggregatedContent - Aggregated file contents from repo scanner
|
|
524
|
+
* @param models - Array of model identifiers to use
|
|
525
|
+
* @param options - Optional configuration
|
|
526
|
+
* @returns Array of TPS analysis results from each model
|
|
527
|
+
*/
|
|
528
|
+
async tpsAudit(aggregatedContent, models, options) {
|
|
529
|
+
const userMessage = buildUserMessage(aggregatedContent, {
|
|
530
|
+
focusAreas: options?.focusAreas,
|
|
531
|
+
repoName: options?.repoName
|
|
532
|
+
});
|
|
533
|
+
logger.debug("Starting TPS audit", {
|
|
534
|
+
contentLength: aggregatedContent.length,
|
|
535
|
+
modelCount: models.length,
|
|
536
|
+
focusAreas: options?.focusAreas
|
|
537
|
+
});
|
|
538
|
+
return executeInParallel(
|
|
539
|
+
models,
|
|
540
|
+
(model) => this.chat(model, SYSTEM_PROMPT, userMessage)
|
|
541
|
+
);
|
|
542
|
+
}
|
|
511
543
|
};
|
|
512
544
|
|
|
513
545
|
// src/session/in-memory-store.ts
|
|
@@ -982,6 +1014,9 @@ async function handleDiscussCouncil(client2, input, sessionStore2) {
|
|
|
982
1014
|
}
|
|
983
1015
|
|
|
984
1016
|
// src/tools/factory.ts
|
|
1017
|
+
import { readFileSync } from "fs";
|
|
1018
|
+
import { dirname, join } from "path";
|
|
1019
|
+
import { fileURLToPath } from "url";
|
|
985
1020
|
function formatResults(results) {
|
|
986
1021
|
return results.map((r) => {
|
|
987
1022
|
if (r.error) {
|
|
@@ -994,6 +1029,39 @@ function formatResults(results) {
|
|
|
994
1029
|
${r.review}`;
|
|
995
1030
|
}).join("\n\n---\n\n");
|
|
996
1031
|
}
|
|
1032
|
+
function formatResultsAsHtml(results, templatePath, data = {}) {
|
|
1033
|
+
try {
|
|
1034
|
+
let template = readFileSync(templatePath, "utf-8");
|
|
1035
|
+
const modelPerspectives = results.map((r) => ({
|
|
1036
|
+
model: r.model,
|
|
1037
|
+
content: r.error ? `Error: ${r.error}` : r.review,
|
|
1038
|
+
hasError: !!r.error
|
|
1039
|
+
}));
|
|
1040
|
+
const reportData = {
|
|
1041
|
+
analysis: data.analysis || null,
|
|
1042
|
+
repoName: data.repoName || "Unknown Repository",
|
|
1043
|
+
modelPerspectives,
|
|
1044
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1045
|
+
};
|
|
1046
|
+
template = template.replace(
|
|
1047
|
+
"{{REPORT_DATA}}",
|
|
1048
|
+
JSON.stringify(reportData, null, 2)
|
|
1049
|
+
);
|
|
1050
|
+
return template;
|
|
1051
|
+
} catch (error) {
|
|
1052
|
+
logger.error("Failed to generate HTML report", error);
|
|
1053
|
+
return formatResults(results);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
function getTemplatesDir() {
|
|
1057
|
+
try {
|
|
1058
|
+
const __filename2 = fileURLToPath(import.meta.url);
|
|
1059
|
+
const __dirname2 = dirname(__filename2);
|
|
1060
|
+
return join(__dirname2, "..", "..", "templates");
|
|
1061
|
+
} catch {
|
|
1062
|
+
return join(process.cwd(), "templates");
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
997
1065
|
function createReviewTool(server2, config) {
|
|
998
1066
|
server2.registerTool(
|
|
999
1067
|
config.name,
|
|
@@ -1239,6 +1307,598 @@ async function handlePlanReview(client2, input) {
|
|
|
1239
1307
|
};
|
|
1240
1308
|
}
|
|
1241
1309
|
|
|
1310
|
+
// src/tools/tps-audit.ts
|
|
1311
|
+
import { join as join4 } from "path";
|
|
1312
|
+
import { z as z7 } from "zod";
|
|
1313
|
+
|
|
1314
|
+
// src/utils/repo-scanner.ts
|
|
1315
|
+
import { readdirSync, readFileSync as readFileSync2, statSync } from "fs";
|
|
1316
|
+
import { extname, join as join3, relative as relative2 } from "path";
|
|
1317
|
+
import ignore from "ignore";
|
|
1318
|
+
|
|
1319
|
+
// src/utils/git-operations.ts
|
|
1320
|
+
import { execSync as execSync2 } from "child_process";
|
|
1321
|
+
import { existsSync, lstatSync, realpathSync } from "fs";
|
|
1322
|
+
import { dirname as dirname2, join as join2, normalize, relative, resolve } from "path";
|
|
1323
|
+
var MAX_TRAVERSAL_DEPTH = 20;
|
|
1324
|
+
function findGitRoot(startPath) {
|
|
1325
|
+
let currentPath = resolve(startPath);
|
|
1326
|
+
let depth = 0;
|
|
1327
|
+
while (depth < MAX_TRAVERSAL_DEPTH) {
|
|
1328
|
+
const gitPath = join2(currentPath, ".git");
|
|
1329
|
+
if (existsSync(gitPath)) {
|
|
1330
|
+
logger.debug("Found git root", { path: currentPath, depth });
|
|
1331
|
+
return currentPath;
|
|
1332
|
+
}
|
|
1333
|
+
const parentPath = dirname2(currentPath);
|
|
1334
|
+
if (parentPath === currentPath) {
|
|
1335
|
+
throw new Error(
|
|
1336
|
+
`Not in a git repository. Searched from ${startPath} to filesystem root.`
|
|
1337
|
+
);
|
|
1338
|
+
}
|
|
1339
|
+
currentPath = parentPath;
|
|
1340
|
+
depth++;
|
|
1341
|
+
}
|
|
1342
|
+
throw new Error(
|
|
1343
|
+
`Max directory traversal depth (${MAX_TRAVERSAL_DEPTH}) exceeded while searching for git root.`
|
|
1344
|
+
);
|
|
1345
|
+
}
|
|
1346
|
+
function isInsideRepo(filePath, repoRoot) {
|
|
1347
|
+
try {
|
|
1348
|
+
const normalizedRepo = normalize(resolve(repoRoot));
|
|
1349
|
+
const normalizedFile = normalize(resolve(filePath));
|
|
1350
|
+
const relativePath = relative(normalizedRepo, normalizedFile);
|
|
1351
|
+
if (relativePath.startsWith("..") || resolve(relativePath) === relativePath) {
|
|
1352
|
+
return false;
|
|
1353
|
+
}
|
|
1354
|
+
return true;
|
|
1355
|
+
} catch {
|
|
1356
|
+
return false;
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
function resolveAndValidatePath(filePath, repoRoot) {
|
|
1360
|
+
try {
|
|
1361
|
+
const absolutePath = resolve(repoRoot, filePath);
|
|
1362
|
+
const stats = lstatSync(absolutePath);
|
|
1363
|
+
if (stats.isSymbolicLink()) {
|
|
1364
|
+
const realPath = realpathSync(absolutePath);
|
|
1365
|
+
if (!isInsideRepo(realPath, repoRoot)) {
|
|
1366
|
+
logger.warn("Symlink target outside repository", {
|
|
1367
|
+
symlink: filePath,
|
|
1368
|
+
target: realPath,
|
|
1369
|
+
repo: repoRoot
|
|
1370
|
+
});
|
|
1371
|
+
return null;
|
|
1372
|
+
}
|
|
1373
|
+
return realPath;
|
|
1374
|
+
}
|
|
1375
|
+
if (!isInsideRepo(absolutePath, repoRoot)) {
|
|
1376
|
+
return null;
|
|
1377
|
+
}
|
|
1378
|
+
return absolutePath;
|
|
1379
|
+
} catch (error) {
|
|
1380
|
+
logger.debug("Path resolution failed", { filePath, error });
|
|
1381
|
+
return null;
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
// src/utils/repo-scanner.ts
|
|
1386
|
+
var SENSITIVE_FILE_PATTERNS = [
|
|
1387
|
+
".env",
|
|
1388
|
+
".env.*",
|
|
1389
|
+
"*.pem",
|
|
1390
|
+
"*.key",
|
|
1391
|
+
"*.p12",
|
|
1392
|
+
"*.pfx",
|
|
1393
|
+
"*.crt",
|
|
1394
|
+
"*credentials*",
|
|
1395
|
+
"*secret*",
|
|
1396
|
+
"id_rsa*",
|
|
1397
|
+
"id_ed25519*",
|
|
1398
|
+
"id_dsa*",
|
|
1399
|
+
"id_ecdsa*",
|
|
1400
|
+
".npmrc",
|
|
1401
|
+
".pypirc",
|
|
1402
|
+
"kubeconfig",
|
|
1403
|
+
".kube/config",
|
|
1404
|
+
".docker/config.json",
|
|
1405
|
+
"*password*",
|
|
1406
|
+
"*token*",
|
|
1407
|
+
"auth.json",
|
|
1408
|
+
".netrc",
|
|
1409
|
+
".git-credentials",
|
|
1410
|
+
"*.keystore",
|
|
1411
|
+
"*.jks",
|
|
1412
|
+
"service-account*.json",
|
|
1413
|
+
"gcloud*.json"
|
|
1414
|
+
];
|
|
1415
|
+
var SECRET_CONTENT_PATTERNS = [
|
|
1416
|
+
/AKIA[0-9A-Z]{16}/g,
|
|
1417
|
+
// AWS Access Key ID
|
|
1418
|
+
/-----BEGIN\s+(RSA |DSA |EC |OPENSSH )?PRIVATE KEY-----/g,
|
|
1419
|
+
// Private keys
|
|
1420
|
+
/-----BEGIN\s+PGP PRIVATE KEY BLOCK-----/g,
|
|
1421
|
+
// PGP private key
|
|
1422
|
+
/ghp_[a-zA-Z0-9]{36}/g,
|
|
1423
|
+
// GitHub Personal Access Token
|
|
1424
|
+
/gho_[a-zA-Z0-9]{36}/g,
|
|
1425
|
+
// GitHub OAuth Token
|
|
1426
|
+
/ghs_[a-zA-Z0-9]{36}/g,
|
|
1427
|
+
// GitHub Server Token
|
|
1428
|
+
/ghu_[a-zA-Z0-9]{36}/g,
|
|
1429
|
+
// GitHub User Token
|
|
1430
|
+
/sk-[a-zA-Z0-9]{48}/g,
|
|
1431
|
+
// OpenAI API Key
|
|
1432
|
+
/sk-proj-[a-zA-Z0-9]{48}/g,
|
|
1433
|
+
// OpenAI Project Key
|
|
1434
|
+
/xox[baprs]-[0-9]{10,13}-[0-9]{10,13}-[a-zA-Z0-9]{24}/g,
|
|
1435
|
+
// Slack tokens
|
|
1436
|
+
/eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]*/g,
|
|
1437
|
+
// JWT tokens
|
|
1438
|
+
/AIza[0-9A-Za-z_-]{35}/g,
|
|
1439
|
+
// Google API Key
|
|
1440
|
+
/[0-9a-f]{32}-us[0-9]+/g,
|
|
1441
|
+
// Mailchimp API Key
|
|
1442
|
+
/SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}/g,
|
|
1443
|
+
// SendGrid API Key
|
|
1444
|
+
/sq0[a-z]{3}-[0-9A-Za-z_-]{22}/g
|
|
1445
|
+
// Square tokens
|
|
1446
|
+
];
|
|
1447
|
+
var DEFAULT_FILE_EXTENSIONS = [
|
|
1448
|
+
".ts",
|
|
1449
|
+
".tsx",
|
|
1450
|
+
".js",
|
|
1451
|
+
".jsx",
|
|
1452
|
+
".mjs",
|
|
1453
|
+
".cjs",
|
|
1454
|
+
".py",
|
|
1455
|
+
".go",
|
|
1456
|
+
".rs",
|
|
1457
|
+
".java",
|
|
1458
|
+
".kt",
|
|
1459
|
+
".scala",
|
|
1460
|
+
".rb",
|
|
1461
|
+
".php",
|
|
1462
|
+
".cs",
|
|
1463
|
+
".cpp",
|
|
1464
|
+
".c",
|
|
1465
|
+
".h",
|
|
1466
|
+
".hpp",
|
|
1467
|
+
".swift",
|
|
1468
|
+
".vue",
|
|
1469
|
+
".svelte",
|
|
1470
|
+
".astro"
|
|
1471
|
+
];
|
|
1472
|
+
var EXCLUDED_DIRS = [
|
|
1473
|
+
"node_modules",
|
|
1474
|
+
".git",
|
|
1475
|
+
"dist",
|
|
1476
|
+
"build",
|
|
1477
|
+
"out",
|
|
1478
|
+
".next",
|
|
1479
|
+
".nuxt",
|
|
1480
|
+
"__pycache__",
|
|
1481
|
+
".pytest_cache",
|
|
1482
|
+
".mypy_cache",
|
|
1483
|
+
"venv",
|
|
1484
|
+
".venv",
|
|
1485
|
+
"env",
|
|
1486
|
+
".env",
|
|
1487
|
+
"vendor",
|
|
1488
|
+
"target",
|
|
1489
|
+
".idea",
|
|
1490
|
+
".vscode",
|
|
1491
|
+
"coverage",
|
|
1492
|
+
".nyc_output"
|
|
1493
|
+
];
|
|
1494
|
+
var HARD_LIMITS = {
|
|
1495
|
+
MAX_FILES: 100,
|
|
1496
|
+
MAX_FILE_SIZE: 100 * 1024,
|
|
1497
|
+
// 100KB per file
|
|
1498
|
+
MAX_TOTAL_SIZE: 1024 * 1024
|
|
1499
|
+
// 1MB total
|
|
1500
|
+
};
|
|
1501
|
+
var DEFAULT_OPTIONS = {
|
|
1502
|
+
maxFiles: 50,
|
|
1503
|
+
maxFileSize: 50 * 1024,
|
|
1504
|
+
// 50KB
|
|
1505
|
+
maxTotalSize: 500 * 1024,
|
|
1506
|
+
// 500KB
|
|
1507
|
+
fileTypes: DEFAULT_FILE_EXTENSIONS,
|
|
1508
|
+
skipSensitive: true,
|
|
1509
|
+
detectSecrets: true
|
|
1510
|
+
};
|
|
1511
|
+
function isSensitiveFile(filename) {
|
|
1512
|
+
const lowerName = filename.toLowerCase();
|
|
1513
|
+
for (const pattern of SENSITIVE_FILE_PATTERNS) {
|
|
1514
|
+
if (pattern.includes("*")) {
|
|
1515
|
+
const regex = new RegExp(
|
|
1516
|
+
"^" + pattern.replace(/\./g, "\\.").replace(/\*/g, ".*") + "$",
|
|
1517
|
+
"i"
|
|
1518
|
+
);
|
|
1519
|
+
if (regex.test(lowerName)) {
|
|
1520
|
+
return true;
|
|
1521
|
+
}
|
|
1522
|
+
} else if (lowerName === pattern.toLowerCase() || lowerName.endsWith(`/${pattern.toLowerCase()}`)) {
|
|
1523
|
+
return true;
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
return false;
|
|
1527
|
+
}
|
|
1528
|
+
function detectEmbeddedSecrets(content) {
|
|
1529
|
+
const detected = [];
|
|
1530
|
+
const secretTypes = [
|
|
1531
|
+
{ pattern: SECRET_CONTENT_PATTERNS[0], name: "AWS Access Key" },
|
|
1532
|
+
{ pattern: SECRET_CONTENT_PATTERNS[1], name: "Private Key" },
|
|
1533
|
+
{ pattern: SECRET_CONTENT_PATTERNS[2], name: "PGP Private Key" },
|
|
1534
|
+
{ pattern: SECRET_CONTENT_PATTERNS[3], name: "GitHub PAT" },
|
|
1535
|
+
{ pattern: SECRET_CONTENT_PATTERNS[4], name: "GitHub OAuth Token" },
|
|
1536
|
+
{ pattern: SECRET_CONTENT_PATTERNS[5], name: "GitHub Server Token" },
|
|
1537
|
+
{ pattern: SECRET_CONTENT_PATTERNS[6], name: "GitHub User Token" },
|
|
1538
|
+
{ pattern: SECRET_CONTENT_PATTERNS[7], name: "OpenAI API Key" },
|
|
1539
|
+
{ pattern: SECRET_CONTENT_PATTERNS[8], name: "OpenAI Project Key" },
|
|
1540
|
+
{ pattern: SECRET_CONTENT_PATTERNS[9], name: "Slack Token" },
|
|
1541
|
+
{ pattern: SECRET_CONTENT_PATTERNS[10], name: "JWT Token" },
|
|
1542
|
+
{ pattern: SECRET_CONTENT_PATTERNS[11], name: "Google API Key" },
|
|
1543
|
+
{ pattern: SECRET_CONTENT_PATTERNS[12], name: "Mailchimp API Key" },
|
|
1544
|
+
{ pattern: SECRET_CONTENT_PATTERNS[13], name: "SendGrid API Key" },
|
|
1545
|
+
{ pattern: SECRET_CONTENT_PATTERNS[14], name: "Square Token" }
|
|
1546
|
+
];
|
|
1547
|
+
for (const { pattern, name } of secretTypes) {
|
|
1548
|
+
if (!pattern) continue;
|
|
1549
|
+
pattern.lastIndex = 0;
|
|
1550
|
+
if (pattern.test(content)) {
|
|
1551
|
+
detected.push(name);
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
return detected;
|
|
1555
|
+
}
|
|
1556
|
+
function isBinaryContent(content) {
|
|
1557
|
+
for (let i = 0; i < Math.min(content.length, 8e3); i++) {
|
|
1558
|
+
if (content[i] === 0) {
|
|
1559
|
+
return true;
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
return false;
|
|
1563
|
+
}
|
|
1564
|
+
function estimateTokens(content) {
|
|
1565
|
+
return Math.ceil(content.length / 4);
|
|
1566
|
+
}
|
|
1567
|
+
async function scanRepository(startPath, options = {}) {
|
|
1568
|
+
const opts = {
|
|
1569
|
+
...DEFAULT_OPTIONS,
|
|
1570
|
+
...options,
|
|
1571
|
+
maxFiles: Math.min(
|
|
1572
|
+
options.maxFiles ?? DEFAULT_OPTIONS.maxFiles,
|
|
1573
|
+
HARD_LIMITS.MAX_FILES
|
|
1574
|
+
),
|
|
1575
|
+
maxFileSize: Math.min(
|
|
1576
|
+
options.maxFileSize ?? DEFAULT_OPTIONS.maxFileSize,
|
|
1577
|
+
HARD_LIMITS.MAX_FILE_SIZE
|
|
1578
|
+
),
|
|
1579
|
+
maxTotalSize: Math.min(
|
|
1580
|
+
options.maxTotalSize ?? DEFAULT_OPTIONS.maxTotalSize,
|
|
1581
|
+
HARD_LIMITS.MAX_TOTAL_SIZE
|
|
1582
|
+
)
|
|
1583
|
+
};
|
|
1584
|
+
const repoRoot = findGitRoot(startPath);
|
|
1585
|
+
logger.info("Scanning repository", { repoRoot, options: opts });
|
|
1586
|
+
const ig = ignore();
|
|
1587
|
+
try {
|
|
1588
|
+
const gitignorePath = join3(repoRoot, ".gitignore");
|
|
1589
|
+
const gitignoreContent = readFileSync2(gitignorePath, "utf-8");
|
|
1590
|
+
ig.add(gitignoreContent);
|
|
1591
|
+
} catch {
|
|
1592
|
+
}
|
|
1593
|
+
ig.add(EXCLUDED_DIRS);
|
|
1594
|
+
const files = [];
|
|
1595
|
+
const skipped = [];
|
|
1596
|
+
const warnings = [];
|
|
1597
|
+
let totalSize = 0;
|
|
1598
|
+
let totalFilesFound = 0;
|
|
1599
|
+
function scanDir(dirPath, depth = 0) {
|
|
1600
|
+
if (depth > 20) {
|
|
1601
|
+
return;
|
|
1602
|
+
}
|
|
1603
|
+
if (files.length >= opts.maxFiles) {
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1606
|
+
let entries;
|
|
1607
|
+
try {
|
|
1608
|
+
entries = readdirSync(dirPath);
|
|
1609
|
+
} catch {
|
|
1610
|
+
return;
|
|
1611
|
+
}
|
|
1612
|
+
for (const entry of entries) {
|
|
1613
|
+
if (files.length >= opts.maxFiles) {
|
|
1614
|
+
break;
|
|
1615
|
+
}
|
|
1616
|
+
const fullPath = join3(dirPath, entry);
|
|
1617
|
+
const relativePath = relative2(repoRoot, fullPath);
|
|
1618
|
+
if (ig.ignores(relativePath)) {
|
|
1619
|
+
continue;
|
|
1620
|
+
}
|
|
1621
|
+
const validPath = resolveAndValidatePath(fullPath, repoRoot);
|
|
1622
|
+
if (!validPath) {
|
|
1623
|
+
skipped.push({
|
|
1624
|
+
path: relativePath,
|
|
1625
|
+
reason: "Path outside repository or invalid symlink"
|
|
1626
|
+
});
|
|
1627
|
+
continue;
|
|
1628
|
+
}
|
|
1629
|
+
let stats2;
|
|
1630
|
+
try {
|
|
1631
|
+
stats2 = statSync(validPath);
|
|
1632
|
+
} catch {
|
|
1633
|
+
continue;
|
|
1634
|
+
}
|
|
1635
|
+
if (stats2.isDirectory()) {
|
|
1636
|
+
scanDir(fullPath, depth + 1);
|
|
1637
|
+
} else if (stats2.isFile()) {
|
|
1638
|
+
totalFilesFound++;
|
|
1639
|
+
const ext = extname(entry).toLowerCase();
|
|
1640
|
+
if (!opts.fileTypes.includes(ext)) {
|
|
1641
|
+
continue;
|
|
1642
|
+
}
|
|
1643
|
+
if (opts.skipSensitive && isSensitiveFile(entry)) {
|
|
1644
|
+
skipped.push({
|
|
1645
|
+
path: relativePath,
|
|
1646
|
+
reason: "Potentially sensitive file"
|
|
1647
|
+
});
|
|
1648
|
+
warnings.push(`Skipped sensitive file: ${relativePath}`);
|
|
1649
|
+
continue;
|
|
1650
|
+
}
|
|
1651
|
+
if (stats2.size > opts.maxFileSize) {
|
|
1652
|
+
skipped.push({
|
|
1653
|
+
path: relativePath,
|
|
1654
|
+
reason: `File too large (${stats2.size} bytes)`
|
|
1655
|
+
});
|
|
1656
|
+
continue;
|
|
1657
|
+
}
|
|
1658
|
+
if (totalSize + stats2.size > opts.maxTotalSize) {
|
|
1659
|
+
skipped.push({
|
|
1660
|
+
path: relativePath,
|
|
1661
|
+
reason: "Total size limit reached"
|
|
1662
|
+
});
|
|
1663
|
+
continue;
|
|
1664
|
+
}
|
|
1665
|
+
let content;
|
|
1666
|
+
try {
|
|
1667
|
+
const buffer = readFileSync2(validPath);
|
|
1668
|
+
if (isBinaryContent(buffer)) {
|
|
1669
|
+
skipped.push({ path: relativePath, reason: "Binary file" });
|
|
1670
|
+
continue;
|
|
1671
|
+
}
|
|
1672
|
+
content = buffer.toString("utf-8");
|
|
1673
|
+
} catch {
|
|
1674
|
+
skipped.push({ path: relativePath, reason: "Could not read file" });
|
|
1675
|
+
continue;
|
|
1676
|
+
}
|
|
1677
|
+
if (opts.detectSecrets) {
|
|
1678
|
+
const secrets = detectEmbeddedSecrets(content);
|
|
1679
|
+
if (secrets.length > 0) {
|
|
1680
|
+
skipped.push({
|
|
1681
|
+
path: relativePath,
|
|
1682
|
+
reason: `Contains potential secrets: ${secrets.join(", ")}`
|
|
1683
|
+
});
|
|
1684
|
+
warnings.push(
|
|
1685
|
+
`Skipped file with potential secrets: ${relativePath} (${secrets.join(", ")})`
|
|
1686
|
+
);
|
|
1687
|
+
continue;
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
files.push({ path: relativePath, content });
|
|
1691
|
+
totalSize += stats2.size;
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
scanDir(repoRoot);
|
|
1696
|
+
const stats = {
|
|
1697
|
+
totalFilesFound,
|
|
1698
|
+
totalFilesIncluded: files.length,
|
|
1699
|
+
totalSize,
|
|
1700
|
+
tokenEstimate: files.reduce((sum, f) => sum + estimateTokens(f.content), 0)
|
|
1701
|
+
};
|
|
1702
|
+
logger.info("Repository scan complete", {
|
|
1703
|
+
filesIncluded: files.length,
|
|
1704
|
+
filesSkipped: skipped.length,
|
|
1705
|
+
totalSize,
|
|
1706
|
+
tokenEstimate: stats.tokenEstimate,
|
|
1707
|
+
warnings: warnings.length
|
|
1708
|
+
});
|
|
1709
|
+
return {
|
|
1710
|
+
files,
|
|
1711
|
+
skipped,
|
|
1712
|
+
warnings,
|
|
1713
|
+
stats,
|
|
1714
|
+
repoRoot
|
|
1715
|
+
};
|
|
1716
|
+
}
|
|
1717
|
+
function aggregateFiles(files) {
|
|
1718
|
+
return files.map(
|
|
1719
|
+
(f) => `=== FILE: ${f.path} ===
|
|
1720
|
+
${f.content}
|
|
1721
|
+
=== END FILE: ${f.path} ===`
|
|
1722
|
+
).join("\n\n");
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
// src/tools/tps-audit.ts
|
|
1726
|
+
var tpsAuditSchemaObj = z7.object({
|
|
1727
|
+
path: z7.string().optional().describe(
|
|
1728
|
+
"Path to repo root (auto-detects current directory if not provided)"
|
|
1729
|
+
),
|
|
1730
|
+
focus_areas: z7.array(z7.string()).optional().describe("Specific areas to focus on (e.g., 'performance', 'security')"),
|
|
1731
|
+
max_files: z7.number().max(100).optional().describe("Maximum files to analyze (default: 50, max: 100)"),
|
|
1732
|
+
file_types: z7.array(z7.string()).optional().describe("File extensions to include (e.g., ['.ts', '.js'])"),
|
|
1733
|
+
include_sensitive: z7.boolean().optional().describe(
|
|
1734
|
+
"Include potentially sensitive files (default: false, use with caution)"
|
|
1735
|
+
),
|
|
1736
|
+
output_format: z7.enum(["html", "markdown", "json"]).optional().describe("Output format (default: html)")
|
|
1737
|
+
});
|
|
1738
|
+
var tpsAuditSchema = tpsAuditSchemaObj.shape;
|
|
1739
|
+
async function handleTpsAudit(client2, models, input) {
|
|
1740
|
+
const startPath = input.path || process.cwd();
|
|
1741
|
+
const outputFormat = input.output_format || "html";
|
|
1742
|
+
logger.info("Starting TPS audit", {
|
|
1743
|
+
startPath,
|
|
1744
|
+
maxFiles: input.max_files,
|
|
1745
|
+
focusAreas: input.focus_areas,
|
|
1746
|
+
outputFormat,
|
|
1747
|
+
modelCount: models.length
|
|
1748
|
+
});
|
|
1749
|
+
const scanResult = await scanRepository(startPath, {
|
|
1750
|
+
maxFiles: input.max_files,
|
|
1751
|
+
fileTypes: input.file_types,
|
|
1752
|
+
skipSensitive: !input.include_sensitive,
|
|
1753
|
+
detectSecrets: !input.include_sensitive
|
|
1754
|
+
});
|
|
1755
|
+
if (scanResult.files.length === 0) {
|
|
1756
|
+
logger.warn("No files found to analyze", {
|
|
1757
|
+
totalFilesFound: scanResult.stats.totalFilesFound,
|
|
1758
|
+
skipped: scanResult.skipped.length
|
|
1759
|
+
});
|
|
1760
|
+
return {
|
|
1761
|
+
results: [
|
|
1762
|
+
{
|
|
1763
|
+
model: "system",
|
|
1764
|
+
review: "No files found to analyze. Check that the repository contains supported file types and that files are not excluded by .gitignore or security filters."
|
|
1765
|
+
}
|
|
1766
|
+
],
|
|
1767
|
+
models: ["system"],
|
|
1768
|
+
scanResult,
|
|
1769
|
+
analysis: null,
|
|
1770
|
+
outputFormat
|
|
1771
|
+
};
|
|
1772
|
+
}
|
|
1773
|
+
if (scanResult.warnings.length > 0) {
|
|
1774
|
+
logger.warn("Security warnings during scan", {
|
|
1775
|
+
warnings: scanResult.warnings
|
|
1776
|
+
});
|
|
1777
|
+
}
|
|
1778
|
+
const aggregatedContent = aggregateFiles(scanResult.files);
|
|
1779
|
+
logger.info("Repository scanned", {
|
|
1780
|
+
filesIncluded: scanResult.files.length,
|
|
1781
|
+
totalSize: scanResult.stats.totalSize,
|
|
1782
|
+
tokenEstimate: scanResult.stats.tokenEstimate
|
|
1783
|
+
});
|
|
1784
|
+
if (scanResult.stats.tokenEstimate > 1e5) {
|
|
1785
|
+
logger.warn("Large token count", {
|
|
1786
|
+
estimate: scanResult.stats.tokenEstimate
|
|
1787
|
+
});
|
|
1788
|
+
}
|
|
1789
|
+
const results = await client2.tpsAudit(aggregatedContent, models, {
|
|
1790
|
+
focusAreas: input.focus_areas,
|
|
1791
|
+
repoName: scanResult.repoRoot.split("/").pop()
|
|
1792
|
+
});
|
|
1793
|
+
let analysis = null;
|
|
1794
|
+
for (const result of results) {
|
|
1795
|
+
if (!result.error && result.review) {
|
|
1796
|
+
const { parseTpsAnalysis } = await import("./tps-audit-GNK4VIKA.js");
|
|
1797
|
+
analysis = parseTpsAnalysis(result.review);
|
|
1798
|
+
if (analysis) break;
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
return {
|
|
1802
|
+
results,
|
|
1803
|
+
models,
|
|
1804
|
+
scanResult,
|
|
1805
|
+
analysis,
|
|
1806
|
+
outputFormat
|
|
1807
|
+
};
|
|
1808
|
+
}
|
|
1809
|
+
function formatTpsAuditResults(auditResult) {
|
|
1810
|
+
const { results, scanResult, analysis, outputFormat } = auditResult;
|
|
1811
|
+
switch (outputFormat) {
|
|
1812
|
+
case "html": {
|
|
1813
|
+
const templatePath = join4(getTemplatesDir(), "tps-report.html");
|
|
1814
|
+
return formatResultsAsHtml(results, templatePath, {
|
|
1815
|
+
analysis,
|
|
1816
|
+
repoName: scanResult.repoRoot.split("/").pop()
|
|
1817
|
+
});
|
|
1818
|
+
}
|
|
1819
|
+
case "json": {
|
|
1820
|
+
return JSON.stringify(
|
|
1821
|
+
{
|
|
1822
|
+
analysis,
|
|
1823
|
+
scanStats: scanResult.stats,
|
|
1824
|
+
warnings: scanResult.warnings,
|
|
1825
|
+
skipped: scanResult.skipped,
|
|
1826
|
+
modelResponses: results.map((r) => ({
|
|
1827
|
+
model: r.model,
|
|
1828
|
+
hasError: !!r.error,
|
|
1829
|
+
content: r.error || r.review
|
|
1830
|
+
}))
|
|
1831
|
+
},
|
|
1832
|
+
null,
|
|
1833
|
+
2
|
|
1834
|
+
);
|
|
1835
|
+
}
|
|
1836
|
+
case "markdown":
|
|
1837
|
+
default: {
|
|
1838
|
+
const parts = [];
|
|
1839
|
+
parts.push("# TPS Audit Report\n");
|
|
1840
|
+
parts.push(`**Repository:** ${scanResult.repoRoot}
|
|
1841
|
+
`);
|
|
1842
|
+
parts.push(`**Files Analyzed:** ${scanResult.files.length}
|
|
1843
|
+
`);
|
|
1844
|
+
parts.push(`**Token Estimate:** ~${scanResult.stats.tokenEstimate}
|
|
1845
|
+
`);
|
|
1846
|
+
if (scanResult.warnings.length > 0) {
|
|
1847
|
+
parts.push("\n## Warnings\n");
|
|
1848
|
+
for (const w of scanResult.warnings) {
|
|
1849
|
+
parts.push(`- ${w}
|
|
1850
|
+
`);
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
if (analysis) {
|
|
1854
|
+
parts.push("\n## Scores\n");
|
|
1855
|
+
parts.push(`- **Overall:** ${analysis.scores.overall}/100
|
|
1856
|
+
`);
|
|
1857
|
+
parts.push(`- **Flow:** ${analysis.scores.flow}/100
|
|
1858
|
+
`);
|
|
1859
|
+
parts.push(`- **Waste Efficiency:** ${analysis.scores.waste}/100
|
|
1860
|
+
`);
|
|
1861
|
+
parts.push(`- **Quality:** ${analysis.scores.quality}/100
|
|
1862
|
+
`);
|
|
1863
|
+
parts.push("\n## Summary\n");
|
|
1864
|
+
parts.push("\n### Strengths\n");
|
|
1865
|
+
for (const s of analysis.summary.strengths) {
|
|
1866
|
+
parts.push(`- ${s}
|
|
1867
|
+
`);
|
|
1868
|
+
}
|
|
1869
|
+
parts.push("\n### Concerns\n");
|
|
1870
|
+
for (const c of analysis.summary.concerns) {
|
|
1871
|
+
parts.push(`- ${c}
|
|
1872
|
+
`);
|
|
1873
|
+
}
|
|
1874
|
+
parts.push("\n### Quick Wins\n");
|
|
1875
|
+
for (const q of analysis.summary.quickWins) {
|
|
1876
|
+
parts.push(`- ${q}
|
|
1877
|
+
`);
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
parts.push("\n## Model Perspectives\n");
|
|
1881
|
+
results.forEach((r) => {
|
|
1882
|
+
if (r.error) {
|
|
1883
|
+
parts.push(`
|
|
1884
|
+
### ${r.model}
|
|
1885
|
+
|
|
1886
|
+
**Error:** ${r.error}
|
|
1887
|
+
`);
|
|
1888
|
+
} else {
|
|
1889
|
+
parts.push(`
|
|
1890
|
+
### ${r.model}
|
|
1891
|
+
|
|
1892
|
+
${r.review}
|
|
1893
|
+
`);
|
|
1894
|
+
}
|
|
1895
|
+
parts.push("\n---\n");
|
|
1896
|
+
});
|
|
1897
|
+
return parts.join("");
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1242
1902
|
// src/index.ts
|
|
1243
1903
|
var OPENROUTER_API_KEY = process.env.OPENROUTER_API_KEY;
|
|
1244
1904
|
if (!OPENROUTER_API_KEY) {
|
|
@@ -1287,6 +1947,42 @@ createReviewTool(server, {
|
|
|
1287
1947
|
inputSchema: gitReviewSchema,
|
|
1288
1948
|
handler: (input) => handleGitReview(client, CODE_REVIEW_MODELS, input)
|
|
1289
1949
|
});
|
|
1950
|
+
server.registerTool(
|
|
1951
|
+
"tps_audit",
|
|
1952
|
+
{
|
|
1953
|
+
description: "Toyota Production System audit - analyze a codebase for flow, waste, bottlenecks, and quality. Scans the repository, identifies entry points, maps data flow, and provides actionable recommendations. Outputs interactive HTML report by default, or markdown/JSON.",
|
|
1954
|
+
inputSchema: tpsAuditSchema
|
|
1955
|
+
},
|
|
1956
|
+
async (input) => {
|
|
1957
|
+
try {
|
|
1958
|
+
logger.debug("Starting tps_audit", {
|
|
1959
|
+
inputKeys: Object.keys(input)
|
|
1960
|
+
});
|
|
1961
|
+
const result = await handleTpsAudit(client, TPS_AUDIT_MODELS, input);
|
|
1962
|
+
const formattedOutput = formatTpsAuditResults(result);
|
|
1963
|
+
logger.info("Completed tps_audit", {
|
|
1964
|
+
modelCount: result.models.length,
|
|
1965
|
+
filesScanned: result.scanResult.files.length,
|
|
1966
|
+
outputFormat: result.outputFormat,
|
|
1967
|
+
hasAnalysis: !!result.analysis
|
|
1968
|
+
});
|
|
1969
|
+
return {
|
|
1970
|
+
content: [
|
|
1971
|
+
{
|
|
1972
|
+
type: "text",
|
|
1973
|
+
text: formattedOutput
|
|
1974
|
+
}
|
|
1975
|
+
]
|
|
1976
|
+
};
|
|
1977
|
+
} catch (error) {
|
|
1978
|
+
logger.error(
|
|
1979
|
+
"Error in tps_audit",
|
|
1980
|
+
error instanceof Error ? error : new Error(String(error))
|
|
1981
|
+
);
|
|
1982
|
+
return formatError(error);
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
);
|
|
1290
1986
|
server.registerTool(
|
|
1291
1987
|
"list_review_config",
|
|
1292
1988
|
{ description: "Show current model configuration" },
|
|
@@ -1322,7 +2018,8 @@ async function main() {
|
|
|
1322
2018
|
frontendReviewModels: FRONTEND_REVIEW_MODELS,
|
|
1323
2019
|
backendReviewModels: BACKEND_REVIEW_MODELS,
|
|
1324
2020
|
planReviewModels: PLAN_REVIEW_MODELS,
|
|
1325
|
-
discussionModels: DISCUSSION_MODELS
|
|
2021
|
+
discussionModels: DISCUSSION_MODELS,
|
|
2022
|
+
tpsAuditModels: TPS_AUDIT_MODELS
|
|
1326
2023
|
});
|
|
1327
2024
|
}
|
|
1328
2025
|
main().catch((error) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@klitchevo/code-council",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"description": "Multi-model AI code review server using OpenRouter - get diverse perspectives from multiple LLMs in parallel",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
"dependencies": {
|
|
65
65
|
"@modelcontextprotocol/sdk": "1.25.1",
|
|
66
66
|
"@openrouter/sdk": "0.3.10",
|
|
67
|
+
"ignore": "^7.0.5",
|
|
67
68
|
"zod": "4.2.1"
|
|
68
69
|
},
|
|
69
70
|
"devDependencies": {
|