dashclaw-ship — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited dashclaw-ship (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
After a feature lands in DashClaw, many descriptions of the system go stale: generated artifacts, hand-authored docs, SDK READMEs, plugin/skill reference narratives, and the marketing site. This skill brings all of them back in line with the live code in one disciplined pass.
dashclaw-ship is the single command that resolves everything that could keep a change off production, and then lands it on main and ships it live. It does not stop at "the docs are accurate," it does not hand back a checklist, and it never says "leave it for you to land." If something blocks main — a failing gate, a stale count, an unbumped version, or a feature stranded on a branch — the job is to resolve that blocker and drive the code to main, then confirm the deploy. The accuracy sweep below is the means, not the end; stale docs are just one blocker. The end state is: our code is on main and the deploy is live.
The one thing the skill genuinely cannot finish itself is the credential-gated SDK publish (npm run release:sdks needs the owner's npm login + a PyPI token). Everything else — gates, version bump, doc accuracy, merging a feature branch into main, and pushing so Vercel deploys — the skill does end to end. Reduce that one handoff to a single copy-paste line; defer nothing else.
If the work is on a feature branch (not `main`), landing it is part of the job — rebase the branch onto the latest main, resolve any conflict (generated files won't conflict once living-merge is installed), run the gates, then fast-forward/merge into main and push. "I committed it to a branch" is not done; "it's on main and the deploy is live" is done. (The only exception is an explicit, still-in-force user instruction this session to hold a specific branch back — honor that, but say so out loud; absent that, ship it.)
The whole job rests on one distinction:
Getting these backwards is the classic mistake (hand-editing a generated file that the next refresh overwrites, or expecting a generator to write marketing copy). The map below and references/surfaces.md keep them straight.
Before touching anything, pin down two things:
git log/diffs. The sweep closes the gap between that code and its descriptions.docs:check validates only links + the Next.js version; version:check only the unified version literal). So the audit below is the only guard — never hardcode a count, never trust a number this skill or your memory quotes, derive each one fresh:docs/api-inventory.md (the generated truth).npm run sdk:count.mcp-server/lib/tools.js (count the name: 'dashclaw_*' entries and the distinct groups; they are hand-curated, so adding routes adds none).mcp-server/lib/resources.js.app/policies/lib/shields.js (this one drifts silently — verify, don't copy the prose).package.json, sdk/package.json, sdk-python/pyproject.toml share one number); npm run version:check confirms no drift.python -m livingcode query ... or app/lib/doctor/generated/shape.json.Get-Date -Format yyyy-MM-dd (PowerShell), or take it from the session's current-date context. You need a real current date so stamps advance to today, never to a guessed or remembered one.Most derived artifacts self-heal: the pre-commit hook runs npm run livingcode:refresh whenever app/api/, app/lib/, schema/schema.js, middleware.js, or livingcode/ change, and openapi/api-inventory regenerate alongside. So after a normal feature merge they're usually already current. Verify with the read-only checks; regenerate only what fails:
| Check (read-only) | If it fails, regenerate with |
|---|---|
npm run openapi:check | npm run openapi:generate |
npm run api:inventory:check | npm run api:inventory:generate |
npm run version:check | fix the hardcoded version (don't regenerate) |
| livingcode currency | npm run livingcode:refresh, then git status — staged generated diffs = it needed it; "unchanged" = already current |
Generated set — never hand-edit (regenerate instead): app/lib/doctor/generated/*, public/livingcode/index.html, the platform-intelligence SKILL.md, mcp-server/lib/routes-inventory.generated.json, the Python hook mirror under plugins/dashclaw/hooks/, and the .zip/.manifest bundles under public/downloads/.
Generated artifacts cover the route/tool shape; they don't write prose, SDK READMEs, curated reference narratives, or marketing copy. Every hand-authored surface (references/surfaces.md has the full list) can drift in three ways — check all three, because none of them is machine-checked:
For a large sweep, fan out subagents — one per surface domain — to map gaps in parallel and return a concrete work-list. Partition by non-overlapping file groups so parallel edits never collide.
#### Sweep the counts repo-wide (the #1 source of "I had to fix it by hand")
The same number is duplicated across README, both SDK READMEs, both reference narratives, PROJECT_DETAILS.md, and the landing/downloads pages — so a single feature can stale a dozen strings. After you read each fresh count in Phase 1, grep the OLD number repo-wide and reconcile every hit; assume you missed one until the grep comes back clean.
Shortcut (if present): `node scripts/check-doc-counts.mjs` reconciles the high-traffic counts (routes, SDK methods, MCP tools/resources, pre-built policies) and flags freshness stamps that predate their file's last commit — run it to catch the gated subset fast, then still hand-sweep the surfaces it lists as UNCOVERED (MCP "N groups", skill section counts, landing-page prose). CI runs it with --strict.
The counts that recur (and where they hide) live in references/surfaces.md; the dense offender is README.md, which alone hardcodes:
#### Sweep the marketing site — an explicit step, every run (it's the one that gets skipped)
The marketing site is in-app (no separate repo) and it is part of every ship, not an optional extra. It drifts in a way the count checker can't see: a shipped feature simply absent from pages that claim completeness. Run this check explicitly:
app/page.tsx (landing),app/self-host/page.tsx, app/downloads/page.tsx, app/connect/page.tsx, app/guides/* — and judge each zero-hit: a page that enumerates capabilities and omits a shipped major subsystem is a gap; a page where the feature is genuinely out of scope (e.g. a framework guide) is fine.
app/landingData.js exportsplatformFeatures / coreFeatures / operationalFeatures / platformCoverage / shippedHighlights which app/page.tsx imports but never renders. Adding a card there ships nothing. The landing page's rendered feature lists are the inline arrays inside `app/page.tsx` itself (the operations-section cards and corePrimitives/frameworkQuickstarts/signals from landingData). After any landing edit, verify with a grep that the feature name now appears in app/page.tsx.
governance API surface — every feature works out of the box," so a missing category card there is a false completeness claim. Add a card for any major new subsystem.
install paths and integration methods must be 100% right): each install command, package name + version pin, env var name, SDK method, MCP tool, and endpoint shown on these pages must match the code on this branch. Never write "(currently X)" published-registry version pins into prose — they stale at every publish.
#### Audit the dates — advance the living ones, never touch the historical ones
The user's standing rule is no old dates anywhere — but that means the dates that are supposed to track now, not the ones that anchor history. Get this backwards and you corrupt the record.
last-verified: front-matter (PROJECT_DETAILS.md, docs/sdk-reference.md, docs/sdk-parity.md), the **N active routes** (verified <date>) line in references/api-surface.md, and any "Last updated …" footer on a living doc (.impeccable.md, brand files). Pull the date from Phase 1 — a real current date, not a remembered one.CHANGELOG.md release dates (## [x.y.z] — <date>), "shipped/landed on <date>" notes, git-history references, dates inside .supergoal/, .organism/, AUDIT_FINDINGS.md, memory, and other scratch/working files. A past date here is correct; rewriting it is the bug.new Date('2026-..') in app code (instead of new Date()) freezes countdowns/filters to that day. If your feature touched such a file, flag it — but it's a code fix, not part of the doc sweep.When in doubt, ask: does this date claim to describe the present, or to record the past? Present → advance it. Past → leave it.
#### Two boundary gotchas to check explicitly
mcp-server/lib/tools.js / resources.js), not route-derived — adding routes adds zero MCP tools. Verify rather than assume a new tool is needed./api/finops/spend → getFleetSpend/getClaudeCodeSpend). They don't change SDK counts and must not be listed as SDK methods.Edit the gaps from Phase 2. references/surfaces.md is the file-by-file checklist. Three things that bite if missed:
public/downloads/dashclaw-platform-intelligence/references/{api-surface,platform-knowledge,troubleshooting}.md are hand-authored. Edit these — livingcode:refresh mirrors them into the plugin / .claude / ~/.claude skill trees and rebuilds the zips. Editing the mirror copies is pointless (they get overwritten).last-verified: / "verified <date>" / "Last updated" stamp (PROJECT_DETAILS.md, docs/sdk-reference.md, docs/sdk-parity.md, references/api-surface.md), bump that date to today's date from Phase 1 in the same edit — an unchanged stamp on a changed doc is itself a stale-date bug. Leave historical/CHANGELOG dates alone.After editing any reference source (or any generated-source path), run npm run livingcode:refresh to mirror the references into the plugin/project/global skill trees and rebuild the .zip/.manifest bundles. Then git status to see exactly what propagated.
DashClaw ships one version across the platform and both SDKs: package.json, sdk/package.json, and sdk-python/pyproject.toml must share the same number, enforced by npm run version:sync:check (CI + pre-commit) — and contracts/sdk/release-plan.json must carry that same number too (npm run contracts:check fails on drift, lines node_release_plan_version_mismatch / python_release_plan_version_mismatch). So the version number itself still advances on every ship, even a platform-only fix: you cannot bump the platform and leave the SDK manifests (or the release plan) behind. A bump is the norm.
*What is conditional is the publish, not the number. Republishing both SDKs to npm + PyPI at every version — even when no SDK code changed — just burns a version number with no code delta. So an SDK release is only cut when the SDK source actually changed this run.* Decide that here; you act on it in Phase 6.
Run this before the bump below, so the version-string churn from this run doesn't pollute the diff:
# Feature branch — diff the SDK source for the work being shipped:
git diff --name-only origin/main...HEAD -- sdk sdk-python
# Shipping accumulated commits straight on main — diff since the previous release
# commit (find it in `git log --oneline`: the last `release:` / version-stamped commit):
git diff --name-only <last-release-commit>..HEAD -- sdk sdk-python(Tags are not a reliable anchor here — release tagging stopped at v2.1.0 while the repo is on 4.x, so diff against the branch base / last release commit, not a tag.)
sdk/ or sdk-python/ beyond the version line in the two manifests) → this release republishes the SDKs; the Phase 6 reminder applies.^4.x still resolves to the last published 4.x). Record this in release-plan.json (below).Then check whether a bump is actually owed, so you don't double-bump: the feature you just swept for may already have bumped the version in its own commit. Compare the working tree against what's published:
node -p "require('./package.json').version" # current unified version (the three manifests)
npm view dashclaw version # last version published to npmcontracts/sdk/release-plan.json and the CHANGELOG agree with it, then skip to the release reminder in Phase 6.When a bump is owed, pick the increment from what shipped, and say which you chose and why:
| What shipped | Increment |
|---|---|
| Breaking SDK API change (renamed/removed method, changed signature) | major x+1.0.0 |
| New SDK method / route / subsystem (additive) | minor x.y+1.0 |
| Platform-only fix, hardening, or a docs/accuracy sweep with no new public surface | patch x.y.z+1 |
Apply it across all three manifests at once — never hand-edit one — then re-lock:
npm run version:set -- <x.y.z> # rewrites the three manifests together
npm install # re-syncs package-lock.json at the new numberThen bring the two hand-authored version carriers in line:
current_version for both node and python to the new number (this is required even when the SDK didn't change — contracts:check fails if it ever drifts from the manifests), leave next_bump: "none", and write a one-line reason that states whether the SDK actually changed: for a platform-only ship say so explicitly (e.g. "no Node/Python SDK source change — version advances per the unified model but the SDKs are NOT republished; registry stays at <last published>"), and set domains: ["platform"]. When SDK code did ship, describe it and include the SDK domains. (A recurring "bump SDK release plan to match manifests" commit exists precisely because the version field here is easy to forget.)## [Unreleased] block to ## [<x.y.z>] — <date> (with ### Added/Fixed/Security as fit), and open a fresh empty ## [Unreleased] above it.Verify the bump is clean and read the output — both gate the build:
npm run version:sync:check # the three manifests agree
npm run version:check # no version hardcoded into docs/CLAUDE.md (UI strings derive from the manifests via next.config.js)Major-bump trap: the repo root depends on its own published SDK ("dashclaw": "^4.0.0"). A patch/minor stays inside that caret range, so npm install is enough. For a major bump the new SDK isn't on npm yet — leave the self-dep pointing at the OLD major until release:sdks has actually published, or npm ci in CI fails on a lockfile it can't resolve. Grep the repo for any removed methods before raising the self-dep across a major.
The plugin bundle and CLI version independently (plugins/dashclaw/.claude-plugin/plugin.json and the CLI manifest) and are deliberately outside the sync check — bump those only if the plugin or CLI itself changed.
A push is its own step — run and read the output, don't assert success:
npm run lintnpx vitest run — the full suite (targeted runs miss regressions in unrelated files; a transient flake in the approval-flow CLI test is known — re-run to confirm)npx next build — for any change under app/**npm run route-sql:check — if any route changedThen commit + push to main with an explicit pathspec — never `git add -A`. Include the bumped manifests, package-lock.json, contracts/sdk/release-plan.json, and CHANGELOG.md in the same commit as the doc updates. Long-standing other-session files live uncommitted in the working tree (.impeccable.md, DESIGN.md, PRODUCT.md, stray docs/ specs); sweeping them in is a hygiene violation. The pre-commit hook will re-run livingcode:refresh and stage generated artifacts — that's expected; let it ride.
Then land it on main — this is the point, not a follow-up. If the commit went onto a feature branch, get it to main now: rebase the branch onto the latest origin/main (generated files won't conflict once living-merge is installed; resolve any authored conflict), re-run the gates if the rebase pulled in new commits, then fast-forward/merge the branch into main and git push origin main. A push to main is what fires the Vercel production build (vercel.json buildCommand), so confirm the deploy goes green rather than assuming — watch it, or ask the user to — and only then is it live. Never end with a "here's how to land it" checklist; stopping at the branch is the exact failure mode this skill exists to kill.
Finally, the SDK publish — conditional on the Phase 5 diff. npm run release:sdks builds and uploads both packages to npm + PyPI and needs the owner's npm login + a PyPI token, so it's outside this skill's reach either way. Whether you prompt it depends on whether the SDK source changed this release:
Bumped to<x.y.z>and pushed. The SDK changed this release — runnpm run release:sdksto publish the Node + Python SDKs to npm + PyPI (owner-only — needsnpm login+ a PyPI token). It's idempotent: any version already on the registry is skipped, so a re-run is safe.
Bumped to<x.y.z>and pushed (platform/docs only — no SDK source change, so the Node + Python SDKs are intentionally not republished; npm + PyPI stay at<last published>).
This conditional also governs the case where Phase 5 found the bump was already staged: surface the publish reminder only if that staged release actually carried SDK source changes. (release:sdks is idempotent, so running it when nothing changed is harmless — but don't reflexively prompt it; the point is to stop cutting empty SDK releases.)
Drive Phases 2–3 with parallel subagents, the way the reference sweep did:
{auto_generated[], hand_authored_gaps[]} work-list..impeccable framing + its exact file list, followed by an adversarial reviewer that re-checks the facts (counts, no-crypto framing, no hardcoded hex) and that nothing unrelated changed.release:sdks reminder.| User says | Reality |
|---|---|
| plugins / skills / hooks / livingcode / MCP server | Mostly generated — verify current (Phase 1); the only hand-authored pieces are the references/*.md sources (Phase 3) and the curated MCP tool/resource list (rarely changes) |
| connectors | Plugin manifests / .mcp.json / hooks.json enumerate no per-feature routes — usually no change; verify |
| docs | Hand-authored: PROJECT_DETAILS.md, README.md, app/docs/page.js, docs/sdk-reference.md, docs/sdk-parity.md, sdk/README.md, sdk-python/README.md. README.md is the densest — it hardcodes route, MCP-tool/group/resource, SDK-method, and guard-policy counts; sweep all of them |
| "no old dates" / "nothing stale" / "make everything accurate" | The two classes CI doesn't catch (Phase 2): every hardcoded count (grep the old number repo-wide, reconcile every hit) and every freshness date-stamp (advance the living ones to today, never touch CHANGELOG / history dates) |
| marketing site | In-app, no separate repo: app/page.tsx, app/landingData.js, app/downloads/page.tsx, app/self-host/page.tsx, app/connect/page.tsx, app/guides/* — an explicit Phase-2 step every run (see "Sweep the marketing site"), checking feature presence, the landingData dead-array trap, the /self-host completeness grid, and install-path accuracy |
| API inventory / OpenAPI | Generated — verify with the *:check commands |
| cut a release / bump the version | Unified platform+SDK number via npm run version:set + contracts/sdk/release-plan.json + CHANGELOG.md (Phase 5). The version number always advances; the SDK publish (npm run release:sdks, Phase 6) is reminded only when the SDK source actually changed this release — a platform-only ship bumps the number but does not republish the SDKs |
Full per-file detail, the SDK-doc checklist, and the canonical-fact sources are in `references/surfaces.md` — read it during Phase 2/3.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.