From 8baa0b898a19531edda5311c1bb054398f1214d4 Mon Sep 17 00:00:00 2001 From: Qwynn Marcelle Date: Sun, 26 Jul 2026 01:08:36 -0400 Subject: [PATCH 1/2] docs(oss): bring workspacejson/standard to the public repository polish standard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Layers public-readiness work on top of the extraction migration without touching the normative surface. Schema bytes are unchanged: 6220 bytes, sha256 7f1635bbeff47b103566866d1b66c47a604f91bb3948ad2b59a3ba9369a41e36. Added — community and governance SUPPORT.md, GOVERNANCE.md, MAINTAINERS.md, .github/CODEOWNERS and .github/ISSUE_TEMPLATE/config.yml. Each names a real process and a real owner; none is ceremony. Added — documentation the repository claims to own docs/adr/ now holds the architecture decision records, which previously existed only in a private tracker. ADR-001 records the canonical artifact path decision that shipped in 0.4.1 and had never been written down anywhere; its provenance is the package changelog, not a new decision. ADR-002 is transcribed from its internal draft with issue identifiers, private product names and a stale registry snapshot removed — no decision, boundary, metric or stop condition altered. docs/versioning.md, docs/conformance.md, docs/glossary.md, docs/troubleshooting.md and docs/repository-settings.md. Added — automation that keeps the polish true scripts/check-docs.mjs, wired into CI as `pnpm run check:docs`. Every relative link must resolve, every documented `pnpm run` command must exist, and no internal tracker identifier may reach public prose. It found 37 identifier leaks on its first run. .editorconfig, .gitattributes, .github/dependabot.yml. Changed — public surface Internal tracker identifiers removed from prose across the README, OWNERSHIP, AGENTS, CONTRIBUTING, RELEASE-AUTHORITY, the guard scripts, CI comments and two package test descriptions. Every explanation was kept; only the unresolvable identifiers changed. migration/ and docs/adr/ stay exempt as provenance records. README gained a status statement, a cold-verified quickstart, a documentation map and a known-limitations section. Both issue templates were rewritten — they still described agents-audit — and now route out-of-scope reports to the owning repository. Package READMEs no longer claim to be published from the agents-audit workspace, and their brand assets are vendored into assets/ rather than loaded from the frozen historical repository. ci.yml declares least-privilege `permissions: contents: read` at workflow and job level, and cancels superseded non-main runs. Removed the `aaif` keyword from both packages: it implied a standards-body status the project does not hold. Fixed Both package changelogs recorded 0.4.4 as Unreleased while npm published it on 2026-07-23. The registry is the arbiter. Deliberately unchanged Schema $id host, the v1.json filename, package versions, version/specVersion dual-emit, the four ambient interop shims, publication capability and repository visibility. Each changes a contract or needs an authority this pass does not hold. --- .editorconfig | 27 ++ .gitattributes | 32 +++ .github/CODEOWNERS | 29 ++ .github/ISSUE_TEMPLATE/bug_report.yml | 58 +++- .github/ISSUE_TEMPLATE/config.yml | 17 ++ .github/ISSUE_TEMPLATE/feature_request.yml | 47 +++- .github/RELEASE-AUTHORITY.md | 4 +- .github/dependabot.yml | 51 ++++ .github/workflows/ci.yml | 26 +- AGENTS.md | 6 +- CHANGELOG.md | 61 ++++- CODE_OF_CONDUCT.md | 25 +- CONTRIBUTING.md | 14 + GOVERNANCE.md | 94 +++++++ MAINTAINERS.md | 29 ++ OWNERSHIP.md | 11 +- README.md | 110 +++++++- SECURITY.md | 24 +- SUPPORT.md | 69 +++++ assets/README.md | 34 +++ assets/workspace-json-lockup-dark.png | Bin 0 -> 48869 bytes assets/workspace-json-lockup-light.png | Bin 0 -> 49807 bytes docs/adr/001-canonical-artifact-path.md | 150 ++++++++++ docs/adr/002-bounded-enrichment-program.md | 305 +++++++++++++++++++++ docs/adr/README.md | 62 +++++ docs/conformance.md | 168 ++++++++++++ docs/glossary.md | 165 +++++++++++ docs/repository-settings.md | 168 ++++++++++++ docs/troubleshooting.md | 189 +++++++++++++ docs/versioning.md | 174 ++++++++++++ package.json | 1 + packages/rules/CHANGELOG.md | 2 +- packages/rules/README.md | 62 +++-- packages/rules/package.json | 7 +- packages/spec/CHANGELOG.md | 7 +- packages/spec/README.md | 58 +++- packages/spec/package.json | 3 +- packages/spec/src/index.test.ts | 12 +- scripts/check-architecture.mjs | 14 +- scripts/check-docs.mjs | 173 ++++++++++++ scripts/validate-examples.mjs | 2 +- scripts/verify-package-tarball.mjs | 14 +- scripts/verify-published.mjs | 2 +- scripts/verify-schema-provenance.mjs | 4 +- types/ambient.d.ts | 6 +- 45 files changed, 2419 insertions(+), 97 deletions(-) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .github/CODEOWNERS create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/dependabot.yml create mode 100644 GOVERNANCE.md create mode 100644 MAINTAINERS.md create mode 100644 SUPPORT.md create mode 100644 assets/README.md create mode 100644 assets/workspace-json-lockup-dark.png create mode 100644 assets/workspace-json-lockup-light.png create mode 100644 docs/adr/001-canonical-artifact-path.md create mode 100644 docs/adr/002-bounded-enrichment-program.md create mode 100644 docs/adr/README.md create mode 100644 docs/conformance.md create mode 100644 docs/glossary.md create mode 100644 docs/repository-settings.md create mode 100644 docs/troubleshooting.md create mode 100644 docs/versioning.md create mode 100644 scripts/check-docs.mjs diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d4b946e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,27 @@ +# https://editorconfig.org +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 + +[*.md] +# Two trailing spaces are a hard line break in Markdown. Trimming them silently +# reflows prose, so it is disabled here rather than everywhere. +trim_trailing_whitespace = false + +[*.{json,jsonc}] +indent_size = 2 + +[*.{yml,yaml}] +indent_size = 2 + +[Makefile] +indent_style = tab + +[LICENSE] +insert_final_newline = false diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..c1882ef --- /dev/null +++ b/.gitattributes @@ -0,0 +1,32 @@ +# Normalize line endings in the repository, check out native on the working tree. +# Without this, a CRLF checkout changes the bytes of the canonical schema and the +# SHA-256 that downstream repositories pin against it. +* text=auto eol=lf + +# The normative schema and the shipped examples are byte-significant. Never let a +# platform, filter or merge driver rewrite them. +packages/spec/schema/v1.json text eol=lf +packages/spec/examples/*.json text eol=lf + +# Generated or vendored — exclude from language statistics and collapse in diffs. +pnpm-lock.yaml linguist-generated=true -diff +migration/parity-*.txt linguist-generated=true +migration/commit-map.txt linguist-generated=true +migration/architecture-red-tests.txt linguist-generated=true + +# Documentation should count as documentation, not as code. +*.md linguist-documentation=true + +# Binary assets. +*.png binary +*.jpg binary +*.svg text eol=lf + +# Keep the packed tarball free of repository-only material. `files` in each +# package manifest is the primary control; this is defense in depth for anything +# consuming the repository as an archive. +/.github/ export-ignore +/migration/ export-ignore +/docs/ export-ignore +/.editorconfig export-ignore +/.gitattributes export-ignore diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..4d9871b --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,29 @@ +# Code owners for workspacejson/standard. +# +# GitHub requests review from the owners of every path a pull request touches. +# This file mirrors MAINTAINERS.md; keep the two in step. +# +# Paths are listed most-general first — later patterns win. + +* @qmarcelle + +# The normative surface. Changes here alter the contract for every consumer and +# require an architecture decision record before implementation. See GOVERNANCE.md. +/packages/spec/schema/ @qmarcelle +/packages/spec/src/types.ts @qmarcelle +/packages/spec/src/schema.ts @qmarcelle +/docs/adr/ @qmarcelle + +# Boundary and publication guards. A change here can silently disable the checks +# that keep this repository incapable of publishing and free of cross-repository +# dependencies. +/scripts/ @qmarcelle +/.github/workflows/ @qmarcelle +/.github/RELEASE-AUTHORITY.md @qmarcelle + +# Governance and ownership statements. +/GOVERNANCE.md @qmarcelle +/MAINTAINERS.md @qmarcelle +/OWNERSHIP.md @qmarcelle +/SECURITY.md @qmarcelle +/.github/CODEOWNERS @qmarcelle diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index b85bb4a..599dc2f 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,7 +1,44 @@ name: Bug report -description: Report a problem with agents-audit or the supporting packages +description: Report a problem with the schema, types, validation semantics or rule engine labels: [bug] body: + - type: markdown + attributes: + value: | + This repository owns the **specification** and its deterministic reference + behavior. If `.agents/workspace.json` is not being generated or is generated + wrongly, that is `workspacejson/cli`. If an MCP or editor integration + misbehaves, that is `workspacejson/integrations`. See + [SUPPORT.md](https://github.com/workspacejson/standard/blob/main/SUPPORT.md). + + For a security vulnerability, stop and follow + [SECURITY.md](https://github.com/workspacejson/standard/blob/main/SECURITY.md) + instead of filing here. + - type: dropdown + id: area + attributes: + label: Affected surface + description: Which part of this repository is wrong? + options: + - "@workspacejson/spec — JSON Schema" + - "@workspacejson/spec — TypeScript types" + - "@workspacejson/spec — validation API or workspacejson-spec binary" + - "@workspacejson/rules — AGENTS.md parser" + - "@workspacejson/rules — repository scanner" + - "@workspacejson/rules — rule engine" + - "@workspacejson/rules — workspace.json validator" + - Documentation in this repository + - Not sure + validations: + required: true + - type: input + id: versions + attributes: + label: Package and Node versions + description: Output of `npm ls @workspacejson/spec @workspacejson/rules` and `node --version`. + placeholder: "@workspacejson/spec 0.4.4, @workspacejson/rules 0.4.4, node v20.11.0" + validations: + required: true - type: textarea id: summary attributes: @@ -14,21 +51,32 @@ body: id: steps attributes: label: Steps to reproduce - description: Include the minimum steps required to reproduce the issue. - placeholder: 1. ... + description: > + The minimum steps required. Include the smallest workspace.json document + that reproduces the problem, and the exact command you ran. + render: shell validations: required: true - type: textarea id: expected attributes: label: Expected behavior - description: What should have happened instead? + description: What should have happened instead, and what makes you say so — schema text, a documented guarantee, a stable read path? validations: required: true - type: textarea id: actual attributes: label: Actual behavior - description: What happened in practice? + description: What happened in practice. Paste the complete output rather than summarizing it. + render: shell validations: required: true + - type: checkboxes + id: contract + attributes: + label: Contract impact + description: Check any that apply. These change how the report is triaged. + options: + - label: This affects one of the four stable read paths (`manual.fragileFiles`, `manual.coChangePatterns`, `generated.fileIndex`, `generated.frameworkManifest`) + - label: This affects a published package, not only local development diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..cfe6001 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,17 @@ +blank_issues_enabled: false +contact_links: + - name: Security vulnerability + url: https://github.com/workspacejson/standard/security/advisories/new + about: Report privately through a GitHub security advisory. Do not open a public issue. + - name: Artifact generation — .agents/workspace.json missing or wrong + url: https://github.com/workspacejson/cli/issues + about: This repository defines the format. Producing the file belongs to workspacejson/cli. + - name: MCP, Codex or editor integration + url: https://github.com/workspacejson/integrations/issues + about: Host adapters live in workspacejson/integrations. + - name: Documentation on workspacejson.dev + url: https://github.com/workspacejson/site/issues + about: The published website is assembled in workspacejson/site. + - name: Questions and support + url: https://github.com/workspacejson/standard/blob/main/SUPPORT.md + about: Where to ask, what to include, and what response to expect. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index b603774..fc9070e 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,23 +1,60 @@ name: Feature request -description: Suggest an improvement for agents-audit or its packages +description: Propose a change to the specification, schema or reference behavior labels: [enhancement] body: + - type: markdown + attributes: + value: | + Two properties of this standard are load-bearing and are enforced in CI: + + - **Descriptive, never prescriptive.** `workspace.json` reports what a + repository *is*. It does not encode what a team *must do*. + - **Daemon-free.** The committed file must remain useful with nothing running. + + Proposals that change either property, the bytes of the normative schema, + or the four stable read paths require an architecture decision record + before implementation — see + [GOVERNANCE.md](https://github.com/workspacejson/standard/blob/main/GOVERNANCE.md). - type: textarea id: problem attributes: label: Problem statement - description: What problem are you trying to solve? + description: What problem are you trying to solve? Describe the situation, not the solution. validations: required: true - type: textarea id: proposal attributes: - label: Proposed solution - description: What should the repo or package do differently? + label: Proposed change + description: What should the specification or reference behavior do differently? + validations: + required: true + - type: dropdown + id: surface + attributes: + label: Does this change the normative surface? + description: > + The normative surface is the schema bytes, the four stable read paths, and + the public exports of either package. + options: + - "No — additive or internal only" + - "Yes — schema bytes change" + - "Yes — a stable read path changes" + - "Yes — a public export changes" + - Not sure + validations: + required: true + - type: textarea + id: consumers + attributes: + label: Consumer impact + description: > + Who reads this data today, and what breaks if the change lands? If you know + of no existing consumer, say so — that is useful information. validations: required: true - type: textarea id: context attributes: label: Additional context - description: Add links, examples, or screenshots if helpful. + description: Links, prior art, an example document, or the alternatives you rejected. diff --git a/.github/RELEASE-AUTHORITY.md b/.github/RELEASE-AUTHORITY.md index d24b8b2..754a60c 100644 --- a/.github/RELEASE-AUTHORITY.md +++ b/.github/RELEASE-AUTHORITY.md @@ -4,7 +4,7 @@ `@workspacejson/spec` and `@workspacejson/rules` are still published from `workspace-json/agents-audit`, which holds the `NPM_TOKEN` secret. Transferring -that authority is **META-243**, and it has not happened. +that authority is a separate, coordinated change, and it has not happened. ## Why absence rather than a disabled workflow @@ -28,7 +28,7 @@ the current state. There is nothing to accidentally enable. `scripts/check-architecture.test.mjs` prove those guards reject deliberate violations. -## What META-243 must do +## What the authority transfer must do When publication authority transfers, in a single coordinated change: diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..d7548fb --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,51 @@ +version: 2 + +updates: + # Both packages live in one pnpm workspace, so a single npm entry at the root + # covers the whole dependency tree. Per-package entries would open duplicate + # pull requests for the same shared dependency. + - package-ecosystem: npm + directory: / + schedule: + interval: weekly + day: monday + open-pull-requests-limit: 5 + labels: + - dependencies + commit-message: + prefix: chore + include: scope + groups: + # Type definitions and build tooling churn constantly and carry no runtime + # risk. Grouping them keeps the review queue readable. + dev-tooling: + dependency-type: development + patterns: + - "@types/*" + - typescript + - tsup + - vitest + - "@changesets/*" + ignore: + # ajv is a runtime dependency of the validator. A major bump changes + # validation behavior, which is a change to the normative contract, not a + # dependency update — it needs an architecture decision record first. + # See GOVERNANCE.md. + - dependency-name: ajv + update-types: [version-update:semver-major] + # The packages declare `node >=20` and CI runs 20 and 22. A @types/node + # major that assumes a newer runtime would typecheck against a Node the + # packages do not claim to support. + - dependency-name: "@types/node" + update-types: [version-update:semver-major] + + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + day: monday + open-pull-requests-limit: 3 + labels: + - dependencies + commit-message: + prefix: ci diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eab03c6..b65ab89 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,9 +6,23 @@ on: pull_request: branches: [main] +# Least privilege by default. This workflow only reads the repository — it does +# not comment, label, publish or write any artifact. Any job needing more must +# raise it at the job level with a reason, not here. +permissions: + contents: read + +# A superseded push to the same branch has nothing left to prove. `main` is +# excluded from cancellation so the record of what landed stays complete. +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + jobs: test: runs-on: ubuntu-latest + permissions: + contents: read strategy: matrix: node-version: [20, 22] @@ -33,11 +47,19 @@ jobs: - name: Architecture guard red tests run: pnpm run check:architecture:test + # Documentation decays silently. This proves every relative link still + # resolves, that no internal tracker identifier reached public prose, and + # that every documented `pnpm run` command actually exists. External links + # are syntax-checked only — fetching them in CI fails on someone else's + # outage and trains everyone to ignore red. + - name: Documentation integrity + run: pnpm run check:docs + # Build precedes typecheck deliberately. @workspacejson/rules typechecks # against @workspacejson/spec's EMITTED declarations, which `tsc --noEmit` # never produces. In the source monorepo this ordering was hidden by a # hand-written ambient `declare module '@workspacejson/spec'`; removing - # that stub (META-239 Phase 3) made the real dependency visible. + # that stub during the extraction made the real dependency visible. - name: Build all packages run: pnpm -r build @@ -52,7 +74,7 @@ jobs: # Standard owns the normative schema. These two gates prove the package # ships the canonical bytes and that every shipped example validates - # against them. See META-211. + # against them. - name: Verify schema provenance run: pnpm run check:schema diff --git a/AGENTS.md b/AGENTS.md index 4931d45..1c1a518 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -51,6 +51,6 @@ pnpm run release:verify-packs ## Publication This repository **cannot publish**. Authority for `@workspacejson/spec` and -`@workspacejson/rules` still belongs to `workspace-json/agents-audit` until -META-243. Do not add an npm secret, a publish step, or a release workflow — see -`.github/RELEASE-AUTHORITY.md`. +`@workspacejson/rules` still belongs to `workspace-json/agents-audit` until a +coordinated cutover transfers it. Do not add an npm secret, a publish step, or a +release workflow — see [`.github/RELEASE-AUTHORITY.md`](./.github/RELEASE-AUTHORITY.md). diff --git a/CHANGELOG.md b/CHANGELOG.md index ef82772..1ead18d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,61 @@ release family, including releases published from `workspace-json/agents-audit`. ## Unreleased +### Added — public readiness + +- Community and governance files: `SUPPORT.md`, `GOVERNANCE.md`, + `MAINTAINERS.md`, `.github/CODEOWNERS` and `.github/ISSUE_TEMPLATE/config.yml`. + Each states a real process and a real owner rather than adding ceremony. +- `docs/adr/` — the architecture decision records now live in this repository + instead of only in a private tracker. ADR-001 records the canonical artifact + path decision that shipped in `0.4.1` and had never been written down anywhere; + ADR-002 is transcribed from its internal draft with private references removed. +- `docs/versioning.md`, `docs/conformance.md`, `docs/glossary.md`, + `docs/troubleshooting.md` and `docs/repository-settings.md`. +- `scripts/check-docs.mjs` and `pnpm run check:docs`, wired into CI: every + relative link must resolve, every documented `pnpm run` command must exist, and + no internal tracker identifier may appear in public prose. +- `.editorconfig`, `.gitattributes` and `.github/dependabot.yml`. +- `assets/` — the brand lockups are vendored here instead of loaded from the + historical repository. + +### Changed — public surface + +- Internal tracker identifiers were removed from public prose across the README, + `OWNERSHIP.md`, `AGENTS.md`, `CONTRIBUTING.md`, `.github/RELEASE-AUTHORITY.md`, + the guard scripts, the CI comments and two package test descriptions. Every + explanation was kept; only the unresolvable identifiers were replaced. + `migration/` and `docs/adr/` remain exempt as provenance records. +- The README gained a status statement, a quickstart, a documentation map and a + known-limitations section. +- Both issue templates were rewritten — they still described `agents-audit` — and + now route out-of-scope reports to the correct repository. +- Package READMEs no longer describe themselves as published from the + `agents-audit` workspace. +- `ci.yml` declares least-privilege `permissions: contents: read` at both + workflow and job level, and cancels superseded non-`main` runs. +- The `aaif` keyword was removed from both packages: it implied a standards-body + status the project does not hold. +- `@workspacejson/rules` description and `homepage` now describe reference + behavior rather than the audit product. +- `author` on both packages reads `workspacejson contributors`, not the former + hyphenated organization name. + +### Fixed + +- Both package changelogs recorded `0.4.4` as `Unreleased` while npm had + published it on 2026-07-23. The registry is the arbiter; the dates are now + correct. + +### Deliberately not changed + +The schema `$id` host, the `v1.json` filename, package versions, the +`version`/`specVersion` dual-emit work, the four ambient interop shims, +publication capability and repository visibility. Each changes a contract or +requires an authority this pass does not hold. + +## Migration + ### Migrated - Extracted `@workspacejson/spec` and `@workspacejson/rules` from @@ -21,9 +76,9 @@ release family, including releases published from `workspace-json/agents-audit`. - **No package was renamed, version-bumped or published.** Both packages remain `0.4.4`, and publication authority remains with `workspace-json/agents-audit` - until META-243. + until a coordinated cutover. -### Changed — package metadata (META-201) +### Changed — package metadata - `repository.url` and `bugs.url` now point at `workspacejson/standard`, with a `repository.directory` pointer per package. @@ -47,7 +102,7 @@ release family, including releases published from `workspace-json/agents-audit`. - `scripts/check-architecture.mjs` — dependency-direction and clean-room guards, with deliberate-violation red tests in `scripts/check-architecture.test.mjs`. - `.github/RELEASE-AUTHORITY.md` — records why this repository ships no release - workflow, and what META-243 must create. + workflow, and what an authority transfer must create. - `scripts/verify-schema-provenance.mjs` — proves a single canonical schema that is exported, packed and complete. - `scripts/validate-examples.mjs` — every shipped example must validate against diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index fed0753..ab94394 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,6 +1,7 @@ # Code of Conduct -We expect all contributors, maintainers, and users to communicate professionally and respectfully. +We expect all contributors, maintainers and users to communicate professionally +and respectfully. ## Standards @@ -9,10 +10,28 @@ We expect all contributors, maintainers, and users to communicate professionally - Assume good intent - Keep feedback focused on the work, not the person +Technical disagreement is welcome and expected — this repository defines a +contract, and contracts improve under scrutiny. Disagreement about a decision is +not a conduct problem. How it is expressed can be. + ## Enforcement -If behavior is abusive, harassing, or disruptive, maintainers may remove comments, close issues, or restrict participation as needed. +If behavior is abusive, harassing or disruptive, maintainers may edit or remove +comments, close issues or pull requests, and restrict or block participation. + +Maintainers are listed in [`MAINTAINERS.md`](./MAINTAINERS.md). They are the +people who act on reports, and they are subject to this document on the same +terms as everyone else. ## Reporting -Use the security or issue channels in this repository if you need help raising a concern. +Raise a concern privately through this repository's +[GitHub security advisory form](https://github.com/workspacejson/standard/security/advisories/new), +which is visible only to maintainers. Use it for conduct reports as well as +vulnerabilities — it is the private channel this repository has. + +Include what happened, where, and any links. Reports are read by the maintainers +listed above. If your report concerns a maintainer, say so; it will not be +routed to them. + +Do not file a conduct report as a public issue. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ce9c677..d644dd2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,11 +9,18 @@ and its deterministic reference behavior. It publishes `@workspacejson/spec` and - Read [`OWNERSHIP.md`](./OWNERSHIP.md) — it states what belongs here and what belongs in `workspacejson/cli`, `workspacejson/integrations` or `workspacejson/site`. +- Read [`GOVERNANCE.md`](./GOVERNANCE.md) — changes to the normative surface need + an architecture decision record **merged before** implementation. Finding that + out after writing the code is the expensive way. - Read [`AGENTS.md`](./AGENTS.md) for entry points and the "do not change without an issue" list. - Keep changes within the owning package where possible. - Avoid changing package entry points unless the public surface changes. +New here? [`docs/troubleshooting.md`](./docs/troubleshooting.md) covers the +failure modes that surprise people on a first clean checkout — chiefly that you +must **build before you typecheck**. + ## Common commands ```bash @@ -26,9 +33,16 @@ pnpm run check:architecture # dependency direction + clean-room guards pnpm run check:architecture:test # deliberate violations must be rejected pnpm run check:schema # canonical schema provenance pnpm run check:examples # every shipped example must validate +pnpm run check:docs # links, documented commands, public prose pnpm run release:verify-packs # packed tarball gates ``` +`check:docs` will fail if you add a relative link that does not resolve, document +a `pnpm run` command that does not exist, or put an internal tracker identifier +into public prose. That last rule exists because a reader outside this +organization cannot resolve one; describe the work instead. Provenance records +under `migration/` and `docs/adr/` are exempt. + ## Changes that need extra care **The schema.** `packages/spec/schema/v1.json` is normative and is the single diff --git a/GOVERNANCE.md b/GOVERNANCE.md new file mode 100644 index 0000000..40f77b0 --- /dev/null +++ b/GOVERNANCE.md @@ -0,0 +1,94 @@ +# Governance + +This repository holds the normative definition of `workspace.json`. Its +governance exists to make one thing predictable: **what a consumer can rely on, +and what may change underneath them.** + +## Scope of authority + +This repository decides: + +- the normative JSON Schema and the types generated from it +- validation semantics, including legacy validation behavior +- deterministic reference behavior — parser, scanner, rule engine +- compatibility profiles and the stable read-path contract +- conformance fixtures and executable examples +- the architecture decision records in [`docs/adr/`](./docs/adr/) + +It does not decide how artifacts are produced, how hosts integrate, or how the +website presents any of it. Those live in `workspacejson/cli`, +`workspacejson/integrations` and `workspacejson/site` respectively. +[`OWNERSHIP.md`](./OWNERSHIP.md) states the full boundary; it is enforced by +`scripts/check-architecture.mjs` in CI rather than by convention. + +## Roles + +**Maintainers** review and merge changes, cut releases when this repository holds +publication authority, and are accountable for the compatibility guarantees +below. The current list is [`MAINTAINERS.md`](./MAINTAINERS.md). + +**Contributors** open issues and pull requests. No agreement or assignment is +required beyond the Apache-2.0 license the repository is under. + +There is no separate committer tier and no voting body. Pretending otherwise +would describe a process that does not exist. + +## How decisions are made + +Ordinary changes — bug fixes, documentation, tests, non-breaking additions — need +one maintainer approval and green CI. + +Changes to the **normative surface** need an architecture decision record before +implementation. The normative surface is: + +- the bytes of `packages/spec/schema/v1.json` +- the four stable read paths +- the public export surface of either package +- the descriptive-not-prescriptive and daemon-free properties + +An ADR is a file in [`docs/adr/`](./docs/adr/) recording context, the decision, +its boundaries, consequences and supersession rules. It is reviewed as a pull +request like any other change. [`docs/adr/README.md`](./docs/adr/README.md) +describes the format and status vocabulary. + +When maintainers disagree, the decision escalates to the ADR owner named in the +record. That is a real tiebreak with a real name on it, not a consensus ritual. + +## What is deliberately hard to change + +Two properties are load-bearing and are enforced in CI, not merely documented: + +**The standard is descriptive, never prescriptive.** `workspace.json` reports +what a repository *is*. Fields that encode what a team *must do* — approval +gates, merge blocking, enforcement policy — are rejected by the architecture +guard. Changing this requires an ADR that supersedes the property explicitly. + +**The committed file must remain useful without a daemon.** Nothing in the +standard may assume a background process is running. + +The four stable read paths — `manual.fragileFiles`, `manual.coChangePatterns`, +`generated.fileIndex`, `generated.frameworkManifest` — are treated as a breaking +change if removed or renamed, regardless of what the version number would +otherwise suggest. See [`docs/versioning.md`](./docs/versioning.md). + +## Publication authority + +**This repository currently cannot publish, by design.** Both packages are +published from the historical repository that this one was extracted from, which +holds the only credential. This repository has no npm secret and no release +workflow at all, and the architecture guard fails the build if a publish step or +credential reference appears in any workflow. + +Transferring authority is a separate, coordinated change that must revoke the old +authority in the same act — two repositories publishing the same package is the +specific failure this arrangement prevents. The full rationale and the checklist +for that transfer are in [`.github/RELEASE-AUTHORITY.md`](./.github/RELEASE-AUTHORITY.md). + +Do not add a credential, a publish step, or a release workflow to this repository +as a convenience. It will fail CI, and that is the intended behavior. + +## Changing this document + +Governance changes are pull requests against this file and require the same +maintainer approval as any other change. Changes that alter the normative surface +or the escalation path additionally require an ADR. diff --git a/MAINTAINERS.md b/MAINTAINERS.md new file mode 100644 index 0000000..a76bea0 --- /dev/null +++ b/MAINTAINERS.md @@ -0,0 +1,29 @@ +# Maintainers + +Maintainers review and merge changes to this repository and are accountable for +the compatibility guarantees described in [`GOVERNANCE.md`](./GOVERNANCE.md). + +| Maintainer | GitHub | Areas | +| -- | -- | -- | +| Qwynn Marcelle | [@qmarcelle](https://github.com/qmarcelle) | specification, schema, validation semantics, rules engine, governance, releases | + +This list is short because it is accurate. It reflects who actually reviews and +merges, not who is nominally associated with the project. + +`.github/CODEOWNERS` mirrors this list and is what GitHub uses to request +reviews automatically. The two must be kept in step. + +## Becoming a maintainer + +There is no application process. Maintainership follows sustained review-quality +contribution — reviewing others' changes, not only landing your own — and is +proposed by an existing maintainer in a pull request that updates both this file +and `.github/CODEOWNERS`. + +## Escalation + +Architecture decision records name an owner who breaks ties on that decision. See +[`docs/adr/README.md`](./docs/adr/README.md). + +For security reports, follow [`SECURITY.md`](./SECURITY.md) rather than +contacting a maintainer directly through a public channel. diff --git a/OWNERSHIP.md b/OWNERSHIP.md index 5bb5ca5..9fd7b6c 100644 --- a/OWNERSHIP.md +++ b/OWNERSHIP.md @@ -22,8 +22,8 @@ merely asserted here. This repository is currently **incapable** of publishing: it has no npm secret and ships no release workflow. See -[`.github/RELEASE-AUTHORITY.md`](./.github/RELEASE-AUTHORITY.md). Authority -transfer is META-243. +[`.github/RELEASE-AUTHORITY.md`](./.github/RELEASE-AUTHORITY.md), which records +what a coordinated authority transfer must do. ## Consumes @@ -68,10 +68,11 @@ packages. The reverse direction is prohibited. | Source repository | `workspace-json/agents-audit` | | Frozen source SHA | `e47eb1b8556c4f361db9a78190a2f36b400756e8` | | Extraction method | `git filter-repo` path filter, history preserved | -| Governing issue | META-239 (parent program META-237) | | Rollback ref | `workspace-json/agents-audit@e47eb1b8556c4f361db9a78190a2f36b400756e8` | -Full detail in [`migration/PROVENANCE.md`](./migration/PROVENANCE.md). +Full detail — including the governing issue identifiers, tree hashes, commit map +and intentional differences — is in +[`migration/PROVENANCE.md`](./migration/PROVENANCE.md). The historical repository remains unmodified and remains the publication -authority until META-243 completes. +authority until the coordinated cutover completes. diff --git a/README.md b/README.md index 1a22864..33efd61 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,22 @@ producer-generated repository metadata with evidence authored by humans or specialized tools. This repository is the **canonical source of the specification** and of the -deterministic reference behavior that interprets it. It is one of four: +deterministic reference behavior that interprets it. + +## Status + +**The format is at v0.4 and the packages are at `0.4.4`. It is usable and +published, and it is pre-1.0.** Four read paths are treated as a hard +compatibility floor and will not be removed or renamed without a recorded +decision. Everything outside that floor may still change. + +There is no external conformance suite yet, and the known gaps are listed +plainly in [`docs/conformance.md`](./docs/conformance.md). No adoption, +endorsement or standards-body status is claimed. + +## The four repositories + +This is one of four, with one-way ownership: | Repository | Owns | | -- | -- | @@ -37,7 +52,40 @@ violations tested in `scripts/check-architecture.test.mjs`. | [`@workspacejson/rules`](./packages/rules) | `0.4.4` | AGENTS.md parser, repository scanner, workspace.json validator integration, and the deterministic rule engine | Published versions are **registry-defined**. The versions above describe the -current release family; the registry remains the source of truth. +current release family; the registry remains the source of truth: + +```bash +npm view @workspacejson/spec version +npm view @workspacejson/rules version +``` + +Both packages are released as a fixed group, so they always carry the same +version number. + +## Quickstart + +Validate a `workspace.json` document without cloning anything: + +```bash +npm install @workspacejson/spec +npx workspacejson-spec validate .agents/workspace.json +``` + +The command exits `0` on a valid document and non-zero otherwise. `validate +` is its only command — there is no `--help` flag. + +To use the schema and types directly: + +```ts +import { validate, validateV4 } from '@workspacejson/spec'; +import schema from '@workspacejson/spec/schema' with { type: 'json' }; + +validate(doc); // true for a valid v0.3 or v0.4 document +validateV4(doc); // true for a valid v0.4 document +``` + +This repository defines the format; it does not generate the artifact. Producing +`.agents/workspace.json` belongs to `workspacejson/cli`. ## Two properties that are load-bearing @@ -91,9 +139,15 @@ pnpm run check:architecture # dependency direction + clean-room guards pnpm run check:architecture:test # deliberate violations must be rejected pnpm run check:schema # canonical schema provenance pnpm run check:examples # every shipped example must validate +pnpm run check:docs # links, documented commands, public prose pnpm run release:verify-packs # packed tarball gates ``` +Build before typecheck. `@workspacejson/rules` typechecks against +`@workspacejson/spec`'s **emitted declarations**, which `tsc --noEmit` never +produces — CI uses the same order deliberately. See +[`docs/troubleshooting.md`](./docs/troubleshooting.md). + `@workspacejson/rules` depends on `@workspacejson/spec` via `workspace:*`. That is correct here: both packages live in **this one** pnpm workspace, and `pnpm pack` rewrites the protocol to the exact version before publication. It is an @@ -108,8 +162,8 @@ proves no `workspace:` protocol ever reaches a packed manifest. `workspace-json/agents-audit`, which holds the credential. This repository has no npm secret and **no release workflow at all** — see [`.github/RELEASE-AUTHORITY.md`](./.github/RELEASE-AUTHORITY.md). Transferring -authority is tracked as META-243 and must revoke the old authority in the same -change. +authority is a separate coordinated change that must revoke the old authority in +the same act. ## Provenance @@ -119,6 +173,52 @@ standard-owned paths. See [`migration/PROVENANCE.md`](./migration/PROVENANCE.md) for the exact command, included and excluded paths, tree hashes, commit map and rollback reference. +## Documentation + +| Document | What it answers | +| -- | -- | +| [Versioning and compatibility](./docs/versioning.md) | What may I rely on, and what may change? | +| [Conformance](./docs/conformance.md) | How do I check an implementation — and what is not yet covered? | +| [Troubleshooting](./docs/troubleshooting.md) | Why did that fail, and is it deliberate? | +| [Glossary](./docs/glossary.md) | What does this term mean here? | +| [Architecture decision records](./docs/adr/) | Why is it this way, and who decided? | +| [Repository settings](./docs/repository-settings.md) | What configuration is intended, and what is actually set? | + +## Project + +| | | +| -- | -- | +| [`CONTRIBUTING.md`](./CONTRIBUTING.md) | How to propose and land a change | +| [`GOVERNANCE.md`](./GOVERNANCE.md) | How decisions are made and what needs an ADR | +| [`MAINTAINERS.md`](./MAINTAINERS.md) | Who reviews and merges | +| [`OWNERSHIP.md`](./OWNERSHIP.md) | What this repository owns and must never define | +| [`SUPPORT.md`](./SUPPORT.md) | Where to ask, and what response to expect | +| [`SECURITY.md`](./SECURITY.md) | How to report a vulnerability privately | +| [`CODE_OF_CONDUCT.md`](./CODE_OF_CONDUCT.md) | Expected conduct | + +## Known limitations + +Stated here rather than discovered later: + +- **No external conformance suite.** An independent implementation cannot yet run + a standard battery to claim conformance. See + [`docs/conformance.md`](./docs/conformance.md) for the full gap list, including + the absence of negative examples and of a shipped legacy-profile fixture. +- **This repository cannot publish.** Both packages are released from the + historical repository, which holds the only credential. That is deliberate and + enforced in CI. +- **The schema `$id` host disagrees with the canonical domain.** The schema + declares the `www.` host while the package manifests use the bare domain. Both + serve the schema, so nothing is broken, but the strings differ. Reconciling + them changes schema bytes and is tracked as a normative change. +- **`v1.json` is a legacy filename**, not a claim that the format is at 1.0. +- **Four ambient interop shims are retained** in `types/ambient.d.ts` for + `simple-git`, `remark` and `ajv`. They are real CJS/ESM mismatches in + third-party packages, tracked as their own work rather than papered over. +- **The repository is private and on a plan without branch protection.** Both + constraints and the gate they create before any publication authority are + recorded in [`docs/repository-settings.md`](./docs/repository-settings.md). + ## License -[Apache-2.0](./LICENSE). +[Apache-2.0](./LICENSE). Copyright the workspacejson contributors. diff --git a/SECURITY.md b/SECURITY.md index bc48fc1..ba5af86 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,12 +1,32 @@ # Security If you discover a security issue in `@workspacejson/spec`, `@workspacejson/rules`, -or the workspace.json specification itself, please report it privately through -this repository's GitHub security advisory workflow. +or the workspace.json specification itself, report it privately through +[this repository's security advisory form](https://github.com/workspacejson/standard/security/advisories/new). Do not open a public issue for a sensitive vulnerability before coordinated disclosure. +> **Maintainer note — this channel is not live yet.** GitHub private +> vulnerability reporting cannot be enabled while this repository is private on +> its current plan, so the link above does not resolve today. Nobody outside the +> organization can read this file yet either, so no report is currently being +> dropped. **Enabling private vulnerability reporting is a required step of +> making this repository public** — see +> [`docs/repository-settings.md`](./docs/repository-settings.md). This note is +> removed when that is done. + +## Supported versions + +| Version | Supported | +| -- | -- | +| `0.4.x` | Yes — the current release family | +| `< 0.4` | No | + +Both packages are released as a fixed group and always carry the same version. +Fixes are made on the current release family; there is no long-term-support +branch, and claiming one would be inaccurate. + ## What to include - a short summary of the issue diff --git a/SUPPORT.md b/SUPPORT.md new file mode 100644 index 0000000..ffe4436 --- /dev/null +++ b/SUPPORT.md @@ -0,0 +1,69 @@ +# Support + +This repository is the canonical source of the **workspace.json specification** +and its deterministic reference behavior. It publishes +[`@workspacejson/spec`](./packages/spec) and [`@workspacejson/rules`](./packages/rules). + +There is no paid support channel and no service-level commitment. What follows is +the honest description of what you can expect. + +## Where to ask + +| You want to | Go to | +| -- | -- | +| Report a bug in the schema, types, validation or rule engine | [GitHub Issues](https://github.com/workspacejson/standard/issues) — bug report form | +| Propose a specification or API change | [GitHub Issues](https://github.com/workspacejson/standard/issues) — feature request form | +| Report a security vulnerability | [`SECURITY.md`](./SECURITY.md) — **not** a public issue | +| Understand what belongs here versus elsewhere | [`OWNERSHIP.md`](./OWNERSHIP.md) | +| Understand how decisions get made | [`GOVERNANCE.md`](./GOVERNANCE.md) | +| Fix something yourself | [`CONTRIBUTING.md`](./CONTRIBUTING.md) | + +Discussions are not enabled. Issues are the single intake channel, which keeps +every question answerable in one place. + +## This is the wrong repository for + +Ownership boundaries are enforced mechanically here, so filing in the wrong place +costs you a round trip: + +| Symptom | Correct repository | +| -- | -- | +| `.agents/workspace.json` is not generated, or generated wrongly | `workspacejson/cli` | +| A repository scan produced bad or missing data | `workspacejson/cli` | +| MCP, Codex or VS Code integration misbehaves | `workspacejson/integrations` | +| Documentation on `workspacejson.dev` is wrong or stale | `workspacejson/site` | + +If a validation error is *reported by* a producer but the schema itself is wrong, +it belongs here. When in doubt, file here and it will be redirected. + +## Before you file + +These four commands resolve most reports without an issue: + +```bash +pnpm install +pnpm -r build # build precedes typecheck — see CONTRIBUTING.md +pnpm run check:schema # prints the canonical schema path, byte length and SHA-256 +pnpm run check:examples # validates every shipped example against that schema +``` + +[`docs/troubleshooting.md`](./docs/troubleshooting.md) covers the failure modes +that come up most often, including the build-before-typecheck ordering and +`workspace:` protocol questions. + +## What to include + +- the package and version, from `npm ls @workspacejson/spec @workspacejson/rules` +- your Node version — the packages declare `node >=20` +- the smallest `workspace.json` document that reproduces the problem +- the exact command and its complete output + +## Response expectations + +This project is maintained by a small number of people, listed in +[`MAINTAINERS.md`](./MAINTAINERS.md), alongside other work. Issues are read; +they are not guaranteed a fixed response time. Security reports are prioritized +over everything else. + +Stating that plainly is more useful than a service-level promise nobody is +staffed to keep. diff --git a/assets/README.md b/assets/README.md new file mode 100644 index 0000000..456de7d --- /dev/null +++ b/assets/README.md @@ -0,0 +1,34 @@ +# Assets + +Brand assets used by this repository's documentation. + +| File | Dimensions | Used by | +| -- | -- | -- | +| `workspace-json-lockup-light.png` | 1520 × 360 | Package READMEs, light interfaces | +| `workspace-json-lockup-dark.png` | 1520 × 360 | Package READMEs, dark interfaces | + +Both are referenced through a `` element with a +`prefers-color-scheme: dark` source, so the lockup stays legible in either +theme, with the light variant as the fallback ``. + +## Why these are vendored + +The package READMEs previously loaded these images from the historical +repository this one was extracted from. That made this repository's rendered +documentation depend on a repository that is being frozen — a cross-repository +reference that would break the moment its visibility or default branch changed. + +Vendoring them removes that dependency. The images are referenced by +repository-relative path, which GitHub resolves directly. + +**Known limitation.** npm resolves relative image paths in a README against the +`repository` field of the package manifest. That resolution requires the +repository to be publicly readable. Until this repository's visibility changes — +a separate authority action — the lockup will not render on the npm package +pages, though it renders correctly on GitHub. This is recorded rather than +worked around, because the alternatives are worse: an absolute URL to a frozen +repository reintroduces the dependency, and a URL to this repository resolves to +nothing while it is private. + +These assets are not included in either package tarball. Neither package's +`files` list covers `assets`, so they add nothing to published package weight. diff --git a/assets/workspace-json-lockup-dark.png b/assets/workspace-json-lockup-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..156d75e08128b060fe56dbade9f855d79f20ddcc GIT binary patch literal 48869 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 Vnzz_2FM=mDm(+FCvd)|P{~!BDNt*xw literal 0 HcmV?d00001 diff --git a/assets/workspace-json-lockup-light.png b/assets/workspace-json-lockup-light.png new file mode 100644 index 0000000000000000000000000000000000000000..ce9cc43d765e02c455bc8b80732beddb851a6f82 GIT binary patch literal 49807 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

>?xR!s`= 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 literal 0 HcmV?d00001 diff --git a/docs/adr/001-canonical-artifact-path.md b/docs/adr/001-canonical-artifact-path.md new file mode 100644 index 0000000..020e78d --- /dev/null +++ b/docs/adr/001-canonical-artifact-path.md @@ -0,0 +1,150 @@ +# ADR-001: Canonical artifact path + +| Field | Value | +| -- | -- | +| **Status** | Accepted | +| **Decision date** | 2026-06-02 — shipped in `@workspacejson/spec@0.4.1` | +| **Record written** | 2026-07-26 | +| **Owner** | Qwynn Marcelle ([@qmarcelle](https://github.com/qmarcelle)) | +| **Supersedes** | Nothing | +| **Superseded by** | Nothing | +| **Depended on by** | [ADR-002](./002-bounded-enrichment-program.md) | +| **Spec version at decision** | v0.4 | + +## Context + +`workspace.json` is a *committed* artifact. Every consumer — a producer writing +it, an agent reading it, a validator checking it, a reviewer diffing it — has to +agree on where the file is before anything else about the format matters. A +format with a negotiable location is not portable. + +The location moved twice before settling, and both moves are visible in +[`packages/spec/CHANGELOG.md`](../../packages/spec/CHANGELOG.md): + +| Release | On-disk path | Note | +| -- | -- | -- | +| v0.1 – v0.2.0 | `agents.workspace.json` at the repository root | Later recorded as incorrect | +| v0.2.x | `.agents/agents.workspace.json` | Corrected write path; root form retained as a read fallback | +| **v0.4.1** | **`.agents/workspace.json`** | Current canonical path | + +By v0.4 the cost of the churn was concrete. Three forms were live across +different surfaces, so a reader following one document could not find the file +another document described. On the single artifact the whole standard hangs on, +that reads as unreliability rather than as flexibility. + +A naming distinction was also being conflated: the name of the *standard* — what +implementers say they support — is not the name of the *file on disk*. Treating +those as one question is what produced three candidates instead of two answers. + +## Decision + +**The canonical on-disk path is `.agents/workspace.json`, relative to the +repository root.** + +The standard is named `workspace.json`. The artifact it defines lives at +`.agents/workspace.json`. These are deliberately different scopes: the first +names a format, the second names a location in a working tree. + +Rationale for the location: + +1. **`.agents/` is the established directory for agent-facing repository + metadata**, alongside `AGENTS.md` conventions. Placing the artifact there + groups it with material of the same kind instead of adding another entry at + the repository root. +2. **The `agents.` prefix is redundant inside `.agents/`.** It restates the + directory in the filename and lengthens every reference without adding + disambiguation. This is the reason recorded at the time of the v0.4.1 change. +3. **A bare root `workspace.json` collides with an existing, widely indexed + meaning** — it is also the name of a deprecated build-tool configuration file. + A root-level file of that name invites misidentification by humans and tooling + alike. The collision does not apply once the file is namespaced by `.agents/`. + +### Read compatibility + +Since v0.4.1 the standard has stated that generators write to +`.agents/workspace.json` and that the previous path, +`.agents/agents.workspace.json`, **remains a valid read fallback**. That +allowance is part of this decision, not an exception to it. A consumer may accept +a document found at the legacy path; a producer may not write there. + +No deprecation date is set for the read fallback. When one is, it requires its +own record — removing it is a breaking change for any repository that still +carries the older filename. + +## Boundaries + +This decision covers the location, the format name, and the read fallback. It +does **not** cover: + +- **Producer behavior.** How the file is generated, by what command, and how + often is owned by `workspacejson/cli`, not by this repository. +- **Discovery beyond the canonical path and the recorded fallback.** A consumer + may accept an explicit path argument. It may not invent further default + locations. +- **Vendor-specific sidecar views.** Some tools maintain derived views alongside + the canonical artifact, conventionally `.agents/workspace..json`. Those + are **not** part of this standard, carry no compatibility guarantee here, and + must never be required in order to read the canonical file. +- **The schema's `$id` host.** The identifier declared inside the schema document + is a separate canonicalization question, tracked independently. It is not + settled by this record, and changing it changes schema bytes. + +## Consequences + +- Every surface this repository owns — schema, shipped examples, package + documentation and reference behavior — uses `.agents/workspace.json`. That is + true as of this record. +- Consumers may resolve the path relative to the repository root rather than + treating it as per-tool configuration. +- Documents still using an older form are wrong, not alternative. Normalizing + them is a documentation change, not a compatibility break, because the read + fallback keeps existing repositories readable. +- The cost accepted: the artifact sits one directory below the root and is + therefore slightly less discoverable to a human browsing the repository. That + is traded for grouping with other agent-facing metadata and for avoiding the + root-level name collision. + +## Open questions + +This record states what is in force. Two related questions are genuinely still +open and are **not** decided here: + +1. **Public copy normalization.** Surfaces outside this repository — notably the + published website and its documentation — have historically used more than one + form. Bringing them to the canonical string is outstanding work owned by those + repositories. +2. **The status of vendor sidecar views.** Whether `.agents/workspace..json` + should be described by the open standard at all, or left entirely as vendor + plumbing, is unresolved. Until it is resolved the standard says nothing about + it, which is the conservative position. + +Neither open question changes the canonical path. + +## Supersession + +Replace this record if the `.agents/` convention is abandoned by the broader +agent-metadata ecosystem, or if a location change becomes necessary for +interoperability with a standard this one adopts. + +Any replacement must state a migration path for repositories that already carry a +committed artifact at the current path. Changing the canonical location is a +breaking change for every consumer regardless of what the version number would +otherwise suggest — see [`docs/versioning.md`](../versioning.md). + +## Provenance + +The decision itself is dated **2026-06-02** and shipped in +`@workspacejson/spec@0.4.1`, which is published on npm. Its original record is +the v0.4.1 entry in [`packages/spec/CHANGELOG.md`](../../packages/spec/CHANGELOG.md), +with the corresponding consumer-side change recorded in +[`packages/rules/CHANGELOG.md`](../../packages/rules/CHANGELOG.md). + +This ADR was written on 2026-07-26 during the public-readiness pass on +`workspacejson/standard` (internal tracker: META-246), which found that +[ADR-002](./002-bounded-enrichment-program.md) declared a dependency on an +ADR-001 that had never been committed anywhere. The record does not make a new +decision; it makes an existing, shipped, implemented one citable by public +implementers instead of leaving them to infer it from a changelog. + +The outstanding cross-surface normalization described under **Open questions** is +tracked internally as META-199. diff --git a/docs/adr/002-bounded-enrichment-program.md b/docs/adr/002-bounded-enrichment-program.md new file mode 100644 index 0000000..4d527f3 --- /dev/null +++ b/docs/adr/002-bounded-enrichment-program.md @@ -0,0 +1,305 @@ +# ADR-002: Bounded enrichment program for workspace.json + +| Field | Value | +| -- | -- | +| **Status** | Proposed | +| **Decision date** | 2026-07-25 | +| **Record written** | 2026-07-26 | +| **Owner / Decider** | Qwynn Marcelle ([@qmarcelle](https://github.com/qmarcelle)) | +| **Supersedes** | Nothing | +| **Amends** | A prior enrichment draft — execution sections only; its architecture was retained | +| **Depends on** | [ADR-001](./001-canonical-artifact-path.md) | +| **Spec version** | v0.4 | + +> **Status note.** This record is `Proposed`: the measurement program it defines +> has not run, and no result from it may be cited. Two of its sections are +> nonetheless already in force through separate ratification — the four-repository +> topology and its dependency direction, and the v0.4 compatibility floor. Those +> are enforced by `scripts/check-architecture.mjs` in CI today. The rest of this +> record is a plan, and is labelled as one. + +## Context + +The claim under test is narrow and falsifiable: + +> A canonical committed artifact reduces an agent's discovery work on real +> repository questions without inducing unsupported certainty. + +Both halves are load-bearing. A cheaper answer that is less correct, or that +produces confident answers the evidence does not support, is a failure — not a +partial success. Enrichment programs usually fail by optimizing the first half +and never measuring the second. + +## Decision + +Adopt a bounded vertical-slice program: + +> One signal. One reader. One consumer. Repeated runs. One deliberately wrong +> run. One bounded decision. + +Execution uses the four-repository topology: + +| Repository | Responsibility under this ADR | +| -- | -- | +| `workspacejson/standard` | normative contract, schema and rules, ADRs, reference reader semantics, compatibility profiles, conformance fixtures | +| `workspacejson/cli` | neutral producer, repository scanning, deterministic artifact generation, CLI execution and distribution | +| `workspacejson/integrations` | host-specific consumer integration — MCP, Codex, editor and other adapters | +| `workspacejson/site` | later publication of pinned docs, examples and evidence summaries; no experimental implementation authority | + +Dependency direction: + +```text +standard + ↓ +cli integrations + \ / + site +``` + +`standard` imports from none of the others. `cli` and `integrations` consume +released or commit-pinned standard contracts. `integrations` may invoke the +public CLI interface. `site` assembles pinned outputs and never becomes an +editable second source of normative truth. + +No `workspacejson/*` repository imports or requires proprietary vendor source. + +## Stable surface + +The v0.4 compatibility floor is unchanged by this program: + +```text +manual.fragileFiles +manual.coChangePatterns +generated.fileIndex +generated.frameworkManifest +``` + +Changes to these read paths are breaking regardless of semver optics. See +[`docs/versioning.md`](../versioning.md). + +## Measurement design + +### Metrics + +| Class | Metric | Direction | +| -- | -- | -- | +| Primary cost | Tool calls to first complete answer; total input tokens | Lower | +| Co-primary calibration | Correctness against labeled ground truth | Higher | +| Co-primary calibration | Correct refusal of unsupported-safety prompts | Higher | +| Secondary | Wall-clock | Report only | + +A lower-cost but less-correct result is a failure. + +### Arms + +| Arm | Artifact state | Purpose | +| -- | -- | -- | +| A | Absent | Baseline | +| B | Present and correct | Treatment | +| C | Present and deliberately corrupted | Perturbation and calibration test | + +Corrupted generated evidence and corrupted maintainer assertions are separately +attributable fixture changes, so a calibration failure can be traced to which +kind of evidence misled the reader. + +### Protocol + +- Fixed repository revision, model and settings, tools and prompts. +- Fresh session per run. +- Randomized arm order where possible. +- Five runs per arm per eligible question. +- Raw transcripts and observations retained. + +### Decision rule + +Classify every corpus entry as cost-eligible, calibration-only or refusal-only +**before** execution. A pass requires all of: + +- treatment cost below baseline on at least 7 of 8 cost-eligible questions, or + the pre-registered eligible denominator; +- no correctness regression; +- no refusal-fidelity regression; +- adversarial data perturbs cost or correctness. + +No magnitude or percentage claim is permitted from this program. + +## Evidence tiers + +| Tier | Meaning | Permitted claim | +| -- | -- | -- | +| REPORTED | Originating team produced and evaluated | Continue internal development | +| REPLICATED | Same team repeated a controlled harness | Internal roadmap commitment | +| EXTERNALLY OBSERVED | A collaborator-maintained consumer demonstrated the behavior | External communication | +| INDEPENDENTLY REPRODUCED | A separate implementation or evaluator reproduced it | Stable-core promotion; standards-body path | + +Demonstrations are labeled `DEMONSTRATION — not evidence` and never counted as a +tier. + +## Budget and stop conditions + +- Five engineering days maximum. +- Eight questions and three refusals maximum. +- Two existing generated paths plus one Git-derived experimental observation + maximum. +- Two reader iterations maximum. + +Stop if any of the following holds: + +1. The evidence packet is incomplete after five engineering days. +2. Sign consistency fails. +3. Correctness or refusal fidelity regresses. +4. Adversarial data produces no movement. +5. Reading the artifact costs as much as direct discovery. +6. The Git-derived observation changes no real consumer answer. + +On stop: keep the producer small and deterministic, keep the next minor version +compatibility- and governance-only, and defer external ingestion and provider-SDK +work. + +## Execution ownership + +### Compatibility and registry reconciliation + +`workspacejson/standard` owns schema and type compatibility for the top-level +`version` and `specVersion` fields, their equality and conflict semantics, the +compatibility fixtures, and standard-owned package releases. + +`workspacejson/cli` owns producer emission of both fields, producer package and +command identity, and its own release and registry verification. + +A coordinated patch release may span both repositories, but **no package has two +publishing authorities**. + +### Harness and corpus + +Harness orchestration may live with the experiment runner. Fixtures and expected +contract behavior belong in `workspacejson/standard`. Host-specific execution +adapters belong in `workspacejson/integrations`. + +### Minimal producer — `workspacejson/cli` + +- deterministic `generated.fileIndex`; +- deterministic `generated.frameworkManifest`; +- one Git-derived experimental observation; +- a minimum basis of `{ revision, producerVersion, algorithmVersion, inputDigest }`. + +The Git-derived result stays harness-side or explicitly experimental unless an +existing compatible generated field is proven. + +### Smallest reader and one consumer + +`workspacejson/standard` owns the reference reader contract: + +1. Parse the four stable paths. +2. Preserve human-versus-machine provenance. +3. Surface missing evidence as missing. +4. Answer one structured question. +5. Refuse unsupported safety conclusions. + +`workspacejson/integrations` owns the real consumer integration: routing one real +host query through the reference behavior, keeping host-specific adapters outside +`standard`, and preserving a measurable fallback to direct repository discovery. + +### Execute and adjudicate + +Run the full matrix across produced artifacts and integration consumers, retain +raw observations, report X/Y counts and distributions, and produce a REPORTED or +REPLICATED evidence packet. + +### Downstream discovery + +After the gate above and after compatibility support lands, measure a real +downstream consumer's actual use of each stable path. Record confirmed read, +confirmed unread, or unable to determine. Confirm the top-level `version` gate +fires. + +### Structural split and cutover + +Independently unblocked: + +- establish the four repositories; +- split history and code by ownership; +- enforce repository dependency direction; +- revoke old publish authority; +- make historical implementation repositories read-only; +- prohibit reverse merges. + +### Behavioral convergence + +Requires the downstream-discovery step above: + +- remove or alter legacy producer behavior; +- change effective stable-path contents; +- retire compatibility shims; +- consolidate competing representations; +- migrate downstream consumers. + +## Initial corpus + +1. Which files are represented in repository scope? +2. Which frameworks are represented? +3. Which candidate package roots should be inspected for a specified file? *(search-narrowing)* +4. Which manifest should be inspected for the applicable package test command? *(search-narrowing)* +5. Which files are declared fragile? +6. Which co-change relationships are maintainer-declared? +7. Which files historically co-change with one selected file? *(experimental Git observation)* +8. What evidence is absent or unavailable? *(calibration)* + +Refusals: + +1. Is this file safe to modify? +2. Will this change pass CI? +3. Is this file AI-authored? + +## Boundaries + +This record does **not** authorize: + +- generalized basis identity; +- a generic evidence envelope; +- external artifact ingestion; +- knowledge-graph ingestion; +- a provider SDK; +- a 20–30 question corpus; +- stable placement of new derived observations. + +Each is deferred behind the gate above. A deferred item is not a roadmap +commitment. + +## Consequences + +- The program is small enough to abandon. That is the point: the stop conditions + are stated before execution so a null result is a legitimate outcome rather + than a reason to widen scope. +- No percentage or magnitude claim can be made from this program at all, in any + venue, regardless of how favorable the result looks. +- Historical repositories and package names are migration inputs and + compatibility constraints. They do not dictate the target architecture. + +## Supersession + +Supersede this record when the program reaches its gate — pass or stop. The +superseding record must state the outcome, the evidence tier reached, and what +the result licenses. A program that quietly expires without a superseding record +is a governance failure, not a neutral outcome. + +## Provenance + +Drafted 2026-07-25 and transcribed here on 2026-07-26 during the public-readiness +pass on `workspacejson/standard`. Its original location was the project's +internal tracker (Linear document *ADR-002 · Bounded Enrichment Program for +workspace.json*; internal tracker references META-246, and the compatibility work +described above is tracked as META-235). + +The transcription is faithful in substance. Three classes of change were made so +that a public implementer can read it without access to a private system: + +1. Internal issue identifiers were replaced with descriptions of the work. +2. Private product and consumer names were replaced with role descriptions + (for example, "a real downstream consumer"). +3. Registry state recorded at drafting time was removed rather than frozen into a + permanent record; npm is the arbiter of published versions, and a snapshot in + an ADR goes stale the moment it is written. Current published versions are + stated in the root [`README.md`](../../README.md). + +No decision, boundary, metric, threshold or stop condition was altered. diff --git a/docs/adr/README.md b/docs/adr/README.md new file mode 100644 index 0000000..314c517 --- /dev/null +++ b/docs/adr/README.md @@ -0,0 +1,62 @@ +# Architecture decision records + +This directory is the authoritative home of the decisions that shape +`workspace.json`. If a decision constrains what implementers may rely on, it is +recorded here — not in a chat log, not in an issue tracker, and not only in +someone's memory. + +That matters because the standard is meant to be implemented by people outside +this organization. A public implementer cannot be asked to depend on access to a +private project-management system, so any decision that binds them lives in this +repository under version control. + +## Index + +| ADR | Title | Status | Date | +| -- | -- | -- | -- | +| [001](./001-canonical-artifact-path.md) | Canonical artifact path | Accepted | 2026-07-26 | +| [002](./002-bounded-enrichment-program.md) | Bounded enrichment program | Proposed | 2026-07-25 | + +## When an ADR is required + +[`GOVERNANCE.md`](../../GOVERNANCE.md) defines the normative surface. A change to +any of the following needs an ADR merged before implementation: + +- the bytes of `packages/spec/schema/v1.json` +- the four stable read paths +- the public export surface of `@workspacejson/spec` or `@workspacejson/rules` +- the descriptive-not-prescriptive property +- the daemon-free property + +Everything else — bug fixes, additive non-breaking work, documentation, tests — +proceeds through ordinary review. + +## Status vocabulary + +| Status | Meaning | +| -- | -- | +| **Proposed** | Recorded and under review. Implementers must not rely on it. | +| **Accepted** | In force. Implementations and this repository conform to it. | +| **Superseded** | Replaced by a later ADR, which is named in the record. Kept for history. | +| **Rejected** | Considered and declined. Kept so the reasoning is not relitigated. | + +An ADR is never deleted or silently edited after acceptance. It is superseded by +a new record that names it explicitly, so the history of a decision stays +readable. + +## Format + +Number files sequentially: `NNN-short-kebab-title.md`. Each record carries a +metadata table (status, date, owner, dependencies) followed by: + +- **Context** — the situation that forced a decision, including what was already + true and what was in tension +- **Decision** — what was decided, stated so an implementer can act on it +- **Boundaries** — what the decision does *not* cover, which is usually the part + that gets misread +- **Consequences** — what follows, including the costs accepted +- **Supersession** — the conditions under which this record should be replaced +- **Provenance** — where the decision came from, for auditability + +The owner named in the metadata breaks ties if maintainers disagree about the +decision's application. diff --git a/docs/conformance.md b/docs/conformance.md new file mode 100644 index 0000000..979ad4e --- /dev/null +++ b/docs/conformance.md @@ -0,0 +1,168 @@ +# Conformance + +This document describes how to check that an implementation conforms to +`workspace.json`, and what this repository verifies about itself. + +It is deliberately explicit about what is **not** yet covered. A conformance +document that implies more coverage than exists is worse than none. + +## What conformance means here + +There are two roles, with different obligations. + +**A producer** writes `.agents/workspace.json`. It conforms when: + +- it writes to the canonical path — see [ADR-001](./adr/001-canonical-artifact-path.md); +- its output validates against the packaged schema; +- it declares `generated.specVersion` matching the profile it emits; +- it preserves the `manual` section verbatim across regeneration, replacing only + the producer-owned `generated`, `agents` and `health` sections; +- it writes only when its material projection changes — timestamps identify the + last material generation, not the last command invocation. + +**A consumer** reads the artifact. It conforms when: + +- it validates before reading, rather than trusting shape; +- it treats the four stable read paths as its safe surface; +- it distinguishes human-authored `manual` evidence from machine-generated + `generated` evidence and does not silently merge them; +- it surfaces missing evidence as missing rather than as a negative finding. + +That last point is the one implementations most often get wrong. An absent +`manual.fragileFiles` means the maintainer declared nothing, not that no file is +fragile. + +## Checking a document + +The packaged validator is the reference implementation. Do not re-implement it — +a second validator is a second source of truth, and they drift. + +```bash +npm install @workspacejson/spec +npx workspacejson-spec validate path/to/workspace.json +``` + +The binary exits `0` on a valid document and non-zero otherwise. It has exactly +one command, `validate `; there is no `--help` flag, and any other +invocation exits non-zero with usage. + +Programmatically: + +```ts +import { validate, validateV4, validateLegacy } from '@workspacejson/spec'; + +validate(doc); // true for a valid v0.3 or v0.4 document +validateV4(doc); // true for a valid v0.4 document +validateLegacy(doc); // true for a legacy v0.1/v0.2 document +``` + +To pin the schema in your own test suite, materialize it from the package rather +than copying it: + +```ts +import schema from '@workspacejson/spec/schema' with { type: 'json' }; +``` + +## Fixtures shipped by this repository + +Four executable examples live in +[`packages/spec/examples/`](../packages/spec/examples/): + +| Example | Profile | +| -- | -- | +| `minimal-v0.3.json` | v0.3 | +| `populated-v0.3.json` | v0.3 | +| `with-manual-block-v0.3.json` | v0.3 | +| `populated-v0.4.json` | v0.4 | + +Every one of them is validated against the package-owned schema in CI by +`pnpm run check:examples`, using the package's own validator rather than a +re-implementation. The gate fails if the examples directory is empty, so it +cannot pass vacuously. + +If an example contradicts the schema, the fix is to the example. Weakening the +schema to make an example pass is explicitly prohibited in the gate's own +failure message. + +The rule engine additionally ships fixtures under +[`packages/rules/src/testing/fixtures/`](../packages/rules/src/testing/fixtures/): +`AGENTS.md` documents covering eight repository shapes, and three miniature +repositories — a clean TypeScript project, a TypeScript monorepo and a Python +package — used by the scanner and rule tests. + +`@workspacejson/rules` exports a `RuleTester` from its `./testing` entry point +for authoring rule tests against those fixtures. + +## Verifying the schema you received + +```bash +pnpm run check:schema +``` + +This prints the canonical path, byte length, SHA-256, `$id`, `$schema` and the +resolved `./schema` export, then asserts that: + +1. the canonical schema exists at exactly one path; +2. `exports["./schema"]` resolves to that same file; +3. the packed tarball includes it — `files` covers `schema`; +4. all four stable read paths are present. + +Measured on the current `main`: + +```text +path packages/spec/schema/v1.json +bytes 6220 +sha256 7f1635bbeff47b103566866d1b66c47a604f91bb3948ad2b59a3ba9369a41e36 +$id https://www.workspacejson.dev/schema/v1.json +$schema https://json-schema.org/draft/2020-12/schema +``` + +Those values are a snapshot, not a guarantee. Re-run the command against the +version you actually installed — that is what pinning means. + +Note the `$id` host disagrees with the bare canonical domain used in the package +manifests. Both hosts serve the schema. Reconciliation changes schema bytes and +is tracked separately; see [`docs/versioning.md`](./versioning.md). + +## What this repository verifies about itself + +CI runs on Node 20 and 22. In order: + +| Gate | Command | What it proves | +| -- | -- | -- | +| Architecture and clean-room guards | `check:architecture` | No cross-repository dependency, no proprietary reference, one schema copy, no publish capability | +| Guard red tests | `check:architecture:test` | Each guard rejects a deliberate violation, plus a baseline case proving the guards accept a clean tree | +| Build | `pnpm -r build` | Both packages compile and emit declarations | +| Typecheck | `pnpm -r typecheck` | Types resolve against emitted declarations | +| Tests | `pnpm -r test` | Unit and integration suites | +| Tarball verification | `release:verify-packs` | No `workspace:` protocol reaches a packed manifest | +| Schema provenance | `check:schema` | The four assertions above | +| Executable examples | `check:examples` | Every shipped example validates | +| Export validation | inline in CI | Each declared export resolves and imports | +| Binary behavior | inline in CI | `validate` succeeds on a valid document; a bare invocation exits non-zero | + +The guard red tests deserve emphasis. A guard that rejected everything would look +identical to a working guard from a green build, so the suite includes a baseline +case asserting that the unmodified repository is *accepted*. Coverage without +that case is not evidence. + +## Known gaps + +Stated plainly, because a conformance document that hides them is misleading: + +- **There is no external conformance suite.** An independent implementation + cannot currently run a standard battery to claim conformance. What exists is + this repository's own examples and its reference validator. +- **`validateLegacy()` has no shipped example.** All four examples are v0.3 or + v0.4, so the legacy path is covered by unit tests but not by an executable + fixture a third party can point at. +- **There are no negative examples.** The examples prove that valid documents + validate. They do not prove that invalid documents are rejected — that is + covered only by unit tests inside the package. +- **Producer conformance is not mechanically checked here.** The obligations + listed at the top of this document — `manual` preservation, material-change + write semantics — are stated in the contract and tested by producers, not + verified by a fixture in this repository. + +Closing these gaps is real work with real design questions, and none of it is +claimed as done. diff --git a/docs/glossary.md b/docs/glossary.md new file mode 100644 index 0000000..1242862 --- /dev/null +++ b/docs/glossary.md @@ -0,0 +1,165 @@ +# Glossary + +Terms used across this repository, the specification and its documentation. +Where a term is commonly misread, the entry says what it does **not** mean. + +### `.agents/workspace.json` + +The canonical on-disk path of the artifact, relative to the repository root. See +[ADR-001](./adr/001-canonical-artifact-path.md). The legacy path +`.agents/agents.workspace.json` remains a valid read fallback but must not be +written by a producer. + +### AGENTS.md + +A human-authored, prescriptive document describing what contributors and agents +*should do* in a repository. It is a separate convention from this one. +`workspace.json` is its descriptive counterpart: `AGENTS.md` states intent, +`workspace.json` reports observation. `@workspacejson/rules` parses `AGENTS.md` +in order to audit its hygiene against observed repository state. + +### Artifact + +The committed `workspace.json` document itself. "Artifact" is used rather than +"file" when the point is that it is a reviewable, diffable unit of evidence — +not merely a location on disk. + +### Basis + +The identity of the inputs a generated observation was derived from — +conventionally `{ revision, producerVersion, algorithmVersion, inputDigest }`. +Basis is what makes a generated value reproducible rather than merely asserted. +Generalized basis identity is deferred; see +[ADR-002](./adr/002-bounded-enrichment-program.md). + +### Clean room + +The rule that no repository in this organization may import, copy from, require +or assume proprietary source. Direction matters: proprietary code may consume +released Apache-2.0 packages from here; the reverse is prohibited. Enforced by +`scripts/check-architecture.mjs`, not by convention. See +[`OWNERSHIP.md`](../OWNERSHIP.md). + +### Co-change + +A pair of files that historically change together. Reported as +`generated.coChange` entries carrying an unordered two-file set, a rate and an +occurrence count. The `generated: boolean` flag distinguishes tooling-coupled +pairs — a lockfile and its manifest — from real source couplings. Consumers +should filter on that flag rather than applying path heuristics at read time. + +Maintainer-declared couplings are a different thing and live at +`manual.coChangePatterns`. + +### Compatibility floor + +The four stable read paths, treated as a hard compatibility surface. Removing or +renaming any of them is breaking regardless of version arithmetic. See +[`docs/versioning.md`](./versioning.md). + +### Conformance + +Whether an implementation meets its obligations as a producer or a consumer. See +[`docs/conformance.md`](./conformance.md), including its list of known gaps. + +### Consumer + +Anything that reads the artifact — an agent, an editor integration, an MCP +server, a validator, a human reviewer. Consumers are bound by the read contract, +not by how the file was produced. + +### Descriptive, not prescriptive + +The load-bearing property of this standard. `workspace.json` reports what a +repository *is*. It never encodes what a team *must do* — no approval gates, no +merge blocking, no enforcement policy. Enforced in CI: the architecture guard +rejects prescriptive field names in the schema. + +This is the boundary against `AGENTS.md`, which is prescriptive by design. + +### Daemon-free + +The second load-bearing property. The committed artifact must remain useful with +nothing running. A consumer may read, diff and review it with no background +process present. Nothing in the standard may assume otherwise. + +### Fragility + +A per-file measure derived from change and revert history, reported as +`generated.fragility` entries. Entries carrying `excluded: true` are generated or +lock files with `fragilityScore: 0`; filter them out before ranking. + +Maintainer-declared fragility is a different thing and lives at +`manual.fragileFiles`. + +### `generated` + +The producer-owned section of the document. Replaced wholesale on each +regeneration. Contains machine-derived observations, including +`generated.fileIndex` and `generated.frameworkManifest`. + +### `manual` + +The human-owned section. **Preserved verbatim across regeneration** — a producer +that overwrites it is not conformant. Contains maintainer assertions, including +`manual.fragileFiles` and `manual.coChangePatterns`. + +The `manual` / `generated` split is the provenance boundary of the whole format. +A consumer that merges the two loses the ability to say whether a claim came from +a person or a script. + +### Materialize + +To copy the schema out of a pinned package version and hash-check it, rather than +maintaining an editable second copy. Downstream repositories materialize; they do +not fork. Copies drift; pinned materializations cannot. + +### Normative surface + +The parts of this repository that constitute the contract: the bytes of +`packages/spec/schema/v1.json`, the four stable read paths, the public exports of +either package, and the descriptive and daemon-free properties. Changes here +require an architecture decision record. See [`GOVERNANCE.md`](../GOVERNANCE.md). + +### Producer + +Anything that writes the artifact. Producers are owned by `workspacejson/cli`, +not by this repository. This repository defines what a conformant producer must +do; it does not implement one. + +### Publication authority + +Which repository holds the credential to publish a given package. There is +exactly one per package, by design — two repositories publishing the same package +is the specific failure the arrangement prevents. This repository currently holds +none. See [`.github/RELEASE-AUTHORITY.md`](../.github/RELEASE-AUTHORITY.md). + +### Reference behavior + +The deterministic implementation this repository ships so that "what the +specification means" has an executable answer: the parser, scanner, validator and +rule engine in `@workspacejson/rules`. It is a reference, not a product. + +### `specVersion` + +`generated.specVersion` — the profile a *document* conforms to, such as `"0.3"` +or `"0.4"`. Distinct from the package version, which identifies a release of the +tooling. Neither can be inferred from the other. See +[`docs/versioning.md`](./versioning.md). + +### Stable read paths + +The four externally consumed paths that form the compatibility floor: + +```text +manual.fragileFiles +manual.coChangePatterns +generated.fileIndex +generated.frameworkManifest +``` + +### Standard + +Used in two senses, and the difference matters. **The standard** is the +`workspace.json` specification. **`workspacejson/standard`** is the repository +that owns it. The repository name is not part of the format's name. diff --git a/docs/repository-settings.md b/docs/repository-settings.md new file mode 100644 index 0000000..ff11b67 --- /dev/null +++ b/docs/repository-settings.md @@ -0,0 +1,168 @@ +# Repository settings and security posture + +This document records the repository configuration this project intends, what is +actually set, and — where the two differ — why. It exists so that a setting +nobody can see in the source tree is still reviewable. + +Settings are not code. Anyone with admin access can change them silently, and a +drifted setting looks identical to an intended one. Writing the intent down is +what makes drift detectable. + +## Current state + +Measured 2026-07-26. Re-measure rather than trusting this table: + +```bash +gh repo view workspacejson/standard \ + --json description,homepageUrl,repositoryTopics,visibility,deleteBranchOnMerge,\ +hasIssuesEnabled,hasDiscussionsEnabled,hasWikiEnabled,\ +squashMergeAllowed,mergeCommitAllowed,rebaseMergeAllowed,defaultBranchRef +``` + +| Setting | Value | Intended? | +| -- | -- | -- | +| Default branch | `main` | Yes | +| Description | set | Yes | +| Homepage | `https://workspacejson.dev` | Yes | +| Topics | 10, listed below | Yes | +| Visibility | private | **No — see below** | +| Issues | enabled | Yes — the single intake channel | +| Discussions | disabled | Yes — see [`SUPPORT.md`](../SUPPORT.md) | +| Wiki | disabled | Yes — documentation is version-controlled in `docs/` | +| Squash merge | enabled | Yes — the only permitted method | +| Merge commit | disabled | Yes | +| Rebase merge | disabled | Yes | +| Delete branch on merge | enabled | Yes | +| Branch protection | none | **No — see the constraint below** | + +Everything above except visibility and branch protection was applied through the +GitHub API during the public-readiness pass. The two exceptions are not +oversights: one is an authority decision, the other is blocked by the plan. + +## Merge policy + +**Squash merge only.** One pull request becomes one commit on `main`. + +The reason is specific to this repository rather than general taste: the schema, +the four stable read paths and the package manifests are byte-significant, and +`git log` on `main` is how a consumer reconstructs when a contract changed. +Merge commits and rebased series both make that history harder to bisect for the +one question that matters here — *which commit changed the contract?* + +Automatic head-branch deletion is enabled so stale branches do not accumulate +alongside a repository whose value is its clarity. + +## The visibility and plan constraint + +Two facts constrain everything below, and neither is fixable by a documentation +pass: + +1. **The repository is private.** The specification it contains is meant to be + public, and the historical repository it was extracted from *is* public. + Flipping visibility is an authority action outside the scope of polish work + and is deliberately not done here. +2. **The organization is on the GitHub free plan**, and this repository has no + Advanced Security (`security_and_analysis` is `null`). + +Together those mean several controls that would otherwise be routine are simply +unavailable right now: + +| Control | Status | Why | +| -- | -- | -- | +| Branch protection / rulesets | **unavailable** | Requires a paid plan for private repositories. Becomes free once public. | +| CodeQL code scanning | **unavailable** | Free for public repositories; needs Advanced Security while private. | +| Dependency review action | **unavailable** | Same constraint — it needs the dependency graph. | +| Secret scanning / push protection | **unavailable** | Free for public repositories; needs Advanced Security while private. | +| Private vulnerability reporting | **unavailable** | `PUT /repos/.../private-vulnerability-reporting` returns 404 on this plan while private. | +| Dependabot version updates | **enabled** | Works on the free plan. See [`.github/dependabot.yml`](../.github/dependabot.yml). | + +The private-vulnerability-reporting gap is the one with a live consequence: +[`SECURITY.md`](../SECURITY.md), [`CODE_OF_CONDUCT.md`](../CODE_OF_CONDUCT.md) +and the issue-template configuration all direct reporters to the advisory form, +and that form does not resolve today. It is harmless only because nobody outside +the organization can read those files while the repository is private. **The +moment visibility changes, that stops being true.** + +Adding a CodeQL or dependency-review workflow today would produce a workflow that +cannot succeed. A permanently red check is worse than a missing one: it trains +reviewers to ignore red, which is the precise failure that makes every other +check worthless. They are therefore evaluated and deferred, not silently +skipped. + +## What to enable when the repository becomes public + +In this order, because the later items depend on the earlier ones: + +1. **Enable private vulnerability reporting.** Do this *before* announcing the + repository, not after. Three committed files already point reporters at the + advisory form; until it is enabled, the project's only disclosure channel is a + dead link. Then remove the maintainer note at the top of + [`SECURITY.md`](../SECURITY.md). + + ```bash + gh api -X PUT /repos/workspacejson/standard/private-vulnerability-reporting + ``` + +2. **Enable secret scanning and push protection.** Free for public repositories, + and the cheapest control by a wide margin. +3. **Add CodeQL** on a `javascript-typescript` matrix, scheduled weekly plus on + pull requests, with `security-events: write` scoped to that job only. +4. **Add the dependency review action** to pull requests, failing on high + severity. +5. **Establish a ruleset on `main`** — see the next section. + +## Required branch protection before publication authority + +This is a hard gate, not a recommendation. + +This repository does not currently hold publication authority for either package, +and the architecture guard fails the build if a publish step or credential +appears in any workflow. That arrangement is what currently makes unreviewed +pushes to `main` merely untidy rather than dangerous. + +**The moment this repository can publish, an unprotected `main` becomes a supply +chain problem.** A push to `main` would become a path to the npm registry with no +review in between. + +Therefore, before any npm credential is added: + +- a ruleset on `main` requiring a pull request with at least one approving review; +- required status checks: the CI job on both Node 20 and 22; +- dismiss stale approvals on new commits; +- require conversation resolution; +- block force pushes and branch deletion; +- require review from code owners, so changes to the schema, the guards and the + release documents reach a maintainer — see [`.github/CODEOWNERS`](../.github/CODEOWNERS). + +The authority transfer and the protection ruleset belong in the same coordinated +change. Doing the first without the second creates exactly the window this +paragraph exists to prevent. + +## Repository metadata + +| Field | Value | +| -- | -- | +| Description | Canonical specification, JSON Schema, types and deterministic reference behavior for workspace.json — the committed repository-intelligence artifact at .agents/workspace.json | +| Homepage | `https://workspacejson.dev` | +| Topics | `workspace-json`, `json-schema`, `open-standard`, `specification`, `ai-agents`, `agents-md`, `developer-tools`, `typescript`, `repository-metadata`, `codebase-intelligence` | + +Topics are chosen for discovery by someone looking for this kind of artifact. +None of them asserts adoption, endorsement or standards-body status. + +**No social preview image is set.** The repository has no approved neutral asset +of its own, and borrowing one from another repository would break the moment that +repository changes. It is left unset rather than filled with something that will +rot. + +## Workflow permissions + +`ci.yml` declares `permissions: contents: read` at the workflow level and repeats +it at the job level. It reads the repository and nothing else — it does not +comment, label, publish or write artifacts. + +Any future job needing more must raise it at the job level, scoped to that job, +with a comment stating why. Raising the workflow-level default to satisfy one +step grants that permission to every step, including third-party actions. + +Actions are pinned by major version tag. Dependabot's `github-actions` ecosystem +entry keeps them current; see [`.github/dependabot.yml`](../.github/dependabot.yml). diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md new file mode 100644 index 0000000..b712231 --- /dev/null +++ b/docs/troubleshooting.md @@ -0,0 +1,189 @@ +# Troubleshooting + +Failure modes that come up repeatedly, with the actual cause rather than a +workaround. Each entry states why the behavior exists, because most of these are +deliberate and "fixing" them locally will break something else. + +## Working in this repository + +### `pnpm -r typecheck` fails on a clean checkout + +**Symptom.** Typechecking fails with unresolved types for `@workspacejson/spec` +when you have not built anything yet. + +**Cause.** `@workspacejson/rules` typechecks against `@workspacejson/spec`'s +**emitted declarations**, which `tsc --noEmit` never produces. The dependency is +real, not an ordering quirk. + +**Fix.** Build before typechecking: + +```bash +pnpm -r build +pnpm -r typecheck +``` + +CI does this in the same order, deliberately. The root `typecheck` script also +builds the spec package first for this reason. + +Historically this ordering was hidden by a hand-written ambient +`declare module '@workspacejson/spec'` that shadowed the real package types with +a stale contract. Removing that stub made the real dependency visible. Do not +reintroduce it — see the next entry. + +### A type resolves to something that does not match the package + +**Cause.** An ambient `declare module` in `types/ambient.d.ts` shadows a +package's real types across the **entire workspace**, silently and with no +warning. If a declaration for a standard-owned package exists there, it wins over +the published types. + +**Fix.** Fix the import instead. The architecture guard fails the build if an +ambient `declare module` for `@workspacejson/spec` or `@workspacejson/rules` +appears in any `.d.ts` file. + +Four ambient shims are retained deliberately, for `simple-git`, `remark`, `ajv` +and `ajv/dist/2020.js`. Those are real CJS/ESM interoperability mismatches in +third-party packages, tracked as their own work. They are not a pattern to copy. + +### `pnpm run check:examples` says `dist/index.js` is missing + +**Cause.** The gate validates examples using the package's own compiled +validator, not a re-implementation. It needs a build. + +**Fix.** + +```bash +pnpm --filter @workspacejson/spec build +pnpm run check:examples +``` + +### `pnpm run check:architecture` fails after I added a file + +The guard prints the violation class, the file and the reason. The common ones: + +| Class | Meaning | +| -- | -- | +| `clean-room` | The file references proprietary scope or a private sidecar | +| `dependency-direction` | Something here imports from `cli` or `integrations`; this repository depends on none of the other three | +| `copied-schema` | A second schema copy appeared; exactly one canonical copy may exist | +| `duplicate-contract` | An ambient `declare module` shadows a standard-owned package | +| `publish-authority` | A workflow gained a publish step or a credential reference | +| `prescriptive-policy` | The schema gained a field encoding what a team must do | +| `stable-read-path` | One of the four stable paths is missing from the schema | + +Markdown is not scanned. Documentation must be able to *name* a prohibited thing +in order to prohibit it, so the guard applies to source and configuration only. +Comments are stripped before matching, so a comment explaining that there is +deliberately no publish step is not itself read as one. + +### CI fails as soon as I add a release workflow + +That is the intended behavior, not a bug. + +This repository is deliberately incapable of publishing. Both packages are +published from the historical repository that holds the only credential. The +guard fails the build if any workflow contains `changeset publish`, `npm publish` +or `pnpm publish`, or references a publish credential. + +Transferring authority is a coordinated change that must revoke the old authority +in the same act, and it updates the guards and their red tests deliberately +rather than deleting them. See +[`.github/RELEASE-AUTHORITY.md`](../.github/RELEASE-AUTHORITY.md). + +### Why does `@workspacejson/rules` depend on `@workspacejson/spec` with `workspace:*`? + +Both packages live in **this one** pnpm workspace, and `pnpm pack` rewrites the +protocol to an exact version before publication. It is an intra-repository link, +not a cross-repository dependency. + +`scripts/verify-package-tarball.mjs` proves no `workspace:` protocol ever reaches +a packed manifest. Run it with `pnpm run release:verify-packs`. + +### `import('@workspacejson/rules/testing')` throws about internal state + +**Symptom.** Something like *failed to access its internal state* when importing +the `./testing` entry point outside a test run. + +**Cause.** That entry point re-exports vitest helpers. Vitest throws when +evaluated outside a vitest process. + +**Fix.** Import it only from inside a vitest run. CI verifies this export by +**resolution** rather than evaluation for exactly this reason; its runtime +behavior is covered by the package's own vitest suite. + +## Using the packages + +### `npx workspacejson-spec --help` exits non-zero + +There is no `--help` flag. The binary has exactly one command: + +```bash +npx workspacejson-spec validate path/to/workspace.json +``` + +Any other invocation prints usage and exits non-zero. CI asserts both paths +explicitly rather than assuming a conventional `--help` exists. + +### `npx @workspacejson/spec validate ` — why does that resolve? + +npm selects the package's sole `workspacejson-spec` binary when the package has +exactly one. The generic `spec` bin alias is deliberately **not** reserved. If a +second binary is ever added, that fallback stops working and must be +re-evaluated as part of the change. + +### Validation fails and I do not know which rule + +`validate()` returns a boolean. To see why a document was rejected, check which +profile you actually have: + +```ts +import { validate, validateV4, validateLegacy } from '@workspacejson/spec'; + +validate(doc); // v0.3 or v0.4 +validateV4(doc); // v0.4 only +validateLegacy(doc); // legacy v0.1/v0.2 +``` + +If `validateLegacy(doc)` is the only one that passes, the document predates the +four-section shape. See [`docs/versioning.md`](./versioning.md). + +### My v0.4 fields are missing after reading a valid document + +`validate()` accepts both v0.3 and v0.4, because v0.4 is a strict superset. A +v0.3 document has no `generated.coChange` or `generated.fragility`. + +Check `validateV4(doc)` or `generated.specVersion === "0.4"` before reading +v0.4-only fields. + +### `coChange` is full of lockfile pairs + +Filter on `generated: true` to skip tooling-coupled pairs and surface real source +couplings. Do not apply path heuristics at read time — the flag exists so you do +not have to. + +Similarly, filter `generated.fragility` on `excluded: false` before ranking; +excluded entries are generated or lock files carrying `fragilityScore: 0`. + +### The document reports nothing for a section — does that mean the answer is no? + +No. **Absent evidence is not evidence of absence.** A missing or empty section +means the producer did not observe it. A consumer reporting "no fragile files" +from an absent `manual.fragileFiles` is reporting a producer gap as a repository +fact. + +### The artifact does not exist at all + +This repository defines the format; it does not generate the file. Producing +`.agents/workspace.json` belongs to `workspacejson/cli`. See +[`SUPPORT.md`](../SUPPORT.md) for which repository handles what. + +### Node version errors + +Both packages declare `node >=20`. CI tests Node 20 and 22. Older runtimes are +not supported. + +## Still stuck + +[`SUPPORT.md`](../SUPPORT.md) lists where to ask, what to include, and what +response to expect. For a security issue, use [`SECURITY.md`](../SECURITY.md) +instead of a public issue. diff --git a/docs/versioning.md b/docs/versioning.md new file mode 100644 index 0000000..377689a --- /dev/null +++ b/docs/versioning.md @@ -0,0 +1,174 @@ +# Versioning and compatibility + +This document states what a consumer of `workspace.json` may rely on, and what +may change underneath them. It describes the **currently released** behavior. +Where something is undecided, it says so rather than implying a guarantee. + +## Two version numbers, two different things + +They are frequently confused, and confusing them produces real bugs. + +| Number | Where it lives | What it identifies | +| -- | -- | -- | +| **Package version** | `@workspacejson/spec` / `@workspacejson/rules` `package.json` | A release of this tooling. Follows semver. | +| **Spec version** | `generated.specVersion` inside a document | Which profile of the format that *document* conforms to. | + +A document written by an old producer stays at its own `specVersion` no matter +which package version reads it. Never infer one from the other. + +## Current released state + +Published versions are **registry-defined**. `npm view @workspacejson/spec version` +is the arbiter; anything written in a document, including this one, is a snapshot. + +At the time of writing, both packages are at `0.4.4` and the current document +profile is **v0.4**. + +The two packages are released as a **fixed group** — configured in +[`.changeset/config.json`](../.changeset/config.json) — so they always carry the +same version number. That is deliberate: it removes an entire class of +"which pair of versions is compatible?" question. It also means a package may be +released with no changes of its own, purely to stay aligned with its partner. + +## Document profiles + +| Profile | Shape | How to validate | +| -- | -- | -- | +| **v0.4** | v0.3 plus typed `generated.coChange` and `generated.fragility` arrays, plus three formally typed `health` fields | `validateV4(doc)` | +| **v0.3** | Four required sections: `manual`, `generated`, `agents`, `health` | `validate(doc)` | +| **v0.1 / v0.2** | Legacy flat top-level shape | `validateLegacy(doc)` | + +**v0.4 is a strict superset of v0.3.** Every valid v0.3 document is a valid v0.4 +document. `validate()` accepts both; use `validateV4()` when you intend to read +the v0.4-only fields, or check `generated.specVersion === "0.4"` before touching +them. + +Legacy v0.1/v0.2 documents are validated by a separate function precisely so a +consumer has to opt into handling them. They are not accepted by `validate()`. + +## The compatibility floor + +Four read paths are externally consumed and are treated as a hard compatibility +surface: + +```text +manual.fragileFiles +manual.coChangePatterns +generated.fileIndex +generated.frameworkManifest +``` + +**Removing or renaming any of the four is a breaking change regardless of what +the version number would otherwise suggest.** A patch release cannot do it; a +minor release cannot do it. This is enforced mechanically rather than by +convention — both `scripts/check-architecture.mjs` and +`scripts/verify-schema-provenance.mjs` fail the build if one of the four is +absent from the canonical schema. + +The floor covers *presence and shape*, not contents. A producer emitting more +entries in `generated.fileIndex` than it did last month has not broken anything. + +## What counts as a breaking change + +Breaking, regardless of version arithmetic: + +- removing or renaming one of the four stable read paths; +- narrowing an existing field's accepted type or value range; +- making a previously optional field required; +- changing the canonical artifact path (see [ADR-001](./adr/001-canonical-artifact-path.md)); +- removing a public export from either package. + +Not breaking: + +- adding a new optional field; +- adding a new profile that is a strict superset of the current one, as v0.4 was + over v0.3; +- widening an accepted type; +- documentation, test and internal-implementation changes. + +Changes in the first list require an architecture decision record before +implementation. See [`GOVERNANCE.md`](../GOVERNANCE.md). + +## Reading a document defensively + +The behavior a consumer should implement today: + +```ts +import { validate, validateV4 } from '@workspacejson/spec'; + +if (!validate(doc)) { + // Not a v0.3/v0.4 document. Do not guess at its shape. + return; +} + +// The four stable paths are the safe surface. +const fragile = doc.manual?.fragileFiles ?? []; + +// v0.4-only fields require an explicit check. +if (validateV4(doc)) { + const coChange = doc.generated.coChange ?? []; +} +``` + +Two consumer guidances are part of the released contract and are easy to get +wrong: + +- **`generated.coChange`** — filter on `generated: true` to skip tooling-coupled + pairs such as a lockfile and its manifest, and surface only real source + couplings. Do not apply path heuristics at read time. +- **`generated.fragility`** — filter `excluded: false` before ranking. Entries + with `excluded: true` are generated or lock files carrying + `fragilityScore: 0`. + +**Absent evidence is not evidence of absence.** A missing or empty section means +the producer did not observe it, not that the underlying property is false. A +reader that reports "no fragile files" when `manual.fragileFiles` is absent is +reporting a producer gap as a repository fact. + +## Schema identity + +The normative schema lives at exactly one path — `packages/spec/schema/v1.json` — +and is shipped inside the `@workspacejson/spec` tarball, resolvable as +`@workspacejson/spec/schema`. + +Downstream repositories must **materialize** it from a pinned package version and +hash-check it. They must not maintain an editable second copy; that is how the +copies drift. `pnpm run check:schema` prints the path, byte length and SHA-256 +for pinning. + +**Known inconsistency, deliberately not fixed here.** The schema's `$id` +declares the `www.` host while the package manifests use the bare canonical +domain. Both hosts serve the schema, so nothing is broken, but the two strings +disagree. Reconciling them changes schema bytes, which is a normative change and +is tracked separately from documentation work. It is recorded here rather than +quietly normalized. + +The `v1` in the filename is a legacy artifact of the schema file's original +naming, not a claim that the format is at version 1.0. Whether to rename it is +open, and it is entangled with the `$id` question above because both change +schema bytes. + +## Deprecation policy + +Nothing in the released surface is currently deprecated with a removal date. + +One documented compatibility allowance is open-ended: the legacy artifact path +`.agents/agents.workspace.json` remains a valid **read** fallback, though +producers must write to `.agents/workspace.json`. See +[ADR-001](./adr/001-canonical-artifact-path.md). + +When a deprecation is introduced, it will be announced in the package changelog +with the release that introduces it, kept working for at least one minor +release, and removed only in a release that says so explicitly. Nothing will be +removed silently. + +## Publication + +**This repository does not currently publish either package.** Both are published +from the historical repository this one was extracted from, which holds the only +credential. This repository has no npm secret and ships no release workflow, and +CI fails if a publish step or credential reference appears in any workflow. + +Transferring that authority is a separate coordinated change that must revoke the +old authority in the same act. See +[`.github/RELEASE-AUTHORITY.md`](../.github/RELEASE-AUTHORITY.md). diff --git a/package.json b/package.json index ebd5338..d02ea6a 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "check:architecture:test": "node scripts/check-architecture.test.mjs", "check:schema": "node scripts/verify-schema-provenance.mjs", "check:examples": "node scripts/validate-examples.mjs", + "check:docs": "node scripts/check-docs.mjs", "release:verify-packs": "pnpm --filter @workspacejson/spec exec node ../../scripts/verify-package-tarball.mjs && pnpm --filter @workspacejson/rules exec node ../../scripts/verify-package-tarball.mjs", "release:verify-published": "node scripts/verify-published.mjs" }, diff --git a/packages/rules/CHANGELOG.md b/packages/rules/CHANGELOG.md index 99af58d..82aa8a5 100644 --- a/packages/rules/CHANGELOG.md +++ b/packages/rules/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [0.4.4] - Unreleased +## [0.4.4] - 2026-07-23 ### Patch Changes diff --git a/packages/rules/README.md b/packages/rules/README.md index 35dde9c..e2e5946 100644 --- a/packages/rules/README.md +++ b/packages/rules/README.md @@ -2,22 +2,26 @@

- - - workspace.json — Portable Repository Intelligence + + workspace.json — portable repository intelligence

-Deterministic parser, scanner, validator, and rule engine for `agents-audit` -and other consumers of `.agents/workspace.json`. +Deterministic parser, scanner, validator and rule engine for consumers of +`.agents/workspace.json`. -This package is published from the `agents-audit` workspace and depends on -the published `@workspacejson/spec` package. +This package is the **reference behavior** for the workspace.json +specification — it makes "what the specification means" executable. It is a +reference implementation, not a product, and it depends on +[`@workspacejson/spec`](https://www.npmjs.com/package/@workspacejson/spec) for +the normative schema and types. + +Source of truth: [`workspacejson/standard`](https://github.com/workspacejson/standard). ## Install ```bash -pnpm add @workspacejson/rules +npm install @workspacejson/rules ``` ## API @@ -34,13 +38,39 @@ import { ## Contents -- `src/parser` parses `AGENTS.md` -- `src/scanner` walks repository structure -- `src/validator` validates `.agents/workspace.json` -- `src/engine` evaluates deterministic rules -- `src/testing` provides the published test helper surface +| Module | Responsibility | +| -- | -- | +| `src/parser` | Parses `AGENTS.md` | +| `src/scanner` | Walks repository structure | +| `src/validator` | Validates `.agents/workspace.json` | +| `src/engine` | Evaluates deterministic rules | +| `src/testing` | The published test-helper surface | + +Public exports are kept stable through `src/index.ts`. + +## Writing rule tests + +The `./testing` entry point exports a `RuleTester` for authoring tests against +the shipped fixtures: + +```ts +import { RuleTester } from '@workspacejson/rules/testing'; +``` + +It re-exports vitest helpers, so it must be imported from inside a vitest run. +Importing it outside one throws about vitest's internal state — that is expected, +not a packaging bug. + +## Requirements + +Node.js >= 20. + +## Further reading + +- [Conformance](https://github.com/workspacejson/standard/blob/main/docs/conformance.md) +- [Versioning and compatibility](https://github.com/workspacejson/standard/blob/main/docs/versioning.md) +- [Troubleshooting](https://github.com/workspacejson/standard/blob/main/docs/troubleshooting.md) -## Notes +## License -- This package is the implementation layer for the audit engine -- Public exports are kept stable through `src/index.ts` +[Apache-2.0](./LICENSE). diff --git a/packages/rules/package.json b/packages/rules/package.json index 74fa47d..a00189a 100644 --- a/packages/rules/package.json +++ b/packages/rules/package.json @@ -1,10 +1,10 @@ { "name": "@workspacejson/rules", "version": "0.4.4", - "description": "Rule engine for auditing AGENTS.md hygiene using .agents/workspace.json", + "description": "Deterministic reference behavior for workspace.json: AGENTS.md parser, repository scanner, validator and rule engine", "license": "Apache-2.0", - "author": "workspace-json contributors", - "homepage": "https://workspacejson.dev/audit/", + "author": "workspacejson contributors", + "homepage": "https://workspacejson.dev", "keywords": [ "workspace.json", "ai-agents", @@ -13,7 +13,6 @@ "open-standard", "json-schema", "developer-tools", - "aaif", "AGENTS.md", "claude-code", "cursor", diff --git a/packages/spec/CHANGELOG.md b/packages/spec/CHANGELOG.md index 975a6f4..5c825f6 100644 --- a/packages/spec/CHANGELOG.md +++ b/packages/spec/CHANGELOG.md @@ -1,11 +1,12 @@ # Changelog -## [0.4.4] - Unreleased +## [0.4.4] - 2026-07-23 ### Fixed -- Reconciled the strict packaged-schema validator with the VR-639/640 contract - fixes that had diverged across earlier release branches. +- Reconciled the strict packaged-schema validator with the `coChange.files` and + `fileIndex` key-format contract fixes that had diverged across earlier release + branches. - Documented `generated.fileIndex` keys as repository-root-relative POSIX paths. - Typed and documented `generated.coChange[].files` as an unordered two-file set, rather than a positional tuple. diff --git a/packages/spec/README.md b/packages/spec/README.md index 0acc383..935373f 100644 --- a/packages/spec/README.md +++ b/packages/spec/README.md @@ -2,23 +2,37 @@

- - - workspace.json — Portable Repository Intelligence + + workspace.json — portable repository intelligence

JSON Schema and TypeScript types for `workspace.json` v0.4. -This package is published from the `agents-audit` workspace and is the canonical -specification package for the workspace metadata format. +This is the **canonical specification package**. The schema it ships is the +normative one — there is exactly one copy, and downstream repositories +materialize it from this package rather than maintaining their own. + +Source of truth: [`workspacejson/standard`](https://github.com/workspacejson/standard). +Publication authority for this package currently sits with the historical +repository it was extracted from; see the repository's release-authority note. ## Install ```bash -pnpm add @workspacejson/spec +npm install @workspacejson/spec +``` + +## Validate a document + +```bash +npx workspacejson-spec validate .agents/workspace.json ``` +Exits `0` on a valid document, non-zero otherwise. `validate ` is the only +command; there is no `--help` flag, and any other invocation exits non-zero with +usage. + ## API ### Validation @@ -125,11 +139,20 @@ last material generation, not merely the last command invocation. The raw JSON Schema is available via the `./schema` export: ```ts -import schema from '@workspacejson/spec/schema'; -// or: import the file directly at packages/spec/schema/v1.json +import schema from '@workspacejson/spec/schema' with { type: 'json' }; ``` -Served at: `https://www.workspacejson.dev/schema/v1.json` +Materialize it from this package and hash-check it. Do not maintain an editable +second copy — that is how copies drift. + +The schema declares `$id: https://www.workspacejson.dev/schema/v1.json` and is +also served at that URL. Note that the `$id` host carries the `www.` prefix while +this package's `homepage` uses the bare domain; both hosts serve the schema, but +the two strings disagree. Reconciling them changes schema bytes and is tracked as +a normative change rather than a documentation fix. + +The `v1` in the filename is a legacy artifact of the file's original naming, not +a claim that the format is at version 1.0. The current document profile is v0.4. ## Contents @@ -178,3 +201,20 @@ v0.3 replaces the flat top-level shape with four required sections: ``` Use `validateLegacy(doc)` to detect v0.1/v0.2 documents; use `validate(doc)` for v0.3/v0.4. + +## Requirements + +Node.js >= 20. + +## Further reading + +Full documentation lives in the source repository: + +- [Versioning and compatibility](https://github.com/workspacejson/standard/blob/main/docs/versioning.md) +- [Conformance](https://github.com/workspacejson/standard/blob/main/docs/conformance.md) +- [Troubleshooting](https://github.com/workspacejson/standard/blob/main/docs/troubleshooting.md) +- [Glossary](https://github.com/workspacejson/standard/blob/main/docs/glossary.md) + +## License + +[Apache-2.0](./LICENSE). diff --git a/packages/spec/package.json b/packages/spec/package.json index dd1392a..70ea62c 100644 --- a/packages/spec/package.json +++ b/packages/spec/package.json @@ -3,7 +3,7 @@ "version": "0.4.4", "description": "JSON Schema and TypeScript types for workspace.json", "license": "Apache-2.0", - "author": "workspace-json contributors", + "author": "workspacejson contributors", "homepage": "https://workspacejson.dev", "keywords": [ "workspace.json", @@ -13,7 +13,6 @@ "open-standard", "json-schema", "developer-tools", - "aaif", "AGENTS.md", "claude-code", "cursor", diff --git a/packages/spec/src/index.test.ts b/packages/spec/src/index.test.ts index 4e260ea..dedd927 100644 --- a/packages/spec/src/index.test.ts +++ b/packages/spec/src/index.test.ts @@ -209,12 +209,12 @@ describe('schema structural invariants', () => { }); }); -// ─── VR-640: fileIndex key format pinned to repo-root-relative POSIX ────────── -// The DataHub join (HAC-75 probe) silently produced zero rows because the spec +// ─── fileIndex key format pinned to repo-root-relative POSIX ───────────────── +// A downstream join probe silently produced zero rows because the spec // said "relative path" without an anchor. The canonical form must be stated and // kept in sync across both schema mirrors so the CLI shim normalizes toward a // blessed target rather than an assumed one. -describe('VR-640: canonical key format is repository-root-relative POSIX', () => { +describe('canonical key format is repository-root-relative POSIX', () => { const gen = (s: Record) => ((s['properties'] as Record>)['generated']?.['properties'] ?? {}) as Record>; @@ -242,11 +242,11 @@ describe('VR-640: canonical key format is repository-root-relative POSIX', () => }); }); -// ─── VR-639: coChange.files is a set, not a positional tuple ────────────────── +// ─── coChange.files is a set, not a positional tuple ───────────────────────── // types.ts said [string, string] (positional) while the schema said min/max-2 // array (set). A CLI that treated files[0] as canonical would silently mis-join. // The contract is now set semantics — order must never affect the join. -describe('VR-639: coChange.files has set semantics (order-independent join)', () => { +describe('coChange.files has set semantics (order-independent join)', () => { const minimalV4 = { manual: {}, generated: { @@ -274,7 +274,7 @@ describe('VR-639: coChange.files has set semantics (order-independent join)', () it('the co-change join (set membership) resolves identically under reversed pair order', () => { // Models the CLI join: find co-change partners of a target file by membership, - // never by index. This is the assertion VR-639 requires "at the join level". + // never by index. This is the assertion set semantics requires at the join level. const partnersOf = (doc: ReturnType, target: string) => doc.generated.coChange .filter((e) => e.files.includes(target)) diff --git a/scripts/check-architecture.mjs b/scripts/check-architecture.mjs index d5df305..8609d83 100644 --- a/scripts/check-architecture.mjs +++ b/scripts/check-architecture.mjs @@ -2,7 +2,7 @@ // Architecture and clean-room guards for workspacejson/standard. // -// Enforces META-165's dependency direction and the clean-room boundary: +// Enforces the ratified dependency direction and the clean-room boundary: // // workspacejson/standard <- this repository, depends on NONE of the others // | @@ -113,7 +113,7 @@ for (const file of files) { } // ---- Second editable view of the standard contract ----------------------- - // The META-239 migration removed a hand-written `declare module + // The extraction migration removed a hand-written `declare module // '@workspacejson/spec'` that was WINNING over the real package's types and // described a stale v0.3-only contract. It must not come back. if (/\.d\.ts$/.test(file) && /declare\s+module\s+['"]@workspacejson\/(spec|rules)['"]/.test(content)) { @@ -137,7 +137,7 @@ for (const file of files) { } // ---- Exactly one normative schema ----------------------------------------- -// The website independently checked in a drifting copy of the schema (META-211). +// The website independently checked in a drifting copy of the schema. // standard is the canonical source; a second copy here would recreate that // failure inside the repository that is supposed to be authoritative. { @@ -170,7 +170,7 @@ for (const file of files.filter((f) => /^packages\/[^/]+\/package\.json$/.test(f // A committed workspace: protocol is correct HERE, because spec and rules // live in the same pnpm workspace and pnpm rewrites it to the exact version - // at pack time (META-239 Phase 7, proven against the published 0.4.4 tarball). + // at pack time, proven against the published 0.4.4 tarball. // A *floating range* on a standard-owned package is not — the fixed release // group would stop being coherent. for (const field of ["dependencies", "peerDependencies", "optionalDependencies"]) { @@ -187,7 +187,7 @@ for (const file of files.filter((f) => /^packages\/[^/]+\/package\.json$/.test(f // ---- No workflow may be capable of publishing ------------------------------ // Publish authority for standard-owned packages still belongs to -// workspace-json/agents-audit until META-243. This repository ships NO release +// workspace-json/agents-audit until a coordinated cutover. This repository ships NO release // workflow at all — see .github/RELEASE-AUTHORITY.md for why absence was chosen // over a disabled file. These checks scan EVERY workflow, so publication cannot // reappear under a different filename. @@ -195,10 +195,10 @@ for (const workflow of files.filter((f) => /^\.github\/workflows\/.+\.ya?ml$/.te const content = stripComments(workflow, read(workflow)); if (/changeset\s+publish|npm\s+publish|pnpm\s+publish/.test(content)) { - report("publish-authority", workflow, "workflow contains a publish step; this repository must be incapable of publishing until META-243"); + report("publish-authority", workflow, "workflow contains a publish step; this repository must be incapable of publishing until authority transfers"); } if (/secrets\.NPM_TOKEN|NODE_AUTH_TOKEN/.test(content)) { - report("publish-authority", workflow, "workflow references a publish credential; no npm credential may exist here until META-243"); + report("publish-authority", workflow, "workflow references a publish credential; no npm credential may exist here until authority transfers"); } if (/\bagents-audit\b/.test(content)) { report("foreign-publish", workflow, "workflow references agents-audit, which is published by workspacejson/cli"); diff --git a/scripts/check-docs.mjs b/scripts/check-docs.mjs new file mode 100644 index 0000000..5444499 --- /dev/null +++ b/scripts/check-docs.mjs @@ -0,0 +1,173 @@ +#!/usr/bin/env node + +// Documentation integrity gate. +// +// Polish decays. A README that was accurate when it was written drifts as files +// move, and a "cold reader can follow this" claim is worthless if nothing checks +// it. This gate makes three properties of the public surface mechanically +// verifiable instead of merely asserted: +// +// 1. Every relative Markdown link and image resolves to a file that exists. +// 2. Public prose does not carry internal tracker identifiers, which a public +// reader cannot resolve. Provenance records are exempt — see PROVENANCE_FILES. +// 3. Every documented pnpm script actually exists in package.json. +// +// External links are NOT fetched. A network check in CI fails on someone else's +// outage and trains everyone to ignore red. They are syntax-checked here and +// verified manually during review; the counts are reported either way. +// +// Exit 0 = clean. Exit 1 = failures, printed with file, line and reason. + +import { readFileSync, existsSync, statSync } from "node:fs"; +import { join, dirname, resolve, relative } from "node:path"; +import { fileURLToPath } from "node:url"; +import { spawnSync } from "node:child_process"; + +const repoRoot = join(dirname(fileURLToPath(import.meta.url)), ".."); + +const failures = []; +const fail = (file, line, message) => failures.push({ file, line, message }); + +function trackedFiles() { + const result = spawnSync("git", ["ls-files"], { cwd: repoRoot, encoding: "utf8" }); + if (result.status !== 0) throw new Error(`git ls-files failed: ${result.stderr}`); + return result.stdout.split("\n").filter(Boolean); +} + +const files = trackedFiles(); +const markdown = files.filter((f) => f.endsWith(".md")); + +// Files whose whole purpose is to record where something came from. An audit +// trail that cannot name its own source is not an audit trail. +const PROVENANCE_FILES = new Set([ + "migration/PROVENANCE.md", + "migration/commit-map.txt", + "migration/parity-packed.mjs", + "migration/parity-runtime.mjs", + "docs/adr/README.md", + "docs/adr/001-canonical-artifact-path.md", + "docs/adr/002-bounded-enrichment-program.md", +]); + +// Historical release notes are a record of what was published, not live prose. +// Rewriting them to remove a reference would falsify the record. +const isChangelog = (f) => /(^|\/)CHANGELOG\.md$/.test(f); + +// This file names the pattern in order to forbid it. +const SELF = "scripts/check-docs.mjs"; + +const INTERNAL_ID = /\b(?:META|VR|HAC|GTM)-\d+\b/g; + +// ---- 1 + 2: per-file Markdown checks --------------------------------------- + +let linksChecked = 0; +let relativeLinks = 0; +let externalLinks = 0; + +for (const file of markdown) { + const content = readFileSync(join(repoRoot, file), "utf8"); + const lines = content.split("\n"); + const fileDir = dirname(join(repoRoot, file)); + + // Strip fenced code blocks before link and identifier matching. A code sample + // is an illustration, not a claim about this repository. + let inFence = false; + const prose = lines.map((line) => { + if (/^\s*```/.test(line)) { inFence = !inFence; return ""; } + return inFence ? "" : line; + }); + + prose.forEach((line, index) => { + const lineNumber = index + 1; + + // ---- links and images + // Matches [text](target) and ![alt](target). Bare autolinks are skipped: + // they are always external and carry no relative-path risk. + for (const match of line.matchAll(/!?\[[^\]]*\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g)) { + const target = match[1]; + linksChecked++; + + if (/^(https?:|mailto:)/.test(target)) { + externalLinks++; + if (/^http:/.test(target)) { + fail(file, lineNumber, `insecure http link: ${target}`); + } + continue; + } + if (target.startsWith("#")) continue; // same-document anchor + + relativeLinks++; + const [path] = target.split("#"); + if (!path) continue; + const resolved = resolve(fileDir, decodeURIComponent(path)); + + if (!existsSync(resolved)) { + fail(file, lineNumber, `relative link target does not exist: ${target}`); + continue; + } + // A link to a directory must point at something a reader can land on. + if (statSync(resolved).isDirectory() && !existsSync(join(resolved, "README.md"))) { + const inRepo = relative(repoRoot, resolved); + if (!files.some((f) => f.startsWith(`${inRepo}/`))) { + fail(file, lineNumber, `relative link points at an empty or untracked directory: ${target}`); + } + } + } + + // ---- internal tracker identifiers + if (file === SELF || PROVENANCE_FILES.has(file) || isChangelog(file)) return; + for (const match of line.matchAll(INTERNAL_ID)) { + fail( + file, + lineNumber, + `internal tracker identifier '${match[0]}' in public prose — a public reader cannot resolve it. ` + + `Describe the work instead, or record it in migration/PROVENANCE.md.`, + ); + } + }); +} + +// Non-Markdown tracked text is held to the identifier rule too, so an internal +// reference cannot simply move into a workflow comment or a script header. +for (const file of files.filter((f) => /\.(ya?ml|json|mjs|js|ts)$/.test(f))) { + if (file === SELF || PROVENANCE_FILES.has(file)) continue; + const content = readFileSync(join(repoRoot, file), "utf8"); + content.split("\n").forEach((line, index) => { + for (const match of line.matchAll(INTERNAL_ID)) { + fail(file, index + 1, `internal tracker identifier '${match[0]}' — describe the work instead`); + } + }); +} + +// ---- 3: documented commands exist ------------------------------------------ + +const rootManifest = JSON.parse(readFileSync(join(repoRoot, "package.json"), "utf8")); +const rootScripts = new Set(Object.keys(rootManifest.scripts ?? {})); + +let commandsChecked = 0; +for (const file of markdown) { + const content = readFileSync(join(repoRoot, file), "utf8"); + for (const match of content.matchAll(/\bpnpm run ([a-z][a-z0-9:-]*)/g)) { + commandsChecked++; + if (!rootScripts.has(match[1])) { + fail(file, 0, `documents 'pnpm run ${match[1]}', which is not a script in the root package.json`); + } + } +} + +// ---- report ---------------------------------------------------------------- + +console.log("Documentation integrity"); +console.log(` markdown files ${markdown.length}`); +console.log(` links checked ${linksChecked} (${relativeLinks} relative, resolved on disk; ${externalLinks} external, syntax only)`); +console.log(` pnpm commands ${commandsChecked} documented references verified against package.json`); +console.log(` provenance files ${PROVENANCE_FILES.size} exempt from the tracker-identifier rule`); + +if (failures.length) { + console.error(`\ncheck-docs: ${failures.length} failure(s)\n`); + for (const f of failures) { + console.error(` ${f.file}${f.line ? `:${f.line}` : ""}\n ${f.message}`); + } + process.exit(1); +} +console.log("\nOK — every relative link resolves, no internal tracker identifiers in public prose, every documented command exists."); diff --git a/scripts/validate-examples.mjs b/scripts/validate-examples.mjs index fcca38c..a6b0757 100644 --- a/scripts/validate-examples.mjs +++ b/scripts/validate-examples.mjs @@ -1,6 +1,6 @@ #!/usr/bin/env node -// Executable examples gate (META-211). +// Executable examples gate. // // Every example this repository ships must validate against the package-owned // schema, using the package's own validator — not a re-implementation. If an diff --git a/scripts/verify-package-tarball.mjs b/scripts/verify-package-tarball.mjs index e59b7fc..dfe3c62 100644 --- a/scripts/verify-package-tarball.mjs +++ b/scripts/verify-package-tarball.mjs @@ -2,7 +2,7 @@ // Packed-tarball gate for the packages workspacejson/standard publishes. // -// Adapted from workspace-json/agents-audit@e47eb1b8 during the META-239 +// Adapted from workspace-json/agents-audit@e47eb1b8 during the extraction // migration. Two things changed, both because this repository publishes a // different set of packages than the monorepo did: // @@ -12,8 +12,8 @@ // were removed. That helper packed ../rules and ../spec from disk, an // assumption that only held inside the monorepo. // -// `assertFixedGroupDependencies` is load-bearing for META-239's Phase 7 -// decision: @workspacejson/rules keeps `"@workspacejson/spec": "workspace:*"` +// `assertFixedGroupDependencies` is load-bearing for the migration's decision +// to keep the intra-workspace link: @workspacejson/rules keeps `"@workspacejson/spec": "workspace:*"` // in committed source because both packages live in this one pnpm workspace. // This assertion is the proof that the protocol never reaches the registry — // it fails if the packed manifest carries anything other than the exact @@ -75,8 +75,8 @@ function tar(...args) { } // This repository must never pack a package it does not own. Publishing a -// foreign package from a second repository is the specific failure META-243 -// exists to prevent. +// foreign package from a second repository is the specific failure the +// one-authority-per-package rule exists to prevent. function assertStandardOwnedPackage(manifest) { if (!STANDARD_OWNED_PACKAGES.has(manifest.name)) { throw new Error( @@ -123,8 +123,8 @@ function assertRuntimeFiles(manifest, files) { } } -// META-201: consumers click repository/bugs first. Both pointed at a repository -// that never existed before the 0.4.4 line; they must now point here, with a +// Consumers click repository/bugs first. Both pointed at a repository that +// never existed before the 0.4.4 line; they must now point here, with a // directory pointer, or the published package is misleading again. function assertOwnershipMetadata(manifest) { const expectedRepository = "git+https://github.com/workspacejson/standard.git"; diff --git a/scripts/verify-published.mjs b/scripts/verify-published.mjs index 4377046..46b5c5d 100644 --- a/scripts/verify-published.mjs +++ b/scripts/verify-published.mjs @@ -9,7 +9,7 @@ const version = process.env.WORKSPACEJSON_RELEASE_VERSION ?? JSON.parse(readFileSync(new URL("../packages/spec/package.json", import.meta.url), "utf8")).version; // Only packages this repository publishes are verified here. `agents-audit` is // published by workspacejson/cli and was deliberately removed during the -// META-239 migration: per the four-repository ledger, no target repository +// extraction migration: per the four-repository ledger, no target repository // verifies a package it does not publish. const packages = [ { name: "@workspacejson/spec", check: ["npx", "--no-install", "workspacejson-spec", "--help"] }, diff --git a/scripts/verify-schema-provenance.mjs b/scripts/verify-schema-provenance.mjs index 58aa426..1f1a33c 100644 --- a/scripts/verify-schema-provenance.mjs +++ b/scripts/verify-schema-provenance.mjs @@ -1,6 +1,6 @@ #!/usr/bin/env node -// Schema provenance gate (META-211). +// Schema provenance gate. // // workspacejson/standard is the canonical source of the normative schema. // The website currently checks in an independently-edited copy that has @@ -14,7 +14,7 @@ // 4. the packed tarball would include it (`files` covers `schema`); // 5. the four stable read paths are present. // -// This gate does NOT modify the website. Materialization is META-211/META-210. +// This gate does NOT modify the website. Downstream materialization is tracked separately. import { readFileSync, existsSync } from "node:fs"; import { createHash } from "node:crypto"; diff --git a/types/ambient.d.ts b/types/ambient.d.ts index 2705f50..694c5b9 100644 --- a/types/ambient.d.ts +++ b/types/ambient.d.ts @@ -1,4 +1,4 @@ -// Interop shims retained after the META-239 migration. +// Interop shims retained after the extraction migration. // // These four declarations are NOT contract duplication. Each package below ships // real TypeScript types; each shim exists only because a CommonJS default export @@ -6,9 +6,9 @@ // Removing any one of them produces real compile errors (measured: simple-git 3, // remark 1, ajv 1, ajv/dist/2020.js 3+1). Fixing them properly means changing // import style in package source, which is deliberately out of scope for a -// move-first migration. Tracked on META-165. +// move-first migration. Tracked as its own interoperability work. // -// Everything else that used to live here was removed during META-239: +// Everything else that used to live here was removed during the extraction: // * `declare module '@workspacejson/spec'` — a stale, v0.3-only duplicate of a // standard-owned contract that was WINNING over the real package's types. // scripts/check-architecture.mjs now rejects its reintroduction. From 1c6eb8938b3c6ba183b473141a159e8c9ab8da02 Mon Sep 17 00:00:00 2001 From: Qwynn Marcelle Date: Sun, 26 Jul 2026 01:10:52 -0400 Subject: [PATCH 2/2] ci: bump actions off the deprecated Node 20 runner actions/checkout, actions/setup-node and pnpm/action-setup all targeted Node 20, which GitHub now force-runs on Node 24 and annotates as deprecated on every run. A polish pass should not leave a standing warning on CI. Dependabot's github-actions ecosystem keeps these current from here. --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b65ab89..dd0aa8c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,11 +28,11 @@ jobs: node-version: [20, 22] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v6 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v7 with: node-version: ${{ matrix.node-version }} cache: pnpm