@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.
- package/README.md +19 -4
- package/assets/skill/SKILL.md +46 -15
- package/package.json +1 -1
- package/spec/cli.md +100 -0
- package/spec/db/schema.sql +170 -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/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/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,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
|
+
});
|
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
|
|