diff --git a/automations/codex-task-preflight.md b/automations/codex-task-preflight.md index 2c8f00d..1189ec3 100644 --- a/automations/codex-task-preflight.md +++ b/automations/codex-task-preflight.md @@ -11,18 +11,44 @@ task-body skill. 1. Read the task body's `Required preflight capabilities` and the complete contracts of every named skill. 2. Confirm that every required skill, connector, CLI, native Codex operation, registry, and source is - available. Exercise required read operations with the smallest safe probe. When Codex task - management is required, require usable current-host listing and exact task read-back; an error, - malformed result, or coverage that cannot be proved is a failed preflight, not an empty state. + available. Exercise required read operations with the smallest safe probes, and record each probe + in an attempt ledger: + - Keep every CLI network probe as one isolated direct read. Do not hide it in a compound shell + script, conditional, pipeline, or unrelated file-read command. + - When GitHub connector or CLI reads are required, read and apply the complete + [`GitHub Read Access`](../references/github-read-access.md) contract. Prove the connector and CLI + independently, use shell variants only for command or profile initialization in authorized host + context, and record the first complete provider route that passed. + - Correct one caller-originated argument-validation mistake and retry that operation once. For a + transport error, hang, malformed result, or unavailable current-host source, make at most three + total attempts. Never treat repeated identical failures as progress. + - When a restricted execution result leaves network access and authentication ambiguous, retry + once through an available authorized host execution context using the same isolated read. Apply + the GitHub-specific provider sequence above when applicable. Require the retry to succeed with the + expected identity, record that context for later task-body CLI reads, and fail closed when the + context is unavailable or the retry fails. + - When Codex task management is required, require a trustworthy current-host listing and exact + task read-back. A valid listing that reaches its supported maximum without a cursor, total, or + completeness marker proves the operation is usable but not that coverage is complete. Repeat a + saturated listing once when the task requires complete coverage. Let the owning task-body skill + decide whether confirmed incomplete coverage is a reported limitation or a task-specific + readiness failure; continuation is allowed only when that skill explicitly permits it. + - Fail immediately on an identity mismatch or ambiguous exact task identity. An error or malformed + final result is a failed preflight, not an empty state. 3. Make no mutation during preflight. Do not probe a write operation by changing state; verify that it is exposed and leave its complete safety gates to the owning skill. -If any required capability is missing, fails, or remains ambiguous, stop without starting the task -body or any fallback workflow. Return only `# AUTOMATION ERROR โ€” ` with: +If any required capability or task-specific readiness requirement is missing, fails, or remains +ambiguous after its bounded attempts, stop without starting the task body or any fallback workflow. +Return only `# โŒ AUTOMATION ERROR โ€” โ€” `, followed immediately by +one quoted sentence explaining why the task body did not run, with: - `## Automation`: managed automation id and intended outcome; +- `## Attempts`: ordered probes, corrected inputs, execution context, sanitized results, and whether + any repeated result confirmed a persistent failure or saturation; - `## Failed Preflight`: failed phase and exact capability; -- `## Evidence`: observed error, malformed result, or incomplete coverage without invented details; +- `## Evidence`: observed error, malformed result, or task-specific incomplete coverage without + invented details; - `## Impact`: which task-body operations were not attempted; - `## Remediation`: the smallest evidence-backed next step; and - `## State Changes`: confirm exactly what remained unchanged. diff --git a/automations/daily-work-plan.md b/automations/daily-work-plan.md index 88ff69f..c88a856 100644 --- a/automations/daily-work-plan.md +++ b/automations/daily-work-plan.md @@ -9,11 +9,16 @@ Managed automation id: `daily-work-plan`. - `$piro-plan-work` and `$piro-find-work` with their complete current contracts. - Native GitHub read operations authenticated as `pirog`, including complete issue and pull-request discovery within the approved scope, plus authenticated `gh api` access for the canonical Work size - fallback. -- Native Codex task listing and exact task reading for current-host commitment and duplicate checks. + fallback. Prove connector and CLI access independently through `GitHub Read Access`. +- A complete current-host active and pending Codex task listing plus exact task reading for commitment + and duplicate checks. A valid listing that reaches its supported maximum without pagination, a + total, or a completeness marker remains incomplete after its one confirmation read and fails this + task-specific readiness requirement. - Readable `WORK_REPOS.md`, `GOALS.md`, and `ACTORS.md` sources from the installed Piroplugin. Do not begin Plan Work when the shared automation preflight fails. +Do not substitute local session files, UI inspection, Computer History, or another non-authoritative +surface for complete native task coverage. First use `$piro-plan-work` in plan-only mode with these explicit inputs: diff --git a/automations/morning-closeout.md b/automations/morning-closeout.md index 0aee67f..ec5a61e 100644 --- a/automations/morning-closeout.md +++ b/automations/morning-closeout.md @@ -10,10 +10,19 @@ Managed automation id: `morning-closeout`. - Native Codex operations for current-host active and pinned task listing, exact task reading, archival, and archived-task read-back. - Native GitHub issue reads plus authenticated `gh api` access for the canonical Work size fallback. + Prove both providers through `GitHub Read Access` and keep the connector first. During preflight, + prove CLI identity and access only; call the issue-field endpoint later and only when one archived + issue actually requires Work size resolution. Use only listing and reading during preflight. Do not begin candidate discovery or archival when the shared automation preflight fails. +A valid current-host listing that reaches its supported maximum without pagination or completeness +evidence is usable but incomplete for this workflow, not a missing capability. Process only exact +visible candidates and state `active-task discovery was incomplete` in the final report. A malformed +listing, an unavailable current-host source, a failed exact task read, or ambiguous task identity +still fails closed. + Work only on Codex tasks visible on the current local host. Inspect active tasks and pinned tasks before selecting any candidate. Never target this currently running task. diff --git a/references/github-issue-work-size.md b/references/github-issue-work-size.md index e224c50..5dbf135 100644 --- a/references/github-issue-work-size.md +++ b/references/github-issue-work-size.md @@ -15,7 +15,8 @@ GitHub Projects access, or changes authentication. case-insensitively. Use the connector result only when it resolves to one canonical value under the interpretation rules below. 3. When connector-native Work size is absent, unsupported, conflicting, or not exposed, call the - canonical repository issue-field endpoint through the existing `gh` authentication path: + canonical repository issue-field endpoint through the CLI route already verified by + [`GitHub Read Access`](./github-read-access.md): ```sh gh api \ diff --git a/references/github-read-access.md b/references/github-read-access.md new file mode 100644 index 0000000..45f9f09 --- /dev/null +++ b/references/github-read-access.md @@ -0,0 +1,78 @@ +# GitHub Read Access + +Use this contract whenever a Piroplugin workflow requires native GitHub reads, authenticated GitHub +CLI reads, or both. This is a read-only access check. It never changes authentication, connector +configuration, repository state, or GitHub objects. + +Prove connector access and CLI access independently. A working connector does not prove that `gh` +can reach the API, and a working CLI does not replace a connector-first workflow. +Try each declared provider route at most once unless an owning preflight supplies a smaller total +transient-retry budget. Do not restart the sequence after a route succeeds or a final identity +mismatch occurs. + +## Provider Sequence + +1. When the workflow requires the native GitHub connector: + - read the authenticated connector login and require the expected actor; + - perform the smallest exact repository, issue, or pull-request read required by the workflow; and + - stop on an identity mismatch, unavailable connector, malformed result, or inaccessible required + source after the owning workflow's bounded transient attempts. + +2. When the workflow requires GitHub CLI access, keep every probe as a separate direct read: + + ```sh + gh auth status + ``` + + ```sh + gh api user --jq .login + ``` + + Require both commands to succeed and the API login to equal the expected actor. `gh auth status` + alone does not prove live API transport. Do not place either command in a compound shell script, + conditional, pipeline, or unrelated file-read command. + +3. Treat an invalid-token message paired with a connection error, a working connector, or another + contradictory access result as a network/auth ambiguity rather than proof that the token is bad. + Retry the same two direct reads once through an available authorized host execution context. + +4. A shell wrapper changes command and profile initialization; it does not grant network access. Use + these routes only in authorized host context and only when direct execution cannot resolve `gh`, + its configuration, or required login-shell initialization. Try them in order, keeping the auth and + API reads separate at every route: + + ```sh + /opt/homebrew/bin/zsh -lc 'gh auth status' + /opt/homebrew/bin/zsh -lc 'gh api user --jq .login' + ``` + + ```sh + /opt/homebrew/bin/zsh -ilc 'gh auth status' + /opt/homebrew/bin/zsh -ilc 'gh api user --jq .login' + ``` + + Do not cycle shell wrappers inside a restricted context as transport retries. If direct execution + already resolves the CLI, prefer context recovery over shell variation. Shell-resolution routes + do not extend an owning preflight's transport-retry budget; use them only when the prior authorized + route failed before API transport because the CLI, configuration, or login initialization could + not be resolved. + +5. Record the connector route and the first complete CLI route that passed. Reuse that exact CLI + execution route for later task-body `gh api` reads. If the workflow requires both providers, do not + continue until both pass. + +6. Fail closed when every permitted route fails, the expected actor cannot be proved, the authorized + host context is unavailable, or the required exact source remains unreadable. Preserve sanitized + command errors and never run `gh auth login`, `gh auth logout`, token replacement, connector + reconfiguration, or another authentication mutation as remediation. + +## Static Scenarios + +- Connector identity and an exact connector read succeed; direct CLI auth and API identity succeed. +- Connector reads succeed, restricted CLI results conflict, and the authorized direct CLI route + succeeds; record and reuse the authorized direct route. +- Authorized direct execution cannot resolve required login initialization, but the authorized + `zsh -lc` route succeeds; record and reuse that route. +- Connector reads succeed but every CLI route fails; a workflow that requires CLI fallback stops, + while a connector-only workflow may continue under its own contract. +- Any provider reports the wrong actor; stop immediately without trying to reauthenticate. diff --git a/skills/automation/SKILL.md b/skills/automation/SKILL.md index 5b9d0a3..c7671e9 100644 --- a/skills/automation/SKILL.md +++ b/skills/automation/SKILL.md @@ -109,6 +109,13 @@ file-backed prompts for their longer workflow contracts. the prompt or its owning skill. Limit shared preflight to operational capabilities, identity, access, readable sources, and trustworthy operation results. Do not turn source formatting, optional metadata, or domain-specific candidate quality into a preflight gate. + - Keep network probes as isolated direct reads. Bound argument correction, transient retries, and + authorized-context retry exactly as the shared preflight declares, and preserve an ordered + attempt ledger for failures. + - Distinguish a missing or untrustworthy operation from a valid supported result-set limit. Defer + the limit's task-specific meaning to the owning prompt and skill: a workflow may continue with an + explicit limitation only when its owner permits it, while a workflow requiring complete coverage + must stop before its task body. 2. Validate the complete manifest before inspecting or changing app state: @@ -180,6 +187,8 @@ digest approval. - [`../../AUTOMATIONS.yaml`](../../AUTOMATIONS.yaml): repository-owned desired automation state. - [`../../automations/codex-task-preflight.md`](../../automations/codex-task-preflight.md): shared capability-based preflight and automation-error contract for Codex task-management runs. +- [`GitHub Read Access`](../../references/github-read-access.md): connector, direct CLI, + authorized-context, and login-shell recovery contract for read-only GitHub access. - [`scripts/automation-task.js`](./scripts/automation-task.js): thin validation and deterministic planning command. - [`../../lib/automation-manifest.js`](../../lib/automation-manifest.js): manifest, prompt-file, @@ -199,7 +208,9 @@ digest approval. 04:00 and 05:00 local time. - Run the focused automation tests and confirm valid schedule variants, schema rejection, the 25-line inline prompt boundary, prompt and preflight containment, deterministic preflight-first - composition, marker conflicts, project resolution, deterministic digests, and every planned + composition, bounded attempt and authorized-context rules, independent GitHub connector and CLI + provider recovery, consumer-specific saturated-list handling, the exact automation-error heading + and attempt ledger, marker conflicts, project resolution, deterministic digests, and every planned lifecycle action. - Run `bun run test` followed by `bun run lint`. - Run `bun run codex:validate`, then the repository's `codex:check` / `codex:sync` / diff --git a/skills/morning-closeout/SKILL.md b/skills/morning-closeout/SKILL.md index e88bc28..665bcc6 100644 --- a/skills/morning-closeout/SKILL.md +++ b/skills/morning-closeout/SKILL.md @@ -72,7 +72,9 @@ turns a failed candidate into an abandonment decision, or mutates external deliv 2. List active and pinned Codex tasks using the broadest supported current-host listing. Record whether discovery is complete. If a native limit or unavailable pagination prevents complete coverage, report that limitation and process only candidates whose exact identities are visible; - never claim a complete clean slate. + never claim a complete clean slate. A valid capped result is a usable operation and not a + preflight capability failure for this workflow. State `active-task discovery was incomplete` in + the final report. 3. Exclude the calling task, every running or pending task, every pinned task, tasks on another host, and entries whose exact task id or environment cannot be read back. Do not change state to @@ -98,7 +100,9 @@ turns a failed candidate into an abandonment decision, or mutates external deliv 6. For an ineligible candidate, retain it and record every failed gate plus the exact state that remains. Continue with independent candidates because a preservation-gated refusal changes no candidate state. Stop the entire run on a native task-operation failure, identity mismatch, or - other systemic failure that makes later reads or mutations unsafe; list all unattempted ids. + other systemic failure that makes later reads or mutations unsafe; list all unattempted ids. A + malformed listing, unavailable current-host source, failed exact task read, or ambiguous task + identity is systemic; a valid supported listing limit by itself is not. 7. For every task verified as archived, record its exact id and displayed title, candidate class, repository when applicable, exact issue or pull-request source when present, delivered outcome, @@ -161,6 +165,8 @@ single-task preservation logic out of Clean Up Task or add direct worktree recla - [`../../AUTOMATIONS.yaml`](../../AUTOMATIONS.yaml): managed report ids and schedules. - [`../../automations/morning-closeout.md`](../../automations/morning-closeout.md): scheduled invocation contract. +- [`GitHub Read Access`](../../references/github-read-access.md): independent connector and CLI + identity, access, and execution-route verification. - [`GitHub Issue Work Size Resolution`](../../references/github-issue-work-size.md): shared native provider order, canonical value interpretation, exclusions, and reporting contract. - [`agents/openai.yaml`](./agents/openai.yaml): Codex presentation and explicit-invocation policy. @@ -174,6 +180,9 @@ single-task preservation logic out of Clean Up Task or add direct worktree recla - Confirm static scenarios cover a mixed list containing the caller, a running task, a pinned task, one eligible worktree task, one blocked worktree task, an exact prior managed report, and a title-only report lookalike. Only the two preservation-gated exact candidates may archive. +- Confirm a valid 50-result listing with no pagination processes only exact visible candidates and + reports `active-task discovery was incomplete`. Confirm malformed results, unavailable current-host + sources, failed exact reads, and ambiguous identities stop before archival. - Confirm capacity follows the shared provider sequence, deduplicates one issue referenced twice, and covers connector-native success, endpoint success, missing, conflicting, unsupported, and personal-repository `HTTP 404` results without Projects GraphQL or an unqualified zero total. diff --git a/skills/plan-work/SKILL.md b/skills/plan-work/SKILL.md index 7bb4380..8dc19c0 100644 --- a/skills/plan-work/SKILL.md +++ b/skills/plan-work/SKILL.md @@ -128,10 +128,14 @@ gap that native GitHub and Codex operations cannot handle. semantic cleanup would materially help and the user explicitly requests it; do not recommend it solely for formatting and never run it during planning. -6. List active and pending Codex tasks and map only exact canonical GitHub sources from their original - assignments or explicit current outcomes. Mark a candidate as an existing commitment when one - unambiguous live task already owns it. Do not create a duplicate because its task is idle, blocked, - or awaiting input. Report ambiguous task associations instead of guessing. +6. List active and pending Codex tasks completely and map only exact canonical GitHub sources from + their original assignments or explicit current outcomes. A valid listing that reaches its + supported maximum without pagination, a total, or a completeness marker does not prove complete + commitment coverage; stop before capacity calculation or recommendations rather than risking a + hidden duplicate or under-counted Work size. Do not substitute local session files, UI inspection, + Computer History, or another non-authoritative surface. Mark a candidate as an existing commitment + when one unambiguous live task already owns it. Do not create a duplicate because its task is idle, + blocked, or awaiting input. Report ambiguous task associations instead of guessing. 7. Classify issue readiness before ranking: - **actionable:** open, assigned, understandable in outcome and next useful step, sufficiently @@ -251,6 +255,8 @@ gap that native GitHub and Codex operations cannot handle. ## Checkpoints - GitHub identity is `pirog`; candidate discovery is complete or explicitly reported incomplete. +- Active and pending Codex commitment discovery is complete; a saturated native result without + completeness evidence stopped before capacity calculation and recommendations. - Every plan records a current explicit include or exclude choice for every current-invocation decision scope in `WORK_REPOS.md`. - The goal basis, milestone filters, repository scope, priority-repository signal, capacity, and @@ -285,6 +291,8 @@ gap that native GitHub and Codex operations cannot handle. - [`GOALS.md`](../../GOALS.md): reviewed fallback direction, priorities, deferrals, and decision rules. - [`WORK_REPOS.md`](../../WORK_REPOS.md): reviewed priority repositories, discovery scopes, per-invocation decisions, narrowing, and authority boundaries. +- [`GitHub Read Access`](../../references/github-read-access.md): independent connector and CLI + identity, access, and execution-route verification. - [`GitHub Issue Work Size Resolution`](../../references/github-issue-work-size.md): shared native provider order, canonical value interpretation, exclusions, and reporting contract. - [`$piro-work-on-task`](../work-on-task/SKILL.md): exact per-source Codex task creation, assessment, @@ -305,8 +313,9 @@ gap that native GitHub and Codex operations cannot handle. choice, exact narrowing, excluded-repository conflicts and raw-hit filtering, direct-evidence ranking, shared Work size connector and endpoint success, missing, unsupported, conflicting, and personal-repository `HTTP 404` results without Projects GraphQL, active-task deduplication, `13` and - `21` handling, pull-request attention, exact-once candidate dispositions, incomplete pagination, - capacity-driven task counts, and natural exact-selection authorization. + `21` handling, pull-request attention, exact-once candidate dispositions, incomplete GitHub + pagination, saturated Codex commitment listing, capacity-driven task counts, and natural + exact-selection authorization. - Prove discovery with bounded read-only fixtures; select nothing and confirm no task or GitHub mutation. Prove queue mode only after separate authorization for exact disposable sources, then retire them through their owning workflows. diff --git a/test/automation-manifest.spec.js b/test/automation-manifest.spec.js index 68d827c..2677416 100644 --- a/test/automation-manifest.spec.js +++ b/test/automation-manifest.spec.js @@ -1,6 +1,6 @@ import assert from 'node:assert/strict'; import { execFile, spawn } from 'node:child_process'; -import { mkdir, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises'; +import { mkdir, mkdtemp, readFile, rm, symlink, writeFile } from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; import { promisify } from 'node:util'; @@ -81,16 +81,18 @@ describe('lib/automation-manifest', () => { assert.equal(automations.get('morning-closeout').status, 'ACTIVE'); assert.equal(automations.get('morning-closeout').name, '๐Ÿงน MORNING CLOSEOUT'); assert.equal(automations.get('morning-closeout').projectId, null); - assert.match(automations.get('morning-closeout').prompt, /# AUTOMATION PREFLIGHT/); - assert.match(automations.get('morning-closeout').prompt, /# AUTOMATION ERROR/); - assert.match( - automations.get('morning-closeout').prompt, - /Preflight proves that the automation/, - ); - assert.match(automations.get('morning-closeout').prompt, /Do not begin candidate discovery/); + const morningPrompt = automations.get('morning-closeout').prompt; + assert.match(morningPrompt, /# AUTOMATION PREFLIGHT/); + assert.match(morningPrompt, /# โŒ AUTOMATION ERROR/); + assert.match(morningPrompt, /## Attempts/); + assert.match(morningPrompt, /Preflight proves that the automation/); + assert.match(morningPrompt, /at most three\s+total attempts/); + assert.match(morningPrompt, /authorized host execution context/); + assert.match(morningPrompt, /GitHub Read Access/); + assert.match(morningPrompt, /active-task discovery was incomplete/); + assert.match(morningPrompt, /Do not begin candidate discovery/); assert.ok( - automations.get('morning-closeout').prompt.indexOf('# AUTOMATION PREFLIGHT') < - automations.get('morning-closeout').prompt.indexOf('# MORNING CLOSEOUT'), + morningPrompt.indexOf('# AUTOMATION PREFLIGHT') < morningPrompt.indexOf('# MORNING CLOSEOUT'), ); assert.equal( automations.get('morning-closeout').rrule, @@ -99,13 +101,17 @@ describe('lib/automation-manifest', () => { assert.equal(automations.get('daily-work-plan').status, 'ACTIVE'); assert.equal(automations.get('daily-work-plan').name, '๐Ÿ“‹ DAILY WORK PLAN'); assert.equal(automations.get('daily-work-plan').projectId, null); - assert.match(automations.get('daily-work-plan').prompt, /# AUTOMATION PREFLIGHT/); - assert.match(automations.get('daily-work-plan').prompt, /# AUTOMATION ERROR/); - assert.match(automations.get('daily-work-plan').prompt, /Preflight proves that the automation/); - assert.match(automations.get('daily-work-plan').prompt, /Do not begin Plan Work/); + const dailyPrompt = automations.get('daily-work-plan').prompt; + assert.match(dailyPrompt, /# AUTOMATION PREFLIGHT/); + assert.match(dailyPrompt, /# โŒ AUTOMATION ERROR/); + assert.match(dailyPrompt, /## Attempts/); + assert.match(dailyPrompt, /Preflight proves that the automation/); + assert.match(dailyPrompt, /complete current-host active and pending Codex task listing/); + assert.match(dailyPrompt, /fails this\s+task-specific readiness requirement/); + assert.match(dailyPrompt, /GitHub Read Access/); + assert.match(dailyPrompt, /Do not begin Plan Work/); assert.ok( - automations.get('daily-work-plan').prompt.indexOf('# AUTOMATION PREFLIGHT') < - automations.get('daily-work-plan').prompt.indexOf('# DAILY WORK PLAN'), + dailyPrompt.indexOf('# AUTOMATION PREFLIGHT') < dailyPrompt.indexOf('# DAILY WORK PLAN'), ); assert.equal( automations.get('daily-work-plan').rrule, @@ -113,6 +119,22 @@ describe('lib/automation-manifest', () => { ); }); + it('should define bounded GitHub connector and CLI recovery', async () => { + const contract = await readFile( + path.join(REPO_ROOT, 'references', 'github-read-access.md'), + 'utf8', + ); + + assert.match(contract, /Prove connector access and CLI access independently/); + assert.match(contract, /gh auth status/); + assert.match(contract, /gh api user --jq \.login/); + assert.match(contract, /authorized host execution context/); + assert.match(contract, /zsh -lc/); + assert.match(contract, /zsh -ilc/); + assert.match(contract, /Do not cycle shell wrappers inside a restricted context/); + assert.match(contract, /never run `gh auth login`/); + }); + it('should resolve prompt files only from automations', async () => { const repoRoot = await createRepo({ automations: [