Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ consumed here as released packages.
> **Status.** The architecture below landed in META-247 and is the ratified
> target shape.
>
> **Registry snapshot, verified 2026-08-04:** both packages are published —
> `@workspacejson/cli@0.5.2` and `agents-audit@0.4.4`.
> **Registry snapshot, verified 2026-08-18:** the published versions are
> `@workspacejson/cli@0.5.2` and `agents-audit@0.4.4`. The manifest table below
> declares `0.6.0` for the CLI, which is the authority-migration release
> prepared but not yet tagged; the registry does not carry it until
> `publish-cli.yml` runs on its `cli-v0.6.0` tag.
>
> The versions shown in the table below are the versions declared by this
> repository's package manifests. `pnpm run check:package-docs` keeps those
Expand All @@ -21,14 +24,16 @@ consumed here as released packages.
> separately under META-293.
>
> Package semver is independent of the specification profile: `@workspacejson/cli`
> at `0.5.x` produces specification **v0.4** artifacts and is not evidence that
> schema v0.5 shipped.
> at `0.6.x` produces specification **v0.4** artifacts and is not evidence that
> schema v0.6 shipped. It consumes `@workspacejson/spec` and
> `@workspacejson/rules` at `0.5.0`, whose document profile is likewise still
> `generated.specVersion: "0.4"`.

## Packages

| Directory | Package | Version | Role |
| -- | -- | -- | -- |
| [`packages/cli/`](./packages/cli/) | `@workspacejson/cli` | `0.5.2` | the neutral producer and its `workspacejson` binary |
| [`packages/cli/`](./packages/cli/) | `@workspacejson/cli` | `0.6.0` | the neutral producer and its `workspacejson` binary |
| [`packages/agents-audit-compat/`](./packages/agents-audit-compat/) | `agents-audit` | `0.4.4` | frozen compatibility bridge; preserves the historical command and API |
| [`packages/mining-core/`](./packages/mining-core/) | `@workspacejson/mining-core` | `0.0.0`, private | L0 commit-graph mining core — extraction, path identity, completeness semantics (META-297 Phases 1–2) |

Expand Down
54 changes: 54 additions & 0 deletions packages/cli/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,59 @@
# Changelog — `@workspacejson/cli`

## 0.6.0

### Minor Changes

- Move the producer's standard authority to the canonical `workspacejson/standard`
release: `@workspacejson/spec` and `@workspacejson/rules` `0.4.4` -> `0.5.0`.

This is an authority-migration release. No capability, mining, ranking,
provenance or command-surface changes ship with it.

**Why the packages moved, not just the versions.** `0.4.4` of both packages was
published from `workspace-json/agents-audit`; `0.5.0` is published from
`workspacejson/standard`. The dependency edge, not only the version range, now
points at the canonical publisher.

**Why this is a minor rather than a patch.** Nothing in this package's own API
changed. But `dist/index.d.ts` carries `WorkspaceJsonV4` in an exported
signature (`GenerateResult.content`, `writeWorkspaceAtomically`), so `spec`'s
`0.5.0` source-level break propagates to anyone consuming those types.
`CoChangeEntry` is now a union whose members declare the other form's field as
`?: never`, so reading `entry.rate` off it without narrowing stops compiling:

```ts
const r: number = result.content.generated.coChange[0].rate; // was fine, now a type error
```

`@workspacejson/spec` and `@workspacejson/rules` classified that propagation as a
minor for exactly this reason, and this package inherits it rather than hiding
it behind a patch.

**Artifact output is unchanged.** Every `.agents/workspace.json` this producer
emits is byte-identical to the one the previously pinned build emits, modulo the
`generatedAt` and `hygiene.scannedAt` timestamps. That was verified by running
the pinned build and this one over the same inputs and diffing the normalized
artifacts; the frozen-source parity harness for the `agents-audit` bridge also
reports its ratified baseline of four expected differences, unchanged.

**`agents-audit` deliberately stays on `0.4.4`.** The compatibility bridge is
frozen and locked for judging (OWNERSHIP.md), is not published from this
repository, and its published artifact declares no dependency on this package.
Moving its pins would put a frozen, parity-gated surface at risk for no benefit,
so the two packages now resolve different versions of the standard on purpose.

**One guarded behavior change, currently unreachable.** `computeHygieneScore`
returns `HygieneScore | null` as of `rules@0.5.0`, returning `null` when a scan
observed nothing. Where that happens the producer now omits `generated.hygiene`
entirely — the field is not in the schema's `generated.required` set, and
absence is the only truthful option, since every placeholder value is a
measurement claim that was not made. This path is not reachable through the
current producer: its rule set emits at least one finding for every input tried,
including an empty repository, so `computeHygieneScore` never receives the empty
array that produces `null`. The handling is in place because the type demands
it, not because output changed.

## 0.5.2

### Patch Changes
Expand Down
8 changes: 4 additions & 4 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@workspacejson/cli",
"version": "0.5.2",
"description": "The workspace.json producer \u2014 scans a repository and generates .agents/workspace.json deterministically, preserving human-authored manual evidence.",
"version": "0.6.0",
"description": "The workspace.json producer scans a repository and generates .agents/workspace.json deterministically, preserving human-authored manual evidence.",
"license": "Apache-2.0",
"author": "workspace.json contributors",
"homepage": "https://workspacejson.dev",
Expand Down Expand Up @@ -58,8 +58,8 @@
"prepublishOnly": "pnpm build && pnpm test && node ../../scripts/verify-package-tarball.mjs"
},
"dependencies": {
"@workspacejson/rules": "0.4.4",
"@workspacejson/spec": "0.4.4",
"@workspacejson/rules": "0.5.0",
"@workspacejson/spec": "0.5.0",
"commander": "^12.1.0",
"ora": "^8.0.1",
"picocolors": "^1.0.1"
Expand Down
43 changes: 34 additions & 9 deletions packages/cli/src/producer/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,14 +352,35 @@ export async function generateWorkspaceJson(
}
: { agentFiles: { workspaceJson: '.agents/workspace.json' } }),
},
hygiene: {
score: score.value,
grade: score.grade,
failCount: score.breakdown.failCount,
warnCount: score.breakdown.warnCount,
scannedAt:
(existing?.generated.hygiene as { scannedAt?: string } | undefined)?.scannedAt ?? now,
},
// `computeHygieneScore` returns null when the scan observed nothing —
// no findings, and no file-count denominator to say anything was
// examined (ADR-003 A-002, `@workspacejson/rules@0.5.0`). Under 0.4.4
// that same input returned `{ value: 100, grade: 'A' }`, so a scan that
// looked at nothing certified this repository as flawless and that value
// reached the published artifact.
//
// The block is therefore OMITTED rather than filled with a substitute.
// `generated.hygiene` is not in the schema's `generated.required` set, so
// absence is valid; and absence is the only truthful option, because
// every available placeholder — a zeroed block, or the old 100/A — is a
// measurement claim we did not make. Absent, not wrong.
//
// This is the one artifact-visible consequence of the 0.4.4 -> 0.5.0
// authority migration. It changes bytes only for a repository that
// produced zero findings; wherever any evidence exists the arithmetic is
// unchanged, which is what the parity fixtures assert.
...(score === null
? {}
: {
hygiene: {
score: score.value,
grade: score.grade,
failCount: score.breakdown.failCount,
warnCount: score.breakdown.warnCount,
scannedAt:
(existing?.generated.hygiene as { scannedAt?: string } | undefined)?.scannedAt ?? now,
},
}),
// Commit-history evidence is PRESERVED, never rebuilt, by ordinary
// generation — see history-carry-forward.ts for why this one part of the
// producer-owned section is carried rather than regenerated.
Expand Down Expand Up @@ -390,7 +411,11 @@ export async function generateWorkspaceJson(
const unchanged = existing !== undefined && isMateriallyCurrent(existing, workspace);
if (unchanged) {
workspace.generated.generatedAt = existing!.generated.generatedAt;
} else {
} else if (workspace.generated.hygiene !== undefined) {
// Guarded because the block is absent when the scan observed nothing; see
// the omission note above. Stamping `scannedAt` onto a block that does not
// exist would resurrect it as a bare timestamp, which reads as "scanned,
// perfectly clean" — precisely the false claim the omission removes.
(workspace.generated.hygiene as { scannedAt: string }).scannedAt = now;
}
if (!options.dryRun && !options.check && !unchanged) {
Expand Down
31 changes: 26 additions & 5 deletions packages/cli/src/spec-contract-visibility.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest';
import { validate, validateV4, version } from '@workspacejson/spec';
import type { CoChangeEntry, FragilityEntry, WorkspaceJsonV4 } from '@workspacejson/spec';
import { validate, validateStoredKey, validateV4, version } from '@workspacejson/spec';
import type { CoChangeEntry, FragilityEntry, StoredKeyResult, WorkspaceJsonV4 } from '@workspacejson/spec';

/**
* META-244 regression guard: the CLI compiles against the REAL published
Expand All @@ -19,9 +19,30 @@ import type { CoChangeEntry, FragilityEntry, WorkspaceJsonV4 } from '@workspacej
*/
describe('@workspacejson/spec contract visibility', () => {
it('exposes the published version as a value, not a local guess', () => {
// The removed stub declared `version: string`. The real package declares the
// literal "0.4.4" — so this also pins which contract we compiled against.
expect(version).toBe('0.4.4');
// Pins which contract we compiled against. This assertion alone no longer
// distinguishes the real package from the removed stub: the stub declared
// `version: string`, and as of 0.5.0 so does the real package, because the
// literal was replaced by a value read at build time. The discriminator
// moved to the stored-key surface below — see that test for why.
expect(version).toBe('0.5.0');
});

it('exposes the stored-key surface, which no stub ever declared', () => {
// This is the load-bearing half of the META-244 guard after 0.5.0.
//
// The guard works by importing symbols that exist ONLY in the real
// published package, so reintroducing a handwritten
// `declare module '@workspacejson/spec'` fails at COMPILE time (TS2305 /
// TS2724) rather than silently shadowing the real typings. `version` used
// to carry that weight via its literal type; 0.5.0 widened it to `string`,
// which is exactly what the stub declared, so it can no longer tell them
// apart.
//
// `validateStoredKey` and `StoredKeyResult` are new in 0.5.0 and were never
// present in the stub, so they restore the compile-time property the
// literal used to provide.
const accepted: StoredKeyResult = validateStoredKey('src/a.ts');
expect(accepted.valid).toBe(true);
});

it('exposes validateV4, which the removed ambient stub did not declare', () => {
Expand Down
32 changes: 28 additions & 4 deletions pnpm-lock.yaml

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

8 changes: 6 additions & 2 deletions scripts/check-package-docs.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,11 @@ const cases = [
mutate: (root) => {
const p = join(root, "packages/cli/package.json");
const manifest = JSON.parse(read(p));
manifest.version = "0.6.0";
// Deliberately choose a value that cannot equal the repository's current
// release version. This red test previously hard-coded 0.6.0; once 0.6.0
// became the real manifest version, the mutation became a no-op and the
// test falsely accused the guard of accepting a contradiction.
manifest.version = "9.9.9";
write(p, `${JSON.stringify(manifest, null, 2)}\n`);
},
},
Expand Down Expand Up @@ -213,4 +217,4 @@ for (const testCase of legitimate) {
}

console.log(`\nPackage documentation guard red tests: ${passed} passed, ${failed} failed.`);
if (failed > 0) process.exit(1);
if (failed > 0) process.exit(1);
Loading