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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 0.2.1 - 2026-10-19

- Locate citation metadata failures in both the delivered table and referenced table, including data-record positions and declared column mappings. Raw IDs and cell values remain omitted.
- Add a direct offline review link and replace unrelated zero-value statistic cards with inspected-file and reported-finding counts.
- Retain legacy baseline reports, accept all eight declared metadata mappings, and bound expanded HTML reference details to32 findings. Checks remain local and read-only.

## 0.2.0 - 2026-10-19

- Distribute exact citation digest/revision checks in the installable GitHub CLI
Expand Down
18 changes: 7 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,10 @@

**Verify Agent Skills before you trust them.** SkillSync checks a local Skill's provenance, compatibility, and changes without executing it.

> **Alpha · GitHub release v0.2.0 · Node.js 20+**
> **Alpha · GitHub release v0.2.1 · Node.js 20+**
> SkillSync performs offline checks of local Skill content. It does not execute Skill scripts, does not read credentials, and does not enable live provider, remote-worker, or runtime capabilities.

GitHub v0.2.0 binds citations to exact source metadata, including digest and
revision, and retains the offline human acceptance report, correction review
and bounded composite-key reconciliation. Mismatched metadata produces an
explicit finding without copying record values into the report. The npm
registry remains at 0.1.0; use the verified GitHub archive for current features.
GitHub v0.2.1 locates citation metadata failures by file, data record and compared columns. It checks exact digests and revisions without copying record values into reports. The offline review, correction workflow and bounded composite-key reconciliation remain available. The npm registry stays at 0.1.0; use the verified GitHub archive for current features.

[![Terminal demo](https://raw.githubusercontent.com/Chumaniac/skillsync/main/docs/assets/verify-demo.svg)](https://github.com/Chumaniac/skillsync/blob/main/docs/assets/verify-demo.svg)

Expand All @@ -19,7 +15,7 @@ registry remains at 0.1.0; use the verified GitHub archive for current features.

Tagged builds now publish a CLI tarball, checksums, a validated CycloneDX SBOM
and a source manifest on [GitHub Releases](https://github.com/Chumaniac/skillsync/releases).
[GitHub v0.2.0](https://github.com/Chumaniac/skillsync/releases/tag/v0.2.0)
[GitHub v0.2.1](https://github.com/Chumaniac/skillsync/releases/tag/v0.2.1)
is distributed as a versioned CLI tarball with checksums, SBOM, source manifest
and GitHub provenance. Its release workflow requires the exact immutable tag
and an already successful main check before packaging. Earlier v0.1.7 assets
Expand All @@ -28,12 +24,12 @@ Installation uses Node.js 20+ and fetches the package's public dependencies;
it needs no npm login.

```bash
release_base="https://github.com/Chumaniac/skillsync/releases/download/v0.2.0"
for asset in chumanic-skillsync-0.2.0.tgz checksums.txt sbom.cyclonedx.json release-manifest.json; do
release_base="https://github.com/Chumaniac/skillsync/releases/download/v0.2.1"
for asset in chumanic-skillsync-0.2.1.tgz checksums.txt sbom.cyclonedx.json release-manifest.json; do
curl --fail --location --output "$asset" "$release_base/$asset"
done
shasum -a 256 -c checksums.txt
npm install --prefix ./skillsync-tools ./chumanic-skillsync-0.2.0.tgz
npm install --prefix ./skillsync-tools ./chumanic-skillsync-0.2.1.tgz
./skillsync-tools/node_modules/.bin/skillsync --version
```

Expand All @@ -51,7 +47,7 @@ npm install -g @chumanic/skillsync@0.1.0
skillsync verify --path . --target codex
```

**From source (0.2.0):**
**From source (0.2.1):**

```bash
git clone https://github.com/Chumaniac/skillsync.git
Expand Down
7 changes: 7 additions & 0 deletions docs/artifact-delivery.md
Original file line number Diff line number Diff line change
Expand Up @@ -338,3 +338,10 @@ The interface is informed by source-linked citation review in
code or dependencies were copied. This local check proves consistency between
observed metadata only. It does not fetch the cited source, authenticate a
publisher, or establish that the underlying source bytes or claims are true.


### Locate a mismatched reference in GitHub 0.2.1

Metadata mismatch and unavailable findings include an optional `reference` object: the declared target path, lookup column, matched data-record position, and compared source/target column names. It contains no record ID or cell value. JSON and text retain these coordinates; the offline HTML displays the first32 reference locations with up to8 mappings each and links directly to the review section. Counts still cover all reported findings. Data-record positions exclude the CSV header, including for quoted multiline records.

The baseline reader continues accepting older reports without this context. Reference context uses the same relative-path, row and eight-column bounds as the current contract. It locates evidence for review; it does not fetch sources, authenticate authors or approve corrections.
2 changes: 2 additions & 0 deletions fixtures/product/citation-metadata/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@ the declared metadata columns. It does not copy the ID or cell values into
JSON, text or HTML. Preserve the original example when trying a correction.

See the [contract guide](../../../docs/artifact-delivery.md#bind-citations-to-exact-source-metadata).

GitHub0.2.1 adds the referenced filename, matched data-record position and compared column mapping to the offline review. Use the review link to jump to the finding. IDs and metadata values remain omitted; preserve originals while checking a copied correction.
4 changes: 2 additions & 2 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@chumanic/skillsync",
"version": "0.2.0",
"version": "0.2.1",
"private": false,
"publishConfig": {
"access": "public"
Expand Down
2 changes: 1 addition & 1 deletion src/cli/commands/artifacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,5 +176,5 @@ export function renderArtifacts(report: ArtifactReport, format = "text"): string
...(safe.comparison.findings ? [`Finding categories: ${safe.comparison.findings.added} new; ${safe.comparison.findings.ongoing} ongoing; ${safe.comparison.findings.resolved} resolved`] : [])] : []),
...(safe.comparison?.checks ? [`Record check changes: ${safe.comparison.checks.filter(check => check.change === "improved").length} improved; ${safe.comparison.checks.filter(check => check.change === "regressed").length} regressed; ${safe.comparison.checks.filter(check => check.change === "changed").length} mixed changes; ${safe.comparison.checks.filter(check => check.change === "unchanged").length} unchanged`] : []),
`Files: ${safe.files.length}`, ...(safe.reconciliations ?? []).map(item => `Record reconciliation: ${item.status}${item.status !== "unknown" ? `; source keys=${item.sourceKeys}; target keys=${item.targetKeys}; missing=${item.missingKeys}; unexpected=${item.unexpectedKeys}; mismatched values=${item.valueMismatches}` : ""}`),
...safe.findings.map(finding => `${finding.code}${finding.path ? ` file=${JSON.stringify(finding.path)}` : ""}${finding.field ? ` field=${JSON.stringify(finding.field)}` : ""}${finding.row !== undefined ? ` row=${finding.row}` : ""}${finding.message ? ` ${finding.message}` : ""}`)].join("\n") + "\n";
...safe.findings.map(finding => `${finding.code}${finding.path ? ` file=${JSON.stringify(finding.path)}` : ""}${finding.field ? ` field=${JSON.stringify(finding.field)}` : ""}${finding.row !== undefined ? ` row=${finding.row}` : ""}${finding.reference ? ` reference=${JSON.stringify(finding.reference)}` : ""}${finding.message ? ` ${finding.message}` : ""}`)].join("\n") + "\n";
}
3 changes: 2 additions & 1 deletion src/domain/artifact-content.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { ArtifactFileRule, ArtifactScalarRule } from "./artifact-contract.js";
import { parseStrictJson } from "./strict-json.js";

export type ArtifactFinding = { code: string; path?: string; field?: string | string[]; row?: number; message?: string };
export type ArtifactFinding = { code: string; path?: string; field?: string | string[]; row?: number; message?: string;
reference?: { path: string; field: string; row: number; matches: Array<{ source: string; target: string }> } };
export type ArtifactFacts = { rows?: number; integers: Map<string, number> };
export type ArtifactCellObserver = (field: string, value: string, row: number) => void;
export type ArtifactRowObserver = (values: ReadonlyMap<string, string | number>, dataRecord?: number) => void;
Expand Down
9 changes: 6 additions & 3 deletions src/domain/artifact-references.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,19 @@ export function createArtifactReferenceIndex(contract: ArtifactContract) {
for (const [position, check] of checks.entries()) {
const targets = index(check.target.path, check.target.field).targets!;
for (const source of index(check.source.path, check.source.field).sources!) {
if (!targets.has(source.digest)) {
const targetRow = targets.get(source.digest);
if (targetRow === undefined) {
findings.push({ code: "artifact.reference-missing", path: check.source.path, field: check.source.field, row: source.row });
if (findings.length >= 256) return findings;
} else if (check.matches) {
const sourceValue = metadata[position].source.get(source.row);
const targetValue = metadata[position].target.get(targets.get(source.digest)!);
const targetValue = metadata[position].target.get(targetRow);
const unavailable = sourceValue === undefined || targetValue === undefined;
if (unavailable || sourceValue !== targetValue) {
findings.push({ code: unavailable ? "artifact.reference-value-unavailable" : "artifact.reference-value-mismatch",
path: check.source.path, field: check.matches.map(pair => pair.source), row: source.row });
path: check.source.path, field: check.matches.map(pair => pair.source), row: source.row,
reference: { path: check.target.path, field: check.target.field, row: targetRow,
matches: check.matches.map(pair => ({ source: pair.source, target: pair.target })) } });
if (findings.length >= 256) return findings;
}
}
Expand Down
5 changes: 4 additions & 1 deletion src/domain/artifact-report-comparison.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,11 @@ const reportSchema = z.object({ schema: z.literal("skillsync.artifacts-report/v1
files: z.array(z.object({ path, bytes: z.number().int().min(0).max(16 * 1024 * 1024),
sha256: sha, rows: count.optional() }).strict()).max(10_000),
findings: z.array(z.object({ code: z.string().min(1).max(128).regex(/^[a-z][a-z0-9.-]*$/),
path: path.optional(), field: z.union([field, z.array(field).min(1).max(4)]).optional(),
path: path.optional(), field: z.union([field, z.array(field).min(1).max(8)]).optional(),
row: count.min(1).optional(), message: z.string().max(4096).optional(),
reference: z.object({ path, field, row: count.min(1),
matches: z.array(z.object({ source: field, target: field }).strict()).min(1).max(8),
}).strict().optional(),
}).strict()).max(256), reconciliations: z.array(summary).max(128).optional(),
comparison: artifactComparisonSchema.optional(),
}).strict().superRefine((report, context) => {
Expand Down
Loading
Loading