@chrisdudek/yg 5.2.2 → 5.2.4

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.
Files changed (3) hide show
  1. package/README.md +2 -2
  2. package/dist/bin.js +21 -18
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @chrisdudek/yg
2
2
 
3
- **Your agent will ignore CLAUDE.md. Yggdrasil makes sure it doesn't.**
3
+ **Stop babysitting your agent.**
4
4
 
5
- Architecture rules your agent can't ignore. Before it edits a file, your agent gets the few rules that apply — and writes to them. After, every change is checked before it moves on: by a script that runs locally for free, or by an LLM reviewer. A rule written as a script *runs* — your agent can't quietly optimize it away the way it drops a line in CLAUDE.md. Works with Claude Code, Cursor, Copilot, Codex, Cline, and more. Checks run against your code, not your diffs; the feedback is specific; the agent has to fix before it can move on.
5
+ Your architecture rules become checks it can't skip, run on every change before it moves on. A script runs them locally for free, or an LLM reviews the call a script can't make. Checks run against your code, not your diffs. The feedback is specific, and the agent has to fix before it can move on. Works with Claude Code, Cursor, Copilot, Codex, Cline, and more.
6
6
 
7
7
  See the [main README](https://github.com/krzysztofdudek/Yggdrasil#readme) for documentation, or visit
8
8
  [krzysztofdudek.github.io/Yggdrasil](https://krzysztofdudek.github.io/Yggdrasil/).
package/dist/bin.js CHANGED
@@ -3621,13 +3621,12 @@ async function writeLock(yggRoot, lock, opts = {}) {
3621
3621
  }
3622
3622
  const { det, nondet } = partitionVerdicts(lock.verdicts, detIds);
3623
3623
  if (scope === "deterministic") {
3624
- const content20 = serializeLock({ version: lock.version, verdicts: det, nodes: {} });
3625
- await writeFileIfChanged(detLockPath(yggRoot), content20);
3624
+ await writeOrRemoveSplitFile(detLockPath(yggRoot), lock.version, det, {});
3626
3625
  return;
3627
3626
  }
3628
- await writeFileIfChanged(nondetLockPath(yggRoot), serializeLock({ version: lock.version, verdicts: nondet, nodes: {} }));
3627
+ await writeOrRemoveSplitFile(nondetLockPath(yggRoot), lock.version, nondet, {});
3629
3628
  await writeOrRemoveSplitFile(logsLockPath(yggRoot), lock.version, {}, lock.nodes);
3630
- await writeFileIfChanged(detLockPath(yggRoot), serializeLock({ version: lock.version, verdicts: det, nodes: {} }));
3629
+ await writeOrRemoveSplitFile(detLockPath(yggRoot), lock.version, det, {});
3631
3630
  }
3632
3631
 
3633
3632
  // src/cli/preamble.ts
@@ -15822,20 +15821,20 @@ function registerCheckCommand(program2) {
15822
15821
  const gitFiles = collectGitFiles(projectRoot);
15823
15822
  const wantsTop = opts.top !== void 0;
15824
15823
  if (wantsTop && opts.summary) {
15825
- process.stdout.write(buildIssueMessage({
15824
+ process.stderr.write(chalk8.red(buildIssueMessage({
15826
15825
  what: "--top and --summary cannot be combined.",
15827
15826
  why: "Both are read-only triage VIEWS of the same `yg check` result \u2014 --top renders the N highest-priority blocks, --summary renders per-node counts only. Asking for both at once is ambiguous; pick one lens.",
15828
15827
  next: "Run: yg check --top <n> (priority blocks), or yg check --summary (per-node counts)."
15829
- }) + "\n");
15828
+ }) + "\n"));
15830
15829
  await exitAfterFlush(1);
15831
15830
  return;
15832
15831
  }
15833
15832
  if ((wantsTop || opts.summary) && opts.approve) {
15834
- process.stdout.write(buildIssueMessage({
15833
+ process.stderr.write(chalk8.red(buildIssueMessage({
15835
15834
  what: `${wantsTop ? "--top" : "--summary"} cannot be combined with --approve.`,
15836
15835
  why: "--top and --summary triage the READ-ONLY check wall (they narrow the output of plain `yg check`, which writes nothing). --approve is the writer path; its own free cost preview is --dry-run. Mixing a read-only triage view with the writer is contradictory.",
15837
15836
  next: `Run: yg check ${wantsTop ? "--top <n>" : "--summary"} (read-only triage), or yg check --approve --dry-run (preview the writer's cost).`
15838
- }) + "\n");
15837
+ }) + "\n"));
15839
15838
  await exitAfterFlush(1);
15840
15839
  return;
15841
15840
  }
@@ -15845,22 +15844,22 @@ function registerCheckCommand(program2) {
15845
15844
  } else if (wantsTop) {
15846
15845
  const n = resolveTopValue(opts.top);
15847
15846
  if (n === null) {
15848
- process.stdout.write(buildIssueMessage({
15847
+ process.stderr.write(chalk8.red(buildIssueMessage({
15849
15848
  what: `--top expects a non-negative whole number; got "${String(opts.top)}".`,
15850
15849
  why: "--top N prints the N highest-priority issue blocks. A negative, fractional, or non-numeric value is meaningless, and printing the full wall instead would silently hide that the flag was ignored \u2014 masking the very output you tried to narrow.",
15851
15850
  next: "Run: yg check --top 5 (top 5 blocks), yg check --top (just the suggestedNext block), or yg check (full output)."
15852
- }) + "\n");
15851
+ }) + "\n"));
15853
15852
  await exitAfterFlush(1);
15854
15853
  return;
15855
15854
  }
15856
15855
  view = { kind: "top", n };
15857
15856
  }
15858
15857
  if (opts.dryRun && !opts.approve) {
15859
- process.stdout.write(buildIssueMessage({
15858
+ process.stderr.write(chalk8.red(buildIssueMessage({
15860
15859
  what: "--dry-run requires --approve.",
15861
15860
  why: "--dry-run previews what `yg check --approve` would fill (the reviewer-call budget and per-node breakdown) without writing or calling the reviewer; it is a mode of --approve, not a variant of the plain read. Plain `yg check` is already a free, no-write read.",
15862
15861
  next: "Run: yg check --approve --dry-run (cost preview), or yg check (plain read)."
15863
- }) + "\n");
15862
+ }) + "\n"));
15864
15863
  await exitAfterFlush(1);
15865
15864
  return;
15866
15865
  }
@@ -15880,7 +15879,7 @@ function registerCheckCommand(program2) {
15880
15879
  return;
15881
15880
  }
15882
15881
  const hasErrors2 = fill.checkResult.issues.some((i) => i.severity === "error");
15883
- if (hasErrors2) await exitAfterFlush(1);
15882
+ await exitAfterFlush(hasErrors2 ? 1 : 0);
15884
15883
  return;
15885
15884
  } catch (err) {
15886
15885
  if (err instanceof FillGatingError) {
@@ -15893,7 +15892,7 @@ function registerCheckCommand(program2) {
15893
15892
  const result = await runCheck(graph, gitFiles);
15894
15893
  process.stdout.write(formatOutput(result, view));
15895
15894
  const hasErrors = result.issues.some((i) => i.severity === "error");
15896
- if (hasErrors) await exitAfterFlush(1);
15895
+ await exitAfterFlush(hasErrors ? 1 : 0);
15897
15896
  } catch (error) {
15898
15897
  debugWrite(`[check] error: ${error.message}`);
15899
15898
  abortOnUnexpectedError(error, "running check");
@@ -19534,10 +19533,14 @@ pairs on that node \u2014 a legitimate vacuous pass, no verdict, no entry.
19534
19533
  \`yg knowledge read log-management\`) and the append-only log integrity baseline.
19535
19534
  The source fingerprint is the log gate's drift basis, so it is recorded ONLY for
19536
19535
  \`log_required\` nodes \u2014 a non-log_required node gets a \`nodes\` entry only when it
19537
- owns a \`log.md\` (then holding just the \`log\` baseline, no \`source\`). When the
19538
- \`nodes\` section is empty (no log_required node, no \`log.md\`), \`yg-lock.logs.json\`
19539
- is not written at all \u2014 an empty committed husk is removed, and readLock treats
19540
- an absent file as empty state.
19536
+ owns a \`log.md\` (then holding just the \`log\` baseline, no \`source\`).
19537
+ - Empty section \u21D2 no file. Each of the three split files is written ONLY when its
19538
+ section is non-empty; when empty it is not written at all (an existing empty husk
19539
+ is removed). So a repo with no LLM aspects has no \`yg-lock.nondeterministic.json\`,
19540
+ one with no \`log_required\` node and no \`log.md\` has no \`yg-lock.logs.json\`, and
19541
+ one with no deterministic aspects has no \`.yg-lock.deterministic.json\`. readLock
19542
+ treats an absent file as empty state, so this is transparent to every reader \u2014 a
19543
+ repo only carries the lock files it actually needs.
19541
19544
  - The built-in relation-conformance check is NOT stored in the lock \u2014 it is
19542
19545
  recomputed live on every \`yg check\`. The lock holds only aspect \`verdicts\`
19543
19546
  and per-node \`nodes\` facts; there is no relation section. See "Relation
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrisdudek/yg",
3
- "version": "5.2.2",
3
+ "version": "5.2.4",
4
4
  "description": "Architecture rules your AI coding agent can't ignore. It gets the rules for a file before it edits, and every change is checked — by a free local script or an LLM reviewer — before it moves on. Works with Claude Code, Cursor, Copilot, Codex, Cline.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -85,7 +85,7 @@
85
85
  "@tree-sitter-grammars/tree-sitter-kotlin": "^1.1.0",
86
86
  "@tree-sitter-grammars/tree-sitter-toml": "^0.7.0",
87
87
  "@tree-sitter-grammars/tree-sitter-yaml": "^0.7.1",
88
- "@types/node": "^25.9.1",
88
+ "@types/node": "^26.0.0",
89
89
  "@types/semver": "^7.7.1",
90
90
  "@vitest/coverage-v8": "^4.1.8",
91
91
  "eslint": "^10.4.1",