claudius-core 0.9.3 → 0.9.6
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/agents/manager.md +3 -13
- package/.claude/agents/worker.md +72 -29
- package/.claude/rules/testing.md +7 -4
- package/.claude/scripts/merge-pr.sh +55 -24
- package/.claude/scripts/project-status.sh +8 -8
- package/.claude/skills/build/SKILL.md +3 -17
- package/.claude-plugin/marketplace.json +2 -2
- package/dist/lib/db.d.ts +30 -0
- package/dist/lib/db.d.ts.map +1 -0
- package/dist/lib/db.js +71 -0
- package/dist/lib/db.js.map +1 -0
- package/dist/lib/slack.d.ts +33 -0
- package/dist/lib/slack.d.ts.map +1 -0
- package/dist/lib/slack.js +79 -0
- package/dist/lib/slack.js.map +1 -0
- package/dist/studio/assets/{_basePickBy-CaRVkU_i.js → _basePickBy-R-uis1nJ.js} +1 -1
- package/dist/studio/assets/{_baseUniq-DB9CANVr.js → _baseUniq-DYva2fSG.js} +1 -1
- package/dist/studio/assets/{arc-DwNKwVLD.js → arc-BzeI-lxb.js} +1 -1
- package/dist/studio/assets/{architectureDiagram-VXUJARFQ-CDW4khKr.js → architectureDiagram-VXUJARFQ-DMa_o-aK.js} +1 -1
- package/dist/studio/assets/{blockDiagram-VD42YOAC-D6_LSve2.js → blockDiagram-VD42YOAC-BFN1vazA.js} +1 -1
- package/dist/studio/assets/{c4Diagram-YG6GDRKO-SFPE64AJ.js → c4Diagram-YG6GDRKO-BFDthHUP.js} +1 -1
- package/dist/studio/assets/channel-huxXjZrf.js +1 -0
- package/dist/studio/assets/{chunk-4BX2VUAB-BqHoEHlQ.js → chunk-4BX2VUAB-BcntFbLt.js} +1 -1
- package/dist/studio/assets/{chunk-55IACEB6-DkHQ5WBd.js → chunk-55IACEB6-sNY6b1p-.js} +1 -1
- package/dist/studio/assets/{chunk-B4BG7PRW-CW_kq6KT.js → chunk-B4BG7PRW-BLekgdES.js} +1 -1
- package/dist/studio/assets/{chunk-DI55MBZ5-DNDwEGQ5.js → chunk-DI55MBZ5-CZw9ICRq.js} +1 -1
- package/dist/studio/assets/{chunk-FMBD7UC4-5PX-ftxQ.js → chunk-FMBD7UC4-ntqmlvfQ.js} +1 -1
- package/dist/studio/assets/{chunk-QN33PNHL-QkQfQaqN.js → chunk-QN33PNHL-CE-WX1bS.js} +1 -1
- package/dist/studio/assets/{chunk-QZHKN3VN-Do2RvhJA.js → chunk-QZHKN3VN-GyZ2p2Bl.js} +1 -1
- package/dist/studio/assets/{chunk-TZMSLE5B-xiebau4j.js → chunk-TZMSLE5B--hsg0lEr.js} +1 -1
- package/dist/studio/assets/classDiagram-2ON5EDUG-ufBxprI3.js +1 -0
- package/dist/studio/assets/classDiagram-v2-WZHVMYZB-ufBxprI3.js +1 -0
- package/dist/studio/assets/clone-BRn04n6L.js +1 -0
- package/dist/studio/assets/{cose-bilkent-S5V4N54A-Apnpf-ki.js → cose-bilkent-S5V4N54A-C4C-DPXZ.js} +1 -1
- package/dist/studio/assets/{dagre-6UL2VRFP-BxyPIzB8.js → dagre-6UL2VRFP-1Bb2UhRi.js} +1 -1
- package/dist/studio/assets/{diagram-PSM6KHXK-24YoaI-e.js → diagram-PSM6KHXK-C-FzwLf5.js} +1 -1
- package/dist/studio/assets/{diagram-QEK2KX5R-lHpdi46q.js → diagram-QEK2KX5R-Bctxbrmx.js} +1 -1
- package/dist/studio/assets/{diagram-S2PKOQOG-9u1_ee6B.js → diagram-S2PKOQOG-3uep1mdW.js} +1 -1
- package/dist/studio/assets/{erDiagram-Q2GNP2WA-Dlmgb2ym.js → erDiagram-Q2GNP2WA-B_Db2yYe.js} +1 -1
- package/dist/studio/assets/{flowDiagram-NV44I4VS-5sj_GCzk.js → flowDiagram-NV44I4VS-Bzc-kwIX.js} +1 -1
- package/dist/studio/assets/{ganttDiagram-JELNMOA3-CUJF-slV.js → ganttDiagram-JELNMOA3-BXibfYz-.js} +1 -1
- package/dist/studio/assets/{gitGraphDiagram-V2S2FVAM-CKpDBk3b.js → gitGraphDiagram-V2S2FVAM-BLkjIgg1.js} +1 -1
- package/dist/studio/assets/{graph-BRYklN25.js → graph-B0Z_gmOm.js} +1 -1
- package/dist/studio/assets/{index-WEgtt1oI.js → index-Ddl_PR-h.js} +2 -2
- package/dist/studio/assets/{infoDiagram-HS3SLOUP-DKF6awtp.js → infoDiagram-HS3SLOUP-BeFdN5yb.js} +1 -1
- package/dist/studio/assets/{journeyDiagram-XKPGCS4Q-CgrhL8R0.js → journeyDiagram-XKPGCS4Q--icE_FdD.js} +1 -1
- package/dist/studio/assets/{kanban-definition-3W4ZIXB7-28MC-pF6.js → kanban-definition-3W4ZIXB7-BYGur8C7.js} +1 -1
- package/dist/studio/assets/{layout-gwYv6lHv.js → layout-B4VDGqHe.js} +1 -1
- package/dist/studio/assets/{linear-Dl48BTRW.js → linear-Bm1oIJ5Y.js} +1 -1
- package/dist/studio/assets/{mermaid.core-YPNEyw9M.js → mermaid.core-Bl28GW7R.js} +4 -4
- package/dist/studio/assets/{mindmap-definition-VGOIOE7T-CNO-62gT.js → mindmap-definition-VGOIOE7T-DC0Ch_L5.js} +1 -1
- package/dist/studio/assets/{pieDiagram-ADFJNKIX-DutNO8TU.js → pieDiagram-ADFJNKIX-Cfm6OLdr.js} +1 -1
- package/dist/studio/assets/{quadrantDiagram-AYHSOK5B-CyL-T3po.js → quadrantDiagram-AYHSOK5B-C68ftZIq.js} +1 -1
- package/dist/studio/assets/{requirementDiagram-UZGBJVZJ-Decs2Rps.js → requirementDiagram-UZGBJVZJ-yH30jAOj.js} +1 -1
- package/dist/studio/assets/{sankeyDiagram-TZEHDZUN-CXQHF2nL.js → sankeyDiagram-TZEHDZUN-BlEt27YS.js} +1 -1
- package/dist/studio/assets/{sequenceDiagram-WL72ISMW-C9cFuo_6.js → sequenceDiagram-WL72ISMW-CT2uLY3i.js} +1 -1
- package/dist/studio/assets/{stateDiagram-FKZM4ZOC-Dvs2TeCM.js → stateDiagram-FKZM4ZOC-DuMW3t9Q.js} +1 -1
- package/dist/studio/assets/stateDiagram-v2-4FDKWEC3-DjR-lF7e.js +1 -0
- package/dist/studio/assets/{timeline-definition-IT6M3QCI-BpsJlgf7.js → timeline-definition-IT6M3QCI-BGETR7lR.js} +1 -1
- package/dist/studio/assets/{treemap-GDKQZRPO-DVxRTPr1.js → treemap-GDKQZRPO-BO8oqrvF.js} +1 -1
- package/dist/studio/assets/{xychartDiagram-PRI3JC2R-QUfHK5a4.js → xychartDiagram-PRI3JC2R-DJ-F4Odf.js} +1 -1
- package/dist/studio/index.html +1 -1
- package/global/rules/global.md +2 -0
- package/package.json +1 -1
- package/dist/studio/assets/channel-h6J62PW8.js +0 -1
- package/dist/studio/assets/classDiagram-2ON5EDUG-D8lIj2-P.js +0 -1
- package/dist/studio/assets/classDiagram-v2-WZHVMYZB-D8lIj2-P.js +0 -1
- package/dist/studio/assets/clone-Chwjzlof.js +0 -1
- package/dist/studio/assets/stateDiagram-v2-4FDKWEC3-NKg7XNVy.js +0 -1
|
@@ -17,7 +17,7 @@ When asked who you are: "I am the Claudius manager. I route work, coordinate age
|
|
|
17
17
|
2. Decide solo (XS/S) vs team (M+) based on scope
|
|
18
18
|
3. Create feature branch from main — **unless already in a worktree** (see Branch Convention)
|
|
19
19
|
4. For solo: implement directly with TDD
|
|
20
|
-
5. For team: spawn developer via Agent tool, then
|
|
20
|
+
5. For team: spawn developer via Agent tool, then reviewer
|
|
21
21
|
6. **Validate before PR** — `bun test && bun run lint && bun run build` must all pass. Fix failures before creating PR. Never create a PR with known lint or build failures.
|
|
22
22
|
7. Create PR
|
|
23
23
|
8. Update issue checkboxes as criteria are met
|
|
@@ -43,19 +43,9 @@ Use the Agent tool with `subagent_type: "developer"`. Include in the prompt:
|
|
|
43
43
|
- The constraint: only implement what's asked, no scope creep
|
|
44
44
|
- **Reminder: developer must run `bun test`, `bun run lint`, and `bun run build` before pushing**
|
|
45
45
|
|
|
46
|
-
The developer runs in an isolated worktree, implements via TDD, validates (test + lint + build), and returns its output. After it completes, verify the developer reported all three validations passing, then
|
|
46
|
+
The developer runs in an isolated worktree, implements via TDD, validates (test + lint + build), and returns its output. After it completes, verify the developer reported all three validations passing, then create the PR: `gh pr create`.
|
|
47
47
|
|
|
48
|
-
**
|
|
49
|
-
Use the Agent tool with `subagent_type: "general-purpose"` and `agent: "code-simplifier"`. Include in the prompt:
|
|
50
|
-
- The git diff of all changes so far: output of `git diff main...HEAD`
|
|
51
|
-
- The original issue acceptance criteria
|
|
52
|
-
- Project conventions: contents of CLAUDE.md
|
|
53
|
-
- Instruction: reduce complexity, improve reuse, fix quality issues — without changing behavior or adding new features
|
|
54
|
-
- Constraint: only touch files already changed on this branch (visible in the diff above)
|
|
55
|
-
|
|
56
|
-
The simplifier is **non-blocking**: if it returns no changes, errors, or is unavailable, log the outcome and continue to the reviewer. Never let the simplifier halt the pipeline.
|
|
57
|
-
|
|
58
|
-
**Reviewer** (for M+ work, after simplifier completes and PR is created):
|
|
48
|
+
**Reviewer** (for M+ work, after PR is created):
|
|
59
49
|
Use the Agent tool with `subagent_type: "reviewer"`. Include in the prompt:
|
|
60
50
|
- The PR number to review
|
|
61
51
|
- Return structured verdict: approve / request-changes / block
|
package/.claude/agents/worker.md
CHANGED
|
@@ -41,7 +41,6 @@ gh pr list --search "Closes #<N> in:body" --state open --json number --limit 1
|
|
|
41
41
|
If no open PR found → stale claim → restore to ready:
|
|
42
42
|
```bash
|
|
43
43
|
gh issue edit <N> --remove-label "in-progress,claude" --add-label "ready"
|
|
44
|
-
bash .claude/scripts/project-status.sh <N> "Todo" "Queued"
|
|
45
44
|
```
|
|
46
45
|
|
|
47
46
|
## Guard Checks
|
|
@@ -63,26 +62,11 @@ tail -20 .claudius/job-runs.jsonl
|
|
|
63
62
|
Before picking new work, merge any open XS/S/M PRs with passing CI:
|
|
64
63
|
|
|
65
64
|
```bash
|
|
66
|
-
gh pr list --json number,title,headRefName,statusCheckRollup
|
|
65
|
+
gh pr list --json number,title,headRefName,statusCheckRollup --limit 20
|
|
66
|
+
bash .claude/scripts/merge-pr.sh <N>
|
|
67
|
+
gh issue close <issue-N> --comment "Closed by PR #<N>."
|
|
67
68
|
```
|
|
68
69
|
|
|
69
|
-
For each PR with all checks passing:
|
|
70
|
-
1. **Check mergeability** — if `mergeable` is `CONFLICTING`, try rebasing first:
|
|
71
|
-
```bash
|
|
72
|
-
gh pr view <N> --json mergeable --jq '.mergeable'
|
|
73
|
-
# If CONFLICTING → attempt rebase:
|
|
74
|
-
git fetch origin main
|
|
75
|
-
git checkout <branch>
|
|
76
|
-
git rebase origin/main
|
|
77
|
-
git push --force-with-lease
|
|
78
|
-
# If rebase fails: skip this PR, leave for human or next cycle
|
|
79
|
-
```
|
|
80
|
-
2. **Merge** — only if mergeable:
|
|
81
|
-
```bash
|
|
82
|
-
bash .claude/scripts/merge-pr.sh <N>
|
|
83
|
-
gh issue close <issue-N> --comment "Closed by PR #<N>."
|
|
84
|
-
```
|
|
85
|
-
|
|
86
70
|
## Step 2: Pick Top-3 Issues
|
|
87
71
|
|
|
88
72
|
```bash
|
|
@@ -100,9 +84,24 @@ Issues are filtered and scored with dependency awareness:
|
|
|
100
84
|
- **Codex issues excluded** — any issue with the `codex` label is skipped entirely
|
|
101
85
|
- **Blocked issues excluded** — any issue with open `blocked-by` relationships is removed from
|
|
102
86
|
candidates
|
|
87
|
+
- **Recent PR guard** — any issue that already had a PR created in the last 6 hours is skipped,
|
|
88
|
+
even if the PR was closed. This prevents duplicate PRs from retry storms.
|
|
103
89
|
- **Cluster momentum** — issues in partially-complete clusters get +15 priority boost
|
|
104
90
|
- **Standard scoring** — label priorities, scope size, goal alignment
|
|
105
91
|
|
|
92
|
+
**Check for recent PRs before claiming** — skip issues that already have PRs (open or recently closed):
|
|
93
|
+
```bash
|
|
94
|
+
for CANDIDATE in <issue-numbers>; do
|
|
95
|
+
RECENT_PR=$(gh pr list --repo "$(gh repo view --json nameWithOwner --jq '.nameWithOwner')" \
|
|
96
|
+
--search "Closes #$CANDIDATE in:body" --state all --json number,createdAt,state \
|
|
97
|
+
--jq "[.[] | select((.createdAt | fromdateiso8601) > (now - 21600))] | length" 2>/dev/null)
|
|
98
|
+
if [ "${RECENT_PR:-0}" -gt "0" ]; then
|
|
99
|
+
echo "Skipping #$CANDIDATE — PR created in last 6 hours"
|
|
100
|
+
continue
|
|
101
|
+
fi
|
|
102
|
+
done
|
|
103
|
+
```
|
|
104
|
+
|
|
106
105
|
**Check attempt count before claiming** — read the existing workpad (if any) to get prior attempt count. If at max, move to Blocked instead of claiming:
|
|
107
106
|
|
|
108
107
|
```bash
|
|
@@ -113,7 +112,13 @@ PRIOR_ATTEMPTS=$(gh issue view <N> --json comments \
|
|
|
113
112
|
if [ "${PRIOR_ATTEMPTS:-0}" -ge "$MAX_ATTEMPTS" ]; then
|
|
114
113
|
gh issue edit <N> --remove-label "ready" --add-label "blocked"
|
|
115
114
|
gh issue comment <N> --body "Claudius: exceeded ${MAX_ATTEMPTS} build attempts. Needs human review before retrying."
|
|
116
|
-
|
|
115
|
+
REPO_NAME=$(gh repo view --json nameWithOwner --jq '.nameWithOwner')
|
|
116
|
+
ITEM_ID=$(gh api graphql -f query='{user(login:"SharadKumar"){projectV2(number:6){items(first:100){nodes{id content{...on Issue{number repository{nameWithOwner}}}}}}}}' \
|
|
117
|
+
--jq ".data.user.projectV2.items.nodes[] | select(.content.number == <N> and .content.repository.nameWithOwner == \"$REPO_NAME\") | .id" 2>/dev/null | head -1)
|
|
118
|
+
[ -n "$ITEM_ID" ] && gh project item-edit --id "$ITEM_ID" \
|
|
119
|
+
--project-id "PVT_kwHOAFO-EM4BRTW5" \
|
|
120
|
+
--field-id "PVTSSF_lAHOAFO-EM4BRTW5zg_K3tE" \
|
|
121
|
+
--single-select-option-id "d239ddb3" 2>/dev/null || true
|
|
117
122
|
# skip this issue — continue to next candidate
|
|
118
123
|
fi
|
|
119
124
|
```
|
|
@@ -133,13 +138,26 @@ NEW_ATTEMPTS=$(( ${PRIOR_ATTEMPTS:-0} + 1 ))
|
|
|
133
138
|
bash .claude/scripts/workpad-upsert.sh <N> "🔄 Claimed — reading issue" "TBD" "- [ ] (loading)" "pending" "$NEW_ATTEMPTS"
|
|
134
139
|
```
|
|
135
140
|
|
|
136
|
-
**Update Project #6 → In Progress
|
|
141
|
+
**Update Project #6 Status → In Progress** — add to project if needed, then set status:
|
|
142
|
+
|
|
137
143
|
```bash
|
|
138
|
-
|
|
144
|
+
ISSUE_URL=$(gh issue view <N> --json url --jq '.url')
|
|
145
|
+
REPO_NAME=$(gh repo view --json nameWithOwner --jq '.nameWithOwner')
|
|
146
|
+
gh project item-add 6 --owner SharadKumar --url "$ISSUE_URL" 2>/dev/null || true
|
|
147
|
+
ITEM_ID=$(gh api graphql -f query='{user(login:"SharadKumar"){projectV2(number:6){items(first:100){nodes{id content{...on Issue{number repository{nameWithOwner}}}}}}}}' \
|
|
148
|
+
--jq ".data.user.projectV2.items.nodes[] | select(.content.number == <N> and .content.repository.nameWithOwner == \"$REPO_NAME\") | .id" 2>/dev/null | head -1)
|
|
149
|
+
[ -n "$ITEM_ID" ] && gh project item-edit --id "$ITEM_ID" \
|
|
150
|
+
--project-id "PVT_kwHOAFO-EM4BRTW5" \
|
|
151
|
+
--field-id "PVTSSF_lAHOAFO-EM4BRTW5zg_K3tE" \
|
|
152
|
+
--single-select-option-id "0d583361" 2>/dev/null || true
|
|
139
153
|
```
|
|
140
154
|
|
|
141
155
|
## Step 3: Develop in Parallel
|
|
142
156
|
|
|
157
|
+
**CRITICAL: One issue = one developer = one PR.** Never combine multiple issues into a single
|
|
158
|
+
developer agent or a single PR. Each issue gets its own isolated developer in its own worktree.
|
|
159
|
+
Violating this causes review failures, duplicate PRs, and retry storms.
|
|
160
|
+
|
|
143
161
|
Each developer runs in its own isolated worktree. Do NOT derive or provide branch names.
|
|
144
162
|
|
|
145
163
|
**Read agent config** from `.claudius/config.yaml` (default: `claude`):
|
|
@@ -153,11 +171,12 @@ process.stdout.write(c.agent?.command ?? 'claude');
|
|
|
153
171
|
```
|
|
154
172
|
|
|
155
173
|
**If `AGENT_COMMAND = "claude"` (default):**
|
|
156
|
-
Spawn one developer agent per issue using the `Agent` tool **concurrently**
|
|
174
|
+
Spawn **exactly one** developer agent per issue using the `Agent` tool **concurrently**
|
|
157
175
|
(all in one tool call message), using `subagent_type: "developer"`.
|
|
176
|
+
Each agent handles ONE issue only — never pass multiple issues to the same agent.
|
|
158
177
|
|
|
159
178
|
**If `AGENT_COMMAND = "codex"`:**
|
|
160
|
-
Spawn via Bash: `codex -p --model <agent.model> <agent.flags> "<prompt>" &` per issue.
|
|
179
|
+
Spawn via Bash: `codex -p --model <agent.model> <agent.flags> "<prompt>" &` — one per issue.
|
|
161
180
|
|
|
162
181
|
For each issue, each developer prompt must include:
|
|
163
182
|
- Full issue body and all acceptance criteria
|
|
@@ -178,7 +197,6 @@ Wait for all developer agents to complete before proceeding.
|
|
|
178
197
|
```bash
|
|
179
198
|
gh issue edit <N> --remove-label "in-progress,claude" --add-label "ready"
|
|
180
199
|
gh issue comment <N> --body "Worker: developer failed. Restored to ready queue."
|
|
181
|
-
bash .claude/scripts/project-status.sh <N> "Todo" "Queued"
|
|
182
200
|
```
|
|
183
201
|
Remove it from the active set and continue with the rest.
|
|
184
202
|
|
|
@@ -246,9 +264,15 @@ if [ -n "$SLACK_CHANNEL" ]; then
|
|
|
246
264
|
fi
|
|
247
265
|
```
|
|
248
266
|
|
|
249
|
-
**Update Project #6 → Under Review
|
|
267
|
+
**Update Project #6 Status → Under Review** (best-effort):
|
|
250
268
|
```bash
|
|
251
|
-
|
|
269
|
+
REPO_NAME=$(gh repo view --json nameWithOwner --jq '.nameWithOwner')
|
|
270
|
+
ITEM_ID=$(gh api graphql -f query='{user(login:"SharadKumar"){projectV2(number:6){items(first:100){nodes{id content{...on Issue{number repository{nameWithOwner}}}}}}}}' \
|
|
271
|
+
--jq ".data.user.projectV2.items.nodes[] | select(.content.number == <N> and .content.repository.nameWithOwner == \"$REPO_NAME\") | .id" 2>/dev/null | head -1)
|
|
272
|
+
[ -n "$ITEM_ID" ] && gh project item-edit --id "$ITEM_ID" \
|
|
273
|
+
--project-id "PVT_kwHOAFO-EM4BRTW5" \
|
|
274
|
+
--field-id "PVTSSF_lAHOAFO-EM4BRTW5zg_K3tE" \
|
|
275
|
+
--single-select-option-id "173633ca" 2>/dev/null || true
|
|
252
276
|
```
|
|
253
277
|
|
|
254
278
|
## Step 5: Review in Parallel
|
|
@@ -272,7 +296,17 @@ Wait for all reviewer agents to complete.
|
|
|
272
296
|
```bash
|
|
273
297
|
gh issue edit <N> --remove-label "in-progress,claude" --add-label "ready"
|
|
274
298
|
gh issue comment <N> --body "Worker: reviewer blocked. Reason: <reason>."
|
|
275
|
-
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
**Update Project #6 Status → Blocked** (best-effort):
|
|
302
|
+
```bash
|
|
303
|
+
REPO_NAME=$(gh repo view --json nameWithOwner --jq '.nameWithOwner')
|
|
304
|
+
ITEM_ID=$(gh api graphql -f query='{user(login:"SharadKumar"){projectV2(number:6){items(first:100){nodes{id content{...on Issue{number repository{nameWithOwner}}}}}}}}' \
|
|
305
|
+
--jq ".data.user.projectV2.items.nodes[] | select(.content.number == <N> and .content.repository.nameWithOwner == \"$REPO_NAME\") | .id" 2>/dev/null | head -1)
|
|
306
|
+
[ -n "$ITEM_ID" ] && gh project item-edit --id "$ITEM_ID" \
|
|
307
|
+
--project-id "PVT_kwHOAFO-EM4BRTW5" \
|
|
308
|
+
--field-id "PVTSSF_lAHOAFO-EM4BRTW5zg_K3tE" \
|
|
309
|
+
--single-select-option-id "d239ddb3" 2>/dev/null || true
|
|
276
310
|
```
|
|
277
311
|
|
|
278
312
|
## Step 6: Merge & Record
|
|
@@ -283,7 +317,16 @@ bash .claude/scripts/merge-pr.sh <PR-number>
|
|
|
283
317
|
gh issue close <N> --comment "Closed by PR #<M>."
|
|
284
318
|
```
|
|
285
319
|
|
|
286
|
-
|
|
320
|
+
**Update Project #6 Status → Done** (best-effort):
|
|
321
|
+
```bash
|
|
322
|
+
REPO_NAME=$(gh repo view --json nameWithOwner --jq '.nameWithOwner')
|
|
323
|
+
ITEM_ID=$(gh api graphql -f query='{user(login:"SharadKumar"){projectV2(number:6){items(first:100){nodes{id content{...on Issue{number repository{nameWithOwner}}}}}}}}' \
|
|
324
|
+
--jq ".data.user.projectV2.items.nodes[] | select(.content.number == <N> and .content.repository.nameWithOwner == \"$REPO_NAME\") | .id" 2>/dev/null | head -1)
|
|
325
|
+
[ -n "$ITEM_ID" ] && gh project item-edit --id "$ITEM_ID" \
|
|
326
|
+
--project-id "PVT_kwHOAFO-EM4BRTW5" \
|
|
327
|
+
--field-id "PVTSSF_lAHOAFO-EM4BRTW5zg_K3tE" \
|
|
328
|
+
--single-select-option-id "b60a81d0" 2>/dev/null || true
|
|
329
|
+
```
|
|
287
330
|
|
|
288
331
|
After each merge, reply in the PR's Slack thread (best-effort):
|
|
289
332
|
```bash
|
package/.claude/rules/testing.md
CHANGED
|
@@ -25,17 +25,20 @@ Every change that can be tested locally MUST be tested locally before committing
|
|
|
25
25
|
|
|
26
26
|
### dist/ freshness
|
|
27
27
|
|
|
28
|
-
`dist/` is
|
|
28
|
+
`dist/` is committed to the repo. The `prepare` script skips building when `dist/` already exists. This means stale `dist/` = users run old code.
|
|
29
29
|
|
|
30
|
-
**After changing any `src/` file, always
|
|
30
|
+
**After changing any `src/` file, always rebuild and commit dist/:**
|
|
31
31
|
```bash
|
|
32
|
-
bun run build
|
|
32
|
+
bun run build:server
|
|
33
|
+
git add dist/
|
|
33
34
|
```
|
|
34
35
|
|
|
36
|
+
If you forget, `npx github:SharadKumar/claudius` will silently run old code — the most dangerous kind of bug.
|
|
37
|
+
|
|
35
38
|
### Anti-patterns
|
|
36
39
|
|
|
37
40
|
- Committing install commands without testing them in the target environment
|
|
38
41
|
- Assuming package managers behave identically (they don't — especially for private repos, workspaces, lockfiles)
|
|
39
42
|
- Shipping a fix for a fix for a fix instead of reverting and getting it right once
|
|
40
43
|
- Trusting `bun run build` as sufficient validation when the change is user-facing
|
|
41
|
-
-
|
|
44
|
+
- Changing `src/` without rebuilding and committing `dist/`
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# merge-pr.sh —
|
|
2
|
+
# merge-pr.sh — Robust PR merge that works regardless of CWD or worktree state.
|
|
3
3
|
#
|
|
4
4
|
# Usage: bash .claude/scripts/merge-pr.sh <PR-number>
|
|
5
5
|
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
6
|
+
# Behavior:
|
|
7
|
+
# 1. Resolve the main repo root via git-common-dir (CWD-agnostic)
|
|
8
|
+
# 2. cd to main root before any git/gh operations
|
|
9
|
+
# 3. Try gh pr merge (happy path — works when main is not checked out elsewhere)
|
|
10
|
+
# 4. On failure, fall back to GitHub API merge + remote branch deletion
|
|
11
|
+
# 5. Sync home branch inline (no external script dependency)
|
|
10
12
|
#
|
|
11
|
-
# Exit codes: 0 = merged, 1 = merge failed
|
|
13
|
+
# Exit codes: 0 = merged + synced, 1 = merge failed, 2 = sync failed
|
|
12
14
|
|
|
13
15
|
set -euo pipefail
|
|
14
16
|
|
|
@@ -18,31 +20,60 @@ if [[ -z "$PR" ]]; then
|
|
|
18
20
|
exit 1
|
|
19
21
|
fi
|
|
20
22
|
|
|
23
|
+
# Resolve main repo root — works from any CWD (worktree, subdir, main checkout)
|
|
24
|
+
GIT_COMMON_DIR=$(git rev-parse --git-common-dir 2>/dev/null)
|
|
25
|
+
MAIN_ROOT=$(cd "$(git rev-parse --show-toplevel)" && cd "$(git rev-parse --git-common-dir)/.." && pwd)
|
|
26
|
+
|
|
27
|
+
cd "$MAIN_ROOT"
|
|
28
|
+
|
|
29
|
+
# Get branch name before merging (needed for API fallback cleanup)
|
|
21
30
|
BRANCH=$(gh pr view "$PR" --json headRefName --jq '.headRefName' 2>/dev/null)
|
|
22
31
|
REPO=$(gh repo view --json nameWithOwner --jq '.nameWithOwner' 2>/dev/null)
|
|
23
|
-
TITLE=$(gh pr view "$PR" --json title --jq '.title')
|
|
24
32
|
|
|
25
33
|
echo "Merging PR #$PR ($BRANCH) in $REPO..."
|
|
26
34
|
|
|
27
|
-
#
|
|
28
|
-
gh
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
35
|
+
# Happy path: gh pr merge handles everything locally
|
|
36
|
+
if gh pr merge "$PR" --squash --delete-branch 2>/dev/null; then
|
|
37
|
+
echo "Merged via gh pr merge."
|
|
38
|
+
else
|
|
39
|
+
echo "gh pr merge failed (likely worktree conflict) — falling back to API merge..."
|
|
40
|
+
|
|
41
|
+
# API merge
|
|
42
|
+
TITLE=$(gh pr view "$PR" --json title --jq '.title')
|
|
43
|
+
gh api "repos/$REPO/pulls/$PR/merge" \
|
|
44
|
+
-X PUT \
|
|
45
|
+
-f merge_method=squash \
|
|
46
|
+
-f commit_title="$TITLE" \
|
|
47
|
+
--silent
|
|
33
48
|
|
|
34
|
-
echo "Merged
|
|
49
|
+
echo "Merged via API."
|
|
50
|
+
|
|
51
|
+
# Delete remote branch
|
|
52
|
+
if git ls-remote --exit-code origin "$BRANCH" &>/dev/null; then
|
|
53
|
+
git push origin --delete "$BRANCH" 2>/dev/null && echo "Deleted remote branch: $BRANCH" || echo "⚠ Could not delete remote branch: $BRANCH"
|
|
54
|
+
fi
|
|
55
|
+
fi
|
|
35
56
|
|
|
36
|
-
#
|
|
37
|
-
|
|
38
|
-
&& echo "Deleted remote branch: $BRANCH" \
|
|
39
|
-
|| echo "⚠ Could not delete remote branch: $BRANCH"
|
|
57
|
+
# Re-anchor to main root (gh pr merge may change CWD)
|
|
58
|
+
cd "$MAIN_ROOT"
|
|
40
59
|
|
|
41
|
-
#
|
|
42
|
-
|
|
60
|
+
# Inline sync: return to home branch
|
|
61
|
+
# Detect if this script was invoked from a worktree context
|
|
62
|
+
GIT_DIR=$(git rev-parse --git-dir 2>/dev/null)
|
|
43
63
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
git
|
|
64
|
+
if [[ "$GIT_DIR" == */.git/worktrees/* ]]; then
|
|
65
|
+
WORKTREE_NAME=$(basename "$GIT_DIR")
|
|
66
|
+
HOME_BRANCH="worktree-$WORKTREE_NAME"
|
|
67
|
+
git checkout "$HOME_BRANCH" 2>/dev/null || true
|
|
68
|
+
echo "Returned to home branch: $HOME_BRANCH"
|
|
69
|
+
else
|
|
70
|
+
# Root repo — return to main and sync
|
|
71
|
+
git checkout main 2>/dev/null || true
|
|
72
|
+
if git pull --ff-only origin main 2>/dev/null; then
|
|
73
|
+
echo "main synced to latest."
|
|
74
|
+
else
|
|
75
|
+
echo "⚠ main has diverged from origin/main — fast-forward not possible."
|
|
76
|
+
echo " Resolve manually: git fetch origin && git rebase origin/main"
|
|
77
|
+
exit 2
|
|
78
|
+
fi
|
|
48
79
|
fi
|
|
@@ -22,19 +22,19 @@ EXECUTION_FIELD="PVTSSF_lAHOAFO-EM4BRTW5zg_Lokk"
|
|
|
22
22
|
|
|
23
23
|
# Resolve Status option ID
|
|
24
24
|
case "$STATUS" in
|
|
25
|
-
"Todo") STATUS_OPT="
|
|
26
|
-
"In Progress") STATUS_OPT="
|
|
27
|
-
"Under Review") STATUS_OPT="
|
|
28
|
-
"Blocked") STATUS_OPT="
|
|
25
|
+
"Todo") STATUS_OPT="59debf59" ;;
|
|
26
|
+
"In Progress") STATUS_OPT="6e8991c5" ;;
|
|
27
|
+
"Under Review") STATUS_OPT="a6a6dc32" ;;
|
|
28
|
+
"Blocked") STATUS_OPT="f77fb75d" ;;
|
|
29
29
|
*) echo "[project-status] Unknown status: $STATUS" >&2; exit 1 ;;
|
|
30
30
|
esac
|
|
31
31
|
|
|
32
32
|
# Resolve Execution option ID
|
|
33
33
|
case "$EXECUTION" in
|
|
34
|
-
"Queued") EXECUTION_OPT="
|
|
35
|
-
"Running") EXECUTION_OPT="
|
|
36
|
-
"Review") EXECUTION_OPT="
|
|
37
|
-
"Blocked") EXECUTION_OPT="
|
|
34
|
+
"Queued") EXECUTION_OPT="b2b4bd91" ;;
|
|
35
|
+
"Running") EXECUTION_OPT="e632d881" ;;
|
|
36
|
+
"Review") EXECUTION_OPT="4098f421" ;;
|
|
37
|
+
"Blocked") EXECUTION_OPT="70bc5d52" ;;
|
|
38
38
|
*) echo "[project-status] Unknown execution: $EXECUTION" >&2; exit 1 ;;
|
|
39
39
|
esac
|
|
40
40
|
|
|
@@ -105,23 +105,9 @@ Also read: `.claudius/design/tokens.css` for token values, `.claudius/design/com
|
|
|
105
105
|
1. Write failing test from first acceptance criterion
|
|
106
106
|
2. Implement until test passes
|
|
107
107
|
3. Repeat for each AC
|
|
108
|
-
4.
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
**Team (M+ — >2 files, >100 lines):** Use the Agent tool to spawn developer, simplifier, and reviewer agents in sequence.
|
|
112
|
-
|
|
113
|
-
1. **Spawn builder** — developer agent implements the issue with TDD (full cycle: failing tests → implementation → passing tests)
|
|
114
|
-
2. **Spawn simplifier** — after builder completes, spawn the `code-simplifier` agent (best-effort: if it returns no changes or errors, log and continue to reviewer):
|
|
115
|
-
```
|
|
116
|
-
Agent tool, subagent_type: general-purpose, agent: code-simplifier
|
|
117
|
-
Prompt:
|
|
118
|
-
<git diff of all changes: `git diff main...HEAD`>
|
|
119
|
-
<original issue ACs>
|
|
120
|
-
Project conventions: see CLAUDE.md
|
|
121
|
-
Task: Reduce complexity, improve reuse, fix quality issues — without changing behavior or adding new features.
|
|
122
|
-
Constraint: Only touch files already changed on this branch (visible in the diff above).
|
|
123
|
-
```
|
|
124
|
-
3. **Spawn reviewer** — reviewer agent runs quality gate after simplifier completes (or after simplifier is skipped due to error)
|
|
108
|
+
4. Run full suite: `bun test`
|
|
109
|
+
|
|
110
|
+
**Team (M+ — >2 files, >100 lines):** Use the Agent tool to spawn developer and reviewer agents.
|
|
125
111
|
|
|
126
112
|
### 5. Validate
|
|
127
113
|
|
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
"name": "claudius",
|
|
22
22
|
"source": "./",
|
|
23
23
|
"description": "Autonomous orchestration layer for Claude Code — agents, skills, hooks, and heartbeat daemon for round-the-clock development",
|
|
24
|
-
"version": "0.9.
|
|
24
|
+
"version": "0.9.6",
|
|
25
25
|
"strict": true,
|
|
26
26
|
"category": "development"
|
|
27
27
|
}
|
|
28
28
|
],
|
|
29
|
-
"version": "0.9.
|
|
29
|
+
"version": "0.9.6"
|
|
30
30
|
}
|
package/dist/lib/db.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic local store — Convex-ready interface.
|
|
3
|
+
*
|
|
4
|
+
* Interface mirrors Convex query/mutation shape:
|
|
5
|
+
* - namespace = table name
|
|
6
|
+
* - key = document id
|
|
7
|
+
*
|
|
8
|
+
* Backed by `.claudius/db.json` with atomic writes (tmp + rename) so the
|
|
9
|
+
* file is always valid JSON even if the process is killed mid-write.
|
|
10
|
+
*
|
|
11
|
+
* Swap path: replace `createDb` with a Convex adapter that implements the
|
|
12
|
+
* same `Db` interface — callers don't change.
|
|
13
|
+
*/
|
|
14
|
+
/** Convex-compatible store interface. */
|
|
15
|
+
export interface Db {
|
|
16
|
+
/** Read a document by namespace (table) and key (doc id). */
|
|
17
|
+
get<T = unknown>(namespace: string, key: string): T | undefined;
|
|
18
|
+
/** Write a document. Overwrites if it already exists. */
|
|
19
|
+
set(namespace: string, key: string, value: unknown): void;
|
|
20
|
+
/** Delete a document. No-op if it doesn't exist. */
|
|
21
|
+
del(namespace: string, key: string): void;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Create a `Db` instance scoped to `projectDir`.
|
|
25
|
+
*
|
|
26
|
+
* @param projectDir - root of the project (where `.claudius/` lives).
|
|
27
|
+
* Defaults to `"."` (current working directory).
|
|
28
|
+
*/
|
|
29
|
+
export declare function createDb(projectDir?: string): Db;
|
|
30
|
+
//# sourceMappingURL=db.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/lib/db.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,yCAAyC;AACzC,MAAM,WAAW,EAAE;IAClB,6DAA6D;IAC7D,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAAC;IAChE,yDAAyD;IACzD,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IAC1D,oDAAoD;IACpD,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1C;AAiCD;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,UAAU,SAAM,GAAG,EAAE,CAqB7C"}
|
package/dist/lib/db.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic local store — Convex-ready interface.
|
|
3
|
+
*
|
|
4
|
+
* Interface mirrors Convex query/mutation shape:
|
|
5
|
+
* - namespace = table name
|
|
6
|
+
* - key = document id
|
|
7
|
+
*
|
|
8
|
+
* Backed by `.claudius/db.json` with atomic writes (tmp + rename) so the
|
|
9
|
+
* file is always valid JSON even if the process is killed mid-write.
|
|
10
|
+
*
|
|
11
|
+
* Swap path: replace `createDb` with a Convex adapter that implements the
|
|
12
|
+
* same `Db` interface — callers don't change.
|
|
13
|
+
*/
|
|
14
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
15
|
+
import { dirname, resolve } from "node:path";
|
|
16
|
+
const DB_FILENAME = ".claudius/db.json";
|
|
17
|
+
function dbPath(projectDir) {
|
|
18
|
+
return resolve(projectDir, DB_FILENAME);
|
|
19
|
+
}
|
|
20
|
+
function read(projectDir) {
|
|
21
|
+
const path = dbPath(projectDir);
|
|
22
|
+
if (!existsSync(path))
|
|
23
|
+
return {};
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function write(projectDir, store) {
|
|
32
|
+
const path = dbPath(projectDir);
|
|
33
|
+
const dir = dirname(path);
|
|
34
|
+
if (!existsSync(dir)) {
|
|
35
|
+
mkdirSync(dir, { recursive: true });
|
|
36
|
+
}
|
|
37
|
+
// Atomic write: write to tmp then rename.
|
|
38
|
+
// pid + random suffix prevents collisions under concurrent writes.
|
|
39
|
+
const tmp = `${path}.tmp.${process.pid}.${Math.random().toString(36).slice(2)}`;
|
|
40
|
+
writeFileSync(tmp, JSON.stringify(store, null, 2));
|
|
41
|
+
renameSync(tmp, path);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Create a `Db` instance scoped to `projectDir`.
|
|
45
|
+
*
|
|
46
|
+
* @param projectDir - root of the project (where `.claudius/` lives).
|
|
47
|
+
* Defaults to `"."` (current working directory).
|
|
48
|
+
*/
|
|
49
|
+
export function createDb(projectDir = ".") {
|
|
50
|
+
return {
|
|
51
|
+
get(namespace, key) {
|
|
52
|
+
const store = read(projectDir);
|
|
53
|
+
return store[namespace]?.[key] ?? undefined;
|
|
54
|
+
},
|
|
55
|
+
set(namespace, key, value) {
|
|
56
|
+
const store = read(projectDir);
|
|
57
|
+
if (!store[namespace])
|
|
58
|
+
store[namespace] = {};
|
|
59
|
+
store[namespace][key] = value;
|
|
60
|
+
write(projectDir, store);
|
|
61
|
+
},
|
|
62
|
+
del(namespace, key) {
|
|
63
|
+
const store = read(projectDir);
|
|
64
|
+
if (!store[namespace])
|
|
65
|
+
return;
|
|
66
|
+
delete store[namespace][key];
|
|
67
|
+
write(projectDir, store);
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=db.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.js","sourceRoot":"","sources":["../../src/lib/db.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACzF,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAc7C,MAAM,WAAW,GAAG,mBAAmB,CAAC;AAExC,SAAS,MAAM,CAAC,UAAkB;IACjC,OAAO,OAAO,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;AACzC,CAAC;AAED,SAAS,IAAI,CAAC,UAAkB;IAC/B,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;IAChC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IACjC,IAAI,CAAC;QACJ,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAU,CAAC;IACzD,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,EAAE,CAAC;IACX,CAAC;AACF,CAAC;AAED,SAAS,KAAK,CAAC,UAAkB,EAAE,KAAY;IAC9C,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;IAChC,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACtB,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACrC,CAAC;IACD,0CAA0C;IAC1C,mEAAmE;IACnE,MAAM,GAAG,GAAG,GAAG,IAAI,QAAQ,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IAChF,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACnD,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;AACvB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,QAAQ,CAAC,UAAU,GAAG,GAAG;IACxC,OAAO;QACN,GAAG,CAAc,SAAiB,EAAE,GAAW;YAC9C,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC;YAC/B,OAAQ,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC,GAAG,CAAO,IAAI,SAAS,CAAC;QACpD,CAAC;QAED,GAAG,CAAC,SAAiB,EAAE,GAAW,EAAE,KAAc;YACjD,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC;YAC/B,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC;gBAAE,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC;YAC7C,KAAK,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;YAC9B,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QAC1B,CAAC;QAED,GAAG,CAAC,SAAiB,EAAE,GAAW;YACjC,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC;YAC/B,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC;gBAAE,OAAO;YAC9B,OAAO,KAAK,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC;YAC7B,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QAC1B,CAAC;KACD,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack PR notification layer.
|
|
3
|
+
*
|
|
4
|
+
* Announces PRs as channel messages and routes all follow-up activity
|
|
5
|
+
* (approvals, merges, CI failures) as thread replies on the original post.
|
|
6
|
+
*
|
|
7
|
+
* Thread timestamps are persisted via the `Db` store under the
|
|
8
|
+
* `slack_threads` namespace so they survive process restarts.
|
|
9
|
+
*
|
|
10
|
+
* Environment variables:
|
|
11
|
+
* SLACK_BOT_TOKEN — required; xoxb-… bot token
|
|
12
|
+
* SLACK_CHANNEL — required; channel ID (C123…) or name
|
|
13
|
+
*/
|
|
14
|
+
import type { Db } from "./db.js";
|
|
15
|
+
export interface SlackNotifier {
|
|
16
|
+
/**
|
|
17
|
+
* Post a new channel message announcing PR creation.
|
|
18
|
+
* Stores the message timestamp in db for thread replies.
|
|
19
|
+
*/
|
|
20
|
+
postPR(prNumber: number, title: string, url: string): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Reply to the original PR announcement thread.
|
|
23
|
+
* No-ops if no thread is found in db or if Slack is unconfigured.
|
|
24
|
+
*/
|
|
25
|
+
replyPR(prNumber: number, text: string): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Create a `SlackNotifier` backed by the given `Db` instance.
|
|
29
|
+
*
|
|
30
|
+
* @param db - db instance for persisting thread timestamps.
|
|
31
|
+
*/
|
|
32
|
+
export declare function createSlack(db: Db): SlackNotifier;
|
|
33
|
+
//# sourceMappingURL=slack.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../../src/lib/slack.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AA4ClC,MAAM,WAAW,aAAa;IAC7B;;;OAGG;IACH,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEpE;;;OAGG;IACH,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvD;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,EAAE,GAAG,aAAa,CA+BjD"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack PR notification layer.
|
|
3
|
+
*
|
|
4
|
+
* Announces PRs as channel messages and routes all follow-up activity
|
|
5
|
+
* (approvals, merges, CI failures) as thread replies on the original post.
|
|
6
|
+
*
|
|
7
|
+
* Thread timestamps are persisted via the `Db` store under the
|
|
8
|
+
* `slack_threads` namespace so they survive process restarts.
|
|
9
|
+
*
|
|
10
|
+
* Environment variables:
|
|
11
|
+
* SLACK_BOT_TOKEN — required; xoxb-… bot token
|
|
12
|
+
* SLACK_CHANNEL — required; channel ID (C123…) or name
|
|
13
|
+
*/
|
|
14
|
+
const SLACK_API = "https://slack.com/api/chat.postMessage";
|
|
15
|
+
const NAMESPACE = "slack_threads";
|
|
16
|
+
function getConfig() {
|
|
17
|
+
const token = process.env.SLACK_BOT_TOKEN;
|
|
18
|
+
const channel = process.env.SLACK_CHANNEL;
|
|
19
|
+
if (!token || !channel)
|
|
20
|
+
return null;
|
|
21
|
+
return { token, channel };
|
|
22
|
+
}
|
|
23
|
+
async function post(token, channel, text, threadTs) {
|
|
24
|
+
const body = { channel, text };
|
|
25
|
+
if (threadTs)
|
|
26
|
+
body.thread_ts = threadTs;
|
|
27
|
+
const response = await fetch(SLACK_API, {
|
|
28
|
+
method: "POST",
|
|
29
|
+
headers: {
|
|
30
|
+
"Content-Type": "application/json",
|
|
31
|
+
Authorization: `Bearer ${token}`,
|
|
32
|
+
},
|
|
33
|
+
body: JSON.stringify(body),
|
|
34
|
+
});
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
console.warn(`[slack] HTTP ${response.status} from Slack API`);
|
|
37
|
+
return { ok: false, error: `http_${response.status}` };
|
|
38
|
+
}
|
|
39
|
+
return response.json();
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Create a `SlackNotifier` backed by the given `Db` instance.
|
|
43
|
+
*
|
|
44
|
+
* @param db - db instance for persisting thread timestamps.
|
|
45
|
+
*/
|
|
46
|
+
export function createSlack(db) {
|
|
47
|
+
return {
|
|
48
|
+
async postPR(prNumber, title, url) {
|
|
49
|
+
const cfg = getConfig();
|
|
50
|
+
if (!cfg)
|
|
51
|
+
return;
|
|
52
|
+
const text = `\u{1F500} PR #${prNumber}: ${title}\n${url}`;
|
|
53
|
+
try {
|
|
54
|
+
const res = await post(cfg.token, cfg.channel, text);
|
|
55
|
+
if (res.ok && res.ts) {
|
|
56
|
+
db.set(NAMESPACE, String(prNumber), res.ts);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
console.warn(`[slack] postPR failed for PR #${prNumber}:`, err);
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
async replyPR(prNumber, text) {
|
|
64
|
+
const cfg = getConfig();
|
|
65
|
+
if (!cfg)
|
|
66
|
+
return;
|
|
67
|
+
const threadTs = db.get(NAMESPACE, String(prNumber));
|
|
68
|
+
if (!threadTs)
|
|
69
|
+
return;
|
|
70
|
+
try {
|
|
71
|
+
await post(cfg.token, cfg.channel, text, threadTs);
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
console.warn(`[slack] replyPR failed for PR #${prNumber}:`, err);
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=slack.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slack.js","sourceRoot":"","sources":["../../src/lib/slack.ts"],"names":[],"mappings":"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"}
|