@gmickel/gno 1.43.0 → 1.44.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/browser-extension/artifacts/{gno-browser-clipper-v1.43.0.zip → gno-browser-clipper-v1.44.0.zip} +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.44.0.zip.sha256 +1 -0
- package/browser-extension/dist/manifest.json +1 -1
- package/package.json +1 -1
- package/spec/cli.md +89 -8
- package/spec/mcp.md +12 -0
- package/spec/output-schemas/changes-follow-event.schema.json +35 -0
- package/spec/output-schemas/index-receipt.schema.json +135 -0
- package/spec/output-schemas/process-status.schema.json +76 -0
- package/src/cli/commands/changes-follow.ts +167 -0
- package/src/cli/commands/changes.ts +63 -0
- package/src/cli/commands/daemon.ts +35 -0
- package/src/cli/commands/doctor.ts +71 -0
- package/src/cli/commands/embed.ts +236 -178
- package/src/cli/commands/index-cmd.ts +238 -57
- package/src/cli/program.ts +94 -4
- package/src/config/types.ts +48 -0
- package/src/core/capture-sync.ts +144 -0
- package/src/core/capture.ts +10 -0
- package/src/core/findings-records.ts +381 -0
- package/src/core/findings-run-state.ts +282 -0
- package/src/embed/stage-state.ts +199 -0
- package/src/mcp/tools/capture.ts +91 -136
- package/src/serve/capture-service.ts +227 -53
- package/src/serve/findings-pass.ts +335 -0
- package/src/serve/resident-runtime.ts +42 -0
- package/src/serve/routes/api.ts +14 -14
- package/browser-extension/artifacts/gno-browser-clipper-v1.43.0.zip.sha256 +0 -1
|
@@ -9,12 +9,14 @@ import type {
|
|
|
9
9
|
ListKnowledgeChangesInput,
|
|
10
10
|
} from "../../core/knowledge-delta";
|
|
11
11
|
import type { StorePort } from "../../store/types";
|
|
12
|
+
import type { ChangesFollowLine, FollowChangesResult } from "./changes-follow";
|
|
12
13
|
|
|
13
14
|
import {
|
|
14
15
|
analyzeKnowledgeImpact,
|
|
15
16
|
getKnowledgeDiff,
|
|
16
17
|
listKnowledgeChanges,
|
|
17
18
|
} from "../../core/knowledge-delta";
|
|
19
|
+
import { followChanges, validateFollowCursor } from "./changes-follow";
|
|
18
20
|
import { initStore } from "./shared";
|
|
19
21
|
|
|
20
22
|
export interface KnowledgeDeltaCliContext {
|
|
@@ -82,6 +84,67 @@ export const impact = (
|
|
|
82
84
|
): Promise<KnowledgeDeltaServiceResult<KnowledgeImpactResult>> =>
|
|
83
85
|
withStore(context, (store) => impactRead(store, ref, input));
|
|
84
86
|
|
|
87
|
+
export interface ChangesFollowInput {
|
|
88
|
+
cursor?: string;
|
|
89
|
+
collection?: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Abort on SIGINT/SIGTERM so the stream ends cleanly after the current line. */
|
|
93
|
+
const abortOnSignals = (): { signal: AbortSignal; dispose: () => void } => {
|
|
94
|
+
const controller = new AbortController();
|
|
95
|
+
const onSignal = (): void => controller.abort();
|
|
96
|
+
process.once("SIGINT", onSignal);
|
|
97
|
+
process.once("SIGTERM", onSignal);
|
|
98
|
+
return {
|
|
99
|
+
signal: controller.signal,
|
|
100
|
+
dispose: (): void => {
|
|
101
|
+
process.off("SIGINT", onSignal);
|
|
102
|
+
process.off("SIGTERM", onSignal);
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* `gno changes --follow --jsonl`: stream journal events as JSON lines on
|
|
109
|
+
* stdout until a signal arrives or the resume cursor expires.
|
|
110
|
+
*/
|
|
111
|
+
export const changesFollow = async (
|
|
112
|
+
input: ChangesFollowInput,
|
|
113
|
+
context: KnowledgeDeltaCliContext = {},
|
|
114
|
+
emit: (line: ChangesFollowLine) => void = (line) => {
|
|
115
|
+
process.stdout.write(`${JSON.stringify(line)}\n`);
|
|
116
|
+
}
|
|
117
|
+
): Promise<FollowChangesResult> => {
|
|
118
|
+
if (input.cursor !== undefined) {
|
|
119
|
+
const invalid = validateFollowCursor(input.cursor);
|
|
120
|
+
if (invalid) return { status: "error", error: invalid, isValidation: true };
|
|
121
|
+
}
|
|
122
|
+
const collection = input.collection?.trim();
|
|
123
|
+
if (input.collection !== undefined && !collection) {
|
|
124
|
+
return {
|
|
125
|
+
status: "error",
|
|
126
|
+
error: "collection cannot be empty",
|
|
127
|
+
isValidation: true,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
const signals = abortOnSignals();
|
|
131
|
+
try {
|
|
132
|
+
const result = await withStore(context, async (store) => ({
|
|
133
|
+
success: true as const,
|
|
134
|
+
data: await followChanges(
|
|
135
|
+
store,
|
|
136
|
+
{ cursor: input.cursor, collection, signal: signals.signal },
|
|
137
|
+
emit
|
|
138
|
+
),
|
|
139
|
+
}));
|
|
140
|
+
return result.success
|
|
141
|
+
? result.data
|
|
142
|
+
: { status: "error", error: result.error, isValidation: false };
|
|
143
|
+
} finally {
|
|
144
|
+
signals.dispose();
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
85
148
|
const json = (value: unknown): string => JSON.stringify(value, null, 2);
|
|
86
149
|
|
|
87
150
|
export function formatChanges(
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { CollectionSyncResult } from "../../ingestion";
|
|
2
2
|
import type { HttpGatewayOverrides } from "../../mcp/http-security";
|
|
3
3
|
import type { BackgroundRuntimeResult } from "../../serve/background-runtime";
|
|
4
|
+
import type { FindingsPassResult } from "../../serve/findings-pass";
|
|
4
5
|
import type { ResidentRuntime } from "../../serve/resident-runtime";
|
|
5
6
|
|
|
6
7
|
import {
|
|
@@ -43,6 +44,29 @@ type DaemonDeps = {
|
|
|
43
44
|
logger?: DaemonLogger;
|
|
44
45
|
};
|
|
45
46
|
|
|
47
|
+
/** Silent when clean: only failures, and non-empty writes when not quiet, reach the log. */
|
|
48
|
+
export function logFindingsPassResult(
|
|
49
|
+
result: FindingsPassResult,
|
|
50
|
+
logger: DaemonLogger,
|
|
51
|
+
options: { quiet?: boolean; verbose?: boolean }
|
|
52
|
+
): void {
|
|
53
|
+
if (result.outcome === "failed") {
|
|
54
|
+
logger.error(`findings pass failed: ${result.error ?? "unknown error"}`);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (result.outcome === "skipped_lease") {
|
|
58
|
+
if (options.verbose) logger.log(`findings pass skipped: ${result.error}`);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const { counts } = result;
|
|
62
|
+
const changed =
|
|
63
|
+
counts.written + counts.reopened + counts.resolved + counts.deleted;
|
|
64
|
+
if (changed === 0 || options.quiet) return;
|
|
65
|
+
logger.log(
|
|
66
|
+
`findings pass: ${counts.written} new, ${counts.reopened} reopened, ${counts.resolved} resolved, ${counts.deleted} expired (${counts.open} open)`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
46
70
|
function formatCollectionSyncSummary(result: CollectionSyncResult): string {
|
|
47
71
|
return `${result.collection}: ${result.filesAdded} added, ${result.filesUpdated} updated, ${result.filesUnchanged} unchanged, ${result.filesErrored} errors`;
|
|
48
72
|
}
|
|
@@ -146,6 +170,11 @@ export async function daemon(
|
|
|
146
170
|
index: options.index,
|
|
147
171
|
requireCollections: true,
|
|
148
172
|
offline: options.offline,
|
|
173
|
+
onFindingsResult: (result) =>
|
|
174
|
+
logFindingsPassResult(result, logger, {
|
|
175
|
+
quiet: options.quiet,
|
|
176
|
+
verbose: options.verbose,
|
|
177
|
+
}),
|
|
149
178
|
watchCallbacks: {
|
|
150
179
|
onSyncStart: ({ collection, relPaths }) => {
|
|
151
180
|
if (!options.quiet) {
|
|
@@ -230,6 +259,12 @@ export async function daemon(
|
|
|
230
259
|
logger.error(`watch failed: ${failed.collection}: ${failed.reason}`);
|
|
231
260
|
}
|
|
232
261
|
}
|
|
262
|
+
const findings = (runtime as Partial<ResidentRuntime>).findingsScheduler;
|
|
263
|
+
if (findings) {
|
|
264
|
+
logger.log(
|
|
265
|
+
`findings pass: every ${findings.state.cadence} into "${findings.state.collection}" (report-only)`
|
|
266
|
+
);
|
|
267
|
+
}
|
|
233
268
|
}
|
|
234
269
|
|
|
235
270
|
if (!options.noSyncOnStart) {
|
|
@@ -21,6 +21,11 @@ import {
|
|
|
21
21
|
loadConfig,
|
|
22
22
|
} from "../../config";
|
|
23
23
|
import { isConnectorActivationComplete } from "../../core/activation-connector-health";
|
|
24
|
+
import {
|
|
25
|
+
findingsRunStatePathForIndex,
|
|
26
|
+
readFindingsRunStatus,
|
|
27
|
+
resolveFindingsSchedule,
|
|
28
|
+
} from "../../core/findings-run-state";
|
|
24
29
|
import { getCodeChunkingStatus } from "../../ingestion/chunker";
|
|
25
30
|
import { ModelCache } from "../../llm/cache";
|
|
26
31
|
import { getActivePreset, resolveModelUri } from "../../llm/registry";
|
|
@@ -560,6 +565,69 @@ async function checkSqliteExtensions(): Promise<DoctorCheck[]> {
|
|
|
560
565
|
return checks;
|
|
561
566
|
}
|
|
562
567
|
|
|
568
|
+
/**
|
|
569
|
+
* Report the daemon's scheduled findings pass from its persisted state so a
|
|
570
|
+
* misconfigured, starved, or failing scheduler is visible without the daemon.
|
|
571
|
+
*/
|
|
572
|
+
export async function checkFindingsPass(
|
|
573
|
+
config: Config,
|
|
574
|
+
indexName?: string
|
|
575
|
+
): Promise<DoctorCheck> {
|
|
576
|
+
const name = "findings-pass";
|
|
577
|
+
const resolution = resolveFindingsSchedule(config);
|
|
578
|
+
if (!resolution.ok) {
|
|
579
|
+
return {
|
|
580
|
+
name,
|
|
581
|
+
status: "error",
|
|
582
|
+
message: "misconfigured",
|
|
583
|
+
details: [resolution.error],
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
if (!resolution.enabled) {
|
|
587
|
+
return {
|
|
588
|
+
name,
|
|
589
|
+
status: "ok",
|
|
590
|
+
message: "disabled (opt-in via findings.enabled)",
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
const status = await readFindingsRunStatus(
|
|
594
|
+
findingsRunStatePathForIndex(indexName)
|
|
595
|
+
);
|
|
596
|
+
const schedule = `every ${resolution.schedule.cadence} into "${resolution.schedule.collection.name}"`;
|
|
597
|
+
if (!status) {
|
|
598
|
+
return {
|
|
599
|
+
name,
|
|
600
|
+
status: "warn",
|
|
601
|
+
message: `enabled (${schedule}) but no run state recorded`,
|
|
602
|
+
details: ["Start gno daemon; the pass only runs inside the daemon."],
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
const details = [
|
|
606
|
+
`last run: ${status.lastRunAt ?? "never"}`,
|
|
607
|
+
`last success: ${status.lastSuccessAt ?? "never"}`,
|
|
608
|
+
`next due: ${status.nextDueAt}`,
|
|
609
|
+
];
|
|
610
|
+
if (status.counts) {
|
|
611
|
+
details.push(
|
|
612
|
+
`counts: ${status.counts.open} open, ${status.counts.written} new, ${status.counts.resolved} resolved`
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
if (status.error) details.push(`error: ${status.error}`);
|
|
616
|
+
const statusOf: Record<typeof status.state, DoctorCheckStatus> = {
|
|
617
|
+
pending: "ok",
|
|
618
|
+
success: "ok",
|
|
619
|
+
skipped_lease: "warn",
|
|
620
|
+
overdue: "warn",
|
|
621
|
+
failed: "error",
|
|
622
|
+
};
|
|
623
|
+
return {
|
|
624
|
+
name,
|
|
625
|
+
status: statusOf[status.state],
|
|
626
|
+
message: `${status.state} (${schedule})`,
|
|
627
|
+
details,
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
|
|
563
631
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
564
632
|
// Implementation
|
|
565
633
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -603,6 +671,9 @@ export async function doctor(
|
|
|
603
671
|
// Embedding fingerprint freshness
|
|
604
672
|
checks.push(await checkEmbeddingFingerprints(config, options.indexName));
|
|
605
673
|
|
|
674
|
+
// Scheduled findings pass (daemon-only, opt-in)
|
|
675
|
+
checks.push(await checkFindingsPass(config, options.indexName));
|
|
676
|
+
|
|
606
677
|
const activation = await buildDoctorActivation(config, options);
|
|
607
678
|
checks.push(checkRetrievalActivation(activation));
|
|
608
679
|
const connectorActivation = checkConnectorActivation(activation);
|