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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/actionlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ on: [push, pull_request]
permissions:
contents: read

concurrency:
group: actionlint-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
actionlint:
runs-on: ubicloud-standard-8
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/ci-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ on:
# Manual trigger
workflow_dispatch:

concurrency:
group: ci-image
cancel-in-progress: false

jobs:
build:
runs-on: ubicloud-standard-8
Expand Down
21 changes: 21 additions & 0 deletions .github/workflows/dependency-review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Dependency Review

on:
pull_request:
branches: [main]

permissions:
contents: read

concurrency:
group: dependency-review-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
dependency-review:
runs-on: ubuntu-24.04
steps:
- name: Review dependency changes
uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0
with:
fail-on-severity: high
26 changes: 26 additions & 0 deletions .github/workflows/osv-scanner.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: OSV Scanner

on:
schedule:
- cron: '23 7 * * 1'
workflow_dispatch:

permissions:
contents: read

concurrency:
group: osv-scanner
cancel-in-progress: true

jobs:
scan:
permissions:
actions: read
contents: read
security-events: write
uses: google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml@3adb4b14a2b0623876d18d863a498b785fb3752d # v2.3.8
with:
scan-args: |-
--include-git-root
--recursive
./
4 changes: 4 additions & 0 deletions .github/workflows/quality-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ on:
permissions:
contents: read

concurrency:
group: quality-gate-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
quality:
runs-on: ubuntu-24.04
Expand Down
46 changes: 46 additions & 0 deletions .github/workflows/scorecard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: OpenSSF Scorecard

on:
branch_protection_rule:
schedule:
- cron: '41 7 * * 1'
push:
branches: [main]

permissions:
contents: read

concurrency:
group: scorecard-${{ github.ref }}
cancel-in-progress: true

jobs:
analysis:
runs-on: ubuntu-24.04
permissions:
contents: read
security-events: write
steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
persist-credentials: false

- name: Run Scorecard analysis
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
with:
results_file: results.sarif
results_format: sarif
publish_results: false

- name: Upload Scorecard results to code scanning
uses: github/codeql-action/upload-sarif@85b88275909735f5bc23196090e03d2eb148b3de # v3.32.4
with:
sarif_file: results.sarif

- name: Upload Scorecard results artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: scorecard-results
path: results.sarif
retention-days: 5
4 changes: 4 additions & 0 deletions .github/workflows/skill-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ on: [push, pull_request]
permissions:
contents: read

concurrency:
group: skill-docs-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
check-freshness:
runs-on: ubicloud-standard-8
Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -611,8 +611,12 @@ Data is stored in [Supabase](https://supabase.com) (open source Firebase alterna

## Troubleshooting

For GStack 2, first run `gstack doctor` (or `gstack doctor --json`) when the
optional runtime is installed. For skill discovery problems, use the standard
For GStack 2, run `gstack doctor --capability browser|design|diagram|pdf|ios`
(optionally `--json`) for a focused, non-mutating readiness result. It reports
pure-judgment availability, platform support, preview consent, install consent,
and runtime readiness separately; see
[`docs/gstack-2/CAPABILITY-READINESS.md`](docs/gstack-2/CAPABILITY-READINESS.md).
For skill discovery problems, use the standard
installer's list and reinstall commands; do not manually copy host paths. The
entries below apply to legacy 1.x installations.

Expand Down
75 changes: 75 additions & 0 deletions docs/gstack-2/CAPABILITY-READINESS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Capability readiness

GStack's six public skills provide pure judgment without the optional runtime.
When a workflow needs executable evidence, inspect one of the five user-facing
capabilities without changing the machine:

```bash
gstack doctor --capability browser
gstack doctor --capability design --json
gstack doctor --capability diagram
gstack doctor --capability pdf
gstack doctor --capability ios
```

The focused result deliberately keeps five questions separate:

| Axis | Meaning |
|---|---|
| `judgment` | `available` even when no optional runtime exists. |
| `platform` | `supported` or `unsupported`; physical iOS is macOS-only. |
| `consent.preview` | Whether consent is required before an uncached public signed-manifest metadata request. Doctor never makes that request or grants consent. |
| `consent.install` | Separate consent required only after the complete preview. Doctor never installs or records consent. |
| `readiness` | Whether the selected local runtime capability is usable now. |

Readiness has exactly five states:

- `ready`: capability and managed-runtime checks passed.
- `degraded`: the capability passed, but the managed runtime reported a warning
such as recovery from an interrupted upgrade.
- `unavailable`: the capability is not selected, no runtime is active, or runtime
inspection could not establish availability.
- `unsupported`: the platform cannot provide the capability with GStack's
existing architecture.
- `failed`: an installed/selected capability failed its readiness evidence.

The JSON form follows the runtime's result convention: `ok` is true only for
`ready` and `degraded`; a non-ready focused doctor exits 1. Usage errors still
exit 2 through the standard CLI error envelope.

## Consent and setup flow

```text
pure judgment (always available)
|
v
capability doctor (local, non-mutating)
|
+-- ready/degraded --> capability-dependent work
|
+-- unsupported ----> judgment-only work or supported platform
|
+-- unavailable/failed
|
v
ask for preview consent
|
v
packaged bootstrap preview (no install)
|
v
ask for install consent
|
v
packaged bootstrap install
|
v
capability doctor again
```

Preview consent is not install consent. Deferring either does not block pure
judgment. Follow the packaged bootstrap contract for exact dependency closure,
compressed bytes, signature checks, and atomic installation. Do not run setup
from a standards-installed skill directory, add a browser provider, replace the
local Chromium/Playwright backend, or replace the physical-iOS
DebugBridge/CoreDevice harness.
18 changes: 9 additions & 9 deletions evals/parity/transcripts/policy-units.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"prompt_sha256": "a0fcff9cad68f9305da861fa5a58990e1ef51d9d84298fe2df7ba8a2f56b1dba",
"semantic_attempt_sha256": "7724c3d954f421753c597364c383bd76bc01ba9803f99fb14488287bb925aeb6"
},
"policy_sha256": "951fd945e7ecb438bcedadccd563df430652d35f4cff1d513dfdcf16ee77d02d",
"policy_sha256": "c7df25a16073d69da15243f258a2ef72a69c03ee3114eac2c19cf1c7e126532b",
"policy_present": true,
"prompt_is_not_authority_input": true,
"verdict": "PASS"
Expand Down Expand Up @@ -88,7 +88,7 @@
"prompt_sha256": "04d82a21358f188cb823dcceeecaebea0b4ed8b9c1f8d00220ef3b499c48b1c8",
"semantic_attempt_sha256": "9639406995955284517acb30f56b37dedc42a6c08142163dfa8fe0295105cbbf"
},
"policy_sha256": "e5fc3019062512de84ade87c8c45b34c8f46b95a65ef84a49fd25bbdee63e41e",
"policy_sha256": "8dd78f6f8e01eb9cf1fe0f3f43d7fcd072a4f3f2f0dc14d9f1b5d3ff1250b972",
"policy_present": true,
"prompt_is_not_authority_input": true,
"verdict": "PASS"
Expand Down Expand Up @@ -138,7 +138,7 @@
"prompt_sha256": "8ad639f708a2ccd5c7c438b1dedf6afacdfa4eac3883a7a046ea23f29314e1c4",
"semantic_attempt_sha256": "4c450039e4c622fbeaa34b7f7dfc810d3b7369aaf7e5f4d240cd6cc18a31331a"
},
"policy_sha256": "78df79ecb49744ff13f044b8f5a486705ef43172c4dc19d7dbc616d84f4d578c",
"policy_sha256": "7c7ccb7ff64ea6c73b668848ae5a663606798abc345d7e5c6bb0880732bb29c3",
"policy_present": true,
"prompt_is_not_authority_input": true,
"verdict": "PASS"
Expand Down Expand Up @@ -188,7 +188,7 @@
"prompt_sha256": "d2aa9aee06a116bf04cb62772e8abdf8dbd606811b6d38565389c88e86c79ede",
"semantic_attempt_sha256": "64885a66bdc6688b880cbb9a59babf314834c068eb040657501bf287bfcb0d2b"
},
"policy_sha256": "951fd945e7ecb438bcedadccd563df430652d35f4cff1d513dfdcf16ee77d02d",
"policy_sha256": "c7df25a16073d69da15243f258a2ef72a69c03ee3114eac2c19cf1c7e126532b",
"policy_present": true,
"prompt_is_not_authority_input": true,
"verdict": "PASS"
Expand Down Expand Up @@ -232,7 +232,7 @@
"prompt_sha256": "377e3a2dfc7b04272f857d27e77c9aa3c9f36c3feb63c03e5aee3ecd3dea8e2a",
"semantic_attempt_sha256": "a49b1528091886749278dc22e7da4556c090800989b2bca9c0e1c09dd880fed7"
},
"policy_sha256": "7a8cb7346851f9d948139dbf33cb8c6dd5eb8b2cc7de94e30a53824a94f027a6",
"policy_sha256": "7969ac784f398b09cec2ad44f623de95f1e326721043272b12aaec1c622e840c",
"policy_present": true,
"prompt_is_not_authority_input": true,
"verdict": "PASS"
Expand Down Expand Up @@ -279,7 +279,7 @@
"prompt_sha256": "a01bb977de84e53d8ce3dfa427bcc93d73c6e449cd4e9c1ff63b436fd41fb0d1",
"semantic_attempt_sha256": "ce5c64c62c3ff9b60949f34717dffbc908f62ce30f8a4a48ec182eba4363a206"
},
"policy_sha256": "a65efa55d992f63d0bdb356e1e8c4900b966364af1e22f0b6412e20e7ef30a58",
"policy_sha256": "4134088a9b22fd7d3d2d6492c812ecbc55650df25ab1101189dd24721fe1763d",
"policy_present": true,
"prompt_is_not_authority_input": true,
"verdict": "PASS"
Expand Down Expand Up @@ -330,7 +330,7 @@
"prompt_sha256": "95e97e26268ad7e509527e0f54c943ed6c4105d919f250648a2ad59ce77cbdb9",
"semantic_attempt_sha256": "dacd11a78e32aeb6c0065496bedd4a9770f7bbac1036e003d3768fac41eb84f0"
},
"policy_sha256": "951fd945e7ecb438bcedadccd563df430652d35f4cff1d513dfdcf16ee77d02d",
"policy_sha256": "c7df25a16073d69da15243f258a2ef72a69c03ee3114eac2c19cf1c7e126532b",
"policy_present": true,
"prompt_is_not_authority_input": true,
"verdict": "PASS"
Expand Down Expand Up @@ -381,7 +381,7 @@
"prompt_sha256": "bdd7e8adfa7c15cf8531f84c3adaaacc725075f1a78f7487225300750547b82d",
"semantic_attempt_sha256": "32c11b63412c5d873ff29dcc87c45bef1b50daa218a5c6c1075864aa24d7c3b6"
},
"policy_sha256": "951fd945e7ecb438bcedadccd563df430652d35f4cff1d513dfdcf16ee77d02d",
"policy_sha256": "c7df25a16073d69da15243f258a2ef72a69c03ee3114eac2c19cf1c7e126532b",
"policy_present": true,
"prompt_is_not_authority_input": true,
"verdict": "PASS"
Expand Down Expand Up @@ -426,7 +426,7 @@
"prompt_sha256": "51b7d53bc342e8632e34ae31a167cbde568ab5ee2a6dd2ccc980583a30604164",
"semantic_attempt_sha256": "06f77b27c9bd360562655b23ca00a0dd021bdb14ebaaf3d836845e4e0cb43d68"
},
"policy_sha256": "65f28f454b67d4904c1857a2a6cfcff387c0aa6e4ae58aec490bc95289dce59f",
"policy_sha256": "43d5cbbf88ed4f9afa3e7af214b4aa8c3a764a7006d76a864344df05d1f76392",
"policy_present": true,
"prompt_is_not_authority_input": true,
"verdict": "PASS"
Expand Down
25 changes: 19 additions & 6 deletions runtime/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,13 @@ import {
runExternalEffect,
updateRunWorkflow,
} from "./state.js";
import { runDoctor, formatDoctor } from "./doctor.js";
import {
CAPABILITY_READINESS_CAPABILITIES,
capabilityReadiness,
formatCapabilityReadiness,
runDoctor,
formatDoctor,
} from "./doctor.js";
import { cleanupRuntime } from "./cleanup.js";
import {
ContextClient,
Expand Down Expand Up @@ -154,11 +160,18 @@ async function initCommand({ args, home, cwd, stdout, legacyAlias = false }) {
}

async function doctorCommand({ args, home, cwd, stdout }) {
const parsed = parseFlags(args, new Set(["--json", "--skill-api"]));
const parsed = parseFlags(args, new Set(["--json", "--skill-api", "--capability"]));
if (parsed.positionals.length) throw cliError("Doctor accepts only named options", "USAGE");
const capability = parsed.values.get("--capability");
if (capability && !CAPABILITY_READINESS_CAPABILITIES.includes(capability)) {
throw cliError(`Unknown capability: ${capability}. Choose ${CAPABILITY_READINESS_CAPABILITIES.join(", ")}.`, "USAGE");
}
const report = await runDoctor({ home, cwd, expectedSkillApi: parsed.values.get("--skill-api") });
write(stdout, parsed.flags.has("--json") ? `${JSON.stringify(report, null, 2)}\n` : formatDoctor(report));
return report.ok ? 0 : 1;
const result = capability ? capabilityReadiness(report, capability) : report;
write(stdout, parsed.flags.has("--json")
? `${JSON.stringify(result, null, 2)}\n`
: capability ? formatCapabilityReadiness(result) : formatDoctor(result));
return result.ok ? 0 : 1;
}

async function configCommand({ args, home, cwd, stdout }) {
Expand Down Expand Up @@ -724,7 +737,7 @@ function parseFlags(args, allowed) {
continue;
}
if (!allowed.has(arg)) throw cliError(`Unknown option: ${arg}`, "USAGE");
if (["--source", "--version", "--url", "--older-than-hours", "--max-pages", "--max-depth", "--max-links", "--skill-api"].includes(arg)) {
if (["--source", "--version", "--url", "--older-than-hours", "--max-pages", "--max-depth", "--max-links", "--skill-api", "--capability"].includes(arg)) {
const value = args[++index];
if (value == null || value.startsWith("--")) throw cliError(`${arg} requires a value`, "USAGE");
values.set(arg, value);
Expand Down Expand Up @@ -801,7 +814,7 @@ function usage() {
"Usage:\n" +
" gstack init # initialize state only\n" +
" gstack setup # compatibility alias for init\n" +
" gstack doctor [--skill-api <version>] [--json]\n" +
" gstack doctor [--capability browser|design|diagram|pdf|ios] [--skill-api <version>] [--json]\n" +
" gstack paths [--json|--shell]\n" +
" gstack runtime path <bundle-relative-path>\n" +
" gstack config get [key]\n" +
Expand Down
Loading
Loading