@geonosis/ratchet 2.5.2 → 2.6.0

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,39 @@
1
1
  # @geonosis/ratchet
2
2
 
3
+ ## 2.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 58565d7: Plan 029, the factory floor: the gate learns to run things at once, and the heavy budget stops
8
+ being one laptop's number.
9
+
10
+ `@geonosis/verify` — `TierStep` carries `group`, the number that says which steps run at once, so a
11
+ tier can declare `{ "parallel": [ … ] }` groups and `{ "command": "… {workspace} …", "per":
12
+ "workspace" }` steps. New: `affected.ts` (`affected`, `affectedOf`, `workspacesIn`, `changedSince`,
13
+ `mergeBase`, `ROOT_SHAPED`, types `Affected` and `Workspace`), `PerWorkspace`, `ParallelStep`,
14
+ `CommandStep` on `config`, and `planSteps`, `runGroups`, `shellRunAsync`, `PlannedStep`,
15
+ `AsyncStepRun` on `run`. `GateReport` gains an optional `affected` block. `runSteps`, `shellRun` and
16
+ `stepIds` are unchanged. The bin gains `--affected`, `--since <ref>` and `--jobs <n>`.
17
+
18
+ `@geonosis/ratchet` and `@geonosis/release` — `heavySlots()` and `DEFAULT_HEAVY_SLOTS` beside
19
+ `HEAVY_SLOTS`, which is no longer the constant 2: the budget is `GEONOSIS_HEAVY_SLOTS` or
20
+ `availableParallelism() - 1`, read on every claim rather than at module load (D-073).
21
+
22
+ `@geonosis/ledger` — `shape.ts`: `shapeCheck`, `readManifest`, `exportsOf`, `formatShape` and the
23
+ types `BriefManifest`, `ShapeRefusal`, `ShapeOutcome` — the lock that refuses an engineer diff
24
+ touching a locked test, renaming an export or adding a file the brief did not name (D-072). Not yet
25
+ on the package's public entry: the CLI door lands with W115's dispatch unit.
26
+
27
+ `geonosis` — the plugin's post-edit hook takes its formatter from `geonosis.json → stack.format`
28
+ (D-070) and the stop hook reads the machine's heavy budget.
29
+ - 58565d7: The heavy budget is memory-aware (D-081, amending D-073). `heavyBudget()` — on `@geonosis/ratchet`'s door, copied into `@geonosis/release` and the plugin's Stop hook and held to one answer by a test — is the smaller of one less than the cores and the memory the machine can spend divided by what one run holds: `geonosis.json → stack.memoryMbPerSlot`, 512 MB when nothing is declared. `GEONOSIS_HEAVY_SLOTS` still wins outright; `GEONOSIS_HEAVY_MEMORY_MB` is the memory to spend instead of the machine's. `heavySlots()` is the budget's number read fresh, and `describeBudget()` names the number and both inputs. `geonosis-verify --jobs` defaults to it, its report carries it as `heavy`, and the run prints it; the kit's own vitest workers read it too. Measured on a 4-core, 3.4 GB box: three slots over 1.4 GB swapped; two do not.
30
+
31
+ ### Patch Changes
32
+
33
+ - 58565d7: The oxlint counters read the graphical format's Unicode markers — `×` for an error, `⚠` for a warning — beside the ASCII `x` and `!` a pipe gets. CI's terminal printed the Unicode pair, `oxlintRule` saw two findings it could attribute to no rule, and refused as its contract says; a laptop's vitest child never printed them.
34
+
35
+ ## 2.5.3
36
+
3
37
  ## 2.5.2
4
38
 
5
39
  ## 2.5.1
@@ -72,7 +72,7 @@ var versionOf = (moduleUrl) => {
72
72
 
73
73
  // src/core/heavy-slot.ts
74
74
  import { readFileSync as readFileSync2, rmSync as rmSync2 } from "fs";
75
- import { tmpdir } from "os";
75
+ import { availableParallelism, freemem, tmpdir } from "os";
76
76
  import { join as join2 } from "path";
77
77
 
78
78
  // src/core/file-claim.ts
@@ -104,7 +104,42 @@ var claimFile = (path, holder) => {
104
104
  };
105
105
 
106
106
  // src/core/heavy-slot.ts
107
- var HEAVY_SLOTS = 2;
107
+ var availableMemoryMb = () => {
108
+ try {
109
+ const found = /^MemAvailable:\s+(\d+) kB/m.exec(readFileSync2("/proc/meminfo", "utf8"));
110
+ if (found?.[1] !== void 0) return Math.floor(Number(found[1]) / 1024);
111
+ } catch {
112
+ }
113
+ return Math.floor(freemem() / 1048576);
114
+ };
115
+ var DEFAULT_MEMORY_MB_PER_SLOT = 512;
116
+ var DEFAULT_HEAVY_SLOTS = Math.max(1, availableParallelism() - 1);
117
+ var perSlotMbDeclaredIn = (root) => {
118
+ try {
119
+ const parsed = JSON.parse(readFileSync2(join2(root, "geonosis.json"), "utf8"));
120
+ const declared = parsed.stack?.memoryMbPerSlot;
121
+ return typeof declared === "number" && Number.isFinite(declared) && declared > 0 ? declared : void 0;
122
+ } catch {
123
+ return void 0;
124
+ }
125
+ };
126
+ var heavyBudget = (input = {}) => {
127
+ const declared = Number(process.env.GEONOSIS_HEAVY_SLOTS ?? "");
128
+ const spendable = Number(process.env.GEONOSIS_HEAVY_MEMORY_MB ?? "");
129
+ const reading = {
130
+ availableMemoryMb: input.availableMemoryMb ?? (Number.isFinite(spendable) && spendable > 0 ? spendable : availableMemoryMb()),
131
+ cores: input.cores ?? availableParallelism(),
132
+ perSlotMb: input.perSlotMb ?? perSlotMbDeclaredIn(input.root ?? process.cwd()) ?? DEFAULT_MEMORY_MB_PER_SLOT
133
+ };
134
+ if (Number.isInteger(declared) && declared > 0)
135
+ return { ...reading, by: "declared", slots: declared };
136
+ const byCores = Math.max(1, reading.cores - 1);
137
+ const byMemory = Math.max(1, Math.floor(reading.availableMemoryMb / reading.perSlotMb));
138
+ return byMemory < byCores ? { ...reading, by: "memory", slots: byMemory } : { ...reading, by: "cores", slots: byCores };
139
+ };
140
+ var describeBudget = (budget) => `${budget.slots} heavy slot(s), bound by ${budget.by === "declared" ? "GEONOSIS_HEAVY_SLOTS" : budget.by} \u2014 ${budget.cores} cores, ${budget.availableMemoryMb} MB available, ${budget.perSlotMb} MB per slot`;
141
+ var heavySlots = () => heavyBudget().slots;
142
+ var HEAVY_SLOTS = heavySlots();
108
143
  var DEFAULT_WAIT_MS = 20 * 6e4;
109
144
  var heavySlotDir = () => {
110
145
  const named = process.env.GEONOSIS_HEAVY_SLOT_DIR;
@@ -130,7 +165,7 @@ var reapIfDead = (path) => {
130
165
  if (holder !== void 0 && !alive(holder.pid)) rmSync2(path, { force: true });
131
166
  };
132
167
  var claim = (path, holder) => claimFile(path, holder);
133
- var namedHolders = (dir) => Array.from({ length: HEAVY_SLOTS }, (_unused, at) => holderOf(slotPath(dir, at))).filter(
168
+ var namedHolders = (dir) => Array.from({ length: heavySlots() }, (_unused, at) => holderOf(slotPath(dir, at))).filter(
134
169
  (holder) => holder !== void 0
135
170
  );
136
171
  var describeHolders = (holders) => holders.map((one) => `${one.pid} ${one.command} since ${one.startedAt}`).join(", ");
@@ -148,7 +183,7 @@ var takeSlot = async ({
148
183
  const until = Date.now() + waitMs;
149
184
  let told = false;
150
185
  for (; ; ) {
151
- for (let at = 0; at < HEAVY_SLOTS; at += 1) {
186
+ for (let at = 0; at < heavySlots(); at += 1) {
152
187
  const path = slotPath(dir, at);
153
188
  reapIfDead(path);
154
189
  const mine = {
@@ -1168,13 +1203,15 @@ var UNIX_FINDING = /^\S[^\n]*:\d+:\d+: .*\[(Error|Warning)\/[^\]\n]+\]$/gm;
1168
1203
  var UNIX_SUMMARY = /^(\d+) problems?$/m;
1169
1204
  var AGENT_FINDING = /^\S[^\n]*:\d+:\d+: (error|warning) /gm;
1170
1205
  var DEFAULT_SUMMARY = /^Found (\d+) warnings? and (\d+) errors?\.$/m;
1171
- var DEFAULT_FINDING = /^\s*([x!]) [^\s(]+\([^)\n]+\): /m;
1206
+ var DEFAULT_FINDING = /^\s*([x!×⚠]) [^\s(]+\([^)\n]+\): /m;
1172
1207
  var FINDING_SHAPES = [UNIX_FINDING, AGENT_FINDING, DEFAULT_FINDING].map(
1173
1208
  (shape) => new RegExp(shape.source)
1174
1209
  );
1175
1210
  var RULE_TOKEN = /\([^()\n]+\)/;
1176
1211
  var SEVERITY_BY_TOKEN = {
1177
1212
  "!": "warning",
1213
+ "\xD7": "error",
1214
+ "\u26A0": "warning",
1178
1215
  Error: "error",
1179
1216
  error: "error",
1180
1217
  Warning: "warning",
@@ -2184,6 +2221,11 @@ export {
2184
2221
  UnbalancedEnvelope,
2185
2222
  writeEnvelope,
2186
2223
  versionOf,
2224
+ availableMemoryMb,
2225
+ DEFAULT_MEMORY_MB_PER_SLOT,
2226
+ heavyBudget,
2227
+ describeBudget,
2228
+ heavySlots,
2187
2229
  HEAVY_SLOTS,
2188
2230
  heavySlotDir,
2189
2231
  describeHolders,
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  takeSlot,
9
9
  versionOf,
10
10
  writeEnvelope
11
- } from "./chunk-WENVCC67.js";
11
+ } from "./chunk-Z2T6MHHQ.js";
12
12
 
13
13
  // src/cli.ts
14
14
  import process from "process";
package/dist/index.d.ts CHANGED
@@ -288,33 +288,44 @@ declare const versionOf: (moduleUrl: string) => string;
288
288
  declare const COUNTERS: Counter[];
289
289
  declare const counterById: (id: string) => Counter;
290
290
 
291
- /** Who is spending a heavy slot, running what, since when. */
292
291
  type HeavySlotHolder = {
293
292
  command: string;
294
293
  cwd: string;
295
294
  pid: number;
296
295
  startedAt: string;
297
296
  };
297
+ /** The memory the machine can spend now: `MemAvailable` where the kernel says it, `os.freemem()` where it does not. */
298
+ declare const availableMemoryMb: () => number;
299
+ /** What one heavy run is budgeted to hold when the repo declares nothing: a vitest worker measured 334 MB and a tsc 224 MB (plan 030). */
300
+ declare const DEFAULT_MEMORY_MB_PER_SLOT = 512;
301
+ type HeavyBudget = {
302
+ availableMemoryMb: number;
303
+ /** What bound the number: a declared `GEONOSIS_HEAVY_SLOTS`, the cores, or the memory. */
304
+ by: 'cores' | 'declared' | 'memory';
305
+ cores: number;
306
+ perSlotMb: number;
307
+ slots: number;
308
+ };
298
309
  /**
299
- * Two, not one: `core/lock.ts`'s `acquireExclusive` serialises a whole run against every other; this
300
- * is the machine's OWN ceiling measured 2026-09-05, load 48 with during.day's Stop-hook tier, this
301
- * kit's Stop-hook tier, this kit's ratchet and an engineer's suite all running at once. A machine that
302
- * can carry two heavy things at a time carries two; a third waits.
310
+ * The heavy budget, read fresh: the smaller of one less than the cores and the memory divided by
311
+ * what one run holds (D-081, amending D-073). A declared `GEONOSIS_HEAVY_SLOTS` wins over both;
312
+ * `GEONOSIS_HEAVY_MEMORY_MB` is the memory to spend instead of the machine's; the per-slot number
313
+ * is `geonosis.json stack.memoryMbPerSlot`, 512 when nothing is declared.
303
314
  */
304
- declare const HEAVY_SLOTS = 2;
315
+ declare const heavyBudget: (input?: {
316
+ availableMemoryMb?: number;
317
+ cores?: number;
318
+ perSlotMb?: number;
319
+ root?: string;
320
+ }) => HeavyBudget;
321
+ /** One line a report can carry: the number, and both inputs it was read from. */
322
+ declare const describeBudget: (budget: HeavyBudget) => string;
323
+ declare const heavySlots: () => number;
324
+ declare const HEAVY_SLOTS: number;
305
325
  declare const heavySlotDir: () => string;
306
326
  declare const describeHolders: (holders: HeavySlotHolder[]) => string;
307
- /** A wait that ran out. The message names every holder, because the caller's own refusal is this text. */
308
327
  declare class HeavySlotTimeout extends Error {
309
328
  }
310
- /**
311
- * Claims one of the two machine-wide heavy slots, waiting for whoever holds them.
312
- *
313
- * A dead holder is reaped before a slot is tried, so a killed run never wedges the budget for
314
- * whoever asks next. Both held is announced ONCE — not on every poll, which would turn a half-hour
315
- * wait into a half hour of identical lines — and the wait is bounded, so a taker that can never get
316
- * in refuses instead of hanging the process that called it.
317
- */
318
329
  declare const takeSlot: ({ command, cwd, dir, pollMs, say, waitMs, }: {
319
330
  command: string;
320
331
  cwd?: string;
@@ -413,4 +424,4 @@ declare const formatProve: ({ proofs, proven }: ProveResult) => string;
413
424
  */
414
425
  declare const runCommand: (cwd: string, counterId: string, env?: NodeJS.ProcessEnv) => (command: string) => CommandResult;
415
426
 
416
- export { CONFIG_FILE, COUNTERS, type CommandResult, type Counter, type CounterConfig, type CounterContext, CounterError, type CounterProbe, ENVELOPES_DIR, type EnvelopeEntry, HEAVY_SLOTS, type HeavySlotHolder, HeavySlotTimeout, type Holder, type Measurement, type Proof, type ProveResult, type RatchetConfig, type RatchetResult, type Refusal, type ReportEnvelope, UnbalancedEnvelope, type Verdict, acquireExclusive, counterById, describeHolders, envelopePath, formatProve, formatReport, heavyLockPath, heavySlotDir, keyOf, loadConfig, resolveBaseline, runCommand, runProve, runRatchet, takeSlot, versionOf, writeEnvelope };
427
+ export { CONFIG_FILE, COUNTERS, type CommandResult, type Counter, type CounterConfig, type CounterContext, CounterError, type CounterProbe, DEFAULT_MEMORY_MB_PER_SLOT, ENVELOPES_DIR, type EnvelopeEntry, HEAVY_SLOTS, type HeavyBudget, type HeavySlotHolder, HeavySlotTimeout, type Holder, type Measurement, type Proof, type ProveResult, type RatchetConfig, type RatchetResult, type Refusal, type ReportEnvelope, UnbalancedEnvelope, type Verdict, acquireExclusive, availableMemoryMb, counterById, describeBudget, describeHolders, envelopePath, formatProve, formatReport, heavyBudget, heavyLockPath, heavySlotDir, heavySlots, keyOf, loadConfig, resolveBaseline, runCommand, runProve, runRatchet, takeSlot, versionOf, writeEnvelope };
package/dist/index.js CHANGED
@@ -2,18 +2,23 @@ import {
2
2
  CONFIG_FILE,
3
3
  COUNTERS,
4
4
  CounterError,
5
+ DEFAULT_MEMORY_MB_PER_SLOT,
5
6
  ENVELOPES_DIR,
6
7
  HEAVY_SLOTS,
7
8
  HeavySlotTimeout,
8
9
  UnbalancedEnvelope,
9
10
  acquireExclusive,
11
+ availableMemoryMb,
10
12
  counterById,
13
+ describeBudget,
11
14
  describeHolders,
12
15
  envelopePath,
13
16
  formatProve,
14
17
  formatReport,
18
+ heavyBudget,
15
19
  heavyLockPath,
16
20
  heavySlotDir,
21
+ heavySlots,
17
22
  keyOf,
18
23
  loadConfig,
19
24
  resolveBaseline,
@@ -23,23 +28,28 @@ import {
23
28
  takeSlot,
24
29
  versionOf,
25
30
  writeEnvelope
26
- } from "./chunk-WENVCC67.js";
31
+ } from "./chunk-Z2T6MHHQ.js";
27
32
  export {
28
33
  CONFIG_FILE,
29
34
  COUNTERS,
30
35
  CounterError,
36
+ DEFAULT_MEMORY_MB_PER_SLOT,
31
37
  ENVELOPES_DIR,
32
38
  HEAVY_SLOTS,
33
39
  HeavySlotTimeout,
34
40
  UnbalancedEnvelope,
35
41
  acquireExclusive,
42
+ availableMemoryMb,
36
43
  counterById,
44
+ describeBudget,
37
45
  describeHolders,
38
46
  envelopePath,
39
47
  formatProve,
40
48
  formatReport,
49
+ heavyBudget,
41
50
  heavyLockPath,
42
51
  heavySlotDir,
52
+ heavySlots,
43
53
  keyOf,
44
54
  loadConfig,
45
55
  resolveBaseline,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geonosis/ratchet",
3
- "version": "2.5.2",
3
+ "version": "2.6.0",
4
4
  "types": "./dist/index.d.ts",
5
5
  "description": "Debt as a number that may only shrink — one ratchet, pluggable counters.",
6
6
  "keywords": [
@@ -43,6 +43,10 @@
43
43
  },
44
44
  "scripts": {
45
45
  "build": "tsup",
46
- "typecheck": "tsc --noEmit"
46
+ "typecheck": "tsc --noEmit",
47
+ "lint": "oxlint --config ../../.oxlintrc.json .",
48
+ "test:unit": "vitest run --project unit --root ../.. --passWithNoTests \"$PWD\"",
49
+ "test:bin": "vitest run --project bin --root ../.. --passWithNoTests \"$PWD\"",
50
+ "test:exam": "vitest run --project exam --root ../.. --passWithNoTests \"$PWD\""
47
51
  }
48
52
  }