@gmickel/gno 1.20.0 → 1.21.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.
Files changed (49) hide show
  1. package/README.md +19 -4
  2. package/assets/skill/SKILL.md +46 -15
  3. package/package.json +1 -1
  4. package/spec/cli.md +100 -0
  5. package/spec/db/schema.sql +170 -0
  6. package/spec/mcp.md +22 -0
  7. package/spec/output-schemas/capsule-reverified-event.schema.json +47 -0
  8. package/spec/output-schemas/changes.schema.json +280 -0
  9. package/spec/output-schemas/document-diff.schema.json +185 -0
  10. package/spec/output-schemas/impact.schema.json +122 -0
  11. package/spec/output-schemas/saved-capsule-list.schema.json +16 -0
  12. package/spec/output-schemas/saved-capsule-registration.schema.json +172 -0
  13. package/spec/output-schemas/saved-capsule-reverification.schema.json +59 -0
  14. package/spec/output-schemas/saved-capsule-unwatch.schema.json +16 -0
  15. package/spec/output-schemas/saved-capsule-watch.schema.json +17 -0
  16. package/src/cli/commands/changes.ts +160 -0
  17. package/src/cli/commands/context-saved.ts +189 -0
  18. package/src/cli/options.ts +8 -0
  19. package/src/cli/program.ts +195 -0
  20. package/src/core/capsule-registry.ts +279 -0
  21. package/src/core/capsule-reverification-scheduler.ts +218 -0
  22. package/src/core/capsule-reverification.ts +289 -0
  23. package/src/core/change-diff.ts +182 -0
  24. package/src/core/change-journal.ts +228 -0
  25. package/src/core/knowledge-delta.ts +395 -0
  26. package/src/core/knowledge-impact.ts +202 -0
  27. package/src/ingestion/sync.ts +214 -165
  28. package/src/mcp/tools/changes.ts +80 -0
  29. package/src/mcp/tools/index.ts +29 -0
  30. package/src/sdk/client.ts +42 -0
  31. package/src/sdk/index.ts +7 -0
  32. package/src/sdk/types.ts +22 -0
  33. package/src/serve/doc-events.ts +12 -1
  34. package/src/serve/resident-runtime.ts +22 -0
  35. package/src/serve/routes/api.ts +13 -0
  36. package/src/serve/routes/changes.ts +102 -0
  37. package/src/serve/server.ts +34 -0
  38. package/src/serve/watch-service.ts +9 -0
  39. package/src/store/index.ts +21 -0
  40. package/src/store/migrations/015-document-change-journal.ts +85 -0
  41. package/src/store/migrations/016-saved-capsules.ts +131 -0
  42. package/src/store/migrations/017-document-change-retention-counters.ts +33 -0
  43. package/src/store/migrations/018-saved-capsule-registration-epoch.ts +24 -0
  44. package/src/store/migrations/019-saved-capsule-registration-generation.ts +53 -0
  45. package/src/store/migrations/index.ts +10 -0
  46. package/src/store/sqlite/adapter.ts +291 -7
  47. package/src/store/sqlite/capsule-registry-store.ts +534 -0
  48. package/src/store/sqlite/change-journal-store.ts +473 -0
  49. package/src/store/types.ts +262 -0
@@ -0,0 +1,172 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "gno://schemas/saved-capsule-registration@1.0",
4
+ "title": "GNO Saved Context Capsule Registration",
5
+ "description": "Metadata-only registration for one caller-owned Context Capsule file. Capsule and passage bytes are never embedded in this payload.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": [
9
+ "registrationId",
10
+ "filePath",
11
+ "fileHash",
12
+ "capsuleId",
13
+ "indexName",
14
+ "question",
15
+ "label",
16
+ "notificationPreference",
17
+ "registeredAtMs",
18
+ "updatedAtMs",
19
+ "lastAttemptedSequence",
20
+ "evidence",
21
+ "verification"
22
+ ],
23
+ "properties": {
24
+ "registrationId": {
25
+ "type": "string",
26
+ "pattern": "^capsule-[a-f0-9]{40}$"
27
+ },
28
+ "filePath": { "type": "string", "minLength": 1, "maxLength": 8192 },
29
+ "fileHash": { "$ref": "#/definitions/sha256" },
30
+ "capsuleId": { "$ref": "#/definitions/sha256" },
31
+ "indexName": { "type": "string", "minLength": 1, "maxLength": 128 },
32
+ "question": {
33
+ "type": ["string", "null"],
34
+ "minLength": 1,
35
+ "maxLength": 8192
36
+ },
37
+ "label": {
38
+ "type": ["string", "null"],
39
+ "minLength": 1,
40
+ "maxLength": 512
41
+ },
42
+ "notificationPreference": { "enum": ["none", "local"] },
43
+ "registeredAtMs": { "type": "integer", "minimum": 0 },
44
+ "updatedAtMs": { "type": "integer", "minimum": 0 },
45
+ "lastAttemptedSequence": { "type": "integer", "minimum": 0 },
46
+ "evidence": {
47
+ "type": "array",
48
+ "maxItems": 10000,
49
+ "items": { "$ref": "#/definitions/evidenceReference" }
50
+ },
51
+ "verification": {
52
+ "anyOf": [{ "$ref": "#/definitions/verification" }, { "type": "null" }]
53
+ }
54
+ },
55
+ "definitions": {
56
+ "sha256": {
57
+ "type": "string",
58
+ "pattern": "^[a-f0-9]{64}$"
59
+ },
60
+ "evidenceReference": {
61
+ "type": "object",
62
+ "additionalProperties": false,
63
+ "required": [
64
+ "evidenceId",
65
+ "canonicalUri",
66
+ "collection",
67
+ "sourceHash",
68
+ "mirrorHash",
69
+ "passageHash"
70
+ ],
71
+ "properties": {
72
+ "evidenceId": { "$ref": "#/definitions/sha256" },
73
+ "canonicalUri": { "type": "string", "pattern": "^gno://.+" },
74
+ "collection": { "type": "string", "minLength": 1, "maxLength": 256 },
75
+ "sourceHash": { "$ref": "#/definitions/sha256" },
76
+ "mirrorHash": { "$ref": "#/definitions/sha256" },
77
+ "passageHash": { "$ref": "#/definitions/sha256" }
78
+ }
79
+ },
80
+ "verification": {
81
+ "type": "object",
82
+ "additionalProperties": false,
83
+ "required": [
84
+ "registrationId",
85
+ "triggerKind",
86
+ "fromSequence",
87
+ "throughSequence",
88
+ "operationStatus",
89
+ "affectedQuestionState",
90
+ "affectedReasons",
91
+ "receiptJson",
92
+ "receiptHash",
93
+ "errorCode",
94
+ "errorMessage",
95
+ "verifiedAtMs"
96
+ ],
97
+ "properties": {
98
+ "registrationId": {
99
+ "type": "string",
100
+ "pattern": "^capsule-[a-f0-9]{40}$"
101
+ },
102
+ "triggerKind": { "enum": ["manual", "journal"] },
103
+ "fromSequence": { "type": "integer", "minimum": 0 },
104
+ "throughSequence": { "type": "integer", "minimum": 0 },
105
+ "operationStatus": { "enum": ["completed", "failed"] },
106
+ "affectedQuestionState": {
107
+ "enum": ["unaffected", "affected", "unknown"]
108
+ },
109
+ "affectedReasons": {
110
+ "type": "array",
111
+ "maxItems": 4,
112
+ "uniqueItems": true,
113
+ "items": {
114
+ "enum": [
115
+ "content_stale",
116
+ "content_missing",
117
+ "ranking_changed",
118
+ "fingerprint_changed"
119
+ ]
120
+ }
121
+ },
122
+ "receiptJson": {
123
+ "type": ["string", "null"],
124
+ "maxLength": 16777216
125
+ },
126
+ "receiptHash": {
127
+ "anyOf": [{ "$ref": "#/definitions/sha256" }, { "type": "null" }]
128
+ },
129
+ "errorCode": {
130
+ "type": ["string", "null"],
131
+ "minLength": 1,
132
+ "maxLength": 256
133
+ },
134
+ "errorMessage": {
135
+ "type": ["string", "null"],
136
+ "minLength": 1,
137
+ "maxLength": 4096
138
+ },
139
+ "verifiedAtMs": { "type": "integer", "minimum": 0 }
140
+ },
141
+ "allOf": [
142
+ {
143
+ "if": {
144
+ "properties": { "operationStatus": { "const": "completed" } },
145
+ "required": ["operationStatus"]
146
+ },
147
+ "then": {
148
+ "properties": {
149
+ "affectedQuestionState": {
150
+ "enum": ["unaffected", "affected"]
151
+ },
152
+ "receiptJson": { "type": "string", "minLength": 1 },
153
+ "receiptHash": { "$ref": "#/definitions/sha256" },
154
+ "errorCode": { "type": "null" },
155
+ "errorMessage": { "type": "null" }
156
+ }
157
+ },
158
+ "else": {
159
+ "properties": {
160
+ "affectedQuestionState": { "const": "unknown" },
161
+ "affectedReasons": { "type": "array", "maxItems": 0 },
162
+ "receiptJson": { "type": "null" },
163
+ "receiptHash": { "type": "null" },
164
+ "errorCode": { "type": "string", "minLength": 1 },
165
+ "errorMessage": { "type": "string", "minLength": 1 }
166
+ }
167
+ }
168
+ }
169
+ ]
170
+ }
171
+ }
172
+ }
@@ -0,0 +1,59 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "gno://schemas/saved-capsule-reverification@1.0",
4
+ "title": "GNO Saved Context Capsule Reverification Result",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schemaVersion", "registration", "verification", "receipt"],
8
+ "properties": {
9
+ "schemaVersion": { "const": "1.0" },
10
+ "registration": {
11
+ "$ref": "gno://schemas/saved-capsule-registration@1.0"
12
+ },
13
+ "verification": {
14
+ "$ref": "gno://schemas/saved-capsule-registration@1.0#/definitions/verification"
15
+ },
16
+ "receipt": {
17
+ "anyOf": [
18
+ { "$ref": "gno://schemas/context-capsule-verification@1.0" },
19
+ { "type": "null" }
20
+ ]
21
+ }
22
+ },
23
+ "allOf": [
24
+ {
25
+ "if": {
26
+ "properties": {
27
+ "verification": {
28
+ "type": "object",
29
+ "properties": { "operationStatus": { "const": "completed" } },
30
+ "required": ["operationStatus"]
31
+ }
32
+ },
33
+ "required": ["verification"]
34
+ },
35
+ "then": {
36
+ "properties": {
37
+ "verification": {
38
+ "type": "object",
39
+ "properties": { "triggerKind": { "const": "manual" } },
40
+ "required": ["triggerKind"]
41
+ },
42
+ "receipt": {
43
+ "$ref": "gno://schemas/context-capsule-verification@1.0"
44
+ }
45
+ }
46
+ },
47
+ "else": {
48
+ "properties": {
49
+ "verification": {
50
+ "type": "object",
51
+ "properties": { "triggerKind": { "const": "manual" } },
52
+ "required": ["triggerKind"]
53
+ },
54
+ "receipt": { "type": "null" }
55
+ }
56
+ }
57
+ }
58
+ ]
59
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "gno://schemas/saved-capsule-unwatch@1.0",
4
+ "title": "GNO Saved Context Capsule Unwatch Receipt",
5
+ "type": "object",
6
+ "additionalProperties": false,
7
+ "required": ["schemaVersion", "registrationId", "removed"],
8
+ "properties": {
9
+ "schemaVersion": { "const": "1.0" },
10
+ "registrationId": {
11
+ "type": "string",
12
+ "pattern": "^capsule-[a-f0-9]{40}$"
13
+ },
14
+ "removed": { "const": true }
15
+ }
16
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "gno://schemas/saved-capsule-watch@1.0",
4
+ "title": "GNO Saved Context Capsule Watch Result",
5
+ "allOf": [
6
+ {
7
+ "$ref": "gno://schemas/saved-capsule-registration@1.0"
8
+ },
9
+ {
10
+ "type": "object",
11
+ "properties": {
12
+ "verification": { "type": "null" }
13
+ },
14
+ "required": ["verification"]
15
+ }
16
+ ]
17
+ }
@@ -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
+ });
@@ -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