Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions .archon/commands/maintainer-review-gate.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ You are the **gatekeeper** for a single GitHub PR. Your job is to decide whether

## Phase 1: LOAD INPUTS

Three sources of upstream context, all already gathered. Each is provided inline below β€” no extra tool calls needed to fetch them.
Three sources of upstream context, all gathered for you below. **You may also `cat .github/PULL_REQUEST_TEMPLATE.md` if you need to compare the PR body's structure against the project's template** β€” that's the one allowed extra read; everything else lives in the inputs below.

### PR data (gh pr view JSON)

Expand All @@ -23,11 +23,11 @@ $fetch-pr.output

### PR diff (truncated to 2500 lines)

```
```text
$fetch-diff.output
```

### Maintainer context (direction.md, profile.md, prior state, recent briefs)
### Maintainer context (direction.md, profile.md, prior state, recent briefs, clock)

```json
$read-context.output
Expand All @@ -38,6 +38,8 @@ Inside `read-context.output`:
- `profile` β€” the running maintainer's profile.md (role, scope, current focus)
- `prior_state` β€” last morning-standup state.json (carry_over may already mention this PR)
- `recent_briefs` β€” last 3 daily briefs (look here if this PR was previously flagged)
- `today` β€” today's local date as `YYYY-MM-DD` (deterministic, set by the gather script)
- `deadline_3d` β€” today + 3 calendar days, `YYYY-MM-DD` (precomputed for the decline comment's reply window)

---

Expand Down Expand Up @@ -74,7 +76,7 @@ Was `.github/PULL_REQUEST_TEMPLATE.md` filled in?
- **partial**: Template structure present but several sections empty or perfunctory ("N/A", "TBD", or single-word answers where prose is expected).
- **empty**: No template, or template skeleton with all sections blank.

The PR body is in `pr_data.body`. The template lives at `.github/PULL_REQUEST_TEMPLATE.md` β€” read it if needed to compare structure.
The PR body is in `pr_data.body`. If you need the template's expected structure for comparison, that's the one allowed extra read: `cat .github/PULL_REQUEST_TEMPLATE.md`.

---

Expand Down Expand Up @@ -152,7 +154,9 @@ Adapt the wording. Don't paste the templates verbatim if the situation is more n

### Compute DATE-3-DAYS-OUT

Today is the date in `read-context.output.prior_state.last_run_at` if available, otherwise today's actual date. Add 3 calendar days. Format as `YYYY-MM-DD` (e.g. `2026-04-30`).
Use `read-context.output.deadline_3d` directly β€” it's already today-plus-three-calendar-days in `YYYY-MM-DD` form, computed deterministically by the gather script (sv-SE locale β†’ ISO date in local time). Do **not** anchor to `prior_state.last_run_at`; that field can be days or weeks stale and would produce a deadline already in the past.

If for any reason `deadline_3d` is missing or empty, abort the comment draft and surface this to the maintainer in the gate-decision artifact rather than guessing.

---

Expand Down Expand Up @@ -211,12 +215,12 @@ If verdict is `decline` or `needs_split`, write the drafted comment in markdown
Allowed output shapes (Pi's parser handles either):

1. **Bare JSON** β€” preferred:
```
```json
{"verdict":"review","direction_alignment":"aligned",...}
```

2. **Fenced JSON** β€” also fine:
````
````markdown
```json
{"verdict":"review","direction_alignment":"aligned",...}
```
Expand Down
2 changes: 1 addition & 1 deletion .archon/commands/maintainer-review-report.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Write `$ARTIFACTS_DIR/final-report.md`:
- Cited direction clauses: <list>
- Comment posted to PR: yes
- Reply window: <YYYY-MM-DD>
- Awaiting-author label added: yes/no
- Awaiting-author label added: read `$ARTIFACTS_DIR/.label-applied` β€” value is `applied` or `skipped`. If `skipped`, surface why by reading `$ARTIFACTS_DIR/.label-error` (gh stderr) and include a one-line explanation. **Do not say `yes` if the file says `skipped`** β€” say `no, label add failed: <reason>` so the maintainer can decide whether to add it manually.

### If unclear branch:
- Gate could not classify confidently.
Expand Down
12 changes: 12 additions & 0 deletions .archon/scripts/maintainer-standup-read-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,23 @@ if (existsSync(briefsDir)) {
}
}

// Deterministic clock β€” emit today's local date + a precomputed 3-day-out
// deadline so downstream prompts don't have to do calendar arithmetic
// (LLMs are unreliable at it) and don't anchor to stale prior_state.last_run_at
// (which can produce past deadlines on long gaps between runs).
const todayDate = new Date();
const today = todayDate.toLocaleDateString('sv-SE'); // YYYY-MM-DD local
const deadlineDate = new Date(todayDate);
deadlineDate.setDate(deadlineDate.getDate() + 3);
const deadline_3d = deadlineDate.toLocaleDateString('sv-SE');

console.log(
JSON.stringify({
direction,
profile,
prior_state: priorState,
recent_briefs: recentBriefs,
today,
deadline_3d,
}),
);
36 changes: 31 additions & 5 deletions .archon/workflows/maintainer/maintainer-review-pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,19 @@ nodes:
- id: fetch-diff
bash: |
PR_NUM=$(cat "$ARTIFACTS_DIR/.pr-number")
# Don't redirect stderr β€” let auth / network / deleted-PR failures surface
# as a node failure rather than feeding an empty diff to the gate (which
# would produce a confident verdict on no evidence).
if ! diff_output=$(gh pr diff "$PR_NUM"); then
echo "ERROR: gh pr diff failed for PR #$PR_NUM" >&2
exit 1
fi
# Cap at 2500 lines to keep prompt size bounded; gate cares about shape, not every line.
gh pr diff "$PR_NUM" 2>/dev/null | head -2500
if [ -z "$diff_output" ]; then
echo "(empty diff β€” PR has no changes)"
else
echo "$diff_output" | head -2500
fi
depends_on: [fetch-pr]
timeout: 30000

Expand Down Expand Up @@ -274,8 +285,19 @@ nodes:
exit 1
fi
gh pr comment "$PR_NUM" --body-file "$ARTIFACTS_DIR/decline-comment.md"
# Optional: tag the PR so the morning brief can surface "awaiting author"
gh pr edit "$PR_NUM" --add-label awaiting-author 2>/dev/null || true

# Tag the PR so the morning brief can surface "awaiting author".
# Failure (label not present in repo, permissions, etc.) is non-fatal,
# but record the actual outcome so the report node doesn't claim the
# label was applied when it wasn't.
if gh pr edit "$PR_NUM" --add-label awaiting-author 2>"$ARTIFACTS_DIR/.label-error"; then
echo "applied" > "$ARTIFACTS_DIR/.label-applied"
rm -f "$ARTIFACTS_DIR/.label-error"
else
echo "skipped" > "$ARTIFACTS_DIR/.label-applied"
echo "WARN: gh pr edit --add-label failed; see $ARTIFACTS_DIR/.label-error" >&2
fi

echo "Posted decline comment to PR #$PR_NUM"
depends_on: [approve-decline]
timeout: 30000
Expand All @@ -290,8 +312,12 @@ nodes:
Gate could not classify this PR confidently. Read the raw gate output
and any artifacts in $ARTIFACTS_DIR/, then decide manually.

Approve = workflow ends here (no comment posted, no review run).
Reject = same outcome but log your reasoning in the run record.
Approve (with optional comment) = workflow ends here (no comment posted,
no review run). Your comment is captured as $approve-unclear.output and
the report node will include it.
Reject (with reason) = workflow is cancelled; reasoning is recorded in
the run.
capture_response: true
depends_on: [gate]
when: "$gate.output.verdict == 'unclear'"

Expand Down
Loading