@raresportan/marram 0.2.0 → 0.2.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raresportan/marram",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "The gate, ratchet lints and journeys that keep agent-written code from drifting.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -4,6 +4,7 @@
4
4
  // gate: {
5
5
  // logs: "where each run's logs go",
6
6
  // progress: /lines from a stage worth showing as it runs/, // besides ✓ and ✗ lines
7
+ // sleepGapMs: 30000, // how long a pause counts as the machine sleeping, so a stage that failed runs again
7
8
  // tiers: { fast: [stage, …], default: [stage, …], full: [stage, …] }, // stage: { name, run: [command, …args], env? }
8
9
  // },
9
10
  // review: { classes: "the mistake-classes doc", rungs: ["type", "lint", …] },
package/src/core/gate.mjs CHANGED
@@ -5,10 +5,12 @@
5
5
  // full The default gate and more (a build, the slow journeys). Before merging UI work, or nightly.
6
6
  // What each tier runs is the project's: `gate.tiers` in marram.config.mjs. Stages run in order and the first failure
7
7
  // stops the gate. Each stage's output is kept in its own log under `gate.logs`/gate-<tier>-<time>/, with report.json.
8
+ // A stage that fails after the machine slept during it runs again: timeouts ran out and frames stopped while it slept,
9
+ // so that failure says nothing about the code. A pause counts as sleep past `gate.sleepGapMs`, 30 seconds by default.
8
10
  // The default and full gates record their verdict for the working tree (gate-state.mjs), which the hooks reuse.
9
11
  import { spawn } from "node:child_process";
10
12
  import { createWriteStream } from "node:fs";
11
- import { mkdir, writeFile } from "node:fs/promises";
13
+ import { mkdir, rename, writeFile } from "node:fs/promises";
12
14
  import { join } from "node:path";
13
15
  import { treeOf, updateState } from "./gate-state.mjs";
14
16
 
@@ -19,7 +21,7 @@ export async function gate(root, config, [tier = "default"]) {
19
21
  console.error(`Unknown gate "${tier}". Usage: marram gate [fast | full]`);
20
22
  process.exit(1);
21
23
  }
22
- const { logs, progress, tiers } = config.gate;
24
+ const { logs, progress, tiers, sleepGapMs = 30_000 } = config.gate;
23
25
  const dir = join(root, logs, `gate-${tier}-${new Date().toISOString().replace(/[:.]/g, "-")}`);
24
26
  await mkdir(dir, { recursive: true });
25
27
  const report = { tier, startedAt: new Date().toISOString(), stages: [], passed: false };
@@ -50,16 +52,26 @@ export async function gate(root, config, [tier = "default"]) {
50
52
  child.stdout.on("data", (chunk) => {
51
53
  for (const line of chunk.toString().split("\n")) if (/^\s*[✓✗]/.test(line) || progress?.test(line)) console.log(` ${name}: ${line.trim()}`);
52
54
  });
55
+ let last = Date.now();
56
+ let slept = 0;
57
+ const noticeSleep = () => {
58
+ const gap = Date.now() - last;
59
+ last += gap;
60
+ if (gap > sleepGapMs) slept += gap;
61
+ };
62
+ const clock = setInterval(noticeSleep, 1000);
53
63
  const code = await new Promise((done) => {
54
64
  child.on("error", () => done(1));
55
65
  child.on("exit", (exit) => done(exit ?? 1));
56
66
  });
67
+ clearInterval(clock);
68
+ noticeSleep();
57
69
  log.end();
58
70
  const seconds = Math.round((Date.now() - started) / 1000);
59
71
  report.stages.push({ name, code, seconds });
60
72
  await writeFile(join(dir, "report.json"), JSON.stringify(report, null, 2));
61
73
  console.log(`${code === 0 ? "✓" : "✗"} ${name} (${seconds}s)${code === 0 ? "" : `: see ${join(dir, `${name}.log`)}`}`);
62
- return code === 0;
74
+ return { passed: code === 0, slept };
63
75
  }
64
76
 
65
77
  // A fast pass doesn't make a change done, so only the default and full gates record a verdict.
@@ -70,7 +82,12 @@ export async function gate(root, config, [tier = "default"]) {
70
82
 
71
83
  console.log(`Gate (${tier}). Logs: ${dir}`);
72
84
  for (const stage of tiers[tier]) {
73
- const passed = await runStage(stage);
85
+ let { passed, slept } = await runStage(stage);
86
+ while (!passed && slept > 0 && !interrupted) {
87
+ console.log(` The machine slept for ${Math.round(slept / 1000)}s during ${stage.name}, so its failure says nothing about the code. Running it again.`);
88
+ await rename(join(dir, `${stage.name}.log`), join(dir, `${stage.name}-slept.log`));
89
+ ({ passed, slept } = await runStage(stage));
90
+ }
74
91
  if (interrupted) {
75
92
  console.log(`\nGate interrupted at ${stage.name}.`);
76
93
  process.exit(1);