@miroshnik/elves 0.0.0 → 2026.1018.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.
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 +203 -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 +221 -0
  27. package/skills/est/reference.md +381 -0
  28. package/skills/est/scripts/est.ts +4854 -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 +231 -0
  66. package/skills/task-env/proxy/compose.yaml +51 -0
  67. package/skills/task-env/reference.md +254 -0
  68. package/skills/task-env/scripts/task-env.ts +1017 -0
package/README.md ADDED
@@ -0,0 +1,344 @@
1
+ <div align="center">
2
+
3
+ # Elves
4
+
5
+ *Elves at work*
6
+
7
+ **A development flow with AI agents: one canon of rules, skills with scripts<br>and a specification checked by the machine, not by the agent's memory.**
8
+
9
+ Claude Code · Codex · Gemini CLI · Cursor · Copilot · OpenCode · Amp
10
+
11
+ [![CI](https://github.com/miroshnik/elves/actions/workflows/ci.yml/badge.svg)](https://github.com/miroshnik/elves/actions/workflows/ci.yml)
12
+ [![Release](https://img.shields.io/github/v/release/miroshnik/elves?sort=date&label=release)](https://github.com/miroshnik/elves/releases)
13
+ [![Agent Skills](https://img.shields.io/badge/Agent%20Skills-standard-6f42c1)](https://agentskills.io)
14
+ [![Spec](https://img.shields.io/badge/spec-branch-0e8a16)](https://github.com/miroshnik/elves/tree/spec)
15
+
16
+ [The idea](#the-idea) · [Why](#why) · [Principles](#principles) · [Elves and OpenSpec](#elves-and-openspec) · [Installation](#installation) · [Skills](#skills) · [Layout](#repository-layout)
17
+
18
+ </div>
19
+
20
+ ---
21
+
22
+ ## What it is
23
+
24
+ Elves is how an agent carries a task from issue to production, written down once for all agents and all projects:
25
+
26
+ - **The canon** [`AGENTS.md`](AGENTS.md) — the rules: tasks and estimates, branches and PRs, merging and CI, the specification, production.
27
+ - **Skills** following the open [Agent Skills](https://agentskills.io) standard — a prompt plus a script for everything deterministic: the GitHub project, estimates from actuals, the spec from tests, waiting for CI.
28
+ - **The installer** — puts the canon, the skills and the hook onto the machine, for all agents at once; a project keeps only the package version in `devDependencies` — CI, colleagues and the cloud session get it from npm — and gets the flow through releases.
29
+
30
+ One task — from issue to actual:
31
+
32
+ ```mermaid
33
+ flowchart LR
34
+ subgraph task ["1 · Task"]
35
+ direction TB
36
+ I["Issue<br>goal · questions · scenarios"] --> E["task new estimates it<br>from analogs with actuals"] --> S["Session #42<br>branch feat/42-slug"]
37
+ end
38
+ subgraph work ["2 · Work"]
39
+ direction TB
40
+ T["Checks<br>red first"] --> C["Code<br>checks green"]
41
+ end
42
+ subgraph ship ["3 · Rollout"]
43
+ direction TB
44
+ P["PR<br>spec-diff · Closes #42"] --> G["Green checks<br>ci-wait"] --> M["Merge and deploy<br>spec to the spec branch"]
45
+ end
46
+ subgraph done ["4 · Closing"]
47
+ direction TB
48
+ U["Epic refresh<br>in parallel with deploy"] --> F["est actual<br>hours · tokens · $"]
49
+ end
50
+ task --> work --> ship --> done
51
+ ```
52
+
53
+ ## The idea
54
+
55
+ Developing any software is making decisions, of two kinds:
56
+
57
+ - **functional** — what the system does: what it stores and for how long (an order — for good, a cart — for the session), what it shows and to whom, which interface it gives;
58
+ - **non-functional** — what it's made of and which rules it obeys: data in Postgres, code in TypeScript, every input form has a reset button, search answers in under 300 ms.
59
+
60
+ Every decision has a **mechanical check** — deterministic and concrete, run by the machine rather than judged by a person or an agent — that the decision holds. A decision without a check is a wish: nothing tells when the code stops following it.
61
+
62
+ So the project answers two questions in two places:
63
+
64
+ | Question | Answered by |
65
+ |---|---|
66
+ | What does the system do and how is it built — now | the code |
67
+ | What should it do and how should it be built | the checks — they are the system's specification |
68
+
69
+ TDD follows by itself. A check is written before the code and fails — the decision isn't implemented yet: that's its acceptance test. The code makes it pass, and from then on the same check holds the decision against every later change: that's its regression test. Not only behavior: "fast" is a decision too, and its check is a load test with a budget.
70
+
71
+ Any decision can be changed — through its check: the check changes first and goes red on the current code, then the code follows it to green. The spec leads, the code follows: a code change that breaks a decision turns its check red.
72
+
73
+ | Decision | Kind | Check | Lives in | Label |
74
+ |---|---|---|---|---|
75
+ | An order survives a restart, a cart lives for the session | functional | an integration test | `tests/capabilities/orders` | 🟩 |
76
+ | The manager sees overdue invoices first | functional | an e2e test | `tests/capabilities/invoices` | 🟩 |
77
+ | Search answers in under 300 ms at 100 requests per second | non-functional, a quality of one capability | a load test with the budget | `tests/capabilities/search` | 🟩 |
78
+ | Every input form has a reset button | non-functional, a standard | "registry + invariant": every form from the code, a test per form | `tests/standards/form-reset` | 🟦 |
79
+ | Code is TypeScript | non-functional, a standard | "registry + invariant": every source file is `.ts` | `tests/standards/typescript` | 🟦 |
80
+ | Data is stored in Postgres | non-functional, architecture | the model: the `db` container and its `pg` client; a module importing another driver — red | `tests/architecture/model.ts` | 🟧 |
81
+
82
+ **Decision labels** on GitHub tasks name the decision and show its kind by color: 🟩 green — a capability, functional; 🟦 blue — a standard and 🟧 orange — architecture, both non-functional. The color follows where the decision lives, so the budget of one capability, kept next to its behavior, is green; `github project check` holds the colors. How each check is wired — the [`spec`](skills/spec/SKILL.md) skill.
83
+
84
+ ## Why
85
+
86
+ An agent writes code fast. Everything around the code is what becomes slow and expensive:
87
+
88
+ | Without the flow | With Elves |
89
+ |---|---|
90
+ | Knowledge lives in the chat and in the agent's memory — the next session, another agent or account starts from scratch | Everything decided is in the issue and the repository; before a pause — a State comment, and any agent can continue |
91
+ | Specs and rules-as-text go stale silently — you see it only once a bug has happened | Every decision — functional and non-functional — is held by a check; a change without one turns CI red |
92
+ | Estimates are guesses — they can't be checked or learned from | An estimate comes from analogs with a measured actual; the actual comes from the agent's transcripts |
93
+ | Every project has its own process; one project's lesson doesn't reach another | One canon for all projects and agents; an improvement reaches everyone with a release |
94
+ | The agent commits, pushes and merges on its own — or asks about everything every time | Reversible — on its own, irreversible — on an explicit "yes" to a specific action |
95
+
96
+ ## Principles
97
+
98
+ The full canon is [`AGENTS.md`](AGENTS.md); here is what it stands on.
99
+
100
+ 1. **Code is what is; checks are what should be.** Code describes what the system does and how it is built now, so it can't be the source of what should be. What should be lives in checks, and they are all of the project's decisions: functional — tests in `tests/capabilities`, what the system must do; non-functional — architectural (`tests/architecture`, what it's made of) and standards (`tests/standards`, which rules the code obeys and which qualities it holds: performance, security, accessibility). They diverge — red CI, not silent drift. A decision changes through its check: the check first, red on the current code, then the code. → [Specification — decisions](AGENTS.md#specification--decisions)
101
+ 2. **No check — no decision.** A decision exists while a mechanism checks it: behavior — a test, a convention — a lint rule or "registry + invariant", architecture — a C1–C3 model with checks over it, a quality — a budget in a capability's test or a standard's cross-cutting invariant. Prose doesn't have to be true; a test fails. Documentation is generated from test names and published to the `spec` branch after merge: it isn't in `main` — a derived copy would conflict in every parallel PR, and review goes by the diff of requirements (`spec-diff`). → [Specification — decisions](AGENTS.md#specification--decisions)
102
+ 3. **One fact — one home.** Every fact has exactly one place: a cross-cutting rule — in a standard, behavior — in its capability, a decision's reason — in `<decision>.md` next to its check, the plan and agreements — in the issue, what is shared by all projects — in Elves, what is project-specific — in the project's spec. A copy goes stale silently: that's how specs diverge — a change edits one place out of several. → [Project knowledge and handoff](AGENTS.md#project-knowledge-and-handoff)
103
+ 4. **A check must be able to fail.** A test is written before the code and is red first — the decision's acceptance test; green after the code, it stays as its regression test. A regression test is proven by reverting the fix, an inherited one — by breaking the code. A test that can't fail is worse than a missing one. → [Tests and code](AGENTS.md#tests-and-code)
104
+ 5. **The issue is the only place to agree.** The goal, `## Questions` with answers, `## Scenarios` — future test names, visible before code; status and links — GitHub fields and relationships. No proposal files, no TODOs, no plan in the agent's memory. → [Task tracking](AGENTS.md#task-tracking)
105
+ 6. **Session = task = branch = PR.** Session `#42 …`, branch `feat/42-slug`, `Closes #42` in the PR: work is always linked to a task, so the task has an actual. A session won't take a second task into work (`task status` refuses): it goes to Backlog, and on the owner's "yes" to queuing it — to a background executor, a subagent in its own worktree. → [Session and branch](AGENTS.md#session-and-branch--one-task)
106
+ 7. **Estimates from actuals.** Analogs with a measured actual instead of a guess; the actual is the agent's active hours, tokens and their API equivalent in dollars. → [Estimate and actual](AGENTS.md#estimate-and-actual--the-est-skill)
107
+ 8. **A script instead of memory.** Everything deterministic is in the skills' scripts (TypeScript under Bun, no dependencies): the agent decides, the script does — the same way in every project. → [This file and skills](AGENTS.md#this-file-and-skills)
108
+ 9. **Irreversible — on an explicit "yes".** Reading, tests, local edits and task tracking — without asking; commit, push, merge, deploy and writes to live databases — with permission for that action and for this time. A project can let the agent run a task entirely by itself — up to merging its own PR on green checks — with the `auto` setting at install; the same install sets the project language the agent talks and writes in. → [What I do without asking and what needs a yes](AGENTS.md#what-i-do-without-asking-and-what-needs-a-yes)
109
+ 10. **Into `main` — each green on its own.** Merge as soon as the PR's own checks are green, without a queue or an up-to-date-branch requirement; before merging, if `main` moved after the PR's CI — fast checks on the merge with a fresh `main`; after a deploy — waiting for its statuses; a test failed — the root cause is fixed, not a retry added. → [Git, PRs and merging](AGENTS.md#git-prs-and-merging)
110
+ 11. **One canon, versions by releases.** A project pins the package version, not a copy of the flow, and updates by release — a few lines in the current task; a session starts by checking whether the flow is behind. → [First step of a session](AGENTS.md#first-step-of-a-session--an-up-to-date-flow)
111
+
112
+ ## Elves and OpenSpec
113
+
114
+ [OpenSpec](https://github.com/Fission-AI/OpenSpec) is a popular spec-driven framework: requirements in Markdown (`SHALL`, `WHEN` / `THEN` scenarios), every change is a folder in `openspec/changes/` with `proposal.md`, `tasks.md`, `design.md` and spec deltas that are merged into `openspec/specs/` after rollout (`/opsx:propose` → `/opsx:apply` → `/opsx:archive`). Elves used to run on top of OpenSpec; now it replaces it entirely.
115
+
116
+ | | OpenSpec | Elves |
117
+ |---|---|---|
118
+ | **Where a requirement lives** | `openspec/specs/<capability>/spec.md` — prose | `tests/capabilities/<capability>/` — `describe` is the requirement, `it` a scenario; it runs |
119
+ | **What keeps the spec true** | the agent's discipline; `validate` checks structure, not truth | CI: behavior changed without a test — red |
120
+ | **Agreement before code** | `proposal.md`, `tasks.md` | the issue: `## Questions` and `## Scenarios` — future test names |
121
+ | **A change in the PR** | ADDED / MODIFIED / REMOVED deltas — separate files written by the agent | `spec-diff` from git: added, changed and removed requirements |
122
+ | **Code standards** | text in `config.yaml` and specs | `tests/standards/`: a lint rule or an invariant with a violator, exceptions with a ratchet |
123
+ | **Architecture and "why"** | `design.md` in the change archive | a C1–C3 model with checks; the reason — in `<decision>.md` next to the check |
124
+ | **Qualities** (performance, security, accessibility) | a prose requirement, like behavior | cross-cutting — a "registry + invariant" standard (audit on every mutation, axe on every page); one capability's budget — in its tests |
125
+ | **Documentation** | the specs themselves — read them whole | generated from test names into the `spec` branch |
126
+ | **Uncheckable (look, copy)** | a prose requirement | not called spec — acceptance in the issue; where the look is a decision, it's a screenshot test |
127
+ | **Tasks, estimates, CI, merge** | out of scope | the canon and skills: GitHub Projects, `est`, `ci-wait` |
128
+
129
+ ### What a live project showed
130
+
131
+ A product project on OpenSpec: 68 specs, ≈1000 requirements and ≈4000 scenarios — 3 MB of Markdown, plus the change archive.
132
+
133
+ **Audit before the switch**
134
+
135
+ - **Specs go stale silently.** Of 11 requirements checked, 6 were stale; three direct contradictions between specs; 23 of 68 specs have "TBD" as their purpose; 14 of 347 file paths lead nowhere. `openspec validate --strict` is green all the while — it checks structure, not truth. The cause is one: a fact is written in several specs, and a change edits its own.
136
+ - **The spec and the tests are two independent records of the same behavior.** Of 40 random spec scenarios, 5 were found among test names.
137
+ - **Standards are held by code, not by text.** Those built into code are followed (an audit record in 64 of 65 mutations), those written in prose aren't: 26 forbidden constructs, 6 of them added after the rule.
138
+ - **Specs are expensive to read.** One spec is 463 KB, over 100k tokens, and to "check against the spec" the agent reads it whole. The archive of 237 changes is 10.9 MB, 19–29% of it makes it into the live specs; `proposal.md` and `tasks.md` repeat the issue. In a measured task ≈26% of what the agent read was OpenSpec artifacts.
139
+
140
+ **Migration into tests** — 1052 requirements, 13 PRs
141
+
142
+ - Only 585 requirements (56%) had a test, 255 had one partially, 149 had none at all.
143
+ - ≈950 requirements became tests; ≈102 were removed with a reason (look, copy, one-off migrations, infrastructure outside the repository); ≈105 were rewritten from the code — the spec had fallen behind an intentional decision.
144
+ - Check by breaking — over 3.6k targeted code breakages — found at least 165 tests that counted as coverage but didn't catch the breakage.
145
+ - Along the way, 70 bugs (2 Urgent, 6 High) and 58 standard candidates were found.
146
+
147
+ How to migrate — the reference [`skills/spec/migration.md`](skills/spec/migration.md): an outcome for every requirement, check by breaking, removal categories, "the spec fell behind the code".
148
+
149
+ No exaggeration: the spec format isn't the main token cost; what costs more is how many subagents are launched and what they are told to read. Task hours and cost before and after are to be compared by `est` actuals, once a sample accumulates.
150
+
151
+ **When OpenSpec fits better:** you need a requirements document for people outside the code; the project has no tests and won't have them; you need a "proposal before code" gate as a separate artifact. Elves moves this gate into the issue — scenarios are visible before code — but not into a file.
152
+
153
+ ## Installation
154
+
155
+ You need `git` and [Bun](https://bun.sh) ≥ 1.2 — the installer (`bunx`) and the skill scripts run under it; a project's CI on Node adds the `oven-sh/setup-bun` step; the `github`, `est` and `dashboard` skills need [`gh`](https://cli.github.com) with the `project` scope (`gh auth refresh -s project`); `task-env` needs Docker with Compose (Docker Desktop on macOS).
156
+
157
+ ```bash
158
+ # onto the machine — the canon, skills and the hook for every agent it has; or a global package:
159
+ # bun add -g @miroshnik/elves && elves install -g
160
+ bunx @miroshnik/elves install -g
161
+
162
+ # into a project — from the root of a git repository; the package version, the manifest, the shim and the AGENTS.md block are committed with the project
163
+ bunx @miroshnik/elves install
164
+
165
+ # the fallback — straight from GitHub, any command: bunx github:miroshnik/elves <command>
166
+ ```
167
+
168
+ Keeping the flow up to date:
169
+
170
+ ```bash
171
+ bunx @miroshnik/elves check -g # 0 — up to date, 1 — behind (in a project also: git ignores flow files, the compose file breaks task isolation, standards or architecture without a test:spec script), 2 — can't check; changes nothing
172
+ bunx @miroshnik/elves update -g # bring it to the latest release
173
+ ```
174
+
175
+ Without `-g` — the same for the project. Claude Code checks the machine and the project by itself: `install -g` sets up the `SessionStart` hook, and the `check` output lands at the start of every session; a session resumed after the app restarted also gets the wait of its task to restart — background waits die with the app's process. The same hook sets up the agent's Bash: in zsh a glob without matches and `====` go to the command as is, as in bash, and `timeout` works on macOS without coreutils (`bin/shims`); `check -g` run from the agent's Bash names it if the session didn't get that. With Docker on the machine `check -g` also names the `task-env` proxy of task addresses — running or not, with the command to start it; it doesn't change the exit code.
176
+
177
+ An agent's sandbox (Claude Code's Bash) doesn't let bunx write its cache `~/.bun/install/cache` — prefix the command with `BUN_INSTALL_CACHE_DIR="$TMPDIR/bun"` — nor the install write `.claude/skills`: then run the command in your own terminal — the refusal names it, and a repeat run finishes a cut-off install.
178
+
179
+ `install` in a project asks in the terminal whether the agent should do tasks entirely by itself — answer the task's questions by recommendation, commit, push, fix CI and merge its own PR on green checks (default — no); without a terminal — `install --auto` or `--no-auto`, without the flag the previous answer stays. `check` names the mode. With auto, `install` prints a hint for Claude Code: the app rejects a merge without human review in this mode too, and the session merges its own PR by itself only with the `Bash(gh pr merge *)` rule in `permissions.allow` of `~/.claude/settings.json` — the owner adds it, `install` doesn't change the machine's permission settings.
180
+
181
+ `install` also asks for the **project language** the agent works in: dialog, tasks, prompts, documents; the flow itself is written in English. It is stored in `.agents/elves.json` as `language` — a BCP 47 code (`ru`, `en`, `pt-BR`), `en` by default; an install of a previous version without the field counts as `ru`. Without a terminal — `install --lang <code>`, without the flag the previous language stays; `update` keeps it, `check` and the hook name it. The flow isn't installed into an Elves clone — `install` writes only these two settings there.
182
+
183
+ <details>
184
+ <summary><b>What goes where</b></summary>
185
+
186
+ **Into a project** — thin: no copy of the flow, its agents get the canon and the skills from the machine. Committed with the project:
187
+
188
+ - `@miroshnik/elves` in `devDependencies` of `package.json` — the exact version of the release, the project's package manager updates its lockfile: the harness and the skills' scripts at the project's version for its tests and CI, colleagues and the cloud session ("The package in a project" below); a project without `package.json` has nothing to pin.
189
+ - `.agents/elves.json` — `auto` — whether the agent runs a task entirely by itself, without a "yes" for commit, push and merge, `language` — the project language, and `env` — the task stack settings of `task-env`.
190
+ - `.agents/slot.sh` — the machine queue's shim the `package.json` checks source (`slot` below).
191
+ - A block at the top of the project's `AGENTS.md` linking to the canon on the machine, `~/.agents/elves/AGENTS.md` — for the agents that read the project's file.
192
+ - The cloud session's `SessionStart` hook in `.claude/settings.json` (your own settings and hooks stay): in a Claude Code cloud session (`CLAUDE_CODE_REMOTE=true`) it installs a fresh Bun and the flow onto the session's machine and runs `check --hook`, outside the cloud it exits at once — no setup script in the environment is needed (`docs/cloud-sessions.md`).
193
+ - Updating — `update` bumps the package version: `package.json` and the lockfile, a few lines in the branch of the current task, committed with it. A project with a copy of the flow of an earlier release moves to a thin one in a task of its own, one per project — `check` prints its `task new`, the hook names the canon loaded twice until then: `update` removes the copy, its symlinks and `.gitignore` exceptions, leads imports and CI paths into the copy to the package and lists what else names `.agents`.
194
+ - The manifest, the shim, `AGENTS.md` and `.claude/settings.json`, if the project's git ignores them (`.agents/` or `.claude/` in `.gitignore`), are un-ignored by `install` and `update` with exceptions — an Elves block at the end of `.gitignore`, committed with them; what an exception can't un-ignore (a directory excluded by a personal rule), and anything ignored until `update` runs — `❌` in the output of the install and of `check`.
195
+
196
+ **Onto the machine** (`-g`) — the canon, the skills and the hook: a thin project keeps no copy of the canon, its agents get it from the machine. `~/.agents/elves/` — `AGENTS.md`, `claude/CLAUDE.md`, `docs/*.md`; Claude Code — symlinks `~/.claude/rules/elves.md` and `elves-claude.md` (loaded in every project without the external-imports dialog), `~/.claude/skills` and the `SessionStart` hook in `~/.claude/settings.json`; the other agents — their global rule file linking to the canon (`~/.codex/AGENTS.md`, `~/.gemini/GEMINI.md`, `~/.copilot/copilot-instructions.md`, `~/.config/opencode/AGENTS.md`, `~/.config/amp/AGENTS.md`; a file of your own stays) and `~/.agents/skills`. A project that still has a copy loads the canon twice until it moves — the hook names it. A Claude Code cloud session gets the same from the project's `SessionStart` hook — `npm i -g bun@latest @miroshnik/elves && elves install -g` (the image's Bun lags behind), then `check --hook`; the machine's hook isn't there (the cloud doesn't read `~/.claude/settings.json`), an environment's setup script with the same command is an optional faster start. In an Elves clone with `--link` the machine's canon is the clone; its `.claude/CLAUDE.md` keeps the root `AGENTS.md` from loading a second time.
197
+
198
+ **Personal configuration** — everything that depends on specific repositories (the registry for `est`, your own model prices, notes) — lives outside the flow, in `~/.config/elves`; another directory — the `ELVES_CONFIG_DIR` variable, read by all skill scripts. `ELVES_PRIVATE=<path>` with `install -g` makes `~/.config/elves` a symlink to a private checkout; with `ELVES_CONFIG_DIR` the symlink isn't needed — the variable points at the checkout itself.
199
+
200
+ **Before the rename to Elves** an install had the names `ai-dev`: `.agents/ai-dev.json`, `.agents/ai-dev/`, `.claude/rules/ai-dev*.md`, `~/.config/ai-dev`, variables `AI_DEV_*`. `update` and `update -g` move them to the new names with the same contents, the skills' scripts read the former ones until then, and `check` names what is left with how to move it.
201
+
202
+ </details>
203
+
204
+ <details>
205
+ <summary><b>Releases and the up-to-date check</b></summary>
206
+
207
+ The machine's copy and a project's package version follow the flow **by releases** — tags `vYYYY.MM.DD` (a same-day patch is `.N`), not the head of `main`: a commit to the rules without a release doesn't make anyone "behind". The commands go through the npm package, and it is a release itself: `install` from it installs its own version without the network, `check` and `update` restart from the latest release's npm package. The fallback `bunx github:miroshnik/elves` installs the package from `main` once and then runs it from its cache, so that package is only a launcher: it finds the latest tag and restarts from it by its SHA; in a cloud session `github:` gives 403.
208
+
209
+ - `check` changes nothing: it compares the machine's copy and a project's package version with the latest release (the same install as a dry run, a list of differences), a `--link` clone — with `origin/main` after `git fetch`.
210
+ - `update`: the machine's copy — reinstalled from the latest release; a project — the package version raised to it, a copy of the flow moved to a thin project; a clone — `git pull --ff-only` and links to new skills (a clone not on `main` or with edits — refused with a reason). It says what changed, and the agent tells the user: the machine's copy and a project — every release after the installed one (a project — after its package version), newest first, with the list of its GitHub Release; a clone — the flow commits it pulled.
211
+ - `release` — the owner's step: a date tag (UTC) on `origin/main` and a GitHub Release listing the flow and installer changes since the previous release (the same list `update` prints); nothing to release — refused.
212
+ - The npm package `@miroshnik/elves` — CI publishes every release tag (`.github/workflows/npm.yml`): `elves pack <tag>` sets the version (`vYYYY.MM.DD.N` → `YYYY.MMDD.N`, versions sort as tags) and builds for Node the modules a project imports, then `npm publish` by npm [trusted publishing](https://docs.npmjs.com/trusted-publishers) — OIDC, no token or secret, provenance included. The owner's step, once: a trusted publisher is set in the package settings on npmjs.com, so first a `0.0.0` stub published from the owner's machine; then the trusted publisher — GitHub Actions, `miroshnik/elves`, workflow `npm.yml` — no earlier than 2 days before the first release from CI (a new one expires without a successful publish within 2 days: delete it and create it again); after that release the stub may be `npm deprecate`d.
213
+
214
+ </details>
215
+
216
+ <details>
217
+ <summary><b>The package in a project — the harness and the scripts at the project's version</b></summary>
218
+
219
+ With `@miroshnik/elves` in a project's `devDependencies`, `bun install` brings the flow at the project's version — for CI, colleagues and a cloud session too (npm works there, `github:` gives 403). Tests import `@miroshnik/elves/spec/harness`, `@miroshnik/elves/spec/architecture`, `@miroshnik/elves/spec/eslint-config` and `@miroshnik/elves/github`: Bun and the type checker take the TypeScript source, any runner on Node (Jest, Playwright, `node:test`, the project's `eslint.config.js`) — the build. The skills' scripts run from `node_modules/.bin` with the same arguments as the scripts themselves, under `bun --no-env-file` — in `package.json` and CI alike: `bun` reads the TypeScript bin `elves` itself, past its shebang, and without the flag it would get the project's env files (`.env.local` with production keys) and pass their keys on to `gh`, `git` and the checked command. On Yarn 2+ — `bunx @miroshnik/elves@"$(jq -r '.devDependencies["@miroshnik/elves"]' package.json)" <script>`, the form `install` writes there: Yarn 2+ runs a `.ts` bin by Node with any `nodeLinker`, and PnP has no `node_modules`.
220
+
221
+ ```bash
222
+ bun --no-env-file node_modules/.bin/elves spec-doc …
223
+ bun --no-env-file node_modules/.bin/elves spec-diff …
224
+ bun --no-env-file node_modules/.bin/elves spec-publish …
225
+ bun --no-env-file node_modules/.bin/elves spec-run …
226
+ bun --no-env-file node_modules/.bin/elves spec-shard …
227
+ bun --no-env-file node_modules/.bin/elves spec-claims …
228
+ bun --no-env-file node_modules/.bin/elves spec-break …
229
+ bun --no-env-file node_modules/.bin/elves spec-exceptions …
230
+ ```
231
+
232
+ The machine queue — `elves slot --for <PID>` of the shim `.agents/slot.sh`: it only prints the queue variables, the command runs in the script's own shell. A GitHub Actions step doesn't see `node_modules/.bin`, and `bunx elves` without the package in `node_modules` would fetch someone else's `elves` from npm; a job that doesn't install the project's dependencies — `bunx @miroshnik/elves@<version> <script>` (the `spec` skill's `ci.md`).
233
+
234
+ </details>
235
+
236
+ <details>
237
+ <summary><b>Working on the flow itself — from a clone</b></summary>
238
+
239
+ ```bash
240
+ git clone https://github.com/miroshnik/elves && cd elves
241
+ bun bin/elves.ts install -g --link # symlinks to the clone instead of copies: edits show up immediately
242
+ bun bin/elves.ts release --dry-run # what goes into the release; without --dry-run — cut it
243
+ ```
244
+
245
+ </details>
246
+
247
+ ## Skills
248
+
249
+ Skills are installed onto the machine with the flow (`install -g`); the agent picks them up by itself — from the description in `SKILL.md`, there's no need to invoke them by hand.
250
+
251
+ ### [`github`](skills/github/SKILL.md) — project and tasks by the canon
252
+
253
+ Checks and fixes the GitHub project and the default-branch rule, creates a task with all its fields in one command, shows a task with its epic, blockers, PR checks and open questions in one output and puts an answer at a question, before merging checks the PR's merge with a fresh `main` if it moved after CI, after merging does the epic refresh's mechanics and prints the subagent's prompt, and closes the task in one call — `est` actual, Done, epic and milestone, the task's Compose stacks and its merged branches removed — sets decision labels from the PR diff, names the wait a resumed session restarts for its task, and gives a dispatcher its queue — the ready tasks of an epic or a filter in order, each with the prompt of its executor, a background subagent.
254
+
255
+ `github project check` · `github project fix` · `github task new | show | answer | status | drop | actualize | close | resume | queue` · `github pr labels | premerge`
256
+
257
+ ### [`est`](skills/est/SKILL.md) — estimates from actuals
258
+
259
+ An estimate from analogs with a measured actual, set by `github task new` itself — the script picks the analogs, the agent has no step; the actual — active hours, tokens and the API equivalent in $ from Claude Code, Codex and cloud session transcripts; a backtest checks the mechanics against manual estimates; a period summary over all of a repo's sessions compares the flow before and after a change.
260
+
261
+ `est estimate` · `est fact` · `est history` · `est backtest` · `est period` · `est cloud-import`
262
+
263
+ ### [`dashboard`](skills/dashboard/SKILL.md) — sessions at work, estimate, actual and cost
264
+
265
+ A browser page the script serves itself, with two tabs. Sessions (the main one) — tasks In progress, their sessions and what each one waits for: CI, merge, deploy, a human's answer, working or silent; on top — questions to the owner from the sessions' answers and from tasks, at the bottom — questions the sessions answered themselves (✅ auto) within the last day. When to call it — "what's up with the sessions", "is anything stuck", "which tasks are hanging". Estimate and actual — estimate and actual in hours by task close date, actual to estimate, tokens, cost and the actual split by waits (CI, merge, checks), with a rolling median and the $ share per model: whether estimates converge and whether a task gets cheaper after a flow change.
266
+
267
+ `dashboard [--repo o/r | --all-repos] [--since 90d]`
268
+
269
+ ### [`spec`](skills/spec/SKILL.md) — specification from tests
270
+
271
+ - `spec-doc` — runner reports (Vitest/Jest, Playwright, `bun test`) over the `tests/` tree → a page per decision, C4 architecture from the model, sequence diagrams from scenario traces.
272
+ - `spec-diff` — the spec section for a PR (`## Spec (tests)`): added, changed and removed requirements, the model, exceptions, a check against the task's scenarios section.
273
+ - `spec-publish` — after merge, publishes the documentation to the `spec` branch: it isn't in `main`.
274
+ - `spec-run` — for publishing on PR merge, finds a green run that checked exactly the merge commit's tree; there is none (a PR that fell behind was merged) — publishing is skipped, the `spec` branch isn't rolled back.
275
+ - `spec-shard` — a CI shard's test files by the durations of the last green run, not by the count of tests: the heavy tests of one folder don't all land in one shard.
276
+ - `spec-claims` — every entry point (route, page, job, command) is called by a capability test — by the call log, not by coverage.
277
+ - `spec-break` — proves that a check catches a breakage: break the code → run the test → always revert.
278
+ - `spec-exceptions` — moves a project's exceptions from `exceptions.ts` into the `exceptions/` directory of the decision folder, one file per exception: parallel PRs paying down debt don't conflict.
279
+ - `harness.ts` — mechanical checks: "registry + invariant" with a violator, lint-rule examples, exceptions with a ratchet (an element with its own file — a mark in it), the C1–C3 architecture model, dead code (knip, with the config-hints reporter `knip-hints.cjs`), the environment.
280
+
281
+ ### [`ci-wait`](skills/ci-wait/SKILL.md) — wait for checks, don't guess
282
+
283
+ Waits for all checks of a PR or a commit (a merge commit — by the PR number) — CI, deploy, an external check — with visible progress and PASS / FAIL / TIMEOUT / ERROR outcomes instead of `gh pr checks --watch` and `sleep` in a loop; an Actions run that didn't become a check (the workflow wasn't parsed or didn't start on the PR) is not a PASS; it also waits for a bug about a red `main` to close.
284
+
285
+ `wait-ci pr | status | merged | issue`
286
+
287
+ ### [`slot`](skills/slot/SKILL.md) — the machine's heavy runs in a queue
288
+
289
+ Project checks — `lint`, `typecheck`, `test`, `test:*` — go through the machine queue: two runs at once, together half the cores, the rest wait in arrival order and print whom they are waiting for; the runner takes its workers from `ELVES_SLOT_CPUS`. The flow install puts the wrapper into the `package.json` scripts — both agents and a human in the terminal join the queue; in CI — no queue.
290
+
291
+ `slot '<command>' [args…]` · in `package.json`: `. .agents/slot.sh && <command>`
292
+
293
+ ### [`task-env`](skills/task-env/SKILL.md) — a task's environment: its Compose stack and addresses `*.localhost`
294
+
295
+ A task's stack is the Docker Compose project `<project>-<N>`: parallel sessions of one project keep their own apps and databases without touching each other. One proxy per machine — Traefik in Docker — gives containers of task stacks the addresses `http://<service>.<N>.<project>.localhost` by their labels: no host port per app, and different hosts keep their own cookies, so parallel sessions and the owner's browser don't log each other out. A container without labels isn't published, a stopped one leaves the routes; `check -g` and the session start name whether the proxy runs, with the command to start it. `task-env up` brings the task's stack up by the number in the branch — the project's compose file plus the proxy's labels for the HTTP services named in `.agents/elves.json` — and prints the addresses; `task-env e2e` brings the e2e stack `<project>-<N>-e2e` up anew with clean volumes and runs the command with its addresses in `TASK_ENV_*` variables; `task-env down` takes both stacks of the task down with their volumes — `github task close` calls it; `task-env prune` finds the stacks of closed tasks left on the machine by the tasks' state on GitHub and takes them down; a stack that didn't come up is named by the failed service, its state and the end of its log. The project's `check` holds the compose file to the isolation rules — no `container_name`, no fixed host port, volumes and networks without `name:` and `external`, code by relative paths — and names each violation with the rule and the service; it reads `docker compose config`, so every form of writing is covered.
296
+
297
+ `task-env up | e2e | down | prune` · `task-env proxy up | down | status` · `task-env check [<dir>]`
298
+
299
+ ### References
300
+
301
+ Mechanics the canon refers to; the agent reads them as needed.
302
+
303
+ - [`docs/pr-checks.md`](docs/pr-checks.md) — waiting for PR and commit checks: polling, `bucket`, empty-list traps; a deploy — by the commit status, not by polling the site; the app's Auto-fix — off: a conflict is a rebase, not a merge of the base.
304
+ - [`docs/ci-concurrency.md`](docs/ci-concurrency.md) — CI for parallel tasks: the full rule and workflow fragments — parallel branches, a non-cancellable `main`, a deploy queue.
305
+ - [`docs/testing.md`](docs/testing.md) — how to test: what runs before a commit and what is left to CI, a run's result by the runner's summary, the environment, e2e without flakes, UI state in the agent's browser — by the DOM, not a screenshot.
306
+ - [`docs/judgment.md`](docs/judgment.md) — judgment and edits: a mistake disguised as an outcome, a label is a claim, editing exactly what was named, "it doesn't reproduce for me".
307
+ - [`docs/parallel-checkouts.md`](docs/parallel-checkouts.md) — parallel worktrees: the full rule, the task's Compose stack and what the compose file holds, callbacks of external services, the runner config, a shared `.git/config` — no `git config` in copies.
308
+ - [`docs/cloud-sessions.md`](docs/cloud-sessions.md) — the Claude Code cloud session: what it can't access, its brief (`#` only for its task) and how to close a task from there.
309
+
310
+ ## Repository layout
311
+
312
+ ```
313
+ AGENTS.md the canon of rules — for all agents
314
+ claude/CLAUDE.md only what Claude Code has
315
+ docs/ references the canon links to
316
+ skills/<name>/ SKILL.md + scripts/ — TypeScript under Bun, no dependencies
317
+ tests/ Elves' own spec: capabilities/ and standards/
318
+ bin/elves.ts the installer — TypeScript under Bun too: bunx runs it from node_modules by its shebang
319
+ bin/shims/ commands the SessionStart hook adds to the end of the agent's Bash PATH: timeout
320
+ ```
321
+
322
+ **A skill = a prompt + scripts** following the open [Agent Skills](https://agentskills.io) standard: a `<name>/` folder with `SKILL.md` (frontmatter `name` = the folder name, `description` ≤ 1,024 characters; at most 2,500 words — when and what to call) and `scripts/` for the deterministic part; reference material — `reference.md` and the scripts' `--help`. The canon is budgeted too: `AGENTS.md` ≤ 3,000 words, `claude/CLAUDE.md` ≤ 700 (standard `canon-size`). Scripts are TypeScript under Bun (`bun script.ts`: no build, no dependencies, imports with `.ts`); script tests — `bun test` in the `tests/` of Elves over the same tree. The installer `bin/elves.ts` is the same TypeScript under Bun and imports skill modules instead of copying their values. A skill imports another skill only by the dependency list of the `skill-imports` standard: `github`, `task-env`, `ci-wait` and `slot` import none, so a rule they share with a neighbor lives as a copy in each script, and a standard keeps the copies identical. Third-party code a script can't do without goes into `scripts/vendor/` as a file with its license, not as a dependency: that's how `spec` carries a TypeScript parser (`@babel/parser`). Everything that depends on specific repositories goes into local configuration, not into the skill.
323
+
324
+ Elves lives by its own flow: the spec — [`tests/`](tests), its documentation — in the [`spec` branch](https://github.com/miroshnik/elves/tree/spec), tasks — in [Issues](https://github.com/miroshnik/elves/issues) and the [elves project](https://github.com/users/miroshnik/projects/6).
325
+
326
+ The spec is read from the branch, not from the working tree — the same in any project with the flow:
327
+
328
+ ```bash
329
+ git fetch origin spec
330
+ git show origin/spec:README.md # table of contents: capabilities, architecture, standards
331
+ git show origin/spec:capabilities/est.md # a decision page: why, what holds, what checks it
332
+ ```
333
+
334
+ ```bash
335
+ bun install
336
+ bun run test:spec # Elves standards — tests/standards, once: test doesn't repeat them
337
+ bun run test # fast level: script tests without standards, Docker and live servers
338
+ bun run test --changed=origin/main # before a commit — what the change affects; a changed script — its tests by path; in full — CI
339
+ bun run test:slow # slow level — *.slow.test.ts: Docker, live servers; CI runs both levels
340
+ bun run typecheck # bun check — Bun's own type checker, Bun 1.4.3 from packageManager
341
+ bun run spec:doc # docs/spec from test names, --strict
342
+ ```
343
+
344
+ The README is part of the flow too: a new skill, command, script subcommand or reference without a line here fails `bun test`, and so does a link to a renamed canon section ([`tests/standards/readme`](tests/standards/readme/readme.md)).
package/bin/ai-dev.ts ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * The installer under its name before the rename (#544) — `bin/elves.ts` itself: `update -g` of a release before the
3
+ * rename pulls a `--link` clone and runs `<clone>/bin/ai-dev.ts install -g --link` with the clone's code, and this file
4
+ * keeps that run working — the links, the SessionStart hook and the clone's origin come from the updated clone. Not a
5
+ * command of the package (`bin` in package.json has only `elves`).
6
+ */
7
+ import "./elves.ts";