diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json index 4c8fe26..4454e00 100644 --- a/.cursor-plugin/plugin.json +++ b/.cursor-plugin/plugin.json @@ -15,7 +15,7 @@ "anti-slop" ], "license": "PolyForm-Noncommercial-1.0.0", - "version": "5.5.0", + "version": "5.6.0", "homepage": "https://github.com/agent-kit-startup/agent-kit", "repository": "https://github.com/agent-kit-startup/agent-kit", "logo": "dashboard/logo-marketplace.svg", diff --git a/.cursor/agent-kit.json b/.cursor/agent-kit.json index f893959..2d42b09 100644 --- a/.cursor/agent-kit.json +++ b/.cursor/agent-kit.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "version": "5.5.0", + "version": "5.6.0", "protected": [ ".cursor/HANDOFF.md", ".cursor/agents/test-suites.md", diff --git a/.cursor/scripts/plan-external-review-model-routing.test.mjs b/.cursor/scripts/plan-external-review-model-routing.test.mjs index 0613882..f2c07b4 100644 --- a/.cursor/scripts/plan-external-review-model-routing.test.mjs +++ b/.cursor/scripts/plan-external-review-model-routing.test.mjs @@ -1,6 +1,7 @@ import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; -import { readFileSync } from "node:fs"; +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; import { dirname, join, resolve } from "node:path"; import { test } from "node:test"; import { fileURLToPath } from "node:url"; @@ -29,6 +30,31 @@ ${body} return spawnSync("bash", ["-c", bash], { encoding: "utf8" }); } +function extractVarAssignment(name) { + const out = spawnSync("grep", ["-m1", `^${name}=`, SCRIPT], { encoding: "utf8" }); + assert.ok(out.stdout.trim().length > 0, `failed to extract ${name}`); + return out.stdout; +} + +// Runs the real monitor_wants_advisor() from the script (plus its +// ADVISOR_ESCALATE_SENTINEL constant) against a scratch file, mirroring how +// maybe_run_advisor() gates the opus escalation. +function monitorWantsAdvisor(root, rel) { + const prelude = [ + extractVarAssignment("ADVISOR_ESCALATE_SENTINEL"), + extractFn("monitor_wants_advisor"), + ].join("\n"); + const bash = ` +set -euo pipefail +ROOT=${JSON.stringify(root)} +${prelude} +if monitor_wants_advisor ${JSON.stringify(rel)}; then echo yes; else echo no; fi +`; + const out = spawnSync("bash", ["-c", bash], { encoding: "utf8" }); + assert.equal(out.status, 0, out.stderr); + return out.stdout.trim(); +} + // Historical default at f27910a was Haiku; classifier-capable Sonnet is the post-amendment default. test("launcher model routing: classifier-capable reviewer spawn, escalate sentinel, same-model refuse", () => { assert.match(SRC, /--reviewer-model/); @@ -43,6 +69,46 @@ test("launcher model routing: classifier-capable reviewer spawn, escalate sentin assert.match(SRC, /This is not a silent self-review/); }); +test("monitor_wants_advisor: anchored to a standalone sentinel line, ignores prose/negated mentions", () => { + const dir = mkdtempSync(join(tmpdir(), "audits-advisor-sentinel-")); + try { + const proseFile = "prose-negated.md"; + writeFileSync( + join(dir, proseFile), + [ + "**To-do:** Opus advisor only when monitor marks ``.", + "Neither finding is high/critical severity; no `` needed.", + ].join("\n"), + ); + assert.equal( + monitorWantsAdvisor(dir, proseFile), + "no", + "inline/negated prose mention of the sentinel must not trigger escalation", + ); + + const genuineFile = "genuine-escalate.md"; + writeFileSync( + join(dir, genuineFile), + ["## Advisor", "", "", ""].join("\n"), + ); + assert.equal( + monitorWantsAdvisor(dir, genuineFile), + "yes", + "a genuine standalone sentinel comment line must trigger escalation", + ); + + const indentedFile = "genuine-indented.md"; + writeFileSync(join(dir, indentedFile), "text\n \nmore\n"); + assert.equal( + monitorWantsAdvisor(dir, indentedFile), + "yes", + "surrounding whitespace on an otherwise-standalone sentinel line must still trigger escalation", + ); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); + test("normalize_model_family collapses vendor aliases", () => { const out = runBash(` echo "$(normalize_model_family "")" diff --git a/.cursor/scripts/plan-external-review.sh b/.cursor/scripts/plan-external-review.sh index 1939815..2922814 100755 --- a/.cursor/scripts/plan-external-review.sh +++ b/.cursor/scripts/plan-external-review.sh @@ -1731,7 +1731,11 @@ exec_reviewer() { monitor_wants_advisor() { local rel="$1" - [[ -f "$ROOT/$rel" ]] && grep -qF "$ADVISOR_ESCALATE_SENTINEL" "$ROOT/$rel" + # Anchored: must be a standalone HTML-comment line (per the prompt template's + # "exactly one HTML comment line" contract), not just any substring/prose + # mention (including negations like "no ...advisor-escalate... needed"). + local pattern="^[[:space:]]*${ADVISOR_ESCALATE_SENTINEL}[[:space:]]*$" + [[ -f "$ROOT/$rel" ]] && grep -qE "$pattern" "$ROOT/$rel" } build_advisor_prompt() { diff --git a/.gitignore b/.gitignore index 4438cc9..9e3a274 100644 --- a/.gitignore +++ b/.gitignore @@ -87,3 +87,10 @@ __pycache__/ # Landing build output — derived from the Claude Design export by # `pnpm landing:build`; never edit by hand. See docs/agentkit-landing.md. .cursor/context/landing-missionkit/dist/ + +# Landing promote/rollback release archives — timestamped zips of bytes that +# were actually deployed to missionkit.io, written by `pnpm landing:promote` +# / archived pre-promote for `pnpm landing:rollback`. Derived, potentially +# large (binary assets), and account-specific; not source. See +# docs/agentkit-landing.md and missionkit-staging-promote.plan.md. +.cursor/context/landing-missionkit/releases/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 46c0c55..533cc1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,23 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and ## [Unreleased] +## [5.6.0] - 2026-08-24 + +### Added + +- Mission Kit landing gains a **staging.missionkit.io hop before every production deploy**: `pnpm landing:deploy:staging` (per-file upload into a `staging/` subdomain directory, never the production root), `pnpm landing:verify:staging` (headless-Chrome acceptance gate, reused as `scripts/verify-landing.mjs --url `), and a Phase-3 HITL gate (operator compares staging against the Claude Design canvas) before `pnpm landing:promote` re-deploys the *exact* staging-validated `dist/` bytes to `missionkit.io` — never rebuilding — with `pnpm landing:rollback [release]` restoring a prior archived production artifact in one command. New `scripts/lib/hostinger.mjs` reimplements the Hostinger REST calls the prior interactive-MCP deploy used, standalone (zero new dependencies). Staging noindex ships as a generated `staging/.htaccess` + `robots.txt`, not a `dist/` toggle, so promote's identical-bytes invariant can't accidentally noindex production. **Live-verified in production 2026-08-23**: staged, operator-approved, and promoted to `missionkit.io` for real, byte-identical DOM confirmed on both hosts. Plan `missionkit-staging-promote`. +- `scripts/build-landing.mjs` gains a **build-time changelog box injector**: it reads `CHANGELOG.md`'s latest public-facing (non-`[Unreleased]`) release entry and injects a plain-text (markdown decoration stripped, not converted — so truncation can never leak an unclosed token), HTML-escaped preview into a canvas-provided `
` container. The container does not exist in the current Mission Kit Landing canvas export yet, so this step currently no-ops with a logged warning; it is the repo-side half of replacing the Footer CTA's terminal-typer animation with a real changelog box. Canvas-side change (plus the hero eyebrow → live Mission Kit release badge, scoped to the one authorized `img.shields.io` live-request exception) is a pending hand-off: `.cursor/context/landing-missionkit/UPSTREAM-DESIGN-FIX-PROMPT-badge-changelog.md`. Confirmed edit route (this account has no owned/shared write access to that canvas) and the scoped self-containment exception are recorded in ADR `2026-08-05_landing-external-design-source-of-record.md` (2026-08-22 addendum). Plan `landing-eyebrow-live-badge-and-changelog-box`. +- `registry/registry.json`'s L0 section is now **generated from code** (`packages/cli/src/lifecycle/l0.ts`'s `L0_ARTIFACTS`) on every `scripts/build-registry.mjs` run, instead of hand-curated — reviving the abandoned `archive/l0-generate-from-code` tag against the current pipeline. New `scripts/derive-l0-artifacts.ts` + `scripts/emit-l0-artifacts.mjs` (via `pnpm --filter @dadado/agent-kit-cli exec tsx`, since `tsx` isn't hoisted to the repo root). Verified idempotent (two consecutive rebuilds produce zero diff) and non-regressive on hand-curated L2 entries (`readCuratedArtifacts` narrowed to `layer === "L2"` only — L0 no longer needs preserving-by-hand). Closed two real drift gaps surfaced along the way: `dashboard-broadcast`/`update` commands added to `L0_ARTIFACTS` (existed on disk, were never declared), and `install.md`'s Port B table reconciled to match. `l0.test.ts` deliberately keeps its portable "legacy `onboard` absent" assert rather than a strict `registryL0Paths === canonicalSources` equality — private-repo parity is already enforced by CI's repo-gated "Registry catalog parity" step, and the public mirror independently owns `registry/**`, so a second unconditional test-level assert would reintroduce the public-CI break `errors/2026-07-24_public-sync-ci-test-portability.md` already paid down twice. Plan `l0-generate-from-code-revival`. +- Public storefront README gets the visual identity it never had: a shields badge row (npm version, PolyForm NC license, Node >=20, public Release Latest — no CI badge, the public mirror pipeline skips factory tests), a helmet hero (`dashboard/logo.svg`, visual-kit palette) plus the CLI's 9-line `HELMET_ASCII` as a monochrome fenced block, a curated 2×2 Mission Control screenshot gallery (`assets/production/*.png`, newly synced), and a GitHub social preview asset (`dashboard/social-preview.svg`/`.png`, 1280×640, helmet on the `#0b0e14` plate per the marketplace logo spec) with an apply checklist in `docs/github-about.md`. `packages/cli/README.md` (the actual npm storefront) gets minimal badge/hero parity. Plan `readme-public-hero-badges`. +- **DeepSeek Harness (`dsh`) modernization study**, docs-only: ADR `2026-08-22_deepseek-harness-reject-adapt-concepts-docs.md` ranks 12 deepseek-harness concepts against Agent Kit surfaces (`/run-plan` tick contract, Task worker dispatch, HITL Ask questions, memory-loop) — 6 reject, 4 already covered kit-native, 1 adapt-concepts (docs-awareness only: a durable per-step replayable event log is a real gap but stays unadopted to avoid a second state system alongside markdown HANDOFF/memory), 1 thin-adapter note (a second, unrelated ACP surface). No `dsh`/Cordis dependency, no plugin-composition runtime, no relaxation of sequential one-Task-per-plan dispatch. No incorporation/modernization work ships from this study; any uptake of the two non-reject items is deferred to a separately-scoped future plan. Plan `deepseek-harness-modernization-study` (Phase 0/1; Phase 2 is this changelog closeout). + +### Fixed + +- Audits `monitor_wants_advisor()` (`.cursor/scripts/plan-external-review.sh:1734`) used an unanchored `grep -qF` substring match on the `` sentinel, so any prose *mention* of it — including a negation like "no `` needed" — false-positived and re-spawned a costly Opus advisor pass on every future audit run touching that monitor. Now anchored to a standalone HTML-comment line (`grep -qE '^[[:space:]]*[[:space:]]*$'`), matching the prompt template's own "exactly one HTML comment line" contract. Verified against both known false positives (`plan-monitor-audits-atomic-wait-reviewer-fallback.md`, `plan-monitor-deepseek-harness-modernization-study.md:79`), which no longer match; a genuine standalone sentinel line still triggers escalation. Regression test added (`plan-external-review-model-routing.test.mjs`). Found by an Opus advisor pass during the 2026-08-22 `deepseek-harness-modernization-study` audit; done-marker suppression (Phase 2) considered and skipped as unsafe — see plan body. Plan `audits-advisor-escalate-sentinel-anchor-fix`. +- `scripts/deploy-landing-staging.mjs`'s DNS-record step raced Hostinger's own auto-provisioning: creating a new subdomain already auto-adds an `ALIAS` record for it, but the script immediately re-checked the zone and, seeing it not yet propagated, tried to add a conflicting `CNAME` — a real `HTTP 422` (`RRset ... IN ALIAS must not be used with CNAME on the same name`) on the first live production run. Now skips the explicit DNS step entirely when this run just created the subdomain; only checks/adds on the branch where the subdomain already existed. Found live 2026-08-23, plan `missionkit-staging-promote`. +- `check-public-deny-links.mjs` failed closed only on an explicit manifest `!exclude`; a README/docs link into a path the manifest never mentions at all (neither included nor excluded) passed silently and 404'd on the public mirror. Now checks `isPublicSyncedPath` directly, so "absent from the manifest" fails too. Also fixes two by-products of the stricter check rather than suppressing them: template `{placeholder}` interpolations (`isPlaceholderTarget`, matching the existing `check-markdown-links.mjs` convention) and a real folder link in a `.mdc` rule file whose trailing-slash target needs restoring before it matches the `git-hooks/**` glob. Broad Intake 2026-08-16 error finding, plan `readme-public-hero-badges`. +- `sync-public.mjs`'s content-denylist scan read every synced file via `readFileSync(path, "utf8")` and relied on a try/catch to skip binaries — but that read never throws on binary bytes in Node, so the intended binary exemption never actually applied. Latent since the manifest never included a binary file before; surfaced as a false positive (a private-client-name denylist term coincidentally present in a screenshot's compressed PNG bytes) the moment `assets/production/*.png` was added. Fixed with a real binary check (NUL byte in the first 8000 bytes, the same heuristic `git`/`grep -I` use) ahead of the UTF-8 decode + regex scan. + ## [5.5.0] - 2026-08-22 ### Added diff --git a/README.md b/README.md index dde8b27..98ab5d7 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,14 @@ # Mission Kit +[![npm version of @dadado/agent-kit-cli](https://img.shields.io/npm/v/%40dadado%2Fagent-kit-cli?label=npm&color=0C8DEB)](https://www.npmjs.com/package/@dadado/agent-kit-cli) +[![License: PolyForm Noncommercial 1.0.0](https://img.shields.io/badge/license-PolyForm--Noncommercial--1.0.0-blue)](LICENSE) +[![Node.js >=20](https://img.shields.io/badge/node-%3E%3D20-339933)](https://nodejs.org) +[![Latest release of the public agent-kit repository](https://img.shields.io/github/v/release/agent-kit-startup/agent-kit?label=release&color=00D0E7)](https://github.com/agent-kit-startup/agent-kit/releases/latest) + +

+ Mission Kit helmet mark +

+ [Watch the demo on YouTube](https://www.youtube.com/watch?v=9mrAg6Mczfg) · [missionkit.io](https://missionkit.io) **Development operations built into Cursor and VS Code.** @@ -34,6 +43,21 @@ Long AI coding sessions fall apart when the context window fills up. Mission Kit Deep dives: [getting started](docs/getting-started.md), [five-layer claim matrix](docs/five-layer-claim-matrix.md), [external plan review](docs/external-plan-review.md), [domain packs](docs/domain-packs.md), [personas](docs/personas-contract.md). +## Mission Control + +A local dashboard over the same workspace state the CLI drives — current mission, checklist, crew activity, and the flight log. + + + + + + + + + + +
![Mission Control Current mission panel](assets/production/1-mission-control-current-mission.png)![Mission Control Checklist panel](assets/production/2-mission-control-check-list.png)
![Mission Control Crew Monitor panel](assets/production/3-mission-control-Crew-Monitor.png)![Mission Control Flight Log panel](assets/production/4-mission-control-flight-log.png)
+ ## Install ### In Cursor (recommended) @@ -54,6 +78,20 @@ From your project root: npx @dadado/agent-kit-cli install ``` +The CLI welcome screen greets you with the same helmet, in ASCII: + +```text + ____ + .-' '-. + / .--. \ + | / \ | + | | () | | + | \ / | + \ '--' / + '-.____.-' + /_/ \_\ +``` + Unpinned `npx` resolves to the latest publish. Pin when you need a reproducible install: `npx @dadado/agent-kit-cli@x.y.z install`. That installs slash commands and a small set of rules into the project. Walkthrough: [docs/getting-started.md](docs/getting-started.md). diff --git a/assets/production/1-mission-control-current-mission.png b/assets/production/1-mission-control-current-mission.png new file mode 100644 index 0000000..331fa17 Binary files /dev/null and b/assets/production/1-mission-control-current-mission.png differ diff --git a/assets/production/2-mission-control-check-list.png b/assets/production/2-mission-control-check-list.png new file mode 100644 index 0000000..93c3df1 Binary files /dev/null and b/assets/production/2-mission-control-check-list.png differ diff --git a/assets/production/3-mission-control-Crew-Monitor.png b/assets/production/3-mission-control-Crew-Monitor.png new file mode 100644 index 0000000..8c233f0 Binary files /dev/null and b/assets/production/3-mission-control-Crew-Monitor.png differ diff --git a/assets/production/4-mission-control-flight-log.png b/assets/production/4-mission-control-flight-log.png new file mode 100644 index 0000000..1d7018c Binary files /dev/null and b/assets/production/4-mission-control-flight-log.png differ diff --git a/dashboard/social-preview.png b/dashboard/social-preview.png new file mode 100644 index 0000000..947c608 Binary files /dev/null and b/dashboard/social-preview.png differ diff --git a/dashboard/social-preview.svg b/dashboard/social-preview.svg new file mode 100644 index 0000000..6b2816b --- /dev/null +++ b/dashboard/social-preview.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + Mission Kit + diff --git a/docs/agentkit-landing.md b/docs/agentkit-landing.md index d3bdab5..df79f61 100644 --- a/docs/agentkit-landing.md +++ b/docs/agentkit-landing.md @@ -44,10 +44,35 @@ pnpm landing:build:check pnpm landing:serve # Open http://127.0.0.1:4173/ -# 5. Deploy -# Zip dist/ and deploy via hosting_deployStaticWebsite to missionkit.io +# 5. Deploy to staging — NEVER production +pnpm landing:deploy:staging +# Ensures the `staging` subdomain + DNS record exist, then uploads dist/ +# into staging/ on the same hosting account. Never touches missionkit.io. + +# 6. Automated acceptance against the live staging URL +pnpm landing:verify:staging +# Headless-Chrome render (not curl): 0 unresolved bindings, #dc-root +# present, video + iframe present, no request outside the origin (mod the +# hero badge's img.shields.io exception below). + +# 7. HITL — open https://staging.missionkit.io, compare side by side +# against the Claude Design canvas, approve explicitly. Nothing below +# this line runs on a passing checklist alone. + +# 8. Promote (only after explicit approval) — re-deploys the SAME dist/ +# bytes already validated on staging. Never rebuilds. +pnpm landing:promote + +# Rollback, any time after a promote +pnpm landing:rollback # redeploys the immediately previous release +pnpm landing:rollback # redeploys a specific archived release ``` +Every production deploy goes through staging first — there is no script path +that deploys straight to `missionkit.io`. `landing:promote` is the only script +that ever writes to the production root; `landing:deploy:staging` refuses (by +assertion, in code) to write anywhere outside `staging/`. + ### What the build changes (and nothing more) 1. Drops stylesheets the landing does not reference (design-system tokens, aggregator) @@ -55,13 +80,42 @@ pnpm landing:serve 3. Injects a static ``, description, canonical, Open Graph, Twitter Card, and favicon into `<head>` for crawlers (dc-runtime moves `<helmet>` into `<head>` at boot, which is too late for anything that does not execute JS) +4. Injects a build-time **changelog box**: the latest public-facing `CHANGELOG.md` + release entry, rendered as plain escaped text, into a canvas-provided + `<div data-changelog-content></div>` container. No-ops with a logged warning + (never fails the build) until the canvas ships that container — see + `.cursor/context/landing-missionkit/UPSTREAM-DESIGN-FIX-PROMPT-badge-changelog.md` + for the exact contract and the paired hero-eyebrow live-badge change. + +### Live vs. build-time exception + +Everything the build ships is self-contained by default (proven headless, external +DNS blocked). One narrow, named exception: the hero release badge is authorized to +make a live client-side request to `img.shields.io` only (same pattern as the +`README.md` release badge). No other element — including the changelog box above, +which is build-time only — gets a live/runtime external request. See ADR +`decisions/2026-08-05_landing-external-design-source-of-record.md` (2026-08-22 +addendum) for the full reasoning and the confirmed edit route (this repo has no +owned/shared write access to the canvas; changes go through the hand-off-prompt +pattern, not a direct canvas edit). ## Deployment - **Hosting:** Hostinger web hosting (`u262837109`), addon vhost root `/home/u262837109/domains/missionkit.io/public_html` -- **DNS:** `@` ALIAS to `missionkit.io.cdn.hstgr.net` (Hostinger CDN) +- **DNS:** `@` ALIAS to `missionkit.io.cdn.hstgr.net` (Hostinger CDN); `staging` CNAME to the same CDN target (added by `landing:deploy:staging` on first run, idempotent thereafter) - **SSL:** Hostinger HTTPS (HTTP/2 200 verified) -- **Deploy method:** `hosting_deployStaticWebsite` with a zip of `dist/` preserving directory structure +- **Deploy method (production):** `scripts/promote-landing.mjs` (`pnpm landing:promote`) — zips the already-staging-validated `dist/`, uploads it, then triggers the Hostinger static-site deploy against `domain=missionkit.io`. Same underlying REST call the interactive `hosting_deployStaticWebsite` MCP tool used for the original production deploy, reimplemented as a standalone script (see `scripts/lib/hostinger.mjs`) so it can run outside an MCP session +- **Deploy method (staging):** `scripts/deploy-landing-staging.mjs` (`pnpm landing:deploy:staging`) — per-file upload into the `staging/` subdomain directory. Deliberately does **not** use the website-level deploy trigger: that endpoint's extraction target is the website root, and a subdomain is not a separate "website" in this API (it does not appear in the `/websites` listing, only under `/subdomains`), so routing staging through it risks overwriting production. Every upload path is asserted to start with `staging/` + +### Staging pipeline (missionkit-staging-promote.plan.md) + +- **Subdomain:** `staging.missionkit.io`, created via `hosting_createWebsiteSubdomainV1` (`POST .../websites/missionkit.io/subdomains`), directory `staging/` under the same document root as production — not a separate hosting account. +- **Never indexed.** Staging must not carry `noindex` inside `dist/`'s own bytes: `landing:promote` ships the *exact same* `dist/` bytes already validated on staging, and if those bytes carried a `noindex` meta tag, promoting would noindex production too. Instead, `landing:deploy:staging` writes two generated, staging-only files alongside the upload — `staging/.htaccess` (`Header set X-Robots-Tag "noindex, nofollow"`) and `staging/robots.txt` (`Disallow: /`) — neither is part of `dist/`, neither is ever promoted. This is a deliberate deviation from the plan's literal wording ("toggle in build-landing.mjs"), made to preserve the plan's own higher-priority rule that promote never rebuilds and ships identical bytes. +- **Residual:** the subdomain directory lives under the same document root as production (`.../public_html/staging/`), so the staged build may also be reachable at `missionkit.io/staging/` in addition to `staging.missionkit.io/`. The `X-Robots-Tag` header covers indexing either way; this is noted, not solved, here. +- **Acceptance:** `pnpm landing:verify:staging` (`scripts/verify-landing.mjs --url https://staging.missionkit.io/`) — headless Chrome (`--headless=new --dump-dom`, external DNS blocked via `--host-resolver-rules`, same self-containment technique as the original production acceptance gate) asserting `#dc-root`, zero unresolved bindings, a `<video>`, the `mc/dashboard.html` iframe, and no request to a host outside the origin (the hero badge's `img.shields.io` call is the one named live exception; see "Live vs. build-time exception" above). +- **Promote/rollback safety:** `landing:promote` archives whatever is *currently live* at `missionkit.io` into `.cursor/context/landing-missionkit/releases/<timestamp>/` (zip, gitignored) **before** deploying — fetched over public HTTPS per file (not the Hostinger file-content API, which refuses binary files) so videos/images are captured too. `landing:rollback [release]` redeploys an archived release; with no argument, the immediately previous one. Neither script ever calls `landing:build`. +- **HITL gate:** Phase 3 of the plan — operator opens `https://staging.missionkit.io`, compares against the Claude Design canvas, approves explicitly. No script promotes on a passing `landing:verify:staging` alone. +- **Operator-owed first run:** the upload leg (`scripts/lib/hostinger.mjs`'s `uploadFile`) reconstructs an undocumented Hostinger upload sequence (POST pre-create, then a TUS-style `PATCH`) from the vendored `hostinger-api-mcp` package's source, since this sandbox's permission classifier refused every mutating call attempted (subdomain create, adding a vendored dependency) and that refusal was not retried per this repo's worker contract. All read-only calls (website lookup, subdomain listing, DNS zone, file listing) were exercised live against the real account and work as documented; run `pnpm landing:deploy:staging -- --dry-run` first, and have the first real run be operator-attended. ### Rollback diff --git a/docs/github-about.md b/docs/github-about.md index 5e22933..93284ef 100644 --- a/docs/github-about.md +++ b/docs/github-about.md @@ -34,4 +34,12 @@ Legacy `agent.startupkit.com.br` redirects here; do not set it as the GitHub Web ## Social preview -GitHub uses the **README** or an image under **Settings → General → Social preview**. Keep the README as the main narrative; do not duplicate the About text there. +Asset: `dashboard/social-preview.png` (1280×640, `dashboard/social-preview.svg` is the vector source) — helmet mark on the Mission Control `#0b0e14` plate, same recipe as `dashboard/logo-marketplace.svg` (Cursor-skin stroke art, centered), plus the "Mission Kit" wordmark. Synced via `dashboard/**` in `scripts/public-sync.manifest`, so it's present on the public mirror after the next promote + sync-public. + +**Apply checklist (manual, public repo, after the asset has synced):** + +1. `agent-kit-startup/agent-kit` → **Settings → General → Social preview → Edit**. +2. Upload `dashboard/social-preview.png` from the public mirror tree (PNG/JPG only, GitHub does not accept SVG here — that's why both files exist). +3. Save. GitHub shows this image for link unfurls (Slack, X/Twitter, Discord, etc.) instead of falling back to the README/avatar. + +Keep the README as the main narrative; do not duplicate the About text there. diff --git a/docs/marketplace.md b/docs/marketplace.md index 8683f76..eaad9a2 100644 --- a/docs/marketplace.md +++ b/docs/marketplace.md @@ -71,7 +71,7 @@ The plugin root is the **parent of `.cursor-plugin/`** - the repo root. Cursor's | `agents` | `.cursor/agents` | 14 subagent definitions | | `commands` | `.cursor/commands` | 27 slash commands | | `hooks` | `.cursor/hooks.json` | Thin adapters; every one is fail-open, and `sessionStart` surfaces the degraded-mode diagnostic (see "Hook resolution boundary" below) | -| `logo` | `dashboard/logo-marketplace.svg` | 512×512 SVG (1:1), transparent canvas, rounded plate `#0b0e14`, Cursor-skin stroke mark from `logo-cursor.svg` centered (~10% padding). Chrome keeps unplated `logo.svg` (legacy helmet) and `logo-cursor.svg` (16px header). Must sit on a path inside `scripts/public-sync.manifest`. `assets/**` is **not** synced and would be dropped from the public mirror without failing anything. Production copies of the three marks live under `assets/production/` (private; not public-synced) | +| `logo` | `dashboard/logo-marketplace.svg` | 512×512 SVG (1:1), transparent canvas, rounded plate `#0b0e14`, Cursor-skin stroke mark from `logo-cursor.svg` centered (~10% padding). Chrome keeps unplated `logo.svg` (legacy helmet) and `logo-cursor.svg` (16px header). Must sit on a path inside `scripts/public-sync.manifest`. `assets/**` is **not** synced in general (only `assets/production/*.png` is, for the README screenshots — see readme-public-hero-badges Phase 2); a link/image into any other `assets/` path now fails `check:public-deny-links` instead of silently dropping. Production copies of the three marks live under `assets/production/` (private SVGs; not public-synced, `logo-marketplace.svg` above is generated from the same art at the `dashboard/` path instead) | #### Hook resolution boundary diff --git a/install.md b/install.md index 4d623c6..159b619 100644 --- a/install.md +++ b/install.md @@ -105,6 +105,7 @@ Copy **only** these artifacts (same content from SoT / registry), not the monore | `.cursor/commands/handoff.md` | idem | | `.cursor/commands/summary.md` | idem | | `.cursor/commands/dashboard.md` | idem | +| `.cursor/commands/dashboard-broadcast.md` | idem | | `.cursor/commands/git-staging.md` | idem | | `.cursor/commands/git-prod.md` | idem | | `.cursor/commands/plan-external-review.md` | idem | @@ -112,6 +113,7 @@ Copy **only** these artifacts (same content from SoT / registry), not the monore | `.cursor/commands/field-report-resolve.md` | idem | | `.cursor/commands/dogfood.md` | idem | | `.cursor/commands/cursor-update-awareness.md` | idem | +| `.cursor/commands/update.md` | idem | | `.cursor/context/templates/plan.md` | idem | | `.cursor/context/templates/context-pack.md` | idem | | `.cursor/context/templates/task-brief.md` | idem | diff --git a/package.json b/package.json index 7bdaf75..cde03a4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "agent-kit", - "version": "5.5.0", + "version": "5.6.0", "description": "HITL framework for AI-assisted IDEs: plan, handoff, staging-to-prod, memory loop; project-aware setup for Cursor, VS Code, and Windsurf.", "private": true, "license": "PolyForm-Noncommercial-1.0.0", @@ -41,6 +41,11 @@ "landing:build": "node scripts/build-landing.mjs", "landing:build:check": "node scripts/build-landing.mjs --check", "landing:serve": "node scripts/serve-landing.mjs", + "landing:deploy:staging": "node scripts/deploy-landing-staging.mjs", + "landing:verify": "node scripts/verify-landing.mjs", + "landing:verify:staging": "node scripts/verify-landing.mjs --url https://staging.missionkit.io/", + "landing:promote": "node scripts/promote-landing.mjs", + "landing:rollback": "node scripts/rollback-landing.mjs", "build": "turbo run build", "dev": "turbo run dev", "lint": "biome check dashboard scripts .cursor/scripts && turbo run lint", diff --git a/packages/cli/README.md b/packages/cli/README.md index 24d3410..0c2c974 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -1,5 +1,12 @@ # @dadado/agent-kit-cli +[![npm version of @dadado/agent-kit-cli](https://img.shields.io/npm/v/%40dadado%2Fagent-kit-cli?label=npm&color=0C8DEB)](https://www.npmjs.com/package/@dadado/agent-kit-cli) +[![License: PolyForm Noncommercial 1.0.0](https://img.shields.io/badge/license-PolyForm--Noncommercial--1.0.0-blue)](LICENSE) +[![Node.js >=20](https://img.shields.io/badge/node-%3E%3D20-339933)](https://nodejs.org) +[![Latest release of the public agent-kit repository](https://img.shields.io/github/v/release/agent-kit-startup/agent-kit?label=release&color=00D0E7)](https://github.com/agent-kit-startup/agent-kit/releases/latest) + +<img src="https://raw.githubusercontent.com/agent-kit-startup/agent-kit/main/dashboard/logo.svg" alt="Mission Kit helmet mark" width="120"> + Agent Kit CLI: HITL operating-layer install and tooling for AI-assisted IDEs (rules, skills, plan/handoff, staging-to-prod, memory). It installs local workspace contracts; it is not a hosted control plane or graph workflow runtime. ## Install diff --git a/packages/cli/package.json b/packages/cli/package.json index 0ac1af1..358f465 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@dadado/agent-kit-cli", - "version": "5.5.0", + "version": "5.6.0", "description": "Agent Kit CLI: HITL framework install and tooling for AI-assisted IDEs (rules, skills, plan/handoff, context).", "license": "PolyForm-Noncommercial-1.0.0", "type": "module", diff --git a/packages/cli/src/lifecycle/l0.test.ts b/packages/cli/src/lifecycle/l0.test.ts index 1db4902..7fd9d1b 100644 --- a/packages/cli/src/lifecycle/l0.test.ts +++ b/packages/cli/src/lifecycle/l0.test.ts @@ -156,10 +156,13 @@ describe("canonical L0 inventory", () => { expect(sorted(portBPairs)).toEqual(sorted(canonicalPairs)); expect(canonicalTargets).toEqual(expect.arrayContaining(requiredRuleTargets)); expect(canonicalTargets).toEqual(expect.arrayContaining(requiredTemplateTargets)); - // Legacy `onboard` must never reappear. The registry.json L0 list is - // hand-curated and can lag behind synced command files on the public mirror, - // so assert the legacy command is absent rather than requiring the new one - // to be registered (agent-kit-onboard.md presence is covered by the file + // Legacy `onboard` must never reappear. This repo generates registry.json's + // L0 section from L0_ARTIFACTS on every rebuild (scripts/build-registry.mjs) + // and CI enforces that parity via a repo-gated step (private only). The + // public mirror's registry.json remains independently maintained and is not + // covered by that check, so this portable test still only asserts the + // legacy command is absent rather than requiring strict equality, to avoid + // breaking public CI (agent-kit-onboard.md presence is covered by the file // existence check below). expect( registry.artifacts.some((artifact) => artifact.path.endsWith("commands/onboard.md")), diff --git a/packages/cli/src/lifecycle/l0.ts b/packages/cli/src/lifecycle/l0.ts index 19f7f80..0892a73 100644 --- a/packages/cli/src/lifecycle/l0.ts +++ b/packages/cli/src/lifecycle/l0.ts @@ -101,6 +101,10 @@ export const L0_ARTIFACTS: readonly L0Artifact[] = [ source: ".cursor/commands/dashboard.md", target: ".cursor/commands/dashboard.md", }, + { + source: ".cursor/commands/dashboard-broadcast.md", + target: ".cursor/commands/dashboard-broadcast.md", + }, { source: ".cursor/commands/git-staging.md", target: ".cursor/commands/git-staging.md", @@ -129,6 +133,10 @@ export const L0_ARTIFACTS: readonly L0Artifact[] = [ source: ".cursor/commands/cursor-update-awareness.md", target: ".cursor/commands/cursor-update-awareness.md", }, + { + source: ".cursor/commands/update.md", + target: ".cursor/commands/update.md", + }, // Context (templates + example config; private config.json is not L0) { source: ".cursor/context/templates/plan-external-review-prompt.md", diff --git a/packages/cli/src/lifecycle/overlay-known-hashes.ts b/packages/cli/src/lifecycle/overlay-known-hashes.ts index 216059d..920982e 100644 --- a/packages/cli/src/lifecycle/overlay-known-hashes.ts +++ b/packages/cli/src/lifecycle/overlay-known-hashes.ts @@ -155,4 +155,5 @@ export const KNOWN_SHIPPED_OVERLAY_HASHES: ReadonlySet<string> = new Set([ "359a142aeadd769a9b89ac909ae5129940e7e6b0892b70256098c584d97631ce", "8eab94f7a78149db1bbbc0fd45f21dc2d874209d534bcff39077fd6e1d2c42fb", "9be406f92f7dca71f3af814b14fb26b011f9f12c37c789f7ddc3d647b1a7ab60", + "bb67ddcdd9ab58ed89287421f5e4b6ebfb3941e6a789287ddec9b9c25b56b1ed", ]); diff --git a/scripts/build-registry.mjs b/scripts/build-registry.mjs index 58cf586..ca632b8 100644 --- a/scripts/build-registry.mjs +++ b/scripts/build-registry.mjs @@ -8,6 +8,7 @@ import { readFile, readdir, writeFile } from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; +import { generateL0Artifacts } from "./emit-l0-artifacts.mjs"; const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); @@ -79,13 +80,14 @@ async function listPacks() { } /** - * Only L1 artifacts are derived from pack manifests. L0 and L2 are curated by - * hand (layers-spec), so they must survive a rebuild or they are silently lost. + * Only L1 artifacts are derived from pack manifests and L0 artifacts are + * generated from `L0_ARTIFACTS` (see emit-l0-artifacts.mjs). L2 is curated by + * hand (layers-spec), so it must survive a rebuild or it is silently lost. */ -async function readCuratedArtifacts(outPath) { +async function readCuratedL2Artifacts(outPath) { try { const existing = JSON.parse(await readFile(outPath, "utf8")); - return (existing.artifacts ?? []).filter((a) => a.layer !== "L1"); + return (existing.artifacts ?? []).filter((a) => a.layer === "L2"); } catch { return []; } @@ -95,13 +97,14 @@ const core = await listSkillTier("core"); const community = await listSkillTier("community"); const { packs, artifacts } = await listPacks(); const outPath = path.join(root, "registry", "registry.json"); -const curatedArtifacts = await readCuratedArtifacts(outPath); +const l0Artifacts = generateL0Artifacts(); +const l2Artifacts = await readCuratedL2Artifacts(outPath); const index = { schemaVersion: 2, skills: { core, community }, packs, - artifacts: [...curatedArtifacts, ...artifacts], + artifacts: [...l0Artifacts, ...l2Artifacts, ...artifacts], }; const allIds = [...core, ...community].map((s) => s.id); diff --git a/scripts/derive-l0-artifacts.ts b/scripts/derive-l0-artifacts.ts new file mode 100644 index 0000000..7d96c9a --- /dev/null +++ b/scripts/derive-l0-artifacts.ts @@ -0,0 +1,42 @@ +import { L0_ARTIFACTS } from "../packages/cli/src/lifecycle/l0"; + +function deriveKind(source: string): string { + if (source.includes(".cursor/commands/")) return "command"; + if (source.includes(".cursor/rules/")) return "rule"; + if (source.includes("registry/rules/")) return "rule"; + if (source === ".cursor/hooks.json") return "hook"; + if (source.includes(".cursor/hooks/")) return "hook"; + if (source.includes(".cursor/context/")) return "template"; + if (source.includes(".cursor/scripts/")) return "script"; + if (source.includes("autogit/")) return "documentation"; + return "other"; +} + +// Curated id overrides, keyed by exact `source`. These exist either because +// raw basename derivation collides/misfires (e.g. an internal dot in the +// basename, or a hand-picked disambiguation suffix) and must be preserved +// verbatim rather than re-derived on every generation. +const ID_OVERRIDES: Record<string, string> = { + ".cursor/hooks.json": "hooks.json", + ".cursor/context/config.example.json": "config-example", + ".cursor/scripts/plan-external-review.sh": "plan-external-review-launcher", + ".cursor/scripts/run-plan-all-consolidate.sh": "run-plan-all-consolidate-launcher", + "autogit/gitupdate.md": "git-workflow-spine", +}; + +function deriveId(source: string): string { + const override = ID_OVERRIDES[source]; + if (override) return override; + const basename = source.split("/").pop() || source; + const dotIdx = basename.lastIndexOf("."); + return dotIdx > 0 ? basename.slice(0, dotIdx) : basename; +} + +const artifacts = L0_ARTIFACTS.map((a) => ({ + kind: deriveKind(a.source), + id: deriveId(a.source), + path: a.source, + layer: "L0", +})); + +console.log(JSON.stringify(artifacts)); diff --git a/scripts/emit-l0-artifacts.mjs b/scripts/emit-l0-artifacts.mjs new file mode 100644 index 0000000..2ef76b4 --- /dev/null +++ b/scripts/emit-l0-artifacts.mjs @@ -0,0 +1,22 @@ +import { execFileSync } from "node:child_process"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); + +// `tsx` is a devDependency of the @dadado/agent-kit-cli workspace package +// only, not hoisted to root node_modules/.bin. Bare `npx tsx` / `pnpm exec +// tsx` fail at repo root with "command not found"; routing through the +// workspace package that owns tsx is the invocation that resolves. +export function generateL0Artifacts() { + const scriptPath = path.join(root, "scripts", "derive-l0-artifacts.ts"); + const out = execFileSync( + "pnpm", + ["--filter", "@dadado/agent-kit-cli", "exec", "tsx", scriptPath], + { + cwd: root, + encoding: "utf-8", + }, + ); + return JSON.parse(out); +}