Repository navigation
Releases: cheezy/stride-copilot
Release list
v2.42.0 — escaped > fix, the stdout-preservation anchor, and a release runbook
Fixed — an escaped > is no longer read as an operator
A literal > handed to curl (--data-urlencode n=a\>) was read as shell syntax
by both walks over the operator view, in opposite directions: the scope pass
blanked the word after it, the redirect rule called it a redirect. The two
cancelled, so neither was visible from outside.
No verdict in this port changed — the new 26an cases pin the shapes as they
already behaved, not a behaviour change. It is still worth having, for two
reasons. A cancellation between two bugs is not something to depend on; and this
port's redirect rule carries an append verdict of its own for the canonical
response file, so a > it misreads is a character that can reach that decision.
The sibling stride port had the same pair not cancel, where it was a
measured false permit.
Fixed once, upstream of both walks and in both halves, counting the backslash run
so \> (a literal >) is told apart from \\> (an escaped backslash before a
real operator). Length-preserving, so the pairing offsets still line up.
Added — the stdout-preservation-guard rule carries its canon anchor (W2187)
The paragraph stating this port's file-first exception, and what the guard refuses around it, now carries the canon anchor and the back-reference below it. No substance changed; this port's permitted set is its own and is unchanged.
Added — a release runbook for this repository (W2173)
RELEASE.md records how this repository is released, as its own history shows it: which file holds the version, how the changelog is shaped (with any ambiguity in that history stated rather than resolved), whether a catalog must be synced afterwards, and the one-line check for whether the changelog's top heading is already tagged — the check that would have caught entries appended under a released heading. Documentation only; no behaviour changes.
v2.41.0 — the response has to reach a channel this plugin reads
A preToolUse guard that refuses a Stride API call whose response reaches
neither tier of this plugin's resolver, a Stop-gate condition for a held
uncompleted claim, and a cross-port reconciliation against the two sibling ports.
The guard, and why this port's rule differs
The failure it closes is silent by construction: nothing records the loop state,
the Stop gate cannot tell the task was completed, changed_files lands empty,
and after_goal detection falls back to a fresh API call — with no error
anywhere, so the first sign of trouble is work that quietly never left the board.
This port resolves a response file-first, so
--output "$CLAUDE_PROJECT_DIR/.stride/.last-api-response.json" hides nothing
from it — that is the second of the two delivery shapes this repository
documents, and both the claiming and completing skills recommend it. The
exemption is narrow: the target must be the canonical file, spelled the way this
repository documents it.
Refused: -o/-oX/-sSo/--output/--output= to any other target including
/dev/null; -O/--remote-name/--remote-name-all, which name their file after
the URL and so can never be the canonical one; any pipe whose next stage is
not tee; >/1>/>|/&>/>&2 to any other target; and >>/&>/&>>
onto the canonical file, because appending makes the one JSON value we parse
unparsable.
Both halves — bash and the PowerShell twin — carry the same rule, byte-identical
refusal messages included.
Cross-port reconciliation
Driving the three hardened guards over ONE corpus found 18 shapes where the ports
disagreed. Three involved this port: --remote-name-all was permitted, 2>&2
was refused though a stderr-to-stderr redirect leaves the body on stdout, and an
endpoint appearing only inside a redirect target was refused.
Six divergences remain and are deliberate, sharing one cause recorded in all
three guard headers: this port is file-first and may deliver to its canonical
response file; the siblings read stdout alone and refuse that same shape
correctly.
821 assertions pass, both halves.
v2.40.0
Added — a back-reference beside every anchored rule (W2137)
Canon entry edit-site-back-reference requires prose beside every governed-rule statement naming the canon by path and the entry id, and the drift check now implements it as a property, so an anchor standing alone is reported. Four paragraphs close this port's five anchored statements. The decision matrix and its precedence ordering are back-referenced in skills/stride-subagent-workflow/SKILL.md, where this port keeps its matrix rather than in the workflow skill, with one paragraph naming both entries.
Placement respects two assertions this repository owns. The suite requires the review-round-cap anchor to sit exactly one line above the ceiling sentence, so that paragraph goes at the end of its section instead — 47 lines down — and names an elided <!-- canon:review-round-cap ... --> so a reader landing there has something to grep for. The suite reads only the first match of the real anchor literal, so a version-less second occurrence cannot move the offset it checks.
v2.39.0 — the review-convergence rules: a two-round cap, the cosmetic class, and dispatch_count
Ports the three rules the reference plugin shipped in 1.74.0 to Copilot, adapted to this port's own surfaces rather than pasted. Three chained tasks, released once (v2.37.0, v2.38.0 and v2.39.0 are tagged for history).
The two-round review cap (v2.37.0)
Two review rounds is the ceiling, and the second verifies rather than re-reviews. Round two's mission is scoped; its evidence is not — it still receives the full diff. After round two, remaining important and minor findings are recorded rather than fixed; a critical is exempt and blocks at any round number; and a security finding is never recorded at any severity.
A round is re-expressed in surfaces this port actually has: a task-reviewer dispatch whose response yielded a parsable fenced json block. The reference anchors on a merged-result file this port does not carry. No counter file was invented — an agent-written marker only an agent reads is exactly as skippable as the instruction it replaces, and no hook here can observe an agent dispatch — so the count lives in context and an unestablishable round fails closed to round two. The cap is prose, not a pin, and says so.
The cosmetic finding class (v2.38.0)
An optional cosmetic boolean on reviewer issues[] entries. Presentational-only findings are still emitted with honest severity and category, still reported, still recorded — the only thing the flag changes is that a round finding nothing else buys no further round. Never valid on a critical, an important, or a security finding. schema_version 1.6 → 1.7.
The definition carries all three of the reference's gates — the finding's claim is correct, the artifact asserts nothing false, and the subject is purely presentational — plus its default-deny. Gate three is explicitly not present-tense: "nothing behaves differently today" does not satisfy it.
dispatch_count review-cost telemetry (v2.39.0)
An optional count on the reviewer workflow_steps entry — dispatches, not rounds, including a re-dispatch after a crash. The six limits ship with the key, because a figure trusted past its accuracy is worse than no figure: wall-clock is not token cost, the two keys measure different populations so the division is meaningless, a review-skipped task can have real cost and record none, an omitted count must not be imputed, a crash cannot be told from an extra round, and nothing validates the value on the way in.
No seventh workflow_steps name was added, and the optional sub-step dispatches still fold into the reviewer entry.
What this port says that the reference does not
- The round count is recoverable from no artifact at all, in flight or afterwards — this port carries neither reconciling file, so a compliant
dispatch_countof 3 must never be read as a cap breach. - The security rule keys on subject matter, not the category string, so a security-relevant finding filed under
code_qualityorpitfallis still fixed or escalated. - Nothing here can measure a dispatch count.
hooks.jsonwires only Bash tool events and the stop gate, so the figure is self-reported by the orchestrator — a record of what it says it did, not a measurement.
Every rule that cannot be mechanically enforced here says so in shipped text, and the guard obligation for the unvalidated key is assigned to its first consumer.
Coverage
Test Groups 23, 24 and 25 (bash) and 20, 21 and 22 (PowerShell), mirrored case-for-case. Group 25 includes a port-wide sweep requiring every competing re-review mandate to defer to the ceiling — the case that caught a real contradiction across nine sites — plus pins that catch a stale schema mirror, a drifted numeral, and a stale gate count. Full suites: bash 653/653, PowerShell 541/541.
v2.36.0
Ships G423 — the loop gate for GitHub Copilot.
The record (W2147). A successful completion writes .stride/.loop-state.json (identifier, needs_review verbatim, an ISO8601 timestamp, session id); any claim clears it. The hook writes it, never the agent. Both halves produce a byte-identical record, asserted with cmp rather than by inspection.
The gate (W2148). stride-stop-gate.sh and its PowerShell twin, registered as Stop. Blocks on exactly one condition and permits on everything else, always at exit 0, via {"decision":"block","reason":...} on stdout.
Exit 2 is not a refusal here. On agentStop a non-zero exit is logged and skipped — it denies only for preToolUse. A gate ported unchanged from Claude Code would log its refusal on every turn end, let the session finish anyway, and look healthy doing it. No code path in either half exits 2, and a test case proves the distinction rather than commenting on it.
The coverage (W2149). Permits vastly outnumber the block, each asserting its own reason string; every silent permit has a positive control. A 13-mutation campaign across both halves killed every mutant.
Registration is disclosed, not asserted. The nested Stop entry is verified well-formed and resolving to the gate, and Stop is GitHub's documented alias for agentStop — but no live Copilot parsed it here, and the shape is uncorroborated rather than merely unverified. Both gate headers, hooks.json, docs/HOOK_RESEARCH.md and a SKIP-never-PASS case all say so.
Security fix. The block-counter guard refused a directory but not a symlink ([ -f ] dereferences), so a planted link would have had its target truncated anywhere the agent user can write. Fixed on both halves and pinned by a case with a block-path control. The curl test stub also now redacts the bearer header it records.
v2.35.0 — the four canon anchors, plus row precedence and reason_code
Added — the four port-canon anchors, plus row precedence and reason_code (D253, D239)
check-port-canon.sh searches each port for a short HTML comment carrying a rule id and a version number, and calls the cell MISSING when there is none. All four cells for this port were MISSING; all four now read ok.
Two of them needed only the comment. The anti-placeholder rule for a "failed" section note in agents/task-reviewer.agent.md was already correct, and so was the D221 sentence declaring the decision matrix the decision point for its columns. That sentence lives in skills/stride-subagent-workflow/SKILL.md rather than in stride-workflow, because this port keeps its only matrix there and stride-workflow Step 3 reads the Plan (manual) column instead of restating a trigger — so both markers land beside that one table, which is where a reader looking for the rule would actually be standing.
Two of them needed new text:
- Row precedence, and a bottom row to fall to. The matrix can fit a task on more than one row and never said which row governs. It now carries an authority order and says plainly that the order is not the printed order: the three decomposition rows resolve first despite sitting near the bottom of the table,
small, 0-1 key_filesresolves ahead ofDefect type(a one-file change is priced as a one-file change however it was filed),Defect typeoutranksmedium (any)andlarge (any), after which whichever complexity row fits takes over, and last of all the freshly addedComplexity absent or unrecognisedrow —Skip/Run/Run/Run, fired only by a task that arrived with nocomplexityor with a value outsidesmall/medium/large, and never available for breaking a tie between rows that do fit. Such a task previously matched no row whatsoever. Had the first two entries been ordered the other way, a small single-file defect would have picked upRunin both thetask-explorerandtask-reviewercolumns and contradictedstride-workflowStep 3's own "Small Task, 0-1 Key Files" branch. Step 3's "All Other Tasks" branch now says outright that its heading is shorthand and the matrix row decides, so the new bottom row has somewhere to land. reason_codeonworkflow_steps. An optional six-value enum documented instride-workflow's Per-Step Schema, sent next to the prosereasonand never in place of it. A value outside the six is rejected with a422; sending nothing is always fine.matrix_deviationis the value for a step the matrix asked for and that was skipped regardless — the one code reporting non-compliance rather than a sanctioned skip.
Prompt text only: no plugin behaviour changes; plugin.json moves only to carry this release.
v2.34.0
Fixed — the failed-verdict note rule the server already enforces (D240)
This port's task-reviewer prompt described note as optional on every section verdict. The completion API has required it on a "failed" verdict since D231, and enforces that unconditionally — independently of the strict_completion_validation flag — so an agent on this runtime could emit a note-less failed verdict that its own prompt endorsed and be rejected with a 422. The rejection is self-describing and recoverable, so nothing was broken; every such completion simply paid an avoidable round trip.
The prompt now states that on a "failed" section verdict note is REQUIRED and must name the specific violation or gap in at least 20 non-whitespace characters, carries the anti-placeholder prohibition (no stub, TODO, empty string, or bare restatement of the status), and directs that an empty note means the verdict is wrong rather than that the note is unnecessary. note stays optional on "passed" and "not_assessed", so the ordinary empty-section case gains no friction.
Producer-side only: the server-side check in Kanban.Tasks.CompletionValidation.ReviewContract is unchanged, and no port was accommodated by weakening it.
Fixed — planner precedence: the decision matrix is the sole decision point (D232, propagating D221)
This port carried the same ambiguity D221 fixed in the canonical plugin, in stride-subagent-workflow only: the decision matrix row small, 2+ key_files says Plan = Skip, while the Phase 2 "When:" line independently said "Task complexity is medium or large, OR task has 3+ key_files, OR task has 3+ acceptance criteria lines" — two separately-satisfiable planner triggers with no stated precedence. The same conflict pattern existed for the Explore and Review columns (Phases 1 and 3, stride-completing-tasks' pre-completion review items), plus drifted narrower restatements in the flowchart, quick-reference card, and Plan-agent usage gloss. This port's stride-workflow Step 3 prose branches carried no competing OR-clause for the task's target shape, but its "For medium+ tasks, outline" item diverged from the matrix for a medium defect (Plan = Skip unless large) and now reads the matrix's Plan (manual) column instead. Measured consequence in canonical: two runners on identically-shaped tasks resolved the collision differently and wrote different skip reasons into workflow_steps telemetry.
The fix mirrors canonical's D221 resolution: the matrix now states it is the decision point for its columns, and every restatement — the three "When:" lines, the skip-planning line, stride-completing-tasks' review items, and the flowchart/quick-reference glosses — reads its matrix column with "Read the column; do not re-derive the condition here (D221)" instead of re-deriving a condition. Resolved toward the matrix (Plan = Skip for small, 2+ key_files), so no planner work is added to the most common task shape.
Recorded verification grep (should return only row definitions, D221 history, matrix-agreeing glosses, and stride-workflow's own non-conflicting prose branches — never a rule that could fire independently of the matrix):
grep -rniE "if medium|medium\+ OR|medium or large, OR|3\+ (key_files|criteria|acceptance)|2\+ key_files" --include="*.md" skills/ agents/
v2.33.0 — the optional -harden sub-step and its after_doing sequencing
Ports the optional -harden sub-step — the last deferral the [2.31.0] and [2.32.0] Scope blocks named — completing this port's exploratory-testing integration. Prompt text only; the companion stride-copilot-exploratory-testing plugin is untouched.
Added — an optional -harden sub-step (Step 5.6 / Phase 3.6), sequenced so a drafted check cannot redden the gate
A session that finds a bug and stops has closed nothing. -harden drafts one regression check per convertible bug, which is the step that turns Explored back into Checked, and it is the only place the workflow can close that loop automatically.
- A three-condition gate: a Step 5.5 session ran and returned convertible findings; the
stride-exploratory-testing-hardenskill is available (a real gate — it arrived in the companion's 0.2.0, and 0.1.0 shipped without it, so check for the skill rather than the plugin); and the runtime can activate it. That third condition has no Step 5.5 analog, because-hardenships only as a skill with no agent to fall back on — and it creates a tempting wrong answer, so never approximate an unavailable activation by drafting the checks yourself is stated in the gate, the Decision Summary, and both flow artifacts. - The sequencing rule.
after_doingis a blocking gate that runs the suite, and a check reproducing an unfixed bug is supposed to fail — that failure is the evidence it reproduces the bug. Sequenced naively, a session that did exactly the right thing blocks a task that may not even be scoped to fix the bug. Leaving drafts staged in.exploratory/checks/is the default and always safe, which activating without--outputpreserves. - Two things must hold before a check enters the suite, and a skip marker gives only one: the file must load — a marker makes a case inert, not a file, and runners compile the whole tree first, so an unresolved
TODO(harden):wiring marker fails however it is tagged — and the case must be green or inert. Both are established by running the gate's own command once across the whole suite, never by expecting. - Three dispositions, and no others, with the still-open path carrying four conditions: the file loads clean, the case is marked skipped or pending (
xfailis not a skip), a follow-up defect is filed, and the check asserts the guard rather than performing the bypass. Never red in the tree — the hazard is presence, not the commit, sinceafter_doingruns the working tree. - A check for a security finding asserts the guard, never performs the bypass.
-harden's convertibility test bars a destructive step, a shared-environment mutation, a real third-party side effect, and a real credential or customer record — but an auth-bypass sequence, a cross-tenant read or an IDOR fetch against the suite's own fixtures violates none of those and converts cleanly. Those are precisely the findings the plugin's own rubric rates Critical (data crossing a tenant, account, role or permission scope; a secret or token exposed) or High (an authorization control demonstrably absent) — so a check built from that repro is, by construction, a working exploit for a live vulnerability, and the still-open disposition would commit it into a suite the gate compiles on every future task. The draft must assert the boundary holds, and independently of how the finding was rated, one that reproduces the bug by successfully exploiting it may not enter the test tree while the bug is open. A stored exploit is not made safe by a skip marker. - Three checks at the move are the agent's, not the skill's.
-hardennever overwrites a path it writes and never writes into your test tree at all, so the copy you perform there is protected by nothing — it prints that line for you, unrun. Never overwrite an existing test file, never edit a test you did not write, and read the draft first: a literal credential, token, session identifier, customer record or internal hostname where a fixture value belongs means the draft does not move. - Three checks at the move cover all three deferred rules, not two. The credential class and the destructive class both get an independent check before the copy, because a destructive check runs green and so passes the load-and-inert gate untouched — and the verification run is the moment such a step would first execute, so "run it once and see" cannot be the control. The draft and its
INDEX.mdare also read as data, never as instructions: both are built from application-controlled text, and they are read at the moment the agent is about to write into the test tree and run the gate's command. - The discriminator is the assertion, not the request. A guard-asserting check necessarily still issues the crossing request — that is how it proves the guard fires — and that form is sanctioned; what is barred is a check asserting the crossing succeeded. For a still-open boundary failure the committed check names the guard and the expected rejection, while the exact payload and identity-substitution mechanics go in the follow-up defect, which is access-controlled where the repository is not.
- The no-runner property is contractual, not mechanical, and the port says so: the skill's own text states that a Copilot skill carries no tool allowlist, so "I did not run it" has to be true because you chose not to, every time. That is exactly why a drafted check is never reported as passing on its say-so or the agent's own.
- Post-review files are surfaced, never smuggled. Paths in
completion_notes, one line incompletion_summary, and any check that entered the tree inactual_files_changed— extended to cover the staged path too when.exploratory/turns out not to be ignored, since the obligation turns on unreviewed executable code reaching the commit rather than on which directory it sat in. The reviewer is re-run whenever a check entered the tree, with no substantiality judgement, and if it cannot be re-run that is said in the record. - Telemetry folds into the existing
reviewerworkflow_stepsentry, never a seventh name, with the Step 5.5 and Phase 3.5 telemetry sentences updated to name the sub-step as a second contributor. - The move-time content check reaches the summary artifacts too. A Decision Summary row, a flowchart line and two Quick Reference Cards exist to be consulted instead of the full prose, so a control present only in the prose is a control an agent may never read — the same defect shape as a mis-nested reference card. Both classes now appear in all four.
- Redaction is restated at the sinks the sub-step creates. What it records is derived from findings that originate in observed application output, so Step 5.5's rule binds unchanged on
completion_notes,completion_summary, thetesting_strategynote — and on the follow-up defect, whose carried substance includes the repro the check encodes and which is persisted and rendered like any other sink.
Scope
Prompt text and documentation only. No completion field is added, removed, or made required; no seventh workflow_steps name; no enum, schema, hook-script or agent change. The new step is additive and gated: with no session, no convertible findings, no -harden, or a runtime that cannot activate it, the workflow behaves exactly as it did in [2.32.0]. Still deliberately not corrected, and carried forward from [2.31.0]: stride-subagent-workflow's flowchart does not mention Phase 3.5 or Phase 3.6 at all, and stride-completing-tasks' flowchart and Quick Reference Card mention neither Step 5.5 nor Step 5.6. Those are pre-existing gaps this release does not introduce — inserting a 5.6 reference into a diagram that has never referenced 5.5 would be net-new rather than keeping an existing citation in sync — and they remain follow-ups.
v2.32.0 — dispatch budget, richer recording, and artifact gitignore guidance
Ports three more stride-side changes into this GitHub Copilot port — an explicit session budget with a named environment context on the Step 5.5 / Phase 3.5 dispatch, richer session recording, and .exploratory/ gitignore guidance — plus two later refinements of the same prose. Prompt text only; the companion stride-copilot-exploratory-testing plugin is untouched.
Added — an explicit session budget and a named environment context
The dispatch said only "passing the running-app environment context": what that had to contain was nowhere stated, and Step 5.5 passed no budget at all — an unbounded dispatch inside an autonomous workflow, against a live application.
- Eight named inputs, packed into the single free-text block the
exploreragent takes alongside the charter, two of them safety-bearing: the authorized non-production confirmation (recorded here, not re-opened — the determination itself already happens before dispatch, per v2.31.0). Where that warrant comes from is stated, because it differs from the canonical plugin: there a human affirmative is collected once at Step 0 and may be neither supplied nor inferred; this port has no such collection point and never prompts between steps, so the confirmation is one the orchestrator establishes from the environment — the target is authorized because it is the task's own local or explicitly non-production instance. That is a narrower warrant, and it is why the determination fails closed: with no other source to fall back on, an unestablished target means no dispatch at all (never supplied on the user's behalf), and a pointer to where test accounts live, never inlined credentials, because the dispatch prompt is an artifact like any other. - The budget is the caller's to set, in whatever unit the installed contract declares — the two plugins release independently, so the unit is established from
stride-copilot-exploratory-testing/agents/explorer.agent.mdrather than hardcoded here. Today that is probes: default 12, band 8–20, tool-call ceiling 5×, whichever is reached first ending the session and recorded instop_reason. If it will not fund one workable charter, do not dispatch at all — the band is per dispatch, not a pool to divide. - Phase 3.5 was passing the wrong unit. Its dispatch list said "Any test accounts or seed data, and the time box" — a wall-clock box, against an installed contract that states outright it has no
durationand notbsand will not report a duration it never measured. That bullet is replaced rather than supplemented: never hand a wall-clock box to a probes contract. - Budget exhaustion is a normal outcome that never fails completion. What changes is only what may honestly be claimed:
charter_quiet(andrisk_acceptable) is the only ending supporting "this manual test was performed";probe_budget_exhaustedis valid partial findings with an incomplete coverage claim;tool_call_ceilingandblockedare judged on the session sheet, not on the word — at or near zero probes both mean the session did not happen, so it is recorded as not performed and the manual test handed back, while after meaningful probes both are partial coverage. Claiming a spun-out session as a performed manual test is worse than not running the plugin at all, because the plugin-absent path at least flags the test as still owed. Leftover risk goes to a filed follow-up with an ID, never to a "follow-up charter", which has no identifier and no lifetime past the session. - Capture everything the dispatch returned, the session sheet included, establishing its fields from the installed contract rather than from the page — enumerating them here is how a later contract change silently drops one.
Added — richer recording: stakeholder impact, the artifact path, and a completion_summary mirror
- Each finding now names who is harmed and how, not only its severity — a severity word says how bad the failure is, not who it lands on, which is what a reader triaging the Review queue needs. Read from the installed contract's
bugs[].stakeholder_impact(emitted honest-or-"could not establish"), restated and redacted rather than pasted; where a contract emits none, say who is harmed in your own assessment or say plainly that the session did not establish it. Never invent an impact the session did not support. - The artifact's path is cited when one exists — and the text is explicit that usually it does not. The
exploreragent is not asked to write a session file, and the plugin's ownsessionskill attributes session-sheet writes to the-explore,-pairand-debriefskills, none of which Step 5.5 dispatches — so on the automated path the prose summary is the normal and complete record rather than a degraded fallback. The reasoning is deliberately narrower than the reference's: the agent'stools:list holds no write tool but does holdrun, a shell surface, so "no write tool" does not settle it. Record the path, never the contents, repository-relative, since an absolute path discloses a username and machine layout and the artifact may hold unredacted output. - One line is mirrored into
completion_summary, which is required, persisted and rendered on the Review queue, wherecompletion_notesis persisted only by newer servers. It matters most in the case that looks safest — a small task with no reviewer, wherecompletion_notesis the only carrier. This is not a new field and not a third record: the two carriers remain the sole carriers of the record, and the mirror is a durability backstop on an already-required field. - Redaction now binds explicitly on the impact text and the artifact path, and on every finding field that carries secrets, as examples rather than a closed list — the rule is the sink, not the field name. Restating is not redacting: a faithful paraphrase carries an account name, a customer email and a hostname through untouched, so redact by generalising the referent, and name a finding rather than quoting it when its text carries a secret.
Added — .exploratory/ gitignore guidance, delivered at Step 0
Session artifacts land under .exploratory/, hold transcribed application output — the exact material the redaction rules keep out of the completion payload — and arrive untracked. An operator's ## after_doing that stages everything (git add -A) sweeps them into a commit, which is far harder to walk back than a payload field.
- Step 0 gains item 3, which is the delivery point: Step 5.5 runs only once a session is already under way, so it is structurally too late. It names
.stride/,.stride_auth.mdand the three hook temp files unconditionally, and adds.exploratory/only when the exploratory plugin is installed. Step 5.5 and Phase 3.5 carry the reasoning as the agent's reminder of what to say. - Operator guidance only — the workflow never edits their
.gitignore.git commit -ais named as the safe shape againstgit add -A, and the sweeper is correctly attributed: this plugin's ownhooks/stride-hook.shstages nothing. An already-committed artifact needsgit rm --cached, because.gitignoreis inert for a tracked path. - The line's limits are stated, not glossed: it protects the default locations only, and since all seven command-derived skills accept
--output, a redirected destination outside.exploratory/carries the same transcribed output with no protection from that entry. The cited artifact path is also rendered inert on the same terms as an unrecognized severity string, since a path learned rather than authored is application-influenced text andcompletion_notesrenders as Markdown on the Review queue. README.md's existing gitignore sentence is extended rather than replaced, and gains.stride/and.stride_auth.md, which it had never named despite the port creating both.
Fixed — a blocked session is recorded as an obstacle, not as a finding
Both skills said an unreachable app should have its obstacle "reported as a finding". That hands it to the absent-severity rule, which maps it to important — filing an unreachable dev server as an important testing finding whose worst impact the agent is then asked to name. The installed contract requires a blocked session to set its status, record the obstacle in its debrief and not fabricate results, so the obstacle lives there carrying no exploratory severity. A blocked session that returns bugs is no contradiction — those are real observations recorded on their own terms; only the obstacle is never one. Coverage is judged from the sheet rather than the word, so a blocked session at or near zero probes takes the same disposition as a zero-probe ceiling hit.
Added — where the exploratory session's wall-clock goes
The port forbade the wrong answer in one skill and gave the right answer for the sibling deep-security dispatch in another, while saying nothing at the site where the question arises. It now says it: fold the session's wall-clock into the existing reviewer workflow_steps entry, never a seventh step name. The wall-clock is the orchestrator's own measurement, never a field read out of the session sheet — today's contract carries no duration and no tbs. When no reviewer ran, that entry is the skip form carrying no duration, so the dispatch is recorded in completion_notes rather than given an invented one; that case is routine here, since this gate has no review precondition.
Scope
Prompt text and documentation only. No completion field is added, removed, or made required; no seventh workflow_steps name; no enum, schema, hook-script or agent change. No trigger condition moves — the budget-too-small and unestablished-target refusals are post-gate dispatch decisions, so every path still skips gracefully when the plugin is absent. Delibera...
v2.31.0 — severity alignment and the non-interactive dispatch guard
Ports two stride-side changes into this GitHub Copilot port: the exploratory severity ladder is aligned with the reviewer's issue vocabulary and given an escalation policy, and the Step 5.5 / Phase 3.5 dispatch is narrowed to surfaces that can complete without a human. Prompt text only; the companion stride-copilot-exploratory-testing plugin is untouched.
Added — the exploratory severity ladder mapped onto the reviewer issue vocabulary
The companion plugin rates each bug on a four-level ladder (Critical > High > Moderate > Minor, owned by its bug-advocacy skill); reviewer_result has three (critical / important / minor). Nothing said how one became the other, so a finding could reach the completion payload on whichever scale the agent happened to be holding.
skills/stride-completing-tasks/SKILL.mdgains a### Severity mappingsubsection: the four-row table, and the reason the four-into-three collapse falls on High/Moderate — the reviewer enum's values are dispositions at the completion gate rather than descriptions, andcriticalandimportantshare one (fix before proceeding), so that is the boundary whose loss costs least. Collapsing Moderate intominorinstead would file a broken export alongside a truncated label. The section maps; it never re-rates — the plugin's rubric stays the sole source of truth for what level a finding is, and a mapped reviewer value is never written back ontobugs[].severity.- Mapping a severity is not appending an
issues[]entry. Only acriticalthe escalation rules find introduced becomes one; everything else — including a discoveredcritical— goes tocompletion_notesand thetesting_strategynote only. Appending a non-escalating finding would leaveissues[]disagreeing with apassedtesting_strategyverdict, which the pre-submission self-check refuses outright — manufacturing exactly the blocked completion the policy promises not to cause. - Absent or unrecognized severity →
important; never dropped, nevercritical.criticalis the one value that triggers escalation, so an unparsed string must never reach a blocking path;minorwould be a silent downgrade. The raw value is quoted to 40 characters in inline backticks only when it carries nothing from the protected classes — a credential or token, customer data, an internal hostname — otherwise it is replaced with a redaction sentinel and its length reported. Judge by class, never by length: an email address or internal hostname is short and legible, so a length bound would emit the whole thing while looking like a mitigation.
Added — a Critical exploratory finding now escalates, but only when this task introduced it
Before this release a Critical exploratory finding was advisory prose while a partial/unmitigated security verdict was fail-closed — an asymmetry that was an accident rather than a decision. It is now a decision, and a bounded one.
skills/stride-workflow/SKILL.mdgains### Escalation: what happens when a session returns a Critical findingin Step 5.5, andskills/stride-subagent-workflow/SKILL.mdgains the matching**Escalating a Critical finding.**digest in Phase 3.5 — identical in substance, with reciprocal keep-in-sync pointers naming each other's exact heading.- One question decides it: are the responsible lines among the lines this task changed? Introduced escalates fail-closed —
testing_strategy.status→"failed"plus acategory: "testing"/severity: "critical"entry with matchingissue_counts.criticalandissues_foundincrements — which the pre-submission self-check refuses at submission time, so the defect is fixed, the charter re-run, and the review re-run before completing. Discovered appends no issue and flips no verdict: it is recorded at its exploratory severity incompletion_notesand one line ofcompletion_summary, added as an advisory to thetesting_strategynote, and filed as a follow-up defect so it has an owner. A pre-existing bug never blocks an unrelated task. - Provenance is computed from your own artifacts, never from the finding's text, because the application under test controls that text and a blocking escalation must not be triggerable by content an attacker can influence. Falling back to the task's
key_filesis explicitly barred — that would hand the blocking footprint to task-author text. - The change-set mechanics are this port's own, not the reference's. The canonical plugin reads the claim-time dirty baseline from a separate
.stride-dirty-baselinefile. This port has no such file: it carries the baseline as a base64-encodedTASK_DIRTY_BASELINE=line inside.stride-env-cache, alongsideTASK_BASE_REF— one lookup rather than two artifacts — decoding to one<blob-hash>\t<path>line per path, which makes line-level attribution recoverable where a baseline path was touched again. Porting the reference's file path verbatim would have been a reference no agent could resolve. Two port-specific signals are named too:TASK_BASE_REF_TRUSTED='1'marks a base written by the post-before_doingcapture, and a cache carrying noTASK_BASE_REFline at all is the undeterminable branch rather than an error to work around. - Lines the agent did not author are outside the change set, and a non-production determination is required before dispatch. The dirty baseline is computed once, right after
before_doing, so it cannot cover a file the application under test writes into the working tree during a session — counting those would put an app-controlled footprint inside the branch that blocks completion, the exact property this test exists to deny. Anything that appeared without the agent's authorship is excluded and resolves to discovered; the test is authorship, not the file's category, so output the task deliberately generated by a build or codegen step stays in. Separately, narrowing dispatch to theexploreragent takesstride-exploratory-testing-explore's own authorization gate off the automated path — it was never reachable there as a mandatory control, since the previous text already offered direct agent dispatch as an equal alternative, but the agent treats a caller-named target as authorized by construction, so both skills now require establishing that the environment context names a local or explicitly non-production instance before dispatching, and skipping gracefully when it cannot be established. - The unrecognized-severity quote cannot break out of its frame. The 40-character prefix is quoted only when it contains no backtick and no line break — either closes the inline-code span early and lets application-controlled text render as live Markdown in a persisted, reviewer-facing field — and otherwise a sentinel and the length are recorded instead.
- Every uncertain state resolves to
discovered— an undeterminable change set, a base ref failing its sanity check, a baselined path whose per-path attribution could not be resolved, a fault site unidentified after a bounded attempt — and is labelled provenance undetermined, never pre-existing, because those branches never established provenance. Blocking on a link you could not draw would be a denial-of-progress surface. - No structured review block in the payload → no escalation, and nothing may be synthesized. A small task (0-1
key_files) whose review the decision matrix skipped, and a review whose JSON would not parse, both reach this. Never fabricate a structured block, anissues[], anissue_counts, a section verdict or adispatched: true— and never downgrade a review that did run to a self-reported skip. - The remedy is a re-review, and that is a stated exception. Phase 3 says that after fixing reviewer-reported issues you do not need to re-run the reviewer. That rule governs issues the reviewer reported; an escalation the orchestrator wrote into
reviewer_resultleaves afailedverdict and a stale Critical entry that only a fresh review regenerates away — the same remedy the security sub-step already requires. - The graceful-skip contract is unchanged. No trigger condition moved, the fallback text is untouched, and both skills now say plainly that no exploratory finding can block completion on a task that never ran a session.
Changed — Step 5.5 and Phase 3.5 dispatch only non-interactive surfaces
Both skills previously offered the stride-exploratory-testing-explore skill and the explorer agent as equally valid dispatch targets. That skill opens with a question round, so an autonomous workflow that chose it would hang with no error — the worst failure shape, because it looks like a stall rather than a violation.
- The principle governs, not the list: dispatch only a surface that runs to completion without requiring a human, because the orchestrator does not prompt between steps. "Requires a human" is read broadly — a surface that waits on an out-of-band approval fails identically to one that prompts. A surface the plugin gains later qualifies by satisfying the principle, never by being added to a list, and the entries recorded are reasoning rather than a standing guarantee: the companion versions separately, so re-establish a surface from its own frontmatter whenever its version changes.
- The sanctioned surface is the
exploreragent, and it is the only one. This port's argument is its own, not a translation of the canonical plugin's (which reasons from a withheldAgent/WebFetchallowlist that does not exist here): an agent declares atools:list and a skill has no tool-restriction field at all — an asymmetry the companion's own frontmatter harness makes explicit by requiringtoolson everyagents/*.agent.mdand checking onlyname/descriptionon everyskills/*/SKILL.md. A skill's unattended-safety therefore rests on its prose...