opencode-self-improvement 0.0.0-stage → 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 +21 -0
- package/README.md +122 -2
- package/command/repo-init.md +16 -0
- package/index.ts +2 -0
- package/package.json +51 -3
- package/src/blocklist.ts +56 -0
- package/src/command.ts +25 -0
- package/src/config.ts +60 -0
- package/src/evidence.ts +50 -0
- package/src/gates.ts +511 -0
- package/src/index.ts +461 -0
- package/src/init.ts +300 -0
- package/src/inject.ts +150 -0
- package/src/logfmt.ts +67 -0
- package/src/memory.ts +242 -0
- package/src/prune.ts +23 -0
- package/src/resolver.ts +298 -0
- package/src/state.ts +88 -0
- package/src/triggers.ts +292 -0
- package/src/undo.ts +44 -0
- package/src/viewer/mutate.ts +152 -0
- package/src/viewer/page.html +362 -0
- package/src/viewer/server.ts +191 -0
- package/vendor/opencode-memory/LICENSE +21 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alex Bogartz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,123 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Opencode-Self-Improvement
|
|
2
|
+
|
|
3
|
+
Durable memory for opencode, plus a one-command per-repo setup.
|
|
4
|
+
|
|
5
|
+
## Why is this here?
|
|
6
|
+
|
|
7
|
+
LLMs waste a lot of tokens reading and re-reading your code and asking you for clarifications you've already given it. This tells it to store those decisions so the next time you hit a similar part of the code, it knows more and gets to the answer faster.
|
|
8
|
+
## What you get
|
|
9
|
+
|
|
10
|
+
- **`memory_*` tools** — remember facts, decisions, and commands the agent should
|
|
11
|
+
keep across sessions (`memory_init`, `memory_remember`, `memory_recall`,
|
|
12
|
+
`memory_update`, `memory_forget`, `memory_list`, `memory_undo`,
|
|
13
|
+
`memory_evidence`).
|
|
14
|
+
- **`/repo-init`** — for each repo: indexes the code graph and remembers the
|
|
15
|
+
test/build commands, keyed to the git head. The command installs itself; no
|
|
16
|
+
setup beyond the plugin.
|
|
17
|
+
- Automatically de-dupes similar memories and avoids contradictions by searching for existing memories before saving new ones.
|
|
18
|
+
- Automatically looks for insights when you complete the verification gates (tests, linting, etc)
|
|
19
|
+
- Always defaults to memories first before sending a prompt to the LLM, which saves tokens!
|
|
20
|
+
|
|
21
|
+
## Quick start
|
|
22
|
+
|
|
23
|
+
1. Add the plugin to your Opencode config (see install below) and restart Opencode.
|
|
24
|
+
2. In any repo, run:
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
/repo-init
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
That's it. Rerun `/repo-init force` after a refactor to refresh.
|
|
31
|
+
|
|
32
|
+
## How to install
|
|
33
|
+
|
|
34
|
+
Add a `plugin` entry to your opencode config —
|
|
35
|
+
`~/.config/opencode/opencode.json` (global) or any project `opencode.json`:
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"$schema": "https://opencode.ai/config.json",
|
|
40
|
+
"plugin": ["opencode-self-improvement@0.1.0"]
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Opencode installs the package (and its dependencies) automatically with Bun at
|
|
45
|
+
startup. Quit and restart opencode; the plugin loads and `/repo-init` installs
|
|
46
|
+
itself. Nothing else to do.
|
|
47
|
+
|
|
48
|
+
Pin the exact version (as above). An unpinned entry resolves `@latest` exactly
|
|
49
|
+
once and is never re-resolved afterwards, so it silently sticks. To update,
|
|
50
|
+
bump the version in the config and restart. On Opencode v2 you can also manage
|
|
51
|
+
it with the CLI:
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
opencode plugin add opencode-self-improvement@0.1.0
|
|
55
|
+
opencode plugin update opencode-self-improvement
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
<details>
|
|
59
|
+
<summary>Install from a clone (development)</summary>
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
git clone https://github.com/abogartz/opencode-self-improvement.git
|
|
63
|
+
cd opencode-self-improvement
|
|
64
|
+
bun install
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Then point the `plugin` entry at the local file instead:
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"$schema": "https://opencode.ai/config.json",
|
|
72
|
+
"plugin": ["/full/path/to/opencode-self-improvement/index.ts"]
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
After changes, `git pull` and restart opencode.
|
|
77
|
+
|
|
78
|
+
</details>
|
|
79
|
+
|
|
80
|
+
## Code graph (optional)
|
|
81
|
+
|
|
82
|
+
`/repo-init` also indexes the repo into a queryable knowledge graph via
|
|
83
|
+
[`codebase-memory-mcp`](https://github.com/DeusData/codebase-memory-mcp),
|
|
84
|
+
pinned to `0.11.0` as an optional dependency, so opencode's auto-install at
|
|
85
|
+
startup brings it in. If a copy is already on your `PATH`, that one is used
|
|
86
|
+
instead; override explicitly with `OPENCODE_CBM_BIN=/path/to/codebase-memory-mcp`.
|
|
87
|
+
When no graph binary is available, `/repo-init` still records scripts and the
|
|
88
|
+
init stamp — the graph is a lookup bonus, not a requirement (see below).
|
|
89
|
+
|
|
90
|
+
Manual install of the pinned binary (if you want it globally):
|
|
91
|
+
|
|
92
|
+
```sh
|
|
93
|
+
curl -fsSL https://raw.githubusercontent.com/DeusData/codebase-memory-mcp/main/install.sh | bash
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## First use
|
|
97
|
+
|
|
98
|
+
Run `/repo-init` once in every repo you work in. It:
|
|
99
|
+
|
|
100
|
+
- indexes the repo's code graph (`codebase-memory-mcp`), only when missing or forced;
|
|
101
|
+
- stores the test/lint/typecheck commands as memories;
|
|
102
|
+
- writes an init stamp tied to the current git head.
|
|
103
|
+
|
|
104
|
+
Ongoing use is just the memory tools. Ask the agent to remember something and it
|
|
105
|
+
sticks across sessions; check what's stored with `memory_list`, refine with
|
|
106
|
+
`memory_update`, retract with `memory_forget`, roll back with `memory_undo`.
|
|
107
|
+
|
|
108
|
+
## Admin Tool
|
|
109
|
+
|
|
110
|
+
Editing and deleting memories is easy!
|
|
111
|
+
|
|
112
|
+
Run `bun memory-view` in your terminal and visit http://127.0.0.1:8787 in your browser.
|
|
113
|
+
|
|
114
|
+
## Troubleshooting
|
|
115
|
+
|
|
116
|
+
| Problem | Fix |
|
|
117
|
+
| --- | --- |
|
|
118
|
+
| `memory_*` tools not available | The plugin didn't load. Restart opencode and check the `plugin` entry in your config. |
|
|
119
|
+
| `/repo-init` missing from the command list | The self-install couldn't write to `~/.config/opencode/commands/` (e.g. read-only config dir). Create it, restart, or just ask the model to "init memory for this repo" — it calls the same tool. |
|
|
120
|
+
| Init says `Graph: unavailable` | `codebase-memory-mcp` couldn't start (not installed, or its once-only download is blocked). Install pinned `0.11.0` via the one-liner above, or point `OPENCODE_CBM_BIN` at the binary. Scripts are still remembered without it. |
|
|
121
|
+
| Stale plugin after an update | Bump the pinned version in the `plugin` entry (or `opencode plugin update opencode-self-improvement` on v2) and restart opencode. |
|
|
122
|
+
| Reset all memories | Delete `~/.config/opencode/memory/`. |
|
|
2
123
|
|
|
3
|
-
This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Initialize the code graph and build/test memories for this repo
|
|
3
|
+
---
|
|
4
|
+
Initialize this repository for durable memory and graph-backed work.
|
|
5
|
+
|
|
6
|
+
1. Ensure the codebase-memory graph is indexed for this repository.
|
|
7
|
+
2. Re-derive build/lint/test/typecheck commands and write them as pattern
|
|
8
|
+
memories (scope = this repo name).
|
|
9
|
+
3. Store an init stamp keyed on git HEAD so future sessions can cheaply tell
|
|
10
|
+
whether the repo has drifted.
|
|
11
|
+
|
|
12
|
+
Call the `memory_init` tool to do this. If the user typed arguments (e.g.
|
|
13
|
+
"force"), pass them through: set `force: true` when $ARGUMENTS mentions force,
|
|
14
|
+
otherwise run a fresh idempotent init.
|
|
15
|
+
|
|
16
|
+
Then report concisely: graph status, scripts recorded, and the stamp head.
|
package/index.ts
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,54 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-self-improvement",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Durable memory for opencode: remember facts, decisions, and commands across sessions, plus a one-command per-repo setup (/repo-init).",
|
|
5
|
+
"main": "index.ts",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./index.ts"
|
|
8
|
+
},
|
|
9
|
+
"module": "index.ts",
|
|
10
|
+
"type": "module",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"author": "Alex Bogartz",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/abogartz/opencode-self-improvement.git"
|
|
16
|
+
},
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/abogartz/opencode-self-improvement/issues"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://github.com/abogartz/opencode-self-improvement#readme",
|
|
21
|
+
"keywords": [
|
|
22
|
+
"opencode",
|
|
23
|
+
"opencode-plugin",
|
|
24
|
+
"plugin",
|
|
25
|
+
"memory",
|
|
26
|
+
"ai-agent"
|
|
27
|
+
],
|
|
28
|
+
"files": [
|
|
29
|
+
"index.ts",
|
|
30
|
+
"src",
|
|
31
|
+
"command",
|
|
32
|
+
"vendor",
|
|
33
|
+
"README.md",
|
|
34
|
+
"LICENSE"
|
|
35
|
+
],
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@opencode-ai/plugin": "1.18.31"
|
|
38
|
+
},
|
|
39
|
+
"optionalDependencies": {
|
|
40
|
+
"codebase-memory-mcp": "0.11.0"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/bun": "latest",
|
|
44
|
+
"typescript": "^7.0.2"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"typescript": "^7.0.2"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"test": "bun test",
|
|
51
|
+
"typecheck": "bunx tsc --noEmit -p tsconfig.json",
|
|
52
|
+
"memory-view": "bun run src/viewer/server.ts"
|
|
53
|
+
}
|
|
6
54
|
}
|
package/src/blocklist.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Human intervention for poisoned/stale memory. A blocked memory is
|
|
2
|
+
// still retained on disk (never destroyed) but is filtered out of every read
|
|
3
|
+
// path: recall, the injected digest and contextual pull, and the viewer's
|
|
4
|
+
// active list. Blocklist lives at <memoryRoot>/blocklist.logfmt so a recursive
|
|
5
|
+
// memory scan must never pick it up.
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { memoryRoot } from "./config.ts";
|
|
8
|
+
import { appendLog, formatLine, parseLine, readLines } from "./logfmt.ts";
|
|
9
|
+
|
|
10
|
+
const BLOCKLIST_FILENAME = "blocklist.logfmt";
|
|
11
|
+
|
|
12
|
+
export interface BlockEntry {
|
|
13
|
+
ts: string;
|
|
14
|
+
reason: string;
|
|
15
|
+
blocked_at: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function blocklistFile(): string {
|
|
19
|
+
return join(memoryRoot(), BLOCKLIST_FILENAME);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function blockedEntries(): Promise<BlockEntry[]> {
|
|
23
|
+
const lines = await readLines(blocklistFile());
|
|
24
|
+
const out: BlockEntry[] = [];
|
|
25
|
+
for (const line of lines) {
|
|
26
|
+
const f = parseLine(line);
|
|
27
|
+
if (!f || typeof f.ts !== "string") continue;
|
|
28
|
+
out.push({
|
|
29
|
+
ts: f.ts,
|
|
30
|
+
reason: typeof f.reason === "string" ? f.reason : "",
|
|
31
|
+
blocked_at: typeof f.blocked_at === "string" ? f.blocked_at : "",
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function blockedTs(): Promise<Set<string>> {
|
|
38
|
+
return new Set((await blockedEntries()).map((b) => b.ts));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function addBlock(ts: string, reason: string): Promise<string> {
|
|
42
|
+
if (!reason.trim()) throw new Error("reason is required to block a memory");
|
|
43
|
+
await appendLog(blocklistFile(), formatLine({ ts, reason, blocked_at: new Date().toISOString() }));
|
|
44
|
+
return ts;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function removeBlock(ts: string): Promise<boolean> {
|
|
48
|
+
const file = Bun.file(blocklistFile());
|
|
49
|
+
if (!(await file.exists())) return false;
|
|
50
|
+
const kept = (await file.text()).split("\n").filter((l) => {
|
|
51
|
+
const f = parseLine(l);
|
|
52
|
+
return !(f && f.ts === ts);
|
|
53
|
+
});
|
|
54
|
+
await Bun.write(blocklistFile(), kept.join("\n") + (kept.length ? "\n" : ""));
|
|
55
|
+
return true;
|
|
56
|
+
}
|
package/src/command.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Self-installs the /repo-init slash command as part of plugin load, so users
|
|
2
|
+
// never run a terminal command to set up init. The command markdown ships in
|
|
3
|
+
// the package (command/repo-init.md) and is mirrored idempotently
|
|
4
|
+
// (write-if-missing; never overwrites a user's edits). Failures are swallowed —
|
|
5
|
+
// a missing command degrades to "ask the model to call memory_init".
|
|
6
|
+
import { existsSync } from "node:fs";
|
|
7
|
+
import { mkdir } from "node:fs/promises";
|
|
8
|
+
import { dirname, join } from "node:path";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { commandsDir } from "./config.ts";
|
|
11
|
+
|
|
12
|
+
const SOURCE = join(dirname(fileURLToPath(import.meta.url)), "..", "command", "repo-init.md");
|
|
13
|
+
|
|
14
|
+
export async function ensureInitCommand(): Promise<string | null> {
|
|
15
|
+
try {
|
|
16
|
+
const target = join(commandsDir(), "repo-init.md");
|
|
17
|
+
if (existsSync(target)) return null;
|
|
18
|
+
const text = await Bun.file(SOURCE).text();
|
|
19
|
+
await mkdir(commandsDir(), { recursive: true });
|
|
20
|
+
await Bun.write(target, text);
|
|
21
|
+
return target;
|
|
22
|
+
} catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Store paths for the self-improvement harness. Everything lives OUTSIDE any
|
|
2
|
+
// repository (invariant I2): under ~/.config/opencode/memory by default, or
|
|
3
|
+
// under $OPENCODE_MEMORY_DIR when set (tests use this for hermetic runs).
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
8
|
+
export function memoryRoot(): string {
|
|
9
|
+
return (
|
|
10
|
+
process.env.OPENCODE_MEMORY_DIR ||
|
|
11
|
+
join(homedir(), ".config", "opencode", "memory")
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Where the plugin mirrors its own slash command (/repo-init). Written once at
|
|
16
|
+
// load (write-if-missing), so no manual setup is needed after install.
|
|
17
|
+
export function commandsDir(): string {
|
|
18
|
+
return (
|
|
19
|
+
process.env.OPENCODE_COMMANDS_DIR ||
|
|
20
|
+
join(homedir(), ".config", "opencode", "commands")
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function evidenceDir(): string {
|
|
25
|
+
return join(memoryRoot(), "evidence");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function snapshotDir(): string {
|
|
29
|
+
return join(memoryRoot(), "snapshots");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function canonicalFile(): string {
|
|
33
|
+
return join(memoryRoot(), "canonical.logfmt");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function deletionsFile(): string {
|
|
37
|
+
return join(memoryRoot(), "deletions.logfmt");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function dateStr(d: Date = new Date()): string {
|
|
41
|
+
return d.toISOString().split("T")[0];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Optional live-graph binary. When present the resolver cross-checks the local
|
|
45
|
+
// source scan against the codebase-memory-mcp file graph; when absent the local
|
|
46
|
+
// scan alone is authoritative (install-from-scratch requirement).
|
|
47
|
+
// Discovery order: OPENCODE_CBM_BIN override, an already-installed global on
|
|
48
|
+
// PATH (matches a running MCP session's build), then the pinned npm copy
|
|
49
|
+
// (codebase-memory-mcp is an optionalDependency of this package).
|
|
50
|
+
export function cbmBin(): string | undefined {
|
|
51
|
+
if (process.env.OPENCODE_CBM_BIN) return process.env.OPENCODE_CBM_BIN;
|
|
52
|
+
if (Bun.which("codebase-memory-mcp")) return undefined;
|
|
53
|
+
try {
|
|
54
|
+
const resolved = import.meta.resolve("codebase-memory-mcp/bin.js");
|
|
55
|
+
if (resolved.startsWith("file://")) return fileURLToPath(resolved);
|
|
56
|
+
} catch {
|
|
57
|
+
// optional graph dependency not installed; PATH lookup still applies
|
|
58
|
+
}
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
package/src/evidence.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Evidence log writer. One logfmt line per event, appended in hook order to
|
|
2
|
+
// ~/.config/opencode/memory/evidence/<date>.logfmt. Evidence is DATA, never
|
|
3
|
+
// chat (invariant I3) and never inside a repo (I2).
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
import { dateStr, evidenceDir } from "./config.ts";
|
|
7
|
+
import { appendLog, formatLine } from "./logfmt.ts";
|
|
8
|
+
import { getCurrent } from "./state.ts";
|
|
9
|
+
|
|
10
|
+
export type EvidenceEvent = string;
|
|
11
|
+
|
|
12
|
+
export async function record(
|
|
13
|
+
ev: EvidenceEvent,
|
|
14
|
+
fields: Record<string, unknown> = {},
|
|
15
|
+
includeContext = true,
|
|
16
|
+
): Promise<void> {
|
|
17
|
+
const { sid, call } = getCurrent();
|
|
18
|
+
const line = formatLine({
|
|
19
|
+
ev,
|
|
20
|
+
ts: new Date().toISOString(),
|
|
21
|
+
...(includeContext ? { sid, call } : {}),
|
|
22
|
+
...fields,
|
|
23
|
+
});
|
|
24
|
+
await appendLog(join(evidenceDir(), `${dateStr()}.logfmt`), line);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function contentHash(content: string): string {
|
|
28
|
+
return createHash("sha1").update(content).digest("hex").slice(0, 12);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function chainId(): string {
|
|
32
|
+
return createHash("sha1")
|
|
33
|
+
.update(`${Date.now()}-${Math.random()}`)
|
|
34
|
+
.digest("hex")
|
|
35
|
+
.slice(0, 8);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function recordWriteBlocked(
|
|
39
|
+
gate: number,
|
|
40
|
+
reason: string,
|
|
41
|
+
flags: string[],
|
|
42
|
+
chain?: string,
|
|
43
|
+
): Promise<void> {
|
|
44
|
+
await record("write_blocked", {
|
|
45
|
+
gate,
|
|
46
|
+
reason,
|
|
47
|
+
flags: `[${flags.join(",")}]`,
|
|
48
|
+
...(chain ? { chain } : {}),
|
|
49
|
+
});
|
|
50
|
+
}
|