prjct-cli 0.45.0 → 0.45.4
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/CHANGELOG.md +82 -0
- package/bin/prjct.ts +117 -10
- package/core/__tests__/agentic/memory-system.test.ts +39 -26
- package/core/__tests__/agentic/plan-mode.test.ts +64 -46
- package/core/__tests__/agentic/prompt-builder.test.ts +14 -14
- package/core/__tests__/services/project-index.test.ts +353 -0
- package/core/__tests__/types/fs.test.ts +3 -3
- package/core/__tests__/utils/date-helper.test.ts +10 -10
- package/core/__tests__/utils/output.test.ts +9 -6
- package/core/__tests__/utils/project-commands.test.ts +5 -6
- package/core/agentic/agent-router.ts +9 -10
- package/core/agentic/chain-of-thought.ts +16 -4
- package/core/agentic/command-executor.ts +66 -40
- package/core/agentic/context-builder.ts +8 -5
- package/core/agentic/ground-truth.ts +15 -9
- package/core/agentic/index.ts +145 -152
- package/core/agentic/loop-detector.ts +40 -11
- package/core/agentic/memory-system.ts +98 -35
- package/core/agentic/orchestrator-executor.ts +135 -71
- package/core/agentic/plan-mode.ts +46 -16
- package/core/agentic/prompt-builder.ts +108 -42
- package/core/agentic/services.ts +10 -9
- package/core/agentic/skill-loader.ts +9 -15
- package/core/agentic/smart-context.ts +129 -79
- package/core/agentic/template-executor.ts +13 -12
- package/core/agentic/template-loader.ts +7 -4
- package/core/agentic/tool-registry.ts +16 -13
- package/core/agents/index.ts +1 -1
- package/core/agents/performance.ts +10 -27
- package/core/ai-tools/formatters.ts +8 -6
- package/core/ai-tools/generator.ts +4 -4
- package/core/ai-tools/index.ts +1 -1
- package/core/ai-tools/registry.ts +21 -11
- package/core/bus/bus.ts +23 -16
- package/core/bus/index.ts +2 -2
- package/core/cli/linear.ts +3 -5
- package/core/cli/start.ts +28 -25
- package/core/commands/analysis.ts +58 -39
- package/core/commands/analytics.ts +52 -44
- package/core/commands/base.ts +15 -13
- package/core/commands/cleanup.ts +6 -13
- package/core/commands/command-data.ts +28 -4
- package/core/commands/commands.ts +57 -24
- package/core/commands/context.ts +4 -4
- package/core/commands/design.ts +3 -10
- package/core/commands/index.ts +5 -8
- package/core/commands/maintenance.ts +7 -4
- package/core/commands/planning.ts +179 -56
- package/core/commands/register.ts +13 -9
- package/core/commands/registry.ts +15 -14
- package/core/commands/setup.ts +26 -14
- package/core/commands/shipping.ts +11 -16
- package/core/commands/snapshots.ts +16 -32
- package/core/commands/uninstall.ts +541 -0
- package/core/commands/workflow.ts +24 -28
- package/core/constants/index.ts +10 -22
- package/core/context/generator.ts +82 -33
- package/core/context-tools/files-tool.ts +18 -19
- package/core/context-tools/imports-tool.ts +13 -33
- package/core/context-tools/index.ts +29 -54
- package/core/context-tools/recent-tool.ts +16 -22
- package/core/context-tools/signatures-tool.ts +17 -26
- package/core/context-tools/summary-tool.ts +20 -22
- package/core/context-tools/token-counter.ts +25 -20
- package/core/context-tools/types.ts +5 -5
- package/core/domain/agent-generator.ts +7 -5
- package/core/domain/agent-loader.ts +2 -2
- package/core/domain/analyzer.ts +19 -16
- package/core/domain/architecture-generator.ts +6 -3
- package/core/domain/context-estimator.ts +3 -4
- package/core/domain/snapshot-manager.ts +25 -22
- package/core/domain/task-stack.ts +24 -14
- package/core/errors.ts +1 -1
- package/core/events/events.ts +2 -4
- package/core/events/index.ts +1 -2
- package/core/index.ts +28 -16
- package/core/infrastructure/agent-detector.ts +3 -3
- package/core/infrastructure/ai-provider.ts +23 -20
- package/core/infrastructure/author-detector.ts +16 -10
- package/core/infrastructure/capability-installer.ts +2 -2
- package/core/infrastructure/claude-agent.ts +6 -6
- package/core/infrastructure/command-installer.ts +22 -17
- package/core/infrastructure/config-manager.ts +18 -14
- package/core/infrastructure/editors-config.ts +8 -4
- package/core/infrastructure/path-manager.ts +8 -6
- package/core/infrastructure/permission-manager.ts +20 -17
- package/core/infrastructure/setup.ts +42 -38
- package/core/infrastructure/update-checker.ts +5 -5
- package/core/integrations/issue-tracker/enricher.ts +8 -19
- package/core/integrations/issue-tracker/index.ts +2 -2
- package/core/integrations/issue-tracker/manager.ts +15 -15
- package/core/integrations/issue-tracker/types.ts +5 -22
- package/core/integrations/jira/client.ts +67 -59
- package/core/integrations/jira/index.ts +11 -14
- package/core/integrations/jira/mcp-adapter.ts +5 -10
- package/core/integrations/jira/service.ts +10 -10
- package/core/integrations/linear/client.ts +27 -18
- package/core/integrations/linear/index.ts +9 -12
- package/core/integrations/linear/service.ts +11 -11
- package/core/integrations/linear/sync.ts +8 -8
- package/core/outcomes/analyzer.ts +5 -18
- package/core/outcomes/index.ts +2 -2
- package/core/outcomes/recorder.ts +3 -3
- package/core/plugin/builtin/webhook.ts +19 -15
- package/core/plugin/hooks.ts +29 -21
- package/core/plugin/index.ts +7 -7
- package/core/plugin/loader.ts +19 -19
- package/core/plugin/registry.ts +12 -23
- package/core/schemas/agents.ts +1 -1
- package/core/schemas/analysis.ts +1 -1
- package/core/schemas/enriched-task.ts +62 -49
- package/core/schemas/ideas.ts +13 -13
- package/core/schemas/index.ts +17 -27
- package/core/schemas/issues.ts +40 -25
- package/core/schemas/metrics.ts +25 -25
- package/core/schemas/outcomes.ts +70 -62
- package/core/schemas/permissions.ts +15 -12
- package/core/schemas/prd.ts +27 -14
- package/core/schemas/project.ts +3 -3
- package/core/schemas/roadmap.ts +47 -34
- package/core/schemas/schemas.ts +3 -4
- package/core/schemas/shipped.ts +3 -3
- package/core/schemas/state.ts +43 -29
- package/core/server/index.ts +5 -6
- package/core/server/routes-extended.ts +68 -72
- package/core/server/routes.ts +3 -3
- package/core/server/server.ts +31 -26
- package/core/services/agent-generator.ts +237 -0
- package/core/services/agent-service.ts +2 -2
- package/core/services/breakdown-service.ts +2 -4
- package/core/services/context-generator.ts +299 -0
- package/core/services/context-selector.ts +420 -0
- package/core/services/doctor-service.ts +426 -0
- package/core/services/file-categorizer.ts +448 -0
- package/core/services/file-scorer.ts +270 -0
- package/core/services/git-analyzer.ts +267 -0
- package/core/services/index.ts +27 -10
- package/core/services/memory-service.ts +3 -4
- package/core/services/project-index.ts +911 -0
- package/core/services/project-service.ts +4 -4
- package/core/services/skill-installer.ts +14 -17
- package/core/services/skill-lock.ts +3 -3
- package/core/services/skill-service.ts +12 -6
- package/core/services/stack-detector.ts +245 -0
- package/core/services/sync-service.ts +87 -345
- package/core/services/watch-service.ts +294 -0
- package/core/session/compaction.ts +23 -31
- package/core/session/index.ts +11 -5
- package/core/session/log-migration.ts +3 -3
- package/core/session/metrics.ts +19 -14
- package/core/session/session-log-manager.ts +12 -17
- package/core/session/task-session-manager.ts +25 -25
- package/core/session/utils.ts +1 -1
- package/core/storage/ideas-storage.ts +41 -57
- package/core/storage/index-storage.ts +514 -0
- package/core/storage/index.ts +41 -17
- package/core/storage/metrics-storage.ts +39 -34
- package/core/storage/queue-storage.ts +35 -45
- package/core/storage/shipped-storage.ts +17 -20
- package/core/storage/state-storage.ts +50 -30
- package/core/storage/storage-manager.ts +6 -6
- package/core/storage/storage.ts +18 -15
- package/core/sync/auth-config.ts +3 -3
- package/core/sync/index.ts +13 -19
- package/core/sync/oauth-handler.ts +3 -3
- package/core/sync/sync-client.ts +4 -9
- package/core/sync/sync-manager.ts +12 -14
- package/core/types/commands.ts +42 -7
- package/core/types/index.ts +284 -305
- package/core/types/integrations.ts +3 -3
- package/core/types/storage.ts +14 -14
- package/core/types/utils.ts +3 -3
- package/core/utils/agent-stream.ts +3 -1
- package/core/utils/animations.ts +14 -11
- package/core/utils/branding.ts +7 -7
- package/core/utils/cache.ts +1 -3
- package/core/utils/collection-filters.ts +3 -15
- package/core/utils/date-helper.ts +2 -7
- package/core/utils/file-helper.ts +13 -8
- package/core/utils/jsonl-helper.ts +13 -10
- package/core/utils/keychain.ts +4 -8
- package/core/utils/logger.ts +1 -1
- package/core/utils/next-steps.ts +3 -3
- package/core/utils/output.ts +58 -11
- package/core/utils/project-commands.ts +6 -6
- package/core/utils/project-credentials.ts +5 -12
- package/core/utils/runtime.ts +2 -2
- package/core/utils/session-helper.ts +3 -4
- package/core/utils/version.ts +3 -3
- package/core/wizard/index.ts +13 -0
- package/core/wizard/onboarding.ts +633 -0
- package/core/workflow/state-machine.ts +7 -7
- package/dist/bin/prjct.mjs +18755 -15574
- package/dist/core/infrastructure/command-installer.js +86 -79
- package/dist/core/infrastructure/editors-config.js +6 -6
- package/dist/core/infrastructure/setup.js +246 -225
- package/dist/core/utils/version.js +9 -9
- package/package.json +11 -12
- package/scripts/build.js +3 -3
- package/scripts/postinstall.js +2 -2
- package/templates/mcp-config.json +6 -1
- package/templates/permissions/permissive.jsonc +1 -1
- package/templates/permissions/strict.jsonc +5 -9
- package/templates/global/docs/agents.md +0 -88
- package/templates/global/docs/architecture.md +0 -103
- package/templates/global/docs/commands.md +0 -96
- package/templates/global/docs/validation.md +0 -95
|
@@ -15,16 +15,15 @@
|
|
|
15
15
|
* @version 1.0.0
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import
|
|
18
|
+
import { metricsStorage } from '../storage/metrics-storage'
|
|
19
|
+
import { getTimestamp } from '../utils/date-helper'
|
|
19
20
|
import { findRelevantFiles } from './files-tool'
|
|
20
|
-
import { extractSignatures, extractDirectorySignatures } from './signatures-tool'
|
|
21
21
|
import { analyzeImports } from './imports-tool'
|
|
22
22
|
import { getRecentFiles } from './recent-tool'
|
|
23
|
-
import {
|
|
23
|
+
import { extractDirectorySignatures, extractSignatures } from './signatures-tool'
|
|
24
|
+
import { summarizeDirectory, summarizeFile } from './summary-tool'
|
|
24
25
|
import { combineMetrics } from './token-counter'
|
|
25
|
-
import {
|
|
26
|
-
import configManager from '../infrastructure/config-manager'
|
|
27
|
-
import { getTimestamp } from '../utils/date-helper'
|
|
26
|
+
import type { ContextToolOutput, ContextToolUsage } from './types'
|
|
28
27
|
|
|
29
28
|
// =============================================================================
|
|
30
29
|
// CLI Dispatcher
|
|
@@ -127,17 +126,14 @@ export async function runContextTool(
|
|
|
127
126
|
// Tool Runners
|
|
128
127
|
// =============================================================================
|
|
129
128
|
|
|
130
|
-
async function runFilesTool(
|
|
131
|
-
args: string[],
|
|
132
|
-
projectPath: string
|
|
133
|
-
): Promise<ContextToolOutput> {
|
|
129
|
+
async function runFilesTool(args: string[], projectPath: string): Promise<ContextToolOutput> {
|
|
134
130
|
// Parse options
|
|
135
131
|
const options: { maxFiles?: number; minScore?: number; includeTests?: boolean } = {}
|
|
136
132
|
const taskParts: string[] = []
|
|
137
133
|
|
|
138
134
|
for (let i = 0; i < args.length; i++) {
|
|
139
135
|
if (args[i] === '--max' && args[i + 1]) {
|
|
140
|
-
options.maxFiles = parseInt(args[++i])
|
|
136
|
+
options.maxFiles = parseInt(args[++i], 10)
|
|
141
137
|
} else if (args[i] === '--min-score' && args[i + 1]) {
|
|
142
138
|
options.minScore = parseFloat(args[++i])
|
|
143
139
|
} else if (args[i] === '--include-tests') {
|
|
@@ -162,10 +158,7 @@ async function runFilesTool(
|
|
|
162
158
|
return { tool: 'files', result }
|
|
163
159
|
}
|
|
164
160
|
|
|
165
|
-
async function runSignaturesTool(
|
|
166
|
-
args: string[],
|
|
167
|
-
projectPath: string
|
|
168
|
-
): Promise<ContextToolOutput> {
|
|
161
|
+
async function runSignaturesTool(args: string[], projectPath: string): Promise<ContextToolOutput> {
|
|
169
162
|
const filePath = args[0]
|
|
170
163
|
if (!filePath) {
|
|
171
164
|
return {
|
|
@@ -178,11 +171,9 @@ async function runSignaturesTool(
|
|
|
178
171
|
}
|
|
179
172
|
|
|
180
173
|
// Check if it's a directory
|
|
181
|
-
const fs = await import('fs/promises')
|
|
182
|
-
const path = await import('path')
|
|
183
|
-
const fullPath = path.isAbsolute(filePath)
|
|
184
|
-
? filePath
|
|
185
|
-
: path.join(projectPath, filePath)
|
|
174
|
+
const fs = await import('node:fs/promises')
|
|
175
|
+
const path = await import('node:path')
|
|
176
|
+
const fullPath = path.isAbsolute(filePath) ? filePath : path.join(projectPath, filePath)
|
|
186
177
|
|
|
187
178
|
try {
|
|
188
179
|
const stat = await fs.stat(fullPath)
|
|
@@ -192,13 +183,11 @@ async function runSignaturesTool(
|
|
|
192
183
|
recursive: args.includes('--recursive') || args.includes('-r'),
|
|
193
184
|
})
|
|
194
185
|
// Combine into single output with cost savings
|
|
195
|
-
const combinedMetrics = combineMetrics(results.map(r => r.metrics))
|
|
186
|
+
const combinedMetrics = combineMetrics(results.map((r) => r.metrics))
|
|
196
187
|
const combined = {
|
|
197
188
|
file: filePath,
|
|
198
189
|
language: 'multiple',
|
|
199
|
-
signatures: results.flatMap((r) =>
|
|
200
|
-
r.signatures.map((s) => ({ ...s, file: r.file }))
|
|
201
|
-
),
|
|
190
|
+
signatures: results.flatMap((r) => r.signatures.map((s) => ({ ...s, file: r.file }))),
|
|
202
191
|
fallback: false,
|
|
203
192
|
metrics: combinedMetrics,
|
|
204
193
|
}
|
|
@@ -212,10 +201,7 @@ async function runSignaturesTool(
|
|
|
212
201
|
return { tool: 'signatures', result }
|
|
213
202
|
}
|
|
214
203
|
|
|
215
|
-
async function runImportsTool(
|
|
216
|
-
args: string[],
|
|
217
|
-
projectPath: string
|
|
218
|
-
): Promise<ContextToolOutput> {
|
|
204
|
+
async function runImportsTool(args: string[], projectPath: string): Promise<ContextToolOutput> {
|
|
219
205
|
const filePath = args[0]
|
|
220
206
|
if (!filePath) {
|
|
221
207
|
return {
|
|
@@ -233,7 +219,7 @@ async function runImportsTool(
|
|
|
233
219
|
if (args[i] === '--reverse' || args[i] === '-r') {
|
|
234
220
|
options.reverse = true
|
|
235
221
|
} else if ((args[i] === '--depth' || args[i] === '-d') && args[i + 1]) {
|
|
236
|
-
options.depth = parseInt(args[++i])
|
|
222
|
+
options.depth = parseInt(args[++i], 10)
|
|
237
223
|
}
|
|
238
224
|
}
|
|
239
225
|
|
|
@@ -241,19 +227,16 @@ async function runImportsTool(
|
|
|
241
227
|
return { tool: 'imports', result }
|
|
242
228
|
}
|
|
243
229
|
|
|
244
|
-
async function runRecentTool(
|
|
245
|
-
args: string[],
|
|
246
|
-
projectPath: string
|
|
247
|
-
): Promise<ContextToolOutput> {
|
|
230
|
+
async function runRecentTool(args: string[], projectPath: string): Promise<ContextToolOutput> {
|
|
248
231
|
const options: { commits?: number; branch?: boolean; maxFiles?: number } = {}
|
|
249
232
|
|
|
250
233
|
for (let i = 0; i < args.length; i++) {
|
|
251
234
|
if (args[i] === '--branch' || args[i] === '-b') {
|
|
252
235
|
options.branch = true
|
|
253
236
|
} else if (args[i] === '--max' && args[i + 1]) {
|
|
254
|
-
options.maxFiles = parseInt(args[++i])
|
|
237
|
+
options.maxFiles = parseInt(args[++i], 10)
|
|
255
238
|
} else if (/^\d+$/.test(args[i])) {
|
|
256
|
-
options.commits = parseInt(args[i])
|
|
239
|
+
options.commits = parseInt(args[i], 10)
|
|
257
240
|
}
|
|
258
241
|
}
|
|
259
242
|
|
|
@@ -261,10 +244,7 @@ async function runRecentTool(
|
|
|
261
244
|
return { tool: 'recent', result }
|
|
262
245
|
}
|
|
263
246
|
|
|
264
|
-
async function runSummaryTool(
|
|
265
|
-
args: string[],
|
|
266
|
-
projectPath: string
|
|
267
|
-
): Promise<ContextToolOutput> {
|
|
247
|
+
async function runSummaryTool(args: string[], projectPath: string): Promise<ContextToolOutput> {
|
|
268
248
|
const targetPath = args[0]
|
|
269
249
|
if (!targetPath) {
|
|
270
250
|
return {
|
|
@@ -277,11 +257,9 @@ async function runSummaryTool(
|
|
|
277
257
|
}
|
|
278
258
|
|
|
279
259
|
// Check if it's a directory
|
|
280
|
-
const fs = await import('fs/promises')
|
|
281
|
-
const path = await import('path')
|
|
282
|
-
const fullPath = path.isAbsolute(targetPath)
|
|
283
|
-
? targetPath
|
|
284
|
-
: path.join(projectPath, targetPath)
|
|
260
|
+
const fs = await import('node:fs/promises')
|
|
261
|
+
const path = await import('node:path')
|
|
262
|
+
const fullPath = path.isAbsolute(targetPath) ? targetPath : path.join(projectPath, targetPath)
|
|
285
263
|
|
|
286
264
|
try {
|
|
287
265
|
const stat = await fs.stat(fullPath)
|
|
@@ -293,11 +271,9 @@ async function runSummaryTool(
|
|
|
293
271
|
const combined = {
|
|
294
272
|
file: targetPath,
|
|
295
273
|
purpose: `Directory with ${results.length} files`,
|
|
296
|
-
publicAPI: results.flatMap((r) =>
|
|
297
|
-
r.publicAPI.map((api) => ({ ...api, file: r.file }))
|
|
298
|
-
),
|
|
274
|
+
publicAPI: results.flatMap((r) => r.publicAPI.map((api) => ({ ...api, file: r.file }))),
|
|
299
275
|
dependencies: [...new Set(results.flatMap((r) => r.dependencies))],
|
|
300
|
-
metrics: combineMetrics(results.map(r => r.metrics)),
|
|
276
|
+
metrics: combineMetrics(results.map((r) => r.metrics)),
|
|
301
277
|
}
|
|
302
278
|
return { tool: 'summary', result: combined }
|
|
303
279
|
}
|
|
@@ -341,19 +317,17 @@ function getCompressionRate(result: ContextToolOutput): number {
|
|
|
341
317
|
case 'signatures':
|
|
342
318
|
case 'summary':
|
|
343
319
|
return result.result.metrics.compression
|
|
344
|
-
case 'files':
|
|
320
|
+
case 'files': {
|
|
345
321
|
const scanned = result.result.metrics.filesScanned
|
|
346
322
|
const returned = result.result.metrics.filesReturned
|
|
347
323
|
return scanned > 0 ? (scanned - returned) / scanned : 0
|
|
324
|
+
}
|
|
348
325
|
default:
|
|
349
326
|
return 0
|
|
350
327
|
}
|
|
351
328
|
}
|
|
352
329
|
|
|
353
|
-
async function recordToolUsage(
|
|
354
|
-
projectId: string,
|
|
355
|
-
usage: ContextToolUsage
|
|
356
|
-
): Promise<void> {
|
|
330
|
+
async function recordToolUsage(projectId: string, usage: ContextToolUsage): Promise<void> {
|
|
357
331
|
try {
|
|
358
332
|
// Record to metrics storage
|
|
359
333
|
await metricsStorage.recordSync(projectId, {
|
|
@@ -451,8 +425,9 @@ export {
|
|
|
451
425
|
summarizeFile,
|
|
452
426
|
summarizeDirectory,
|
|
453
427
|
}
|
|
428
|
+
|
|
454
429
|
// Note: runContextTool is already exported at its definition (line 48)
|
|
455
430
|
|
|
431
|
+
export * from './token-counter'
|
|
456
432
|
// Re-export types
|
|
457
433
|
export * from './types'
|
|
458
|
-
export * from './token-counter'
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
* @version 1.0.0
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { exec as execCallback } from 'child_process'
|
|
12
|
-
import { promisify } from 'util'
|
|
13
|
-
import type {
|
|
11
|
+
import { exec as execCallback } from 'node:child_process'
|
|
12
|
+
import { promisify } from 'node:util'
|
|
13
|
+
import type { HotFile, RecentToolOutput } from './types'
|
|
14
14
|
|
|
15
15
|
const exec = promisify(execCallback)
|
|
16
16
|
|
|
@@ -74,9 +74,7 @@ export async function getRecentFiles(
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
// Filter and limit
|
|
77
|
-
hotFiles = hotFiles
|
|
78
|
-
.filter((f) => !shouldIgnore(f.path))
|
|
79
|
-
.slice(0, maxFiles)
|
|
77
|
+
hotFiles = hotFiles.filter((f) => !shouldIgnore(f.path)).slice(0, maxFiles)
|
|
80
78
|
|
|
81
79
|
return {
|
|
82
80
|
hotFiles,
|
|
@@ -88,7 +86,7 @@ export async function getRecentFiles(
|
|
|
88
86
|
analysisWindow,
|
|
89
87
|
},
|
|
90
88
|
}
|
|
91
|
-
} catch (
|
|
89
|
+
} catch (_error) {
|
|
92
90
|
// Git not available or not a repo
|
|
93
91
|
return {
|
|
94
92
|
hotFiles: [],
|
|
@@ -110,10 +108,7 @@ export async function getRecentFiles(
|
|
|
110
108
|
/**
|
|
111
109
|
* Get hot files from recent commits
|
|
112
110
|
*/
|
|
113
|
-
async function getHotFilesFromCommits(
|
|
114
|
-
projectPath: string,
|
|
115
|
-
commits: number
|
|
116
|
-
): Promise<HotFile[]> {
|
|
111
|
+
async function getHotFilesFromCommits(projectPath: string, commits: number): Promise<HotFile[]> {
|
|
117
112
|
// Get file change counts and last modified times
|
|
118
113
|
const { stdout } = await exec(
|
|
119
114
|
`git log -${commits} --pretty=format:"%ct" --name-only | awk '
|
|
@@ -138,7 +133,7 @@ async function getHotFilesFromCommits(
|
|
|
138
133
|
for (const line of lines) {
|
|
139
134
|
const match = line.match(/^(\d+)\s+(\d+)\s+(.+)$/)
|
|
140
135
|
if (match) {
|
|
141
|
-
const changes = parseInt(match[1])
|
|
136
|
+
const changes = parseInt(match[1], 10)
|
|
142
137
|
if (changes > maxChanges) maxChanges = changes
|
|
143
138
|
}
|
|
144
139
|
}
|
|
@@ -147,8 +142,8 @@ async function getHotFilesFromCommits(
|
|
|
147
142
|
const match = line.match(/^(\d+)\s+(\d+)\s+(.+)$/)
|
|
148
143
|
if (!match) continue
|
|
149
144
|
|
|
150
|
-
const changes = parseInt(match[1])
|
|
151
|
-
const timestamp = parseInt(match[2])
|
|
145
|
+
const changes = parseInt(match[1], 10)
|
|
146
|
+
const timestamp = parseInt(match[2], 10)
|
|
152
147
|
const filePath = match[3]
|
|
153
148
|
|
|
154
149
|
const secondsAgo = now - timestamp
|
|
@@ -199,7 +194,7 @@ async function getBranchOnlyFiles(projectPath: string): Promise<{
|
|
|
199
194
|
const { stdout: branchOutput } = await exec('git branch --show-current', {
|
|
200
195
|
cwd: projectPath,
|
|
201
196
|
})
|
|
202
|
-
const
|
|
197
|
+
const _currentBranch = branchOutput.trim()
|
|
203
198
|
|
|
204
199
|
// Determine base branch (main or master)
|
|
205
200
|
let baseBranch = 'main'
|
|
@@ -210,10 +205,9 @@ async function getBranchOnlyFiles(projectPath: string): Promise<{
|
|
|
210
205
|
}
|
|
211
206
|
|
|
212
207
|
// Get files changed in this branch
|
|
213
|
-
const { stdout: diffOutput } = await exec(
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
)
|
|
208
|
+
const { stdout: diffOutput } = await exec(`git diff --name-only ${baseBranch}...HEAD`, {
|
|
209
|
+
cwd: projectPath,
|
|
210
|
+
})
|
|
217
211
|
|
|
218
212
|
const branchOnlyFiles = diffOutput.trim().split('\n').filter(Boolean)
|
|
219
213
|
|
|
@@ -241,7 +235,7 @@ async function getBranchOnlyFiles(projectPath: string): Promise<{
|
|
|
241
235
|
for (const line of lines) {
|
|
242
236
|
const match = line.match(/^(\d+)\s+(\d+)\s+(.+)$/)
|
|
243
237
|
if (match) {
|
|
244
|
-
const changes = parseInt(match[1])
|
|
238
|
+
const changes = parseInt(match[1], 10)
|
|
245
239
|
if (changes > maxChanges) maxChanges = changes
|
|
246
240
|
}
|
|
247
241
|
}
|
|
@@ -250,8 +244,8 @@ async function getBranchOnlyFiles(projectPath: string): Promise<{
|
|
|
250
244
|
const match = line.match(/^(\d+)\s+(\d+)\s+(.+)$/)
|
|
251
245
|
if (!match) continue
|
|
252
246
|
|
|
253
|
-
const changes = parseInt(match[1])
|
|
254
|
-
const timestamp = parseInt(match[2])
|
|
247
|
+
const changes = parseInt(match[1], 10)
|
|
248
|
+
const timestamp = parseInt(match[2], 10)
|
|
255
249
|
const filePath = match[3]
|
|
256
250
|
|
|
257
251
|
const secondsAgo = now - timestamp
|
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
* @version 1.0.0
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import fs from 'fs/promises'
|
|
20
|
-
import path from 'path'
|
|
21
|
-
import type { SignaturesToolOutput, CodeSignature, SignatureType } from './types'
|
|
22
|
-
import { measureCompression, noCompression } from './token-counter'
|
|
19
|
+
import fs from 'node:fs/promises'
|
|
20
|
+
import path from 'node:path'
|
|
23
21
|
import { isNotFoundError } from '../types/fs'
|
|
22
|
+
import { measureCompression, noCompression } from './token-counter'
|
|
23
|
+
import type { CodeSignature, SignaturesToolOutput, SignatureType } from './types'
|
|
24
24
|
|
|
25
25
|
// =============================================================================
|
|
26
26
|
// Language Support
|
|
@@ -92,15 +92,13 @@ const TS_PATTERNS: ExtractionPattern[] = [
|
|
|
92
92
|
// Regular function declarations
|
|
93
93
|
{
|
|
94
94
|
type: 'function',
|
|
95
|
-
pattern:
|
|
96
|
-
/^(?:async\s+)?function\s+(\w+)\s*(<[^>]*>)?\s*\(([^)]*)\)\s*(?::\s*([^{;]+))?/gm,
|
|
95
|
+
pattern: /^(?:async\s+)?function\s+(\w+)\s*(<[^>]*>)?\s*\(([^)]*)\)\s*(?::\s*([^{;]+))?/gm,
|
|
97
96
|
nameIndex: 1,
|
|
98
97
|
},
|
|
99
98
|
// Const arrow functions
|
|
100
99
|
{
|
|
101
100
|
type: 'function',
|
|
102
|
-
pattern:
|
|
103
|
-
/^const\s+(\w+)\s*(?::\s*[^=]+)?\s*=\s*(?:async\s+)?\([^)]*\)\s*(?::\s*[^=]+)?\s*=>/gm,
|
|
101
|
+
pattern: /^const\s+(\w+)\s*(?::\s*[^=]+)?\s*=\s*(?:async\s+)?\([^)]*\)\s*(?::\s*[^=]+)?\s*=>/gm,
|
|
104
102
|
nameIndex: 1,
|
|
105
103
|
},
|
|
106
104
|
// Interface declarations
|
|
@@ -131,14 +129,14 @@ const TS_PATTERNS: ExtractionPattern[] = [
|
|
|
131
129
|
{
|
|
132
130
|
type: 'class',
|
|
133
131
|
pattern:
|
|
134
|
-
/^export\s+(?:abstract\s+)?class\s+(\w+)(?:<[^>]+>)?(?:\s+extends\s+[
|
|
132
|
+
/^export\s+(?:abstract\s+)?class\s+(\w+)(?:<[^>]+>)?(?:\s+extends\s+[^{]+)?(?:\s+implements\s+[^{]+)?\s*\{/gm,
|
|
135
133
|
nameIndex: 1,
|
|
136
134
|
exported: true,
|
|
137
135
|
},
|
|
138
136
|
{
|
|
139
137
|
type: 'class',
|
|
140
138
|
pattern:
|
|
141
|
-
/^(?:abstract\s+)?class\s+(\w+)(?:<[^>]+>)?(?:\s+extends\s+[
|
|
139
|
+
/^(?:abstract\s+)?class\s+(\w+)(?:<[^>]+>)?(?:\s+extends\s+[^{]+)?(?:\s+implements\s+[^{]+)?\s*\{/gm,
|
|
142
140
|
nameIndex: 1,
|
|
143
141
|
},
|
|
144
142
|
// Enum declarations
|
|
@@ -325,9 +323,7 @@ export async function extractSignatures(
|
|
|
325
323
|
projectPath: string = process.cwd()
|
|
326
324
|
): Promise<SignaturesToolOutput> {
|
|
327
325
|
// Resolve to absolute path
|
|
328
|
-
const absolutePath = path.isAbsolute(filePath)
|
|
329
|
-
? filePath
|
|
330
|
-
: path.join(projectPath, filePath)
|
|
326
|
+
const absolutePath = path.isAbsolute(filePath) ? filePath : path.join(projectPath, filePath)
|
|
331
327
|
|
|
332
328
|
// Read file content
|
|
333
329
|
let content: string
|
|
@@ -392,9 +388,7 @@ export async function extractDirectorySignatures(
|
|
|
392
388
|
projectPath: string = process.cwd(),
|
|
393
389
|
options: { recursive?: boolean } = {}
|
|
394
390
|
): Promise<SignaturesToolOutput[]> {
|
|
395
|
-
const absolutePath = path.isAbsolute(dirPath)
|
|
396
|
-
? dirPath
|
|
397
|
-
: path.join(projectPath, dirPath)
|
|
391
|
+
const absolutePath = path.isAbsolute(dirPath) ? dirPath : path.join(projectPath, dirPath)
|
|
398
392
|
|
|
399
393
|
const results: SignaturesToolOutput[] = []
|
|
400
394
|
|
|
@@ -407,11 +401,7 @@ export async function extractDirectorySignatures(
|
|
|
407
401
|
|
|
408
402
|
if (entry.isDirectory()) {
|
|
409
403
|
// Skip common ignore patterns
|
|
410
|
-
if (
|
|
411
|
-
entry.name === 'node_modules' ||
|
|
412
|
-
entry.name === '.git' ||
|
|
413
|
-
entry.name.startsWith('.')
|
|
414
|
-
) {
|
|
404
|
+
if (entry.name === 'node_modules' || entry.name === '.git' || entry.name.startsWith('.')) {
|
|
415
405
|
continue
|
|
416
406
|
}
|
|
417
407
|
if (options.recursive) {
|
|
@@ -438,10 +428,7 @@ export async function extractDirectorySignatures(
|
|
|
438
428
|
/**
|
|
439
429
|
* Extract signatures from content using patterns
|
|
440
430
|
*/
|
|
441
|
-
function extractFromContent(
|
|
442
|
-
content: string,
|
|
443
|
-
patterns: ExtractionPattern[]
|
|
444
|
-
): CodeSignature[] {
|
|
431
|
+
function extractFromContent(content: string, patterns: ExtractionPattern[]): CodeSignature[] {
|
|
445
432
|
const signatures: CodeSignature[] = []
|
|
446
433
|
const lines = content.split('\n')
|
|
447
434
|
|
|
@@ -473,7 +460,11 @@ function extractFromContent(
|
|
|
473
460
|
let docstring: string | undefined
|
|
474
461
|
if (lineNumber > 1) {
|
|
475
462
|
const prevLine = lines[lineNumber - 2]?.trim()
|
|
476
|
-
if (
|
|
463
|
+
if (
|
|
464
|
+
prevLine?.startsWith('/**') ||
|
|
465
|
+
prevLine?.startsWith('///') ||
|
|
466
|
+
prevLine?.startsWith('#')
|
|
467
|
+
) {
|
|
477
468
|
docstring = prevLine
|
|
478
469
|
}
|
|
479
470
|
}
|
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
* @version 1.0.0
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import fs from 'fs/promises'
|
|
16
|
-
import path from 'path'
|
|
17
|
-
import type { SummaryToolOutput, PublicAPIEntry, SignatureType } from './types'
|
|
18
|
-
import { measureCompression, noCompression, combineMetrics } from './token-counter'
|
|
19
|
-
import { extractSignatures } from './signatures-tool'
|
|
20
|
-
import { analyzeImports } from './imports-tool'
|
|
15
|
+
import fs from 'node:fs/promises'
|
|
16
|
+
import path from 'node:path'
|
|
21
17
|
import { isNotFoundError } from '../types/fs'
|
|
18
|
+
import { analyzeImports } from './imports-tool'
|
|
19
|
+
import { extractSignatures } from './signatures-tool'
|
|
20
|
+
import { measureCompression, noCompression } from './token-counter'
|
|
21
|
+
import type { PublicAPIEntry, SummaryToolOutput } from './types'
|
|
22
22
|
|
|
23
23
|
// =============================================================================
|
|
24
24
|
// Docstring Patterns
|
|
@@ -83,9 +83,7 @@ export async function summarizeFile(
|
|
|
83
83
|
filePath: string,
|
|
84
84
|
projectPath: string = process.cwd()
|
|
85
85
|
): Promise<SummaryToolOutput> {
|
|
86
|
-
const absolutePath = path.isAbsolute(filePath)
|
|
87
|
-
? filePath
|
|
88
|
-
: path.join(projectPath, filePath)
|
|
86
|
+
const absolutePath = path.isAbsolute(filePath) ? filePath : path.join(projectPath, filePath)
|
|
89
87
|
|
|
90
88
|
// Read file content
|
|
91
89
|
let content: string
|
|
@@ -124,9 +122,7 @@ export async function summarizeFile(
|
|
|
124
122
|
name: sig.name,
|
|
125
123
|
type: sig.type,
|
|
126
124
|
signature: sig.signature,
|
|
127
|
-
description: sig.docstring
|
|
128
|
-
? extractDescriptionFromDocstring(sig.docstring)
|
|
129
|
-
: undefined,
|
|
125
|
+
description: sig.docstring ? extractDescriptionFromDocstring(sig.docstring) : undefined,
|
|
130
126
|
}))
|
|
131
127
|
|
|
132
128
|
// Get key dependencies (internal only, external are obvious from package.json)
|
|
@@ -155,9 +151,7 @@ export async function summarizeDirectory(
|
|
|
155
151
|
projectPath: string = process.cwd(),
|
|
156
152
|
options: { recursive?: boolean } = {}
|
|
157
153
|
): Promise<SummaryToolOutput[]> {
|
|
158
|
-
const absolutePath = path.isAbsolute(dirPath)
|
|
159
|
-
? dirPath
|
|
160
|
-
: path.join(projectPath, dirPath)
|
|
154
|
+
const absolutePath = path.isAbsolute(dirPath) ? dirPath : path.join(projectPath, dirPath)
|
|
161
155
|
|
|
162
156
|
const results: SummaryToolOutput[] = []
|
|
163
157
|
|
|
@@ -170,11 +164,7 @@ export async function summarizeDirectory(
|
|
|
170
164
|
|
|
171
165
|
if (entry.isDirectory()) {
|
|
172
166
|
// Skip common ignore patterns
|
|
173
|
-
if (
|
|
174
|
-
entry.name === 'node_modules' ||
|
|
175
|
-
entry.name === '.git' ||
|
|
176
|
-
entry.name.startsWith('.')
|
|
177
|
-
) {
|
|
167
|
+
if (entry.name === 'node_modules' || entry.name === '.git' || entry.name.startsWith('.')) {
|
|
178
168
|
continue
|
|
179
169
|
}
|
|
180
170
|
if (options.recursive) {
|
|
@@ -227,7 +217,7 @@ function extractFilePurpose(content: string, language: string): string {
|
|
|
227
217
|
let comment = ''
|
|
228
218
|
let j = i
|
|
229
219
|
while (j < lines.length) {
|
|
230
|
-
comment += lines[j]
|
|
220
|
+
comment += `${lines[j]}\n`
|
|
231
221
|
if (pattern.end.test(lines[j])) break
|
|
232
222
|
j++
|
|
233
223
|
}
|
|
@@ -246,7 +236,15 @@ function extractFilePurpose(content: string, language: string): string {
|
|
|
246
236
|
}
|
|
247
237
|
|
|
248
238
|
// Stop if we hit code (not comments or empty lines)
|
|
249
|
-
if (
|
|
239
|
+
if (
|
|
240
|
+
line.length > 0 &&
|
|
241
|
+
!line.startsWith('//') &&
|
|
242
|
+
!line.startsWith('#') &&
|
|
243
|
+
!line.startsWith('/*') &&
|
|
244
|
+
!line.startsWith('*') &&
|
|
245
|
+
!line.startsWith("'") &&
|
|
246
|
+
!line.startsWith('"')
|
|
247
|
+
) {
|
|
250
248
|
break
|
|
251
249
|
}
|
|
252
250
|
}
|
|
@@ -36,17 +36,17 @@ const CHARS_PER_TOKEN = 4
|
|
|
36
36
|
*/
|
|
37
37
|
const MODEL_PRICING = {
|
|
38
38
|
// Anthropic Claude (2026)
|
|
39
|
-
'claude-opus-4.5':
|
|
40
|
-
'claude-sonnet-4.5': { input: 0.003,
|
|
41
|
-
'claude-haiku-4.5':
|
|
42
|
-
'claude-opus-4':
|
|
39
|
+
'claude-opus-4.5': { input: 0.005, output: 0.025 }, // $5/$25 per M
|
|
40
|
+
'claude-sonnet-4.5': { input: 0.003, output: 0.015 }, // $3/$15 per M
|
|
41
|
+
'claude-haiku-4.5': { input: 0.001, output: 0.005 }, // $1/$5 per M
|
|
42
|
+
'claude-opus-4': { input: 0.015, output: 0.075 }, // $15/$75 per M (legacy)
|
|
43
43
|
// OpenAI
|
|
44
|
-
'gpt-4o':
|
|
45
|
-
'gpt-4-turbo':
|
|
46
|
-
'gpt-4o-mini':
|
|
44
|
+
'gpt-4o': { input: 0.0025, output: 0.01 }, // $2.50/$10 per M
|
|
45
|
+
'gpt-4-turbo': { input: 0.01, output: 0.03 }, // $10/$30 per M
|
|
46
|
+
'gpt-4o-mini': { input: 0.00015, output: 0.0006 }, // $0.15/$0.60 per M
|
|
47
47
|
// Google
|
|
48
|
-
'gemini-1.5-pro':
|
|
49
|
-
'gemini-1.5-flash':
|
|
48
|
+
'gemini-1.5-pro': { input: 0.00125, output: 0.005 }, // $1.25/$5 per M
|
|
49
|
+
'gemini-1.5-flash': { input: 0.000075, output: 0.0003 }, // $0.075/$0.30 per M
|
|
50
50
|
} as const
|
|
51
51
|
|
|
52
52
|
type ModelName = keyof typeof MODEL_PRICING
|
|
@@ -86,7 +86,10 @@ const BREAKDOWN_MODELS: ModelName[] = [
|
|
|
86
86
|
* Calculate cost breakdown for a model
|
|
87
87
|
* Output potential = estimated savings if response is proportionally shorter
|
|
88
88
|
*/
|
|
89
|
-
function calculateModelCost(
|
|
89
|
+
function calculateModelCost(
|
|
90
|
+
tokensSaved: number,
|
|
91
|
+
model: ModelName
|
|
92
|
+
): {
|
|
90
93
|
inputSaved: number
|
|
91
94
|
outputPotential: number
|
|
92
95
|
total: number
|
|
@@ -128,14 +131,13 @@ export function measureCompression(original: string, filtered: string): TokenMet
|
|
|
128
131
|
const filteredTokens = countTokens(filtered)
|
|
129
132
|
const tokensSaved = Math.max(0, originalTokens - filteredTokens)
|
|
130
133
|
|
|
131
|
-
const compression =
|
|
132
|
-
originalTokens > 0 ? (originalTokens - filteredTokens) / originalTokens : 0
|
|
134
|
+
const compression = originalTokens > 0 ? (originalTokens - filteredTokens) / originalTokens : 0
|
|
133
135
|
|
|
134
136
|
// Calculate cost for default model
|
|
135
137
|
const defaultCost = calculateModelCost(tokensSaved, DEFAULT_MODEL)
|
|
136
138
|
|
|
137
139
|
// Calculate breakdown for popular models
|
|
138
|
-
const byModel = BREAKDOWN_MODELS.map(model => ({
|
|
140
|
+
const byModel = BREAKDOWN_MODELS.map((model) => ({
|
|
139
141
|
model,
|
|
140
142
|
...calculateModelCost(tokensSaved, model),
|
|
141
143
|
}))
|
|
@@ -170,7 +172,7 @@ export function noCompression(content: string): TokenMetrics {
|
|
|
170
172
|
cost: {
|
|
171
173
|
saved: 0,
|
|
172
174
|
formatted: '$0.00',
|
|
173
|
-
byModel: BREAKDOWN_MODELS.map(model => ({
|
|
175
|
+
byModel: BREAKDOWN_MODELS.map((model) => ({
|
|
174
176
|
model,
|
|
175
177
|
inputSaved: 0,
|
|
176
178
|
outputPotential: 0,
|
|
@@ -199,14 +201,17 @@ export function combineMetrics(metrics: TokenMetrics[]): TokenMetrics {
|
|
|
199
201
|
const totalSaved = metrics.reduce((sum, m) => sum + m.tokens.saved, 0)
|
|
200
202
|
|
|
201
203
|
// Calculate overall compression
|
|
202
|
-
const compression = totalOriginal > 0
|
|
203
|
-
? (totalOriginal - totalFiltered) / totalOriginal
|
|
204
|
-
: 0
|
|
204
|
+
const compression = totalOriginal > 0 ? (totalOriginal - totalFiltered) / totalOriginal : 0
|
|
205
205
|
|
|
206
206
|
// Sum costs by model
|
|
207
|
-
const byModel = BREAKDOWN_MODELS.map(model => {
|
|
208
|
-
const modelMetrics = metrics.map(
|
|
209
|
-
m
|
|
207
|
+
const byModel = BREAKDOWN_MODELS.map((model) => {
|
|
208
|
+
const modelMetrics = metrics.map(
|
|
209
|
+
(m) =>
|
|
210
|
+
m.cost.byModel.find((b) => b.model === model) || {
|
|
211
|
+
inputSaved: 0,
|
|
212
|
+
outputPotential: 0,
|
|
213
|
+
total: 0,
|
|
214
|
+
}
|
|
210
215
|
)
|
|
211
216
|
return {
|
|
212
217
|
model,
|
|
@@ -18,9 +18,9 @@
|
|
|
18
18
|
*/
|
|
19
19
|
export interface CostBreakdown {
|
|
20
20
|
model: string
|
|
21
|
-
inputSaved: number
|
|
21
|
+
inputSaved: number // $ saved on input tokens
|
|
22
22
|
outputPotential: number // $ potential savings on output (estimated)
|
|
23
|
-
total: number
|
|
23
|
+
total: number // Combined savings
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
/**
|
|
@@ -32,10 +32,10 @@ export interface TokenMetrics {
|
|
|
32
32
|
filtered: number
|
|
33
33
|
saved: number
|
|
34
34
|
}
|
|
35
|
-
compression: number
|
|
35
|
+
compression: number // 0-1 (e.g., 0.90 = 90% reduction)
|
|
36
36
|
cost: {
|
|
37
|
-
saved: number
|
|
38
|
-
formatted: string
|
|
37
|
+
saved: number // $ saved (using default model)
|
|
38
|
+
formatted: string // Human-readable (e.g., "$0.02")
|
|
39
39
|
byModel: CostBreakdown[] // Breakdown by popular models
|
|
40
40
|
}
|
|
41
41
|
}
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
* @version 1.0.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import fs from 'fs/promises'
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import AgentLoader from './agent-loader'
|
|
7
|
+
import fs from 'node:fs/promises'
|
|
8
|
+
import os from 'node:os'
|
|
9
|
+
import path from 'node:path'
|
|
11
10
|
import log from '../utils/logger'
|
|
11
|
+
import AgentLoader from './agent-loader'
|
|
12
12
|
|
|
13
13
|
interface AgentConfig {
|
|
14
14
|
role?: string
|
|
@@ -152,7 +152,9 @@ ${config.contextFilter || 'Only relevant files'}
|
|
|
152
152
|
async listAgents(): Promise<string[]> {
|
|
153
153
|
try {
|
|
154
154
|
const files = await fs.readdir(this.outputDir)
|
|
155
|
-
return files
|
|
155
|
+
return files
|
|
156
|
+
.filter((f) => f.endsWith('.md') && !f.startsWith('.'))
|
|
157
|
+
.map((f) => f.replace('.md', ''))
|
|
156
158
|
} catch (_error) {
|
|
157
159
|
return []
|
|
158
160
|
}
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* @version 1.0.0
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import fs from 'fs/promises'
|
|
10
|
-
import path from 'path'
|
|
9
|
+
import fs from 'node:fs/promises'
|
|
10
|
+
import path from 'node:path'
|
|
11
11
|
import pathManager from '../infrastructure/path-manager'
|
|
12
12
|
import { isNotFoundError } from '../types/fs'
|
|
13
13
|
|