prjct-cli 0.44.1 → 0.45.3
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 +114 -0
- package/bin/prjct.ts +131 -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 +287 -29
- 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 +49 -8
- package/core/commands/commands.ts +60 -23
- 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 +14 -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 +583 -0
- package/core/context-tools/imports-tool.ts +403 -0
- package/core/context-tools/index.ts +433 -0
- package/core/context-tools/recent-tool.ts +307 -0
- package/core/context-tools/signatures-tool.ts +501 -0
- package/core/context-tools/summary-tool.ts +307 -0
- package/core/context-tools/token-counter.ts +284 -0
- package/core/context-tools/types.ts +253 -0
- 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 -12
- 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 +143 -0
- 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 +170 -329
- 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 -13
- package/core/storage/metrics-storage.ts +320 -0
- 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 -302
- package/core/types/integrations.ts +3 -3
- package/core/types/storage.ts +49 -0
- 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 +18907 -13189
- package/dist/core/infrastructure/command-installer.js +96 -111
- package/dist/core/infrastructure/editors-config.js +6 -6
- package/dist/core/infrastructure/setup.js +256 -257
- 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
|
@@ -2,28 +2,32 @@
|
|
|
2
2
|
* Analysis Commands: analyze, sync, and related helpers
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import path from 'path'
|
|
6
|
-
|
|
7
|
-
import type { CommandResult, AnalyzeOptions, ProjectContext } from '../types'
|
|
8
|
-
import {
|
|
9
|
-
PrjctCommandsBase,
|
|
10
|
-
contextBuilder,
|
|
11
|
-
toolRegistry,
|
|
12
|
-
pathManager,
|
|
13
|
-
configManager,
|
|
14
|
-
dateHelper
|
|
15
|
-
} from './base'
|
|
16
|
-
import analyzer from '../domain/analyzer'
|
|
5
|
+
import path from 'node:path'
|
|
17
6
|
import { generateContext } from '../context/generator'
|
|
7
|
+
import analyzer from '../domain/analyzer'
|
|
18
8
|
import commandInstaller from '../infrastructure/command-installer'
|
|
9
|
+
import { formatCost } from '../schemas/metrics'
|
|
19
10
|
import { syncService } from '../services'
|
|
11
|
+
import { metricsStorage } from '../storage/metrics-storage'
|
|
12
|
+
import type { AnalyzeOptions, CommandResult, ProjectContext } from '../types'
|
|
20
13
|
import { showNextSteps } from '../utils/next-steps'
|
|
14
|
+
import {
|
|
15
|
+
configManager,
|
|
16
|
+
contextBuilder,
|
|
17
|
+
dateHelper,
|
|
18
|
+
PrjctCommandsBase,
|
|
19
|
+
pathManager,
|
|
20
|
+
toolRegistry,
|
|
21
|
+
} from './base'
|
|
21
22
|
|
|
22
23
|
export class AnalysisCommands extends PrjctCommandsBase {
|
|
23
24
|
/**
|
|
24
25
|
* /p:analyze - Analyze repository and generate summary
|
|
25
26
|
*/
|
|
26
|
-
async analyze(
|
|
27
|
+
async analyze(
|
|
28
|
+
options: AnalyzeOptions = {},
|
|
29
|
+
projectPath: string = process.cwd()
|
|
30
|
+
): Promise<CommandResult> {
|
|
27
31
|
try {
|
|
28
32
|
await this.initializeAgent()
|
|
29
33
|
|
|
@@ -31,7 +35,7 @@ export class AnalysisCommands extends PrjctCommandsBase {
|
|
|
31
35
|
|
|
32
36
|
analyzer.init(projectPath)
|
|
33
37
|
|
|
34
|
-
const context = await contextBuilder.build(projectPath, options) as ProjectContext
|
|
38
|
+
const context = (await contextBuilder.build(projectPath, options)) as ProjectContext
|
|
35
39
|
|
|
36
40
|
const analysisData = {
|
|
37
41
|
packageJson: await analyzer.readPackageJson(),
|
|
@@ -58,12 +62,7 @@ export class AnalysisCommands extends PrjctCommandsBase {
|
|
|
58
62
|
|
|
59
63
|
const projectId = await configManager.getProjectId(projectPath)
|
|
60
64
|
const summaryPath =
|
|
61
|
-
context.paths.analysis ||
|
|
62
|
-
pathManager.getFilePath(
|
|
63
|
-
projectId!,
|
|
64
|
-
'analysis',
|
|
65
|
-
'repo-summary.md'
|
|
66
|
-
)
|
|
65
|
+
context.paths.analysis || pathManager.getFilePath(projectId!, 'analysis', 'repo-summary.md')
|
|
67
66
|
|
|
68
67
|
await toolRegistry.get('Write')!(summaryPath, summary)
|
|
69
68
|
|
|
@@ -163,7 +162,9 @@ export class AnalysisCommands extends PrjctCommandsBase {
|
|
|
163
162
|
if (data.hasReadme) lines.push('- **Documentation**: README.md found')
|
|
164
163
|
lines.push('')
|
|
165
164
|
|
|
166
|
-
const gitStats = data.gitStats as
|
|
165
|
+
const gitStats = data.gitStats as
|
|
166
|
+
| { totalCommits?: number; contributors?: number; age?: string }
|
|
167
|
+
| undefined
|
|
167
168
|
lines.push('## Git Statistics\n')
|
|
168
169
|
lines.push(`- **Total Commits**: ${gitStats?.totalCommits || 0}`)
|
|
169
170
|
lines.push(`- **Contributors**: ${gitStats?.contributors || 0}`)
|
|
@@ -206,7 +207,10 @@ export class AnalysisCommands extends PrjctCommandsBase {
|
|
|
206
207
|
*
|
|
207
208
|
* This eliminates the need for Claude to make 50+ individual tool calls.
|
|
208
209
|
*/
|
|
209
|
-
async sync(
|
|
210
|
+
async sync(
|
|
211
|
+
projectPath: string = process.cwd(),
|
|
212
|
+
options: { aiTools?: string[] } = {}
|
|
213
|
+
): Promise<CommandResult> {
|
|
210
214
|
try {
|
|
211
215
|
const initResult = await this.ensureProjectInit(projectPath)
|
|
212
216
|
if (!initResult.success) return initResult
|
|
@@ -250,7 +254,7 @@ export class AnalysisCommands extends PrjctCommandsBase {
|
|
|
250
254
|
|
|
251
255
|
// Show AI Tools generated (multi-agent output)
|
|
252
256
|
if (result.aiTools && result.aiTools.length > 0) {
|
|
253
|
-
const successTools = result.aiTools.filter(t => t.success)
|
|
257
|
+
const successTools = result.aiTools.filter((t) => t.success)
|
|
254
258
|
console.log(`🤖 AI Tools Context (${successTools.length})`)
|
|
255
259
|
for (const tool of result.aiTools) {
|
|
256
260
|
const status = tool.success ? '✓' : '✗'
|
|
@@ -259,8 +263,8 @@ export class AnalysisCommands extends PrjctCommandsBase {
|
|
|
259
263
|
console.log('')
|
|
260
264
|
}
|
|
261
265
|
|
|
262
|
-
const workflowAgents = result.agents.filter(a => a.type === 'workflow').map(a => a.name)
|
|
263
|
-
const domainAgents = result.agents.filter(a => a.type === 'domain').map(a => a.name)
|
|
266
|
+
const workflowAgents = result.agents.filter((a) => a.type === 'workflow').map((a) => a.name)
|
|
267
|
+
const domainAgents = result.agents.filter((a) => a.type === 'domain').map((a) => a.name)
|
|
264
268
|
|
|
265
269
|
console.log(`🤖 Agents Regenerated (${result.agents.length})`)
|
|
266
270
|
console.log(`├── Workflow: ${workflowAgents.join(', ')}`)
|
|
@@ -284,14 +288,21 @@ export class AnalysisCommands extends PrjctCommandsBase {
|
|
|
284
288
|
|
|
285
289
|
// Summary metrics
|
|
286
290
|
const elapsed = Date.now() - startTime
|
|
287
|
-
const contextFilesCount =
|
|
291
|
+
const contextFilesCount =
|
|
292
|
+
result.contextFiles.length + (result.aiTools?.filter((t) => t.success).length || 0)
|
|
288
293
|
const agentCount = result.agents.length
|
|
289
294
|
|
|
290
295
|
console.log('─'.repeat(45))
|
|
291
296
|
console.log(`📊 Sync Summary`)
|
|
292
|
-
console.log(
|
|
293
|
-
|
|
294
|
-
|
|
297
|
+
console.log(
|
|
298
|
+
` Stack: ${result.stats.ecosystem} (${result.stats.frameworks.join(', ') || 'no frameworks'})`
|
|
299
|
+
)
|
|
300
|
+
console.log(
|
|
301
|
+
` Files: ${result.stats.fileCount} analyzed → ${contextFilesCount} context files`
|
|
302
|
+
)
|
|
303
|
+
console.log(
|
|
304
|
+
` Agents: ${agentCount} (${result.agents.filter((a) => a.type === 'domain').length} domain)`
|
|
305
|
+
)
|
|
295
306
|
console.log(` Time: ${(elapsed / 1000).toFixed(1)}s`)
|
|
296
307
|
console.log('')
|
|
297
308
|
|
|
@@ -311,4 +322,251 @@ export class AnalysisCommands extends PrjctCommandsBase {
|
|
|
311
322
|
}
|
|
312
323
|
}
|
|
313
324
|
|
|
325
|
+
/**
|
|
326
|
+
* /p:stats - Value dashboard showing accumulated savings and impact
|
|
327
|
+
*
|
|
328
|
+
* Displays:
|
|
329
|
+
* - Token savings (total, compression rate, estimated cost)
|
|
330
|
+
* - Performance metrics (sync count, avg duration)
|
|
331
|
+
* - Agent usage breakdown
|
|
332
|
+
* - 30-day trend visualization
|
|
333
|
+
*/
|
|
334
|
+
async stats(
|
|
335
|
+
projectPath: string = process.cwd(),
|
|
336
|
+
options: { json?: boolean; export?: boolean } = {}
|
|
337
|
+
): Promise<CommandResult> {
|
|
338
|
+
try {
|
|
339
|
+
const initResult = await this.ensureProjectInit(projectPath)
|
|
340
|
+
if (!initResult.success) return initResult
|
|
341
|
+
|
|
342
|
+
const projectId = await configManager.getProjectId(projectPath)
|
|
343
|
+
if (!projectId) {
|
|
344
|
+
return { success: false, error: 'No project ID found' }
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Get metrics summary
|
|
348
|
+
const summary = await metricsStorage.getSummary(projectId)
|
|
349
|
+
const dailyStats = await metricsStorage.getDailyStats(projectId, 30)
|
|
350
|
+
|
|
351
|
+
// JSON output mode
|
|
352
|
+
if (options.json) {
|
|
353
|
+
const jsonOutput = {
|
|
354
|
+
totalTokensSaved: summary.totalTokensSaved,
|
|
355
|
+
estimatedCostSaved: summary.estimatedCostSaved,
|
|
356
|
+
compressionRate: summary.compressionRate,
|
|
357
|
+
syncCount: summary.syncCount,
|
|
358
|
+
avgSyncDuration: summary.avgSyncDuration,
|
|
359
|
+
topAgents: summary.topAgents,
|
|
360
|
+
last30DaysTokens: summary.last30DaysTokens,
|
|
361
|
+
trend: summary.trend,
|
|
362
|
+
dailyStats,
|
|
363
|
+
}
|
|
364
|
+
console.log(JSON.stringify(jsonOutput, null, 2))
|
|
365
|
+
return { success: true, data: jsonOutput }
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Get project info for header
|
|
369
|
+
const globalPath = pathManager.getGlobalProjectPath(projectId)
|
|
370
|
+
let projectName = 'Unknown'
|
|
371
|
+
try {
|
|
372
|
+
const fs = require('node:fs/promises')
|
|
373
|
+
const path = require('node:path')
|
|
374
|
+
const projectJson = JSON.parse(
|
|
375
|
+
await fs.readFile(path.join(globalPath, 'project.json'), 'utf-8')
|
|
376
|
+
)
|
|
377
|
+
projectName = projectJson.name || 'Unknown'
|
|
378
|
+
} catch {
|
|
379
|
+
// Use fallback
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Determine first sync date
|
|
383
|
+
const metricsData = await metricsStorage.read(projectId)
|
|
384
|
+
const firstSyncDate = metricsData.firstSync
|
|
385
|
+
? new Date(metricsData.firstSync).toLocaleDateString('en-US', {
|
|
386
|
+
month: 'short',
|
|
387
|
+
day: 'numeric',
|
|
388
|
+
year: 'numeric',
|
|
389
|
+
})
|
|
390
|
+
: 'N/A'
|
|
391
|
+
|
|
392
|
+
// ASCII Dashboard
|
|
393
|
+
console.log('')
|
|
394
|
+
console.log('╭─────────────────────────────────────────────────╮')
|
|
395
|
+
console.log('│ 📊 prjct-cli Value Dashboard │')
|
|
396
|
+
console.log(
|
|
397
|
+
`│ Project: ${projectName.padEnd(20).slice(0, 20)} | Since: ${firstSyncDate.padEnd(12).slice(0, 12)} │`
|
|
398
|
+
)
|
|
399
|
+
console.log('╰─────────────────────────────────────────────────╯')
|
|
400
|
+
console.log('')
|
|
401
|
+
|
|
402
|
+
// Token Savings Section
|
|
403
|
+
console.log('💰 TOKEN SAVINGS')
|
|
404
|
+
console.log(` Total saved: ${this._formatTokens(summary.totalTokensSaved)} tokens`)
|
|
405
|
+
console.log(
|
|
406
|
+
` Compression: ${(summary.compressionRate * 100).toFixed(0)}% average reduction`
|
|
407
|
+
)
|
|
408
|
+
console.log(` Estimated cost: ${formatCost(summary.estimatedCostSaved)} saved`)
|
|
409
|
+
console.log('')
|
|
410
|
+
|
|
411
|
+
// Performance Section
|
|
412
|
+
console.log('⚡ PERFORMANCE')
|
|
413
|
+
console.log(` Syncs completed: ${summary.syncCount.toLocaleString()}`)
|
|
414
|
+
console.log(` Avg sync time: ${this._formatDuration(summary.avgSyncDuration)}`)
|
|
415
|
+
console.log('')
|
|
416
|
+
|
|
417
|
+
// Agent Usage Section
|
|
418
|
+
if (summary.topAgents.length > 0) {
|
|
419
|
+
console.log('🤖 AGENT USAGE')
|
|
420
|
+
const totalUsage = summary.topAgents.reduce((sum, a) => sum + a.usageCount, 0)
|
|
421
|
+
for (const agent of summary.topAgents) {
|
|
422
|
+
const pct = totalUsage > 0 ? ((agent.usageCount / totalUsage) * 100).toFixed(0) : 0
|
|
423
|
+
console.log(` ${agent.agentName.padEnd(12)}: ${pct}% (${agent.usageCount} uses)`)
|
|
424
|
+
}
|
|
425
|
+
console.log('')
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// 30-Day Trend Section
|
|
429
|
+
if (dailyStats.length > 0) {
|
|
430
|
+
console.log('📈 TREND (last 30 days)')
|
|
431
|
+
const sparkline = this._generateSparkline(dailyStats)
|
|
432
|
+
console.log(` ${sparkline} ${this._formatTokens(summary.last30DaysTokens)} tokens saved`)
|
|
433
|
+
|
|
434
|
+
if (summary.trend !== 0) {
|
|
435
|
+
const trendIcon = summary.trend > 0 ? '↑' : '↓'
|
|
436
|
+
const trendSign = summary.trend > 0 ? '+' : ''
|
|
437
|
+
console.log(
|
|
438
|
+
` ${trendIcon} ${trendSign}${summary.trend.toFixed(0)}% vs previous 30 days`
|
|
439
|
+
)
|
|
440
|
+
}
|
|
441
|
+
console.log('')
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Footer
|
|
445
|
+
console.log('───────────────────────────────────────────────────')
|
|
446
|
+
console.log(`Export: prjct stats --export > stats.md`)
|
|
447
|
+
console.log('')
|
|
448
|
+
|
|
449
|
+
// Export mode - return markdown
|
|
450
|
+
if (options.export) {
|
|
451
|
+
const markdown = this._generateStatsMarkdown(
|
|
452
|
+
summary,
|
|
453
|
+
dailyStats,
|
|
454
|
+
projectName,
|
|
455
|
+
firstSyncDate
|
|
456
|
+
)
|
|
457
|
+
console.log(markdown)
|
|
458
|
+
return { success: true, data: { markdown } }
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return {
|
|
462
|
+
success: true,
|
|
463
|
+
data: summary,
|
|
464
|
+
}
|
|
465
|
+
} catch (error) {
|
|
466
|
+
console.error('❌ Error:', (error as Error).message)
|
|
467
|
+
return { success: false, error: (error as Error).message }
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// =========== Stats Helper Methods ===========
|
|
472
|
+
|
|
473
|
+
private _formatTokens(tokens: number): string {
|
|
474
|
+
if (tokens >= 1_000_000) {
|
|
475
|
+
return `${(tokens / 1_000_000).toFixed(1)}M`
|
|
476
|
+
}
|
|
477
|
+
if (tokens >= 1_000) {
|
|
478
|
+
return `${(tokens / 1_000).toFixed(1)}K`
|
|
479
|
+
}
|
|
480
|
+
return tokens.toLocaleString()
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
private _formatDuration(ms: number): string {
|
|
484
|
+
if (ms < 1000) {
|
|
485
|
+
return `${Math.round(ms)}ms`
|
|
486
|
+
}
|
|
487
|
+
return `${(ms / 1000).toFixed(1)}s`
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
private _generateSparkline(dailyStats: { tokensSaved: number }[]): string {
|
|
491
|
+
if (dailyStats.length === 0) return ''
|
|
492
|
+
|
|
493
|
+
const chars = '▁▂▃▄▅▆▇█'
|
|
494
|
+
const values = dailyStats.map((d) => d.tokensSaved)
|
|
495
|
+
const max = Math.max(...values, 1)
|
|
496
|
+
|
|
497
|
+
return values
|
|
498
|
+
.map((v) => {
|
|
499
|
+
const idx = Math.min(Math.floor((v / max) * (chars.length - 1)), chars.length - 1)
|
|
500
|
+
return chars[idx]
|
|
501
|
+
})
|
|
502
|
+
.join('')
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
private _generateStatsMarkdown(
|
|
506
|
+
summary: {
|
|
507
|
+
totalTokensSaved: number
|
|
508
|
+
estimatedCostSaved: number
|
|
509
|
+
compressionRate: number
|
|
510
|
+
syncCount: number
|
|
511
|
+
avgSyncDuration: number
|
|
512
|
+
topAgents: { agentName: string; usageCount: number }[]
|
|
513
|
+
last30DaysTokens: number
|
|
514
|
+
trend: number
|
|
515
|
+
},
|
|
516
|
+
_dailyStats: { date: string; tokensSaved: number; syncs: number }[],
|
|
517
|
+
projectName: string,
|
|
518
|
+
firstSyncDate: string
|
|
519
|
+
): string {
|
|
520
|
+
const lines: string[] = []
|
|
521
|
+
|
|
522
|
+
lines.push(`# ${projectName} - Value Dashboard`)
|
|
523
|
+
lines.push('')
|
|
524
|
+
lines.push(`_Generated: ${new Date().toLocaleString()} | Tracking since: ${firstSyncDate}_`)
|
|
525
|
+
lines.push('')
|
|
526
|
+
|
|
527
|
+
lines.push('## 💰 Token Savings')
|
|
528
|
+
lines.push('')
|
|
529
|
+
lines.push(`| Metric | Value |`)
|
|
530
|
+
lines.push(`|--------|-------|`)
|
|
531
|
+
lines.push(`| Total saved | ${this._formatTokens(summary.totalTokensSaved)} tokens |`)
|
|
532
|
+
lines.push(`| Compression | ${(summary.compressionRate * 100).toFixed(0)}% |`)
|
|
533
|
+
lines.push(`| Cost saved | ${formatCost(summary.estimatedCostSaved)} |`)
|
|
534
|
+
lines.push('')
|
|
535
|
+
|
|
536
|
+
lines.push('## ⚡ Performance')
|
|
537
|
+
lines.push('')
|
|
538
|
+
lines.push(`| Metric | Value |`)
|
|
539
|
+
lines.push(`|--------|-------|`)
|
|
540
|
+
lines.push(`| Syncs | ${summary.syncCount} |`)
|
|
541
|
+
lines.push(`| Avg time | ${this._formatDuration(summary.avgSyncDuration)} |`)
|
|
542
|
+
lines.push('')
|
|
543
|
+
|
|
544
|
+
if (summary.topAgents.length > 0) {
|
|
545
|
+
lines.push('## 🤖 Agent Usage')
|
|
546
|
+
lines.push('')
|
|
547
|
+
lines.push(`| Agent | Usage |`)
|
|
548
|
+
lines.push(`|-------|-------|`)
|
|
549
|
+
const totalUsage = summary.topAgents.reduce((sum, a) => sum + a.usageCount, 0)
|
|
550
|
+
for (const agent of summary.topAgents) {
|
|
551
|
+
const pct = totalUsage > 0 ? ((agent.usageCount / totalUsage) * 100).toFixed(0) : 0
|
|
552
|
+
lines.push(`| ${agent.agentName} | ${pct}% (${agent.usageCount}) |`)
|
|
553
|
+
}
|
|
554
|
+
lines.push('')
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
lines.push('## 📈 30-Day Trend')
|
|
558
|
+
lines.push('')
|
|
559
|
+
lines.push(`- Tokens saved: ${this._formatTokens(summary.last30DaysTokens)}`)
|
|
560
|
+
if (summary.trend !== 0) {
|
|
561
|
+
const trendSign = summary.trend > 0 ? '+' : ''
|
|
562
|
+
lines.push(`- Trend: ${trendSign}${summary.trend.toFixed(0)}% vs previous period`)
|
|
563
|
+
}
|
|
564
|
+
lines.push('')
|
|
565
|
+
|
|
566
|
+
lines.push('---')
|
|
567
|
+
lines.push('')
|
|
568
|
+
lines.push('_Generated with [prjct-cli](https://prjct.app)_')
|
|
569
|
+
|
|
570
|
+
return lines.join('\n')
|
|
571
|
+
}
|
|
314
572
|
}
|
|
@@ -3,21 +3,20 @@
|
|
|
3
3
|
* Unified dashboard and contextual help - MD-First Architecture
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import path from 'path'
|
|
7
|
-
|
|
8
|
-
import { commandRegistry } from './registry'
|
|
6
|
+
import path from 'node:path'
|
|
7
|
+
import { ideasStorage, queueStorage, shippedStorage, stateStorage } from '../storage'
|
|
9
8
|
import type { CommandResult, ProjectContext } from '../types'
|
|
10
9
|
import {
|
|
11
|
-
PrjctCommandsBase,
|
|
12
|
-
contextBuilder,
|
|
13
|
-
toolRegistry,
|
|
14
|
-
pathManager,
|
|
15
10
|
configManager,
|
|
16
|
-
|
|
11
|
+
contextBuilder,
|
|
17
12
|
dateHelper,
|
|
18
|
-
|
|
13
|
+
jsonlHelper,
|
|
14
|
+
out,
|
|
15
|
+
PrjctCommandsBase,
|
|
16
|
+
pathManager,
|
|
17
|
+
toolRegistry,
|
|
19
18
|
} from './base'
|
|
20
|
-
import {
|
|
19
|
+
import { commandRegistry } from './registry'
|
|
21
20
|
|
|
22
21
|
interface MemoryEntry {
|
|
23
22
|
timestamp: string
|
|
@@ -30,7 +29,10 @@ export class AnalyticsCommands extends PrjctCommandsBase {
|
|
|
30
29
|
* /p:dash - Unified dashboard
|
|
31
30
|
* Views: default, week, month, roadmap, compact
|
|
32
31
|
*/
|
|
33
|
-
async dash(
|
|
32
|
+
async dash(
|
|
33
|
+
view: string = 'default',
|
|
34
|
+
projectPath: string = process.cwd()
|
|
35
|
+
): Promise<CommandResult> {
|
|
34
36
|
try {
|
|
35
37
|
const initResult = await this.ensureProjectInit(projectPath)
|
|
36
38
|
if (!initResult.success) return initResult
|
|
@@ -72,9 +74,11 @@ export class AnalyticsCommands extends PrjctCommandsBase {
|
|
|
72
74
|
const memoryPath = pathManager.getFilePath(projectId, 'memory', 'context.jsonl')
|
|
73
75
|
let entries: MemoryEntry[] = []
|
|
74
76
|
try {
|
|
75
|
-
const allEntries = await jsonlHelper.readJsonLines(memoryPath) as MemoryEntry[]
|
|
77
|
+
const allEntries = (await jsonlHelper.readJsonLines(memoryPath)) as MemoryEntry[]
|
|
76
78
|
entries = allEntries.filter((e) => new Date(e.timestamp) >= startDate)
|
|
77
|
-
} catch {
|
|
79
|
+
} catch {
|
|
80
|
+
entries = []
|
|
81
|
+
}
|
|
78
82
|
|
|
79
83
|
const metrics = {
|
|
80
84
|
tasksCompleted: entries.filter((e) => e.action === 'task_completed').length,
|
|
@@ -98,8 +102,10 @@ export class AnalyticsCommands extends PrjctCommandsBase {
|
|
|
98
102
|
|
|
99
103
|
if (view === 'roadmap') {
|
|
100
104
|
// Roadmap view
|
|
101
|
-
const context = await contextBuilder.build(projectPath) as ProjectContext
|
|
102
|
-
const roadmapContent = (await toolRegistry.get('Read')!(context.paths.roadmap)) as
|
|
105
|
+
const context = (await contextBuilder.build(projectPath)) as ProjectContext
|
|
106
|
+
const roadmapContent = (await toolRegistry.get('Read')!(context.paths.roadmap)) as
|
|
107
|
+
| string
|
|
108
|
+
| null
|
|
103
109
|
|
|
104
110
|
console.log(`\n🗺️ ROADMAP - ${projectName}\n`)
|
|
105
111
|
console.log('═'.repeat(50))
|
|
@@ -109,7 +115,9 @@ export class AnalyticsCommands extends PrjctCommandsBase {
|
|
|
109
115
|
console.log(' Use /p:feature to add features.\n')
|
|
110
116
|
} else {
|
|
111
117
|
// Parse and display roadmap
|
|
112
|
-
const features = roadmapContent
|
|
118
|
+
const features = roadmapContent
|
|
119
|
+
.split('##')
|
|
120
|
+
.filter((s) => s.trim() && !s.includes('ROADMAP'))
|
|
113
121
|
features.slice(0, 5).forEach((f, i) => {
|
|
114
122
|
const name = f.split('\n')[0].trim()
|
|
115
123
|
console.log(` ${i + 1}. ${name}`)
|
|
@@ -118,7 +126,7 @@ export class AnalyticsCommands extends PrjctCommandsBase {
|
|
|
118
126
|
console.log(` ... and ${features.length - 5} more`)
|
|
119
127
|
}
|
|
120
128
|
}
|
|
121
|
-
console.log('═'.repeat(50)
|
|
129
|
+
console.log(`${'═'.repeat(50)}\n`)
|
|
122
130
|
|
|
123
131
|
return { success: true, view: 'roadmap' }
|
|
124
132
|
}
|
|
@@ -168,7 +176,7 @@ export class AnalyticsCommands extends PrjctCommandsBase {
|
|
|
168
176
|
console.log('\n💡 IDEAS')
|
|
169
177
|
console.log(` ${ideas.length} pending ideas`)
|
|
170
178
|
|
|
171
|
-
console.log(
|
|
179
|
+
console.log(`\n${'═'.repeat(50)}`)
|
|
172
180
|
console.log('💡 /p:work to start | /p:done to complete | /p:ship to ship\n')
|
|
173
181
|
|
|
174
182
|
await this.logToMemory(projectPath, 'dash_viewed', {
|
|
@@ -183,8 +191,8 @@ export class AnalyticsCommands extends PrjctCommandsBase {
|
|
|
183
191
|
currentTask: currentTask?.description || null,
|
|
184
192
|
queueCount: queueTasks.length,
|
|
185
193
|
shippedCount: shipped.length,
|
|
186
|
-
ideasCount: ideas.length
|
|
187
|
-
}
|
|
194
|
+
ideasCount: ideas.length,
|
|
195
|
+
},
|
|
188
196
|
}
|
|
189
197
|
} catch (error) {
|
|
190
198
|
out.fail((error as Error).message)
|
|
@@ -195,7 +203,7 @@ export class AnalyticsCommands extends PrjctCommandsBase {
|
|
|
195
203
|
/**
|
|
196
204
|
* /p:help - Contextual help and guidance
|
|
197
205
|
*/
|
|
198
|
-
async help(topic: string = '',
|
|
206
|
+
async help(topic: string = '', _projectPath: string = process.cwd()): Promise<CommandResult> {
|
|
199
207
|
try {
|
|
200
208
|
if (!topic) {
|
|
201
209
|
// Show command overview
|
|
@@ -207,7 +215,7 @@ export class AnalyticsCommands extends PrjctCommandsBase {
|
|
|
207
215
|
|
|
208
216
|
// Group by category
|
|
209
217
|
const byCategory: Record<string, typeof commands> = {}
|
|
210
|
-
commands.forEach(cmd => {
|
|
218
|
+
commands.forEach((cmd) => {
|
|
211
219
|
if (cmd.deprecated) return
|
|
212
220
|
if (!byCategory[cmd.group]) byCategory[cmd.group] = []
|
|
213
221
|
byCategory[cmd.group].push(cmd)
|
|
@@ -216,14 +224,14 @@ export class AnalyticsCommands extends PrjctCommandsBase {
|
|
|
216
224
|
Object.entries(byCategory).forEach(([cat, cmds]) => {
|
|
217
225
|
const catInfo = categories.get(cat)
|
|
218
226
|
console.log(`\n${catInfo?.title || cat}:`)
|
|
219
|
-
cmds.forEach(cmd => {
|
|
227
|
+
cmds.forEach((cmd) => {
|
|
220
228
|
const params = cmd.params ? ` ${cmd.params}` : ''
|
|
221
229
|
console.log(` ${cmd.name}${params}`)
|
|
222
230
|
console.log(` ${cmd.description}`)
|
|
223
231
|
})
|
|
224
232
|
})
|
|
225
233
|
|
|
226
|
-
console.log(
|
|
234
|
+
console.log(`\n${'═'.repeat(50)}`)
|
|
227
235
|
console.log('💡 Use /p:help <command> for detailed help\n')
|
|
228
236
|
|
|
229
237
|
return { success: true, topic: 'overview' }
|
|
@@ -248,31 +256,31 @@ export class AnalyticsCommands extends PrjctCommandsBase {
|
|
|
248
256
|
|
|
249
257
|
if (command.features) {
|
|
250
258
|
console.log('\nFeatures:')
|
|
251
|
-
command.features.forEach(f => console.log(` • ${f}`))
|
|
259
|
+
command.features.forEach((f) => console.log(` • ${f}`))
|
|
252
260
|
}
|
|
253
261
|
|
|
254
|
-
console.log(
|
|
262
|
+
console.log(`\n${'═'.repeat(50)}\n`)
|
|
255
263
|
return { success: true, topic, command }
|
|
256
264
|
}
|
|
257
265
|
|
|
258
266
|
// Intent translation (like old /p:ask)
|
|
259
267
|
const intents: Record<string, { command: string; hint: string }> = {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
268
|
+
start: { command: 'work', hint: 'Start working on a task' },
|
|
269
|
+
begin: { command: 'work', hint: 'Start working on a task' },
|
|
270
|
+
finish: { command: 'done', hint: 'Mark current task complete' },
|
|
271
|
+
complete: { command: 'done', hint: 'Mark current task complete' },
|
|
272
|
+
deploy: { command: 'ship', hint: 'Ship a feature' },
|
|
273
|
+
release: { command: 'ship', hint: 'Ship a feature' },
|
|
274
|
+
status: { command: 'dash', hint: 'View project dashboard' },
|
|
275
|
+
overview: { command: 'dash', hint: 'View project dashboard' },
|
|
276
|
+
queue: { command: 'next', hint: 'View task queue' },
|
|
277
|
+
tasks: { command: 'next', hint: 'View task queue' },
|
|
278
|
+
add: { command: 'feature', hint: 'Add a new feature' },
|
|
279
|
+
new: { command: 'feature', hint: 'Add a new feature' },
|
|
280
|
+
break: { command: 'pause', hint: 'Pause current task' },
|
|
281
|
+
stop: { command: 'pause', hint: 'Pause current task' },
|
|
282
|
+
continue: { command: 'resume', hint: 'Resume paused task' },
|
|
283
|
+
back: { command: 'resume', hint: 'Resume paused task' },
|
|
276
284
|
}
|
|
277
285
|
|
|
278
286
|
const lowerTopic = topic.toLowerCase()
|
|
@@ -308,7 +316,7 @@ export class AnalyticsCommands extends PrjctCommandsBase {
|
|
|
308
316
|
const dayStart = new Date(date.setHours(0, 0, 0, 0))
|
|
309
317
|
const dayEnd = new Date(date.setHours(23, 59, 59, 999))
|
|
310
318
|
|
|
311
|
-
const count = entries.filter(e => {
|
|
319
|
+
const count = entries.filter((e) => {
|
|
312
320
|
const ts = new Date(e.timestamp)
|
|
313
321
|
return ts >= dayStart && ts <= dayEnd
|
|
314
322
|
}).length
|
|
@@ -317,6 +325,6 @@ export class AnalyticsCommands extends PrjctCommandsBase {
|
|
|
317
325
|
}
|
|
318
326
|
|
|
319
327
|
const max = Math.max(...counts, 1)
|
|
320
|
-
return counts.map(c => bars[Math.floor((c / max) * (bars.length - 1))]).join('')
|
|
328
|
+
return counts.map((c) => bars[Math.floor((c / max) * (bars.length - 1))]).join('')
|
|
321
329
|
}
|
|
322
330
|
}
|
package/core/commands/base.ts
CHANGED
|
@@ -8,24 +8,22 @@
|
|
|
8
8
|
import commandExecutor from '../agentic/command-executor'
|
|
9
9
|
import contextBuilder from '../agentic/context-builder'
|
|
10
10
|
import toolRegistry from '../agentic/tool-registry'
|
|
11
|
-
import pathManager from '../infrastructure/path-manager'
|
|
12
11
|
import configManager from '../infrastructure/config-manager'
|
|
12
|
+
import pathManager from '../infrastructure/path-manager'
|
|
13
13
|
import UpdateChecker from '../infrastructure/update-checker'
|
|
14
|
-
import dateHelper from '../utils/date-helper'
|
|
15
|
-
import jsonlHelper from '../utils/jsonl-helper'
|
|
16
|
-
import * as fileHelper from '../utils/file-helper'
|
|
17
|
-
import out from '../utils/output'
|
|
18
|
-
|
|
19
14
|
// Services
|
|
20
|
-
import { agentService,
|
|
21
|
-
|
|
15
|
+
import { agentService, breakdownService, memoryService, projectService } from '../services'
|
|
22
16
|
import type {
|
|
23
|
-
|
|
17
|
+
AgentAssignmentResult,
|
|
24
18
|
AgentInfo,
|
|
25
19
|
Author,
|
|
26
|
-
|
|
27
|
-
ProjectContext
|
|
20
|
+
CommandResult,
|
|
21
|
+
ProjectContext,
|
|
28
22
|
} from '../types'
|
|
23
|
+
import dateHelper from '../utils/date-helper'
|
|
24
|
+
import * as fileHelper from '../utils/file-helper'
|
|
25
|
+
import jsonlHelper from '../utils/jsonl-helper'
|
|
26
|
+
import out from '../utils/output'
|
|
29
27
|
|
|
30
28
|
/**
|
|
31
29
|
* Base class with shared state and utilities
|
|
@@ -73,7 +71,11 @@ export class PrjctCommandsBase {
|
|
|
73
71
|
return projectService.getGlobalPath(projectPath)
|
|
74
72
|
}
|
|
75
73
|
|
|
76
|
-
async logToMemory(
|
|
74
|
+
async logToMemory(
|
|
75
|
+
projectPath: string,
|
|
76
|
+
action: string,
|
|
77
|
+
data: Record<string, unknown>
|
|
78
|
+
): Promise<void> {
|
|
77
79
|
const author = await this.ensureAuthor()
|
|
78
80
|
return memoryService.log(projectPath, action, data, author.name)
|
|
79
81
|
}
|
|
@@ -112,5 +114,5 @@ export {
|
|
|
112
114
|
fileHelper,
|
|
113
115
|
jsonlHelper,
|
|
114
116
|
dateHelper,
|
|
115
|
-
out
|
|
117
|
+
out,
|
|
116
118
|
}
|
package/core/commands/cleanup.ts
CHANGED
|
@@ -4,19 +4,12 @@
|
|
|
4
4
|
* Memory and project file cleanup operations.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import path from 'path'
|
|
8
|
-
|
|
9
|
-
import { isNotFoundError } from '../types/fs'
|
|
10
|
-
import type { CommandResult, CleanupOptions } from '../types'
|
|
11
|
-
import {
|
|
12
|
-
pathManager,
|
|
13
|
-
configManager,
|
|
14
|
-
jsonlHelper,
|
|
15
|
-
dateHelper,
|
|
16
|
-
out
|
|
17
|
-
} from './base'
|
|
18
|
-
import { ideasStorage, queueStorage } from '../storage'
|
|
7
|
+
import path from 'node:path'
|
|
19
8
|
import { memoryService } from '../services'
|
|
9
|
+
import { ideasStorage, queueStorage } from '../storage'
|
|
10
|
+
import type { CleanupOptions, CommandResult } from '../types'
|
|
11
|
+
import { isNotFoundError } from '../types/fs'
|
|
12
|
+
import { configManager, dateHelper, jsonlHelper, out, pathManager } from './base'
|
|
20
13
|
|
|
21
14
|
/**
|
|
22
15
|
* Memory cleanup helper
|
|
@@ -131,7 +124,7 @@ export async function cleanup(
|
|
|
131
124
|
// Check queue for completed tasks using queueStorage
|
|
132
125
|
try {
|
|
133
126
|
const tasks = await queueStorage.getActiveTasks(projectId)
|
|
134
|
-
const completedTasks = tasks.filter(t => t.completed).length
|
|
127
|
+
const completedTasks = tasks.filter((t) => t.completed).length
|
|
135
128
|
|
|
136
129
|
if (completedTasks > 0) {
|
|
137
130
|
cleaned.push(
|