@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.
- package/AGENTS.md +310 -0
- package/README.md +344 -0
- package/bin/ai-dev.ts +7 -0
- package/bin/elves.ts +2847 -0
- package/bin/shims/timeout +113 -0
- package/claude/CLAUDE.md +71 -0
- package/dist/github/scripts/github.js +3428 -0
- package/dist/spec/scripts/architecture.js +628 -0
- package/dist/spec/scripts/eslint-config.js +54 -0
- package/dist/spec/scripts/harness-qzb932c8.js +959 -0
- package/dist/spec/scripts/harness-v9s1v9w3.js +16045 -0
- package/dist/spec/scripts/harness.js +53 -0
- package/dist/spec/scripts/knip-hints.cjs +34 -0
- package/docs/ci-concurrency.md +82 -0
- package/docs/cloud-sessions.md +204 -0
- package/docs/judgment.md +81 -0
- package/docs/parallel-checkouts.md +220 -0
- package/docs/pr-checks.md +117 -0
- package/docs/testing.md +304 -0
- package/package.json +70 -1
- package/skills/ci-wait/SKILL.md +180 -0
- package/skills/ci-wait/scripts/wait-ci.sh +377 -0
- package/skills/dashboard/SKILL.md +212 -0
- package/skills/dashboard/scripts/dashboard.ts +912 -0
- package/skills/dashboard/scripts/sessions.ts +772 -0
- package/skills/est/SKILL.md +222 -0
- package/skills/est/reference.md +381 -0
- package/skills/est/scripts/est.ts +4867 -0
- package/skills/github/SKILL.md +206 -0
- package/skills/github/locales/en.json +449 -0
- package/skills/github/locales/ru.json +449 -0
- package/skills/github/reference.md +1073 -0
- package/skills/github/scripts/github.ts +4390 -0
- package/skills/github/scripts/locale.ts +495 -0
- package/skills/github/scripts/package.json +3 -0
- package/skills/slot/SKILL.md +145 -0
- package/skills/slot/scripts/package.json +3 -0
- package/skills/slot/scripts/slot.ts +279 -0
- package/skills/spec/SKILL.md +247 -0
- package/skills/spec/canon.md +219 -0
- package/skills/spec/checks.md +709 -0
- package/skills/spec/ci.md +378 -0
- package/skills/spec/migration.md +125 -0
- package/skills/spec/reference.md +889 -0
- package/skills/spec/scripts/architecture.ts +839 -0
- package/skills/spec/scripts/c4.ts +184 -0
- package/skills/spec/scripts/eslint-config.ts +82 -0
- package/skills/spec/scripts/harness.ts +1483 -0
- package/skills/spec/scripts/knip-hints.cjs +34 -0
- package/skills/spec/scripts/knip-hints.d.cts +17 -0
- package/skills/spec/scripts/package-run.ts +22 -0
- package/skills/spec/scripts/package.json +3 -0
- package/skills/spec/scripts/spec-break.ts +232 -0
- package/skills/spec/scripts/spec-claims.ts +198 -0
- package/skills/spec/scripts/spec-diff.ts +1111 -0
- package/skills/spec/scripts/spec-doc.ts +744 -0
- package/skills/spec/scripts/spec-exceptions.ts +233 -0
- package/skills/spec/scripts/spec-publish.ts +214 -0
- package/skills/spec/scripts/spec-run.ts +235 -0
- package/skills/spec/scripts/spec-shard.ts +267 -0
- package/skills/spec/scripts/speclib.ts +826 -0
- package/skills/spec/scripts/vendor/babel-parser.LICENSE +19 -0
- package/skills/spec/scripts/vendor/babel-parser.cjs +14615 -0
- package/skills/spec/scripts/vendor/babel-parser.d.cts +3 -0
- package/skills/task-env/SKILL.md +229 -0
- package/skills/task-env/proxy/compose.yaml +48 -0
- package/skills/task-env/reference.md +246 -0
- package/skills/task-env/scripts/task-env.ts +981 -0
|
@@ -0,0 +1,959 @@
|
|
|
1
|
+
// skills/github/scripts/locale.ts
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
// skills/github/locales/en.json
|
|
5
|
+
var en_default = {
|
|
6
|
+
issueTypes: {
|
|
7
|
+
task: "Task",
|
|
8
|
+
bug: "Bug",
|
|
9
|
+
epic: "Epic"
|
|
10
|
+
},
|
|
11
|
+
sections: {
|
|
12
|
+
questions: "## Questions",
|
|
13
|
+
scenarios: "## Scenarios"
|
|
14
|
+
},
|
|
15
|
+
labels: {
|
|
16
|
+
questions: "questions",
|
|
17
|
+
questionsDescription: "Open questions in the «## Questions» section",
|
|
18
|
+
epicDescription: "Epic: a large task with subtasks",
|
|
19
|
+
externalBlocker: "external blocker"
|
|
20
|
+
},
|
|
21
|
+
status: {
|
|
22
|
+
backlog: "Backlog",
|
|
23
|
+
inProgress: "In progress",
|
|
24
|
+
done: "Done"
|
|
25
|
+
},
|
|
26
|
+
fields: {
|
|
27
|
+
estimate: "Estimate, h",
|
|
28
|
+
actual: "Actual, h",
|
|
29
|
+
tokens: "Tokens, M",
|
|
30
|
+
cost: "Cost, $"
|
|
31
|
+
},
|
|
32
|
+
views: {
|
|
33
|
+
board: "Board",
|
|
34
|
+
table: "Table",
|
|
35
|
+
roadmap: "Roadmap"
|
|
36
|
+
},
|
|
37
|
+
labelMarkers: {
|
|
38
|
+
decision: "Decision:",
|
|
39
|
+
slice: "Slice:",
|
|
40
|
+
architecturePath: "tests/architecture — rule or module {name}"
|
|
41
|
+
},
|
|
42
|
+
comments: {
|
|
43
|
+
state: "State",
|
|
44
|
+
actual: "Actual:",
|
|
45
|
+
closedByPr: "Closed by PR {prs}: GitHub didn't link the PR to the task",
|
|
46
|
+
externalBlocker: "External blocker:"
|
|
47
|
+
},
|
|
48
|
+
subagentPrompt: {
|
|
49
|
+
task: "Task",
|
|
50
|
+
epic: "epic"
|
|
51
|
+
},
|
|
52
|
+
mainRed: {
|
|
53
|
+
title: "{base} is red",
|
|
54
|
+
check: "check {names}",
|
|
55
|
+
checks: "checks {names}"
|
|
56
|
+
},
|
|
57
|
+
actualize: {
|
|
58
|
+
lead: "{task} #{number} ({epicWord} #{epic}): epic refresh after the merge — bring the epic's open tasks in line with what shipped.",
|
|
59
|
+
fetch: "The whole assignment — the texts of the epic's open tasks, the PR files, what the script named, the checks by meaning — is printed by `{command}`: run it first, outside the sandbox (gh doesn't work in it); don't collect the tasks and the PR yourself.",
|
|
60
|
+
shipped: "Repository {repo}. Shipped: #{number} «{title}», {shipped}.",
|
|
61
|
+
prs: "PR {list}",
|
|
62
|
+
noPr: "no PR found — commits {command}",
|
|
63
|
+
files: "PR #{pr} files:",
|
|
64
|
+
epic: "## Epic #{epic} «{title}»",
|
|
65
|
+
openTasks: "## The epic's open tasks — its sub-issues, not a list in the epic's description",
|
|
66
|
+
openTask: "### #{number} «{title}»",
|
|
67
|
+
noBody: "(no description)",
|
|
68
|
+
copies: "## Copies of links in text — remove them: GitHub keeps the links (sub-issue, blocked by)",
|
|
69
|
+
copy: "- #{number}: «{line}»",
|
|
70
|
+
answers: "## Answers to open questions in comments — check that it answers that question and move it into «{questions}» (✅ and the answer at the question, same numbering); if not — leave it",
|
|
71
|
+
commentAnswer: "- #{number}, question {k}: «{text}» — {url}",
|
|
72
|
+
checks: "## Checks by meaning — yours (canon — reference.md of the github skill, «Epic refresh on task close»); the mechanics — blocked by on done tasks, the questions label without open questions, milestones without open tasks — the script did",
|
|
73
|
+
scope: "You change only GitHub tasks — description, «{questions}», links, status; the github skill script — `{script}`: not done — `task drop <N>`, a new one — `task new … --branch-type <type> --epic {epic}` (it sets the estimate itself); an answer at a question — `task answer <N> <k> \"<answer>\"`. Don't touch code, branches or git; don't close #{number}, epic #{epic} or their milestones — task close does that after you.",
|
|
74
|
+
comment: "The result — always, as the last step: a comment on epic #{epic} (`gh issue comment {epic} --repo {repo}`), one line «{report}» — after the colon what you fixed, nothing to fix — «the block is current». By it `task close {number}` sees the refresh is over: without it the close refuses.",
|
|
75
|
+
answer: "Answer — fixes one per line with task numbers and questions to the owner, if any; otherwise «The block is current».",
|
|
76
|
+
linkWords: "blocked by|blocks|after|depends on|waits for",
|
|
77
|
+
report: "Refresh after #{number}, result: {text}",
|
|
78
|
+
mechanics: "Refresh after #{number}, script: {fixes}.",
|
|
79
|
+
unblocked: "#{task}: blocked by done {blockers} removed",
|
|
80
|
+
unlabeled: "#{task}: label «{label}» removed — no open questions",
|
|
81
|
+
milestone: "milestone «{title}» closed — no open tasks in it"
|
|
82
|
+
},
|
|
83
|
+
queue: {
|
|
84
|
+
prompt: "{task} #{number} in {repo}{epic}: you are an executor of the dispatcher session. The whole assignment is printed by `{command}` — call it first, outside the sandbox (gh doesn't work in it).",
|
|
85
|
+
epic: " ({epicWord} #{epic})",
|
|
86
|
+
lead: '{task} #{number} «{title}» in {repo}{epic}: you are an executor of the dispatcher session — do the task whole by the canon, like a session in auto mode: the task\'s questions, `{script} task status {number} "{inProgress}" --branch <type>/{number}-<slug>`, the branch from a fresh origin/main, code and tests, PR, green CI, `pr premerge`, merge, deploy, the epic refresh, `{script} task close {number}`.',
|
|
87
|
+
resume: "The task is already «{inProgress}» and has a branch {branches} — a restart: continue its work. The branch is checked out in another worktree (`git worktree list`) — work in that one, not in your new one: in Claude Code — `EnterWorktree` with its path; uncommitted work — `git status` there. In no worktree — check it out in yours.",
|
|
88
|
+
subagent: "You are a subagent, not a session:",
|
|
89
|
+
session: "- don't rename or archive the session and don't write the last line («{done}») — the dispatcher does;",
|
|
90
|
+
wait: "- wait for CI and the deploy with a regular call, not in the background: a background command doesn't wake a subagent, and a Bash call lives up to 10 minutes — `{waitCi} pr <PR> --timeout 540` (after the merge — `merged <PR>`), on TIMEOUT — the same call again;",
|
|
91
|
+
git: "- git — in separate calls, without `cd`, `&&` and `|`: worktree isolation refuses a compound command with git;",
|
|
92
|
+
subagents: "- writing subagents — launch as needed, no limit: each gets its own worktree and «{task} #{number}» at the start of its prompt (`est` links its work by it); bring their commits into your branch, remove their worktrees and branches yourself after the merge — what didn't go, name in the answer;",
|
|
93
|
+
live: "- while any of your subagents works, don't answer: wait for their answers — a background subagent wakes you with its answer; an answer with live subagents looks like a failure, and the dispatcher would give the task to a second executor next to you. Can't wait — answer `waits for subagents: <which and what they do>`: it isn't `failed`, there will be no second executor;",
|
|
94
|
+
found: "- work found outside the task — `{script} task new …` (work of the task's epic — with `--epic`: the dispatcher picks it up) — name it in the answer: whether to queue it is the owner's call;",
|
|
95
|
+
question: "- a question without a clear recommendation — into «{questions}» of the task; don't start the code that depends on it and answer the dispatcher: the owner's answer comes back here.",
|
|
96
|
+
answer: "The answer to the dispatcher — one line: `merged #<PR>` · `question: <the question, options, recommendation>` · `waits for subagents: <which and what they do>` · `failed: <what stopped and where>`.",
|
|
97
|
+
alive: "{task} #{number} in {repo}: the task's previous executor or its subagent is alive — the `!` lines above. Don't take the task and change nothing: answer the dispatcher in one line `waits for the previous executor: <the worktree and why it is alive>` — the dispatcher restarts the task only when no worktree of the previous executor and its subagents is locked by a live process or gets commits.",
|
|
98
|
+
cloudEpic: " ({epicWord} {epic})",
|
|
99
|
+
cloud: "{task} #{number} «{title}» in {repo}{epic}: you are a cloud executor of the dispatcher session — do the task whole by the canon, like a session in auto mode: the task's questions, code and tests, PR, green CI, merge, deploy.",
|
|
100
|
+
cloudDoc: "- the cloud — by `docs/cloud-sessions.md` of the canon, read it first: of the github skill's scripts `task show`, `task answer` and `task new` work there (over REST), the rest refuse, the scenarios section — by editing the description over REST, project fields aren't set from the cloud; the branch is the one the cloud assigned, don't rename it;",
|
|
101
|
+
cloudClose: "- after the merge — its section «Closing a task from the cloud»: the «{actual}» comment of `est fact {number}` and a «{state}» comment with what the cloud can't do — both into the task;",
|
|
102
|
+
cloudDispatcher: "- the dispatcher waits for the task to close and closes it locally — the actual, the status, the merged branch, archiving this session: don't message it, don't archive the session;",
|
|
103
|
+
cloudQuestion: "- a question without a clear recommendation — into «{questions}» of the task and to the owner in this session, don't start the code that depends on it; stopped (a question, a failure, a limit) — a «{state}» comment into the task."
|
|
104
|
+
},
|
|
105
|
+
est: {
|
|
106
|
+
answer: "Answer",
|
|
107
|
+
manual: "+ manually:",
|
|
108
|
+
reason: "Reason:",
|
|
109
|
+
hours: "h",
|
|
110
|
+
closingWords: "close closes closed fix fixes fixed resolve resolves resolved",
|
|
111
|
+
stopWords: "the and for with from into",
|
|
112
|
+
fact: {
|
|
113
|
+
unavailable: "Actual unavailable: no sessions found (coverage none).",
|
|
114
|
+
body: "{h} h active in {agents} ({estimate}). {sessions}, {prompts},{subagents} wall {wall} h, coverage {cov}.",
|
|
115
|
+
estimate: "estimate {h} h{ratio}",
|
|
116
|
+
noEstimate: "no estimate",
|
|
117
|
+
sessions: {
|
|
118
|
+
one: "session",
|
|
119
|
+
few: "sessions",
|
|
120
|
+
many: "sessions",
|
|
121
|
+
other: "sessions"
|
|
122
|
+
},
|
|
123
|
+
prompts: {
|
|
124
|
+
one: "prompt",
|
|
125
|
+
few: "prompts",
|
|
126
|
+
many: "prompts",
|
|
127
|
+
other: "prompts"
|
|
128
|
+
},
|
|
129
|
+
subagents: {
|
|
130
|
+
one: "subagent",
|
|
131
|
+
few: "subagents",
|
|
132
|
+
many: "subagents",
|
|
133
|
+
other: "subagents"
|
|
134
|
+
},
|
|
135
|
+
commits: {
|
|
136
|
+
one: "commit",
|
|
137
|
+
few: "commits",
|
|
138
|
+
many: "commits",
|
|
139
|
+
other: "commits"
|
|
140
|
+
},
|
|
141
|
+
prs: " PR {list};",
|
|
142
|
+
noPr: " no PR;",
|
|
143
|
+
diff: " {commits}, diff {diff}.",
|
|
144
|
+
diffLines: "{n} lines",
|
|
145
|
+
diffNa: "n/a (no PR)",
|
|
146
|
+
shared: "shared {unit} with {list} (share 1/{k})",
|
|
147
|
+
commitUnit: "commit {oid}",
|
|
148
|
+
taken: " Not counted again: {h} h already in the actual of #{issue}.",
|
|
149
|
+
overlap: " Overlap with the actual of #{issue}: {h} h — recompute #{issue}.",
|
|
150
|
+
cloudMissing: " Cloud session https://claude.ai/code/{sid} not imported — est cloud-import.",
|
|
151
|
+
tokens: "Tokens: {total} M",
|
|
152
|
+
tokensSplit: " (input {in} · output {out} · cache write {cw} · cache read {cr})",
|
|
153
|
+
cost: "; cost at API rates ≈ ${usd}",
|
|
154
|
+
unpriced: "; no price (not in the price list): {models}",
|
|
155
|
+
steps: "Turns: {n} (subagents {agents}, their turns {agentSteps}), preamble {preamble}K, context at the end {end}K, turns with context > {tail}K — {share} %.",
|
|
156
|
+
waits: "Waits: CI {ci} min · after the merge {merged} · checks {checks}{slot} · rest {rest}.",
|
|
157
|
+
waitsSlot: " (slot queue {slot})",
|
|
158
|
+
and: " and ",
|
|
159
|
+
cloudAgent: "a Claude Code cloud session"
|
|
160
|
+
},
|
|
161
|
+
cloud: {
|
|
162
|
+
actual: "Actual (cloud):",
|
|
163
|
+
body: "{h} h active in a Claude Code cloud session, {prompts}.",
|
|
164
|
+
tokens: " Tokens: {total} M{cost}.",
|
|
165
|
+
cost: ", at API rates ≈ ${usd}",
|
|
166
|
+
fields: " Session: https://claude.ai/code/{sid}. The local session sets the project fields: est fact {number} --write.",
|
|
167
|
+
unavailable: "Actual unavailable (cloud): the task was done in a Claude Code cloud session — est can't see the GitHub project there; a local session gives the number by importing this session's events (est cloud-import) (coverage none).",
|
|
168
|
+
session: " Session: https://claude.ai/code/{sid}."
|
|
169
|
+
},
|
|
170
|
+
estimate: {
|
|
171
|
+
headline: "Estimate: {h} h",
|
|
172
|
+
tokens: ", ≈ {tok}M tokens",
|
|
173
|
+
partial: " (tokens and cost from {k} of {n} analogs)",
|
|
174
|
+
body: "{headline} (type {type}, confidence {conf}; {basis}). Analogs{auto}: {analogs}.{correction}{spread} {k}.",
|
|
175
|
+
auto: " (picked by the script)",
|
|
176
|
+
forecast: "forecast from the analogs' actuals",
|
|
177
|
+
expertOver: "expert estimate; the analogs would give {h} h, tokens and cost rescaled ×{ratio}",
|
|
178
|
+
expert: "expert estimate, no analogs with an actual",
|
|
179
|
+
spread: " The analogs' actuals spread {min}…{max} h — the median is taken.",
|
|
180
|
+
correction: " Correction: ×{mult}{note}.",
|
|
181
|
+
tokCorrection: " Token and cost correction: ×{mult}{note}.",
|
|
182
|
+
k: "k={k} (n={n}, level «{level}»; for reference, not applied to the forecast)",
|
|
183
|
+
noK: "k: no history",
|
|
184
|
+
levelRepo: "repo",
|
|
185
|
+
levelAll: "all repos",
|
|
186
|
+
none: "none",
|
|
187
|
+
otherProject: "from project {repo}: {list}",
|
|
188
|
+
analog: "{label} (actual {h} h{why})",
|
|
189
|
+
unknown: "{label} (actual unknown)",
|
|
190
|
+
partialCoverage: "{label} (actual {h} h, coverage {cov} — not counted)",
|
|
191
|
+
notInProject: "{label} (not in the project)",
|
|
192
|
+
labels: {
|
|
193
|
+
one: "label",
|
|
194
|
+
few: "labels",
|
|
195
|
+
many: "labels",
|
|
196
|
+
other: "labels"
|
|
197
|
+
},
|
|
198
|
+
type: "type {type}",
|
|
199
|
+
words: {
|
|
200
|
+
one: "word",
|
|
201
|
+
few: "words",
|
|
202
|
+
many: "words",
|
|
203
|
+
other: "words"
|
|
204
|
+
},
|
|
205
|
+
size: "description ×{ratio}"
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
updateCommit: "Elves flow {version}",
|
|
209
|
+
updateTask: "Update the Elves flow",
|
|
210
|
+
formerly: {
|
|
211
|
+
updateTask: "Update the ai-dev flow"
|
|
212
|
+
},
|
|
213
|
+
standardIssue: "Standard ·",
|
|
214
|
+
autoAnswer: "✅ auto",
|
|
215
|
+
lastLine: {
|
|
216
|
+
done: "All done. The session can be closed.",
|
|
217
|
+
questions: "All done, but there are questions:",
|
|
218
|
+
remaining: "Remaining:"
|
|
219
|
+
},
|
|
220
|
+
prSpecSection: "## Spec (tests)",
|
|
221
|
+
spec: {
|
|
222
|
+
tests: {
|
|
223
|
+
one: "test",
|
|
224
|
+
few: "tests",
|
|
225
|
+
many: "tests",
|
|
226
|
+
other: "tests"
|
|
227
|
+
},
|
|
228
|
+
lint: {
|
|
229
|
+
allRules: "all rules",
|
|
230
|
+
noReason: "no reason"
|
|
231
|
+
},
|
|
232
|
+
doc: {
|
|
233
|
+
mark: "<!-- spec-doc: generated from test names, don't edit by hand -->",
|
|
234
|
+
title: "Specification",
|
|
235
|
+
intro: "Generated from the `tests/` tree, test names and their JSDoc (the `spec` skill, `spec-doc`). Don't edit by hand: edit the tests.",
|
|
236
|
+
capabilities: "What the system does",
|
|
237
|
+
architecture: "What it's made of",
|
|
238
|
+
standards: "Which rules the code obeys",
|
|
239
|
+
skippedCount: {
|
|
240
|
+
one: "skipped",
|
|
241
|
+
few: "skipped",
|
|
242
|
+
many: "skipped",
|
|
243
|
+
other: "skipped"
|
|
244
|
+
},
|
|
245
|
+
failingCount: {
|
|
246
|
+
one: "failing",
|
|
247
|
+
few: "failing",
|
|
248
|
+
many: "failing",
|
|
249
|
+
other: "failing"
|
|
250
|
+
},
|
|
251
|
+
variants: {
|
|
252
|
+
one: "variant",
|
|
253
|
+
few: "variants",
|
|
254
|
+
many: "variants",
|
|
255
|
+
other: "variants"
|
|
256
|
+
},
|
|
257
|
+
skipped: "skipped",
|
|
258
|
+
failing: "failing",
|
|
259
|
+
todo: "todo",
|
|
260
|
+
outside: "Outside the tree",
|
|
261
|
+
outsideIntro: "Tests without a home in {homes} and {last} — move them:",
|
|
262
|
+
none: "— none.",
|
|
263
|
+
rules: "Rules"
|
|
264
|
+
},
|
|
265
|
+
c4: {
|
|
266
|
+
title: "Architecture",
|
|
267
|
+
intro: "Generated from the model `tests/architecture/model.ts` (the `spec` skill, `spec-doc`): C4 diagrams and a table of modules. Edit the model; the architecture rules check it against the code.",
|
|
268
|
+
system: "System",
|
|
269
|
+
context: "System and external systems",
|
|
270
|
+
containers: "Containers",
|
|
271
|
+
components: "Modules",
|
|
272
|
+
perimeter: "perimeter",
|
|
273
|
+
viaModule: "via module {modules}",
|
|
274
|
+
viaModules: "via modules {modules}",
|
|
275
|
+
uses: "uses",
|
|
276
|
+
dependsOn: "depends on",
|
|
277
|
+
calls: "calls",
|
|
278
|
+
library: "library",
|
|
279
|
+
table: "| Module | Purpose | Directory | API | Depends on | Packages |",
|
|
280
|
+
summary: "model: {parts}",
|
|
281
|
+
moduleCount: {
|
|
282
|
+
one: "module",
|
|
283
|
+
few: "modules",
|
|
284
|
+
many: "modules",
|
|
285
|
+
other: "modules"
|
|
286
|
+
},
|
|
287
|
+
containerCount: {
|
|
288
|
+
one: "container",
|
|
289
|
+
few: "containers",
|
|
290
|
+
many: "containers",
|
|
291
|
+
other: "containers"
|
|
292
|
+
},
|
|
293
|
+
externalCount: {
|
|
294
|
+
one: "external system",
|
|
295
|
+
few: "external systems",
|
|
296
|
+
many: "external systems",
|
|
297
|
+
other: "external systems"
|
|
298
|
+
}
|
|
299
|
+
},
|
|
300
|
+
diff: {
|
|
301
|
+
base: "_Base: `{base}`._",
|
|
302
|
+
unchanged: "Tests didn't change.",
|
|
303
|
+
removed: "Removed",
|
|
304
|
+
changed: "Changed",
|
|
305
|
+
added: "Added",
|
|
306
|
+
empty: "**{title}:** none.",
|
|
307
|
+
outsideMark: "outside the tree",
|
|
308
|
+
moved: "**Moved into the tree ({n}):** the same names as outside `{tests}/` on the base; from {files}.",
|
|
309
|
+
movedFolder: "{tests} from {files}",
|
|
310
|
+
files: {
|
|
311
|
+
one: "file",
|
|
312
|
+
few: "files",
|
|
313
|
+
many: "files",
|
|
314
|
+
other: "files"
|
|
315
|
+
},
|
|
316
|
+
summary: "**Summary by folder** — {tests}, over the threshold of {limit}: the full list — `spec-diff --full` (in CI — the job summary).",
|
|
317
|
+
folder: "Folder",
|
|
318
|
+
harnessColumn: "Of them, from the harness",
|
|
319
|
+
total: "Total",
|
|
320
|
+
outsideFiles: "**Outside the `{tests}/` tree** changed test files: {files}.",
|
|
321
|
+
notStatements: "⚠️ **Names that aren't statements: {n}** — new in this PR; `spec-doc` names them in warnings.",
|
|
322
|
+
scenarios: "Task scenarios",
|
|
323
|
+
noScenarios: "The issue has no `{section}` section — nothing to check against.",
|
|
324
|
+
becameTests: "**Became tests ({n}):** in the lists above.",
|
|
325
|
+
noTest: "no test",
|
|
326
|
+
extraMark: "beyond scenarios",
|
|
327
|
+
extraTitle: "Tests beyond scenarios",
|
|
328
|
+
extraListed: "in the lists above, with the «{mark}» mark.",
|
|
329
|
+
extraFull: "as a list — `spec-diff --full`.",
|
|
330
|
+
harnessCheck: "harness check",
|
|
331
|
+
fromReport: "from the report",
|
|
332
|
+
harnessNote: "_Harness tests — from the report; base — branch {branch} (source {source}{gap})._",
|
|
333
|
+
harnessGap: " older than the diff base {sha}: the lists may contain tests of PRs merged between them",
|
|
334
|
+
noSpecBase: "_Harness tests: branch {branch} has no tests.json for the base — a diff by sources, harness tests aren't visible in it._",
|
|
335
|
+
partialReport: "⚠️ **Incomplete report:** it has no tests from {folders} whose names only a run knows — their tests weren't compared ({n} in branch {branch}) and aren't in «{removed}»; the full diff — with the report of a run of all tests.",
|
|
336
|
+
modelModule: "module {module}",
|
|
337
|
+
modelDependency: "dependency {from} → {to}",
|
|
338
|
+
modelPackage: "package {package} of module {module}",
|
|
339
|
+
registry: "registry",
|
|
340
|
+
registryNamed: "registry «{value}»",
|
|
341
|
+
rule: "rule",
|
|
342
|
+
ruleNamed: "rule {value}",
|
|
343
|
+
deadCode: "code without a consumer",
|
|
344
|
+
deadCodeNamed: "code without a consumer ({value})",
|
|
345
|
+
architecture: "architecture model",
|
|
346
|
+
modelTitle: "Architecture model",
|
|
347
|
+
exceptionsTitle: "Exceptions",
|
|
348
|
+
checksTitle: "Harness checks",
|
|
349
|
+
removedKind: "{title} — removed",
|
|
350
|
+
addedKind: "{title} — added",
|
|
351
|
+
summarized: "**{title} ({n}), as a summary:**"
|
|
352
|
+
},
|
|
353
|
+
claims: {
|
|
354
|
+
describe: "Every entry point is called by at least one capability test",
|
|
355
|
+
registry: "entry points",
|
|
356
|
+
unique: "entry-point ids do not repeat in the registry",
|
|
357
|
+
called: "{entry} is called by a capability test",
|
|
358
|
+
known: "ids from the log are in the registry"
|
|
359
|
+
},
|
|
360
|
+
harness: {
|
|
361
|
+
registryNotEmpty: "registry «{registry}» is not empty{tag}",
|
|
362
|
+
registryFinds: "registry «{registry}» finds {items}{tag}",
|
|
363
|
+
violator: "violator does not pass{tag}: {name}",
|
|
364
|
+
oneRecord: "one record per element{tag}",
|
|
365
|
+
markOneElement: "a mark belongs in one element's file{tag}",
|
|
366
|
+
elementFiles: "element files from fileOf exist{tag}",
|
|
367
|
+
outside: "outside scope{tag}: {item}",
|
|
368
|
+
exception: "exception{tag}: {item} (#{issue})",
|
|
369
|
+
exceptionFiles: "exceptions are one file per element in exceptions/, not exceptions.ts",
|
|
370
|
+
exceptionJson: "exceptions are <element>.json files in the decision folder's exceptions/",
|
|
371
|
+
exceptionRules: "exceptions with a rule belong to the folder's invariant rules",
|
|
372
|
+
markRules: "marks with a rule belong to the folder's invariant rules",
|
|
373
|
+
badExample: "rule {rule} has a «not allowed» example",
|
|
374
|
+
bad: "not allowed",
|
|
375
|
+
good: "allowed",
|
|
376
|
+
outsideExample: "outside scope",
|
|
377
|
+
codeFiles: "there are code files in {dirs}",
|
|
378
|
+
lintExceptions: "exceptions in {file}: {rules}{issues}",
|
|
379
|
+
knip: {
|
|
380
|
+
files: "no files without a consumer",
|
|
381
|
+
exports: "no exports without a consumer",
|
|
382
|
+
types: "no types without a consumer",
|
|
383
|
+
dependencies: "no dependencies without an import",
|
|
384
|
+
unlisted: "no imports of unlisted packages",
|
|
385
|
+
unresolved: "no unresolved imports",
|
|
386
|
+
enumMembers: "no enum members without a consumer",
|
|
387
|
+
nsMembers: "no namespace members without a consumer",
|
|
388
|
+
duplicates: "no duplicate exports",
|
|
389
|
+
binaries: "no calls to unlisted binaries",
|
|
390
|
+
catalog: "no unused catalog entries",
|
|
391
|
+
catalogReferences: "no references to missing catalog entries",
|
|
392
|
+
known: "all knip findings are of known kinds{tag}",
|
|
393
|
+
hints: "no knip configuration hints{tag}"
|
|
394
|
+
},
|
|
395
|
+
ignoreNoReason: "outside scope without a reason: {items}",
|
|
396
|
+
env: {
|
|
397
|
+
inCode: "environment variables in the code",
|
|
398
|
+
declaredName: "{name} is declared",
|
|
399
|
+
undeclared: "a variable without a declaration",
|
|
400
|
+
declared: "declared environment variables",
|
|
401
|
+
readName: "{name} is read in the code",
|
|
402
|
+
unread: "declared and not read"
|
|
403
|
+
}
|
|
404
|
+
},
|
|
405
|
+
architecture: {
|
|
406
|
+
dirsRegistry: "code directories",
|
|
407
|
+
dirName: "directory {dir} is in module {module}",
|
|
408
|
+
dirViolator: "a directory outside the modules",
|
|
409
|
+
workspaceRegistry: "workspace packages",
|
|
410
|
+
workspaceName: "workspace package {path} is in the code roots",
|
|
411
|
+
workspaceViolator: "a workspace package outside the roots",
|
|
412
|
+
importsRegistry: "external packages of modules",
|
|
413
|
+
importsName: "{module} imports {package}",
|
|
414
|
+
importsViolator: "a package not allowed for the module",
|
|
415
|
+
allowedRegistry: "allowed packages of the model",
|
|
416
|
+
allowedName: "{module} uses allowed package {package}",
|
|
417
|
+
allowedViolator: "an allowed package without an import",
|
|
418
|
+
containersRegistry: "modules in containers",
|
|
419
|
+
libraryName: "library {module} is in containers {containers}",
|
|
420
|
+
moduleName: "module {module} is in container {container}",
|
|
421
|
+
containersViolator: "a module outside the containers",
|
|
422
|
+
edgesRegistry: "module dependencies",
|
|
423
|
+
edgeName: "dependency {from} → {to} is within a container",
|
|
424
|
+
edgeViolator: "a dependency across containers",
|
|
425
|
+
unitsRegistry: "deployable units",
|
|
426
|
+
unitName: "deployable unit {unit} is container {container}",
|
|
427
|
+
unitViolator: "a unit outside the model",
|
|
428
|
+
clientsRegistry: "storage clients in containers",
|
|
429
|
+
clientName: "client {package} of storage {storage} is in container {container}",
|
|
430
|
+
clientViolator: "a client without a link",
|
|
431
|
+
hostsRegistry: "hosts of external systems",
|
|
432
|
+
hostName: "host {host} is only in {adapters}",
|
|
433
|
+
hostPerimeter: "host {host} of external system {system} isn't in the code: no adapter",
|
|
434
|
+
hostViolator: "a host outside the model",
|
|
435
|
+
inAdapter: "adapter {list}",
|
|
436
|
+
inAdapters: "adapters {list}",
|
|
437
|
+
packagesRegistry: "packages of external systems",
|
|
438
|
+
packageName: "package {package} of external system {system} is only with {adapters}",
|
|
439
|
+
packagePerimeter: "package {package} of external system {system} is with no module: no adapter",
|
|
440
|
+
packageViolator: "a package without an adapter",
|
|
441
|
+
withAdapter: "adapter {list}",
|
|
442
|
+
withAdapters: "adapters {list}",
|
|
443
|
+
envRegistry: "environment keys of external systems",
|
|
444
|
+
envNoun: "key",
|
|
445
|
+
headersRegistry: "headers of external systems",
|
|
446
|
+
headerNoun: "header",
|
|
447
|
+
readName: "{noun} {key} of external system {system} is read only by adapter {list}",
|
|
448
|
+
readNameMany: "{noun} {key} of external system {system} is read only by adapters {list}",
|
|
449
|
+
readPerimeter: "{noun} {key} of external system {system} isn't in the code: no adapter",
|
|
450
|
+
readViolator: "{noun} outside the adapter"
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
// skills/github/locales/ru.json
|
|
455
|
+
var ru_default = {
|
|
456
|
+
issueTypes: {
|
|
457
|
+
task: "Задача",
|
|
458
|
+
bug: "Баг",
|
|
459
|
+
epic: "Эпик"
|
|
460
|
+
},
|
|
461
|
+
sections: {
|
|
462
|
+
questions: "## Вопросы",
|
|
463
|
+
scenarios: "## Сценарии"
|
|
464
|
+
},
|
|
465
|
+
labels: {
|
|
466
|
+
questions: "вопросы",
|
|
467
|
+
questionsDescription: "Есть открытые вопросы в разделе «## Вопросы»",
|
|
468
|
+
epicDescription: "Эпик: большая задача с подзадачами",
|
|
469
|
+
externalBlocker: "внешний блокер"
|
|
470
|
+
},
|
|
471
|
+
status: {
|
|
472
|
+
backlog: "Бэклог",
|
|
473
|
+
inProgress: "В работе",
|
|
474
|
+
done: "Готово"
|
|
475
|
+
},
|
|
476
|
+
fields: {
|
|
477
|
+
estimate: "Оценка, ч",
|
|
478
|
+
actual: "Факт, ч",
|
|
479
|
+
tokens: "Токены, млн",
|
|
480
|
+
cost: "Стоимость, $"
|
|
481
|
+
},
|
|
482
|
+
views: {
|
|
483
|
+
board: "Доска",
|
|
484
|
+
table: "Таблица",
|
|
485
|
+
roadmap: "Роадмэп"
|
|
486
|
+
},
|
|
487
|
+
labelMarkers: {
|
|
488
|
+
decision: "Решение:",
|
|
489
|
+
slice: "Разрез:",
|
|
490
|
+
architecturePath: "tests/architecture — правило или модуль {name}"
|
|
491
|
+
},
|
|
492
|
+
comments: {
|
|
493
|
+
state: "Состояние",
|
|
494
|
+
actual: "Факт:",
|
|
495
|
+
closedByPr: "Закрыта по PR {prs}: GitHub не связал PR с задачей",
|
|
496
|
+
externalBlocker: "Внешний блокер:"
|
|
497
|
+
},
|
|
498
|
+
subagentPrompt: {
|
|
499
|
+
task: "Задача",
|
|
500
|
+
epic: "эпик"
|
|
501
|
+
},
|
|
502
|
+
mainRed: {
|
|
503
|
+
title: "{base} красный",
|
|
504
|
+
check: "чек {names}",
|
|
505
|
+
checks: "чеки {names}"
|
|
506
|
+
},
|
|
507
|
+
actualize: {
|
|
508
|
+
lead: "{task} #{number} ({epicWord} #{epic}): актуализация блока после мержа — открытые задачи эпика привести в соответствие с выкаченным.",
|
|
509
|
+
fetch: "Задание целиком — тексты открытых задач эпика, файлы PR, что назвал скрипт, проверки по смыслу — печатает `{command}`: позови первым, вне песочницы (gh в ней не работает); задачи и PR сам не собирай.",
|
|
510
|
+
shipped: "Репозиторий {repo}. Выкачено: #{number} «{title}», {shipped}.",
|
|
511
|
+
prs: "PR {list}",
|
|
512
|
+
noPr: "PR не найден — коммиты {command}",
|
|
513
|
+
files: "Файлы PR #{pr}:",
|
|
514
|
+
epic: "## Эпик #{epic} «{title}»",
|
|
515
|
+
openTasks: "## Открытые задачи эпика — его подзадачи (sub-issues), а не список в описании эпика",
|
|
516
|
+
openTask: "### #{number} «{title}»",
|
|
517
|
+
noBody: "(описания нет)",
|
|
518
|
+
copies: "## Копии связей в тексте — убрать: связи держит GitHub (sub-issue, blocked by)",
|
|
519
|
+
copy: "- #{number}: «{line}»",
|
|
520
|
+
answers: "## Ответы на открытые вопросы в комментариях — сверить, что это ответ на этот вопрос, и перенести в «{questions}» (✅ и ответ у вопроса, нумерация та же); не он — оставить",
|
|
521
|
+
commentAnswer: "- #{number}, вопрос {k}: «{text}» — {url}",
|
|
522
|
+
checks: "## Проверки по смыслу — твои (канон — reference.md скилла github, «Epic refresh on task close»); механику — blocked by на выполненных задачах, метку вопросов без открытых вопросов, milestone без открытых задач — сделал скрипт",
|
|
523
|
+
scope: 'Меняешь только задачи GitHub — описание, «{questions}», связи, статус; скрипт скилла github — `{script}`: без выполнения — `task drop <N>`, новая — `task new … --branch-type <тип> --epic {epic}` (оценку ставит сама); ответ у вопроса — `task answer <N> <k> "<ответ>"`. Код, ветки и git не трогать; #{number}, эпик #{epic} и их milestone не закрывать — это сделает task close после тебя.',
|
|
524
|
+
comment: "Итог — всегда, последним шагом: комментарий к эпику #{epic} (`gh issue comment {epic} --repo {repo}`) одной строкой «{report}» — после двоеточия что поправил, а править нечего — «блок актуален». По нему `task close {number}` видит, что актуализация закончена: без него закрытие откажет.",
|
|
525
|
+
answer: "Ответ — правки по строке с номерами задач и вопросы владельцу, если есть; иначе «Блок актуален».",
|
|
526
|
+
linkWords: "после|зависит от|заблокирована|заблокирован|заблокировано|блокирует|ждёт|ждет",
|
|
527
|
+
report: "Актуализация после #{number}, итог: {text}",
|
|
528
|
+
mechanics: "Актуализация после #{number}, скрипт: {fixes}.",
|
|
529
|
+
unblocked: "у #{task} снят blocked by на выполненных {blockers}",
|
|
530
|
+
unlabeled: "у #{task} снята метка «{label}»: открытых вопросов нет",
|
|
531
|
+
milestone: "закрыт milestone «{title}»: открытых задач в нём нет"
|
|
532
|
+
},
|
|
533
|
+
queue: {
|
|
534
|
+
prompt: "{task} #{number} в {repo}{epic}: ты исполнитель сессии-диспетчера. Задание целиком печатает `{command}` — позови первым, вне песочницы (gh в ней не работает).",
|
|
535
|
+
epic: " ({epicWord} #{epic})",
|
|
536
|
+
lead: '{task} #{number} «{title}» в {repo}{epic}: ты исполнитель сессии-диспетчера — сделай задачу целиком по канону, как сессия в режиме auto: вопросы задачи, `{script} task status {number} "{inProgress}" --branch <тип>/{number}-<slug>`, ветка от свежего origin/main, код и тесты, PR, зелёный CI, `pr premerge`, мерж, деплой, актуализация эпика, `{script} task close {number}`.',
|
|
537
|
+
resume: "Задача уже «{inProgress}» и у неё есть ветка {branches} — это перезапуск: продолжи её работу. Ветка взята в другом worktree (`git worktree list`) — работай в нём, а не в своём новом: в Claude Code — `EnterWorktree` с его путём; незакоммиченное — `git status` там. Ни в одном worktree её нет — возьми её в свой.",
|
|
538
|
+
subagent: "Ты субагент, а не сессия:",
|
|
539
|
+
session: "- сессию не переименовывай и не архивируй, последнюю строку («{done}») не пиши — это делает диспетчер;",
|
|
540
|
+
wait: "- CI и деплой жди обычным вызовом, не в фоне: фоновая команда субагента не будит, а вызов Bash живёт до 10 минут — `{waitCi} pr <PR> --timeout 540` (после мержа — `merged <PR>`), на TIMEOUT — тот же вызов снова;",
|
|
541
|
+
git: "- git — отдельными вызовами, без `cd`, `&&` и `|`: составную команду с git изоляция worktree не пропускает;",
|
|
542
|
+
subagents: "- пишущих субагентов запускай по необходимости, без лимита: каждому — свой worktree и «{task} #{number}» в начале промпта (по нему `est` привяжет его работу); их коммиты перенеси в свою ветку, их worktree и ветки после мержа убери сам — что не убралось, назови в ответе;",
|
|
543
|
+
live: "- пока работает хоть один твой субагент, ответ не отдавай: жди их ответов — фоновый субагент будит тебя своим ответом; ответ при живых субагентах выглядит провалом, и диспетчер отдал бы задачу второму исполнителю рядом с тобой. Ждать нельзя — ответь `ждёт субагентов: <какие и что делают>`: это не «сломалось», второго исполнителя не будет;",
|
|
544
|
+
found: "- найденная работа вне задачи — `{script} task new …` (работа эпика задачи — с `--epic`: диспетчер её подхватит) — назови её в ответе: ставить ли её в очередь, решает владелец;",
|
|
545
|
+
question: "- вопрос без ясной рекомендации — в «{questions}» задачи; код, который от него зависит, не начинай и ответь диспетчеру: ответ владельца придёт сюда же.",
|
|
546
|
+
answer: "Ответ диспетчеру — одной строкой: `влито #<PR>` · `вопрос: <вопрос, варианты, рекомендация>` · `ждёт субагентов: <какие и что делают>` · `сломалось: <что и где остановилось>`.",
|
|
547
|
+
alive: "{task} #{number} в {repo}: у задачи жив прежний исполнитель или его субагент — строки `!` выше. Задачу не бери и ничего не меняй: ответь диспетчеру одной строкой `ждёт прежнего исполнителя: <worktree и почему он жив>` — диспетчер перезапускает задачу, только когда ни один worktree прежнего исполнителя и его субагентов не заблокирован живым процессом и не получает коммитов.",
|
|
548
|
+
cloudEpic: " ({epicWord} {epic})",
|
|
549
|
+
cloud: "{task} #{number} «{title}» в {repo}{epic}: ты облачный исполнитель сессии-диспетчера — сделай задачу целиком по канону, как сессия в режиме auto: вопросы задачи, код и тесты, PR, зелёный CI, мерж, деплой.",
|
|
550
|
+
cloudDoc: "- облако — по `docs/cloud-sessions.md` канона, прочитай его первым: из скриптов скилла github там работают `task show`, `task answer` и `task new` (через REST), остальные отказывают, раздел сценариев — правкой описания через REST, поля проекта из облака не ставятся; ветка — та, что назначило облако, не переименовывай;",
|
|
551
|
+
cloudClose: "- после мержа — его раздел «Closing a task from the cloud»: комментарий «{actual}» от `est fact {number}` и комментарий «{state}» с тем, что из облака не сделать, — оба в задачу;",
|
|
552
|
+
cloudDispatcher: "- диспетчер ждёт закрытия задачи и закрывает её локально — факт, статус, влитая ветка, архив этой сессии: сообщать ему не нужно, сессию не архивируй;",
|
|
553
|
+
cloudQuestion: "- вопрос без ясной рекомендации — в «{questions}» задачи и владельцу в этой сессии, код, который от него зависит, не начинай; остановился (вопрос, сбой, лимит) — комментарий «{state}» в задачу."
|
|
554
|
+
},
|
|
555
|
+
est: {
|
|
556
|
+
answer: "Ответ",
|
|
557
|
+
manual: "+ вручную:",
|
|
558
|
+
reason: "Причина:",
|
|
559
|
+
hours: "ч",
|
|
560
|
+
closingWords: "закрывает закрыт закрыта закрыто закрыты закрыть исправляет решает устраняет",
|
|
561
|
+
stopWords: "для без при что как или все это его из от до за под над про через после перед если чем где когда только также уже ещё еще так",
|
|
562
|
+
fact: {
|
|
563
|
+
unavailable: "Факт недоступен: сессии не найдены (покрытие none).",
|
|
564
|
+
body: "{h} ч активных в {agents} ({estimate}). {sessions}, {prompts},{subagents} стена {wall} ч, покрытие {cov}.",
|
|
565
|
+
estimate: "оценка {h} ч{ratio}",
|
|
566
|
+
noEstimate: "оценки нет",
|
|
567
|
+
sessions: {
|
|
568
|
+
one: "сессия",
|
|
569
|
+
few: "сессии",
|
|
570
|
+
many: "сессий",
|
|
571
|
+
other: "сессии"
|
|
572
|
+
},
|
|
573
|
+
prompts: {
|
|
574
|
+
one: "промпт",
|
|
575
|
+
few: "промпта",
|
|
576
|
+
many: "промптов",
|
|
577
|
+
other: "промпта"
|
|
578
|
+
},
|
|
579
|
+
subagents: {
|
|
580
|
+
one: "субагент",
|
|
581
|
+
few: "субагента",
|
|
582
|
+
many: "субагентов",
|
|
583
|
+
other: "субагента"
|
|
584
|
+
},
|
|
585
|
+
commits: {
|
|
586
|
+
one: "коммит",
|
|
587
|
+
few: "коммита",
|
|
588
|
+
many: "коммитов",
|
|
589
|
+
other: "коммита"
|
|
590
|
+
},
|
|
591
|
+
prs: " PR {list};",
|
|
592
|
+
noPr: " PR нет;",
|
|
593
|
+
diff: " {commits}, дифф {diff}.",
|
|
594
|
+
diffLines: "{n} строк",
|
|
595
|
+
diffNa: "н/д (без PR)",
|
|
596
|
+
shared: "общий {unit} с {list} (доля 1/{k})",
|
|
597
|
+
commitUnit: "коммит {oid}",
|
|
598
|
+
taken: " Не засчитано повторно: {h} ч уже в факте #{issue}.",
|
|
599
|
+
overlap: " Пересечение с фактом #{issue}: {h} ч — пересчитать #{issue}.",
|
|
600
|
+
cloudMissing: " Облачная сессия https://claude.ai/code/{sid} не импортирована — est cloud-import.",
|
|
601
|
+
tokens: "Токены: {total} млн",
|
|
602
|
+
tokensSplit: " (вход {in} · выход {out} · запись кэша {cw} · чтение кэша {cr})",
|
|
603
|
+
cost: "; стоимость по API-тарифам ≈ ${usd}",
|
|
604
|
+
unpriced: "; без цены (нет в прайсе): {models}",
|
|
605
|
+
steps: "Ходы: {n} (субагентов {agents}, их ходов {agentSteps}), преамбула {preamble} тыс., контекст в конце {end} тыс., ходов с контекстом > {tail} тыс. — {share} %.",
|
|
606
|
+
waits: "Ожидания: CI {ci} мин · после мержа {merged} · проверки {checks}{slot} · остальное {rest}.",
|
|
607
|
+
waitsSlot: " (очередь slot {slot})",
|
|
608
|
+
and: " и ",
|
|
609
|
+
cloudAgent: "облачной сессии Claude Code"
|
|
610
|
+
},
|
|
611
|
+
cloud: {
|
|
612
|
+
actual: "Факт (облако):",
|
|
613
|
+
body: "{h} ч активных в облачной сессии Claude Code, {prompts}.",
|
|
614
|
+
tokens: " Токены: {total} млн{cost}.",
|
|
615
|
+
cost: ", по API-тарифам ≈ ${usd}",
|
|
616
|
+
fields: " Сессия: https://claude.ai/code/{sid}. Поля проекта поставит локальная сессия: est fact {number} --write.",
|
|
617
|
+
unavailable: "Факт недоступен (облако): задача сделана в облачной сессии Claude Code — там est не видит проекта GitHub; цифру даст локальная сессия, импортировав события этой сессии (est cloud-import) (покрытие none).",
|
|
618
|
+
session: " Сессия: https://claude.ai/code/{sid}."
|
|
619
|
+
},
|
|
620
|
+
estimate: {
|
|
621
|
+
headline: "Оценка: {h} ч",
|
|
622
|
+
tokens: ", ≈ {tok} млн токенов",
|
|
623
|
+
partial: " (токены и стоимость по {k} из {n} аналогов)",
|
|
624
|
+
body: "{headline} (тип {type}, доверие {conf}; {basis}). Аналоги{auto}: {analogs}.{correction}{spread} {k}.",
|
|
625
|
+
auto: " (подбор скриптом)",
|
|
626
|
+
forecast: "прогноз по фактам аналогов",
|
|
627
|
+
expertOver: "экспертная оценка; по аналогам вышло бы {h} ч, токены и стоимость пересчитаны ×{ratio}",
|
|
628
|
+
expert: "экспертная оценка, аналогов с фактом нет",
|
|
629
|
+
spread: " Разброс фактов аналогов {min}…{max} ч — взята медиана.",
|
|
630
|
+
correction: " Поправка: ×{mult}{note}.",
|
|
631
|
+
tokCorrection: " Поправка токенов и стоимости: ×{mult}{note}.",
|
|
632
|
+
k: "k={k} (n={n}, уровень «{level}»; справочно, к прогнозу не применяется)",
|
|
633
|
+
noK: "k: истории нет",
|
|
634
|
+
levelRepo: "репо",
|
|
635
|
+
levelAll: "все репо",
|
|
636
|
+
none: "нет",
|
|
637
|
+
otherProject: "из проекта {repo}: {list}",
|
|
638
|
+
analog: "{label} (факт {h} ч{why})",
|
|
639
|
+
unknown: "{label} (факт неизвестен)",
|
|
640
|
+
partialCoverage: "{label} (факт {h} ч, покрытие {cov} — не учтён)",
|
|
641
|
+
notInProject: "{label} (нет в проекте)",
|
|
642
|
+
labels: {
|
|
643
|
+
one: "метка",
|
|
644
|
+
few: "метки",
|
|
645
|
+
many: "меток",
|
|
646
|
+
other: "метки"
|
|
647
|
+
},
|
|
648
|
+
type: "тип {type}",
|
|
649
|
+
words: {
|
|
650
|
+
one: "слово",
|
|
651
|
+
few: "слова",
|
|
652
|
+
many: "слов",
|
|
653
|
+
other: "слова"
|
|
654
|
+
},
|
|
655
|
+
size: "описание ×{ratio}"
|
|
656
|
+
}
|
|
657
|
+
},
|
|
658
|
+
updateCommit: "флоу Elves {version}",
|
|
659
|
+
updateTask: "Обновить флоу Elves",
|
|
660
|
+
formerly: {
|
|
661
|
+
updateTask: "Обновить флоу ai-dev"
|
|
662
|
+
},
|
|
663
|
+
standardIssue: "Стандарт ·",
|
|
664
|
+
autoAnswer: "✅ авто",
|
|
665
|
+
lastLine: {
|
|
666
|
+
done: "Всё сделано. Сессию можно закрывать.",
|
|
667
|
+
questions: "Всё сделано, но есть вопросы:",
|
|
668
|
+
remaining: "Осталось:"
|
|
669
|
+
},
|
|
670
|
+
prSpecSection: "## Спека (тесты)",
|
|
671
|
+
spec: {
|
|
672
|
+
tests: {
|
|
673
|
+
one: "тест",
|
|
674
|
+
few: "теста",
|
|
675
|
+
many: "тестов",
|
|
676
|
+
other: "теста"
|
|
677
|
+
},
|
|
678
|
+
lint: {
|
|
679
|
+
allRules: "все правила",
|
|
680
|
+
noReason: "без причины"
|
|
681
|
+
},
|
|
682
|
+
doc: {
|
|
683
|
+
mark: "<!-- spec-doc: сгенерировано из названий тестов, руками не править -->",
|
|
684
|
+
title: "Спецификация",
|
|
685
|
+
intro: "Сгенерировано из дерева `tests/`, названий тестов и их JSDoc (скилл `spec`, `spec-doc`). Руками не править: правка — в тестах.",
|
|
686
|
+
capabilities: "Что делает система",
|
|
687
|
+
architecture: "Из чего состоит",
|
|
688
|
+
standards: "Каким правилам подчиняется код",
|
|
689
|
+
skippedCount: {
|
|
690
|
+
one: "пропущен",
|
|
691
|
+
few: "пропущено",
|
|
692
|
+
many: "пропущено",
|
|
693
|
+
other: "пропущено"
|
|
694
|
+
},
|
|
695
|
+
failingCount: {
|
|
696
|
+
one: "падает",
|
|
697
|
+
few: "падают",
|
|
698
|
+
many: "падают",
|
|
699
|
+
other: "падают"
|
|
700
|
+
},
|
|
701
|
+
variants: {
|
|
702
|
+
one: "вариант",
|
|
703
|
+
few: "варианта",
|
|
704
|
+
many: "вариантов",
|
|
705
|
+
other: "варианта"
|
|
706
|
+
},
|
|
707
|
+
skipped: "пропущен",
|
|
708
|
+
failing: "падает",
|
|
709
|
+
todo: "todo",
|
|
710
|
+
outside: "Вне дерева",
|
|
711
|
+
outsideIntro: "Тесты без дома в {homes} и {last} — перенести:",
|
|
712
|
+
none: "— нет.",
|
|
713
|
+
rules: "Правила"
|
|
714
|
+
},
|
|
715
|
+
c4: {
|
|
716
|
+
title: "Архитектура",
|
|
717
|
+
intro: "Сгенерировано из модели `tests/architecture/model.ts` (скилл `spec`, `spec-doc`): схемы C4 и таблица модулей. Правка — в модели; с кодом её сверяют правила архитектуры.",
|
|
718
|
+
system: "Система",
|
|
719
|
+
context: "Система и внешние системы",
|
|
720
|
+
containers: "Контейнеры",
|
|
721
|
+
components: "Модули",
|
|
722
|
+
perimeter: "периметр",
|
|
723
|
+
viaModule: "через модуль {modules}",
|
|
724
|
+
viaModules: "через модули {modules}",
|
|
725
|
+
uses: "использует",
|
|
726
|
+
dependsOn: "зависит",
|
|
727
|
+
calls: "вызывает",
|
|
728
|
+
library: "библиотека",
|
|
729
|
+
table: "| Модуль | Назначение | Каталог | API | Зависит от | Пакеты |",
|
|
730
|
+
summary: "модель: {parts}",
|
|
731
|
+
moduleCount: {
|
|
732
|
+
one: "модуль",
|
|
733
|
+
few: "модуля",
|
|
734
|
+
many: "модулей",
|
|
735
|
+
other: "модуля"
|
|
736
|
+
},
|
|
737
|
+
containerCount: {
|
|
738
|
+
one: "контейнер",
|
|
739
|
+
few: "контейнера",
|
|
740
|
+
many: "контейнеров",
|
|
741
|
+
other: "контейнера"
|
|
742
|
+
},
|
|
743
|
+
externalCount: {
|
|
744
|
+
one: "внешняя система",
|
|
745
|
+
few: "внешние системы",
|
|
746
|
+
many: "внешних систем",
|
|
747
|
+
other: "внешней системы"
|
|
748
|
+
}
|
|
749
|
+
},
|
|
750
|
+
diff: {
|
|
751
|
+
base: "_База: `{base}`._",
|
|
752
|
+
unchanged: "Тесты не менялись.",
|
|
753
|
+
removed: "Удалены",
|
|
754
|
+
changed: "Изменены",
|
|
755
|
+
added: "Добавлены",
|
|
756
|
+
empty: "**{title}:** нет.",
|
|
757
|
+
outsideMark: "вне дерева",
|
|
758
|
+
moved: "**Перенесены в дерево ({n}):** названия те же, что вне `{tests}/` на базе; из {files}.",
|
|
759
|
+
movedFolder: "{tests} из {files}",
|
|
760
|
+
files: {
|
|
761
|
+
one: "файла",
|
|
762
|
+
few: "файлов",
|
|
763
|
+
many: "файлов",
|
|
764
|
+
other: "файла"
|
|
765
|
+
},
|
|
766
|
+
summary: "**Сводка по папкам** — {tests}, больше порога {limit}: полный список — `spec-diff --full` (в CI — summary джоба).",
|
|
767
|
+
folder: "Папка",
|
|
768
|
+
harnessColumn: "Из них харнесса",
|
|
769
|
+
total: "Всего",
|
|
770
|
+
outsideFiles: "**Вне дерева `{tests}/`** изменены файлы тестов: {files}.",
|
|
771
|
+
notStatements: "⚠️ **Названия — не утверждения: {n}** — новые в этом PR; `spec-doc` называет их предупреждениями.",
|
|
772
|
+
scenarios: "Сценарии задачи",
|
|
773
|
+
noScenarios: "В задаче нет раздела «{section}» — сверять не с чем.",
|
|
774
|
+
becameTests: "**Стали тестами ({n}):** в списках выше.",
|
|
775
|
+
noTest: "теста нет",
|
|
776
|
+
extraMark: "сверх сценариев",
|
|
777
|
+
extraTitle: "Тесты сверх сценариев",
|
|
778
|
+
extraListed: "в списках выше, с пометкой «{mark}».",
|
|
779
|
+
extraFull: "списком — `spec-diff --full`.",
|
|
780
|
+
harnessCheck: "проверка харнесса",
|
|
781
|
+
fromReport: "по отчёту",
|
|
782
|
+
harnessNote: "_Тесты харнесса — по отчёту; база — ветка {branch} (исходник {source}{gap})._",
|
|
783
|
+
harnessGap: " старше базы диффа {sha}: в списках возможны тесты PR, влитых между ними",
|
|
784
|
+
noSpecBase: "_Тесты харнесса: в ветке {branch} нет tests.json для базы — дифф по исходникам, тестов харнесса в нём не видно._",
|
|
785
|
+
partialReport: "⚠️ **Отчёт неполный:** в нём нет тестов из {folders}, чьи названия знает только прогон, — их тесты не сравнивались ({n} в ветке {branch}) и в «{removed}» не попали; полный дифф — с отчётом прогона всех тестов.",
|
|
786
|
+
modelModule: "модуль {module}",
|
|
787
|
+
modelDependency: "зависимость {from} → {to}",
|
|
788
|
+
modelPackage: "пакет {package} модуля {module}",
|
|
789
|
+
registry: "реестр",
|
|
790
|
+
registryNamed: "реестр «{value}»",
|
|
791
|
+
rule: "правило",
|
|
792
|
+
ruleNamed: "правило {value}",
|
|
793
|
+
deadCode: "код без потребителя",
|
|
794
|
+
deadCodeNamed: "код без потребителя ({value})",
|
|
795
|
+
architecture: "модель архитектуры",
|
|
796
|
+
modelTitle: "Модель архитектуры",
|
|
797
|
+
exceptionsTitle: "Исключения",
|
|
798
|
+
checksTitle: "Проверки харнесса",
|
|
799
|
+
removedKind: "{title} — снято",
|
|
800
|
+
addedKind: "{title} — добавлено",
|
|
801
|
+
summarized: "**{title} ({n}), сводкой:**"
|
|
802
|
+
},
|
|
803
|
+
claims: {
|
|
804
|
+
describe: "Каждая точка входа вызывается хотя бы одним тестом capability",
|
|
805
|
+
registry: "точки входа",
|
|
806
|
+
unique: "id точек входа в реестре не повторяются",
|
|
807
|
+
called: "{entry} вызывается тестом capability",
|
|
808
|
+
known: "id из журнала есть в реестре"
|
|
809
|
+
},
|
|
810
|
+
harness: {
|
|
811
|
+
registryNotEmpty: "реестр «{registry}» не пуст{tag}",
|
|
812
|
+
registryFinds: "реестр «{registry}» находит {items}{tag}",
|
|
813
|
+
violator: "нарушитель не проходит{tag}: {name}",
|
|
814
|
+
oneRecord: "у элемента одна запись — отметка или exceptions/ и outside{tag}",
|
|
815
|
+
markOneElement: "отметка — в файле одного элемента{tag}",
|
|
816
|
+
elementFiles: "файлы элементов из fileOf есть на диске{tag}",
|
|
817
|
+
outside: "вне охвата{tag}: {item}",
|
|
818
|
+
exception: "исключение{tag}: {item} (#{issue})",
|
|
819
|
+
exceptionFiles: "исключения — файлом на элемент в exceptions/, а не в exceptions.ts",
|
|
820
|
+
exceptionJson: "исключения — файлы <элемент>.json в exceptions/ папки решения",
|
|
821
|
+
exceptionRules: "исключения с rule — к правилам инвариантов папки",
|
|
822
|
+
markRules: "отметки с rule — к правилам инвариантов папки",
|
|
823
|
+
badExample: "у правила {rule} есть пример «нельзя»",
|
|
824
|
+
bad: "нельзя",
|
|
825
|
+
good: "можно",
|
|
826
|
+
outsideExample: "вне охвата",
|
|
827
|
+
codeFiles: "в {dirs} есть файлы кода",
|
|
828
|
+
lintExceptions: "исключения в {file}: {rules}{issues}",
|
|
829
|
+
knip: {
|
|
830
|
+
files: "нет файлов без потребителя",
|
|
831
|
+
exports: "нет экспортов без потребителя",
|
|
832
|
+
types: "нет типов без потребителя",
|
|
833
|
+
dependencies: "нет зависимостей без импорта",
|
|
834
|
+
unlisted: "нет импортов неустановленных пакетов",
|
|
835
|
+
unresolved: "нет нерезолвящихся импортов",
|
|
836
|
+
enumMembers: "нет членов перечислений без потребителя",
|
|
837
|
+
nsMembers: "нет членов пространств имён без потребителя",
|
|
838
|
+
duplicates: "нет экспортов-дублей",
|
|
839
|
+
binaries: "нет вызовов неустановленных бинарников",
|
|
840
|
+
catalog: "нет лишних записей каталога пакетов",
|
|
841
|
+
catalogReferences: "нет ссылок на отсутствующие записи каталога пакетов",
|
|
842
|
+
known: "все находки knip — известных видов{tag}",
|
|
843
|
+
hints: "нет подсказок конфигурации knip{tag}"
|
|
844
|
+
},
|
|
845
|
+
ignoreNoReason: "вне охвата без причины: {items}",
|
|
846
|
+
env: {
|
|
847
|
+
inCode: "переменные окружения в коде",
|
|
848
|
+
declaredName: "{name} объявлена",
|
|
849
|
+
undeclared: "переменная без объявления",
|
|
850
|
+
declared: "объявленные переменные окружения",
|
|
851
|
+
readName: "{name} читается в коде",
|
|
852
|
+
unread: "объявлена и не читается"
|
|
853
|
+
}
|
|
854
|
+
},
|
|
855
|
+
architecture: {
|
|
856
|
+
dirsRegistry: "каталоги кода",
|
|
857
|
+
dirName: "каталог {dir} — в модуле {module}",
|
|
858
|
+
dirViolator: "каталог вне модулей",
|
|
859
|
+
workspaceRegistry: "пакеты workspace",
|
|
860
|
+
workspaceName: "пакет workspace {path} — в корнях кода",
|
|
861
|
+
workspaceViolator: "пакет workspace вне корней",
|
|
862
|
+
importsRegistry: "внешние пакеты модулей",
|
|
863
|
+
importsName: "{module} импортирует {package}",
|
|
864
|
+
importsViolator: "пакет, не разрешённый модулю",
|
|
865
|
+
allowedRegistry: "разрешённые пакеты модели",
|
|
866
|
+
allowedName: "{module} использует разрешённый пакет {package}",
|
|
867
|
+
allowedViolator: "разрешённый пакет без импорта",
|
|
868
|
+
containersRegistry: "модули в контейнерах",
|
|
869
|
+
libraryName: "библиотека {module} — в контейнерах {containers}",
|
|
870
|
+
moduleName: "модуль {module} — в контейнере {container}",
|
|
871
|
+
containersViolator: "модуль вне контейнеров",
|
|
872
|
+
edgesRegistry: "зависимости модулей",
|
|
873
|
+
edgeName: "зависимость {from} → {to} — внутри контейнера",
|
|
874
|
+
edgeViolator: "зависимость через контейнер",
|
|
875
|
+
unitsRegistry: "развёртываемые единицы",
|
|
876
|
+
unitName: "развёртываемая единица {unit} — контейнер {container}",
|
|
877
|
+
unitViolator: "единица вне модели",
|
|
878
|
+
clientsRegistry: "клиенты хранилищ в контейнерах",
|
|
879
|
+
clientName: "клиент {package} хранилища {storage} — в контейнере {container}",
|
|
880
|
+
clientViolator: "клиент без связи",
|
|
881
|
+
hostsRegistry: "хосты внешних систем",
|
|
882
|
+
hostName: "хост {host} — только в {adapters}",
|
|
883
|
+
hostPerimeter: "хост {host} внешней системы {system} — не в коде: адаптера нет",
|
|
884
|
+
hostViolator: "хост вне модели",
|
|
885
|
+
inAdapter: "адаптере {list}",
|
|
886
|
+
inAdapters: "адаптерах {list}",
|
|
887
|
+
packagesRegistry: "пакеты внешних систем",
|
|
888
|
+
packageName: "пакет {package} внешней системы {system} — только у {adapters}",
|
|
889
|
+
packagePerimeter: "пакет {package} внешней системы {system} — ни у одного модуля: адаптера нет",
|
|
890
|
+
packageViolator: "пакет без адаптера",
|
|
891
|
+
withAdapter: "адаптера {list}",
|
|
892
|
+
withAdapters: "адаптеров {list}",
|
|
893
|
+
envRegistry: "ключи окружения внешних систем",
|
|
894
|
+
envNoun: "ключ",
|
|
895
|
+
headersRegistry: "заголовки внешних систем",
|
|
896
|
+
headerNoun: "заголовок",
|
|
897
|
+
readName: "{noun} {key} внешней системы {system} читает только адаптер {list}",
|
|
898
|
+
readNameMany: "{noun} {key} внешней системы {system} читают только адаптеры {list}",
|
|
899
|
+
readPerimeter: "{noun} {key} внешней системы {system} — не в коде: адаптера нет",
|
|
900
|
+
readViolator: "{noun} вне адаптера"
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
};
|
|
904
|
+
|
|
905
|
+
// skills/github/scripts/locale.ts
|
|
906
|
+
var LANGUAGE_CODE = /^[a-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$/;
|
|
907
|
+
var DEFAULT_LANGUAGE = "en";
|
|
908
|
+
var LEGACY_LANGUAGE = "ru";
|
|
909
|
+
var MANIFEST = path.join(".agents", "elves.json");
|
|
910
|
+
var FORMER_MANIFEST = path.join(".agents", "ai-dev.json");
|
|
911
|
+
var LOCALES = { en: en_default, ru: ru_default };
|
|
912
|
+
function projectRoot(dir) {
|
|
913
|
+
const start = path.resolve(dir);
|
|
914
|
+
for (let d = start;; d = path.dirname(d)) {
|
|
915
|
+
if (existsSync(path.join(d, ".git")))
|
|
916
|
+
return d;
|
|
917
|
+
if (path.dirname(d) === d)
|
|
918
|
+
return start;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
function projectLanguage(dir = process.cwd()) {
|
|
922
|
+
const root = projectRoot(dir);
|
|
923
|
+
const file = [MANIFEST, FORMER_MANIFEST].map((m) => path.join(root, m)).find((f) => existsSync(f));
|
|
924
|
+
if (!file)
|
|
925
|
+
return DEFAULT_LANGUAGE;
|
|
926
|
+
let manifest = {};
|
|
927
|
+
try {
|
|
928
|
+
manifest = JSON.parse(readFileSync(file, "utf8")) ?? {};
|
|
929
|
+
} catch {}
|
|
930
|
+
return typeof manifest.language === "string" && LANGUAGE_CODE.test(manifest.language) ? manifest.language : LEGACY_LANGUAGE;
|
|
931
|
+
}
|
|
932
|
+
function names(language) {
|
|
933
|
+
return Object.hasOwn(LOCALES, language) ? LOCALES[language] : en_default;
|
|
934
|
+
}
|
|
935
|
+
function languages() {
|
|
936
|
+
return [DEFAULT_LANGUAGE, ...Object.keys(LOCALES).filter((l) => l !== DEFAULT_LANGUAGE).sort()];
|
|
937
|
+
}
|
|
938
|
+
function everyNames() {
|
|
939
|
+
return languages().map(names);
|
|
940
|
+
}
|
|
941
|
+
function every(pick) {
|
|
942
|
+
return [...new Set(everyNames().map(pick))];
|
|
943
|
+
}
|
|
944
|
+
function fill(template, vars) {
|
|
945
|
+
return template.replace(/\{(\w+)\}/g, (m, k) => Object.hasOwn(vars, k) ? String(vars[k]) : m);
|
|
946
|
+
}
|
|
947
|
+
function plural(language, forms, n) {
|
|
948
|
+
const form = new Intl.PluralRules(language).select(n);
|
|
949
|
+
return forms[form] ?? forms.other;
|
|
950
|
+
}
|
|
951
|
+
function count(language, forms, n) {
|
|
952
|
+
return `${n} ${plural(language, forms, n)}`;
|
|
953
|
+
}
|
|
954
|
+
function escapeRe(text) {
|
|
955
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
956
|
+
}
|
|
957
|
+
var locale_default = { LANGUAGE_CODE, DEFAULT_LANGUAGE, LEGACY_LANGUAGE, projectRoot, projectLanguage, names, languages, everyNames, every, fill, plural, count, escapeRe };
|
|
958
|
+
|
|
959
|
+
export { locale_default };
|