omakit 0.1.8 → 0.2.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.
@@ -0,0 +1,166 @@
1
+ // Text rendering of a weigh document and of the confirmation that precedes
2
+ // it, for the person whose shell is about to be restarted and the agent
3
+ // reading over their shoulder. Drawn with style.mjs and nothing of its own:
4
+ // the same marks, columns and rule every other command uses.
5
+ //
6
+ // The noise floor is printed once, in the header, before any plugin row, and
7
+ // every row says "within noise" in words where its median delta does not
8
+ // clear that floor. A row is `ok` when both figures are within noise, `note`
9
+ // when either is above it, and `?` when no run of it completed. None of that
10
+ // is a judgement about whether a weight is acceptable; the number is the
11
+ // author's to read.
12
+
13
+ import { action, colourEnabled, field, GUTTER, labelled, section, STEP, styler, verdict, wrap } from "../marketplace/style.mjs"
14
+ import { head } from "../marketplace/report.mjs"
15
+ import { withHomeAbbreviated } from "../marketplace/paths.mjs"
16
+ import { figure } from "./stats.mjs"
17
+ import { stockShellPath } from "./audit.mjs"
18
+ import { ARG0_CHARS } from "./proc.mjs"
19
+
20
+ /**
21
+ * A redacted command for a row. The document carries comm and a first
22
+ * argument cut to 80 characters (docs/WEIGH.md), which can end mid-word
23
+ * (`.../helper/sideca`); a person reads better when the cut falls at the
24
+ * last path separator, with an ellipsis saying it was cut. Nothing is
25
+ * added back: the row shows less than the document, never more.
26
+ */
27
+ export function redactedCommand(command, limit = ARG0_CHARS) {
28
+ const [comm, ...rest] = String(command).split(" ")
29
+ const arg0 = rest.join(" ")
30
+ if (arg0.length < limit) return command
31
+ const cut = arg0.lastIndexOf("/")
32
+ return cut > 0 ? `${comm} ${arg0.slice(0, cut + 1)}...` : `${comm} ${arg0}...`
33
+ }
34
+
35
+ /** The version alone on a stock install; the path beside it only when the running shell is somewhere else. */
36
+ function shellLine(version, omarchyPath, env) {
37
+ return omarchyPath === stockShellPath(env) ? version : `${version} at ${withHomeAbbreviated(omarchyPath, env)}`
38
+ }
39
+
40
+ /**
41
+ * The mark a row gets: from its CPU verdict. Memory is a fact about the
42
+ * shell's start until its two resting levels are understood (C1 and C2),
43
+ * so it is printed with its figure and never turns a row into a note.
44
+ */
45
+ export function rowState(plugin) {
46
+ const { memory, cpu } = plugin.verdict
47
+ if (memory === "unknown" || cpu === "unknown") return "unknown"
48
+ return cpu === "within-noise" ? "pass" : "advisory"
49
+ }
50
+
51
+ /** A figure with its spread, and the words when it is within noise. */
52
+ function measured(stat, unit, within) {
53
+ if (stat.median === null) return "no completed run"
54
+ const value = unit === "%" ? `${figure(stat.median)}%` : `${figure(stat.median)} ${unit}`
55
+ return `${value} (spread ${figure(stat.spread)})${within ? ", within noise" : ""}`
56
+ }
57
+
58
+ /**
59
+ * The confirmation, as lines: what will be restarted, how often, and for how
60
+ * long, from the plan and nothing else. Printed before the question, and
61
+ * printed the same way under --yes so the record says what was agreed to.
62
+ */
63
+ export function renderPlan(plan, { colour = colourEnabled(), env = process.env } = {}) {
64
+ const c = styler(colour)
65
+ const out = []
66
+ const names = plan.audited.map((plugin) => plugin.id)
67
+ out.push(...field("weighing", names.join(", "), c))
68
+ out.push(...field("shell", shellLine(plan.shellVersion, plan.omarchyPath, env), c))
69
+ out.push(...field("restarts", `${plan.restarts}: (1 baseline + ${plan.audited.length} plugin${plan.audited.length === 1 ? "" : "s"}) × ${plan.runs} run${plan.runs === 1 ? "" : "s"}`, c))
70
+ out.push(...field("estimate", `about ${plan.estimatedMinutes} minute${plan.estimatedMinutes === 1 ? "" : "s"}, ${figure(plan.perRestartSeconds, 0)} s per restart: ${figure(plan.timing.seconds, 1)} s for the shell to come back (${plan.timing.source}), then the ${plan.settleSeconds} s settle and the ${plan.windowSeconds} s window`, c))
71
+ out.push(...field("shell.json", `backed up beside itself and restored on every exit path; the md5 is printed before and after`, c))
72
+ out.push(...field("writes", withHomeAbbreviated(plan.out, env), c))
73
+ return out
74
+ }
75
+
76
+ /**
77
+ * The one question, from the plan: the count, the minutes, and the knob
78
+ * that sets both, so a person who wants a quick look knows what to type
79
+ * before the shell goes down once.
80
+ */
81
+ export function confirmationQuestion(plan) {
82
+ const knob = plan.runs === 1 ? "(--runs 1: a quick look, no spread and no verdict)" : `(--runs ${plan.runs}; --runs 1 for a quick look without a spread)`
83
+ return `Restart the shell ${plan.restarts} times now, about ${plan.estimatedMinutes} minute${plan.estimatedMinutes === 1 ? "" : "s"}? ${knob}`
84
+ }
85
+
86
+ /**
87
+ * @param {object} document the weigh document, docs/WEIGH.md
88
+ * @param {{ colour?: boolean, env?: object }} [options] `env` is where `$HOME` is read from for `~/` in paths; the document keeps them absolute.
89
+ */
90
+ export function renderWeigh(document, { colour = colourEnabled(), env = process.env } = {}) {
91
+ const c = styler(colour)
92
+ const out = []
93
+ const { settings, baseline, noiseFloor, config } = document
94
+ const baseRuns = baseline.pssMb.runs.length
95
+ out.push(...field("shell", `${shellLine(document.shell.version, document.shell.omarchyPath, env)}, ${document.started}`, c))
96
+ out.push(...field("method", `startup A/B, ${settings.runs} run${settings.runs === 1 ? "" : "s"}, a ${settings.windowSeconds} s window after a settle of ${settings.settleSeconds} s; Pss from /proc/<pid>/smaps_rollup at the end of the window, CPU from /proc/<pid>/stat over the window, children from a /proc walk every ${settings.sampleIntervalMs} ms`, c))
97
+ out.push(...field("noise floor", noiseFloor.cpuPercent === null
98
+ ? (baseRuns === 1 ? "none: one run has no spread, so no verdict is given (--runs 3 gives a floor)" : "unknown: no baseline run completed")
99
+ : `${figure(noiseFloor.cpuPercent)}% CPU, the spread of ${baseRuns} baseline run${baseRuns === 1 ? "" : "s"}; a CPU delta inside it is within noise`, c))
100
+ out.push(...field("memory", noiseFloor.pssMb === null
101
+ ? (baseRuns === 1 ? `${figure(baseline.pssMb.median, 1)} MB Pss in the one baseline run; no spread, so no variance to state` : "unknown: no baseline run completed")
102
+ : `within the shell's own startup variance (${figure(noiseFloor.pssMb)} MB over ${baseRuns} baseline run${baseRuns === 1 ? "" : "s"}, ${figure(noiseFloor.pssMbSettled)} MB at the settle): a fact about the shell's start, not a plugin's weight, until its two resting levels are understood (docs/MEASUREMENTS.md C1, C2)`, c))
103
+ if (baseline.pssMb.median !== null) {
104
+ out.push(...field("baseline", `${figure(baseline.pssMb.median, 1)} MB Pss and ${figure(baseline.cpuPercent.median)}% CPU, the median of ${baseRuns} run${baseRuns === 1 ? "" : "s"} without ${document.audited.length === 1 ? "the plugin" : `the ${document.audited.length} plugins`}`, c))
105
+ }
106
+ const restored = config.restored ? "restored and verified, backup removed" : c("fail", `differs from the backup, which is kept at ${withHomeAbbreviated(config.backup, env)}`)
107
+ out.push(...field("shell.json", `md5 ${config.md5Before} before, ${config.md5After} after: ${restored}`, c))
108
+ if (config.shellAnsweredAfterRestore === false) out.push(...action("The shell did not answer after the restore: run omarchy-restart-shell.", c))
109
+ if (document.failedRuns?.length) {
110
+ out.push(...field("incomplete", document.failedRuns.map((failed) => `run ${failed.run} of ${failed.label}: ${failed.reason}`).join("; "), c))
111
+ }
112
+ out.push("")
113
+
114
+ for (const [index, plugin] of document.plugins.entries()) {
115
+ if (index > 0) out.push("")
116
+ const state = rowState(plugin)
117
+ out.push(head(state, plugin.id, plugin.kinds.join(", ") || "no kinds", c))
118
+ out.push(...wrap(`${plugin.name === plugin.id ? "" : `${plugin.name}: `}${plugin.verdict.summary}${plugin.runsCompleted < settings.runs ? ` (${plugin.runsCompleted} of ${settings.runs} runs completed)` : ""}`, { indent: GUTTER }, c))
119
+ out.push(...labelled("memory", measured(plugin.shellPssMb, "MB", false), c))
120
+ out.push(...labelled("cpu", measured(plugin.shellCpuPercent, "%", plugin.withinNoise.cpu), c))
121
+ const spawns = plugin.childSpawns.median
122
+ // Unattributed extras in every run are stated as a count; extras seen
123
+ // in some runs only are stated with how many runs, so a difference in
124
+ // one run of three is never rounded away by the median.
125
+ const extras = plugin.unattributedChildren?.runs || []
126
+ const runsWithExtras = extras.filter((count) => count > 0).length
127
+ const extra = plugin.unattributedChildren?.median || 0
128
+ const commands = `(commands: ${(plugin.unattributedCommands || []).map((command) => withHomeAbbreviated(redactedCommand(command), env)).join("; ")})`
129
+ const unattributed = extra > 0
130
+ ? `${figure(extra, 0)} unattributed child process${extra === 1 ? "" : "es"} ${commands}`
131
+ : runsWithExtras > 0
132
+ ? `up to ${figure(Math.max(...extras), 0)} unattributed child process${Math.max(...extras) === 1 ? "" : "es"} in ${runsWithExtras} of ${extras.length} runs ${commands}`
133
+ : ""
134
+ out.push(...labelled("children", spawns === null
135
+ ? "no completed run"
136
+ : spawns === 0
137
+ ? (unattributed || "none attributed")
138
+ : `${figure(plugin.childRssMb.median)} MB and ${figure(plugin.childCpuPercent.median)}% CPU outside the shell, ${figure(spawns, 0)} process${spawns === 1 ? "" : "es"} per window${unattributed ? `; ${unattributed}` : ""}`, c))
139
+ }
140
+ out.push("")
141
+ out.push(...wrap(`Every figure is the median over ${settings.runs} run${settings.runs === 1 ? "" : "s"} of (with the plugin minus without it) with its spread, and carries its origin in the document under \`origin\`.`, {}, c))
142
+ out.push("")
143
+ // The closing word: weighed when every row has a verdict, a question
144
+ // otherwise, and the file the person is left with either way.
145
+ const unknown = document.plugins.filter((plugin) => rowState(plugin) === "unknown").length
146
+ const count = `${document.plugins.length} plugin${document.plugins.length === 1 ? "" : "s"} over ${settings.runs} run${settings.runs === 1 ? "" : "s"}`
147
+ const kept = config.restored ? "shell.json restored and verified." : `shell.json differs from the backup, which is kept at ${withHomeAbbreviated(config.backup, env)}.`
148
+ out.push(...(unknown
149
+ ? verdict("unknown", "WEIGHED", `${count}, ${unknown === document.plugins.length ? "with no verdict" : `${unknown} without a verdict`}: ${document.plugins.find((plugin) => rowState(plugin) === "unknown").verdict.summary}. ${kept}`, c)
150
+ : verdict("pass", "WEIGHED", `${count}. ${kept}`, c)))
151
+
152
+ // Last, because it is what an author came for: the sentence to paste, and
153
+ // the document that is its evidence. CPU and child processes only; the
154
+ // memory figures above are the shell's until C2 is understood.
155
+ const written = document.plugins.filter((plugin) => plugin.readme)
156
+ if (written.length) {
157
+ out.push("")
158
+ out.push(...section("for the README", c))
159
+ for (const plugin of written) {
160
+ out.push(`${" ".repeat(STEP)}${c("name", plugin.id)}`)
161
+ out.push(...wrap(plugin.readme, { indent: GUTTER }, c))
162
+ out.push(...labelled("evidence", withHomeAbbreviated(document.out, env), c))
163
+ }
164
+ }
165
+ return out.join("\n")
166
+ }
@@ -0,0 +1,63 @@
1
+ // The arithmetic behind every figure: a median with its spread, and the
2
+ // comparison that decides "within noise". Kept apart so tests/unit/weigh.test.mjs
3
+ // can hold it to known inputs without a shell.
4
+
5
+ /** The median of a list; null when the list is empty. */
6
+ export function median(values) {
7
+ const sorted = [...values].sort((a, b) => a - b)
8
+ if (!sorted.length) return null
9
+ const mid = Math.floor(sorted.length / 2)
10
+ return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2
11
+ }
12
+
13
+ /** Highest minus lowest; null when the list is empty. */
14
+ export function spread(values) {
15
+ if (!values.length) return null
16
+ return Math.max(...values) - Math.min(...values)
17
+ }
18
+
19
+ /**
20
+ * A figure over runs: `{ median, spread, min, max, runs }`. `runs` keeps the
21
+ * value per run, in run order, so a reader can see the three numbers a
22
+ * median came from.
23
+ */
24
+ export function stats(values) {
25
+ return {
26
+ median: median(values),
27
+ spread: spread(values),
28
+ min: values.length ? Math.min(...values) : null,
29
+ max: values.length ? Math.max(...values) : null,
30
+ runs: [...values],
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Within noise, or above it. A delta is above noise only when its magnitude
36
+ * exceeds the baseline's own spread and exceeds `quantum`, the smallest
37
+ * difference the measurement can express. For CPU that is one clock tick
38
+ * over the window (`tickPercent`); for memory it is zero, because a page is
39
+ * far below any floor. Measured in the lab on 14 September 2026: a plugin
40
+ * whose CPU delta was one tick over its 15.003 s window (-0.066662%) read
41
+ * as above a floor of one tick over a 15.005 s window (0.066653%), and one
42
+ * tick against one tick is no difference at all. A null median (no run
43
+ * completed) or a null floor (no baseline run) is unknown.
44
+ *
45
+ * @returns {"within-noise"|"above-noise"|"unknown"}
46
+ */
47
+ export function verdict(delta, floor, quantum = 0) {
48
+ if (delta === null || delta === undefined || floor === null || floor === undefined) return "unknown"
49
+ const size = Math.abs(delta)
50
+ return size > floor && size > quantum ? "above-noise" : "within-noise"
51
+ }
52
+
53
+ /** One clock tick over the window, as CPU percent: the quantum of every CPU figure here. */
54
+ export function tickPercent(clockTicksPerSecond, windowSeconds) {
55
+ return 100 / (clockTicksPerSecond * windowSeconds)
56
+ }
57
+
58
+ /** A number for a person: two decimals, no trailing zeros beyond the first, never "-0". */
59
+ export function figure(value, decimals = 2) {
60
+ if (value === null || value === undefined || !Number.isFinite(value)) return "?"
61
+ const rounded = Number(value.toFixed(decimals))
62
+ return String(Object.is(rounded, -0) ? 0 : rounded)
63
+ }