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

## 0.1.7 source candidate - 2026-10-10

- Add `artifacts --format html`: a self-contained offline delivery review with
declared-check status, file/Receipt binding, record-identity defects, incomplete
checks, observed metadata and concrete review actions.
- Preserve JSON/text and exit semantics. HTML uses the same local-path redaction,
escapes every metadata field, includes no script or external request, and keeps
source truth, author authentication and business approval separate.
- Locate affected CSV data records in preserved expected/delivered files, including
split groups, without reporting IDs or amounts. Keep 8 row references per group,
32 examples per check and 64 total; unknown positions are never invented.
Default JSON/text observation and report shapes stay intact.
- Show at most 100 file details and 64 reconciliation cards, prioritizing failed
and incomplete checks. Keep full-scope counts and explicit shortening notices;
raw IDs, values and file contents never enter the report.
- This is a source update. GitHub0.1.4/npm0.1.0 remain the published packages.

## 0.1.6 source candidate - 2026-10-10

- Compare ordered two-to-four-column string identities as well as existing
Expand Down
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

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

> **Alpha · source candidate v0.1.6 · Node.js 20+**
> **Alpha · source candidate v0.1.7 · 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.

Source v0.1.6 adds bounded ordered composite keys to per-key integer reconciliation. The published GitHub package
Source v0.1.7 adds an offline human acceptance report on top of bounded composite-key reconciliation. The published GitHub package
is still v0.1.4 and npm is still 0.1.0; neither includes the new rule.

[![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 @@ -21,7 +21,7 @@ npm install -g @chumanic/skillsync@0.1.0
skillsync verify --path . --target codex
```

**From source (0.1.6 candidate):**
**From source (0.1.7 candidate):**

```bash
git clone https://github.com/Chumaniac/skillsync.git
Expand Down Expand Up @@ -62,6 +62,10 @@ The command above verifies the included sample Skill. Replace the fixture path w

## What you get

- Source0.1.7 produces an offline acceptance report with `artifacts --format html`.
Reviewers see file binding, failed or incomplete identity checks, affected
metadata and concrete review actions in a browser. The report includes no raw
record values, scripts or network requests; see [the visible review guide](docs/artifact-delivery.md#hand-a-visible-report-to-a-reviewer).
- Source0.1.6 supports ordered two-to-four-column identity tuples for integer
reconciliation. The [multi-organization example](fixtures/product/tenant-reconciliation/README.md)
separates repeated local IDs, without delimiter collisions or raw ID reporting.
Expand Down
48 changes: 48 additions & 0 deletions docs/artifact-delivery.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,54 @@ Receipt hash, artifact hashes/sizes, and deterministic artifact-set digest befor
accepting a complete result. A consistently rewritten manifest is not signed or
authenticated evidence; the report marks provenance as not authenticated.

## Hand a visible report to a reviewer

Source0.1.7 adds `--format html`. The report opens directly in a browser, with no
server, script, login, provider or network request. A reviewer sees the declared
result, supplied file/Receipt binding, failed or incomplete reconciliation,
affected file/column metadata and a concrete next review step.

```bash
# Build the current source first. Use a new destination; keep shell no-clobber.
(set -C; node dist/cli/index.js artifacts \
--contract fixtures/product/tenant-reconciliation/contract.json \
--path fixtures/product/tenant-reconciliation/artifacts \
--format html > acceptance.html)
```

For a retained physical delivery, select `--delivery` instead. Exit0 means the
declared local checks passed, exit1 retains findings/incomplete checks, and exit2
reports invalid setup. A failed inspection still produces a reviewable HTML
report; caller-side redirect errors remain the caller's responsibility.

Reconciliation cards separate expected/delivered identity counts, missing or
unexpected identities, and mismatched values. Unknown checks never display zero
mismatches as accepted evidence. The review actions point to saved originals,
declared identity columns and reviewed expectations; they do not execute a
correction or establish business approval.

HTML also requests bounded CSV record positions for affected identity groups.
Expand "Locate affected CSV records" to find the expected and delivered data
records in the original local files. Positions are 1-based, exclude the header,
and count a quoted multiline record once; they are not physical text-line numbers.
Up to8 positions per group, 32 examples per check and 64 examples across the
invocation are retained. Truncated or unavailable positions are explicit, and
unknown checks carry no position evidence. No IDs, fingerprints or amounts are
returned. Ordinary JSON/text inspections do not request or retain this metadata.

HTML shows at most 100 file details, 64 reconciliation cards and the existing
256 findings. Failed/incomplete cards appear first, full-scope counts remain
visible, and metadata longer than160 characters is shortened. Use the existing
JSON output when complete metadata is required. HTML retains no raw record IDs,
integer values, contents, key fingerprints or absolute host paths. Every visible
field is escaped after the existing path redaction. CSP disables scripts and
external resources. The output is bounded by the report ceilings and has no
client timers or caches.

The report is a captured local snapshot. Keep the successful original delivery;
rerun read-only checks on its files to capture a later state. Source0.1.7 HTML is
absent from the published GitHub0.1.4/npm0.1.0 packages.

## Per-key integer reconciliation

Source candidate 0.1.6 supports `keyed_integer_sum_equals`. It is absent from
Expand Down
10 changes: 10 additions & 0 deletions docs/deep-optimization-roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ Keep the existing 10,000-file/16 MiB-file/64 MiB-total and 256-finding ceilings.

## Domain expansion

### Visible business continuation — 2026-10-10

Source0.1.7 converts physical-file and composite-identity results into an offline
acceptance report. Operations reviewers can see which declared relation needs
attention, whether counts are incomplete, and which original files/columns to
review without exposing the underlying record identities or values. The local
CLI generates the HTML from the real inspection result; it does not embed a
fabricated dashboard or claim that an automated check supplies business approval.
The source-only HTML and current published packages remain separate.

### Continuation cycle — 2026-10-10

Source0.1.6 implements ordered two-to-four-column string keys for integer
Expand Down
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.1.6",
"version": "0.1.7",
"private": false,
"publishConfig": {
"access": "public"
Expand Down
6 changes: 4 additions & 2 deletions src/cli/commands/artifacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { inspectArtifactContent, type ArtifactFacts, type ArtifactFinding } from
import { createArtifactReferenceIndex } from "../../domain/artifact-references.js";
import { createArtifactReconciliationIndex, type ReconciliationSummary } from "../../domain/artifact-reconciliation.js";
import { redactLocalPaths } from "../../reporters/local-paths.js";
import { renderArtifactsHtml } from "../../reporters/artifacts-html.js";
import { readWorkspaceFile, scanStagedWorkspace, WorkspaceTreeError } from "../../sandbox/workspace-tree.js";

const hash = (bytes: Uint8Array | string) => createHash("sha256").update(bytes).digest("hex");
Expand All @@ -19,7 +20,7 @@ const manifestSchema = z.object({ schema: z.literal("skilltape.dev/delivery/v1")
files: z.array(z.object({ path: z.string().min(1).max(1024), bytes: z.number().int().min(0).max(16 * 1024 * 1024), sha256: sha }).strict()).max(10_000),
}).strict();

export type ArtifactsOptions = { contract: string; delivery?: string; path?: string };
export type ArtifactsOptions = { contract: string; delivery?: string; path?: string; includeRowEvidence?: boolean };
export type ArtifactReport = {
schema: "skillsync.artifacts-report/v1"; status: "passed" | "failed"; exitCode: number;
evidence: "physical-files"; execution: "not-run"; provenance: "not-authenticated";
Expand Down Expand Up @@ -89,7 +90,7 @@ export async function runArtifacts(options: ArtifactsOptions): Promise<ArtifactR
}
const facts = new Map<string, ArtifactFacts>();
const references = createArtifactReferenceIndex(contract);
const reconciliation = createArtifactReconciliationIndex(contract);
const reconciliation = createArtifactReconciliationIndex(contract, { rowEvidence: options.includeRowEvidence });
try {
const rules = new Map(contract.files.map(file => [file.path, file]));
const tree = await scanStagedWorkspace(root, {
Expand Down Expand Up @@ -146,6 +147,7 @@ export async function runArtifacts(options: ArtifactsOptions): Promise<ArtifactR
}

export function renderArtifacts(report: ArtifactReport, format = "text"): string {
if (format === "html") return renderArtifactsHtml(report);
const safe = redactLocalPaths(report);
if (format === "json") return JSON.stringify(safe, null, 2) + "\n";
return [`Artifact validation: ${safe.status}`, `Evidence: physical files; execution: not-run; receipt binding: ${safe.receipt_binding}`,
Expand Down
6 changes: 3 additions & 3 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -569,10 +569,10 @@ export function createCli(io: CliIO = defaultCliIO): Command {
.requiredOption("--contract <path>", "JSON or YAML artifact contract")
.option("--delivery <path>", "SkillTape delivery bundle directory")
.option("--path <path>", "Standalone artifact directory; choose this or --delivery")
.option("--format <format>", "Output format: text or json", "text")
.option("--format <format>", "Output format: text, json or offline html", "text")
.action(async (options: ArtifactsOptions & { format?: string }) => {
if (options.format !== "text" && options.format !== "json") throw new Error("artifacts format must be text or json");
const report = await runArtifacts(options);
if (options.format !== "text" && options.format !== "json" && options.format !== "html") throw new Error("artifacts format must be text, json or html");
const report = await runArtifacts({ ...options, includeRowEvidence: options.format === "html" });
io.writeOut(renderArtifacts(report, options.format));
if (report.exitCode) io.setExitCode?.(report.exitCode);
});
Expand Down
4 changes: 2 additions & 2 deletions src/domain/artifact-content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { parseStrictJson } from "./strict-json.js";
export type ArtifactFinding = { code: string; path?: string; field?: string | string[]; row?: number; message?: 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>) => void;
export type ArtifactRowObserver = (values: ReadonlyMap<string, string | number>, dataRecord?: number) => void;

function* csvRows(text: string, maxFields: number): Generator<string[]> {
let field = "", row: string[] = [], quoted = false, afterQuote = false, active = false;
Expand Down Expand Up @@ -141,7 +141,7 @@ export function inspectArtifactContent(rule: ArtifactFileRule, bytes: Uint8Array
else facts.integers.set(column.name, total);
}
}
if (values) observeRow?.(values);
if (values) observeRow?.(values, count);
}
facts.rows = count;
if (count < rule.min_rows) issue("artifact.csv-too-few-rows");
Expand Down
Loading
Loading