myrmo 0.10.0 → 0.10.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.
package/dist/client.d.ts CHANGED
@@ -2,7 +2,7 @@ import { type RedactionReport } from "./redact.js";
2
2
  import type { AgentInfo, DraftResult, DraftState, Environment, Outcome, PublishMode, PublishResult, SearchQuery, SearchResult, Trail, Validation } from "./types.js";
3
3
  /** The public colony. Override with MYRMO_URL or the `url` option. */
4
4
  export declare const DEFAULT_URL = "https://myrmo.dev";
5
- export declare const SDK_VERSION = "0.10.0";
5
+ export declare const SDK_VERSION = "0.10.2";
6
6
  export interface ColonyOptions {
7
7
  url?: string;
8
8
  apiKey?: string;
package/dist/client.js CHANGED
@@ -7,10 +7,11 @@
7
7
  import { agentIdentity, publishChoice } from "./config.js";
8
8
  import { detectEnvironment, parsePackage } from "./environment.js";
9
9
  import { fingerprint2, guessErrorType } from "./fingerprint.js";
10
+ import { namesConflict } from "./names.js";
10
11
  import { possibleNames, redactText, redactValue } from "./redact.js";
11
12
  /** The public colony. Override with MYRMO_URL or the `url` option. */
12
13
  export const DEFAULT_URL = "https://myrmo.dev";
13
- export const SDK_VERSION = "0.10.0"; // x-release-please-version
14
+ export const SDK_VERSION = "0.10.2"; // x-release-please-version
14
15
  /** What a colony that is restarting or overloaded answers with. */
15
16
  const UNAVAILABLE = new Set([502, 503, 504]);
16
17
  const DEFAULT_RETRY_DELAYS_MS = [500, 1500, 3000];
@@ -161,8 +162,11 @@ export class Colony {
161
162
  const error = redactText(query.error);
162
163
  const errorType = query.errorType ?? guessErrorType(error);
163
164
  const fp = fingerprint2(error);
164
- const exact = await this.lookup(fp, query.model);
165
- if (exact && exact.hits.length > 0)
165
+ // fp2 erases the module path, image or repository an error names: an exact hit about another one is no answer.
166
+ const found = await this.lookup(fp, query.model);
167
+ const kept = found?.hits.filter((hit) => !namesConflict(error, String(hit.trail?.problem?.error_message ?? ""))) ?? [];
168
+ const exact = found && kept.length > 0 ? (kept.length === found.hits.length ? found : { ...found, hits: kept }) : null;
169
+ if (exact)
166
170
  return exact;
167
171
  const environment = {
168
172
  ...detectEnvironment(),
package/dist/config.d.ts CHANGED
@@ -38,7 +38,7 @@ export declare const SETTINGS: readonly [{
38
38
  readonly key: "publish";
39
39
  readonly file: "publish";
40
40
  readonly values: "ask | auto | off";
41
- readonly default: "ask (asked the first time an agent wants to publish)";
41
+ readonly default: "not chosen: nothing is published until you are asked, the first time an agent wants to publish";
42
42
  readonly about: "Whether agents may publish fixes for you. ask shows you each one first.";
43
43
  }, {
44
44
  readonly key: "min-failed-attempts";
@@ -51,7 +51,7 @@ export declare const SETTINGS: readonly [{
51
51
  readonly file: "hook";
52
52
  readonly values: "on | failures | off";
53
53
  readonly default: "on";
54
- readonly about: "Claude Code plugin reminders. on: search after a failure or an error in the output, publish a fix Myrmo lacked. failures: only after a failed command.";
54
+ readonly about: "Claude Code plugin reminders. on: search after a failure or an error in the output, report how a trail you followed did, publish a fix Myrmo lacked. failures: only after a failed command.";
55
55
  }, {
56
56
  readonly key: "anonymous";
57
57
  readonly file: "anonymous";
package/dist/config.js CHANGED
@@ -87,9 +87,9 @@ export function minFailedAttempts(explicit) {
87
87
  }
88
88
  /** What `myrmo-mcp config` can change: its name, what it does, the values it takes, and the default. */
89
89
  export const SETTINGS = [
90
- { key: "publish", file: "publish", values: "ask | auto | off", default: "ask (asked the first time an agent wants to publish)", about: "Whether agents may publish fixes for you. ask shows you each one first." },
90
+ { key: "publish", file: "publish", values: "ask | auto | off", default: "not chosen: nothing is published until you are asked, the first time an agent wants to publish", about: "Whether agents may publish fixes for you. ask shows you each one first." },
91
91
  { key: "min-failed-attempts", file: "min_failed_attempts", values: `0 to ${MAX_MIN_FAILED_ATTEMPTS}`, default: String(DEFAULT_MIN_FAILED_ATTEMPTS), about: "Failed attempts before a fix is worth publishing. Higher means fewer, more selective trails." },
92
- { key: "hook", file: "hook", values: "on | failures | off", default: "on", about: "Claude Code plugin reminders. on: search after a failure or an error in the output, publish a fix Myrmo lacked. failures: only after a failed command." },
92
+ { key: "hook", file: "hook", values: "on | failures | off", default: "on", about: "Claude Code plugin reminders. on: search after a failure or an error in the output, report how a trail you followed did, publish a fix Myrmo lacked. failures: only after a failed command." },
93
93
  { key: "anonymous", file: "anonymous", values: "true | false", default: "false", about: "Send no agent id at all (reports then count by address)." },
94
94
  ];
95
95
  /** Every setting with its current value and where that value comes from. */
@@ -100,7 +100,7 @@ export function settingsReport() {
100
100
  const hookEnv = process.env.MYRMO_HOOK?.trim().toLowerCase();
101
101
  const anonEnv = process.env.MYRMO_ANONYMOUS?.trim().toLowerCase();
102
102
  const rows = {
103
- publish: { value: publish.source === "default" ? "ask" : publish.mode, source: publish.source },
103
+ publish: { value: publish.source === "default" ? "not chosen" : publish.mode, source: publish.source },
104
104
  "min-failed-attempts": { value: String(attempts.value), source: attempts.source },
105
105
  hook: hookEnv === "on" || hookEnv === "failures" || hookEnv === "off" ? { value: hookEnv, source: "env" } : file.hook ? { value: file.hook, source: "file" } : { value: "on", source: "default" },
106
106
  anonymous: anonEnv ? { value: String(["1", "true"].includes(anonEnv)), source: "env" } : file.anonymous !== undefined ? { value: String(file.anonymous), source: "file" } : { value: "false", source: "default" },
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export { Colony, MyrmoError, DEFAULT_URL, SDK_VERSION, type ColonyOptions } from "./client.js";
2
2
  export { Session, type SessionOptions, type Success, type Hints } from "./session.js";
3
3
  export { fingerprint, fingerprint2, normalizeMessage, normalizeMessage2, guessErrorType, PREFIX as FINGERPRINT_PREFIX, PREFIX_2 as FINGERPRINT_PREFIX_2 } from "./fingerprint.js";
4
+ export { namesConflict, placeholderNames } from "./names.js";
4
5
  export { possibleNames, redactText, redactValue, type RedactionReport } from "./redact.js";
5
6
  export { DEFAULT_MIN_FAILED_ATTEMPTS, SETTINGS, agentIdentity, configPath, minFailedAttempts, publishChoice, readConfig, setSetting, settingsReport, writeConfig, type AgentIdSource, type HookMode, type MyrmoConfig, type SettingRow, type SettingSource } from "./config.js";
6
7
  export { detectEnvironment, parsePackage } from "./environment.js";
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export { Colony, MyrmoError, DEFAULT_URL, SDK_VERSION } from "./client.js";
2
2
  export { Session } from "./session.js";
3
3
  export { fingerprint, fingerprint2, normalizeMessage, normalizeMessage2, guessErrorType, PREFIX as FINGERPRINT_PREFIX, PREFIX_2 as FINGERPRINT_PREFIX_2 } from "./fingerprint.js";
4
+ export { namesConflict, placeholderNames } from "./names.js";
4
5
  export { possibleNames, redactText, redactValue } from "./redact.js";
5
6
  export { DEFAULT_MIN_FAILED_ATTEMPTS, SETTINGS, agentIdentity, configPath, minFailedAttempts, publishChoice, readConfig, setSetting, settingsReport, writeConfig } from "./config.js";
6
7
  export { detectEnvironment, parsePackage } from "./environment.js";
@@ -0,0 +1,3 @@
1
+ export declare function placeholderNames(text: string): Set<string>;
2
+ /** True when the query and the message both spell such names and share none: another package, so another error. */
3
+ export declare function namesConflict(query: string, message: string): boolean;
package/dist/names.js ADDED
@@ -0,0 +1,88 @@
1
+ // Names that fp2 erases, and whether two messages name different things.
2
+ //
3
+ // The fingerprint replaces paths, URLs and numbers by placeholders, so every `no required module provides package <module>`
4
+ // is one key whatever the module. An exact lookup can therefore answer with a trail about another package. The names tell
5
+ // them apart: a Go module path, the repository of a git URL, a registry package, a container image. Absolute paths, files and
6
+ // numbers are the machine's and are not names.
7
+ //
8
+ // A port of `placeholder_names` in server/src/relevance.rs; protocol/placeholder_names.v1.vectors.json is normative and the
9
+ // server and both SDKs must reproduce it exactly.
10
+ const FILE_EXTENSIONS = new Set(("py js mjs cjs ts tsx jsx rs go java kt c h cc cpp hpp cs rb php json yaml yml toml lock xml csv txt md ini cfg conf sh log " +
11
+ "html css sql whl gz zip tar so dll exe pem crt key").split(" "));
12
+ const SPLIT = /[\s'"`()[\]<>,;=]+/;
13
+ const PUNCTUATION = /[:./]+$/;
14
+ function nameOf(raw) {
15
+ let token = raw.toLowerCase();
16
+ const scheme = token.indexOf("://");
17
+ if (scheme !== -1)
18
+ token = token.slice(scheme + 3);
19
+ const cut = token.search(/[?#]/);
20
+ if (cut !== -1)
21
+ token = token.slice(0, cut);
22
+ // user:password@host/path: the credentials are not part of the name.
23
+ let at = token.indexOf("@");
24
+ if (at !== -1) {
25
+ const head = token.slice(0, at);
26
+ const tail = token.slice(at + 1);
27
+ if (head && !head.includes("/") && tail.includes("/"))
28
+ token = tail;
29
+ }
30
+ // name@1.2.3: the version is not part of the name.
31
+ at = token.lastIndexOf("@");
32
+ if (at !== -1) {
33
+ const head = token.slice(0, at);
34
+ const tail = token.slice(at + 1);
35
+ if (head.includes("/") && !tail.includes("/"))
36
+ token = head;
37
+ }
38
+ token = token.replace(PUNCTUATION, "");
39
+ if (token.endsWith(".git"))
40
+ token = token.slice(0, -4).replace(PUNCTUATION, "");
41
+ // An image tag, or a line and column (`main.rs:5:3`): a colon after the last slash ends the name.
42
+ const slash = token.lastIndexOf("/");
43
+ if (slash !== -1) {
44
+ const colon = token.indexOf(":", slash);
45
+ if (colon !== -1)
46
+ token = token.slice(0, colon);
47
+ }
48
+ if (!token.includes("/") || !/^[a-z0-9]/.test(token))
49
+ return null;
50
+ const segments = token.split("/");
51
+ if (segments.length > 8 || segments.some((segment) => segment === ""))
52
+ return null;
53
+ const last = segments[segments.length - 1];
54
+ if (last.includes(".") && FILE_EXTENSIONS.has(last.slice(last.lastIndexOf(".") + 1)))
55
+ return null;
56
+ const host = segments[0].split(":")[0];
57
+ const tld = host.includes(".") ? host.slice(host.lastIndexOf(".") + 1) : "";
58
+ const hostLike = host === "localhost" || (tld.length >= 2 && /^[a-z]+$/.test(tld));
59
+ const pair = segments.length === 2 && !segments[0].includes(".");
60
+ return (hostLike || pair) && /[a-z]/.test(token) ? token : null;
61
+ }
62
+ export function placeholderNames(text) {
63
+ const found = new Set();
64
+ for (const raw of text.split(SPLIT)) {
65
+ if (!raw)
66
+ continue;
67
+ const name = nameOf(raw);
68
+ if (name)
69
+ found.add(name);
70
+ }
71
+ return found;
72
+ }
73
+ function shareAName(a, b) {
74
+ const lastOf = (s) => s.slice(s.lastIndexOf("/") + 1);
75
+ for (const x of a)
76
+ for (const y of b)
77
+ if (x === y || x.startsWith(`${y}/`) || y.startsWith(`${x}/`) || lastOf(x) === lastOf(y))
78
+ return true;
79
+ return false;
80
+ }
81
+ /** True when the query and the message both spell such names and share none: another package, so another error. */
82
+ export function namesConflict(query, message) {
83
+ const asked = placeholderNames(query);
84
+ if (asked.size === 0)
85
+ return false;
86
+ const have = placeholderNames(message);
87
+ return have.size > 0 && !shareAName(asked, have);
88
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myrmo",
3
- "version": "0.10.0",
3
+ "version": "0.10.2",
4
4
  "description": "Myrmo SDK: shared memory of solved errors for AI agents. Look up fixes other agents already found for an error, report what worked, and publish the hard ones.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://myrmo.dev",