diff --git a/.archon/commands/maintainer-review-gate.md b/.archon/commands/maintainer-review-gate.md index eb01cb4325..92e97a4691 100644 --- a/.archon/commands/maintainer-review-gate.md +++ b/.archon/commands/maintainer-review-gate.md @@ -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) @@ -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 @@ -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) --- @@ -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`. --- @@ -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. --- @@ -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",...} ``` diff --git a/.archon/commands/maintainer-review-report.md b/.archon/commands/maintainer-review-report.md index 6e892b4f16..646510a105 100644 --- a/.archon/commands/maintainer-review-report.md +++ b/.archon/commands/maintainer-review-report.md @@ -57,7 +57,7 @@ Write `$ARTIFACTS_DIR/final-report.md`: - Cited direction clauses: - Comment posted to PR: yes - Reply window: -- 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: ` so the maintainer can decide whether to add it manually. ### If unclear branch: - Gate could not classify confidently. diff --git a/.archon/scripts/maintainer-standup-read-context.ts b/.archon/scripts/maintainer-standup-read-context.ts index 02b8054701..0c4614f053 100644 --- a/.archon/scripts/maintainer-standup-read-context.ts +++ b/.archon/scripts/maintainer-standup-read-context.ts @@ -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, }), ); diff --git a/.archon/workflows/maintainer/maintainer-review-pr.yaml b/.archon/workflows/maintainer/maintainer-review-pr.yaml index 4cb14b645e..aa4a10eea3 100644 --- a/.archon/workflows/maintainer/maintainer-review-pr.yaml +++ b/.archon/workflows/maintainer/maintainer-review-pr.yaml @@ -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 @@ -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 @@ -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'"