@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
@@ -0,0 +1,53 @@
1
+ import {
2
+ marksIn2,
3
+ exceptionsIn2,
4
+ exceptionFiles2,
5
+ invariant2,
6
+ examples2,
7
+ eslintLinter2,
8
+ source2,
9
+ sources2,
10
+ codeFiles2,
11
+ codeOnly2,
12
+ disablesIn2,
13
+ lintExceptions2,
14
+ testFileIn2,
15
+ journal2,
16
+ resetJournal2,
17
+ trace2,
18
+ sequence2,
19
+ deadCode2,
20
+ ENV_SERVICE2,
21
+ configGet2,
22
+ envNamesIn2,
23
+ envNamesInCode2,
24
+ dotenvNames2,
25
+ envVars2
26
+ } from "./harness-v9s1v9w3.js";
27
+ import"./harness-qzb932c8.js";
28
+ export {
29
+ ENV_SERVICE2 as ENV_SERVICE,
30
+ codeFiles2 as codeFiles,
31
+ codeOnly2 as codeOnly,
32
+ configGet2 as configGet,
33
+ deadCode2 as deadCode,
34
+ disablesIn2 as disablesIn,
35
+ dotenvNames2 as dotenvNames,
36
+ envNamesIn2 as envNamesIn,
37
+ envNamesInCode2 as envNamesInCode,
38
+ envVars2 as envVars,
39
+ eslintLinter2 as eslintLinter,
40
+ examples2 as examples,
41
+ exceptionFiles2 as exceptionFiles,
42
+ exceptionsIn2 as exceptionsIn,
43
+ invariant2 as invariant,
44
+ journal2 as journal,
45
+ lintExceptions2 as lintExceptions,
46
+ marksIn2 as marksIn,
47
+ resetJournal2 as resetJournal,
48
+ sequence2 as sequence,
49
+ source2 as source,
50
+ sources2 as sources,
51
+ testFileIn2 as testFileIn,
52
+ trace2 as trace
53
+ };
@@ -0,0 +1,34 @@
1
+ /**
2
+ * knip-hints — a knip reporter for the harness's `deadCode`: configuration hints (a redundant `ignore`, an `entry`
3
+ * without matches, a redundant workspace) as a JSON line
4
+ * `{ "configurationHints": [{ type, identifier, workspace, file? }] }`. knip's `json` reporter doesn't print them,
5
+ * and `--treat-config-hints-as-errors` gives only an exit code — the hints themselves aren't visible.
6
+ *
7
+ * knip --reporter json --reporter .agents/skills/spec/scripts/knip-hints.cjs
8
+ *
9
+ * The second output line is its own: `deadCode` joins both. CommonJS, not TypeScript: the harness takes the file
10
+ * path (`report.file`) with a static import — `import.meta` in a module that Playwright and Jest load as CommonJS is
11
+ * a syntax error (tests/standards/harness-load); knip loads it under both Node and Bun. Types — `knip-hints.d.cts`.
12
+ */
13
+ "use strict";
14
+
15
+ const path = require("node:path");
16
+
17
+ /**
18
+ * Hints as knip collected them (`ReporterOptions.configurationHints`): an identifier may be a regex (an `ignore`
19
+ * pattern), a file path is absolute; `--no-config-hints` — the project itself opted out of hints.
20
+ */
21
+ function report(options) {
22
+ const hints = options.isDisableConfigHints
23
+ ? []
24
+ : options.configurationHints.map((h) => ({
25
+ type: h.type,
26
+ identifier: h.identifier instanceof RegExp ? h.identifier.source.replaceAll("\\/", "/") : String(h.identifier),
27
+ workspace: h.workspaceName ?? ".",
28
+ ...(h.filePath ? { file: path.relative(options.cwd, path.resolve(options.cwd, h.filePath)).split(path.sep).join("/") } : {}),
29
+ }));
30
+ process.stdout.write(JSON.stringify({ configurationHints: hints }) + "\n");
31
+ }
32
+
33
+ report.file = __filename;
34
+ module.exports = report;
@@ -0,0 +1,82 @@
1
+ # CI: parallel tasks — the rule and workflow fragments
2
+
3
+ A reference for the "CI: parallel tasks" section of `AGENTS.md`: here is the
4
+ full rule and ready-made fragments to copy.
5
+
6
+ ## Rule
7
+
8
+ Every branch and every PR runs independently; only the branch's own stale run
9
+ is cancelled. Serialization — only where there is a shared resource (a deploy
10
+ environment, a shared database, an external service with limits). One queue
11
+ for all of CI is not a solution: it slows down every task, and without
12
+ `queue: max` it also cancels pending runs. Workflow fragments — below.
13
+
14
+ - **Branches and PRs run in parallel:** a `concurrency` group by PR number or
15
+ branch, `cancel-in-progress: true`.
16
+ - **`main`, tags and manual runs are never cancelled:** their runs deploy, and
17
+ an interrupted deploy is worse than an extra run. Instead of cancelling —
18
+ `queue: max` (by default GitHub keeps one pending run and evicts it with the
19
+ next one: a third merge in a row would cancel the second).
20
+ - **Deploy — a queue per environment:** the deploy job has the group
21
+ `deploy-<env>`, shared by all workflows of the repository, and `queue: max`.
22
+ Without it the job queue evicts pending jobs, just as at the workflow level:
23
+ at run time the environment's third deploy job cancels the waiting second
24
+ one, and a manual run cancels a waiting tag deploy.
25
+ - **CI and deploy — in different workflows:** `queue: max` is incompatible
26
+ with `cancel-in-progress: true`. In one workflow —
27
+ `cancel-in-progress: ${{ github.event_name == 'pull_request' }}` without
28
+ `queue: max`: the deploy won't be interrupted, but a pending run may be
29
+ evicted.
30
+ - **The agent doesn't cancel or re-run others' runs** (`gh run cancel` and
31
+ `gh run rerun` — only its own run on its own branch). Waiting for its own
32
+ checks and merging — the "Git, PRs and merging" section of `AGENTS.md`.
33
+
34
+ ## Workflow fragments
35
+
36
+ Checked against the GitHub Actions documentation ("Control the concurrency of
37
+ workflows and jobs"): `queue: max` allows up to 100 pending runs (beyond that
38
+ they are cancelled) and is incompatible with `cancel-in-progress: true`;
39
+ `false` is the default; `queue` also works on `jobs.<id>.concurrency`, which
40
+ is supported on a job with `uses:` (reusable workflow) too;
41
+ `cancel-in-progress` accepts expressions; a deploy job's group is shared
42
+ across the repository, so the same name in different workflows gives one
43
+ queue.
44
+
45
+ ```yaml
46
+ # ci.yml — branches and PRs in parallel; a branch's stale run is cancelled
47
+ on:
48
+ pull_request:
49
+ concurrency:
50
+ group: ci-${{ github.event.pull_request.number || github.ref }}
51
+ cancel-in-progress: true
52
+ ```
53
+
54
+ ```yaml
55
+ # cd.yml — main, tags, manual runs: nothing is cancelled, runs wait in a queue
56
+ on:
57
+ push:
58
+ branches: [main]
59
+ tags: ['v*']
60
+ workflow_dispatch:
61
+ concurrency:
62
+ group: cd-${{ github.ref }}
63
+ queue: max
64
+ jobs:
65
+ deploy-dev:
66
+ concurrency:
67
+ group: deploy-dev # environment = queue, shared by all workflows
68
+ queue: max # the next deploy doesn't evict a pending one
69
+ ```
70
+
71
+ If CI and deploy are in one workflow (they can't be split right now):
72
+
73
+ ```yaml
74
+ concurrency:
75
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
76
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
77
+ ```
78
+
79
+ A running deploy on `main` won't be interrupted this way, but of three merges
80
+ in a row the third run evicts the pending second one — the third deploys
81
+ everything together, and the second commit is left without a CI status on
82
+ `main`.
@@ -0,0 +1,203 @@
1
+ # Claude Code cloud session
2
+
3
+ Reference for the cloud-session line in `AGENTS.md`. A cloud session is
4
+ Claude Code on claude.ai/code, including one started from the phone app: a
5
+ temporary container into which only the project repository is cloned.
6
+ A dispatcher's executor can be one (`AGENTS.md`, "Session and branch — one
7
+ task"): at most 3 subagents and 5 cloud sessions at once, the owner's number
8
+ replaces either — a cloud session's container doesn't load the machine.
9
+
10
+ ## What it sees
11
+
12
+ Only the project repository. The canon, the skills and the agent's memory of
13
+ the user's machine (`~/.agents`, `~/.claude`) aren't there: the flow comes
14
+ from the project's **`SessionStart` hook** — `.claude/settings.json`, which
15
+ `install` and `update` put into a thin project (#610). The cloud runs a
16
+ project's hooks — not `~/.claude/settings.json` — after Claude Code starts,
17
+ on every start and resume. With `CLAUDE_CODE_REMOTE=true` the hook does what
18
+ an environment's setup script did (#555), without the owner's step: `npm i -g
19
+ bun@latest @miroshnik/elves && elves install -g`, then `check --hook` into the
20
+ context; outside the cloud it exits at once. The canon already on the machine
21
+ — nothing to install; a failed step — a line with its command, code 0. An
22
+ Elves clone carries the hook in its commit: Bun, zsh for its tests
23
+ (`tests/capabilities/agent-shell`) and the flow from the clone itself —
24
+ `install -g --link`, the branch's canon, no npm. A fresh start of the clone's
25
+ hook with zsh in place took 4.4 s (2026-10-17: npm about 3 s, `check --hook`
26
+ about 1 s); without zsh `apt-get update` and its install add about 6 s.
27
+
28
+ - `bun@latest` — the image's Bun lags behind a project's `packageManager`
29
+ (1.4.2 on 2026-10-16, without the `bun check` of 1.4.3): npm's global
30
+ directory comes before `~/.bun/bin` in the image's `PATH`, while `bun
31
+ upgrade` in the session gets 403 from GitHub.
32
+ - **No apt lists in the image** (2026-10-17): `apt-get install zsh` alone —
33
+ "Unable to locate package"; `apt-get update` first. `--force-confold` — the
34
+ image has its own `/etc/zsh/zshrc`: without it dpkg asks whether to replace
35
+ it and fails without a terminal (#598).
36
+ - **npm has no release yet** (2026-10-17): `@miroshnik/elves` there is the
37
+ `0.0.0` stub without the `elves` command, so a thin project's hook prints
38
+ "the flow isn't installed (exit 127)" until CI publishes a release (README,
39
+ "Installation" — the owner's step). The clone's hook doesn't need npm.
40
+ - **The setup script** is optional, a faster start: the environment caches
41
+ its result for about 7 days, and the hook then installs nothing. Network
42
+ access — Trusted (the default), about 7 s:
43
+
44
+ ```bash
45
+ npm i -g bun@latest @miroshnik/elves && elves install -g
46
+ # an Elves clone's environment: zsh for its tests (tests/capabilities/agent-shell), CI installs it too
47
+ apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y -o Dpkg::Options::=--force-confold zsh
48
+ ```
49
+
50
+ - **The project's version** — `@miroshnik/elves` in its `devDependencies`,
51
+ only from npm: bun installs it through the session's proxy, `github:` in
52
+ the session is 403. It works in the project language, just like a local
53
+ session: `language` in `.agents/elves.json`, which comes with the clone.
54
+ - **A thin project behind** — updated in the session's task, as locally:
55
+ `bunx @miroshnik/elves update` raises the package version, a few lines into
56
+ the task's branch; a project without the hook yet gets it the same way, and
57
+ until then — `check` by hand.
58
+ - **No machine hook:** the cloud doesn't read `~/.claude/settings.json`; the
59
+ project's hook runs `check --hook` instead, without `github task resume`
60
+ (Projects are closed there). An environment without Bun or zsh all the same
61
+ — `check` names it with a `cloud environment:` line and the command, which
62
+ the session runs itself.
63
+ - **A project with a copy of the flow** loads the canon twice in the cloud —
64
+ the copy's `.claude/rules` and the setup script's — until its move to the
65
+ thin project; a copy gets no project hook, nothing names it.
66
+
67
+ **What is visible at the start** — checked from inside a cloud session
68
+ (2026-10-17, Claude Code 2.1.302, #610), which itself started without the
69
+ hook:
70
+
71
+ - Skills `install -g --link` put into `~/.claude/skills` mid-session —
72
+ Claude Code picked them up at once (the skills list in the context
73
+ changed).
74
+ - Rules `~/.claude/rules/elves*.md` written mid-session didn't reach the
75
+ context of that turn. By the diagnostics log (`$CLAUDE_CODE_DIAGNOSTICS_FILE`)
76
+ Claude Code reads memory files at the start of a user turn
77
+ (`cli_turn_started`, then `memory_files_completed` with `file_count`), so
78
+ the rules the hook writes before the first turn should load.
79
+ - The clone hook's command, run as Claude Code runs it (`sh -c`, a fresh
80
+ `HOME`): code 0, the output is `check --hook`'s; outside the cloud — no
81
+ output.
82
+
83
+ Still to check — in the first cloud session of the clone after the merge:
84
+ the hook's output in the context; `memory_files_completed` at the first
85
+ `cli_turn_started` has `file_count` 3 (the clone's `.claude/CLAUDE.md` and
86
+ both rules); ask the agent to quote a rule from `AGENTS.md` and list the
87
+ skills — `est`, `spec`, `ci-wait` must be there. The rules didn't load — the
88
+ hook prints the canon (#610, question 2).
89
+
90
+ ## What the cloud lacks (checked 2026-09-25, 2026-10-17)
91
+
92
+ - **GitHub GraphQL** is closed by the session proxy ("use the REST API").
93
+ REST — only `repos/{owner}/{repo}/…` paths of the session's repositories;
94
+ **Projects v2** (`users/…/projectsV2…`, `orgs/…`) — 403. Project fields
95
+ (`Status`, `Estimate, h`, `Actual, h`, tokens, cost) can't be set from the
96
+ cloud; `est` computes neither the estimate nor the actual.
97
+ - **Writing to the repository** (push, editing issues, comments, PRs) — only
98
+ if the Claude GitHub App has access to the repository. No access — 403
99
+ ("Resource not accessible by integration", "Claude doesn't have GitHub
100
+ access"): the user installs or reconnects the app
101
+ (claude.ai → Settings → Connectors → GitHub; github.com/apps/claude).
102
+ - **The cloud assigns the branch** (`claude/<slug>`) and forbids pushing to
103
+ others without explicit permission. A branch by the `<type>/<N>-<slug>`
104
+ convention — create it after asking the user at the start, together with
105
+ the task's questions.
106
+ - **The session title** (`set_session_title`) is stored on the server and
107
+ doesn't get into the transcript: `est` links cloud work to the task only by
108
+ the branch, the only `#N` in the first prompt (section "The brief") and
109
+ commit hashes.
110
+ - **The container is temporary**: anything unpushed is lost.
111
+ - **Root:** the session runs as uid 0 — `chmod` doesn't deny writes. The
112
+ flow's write-denial tests (`install`, `slot`) run the checked process as
113
+ `nobody` (`setpriv`); a test relying on permissions without that is green
114
+ or red for the wrong reason.
115
+ - **GitHub Actions job logs** over REST (`actions/jobs/<id>/logs`) — 403: the
116
+ answer redirects to blob storage the proxy doesn't let through;
117
+ `get_job_logs` of the GitHub MCP works.
118
+ - **GitHub search** (`search/issues`) — 403 ("sessions are bound to their
119
+ configured repositories"): the duplicate check before a new task — by
120
+ listing `repos/{owner}/{repo}/issues`.
121
+ - **The `github` skill's scripts** — `task show`, `task answer`, `task new`
122
+ over the repository's REST (#603): without the project — `task new` names
123
+ Status and the estimate in a line for a local session; the rest refuse
124
+ (`CLAUDE_CODE_REMOTE`). The scenarios section — by hand over REST (`PATCH
125
+ repos/…/issues/N`). The proxy ends a description it writes with its
126
+ "Generated by Claude Code" signature after `---` (doesn't add a second one):
127
+ the questions section ends at `---`, the signature isn't its last question's.
128
+
129
+ ## The brief — `#` only for its task
130
+
131
+ The cloud names the branch itself (`claude/<slug>`), so `est` links a session
132
+ to its task by the first prompt: it takes every `#N` of the whole prompt —
133
+ with the "Background (from the session that started this one)" block the
134
+ harness puts before the task — and links only by exactly one (#594). Whoever
135
+ writes a cloud session's first prompt — the owner or a dispatcher session —
136
+ names only its task with `#`, in the background too; the epic, other tasks and
137
+ PRs — a number without `#` ("epic 40", "tasks 41 and 43") or a title. The
138
+ executor prompt of `github task queue` has `(epic #M)`: fine for a subagent,
139
+ whose assignment `est` reads by the word "Task"; a cloud executor's brief is
140
+ `github task queue --assignment <N> --cloud` — whole (`task queue` refuses
141
+ in the cloud), the epic and numbers in the title without `#`. Two `#N` —
142
+ `est fact <N>` in the cloud prints `Actual unavailable (cloud)` though the
143
+ transcript is in the container.
144
+
145
+ ## Closing a task from the cloud
146
+
147
+ `github task close` refuses in the cloud (GitHub Projects are unavailable)
148
+ and prints the same steps:
149
+
150
+ 1. `est fact <N>` computes this session's part of the actual from its
151
+ transcript in the container (linking — the branch `<type>/<N>-…` or the
152
+ only `#N` in the first prompt, section "The brief") and prints an
153
+ `Actual (cloud)` comment
154
+ with hours, tokens, a link to the session and a part marker — post it to
155
+ the issue with a GitHub tool. Project fields (`Actual, h`, tokens, cost)
156
+ are set by the next local session: `est fact <N> --write` or
157
+ `est fact --sweep --write` adds the cloud part to the local work. The
158
+ transcript didn't link — `Actual unavailable (cloud)`, and a local session imports the session's events via the browser
159
+ (the `est` skill, "Cloud session").
160
+ 2. `Status` = Done is set by the project's built-in workflow "Item closed"
161
+ (whether it is enabled — a local session checks with the `github` skill);
162
+ not enabled — ask the user to set the status.
163
+ 3. Epic refresh — by a subagent with a fresh context, as locally (issues,
164
+ sub-issues, milestones are available through the repository's REST); what
165
+ can't be done from the cloud (project fields, estimates of new tasks via
166
+ `est`) — into a State comment for the local session.
167
+
168
+ **A local dispatcher learns of it by the task's closing** (#609): a cloud
169
+ session can't message the local one that started it. `send_message` to a
170
+ local session is refused — `elevated_session_untrusted_device`, then "target
171
+ session could not be verified" on every retry (checked 2026-10-17);
172
+ `create_trigger` (`persistent_session_id`) and `get_session` don't take a
173
+ local session's id (`local_…`); `ListAgents` in the cloud sees no sessions of
174
+ the owner's machine; the owner's approval on a trusted device
175
+ (`confirm_repository_trust` with the `content_digest` of such a refusal —
176
+ these carried none) is, by the tool's description, for one cloud session,
177
+ i.e. per executor. So the brief tells the executor not to message the
178
+ dispatcher and not to archive its session, and the dispatcher waits for the
179
+ merge's `Closes #N`: `ci-wait issue <N>` as its own background command; PASS
180
+ — `github task close <N>` locally (the actual with `--write`, `Status`, the
181
+ merged `claude/<slug>` branch), then archiving the executor's session where
182
+ it has the tool (till then its transcript may be needed for `est`); TIMEOUT —
183
+ the task's questions section and State comment, the executor's session, the
184
+ wait again. After the app restarts the queue's `=` line names the wait.
185
+
186
+ ## Push failed — a local session finishes
187
+
188
+ A cloud session that can't write hands the work over as files: the branch —
189
+ `git bundle` or `git format-patch --stdout` (a session attachment), answers
190
+ to questions — in the PR text or the final message. A local session, by the
191
+ task number:
192
+
193
+ 1. Answers from the cloud — into the task's questions section
194
+ (`github task answer`), before code.
195
+ 2. The branch — from a fresh `origin/main`:
196
+ `git fetch <file>.bundle <branch>:<branch>` or `git am <file>.mbox`; if the
197
+ cloud didn't start from it — rebase.
198
+ 3. Review the diff and the same checks as CI **on this machine**: the cloud
199
+ is Linux, and platform differences (symlinks via `/var` → `/private/var`
200
+ on macOS) aren't visible there.
201
+ 4. Then as usual: PR, green checks, merge, actual. The actual sees only the
202
+ local part of the work — add the line `Reason: missed work: …`
203
+ (reason: missed work) with a link to the cloud session.
@@ -0,0 +1,81 @@
1
+ # Judgment and edits: where a mistake looks like a result — for "Tests and code"
2
+
3
+ Reference for the "Tests and code" section of `AGENTS.md`: the full rules and
4
+ examples (made up). What the four have in common: such a mistake is caught
5
+ neither by green checks nor by plausible numbers — only by the question of
6
+ what the result, the label or the request is really about.
7
+
8
+ ## A mistake disguised as an outcome
9
+
10
+ The most dangerous defect of a plan is one whose symptom matches one of the
11
+ expected results: neither a check nor reading the numbers will catch it — the
12
+ outcome is plausible, and it will be read as the answer.
13
+
14
+ - **Before a measurement or a launch** (an experiment, a campaign, a load
15
+ run, a data migration) — write down the expected outcomes and for each one
16
+ ask: which of our mistakes would give exactly the same result?
17
+ - **There is one — a separate check before the launch,** not an analysis
18
+ after it: after the launch its outcome can no longer be told apart from the
19
+ answer.
20
+ - **An unchecked number is marked in the table itself** (in the cell, in the
21
+ row), not in a footnote: it may reverse the conclusion, and not everyone
22
+ reads footnotes.
23
+
24
+ Example. A bid of "0.60" in an ad account whose currency turns out to be not
25
+ the euro but one where 0.60 is next to nothing: there are no impressions, and
26
+ it will be read as "no demand". The check before the launch — the account's
27
+ currency and a test impression.
28
+
29
+ ## A label is a claim
30
+
31
+ A label, a unit and a condition ("per month", "excl. VAT", "per user") claim
32
+ what the number under them means. A request to "make the labels (units,
33
+ conditions) the same" — first check against the source of each number that
34
+ they are about the same thing, and only then change the text.
35
+
36
+ - About the same thing — I align them.
37
+ - About different things — I say so plainly and give options: recalculate to
38
+ one meaning, honestly different labels, leave as is.
39
+ - Green checks (a snapshot, a text lint, a "labels match" test) compare
40
+ labels with each other, not with what the number means: the same label over
41
+ different numbers passes them all.
42
+
43
+ Example. One table says "Revenue, ₽", another "Revenue, thousand ₽"; the
44
+ request is for identical labels. First — what units the numbers are really
45
+ in: otherwise one of the labels becomes false by a factor of a thousand.
46
+
47
+ ## Editing exactly what was named
48
+
49
+ Asked for order, alignment or formatting — I change only that: not the
50
+ content, not the composition, not the neighboring wording. The rule isn't
51
+ only for an autonomous routine ("What I do without asking and what needs a
52
+ yes" in `AGENTS.md`): any request names its own scope.
53
+
54
+ - **The named change requires deleting an entity** (a row, a field, a
55
+ section, a test) — the solution is wrong, not the entity redundant: I look
56
+ for another one.
57
+ - **The requirements really are incompatible** — I say so and let the person
58
+ choose, rather than resolving it by deletion.
59
+
60
+ Example. The request is to align pricing cards by height; one is taller than
61
+ the rest by one item. Removing the item is no longer alignment: height by the
62
+ longest one, wrapping into two columns or shortening the text — the human
63
+ chooses.
64
+
65
+ ## "It doesn't reproduce for me"
66
+
67
+ A discrepancy between what the human sees and what I see is a claim about my
68
+ tool, not about the product. I don't argue, but:
69
+
70
+ - name what I measured with and where: browser and version, window width,
71
+ data and account, environment (local, preview, production), cache;
72
+ - ask for the human's conditions and reproduce in them;
73
+ - still didn't reproduce — I say what was checked and ask for what's missing
74
+ (a screenshot, an address, a time), rather than closing it as "not a bug".
75
+
76
+ Example. The human sees buttons overlapping each other, for me they're even:
77
+ I looked at a width of 1440 px in a clean profile, the human — at 390 px with
78
+ a long name in the header. One product in the conversation, two tools.
79
+
80
+ "Didn't help" after a fix is a different rule: the "Production and debugging"
81
+ section of `AGENTS.md` (production facts, not a third reproduction).