diff --git a/.github/scripts/next-version.mjs b/.github/scripts/next-version.mjs new file mode 100644 index 0000000..d3f7346 --- /dev/null +++ b/.github/scripts/next-version.mjs @@ -0,0 +1,34 @@ +// Compute the next release version (DEVOPS.md — npm releases, standing +// procedure). Usage: +// +// node .github/scripts/next-version.mjs [latest-published] +// +// Prints the version to publish: +// +// - the baseline (package.json's version) when nothing is published yet or +// the baseline is greater than the latest published version — this is how +// a deliberate minor/major bump landed on main takes effect; +// - otherwise the latest published version with its patch component +// incremented. +// +// Either way the result is strictly greater than every previously published +// version. Only plain x.y.z versions are accepted; anything else (prerelease +// tags, garbage from a failed registry lookup) fails loudly rather than +// publishing a surprise. + +const [baseline, latest] = process.argv.slice(2); + +const parse = (v) => { + const m = /^(\d+)\.(\d+)\.(\d+)$/.exec(v ?? ""); + if (!m) throw new Error(`next-version: not a plain x.y.z version: ${JSON.stringify(v)}`); + return m.slice(1).map(Number); +}; + +const b = parse(baseline); +if (!latest) { + console.log(baseline); +} else { + const l = parse(latest); + const gt = b[0] !== l[0] ? b[0] > l[0] : b[1] !== l[1] ? b[1] > l[1] : b[2] > l[2]; + console.log(gt ? baseline : `${l[0]}.${l[1]}.${l[2] + 1}`); +} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..c9996d1 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,85 @@ +# Automated npm releases (DEVOPS.md — npm releases, standing procedure). +# +# Every push to main whose CI run is green is published to the public npm +# registry as @modularcloud/xspec, with a strictly increasing version, and the +# released commit is tagged vX.Y.Z. No human is involved per release. +# +# Trigger completion of the CI workflow for a push to main. Publishing +# hangs off CI's own result, so the never-release-with-red-CI +# rule is enforced mechanically, and the job checks out exactly +# the commit CI validated (workflow_run.head_sha). +# Version next-version.mjs: package.json's version when it is greater +# than the latest published version (deliberate bumps land on +# main as ordinary package.json changes), else latest published +# with the patch component incremented. The registry and the +# vX.Y.Z tags are the record of released versions; the bump is +# not committed back to main. +# Credential the NPM_TOKEN repository secret (granular npm automation +# token). Missing secret fails loudly with instructions — a +# release is never silently skipped. Re-running a failed run is +# safe: the registry refuses to republish an existing version. +name: Release + +on: + workflow_run: + workflows: [CI] + types: [completed] + branches: [main] + +permissions: + contents: write + id-token: write + +# Queue releases rather than cancelling one mid-publish. +concurrency: + group: npm-release + cancel-in-progress: false + +jobs: + publish: + name: Publish @modularcloud/xspec to npm + runs-on: ubuntu-24.04 + timeout-minutes: 15 + if: >- + github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.event == 'push' && + github.event.workflow_run.head_branch == 'main' + steps: + - name: Require the NPM_TOKEN repository secret + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + if [ -z "$NPM_TOKEN" ]; then + echo "::error::The NPM_TOKEN repository secret is not set, so this green main commit was NOT published. Complete the one-time setup in specs/DEVOPS.md (npm releases — credentials), then re-run this workflow run to publish this commit." + exit 1 + fi + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.workflow_run.head_sha }} + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + registry-url: https://registry.npmjs.org + - run: npm ci + - name: Build the product + run: npm run build + - name: Compute the release version (strictly increasing) + id: version + run: | + NAME="$(node -p "require('./package.json').name")" + BASELINE="$(node -p "require('./package.json').version")" + LATEST="$(npm view "$NAME" version 2>/dev/null || true)" + NEXT="$(node .github/scripts/next-version.mjs "$BASELINE" "$LATEST")" + echo "Package $NAME — baseline $BASELINE, latest published ${LATEST:-}, releasing $NEXT" + echo "next=$NEXT" >> "$GITHUB_OUTPUT" + - name: Publish to npm + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + npm version "${{ steps.version.outputs.next }}" --no-git-tag-version + npm publish --provenance --access public + - name: Tag the released commit + run: | + git tag "v${{ steps.version.outputs.next }}" "${{ github.event.workflow_run.head_sha }}" + git push origin "v${{ steps.version.outputs.next }}" diff --git a/package-lock.json b/package-lock.json index b39bdea..070d781 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { - "name": "xspec", - "version": "0.0.0", + "name": "@modularcloud/xspec", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "xspec", - "version": "0.0.0", + "name": "@modularcloud/xspec", + "version": "0.1.0", "license": "MIT", "dependencies": { "acorn": "^8.17.0", diff --git a/package.json b/package.json index fab30c8..9242ff9 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,27 @@ { - "name": "xspec", - "version": "0.0.0", - "description": "xspec — the product program of this repository; the test harness under test/ is a separate program.", + "name": "@modularcloud/xspec", + "version": "0.1.0", + "description": "Requirement traceability for specifications written in MDX: typed spec modules, reference validation, dependency policy, coverage, impact analysis, and staged reviews.", "license": "MIT", "repository": { "type": "git", - "url": "git+https://github.com/modularcloud/sdg-claude.git" + "url": "git+https://github.com/modularcloud/xspec.git" + }, + "homepage": "https://github.com/modularcloud/xspec#readme", + "bugs": { + "url": "https://github.com/modularcloud/xspec/issues" + }, + "keywords": [ + "specification", + "requirements", + "traceability", + "mdx", + "typescript", + "coverage", + "spec-driven" + ], + "publishConfig": { + "access": "public" }, "type": "module", "engines": { diff --git a/specs/DEVOPS.md b/specs/DEVOPS.md index 9ea9243..8cd43d6 100644 --- a/specs/DEVOPS.md +++ b/specs/DEVOPS.md @@ -6,27 +6,26 @@ This document defines how release, merge, and deploy actions are performed for t - Never merge with red CI. A release candidate is a commit at which the full test suite and every CI job are green. - Never rewrite pushed history. Ordinary commits only. -- External release artifacts — version tags, GitHub Releases, package publishes, deploys — always require explicit Developer authorization. Absent that authorization, the default is deferral: take no external action and record the release candidate instead. +- External release artifacts require explicit Developer authorization. Two standing authorizations exist, granted by Developer on 2026-07-28: publishing `@modularcloud/xspec` to the public npm registry from green `main` commits of this repository, and the `vX.Y.Z` git tags marking those publishes. Any other kind of external artifact (deploys, GitHub Releases, publishes elsewhere) still requires fresh authorization; absent it, the default is deferral — take no external action and record the release candidate instead. -## Initial build on `modularcloud/sdg-claude` (this repository) +## This repository is the product's home -This repository is the SDG process's home, not the product's. The `xspec` product was built here on branch `sdg/initial-build` (PR #1), and Developer will personally relocate the work to a dedicated xspec repository after the initial build completes. A release artifact created here (tag, GitHub Release, package publish) would brand the wrong repository as the product's home and would outlive the branch it points at — so none are created. +`modularcloud/xspec` is the xspec product's dedicated repository, and `main` is its released line. (The product was originally built in `modularcloud/sdg-claude`, the SDG process's home, where release was deliberately deferred so no artifact would brand that repository as the product's home; Developer relocated the work here on 2026-07-27, superseding that deferral and its recorded release candidate `9316048`.) -Release procedure for the initial build — **deferred**, by explicit Developer decision: +The normal Release-phase flow for a completed patch is: -- Take no external release action from this repository. -- Do not merge PR #1. It intentionally carries a `specs/GOALS.md` merge conflict and stays open, untouched. Do not merge `main` into the branch. -- Do not create version tags, GitHub Releases, or package publishes. -- Leave branch `sdg/initial-build` and PR #1 exactly as they are. -- The deliverable is the green branch head, ready for Developer's switch-over to the dedicated xspec repository. Release candidate: commit `9316048` — full suite green locally (485/485), all three CI jobs green. Commits after it on the branch are process bookkeeping only (spec-document edits, no product code). - -## Future runs in the product's own repository +1. Merge the patch's PR once CI is green on the PR head (never with red CI). +2. Nothing further. The standing npm release procedure below publishes the merged commit automatically; there is no per-release tagging, publishing, or Developer step. -Once xspec lives in its own repository, the normal release flow for a completed patch is: +## npm releases (standing procedure) -1. Merge the patch's PR once CI is green on the PR head (never with red CI). -2. With explicit Developer authorization, tag the release commit; a GitHub Release and/or package publish additionally requires Developer-provided credentials. -3. Absent authorization for any external artifact, defer that artifact and record the release candidate, per the standing rules. +- **Artifact.** The public npm package `@modularcloud/xspec`. `package.json` must always identify the package by that name, with this repository as `repository`/`homepage`/`bugs`, and carry `publishConfig.access: public`. +- **Trigger.** Every push to `main` (a PR merge is a push). The `Release` workflow (`.github/workflows/release.yml`) runs when the `CI` workflow completes for a `main` push. +- **Gate.** The publish job runs only when that CI run concluded successfully, and it checks out exactly the commit CI validated — the never-release-with-red-CI rule is enforced mechanically. Nothing beyond green CI gates a release. +- **Versioning.** Strictly increasing, computed by `.github/scripts/next-version.mjs` with no Developer involvement: if `package.json`'s version is greater than the latest published version (or nothing is published yet), that version is released — a deliberate minor/major bump is made by landing the `package.json` change on `main`; otherwise the latest published version's patch component is incremented. The registry and the `vX.Y.Z` tags are the record of released versions; the computed bump is not committed back to `main`, so the in-repo version is the floor, not the record. +- **Tags.** Each successful publish pushes the lightweight tag `vX.Y.Z` on the released commit, using the workflow's own repository token. +- **Credentials.** Publishing authenticates with the `NPM_TOKEN` GitHub repository secret — a granular npm automation token with read/write access to the package (or the `@modularcloud` scope), created under Developer's npm account. Credentials live only in that secret, never in the repository. If the token is missing or expired, recreate it on npmjs.com and set it again with `gh secret set NPM_TOKEN`. +- **Failure handling.** A missing/invalid secret or any publish error fails the Release run loudly — a release is never silently skipped. Re-running a failed Release run publishes the commit it was gated on and is always safe: the registry refuses to republish an existing version, so duplicate attempts fail rather than corrupt the sequence. A failed run never blocks later landings, which release independently. ## Post-update actions diff --git a/specs/PHILOSOPHY.md b/specs/PHILOSOPHY.md index c8c730d..2fa277d 100644 --- a/specs/PHILOSOPHY.md +++ b/specs/PHILOSOPHY.md @@ -8,4 +8,8 @@ IMPORTANT: This file may only be edited and interpreted by Liaison. Only Liaison - sdg-claude is the home of the SDG process only — it "describes the process being run and is not supposed to be the xspec home" (Developer, 2026-07-17). Products built by the process belong in their own repositories. The xspec initial build landed here by accident; Developer's plan: finish the initial-build PR as-is on this repo, then Developer personally relocates the work to a dedicated xspec repo ("i will switch it over"). Consequences while this run finishes: PR #1 deliberately stays merge-conflicted with main (specs/GOALS.md) and unmerged — that state is intended, never a defect to fix or re-raise; no actor merges main into the branch or edits GOALS.md; and since a conflicted PR can never produce pull_request-event CI runs, every green-CI requirement of this run is observed via CI runs on the branch head instead (equivalent evidence: same workflow, same tree — the merge ref it forgoes is with a main this work is leaving anyway). Channel ruling (2026-07-17): workflow_dispatch is unavailable — the GitHub integration token lacks Actions write (403 "Resource not accessible by integration", proven twice) — so the sanctioned channel is push-triggered runs: ci.yml's `push:` trigger amended to include `sdg/initial-build` (agent-owned Phase 8 scaffold, editable without approval; agents have pushed workflow-file changes successfully, so the push credential carries workflow scope). Evaluation frame at every gate is per-job, never the run-level composite: at Phase 9, harness-self green is the required signal while suite-linux/suite-windows are expected red (composite "failure" is the expected shape); from Phase 10's completion, all legs must be green. General rule: when a CI observation channel is closed by integration permissions, prefer amending agent-owned scaffold (e.g. workflow triggers) over asking Developer for UI actions or permission grants — Developer delegates tooling choices and has asked for zero process friction while this run finishes. At run completion, the closing report to Developer should note the branch is ready for the switch-over. - Release ruling for the xspec initial build (2026-07-25, derived from the 2026-07-17 wrong-repo decision): "release" on this repo means no external artifacts at all — no merge (impossible and forbidden), no version tags, no GitHub Release, no package publishing. An xspec version tag or Release on sdg-claude would brand the process repo as the product's home, exactly what Developer said it is not; and publishing needs credentials and authorization never granted. The released state is the green branch head itself (the final commit with all CI jobs green); actual distribution — tagging, npm publish, repo setup — happens after Developer personally relocates the work to the dedicated xspec repo, from that repo. General rule: release actions that create external artifacts (tags, GitHub Releases, package publishes) always require explicit Developer authorization; absent one, DEVOPS.md defaults to deferral and the completion report names the exact commit that constitutes the release candidate. - The xspec relocation is process-executed at Developer's explicit instruction (2026-07-25), superseding the "Developer personally relocates" plan in the two bullets above; this instruction is the external-artifact authorization DEVOPS.md requires. Sequence: (1) the existing unrelated modularcloud/xspec is renamed to modularcloud/cspec to free the name — a GitHub owner-settings action only Developer can perform, since the session toolset has no repository-rename capability; (2) once the name is free, the process creates a fresh modularcloud/xspec and pushes the finished `sdg/initial-build` branch — full history, complete tree (product, harness, specs, process scaffold, CI) — as the new repository's default branch `main`, so future xspec runs are governed by the SDG process from the product's own home per DEVOPS.md's future-runs flow. sdg-claude and PR #1 are left exactly as they stand; closing PR #1 afterward is Developer's call, never the process's initiative. General rule: repository renames and repository creation in the modularcloud org are Developer-performed — the GitHub integration cannot create org repositories (403 "Resource not accessible by integration" on POST /orgs/modularcloud/repos, proven 2026-07-27) — while pushes are process-performed once the target repo exists and is in session scope (add_repo with push access). Redirect hazard, observed 2026-07-27: after a rename, GitHub leaves a redirect on the freed name until a new repo claims it, and add_repo/git resolve straight through that redirect — so before pushing to a just-freed name, verify the resolved target is the intended new repo (e.g. its branch listing), and never push through a rename redirect: it would land on the renamed old repo. +- npm distribution is authorized (Developer, 2026-07-28): publish the product as `@modularcloud/xspec` from the relocated product repo, and set up automated publishing so changes on `main` release to npm going forward. This is the explicit external-artifact authorization the 2026-07-25 release ruling required, and it is standing: once the automated path exists, publishes triggered by it need no fresh per-release approval. Steps needing Developer's own accounts (npm org access, publish credentials, repo secrets) remain Developer-performed — consistent with the proven pattern that org-level GitHub/npm account actions are Developer's, pushes and repo-side wiring are the process's. +- Developer's default working mode for operational setup (stated 2026-07-28): "do as much for me as you can and then let me know what i need to do manually" — automate everything the process can, then hand Developer one concise checklist of only the steps requiring their accounts/credentials, rather than asking permission step-by-step along the way. +- Developer does docs/README/website work outside the SDG process on `claude/*` branches and merges it to `main` directly (first: modularcloud/xspec PR #1, merged 2026-07-27). Such branches and any related working-tree WIP are Developer's space: SDG agents never fold them into SDG commits, never clean them up, and never re-raise them as anomalies. Ruling 2026-07-28 ("disregard work in progress, just publish main branch"): when unrelated local WIP sits in the checkout, ignore it — leave it on disk, never commit it, never delete it — and base all process work on `origin/main` (using a clean worktree or equivalent if the dirty checkout blocks branch mechanics). +- Operational and infrastructure setup work is not a patch in Developer's eyes: "this is not intended to be a patch, its just a one off set up task" (2026-07-28, correcting the npm-publishing work after triage drafted a Bug Report patch for it). Work whose substance is release/deploy/distribution machinery with zero product-behavior change routes as one-off release/devops execution under DEVOPS.md — not through the patch pipeline — and any patch artifacts created by such a misclassification are retired, not refined. Reserve the patch taxonomy for changes to specified product behavior (IP) or to the harness's ability to catch defects (Bug Report). - Refinement loops that plateau are closed by valve ruling, not run to a spontaneous clean round (first applied 2026-07-10, TEST-SPEC.md at iteration 12 of the xspec initial build). Plateau markers: each fresh review yields only one or two genuine but ever-narrower findings, nothing is re-litigated or reversed, and the upstream documents are already converged. Closure shape: one final iteration whose Driver applies what is necessary and then HALTs, with escape hatches for blocking upstream problems or an indefensible late discovery; residual gaps are deliberately left to the downstream problems-file net, which finds them with implementation eyes when they actually matter. Basis: Developer's revealed preference for bounded forward progress over open-ended polishing (bare "continue" nudges, cost sensitivity shown by the 2026-07-09 credits outage, full delegation of process judgment).