@gmickel/gno 1.24.0 → 1.26.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 +18 -4
- package/assets/skill/SKILL.md +40 -17
- package/assets/skill/recipes/capture-and-file.md +20 -5
- package/browser-extension/artifacts/gno-browser-clipper-v1.26.0.zip +0 -0
- package/browser-extension/artifacts/gno-browser-clipper-v1.26.0.zip.sha256 +1 -0
- package/browser-extension/dist/PRIVACY.md +55 -0
- package/browser-extension/dist/chunk-vn5f663b.js +50 -0
- package/browser-extension/dist/chunk-ydfx5d7p.css +1 -0
- package/browser-extension/dist/content.js +1 -0
- package/browser-extension/dist/manifest.json +25 -0
- package/browser-extension/dist/preview.html +13 -0
- package/browser-extension/dist/service-worker.js +40 -0
- package/package.json +13 -3
- package/spec/cli.md +196 -4
- package/spec/db/schema.sql +102 -1
- package/spec/mcp.md +10 -0
- package/spec/output-schemas/browser-clip-preview.schema.json +83 -0
- package/spec/output-schemas/browser-clip.schema.json +586 -0
- package/spec/output-schemas/capture-receipt.schema.json +22 -1
- package/spec/output-schemas/clipper-csrf.schema.json +12 -0
- package/spec/output-schemas/clipper-error.schema.json +46 -0
- package/spec/output-schemas/clipper-pair-approval.schema.json +17 -0
- package/spec/output-schemas/clipper-pair-start.schema.json +26 -0
- package/spec/output-schemas/clipper-pair-status.schema.json +46 -0
- package/spec/output-schemas/clipper-revoke.schema.json +28 -0
- package/spec/output-schemas/mcp-capture-result.schema.json +12 -1
- package/spec/output-schemas/project-profile-apply.schema.json +209 -0
- package/spec/output-schemas/project-profile-command.schema.json +160 -0
- package/spec/output-schemas/query-diagnose.schema.json +7 -1
- package/spec/output-schemas/setup-profile-result.schema.json +87 -0
- package/spec/project-profile.schema.json +301 -0
- package/src/cli/commands/collection/add.ts +39 -45
- package/src/cli/commands/collection/remove.ts +28 -28
- package/src/cli/commands/collection/rename.ts +55 -73
- package/src/cli/commands/context/add.ts +37 -26
- package/src/cli/commands/context/rm.ts +47 -20
- package/src/cli/commands/init.ts +55 -125
- package/src/cli/commands/models/use.ts +43 -38
- package/src/cli/commands/profile-apply.ts +334 -0
- package/src/cli/commands/profile.ts +409 -0
- package/src/cli/commands/setup-activation.ts +205 -54
- package/src/cli/commands/setup-profile.ts +223 -0
- package/src/cli/commands/setup.ts +3 -0
- package/src/cli/program.ts +110 -9
- package/src/config/index.ts +3 -0
- package/src/config/project-profile.ts +367 -0
- package/src/config/saver.ts +16 -7
- package/src/config/types.ts +42 -0
- package/src/core/browser-clip-provenance.ts +139 -0
- package/src/core/browser-clip.ts +473 -0
- package/src/core/capture-write.ts +5 -0
- package/src/core/capture.ts +75 -18
- package/src/core/config-mutation.ts +138 -76
- package/src/core/config-write-lock.ts +89 -0
- package/src/core/context-identity.ts +16 -0
- package/src/core/context-resolver.ts +2 -12
- package/src/core/file-lock.ts +20 -6
- package/src/core/folder-setup-planning.ts +6 -21
- package/src/core/folder-setup.ts +30 -2
- package/src/core/path-rules.ts +53 -0
- package/src/core/project-affinity-surface.ts +102 -7
- package/src/core/project-profile-apply-state.ts +268 -0
- package/src/core/project-profile-apply-validation.ts +95 -0
- package/src/core/project-profile-apply.ts +408 -0
- package/src/core/project-profile-canonical.ts +71 -0
- package/src/core/project-profile-diff.ts +302 -0
- package/src/core/project-profile-discovery.ts +519 -0
- package/src/core/project-profile-file.ts +37 -0
- package/src/core/project-profile-parser.ts +98 -0
- package/src/core/project-profile.ts +490 -0
- package/src/ingestion/walker.ts +85 -44
- package/src/llm/cache.ts +21 -0
- package/src/serve/capture-service.ts +420 -0
- package/src/serve/clipper-body.ts +62 -0
- package/src/serve/clipper-capture.ts +248 -0
- package/src/serve/clipper-contract.ts +57 -0
- package/src/serve/clipper-idempotency.ts +35 -0
- package/src/serve/clipper-pairing.ts +297 -0
- package/src/serve/clipper-security-errors.ts +23 -0
- package/src/serve/clipper-security.ts +449 -0
- package/src/serve/config-sync.ts +2 -2
- package/src/serve/public/app.tsx +8 -1
- package/src/serve/public/globals.built.css +1 -1
- package/src/serve/public/index.html +1 -0
- package/src/serve/public/lib/clipper-approval.ts +206 -0
- package/src/serve/public/pages/ClipperPairing.tsx +210 -0
- package/src/serve/resident-runtime.ts +1 -0
- package/src/serve/routes/api.ts +20 -115
- package/src/serve/routes/clipper.ts +394 -0
- package/src/serve/server.ts +22 -0
- package/src/store/migrations/020-browser-clipper-security.ts +128 -0
- package/src/store/migrations/021-multi-context-identity.ts +37 -0
- package/src/store/migrations/index.ts +4 -0
- package/src/store/sqlite/adapter.ts +83 -0
- package/src/store/sqlite/clipper-store-types.ts +104 -0
- package/src/store/sqlite/clipper-store.ts +496 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic, path-redacted comparison of project profile desired state
|
|
3
|
+
* with current local config.
|
|
4
|
+
*
|
|
5
|
+
* @module src/core/project-profile-diff
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Config, ProjectProfileBinding } from "../config/types";
|
|
9
|
+
import type { ProjectProfileDesiredState } from "./project-profile";
|
|
10
|
+
|
|
11
|
+
import { getPreset } from "../llm/registry";
|
|
12
|
+
import { normalizePersistedContextText } from "./context-identity";
|
|
13
|
+
import {
|
|
14
|
+
canonicalProjectProfileJson,
|
|
15
|
+
projectProfileIncludePattern,
|
|
16
|
+
} from "./project-profile";
|
|
17
|
+
|
|
18
|
+
export interface ProjectProfileChange {
|
|
19
|
+
action: "add" | "update" | "repair" | "review";
|
|
20
|
+
field: string;
|
|
21
|
+
destructive: boolean;
|
|
22
|
+
summary: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ProjectProfileStaleMapping {
|
|
26
|
+
collection: string;
|
|
27
|
+
reason: "name_changed" | "root_changed";
|
|
28
|
+
choices: ["repair", "remove_explicitly"];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ProjectProfileDiff {
|
|
32
|
+
status: "in_sync" | "changes_required";
|
|
33
|
+
changes: ProjectProfileChange[];
|
|
34
|
+
staleMappings: ProjectProfileStaleMapping[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ProjectProfileDiffDiagnostic {
|
|
38
|
+
code: "STALE_PROFILE_MAPPING";
|
|
39
|
+
severity: "warning";
|
|
40
|
+
path: string;
|
|
41
|
+
message: string;
|
|
42
|
+
remediation: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface BuildProjectProfileDiffOptions {
|
|
46
|
+
desiredState: ProjectProfileDesiredState;
|
|
47
|
+
expectedCollectionRoot: string;
|
|
48
|
+
profileBinding: ProjectProfileBinding;
|
|
49
|
+
config: Config;
|
|
50
|
+
canonicalizePath: (path: string) => Promise<string>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const compareCodeUnits = (left: string, right: string): number =>
|
|
54
|
+
left < right ? -1 : left > right ? 1 : 0;
|
|
55
|
+
|
|
56
|
+
const canonicalEqual = (left: unknown, right: unknown): boolean =>
|
|
57
|
+
canonicalProjectProfileJson(left) === canonicalProjectProfileJson(right);
|
|
58
|
+
|
|
59
|
+
const hasEveryProfileContext = (
|
|
60
|
+
desiredState: ProjectProfileDesiredState,
|
|
61
|
+
config: Config
|
|
62
|
+
): boolean => {
|
|
63
|
+
const configured = new Set(
|
|
64
|
+
config.contexts
|
|
65
|
+
.filter(
|
|
66
|
+
(context) =>
|
|
67
|
+
context.scopeType === "collection" &&
|
|
68
|
+
context.scopeKey === `${desiredState.collection.name}:`
|
|
69
|
+
)
|
|
70
|
+
.map((context) => normalizePersistedContextText(context.text))
|
|
71
|
+
);
|
|
72
|
+
return desiredState.contexts.every((context) =>
|
|
73
|
+
configured.has(normalizePersistedContextText(context.text))
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const desiredCollectionRules = (
|
|
78
|
+
desiredState: ProjectProfileDesiredState
|
|
79
|
+
): { pattern: string; include: string[]; exclude: string[] } => ({
|
|
80
|
+
pattern: projectProfileIncludePattern(desiredState.collection.include),
|
|
81
|
+
include: [],
|
|
82
|
+
exclude: desiredState.collection.exclude,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const configuredCollectionRules = (
|
|
86
|
+
collection: Config["collections"][number]
|
|
87
|
+
): { pattern: string; include: string[]; exclude: string[] } => ({
|
|
88
|
+
pattern: collection.pattern,
|
|
89
|
+
include: [...collection.include].sort(compareCodeUnits),
|
|
90
|
+
exclude: [...collection.exclude].sort(compareCodeUnits),
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const modelSelectionMatches = (
|
|
94
|
+
config: Config,
|
|
95
|
+
collection: Config["collections"][number],
|
|
96
|
+
presetId: string
|
|
97
|
+
): boolean => {
|
|
98
|
+
const preset = getPreset(config, presetId);
|
|
99
|
+
if (!preset) return false;
|
|
100
|
+
return canonicalEqual(collection.models ?? {}, {
|
|
101
|
+
embed: preset.embed,
|
|
102
|
+
rerank: preset.rerank,
|
|
103
|
+
...(preset.expand ? { expand: preset.expand } : {}),
|
|
104
|
+
gen: preset.gen,
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const hasEveryProfileContentType = (
|
|
109
|
+
desiredState: ProjectProfileDesiredState,
|
|
110
|
+
config: Config
|
|
111
|
+
): boolean => {
|
|
112
|
+
const configured = new Map(
|
|
113
|
+
(config.contentTypes ?? []).map((rule) => [rule.id, rule])
|
|
114
|
+
);
|
|
115
|
+
return desiredState.contentTypes.every((rule) =>
|
|
116
|
+
canonicalEqual(configured.get(rule.id), rule)
|
|
117
|
+
);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const canonicalizeConfiguredPaths = async (
|
|
121
|
+
config: Config,
|
|
122
|
+
canonicalizePath: (path: string) => Promise<string>
|
|
123
|
+
): Promise<Map<string, string>> => {
|
|
124
|
+
const paths = new Map<string, string>();
|
|
125
|
+
for (const collection of config.collections) {
|
|
126
|
+
try {
|
|
127
|
+
paths.set(collection.name, await canonicalizePath(collection.path));
|
|
128
|
+
} catch {
|
|
129
|
+
// Unavailable roots are stale. Never echo their machine-local values.
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return paths;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export async function buildProjectProfileDiff(
|
|
136
|
+
options: BuildProjectProfileDiffOptions
|
|
137
|
+
): Promise<{
|
|
138
|
+
diff: ProjectProfileDiff;
|
|
139
|
+
diagnostics: ProjectProfileDiffDiagnostic[];
|
|
140
|
+
}> {
|
|
141
|
+
const { desiredState, expectedCollectionRoot, config, canonicalizePath } =
|
|
142
|
+
options;
|
|
143
|
+
const changes: ProjectProfileChange[] = [];
|
|
144
|
+
const staleMappings: ProjectProfileStaleMapping[] = [];
|
|
145
|
+
const diagnostics: ProjectProfileDiffDiagnostic[] = [];
|
|
146
|
+
const collectionName = desiredState.collection.name;
|
|
147
|
+
const target = config.collections.find(
|
|
148
|
+
(collection) => collection.name === collectionName
|
|
149
|
+
);
|
|
150
|
+
const canonicalPaths = await canonicalizeConfiguredPaths(
|
|
151
|
+
config,
|
|
152
|
+
canonicalizePath
|
|
153
|
+
);
|
|
154
|
+
const targetRoot = canonicalPaths.get(collectionName);
|
|
155
|
+
const currentBinding = config.projectProfileBindings?.find(
|
|
156
|
+
(binding) => binding.path === options.profileBinding.path
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
if (!currentBinding) {
|
|
160
|
+
changes.push({
|
|
161
|
+
action: "add",
|
|
162
|
+
field: "projectProfileBindings",
|
|
163
|
+
destructive: false,
|
|
164
|
+
summary: "Record local provenance for the selected project profile.",
|
|
165
|
+
});
|
|
166
|
+
} else if (!canonicalEqual(currentBinding, options.profileBinding)) {
|
|
167
|
+
changes.push({
|
|
168
|
+
action: "update",
|
|
169
|
+
field: "projectProfileBindings",
|
|
170
|
+
destructive: false,
|
|
171
|
+
summary: "Update local provenance for the selected project profile.",
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (!target) {
|
|
176
|
+
changes.push({
|
|
177
|
+
action: "add",
|
|
178
|
+
field: "collection",
|
|
179
|
+
destructive: false,
|
|
180
|
+
summary: `Add collection "${collectionName}" from the project profile.`,
|
|
181
|
+
});
|
|
182
|
+
} else {
|
|
183
|
+
if (targetRoot !== expectedCollectionRoot) {
|
|
184
|
+
changes.push({
|
|
185
|
+
action: "repair",
|
|
186
|
+
field: "collection.root",
|
|
187
|
+
destructive: false,
|
|
188
|
+
summary: `Repair the local root mapping for collection "${collectionName}".`,
|
|
189
|
+
});
|
|
190
|
+
staleMappings.push({
|
|
191
|
+
collection: collectionName,
|
|
192
|
+
reason: "root_changed",
|
|
193
|
+
choices: ["repair", "remove_explicitly"],
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
if (
|
|
197
|
+
!canonicalEqual(
|
|
198
|
+
configuredCollectionRules(target),
|
|
199
|
+
desiredCollectionRules(desiredState)
|
|
200
|
+
)
|
|
201
|
+
) {
|
|
202
|
+
changes.push({
|
|
203
|
+
action: "update",
|
|
204
|
+
field: "collection.rules",
|
|
205
|
+
destructive: false,
|
|
206
|
+
summary: "Update portable include and exclude rules.",
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
if (
|
|
210
|
+
desiredState.collection.languageHint !== undefined &&
|
|
211
|
+
target.languageHint !== desiredState.collection.languageHint
|
|
212
|
+
) {
|
|
213
|
+
changes.push({
|
|
214
|
+
action: "update",
|
|
215
|
+
field: "collection.languageHint",
|
|
216
|
+
destructive: false,
|
|
217
|
+
summary: "Update the collection language hint.",
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
if (
|
|
221
|
+
desiredState.collection.modelPreset &&
|
|
222
|
+
!modelSelectionMatches(
|
|
223
|
+
config,
|
|
224
|
+
target,
|
|
225
|
+
desiredState.collection.modelPreset
|
|
226
|
+
)
|
|
227
|
+
) {
|
|
228
|
+
changes.push({
|
|
229
|
+
action: "update",
|
|
230
|
+
field: "collection.modelPreset",
|
|
231
|
+
destructive: false,
|
|
232
|
+
summary: "Update the collection model preset alias.",
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
for (const collection of config.collections) {
|
|
238
|
+
if (
|
|
239
|
+
collection.name !== collectionName &&
|
|
240
|
+
canonicalPaths.get(collection.name) === expectedCollectionRoot
|
|
241
|
+
) {
|
|
242
|
+
staleMappings.push({
|
|
243
|
+
collection: collection.name,
|
|
244
|
+
reason: "name_changed",
|
|
245
|
+
choices: ["repair", "remove_explicitly"],
|
|
246
|
+
});
|
|
247
|
+
changes.push({
|
|
248
|
+
action: "review",
|
|
249
|
+
field: `collections.${collection.name}`,
|
|
250
|
+
destructive: false,
|
|
251
|
+
summary: `Review stale collection mapping "${collection.name}"; removal is never implicit.`,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (!hasEveryProfileContext(desiredState, config)) {
|
|
257
|
+
changes.push({
|
|
258
|
+
action: "update",
|
|
259
|
+
field: "contexts",
|
|
260
|
+
destructive: false,
|
|
261
|
+
summary: "Update collection-scoped profile contexts.",
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
if (!hasEveryProfileContentType(desiredState, config)) {
|
|
265
|
+
changes.push({
|
|
266
|
+
action: "update",
|
|
267
|
+
field: "contentTypes",
|
|
268
|
+
destructive: false,
|
|
269
|
+
summary: "Update normalized content type rules.",
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
changes.sort(
|
|
273
|
+
(left, right) =>
|
|
274
|
+
compareCodeUnits(left.field, right.field) ||
|
|
275
|
+
compareCodeUnits(left.action, right.action)
|
|
276
|
+
);
|
|
277
|
+
staleMappings.sort(
|
|
278
|
+
(left, right) =>
|
|
279
|
+
compareCodeUnits(left.collection, right.collection) ||
|
|
280
|
+
compareCodeUnits(left.reason, right.reason)
|
|
281
|
+
);
|
|
282
|
+
for (const mapping of staleMappings) {
|
|
283
|
+
diagnostics.push({
|
|
284
|
+
code: "STALE_PROFILE_MAPPING",
|
|
285
|
+
severity: "warning",
|
|
286
|
+
path: `collections.${mapping.collection}`,
|
|
287
|
+
message:
|
|
288
|
+
mapping.reason === "root_changed"
|
|
289
|
+
? `Collection "${mapping.collection}" points at a different local root.`
|
|
290
|
+
: `Collection "${mapping.collection}" maps the selected root under a different name.`,
|
|
291
|
+
remediation: `Choose repair or run gno collection remove ${mapping.collection} explicitly; diff made no changes.`,
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
return {
|
|
295
|
+
diff: {
|
|
296
|
+
status: changes.length === 0 ? "in_sync" : "changes_required",
|
|
297
|
+
changes,
|
|
298
|
+
staleMappings,
|
|
299
|
+
},
|
|
300
|
+
diagnostics,
|
|
301
|
+
};
|
|
302
|
+
}
|