@geonosis/verify 1.0.0 → 1.1.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.
@@ -1,4 +1,27 @@
1
1
  #!/usr/bin/env node
2
2
  // Committed, so `pnpm install` can link the bin on a fresh clone — before `pnpm build` has
3
3
  // produced dist/. A bin that only exists after a build is a bin that is missing when you need it.
4
- import '../dist/cli.js'
4
+ //
5
+ // In the repo, `src/` sits beside `dist/`, and a dist older than src answered for a fix it did not
6
+ // carry once (#62). The published package ships no src, so there the check is skipped.
7
+ import { existsSync, readdirSync, statSync } from 'node:fs'
8
+ import { dirname, join } from 'node:path'
9
+ import { fileURLToPath } from 'node:url'
10
+
11
+ const here = dirname(fileURLToPath(import.meta.url))
12
+ const newest = (dir) =>
13
+ existsSync(dir)
14
+ ? readdirSync(dir, { withFileTypes: true }).reduce((most, entry) => {
15
+ const at = join(dir, entry.name)
16
+ return Math.max(most, entry.isDirectory() ? newest(at) : statSync(at).mtimeMs)
17
+ }, 0)
18
+ : 0
19
+ const src = join(here, '..', 'src')
20
+ if (existsSync(src) && newest(src) > newest(join(here, '..', 'dist'))) {
21
+ process.stderr.write(
22
+ 'geonosis-verify: dist is older than src — run pnpm build before trusting this bin.\n',
23
+ )
24
+ process.exit(2)
25
+ }
26
+
27
+ await import('../dist/cli.js')
@@ -0,0 +1,157 @@
1
+ declare const CONFIG_FILE = "geonosis.json";
2
+ /** The tiers a repo declared, in the order it declared them. */
3
+ type VerifyConfig = {
4
+ tiers: Record<string, string[]>;
5
+ };
6
+ /**
7
+ * There is no default tier and there never will be one. A default that happened to match one repo's
8
+ * script names would run the wrong commands everywhere else and still report green, so an
9
+ * unconfigured repo is refused by name instead of guessed at.
10
+ */
11
+ declare const loadVerifyConfig: (cwd: string) => VerifyConfig;
12
+ /**
13
+ * One command to run, and the tier that DECLARED it — which is not always the tier being run. A
14
+ * step inlined from `fast` into `full` is still a step of the fast tier, and it keeps saying so, so
15
+ * that `fast/2` names the same command whether the operator asked for `fast` or for `full`.
16
+ */
17
+ type TierStep = {
18
+ command: string;
19
+ tier: string;
20
+ };
21
+ /**
22
+ * Flattens a tier into the steps it actually runs: a step whose text is another tier's name is that
23
+ * tier, inlined in place, however deep it goes.
24
+ *
25
+ * The visiting path is carried so a cycle is named rather than discovered as a stack overflow with
26
+ * nothing in it a reader could act on.
27
+ */
28
+ declare const resolveTier: (config: VerifyConfig, tier: string, path?: string[]) => TierStep[];
29
+
30
+ type Proof = {
31
+ ok: boolean;
32
+ message: string;
33
+ };
34
+ /**
35
+ * The gate proves itself before anyone trusts it.
36
+ *
37
+ * A runner that reports green is worth nothing until it has been watched going red on purpose: this
38
+ * plants a tier with one passing step and one that exits 3, runs the real binary over it in a
39
+ * throwaway directory, and checks that the failure reached the report, the right step was named and
40
+ * the process exited 1. Everything it touches is in a temp dir, so it can be run in any repo at any
41
+ * time without disturbing that repo's own report.
42
+ */
43
+ declare const prove: ({ entry, execPath }: {
44
+ entry: string;
45
+ execPath: string;
46
+ }) => Proof;
47
+
48
+ /** Where the report lands unless `--report` says otherwise. Runner-owned; agents read, never write. */
49
+ declare const DEFAULT_REPORT = ".geonosis/gate-report.json";
50
+ /** How much of a step's output survives into the report — enough to act on, small enough to read. */
51
+ declare const TAIL_LINES = 40;
52
+ /** A step that never ran carries this code: it has no exit status of its own to report. */
53
+ declare const NEVER_RAN = -1;
54
+ type GateStep = {
55
+ id: string;
56
+ command: string;
57
+ ok: boolean;
58
+ exitCode: number;
59
+ ms: number;
60
+ tail: string;
61
+ skipped?: boolean;
62
+ };
63
+ type GateReport = {
64
+ tier: string;
65
+ startedAt: string;
66
+ finishedAt: string;
67
+ ok: boolean;
68
+ steps: GateStep[];
69
+ };
70
+ /**
71
+ * The end of a step's output, with the colour codes taken out — the report is read by a hook that
72
+ * pipes it into stderr and by a human reading JSON, and neither wants escape sequences.
73
+ */
74
+ declare const lastLines: (output: string, limit?: number) => string;
75
+ /**
76
+ * A gate that cannot record has not passed, so a failed write is thrown rather than warned about:
77
+ * the caller turns it into a refusal instead of handing back a green nobody can go back and read.
78
+ */
79
+ declare const writeReport: (cwd: string, path: string, report: GateReport) => string;
80
+
81
+ type StepResult = {
82
+ exitCode: number;
83
+ output: string;
84
+ };
85
+ type StepRun = (command: string) => StepResult;
86
+ /**
87
+ * One step, through the shell, with stderr folded into stdout by the shell itself so the two stay
88
+ * interleaved in the order they were written — concatenating two captured buffers would not.
89
+ *
90
+ * The step runs inside a subshell because `<step> 2>&1` binds the redirect to the LAST command in
91
+ * the step only: `echo boom >&2; exit 3` would lose "boom" entirely, and a gate whose report is
92
+ * missing the error line is a gate nobody can act on. The newlines let a step end in a comment.
93
+ */
94
+ declare const shellRun: (cwd: string) => StepRun;
95
+ /**
96
+ * Serial, and it stops at the first red.
97
+ *
98
+ * Serial because the parts of a gate compete for the same cores, and a suite that lost a core reads
99
+ * as a flake. Stopping because the second failure of a broken build tells the operator nothing the
100
+ * first one did not — but the steps that never ran are still written down, as `skipped`, so the
101
+ * report cannot be mistaken for a shorter, greener tier.
102
+ */
103
+ declare const runSteps: ({ run, steps: planned, tier, }: {
104
+ run: StepRun;
105
+ steps: TierStep[];
106
+ tier: string;
107
+ }) => GateReport;
108
+
109
+ /**
110
+ * The Stop hook's own record, one bucket per day.
111
+ *
112
+ * Per DAY rather than per session, and two integers rather than a log, because the question it
113
+ * answers is "did cutting the law change how often a turn ends green" — which needs a before window
114
+ * and an after window and nothing else. A per-session log would be a session log: bigger, more
115
+ * personal, and no better at the one question.
116
+ *
117
+ * The sibling `stop-blocks.json` cannot answer it. That file is the block CAP's working memory, and
118
+ * a green turn deletes its own row from it — so a session that never blocked and a session that
119
+ * blocked and recovered are both simply absent.
120
+ */
121
+ declare const TURNS_FILE = ".geonosis/stop-turns.json";
122
+ type DayRecord = {
123
+ blocked: number;
124
+ turns: number;
125
+ };
126
+ type Window = {
127
+ blocked: number;
128
+ passRate: number | undefined;
129
+ turns: number;
130
+ };
131
+ type Stats = Window & {
132
+ before?: Window;
133
+ since?: string;
134
+ };
135
+ /**
136
+ * The first-try pass rate: the share of turn-ends whose fast tier was green on the first run.
137
+ *
138
+ * With `since`, the reading is the window from that day on, and `before` is everything earlier —
139
+ * one command, two windows, one instrument, which is what makes a law diet measurable rather than
140
+ * a matter of opinion.
141
+ */
142
+ declare const readStats: (root: string, since?: string) => Stats;
143
+ declare const formatStats: (stats: Stats) => string;
144
+ /**
145
+ * One turn ended. Appends to the day's bucket, creating the file if it is not there.
146
+ *
147
+ * Read-modify-write with no lock, deliberately: the writer is a Stop hook, one per turn per session,
148
+ * and the cost of a lost increment is a KPI one turn out. A lock here would be machinery guarding a
149
+ * number that is already an estimate.
150
+ */
151
+ declare const recordTurn: ({ blocked, day, root, }: {
152
+ blocked: boolean;
153
+ day: string;
154
+ root: string;
155
+ }) => void;
156
+
157
+ export { CONFIG_FILE, DEFAULT_REPORT, type DayRecord, type GateReport, type GateStep, NEVER_RAN, type Proof, type Stats, type StepResult, type StepRun, TAIL_LINES, TURNS_FILE, type TierStep, type VerifyConfig, type Window, formatStats, lastLines, loadVerifyConfig, prove, readStats, recordTurn, resolveTier, runSteps, shellRun, writeReport };
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@geonosis/verify",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
+ "types": "./dist/index.d.ts",
4
5
  "description": "One gate runner — tiers of shell steps, run serially, recorded as a JSON gate report.",
5
6
  "keywords": [
6
7
  "gate",
@@ -23,14 +24,17 @@
23
24
  "geonosis-verify": "bin/geonosis-verify.mjs"
24
25
  },
25
26
  "exports": {
26
- ".": "./dist/index.js"
27
+ ".": {
28
+ "types": "./dist/index.d.ts",
29
+ "default": "./dist/index.js"
30
+ }
27
31
  },
28
32
  "files": [
29
33
  "bin",
30
34
  "dist"
31
35
  ],
32
36
  "dependencies": {
33
- "@geonosis/ratchet": "1.0.0"
37
+ "@geonosis/ratchet": "1.1.0"
34
38
  },
35
39
  "engines": {
36
40
  "node": ">=22"