prjct-cli 0.45.0 → 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 +75 -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
|
@@ -8,16 +8,16 @@
|
|
|
8
8
|
* Re-exported from issue-tracker module
|
|
9
9
|
*/
|
|
10
10
|
export type {
|
|
11
|
+
GitHubConfig,
|
|
11
12
|
IssueTrackerConfig,
|
|
12
|
-
LinearConfig,
|
|
13
13
|
JiraConfig,
|
|
14
|
+
LinearConfig,
|
|
14
15
|
MondayConfig,
|
|
15
|
-
GitHubConfig,
|
|
16
16
|
} from '../integrations/issue-tracker/types'
|
|
17
17
|
|
|
18
18
|
export {
|
|
19
|
-
DEFAULT_LINEAR_CONFIG,
|
|
20
19
|
DEFAULT_JIRA_CONFIG,
|
|
20
|
+
DEFAULT_LINEAR_CONFIG,
|
|
21
21
|
} from '../integrations/issue-tracker/types'
|
|
22
22
|
|
|
23
23
|
/**
|
package/core/types/storage.ts
CHANGED
|
@@ -155,20 +155,20 @@ export interface IdeasJson {
|
|
|
155
155
|
* Daily stats for trend analysis
|
|
156
156
|
*/
|
|
157
157
|
export interface DailyStats {
|
|
158
|
-
date: string
|
|
159
|
-
tokensSaved: number
|
|
160
|
-
syncs: number
|
|
161
|
-
avgCompressionRate: number
|
|
162
|
-
totalDuration: number
|
|
158
|
+
date: string // YYYY-MM-DD
|
|
159
|
+
tokensSaved: number // Tokens saved that day
|
|
160
|
+
syncs: number // Number of syncs
|
|
161
|
+
avgCompressionRate: number // Average compression rate (0-1)
|
|
162
|
+
totalDuration: number // Total sync time in ms
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
/**
|
|
166
166
|
* Agent usage tracking
|
|
167
167
|
*/
|
|
168
168
|
export interface AgentUsage {
|
|
169
|
-
agentName: string
|
|
170
|
-
usageCount: number
|
|
171
|
-
tokensSaved: number
|
|
169
|
+
agentName: string // e.g., "backend", "frontend"
|
|
170
|
+
usageCount: number // Times invoked
|
|
171
|
+
tokensSaved: number // Tokens saved by this agent
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
/**
|
|
@@ -177,13 +177,13 @@ export interface AgentUsage {
|
|
|
177
177
|
export interface MetricsJson {
|
|
178
178
|
// Token metrics
|
|
179
179
|
totalTokensSaved: number
|
|
180
|
-
avgCompressionRate: number
|
|
180
|
+
avgCompressionRate: number // 0-1 (e.g., 0.63 = 63% reduction)
|
|
181
181
|
|
|
182
182
|
// Sync metrics
|
|
183
183
|
syncCount: number
|
|
184
|
-
watchTriggers: number
|
|
185
|
-
avgSyncDuration: number
|
|
186
|
-
totalSyncDuration: number
|
|
184
|
+
watchTriggers: number // Auto-syncs from watch mode
|
|
185
|
+
avgSyncDuration: number // Average in ms
|
|
186
|
+
totalSyncDuration: number // Total in ms
|
|
187
187
|
|
|
188
188
|
// Agent usage
|
|
189
189
|
agentUsage: AgentUsage[]
|
|
@@ -192,6 +192,6 @@ export interface MetricsJson {
|
|
|
192
192
|
dailyStats: DailyStats[]
|
|
193
193
|
|
|
194
194
|
// Metadata
|
|
195
|
-
firstSync: string
|
|
196
|
-
lastUpdated: string
|
|
195
|
+
firstSync: string // ISO8601 - when tracking started
|
|
196
|
+
lastUpdated: string // ISO8601
|
|
197
197
|
}
|
package/core/types/utils.ts
CHANGED
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
|
|
6
6
|
// Re-export file system types
|
|
7
7
|
export {
|
|
8
|
-
NodeError,
|
|
9
|
-
isNotFoundError,
|
|
10
|
-
isPermissionError,
|
|
11
8
|
isDirNotEmptyError,
|
|
12
9
|
isFileExistsError,
|
|
13
10
|
isNodeError,
|
|
11
|
+
isNotFoundError,
|
|
12
|
+
isPermissionError,
|
|
13
|
+
NodeError,
|
|
14
14
|
} from './fs'
|
|
15
15
|
|
|
16
16
|
export type AsyncFunction<T = unknown> = (...args: unknown[]) => Promise<T>
|
|
@@ -118,7 +118,9 @@ class AgentStream {
|
|
|
118
118
|
complete(taskName: string, totalDuration?: number): void {
|
|
119
119
|
if (this.quiet) return
|
|
120
120
|
|
|
121
|
-
const durationStr = totalDuration
|
|
121
|
+
const durationStr = totalDuration
|
|
122
|
+
? ` ${chalk.dim(`[${this.formatDuration(totalDuration)}]`)}`
|
|
123
|
+
: ''
|
|
122
124
|
console.log(chalk.green(`✅ ${taskName}${durationStr}`))
|
|
123
125
|
}
|
|
124
126
|
|
package/core/utils/animations.ts
CHANGED
|
@@ -132,15 +132,15 @@ function sleep(ms: number): Promise<void> {
|
|
|
132
132
|
|
|
133
133
|
export async function animate(frameList: string[], duration = 100): Promise<void> {
|
|
134
134
|
for (const frame of frameList) {
|
|
135
|
-
process.stdout.write(
|
|
135
|
+
process.stdout.write(`\r${frame}`)
|
|
136
136
|
await sleep(duration)
|
|
137
137
|
}
|
|
138
|
-
process.stdout.write(
|
|
138
|
+
process.stdout.write(`\r${' '.repeat(30)}\r`)
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
export async function typeWriter(text: string, delay = 30): Promise<void> {
|
|
142
142
|
for (let i = 0; i <= text.length; i++) {
|
|
143
|
-
process.stdout.write(
|
|
143
|
+
process.stdout.write(`\r${text.slice(0, i)}${i < text.length ? '▋' : ''}`)
|
|
144
144
|
await sleep(delay)
|
|
145
145
|
}
|
|
146
146
|
process.stdout.write('\n')
|
|
@@ -154,8 +154,8 @@ export async function progressBar(duration = 1000, label = 'Processing'): Promis
|
|
|
154
154
|
const percent = Math.round((i / steps) * 100)
|
|
155
155
|
const filled = '▓'.repeat(i)
|
|
156
156
|
const empty = '░'.repeat(steps - i)
|
|
157
|
-
const bar = `${colors.dim(label)} [${colors.primary(filled)}${colors.dim(empty)}] ${colors.text(percent
|
|
158
|
-
process.stdout.write(
|
|
157
|
+
const bar = `${colors.dim(label)} [${colors.primary(filled)}${colors.dim(empty)}] ${colors.text(`${percent}%`)}`
|
|
158
|
+
process.stdout.write(`\r${bar}`)
|
|
159
159
|
await sleep(stepDuration)
|
|
160
160
|
}
|
|
161
161
|
process.stdout.write('\n')
|
|
@@ -168,9 +168,9 @@ export async function sparkle(message: string): Promise<void> {
|
|
|
168
168
|
for (let i = 0; i < 3; i++) {
|
|
169
169
|
const spark = sparkles[Math.floor(Math.random() * sparkles.length)]
|
|
170
170
|
output = `${spark} ${message} ${spark}`
|
|
171
|
-
process.stdout.write(
|
|
171
|
+
process.stdout.write(`\r${output}`)
|
|
172
172
|
await sleep(200)
|
|
173
|
-
process.stdout.write(
|
|
173
|
+
process.stdout.write(`\r${' '.repeat(output.length)}`)
|
|
174
174
|
await sleep(100)
|
|
175
175
|
}
|
|
176
176
|
|
|
@@ -216,13 +216,17 @@ ${colors.dim('Added to your ideas backlog')}
|
|
|
216
216
|
`
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
-
export function formatCleanup(
|
|
219
|
+
export function formatCleanup(
|
|
220
|
+
filesRemoved: number,
|
|
221
|
+
tasksArchived: number,
|
|
222
|
+
spaceFeed: number
|
|
223
|
+
): string {
|
|
220
224
|
return `
|
|
221
225
|
${banners.cleanup}
|
|
222
226
|
|
|
223
227
|
${colors.text('🗑️ Files removed:')} ${colors.success.bold(String(filesRemoved))}
|
|
224
228
|
${colors.text('📦 Tasks archived:')} ${colors.success.bold(String(tasksArchived))}
|
|
225
|
-
${colors.text('💾 Space freed:')} ${colors.success.bold(spaceFeed
|
|
229
|
+
${colors.text('💾 Space freed:')} ${colors.success.bold(`${spaceFeed} MB`)}
|
|
226
230
|
|
|
227
231
|
${colors.celebrate('✨ Your project is clean and lean!')}
|
|
228
232
|
`
|
|
@@ -261,6 +265,5 @@ export default {
|
|
|
261
265
|
formatError,
|
|
262
266
|
formatIdea,
|
|
263
267
|
formatCleanup,
|
|
264
|
-
formatRecap
|
|
268
|
+
formatRecap,
|
|
265
269
|
}
|
|
266
|
-
|
package/core/utils/branding.ts
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import chalk from 'chalk'
|
|
9
|
+
import { getProviderBranding } from '../infrastructure/ai-provider'
|
|
9
10
|
import type { AIProviderName } from '../types/provider'
|
|
10
|
-
import { getProviderBranding, Providers } from '../infrastructure/ai-provider'
|
|
11
11
|
|
|
12
12
|
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
|
|
13
13
|
const SPINNER_SPEED = 80
|
|
@@ -48,21 +48,21 @@ const branding: Branding = {
|
|
|
48
48
|
// Spinner config
|
|
49
49
|
spinner: {
|
|
50
50
|
frames: SPINNER_FRAMES,
|
|
51
|
-
speed: SPINNER_SPEED
|
|
51
|
+
speed: SPINNER_SPEED,
|
|
52
52
|
},
|
|
53
53
|
|
|
54
54
|
// CLI output (with chalk colors)
|
|
55
55
|
cli: {
|
|
56
|
-
header: () => chalk.cyan.bold('⚡')
|
|
56
|
+
header: () => `${chalk.cyan.bold('⚡')} ${chalk.cyan('prjct')}`,
|
|
57
57
|
footer: () => chalk.dim('⚡ prjct'),
|
|
58
58
|
spin: (frame: number, msg?: string) =>
|
|
59
|
-
chalk.cyan('⚡')
|
|
59
|
+
`${chalk.cyan('⚡')} ${chalk.cyan('prjct')} ${chalk.cyan(SPINNER_FRAMES[frame % 10])} ${chalk.dim(msg || '')}`,
|
|
60
60
|
},
|
|
61
61
|
|
|
62
62
|
// Template (plain text)
|
|
63
63
|
template: {
|
|
64
64
|
header: '⚡ prjct',
|
|
65
|
-
footer: '⚡ prjct'
|
|
65
|
+
footer: '⚡ prjct',
|
|
66
66
|
},
|
|
67
67
|
|
|
68
68
|
// Default Git commit footer (generic)
|
|
@@ -71,7 +71,7 @@ const branding: Branding = {
|
|
|
71
71
|
// URLs
|
|
72
72
|
urls: {
|
|
73
73
|
website: 'https://prjct.app',
|
|
74
|
-
docs: 'https://prjct.app/docs'
|
|
74
|
+
docs: 'https://prjct.app/docs',
|
|
75
75
|
},
|
|
76
76
|
|
|
77
77
|
// Provider-aware commit footer
|
|
@@ -82,7 +82,7 @@ const branding: Branding = {
|
|
|
82
82
|
// Provider-aware signature
|
|
83
83
|
getSignature: (provider: AIProviderName = 'claude') => {
|
|
84
84
|
return getProviderBranding(provider).signature
|
|
85
|
-
}
|
|
85
|
+
},
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
export default branding
|
package/core/utils/cache.ts
CHANGED
|
@@ -91,9 +91,7 @@ export class TTLCache<T> {
|
|
|
91
91
|
private evictOldEntries(): void {
|
|
92
92
|
if (this.cache.size <= this.maxSize) return
|
|
93
93
|
|
|
94
|
-
const entries = Array.from(this.cache.entries()).sort(
|
|
95
|
-
(a, b) => a[1].timestamp - b[1].timestamp
|
|
96
|
-
)
|
|
94
|
+
const entries = Array.from(this.cache.entries()).sort((a, b) => a[1].timestamp - b[1].timestamp)
|
|
97
95
|
|
|
98
96
|
const toRemove = entries.slice(0, this.cache.size - this.maxSize)
|
|
99
97
|
for (const [key] of toRemove) {
|
|
@@ -29,33 +29,21 @@ export const SECTION_ORDER: Record<TaskSection, number> = {
|
|
|
29
29
|
/**
|
|
30
30
|
* Filter items by a specific field value
|
|
31
31
|
*/
|
|
32
|
-
export function filterByField<T, K extends keyof T>(
|
|
33
|
-
items: T[],
|
|
34
|
-
field: K,
|
|
35
|
-
value: T[K]
|
|
36
|
-
): T[] {
|
|
32
|
+
export function filterByField<T, K extends keyof T>(items: T[], field: K, value: T[K]): T[] {
|
|
37
33
|
return items.filter((item) => item[field] === value)
|
|
38
34
|
}
|
|
39
35
|
|
|
40
36
|
/**
|
|
41
37
|
* Filter items by multiple field values (OR logic)
|
|
42
38
|
*/
|
|
43
|
-
export function filterByFieldIn<T, K extends keyof T>(
|
|
44
|
-
items: T[],
|
|
45
|
-
field: K,
|
|
46
|
-
values: T[K][]
|
|
47
|
-
): T[] {
|
|
39
|
+
export function filterByFieldIn<T, K extends keyof T>(items: T[], field: K, values: T[K][]): T[] {
|
|
48
40
|
return items.filter((item) => values.includes(item[field]))
|
|
49
41
|
}
|
|
50
42
|
|
|
51
43
|
/**
|
|
52
44
|
* Filter items excluding a specific field value
|
|
53
45
|
*/
|
|
54
|
-
export function filterByFieldNot<T, K extends keyof T>(
|
|
55
|
-
items: T[],
|
|
56
|
-
field: K,
|
|
57
|
-
value: T[K]
|
|
58
|
-
): T[] {
|
|
46
|
+
export function filterByFieldNot<T, K extends keyof T>(items: T[], field: K, value: T[K]): T[] {
|
|
59
47
|
return items.filter((item) => item[field] !== value)
|
|
60
48
|
}
|
|
61
49
|
|
|
@@ -97,11 +97,7 @@ export function getDateRange(fromDate: Date, toDate: Date): Date[] {
|
|
|
97
97
|
|
|
98
98
|
while (current <= toDate) {
|
|
99
99
|
dates.push(new Date(current))
|
|
100
|
-
current = new Date(
|
|
101
|
-
current.getFullYear(),
|
|
102
|
-
current.getMonth(),
|
|
103
|
-
current.getDate() + 1,
|
|
104
|
-
)
|
|
100
|
+
current = new Date(current.getFullYear(), current.getMonth(), current.getDate() + 1)
|
|
105
101
|
}
|
|
106
102
|
|
|
107
103
|
return dates
|
|
@@ -186,6 +182,5 @@ export default {
|
|
|
186
182
|
formatDuration,
|
|
187
183
|
calculateDuration,
|
|
188
184
|
getStartOfDay,
|
|
189
|
-
getEndOfDay
|
|
185
|
+
getEndOfDay,
|
|
190
186
|
}
|
|
191
|
-
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import fs from 'fs/promises'
|
|
2
|
-
import path from 'path'
|
|
3
|
-
import {
|
|
1
|
+
import fs from 'node:fs/promises'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { isNotFoundError } from '../types/fs'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* File Helper - Centralized file operations with error handling
|
|
@@ -20,7 +20,10 @@ interface ListFilesOptions {
|
|
|
20
20
|
/**
|
|
21
21
|
* Read JSON file and parse
|
|
22
22
|
*/
|
|
23
|
-
export async function readJson<T = unknown>(
|
|
23
|
+
export async function readJson<T = unknown>(
|
|
24
|
+
filePath: string,
|
|
25
|
+
defaultValue: T | null = null
|
|
26
|
+
): Promise<T | null> {
|
|
24
27
|
try {
|
|
25
28
|
const content = await fs.readFile(filePath, 'utf-8')
|
|
26
29
|
return JSON.parse(content) as T
|
|
@@ -87,7 +90,7 @@ export async function appendToFile(filePath: string, content: string): Promise<v
|
|
|
87
90
|
export async function appendLine(filePath: string, line: string): Promise<void> {
|
|
88
91
|
const dir = path.dirname(filePath)
|
|
89
92
|
await fs.mkdir(dir, { recursive: true })
|
|
90
|
-
await fs.appendFile(filePath, line
|
|
93
|
+
await fs.appendFile(filePath, `${line}\n`, 'utf-8')
|
|
91
94
|
}
|
|
92
95
|
|
|
93
96
|
/**
|
|
@@ -176,7 +179,10 @@ export async function deleteDir(dirPath: string): Promise<boolean> {
|
|
|
176
179
|
/**
|
|
177
180
|
* List files in directory
|
|
178
181
|
*/
|
|
179
|
-
export async function listFiles(
|
|
182
|
+
export async function listFiles(
|
|
183
|
+
dirPath: string,
|
|
184
|
+
options: ListFilesOptions = {}
|
|
185
|
+
): Promise<string[]> {
|
|
180
186
|
try {
|
|
181
187
|
const entries = await fs.readdir(dirPath, { withFileTypes: true })
|
|
182
188
|
let files = entries
|
|
@@ -285,6 +291,5 @@ export default {
|
|
|
285
291
|
readLines,
|
|
286
292
|
writeLines,
|
|
287
293
|
getFileExtension,
|
|
288
|
-
getFileNameWithoutExtension
|
|
294
|
+
getFileNameWithoutExtension,
|
|
289
295
|
}
|
|
290
|
-
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
1
|
+
import fsSync from 'node:fs'
|
|
2
|
+
import fs from 'node:fs/promises'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import readline from 'node:readline'
|
|
5
5
|
import { isNotFoundError } from '../types/fs'
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -46,7 +46,7 @@ export function parseJsonLines<T = Record<string, unknown>>(content: string): T[
|
|
|
46
46
|
* Convert array of objects to JSONL string
|
|
47
47
|
*/
|
|
48
48
|
export function stringifyJsonLines(objects: unknown[]): string {
|
|
49
|
-
return objects.map((obj) => JSON.stringify(obj)).join('\n')
|
|
49
|
+
return `${objects.map((obj) => JSON.stringify(obj)).join('\n')}\n`
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
/**
|
|
@@ -77,7 +77,7 @@ export async function writeJsonLines(filePath: string, objects: unknown[]): Prom
|
|
|
77
77
|
* Uses append mode for efficiency (no full file read/write)
|
|
78
78
|
*/
|
|
79
79
|
export async function appendJsonLine(filePath: string, object: unknown): Promise<void> {
|
|
80
|
-
const line = JSON.stringify(object)
|
|
80
|
+
const line = `${JSON.stringify(object)}\n`
|
|
81
81
|
await fs.appendFile(filePath, line, 'utf-8')
|
|
82
82
|
}
|
|
83
83
|
|
|
@@ -144,7 +144,9 @@ export async function getFirstJsonLines<T = Record<string, unknown>>(
|
|
|
144
144
|
* Merge multiple JSONL files into one array
|
|
145
145
|
* Useful for reading multiple sessions
|
|
146
146
|
*/
|
|
147
|
-
export async function mergeJsonLines<T = Record<string, unknown>>(
|
|
147
|
+
export async function mergeJsonLines<T = Record<string, unknown>>(
|
|
148
|
+
filePaths: string[]
|
|
149
|
+
): Promise<T[]> {
|
|
148
150
|
const allEntries: T[] = []
|
|
149
151
|
|
|
150
152
|
for (const filePath of filePaths) {
|
|
@@ -240,7 +242,9 @@ export async function rotateJsonLinesIfNeeded(filePath: string, maxSizeMB = 10):
|
|
|
240
242
|
// Move file to archive
|
|
241
243
|
await fs.rename(filePath, archivePath)
|
|
242
244
|
|
|
243
|
-
console.log(
|
|
245
|
+
console.log(
|
|
246
|
+
`📦 Rotated ${path.basename(filePath)} (${sizeMB.toFixed(1)}MB) → ${path.basename(archivePath)}`
|
|
247
|
+
)
|
|
244
248
|
|
|
245
249
|
return true
|
|
246
250
|
}
|
|
@@ -299,6 +303,5 @@ export default {
|
|
|
299
303
|
getFileSizeMB,
|
|
300
304
|
rotateJsonLinesIfNeeded,
|
|
301
305
|
appendJsonLineWithRotation,
|
|
302
|
-
checkFileSizeWarning
|
|
306
|
+
checkFileSizeWarning,
|
|
303
307
|
}
|
|
304
|
-
|
package/core/utils/keychain.ts
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* Falls back to environment variables if keychain is not available.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { exec } from 'child_process'
|
|
9
|
-
import { promisify } from 'util'
|
|
8
|
+
import { exec } from 'node:child_process'
|
|
9
|
+
import { promisify } from 'node:util'
|
|
10
10
|
|
|
11
11
|
const execAsync = promisify(exec)
|
|
12
12
|
|
|
@@ -30,9 +30,7 @@ export async function setCredential(key: CredentialKey, value: string): Promise<
|
|
|
30
30
|
)
|
|
31
31
|
|
|
32
32
|
// Add new entry
|
|
33
|
-
await execAsync(
|
|
34
|
-
`security add-generic-password -s "${SERVICE_NAME}" -a "${key}" -w "${value}"`
|
|
35
|
-
)
|
|
33
|
+
await execAsync(`security add-generic-password -s "${SERVICE_NAME}" -a "${key}" -w "${value}"`)
|
|
36
34
|
|
|
37
35
|
return true
|
|
38
36
|
} catch (error) {
|
|
@@ -70,9 +68,7 @@ export async function deleteCredential(key: CredentialKey): Promise<boolean> {
|
|
|
70
68
|
}
|
|
71
69
|
|
|
72
70
|
try {
|
|
73
|
-
await execAsync(
|
|
74
|
-
`security delete-generic-password -s "${SERVICE_NAME}" -a "${key}" 2>/dev/null`
|
|
75
|
-
)
|
|
71
|
+
await execAsync(`security delete-generic-password -s "${SERVICE_NAME}" -a "${key}" 2>/dev/null`)
|
|
76
72
|
return true
|
|
77
73
|
} catch (_error) {
|
|
78
74
|
// Not found in keychain - expected
|
package/core/utils/logger.ts
CHANGED
package/core/utils/next-steps.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import chalk from 'chalk'
|
|
9
|
-
import {
|
|
9
|
+
import { type WorkflowState, workflowStateMachine } from '../workflow/state-machine'
|
|
10
10
|
|
|
11
11
|
interface NextStep {
|
|
12
12
|
cmd: string
|
|
@@ -59,7 +59,7 @@ export function showNextSteps(command: string, options: { quiet?: boolean } = {}
|
|
|
59
59
|
|
|
60
60
|
if (validCommands.length === 0) return
|
|
61
61
|
|
|
62
|
-
const steps: NextStep[] = validCommands.map(cmd => ({
|
|
62
|
+
const steps: NextStep[] = validCommands.map((cmd) => ({
|
|
63
63
|
cmd: `p. ${cmd}`,
|
|
64
64
|
desc: CMD_DESCRIPTIONS[cmd] || cmd,
|
|
65
65
|
}))
|
|
@@ -78,7 +78,7 @@ export function getNextSteps(command: string): NextStep[] {
|
|
|
78
78
|
const resultingState = COMMAND_TO_STATE[command] || 'idle'
|
|
79
79
|
const validCommands = workflowStateMachine.getValidCommands(resultingState)
|
|
80
80
|
|
|
81
|
-
return validCommands.map(cmd => ({
|
|
81
|
+
return validCommands.map((cmd) => ({
|
|
82
82
|
cmd: `p. ${cmd}`,
|
|
83
83
|
desc: CMD_DESCRIPTIONS[cmd] || cmd,
|
|
84
84
|
}))
|
package/core/utils/output.ts
CHANGED
|
@@ -2,27 +2,56 @@
|
|
|
2
2
|
* Minimal Output System for prjct-cli
|
|
3
3
|
* Spinner while working → Single line result
|
|
4
4
|
* With prjct branding
|
|
5
|
+
*
|
|
6
|
+
* Supports --quiet mode for CI/CD and scripting
|
|
5
7
|
*/
|
|
6
8
|
|
|
7
9
|
import chalk from 'chalk'
|
|
8
10
|
import branding from './branding'
|
|
9
11
|
|
|
10
|
-
const
|
|
12
|
+
const _FRAMES = branding.spinner.frames
|
|
11
13
|
const SPEED = branding.spinner.speed
|
|
12
14
|
|
|
13
15
|
let interval: ReturnType<typeof setInterval> | null = null
|
|
14
16
|
let frame = 0
|
|
15
17
|
|
|
18
|
+
// Quiet mode - suppress all stdout except errors
|
|
19
|
+
let quietMode = false
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Enable quiet mode (no stdout, only stderr for errors)
|
|
23
|
+
*/
|
|
24
|
+
export function setQuietMode(enabled: boolean): void {
|
|
25
|
+
quietMode = enabled
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Check if quiet mode is enabled
|
|
30
|
+
*/
|
|
31
|
+
export function isQuietMode(): boolean {
|
|
32
|
+
return quietMode
|
|
33
|
+
}
|
|
34
|
+
|
|
16
35
|
const truncate = (s: string | undefined | null, max = 50): string =>
|
|
17
|
-
s && s.length > max ? s.slice(0, max - 1)
|
|
36
|
+
s && s.length > max ? `${s.slice(0, max - 1)}…` : s || ''
|
|
18
37
|
|
|
19
|
-
const clear = (): boolean => process.stdout.write(
|
|
38
|
+
const clear = (): boolean => process.stdout.write(`\r${' '.repeat(80)}\r`)
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Metrics to display after command completion
|
|
42
|
+
* Shows value provided by prjct (compression, agent count, etc.)
|
|
43
|
+
*/
|
|
44
|
+
interface OutputMetrics {
|
|
45
|
+
agents?: number // Number of agents used
|
|
46
|
+
reduction?: number // Context reduction percentage
|
|
47
|
+
tokens?: number // Token count (in thousands)
|
|
48
|
+
}
|
|
20
49
|
|
|
21
50
|
interface Output {
|
|
22
51
|
start(): Output
|
|
23
52
|
end(): Output
|
|
24
53
|
spin(msg: string): Output
|
|
25
|
-
done(msg: string): Output
|
|
54
|
+
done(msg: string, metrics?: OutputMetrics): Output
|
|
26
55
|
fail(msg: string): Output
|
|
27
56
|
warn(msg: string): Output
|
|
28
57
|
stop(): Output
|
|
@@ -33,18 +62,19 @@ interface Output {
|
|
|
33
62
|
const out: Output = {
|
|
34
63
|
// Branding: Show header at start
|
|
35
64
|
start() {
|
|
36
|
-
console.log(branding.cli.header())
|
|
65
|
+
if (!quietMode) console.log(branding.cli.header())
|
|
37
66
|
return this
|
|
38
67
|
},
|
|
39
68
|
|
|
40
69
|
// Branding: Show footer at end
|
|
41
70
|
end() {
|
|
42
|
-
console.log(branding.cli.footer())
|
|
71
|
+
if (!quietMode) console.log(branding.cli.footer())
|
|
43
72
|
return this
|
|
44
73
|
},
|
|
45
74
|
|
|
46
75
|
// Branded spinner: prjct message...
|
|
47
76
|
spin(msg: string) {
|
|
77
|
+
if (quietMode) return this
|
|
48
78
|
this.stop()
|
|
49
79
|
interval = setInterval(() => {
|
|
50
80
|
process.stdout.write(`\r${branding.cli.spin(frame++, truncate(msg, 45))}`)
|
|
@@ -52,21 +82,35 @@ const out: Output = {
|
|
|
52
82
|
return this
|
|
53
83
|
},
|
|
54
84
|
|
|
55
|
-
done(msg: string) {
|
|
85
|
+
done(msg: string, metrics?: OutputMetrics) {
|
|
56
86
|
this.stop()
|
|
57
|
-
|
|
87
|
+
if (!quietMode) {
|
|
88
|
+
// Build metrics suffix if provided: [2a | 97% | 45K]
|
|
89
|
+
let suffix = ''
|
|
90
|
+
if (metrics) {
|
|
91
|
+
const parts: string[] = []
|
|
92
|
+
if (metrics.agents !== undefined) parts.push(`${metrics.agents}a`)
|
|
93
|
+
if (metrics.reduction !== undefined) parts.push(`${metrics.reduction}%`)
|
|
94
|
+
if (metrics.tokens !== undefined) parts.push(`${Math.round(metrics.tokens)}K`)
|
|
95
|
+
if (parts.length > 0) {
|
|
96
|
+
suffix = chalk.dim(` [${parts.join(' | ')}]`)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
console.log(`${chalk.green('✓')} ${truncate(msg, 50)}${suffix}`)
|
|
100
|
+
}
|
|
58
101
|
return this
|
|
59
102
|
},
|
|
60
103
|
|
|
104
|
+
// Errors go to stderr even in quiet mode
|
|
61
105
|
fail(msg: string) {
|
|
62
106
|
this.stop()
|
|
63
|
-
console.
|
|
107
|
+
console.error(`${chalk.red('✗')} ${truncate(msg, 65)}`)
|
|
64
108
|
return this
|
|
65
109
|
},
|
|
66
110
|
|
|
67
111
|
warn(msg: string) {
|
|
68
112
|
this.stop()
|
|
69
|
-
console.log(`${chalk.yellow('⚠')} ${truncate(msg, 65)}`)
|
|
113
|
+
if (!quietMode) console.log(`${chalk.yellow('⚠')} ${truncate(msg, 65)}`)
|
|
70
114
|
return this
|
|
71
115
|
},
|
|
72
116
|
|
|
@@ -81,6 +125,7 @@ const out: Output = {
|
|
|
81
125
|
|
|
82
126
|
// Step counter: [3/7] Running tests...
|
|
83
127
|
step(current: number, total: number, msg: string) {
|
|
128
|
+
if (quietMode) return this
|
|
84
129
|
this.stop()
|
|
85
130
|
const counter = chalk.dim(`[${current}/${total}]`)
|
|
86
131
|
interval = setInterval(() => {
|
|
@@ -91,6 +136,7 @@ const out: Output = {
|
|
|
91
136
|
|
|
92
137
|
// Progress bar: [████░░░░] 50% Analyzing...
|
|
93
138
|
progress(current: number, total: number, msg?: string) {
|
|
139
|
+
if (quietMode) return this
|
|
94
140
|
this.stop()
|
|
95
141
|
const percent = Math.round((current / total) * 100)
|
|
96
142
|
const filled = Math.round(percent / 10)
|
|
@@ -101,7 +147,8 @@ const out: Output = {
|
|
|
101
147
|
process.stdout.write(`\r${branding.cli.spin(frame++, `[${bar}] ${percent}%${text}`)}`)
|
|
102
148
|
}, SPEED)
|
|
103
149
|
return this
|
|
104
|
-
}
|
|
150
|
+
},
|
|
105
151
|
}
|
|
106
152
|
|
|
153
|
+
export type { OutputMetrics }
|
|
107
154
|
export default out
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import path from 'path'
|
|
2
|
-
|
|
3
|
-
import * as fileHelper from './file-helper'
|
|
1
|
+
import path from 'node:path'
|
|
4
2
|
import type { DetectedProjectCommands } from '../types'
|
|
3
|
+
import * as fileHelper from './file-helper'
|
|
5
4
|
|
|
6
5
|
type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun'
|
|
7
6
|
type DetectedStack = 'js' | 'python' | 'go' | 'rust' | 'dotnet' | 'java' | 'unknown'
|
|
@@ -21,7 +20,10 @@ interface PackageJson {
|
|
|
21
20
|
*
|
|
22
21
|
* Reason: installed users may not have Bun, and many projects use pnpm/yarn.
|
|
23
22
|
*/
|
|
24
|
-
async function detectPackageManager(
|
|
23
|
+
async function detectPackageManager(
|
|
24
|
+
projectPath: string,
|
|
25
|
+
pkg: PackageJson | null
|
|
26
|
+
): Promise<PackageManager> {
|
|
25
27
|
const declared = pkg?.packageManager?.trim().toLowerCase()
|
|
26
28
|
if (declared?.startsWith('pnpm@')) return 'pnpm'
|
|
27
29
|
if (declared?.startsWith('yarn@')) return 'yarn'
|
|
@@ -128,5 +130,3 @@ export async function detectProjectCommands(projectPath: string): Promise<Detect
|
|
|
128
130
|
|
|
129
131
|
return { stack: 'unknown' }
|
|
130
132
|
}
|
|
131
|
-
|
|
132
|
-
|