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
|
@@ -17,23 +17,22 @@
|
|
|
17
17
|
* - scripts/postinstall.js (if npm scripts are enabled)
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import { execSync } from 'child_process'
|
|
21
|
-
import fs from 'fs'
|
|
22
|
-
import
|
|
23
|
-
import
|
|
24
|
-
import installer from './command-installer'
|
|
25
|
-
import editorsConfig from './editors-config'
|
|
26
|
-
import { VERSION, getPackageRoot } from '../utils/version'
|
|
20
|
+
import { execSync } from 'node:child_process'
|
|
21
|
+
import fs from 'node:fs'
|
|
22
|
+
import os from 'node:os'
|
|
23
|
+
import path from 'node:path'
|
|
27
24
|
import { isNotFoundError } from '../types/fs'
|
|
25
|
+
import type { AIProviderConfig, AIProviderName } from '../types/provider'
|
|
26
|
+
import { getPackageRoot, VERSION } from '../utils/version'
|
|
28
27
|
import {
|
|
29
|
-
selectProvider,
|
|
30
|
-
detectProvider,
|
|
31
28
|
detectAllProviders,
|
|
32
29
|
detectAntigravity,
|
|
30
|
+
detectProvider,
|
|
33
31
|
Providers,
|
|
34
|
-
|
|
32
|
+
selectProvider,
|
|
35
33
|
} from './ai-provider'
|
|
36
|
-
import
|
|
34
|
+
import installer from './command-installer'
|
|
35
|
+
import editorsConfig from './editors-config'
|
|
37
36
|
|
|
38
37
|
// Colors
|
|
39
38
|
const GREEN = '\x1b[32m'
|
|
@@ -50,8 +49,8 @@ interface ProviderSetupResult {
|
|
|
50
49
|
}
|
|
51
50
|
|
|
52
51
|
interface SetupResults {
|
|
53
|
-
provider: AIProviderName
|
|
54
|
-
providers: ProviderSetupResult[]
|
|
52
|
+
provider: AIProviderName // Primary provider (for backward compat)
|
|
53
|
+
providers: ProviderSetupResult[] // All installed providers
|
|
55
54
|
cliInstalled: boolean
|
|
56
55
|
commandsAdded: number
|
|
57
56
|
commandsUpdated: number
|
|
@@ -61,7 +60,7 @@ interface SetupResults {
|
|
|
61
60
|
/**
|
|
62
61
|
* Check if an AI CLI is installed
|
|
63
62
|
*/
|
|
64
|
-
async function
|
|
63
|
+
async function _hasAICLI(provider: AIProviderConfig): Promise<boolean> {
|
|
65
64
|
const detection = detectProvider(provider.name)
|
|
66
65
|
return detection.installed
|
|
67
66
|
}
|
|
@@ -70,9 +69,8 @@ async function hasAICLI(provider: AIProviderConfig): Promise<boolean> {
|
|
|
70
69
|
* Install AI CLI for the specified provider
|
|
71
70
|
*/
|
|
72
71
|
async function installAICLI(provider: AIProviderConfig): Promise<boolean> {
|
|
73
|
-
const packageName =
|
|
74
|
-
? '@anthropic-ai/claude-code'
|
|
75
|
-
: '@google/gemini-cli'
|
|
72
|
+
const packageName =
|
|
73
|
+
provider.name === 'claude' ? '@anthropic-ai/claude-code' : '@google/gemini-cli'
|
|
76
74
|
|
|
77
75
|
try {
|
|
78
76
|
console.log(`${YELLOW}📦 ${provider.displayName} not found. Installing...${NC}`)
|
|
@@ -83,7 +81,9 @@ async function installAICLI(provider: AIProviderConfig): Promise<boolean> {
|
|
|
83
81
|
console.log('')
|
|
84
82
|
return true
|
|
85
83
|
} catch (error) {
|
|
86
|
-
console.log(
|
|
84
|
+
console.log(
|
|
85
|
+
`${YELLOW}⚠️ Failed to install ${provider.displayName}: ${(error as Error).message}${NC}`
|
|
86
|
+
)
|
|
87
87
|
console.log(`${DIM}Please install manually: npm install -g ${packageName}${NC}`)
|
|
88
88
|
console.log('')
|
|
89
89
|
return false
|
|
@@ -97,7 +97,7 @@ export async function run(): Promise<SetupResults> {
|
|
|
97
97
|
// Step 0: Detect all available providers
|
|
98
98
|
const detection = detectAllProviders()
|
|
99
99
|
const selection = selectProvider()
|
|
100
|
-
const
|
|
100
|
+
const _primaryProvider = Providers[selection.provider]
|
|
101
101
|
|
|
102
102
|
const results: SetupResults = {
|
|
103
103
|
provider: selection.provider,
|
|
@@ -281,7 +281,7 @@ async function installGeminiGlobalConfig(): Promise<{ success: boolean; action:
|
|
|
281
281
|
|
|
282
282
|
if (!hasMarkers) {
|
|
283
283
|
// No markers - append prjct section at the end
|
|
284
|
-
const updatedContent = existingContent
|
|
284
|
+
const updatedContent = `${existingContent}\n\n${templateContent}`
|
|
285
285
|
fs.writeFileSync(globalConfigPath, updatedContent, 'utf-8')
|
|
286
286
|
return { success: true, action: 'appended' }
|
|
287
287
|
}
|
|
@@ -317,7 +317,10 @@ async function installGeminiGlobalConfig(): Promise<{ success: boolean; action:
|
|
|
317
317
|
* Antigravity uses SKILL.md files in ~/.gemini/antigravity/skills/
|
|
318
318
|
* This is the recommended integration method (not MCP).
|
|
319
319
|
*/
|
|
320
|
-
export async function installAntigravitySkill(): Promise<{
|
|
320
|
+
export async function installAntigravitySkill(): Promise<{
|
|
321
|
+
success: boolean
|
|
322
|
+
action: string | null
|
|
323
|
+
}> {
|
|
321
324
|
try {
|
|
322
325
|
const antigravitySkillsDir = path.join(os.homedir(), '.gemini', 'antigravity', 'skills')
|
|
323
326
|
const prjctSkillDir = path.join(antigravitySkillsDir, 'prjct')
|
|
@@ -408,8 +411,7 @@ export async function installCursorProject(projectRoot: string): Promise<{
|
|
|
408
411
|
// Copy individual command files → .cursor/commands/
|
|
409
412
|
// This enables /sync, /task, /done, /ship, etc. syntax in Cursor
|
|
410
413
|
if (fs.existsSync(cursorCommandsSource)) {
|
|
411
|
-
const commandFiles = fs.readdirSync(cursorCommandsSource)
|
|
412
|
-
.filter(f => f.endsWith('.md'))
|
|
414
|
+
const commandFiles = fs.readdirSync(cursorCommandsSource).filter((f) => f.endsWith('.md'))
|
|
413
415
|
|
|
414
416
|
for (const file of commandFiles) {
|
|
415
417
|
const src = path.join(cursorCommandsSource, file)
|
|
@@ -469,8 +471,8 @@ async function addCursorToGitignore(projectRoot: string): Promise<boolean> {
|
|
|
469
471
|
|
|
470
472
|
// Append to .gitignore
|
|
471
473
|
const newContent = fileExists
|
|
472
|
-
? content.trimEnd()
|
|
473
|
-
: entriesToAdd.join('\n')
|
|
474
|
+
? `${content.trimEnd()}\n\n${entriesToAdd.join('\n')}\n`
|
|
475
|
+
: `${entriesToAdd.join('\n')}\n`
|
|
474
476
|
|
|
475
477
|
fs.writeFileSync(gitignorePath, newContent, 'utf-8')
|
|
476
478
|
return true
|
|
@@ -537,7 +539,12 @@ export async function installWindsurfProject(projectRoot: string): Promise<{
|
|
|
537
539
|
const routerDest = path.join(rulesDir, 'prjct.md')
|
|
538
540
|
|
|
539
541
|
const routerSource = path.join(getPackageRoot(), 'templates', 'windsurf', 'router.md')
|
|
540
|
-
const windsurfWorkflowsSource = path.join(
|
|
542
|
+
const windsurfWorkflowsSource = path.join(
|
|
543
|
+
getPackageRoot(),
|
|
544
|
+
'templates',
|
|
545
|
+
'windsurf',
|
|
546
|
+
'workflows'
|
|
547
|
+
)
|
|
541
548
|
|
|
542
549
|
// Ensure directories exist
|
|
543
550
|
fs.mkdirSync(rulesDir, { recursive: true })
|
|
@@ -552,8 +559,7 @@ export async function installWindsurfProject(projectRoot: string): Promise<{
|
|
|
552
559
|
// Copy individual workflow files → .windsurf/workflows/
|
|
553
560
|
// This enables /sync, /task, /done, /ship, etc. syntax in Windsurf
|
|
554
561
|
if (fs.existsSync(windsurfWorkflowsSource)) {
|
|
555
|
-
const workflowFiles = fs.readdirSync(windsurfWorkflowsSource)
|
|
556
|
-
.filter(f => f.endsWith('.md'))
|
|
562
|
+
const workflowFiles = fs.readdirSync(windsurfWorkflowsSource).filter((f) => f.endsWith('.md'))
|
|
557
563
|
|
|
558
564
|
for (const file of workflowFiles) {
|
|
559
565
|
const src = path.join(windsurfWorkflowsSource, file)
|
|
@@ -613,8 +619,8 @@ async function addWindsurfToGitignore(projectRoot: string): Promise<boolean> {
|
|
|
613
619
|
|
|
614
620
|
// Append to .gitignore
|
|
615
621
|
const newContent = fileExists
|
|
616
|
-
? content.trimEnd()
|
|
617
|
-
: entriesToAdd.join('\n')
|
|
622
|
+
? `${content.trimEnd()}\n\n${entriesToAdd.join('\n')}\n`
|
|
623
|
+
: `${entriesToAdd.join('\n')}\n`
|
|
618
624
|
|
|
619
625
|
fs.writeFileSync(gitignorePath, newContent, 'utf-8')
|
|
620
626
|
return true
|
|
@@ -654,9 +660,10 @@ async function migrateProjectsCliVersion(): Promise<void> {
|
|
|
654
660
|
return
|
|
655
661
|
}
|
|
656
662
|
|
|
657
|
-
const projectDirs = fs
|
|
658
|
-
.
|
|
659
|
-
.
|
|
663
|
+
const projectDirs = fs
|
|
664
|
+
.readdirSync(projectsDir, { withFileTypes: true })
|
|
665
|
+
.filter((dirent) => dirent.isDirectory())
|
|
666
|
+
.map((dirent) => dirent.name)
|
|
660
667
|
|
|
661
668
|
let migrated = 0
|
|
662
669
|
|
|
@@ -795,10 +802,7 @@ async function installStatusLine(): Promise<void> {
|
|
|
795
802
|
if (fs.existsSync(sourceScript)) {
|
|
796
803
|
// Copy script and update version
|
|
797
804
|
let scriptContent = fs.readFileSync(sourceScript, 'utf8')
|
|
798
|
-
scriptContent = scriptContent.replace(
|
|
799
|
-
/CLI_VERSION="[^"]*"/,
|
|
800
|
-
`CLI_VERSION="${VERSION}"`
|
|
801
|
-
)
|
|
805
|
+
scriptContent = scriptContent.replace(/CLI_VERSION="[^"]*"/, `CLI_VERSION="${VERSION}"`)
|
|
802
806
|
fs.writeFileSync(prjctStatusLinePath, scriptContent, { mode: 0o755 })
|
|
803
807
|
|
|
804
808
|
// Copy lib/ modules
|
|
@@ -910,7 +914,7 @@ function ensureStatusLineSymlink(linkPath: string, targetPath: string): void {
|
|
|
910
914
|
}
|
|
911
915
|
// Create symlink
|
|
912
916
|
fs.symlinkSync(targetPath, linkPath)
|
|
913
|
-
} catch (
|
|
917
|
+
} catch (_error) {
|
|
914
918
|
// If symlink fails (e.g., Windows, permission issues), try copy instead
|
|
915
919
|
try {
|
|
916
920
|
if (fs.existsSync(targetPath)) {
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
* @version 0.5.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
7
|
+
import fs from 'node:fs'
|
|
8
|
+
import https from 'node:https'
|
|
9
|
+
import os from 'node:os'
|
|
10
|
+
import path from 'node:path'
|
|
11
11
|
import chalk from 'chalk'
|
|
12
12
|
|
|
13
13
|
interface UpdateCache {
|
|
@@ -162,7 +162,7 @@ class UpdateChecker {
|
|
|
162
162
|
const cache = this.readCache()
|
|
163
163
|
const now = Date.now()
|
|
164
164
|
|
|
165
|
-
if (cache
|
|
165
|
+
if (cache?.lastCheck && now - cache.lastCheck < this.checkInterval) {
|
|
166
166
|
// Cache is still valid
|
|
167
167
|
if (cache.latestVersion && this.compareVersions(cache.latestVersion, currentVersion) > 0) {
|
|
168
168
|
return {
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* The actual AI call happens through Claude Code's execution context.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type {
|
|
9
|
+
import type { EnrichedIssue, Issue } from './types'
|
|
10
10
|
|
|
11
11
|
// =============================================================================
|
|
12
12
|
// Enrichment Templates
|
|
@@ -81,13 +81,9 @@ export function generateLLMPrompt(
|
|
|
81
81
|
projectContext: ProjectContext,
|
|
82
82
|
enrichment: EnrichmentResult
|
|
83
83
|
): string {
|
|
84
|
-
const filesList = enrichment.affectedFiles
|
|
85
|
-
.map(f => `- ${f}`)
|
|
86
|
-
.join('\n')
|
|
84
|
+
const filesList = enrichment.affectedFiles.map((f) => `- ${f}`).join('\n')
|
|
87
85
|
|
|
88
|
-
const acList = enrichment.acceptanceCriteria
|
|
89
|
-
.map(ac => `- [ ] ${ac}`)
|
|
90
|
-
.join('\n')
|
|
86
|
+
const acList = enrichment.acceptanceCriteria.map((ac) => `- [ ] ${ac}`).join('\n')
|
|
91
87
|
|
|
92
88
|
return `## Task: ${issue.title}
|
|
93
89
|
|
|
@@ -150,10 +146,7 @@ export interface EnrichmentResult {
|
|
|
150
146
|
/**
|
|
151
147
|
* Build enriched issue from result
|
|
152
148
|
*/
|
|
153
|
-
export function buildEnrichedIssue(
|
|
154
|
-
issue: Issue,
|
|
155
|
-
enrichment: EnrichmentResult
|
|
156
|
-
): EnrichedIssue {
|
|
149
|
+
export function buildEnrichedIssue(issue: Issue, enrichment: EnrichmentResult): EnrichedIssue {
|
|
157
150
|
return {
|
|
158
151
|
...issue,
|
|
159
152
|
enrichment: {
|
|
@@ -213,8 +206,8 @@ export function formatEnrichmentAsMarkdown(enrichment: EnrichmentResult): string
|
|
|
213
206
|
export function parseEnrichmentResponse(response: string): EnrichmentResult | null {
|
|
214
207
|
try {
|
|
215
208
|
// Extract JSON from response (may be wrapped in markdown code block)
|
|
216
|
-
const jsonMatch =
|
|
217
|
-
|
|
209
|
+
const jsonMatch =
|
|
210
|
+
response.match(/```(?:json)?\s*([\s\S]*?)```/) || response.match(/\{[\s\S]*\}/)
|
|
218
211
|
|
|
219
212
|
if (!jsonMatch) {
|
|
220
213
|
console.error('[enricher] No JSON found in response')
|
|
@@ -232,12 +225,8 @@ export function parseEnrichmentResponse(response: string): EnrichmentResult | nu
|
|
|
232
225
|
|
|
233
226
|
return {
|
|
234
227
|
description: parsed.description,
|
|
235
|
-
acceptanceCriteria: Array.isArray(parsed.acceptanceCriteria)
|
|
236
|
-
|
|
237
|
-
: [],
|
|
238
|
-
affectedFiles: Array.isArray(parsed.affectedFiles)
|
|
239
|
-
? parsed.affectedFiles
|
|
240
|
-
: [],
|
|
228
|
+
acceptanceCriteria: Array.isArray(parsed.acceptanceCriteria) ? parsed.acceptanceCriteria : [],
|
|
229
|
+
affectedFiles: Array.isArray(parsed.affectedFiles) ? parsed.affectedFiles : [],
|
|
241
230
|
technicalNotes: parsed.technicalNotes || '',
|
|
242
231
|
estimatedComplexity: parsed.estimatedComplexity,
|
|
243
232
|
suggestedApproach: parsed.suggestedApproach,
|
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
* Unified interface for Linear, Jira, Monday, and other issue trackers.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
export * from './types'
|
|
7
6
|
export * from './enricher'
|
|
8
|
-
export {
|
|
7
|
+
export { IssueTrackerManager, issueTrackerManager } from './manager'
|
|
8
|
+
export * from './types'
|
|
@@ -3,26 +3,26 @@
|
|
|
3
3
|
* Orchestrates multiple issue tracker providers and enrichment.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
IssueTrackerConfig,
|
|
9
|
-
Issue,
|
|
10
|
-
EnrichedIssue,
|
|
11
|
-
SyncResult,
|
|
12
|
-
FetchOptions,
|
|
13
|
-
CreateIssueInput,
|
|
14
|
-
IssueProvider,
|
|
15
|
-
} from './types'
|
|
6
|
+
import { jiraProvider } from '../jira/client'
|
|
7
|
+
import { linearProvider } from '../linear/client'
|
|
16
8
|
import {
|
|
17
|
-
generateEnrichmentPrompt,
|
|
18
9
|
buildEnrichedIssue,
|
|
10
|
+
type EnrichmentResult,
|
|
19
11
|
formatEnrichmentAsMarkdown,
|
|
12
|
+
generateEnrichmentPrompt,
|
|
20
13
|
generateQuickEnrichment,
|
|
21
14
|
type ProjectContext,
|
|
22
|
-
type EnrichmentResult,
|
|
23
15
|
} from './enricher'
|
|
24
|
-
import {
|
|
25
|
-
|
|
16
|
+
import type {
|
|
17
|
+
CreateIssueInput,
|
|
18
|
+
EnrichedIssue,
|
|
19
|
+
FetchOptions,
|
|
20
|
+
Issue,
|
|
21
|
+
IssueProvider,
|
|
22
|
+
IssueTrackerConfig,
|
|
23
|
+
IssueTrackerProvider,
|
|
24
|
+
SyncResult,
|
|
25
|
+
} from './types'
|
|
26
26
|
|
|
27
27
|
// =============================================================================
|
|
28
28
|
// Manager Class
|
|
@@ -232,7 +232,7 @@ export class IssueTrackerManager {
|
|
|
232
232
|
for (const issue of issues) {
|
|
233
233
|
try {
|
|
234
234
|
// Generate prompt (actual AI execution is external)
|
|
235
|
-
const
|
|
235
|
+
const _prompt = this.getEnrichmentPrompt(issue, projectContext)
|
|
236
236
|
console.log(`[issue-tracker] Enrichment prompt ready for ${issue.externalId}`)
|
|
237
237
|
|
|
238
238
|
// For now, use quick enrichment as placeholder
|
|
@@ -95,28 +95,11 @@ export interface UpdateIssueInput {
|
|
|
95
95
|
|
|
96
96
|
export type IssueProvider = 'linear' | 'jira' | 'monday' | 'github' | 'asana' | 'none'
|
|
97
97
|
|
|
98
|
-
export type IssueStatus =
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
| 'done'
|
|
104
|
-
| 'cancelled'
|
|
105
|
-
|
|
106
|
-
export type IssuePriority =
|
|
107
|
-
| 'none'
|
|
108
|
-
| 'urgent'
|
|
109
|
-
| 'high'
|
|
110
|
-
| 'medium'
|
|
111
|
-
| 'low'
|
|
112
|
-
|
|
113
|
-
export type IssueType =
|
|
114
|
-
| 'feature'
|
|
115
|
-
| 'bug'
|
|
116
|
-
| 'improvement'
|
|
117
|
-
| 'task'
|
|
118
|
-
| 'chore'
|
|
119
|
-
| 'epic'
|
|
98
|
+
export type IssueStatus = 'backlog' | 'todo' | 'in_progress' | 'in_review' | 'done' | 'cancelled'
|
|
99
|
+
|
|
100
|
+
export type IssuePriority = 'none' | 'urgent' | 'high' | 'medium' | 'low'
|
|
101
|
+
|
|
102
|
+
export type IssueType = 'feature' | 'bug' | 'improvement' | 'task' | 'chore' | 'epic'
|
|
120
103
|
|
|
121
104
|
// =============================================================================
|
|
122
105
|
// Provider Interface
|
|
@@ -15,15 +15,15 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import type {
|
|
18
|
-
IssueTrackerProvider,
|
|
19
|
-
Issue,
|
|
20
18
|
CreateIssueInput,
|
|
21
|
-
UpdateIssueInput,
|
|
22
19
|
FetchOptions,
|
|
23
|
-
|
|
24
|
-
IssueStatus,
|
|
20
|
+
Issue,
|
|
25
21
|
IssuePriority,
|
|
22
|
+
IssueStatus,
|
|
23
|
+
IssueTrackerProvider,
|
|
26
24
|
IssueType,
|
|
25
|
+
JiraConfig,
|
|
26
|
+
UpdateIssueInput,
|
|
27
27
|
} from '../issue-tracker/types'
|
|
28
28
|
|
|
29
29
|
// =============================================================================
|
|
@@ -36,13 +36,16 @@ interface JiraIssue {
|
|
|
36
36
|
self: string
|
|
37
37
|
fields: {
|
|
38
38
|
summary: string
|
|
39
|
-
description?:
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
39
|
+
description?:
|
|
40
|
+
| {
|
|
41
|
+
type: string
|
|
42
|
+
content: Array<{
|
|
43
|
+
type: string
|
|
44
|
+
content?: Array<{ type: string; text?: string }>
|
|
45
|
+
}>
|
|
46
|
+
}
|
|
47
|
+
| string
|
|
48
|
+
| null
|
|
46
49
|
status: {
|
|
47
50
|
id: string
|
|
48
51
|
name: string
|
|
@@ -114,7 +117,7 @@ const JIRA_STATUS_CATEGORY_MAP: Record<string, IssueStatus> = {
|
|
|
114
117
|
const JIRA_STATUS_NAME_MAP: Record<string, IssueStatus> = {
|
|
115
118
|
// Backlog states
|
|
116
119
|
backlog: 'backlog',
|
|
117
|
-
|
|
120
|
+
open: 'backlog',
|
|
118
121
|
'to do': 'todo',
|
|
119
122
|
todo: 'todo',
|
|
120
123
|
new: 'todo',
|
|
@@ -124,7 +127,7 @@ const JIRA_STATUS_NAME_MAP: Record<string, IssueStatus> = {
|
|
|
124
127
|
'in development': 'in_progress',
|
|
125
128
|
'in review': 'in_review',
|
|
126
129
|
'code review': 'in_review',
|
|
127
|
-
|
|
130
|
+
review: 'in_review',
|
|
128
131
|
|
|
129
132
|
// Done states
|
|
130
133
|
done: 'done',
|
|
@@ -241,13 +244,19 @@ export class JiraProvider implements IssueTrackerProvider {
|
|
|
241
244
|
|
|
242
245
|
// Verify connection by fetching current user
|
|
243
246
|
try {
|
|
244
|
-
const response = await this.request<{
|
|
247
|
+
const response = await this.request<{
|
|
248
|
+
accountId: string
|
|
249
|
+
displayName: string
|
|
250
|
+
emailAddress?: string
|
|
251
|
+
}>('/rest/api/3/myself')
|
|
245
252
|
this.currentUser = {
|
|
246
253
|
accountId: response.accountId,
|
|
247
254
|
displayName: response.displayName,
|
|
248
255
|
email: response.emailAddress,
|
|
249
256
|
}
|
|
250
|
-
console.log(
|
|
257
|
+
console.log(
|
|
258
|
+
`[jira] Connected as ${this.currentUser.displayName} (${this.currentUser.email || 'no email'})`
|
|
259
|
+
)
|
|
251
260
|
} catch (error) {
|
|
252
261
|
this.baseUrl = ''
|
|
253
262
|
this.auth = ''
|
|
@@ -360,7 +369,7 @@ export class JiraProvider implements IssueTrackerProvider {
|
|
|
360
369
|
|
|
361
370
|
// Add optional fields
|
|
362
371
|
if (input.description) {
|
|
363
|
-
(payload.fields as Record<string, unknown>).description = {
|
|
372
|
+
;(payload.fields as Record<string, unknown>).description = {
|
|
364
373
|
type: 'doc',
|
|
365
374
|
version: 1,
|
|
366
375
|
content: [
|
|
@@ -373,28 +382,25 @@ export class JiraProvider implements IssueTrackerProvider {
|
|
|
373
382
|
}
|
|
374
383
|
|
|
375
384
|
if (input.priority) {
|
|
376
|
-
(payload.fields as Record<string, unknown>).priority = {
|
|
385
|
+
;(payload.fields as Record<string, unknown>).priority = {
|
|
377
386
|
name: PRIORITY_TO_JIRA[input.priority],
|
|
378
387
|
}
|
|
379
388
|
}
|
|
380
389
|
|
|
381
390
|
if (input.labels?.length) {
|
|
382
|
-
(payload.fields as Record<string, unknown>).labels = input.labels
|
|
391
|
+
;(payload.fields as Record<string, unknown>).labels = input.labels
|
|
383
392
|
}
|
|
384
393
|
|
|
385
394
|
if (input.assigneeId) {
|
|
386
|
-
(payload.fields as Record<string, unknown>).assignee = {
|
|
395
|
+
;(payload.fields as Record<string, unknown>).assignee = {
|
|
387
396
|
accountId: input.assigneeId,
|
|
388
397
|
}
|
|
389
398
|
}
|
|
390
399
|
|
|
391
|
-
const created = await this.request<{ id: string; key: string }>(
|
|
392
|
-
'
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
body: JSON.stringify(payload),
|
|
396
|
-
}
|
|
397
|
-
)
|
|
400
|
+
const created = await this.request<{ id: string; key: string }>('/rest/api/3/issue', {
|
|
401
|
+
method: 'POST',
|
|
402
|
+
body: JSON.stringify(payload),
|
|
403
|
+
})
|
|
398
404
|
|
|
399
405
|
// Fetch the full issue
|
|
400
406
|
const issue = await this.fetchIssue(created.key)
|
|
@@ -414,7 +420,7 @@ export class JiraProvider implements IssueTrackerProvider {
|
|
|
414
420
|
const payload: Record<string, unknown> = { fields: {} }
|
|
415
421
|
|
|
416
422
|
if (input.description) {
|
|
417
|
-
(payload.fields as Record<string, unknown>).description = {
|
|
423
|
+
;(payload.fields as Record<string, unknown>).description = {
|
|
418
424
|
type: 'doc',
|
|
419
425
|
version: 1,
|
|
420
426
|
content: this.markdownToADF(input.description),
|
|
@@ -442,9 +448,9 @@ export class JiraProvider implements IssueTrackerProvider {
|
|
|
442
448
|
if (!this.isConfigured()) throw new Error('JIRA not initialized')
|
|
443
449
|
|
|
444
450
|
// Get available transitions
|
|
445
|
-
const transitions = await this.request<{
|
|
446
|
-
|
|
447
|
-
)
|
|
451
|
+
const transitions = await this.request<{
|
|
452
|
+
transitions: Array<{ id: string; name: string; to: { statusCategory: { key: string } } }>
|
|
453
|
+
}>(`/rest/api/3/issue/${id}/transitions`)
|
|
448
454
|
|
|
449
455
|
// Find transition to "in progress" state
|
|
450
456
|
const inProgressTransition = transitions.transitions.find(
|
|
@@ -469,9 +475,9 @@ export class JiraProvider implements IssueTrackerProvider {
|
|
|
469
475
|
if (!this.isConfigured()) throw new Error('JIRA not initialized')
|
|
470
476
|
|
|
471
477
|
// Get available transitions
|
|
472
|
-
const transitions = await this.request<{
|
|
473
|
-
|
|
474
|
-
)
|
|
478
|
+
const transitions = await this.request<{
|
|
479
|
+
transitions: Array<{ id: string; name: string; to: { statusCategory: { key: string } } }>
|
|
480
|
+
}>(`/rest/api/3/issue/${id}/transitions`)
|
|
475
481
|
|
|
476
482
|
// Find transition to "done" state
|
|
477
483
|
const doneTransition = transitions.transitions.find(
|
|
@@ -556,9 +562,7 @@ export class JiraProvider implements IssueTrackerProvider {
|
|
|
556
562
|
|
|
557
563
|
// Try exact status name match first, then category
|
|
558
564
|
const status: IssueStatus =
|
|
559
|
-
JIRA_STATUS_NAME_MAP[statusName] ||
|
|
560
|
-
JIRA_STATUS_CATEGORY_MAP[statusCategory] ||
|
|
561
|
-
'backlog'
|
|
565
|
+
JIRA_STATUS_NAME_MAP[statusName] || JIRA_STATUS_CATEGORY_MAP[statusCategory] || 'backlog'
|
|
562
566
|
|
|
563
567
|
const priorityName = jiraIssue.fields.priority?.name?.toLowerCase() || 'medium'
|
|
564
568
|
const priority: IssuePriority = JIRA_PRIORITY_MAP[priorityName] || 'medium'
|
|
@@ -599,9 +603,7 @@ export class JiraProvider implements IssueTrackerProvider {
|
|
|
599
603
|
/**
|
|
600
604
|
* Extract plain text from JIRA description (ADF or string)
|
|
601
605
|
*/
|
|
602
|
-
private extractDescription(
|
|
603
|
-
description: JiraIssue['fields']['description']
|
|
604
|
-
): string | undefined {
|
|
606
|
+
private extractDescription(description: JiraIssue['fields']['description']): string | undefined {
|
|
605
607
|
if (!description) return undefined
|
|
606
608
|
|
|
607
609
|
// Handle string descriptions (older JIRA versions)
|
|
@@ -661,34 +663,42 @@ export class JiraProvider implements IssueTrackerProvider {
|
|
|
661
663
|
content.push({
|
|
662
664
|
type: 'taskList',
|
|
663
665
|
attrs: { localId: crypto.randomUUID() },
|
|
664
|
-
content: [
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
666
|
+
content: [
|
|
667
|
+
{
|
|
668
|
+
type: 'taskItem',
|
|
669
|
+
attrs: { localId: crypto.randomUUID(), state: 'TODO' },
|
|
670
|
+
content: [{ type: 'text', text: line.slice(6) }],
|
|
671
|
+
},
|
|
672
|
+
],
|
|
669
673
|
})
|
|
670
674
|
} else if (line.startsWith('- [x] ')) {
|
|
671
675
|
// Checkbox checked
|
|
672
676
|
content.push({
|
|
673
677
|
type: 'taskList',
|
|
674
678
|
attrs: { localId: crypto.randomUUID() },
|
|
675
|
-
content: [
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
679
|
+
content: [
|
|
680
|
+
{
|
|
681
|
+
type: 'taskItem',
|
|
682
|
+
attrs: { localId: crypto.randomUUID(), state: 'DONE' },
|
|
683
|
+
content: [{ type: 'text', text: line.slice(6) }],
|
|
684
|
+
},
|
|
685
|
+
],
|
|
680
686
|
})
|
|
681
687
|
} else if (line.startsWith('- ')) {
|
|
682
688
|
// Bullet point
|
|
683
689
|
content.push({
|
|
684
690
|
type: 'bulletList',
|
|
685
|
-
content: [
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
691
|
+
content: [
|
|
692
|
+
{
|
|
693
|
+
type: 'listItem',
|
|
694
|
+
content: [
|
|
695
|
+
{
|
|
696
|
+
type: 'paragraph',
|
|
697
|
+
content: [{ type: 'text', text: line.slice(2) }],
|
|
698
|
+
},
|
|
699
|
+
],
|
|
700
|
+
},
|
|
701
|
+
],
|
|
692
702
|
})
|
|
693
703
|
} else if (line.trim()) {
|
|
694
704
|
// Regular paragraph
|
|
@@ -741,8 +751,6 @@ export class JiraProvider implements IssueTrackerProvider {
|
|
|
741
751
|
return 'Improvement'
|
|
742
752
|
case 'epic':
|
|
743
753
|
return 'Epic'
|
|
744
|
-
case 'chore':
|
|
745
|
-
case 'task':
|
|
746
754
|
default:
|
|
747
755
|
return 'Task'
|
|
748
756
|
}
|
|
@@ -9,33 +9,30 @@
|
|
|
9
9
|
* - JIRA_API_TOKEN: API token from https://id.atlassian.com/manage-profile/security/api-tokens
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
// REST API client
|
|
13
|
-
export { JiraProvider, jiraProvider, type JiraAuthMode } from './client'
|
|
14
|
-
|
|
15
|
-
// Service layer with caching (preferred API)
|
|
16
|
-
export { JiraService, jiraService } from './service'
|
|
17
|
-
|
|
18
12
|
// Cache utilities
|
|
19
13
|
export {
|
|
20
|
-
issueCache,
|
|
21
14
|
assignedIssuesCache,
|
|
22
|
-
projectsCache,
|
|
23
15
|
clearJiraCache,
|
|
24
16
|
getJiraCacheStats,
|
|
17
|
+
issueCache,
|
|
18
|
+
projectsCache,
|
|
25
19
|
} from './cache'
|
|
26
|
-
|
|
20
|
+
// REST API client
|
|
21
|
+
export { type JiraAuthMode, JiraProvider, jiraProvider } from './client'
|
|
27
22
|
// MCP adapter (deprecated - will be removed)
|
|
28
23
|
export {
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
createCreateIssueInstruction,
|
|
25
|
+
createGetIssueInstruction,
|
|
31
26
|
// MCP instruction generators
|
|
32
27
|
createSearchInstruction,
|
|
33
|
-
createGetIssueInstruction,
|
|
34
28
|
createTransitionInstruction,
|
|
35
29
|
createUpdateInstruction,
|
|
36
|
-
|
|
30
|
+
getMCPSetupInstructions,
|
|
37
31
|
// Utilities
|
|
38
32
|
isMCPAvailable,
|
|
39
|
-
|
|
33
|
+
JiraMCPAdapter,
|
|
34
|
+
jiraMCPAdapter,
|
|
40
35
|
type MCPInstruction,
|
|
41
36
|
} from './mcp-adapter'
|
|
37
|
+
// Service layer with caching (preferred API)
|
|
38
|
+
export { JiraService, jiraService } from './service'
|