@0xdevabir/enhance 0.1.0 → 0.1.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.
- package/README.md +20 -1
- package/dist/index.js +615 -122
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,12 +23,31 @@ Instead of sending `build login page` directly to Claude/Codex/OpenCode:
|
|
|
23
23
|
- Node.js 20+
|
|
24
24
|
- At least one AI coding tool: [Claude Code](https://claude.ai/code), [OpenCode](https://opencode.ai), or [Codex CLI](https://github.com/openai/codex)
|
|
25
25
|
|
|
26
|
-
###
|
|
26
|
+
### Option A — No install (npx)
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx @0xdevabir/enhance@latest "build a login page"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Runs directly without installing anything globally. Great for trying it out or if you hit permission errors.
|
|
33
|
+
|
|
34
|
+
### Option B — Global install
|
|
27
35
|
|
|
28
36
|
```bash
|
|
29
37
|
npm install -g @0xdevabir/enhance
|
|
30
38
|
```
|
|
31
39
|
|
|
40
|
+
> **Permission error (EACCES)?** Fix npm's global prefix first, then retry:
|
|
41
|
+
> ```bash
|
|
42
|
+
> mkdir -p ~/.npm-global
|
|
43
|
+
> npm config set prefix '~/.npm-global'
|
|
44
|
+
> echo 'export PATH=$HOME/.npm-global/bin:$PATH' >> ~/.zshrc
|
|
45
|
+
> source ~/.zshrc
|
|
46
|
+
> npm install -g @0xdevabir/enhance
|
|
47
|
+
> ```
|
|
48
|
+
|
|
49
|
+
### Step 2 — Run setup
|
|
50
|
+
|
|
32
51
|
### Step 2 — Run setup
|
|
33
52
|
|
|
34
53
|
```bash
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
|
+
import { createInterface } from "readline";
|
|
5
6
|
import ora from "ora";
|
|
7
|
+
import chalk3 from "chalk";
|
|
6
8
|
|
|
7
9
|
// src/cli/logger.ts
|
|
8
10
|
import chalk from "chalk";
|
|
@@ -28,7 +30,7 @@ import { cosmiconfig } from "cosmiconfig";
|
|
|
28
30
|
var DEFAULT_CONFIG = {
|
|
29
31
|
provider: "claude",
|
|
30
32
|
model: "claude-sonnet-4-6",
|
|
31
|
-
maxContextTokens:
|
|
33
|
+
maxContextTokens: 8e3
|
|
32
34
|
};
|
|
33
35
|
async function loadConfig(root) {
|
|
34
36
|
const explorer = cosmiconfig("enhance", {
|
|
@@ -243,69 +245,178 @@ async function setCached(root, result) {
|
|
|
243
245
|
|
|
244
246
|
// src/analyzer/intent.ts
|
|
245
247
|
var ACTION_KEYWORDS = {
|
|
246
|
-
create: ["build", "create", "make", "add", "implement", "write", "generate", "scaffold", "set up", "setup", "new"],
|
|
247
248
|
fix: ["fix", "debug", "resolve", "repair", "broken", "failing", "crash", "error", "bug", "issue", "problem"],
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
249
|
+
create: ["create", "build", "make", "generate", "scaffold", "new", "write"],
|
|
250
|
+
add: ["add", "implement", "integrate", "include", "append", "enable"],
|
|
251
|
+
refactor: ["refactor", "restructure", "reorganize", "clean up", "cleanup", "simplify", "rewrite", "move", "extract"],
|
|
252
|
+
explain: ["explain", "describe", "how does", "what is", "why does", "understand", "document", "walkthrough"],
|
|
253
|
+
delete: ["delete", "remove", "drop", "destroy", "uninstall", "clean"],
|
|
252
254
|
unknown: []
|
|
253
255
|
};
|
|
254
256
|
var ENTITY_KEYWORDS = {
|
|
255
257
|
page: ["page", "route", "view", "screen", "layout"],
|
|
256
|
-
component: ["component", "widget", "
|
|
258
|
+
component: ["component", "widget", "card", "modal", "dialog", "dropdown", "button", "input", "form"],
|
|
257
259
|
api: ["api", "endpoint", "route handler", "server action", "action", "mutation", "query", "rest", "graphql"],
|
|
258
|
-
hook: ["hook", "
|
|
259
|
-
util: ["util", "
|
|
260
|
-
config: ["config", "configuration", "
|
|
261
|
-
style: ["style", "css", "theme", "design", "
|
|
260
|
+
hook: ["hook", "use", "custom hook"],
|
|
261
|
+
util: ["util", "helper", "function", "service", "lib", "library", "module"],
|
|
262
|
+
config: ["config", "configuration", "settings", "env", "environment"],
|
|
263
|
+
style: ["style", "css", "theme", "color", "design", "ui", "ux", "animation"],
|
|
262
264
|
unknown: []
|
|
263
265
|
};
|
|
264
266
|
var FEATURE_KEYWORDS = {
|
|
265
|
-
auth: ["auth", "
|
|
266
|
-
dashboard: ["dashboard", "admin", "
|
|
267
|
+
auth: ["auth", "login", "logout", "signup", "register", "session", "jwt", "token", "oauth", "credential", "password", "permission", "role"],
|
|
268
|
+
dashboard: ["dashboard", "admin", "overview", "analytics", "metrics", "stats"],
|
|
267
269
|
payment: ["payment", "checkout", "billing", "stripe", "invoice", "subscription", "pricing"],
|
|
268
|
-
user: ["user", "profile", "account", "avatar", "
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
chart: ["chart", "graph", "visualization", "plot", "diagram"]
|
|
270
|
+
user: ["user", "profile", "account", "avatar", "member"],
|
|
271
|
+
upload: ["upload", "file", "image", "storage", "media", "s3", "cdn"],
|
|
272
|
+
notification: ["notification", "alert", "email", "toast", "push", "sms"],
|
|
273
|
+
navigation: ["nav", "navbar", "sidebar", "header", "menu", "breadcrumb"],
|
|
274
|
+
table: ["table", "grid", "list", "pagination", "sort", "filter"],
|
|
275
|
+
form: ["form", "field", "input", "validation", "submit"],
|
|
276
|
+
search: ["search", "filter", "query", "autocomplete"]
|
|
276
277
|
};
|
|
278
|
+
var SYSTEM_SCOPE_SIGNALS = [
|
|
279
|
+
"entire",
|
|
280
|
+
"whole",
|
|
281
|
+
"all",
|
|
282
|
+
"system",
|
|
283
|
+
"everywhere",
|
|
284
|
+
"global",
|
|
285
|
+
"across",
|
|
286
|
+
"throughout",
|
|
287
|
+
"full",
|
|
288
|
+
"complete",
|
|
289
|
+
"migrate",
|
|
290
|
+
"overhaul"
|
|
291
|
+
];
|
|
292
|
+
var SIMPLE_SCOPE_SIGNALS = [
|
|
293
|
+
"this file",
|
|
294
|
+
"this component",
|
|
295
|
+
"this function",
|
|
296
|
+
"line",
|
|
297
|
+
"typo",
|
|
298
|
+
"typos",
|
|
299
|
+
"small",
|
|
300
|
+
"quick",
|
|
301
|
+
"minor",
|
|
302
|
+
"just change",
|
|
303
|
+
"rename"
|
|
304
|
+
];
|
|
277
305
|
function analyzeIntent(rawPrompt) {
|
|
278
306
|
const lower = rawPrompt.toLowerCase();
|
|
307
|
+
const words = lower.split(/\s+/);
|
|
279
308
|
let action = "unknown";
|
|
309
|
+
let actionMatchCount = 0;
|
|
280
310
|
for (const [act, keywords] of Object.entries(ACTION_KEYWORDS)) {
|
|
281
311
|
if (act === "unknown") continue;
|
|
282
|
-
|
|
312
|
+
const matches = keywords.filter((k) => lower.includes(k)).length;
|
|
313
|
+
if (matches > actionMatchCount) {
|
|
283
314
|
action = act;
|
|
284
|
-
|
|
315
|
+
actionMatchCount = matches;
|
|
285
316
|
}
|
|
286
317
|
}
|
|
287
318
|
let entity = "unknown";
|
|
319
|
+
let entityMatchCount = 0;
|
|
288
320
|
for (const [ent, keywords] of Object.entries(ENTITY_KEYWORDS)) {
|
|
289
321
|
if (ent === "unknown") continue;
|
|
290
|
-
|
|
322
|
+
const matches = keywords.filter((k) => lower.includes(k)).length;
|
|
323
|
+
if (matches > entityMatchCount) {
|
|
291
324
|
entity = ent;
|
|
292
|
-
|
|
325
|
+
entityMatchCount = matches;
|
|
293
326
|
}
|
|
294
327
|
}
|
|
295
328
|
let feature = "general";
|
|
329
|
+
let featureMatchCount = 0;
|
|
296
330
|
for (const [feat, keywords] of Object.entries(FEATURE_KEYWORDS)) {
|
|
297
|
-
|
|
331
|
+
const matches = keywords.filter((k) => lower.includes(k)).length;
|
|
332
|
+
if (matches > featureMatchCount) {
|
|
298
333
|
feature = feat;
|
|
299
|
-
|
|
334
|
+
featureMatchCount = matches;
|
|
300
335
|
}
|
|
301
336
|
}
|
|
302
|
-
|
|
337
|
+
let scope = "feature";
|
|
338
|
+
if (SYSTEM_SCOPE_SIGNALS.some((s) => lower.includes(s))) {
|
|
339
|
+
scope = "system";
|
|
340
|
+
} else if (SIMPLE_SCOPE_SIGNALS.some((s) => lower.includes(s))) {
|
|
341
|
+
scope = "file";
|
|
342
|
+
} else if (action === "fix" && words.length <= 8) {
|
|
343
|
+
scope = "file";
|
|
344
|
+
}
|
|
345
|
+
let complexity = "feature";
|
|
346
|
+
if (scope === "system" || action === "refactor" && scope !== "file") {
|
|
347
|
+
complexity = "system";
|
|
348
|
+
} else if (scope === "file" || action === "fix" || action === "explain") {
|
|
349
|
+
complexity = "simple";
|
|
350
|
+
}
|
|
351
|
+
const totalMatches = actionMatchCount + entityMatchCount + featureMatchCount;
|
|
352
|
+
const confidence = Math.min(1, totalMatches / Math.max(words.length * 0.3, 1));
|
|
353
|
+
return { action, entity, feature, scope, complexity, confidence, rawPrompt };
|
|
303
354
|
}
|
|
304
355
|
|
|
305
356
|
// src/analyzer/context.ts
|
|
306
357
|
import { readFile as readFile4 } from "fs/promises";
|
|
307
|
-
import { join as join6 } from "path";
|
|
358
|
+
import { join as join6, dirname } from "path";
|
|
359
|
+
import { exec } from "child_process";
|
|
360
|
+
import { promisify } from "util";
|
|
308
361
|
import fg from "fast-glob";
|
|
362
|
+
var execAsync = promisify(exec);
|
|
363
|
+
var PROJECT_INSTRUCTION_FILES = [
|
|
364
|
+
".claude/CLAUDE.md",
|
|
365
|
+
"CLAUDE.md",
|
|
366
|
+
"AGENTS.md",
|
|
367
|
+
".cursorrules"
|
|
368
|
+
];
|
|
369
|
+
async function findProjectInstructions(root) {
|
|
370
|
+
const parts = [];
|
|
371
|
+
const searchRoots = [root, dirname(root)];
|
|
372
|
+
for (const searchRoot of searchRoots) {
|
|
373
|
+
for (const fileName of PROJECT_INSTRUCTION_FILES) {
|
|
374
|
+
const content = await tryRead(join6(searchRoot, fileName));
|
|
375
|
+
if (content) {
|
|
376
|
+
const relPath = fileName;
|
|
377
|
+
parts.push(`<!-- Source: ${relPath} -->
|
|
378
|
+
${truncateLines(content, 300)}`);
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return parts.join("\n\n");
|
|
384
|
+
}
|
|
385
|
+
async function findGitContext(root, charBudget) {
|
|
386
|
+
const parts = [];
|
|
387
|
+
let used = 0;
|
|
388
|
+
try {
|
|
389
|
+
const { stdout: diffOutput } = await execAsync("git diff HEAD", { cwd: root, timeout: 5e3 });
|
|
390
|
+
if (diffOutput.trim()) {
|
|
391
|
+
const truncated = diffOutput.slice(0, Math.min(3e3, charBudget * 0.4));
|
|
392
|
+
const section = `// GIT: uncommitted changes
|
|
393
|
+
${truncated}${diffOutput.length > truncated.length ? "\n// ... (diff truncated)" : ""}`;
|
|
394
|
+
parts.push(section);
|
|
395
|
+
used += section.length;
|
|
396
|
+
}
|
|
397
|
+
if (used < charBudget * 0.6) {
|
|
398
|
+
const { stdout: logOutput } = await execAsync(
|
|
399
|
+
"git diff HEAD~3..HEAD --name-only 2>/dev/null || git diff HEAD~1..HEAD --name-only",
|
|
400
|
+
{ cwd: root, timeout: 5e3 }
|
|
401
|
+
);
|
|
402
|
+
const changedFiles = logOutput.trim().split("\n").filter(Boolean).slice(0, 8);
|
|
403
|
+
for (const relPath of changedFiles) {
|
|
404
|
+
if (used >= charBudget * 0.7) break;
|
|
405
|
+
const content = await tryRead(join6(root, relPath));
|
|
406
|
+
if (!content) continue;
|
|
407
|
+
const snippet = truncateLines(content, 100);
|
|
408
|
+
const section = `// FILE (recently changed): ${relPath}
|
|
409
|
+
${snippet}`;
|
|
410
|
+
if (used + section.length < charBudget * 0.7) {
|
|
411
|
+
parts.push(section);
|
|
412
|
+
used += section.length;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
} catch {
|
|
417
|
+
}
|
|
418
|
+
return { content: parts.join("\n\n"), charsUsed: used };
|
|
419
|
+
}
|
|
309
420
|
var FEATURE_PATTERNS = {
|
|
310
421
|
auth: ["*auth*", "*login*", "*logout*", "*session*", "*jwt*", "*token*", "*credential*", "*middleware*", "*guard*"],
|
|
311
422
|
dashboard: ["*dashboard*", "*admin*", "*overview*", "*analytics*"],
|
|
@@ -326,25 +437,57 @@ var ALWAYS_INCLUDE_PATTERNS = [
|
|
|
326
437
|
"pages/_app.ts",
|
|
327
438
|
"src/pages/_app.tsx"
|
|
328
439
|
];
|
|
329
|
-
var
|
|
330
|
-
var
|
|
331
|
-
|
|
440
|
+
var DEFAULT_TOKEN_BUDGET = 8e3;
|
|
441
|
+
var CHARS_PER_TOKEN = 4;
|
|
442
|
+
function estimateTokens(text) {
|
|
443
|
+
return Math.ceil(text.length / CHARS_PER_TOKEN);
|
|
444
|
+
}
|
|
445
|
+
function smartTruncate(content, maxTokens) {
|
|
446
|
+
const maxChars = maxTokens * CHARS_PER_TOKEN;
|
|
447
|
+
if (content.length <= maxChars) return content;
|
|
448
|
+
const lines = content.split("\n");
|
|
449
|
+
const result = [];
|
|
450
|
+
let chars = 0;
|
|
451
|
+
for (const line of lines) {
|
|
452
|
+
if (chars + line.length + 1 > maxChars) {
|
|
453
|
+
result.push(`// ... (truncated, ${lines.length - result.length} lines omitted)`);
|
|
454
|
+
break;
|
|
455
|
+
}
|
|
456
|
+
result.push(line);
|
|
457
|
+
chars += line.length + 1;
|
|
458
|
+
}
|
|
459
|
+
return result.join("\n");
|
|
460
|
+
}
|
|
461
|
+
async function findRelevantFiles(root, intent, structure, maxTokens = DEFAULT_TOKEN_BUDGET, alwaysInclude = []) {
|
|
332
462
|
const sections = [];
|
|
333
|
-
let
|
|
463
|
+
let tokenBudget = maxTokens;
|
|
464
|
+
const { content: gitContent, charsUsed: gitUsed } = await findGitContext(root, Math.floor(tokenBudget * 0.35) * CHARS_PER_TOKEN);
|
|
465
|
+
tokenBudget -= Math.ceil(gitUsed / CHARS_PER_TOKEN);
|
|
466
|
+
for (const relPath of alwaysInclude) {
|
|
467
|
+
if (tokenBudget <= 100) break;
|
|
468
|
+
const content = await tryRead(join6(root, relPath));
|
|
469
|
+
if (!content) continue;
|
|
470
|
+
const allowedTokens = Math.min(estimateTokens(content), Math.floor(tokenBudget * 0.2));
|
|
471
|
+
const snippet = smartTruncate(content, allowedTokens);
|
|
472
|
+
const section = `// FILE: ${relPath} (pinned)
|
|
473
|
+
${snippet}`;
|
|
474
|
+
sections.push(section);
|
|
475
|
+
tokenBudget -= estimateTokens(section);
|
|
476
|
+
}
|
|
334
477
|
for (const pattern of ALWAYS_INCLUDE_PATTERNS) {
|
|
478
|
+
if (tokenBudget <= 100) break;
|
|
335
479
|
const content = await tryRead(join6(root, pattern));
|
|
336
|
-
if (content)
|
|
337
|
-
|
|
338
|
-
|
|
480
|
+
if (!content) continue;
|
|
481
|
+
const fileTokens = estimateTokens(content);
|
|
482
|
+
const allowedTokens = Math.min(fileTokens, Math.floor(tokenBudget * 0.25));
|
|
483
|
+
const snippet = smartTruncate(content, allowedTokens);
|
|
484
|
+
const section = `// FILE: ${pattern}
|
|
339
485
|
${snippet}`;
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
charBudget -= section.length;
|
|
343
|
-
}
|
|
344
|
-
}
|
|
486
|
+
sections.push(section);
|
|
487
|
+
tokenBudget -= estimateTokens(section);
|
|
345
488
|
}
|
|
346
489
|
const featurePatterns = FEATURE_PATTERNS[intent.feature] ?? [];
|
|
347
|
-
if (featurePatterns.length > 0) {
|
|
490
|
+
if (featurePatterns.length > 0 && tokenBudget > 200) {
|
|
348
491
|
const searchBase = structure.hasSrcDir ? join6(root, "src") : root;
|
|
349
492
|
const globs = featurePatterns.flatMap((p) => [
|
|
350
493
|
`**/${p}.{ts,tsx,js,jsx}`,
|
|
@@ -360,21 +503,38 @@ ${snippet}`;
|
|
|
360
503
|
});
|
|
361
504
|
} catch {
|
|
362
505
|
}
|
|
363
|
-
|
|
364
|
-
|
|
506
|
+
const foundFeatureFiles = [];
|
|
507
|
+
for (const file of files.slice(0, 6)) {
|
|
508
|
+
if (tokenBudget <= 100) break;
|
|
365
509
|
const content = await tryRead(file);
|
|
366
510
|
if (!content) continue;
|
|
367
511
|
const relPath = file.replace(root + "/", "");
|
|
368
|
-
const
|
|
512
|
+
const allowedTokens = Math.min(estimateTokens(content), Math.floor(tokenBudget * 0.4));
|
|
513
|
+
const snippet = smartTruncate(content, allowedTokens);
|
|
369
514
|
const section = `// FILE: ${relPath}
|
|
370
515
|
${snippet}`;
|
|
371
|
-
|
|
516
|
+
sections.push(section);
|
|
517
|
+
tokenBudget -= estimateTokens(section);
|
|
518
|
+
foundFeatureFiles.push(file);
|
|
519
|
+
}
|
|
520
|
+
if (foundFeatureFiles.length > 0 && tokenBudget > 200) {
|
|
521
|
+
const importedFiles = await crawlImports(foundFeatureFiles, root);
|
|
522
|
+
const alreadyIncluded = new Set(foundFeatureFiles.map((f) => f));
|
|
523
|
+
for (const file of importedFiles.filter((f) => !alreadyIncluded.has(f)).slice(0, 4)) {
|
|
524
|
+
if (tokenBudget <= 150) break;
|
|
525
|
+
const content = await tryRead(file);
|
|
526
|
+
if (!content) continue;
|
|
527
|
+
const relPath = file.replace(root + "/", "");
|
|
528
|
+
const allowedTokens = Math.min(estimateTokens(content), Math.floor(tokenBudget * 0.2));
|
|
529
|
+
const snippet = smartTruncate(content, allowedTokens);
|
|
530
|
+
const section = `// FILE: ${relPath} (imported)
|
|
531
|
+
${snippet}`;
|
|
372
532
|
sections.push(section);
|
|
373
|
-
|
|
533
|
+
tokenBudget -= estimateTokens(section);
|
|
374
534
|
}
|
|
375
535
|
}
|
|
376
536
|
}
|
|
377
|
-
if (["component", "page"].includes(intent.entity) &&
|
|
537
|
+
if (["component", "page"].includes(intent.entity) && tokenBudget > 300) {
|
|
378
538
|
const componentDir = structure.hasSrcDir ? join6(root, "src", "components") : join6(root, "components");
|
|
379
539
|
let sampleFiles = [];
|
|
380
540
|
try {
|
|
@@ -388,20 +548,44 @@ ${snippet}`;
|
|
|
388
548
|
} catch {
|
|
389
549
|
}
|
|
390
550
|
for (const file of sampleFiles.slice(0, 2)) {
|
|
391
|
-
if (
|
|
551
|
+
if (tokenBudget <= 150) break;
|
|
392
552
|
const content = await tryRead(file);
|
|
393
553
|
if (!content) continue;
|
|
394
554
|
const relPath = file.replace(root + "/", "");
|
|
395
|
-
const snippet =
|
|
555
|
+
const snippet = smartTruncate(content, Math.min(200, tokenBudget - 50));
|
|
396
556
|
const section = `// FILE: ${relPath} (pattern reference)
|
|
397
557
|
${snippet}`;
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
558
|
+
sections.push(section);
|
|
559
|
+
tokenBudget -= estimateTokens(section);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return { contextFiles: sections.join("\n\n"), gitContext: gitContent };
|
|
563
|
+
}
|
|
564
|
+
async function crawlImports(filePaths, root) {
|
|
565
|
+
const IMPORT_RE = /(?:import|from)\s+['"](\.[^'"]+)['"]/g;
|
|
566
|
+
const TS_EXTS = [".ts", ".tsx", ".js", ".jsx"];
|
|
567
|
+
const found = /* @__PURE__ */ new Set();
|
|
568
|
+
for (const filePath of filePaths) {
|
|
569
|
+
const content = await tryRead(filePath);
|
|
570
|
+
if (!content) continue;
|
|
571
|
+
const dir = filePath.replace(/\/[^/]+$/, "");
|
|
572
|
+
let match;
|
|
573
|
+
IMPORT_RE.lastIndex = 0;
|
|
574
|
+
while ((match = IMPORT_RE.exec(content)) !== null) {
|
|
575
|
+
const importPath = match[1];
|
|
576
|
+
if (!importPath) continue;
|
|
577
|
+
const base = join6(dir, importPath);
|
|
578
|
+
for (const ext of TS_EXTS) {
|
|
579
|
+
const candidate = base.endsWith(ext) ? base : `${base}${ext}`;
|
|
580
|
+
if (candidate.includes("node_modules") || candidate.includes(".next")) continue;
|
|
581
|
+
if (candidate.startsWith(root)) {
|
|
582
|
+
found.add(candidate);
|
|
583
|
+
break;
|
|
584
|
+
}
|
|
401
585
|
}
|
|
402
586
|
}
|
|
403
587
|
}
|
|
404
|
-
return
|
|
588
|
+
return [...found];
|
|
405
589
|
}
|
|
406
590
|
async function tryRead(path) {
|
|
407
591
|
try {
|
|
@@ -418,7 +602,7 @@ function truncateLines(content, maxLines) {
|
|
|
418
602
|
}
|
|
419
603
|
|
|
420
604
|
// src/enhancer/injectors.ts
|
|
421
|
-
function getInjections(stack, intent) {
|
|
605
|
+
function getInjections(stack, intent, config) {
|
|
422
606
|
const rules = [];
|
|
423
607
|
if (stack.language === "typescript") {
|
|
424
608
|
rules.push("Use TypeScript throughout \u2014 no `any`, prefer `unknown` with type guards");
|
|
@@ -487,75 +671,160 @@ function getInjections(stack, intent) {
|
|
|
487
671
|
rules.push("Match the existing code style and file naming conventions");
|
|
488
672
|
rules.push("List every file you create or modify at the end of your response");
|
|
489
673
|
rules.push("Prefer reusing existing utilities over creating new ones");
|
|
674
|
+
if (config?.customRules?.length) {
|
|
675
|
+
rules.unshift(...config.customRules);
|
|
676
|
+
}
|
|
677
|
+
if (config?.featureRules?.[intent.feature]?.length) {
|
|
678
|
+
rules.unshift(...config.featureRules[intent.feature]);
|
|
679
|
+
}
|
|
490
680
|
return rules;
|
|
491
681
|
}
|
|
492
682
|
|
|
493
683
|
// src/enhancer/template.ts
|
|
494
684
|
function buildEnhancedPrompt(opts) {
|
|
495
|
-
const {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
685
|
+
const { intent } = opts;
|
|
686
|
+
switch (intent.complexity) {
|
|
687
|
+
case "simple":
|
|
688
|
+
return buildSimplePrompt(opts);
|
|
689
|
+
case "system":
|
|
690
|
+
return buildSystemPrompt(opts);
|
|
691
|
+
default:
|
|
692
|
+
return buildFeaturePrompt(opts);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
function buildSimplePrompt(opts) {
|
|
696
|
+
const { stack, intent, contextFiles, gitContext, injections, projectInstructions } = opts;
|
|
697
|
+
const sections = [];
|
|
698
|
+
if (projectInstructions) {
|
|
699
|
+
sections.push(`## Project Instructions
|
|
508
700
|
|
|
509
|
-
|
|
510
|
-
|
|
701
|
+
${projectInstructions}`);
|
|
702
|
+
}
|
|
703
|
+
sections.push(`## Context
|
|
704
|
+
**Stack**: ${formatStack(stack)}
|
|
705
|
+
${formatStackDetails(stack)}`);
|
|
706
|
+
sections.push(`## Task
|
|
511
707
|
|
|
512
|
-
|
|
513
|
-
|
|
708
|
+
${buildTaskDescription(intent)}`);
|
|
709
|
+
const rules = [...injections.slice(0, 3), ...injections.slice(-3)];
|
|
710
|
+
sections.push(`## Requirements
|
|
514
711
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
712
|
+
${rules.map((r) => `- ${r}`).join("\n")}`);
|
|
713
|
+
if (gitContext) {
|
|
714
|
+
sections.push(`## Recent Changes
|
|
518
715
|
|
|
519
|
-
|
|
520
|
-
Complete the task above. Follow every requirement. At the end, list every file you created or modified.`;
|
|
521
|
-
}
|
|
522
|
-
function formatStack(stack) {
|
|
523
|
-
const primary = stack.frameworks[0] ?? "Node.js";
|
|
524
|
-
const lang = stack.language === "typescript" ? "TypeScript" : "JavaScript";
|
|
525
|
-
return `${capitalize(primary)} ${lang}`;
|
|
526
|
-
}
|
|
527
|
-
function formatStackDetails(stack) {
|
|
528
|
-
const lines = [];
|
|
529
|
-
lines.push(`- **Language**: ${stack.language === "typescript" ? "TypeScript" : "JavaScript"}`);
|
|
530
|
-
if (stack.frameworks.length > 0) {
|
|
531
|
-
lines.push(`- **Frameworks**: ${stack.frameworks.map(capitalize).join(", ")}`);
|
|
716
|
+
${gitContext}`);
|
|
532
717
|
}
|
|
533
|
-
if (
|
|
534
|
-
|
|
718
|
+
if (contextFiles) {
|
|
719
|
+
sections.push(`## Code
|
|
720
|
+
|
|
721
|
+
${contextFiles}`);
|
|
535
722
|
}
|
|
536
|
-
|
|
537
|
-
|
|
723
|
+
sections.push(`## Expected Output
|
|
724
|
+
|
|
725
|
+
${buildOutputFormat(intent)}`);
|
|
726
|
+
return sections.join("\n\n---\n\n");
|
|
727
|
+
}
|
|
728
|
+
function buildFeaturePrompt(opts) {
|
|
729
|
+
const { stack, structure, intent, contextFiles, gitContext, injections, projectInstructions } = opts;
|
|
730
|
+
const sections = [];
|
|
731
|
+
if (projectInstructions) {
|
|
732
|
+
sections.push(`## Project Instructions
|
|
733
|
+
|
|
734
|
+
${projectInstructions}`);
|
|
538
735
|
}
|
|
539
|
-
|
|
540
|
-
|
|
736
|
+
sections.push(`## Project Context
|
|
737
|
+
|
|
738
|
+
**Stack**: ${formatStack(stack)}
|
|
739
|
+
${formatStackDetails(stack)}
|
|
740
|
+
|
|
741
|
+
**Structure**: ${formatStructure(structure)}`);
|
|
742
|
+
sections.push(`## Task
|
|
743
|
+
|
|
744
|
+
${buildTaskDescription(intent)}`);
|
|
745
|
+
const specific = injections.slice(0, -3);
|
|
746
|
+
const universal = injections.slice(-3);
|
|
747
|
+
const requirementLines = [
|
|
748
|
+
...specific.map((r) => `- ${r}`),
|
|
749
|
+
...specific.length > 0 ? ["", "**Always:**"] : ["**Always:**"],
|
|
750
|
+
...universal.map((r) => `- ${r}`)
|
|
751
|
+
].join("\n");
|
|
752
|
+
sections.push(`## Requirements
|
|
753
|
+
|
|
754
|
+
${requirementLines}`);
|
|
755
|
+
if (gitContext) {
|
|
756
|
+
sections.push(`## Recent Changes
|
|
757
|
+
|
|
758
|
+
${gitContext}`);
|
|
541
759
|
}
|
|
542
|
-
if (
|
|
543
|
-
|
|
760
|
+
if (contextFiles) {
|
|
761
|
+
sections.push(`## Relevant Code
|
|
762
|
+
|
|
763
|
+
${contextFiles}`);
|
|
544
764
|
}
|
|
545
|
-
|
|
546
|
-
|
|
765
|
+
sections.push(`## Expected Output
|
|
766
|
+
|
|
767
|
+
${buildOutputFormat(intent)}`);
|
|
768
|
+
return sections.join("\n\n---\n\n");
|
|
547
769
|
}
|
|
548
|
-
function
|
|
549
|
-
const
|
|
550
|
-
|
|
551
|
-
|
|
770
|
+
function buildSystemPrompt(opts) {
|
|
771
|
+
const { stack, structure, intent, contextFiles, gitContext, injections, projectInstructions } = opts;
|
|
772
|
+
const sections = [];
|
|
773
|
+
if (projectInstructions) {
|
|
774
|
+
sections.push(`## Project Instructions
|
|
775
|
+
|
|
776
|
+
${projectInstructions}`);
|
|
552
777
|
}
|
|
553
|
-
|
|
554
|
-
|
|
778
|
+
sections.push(`## Project Context
|
|
779
|
+
|
|
780
|
+
**Stack**: ${formatStack(stack)}
|
|
781
|
+
${formatStackDetails(stack)}
|
|
782
|
+
|
|
783
|
+
**Structure**: ${formatStructure(structure)}`);
|
|
784
|
+
sections.push(`## Task
|
|
785
|
+
|
|
786
|
+
${buildTaskDescription(intent)}
|
|
787
|
+
|
|
788
|
+
> This is a system-level change. Before writing any code:
|
|
789
|
+
> 1. Analyze the current state and identify all affected areas
|
|
790
|
+
> 2. Define your implementation plan step by step
|
|
791
|
+
> 3. Highlight any breaking changes or migration steps
|
|
792
|
+
> 4. Then implement each step`);
|
|
793
|
+
const requirementLines = injections.map((r) => `- ${r}`).join("\n");
|
|
794
|
+
sections.push(`## Requirements
|
|
795
|
+
|
|
796
|
+
${requirementLines}`);
|
|
797
|
+
if (gitContext) {
|
|
798
|
+
sections.push(`## Recent Changes
|
|
799
|
+
|
|
800
|
+
${gitContext}`);
|
|
801
|
+
}
|
|
802
|
+
if (contextFiles) {
|
|
803
|
+
sections.push(`## Relevant Code
|
|
804
|
+
|
|
805
|
+
${contextFiles}`);
|
|
806
|
+
}
|
|
807
|
+
sections.push(`## Expected Output
|
|
808
|
+
|
|
809
|
+
Start with a numbered implementation plan. Then implement each step. Mark breaking changes with \u26A0\uFE0F. List every file created or modified at the end.`);
|
|
810
|
+
return sections.join("\n\n---\n\n");
|
|
811
|
+
}
|
|
812
|
+
function buildOutputFormat(intent) {
|
|
813
|
+
switch (intent.action) {
|
|
814
|
+
case "fix":
|
|
815
|
+
return "Show exactly what changed. Explain in one sentence why it was broken. Provide the corrected code. List every file modified at the end.";
|
|
816
|
+
case "create":
|
|
817
|
+
case "add":
|
|
818
|
+
return "First list every file you will create or modify and why. Then implement each file completely. End with a summary list of all created/modified files.";
|
|
819
|
+
case "refactor":
|
|
820
|
+
return "For each change, show before and after. Explain the improvement in one line. Do not change behavior \u2014 only structure. List every file modified at the end.";
|
|
821
|
+
case "explain":
|
|
822
|
+
return "Walk through step by step. Reference specific function names, line numbers, or variable names from the existing code. Use concrete examples.";
|
|
823
|
+
case "delete":
|
|
824
|
+
return "List exactly what you will remove and why. Show the cleaned-up result. Confirm nothing else depends on the removed code.";
|
|
825
|
+
default:
|
|
826
|
+
return "Be thorough and precise. Show all changes. List every file created or modified at the end of your response.";
|
|
555
827
|
}
|
|
556
|
-
if (structure.hasAppDir) lines.push("Uses Next.js App Router (`app/` directory)");
|
|
557
|
-
if (structure.hasPagesDir) lines.push("Uses Next.js Pages Router (`pages/` directory)");
|
|
558
|
-
return lines.join("\n") || "Standard project structure";
|
|
559
828
|
}
|
|
560
829
|
function buildTaskDescription(intent) {
|
|
561
830
|
const actionMap = {
|
|
@@ -568,7 +837,36 @@ function buildTaskDescription(intent) {
|
|
|
568
837
|
unknown: "Handle"
|
|
569
838
|
};
|
|
570
839
|
const action = actionMap[intent.action] ?? "Handle";
|
|
571
|
-
return
|
|
840
|
+
return `**${action}**: ${intent.rawPrompt}`;
|
|
841
|
+
}
|
|
842
|
+
function formatStack(stack) {
|
|
843
|
+
const primary = stack.frameworks[0] ?? "Node.js";
|
|
844
|
+
const lang = stack.language === "typescript" ? "TypeScript" : "JavaScript";
|
|
845
|
+
return `${capitalize(primary)} / ${lang}`;
|
|
846
|
+
}
|
|
847
|
+
function formatStackDetails(stack) {
|
|
848
|
+
const lines = [];
|
|
849
|
+
if (stack.frameworks.length > 0) {
|
|
850
|
+
lines.push(`- **Frameworks**: ${stack.frameworks.map(capitalize).join(", ")}`);
|
|
851
|
+
}
|
|
852
|
+
if (stack.uiLibrary) lines.push(`- **UI**: ${capitalize(stack.uiLibrary)}`);
|
|
853
|
+
if (stack.orm) lines.push(`- **ORM**: ${capitalize(stack.orm)}`);
|
|
854
|
+
if (stack.testing) lines.push(`- **Testing**: ${capitalize(stack.testing)}`);
|
|
855
|
+
if (stack.nextRouterType) {
|
|
856
|
+
lines.push(`- **Router**: ${stack.nextRouterType === "app" ? "App Router" : "Pages Router"}`);
|
|
857
|
+
}
|
|
858
|
+
lines.push(`- **Package Manager**: ${stack.packageManager}`);
|
|
859
|
+
return lines.join("\n");
|
|
860
|
+
}
|
|
861
|
+
function formatStructure(structure) {
|
|
862
|
+
const parts = [];
|
|
863
|
+
if (structure.dirs.length > 0) parts.push(`dirs: \`${structure.dirs.join(", ")}\``);
|
|
864
|
+
if (structure.hasSrcDir && structure.srcDirs.length > 0) {
|
|
865
|
+
parts.push(`src/: \`${structure.srcDirs.join(", ")}\``);
|
|
866
|
+
}
|
|
867
|
+
if (structure.hasAppDir) parts.push("App Router (`app/`)");
|
|
868
|
+
if (structure.hasPagesDir) parts.push("Pages Router (`pages/`)");
|
|
869
|
+
return parts.join(" | ") || "Standard layout";
|
|
572
870
|
}
|
|
573
871
|
function capitalize(s) {
|
|
574
872
|
if (!s) return s;
|
|
@@ -589,21 +887,77 @@ function capitalize(s) {
|
|
|
589
887
|
}
|
|
590
888
|
|
|
591
889
|
// src/enhancer/index.ts
|
|
592
|
-
function enhance(rawPrompt, scan, contextFiles) {
|
|
593
|
-
const intent = analyzeIntent(rawPrompt);
|
|
594
|
-
const injections = getInjections(scan.stack, intent);
|
|
890
|
+
function enhance(rawPrompt, scan, contextFiles, projectInstructions = "", gitContext = "", intentOverride, config) {
|
|
891
|
+
const intent = intentOverride ?? analyzeIntent(rawPrompt);
|
|
892
|
+
const injections = getInjections(scan.stack, intent, config);
|
|
595
893
|
const enhanced = buildEnhancedPrompt({
|
|
596
894
|
stack: scan.stack,
|
|
597
895
|
structure: scan.structure,
|
|
598
896
|
intent,
|
|
599
897
|
contextFiles,
|
|
600
|
-
injections
|
|
898
|
+
injections,
|
|
899
|
+
projectInstructions,
|
|
900
|
+
gitContext
|
|
601
901
|
});
|
|
602
902
|
return { original: rawPrompt, enhanced, intent, stack: scan.stack };
|
|
603
903
|
}
|
|
604
904
|
|
|
605
|
-
// src/
|
|
905
|
+
// src/enhancer/planner.ts
|
|
606
906
|
import Anthropic from "@anthropic-ai/sdk";
|
|
907
|
+
async function decomposeToPlan(rawPrompt, intent, stack, apiKey) {
|
|
908
|
+
const client = new Anthropic({ apiKey });
|
|
909
|
+
const stackSummary = `${stack.frameworks.join(", ")} / ${stack.language}`;
|
|
910
|
+
const systemPrompt = `You are a software architect. Break complex development tasks into sequential, focused implementation steps.
|
|
911
|
+
Each step should be small enough to implement in a single AI session.
|
|
912
|
+
Return valid JSON only \u2014 no prose, no markdown fences.`;
|
|
913
|
+
const userPrompt = `Task: "${rawPrompt}"
|
|
914
|
+
Stack: ${stackSummary}
|
|
915
|
+
|
|
916
|
+
Break this into ordered implementation steps. Each step must be self-contained and reference the previous step's outputs when needed.
|
|
917
|
+
|
|
918
|
+
Return JSON with this exact shape:
|
|
919
|
+
{
|
|
920
|
+
"steps": [
|
|
921
|
+
{
|
|
922
|
+
"step": 1,
|
|
923
|
+
"title": "short title",
|
|
924
|
+
"prompt": "the full focused prompt for this step, including what was done in previous steps",
|
|
925
|
+
"dependsOn": []
|
|
926
|
+
}
|
|
927
|
+
]
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
Rules:
|
|
931
|
+
- 2\u20136 steps maximum
|
|
932
|
+
- Each prompt should be specific and actionable
|
|
933
|
+
- Later steps must mention what the earlier steps created
|
|
934
|
+
- Do not include setup/install steps unless strictly necessary`;
|
|
935
|
+
const response = await client.messages.create({
|
|
936
|
+
model: "claude-haiku-4-5-20251001",
|
|
937
|
+
max_tokens: 2048,
|
|
938
|
+
system: systemPrompt,
|
|
939
|
+
messages: [{ role: "user", content: userPrompt }]
|
|
940
|
+
});
|
|
941
|
+
const text = response.content[0]?.type === "text" ? response.content[0].text : "{}";
|
|
942
|
+
try {
|
|
943
|
+
const parsed = JSON.parse(text);
|
|
944
|
+
return parsed;
|
|
945
|
+
} catch {
|
|
946
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
947
|
+
if (jsonMatch) {
|
|
948
|
+
return JSON.parse(jsonMatch[0]);
|
|
949
|
+
}
|
|
950
|
+
throw new Error("Failed to parse plan JSON from AI response");
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// src/providers/claude.ts
|
|
955
|
+
import Anthropic2 from "@anthropic-ai/sdk";
|
|
956
|
+
var MODEL_BY_COMPLEXITY = {
|
|
957
|
+
simple: "claude-haiku-4-5-20251001",
|
|
958
|
+
feature: "claude-sonnet-4-6",
|
|
959
|
+
system: "claude-opus-4-7"
|
|
960
|
+
};
|
|
607
961
|
var ClaudeProvider = class {
|
|
608
962
|
name = "Claude";
|
|
609
963
|
client;
|
|
@@ -615,10 +969,16 @@ var ClaudeProvider = class {
|
|
|
615
969
|
"ANTHROPIC_API_KEY environment variable is not set."
|
|
616
970
|
);
|
|
617
971
|
}
|
|
618
|
-
this.client = new
|
|
972
|
+
this.client = new Anthropic2({ apiKey: config.apiKey });
|
|
619
973
|
this.model = config.model;
|
|
620
974
|
}
|
|
621
|
-
async send(prompt) {
|
|
975
|
+
async send(prompt, options) {
|
|
976
|
+
const complexity = options?.["complexity"];
|
|
977
|
+
if (complexity && MODEL_BY_COMPLEXITY[complexity] && this.model === "claude-sonnet-4-6") {
|
|
978
|
+
this.model = MODEL_BY_COMPLEXITY[complexity];
|
|
979
|
+
}
|
|
980
|
+
console.log(` Model: ${this.model}
|
|
981
|
+
`);
|
|
622
982
|
const stream = await this.client.messages.create({
|
|
623
983
|
model: this.model,
|
|
624
984
|
max_tokens: 8192,
|
|
@@ -626,12 +986,22 @@ var ClaudeProvider = class {
|
|
|
626
986
|
messages: [{ role: "user", content: prompt }],
|
|
627
987
|
stream: true
|
|
628
988
|
});
|
|
989
|
+
let inputTokens = 0;
|
|
990
|
+
let outputTokens = 0;
|
|
629
991
|
for await (const event of stream) {
|
|
630
992
|
if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
|
|
631
993
|
process.stdout.write(event.delta.text);
|
|
632
994
|
}
|
|
995
|
+
if (event.type === "message_start" && event.message.usage) {
|
|
996
|
+
inputTokens = event.message.usage.input_tokens;
|
|
997
|
+
}
|
|
998
|
+
if (event.type === "message_delta" && event.usage) {
|
|
999
|
+
outputTokens = event.usage.output_tokens;
|
|
1000
|
+
}
|
|
633
1001
|
}
|
|
634
1002
|
process.stdout.write("\n");
|
|
1003
|
+
const onUsage = options?.["onUsage"];
|
|
1004
|
+
onUsage?.({ inputTokens, outputTokens, model: this.model });
|
|
635
1005
|
}
|
|
636
1006
|
};
|
|
637
1007
|
|
|
@@ -858,8 +1228,9 @@ program.command("<prompt>", { isDefault: true }).description("Enhance a prompt a
|
|
|
858
1228
|
Examples:
|
|
859
1229
|
enhance "build a login page"
|
|
860
1230
|
enhance "fix the auth bug" --dry-run
|
|
861
|
-
enhance "create dashboard" --
|
|
862
|
-
enhance "refactor user service" --provider codex
|
|
1231
|
+
enhance "create dashboard" --confirm --verbose
|
|
1232
|
+
enhance "refactor user service" --provider codex
|
|
1233
|
+
enhance "add auth" --action add --feature auth`).option("-p, --provider <name>", "AI provider: claude | codex | opencode").option("--dry-run", "Print enhanced prompt without sending to AI").option("--print-prompt", "Print enhanced prompt before sending to AI").option("--preview", "Color-coded prompt preview with per-section token counts").option("--verbose", "Show detected stack, intent, and context stats").option("--confirm", "Show detected intent and ask for approval before enhancing").option("--plan", "Decompose complex task into sequential sub-prompts (uses AI)").option("--action <action>", "Override detected action (create|fix|refactor|explain|add|delete)").option("--entity <entity>", "Override detected entity (page|component|api|hook|util|config|style)").option("--feature <feature>", "Override detected feature (auth|payment|user|upload|...)").action(async (rawPrompt, opts) => {
|
|
863
1234
|
if (!rawPrompt) {
|
|
864
1235
|
program.help();
|
|
865
1236
|
return;
|
|
@@ -877,7 +1248,41 @@ Examples:
|
|
|
877
1248
|
await setCached(cwd, scan);
|
|
878
1249
|
}
|
|
879
1250
|
spinner.text = "Analyzing intent...";
|
|
880
|
-
|
|
1251
|
+
let intent = analyzeIntent(rawPrompt);
|
|
1252
|
+
if (opts.action) intent = { ...intent, action: opts.action };
|
|
1253
|
+
if (opts.entity) intent = { ...intent, entity: opts.entity };
|
|
1254
|
+
if (opts.feature) intent = { ...intent, feature: opts.feature };
|
|
1255
|
+
if (opts.plan) {
|
|
1256
|
+
if (!config.apiKey) {
|
|
1257
|
+
error("ANTHROPIC_API_KEY required for --plan");
|
|
1258
|
+
process.exit(1);
|
|
1259
|
+
}
|
|
1260
|
+
spinner.text = "Decomposing task into steps...";
|
|
1261
|
+
const plan = await decomposeToPlan(rawPrompt, intent, scan.stack, config.apiKey);
|
|
1262
|
+
spinner.stop();
|
|
1263
|
+
console.log("\n" + chalk3.bold(`Implementation Plan (${plan.steps.length} steps)
|
|
1264
|
+
`));
|
|
1265
|
+
for (const step of plan.steps) {
|
|
1266
|
+
console.log(chalk3.cyan(`Step ${step.step}: ${step.title}`));
|
|
1267
|
+
if (step.dependsOn.length > 0) {
|
|
1268
|
+
console.log(chalk3.dim(` Depends on: Step ${step.dependsOn.join(", ")}`));
|
|
1269
|
+
}
|
|
1270
|
+
console.log(chalk3.dim("\u2500".repeat(60)));
|
|
1271
|
+
console.log(step.prompt);
|
|
1272
|
+
console.log();
|
|
1273
|
+
}
|
|
1274
|
+
process.exit(0);
|
|
1275
|
+
}
|
|
1276
|
+
if (opts.confirm) {
|
|
1277
|
+
spinner.stop();
|
|
1278
|
+
printIntentSummary(intent);
|
|
1279
|
+
const approved = await confirmPrompt("Proceed with this intent? [Y/n] ");
|
|
1280
|
+
if (!approved) {
|
|
1281
|
+
console.log(chalk3.yellow("\nAborted. Use --action / --entity / --feature to override detection.\n"));
|
|
1282
|
+
process.exit(0);
|
|
1283
|
+
}
|
|
1284
|
+
spinner.start("Optimizing context...");
|
|
1285
|
+
}
|
|
881
1286
|
if (opts.verbose) {
|
|
882
1287
|
spinner.stop();
|
|
883
1288
|
console.log("\nStack detected:", JSON.stringify(scan.stack, null, 2));
|
|
@@ -885,11 +1290,26 @@ Examples:
|
|
|
885
1290
|
spinner.start("Optimizing context...");
|
|
886
1291
|
}
|
|
887
1292
|
spinner.text = "Optimizing context...";
|
|
888
|
-
const contextFiles = await
|
|
1293
|
+
const [{ contextFiles, gitContext }, projectInstructions] = await Promise.all([
|
|
1294
|
+
findRelevantFiles(cwd, intent, scan.structure, config.maxContextTokens, config.alwaysInclude ?? []),
|
|
1295
|
+
findProjectInstructions(cwd)
|
|
1296
|
+
]);
|
|
1297
|
+
if (opts.verbose) {
|
|
1298
|
+
spinner.stop();
|
|
1299
|
+
if (projectInstructions) console.log(chalk3.dim(" Project instructions: found (CLAUDE.md / AGENTS.md)"));
|
|
1300
|
+
if (gitContext) console.log(chalk3.dim(" Git context: recent changes detected"));
|
|
1301
|
+
spinner.start("Enhancing prompt...");
|
|
1302
|
+
}
|
|
889
1303
|
spinner.text = "Enhancing prompt...";
|
|
890
|
-
const enhanced = enhance(rawPrompt, scan, contextFiles);
|
|
1304
|
+
const enhanced = enhance(rawPrompt, scan, contextFiles, projectInstructions, gitContext, intent, config);
|
|
891
1305
|
spinner.stop();
|
|
892
|
-
if (opts.
|
|
1306
|
+
if (opts.preview) {
|
|
1307
|
+
printColoredPreview(enhanced.enhanced);
|
|
1308
|
+
if (opts.dryRun) process.exit(0);
|
|
1309
|
+
const go = await confirmPrompt("Send to AI? [Y/n] ");
|
|
1310
|
+
if (!go) process.exit(0);
|
|
1311
|
+
spinner.start(`Sending to AI...`);
|
|
1312
|
+
} else if (opts.dryRun || opts.printPrompt) {
|
|
893
1313
|
console.log("\n\u2500\u2500\u2500 Enhanced Prompt \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
894
1314
|
console.log(enhanced.enhanced);
|
|
895
1315
|
console.log("\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
@@ -901,7 +1321,14 @@ Examples:
|
|
|
901
1321
|
console.log(`
|
|
902
1322
|
[${provider.name}]
|
|
903
1323
|
`);
|
|
904
|
-
await provider.send(enhanced.enhanced
|
|
1324
|
+
await provider.send(enhanced.enhanced, {
|
|
1325
|
+
complexity: enhanced.intent.complexity,
|
|
1326
|
+
onUsage: (usage) => {
|
|
1327
|
+
const cost = estimateCost(usage.model, usage.inputTokens, usage.outputTokens);
|
|
1328
|
+
console.log(chalk3.dim(`
|
|
1329
|
+
Tokens: ${usage.inputTokens} in / ${usage.outputTokens} out \xB7 ~$${cost}`));
|
|
1330
|
+
}
|
|
1331
|
+
});
|
|
905
1332
|
} catch (err) {
|
|
906
1333
|
spinner.stop();
|
|
907
1334
|
if (err instanceof EnhanceError) {
|
|
@@ -916,3 +1343,69 @@ Examples:
|
|
|
916
1343
|
}
|
|
917
1344
|
});
|
|
918
1345
|
program.parse();
|
|
1346
|
+
function printIntentSummary(intent) {
|
|
1347
|
+
const confidencePct = Math.round(intent.confidence * 100);
|
|
1348
|
+
const confidenceColor = intent.confidence >= 0.6 ? chalk3.green : intent.confidence >= 0.3 ? chalk3.yellow : chalk3.red;
|
|
1349
|
+
console.log("\n" + chalk3.bold("Detected Intent:"));
|
|
1350
|
+
console.log(` ${chalk3.cyan("Action")}: ${intent.action}`);
|
|
1351
|
+
console.log(` ${chalk3.cyan("Entity")}: ${intent.entity}`);
|
|
1352
|
+
console.log(` ${chalk3.cyan("Feature")}: ${intent.feature}`);
|
|
1353
|
+
console.log(` ${chalk3.cyan("Scope")}: ${intent.scope}`);
|
|
1354
|
+
console.log(` ${chalk3.cyan("Complexity")}: ${intent.complexity}`);
|
|
1355
|
+
console.log(` ${chalk3.cyan("Confidence")}: ${confidenceColor(`${confidencePct}%`)}`);
|
|
1356
|
+
console.log();
|
|
1357
|
+
}
|
|
1358
|
+
async function confirmPrompt(question) {
|
|
1359
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1360
|
+
return new Promise((resolve2) => {
|
|
1361
|
+
rl.question(question, (answer) => {
|
|
1362
|
+
rl.close();
|
|
1363
|
+
resolve2(answer.toLowerCase() !== "n" && answer.toLowerCase() !== "no");
|
|
1364
|
+
});
|
|
1365
|
+
});
|
|
1366
|
+
}
|
|
1367
|
+
function printColoredPreview(prompt) {
|
|
1368
|
+
const SECTION_COLORS = {
|
|
1369
|
+
"Project Instructions": chalk3.magenta,
|
|
1370
|
+
"Project Context": chalk3.blue,
|
|
1371
|
+
"Task": chalk3.yellow,
|
|
1372
|
+
"Requirements": chalk3.green,
|
|
1373
|
+
"Recent Changes": chalk3.cyan,
|
|
1374
|
+
"Relevant Code": chalk3.dim,
|
|
1375
|
+
"Code": chalk3.dim,
|
|
1376
|
+
"Expected Output": chalk3.white
|
|
1377
|
+
};
|
|
1378
|
+
const CHARS_PER_TOKEN2 = 4;
|
|
1379
|
+
const sections = prompt.split(/\n---\n/);
|
|
1380
|
+
const totalTokens = Math.ceil(prompt.length / CHARS_PER_TOKEN2);
|
|
1381
|
+
console.log("\n" + chalk3.bold("\u2500\u2500\u2500 Enhanced Prompt Preview \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
1382
|
+
console.log(chalk3.dim(`Total: ~${totalTokens} tokens
|
|
1383
|
+
`));
|
|
1384
|
+
for (const section of sections) {
|
|
1385
|
+
const headerMatch = section.match(/^## (.+)/m);
|
|
1386
|
+
const headerName = headerMatch?.[1]?.trim() ?? "Section";
|
|
1387
|
+
const colorFn = SECTION_COLORS[headerName] ?? chalk3.white;
|
|
1388
|
+
const sectionTokens = Math.ceil(section.length / CHARS_PER_TOKEN2);
|
|
1389
|
+
console.log(colorFn(chalk3.bold(`## ${headerName}`)) + chalk3.dim(` (~${sectionTokens} tok)`));
|
|
1390
|
+
const body = section.replace(/^## .+\n?/, "").trim();
|
|
1391
|
+
if (body) {
|
|
1392
|
+
const lines = body.split("\n");
|
|
1393
|
+
const preview = lines.slice(0, 8).join("\n");
|
|
1394
|
+
const rest = lines.length > 8 ? chalk3.dim(`
|
|
1395
|
+
... (${lines.length - 8} more lines)`) : "";
|
|
1396
|
+
console.log(colorFn(preview) + rest);
|
|
1397
|
+
}
|
|
1398
|
+
console.log();
|
|
1399
|
+
}
|
|
1400
|
+
console.log(chalk3.bold("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
|
|
1401
|
+
}
|
|
1402
|
+
var MODEL_PRICING = {
|
|
1403
|
+
"claude-haiku-4-5-20251001": { input: 0.8, output: 4 },
|
|
1404
|
+
"claude-sonnet-4-6": { input: 3, output: 15 },
|
|
1405
|
+
"claude-opus-4-7": { input: 15, output: 75 }
|
|
1406
|
+
};
|
|
1407
|
+
function estimateCost(model, inputTokens, outputTokens) {
|
|
1408
|
+
const pricing = MODEL_PRICING[model] ?? MODEL_PRICING["claude-sonnet-4-6"];
|
|
1409
|
+
const cost = (inputTokens * pricing.input + outputTokens * pricing.output) / 1e6;
|
|
1410
|
+
return cost < 1e-3 ? "<0.001" : cost.toFixed(4);
|
|
1411
|
+
}
|