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.
Files changed (207) hide show
  1. package/CHANGELOG.md +114 -0
  2. package/bin/prjct.ts +131 -10
  3. package/core/__tests__/agentic/memory-system.test.ts +39 -26
  4. package/core/__tests__/agentic/plan-mode.test.ts +64 -46
  5. package/core/__tests__/agentic/prompt-builder.test.ts +14 -14
  6. package/core/__tests__/services/project-index.test.ts +353 -0
  7. package/core/__tests__/types/fs.test.ts +3 -3
  8. package/core/__tests__/utils/date-helper.test.ts +10 -10
  9. package/core/__tests__/utils/output.test.ts +9 -6
  10. package/core/__tests__/utils/project-commands.test.ts +5 -6
  11. package/core/agentic/agent-router.ts +9 -10
  12. package/core/agentic/chain-of-thought.ts +16 -4
  13. package/core/agentic/command-executor.ts +66 -40
  14. package/core/agentic/context-builder.ts +8 -5
  15. package/core/agentic/ground-truth.ts +15 -9
  16. package/core/agentic/index.ts +145 -152
  17. package/core/agentic/loop-detector.ts +40 -11
  18. package/core/agentic/memory-system.ts +98 -35
  19. package/core/agentic/orchestrator-executor.ts +135 -71
  20. package/core/agentic/plan-mode.ts +46 -16
  21. package/core/agentic/prompt-builder.ts +108 -42
  22. package/core/agentic/services.ts +10 -9
  23. package/core/agentic/skill-loader.ts +9 -15
  24. package/core/agentic/smart-context.ts +129 -79
  25. package/core/agentic/template-executor.ts +13 -12
  26. package/core/agentic/template-loader.ts +7 -4
  27. package/core/agentic/tool-registry.ts +16 -13
  28. package/core/agents/index.ts +1 -1
  29. package/core/agents/performance.ts +10 -27
  30. package/core/ai-tools/formatters.ts +8 -6
  31. package/core/ai-tools/generator.ts +4 -4
  32. package/core/ai-tools/index.ts +1 -1
  33. package/core/ai-tools/registry.ts +21 -11
  34. package/core/bus/bus.ts +23 -16
  35. package/core/bus/index.ts +2 -2
  36. package/core/cli/linear.ts +3 -5
  37. package/core/cli/start.ts +28 -25
  38. package/core/commands/analysis.ts +287 -29
  39. package/core/commands/analytics.ts +52 -44
  40. package/core/commands/base.ts +15 -13
  41. package/core/commands/cleanup.ts +6 -13
  42. package/core/commands/command-data.ts +49 -8
  43. package/core/commands/commands.ts +60 -23
  44. package/core/commands/context.ts +4 -4
  45. package/core/commands/design.ts +3 -10
  46. package/core/commands/index.ts +5 -8
  47. package/core/commands/maintenance.ts +7 -4
  48. package/core/commands/planning.ts +179 -56
  49. package/core/commands/register.ts +14 -9
  50. package/core/commands/registry.ts +15 -14
  51. package/core/commands/setup.ts +26 -14
  52. package/core/commands/shipping.ts +11 -16
  53. package/core/commands/snapshots.ts +16 -32
  54. package/core/commands/uninstall.ts +541 -0
  55. package/core/commands/workflow.ts +24 -28
  56. package/core/constants/index.ts +10 -22
  57. package/core/context/generator.ts +82 -33
  58. package/core/context-tools/files-tool.ts +583 -0
  59. package/core/context-tools/imports-tool.ts +403 -0
  60. package/core/context-tools/index.ts +433 -0
  61. package/core/context-tools/recent-tool.ts +307 -0
  62. package/core/context-tools/signatures-tool.ts +501 -0
  63. package/core/context-tools/summary-tool.ts +307 -0
  64. package/core/context-tools/token-counter.ts +284 -0
  65. package/core/context-tools/types.ts +253 -0
  66. package/core/domain/agent-generator.ts +7 -5
  67. package/core/domain/agent-loader.ts +2 -2
  68. package/core/domain/analyzer.ts +19 -16
  69. package/core/domain/architecture-generator.ts +6 -3
  70. package/core/domain/context-estimator.ts +3 -4
  71. package/core/domain/snapshot-manager.ts +25 -22
  72. package/core/domain/task-stack.ts +24 -14
  73. package/core/errors.ts +1 -1
  74. package/core/events/events.ts +2 -4
  75. package/core/events/index.ts +1 -2
  76. package/core/index.ts +28 -12
  77. package/core/infrastructure/agent-detector.ts +3 -3
  78. package/core/infrastructure/ai-provider.ts +23 -20
  79. package/core/infrastructure/author-detector.ts +16 -10
  80. package/core/infrastructure/capability-installer.ts +2 -2
  81. package/core/infrastructure/claude-agent.ts +6 -6
  82. package/core/infrastructure/command-installer.ts +22 -17
  83. package/core/infrastructure/config-manager.ts +18 -14
  84. package/core/infrastructure/editors-config.ts +8 -4
  85. package/core/infrastructure/path-manager.ts +8 -6
  86. package/core/infrastructure/permission-manager.ts +20 -17
  87. package/core/infrastructure/setup.ts +42 -38
  88. package/core/infrastructure/update-checker.ts +5 -5
  89. package/core/integrations/issue-tracker/enricher.ts +8 -19
  90. package/core/integrations/issue-tracker/index.ts +2 -2
  91. package/core/integrations/issue-tracker/manager.ts +15 -15
  92. package/core/integrations/issue-tracker/types.ts +5 -22
  93. package/core/integrations/jira/client.ts +67 -59
  94. package/core/integrations/jira/index.ts +11 -14
  95. package/core/integrations/jira/mcp-adapter.ts +5 -10
  96. package/core/integrations/jira/service.ts +10 -10
  97. package/core/integrations/linear/client.ts +27 -18
  98. package/core/integrations/linear/index.ts +9 -12
  99. package/core/integrations/linear/service.ts +11 -11
  100. package/core/integrations/linear/sync.ts +8 -8
  101. package/core/outcomes/analyzer.ts +5 -18
  102. package/core/outcomes/index.ts +2 -2
  103. package/core/outcomes/recorder.ts +3 -3
  104. package/core/plugin/builtin/webhook.ts +19 -15
  105. package/core/plugin/hooks.ts +29 -21
  106. package/core/plugin/index.ts +7 -7
  107. package/core/plugin/loader.ts +19 -19
  108. package/core/plugin/registry.ts +12 -23
  109. package/core/schemas/agents.ts +1 -1
  110. package/core/schemas/analysis.ts +1 -1
  111. package/core/schemas/enriched-task.ts +62 -49
  112. package/core/schemas/ideas.ts +13 -13
  113. package/core/schemas/index.ts +17 -27
  114. package/core/schemas/issues.ts +40 -25
  115. package/core/schemas/metrics.ts +143 -0
  116. package/core/schemas/outcomes.ts +70 -62
  117. package/core/schemas/permissions.ts +15 -12
  118. package/core/schemas/prd.ts +27 -14
  119. package/core/schemas/project.ts +3 -3
  120. package/core/schemas/roadmap.ts +47 -34
  121. package/core/schemas/schemas.ts +3 -4
  122. package/core/schemas/shipped.ts +3 -3
  123. package/core/schemas/state.ts +43 -29
  124. package/core/server/index.ts +5 -6
  125. package/core/server/routes-extended.ts +68 -72
  126. package/core/server/routes.ts +3 -3
  127. package/core/server/server.ts +31 -26
  128. package/core/services/agent-generator.ts +237 -0
  129. package/core/services/agent-service.ts +2 -2
  130. package/core/services/breakdown-service.ts +2 -4
  131. package/core/services/context-generator.ts +299 -0
  132. package/core/services/context-selector.ts +420 -0
  133. package/core/services/doctor-service.ts +426 -0
  134. package/core/services/file-categorizer.ts +448 -0
  135. package/core/services/file-scorer.ts +270 -0
  136. package/core/services/git-analyzer.ts +267 -0
  137. package/core/services/index.ts +27 -10
  138. package/core/services/memory-service.ts +3 -4
  139. package/core/services/project-index.ts +911 -0
  140. package/core/services/project-service.ts +4 -4
  141. package/core/services/skill-installer.ts +14 -17
  142. package/core/services/skill-lock.ts +3 -3
  143. package/core/services/skill-service.ts +12 -6
  144. package/core/services/stack-detector.ts +245 -0
  145. package/core/services/sync-service.ts +170 -329
  146. package/core/services/watch-service.ts +294 -0
  147. package/core/session/compaction.ts +23 -31
  148. package/core/session/index.ts +11 -5
  149. package/core/session/log-migration.ts +3 -3
  150. package/core/session/metrics.ts +19 -14
  151. package/core/session/session-log-manager.ts +12 -17
  152. package/core/session/task-session-manager.ts +25 -25
  153. package/core/session/utils.ts +1 -1
  154. package/core/storage/ideas-storage.ts +41 -57
  155. package/core/storage/index-storage.ts +514 -0
  156. package/core/storage/index.ts +41 -13
  157. package/core/storage/metrics-storage.ts +320 -0
  158. package/core/storage/queue-storage.ts +35 -45
  159. package/core/storage/shipped-storage.ts +17 -20
  160. package/core/storage/state-storage.ts +50 -30
  161. package/core/storage/storage-manager.ts +6 -6
  162. package/core/storage/storage.ts +18 -15
  163. package/core/sync/auth-config.ts +3 -3
  164. package/core/sync/index.ts +13 -19
  165. package/core/sync/oauth-handler.ts +3 -3
  166. package/core/sync/sync-client.ts +4 -9
  167. package/core/sync/sync-manager.ts +12 -14
  168. package/core/types/commands.ts +42 -7
  169. package/core/types/index.ts +284 -302
  170. package/core/types/integrations.ts +3 -3
  171. package/core/types/storage.ts +49 -0
  172. package/core/types/utils.ts +3 -3
  173. package/core/utils/agent-stream.ts +3 -1
  174. package/core/utils/animations.ts +14 -11
  175. package/core/utils/branding.ts +7 -7
  176. package/core/utils/cache.ts +1 -3
  177. package/core/utils/collection-filters.ts +3 -15
  178. package/core/utils/date-helper.ts +2 -7
  179. package/core/utils/file-helper.ts +13 -8
  180. package/core/utils/jsonl-helper.ts +13 -10
  181. package/core/utils/keychain.ts +4 -8
  182. package/core/utils/logger.ts +1 -1
  183. package/core/utils/next-steps.ts +3 -3
  184. package/core/utils/output.ts +58 -11
  185. package/core/utils/project-commands.ts +6 -6
  186. package/core/utils/project-credentials.ts +5 -12
  187. package/core/utils/runtime.ts +2 -2
  188. package/core/utils/session-helper.ts +3 -4
  189. package/core/utils/version.ts +3 -3
  190. package/core/wizard/index.ts +13 -0
  191. package/core/wizard/onboarding.ts +633 -0
  192. package/core/workflow/state-machine.ts +7 -7
  193. package/dist/bin/prjct.mjs +18907 -13189
  194. package/dist/core/infrastructure/command-installer.js +96 -111
  195. package/dist/core/infrastructure/editors-config.js +6 -6
  196. package/dist/core/infrastructure/setup.js +256 -257
  197. package/dist/core/utils/version.js +9 -9
  198. package/package.json +11 -12
  199. package/scripts/build.js +3 -3
  200. package/scripts/postinstall.js +2 -2
  201. package/templates/mcp-config.json +6 -1
  202. package/templates/permissions/permissive.jsonc +1 -1
  203. package/templates/permissions/strict.jsonc +5 -9
  204. package/templates/global/docs/agents.md +0 -88
  205. package/templates/global/docs/architecture.md +0 -103
  206. package/templates/global/docs/commands.md +0 -96
  207. 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 path from 'path'
23
- import os from 'os'
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
- AntigravityProvider,
32
+ selectProvider,
35
33
  } from './ai-provider'
36
- import type { AIProviderName, AIProviderConfig } from '../types/provider'
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 // Primary provider (for backward compat)
54
- providers: ProviderSetupResult[] // All installed providers
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 hasAICLI(provider: AIProviderConfig): Promise<boolean> {
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 = provider.name === 'claude'
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(`${YELLOW}⚠️ Failed to install ${provider.displayName}: ${(error as Error).message}${NC}`)
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 primaryProvider = Providers[selection.provider]
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 + '\n\n' + templateContent
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<{ success: boolean; action: string | null }> {
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() + '\n\n' + entriesToAdd.join('\n') + '\n'
473
- : entriesToAdd.join('\n') + '\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(getPackageRoot(), 'templates', 'windsurf', 'workflows')
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() + '\n\n' + entriesToAdd.join('\n') + '\n'
617
- : entriesToAdd.join('\n') + '\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.readdirSync(projectsDir, { withFileTypes: true })
658
- .filter(dirent => dirent.isDirectory())
659
- .map(dirent => dirent.name)
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 (error) {
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 https from 'https'
8
- import fs from 'fs'
9
- import path from 'path'
10
- import os from 'os'
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 && cache.lastCheck && now - cache.lastCheck < this.checkInterval) {
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 { Issue, EnrichedIssue } from './types'
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 = response.match(/```(?:json)?\s*([\s\S]*?)```/) ||
217
- response.match(/\{[\s\S]*\}/)
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
- ? parsed.acceptanceCriteria
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 { issueTrackerManager, IssueTrackerManager } from './manager'
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 type {
7
- IssueTrackerProvider,
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 { linearProvider } from '../linear/client'
25
- import { jiraProvider } from '../jira/client'
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 prompt = this.getEnrichmentPrompt(issue, projectContext)
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
- | 'backlog'
100
- | 'todo'
101
- | 'in_progress'
102
- | 'in_review'
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
- JiraConfig,
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
- type: string
41
- content: Array<{
42
- type: string
43
- content?: Array<{ type: string; text?: string }>
44
- }>
45
- } | string | null
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
- 'open': 'backlog',
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
- 'review': 'in_review',
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<{ accountId: string; displayName: string; emailAddress?: string }>('/rest/api/3/myself')
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(`[jira] Connected as ${this.currentUser.displayName} (${this.currentUser.email || 'no email'})`)
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
- '/rest/api/3/issue',
393
- {
394
- method: 'POST',
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<{ transitions: Array<{ id: string; name: string; to: { statusCategory: { key: string } } }> }>(
446
- `/rest/api/3/issue/${id}/transitions`
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<{ transitions: Array<{ id: string; name: string; to: { statusCategory: { key: string } } }> }>(
473
- `/rest/api/3/issue/${id}/transitions`
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
- type: 'taskItem',
666
- attrs: { localId: crypto.randomUUID(), state: 'TODO' },
667
- content: [{ type: 'text', text: line.slice(6) }],
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
- type: 'taskItem',
677
- attrs: { localId: crypto.randomUUID(), state: 'DONE' },
678
- content: [{ type: 'text', text: line.slice(6) }],
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
- type: 'listItem',
687
- content: [{
688
- type: 'paragraph',
689
- content: [{ type: 'text', text: line.slice(2) }],
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
- JiraMCPAdapter,
30
- jiraMCPAdapter,
24
+ createCreateIssueInstruction,
25
+ createGetIssueInstruction,
31
26
  // MCP instruction generators
32
27
  createSearchInstruction,
33
- createGetIssueInstruction,
34
28
  createTransitionInstruction,
35
29
  createUpdateInstruction,
36
- createCreateIssueInstruction,
30
+ getMCPSetupInstructions,
37
31
  // Utilities
38
32
  isMCPAvailable,
39
- getMCPSetupInstructions,
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'