@asrunjs/asrun 0.2.0 → 0.5.2

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 (94) hide show
  1. package/.claude/commands/explore-case.md +18 -15
  2. package/.claude/commands/gen-spec.md +16 -13
  3. package/.claude/commands/heal-spec.md +26 -23
  4. package/.claude/commands/make-case.md +11 -8
  5. package/.claude/skills/exploring-cases/SKILL.md +146 -132
  6. package/.claude/skills/exploring-cases/references/open-questions.md +116 -112
  7. package/.claude/skills/exploring-cases/references/readiness.md +146 -137
  8. package/.claude/skills/exploring-cases/references/user-handoff.md +157 -147
  9. package/.claude/skills/generating-specs/SKILL.md +219 -207
  10. package/.claude/skills/generating-specs/references/conventions.md +148 -148
  11. package/.claude/skills/making-cases/SKILL.md +72 -64
  12. package/.claude/skills/making-cases/references/normalizing.md +39 -38
  13. package/.claude/skills/making-cases/references/writes-and-email.md +37 -36
  14. package/AGENTS.md +159 -154
  15. package/CLAUDE.md +2 -1
  16. package/README.md +512 -410
  17. package/dist/lib/build-artifacts.d.ts +8 -0
  18. package/dist/lib/build-artifacts.js +10 -0
  19. package/dist/lib/case-doc.d.ts +311 -0
  20. package/dist/lib/case-doc.js +1117 -0
  21. package/dist/lib/cases.d.ts +2 -15
  22. package/dist/lib/cases.js +5 -78
  23. package/dist/lib/command.d.ts +24 -0
  24. package/dist/lib/command.js +90 -0
  25. package/dist/lib/fixtures.js +2 -2
  26. package/dist/lib/login-card.d.ts +22 -0
  27. package/dist/lib/login-card.js +183 -0
  28. package/dist/lib/node-cli.d.ts +6 -2
  29. package/dist/lib/node-cli.js +16 -6
  30. package/dist/lib/profiles.d.ts +25 -1
  31. package/dist/lib/profiles.js +54 -1
  32. package/dist/lib/run-filters.js +10 -2
  33. package/dist/lib/session.d.ts +13 -1
  34. package/dist/lib/session.js +73 -7
  35. package/dist/lib/targets.d.ts +5 -13
  36. package/dist/lib/targets.js +4 -29
  37. package/dist/lib/values.js +2 -2
  38. package/dist/scripts/build.js +2 -1
  39. package/dist/scripts/check-cases.js +74 -6
  40. package/dist/scripts/doctor.js +115 -4
  41. package/dist/scripts/explore.js +22 -1
  42. package/dist/scripts/init.d.ts +7 -1
  43. package/dist/scripts/init.js +61 -13
  44. package/dist/scripts/lint-specs.js +4 -1
  45. package/dist/scripts/login.js +30 -2
  46. package/dist/ui/server/actions.d.ts +4 -0
  47. package/dist/ui/server/actions.js +25 -12
  48. package/dist/ui/server/ai-models.d.ts +3 -0
  49. package/dist/ui/server/ai-models.js +347 -0
  50. package/dist/ui/server/ai-stream.d.ts +34 -9
  51. package/dist/ui/server/ai-stream.js +283 -77
  52. package/dist/ui/server/app.js +74 -12
  53. package/dist/ui/server/jobs.d.ts +7 -1
  54. package/dist/ui/server/jobs.js +69 -15
  55. package/dist/ui/server/profiles.d.ts +20 -1
  56. package/dist/ui/server/profiles.js +214 -38
  57. package/dist/ui/shared/ai-commands.d.ts +16 -6
  58. package/dist/ui/shared/ai-commands.js +35 -12
  59. package/dist/ui/shared/types.d.ts +114 -0
  60. package/docs/agents/framework-dev.md +79 -0
  61. package/docs/theme-guide.html +922 -692
  62. package/package.json +12 -1
  63. package/templates/.claude/commands/explore-case.md +22 -21
  64. package/templates/.claude/commands/gen-spec.md +21 -20
  65. package/templates/.claude/commands/heal-spec.md +31 -30
  66. package/templates/.claude/commands/make-case.md +13 -12
  67. package/templates/.mcp.json +8 -8
  68. package/templates/AGENTS.md +64 -45
  69. package/templates/cases/_template.md +57 -63
  70. package/templates/opencode.jsonc +16 -11
  71. package/templates/profiles.yaml +42 -29
  72. package/ui/dist/assets/{dist-BHaIOZdW.js → dist-BGbe0-Aa.js} +1 -1
  73. package/ui/dist/assets/{dist-CBV7_vDN.js → dist-Ba-HC662.js} +1 -1
  74. package/ui/dist/assets/{dist-BiE4suo9.js → dist-BtMLMthJ.js} +1 -1
  75. package/ui/dist/assets/{dist-gF2bFn-5.js → dist-C-Oyo1ZT.js} +1 -1
  76. package/ui/dist/assets/{dist-DX8knLVr.js → dist-C8MdhqgG.js} +1 -1
  77. package/ui/dist/assets/{dist-DHCY9KpV.js → dist-CG1pHn5m.js} +1 -1
  78. package/ui/dist/assets/{dist-BwYRbe5s.js → dist-CmpteMYY.js} +1 -1
  79. package/ui/dist/assets/{dist-D_I45xgJ.js → dist-DSy3dVf4.js} +1 -1
  80. package/ui/dist/assets/{dist-BqAROxzI.js → dist-Dq6sW5Ur.js} +1 -1
  81. package/ui/dist/assets/{dist-BSJMM_jp.js → dist-jjxQutEm.js} +1 -1
  82. package/ui/dist/assets/index-CTmnF0Wp.css +2 -0
  83. package/ui/dist/assets/index-P_Yo0ZZn.js +338 -0
  84. package/ui/dist/help/ai-tools.png +0 -0
  85. package/ui/dist/help/case-explore.png +0 -0
  86. package/ui/dist/help/case-generate.png +0 -0
  87. package/ui/dist/help/cases-memo.png +0 -0
  88. package/ui/dist/help/profile-form.png +0 -0
  89. package/ui/dist/help/profiles-login.png +0 -0
  90. package/ui/dist/help/reports.png +0 -0
  91. package/ui/dist/help/run-tests.png +0 -0
  92. package/ui/dist/index.html +22 -16
  93. package/ui/dist/assets/index-DmasQ6Rq.css +0 -2
  94. package/ui/dist/assets/index-I7uU93kk.js +0 -294
@@ -0,0 +1,8 @@
1
+ /**
2
+ * What `pnpm build` produces, which `scripts/build.ts` writes and `scripts/doctor.ts` checks. `tsconfig.build.json`
3
+ * compiles every `.ts` file into the same relative path under `dist/`; the plain `.mjs` tools are copied as they are.
4
+ * Keeping the copied files here means the build and the check of a stale build cannot drift apart.
5
+ */
6
+ export declare const COPIED_SCRIPTS: string[];
7
+ /** The file `dist/` holds for a framework source: `lib/paths.ts` -> `dist/lib/paths.js`, `scripts/go.mjs` -> `dist/scripts/go.mjs`. */
8
+ export declare function builtFile(source: string): string;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * What `pnpm build` produces, which `scripts/build.ts` writes and `scripts/doctor.ts` checks. `tsconfig.build.json`
3
+ * compiles every `.ts` file into the same relative path under `dist/`; the plain `.mjs` tools are copied as they are.
4
+ * Keeping the copied files here means the build and the check of a stale build cannot drift apart.
5
+ */
6
+ export const COPIED_SCRIPTS = ['go.mjs', 'heal-evidence.mjs'];
7
+ /** The file `dist/` holds for a framework source: `lib/paths.ts` -> `dist/lib/paths.js`, `scripts/go.mjs` -> `dist/scripts/go.mjs`. */
8
+ export function builtFile(source) {
9
+ return `dist/${source.replace(/\.ts$/, '.js')}`;
10
+ }
@@ -0,0 +1,311 @@
1
+ export type StepTag = 'writes' | 'input' | 'email';
2
+ export declare const STEP_TAGS: readonly StepTag[];
3
+ export interface TextRange {
4
+ start: number;
5
+ end: number;
6
+ }
7
+ export interface TextEdit extends TextRange {
8
+ text: string;
9
+ }
10
+ /** One line of the file: `text` without its line break, `start`/`end` are offsets into the whole document. */
11
+ export interface CaseLine {
12
+ number: number;
13
+ start: number;
14
+ end: number;
15
+ text: string;
16
+ }
17
+ export interface CaseSection {
18
+ name: string;
19
+ /** Offset of the `## ` heading. */
20
+ headingStart: number;
21
+ /** The body: after the heading line up to the next `## ` heading (or the end of the file). */
22
+ body: TextRange;
23
+ }
24
+ /** One entry of the front matter, with the ranges the panel edits. */
25
+ export interface FrontMatterEntry {
26
+ key: string;
27
+ /** The parsed value (a string, a list, ...). */
28
+ value: unknown;
29
+ /** The key line through the last line of its value (trailing blank lines excluded). */
30
+ range: TextRange;
31
+ /** The value itself: on the key line after the colon, or the block below the key. */
32
+ valueRange: TextRange;
33
+ line: number;
34
+ /** True when the value continues below the key (a block list, a `|`/`>` block). */
35
+ block: boolean;
36
+ }
37
+ export interface CaseFrontMatter {
38
+ data: Record<string, unknown>;
39
+ entries: FrontMatterEntry[];
40
+ /** The opening `---` through the closing `---` (without the line break after it). */
41
+ range: TextRange;
42
+ /** Offset just after the opening `---` line, where a new key is inserted first. */
43
+ insertAt: number;
44
+ /** Why the yaml cannot be read; null when it parses. */
45
+ problem: string | null;
46
+ }
47
+ /** The front matter of a case document that the tools care about. The case document is the single source of truth. */
48
+ export interface CaseMeta {
49
+ id: string;
50
+ /** Suites the case belongs to, e.g. `['smoke', 'regression']`. A case runs in every suite it lists. */
51
+ suites: string[];
52
+ /**
53
+ * Environments in which the case may create or change data of the test accounts (and cleans it up), e.g.
54
+ * `['staging']`. Empty = the case never writes. In any other environment the case is skipped, not failed.
55
+ */
56
+ writes: string[];
57
+ /** What the case writes; required when `writes` is not empty. Shown to the person who confirms a production run. */
58
+ writesNote: string | null;
59
+ /** Environments the case may run in; `null` = all. */
60
+ envs: string[] | null;
61
+ }
62
+ /** A prose section (`## Goal`, `## Notes`, ...), read-only in the panel: clicking it reveals its line. */
63
+ export interface CaseProse {
64
+ name: string;
65
+ body: string;
66
+ range: TextRange;
67
+ /** 1-based line of the first character of the body, for the editor offset of the rendered Markdown. */
68
+ line: number;
69
+ }
70
+ /** One top-level bullet of a list section (`## Expected`, `## Visual checks`), with the question blocks below it. */
71
+ export interface CaseBullet {
72
+ /** The bullet's text without its marker, wrapped lines joined. */
73
+ text: string;
74
+ /** The whole block: the bullet through its nested lines, without trailing blank lines. */
75
+ range: TextRange;
76
+ /** The text only: after the marker through the last line of the bullet's own text (nested bullets excluded). */
77
+ textRange: TextRange;
78
+ line: number;
79
+ /** Lines of the `TODO` blocks that hang under this bullet. */
80
+ questionLines: number[];
81
+ }
82
+ /** One `### T<n>` card of `## Targets`, with the ranges the panel shows next to a visual check. */
83
+ export interface CaseTargetCard {
84
+ id: string;
85
+ name: string;
86
+ /** The `- <Field>: ...` bullets, keyed by lower-case field name (`what`, `where`, `identified by`, `not`, ...). */
87
+ fields: Record<string, string>;
88
+ /** The `- Reference <profile>: <path>` bullets, keyed by profile id. */
89
+ references: Record<string, string>;
90
+ /** The card holds a `🚫 TODO [blocking]` block: the check that uses it is not generated yet. */
91
+ blocking: boolean;
92
+ /** 1-based line of the `### T<n>` heading in the whole file. */
93
+ line: number;
94
+ /** The heading line through the last line of the card (without trailing blank lines). */
95
+ range: TextRange;
96
+ }
97
+ /** The fields every card must have; `pnpm case:check` fails without them. Kept here (pure) for the web panel. */
98
+ export declare const TARGET_REQUIRED_FIELDS: readonly ['what', 'where', 'identified by'];
99
+ export interface CaseStep {
100
+ /** The number as written in the file (a case written by hand may skip or repeat one). */
101
+ number: number;
102
+ /** The sentence without its trailing tags. */
103
+ text: string;
104
+ tags: StepTag[];
105
+ /** The whole block: the step line through its wrapped lines, without its trailing tags. */
106
+ range: TextRange;
107
+ /** The sentence only (after `N. ` through the last wrapped line, tags excluded). */
108
+ textRange: TextRange;
109
+ /** Where ` [tag]` is inserted when a tag is added (end of the sentence, before trailing blanks). */
110
+ insertAt: number;
111
+ /** 1-based line number in the file. */
112
+ line: number;
113
+ /** The `- Recipe (...):` lines under the step, in the order written; `/gen-spec` replays them. */
114
+ recipes: CaseRecipe[];
115
+ /** The `- Ready when:` lines under the step, in the order written; `/gen-spec` waits for them. */
116
+ readiness: CaseReadySignal[];
117
+ }
118
+ /**
119
+ * One `- Ready when: ...` bullet under a step: what proves that the content the step loads is really there. It is a
120
+ * wait, not a check: the expected behaviour lives in `## Expected`.
121
+ */
122
+ export interface CaseReadySignal {
123
+ /** The text after the colon, wrapped lines joined. */
124
+ text: string;
125
+ /** 1-based line number in the file. */
126
+ line: number;
127
+ /** The bullet through its last continuation line. */
128
+ range: TextRange;
129
+ /** The text the panel edits, from the colon to the end of the last continuation line. */
130
+ textRange: TextRange;
131
+ }
132
+ /**
133
+ * One `- Recipe (phase): ...` bullet under a step: what a person did for that step during `/explore-case --attended`,
134
+ * as a general recipe (positions, `[asrun]` names, control labels). It is not test data: the asserted values live in
135
+ * `## Values`.
136
+ */
137
+ export interface CaseRecipe {
138
+ /** The phase label in parentheses, e.g. `start`; null when not written. */
139
+ what: string | null;
140
+ /** The text after the colon, wrapped lines joined. */
141
+ text: string;
142
+ /** 1-based line number in the file. */
143
+ line: number;
144
+ /** The bullet through its last continuation line. */
145
+ range: TextRange;
146
+ /** The text the panel edits, from the colon to the end of the last continuation line. */
147
+ textRange: TextRange;
148
+ }
149
+ export type QuestionKind = 'blocking' | 'non-blocking';
150
+ export interface CaseQuestion {
151
+ kind: QuestionKind;
152
+ /** The question itself, without the marker and without the trailing evidence in parentheses. Wrapped lines are joined. */
153
+ question: string;
154
+ /** What the page showed, in parentheses at the end of the question; null when there is none. */
155
+ evidence: string | null;
156
+ /**
157
+ * The block the question hangs under: one list item or one paragraph, wrapped lines joined. It stays in the case when
158
+ * the block is accepted. Null when there is no such text (a heading, a code fence or the start of the file above it).
159
+ */
160
+ parent: {
161
+ text: string;
162
+ line: number;
163
+ from: number;
164
+ } | null;
165
+ section: string;
166
+ line: number;
167
+ indent: number;
168
+ /** Text written after `Answer:` ('' when empty or when there is no answer line). */
169
+ answer: string;
170
+ /** The text after `Answer:` (an empty range at the end of that line when nothing was typed). */
171
+ answerRange: TextRange;
172
+ hasAnswerLine: boolean;
173
+ /** Line number of the `Answer:` line; null when the block has none. */
174
+ answerLine: number | null;
175
+ /** Line number of the last line of the block, to check that the answer is the last one. */
176
+ lastLine: number;
177
+ /** The TODO line through the last line of its block (the answer and any continuation), without the final line break. */
178
+ blockRange: TextRange;
179
+ /**
180
+ * The question with the text it hangs under: from the first line of `parent` (the TODO line when there is none)
181
+ * through the last line of its block. The case panel highlights it when its row is clicked.
182
+ */
183
+ region: TextRange;
184
+ }
185
+ export type MarkerKind = 'confirm' | 'unseen';
186
+ /** `🔍 to be confirmed by QA` and `👁️ not observed`: markers of what was seen but not checked by a person. */
187
+ export interface CaseMarker {
188
+ kind: MarkerKind;
189
+ /** The marker as written, including its emoji. */
190
+ text: string;
191
+ range: TextRange;
192
+ /** The whole line, for display. */
193
+ sentence: string;
194
+ section: string;
195
+ line: number;
196
+ }
197
+ export type CaseValues = Record<string, Record<string, unknown>>;
198
+ export interface CaseValuesBlock {
199
+ /** False when "## Values" exists but has no complete fenced yaml block (nothing can be edited then). */
200
+ hasBlock: boolean;
201
+ /** The yaml text between the fences. */
202
+ range: TextRange;
203
+ values: CaseValues;
204
+ /** Why the block cannot be read or edited; null when it is fine. */
205
+ problem: string | null;
206
+ /** Values that are empty or start with TODO, as `profile.key`: `requireValues` refuses them at run time. */
207
+ placeholders: string[];
208
+ }
209
+ /** One line of the old `## Inputs` section; new cases put the same text in a `- Recipe (...)` line under the step. */
210
+ export interface CaseInput {
211
+ /** The step number the line belongs to; null for a free note (a bullet with no `Step <n>`). */
212
+ step: number | null;
213
+ /** The label in parentheses after the step number, e.g. `start` or `items`; null when not written. */
214
+ what: string | null;
215
+ /** The text after the colon (a recipe), or the whole bullet of a note. Wrapped lines are joined. */
216
+ recipe: string;
217
+ line: number;
218
+ /** The bullet through its last continuation line. */
219
+ range: TextRange;
220
+ /** The text the panel edits, from the colon (or the bullet marker) to the end of the last continuation line. */
221
+ recipeRange: TextRange;
222
+ }
223
+ export interface CaseDocument {
224
+ text: string;
225
+ frontMatter: CaseFrontMatter | null;
226
+ sections: CaseSection[];
227
+ steps: CaseStep[];
228
+ questions: CaseQuestion[];
229
+ markers: CaseMarker[];
230
+ values: CaseValuesBlock | null;
231
+ targets: CaseTargetCard[];
232
+ inputs: CaseInput[];
233
+ }
234
+ /**
235
+ * The body of `## <name>`: exactly the regex every reader used before this module existed
236
+ * (`lib/values.ts`, `lib/targets.ts`, `scripts/check-cases.ts`), so behaviour does not change.
237
+ */
238
+ export declare function caseSectionText(text: string, name: string): string;
239
+ export declare function splitLines(text: string): CaseLine[];
240
+ /** Offset just after the line break that starts at `end` (or `end` itself at the end of the file). */
241
+ export declare function endWithBreak(text: string, end: number): number;
242
+ /** Offset just after the line break of `line` (also when the line is the last one, without a break). */
243
+ export declare const afterLine: (text: string, line: CaseLine) => number;
244
+ /** The front matter block: its parsed data plus the range of every key, so the panel can edit one key at a time. */
245
+ export declare function parseCaseFrontMatter(text: string): CaseFrontMatter | null;
246
+ /** A prose section (`## Goal`, `## Notes`): the panel renders it read-only and reveals a line on click. */
247
+ export declare function parseProse(text: string, name: string): CaseProse | null;
248
+ /** The top-level bullets of a list section, with the blocks (a wrapped text, a TODO block) that belong to each. */
249
+ export declare function parseBullets(text: string, name: string): CaseBullet[];
250
+ /** The `### T<n>` cards of `## Targets`. Pure, so the web panel can show a card name next to a visual check. */
251
+ export declare function parseTargetCards(text: string): CaseTargetCard[];
252
+ /** Parses and validates the front matter. Throws an Error that starts with `label` and says what to fix. */
253
+ export declare function validateCaseMeta(id: string, data: Record<string, unknown>, knownEnvs: string[], label?: string): CaseMeta;
254
+ /** The front matter problems a form can show without throwing; empty when it is valid. */
255
+ export declare function caseMetaProblems(id: string, data: Record<string, unknown>, knownEnvs: string[], label?: string): string[];
256
+ export declare function parseCaseDocument(text: string): CaseDocument;
257
+ /** Applies replacements that do not overlap. Positions refer to `text`, the text they were parsed from. */
258
+ export declare function patchText(text: string, edits: TextEdit[]): string;
259
+ /** Adds or removes one `[writes]` / `[input]` / `[email]` tag at the end of a step. */
260
+ export declare function setStepTag(text: string, stepNumber: number, tag: StepTag, on: boolean): string;
261
+ /** Writes an answer after `Answer:`; an empty answer clears the slot and keeps the line. */
262
+ export declare function setQuestionAnswer(text: string, question: CaseQuestion, answer: string): string;
263
+ /** Accepts the suggestion: deletes the whole question block, the answer line included. */
264
+ export declare function acceptQuestion(text: string, question: CaseQuestion): string;
265
+ /**
266
+ * Removes a `🔍 to be confirmed by QA` / `👁️ not observed` marker. When the marker is the only content of its
267
+ * parentheses, the parentheses go too (`(👁️ not observed)`); otherwise the marker and one separator leave
268
+ * `(observed, 🔍 to be confirmed by QA)` as `(observed)`.
269
+ */
270
+ export declare function removeMarker(text: string, marker: CaseMarker): string;
271
+ /** Sets one value of one profile; `null` removes it (an empty cell in the panel means "not set here"). */
272
+ export declare function setCaseValue(text: string, profile: string, key: string, value: string | null): string;
273
+ /** Adds a key to the `default` values of the case. */
274
+ export declare function addCaseValueKey(text: string, key: string, value: string): string;
275
+ /** Writes the text of one `## Inputs` line (a recipe or a note); an empty text deletes the line. */
276
+ export declare function setInputText(text: string, input: CaseInput, value: string): string;
277
+ /** Deletes one `## Inputs` bullet (a recipe line or a note), its wrapped lines included. */
278
+ export declare function removeInputLine(text: string, input: CaseInput): string;
279
+ /** Writes the text of one recipe line under a step; an empty text deletes the line. */
280
+ export declare function setRecipeText(text: string, recipe: CaseRecipe, value: string): string;
281
+ /** Deletes one recipe line under a step, its wrapped lines included. */
282
+ export declare function removeRecipe(text: string, recipe: CaseRecipe): string;
283
+ /** Writes the text of one `- Ready when:` line under a step; an empty text deletes the line. */
284
+ export declare function setStepReady(text: string, signal: CaseReadySignal, value: string): string;
285
+ /** Deletes one `- Ready when:` line under a step, its wrapped lines included. */
286
+ export declare function removeStepReady(text: string, signal: CaseReadySignal): string;
287
+ /**
288
+ * Adds a `- Ready when: ...` line under a step, right under the step sentence and above its recipes: readiness is read
289
+ * first, then what a person did. A second line goes under the first.
290
+ */
291
+ export declare function addStepReady(text: string, stepNumber: number, value: string): string;
292
+ /**
293
+ * Adds a `- Recipe (phase): ...` line under a step. It goes after the step's last recipe, or after its last readiness
294
+ * line, or directly under the step sentence, so a step keeps its readiness and recipes together and no separate input
295
+ * section is needed.
296
+ */
297
+ export declare function addStepRecipe(text: string, stepNumber: number, what: string | null, value: string): string;
298
+ /** Sets one front matter key (a scalar or a list); `null` or an empty list removes it. */
299
+ export declare function setFrontMatterValue(text: string, key: string, value: string | string[] | null): string;
300
+ /** Removes one front matter key and its lines. */
301
+ export declare function removeFrontMatterKey(text: string, key: string): string;
302
+ /** Sets the sentence of a step (a multi-line value keeps its wrapping, indented under the number). */
303
+ export declare function setStepText(text: string, step: CaseStep, value: string): string;
304
+ /** Sets the text of one bullet (`## Expected`, `## Visual checks`); a nested TODO block is not touched. */
305
+ export declare function setBulletText(text: string, bullet: CaseBullet, value: string): string;
306
+ /** Appends a bullet to a list section. Returns the text unchanged when the section does not exist. */
307
+ export declare function addBullet(text: string, name: string, value: string): string;
308
+ /** Deletes one bullet and everything under it (a nested question block included). */
309
+ export declare function removeBullet(text: string, bullet: CaseBullet): string;
310
+ /** Moves one bullet block (and everything under it) to another position, with `arrayMove` semantics. */
311
+ export declare function moveBullet(text: string, name: string, from: number, to: number): string;