opencode-self-improvement 0.3.1 → 0.3.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/package.json +1 -1
- package/src/gates.ts +19 -0
- package/src/memory.ts +15 -0
- package/src/resolver.ts +10 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-self-improvement",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Durable memory for opencode: remember facts, decisions, and commands across sessions, plus a one-command per-repo setup (/repo-init).",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"exports": {
|
package/src/gates.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
loadScope,
|
|
11
11
|
markSuperseded,
|
|
12
12
|
snapshotFiles,
|
|
13
|
+
MAX_CONTENT_CHARS,
|
|
13
14
|
type Memory,
|
|
14
15
|
} from "./memory.ts";
|
|
15
16
|
import { chainId, contentHash, record, recordWriteBlocked } from "./evidence.ts";
|
|
@@ -196,6 +197,24 @@ export async function runWriteGate(
|
|
|
196
197
|
ctx: GateContext,
|
|
197
198
|
): Promise<GateOutcome> {
|
|
198
199
|
const chain = chainId();
|
|
200
|
+
|
|
201
|
+
// Size/type floor BEFORE any scanning (issue #20): gate analysis regexes
|
|
202
|
+
// over megabyte content backtracked super-linearly and froze the service
|
|
203
|
+
// event loop. Measured pre-fix: 100KB write = 6s, 2MB = never returned.
|
|
204
|
+
if (typeof candidate.content !== "string") {
|
|
205
|
+
await recordWriteBlocked(4, "invalid_content", [`content_type=${typeof candidate.content}`], chain);
|
|
206
|
+
return { action: "blocked", reason: "invalid_content", chain, hint: "content must be a string." };
|
|
207
|
+
}
|
|
208
|
+
if (candidate.content.length > MAX_CONTENT_CHARS) {
|
|
209
|
+
await recordWriteBlocked(4, "content_too_long", [`len=${candidate.content.length}`, `max=${MAX_CONTENT_CHARS}`], chain);
|
|
210
|
+
return {
|
|
211
|
+
action: "blocked",
|
|
212
|
+
reason: "content_too_long",
|
|
213
|
+
chain,
|
|
214
|
+
hint: `content is ${candidate.content.length} chars (max ${MAX_CONTENT_CHARS}). Distill the memory to its durable core and retry.`,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
199
218
|
const ch = contentHash(candidate.content);
|
|
200
219
|
await record("gate_start", {
|
|
201
220
|
chain,
|
package/src/memory.ts
CHANGED
|
@@ -10,6 +10,14 @@ import { blockedTs } from "./blocklist.ts";
|
|
|
10
10
|
|
|
11
11
|
export type MemoryStatus = "open" | "settled";
|
|
12
12
|
|
|
13
|
+
// Hard ceiling on stored content (issue #20). No entry point may persist a
|
|
14
|
+
// line longer than this: injection ever shows ~1200 chars, the gate runs on
|
|
15
|
+
// the shared service event loop, and regex scanning over megabyte-scale
|
|
16
|
+
// strings froze every session (measured: 100KB write = 6s, 2MB = hang).
|
|
17
|
+
// Enforced twice — politely in runWriteGate (blocked outcome + evidence),
|
|
18
|
+
// absolutely in appendMemory (throws, covering gate-bypassing callers).
|
|
19
|
+
export const MAX_CONTENT_CHARS = 4000;
|
|
20
|
+
|
|
13
21
|
export interface Memory {
|
|
14
22
|
ts: string;
|
|
15
23
|
type: string;
|
|
@@ -109,6 +117,13 @@ async function existingTs(): Promise<Set<string>> {
|
|
|
109
117
|
|
|
110
118
|
// Append a durable memory. Only the gate's GATE-4 promotion step calls this.
|
|
111
119
|
export async function appendMemory(input: AppendMemoryInput): Promise<string> {
|
|
120
|
+
// Absolute floor (issue #20): every write SHOULD arrive through the gate,
|
|
121
|
+
// which rejects oversized content politely; the viewer's restore/edit paths
|
|
122
|
+
// do not, so nothing oversized leaves this function either.
|
|
123
|
+
if (typeof input.content !== "string") throw new Error("memory content must be a string");
|
|
124
|
+
if (input.content.length > MAX_CONTENT_CHARS) {
|
|
125
|
+
throw new Error(`memory content exceeds ${MAX_CONTENT_CHARS} chars (${input.content.length})`);
|
|
126
|
+
}
|
|
112
127
|
const taken = await existingTs();
|
|
113
128
|
let ts = new Date().toISOString();
|
|
114
129
|
let guard = 0;
|
package/src/resolver.ts
CHANGED
|
@@ -82,7 +82,15 @@ function looksProseArgs(inner: string): boolean {
|
|
|
82
82
|
return s.split(/\s+/).length >= 2;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
// Regex scanning ceiling (issue #20): PATH_RE backtracks super-linearly on
|
|
86
|
+
// long word-character runs (200KB of base64 as memory content hung the gate
|
|
87
|
+
// on the service event loop; ~6s already at 100KB). The write gate rejects
|
|
88
|
+
// content far above this ceiling upstream; the bails protect every scanner
|
|
89
|
+
// from legacy oversized rows and any direct caller.
|
|
90
|
+
export const REGEX_SCAN_MAX_CHARS = 10_000;
|
|
91
|
+
|
|
85
92
|
export function extractCallForms(content: string): CallForm[] {
|
|
93
|
+
if (content.length > REGEX_SCAN_MAX_CHARS) return [];
|
|
86
94
|
const forms: CallForm[] = [];
|
|
87
95
|
CALL_RE.lastIndex = 0;
|
|
88
96
|
for (;;) {
|
|
@@ -111,6 +119,7 @@ export function extractCallForms(content: string): CallForm[] {
|
|
|
111
119
|
const PATH_RE = /[A-Za-z0-9_@./-]+\.(?:ts|tsx|js|jsx|mjs|cjs)(?::\d+)?/g;
|
|
112
120
|
|
|
113
121
|
export function extractPathRefs(content: string): string[] {
|
|
122
|
+
if (content.length > REGEX_SCAN_MAX_CHARS) return [];
|
|
114
123
|
return [...content.matchAll(PATH_RE)].map((m) => m[0]);
|
|
115
124
|
}
|
|
116
125
|
|
|
@@ -121,6 +130,7 @@ export function extractPathRefs(content: string): string[] {
|
|
|
121
130
|
// another machine, e.g. in `$BUILD_ROOT/src/a.ts` the regex match is
|
|
122
131
|
// `BUILD_ROOT/src/a.ts`, which the preceding-char check drops.
|
|
123
132
|
export function checkablePathRefs(content: string): string[] {
|
|
133
|
+
if (content.length > REGEX_SCAN_MAX_CHARS) return [];
|
|
124
134
|
const out: string[] = [];
|
|
125
135
|
for (const m of content.matchAll(PATH_RE)) {
|
|
126
136
|
const ref = m[0].replace(/:\d+$/, "");
|