oh-my-claude-sisyphus 3.8.16 → 3.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.mcp.json +1 -1
- package/agents/analyst.md +41 -0
- package/agents/architect.md +45 -0
- package/agents/critic.md +42 -0
- package/agents/deep-executor.md +193 -0
- package/agents/planner.md +82 -0
- package/bridge/mcp-server.cjs +181 -181
- package/dist/__tests__/agent-registry.test.js +1 -1
- package/dist/__tests__/installer.test.js +8 -8
- package/dist/__tests__/installer.test.js.map +1 -1
- package/dist/__tests__/omc-tools-server.test.js +2 -2
- package/dist/__tests__/omc-tools-server.test.js.map +1 -1
- package/dist/__tests__/skills.test.js +5 -4
- package/dist/__tests__/skills.test.js.map +1 -1
- package/dist/agents/deep-executor.d.ts +15 -0
- package/dist/agents/deep-executor.d.ts.map +1 -0
- package/dist/agents/deep-executor.js +47 -0
- package/dist/agents/deep-executor.js.map +1 -0
- package/dist/agents/definitions.d.ts +15 -0
- package/dist/agents/definitions.d.ts.map +1 -1
- package/dist/agents/definitions.js +25 -0
- package/dist/agents/definitions.js.map +1 -1
- package/dist/agents/index.d.ts +1 -0
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/index.js +1 -0
- package/dist/agents/index.js.map +1 -1
- package/dist/cli/commands/doctor-conflicts.d.ts +55 -0
- package/dist/cli/commands/doctor-conflicts.d.ts.map +1 -0
- package/dist/cli/commands/doctor-conflicts.js +261 -0
- package/dist/cli/commands/doctor-conflicts.js.map +1 -0
- package/dist/cli/index.js +16 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/features/auto-update.d.ts +12 -0
- package/dist/features/auto-update.d.ts.map +1 -1
- package/dist/features/auto-update.js +4 -1
- package/dist/features/auto-update.js.map +1 -1
- package/dist/features/context-injector/types.d.ts +1 -1
- package/dist/features/context-injector/types.d.ts.map +1 -1
- package/dist/hooks/__tests__/bridge.test.d.ts +2 -0
- package/dist/hooks/__tests__/bridge.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/bridge.test.js +199 -0
- package/dist/hooks/__tests__/bridge.test.js.map +1 -0
- package/dist/hooks/beads-context/__tests__/index.test.d.ts +2 -0
- package/dist/hooks/beads-context/__tests__/index.test.d.ts.map +1 -0
- package/dist/hooks/beads-context/__tests__/index.test.js +150 -0
- package/dist/hooks/beads-context/__tests__/index.test.js.map +1 -0
- package/dist/hooks/beads-context/constants.d.ts +3 -0
- package/dist/hooks/beads-context/constants.d.ts.map +1 -0
- package/dist/hooks/beads-context/constants.js +35 -0
- package/dist/hooks/beads-context/constants.js.map +1 -0
- package/dist/hooks/beads-context/index.d.ts +21 -0
- package/dist/hooks/beads-context/index.d.ts.map +1 -0
- package/dist/hooks/beads-context/index.js +62 -0
- package/dist/hooks/beads-context/index.js.map +1 -0
- package/dist/hooks/beads-context/types.d.ts +7 -0
- package/dist/hooks/beads-context/types.d.ts.map +1 -0
- package/dist/hooks/beads-context/types.js +2 -0
- package/dist/hooks/beads-context/types.js.map +1 -0
- package/dist/hooks/bridge.d.ts +4 -0
- package/dist/hooks/bridge.d.ts.map +1 -1
- package/dist/hooks/bridge.js +76 -23
- package/dist/hooks/bridge.js.map +1 -1
- package/dist/hooks/clear-suggestions/constants.d.ts +54 -0
- package/dist/hooks/clear-suggestions/constants.d.ts.map +1 -0
- package/dist/hooks/clear-suggestions/constants.js +102 -0
- package/dist/hooks/clear-suggestions/constants.js.map +1 -0
- package/dist/hooks/clear-suggestions/index.d.ts +61 -0
- package/dist/hooks/clear-suggestions/index.d.ts.map +1 -0
- package/dist/hooks/clear-suggestions/index.js +282 -0
- package/dist/hooks/clear-suggestions/index.js.map +1 -0
- package/dist/hooks/clear-suggestions/triggers.d.ts +65 -0
- package/dist/hooks/clear-suggestions/triggers.d.ts.map +1 -0
- package/dist/hooks/clear-suggestions/triggers.js +222 -0
- package/dist/hooks/clear-suggestions/triggers.js.map +1 -0
- package/dist/hooks/clear-suggestions/types.d.ts +92 -0
- package/dist/hooks/clear-suggestions/types.d.ts.map +1 -0
- package/dist/hooks/clear-suggestions/types.js +9 -0
- package/dist/hooks/clear-suggestions/types.js.map +1 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +3 -0
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/permission-handler/index.d.ts.map +1 -1
- package/dist/hooks/permission-handler/index.js +3 -1
- package/dist/hooks/permission-handler/index.js.map +1 -1
- package/dist/hooks/setup/index.d.ts.map +1 -1
- package/dist/hooks/setup/index.js +12 -5
- package/dist/hooks/setup/index.js.map +1 -1
- package/dist/hooks/subagent-tracker/index.d.ts.map +1 -1
- package/dist/hooks/subagent-tracker/index.js +25 -9
- package/dist/hooks/subagent-tracker/index.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/installer/__tests__/claude-md-merge.test.d.ts +6 -0
- package/dist/installer/__tests__/claude-md-merge.test.d.ts.map +1 -0
- package/dist/installer/__tests__/claude-md-merge.test.js +220 -0
- package/dist/installer/__tests__/claude-md-merge.test.js.map +1 -0
- package/dist/installer/__tests__/safe-installer.test.d.ts +6 -0
- package/dist/installer/__tests__/safe-installer.test.d.ts.map +1 -0
- package/dist/installer/__tests__/safe-installer.test.js +172 -0
- package/dist/installer/__tests__/safe-installer.test.js.map +1 -0
- package/dist/installer/hooks.d.ts.map +1 -1
- package/dist/installer/hooks.js +3 -1
- package/dist/installer/hooks.js.map +1 -1
- package/dist/installer/index.d.ts +27 -1
- package/dist/installer/index.d.ts.map +1 -1
- package/dist/installer/index.js +209 -85
- package/dist/installer/index.js.map +1 -1
- package/dist/mcp/omc-tools-server.d.ts +1 -1
- package/dist/mcp/omc-tools-server.d.ts.map +1 -1
- package/dist/mcp/omc-tools-server.js +3 -3
- package/dist/mcp/omc-tools-server.js.map +1 -1
- package/dist/mcp/standalone-server.js +1 -1
- package/dist/mcp/standalone-server.js.map +1 -1
- package/dist/verification/tier-selector.d.ts +40 -0
- package/dist/verification/tier-selector.d.ts.map +1 -0
- package/dist/verification/tier-selector.js +95 -0
- package/dist/verification/tier-selector.js.map +1 -0
- package/dist/verification/tier-selector.test.d.ts +2 -0
- package/dist/verification/tier-selector.test.d.ts.map +1 -0
- package/dist/verification/tier-selector.test.js +282 -0
- package/dist/verification/tier-selector.test.js.map +1 -0
- package/docs/AGENTS.md +1 -1
- package/docs/CLAUDE.md +90 -378
- package/docs/partials/agent-tiers.md +165 -0
- package/docs/partials/features.md +131 -0
- package/docs/partials/mode-hierarchy.md +120 -0
- package/docs/partials/mode-selection-guide.md +82 -0
- package/docs/partials/verification-tiers.md +107 -0
- package/docs/shared/agent-tiers.md +165 -0
- package/docs/shared/features.md +131 -0
- package/docs/shared/mode-hierarchy.md +120 -0
- package/docs/shared/mode-selection-guide.md +82 -0
- package/docs/shared/verification-tiers.md +107 -0
- package/package.json +4 -3
- package/scripts/compose-docs.mjs +44 -0
- package/scripts/keyword-detector.mjs +13 -3
- package/skills/deep-executor/SKILL.md +50 -0
- package/skills/ecomode/SKILL.md +58 -103
- package/skills/omc-setup/SKILL.md +197 -20
- package/skills/plan/SKILL.md +62 -0
- package/skills/project-session-manager/SKILL.md +87 -4
- package/skills/project-session-manager/lib/config.sh +54 -5
- package/skills/project-session-manager/lib/parse.sh +65 -11
- package/skills/project-session-manager/lib/providers/github.sh +52 -0
- package/skills/project-session-manager/lib/providers/interface.sh +76 -0
- package/skills/project-session-manager/lib/providers/jira.sh +79 -0
- package/skills/project-session-manager/lib/session.sh +49 -12
- package/skills/project-session-manager/lib/worktree.sh +37 -4
- package/skills/project-session-manager/psm.sh +116 -51
- package/skills/ralph/SKILL.md +41 -27
- package/skills/ultrawork/SKILL.md +56 -66
- package/templates/hooks/keyword-detector.mjs +21 -13
- package/templates/hooks/lib/stdin.mjs +62 -0
- package/templates/hooks/persistent-mode.mjs +7 -8
- package/templates/hooks/post-tool-use.mjs +8 -10
- package/templates/hooks/pre-tool-use.mjs +9 -6
- package/templates/hooks/session-start.mjs +7 -8
|
@@ -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
|
-
|
|
375
|
-
- `
|
|
376
|
-
- `
|
|
377
|
-
- `
|
|
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[
|
|
52
|
-
local local_path=$(jq -r ".aliases[
|
|
53
|
-
local default_base=$(jq -r ".aliases[
|
|
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
|
-
|
|
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 "
|
|
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
|
-
#
|
|
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
|
-
|
|
59
|
-
|
|
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 ==
|
|
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
|
+
}
|
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# PSM Session Registry Management
|
|
3
3
|
|
|
4
|
-
#
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
# Lock file for atomic registry operations
|
|
5
|
+
PSM_LOCK_FILE="${PSM_DATA_DIR:-.psm}/.psm-lock"
|
|
6
|
+
|
|
7
|
+
# Wrapper for atomic operations with file locking
|
|
8
|
+
# Usage: psm_with_lock <command> [args...]
|
|
9
|
+
psm_with_lock() {
|
|
10
|
+
local timeout="${PSM_LOCK_TIMEOUT:-5}"
|
|
11
|
+
(
|
|
12
|
+
flock -w "$timeout" 200 || {
|
|
13
|
+
echo "error|Failed to acquire lock after ${timeout}s" >&2
|
|
14
|
+
return 1
|
|
15
|
+
}
|
|
16
|
+
"$@"
|
|
17
|
+
) 200>"$PSM_LOCK_FILE"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
# Internal: Add session to registry (must be called via psm_with_lock)
|
|
21
|
+
_psm_add_session_impl() {
|
|
7
22
|
local id="$1"
|
|
8
23
|
local type="$2"
|
|
9
24
|
local project="$3"
|
|
@@ -14,6 +29,8 @@ psm_add_session() {
|
|
|
14
29
|
local worktree="$8"
|
|
15
30
|
local source_repo="$9"
|
|
16
31
|
local metadata="${10:-{}}"
|
|
32
|
+
local provider="${11:-github}"
|
|
33
|
+
local provider_ref="${12:-}"
|
|
17
34
|
|
|
18
35
|
local now=$(date -Iseconds)
|
|
19
36
|
|
|
@@ -28,6 +45,8 @@ psm_add_session() {
|
|
|
28
45
|
--arg worktree "$worktree" \
|
|
29
46
|
--arg source "$source_repo" \
|
|
30
47
|
--arg now "$now" \
|
|
48
|
+
--arg provider "$provider" \
|
|
49
|
+
--arg provider_ref "$provider_ref" \
|
|
31
50
|
--argjson meta "$metadata" \
|
|
32
51
|
'.sessions[$id] = {
|
|
33
52
|
"id": $id,
|
|
@@ -42,21 +61,28 @@ psm_add_session() {
|
|
|
42
61
|
"created_at": $now,
|
|
43
62
|
"last_accessed": $now,
|
|
44
63
|
"state": "active",
|
|
64
|
+
"provider": $provider,
|
|
65
|
+
"provider_ref": $provider_ref,
|
|
45
66
|
"metadata": $meta
|
|
46
67
|
} | .stats.total_created += 1' \
|
|
47
68
|
"$PSM_SESSIONS" > "$tmp" && mv "$tmp" "$PSM_SESSIONS"
|
|
48
69
|
}
|
|
49
70
|
|
|
71
|
+
# Add session to registry (with file locking)
|
|
72
|
+
# Usage: psm_add_session <id> <type> <project> <ref> <branch> <base> <tmux> <worktree> <source_repo> <metadata_json> [provider] [provider_ref]
|
|
73
|
+
psm_add_session() {
|
|
74
|
+
psm_with_lock _psm_add_session_impl "$@"
|
|
75
|
+
}
|
|
76
|
+
|
|
50
77
|
# Get session by ID
|
|
51
78
|
# Usage: psm_get_session <id>
|
|
52
79
|
psm_get_session() {
|
|
53
80
|
local id="$1"
|
|
54
|
-
jq -r ".sessions[
|
|
81
|
+
jq -r --arg i "$id" '.sessions[$i] // empty' "$PSM_SESSIONS"
|
|
55
82
|
}
|
|
56
83
|
|
|
57
|
-
# Update session state
|
|
58
|
-
|
|
59
|
-
psm_update_session_state() {
|
|
84
|
+
# Internal: Update session state (must be called via psm_with_lock)
|
|
85
|
+
_psm_update_session_state_impl() {
|
|
60
86
|
local id="$1"
|
|
61
87
|
local state="$2"
|
|
62
88
|
local now=$(date -Iseconds)
|
|
@@ -69,9 +95,14 @@ psm_update_session_state() {
|
|
|
69
95
|
"$PSM_SESSIONS" > "$tmp" && mv "$tmp" "$PSM_SESSIONS"
|
|
70
96
|
}
|
|
71
97
|
|
|
72
|
-
#
|
|
73
|
-
# Usage:
|
|
74
|
-
|
|
98
|
+
# Update session state (with file locking)
|
|
99
|
+
# Usage: psm_update_session_state <id> <state>
|
|
100
|
+
psm_update_session_state() {
|
|
101
|
+
psm_with_lock _psm_update_session_state_impl "$@"
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# Internal: Remove session from registry (must be called via psm_with_lock)
|
|
105
|
+
_psm_remove_session_impl() {
|
|
75
106
|
local id="$1"
|
|
76
107
|
|
|
77
108
|
local tmp=$(mktemp)
|
|
@@ -80,13 +111,19 @@ psm_remove_session() {
|
|
|
80
111
|
"$PSM_SESSIONS" > "$tmp" && mv "$tmp" "$PSM_SESSIONS"
|
|
81
112
|
}
|
|
82
113
|
|
|
114
|
+
# Remove session from registry (with file locking)
|
|
115
|
+
# Usage: psm_remove_session <id>
|
|
116
|
+
psm_remove_session() {
|
|
117
|
+
psm_with_lock _psm_remove_session_impl "$@"
|
|
118
|
+
}
|
|
119
|
+
|
|
83
120
|
# List all sessions
|
|
84
121
|
# Usage: psm_list_sessions [project]
|
|
85
122
|
psm_list_sessions() {
|
|
86
123
|
local project="$1"
|
|
87
124
|
|
|
88
125
|
if [[ -n "$project" ]]; then
|
|
89
|
-
jq -r ".sessions | to_entries[] | select(.value.project ==
|
|
126
|
+
jq -r --arg p "$project" '.sessions | to_entries[] | select(.value.project == $p) | .value | "\(.id)|\(.type)|\(.state)|\(.worktree)"' "$PSM_SESSIONS"
|
|
90
127
|
else
|
|
91
128
|
jq -r '.sessions | to_entries[] | .value | "\(.id)|\(.type)|\(.state)|\(.worktree)"' "$PSM_SESSIONS"
|
|
92
129
|
fi
|
|
@@ -95,7 +132,7 @@ psm_list_sessions() {
|
|
|
95
132
|
# Get sessions by state
|
|
96
133
|
psm_get_sessions_by_state() {
|
|
97
134
|
local state="$1"
|
|
98
|
-
jq -r ".sessions | to_entries[] | select(.value.state ==
|
|
135
|
+
jq -r --arg s "$state" '.sessions | to_entries[] | select(.value.state == $s) | .value.id' "$PSM_SESSIONS"
|
|
99
136
|
}
|
|
100
137
|
|
|
101
138
|
# Get session count
|