Skip to content
Merged
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
11 changes: 7 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 19 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down
21 changes: 11 additions & 10 deletions docs/ci.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <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
Expand All @@ -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

Expand Down Expand Up @@ -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).
24 changes: 23 additions & 1 deletion docs/release-readiness-2026-08-05.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down Expand Up @@ -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.
5 changes: 3 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
7 changes: 4 additions & 3 deletions src/cli/commands/ci.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down Expand Up @@ -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:
Expand All @@ -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
`;
Expand Down
4 changes: 2 additions & 2 deletions templates/github/skillsync.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion templates/pre-commit/skillsync.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 4 additions & 4 deletions tests/cli/ci.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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/**");
});
Expand All @@ -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 () => {
Expand All @@ -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({
Expand Down
12 changes: 8 additions & 4 deletions tests/docs/documentation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
};

Expand Down Expand Up @@ -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",
Expand Down
11 changes: 7 additions & 4 deletions tests/integration/live-runtime-preparation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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 () => {
Expand All @@ -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/**");
Expand Down
Loading