oh-my-claude-sisyphus 3.8.16 → 3.9.1

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 (178) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.mcp.json +1 -1
  4. package/agents/analyst.md +41 -0
  5. package/agents/architect.md +47 -2
  6. package/agents/critic.md +42 -0
  7. package/agents/deep-executor.md +193 -0
  8. package/agents/planner.md +82 -0
  9. package/bridge/mcp-server.cjs +181 -181
  10. package/commands/build-fix.md +3 -3
  11. package/commands/ralph.md +3 -3
  12. package/commands/ultraqa.md +4 -4
  13. package/commands/ultrawork.md +3 -3
  14. package/dist/__tests__/agent-registry.test.js +1 -1
  15. package/dist/__tests__/installer.test.js +8 -8
  16. package/dist/__tests__/installer.test.js.map +1 -1
  17. package/dist/__tests__/omc-tools-server.test.js +2 -2
  18. package/dist/__tests__/omc-tools-server.test.js.map +1 -1
  19. package/dist/__tests__/skills.test.js +5 -4
  20. package/dist/__tests__/skills.test.js.map +1 -1
  21. package/dist/agents/deep-executor.d.ts +15 -0
  22. package/dist/agents/deep-executor.d.ts.map +1 -0
  23. package/dist/agents/deep-executor.js +47 -0
  24. package/dist/agents/deep-executor.js.map +1 -0
  25. package/dist/agents/definitions.d.ts +16 -1
  26. package/dist/agents/definitions.d.ts.map +1 -1
  27. package/dist/agents/definitions.js +26 -1
  28. package/dist/agents/definitions.js.map +1 -1
  29. package/dist/agents/index.d.ts +1 -0
  30. package/dist/agents/index.d.ts.map +1 -1
  31. package/dist/agents/index.js +1 -0
  32. package/dist/agents/index.js.map +1 -1
  33. package/dist/cli/commands/doctor-conflicts.d.ts +55 -0
  34. package/dist/cli/commands/doctor-conflicts.d.ts.map +1 -0
  35. package/dist/cli/commands/doctor-conflicts.js +261 -0
  36. package/dist/cli/commands/doctor-conflicts.js.map +1 -0
  37. package/dist/cli/index.js +16 -1
  38. package/dist/cli/index.js.map +1 -1
  39. package/dist/features/auto-update.d.ts +12 -0
  40. package/dist/features/auto-update.d.ts.map +1 -1
  41. package/dist/features/auto-update.js +4 -1
  42. package/dist/features/auto-update.js.map +1 -1
  43. package/dist/features/background-tasks.js +4 -4
  44. package/dist/features/context-injector/types.d.ts +1 -1
  45. package/dist/features/context-injector/types.d.ts.map +1 -1
  46. package/dist/features/task-decomposer/index.js +3 -3
  47. package/dist/features/task-decomposer/index.js.map +1 -1
  48. package/dist/features/verification/index.d.ts +3 -3
  49. package/dist/features/verification/index.js +3 -3
  50. package/dist/features/verification/index.js.map +1 -1
  51. package/dist/hooks/__tests__/bridge.test.d.ts +2 -0
  52. package/dist/hooks/__tests__/bridge.test.d.ts.map +1 -0
  53. package/dist/hooks/__tests__/bridge.test.js +199 -0
  54. package/dist/hooks/__tests__/bridge.test.js.map +1 -0
  55. package/dist/hooks/autopilot/state.js +3 -3
  56. package/dist/hooks/beads-context/__tests__/index.test.d.ts +2 -0
  57. package/dist/hooks/beads-context/__tests__/index.test.d.ts.map +1 -0
  58. package/dist/hooks/beads-context/__tests__/index.test.js +150 -0
  59. package/dist/hooks/beads-context/__tests__/index.test.js.map +1 -0
  60. package/dist/hooks/beads-context/constants.d.ts +3 -0
  61. package/dist/hooks/beads-context/constants.d.ts.map +1 -0
  62. package/dist/hooks/beads-context/constants.js +35 -0
  63. package/dist/hooks/beads-context/constants.js.map +1 -0
  64. package/dist/hooks/beads-context/index.d.ts +21 -0
  65. package/dist/hooks/beads-context/index.d.ts.map +1 -0
  66. package/dist/hooks/beads-context/index.js +62 -0
  67. package/dist/hooks/beads-context/index.js.map +1 -0
  68. package/dist/hooks/beads-context/types.d.ts +7 -0
  69. package/dist/hooks/beads-context/types.d.ts.map +1 -0
  70. package/dist/hooks/beads-context/types.js +2 -0
  71. package/dist/hooks/beads-context/types.js.map +1 -0
  72. package/dist/hooks/bridge.d.ts +4 -0
  73. package/dist/hooks/bridge.d.ts.map +1 -1
  74. package/dist/hooks/bridge.js +76 -23
  75. package/dist/hooks/bridge.js.map +1 -1
  76. package/dist/hooks/clear-suggestions/constants.d.ts +54 -0
  77. package/dist/hooks/clear-suggestions/constants.d.ts.map +1 -0
  78. package/dist/hooks/clear-suggestions/constants.js +102 -0
  79. package/dist/hooks/clear-suggestions/constants.js.map +1 -0
  80. package/dist/hooks/clear-suggestions/index.d.ts +61 -0
  81. package/dist/hooks/clear-suggestions/index.d.ts.map +1 -0
  82. package/dist/hooks/clear-suggestions/index.js +282 -0
  83. package/dist/hooks/clear-suggestions/index.js.map +1 -0
  84. package/dist/hooks/clear-suggestions/triggers.d.ts +65 -0
  85. package/dist/hooks/clear-suggestions/triggers.d.ts.map +1 -0
  86. package/dist/hooks/clear-suggestions/triggers.js +222 -0
  87. package/dist/hooks/clear-suggestions/triggers.js.map +1 -0
  88. package/dist/hooks/clear-suggestions/types.d.ts +92 -0
  89. package/dist/hooks/clear-suggestions/types.d.ts.map +1 -0
  90. package/dist/hooks/clear-suggestions/types.js +9 -0
  91. package/dist/hooks/clear-suggestions/types.js.map +1 -0
  92. package/dist/hooks/index.d.ts +1 -0
  93. package/dist/hooks/index.d.ts.map +1 -1
  94. package/dist/hooks/index.js +3 -0
  95. package/dist/hooks/index.js.map +1 -1
  96. package/dist/hooks/permission-handler/index.d.ts.map +1 -1
  97. package/dist/hooks/permission-handler/index.js +3 -1
  98. package/dist/hooks/permission-handler/index.js.map +1 -1
  99. package/dist/hooks/setup/index.d.ts.map +1 -1
  100. package/dist/hooks/setup/index.js +12 -5
  101. package/dist/hooks/setup/index.js.map +1 -1
  102. package/dist/hooks/subagent-tracker/index.d.ts.map +1 -1
  103. package/dist/hooks/subagent-tracker/index.js +25 -9
  104. package/dist/hooks/subagent-tracker/index.js.map +1 -1
  105. package/dist/hooks/ultraqa/index.js +4 -4
  106. package/dist/hooks/ultraqa/index.js.map +1 -1
  107. package/dist/index.js +1 -1
  108. package/dist/index.js.map +1 -1
  109. package/dist/installer/__tests__/claude-md-merge.test.d.ts +6 -0
  110. package/dist/installer/__tests__/claude-md-merge.test.d.ts.map +1 -0
  111. package/dist/installer/__tests__/claude-md-merge.test.js +220 -0
  112. package/dist/installer/__tests__/claude-md-merge.test.js.map +1 -0
  113. package/dist/installer/__tests__/safe-installer.test.d.ts +6 -0
  114. package/dist/installer/__tests__/safe-installer.test.d.ts.map +1 -0
  115. package/dist/installer/__tests__/safe-installer.test.js +172 -0
  116. package/dist/installer/__tests__/safe-installer.test.js.map +1 -0
  117. package/dist/installer/hooks.d.ts.map +1 -1
  118. package/dist/installer/hooks.js +3 -1
  119. package/dist/installer/hooks.js.map +1 -1
  120. package/dist/installer/index.d.ts +27 -1
  121. package/dist/installer/index.d.ts.map +1 -1
  122. package/dist/installer/index.js +209 -85
  123. package/dist/installer/index.js.map +1 -1
  124. package/dist/mcp/omc-tools-server.d.ts +1 -1
  125. package/dist/mcp/omc-tools-server.d.ts.map +1 -1
  126. package/dist/mcp/omc-tools-server.js +3 -3
  127. package/dist/mcp/omc-tools-server.js.map +1 -1
  128. package/dist/mcp/standalone-server.js +1 -1
  129. package/dist/mcp/standalone-server.js.map +1 -1
  130. package/dist/verification/tier-selector.d.ts +40 -0
  131. package/dist/verification/tier-selector.d.ts.map +1 -0
  132. package/dist/verification/tier-selector.js +95 -0
  133. package/dist/verification/tier-selector.js.map +1 -0
  134. package/dist/verification/tier-selector.test.d.ts +2 -0
  135. package/dist/verification/tier-selector.test.d.ts.map +1 -0
  136. package/dist/verification/tier-selector.test.js +282 -0
  137. package/dist/verification/tier-selector.test.js.map +1 -0
  138. package/docs/AGENTS.md +1 -1
  139. package/docs/CLAUDE.md +90 -378
  140. package/docs/partials/agent-tiers.md +165 -0
  141. package/docs/partials/features.md +131 -0
  142. package/docs/partials/mode-hierarchy.md +120 -0
  143. package/docs/partials/mode-selection-guide.md +82 -0
  144. package/docs/partials/verification-tiers.md +107 -0
  145. package/docs/shared/agent-tiers.md +165 -0
  146. package/docs/shared/features.md +131 -0
  147. package/docs/shared/mode-hierarchy.md +120 -0
  148. package/docs/shared/mode-selection-guide.md +82 -0
  149. package/docs/shared/verification-tiers.md +107 -0
  150. package/package.json +4 -3
  151. package/scripts/compose-docs.mjs +44 -0
  152. package/scripts/keyword-detector.mjs +13 -3
  153. package/skills/build-fix/SKILL.md +8 -8
  154. package/skills/deep-executor/SKILL.md +50 -0
  155. package/skills/deepinit/SKILL.md +2 -2
  156. package/skills/ecomode/SKILL.md +58 -103
  157. package/skills/omc-setup/SKILL.md +197 -20
  158. package/skills/plan/SKILL.md +62 -0
  159. package/skills/project-session-manager/SKILL.md +87 -4
  160. package/skills/project-session-manager/lib/config.sh +54 -5
  161. package/skills/project-session-manager/lib/parse.sh +65 -11
  162. package/skills/project-session-manager/lib/providers/github.sh +52 -0
  163. package/skills/project-session-manager/lib/providers/interface.sh +76 -0
  164. package/skills/project-session-manager/lib/providers/jira.sh +79 -0
  165. package/skills/project-session-manager/lib/session.sh +49 -12
  166. package/skills/project-session-manager/lib/worktree.sh +37 -4
  167. package/skills/project-session-manager/psm.sh +116 -51
  168. package/skills/ralph/SKILL.md +44 -30
  169. package/skills/tdd/SKILL.md +2 -2
  170. package/skills/ultrapilot/SKILL.md +3 -3
  171. package/skills/ultraqa/SKILL.md +4 -4
  172. package/skills/ultrawork/SKILL.md +59 -69
  173. package/templates/hooks/keyword-detector.mjs +21 -13
  174. package/templates/hooks/lib/stdin.mjs +62 -0
  175. package/templates/hooks/persistent-mode.mjs +7 -8
  176. package/templates/hooks/post-tool-use.mjs +8 -10
  177. package/templates/hooks/pre-tool-use.mjs +9 -6
  178. package/templates/hooks/session-start.mjs +7 -8
@@ -17,6 +17,20 @@ You guide users through planning by:
17
17
 
18
18
  ## Planning Modes
19
19
 
20
+ | Mode | Trigger | Behavior |
21
+ |------|---------|----------|
22
+ | interview | Default | Interactive requirements gathering with adaptive exploration |
23
+ | direct | --direct, detailed request | Skip interview, generate plan directly |
24
+ | consensus | --consensus, "ralplan" | Planner → Architect → Critic loop until consensus |
25
+ | review | --review | Critic review of existing plan |
26
+
27
+ ### Review Mode
28
+
29
+ When `--review` is specified or user says "review this plan":
30
+ 1. Read the plan file from `.omc/plans/`
31
+ 2. Spawn Critic agent to review
32
+ 3. Return verdict (OKAY or REJECT with improvements)
33
+
20
34
  ### Auto-Detection: Interview vs Direct Planning
21
35
 
22
36
  **Interview Mode** (when request is BROAD):
@@ -53,6 +67,54 @@ Ask clarifying questions about: Goals, Constraints, Context, Risks, Preferences
53
67
 
54
68
  **When plain text is OK:** Questions needing specific values (port numbers, names) or follow-up clarifications.
55
69
 
70
+ ## Adaptive Context Gathering (CRITICAL)
71
+
72
+ Before asking ANY question, classify it:
73
+
74
+ ### Question Classification
75
+
76
+ | Type | Examples | Action |
77
+ |------|----------|--------|
78
+ | **Codebase Fact** | "What patterns exist?", "Where is X implemented?" | Explore first, DON'T ask user |
79
+ | **User Preference** | "Priority?", "Timeline?", "Risk tolerance?" | Ask user via AskUserQuestion |
80
+ | **Scope Decision** | "Include feature Y?" | Ask user |
81
+ | **Requirement** | "Performance constraints?" | Ask user |
82
+
83
+ ### Adaptive Flow
84
+
85
+ 1. Generate interview question
86
+ 2. Classify: Is this a codebase fact or user preference?
87
+ 3. If **CODEBASE FACT**:
88
+ a. Spawn `explore` agent (haiku, 30s timeout)
89
+ b. Query: focused on the specific fact needed
90
+ c. Use findings to inform next question or skip question entirely
91
+ 4. If **USER PREFERENCE**:
92
+ a. Use AskUserQuestion tool with options
93
+ b. Wait for response
94
+ 5. Repeat for next question
95
+
96
+ ### Exploration Integration
97
+
98
+ When context is gathered via explore agent:
99
+ - **DO NOT** ask "What patterns does the codebase use?"
100
+ - **DO** say "I see the codebase uses [pattern X]. Would you like to follow this pattern or try something different?"
101
+
102
+ ### Example Adaptive Interview
103
+
104
+ **Without Adaptive (BAD):**
105
+ ```
106
+ Planner: "Where is authentication implemented in your codebase?"
107
+ User: "Uh, somewhere in src/auth I think?"
108
+ ```
109
+
110
+ **With Adaptive (GOOD):**
111
+ ```
112
+ Planner: [spawns explore agent: "find authentication implementation"]
113
+ Planner: [receives: "Auth is in src/auth/ using JWT with passport.js"]
114
+ Planner: "I see you're using JWT authentication with passport.js in src/auth/.
115
+ For this new feature, should we extend the existing auth or add a separate auth flow?"
116
+ ```
117
+
56
118
  **MANDATORY: Single Question at a Time**
57
119
 
58
120
  **Core Rule:** Never ask multiple questions in one message during interview mode.
@@ -49,6 +49,85 @@ Supported formats:
49
49
  }
50
50
  ```
51
51
 
52
+ ## Providers
53
+
54
+ PSM supports multiple issue tracking providers:
55
+
56
+ | Provider | CLI Required | Reference Formats | Commands |
57
+ |----------|--------------|-------------------|----------|
58
+ | GitHub (default) | `gh` | `owner/repo#123`, `alias#123`, GitHub URLs | review, fix, feature |
59
+ | Jira | `jira` | `PROJ-123` (if PROJ configured), `alias#123` | fix, feature |
60
+
61
+ ### Jira Configuration
62
+
63
+ To use Jira, add an alias with `jira_project` and `provider: "jira"`:
64
+
65
+ ```json
66
+ {
67
+ "aliases": {
68
+ "mywork": {
69
+ "jira_project": "MYPROJ",
70
+ "repo": "mycompany/my-project",
71
+ "local": "~/Workspace/my-project",
72
+ "default_base": "develop",
73
+ "provider": "jira"
74
+ }
75
+ }
76
+ }
77
+ ```
78
+
79
+ **Important:** The `repo` field is still required for cloning the git repository. Jira tracks issues, but you work in a git repo.
80
+
81
+ For non-GitHub repos, use `clone_url` instead:
82
+ ```json
83
+ {
84
+ "aliases": {
85
+ "private": {
86
+ "jira_project": "PRIV",
87
+ "clone_url": "git@gitlab.internal:team/repo.git",
88
+ "local": "~/Workspace/repo",
89
+ "provider": "jira"
90
+ }
91
+ }
92
+ }
93
+ ```
94
+
95
+ ### Jira Reference Detection
96
+
97
+ PSM only recognizes `PROJ-123` format as Jira when `PROJ` is explicitly configured as a `jira_project` in your aliases. This prevents false positives from branch names like `FIX-123`.
98
+
99
+ ### Jira Examples
100
+
101
+ ```bash
102
+ # Fix a Jira issue (MYPROJ must be configured)
103
+ psm fix MYPROJ-123
104
+
105
+ # Fix using alias (recommended)
106
+ psm fix mywork#123
107
+
108
+ # Feature development (works same as GitHub)
109
+ psm feature mywork add-webhooks
110
+
111
+ # Note: 'psm review' is not supported for Jira (no PR concept)
112
+ # Use 'psm fix' for Jira issues
113
+ ```
114
+
115
+ ### Jira CLI Setup
116
+
117
+ Install the Jira CLI:
118
+ ```bash
119
+ # macOS
120
+ brew install ankitpokhrel/jira-cli/jira-cli
121
+
122
+ # Linux
123
+ # See: https://github.com/ankitpokhrel/jira-cli#installation
124
+
125
+ # Configure (interactive)
126
+ jira init
127
+ ```
128
+
129
+ The Jira CLI handles authentication separately from PSM.
130
+
52
131
  ## Directory Structure
53
132
 
54
133
  ```
@@ -371,10 +450,14 @@ Parse `{{ARGUMENTS}}` to determine:
371
450
 
372
451
  ## Requirements
373
452
 
374
- - `git` with worktree support (v2.5+)
375
- - `gh` CLI (authenticated)
376
- - `tmux`
377
- - `jq` for JSON parsing
453
+ Required:
454
+ - `git` - Version control (with worktree support v2.5+)
455
+ - `jq` - JSON parsing
456
+ - `tmux` - Session management (optional, but recommended)
457
+
458
+ Optional (per provider):
459
+ - `gh` - GitHub CLI (for GitHub workflows)
460
+ - `jira` - Jira CLI (for Jira workflows)
378
461
 
379
462
  ## Initialization
380
463
 
@@ -48,11 +48,12 @@ psm_get_project() {
48
48
  return 1
49
49
  fi
50
50
 
51
- local repo=$(jq -r ".aliases[\"$alias\"].repo // empty" "$PSM_PROJECTS")
52
- local local_path=$(jq -r ".aliases[\"$alias\"].local // empty" "$PSM_PROJECTS")
53
- local default_base=$(jq -r ".aliases[\"$alias\"].default_base // \"main\"" "$PSM_PROJECTS")
51
+ local repo=$(jq -r --arg a "$alias" '.aliases[$a].repo // empty' "$PSM_PROJECTS")
52
+ local local_path=$(jq -r --arg a "$alias" '.aliases[$a].local // empty' "$PSM_PROJECTS")
53
+ local default_base=$(jq -r --arg a "$alias" '.aliases[$a].default_base // "main"' "$PSM_PROJECTS")
54
54
 
55
- if [[ -z "$repo" ]]; then
55
+ local clone_url=$(jq -r --arg a "$alias" '.aliases[$a].clone_url // empty' "$PSM_PROJECTS")
56
+ if [[ -z "$repo" && -z "$clone_url" ]]; then
56
57
  return 1
57
58
  fi
58
59
 
@@ -62,6 +63,53 @@ psm_get_project() {
62
63
  echo "${repo}|${local_path}|${default_base}"
63
64
  }
64
65
 
66
+ # Get provider for a project alias
67
+ # Usage: psm_get_project_provider "mywork"
68
+ # Returns: "github" | "jira" | empty (defaults to github)
69
+ psm_get_project_provider() {
70
+ local alias="$1"
71
+ if [[ ! -f "$PSM_PROJECTS" ]]; then
72
+ echo "github"
73
+ return
74
+ fi
75
+ local provider
76
+ provider=$(jq -r --arg a "$alias" '.aliases[$a].provider // "github"' "$PSM_PROJECTS")
77
+ echo "$provider"
78
+ }
79
+
80
+ # Get Jira project key for alias
81
+ # Usage: psm_get_project_jira_project "mywork"
82
+ # Returns: "MYPROJ" or empty
83
+ psm_get_project_jira_project() {
84
+ local alias="$1"
85
+ if [[ ! -f "$PSM_PROJECTS" ]]; then
86
+ return
87
+ fi
88
+ jq -r --arg a "$alias" '.aliases[$a].jira_project // empty' "$PSM_PROJECTS"
89
+ }
90
+
91
+ # Get explicit clone_url for alias (for non-GitHub repos)
92
+ # Usage: psm_get_project_clone_url "mywork"
93
+ # Returns: URL or empty
94
+ psm_get_project_clone_url() {
95
+ local alias="$1"
96
+ if [[ ! -f "$PSM_PROJECTS" ]]; then
97
+ return
98
+ fi
99
+ jq -r --arg a "$alias" '.aliases[$a].clone_url // empty' "$PSM_PROJECTS"
100
+ }
101
+
102
+ # Get repo field for alias
103
+ # Usage: psm_get_project_repo "mywork"
104
+ # Returns: "owner/repo" or empty
105
+ psm_get_project_repo() {
106
+ local alias="$1"
107
+ if [[ ! -f "$PSM_PROJECTS" ]]; then
108
+ return
109
+ fi
110
+ jq -r --arg a "$alias" '.aliases[$a].repo // empty' "$PSM_PROJECTS"
111
+ }
112
+
65
113
  # Add or update project alias
66
114
  psm_set_project() {
67
115
  local alias="$1"
@@ -70,7 +118,8 @@ psm_set_project() {
70
118
  local default_base="${4:-main}"
71
119
 
72
120
  local tmp=$(mktemp)
73
- jq ".aliases[\"$alias\"] = {\"repo\": \"$repo\", \"local\": \"$local_path\", \"default_base\": \"$default_base\"}" \
121
+ jq --arg a "$alias" --arg r "$repo" --arg l "$local_path" --arg b "$default_base" \
122
+ '.aliases[$a] = {"repo": $r, "local": $l, "default_base": $b}' \
74
123
  "$PSM_PROJECTS" > "$tmp" && mv "$tmp" "$PSM_PROJECTS"
75
124
  }
76
125
 
@@ -9,7 +9,7 @@
9
9
  # #123 -> number=123 (use current repo)
10
10
  #
11
11
  # Usage: psm_parse_ref "omc#123"
12
- # Returns: type|alias|repo|number|local_path|base
12
+ # Returns: type|alias|repo|number|local_path|base|provider|provider_ref
13
13
  psm_parse_ref() {
14
14
  local ref="$1"
15
15
  local type=""
@@ -29,7 +29,7 @@ psm_parse_ref() {
29
29
  if [[ -n "$alias" ]]; then
30
30
  IFS='|' read -r _ local_path base <<< "$(psm_get_project "$alias")"
31
31
  fi
32
- echo "pr|${alias:-}|$repo|$number|${local_path:-}|$base"
32
+ echo "pr|${alias:-}|$repo|$number|${local_path:-}|$base|github|${repo}#${number}"
33
33
  return 0
34
34
  fi
35
35
 
@@ -42,11 +42,24 @@ psm_parse_ref() {
42
42
  if [[ -n "$alias" ]]; then
43
43
  IFS='|' read -r _ local_path base <<< "$(psm_get_project "$alias")"
44
44
  fi
45
- echo "issue|${alias:-}|$repo|$number|${local_path:-}|$base"
45
+ echo "issue|${alias:-}|$repo|$number|${local_path:-}|$base|github|${repo}#${number}"
46
46
  return 0
47
47
  fi
48
48
 
49
- # alias#number format (e.g., omc#123)
49
+ # Jira direct reference (PROJ-123) - config-validated
50
+ local jira_info
51
+ if jira_info=$(psm_detect_jira_key "$ref"); then
52
+ IFS='|' read -r alias project_key issue_number <<< "$jira_info"
53
+ local project_info
54
+ project_info=$(psm_get_project "$alias")
55
+ if [[ $? -eq 0 ]]; then
56
+ IFS='|' read -r repo local_path base <<< "$project_info"
57
+ echo "issue|${alias}|${repo}|${issue_number}|${local_path}|${base}|jira|${project_key}-${issue_number}"
58
+ return 0
59
+ fi
60
+ fi
61
+
62
+ # alias#number format (e.g., omc#123 or mywork#123)
50
63
  if [[ "$ref" =~ ^([a-zA-Z][a-zA-Z0-9_-]*)#([0-9]+)$ ]]; then
51
64
  alias="${BASH_REMATCH[1]}"
52
65
  number="${BASH_REMATCH[2]}"
@@ -55,11 +68,22 @@ psm_parse_ref() {
55
68
  project_info=$(psm_get_project "$alias")
56
69
  if [[ $? -eq 0 ]]; then
57
70
  IFS='|' read -r repo local_path base <<< "$project_info"
58
- # Determine type from context (default to issue, caller specifies)
59
- echo "ref|$alias|$repo|$number|$local_path|$base"
71
+ local provider
72
+ provider=$(psm_get_project_provider "$alias")
73
+ local provider_ref=""
74
+
75
+ if [[ "$provider" == "jira" ]]; then
76
+ local jira_proj
77
+ jira_proj=$(psm_get_project_jira_project "$alias")
78
+ provider_ref="${jira_proj}-${number}"
79
+ else
80
+ provider_ref="${repo}#${number}"
81
+ fi
82
+
83
+ echo "ref|$alias|$repo|$number|$local_path|$base|$provider|$provider_ref"
60
84
  return 0
61
85
  else
62
- echo "error|Unknown project alias: $alias|||"
86
+ echo "error|Unknown project alias: $alias|||||||"
63
87
  return 1
64
88
  fi
65
89
  fi
@@ -72,7 +96,7 @@ psm_parse_ref() {
72
96
  if [[ -n "$alias" ]]; then
73
97
  IFS='|' read -r _ local_path base <<< "$(psm_get_project "$alias")"
74
98
  fi
75
- echo "ref|${alias:-}|$repo|$number|${local_path:-}|$base"
99
+ echo "ref|${alias:-}|$repo|$number|${local_path:-}|$base|github|${repo}#${number}"
76
100
  return 0
77
101
  fi
78
102
 
@@ -88,11 +112,11 @@ psm_parse_ref() {
88
112
  alias=$(psm_find_alias_for_repo "$repo")
89
113
  fi
90
114
  fi
91
- echo "ref|${alias:-}|${repo:-}|$number|${local_path:-}|$base"
115
+ echo "ref|${alias:-}|${repo:-}|$number|${local_path:-}|$base|github|${repo:+${repo}#${number}}"
92
116
  return 0
93
117
  fi
94
118
 
95
- echo "error|Cannot parse reference: $ref|||"
119
+ echo "error|Cannot parse reference: $ref||||||"
96
120
  return 1
97
121
  }
98
122
 
@@ -103,7 +127,7 @@ psm_find_alias_for_repo() {
103
127
  return 1
104
128
  fi
105
129
 
106
- jq -r ".aliases | to_entries[] | select(.value.repo == \"$target_repo\") | .key" "$PSM_PROJECTS" | head -1
130
+ jq -r --arg r "$target_repo" '.aliases | to_entries[] | select(.value.repo == $r) | .key' "$PSM_PROJECTS" | head -1
107
131
  }
108
132
 
109
133
  # Sanitize a string for use in filenames/session names
@@ -119,3 +143,33 @@ psm_slugify() {
119
143
  local max_len="${2:-30}"
120
144
  echo "$title" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//' | sed 's/-$//' | head -c "$max_len"
121
145
  }
146
+
147
+ # Check if input matches a configured Jira project
148
+ # Usage: psm_detect_jira_key "PROJ-123"
149
+ # Returns: alias|project_key|issue_number OR exits 1
150
+ psm_detect_jira_key() {
151
+ local input="$1"
152
+
153
+ # Must match PROJ-123 pattern (uppercase project, dash, digits)
154
+ if [[ ! "$input" =~ ^([A-Z][A-Z0-9]*)-([0-9]+)$ ]]; then
155
+ return 1
156
+ fi
157
+
158
+ local project_prefix="${BASH_REMATCH[1]}"
159
+ local issue_number="${BASH_REMATCH[2]}"
160
+
161
+ # Verify this project prefix exists in config
162
+ if [[ ! -f "$PSM_PROJECTS" ]]; then
163
+ return 1
164
+ fi
165
+
166
+ local matching_alias
167
+ matching_alias=$(jq -r --arg p "$project_prefix" '.aliases | to_entries[] | select(.value.jira_project == $p) | .key' "$PSM_PROJECTS" | head -1)
168
+
169
+ if [[ -n "$matching_alias" ]]; then
170
+ echo "${matching_alias}|${project_prefix}|${issue_number}"
171
+ return 0
172
+ fi
173
+
174
+ return 1
175
+ }
@@ -0,0 +1,52 @@
1
+ #!/bin/bash
2
+ # PSM GitHub Provider
3
+
4
+ provider_github_available() {
5
+ command -v gh &> /dev/null
6
+ }
7
+
8
+ provider_github_detect_ref() {
9
+ local ref="$1"
10
+ # Matches github URLs or owner/repo#num patterns
11
+ [[ "$ref" =~ ^https://github\.com/ ]] || [[ "$ref" =~ ^[a-zA-Z0-9_-]+/[a-zA-Z0-9_.-]+#[0-9]+$ ]]
12
+ }
13
+
14
+ provider_github_fetch_pr() {
15
+ local pr_number="$1"
16
+ local repo="$2"
17
+ gh pr view "$pr_number" --repo "$repo" --json number,title,author,headRefName,baseRefName,body,url 2>/dev/null
18
+ }
19
+
20
+ provider_github_fetch_issue() {
21
+ local issue_number="$1"
22
+ local repo="$2"
23
+ gh issue view "$issue_number" --repo "$repo" --json number,title,body,labels,url 2>/dev/null
24
+ }
25
+
26
+ provider_github_pr_merged() {
27
+ local pr_number="$1"
28
+ local repo="$2"
29
+ local merged
30
+ merged=$(gh pr view "$pr_number" --repo "$repo" --json merged 2>/dev/null | jq -r '.merged')
31
+ [[ "$merged" == "true" ]]
32
+ }
33
+
34
+ provider_github_issue_closed() {
35
+ local issue_number="$1"
36
+ local repo="$2"
37
+ local closed
38
+ closed=$(gh issue view "$issue_number" --repo "$repo" --json closed 2>/dev/null | jq -r '.closed')
39
+ [[ "$closed" == "true" ]]
40
+ }
41
+
42
+ provider_github_clone_url() {
43
+ local repo="$1"
44
+
45
+ # Validate owner/repo format
46
+ if [[ ! "$repo" =~ ^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$ ]]; then
47
+ echo "error|Invalid repository format: $repo" >&2
48
+ return 1
49
+ fi
50
+
51
+ echo "https://github.com/${repo}.git"
52
+ }
@@ -0,0 +1,76 @@
1
+ #!/bin/bash
2
+ # PSM Provider Interface
3
+ # Each provider implements: _available, _detect_ref, _fetch_issue, _issue_closed,
4
+ # _fetch_pr (optional), _pr_merged (optional), _clone_url
5
+
6
+ # List available providers
7
+ provider_list() {
8
+ echo "github jira"
9
+ }
10
+
11
+ # Allowlist of valid providers
12
+ readonly VALID_PROVIDERS="github jira"
13
+
14
+ # Check if a provider is available (CLI installed)
15
+ # Usage: provider_available "github"
16
+ provider_available() {
17
+ local provider="$1"
18
+
19
+ # Validate provider against allowlist
20
+ if ! echo "$VALID_PROVIDERS" | grep -qw "$provider"; then
21
+ echo "error|Invalid provider: $provider" >&2
22
+ return 1
23
+ fi
24
+
25
+ "provider_${provider}_available"
26
+ }
27
+
28
+ # Dispatch to provider function
29
+ # Usage: provider_call "github" "fetch_issue" "123" "owner/repo"
30
+ provider_call() {
31
+ local provider="$1"
32
+ local func="$2"
33
+ shift 2
34
+
35
+ # Validate provider against allowlist
36
+ if ! echo "$VALID_PROVIDERS" | grep -qw "$provider"; then
37
+ echo "error|Invalid provider: $provider" >&2
38
+ return 1
39
+ fi
40
+
41
+ # Validate function name (alphanumeric and underscore only)
42
+ if [[ ! "$func" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]]; then
43
+ echo "error|Invalid function name: $func" >&2
44
+ return 1
45
+ fi
46
+
47
+ "provider_${provider}_${func}" "$@"
48
+ }
49
+
50
+ # Detect provider from reference (with config validation)
51
+ # Usage: provider_detect_from_ref "PROJ-123"
52
+ # Returns: provider name or empty
53
+ provider_detect_from_ref() {
54
+ local ref="$1"
55
+
56
+ # Check Jira pattern first (config-validated)
57
+ if psm_detect_jira_key "$ref" >/dev/null 2>&1; then
58
+ echo "jira"
59
+ return 0
60
+ fi
61
+
62
+ # GitHub URL patterns
63
+ if [[ "$ref" =~ ^https://github\.com/ ]]; then
64
+ echo "github"
65
+ return 0
66
+ fi
67
+
68
+ # owner/repo#num pattern -> GitHub
69
+ if [[ "$ref" =~ ^[a-zA-Z0-9_-]+/[a-zA-Z0-9_.-]+#[0-9]+$ ]]; then
70
+ echo "github"
71
+ return 0
72
+ fi
73
+
74
+ # Default
75
+ echo "github"
76
+ }
@@ -0,0 +1,79 @@
1
+ #!/bin/bash
2
+ # PSM Jira Provider
3
+ # Uses `jira` CLI (https://github.com/ankitpokhrel/jira-cli)
4
+
5
+ provider_jira_available() {
6
+ command -v jira &> /dev/null
7
+ }
8
+
9
+ provider_jira_detect_ref() {
10
+ local ref="$1"
11
+ # Config-validated detection only
12
+ psm_detect_jira_key "$ref" >/dev/null 2>&1
13
+ }
14
+
15
+ provider_jira_fetch_issue() {
16
+ local issue_key="$1" # e.g., "PROJ-123"
17
+ # Note: second arg (repo) is ignored for Jira
18
+ jira issue view "$issue_key" --output json 2>/dev/null
19
+ }
20
+
21
+ provider_jira_issue_closed() {
22
+ local issue_key="$1"
23
+ local status_category
24
+ status_category=$(jira issue view "$issue_key" --output json 2>/dev/null | jq -r '.fields.status.statusCategory.key')
25
+ # Jira status categories: "new", "indeterminate", "done"
26
+ [[ "$status_category" == "done" ]]
27
+ }
28
+
29
+ # Jira has no PRs - return error
30
+ provider_jira_fetch_pr() {
31
+ echo '{"error": "Jira does not support pull requests"}' >&2
32
+ return 1
33
+ }
34
+
35
+ provider_jira_pr_merged() {
36
+ return 1 # Always false - Jira has no PRs
37
+ }
38
+
39
+ provider_jira_clone_url() {
40
+ local alias="$1"
41
+ # For Jira, we need to get clone_url from config
42
+ # First try explicit clone_url, then fall back to repo as GitHub
43
+ local clone_url
44
+ clone_url=$(psm_get_project_clone_url "$alias")
45
+ if [[ -n "$clone_url" ]]; then
46
+ echo "$clone_url"
47
+ return 0
48
+ fi
49
+
50
+ local repo
51
+ repo=$(psm_get_project_repo "$alias")
52
+ if [[ -n "$repo" ]]; then
53
+ echo "https://github.com/${repo}.git"
54
+ return 0
55
+ fi
56
+
57
+ echo "error: No clone_url or repo configured for alias '$alias'" >&2
58
+ return 1
59
+ }
60
+
61
+ # Parse Jira reference into components
62
+ # Input: "PROJ-123" or "mywork#123"
63
+ # Output: Extended format for session creation
64
+ provider_jira_parse_ref() {
65
+ local ref="$1"
66
+ local jira_info
67
+
68
+ # Try direct PROJ-123 pattern
69
+ if jira_info=$(psm_detect_jira_key "$ref"); then
70
+ IFS='|' read -r alias project_key issue_number <<< "$jira_info"
71
+ local project_info
72
+ project_info=$(psm_get_project "$alias")
73
+ IFS='|' read -r repo local_path base <<< "$project_info"
74
+ echo "issue|${alias}|${repo}|${issue_number}|${local_path}|${base}|jira|${project_key}-${issue_number}"
75
+ return 0
76
+ fi
77
+
78
+ return 1
79
+ }