workflow-status
Read-only workflow sensor: run the deterministic script, read the fixed machine envelope, interpret the recommendation. Never edits. Triggers: "workflow-status", "workflow status", "what can I build next".
Install
npx skills add https://github.com/gtrabanco/agentic-workflow/tree/main/skills/workflow-status
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install gtrabanco-agentic-workflow@llmmart
git clone https://github.com/gtrabanco/agentic-workflow.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole gtrabanco/agentic-workflow collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Workflow Status (the orchestrator's sensor)
One read-only pass that answers, in a single fixed JSON envelope: what exists,
what is blocked on what, what is startable right now, and what the recommended
next command is. The script is the deterministic producer:
scripts/workflow-status.mjs executes the published SENSOR_CORE sequence
(steps 1–9 including 6a) and prints the envelope. This skill runs the script,
reads the JSON, interprets it against the references below, and prints the human
report — it never assembles the envelope by hand.
Turn contract — verify before ending the turn
✓ The script was RUN — `bun scripts/workflow-status.mjs [--json-only]
[--last-envelope <json|path>]` (node is the fallback when bun is absent, per
the repository's runtime convention); the envelope is the script's stdout, never
assembled by the model
✓ Nothing was edited, committed, pushed, or created — read-only, always
✓ `next.recommended` is non-bare (carries the unit's slug/NN, never a bare
`/plan-feature`) AND the script computed it from the unit's resolved status
**and** its current pre-execution evidence: `idea`/undesigned →
`/design-feature <slug>`; `defined` → `/plan-feature <slug>` only on a current
`SPEC-REVIEW-PASS`, else `/review-spec <slug>`; `planned`/`in-progress` → `/execute-phase <NN>` only on a
current `PLAN-REVIEW-PASS`, else `/review-plan <NN>` (step 6a)
✓ `detail.crash_recovery` carries a verdict from the decision table and the
envelope `state` matches it (CLEAN→OK, RESUMABLE→CONTINUE,
AMBIGUOUS→NEEDS_INPUT)
✓ Every `detail.design_candidates[].next` begins with `/design-feature `
✓ Every degraded dimension is named in `detail.degradations` as
`unavailable-<source>-<cause>` — and, when `--last-envelope` was supplied, the
no-progress guard's `workflow_observations` note is present (never a silently
repeated bland recommendation)
✓ The envelope is emitted on **every** invocation, including a same-session
natural-language follow-up about state — never replaced by prose
✓ The human-readable summary is printed, then the machine envelope (the script's
JSON) is the ABSOLUTE last output
With --json-only, skip the human-readable summary: print the envelope alone.
When to use
- Between orchestration steps: an external driver runs it to decide the next command and model tier without parsing prose.
- Before picking work manually: "what can I start right now?"
- Not for judging quality (
review-change/audit-pr) or product health (product-audit) — this skill reports state, it never judges.
Step 0 — Discover the project (always first)
Per the agent guide's Workflow conventions + documentation map. The
script reads what THIS skill needs (docs/features/ROADMAP.md, the fix index
docs/fix/README.md, every in-flight feature folder's TASKS.md +
progress.md + review-findings.md, and docs/workflow/REPOSITORY_STATE.md);
read them yourself only to interpret a field the script emitted.
Progressive loading — fixed sensor route
The reference allowlist is exactly the seven linked paths below. Never invent or
read another references/ path. This skill is a read-only sensor.
- sensor core — the sequence the script executes
- crash recovery
- envelope core
- envelope fields
- pre-execution evidence
- guardrails
Add sensor signals only when a unit, issue,
finding, or recommendation exists; an empty project skips that file but still
emits the empty shapes defined by envelope fields. Add
portability only when the platform actually lacks a
named primitive. --json-only does not skip any baseline file.
Portability
The sensor uses repository and forge commands only. When a named agent feature is unavailable, follow portability without changing the JSON contract.
Relationship to other skills
- The sensor counterpart to
ship-roadmap's conductor: an external orchestrator callsworkflow-status→ routes on the envelope → invokesplan-feature/execute-phase/review-change/audit-pr/triage-issuedirectly, choosing the model per step — the same loop without the in-agent autopilot. - Read-only sibling of
audit-docs(which judges coherence and can fix) andproduct-audit(which judges health): this one only reports state. - Schema owner:
orchestration-envelope(internal).
Done when
- The script ran and every claim in the report comes from its envelope — nothing inferred from memory, nothing assembled by hand.
detail.design_candidates,detail.features,detail.fixes,detail.startable_now,detail.blocked_units,detail.open_prs,detail.untriaged_issues,detail.urgent,detail.degradations, anddetail.crash_recoverywere read from the envelope, and the envelopestatematches the crash-recovery verdict.- With
--last-envelopesupplied: the no-progress guard ran — a stalled/plan-feature//design-featurehint surfaces as aworkflow_observationsnote, never a silent bland repeat, with no new write path introduced. - The human summary (unless
--json-only) and the envelope are printed, envelope last. - Nothing was modified anywhere.
- When the envelope carries
next.continuation, the next-command echo quotes the emittednext.continuation(renderingfor display), never author exact command tokens; the human-facing prose→ Next:block below stays.
→ Next: the envelope's next.recommended command — it is computed from the
actual state, so it IS the recommendation
· a human overview → read the printed table
· orchestrating programmatically → parse the script's JSON
Files (agentic-workflow)
-
references
-
CRASH_RECOVERY.md 3.7 KB
## Crash recovery (run every invocation) A driver process can die mid-turn; on restart, the persisted state it holds is a **hint, never a source** — everything below is recomputed from git, the forge, and the docs. Nothing is cleaned up here (read-only stands): this section *classifies*; the resume command it recommends does the acting. **Checklist:** - ✓ **Working tree per unit branch.** A dirty tree (`git status --porcelain`) or unpushed commits on a `feat/*`/`fix/*` branch → interrupted-turn candidate. Cite branch + files. Checking unpushed commits: **first check the branch has an upstream** (`git rev-parse --abbrev-ref <branch>@{u}` — non-zero exit = no upstream). No upstream → **every commit on the branch is unpushed by definition**, don't run `git log @{u}..` (it errors with `fatal: no upstream configured`, which is exactly the mid-crash never-pushed case, not an error to surface). Has an upstream → use `git log @{u}.. --oneline` / `git status -sb` as usual. - ✓ **Phase-ledger coherence.** Compare the unit's `progress.md`/`TASKS.md` against the branch's actual commits: commits after the last closed phase entry, or ticked tasks with no matching commit, are cited as evidence. - ✓ **Hint envelope (optional).** With `--last-envelope <json|path>`, diff the caller's persisted envelope against the recomputed state and report the divergence in one line. The hint never overrides recomputed state. - ✓ **No-progress guard (optional, requires `--last-envelope`).** When the hint's `next.recommended` was `/plan-feature <slug>` or `/design-feature <slug>` for a given unit, and this run's own recomputed status for that **same unit** is still at the **same pre-advance status** the hint expected to move it off of (`defined` for a `/plan-feature` hint; `idea` for a `/design-feature` hint) — either the recommended command ran but its status write was dropped, or it never ran at all; this guard cannot distinguish the two from the envelope alone, so the note names it as a **suspected** stall, not a confirmed dropped write. Emit a `workflow_observations` note (see `## Machine envelope` for the exact note shape). This is strictly additive: the same `next.recommended` / `next.tier` still fire per the normal classification (step 6) — the guard only stops the silent, bland repeat by making the stall visible. Still read-only: no write, no repair, no new persistence. **Classification (decision table — every row independently checkable; first matching row wins per branch):** | Evidence | Verdict | |---|---| | Clean tree, ledger coherent with commits | `CLEAN` | | Dirty/unpushed on a unit branch AND the ledger points to a unique next task/phase | `RESUMABLE` — resume command: `execute-phase <NN> <phase>` | | Dirty/unpushed AND ledger contradiction (ticks ahead of commits, unknown branch, detached HEAD) | `AMBIGUOUS` — a human looks first | **Return exactly (appended to the report):** ``` CRASH RECOVERY — verdict: CLEAN | RESUMABLE | AMBIGUOUS | Branch | Evidence | Classification | Resume command | |---|---|---|---| | <branch> | <dirty: n files; ledger: <state>> | RESUMABLE | execute-phase <NN> <phase> | Hint envelope: matched | diverged: <one line> | not provided ``` (`CLEAN` with no unit branches in play → the table body is a single `| — | clean tree, coherent ledgers | CLEAN | — |` row.) **Multiple unit branches, multiple verdicts → one envelope `state` (fixed precedence, worst wins):** `AMBIGUOUS` > `RESUMABLE` > `CLEAN`. A human decision pending on ANY branch outranks a mechanical resume on another, which outranks an all-clean state. The report's per-branch table still lists every verdict; only the envelope's single `state` is reduced to the worst one. -
ENVELOPE_CORE.md 6.8 KB
## Machine envelope `scripts/workflow-status.mjs` is the **deterministic producer** of this envelope: it performs the assembly, runs the `validateEnvelope` self-check (diagnostic only, exit 0), and prints the one JSON document. The skill runs the script and interprets the JSON — it never assembles the envelope. Schema and placement per the installed `orchestration-envelope` skill. The `state` maps 1:1 from the crash-recovery verdict — **no new schema fields or states** (the schema package needs no release): - `CLEAN` → `state: OK` (the sensor default when the substrate is usable). - A missing or non-frozen repository-state ledger is a run-scoped substrate gate and overrides the crash-recovery state with `state: BLOCKED`; the blocker and concrete discovery/resolution command are emitted before readiness data. - `RESUMABLE` → `state: CONTINUE`, `next.recommended` = the resume command from the decision table. - `AMBIGUOUS` → `state: NEEDS_INPUT`, `needs_input.question` = what is contradictory, `needs_input.options` = the concrete choices (resume / redo the phase / discard the dirty work), evidence in `detail.crash_recovery`. `next` always carries the single best command for the project right now, and `detail` the full tree (plus `crash_recovery: {verdict, branches: [...]}`). **`detail.design_candidates`** is an array beside `detail.startable_now` / `detail.blocked_units` — every `idea`-status unit, deps-agnostic (design happens before dependency startability matters). It is never a top-level envelope key. **`detail.urgent`** — the injection-safe urgency channel (feature 15): `{issues: [...], interruptibility: {...}}`. `issues` lists every **open** issue carrying `urgent` or `fix-next`, read **only** from the `labels` object returned in step 2/3's `gh issue list --state open … --json … ,labels` call — never from title, body, or comments; an issue with "URGENT" only in its text never appears here (`urgent` wins when an issue somehow carries both labels). Scoping the list to `--state open` means a shipped fix's issue drops out of `detail.urgent` the moment it closes — automatically, on the next poll, with no manual label strip required (there is nothing to reconcile: this field is recomputed fresh every invocation, never persisted). `interruptibility` carries the in-flight unit's facts — `phase`, `dirty` (bool), `tasks_from_boundary` (count of unticked tasks left in the current phase) — reusing the same phase-progress and crash-recovery reconcile, not a new computation. This field is **presence-only reporting**; it never contains a pause-vs-finish verdict — that decision belongs entirely to the consumer's bounded judge (`docs/workflow/ORCHESTRATION.md`). An empty `issues` array means no urgency signal is in play; `next.recommended` may still be influenced by a non-empty one (e.g. surfaced as an `alternatives` entry), but is never silently replaced by it. **`detail.untriaged_issues`** — the plain open-issue backlog surfaced by step 15: `{count, oldest_open: [numbers]}` (oldest-first, capped at 5 numbers). `detail` is schema-unconstrained (`envelope.schema.json:170`, `"detail": {}`), so this field needs **no package change**. Kept strictly distinct from `detail.pending_triage` (findings pulled from `known-issues.md`/postponed-labeled issues, step 14) and `findings.untriaged` (review-finding routing) — none of the three subsumes another. `count: 0` means every open issue has a triage disposition; a non-zero `count` may drive `next.recommended`/`alternatives` toward a concrete `/triage-issue <numbers>` citing the listed issues. **Per-unit `review`/`closure`/`issues_born` (step 10–12) — carried on each `detail.features[]`/`detail.fixes[]` entry, not as new top-level keys.** `detail` is schema-unconstrained (`envelope.schema.json:170`, `"detail": {}`) — same precedent as `detail.urgent`/`detail.untriaged_issues` (fix `#52`), so these need **no package change**: - `review: {last_checkpoint_sha, unreviewed_diff: {lines, files}, terminal_done, adversarial: {ran, n}}` — step 10. `adversarial.ran`/`n` are `null` unless real evidence exists (no skill persists that marker today — never guessed). - `closure: {state}` ∈ `present | absent-legacy | blocked` (feature units) or `n/a` (fix units) — step 11, reusing `audit-pr`'s own grep verbatim. - `issues_born: {n, with_descope_amendment}` — step 12, reusing `audit-pr`'s scope-bleed detection (widened by `#79`/`#89` to also match an issue linked from an `## Amendments` row). **Not yet mechanized (read this before consuming the three fields above).** The `scripts/workflow-status.mjs` sensor does **not** emit `review`, `closure`, or `issues_born` today: they are steps 10–12 of the published sequence and remain skill-side work, so no `detail.features[]`/`detail.fixes[]` entry carries them until a sensor phase owns them. A consumer must treat their absence as "not computed", never as "empty": absence is not a `closure: absent-legacy` verdict and not an `issues_born.n: 0`. **`next.suggested[]`** — step 13's trigger-attributed suggestion surface, `{command, trigger, source_skill}[]`, **optional** (mirrors `packages/agentic-workflow-schema` 2.1.0's optional `EnvelopeSuggestion[]`). Each `trigger` string quotes the owning skill's own condition — never a second, drifting copy of that skill's logic. Advisory only: it rides beside `next.recommended`/`next.tier`, never replaces them. No unit has a fired trigger this run → `next.suggested` is emitted as an empty array (`"suggested": []`), never an absent key: a consumer keys on the array, not on the key's presence. **Envelope shape reminders (the script validates these before printing — mirrors `packages/agentic-workflow-schema/envelope.schema.json`):** - `blockers[].scope` ∈ `{"unit","run"}` — there is **no** `"code"` value; doc/roadmap drift is always `"unit"`-scope (`envelope.schema.json:111`). - A `"run"`-scope blocker forces `state` ∈ `{BLOCKED, HALT}` — it is **never** compatible with `state: OK` (see `orchestration-envelope`). - `dependencies.unmet` is an **array of strings** (unit ids / `#issue` refs) — never an array of objects (`envelope.schema.json:120`); any richer detail belongs in a `blockers[].detail` string instead. **`next.tier` derivation — a fixed command→tier map, never guessed:** | Command | Tier | |---|---| | `/discover-repository-state` | `strong` | | `/resolve-repository-state` | `strong` | | `/plan-feature` | `strong` | | `/design-feature` | `strong` | | `/review-change` | `strong` | | `/audit-pr` | `strong` | | `/triage-issue` | `strong` | | `/product-audit` | `strong` | | `/execute-phase` | `cheap` | | `/review-spec` | `strong` | | `/review-plan` | `strong` | `next.tier` is read off this map by matching the resolved `next.recommended` command's name (ignoring its arguments) — never guessed and never copied from the invoking driver's own tier. -
ENVELOPE_FIELDS.md 6 KB
## Envelope fields and example **No-progress guard note (`workflow_observations`, requires `--last-envelope`, see the crash-recovery checklist above)** — when the hint's `next.recommended` targeted `/plan-feature <slug>` or `/design-feature <slug>` and this run still classifies that same unit at the same pre-advance status, append a note of the exact shape: `"<slug> still 'defined' after the hint's /plan-feature <slug> recommendation — suspected dropped defined→planned write (see #51)"` (swap `defined`/`/plan-feature` for `idea`/`/design-feature` on the design side). The recommendation itself is unaffected — this only adds visibility. **Shape vs. current output — read this before trusting the example below.** The example is the envelope's **intended shape**, not everything the deterministic producer emits today: per-unit `review`, `closure`, and `issues_born` (steps 10–12) are **not yet mechanized** by `scripts/workflow-status.mjs` and do not appear in its output (`next.suggested[]`, step 13, is emitted — as an empty array when nothing fired). A consumer must read their absence as "not computed", never as `closure: absent-legacy` or `issues_born.n: 0`; the owning note is `ENVELOPE_CORE.md` §Not yet mechanized. ```json { "skill": "workflow-status", "state": "OK", "summary": "2 features merged, 07 in-progress at P2/4 awaiting review, 05 startable, fix #43 pending triage, 08 needs design.", "unit": {"type": "none", "id": null, "issue": null, "branch": "main"}, "phase": {"current": null, "total": null, "completed": null}, "pr": {"number": null, "url": null, "state": "none", "head_sha": null, "merge_ready": null, "ci": null}, "gates": {"verification": null, "review_pending": null, "audit_pending": null}, "findings": {"fix_now": [{"id": "F1", "file": "src/export/handler.ts:88", "axis": "security", "severity": "high", "class": "fix-now", "route": "fold into phase", "suggested_tier": "strong"}], "issues_filed": [], "untriaged": 2, "decisions_recorded": 0}, "blockers": [], "dependencies": {"unmet": [], "build_order": []}, "recommendations": {"product_audit": false, "reason": null}, "needs_input": null, "next": {"recommended": "/review-change", "alternatives": ["/plan-feature 05"], "tier": "strong", "suggested": [{"command": "/review-change", "trigger": "accumulation: 420 changed lines since last-reviewed sha", "source_skill": "execute-phase"}]}, "detail": { "design_candidates": [{"id": "08-billing-webhooks", "status": "idea", "next": "/design-feature 08-billing-webhooks"}], "pre_execution": [{"unit": "12-export-api", "stage": "plan", "label": "stale", "verdict": "PLAN-REVIEW-PASS", "boundDigest": "sha256:9f2c…", "observedDigest": "sha256:41ab…", "recommended": "/review-plan 12-export-api", "reason": "docs/features/12-export-api/ACCEPTANCE.md changed after the review"}], "features": [ {"id": "07-csv-export", "status": "in-progress", "deps": ["01"], "deps_unmet": [], "phase": {"current": "P2", "total": 4}, "pr": null, "review_pending": true, "audit_pending": null, "merge_ready": null, "review": {"last_checkpoint_sha": "a1b2c3d", "unreviewed_diff": {"lines": 420, "files": 9}, "terminal_done": false, "adversarial": {"ran": null, "n": null}}, "closure": {"state": "present"}, "issues_born": {"n": 0, "with_descope_amendment": 0}}, {"id": "05-auth", "status": "defined", "deps": [], "deps_unmet": [], "phase": {"current": null, "total": null}, "pr": null, "review_pending": null, "audit_pending": null, "merge_ready": null, "review": {"last_checkpoint_sha": null, "unreviewed_diff": {"lines": null, "files": null}, "terminal_done": false, "adversarial": {"ran": null, "n": null}}, "closure": {"state": "absent-legacy"}, "issues_born": {"n": 0, "with_descope_amendment": 0}} ], "fixes": [ {"id": "43-null-crash", "issue": 43, "status": "planned", "deps_unmet": [], "pr": null, "review": {"last_checkpoint_sha": null, "unreviewed_diff": {"lines": null, "files": null}, "terminal_done": false, "adversarial": {"ran": null, "n": null}}, "closure": {"state": "n/a"}, "issues_born": {"n": 0, "with_descope_amendment": 0}} ], "startable_now": ["05-auth", "fix-43"], "blocked_units": {"09-billing": {"unmet": ["05-auth"], "build_order": ["05-auth", "09-billing"]}}, "open_prs": [{"number": 13, "unit": "07-csv-export", "ci": "green", "merge_ready": false}], "pending_triage": [{"source": "docs/features/07-csv-export/known-issues.md", "title": "empty-file edge"}], "untriaged_issues": {"count": 3, "oldest_open": [21, 33, 40]}, "workflow_observations": ["branch feat/07-csv-export is 1 commit ahead of origin"], "urgent": { "issues": [{"number": 51, "title": "prod webhook signature check bypassed", "label": "urgent"}], "interruptibility": {"unit": "07-csv-export", "phase": "P2", "dirty": true, "tasks_from_boundary": 2} }, "crash_recovery": { "verdict": "CLEAN", "branches": [ {"branch": "feat/07-csv-export", "evidence": "1 commit ahead of origin; ledger coherent", "verdict": "CLEAN", "resume_command": null} ] } } } ``` `detail.startable_now`, `detail.blocked_units` (with build orders), `detail.design_candidates`, `detail.pre_execution` (one row per sensed unit from step 6a, `{unit, stage, label, verdict, boundDigest, observedDigest, recommended, reason}`, `verdict`/`reason` null when absent) and `detail.pending_triage` are the keys an orchestrator routes on; a unit appears in `startable_now` only when its `pre_execution[].label` is `current` for the stage it is about to enter; every id in `startable_now`/`blocked_units` must appear fully in `features`/`fixes` — an `idea` unit appears ONLY in `design_candidates` (and `detail.features`), never in `startable_now`, since it has no deps-met check to pass (design precedes dependency startability). `05-auth` above illustrates `defined` (not yet `planned`): startable, next `/plan-feature`, phase fields null (no planning artifacts yet). -
GUARDRAILS.md 1 KB
## Guardrails - **Read-only, always.** No commit, push, issue, comment, label, or file edit — not even fixing an obviously stale roadmap row (report it as a blocker of kind `substrate` instead; `audit-docs` is the fixer). - Evidence discipline per the project's **Workflow conventions**: every status comes from a command's output or a file's content; unverifiable → `null` + a `workflow_observations` note, never a guess. - Forge unavailable → still report the git/docs view, with a `blockers` entry `{"kind": "substrate", "id": "forge", "scope": "run"}` so the orchestrator knows PR-dependent states are unknown. - **`detail.urgent` is presence-only and read-only, always.** Derive it **exclusively** from the `labels` object of `gh issue list … --json … ,labels` — never parse title, body, or comments, and never cross-check the labeling actor's permission via the issue timeline (presence is already triage+-gated by GitHub). This sensor never emits a pause-vs-finish decision — only the facts the consumer's judge needs. -
PORTABILITY.md 812 B
## Portability (agents other than Claude Code) The workflow is the contract; Claude Code features are conveniences. This skill has no Claude Code dependency at all — it is the piece that lets ANY driver (a shell loop, a CI job, another agent) orchestrate the workflow: - **No slash-command menu** — open this `SKILL.md` and follow it literally in a fresh conversation, or invoke it headless (see `docs/workflow/ORCHESTRATION.md` for per-agent invocation patterns). - **No per-skill `model:`/`effort:`** — this is mechanical reading and counting: a **cheap** tier is enough; never spend a strong model here. - **No argument passing (`--last-envelope`)** — paste the persisted envelope JSON into the invocation message: the skill treats the last fenced json block of the *request* as the hint. -
PRE_EXECUTION.md 5.7 KB
## Pre-execution receipt sensing (step 6a) Roadmap status says what exists; only a receipt bound to the current bytes says whether the unit may be executed. Sense it like every other signal — read the file, recompute the digest, never trust the prose sitting around the block. ### What is read For every unit whose resolved status is `defined`, `planned` or `in-progress`: 1. The newest `## Pre-execution review receipt v1 — spec` and `… — plan` block in the unit's `progress.md` (no `progress.md`, or no block for that stage → `missing`). 2. The `snapshot` line — the `sha256:` digest the receipt binds — and each bound artifact's current bytes: re-derive the digest with the recipe owner's verify mode — `bun scripts/pre-execution-snapshot.mjs verify --stage <spec|plan> --unit <id> [--parent <64-hex>]` (`pre-execution-review` owns the recipe; a feature plan check needs the Product digest it descended from, a fix check binds no parent). Read `structural.reasonCode` and `structural.changedPaths` out of the JSON — they name the dimension that stopped being true, which is what the sensor labels `stale`. A snapshot digest is a canonical SHA-256 over the snapshot object, so `git hash-object` is never a substitute: it stays correct only for the frozen `ACCEPTANCE.md` manifest blob. 3. The verdict, the reviewed `unit`, the stage, and the author fields (`reviewer` and `authorId` — the receipt's `Reviewer:` / `Author:` lines — and `authorExclusion`). ### What proves a review ran (step 8) One artifact later, the same discipline: step 8's `review_pending` keys on the unit's **durable review mark** — the `REVIEW-RAN` row of its `review-findings.md` ledger, whose shape and writer `pre-execution-review`'s `LEDGERS.md` owns — and judges it by step 8's currency rule in `SENSOR_CORE.md`, never by a keying invented here. A findings ledger with rows and no mark leaves the unit review-pending: its rows prove findings were folded against some state, which is a different fact from *this* state being reviewed, so the sensor reports a missing gate, never a verdict that no review ever happened. ### One label per stage, and the command it recommends | Label | Evidence | Recommended | |---|---|---| | `current` | stage PASS verdict **and** recomputed digest = bound digest | spec: `/plan-feature <slug>` · plan: `/execute-phase <NN>` | | `missing` | no receipt block for the stage | `defined` → `/review-spec <slug>` · planned/in-progress → `/review-plan <NN>` | | `stale` | bound digest differs, or a bound context row moved | re-run **that stage's** review — quote the emitted planning-gate re-run continuation (`next.continuation`, `rendering` for display), never author exact tokens | | `wrong-stage` | the only PASS belongs to the other stage | the stage that is missing — never the stage that passed | | `substitute` | prose verdict, legacy block, or internal attestation stands where a receipt should be | that stage's review, and report the substitute as a blocker detail | | `self-approved` | the author of the artifact recorded the PASS | that stage's review in a clean context | | `author-readiness` | an authoring readiness result sits where a verdict should be | that stage's review — readiness never licenses execution | | `legacy` | `planned`/`in-progress` unit with no ledgers and no receipt (predates feature 28) | the adoption route below | | `impossible-timeline` | `structural.fresh: false`, `structural.reasonCode: impossible-timeline` — digest and revision match by construction; a receipt's own recorded timeline is physically impossible (finish predating source revision's commit date beyond the published skew); a legacy receipt with no parsable Started/finished: line, or a source revision git cannot resolve, stays unflagged (fail-open) | that stage's review — the timeline is self-refuting, not a stale source | A stale receipt re-runs the **review**, not the authoring skill: the artifacts may be exactly right and simply need re-judging in a clean context. The command to re-run is the **emitted** `next.continuation` (`rendering` for display) — quoted as-is, never authored as fresh command prose. A missing verdict block, or a verdict outside the stage's fixed set, is `missing` with the reason recorded — never a guess in the recommended direction. ### Envelope projection - A unit that is otherwise startable but lacks a current PASS for the stage it is about to enter becomes a `gate` blocker (`scope: unit`, detail names stage + label). `startable_now` keeps only units whose next command is genuinely runnable, so `next.recommended` never points a human at `execute-phase` on an unreviewed plan. - `detail.pre_execution[]` gains one row per sensed unit: `{unit, stage, label, verdict, boundDigest, observedDigest, recommended, reason}` — `verdict`/`reason` are `null` when absent, `observedDigest` is always the digest computed **now**. `detail` is opaque to the envelope schema, so no field outside it changes. - Nothing is inferred from the *absence* of a block: an unreadable artifact is `missing` with the failure in `reason`, never a carried-forward claim from the receipt itself. ### Legacy units A `planned`/`in-progress` unit with no ledgers and no receipt predates the gate; it reports as `legacy`, which is a different fact from `missing` ("never reviewed") and routes the human differently. `pre-execution-review` owns the adoption rule — construct the ledgers, never coerce old evidence, keep frozen acceptance and every past commit byte-identical, and resume only on a current `PLAN-REVIEW-PASS`. This sensor only reports the label and the command; it never edits a unit to make the label disappear, and it files nothing. -
SENSOR_CORE.md 8.4 KB
## Process (fixed sequence — executed by the script) `bun scripts/workflow-status.mjs` (node when bun is absent — the repository's runtime convention) executes this whole sequence (git/forge collection, urgency labels-only scan, roadmap + fix-index parse, dependency closure, readiness + step-6a receipt sensing, phase progress, review-mark currency, fix-now fold projection) and prints the fixed Envelope v2 JSON. The numbered blocks below state the semantics the script implements; the model reads the JSON and interprets it. ### Normalized Repository State Read `docs/workflow/REPOSITORY_STATE.md` when present and always emit `detail.repository_state: {status, snapshot_id, source_revision}`. Before readiness classification, if the ledger is missing, `draft`, `contradicted`, or `resolved`, add a run-scoped `substrate` blocker, set the envelope state to `BLOCKED`, leave `startable_now` empty, and set `next.recommended` to `/discover-repository-state` for missing/non-frozen state or `/resolve-repository-state <contradiction-id>` for `contradicted` state. The sensor remains read-only; it never edits or resolves the ledger. Conflicting live evidence against a frozen ledger remains a contradiction candidate. 3. **Urgency labels (`detail.urgent`) — labels-only, presence-only, never decides.** Reuse the open-issue list from step 2 (`gh issue list --json labels` — the JSON labels array already carried by that call); no separate call is required. Scan the **labels object only** for `urgent` / `fix-next` — never the issue's title, body, or comments (the injection-safety invariant `triage-issue` owns: these two labels can only be applied by a triage+-permission actor, so presence alone is trustworthy). For each open issue carrying either label, emit `{number, title, label}`; if an issue carries **both**, report `urgent` (it strictly dominates — reaches the judge — so `fix-next`'s head-of-queue, no-interrupt path is redundant on that issue). Alongside the label list, carry the **in-flight unit's interruptibility facts** — current phase, dirty/clean tree, distance to the next commit boundary — reusing the same reconcile step 7 (phase progress) and the crash-recovery dirty-tree check already compute; do not duplicate the git calls. This sensor **reports facts only** — it never decides pause-vs-finish (that is the consumer's bounded judge, canonical in `docs/workflow/ORCHESTRATION.md`); urgency may only *inform* `next.recommended`, never silently override it. 4. **Roadmap + fix index.** Parse every row: id, slug, status — the five-state machine `idea / defined / planned / in-progress / done` (see `docs/features/ROADMAP.md` → Status legend) — depends-on, linked PR. A legacy row reading a plain `planned` with no five-state history: check its `SPEC.md` product half; complete (`## Design status: designed`) → treat as `defined`+`planned` (no redirect, per `docs/workflow/MIGRATION.md`); otherwise treat as `idea`. **Unknown status.** A row whose status is **not** one of the five states above (e.g. a non-standard `scheduled`) maps to the **nearest** five-state value, **defaulting to `idea`** when no nearer value is evident — so it safely routes to `/design-feature` rather than skipping design. Worked example: `scheduled → idea` (cross-reference `#51`, which owns the fuller status-vocabulary reconciliation). Note the raw status string in `workflow_observations` so the mapping is visible, never silent. 5. **Compute the dependency tree.** For every non-merged unit, build the **transitive** depends-on closure and mark each edge met (dep's PR merged) or unmet — same rule as execute-phase's dependency gate: `done`-with-open-PR is NOT met. Detect inconsistencies (a "merged" row whose own deps aren't merged; cycles) and report them as `substrate` blockers. 6. **Classify readiness — `startable_now` requires status ≥ `defined` AND deps met.** For every unit in the roadmap/fix index: - status `idea` → list under **`design_candidates`**, next command `/design-feature <slug>`. Never `startable_now`, regardless of deps. - status `defined` or `planned`, deps met → `startable_now`, with the next command matched to the exact status: `defined` → `/plan-feature <slug>`, `planned` → `/execute-phase <NN>`. - deps unmet (any status ≥ `defined`) → `blocked_units` (unchanged). 6a. **Sense the pre-execution receipts** for every unit at `defined`, `planned`, `in-progress`, **or `done` with a linked PR that is still open (unmerged — merge state lives in the forge)** ([pre-execution evidence](PRE_EXECUTION.md)). A done-but-unmerged row is a lifecycle label, never merge-ready, so a stale or missing receipt there surfaces as a gate blocker; merged units stay excluded (the merge itself closes their gates). Read the stage's newest receipt block, re-derive the bound digest with the recipe owner's verify mode (`bun scripts/pre-execution-snapshot.mjs verify --stage <spec|plan> --unit <id> [--parent <64-hex>]` — a snapshot digest is a canonical SHA-256, never a git blob id; `structural.reasonCode` names the dimension that drifted), and label the stage `current`/`missing`/`stale`/`wrong-stage`/`substitute`/ `self-approved`/`author-readiness`/`legacy`/`impossible-timeline` (unresolvable revision or unparsable timeline: fail-open → unflagged). The label **overrides step 6's status-only command**: a unit without a current PASS for the stage it is about to enter is demoted out of `startable_now` into a `gate` blocker naming the missing review, and `detail.pre_execution[]` records the row. A roadmap row is never edited here — sensing only reads. 7. **Phase progress.** For each in-progress feature, read `TASKS.md`: current phase, total phases, per-phase checkbox completion. 8. **Pending quality gates.** For each unit with commits: has the mandatory `review-change` for its current state run? The artifact is the unit's **durable review mark** — the `REVIEW-RAN` row of its `review-findings.md` fold ledger, whose shape and writer `pre-execution-review`'s `LEDGERS.md` owns — and it counts while the sha it names is an ancestor of the unit's head and no commit after it touched a bound input of that review, over the paths `pre-execution-review`'s `SNAPSHOT.md` binds: `git log <mark-sha>..HEAD -- <bound paths>` prints nothing. Equality with the head is not the test and cannot be: carrying the mark is a commit, and that commit moves the head, so a rule keyed on equality calls every review that finished its own turn unreviewed. The ledger's presence is never that proof: a review that found nothing writes no finding row, so presence would call a reviewed unit unreviewed and an unreviewed one reviewed. Has `audit-pr` a MERGE-READY bound to the PR's current head SHA (look for the audit comment marker on the PR)? Derive `review_pending` (no current mark) / `audit_pending` / `merge_ready` per unit. 9. **Fix-now fold ledger → `findings.fix_now[]`.** For each in-flight unit (feature or fix) that has a `review-findings.md` ledger, read only its `folded: no` rows and emit each as a structured item: `{id, file, axis, severity, class, route, suggested_tier}` (`file` = the ledger's `file:line` column value, verbatim). Derive `suggested_tier` from this fixed table — mechanical, never guessed: | Condition | `suggested_tier` | |---|---| | `severity == "high"` | `strong` | | `axis` ∈ {security, correctness, logic, architecture, design, concurrency} | `strong` | | anything else | `cheap` | Reuses `next.tier`'s `strong`/`cheap` vocabulary — `next.tier`'s own derivation (below) is **unchanged**, this is a separate, per-finding field. No ledger for a unit → that unit contributes nothing to `fix_now` (not an error); no unit in the run has one → `findings.fix_now: []`, same as today. **Read-only**: this step only projects the ledger's current unfolded rows — never writes, ticks `folded`, or judges. ### Envelope fields the sensor projects (versioned grammar) Steps 1-9 print these keys and no others; each row must be a field the schema package's envelope validator declares for that object. ```text sensor-fields@1 # machine: envelope object | field unit | type pr | merge_ready gates | review_pending gates | audit_pending findings | fix_now findings.fix_now | suggested_tier dependencies | unmet blockers | kind recommendations | product_audit ``` -
SENSOR_SIGNALS.md 9.8 KB
## Conditional sensor signals 10. **Per-unit review signals (`detail.*.review`).** For each in-flight feature/fix, compute: - `last_checkpoint_sha` — feature-mode units only: read `progress.md`'s `Last reviewed: <sha>` header line (`execute-phase`'s per-phase checkpoint cadence, `#77`, "Last-reviewed marker"). Fix/single-pass units have no phase checkpoints — the cadence section is scoped "(feature mode)" only — so this is always `null` for them, not an error. - `unreviewed_diff: {lines, files}` — `git diff --stat <baseline>..HEAD` where `<baseline>` is `last_checkpoint_sha` if present, else `git merge-base <default-branch> HEAD` — the identical fallback `execute-phase`'s cadence triggers define (`#77`); never a new rule. - `terminal_done` — **reused, not recomputed**: `= !review_pending` (step 8's existing computation) for any unit at or past `done`/PR-open status. Before that, `false` by contract — `execute-phase` always opens the PR **before** the mandatory review hand-off (see its *Workflows* close-out order), so a `done` status alone never implies the terminal review already ran. - `adversarial: {ran, n}` — **best-effort, evidence-gated; never guessed.** No skill persists an adversarial-mode marker anywhere this sensor can read: `review-change`'s report (including its `Reviewers n/N` column, `skills/review-change/SKILL.md`) prints to chat only, and the fold ledger's fixed schema (`| id | file:line | axis | severity | class | route | folded |`) carries no reviewer-count field. Emit `{ran: null, n: null}` with a `workflow_observations` note ("adversarial mode unverifiable — no persisted marker, see `#76`") — never infer `true`/`false` from absence of evidence. 11. **Per-unit closure state (`detail.*.closure`).** `{state}` ∈ `present | absent-legacy | blocked` — reuse `audit-pr`'s own check verbatim (`skills/audit-pr/SKILL.md`, "Closure integrity"): grep the SPEC for a `Capability closure` heading. Fix-governed unit → `n/a` (fix SPECs carry no closure block by design, same carve-out `audit-pr` applies). Feature SPEC, block absent → `absent-legacy`. Feature SPEC, block present with any blank row or a resolved non-`n/a` row unmapped to an acceptance criterion → `blocked`. Feature SPEC, block present and every row filled or `n/a`-justified and mapped → `present`. Single-sourced: never re-derive it — re-run `audit-pr`'s grep. 12. **Per-unit descope provenance (`detail.*.issues_born`).** `{n, with_descope_amendment}` — reuse `audit-pr`'s scope-bleed gate detection verbatim (`skills/audit-pr/SKILL.md` "Scope integrity (descope) — fixed output" step 1, widened by `#79`/`#89` to also match an issue **linked from** an `## Amendments` row, not only a slug/number text match): enumerate issues born since branch divergence that reference this unit. `n` = that count; `with_descope_amendment` = the subset carrying a matching, dated, user-approved `## Amendments` row. Evidence is labels, the `## Amendments` log, and the mechanical slug/number text match `audit-pr` itself defines — **never** an issue's free-text body beyond that defined match (injection-safety, mirrors `detail.urgent`'s labels-only discipline). *Steps 10–12's fields are **not yet emitted** — see `ENVELOPE_CORE.md` §Not yet mechanized.* 13. **`next.suggested[]` — single-sourced trigger surface.** One entry per **fired** trigger the driver can act on now, `{command, trigger, source_skill}` — the `trigger` string **quotes**, never paraphrases, the owning skill's own condition: - a review checkpoint trigger fired (layer boundary / accumulation / sensitivity — step 10's `unreviewed_diff` plus the unit's declared phase layers) → `{command: "/review-change", trigger: "<the fired trigger's name and evidence, quoting execute-phase's own wording>", source_skill: "execute-phase"}` (`#77`). - `review.terminal_done: false` on a unit at/past `done` AND `review-change`'s own adversarial recommendation checklist fires (reuse that checklist verbatim, never re-derive it) → `{command: "/review-change --adversarial 2", trigger: "<which checklist box fired>", source_skill: "review-change"}` (`#76`). - `closure.state: "absent-legacy"` on a unit about to receive new planned work → `{command: "/design-feature <slug>", trigger: "closure absent, SPEC predates the rule — retrofit trigger", source_skill: "audit-pr"}` (`#78`). - a unit's `review-findings.md` ledger carries an open row whose frozen route is the plan owner → `{command: "/plan-feature <unit>" | "/plan-fix <issue>", trigger: "an open finding's frozen route is the plan owner — replan-in-unit (<ids>)", source_skill: "review-change"}` (`#224`) — the same class→destination decision `scripts/unit-route.mjs` owns; and - a unit's `review-findings.md` ledger carries any other open row → `{command: "/fold-findings", trigger: "unfolded fix-now finding(s) on the ledger", source_skill: "fold-findings"}` (`#65`). A row is **open** under the router's own predicate (`scripts/unit-route.mjs` `isOpen`), never a re-derived one: `folded: yes`, `—`, `-`, `n/a` and an empty cell all close it. The id shapes `VF-<n>` (a finding's verification mark) and `REVIEW-RAN` (a review mark) are **never** findings — a ledger pads a mark row with empty cells, so it reaches the row parser looking like an open finding and would otherwise project a suggestion for a merged unit. A **decision-required** open row contributes **nothing**: it outranks the fold in the route table (which is first-match) and its work stops for the user, so it is not a driver command — the unit must not be sent to the fold on the strength of a co-resident plain row. The plan-entry ids ride the same 160-char cell bound (`CELL_MAX`) as every other echoed ledger cell. No trigger fired for a unit → it contributes nothing (not an error, same convention as `findings.fix_now`). **Additive advisory only**: `next.recommended`/`next.tier` (step 6/turn contract) are computed exactly as before — `next.suggested` never replaces or reorders them. 14. **Findings awaiting a destination.** Scan the in-flight folders' `known-issues.md` for entries with no linked issue, and open issues labeled or titled as postponed findings. Count + list them. 15. **Untriaged open-issue backlog (`detail.untriaged_issues`) — distinct from step 14's `pending_triage`.** Cross-reference the open-issue list already fetched in step 2 (`gh issue list --state open`) against triage disposition. The **authoritative** triaged signal is a `wontfix` / `postponed` / `promoted` disposition label — `triage-issue` is the sole owner/writer of that vocabulary (`skills/triage-issue/SKILL.md` → *Disposition label vocabulary*) and label mutation is triage+-permission- gated, so its presence cannot be forged by comment text. A dated `triage-issue` `VERDICT:` comment (the fixed-format block — `skills/triage-issue/SKILL.md:193-200`) is honored too, as a **legacy fallback** for issues triaged before disposition labels existed — kept for backward compatibility, not because it is as trustworthy as the label. **Accepted residual:** because the comment-text fallback stays active, a hand-authored `VERDICT:` string on an issue that was never actually triaged can still cause it to be excluded here — an under-count, not a privilege or content-injection issue (`detail.urgent` is unaffected). Revisit this residual only if exploitation evidence surfaces (see `#54`). An issue is **untriaged** iff it carries **neither** signal. Count the untriaged subset and list its oldest entries (cap: 5) by issue number. Emit the result as `detail.untriaged_issues: {count, oldest_open: [numbers]}` — kept separate from `pending_triage` (findings-derived, step 14) and `findings.untriaged` (review-finding routing); never merge the three. A non-zero `count` may surface a concrete, non-bare `/triage-issue <numbers>` in `next.recommended`/`alternatives` (ties the backlog into the routing decision from step 6/the turn contract) — it never silently replaces the resolved recommendation. 16. **Product-audit recommendation — a mechanical two-condition checklist, no exception clause.** Set `recommendations.product_audit: true` with a stated `reason` when **either** condition holds — this is a count, not a judgment call. **No exception clause exists**: a "wait for a natural pause" or "wait for a bigger milestone" rationale is not defined anywhere in this checklist and must never be invented to skip a fired trigger: - ✓ `merged_count >= 3` — features/fixes merged since the last `SHIP_REPORT`/product-audit artifact (a literal count from the forge's merged-PR list in step 2) - ✓ the same drift kind recurs in **≥2** units' docs Otherwise `recommendations.product_audit: false`, `reason: null`. A fired trigger may additionally surface `/product-audit` as `next.recommended` or an `alternatives` entry (backlog/audit over net-new feature work) — never run it. 17. **Crash recovery (run every invocation — cheap, see the section below).** Classify whether an interrupted turn is in evidence and append the fixed `CRASH RECOVERY` sub-block to the report. 18. **Report.** Print a short human summary (table: unit | status | deps unmet | PR | next gate) plus a **design candidates** line (`idea` units and their `/design-feature` next command) plus the `CRASH RECOVERY` sub-block, then the envelope. With `--json-only`, envelope only.
-
-
SKILL.md 6.3 KB
--- name: workflow-status user-invocable: true version: 3.7.0 author: "Gabriel Trabanco <gtrabanco@users.noreply.github.com>" license: MIT argument-hint: "[--json-only] [--last-envelope <json|path>]" description: > Read-only workflow sensor: run the deterministic script, read the fixed machine envelope, interpret the recommendation. Never edits. Triggers: "workflow-status", "workflow status", "what can I build next". --- # Workflow Status (the orchestrator's sensor) One read-only pass that answers, in a single fixed JSON envelope: **what exists, what is blocked on what, what is startable right now, and what the recommended next command is.** The **script is the deterministic producer**: `scripts/workflow-status.mjs` executes the published `SENSOR_CORE` sequence (steps 1–9 including 6a) and prints the envelope. This skill runs the script, reads the JSON, interprets it against the references below, and prints the human report — it never assembles the envelope by hand. ## Turn contract — verify before ending the turn ``` ✓ The script was RUN — `bun scripts/workflow-status.mjs [--json-only] [--last-envelope <json|path>]` (node is the fallback when bun is absent, per the repository's runtime convention); the envelope is the script's stdout, never assembled by the model ✓ Nothing was edited, committed, pushed, or created — read-only, always ✓ `next.recommended` is non-bare (carries the unit's slug/NN, never a bare `/plan-feature`) AND the script computed it from the unit's resolved status **and** its current pre-execution evidence: `idea`/undesigned → `/design-feature <slug>`; `defined` → `/plan-feature <slug>` only on a current `SPEC-REVIEW-PASS`, else `/review-spec <slug>`; `planned`/`in-progress` → `/execute-phase <NN>` only on a current `PLAN-REVIEW-PASS`, else `/review-plan <NN>` (step 6a) ✓ `detail.crash_recovery` carries a verdict from the decision table and the envelope `state` matches it (CLEAN→OK, RESUMABLE→CONTINUE, AMBIGUOUS→NEEDS_INPUT) ✓ Every `detail.design_candidates[].next` begins with `/design-feature ` ✓ Every degraded dimension is named in `detail.degradations` as `unavailable-<source>-<cause>` — and, when `--last-envelope` was supplied, the no-progress guard's `workflow_observations` note is present (never a silently repeated bland recommendation) ✓ The envelope is emitted on **every** invocation, including a same-session natural-language follow-up about state — never replaced by prose ✓ The human-readable summary is printed, then the machine envelope (the script's JSON) is the ABSOLUTE last output ``` With `--json-only`, skip the human-readable summary: print the envelope alone. ## When to use - Between orchestration steps: an external driver runs it to decide the next command and model tier without parsing prose. - Before picking work manually: "what can I start right now?" - **Not** for judging quality (`review-change`/`audit-pr`) or product health (`product-audit`) — this skill reports state, it never judges. ## Step 0 — Discover the project (always first) Per the agent guide's **Workflow conventions** + **documentation map**. The script reads what THIS skill needs (`docs/features/ROADMAP.md`, the fix index `docs/fix/README.md`, every in-flight feature folder's `TASKS.md` + `progress.md` + `review-findings.md`, and `docs/workflow/REPOSITORY_STATE.md`); read them yourself only to interpret a field the script emitted. ## Progressive loading — fixed sensor route The reference allowlist is exactly the seven linked paths below. Never invent or read another `references/` path. This skill is a read-only sensor. 1. [sensor core](references/SENSOR_CORE.md) — the sequence the script executes 2. [crash recovery](references/CRASH_RECOVERY.md) 3. [envelope core](references/ENVELOPE_CORE.md) 4. [envelope fields](references/ENVELOPE_FIELDS.md) 5. [pre-execution evidence](references/PRE_EXECUTION.md) 6. [guardrails](references/GUARDRAILS.md) Add [sensor signals](references/SENSOR_SIGNALS.md) only when a unit, issue, finding, or recommendation exists; an empty project skips that file but still emits the empty shapes defined by envelope fields. Add [portability](references/PORTABILITY.md) only when the platform actually lacks a named primitive. `--json-only` does not skip any baseline file. ## Portability The sensor uses repository and forge commands only. When a named agent feature is unavailable, follow [portability](references/PORTABILITY.md) without changing the JSON contract. ## Relationship to other skills - The **sensor** counterpart to `ship-roadmap`'s conductor: an external orchestrator calls `workflow-status` → routes on the envelope → invokes `plan-feature` / `execute-phase` / `review-change` / `audit-pr` / `triage-issue` directly, choosing the model per step — the same loop without the in-agent autopilot. - Read-only sibling of `audit-docs` (which judges coherence and can fix) and `product-audit` (which judges health): this one only reports state. - Schema owner: `orchestration-envelope` (internal). ## Done when - The script ran and every claim in the report comes from its envelope — nothing inferred from memory, nothing assembled by hand. - `detail.design_candidates`, `detail.features`, `detail.fixes`, `detail.startable_now`, `detail.blocked_units`, `detail.open_prs`, `detail.untriaged_issues`, `detail.urgent`, `detail.degradations`, and `detail.crash_recovery` were read from the envelope, and the envelope `state` matches the crash-recovery verdict. - With `--last-envelope` supplied: the no-progress guard ran — a stalled `/plan-feature`/`/design-feature` hint surfaces as a `workflow_observations` note, never a silent bland repeat, with no new write path introduced. - The human summary (unless `--json-only`) and the envelope are printed, envelope last. - Nothing was modified anywhere. - When the envelope carries `next.continuation`, the next-command echo **quotes the emitted `next.continuation`** (`rendering` for display), never author exact command tokens; the human-facing prose `→ Next:` block below stays. → Next: the envelope's `next.recommended` command — it is computed from the actual state, so it IS the recommendation · a human overview → read the printed table · orchestrating programmatically → parse the script's JSON
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.