@orderful/droid 0.38.0 → 0.39.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 +10 -0
- package/README.md +17 -0
- package/dist/bin/droid.js +136 -3
- package/dist/commands/auth.d.ts +3 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/lib/secrets.d.ts +7 -0
- package/dist/lib/secrets.d.ts.map +1 -0
- package/dist/tools/status-update/.claude-plugin/plugin.json +22 -0
- package/dist/tools/status-update/TOOL.yaml +21 -0
- package/dist/tools/status-update/commands/status-update.md +27 -0
- package/dist/tools/status-update/skills/status-update/SKILL.md +253 -0
- package/dist/tools/status-update/skills/status-update/references/formatting.md +203 -0
- package/package.json +1 -1
- package/src/bin/droid.ts +19 -0
- package/src/commands/auth.ts +150 -0
- package/src/lib/secrets.ts +12 -0
- package/src/tools/status-update/.claude-plugin/plugin.json +22 -0
- package/src/tools/status-update/TOOL.yaml +21 -0
- package/src/tools/status-update/commands/status-update.md +27 -0
- package/src/tools/status-update/skills/status-update/SKILL.md +253 -0
- package/src/tools/status-update/skills/status-update/references/formatting.md +203 -0
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"./src/tools/droid/skills/droid-bootstrap/SKILL.md",
|
|
25
25
|
"./src/tools/plan/skills/plan/SKILL.md",
|
|
26
26
|
"./src/tools/project/skills/project/SKILL.md",
|
|
27
|
+
"./src/tools/status-update/skills/status-update/SKILL.md",
|
|
27
28
|
"./src/tools/tech-design/skills/tech-design/SKILL.md",
|
|
28
29
|
"./src/tools/wrapup/skills/wrapup/SKILL.md"
|
|
29
30
|
],
|
|
@@ -36,6 +37,7 @@
|
|
|
36
37
|
"./src/tools/droid/commands/setup.md",
|
|
37
38
|
"./src/tools/plan/commands/plan.md",
|
|
38
39
|
"./src/tools/project/commands/project.md",
|
|
40
|
+
"./src/tools/status-update/commands/status-update.md",
|
|
39
41
|
"./src/tools/tech-design/commands/tech-design.md",
|
|
40
42
|
"./src/tools/wrapup/commands/wrapup.md"
|
|
41
43
|
],
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @orderful/droid
|
|
2
2
|
|
|
3
|
+
## 0.39.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#240](https://github.com/Orderful/droid/pull/240) [`d78f9d9`](https://github.com/Orderful/droid/commit/d78f9d9fa7fc787fd2db7863aa947cde9f0d9cf0) Thanks [@theabuitendyk](https://github.com/theabuitendyk)! - Add status-update tool and auth commands
|
|
8
|
+
- `/status-update` skill for posting project status updates to Slack
|
|
9
|
+
- `droid auth slack` command for per-user OAuth setup
|
|
10
|
+
- `droid auth status` command to check credentials
|
|
11
|
+
- Supports channel posting, crossposting, and canvas log updates
|
|
12
|
+
|
|
3
13
|
## 0.38.0
|
|
4
14
|
|
|
5
15
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -115,6 +115,7 @@ Browse available tools, see what's installed, and manage everything from one pla
|
|
|
115
115
|
| **code-review** | Multi-agent code review with specialized checkers | alpha |
|
|
116
116
|
| **tech-design** | Three-document approach for technical designs | beta |
|
|
117
117
|
| **wrapup** | Session wrap-up that captures decisions to persistent docs | alpha |
|
|
118
|
+
| **status-update** | Generate and post project status updates to Slack | beta |
|
|
118
119
|
|
|
119
120
|
### Comments
|
|
120
121
|
|
|
@@ -178,6 +179,20 @@ Capture session context before ending:
|
|
|
178
179
|
|
|
179
180
|
Extracts decisions, learnings, and open items to project files and brain docs.
|
|
180
181
|
|
|
182
|
+
### Status Update
|
|
183
|
+
|
|
184
|
+
Post project status updates to Slack:
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
/status-update # Generate and post status update
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Pulls context from codex projects and Jira, formats with phase checklists, posts to Slack (or prints to terminal). Requires one-time setup:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
droid auth slack # Set up Slack OAuth (per user)
|
|
194
|
+
```
|
|
195
|
+
|
|
181
196
|
## Configuration
|
|
182
197
|
|
|
183
198
|
Config lives in `~/.droid/`:
|
|
@@ -233,6 +248,8 @@ While the TUI is the primary interface, direct commands are available:
|
|
|
233
248
|
| `droid uninstall <tool>` | Remove a tool |
|
|
234
249
|
| `droid update` | Update droid and tools |
|
|
235
250
|
| `droid config` | View/edit config |
|
|
251
|
+
| `droid auth` | Show authentication status |
|
|
252
|
+
| `droid auth slack` | Set up Slack OAuth for status updates |
|
|
236
253
|
| `droid exec <skill> <script>` | Run a skill's deterministic script |
|
|
237
254
|
|
|
238
255
|
## Development
|
package/dist/bin/droid.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/bin/droid.ts
|
|
4
4
|
import { program } from "commander";
|
|
5
|
-
import
|
|
5
|
+
import chalk12 from "chalk";
|
|
6
6
|
|
|
7
7
|
// src/commands/setup.ts
|
|
8
8
|
import inquirer from "inquirer";
|
|
@@ -4748,6 +4748,135 @@ async function reposGetCommand(name) {
|
|
|
4748
4748
|
console.log(JSON.stringify(repo, null, 2));
|
|
4749
4749
|
}
|
|
4750
4750
|
|
|
4751
|
+
// src/commands/auth.ts
|
|
4752
|
+
import inquirer5 from "inquirer";
|
|
4753
|
+
import chalk11 from "chalk";
|
|
4754
|
+
import { execSync as execSync6 } from "child_process";
|
|
4755
|
+
|
|
4756
|
+
// src/lib/secrets.ts
|
|
4757
|
+
function hasSlackToken() {
|
|
4758
|
+
return !!process.env.SLACK_USER_TOKEN;
|
|
4759
|
+
}
|
|
4760
|
+
|
|
4761
|
+
// src/commands/auth.ts
|
|
4762
|
+
var SLACK_SCOPES = "chat:write,canvases:write";
|
|
4763
|
+
var REDIRECT_URI = "https://localhost:9876/callback";
|
|
4764
|
+
function getShellConfig() {
|
|
4765
|
+
const shell = process.env.SHELL || "/bin/zsh";
|
|
4766
|
+
if (shell.includes("zsh")) return "~/.zshrc";
|
|
4767
|
+
if (shell.includes("bash")) return "~/.bashrc";
|
|
4768
|
+
if (shell.includes("fish")) return "~/.config/fish/config.fish";
|
|
4769
|
+
return "~/.profile";
|
|
4770
|
+
}
|
|
4771
|
+
async function authSlackCommand() {
|
|
4772
|
+
console.log(chalk11.bold("\n\u{1F510} Slack Authentication Setup\n"));
|
|
4773
|
+
if (hasSlackToken()) {
|
|
4774
|
+
console.log(chalk11.green("\u2713 SLACK_USER_TOKEN is already set in your environment\n"));
|
|
4775
|
+
const { reauth } = await inquirer5.prompt([
|
|
4776
|
+
{
|
|
4777
|
+
type: "confirm",
|
|
4778
|
+
name: "reauth",
|
|
4779
|
+
message: "Re-authenticate anyway?",
|
|
4780
|
+
default: false
|
|
4781
|
+
}
|
|
4782
|
+
]);
|
|
4783
|
+
if (!reauth) {
|
|
4784
|
+
return;
|
|
4785
|
+
}
|
|
4786
|
+
console.log("");
|
|
4787
|
+
}
|
|
4788
|
+
const clientId = process.env.SLACK_CLIENT_ID;
|
|
4789
|
+
const clientSecret = process.env.SLACK_CLIENT_SECRET;
|
|
4790
|
+
if (!clientId || !clientSecret) {
|
|
4791
|
+
const shellConfig = getShellConfig();
|
|
4792
|
+
console.log(chalk11.yellow("Missing Slack app credentials.\n"));
|
|
4793
|
+
console.log(chalk11.gray('Get Client ID and Client Secret from 1Password ("Droid Slack App")'));
|
|
4794
|
+
console.log(chalk11.gray(`and add to your ${shellConfig}:
|
|
4795
|
+
`));
|
|
4796
|
+
console.log(chalk11.white(' export SLACK_CLIENT_ID="your-client-id"'));
|
|
4797
|
+
console.log(chalk11.white(' export SLACK_CLIENT_SECRET="your-client-secret"\n'));
|
|
4798
|
+
console.log(chalk11.gray("Then reload and run this again:\n"));
|
|
4799
|
+
console.log(chalk11.white(` source ${shellConfig} && droid auth slack
|
|
4800
|
+
`));
|
|
4801
|
+
return;
|
|
4802
|
+
}
|
|
4803
|
+
const authorizeUrl = `https://slack.com/oauth/v2/authorize?client_id=${clientId}&user_scope=${SLACK_SCOPES}&redirect_uri=${encodeURIComponent(REDIRECT_URI)}`;
|
|
4804
|
+
console.log(chalk11.yellow("Step 1: Authorize in Slack\n"));
|
|
4805
|
+
console.log(chalk11.gray(' Go to the "Droid Setup" canvas in #rnd-updates and click "Install Droid Slack App"'));
|
|
4806
|
+
console.log(chalk11.gray(" Or open this URL:\n"));
|
|
4807
|
+
console.log(chalk11.cyan(` ${authorizeUrl}
|
|
4808
|
+
`));
|
|
4809
|
+
console.log(chalk11.gray(" After clicking Allow, you'll be redirected to a URL that won't load."));
|
|
4810
|
+
console.log(chalk11.gray(' Copy the "code" parameter from the URL bar.\n'));
|
|
4811
|
+
const { code } = await inquirer5.prompt([
|
|
4812
|
+
{
|
|
4813
|
+
type: "input",
|
|
4814
|
+
name: "code",
|
|
4815
|
+
message: "Paste the code:",
|
|
4816
|
+
validate: (input) => input.length > 0 || "Code is required"
|
|
4817
|
+
}
|
|
4818
|
+
]);
|
|
4819
|
+
console.log(chalk11.yellow("\nStep 2: Exchanging code for token...\n"));
|
|
4820
|
+
try {
|
|
4821
|
+
const response = execSync6(
|
|
4822
|
+
`curl -s -X POST https://slack.com/api/oauth.v2.access -d "client_id=${clientId}" -d "client_secret=${clientSecret}" -d "code=${code}" -d "redirect_uri=${REDIRECT_URI}"`,
|
|
4823
|
+
{ encoding: "utf-8" }
|
|
4824
|
+
);
|
|
4825
|
+
const result = JSON.parse(response);
|
|
4826
|
+
if (!result.ok) {
|
|
4827
|
+
console.log(chalk11.red(`\u2717 Slack API error: ${result.error}`));
|
|
4828
|
+
if (result.error === "invalid_code") {
|
|
4829
|
+
console.log(chalk11.gray(" The code may have expired. Try again with a fresh code."));
|
|
4830
|
+
}
|
|
4831
|
+
return;
|
|
4832
|
+
}
|
|
4833
|
+
const token = result.authed_user?.access_token;
|
|
4834
|
+
if (!token) {
|
|
4835
|
+
console.log(chalk11.red("\u2717 No user token in response"));
|
|
4836
|
+
console.log(chalk11.gray(" Response:"), JSON.stringify(result, null, 2));
|
|
4837
|
+
return;
|
|
4838
|
+
}
|
|
4839
|
+
const shellConfig = getShellConfig();
|
|
4840
|
+
console.log(chalk11.green("\u2713 Got token!\n"));
|
|
4841
|
+
console.log(chalk11.yellow("Step 3: Save the token\n"));
|
|
4842
|
+
console.log(chalk11.gray(` Add to your ${shellConfig}:
|
|
4843
|
+
`));
|
|
4844
|
+
console.log(chalk11.white(` export SLACK_USER_TOKEN="${token}"
|
|
4845
|
+
`));
|
|
4846
|
+
console.log(chalk11.gray(" Then reload your shell:\n"));
|
|
4847
|
+
console.log(chalk11.white(` source ${shellConfig}
|
|
4848
|
+
`));
|
|
4849
|
+
} catch (error) {
|
|
4850
|
+
console.log(chalk11.red("\u2717 Failed to exchange code for token"));
|
|
4851
|
+
if (error instanceof Error) {
|
|
4852
|
+
console.log(chalk11.gray(` ${error.message}`));
|
|
4853
|
+
}
|
|
4854
|
+
}
|
|
4855
|
+
}
|
|
4856
|
+
async function authStatusCommand() {
|
|
4857
|
+
console.log(chalk11.bold("\n\u{1F510} Auth Status\n"));
|
|
4858
|
+
const clientId = process.env.SLACK_CLIENT_ID;
|
|
4859
|
+
const clientSecret = process.env.SLACK_CLIENT_SECRET;
|
|
4860
|
+
const hasCredentials = clientId && clientSecret;
|
|
4861
|
+
if (hasCredentials) {
|
|
4862
|
+
console.log(chalk11.green("\u2713 Slack app credentials configured"));
|
|
4863
|
+
} else {
|
|
4864
|
+
console.log(chalk11.yellow("\u2717 Slack app credentials missing"));
|
|
4865
|
+
console.log(chalk11.gray(" Run: droid auth slack"));
|
|
4866
|
+
}
|
|
4867
|
+
if (hasSlackToken()) {
|
|
4868
|
+
const token = process.env.SLACK_USER_TOKEN;
|
|
4869
|
+
const masked = token.slice(0, 10) + "..." + token.slice(-4);
|
|
4870
|
+
console.log(chalk11.green("\u2713 Slack user token configured"));
|
|
4871
|
+
console.log(chalk11.gray(` Token: ${masked}`));
|
|
4872
|
+
} else {
|
|
4873
|
+
console.log(chalk11.yellow("\u2717 Slack user token missing"));
|
|
4874
|
+
if (hasCredentials) {
|
|
4875
|
+
console.log(chalk11.gray(" Run: droid auth slack"));
|
|
4876
|
+
}
|
|
4877
|
+
}
|
|
4878
|
+
}
|
|
4879
|
+
|
|
4751
4880
|
// src/bin/droid.ts
|
|
4752
4881
|
var version = getVersion();
|
|
4753
4882
|
program.name("droid").description("Droid, teaching your AI new tricks").version(version);
|
|
@@ -4767,12 +4896,16 @@ program.command("uninstall <tool>").description("Uninstall a tool").action(unins
|
|
|
4767
4896
|
program.command("update").description("Update droid and installed tools").option("--tools", "Only update tools").option("--cli", "Only update the CLI").argument("[tool]", "Update a specific tool").action(updateCommand);
|
|
4768
4897
|
program.command("tui").description("Launch interactive TUI dashboard").action(tuiCommand);
|
|
4769
4898
|
program.command("exec <tool> <script>").description("Execute a tool script").argument("[args...]", "Arguments to pass to the script").allowUnknownOption().action(execCommand);
|
|
4899
|
+
var auth = program.command("auth").description("Set up authentication for external services");
|
|
4900
|
+
auth.command("slack").description("Set up Slack authentication for status updates").action(authSlackCommand);
|
|
4901
|
+
auth.command("status").description("Show authentication status").action(authStatusCommand);
|
|
4902
|
+
auth.action(authStatusCommand);
|
|
4770
4903
|
if (configExists()) {
|
|
4771
4904
|
const config = loadConfig();
|
|
4772
4905
|
const newPlatforms = syncNewPlatforms(config);
|
|
4773
4906
|
if (newPlatforms.length > 0) {
|
|
4774
|
-
console.log(
|
|
4775
|
-
console.log(
|
|
4907
|
+
console.log(chalk12.green(`\u2713 Detected new platform(s): ${newPlatforms.join(", ")}`));
|
|
4908
|
+
console.log(chalk12.gray(" Synced installed tools to new platform(s)\n"));
|
|
4776
4909
|
saveConfig(config);
|
|
4777
4910
|
}
|
|
4778
4911
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AAoBA,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAsGtD;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAyBvD"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secret/token helpers - reads from environment variables only
|
|
3
|
+
* Users manage their own tokens in shell config (e.g., ~/.zshrc)
|
|
4
|
+
*/
|
|
5
|
+
export declare function getSlackToken(): string | undefined;
|
|
6
|
+
export declare function hasSlackToken(): boolean;
|
|
7
|
+
//# sourceMappingURL=secrets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secrets.d.ts","sourceRoot":"","sources":["../../src/lib/secrets.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,wBAAgB,aAAa,IAAI,MAAM,GAAG,SAAS,CAElD;AAED,wBAAgB,aAAa,IAAI,OAAO,CAEvC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "droid-status-update",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Generate and post project status updates. Aggregates context from codex projects, Jira epics, and manual input. Posts to Slack or prints to terminal.",
|
|
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
|
+
"status-update"
|
|
15
|
+
],
|
|
16
|
+
"skills": [
|
|
17
|
+
"./skills/status-update/SKILL.md"
|
|
18
|
+
],
|
|
19
|
+
"commands": [
|
|
20
|
+
"./commands/status-update.md"
|
|
21
|
+
]
|
|
22
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: status-update
|
|
2
|
+
description: "Generate and post project status updates. Aggregates context from codex projects, Jira epics, and manual input. Posts to Slack or prints to terminal."
|
|
3
|
+
version: 0.1.0
|
|
4
|
+
status: beta
|
|
5
|
+
|
|
6
|
+
includes:
|
|
7
|
+
skills:
|
|
8
|
+
- name: status-update
|
|
9
|
+
required: true
|
|
10
|
+
commands:
|
|
11
|
+
- name: status-update
|
|
12
|
+
is_alias: false
|
|
13
|
+
agents: []
|
|
14
|
+
|
|
15
|
+
dependencies: []
|
|
16
|
+
|
|
17
|
+
config_schema:
|
|
18
|
+
default_crosspost_channel:
|
|
19
|
+
type: string
|
|
20
|
+
description: Default channel for cross-posting updates (e.g., rnd-updates)
|
|
21
|
+
required: false
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: status-update
|
|
3
|
+
description: "Generate and post project status updates. Pulls from codex, Jira, and manual input. Posts to Slack or prints to terminal."
|
|
4
|
+
argument-hint: "[{project}]"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /status-update
|
|
8
|
+
|
|
9
|
+
**User invoked:** `/status-update $ARGUMENTS`
|
|
10
|
+
|
|
11
|
+
**Your task:** Invoke the **status-update skill** with these arguments.
|
|
12
|
+
|
|
13
|
+
## Examples
|
|
14
|
+
|
|
15
|
+
- `/status-update` → Prompt for project or use active project
|
|
16
|
+
- `/status-update partnership-automation` → Generate update for specific project
|
|
17
|
+
- `/status-update --no-slack` → Generate update but print to terminal instead of posting
|
|
18
|
+
|
|
19
|
+
## Quick Reference
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
/status-update # Interactive - prompts for everything
|
|
23
|
+
/status-update {project} # Use codex project for metadata
|
|
24
|
+
/status-update --no-slack # Output to terminal instead of Slack
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
See the **status-update skill** for complete documentation.
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: status-update
|
|
3
|
+
description: "Generate and post project status updates. Aggregates context from codex projects, Jira epics, and manual input. Posts to Slack or prints to terminal."
|
|
4
|
+
argument-hint: "[{project}]"
|
|
5
|
+
allowed-tools:
|
|
6
|
+
[
|
|
7
|
+
Read,
|
|
8
|
+
Glob,
|
|
9
|
+
Grep,
|
|
10
|
+
Bash(git:*),
|
|
11
|
+
Bash(curl:*),
|
|
12
|
+
Bash(droid:*),
|
|
13
|
+
]
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Status Update Skill
|
|
17
|
+
|
|
18
|
+
Generate formatted project status updates from multiple sources and post to Slack (or print to terminal).
|
|
19
|
+
|
|
20
|
+
## Data Sources (All Optional)
|
|
21
|
+
|
|
22
|
+
| Source | When available | What it provides |
|
|
23
|
+
|--------|----------------|------------------|
|
|
24
|
+
| **Codex project** | If project name matches a codex project | Metadata (slack, jira, links) + git history |
|
|
25
|
+
| **Jira MCP** | If jira.epic configured + MCP connected | Ticket status for in-flight/up-next drafts |
|
|
26
|
+
| **Manual input** | Always | User-provided status, phase, bullets |
|
|
27
|
+
|
|
28
|
+
## Output Destinations
|
|
29
|
+
|
|
30
|
+
| Destination | When available | Fallback |
|
|
31
|
+
|-------------|----------------|----------|
|
|
32
|
+
| **Slack** | If slack.channel configured + SLACK_USER_TOKEN set | Print to terminal |
|
|
33
|
+
|
|
34
|
+
## Procedure
|
|
35
|
+
|
|
36
|
+
### 1. Resolve Project Context
|
|
37
|
+
|
|
38
|
+
**If project name provided:**
|
|
39
|
+
|
|
40
|
+
1. Check if codex is configured:
|
|
41
|
+
```bash
|
|
42
|
+
droid config --get tools.codex
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
2. If codex configured, look for matching project:
|
|
46
|
+
```bash
|
|
47
|
+
ls {codex_repo}/projects/ | grep -i {project}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
3. If found, read CONTEXT.md frontmatter:
|
|
51
|
+
- `title` - Project name
|
|
52
|
+
- `slack.channel` - Primary Slack channel
|
|
53
|
+
- `slack.crosspost_channel` - Optional crosspost channel
|
|
54
|
+
- `slack.canvas_id` - Optional canvas for update log
|
|
55
|
+
- `jira.epic` - Optional Jira epic key
|
|
56
|
+
- `links.prd`, `links.tech_design`, `links.figma` - Optional doc links
|
|
57
|
+
|
|
58
|
+
**If no project name provided:**
|
|
59
|
+
|
|
60
|
+
1. Check for active project in conversation context
|
|
61
|
+
2. If none, ask: "Which project is this update for?"
|
|
62
|
+
3. If user provides a name, attempt to match to codex project
|
|
63
|
+
4. If no match, continue without codex metadata (prompt for details manually)
|
|
64
|
+
|
|
65
|
+
**If no codex match:**
|
|
66
|
+
|
|
67
|
+
Prompt user for:
|
|
68
|
+
- Project name (for display)
|
|
69
|
+
- Slack channel (optional - skip to print to terminal)
|
|
70
|
+
- Jira epic key (optional)
|
|
71
|
+
|
|
72
|
+
### 2. Generate Draft Content
|
|
73
|
+
|
|
74
|
+
Attempt to generate draft bullets from available sources.
|
|
75
|
+
|
|
76
|
+
#### Source A: Codex Git History (if codex project found)
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
git -C {codex_repo} log --oneline --since="7 days ago" -- projects/{project}/
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Parse commits to understand recent activity:
|
|
83
|
+
- DECISIONS.md changes → decisions made
|
|
84
|
+
- New artifacts → meetings, documents produced
|
|
85
|
+
- CONTEXT.md changes → project evolution
|
|
86
|
+
- PRD.md/TECH-DESIGN.md changes → documentation progress
|
|
87
|
+
|
|
88
|
+
Generate human-readable draft bullets from the git history.
|
|
89
|
+
|
|
90
|
+
#### Source B: Jira MCP (if configured)
|
|
91
|
+
|
|
92
|
+
**Check MCP availability:**
|
|
93
|
+
```
|
|
94
|
+
mcp__atlassian__getAccessibleAtlassianResources
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
If available and `jira.epic` is set, run two JQL queries:
|
|
98
|
+
|
|
99
|
+
1. **Recently completed + in progress:**
|
|
100
|
+
```
|
|
101
|
+
parent = {jira.epic} AND (status changed to Done AFTER -7d OR statusCategory in ("In Progress"))
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
2. **Up next:**
|
|
105
|
+
```
|
|
106
|
+
parent = {jira.epic} AND statusCategory = "To Do"
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Categorise tickets:
|
|
110
|
+
|
|
111
|
+
| Jira Status | Category |
|
|
112
|
+
|-------------|----------|
|
|
113
|
+
| Done (last 7 days) | In flight (completed) |
|
|
114
|
+
| In Progress, In Review, In QA | In flight (active) |
|
|
115
|
+
| To Do, Backlog | Up next |
|
|
116
|
+
|
|
117
|
+
#### Combine Sources
|
|
118
|
+
|
|
119
|
+
If multiple sources available:
|
|
120
|
+
- Deduplicate where codex change and Jira ticket describe the same work
|
|
121
|
+
- Prefer Jira summaries for ticket-tracked work
|
|
122
|
+
- Include codex-only changes (decisions, meetings) not in Jira
|
|
123
|
+
|
|
124
|
+
**Present draft to user** for review/editing before finalising.
|
|
125
|
+
|
|
126
|
+
### 3. Gather Update Content
|
|
127
|
+
|
|
128
|
+
Ask user for (pre-fill from drafts where available):
|
|
129
|
+
|
|
130
|
+
1. **Status** - One of:
|
|
131
|
+
- `on_track` (green) - Everything proceeding as planned
|
|
132
|
+
- `at_risk` (yellow) - Some concerns or blockers emerging
|
|
133
|
+
- `blocked` (red) - Cannot proceed without resolution
|
|
134
|
+
|
|
135
|
+
2. **Current phase** - One of:
|
|
136
|
+
- `discovery` - Research and requirements gathering
|
|
137
|
+
- `post_discovery_feedback` - Incorporating discovery feedback
|
|
138
|
+
- `design` - Prototype and design work
|
|
139
|
+
- `post_design_feedback` - Incorporating design feedback
|
|
140
|
+
- `prd` - Writing PRD
|
|
141
|
+
- `tech_design` - Writing Tech Design
|
|
142
|
+
- `development` - Active implementation
|
|
143
|
+
- `qa` - QA & Testing
|
|
144
|
+
- `release` - Released to production
|
|
145
|
+
|
|
146
|
+
3. **In flight this week** - Bullet points (from draft or manual)
|
|
147
|
+
|
|
148
|
+
4. **Up next** - Bullet points (from draft or manual)
|
|
149
|
+
|
|
150
|
+
### 4. Format Message
|
|
151
|
+
|
|
152
|
+
See `references/formatting.md` for detailed formatting rules.
|
|
153
|
+
|
|
154
|
+
**Structure:**
|
|
155
|
+
```
|
|
156
|
+
*Status:* {status_emoji} *{status_text}*
|
|
157
|
+
|
|
158
|
+
*Where we're at*
|
|
159
|
+
{phase_checklist}
|
|
160
|
+
|
|
161
|
+
*In flight this week*
|
|
162
|
+
• {bullet 1}
|
|
163
|
+
• {bullet 2}
|
|
164
|
+
|
|
165
|
+
*Up next*
|
|
166
|
+
• {bullet 1}
|
|
167
|
+
• {bullet 2}
|
|
168
|
+
|
|
169
|
+
{links_footer if configured}
|
|
170
|
+
|
|
171
|
+
Posted with help from :droid:
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Add ` & :codex:` to footer if codex project was used.
|
|
175
|
+
|
|
176
|
+
### 5. Output
|
|
177
|
+
|
|
178
|
+
#### If Slack configured (channel + SLACK_USER_TOKEN):
|
|
179
|
+
|
|
180
|
+
**Post to primary channel:**
|
|
181
|
+
```bash
|
|
182
|
+
curl -s -X POST https://slack.com/api/chat.postMessage \
|
|
183
|
+
-H "Authorization: Bearer $SLACK_USER_TOKEN" \
|
|
184
|
+
-H "Content-Type: application/json; charset=utf-8" \
|
|
185
|
+
-d '{
|
|
186
|
+
"channel": "{slack.channel}",
|
|
187
|
+
"text": "{formatted_message}",
|
|
188
|
+
"unfurl_links": false
|
|
189
|
+
}'
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Check response for `"ok": true`. Save `ts` and `channel` from response.
|
|
193
|
+
|
|
194
|
+
**Crosspost (if crosspost_channel configured):**
|
|
195
|
+
|
|
196
|
+
Post summary version with link to full update:
|
|
197
|
+
```
|
|
198
|
+
*{project_title}*
|
|
199
|
+
|
|
200
|
+
{status_line}
|
|
201
|
+
{current_phase_only}
|
|
202
|
+
|
|
203
|
+
{in_flight_section}
|
|
204
|
+
|
|
205
|
+
:link: <{message_url}|View full update>
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Message URL format: `https://orderful.slack.com/archives/{channel_id}/p{ts_without_dot}`
|
|
209
|
+
|
|
210
|
+
**Update canvas log (if canvas_id configured):**
|
|
211
|
+
```bash
|
|
212
|
+
curl -s -X POST https://slack.com/api/canvases.edit \
|
|
213
|
+
-H "Authorization: Bearer $SLACK_USER_TOKEN" \
|
|
214
|
+
-H "Content-Type: application/json; charset=utf-8" \
|
|
215
|
+
-d '{
|
|
216
|
+
"canvas_id": "{slack.canvas_id}",
|
|
217
|
+
"changes": [{
|
|
218
|
+
"operation": "insert_at_start",
|
|
219
|
+
"document_content": {
|
|
220
|
+
"type": "markdown",
|
|
221
|
+
"markdown": "### {date}\n{status} · {current_phase}\n[View update]({message_url})\n\n---"
|
|
222
|
+
}
|
|
223
|
+
}]
|
|
224
|
+
}'
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
**Confirm to user:**
|
|
228
|
+
- "Posted update to #{channel}"
|
|
229
|
+
- "Cross-posted to #{crosspost_channel}" (if applicable)
|
|
230
|
+
- "Added to canvas log" (if applicable)
|
|
231
|
+
|
|
232
|
+
#### If Slack NOT configured:
|
|
233
|
+
|
|
234
|
+
Print the formatted message to terminal.
|
|
235
|
+
|
|
236
|
+
Tell user: "Slack not configured. To post updates directly, add `slack.channel` to your project's CONTEXT.md and set `SLACK_USER_TOKEN` in your environment."
|
|
237
|
+
|
|
238
|
+
### Error Handling
|
|
239
|
+
|
|
240
|
+
| Error | Action |
|
|
241
|
+
|-------|--------|
|
|
242
|
+
| No SLACK_USER_TOKEN | Print to terminal, suggest setup |
|
|
243
|
+
| Slack API error | Show error, offer to print instead |
|
|
244
|
+
| Jira MCP not available | Skip Jira, continue with other sources |
|
|
245
|
+
| No codex project found | Continue with manual input |
|
|
246
|
+
|
|
247
|
+
## Configuration
|
|
248
|
+
|
|
249
|
+
No required configuration. Optional settings via `droid config`:
|
|
250
|
+
|
|
251
|
+
| Setting | Description |
|
|
252
|
+
|---------|-------------|
|
|
253
|
+
| `default_crosspost_channel` | Default channel for cross-posts if not in project metadata |
|