@gmickel/gno 1.20.0 → 1.22.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/README.md +29 -5
- package/assets/skill/SKILL.md +46 -15
- package/package.json +2 -1
- package/spec/cli.md +144 -0
- package/spec/db/schema.sql +170 -0
- package/spec/evals-agentic.md +48 -0
- package/spec/mcp.md +22 -0
- package/spec/output-schemas/capsule-reverified-event.schema.json +47 -0
- package/spec/output-schemas/changes.schema.json +280 -0
- package/spec/output-schemas/document-diff.schema.json +185 -0
- package/spec/output-schemas/impact.schema.json +122 -0
- package/spec/output-schemas/publish-artifact.schema.json +284 -0
- package/spec/output-schemas/saved-capsule-list.schema.json +16 -0
- package/spec/output-schemas/saved-capsule-registration.schema.json +172 -0
- package/spec/output-schemas/saved-capsule-reverification.schema.json +59 -0
- package/spec/output-schemas/saved-capsule-unwatch.schema.json +16 -0
- package/spec/output-schemas/saved-capsule-watch.schema.json +17 -0
- package/src/cli/commands/changes.ts +160 -0
- package/src/cli/commands/context-saved.ts +189 -0
- package/src/cli/options.ts +8 -0
- package/src/cli/program.ts +195 -0
- package/src/core/capsule-registry.ts +279 -0
- package/src/core/capsule-reverification-scheduler.ts +218 -0
- package/src/core/capsule-reverification.ts +289 -0
- package/src/core/change-diff.ts +182 -0
- package/src/core/change-journal.ts +228 -0
- package/src/core/knowledge-delta.ts +395 -0
- package/src/core/knowledge-impact.ts +202 -0
- package/src/ingestion/sync.ts +214 -165
- package/src/mcp/tools/changes.ts +80 -0
- package/src/mcp/tools/index.ts +29 -0
- package/src/publish/artifact-validation.ts +259 -0
- package/src/publish/artifact.ts +234 -118
- package/src/publish/export-service.ts +5 -9
- package/src/publish/metadata.ts +195 -0
- package/src/sdk/client.ts +42 -0
- package/src/sdk/index.ts +7 -0
- package/src/sdk/types.ts +22 -0
- package/src/serve/doc-events.ts +12 -1
- package/src/serve/resident-runtime.ts +22 -0
- package/src/serve/routes/api.ts +13 -0
- package/src/serve/routes/changes.ts +102 -0
- package/src/serve/server.ts +34 -0
- package/src/serve/watch-service.ts +9 -0
- package/src/store/index.ts +21 -0
- package/src/store/migrations/015-document-change-journal.ts +85 -0
- package/src/store/migrations/016-saved-capsules.ts +131 -0
- package/src/store/migrations/017-document-change-retention-counters.ts +33 -0
- package/src/store/migrations/018-saved-capsule-registration-epoch.ts +24 -0
- package/src/store/migrations/019-saved-capsule-registration-generation.ts +53 -0
- package/src/store/migrations/index.ts +10 -0
- package/src/store/sqlite/adapter.ts +291 -7
- package/src/store/sqlite/capsule-registry-store.ts +534 -0
- package/src/store/sqlite/change-journal-store.ts +473 -0
- package/src/store/types.ts +262 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/** CLI adapters and readable formatters for knowledge delta read services. */
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
KnowledgeChangesResult,
|
|
5
|
+
KnowledgeDeltaServiceResult,
|
|
6
|
+
KnowledgeDiffResult,
|
|
7
|
+
KnowledgeImpactInput,
|
|
8
|
+
KnowledgeImpactResult,
|
|
9
|
+
ListKnowledgeChangesInput,
|
|
10
|
+
} from "../../core/knowledge-delta";
|
|
11
|
+
import type { StorePort } from "../../store/types";
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
analyzeKnowledgeImpact,
|
|
15
|
+
getKnowledgeDiff,
|
|
16
|
+
listKnowledgeChanges,
|
|
17
|
+
} from "../../core/knowledge-delta";
|
|
18
|
+
import { initStore } from "./shared";
|
|
19
|
+
|
|
20
|
+
export interface KnowledgeDeltaCliContext {
|
|
21
|
+
configPath?: string;
|
|
22
|
+
indexName?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const changesRead = (
|
|
26
|
+
store: StorePort,
|
|
27
|
+
input: ListKnowledgeChangesInput = {}
|
|
28
|
+
): Promise<KnowledgeDeltaServiceResult<KnowledgeChangesResult>> =>
|
|
29
|
+
listKnowledgeChanges(store, input);
|
|
30
|
+
|
|
31
|
+
export const diffRead = (
|
|
32
|
+
store: StorePort,
|
|
33
|
+
ref: string,
|
|
34
|
+
changeId?: string
|
|
35
|
+
): Promise<KnowledgeDeltaServiceResult<KnowledgeDiffResult>> =>
|
|
36
|
+
getKnowledgeDiff(store, ref, changeId);
|
|
37
|
+
|
|
38
|
+
export const impactRead = (
|
|
39
|
+
store: StorePort,
|
|
40
|
+
ref: string,
|
|
41
|
+
input: KnowledgeImpactInput = {}
|
|
42
|
+
): Promise<KnowledgeDeltaServiceResult<KnowledgeImpactResult>> =>
|
|
43
|
+
analyzeKnowledgeImpact(store, ref, input);
|
|
44
|
+
|
|
45
|
+
const withStore = async <T>(
|
|
46
|
+
context: KnowledgeDeltaCliContext,
|
|
47
|
+
operation: (store: StorePort) => Promise<KnowledgeDeltaServiceResult<T>>
|
|
48
|
+
): Promise<KnowledgeDeltaServiceResult<T>> => {
|
|
49
|
+
const initialized = await initStore({
|
|
50
|
+
configPath: context.configPath,
|
|
51
|
+
indexName: context.indexName,
|
|
52
|
+
syncConfig: false,
|
|
53
|
+
allowEmptyCollections: true,
|
|
54
|
+
});
|
|
55
|
+
if (!initialized.ok) {
|
|
56
|
+
return { success: false, error: initialized.error };
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
return await operation(initialized.store);
|
|
60
|
+
} finally {
|
|
61
|
+
await initialized.store.close();
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export const changes = (
|
|
66
|
+
input: ListKnowledgeChangesInput,
|
|
67
|
+
context: KnowledgeDeltaCliContext = {}
|
|
68
|
+
): Promise<KnowledgeDeltaServiceResult<KnowledgeChangesResult>> =>
|
|
69
|
+
withStore(context, (store) => changesRead(store, input));
|
|
70
|
+
|
|
71
|
+
export const diff = (
|
|
72
|
+
ref: string,
|
|
73
|
+
changeId: string | undefined,
|
|
74
|
+
context: KnowledgeDeltaCliContext = {}
|
|
75
|
+
): Promise<KnowledgeDeltaServiceResult<KnowledgeDiffResult>> =>
|
|
76
|
+
withStore(context, (store) => diffRead(store, ref, changeId));
|
|
77
|
+
|
|
78
|
+
export const impact = (
|
|
79
|
+
ref: string,
|
|
80
|
+
input: KnowledgeImpactInput,
|
|
81
|
+
context: KnowledgeDeltaCliContext = {}
|
|
82
|
+
): Promise<KnowledgeDeltaServiceResult<KnowledgeImpactResult>> =>
|
|
83
|
+
withStore(context, (store) => impactRead(store, ref, input));
|
|
84
|
+
|
|
85
|
+
const json = (value: unknown): string => JSON.stringify(value, null, 2);
|
|
86
|
+
|
|
87
|
+
export function formatChanges(
|
|
88
|
+
result: KnowledgeChangesResult,
|
|
89
|
+
format: "terminal" | "json"
|
|
90
|
+
): string {
|
|
91
|
+
if (format === "json") return json(result);
|
|
92
|
+
const lines = [`${result.changes.length} retained changes`];
|
|
93
|
+
for (const change of result.changes) {
|
|
94
|
+
const uri = change.current?.uri ?? change.previous?.uri ?? "(unknown)";
|
|
95
|
+
lines.push(
|
|
96
|
+
`${change.observedAt} ${change.kind.padEnd(10)} ${uri} ${change.id}`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
if (result.page.cursorExpired) {
|
|
100
|
+
lines.push(`Cursor expired; earliest: ${result.page.earliestCursor}`);
|
|
101
|
+
} else if (result.page.nextCursor) {
|
|
102
|
+
lines.push(`Next cursor: ${result.page.nextCursor}`);
|
|
103
|
+
}
|
|
104
|
+
for (const warning of result.warnings) {
|
|
105
|
+
lines.push(`Warning: ${warning}`);
|
|
106
|
+
}
|
|
107
|
+
return lines.join("\n");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function formatDiff(
|
|
111
|
+
result: KnowledgeDiffResult,
|
|
112
|
+
format: "terminal" | "json"
|
|
113
|
+
): string {
|
|
114
|
+
if (format === "json") return json(result);
|
|
115
|
+
const lines = [
|
|
116
|
+
`Structural diff for ${result.document.uri}`,
|
|
117
|
+
`Status: ${result.status}`,
|
|
118
|
+
`History: ${result.history.status}${result.history.reason ? ` (${result.history.reason})` : ""}`,
|
|
119
|
+
"Content: not retained (metadata-only journal)",
|
|
120
|
+
];
|
|
121
|
+
if (result.change) {
|
|
122
|
+
const delta = result.change.structureDelta;
|
|
123
|
+
lines.push(
|
|
124
|
+
`Change: ${result.change.kind} at ${result.change.observedAt} (${result.change.id})`,
|
|
125
|
+
`Headings: +${delta.headings.added.length} -${delta.headings.removed.length}`,
|
|
126
|
+
`Links: +${delta.links.added.length} -${delta.links.removed.length}`,
|
|
127
|
+
`Typed edges: +${delta.typedEdges.added.length} -${delta.typedEdges.removed.length}`,
|
|
128
|
+
`Dates: +${delta.dates.added.length} -${delta.dates.removed.length} ~${delta.dates.changed.length}`
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
for (const warning of result.warnings) {
|
|
132
|
+
lines.push(`Warning: ${warning}`);
|
|
133
|
+
}
|
|
134
|
+
return lines.join("\n");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function formatImpact(
|
|
138
|
+
result: KnowledgeImpactResult,
|
|
139
|
+
format: "terminal" | "json"
|
|
140
|
+
): string {
|
|
141
|
+
if (format === "json") return json(result);
|
|
142
|
+
const lines = [
|
|
143
|
+
`${result.impacted.length} documents depend on ${result.root.uri}`,
|
|
144
|
+
];
|
|
145
|
+
for (const item of result.impacted) {
|
|
146
|
+
const path = item.evidencePath
|
|
147
|
+
.map(
|
|
148
|
+
(step) => `${step.source.uri} -[${step.edgeType}]-> ${step.target.uri}`
|
|
149
|
+
)
|
|
150
|
+
.join(" -> ");
|
|
151
|
+
lines.push(`depth ${item.depth} ${item.document.uri}`, ` ${path}`);
|
|
152
|
+
}
|
|
153
|
+
if (result.meta.truncated) {
|
|
154
|
+
lines.push("Traversal truncated by configured caps");
|
|
155
|
+
}
|
|
156
|
+
for (const warning of result.meta.warnings) {
|
|
157
|
+
lines.push(`Warning: ${warning}`);
|
|
158
|
+
}
|
|
159
|
+
return lines.join("\n");
|
|
160
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/** CLI lifecycle for explicitly saved Context Capsules. */
|
|
2
|
+
|
|
3
|
+
import type { Config } from "../../config/types";
|
|
4
|
+
import type { SqliteAdapter } from "../../store/sqlite/adapter";
|
|
5
|
+
import type { SavedCapsuleRegistrationRecord } from "../../store/types";
|
|
6
|
+
|
|
7
|
+
import { DEFAULT_INDEX_NAME } from "../../app/constants";
|
|
8
|
+
import { canonicalizeIndexName } from "../../app/index-name";
|
|
9
|
+
import {
|
|
10
|
+
canonicalSavedCapsuleRegistryJson,
|
|
11
|
+
listSavedCapsules,
|
|
12
|
+
loadSavedCapsuleFile,
|
|
13
|
+
registerSavedCapsule,
|
|
14
|
+
unregisterSavedCapsule,
|
|
15
|
+
} from "../../core/capsule-registry";
|
|
16
|
+
import { reverifySavedCapsuleManually } from "../../core/capsule-reverification";
|
|
17
|
+
import { CliError } from "../errors";
|
|
18
|
+
import { contextCliError } from "./context-build";
|
|
19
|
+
import { initStore } from "./shared";
|
|
20
|
+
|
|
21
|
+
export interface SavedCapsuleCommandOptions {
|
|
22
|
+
configPath?: string;
|
|
23
|
+
indexName?: string;
|
|
24
|
+
format: "terminal" | "json";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface WatchSavedCapsuleCommandOptions extends SavedCapsuleCommandOptions {
|
|
28
|
+
explicitIndexName?: string;
|
|
29
|
+
question?: string;
|
|
30
|
+
label?: string;
|
|
31
|
+
notify?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ReverifySavedCapsuleCommandResult {
|
|
35
|
+
output: string;
|
|
36
|
+
operationStatus: "completed" | "failed";
|
|
37
|
+
errorMessage: string | null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const withStore = async <T>(
|
|
41
|
+
options: SavedCapsuleCommandOptions,
|
|
42
|
+
operation: (input: {
|
|
43
|
+
store: SqliteAdapter;
|
|
44
|
+
config: Config;
|
|
45
|
+
indexName: string;
|
|
46
|
+
}) => Promise<T>
|
|
47
|
+
): Promise<T> => {
|
|
48
|
+
const indexName = canonicalizeIndexName(
|
|
49
|
+
options.indexName ?? DEFAULT_INDEX_NAME
|
|
50
|
+
);
|
|
51
|
+
const initialized = await initStore({
|
|
52
|
+
configPath: options.configPath,
|
|
53
|
+
indexName,
|
|
54
|
+
syncConfig: true,
|
|
55
|
+
allowEmptyCollections: true,
|
|
56
|
+
});
|
|
57
|
+
if (!initialized.ok) throw new CliError("RUNTIME", initialized.error);
|
|
58
|
+
try {
|
|
59
|
+
return await operation({
|
|
60
|
+
store: initialized.store,
|
|
61
|
+
config: initialized.config,
|
|
62
|
+
indexName,
|
|
63
|
+
});
|
|
64
|
+
} finally {
|
|
65
|
+
await initialized.store.close();
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const formatRegistration = (
|
|
70
|
+
registration: SavedCapsuleRegistrationRecord
|
|
71
|
+
): string =>
|
|
72
|
+
[
|
|
73
|
+
`${registration.registrationId} ${registration.label ?? registration.capsuleId}`,
|
|
74
|
+
` file: ${registration.filePath}`,
|
|
75
|
+
` index: ${registration.indexName}`,
|
|
76
|
+
` evidence: ${registration.evidence.length}`,
|
|
77
|
+
` notify: ${registration.notificationPreference}`,
|
|
78
|
+
` state: ${registration.verification?.affectedQuestionState ?? "not_verified"}`,
|
|
79
|
+
].join("\n");
|
|
80
|
+
|
|
81
|
+
export const watchSavedCapsule = async (
|
|
82
|
+
filePath: string,
|
|
83
|
+
options: WatchSavedCapsuleCommandOptions
|
|
84
|
+
): Promise<string> => {
|
|
85
|
+
try {
|
|
86
|
+
const loaded = await loadSavedCapsuleFile(filePath);
|
|
87
|
+
const explicit = options.explicitIndexName
|
|
88
|
+
? canonicalizeIndexName(options.explicitIndexName)
|
|
89
|
+
: undefined;
|
|
90
|
+
if (explicit && explicit !== loaded.capsule.scope.indexName) {
|
|
91
|
+
throw Object.assign(
|
|
92
|
+
new Error(
|
|
93
|
+
`Context Capsule index ${loaded.capsule.scope.indexName} does not match --index ${explicit}`
|
|
94
|
+
),
|
|
95
|
+
{ code: "invalid_filter" }
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
return await withStore(
|
|
99
|
+
{ ...options, indexName: loaded.capsule.scope.indexName },
|
|
100
|
+
async ({ store, indexName }) => {
|
|
101
|
+
const registration = await registerSavedCapsule(store, indexName, {
|
|
102
|
+
filePath,
|
|
103
|
+
question: options.question,
|
|
104
|
+
label: options.label,
|
|
105
|
+
notificationPreference: options.notify ? "local" : "none",
|
|
106
|
+
});
|
|
107
|
+
return options.format === "json"
|
|
108
|
+
? canonicalSavedCapsuleRegistryJson(registration)
|
|
109
|
+
: formatRegistration(registration);
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
} catch (error) {
|
|
113
|
+
throw contextCliError(error);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
export const listWatchedCapsules = async (
|
|
118
|
+
options: SavedCapsuleCommandOptions
|
|
119
|
+
): Promise<string> =>
|
|
120
|
+
withStore(options, async ({ store }) => {
|
|
121
|
+
const registrations = await listSavedCapsules(store);
|
|
122
|
+
if (options.format === "json") {
|
|
123
|
+
return canonicalSavedCapsuleRegistryJson({
|
|
124
|
+
schemaVersion: "1.0",
|
|
125
|
+
registrations,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return registrations.length === 0
|
|
129
|
+
? "No saved Context Capsules are watched."
|
|
130
|
+
: registrations.map(formatRegistration).join("\n\n");
|
|
131
|
+
}).catch((error) => {
|
|
132
|
+
throw contextCliError(error);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
export const unwatchSavedCapsule = async (
|
|
136
|
+
registrationId: string,
|
|
137
|
+
options: SavedCapsuleCommandOptions
|
|
138
|
+
): Promise<string> =>
|
|
139
|
+
withStore(options, async ({ store }) => {
|
|
140
|
+
await unregisterSavedCapsule(store, registrationId);
|
|
141
|
+
return options.format === "json"
|
|
142
|
+
? canonicalSavedCapsuleRegistryJson({
|
|
143
|
+
schemaVersion: "1.0",
|
|
144
|
+
registrationId,
|
|
145
|
+
removed: true,
|
|
146
|
+
})
|
|
147
|
+
: `Stopped watching ${registrationId}.`;
|
|
148
|
+
}).catch((error) => {
|
|
149
|
+
throw contextCliError(error);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
export const reverifyWatchedCapsule = async (
|
|
153
|
+
registrationId: string,
|
|
154
|
+
options: SavedCapsuleCommandOptions
|
|
155
|
+
): Promise<ReverifySavedCapsuleCommandResult> =>
|
|
156
|
+
withStore(options, async ({ store, config, indexName }) => {
|
|
157
|
+
const outcome = await reverifySavedCapsuleManually(registrationId, {
|
|
158
|
+
store,
|
|
159
|
+
config,
|
|
160
|
+
indexName,
|
|
161
|
+
});
|
|
162
|
+
const errorMessage =
|
|
163
|
+
outcome.verification.operationStatus === "failed"
|
|
164
|
+
? `${outcome.verification.errorCode ?? "verification_failed"}: ${
|
|
165
|
+
outcome.verification.errorMessage ??
|
|
166
|
+
"Saved Context Capsule verification failed"
|
|
167
|
+
}`
|
|
168
|
+
: null;
|
|
169
|
+
const output =
|
|
170
|
+
options.format === "json"
|
|
171
|
+
? canonicalSavedCapsuleRegistryJson({
|
|
172
|
+
schemaVersion: "1.0",
|
|
173
|
+
registration: outcome.registration,
|
|
174
|
+
verification: outcome.verification,
|
|
175
|
+
receipt: outcome.receipt,
|
|
176
|
+
})
|
|
177
|
+
: [
|
|
178
|
+
formatRegistration(outcome.registration),
|
|
179
|
+
` operation: ${outcome.verification.operationStatus}`,
|
|
180
|
+
...(errorMessage ? [` error: ${errorMessage}`] : []),
|
|
181
|
+
].join("\n");
|
|
182
|
+
return {
|
|
183
|
+
output,
|
|
184
|
+
operationStatus: outcome.verification.operationStatus,
|
|
185
|
+
errorMessage,
|
|
186
|
+
};
|
|
187
|
+
}).catch((error) => {
|
|
188
|
+
throw contextCliError(error);
|
|
189
|
+
});
|
package/src/cli/options.ts
CHANGED
|
@@ -35,6 +35,7 @@ export const CMD = {
|
|
|
35
35
|
contextCheck: "context.check",
|
|
36
36
|
contextBuild: "context.build",
|
|
37
37
|
contextVerify: "context.verify",
|
|
38
|
+
contextSaved: "context.saved",
|
|
38
39
|
modelsList: "models.list",
|
|
39
40
|
tagsList: "tags.list",
|
|
40
41
|
linksList: "links.list",
|
|
@@ -42,6 +43,9 @@ export const CMD = {
|
|
|
42
43
|
similar: "similar",
|
|
43
44
|
graph: "graph",
|
|
44
45
|
graphQuery: "graph.query",
|
|
46
|
+
changes: "changes",
|
|
47
|
+
diff: "diff",
|
|
48
|
+
impact: "impact",
|
|
45
49
|
capture: "capture",
|
|
46
50
|
} as const;
|
|
47
51
|
|
|
@@ -64,6 +68,7 @@ const FORMAT_SUPPORT: Record<CommandId, OutputFormat[]> = {
|
|
|
64
68
|
[CMD.contextCheck]: ["terminal", "json", "md"],
|
|
65
69
|
[CMD.contextBuild]: ["terminal", "json", "md"],
|
|
66
70
|
[CMD.contextVerify]: ["terminal", "json", "md"],
|
|
71
|
+
[CMD.contextSaved]: ["terminal", "json"],
|
|
67
72
|
[CMD.modelsList]: ["terminal", "json"],
|
|
68
73
|
[CMD.tagsList]: ["terminal", "json", "md"],
|
|
69
74
|
[CMD.linksList]: ["terminal", "json", "md"],
|
|
@@ -72,6 +77,9 @@ const FORMAT_SUPPORT: Record<CommandId, OutputFormat[]> = {
|
|
|
72
77
|
// graph uses custom --dot/--mermaid flags (not OutputFormat) and writes via terminal output
|
|
73
78
|
[CMD.graph]: ["json", "terminal"],
|
|
74
79
|
[CMD.graphQuery]: ["terminal", "json"],
|
|
80
|
+
[CMD.changes]: ["terminal", "json"],
|
|
81
|
+
[CMD.diff]: ["terminal", "json"],
|
|
82
|
+
[CMD.impact]: ["terminal", "json"],
|
|
75
83
|
[CMD.capture]: ["terminal", "json"],
|
|
76
84
|
};
|
|
77
85
|
|
package/src/cli/program.ts
CHANGED
|
@@ -308,6 +308,7 @@ export function createProgram(): Command {
|
|
|
308
308
|
wireTagsCommands(program);
|
|
309
309
|
wireLinksCommands(program);
|
|
310
310
|
wireGraphCommand(program);
|
|
311
|
+
wireKnowledgeDeltaCommands(program);
|
|
311
312
|
wireMcpCommand(program);
|
|
312
313
|
wireSkillCommands(program);
|
|
313
314
|
wireDaemonCommand(program);
|
|
@@ -2088,6 +2089,106 @@ function wireManagementCommands(program: Command): void {
|
|
|
2088
2089
|
}
|
|
2089
2090
|
);
|
|
2090
2091
|
|
|
2092
|
+
contextCmd
|
|
2093
|
+
.command("watch <file>")
|
|
2094
|
+
.description("Watch a saved Context Capsule for evidence changes")
|
|
2095
|
+
.option("--question <text>", "question associated with this Capsule")
|
|
2096
|
+
.option("--label <text>", "short local label")
|
|
2097
|
+
.option("--notify", "emit metadata-only local reverification events")
|
|
2098
|
+
.option("--json", "JSON output")
|
|
2099
|
+
.action(
|
|
2100
|
+
async (
|
|
2101
|
+
file: string,
|
|
2102
|
+
cmdOpts: Record<string, unknown>,
|
|
2103
|
+
command: Command
|
|
2104
|
+
) => {
|
|
2105
|
+
const format = getFormat(cmdOpts);
|
|
2106
|
+
assertFormatSupported(CMD.contextSaved, format);
|
|
2107
|
+
const globals = getGlobals();
|
|
2108
|
+
const explicitIndexName =
|
|
2109
|
+
command.getOptionValueSourceWithGlobals("index") === "cli"
|
|
2110
|
+
? globals.index
|
|
2111
|
+
: undefined;
|
|
2112
|
+
const { watchSavedCapsule } = await import("./commands/context-saved");
|
|
2113
|
+
await writeOutput(
|
|
2114
|
+
await watchSavedCapsule(file, {
|
|
2115
|
+
configPath: globals.config,
|
|
2116
|
+
indexName: globals.index,
|
|
2117
|
+
explicitIndexName,
|
|
2118
|
+
question: cmdOpts.question as string | undefined,
|
|
2119
|
+
label: cmdOpts.label as string | undefined,
|
|
2120
|
+
notify: Boolean(cmdOpts.notify),
|
|
2121
|
+
format: format === "json" ? "json" : "terminal",
|
|
2122
|
+
}),
|
|
2123
|
+
format === "json" ? "json" : "terminal"
|
|
2124
|
+
);
|
|
2125
|
+
}
|
|
2126
|
+
);
|
|
2127
|
+
|
|
2128
|
+
contextCmd
|
|
2129
|
+
.command("watches")
|
|
2130
|
+
.description("List watched saved Context Capsules")
|
|
2131
|
+
.option("--json", "JSON output")
|
|
2132
|
+
.action(async (cmdOpts: Record<string, unknown>) => {
|
|
2133
|
+
const format = getFormat(cmdOpts);
|
|
2134
|
+
assertFormatSupported(CMD.contextSaved, format);
|
|
2135
|
+
const globals = getGlobals();
|
|
2136
|
+
const { listWatchedCapsules } = await import("./commands/context-saved");
|
|
2137
|
+
await writeOutput(
|
|
2138
|
+
await listWatchedCapsules({
|
|
2139
|
+
configPath: globals.config,
|
|
2140
|
+
indexName: globals.index,
|
|
2141
|
+
format: format === "json" ? "json" : "terminal",
|
|
2142
|
+
}),
|
|
2143
|
+
format === "json" ? "json" : "terminal"
|
|
2144
|
+
);
|
|
2145
|
+
});
|
|
2146
|
+
|
|
2147
|
+
contextCmd
|
|
2148
|
+
.command("unwatch <registration>")
|
|
2149
|
+
.description("Stop watching a saved Context Capsule")
|
|
2150
|
+
.option("--json", "JSON output")
|
|
2151
|
+
.action(async (registration: string, cmdOpts: Record<string, unknown>) => {
|
|
2152
|
+
const format = getFormat(cmdOpts);
|
|
2153
|
+
assertFormatSupported(CMD.contextSaved, format);
|
|
2154
|
+
const globals = getGlobals();
|
|
2155
|
+
const { unwatchSavedCapsule } = await import("./commands/context-saved");
|
|
2156
|
+
await writeOutput(
|
|
2157
|
+
await unwatchSavedCapsule(registration, {
|
|
2158
|
+
configPath: globals.config,
|
|
2159
|
+
indexName: globals.index,
|
|
2160
|
+
format: format === "json" ? "json" : "terminal",
|
|
2161
|
+
}),
|
|
2162
|
+
format === "json" ? "json" : "terminal"
|
|
2163
|
+
);
|
|
2164
|
+
});
|
|
2165
|
+
|
|
2166
|
+
contextCmd
|
|
2167
|
+
.command("reverify <registration>")
|
|
2168
|
+
.description("Reverify one watched saved Context Capsule")
|
|
2169
|
+
.option("--json", "JSON output")
|
|
2170
|
+
.action(async (registration: string, cmdOpts: Record<string, unknown>) => {
|
|
2171
|
+
const format = getFormat(cmdOpts);
|
|
2172
|
+
assertFormatSupported(CMD.contextSaved, format);
|
|
2173
|
+
const globals = getGlobals();
|
|
2174
|
+
const { reverifyWatchedCapsule } =
|
|
2175
|
+
await import("./commands/context-saved");
|
|
2176
|
+
const result = await reverifyWatchedCapsule(registration, {
|
|
2177
|
+
configPath: globals.config,
|
|
2178
|
+
indexName: globals.index,
|
|
2179
|
+
format: format === "json" ? "json" : "terminal",
|
|
2180
|
+
});
|
|
2181
|
+
await writeOutput(result.output, format === "json" ? "json" : "terminal");
|
|
2182
|
+
if (result.operationStatus === "failed") {
|
|
2183
|
+
throw new CliError(
|
|
2184
|
+
"RUNTIME",
|
|
2185
|
+
result.errorMessage ??
|
|
2186
|
+
"Saved Context Capsule verification operation failed",
|
|
2187
|
+
{ operationStatus: "failed" }
|
|
2188
|
+
);
|
|
2189
|
+
}
|
|
2190
|
+
});
|
|
2191
|
+
|
|
2091
2192
|
contextCmd
|
|
2092
2193
|
.command("rm <uri>")
|
|
2093
2194
|
.description("Remove context item")
|
|
@@ -3088,6 +3189,100 @@ function wireGraphCommand(program: Command): void {
|
|
|
3088
3189
|
);
|
|
3089
3190
|
}
|
|
3090
3191
|
|
|
3192
|
+
function wireKnowledgeDeltaCommands(program: Command): void {
|
|
3193
|
+
program
|
|
3194
|
+
.command("changes")
|
|
3195
|
+
.description("List retained metadata-only document changes")
|
|
3196
|
+
.option("--since <time-or-cursor>", "ISO-8601 time or opaque cursor")
|
|
3197
|
+
.option("-c, --collection <name>", "filter by collection")
|
|
3198
|
+
.option("-n, --limit <num>", "maximum changes", "100")
|
|
3199
|
+
.option("--json", "JSON output")
|
|
3200
|
+
.action(async (cmdOpts: Record<string, unknown>) => {
|
|
3201
|
+
const format = getFormat(cmdOpts);
|
|
3202
|
+
assertFormatSupported(CMD.changes, format);
|
|
3203
|
+
const deltaFormat = format === "json" ? "json" : "terminal";
|
|
3204
|
+
const globals = getGlobals();
|
|
3205
|
+
const { changes, formatChanges } = await import("./commands/changes");
|
|
3206
|
+
const result = await changes(
|
|
3207
|
+
{
|
|
3208
|
+
since: cmdOpts.since as string | undefined,
|
|
3209
|
+
collection: cmdOpts.collection as string | undefined,
|
|
3210
|
+
limit: parsePositiveInt("limit", cmdOpts.limit),
|
|
3211
|
+
},
|
|
3212
|
+
{ configPath: globals.config, indexName: globals.index }
|
|
3213
|
+
);
|
|
3214
|
+
if (!result.success) {
|
|
3215
|
+
throw new CliError(
|
|
3216
|
+
result.isValidation ? "VALIDATION" : "RUNTIME",
|
|
3217
|
+
result.error
|
|
3218
|
+
);
|
|
3219
|
+
}
|
|
3220
|
+
await writeOutput(formatChanges(result.data, deltaFormat), deltaFormat);
|
|
3221
|
+
});
|
|
3222
|
+
|
|
3223
|
+
program
|
|
3224
|
+
.command("diff <doc>")
|
|
3225
|
+
.description("Show one retained metadata-only structural change")
|
|
3226
|
+
.option("--change <id>", "opaque change ID")
|
|
3227
|
+
.option("--json", "JSON output")
|
|
3228
|
+
.action(async (doc: string, cmdOpts: Record<string, unknown>) => {
|
|
3229
|
+
const format = getFormat(cmdOpts);
|
|
3230
|
+
assertFormatSupported(CMD.diff, format);
|
|
3231
|
+
const deltaFormat = format === "json" ? "json" : "terminal";
|
|
3232
|
+
const globals = getGlobals();
|
|
3233
|
+
const { diff, formatDiff } = await import("./commands/changes");
|
|
3234
|
+
const result = await diff(doc, cmdOpts.change as string | undefined, {
|
|
3235
|
+
configPath: globals.config,
|
|
3236
|
+
indexName: globals.index,
|
|
3237
|
+
});
|
|
3238
|
+
if (!result.success) {
|
|
3239
|
+
throw new CliError(
|
|
3240
|
+
result.isValidation ? "VALIDATION" : "RUNTIME",
|
|
3241
|
+
result.error
|
|
3242
|
+
);
|
|
3243
|
+
}
|
|
3244
|
+
await writeOutput(formatDiff(result.data, deltaFormat), deltaFormat);
|
|
3245
|
+
});
|
|
3246
|
+
|
|
3247
|
+
program
|
|
3248
|
+
.command("impact <doc>")
|
|
3249
|
+
.description("Find bounded inbound knowledge dependencies")
|
|
3250
|
+
.option("--max-depth <n>", "maximum dependency depth", "3")
|
|
3251
|
+
.option("--max-nodes <n>", "maximum returned nodes", "100")
|
|
3252
|
+
.option("--max-edges <n>", "maximum traversed evidence edges", "250")
|
|
3253
|
+
.option("--frontier-limit <n>", "maximum frontier width", "100")
|
|
3254
|
+
.option("--visited-limit <n>", "maximum visited rows", "500")
|
|
3255
|
+
.option("--json", "JSON output")
|
|
3256
|
+
.action(async (doc: string, cmdOpts: Record<string, unknown>) => {
|
|
3257
|
+
const format = getFormat(cmdOpts);
|
|
3258
|
+
assertFormatSupported(CMD.impact, format);
|
|
3259
|
+
const deltaFormat = format === "json" ? "json" : "terminal";
|
|
3260
|
+
const globals = getGlobals();
|
|
3261
|
+
const { impact, formatImpact } = await import("./commands/changes");
|
|
3262
|
+
const result = await impact(
|
|
3263
|
+
doc,
|
|
3264
|
+
{
|
|
3265
|
+
maxDepth: parsePositiveInt("max-depth", cmdOpts.maxDepth),
|
|
3266
|
+
maxNodes: parsePositiveInt("max-nodes", cmdOpts.maxNodes),
|
|
3267
|
+
maxEdges: parsePositiveInt("max-edges", cmdOpts.maxEdges),
|
|
3268
|
+
frontierLimit: parsePositiveInt(
|
|
3269
|
+
"frontier-limit",
|
|
3270
|
+
cmdOpts.frontierLimit
|
|
3271
|
+
),
|
|
3272
|
+
visitedLimit: parsePositiveInt("visited-limit", cmdOpts.visitedLimit),
|
|
3273
|
+
},
|
|
3274
|
+
{ configPath: globals.config, indexName: globals.index }
|
|
3275
|
+
);
|
|
3276
|
+
if (!result.success) {
|
|
3277
|
+
throw new CliError(
|
|
3278
|
+
result.isValidation ? "VALIDATION" : "RUNTIME",
|
|
3279
|
+
result.error
|
|
3280
|
+
);
|
|
3281
|
+
}
|
|
3282
|
+
await writeOutput(formatImpact(result.data, deltaFormat), deltaFormat);
|
|
3283
|
+
});
|
|
3284
|
+
}
|
|
3285
|
+
|
|
3091
3286
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
3092
3287
|
// Serve Command (web UI)
|
|
3093
3288
|
// ─────────────────────────────────────────────────────────────────────────────
|