Skip to content

ci: onboard to the CI platform (ci-workflows + standards)#4

Merged
kyle-sexton merged 1 commit into
mainfrom
ci/onboard-ci-platform
Jun 24, 2026
Merged

ci: onboard to the CI platform (ci-workflows + standards)#4
kyle-sexton merged 1 commit into
mainfrom
ci/onboard-ci-platform

Conversation

@kyle-sexton

Copy link
Copy Markdown
Contributor

@
Stands up quality CI for claude-code-plugins, the next consumer in the dedup-program rollout (Phase 6 / greenfield). CI consumes the ci-workflows building blocks by pinned SHA behind a local ci-status gateway, and adopts the standards drop-in configs by copy (byte-identical to upstream).

Step 0 resolution (visibility)

The rollout prompt assumed ci-workflows was private (which would block a public caller per architecture D6). Live state: ci-workflows is already public, so uses: melodic-software/ci-workflows/...@sha resolves from this public repo — the standard consume-by-reference model works, no architectural change needed. (Follow-ups below reconcile the IaC/doc drift this surfaced.)

Lanes (all dogfooded green locally)

markdown, typos, gitleaks, editorconfig, shellcheck, actionlint, check-jsonschema (plugin manifests via schemastore + dependabot.yml + workflows), the four hygiene lanes (exec-bit, machine-specific-paths, eol-renormalize, comment-hygiene), and zizmor (advisory). link-check.yml is a separate scheduled, advisory reusable-workflow caller (issues: write, files a rolling tracking issue). Dependabot (github-actions, 7-day cooldown) keeps the SHA pins + version comments current.

Deviations from the tracker, driven by live tracked files

  • shellcheck included now (tracker said "later") — the repo already tracks shell hooks and a .shellcheckrc.
  • hook-utils.sh drops its vestigial shebang# shellcheck shell=bash, matching the standards sourced-library convention, so exec-bit passes (it is sourced, never executed; mode stays 100644). This is a one-line line-1 change, disjoint from the concurrent feat/hook-telemetry-convention work on the same file (which adds a function lower down) — they 3-way merge cleanly.
  • osv-scanner omitted — no dependency lockfiles exist to scan.
  • Existing hand-rolled .editorconfig / .gitattributes / .shellcheckrc replaced with the canonical standards copies.

Follow-ups (separate PRs, after this is green)

  1. github-iac: fix the visibility drift (Program.cs still declares ci-workflows private), enable free public-repo secret scanning + push protection on this repo, and tag it requires-ci to arm the org ci-gate (sequenced so a green ci-status exists on main first).
  2. standards: update the dedup-program rollout/plan docs to mark adoption.
    @

feat(ci): onboard claude-code-plugins to the CI platform

Stand up quality CI by consuming the ci-workflows composite actions and
reusable workflows by pinned SHA (08a9343), aggregated behind a local
ci-status gateway (D2), plus the standards drop-in configs adopted by copy
(byte-identical to the upstream source).

Lanes: markdown, typos, gitleaks, editorconfig, shellcheck, actionlint,
check-jsonschema (plugin manifests + dependabot + workflows), the four repo
hygiene lanes (exec-bit, machine-specific-paths, eol-renormalize,
comment-hygiene), and zizmor (advisory). link-check runs as a separate
scheduled, advisory reusable-workflow caller. Dependabot (github-actions,
7-day cooldown) keeps the SHA pins and version comments current.

shellcheck is included now (not deferred) because the repo already tracks
shell hooks and a .shellcheckrc. hook-utils.sh drops its vestigial shebang
to match the standards sourced-library convention so the exec-bit lane
passes (it is sourced, never executed; mode stays 100644).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@
@kyle-sexton
kyle-sexton merged commit e36c165 into main Jun 24, 2026
14 checks passed
@kyle-sexton
kyle-sexton deleted the ci/onboard-ci-platform branch June 24, 2026 02:11
kyle-sexton added a commit that referenced this pull request Jun 24, 2026
…formatter producer (#5)

* docs: add hook-telemetry envelope contract (schema_version 1.0)

Publish the marketplace-wide hook-telemetry convention: a versioned
public-API contract for plugin hooks (producers) to emit structured
execution telemetry to a consumer-set sink (HOOK_TELEMETRY_SINK,
fire-and-forget, opt-in; unset = no-op). Carries this hook's own
duration_ms, outcome, and findings — the per-hook signal CC-native
OTEL structurally cannot provide (it reports aggregate total_duration_ms
and excludes third-party plugin content).

docs/conventions/hook-telemetry/:
- README.md: spec — mediator boundary, fire-and-forget, best-effort/lossy
  guarantee; 7-field common envelope; status enum ok|error|skipped|blocked;
  hook_event free string; per-hook data + data/<hook>.schema.json discovery
  keyed on hook; forward-compat (additive-only, ignore unknown keys AND
  tolerate unknown enum values); deprecation policy; SemVer versioning;
  schemas are contract-docs (not machine-enforced); adopt-by-copy.
- envelope.schema.json: draft 2020-12; required = all 7 common fields;
  additionalProperties true; status enum (4); duration_ms integer >= 0.
- data/markdown-format.schema.json: required [tool, file, findings].
- CHANGELOG.md: schema_version 1.0 initial.
- examples/markdown-format.json: worked fixture, consistent with both schemas.

Contract authoring only; no runtime code. markdown-formatter is the first
implementer (emit lands Phase 2).

Verified (Tier-0, all PASS): fixture shape (schema_version/status/hook_event/
duration_ms/data); envelope additionalProperties + 4-value status enum;
README forward-compat/deprecation/not-machine-enforced text; all 3 JSON parse.
Lint deferred — markdownlint-cli2 absent locally.

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(markdown-formatter): emit hook telemetry (schema_version 1.0)

First implementer of the hook-telemetry convention. The markdown-format
hook now emits one telemetry envelope per run to a consumer-set sink
(HOOK_TELEMETRY_SINK), fire-and-forget. Additive and opt-in: sink unset =
no-op, with stdout and exit code byte-identical to before.

hook-utils.sh: add hook::emit_telemetry <hook_id> <hook_event> <status>
  <start_epoch> <data_json>. Opt-in guard (sink unset -> return 0) +
  fail-open (jq absent -> return 0). duration_ms from $EPOCHREALTIME
  (locale-safe: handles . and , separators, 10# guards octal). timestamp
  via TZ=UTC printf (true UTC). Envelope built with jq -n (stderr ->
  /dev/null, never touches fd1). Background dispatch with the sink's stdout
  AND stderr -> /dev/null so it cannot block CC's stdout read to the 15s
  hook timeout (the C1 fd1-inheritance blocker) nor leak into
  additionalContext.

markdown-format.sh: capture start before formatting; parse tool_name for
  data.tool; compute repo-relative data.file (cygpath -lm on Windows Git
  Bash to reconcile drive-letter vs mount form, plain prefix-strip on
  Linux/macOS, raw-path fallback); call hook::emit_telemetry LAST on the
  three post-format paths (markdownlint absent -> skipped; clean -> ok,
  findings []; residual -> ok, findings = violation lines filtered to the
  " MD<n>/" pattern). Pre-format exits (ext gate, missing file, kill
  switch) do not emit. stdin read once (INPUT=$(cat)) and fed to both the
  file_path and tool_name parses.

Tests (TDD): markdown-format.test.sh PASS=41/0 (adds sink-set envelope
validity, sink-unset additive-safety, non-zero sink, slow-sink C1 timing,
stdout-leak); hook-utils.test.sh PASS=23/0 (sourced-lib unit). Shellcheck
clean.

Verified Tier-0 end-to-end: schema-valid envelope lands in sink; no fd1
leak; sink-unset produces no envelope with identical stdout; hook returns
~1.4s under a 3s sink (non-blocking).

Co-Authored-By: Claude <noreply@anthropic.com>

* feat(markdown-formatter): resolve relative HOOK_TELEMETRY_SINK producer-side

Resolve a relative HOOK_TELEMETRY_SINK against the caller's repo root so the
sink path is clone-portable and worktree-safe when wired via a tracked
settings.json env value (CC injects env literally, no ${VAR} expansion).

- hook::emit_telemetry: optional 6th repo_root arg ($CLAUDE_PROJECT_DIR
  fallback, fail-open skip if neither); absolute paths pass through.
- Exec changed from unquoted $SINK to quoted "$sink": the sink is now a single
  executable path (wrap in a script to pass arguments).
- markdown-format.sh passes its file-derived REPO_ROOT at all 3 emit sites
  (CC #9447: CLAUDE_PROJECT_DIR can be empty in plugin hooks).
- Contract README gains "Sink path resolution"; CHANGELOG notes it under 1.0.
  No schema_version bump - envelope wire-format unchanged.
- Tests: stub-script sinks replace command-with-args sinks + 4 resolution
  cases (hook-utils 27/0, markdown-format 41/0).

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(hook-telemetry): relax status to documented open string; tighten contract docs

A closed JSON-Schema enum on `status` contradicted the contract's own
"consumers MUST tolerate unknown enum values" rule: a validator code-generated
from the published schema would reject a future status value (JSON Schema
2020-12 sec 6.1.2). Relax `status` to a documented open string (mirrors
`hook_event`), matching OpenTelemetry's open-enum pattern (`error.type`).

- envelope.schema.json: drop the closed `status` enum; keep type:string and
  list the documented values in the description.
- README: correct the `additionalProperties: true` claim (it encodes only
  unknown-keys-tolerated, not never-remove/rename — the rest is review/policy);
  record a deferred-with-trigger note for per-data versioning (CloudEvents
  `dataschema` precedent); add a consumer/sink adoption section.
- CHANGELOG: 1.0 `status` documented as an open string, not a closed enum.
  Pre-publish correction — no schema_version bump.

Validated against CloudEvents, OpenTelemetry semantic conventions, and JSON
Schema 2020-12 (primary sources).

Co-Authored-By: Claude <noreply@anthropic.com>

* chore(hook-telemetry): conform to CI platform merged from main (#4)

Bring the pre-CI-era telemetry files into conformance with the lanes #4 added:

- hook-utils.test.sh: git mode 100644 -> 100755 (it carries a shebang; the
  exec-bit lane requires shebang scripts be 100755, matching its sibling).
- Reword "mis-parse"/"mis-resolve" to plain English in the README sink-path
  note and two hook-utils.sh comments; the typos lane flags the bare "mis"
  token. Comment-only; no behavior change.

Verified: hook-utils 27/0, markdown-format 41/0, shellcheck clean, typos clean.

---------

Co-authored-by: Claude <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Jul 12, 2026
Address two more Codex P2 findings plus the analogous coaching case:

- codebase-detection case (#2): the eval repo has no real auth-flow
  module, so grounding would fail or reward inventing sources. Reframe to
  the routing + graceful-degradation invariant — resolve to codebase
  mode, discover from live files, and ask the user to point when no
  grounding exists rather than inventing repo sources.
- primary-source case (#5) and coaching case (#4): a fresh topic
  invocation runs the mission interview before teaching, so expecting
  immediate content rewarded skipping the mission gate. Make both
  gate-aware — respect the new-workspace mission flow while asserting the
  grounding / one-question-coaching invariants.
kyle-sexton added a commit that referenced this pull request Jul 17, 2026
…opy through standards SSOT

Corrector #4 reason-dont-recite (incumbency discipline): inherited content
is evidence of what is, never self-justifying authority; a choice supported
only by precedent earns first-principles re-derivation, not compliance. A
distinct axis from do-your-research (acquire external evidence you lack —
this questions internal evidence you inherited). SSOT: the consuming
project's incumbency / first-principles rule, degrading to a portable
baseline. A standards disagreement it surfaces routes upstream via
follow-our-standards (routing only — that skill now owns "never silent
deviation, never silent conformance"; no doctrine duplicated).

point-dont-copy SSOT correction: its re-anchor step now routes through the
org standards' reference-don't-duplicate (in-repo facts: literal/semantic,
describe/use/expose roles, stable anchors) and documentation-and-citations
(external facts: cite + fetch at read time, no upstream-inventory recap,
citation placement) conventions instead of restating that doctrine. The
skill keeps only its own pins — threshold two (tighter than the convention's
three-or-more smell signal), point-at-public-contracts, no-capability-
enumeration — and the not-a-copy carve-outs.

Metadata, README, CHANGELOG updated; root catalog regenerated via
scripts/generate-catalog.mjs. Corrector #5 (terseness) body authored and
held pending its locked name.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

🤖 Generated with [Claude Code](https://claude.com/claude-code)
kyle-sexton added a commit that referenced this pull request Jul 19, 2026
…ention

Eval #2 expectation #4 framed <type>/<description> as unconditionally
acceptable; align it with Step 1 (SKILL.md:56), which treats that form as
the fallback default only when the consumer declares no branch convention.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Jul 19, 2026
Eval #4 exercised only the briefed-delegable open outcome. The closing
invariant names status:needs-info as the other open outcome that must
clear status:needs-triage, and its re-entry path differs (attention-view
needs-info bucket on reporter reply, not the raw-marker bucket). Add
eval #5 to prove an agent clears the raw marker on the needs-info
outcome without orphaning the item.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Jul 20, 2026
…ard blank stderr

Address PR #590 review observations:
- #2 (required): emit `Unremovable: 0` before `exit 7` so the apply-path label
  contract is uniform between the clean success and clean-failure paths.
- #3: guard the CLEAN_STDERR echo with `[[ -n ... ]]` so a clean that fails with
  empty stderr no longer prints a bare newline.
- #4: assert RestoredTracked:/Unremovable: labels in the clean-failure test.
- #1: one-line comment documenting the deferred mixed locked+unrelated-error
  fall-through (pre-existing heuristic, not a regression).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011V31qpAHP3jfs76B9d5Rfo
kyle-sexton added a commit that referenced this pull request Jul 21, 2026
…oast

- guard the /review:quality-gate cross-plugin ref with a degrade-to-prose
  fallback (bare unguarded cross-plugin ref is a defect per PLUGIN-PHILOSOPHY)
- point-not-copy the fresh-context rationale at the shared method doc's rule
  instead of restating it; keep only the skill-specific "core step" delta
- reframe the audit list as the brief handed to the fresh-context pass rather
  than a same-context self-audit
- reword eval #4 to a disposition check (focus narrows aim, not a hard filter),
  removing the overreach that assumed a planted out-of-focus flaw with files:[]

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Jul 21, 2026
## Summary

Lane 1 of a locked 3-lane batch. One `session-flow` version bump (0.11.0
→ **0.12.0**), two changes:

1. **New `orient` skill** — read-only session orientation. Synthesizes
*where do we stand, what are we doing, and why* from both the live
conversation and the durable + off-thread state a conversation does not
hold: handoff save-points, the workflow checklist, running-retro ledgers
(via the plugin's topic-docs binding), git state, open PRs, and open
work-items. Strictly read-only — writes nothing, ends nothing, and
**routes rather than acts**: freshness → `reanchor`, off-thread recovery
→ `keep-going`, next-stage → `workflow`, learnings → `retro`.
2. **Hardened `keep-going`** — (a) broadened beyond "after an
interruption" to a live-session **poke** ("check the monitor", "poke
it", "is it stuck", "stop staring") with an active-verification protocol
that reads real output first, treats progress-vs-elapsed as *suspicion
only*, and gates killing/restarting as a side effect so live-but-slow
work is not killed on a hunch; (b) **usage-limit stall fix** — once a
limit lifts and the session is executing again, continue rather than
summarize-and-stall; (c) intent inferred from the conversation,
arguments optional. All prior recovery behavior and **all prior trigger
phrases preserved**.

The plugin now bundles nine skills. Both skills pass
`skill-quality:check` (0 errors, 0 warnings); `claude plugin validate
--strict` passes; `marketplace.json` untouched (it carries no
version/description shadow — only catalog metadata).

## Naming tournament (provisional — user picks)

Dogfooded `naming:name-it-better tournament`: 5 blind fresh-context
generators (responsibility-literal, moment-of-use, domain-lore,
verb-precision, newcomer-onboarding), each seeded only with the
structured brief and blind to the conversation; the main thread merged,
applied the syntactic shaping pass, and scored against the repo's
imperative-verb-phrase grammar and reserved-verb contract.

- **Winner (used provisionally): `orient`** → `/session-flow:orient`.
Converged across **all five** lenses (top pick of verb-precision; top of
moment-of-use and newcomer-onboarding after the `-me` normalization
below). Semantic accuracy first: *orientation is the responsibility*.
One-word imperative verb, temporal-neutral (valid loaded at session
start, unlike `reanchor`/`reorient` which presuppose a prior fix),
collides with no reserved verb or sibling, and is directly cued by the
"orient me" trigger.
- **Runners-up (rejection reasons):**
- `take-stock` — 4 lenses; accurate and temporal-neutral ("stock" can be
empty at start), but leans toward *assessment/evaluation* over pure
orientation and is less directly cued by the firing phrases.
- `brief` — 4 lenses and a strong "brief me" trigger, but collides with
the planning **Brief** artifact (an established noun in this
marketplace: `planning:interview` locks a PLAN.md *Brief*), and carries
verb/adjective ambiguity ("brief" = short).
- `get-bearings` — the newcomer lens's top pick, but two words, a weak
"get", "bearings" faintly presupposes mid-journey (temporal-neutrality
ding), and its nautical family brushes `reanchor`'s anchor metaphor.
- Also surfaced and cut: `read-in` (intel jargon, low general
recognition), `situate` (accurate, lower trigger utility), `size-up` /
`survey-*` (tilt to assessment/scan).
- **Syntactic shaping:** the moment-of-use and newcomer lenses proposed
`orient-me` / `brief-me` / `catch-me-up`; normalized to bare imperative
verbs per the house grammar (the namespace and conversation supply the
object — no sibling bakes a pronoun into its name).
- **Blocklist enforced:** `recap` (built-in `/recap` collision),
`resume`/`continue` (`keep-going` owns), `next` (`workflow` owns).

**@user — final name is yours;** `orient` is provisional. If you prefer
a runner-up, it is a directory rename on this branch before merge.

## Fact-check verdicts (fresh-docs mandate; fetched 2026-07-21)

1. **Can a skill invoke the built-in `/recap`? — NO.** Airtight
citation: the skills doc allowlist — *"A few built-in commands are also
available through the Skill tool, including `/init`, `/review`, and
`/security-review`. Other built-in commands such as `/compact` are
not."* ([skills](https://code.claude.com/docs/en/skills)). `/recap` is
not on that allowlist, so `orient` synthesizes a recap-equivalent
**inline** and adds the durable/off-thread layer recap can't see. (My
commands-doc fetch did not positively confirm `/recap` as a built-in, so
the design cites the allowlist mechanism, not "/recap is built-in".)
2. **Rate-limit / reset info accessible in-session? — Only the message
text; no programmatic surface.** The reset time appears in the limit
**message** (e.g. `resets 3:45pm`), the interactive `/usage` view, the
Desktop usage ring, and custom-statusline `rate_limits` fields — there
is **no env var, file, or API** a skill body can read
([errors](https://code.claude.com/docs/en/errors),
[costs](https://code.claude.com/docs/en/costs),
[statusline](https://code.claude.com/docs/en/statusline)). So
`keep-going` reads the reset from the message text and never fabricates
a window.

## Open design thread — RESOLVED (limit-window not yet reset)

The two options ("schedule a wakeup + auto-continue" vs "summarize +
hand back") are **not mutually exclusive — both sit on a handoff
artifact**:

- **Built-in default (this PR):** while a limit still holds,
`keep-going` cannot progress in-session, so it **hands back via
`/session-flow:handoff`** — no work lost — and stops. It does **not**
build or arm its own scheduler.
- **Auto-continue = an external scheduler's job**, launched to resume
from that same handoff at the reset time. Two forms, each with caveats:
- *Desktop scheduled task* — local, loads plugin/personal skills, but
requires the app open + machine awake
([desktop-scheduled-tasks](https://code.claude.com/docs/en/desktop-scheduled-tasks)).
- *Cloud routine* — runs even when the machine is off, but starts from a
fresh clone and loads only repo-committed / plugin-declared skills,
**not** `~/.claude/skills`
([routines](https://code.claude.com/docs/en/routines),
[skills](https://code.claude.com/docs/en/skills)).
- Both need the reset time parsed from the message text (no API) and the
skill reachable in the launched session.

Rationale (per the independent-reviewer steer): if `keep-going` is
*executing*, the block is already over — there is nothing to wait for in
a single session; the time-vs-window check therefore earns its place
only in the **orchestration** case (inspecting a limited worker), which
the skill scopes it to. This is a resolved default with a documented
deferred path, **not a blocking question**.

## Gates

- [x] `skill-quality:check` PASS on `orient` (new) and `keep-going`
(rewrite; all 8 prior trigger phrases preserved)
- [x] Evals: `orient` 4 cases; `keep-going` 6 cases (4 prior verbatim +
`limit-lifted-go-not-summarize` + `live-poke-evidence-before-kill`).
Eval #4 (`does-not-diagnose-limit-type`) kept **verbatim** — the
anti-stall directive is orthogonal to it.
- [x] `claude plugin validate --strict` PASS
- [x] Version bump in `plugin.json` only (one bump, both changes)
- [x] `marketplace.json` **untouched** — deliberate call: its `tags` are
a curated subset, not a per-skill mirror (`running-retro` is untagged
too), the lane constraint is "untouched unless schema requires", and
`plugin.json` `keywords` already carry `orient`/`orientation` for
discovery.
- [x] **Plugin-acceptance security review (light):** no new trust
surface. `orient` is a read-only prompt skill (no hooks, MCP, scripts,
`userConfig`, `bin/`); it reads only the consumer's memory-tier dirs
under the project and the plugin's own bundled reference via
`${CLAUDE_PLUGIN_ROOT}` — no `../` reach-out, no egress. `keep-going`
remains prompt-only; the new kill/restart guidance is model-facing
prose, gated as a side effect. Surfaces 2/5/6/7 untouched → no SSOT
record required.
- [x] **Independent code review** (fresh-context reviewer) complete — no
CRITICAL, all behavioral MUSTs met. Findings folded (commit `1752bfb`):
plugin `README.md` (nine skills, `orient` row + section,
hardened-`keep-going` section, `orient` added to the Network list) and
`reference/topic-docs.md` (read-only `orient` reader note). The
`marketplace.json` tag suggestion was the deliberate no-change above.

## Related

No linked issue — this PR lands two locked batch-3 decision-ledger items
(the `orient` orientation skill and the `keep-going` hardening) tracked
in the project's private planning ledger, not a GitHub issue.

Do **not** merge — opened for review per lane instructions.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Jul 21, 2026
## What

Adds **`scrutinize-dont-coast`** to the `re-anchor` plugin (0.4.0 →
0.5.0): a drift corrector for **adversarial self-scrutiny** — the
general "don't coast on your own recent output" re-look, distinct from
the plugin's content-axis correctors (research, incumbency, terseness,
…). The user hits the brakes; the skill stops, re-examines whether the
recent output is actually sound (not merely confidently produced), and
remediates with the user.

## Design

Re-anchors a **meta** discipline rather than a single content axis. It
runs the shared re-anchor / audit / correct-forward loop
(`context/re-anchor-audit-correct.md`) with **two deliberate, documented
deltas** — flagged in the SKILL.md rather than silently diverging:

1. **Stop first.** The failure mode is over-confident forward momentum,
so it halts the trajectory before auditing. No sibling prepends a stop.
2. **Remediate _with_ the user, not autonomously.** The shared loop's
step 3 corrects forward on its own; here it becomes collaborative — the
remedy for runaway momentum can't be _more_ unilateral momentum.

The load-bearing adversarial pass is **delegated to a fresh-context
(non-fork) subagent blind to the reasoning that produced the output**,
satisfying the philosophy's fresh-eyes rule that a same-context
self-check cannot. Findings return and remediation proceeds _with_ the
user — composing the fresh-eyes constraint and the
collaborative-remediation intent instead of trading one off. An optional
focus scopes the pass without suppressing a serious out-of-focus flaw.

**Negative routing (explicit in the skill):** pre-implementation plan
stress-tests → `/planning:devils-advocate` (guarded, degrades to prose
when absent); review checkpoints → `/review:quality-gate`; single-axis
flaws → the sibling that owns them.

## Naming tournament

Research-backed tournament via `naming:name-it-better` — **5 blind
fresh-context generators** (responsibility-literal, moment-of-use,
domain-lore, physical-metaphor, family-idiom-fit), each seeded only with
a structured brief (temporal-neutrality constraint, sentence-form test,
negative boundary vs devils-advocate/quality-gate, word blocklist:
audit/gate/review/devil/advocate/bare-check).

- **Winner (provisional, user-confirmable pre-merge):
`scrutinize-dont-coast`** — top-2 in 3 of 5 lenses, including the two
most decision-relevant (responsibility-literal, family-idiom-fit).
Verb-dont-verb family shape (shared with reason-dont-recite,
point-dont-copy, reuse-or-replace); temporally neutral (a standing
principle, not a presupposition of existing flawed work); names both the
action and the failure mode.
- **Runners-up:** `distrust-your-momentum` (co-convergent — top-2 in 3
lenses; evocative, but names the cause not the action and reads weaker
at the call site); `red-team-your-own-work` (exact term of art, but
longer and security-jargon-tinged). Also surfaced: prove-it-holds,
pump-the-brakes, stop-and-look-hard.
- **My-pick-vs-tournament note:** my own same-context pick before the
blind round was `stop-and-scrutinize`; it did not surface in any lens,
and the judging confirms the convergent winner scores higher on
family-idiom fit, temporal-neutrality, and sentence-form. Dogfooding the
blind method overrode the author's anchored pick — exactly this skill's
own thesis.

## Gates

- `claude plugin validate .` — PASS
- `skill-quality check scrutinize-dont-coast` — PASS (0 errors, 0
warnings; markdownlint clean; 133/500 lines; description 529/1536 chars)
- evals: 5 cases (trigger+fresh-context happy path, clean-guardrail,
devils-advocate negative-routing, focus-scoping, conversation-start
posture), schema-valid
- Independent fresh-context review (blind to author rationale): verdict
SHIP-WITH-FIXES, no CRITICAL; all findings folded (quality-gate ref
guarded; fresh-context rationale pointed-not-copied at the method doc;
audit list reframed as the fresh-pass brief; eval #4 reworded to a
disposition check). The review's skill-quality dimension is the
`check-skill.sh` PASS above.
- **Plugin-acceptance security review: ACCEPT** — prompt-only skill,
zero trust surface added (no
hooks/scripts/MCP/userConfig/bin/settings-agent; only internal
`${CLAUDE_PLUGIN_ROOT}` ref). Additive 0.4.0→0.5.0 bump touches no trust
surface.

## Scope


`plugins/re-anchor/skills/scrutinize-dont-coast/{SKILL.md,evals/evals.json}`
(new); `plugin.json` (version, description, keywords); `README.md`
catalog entry; `plugins/re-anchor/CHANGELOG.md`. `marketplace.json`
untouched (version lives in plugin.json; adding a skill to an existing
plugin does not change its catalog entry).

## Open questions for the user

1. **Precedent — first family skill to override shared-loop steps (needs
a user ruling).** This is the first corrector to declare deltas to the
shared re-anchor / audit / correct loop: it prepends a STOP and makes
step 3 collaborative ("remediate _with_ the user") where every sibling
corrects forward autonomously. Resolved here as documented per-skill
deltas — `context/re-anchor-audit-correct.md` was NOT touched. Two ways
to settle the precedent, your call: **(a) sanction** declared per-skill
step-overrides by adding an allowance to the shared method doc (so
future correctors may do this cleanly), or **(b) reconsider** — fold the
behavior back to the autonomous family default. If instead STOP-first +
collaborative-remediation should become the family norm, that's a
method-doc change touching all 13 skills — a separate decision, not made
unilaterally here.
2. **Name.** `scrutinize-dont-coast` is the provisional tournament
winner; `distrust-your-momentum` is the co-convergent alternative.
Confirm or swap before merge (trivial rename).

## Cross-lane note

Lane 3's `planning:devils-advocate` incumbent extension is in flight
concurrently. This skill's boundary text keys on the stable contract
(devils-advocate = plans/proposals _before_ implementation; this =
output _already produced_, mid-flight), which is robust to that
extension — no coordination edit needed.

## Related

- **No linked issue** — this skill was locked from the batch-3 interview
ledger (re-anchor item 2); it was never tracked as a GitHub issue, so
this PR closes nothing.
- Plugin `plugins/re-anchor` (0.4.0 → 0.5.0).
- Concurrent batch-3 lanes (independent PRs): session-flow orientation +
keep-going hardening; `planning:devils-advocate` incumbent extension —
see the cross-lane note above.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Jul 21, 2026
## What

Folds the four naming-tournament lessons recorded during the
running-retro naming round (batch ledger, "Naming-skill lessons this
round") into `name-it-better`; naming 0.2.0 → 0.3.0.

- **Terms-of-art brief field** — the field's established names for the
act with researched, not recalled, meanings. Evidence: running-retro r4
found `reflect` (agent-lit: output-critique-retry), `introspect` (type
inspection), and `self-review` (repo-colonized for diff review) all
misleading; the honest field term was `retrospective` minus cadence.
- **Blocklist provenance** — every word-level blocklist entry records
user-stated vs agent-inferred origin; inferred entries are proposals to
confirm. Evidence: the "retro" blocklist entry was agent-assumed, never
user-decided, and was reversed on re-derivation.
- **Sentence-form test** as an early merge filter for utterance names
(skills/commands): the imperative you would actually say, cold-readable.
Evidence: filtered contentless idioms (`take-stock`, bare `check`)
across r1-r2.
- **Temporal-neutrality constraint** for skills loadable as primed
context: name must read valid before any work exists
(`critique-your-own-run` failure class).

## Gates

- `claude plugin validate .` — PASS
- `markdownlint-cli2` — 0 errors
- `skill-quality check name-it-better` — PASS (0 errors; 1 pre-existing
soft line-target warning, file was already over 200 before this change)

## Related

- No linked issue — ledger-tracked follow-up (re-anchor round 2 ledger,
task #4); closes nothing.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Jul 21, 2026
…857)

## What

Adds a bundled **`jira`** adapter to the work-item-tracker seam
(`plugins/work-items/tools/work-item-tracker/adapters/jira/`), alongside
the shipped `github` and `local-markdown` adapters. GitHub stays the
default. Scoped to the **read/resolve path** per the #379 maintainer
decisions
([15:48](#379 (comment)),
[re-intake](#379 (comment))).

Closes #379.

## Adapter surface

Consume-only by default (issue #379 hard constraint) — the manifest
declares:

| Verb | Supported | Notes |
|---|---|---|
| `get-item` | ✅ | `GET /rest/api/3/issue/{key}` → normalized item |
| `list-items` | ✅ | `POST /rest/api/3/search/jql`, `nextPageToken`
pagination, JQL scoped to `project_keys` |
| `capabilities` | ✅ | cats the manifest |
| `create-item`, `claim`, `renew-lease`, `reclaim`, `link-blocks`,
`add-sub-item` | ❌ exit 6 | writes — no code path mutates a Jira ticket
|
| `list-sub-items` | ❌ exit 6 | Jira epic-child/subtask enumeration is
the deferred #6 link-type question; `list-frontier --parent` degrades to
exit 6 |

**No code path creates, claims, or mutates a Jira ticket by default.**
`/work-items:work`, `track start`, and `list-frontier --parent` are
consequently unavailable on a Jira binding — an accepted gap. Branch/PR
`SW2-*` linkage and opt-in writes are sequenced follow-ups.

## Read-path normalization

`state` (statusCategory → open/closed), `assignees` (single assignee →
accountId), `labels` (verbatim), `type` (issue-type name), open-only
`blocked_by_count` (inward blocker links; linked-issue status inlined in
`issuelinks`, so one call), `parent_id` (`fields.parent`), `url` (browse
link). ID grammar: `jira:<site>/<PROJECTKEY>#<number>` ⇄ native key
`PROJECTKEY-number`.

## Auth & config

Basic auth (email + API token). The token is read from the env var
**named** by `config.jira.auth_env` — never stored in the tracked
binding, and passed to curl via a stdin config (`-K -`) so it never
appears in argv. Required config: `config.jira.{site, project_keys[],
auth_email, auth_env}`. The blocker link type (`blocked_by_link_type`,
default `Blocks`) and the "done" statusCategory key set
(`done_category_keys`, default `["done","completed"]`) are configurable
override seams for the two facts deferred to the live-instance pass, so
the adapter is independent of them.

## Fresh-docs research (per CLAUDE.md fresh-docs mandate)

Every endpoint/field was verified this session against the **official
Atlassian OpenAPI spec**
(`https://dac-static.atlassian.com/cloud/jira/platform/swagger-v3.v3.json`)
and official docs — not training-data recall:

- **Enhanced JQL search** `POST /rest/api/3/search/jql` (request
`jql`/`fields`/`maxResults`≤5000/`nextPageToken`; response
`issues[]`/`nextPageToken`/`isLast`):
https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-search/#api-rest-api-3-search-jql-post
- **Get issue** `GET /rest/api/3/issue/{issueIdOrKey}` (`fields` query
param):
https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issues/#api-rest-api-3-issue-issueidorkey-get
- **Bulk blocked_by_count is schema-supported**:
`IssueBean.fields.issuelinks[].inwardIssue` is a `LinkedIssue` whose
`fields` uses the `Fields` schema, which includes
`status.statusCategory.key` — so a single search call deep-populates
each blocker's status (no second round-trip). Verified in the spec's
component schemas.
- **statusCategory key ambiguity (why `done_category_keys` defaults to
both)**: the spec's own `GET /statuscategory` example returns keys
`in-flight`/`completed`, while real Jira Cloud instances widely return
`new`/`indeterminate`/`done` (community-flagged doc gap) — only the live
instance settles it.
https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-workflow-status-categories/#api-rest-api-3-statuscategory-get
- **Basic auth** (email + API token, passwords deprecated,
`Authorization: Basic base64(email:token)`):
https://developer.atlassian.com/cloud/jira/platform/basic-auth-for-rest-apis/
· tokens at https://id.atlassian.com/manage/api-tokens

## Tests

- Adapter unit tests mock `curl` via a `WIT_JIRA_CURL` seam — the read
path (normalization, OPEN-only blocked_by_count, done-exclusion, parent
qualification, pagination across two `nextPageToken` pages, error →
exit-code mapping) is exercised **fully offline, no live Jira call**.
- A `jira` conformance binding runs the abstract seam suite **offline in
CI** (the consume-only manifest makes every suite-exercised path
pre-network), and re-runs it under a `gh`/`curl`-blocking PATH shim to
prove zero network.
- All 38 work-item-tracker seam tests, `shellcheck
--rcfile=.shellcheckrc`, changelog-parity, plugin validation,
skill-portability, and markdownlint pass locally.

## Independent review

Audited by a fresh-context reviewer (producer ≠ critic). It found — and
this branch fixes — a **JQL-injection CRITICAL**: `list-items` assembled
the `project in (...)` / `statusCategory in (...)` clauses by string
concatenation without escaping embedded quotes, so a `--repo` or
configured key containing `"` could inject arbitrary JQL. Fixed by
allowlist-validating every JQL-interpolated value before assembly
(configured keys → exit 3 at config load; `--repo` key → exit 2 at
parse; both before any HTTP call), with tests asserting the literal JQL
payload and hostile-input rejection. Advisory nits (null-accountId
assignee → `[]`, up-front rejection of a malformed project key in an id,
a hardcoded date in the conformance binding) also folded in. Everything
else — consume-only structure, token hygiene, exit codes, pagination —
verified clean.

## Contract gaps found

- **Provider-config injection** (`lib/binding.sh` carries a
`local-markdown`-specific `storage_dir` leak; adapters must re-resolve
the binding to read their own `config.<provider>`). The Jira adapter
reads `config.jira` itself rather than growing that leak. Filed as
scoped follow-up **#852**.
- **Live-instance facts deferred** (#4 exact "done" key, #6
blocker/sub-item link type) — handled via configurable defaults, not
blocking.

## Not in scope (sequenced follow-ups)

Opt-in writes; branch/PR `SW2-*` linkage (spans two plugins' source,
forbidden by this issue's acceptance); container-scoped frontier
(`list-sub-items`).

## Related

Related work this PR does **not** close:

- **#852** — scoped follow-up filed from this PR: the seam should export
the resolved binding path so adapters read `config.<provider>` without
re-resolving (retires the `lib/binding.sh` provider-config leak). Not
required for this adapter.
- **#440** — outward write-mode / posture decision that a future
opt-in-writes change for this adapter will key on (this PR ships
writes-off, so it does not depend on #440 yet).
- **#416 / #418** — seam-agnosticism prerequisites (skills that hardcode
`gh` bypass the seam). Independent of this adapter landing, but they
bound how broadly a non-GitHub provider is reachable across all
consumers.
- Live-instance confirmations deferred to the work-laptop pass — the
exact `statusCategory` "done" key (#379 decision #4) and the
authoritative blocker/sub-item link type (#379 decision #6); both have
configurable defaults here.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant