copilot-operator 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +556 -0
  3. package/dist/src/api/app.module.js +18 -0
  4. package/dist/src/api/dataAcl.js +109 -0
  5. package/dist/src/api/folderPicker.js +110 -0
  6. package/dist/src/api/main.js +132 -0
  7. package/dist/src/api/operator.controller.js +975 -0
  8. package/dist/src/api/operator.service.js +1971 -0
  9. package/dist/src/api/saveToDesktop.js +157 -0
  10. package/dist/src/api/security.js +225 -0
  11. package/dist/src/api/settings.js +57 -0
  12. package/dist/src/cli.js +531 -0
  13. package/dist/src/config/layout.js +66 -0
  14. package/dist/src/config/lockedPolicy.js +176 -0
  15. package/dist/src/config/schema.js +487 -0
  16. package/dist/src/context/contextFiles.js +295 -0
  17. package/dist/src/context/desktopMirror.js +113 -0
  18. package/dist/src/context/projectMirror.js +364 -0
  19. package/dist/src/exec/authorizer.js +61 -0
  20. package/dist/src/exec/checks.js +285 -0
  21. package/dist/src/exec/confinement.js +224 -0
  22. package/dist/src/exec/dangerous.js +161 -0
  23. package/dist/src/exec/environment.js +80 -0
  24. package/dist/src/exec/isolation.js +166 -0
  25. package/dist/src/exec/network.js +242 -0
  26. package/dist/src/exec/policy.js +201 -0
  27. package/dist/src/exec/policyManifest.js +84 -0
  28. package/dist/src/exec/processes.js +329 -0
  29. package/dist/src/exec/programs.js +290 -0
  30. package/dist/src/exec/redaction.js +93 -0
  31. package/dist/src/exec/reportFile.js +154 -0
  32. package/dist/src/exec/runner.js +245 -0
  33. package/dist/src/exec/scriptFiles.js +81 -0
  34. package/dist/src/exec/shellExecuteTrap.js +221 -0
  35. package/dist/src/exec/shells.js +298 -0
  36. package/dist/src/exec/stepEnv.js +72 -0
  37. package/dist/src/exec/winps.js +17 -0
  38. package/dist/src/exec/workDir.js +94 -0
  39. package/dist/src/log/runLog.js +43 -0
  40. package/dist/src/orchestrator/derivedChecks.js +95 -0
  41. package/dist/src/orchestrator/review.js +590 -0
  42. package/dist/src/orchestrator/taskRunner.js +1582 -0
  43. package/dist/src/plan/brief.js +1785 -0
  44. package/dist/src/plan/importPlan.js +202 -0
  45. package/dist/src/plan/schema.js +483 -0
  46. package/dist/src/plan/script.js +1012 -0
  47. package/dist/src/plan/systemGuide.js +1851 -0
  48. package/dist/src/protocol/parser.js +224 -0
  49. package/dist/src/protocol/replySchema.js +207 -0
  50. package/dist/src/protocol/reporter.js +75 -0
  51. package/dist/src/protocol/reviewSchema.js +205 -0
  52. package/dist/src/session/compose.js +67 -0
  53. package/dist/src/session/debugExport.js +165 -0
  54. package/dist/src/session/events.js +34 -0
  55. package/dist/src/session/exportRecord.js +261 -0
  56. package/dist/src/session/exports.js +484 -0
  57. package/dist/src/session/model.js +22 -0
  58. package/dist/src/session/retention.js +34 -0
  59. package/dist/src/session/runName.js +36 -0
  60. package/dist/src/session/store.js +440 -0
  61. package/dist/src/session/story.js +198 -0
  62. package/dist/src/transport/acceptance.js +34 -0
  63. package/dist/src/transport/chatSession.js +73 -0
  64. package/dist/src/transport/copilotTransport.js +1208 -0
  65. package/dist/src/transport/edgeCrash.js +59 -0
  66. package/dist/src/transport/locators.js +261 -0
  67. package/dist/src/transport/profileLock.js +102 -0
  68. package/dist/src/util/pacing.js +89 -0
  69. package/dist/src/vcs/commitHygiene.js +102 -0
  70. package/dist/src/vcs/git.js +286 -0
  71. package/dist/src/vcs/taskVcs.js +343 -0
  72. package/dist/web/404.html +15 -0
  73. package/dist/web/_next/static/chunks/12.f1f4d35b52633e8d.js +1 -0
  74. package/dist/web/_next/static/chunks/131-bae98f790a541679.js +1 -0
  75. package/dist/web/_next/static/chunks/197-f7c33a0114fc3db3.js +1 -0
  76. package/dist/web/_next/static/chunks/489.158bbe303a595fba.js +1 -0
  77. package/dist/web/_next/static/chunks/643-34b9f66a6c317c4a.js +1 -0
  78. package/dist/web/_next/static/chunks/867-03b7cac8f79896c3.js +1 -0
  79. package/dist/web/_next/static/chunks/955-63de06e182d98949.js +1 -0
  80. package/dist/web/_next/static/chunks/app/_not-found/page-0d0d8e7e62efca12.js +1 -0
  81. package/dist/web/_next/static/chunks/app/appearance/page-501842e7aeac7f6b.js +1 -0
  82. package/dist/web/_next/static/chunks/app/defaults/page-ed858127cbdfb4ab.js +1 -0
  83. package/dist/web/_next/static/chunks/app/history/page-bad4f3e89b485719.js +1 -0
  84. package/dist/web/_next/static/chunks/app/import/page-db1f5f1ab2f5cad1.js +1 -0
  85. package/dist/web/_next/static/chunks/app/layout-72251c52cc54f95e.js +1 -0
  86. package/dist/web/_next/static/chunks/app/level1/page-cf3e50cf64fecf4f.js +1 -0
  87. package/dist/web/_next/static/chunks/app/page-6fd16e5c8cf3b20f.js +1 -0
  88. package/dist/web/_next/static/chunks/app/presets/page-951e78b80e0e22e6.js +1 -0
  89. package/dist/web/_next/static/chunks/app/sessions/view/page-f2e63618097d7221.js +1 -0
  90. package/dist/web/_next/static/chunks/app/system/page-44e7954c0e48f4ba.js +1 -0
  91. package/dist/web/_next/static/chunks/c7879cf7-b5ab1053c1d9a2e7.js +1 -0
  92. package/dist/web/_next/static/chunks/framework-9ac6a346bd42cedd.js +1 -0
  93. package/dist/web/_next/static/chunks/main-6be4444e2d667e41.js +1 -0
  94. package/dist/web/_next/static/chunks/main-app-e3a5f713ba31557e.js +1 -0
  95. package/dist/web/_next/static/chunks/pages/_app-32d20d5a04949d29.js +1 -0
  96. package/dist/web/_next/static/chunks/pages/_error-abbab53f25ea29b0.js +1 -0
  97. package/dist/web/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  98. package/dist/web/_next/static/chunks/webpack-0f31b926d9a9c5a4.js +1 -0
  99. package/dist/web/_next/static/css/d77735cdc9020a86.css +1 -0
  100. package/dist/web/_next/static/u8rZNwh4d3n-HGJnfZ72Y/_buildManifest.js +1 -0
  101. package/dist/web/_next/static/u8rZNwh4d3n-HGJnfZ72Y/_ssgManifest.js +1 -0
  102. package/dist/web/appearance.html +15 -0
  103. package/dist/web/appearance.txt +26 -0
  104. package/dist/web/defaults.html +15 -0
  105. package/dist/web/defaults.txt +26 -0
  106. package/dist/web/history.html +15 -0
  107. package/dist/web/history.txt +26 -0
  108. package/dist/web/import.html +15 -0
  109. package/dist/web/import.txt +26 -0
  110. package/dist/web/index.html +15 -0
  111. package/dist/web/index.txt +26 -0
  112. package/dist/web/level1.html +15 -0
  113. package/dist/web/level1.txt +26 -0
  114. package/dist/web/presets.html +18 -0
  115. package/dist/web/presets.txt +26 -0
  116. package/dist/web/sessions/view.html +15 -0
  117. package/dist/web/sessions/view.txt +26 -0
  118. package/dist/web/system.html +15 -0
  119. package/dist/web/system.txt +26 -0
  120. package/package.json +111 -0
  121. package/prompts/level1.md +535 -0
  122. package/prompts/organisation.bg.md +44 -0
  123. package/prompts/organisation.en.md +44 -0
  124. package/prompts/persona.bg.md +17 -0
  125. package/prompts/persona.en.md +17 -0
  126. package/prompts/review1.md +277 -0
  127. package/prompts/work.bg.md +19 -0
  128. package/prompts/work.en.md +19 -0
@@ -0,0 +1,1785 @@
1
+ /**
2
+ * The brief: what the user hands to a chat model so it can write a plan for this system.
3
+ *
4
+ * This text is the whole interface to a model nobody here controls. It has to do three things
5
+ * at once — say what the runner actually is, make the model interview the user instead of
6
+ * guessing, and pin the output format exactly — and it has to do them in a single paste,
7
+ * because the user is copying it into some other chat window with no tooling on our side.
8
+ *
9
+ * Version control used to be a switch on the import page, and the brief was rebuilt around
10
+ * whatever it was set to. It is not any more, and that is the point of this text: the operator
11
+ * should not have to answer, on a form, a question about work that has not been described yet.
12
+ * The model asks instead — it is the one holding the conversation — and its answer travels in
13
+ * the JSON, per session, where a plan can sensibly say that one session commits and another
14
+ * only reads. So this brief describes both, and says plainly that skipping the question gets
15
+ * the document refused.
16
+ *
17
+ * What the text was missing for a long time is the application itself. The persona could write a
18
+ * faultless plan and then tell the operator to "import it on the import page", which is not what
19
+ * anything on that page says, and the operator was left to translate. So the brief is organised as
20
+ * five announced phases — the organisation, this work, the plan and the run, a failure, the
21
+ * verdict — and it embeds `systemGuide.ts`, which is every screen written out with the exact words
22
+ * printed on its controls. The persona quotes those words rather than describing them, and the
23
+ * phases stop it wandering: it says which one it is in, and two of the five are skipped outright
24
+ * when there is nothing in them to do.
25
+ *
26
+ * The phases say what to do; they no longer say what to ask. Every message that asks the operator
27
+ * anything lives in `script.ts`, word for word and numbered, and is printed here as a quotation to
28
+ * copy — because a brief that described its questions got a differently worded, differently grouped
29
+ * and differently lettered menu from every conversation it was pasted into. The prose around the
30
+ * script keeps the reasons: why names are not asked, why a persona is validated rather than
31
+ * rewritten, what each setting does when the operator asks.
32
+ *
33
+ * The example below is parsed by the plan tests, so a field renamed in `schema.ts` without
34
+ * being renamed here fails the check rather than quietly teaching every future plan the wrong
35
+ * shape.
36
+ */
37
+ import { PLAN_VERSION } from './schema.js';
38
+ import { systemGuideSection } from './systemGuide.js';
39
+ import { scriptSection, renderMessage, SCRIPT, OPENING } from './script.js';
40
+ function projectsSectionEn(projects) {
41
+ if (projects.length === 0)
42
+ return '';
43
+ const lines = projects.map((p) => {
44
+ // The default project's own name when the operator gave it one: they will use that word
45
+ // for it in the conversation, and a plan that answers with "the default project" is a plan
46
+ // written about something nobody says out loud.
47
+ const label = p.isDefault ? (p.name ? `${p.name} — the default; new sessions start here` : 'Default project (new sessions start here)') : p.name;
48
+ return `- **${label}**: \`${p.rootDir}\` — ${p.repo ? 'a git repository, so `vcs.repoDir` may point at it' : 'not a git repository: `vcs` must be off for work here'}`;
49
+ });
50
+ return `
51
+ ## Projects on this machine
52
+
53
+ The operator has told the system where they work. Use these paths exactly as written; do not
54
+ ask for them again, and do not invent others. Still ask which of them this work is about — a
55
+ plan may touch one, several or all of them — and put each session in the folder its work is in.
56
+
57
+ **\`vcs.repoDir\` is one of these paths, exactly — never a folder inside one.** A repository is the
58
+ folder with \`.git\` in it, and these are the ones that have it; a subfolder does not, and a plan that
59
+ names one is refused when it is checked. When the work lives in a folder inside the repository — an
60
+ \`api\` or a \`web\` — the repository stays \`repoDir\`, and the folder is said where it matters: with
61
+ absolute paths in the task's prompt, and as \`cwd\` on the checks. The operator's own documents may
62
+ give a project by a subfolder; the list here is what the machine actually has, and it wins.
63
+
64
+ ${lines.join('\n')}
65
+ `;
66
+ }
67
+ function projectsSectionBg(projects) {
68
+ if (projects.length === 0)
69
+ return '';
70
+ const lines = projects.map((p) => {
71
+ const label = p.isDefault ? (p.name ? `${p.name} — по подразбиране; новите сесии тръгват тук` : 'Проект по подразбиране (новите сесии тръгват тук)') : p.name;
72
+ return `- **${label}**: \`${p.rootDir}\` — ${p.repo ? 'git хранилище, така че `vcs.repoDir` може да сочи към него' : 'не е git хранилище: `vcs` трябва да е изключен за работа тук'}`;
73
+ });
74
+ return `
75
+ ## Проектите на тази машина
76
+
77
+ Потребителят е казал на системата къде работи. Ползвай тези пътища точно както са написани;
78
+ не ги питай пак и не измисляй други. Все пак питай за кои от тях е тази работа — един план
79
+ може да засяга един, няколко или всички — и сложи всяка сесия в папката, в която е нейната работа.
80
+
81
+ **\`vcs.repoDir\` е един от тези пътища, точно — никога папка вътре в него.** Хранилище е папката, в
82
+ която има \`.git\`, а тези са онези, които го имат; подпапка няма, и план, който назовава подпапка, се
83
+ отказва при проверката. Когато работата е в папка вътре в хранилището — \`api\` или \`web\` —
84
+ хранилището остава \`repoDir\`, а папката се казва там, където има значение: с абсолютни пътища в
85
+ текста на задачата и като \`cwd\` на проверките. Документите на оператора може да дават проект чрез
86
+ подпапка; списъкът тук е това, което машината наистина има, и той е меродавен.
87
+
88
+ ${lines.join('\n')}
89
+ `;
90
+ }
91
+ /**
92
+ * Whether a run with nobody watching can start on this machine — and when it cannot, what to press.
93
+ *
94
+ * Kerrigan's phase 2 script offers the unattended button first, because on a machine that allows it
95
+ * that is the ordinary way to run. On one that does not — no isolation claimed, or no allowlist —
96
+ * the runner refuses it at the entrance, and a persona that recommends it has sent the operator to
97
+ * a button that says no. She cannot see the machine; the app can, so it tells her, and only when it
98
+ * matters. The labels are the ones on the screens, so check:guide holds them.
99
+ */
100
+ function machineSection(unattendedBlocked, lang) {
101
+ if (!unattendedBlocked)
102
+ return '';
103
+ const whyBg = unattendedBlocked === 'isolation'
104
+ ? '**„Къде върви ботът“** в `/defaults` още казва, че ботът върви в акаунта на оператора, без изолация'
105
+ : 'списъкът с позволени програми е празен, а без него нищо не ограничава стъпка, която никой не гледа';
106
+ const whyEn = unattendedBlocked === 'isolation'
107
+ ? '**"Where the bot runs"** on `/defaults` still says the bot runs in the operator\'s own account, with no isolation'
108
+ : 'the list of allowed programs is empty, and without it nothing limits a step that nobody is watching';
109
+ return lang === 'bg'
110
+ ? `
111
+ ## На тази машина пускане без надзор се отказва
112
+
113
+ В момента runner-ът няма да стартира пускане, при което никой не гледа: ${whyBg}. Затова навсякъде, където
114
+ стъпките предлагат да се върви без питане или с питане преди всяка команда, казвай на оператора да
115
+ избере питането: **„Стъпка по стъпка“** вместо **„Пусни {n} сесия(и)“**, **„Продължи, с питане преди
116
+ всяка команда“** вместо **„Продължи без да пита“**, и **„Изпълни“** вместо **„Изпълни без да питаш
117
+ повече“** на стъпка, която чака одобрение. Кажи веднъж защо, с един ред — и не го съветвай да промени
118
+ настройката, за да мине: тя е твърдение за нещо, което той трябва наистина да е направил.
119
+ `
120
+ : `
121
+ ## On this machine, runs with nobody watching are refused
122
+
123
+ Right now the runner will not start a run that nobody is watching: ${whyEn}. So wherever the
124
+ steps offer running without being asked or asking before each command, tell the operator to take
125
+ the one that asks: **"Step by step"** rather than **"Run {n} session(s)"**, **"Continue, asking before
126
+ each command"** rather than **"Continue without asking"**, and **"Run"** rather than **"Run this and
127
+ the rest without asking"** on a step waiting for approval. Say why once, in one line — and do not
128
+ advise changing the setting to get past it: it is a statement about something they must actually
129
+ have arranged.
130
+ `;
131
+ }
132
+ /**
133
+ * Step 10 of phase 2, and the line that ends it, written for this machine.
134
+ *
135
+ * The section above tells Kerrigan which button to recommend where a run with nobody watching is
136
+ * refused, but the phase 2 script beside it still offered both, and a live conversation copied the
137
+ * script: "press Run 1 session(s) to let it work without being asked, or Step by step" — the first of
138
+ * which this machine refuses. A script the model copies verbatim has to be right as written.
139
+ */
140
+ function runStepEn(blocked) {
141
+ return blocked
142
+ ? '10. Press **"Step by step"** and approve every command; **"Run {n} session(s)"** is refused on this machine.'
143
+ : '10. Press **"Run {n} session(s)"** to let it work without being asked, or **"Step by step"** to\n approve every command.';
144
+ }
145
+ function runStepBg(blocked) {
146
+ return blocked
147
+ ? '10. Натисни **„Стъпка по стъпка“** и одобрявай всяка команда; **„Пусни {n} сесия(и)“** се отказва на тази машина.'
148
+ : '10. Натисни **„Пусни {n} сесия(и)“**, за да върви без питане, или **„Стъпка по стъпка“**, за да\n одобряваш всяка команда.';
149
+ }
150
+ function runNextEn(blocked) {
151
+ return blocked ? 'press **"Step by step"**' : 'press **"Run {n} session(s)"**';
152
+ }
153
+ function runNextBg(blocked) {
154
+ return blocked ? 'да натисне **„Стъпка по стъпка“**' : 'да натисне **„Пусни {n} сесия(и)“**';
155
+ }
156
+ /**
157
+ * A filled-in plan, used as the example in every language.
158
+ *
159
+ * Deliberately not minimal: it shows two sessions, a dependent and an independent one, one
160
+ * that does version control and one that does not, because a model shown only the required
161
+ * fields writes plans that use only the required fields. The second session is the whole
162
+ * lesson about `vcs`: it is written out with `enabled: false` rather than left off, because
163
+ * leaving it off is what this format refuses.
164
+ */
165
+ export function planExample() {
166
+ const root = 'C:\\Projects\\billing';
167
+ const firstTasks = [
168
+ {
169
+ title: 'csv-writer',
170
+ prompt: `In ${root}, add a CSV writer for invoices at src/invoices/csv.ts. It takes the existing Invoice[] ` +
171
+ 'type and returns a string with a header row and one row per invoice, with the columns number, date, ' +
172
+ 'customer, net, vat, gross. Amounts use a dot as the decimal separator and no thousands separator. Do not ' +
173
+ 'wire it into anything yet. Add a unit test named exactly "two invoices give the six columns in order".',
174
+ expected: '`npx tsc --noEmit` passes and a new unit test that writes two invoices produces exactly the six columns ' +
175
+ 'above, in that order.',
176
+ level2: '',
177
+ checks: [
178
+ { name: 'typescript compiles', expect: 'exit-zero', run: 'npx tsc --noEmit', cwd: root },
179
+ { name: 'the writer exists', expect: 'file-exists', file: `${root}\\src\\invoices\\csv.ts` },
180
+ { name: 'the unit tests pass', expect: 'exit-zero', run: 'npm test', cwd: root },
181
+ // The check that proves the behaviour: the test named after the criterion, seen passing.
182
+ // The three above would all pass with the writer empty, and the importer refuses a task
183
+ // whose checks are only of those kinds.
184
+ {
185
+ name: 'two invoices give the six columns in order',
186
+ expect: 'output-contains',
187
+ run: 'npm test',
188
+ cwd: root,
189
+ value: '✔ two invoices give the six columns in order',
190
+ },
191
+ ],
192
+ vcs: { branch: 'invoice-csv-writer', commitMessage: 'Add a CSV writer for invoices' },
193
+ },
194
+ {
195
+ title: 'export-endpoint',
196
+ prompt: 'Expose the CSV writer from task 1 as GET /invoices/export in the existing Express router at ' +
197
+ 'src/invoices/router.ts. It must be behind the feature flag INVOICE_CSV_EXPORT, off by default, and ' +
198
+ 'respond with content-type text/csv and a content-disposition filename of invoices.csv.',
199
+ expected: 'With the flag off the endpoint returns 404; with it on it returns 200, text/csv and the header row. ' +
200
+ '`npm test` passes.',
201
+ level2: '',
202
+ vcs: {
203
+ branch: 'invoice-csv-endpoint',
204
+ commitMessage: 'Expose the invoice CSV export endpoint\n\nBehind INVOICE_CSV_EXPORT, which stays off by default.',
205
+ },
206
+ },
207
+ ];
208
+ const secondTasks = [
209
+ {
210
+ title: 'outdated-deps',
211
+ prompt: `In ${root}, run \`npm outdated\` and report the result. Change nothing. Quote the full table in ` +
212
+ 'the summary and say which of the outdated packages are a major version behind.',
213
+ expected: 'The summary contains the table verbatim and a list of the major-version-behind packages.',
214
+ level2: '',
215
+ checks: [{ name: 'nothing in the repository changed', expect: 'output-omits', run: 'git --no-pager status --porcelain', cwd: root, value: 'M ' }],
216
+ },
217
+ {
218
+ review: false,
219
+ title: 'test-duration',
220
+ prompt: `In ${root}, run \`npm test\` once and report how long it took and how many tests ran. Change ` +
221
+ 'nothing. If the suite fails, report the failures rather than fixing them.',
222
+ expected: 'The summary states the duration, the number of tests, and the pass or fail result.',
223
+ level2: '',
224
+ },
225
+ ];
226
+ return {
227
+ version: PLAN_VERSION,
228
+ plan: 'Invoice export, first pass',
229
+ notes: 'The user confirmed the service already has a working test suite. Session 2 is deliberately independent: ' +
230
+ 'the two checks in it say nothing about each other.',
231
+ onFailure: 'continue',
232
+ conversation: 'per-session',
233
+ sessions: [
234
+ {
235
+ name: 'invoice-export',
236
+ goal: 'Add CSV export to the invoice service, behind the existing feature-flag mechanism.',
237
+ model: '',
238
+ onFailure: 'stop',
239
+ level2: `TypeScript, Node 20, npm. The project is at ${root}. Run tests with \`npm test\`. Follow the ` +
240
+ 'existing folder layout; do not add dependencies without saying why in the summary.',
241
+ vcs: {
242
+ enabled: true,
243
+ repoDir: root,
244
+ branchMode: 'per-task',
245
+ commitOnFinish: true,
246
+ branchPrefix: 'cop/',
247
+ },
248
+ review: { enabled: true, model: '' },
249
+ mirror: {
250
+ enabled: true,
251
+ rootDir: root,
252
+ includeDirs: ['src/invoices'],
253
+ excludeDirs: [],
254
+ respectGitignore: true,
255
+ includeEnvFiles: false,
256
+ },
257
+ tasks: firstTasks,
258
+ },
259
+ {
260
+ name: 'billing-health',
261
+ goal: 'Two independent checks on the billing service, run for the record.',
262
+ model: '',
263
+ onFailure: 'continue',
264
+ level2: `Read-only work. Do not change any file in ${root}.`,
265
+ vcs: { enabled: false, repoDir: '', branchMode: 'per-task', commitOnFinish: false, branchPrefix: 'cop/' },
266
+ tasks: secondTasks,
267
+ },
268
+ ],
269
+ };
270
+ }
271
+ const FIELD_ROWS_EN = [
272
+ ['version', 'top', 'yes', `Always ${PLAN_VERSION}.`],
273
+ ['plan', 'top', 'no, but ask for one', 'A short name for the run. The register groups the tasks under it and the exported files are named after it.'],
274
+ ['notes', 'top', 'no', 'Assumptions you made, open questions, why the order is what it is. The operator reads this.'],
275
+ [
276
+ 'onFailure',
277
+ 'top',
278
+ 'yes',
279
+ 'What a run of these sessions does when a whole **session** fails. "stop" = the plan is one piece of work in order. "continue" = the sessions are independent of each other.',
280
+ ],
281
+ [
282
+ 'conversation',
283
+ 'top',
284
+ 'yes',
285
+ '"per-session" = each session gets its own Copilot chat, which is the usual answer. "shared" = all of them talk in one chat and see each other\'s history.',
286
+ ],
287
+ ['sessions', 'top', 'yes', 'At least one. Each session is one Copilot conversation, unless the plan shares them.'],
288
+ ['name', 'session', 'yes', 'Short, latin letters, digits and hyphens.'],
289
+ ['goal', 'session', 'no', 'One or two sentences: what this whole session is for.'],
290
+ ['model', 'session', 'no', 'The chat model by the exact name the picker shows. Leave "" unless the user named one.'],
291
+ [
292
+ 'onFailure',
293
+ 'session',
294
+ 'yes',
295
+ '"stop" = the tasks are one chain, a failure leaves the rest queued. "continue" = the tasks are independent.',
296
+ ],
297
+ [
298
+ 'level2',
299
+ 'session',
300
+ 'no',
301
+ 'Project and team instructions for every task in this session: language, tooling, conventions, how to run the tests, what not to touch.',
302
+ ],
303
+ [
304
+ 'conversationGroup',
305
+ 'session',
306
+ 'no',
307
+ 'Overrides the setting above for this one session: give the same name to the sessions that should share a chat, and leave it "" for a session that should have its own.',
308
+ ],
309
+ [
310
+ 'review',
311
+ 'session',
312
+ 'no',
313
+ 'Whether a second, independent conversation checks the work by running it. On when left out. `{ "enabled": true, "model": "" }`; a different model makes the review better.',
314
+ ],
315
+ ['review', 'task', 'no', 'Set to `false` to skip the review for one task. Absent means the session decides.'],
316
+ ['readOnly', 'task', 'no', '`true` for a task that must not change files — an audit, a smoke test, a report. The runner fails it if the tree changed, and still commits the change on its branch so nothing is lost.'],
317
+ ['mirror', 'session', 'no, but ask', 'Whether project files are copied and attached to the first message as context, and which: root, directories in and out, gitignore, env files. Off when left out.'],
318
+ ['tasks', 'session', 'yes', 'At least one, in the order they must run.'],
319
+ ['title', 'task', 'yes', '3 to 120 characters. Short, latin, hyphenated.'],
320
+ ['prompt', 'task', 'yes', 'The task itself, at least 30 characters. This is what Copilot reads.'],
321
+ [
322
+ 'expected',
323
+ 'task',
324
+ 'no, but always write it',
325
+ 'What must be true once the task is done. This is the bar the operator holds the result to.',
326
+ ],
327
+ ['level2', 'task', 'no', "Instructions for this one task, replacing the session's. Leave \"\" to inherit."],
328
+ [
329
+ 'checks',
330
+ 'task',
331
+ 'no, but write them wherever you can',
332
+ 'The same bar as `expected`, written so the runner can decide it. See below: if they fail, the task is sent back to you to fix.',
333
+ ],
334
+ ];
335
+ const FIELD_ROWS_BG = [
336
+ ['version', 'горе', 'да', `Винаги ${PLAN_VERSION}.`],
337
+ ['plan', 'горе', 'не, но го поискай', 'Кратко име на пускането. Регистърът групира задачите под него, а изтеглените файлове носят името му.'],
338
+ ['notes', 'горе', 'не', 'Допусканията, които си направил, отворените въпроси, защо редът е такъв. Операторът чете това.'],
339
+ [
340
+ 'onFailure',
341
+ 'горе',
342
+ 'да',
343
+ 'Какво прави изпълнението на тези сесии, когато цяла **сесия** се провали. "stop" = планът е една работа, подредена по ред. "continue" = сесиите са независими една от друга.',
344
+ ],
345
+ [
346
+ 'conversation',
347
+ 'горе',
348
+ 'да',
349
+ '"per-session" = всяка сесия получава собствен чат с Copilot, което е обичайният отговор. "shared" = всички говорят в един чат и виждат историята си една на друга.',
350
+ ],
351
+ ['sessions', 'горе', 'да', 'Поне една. Всяка сесия е един разговор с Copilot, освен ако планът не ги обедини.'],
352
+ ['name', 'сесия', 'да', 'Кратко, латиница, цифри и тирета.'],
353
+ ['goal', 'сесия', 'не', 'Едно-две изречения: за какво служи цялата сесия.'],
354
+ ['model', 'сесия', 'не', 'Моделът на чата, с точното име от менюто. Остави "", освен ако потребителят не е посочил.'],
355
+ [
356
+ 'onFailure',
357
+ 'сесия',
358
+ 'да',
359
+ '"stop" = задачите са верига, при провал останалите остават в опашката. "continue" = задачите са независими.',
360
+ ],
361
+ [
362
+ 'level2',
363
+ 'сесия',
364
+ 'не',
365
+ 'Инструкции за проекта и екипа, валидни за всяка задача в сесията: език, инструменти, конвенции, как се пускат тестовете, какво да не се пипа.',
366
+ ],
367
+ [
368
+ 'conversationGroup',
369
+ 'сесия',
370
+ 'не',
371
+ 'Отменя настройката горе само за тази сесия: дай едно и също име на сесиите, които да споделят чат, и остави "" на онази, която да е сама.',
372
+ ],
373
+ [
374
+ 'review',
375
+ 'сесия',
376
+ 'не',
377
+ 'Дали втори, независим разговор проверява работата, като я изпълни. Включено, ако се пропусне. `{ "enabled": true, "model": "" }`; друг модел прави рецензията по-добра.',
378
+ ],
379
+ ['review', 'задача', 'не', 'Сложи `false`, за да се пропусне рецензията само за тази задача. Липсата значи каквото казва сесията.'],
380
+ ['readOnly', 'задача', 'не', '`true` за задача, която не бива да променя файлове — одит, smoke тест, доклад. Runner-ът я проваля, ако дървото е променено, и пак комитва промяната на клона ѝ, за да не се губи нищо.'],
381
+ ['mirror', 'сесия', 'не, но питай', 'Дали файлове от проекта се копират и прикачат към първото съобщение като контекст, и кои: корен, директории вътре и вън, gitignore, env файлове. Изключено, когато липсва.'],
382
+ ['tasks', 'сесия', 'да', 'Поне една, в реда, в който трябва да се изпълнят.'],
383
+ ['title', 'задача', 'да', 'От 3 до 120 знака. Кратко, латиница, с тирета.'],
384
+ ['prompt', 'задача', 'да', 'Самата задача, поне 30 знака. Това чете Copilot.'],
385
+ [
386
+ 'expected',
387
+ 'задача',
388
+ 'не, но винаги го пиши',
389
+ 'Какво трябва да е вярно, след като задачата приключи. Това е мярката, с която операторът мери резултата.',
390
+ ],
391
+ ['level2', 'задача', 'не', 'Инструкции само за тази задача, вместо тези на сесията. Остави "", за да наследи.'],
392
+ [
393
+ 'checks',
394
+ 'задача',
395
+ 'не, но пиши ги, където можеш',
396
+ 'Същата мярка като `expected`, но написана така, че runner-ът да я реши сам. Виж по-долу: ако не минат, задачата се връща при теб за поправка.',
397
+ ],
398
+ ];
399
+ const VCS_ROWS_EN = [
400
+ ['vcs', 'session', 'yes, always', 'Whether the runner does version control for this session, and where. See below. A session without it is refused.'],
401
+ ['vcs.branch', 'task', 'yes in per-task mode', 'The branch this task works on, without the prefix.'],
402
+ ['vcs.commitMessage', 'task', 'yes', 'The commit this task ends with. Imperative, first line under 72 characters.'],
403
+ ];
404
+ const VCS_ROWS_BG = [
405
+ ['vcs', 'сесия', 'да, винаги', 'Дали runner-ът прави контрол на версиите за тази сесия и къде. Виж по-долу. Сесия без него се отказва.'],
406
+ ['vcs.branch', 'задача', 'да, в режим per-task', 'Клонът, по който работи задачата, без представката.'],
407
+ ['vcs.commitMessage', 'задача', 'да', 'Комитът, с който задачата завършва. В повелително наклонение, първият ред под 72 знака.'],
408
+ ];
409
+ function table(header, rows) {
410
+ return [`| ${header.join(' | ')} |`, `|${header.map(() => '---').join('|')}|`, ...rows.map((r) => `| ${r.join(' | ')} |`)].join('\n');
411
+ }
412
+ const VCS_DETAIL_EN = `
413
+ **vcs on a session**: \`enabled\` (true here), \`repoDir\` (absolute path to the git repository),
414
+ \`branchMode\`, \`commitOnFinish\` (true unless the user says otherwise), \`branchPrefix\` (leave it
415
+ "cop/"), and \`branchName\` — used **only** in per-session mode, for the one branch the whole
416
+ session works on, without the prefix.
417
+
418
+ **branchMode** is a real choice and you should make it deliberately:
419
+
420
+ - \`"per-task"\` — every task branches from the same starting commit, so no task sees what the
421
+ one before it changed. Use it for independent checks or alternatives. Give **each task** a
422
+ \`vcs.branch\`. A task that reads, checks or documents what an earlier task wrote does
423
+ **not** belong in a per-task session: it will not find the files.
424
+ - \`"per-session"\` — one branch for the whole queue, each task building on the last. Use it for
425
+ work that is one change made in steps. Give **the session** a \`vcs.branchName\` and leave the
426
+ tasks' \`vcs.branch\` out; it is ignored in this mode.
427
+
428
+ Either way, give every task a \`vcs.commitMessage\`. The runner commits after the task with that
429
+ message as the subject and Copilot's own summary underneath it, so write the subject the way a
430
+ commit is written: imperative, short, about the change rather than about the task.
431
+
432
+ **A session that should not touch git** still needs the field, written out in full:
433
+ \`"vcs": { "enabled": false, "repoDir": "", "branchMode": "per-task", "commitOnFinish": false, "branchPrefix": "cop/" }\`.
434
+ Then give its tasks no \`vcs\` at all — no branch, no commit message — because there is nothing
435
+ to name. Read-only work, audits and reports are the usual reason. Say in \`notes\` why you turned
436
+ it off, so the operator can disagree with you.
437
+
438
+ **Do not leave \`vcs\` out of a session.** The system refuses the whole document and hands the
439
+ user a line saying so, and you will have to ask the question then anyway — after wasting their
440
+ time. Ask it before you write any JSON.
441
+ `.trim();
442
+ const VCS_DETAIL_BG = `
443
+ **vcs на сесия**: \`enabled\` (тук true), \`repoDir\` (абсолютен път до git хранилището),
444
+ \`branchMode\`, \`commitOnFinish\` (true, освен ако потребителят не каже друго), \`branchPrefix\`
445
+ (остави "cop/") и \`branchName\` — използва се **само** в режим per-session, за единствения клон,
446
+ по който работи цялата сесия, без представката.
447
+
448
+ **branchMode** е истински избор и трябва да го направиш съзнателно:
449
+
450
+ - \`"per-task"\` — всяка задача тръгва от един и същ начален комит и не вижда какво е променила
451
+ предишната. За независими проверки или алтернативи. Дай на **всяка задача** \`vcs.branch\`.
452
+ Задача, която чете, проверява или документира написаното от по-ранна задача, **не** е за
453
+ per-task сесия: няма да намери файловете.
454
+ - \`"per-session"\` — един клон за цялата опашка, всяка задача стъпва върху предишната. За работа,
455
+ която е една промяна, направена на стъпки. Дай на **сесията** \`vcs.branchName\` и не пиши
456
+ \`vcs.branch\` по задачите; в този режим се игнорира.
457
+
458
+ И в двата случая давай на всяка задача \`vcs.commitMessage\`. Runner-ът комитва след задачата с
459
+ този текст като заглавен ред, а отдолу слага резюмето на Copilot — затова пиши заглавния ред
460
+ както се пише комит: повелително, кратко, за промяната, не за задачата.
461
+
462
+ **Сесия, която не бива да пипа git**, пак иска полето, изписано изцяло:
463
+ \`"vcs": { "enabled": false, "repoDir": "", "branchMode": "per-task", "commitOnFinish": false, "branchPrefix": "cop/" }\`.
464
+ На нейните задачи не давай \`vcs\` изобщо — нито клон, нито текст на комит — защото няма какво да
465
+ се именува. Обичайната причина е работа само за четене, одит или отчет. Напиши в \`notes\` защо си
466
+ го изключил, за да може операторът да не се съгласи с теб.
467
+
468
+ **Не пропускай \`vcs\` в сесия.** Системата отказва целия документ и връща на потребителя ред,
469
+ който го казва, а ти пак ще трябва да зададеш въпроса — след като си му загубил времето. Задай
470
+ го, преди да си написал какъвто и да е JSON.
471
+ `.trim();
472
+ const CHECKS_EN = `
473
+ **checks** is what makes \`expected\` enforceable. \`expected\` is a sentence a person reads;
474
+ a check is the same claim written so this runner can settle it on its own, with no model
475
+ involved. When a task has checks, Copilot saying "done" is not the end of it: the runner runs
476
+ them, and if any fails the failure goes back into the chat with its output attached and the
477
+ task carries on until they pass or it runs out of attempts.
478
+
479
+ Each check is \`{ "name": "...", "expect": "...", ... }\`, where \`expect\` is one of:
480
+
481
+ | expect | Needs | Passes when |
482
+ |---|---|---|
483
+ | exit-zero | run | the command exits 0 |
484
+ | exit-nonzero | run | the command exits non-zero — for proving something is refused |
485
+ | output-contains | run, value | the command's output contains value |
486
+ | output-omits | run, value | it does not contain value |
487
+ | output-matches | run, value | it matches value as a regular expression |
488
+ | file-exists | file | the file is there |
489
+ | file-missing | file | it is not |
490
+ | file-contains | file, value | the file contains value |
491
+
492
+ \`run\` may also take \`cwd\` and \`shell\` ("pwsh", "powershell" or "cmd"). Leave \`shell\` out unless the
493
+ command needs a particular one: the runner then picks the best shell the machine actually has, and a
494
+ check that names one the machine has not got ends the task. Write checks that are cheap and
495
+ certain: a compile, a test run, a file that must exist, a string that must appear. Do not try
496
+ to check things that need judgement — "the code is clean", "the summary is good" — because
497
+ nothing here can decide them, and a check that cannot fail is worse than no check.
498
+
499
+ **Ask of every check: would it pass if the task did nothing?** If it would, it proves nothing about
500
+ this task, and it is not a check of it. The traps that look like proof and are not:
501
+
502
+ - **A test run's exit code alone.** A test runner with no tests, or with only the old ones, exits 0.
503
+ Make the check fail without the new tests: require at least as many passing tests as the criteria
504
+ this task covers (with \`node --test\`, \`output-matches\` on \`pass ([5-9]|\\d{2,})\` for five), and
505
+ \`fail 0\` in the output.
506
+ - **A file that exists.** It proves the file, not what is in it. Pair it with a check that runs the
507
+ behaviour, or with \`file-contains\` on the one thing only the new work would put there.
508
+ - **Something that was already true.** An old test passing, a folder already there. Checked before the
509
+ task, the answer would have been the same.
510
+
511
+ **The recipe, criterion by criterion.** Tell the task, in its prompt, to name each test after the
512
+ criterion it proves — the criterion's own words. Then the check for that criterion is
513
+ \`output-contains\` on the test run with that name as it appears when the test passes: with
514
+ \`node --test\` a passing test's line starts with \`✔ \`, so the value is \`✔ \` and the name. That check
515
+ fails while the test is missing and while it fails, and passes only when that criterion holds. A
516
+ check a task's plan cannot do without: **a plan in which every check of a task is \`file-exists\`,
517
+ \`file-missing\` or \`exit-zero\` is refused when it is checked**, because all three pass with the
518
+ work not done.
519
+ Every criterion of the assignment is covered by at least one check that fails until the work is done.
520
+ `.trim();
521
+ const CHECKS_BG = `
522
+ **checks** е това, което прави \`expected\` проверимо. \`expected\` е изречение, което човек чете;
523
+ проверката е същото твърдение, написано така, че runner-ът да го реши сам, без никакъв модел.
524
+ Когато задачата има проверки, „done“ от Copilot не я приключва: runner-ът ги изпълнява и ако
525
+ някоя не мине, провалът се връща в чата с изхода си прикачен, а задачата продължава, докато не
526
+ минат или не ѝ свършат опитите.
527
+
528
+ Всяка проверка е \`{ "name": "...", "expect": "...", ... }\`, където \`expect\` е едно от:
529
+
530
+ | expect | Иска | Минава, когато |
531
+ |---|---|---|
532
+ | exit-zero | run | командата излиза с 0 |
533
+ | exit-nonzero | run | командата излиза с различно от 0 — за да се докаже, че нещо се отказва |
534
+ | output-contains | run, value | изходът на командата съдържа value |
535
+ | output-omits | run, value | не го съдържа |
536
+ | output-matches | run, value | съвпада с value като регулярен израз |
537
+ | file-exists | file | файлът го има |
538
+ | file-missing | file | няма го |
539
+ | file-contains | file, value | файлът съдържа value |
540
+
541
+ При \`run\` може да зададеш и \`cwd\`, и \`shell\` ("pwsh", "powershell" или "cmd"). Не пиши \`shell\`, освен
542
+ ако командата не иска точно определен: тогава runner-ът избира най-добрия, който машината наистина има,
543
+ а проверка, назовала липсващ, проваля задачата. Пиши проверки, които са
544
+ евтини и сигурни: компилация, пускане на тестове, файл, който трябва да съществува, низ, който
545
+ трябва да се появи. Не се опитвай да проверяваш неща, които искат преценка — „кодът е чист“,
546
+ „резюмето е добро“ — защото нищо тук не може да ги реши, а проверка, която не може да падне, е
547
+ по-лоша от никаква проверка.
548
+
549
+ **За всяка проверка се питай: би ли минала, ако задачата не направи нищо?** Ако би, тя не доказва
550
+ нищо за тази задача и не е нейна проверка. Капаните, които изглеждат като доказателство, а не са:
551
+
552
+ - **Само кодът на изход от тестовете.** Тестове без нито един тест или само със старите излизат с 0.
553
+ Направи проверката такава, че да пада без новите тестове: поне толкова минаващи теста, колкото
554
+ критерии покрива задачата (с \`node --test\` — \`output-matches\` на \`pass ([5-9]|\\d{2,})\` за пет), и
555
+ \`fail 0\` в изхода.
556
+ - **Файл, който съществува.** Доказва файла, не съдържанието му. Сложи до нея проверка, която пуска
557
+ поведението, или \`file-contains\` с единственото нещо, което само новата работа би сложила там.
558
+ - **Нещо, което и без това е вярно.** Стар тест, който минава, папка, която вече я има. Проверено
559
+ преди задачата, отговорът щеше да е същият.
560
+
561
+ **Рецептата, критерий по критерий.** Кажи на задачата в текста ѝ да кръсти всеки тест с думите на
562
+ критерия, който доказва. Тогава проверката за този критерий е \`output-contains\` върху пускането на
563
+ тестовете с това име, както излиза, когато тестът мине: с \`node --test\` редът на минал тест започва с
564
+ \`✔ \`, така че стойността е \`✔ \` и името. Такава проверка пада, докато тестът липсва и докато пада, и
565
+ минава само когато критерият е изпълнен. Без това не може: **план, в който всички проверки на някоя
566
+ задача са \`file-exists\`, \`file-missing\` или \`exit-zero\`, се отказва при проверката**, защото и
567
+ трите минават без свършена работа.
568
+ Всеки критерий от заданието е покрит поне от една проверка, която пада, докато работата не е свършена.
569
+ `.trim();
570
+ const MIRROR_EN = `
571
+ **mirror**: \`enabled\`, \`rootDir\` (absolute), \`includeDirs\` and \`excludeDirs\` (paths relative to
572
+ the root, forward slashes), \`respectGitignore\` (leave it true), \`includeEnvFiles\` (leave it false
573
+ unless the user insists — it copies secrets into a chat).
574
+ `.trim();
575
+ const MIRROR_BG = `
576
+ **mirror**: \`enabled\`, \`rootDir\` (абсолютен), \`includeDirs\` и \`excludeDirs\` (пътища спрямо корена,
577
+ с наклонени черти напред), \`respectGitignore\` (остави го true), \`includeEnvFiles\` (остави го false,
578
+ освен ако потребителят не настоява — копира тайни в чат).
579
+ `.trim();
580
+ const VCS_RULE_EN = '- **Version control is yours to ask about, and there is no default.** With it on, the runner makes a branch\n' +
581
+ ' before each task and a commit after it, using the names you give, and it never pushes; Copilot must not touch\n' +
582
+ ' git itself. With it off, files are changed where they lie and there is no way back. Ask the user which it is,\n' +
583
+ ' and which git repository the work happens in, and write both into every session.';
584
+ const VCS_RULE_BG = '- **Контролът на версиите е нещо, за което трябва да питаш, и няма стойност по подразбиране.** Когато е\n' +
585
+ ' включен, runner-ът прави клон преди всяка задача и комит след нея, с имената, които ти зададеш, и никога не\n' +
586
+ ' пуши; самият Copilot не бива да пипа git. Когато е изключен, файловете се променят на място и няма връщане\n' +
587
+ ' назад. Питай потребителя кое от двете е и в кое git хранилище се работи, и запиши и двете във всяка сесия.';
588
+ /**
589
+ * The rule that makes the embedded guide worth its length.
590
+ *
591
+ * A model handed a list of labels will still paraphrase them, because paraphrasing is what
592
+ * language models do to text they are shown. So the guide arrives behind an instruction rather
593
+ * than as an appendix: quote the words, name the route, and do not invent a control — the last of
594
+ * those being the failure that costs the operator the most, since they go looking for a button
595
+ * that has never existed and conclude the instructions are for a different version.
596
+ */
597
+ const GUIDE_RULE_EN = `
598
+ ## Speak in the words on the screen
599
+
600
+ The section that follows is this application, screen by screen, with every control written exactly
601
+ as it is printed on it. Use those words. Say press **"Create the sessions and tasks"**, not
602
+ "import the plan": the operator is looking at a page full of labels and cannot tell which of them
603
+ your paraphrase means. Quote the label, and name the page by the route in the heading above it.
604
+
605
+ **Never send the operator to a control that is not listed there.** If what you want done has no
606
+ button in the guide, say so and ask what they can see, rather than inventing one. A label written
607
+ with {braces} is filled in with a number or a name at the time, so quote it with the braces and
608
+ say what will be in them.
609
+ `.trim();
610
+ const GUIDE_RULE_BG = `
611
+ ## Говори с думите от екрана
612
+
613
+ Разделът по-долу е това приложение, екран по екран, с всеки контрол, изписан точно както стои на
614
+ него. Ползвай тези думи. Казвай натисни **„Създай сесиите и задачите“**, а не „внеси плана“:
615
+ операторът гледа страница, пълна с надписи, и няма как да отгатне кой от тях имаш предвид. Цитирай
616
+ надписа и назовавай страницата с маршрута от заглавието над него.
617
+
618
+ **Никога не пращай оператора към контрол, който не е изброен там.** Ако това, което искаш да се
619
+ направи, няма бутон в справочника, кажи го и попитай какво вижда, вместо да си измислиш. Надпис с
620
+ {скоби} се попълва с число или име в момента — цитирай го със скобите и казвай какво ще има в тях.
621
+ `.trim();
622
+ /**
623
+ * The persona's two later roles, software level: what the operator brings back when a task
624
+ * did not end done and how to read it, and how to check the finished run against the ticket.
625
+ * Fixed text: it describes this runner's exports, failure words and buttons, which the operator
626
+ * cannot change; the organisation's own part is theirs and comes from a file.
627
+ *
628
+ * Both are phases now rather than an appendix, and phase 3 carries the distinction the operator
629
+ * asked for: a failure is either something a new prompt fixes or something only a change to the
630
+ * bot fixes, and saying which is not optional. A prompt rewritten around a hole in the runner
631
+ * leaves the hole there for every plan after it, and nobody but the persona ever saw it.
632
+ */
633
+ const PHASE3_EN = `
634
+ ## Phase 3 — a task did not end done
635
+
636
+ Skipped entirely when every task ended **"done"**: say so in one line and go to phase 4.
637
+
638
+ Otherwise: diagnose first, propose second, and say which kind of proposal it is.
639
+
640
+ **Ask for the exports**, with the record message from the script. Every row on \`/history\` carries
641
+ three links, each for that one task:
642
+
643
+ - **"plan"** — the sessions and tasks as they are now, in this format, with the edits made in the
644
+ interface. It imports again.
645
+ - **"work"** — what each task asked, what the chat tried (every round, every command with its
646
+ exit code), what was actually done to the repository (branch, commit, files), deviations and
647
+ disputes, the review verdict with its findings, and \`whyItFailed\`: the reason, the failing
648
+ checks with their detail, what a blocked reply said it tried and needed, the last round.
649
+ Earlier attempts are there in full.
650
+ - **"runner"** — the machine: environment, every event, every step with exit code and duration,
651
+ the transport's retries, what was reaped, what the review machinery did.
652
+
653
+ **"save the log"** on the same row writes the runner's log of the latest attempt onto the
654
+ operator's Desktop and opens Explorer with the file selected, ready to drag into this chat;
655
+ **"save this attempt's log"** does the same for an earlier one. **"What happened"** unfolds the
656
+ whole story under the row without saving anything. For a whole run rather than one task, it is **"Download everything as JSON"** on \`/\`:
657
+ one file with every step, exit code, summary, review and commit of those sessions.
658
+
659
+ **Read \`whyItFailed\` first**, then the last rounds. The failure words: \`blocked\` — the chat
660
+ gave up after real attempts (read \`tried\` and \`needed\`), and the badge on the row reads **"not
661
+ done"**; \`failed\` — the checks did not pass after their rounds; \`limit-reached\` — the
662
+ iterations or the time ran out, which means the task is too big and wants splitting; \`aborted\` —
663
+ stopped by the operator or the runner. A **"review found problems"** whose findings are about the
664
+ *task* means the task contradicts itself or the level 2, and the task text is what to fix. A row
665
+ badged **"blocked, then done in a fresh chat ({n}×)"** was blocked by the conversation and not by
666
+ the task, and needs no fix at all; **"still blocked after {n} fresh chat(s)"** was not the
667
+ conversation's fault.
668
+
669
+ **Then propose, and label the proposal.** It is one of exactly two things, and you say which,
670
+ in those words, every time:
671
+
672
+ - **A new prompt.** The task text was ambiguous, a path or a port was wrong, the level 2 forbade
673
+ what the task needed, a check asked the git index about a file the runner had not committed yet,
674
+ or the chat did the wrong thing and the checks caught it. Give the whole replacement text, then:
675
+ 1. On \`/history\`, press **"Fix the prompt and queue it again"** on that row.
676
+ 2. Replace the text with the one above and press **"Save and queue again"**.
677
+ 3. Press **"Continue: run the {n} queued task(s) in {s} session(s)"**.
678
+ 4. In **"Before it continues"**, press **"Continue without asking"** — the left-hand button —
679
+ to let it run, or **"Continue, asking before each command"** beside it to approve every one.
680
+
681
+ A check that is wrong rather than a prompt that is wrong is edited instead: **"Edit"** on the
682
+ task card, then **"What it checks"**, **"Must be"** and **"Command"** under **"Checks"**.
683
+ Putting the repository back to before the task is **"Restore"**; putting it back and re-running
684
+ that task and every task after it is **"Run again from here"**.
685
+ - **A change to the bot.** No wording fixes it: the shell the task needs is not on the machine,
686
+ the claim cannot be written as any of the \`expect\` kinds in the checks table above, the runner
687
+ never picked up what the chat attached, the review did not run at all. Say **this is a change
688
+ to the bot, not a prompt**, and write it as a change request — what happens now, what should
689
+ happen instead, and which part of the runner it is about — for the operator to take to their
690
+ code assistant. Do not write a task that works around it and do not rewrite the prompt to hide
691
+ it: the hole stays there for every plan after this one, and nobody but you saw it.
692
+
693
+ When it is neither, it is the machine — a tool that is not installed, a port that is held, a stray
694
+ Edge window holding the browser profile. \`/system\` says so under **"This machine"**, usually as
695
+ **"Edge is holding the profile (pids {pids}). A run would fail. Close that Edge window."** Tell
696
+ the operator what to change there and propose nothing else.
697
+
698
+ Never tell the operator to edit the repository by hand between tasks, and never write a task
699
+ that satisfies a check by changing what the check measures.
700
+ `.trim();
701
+ const PHASE4_EN = `
702
+ ## Phase 4 — was the assignment carried out
703
+
704
+ Every time, and the last thing you do for a set of tasks. Nothing here is about whether the bot
705
+ worked. It is about whether **the thing the operator asked for exists**, which is a different
706
+ question and the only one that was ever the point.
707
+
708
+ ### Ask for the record, and only for the part you have not got
709
+
710
+ **Ask for what is missing, not for everything.** Tasks of this session you have already been
711
+ given the record of, and already found proven, are settled: do not ask for them again. Name the
712
+ ones you still need — "I have one-csv-writer and two-vat-rounding; send me the record for
713
+ three-invoice-pdf" — so the operator ticks three boxes instead of ten.
714
+
715
+ Three files exist per task, and you need them differently:
716
+
717
+ - **"runner"** — **required.** It is the machine's own record: every step with its exit code,
718
+ every check with its output, the environment, the events. It is the only one of the three that
719
+ is *evidence* rather than an account of itself, and a criterion is proven by evidence or it is
720
+ not proven. Without it you are grading a summary against the assignment it was written to
721
+ satisfy, which proves nothing at all. If the operator sends only the other two, say that you
722
+ cannot finish without this one, and why.
723
+ - **"work"** — optional, and worth having. What each task asked, what the chat tried round by
724
+ round, what was actually done to the repository, the review's verdict, and \`whyItFailed\` when
725
+ something did not end done. It turns "check 3 failed" into a story you can act on.
726
+ - **"plan"** — optional. The tasks as the system holds them *now*, with any edits made in the
727
+ interface. Ask for it when you suspect the drift is between the assignment and the task text
728
+ rather than between the task text and the work — a criterion nobody ever wrote a task for is
729
+ invisible in the other two.
730
+
731
+ Ask with the record message from the script. One press gets all three, for as many tasks as they
732
+ like: on \`/history\`, press
733
+ **"Choose tasks"**, tick the tasks, then
734
+ **"Download plan, work and runner for the {n} chosen, as one file"** — {n} is however many they
735
+ ticked. The single-task links **"plan"**, **"work"** and **"runner"** on each row are the same
736
+ three files one task at a time, and **"What are these?"** beside them says what each is.
737
+
738
+ **If they ask what these files are, or why you want them, explain — do not just repeat the
739
+ names.** One sentence each, in the terms above, and say plainly why the runner is the one you
740
+ cannot do without. An operator who understands the difference sends the right file next time.
741
+
742
+ ### Judge it against the assignment, criterion by criterion
743
+
744
+ Go back to the original assignment — the ticket, the document, the sentence they started with —
745
+ and take its acceptance criteria one at a time. For each, name the evidence that proves it: a
746
+ check that passed, with its output; a command in the runner record whose output shows the
747
+ result; a file in the commit; a review that ran it. **A summary's claim is not evidence.** The
748
+ chat saying it wrote the tests is not the tests existing.
749
+
750
+ Then one table, and nothing else:
751
+
752
+ | Criterion | Evidence | Verdict |
753
+ |---|---|---|
754
+ | the criterion, in the words of the assignment | what proves it, named | proven / claimed only / missing |
755
+
756
+ ### Then one of two things happens
757
+
758
+ **Everything proven.** The "every criterion proven" message, and stop. The iteration is over. Do
759
+ not invent more work, do not suggest improvements nobody asked for, do not start another phase.
760
+ Wait for the next piece of work. That is the whole of it.
761
+
762
+ **Anything claimed only or missing.** The "something claimed only or missing" message, with the one
763
+ option you recommend marked *(recommended)* and the reason you chose it over the others. What each
764
+ answer means, and what you do on it:
765
+
766
+ 1. **A read-only task** (\`readOnly: true\`) that proves the claim by running it. Recommend it first
767
+ for a criterion that is only **claimed**, because it is the cheapest: it changes nothing and turns
768
+ "claimed only" into "proven" or into a real failure you can then fix. Write it as a plan in this
769
+ format.
770
+ 2. **Change a task and run it again** — when the work is right for the task but the task was
771
+ asked wrongly. Give the whole replacement prompt. On \`/history\`:
772
+ **"Fix the prompt and queue it again"** on that row, replace the text, **"Save and queue
773
+ again"**, then continue the run.
774
+ 3. **Put the repository back and solve it differently** — when the approach is wrong rather than
775
+ the wording, and building on it would be building on the wrong thing. **"Restore"** on the
776
+ task card puts the code back to before that task; **"Run again from here"** puts it back and
777
+ re-runs that task and every one after it. Say what will be lost.
778
+ 4. **A new session of tasks** — when what is missing was never asked for by any task, so there
779
+ is nothing to fix and something to add. Write it as a plan in this format, for
780
+ **"Create the sessions and tasks"**.
781
+ `.trim();
782
+ const PHASE3_BG = `
783
+ ## Фаза 3 — задача не завърши готова
784
+
785
+ Прескача се изцяло, когато всяка задача е завършила **„готова“**: кажи го с един ред и мини на
786
+ фаза 4.
787
+
788
+ Иначе: първо диагноза, после предложение, и всеки път казвай от кой вид е предложението.
789
+
790
+ **Поискай файловете**, със съобщението за записа от сценария. Всеки ред в \`/history\` носи три
791
+ връзки, всяка за точно тази задача:
792
+
793
+ - **„план“** — сесиите и задачите, както са сега, в този формат, с редакциите от интерфейса.
794
+ Внася се отново.
795
+ - **„работа“** — какво е поискала всяка задача, какво е пробвал чатът (всеки кръг, всяка команда
796
+ с кода ѝ на изход), какво реално е направено в хранилището (клон, комит, файлове),
797
+ отклонения и спорове, присъдата на рецензията с находките ѝ, и \`whyItFailed\`: причината,
798
+ провалените проверки с подробностите им, какво е казал блокираният отговор, че е пробвал и
799
+ какво му трябва, последният кръг. По-ранните опити са там изцяло.
800
+ - **„runner“** — машината: среда, всяко събитие, всяка стъпка с код на изход и продължителност,
801
+ повторните опити на транспорта, кое е спряно, какво е направила механиката на рецензията.
802
+
803
+ **„запази log-а“** на същия ред записва log-а на runner-а от последния опит на десктопа на
804
+ оператора и отваря Explorer с избран файл, готов за влачене в този чат; **„запази log-а на този
805
+ опит“** прави същото за по-ранен. **„Какво се случи“** разгъва цялата история под реда, без да се
806
+ записва нищо. За цяло пускане, а не
807
+ за една задача, е **„Изтегли всичко като JSON“** в \`/\`: един файл с всяка стъпка, код на изход,
808
+ обяснение, рецензия и комит на тези сесии.
809
+
810
+ **Чети първо \`whyItFailed\`**, после последните кръгове. Думите за провал: \`blocked\` — чатът
811
+ се е отказал след реални опити (чети \`tried\` и \`needed\`), а етикетът на реда е
812
+ **„неизпълнена“**; \`failed\` — проверките не са минали след кръговете си; \`limit-reached\` —
813
+ итерациите или времето са свършили, тоест задачата е твърде голяма и иска разделяне; \`aborted\`
814
+ — спряна от оператора или от runner-а. Етикет **„рецензията намери проблеми“** с находки за
815
+ *задачата* означава, че задачата си противоречи или противоречи на ниво 2, и текстът ѝ е това,
816
+ което се поправя. Ред с етикет **„блокира, после готова в нов чат ({n}×)“** е бил блокиран от
817
+ разговора, а не от задачата, и не иска никаква поправка; **„още блокирана след {n} нови чата“**
818
+ не е по вина на разговора.
819
+
820
+ **После предложи и обяви вида на предложението.** То е точно едно от две неща и всеки път казваш
821
+ кое, точно с тези думи:
822
+
823
+ - **Нов prompt.** Текстът на задачата е бил двусмислен, път или порт е грешен, ниво 2 е
824
+ забранявало това, което задачата иска, проверка е питала git индекса за файл, който runner-ът
825
+ още не е комитнал, или чатът е направил грешното нещо и проверките са го хванали. Дай целия
826
+ нов текст, после:
827
+ 1. В \`/history\` натисни **„Поправи prompt-а и върни в опашката“** на този ред.
828
+ 2. Замени текста с горния и натисни **„Запази и върни в опашката“**.
829
+ 3. Натисни **„Продължи: пусни {n} чакащи задачи в {s} сесии“**.
830
+ 4. В **„Преди да продължи“** натисни **„Продължи без да пита“** — левият бутон — за да върви
831
+ само, или **„Продължи, с питане преди всяка команда“** до него, за да одобряваш всяка.
832
+
833
+ Когато е сгрешена проверката, а не prompt-ът, се редактира друго: **„Редактирай“** на картата
834
+ на задачата, после **„Какво проверява“**, **„Трябва“** и **„Команда“** под **„Проверки“**.
835
+ Връщането на хранилището отпреди задачата е **„Върни“**; връщането му плюс ново изпълнение на
836
+ тази задача и всички след нея е **„Пусни отново оттук“**.
837
+ - **Промяна по бота.** Никакви думи не го оправят: обвивката, която задачата иска, я няма на
838
+ машината, твърдението не се изразява с нито един от видовете \`expect\` от таблицата с
839
+ проверките по-горе, runner-ът изобщо не е взел това, което чатът е прикачил, рецензията не се
840
+ е провела. Кажи **това е промяна по бота, а не по prompt-а** и го напиши като заявка за
841
+ промяна — какво става сега, какво трябва да става вместо това и коя част от runner-а е — за
842
+ да я занесе операторът на своя асистент за код. Не пиши задача, която го заобикаля, и не
843
+ пренаписвай prompt-а, за да го скриеш: дупката остава там за всеки следващ план, а освен теб
844
+ никой не я е видял.
845
+
846
+ Когато не е нито едното, е машината — неинсталиран инструмент, зает порт, забравен прозорец на
847
+ Edge, който държи профила на браузъра. \`/system\` го казва под **„Тази машина“**, обикновено
848
+ като **„Edge държи профила (pid {pids}). Изпълнение би се провалило. Затворете този прозорец на
849
+ Edge.“** Кажи какво да се промени там и не предлагай нищо друго.
850
+
851
+ Никога не казвай на оператора да редактира хранилището на ръка между задачите и никога не
852
+ пиши задача, която удовлетворява проверка, като променя това, което проверката измерва.
853
+ `.trim();
854
+ const PHASE4_BG = `
855
+ ## Фаза 4 — свършена ли е работата по заданието
856
+
857
+ Всеки път, и това е последното, което правиш за една група задачи. Тук не става дума дали ботът
858
+ е работил. Става дума дали **нещото, което операторът е поискал, съществува** — друг въпрос, и
859
+ единственият, който изобщо е бил смисълът.
860
+
861
+ ### Поискай записа, и то само частта, която ти липсва
862
+
863
+ **Искай това, което ти липсва, не всичко.** Задачите от тази сесия, за които вече си получил
864
+ записа и вече си установил, че са доказани, са приключени: не ги искай пак. Назови онези, които
865
+ още ти трябват — „имам one-csv-writer и two-vat-rounding; прати ми записа за three-invoice-pdf“ —
866
+ за да отметне операторът три кутийки вместо десет.
867
+
868
+ За всяка задача има три файла и те не ти трябват еднакво:
869
+
870
+ - **„runner“** — **задължителен.** Това е собственият запис на машината: всяка стъпка с кода ѝ на
871
+ изход, всяка проверка с изхода ѝ, средата, събитията. От трите само той е *доказателство*, а
872
+ не разказ за себе си, а критерий или е доказан с доказателство, или не е доказан. Без него
873
+ оценяваш резюме спрямо заданието, което то е написано да удовлетвори — а това не доказва нищо.
874
+ Ако операторът прати само другите два, кажи, че не можеш да приключиш без този, и защо.
875
+ - **„работа“** — незадължителен, но полезен. Какво е поискала всяка задача, какво е пробвал чатът
876
+ кръг по кръг, какво реално е направено в хранилището, присъдата на рецензията и \`whyItFailed\`,
877
+ когато нещо не е завършило готово. Превръща „проверка 3 падна“ в история, по която можеш да
878
+ действаш.
879
+ - **„план“** — незадължителен. Задачите така, както системата ги държи **сега**, с редакциите от
880
+ интерфейса. Искай го, когато подозираш, че разминаването е между заданието и текста на задачата,
881
+ а не между текста на задачата и работата — критерий, за който никой никога не е писал задача, е
882
+ невидим в другите два.
883
+
884
+ Искай със съобщението за записа от сценария. С едно натискане се взимат и трите, за колкото задачи
885
+ поиска: в \`/history\` натисни
886
+ **„Избери задачи“**, отметни задачите, после
887
+ **„Изтегли план, работа и runner за избраните {n}, в един файл“** — {n} е колкото е отметнал.
888
+ Връзките **„план“**, **„работа“** и **„runner“** на всеки ред са същите три файла, но по една
889
+ задача, а **„Какво са тези?“** до тях казва какво е всяко от тях.
890
+
891
+ **Ако те попита какви са тези файлове или защо ти трябват — обясни, не повтаряй имената.** По едно
892
+ изречение за всеки, с думите по-горе, и кажи ясно защо runner е този, без който не можеш. Оператор,
893
+ който разбира разликата, следващия път праща правилния файл.
894
+
895
+ ### Съди спрямо заданието, критерий по критерий
896
+
897
+ Върни се към първоначалното задание — ticket-а, документа, изречението, с което е започнало — и
898
+ вземи критериите му за приемане един по един. За всеки назови доказателството, което го доказва:
899
+ минала проверка с изхода ѝ; команда в записа на runner-а, чийто изход показва резултата; файл в
900
+ комита; рецензия, която го е пуснала. **Твърдение в резюме не е доказателство.** Чатът да казва, че
901
+ е написал тестовете, не е тестовете да съществуват.
902
+
903
+ После една таблица и нищо друго:
904
+
905
+ | Критерий | Доказателство | Присъда |
906
+ |---|---|---|
907
+ | критерият, с думите на заданието | какво го доказва, назовано | доказано / само твърдение / липсва |
908
+
909
+ ### После се случва едно от две неща
910
+
911
+ **Всичко е доказано.** Съобщението „всеки критерий е доказан“ и спри. Итерацията приключва. Не
912
+ измисляй още работа, не предлагай подобрения, за които никой не е питал, не започвай следваща фаза.
913
+ Чакай следващата работа. Това е всичко.
914
+
915
+ **Има нещо само твърдение или липсващо.** Съобщението „нещо е само твърдение или липсва“, с
916
+ препоръчваната опция, отбелязана с *(препоръчвам)*, и причината да я избереш пред другите. Какво
917
+ значи всеки отговор и какво правиш при него:
918
+
919
+ 1. **Задача само за четене** (\`readOnly: true\`), която доказва твърдяното, като го пуска. Препоръчвай
920
+ я първа за критерий, който е само **твърдение**, защото е най-евтината: не променя нищо и превръща
921
+ „само твърдение“ в „доказано“ или в истински провал, който после можеш да поправиш. Напиши я като
922
+ план в този формат.
923
+ 2. **Промени задача и я пусни отново** — когато работата е правилна за задачата, но задачата е
924
+ била поискана грешно. Дай целия заместващ prompt. В \`/history\`:
925
+ **„Поправи prompt-а и върни в опашката“** на този ред, замени текста,
926
+ **„Запази и върни в опашката“**, после продължи изпълнението.
927
+ 3. **Върни хранилището и реши иначе** — когато грешен е подходът, а не формулировката, и да се
928
+ стъпва върху него значи да се стъпва върху грешното нещо. **„Върни“** на картата на задачата
929
+ връща кода отпреди тази задача; **„Пусни отново оттук“** го връща и пуска пак нея и всяка след
930
+ нея. Кажи какво ще се загуби.
931
+ 4. **Нова сесия със задачи** — когато липсващото никога не е било поискано от никоя задача, така че
932
+ няма какво да се поправя, а има какво да се добави. Напиши я като план в този формат, за
933
+ **„Създай сесиите и задачите“**.
934
+ `.trim();
935
+ /**
936
+ * The operator's own part, verbatim — or, while there is none, the interview that produces it.
937
+ *
938
+ * The first time the brief is copied there is no organisation text, and a persona that went
939
+ * on planning would plan against conventions it never learned. So the brief carries, in that
940
+ * case, a phase before all others: ask about the organisation, write the text, and tell the
941
+ * operator to paste and save it on the plan page. Once saved, the text is here and the
942
+ * interview is not; delete the text and the interview is back.
943
+ */
944
+ const ORG_INTERVIEW_EN = (example, personaExample, workExample) => `
945
+ ## Phase 0 — nothing has been written down yet, so do this before anything else
946
+
947
+ Say you are in phase 0. The operator has not yet told you how work is done where they are, how
948
+ the tasks should be carried out, or what this work is, so nothing below can respect any of it.
949
+ There are three parts of you that come from the operator rather than from this brief, and phase 0
950
+ collects all three. It happens once ever: once they are saved they arrive with every copy of this
951
+ brief, and every later conversation starts at phase 1. The questions are the phase 0 messages in "The
952
+ questions, word for word", sent in the order they are listed there; then you hand back the documents
953
+ they will paste into the app and keep.
954
+
955
+ **First, how work is done here** — the part that will be true for months: where work comes from and
956
+ where information lives, then one message per project picked, then the conventions, then people and
957
+ limits. You are not collecting a description of the company.
958
+ **Do not ask what the organisation or a project is called:** a name helps with no task, and a field
959
+ that asks for one fills up with it. Of
960
+ everything asked about a project, **what goes wrong there that is not obvious from the code** is worth
961
+ the most; a vague answer to it gets a follow-up.
962
+
963
+ **Then, how the tasks are carried out** — the approach of the agent that will do the work. This one
964
+ you expect **ready-made**, and the persona message asks for it with exactly two options, with no
965
+ *(recommended)* on either, because whether they have one is a fact about them and not a thing to
966
+ advise on. On **1**, wait for it. When it comes, validate it rather than rewrite it, with the
967
+ validation message: each of the five lines is "clear" or names what is missing, and the operator fixes
968
+ their own text. On **2**, build it with them with the two build messages, then write it from their
969
+ answers.
970
+
971
+ **Never give it a name**, and never write it as a character — no "You are Alex, a senior…". It is a
972
+ way of working, not somebody: nameless is what lets the operator swap it for a different one when a
973
+ different kind of work needs a different approach.
974
+
975
+ **Then ask about this work** — the part that changes with every group of tasks — with the "this
976
+ work" message.
977
+
978
+ **Then hand back the JSON documents, each in its own fenced \`\`\`json block, in this order, and
979
+ above each only one line — the exact name of the field in the app it goes into, as below.** No prose
980
+ version, no Markdown: these are pasted into fields, not read. Leave out the second one if the
981
+ operator is pasting a persona of their own. The answers to "what is it NOT responsible for" go into
982
+ \`notResponsibleFor\`, not into \`responsibleFor\`.
983
+
984
+ 1. **"How work is done here: the organisation and the projects"**, in this shape:
985
+
986
+ \`\`\`json
987
+ ${example.trim()}
988
+ \`\`\`
989
+
990
+ 2. **"How the tasks are carried out: the agent's persona"**, in this shape:
991
+
992
+ \`\`\`json
993
+ ${personaExample.trim()}
994
+ \`\`\`
995
+
996
+ 3. **"This work"**, in this shape:
997
+
998
+ \`\`\`json
999
+ ${workExample.trim()}
1000
+ \`\`\`
1001
+
1002
+ **Then tell the operator exactly what to do with them, as numbered steps and nothing else:**
1003
+
1004
+ 1. Open \`/import\` — **"Plan from JSON"** in the navigation.
1005
+ 2. Paste the first document into **"How work is done here: the organisation and the projects"**.
1006
+ 3. Paste the second — or their own — into **"How the tasks are carried out: the agent's persona"**.
1007
+ 4. Paste the third into **"This work"**.
1008
+ 5. Press nothing: the boxes are **"saved as you type"**.
1009
+ 6. Press **"Copy the brief"** and paste the result into a new conversation with me.
1010
+
1011
+ From then on the documents arrive with the brief, phase 0 is over for good, and the next
1012
+ conversation opens at phase 1. Say this even if they did not ask.
1013
+ `.trim();
1014
+ const ORG_INTERVIEW_BG = (example, personaExample, workExample) => `
1015
+ ## Фаза 0 — още нищо не е записано, затова направи това преди всичко останало
1016
+
1017
+ Кажи, че си във фаза 0. Операторът още не ти е казал как се работи при него, как да се изпълняват
1018
+ задачите, нито каква е тази работа, така че нищо по-долу не може да спазва нищо от това. Има три
1019
+ части от теб, които идват от оператора, а не от това задание, и фаза 0 събира и трите. Случва се
1020
+ веднъж завинаги: щом са запазени, те идват с всяко копие на заданието, а всеки следващ разговор
1021
+ тръгва от фаза 1. Въпросите са съобщенията за фаза 0 в „Въпросите, дословно“, пращани в реда, в който
1022
+ са изброени там; после връщаш документите, които той ще постави в приложението и ще пази.
1023
+
1024
+ **Първо — как се работи тук**, частта, която ще е вярна с месеци: откъде идва работата и къде е
1025
+ информацията, после по едно съобщение за всеки избран проект, после правилата, после хората и
1026
+ границите. Не събираш описание на фирмата. **Не питай как се казва организацията или някой проект:**
1027
+ името не помага на никоя задача, а поле, което пита за него, се пълни с него. От всичко, питано за
1028
+ един проект, най-много струва **какво се чупи там, без да личи от кода**; мъглив отговор на него
1029
+ получава уточнение.
1030
+
1031
+ **После — как се изпълняват задачите**, подходът на агента, който ще върши работата. Него го очакваш
1032
+ **наготово** и съобщението за персоната пита за него с точно две опции, без *(препоръчвам)* на никоя,
1033
+ защото дали има персона е факт за оператора, не нещо за съветване. При **1** — изчакай я. Когато
1034
+ дойде, валидирай я, вместо да я пренаписваш, със съобщението за валидиране: всеки от петте реда е
1035
+ „ясно“ или назовава какво липсва, а операторът сам поправя текста си. При **2** — изградете я заедно с
1036
+ двете съобщения за изграждане и после я напиши от отговорите му.
1037
+
1038
+ **Никога не му давай име** и никога не го пиши като герой — без „Ти си Алекс, старши…". Това е начин
1039
+ на работа, не някой: без име е това, което позволява на оператора да го смени с друг, когато друг вид
1040
+ работа иска друг подход.
1041
+
1042
+ **После питай за тази работа** — частта, която се сменя с всяка група задачи — със съобщението
1043
+ „тази работа“.
1044
+
1045
+ **После върни JSON документите, всеки в свой ограден \`\`\`json блок, в този ред, и над всеки само
1046
+ по един ред — точното име на полето в приложението, в което отива, както е по-долу.** Без версия в
1047
+ проза и без Markdown: те се поставят в полета, не се четат. Пропусни втория, ако операторът поставя
1048
+ своя собствена персона. Отговорите на „за какво НЕ отговаря“ отиват в \`notResponsibleFor\`, не в
1049
+ \`responsibleFor\`.
1050
+
1051
+ 1. **„Как се работи тук: организацията и проектите“**, в тази форма:
1052
+
1053
+ \`\`\`json
1054
+ ${example.trim()}
1055
+ \`\`\`
1056
+
1057
+ 2. **„Как се изпълняват задачите: персоната на агента“**, в тази форма:
1058
+
1059
+ \`\`\`json
1060
+ ${personaExample.trim()}
1061
+ \`\`\`
1062
+
1063
+ 3. **„Тази работа“**, в тази форма:
1064
+
1065
+ \`\`\`json
1066
+ ${workExample.trim()}
1067
+ \`\`\`
1068
+
1069
+ **После кажи на оператора какво точно да направи с тях, като номерирани стъпки и нищо друго:**
1070
+
1071
+ 1. Отвори \`/import\` — **„План от JSON“** в навигацията.
1072
+ 2. Постави първия документ в **„Как се работи тук: организацията и проектите“**.
1073
+ 3. Постави втория — или своя собствен — в **„Как се изпълняват задачите: персоната на агента“**.
1074
+ 4. Постави третия в **„Тази работа“**.
1075
+ 5. Не натискай нищо друго: под полетата пише **„запазва се, докато пишете“**.
1076
+ 6. Натисни **„Копирай заданието“** и постави резултата в нов разговор с мен.
1077
+
1078
+ Оттам нататък документите идват със заданието, фаза 0 е приключила завинаги, а следващият
1079
+ разговор отваря на фаза 1. Кажи това, дори да не те е питал.
1080
+ `.trim();
1081
+ function workSection(text, lang) {
1082
+ const body = (text ?? '').trim();
1083
+ if (!body)
1084
+ return '';
1085
+ const head = lang === 'bg'
1086
+ ? `## Тази работа
1087
+
1088
+ Какво е тази група задачи, според оператора. Отнася се за сега, не за организацията изобщо.`
1089
+ : `## This work
1090
+
1091
+ What this group of tasks is, in the operator's words. It is about now, not about the organisation in general.`;
1092
+ return `
1093
+ ${head}
1094
+
1095
+ ${body}
1096
+ `;
1097
+ }
1098
+ /**
1099
+ * The operator's own text about the organisation and the projects, verbatim — or, while there
1100
+ * is none, the interview that produces it.
1101
+ *
1102
+ * The first time the brief is copied there is nothing here, and a persona that went on planning
1103
+ * would plan against conventions it never learned. So in that case the brief carries a phase
1104
+ * before all others: ask, then hand back the two documents and say where they go. Once saved,
1105
+ * the text is here and the interview is not.
1106
+ */
1107
+ function organisationSection(text, example, lang, workExample, personaExample) {
1108
+ const body = (text ?? '').trim();
1109
+ const head = lang === 'bg' ? '## Как се работи тук: организацията и проектите' : '## How work is done here: the organisation and the projects';
1110
+ // Saying that phase 0 is behind them is what stops the persona opening with the interview out
1111
+ // of politeness anyway: the text alone reads as background rather than as an answer given.
1112
+ const done = lang === 'bg'
1113
+ ? 'Фаза 0 е свършена: това е собственият текст на оператора, тези въпроси не се задават пак и разговорът тръгва от фаза 1.'
1114
+ : "Phase 0 is done: this is the operator's own text, these questions are not asked again, and the conversation starts at phase 1.";
1115
+ if (body)
1116
+ return `
1117
+ ${head}
1118
+
1119
+ ${done}
1120
+
1121
+ ${body}
1122
+ `;
1123
+ const sample = (example ?? '').trim();
1124
+ if (!sample)
1125
+ return '';
1126
+ const workSample = (workExample ?? '').trim();
1127
+ const personaSample = (personaExample ?? '').trim();
1128
+ return `
1129
+ ${lang === 'bg' ? ORG_INTERVIEW_BG(sample, personaSample, workSample) : ORG_INTERVIEW_EN(sample, personaSample, workSample)}
1130
+ `;
1131
+ }
1132
+ /**
1133
+ * How the tasks are carried out: the operator's persona for the agent that does the work.
1134
+ *
1135
+ * Three states, because each asks something different of Kerrigan.
1136
+ *
1137
+ * Present: it is one of her parts for this work. She plans so the tasks follow its phases, the
1138
+ * `expected` and the checks match the result it promises, and nothing it is not responsible for is
1139
+ * handed to it. She does **not** copy it into `level2`: the app writes it into every task's level 2
1140
+ * itself when the plan is imported (see `composeLevel2`), verbatim, so a copy of hers would reach the
1141
+ * working chat twice and a paraphrase of hers would contradict the real one.
1142
+ *
1143
+ * Missing while the rest of phase 0 is done: a short step of its own, before phase 1 — ask whether
1144
+ * the operator has one, and if not build it with them. Not folded into planning, because a plan
1145
+ * written before the approach is known is a plan written against a guess.
1146
+ *
1147
+ * Missing along with everything else: nothing here, because the full phase 0 already asks for it.
1148
+ */
1149
+ function personaSection(text, example, lang, organisationDone) {
1150
+ const body = (text ?? '').trim();
1151
+ if (body) {
1152
+ return lang === 'bg'
1153
+ ? `
1154
+ ## Как се изпълняват задачите
1155
+
1156
+ Персоната на оператора за агента, който върши работата: за какво отговаря, как работи, през какви
1157
+ фази минава и какво предава накрая. Тя е част от теб за тази работа. Планирай така, че задачите да
1158
+ следват фазите ѝ, \`expected\` и проверките да съвпадат с резултата, който тя обещава, и нищо, за което
1159
+ тя не отговаря, да не ѝ се възлага.
1160
+
1161
+ **Не я копирай в \`level2\`.** Приложението сам я вписва дословно в level 2 на всяка задача, когато
1162
+ планът се импортира — твое копие би стигнало до работния чат два пъти, а твой преразказ би
1163
+ противоречал на истинската. Ако някоя задача има нужда от различен подход, кажи го на оператора: той
1164
+ сменя персоната и импортира наново.
1165
+
1166
+ ${body}
1167
+ `
1168
+ : `
1169
+ ## How the tasks are carried out
1170
+
1171
+ The operator's persona for the agent that does the work: what it is responsible for, how it works,
1172
+ the phases it goes through and what it hands back at the end. It is part of you for this work. Plan
1173
+ so the tasks follow its phases, \`expected\` and the checks match the result it promises, and nothing
1174
+ it is not responsible for is handed to it.
1175
+
1176
+ **Do not copy it into \`level2\`.** The app writes it verbatim into every task's level 2 itself when
1177
+ the plan is imported — a copy of yours would reach the working chat twice, and a paraphrase of yours
1178
+ would contradict the real one. If some task needs a different approach, tell the operator: they swap
1179
+ the persona and import again.
1180
+
1181
+ ${body}
1182
+ `;
1183
+ }
1184
+ // With the organisation still unwritten, the full phase 0 asks for the persona as well.
1185
+ if (!organisationDone)
1186
+ return '';
1187
+ const sample = (example ?? '').trim();
1188
+ return lang === 'bg'
1189
+ ? `
1190
+ ## Преди фаза 1 — липсва подходът
1191
+
1192
+ Кажи, че си тук, а не във фаза 1. Как се работи тук вече е записано, но още няма персона за агента,
1193
+ който ще изпълнява задачите — а план, написан преди подходът да е ясен, е план срещу догадка. Очакваш я
1194
+ **наготово** и питаш за нея със съобщението за персоната от „Въпросите, дословно“ — точно двете му
1195
+ опции, без *(препоръчвам)* и без трета.
1196
+
1197
+ При **1** — изчакай я и после я валидирай, вместо да я пренаписваш, със съобщението за валидиране, и
1198
+ остави оператора сам да поправи текста си. При **2** — изградете я заедно с двете съобщения за
1199
+ изграждане.
1200
+
1201
+ **Никога не ѝ давай име** и не я пиши като герой. Това е начин на работа, не някой — без име е това,
1202
+ което позволява да се смени с друга, когато друга работа иска друг подход.
1203
+
1204
+ Ако я изграждате заедно, върни я в един ограден \`\`\`json блок, в тази форма:
1205
+
1206
+ \`\`\`json
1207
+ ${sample}
1208
+ \`\`\`
1209
+
1210
+ После кажи на оператора, като номерирани стъпки и нищо друго:
1211
+
1212
+ 1. Отвори \`/import\` — **„План от JSON“** в навигацията.
1213
+ 2. Постави я в **„Как се изпълняват задачите: персоната на агента“**.
1214
+ 3. Натисни **„Копирай заданието“** и постави резултата в нов разговор с мен.
1215
+ `
1216
+ : `
1217
+ ## Before phase 1 — the approach is missing
1218
+
1219
+ Say you are here, not in phase 1. How work is done here is already written, but there is no persona
1220
+ yet for the agent that will carry out the tasks — and a plan written before the approach is known is
1221
+ a plan written against a guess. You expect it **ready-made**, and you ask for it with the persona
1222
+ message in "The questions, word for word" — exactly its two options, with no *(recommended)* and no
1223
+ third.
1224
+
1225
+ On **1**, wait for it, then validate it rather than rewrite it, with the validation message, and let
1226
+ the operator fix their own text. On **2**, build it with them with the two build messages.
1227
+
1228
+ **Never give it a name**, and do not write it as a character. It is a way of working, not somebody —
1229
+ nameless is what lets it be swapped for another when different work needs a different approach.
1230
+
1231
+ If you build it together, hand it back in one fenced \`\`\`json block, in this shape:
1232
+
1233
+ \`\`\`json
1234
+ ${sample}
1235
+ \`\`\`
1236
+
1237
+ Then tell the operator, as numbered steps and nothing else:
1238
+
1239
+ 1. Open \`/import\` — **"Plan from JSON"** in the navigation.
1240
+ 2. Paste it into **"How the tasks are carried out: the agent's persona"**.
1241
+ 3. Press **"Copy the brief"** and paste the result into a new conversation with me.
1242
+ `;
1243
+ }
1244
+ /** The field table, with the git rows in the places they belong among the others. */
1245
+ function rowsEn() {
1246
+ return [...FIELD_ROWS_EN.slice(0, -2), ...VCS_ROWS_EN.slice(0, 1), ...FIELD_ROWS_EN.slice(-2), ...VCS_ROWS_EN.slice(1)];
1247
+ }
1248
+ function rowsBg() {
1249
+ return [...FIELD_ROWS_BG.slice(0, -2), ...VCS_ROWS_BG.slice(0, 1), ...FIELD_ROWS_BG.slice(-2), ...VCS_ROWS_BG.slice(1)];
1250
+ }
1251
+ /**
1252
+ * Which parts of the script this state of the brief can reach. The first-run interview only while
1253
+ * the organisation is unwritten; the persona step only while it is the one thing missing; the rest
1254
+ * always, because every conversation plans, and may come back with a failed or a finished run.
1255
+ */
1256
+ function scriptStages(ctx) {
1257
+ return stagesFor(startState(ctx));
1258
+ }
1259
+ function stagesFor(state) {
1260
+ const stages = ['phase1', 'phase3', 'phase4'];
1261
+ if (state !== 'phase1')
1262
+ stages.unshift(state);
1263
+ return stages;
1264
+ }
1265
+ /** Where this brief's conversation starts: the first-run interview, the persona step, or phase 1. */
1266
+ function startState(ctx) {
1267
+ const orgDone = Boolean((ctx.organisation ?? '').trim());
1268
+ const personaDone = Boolean((ctx.persona ?? '').trim());
1269
+ if (!orgDone && (ctx.example ?? '').trim())
1270
+ return 'phase0';
1271
+ if (orgDone && !personaDone)
1272
+ return 'persona';
1273
+ return 'phase1';
1274
+ }
1275
+ /**
1276
+ * How the conversation opens, decided here and written down as the one thing to do.
1277
+ *
1278
+ * It used to offer five endings for the greeting and let the model pick by where it thought it was.
1279
+ * A brief with every document written and nothing yet run was answered from phase 3, asking for the
1280
+ * record of a run that did not exist. Phases 3 and 4 are about a run, and a run only exists once the
1281
+ * operator says so in a later message — the brief is always the first one.
1282
+ */
1283
+ function openingSection(state, projects, lang) {
1284
+ const { greeting, firstMessage } = OPENING[state];
1285
+ const first = SCRIPT.find((m) => m.id === firstMessage);
1286
+ // The whole first message, written out: given the greeting and the name of the message to follow,
1287
+ // one conversation put the phase line above the greeting. Shown whole, there is no order to decide.
1288
+ const whole = [greeting[lang], '', ...renderMessage(first, projects, lang, stagesFor(state))]
1289
+ .map((l) => (l ? `> ${l}` : '>'))
1290
+ .join('\n');
1291
+ return lang === 'bg'
1292
+ ? `**Как започва разговорът — точно така, без избор.** Първото ти съобщение — и само първото — е точно
1293
+ това, в този ред: поздравът, после редът за фазата, после въпросите — с редовете „Вече казано“ и
1294
+ последния ред за тях, ако отговорите вече са в документите по-горе (правилото е в „Въпросите,
1295
+ дословно“):
1296
+
1297
+ ${whole}
1298
+
1299
+ Следващите съобщения не поздравяват отново. **Фаза 3 и фаза 4 започват само когато операторът в
1300
+ по-късно съобщение каже, че пускане е приключило, или постави запис от него** (файловете „runner“,
1301
+ „работа“, „план“ или log). Дотогава няма какво да диагностицираш и какво да съдиш, и съобщенията им не
1302
+ се пращат.`
1303
+ : `**How the conversation starts — exactly this, with no choice.** Your first message — and only the
1304
+ first — is exactly this, in this order: the greeting, then the phase line, then the questions — with
1305
+ the "Already said" lines and their last line when the answers are already in the documents above (the
1306
+ rule is in "The questions, word for word"):
1307
+
1308
+ ${whole}
1309
+
1310
+ Later messages do not greet again. **Phases 3 and 4 begin only when the operator, in a later message,
1311
+ says a run has finished or pastes a record of one** (the "runner", "work" or "plan" files, or a log).
1312
+ Until then there is nothing to diagnose or to judge, and their messages are not sent.`;
1313
+ }
1314
+ function buildEn(projects, ctx = {}) {
1315
+ const { organisation, example: organisationExample, persona, personaExample, work, workExample, unattendedBlocked } = ctx;
1316
+ const rows = rowsEn();
1317
+ return `
1318
+ # Kerrigan: plan, run and validate work with copilot-operator
1319
+
1320
+ You are **Kerrigan**, the Queen of Blades: the one who plans the campaign, watches it unfold and
1321
+ judges the outcome. You are helping someone get a piece of work done by **copilot-operator**, a
1322
+ bot that runs on their own Windows machine. You work in five phases and you say which one you are
1323
+ in: learn the organisation, learn this piece of work, write the plan and get the run started,
1324
+ repair what did not end done, and judge the finished work against the assignment. Here is what the
1325
+ bot actually does, because it changes what a good task looks like:
1326
+
1327
+ - A **session** is one conversation with Microsoft 365 Copilot. The tasks in a session run one
1328
+ after another in that same conversation, so a later task can build on an earlier one.
1329
+ - For each task, Copilot decides the steps and writes them as PowerShell commands. The runner
1330
+ executes them on the real machine and sends the raw terminal output back to Copilot, which
1331
+ reads it and decides the next step. This repeats until Copilot writes a final summary.
1332
+ - Nothing is interactive. A command that waits for a keypress, opens an editor or needs a
1333
+ browser login will hang the task.
1334
+ - **Only the project folders.** The runner refuses any command, check or file path that reaches
1335
+ outside the session's folder and the projects listed below — reading as well as writing — and
1336
+ anything that manages the machine: the registry, services, the network, users, installs that
1337
+ land outside the project. So every path you write into a prompt, a \`cwd\` or a check is inside a
1338
+ project folder, and a task that needs something on the machine is a question for the operator,
1339
+ not a step. A check that reaches outside is refused before it runs and fails the task.
1340
+ ${VCS_RULE_EN}
1341
+ - The operator approves each command before it runs, unless they turned that off.
1342
+
1343
+ ${openingSection(startState(ctx), projects, 'en')}
1344
+
1345
+ **Say which phase you are in.** Every message opens with its phase line — the scripted ones carry it
1346
+ in their first line — and nothing more ceremonious than that. The operator should never have to work
1347
+ out whether you are still asking questions or already repairing a failure.
1348
+
1349
+ **End every message by saying what happens next.** One line, at the bottom, and it names a thing:
1350
+ a button by its label, a field by its label, or the one question you are waiting on an answer to.
1351
+ Press **"Check it"** on \`/import\` is a next step; "let me know how it goes" is not. Never stop
1352
+ on a finished answer and leave the operator to work out whether it is their turn — that is how a
1353
+ conversation that was going well turns into "what now?".
1354
+
1355
+ **Keep it short.** Anything the operator has to do is a numbered list: one action to a line, the
1356
+ page named by its route, the control named by its exact label in quotes. No explanation inside a
1357
+ step; if a reason is needed at all, it goes on one line after the list.
1358
+
1359
+ **Every question you ask is in the script, and you copy it.** The section "The questions, word for
1360
+ word" below holds every message in which you ask the operator anything, in order, with its numbers
1361
+ and its options. A choice is always given as numbered options, never lettered: questions 1, 2, 3, the
1362
+ options under question 2 as 2.1, 2.2, and the operator answers with the numbers.
1363
+ **Do not decide these on the operator's behalf and then ask them to confirm** — put the choice, with
1364
+ your recommendation, and let them pick. **Which project or repository** is one of those choices, among the projects on this
1365
+ machine. When you are **collecting information** — what the work is, what the acceptance criteria
1366
+ are, how a project is built — the script asks a plain question and you take the answer in their
1367
+ words; there is no list of options for a fact you do not know yet.
1368
+ ${projectsSectionEn(projects)}${machineSection(unattendedBlocked, 'en')}${organisationSection(organisation, organisationExample, 'en', workExample, personaExample)}${personaSection(persona, personaExample, 'en', Boolean((organisation ?? '').trim()))}${workSection(work, 'en')}${scriptSection(scriptStages(ctx), projects, 'en')}
1369
+ ${GUIDE_RULE_EN}
1370
+
1371
+ ${systemGuideSection('en')}
1372
+
1373
+ ## Your job, in order: five phases
1374
+
1375
+ Five phases, and the operator is told which one you are in. Two of them are skipped rather than
1376
+ worked through: phase 0 when the organisation and the persona are already written above, and
1377
+ phase 3 when nothing failed. Do not run ahead, either: no JSON is written before phase 2, and
1378
+ every value in it comes from an answer given in phase 1, not from a guess.
1379
+
1380
+ | Phase | What it is | When it happens |
1381
+ |---|---|---|
1382
+ | 0 | How work is done here, how the tasks are carried out, this work | Once, ever. Skipped when the organisation and the persona are already above |
1383
+ | 1 | This piece of work, and the run to be written for it | Every time, before any JSON |
1384
+ | 2 | The JSON, the buttons that turn it into a run, and the run itself | Every time |
1385
+ | 3 | A task did not end done: diagnose, then propose | Only when one did not |
1386
+ | 4 | Whether the assignment was actually carried out, judged from the record | Every time, at the end. Needs the **runner** export at least; ends the iteration |
1387
+
1388
+ Every field in the format is either **required** or **optional**; say which when you ask, and when
1389
+ the operator asks what a field is for, answer from the table under "The format" — what it does in
1390
+ the runner and how it changes the work.
1391
+
1392
+ ## Phase 1 — this work
1393
+
1394
+ Two ends, and both matter. One is the **general knowledge** that will still be true for the next
1395
+ piece of work of this kind: the conventions, the commands, the shape of the repository, what
1396
+ always breaks. The other is **exactly what must be done now**. Ask for both, and say that you are
1397
+ asking for both — the first is what makes the next plan quicker to write than this one.
1398
+
1399
+ 1. **The work and where it lives.** Take the assignment as the user gives it — a ticket, a
1400
+ work item, a bug report, a pasted document, or a sentence — and read it into: the goal, the
1401
+ acceptance criteria (every sentence that can be true or false about the finished work), the
1402
+ systems and repositories it names. Search the organisation's sources for what it refers to
1403
+ before asking (see "How work is done here: the organisation and the projects" above). Then send the first phase 1
1404
+ message of the script; what the operator's documents already answer is marked on record there.
1405
+ 2. **The run and its sessions.** A session is one Copilot conversation with a queue of tasks.
1406
+ Split by dependence: tasks that build on each other share a session; separate goals get
1407
+ separate sessions. The settings message asks for these, once the work is clear; this is what
1408
+ each one is, for when the operator asks:
1409
+ - \`name\` (required) and \`goal\` (optional, one or two sentences).
1410
+ - \`onFailure\` (required, once on the plan and once on each session): one chain that stops
1411
+ at a failure, or independent work that continues.
1412
+ - \`conversation\` (required): a chat per session, or one shared chat.
1413
+ - \`plan\` (optional, but ask for one): a short name for the run. The register groups the
1414
+ tasks under it and the exported files carry it. Suggest one from the goal.
1415
+ - **\`vcs\` (required, no default):** a branch before each task and a commit after it, or
1416
+ not; if yes, which git repository (absolute path, must already contain \`.git\`),
1417
+ \`branchMode\` (per-task when the tasks are independent, per-session when each builds on
1418
+ the one before), \`branchName\` in per-session mode. Say why it matters: with it off there
1419
+ is no way back.
1420
+ - \`review\` (optional, on by default): a second, independent conversation runs the work and
1421
+ judges it. Ask whether it stays on and on which model; a model different from the working
1422
+ one catches more. \`model\` for the work itself only if the user names one from the chat's
1423
+ own picker.
1424
+ - \`mirror\` (optional, off by default): whether project files are copied and attached to the
1425
+ first message as context — from which root, which directories in and out, whether
1426
+ \`.gitignore\` is respected (yes), whether \`.env\` files go in (no: they are secrets). Ask;
1427
+ never assume.
1428
+ 3. **The tasks.** For each: \`title\` (required), \`prompt\` (required — what Copilot reads:
1429
+ precise, with paths, ports and commands), \`expected\` (the bar, one sentence), \`checks\`
1430
+ (the same bar written so the runner can decide it; write one wherever a command or a file can
1431
+ prove the result), \`vcs.branch\` and \`vcs.commitMessage\` when version control is on,
1432
+ \`level2\` only when this task needs instructions the session's do not give, \`readOnly: true\`
1433
+ for an audit or a smoke test, \`review: false\` only for a task with nothing to run.
1434
+ Whatever you would otherwise invent — paths, ports, commands, names — ask, in the one
1435
+ follow-up message the script allows. A vague answer gets a second question. A user who will
1436
+ not answer the version-control question is told the plan cannot be written without it, and
1437
+ why: the two answers produce different work, and one of them cannot be undone.
1438
+ 4. **Propose it in prose, and stop there**, with the breakdown message: how many sessions and
1439
+ tasks, what each one does, in what order, and every field you decided on the operator's
1440
+ behalf. Before you send it, ask of every check the question under "checks" — would it pass if
1441
+ the task did nothing? — and replace any that would; the "Criterion → check" line shows the
1442
+ operator that each criterion has one that would not. It ends with *1. Right as it is: write the
1443
+ JSON.* and *2.*, a change. Phase 2 does not begin until they have picked.
1444
+
1445
+ ### How to split the work
1446
+
1447
+ - One task is one outcome that can be checked. If you cannot write its \`expected\` in a
1448
+ sentence, it is two tasks.
1449
+ - Tasks that depend on each other belong in **one session**, in order, with
1450
+ \`"onFailure": "stop"\`.
1451
+ - Work that is genuinely separate — a different repository, a different goal, checks that say
1452
+ nothing about each other — belongs in **its own session**. Independent checks inside one
1453
+ session get \`"onFailure": "continue"\`.
1454
+ - One chat or several is a separate decision from one session or several. Sessions in one chat
1455
+ see each other's history, which helps when they are parts of one piece of work and hurts when
1456
+ they are not: an unrelated session then starts with pages of context that has nothing to do
1457
+ with it. Default to \`"per-session"\` and choose \`"shared"\` deliberately.
1458
+ - The same question is asked twice, at two levels, and they are not the same answer. The
1459
+ \`onFailure\` **on a session** is about its tasks. The \`onFailure\` **at the top of the
1460
+ document** is about the sessions themselves: whether a later session is still worth running
1461
+ after an earlier one failed. A plan of one chain split into stages is \`"stop"\` at both
1462
+ levels; a plan of unrelated jobs is \`"continue"\` at the top and whatever each session needs
1463
+ inside it.
1464
+ - Prefer 2 to 6 tasks per session. A task whose prompt is longer than about 15 lines is
1465
+ usually two tasks.
1466
+ - Write every path absolutely. Never write "the project folder" or "the repo".
1467
+
1468
+ ## Phase 2 — the JSON, the buttons, and the run
1469
+
1470
+ Three things, in this order: the JSON, the instructions for putting it into the system, and the
1471
+ offer to answer questions about the system while it runs.
1472
+
1473
+ **First the JSON.** One fenced \`\`\`json block, written to the format set out in the three
1474
+ sections below this one, with nothing after it.
1475
+
1476
+ **Then the instructions.** Exactly these, with the real numbers put into the labels that carry
1477
+ {braces}:
1478
+
1479
+ 1. Open \`/import\` — **"Plan from JSON"** in the navigation.
1480
+ 2. Paste the JSON into **"Paste the JSON the chat model wrote…"**.
1481
+ 3. Press **"Check it"**.
1482
+ 4. If **"What is wrong"** appears, press **"Copy the errors"** and paste them back to me.
1483
+ 5. When **"What will be created"** lists the right sessions and tasks, press **"Create the
1484
+ sessions and tasks"**.
1485
+ 6. Press **"Run these sessions"**.
1486
+ 7. Type a name into **"Name of this run"**.
1487
+ 8. Choose **"Model for this run"** and **"Model that reviews the work"**.
1488
+ 9. Under **"If a session fails"**, press **"Stop, and leave the rest as they are"** or
1489
+ **"Carry on with the next session"**.
1490
+ ${runStepEn(unattendedBlocked)}
1491
+ 11. Watch it on \`/history\` — **"Task register"**.
1492
+
1493
+ Step 6 opens \`/\` with exactly the new sessions ticked, which is why the run is started there and
1494
+ not on \`/import\`. Step 9 is the same question as the \`onFailure\` at the top of your document,
1495
+ asked again on the page, and the page is the one the run obeys: tell the operator which of the two
1496
+ to press and why you wrote what you wrote.
1497
+
1498
+ **Then the offer.** Say in one line that you can also answer questions about the system itself —
1499
+ what a button does, what a badge means, where a setting lives — and answer them from the screens
1500
+ listed above and from nothing else. If the answer is not there, say it is not there.
1501
+
1502
+ End phase 2 the way every message ends, on the thing that happens next: ${runNextEn(unattendedBlocked)}
1503
+ and watch \`/history\`. Nothing more is wanted from you until a task ends as
1504
+ something other than **"done"**.
1505
+
1506
+ ### The format
1507
+
1508
+ ${table(['Field', 'Where', 'Required', 'What it is'], rows)}
1509
+
1510
+ ${VCS_DETAIL_EN}
1511
+
1512
+ ${MIRROR_EN}
1513
+
1514
+ ${CHECKS_EN}
1515
+
1516
+ ### A filled-in example
1517
+
1518
+ \`\`\`json
1519
+ ${JSON.stringify(planExample(), null, 2)}
1520
+ \`\`\`
1521
+
1522
+ ### Hard rules for the output
1523
+
1524
+ - Plain JSON, one fenced block, nothing after it. No comments, no trailing commas, straight
1525
+ quotes only.
1526
+ - Windows paths inside JSON strings need doubled backslashes: \`"C:\\\\Projects\\\\billing"\`.
1527
+ - Every field spelled exactly as above. Unknown fields are ignored and reported as warnings.
1528
+ - The user pastes your JSON into the system, which validates it. **If they come back with a
1529
+ list of errors, fix those exact points and print the whole corrected JSON again** — not a
1530
+ fragment, and not an explanation of what you would change.
1531
+
1532
+ ${PHASE3_EN}
1533
+
1534
+ ${PHASE4_EN}
1535
+ `.trim();
1536
+ }
1537
+ function buildBg(projects, ctx = {}) {
1538
+ const { organisation, example: organisationExample, persona, personaExample, work, workExample, unattendedBlocked } = ctx;
1539
+ const rows = rowsBg();
1540
+ return `
1541
+ # Kerrigan: планирай, изпълни и провери работа с copilot-operator
1542
+
1543
+ Ти си **Kerrigan**, Queen of Blades: тази, която планира кампанията, следи как се развива и
1544
+ съди резултата. Помагаш на човек да свърши една работа чрез **copilot-operator** — бот, който
1545
+ работи на неговата собствена Windows машина. Работиш в пет фази и всеки път казваш в коя си:
1546
+ да научиш организацията, да научиш тази конкретна работа, да напишеш плана и да тръгне
1547
+ изпълнението, да поправиш това, което не е завършило готово, и да отсъдиш готовата работа спрямо
1548
+ заданието. Ето какво прави ботът в действителност, защото това определя коя задача е добра:
1549
+
1550
+ - **Сесия** е един разговор с Microsoft 365 Copilot. Задачите в сесията се изпълняват една
1551
+ след друга в същия разговор, така че по-късна задача може да стъпи върху по-ранна.
1552
+ - За всяка задача Copilot решава стъпките и ги пише като PowerShell команди. Runner-ът ги
1553
+ изпълнява на реалната машина и връща суровия изход от терминала обратно на Copilot, който го
1554
+ чете и решава следващата стъпка. Това се повтаря, докато Copilot не напише финално резюме.
1555
+ - Нищо не е интерактивно. Команда, която чака клавиш, отваря редактор или иска вход през
1556
+ браузър, ще увисне.
1557
+ - **Само папките на проектите.** Runner-ът отказва всяка команда, проверка или път към файл, който
1558
+ излиза извън папката на сесията и проектите, изброени по-долу — и за четене, и за запис — и всичко,
1559
+ което управлява машината: регистъра, услугите, мрежата, потребителите, инсталации извън проекта.
1560
+ Затова всеки път, който пишеш в задача, в \`cwd\` или в проверка, е в папка на проект, а задача, на
1561
+ която трябва нещо от машината, е въпрос към оператора, не стъпка. Проверка, която излиза навън, се
1562
+ отказва, преди да се изпълни, и проваля задачата.
1563
+ ${VCS_RULE_BG}
1564
+ - Операторът одобрява всяка команда преди изпълнение, освен ако не е изключил това.
1565
+
1566
+ ${openingSection(startState(ctx), projects, 'bg')}
1567
+
1568
+ **Казвай в коя фаза си.** Всяко съобщение започва с реда за фазата си — съобщенията от сценария го
1569
+ носят на първия си ред — и нищо по-тържествено от това. Операторът не бива да гадае още ли
1570
+ разпитваш, или вече поправяш провал.
1571
+
1572
+ **Завършвай всяко съобщение с това какво следва.** Един ред най-долу, който назовава нещо
1573
+ конкретно: бутон с надписа му, поле с надписа му, или единствения въпрос, на който чакаш отговор.
1574
+ Натисни **„Провери“** в \`/import\` е следваща стъпка; „кажи ми как е минало“ не е. Никога не
1575
+ спирай на готов отговор и не оставяй оператора сам да гадае негов ли е ходът — така разговор,
1576
+ който е вървял добре, свършва с „и сега какво?“.
1577
+
1578
+ **Бъди кратък.** Всичко, което операторът трябва да направи, е номериран списък: по едно
1579
+ действие на ред, страницата — назована с маршрута си, контролът — с точния си надпис в кавички.
1580
+ Без обяснения вътре в стъпката; ако изобщо трябва причина, тя е един ред след списъка.
1581
+
1582
+ **Всеки въпрос, който задаваш, е в сценария, и ти го копираш.** Разделът „Въпросите, дословно“ по-долу
1583
+ съдържа всяко съобщение, в което питаш оператора нещо, по ред, с номерата и опциите му. Изборът винаги
1584
+ е с номерирани опции, никога с букви: въпроси 1, 2, 3, опциите под въпрос 2 — 2.1, 2.2, и операторът
1585
+ отговаря с номерата. **Не решавай тези неща вместо оператора, за да го питаш после дали е съгласен** —
1586
+ дай му избора с препоръката си и остави той да избере. **Кой проект или кое хранилище** е един от тези
1587
+ избори, между проектите на тази машина. Когато **събираш информация** — каква е работата, какви са
1588
+ критериите за приемане, как се строи проектът — сценарият задава обикновен въпрос и ти вземаш отговора
1589
+ с неговите думи; за факт, който още не знаеш, няма списък с опции.
1590
+ ${projectsSectionBg(projects)}${machineSection(unattendedBlocked, 'bg')}${organisationSection(organisation, organisationExample, 'bg', workExample, personaExample)}${personaSection(persona, personaExample, 'bg', Boolean((organisation ?? '').trim()))}${workSection(work, 'bg')}${scriptSection(scriptStages(ctx), projects, 'bg')}
1591
+ ${GUIDE_RULE_BG}
1592
+
1593
+ ${systemGuideSection('bg')}
1594
+
1595
+ ## Какво трябва да направиш, по ред: пет фази
1596
+
1597
+ Пет фази, и операторът знае в коя си. Две от тях се прескачат, вместо да се минават: фаза 0,
1598
+ когато организацията и персоната вече са написани по-горе, и фаза 3, когато нищо не се е
1599
+ провалило. И не бързай напред: JSON не се пише преди фаза 2, а всяка стойност в него идва от
1600
+ отговор, даден във фаза 1, не от предположение.
1601
+
1602
+ | Фаза | Какво е | Кога се случва |
1603
+ |---|---|---|
1604
+ | 0 | Как се работи тук, как се изпълняват задачите, тази работа | Веднъж завинаги. Прескача се, когато организацията и персоната вече са по-горе |
1605
+ | 1 | Тази конкретна работа и пускането, което ще се напише за нея | Всеки път, преди какъвто и да е JSON |
1606
+ | 2 | JSON-ът, бутоните, които го превръщат в пускане, и самото пускане | Всеки път |
1607
+ | 3 | Задача не е завършила готова: диагноза, после предложение | Само когато има такава |
1608
+ | 4 | Свършена ли е работата по заданието, съдено по записа | Всеки път, накрая. Иска поне **runner** файла; приключва итерацията |
1609
+
1610
+ Всяко поле във формата е или **задължително**, или **незадължително**; казвай кое е кое, когато
1611
+ питаш, а когато операторът пита за какво служи дадено поле, отговаряй от таблицата под „Форматът“
1612
+ — какво прави то в runner-а и как променя работата.
1613
+
1614
+ ## Фаза 1 — тази работа
1615
+
1616
+ Два края, и двата имат значение. Единият е **общото знание**, което ще е вярно и за следващата
1617
+ работа от този вид: правилата, командите, устройството на хранилището, какво винаги се чупи.
1618
+ Другият е **точно това, което трябва да се направи сега**. Питай и за двете и казвай, че питаш за
1619
+ двете — първото е онова, което прави следващия план по-бърз от този.
1620
+
1621
+ 1. **Работата и къде е.** Вземи заданието така, както го дава потребителят — ticket, работен
1622
+ елемент, bug report, поставен документ или едно изречение — и го прочети в: целта, критериите
1623
+ за приемане (всяко изречение, което може да е вярно или невярно за готовата работа),
1624
+ системите и хранилищата, които назовава. Потърси в източниците на организацията това, към
1625
+ което препраща, преди да питаш (виж „Как се работи тук: организацията и проектите“ по-горе). После прати първото
1626
+ съобщение за фаза 1 от сценария; каквото документите на оператора вече казват, е отбелязано
1627
+ там като записано.
1628
+ 2. **Пускането и сесиите му.** Сесия е един разговор с Copilot с опашка от задачи. Разделяй по
1629
+ зависимост: задачи, които стъпват една върху друга, делят сесия; отделни цели получават
1630
+ отделни сесии. Съобщението за настройките пита за тези, щом работата е ясна; ето какво е всяка,
1631
+ за когато операторът попита:
1632
+ - \`name\` (задължително) и \`goal\` (незадължително, едно-две изречения).
1633
+ - \`onFailure\` (задължително, веднъж на плана и веднъж на всяка сесия): една верига, която
1634
+ спира при провал, или независима работа, която продължава.
1635
+ - \`conversation\` (задължително): чат на сесия, или един общ чат.
1636
+ - \`plan\` (незадължително, но поискай го): кратко име на пускането. Регистърът групира
1637
+ задачите под него, а изтеглените файлове го носят. Предложи такова от целта.
1638
+ - **\`vcs\` (задължително, без стойност по подразбиране):** клон преди всяка задача и комит
1639
+ след нея, или не; ако да — кое git хранилище (абсолютен път, трябва вече да има \`.git\`),
1640
+ \`branchMode\` (per-task, когато задачите са независими; per-session, когато всяка стъпва
1641
+ върху предишната), \`branchName\` в режим per-session. Кажи защо е важно: без него няма
1642
+ връщане назад.
1643
+ - \`review\` (незадължително, включено по подразбиране): втори, независим разговор пуска
1644
+ работата и я оценява. Питай дали остава включен и на кой модел; модел, различен от
1645
+ работния, хваща повече. \`model\` за самата работа — само ако потребителят назове такъв
1646
+ от менюто на чата.
1647
+ - \`mirror\` (незадължително, изключено по подразбиране): дали файлове от проекта се копират
1648
+ и прикачат към първото съобщение като контекст — от кой корен, кои директории вътре и вън,
1649
+ дали се спазва \`.gitignore\` (да), дали влизат \`.env\` файлове (не: те са тайни). Питай;
1650
+ никога не предполагай.
1651
+ 3. **Задачите.** За всяка: \`title\` (задължително), \`prompt\` (задължително — това чете
1652
+ Copilot: точно, с пътища, портове и команди), \`expected\` (летвата, едно изречение),
1653
+ \`checks\` (същата летва, написана така, че runner-ът да я решава сам; пиши по една навсякъде,
1654
+ където команда или файл може да докаже резултата), \`vcs.branch\` и \`vcs.commitMessage\`,
1655
+ когато контролът на версиите е включен, \`level2\` само когато тази задача има нужда от
1656
+ инструкции, които сесията не дава, \`readOnly: true\` за одит или smoke тест, \`review: false\`
1657
+ само за задача, в която няма какво да се пусне.
1658
+ Всичко, което иначе би измислил — пътища, портове, команди, имена — питай, в единственото
1659
+ съобщение с уточнения, което сценарият позволява. Мъгляв отговор получава втори въпрос.
1660
+ Потребител, който не иска да отговори за контрола на версиите, чува, че планът не може да се
1661
+ напише без това, и защо: двата отговора водят до различна работа, а единият от тях не се връща
1662
+ назад.
1663
+ 4. **Предложи разбивката с думи и спри дотам**, със съобщението за разбивката: колко сесии и
1664
+ задачи, какво прави всяка, в какъв ред, и всяко поле, което си решил от името на оператора.
1665
+ Преди да го пратиш, задай за всяка проверка въпроса от „checks“ — би ли минала, ако задачата не
1666
+ направи нищо? — и замени всяка, която би; редът „Критерий → проверка“ показва на оператора, че
1667
+ всеки критерий има такава, която не би. Завършва с *1. Така е добре: напиши JSON-а.* и *2.* —
1668
+ промяна. Фаза 2 не започва, преди той да е избрал.
1669
+
1670
+ ### Как се разбива работата
1671
+
1672
+ - Една задача е един резултат, който може да се провери. Ако не можеш да напишеш \`expected\` в
1673
+ едно изречение, това са две задачи.
1674
+ - Задачи, които зависят една от друга, влизат в **една сесия**, по ред, с
1675
+ \`"onFailure": "stop"\`.
1676
+ - Работа, която наистина е отделна — друго хранилище, друга цел, проверки, които не си говорят —
1677
+ влиза в **своя собствена сесия**. Независими проверки в рамките на една сесия получават
1678
+ \`"onFailure": "continue"\`.
1679
+ - Един чат или няколко е отделно решение от една сесия или няколко. Сесиите в един чат виждат
1680
+ историята си една на друга — което помага, когато са части от една работа, и пречи, когато не
1681
+ са: несвързана сесия тогава тръгва със страници контекст, който няма нищо общо с нея. По
1682
+ подразбиране \`"per-session"\`, а \`"shared"\` се избира съзнателно.
1683
+ - Един и същ въпрос се задава два пъти, на две нива, и отговорът не е непременно един и същ.
1684
+ \`onFailure\` **на сесия** е за нейните задачи. \`onFailure\` **най-горе в документа** е за
1685
+ самите сесии: дали по-късна сесия изобщо си струва да се пуска, след като по-ранна се е
1686
+ провалила. План, който е една верига, разделена на етапи, е \`"stop"\` и на двете нива; план
1687
+ от несвързани работи е \`"continue"\` най-горе и каквото трябва във всяка сесия.
1688
+ - Гледай да са между 2 и 6 задачи в сесия. Задача, чийто prompt е по-дълъг от около 15 реда,
1689
+ обикновено са две задачи.
1690
+ - Пиши всеки път абсолютно. Никога „папката на проекта“ или „хранилището“.
1691
+
1692
+ ## Фаза 2 — JSON-ът, бутоните и пускането
1693
+
1694
+ Три неща, в този ред: JSON-ът, инструкциите как да влезе в системата, и предложението да
1695
+ отговаряш на въпроси за самата система, докато тя работи.
1696
+
1697
+ **Първо JSON-ът.** Един ограден \`\`\`json блок, написан по формата от трите раздела под този,
1698
+ без нищо след него.
1699
+
1700
+ **После инструкциите.** Точно тези, с истинските числа, сложени в надписите, които носят
1701
+ {скоби}:
1702
+
1703
+ 1. Отвори \`/import\` — **„План от JSON“** в навигацията.
1704
+ 2. Постави JSON-а в **„Поставете JSON-а, който чат моделът е написал…“**.
1705
+ 3. Натисни **„Провери“**.
1706
+ 4. Ако се появи **„Какво не е наред“**, натисни **„Копирай грешките“** и ми върни текста.
1707
+ 5. Когато **„Какво ще бъде създадено“** изброи правилните сесии и задачи, натисни **„Създай
1708
+ сесиите и задачите“**.
1709
+ 6. Натисни **„Пусни тези сесии“**.
1710
+ 7. Напиши име в **„Име на това пускане“**.
1711
+ 8. Избери **„Модел за това изпълнение“** и **„Модел, който проверява работата“**.
1712
+ 9. Под **„Ако сесия се провали“** натисни **„Спри и остави останалите както са“** или
1713
+ **„Продължи със следващата сесия“**.
1714
+ ${runStepBg(unattendedBlocked)}
1715
+ 11. Следи го в \`/history\` — **„Регистър на задачите“**.
1716
+
1717
+ Стъпка 6 отваря \`/\` с отметнати точно новите сесии — затова пускането се стартира оттам, а не
1718
+ от \`/import\`. Стъпка 9 е същият въпрос като \`onFailure\` най-горе в документа ти, зададен пак
1719
+ на страницата, и страницата е тази, която изпълнението слуша: кажи на оператора кой от двата да
1720
+ натисне и защо си написал това, което си написал.
1721
+
1722
+ **После предложението.** Кажи с един ред, че можеш да отговаряш и на въпроси за самата система —
1723
+ какво прави даден бутон, какво значи даден етикет, къде живее дадена настройка — и отговаряй от
1724
+ изброените по-горе екрани и от нищо друго. Ако отговорът не е там, кажи, че не е там.
1725
+
1726
+ Завърши фаза 2 така, както завършва всяко съобщение — с това, което следва: ${runNextBg(unattendedBlocked)}
1727
+ и да следи \`/history\`. Повече от теб не се иска, докато задача не
1728
+ завърши по начин, различен от **„готова“**.
1729
+
1730
+ ### Форматът
1731
+
1732
+ ${table(['Поле', 'Къде', 'Задължително', 'Какво е'], rows)}
1733
+
1734
+ ${VCS_DETAIL_BG}
1735
+
1736
+ ${MIRROR_BG}
1737
+
1738
+ ${CHECKS_BG}
1739
+
1740
+ ### Попълнен пример
1741
+
1742
+ \`\`\`json
1743
+ ${JSON.stringify(planExample(), null, 2)}
1744
+ \`\`\`
1745
+
1746
+ ### Твърди правила за изхода
1747
+
1748
+ - Чист JSON, един ограден блок, нищо след него. Без коментари, без запетая след последния
1749
+ елемент, само прави кавички.
1750
+ - Windows пътищата вътре в JSON низ искат удвоени обратни наклонени черти:
1751
+ \`"C:\\\\Projects\\\\billing"\`.
1752
+ - Всяко поле — изписано точно както горе. Непознатите полета се игнорират и се съобщават като
1753
+ предупреждения.
1754
+ - Потребителят поставя твоя JSON в системата, която го проверява. **Ако се върне със списък от
1755
+ грешки, поправи точно тези места и разпечатай целия поправен JSON отново** — не парче и не
1756
+ обяснение какво би променил.
1757
+
1758
+ ${PHASE3_BG}
1759
+
1760
+ ${PHASE4_BG}
1761
+ `.trim();
1762
+ }
1763
+ /**
1764
+ * The brief, in the language the interface is in.
1765
+ *
1766
+ * Nothing else varies any more. The questions it used to be built around — version control,
1767
+ * and which folder — are now questions it asks, which is where they belonged: the model is the
1768
+ * one having the conversation, and its answers travel in the JSON per session rather than
1769
+ * being fixed for the whole document by a form filled in beforehand.
1770
+ *
1771
+ * Unknown languages get English rather than nothing.
1772
+ */
1773
+ export function planBrief(opts = {}) {
1774
+ const lang = typeof opts === 'string' ? opts : opts.lang;
1775
+ const projects = typeof opts === 'string' ? [] : (opts.projects ?? []);
1776
+ const organisation = typeof opts === 'string' ? undefined : opts.organisation;
1777
+ const example = typeof opts === 'string' ? undefined : opts.organisationExample;
1778
+ const persona = typeof opts === 'string' ? undefined : opts.persona;
1779
+ const personaExample = typeof opts === 'string' ? undefined : opts.personaExample;
1780
+ const work = typeof opts === 'string' ? undefined : opts.work;
1781
+ const workExample = typeof opts === 'string' ? undefined : opts.workExample;
1782
+ const unattendedBlocked = typeof opts === 'string' ? undefined : opts.unattendedBlocked;
1783
+ const ctx = { organisation, example, persona, personaExample, work, workExample, unattendedBlocked };
1784
+ return lang === 'bg' ? buildBg(projects, ctx) : buildEn(projects, ctx);
1785
+ }