jig-ui 0.18.2 → 0.18.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.18.3 (2026-09-25)
4
+
5
+ Recording a decision no longer undoes the critiques written before it, and an
6
+ older probe file says it is older rather than forged.
7
+
8
+ ### Fixed
9
+
10
+ - **A decision recorded after a critique no longer undoes it.** On jig-site one
11
+ `/jig decide` session added eight decisions; every finished critique turned
12
+ "incomplete", and the gate stopped an unrelated spec session over records that
13
+ could not have judged them. `jig verdicts` now asks git: a decision whose
14
+ heading was added in a commit the critique's `decisions.json` commit does not
15
+ contain, or not committed yet, is left for the next critique and listed as
16
+ such. Without git, or while `decisions.json` has uncommitted changes (a
17
+ critique in progress), every decision is still required.
18
+ - **An older probe file is called older, not forged.** After a release changed
19
+ the probe's version, the old files read "not output of `jig probe` … do not
20
+ write it by hand", and an agent stopped work believing they were fabricated.
21
+ They now say which version took them and to record them again.
22
+
3
23
  ## 0.18.2 (2026-09-25)
4
24
 
5
25
  Every release in this changelog now carries its date and a one-line summary,
package/dist/index.js CHANGED
@@ -5673,8 +5673,9 @@ Could not write to ${wireTarget}: ${err.message}`);
5673
5673
  }
5674
5674
 
5675
5675
  // src/commands/verdicts.ts
5676
+ import { execFileSync as execFileSync3 } from "child_process";
5676
5677
  import { existsSync as existsSync23, readFileSync as readFileSync30 } from "fs";
5677
- import { join as join37, resolve as resolve5 } from "path";
5678
+ import { join as join37, relative as relative4, resolve as resolve5 } from "path";
5678
5679
 
5679
5680
  // src/rules/citations.ts
5680
5681
  import { join as join29 } from "path";
@@ -6258,6 +6259,10 @@ function readProbes(projectRoot, dir, errors) {
6258
6259
  for (const f of readdirSync13(dir).filter((n) => /^probe-\d+\.json$/.test(n)).sort()) {
6259
6260
  try {
6260
6261
  const p = JSON.parse(readFileSync27(join34(dir, f), "utf8"));
6262
+ if (typeof p.jigProbe === "number" && p.jigProbe < PROBE_VERSION && typeof p.width === "number") {
6263
+ errors.push(`${f} was taken by an older \`jig probe\` (version ${p.jigProbe}; this Jig reads version ${PROBE_VERSION}). Record it again with \`jig probe --run\`.`);
6264
+ continue;
6265
+ }
6261
6266
  if (p.jigProbe !== PROBE_VERSION || typeof p.width !== "number") {
6262
6267
  errors.push(`${f} is not output of \`jig probe\` (version ${PROBE_VERSION}). Re-run the probe; do not write it by hand.`);
6263
6268
  continue;
@@ -6366,21 +6371,24 @@ function decisionsFile(projectRoot) {
6366
6371
  return void 0;
6367
6372
  }
6368
6373
  function decisionNames(projectRoot) {
6374
+ return [...decisionHeadings(projectRoot).keys()];
6375
+ }
6376
+ function decisionHeadings(projectRoot) {
6377
+ const headings = /* @__PURE__ */ new Map();
6369
6378
  const path = decisionsFile(projectRoot);
6370
- if (!path) return [];
6379
+ if (!path) return headings;
6371
6380
  let body;
6372
6381
  try {
6373
6382
  body = readFileSync28(join35(projectRoot, path), "utf8");
6374
6383
  } catch {
6375
- return [];
6384
+ return headings;
6376
6385
  }
6377
- const names = [];
6378
6386
  for (const match of body.matchAll(HEADING3)) {
6379
6387
  const name = match[2].replace(/[`*]/g, "").trim();
6380
6388
  if (!name || NOT_A_DECISION.test(name)) continue;
6381
- if (!names.includes(name)) names.push(name);
6389
+ if (!headings.has(name)) headings.set(name, match[0].trim());
6382
6390
  }
6383
- return names;
6391
+ return headings;
6384
6392
  }
6385
6393
 
6386
6394
  // src/check/spec-shape.ts
@@ -6583,11 +6591,41 @@ function checkDecisions(projectRoot, dir, errors) {
6583
6591
  else if (ABSENCE.test(reason)) errors.push(`decisions.json: "${match}" \u2014 "${reason}" says the decision was not read.`);
6584
6592
  if (v.verdict === "finding") findings++;
6585
6593
  }
6586
- const missing = required.filter((r) => !seen.has(r));
6594
+ const unjudged = required.filter((r) => !seen.has(r));
6595
+ const since = decisionsSince(projectRoot, dir, unjudged);
6596
+ const missing = unjudged.filter((r) => !since.includes(r));
6597
+ const total = required.length - since.length;
6587
6598
  if (missing.length) {
6588
- errors.push(`decisions.json: ${missing.length} of ${required.length} decisions have no verdict: ${missing.join(", ")}.`);
6599
+ errors.push(`decisions.json: ${missing.length} of ${total} decisions have no verdict: ${missing.join(", ")}.`);
6600
+ }
6601
+ return { state: missing.length ? "incomplete" : "ran", judged: total - missing.length, total, findings, ...since.length ? { since } : {} };
6602
+ }
6603
+ function decisionsSince(projectRoot, dir, unjudged) {
6604
+ if (unjudged.length === 0) return [];
6605
+ const path = decisionsFile(projectRoot);
6606
+ if (!path) return [];
6607
+ const git = (args) => execFileSync3("git", args, { cwd: projectRoot, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
6608
+ try {
6609
+ const verdictFile = relative4(projectRoot, join37(dir, "decisions.json"));
6610
+ if (git(["status", "--porcelain", "--", verdictFile])) return [];
6611
+ const judgedIn = git(["log", "-1", "--format=%H", "--", verdictFile]);
6612
+ if (!judgedIn) return [];
6613
+ const headings = decisionHeadings(projectRoot);
6614
+ return unjudged.filter((name) => {
6615
+ const line = headings.get(name);
6616
+ if (!line) return false;
6617
+ const addedIn = git(["log", "--format=%H", "-S", line, "--", path]).split("\n").filter(Boolean).pop();
6618
+ if (!addedIn) return true;
6619
+ try {
6620
+ git(["merge-base", "--is-ancestor", addedIn, judgedIn]);
6621
+ return false;
6622
+ } catch {
6623
+ return true;
6624
+ }
6625
+ });
6626
+ } catch {
6627
+ return [];
6589
6628
  }
6590
- return { state: missing.length ? "incomplete" : "ran", judged: required.length - missing.length, total: required.length, findings };
6591
6629
  }
6592
6630
  function verifyVerdicts(opts) {
6593
6631
  const root = opts.packageRoot ?? assetRoot();
@@ -7120,6 +7158,8 @@ program.command("verdicts").description("Verify a critique's verdict files and c
7120
7158
  const result = verifyVerdicts({ projectRoot, surface });
7121
7159
  for (const error of result.errors) console.error(` \u2717 ${error}`);
7122
7160
  if (result.ok) console.log(` Every rule in both passes has a verdict.`);
7161
+ const since = result.decisions.since ?? [];
7162
+ if (since.length) console.log(` ${since.length} decision(s) recorded after this critique, for the next one to judge: ${since.join(", ")}.`);
7123
7163
  console.log(` ${result.line}`);
7124
7164
  process.exit(result.ok ? 0 : 1);
7125
7165
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jig-ui",
3
- "version": "0.18.2",
3
+ "version": "0.18.3",
4
4
  "description": "A design system for coding agents. 143 numbered UI rules, brand x mode design tokens, and an installer for Claude Code, Codex, Cursor and opencode.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",