diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9371b94..c8fda7c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,16 +6,17 @@ on: permissions: contents: read + id-token: write jobs: validate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: - node-version: "20" - cache: npm + node-version: "24" + package-manager-cache: false - run: npm ci - name: Run the offline test suite run: npm test @@ -25,5 +26,7 @@ jobs: run: npm run lint - name: Build the release candidate run: npm run build - - name: Inspect the package without publishing + - name: Inspect the public package contents run: npm pack --dry-run + - name: Publish the package with npm provenance + run: npm publish --provenance --access public diff --git a/CHANGELOG.md b/CHANGELOG.md index 421f32b..d6eac36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +- Prepared the public npm package as `@chumaniac/skillsync@0.1.0`, updated generated CI + templates to pin the scoped package, and added tag-based OIDC/provenance publication + without a long-lived npm token. - Documented the English-only documentation migration by aligning the public workflow filters with `MVP-Implementation-Plan.md`, `SkillSync-Complete-Design.md`, and `Competitive-Research-and-Design-Rationale.md`; historical versions remain available diff --git a/README.md b/README.md index 12ab265..eeb7f89 100644 --- a/README.md +++ b/README.md @@ -86,13 +86,17 @@ The most recent validation ran locally and offline inside the repository: - type-check, lint, build, `npm pack --dry-run`, 4 workflows, 2 release-template parses, and 20 tracked JSON files (including 3 JSON Schemas) all passed; the public-tree hygiene scan and AST side-effect scan reported no findings. - Docker reference integration was skipped by the availability gate because no local daemon socket existed. This validation did not use any real endpoint, credential, Docker, microVM, remote Worker, or controlled environment, and it did not present local simulated output as live evidence. -### Source repository and npm publication boundary +### Source repository and npm package This project is published as a public source repository at [github.com/Chumaniac/skillsync](https://github.com/Chumaniac/skillsync). [`package.json`](./package.json) already includes `repository`, `homepage`, and `bugs` metadata. -The package still keeps `private: true`, which means npm publication is not enabled yet. That does not block public GitHub source publication. +The distributable CLI package is `@chumaniac/skillsync`. Scoped public access is declared in +`package.json`, while the executable remains available as the `skillsync` command. -If npm publication is enabled in the future, it must happen through a separate release workflow review together with a package-content and provenance-policy recheck. +Tag releases run the full offline validation, inspect the package allowlist, and publish with +GitHub OIDC and npm provenance. The release workflow does not store or use a long-lived npm +token. The package's npm Trusted Publisher must be configured for `Chumaniac/skillsync` and +`.github/workflows/release.yml` before a tag can publish successfully. ### Report privacy boundary @@ -108,6 +112,18 @@ reporting: This option controls only whether local paths are preserved. It does not change the default no-script-execution boundary, the no-network boundary, or the no-credential / no-file-content-output boundary. +## Install from npm + +After a tagged release is published, install the CLI globally or run a pinned +version without a global install: + +```bash +npm install --global @chumaniac/skillsync +npx --yes @chumaniac/skillsync@0.1.0 --help +``` + +The executable name is `skillsync` in both cases. + ## Quick start ```bash diff --git a/docs/ci.md b/docs/ci.md index 76ba9aa..227e9a1 100644 --- a/docs/ci.md +++ b/docs/ci.md @@ -13,11 +13,11 @@ they can be replaced. The generated GitHub Action grants `contents: read` and uploads SARIF findings; it does not execute Skill scripts. The generated consumer command pins the -published SkillSync package version (`skillsync@0.1.0` by default); override it +published SkillSync package version (`@chumaniac/skillsync@0.1.0` by default); override it with `ci init --package-version ` when upgrading. Because the current -repository keeps `private: true`, this consumer template becomes runnable only -after an explicit package publication decision; the repository's own workflow -uses the checked-out build instead. +repository publishes a scoped public package, the generated consumer template can +be used after that package version is available; the repository's own workflow uses +the checked-out build instead. The repository's own `.github/workflows/skillsync.yml` runs the offline regression and verifies the checked-in `fixtures/behavior/review-basic` fixture on pull @@ -27,8 +27,8 @@ and public-tree hygiene rather than assuming that this repository contains a user's `~/.agents/skills` or `~/.claude/skills` directory. The default regression also runs the live-runtime preparation integration and -documentation tests. Its package step is `npm pack --dry-run`; it does not -publish the private package or resolve any deployment-owned reference. +documentation tests. Its package step is `npm pack --dry-run`; it does not publish +or resolve any deployment-owned reference. ## Runtime canary contracts @@ -65,10 +65,11 @@ placeholders is intentionally not accepted as production evidence. ## Release validation `.github/workflows/release.yml` runs only for tags matching `v*`. It checks the -test suite, type-check, lint, build, and `npm pack --dry-run`. It has no -publication step, no public secret input, and no live runtime input. `private: true` -remains in `package.json`; a tag is a validation signal, not permission -to publish or activate a capability. +test suite, type-check, lint, build, and `npm pack --dry-run`, then publishes +`@chumaniac/skillsync` with `npm publish --provenance --access public`. The job +uses GitHub OIDC (`id-token: write`) and no long-lived npm token. npm Trusted +Publisher configuration is an external prerequisite; a tag is not permission +to activate a live runtime capability. The operator-facing activation, revocation, rollback, and evidence review procedure is in [`runtime-operator-runbook.md`](runtime-operator-runbook.md). diff --git a/docs/release-readiness-2026-08-05.md b/docs/release-readiness-2026-08-05.md index bba92a4..4128b46 100644 --- a/docs/release-readiness-2026-08-05.md +++ b/docs/release-readiness-2026-08-05.md @@ -28,7 +28,7 @@ remote Workers remain disabled. | Activation readiness | Prepared | Non-live readiness evaluator and manual canary; 5 focused tests | | External deployment requirements | Contract prepared | Schema, reference-only template, pure parser/evaluator; 15 focused tests; does not parse root or Worker references | | Controlled canary workflow | Prepared | Manual workflow; runs full offline runtime simulator contracts by default, optional local reference Docker smoke, no credential injection; `enable_live_capabilities` defaults to `false`, and the job rejects any value other than `false` | -| Release validation workflow | Prepared | `v*` tags run only test, type-check, lint, build, and `npm pack --dry-run`; does not publish; `private: true` remains unchanged | +| Release validation workflow | Prepared | `v*` tags run test, type-check, lint, build, package inspection, and OIDC-backed public npm publication with provenance; no long-lived npm token is used | | Runtime operator runbook | Prepared | Covers activation order, revocation, rollback, evidence review, and deployment-owned external prerequisites; contains no real endpoint or secret location | | Remote lifecycle and cleanup proof | Local pass, pending review | 17 focused tests; secure mode validates strictly and requires a Worker receipt; retries must clean up the current attempt first | | Dogfood results | Recorded | `docs/dogfood-2026-08-05.md`; known issues reserved to the user directory were found and not rewritten automatically | @@ -180,3 +180,25 @@ and updated workflow filters. | Current local evidence | Pass | 69 test files passed, 1 skipped; 426 tests passed, 1 skipped; type-check, lint, build, and package dry-run passed | | npm publication | Intentionally pending | `private: true` remains; npm publication requires a separate release decision | | Live runtime enablement | Intentionally pending | Independent security approval and controlled-environment evidence remain mandatory | + +## M6 npm package release preparation (2026-08-07) + +The package release track now targets the scoped public package +`@chumaniac/skillsync`. The package metadata, generated consumer templates, and +tag workflow are aligned. Publication uses GitHub OIDC and npm provenance rather +than a long-lived registry token; the npm Trusted Publisher configuration remains +an external one-time setup for the package owner. + +| Review item | Result | +| --- | --- | +| Package identity | Prepared | `@chumaniac/skillsync@0.1.0`; the unscoped `skillsync` name is already occupied by another package | +| Public access | Prepared | `private: false` and `publishConfig.access: public` | +| Consumer templates | Pass | GitHub Action and pre-commit templates pin `@chumaniac/skillsync@0.1.0` | +| Release workflow | Prepared | Tag validation runs on Node 24, then publishes with OIDC and provenance; no npm token is stored in GitHub | +| npm Trusted Publisher | Pending owner setup | Configure user `Chumaniac`, repository `skillsync`, workflow `.github/workflows/release.yml`, and allow `npm publish` | +| First publication | Pending authentication | Requires an authenticated npm account that owns the `@chumaniac` scope | + +This package track does not change the offline-first product boundary. Real +network access, provider credentials, Docker/microVM execution, and remote Worker +execution remain disabled pending the independent security and controlled-runtime +gates above. diff --git a/package-lock.json b/package-lock.json index ecec269..6f8a87a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,13 @@ { - "name": "skillsync", + "name": "@chumaniac/skillsync", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "skillsync", + "name": "@chumaniac/skillsync", "version": "0.1.0", + "license": "MIT", "dependencies": { "commander": "^13.1.0", "yaml": "^2.8.0", diff --git a/package.json b/package.json index d586307..3c1f1e7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,10 @@ { - "name": "skillsync", + "name": "@chumaniac/skillsync", "version": "0.1.0", - "private": true, + "private": false, + "publishConfig": { + "access": "public" + }, "license": "MIT", "description": "A provenance, compatibility, and behavior verification layer for Agent Skills.", "repository": { diff --git a/src/cli/commands/ci.ts b/src/cli/commands/ci.ts index b21068e..a6c179f 100644 --- a/src/cli/commands/ci.ts +++ b/src/cli/commands/ci.ts @@ -24,6 +24,7 @@ export type CiInitResult = { const DEFAULT_PATHS = [".agents/skills", ".claude/skills", ".cursor/skills"]; const DEFAULT_PACKAGE_VERSION = "0.1.0"; +const PUBLISHED_PACKAGE_NAME = "@chumaniac/skillsync"; function validateNodeVersion(value: string): string { if (!/^\d+(?:\.\d+){0,2}$/.test(value)) { @@ -78,8 +79,8 @@ jobs: - uses: actions/setup-node@v4 with: node-version: "${nodeVersion}" - # Requires the published skillsync@${packageVersion} package; publication remains an explicit release step. - - run: npx --yes skillsync@${packageVersion} verify --format sarif ${shellPaths(paths)} > skillsync.sarif + # Requires the published ${PUBLISHED_PACKAGE_NAME}@${packageVersion} package. + - run: npx --yes ${PUBLISHED_PACKAGE_NAME}@${packageVersion} verify --format sarif ${shellPaths(paths)} > skillsync.sarif - uses: github/codeql-action/upload-sarif@v4 if: always() with: @@ -95,7 +96,7 @@ export function renderPreCommit(options: { packageVersion?: string; paths: strin hooks: - id: skillsync-verify name: Verify Agent Skills with SkillSync - entry: npx --yes skillsync@${packageVersion} verify --format json ${shellPaths(paths)} + entry: npx --yes ${PUBLISHED_PACKAGE_NAME}@${packageVersion} verify --format json ${shellPaths(paths)} language: system pass_filenames: false `; diff --git a/templates/github/skillsync.yml b/templates/github/skillsync.yml index 27315af..93cc580 100644 --- a/templates/github/skillsync.yml +++ b/templates/github/skillsync.yml @@ -17,8 +17,8 @@ jobs: - uses: actions/setup-node@v4 with: node-version: "20" - # Requires the published skillsync@0.1.0 package; publication remains an explicit release step. - - run: npx --yes skillsync@0.1.0 verify --format sarif --path .agents/skills > skillsync.sarif + # Requires the published @chumaniac/skillsync@0.1.0 package. + - run: npx --yes @chumaniac/skillsync@0.1.0 verify --format sarif --path .agents/skills > skillsync.sarif - uses: github/codeql-action/upload-sarif@v4 if: always() with: diff --git a/templates/pre-commit/skillsync.yaml b/templates/pre-commit/skillsync.yaml index 8195c36..53abe77 100644 --- a/templates/pre-commit/skillsync.yaml +++ b/templates/pre-commit/skillsync.yaml @@ -3,6 +3,6 @@ repos: hooks: - id: skillsync-verify name: Verify Agent Skills with SkillSync - entry: npx --yes skillsync@0.1.0 verify --format json --path .claude/skills --path .agents/skills + entry: npx --yes @chumaniac/skillsync@0.1.0 verify --format json --path .claude/skills --path .agents/skills language: system pass_filenames: false diff --git a/tests/cli/ci.test.ts b/tests/cli/ci.test.ts index 397601c..0c17ff9 100644 --- a/tests/cli/ci.test.ts +++ b/tests/cli/ci.test.ts @@ -16,8 +16,8 @@ describe("skillsync ci", () => { const content = renderGitHubAction({ nodeVersion: "20", paths: [".agents/skills"], packageVersion: "0.1.0" }); expect(content).toContain("contents: read"); - expect(content).toContain("npx --yes skillsync@0.1.0 verify --format sarif"); - expect(content).toContain("published skillsync@0.1.0"); + expect(content).toContain("npx --yes @chumaniac/skillsync@0.1.0 verify --format sarif"); + expect(content).toContain("published @chumaniac/skillsync@0.1.0"); expect(content).toContain("github/codeql-action/upload-sarif@v4"); expect(content).toContain(".agents/skills/**"); }); @@ -28,7 +28,7 @@ describe("skillsync ci", () => { expect(content).toContain("id: skillsync-verify"); expect(content).toContain(".claude/skills"); expect(content).toContain(".agents/skills"); - expect(content).toContain("npx --yes skillsync@0.1.0 verify --format json"); + expect(content).toContain("npx --yes @chumaniac/skillsync@0.1.0 verify --format json"); }); it("prints a plan without writing, and applies only when requested", async () => { @@ -50,7 +50,7 @@ describe("skillsync ci", () => { apply: true, }); expect(applied.applied).toBe(true); - expect(await readFile(applied.outputPath, "utf8")).toContain("skillsync@0.1.0 verify --format sarif"); + expect(await readFile(applied.outputPath, "utf8")).toContain("@chumaniac/skillsync@0.1.0 verify --format sarif"); await expect( runCiInit({ diff --git a/tests/docs/documentation.test.ts b/tests/docs/documentation.test.ts index a661dfb..7b3b1a6 100644 --- a/tests/docs/documentation.test.ts +++ b/tests/docs/documentation.test.ts @@ -29,7 +29,9 @@ describe("release documentation", () => { const packageJson = JSON.parse(await readFile("package.json", "utf8")) as { bugs?: { url?: string }; homepage?: string; + name?: string; private?: boolean; + publishConfig?: { access?: string }; repository?: { type?: string; url?: string }; }; @@ -113,17 +115,19 @@ describe("release documentation", () => { expect(runbook).toContain("mTLS"); expect(runbook).toContain("remote Worker"); expect(releaseWorkflow).toContain('tags: ["v*"]'); - expect(releaseWorkflow).not.toContain("npm publish"); + expect(releaseWorkflow).toContain("npm publish --provenance --access public"); expect(repositoryWorkflow).toContain("git grep -nE"); expect(repositoryWorkflow).not.toContain("rg -n"); expect(repositoryWorkflow).toContain("SkillSync-Complete-Design.md"); expect(repositoryWorkflow).toContain("Competitive-Research-and-Design-Rationale.md"); expect(repositoryWorkflow).toContain("MVP-Implementation-Plan.md"); - expect(githubTemplate).toContain("skillsync@0.1.0"); - expect(preCommitTemplate).toContain("skillsync@0.1.0"); + expect(githubTemplate).toContain("@chumaniac/skillsync@0.1.0"); + expect(preCommitTemplate).toContain("@chumaniac/skillsync@0.1.0"); expect(review).toContain("runtime-activation-policy.ts"); expect(review).toContain("runtime-deployment-requirements.ts"); - expect(packageJson.private).toBe(true); + expect(packageJson.name).toBe("@chumaniac/skillsync"); + expect(packageJson.private).toBe(false); + expect(packageJson.publishConfig).toEqual({ access: "public" }); expect(packageJson.repository).toEqual({ type: "git", url: "https://github.com/Chumaniac/skillsync.git", diff --git a/tests/integration/live-runtime-preparation.test.ts b/tests/integration/live-runtime-preparation.test.ts index c66d4b5..2e3a4fe 100644 --- a/tests/integration/live-runtime-preparation.test.ts +++ b/tests/integration/live-runtime-preparation.test.ts @@ -99,7 +99,7 @@ describe("live runtime preparation", () => { expect(content).not.toMatch(/npm publish|NODE_AUTH_TOKEN|secrets\.|docker\.sock/i); }); - it("keeps release validation tag-based and publication-free", async () => { + it("keeps release validation tag-based and publishes only with provenance", async () => { const { content, document } = await readWorkflow("release.yml"); const trigger = asRecord(document.on); const push = asRecord(trigger.push); @@ -119,8 +119,11 @@ describe("live runtime preparation", () => { expect(runs).toContain(command); } - expect(content).not.toMatch(/npm publish|npm dist-tag|NODE_AUTH_TOKEN|registry-url|secrets\./i); - expect(asRecord(document.permissions)).toEqual({ contents: "read" }); + expect(runs).toContain("npm publish --provenance --access public"); + expect(content).toContain('node-version: "24"'); + expect(content).toContain("package-manager-cache: false"); + expect(content).not.toMatch(/npm dist-tag|NODE_AUTH_TOKEN|registry-url|secrets\./i); + expect(asRecord(document.permissions)).toEqual({ contents: "read", "id-token": "write" }); }); it("keeps workflow and test sources outside the package artifact allowlist", async () => { @@ -132,7 +135,7 @@ describe("live runtime preparation", () => { ? packageJson.files.filter((entry): entry is string => typeof entry === "string") : []; - expect(packageJson.private).toBe(true); + expect(packageJson.private).toBe(false); expect(files).toContain("dist"); expect(files).not.toContain(".github"); expect(files).not.toContain(".github/**");