@gmickel/gno 2.2.1 → 2.3.1
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/README.md +50 -4
- package/assets/skill/README.md +60 -21
- package/assets/skill/SKILL.md +7 -0
- package/assets/skill/cli-reference.md +16 -0
- package/browser-extension/artifacts/{gno-browser-clipper-v2.2.1.zip → gno-browser-clipper-v2.3.1.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v2.3.1.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +1 -1
- package/spec/cli.md +21 -0
- package/spec/mcp.md +9 -0
- package/spec/output-schemas/status.schema.json +41 -0
- package/src/cli/commands/shared.ts +6 -0
- package/src/cli/commands/status.ts +9 -0
- package/src/config/chunking.ts +46 -0
- package/src/config/types.ts +6 -1
- package/src/core/chunking-status.ts +30 -0
- package/src/core/mutation-generations.ts +2 -0
- package/src/ingestion/chunking.ts +108 -0
- package/src/ingestion/record-container.ts +9 -8
- package/src/ingestion/sync-options.ts +3 -1
- package/src/ingestion/sync.ts +33 -24
- package/src/ingestion/types.ts +15 -2
- package/src/mcp/tools/status.ts +5 -0
- package/src/sdk/client.ts +1 -0
- package/src/serve/status-model.ts +2 -0
- package/src/serve/status.ts +2 -0
- package/src/store/chunking.ts +31 -0
- package/src/store/sqlite/adapter.ts +104 -2
- package/src/store/sqlite/chunking-policy.ts +212 -0
- package/src/store/types.ts +32 -1
- package/browser-extension/artifacts/gno-browser-clipper-v2.2.1.zip.sha256 +0 -1
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import type { Database } from "bun:sqlite";
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
ChunkingPolicyToken,
|
|
5
|
+
ChunkingStatus,
|
|
6
|
+
PendingChunkingMirror,
|
|
7
|
+
} from "../chunking";
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
ChunkingConfigSchema,
|
|
11
|
+
chunkingPolicyKey,
|
|
12
|
+
DEFAULT_CHUNKING_PARAMS,
|
|
13
|
+
DEFAULT_CHUNKING_POLICY_KEY,
|
|
14
|
+
resolveChunkingParams,
|
|
15
|
+
type ChunkingParams,
|
|
16
|
+
} from "../../config/chunking";
|
|
17
|
+
import { ChunkingPolicyConflictError } from "../chunking";
|
|
18
|
+
|
|
19
|
+
const TARGET_KEY = "chunking_policy_v1";
|
|
20
|
+
const LAYOUT_PREFIX = "chunking_mirror_v1:";
|
|
21
|
+
|
|
22
|
+
export function readChunkingTarget(db: Database): ChunkingPolicyToken {
|
|
23
|
+
const row = db
|
|
24
|
+
.query<{ value: string }, [string]>(
|
|
25
|
+
"SELECT value FROM schema_meta WHERE key = ?"
|
|
26
|
+
)
|
|
27
|
+
.get(TARGET_KEY);
|
|
28
|
+
if (!row) return { params: { ...DEFAULT_CHUNKING_PARAMS }, generation: 0 };
|
|
29
|
+
const parsed: unknown = JSON.parse(row.value);
|
|
30
|
+
if (
|
|
31
|
+
!parsed ||
|
|
32
|
+
typeof parsed !== "object" ||
|
|
33
|
+
!("generation" in parsed) ||
|
|
34
|
+
!Number.isSafeInteger(parsed.generation) ||
|
|
35
|
+
typeof parsed.generation !== "number" ||
|
|
36
|
+
parsed.generation < 1 ||
|
|
37
|
+
!("params" in parsed)
|
|
38
|
+
) {
|
|
39
|
+
throw new Error("Invalid stored chunking policy");
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
params: ChunkingConfigSchema.parse(parsed.params),
|
|
43
|
+
generation: parsed.generation,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function assertChunkingTarget(
|
|
48
|
+
db: Database,
|
|
49
|
+
token: ChunkingPolicyToken
|
|
50
|
+
): void {
|
|
51
|
+
const current = readChunkingTarget(db);
|
|
52
|
+
if (
|
|
53
|
+
current.generation !== token.generation ||
|
|
54
|
+
chunkingPolicyKey(current.params) !== chunkingPolicyKey(token.params)
|
|
55
|
+
) {
|
|
56
|
+
throw new ChunkingPolicyConflictError();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Called while the adapter holds its transaction writer. */
|
|
61
|
+
export function claimChunkingTarget(
|
|
62
|
+
db: Database,
|
|
63
|
+
observedGeneration: number,
|
|
64
|
+
params: ChunkingParams
|
|
65
|
+
): ChunkingPolicyToken {
|
|
66
|
+
const current = readChunkingTarget(db);
|
|
67
|
+
if (current.generation !== observedGeneration) {
|
|
68
|
+
throw new ChunkingPolicyConflictError();
|
|
69
|
+
}
|
|
70
|
+
if (chunkingPolicyKey(current.params) === chunkingPolicyKey(params)) {
|
|
71
|
+
return current;
|
|
72
|
+
}
|
|
73
|
+
const generation = current.generation + 1;
|
|
74
|
+
if (!Number.isSafeInteger(generation)) {
|
|
75
|
+
throw new Error("Chunking policy generation exhausted");
|
|
76
|
+
}
|
|
77
|
+
const target = { params, generation };
|
|
78
|
+
db.run(
|
|
79
|
+
"INSERT INTO schema_meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value",
|
|
80
|
+
[TARGET_KEY, JSON.stringify(target)]
|
|
81
|
+
);
|
|
82
|
+
return target;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Keyset paging bounds memory even for a large index. */
|
|
86
|
+
export function pendingChunkingMirrors(
|
|
87
|
+
db: Database,
|
|
88
|
+
token: ChunkingPolicyToken,
|
|
89
|
+
afterHash: string
|
|
90
|
+
): PendingChunkingMirror[] {
|
|
91
|
+
assertChunkingTarget(db, token);
|
|
92
|
+
// No policy has ever changed, so every legacy/default layout is current.
|
|
93
|
+
if (token.generation === 0) return [];
|
|
94
|
+
return db
|
|
95
|
+
.query<
|
|
96
|
+
{
|
|
97
|
+
mirror_hash: string;
|
|
98
|
+
source_path: string;
|
|
99
|
+
language_hint: string | null;
|
|
100
|
+
},
|
|
101
|
+
[string, string, string, string]
|
|
102
|
+
>(
|
|
103
|
+
`SELECT c.mirror_hash,
|
|
104
|
+
COALESCE(json_extract(m.value, '$.sourcePath'), d.rel_path) AS source_path,
|
|
105
|
+
CASE WHEN m.value IS NOT NULL
|
|
106
|
+
THEN json_extract(m.value, '$.languageHint') ELSE d.language_hint END AS language_hint
|
|
107
|
+
FROM content c JOIN documents d ON d.id = (
|
|
108
|
+
SELECT id FROM documents
|
|
109
|
+
WHERE mirror_hash = c.mirror_hash AND active = 1
|
|
110
|
+
ORDER BY collection, rel_path, id LIMIT 1
|
|
111
|
+
)
|
|
112
|
+
LEFT JOIN schema_meta m ON m.key = ? || c.mirror_hash
|
|
113
|
+
WHERE c.mirror_hash > ?
|
|
114
|
+
AND COALESCE(json_extract(m.value, '$.params'), ?) <> ?
|
|
115
|
+
ORDER BY c.mirror_hash LIMIT 64`
|
|
116
|
+
)
|
|
117
|
+
.all(
|
|
118
|
+
LAYOUT_PREFIX,
|
|
119
|
+
afterHash,
|
|
120
|
+
DEFAULT_CHUNKING_POLICY_KEY,
|
|
121
|
+
chunkingPolicyKey(token.params)
|
|
122
|
+
)
|
|
123
|
+
.map((row) => ({
|
|
124
|
+
mirrorHash: row.mirror_hash,
|
|
125
|
+
sourcePath: row.source_path,
|
|
126
|
+
languageHint: row.language_hint ?? undefined,
|
|
127
|
+
}));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function markChunkingApplied(
|
|
131
|
+
db: Database,
|
|
132
|
+
mirrorHash: string,
|
|
133
|
+
token: ChunkingPolicyToken,
|
|
134
|
+
sourcePath: string,
|
|
135
|
+
languageHint?: string
|
|
136
|
+
): void {
|
|
137
|
+
const updated = db.run(
|
|
138
|
+
`INSERT INTO schema_meta (key, value)
|
|
139
|
+
SELECT ?, ? WHERE EXISTS (SELECT 1 FROM content WHERE mirror_hash = ?)
|
|
140
|
+
ON CONFLICT(key) DO UPDATE SET value = excluded.value`,
|
|
141
|
+
[
|
|
142
|
+
`${LAYOUT_PREFIX}${mirrorHash}`,
|
|
143
|
+
JSON.stringify({ params: token.params, sourcePath, languageHint }),
|
|
144
|
+
mirrorHash,
|
|
145
|
+
]
|
|
146
|
+
);
|
|
147
|
+
if (updated.changes !== 1)
|
|
148
|
+
throw new Error("Cannot mark a missing cached mirror applied");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function getChunkingStatus(
|
|
152
|
+
db: Database,
|
|
153
|
+
configured?: Partial<ChunkingParams>
|
|
154
|
+
): ChunkingStatus {
|
|
155
|
+
const params = resolveChunkingParams(
|
|
156
|
+
configured ?? readChunkingTarget(db).params
|
|
157
|
+
);
|
|
158
|
+
const key = chunkingPolicyKey(params);
|
|
159
|
+
const groups = db
|
|
160
|
+
.query<
|
|
161
|
+
{ policy: string | null; documents: number; mirrors: number },
|
|
162
|
+
[string]
|
|
163
|
+
>(
|
|
164
|
+
`SELECT json_extract(m.value, '$.params') AS policy, COUNT(*) AS documents,
|
|
165
|
+
COUNT(DISTINCT c.mirror_hash) AS mirrors
|
|
166
|
+
FROM content c JOIN documents d ON d.mirror_hash = c.mirror_hash
|
|
167
|
+
LEFT JOIN schema_meta m ON m.key = ? || c.mirror_hash
|
|
168
|
+
WHERE d.active = 1 GROUP BY policy`
|
|
169
|
+
)
|
|
170
|
+
.all(LAYOUT_PREFIX);
|
|
171
|
+
const appliedPolicies = new Map<string, ChunkingParams>();
|
|
172
|
+
let pendingDocuments = 0;
|
|
173
|
+
let pendingMirrors = 0;
|
|
174
|
+
for (const group of groups) {
|
|
175
|
+
const applied = group.policy
|
|
176
|
+
? ChunkingConfigSchema.parse(JSON.parse(group.policy))
|
|
177
|
+
: { ...DEFAULT_CHUNKING_PARAMS };
|
|
178
|
+
const appliedKey = chunkingPolicyKey(applied);
|
|
179
|
+
appliedPolicies.set(appliedKey, applied);
|
|
180
|
+
if (appliedKey !== key) {
|
|
181
|
+
pendingDocuments += group.documents;
|
|
182
|
+
pendingMirrors += group.mirrors;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
let state: ChunkingStatus["state"] = "current";
|
|
186
|
+
if (groups.length === 0) state = "empty";
|
|
187
|
+
else if (appliedPolicies.size > 1) state = "mixed";
|
|
188
|
+
else if (pendingMirrors > 0) state = "pending";
|
|
189
|
+
else if (groups.every((group) => group.policy === null))
|
|
190
|
+
state = "legacy-default";
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
configured: params,
|
|
194
|
+
applied:
|
|
195
|
+
appliedPolicies.size === 1
|
|
196
|
+
? (appliedPolicies.values().next().value ?? null)
|
|
197
|
+
: null,
|
|
198
|
+
state,
|
|
199
|
+
pendingDocuments,
|
|
200
|
+
pendingMirrors,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function pruneChunkingMetadata(db: Database): void {
|
|
205
|
+
db.run(
|
|
206
|
+
`DELETE FROM schema_meta WHERE key GLOB ?
|
|
207
|
+
AND NOT EXISTS (
|
|
208
|
+
SELECT 1 FROM content WHERE mirror_hash = substr(schema_meta.key, ?)
|
|
209
|
+
)`,
|
|
210
|
+
[`${LAYOUT_PREFIX}*`, LAYOUT_PREFIX.length + 1]
|
|
211
|
+
);
|
|
212
|
+
}
|
package/src/store/types.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* @module src/store/types
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import type { ChunkingParams } from "../config/chunking";
|
|
8
9
|
import type {
|
|
9
10
|
Collection,
|
|
10
11
|
Context,
|
|
@@ -19,6 +20,11 @@ import type {
|
|
|
19
20
|
FileRefactorRecoveryReceipt,
|
|
20
21
|
FileRefactorRecoveryReceiptDraft,
|
|
21
22
|
} from "../core/file-refactor-journal";
|
|
23
|
+
import type {
|
|
24
|
+
ChunkingPolicyToken,
|
|
25
|
+
ChunkingStatus,
|
|
26
|
+
PendingChunkingMirror,
|
|
27
|
+
} from "./chunking";
|
|
22
28
|
|
|
23
29
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
24
30
|
// Error Types
|
|
@@ -42,6 +48,7 @@ export type StoreErrorCode =
|
|
|
42
48
|
| "QUERY_FAILED"
|
|
43
49
|
| "TRANSACTION_FAILED"
|
|
44
50
|
| "INVALID_INPUT"
|
|
51
|
+
| "CHUNKING_POLICY_CONFLICT"
|
|
45
52
|
| "IO_ERROR"
|
|
46
53
|
| "INTERNAL"
|
|
47
54
|
| "EGRESS_DENIED"
|
|
@@ -806,6 +813,8 @@ export interface IndexStatus {
|
|
|
806
813
|
totalChunks: number;
|
|
807
814
|
/** Chunks without embeddings */
|
|
808
815
|
embeddingBacklog: number;
|
|
816
|
+
/** Configuration and applied cached layouts; separate from source freshness. */
|
|
817
|
+
chunking?: ChunkingStatus;
|
|
809
818
|
/** Recent ingest errors (last 24h) */
|
|
810
819
|
recentErrors: number;
|
|
811
820
|
/** Last successful update timestamp (ISO 8601) */
|
|
@@ -2036,7 +2045,28 @@ export interface StorePort {
|
|
|
2036
2045
|
*/
|
|
2037
2046
|
upsertChunks(
|
|
2038
2047
|
mirrorHash: string,
|
|
2039
|
-
chunks: ChunkInput[]
|
|
2048
|
+
chunks: ChunkInput[],
|
|
2049
|
+
policy?: ChunkingPolicyToken
|
|
2050
|
+
): Promise<StoreResult<void>>;
|
|
2051
|
+
|
|
2052
|
+
/** Claim an index-wide policy against the generation observed at open. */
|
|
2053
|
+
claimChunkingPolicy?(
|
|
2054
|
+
params: ChunkingParams
|
|
2055
|
+
): Promise<StoreResult<ChunkingPolicyToken>>;
|
|
2056
|
+
|
|
2057
|
+
/** Page cached mirrors whose applied policy differs from the claimed target. */
|
|
2058
|
+
listPendingChunkingMirrors?(
|
|
2059
|
+
policy: ChunkingPolicyToken,
|
|
2060
|
+
afterHash?: string
|
|
2061
|
+
): Promise<StoreResult<PendingChunkingMirror[]>>;
|
|
2062
|
+
|
|
2063
|
+
/** Atomically replace a layout, refresh FTS and mark its actual policy. */
|
|
2064
|
+
applyChunkLayout?(
|
|
2065
|
+
mirrorHash: string,
|
|
2066
|
+
chunks: ChunkInput[],
|
|
2067
|
+
policy: ChunkingPolicyToken,
|
|
2068
|
+
sourcePath: string,
|
|
2069
|
+
languageHint?: string
|
|
2040
2070
|
): Promise<StoreResult<void>>;
|
|
2041
2071
|
|
|
2042
2072
|
/**
|
|
@@ -2288,6 +2318,7 @@ export interface StorePort {
|
|
|
2288
2318
|
getStatus(options?: {
|
|
2289
2319
|
embedModel?: string;
|
|
2290
2320
|
embedFingerprint?: string;
|
|
2321
|
+
chunking?: Partial<ChunkingParams>;
|
|
2291
2322
|
}): Promise<StoreResult<IndexStatus>>;
|
|
2292
2323
|
|
|
2293
2324
|
// ─────────────────────────────────────────────────────────────────────────
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
cf4c351a255144b6f65dd25b71096effd05c0ea93a0691fc83ee49a3166a37c2 gno-browser-clipper-v2.2.1.zip
|