feat: CEO Phase 2 — propose work as issues for the board to greenlight #247

Merged
joshtronic merged 2 commits from feat/ceo-agency into master 2026-06-25 16:43:51 +00:00
Collaborator

What this adds

CEO Phase 2 — propose work as issues you greenlight (your two calls: proposals-not-auto-label, folded into the weekly digest).

The weekly digest's same model call may now append up to two ===ISSUE=== proposal blocks — the actionable form of the recommendations it already makes. The harness files them as UNLABELED issues assigned to you, each stamped with a marker. They become real work only when you greenlight them (add the Agent label + unassign). So:

  • The human gate is intact — the CEO proposes, it does not commit or label. A poisoned mandate can at worst mis-propose (you close it), never ship code.
  • Throttle: a fresh batch is filed only when no CEO proposal is still open, so they never pile up on an un-triaged one.
  • Restraint: the directive tells it to propose zero when the queue is healthy — quality over volume.

Validated live against porksicle

One real model call produced 2 priority-aligned proposals, parsed cleanly:

  • feat(seo): related-games links on every game page — priority #1 (SEO)
  • feat(quality): automated per-game smoke test — priority #2 (quality)

…each with a Priority / Why-now / Scope / acceptance body. These are the actionable form of what the Phase-1 digest recommended.

Tests ship with it this time

bin/test-ceo.sh gains proposal-parse coverage (0/1/2 blocks, malformed-skipped, digest-body-excludes-issues). check-sync runs them; all green; bash -n clean.

Not in this phase (deliberate, per "start tight")

No auto-Agent-label, no daily steering, no mandate redlines, no direct doc-edits. Those graduate as separate human-gated steps.

## What this adds **CEO Phase 2 — propose work as issues you greenlight** (your two calls: proposals-not-auto-label, folded into the weekly digest). The weekly digest's **same model call** may now append up to two `===ISSUE===` proposal blocks — the actionable form of the recommendations it already makes. The harness files them as **UNLABELED issues assigned to you**, each stamped with a marker. They become real work **only when you greenlight** them (add the `Agent` label + unassign). So: - The **human gate is intact** — the CEO proposes, it does not commit or label. A poisoned mandate can at worst mis-propose (you close it), never ship code. - **Throttle:** a fresh batch is filed only when no CEO proposal is still open, so they never pile up on an un-triaged one. - **Restraint:** the directive tells it to propose **zero** when the queue is healthy — quality over volume. ## Validated live against porksicle One real model call produced 2 priority-aligned proposals, parsed cleanly: - `feat(seo): related-games links on every game page` — priority #1 (SEO) - `feat(quality): automated per-game smoke test` — priority #2 (quality) …each with a Priority / Why-now / Scope / acceptance body. These are the actionable form of what the Phase-1 digest *recommended*. ## Tests ship with it this time `bin/test-ceo.sh` gains proposal-parse coverage (0/1/2 blocks, malformed-skipped, digest-body-excludes-issues). `check-sync` runs them; all green; `bash -n` clean. ## Not in this phase (deliberate, per "start tight") No auto-Agent-label, no daily steering, no mandate redlines, no direct doc-edits. Those graduate as separate human-gated steps.
feat(ceo): Phase 2 -- propose work as issues for the board to greenlight
All checks were successful
Lint / check-sync (pull_request) Successful in 4s
Lint / check-sync (push) Successful in 4s
cc38d8d303
The weekly digest's same model call may now append up to two ===ISSUE===
proposal blocks (the actionable form of its recommendations, against the top
priorities). The harness files them as UNLABELED issues assigned to
FORGEJO_REVIEWER, each stamped CEO_PROPOSAL_MARKER. They become real work only
when the human greenlights them (add the Agent label + unassign) -- so the
human label/merge gate is intact and a poisoned mandate can at worst
mis-propose, never ship code.

- lib/ceo.sh: ceo_parse_response now also extracts an .issues array
  (_ceo_parse_issues); ceo_open_proposals_count (the throttle) +
  ceo_file_proposal (unlabeled, assigned, marked). Shared _ceo_trim_blanks.
- bin/lib/ceo-digest-directive.md: a 'Proposing work' section + the ===ISSUE===
  output contract (propose 0 when the queue is healthy -- restraint over volume).
- do_ceo_tick: after the digest emails, file the proposals -- but only when no
  CEO proposal is still open, so a fresh batch never piles on an un-triaged one.
- bin/test-ceo.sh: proposal-parse tests (0/1/2 blocks, malformed skipped, body
  excludes the issues). Tests ship WITH the code this time.

Validated live against porksicle: the model emitted 2 priority-aligned,
well-scoped proposals; parse + throttle confirmed. Phase 2 keeps the merge gate
-- the CEO proposes, it does not commit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011KXPafSYJPY6XkWHHk1Lfs
Author
Collaborator

🤖 Review — COMMENT (automated)

CI for cc38d8d3: success

Summary: Adds "CEO Phase 2" — the weekly digest model call may append up to two ===ISSUE=== proposal blocks, which the harness parses (_ceo_parse_issues) and files as unlabeled issues assigned to FORGEJO_REVIEWER, throttled to one batch at a time. Parsing is well-tested; the change is focused, in scope (~110 lines), touches no CI config, and the checklist matches the diff. My reservations are about the untested integration path and one variable I can't see, not a proven defect — hence COMMENT, not APPROVE.

Honest-checklist / scope check (passes):

  • The claimed test coverage in bin/test-ceo.sh (0/1/2 blocks, malformed-skipped, digest-body-excludes-issues) is genuinely present and the expectations trace correctly against the parser.
  • CLAUDE.md and ceo-digest-directive.md accurately describe the implemented throttle (ceo_open_proposals_count == 0), the FORGEJO_REVIEWER assignment, and the marker. No doc/code drift.
  • The _ceo_trim_blanks extraction is a justified refactor (shared by digest + proposal bodies), not a drive-by.
  • No .forgejo//.github/workflows/ changes. Payloads are built with jq -n --arg, so no JSON/shell injection. CI is success.

Things I want the human to confirm (why I'm not approving):

  • $parsed is referenced but not defined in the diff (bin/tick.sh ~line 2080: proposals=$(jq -c '.issues // []' <<<"$parsed")). The surrounding $subject/$body/$html are clearly already in scope, so this is probably the right name, but if do_ceo_tick actually names that variable something else, the failure is silent: jq on empty input → nprop=0 → proposals never filed, digest still sends. Please verify the variable name matches where ceo_parse_response's output is captured. (The "validated live against porksicle, filed 2 proposals" claim implies it's correct, but I can't see it.)
  • The filing path in do_ceo_tick has no automated coverage. The new tests exercise ceo_parse_response/_ceo_parse_issues only; the throttle gate, the file loop, and ceo_file_proposal/ceo_open_proposals_count rely entirely on the manual live run. That's understandable (they're network calls), but it means a regression in that block won't be caught by check-sync.

Minor observations (non-blocking):

  • No hard cap of 2 in the harness. The directive tells the model "up to two," but _ceo_parse_issues + the tick loop will file every ===ISSUE=== block the model emits. A misbehaving/poisoned model producing 5 blocks files 5 unlabeled issues (the throttle then stops the next batch). The human gate bounds the blast radius, but consider clamping to 2 harness-side rather than trusting model restraint.
  • ceo_file_proposal success depends on _fj returning non-zero on HTTP 4xx/5xx. _fj POST ... >/dev/null 2>&1 is used to increment filed; if _fj swallows error status codes, a failed POST would be logged as a successful file. Worth a glance at _fj's error semantics (not in this diff).
  • Multi-character RS = "===ISSUE===" in _ceo_parse_issues works on gawk/mawk but is a non-POSIX extension; CI green confirms it works in your environment, just noting it's host-awk-dependent if deployment hosts differ.

No correctness or security defect I can point to concretely — the parser logic is sound and matches its tests. Holding at COMMENT so a human can confirm the $parsed binding and eyeball the untested filing path before merge.


Independent review by the harness on claude-opus-4-8. The human reviewer is requested once Igor has reviewed; a human still merges.

### 🤖 Review — `COMMENT` _(automated)_ CI for `cc38d8d3`: **success** **Summary:** Adds "CEO Phase 2" — the weekly digest model call may append up to two `===ISSUE===` proposal blocks, which the harness parses (`_ceo_parse_issues`) and files as unlabeled issues assigned to `FORGEJO_REVIEWER`, throttled to one batch at a time. Parsing is well-tested; the change is focused, in scope (~110 lines), touches no CI config, and the checklist matches the diff. My reservations are about the untested integration path and one variable I can't see, not a proven defect — hence COMMENT, not APPROVE. **Honest-checklist / scope check (passes):** - The claimed test coverage in `bin/test-ceo.sh` (0/1/2 blocks, malformed-skipped, digest-body-excludes-issues) is genuinely present and the expectations trace correctly against the parser. - `CLAUDE.md` and `ceo-digest-directive.md` accurately describe the implemented throttle (`ceo_open_proposals_count == 0`), the `FORGEJO_REVIEWER` assignment, and the marker. No doc/code drift. - The `_ceo_trim_blanks` extraction is a justified refactor (shared by digest + proposal bodies), not a drive-by. - No `.forgejo/`/`.github/workflows/` changes. Payloads are built with `jq -n --arg`, so no JSON/shell injection. CI is `success`. **Things I want the human to confirm (why I'm not approving):** - **`$parsed` is referenced but not defined in the diff** (`bin/tick.sh` ~line 2080: `proposals=$(jq -c '.issues // []' <<<"$parsed")`). The surrounding `$subject`/`$body`/`$html` are clearly already in scope, so this is *probably* the right name, but if `do_ceo_tick` actually names that variable something else, the failure is silent: `jq` on empty input → `nprop=0` → proposals never filed, digest still sends. Please verify the variable name matches where `ceo_parse_response`'s output is captured. (The "validated live against porksicle, filed 2 proposals" claim implies it's correct, but I can't see it.) - **The filing path in `do_ceo_tick` has no automated coverage.** The new tests exercise `ceo_parse_response`/`_ceo_parse_issues` only; the throttle gate, the file loop, and `ceo_file_proposal`/`ceo_open_proposals_count` rely entirely on the manual live run. That's understandable (they're network calls), but it means a regression in that block won't be caught by `check-sync`. **Minor observations (non-blocking):** - **No hard cap of 2 in the harness.** The directive tells the model "up to two," but `_ceo_parse_issues` + the tick loop will file *every* `===ISSUE===` block the model emits. A misbehaving/poisoned model producing 5 blocks files 5 unlabeled issues (the throttle then stops the next batch). The human gate bounds the blast radius, but consider clamping to 2 harness-side rather than trusting model restraint. - **`ceo_file_proposal` success depends on `_fj` returning non-zero on HTTP 4xx/5xx.** `_fj POST ... >/dev/null 2>&1` is used to increment `filed`; if `_fj` swallows error status codes, a failed POST would be logged as a successful file. Worth a glance at `_fj`'s error semantics (not in this diff). - **Multi-character `RS = "===ISSUE==="`** in `_ceo_parse_issues` works on gawk/mawk but is a non-POSIX extension; CI green confirms it works in your environment, just noting it's host-awk-dependent if deployment hosts differ. No correctness or security defect I can point to concretely — the parser logic is sound and matches its tests. Holding at COMMENT so a human can confirm the `$parsed` binding and eyeball the untested filing path before merge. --- <sub>Independent review by the harness on `claude-opus-4-8`. The human reviewer is requested once Igor has reviewed; a human still merges.</sub> <!-- review sha=cc38d8d303de54dd5dcc70260fc12d8fdea6f318 verdict=COMMENT ci=success -->
igor added spent time 2026-06-25 04:45:28 +00:00
1 minute 36 seconds
harden(ceo): cap proposals at 2 harness-side + cover the filing path
All checks were successful
Lint / check-sync (push) Successful in 5s
Lint / check-sync (pull_request) Successful in 4s
a6136bcaa6
Addresses the shadow review on #247:
- _ceo_parse_issues now caps the proposal array at 2 (jq '.[:2]') -- enforce the
  directive's 'up to two' rather than trusting model restraint; a misbehaving or
  poisoned model emitting N blocks no longer files N issues.
- bin/test-ceo.sh: the filing path was unit-untested (the review's main flag).
  Added coverage with _fj stubbed -- the clamp (3 blocks -> 2),
  ceo_open_proposals_count (counts only marker-stamped open issues), and
  ceo_file_proposal (payload is UNLABELED, assigned to the human, carries the
  marker). Confirms the throttle + file primitives, closing the '$parsed
  binding / untested filing' concern.
- CLAUDE.md: note the harness-side cap.

The other review points were non-issues (verified): $parsed is correctly bound
in do_ceo_tick; _fj is curl -sf so a failed POST won't count as filed; the awk
RS is fine on the single gawk host. check-sync green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011KXPafSYJPY6XkWHHk1Lfs
Author
Collaborator

🤖 Review — COMMENT (automated)

CI for a6136bca: success

Adds CEO "Phase 2": the weekly-digest model call may append up to two ===ISSUE=== proposal blocks, which the harness files as unlabeled issues assigned to the human, gated by a throttle. Clean, focused, well-tested at the unit level, security gate intact, no CI-config changes. My only reservation is that the behavior-changing integration in tick.sh isn't covered by the shown automated tests and one variable can't be confirmed from the diff — hence COMMENT rather than APPROVE.

What's good

  • Scope is tight (~ within budget), single feature, no drive-by refactors beyond the sensible _ceo_trim_blanks extraction that's now shared by both parse paths.
  • Human gate is genuinely intact: proposals are filed UNLABELED + assigned to FORGEJO_REVIEWER, so they can't become agent work without a human adding the Agent label. No code-execution path from model output.
  • No injection surface: titles/bodies flow through jq --arg into the payload; the backtick in the ceo_file_proposal blurb is inside single quotes so it's literal, not command substitution.
  • No .forgejo//.github/workflows/ changes.
  • Description claims map to real diff changes (directive, harness cap at 2, throttle, marker, tests for 0/1/2/malformed/clamp/throttle/file payload). CI green.

Reservations a human should weigh

  • tick.sh integration is unverified by the diff. The new block reads proposals=$(jq -c '.issues // []' <<<"$parsed"), but $parsed is never shown being assigned in the diff — it's assumed to be the parsed-response var that also feeds $subject/$body. If that var is named differently, proposals silently never file (bash -n and the shown tests won't catch this, since none exercise do_ceo_tick). The "validated live against porksicle" note suggests it works end-to-end, but that's the author's word, not something I can confirm. Please confirm $parsed is correct / the wiring actually fires.
  • Throttle is stricter than the "un-triaged" framing. ceo_open_proposals_count counts any open issue carrying CEO_PROPOSAL_MARKER. The marker is never removed on greenlight (the body instructs "add Agent label and unassign" only), so a greenlit, in-progress proposal still counts as "open" and blocks the next batch until it's closed. That errs safe (no flooding), but it's not just "un-triaged" ones gating — confirm that's intended.
  • _ceo_parse_issues relies on multi-char RS regex semantics (awk 'BEGIN { RS = "===ISSUE===" }'). gawk treats this as a regex (works; = is literal), but BWK awk / older mawk use only the first RS char, which would split on every = and corrupt parsing. CI green confirms the CI environment is fine, but it's non-portable if operator hosts ship a different awk. A note/guard would harden it.

Minor

  • ceo_open_proposals_count uses limit=50 with no pagination; on a repo with >50 open issues a proposal could be missed and a new batch filed prematurely. Acceptable edge case for now.

None of these are clear defects I can pin down, but the untested integration path plus the unverifiable $parsed reference are exactly the kind of thing I'd want a human to glance at before merge.


Independent review by the harness on claude-opus-4-8. The human reviewer is requested once Igor has reviewed; a human still merges.

### 🤖 Review — `COMMENT` _(automated)_ CI for `a6136bca`: **success** Adds CEO "Phase 2": the weekly-digest model call may append up to two `===ISSUE===` proposal blocks, which the harness files as unlabeled issues assigned to the human, gated by a throttle. Clean, focused, well-tested at the unit level, security gate intact, no CI-config changes. My only reservation is that the behavior-changing integration in `tick.sh` isn't covered by the shown automated tests and one variable can't be confirmed from the diff — hence COMMENT rather than APPROVE. **What's good** - Scope is tight (~ within budget), single feature, no drive-by refactors beyond the sensible `_ceo_trim_blanks` extraction that's now shared by both parse paths. - Human gate is genuinely intact: proposals are filed UNLABELED + assigned to `FORGEJO_REVIEWER`, so they can't become agent work without a human adding the `Agent` label. No code-execution path from model output. - No injection surface: titles/bodies flow through `jq --arg` into the payload; the backtick in the `ceo_file_proposal` blurb is inside single quotes so it's literal, not command substitution. - No `.forgejo/`/`.github/workflows/` changes. - Description claims map to real diff changes (directive, harness cap at 2, throttle, marker, tests for 0/1/2/malformed/clamp/throttle/file payload). CI green. **Reservations a human should weigh** - **`tick.sh` integration is unverified by the diff.** The new block reads `proposals=$(jq -c '.issues // []' <<<"$parsed")`, but `$parsed` is never shown being assigned in the diff — it's assumed to be the parsed-response var that also feeds `$subject`/`$body`. If that var is named differently, proposals silently never file (`bash -n` and the shown tests won't catch this, since none exercise `do_ceo_tick`). The "validated live against porksicle" note suggests it works end-to-end, but that's the author's word, not something I can confirm. Please confirm `$parsed` is correct / the wiring actually fires. - **Throttle is stricter than the "un-triaged" framing.** `ceo_open_proposals_count` counts *any* open issue carrying `CEO_PROPOSAL_MARKER`. The marker is never removed on greenlight (the body instructs "add `Agent` label and unassign" only), so a greenlit, in-progress proposal still counts as "open" and blocks the next batch until it's closed. That errs safe (no flooding), but it's not just "un-triaged" ones gating — confirm that's intended. - **`_ceo_parse_issues` relies on multi-char `RS` regex semantics** (`awk 'BEGIN { RS = "===ISSUE===" }'`). gawk treats this as a regex (works; `=` is literal), but BWK awk / older mawk use only the first `RS` char, which would split on every `=` and corrupt parsing. CI green confirms the CI environment is fine, but it's non-portable if operator hosts ship a different awk. A note/guard would harden it. **Minor** - `ceo_open_proposals_count` uses `limit=50` with no pagination; on a repo with >50 open issues a proposal could be missed and a new batch filed prematurely. Acceptable edge case for now. None of these are clear defects I can pin down, but the untested integration path plus the unverifiable `$parsed` reference are exactly the kind of thing I'd want a human to glance at before merge. --- <sub>Independent review by the harness on `claude-opus-4-8`. The human reviewer is requested once Igor has reviewed; a human still merges.</sub> <!-- review sha=a6136bcaa6d89be5b234050c67057a654adacd4f verdict=COMMENT ci=success -->
igor added spent time 2026-06-25 16:10:10 +00:00
2 minutes 10 seconds
joshtronic deleted branch feat/ceo-agency 2026-06-25 16:43:51 +00:00
joshtronic approved these changes 2026-06-25 16:59:00 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No assignees
2 participants
Notifications
Total time spent: 3 minutes 46 seconds
igor
3 minutes 46 seconds
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
joshtronic/igor!247
No description provided.