@orderful/droid 0.42.0 → 0.43.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/plugin.json +2 -0
- package/CHANGELOG.md +12 -0
- package/dist/bin/droid.js +183 -16
- package/dist/commands/repos.d.ts.map +1 -1
- package/dist/commands/tui/views/ReposManagementScreen.d.ts.map +1 -1
- package/dist/commands/tui/views/ReposViewerScreen.d.ts.map +1 -1
- package/dist/lib/types.d.ts +2 -0
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/tools/meeting/.claude-plugin/plugin.json +1 -1
- package/dist/tools/meeting/TOOL.yaml +1 -1
- package/dist/tools/meeting/skills/meeting/SKILL.md +15 -7
- package/dist/tools/release/.claude-plugin/plugin.json +22 -0
- package/dist/tools/release/TOOL.yaml +21 -0
- package/dist/tools/release/commands/release.md +28 -0
- package/dist/tools/release/skills/release/SKILL.md +73 -0
- package/dist/tools/release/skills/release/references/templates.md +83 -0
- package/dist/tools/release/skills/release/references/workflows.md +129 -0
- package/package.json +1 -1
- package/src/commands/repos.ts +29 -0
- package/src/commands/tui/views/ReposManagementScreen.tsx +177 -16
- package/src/commands/tui/views/ReposViewerScreen.tsx +5 -0
- package/src/lib/types.ts +2 -0
- package/src/tools/meeting/.claude-plugin/plugin.json +1 -1
- package/src/tools/meeting/TOOL.yaml +1 -1
- package/src/tools/meeting/skills/meeting/SKILL.md +15 -7
- package/src/tools/release/.claude-plugin/plugin.json +22 -0
- package/src/tools/release/TOOL.yaml +21 -0
- package/src/tools/release/commands/release.md +28 -0
- package/src/tools/release/skills/release/SKILL.md +73 -0
- package/src/tools/release/skills/release/references/templates.md +83 -0
- package/src/tools/release/skills/release/references/workflows.md +129 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Release Templates
|
|
2
|
+
|
|
3
|
+
Slack mrkdwn and PR body templates for release notifications.
|
|
4
|
+
|
|
5
|
+
## Slack Messages
|
|
6
|
+
|
|
7
|
+
All Slack messages are posted via `droid integrations slack post`. Format as Slack mrkdwn (not GitHub markdown).
|
|
8
|
+
|
|
9
|
+
### Release Started
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
:rocket: *Release started — {repo_name}*
|
|
13
|
+
|
|
14
|
+
*Risk:* {risk_emoji} {risk_level}
|
|
15
|
+
*PR:* <{pr_url}|#{pr_number}>
|
|
16
|
+
*Branch:* `{release_branch}` → `{production_branch}`
|
|
17
|
+
|
|
18
|
+
{pr_summary}
|
|
19
|
+
|
|
20
|
+
Posted with :droid:
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Risk emojis:
|
|
24
|
+
- Low Risk: `:large_green_circle:`
|
|
25
|
+
- High Risk: `:warning:`
|
|
26
|
+
|
|
27
|
+
### Release Complete
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
:white_check_mark: *Release complete — {repo_name}*
|
|
31
|
+
|
|
32
|
+
*PR:* <{pr_url}|#{pr_number}>
|
|
33
|
+
*Branch:* `{release_branch}` → `{production_branch}`
|
|
34
|
+
|
|
35
|
+
Posted with :droid:
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Release PR Body
|
|
41
|
+
|
|
42
|
+
Used when creating the release PR via `gh pr create --body`.
|
|
43
|
+
|
|
44
|
+
```markdown
|
|
45
|
+
### {repo_name} Release
|
|
46
|
+
|
|
47
|
+
**Risk:** [{risk_level}]
|
|
48
|
+
|
|
49
|
+
**When:** Imminent
|
|
50
|
+
|
|
51
|
+
**PRs included:**
|
|
52
|
+
{pr_list}
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
Created with :robot: [droid](https://github.com/Orderful/droid)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Where `{pr_list}` is a bulleted list of merged PRs:
|
|
60
|
+
```markdown
|
|
61
|
+
- #{number} {title} (@{author})
|
|
62
|
+
- #{number} {title} (@{author})
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Terminal Fallback
|
|
68
|
+
|
|
69
|
+
When Slack is not configured, print a plain-text version to terminal:
|
|
70
|
+
|
|
71
|
+
### Release Started (terminal)
|
|
72
|
+
```
|
|
73
|
+
Release started — {repo_name}
|
|
74
|
+
Risk: {risk_level}
|
|
75
|
+
PR: {pr_url}
|
|
76
|
+
Branch: {release_branch} -> {production_branch}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Release Complete (terminal)
|
|
80
|
+
```
|
|
81
|
+
Release complete — {repo_name}
|
|
82
|
+
PR: {pr_url}
|
|
83
|
+
```
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Release Workflows
|
|
2
|
+
|
|
3
|
+
Detailed step-by-step procedures for each `/release` subcommand. All commands use `gh` CLI directly.
|
|
4
|
+
|
|
5
|
+
## Common Setup (every command)
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# 1. Read config
|
|
9
|
+
SLACK_CHANNEL=$(droid config --get tools.release.slack_channel)
|
|
10
|
+
# Default to #release-management if not set
|
|
11
|
+
SLACK_CHANNEL="${SLACK_CHANNEL:-#release-management}"
|
|
12
|
+
|
|
13
|
+
# 2. Get repos and filter for release repos (those with release_branch set)
|
|
14
|
+
droid config --get repos
|
|
15
|
+
# Parse JSON output — release repos have release_branch defined
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Detect repo from cwd or ask user. Extract `release_branch`, `production_branch` (default: `master`), and the GitHub `owner/repo` slug from the repo's remote.
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Get owner/repo from git remote
|
|
22
|
+
git -C {repo_path} remote get-url origin
|
|
23
|
+
# Parse to extract owner/repo (e.g., "Orderful/orderful-workspace")
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## `/release start [repo]`
|
|
29
|
+
|
|
30
|
+
Create a release PR and notify Slack.
|
|
31
|
+
|
|
32
|
+
### Steps
|
|
33
|
+
|
|
34
|
+
1. **Detect repo** — match cwd or ask user to pick from release repos
|
|
35
|
+
|
|
36
|
+
2. **Check for existing release PR:**
|
|
37
|
+
```bash
|
|
38
|
+
gh pr list --search "[RELEASE]" --state open --base {production_branch} --head {release_branch} --json number,title,url --repo {owner}/{repo}
|
|
39
|
+
```
|
|
40
|
+
If one exists, show it and ask: "A release PR already exists. Open it instead?"
|
|
41
|
+
|
|
42
|
+
3. **Generate release notes** — list PRs merged to the release branch since last release:
|
|
43
|
+
```bash
|
|
44
|
+
gh pr list --base {release_branch} --state merged --json number,title,author --limit 50 --repo {owner}/{repo}
|
|
45
|
+
```
|
|
46
|
+
Format as a bulleted list for the PR body.
|
|
47
|
+
|
|
48
|
+
4. **Ask risk level** — use AskUserQuestion:
|
|
49
|
+
- Low Risk (routine release, no breaking changes)
|
|
50
|
+
- High Risk (breaking changes, data migrations, or high-traffic feature)
|
|
51
|
+
|
|
52
|
+
5. **Create the release PR:**
|
|
53
|
+
```bash
|
|
54
|
+
gh pr create \
|
|
55
|
+
--base {production_branch} \
|
|
56
|
+
--head {release_branch} \
|
|
57
|
+
--title "[RELEASE] {repo_name}" \
|
|
58
|
+
--label "READY" \
|
|
59
|
+
--body "{release_pr_body}" \
|
|
60
|
+
--repo {owner}/{repo}
|
|
61
|
+
```
|
|
62
|
+
See `templates.md` for the PR body template.
|
|
63
|
+
|
|
64
|
+
6. **Post to Slack:**
|
|
65
|
+
```bash
|
|
66
|
+
node -e 'process.stdout.write(JSON.stringify({
|
|
67
|
+
channel: "{slack_channel}",
|
|
68
|
+
text: "{slack_message}",
|
|
69
|
+
unfurl_links: false
|
|
70
|
+
}))' | droid integrations slack post
|
|
71
|
+
```
|
|
72
|
+
See `templates.md` for the Slack message template (release started).
|
|
73
|
+
|
|
74
|
+
7. **Confirm to user** — show PR URL and Slack post confirmation.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## `/release status`
|
|
79
|
+
|
|
80
|
+
Check release status across all configured release repos.
|
|
81
|
+
|
|
82
|
+
### Steps
|
|
83
|
+
|
|
84
|
+
1. **Get all release repos** from config
|
|
85
|
+
|
|
86
|
+
2. **For each release repo**, gather:
|
|
87
|
+
|
|
88
|
+
**Open release PRs:**
|
|
89
|
+
```bash
|
|
90
|
+
gh pr list --search "[RELEASE]" --state open --json number,title,url,statusCheckRollup --repo {owner}/{repo}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
3. **Format and display** as a summary:
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
{repo_name}
|
|
97
|
+
Release PR: #{number} — {CI status} (green/pending/failing)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
If no open release PR: "No active release"
|
|
101
|
+
If no release repos configured: "No release repos configured"
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## `/release complete [repo]`
|
|
106
|
+
|
|
107
|
+
Close out a release — notify Slack.
|
|
108
|
+
|
|
109
|
+
### Steps
|
|
110
|
+
|
|
111
|
+
1. **Detect repo** — match cwd or ask user
|
|
112
|
+
|
|
113
|
+
2. **Find the release PR:**
|
|
114
|
+
```bash
|
|
115
|
+
# Check merged first
|
|
116
|
+
gh pr list --search "[RELEASE]" --state merged --base {production_branch} --head {release_branch} --json number,title,url,mergedAt --limit 1 --repo {owner}/{repo}
|
|
117
|
+
```
|
|
118
|
+
If no merged PR found, check open:
|
|
119
|
+
```bash
|
|
120
|
+
gh pr list --search "[RELEASE]" --state open --base {production_branch} --head {release_branch} --json number,title,url --repo {owner}/{repo}
|
|
121
|
+
```
|
|
122
|
+
- If PR is still open (not merged): warn "Release PR #{number} is still open. Merge it first, or complete anyway?"
|
|
123
|
+
- If no PR found at all: warn "No release PR found. Post completion anyway?"
|
|
124
|
+
|
|
125
|
+
3. **Post to Slack** — release complete notification (see `templates.md`).
|
|
126
|
+
|
|
127
|
+
4. **Confirm to user:**
|
|
128
|
+
- "Release complete for `{repo_name}`"
|
|
129
|
+
- Show Slack message confirmation
|
package/package.json
CHANGED
package/src/commands/repos.ts
CHANGED
|
@@ -40,6 +40,10 @@ export async function reposListCommand(options?: ReposListOptions): Promise<void
|
|
|
40
40
|
if (repo.description) {
|
|
41
41
|
console.log(chalk.gray(` ${repo.description}`));
|
|
42
42
|
}
|
|
43
|
+
if (repo.release_branch) {
|
|
44
|
+
const prod = repo.production_branch || 'master';
|
|
45
|
+
console.log(chalk.gray(` Release: ${repo.release_branch} → ${prod}`));
|
|
46
|
+
}
|
|
43
47
|
console.log();
|
|
44
48
|
}
|
|
45
49
|
}
|
|
@@ -93,6 +97,29 @@ export async function reposAddCommand(
|
|
|
93
97
|
repoDescription = answers.description || undefined;
|
|
94
98
|
}
|
|
95
99
|
|
|
100
|
+
// Optional release fields
|
|
101
|
+
const releaseAnswers = await inquirer.prompt([
|
|
102
|
+
{
|
|
103
|
+
type: 'input',
|
|
104
|
+
name: 'release_branch',
|
|
105
|
+
message: 'Release branch (optional, e.g. "dev"):',
|
|
106
|
+
},
|
|
107
|
+
]);
|
|
108
|
+
const releaseBranch = releaseAnswers.release_branch || undefined;
|
|
109
|
+
|
|
110
|
+
let productionBranch: string | undefined;
|
|
111
|
+
if (releaseBranch) {
|
|
112
|
+
const prodAnswers = await inquirer.prompt([
|
|
113
|
+
{
|
|
114
|
+
type: 'input',
|
|
115
|
+
name: 'production_branch',
|
|
116
|
+
message: 'Production branch:',
|
|
117
|
+
default: 'master',
|
|
118
|
+
},
|
|
119
|
+
]);
|
|
120
|
+
productionBranch = prodAnswers.production_branch || 'master';
|
|
121
|
+
}
|
|
122
|
+
|
|
96
123
|
// At this point, repoName and repoPath should be defined
|
|
97
124
|
if (!repoName || !repoPath) {
|
|
98
125
|
console.error(chalk.red('Name and path are required'));
|
|
@@ -103,6 +130,8 @@ export async function reposAddCommand(
|
|
|
103
130
|
name: repoName,
|
|
104
131
|
path: repoPath,
|
|
105
132
|
description: repoDescription,
|
|
133
|
+
...(releaseBranch && { release_branch: releaseBranch }),
|
|
134
|
+
...(productionBranch && { production_branch: productionBranch }),
|
|
106
135
|
};
|
|
107
136
|
|
|
108
137
|
addRepo(repo);
|
|
@@ -10,7 +10,15 @@ export interface ReposManagementScreenProps {
|
|
|
10
10
|
onCancel: () => void;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
type Screen =
|
|
13
|
+
type Screen =
|
|
14
|
+
| 'list'
|
|
15
|
+
| 'add-name'
|
|
16
|
+
| 'add-path'
|
|
17
|
+
| 'add-desc'
|
|
18
|
+
| 'repo-actions'
|
|
19
|
+
| 'edit-release-branch'
|
|
20
|
+
| 'edit-production-branch'
|
|
21
|
+
| 'confirm-delete';
|
|
14
22
|
|
|
15
23
|
export function ReposManagementScreen({ onComplete: _onComplete, onCancel }: ReposManagementScreenProps) {
|
|
16
24
|
const [repos, setRepos] = useState<RepoConfig[]>(() => getRepos());
|
|
@@ -23,8 +31,11 @@ export function ReposManagementScreen({ onComplete: _onComplete, onCancel }: Rep
|
|
|
23
31
|
const [newRepoPath, setNewRepoPath] = useState('');
|
|
24
32
|
const [newRepoDesc, setNewRepoDesc] = useState('');
|
|
25
33
|
|
|
26
|
-
//
|
|
27
|
-
const [
|
|
34
|
+
// Edit/delete repo state
|
|
35
|
+
const [activeRepo, setActiveRepo] = useState<RepoConfig | null>(null);
|
|
36
|
+
const [actionIndex, setActionIndex] = useState(0);
|
|
37
|
+
const [editReleaseBranch, setEditReleaseBranch] = useState('');
|
|
38
|
+
const [editProductionBranch, setEditProductionBranch] = useState('');
|
|
28
39
|
|
|
29
40
|
const handleAddRepo = () => {
|
|
30
41
|
if (!newRepoName || !newRepoPath) {
|
|
@@ -53,7 +64,7 @@ export function ReposManagementScreen({ onComplete: _onComplete, onCancel }: Rep
|
|
|
53
64
|
removeRepo(repoName);
|
|
54
65
|
setRepos(getRepos());
|
|
55
66
|
setMessage({ text: `Removed ${repoName}`, type: 'success' });
|
|
56
|
-
|
|
67
|
+
setActiveRepo(null);
|
|
57
68
|
setScreen('list');
|
|
58
69
|
|
|
59
70
|
// Adjust selected index if needed
|
|
@@ -62,6 +73,25 @@ export function ReposManagementScreen({ onComplete: _onComplete, onCancel }: Rep
|
|
|
62
73
|
}
|
|
63
74
|
};
|
|
64
75
|
|
|
76
|
+
const handleSaveReleaseBranches = () => {
|
|
77
|
+
if (!activeRepo) return;
|
|
78
|
+
|
|
79
|
+
const updated: RepoConfig = {
|
|
80
|
+
...activeRepo,
|
|
81
|
+
release_branch: editReleaseBranch || undefined,
|
|
82
|
+
production_branch: editReleaseBranch ? (editProductionBranch || 'master') : undefined,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
addRepo(updated);
|
|
86
|
+
setRepos(getRepos());
|
|
87
|
+
const label = editReleaseBranch
|
|
88
|
+
? `Set release: ${editReleaseBranch} → ${editProductionBranch || 'master'}`
|
|
89
|
+
: 'Cleared release branches';
|
|
90
|
+
setMessage({ text: `${activeRepo.name}: ${label}`, type: 'success' });
|
|
91
|
+
setActiveRepo(null);
|
|
92
|
+
setScreen('list');
|
|
93
|
+
};
|
|
94
|
+
|
|
65
95
|
// List screen navigation
|
|
66
96
|
useInput((input, key) => {
|
|
67
97
|
if (message) setMessage(null);
|
|
@@ -86,13 +116,42 @@ export function ReposManagementScreen({ onComplete: _onComplete, onCancel }: Rep
|
|
|
86
116
|
// "Add New Repo" selected
|
|
87
117
|
setScreen('add-name');
|
|
88
118
|
} else if (selectedIndex < repos.length) {
|
|
89
|
-
// Repo selected -
|
|
90
|
-
|
|
91
|
-
|
|
119
|
+
// Repo selected - show action menu
|
|
120
|
+
setActiveRepo(repos[selectedIndex]);
|
|
121
|
+
setActionIndex(0);
|
|
122
|
+
setScreen('repo-actions');
|
|
92
123
|
}
|
|
93
124
|
}
|
|
94
125
|
}, { isActive: screen === 'list' });
|
|
95
126
|
|
|
127
|
+
// Repo action menu
|
|
128
|
+
useInput((input, key) => {
|
|
129
|
+
if (key.escape) {
|
|
130
|
+
setActiveRepo(null);
|
|
131
|
+
setScreen('list');
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (key.upArrow) {
|
|
136
|
+
setActionIndex((prev) => Math.max(0, prev - 1));
|
|
137
|
+
}
|
|
138
|
+
if (key.downArrow) {
|
|
139
|
+
setActionIndex((prev) => Math.min(1, prev + 1));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (key.return && activeRepo) {
|
|
143
|
+
if (actionIndex === 0) {
|
|
144
|
+
// Edit release branches
|
|
145
|
+
setEditReleaseBranch(activeRepo.release_branch || '');
|
|
146
|
+
setEditProductionBranch(activeRepo.production_branch || '');
|
|
147
|
+
setScreen('edit-release-branch');
|
|
148
|
+
} else {
|
|
149
|
+
// Delete
|
|
150
|
+
setScreen('confirm-delete');
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}, { isActive: screen === 'repo-actions' });
|
|
154
|
+
|
|
96
155
|
// Add name input
|
|
97
156
|
useInput((input, key) => {
|
|
98
157
|
if (key.escape) {
|
|
@@ -117,14 +176,27 @@ export function ReposManagementScreen({ onComplete: _onComplete, onCancel }: Rep
|
|
|
117
176
|
}
|
|
118
177
|
}, { isActive: screen === 'add-desc' });
|
|
119
178
|
|
|
179
|
+
// Edit release branch input
|
|
180
|
+
useInput((input, key) => {
|
|
181
|
+
if (key.escape) {
|
|
182
|
+
setScreen('repo-actions');
|
|
183
|
+
}
|
|
184
|
+
}, { isActive: screen === 'edit-release-branch' });
|
|
185
|
+
|
|
186
|
+
// Edit production branch input
|
|
187
|
+
useInput((input, key) => {
|
|
188
|
+
if (key.escape) {
|
|
189
|
+
setScreen('edit-release-branch');
|
|
190
|
+
}
|
|
191
|
+
}, { isActive: screen === 'edit-production-branch' });
|
|
192
|
+
|
|
120
193
|
// Confirm delete
|
|
121
194
|
useInput((input, key) => {
|
|
122
195
|
if (key.escape || input === 'n') {
|
|
123
|
-
|
|
124
|
-
setScreen('list');
|
|
196
|
+
setScreen('repo-actions');
|
|
125
197
|
}
|
|
126
|
-
if (input === 'y' &&
|
|
127
|
-
handleDeleteRepo(
|
|
198
|
+
if (input === 'y' && activeRepo) {
|
|
199
|
+
handleDeleteRepo(activeRepo.name);
|
|
128
200
|
}
|
|
129
201
|
}, { isActive: screen === 'confirm-delete' });
|
|
130
202
|
|
|
@@ -206,14 +278,98 @@ export function ReposManagementScreen({ onComplete: _onComplete, onCancel }: Rep
|
|
|
206
278
|
);
|
|
207
279
|
}
|
|
208
280
|
|
|
209
|
-
if (screen === '
|
|
210
|
-
const
|
|
281
|
+
if (screen === 'repo-actions' && activeRepo) {
|
|
282
|
+
const actions = ['Edit release branches', 'Remove repository'];
|
|
283
|
+
return (
|
|
284
|
+
<Box flexDirection="column" padding={2}>
|
|
285
|
+
<Text color={colors.text} bold>{activeRepo.name}</Text>
|
|
286
|
+
<Box marginTop={0}>
|
|
287
|
+
<Text color={colors.textDim}>{activeRepo.path}</Text>
|
|
288
|
+
</Box>
|
|
289
|
+
{activeRepo.release_branch && (
|
|
290
|
+
<Box marginTop={0}>
|
|
291
|
+
<Text color={colors.textDim}>Release: {activeRepo.release_branch} → {activeRepo.production_branch || 'master'}</Text>
|
|
292
|
+
</Box>
|
|
293
|
+
)}
|
|
294
|
+
|
|
295
|
+
<Box flexDirection="column" marginTop={1}>
|
|
296
|
+
{actions.map((action, index) => (
|
|
297
|
+
<Box key={action}>
|
|
298
|
+
<Text
|
|
299
|
+
color={actionIndex === index ? colors.primary : (index === 1 ? colors.error : colors.text)}
|
|
300
|
+
bold={actionIndex === index}
|
|
301
|
+
>
|
|
302
|
+
{actionIndex === index ? '> ' : ' '}
|
|
303
|
+
{action}
|
|
304
|
+
</Text>
|
|
305
|
+
</Box>
|
|
306
|
+
))}
|
|
307
|
+
</Box>
|
|
308
|
+
|
|
309
|
+
<Box marginTop={2}>
|
|
310
|
+
<Text color={colors.textDim}>↑↓ navigate · enter select · esc back</Text>
|
|
311
|
+
</Box>
|
|
312
|
+
</Box>
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (screen === 'edit-release-branch' && activeRepo) {
|
|
317
|
+
return (
|
|
318
|
+
<Box flexDirection="column" padding={2}>
|
|
319
|
+
<Text color={colors.text} bold>Edit Release Branches — {activeRepo.name}</Text>
|
|
320
|
+
<Box marginTop={1}>
|
|
321
|
+
<Text color={colors.textDim}>Release branch (e.g. "dev", blank to clear): </Text>
|
|
322
|
+
<TextInput
|
|
323
|
+
value={editReleaseBranch}
|
|
324
|
+
onChange={setEditReleaseBranch}
|
|
325
|
+
onSubmit={() => {
|
|
326
|
+
if (editReleaseBranch) {
|
|
327
|
+
setScreen('edit-production-branch');
|
|
328
|
+
} else {
|
|
329
|
+
handleSaveReleaseBranches();
|
|
330
|
+
}
|
|
331
|
+
}}
|
|
332
|
+
placeholder={activeRepo.release_branch || 'dev'}
|
|
333
|
+
/>
|
|
334
|
+
</Box>
|
|
335
|
+
<Box marginTop={1}>
|
|
336
|
+
<Text color={colors.textDim}>enter {editReleaseBranch ? 'next' : 'clear & save'} · esc back</Text>
|
|
337
|
+
</Box>
|
|
338
|
+
</Box>
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (screen === 'edit-production-branch' && activeRepo) {
|
|
343
|
+
return (
|
|
344
|
+
<Box flexDirection="column" padding={2}>
|
|
345
|
+
<Text color={colors.text} bold>Edit Release Branches — {activeRepo.name}</Text>
|
|
346
|
+
<Box marginTop={1}>
|
|
347
|
+
<Text color={colors.textDim}>Release branch: </Text>
|
|
348
|
+
<Text color={colors.text}>{editReleaseBranch}</Text>
|
|
349
|
+
</Box>
|
|
350
|
+
<Box marginTop={1}>
|
|
351
|
+
<Text color={colors.textDim}>Production branch: </Text>
|
|
352
|
+
<TextInput
|
|
353
|
+
value={editProductionBranch}
|
|
354
|
+
onChange={setEditProductionBranch}
|
|
355
|
+
onSubmit={handleSaveReleaseBranches}
|
|
356
|
+
placeholder="master"
|
|
357
|
+
/>
|
|
358
|
+
</Box>
|
|
359
|
+
<Box marginTop={1}>
|
|
360
|
+
<Text color={colors.textDim}>enter save · esc back</Text>
|
|
361
|
+
</Box>
|
|
362
|
+
</Box>
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (screen === 'confirm-delete' && activeRepo) {
|
|
211
367
|
return (
|
|
212
368
|
<Box flexDirection="column" padding={2}>
|
|
213
369
|
<Text color={colors.text} bold>Remove Repository</Text>
|
|
214
370
|
<Box marginTop={1} flexDirection="column">
|
|
215
|
-
<Text color={colors.text}>{
|
|
216
|
-
<Text color={colors.textDim}>{
|
|
371
|
+
<Text color={colors.text}>{activeRepo.name}</Text>
|
|
372
|
+
<Text color={colors.textDim}>{activeRepo.path}</Text>
|
|
217
373
|
</Box>
|
|
218
374
|
<Box marginTop={2}>
|
|
219
375
|
<Text color={colors.error}>Remove this repository from the registry?</Text>
|
|
@@ -263,6 +419,11 @@ export function ReposManagementScreen({ onComplete: _onComplete, onCancel }: Rep
|
|
|
263
419
|
<Text color={colors.textDim}>{repo.description}</Text>
|
|
264
420
|
</Box>
|
|
265
421
|
)}
|
|
422
|
+
{repo.release_branch && (
|
|
423
|
+
<Box paddingLeft={2}>
|
|
424
|
+
<Text color={colors.textDim}>Release: {repo.release_branch} → {repo.production_branch || 'master'}</Text>
|
|
425
|
+
</Box>
|
|
426
|
+
)}
|
|
266
427
|
</Box>
|
|
267
428
|
))
|
|
268
429
|
)}
|
|
@@ -282,7 +443,7 @@ export function ReposManagementScreen({ onComplete: _onComplete, onCancel }: Rep
|
|
|
282
443
|
<Box marginTop={2}>
|
|
283
444
|
<Text color={colors.textDim}>
|
|
284
445
|
{repos.length > 0 && selectedIndex < repos.length
|
|
285
|
-
? 'enter
|
|
446
|
+
? 'enter edit · esc back'
|
|
286
447
|
: '↑↓ navigate · enter select · esc back'}
|
|
287
448
|
</Text>
|
|
288
449
|
</Box>
|
|
@@ -36,6 +36,11 @@ export function ReposViewerScreen({ onClose }: ReposViewerScreenProps) {
|
|
|
36
36
|
<Text color={colors.textDim}>{repo.description}</Text>
|
|
37
37
|
</Box>
|
|
38
38
|
)}
|
|
39
|
+
{repo.release_branch && (
|
|
40
|
+
<Box marginTop={0}>
|
|
41
|
+
<Text color={colors.textDim}>Release: {repo.release_branch} → {repo.production_branch || 'master'}</Text>
|
|
42
|
+
</Box>
|
|
43
|
+
)}
|
|
39
44
|
</Box>
|
|
40
45
|
))}
|
|
41
46
|
</Box>
|
package/src/lib/types.ts
CHANGED
|
@@ -53,6 +53,8 @@ export interface RepoConfig {
|
|
|
53
53
|
name: string;
|
|
54
54
|
path: string;
|
|
55
55
|
description?: string;
|
|
56
|
+
release_branch?: string; // e.g., "dev" — source branch for releases
|
|
57
|
+
production_branch?: string; // e.g., "master" — target branch for releases
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
// ToolConfig is flexible - each tool defines its own keys
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "droid-meeting",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Work with meeting notes, summaries, and transcripts. List recent meetings, search content, generate context-aware summaries, export to codex. Backed by Granola MCP.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Orderful",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: meeting
|
|
3
3
|
description: "Work with meeting notes, summaries, and transcripts. Use when user asks about meetings, wants to review notes, search decisions, or export to codex. User prompts like 'what did we discuss today', 'summarise the tech design review', 'export my meeting with Thea to codex'."
|
|
4
|
-
argument-hint: "[search {query} | summary {title} | summarize {title} | export {title} | decisions | last week]"
|
|
4
|
+
argument-hint: "[search {query} [--all] | summary {title} | summarize {title} | export {title} | decisions | last week]"
|
|
5
5
|
allowed-tools: [Read, Write, Edit, Glob, Grep, Bash(droid:*)]
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -41,7 +41,7 @@ If connected, proceed.
|
|
|
41
41
|
|---------|--------|
|
|
42
42
|
| `/meeting` | List recent meetings (this week) |
|
|
43
43
|
| `/meeting last week` | List meetings from last week |
|
|
44
|
-
| `/meeting search {query}` |
|
|
44
|
+
| `/meeting search {query}` | Search meetings (latest match by default; use `--all` for all matches) |
|
|
45
45
|
| `/meeting summary {title}` | Quick summary from Granola (fast, no context cost) |
|
|
46
46
|
| `/meeting summarize {title}` | Context-aware summary using transcript + loaded project/codex context |
|
|
47
47
|
| `/meeting export {title}` | Export meeting to codex |
|
|
@@ -61,12 +61,20 @@ Natural language is the primary interface. Users should not need these commands.
|
|
|
61
61
|
2. Call with `time_range`: `"this_week"` (default) or `"last_week"`
|
|
62
62
|
3. Present results as a table: title, date, participants
|
|
63
63
|
|
|
64
|
-
### Search (`/meeting search {query}`)
|
|
64
|
+
### Search (`/meeting search {query}` / `/meeting search {query} --all`)
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
**Default behaviour: latest match.** Recurring meetings (dailies, weeklies) return multiple hits. Searching all of them is rarely what the user wants — they usually mean the most recent one.
|
|
67
|
+
|
|
68
|
+
1. Use `ToolSearch` to load `mcp__granola__list_meetings` and `mcp__granola__query_granola_meetings`
|
|
69
|
+
2. Call `list_meetings` with `time_range: "this_week"` (or `"last_week"` / `"last_30_days"` if no matches)
|
|
70
|
+
3. Fuzzy-match the query against meeting titles in the list
|
|
71
|
+
4. **If multiple meetings match the query title:**
|
|
72
|
+
- **Without `--all`:** Scope the Granola query to only the **most recent** matching meeting ID (pass `document_ids`). Mention: "Showing results from {date}'s {title}. Use `--all` to search across all {N} matches."
|
|
73
|
+
- **With `--all`:** Query across all matching meeting IDs (pass all IDs in `document_ids`). Mention: "Searching across {N} {title} meetings."
|
|
74
|
+
5. **If only one meeting matches:** Query that single meeting (pass `document_ids`)
|
|
75
|
+
6. **If no title matches but query looks like a topic:** Fall back to a broad Granola query without `document_ids` (original behaviour)
|
|
76
|
+
7. Preserve citation links in the response
|
|
77
|
+
8. Present the response to the user
|
|
70
78
|
|
|
71
79
|
### Quick summary (`/meeting summary {title}`)
|
|
72
80
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "droid-release",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Release ceremony automation — create release PRs, check status, notify Slack.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Orderful",
|
|
7
|
+
"url": "https://github.com/orderful"
|
|
8
|
+
},
|
|
9
|
+
"repository": "https://github.com/orderful/droid",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"droid",
|
|
13
|
+
"ai",
|
|
14
|
+
"release"
|
|
15
|
+
],
|
|
16
|
+
"skills": [
|
|
17
|
+
"./skills/release/SKILL.md"
|
|
18
|
+
],
|
|
19
|
+
"commands": [
|
|
20
|
+
"./commands/release.md"
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
description: "Release ceremony automation — create release PRs, check status, notify Slack."
|
|
3
|
+
version: 0.1.0
|
|
4
|
+
status: alpha
|
|
5
|
+
|
|
6
|
+
includes:
|
|
7
|
+
skills:
|
|
8
|
+
- name: release
|
|
9
|
+
required: true
|
|
10
|
+
commands:
|
|
11
|
+
- name: release
|
|
12
|
+
is_alias: false
|
|
13
|
+
agents: []
|
|
14
|
+
|
|
15
|
+
dependencies: []
|
|
16
|
+
|
|
17
|
+
config_schema:
|
|
18
|
+
slack_channel:
|
|
19
|
+
type: string
|
|
20
|
+
description: "Slack channel for release notifications"
|
|
21
|
+
default: "#release-management"
|