@codesema/runsema 0.0.0-stage → 0.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Hasan TASKIN
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,134 @@
1
- # Temporary Holding Version
1
+ # runsema
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A small launcher that runs a command with the share of the machine that is free right now, CPU and
4
+ memory, instead of a fixed worker count.
5
+
6
+ ```sh
7
+ runsema -- vitest run --maxWorkers={jobs}
8
+ ```
9
+
10
+ Linux and macOS. Go, standard library only, one static binary.
11
+
12
+ ## Why
13
+
14
+ Test runners, bundlers and linters size their workers once, from the core count. Two of them started
15
+ together each believe they own the machine, and so do the tools of every other terminal, agent session
16
+ or worktree. The usual answer is a fixed cap (`--maxWorkers=4`, `--concurrency=2`): too low when the
17
+ machine is idle, too high when it is busy. And a cap on workers says nothing about memory, which is
18
+ what kills processes.
19
+
20
+ runsema makes the cap follow the machine. Every run asks before it starts, gets what is free, and gives
21
+ it back when it ends. It learns what each command uses, so nothing has to be tuned. There is no daemon
22
+ and no configuration.
23
+
24
+ ## How it works
25
+
26
+ 1. **Slots.** One lock file per CPU, in a directory shared by every run. runsema takes the free ones
27
+ with `flock`, up to `-max`. The kernel frees them when runsema exits, whatever the reason, so a
28
+ killed run never leaks its slots.
29
+ 2. **Learning.** While a command runs, runsema measures the memory of its whole process tree. When it
30
+ ends, the peak is stored with the number of jobs it had. The weight of a command is the heaviest of
31
+ its last 10 runs, per job: a suite that grows counts from its next run.
32
+ 3. **Memory.** A command already measured gets as many jobs as fit in the memory available right now,
33
+ keeping 10% of the machine free. It announces what it is about to use, so the runs admitted behind
34
+ it do not count the same free memory twice while it loads.
35
+ 4. **Gate.** Runs are admitted one at a time. A command never measured keeps the gate closed until its
36
+ memory stops growing (30 s at most): it loads alone, then the next run decides on real figures.
37
+ 5. **Margin.** Every weight is multiplied by a safety margin, 1.2 at first and never under 1.1. A run
38
+ during which the machine ran short of memory widens it by a quarter; a quiet run tightens it a
39
+ little. The margin tunes itself.
40
+ 6. **Pressure.** runsema reads Linux pressure stall information (PSI): the share of the last 10 s some
41
+ task spent waiting for CPU or memory. Above `-pressure` it waits, because something that does not
42
+ go through runsema is already loading the machine.
43
+ 7. **Grant.** The command starts with the jobs that passed all of the above. After `-timeout` it starts
44
+ anyway with `-min`: runsema delays a run, it never blocks one.
45
+ 8. **Hand-over.** The grant reaches the command as `{jobs}` replaced in its arguments and as the
46
+ `RUNSEMA_JOBS` environment variable. Use whichever the tool can read.
47
+ 9. **Nesting.** A runsema started under another one reuses the grant of its parent and reserves
48
+ nothing, so a task runner and the tools it starts can both be wrapped.
49
+
50
+ The command gets the terminal as is, receives the signals runsema receives, and its exit code is the
51
+ exit code of runsema.
52
+
53
+ Inside a container the CPU count is the cgroup quota (`cpu.max`), the memory is the cgroup limit
54
+ (`memory.max`) and the pressure is the one of the container itself, so a busy neighbour does not slow
55
+ the run down. On a host all three are machine-wide.
56
+
57
+ ## Install
58
+
59
+ With npm, pnpm, yarn or bun. One package holds the binary of every platform and starts the one that
60
+ matches the machine, so nothing is downloaded at install and there is no optional dependency to resolve:
61
+
62
+ ```sh
63
+ npm install --save-dev @codesema/runsema
64
+ ```
65
+
66
+ With Go:
67
+
68
+ ```sh
69
+ go install github.com/getCodesema/runsema@latest
70
+ ```
71
+
72
+ Or build it and copy the binary anywhere:
73
+
74
+ ```sh
75
+ CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o runsema .
76
+ ```
77
+
78
+ ## Usage
79
+
80
+ ```sh
81
+ runsema -- jest --maxWorkers={jobs}
82
+ runsema -- turbo run test --concurrency={jobs}
83
+ runsema -max 4 -- playwright test --workers={jobs}
84
+ runsema -- sh -c 'make -j"$RUNSEMA_JOBS"'
85
+ ```
86
+
87
+ | Flag | Default | Meaning |
88
+ | ----------- | ----------------- | ---------------------------------------------------------------- |
89
+ | `-min` | `1` | Smallest grant; the run waits for it until `-timeout` |
90
+ | `-max` | half the CPUs | Largest grant, so that two full runs fit side by side |
91
+ | `-pressure` | `25` | PSI `avg10` percentage above which the run waits |
92
+ | `-timeout` | `5m` | Longest wait before running anyway with `-min` |
93
+ | `-io` | off | Also watch IO pressure (CPU and memory are always watched) |
94
+ | `-quiet` | off | Do not print the grant on stderr |
95
+ | `-dir` | see below | Directory of the lock files (slots, gate, reservations) |
96
+ | `-state` | see below | File remembering what each command used |
97
+
98
+ The lock directory is `$RUNSEMA_DIR`, else `$XDG_RUNTIME_DIR/runsema`, else `/tmp/runsema-<uid>`. Runs
99
+ share the machine only when they share this directory. What runsema learned is kept in
100
+ `runsema/profiles.json` under the user cache directory (`~/.cache` on Linux); delete it to forget.
101
+
102
+ A command is recognised by its directory and its arguments: the same script in another worktree is
103
+ measured again.
104
+
105
+ ## Limits
106
+
107
+ - The grant is fixed when the command starts: a run does not grow when the machine frees up, and a
108
+ run that outgrows what it announced is not stopped.
109
+ - Memory is counted per job, as if it grew in proportion: a command with a large fixed part is
110
+ overestimated at low job counts.
111
+ - A command that ends in under a second is never measured.
112
+ - Nested runs each reuse the whole grant of their parent; it is not divided between siblings.
113
+ - A tool that ignores both `{jobs}` and `RUNSEMA_JOBS` is only delayed, not sized.
114
+ - Without PSI (kernel before 4.20, or PSI disabled) the slots and the memory still apply.
115
+ - macOS has neither PSI nor `/proc`: only the slots apply there. No Windows (`flock`).
116
+
117
+ ## Development
118
+
119
+ ```sh
120
+ go vet ./... && go test ./...
121
+ sh scripts/smoke-npm.sh
122
+ ```
123
+
124
+ `admission.go` holds the decisions as pure functions (`bytesPerJob`, `jobsThatFit`, `rampStable`,
125
+ `nextMargin`). `memory.go` reads the machine: memory, process trees, PSI. `profile.go` stores what was
126
+ learned. `main.go` is the flow: gate, slots, reservations, the command, the measure. Each has its test
127
+ file, on temporary directories.
128
+
129
+ `scripts/build-npm.sh <version>` builds the npm package into `dist/npm/runsema`: the launcher and one
130
+ binary per platform. `scripts/smoke-npm.sh` installs it in an empty project and runs the command.
131
+
132
+ ## License
133
+
134
+ [MIT](LICENSE)
Binary file
Binary file
Binary file
Binary file
package/bin/runsema.js ADDED
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // Starts the binary built for this machine, shipped next to this file, and gets out of the way:
5
+ // same arguments, same terminal, same signals, same exit code.
6
+ const { spawn } = require('node:child_process');
7
+ const { existsSync } = require('node:fs');
8
+ const { constants } = require('node:os');
9
+ const { join } = require('node:path');
10
+
11
+ const binary = join(__dirname, `runsema-${process.platform}-${process.arch}`);
12
+
13
+ if (!existsSync(binary)) {
14
+ console.error(`runsema: no binary for ${process.platform} ${process.arch} (runsema runs on Linux and macOS, x64 or arm64).`);
15
+ process.exit(1);
16
+ }
17
+
18
+ const child = spawn(binary, process.argv.slice(2), { stdio: 'inherit' });
19
+
20
+ for (const name of ['SIGINT', 'SIGTERM', 'SIGHUP']) {
21
+ process.on(name, () => child.kill(name));
22
+ }
23
+
24
+ child.on('error', (error) => {
25
+ console.error(`runsema: ${error.message}`);
26
+ process.exit(127);
27
+ });
28
+
29
+ child.on('exit', (code, signal) => {
30
+ process.exit(signal === null ? code : 128 + constants.signals[signal]);
31
+ });
package/package.json CHANGED
@@ -1,6 +1,24 @@
1
1
  {
2
- "name": "@codesema/runsema",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
2
+ "name": "@codesema/runsema",
3
+ "version": "0.1.0",
4
+ "description": "Launcher that sizes a command's parallelism from the CPUs and memory free right now: shared flock slots, learned memory, Linux pressure (PSI).",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/getCodesema/runsema.git"
9
+ },
10
+ "bin": {
11
+ "runsema": "bin/runsema.js"
12
+ },
13
+ "files": [
14
+ "bin"
15
+ ],
16
+ "os": [
17
+ "darwin",
18
+ "linux"
19
+ ],
20
+ "cpu": [
21
+ "arm64",
22
+ "x64"
23
+ ]
24
+ }