mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-20 20:00:45 +02:00
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.
This commit is contained in:
@@ -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 <artifact-dir> <owner/repo> [version]");
|
||||
console.error("Usage: create-runtime-release-manifest.mjs <artifact-dir> <owner/repo> [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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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 <complete-gstack-package> --version <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 <complete-gstack-package> --version <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"')
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- GENERATED by scripts/gstack2/generate-skill-tree.ts; do not edit. -->
|
||||
<!-- GSTACK2_PROVENANCE source=gstack-upgrade/SKILL.md.tmpl base=bb57306d98c97011b0919c6132705a15b1579781 blob=5402a1da3c857cbf50668085fce53172b72bba0d baseline_render_sha256=609971d414a31d49180e2630bd79a10b8d09085203f7e48c03d2ca69c2ef129a ported_render_sha256=a913cf77f76c4d68c576a06190b498e3d8d3b60b85498a68f173b23a7f800828 disposition=DUPLICATE_INFRASTRUCTURE -->
|
||||
<!-- GSTACK2_PROVENANCE source=gstack-upgrade/SKILL.md.tmpl base=bb57306d98c97011b0919c6132705a15b1579781 blob=5402a1da3c857cbf50668085fce53172b72bba0d baseline_render_sha256=609971d414a31d49180e2630bd79a10b8d09085203f7e48c03d2ca69c2ef129a ported_render_sha256=2c8cf9e505b7da27730bf646a33ee38e8092259f3335a524c71e57ba5b602541 disposition=DUPLICATE_INFRASTRUCTURE -->
|
||||
<!-- GSTACK2_ROUTING replacement=$ship --mode Prepare --module gstack-upgrade visibility=internal depth=standard mutation=installation web=none -->
|
||||
|
||||
<!-- GSTACK2_LEGACY_BODY_START source=gstack-upgrade -->
|
||||
@@ -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 <complete-gstack-package> --version <version>`.
|
||||
- Roll back the runtime with `gstack upgrade --rollback`.
|
||||
- Run `gstack doctor` after either operation.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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"');
|
||||
});
|
||||
});
|
||||
@@ -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<string, unknown> = {
|
||||
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-"));
|
||||
|
||||
Reference in New Issue
Block a user