@miroshnik/elves 0.0.0 → 2026.1018.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.
Files changed (68) hide show
  1. package/AGENTS.md +310 -0
  2. package/README.md +344 -0
  3. package/bin/ai-dev.ts +7 -0
  4. package/bin/elves.ts +2847 -0
  5. package/bin/shims/timeout +113 -0
  6. package/claude/CLAUDE.md +71 -0
  7. package/dist/github/scripts/github.js +3428 -0
  8. package/dist/spec/scripts/architecture.js +628 -0
  9. package/dist/spec/scripts/eslint-config.js +54 -0
  10. package/dist/spec/scripts/harness-qzb932c8.js +959 -0
  11. package/dist/spec/scripts/harness-v9s1v9w3.js +16045 -0
  12. package/dist/spec/scripts/harness.js +53 -0
  13. package/dist/spec/scripts/knip-hints.cjs +34 -0
  14. package/docs/ci-concurrency.md +82 -0
  15. package/docs/cloud-sessions.md +204 -0
  16. package/docs/judgment.md +81 -0
  17. package/docs/parallel-checkouts.md +220 -0
  18. package/docs/pr-checks.md +117 -0
  19. package/docs/testing.md +304 -0
  20. package/package.json +70 -1
  21. package/skills/ci-wait/SKILL.md +180 -0
  22. package/skills/ci-wait/scripts/wait-ci.sh +377 -0
  23. package/skills/dashboard/SKILL.md +212 -0
  24. package/skills/dashboard/scripts/dashboard.ts +912 -0
  25. package/skills/dashboard/scripts/sessions.ts +772 -0
  26. package/skills/est/SKILL.md +222 -0
  27. package/skills/est/reference.md +381 -0
  28. package/skills/est/scripts/est.ts +4867 -0
  29. package/skills/github/SKILL.md +206 -0
  30. package/skills/github/locales/en.json +449 -0
  31. package/skills/github/locales/ru.json +449 -0
  32. package/skills/github/reference.md +1073 -0
  33. package/skills/github/scripts/github.ts +4390 -0
  34. package/skills/github/scripts/locale.ts +495 -0
  35. package/skills/github/scripts/package.json +3 -0
  36. package/skills/slot/SKILL.md +145 -0
  37. package/skills/slot/scripts/package.json +3 -0
  38. package/skills/slot/scripts/slot.ts +279 -0
  39. package/skills/spec/SKILL.md +247 -0
  40. package/skills/spec/canon.md +219 -0
  41. package/skills/spec/checks.md +709 -0
  42. package/skills/spec/ci.md +378 -0
  43. package/skills/spec/migration.md +125 -0
  44. package/skills/spec/reference.md +889 -0
  45. package/skills/spec/scripts/architecture.ts +839 -0
  46. package/skills/spec/scripts/c4.ts +184 -0
  47. package/skills/spec/scripts/eslint-config.ts +82 -0
  48. package/skills/spec/scripts/harness.ts +1483 -0
  49. package/skills/spec/scripts/knip-hints.cjs +34 -0
  50. package/skills/spec/scripts/knip-hints.d.cts +17 -0
  51. package/skills/spec/scripts/package-run.ts +22 -0
  52. package/skills/spec/scripts/package.json +3 -0
  53. package/skills/spec/scripts/spec-break.ts +232 -0
  54. package/skills/spec/scripts/spec-claims.ts +198 -0
  55. package/skills/spec/scripts/spec-diff.ts +1111 -0
  56. package/skills/spec/scripts/spec-doc.ts +744 -0
  57. package/skills/spec/scripts/spec-exceptions.ts +233 -0
  58. package/skills/spec/scripts/spec-publish.ts +214 -0
  59. package/skills/spec/scripts/spec-run.ts +235 -0
  60. package/skills/spec/scripts/spec-shard.ts +267 -0
  61. package/skills/spec/scripts/speclib.ts +826 -0
  62. package/skills/spec/scripts/vendor/babel-parser.LICENSE +19 -0
  63. package/skills/spec/scripts/vendor/babel-parser.cjs +14615 -0
  64. package/skills/spec/scripts/vendor/babel-parser.d.cts +3 -0
  65. package/skills/task-env/SKILL.md +229 -0
  66. package/skills/task-env/proxy/compose.yaml +48 -0
  67. package/skills/task-env/reference.md +246 -0
  68. package/skills/task-env/scripts/task-env.ts +981 -0
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env -S bun --no-env-file
2
+ /**
3
+ * timeout — GNU coreutils `timeout` for a machine without it (macOS without coreutils). The SessionStart hook puts this
4
+ * directory at the end of the agent's Bash PATH (bin/elves.ts, `agentShellScript`), so a real `timeout` wins;
5
+ * tests/capabilities/agent-shell.
6
+ *
7
+ * timeout [-s SIGNAL] [-k DURATION] [--preserve-status] [--foreground] [-v] DURATION COMMAND [ARG]...
8
+ *
9
+ * DURATION — a number with an optional suffix s, m, h, d; 0 — no limit. On expiry the command's process group gets
10
+ * SIGNAL (TERM by default): a grandchild that outlived the command would hold the call's output until its own end;
11
+ * with -k — KILL after DURATION more. Exit code: 124 — timed out (137 if KILL ended the command, as GNU), the
12
+ * command's own with --preserve-status; 125 — timeout itself failed; 126 — the command can't run; 127 — not found;
13
+ * otherwise the command's (128 + N for signal N). INT, TERM, HUP and QUIT that timeout gets go to the command.
14
+ * The `--no-env-file` flag in the shebang: Bun would load the project's env files and pass them to the command.
15
+ */
16
+ import { spawn } from "node:child_process";
17
+ import { constants } from "node:os";
18
+
19
+ const USAGE = "Usage: timeout [-s SIGNAL] [-k DURATION] [--preserve-status] [--foreground] [-v] DURATION COMMAND [ARG]...";
20
+ const SIGNALS: Record<string, number> = constants.signals;
21
+ /** setTimeout fires at once past 2^31 - 1 ms (24.8 days): a longer limit waits that long — no agent call lasts it. */
22
+ const MAX_DELAY = 2 ** 31 - 1;
23
+ const UNITS: Record<string, number> = { "": 1, s: 1, m: 60, h: 3600, d: 86400 };
24
+
25
+ function fail(message: string): never {
26
+ process.stderr.write(`timeout: ${message}\n`);
27
+ process.exit(125);
28
+ }
29
+
30
+ /** DURATION in milliseconds. */
31
+ function duration(text: string) {
32
+ const m = /^(\d+(?:\.\d*)?|\.\d+)([smhd]?)$/.exec(text);
33
+ if (!m) fail(`invalid time interval '${text}'\n${USAGE}`);
34
+ return Math.min(Number(m[1]) * (UNITS[m[2] ?? ""] ?? 1) * 1000, MAX_DELAY);
35
+ }
36
+
37
+ /** A signal by name (KILL, SIGKILL, kill) or number. */
38
+ function signal(text: string): NodeJS.Signals {
39
+ const name = /^\d+$/.test(text) ? Object.keys(SIGNALS).find((k) => SIGNALS[k] === Number(text)) : `SIG${text.toUpperCase().replace(/^SIG/, "")}`;
40
+ if (!name || !Object.hasOwn(SIGNALS, name)) fail(`${text}: invalid signal`);
41
+ return name as NodeJS.Signals;
42
+ }
43
+
44
+ const args = process.argv.slice(2);
45
+
46
+ /** The value of an option before DURATION: `-s KILL`, `-sKILL`, `--signal KILL`, `--signal=KILL`; not it — undefined. */
47
+ function value(short: string, long: string) {
48
+ const a = args[0] ?? "";
49
+ const inline = a.startsWith(`${long}=`) ? a.slice(long.length + 1) : a.startsWith(short) && a.length > short.length ? a.slice(short.length) : undefined;
50
+ if (inline !== undefined) return args.shift(), inline;
51
+ if (a !== short && a !== long) return undefined;
52
+ args.shift();
53
+ return args.shift() ?? fail(`option requires an argument -- '${short.slice(1)}'\n${USAGE}`);
54
+ }
55
+
56
+ let sig: NodeJS.Signals = "SIGTERM";
57
+ let killAfter = 0;
58
+ let preserve = false;
59
+ let foreground = false;
60
+ let verbose = false;
61
+ // options go before DURATION, as with GNU: what follows belongs to the command
62
+ for (let a = args[0]; a?.startsWith("-") && a !== "-"; a = args[0]) {
63
+ let v;
64
+ if (a === "--") {
65
+ args.shift();
66
+ break;
67
+ } else if ((v = value("-s", "--signal")) !== undefined) sig = signal(v);
68
+ else if ((v = value("-k", "--kill-after")) !== undefined) killAfter = duration(v);
69
+ else if (a === "--preserve-status") (preserve = true), args.shift();
70
+ else if (a === "--foreground") (foreground = true), args.shift();
71
+ else if (a === "-v" || a === "--verbose") (verbose = true), args.shift();
72
+ else if (a === "--help") process.stdout.write(USAGE + "\n"), process.exit(0);
73
+ else fail(`unrecognized option '${a}'\n${USAGE}`);
74
+ }
75
+ const [limitText, cmd, ...rest] = args;
76
+ if (limitText === undefined || cmd === undefined) fail(`missing operand\n${USAGE}`);
77
+ const limit = duration(limitText);
78
+
79
+ // its own process group (a session) — the signal reaches the command's children too; --foreground — the command only
80
+ const child = spawn(cmd, rest, { stdio: "inherit", detached: !foreground });
81
+ let timedOut = false;
82
+ const timers: ReturnType<typeof setTimeout>[] = [];
83
+
84
+ function send(s: NodeJS.Signals) {
85
+ if (verbose && timedOut) process.stderr.write(`timeout: sending signal ${s.slice(3)} to command '${cmd}'\n`);
86
+ try {
87
+ if (foreground || child.pid === undefined) child.kill(s);
88
+ else process.kill(-child.pid, s);
89
+ } catch {
90
+ // the group is already gone
91
+ }
92
+ }
93
+
94
+ if (limit > 0) {
95
+ timers.push(
96
+ setTimeout(() => {
97
+ timedOut = true;
98
+ send(sig);
99
+ if (killAfter > 0) timers.push(setTimeout(() => send("SIGKILL"), killAfter));
100
+ }, limit),
101
+ );
102
+ }
103
+ for (const s of ["SIGINT", "SIGTERM", "SIGHUP", "SIGQUIT"] as const) process.on(s, () => send(s));
104
+
105
+ child.on("error", (e: NodeJS.ErrnoException) => {
106
+ process.stderr.write(`timeout: failed to run command '${cmd}': ${e.code === "ENOENT" ? "No such file or directory" : e.message}\n`);
107
+ process.exit(e.code === "ENOENT" ? 127 : 126);
108
+ });
109
+ child.on("exit", (code, signalName) => {
110
+ for (const t of timers) clearTimeout(t);
111
+ const status = signalName ? 128 + (SIGNALS[signalName] ?? 0) : (code ?? 0);
112
+ process.exit(timedOut && !preserve && signalName !== "SIGKILL" ? 124 : status);
113
+ });
@@ -0,0 +1,71 @@
1
+ # Claude Code
2
+
3
+ Shared rules — `AGENTS.md`; here — only what Claude Code has. `install -g`
4
+ puts both into `~/.claude/rules/` (`elves.md`, `elves-claude.md`): loaded in
5
+ every project, whatever its `CLAUDE.md`.
6
+
7
+ - Session transcripts (`~/.claude/projects/<path-with-dashes>*/`) are the
8
+ source of actuals for the `est` skill; `~/.claude/settings.json` must have
9
+ `"cleanupPeriodDays": 365`, otherwise history is cleaned before it's needed.
10
+ - The app's worktree names the branch `claude/<slug>-<hash>` — it is renamed
11
+ when the task is taken: `github task status <N> "In progress" --branch
12
+ <type>/<N>-<slug>` (a bare `git branch -m` fails on `.git/config` locked by
13
+ a parallel worktree and leaves the upstream behind). The session panel
14
+ (branch, diff to `main`, "Create PR") reads git, not GitHub: after a merge
15
+ it is accurate once the checkout is on `origin/main` (`github task close`
16
+ moves it).
17
+ - Auto-fix (CI monitor) — off: its conflict event wants the base merged in,
18
+ breaking the rebase merge. After `gh pr create` — `set_monitor` with
19
+ `auto_fix: false` and the PR's url; a conflict — rebase onto a fresh
20
+ `origin/main`; an executor's PR — its executor's (`docs/pr-checks.md`,
21
+ "Auto-fix"). Checks and the deploy — `ci-wait` as a background command,
22
+ its completion wakes the session; no `Monitor`, watcher loop or
23
+ `ScheduleWakeup` on top: an armed wakeup keeps `archive_session` refused
24
+ even after `stop`.
25
+ - Merge in auto mode — `gh pr merge <N> --rebase` as a separate call, without
26
+ `cd`, `|`, `;`: otherwise the `Bash(gh pr merge *)` rule (set by the owner)
27
+ won't match and the classifier rejects the merge.
28
+ - `install -g` puts a `SessionStart` hook into `~/.claude/settings.json`
29
+ (`startup|resume|clear`): `check --hook` — the machine (canon, skills, hook)
30
+ and the project's flow with its mode and language (`AGENTS.md`, "First step
31
+ of a session") into the start of the context: a thin project's lag —
32
+ `update` in the session's task, a copy — the canon twice and its move task,
33
+ no flow — install it; a
34
+ session with a pinned task (a resume after the app restarted) gets the wait
35
+ to restart — `github task resume` with the session id from the hook's input.
36
+ The hook always exits 0 (else Claude Code drops its output), timeout 60 s.
37
+ No hook output in context — run `check` by hand. The hook also writes the
38
+ agent's Bash environment to `$CLAUDE_ENV_FILE`: zsh passes a glob without
39
+ matches and `====` as is, `timeout` works without coreutils; the
40
+ «agent Bash: not set up» line — it didn't, follow the line.
41
+ - `set_session_title` renames the session; in auto mode `archive_session`
42
+ with `self` archives it, ending the conversation: the last action, after
43
+ the "can be closed" line. Before it — `TaskStop` for each own
44
+ background task still running: a live one gives "still has live work".
45
+ Refused after that — don't retry: the owner archives from the sidebar.
46
+ - Skills — the machine's `~/.claude/skills/<name>` (symlinks to
47
+ `~/.agents/skills/<name>`), a thin project has no others; the skill
48
+ directory is `${CLAUDE_SKILL_DIR}`; a same-named machine skill wins over a
49
+ project's. `SKILL.md` frontmatter also reads `when_to_use`,
50
+ `argument-hint`, `allowed-tools`.
51
+ - The Bash sandbox: `gh` in it fails on TLS (`x509: OSStatus -26276`). A
52
+ skill script that calls `gh` (`github`, `est`, `dashboard`, `spec-run`) —
53
+ at once a separate call with `dangerouslyDisableSandbox`: no
54
+ `excludedCommands` entry takes `bun <file>` out. Entries `gh *` and `bash
55
+ <path>/wait-ci.sh *` (`ci-wait`) do — the owner's step; without them — the
56
+ same separate call. The `task-env` skill, section "Docker and the sandbox".
57
+ - A subagent that writes gets `isolation: worktree`: an isolated session
58
+ doesn't write into others' worktrees, an agent without it inherits the
59
+ parent's isolation. An agent cut off (a limit, a dropped session) leaves
60
+ uncommitted work in its worktree — `git status` there before resuming;
61
+ after a base change — dependencies from the lockfile, else typecheck runs
62
+ on old packages.
63
+ - In a cloud session (claude.ai/code) the cloud assigns the branch — rename
64
+ it or push to another one only with the user's permission
65
+ (`docs/cloud-sessions.md`).
66
+ - Another task isn't a session card (`spawn_task`, `start_session`): the
67
+ question «queue #M?», on "yes" its executor — `Agent` with `isolation:
68
+ "worktree"`, `run_in_background: true` and the prompt `github` printed.
69
+ The executor isn't `TaskStop`ped: the last line — after its answer.
70
+ - An Elves clone: `install -g --link` links `~/.claude/rules/elves*.md` to
71
+ it, its `.claude/CLAUDE.md` keeps the root `AGENTS.md` from loading twice.