diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index c410162..7aabf28 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -23,7 +23,10 @@ // Python 3.12 for the research/observer tooling under research/ (Poetry-managed). "ghcr.io/devcontainers/features/python:1": { "version": "3.12" - } + }, + // go-task: the Taskfile.yml runner (`task --list`) for the build/package/run + // flows — one source of truth shared with WSL/host and CI. + "ghcr.io/devcontainers-extra/features/go-task:1": {} }, // Persisted cache volume: survives rebuilds. bash history + npm/pnpm stores + // corepack + Playwright browsers all live here (see remoteEnv below). @@ -52,10 +55,13 @@ } } }, - // 7801 = local bridge WebSocket/HTTP; 5280 = Vite PWA dev server (see docs/03-architecture.md). + // 7801 = local bridge; 5280 = Vite PWA dev; 7900 = dev gateway (task gateway:dev); + // 5285 = UI playground (task playground). See docs/03-architecture.md / docs/07. "forwardPorts": [ 7801, - 5280 + 5280, + 7900, + 5285 ], "portsAttributes": { "7801": { @@ -65,6 +71,14 @@ "5280": { "label": "CloakCode PWA (Vite)", "onAutoForward": "notify" + }, + "7900": { + "label": "CloakCode Gateway (dev)", + "onAutoForward": "silent" + }, + "5285": { + "label": "CloakCode UI Playground (Vite)", + "onAutoForward": "notify" } }, // Persisted-cache wiring. The named volume (mounted at /.devcontainercache) survives diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de0bcd7..bde9598 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -95,18 +95,24 @@ jobs: python -m pip install --upgrade pre-commit pre-commit run --all-files --show-diff-on-failure --color=always + # Runs every package's tests with v8 coverage and FAILS on any regression below + # the per-package thresholds (85% statements/lines/functions, 75% branches) — + # this in-CI gate is the real coverage protection. + # + # We deliberately do NOT upload to GitHub Code Quality (the coverage-on-PRs + # feature). Per the public-preview changelog (2026-05-26, "Availability and + # pricing"), GitHub Code Quality is only available on GitHub Enterprise + # Cloud/Team — so it CANNOT be enabled on this public/free open-source repo: + # there is no setting to turn it on and the upload endpoint 404s. Uploading + # anyway would either fail CI or need a silent `fail-on-error: false` mask, which + # we won't carry. Revisit (add the actions/upload-code-coverage steps + + # `code-quality: write` permission) only if this repo ever moves under an + # Enterprise Cloud/Team org. See docs/06 field notes. coverage: - name: Coverage → Code Quality + name: Coverage — 85% gate runs-on: ubuntu-latest - permissions: - contents: read - code-quality: write # upload coverage to GitHub Code Quality (no third party) steps: - # Check out the PR head (not the merge commit) so coverage line numbers map - # to the diff for the github-code-quality[bot] PR comment. - uses: actions/checkout@v7 - with: - ref: ${{ github.event.pull_request.head.sha || github.sha }} - name: Install pnpm uses: pnpm/action-setup@v6 - name: Setup Node @@ -119,45 +125,8 @@ jobs: # Downstream tests import built workspace packages (e.g. the extension # tests import @cloakcode/gateway's dist), so build everything first — like # the node job. Then run every package's tests with v8 coverage: the - # per-package thresholds (85% statements/lines/functions, 75% branches) FAIL - # this job on a regression, and each run emits a Cobertura report. + # per-package thresholds FAIL this job on a regression. - name: Test with coverage (enforces the 85% gate) run: | pnpm -r build pnpm -r test:coverage - # Upload each package's Cobertura report. No third party — the - # github-code-quality[bot] posts the coverage summary on the pull request. - # fail-on-error: false so CI stays green until Code Quality is enabled in the - # repo's Code security settings (the action 404s the upload until then). - - name: Upload protocol coverage - if: ${{ !cancelled() }} - uses: actions/upload-code-coverage@v1.3.0 - with: - file: packages/protocol/coverage/cobertura-coverage.xml - language: TypeScript - label: code-coverage/protocol - fail-on-error: false - - name: Upload gateway coverage - if: ${{ !cancelled() }} - uses: actions/upload-code-coverage@v1.3.0 - with: - file: packages/gateway/coverage/cobertura-coverage.xml - language: TypeScript - label: code-coverage/gateway - fail-on-error: false - - name: Upload web coverage - if: ${{ !cancelled() }} - uses: actions/upload-code-coverage@v1.3.0 - with: - file: packages/web/coverage/cobertura-coverage.xml - language: TypeScript - label: code-coverage/web - fail-on-error: false - - name: Upload extension coverage - if: ${{ !cancelled() }} - uses: actions/upload-code-coverage@v1.3.0 - with: - file: packages/extension/coverage/cobertura-coverage.xml - language: TypeScript - label: code-coverage/extension - fail-on-error: false diff --git a/README.md b/README.md index 3181570..d8ef803 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,18 @@ pnpm -r test # Vitest pnpm -r test:coverage # coverage gate (85% statements/lines/functions, 75% branches) ``` +Common build/package/run flows are wrapped in a [`Taskfile.yml`](Taskfile.yml) — the dev container +installs [`task`](https://taskfile.dev), so run `task` (or `task --list`) to see them all: + +| Task | What | +| --- | --- | +| `task build` · `task check` | build all · full gate (typecheck + lint + test) | +| `task package` | package everything — extension `.vsix` + assembled gateway | +| `task extension:install` / `extension:uninstall` | install / remove the packaged extension (+ its Copilot hook) | +| `task gateway:dev` · `task gateway:run -- --tunnel` | run the gateway (dev) · run the assembled hub with a tunnel | +| `task playground` · `task web:dev` | UI playground · PWA dev server | +| `task docker:gateway` | build the gateway image — **run from your WSL/host** (Docker isn't in the container) | + Try the research tools against your local Copilot sessions: ```bash diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 0000000..ee56733 --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,143 @@ +# https://taskfile.dev — CloakCode task runner. +# +# One home for the build / package / run flows so the dev container, your WSL +# host, and CI never drift. `task` (or `task --list`) shows everything; pass args +# to a wrapped script after `--` (e.g. `task gateway:run -- --tunnel`). +# +# Docker is NOT in the dev container, so `task docker:gateway` is meant to run +# from your WSL/host (it needs only the repo + Docker). Everything else runs in +# the dev container. +version: "3" + +vars: + # Gateway dev listen port (the dev-container F5 flow expects ws://:7900). + GATEWAY_PORT: '{{.GATEWAY_PORT | default "7900"}}' + GATEWAY_DIST: "{{.TASKFILE_DIR}}/dist/gateway" + EXT_DIST: "{{.TASKFILE_DIR}}/dist/extension" + +tasks: + default: + desc: List every task. + cmds: + - task --list + silent: true + + # ── deps & quality gates ──────────────────────────────────────────────── + install: + desc: Install workspace deps (pnpm, frozen lockfile). + cmds: + - pnpm install --frozen-lockfile + + build: + desc: "① Build all packages (topological)." + cmds: + - pnpm -r build + + test: + desc: Run all tests (Vitest). + cmds: + - pnpm -r test + + test:coverage: + desc: Run all tests with the coverage gate (85% stmts/lines/funcs, 75% branches). + cmds: + - pnpm -r test:coverage + + lint: + desc: Lint all packages (ESLint). + cmds: + - pnpm -r lint + + typecheck: + desc: Type-check all packages (tsc --noEmit). + cmds: + - pnpm -r typecheck + + check: + desc: Full TS gate — typecheck + lint + test (the Definition of Done). + cmds: + - pnpm -r typecheck + - pnpm -r lint + - pnpm -r test + + check:py: + desc: Python gate for research/ — ruff + mypy (needs Poetry). + cmds: + - poetry run ruff check . + - poetry run mypy research + + # ── package ───────────────────────────────────────────────────────────── + package: + desc: "② Build AND package everything — extension .vsix + assembled gateway." + cmds: + - task: package:extension + - task: package:gateway + + package:extension: + desc: "③ Build + package the extension → dist/extension/ (cloakcode-.vsix + install.sh / uninstall.sh)." + cmds: + - pnpm --filter @cloakcode/extension package + + package:gateway: + desc: "④ Build + package the gateway → dist/gateway/ (main.mjs + web/ + run.sh), copy-ready." + cmds: + - pnpm --filter @cloakcode/gateway assemble + + docker:gateway: + desc: "⑤ Build (+ smoke-test) the gateway Docker image. Needs Docker — run from WSL/host, NOT the dev container. Args after -- (e.g. build, --no-cache)." + cmds: + - bash scripts/docker-gateway.sh {{.CLI_ARGS}} + + # ── run ───────────────────────────────────────────────────────────────── + gateway:dev: + desc: "⑥ Run the gateway from the workspace build (verbose), serving the local PWA — for dev." + env: + CLOAKCODE_WEB_DIR: "{{.TASKFILE_DIR}}/packages/web/dist" + CLOAKCODE_GATEWAY_PORT: "{{.GATEWAY_PORT}}" + CLOAKCODE_VERBOSE: "1" + cmds: + - pnpm --filter @cloakcode/protocol build + - pnpm --filter @cloakcode/web build + - pnpm --filter @cloakcode/gateway bundle + - node "{{.TASKFILE_DIR}}/packages/gateway/dist/main.mjs" + + gateway:run: + desc: "⑦ Run the assembled gateway (from ④) via its run.sh. Flags after -- (e.g. task gateway:run -- --tunnel)." + deps: + - package:gateway + cmds: + - bash "{{.GATEWAY_DIST}}/run.sh" {{.CLI_ARGS}} + + extension:install: + desc: "⑧ Package (③) then install the .vsix into VS Code (runs dist/extension/install.sh). CODE_BIN overrides the editor CLI." + deps: + - package:extension + cmds: + - bash "{{.EXT_DIST}}/install.sh" + + extension:uninstall: + desc: "⑨ Uninstall the extension + its Copilot hook (runs dist/extension/uninstall.sh from ③). Pass --keep-hook after --." + cmds: + - bash "{{.EXT_DIST}}/uninstall.sh" {{.CLI_ARGS}} + + playground: + desc: "⑩ Run the UI playground (Vite) — the real PWA against an in-browser fake bridge." + cmds: + - pnpm --filter @cloakcode/web-playground dev + + # ── extra dev flows ───────────────────────────────────────────────────── + web:dev: + desc: Run the PWA dev server (Vite). + cmds: + - pnpm --filter @cloakcode/web dev + + extension:dev: + desc: Run the bridge dev harness (dev-server.ts — the bridge with no extension host). + cmds: + - pnpm --filter @cloakcode/extension dev + + clean: + desc: Remove build artifacts (dist/, package dist/, stray .vsix + install scripts). + cmds: + - rm -rf dist packages/*/dist + - rm -f packages/extension/*.vsix packages/extension/install.sh packages/extension/uninstall.sh diff --git a/docs/06-field-notes.md b/docs/06-field-notes.md index e955cf9..7123afe 100644 --- a/docs/06-field-notes.md +++ b/docs/06-field-notes.md @@ -219,6 +219,19 @@ Base: `~/.vscode-server/data/User/` LTS**, so a blanket major-ignore would wrongly block the next LTS bumps. 25 is the last non-LTS release (older odd 21/23 are EOL). When bumping the major manually, change **both** `FROM node:` stages in `packages/gateway/Dockerfile` (build + runtime) together. +- **TypeScript stays on 5.x — the tsgo 7.x major is gated on typescript-eslint, not us (2026-07-23).** + `typescript@7.0.2` is now `latest` — the **Go-native ("tsgo") rewrite** with a different compiler + API. We can't adopt it while our type-aware linter **`typescript-eslint@8`** declares peer + `typescript: >=4.8.4 <6.1.0` (verified on the registry; `@typescript-eslint/typescript-estree` + carries the same cap): bumping past 5.x would break type-aware lint **and** introduce a + peer-dependency warning (a no-regression violation). So `.github/dependabot.yml` (npm ecosystem) + ignores **only the `typescript` majors** via `update-types: [version-update:semver-major]` — 5.x + minor/patch still auto-update. This is the **opposite** call from the Node-LTS note above (there we + ignore a _specific_ version, not the update-type) because here the block is the ecosystem peer cap, + not an LTS cadence: any TS major is gated until typescript-eslint widens its peer range. **Revisit + trigger:** the day a `typescript-eslint` release lands whose `typescript` peer covers the next TS + major — drop the ignore in that same change. Dependabot auto-closed the stale 5.9.3→7.0.2 PR once + the ignore hit `main`. - **No duplicate tool-version pins — run the tool from the project's dep manager (2026-07-22).** Dependabot has **no `pre-commit` ecosystem**, so a tool version living in a pre-commit `rev:` never gets a Dependabot PR. If that same tool is _also_ pinned elsewhere (e.g. ruff in both @@ -365,4 +378,28 @@ Base: `~/.vscode-server/data/User/` Support/Code/User` / `~/.config/Code/User`), and `--user-data-dir` moves it again → a hardcoded path finds **0 sessions**. Derive the root from **`context.globalStorageUri`** (sibling `…/User/workspaceStorage`) instead. Both fixes are host-accurate with no `process.platform` check. +- **GitHub Code Quality coverage is PLAN-gated — unavailable on a public/free repo, so we do NOT + upload (2026-07-23).** The `coverage` job in [ci.yml](../.github/workflows/ci.yml) once uploaded + Cobertura reports via `actions/upload-code-coverage` so `github-code-quality[bot]` could post a PR + coverage summary. On `lsiddiquee/CloakCode` (a **personal / public / free** repo) every upload + **404s** and the setting to enable it is **nowhere in repo/profile settings** — because **GitHub + Code Quality is only available on GitHub Enterprise Cloud and Team**, per the public-preview + changelog's _Availability and pricing_ (2026-05-26; the product went GA 2026-07-20). It is **not** a + rollout wait and **not** a permission problem — the `code-quality: write` permission is + necessary-but-not-sufficient; the gate is the **account tier**. The `code-scanning` default/advanced + setup type is **irrelevant** to it: Code Quality (coverage) and CodeQL (code scanning) are + **separate features**, so **switching CodeQL to default setup does NOT unlock coverage** — don't do + it hoping to (we stay on **advanced** CodeQL: [codeql.yml](../.github/workflows/codeql.yml), + `javascript-typescript` + `security-extended`). **Decision (2026-07-23): the upload steps + the + `code-quality: write` permission were REMOVED** rather than left masked with `fail-on-error: false` + — a permanently-404ing step behind a silent mask is exactly the kind of hidden CI failure we don't + carry. The **real** protection stays: the in-CI `pnpm -r test:coverage` **85% gate** + (statements/lines/functions; 75% branches) FAILS the job on a regression, independent of any upload. + The job was renamed `Coverage → Code Quality` → **`Coverage — 85% gate`** to match reality; that + string is a **required status check** on `main`, so branch protection's required-checks list was + updated in the same change (rename the job ⇒ update branch protection or PRs hang on a check that + never reports). **Revisit trigger:** the day this repo lives under a GitHub Enterprise Cloud/Team + org (or Code Quality reaches personal plans) — enable Code Quality in the repo's Code security + settings, then re-add the `actions/upload-code-coverage` steps + `code-quality: write` permission + (no `fail-on-error: false` mask — a real upload failure should fail CI). Details in docs/02.3 §4.27 + docs/02.4 §4.28. diff --git a/docs/07-deployment.md b/docs/07-deployment.md index 3893a3a..1bcf760 100644 --- a/docs/07-deployment.md +++ b/docs/07-deployment.md @@ -107,6 +107,24 @@ pass in proto tcp from 192.168.65.0/24 to any port 3543 # Docker Desktop VM s On every OS the firewall is the *fallback* — prefer loopback + a private tunnel, or a virtual-interface bind, and keep the phone on the tunnel rather than a LAN IP. +## Build, package & run — `task` + +Every build/package/run flow is wrapped in the repo's [`Taskfile.yml`](../Taskfile.yml) — the dev +container installs [`task`](https://taskfile.dev) (run `task --list`) so the container, your WSL +host, and CI never drift: + +| Task | What it does | +| --- | --- | +| `task package:extension` | build + package the extension → `dist/extension/` (`.vsix` + `install.sh` / `uninstall.sh`) | +| `task extension:install` / `extension:uninstall` | install / remove the packaged extension (uninstall also drops the per-env Copilot hook) | +| `task package:gateway` | assemble the copy-ready gateway → `dist/gateway/` (`main.mjs` + `web/` + `run.sh`) | +| `task gateway:run -- --tunnel` | run the assembled gateway via its `run.sh` (opens a private tunnel) | +| `task gateway:dev` | run the gateway from the workspace build (verbose) for local iteration | +| `task docker:gateway` | build (+ smoke-test) the gateway image — **run from your WSL/host**; Docker isn't in the dev container | + +`task docker:gateway` and `task gateway:run -- --tunnel` are the two you reach for when exposing a +hub; the sections below cover how to bind/forward it safely. + ## Docker The gateway ships as a container image that **bundles the `devtunnel` CLI**, so it can host the