claude-code-workflow 6.3.4 → 6.3.6
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/.claude/agents/issue-plan-agent.md +859 -0
- package/.claude/agents/issue-queue-agent.md +702 -0
- package/.claude/commands/issue/execute.md +453 -0
- package/.claude/commands/issue/manage.md +865 -0
- package/.claude/commands/issue/new.md +484 -0
- package/.claude/commands/issue/plan.md +421 -0
- package/.claude/commands/issue/queue.md +354 -0
- package/.claude/commands/{clean.md → workflow/clean.md} +5 -5
- package/.claude/commands/workflow/docs/analyze.md +1467 -0
- package/.claude/commands/workflow/docs/copyright.md +1265 -0
- package/.claude/commands/workflow/execute.md +0 -1
- package/.claude/commands/workflow/tools/conflict-resolution.md +76 -240
- package/.claude/commands/workflow/tools/context-gather.md +0 -2
- package/.claude/commands/workflow/tools/task-generate-agent.md +81 -8
- package/.claude/commands/workflow/tools/task-generate-tdd.md +0 -9
- package/.claude/commands/workflow/tools/test-context-gather.md +2 -3
- package/.claude/commands/workflow/tools/test-task-generate.md +0 -2
- package/.claude/skills/_shared/mermaid-utils.md +584 -0
- package/.claude/skills/command-guide/reference/agents/action-planning-agent.md +0 -2
- package/.claude/skills/command-guide/reference/commands/workflow/execute.md +1 -1
- package/.claude/skills/command-guide/reference/commands/workflow/tools/context-gather.md +1 -2
- package/.claude/skills/command-guide/reference/commands/workflow/tools/task-generate-tdd.md +1 -8
- package/.claude/skills/command-guide/reference/commands/workflow/tools/test-context-gather.md +1 -4
- package/.claude/skills/command-guide/reference/commands/workflow/tools/test-task-generate.md +0 -2
- package/.claude/skills/copyright-docs/SKILL.md +132 -0
- package/.claude/skills/copyright-docs/phases/01-metadata-collection.md +78 -0
- package/.claude/skills/copyright-docs/phases/01.5-project-exploration.md +150 -0
- package/.claude/skills/copyright-docs/phases/02-deep-analysis.md +664 -0
- package/.claude/skills/copyright-docs/phases/02.5-consolidation.md +192 -0
- package/.claude/skills/copyright-docs/phases/04-document-assembly.md +261 -0
- package/.claude/skills/copyright-docs/phases/05-compliance-refinement.md +192 -0
- package/.claude/skills/copyright-docs/specs/cpcc-requirements.md +121 -0
- package/.claude/skills/copyright-docs/templates/agent-base.md +200 -0
- package/.claude/skills/project-analyze/SKILL.md +162 -0
- package/.claude/skills/project-analyze/phases/01-requirements-discovery.md +79 -0
- package/.claude/skills/project-analyze/phases/02-project-exploration.md +176 -0
- package/.claude/skills/project-analyze/phases/03-deep-analysis.md +854 -0
- package/.claude/skills/project-analyze/phases/03.5-consolidation.md +233 -0
- package/.claude/skills/project-analyze/phases/04-report-generation.md +217 -0
- package/.claude/skills/project-analyze/phases/05-iterative-refinement.md +124 -0
- package/.claude/skills/project-analyze/specs/quality-standards.md +115 -0
- package/.claude/skills/project-analyze/specs/writing-style.md +152 -0
- package/.claude/workflows/cli-templates/schemas/conflict-resolution-schema.json +79 -65
- package/.claude/workflows/cli-templates/schemas/issue-task-jsonl-schema.json +136 -0
- package/.claude/workflows/cli-templates/schemas/issues-jsonl-schema.json +74 -0
- package/.claude/workflows/cli-templates/schemas/queue-schema.json +136 -0
- package/.claude/workflows/cli-templates/schemas/registry-schema.json +94 -0
- package/.claude/workflows/cli-templates/schemas/solution-schema.json +120 -0
- package/.claude/workflows/cli-templates/schemas/solutions-jsonl-schema.json +125 -0
- package/.codex/prompts/issue-execute.md +266 -0
- package/README.md +11 -1
- package/ccw/dist/cli.d.ts.map +1 -1
- package/ccw/dist/cli.js +25 -0
- package/ccw/dist/cli.js.map +1 -1
- package/ccw/dist/commands/cli.d.ts.map +1 -1
- package/ccw/dist/commands/cli.js +46 -8
- package/ccw/dist/commands/cli.js.map +1 -1
- package/ccw/dist/commands/issue.d.ts +21 -0
- package/ccw/dist/commands/issue.d.ts.map +1 -0
- package/ccw/dist/commands/issue.js +895 -0
- package/ccw/dist/commands/issue.js.map +1 -0
- package/ccw/dist/core/dashboard-generator-patch.js +1 -0
- package/ccw/dist/core/dashboard-generator-patch.js.map +1 -1
- package/ccw/dist/core/routes/cli-routes.js +2 -2
- package/ccw/dist/core/routes/cli-routes.js.map +1 -1
- package/ccw/dist/core/routes/issue-routes.d.ts +34 -0
- package/ccw/dist/core/routes/issue-routes.d.ts.map +1 -0
- package/ccw/dist/core/routes/issue-routes.js +487 -0
- package/ccw/dist/core/routes/issue-routes.js.map +1 -0
- package/ccw/dist/core/server.d.ts.map +1 -1
- package/ccw/dist/core/server.js +17 -2
- package/ccw/dist/core/server.js.map +1 -1
- package/ccw/dist/tools/claude-cli-tools.d.ts +7 -3
- package/ccw/dist/tools/claude-cli-tools.d.ts.map +1 -1
- package/ccw/dist/tools/claude-cli-tools.js +31 -17
- package/ccw/dist/tools/claude-cli-tools.js.map +1 -1
- package/ccw/dist/tools/smart-search.d.ts +25 -0
- package/ccw/dist/tools/smart-search.d.ts.map +1 -1
- package/ccw/dist/tools/smart-search.js +121 -17
- package/ccw/dist/tools/smart-search.js.map +1 -1
- package/ccw/src/cli.ts +26 -0
- package/ccw/src/commands/cli.ts +49 -7
- package/ccw/src/commands/issue.ts +1184 -0
- package/ccw/src/core/dashboard-generator-patch.ts +1 -0
- package/ccw/src/core/routes/cli-routes.ts +3 -3
- package/ccw/src/core/routes/issue-routes.ts +559 -0
- package/ccw/src/core/server.ts +17 -2
- package/ccw/src/templates/dashboard-css/32-issue-manager.css +2544 -0
- package/ccw/src/templates/dashboard-css/33-cli-stream-viewer.css +467 -0
- package/ccw/src/templates/dashboard-js/components/cli-history.js +40 -13
- package/ccw/src/templates/dashboard-js/components/cli-status.js +26 -2
- package/ccw/src/templates/dashboard-js/components/cli-stream-viewer.js +461 -0
- package/ccw/src/templates/dashboard-js/components/navigation.js +8 -0
- package/ccw/src/templates/dashboard-js/components/notifications.js +16 -0
- package/ccw/src/templates/dashboard-js/i18n.js +290 -2
- package/ccw/src/templates/dashboard-js/views/cli-manager.js +5 -0
- package/ccw/src/templates/dashboard-js/views/history.js +19 -4
- package/ccw/src/templates/dashboard-js/views/hook-manager.js +11 -5
- package/ccw/src/templates/dashboard-js/views/issue-manager.js +1546 -0
- package/ccw/src/templates/dashboard.html +55 -0
- package/ccw/src/tools/claude-cli-tools.ts +37 -20
- package/ccw/src/tools/smart-search.ts +157 -16
- package/codex-lens/src/codexlens/__pycache__/config.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/config.py +5 -0
- package/codex-lens/src/codexlens/search/__pycache__/hybrid_search.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/__pycache__/ranking.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/search/hybrid_search.py +144 -11
- package/codex-lens/src/codexlens/search/ranking.py +267 -1
- package/codex-lens/src/codexlens/semantic/__pycache__/chunker.cpython-313.pyc +0 -0
- package/codex-lens/src/codexlens/semantic/chunker.py +55 -10
- package/package.json +2 -2
|
@@ -275,6 +275,18 @@
|
|
|
275
275
|
</div>
|
|
276
276
|
</div>
|
|
277
277
|
</div>
|
|
278
|
+
<!-- CLI Stream Viewer Button -->
|
|
279
|
+
<button class="cli-stream-btn p-1.5 text-muted-foreground hover:text-foreground hover:bg-hover rounded relative"
|
|
280
|
+
id="cliStreamBtn"
|
|
281
|
+
onclick="toggleCliStreamViewer()"
|
|
282
|
+
data-i18n-title="header.cliStream"
|
|
283
|
+
title="CLI Stream Viewer">
|
|
284
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
285
|
+
<polyline points="4 17 10 11 4 5"/>
|
|
286
|
+
<line x1="12" y1="19" x2="20" y2="19"/>
|
|
287
|
+
</svg>
|
|
288
|
+
<span class="cli-stream-badge" id="cliStreamBadge"></span>
|
|
289
|
+
</button>
|
|
278
290
|
<!-- Refresh Button -->
|
|
279
291
|
<button class="refresh-btn p-1.5 text-muted-foreground hover:text-foreground hover:bg-hover rounded" id="refreshWorkspace" data-i18n-title="header.refreshWorkspace" title="Refresh workspace">
|
|
280
292
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
@@ -394,6 +406,21 @@
|
|
|
394
406
|
</ul>
|
|
395
407
|
</div>
|
|
396
408
|
|
|
409
|
+
<!-- Issues Section -->
|
|
410
|
+
<div class="mb-2" id="issuesNav">
|
|
411
|
+
<div class="flex items-center px-4 py-2 text-sm font-semibold text-muted-foreground uppercase tracking-wide">
|
|
412
|
+
<i data-lucide="clipboard-list" class="nav-section-icon mr-2"></i>
|
|
413
|
+
<span class="nav-section-title" data-i18n="nav.issues">Issues</span>
|
|
414
|
+
</div>
|
|
415
|
+
<ul class="space-y-0.5">
|
|
416
|
+
<li class="nav-item flex items-center gap-2 px-3 py-2.5 text-sm text-muted-foreground hover:bg-hover hover:text-foreground rounded cursor-pointer transition-colors" data-view="issue-manager" data-tooltip="Issue Manager">
|
|
417
|
+
<i data-lucide="list-checks" class="nav-icon"></i>
|
|
418
|
+
<span class="nav-text flex-1" data-i18n="nav.issueManager">Manager</span>
|
|
419
|
+
<span class="badge px-2 py-0.5 text-xs font-semibold rounded-full bg-hover text-muted-foreground" id="badgeIssues">0</span>
|
|
420
|
+
</li>
|
|
421
|
+
</ul>
|
|
422
|
+
</div>
|
|
423
|
+
|
|
397
424
|
<!-- MCP Servers Section -->
|
|
398
425
|
<div class="mb-2" id="mcpServersNav">
|
|
399
426
|
<div class="flex items-center px-4 py-2 text-sm font-semibold text-muted-foreground uppercase tracking-wide">
|
|
@@ -578,6 +605,34 @@
|
|
|
578
605
|
<div class="drawer-overlay hidden fixed inset-0 bg-black/50 z-40" id="drawerOverlay" onclick="closeTaskDrawer()"></div>
|
|
579
606
|
</div>
|
|
580
607
|
|
|
608
|
+
<!-- CLI Stream Viewer Panel -->
|
|
609
|
+
<div class="cli-stream-viewer" id="cliStreamViewer">
|
|
610
|
+
<div class="cli-stream-header">
|
|
611
|
+
<div class="cli-stream-title">
|
|
612
|
+
<i data-lucide="terminal"></i>
|
|
613
|
+
<span data-i18n="cliStream.title">CLI Stream</span>
|
|
614
|
+
<span class="cli-stream-count-badge" id="cliStreamCountBadge">0</span>
|
|
615
|
+
</div>
|
|
616
|
+
<div class="cli-stream-actions">
|
|
617
|
+
<button class="cli-stream-action-btn" onclick="clearCompletedStreams()" data-i18n="cliStream.clearCompleted">
|
|
618
|
+
<i data-lucide="trash-2"></i>
|
|
619
|
+
<span>Clear</span>
|
|
620
|
+
</button>
|
|
621
|
+
<button class="cli-stream-close-btn" onclick="toggleCliStreamViewer()" title="Close">×</button>
|
|
622
|
+
</div>
|
|
623
|
+
</div>
|
|
624
|
+
<div class="cli-stream-tabs" id="cliStreamTabs">
|
|
625
|
+
<!-- Dynamic tabs -->
|
|
626
|
+
</div>
|
|
627
|
+
<div class="cli-stream-content" id="cliStreamContent">
|
|
628
|
+
<!-- Terminal output -->
|
|
629
|
+
</div>
|
|
630
|
+
<div class="cli-stream-status" id="cliStreamStatus">
|
|
631
|
+
<!-- Status bar -->
|
|
632
|
+
</div>
|
|
633
|
+
</div>
|
|
634
|
+
<div class="cli-stream-overlay" id="cliStreamOverlay" onclick="toggleCliStreamViewer()"></div>
|
|
635
|
+
|
|
581
636
|
<!-- Markdown Preview Modal -->
|
|
582
637
|
<div id="markdownModal" class="markdown-modal hidden fixed inset-0 z-[100] flex items-center justify-center">
|
|
583
638
|
<div class="markdown-modal-backdrop absolute inset-0 bg-black/60" onclick="closeMarkdownModal()"></div>
|
|
@@ -42,7 +42,7 @@ export interface ClaudeCliToolsConfig {
|
|
|
42
42
|
nativeResume: boolean;
|
|
43
43
|
recursiveQuery: boolean;
|
|
44
44
|
cache: ClaudeCacheSettings;
|
|
45
|
-
codeIndexMcp: 'codexlens' | 'ace'; // Code Index MCP provider
|
|
45
|
+
codeIndexMcp: 'codexlens' | 'ace' | 'none'; // Code Index MCP provider
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -308,7 +308,7 @@ export function getClaudeCliToolsInfo(projectDir: string): {
|
|
|
308
308
|
*/
|
|
309
309
|
export function updateCodeIndexMcp(
|
|
310
310
|
projectDir: string,
|
|
311
|
-
provider: 'codexlens' | 'ace'
|
|
311
|
+
provider: 'codexlens' | 'ace' | 'none'
|
|
312
312
|
): { success: boolean; error?: string; config?: ClaudeCliToolsConfig } {
|
|
313
313
|
try {
|
|
314
314
|
// Update config
|
|
@@ -319,21 +319,28 @@ export function updateCodeIndexMcp(
|
|
|
319
319
|
// Only update global CLAUDE.md (consistent with Chinese response / Windows platform)
|
|
320
320
|
const globalClaudeMdPath = path.join(os.homedir(), '.claude', 'CLAUDE.md');
|
|
321
321
|
|
|
322
|
+
// Define patterns for all formats
|
|
323
|
+
const codexlensPattern = /@~\/\.claude\/workflows\/context-tools\.md/g;
|
|
324
|
+
const acePattern = /@~\/\.claude\/workflows\/context-tools-ace\.md/g;
|
|
325
|
+
const nonePattern = /@~\/\.claude\/workflows\/context-tools-none\.md/g;
|
|
326
|
+
|
|
327
|
+
// Determine target file based on provider
|
|
328
|
+
const targetFile = provider === 'ace'
|
|
329
|
+
? '@~/.claude/workflows/context-tools-ace.md'
|
|
330
|
+
: provider === 'none'
|
|
331
|
+
? '@~/.claude/workflows/context-tools-none.md'
|
|
332
|
+
: '@~/.claude/workflows/context-tools.md';
|
|
333
|
+
|
|
322
334
|
if (!fs.existsSync(globalClaudeMdPath)) {
|
|
323
335
|
// If global CLAUDE.md doesn't exist, check project-level
|
|
324
336
|
const projectClaudeMdPath = path.join(projectDir, '.claude', 'CLAUDE.md');
|
|
325
337
|
if (fs.existsSync(projectClaudeMdPath)) {
|
|
326
338
|
let content = fs.readFileSync(projectClaudeMdPath, 'utf-8');
|
|
327
339
|
|
|
328
|
-
//
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
if (provider === 'ace') {
|
|
333
|
-
content = content.replace(codexlensPattern, '@~/.claude/workflows/context-tools-ace.md');
|
|
334
|
-
} else {
|
|
335
|
-
content = content.replace(acePattern, '@~/.claude/workflows/context-tools.md');
|
|
336
|
-
}
|
|
340
|
+
// Replace any existing pattern with the target
|
|
341
|
+
content = content.replace(codexlensPattern, targetFile);
|
|
342
|
+
content = content.replace(acePattern, targetFile);
|
|
343
|
+
content = content.replace(nonePattern, targetFile);
|
|
337
344
|
|
|
338
345
|
fs.writeFileSync(projectClaudeMdPath, content, 'utf-8');
|
|
339
346
|
console.log(`[claude-cli-tools] Updated project CLAUDE.md to use ${provider} (no global CLAUDE.md found)`);
|
|
@@ -342,14 +349,10 @@ export function updateCodeIndexMcp(
|
|
|
342
349
|
// Update global CLAUDE.md (primary target)
|
|
343
350
|
let content = fs.readFileSync(globalClaudeMdPath, 'utf-8');
|
|
344
351
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
content = content.replace(codexlensPattern, '@~/.claude/workflows/context-tools-ace.md');
|
|
350
|
-
} else {
|
|
351
|
-
content = content.replace(acePattern, '@~/.claude/workflows/context-tools.md');
|
|
352
|
-
}
|
|
352
|
+
// Replace any existing pattern with the target
|
|
353
|
+
content = content.replace(codexlensPattern, targetFile);
|
|
354
|
+
content = content.replace(acePattern, targetFile);
|
|
355
|
+
content = content.replace(nonePattern, targetFile);
|
|
353
356
|
|
|
354
357
|
fs.writeFileSync(globalClaudeMdPath, content, 'utf-8');
|
|
355
358
|
console.log(`[claude-cli-tools] Updated global CLAUDE.md to use ${provider}`);
|
|
@@ -365,7 +368,21 @@ export function updateCodeIndexMcp(
|
|
|
365
368
|
/**
|
|
366
369
|
* Get current Code Index MCP provider
|
|
367
370
|
*/
|
|
368
|
-
export function getCodeIndexMcp(projectDir: string): 'codexlens' | 'ace' {
|
|
371
|
+
export function getCodeIndexMcp(projectDir: string): 'codexlens' | 'ace' | 'none' {
|
|
369
372
|
const config = loadClaudeCliTools(projectDir);
|
|
370
373
|
return config.settings.codeIndexMcp || 'codexlens';
|
|
371
374
|
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Get the context-tools file path based on provider
|
|
378
|
+
*/
|
|
379
|
+
export function getContextToolsPath(provider: 'codexlens' | 'ace' | 'none'): string {
|
|
380
|
+
switch (provider) {
|
|
381
|
+
case 'ace':
|
|
382
|
+
return 'context-tools-ace.md';
|
|
383
|
+
case 'none':
|
|
384
|
+
return 'context-tools-none.md';
|
|
385
|
+
default:
|
|
386
|
+
return 'context-tools.md';
|
|
387
|
+
}
|
|
388
|
+
}
|
|
@@ -24,6 +24,39 @@ import {
|
|
|
24
24
|
import type { ProgressInfo } from './codex-lens.js';
|
|
25
25
|
import { getProjectRoot } from '../utils/path-validator.js';
|
|
26
26
|
|
|
27
|
+
// Timing utilities for performance analysis
|
|
28
|
+
const TIMING_ENABLED = process.env.SMART_SEARCH_TIMING === '1' || process.env.DEBUG?.includes('timing');
|
|
29
|
+
|
|
30
|
+
interface TimingData {
|
|
31
|
+
[key: string]: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function createTimer(): { mark: (name: string) => void; getTimings: () => TimingData; log: () => void } {
|
|
35
|
+
const startTime = performance.now();
|
|
36
|
+
const marks: { name: string; time: number }[] = [];
|
|
37
|
+
let lastMark = startTime;
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
mark(name: string) {
|
|
41
|
+
const now = performance.now();
|
|
42
|
+
marks.push({ name, time: now - lastMark });
|
|
43
|
+
lastMark = now;
|
|
44
|
+
},
|
|
45
|
+
getTimings(): TimingData {
|
|
46
|
+
const timings: TimingData = {};
|
|
47
|
+
marks.forEach(m => { timings[m.name] = Math.round(m.time * 100) / 100; });
|
|
48
|
+
timings['_total'] = Math.round((performance.now() - startTime) * 100) / 100;
|
|
49
|
+
return timings;
|
|
50
|
+
},
|
|
51
|
+
log() {
|
|
52
|
+
if (TIMING_ENABLED) {
|
|
53
|
+
const timings = this.getTimings();
|
|
54
|
+
console.error(`[TIMING] smart-search: ${JSON.stringify(timings)}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
27
60
|
// Define Zod schema for validation
|
|
28
61
|
const ParamsSchema = z.object({
|
|
29
62
|
// Action: search (content), find_files (path/name pattern), init, status
|
|
@@ -48,6 +81,9 @@ const ParamsSchema = z.object({
|
|
|
48
81
|
regex: z.boolean().default(true), // Use regex pattern matching (default: enabled)
|
|
49
82
|
caseSensitive: z.boolean().default(true), // Case sensitivity (default: case-sensitive)
|
|
50
83
|
tokenize: z.boolean().default(true), // Tokenize multi-word queries for OR matching (default: enabled)
|
|
84
|
+
// File type filtering
|
|
85
|
+
excludeExtensions: z.array(z.string()).optional().describe('File extensions to exclude from results (e.g., ["md", "txt"])'),
|
|
86
|
+
codeOnly: z.boolean().default(false).describe('Only return code files (excludes md, txt, json, yaml, xml, etc.)'),
|
|
51
87
|
// Fuzzy matching is implicit in hybrid mode (RRF fusion)
|
|
52
88
|
});
|
|
53
89
|
|
|
@@ -254,6 +290,8 @@ interface SearchMetadata {
|
|
|
254
290
|
tokenized?: boolean; // Whether tokenization was applied
|
|
255
291
|
// Pagination metadata
|
|
256
292
|
pagination?: PaginationInfo;
|
|
293
|
+
// Performance timing data (when SMART_SEARCH_TIMING=1 or DEBUG includes 'timing')
|
|
294
|
+
timing?: TimingData;
|
|
257
295
|
// Init action specific
|
|
258
296
|
action?: string;
|
|
259
297
|
path?: string;
|
|
@@ -1086,7 +1124,8 @@ async function executeCodexLensExactMode(params: Params): Promise<SearchResult>
|
|
|
1086
1124
|
* Requires index with embeddings
|
|
1087
1125
|
*/
|
|
1088
1126
|
async function executeHybridMode(params: Params): Promise<SearchResult> {
|
|
1089
|
-
const
|
|
1127
|
+
const timer = createTimer();
|
|
1128
|
+
const { query, path = '.', maxResults = 5, extraFilesCount = 10, maxContentLength = 200, enrich = false, excludeExtensions, codeOnly = false } = params;
|
|
1090
1129
|
|
|
1091
1130
|
if (!query) {
|
|
1092
1131
|
return {
|
|
@@ -1097,6 +1136,7 @@ async function executeHybridMode(params: Params): Promise<SearchResult> {
|
|
|
1097
1136
|
|
|
1098
1137
|
// Check CodexLens availability
|
|
1099
1138
|
const readyStatus = await ensureCodexLensReady();
|
|
1139
|
+
timer.mark('codexlens_ready_check');
|
|
1100
1140
|
if (!readyStatus.ready) {
|
|
1101
1141
|
return {
|
|
1102
1142
|
success: false,
|
|
@@ -1106,6 +1146,7 @@ async function executeHybridMode(params: Params): Promise<SearchResult> {
|
|
|
1106
1146
|
|
|
1107
1147
|
// Check index status
|
|
1108
1148
|
const indexStatus = await checkIndexStatus(path);
|
|
1149
|
+
timer.mark('index_status_check');
|
|
1109
1150
|
|
|
1110
1151
|
// Request more results to support split (full content + extra files)
|
|
1111
1152
|
const totalToFetch = maxResults + extraFilesCount;
|
|
@@ -1114,8 +1155,10 @@ async function executeHybridMode(params: Params): Promise<SearchResult> {
|
|
|
1114
1155
|
args.push('--enrich');
|
|
1115
1156
|
}
|
|
1116
1157
|
const result = await executeCodexLens(args, { cwd: path });
|
|
1158
|
+
timer.mark('codexlens_search');
|
|
1117
1159
|
|
|
1118
1160
|
if (!result.success) {
|
|
1161
|
+
timer.log();
|
|
1119
1162
|
return {
|
|
1120
1163
|
success: false,
|
|
1121
1164
|
error: result.error,
|
|
@@ -1150,6 +1193,7 @@ async function executeHybridMode(params: Params): Promise<SearchResult> {
|
|
|
1150
1193
|
symbol: item.symbol || null,
|
|
1151
1194
|
};
|
|
1152
1195
|
});
|
|
1196
|
+
timer.mark('parse_results');
|
|
1153
1197
|
|
|
1154
1198
|
initialCount = allResults.length;
|
|
1155
1199
|
|
|
@@ -1159,14 +1203,15 @@ async function executeHybridMode(params: Params): Promise<SearchResult> {
|
|
|
1159
1203
|
allResults = baselineResult.filteredResults;
|
|
1160
1204
|
baselineInfo = baselineResult.baselineInfo;
|
|
1161
1205
|
|
|
1162
|
-
// 1. Filter noisy files (coverage, node_modules, etc.)
|
|
1163
|
-
allResults = filterNoisyFiles(allResults);
|
|
1206
|
+
// 1. Filter noisy files (coverage, node_modules, etc.) and excluded extensions
|
|
1207
|
+
allResults = filterNoisyFiles(allResults, { excludeExtensions, codeOnly });
|
|
1164
1208
|
// 2. Boost results containing query keywords
|
|
1165
1209
|
allResults = applyKeywordBoosting(allResults, query);
|
|
1166
1210
|
// 3. Enforce score diversity (penalize identical scores)
|
|
1167
1211
|
allResults = enforceScoreDiversity(allResults);
|
|
1168
1212
|
// 4. Re-sort by adjusted scores
|
|
1169
1213
|
allResults.sort((a, b) => b.score - a.score);
|
|
1214
|
+
timer.mark('post_processing');
|
|
1170
1215
|
} catch {
|
|
1171
1216
|
return {
|
|
1172
1217
|
success: true,
|
|
@@ -1184,6 +1229,7 @@ async function executeHybridMode(params: Params): Promise<SearchResult> {
|
|
|
1184
1229
|
|
|
1185
1230
|
// Split results: first N with full content, rest as file paths only
|
|
1186
1231
|
const { results, extra_files } = splitResultsWithExtraFiles(allResults, maxResults, extraFilesCount);
|
|
1232
|
+
timer.mark('split_results');
|
|
1187
1233
|
|
|
1188
1234
|
// Build metadata with baseline info if detected
|
|
1189
1235
|
let note = 'Hybrid mode uses RRF fusion (exact + fuzzy + vector) for best results';
|
|
@@ -1191,6 +1237,10 @@ async function executeHybridMode(params: Params): Promise<SearchResult> {
|
|
|
1191
1237
|
note += ` | Filtered ${initialCount - allResults.length} hot-spot results with baseline score ~${baselineInfo.score.toFixed(4)}`;
|
|
1192
1238
|
}
|
|
1193
1239
|
|
|
1240
|
+
// Log timing data
|
|
1241
|
+
timer.log();
|
|
1242
|
+
const timings = timer.getTimings();
|
|
1243
|
+
|
|
1194
1244
|
return {
|
|
1195
1245
|
success: true,
|
|
1196
1246
|
results,
|
|
@@ -1203,22 +1253,82 @@ async function executeHybridMode(params: Params): Promise<SearchResult> {
|
|
|
1203
1253
|
note,
|
|
1204
1254
|
warning: indexStatus.warning,
|
|
1205
1255
|
suggested_weights: getRRFWeights(query),
|
|
1256
|
+
timing: TIMING_ENABLED ? timings : undefined,
|
|
1206
1257
|
},
|
|
1207
1258
|
};
|
|
1208
1259
|
}
|
|
1209
1260
|
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1261
|
+
/**
|
|
1262
|
+
* Query intent used to adapt RRF weights (Python parity).
|
|
1263
|
+
*
|
|
1264
|
+
* Keep this logic aligned with CodexLens Python hybrid search:
|
|
1265
|
+
* `codex-lens/src/codexlens/search/hybrid_search.py`
|
|
1266
|
+
*/
|
|
1267
|
+
export type QueryIntent = 'keyword' | 'semantic' | 'mixed';
|
|
1268
|
+
|
|
1269
|
+
// Python default: vector 60%, exact 30%, fuzzy 10%
|
|
1270
|
+
const DEFAULT_RRF_WEIGHTS = {
|
|
1271
|
+
exact: 0.3,
|
|
1272
|
+
fuzzy: 0.1,
|
|
1273
|
+
vector: 0.6,
|
|
1274
|
+
} as const;
|
|
1275
|
+
|
|
1276
|
+
function normalizeWeights(weights: Record<string, number>): Record<string, number> {
|
|
1277
|
+
const sum = Object.values(weights).reduce((acc, v) => acc + v, 0);
|
|
1278
|
+
if (!Number.isFinite(sum) || sum <= 0) return { ...weights };
|
|
1279
|
+
return Object.fromEntries(Object.entries(weights).map(([k, v]) => [k, v / sum]));
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
/**
|
|
1283
|
+
* Detect query intent using the same heuristic signals as Python:
|
|
1284
|
+
* - Code patterns: `.`, `::`, `->`, CamelCase, snake_case, common code keywords
|
|
1285
|
+
* - Natural language patterns: >5 words, question marks, interrogatives, common verbs
|
|
1286
|
+
*/
|
|
1287
|
+
export function detectQueryIntent(query: string): QueryIntent {
|
|
1288
|
+
const trimmed = query.trim();
|
|
1289
|
+
if (!trimmed) return 'mixed';
|
|
1290
|
+
|
|
1291
|
+
const lower = trimmed.toLowerCase();
|
|
1292
|
+
const wordCount = trimmed.split(/\s+/).filter(Boolean).length;
|
|
1293
|
+
|
|
1294
|
+
const hasCodeSignals =
|
|
1295
|
+
/(::|->|\.)/.test(trimmed) ||
|
|
1296
|
+
/[A-Z][a-z]+[A-Z]/.test(trimmed) ||
|
|
1297
|
+
/\b\w+_\w+\b/.test(trimmed) ||
|
|
1298
|
+
/\b(def|class|function|const|let|var|import|from|return|async|await|interface|type)\b/i.test(lower);
|
|
1299
|
+
|
|
1300
|
+
const hasNaturalSignals =
|
|
1301
|
+
wordCount > 5 ||
|
|
1302
|
+
/\?/.test(trimmed) ||
|
|
1303
|
+
/\b(how|what|why|when|where)\b/i.test(trimmed) ||
|
|
1304
|
+
/\b(handle|explain|fix|implement|create|build|use|find|search|convert|parse|generate|support)\b/i.test(trimmed);
|
|
1305
|
+
|
|
1306
|
+
if (hasCodeSignals && hasNaturalSignals) return 'mixed';
|
|
1307
|
+
if (hasCodeSignals) return 'keyword';
|
|
1308
|
+
if (hasNaturalSignals) return 'semantic';
|
|
1309
|
+
return 'mixed';
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
/**
|
|
1313
|
+
* Intent → weights mapping (Python parity).
|
|
1314
|
+
* - keyword: exact-heavy
|
|
1315
|
+
* - semantic: vector-heavy
|
|
1316
|
+
* - mixed: keep defaults
|
|
1317
|
+
*/
|
|
1318
|
+
export function adjustWeightsByIntent(
|
|
1319
|
+
intent: QueryIntent,
|
|
1320
|
+
baseWeights: Record<string, number>,
|
|
1321
|
+
): Record<string, number> {
|
|
1322
|
+
if (intent === 'keyword') return normalizeWeights({ exact: 0.5, fuzzy: 0.1, vector: 0.4 });
|
|
1323
|
+
if (intent === 'semantic') return normalizeWeights({ exact: 0.2, fuzzy: 0.1, vector: 0.7 });
|
|
1324
|
+
return normalizeWeights({ ...baseWeights });
|
|
1325
|
+
}
|
|
1215
1326
|
|
|
1216
|
-
function getRRFWeights(
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
return RRF_WEIGHTS.default;
|
|
1327
|
+
export function getRRFWeights(
|
|
1328
|
+
query: string,
|
|
1329
|
+
baseWeights: Record<string, number> = DEFAULT_RRF_WEIGHTS,
|
|
1330
|
+
): Record<string, number> {
|
|
1331
|
+
return adjustWeightsByIntent(detectQueryIntent(query), baseWeights);
|
|
1222
1332
|
}
|
|
1223
1333
|
|
|
1224
1334
|
/**
|
|
@@ -1231,7 +1341,29 @@ const FILE_EXCLUDE_REGEXES = [...FILTER_CONFIG.exclude_files].map(pattern =>
|
|
|
1231
1341
|
new RegExp('^' + pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\\\*/g, '.*') + '$')
|
|
1232
1342
|
);
|
|
1233
1343
|
|
|
1234
|
-
|
|
1344
|
+
// Non-code file extensions (for codeOnly filter)
|
|
1345
|
+
const NON_CODE_EXTENSIONS = new Set([
|
|
1346
|
+
'md', 'txt', 'json', 'yaml', 'yml', 'xml', 'csv', 'log',
|
|
1347
|
+
'ini', 'cfg', 'conf', 'toml', 'env', 'properties',
|
|
1348
|
+
'html', 'htm', 'svg', 'png', 'jpg', 'jpeg', 'gif', 'ico', 'webp',
|
|
1349
|
+
'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx',
|
|
1350
|
+
'lock', 'sum', 'mod',
|
|
1351
|
+
]);
|
|
1352
|
+
|
|
1353
|
+
interface FilterOptions {
|
|
1354
|
+
excludeExtensions?: string[];
|
|
1355
|
+
codeOnly?: boolean;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
function filterNoisyFiles(results: SemanticMatch[], options: FilterOptions = {}): SemanticMatch[] {
|
|
1359
|
+
const { excludeExtensions = [], codeOnly = false } = options;
|
|
1360
|
+
|
|
1361
|
+
// Build extension filter set
|
|
1362
|
+
const excludedExtSet = new Set(excludeExtensions.map(ext => ext.toLowerCase().replace(/^\./, '')));
|
|
1363
|
+
if (codeOnly) {
|
|
1364
|
+
NON_CODE_EXTENSIONS.forEach(ext => excludedExtSet.add(ext));
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1235
1367
|
return results.filter(r => {
|
|
1236
1368
|
const filePath = r.file || '';
|
|
1237
1369
|
if (!filePath) return true;
|
|
@@ -1249,6 +1381,14 @@ function filterNoisyFiles(results: SemanticMatch[]): SemanticMatch[] {
|
|
|
1249
1381
|
return false;
|
|
1250
1382
|
}
|
|
1251
1383
|
|
|
1384
|
+
// Extension filter check
|
|
1385
|
+
if (excludedExtSet.size > 0) {
|
|
1386
|
+
const ext = filename.split('.').pop()?.toLowerCase() || '';
|
|
1387
|
+
if (excludedExtSet.has(ext)) {
|
|
1388
|
+
return false;
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1252
1392
|
return true;
|
|
1253
1393
|
});
|
|
1254
1394
|
}
|
|
@@ -1396,10 +1536,11 @@ function filterDominantBaselineScores(
|
|
|
1396
1536
|
*/
|
|
1397
1537
|
function applyRRFFusion(
|
|
1398
1538
|
resultsMap: Map<string, any[]>,
|
|
1399
|
-
|
|
1539
|
+
weightsOrQuery: Record<string, number> | string,
|
|
1400
1540
|
limit: number,
|
|
1401
1541
|
k: number = 60,
|
|
1402
1542
|
): any[] {
|
|
1543
|
+
const weights = typeof weightsOrQuery === 'string' ? getRRFWeights(weightsOrQuery) : weightsOrQuery;
|
|
1403
1544
|
const pathScores = new Map<string, { score: number; result: any; sources: string[] }>();
|
|
1404
1545
|
|
|
1405
1546
|
resultsMap.forEach((results, source) => {
|
|
Binary file
|
|
@@ -103,6 +103,11 @@ class Config:
|
|
|
103
103
|
# Indexing/search optimizations
|
|
104
104
|
global_symbol_index_enabled: bool = True # Enable project-wide symbol index fast path
|
|
105
105
|
|
|
106
|
+
# Optional search reranking (disabled by default)
|
|
107
|
+
enable_reranking: bool = False
|
|
108
|
+
reranking_top_k: int = 50
|
|
109
|
+
symbol_boost_factor: float = 1.5
|
|
110
|
+
|
|
106
111
|
# Multi-endpoint configuration for litellm backend
|
|
107
112
|
embedding_endpoints: List[Dict[str, Any]] = field(default_factory=list)
|
|
108
113
|
# List of endpoint configs: [{"model": "...", "api_key": "...", "api_base": "...", "weight": 1.0}]
|
|
Binary file
|
|
Binary file
|