@takara-ai/miru-code 1.6.0 → 1.7.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.
@@ -0,0 +1,218 @@
1
+ /**
2
+ * STE mode — on-demand Agent Skill + references (clear technical English).
3
+ * Unofficial ASD-STE100-inspired aid; not ASD-certified.
4
+ */
5
+
6
+ export const STE_SKILL_NAME = "ste";
7
+
8
+ export interface SteReferenceFile {
9
+ relativePath: string;
10
+ content: string;
11
+ }
12
+
13
+ /** Compact pragmatic `SKILL.md` (YAML frontmatter + body). */
14
+ export const STE_SKILL_MD = `---
15
+ name: ste
16
+ description: >-
17
+ Clear technical English for docs, runbooks, errors, and release notes.
18
+ Use when the user says STE, ASD-STE100, /ste, de-slop, write for non-native
19
+ readers, or asks to rewrite technical prose. Pragmatic by default; strict
20
+ when they ask for STE compliance. Not for marketing or brand copy. Not for
21
+ ultra-compressed chat telegraph style.
22
+ ---
23
+
24
+ # STE writing (Simplified Technical English)
25
+
26
+ On-demand clear technical writing for Miru. **Short complete sentences. Keep articles.**
27
+
28
+ Unofficial aid inspired by [ASD-STE100](https://www.asd-ste100.org) Issue 9 themes.
29
+ **Not ASD-certified. Not ASD-endorsed.** Full dictionary compliance needs the official standard at asd-ste100.org.
30
+
31
+ Inspired by prior art ([AminBlg/SimpleEnglish](https://github.com/AminBlg/SimpleEnglish), [woosal STE kit](https://github.com/woosal1337/blog/tree/main/videos/ep01-the-cure-for-ai-slop)) — original Miru skill text. Do not vend copyrighted ASD dictionary word lists.
32
+
33
+ ## Activate
34
+
35
+ | User says | Effect |
36
+ |-----------|--------|
37
+ | \`/ste\`, \`STE\`, \`write in STE\`, \`de-slop this\` | ON **pragmatic** (default) |
38
+ | \`ASD-STE100\`, \`strict STE\`, \`STE compliance\` | ON **strict** — also load \`references/rules.md\` |
39
+ | Check / audit my STE | Use \`references/checklist.md\` |
40
+
41
+ ## Task flow
42
+
43
+ 1. Select mode (pragmatic vs strict)
44
+ 2. Classify text as **procedural** or **descriptive**
45
+ 3. Pick one consistent term per concept
46
+ 4. Apply core rules below
47
+ 5. Run **self-check**
48
+ 6. Deliver
49
+
50
+ ## Modes
51
+
52
+ | Mode | Behaviour |
53
+ |------|-----------|
54
+ | **Pragmatic** (default) | Structural rules + consistent terms. Domain nouns/verbs OK (\`webhook\`, \`deploy\`, \`commit\`). |
55
+ | **Strict** | Stricter vocabulary discipline. Tell the user full compliance needs the official ASD dictionary: https://www.asd-ste100.org — then load \`references/rules.md\`. |
56
+
57
+ Cite rule numbers **only** when they appear in \`references/rules.md\`. Do not invent ASD rule IDs.
58
+
59
+ ## Core rules (pragmatic)
60
+
61
+ - Short **complete** sentences. Keep \`a\` / \`an\` / \`the\` and \`that\`. No telegraph omission.
62
+ - No semicolons — use two sentences.
63
+ - **Procedural:** imperative; about 20 words max per sentence; one instruction per sentence; **condition BEFORE command**.
64
+ - **Descriptive:** simple tenses; about 25 words max per sentence; one topic per paragraph.
65
+ - Active voice; simple tenses; avoid present-perfect stacks and comma "-ing" padding.
66
+ - Prefer modals \`can\` / \`will\` / \`must\`. Avoid \`should\` / \`would\` / \`may\` / \`might\` / \`could\` in instructions.
67
+ - One name per concept in the document (no check/verify/confirm/validate roulette).
68
+ - Warnings: state the command or condition first, then the risk.
69
+
70
+ ## Untouchables
71
+
72
+ Never alter:
73
+
74
+ - Code fences, identifiers, CLI flags, paths
75
+ - Quoted errors, product/API names
76
+ - Required safety conditions, limits, versions
77
+
78
+ If shortening would drop a required qualifier, keep the longer clear sentence.
79
+ Miru MCP tool use stays intact when search policy is installed.
80
+
81
+ ## Not for marketing
82
+
83
+ Do **not** apply STE to marketing, brand, or persuasive copy. Decline and offer a docs-oriented rewrite instead.
84
+
85
+ ## Slop → plain (examples)
86
+
87
+ | Slop | Plain |
88
+ |------|-------|
89
+ | It is worth noting that the service is highly robust | The service stays up if the primary node fails |
90
+ | You should consider carefully validating the input | You must validate the input |
91
+ | Feel free to simply leverage the existing utility | Use the existing utility |
92
+
93
+ ## Before / after
94
+
95
+ **Before (slop):**
96
+ > It is worth noting that you should carefully ensure the migration has completed successfully before you proceed to delete the old volume, as doing so might potentially result in irreversible data loss.
97
+
98
+ **After (STE pragmatic):**
99
+ > Wait until the migration is complete. Then delete the old volume. If you delete the volume too early, you will lose data permanently.
100
+
101
+ ## Self-check (before delivery)
102
+
103
+ 1. Sentences complete with articles where needed?
104
+ 2. One instruction per procedural sentence; condition before command?
105
+ 3. Terms consistent; modals are can/will/must where instructions appear?
106
+ 4. Code, paths, errors, and safety limits untouched?
107
+
108
+ For a longer audit, read \`references/checklist.md\`.
109
+
110
+ ## References
111
+
112
+ - \`references/rules.md\` — fuller paraphrased rule catalog (strict / audit)
113
+ - \`references/checklist.md\` — extended self-check patterns
114
+ `;
115
+
116
+ export const STE_RULES_MD = `# STE rules catalog (Miru paraphrase)
117
+
118
+ Unofficial structural catalog for Miru STE **strict** / audit use.
119
+ Themes follow ASD-STE100 Issue 9. **Not a copy of the official standard. Not a dictionary.**
120
+ Official standard and dictionary: https://www.asd-ste100.org
121
+
122
+ Cite the rule IDs below only. Do not invent other ASD numbers.
123
+
124
+ ## Words and terms
125
+
126
+ ### R1 — One name per concept
127
+ Pick one term for each idea in the document. Do not rotate synonyms (\`check\` / \`verify\` / \`validate\`) for the same action.
128
+
129
+ ### R2 — Approved instruction modals
130
+ In procedures, prefer \`can\`, \`will\`, \`must\`. Avoid \`should\`, \`would\`, \`may\`, \`might\`, \`could\` when you give an instruction.
131
+
132
+ ### R3 — Keep articles and "that"
133
+ Write complete noun phrases. Keep \`a\`, \`an\`, \`the\`, and \`that\`. Do not drop them for brevity.
134
+
135
+ ### R4 — No semicolon glue
136
+ Do not join two ideas with a semicolon. Use two sentences.
137
+
138
+ ## Sentences
139
+
140
+ ### R5 — Procedural length
141
+ In procedures, aim for about **20 words or fewer** per sentence. Give **one instruction** per sentence.
142
+
143
+ ### R6 — Descriptive length
144
+ In descriptions, aim for about **25 words or fewer** per sentence. Keep **one topic** per paragraph.
145
+
146
+ ### R7 — Condition before command
147
+ State the condition first. Then give the command.
148
+
149
+ Example: \`If the node is down, stop the job.\`
150
+ Not: \`Stop the job if the node is down.\` when the condition is the safety gate.
151
+
152
+ ### R8 — Active voice and simple tense
153
+ Prefer active voice and simple present or simple past. Avoid stacked present-perfect and trailing "-ing" clauses that hide the actor.
154
+
155
+ ## Safety
156
+
157
+ ### R9 — Warning shape
158
+ For destructive actions: state the command or the condition clearly, then state the risk.
159
+
160
+ Example: \`Do not run this on production. This command deletes all rows in the table.\`
161
+
162
+ ### R10 — Do not drop required limits
163
+ If a version, limit, or condition is required for safety, keep it. Clarity beats compression.
164
+
165
+ ## Software examples
166
+
167
+ | Weak | Stronger STE shape |
168
+ |------|--------------------|
169
+ | You might want to restart the pod | Restart the pod |
170
+ | Ensure that connectivity has been established | Make sure the connection is up |
171
+ | After having deployed the chart, proceed to verify | Deploy the chart. Then check the release status |
172
+
173
+ ## Dictionary
174
+
175
+ Miru does **not** ship the ASD approved-word list. For strict vocabulary compliance, use the official dictionary from asd-ste100.org. Domain technical nouns (\`Kubernetes\`, \`webhook\`, \`IAM\`) stay as product names (untouchable).
176
+ `;
177
+
178
+ export const STE_CHECKLIST_MD = `# STE extended checklist (Miru)
179
+
180
+ Use this for **check mode** / audits after a rewrite. Unofficial. Not ASD certification.
181
+
182
+ ## Structure
183
+
184
+ - [ ] Procedural sentences ≈ ≤20 words; one instruction each
185
+ - [ ] Descriptive sentences ≈ ≤25 words; one topic per paragraph
186
+ - [ ] Condition appears before the command when safety depends on it
187
+ - [ ] No semicolons used as sentence glue
188
+ - [ ] Articles and \`that\` kept (not telegraph)
189
+
190
+ ## Wording
191
+
192
+ - [ ] One term per concept across the document
193
+ - [ ] Instruction modals are can / will / must (not should / might / could)
194
+ - [ ] Active voice; simple tenses
195
+ - [ ] No marketing filler ("robust", "seamless", "it is worth noting")
196
+
197
+ ## Safety and precision
198
+
199
+ - [ ] Destructive steps warn with command/condition first, then risk
200
+ - [ ] Required versions, limits, and conditions still present
201
+ - [ ] Code, paths, CLI, quoted errors unchanged
202
+
203
+ ## Scope
204
+
205
+ - [ ] Not applied to marketing / brand copy
206
+ - [ ] Strict mode users pointed at https://www.asd-ste100.org for the official dictionary
207
+ - [ ] Disclaimer remembered: Miru STE is unofficial / not certified
208
+
209
+ ## Delivery
210
+
211
+ - [ ] Self-check in the main skill completed before send
212
+ `;
213
+
214
+ /** Files written under the skill directory beside `SKILL.md`. */
215
+ export const STE_REFERENCE_FILES: SteReferenceFile[] = [
216
+ { relativePath: "references/rules.md", content: STE_RULES_MD },
217
+ { relativePath: "references/checklist.md", content: STE_CHECKLIST_MD },
218
+ ];
@@ -3,20 +3,54 @@ import {
3
3
  checkDeviceAuthorizationOnce,
4
4
  type DeviceAuthConfig,
5
5
  type DeviceAuthorizationStart,
6
+ openBrowserForDeviceLogin,
6
7
  resolveDeviceAuthConfig,
7
8
  startDeviceAuthorization,
8
9
  } from "../auth/device.ts";
10
+ import { isCredentialsError } from "../auth/errors.ts";
9
11
  import { saveStoredCredentials, setStoredCredentialsEnvToken } from "../credentials.ts";
10
12
  import { toolText } from "./index-cache.ts";
11
13
  import type { MiruMcpServer } from "./runtime.ts";
12
14
 
15
+ type ToolResult = ReturnType<typeof toolText>;
16
+
17
+ const CHECK_HINT =
18
+ 'Once the user approves, call `auth` again with action "check" to finish signing in.';
19
+
20
+ /** Appended to credentials failures so an agent knows Miru's own recovery step. */
21
+ const RECOVERY_HINT =
22
+ 'Miru is not signed in. Call the `auth` tool with action "start" — it opens the Takara ' +
23
+ 'device-login page in the browser — then action "check" once the user approves.';
24
+
13
25
  const AUTH_TOOL_DESCRIPTION =
14
26
  "Sign in with Takara credentials via device-code login — no terminal required. " +
15
27
  "Only call this in direct response to a tool error mentioning missing/expired " +
16
28
  "credentials — never speculatively, since it starts a real sign-in prompt for the " +
17
- 'user. Call with no arguments (or action "start") to begin: it returns a URL and a ' +
18
- "short code. Show both to the user and ask them to open the link and approve. Once " +
19
- 'they confirm, call again with action "check" to complete sign-in.';
29
+ 'user. Call with no arguments (or action "start") to begin: it opens the device-login ' +
30
+ `page in the user's browser and returns that URL plus a short code. ${CHECK_HINT}`;
31
+
32
+ /** Browser opener seam; the real one spawns a detached `open`/`xdg-open`/`start`. */
33
+ export type BrowserOpener = (url: string) => boolean;
34
+
35
+ /** Opening is on unless MIRU_OPEN_BROWSER is set to something other than "1" (matches `miru setup`). */
36
+ function openIfAllowed(open: BrowserOpener, url: string): boolean {
37
+ const raw = process.env.MIRU_OPEN_BROWSER;
38
+ if (raw !== undefined && raw !== "1") {
39
+ return false;
40
+ }
41
+ return open(url);
42
+ }
43
+
44
+ function approvalText(link: string, userCode: string, opened: boolean): string {
45
+ const lead = opened ? `A browser tab is open at ${link}` : `Ask the user to open ${link}`;
46
+ return `${lead}. Code: ${userCode}. ${CHECK_HINT}`;
47
+ }
48
+
49
+ /** Tool-failure text for every Miru tool, with the sign-in step added when relevant. */
50
+ export function toolErrorText(err: unknown): ToolResult {
51
+ const message = err instanceof Error ? err.message : String(err);
52
+ return toolText(isCredentialsError(err) ? `${message}\n\n${RECOVERY_HINT}` : message);
53
+ }
20
54
 
21
55
  type PendingDeviceAuth = {
22
56
  start: DeviceAuthorizationStart;
@@ -24,7 +58,7 @@ type PendingDeviceAuth = {
24
58
  startedAtMs: number;
25
59
  };
26
60
 
27
- function isExpired(entry: Pick<PendingDeviceAuth, "start" | "startedAtMs">): boolean {
61
+ function isExpired(entry: PendingDeviceAuth): boolean {
28
62
  return Date.now() >= entry.startedAtMs + entry.start.expiresIn * 1000;
29
63
  }
30
64
 
@@ -36,31 +70,24 @@ function isExpired(entry: Pick<PendingDeviceAuth, "start" | "startedAtMs">): boo
36
70
  class AuthToolState {
37
71
  private pending: PendingDeviceAuth | null = null;
38
72
 
39
- async start(): Promise<{ content: Array<{ type: "text"; text: string }> }> {
40
- if (this.pending && !isExpired(this.pending)) {
41
- const { userCode, verificationUriComplete, verificationUri } = this.pending.start;
42
- return toolText(
43
- `A device login is already pending. Open ${verificationUriComplete ?? verificationUri} ` +
44
- `and enter code ${userCode} if not already filled in, then call \`auth\` again with ` +
45
- 'action "check" once approved.',
46
- );
73
+ constructor(private readonly openBrowser: BrowserOpener) {}
74
+
75
+ async start(): Promise<ToolResult> {
76
+ let pending = this.pending;
77
+ if (!pending || isExpired(pending)) {
78
+ const config = resolveDeviceAuthConfig();
79
+ const start = await startDeviceAuthorization({ config });
80
+ pending = { start, config, startedAtMs: Date.now() };
81
+ this.pending = pending;
47
82
  }
48
- this.pending = null;
49
-
50
- const config = resolveDeviceAuthConfig();
51
- const start = await startDeviceAuthorization({ config });
52
- this.pending = { start, config, startedAtMs: Date.now() };
53
-
54
- const link = start.verificationUriComplete ?? start.verificationUri;
55
- return toolText(
56
- `Open ${link} and approve the request` +
57
- (start.verificationUriComplete ? "." : ` (enter code ${start.userCode} if prompted).`) +
58
- ` Code: ${start.userCode}. Once the user confirms they've approved it, call \`auth\` again ` +
59
- 'with action "check" to finish signing in.',
60
- );
83
+
84
+ const { verificationUriComplete, verificationUri, userCode } = pending.start;
85
+ const link = verificationUriComplete ?? verificationUri;
86
+ // Reopening on a repeat call is deliberate: the user may have closed the first tab.
87
+ return toolText(approvalText(link, userCode, openIfAllowed(this.openBrowser, link)));
61
88
  }
62
89
 
63
- async check(): Promise<{ content: Array<{ type: "text"; text: string }> }> {
90
+ async check(): Promise<ToolResult> {
64
91
  if (!this.pending) {
65
92
  return toolText('No device login is pending. Call `auth` with action "start" first.');
66
93
  }
@@ -106,8 +133,11 @@ class AuthToolState {
106
133
  }
107
134
  }
108
135
 
109
- export function registerAuthTool(server: MiruMcpServer): void {
110
- const state = new AuthToolState();
136
+ export function registerAuthTool(
137
+ server: MiruMcpServer,
138
+ options?: { openBrowser?: BrowserOpener },
139
+ ): void {
140
+ const state = new AuthToolState(options?.openBrowser ?? openBrowserForDeviceLogin);
111
141
 
112
142
  server.registerTool(
113
143
  "auth",
@@ -399,7 +399,8 @@ export async function getIndexForRepo(
399
399
  return await cache.get(repo, ref);
400
400
  } catch (err) {
401
401
  const message = err instanceof Error ? err.message : String(err);
402
- throw new Error(`Failed to index ${repo}: ${message}`);
402
+ // Keep `cause` so the tool boundary can still recognize a credentials failure.
403
+ throw new Error(`Failed to index ${repo}: ${message}`, { cause: err });
403
404
  }
404
405
  }
405
406
 
package/src/mcp/server.ts CHANGED
@@ -34,7 +34,7 @@ import {
34
34
  MAX_MCP_TOP_K,
35
35
  resolveChunk,
36
36
  } from "../utils.ts";
37
- import { registerAuthTool } from "./auth-tool.ts";
37
+ import { registerAuthTool, toolErrorText } from "./auth-tool.ts";
38
38
  import {
39
39
  formatExpandResultsText,
40
40
  formatLiteralLocateText,
@@ -161,7 +161,7 @@ export function createMcpServer(
161
161
  const body = formatResultsText(payload);
162
162
  return toolText(skip ? withBenchmarkSkippedNote(body, skip) : body);
163
163
  } catch (err) {
164
- return toolText(err instanceof Error ? err.message : String(err));
164
+ return toolErrorText(err);
165
165
  }
166
166
  },
167
167
  );
@@ -247,7 +247,7 @@ export function createMcpServer(
247
247
  const body = formatLiteralLocateText(payload);
248
248
  return toolText(skip ? withBenchmarkSkippedNote(body, skip) : body);
249
249
  } catch (err) {
250
- return toolText(err instanceof Error ? err.message : String(err));
250
+ return toolErrorText(err);
251
251
  }
252
252
  },
253
253
  );
@@ -308,7 +308,7 @@ export function createMcpServer(
308
308
  ),
309
309
  );
310
310
  } catch (err) {
311
- return toolText(err instanceof Error ? err.message : String(err));
311
+ return toolErrorText(err);
312
312
  }
313
313
  },
314
314
  );
@@ -360,7 +360,7 @@ export function createMcpServer(
360
360
  ),
361
361
  );
362
362
  } catch (err) {
363
- return toolText(err instanceof Error ? err.message : String(err));
363
+ return toolErrorText(err);
364
364
  }
365
365
  },
366
366
  );
package/src/spinner.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  import { dim, green, red } from "./cli-ui.ts";
2
+ import { displayWidth } from "./terminal.ts";
2
3
 
3
4
  const FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
4
5
 
5
6
  export class Spinner {
6
7
  private timer: ReturnType<typeof setInterval> | null = null;
7
8
  private frame = 0;
9
+ private belowLines = 0;
8
10
 
9
11
  constructor(private readonly message: string) {}
10
12
 
@@ -13,10 +15,22 @@ export class Spinner {
13
15
  process.stderr.write(`${this.message}...\n`);
14
16
  return;
15
17
  }
16
- this.timer = setInterval(() => {
17
- const glyph = FRAMES[this.frame++ % FRAMES.length];
18
- process.stderr.write(`\r${dim(glyph)} ${this.message}`);
19
- }, 80);
18
+ this.draw();
19
+ this.timer = setInterval(() => this.draw(), 80);
20
+ }
21
+
22
+ follow(line: string): void {
23
+ if (!process.stderr.isTTY) {
24
+ process.stderr.write(`${line}\n`);
25
+ return;
26
+ }
27
+ const cols = process.stderr.columns || 80;
28
+ const rows = line.split("\n").reduce((count, part) => {
29
+ return count + Math.max(1, Math.ceil(displayWidth(part) / cols));
30
+ }, 0);
31
+ process.stderr.write(`\n${line}`);
32
+ this.belowLines += rows;
33
+ process.stderr.write(`\x1b[${this.belowLines}A`);
20
34
  }
21
35
 
22
36
  stop(finalMessage?: string): void {
@@ -26,8 +40,16 @@ export class Spinner {
26
40
  }
27
41
  if (process.stderr.isTTY) {
28
42
  process.stderr.write("\r\x1b[K");
29
- }
30
- if (finalMessage) {
43
+ if (finalMessage) {
44
+ process.stderr.write(finalMessage);
45
+ }
46
+ if (this.belowLines > 0) {
47
+ process.stderr.write(`\x1b[${this.belowLines}B`);
48
+ }
49
+ if (finalMessage || this.belowLines > 0) {
50
+ process.stderr.write("\n");
51
+ }
52
+ } else if (finalMessage) {
31
53
  process.stderr.write(`${finalMessage}\n`);
32
54
  }
33
55
  }
@@ -43,6 +65,11 @@ export class Spinner {
43
65
  fail(message?: string): void {
44
66
  this.stop(red("✗ ") + (message ?? this.message));
45
67
  }
68
+
69
+ private draw(): void {
70
+ const glyph = FRAMES[this.frame++ % FRAMES.length];
71
+ process.stderr.write(`\r${dim(glyph)} ${this.message}`);
72
+ }
46
73
  }
47
74
 
48
75
  export async function withSpinner<T>(