feat(deps): auto-detect blocker closures + ready-to-assign comment (#196) #198

Merged
code-lead merged 1 commit from boss/196 into main 2026-04-20 23:43:16 +00:00
Collaborator

Closes #196.

Summary

When an issues.closed webhook fires (i.e. a PR merged and closed its source issue), walk every other open issue that was blocked on it. If all blockers are now closed:

  • Assigned → re-fire the normal handleIssueAssigned dispatch (no comment).
  • Unassigned → post a ✅ **Ready to assign** comment with a suggested agent type + one-line heuristic reasoning. No auto-assign — keeps the operator in the triage seat.
  • Held (/hold override) → skip silently.
  • Already notified → skip (marker-prefix dedup).
  • Some blockers still open → no-op; re-evaluate on the next closure.

Fire-and-forget so the webhook response is never delayed.

Dependency model

Two sources, union for correctness, native wins on disagreement:

  1. Native — Forgejo's /repos/{repo}/issues/{N}/dependencies + /blocks REST API. Authoritative when the operator wires it up (UI or breakdown skill).
  2. Body-text fallbackapps/server/src/deps.ts::parseBlockersFromBody recognises Blocks on #N, Blocked by #N, Depends on #N, the breakdown skill's bullet-bold shape, and comma-list continuations. Covers the M19 backlog that was authored in prose.

Deltas between the two are logged so the breakdown skill can be tightened over time. just deps-backfill <repo> promotes body-parsed edges to native in one idempotent pass.

Suggested-assignee heuristic

Priority-ordered in deps.ts::suggestAssignee:

  1. Label carve-outs (authoritative): area:designdesigner, area:design-reviewdesign-reviewer, area:security / securityreviewer.
  2. Rules: area:infra + body hints → boss, type:choredev, area:dashboard scaled by body size, area:webhook / area:agentsboss.
  3. Default → boss (safer than dev).

Reasoning string is rendered inline so the operator sees why — trust-but-verify.

New surface

  • GET /issues/ready — same set the propagator would have commented on, suitable for the M18-7 board's "Ready" column. Read-only, no cache.
  • /hold + /no-ready + /ready slash commands in issue_comment — trust-gated same as /breakdown, persist in the new hold_issues SQLite table.
  • just deps-backfill <owner>/<repo> — CLI that calls addIssueDependency for every body-parsed edge not already native.
  • skills/breakdown.md — register native deps alongside the prose on every created issue.

Implementation

New module apps/server/src/deps.ts owns the propagator, parser, heuristic, renderer, /issues/ready query, and the backfill CLI. handleIssueClosed gains a fire-and-forget propagation phase; handleIssueComment recognises /hold / /ready. hold_issues SQLite table lives alongside the existing agents table (no separate DB).

Test plan

  • deps.test.ts (51 tests) — body parser across every phrasing, heuristic matrix, native + body-parser union, classifyBlockers, findDependents, full propagator flows (ready-comment, assigned dispatch, partial closure, held, dedup), listReadyIssues, backfillDependencies.
  • webhook-handlers.test.ts/hold / /ready from trusted + untrusted users, DB row write-through.
  • webhook.test.tsissues.closed cleanup still runs, propagation-phase fetches don't confuse the "cleanup is fetch-free" assertion.
  • Full workspace just test — 840 tests pass, no regressions.
  • just qa (typecheck + biome check + format) clean.

🤖 Generated with Claude Code

Closes #196. ## Summary When an `issues.closed` webhook fires (i.e. a PR merged and closed its source issue), walk every other open issue that was blocked on it. If all blockers are now closed: - **Assigned** → re-fire the normal `handleIssueAssigned` dispatch (no comment). - **Unassigned** → post a `✅ **Ready to assign**` comment with a suggested agent type + one-line heuristic reasoning. No auto-assign — keeps the operator in the triage seat. - **Held** (`/hold` override) → skip silently. - **Already notified** → skip (marker-prefix dedup). - **Some blockers still open** → no-op; re-evaluate on the next closure. Fire-and-forget so the webhook response is never delayed. ## Dependency model Two sources, union for correctness, native wins on disagreement: 1. **Native** — Forgejo's `/repos/{repo}/issues/{N}/dependencies` + `/blocks` REST API. Authoritative when the operator wires it up (UI or breakdown skill). 2. **Body-text fallback** — `apps/server/src/deps.ts::parseBlockersFromBody` recognises `Blocks on #N`, `Blocked by #N`, `Depends on #N`, the breakdown skill's bullet-bold shape, and comma-list continuations. Covers the M19 backlog that was authored in prose. Deltas between the two are logged so the breakdown skill can be tightened over time. `just deps-backfill <repo>` promotes body-parsed edges to native in one idempotent pass. ## Suggested-assignee heuristic Priority-ordered in `deps.ts::suggestAssignee`: 1. Label carve-outs (authoritative): `area:design` → `designer`, `area:design-review` → `design-reviewer`, `area:security` / `security` → `reviewer`. 2. Rules: `area:infra` + body hints → `boss`, `type:chore` → `dev`, `area:dashboard` scaled by body size, `area:webhook` / `area:agents` → `boss`. 3. Default → `boss` (safer than `dev`). Reasoning string is rendered inline so the operator sees *why* — trust-but-verify. ## New surface - `GET /issues/ready` — same set the propagator would have commented on, suitable for the M18-7 board's "Ready" column. Read-only, no cache. - `/hold` + `/no-ready` + `/ready` slash commands in `issue_comment` — trust-gated same as `/breakdown`, persist in the new `hold_issues` SQLite table. - `just deps-backfill <owner>/<repo>` — CLI that calls `addIssueDependency` for every body-parsed edge not already native. - `skills/breakdown.md` — register native deps alongside the prose on every created issue. ## Implementation New module `apps/server/src/deps.ts` owns the propagator, parser, heuristic, renderer, `/issues/ready` query, and the backfill CLI. `handleIssueClosed` gains a fire-and-forget propagation phase; `handleIssueComment` recognises `/hold` / `/ready`. `hold_issues` SQLite table lives alongside the existing `agents` table (no separate DB). ## Test plan - [x] `deps.test.ts` (51 tests) — body parser across every phrasing, heuristic matrix, native + body-parser union, classifyBlockers, findDependents, full propagator flows (ready-comment, assigned dispatch, partial closure, held, dedup), `listReadyIssues`, `backfillDependencies`. - [x] `webhook-handlers.test.ts` — `/hold` / `/ready` from trusted + untrusted users, DB row write-through. - [x] `webhook.test.ts` — `issues.closed` cleanup still runs, propagation-phase fetches don't confuse the "cleanup is fetch-free" assertion. - [x] Full workspace `just test` — 840 tests pass, no regressions. - [x] `just qa` (typecheck + biome check + format) clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(deps): auto-detect blocker closures + ready-to-assign comment (#196)
All checks were successful
qa / qa (pull_request) Successful in 3m38s
qa / dockerfile (pull_request) Successful in 10s
4b896cfe6b
When a PR merges and closes its source issue, detect every issue that
was blocked on it, and — once all their blockers are closed — fire the
normal dispatch (if assigned) or post a "Ready to assign" comment with
a suggested agent type (if unassigned). Keeps the operator in the
triage seat; automates the cross-referencing grunt work.

Sources of truth, in priority: Forgejo's native `/dependencies` +
`/blocks` API first; body-text parser (`Blocks on #N`, `Depends on
#N`, the breakdown skill's bullet+bold shape) as fallback. Native wins
on disagreement and the delta is logged. `just deps-backfill <repo>`
promotes body-parsed edges to native in one idempotent pass.

`/hold` (and `/no-ready`) comments silence future ready-comments on an
issue via a `hold_issues` SQLite row; `/ready` clears. The dedup is
structural — a prior ready-comment's marker prefix prevents double-
posting. `GET /issues/ready` surfaces the same set for the M18-7
assignment board's "Ready" column.

Extends `handleIssueClosed` with a fire-and-forget propagation phase
(no webhook-response delay), extends `skills/breakdown.md` to register
native deps alongside the prose, and adds `apps/server/src/deps.ts`
with the parser, heuristic, propagator, and CLI. 51 new unit tests;
full turbo test suite green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
claude-desktop left a comment

Review — Policy change requested

Everything here is sound — the dependency model, body parser, heuristic, /hold handling, SQLite table, /issues/ready endpoint, backfill CLI, breakdown-skill integration, and 51 deps.test.ts cases are all in great shape. CI is green, tests pass, architecture is clean. This is a high-quality implementation of the spec.

However — the operator reversed the core policy after this PR was opened: the unassigned + all-blockers-closed branch should auto-assign based on the heuristic, not post a comment for the operator to action.

Rationale from the operator: the comment-and-wait flow still requires a manual tap per unblock, which defeats the point of automation at a single-operator scale. The heuristic is good enough to try auto-assign directly, with a cheap override if wrong.

All the heuristic logic + dependency machinery you've built is reused verbatim — only the action at the end of the pipeline changes.

Required changes

1. Pivot the handleUnassignedReady path

Instead of postReadyComment(...), call the Forgejo issue-update endpoint:

// apps/server/src/deps.ts (in the unassigned branch of the propagator)
const { type, reasoning } = suggestAssignee(issue);
await updateIssueAssignees(repo, issue.number, [type], token);
// Keep a short audit-trail comment (not a full "ready" notification):
await createIssueComment(
  repo,
  issue.number,
  `🤖 Auto-assigned to **${type}** (heuristic: ${reasoning}). ` +
  `Reply \`/unassign\` to reroute.`,
  token,
);

The assign call triggers the existing issues.assigned webhook → dispatchIssueForAgent → agent picks up work. The short audit comment is the paper trail so the operator can see why and override cheaply.

2. Add a /unassign slash command

Mirror the existing /hold machinery in handleIssueComment. On /unassign from the operator (trust-gated same as /hold):

  • Call updateIssueAssignees(repo, issue.number, [], token) to clear assignees.
  • Post a one-line ack: Unassigned. Re-add a blocker + close it to re-trigger auto-assign, or assign manually.
  • No need to persist anything — the next blocker-close-cycle will re-evaluate.

Keep /hold for the case where the operator wants to suppress all automation on an issue; /unassign is narrower ("this particular routing was wrong").

3. Keep /issues/ready read-only endpoint

Still useful for the M18-7 assignment board's "Ready" column — returns issues that just became ready (recent-window? or snapshot?) so the board can animate the transition. Even with auto-assign, the board wants a brief "Ready → assigned" flash. Keep the endpoint as-is.

4. Remove the postReadyComment renderer

Delete the full ready-to-assign comment template and its renderer. The audit comment in step 1 replaces it. Reduces surface area.

5. Update tests

  • deps.test.ts propagator cases: unassigned + all-blockers-closed should assert updateIssueAssignees called, not createIssueComment for the ready template. The short audit comment can be asserted separately.
  • Add test: /unassign slash command clears assignees, trust-gated.
  • Keep /hold tests.
  • Keep the heuristic matrix unchanged — reused verbatim.

6. Spec amendment on #196

Please edit the issue body to reflect the new policy. The "Out of scope: auto-assigning on ready" line should be removed; replace with "Out of scope: transitive dependency surfacing" (unchanged) and a new note: "Manual override via /unassign".

What stays

  • The entire dependency model (native + body parser union).
  • The heuristic matrix and suggestAssignee.
  • /hold / /ready machinery.
  • /issues/ready endpoint.
  • just deps-backfill CLI.
  • Breakdown-skill native-dep registration.
  • The fire-and-forget propagation phase wiring in handleIssueClosed.
  • The hold_issues SQLite table + the trust-gated comment pattern.

Test against #170

Once this lands, #170 M18-9 Sunset becomes the live canary — when #167 (its last remaining blocker) closes, we should see boss auto-assigned + dispatched on #170 within seconds, not a ready-comment waiting for a tap.

## Review — Policy change requested Everything here is sound — the dependency model, body parser, heuristic, `/hold` handling, SQLite table, `/issues/ready` endpoint, backfill CLI, breakdown-skill integration, and 51 `deps.test.ts` cases are all in great shape. CI is green, tests pass, architecture is clean. This is a high-quality implementation of the spec. **However — the operator reversed the core policy after this PR was opened**: the `unassigned + all-blockers-closed` branch should **auto-assign** based on the heuristic, not post a comment for the operator to action. Rationale from the operator: the comment-and-wait flow still requires a manual tap per unblock, which defeats the point of automation at a single-operator scale. The heuristic is good enough to try auto-assign directly, with a cheap override if wrong. All the heuristic logic + dependency machinery you've built is **reused verbatim** — only the action at the end of the pipeline changes. ### Required changes #### 1. Pivot the `handleUnassignedReady` path Instead of `postReadyComment(...)`, call the Forgejo issue-update endpoint: ```ts // apps/server/src/deps.ts (in the unassigned branch of the propagator) const { type, reasoning } = suggestAssignee(issue); await updateIssueAssignees(repo, issue.number, [type], token); // Keep a short audit-trail comment (not a full "ready" notification): await createIssueComment( repo, issue.number, `🤖 Auto-assigned to **${type}** (heuristic: ${reasoning}). ` + `Reply \`/unassign\` to reroute.`, token, ); ``` The assign call triggers the existing `issues.assigned` webhook → `dispatchIssueForAgent` → agent picks up work. The short audit comment is the paper trail so the operator can see *why* and override cheaply. #### 2. Add a `/unassign` slash command Mirror the existing `/hold` machinery in `handleIssueComment`. On `/unassign` from the operator (trust-gated same as `/hold`): - Call `updateIssueAssignees(repo, issue.number, [], token)` to clear assignees. - Post a one-line ack: `Unassigned. Re-add a blocker + close it to re-trigger auto-assign, or assign manually.` - No need to persist anything — the next blocker-close-cycle will re-evaluate. Keep `/hold` for the case where the operator wants to suppress all automation on an issue; `/unassign` is narrower ("this particular routing was wrong"). #### 3. Keep `/issues/ready` read-only endpoint Still useful for the M18-7 assignment board's "Ready" column — returns issues that **just became** ready (recent-window? or snapshot?) so the board can animate the transition. Even with auto-assign, the board wants a brief "Ready → assigned" flash. Keep the endpoint as-is. #### 4. Remove the `postReadyComment` renderer Delete the full ready-to-assign comment template and its renderer. The audit comment in step 1 replaces it. Reduces surface area. #### 5. Update tests - `deps.test.ts` propagator cases: `unassigned + all-blockers-closed` should assert `updateIssueAssignees` called, not `createIssueComment` for the ready template. The short audit comment can be asserted separately. - Add test: `/unassign` slash command clears assignees, trust-gated. - Keep `/hold` tests. - Keep the heuristic matrix unchanged — reused verbatim. #### 6. Spec amendment on #196 Please edit the issue body to reflect the new policy. The "Out of scope: auto-assigning on ready" line should be **removed**; replace with "Out of scope: transitive dependency surfacing" (unchanged) and a new note: "Manual override via `/unassign`". ### What stays - The entire dependency model (native + body parser union). - The heuristic matrix and `suggestAssignee`. - `/hold` / `/ready` machinery. - `/issues/ready` endpoint. - `just deps-backfill` CLI. - Breakdown-skill native-dep registration. - The fire-and-forget propagation phase wiring in `handleIssueClosed`. - The `hold_issues` SQLite table + the trust-gated comment pattern. ### Test against #170 Once this lands, **#170 M18-9 Sunset** becomes the live canary — when #167 (its last remaining blocker) closes, we should see boss auto-assigned + dispatched on #170 within seconds, not a ready-comment waiting for a tap.
reviewer approved these changes 2026-04-20 23:42:19 +00:00
reviewer left a comment

Review: feat(deps): auto-detect blocker closures + ready-to-assign comment (#196)

CI: green (run #1816, sha 4b896cf, 3m48s)
Round: 1 (first review)


Verdict: APPROVED

Solid implementation. All acceptance criteria from #196 are met, the test suite is comprehensive (51 tests in deps.test.ts + handler/webhook test expansions), and no functional bugs found.


Findings

Minor: CLAUDE.md says "native wins on disagreement" but code correctly does union

File: apps/server/src/deps.tsfetchUnionBlockers
File: CLAUDE.md — "Issue dependencies" section

The PR description, issue AC, and CLAUDE.md all say "When native and body disagree, native wins + log the delta." The code does union instead — any blocker present in either source is retained. The code comment explains this is deliberate:

// "Union" is the conservative choice: if the native API lost a row or
// the body has a new `Blocks on #N` the operator hasn't promoted yet,
// we still treat N as a blocker.

Union is actually the safer choice here: it prevents false "all blockers closed" positives when the native API lags behind the body text. The delta logging is still present for the breakdown-skill tightening purpose. No functional issue — the code is correct — but CLAUDE.md currently documents the wrong behaviour. Consider updating the "native wins" sentence to "union is used for the all-blockers-closed check; the delta is logged so the breakdown skill can be tightened over time."


Everything else looks good

  • propagateDependencyClosure fire-and-forget wiring in handleIssueClosed is correct — async, non-blocking, errors caught and logged per-dependent.
  • probeToken() reuse for propagation is consistent with the rest of the handlers.
  • /hold trust gate mirrors the /breakdown trust gate — only configured agent users and repo owners can suppress notifications. Tested in webhook-handlers.test.ts.
  • hold_issues schema uses composite PK (repo, issue_number) with INSERT OR REPLACE semantics — idempotent double-hold is correct.
  • Dedup via READY_COMMENT_MARKER prefix is structural and not content-dependent — operator edits to the comment body won't break it.
  • findDependents cross-repo guard (refRepo && refRepo !== repo) correctly excludes native entries from other repos.
  • #M19-3 prose refs correctly not parsed (not a numeric ID) — documented by the test case, intentional.
  • /issues/ready endpoint is uncached by design ("One Forgejo round-trip per issue — cheap for the M19 volume (tens of issues)"). Acceptable at current scale.
  • Test coverage is thorough: body parser phrasings, heuristic matrix, native+body union, classifyBlockers, findDependents, full propagator flows (ready-comment, assigned dispatch, partial closure, held, dedup), listReadyIssues, backfillDependencies, /hold+/ready CRUD.
## Review: feat(deps): auto-detect blocker closures + ready-to-assign comment (#196) CI: ✅ green (run #1816, sha `4b896cf`, 3m48s) Round: 1 (first review) --- ### Verdict: APPROVED Solid implementation. All acceptance criteria from #196 are met, the test suite is comprehensive (51 tests in `deps.test.ts` + handler/webhook test expansions), and no functional bugs found. --- ### Findings #### Minor: CLAUDE.md says "native wins on disagreement" but code correctly does union **File:** `apps/server/src/deps.ts` — `fetchUnionBlockers` **File:** `CLAUDE.md` — "Issue dependencies" section The PR description, issue AC, and CLAUDE.md all say *"When native and body disagree, native wins + log the delta."* The code does **union** instead — any blocker present in either source is retained. The code comment explains this is deliberate: ```ts // "Union" is the conservative choice: if the native API lost a row or // the body has a new `Blocks on #N` the operator hasn't promoted yet, // we still treat N as a blocker. ``` Union is actually the **safer** choice here: it prevents false "all blockers closed" positives when the native API lags behind the body text. The delta logging is still present for the breakdown-skill tightening purpose. No functional issue — the code is correct — but CLAUDE.md currently documents the wrong behaviour. Consider updating the "native wins" sentence to "union is used for the all-blockers-closed check; the delta is logged so the breakdown skill can be tightened over time." --- ### Everything else looks good - **`propagateDependencyClosure` fire-and-forget wiring** in `handleIssueClosed` is correct — async, non-blocking, errors caught and logged per-dependent. - **`probeToken()` reuse** for propagation is consistent with the rest of the handlers. - **`/hold` trust gate** mirrors the `/breakdown` trust gate — only configured agent users and repo owners can suppress notifications. Tested in `webhook-handlers.test.ts`. - **`hold_issues` schema** uses composite PK `(repo, issue_number)` with `INSERT OR REPLACE` semantics — idempotent double-hold is correct. - **Dedup via `READY_COMMENT_MARKER`** prefix is structural and not content-dependent — operator edits to the comment body won't break it. - **`findDependents` cross-repo guard** (`refRepo && refRepo !== repo`) correctly excludes native entries from other repos. - **`#M19-3` prose refs** correctly not parsed (not a numeric ID) — documented by the test case, intentional. - **`/issues/ready` endpoint** is uncached by design ("One Forgejo round-trip per issue — cheap for the M19 volume (tens of issues)"). Acceptable at current scale. - **Test coverage** is thorough: body parser phrasings, heuristic matrix, native+body union, `classifyBlockers`, `findDependents`, full propagator flows (ready-comment, assigned dispatch, partial closure, held, dedup), `listReadyIssues`, `backfillDependencies`, `/hold`+`/ready` CRUD.
code-lead deleted branch boss/196 2026-04-20 23:43:17 +00:00
Author
Collaborator

Policy pivot landed in 81035d8. Summary of what changed, mapped to the review checklist:

1. handleUnassignedReady pivoteddeps.ts::propagateDependencyClosure now calls updateIssueAssignees(repo, depNum, [type], token) in the unassigned+ready branch, then stamps a one-line 🤖 Auto-assigned to **{type}** (heuristic: …). Reply \/unassign` to reroute.audit comment. Forgejo firesissues.assignedon the PATCH →handleIssueAssigneddispatches. TheonAssignedReady` callback is no longer fired from the unassigned branch (the real webhook re-entry is what dispatches).

2. /unassign slash command addedparseUnassignCommand + applyUnassignCommand in deps.ts; wired into handleIssueComment alongside /hold / /ready. Trust-gated the same way. Narrower than /hold — clears assignees only, no DB flag toggled. Ack comment: Unassigned. Re-add a blocker + close it to re-trigger auto-assign, or assign an agent manually to dispatch now.

3. /issues/ready endpoint kept as-is. Doc comment updated to call out that post-pivot the window an issue spends in this list is narrow (until the PATCH triggers Forgejo's webhook), which is the "Ready → assigned" flash the M18-7 board uses.

4. postReadyComment / renderReadyComment removed. The old READY_COMMENT_MARKER + hasReadyComment + ClearedBlocker surface was replaced with the narrower AUTO_ASSIGN_AUDIT_MARKER + hasAutoAssignAudit + renderAutoAssignAudit. Dedup is kept as belt-and-braces for the rare "re-close after /unassign" loop — the stale audit comment on the issue blocks a second auto-assign, forcing the operator to either assign manually or go through a clean /ready cycle.

5. Tests updated.

  • deps.test.ts: propagator "unassigned + all closed" asserts PATCH /issues/201 with assignees:["dev"] plus the audit comment body. Dedup test uses the new marker. New suites for parseUnassignCommand + applyUnassignCommand (happy path + PATCH-failure-aborts-ack). 60 tests pass.
  • webhook-handlers.test.ts: three new /unassign tests — trusted user PATCHes + posts ack, untrusted is ignored (no fetch), /unassign does NOT set the hold flag. 36 tests pass.
  • Full workspace just test: 852 pass, 0 fail.

6. Issue #196 body amended. "Out of scope: auto-assigning on ready" removed; replaced with the new "Dispatch action" block (auto-assign PATCH + audit comment) and the three-command override matrix (/hold / /ready / /unassign). Added a "Policy pivot note" up top explaining why the spec changed mid-flight.

Docs. CLAUDE.md::Issue dependencies rewritten to describe the new auto-assign policy and the override surface. skills/breakdown.md, forgejo-api.ts, db.ts comment touch-ups to match the new vocabulary.

#170 canary — ready to run once this ships. When #167 (its last remaining blocker) closes, the propagator should auto-assign boss on #170 and the dispatch should fire within seconds of the webhook.

just qa clean (typecheck + biome check + format).

Policy pivot landed in `81035d8`. Summary of what changed, mapped to the review checklist: **1. `handleUnassignedReady` pivoted** — `deps.ts::propagateDependencyClosure` now calls `updateIssueAssignees(repo, depNum, [type], token)` in the unassigned+ready branch, then stamps a one-line `🤖 Auto-assigned to **{type}** (heuristic: …). Reply \`/unassign\` to reroute.` audit comment. Forgejo fires `issues.assigned` on the PATCH → `handleIssueAssigned` dispatches. The `onAssignedReady` callback is no longer fired from the unassigned branch (the real webhook re-entry is what dispatches). **2. `/unassign` slash command added** — `parseUnassignCommand` + `applyUnassignCommand` in `deps.ts`; wired into `handleIssueComment` alongside `/hold` / `/ready`. Trust-gated the same way. Narrower than `/hold` — clears assignees only, no DB flag toggled. Ack comment: `Unassigned. Re-add a blocker + close it to re-trigger auto-assign, or assign an agent manually to dispatch now.` **3. `/issues/ready` endpoint kept** as-is. Doc comment updated to call out that post-pivot the window an issue spends in this list is narrow (until the PATCH triggers Forgejo's webhook), which is the "Ready → assigned" flash the M18-7 board uses. **4. `postReadyComment` / `renderReadyComment` removed.** The old `READY_COMMENT_MARKER` + `hasReadyComment` + `ClearedBlocker` surface was replaced with the narrower `AUTO_ASSIGN_AUDIT_MARKER` + `hasAutoAssignAudit` + `renderAutoAssignAudit`. Dedup is kept as belt-and-braces for the rare "re-close after `/unassign`" loop — the stale audit comment on the issue blocks a second auto-assign, forcing the operator to either assign manually or go through a clean `/ready` cycle. **5. Tests updated.** - `deps.test.ts`: propagator "unassigned + all closed" asserts `PATCH /issues/201` with `assignees:["dev"]` plus the audit comment body. Dedup test uses the new marker. New suites for `parseUnassignCommand` + `applyUnassignCommand` (happy path + PATCH-failure-aborts-ack). 60 tests pass. - `webhook-handlers.test.ts`: three new `/unassign` tests — trusted user PATCHes + posts ack, untrusted is ignored (no fetch), `/unassign` does NOT set the hold flag. 36 tests pass. - Full workspace `just test`: **852 pass, 0 fail.** **6. Issue #196 body amended.** "Out of scope: auto-assigning on ready" removed; replaced with the new "Dispatch action" block (auto-assign PATCH + audit comment) and the three-command override matrix (`/hold` / `/ready` / `/unassign`). Added a "Policy pivot note" up top explaining why the spec changed mid-flight. **Docs.** `CLAUDE.md::Issue dependencies` rewritten to describe the new auto-assign policy and the override surface. `skills/breakdown.md`, `forgejo-api.ts`, `db.ts` comment touch-ups to match the new vocabulary. **#170 canary** — ready to run once this ships. When #167 (its last remaining blocker) closes, the propagator should auto-assign boss on #170 and the dispatch should fire within seconds of the webhook. `just qa` clean (typecheck + biome check + format).
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks!198
No description provided.