Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/standards/pyright/pyrightconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"typeCheckingMode": "strict",
"reportMissingTypeStubs": "error",
"reportUnnecessaryTypeIgnoreComment": "error",
"reportImplicitOverride": "error",
"reportImplicitStringConcatenation": "error",
"reportCallInDefaultInitializer": "error",
"reportPropertyTypeMismatch": "error",
"reportUninitializedInstanceVariable": "error",
"reportImportCycles": "error",
"reportUnreachable": "error",
"reportUnusedImport": "none",
"reportUnusedVariable": "none"
}
3 changes: 2 additions & 1 deletion .github/standards/runner-policy/policy.json
Original file line number Diff line number Diff line change
Expand Up @@ -749,7 +749,8 @@
"name": "runner",
"expression": "${{ inputs.runner }}",
"default": "ubuntu-24.04",
"failureSentinel": "ci-runner-selection-failed"
"failureSentinel": "ubuntu-24.04",
"failureSentinelMarker": "ci-runner-selection-failed"
},
"fallbackLabelAllowlist": ["ubuntu-24.04", "melodic-ubuntu-24.04-x64"],
"forbiddenHostedRunnerLabels": ["macos-latest", "ubuntu-latest", "windows-latest"],
Expand Down
5 changes: 3 additions & 2 deletions .github/standards/runner-policy/policy.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,13 @@
"governedReusableRunnerInput": {
"type": "object",
"additionalProperties": false,
"required": ["name", "expression", "default", "failureSentinel"],
"required": ["name", "expression", "default", "failureSentinel", "failureSentinelMarker"],
"properties": {
"name": { "$ref": "#/$defs/nonWhitespaceString" },
"expression": { "$ref": "#/$defs/nonWhitespaceString" },
"default": { "$ref": "#/$defs/nonWhitespaceString" },
"failureSentinel": { "const": "ci-runner-selection-failed" }
"failureSentinel": { "const": "ubuntu-24.04" },
"failureSentinelMarker": { "const": "ci-runner-selection-failed" }
}
},
"fallbackLabelAllowlist": { "$ref": "#/$defs/nonEmptyUniqueStringArray" },
Expand Down
115 changes: 79 additions & 36 deletions .github/standards/runner-policy/runner-policy.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -346,17 +346,22 @@ export function validatePolicy(value) {
);
}
}
if (!approvedHostedRunnerLabels.has(value.governedReusableRunnerInput.failureSentinel)) {
throw new ConfigurationError(
"policy.governedReusableRunnerInput.failureSentinel must be an approved hosted runner label",
);
}
if (
approvedHostedRunnerLabels.has(value.governedReusableRunnerInput.failureSentinel) ||
approvedHostedRunnerLabels.has(value.governedReusableRunnerInput.failureSentinelMarker) ||
forbiddenHostedRunnerLabels.has(
value.governedReusableRunnerInput.failureSentinel.toLowerCase(),
value.governedReusableRunnerInput.failureSentinelMarker.toLowerCase(),
) ||
managedLabelRegexes.some((pattern) =>
pattern.test(value.governedReusableRunnerInput.failureSentinel),
pattern.test(value.governedReusableRunnerInput.failureSentinelMarker),
)
) {
throw new ConfigurationError(
"policy.governedReusableRunnerInput.failureSentinel must remain outside every hosted and managed runner label set",
"policy.governedReusableRunnerInput.failureSentinelMarker must remain outside every hosted and managed runner label set",
);
}
const hostedMatrixAxes = new Map();
Expand Down Expand Up @@ -2000,15 +2005,52 @@ function selfHostedSelectorConditionStatus(job, selectorId) {
return { approved: true };
}

function unroutableFailureStatus(jobId, target, job, jobs, policy) {
if (target !== policy.governedReusableRunnerInput.failureSentinel) {
function selectorFailureSentinelStepShape(step, policy, { requireMarkerName }) {
const stepKeys = isMapping(step) ? Object.keys(step) : [];
const allowedStepKeys = new Set(["name", "run", "shell"]);
const lines = typeof step?.run === "string" ? step.run.trim().split(/\r?\n/) : [];
if (
stepKeys.some((key) => !allowedStepKeys.has(key)) ||
typeof step?.name !== "string" ||
step.name.trim() === "" ||
(requireMarkerName && step.name !== policy.governedReusableRunnerInput.failureSentinelMarker) ||
(requireMarkerName && step.shell !== "bash") ||
(Object.hasOwn(step, "shell") && step.shell !== "bash") ||
lines.length !== 2 ||
!/^echo "::error::[A-Za-z0-9][A-Za-z0-9 .:_-]*"$/.test(lines[0].trim()) ||
lines[1].trim() !== "exit 1"
) {
return {
approved: false,
reason: requireMarkerName
? "the selector failure sentinel step must use the declared marker name, pin shell: bash, emit a static error annotation, and exit 1"
: "the legacy selector failure sentinel step must only emit a static error annotation and exit 1",
};
}
return { approved: true };
}

function selectorFailureSentinelStatus(jobId, target, job, jobs, policy) {
const { failureSentinel, failureSentinelMarker } = policy.governedReusableRunnerInput;
const legacySentinel = target === failureSentinelMarker;
const hostedSentinel = target === failureSentinel;
if (!legacySentinel && !hostedSentinel) {
return undefined;
}
const prerequisites = normalizeNeeds(job.needs);
if (hostedSentinel) {
const steps = Array.isArray(job.steps) ? job.steps : [];
const [step] = steps;
const hasMarkerStep =
steps.length === 1 && isMapping(step) && step.name === failureSentinelMarker;
if (!hasMarkerStep) {
return undefined;
}
}
if (prerequisites.length !== 1) {
return {
approved: false,
reason: `${jobId} must declare exactly one selector job in needs to use the unroutable failure sentinel`,
reason: `${jobId} must declare exactly one selector job in needs to use the selector failure sentinel`,
};
}
const [selectorId] = prerequisites;
Expand All @@ -2029,7 +2071,7 @@ function unroutableFailureStatus(jobId, target, job, jobs, policy) {
return {
approved: false,
reason:
"the unroutable failure sentinel requires the exact complement of a successful governed self-hosted selector route",
"the selector failure sentinel requires the exact complement of a successful governed self-hosted selector route",
};
}
const allowedJobKeys = new Set([
Expand All @@ -2045,7 +2087,7 @@ function unroutableFailureStatus(jobId, target, job, jobs, policy) {
if (extraJobKeys.length > 0) {
return {
approved: false,
reason: `the unroutable failure sentinel job has forbidden keys: ${extraJobKeys.join(", ")}`,
reason: `the selector failure sentinel job has forbidden keys: ${extraJobKeys.join(", ")}`,
};
}
if (
Expand All @@ -2055,33 +2097,23 @@ function unroutableFailureStatus(jobId, target, job, jobs, policy) {
) {
return {
approved: false,
reason: "the unroutable failure sentinel job requires timeout-minutes: 1 and permissions: {}",
reason: "the selector failure sentinel job requires timeout-minutes: 1 and permissions: {}",
};
}
if (!Array.isArray(job.steps) || job.steps.length !== 1) {
return {
approved: false,
reason: "the unroutable failure sentinel job requires exactly one rejecting shell step",
reason: "the selector failure sentinel job requires exactly one rejecting shell step",
};
}
const [step] = job.steps;
const stepKeys = isMapping(step) ? Object.keys(step) : [];
const lines = typeof step?.run === "string" ? step.run.trim().split(/\r?\n/) : [];
if (
stepKeys.some((key) => key !== "name" && key !== "run") ||
typeof step?.name !== "string" ||
step.name.trim() === "" ||
lines.length !== 2 ||
!/^echo "::error::[A-Za-z0-9][A-Za-z0-9 .:_-]*"$/.test(lines[0].trim()) ||
lines[1].trim() !== "exit 1"
) {
return {
approved: false,
reason:
"the unroutable failure sentinel step must only emit a static error annotation and exit 1",
};
const stepShape = selectorFailureSentinelStepShape(step, policy, {
requireMarkerName: hostedSentinel,
});
if (!stepShape.approved) {
return stepShape;
}
return { approved: true, selectorId };
return { approved: true, selectorId, legacy: legacySentinel };
}

function routeStatus(jobId, target, job, jobs, policy, reusableContract, localRunnerInputMode) {
Expand Down Expand Up @@ -2366,13 +2398,14 @@ function runnerTargetStatus(jobId, job, jobs, workflow, policy, file, workflowIn
};
}

const unroutableFailure = unroutableFailureStatus(jobId, target, job, jobs, policy);
if (unroutableFailure) {
const selectorFailureSentinel = selectorFailureSentinelStatus(jobId, target, job, jobs, policy);
if (selectorFailureSentinel) {
return {
approved: unroutableFailure.approved,
kind: unroutableFailure.approved ? "unroutable-failure" : "invalid",
route: { attempted: true, selectorId: unroutableFailure.selectorId },
...(unroutableFailure.reason ? { reason: unroutableFailure.reason } : {}),
approved: selectorFailureSentinel.approved,
kind: selectorFailureSentinel.approved ? "selector-failure-sentinel" : "invalid",
route: { attempted: true, selectorId: selectorFailureSentinel.selectorId },
...(selectorFailureSentinel.legacy ? { legacySentinel: true } : {}),
...(selectorFailureSentinel.reason ? { reason: selectorFailureSentinel.reason } : {}),
};
}

Expand Down Expand Up @@ -3375,10 +3408,20 @@ export async function auditRepository({
callers.push(jobId);
requiredNoDefaultCallers.set(target.route.selectorId, callers);
}
if (routingEnabled && target?.kind === "unroutable-failure" && target.approved) {
if (routingEnabled && target?.kind === "selector-failure-sentinel" && target.approved) {
const sentinels = approvedFailureSentinels.get(target.route.selectorId) ?? [];
sentinels.push(jobId);
approvedFailureSentinels.set(target.route.selectorId, sentinels);
if (target.legacySentinel) {
findings.push(
finding(
"selector-failure-sentinel-legacy",
file,
jobId,
"the legacy unroutable runs-on sentinel shape is accepted during migration; migrate to the hosted failureSentinel label with the declared failureSentinelMarker step",
),
);
}
}
const seedLocalPermissionFlow =
!isWorkflowCallExclusive(workflow) || !localIncomingFiles.has(file);
Expand Down Expand Up @@ -3597,7 +3640,7 @@ export async function auditRepository({
continue;
}

if (target?.kind === "unroutable-failure" && target.approved) {
if (target?.kind === "selector-failure-sentinel" && target.approved) {
continue;
}
if (target?.kind === "selector-output" && target.approved) {
Expand Down Expand Up @@ -3655,7 +3698,7 @@ export async function auditRepository({
"selector-failure-sentinel-required",
file,
jobId,
`required no-default local runner calls using ${selectorId} require exactly one approved ${policy.governedReusableRunnerInput.failureSentinel} rejection job for the same selector in this workflow; found ${sentinelIds.length}`,
`required no-default local runner calls using ${selectorId} require exactly one approved selector failure sentinel for the same selector in this workflow; found ${sentinelIds.length}`,
),
);
}
Expand Down
2 changes: 1 addition & 1 deletion docs/CATALOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ plugin manifests and kept in sync by CI — never hand-edit it; the category voc
- [`playbooks`](../plugins/playbooks) — Doctrine and knowledge playbooks as on-demand skills, plus a maintainer-facing update skill. boris — Boris Cherny's Claude Code workflow tips (howborisusesclaudecode.com); skill-authoring — Anthropic's internal skill-authoring playbook; fable-5 — Claude Fable 5's operating doctrine (self-authored, no upstream). The boris and skill-authoring packs vendor a verbatim upstream baseline; /playbooks:update drift-checks and syncs those baselines centrally (maintainers).
- [`claude-config`](../plugins/claude-config) — Eight configuration-health skills (plus setup) for a repo's Claude Code configuration: audit (settings.json / .mcp.json / hooks / plugins / permissions drift), audit-automation-gaps (evidence-gated verdicts on automation gaps), audit-permission-grants (allow-rule / allowed-tools grants for auto-mode durability and portability), audit-permission-state (which settings scopes exist and what rules each one holds — managed policy, user-global, project, local, and the pre-v2.1.211 start-directory copy), audit-instructions (locally-owned instruction surfaces vs current model capability — proposes removals/rewrites of instructions the model no longer needs, and detects cross-surface instruction conflicts), audit-prompting-postures (the additive lane — posture guidance the prompting guide says a component's purpose needs but the component does not carry), audit-pass (one coordinated, ordered, resumable pass over a named target — three-scope inventory, run-time-derived exclusion set, stable finding identity, suppression memory, resume, one human gate — delegating every check to the plugin that owns it), and unhobble (the empirical bare-baseline experiment: reversibly strip a repo's standing instructions, log real stumbles against the current model, re-add only what evidence earns).
- [`claude-memory`](../plugins/claude-memory) — Keeps a repo's Claude Code memory layer healthy and under your control, against criteria derived from official Claude Code documentation. The audit skill checks the instruction/memory layer (CLAUDE.md, CLAUDE.local.md, .claude/rules/, auto-memory) with a deterministic script-backed spine plus judgment-tier checks. The stateless skill inspects, disables, and (confirm-gated) purges Claude-written auto memory across all settings scopes.
- [`claude-ops`](../plugins/claude-ops) — Claude Code operations toolkit. Eight skills: audit-install-state (read-only audit of the machine-scope ~/.claude installation directory and ~/.claude.json — full inventory split into an authored surface and rolled-up bulk trees, product-managed retention vs genuinely unmanaged state, filename-scheme resolution before any process-liveness check, and deliberate/mid-experiment detection; reports, never deletes), observability (read locally captured telemetry — OTEL store, collector, hook-event JSONL, ccusage — with trend reports and store pruning), known-issues (search known Claude product GitHub bugs, check service health, maintain a persistent tracked-issue registry), changelog (ingest Claude Code changelog entries and integrate them into the current repo), plugins (bring a machine's plugin fleet current on demand — marketplace refresh, effective-scope updates including in-repo project/local installs, new-plugin install per policy, scope-divergence detection and explicit convergence), morning-brief (read-only gh-based operator morning view — queue-label counts, merge-ready PRs, parked decisions with their RECOMMENDED lines, and loop-lane telemetry freshness), lanes (start/restart/stop/status loop lanes as named background Claude Code sessions seeded from canonical prompt files, with per-lane model/effort, a repo-pull + marketplace-refresh launch step, and a consume-restarts action — an OS-schedulable reader that relaunches stopped lanes whose telemetry carries a restart_request), and a re-runnable setup action that settles where the known-issues registry lives. Plus a family of seven advisory *-audit telemetry-emitter hooks (API errors, config changes, instruction loads, permission denials, pre-compaction, skill usage, tool failures) that emit the shared hook-telemetry envelope, and a reference sink that maps envelopes into the hook-events.jsonl the observability skill reads.
- [`claude-ops`](../plugins/claude-ops) — Claude Code operations toolkit. Nine skills: inventory (read-only enumeration of the complete invocable surface — every built-in CLI command with aliases and hidden/gated status, every bundled skill, and every component of every installed plugin across all marketplaces; reads the shipped binary because upstream publishes no built-in command list, and carries an integrity verdict so a drifted build reports counts as floors rather than silently short totals), audit-install-state (read-only audit of the machine-scope ~/.claude installation directory and ~/.claude.json — full inventory split into an authored surface and rolled-up bulk trees, product-managed retention vs genuinely unmanaged state, filename-scheme resolution before any process-liveness check, and deliberate/mid-experiment detection; reports, never deletes), observability (read locally captured telemetry — OTEL store, collector, hook-event JSONL, ccusage — with trend reports and store pruning), known-issues (search known Claude product GitHub bugs, check service health, maintain a persistent tracked-issue registry), changelog (ingest Claude Code changelog entries and integrate them into the current repo), plugins (bring a machine's plugin fleet current on demand — marketplace refresh, effective-scope updates including in-repo project/local installs, new-plugin install per policy, scope-divergence detection and explicit convergence), morning-brief (read-only gh-based operator morning view — queue-label counts, merge-ready PRs, parked decisions with their RECOMMENDED lines, and loop-lane telemetry freshness), lanes (start/restart/stop/status loop lanes as named background Claude Code sessions seeded from canonical prompt files, with per-lane model/effort, a repo-pull + marketplace-refresh launch step, and a consume-restarts action — an OS-schedulable reader that relaunches stopped lanes whose telemetry carries a restart_request), and a re-runnable setup action that settles where the known-issues registry lives. Plus a family of seven advisory *-audit telemetry-emitter hooks (API errors, config changes, instruction loads, permission denials, pre-compaction, skill usage, tool failures) that emit the shared hook-telemetry envelope, and a reference sink that maps envelopes into the hook-events.jsonl the observability skill reads.
- [`rate-limit-guard`](../plugins/rate-limit-guard) — Shared rate-limit guard for loop lanes: a statusline wrapper tees the subscription rate-limit windows to a fixed machine-scope file, a StopFailure hook records rate-limit stops reactively, and a reader contract fixes how consuming sessions pause and resume.
- [`context-guard`](../plugins/context-guard) — Per-session context-window observability plus the first shipped consumer: a statusline wrapper tees each session's context_window fields to a per-session snapshot file, a zone resolver classifies usage into smart/acceptable/dumb bands (percentage bands plus window-class token bands, conservative-min combination, zones.json SSOT with shipped defaults), a reader contract fixes how consuming sessions interpret the snapshots, and zone-crossing hooks report once per transition into a worse zone across two channels — the continuation menu to the operator, who owns that choice, and to the model only the zone determination plus the counter-steer that a zone word is not a decay signal (advisory by default; an optional blocking mode gates new mutating work on a fresh dumb-zone snapshot with handoff-writing exempt), with a PostCompact hook persisting an evidence-degraded marker.
- [`plugin-quality`](../plugins/plugin-quality) — Post-use behavioral audit of Claude Code plugin components: a six-step audit workflow (evidence capture, grounded mapping in a fresh subagent, blindspot pass, interactive contract lock, presence-gated review seams, work-item emit with draft+confirm) over any skill, agent, hook, command, or config you have actually used — zone-informed by context-guard snapshots when present, conservative when not.
Expand Down
Loading
Loading