From b0047cc525e4708ed0d05c6b724d915343b81f19 Mon Sep 17 00:00:00 2001 From: Sinabina Date: Mon, 20 Jul 2026 16:51:59 -0700 Subject: [PATCH] fix: add production runtime RC release channel Publish signed prerelease artifacts from v2.0.0-rc.* tags, bind bootstrap trust to the immutable RC tag, make missing-release errors actionable, and install the six public skills from the canonical subpath. --- .../create-runtime-release-manifest.mjs | 16 +++-- .github/workflows/release-artifacts.yml | 10 ++- AGENTS.md | 2 +- CLAUDE.md | 2 +- README.md | 8 +-- docs/gstack-2/ARCHITECTURE.md | 2 +- docs/gstack-2/HOST-COMPATIBILITY.md | 4 +- docs/gstack-2/JUDGMENT-PROVENANCE.json | 2 +- docs/gstack-2/RELEASE-INTEGRITY.md | 17 +++-- docs/gstack-2/UPGRADE-AND-ROLLBACK.md | 2 +- evals/parity/contracts/gstack-upgrade.json | 2 +- evals/parity/manifest.json | 2 +- runtime/runtime-bootstrap.mjs | 26 ++++++-- scripts/gstack2/generate-skill-tree.ts | 4 +- scripts/gstack2/render-legacy.ts | 4 +- scripts/gstack2/test-install-matrix.ts | 27 +++++--- skills/.compat/autoplan/SKILL.md | 2 +- skills/.compat/benchmark-models/SKILL.md | 2 +- skills/.compat/benchmark/SKILL.md | 2 +- skills/.compat/browse/SKILL.md | 2 +- skills/.compat/canary/SKILL.md | 2 +- skills/.compat/careful/SKILL.md | 2 +- skills/.compat/claude/SKILL.md | 2 +- skills/.compat/codex/SKILL.md | 2 +- skills/.compat/context-restore/SKILL.md | 2 +- skills/.compat/context-save/SKILL.md | 2 +- skills/.compat/cso/SKILL.md | 2 +- skills/.compat/design-consultation/SKILL.md | 2 +- skills/.compat/design-html/SKILL.md | 2 +- skills/.compat/design-review/SKILL.md | 2 +- skills/.compat/design-shotgun/SKILL.md | 2 +- skills/.compat/devex-review/SKILL.md | 2 +- skills/.compat/diagram/SKILL.md | 2 +- skills/.compat/document-generate/SKILL.md | 2 +- skills/.compat/document-release/SKILL.md | 2 +- skills/.compat/freeze/SKILL.md | 2 +- skills/.compat/gstack-upgrade/SKILL.md | 2 +- skills/.compat/gstack/SKILL.md | 2 +- skills/.compat/guard/SKILL.md | 2 +- skills/.compat/health/SKILL.md | 2 +- skills/.compat/investigate/SKILL.md | 2 +- skills/.compat/ios-clean/SKILL.md | 2 +- skills/.compat/ios-design-review/SKILL.md | 2 +- skills/.compat/ios-fix/SKILL.md | 2 +- skills/.compat/ios-qa/SKILL.md | 2 +- skills/.compat/ios-sync/SKILL.md | 2 +- skills/.compat/land-and-deploy/SKILL.md | 2 +- skills/.compat/landing-report/SKILL.md | 2 +- skills/.compat/learn/SKILL.md | 2 +- skills/.compat/make-pdf/SKILL.md | 2 +- skills/.compat/office-hours/SKILL.md | 2 +- skills/.compat/open-gstack-browser/SKILL.md | 2 +- skills/.compat/pair-agent/SKILL.md | 2 +- skills/.compat/plan-ceo-review/SKILL.md | 2 +- skills/.compat/plan-design-review/SKILL.md | 2 +- skills/.compat/plan-devex-review/SKILL.md | 2 +- skills/.compat/plan-eng-review/SKILL.md | 2 +- skills/.compat/plan-tune/SKILL.md | 2 +- skills/.compat/qa-only/SKILL.md | 2 +- skills/.compat/retro/SKILL.md | 2 +- skills/.compat/scrape/SKILL.md | 2 +- skills/.compat/setup-browser-cookies/SKILL.md | 2 +- skills/.compat/setup-deploy/SKILL.md | 2 +- skills/.compat/setup-gbrain/SKILL.md | 2 +- skills/.compat/skillify/SKILL.md | 2 +- skills/.compat/spec/SKILL.md | 2 +- skills/.compat/sync-gbrain/SKILL.md | 2 +- skills/.compat/unfreeze/SKILL.md | 2 +- skills/debug/references/WEB-CONTEXT.md | 2 +- .../references/support/runtime-bootstrap.mjs | 26 ++++++-- skills/design/references/WEB-CONTEXT.md | 2 +- .../references/support/runtime-bootstrap.mjs | 26 ++++++-- skills/plan/references/WEB-CONTEXT.md | 2 +- .../references/support/runtime-bootstrap.mjs | 26 ++++++-- skills/qa/references/WEB-CONTEXT.md | 2 +- .../references/support/runtime-bootstrap.mjs | 26 ++++++-- skills/review/references/WEB-CONTEXT.md | 2 +- .../references/support/runtime-bootstrap.mjs | 26 ++++++-- skills/ship/references/WEB-CONTEXT.md | 2 +- .../ship/references/legacy/gstack-upgrade.md | 4 +- .../references/support/runtime-bootstrap.mjs | 26 ++++++-- test/gstack2-installation.test.ts | 7 ++ test/gstack2-runtime-release-channel.test.ts | 66 +++++++++++++++++++ test/gstack2-runtime-setup-ux.test.ts | 28 +++++++- 84 files changed, 367 insertions(+), 140 deletions(-) create mode 100644 test/gstack2-runtime-release-channel.test.ts diff --git a/.github/scripts/create-runtime-release-manifest.mjs b/.github/scripts/create-runtime-release-manifest.mjs index 4fd487bac..198a48286 100644 --- a/.github/scripts/create-runtime-release-manifest.mjs +++ b/.github/scripts/create-runtime-release-manifest.mjs @@ -2,11 +2,19 @@ import fs from "node:fs/promises"; import path from "node:path"; -const [directory, repository = process.env.GITHUB_REPOSITORY, version = "2.0.0"] = process.argv.slice(2); +const [ + directory, + repository = process.env.GITHUB_REPOSITORY, + version = "2.0.0", + releaseTag = `v${version}`, +] = process.argv.slice(2); if (!directory || !repository) { - console.error("Usage: create-runtime-release-manifest.mjs [version]"); + console.error("Usage: create-runtime-release-manifest.mjs [version] [release-tag]"); process.exit(2); } +if (!/^v\d+\.\d+\.\d+(?:-rc\.\d+)?$/.test(releaseTag)) { + throw new Error(`Invalid runtime release tag: ${releaseTag}`); +} const targets = [ "darwin-arm64", @@ -35,8 +43,8 @@ const capabilityComponents = { ios: ["ios"], }; const commonComponents = ["core", "browser-code", "browser-headless", "browser-visible", "design", "diagram", "pdf"]; -const release = `https://github.com/${repository}/releases/download/v${version}`; -const certificateIdentity = `https://github.com/${repository}/.github/workflows/release-artifacts.yml@refs/tags/v${version}`; +const release = `https://github.com/${repository}/releases/download/${releaseTag}`; +const certificateIdentity = `https://github.com/${repository}/.github/workflows/release-artifacts.yml@refs/tags/${releaseTag}`; const targetRecords = {}; for (const target of targets) { diff --git a/.github/workflows/release-artifacts.yml b/.github/workflows/release-artifacts.yml index 4ef821ebd..bb4c7b14d 100644 --- a/.github/workflows/release-artifacts.yml +++ b/.github/workflows/release-artifacts.yml @@ -2,7 +2,9 @@ name: Release runtime artifacts on: push: - tags: [v2.0.0] + tags: + - v2.0.0 + - v2.0.0-rc.* workflow_dispatch: permissions: @@ -170,7 +172,7 @@ jobs: - uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.0 - name: Create strict six-target manifest - run: node .github/scripts/create-runtime-release-manifest.mjs release-output "$GITHUB_REPOSITORY" 2.0.0 + run: node .github/scripts/create-runtime-release-manifest.mjs release-output "$GITHUB_REPOSITORY" 2.0.0 "$GITHUB_REF_NAME" - name: Checksum and keyless-sign manifest run: | @@ -188,11 +190,13 @@ jobs: - name: Publish immutable release assets env: GH_TOKEN: ${{ github.token }} + PRERELEASE_FLAG: ${{ contains(github.ref_name, '-rc.') && '--prerelease' || '' }} run: | set -euo pipefail gh release create "$GITHUB_REF_NAME" \ --verify-tag \ - --title "GStack runtime 2.0.0" \ + $PRERELEASE_FLAG \ + --title "GStack runtime $GITHUB_REF_NAME" \ --notes "Signed optional runtime artifacts for the six portable GStack skills." \ release-output/* shell: bash diff --git a/AGENTS.md b/AGENTS.md index 09bfc9be5..9f53c3b90 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -60,7 +60,7 @@ they contain no copied judgment. Representative mappings: Canonical installation is standards-based: ```bash -npx skills add time-attack/gstack +npx skills add time-attack/gstack/skills ``` Delegate host detection, placement, project/global scope, selected-skill diff --git a/CLAUDE.md b/CLAUDE.md index 1e1a9c033..56eeb62ba 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,7 +13,7 @@ mutation boundaries, recommendations, or voice. Compatibility files under Specification, and Full chain. Do not expose its internal aliases as more top-level modes. -Canonical install is `npx skills add time-attack/gstack`. Standard installers +Canonical install is `npx skills add time-attack/gstack/skills`. Standard installers own host placement and scope. Do not add host-specific install logic to the 2.0 path. Pure judgment must work without the optional `bin/gstack` runtime. The host-neutral `./setup` installs only that managed runtime/capability bundle; diff --git a/README.md b/README.md index ed00ef336..07793085c 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Install with the standard Agent Skills installer; it owns host detection, scope, destination paths, updates, removal, and selected-skill installation: ```bash -npx skills add time-attack/gstack +npx skills add time-attack/gstack/skills ``` That installs the six judgment skills. Install a subset with the installer's @@ -87,7 +87,7 @@ extension, connector, MCP, settings entry, profile, alternate host, or browser runtime is configured without consent, and `./setup` is never a GStack 2 browser setup command. -Install through `npx skills add time-attack/gstack`; do not clone the repository +Install through `npx skills add time-attack/gstack/skills`; do not clone the repository under `.agents/skills/gstack`. A standards installation exposes exactly one canonical QA skill at `.agents/skills/qa/SKILL.md`. The cloned compatibility tree contains legacy GStack 1 entry points and is not the GStack 2 install @@ -96,7 +96,7 @@ surface. The npm package is deliberately not the skill installer and does not contain the six skill tree or compiled browser/design/PDF payloads. It is the small host-neutral runtime control/bootstrap surface used by release tooling. New -users should install skills with `npx skills add time-attack/gstack`; optional +users should install skills with `npx skills add time-attack/gstack/skills`; optional capabilities are downloaded by a skill after consent. Public web research is optional. Context.dev is the only new external service, @@ -161,7 +161,7 @@ archive. It is not the GStack 2 installation or first-run path. ## Legacy 1.x host-specific install (compatibility only) Do not use this section for a new GStack 2 install. Use -`npx skills add time-attack/gstack` above. These instructions remain while old +`npx skills add time-attack/gstack/skills` above. These instructions remain while old commands are documented as opt-in aliases. They describe a 1.x release/tag: the current branch's `./setup` is runtime-only and will not perform the host-specific actions shown below. diff --git a/docs/gstack-2/ARCHITECTURE.md b/docs/gstack-2/ARCHITECTURE.md index f6956c18e..b17286b9f 100644 --- a/docs/gstack-2/ARCHITECTURE.md +++ b/docs/gstack-2/ARCHITECTURE.md @@ -85,7 +85,7 @@ judgment. This prevents an alias from drifting away from the canonical source. The standard Agent Skills tree is the unit of distribution: ```bash -npx skills add time-attack/gstack +npx skills add time-attack/gstack/skills ``` The installer—not GStack—owns host detection, project/global scope, destination diff --git a/docs/gstack-2/HOST-COMPATIBILITY.md b/docs/gstack-2/HOST-COMPATIBILITY.md index 01135207f..b8b108091 100644 --- a/docs/gstack-2/HOST-COMPATIBILITY.md +++ b/docs/gstack-2/HOST-COMPATIBILITY.md @@ -18,7 +18,7 @@ bundle. ## Canonical installation ```bash -npx skills add time-attack/gstack +npx skills add time-attack/gstack/skills ``` The standards installer owns host detection, destination paths, project/global @@ -32,7 +32,7 @@ Examples supported by the installer interface: npx skills add time-attack/gstack/skills --skill qa # Installer-managed global scope -npx skills add time-attack/gstack -g +npx skills add time-attack/gstack/skills -g ``` Run `npx skills add --help` for the installed CLI version before scripting diff --git a/docs/gstack-2/JUDGMENT-PROVENANCE.json b/docs/gstack-2/JUDGMENT-PROVENANCE.json index 89043877e..39ba08820 100644 --- a/docs/gstack-2/JUDGMENT-PROVENANCE.json +++ b/docs/gstack-2/JUDGMENT-PROVENANCE.json @@ -4552,7 +4552,7 @@ "source_path": "gstack-upgrade/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "5402a1da3c857cbf50668085fce53172b72bba0d", - "normalized_render_sha256": "a913cf77f76c4d68c576a06190b498e3d8d3b60b85498a68f173b23a7f800828", + "normalized_render_sha256": "2c8cf9e505b7da27730bf646a33ee38e8092259f3335a524c71e57ba5b602541", "target": "skills/ship/references/legacy/gstack-upgrade.md", "disposition": "DUPLICATE_INFRASTRUCTURE", "overlays": [ diff --git a/docs/gstack-2/RELEASE-INTEGRITY.md b/docs/gstack-2/RELEASE-INTEGRITY.md index 4ae6659fc..e8d12a94d 100644 --- a/docs/gstack-2/RELEASE-INTEGRITY.md +++ b/docs/gstack-2/RELEASE-INTEGRITY.md @@ -5,10 +5,11 @@ GStack has two explicit version identities during the 2.0 migration: - `VERSION` and `package.json.version` are the repository/package release counter. They remain byte-equal and retain the existing four-slot format so the 1.x compatibility ship queue does not silently fail open. -- `package.json.gstack.runtimeVersion`, `runtime/index.js`, - `runtime/install.js`, and every standards-installed bootstrap declare the - managed-runtime protocol release `2.0.0`. The official artifact tag and - manifest use that value. +- `package.json.gstack.runtimeVersion`, `runtime/index.js`, and + `runtime/install.js` declare the managed-runtime protocol release `2.0.0`. + Each standards-installed bootstrap separately pins one immutable artifact + release tag. Candidate bootstraps use `v2.0.0-rc.N`; the manifest and bundle + remain runtime-compatible with `2.0.0`. Stable bootstraps use `v2.0.0`. They are intentionally different namespaces. CI fails if either identity drifts inside its own namespace. @@ -24,6 +25,14 @@ linux-arm64 linux-x64 (glibc) windows-arm64 windows-x64 ``` +Both `v2.0.0-rc.*` and `v2.0.0` tags use the same build, signing, manifest, +attestation, and smoke path. RC tags publish GitHub prereleases so the exact +fresh-machine production bootstrap can be exercised before the stable tag is +created. Runtime compatibility and release-channel identity are deliberately +separate: archive names and manifest `version` remain `2.0.0`, while URLs and +Sigstore certificate identity bind to the immutable RC or stable tag that +actually published them. + Each archive has one `gstack/` root and no symlinks. CI records an exact byte count and SHA-256, signs the archive keylessly with Cosign, emits a Sigstore bundle, and also creates a GitHub build-provenance attestation. The release diff --git a/docs/gstack-2/UPGRADE-AND-ROLLBACK.md b/docs/gstack-2/UPGRADE-AND-ROLLBACK.md index 3382e70c7..4dd91a3d3 100644 --- a/docs/gstack-2/UPGRADE-AND-ROLLBACK.md +++ b/docs/gstack-2/UPGRADE-AND-ROLLBACK.md @@ -10,7 +10,7 @@ Install the canonical Agent Skills source, then use that installer's tracked source and lock metadata for discovery, updates, and removal: ```bash -npx skills add time-attack/gstack +npx skills add time-attack/gstack/skills npx skills update # interactive scope npx skills update -p # project installs only npx skills update -g # global installs only diff --git a/evals/parity/contracts/gstack-upgrade.json b/evals/parity/contracts/gstack-upgrade.json index 77be5642c..f77ce1217 100644 --- a/evals/parity/contracts/gstack-upgrade.json +++ b/evals/parity/contracts/gstack-upgrade.json @@ -9,7 +9,7 @@ "source_path": "gstack-upgrade/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "5402a1da3c857cbf50668085fce53172b72bba0d", - "normalized_render_sha256": "a913cf77f76c4d68c576a06190b498e3d8d3b60b85498a68f173b23a7f800828", + "normalized_render_sha256": "2c8cf9e505b7da27730bf646a33ee38e8092259f3335a524c71e57ba5b602541", "target": "skills/ship/references/legacy/gstack-upgrade.md", "overlays": [ 679 diff --git a/evals/parity/manifest.json b/evals/parity/manifest.json index 89043877e..39ba08820 100644 --- a/evals/parity/manifest.json +++ b/evals/parity/manifest.json @@ -4552,7 +4552,7 @@ "source_path": "gstack-upgrade/SKILL.md.tmpl", "base_sha": "bb57306d98c97011b0919c6132705a15b1579781", "blob_sha": "5402a1da3c857cbf50668085fce53172b72bba0d", - "normalized_render_sha256": "a913cf77f76c4d68c576a06190b498e3d8d3b60b85498a68f173b23a7f800828", + "normalized_render_sha256": "2c8cf9e505b7da27730bf646a33ee38e8092259f3335a524c71e57ba5b602541", "target": "skills/ship/references/legacy/gstack-upgrade.md", "disposition": "DUPLICATE_INFRASTRUCTURE", "overlays": [ diff --git a/runtime/runtime-bootstrap.mjs b/runtime/runtime-bootstrap.mjs index 84370d441..9419f2a87 100644 --- a/runtime/runtime-bootstrap.mjs +++ b/runtime/runtime-bootstrap.mjs @@ -13,8 +13,12 @@ import { fileURLToPath } from "node:url"; export const BOOTSTRAP_SCHEMA_VERSION = 2; export const BOOTSTRAP_RUNTIME_VERSION = "2.0.0"; +// Keep the runtime compatibility version separate from the immutable release +// channel. Release candidates carry the 2.0.0 runtime contract while letting +// fresh-machine production journeys run before the stable v2.0.0 tag exists. +export const BOOTSTRAP_RELEASE_TAG = "v2.0.0-rc.1"; export const OFFICIAL_MANIFEST_URL = - `https://github.com/time-attack/gstack/releases/download/v${BOOTSTRAP_RUNTIME_VERSION}/gstack-runtime-manifest.json`; + `https://github.com/time-attack/gstack/releases/download/${BOOTSTRAP_RELEASE_TAG}/gstack-runtime-manifest.json`; const CAPABILITIES = new Set(["browser", "browser-visible", "design", "pdf", "diagram", "ios"]); const CAPABILITY_DEPENDENCIES = Object.freeze({ browser: Object.freeze([]), @@ -47,9 +51,9 @@ const ALLOWED_DOWNLOAD_HOSTS = new Set([ "objects.githubusercontent.com", "release-assets.githubusercontent.com", ]); -const OFFICIAL_RELEASE_PREFIX = `/time-attack/gstack/releases/download/v${BOOTSTRAP_RUNTIME_VERSION}/`; +const OFFICIAL_RELEASE_PREFIX = `/time-attack/gstack/releases/download/${BOOTSTRAP_RELEASE_TAG}/`; const OFFICIAL_CERTIFICATE_IDENTITY = - `https://github.com/time-attack/gstack/.github/workflows/release-artifacts.yml@refs/tags/v${BOOTSTRAP_RUNTIME_VERSION}`; + `https://github.com/time-attack/gstack/.github/workflows/release-artifacts.yml@refs/tags/${BOOTSTRAP_RELEASE_TAG}`; const GITHUB_OIDC_ISSUER = "https://token.actions.githubusercontent.com"; export async function main(argv = process.argv.slice(2), options = {}) { @@ -90,7 +94,9 @@ export async function main(argv = process.argv.slice(2), options = {}) { ); const manifestUrl = options.manifestUrl ?? OFFICIAL_MANIFEST_URL; assertOfficialUrl(manifestUrl, { manifest: true }); - const manifest = await fetchJson(fetch_, manifestUrl); + const manifest = await fetchJson(fetch_, manifestUrl, { + official: manifestUrl === OFFICIAL_MANIFEST_URL, + }); validateManifest(manifest, target); const home = path.resolve(parsed.home ?? process.env.GSTACK_HOME ?? path.join(os.homedir(), ".gstack")); const reusable = await inspectReusableRuntime(home, manifest.version).catch(() => null); @@ -300,10 +306,18 @@ function sha256File(file) { }); } -async function fetchJson(fetch_, url) { +async function fetchJson(fetch_, url, options = {}) { const response = await fetch_(url, { headers: { Accept: "application/json" }, redirect: "follow" }); assertFinalDownloadUrl(response.url || url); - if (!response.ok) throw bootstrapError(`Download failed with HTTP ${response.status}`, "BOOTSTRAP_DOWNLOAD_FAILED"); + if (!response.ok) { + if (options.official && response.status === 404) { + throw bootstrapError( + `Official runtime release ${BOOTSTRAP_RELEASE_TAG} is not published at ${url}. No files were downloaded or installed.`, + "BOOTSTRAP_RELEASE_UNAVAILABLE", + ); + } + throw bootstrapError(`Manifest download failed with HTTP ${response.status} from ${url}. No files were downloaded or installed.`, "BOOTSTRAP_DOWNLOAD_FAILED"); + } const value = await response.json(); if (!value || typeof value !== "object") throw bootstrapError("Manifest returned invalid JSON", "BOOTSTRAP_MANIFEST_INVALID"); return value; diff --git a/scripts/gstack2/generate-skill-tree.ts b/scripts/gstack2/generate-skill-tree.ts index 90159925e..4dfe43335 100644 --- a/scripts/gstack2/generate-skill-tree.ts +++ b/scripts/gstack2/generate-skill-tree.ts @@ -384,7 +384,7 @@ Print this replacement invocation, then dispatch to it exactly: \`${assignment.replacement}\` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved \`${assignment.source}\` module. If that dispatcher is not installed, tell the user to install it with \`npx skills add time-attack/gstack --skill ${assignment.tree}\`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved \`${assignment.source}\` module. If that dispatcher is not installed, tell the user to install it with \`npx skills add time-attack/gstack/skills --skill ${assignment.tree}\`. `); rows.push(`| \`/${assignment.source}\` | \`${assignment.replacement}\` | \`skills/${assignment.tree}/references/legacy/${assignment.source}.md\` |`); aliases.push({ @@ -483,7 +483,7 @@ function webContextContract(): string { '', 'Persist only the explicit choice with `gstack context select host`, `gstack context select local-browser`, or `gstack context select none`. For Context.dev, show `gstack context options`, then use `gstack context setup` and its hidden key prompt; consent and key storage belong to the runtime, never this judgment prompt. Do not infer Context choice or consent.', '', - 'Capability-dependent work follows `references/RUNTIME.md`. Pure judgment never requires the runtime. Skill placement remains owned by `npx skills add time-attack/gstack` and is never inferred from runtime state.', + 'Capability-dependent work follows `references/RUNTIME.md`. Pure judgment never requires the runtime. Skill placement remains owned by `npx skills add time-attack/gstack/skills` and is never inferred from runtime state.', '', ].join('\n'); } diff --git a/scripts/gstack2/render-legacy.ts b/scripts/gstack2/render-legacy.ts index e3631c963..f2fb4f882 100644 --- a/scripts/gstack2/render-legacy.ts +++ b/scripts/gstack2/render-legacy.ts @@ -188,7 +188,7 @@ export function renderLegacyBody(source: string): string { */ function portLegacyText(value: string, source: string): string { if (source === 'gstack-upgrade') { - return `# Legacy upgrade compatibility\n\nThe 1.x host-directory detector, vendored-copy synchronizer, and destructive Git replacement blocks were duplicated installation infrastructure. GStack 2 delegates skill placement and updates to the standard Agent Skills installer and manages the optional shared runtime atomically.\n\n- Update selected skills with \`npx skills add time-attack/gstack\` using the user's existing project/global choice. Never infer or enroll a host.\n- Upgrade a complete local runtime package with \`gstack upgrade --source --version \`.\n- Roll back the runtime with \`gstack upgrade --rollback\`.\n- Run \`gstack doctor\` after either operation.\n- Do not reset, delete, move, or rewrite a host skill directory. Do not infer Context.dev choice or consent.\n\nThis compatibility module contains no specialist judgment; release readiness and rollback judgment remain in the preserved ship modules.\n`; + return `# Legacy upgrade compatibility\n\nThe 1.x host-directory detector, vendored-copy synchronizer, and destructive Git replacement blocks were duplicated installation infrastructure. GStack 2 delegates skill placement and updates to the standard Agent Skills installer and manages the optional shared runtime atomically.\n\n- Update selected skills with \`npx skills add time-attack/gstack/skills\` using the user's existing project/global choice. Never infer or enroll a host.\n- Upgrade a complete local runtime package with \`gstack upgrade --source --version \`.\n- Roll back the runtime with \`gstack upgrade --rollback\`.\n- Run \`gstack doctor\` after either operation.\n- Do not reset, delete, move, or rewrite a host skill directory. Do not infer Context.dev choice or consent.\n\nThis compatibility module contains no specialist judgment; release readiness and rollback judgment remain in the preserved ship modules.\n`; } let body = value; @@ -280,7 +280,7 @@ function portLegacyText(value: string, source: string): string { .replaceAll('$GSTACK_ROOT/lib/redact-audit-log.ts', '$GSTACK_BIN/gstack-redact-audit-log') .replaceAll('bun $GSTACK_BIN/gstack-redact-audit-log', '$GSTACK_BIN/gstack-redact-audit-log') .replaceAll('Disk paths stay `$GSTACK_ROOT/[skill-name]/SKILL.md`.', 'Resolve retired names through `references/COMPATIBILITY.md`; skill placement is installer-owned.') - .replaceAll('Tell the user: "Done. Each developer now runs: `cd $GSTACK_ROOT && ./setup --team`"', 'Tell the user: "Done. Each developer installs the selected canonical skills with `npx skills add time-attack/gstack`; the optional runtime remains user-scoped."'); + .replaceAll('Tell the user: "Done. Each developer now runs: `cd $GSTACK_ROOT && ./setup --team`"', 'Tell the user: "Done. Each developer installs the selected canonical skills with `npx skills add time-attack/gstack/skills`; the optional runtime remains user-scoped."'); body = body .replace(/_VENDORED="no"\nif \[ -d "\.agents\/skills\/gstack" \][\s\S]*?echo "VENDORED_GSTACK: \$_VENDORED"/g, '_VENDORED="managed-by-standard-installer"\necho "VENDORED_GSTACK: $_VENDORED"') diff --git a/scripts/gstack2/test-install-matrix.ts b/scripts/gstack2/test-install-matrix.ts index fa2977f46..f0a07e1ac 100644 --- a/scripts/gstack2/test-install-matrix.ts +++ b/scripts/gstack2/test-install-matrix.ts @@ -438,6 +438,15 @@ function verifyInstalledCase( const installedSkills = listInstalledSkills(targetRoot); const sortedExpected = [...expectedSkills].sort(); record(checks, `${id}.command`, command.exitCode === 0, `exit=${command.exitCode}; signal=${command.signal ?? 'none'}`); + if (sourceSkillSegments.length === 1 && sourceSkillSegments[0] === 'skills') { + const reported = Number(stripTerminalControls(command.stdout).match(/Found\s+(\d+)\s+skills?/)?.[1]); + record( + checks, + `${id}.public-discovery-count`, + reported === PUBLIC_SKILLS.length, + `expected installer to report 6 public skills; found ${Number.isFinite(reported) ? reported : '(unparsed)'}`, + ); + } record( checks, `${id}.selected-skills`, @@ -595,10 +604,10 @@ export function runFullMatrix(options: FullMatrixOptions): InstallMatrixEvidence const supportsRemoval = /remove\s+\[skills\]/.test(helpCommand.stdout) && /Remove Options/.test(helpCommand.stdout); const discoveryCommand = execute( - // Exercise the repository root exactly as the documented - // `npx skills add time-attack/gstack` path will after checkout. The - // curated projection alone could hide stray root-level SKILL.md files. - skillsCliArgv(npxExecutable, ['add', repoRoot, '--list']), + // Exercise the documented public source directly. The repository root + // also contains opt-in 1.x compatibility aliases; skills@1.5.19 counts + // those internal entries before applying an explicit --skill filter. + skillsCliArgv(npxExecutable, ['add', path.join(repoRoot, 'skills'), '--list']), controlProject, controlEnv, ); @@ -626,7 +635,7 @@ export function runFullMatrix(options: FullMatrixOptions): InstallMatrixEvidence entry, scope, sourceKind, - sourceArgument: sourceKind === 'source-symlink' ? sourceLink : sourceRoot, + sourceArgument: path.join(sourceKind === 'source-symlink' ? sourceLink : sourceRoot, 'skills'), sourceRoot, expectedSkills: PUBLIC_SKILLS, explicitSelection: false, @@ -643,7 +652,7 @@ export function runFullMatrix(options: FullMatrixOptions): InstallMatrixEvidence entry: cursor, scope: 'project', sourceKind: 'repository-root', - sourceArgument: repoRoot, + sourceArgument: path.join(repoRoot, 'skills'), sourceRoot: repoRoot, expectedSkills: COLLISION_SKILLS, explicitSelection: true, @@ -659,7 +668,7 @@ export function runFullMatrix(options: FullMatrixOptions): InstallMatrixEvidence entry: codex, scope: 'global', sourceKind: 'path-with-spaces', - sourceArgument: sourceRoot, + sourceArgument: path.join(sourceRoot, 'skills'), sourceRoot, expectedSkills: COLLISION_SKILLS, explicitSelection: true, @@ -675,7 +684,7 @@ export function runFullMatrix(options: FullMatrixOptions): InstallMatrixEvidence entry: openclaw, scope: 'project', sourceKind: 'path-with-spaces', - sourceArgument: sourceRoot, + sourceArgument: path.join(sourceRoot, 'skills'), sourceRoot, expectedSkills: ['ship'], explicitSelection: true, @@ -747,7 +756,7 @@ export function runFullMatrix(options: FullMatrixOptions): InstallMatrixEvidence platform: process.platform, architecture: process.arch, repositoryRoot: repoRoot, - sourceProjection: 'repository-root-and-canonical-projection', + sourceProjection: 'canonical-skills-subpath-and-opt-in-compatibility-root', cli: { executable: npxExecutable, version, diff --git a/skills/.compat/autoplan/SKILL.md b/skills/.compat/autoplan/SKILL.md index c92a62d0d..54e834b0b 100644 --- a/skills/.compat/autoplan/SKILL.md +++ b/skills/.compat/autoplan/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$plan --mode Full chain --module autoplan` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `autoplan` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill plan`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `autoplan` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill plan`. diff --git a/skills/.compat/benchmark-models/SKILL.md b/skills/.compat/benchmark-models/SKILL.md index 277111610..f8d9bdc4d 100644 --- a/skills/.compat/benchmark-models/SKILL.md +++ b/skills/.compat/benchmark-models/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$qa --mode Report --module benchmark-models` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `benchmark-models` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill qa`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `benchmark-models` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill qa`. diff --git a/skills/.compat/benchmark/SKILL.md b/skills/.compat/benchmark/SKILL.md index df8b2db72..6ec810395 100644 --- a/skills/.compat/benchmark/SKILL.md +++ b/skills/.compat/benchmark/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$qa --mode Report --module benchmark` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `benchmark` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill qa`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `benchmark` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill qa`. diff --git a/skills/.compat/browse/SKILL.md b/skills/.compat/browse/SKILL.md index 5006f106c..d6a30c39b 100644 --- a/skills/.compat/browse/SKILL.md +++ b/skills/.compat/browse/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$qa --mode Report --module browse` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `browse` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill qa`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `browse` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill qa`. diff --git a/skills/.compat/canary/SKILL.md b/skills/.compat/canary/SKILL.md index 28e8f5795..be745fc93 100644 --- a/skills/.compat/canary/SKILL.md +++ b/skills/.compat/canary/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$qa --mode Report --module canary` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `canary` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill qa`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `canary` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill qa`. diff --git a/skills/.compat/careful/SKILL.md b/skills/.compat/careful/SKILL.md index 449c57a1b..daf42d3fe 100644 --- a/skills/.compat/careful/SKILL.md +++ b/skills/.compat/careful/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$debug --mode Diagnose-only --module careful` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `careful` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill debug`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `careful` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill debug`. diff --git a/skills/.compat/claude/SKILL.md b/skills/.compat/claude/SKILL.md index 9e3474b4a..25c25892a 100644 --- a/skills/.compat/claude/SKILL.md +++ b/skills/.compat/claude/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$review --mode Deep --module claude` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `claude` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill review`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `claude` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill review`. diff --git a/skills/.compat/codex/SKILL.md b/skills/.compat/codex/SKILL.md index 02f838ac3..9be72dfaf 100644 --- a/skills/.compat/codex/SKILL.md +++ b/skills/.compat/codex/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$review --mode Deep --module codex` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `codex` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill review`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `codex` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill review`. diff --git a/skills/.compat/context-restore/SKILL.md b/skills/.compat/context-restore/SKILL.md index 2d00f75a0..2d1ea108f 100644 --- a/skills/.compat/context-restore/SKILL.md +++ b/skills/.compat/context-restore/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$plan --mode Discovery --module context-restore` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `context-restore` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill plan`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `context-restore` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill plan`. diff --git a/skills/.compat/context-save/SKILL.md b/skills/.compat/context-save/SKILL.md index c068d6598..da1108a96 100644 --- a/skills/.compat/context-save/SKILL.md +++ b/skills/.compat/context-save/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$plan --mode Discovery --module context-save` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `context-save` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill plan`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `context-save` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill plan`. diff --git a/skills/.compat/cso/SKILL.md b/skills/.compat/cso/SKILL.md index a149e6199..11b6ff823 100644 --- a/skills/.compat/cso/SKILL.md +++ b/skills/.compat/cso/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$review --mode Security --module cso` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `cso` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill review`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `cso` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill review`. diff --git a/skills/.compat/design-consultation/SKILL.md b/skills/.compat/design-consultation/SKILL.md index 8b9bab86c..689edccb9 100644 --- a/skills/.compat/design-consultation/SKILL.md +++ b/skills/.compat/design-consultation/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$design --mode Generate --module design-consultation` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `design-consultation` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill design`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `design-consultation` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill design`. diff --git a/skills/.compat/design-html/SKILL.md b/skills/.compat/design-html/SKILL.md index a9ed55261..dec4610a0 100644 --- a/skills/.compat/design-html/SKILL.md +++ b/skills/.compat/design-html/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$design --mode Implement --module design-html` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `design-html` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill design`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `design-html` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill design`. diff --git a/skills/.compat/design-review/SKILL.md b/skills/.compat/design-review/SKILL.md index f1ac0ccbf..b23371b89 100644 --- a/skills/.compat/design-review/SKILL.md +++ b/skills/.compat/design-review/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$design --mode Implement --module design-review` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `design-review` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill design`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `design-review` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill design`. diff --git a/skills/.compat/design-shotgun/SKILL.md b/skills/.compat/design-shotgun/SKILL.md index 9fc8a3790..09fa43ec7 100644 --- a/skills/.compat/design-shotgun/SKILL.md +++ b/skills/.compat/design-shotgun/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$design --mode Explore --module design-shotgun` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `design-shotgun` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill design`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `design-shotgun` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill design`. diff --git a/skills/.compat/devex-review/SKILL.md b/skills/.compat/devex-review/SKILL.md index ba2d8caf4..7b812c06d 100644 --- a/skills/.compat/devex-review/SKILL.md +++ b/skills/.compat/devex-review/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$qa --mode Report --module devex-review` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `devex-review` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill qa`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `devex-review` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill qa`. diff --git a/skills/.compat/diagram/SKILL.md b/skills/.compat/diagram/SKILL.md index adf1fc52d..c3e138a91 100644 --- a/skills/.compat/diagram/SKILL.md +++ b/skills/.compat/diagram/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$design --mode Generate --module diagram` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `diagram` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill design`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `diagram` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill design`. diff --git a/skills/.compat/document-generate/SKILL.md b/skills/.compat/document-generate/SKILL.md index 0cf7bcf52..13e74ed12 100644 --- a/skills/.compat/document-generate/SKILL.md +++ b/skills/.compat/document-generate/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$ship --mode Prepare --module document-generate` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `document-generate` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill ship`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `document-generate` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill ship`. diff --git a/skills/.compat/document-release/SKILL.md b/skills/.compat/document-release/SKILL.md index a371a75cc..be9d54267 100644 --- a/skills/.compat/document-release/SKILL.md +++ b/skills/.compat/document-release/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$ship --mode Prepare --module document-release` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `document-release` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill ship`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `document-release` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill ship`. diff --git a/skills/.compat/freeze/SKILL.md b/skills/.compat/freeze/SKILL.md index 0e578ab2d..d4b531262 100644 --- a/skills/.compat/freeze/SKILL.md +++ b/skills/.compat/freeze/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$debug --mode Diagnose-only --module freeze` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `freeze` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill debug`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `freeze` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill debug`. diff --git a/skills/.compat/gstack-upgrade/SKILL.md b/skills/.compat/gstack-upgrade/SKILL.md index 5b64a9080..534cad396 100644 --- a/skills/.compat/gstack-upgrade/SKILL.md +++ b/skills/.compat/gstack-upgrade/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$ship --mode Prepare --module gstack-upgrade` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `gstack-upgrade` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill ship`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `gstack-upgrade` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill ship`. diff --git a/skills/.compat/gstack/SKILL.md b/skills/.compat/gstack/SKILL.md index f4c2bce35..449b4f746 100644 --- a/skills/.compat/gstack/SKILL.md +++ b/skills/.compat/gstack/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$plan --mode Discovery --module gstack` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `gstack` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill plan`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `gstack` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill plan`. diff --git a/skills/.compat/guard/SKILL.md b/skills/.compat/guard/SKILL.md index aebadfe6d..2e3a4e29a 100644 --- a/skills/.compat/guard/SKILL.md +++ b/skills/.compat/guard/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$debug --mode Diagnose-only --module guard` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `guard` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill debug`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `guard` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill debug`. diff --git a/skills/.compat/health/SKILL.md b/skills/.compat/health/SKILL.md index dc45be44b..069829932 100644 --- a/skills/.compat/health/SKILL.md +++ b/skills/.compat/health/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$review --mode Deep --module health` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `health` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill review`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `health` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill review`. diff --git a/skills/.compat/investigate/SKILL.md b/skills/.compat/investigate/SKILL.md index 0615b2ec8..570c820c9 100644 --- a/skills/.compat/investigate/SKILL.md +++ b/skills/.compat/investigate/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$debug --mode Diagnose-only --module investigate` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `investigate` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill debug`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `investigate` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill debug`. diff --git a/skills/.compat/ios-clean/SKILL.md b/skills/.compat/ios-clean/SKILL.md index 12f55f270..f55eb9125 100644 --- a/skills/.compat/ios-clean/SKILL.md +++ b/skills/.compat/ios-clean/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$ship --mode Prepare --module ios-clean` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `ios-clean` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill ship`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `ios-clean` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill ship`. diff --git a/skills/.compat/ios-design-review/SKILL.md b/skills/.compat/ios-design-review/SKILL.md index 86422bb3f..0a70dc4b5 100644 --- a/skills/.compat/ios-design-review/SKILL.md +++ b/skills/.compat/ios-design-review/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$design --mode Critique --module ios-design-review` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `ios-design-review` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill design`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `ios-design-review` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill design`. diff --git a/skills/.compat/ios-fix/SKILL.md b/skills/.compat/ios-fix/SKILL.md index af42c4cc3..a8548ba97 100644 --- a/skills/.compat/ios-fix/SKILL.md +++ b/skills/.compat/ios-fix/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$debug --mode Fix --module ios-fix` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `ios-fix` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill debug`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `ios-fix` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill debug`. diff --git a/skills/.compat/ios-qa/SKILL.md b/skills/.compat/ios-qa/SKILL.md index b9c8584c0..e5c83c655 100644 --- a/skills/.compat/ios-qa/SKILL.md +++ b/skills/.compat/ios-qa/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$qa --mode Report --module ios-qa` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `ios-qa` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill qa`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `ios-qa` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill qa`. diff --git a/skills/.compat/ios-sync/SKILL.md b/skills/.compat/ios-sync/SKILL.md index 59e19f9ec..316fd1090 100644 --- a/skills/.compat/ios-sync/SKILL.md +++ b/skills/.compat/ios-sync/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$ship --mode Prepare --module ios-sync` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `ios-sync` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill ship`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `ios-sync` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill ship`. diff --git a/skills/.compat/land-and-deploy/SKILL.md b/skills/.compat/land-and-deploy/SKILL.md index ebd525b26..cb0317fd3 100644 --- a/skills/.compat/land-and-deploy/SKILL.md +++ b/skills/.compat/land-and-deploy/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$ship --mode Land --module land-and-deploy` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `land-and-deploy` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill ship`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `land-and-deploy` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill ship`. diff --git a/skills/.compat/landing-report/SKILL.md b/skills/.compat/landing-report/SKILL.md index 42dfd68bb..378b8a38a 100644 --- a/skills/.compat/landing-report/SKILL.md +++ b/skills/.compat/landing-report/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$ship --mode Prepare --module landing-report` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `landing-report` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill ship`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `landing-report` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill ship`. diff --git a/skills/.compat/learn/SKILL.md b/skills/.compat/learn/SKILL.md index 564b2d1d9..a3a68056a 100644 --- a/skills/.compat/learn/SKILL.md +++ b/skills/.compat/learn/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$plan --mode Discovery --module learn` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `learn` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill plan`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `learn` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill plan`. diff --git a/skills/.compat/make-pdf/SKILL.md b/skills/.compat/make-pdf/SKILL.md index b6f77097f..5e40076f7 100644 --- a/skills/.compat/make-pdf/SKILL.md +++ b/skills/.compat/make-pdf/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$design --mode Generate --module make-pdf` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `make-pdf` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill design`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `make-pdf` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill design`. diff --git a/skills/.compat/office-hours/SKILL.md b/skills/.compat/office-hours/SKILL.md index 44272ff0c..8619388ae 100644 --- a/skills/.compat/office-hours/SKILL.md +++ b/skills/.compat/office-hours/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$plan --mode Discovery --module office-hours` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `office-hours` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill plan`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `office-hours` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill plan`. diff --git a/skills/.compat/open-gstack-browser/SKILL.md b/skills/.compat/open-gstack-browser/SKILL.md index 0d8cbfca4..01717a212 100644 --- a/skills/.compat/open-gstack-browser/SKILL.md +++ b/skills/.compat/open-gstack-browser/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$qa --mode Report --module open-gstack-browser` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `open-gstack-browser` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill qa`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `open-gstack-browser` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill qa`. diff --git a/skills/.compat/pair-agent/SKILL.md b/skills/.compat/pair-agent/SKILL.md index 862827242..4ef10e5ba 100644 --- a/skills/.compat/pair-agent/SKILL.md +++ b/skills/.compat/pair-agent/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$qa --mode Report --module pair-agent` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `pair-agent` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill qa`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `pair-agent` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill qa`. diff --git a/skills/.compat/plan-ceo-review/SKILL.md b/skills/.compat/plan-ceo-review/SKILL.md index d56d824ff..a2dfbbca7 100644 --- a/skills/.compat/plan-ceo-review/SKILL.md +++ b/skills/.compat/plan-ceo-review/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$plan --mode Product --module plan-ceo-review` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `plan-ceo-review` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill plan`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `plan-ceo-review` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill plan`. diff --git a/skills/.compat/plan-design-review/SKILL.md b/skills/.compat/plan-design-review/SKILL.md index bb1b3e3ea..0c0d143a1 100644 --- a/skills/.compat/plan-design-review/SKILL.md +++ b/skills/.compat/plan-design-review/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$design --mode Critique --module plan-design-review` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `plan-design-review` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill design`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `plan-design-review` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill design`. diff --git a/skills/.compat/plan-devex-review/SKILL.md b/skills/.compat/plan-devex-review/SKILL.md index 777ac1999..c4c0e9a51 100644 --- a/skills/.compat/plan-devex-review/SKILL.md +++ b/skills/.compat/plan-devex-review/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$plan --mode DX --module plan-devex-review` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `plan-devex-review` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill plan`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `plan-devex-review` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill plan`. diff --git a/skills/.compat/plan-eng-review/SKILL.md b/skills/.compat/plan-eng-review/SKILL.md index 4e9c800ea..bc7e459b3 100644 --- a/skills/.compat/plan-eng-review/SKILL.md +++ b/skills/.compat/plan-eng-review/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$plan --mode Engineering --module plan-eng-review` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `plan-eng-review` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill plan`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `plan-eng-review` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill plan`. diff --git a/skills/.compat/plan-tune/SKILL.md b/skills/.compat/plan-tune/SKILL.md index dd2bc44db..ae932f0fd 100644 --- a/skills/.compat/plan-tune/SKILL.md +++ b/skills/.compat/plan-tune/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$plan --mode Discovery --module plan-tune` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `plan-tune` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill plan`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `plan-tune` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill plan`. diff --git a/skills/.compat/qa-only/SKILL.md b/skills/.compat/qa-only/SKILL.md index 7cfa6bff3..e53b2b25d 100644 --- a/skills/.compat/qa-only/SKILL.md +++ b/skills/.compat/qa-only/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$qa --mode Report --module qa-only` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `qa-only` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill qa`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `qa-only` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill qa`. diff --git a/skills/.compat/retro/SKILL.md b/skills/.compat/retro/SKILL.md index eb1adf538..fb50c4015 100644 --- a/skills/.compat/retro/SKILL.md +++ b/skills/.compat/retro/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$plan --mode Discovery --module retro` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `retro` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill plan`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `retro` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill plan`. diff --git a/skills/.compat/scrape/SKILL.md b/skills/.compat/scrape/SKILL.md index 54c69d5f2..b244708b5 100644 --- a/skills/.compat/scrape/SKILL.md +++ b/skills/.compat/scrape/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$qa --mode Report --module scrape` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `scrape` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill qa`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `scrape` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill qa`. diff --git a/skills/.compat/setup-browser-cookies/SKILL.md b/skills/.compat/setup-browser-cookies/SKILL.md index 861dc9b5c..90b724e48 100644 --- a/skills/.compat/setup-browser-cookies/SKILL.md +++ b/skills/.compat/setup-browser-cookies/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$qa --mode Report --module setup-browser-cookies` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `setup-browser-cookies` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill qa`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `setup-browser-cookies` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill qa`. diff --git a/skills/.compat/setup-deploy/SKILL.md b/skills/.compat/setup-deploy/SKILL.md index 1f48a55fb..587eab4a7 100644 --- a/skills/.compat/setup-deploy/SKILL.md +++ b/skills/.compat/setup-deploy/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$ship --mode Deploy --module setup-deploy` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `setup-deploy` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill ship`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `setup-deploy` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill ship`. diff --git a/skills/.compat/setup-gbrain/SKILL.md b/skills/.compat/setup-gbrain/SKILL.md index f120381ef..350277576 100644 --- a/skills/.compat/setup-gbrain/SKILL.md +++ b/skills/.compat/setup-gbrain/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$plan --mode Discovery --module setup-gbrain` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `setup-gbrain` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill plan`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `setup-gbrain` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill plan`. diff --git a/skills/.compat/skillify/SKILL.md b/skills/.compat/skillify/SKILL.md index 7400c78ef..58ea41f85 100644 --- a/skills/.compat/skillify/SKILL.md +++ b/skills/.compat/skillify/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$qa --mode Report --module skillify` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `skillify` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill qa`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `skillify` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill qa`. diff --git a/skills/.compat/spec/SKILL.md b/skills/.compat/spec/SKILL.md index ecfdd7e63..1ab212d15 100644 --- a/skills/.compat/spec/SKILL.md +++ b/skills/.compat/spec/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$plan --mode Specification --module spec` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `spec` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill plan`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `spec` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill plan`. diff --git a/skills/.compat/sync-gbrain/SKILL.md b/skills/.compat/sync-gbrain/SKILL.md index 71aac6ec5..d360fe6e3 100644 --- a/skills/.compat/sync-gbrain/SKILL.md +++ b/skills/.compat/sync-gbrain/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$plan --mode Discovery --module sync-gbrain` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `sync-gbrain` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill plan`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `sync-gbrain` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill plan`. diff --git a/skills/.compat/unfreeze/SKILL.md b/skills/.compat/unfreeze/SKILL.md index 81b0b94c0..b8902c999 100644 --- a/skills/.compat/unfreeze/SKILL.md +++ b/skills/.compat/unfreeze/SKILL.md @@ -12,4 +12,4 @@ Print this replacement invocation, then dispatch to it exactly: `$debug --mode Diagnose-only --module unfreeze` -Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `unfreeze` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack --skill debug`. +Do not reproduce or summarize the retired specialist here. The canonical dispatcher must load its preserved `unfreeze` module. If that dispatcher is not installed, tell the user to install it with `npx skills add time-attack/gstack/skills --skill debug`. diff --git a/skills/debug/references/WEB-CONTEXT.md b/skills/debug/references/WEB-CONTEXT.md index 81bbf5d08..2ba9cab1e 100644 --- a/skills/debug/references/WEB-CONTEXT.md +++ b/skills/debug/references/WEB-CONTEXT.md @@ -7,4 +7,4 @@ When no public-web choice is stored, present: A) Context.dev free setup (recomme Persist only the explicit choice with `gstack context select host`, `gstack context select local-browser`, or `gstack context select none`. For Context.dev, show `gstack context options`, then use `gstack context setup` and its hidden key prompt; consent and key storage belong to the runtime, never this judgment prompt. Do not infer Context choice or consent. -Capability-dependent work follows `references/RUNTIME.md`. Pure judgment never requires the runtime. Skill placement remains owned by `npx skills add time-attack/gstack` and is never inferred from runtime state. +Capability-dependent work follows `references/RUNTIME.md`. Pure judgment never requires the runtime. Skill placement remains owned by `npx skills add time-attack/gstack/skills` and is never inferred from runtime state. diff --git a/skills/debug/references/support/runtime-bootstrap.mjs b/skills/debug/references/support/runtime-bootstrap.mjs index 84370d441..9419f2a87 100644 --- a/skills/debug/references/support/runtime-bootstrap.mjs +++ b/skills/debug/references/support/runtime-bootstrap.mjs @@ -13,8 +13,12 @@ import { fileURLToPath } from "node:url"; export const BOOTSTRAP_SCHEMA_VERSION = 2; export const BOOTSTRAP_RUNTIME_VERSION = "2.0.0"; +// Keep the runtime compatibility version separate from the immutable release +// channel. Release candidates carry the 2.0.0 runtime contract while letting +// fresh-machine production journeys run before the stable v2.0.0 tag exists. +export const BOOTSTRAP_RELEASE_TAG = "v2.0.0-rc.1"; export const OFFICIAL_MANIFEST_URL = - `https://github.com/time-attack/gstack/releases/download/v${BOOTSTRAP_RUNTIME_VERSION}/gstack-runtime-manifest.json`; + `https://github.com/time-attack/gstack/releases/download/${BOOTSTRAP_RELEASE_TAG}/gstack-runtime-manifest.json`; const CAPABILITIES = new Set(["browser", "browser-visible", "design", "pdf", "diagram", "ios"]); const CAPABILITY_DEPENDENCIES = Object.freeze({ browser: Object.freeze([]), @@ -47,9 +51,9 @@ const ALLOWED_DOWNLOAD_HOSTS = new Set([ "objects.githubusercontent.com", "release-assets.githubusercontent.com", ]); -const OFFICIAL_RELEASE_PREFIX = `/time-attack/gstack/releases/download/v${BOOTSTRAP_RUNTIME_VERSION}/`; +const OFFICIAL_RELEASE_PREFIX = `/time-attack/gstack/releases/download/${BOOTSTRAP_RELEASE_TAG}/`; const OFFICIAL_CERTIFICATE_IDENTITY = - `https://github.com/time-attack/gstack/.github/workflows/release-artifacts.yml@refs/tags/v${BOOTSTRAP_RUNTIME_VERSION}`; + `https://github.com/time-attack/gstack/.github/workflows/release-artifacts.yml@refs/tags/${BOOTSTRAP_RELEASE_TAG}`; const GITHUB_OIDC_ISSUER = "https://token.actions.githubusercontent.com"; export async function main(argv = process.argv.slice(2), options = {}) { @@ -90,7 +94,9 @@ export async function main(argv = process.argv.slice(2), options = {}) { ); const manifestUrl = options.manifestUrl ?? OFFICIAL_MANIFEST_URL; assertOfficialUrl(manifestUrl, { manifest: true }); - const manifest = await fetchJson(fetch_, manifestUrl); + const manifest = await fetchJson(fetch_, manifestUrl, { + official: manifestUrl === OFFICIAL_MANIFEST_URL, + }); validateManifest(manifest, target); const home = path.resolve(parsed.home ?? process.env.GSTACK_HOME ?? path.join(os.homedir(), ".gstack")); const reusable = await inspectReusableRuntime(home, manifest.version).catch(() => null); @@ -300,10 +306,18 @@ function sha256File(file) { }); } -async function fetchJson(fetch_, url) { +async function fetchJson(fetch_, url, options = {}) { const response = await fetch_(url, { headers: { Accept: "application/json" }, redirect: "follow" }); assertFinalDownloadUrl(response.url || url); - if (!response.ok) throw bootstrapError(`Download failed with HTTP ${response.status}`, "BOOTSTRAP_DOWNLOAD_FAILED"); + if (!response.ok) { + if (options.official && response.status === 404) { + throw bootstrapError( + `Official runtime release ${BOOTSTRAP_RELEASE_TAG} is not published at ${url}. No files were downloaded or installed.`, + "BOOTSTRAP_RELEASE_UNAVAILABLE", + ); + } + throw bootstrapError(`Manifest download failed with HTTP ${response.status} from ${url}. No files were downloaded or installed.`, "BOOTSTRAP_DOWNLOAD_FAILED"); + } const value = await response.json(); if (!value || typeof value !== "object") throw bootstrapError("Manifest returned invalid JSON", "BOOTSTRAP_MANIFEST_INVALID"); return value; diff --git a/skills/design/references/WEB-CONTEXT.md b/skills/design/references/WEB-CONTEXT.md index 81bbf5d08..2ba9cab1e 100644 --- a/skills/design/references/WEB-CONTEXT.md +++ b/skills/design/references/WEB-CONTEXT.md @@ -7,4 +7,4 @@ When no public-web choice is stored, present: A) Context.dev free setup (recomme Persist only the explicit choice with `gstack context select host`, `gstack context select local-browser`, or `gstack context select none`. For Context.dev, show `gstack context options`, then use `gstack context setup` and its hidden key prompt; consent and key storage belong to the runtime, never this judgment prompt. Do not infer Context choice or consent. -Capability-dependent work follows `references/RUNTIME.md`. Pure judgment never requires the runtime. Skill placement remains owned by `npx skills add time-attack/gstack` and is never inferred from runtime state. +Capability-dependent work follows `references/RUNTIME.md`. Pure judgment never requires the runtime. Skill placement remains owned by `npx skills add time-attack/gstack/skills` and is never inferred from runtime state. diff --git a/skills/design/references/support/runtime-bootstrap.mjs b/skills/design/references/support/runtime-bootstrap.mjs index 84370d441..9419f2a87 100644 --- a/skills/design/references/support/runtime-bootstrap.mjs +++ b/skills/design/references/support/runtime-bootstrap.mjs @@ -13,8 +13,12 @@ import { fileURLToPath } from "node:url"; export const BOOTSTRAP_SCHEMA_VERSION = 2; export const BOOTSTRAP_RUNTIME_VERSION = "2.0.0"; +// Keep the runtime compatibility version separate from the immutable release +// channel. Release candidates carry the 2.0.0 runtime contract while letting +// fresh-machine production journeys run before the stable v2.0.0 tag exists. +export const BOOTSTRAP_RELEASE_TAG = "v2.0.0-rc.1"; export const OFFICIAL_MANIFEST_URL = - `https://github.com/time-attack/gstack/releases/download/v${BOOTSTRAP_RUNTIME_VERSION}/gstack-runtime-manifest.json`; + `https://github.com/time-attack/gstack/releases/download/${BOOTSTRAP_RELEASE_TAG}/gstack-runtime-manifest.json`; const CAPABILITIES = new Set(["browser", "browser-visible", "design", "pdf", "diagram", "ios"]); const CAPABILITY_DEPENDENCIES = Object.freeze({ browser: Object.freeze([]), @@ -47,9 +51,9 @@ const ALLOWED_DOWNLOAD_HOSTS = new Set([ "objects.githubusercontent.com", "release-assets.githubusercontent.com", ]); -const OFFICIAL_RELEASE_PREFIX = `/time-attack/gstack/releases/download/v${BOOTSTRAP_RUNTIME_VERSION}/`; +const OFFICIAL_RELEASE_PREFIX = `/time-attack/gstack/releases/download/${BOOTSTRAP_RELEASE_TAG}/`; const OFFICIAL_CERTIFICATE_IDENTITY = - `https://github.com/time-attack/gstack/.github/workflows/release-artifacts.yml@refs/tags/v${BOOTSTRAP_RUNTIME_VERSION}`; + `https://github.com/time-attack/gstack/.github/workflows/release-artifacts.yml@refs/tags/${BOOTSTRAP_RELEASE_TAG}`; const GITHUB_OIDC_ISSUER = "https://token.actions.githubusercontent.com"; export async function main(argv = process.argv.slice(2), options = {}) { @@ -90,7 +94,9 @@ export async function main(argv = process.argv.slice(2), options = {}) { ); const manifestUrl = options.manifestUrl ?? OFFICIAL_MANIFEST_URL; assertOfficialUrl(manifestUrl, { manifest: true }); - const manifest = await fetchJson(fetch_, manifestUrl); + const manifest = await fetchJson(fetch_, manifestUrl, { + official: manifestUrl === OFFICIAL_MANIFEST_URL, + }); validateManifest(manifest, target); const home = path.resolve(parsed.home ?? process.env.GSTACK_HOME ?? path.join(os.homedir(), ".gstack")); const reusable = await inspectReusableRuntime(home, manifest.version).catch(() => null); @@ -300,10 +306,18 @@ function sha256File(file) { }); } -async function fetchJson(fetch_, url) { +async function fetchJson(fetch_, url, options = {}) { const response = await fetch_(url, { headers: { Accept: "application/json" }, redirect: "follow" }); assertFinalDownloadUrl(response.url || url); - if (!response.ok) throw bootstrapError(`Download failed with HTTP ${response.status}`, "BOOTSTRAP_DOWNLOAD_FAILED"); + if (!response.ok) { + if (options.official && response.status === 404) { + throw bootstrapError( + `Official runtime release ${BOOTSTRAP_RELEASE_TAG} is not published at ${url}. No files were downloaded or installed.`, + "BOOTSTRAP_RELEASE_UNAVAILABLE", + ); + } + throw bootstrapError(`Manifest download failed with HTTP ${response.status} from ${url}. No files were downloaded or installed.`, "BOOTSTRAP_DOWNLOAD_FAILED"); + } const value = await response.json(); if (!value || typeof value !== "object") throw bootstrapError("Manifest returned invalid JSON", "BOOTSTRAP_MANIFEST_INVALID"); return value; diff --git a/skills/plan/references/WEB-CONTEXT.md b/skills/plan/references/WEB-CONTEXT.md index 81bbf5d08..2ba9cab1e 100644 --- a/skills/plan/references/WEB-CONTEXT.md +++ b/skills/plan/references/WEB-CONTEXT.md @@ -7,4 +7,4 @@ When no public-web choice is stored, present: A) Context.dev free setup (recomme Persist only the explicit choice with `gstack context select host`, `gstack context select local-browser`, or `gstack context select none`. For Context.dev, show `gstack context options`, then use `gstack context setup` and its hidden key prompt; consent and key storage belong to the runtime, never this judgment prompt. Do not infer Context choice or consent. -Capability-dependent work follows `references/RUNTIME.md`. Pure judgment never requires the runtime. Skill placement remains owned by `npx skills add time-attack/gstack` and is never inferred from runtime state. +Capability-dependent work follows `references/RUNTIME.md`. Pure judgment never requires the runtime. Skill placement remains owned by `npx skills add time-attack/gstack/skills` and is never inferred from runtime state. diff --git a/skills/plan/references/support/runtime-bootstrap.mjs b/skills/plan/references/support/runtime-bootstrap.mjs index 84370d441..9419f2a87 100644 --- a/skills/plan/references/support/runtime-bootstrap.mjs +++ b/skills/plan/references/support/runtime-bootstrap.mjs @@ -13,8 +13,12 @@ import { fileURLToPath } from "node:url"; export const BOOTSTRAP_SCHEMA_VERSION = 2; export const BOOTSTRAP_RUNTIME_VERSION = "2.0.0"; +// Keep the runtime compatibility version separate from the immutable release +// channel. Release candidates carry the 2.0.0 runtime contract while letting +// fresh-machine production journeys run before the stable v2.0.0 tag exists. +export const BOOTSTRAP_RELEASE_TAG = "v2.0.0-rc.1"; export const OFFICIAL_MANIFEST_URL = - `https://github.com/time-attack/gstack/releases/download/v${BOOTSTRAP_RUNTIME_VERSION}/gstack-runtime-manifest.json`; + `https://github.com/time-attack/gstack/releases/download/${BOOTSTRAP_RELEASE_TAG}/gstack-runtime-manifest.json`; const CAPABILITIES = new Set(["browser", "browser-visible", "design", "pdf", "diagram", "ios"]); const CAPABILITY_DEPENDENCIES = Object.freeze({ browser: Object.freeze([]), @@ -47,9 +51,9 @@ const ALLOWED_DOWNLOAD_HOSTS = new Set([ "objects.githubusercontent.com", "release-assets.githubusercontent.com", ]); -const OFFICIAL_RELEASE_PREFIX = `/time-attack/gstack/releases/download/v${BOOTSTRAP_RUNTIME_VERSION}/`; +const OFFICIAL_RELEASE_PREFIX = `/time-attack/gstack/releases/download/${BOOTSTRAP_RELEASE_TAG}/`; const OFFICIAL_CERTIFICATE_IDENTITY = - `https://github.com/time-attack/gstack/.github/workflows/release-artifacts.yml@refs/tags/v${BOOTSTRAP_RUNTIME_VERSION}`; + `https://github.com/time-attack/gstack/.github/workflows/release-artifacts.yml@refs/tags/${BOOTSTRAP_RELEASE_TAG}`; const GITHUB_OIDC_ISSUER = "https://token.actions.githubusercontent.com"; export async function main(argv = process.argv.slice(2), options = {}) { @@ -90,7 +94,9 @@ export async function main(argv = process.argv.slice(2), options = {}) { ); const manifestUrl = options.manifestUrl ?? OFFICIAL_MANIFEST_URL; assertOfficialUrl(manifestUrl, { manifest: true }); - const manifest = await fetchJson(fetch_, manifestUrl); + const manifest = await fetchJson(fetch_, manifestUrl, { + official: manifestUrl === OFFICIAL_MANIFEST_URL, + }); validateManifest(manifest, target); const home = path.resolve(parsed.home ?? process.env.GSTACK_HOME ?? path.join(os.homedir(), ".gstack")); const reusable = await inspectReusableRuntime(home, manifest.version).catch(() => null); @@ -300,10 +306,18 @@ function sha256File(file) { }); } -async function fetchJson(fetch_, url) { +async function fetchJson(fetch_, url, options = {}) { const response = await fetch_(url, { headers: { Accept: "application/json" }, redirect: "follow" }); assertFinalDownloadUrl(response.url || url); - if (!response.ok) throw bootstrapError(`Download failed with HTTP ${response.status}`, "BOOTSTRAP_DOWNLOAD_FAILED"); + if (!response.ok) { + if (options.official && response.status === 404) { + throw bootstrapError( + `Official runtime release ${BOOTSTRAP_RELEASE_TAG} is not published at ${url}. No files were downloaded or installed.`, + "BOOTSTRAP_RELEASE_UNAVAILABLE", + ); + } + throw bootstrapError(`Manifest download failed with HTTP ${response.status} from ${url}. No files were downloaded or installed.`, "BOOTSTRAP_DOWNLOAD_FAILED"); + } const value = await response.json(); if (!value || typeof value !== "object") throw bootstrapError("Manifest returned invalid JSON", "BOOTSTRAP_MANIFEST_INVALID"); return value; diff --git a/skills/qa/references/WEB-CONTEXT.md b/skills/qa/references/WEB-CONTEXT.md index 81bbf5d08..2ba9cab1e 100644 --- a/skills/qa/references/WEB-CONTEXT.md +++ b/skills/qa/references/WEB-CONTEXT.md @@ -7,4 +7,4 @@ When no public-web choice is stored, present: A) Context.dev free setup (recomme Persist only the explicit choice with `gstack context select host`, `gstack context select local-browser`, or `gstack context select none`. For Context.dev, show `gstack context options`, then use `gstack context setup` and its hidden key prompt; consent and key storage belong to the runtime, never this judgment prompt. Do not infer Context choice or consent. -Capability-dependent work follows `references/RUNTIME.md`. Pure judgment never requires the runtime. Skill placement remains owned by `npx skills add time-attack/gstack` and is never inferred from runtime state. +Capability-dependent work follows `references/RUNTIME.md`. Pure judgment never requires the runtime. Skill placement remains owned by `npx skills add time-attack/gstack/skills` and is never inferred from runtime state. diff --git a/skills/qa/references/support/runtime-bootstrap.mjs b/skills/qa/references/support/runtime-bootstrap.mjs index 84370d441..9419f2a87 100644 --- a/skills/qa/references/support/runtime-bootstrap.mjs +++ b/skills/qa/references/support/runtime-bootstrap.mjs @@ -13,8 +13,12 @@ import { fileURLToPath } from "node:url"; export const BOOTSTRAP_SCHEMA_VERSION = 2; export const BOOTSTRAP_RUNTIME_VERSION = "2.0.0"; +// Keep the runtime compatibility version separate from the immutable release +// channel. Release candidates carry the 2.0.0 runtime contract while letting +// fresh-machine production journeys run before the stable v2.0.0 tag exists. +export const BOOTSTRAP_RELEASE_TAG = "v2.0.0-rc.1"; export const OFFICIAL_MANIFEST_URL = - `https://github.com/time-attack/gstack/releases/download/v${BOOTSTRAP_RUNTIME_VERSION}/gstack-runtime-manifest.json`; + `https://github.com/time-attack/gstack/releases/download/${BOOTSTRAP_RELEASE_TAG}/gstack-runtime-manifest.json`; const CAPABILITIES = new Set(["browser", "browser-visible", "design", "pdf", "diagram", "ios"]); const CAPABILITY_DEPENDENCIES = Object.freeze({ browser: Object.freeze([]), @@ -47,9 +51,9 @@ const ALLOWED_DOWNLOAD_HOSTS = new Set([ "objects.githubusercontent.com", "release-assets.githubusercontent.com", ]); -const OFFICIAL_RELEASE_PREFIX = `/time-attack/gstack/releases/download/v${BOOTSTRAP_RUNTIME_VERSION}/`; +const OFFICIAL_RELEASE_PREFIX = `/time-attack/gstack/releases/download/${BOOTSTRAP_RELEASE_TAG}/`; const OFFICIAL_CERTIFICATE_IDENTITY = - `https://github.com/time-attack/gstack/.github/workflows/release-artifacts.yml@refs/tags/v${BOOTSTRAP_RUNTIME_VERSION}`; + `https://github.com/time-attack/gstack/.github/workflows/release-artifacts.yml@refs/tags/${BOOTSTRAP_RELEASE_TAG}`; const GITHUB_OIDC_ISSUER = "https://token.actions.githubusercontent.com"; export async function main(argv = process.argv.slice(2), options = {}) { @@ -90,7 +94,9 @@ export async function main(argv = process.argv.slice(2), options = {}) { ); const manifestUrl = options.manifestUrl ?? OFFICIAL_MANIFEST_URL; assertOfficialUrl(manifestUrl, { manifest: true }); - const manifest = await fetchJson(fetch_, manifestUrl); + const manifest = await fetchJson(fetch_, manifestUrl, { + official: manifestUrl === OFFICIAL_MANIFEST_URL, + }); validateManifest(manifest, target); const home = path.resolve(parsed.home ?? process.env.GSTACK_HOME ?? path.join(os.homedir(), ".gstack")); const reusable = await inspectReusableRuntime(home, manifest.version).catch(() => null); @@ -300,10 +306,18 @@ function sha256File(file) { }); } -async function fetchJson(fetch_, url) { +async function fetchJson(fetch_, url, options = {}) { const response = await fetch_(url, { headers: { Accept: "application/json" }, redirect: "follow" }); assertFinalDownloadUrl(response.url || url); - if (!response.ok) throw bootstrapError(`Download failed with HTTP ${response.status}`, "BOOTSTRAP_DOWNLOAD_FAILED"); + if (!response.ok) { + if (options.official && response.status === 404) { + throw bootstrapError( + `Official runtime release ${BOOTSTRAP_RELEASE_TAG} is not published at ${url}. No files were downloaded or installed.`, + "BOOTSTRAP_RELEASE_UNAVAILABLE", + ); + } + throw bootstrapError(`Manifest download failed with HTTP ${response.status} from ${url}. No files were downloaded or installed.`, "BOOTSTRAP_DOWNLOAD_FAILED"); + } const value = await response.json(); if (!value || typeof value !== "object") throw bootstrapError("Manifest returned invalid JSON", "BOOTSTRAP_MANIFEST_INVALID"); return value; diff --git a/skills/review/references/WEB-CONTEXT.md b/skills/review/references/WEB-CONTEXT.md index 81bbf5d08..2ba9cab1e 100644 --- a/skills/review/references/WEB-CONTEXT.md +++ b/skills/review/references/WEB-CONTEXT.md @@ -7,4 +7,4 @@ When no public-web choice is stored, present: A) Context.dev free setup (recomme Persist only the explicit choice with `gstack context select host`, `gstack context select local-browser`, or `gstack context select none`. For Context.dev, show `gstack context options`, then use `gstack context setup` and its hidden key prompt; consent and key storage belong to the runtime, never this judgment prompt. Do not infer Context choice or consent. -Capability-dependent work follows `references/RUNTIME.md`. Pure judgment never requires the runtime. Skill placement remains owned by `npx skills add time-attack/gstack` and is never inferred from runtime state. +Capability-dependent work follows `references/RUNTIME.md`. Pure judgment never requires the runtime. Skill placement remains owned by `npx skills add time-attack/gstack/skills` and is never inferred from runtime state. diff --git a/skills/review/references/support/runtime-bootstrap.mjs b/skills/review/references/support/runtime-bootstrap.mjs index 84370d441..9419f2a87 100644 --- a/skills/review/references/support/runtime-bootstrap.mjs +++ b/skills/review/references/support/runtime-bootstrap.mjs @@ -13,8 +13,12 @@ import { fileURLToPath } from "node:url"; export const BOOTSTRAP_SCHEMA_VERSION = 2; export const BOOTSTRAP_RUNTIME_VERSION = "2.0.0"; +// Keep the runtime compatibility version separate from the immutable release +// channel. Release candidates carry the 2.0.0 runtime contract while letting +// fresh-machine production journeys run before the stable v2.0.0 tag exists. +export const BOOTSTRAP_RELEASE_TAG = "v2.0.0-rc.1"; export const OFFICIAL_MANIFEST_URL = - `https://github.com/time-attack/gstack/releases/download/v${BOOTSTRAP_RUNTIME_VERSION}/gstack-runtime-manifest.json`; + `https://github.com/time-attack/gstack/releases/download/${BOOTSTRAP_RELEASE_TAG}/gstack-runtime-manifest.json`; const CAPABILITIES = new Set(["browser", "browser-visible", "design", "pdf", "diagram", "ios"]); const CAPABILITY_DEPENDENCIES = Object.freeze({ browser: Object.freeze([]), @@ -47,9 +51,9 @@ const ALLOWED_DOWNLOAD_HOSTS = new Set([ "objects.githubusercontent.com", "release-assets.githubusercontent.com", ]); -const OFFICIAL_RELEASE_PREFIX = `/time-attack/gstack/releases/download/v${BOOTSTRAP_RUNTIME_VERSION}/`; +const OFFICIAL_RELEASE_PREFIX = `/time-attack/gstack/releases/download/${BOOTSTRAP_RELEASE_TAG}/`; const OFFICIAL_CERTIFICATE_IDENTITY = - `https://github.com/time-attack/gstack/.github/workflows/release-artifacts.yml@refs/tags/v${BOOTSTRAP_RUNTIME_VERSION}`; + `https://github.com/time-attack/gstack/.github/workflows/release-artifacts.yml@refs/tags/${BOOTSTRAP_RELEASE_TAG}`; const GITHUB_OIDC_ISSUER = "https://token.actions.githubusercontent.com"; export async function main(argv = process.argv.slice(2), options = {}) { @@ -90,7 +94,9 @@ export async function main(argv = process.argv.slice(2), options = {}) { ); const manifestUrl = options.manifestUrl ?? OFFICIAL_MANIFEST_URL; assertOfficialUrl(manifestUrl, { manifest: true }); - const manifest = await fetchJson(fetch_, manifestUrl); + const manifest = await fetchJson(fetch_, manifestUrl, { + official: manifestUrl === OFFICIAL_MANIFEST_URL, + }); validateManifest(manifest, target); const home = path.resolve(parsed.home ?? process.env.GSTACK_HOME ?? path.join(os.homedir(), ".gstack")); const reusable = await inspectReusableRuntime(home, manifest.version).catch(() => null); @@ -300,10 +306,18 @@ function sha256File(file) { }); } -async function fetchJson(fetch_, url) { +async function fetchJson(fetch_, url, options = {}) { const response = await fetch_(url, { headers: { Accept: "application/json" }, redirect: "follow" }); assertFinalDownloadUrl(response.url || url); - if (!response.ok) throw bootstrapError(`Download failed with HTTP ${response.status}`, "BOOTSTRAP_DOWNLOAD_FAILED"); + if (!response.ok) { + if (options.official && response.status === 404) { + throw bootstrapError( + `Official runtime release ${BOOTSTRAP_RELEASE_TAG} is not published at ${url}. No files were downloaded or installed.`, + "BOOTSTRAP_RELEASE_UNAVAILABLE", + ); + } + throw bootstrapError(`Manifest download failed with HTTP ${response.status} from ${url}. No files were downloaded or installed.`, "BOOTSTRAP_DOWNLOAD_FAILED"); + } const value = await response.json(); if (!value || typeof value !== "object") throw bootstrapError("Manifest returned invalid JSON", "BOOTSTRAP_MANIFEST_INVALID"); return value; diff --git a/skills/ship/references/WEB-CONTEXT.md b/skills/ship/references/WEB-CONTEXT.md index 81bbf5d08..2ba9cab1e 100644 --- a/skills/ship/references/WEB-CONTEXT.md +++ b/skills/ship/references/WEB-CONTEXT.md @@ -7,4 +7,4 @@ When no public-web choice is stored, present: A) Context.dev free setup (recomme Persist only the explicit choice with `gstack context select host`, `gstack context select local-browser`, or `gstack context select none`. For Context.dev, show `gstack context options`, then use `gstack context setup` and its hidden key prompt; consent and key storage belong to the runtime, never this judgment prompt. Do not infer Context choice or consent. -Capability-dependent work follows `references/RUNTIME.md`. Pure judgment never requires the runtime. Skill placement remains owned by `npx skills add time-attack/gstack` and is never inferred from runtime state. +Capability-dependent work follows `references/RUNTIME.md`. Pure judgment never requires the runtime. Skill placement remains owned by `npx skills add time-attack/gstack/skills` and is never inferred from runtime state. diff --git a/skills/ship/references/legacy/gstack-upgrade.md b/skills/ship/references/legacy/gstack-upgrade.md index 084607f92..fc7d71f6a 100644 --- a/skills/ship/references/legacy/gstack-upgrade.md +++ b/skills/ship/references/legacy/gstack-upgrade.md @@ -1,5 +1,5 @@ - + @@ -7,7 +7,7 @@ The 1.x host-directory detector, vendored-copy synchronizer, and destructive Git replacement blocks were duplicated installation infrastructure. GStack 2 delegates skill placement and updates to the standard Agent Skills installer and manages the optional shared runtime atomically. -- Update selected skills with `npx skills add time-attack/gstack` using the user's existing project/global choice. Never infer or enroll a host. +- Update selected skills with `npx skills add time-attack/gstack/skills` using the user's existing project/global choice. Never infer or enroll a host. - Upgrade a complete local runtime package with `gstack upgrade --source --version `. - Roll back the runtime with `gstack upgrade --rollback`. - Run `gstack doctor` after either operation. diff --git a/skills/ship/references/support/runtime-bootstrap.mjs b/skills/ship/references/support/runtime-bootstrap.mjs index 84370d441..9419f2a87 100644 --- a/skills/ship/references/support/runtime-bootstrap.mjs +++ b/skills/ship/references/support/runtime-bootstrap.mjs @@ -13,8 +13,12 @@ import { fileURLToPath } from "node:url"; export const BOOTSTRAP_SCHEMA_VERSION = 2; export const BOOTSTRAP_RUNTIME_VERSION = "2.0.0"; +// Keep the runtime compatibility version separate from the immutable release +// channel. Release candidates carry the 2.0.0 runtime contract while letting +// fresh-machine production journeys run before the stable v2.0.0 tag exists. +export const BOOTSTRAP_RELEASE_TAG = "v2.0.0-rc.1"; export const OFFICIAL_MANIFEST_URL = - `https://github.com/time-attack/gstack/releases/download/v${BOOTSTRAP_RUNTIME_VERSION}/gstack-runtime-manifest.json`; + `https://github.com/time-attack/gstack/releases/download/${BOOTSTRAP_RELEASE_TAG}/gstack-runtime-manifest.json`; const CAPABILITIES = new Set(["browser", "browser-visible", "design", "pdf", "diagram", "ios"]); const CAPABILITY_DEPENDENCIES = Object.freeze({ browser: Object.freeze([]), @@ -47,9 +51,9 @@ const ALLOWED_DOWNLOAD_HOSTS = new Set([ "objects.githubusercontent.com", "release-assets.githubusercontent.com", ]); -const OFFICIAL_RELEASE_PREFIX = `/time-attack/gstack/releases/download/v${BOOTSTRAP_RUNTIME_VERSION}/`; +const OFFICIAL_RELEASE_PREFIX = `/time-attack/gstack/releases/download/${BOOTSTRAP_RELEASE_TAG}/`; const OFFICIAL_CERTIFICATE_IDENTITY = - `https://github.com/time-attack/gstack/.github/workflows/release-artifacts.yml@refs/tags/v${BOOTSTRAP_RUNTIME_VERSION}`; + `https://github.com/time-attack/gstack/.github/workflows/release-artifacts.yml@refs/tags/${BOOTSTRAP_RELEASE_TAG}`; const GITHUB_OIDC_ISSUER = "https://token.actions.githubusercontent.com"; export async function main(argv = process.argv.slice(2), options = {}) { @@ -90,7 +94,9 @@ export async function main(argv = process.argv.slice(2), options = {}) { ); const manifestUrl = options.manifestUrl ?? OFFICIAL_MANIFEST_URL; assertOfficialUrl(manifestUrl, { manifest: true }); - const manifest = await fetchJson(fetch_, manifestUrl); + const manifest = await fetchJson(fetch_, manifestUrl, { + official: manifestUrl === OFFICIAL_MANIFEST_URL, + }); validateManifest(manifest, target); const home = path.resolve(parsed.home ?? process.env.GSTACK_HOME ?? path.join(os.homedir(), ".gstack")); const reusable = await inspectReusableRuntime(home, manifest.version).catch(() => null); @@ -300,10 +306,18 @@ function sha256File(file) { }); } -async function fetchJson(fetch_, url) { +async function fetchJson(fetch_, url, options = {}) { const response = await fetch_(url, { headers: { Accept: "application/json" }, redirect: "follow" }); assertFinalDownloadUrl(response.url || url); - if (!response.ok) throw bootstrapError(`Download failed with HTTP ${response.status}`, "BOOTSTRAP_DOWNLOAD_FAILED"); + if (!response.ok) { + if (options.official && response.status === 404) { + throw bootstrapError( + `Official runtime release ${BOOTSTRAP_RELEASE_TAG} is not published at ${url}. No files were downloaded or installed.`, + "BOOTSTRAP_RELEASE_UNAVAILABLE", + ); + } + throw bootstrapError(`Manifest download failed with HTTP ${response.status} from ${url}. No files were downloaded or installed.`, "BOOTSTRAP_DOWNLOAD_FAILED"); + } const value = await response.json(); if (!value || typeof value !== "object") throw bootstrapError("Manifest returned invalid JSON", "BOOTSTRAP_MANIFEST_INVALID"); return value; diff --git a/test/gstack2-installation.test.ts b/test/gstack2-installation.test.ts index 5b72f96cc..fa60f26e9 100644 --- a/test/gstack2-installation.test.ts +++ b/test/gstack2-installation.test.ts @@ -29,6 +29,13 @@ afterEach(() => { }); describe('GStack 2 standard installer surface', () => { + test('documents the canonical public subpath instead of the legacy-bearing repository root', () => { + for (const file of ['AGENTS.md', 'CLAUDE.md', 'README.md']) { + const content = fs.readFileSync(path.join(DEFAULT_REPO_ROOT, file), 'utf8'); + expect(content, file).toContain('npx skills add time-attack/gstack/skills'); + } + }); + test('publishes exactly six uniquely named canonical skills', () => { const result = inspectRepository(DEFAULT_REPO_ROOT); diff --git a/test/gstack2-runtime-release-channel.test.ts b/test/gstack2-runtime-release-channel.test.ts new file mode 100644 index 000000000..53628ede0 --- /dev/null +++ b/test/gstack2-runtime-release-channel.test.ts @@ -0,0 +1,66 @@ +import { describe, expect, test } from "bun:test"; +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { spawnSync } from "node:child_process"; + +const ROOT = path.resolve(import.meta.dir, ".."); +const SCRIPT = path.join(ROOT, ".github", "scripts", "create-runtime-release-manifest.mjs"); +const WORKFLOW = path.join(ROOT, ".github", "workflows", "release-artifacts.yml"); +const TARGETS = ["darwin-arm64", "darwin-x64", "linux-arm64", "linux-x64", "windows-arm64", "windows-x64"]; +const COMMON = ["core", "browser-code", "browser-headless", "browser-visible", "design", "diagram", "pdf"]; + +async function stageFixture(directory: string) { + for (const target of TARGETS) { + const components = [...COMMON, ...(target.startsWith("darwin-") ? ["ios"] : [])]; + for (const component of components) { + const name = `gstack-runtime-2.0.0-${target}-${component}.tar.gz`; + await fs.writeFile(path.join(directory, name), "fixture\n"); + await fs.writeFile(path.join(directory, `${name}.sha256`), `${"a".repeat(64)} ${name}\n`); + await fs.writeFile(path.join(directory, `${name}.sigstore.json`), "{}\n"); + } + } +} + +describe("GStack runtime release channel", () => { + test("release candidates retain runtime compatibility while binding URLs and signatures to the RC tag", async () => { + const directory = await fs.mkdtemp(path.join(os.tmpdir(), "gstack-runtime-release-channel-")); + try { + await stageFixture(directory); + const result = spawnSync(process.execPath, [SCRIPT, directory, "time-attack/gstack", "2.0.0", "v2.0.0-rc.1"], { + encoding: "utf8", + }); + expect(result.status).toBe(0); + const manifest = JSON.parse(await fs.readFile(path.join(directory, "gstack-runtime-manifest.json"), "utf8")); + expect(manifest.version).toBe("2.0.0"); + expect(manifest.targets["darwin-arm64"].components["browser-visible"]).toMatchObject({ + url: "https://github.com/time-attack/gstack/releases/download/v2.0.0-rc.1/gstack-runtime-2.0.0-darwin-arm64-browser-visible.tar.gz", + certificateIdentity: "https://github.com/time-attack/gstack/.github/workflows/release-artifacts.yml@refs/tags/v2.0.0-rc.1", + }); + } finally { + await fs.rm(directory, { recursive: true, force: true }); + } + }); + + test("release manifest generation rejects non-runtime tags before reading artifacts", async () => { + const directory = await fs.mkdtemp(path.join(os.tmpdir(), "gstack-runtime-invalid-tag-")); + try { + const result = spawnSync(process.execPath, [SCRIPT, directory, "time-attack/gstack", "2.0.0", "main"], { + encoding: "utf8", + }); + expect(result.status).not.toBe(0); + expect(`${result.stdout}${result.stderr}`).toContain("Invalid runtime release tag"); + } finally { + await fs.rm(directory, { recursive: true, force: true }); + } + }); + + test("release workflow publishes both RC and stable tags through the same signed manifest path", async () => { + const workflow = await fs.readFile(WORKFLOW, "utf8"); + expect(workflow).toContain("v2.0.0-rc.*"); + expect(workflow).toContain('2.0.0 "$GITHUB_REF_NAME"'); + expect(workflow).toContain("PRERELEASE_FLAG:"); + expect(workflow).toContain("--prerelease"); + expect(workflow).toContain('gh release create "$GITHUB_REF_NAME"'); + }); +}); diff --git a/test/gstack2-runtime-setup-ux.test.ts b/test/gstack2-runtime-setup-ux.test.ts index b4880109b..4fca7bf4b 100644 --- a/test/gstack2-runtime-setup-ux.test.ts +++ b/test/gstack2-runtime-setup-ux.test.ts @@ -10,6 +10,7 @@ import { setupRuntime } from "../runtime/setup.js"; import { bashCandidates, resolveBashCommand } from "../runtime/tooling.js"; import { BOOTSTRAP_SCHEMA_VERSION, + BOOTSTRAP_RELEASE_TAG, BOOTSTRAP_RUNTIME_VERSION, CAPABILITY_COMPONENTS, COMPONENT_DEPENDENCIES, @@ -27,7 +28,7 @@ function officialManifestFixture(target: string, customize?: (component: string, .filter((component) => component !== "ios" || target.startsWith("darwin-")) .map((component) => { const artifact: Record = { - url: `https://github.com/time-attack/gstack/releases/download/v${BOOTSTRAP_RUNTIME_VERSION}/${component}.tar.gz`, + url: `https://github.com/time-attack/gstack/releases/download/${BOOTSTRAP_RELEASE_TAG}/${component}.tar.gz`, sha256: "0".repeat(64), bytes: 8, format: "tar.gz", @@ -296,6 +297,31 @@ describe("GStack runtime setup UX", () => { expect(fetches).toBe(0); }); + test("missing official release stops before install with an actionable immutable-tag error", async () => { + const root = await fs.mkdtemp(path.join(os.tmpdir(), "gstack-bootstrap-missing-release-")); + const output = capture(); + let calls = 0; + try { + expect(await bootstrapMain([ + "preview", "--capability", "browser-visible", "--home", path.join(root, "home"), + ], { + stdout: output.stream, + stderr: output.stream, + fetch: async (url: string) => { + calls += 1; + return { ok: false, status: 404, url }; + }, + })).toBe(1); + expect(calls).toBe(1); + expect(output.value()).toContain(`Official runtime release ${BOOTSTRAP_RELEASE_TAG} is not published`); + expect(output.value()).toContain(OFFICIAL_MANIFEST_URL); + expect(output.value()).toContain("No files were downloaded or installed"); + expect(await fs.readdir(root)).toEqual([]); + } finally { + await fs.rm(root, { recursive: true, force: true }); + } + }); + test("bootstrap executes through a symlinked or aliased filesystem path", async () => { if (process.platform === "win32") return; const root = await fs.mkdtemp(path.join(os.tmpdir(), "gstack-bootstrap-link-"));