prjct-cli 0.30.3 → 0.33.5

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 (93) hide show
  1. package/CHANGELOG.md +118 -0
  2. package/CLAUDE.md +41 -0
  3. package/assets/statusline/components/jira.sh +108 -0
  4. package/assets/statusline/default-config.json +8 -1
  5. package/assets/statusline/lib/config.sh +21 -5
  6. package/core/__tests__/agentic/memory-system.test.ts +2 -2
  7. package/core/__tests__/types/fs.test.ts +125 -0
  8. package/core/agentic/agent-router.ts +16 -4
  9. package/core/agentic/chain-of-thought.ts +4 -12
  10. package/core/agentic/command-executor.ts +10 -11
  11. package/core/agentic/context-builder.ts +24 -10
  12. package/core/agentic/ground-truth.ts +139 -55
  13. package/core/agentic/prompt-builder.ts +20 -7
  14. package/core/agentic/smart-context.ts +1 -1
  15. package/core/agentic/template-loader.ts +1 -1
  16. package/core/agentic/tool-registry.ts +4 -2
  17. package/core/bus/bus.ts +1 -1
  18. package/core/commands/cleanup.ts +24 -8
  19. package/core/commands/planning.ts +4 -2
  20. package/core/commands/setup.ts +4 -4
  21. package/core/commands/shipping.ts +34 -8
  22. package/core/commands/snapshots.ts +27 -13
  23. package/core/context/generator.ts +9 -5
  24. package/core/domain/agent-generator.ts +1 -1
  25. package/core/domain/agent-loader.ts +1 -1
  26. package/core/domain/analyzer.ts +76 -31
  27. package/core/domain/context-estimator.ts +1 -1
  28. package/core/domain/snapshot-manager.ts +55 -21
  29. package/core/domain/task-stack.ts +16 -7
  30. package/core/infrastructure/author-detector.ts +1 -1
  31. package/core/infrastructure/claude-agent.ts +12 -8
  32. package/core/infrastructure/command-installer.ts +42 -21
  33. package/core/infrastructure/editors-config.ts +1 -1
  34. package/core/infrastructure/path-manager.ts +27 -2
  35. package/core/infrastructure/permission-manager.ts +1 -1
  36. package/core/infrastructure/setup.ts +31 -13
  37. package/core/infrastructure/update-checker.ts +5 -5
  38. package/core/integrations/issue-tracker/manager.ts +2 -1
  39. package/core/integrations/jira/client.ts +753 -0
  40. package/core/integrations/jira/index.ts +36 -0
  41. package/core/integrations/jira/mcp-adapter.ts +451 -0
  42. package/core/integrations/linear/client.ts +23 -3
  43. package/core/plugin/loader.ts +16 -6
  44. package/core/plugin/registry.ts +16 -6
  45. package/core/server/routes-extended.ts +13 -6
  46. package/core/server/routes.ts +15 -5
  47. package/core/server/sse.ts +4 -3
  48. package/core/services/agent-service.ts +4 -2
  49. package/core/services/memory-service.ts +16 -5
  50. package/core/services/project-service.ts +11 -2
  51. package/core/services/skill-service.ts +4 -3
  52. package/core/session/compaction.ts +4 -5
  53. package/core/session/metrics.ts +11 -4
  54. package/core/session/task-session-manager.ts +27 -9
  55. package/core/storage/storage-manager.ts +12 -5
  56. package/core/storage/storage.ts +26 -10
  57. package/core/sync/auth-config.ts +2 -2
  58. package/core/sync/oauth-handler.ts +1 -1
  59. package/core/sync/sync-client.ts +4 -2
  60. package/core/sync/sync-manager.ts +1 -1
  61. package/core/types/agentic.ts +8 -18
  62. package/core/types/config.ts +1 -1
  63. package/core/types/index.ts +3 -2
  64. package/core/types/integrations.ts +4 -48
  65. package/core/types/storage.ts +0 -8
  66. package/core/types/task.ts +0 -4
  67. package/core/utils/file-helper.ts +10 -4
  68. package/core/utils/jsonl-helper.ts +4 -4
  69. package/core/utils/keychain.ts +130 -0
  70. package/core/utils/logger.ts +27 -25
  71. package/core/utils/runtime.ts +1 -1
  72. package/core/utils/session-helper.ts +4 -4
  73. package/core/utils/version.ts +1 -1
  74. package/dist/bin/prjct.mjs +1 -1
  75. package/package.json +1 -1
  76. package/packages/shared/src/utils.ts +1 -1
  77. package/templates/commands/enrich.md +601 -195
  78. package/templates/commands/github.md +231 -0
  79. package/templates/commands/init.md +45 -26
  80. package/templates/commands/jira.md +276 -0
  81. package/templates/commands/linear.md +159 -177
  82. package/templates/commands/monday.md +196 -0
  83. package/templates/commands/setup.md +4 -1
  84. package/templates/mcp-config.json +42 -39
  85. package/core/integrations/notion/client.ts +0 -413
  86. package/core/integrations/notion/index.ts +0 -46
  87. package/core/integrations/notion/setup.ts +0 -235
  88. package/core/integrations/notion/sync.ts +0 -818
  89. package/core/integrations/notion/templates.ts +0 -246
  90. package/core/plugin/builtin/notion.ts +0 -178
  91. package/templates/skills/notion-push.md +0 -116
  92. package/templates/skills/notion-setup.md +0 -199
  93. package/templates/skills/notion-sync.md +0 -290
package/CHANGELOG.md CHANGED
@@ -1,5 +1,123 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.33.5] - 2026-01-13
4
+
5
+ ### Fix: Type Safety Improvements (PRJ-54)
6
+
7
+ Remove unsafe `as unknown` type casts with proper TypeScript interfaces.
8
+
9
+ **Changes:**
10
+ - `GroundTruthContext` now uses `ContextPaths` directly
11
+ - `chain-of-thought.ts` uses `Pick<ProjectContext, ...>` type alias
12
+ - `command-executor.ts` uses `PromptContext` instead of `Record<string, unknown>`
13
+
14
+ ---
15
+
16
+ ## [0.33.4] - 2026-01-13
17
+
18
+ ### Refactor: Error Type Differentiation Phase 3 (PRJ-61)
19
+
20
+ **Final phase** - Complete error differentiation across all 116 remaining catch blocks in 52 files.
21
+
22
+ **Pattern Applied:**
23
+ - `isNotFoundError()` for expected ENOENT errors
24
+ - `instanceof SyntaxError` for JSON parse errors
25
+ - `_error` capture for intentional catch-all blocks (network, git, etc.)
26
+ - Unexpected errors propagate with `throw error`
27
+
28
+ **Key Files Fixed:**
29
+ - `core/storage/storage.ts` - Storage read/exists operations
30
+ - `core/commands/shipping.ts` - Ship workflow error handling
31
+ - `core/session/task-session-manager.ts` - Session management
32
+ - `core/commands/cleanup.ts` - Cleanup operations
33
+ - `core/agentic/context-builder.ts` - Context building
34
+ - Plus 47 more files
35
+
36
+ **Stats:**
37
+ - Catches fixed this phase: 116
38
+ - Total fixed (Phase 1+2+3): 185
39
+ - Remaining: 0 empty catches
40
+
41
+ ---
42
+
43
+ ## [0.33.3] - 2026-01-13
44
+
45
+ ### Refactor: Error Type Differentiation Phase 2 (PRJ-60)
46
+
47
+ **Continuation of PRJ-51** - Differentiate error types in 3 more files (14 catch blocks):
48
+
49
+ **Files Modified:**
50
+ - `core/domain/snapshot-manager.ts` - 6 catches (fs.access, fs.readFile, fs.unlink, JSON.parse)
51
+ - `core/server/routes-extended.ts` - 2 catches (readJsonFile, writeJsonFile helpers)
52
+ - `core/infrastructure/setup.ts` - 6 catches (migration, settings parsing, status line)
53
+
54
+ **Pattern Applied:**
55
+ - ENOENT errors → handled gracefully (expected for missing files)
56
+ - SyntaxError → handled gracefully (expected for malformed JSON)
57
+ - Other errors → propagated or logged (unexpected)
58
+
59
+ **Stats:**
60
+ - Catches fixed: 14
61
+ - Total fixed (Phase 1+2): 69
62
+ - Remaining: ~211 catches in 63 files
63
+
64
+ ---
65
+
66
+ ## [0.33.2] - 2026-01-13
67
+
68
+ ### Refactor: Error Type Differentiation (PRJ-51)
69
+
70
+ **Problem:** 280 catch blocks across 69 files used empty `catch {}` which swallowed all errors without differentiating between expected (ENOENT) and unexpected errors.
71
+
72
+ **Solution:** Phase 1 implementation - differentiate error types in 3 priority files (55 catch blocks):
73
+
74
+ **Pattern Applied:**
75
+ ```typescript
76
+ // Before
77
+ } catch { return null }
78
+
79
+ // After
80
+ } catch (error) {
81
+ if (isNotFoundError(error)) return null // Expected: file doesn't exist
82
+ if (error instanceof SyntaxError) return null // Expected: invalid JSON
83
+ throw error // Unexpected: propagate
84
+ }
85
+ ```
86
+
87
+ **Files Modified:**
88
+ - `core/agentic/ground-truth.ts` - 21 catches (verification functions)
89
+ - `core/domain/analyzer.ts` - 18 catches (codebase analysis)
90
+ - `core/infrastructure/command-installer.ts` - 16 catches (command management)
91
+
92
+ **Tests Added:**
93
+ - `core/__tests__/types/fs.test.ts` - 15 new tests for error utilities
94
+
95
+ **Stats:**
96
+ - Tests: 137 → 152 (+15)
97
+ - Catches fixed: 55
98
+ - Files: 4 (3 refactored + 1 test)
99
+
100
+ ---
101
+
102
+ ## [0.33.1] - 2026-01-13
103
+
104
+ ### Refactor: Code Quality Improvements
105
+
106
+ **PRJ-58: Consolidate hardcoded paths to pathManager**
107
+ - Added `getClaudeDir()`, `getClaudeCommandsDir()`, `getClaudeSettingsPath()` to pathManager
108
+ - Refactored 6 files to use centralized path methods instead of `os.homedir()` concatenation
109
+ - Files: `compaction.ts`, `generator.ts`, `routes-extended.ts`, `routes.ts`, `auth-config.ts`, `setup.ts`
110
+
111
+ **PRJ-57: Simplify logger level detection**
112
+ - Use `Set` for truthy values instead of multiple OR conditions
113
+ - Remove redundant `'prjct'` equality check (covered by `includes`)
114
+ - Use nullish coalescing (`??`) instead of ternary
115
+ - Pre-compute level name to avoid runtime lookup
116
+ - Add `createLogMethod()` factory to reduce repetition
117
+ - File: `core/utils/logger.ts`
118
+
119
+ ---
120
+
3
121
  ## [0.30.3] - 2026-01-13
4
122
 
5
123
  ### Fix: Enrichment Not Enabled by Default
package/CLAUDE.md CHANGED
@@ -91,6 +91,7 @@ User Action → Storage (JSON) → Context (MD) → Sync Events
91
91
 
92
92
  ## COMMANDS
93
93
 
94
+ ### Core Workflow
94
95
  | Trigger | Purpose |
95
96
  |---------|---------|
96
97
  | `p. init` | Initialize project with deep analysis |
@@ -102,11 +103,51 @@ User Action → Storage (JSON) → Context (MD) → Sync Events
102
103
  | `p. resume` | Resume paused task |
103
104
  | `p. bug <desc>` | Report bug with auto-priority |
104
105
 
106
+ ### Issue Tracker Integrations
107
+ | Trigger | Purpose |
108
+ |---------|---------|
109
+ | `p. linear` | Linear issues (OAuth via MCP) |
110
+ | `p. jira` | JIRA issues (OAuth/SSO via MCP) |
111
+ | `p. github` | GitHub Issues (token via MCP) |
112
+ | `p. monday` | Monday.com boards (OAuth via MCP) |
113
+ | `p. enrich <ID>` | **AI-powered ticket enrichment** |
114
+
115
+ ### Ticket Enrichment (`p. enrich`)
116
+
117
+ Transform vague PM tickets into technical PRDs:
118
+
119
+ ```
120
+ PM creates: "Add user auth"
121
+
122
+ p. enrich PRJ-59
123
+
124
+ Architect analyzes codebase:
125
+ - Similar implementations found
126
+ - 5 files affected
127
+ - OAuth2 approach recommended
128
+ - 8 story points (not PM's guess of 2)
129
+
130
+ Publishes PRD to tracker:
131
+ - Technical approach
132
+ - Acceptance criteria
133
+ - LLM-ready prompt (for any AI tool)
134
+ ```
135
+
136
+ **Subcommands:**
137
+ - `p. enrich <ID>` - Enrich specific ticket
138
+ - `p. enrich setup` - Configure team preferences (estimation, output)
139
+ - `p. enrich batch` - Enrich all assigned tickets (backlog grooming)
140
+
105
141
  ### Workflow
106
142
  ```
107
143
  p. sync → p. task "description" → [work] → p. done → p. ship
108
144
  ```
109
145
 
146
+ ### With Issue Tracker
147
+ ```
148
+ p. linear → p. enrich PRJ-59 → p. linear start PRJ-59 → [work] → p. done → p. ship
149
+ ```
150
+
110
151
  ---
111
152
 
112
153
  ## INTELLIGENT BEHAVIOR
@@ -0,0 +1,108 @@
1
+ #!/bin/bash
2
+ # prjct statusline - JIRA integration component
3
+ # Displays the linked JIRA issue key and priority
4
+
5
+ component_jira() {
6
+ component_enabled "jira" || return
7
+ [[ "${CONFIG_JIRA_ENABLED}" != "true" ]] && return
8
+
9
+ local cache_file="${CACHE_DIR}/jira.cache"
10
+ local issue_data=""
11
+
12
+ # Check cache first
13
+ if cache_valid "$cache_file" "$CONFIG_CACHE_TTL_JIRA"; then
14
+ issue_data=$(cat "$cache_file")
15
+ else
16
+ # Get project ID
17
+ local project_id=$(get_project_id)
18
+ [[ -z "$project_id" ]] && return
19
+
20
+ local global_path="${HOME}/.prjct-cli/projects/${project_id}"
21
+ local state_file="${global_path}/storage/state.json"
22
+ local issues_file="${global_path}/storage/issues.json"
23
+
24
+ # Check if state file exists
25
+ [[ ! -f "$state_file" ]] && return
26
+
27
+ # Get linked issue from current task
28
+ local linked_issue=""
29
+
30
+ # First check if current task has a linked JIRA issue
31
+ local linked_provider=$(jq -r '.currentTask.linkedIssue.provider // ""' "$state_file" 2>/dev/null)
32
+ if [[ "$linked_provider" == "jira" ]]; then
33
+ linked_issue=$(jq -r '.currentTask.linkedIssue.id // ""' "$state_file" 2>/dev/null)
34
+ fi
35
+
36
+ # If no linked issue, try to get from issues.json (last worked)
37
+ if [[ -z "$linked_issue" ]] && [[ -f "$issues_file" ]]; then
38
+ # Check if issues.json is for JIRA
39
+ local provider=$(jq -r '.provider // ""' "$issues_file" 2>/dev/null)
40
+ if [[ "$provider" == "jira" ]]; then
41
+ # Get most recently updated issue that's in_progress
42
+ linked_issue=$(jq -r '
43
+ .issues // {} | to_entries
44
+ | map(select(.value.status == "in_progress"))
45
+ | sort_by(.value.updatedAt) | last
46
+ | .key // ""
47
+ ' "$issues_file" 2>/dev/null)
48
+ fi
49
+ fi
50
+
51
+ [[ -z "$linked_issue" ]] && return
52
+
53
+ # Get issue details from issues cache
54
+ if [[ -f "$issues_file" ]]; then
55
+ issue_data=$(jq -r --arg id "$linked_issue" '
56
+ .issues[$id] // {} |
57
+ "\(.externalId // "")|\(.priority // "none")|\(.status // "")"
58
+ ' "$issues_file" 2>/dev/null)
59
+
60
+ # Cache the result
61
+ write_cache "$cache_file" "$issue_data"
62
+ fi
63
+ fi
64
+
65
+ # Return empty if no data
66
+ [[ -z "$issue_data" || "$issue_data" == "||" ]] && return
67
+
68
+ # Parse issue data
69
+ local issue_key=$(echo "$issue_data" | cut -d'|' -f1)
70
+ local priority=$(echo "$issue_data" | cut -d'|' -f2)
71
+ local status=$(echo "$issue_data" | cut -d'|' -f3)
72
+
73
+ [[ -z "$issue_key" ]] && return
74
+
75
+ # Format output with JIRA-style coloring
76
+ local output=""
77
+
78
+ # Add status indicator if enabled
79
+ if [[ "${CONFIG_JIRA_SHOW_STATUS}" == "true" ]] && [[ -n "$status" ]]; then
80
+ local status_icon=$(get_jira_status_icon "$status")
81
+ [[ -n "$status_icon" ]] && output+="${status_icon} "
82
+ fi
83
+
84
+ # Issue key
85
+ output+="${ACCENT}${issue_key}${NC}"
86
+
87
+ # Add priority icon if enabled and priority is significant
88
+ if [[ "${CONFIG_JIRA_SHOW_PRIORITY}" == "true" ]]; then
89
+ local priority_icon=$(get_priority_icon "$priority")
90
+ [[ -n "$priority_icon" ]] && output+=" ${priority_icon}"
91
+ fi
92
+
93
+ echo -e "$output"
94
+ }
95
+
96
+ # Get JIRA-specific status icon
97
+ get_jira_status_icon() {
98
+ local status="$1"
99
+ case "$status" in
100
+ backlog) echo "📋" ;;
101
+ todo) echo "📝" ;;
102
+ in_progress) echo "🔄" ;;
103
+ in_review) echo "👀" ;;
104
+ done) echo "✅" ;;
105
+ cancelled) echo "❌" ;;
106
+ *) echo "" ;;
107
+ esac
108
+ }
@@ -4,7 +4,8 @@
4
4
  "cacheTTL": {
5
5
  "prjct": 30,
6
6
  "git": 5,
7
- "linear": 60
7
+ "linear": 60,
8
+ "jira": 60
8
9
  },
9
10
  "components": {
10
11
  "prjct_icon": {
@@ -21,6 +22,12 @@
21
22
  "position": 2,
22
23
  "showPriority": true
23
24
  },
25
+ "jira": {
26
+ "enabled": false,
27
+ "position": 2,
28
+ "showPriority": true,
29
+ "showStatus": false
30
+ },
24
31
  "dir": {
25
32
  "enabled": true,
26
33
  "position": 3
@@ -10,6 +10,7 @@ DEFAULT_THEME="default"
10
10
  DEFAULT_CACHE_TTL_PRJCT=30
11
11
  DEFAULT_CACHE_TTL_GIT=5
12
12
  DEFAULT_CACHE_TTL_LINEAR=60
13
+ DEFAULT_CACHE_TTL_JIRA=60
13
14
  DEFAULT_TASK_MAX_LENGTH=25
14
15
  DEFAULT_CONTEXT_MIN_PERCENT=30
15
16
  DEFAULT_ENRICHMENT_ENABLED="true"
@@ -26,16 +27,21 @@ load_config() {
26
27
  CONFIG_CACHE_TTL_PRJCT="$DEFAULT_CACHE_TTL_PRJCT"
27
28
  CONFIG_CACHE_TTL_GIT="$DEFAULT_CACHE_TTL_GIT"
28
29
  CONFIG_CACHE_TTL_LINEAR="$DEFAULT_CACHE_TTL_LINEAR"
30
+ CONFIG_CACHE_TTL_JIRA="$DEFAULT_CACHE_TTL_JIRA"
29
31
  CONFIG_TASK_MAX_LENGTH="$DEFAULT_TASK_MAX_LENGTH"
30
32
  CONFIG_CONTEXT_MIN_PERCENT="$DEFAULT_CONTEXT_MIN_PERCENT"
31
33
  CONFIG_LINEAR_ENABLED="true"
32
34
  CONFIG_LINEAR_SHOW_PRIORITY="true"
35
+ CONFIG_JIRA_ENABLED="false"
36
+ CONFIG_JIRA_SHOW_PRIORITY="true"
37
+ CONFIG_JIRA_SHOW_STATUS="false"
33
38
  CONFIG_ENRICHMENT_ENABLED="$DEFAULT_ENRICHMENT_ENABLED"
34
39
 
35
40
  # Default component configuration
36
41
  COMPONENT_ENABLED["prjct_icon"]="true"
37
42
  COMPONENT_ENABLED["task"]="true"
38
43
  COMPONENT_ENABLED["linear"]="true"
44
+ COMPONENT_ENABLED["jira"]="false"
39
45
  COMPONENT_ENABLED["dir"]="true"
40
46
  COMPONENT_ENABLED["git"]="true"
41
47
  COMPONENT_ENABLED["changes"]="true"
@@ -45,6 +51,7 @@ load_config() {
45
51
  COMPONENT_POSITION["prjct_icon"]=0
46
52
  COMPONENT_POSITION["task"]=1
47
53
  COMPONENT_POSITION["linear"]=2
54
+ COMPONENT_POSITION["jira"]=2
48
55
  COMPONENT_POSITION["dir"]=3
49
56
  COMPONENT_POSITION["git"]=4
50
57
  COMPONENT_POSITION["changes"]=5
@@ -63,12 +70,16 @@ load_config() {
63
70
  (.cacheTTL.prjct // 30),
64
71
  (.cacheTTL.git // 5),
65
72
  (.cacheTTL.linear // 60),
73
+ (.cacheTTL.jira // 60),
66
74
  (.components.task.maxLength // 25),
67
75
  (.components.context.minPercent // 30),
68
76
  (if .components.linear.showPriority == null then true else .components.linear.showPriority end),
77
+ (if .components.jira.showPriority == null then true else .components.jira.showPriority end),
78
+ (if .components.jira.showStatus == null then false else .components.jira.showStatus end),
69
79
  (if .components.prjct_icon.enabled == null then true else .components.prjct_icon.enabled end),
70
80
  (if .components.task.enabled == null then true else .components.task.enabled end),
71
81
  (if .components.linear.enabled == null then true else .components.linear.enabled end),
82
+ (if .components.jira.enabled == null then false else .components.jira.enabled end),
72
83
  (if .components.dir.enabled == null then true else .components.dir.enabled end),
73
84
  (if .components.git.enabled == null then true else .components.git.enabled end),
74
85
  (if .components.changes.enabled == null then true else .components.changes.enabled end),
@@ -78,6 +89,7 @@ load_config() {
78
89
  (.components.prjct_icon.position // 0),
79
90
  (.components.task.position // 1),
80
91
  (.components.linear.position // 2),
92
+ (.components.jira.position // 2),
81
93
  (.components.dir.position // 3),
82
94
  (.components.git.position // 4),
83
95
  (.components.changes.position // 5),
@@ -92,10 +104,11 @@ load_config() {
92
104
  local old_ifs="$IFS"
93
105
  IFS=$'\t' read -r \
94
106
  CONFIG_THEME \
95
- CONFIG_CACHE_TTL_PRJCT CONFIG_CACHE_TTL_GIT CONFIG_CACHE_TTL_LINEAR \
96
- CONFIG_TASK_MAX_LENGTH CONFIG_CONTEXT_MIN_PERCENT CONFIG_LINEAR_SHOW_PRIORITY \
97
- E_PRJCT_ICON E_TASK E_LINEAR E_DIR E_GIT E_CHANGES E_CONTEXT E_MODEL E_ENRICHMENT \
98
- P_PRJCT_ICON P_TASK P_LINEAR P_DIR P_GIT P_CHANGES P_CONTEXT P_MODEL \
107
+ CONFIG_CACHE_TTL_PRJCT CONFIG_CACHE_TTL_GIT CONFIG_CACHE_TTL_LINEAR CONFIG_CACHE_TTL_JIRA \
108
+ CONFIG_TASK_MAX_LENGTH CONFIG_CONTEXT_MIN_PERCENT \
109
+ CONFIG_LINEAR_SHOW_PRIORITY CONFIG_JIRA_SHOW_PRIORITY CONFIG_JIRA_SHOW_STATUS \
110
+ E_PRJCT_ICON E_TASK E_LINEAR E_JIRA E_DIR E_GIT E_CHANGES E_CONTEXT E_MODEL E_ENRICHMENT \
111
+ P_PRJCT_ICON P_TASK P_LINEAR P_JIRA P_DIR P_GIT P_CHANGES P_CONTEXT P_MODEL \
99
112
  <<< "$config_data"
100
113
  IFS="$old_ifs"
101
114
 
@@ -103,6 +116,7 @@ load_config() {
103
116
  COMPONENT_ENABLED["prjct_icon"]="$E_PRJCT_ICON"
104
117
  COMPONENT_ENABLED["task"]="$E_TASK"
105
118
  COMPONENT_ENABLED["linear"]="$E_LINEAR"
119
+ COMPONENT_ENABLED["jira"]="$E_JIRA"
106
120
  COMPONENT_ENABLED["dir"]="$E_DIR"
107
121
  COMPONENT_ENABLED["git"]="$E_GIT"
108
122
  COMPONENT_ENABLED["changes"]="$E_CHANGES"
@@ -113,14 +127,16 @@ load_config() {
113
127
  COMPONENT_POSITION["prjct_icon"]="$P_PRJCT_ICON"
114
128
  COMPONENT_POSITION["task"]="$P_TASK"
115
129
  COMPONENT_POSITION["linear"]="$P_LINEAR"
130
+ COMPONENT_POSITION["jira"]="$P_JIRA"
116
131
  COMPONENT_POSITION["dir"]="$P_DIR"
117
132
  COMPONENT_POSITION["git"]="$P_GIT"
118
133
  COMPONENT_POSITION["changes"]="$P_CHANGES"
119
134
  COMPONENT_POSITION["context"]="$P_CONTEXT"
120
135
  COMPONENT_POSITION["model"]="$P_MODEL"
121
136
 
122
- # Update linear enabled based on component config
137
+ # Update linear/jira enabled based on component config
123
138
  CONFIG_LINEAR_ENABLED="${COMPONENT_ENABLED["linear"]}"
139
+ CONFIG_JIRA_ENABLED="${COMPONENT_ENABLED["jira"]}"
124
140
 
125
141
  # Update enrichment enabled from config
126
142
  [[ -n "$E_ENRICHMENT" ]] && CONFIG_ENRICHMENT_ENABLED="$E_ENRICHMENT"
@@ -253,7 +253,7 @@ describe('MemorySystem P3.3', () => {
253
253
  try {
254
254
  const testPath = pathManager.getGlobalProjectPath(TEST_PROJECT_ID)
255
255
  await fs.rm(testPath, { recursive: true, force: true })
256
- } catch {
256
+ } catch (_error) {
257
257
  // Ignore cleanup errors
258
258
  }
259
259
  })
@@ -261,7 +261,7 @@ describe('MemorySystem P3.3', () => {
261
261
  afterAll(async () => {
262
262
  try {
263
263
  await fs.rm(TEST_GLOBAL_BASE_DIR, { recursive: true, force: true })
264
- } catch {
264
+ } catch (_error) {
265
265
  // Ignore cleanup errors
266
266
  }
267
267
  })
@@ -0,0 +1,125 @@
1
+ /**
2
+ * FS Types Tests
3
+ * Tests for file system error utilities
4
+ */
5
+
6
+ import { describe, it, expect } from 'bun:test'
7
+ import {
8
+ isNotFoundError,
9
+ isPermissionError,
10
+ isDirNotEmptyError,
11
+ isFileExistsError,
12
+ isNodeError,
13
+ } from '../../types/fs'
14
+
15
+ describe('FS Error Utilities', () => {
16
+ describe('isNotFoundError', () => {
17
+ it('should return true for ENOENT error', () => {
18
+ const error = new Error('File not found') as NodeJS.ErrnoException
19
+ error.code = 'ENOENT'
20
+ expect(isNotFoundError(error)).toBe(true)
21
+ })
22
+
23
+ it('should return false for other error codes', () => {
24
+ const error = new Error('Permission denied') as NodeJS.ErrnoException
25
+ error.code = 'EACCES'
26
+ expect(isNotFoundError(error)).toBe(false)
27
+ })
28
+
29
+ it('should return false for errors without code', () => {
30
+ const error = new Error('Generic error')
31
+ expect(isNotFoundError(error)).toBe(false)
32
+ })
33
+
34
+ it('should return false for non-error values', () => {
35
+ expect(isNotFoundError(null)).toBe(false)
36
+ expect(isNotFoundError(undefined)).toBe(false)
37
+ expect(isNotFoundError('string')).toBe(false)
38
+ })
39
+ })
40
+
41
+ describe('isPermissionError', () => {
42
+ it('should return true for EACCES error', () => {
43
+ const error = new Error('Permission denied') as NodeJS.ErrnoException
44
+ error.code = 'EACCES'
45
+ expect(isPermissionError(error)).toBe(true)
46
+ })
47
+
48
+ it('should return true for EPERM error', () => {
49
+ const error = new Error('Operation not permitted') as NodeJS.ErrnoException
50
+ error.code = 'EPERM'
51
+ expect(isPermissionError(error)).toBe(true)
52
+ })
53
+
54
+ it('should return false for other error codes', () => {
55
+ const error = new Error('File not found') as NodeJS.ErrnoException
56
+ error.code = 'ENOENT'
57
+ expect(isPermissionError(error)).toBe(false)
58
+ })
59
+ })
60
+
61
+ describe('isDirNotEmptyError', () => {
62
+ it('should return true for ENOTEMPTY error', () => {
63
+ const error = new Error('Directory not empty') as NodeJS.ErrnoException
64
+ error.code = 'ENOTEMPTY'
65
+ expect(isDirNotEmptyError(error)).toBe(true)
66
+ })
67
+
68
+ it('should return false for other error codes', () => {
69
+ const error = new Error('File not found') as NodeJS.ErrnoException
70
+ error.code = 'ENOENT'
71
+ expect(isDirNotEmptyError(error)).toBe(false)
72
+ })
73
+ })
74
+
75
+ describe('isFileExistsError', () => {
76
+ it('should return true for EEXIST error', () => {
77
+ const error = new Error('File exists') as NodeJS.ErrnoException
78
+ error.code = 'EEXIST'
79
+ expect(isFileExistsError(error)).toBe(true)
80
+ })
81
+
82
+ it('should return false for other error codes', () => {
83
+ const error = new Error('File not found') as NodeJS.ErrnoException
84
+ error.code = 'ENOENT'
85
+ expect(isFileExistsError(error)).toBe(false)
86
+ })
87
+ })
88
+
89
+ describe('isNodeError', () => {
90
+ it('should return true for Error with code property', () => {
91
+ const error = new Error('File not found') as NodeJS.ErrnoException
92
+ error.code = 'ENOENT'
93
+ expect(isNodeError(error)).toBe(true)
94
+ })
95
+
96
+ it('should return false for Error without code property', () => {
97
+ const error = new Error('Generic error')
98
+ expect(isNodeError(error)).toBe(false)
99
+ })
100
+
101
+ it('should return false for non-Error values', () => {
102
+ expect(isNodeError(null)).toBe(false)
103
+ expect(isNodeError({ code: 'ENOENT' })).toBe(false) // Not an Error instance
104
+ })
105
+ })
106
+
107
+ describe('SyntaxError handling pattern', () => {
108
+ it('should differentiate SyntaxError from fs errors', () => {
109
+ const syntaxError = new SyntaxError('Unexpected token')
110
+ const fsError = new Error('File not found') as NodeJS.ErrnoException
111
+ fsError.code = 'ENOENT'
112
+
113
+ // Pattern used in code
114
+ const handleError = (error: unknown): string => {
115
+ if (isNotFoundError(error)) return 'not-found'
116
+ if (error instanceof SyntaxError) return 'parse-error'
117
+ return 'other'
118
+ }
119
+
120
+ expect(handleError(fsError)).toBe('not-found')
121
+ expect(handleError(syntaxError)).toBe('parse-error')
122
+ expect(handleError(new Error('Other'))).toBe('other')
123
+ })
124
+ })
125
+ })
@@ -15,6 +15,7 @@ import fs from 'fs/promises'
15
15
  import path from 'path'
16
16
  import configManager from '../infrastructure/config-manager'
17
17
  import pathManager from '../infrastructure/path-manager'
18
+ import { isNotFoundError } from '../types/fs'
18
19
  import type { Agent, AssignmentContext } from '../types'
19
20
 
20
21
  // Re-export types for convenience
@@ -57,7 +58,11 @@ class AgentRouter {
57
58
  }
58
59
 
59
60
  return agents
60
- } catch {
61
+ } catch (error) {
62
+ // Agents directory doesn't exist yet - expected for new projects
63
+ if (!isNotFoundError(error)) {
64
+ console.error(`Agent loading error: ${(error as Error).message}`)
65
+ }
61
66
  return []
62
67
  }
63
68
  }
@@ -80,7 +85,11 @@ class AgentRouter {
80
85
  const filePath = path.join(this.agentsPath, `${name}.md`)
81
86
  const content = await fs.readFile(filePath, 'utf-8')
82
87
  return { name, content }
83
- } catch {
88
+ } catch (error) {
89
+ // Agent file doesn't exist - expected
90
+ if (!isNotFoundError(error)) {
91
+ console.error(`Agent load error: ${(error as Error).message}`)
92
+ }
84
93
  return null
85
94
  }
86
95
  }
@@ -130,8 +139,11 @@ class AgentRouter {
130
139
  }) + '\n'
131
140
 
132
141
  await fs.appendFile(logPath, entry)
133
- } catch {
134
- // Silent fail for logging
142
+ } catch (error) {
143
+ // Non-critical - log unexpected errors but don't fail
144
+ if (!isNotFoundError(error)) {
145
+ console.error(`Agent usage log error: ${(error as Error).message}`)
146
+ }
135
147
  }
136
148
  }
137
149
  }
@@ -6,19 +6,11 @@
6
6
  * @version 1.0.0
7
7
  */
8
8
 
9
- interface Context {
10
- projectId?: string | null
11
- projectPath: string
12
- params: Record<string, unknown>
13
- }
9
+ import type { ProjectContext, ContextState } from '../types'
14
10
 
15
- interface State {
16
- now?: string | null
17
- next?: string | null
18
- shipped?: string | null
19
- analysis?: string | null
20
- [key: string]: unknown
21
- }
11
+ // Type aliases for compatibility with ProjectContext from contextBuilder.build()
12
+ type Context = Pick<ProjectContext, 'projectId' | 'projectPath' | 'params'>
13
+ type State = ContextState
22
14
 
23
15
  interface ReasoningStep {
24
16
  step: string