@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.
Files changed (96) hide show
  1. package/README.md +18 -4
  2. package/assets/skill/SKILL.md +40 -17
  3. package/assets/skill/recipes/capture-and-file.md +20 -5
  4. package/browser-extension/artifacts/gno-browser-clipper-v1.26.0.zip +0 -0
  5. package/browser-extension/artifacts/gno-browser-clipper-v1.26.0.zip.sha256 +1 -0
  6. package/browser-extension/dist/PRIVACY.md +55 -0
  7. package/browser-extension/dist/chunk-vn5f663b.js +50 -0
  8. package/browser-extension/dist/chunk-ydfx5d7p.css +1 -0
  9. package/browser-extension/dist/content.js +1 -0
  10. package/browser-extension/dist/manifest.json +25 -0
  11. package/browser-extension/dist/preview.html +13 -0
  12. package/browser-extension/dist/service-worker.js +40 -0
  13. package/package.json +13 -3
  14. package/spec/cli.md +196 -4
  15. package/spec/db/schema.sql +102 -1
  16. package/spec/mcp.md +10 -0
  17. package/spec/output-schemas/browser-clip-preview.schema.json +83 -0
  18. package/spec/output-schemas/browser-clip.schema.json +586 -0
  19. package/spec/output-schemas/capture-receipt.schema.json +22 -1
  20. package/spec/output-schemas/clipper-csrf.schema.json +12 -0
  21. package/spec/output-schemas/clipper-error.schema.json +46 -0
  22. package/spec/output-schemas/clipper-pair-approval.schema.json +17 -0
  23. package/spec/output-schemas/clipper-pair-start.schema.json +26 -0
  24. package/spec/output-schemas/clipper-pair-status.schema.json +46 -0
  25. package/spec/output-schemas/clipper-revoke.schema.json +28 -0
  26. package/spec/output-schemas/mcp-capture-result.schema.json +12 -1
  27. package/spec/output-schemas/project-profile-apply.schema.json +209 -0
  28. package/spec/output-schemas/project-profile-command.schema.json +160 -0
  29. package/spec/output-schemas/query-diagnose.schema.json +7 -1
  30. package/spec/output-schemas/setup-profile-result.schema.json +87 -0
  31. package/spec/project-profile.schema.json +301 -0
  32. package/src/cli/commands/collection/add.ts +39 -45
  33. package/src/cli/commands/collection/remove.ts +28 -28
  34. package/src/cli/commands/collection/rename.ts +55 -73
  35. package/src/cli/commands/context/add.ts +37 -26
  36. package/src/cli/commands/context/rm.ts +47 -20
  37. package/src/cli/commands/init.ts +55 -125
  38. package/src/cli/commands/models/use.ts +43 -38
  39. package/src/cli/commands/profile-apply.ts +334 -0
  40. package/src/cli/commands/profile.ts +409 -0
  41. package/src/cli/commands/setup-activation.ts +205 -54
  42. package/src/cli/commands/setup-profile.ts +223 -0
  43. package/src/cli/commands/setup.ts +3 -0
  44. package/src/cli/program.ts +110 -9
  45. package/src/config/index.ts +3 -0
  46. package/src/config/project-profile.ts +367 -0
  47. package/src/config/saver.ts +16 -7
  48. package/src/config/types.ts +42 -0
  49. package/src/core/browser-clip-provenance.ts +139 -0
  50. package/src/core/browser-clip.ts +473 -0
  51. package/src/core/capture-write.ts +5 -0
  52. package/src/core/capture.ts +75 -18
  53. package/src/core/config-mutation.ts +138 -76
  54. package/src/core/config-write-lock.ts +89 -0
  55. package/src/core/context-identity.ts +16 -0
  56. package/src/core/context-resolver.ts +2 -12
  57. package/src/core/file-lock.ts +20 -6
  58. package/src/core/folder-setup-planning.ts +6 -21
  59. package/src/core/folder-setup.ts +30 -2
  60. package/src/core/path-rules.ts +53 -0
  61. package/src/core/project-affinity-surface.ts +102 -7
  62. package/src/core/project-profile-apply-state.ts +268 -0
  63. package/src/core/project-profile-apply-validation.ts +95 -0
  64. package/src/core/project-profile-apply.ts +408 -0
  65. package/src/core/project-profile-canonical.ts +71 -0
  66. package/src/core/project-profile-diff.ts +302 -0
  67. package/src/core/project-profile-discovery.ts +519 -0
  68. package/src/core/project-profile-file.ts +37 -0
  69. package/src/core/project-profile-parser.ts +98 -0
  70. package/src/core/project-profile.ts +490 -0
  71. package/src/ingestion/walker.ts +85 -44
  72. package/src/llm/cache.ts +21 -0
  73. package/src/serve/capture-service.ts +420 -0
  74. package/src/serve/clipper-body.ts +62 -0
  75. package/src/serve/clipper-capture.ts +248 -0
  76. package/src/serve/clipper-contract.ts +57 -0
  77. package/src/serve/clipper-idempotency.ts +35 -0
  78. package/src/serve/clipper-pairing.ts +297 -0
  79. package/src/serve/clipper-security-errors.ts +23 -0
  80. package/src/serve/clipper-security.ts +449 -0
  81. package/src/serve/config-sync.ts +2 -2
  82. package/src/serve/public/app.tsx +8 -1
  83. package/src/serve/public/globals.built.css +1 -1
  84. package/src/serve/public/index.html +1 -0
  85. package/src/serve/public/lib/clipper-approval.ts +206 -0
  86. package/src/serve/public/pages/ClipperPairing.tsx +210 -0
  87. package/src/serve/resident-runtime.ts +1 -0
  88. package/src/serve/routes/api.ts +20 -115
  89. package/src/serve/routes/clipper.ts +394 -0
  90. package/src/serve/server.ts +22 -0
  91. package/src/store/migrations/020-browser-clipper-security.ts +128 -0
  92. package/src/store/migrations/021-multi-context-identity.ts +37 -0
  93. package/src/store/migrations/index.ts +4 -0
  94. package/src/store/sqlite/adapter.ts +83 -0
  95. package/src/store/sqlite/clipper-store-types.ts +104 -0
  96. 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
+ }