@gmickel/gno 1.25.1 → 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 (56) hide show
  1. package/README.md +10 -4
  2. package/assets/skill/SKILL.md +29 -17
  3. package/browser-extension/artifacts/{gno-browser-clipper-v1.25.1.zip → gno-browser-clipper-v1.26.0.zip} +0 -0
  4. package/browser-extension/artifacts/gno-browser-clipper-v1.26.0.zip.sha256 +1 -0
  5. package/browser-extension/dist/manifest.json +1 -1
  6. package/package.json +1 -1
  7. package/spec/cli.md +146 -4
  8. package/spec/db/schema.sql +1 -1
  9. package/spec/output-schemas/project-profile-apply.schema.json +209 -0
  10. package/spec/output-schemas/project-profile-command.schema.json +160 -0
  11. package/spec/output-schemas/query-diagnose.schema.json +7 -1
  12. package/spec/output-schemas/setup-profile-result.schema.json +87 -0
  13. package/spec/project-profile.schema.json +301 -0
  14. package/src/cli/commands/collection/add.ts +39 -45
  15. package/src/cli/commands/collection/remove.ts +28 -28
  16. package/src/cli/commands/collection/rename.ts +55 -73
  17. package/src/cli/commands/context/add.ts +37 -26
  18. package/src/cli/commands/context/rm.ts +47 -20
  19. package/src/cli/commands/init.ts +55 -125
  20. package/src/cli/commands/models/use.ts +43 -38
  21. package/src/cli/commands/profile-apply.ts +334 -0
  22. package/src/cli/commands/profile.ts +409 -0
  23. package/src/cli/commands/setup-activation.ts +205 -54
  24. package/src/cli/commands/setup-profile.ts +223 -0
  25. package/src/cli/commands/setup.ts +3 -0
  26. package/src/cli/program.ts +110 -9
  27. package/src/config/index.ts +3 -0
  28. package/src/config/project-profile.ts +367 -0
  29. package/src/config/saver.ts +16 -7
  30. package/src/config/types.ts +42 -0
  31. package/src/core/config-mutation.ts +138 -76
  32. package/src/core/config-write-lock.ts +89 -0
  33. package/src/core/context-identity.ts +16 -0
  34. package/src/core/context-resolver.ts +2 -12
  35. package/src/core/folder-setup-planning.ts +6 -21
  36. package/src/core/folder-setup.ts +30 -2
  37. package/src/core/path-rules.ts +53 -0
  38. package/src/core/project-affinity-surface.ts +102 -7
  39. package/src/core/project-profile-apply-state.ts +268 -0
  40. package/src/core/project-profile-apply-validation.ts +95 -0
  41. package/src/core/project-profile-apply.ts +408 -0
  42. package/src/core/project-profile-canonical.ts +71 -0
  43. package/src/core/project-profile-diff.ts +302 -0
  44. package/src/core/project-profile-discovery.ts +519 -0
  45. package/src/core/project-profile-file.ts +37 -0
  46. package/src/core/project-profile-parser.ts +98 -0
  47. package/src/core/project-profile.ts +490 -0
  48. package/src/ingestion/walker.ts +85 -44
  49. package/src/llm/cache.ts +21 -0
  50. package/src/serve/config-sync.ts +2 -2
  51. package/src/serve/resident-runtime.ts +1 -0
  52. package/src/serve/routes/api.ts +1 -0
  53. package/src/store/migrations/021-multi-context-identity.ts +37 -0
  54. package/src/store/migrations/index.ts +2 -0
  55. package/src/store/sqlite/adapter.ts +83 -0
  56. package/browser-extension/artifacts/gno-browser-clipper-v1.25.1.zip.sha256 +0 -1
@@ -5,11 +5,10 @@ import type {
5
5
  SetupConnectorState,
6
6
  } from "../../core/setup-activation";
7
7
  import type { SqliteAdapter } from "../../store/sqlite/adapter";
8
- import type {
9
- SetupCommandOptions,
10
- SetupCommandOutcome,
11
- SetupCommandResult,
12
- } from "./setup";
8
+ import type { ProjectProfileCommandResult } from "./profile";
9
+ import type { ProjectProfileApplyCommandResult } from "./profile-apply";
10
+ import type { SetupCommandOutcome, SetupCommandResult } from "./setup";
11
+ import type { SetupProfileIntegrationOptions } from "./setup-profile";
13
12
 
14
13
  import { getIndexDbPath } from "../../app/constants";
15
14
  import { getConfigPaths, loadConfig, toAbsolutePath } from "../../config";
@@ -31,6 +30,16 @@ import {
31
30
  SETUP_COMMAND_SCHEMA_VERSION,
32
31
  setup,
33
32
  } from "./setup";
33
+ import {
34
+ applySetupProfile,
35
+ inspectSetupProfile,
36
+ profileApplyFailureOutcome,
37
+ profileApplySucceeded,
38
+ profileInspectionFailureOutcome,
39
+ setupOptionsAfterProfileApply,
40
+ } from "./setup-profile";
41
+
42
+ export { formatSetupProfileAdvisory } from "./setup-profile";
34
43
 
35
44
  export interface SetupActivationResult {
36
45
  schemaVersion: typeof SETUP_ACTIVATION_SCHEMA_VERSION;
@@ -39,26 +48,38 @@ export interface SetupActivationResult {
39
48
  connectors: SetupConnectorResult[];
40
49
  }
41
50
 
42
- export type SetupOutputResult = SetupCommandResult | SetupActivationResult;
51
+ export interface SetupProfileResult {
52
+ schemaVersion: "1.0";
53
+ status: "completed" | "completed_with_actions" | "failed";
54
+ profile: {
55
+ check: ProjectProfileCommandResult;
56
+ apply: ProjectProfileApplyCommandResult | null;
57
+ };
58
+ setup: SetupCommandResult;
59
+ connectors: SetupConnectorResult[];
60
+ }
61
+
62
+ export type SetupOutputResult =
63
+ | SetupCommandResult
64
+ | SetupActivationResult
65
+ | SetupProfileResult;
43
66
 
44
67
  export interface SetupOutputOutcome {
45
68
  result: SetupOutputResult;
46
69
  exitCode: 0 | 1 | 2;
47
70
  }
48
71
 
49
- export interface SetupProfileAdvisoryInput {
50
- folder: string;
51
- collection: string;
72
+ function isComposedSetupResult(
73
+ result: SetupOutputResult
74
+ ): result is SetupActivationResult | SetupProfileResult {
75
+ return "setup" in result;
52
76
  }
53
77
 
54
- export interface SetupActivationCommandOptions extends SetupCommandOptions {
78
+ export interface SetupActivationCommandOptions extends SetupProfileIntegrationOptions {
55
79
  connectorIds?: string[];
56
80
  connectorWorkspace?: { cwd?: string; homeDir?: string };
57
81
  connectorDeps?: SetupConnectorCompositionDeps;
58
82
  createActivationStore?: () => SqliteAdapter;
59
- discoverProfileAdvisory?: (
60
- input: SetupProfileAdvisoryInput
61
- ) => Promise<unknown>;
62
83
  }
63
84
 
64
85
  function dedupeConnectorIds(connectorIds: string[]): string[] {
@@ -115,6 +136,27 @@ function invalidConnectorOutcome(connectorIds: string[]): SetupCommandOutcome {
115
136
  };
116
137
  }
117
138
 
139
+ function profileOptionConflictOutcome(): SetupCommandOutcome {
140
+ return {
141
+ result: {
142
+ schemaVersion: SETUP_COMMAND_SCHEMA_VERSION,
143
+ status: "failed",
144
+ lexical: {
145
+ receipt: null,
146
+ error: {
147
+ code: "profile_option_conflict",
148
+ message:
149
+ "--apply-profile cannot be combined with explicit --name or --exclude options.",
150
+ remediation:
151
+ "Remove the explicit setup overrides or run setup without --apply-profile.",
152
+ },
153
+ },
154
+ semantic: null,
155
+ },
156
+ exitCode: 1,
157
+ };
158
+ }
159
+
118
160
  function failedSetupActivationOutcome(
119
161
  setupOutcome: SetupCommandOutcome
120
162
  ): SetupOutputOutcome {
@@ -158,18 +200,37 @@ function defaultConnectorDeps(
158
200
  };
159
201
  }
160
202
 
161
- async function discoverProfileAdvisory(
162
- discover: SetupActivationCommandOptions["discoverProfileAdvisory"],
163
- input: SetupProfileAdvisoryInput
164
- ): Promise<void> {
165
- if (!discover) {
166
- return;
167
- }
168
- try {
169
- await discover(input);
170
- } catch {
171
- // Advisory discovery cannot mutate or fail verified setup.
172
- }
203
+ function withProfileResult(
204
+ outcome: SetupOutputOutcome,
205
+ requested: boolean,
206
+ check: ProjectProfileCommandResult | null,
207
+ applied: ProjectProfileApplyCommandResult | null
208
+ ): SetupOutputOutcome {
209
+ if (!requested || !check) return outcome;
210
+ const setupResult = isComposedSetupResult(outcome.result)
211
+ ? outcome.result.setup
212
+ : outcome.result;
213
+ const connectors = isComposedSetupResult(outcome.result)
214
+ ? outcome.result.connectors
215
+ : [];
216
+ const profileCompleted =
217
+ applied?.status === "applied" || applied?.status === "unchanged";
218
+ return {
219
+ exitCode: outcome.exitCode,
220
+ result: {
221
+ schemaVersion: "1.0",
222
+ status:
223
+ setupResult.status === "failed"
224
+ ? "failed"
225
+ : outcome.result.status === "completed_with_actions" ||
226
+ !profileCompleted
227
+ ? "completed_with_actions"
228
+ : "completed",
229
+ profile: { check, apply: applied },
230
+ setup: setupResult,
231
+ connectors,
232
+ },
233
+ };
173
234
  }
174
235
 
175
236
  function withUnavailableConnectors(
@@ -220,17 +281,87 @@ export async function setupWithActivation(
220
281
  connectorWorkspace: _connectorWorkspace,
221
282
  connectorDeps: _connectorDeps,
222
283
  createActivationStore: _createActivationStore,
223
- discoverProfileAdvisory: _discoverProfileAdvisory,
284
+ applyProfile: _applyProfile,
285
+ inspectProfileAdvisory: _inspectProfileAdvisory,
286
+ applyProfileAdvisory: _applyProfileAdvisory,
287
+ onProfileAdvisory: _onProfileAdvisory,
288
+ onProfileApply: _onProfileApply,
224
289
  ...setupOptions
225
290
  } = options;
226
- const setupOutcome = await setup(setupOptions);
291
+ const profileInspection = await inspectSetupProfile(options);
292
+ const profileCheck = profileInspection.result;
293
+ if (options.applyProfile && profileInspection.failed) {
294
+ const failedInspection = profileInspectionFailureOutcome();
295
+ return definitions.length > 0
296
+ ? failedSetupActivationOutcome(failedInspection)
297
+ : failedInspection;
298
+ }
299
+ if (
300
+ options.applyProfile &&
301
+ profileCheck?.status === "valid" &&
302
+ (options.name !== undefined || (options.exclude?.length ?? 0) > 0)
303
+ ) {
304
+ return withProfileResult(
305
+ definitions.length > 0
306
+ ? failedSetupActivationOutcome(profileOptionConflictOutcome())
307
+ : profileOptionConflictOutcome(),
308
+ true,
309
+ profileCheck,
310
+ null
311
+ );
312
+ }
313
+ const profileApplyOutcome = await applySetupProfile(options, profileCheck);
314
+ const profileApplySuccess = profileApplySucceeded(profileApplyOutcome);
315
+ const returnedProfileApply = profileApplyOutcome?.result ?? null;
316
+ const profileApply =
317
+ profileApplyOutcome?.exitCode === 0 && !profileApplySuccess
318
+ ? null
319
+ : returnedProfileApply;
320
+ if (
321
+ options.applyProfile &&
322
+ profileCheck?.status === "valid" &&
323
+ !profileApplySuccess
324
+ ) {
325
+ const failedApply = profileApplyFailureOutcome(profileApplyOutcome);
326
+ return withProfileResult(
327
+ definitions.length > 0
328
+ ? failedSetupActivationOutcome(failedApply)
329
+ : failedApply,
330
+ true,
331
+ profileCheck,
332
+ profileApply
333
+ );
334
+ }
335
+ const effectiveSetupOptions = await setupOptionsAfterProfileApply(
336
+ setupOptions,
337
+ profileApply
338
+ );
339
+ if (effectiveSetupOptions === null) {
340
+ const failedApply = profileApplyFailureOutcome(profileApplyOutcome);
341
+ return withProfileResult(
342
+ definitions.length > 0
343
+ ? failedSetupActivationOutcome(failedApply)
344
+ : failedApply,
345
+ true,
346
+ profileCheck,
347
+ profileApply
348
+ );
349
+ }
350
+ const setupOutcome = await setup(effectiveSetupOptions);
227
351
  if (
228
352
  setupOutcome.exitCode !== 0 ||
229
353
  setupOutcome.result.status !== "completed"
230
354
  ) {
231
- return definitions.length > 0
232
- ? failedSetupActivationOutcome(setupOutcome)
233
- : setupOutcome;
355
+ const failed =
356
+ definitions.length > 0
357
+ ? failedSetupActivationOutcome(setupOutcome)
358
+ : setupOutcome;
359
+ return withProfileResult(
360
+ failed,
361
+ Boolean(options.applyProfile),
362
+ profileCheck,
363
+ profileApply
364
+ );
234
365
  }
235
366
 
236
367
  const lexicalReceipt = setupOutcome.result.lexical.receipt;
@@ -240,15 +371,21 @@ export async function setupWithActivation(
240
371
  !collection ||
241
372
  !lexicalSuccessIsProven(lexicalReceipt)
242
373
  ) {
243
- return setupOutcome;
374
+ return withProfileResult(
375
+ setupOutcome,
376
+ Boolean(options.applyProfile),
377
+ profileCheck,
378
+ profileApply
379
+ );
244
380
  }
245
381
 
246
- await discoverProfileAdvisory(options.discoverProfileAdvisory, {
247
- folder: lexicalReceipt.input.folder,
248
- collection,
249
- });
250
382
  if (definitions.length === 0) {
251
- return setupOutcome;
383
+ return withProfileResult(
384
+ setupOutcome,
385
+ Boolean(options.applyProfile),
386
+ profileCheck,
387
+ profileApply
388
+ );
252
389
  }
253
390
 
254
391
  let store: SqliteAdapter | null = null;
@@ -258,7 +395,12 @@ export async function setupWithActivation(
258
395
  const indexName = options.indexName ?? "default";
259
396
  const configResult = await loadConfig(configPath);
260
397
  if (!configResult.ok) {
261
- return withUnavailableConnectors(setupOutcome.result, definitions);
398
+ return withProfileResult(
399
+ withUnavailableConnectors(setupOutcome.result, definitions),
400
+ Boolean(options.applyProfile),
401
+ profileCheck,
402
+ profileApply
403
+ );
262
404
  }
263
405
 
264
406
  store =
@@ -270,7 +412,12 @@ export async function setupWithActivation(
270
412
  configResult.value.ftsTokenizer
271
413
  );
272
414
  if (!opened.ok) {
273
- return withUnavailableConnectors(setupOutcome.result, definitions);
415
+ return withProfileResult(
416
+ withUnavailableConnectors(setupOutcome.result, definitions),
417
+ Boolean(options.applyProfile),
418
+ profileCheck,
419
+ profileApply
420
+ );
274
421
  }
275
422
 
276
423
  const composition = await composeSetupConnectors({
@@ -283,28 +430,32 @@ export async function setupWithActivation(
283
430
  options.connectorDeps ??
284
431
  defaultConnectorDeps(options.connectorWorkspace),
285
432
  });
286
- return {
287
- result: {
288
- schemaVersion: SETUP_ACTIVATION_SCHEMA_VERSION,
289
- status: composition.status,
290
- setup: setupOutcome.result,
291
- connectors: composition.connectors,
433
+ return withProfileResult(
434
+ {
435
+ result: {
436
+ schemaVersion: SETUP_ACTIVATION_SCHEMA_VERSION,
437
+ status: composition.status,
438
+ setup: setupOutcome.result,
439
+ connectors: composition.connectors,
440
+ },
441
+ exitCode: 0,
292
442
  },
293
- exitCode: 0,
294
- };
443
+ Boolean(options.applyProfile),
444
+ profileCheck,
445
+ profileApply
446
+ );
295
447
  } catch {
296
- return withUnavailableConnectors(setupOutcome.result, definitions);
448
+ return withProfileResult(
449
+ withUnavailableConnectors(setupOutcome.result, definitions),
450
+ Boolean(options.applyProfile),
451
+ profileCheck,
452
+ profileApply
453
+ );
297
454
  } finally {
298
455
  await closeActivationStore(store);
299
456
  }
300
457
  }
301
458
 
302
- function isSetupActivationResult(
303
- result: SetupOutputResult
304
- ): result is SetupActivationResult {
305
- return "setup" in result;
306
- }
307
-
308
459
  export function formatSetupOutputResult(
309
460
  result: SetupOutputResult,
310
461
  options: { json: boolean }
@@ -312,7 +463,7 @@ export function formatSetupOutputResult(
312
463
  if (options.json) {
313
464
  return JSON.stringify(result, null, 2);
314
465
  }
315
- if (!isSetupActivationResult(result)) {
466
+ if (!isComposedSetupResult(result)) {
316
467
  return formatSetupResult(result, options);
317
468
  }
318
469
  const setupOutput = formatSetupResult(result.setup, options);
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Optional project-profile composition for `gno setup`.
3
+ *
4
+ * Inspection is always read-only and non-fatal. Application requires the
5
+ * explicit `--apply-profile` request and reuses the lock-safe profile command.
6
+ *
7
+ * @module src/cli/commands/setup-profile
8
+ */
9
+
10
+ import type {
11
+ ProjectProfileCommandOutcome,
12
+ ProjectProfileCommandResult,
13
+ } from "./profile";
14
+ import type {
15
+ ProjectProfileApplyCommandOutcome,
16
+ ProjectProfileApplyCommandResult,
17
+ } from "./profile-apply";
18
+ import type { SetupCommandOptions, SetupCommandOutcome } from "./setup";
19
+
20
+ import { loadConfig } from "../../config";
21
+ import { isProjectProfileApplyReceipt } from "../../core/project-profile-apply-validation";
22
+ import { runProjectProfileCommand } from "./profile";
23
+ import { SETUP_COMMAND_SCHEMA_VERSION } from "./setup";
24
+
25
+ export interface SetupProfileAdvisoryInput {
26
+ folder: string;
27
+ configPath?: string;
28
+ offline?: boolean;
29
+ }
30
+
31
+ export interface SetupProfileIntegrationOptions extends SetupCommandOptions {
32
+ applyProfile?: boolean;
33
+ inspectProfileAdvisory?: (
34
+ input: SetupProfileAdvisoryInput
35
+ ) => Promise<ProjectProfileCommandOutcome>;
36
+ applyProfileAdvisory?: (
37
+ input: SetupProfileAdvisoryInput
38
+ ) => Promise<ProjectProfileApplyCommandOutcome>;
39
+ onProfileAdvisory?: (result: ProjectProfileCommandResult) => void;
40
+ onProfileApply?: (result: ProjectProfileApplyCommandResult) => void;
41
+ }
42
+
43
+ export interface SetupProfileInspectionOutcome {
44
+ result: ProjectProfileCommandResult | null;
45
+ failed: boolean;
46
+ }
47
+
48
+ export function profileInspectionFailureOutcome(): SetupCommandOutcome {
49
+ return {
50
+ result: {
51
+ schemaVersion: SETUP_COMMAND_SCHEMA_VERSION,
52
+ status: "failed",
53
+ lexical: {
54
+ receipt: null,
55
+ error: {
56
+ code: "profile_inspection_failed",
57
+ message:
58
+ "The requested project profile could not be inspected safely before setup.",
59
+ remediation:
60
+ "Run `gno profile check` for diagnostics, repair the reported problem, and retry setup.",
61
+ },
62
+ },
63
+ semantic: null,
64
+ },
65
+ exitCode: 2,
66
+ };
67
+ }
68
+
69
+ export function profileApplyFailureOutcome(
70
+ outcome: ProjectProfileApplyCommandOutcome | null
71
+ ): SetupCommandOutcome {
72
+ return {
73
+ result: {
74
+ schemaVersion: SETUP_COMMAND_SCHEMA_VERSION,
75
+ status: "failed",
76
+ lexical: {
77
+ receipt: null,
78
+ error: {
79
+ code: "profile_apply_failed",
80
+ message:
81
+ "The valid project profile could not be applied safely before setup.",
82
+ remediation:
83
+ "Run `gno profile apply` for diagnostics, repair the reported problem, and retry setup.",
84
+ },
85
+ },
86
+ semantic: null,
87
+ },
88
+ exitCode: outcome?.exitCode === 1 ? 1 : 2,
89
+ };
90
+ }
91
+
92
+ export function profileApplySucceeded(
93
+ outcome: ProjectProfileApplyCommandOutcome | null
94
+ ): boolean {
95
+ if (
96
+ outcome?.exitCode !== 0 ||
97
+ (outcome.result.status !== "applied" &&
98
+ outcome.result.status !== "unchanged")
99
+ ) {
100
+ return false;
101
+ }
102
+ const receipt = outcome.result.receipt;
103
+ if (
104
+ !isProjectProfileApplyReceipt(receipt) ||
105
+ receipt.status !== outcome.result.status ||
106
+ outcome.result.applied !== (outcome.result.status === "applied")
107
+ ) {
108
+ return false;
109
+ }
110
+ const collectionResources = receipt.resources.filter(
111
+ (resource) => resource.kind === "collection"
112
+ );
113
+ return (
114
+ collectionResources.length === 1 &&
115
+ Boolean(collectionResources[0]?.id.trim())
116
+ );
117
+ }
118
+
119
+ export async function inspectSetupProfile(
120
+ options: SetupProfileIntegrationOptions
121
+ ): Promise<SetupProfileInspectionOutcome> {
122
+ try {
123
+ const outcome = await (
124
+ options.inspectProfileAdvisory ??
125
+ ((input: SetupProfileAdvisoryInput) =>
126
+ runProjectProfileCommand({
127
+ command: "check",
128
+ cwd: input.folder,
129
+ configPath: input.configPath,
130
+ offline: input.offline,
131
+ }))
132
+ )({
133
+ folder: options.folder,
134
+ configPath: options.configPath,
135
+ offline: options.offline,
136
+ });
137
+ options.onProfileAdvisory?.(outcome.result);
138
+ return { result: outcome.result, failed: false };
139
+ } catch {
140
+ return { result: null, failed: true };
141
+ }
142
+ }
143
+
144
+ export async function applySetupProfile(
145
+ options: SetupProfileIntegrationOptions,
146
+ inspection: ProjectProfileCommandResult | null
147
+ ): Promise<ProjectProfileApplyCommandOutcome | null> {
148
+ if (!options.applyProfile || inspection?.status !== "valid") return null;
149
+ try {
150
+ const outcome = await (
151
+ options.applyProfileAdvisory ??
152
+ ((input: SetupProfileAdvisoryInput) =>
153
+ import("./profile-apply").then(({ runProjectProfileApplyCommand }) =>
154
+ runProjectProfileApplyCommand({
155
+ cwd: input.folder,
156
+ configPath: input.configPath,
157
+ indexName: options.indexName,
158
+ })
159
+ ))
160
+ )({
161
+ folder: options.folder,
162
+ configPath: options.configPath,
163
+ offline: options.offline,
164
+ });
165
+ options.onProfileApply?.(outcome.result);
166
+ return outcome;
167
+ } catch {
168
+ return null;
169
+ }
170
+ }
171
+
172
+ export async function setupOptionsAfterProfileApply(
173
+ options: SetupCommandOptions,
174
+ applied: ProjectProfileApplyCommandResult | null
175
+ ): Promise<SetupCommandOptions | null> {
176
+ if (
177
+ !applied ||
178
+ (applied.status !== "applied" && applied.status !== "unchanged")
179
+ ) {
180
+ return options;
181
+ }
182
+ const collectionName = applied?.receipt?.resources.find(
183
+ (resource) => resource.kind === "collection"
184
+ )?.id;
185
+ if (!collectionName) return null;
186
+ const loaded = await loadConfig(options.configPath);
187
+ const collection = loaded.ok
188
+ ? loaded.value.collections.find((item) => item.name === collectionName)
189
+ : undefined;
190
+ if (!collection) return null;
191
+ return {
192
+ ...options,
193
+ folder: collection.path,
194
+ name: options.name ?? collection.name,
195
+ exclude: options.exclude,
196
+ additiveStoreProjection: true,
197
+ };
198
+ }
199
+
200
+ export function formatSetupProfileAdvisory(
201
+ result: ProjectProfileCommandResult,
202
+ options: { applyRequested?: boolean } = {}
203
+ ): string {
204
+ if (result.status === "not_found" || result.status === "disabled") return "";
205
+ if (result.status === "valid" && result.profile) {
206
+ return [
207
+ `setup: project profile valid (${result.discovery.profile}; fingerprint=${result.profile.fingerprint})`,
208
+ "setup: preview profile changes with `gno profile diff`",
209
+ options.applyRequested
210
+ ? "setup: applying the profile before lexical setup"
211
+ : "setup: rerun with `--apply-profile` to apply it before lexical setup",
212
+ ].join("\n");
213
+ }
214
+ const firstError = result.diagnostics.find(
215
+ (diagnostic) => diagnostic.severity === "error"
216
+ );
217
+ return [
218
+ `setup: project profile ${result.status}; continuing without applying it`,
219
+ firstError
220
+ ? `setup: ${firstError.code}: ${firstError.message}`
221
+ : "setup: run `gno profile check` for repair guidance",
222
+ ].join("\n");
223
+ }
@@ -65,6 +65,8 @@ export interface SetupCommandOptions {
65
65
  yes?: boolean;
66
66
  json?: boolean;
67
67
  quiet?: boolean;
68
+ /** Internal composition flag used after an additive project-profile apply. */
69
+ additiveStoreProjection?: boolean;
68
70
  stdinIsTTY?: boolean;
69
71
  stderrIsTTY?: boolean;
70
72
  progress?: (stage: SetupStageName, receipt: FolderSetupReceipt) => void;
@@ -297,6 +299,7 @@ async function executeSetup(
297
299
  name: options.name,
298
300
  exclude: options.exclude,
299
301
  secretRiskAuthorized: authorized,
302
+ additiveStoreProjection: options.additiveStoreProjection,
300
303
  receiptWriter,
301
304
  });
302
305