@aistastudio/myc 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.
- package/LICENSE +18 -0
- package/README.md +156 -0
- package/bin/myc.js +103 -0
- package/bin/preflight.js +41 -0
- package/dist/myc.js +5828 -0
- package/dist/worker.ts +7 -0
- package/package.json +46 -0
- package/vendor/ort/README-onnxruntime.txt +3 -0
- package/vendor/ort/ort-wasm-simd-threaded.mjs +81 -0
- package/vendor/ort/ort-wasm-simd-threaded.wasm +0 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 aistastudio
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
|
6
|
+
associated documentation files (the "Software"), to deal in the Software without restriction, including
|
|
7
|
+
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
|
|
9
|
+
following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial
|
|
12
|
+
portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
|
15
|
+
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
|
|
16
|
+
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
17
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
18
|
+
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# myc
|
|
2
|
+
|
|
3
|
+
A local, fast task-and-memory layer for coding agents: a task queue, an oplog of
|
|
4
|
+
facts and decisions, and hybrid (lexical + vector) search over the project's
|
|
5
|
+
memory — with no network calls and no mandatory LLM key.
|
|
6
|
+
|
|
7
|
+
Agents forget. `myc` is the part that doesn't: decisions survive context
|
|
8
|
+
compaction, work survives process death, and both survive being moved between
|
|
9
|
+
machines through plain git.
|
|
10
|
+
|
|
11
|
+
Design docs live in `docs/design/` (start with `00-brief.md`); the measurements
|
|
12
|
+
quoted below are reproducible from `bench/` and `scripts/`.
|
|
13
|
+
|
|
14
|
+
## Requires Bun — this is not fine print
|
|
15
|
+
|
|
16
|
+
The runtime is bound to `bun:sqlite` (SQLite and `sqlite-vec` ship inside Bun,
|
|
17
|
+
with no native bindings on the Node side). **It will not start on plain Node.js
|
|
18
|
+
or Deno.** Bun ≥ 1.3.0 is required and pinned in `package.json` → `engines.bun`.
|
|
19
|
+
|
|
20
|
+
Install Bun: https://bun.sh
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
Once published, installation is one command — the package is built and verified
|
|
25
|
+
from a tarball today, but nothing has been pushed to the registry yet:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
bun add @aistastudio/myc # 3.16 MB, 9 files, no models pulled at install
|
|
29
|
+
bunx myc --version
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The embedding model is **not** downloaded during install. Semantic search is
|
|
33
|
+
opt-in and explicit: `myc models fetch` (129 MB, ~7 s). Until then search is
|
|
34
|
+
lexical and says so.
|
|
35
|
+
|
|
36
|
+
Until it is published, build from source:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
git clone <repo> && cd myc
|
|
40
|
+
bun install
|
|
41
|
+
bun run build # produces a single binary: dist/myc
|
|
42
|
+
./dist/myc --version
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Put the binary somewhere `myc wire` can find it — `MYC_BIN`, `node_modules/.bin`,
|
|
46
|
+
`~/.myc/bin/myc`, or `PATH`. If it can't, `wire` says so out loud instead of
|
|
47
|
+
writing a config that silently won't start.
|
|
48
|
+
|
|
49
|
+
## Quick start
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
./dist/myc init # .myc/ + SQLite + migrations in this repo
|
|
53
|
+
./dist/myc wire # hooks for Claude Code / Codex / opencode
|
|
54
|
+
./dist/myc ready # what can be picked up right now
|
|
55
|
+
./dist/myc remember "why X, not Y" # record a fact or decision
|
|
56
|
+
./dist/myc recall "how retrieval works"
|
|
57
|
+
./dist/myc prime # session context packet (agents call it)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Full command list: `./dist/myc --help`.
|
|
61
|
+
|
|
62
|
+
## What makes it different
|
|
63
|
+
|
|
64
|
+
**Speed is a constraint, not an optimisation.** Every hot path has a budget
|
|
65
|
+
enforced in CI; a p95 regression over 15% fails the build. Measured on 100 000
|
|
66
|
+
nodes (`bun run scripts/bench-latency.ts`):
|
|
67
|
+
|
|
68
|
+
| operation | p99 | budget |
|
|
69
|
+
|---|---|---|
|
|
70
|
+
| `prime` (session context) | 0.70 ms | 30 ms |
|
|
71
|
+
| read | 0.011 ms | 3 ms |
|
|
72
|
+
| search | 9.1 ms | 25 ms |
|
|
73
|
+
| write | 0.5 ms | 5 ms |
|
|
74
|
+
| cold start | 24 ms | 60 ms |
|
|
75
|
+
|
|
76
|
+
**Ranking is measured, not asserted.** Two labelled corpora with graded
|
|
77
|
+
relevance, each containing a *control group that gets worse* when the feature
|
|
78
|
+
works — so a gain cannot be manufactured by shaping the corpus:
|
|
79
|
+
|
|
80
|
+
- boosts (priority, freshness, layer): MRR@10 **0.520 → 0.867** (`bench/boost-eval.ts`)
|
|
81
|
+
- graph expansion to 2 hops: MRR@10 **0.193 → 0.422** (`bench/graph-eval.ts`),
|
|
82
|
+
and a query group unreachable in one hop goes 0.000 → 0.333
|
|
83
|
+
|
|
84
|
+
**Caching that cannot go stale silently.** Result, embedding and hydration
|
|
85
|
+
caches are invalidated by `MAX(oplog.seq)` read *from the database*, so a write
|
|
86
|
+
by another process invalidates them too. A cache hit is 162–198× cheaper than a
|
|
87
|
+
miss (≈25 000× for embeddings) and the ranking is bit-identical: same MRR to
|
|
88
|
+
three decimals, zero rank differences.
|
|
89
|
+
|
|
90
|
+
**Memory has three independent axes**, and the surface says what it hid:
|
|
91
|
+
tier (project vs personal), session reach, repository reach. `prime` prints
|
|
92
|
+
`N notes from other repositories hidden` rather than quietly narrowing results.
|
|
93
|
+
|
|
94
|
+
**Degradation is loud.** No silent fallbacks: when the vector branch is
|
|
95
|
+
unavailable the output says so and marks the answer as lexical-only; when a
|
|
96
|
+
budget is exceeded it is named with the number. The invariant is that a
|
|
97
|
+
degraded answer must never be indistinguishable from a healthy one.
|
|
98
|
+
|
|
99
|
+
**Multi-machine sync through plain git, merged per field.** Only the oplog is
|
|
100
|
+
committed. Two machines editing the same node converge: one changes title and
|
|
101
|
+
priority, the other title and tags — after exchange both show the later title,
|
|
102
|
+
the first machine's priority and the second's tags. Nothing is lost to
|
|
103
|
+
last-writer-wins over whole records.
|
|
104
|
+
|
|
105
|
+
**Migration from beads is real, not a demo.** A working project imported in
|
|
106
|
+
684 ms: 796 tasks, 972 dependencies, 265 notes, 41 memories — with unknown
|
|
107
|
+
issue types carried over verbatim and named, and out-of-range priorities
|
|
108
|
+
clamped and named, instead of one odd row aborting the import.
|
|
109
|
+
|
|
110
|
+
**Guards are proved by mutation.** Every refusal and every invariant is
|
|
111
|
+
accompanied by a mutation that removes it; a guard whose removal breaks no test
|
|
112
|
+
is treated as absent.
|
|
113
|
+
|
|
114
|
+
## Roadmap
|
|
115
|
+
|
|
116
|
+
Numbers are closed/total subtasks per milestone (`myc show <epic-id>`), as of
|
|
117
|
+
2026-09-07. Done and not-done are shown the same way on purpose.
|
|
118
|
+
|
|
119
|
+
| milestone | status |
|
|
120
|
+
|---|---|
|
|
121
|
+
| **M0** core and tasks | 30 / 33 |
|
|
122
|
+
| **M0.5** self-hosting (myc developed through myc) | **4 / 4 — closed** |
|
|
123
|
+
| **M1** memory | 20 / 23 |
|
|
124
|
+
| **M2** semantics | 15 / 19 |
|
|
125
|
+
| **M7** human interface (board, cards, threads, routing panel) | 11 / 13 |
|
|
126
|
+
| **M3** code intelligence | 0 / 7 |
|
|
127
|
+
| **M4** team: `myc serve`, ACL, network sync, Postgres, containers | 0 / 11 |
|
|
128
|
+
| **M5** swarm self-learning: routing by cost and outcome | 0 / 12 |
|
|
129
|
+
| **M6** distillation | 0 / 7 |
|
|
130
|
+
|
|
131
|
+
What that means in practice: **today myc is a single-user local tool over files
|
|
132
|
+
in git.** There is no server, no ACL, no team mode, and no code↔knowledge
|
|
133
|
+
anchors yet. Those are designed (`docs/design/03…`, `04…`, `05…`) and tracked,
|
|
134
|
+
not implemented.
|
|
135
|
+
|
|
136
|
+
## Syncing between machines
|
|
137
|
+
|
|
138
|
+
Only the oplog goes to git (`myc export` → `.myc/graph`, `myc import` on
|
|
139
|
+
clone) — never derived projections or caches. `.myc/workspace.toml` and the
|
|
140
|
+
oplog are committed deliberately; `.myc/myc.db*` and local state are not.
|
|
141
|
+
|
|
142
|
+
Register the merge driver once per clone:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
git config merge.myc-oplog.driver "myc merge-driver %O %A %B %L %P"
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## License
|
|
149
|
+
|
|
150
|
+
MIT — see [`LICENSE`](LICENSE). Chosen for the lowest possible friction for
|
|
151
|
+
anyone embedding or forking this; every runtime dependency (Bun, ONNX Runtime,
|
|
152
|
+
sqlite-vec) is permissive too.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
Russian version of this document: [`docs/README.ru.md`](docs/README.ru.md).
|
package/bin/myc.js
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Точка входа npm-пакета @myc/cli.
|
|
4
|
+
*
|
|
5
|
+
* Файл СОЗНАТЕЛЬНО написан на голом JS без единого `bun:`-импорта и без TS:
|
|
6
|
+
* его обязан уметь разобрать и выполнить Node. Иначе отказ выглядел бы как
|
|
7
|
+
* `Cannot find module 'bun:sqlite'` из глубины бандла — стек вместо причины
|
|
8
|
+
* (И2: деградация обязана быть громкой И понятной).
|
|
9
|
+
*
|
|
10
|
+
* Порядок важен: сначала проверка рантайма, и только потом ДИНАМИЧЕСКИЙ
|
|
11
|
+
* импорт бандла. Статический импорт Node разрешил бы до первой строки тела
|
|
12
|
+
* модуля — и мы бы снова упали на `bun:sqlite`, не успев ничего сказать.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { spawnSync } from "node:child_process";
|
|
16
|
+
import { createRequire } from "node:module";
|
|
17
|
+
import { dirname, join } from "node:path";
|
|
18
|
+
import { fileURLToPath } from "node:url";
|
|
19
|
+
|
|
20
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
|
|
22
|
+
if (typeof process.versions.bun !== "string") {
|
|
23
|
+
process.stderr.write(refusal());
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Файлы ONNX-рантайма лежат внутри пакета: JS-часть ort вшита в бандл, а .wasm
|
|
28
|
+
// грузится с диска по этому пути. Явное значение пользователя не трогаем.
|
|
29
|
+
const wasmDir = process.env.MYC_ORT_WASM_DIR;
|
|
30
|
+
if (typeof wasmDir !== "string" || wasmDir === "") {
|
|
31
|
+
process.env.MYC_ORT_WASM_DIR = join(here, "..", "vendor", "ort");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// vec0 (sqlite-vec) — векторный индекс. Автопоиск в @myc/store-sqlite смотрит
|
|
35
|
+
// рядом с process.execPath (у нас это bun, не пакет) и в кеш `bun install`,
|
|
36
|
+
// которого при установке через npm нет. Отдаём точный путь из зависимости.
|
|
37
|
+
// Явно заданный пользователем MYC_SQLITE_VEC не трогаем.
|
|
38
|
+
const vecEnv = process.env.MYC_SQLITE_VEC;
|
|
39
|
+
if (typeof vecEnv !== "string" || vecEnv === "") {
|
|
40
|
+
const vec = resolveVec0();
|
|
41
|
+
if (vec !== null) process.env.MYC_SQLITE_VEC = vec;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
await import("../dist/myc.js");
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Путь к vec0 внутри платформенного пакета sqlite-vec-<os>-<arch>. null —
|
|
48
|
+
* не нашли: тогда работает штатный автопоиск, а без него myc честно скажет
|
|
49
|
+
* `vec0 не загружен` и уйдёт на BM25.
|
|
50
|
+
*/
|
|
51
|
+
function resolveVec0() {
|
|
52
|
+
const os = { darwin: "darwin", linux: "linux", win32: "windows" }[process.platform];
|
|
53
|
+
const ext = { darwin: "dylib", linux: "so", win32: "dll" }[process.platform];
|
|
54
|
+
const cpu = { arm64: "arm64", x64: "x64" }[process.arch];
|
|
55
|
+
if (os === undefined || cpu === undefined) return null;
|
|
56
|
+
const file = `sqlite-vec-${os}-${cpu}/vec0.${ext}`;
|
|
57
|
+
try {
|
|
58
|
+
return createRequire(import.meta.url).resolve(file);
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function refusal() {
|
|
65
|
+
const node = process.versions.node;
|
|
66
|
+
const lines = [
|
|
67
|
+
"",
|
|
68
|
+
" myc requires Bun — it cannot run on Node.",
|
|
69
|
+
"",
|
|
70
|
+
` myc запущен под Node ${node}, а он работает только на Bun: хранилище`,
|
|
71
|
+
" построено на встроенном в Bun `bun:sqlite`, которого в Node нет.",
|
|
72
|
+
"",
|
|
73
|
+
];
|
|
74
|
+
if (bunOnPath()) {
|
|
75
|
+
lines.push(
|
|
76
|
+
" Bun у вас установлен — запускайте через него:",
|
|
77
|
+
" bun x myc <команда>",
|
|
78
|
+
" либо переустановите пакет средствами bun:",
|
|
79
|
+
" bun add -g @myc/cli",
|
|
80
|
+
"",
|
|
81
|
+
);
|
|
82
|
+
} else {
|
|
83
|
+
lines.push(
|
|
84
|
+
" Установите Bun (>= 1.3.0) и повторите:",
|
|
85
|
+
" curl -fsSL https://bun.sh/install | bash # macOS, Linux, WSL",
|
|
86
|
+
' powershell -c "irm bun.sh/install.ps1 | iex" # Windows',
|
|
87
|
+
"",
|
|
88
|
+
" После установки: myc --version",
|
|
89
|
+
"",
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
return lines.join("\n");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Только для текста отказа: лишний spawn на нормальном пути не делается. */
|
|
96
|
+
function bunOnPath() {
|
|
97
|
+
try {
|
|
98
|
+
const r = spawnSync("bun", ["--version"], { stdio: "ignore" });
|
|
99
|
+
return r.status === 0;
|
|
100
|
+
} catch {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
package/bin/preflight.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* postinstall-проверка. Запускается тем рантаймом, который ставил пакет —
|
|
4
|
+
* обычно это node, поэтому здесь снова голый JS без `bun:`.
|
|
5
|
+
*
|
|
6
|
+
* Зачем: shebang у bin/myc.js — `#!/usr/bin/env bun`, и если Bun в системе
|
|
7
|
+
* нет вовсе, первая же попытка запустить `myc` даст `env: bun: No such file
|
|
8
|
+
* or directory` (код 127) — сообщение, по которому нельзя понять ни причину,
|
|
9
|
+
* ни что делать. Наша заглушка до этого не доживает: её просто некому
|
|
10
|
+
* выполнить. Значит сказать надо здесь, на установке.
|
|
11
|
+
*
|
|
12
|
+
* Установку НЕ роняем: пакет разложен правильно, не хватает только рантайма,
|
|
13
|
+
* и это чинится `curl … | bash` без переустановки.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { spawnSync } from "node:child_process";
|
|
17
|
+
|
|
18
|
+
if (typeof process.versions.bun !== "string" && !bunOnPath()) {
|
|
19
|
+
process.stderr.write(
|
|
20
|
+
[
|
|
21
|
+
"",
|
|
22
|
+
" ┌─ @myc/cli установлен, но запускаться пока не будет ────────────┐",
|
|
23
|
+
" │ myc работает только на Bun (хранилище на bun:sqlite). │",
|
|
24
|
+
" │ Bun в системе не найден. │",
|
|
25
|
+
" │ │",
|
|
26
|
+
" │ curl -fsSL https://bun.sh/install | bash │",
|
|
27
|
+
" │ │",
|
|
28
|
+
" │ После этого: myc --version │",
|
|
29
|
+
" └────────────────────────────────────────────────────────────────┘",
|
|
30
|
+
"",
|
|
31
|
+
].join("\n"),
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function bunOnPath() {
|
|
36
|
+
try {
|
|
37
|
+
return spawnSync("bun", ["--version"], { stdio: "ignore" }).status === 0;
|
|
38
|
+
} catch {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|