@gmickel/gno 2.3.3 → 2.5.1

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 (117) hide show
  1. package/README.md +22 -3
  2. package/assets/skill/SKILL.md +53 -0
  3. package/assets/skill/cli-reference.md +29 -0
  4. package/assets/skill/examples.md +30 -0
  5. package/assets/skill/mcp-reference.md +26 -0
  6. package/assets/spa-production.json.gz +0 -0
  7. package/browser-extension/artifacts/{gno-browser-clipper-v2.3.3.zip → gno-browser-clipper-v2.5.1.zip} +0 -0
  8. package/browser-extension/artifacts/gno-browser-clipper-v2.5.1.zip.sha256 +1 -0
  9. package/browser-extension/dist/manifest.json +1 -1
  10. package/package.json +1 -1
  11. package/spec/cli.md +41 -0
  12. package/spec/compiled-context.md +68 -0
  13. package/spec/db/schema.sql +2 -0
  14. package/spec/evals.md +48 -0
  15. package/spec/mcp.md +37 -0
  16. package/spec/output-schemas/ask.schema.json +12 -0
  17. package/spec/output-schemas/compiled-context-check.schema.json +44 -0
  18. package/spec/output-schemas/compiled-context-file.schema.json +165 -0
  19. package/spec/output-schemas/compiled-context-preview.schema.json +142 -0
  20. package/spec/output-schemas/context-capsule-v1.schema.json +205 -32
  21. package/spec/output-schemas/context-capsule-verification.schema.json +32 -9
  22. package/spec/output-schemas/evidence-fixtures.schema.json +176 -0
  23. package/spec/output-schemas/evidence-report.schema.json +322 -0
  24. package/spec/output-schemas/evidence-run.schema.json +277 -0
  25. package/spec/output-schemas/metadata-predicate.schema.json +460 -0
  26. package/spec/output-schemas/retrieval-trace-filters.schema.json +13 -4
  27. package/spec/output-schemas/search-results.schema.json +12 -0
  28. package/spec/output-schemas/status.schema.json +37 -6
  29. package/src/app/compiled-context-files.ts +361 -0
  30. package/src/app/compiled-context.ts +240 -0
  31. package/src/app/context-runtime-contract.ts +33 -2
  32. package/src/app/context-runtime-input.ts +17 -0
  33. package/src/app/context-runtime-types.ts +2 -0
  34. package/src/app/context-runtime.ts +27 -3
  35. package/src/app/context-surface.ts +7 -0
  36. package/src/app/verified-ask.ts +1 -0
  37. package/src/cli/commands/ask.ts +1 -0
  38. package/src/cli/commands/audit.ts +4 -0
  39. package/src/cli/commands/context-build.ts +1 -0
  40. package/src/cli/commands/context-compiled.ts +136 -0
  41. package/src/cli/commands/search.ts +1 -0
  42. package/src/cli/commands/status.ts +10 -0
  43. package/src/cli/errors.ts +10 -3
  44. package/src/cli/options.ts +19 -0
  45. package/src/cli/program.ts +82 -0
  46. package/src/converters/types.ts +2 -0
  47. package/src/core/compiled-context.ts +254 -0
  48. package/src/core/context-budget.ts +2 -10
  49. package/src/core/context-capsule-retrieval-schema.ts +8 -0
  50. package/src/core/context-capsule-schema.ts +47 -3
  51. package/src/core/context-capsule-validation.ts +14 -2
  52. package/src/core/context-capsule-verification.ts +1 -0
  53. package/src/core/context-capsule.ts +16 -2
  54. package/src/core/context-compiler.ts +3 -0
  55. package/src/core/file-lock.ts +22 -5
  56. package/src/core/folder-setup-planning.ts +2 -1
  57. package/src/core/network-boundary-inventory.ts +8 -0
  58. package/src/core/record-metadata.ts +1 -1
  59. package/src/core/retrieval-replay-candidate.ts +5 -0
  60. package/src/core/retrieval-trace-filter-normalization.ts +4 -0
  61. package/src/core/retrieval-trace-request.ts +1 -0
  62. package/src/core/retrieval-trace-session.ts +5 -1
  63. package/src/core/retrieval-trace.ts +2 -0
  64. package/src/core/setup-receipt.ts +27 -20
  65. package/src/core/typed-metadata.ts +228 -0
  66. package/src/core/validation.ts +9 -2
  67. package/src/core/windows-private-path.ts +96 -0
  68. package/src/index.ts +2 -2
  69. package/src/ingestion/compiled-context.ts +15 -0
  70. package/src/ingestion/record-adapter-canonical.ts +13 -2
  71. package/src/ingestion/record-container.ts +9 -0
  72. package/src/ingestion/sync.ts +51 -9
  73. package/src/ingestion/typed-metadata.ts +55 -0
  74. package/src/ingestion/walker.ts +5 -4
  75. package/src/llm/nodeLlamaCpp/simulator-install.ts +6 -2
  76. package/src/mcp/http-egress.ts +11 -3
  77. package/src/mcp/retrieval-warnings.ts +24 -0
  78. package/src/mcp/tools/ask.ts +23 -2
  79. package/src/mcp/tools/context.ts +47 -2
  80. package/src/mcp/tools/index.ts +58 -1
  81. package/src/mcp/tools/query.ts +16 -1
  82. package/src/mcp/tools/search.ts +12 -1
  83. package/src/mcp/tools/status.ts +4 -0
  84. package/src/mcp/tools/vsearch.ts +12 -1
  85. package/src/pipeline/diagnose.ts +6 -0
  86. package/src/pipeline/filters.ts +65 -0
  87. package/src/pipeline/graph-retrieval.ts +9 -1
  88. package/src/pipeline/hybrid.ts +22 -2
  89. package/src/pipeline/search.ts +11 -0
  90. package/src/pipeline/types.ts +7 -1
  91. package/src/pipeline/vsearch.ts +21 -1
  92. package/src/sdk/client.ts +98 -3
  93. package/src/sdk/index.ts +11 -0
  94. package/src/sdk/types.ts +35 -1
  95. package/src/serve/compiled-context.ts +84 -0
  96. package/src/serve/context-capsule.ts +1 -0
  97. package/src/serve/public/app.tsx +12 -1
  98. package/src/serve/public/components/MetadataFilter.tsx +186 -0
  99. package/src/serve/public/globals.built.css +1 -1
  100. package/src/serve/public/lib/metadata-filter.ts +28 -0
  101. package/src/serve/public/lib/retrieval-filters.ts +3 -0
  102. package/src/serve/public/lib/workspace-tabs.ts +2 -0
  103. package/src/serve/public/pages/Ask.tsx +48 -3
  104. package/src/serve/public/pages/CompiledContext.tsx +364 -0
  105. package/src/serve/public/pages/Dashboard.tsx +7 -0
  106. package/src/serve/public/pages/DocView.tsx +33 -0
  107. package/src/serve/public/pages/Search.tsx +44 -1
  108. package/src/serve/routes/api.ts +50 -0
  109. package/src/serve/server.ts +43 -2
  110. package/src/serve/spa-production-build.ts +6 -5
  111. package/src/store/migrations/030-typed-metadata.ts +13 -0
  112. package/src/store/migrations/index.ts +2 -0
  113. package/src/store/sqlite/adapter.ts +69 -5
  114. package/src/store/sqlite/eligibility.ts +12 -0
  115. package/src/store/sqlite/metadata-predicate.ts +71 -0
  116. package/src/store/types.ts +11 -0
  117. package/browser-extension/artifacts/gno-browser-clipper-v2.3.3.zip.sha256 +0 -1
@@ -63,6 +63,7 @@ export const buildContextCapsule = async (
63
63
  deps.config.collections.map((collection) => collection.name)
64
64
  );
65
65
  const noRerank = normalized.depthPolicy === "fast" || normalized.noRerank;
66
+ let metadataCoverageIncomplete = false;
66
67
  const plan = await compileContextEvidence<ContextCapsuleV1>(
67
68
  {
68
69
  goal: normalized.goal,
@@ -74,6 +75,7 @@ export const buildContextCapsule = async (
74
75
  tagsAll: normalized.tagsAll,
75
76
  tagsAny: normalized.tagsAny,
76
77
  categories: normalized.categories,
78
+ filter: normalized.filter,
77
79
  author: normalized.author ?? undefined,
78
80
  lang: normalized.lang ?? undefined,
79
81
  intent: normalized.intent ?? undefined,
@@ -104,8 +106,14 @@ export const buildContextCapsule = async (
104
106
  store: deps.store,
105
107
  hydration: deps.hydration,
106
108
  config: deps.config,
107
- vectorIndex: deps.vectorIndex ?? null,
108
- embedPort: deps.embedPort ?? null,
109
+ vectorIndex:
110
+ normalized.depthPolicy === "fast"
111
+ ? null
112
+ : (deps.vectorIndex ?? null),
113
+ embedPort:
114
+ normalized.depthPolicy === "fast"
115
+ ? null
116
+ : (deps.embedPort ?? null),
109
117
  expandPort: null,
110
118
  rerankPort: requestNoRerank ? null : (deps.rerankPort ?? null),
111
119
  },
@@ -124,10 +132,26 @@ export const buildContextCapsule = async (
124
132
  result.error.cause
125
133
  );
126
134
  }
135
+ if (
136
+ normalized.filter &&
137
+ result.value.meta.warnings?.some(
138
+ (warning) =>
139
+ warning.code === "METADATA_COVERAGE_UNKNOWN" ||
140
+ warning.code === "METADATA_COVERAGE_INCOMPLETE"
141
+ )
142
+ ) {
143
+ metadataCoverageIncomplete = true;
144
+ }
127
145
  return result.value;
128
146
  },
129
147
  projectCanonical: (draft, snapshots) =>
130
- projectContextCapsule(draft, snapshots, normalized, deps),
148
+ projectContextCapsule(
149
+ draft,
150
+ snapshots,
151
+ normalized,
152
+ deps,
153
+ metadataCoverageIncomplete
154
+ ),
131
155
  }
132
156
  );
133
157
  if (!plan.projection) {
@@ -5,6 +5,10 @@ import { z } from "zod";
5
5
  import type { ContextCapsuleBuildInput } from "./context-runtime-types";
6
6
 
7
7
  import { ContextCapsuleContractError } from "../core/context-capsule";
8
+ import {
9
+ metadataPredicateSchema,
10
+ METADATA_FILTER_DESCRIPTION,
11
+ } from "../core/typed-metadata";
8
12
 
9
13
  const queryModeSchema = z
10
14
  .object({
@@ -28,6 +32,9 @@ export const contextBuildSurfaceSchema = z
28
32
  tagsAll: stringList.optional(),
29
33
  tagsAny: stringList.optional(),
30
34
  categories: stringList.optional(),
35
+ filter: metadataPredicateSchema
36
+ .optional()
37
+ .describe(METADATA_FILTER_DESCRIPTION),
31
38
  author: z.string().optional(),
32
39
  lang: z.string().optional(),
33
40
  intent: z.string().optional(),
@@ -184,6 +184,7 @@ export const buildVerifiedAsk = async (
184
184
  tagsAll: options.tagsAll,
185
185
  tagsAny: options.tagsAny,
186
186
  categories: options.categories,
187
+ filter: options.filter,
187
188
  author: options.author,
188
189
  lang: options.lang,
189
190
  intent: options.intent,
@@ -323,6 +323,7 @@ export async function ask(
323
323
  until: options.until,
324
324
  categories: options.categories,
325
325
  author: options.author,
326
+ filter: options.filter,
326
327
  tagsAll: options.tagsAll,
327
328
  tagsAny: options.tagsAny,
328
329
  exclude: options.exclude,
@@ -21,6 +21,7 @@ import {
21
21
  import { runWorkspaceAudit } from "../../core/audit-workspace";
22
22
  import { normalizeTag, validateTag } from "../../core/tags";
23
23
  import { normalizeCollectionName } from "../../core/validation";
24
+ import { windowsPrivatePath } from "../../core/windows-private-path";
24
25
  import { SqliteAdapter } from "../../store/sqlite/adapter";
25
26
 
26
27
  export interface AuditCommandOptions {
@@ -218,12 +219,15 @@ export const writeAuditReport = async (
218
219
  );
219
220
  const temporaryPath = join(temporaryDirectory, "report");
220
221
  try {
222
+ if (process.platform === "win32")
223
+ await windowsPrivatePath(temporaryDirectory, true);
221
224
  await Bun.write(temporaryPath, `${formatAuditReport(report, options)}\n`, {
222
225
  createPath: false,
223
226
  mode: 0o600,
224
227
  });
225
228
  await chmod(temporaryPath, 0o600);
226
229
  await rename(temporaryPath, path);
230
+ if (process.platform === "win32") await windowsPrivatePath(path);
227
231
  } finally {
228
232
  await unlink(temporaryPath).catch(() => undefined);
229
233
  await rmdir(temporaryDirectory).catch(() => undefined);
@@ -124,6 +124,7 @@ export const contextBuild = async (
124
124
  until: options.until,
125
125
  categories: options.categories,
126
126
  author: options.author,
127
+ filter: options.filter,
127
128
  graph: options.graph,
128
129
  candidateLimit: options.candidateLimit,
129
130
  queryModes: options.queryModes,
@@ -0,0 +1,136 @@
1
+ import { previewCompiledContext } from "../../app/compiled-context";
2
+ import {
3
+ compileContextFile,
4
+ checkContextFile,
5
+ refreshContextFile,
6
+ } from "../../app/compiled-context-files";
7
+ /** Explicit local compiled context operations. */
8
+ import { createGnoClient } from "../../sdk/client";
9
+ import { CliError } from "../errors";
10
+ import { contextCliError } from "./context-build";
11
+ import { initStore } from "./shared";
12
+
13
+ export interface CompiledCommandOptions {
14
+ configPath?: string;
15
+ indexName?: string;
16
+ capsulePath?: string;
17
+ capsuleOutputPath?: string;
18
+ outputPath?: string;
19
+ budgetTokens?: number;
20
+ budgetBytes?: number;
21
+ json?: boolean;
22
+ }
23
+
24
+ export async function contextCompiled(
25
+ operation: "preview" | "compile" | "check" | "refresh",
26
+ options: CompiledCommandOptions
27
+ ): Promise<{
28
+ output: string;
29
+ status?: "current" | "stale" | "conflict" | "unverifiable";
30
+ }> {
31
+ const initialized = await initStore({
32
+ configPath: options.configPath,
33
+ indexName: options.indexName,
34
+ syncConfig: false,
35
+ allowEmptyCollections: true,
36
+ });
37
+ if (!initialized.ok) throw new CliError("RUNTIME", initialized.error);
38
+ const deps = {
39
+ store: initialized.store,
40
+ config: initialized.config,
41
+ indexName: options.indexName,
42
+ };
43
+ try {
44
+ if (operation === "check") {
45
+ if (!options.outputPath)
46
+ throw new CliError("VALIDATION", "Artifact path required");
47
+ const result = await checkContextFile(
48
+ { outputPath: options.outputPath, capsulePath: options.capsulePath },
49
+ deps
50
+ );
51
+ return {
52
+ output: options.json
53
+ ? JSON.stringify(result)
54
+ : `${result.status}: ${result.reasons.join("; ") || "verified current"}`,
55
+ status: result.status,
56
+ };
57
+ }
58
+ if (operation === "refresh") {
59
+ if (!options.outputPath || !options.capsuleOutputPath)
60
+ throw new CliError(
61
+ "VALIDATION",
62
+ "Artifact and Capsule output paths required"
63
+ );
64
+ const result = await refreshContextFile(
65
+ {
66
+ outputPath: options.outputPath,
67
+ capsuleOutputPath: options.capsuleOutputPath,
68
+ },
69
+ deps,
70
+ async (request) => {
71
+ const client = await createGnoClient({
72
+ configPath: options.configPath,
73
+ indexName: options.indexName,
74
+ });
75
+ try {
76
+ return await client.context(request);
77
+ } finally {
78
+ await client.close();
79
+ }
80
+ }
81
+ );
82
+ return {
83
+ output: options.json
84
+ ? JSON.stringify(result)
85
+ : `${result.status}: ${result.outputPath}`,
86
+ };
87
+ }
88
+ if (!options.capsulePath || !options.budgetTokens)
89
+ throw new CliError("VALIDATION", "Capsule path and budget required");
90
+ if (operation === "compile") {
91
+ if (!options.outputPath)
92
+ throw new CliError("VALIDATION", "Output path required");
93
+ const result = await compileContextFile(
94
+ {
95
+ capsulePath: options.capsulePath,
96
+ outputPath: options.outputPath,
97
+ budgetTokens: options.budgetTokens,
98
+ budgetBytes: options.budgetBytes,
99
+ },
100
+ deps
101
+ );
102
+ return {
103
+ output: options.json
104
+ ? JSON.stringify(result)
105
+ : `${result.status}: ${result.outputPath}`,
106
+ };
107
+ }
108
+ const source = Bun.file(options.capsulePath);
109
+ if (source.size > 4 * 1024 * 1024)
110
+ throw new CliError("VALIDATION", "Capsule exceeds byte limit");
111
+ const capsule: unknown = await source.json();
112
+ const result = await previewCompiledContext(
113
+ {
114
+ capsule,
115
+ budgetTokens: options.budgetTokens,
116
+ budgetBytes: options.budgetBytes,
117
+ },
118
+ deps
119
+ );
120
+ return { output: options.json ? JSON.stringify(result) : result.markdown };
121
+ } catch (error) {
122
+ if (error instanceof CliError) throw error;
123
+ if (
124
+ error instanceof SyntaxError ||
125
+ (error instanceof Error && error.name === "ZodError")
126
+ ) {
127
+ throw new CliError(
128
+ "VALIDATION",
129
+ "Invalid compiled context input; provide a valid Capsule and budget"
130
+ );
131
+ }
132
+ throw contextCliError(error);
133
+ } finally {
134
+ await initialized.store.close();
135
+ }
136
+ }
@@ -113,6 +113,7 @@ export async function search(
113
113
  until: options.until,
114
114
  categories: options.categories,
115
115
  author: options.author,
116
+ filter: options.filter,
116
117
  intent: options.intent,
117
118
  exclude: options.exclude,
118
119
  },
@@ -87,6 +87,11 @@ function formatTerminal(
87
87
  ): string {
88
88
  const lines: string[] = [];
89
89
 
90
+ if (indexStatus.typedMetadata) {
91
+ lines.push(
92
+ `Typed metadata: ${indexStatus.typedMetadata.pending} pending sync, ${indexStatus.typedMetadata.invalid} invalid`
93
+ );
94
+ }
90
95
  lines.push(`Index: ${indexStatus.indexName}`);
91
96
  lines.push(`Config: ${indexStatus.configPath}`);
92
97
  lines.push(`Database: ${indexStatus.dbPath}`);
@@ -200,6 +205,10 @@ function formatMarkdown(
200
205
  lines.push(`- **Documents**: ${indexStatus.activeDocuments}`);
201
206
  lines.push(`- **Chunks**: ${indexStatus.totalChunks}`);
202
207
  lines.push(`- **Embedding backlog**: ${indexStatus.embeddingBacklog}`);
208
+ if (indexStatus.typedMetadata)
209
+ lines.push(
210
+ `- **Typed metadata**: ${indexStatus.typedMetadata.pending} pending sync, ${indexStatus.typedMetadata.invalid} invalid`
211
+ );
203
212
  lines.push(`- **Recent errors**: ${indexStatus.recentErrors}`);
204
213
 
205
214
  const chunking = formatChunkingStatus(indexStatus.chunking);
@@ -347,6 +356,7 @@ export function formatStatus(
347
356
  totalDocuments: s.activeDocuments,
348
357
  totalChunks: s.totalChunks,
349
358
  embeddingBacklog: s.embeddingBacklog,
359
+ typedMetadata: s.typedMetadata,
350
360
  chunking: s.chunking,
351
361
  lastUpdated: s.lastUpdatedAt,
352
362
  healthy: isStatusHealthy(s, result.activation),
package/src/cli/errors.ts CHANGED
@@ -15,7 +15,9 @@ export type CliErrorCode =
15
15
  | "NOT_RUNNING"
16
16
  | "BUSY"
17
17
  | "AUDIT_FINDINGS"
18
- | "AUDIT_PARTIAL";
18
+ | "AUDIT_PARTIAL"
19
+ | "CONTEXT_STALE"
20
+ | "CONTEXT_CONFLICT";
19
21
 
20
22
  export interface CliErrorOptions {
21
23
  details?: Record<string, unknown>;
@@ -72,10 +74,15 @@ export function exitCodeFor(err: CliError): 1 | 2 | 3 | 4 | 5 {
72
74
  if (err.code === "VALIDATION") {
73
75
  return 1;
74
76
  }
75
- if (err.code === "NOT_RUNNING") {
77
+ if (err.code === "NOT_RUNNING" || err.code === "CONTEXT_STALE") {
76
78
  return 3;
77
79
  }
78
- if (err.code === "BUSY" || err.code === "AUDIT_FINDINGS") return 4;
80
+ if (
81
+ err.code === "BUSY" ||
82
+ err.code === "AUDIT_FINDINGS" ||
83
+ err.code === "CONTEXT_CONFLICT"
84
+ )
85
+ return 4;
79
86
  if (err.code === "AUDIT_PARTIAL") return 5;
80
87
  return 2;
81
88
  }
@@ -9,6 +9,10 @@ import {
9
9
  normalizeProjectAffinityValues,
10
10
  ProjectAffinityInputError,
11
11
  } from "../core/project-affinity-surface";
12
+ import {
13
+ normalizeMetadataPredicate,
14
+ type MetadataPredicate,
15
+ } from "../core/typed-metadata";
12
16
  import { CliError } from "./errors";
13
17
 
14
18
  // ─────────────────────────────────────────────────────────────────────────────
@@ -259,3 +263,18 @@ export function parseOptionalFloat(
259
263
  }
260
264
  return num;
261
265
  }
266
+
267
+ export const parseCliMetadataFilter = (
268
+ value: unknown
269
+ ): MetadataPredicate | undefined => {
270
+ if (value === undefined) return undefined;
271
+ try {
272
+ if (typeof value !== "string") throw new Error("Expected JSON text");
273
+ return normalizeMetadataPredicate(JSON.parse(value));
274
+ } catch (error) {
275
+ throw new CliError(
276
+ "VALIDATION",
277
+ `filter: ${error instanceof Error ? error.message : "Invalid predicate"}`
278
+ );
279
+ }
280
+ };
@@ -49,6 +49,7 @@ import {
49
49
  collectRepeatableValue,
50
50
  getDefaultLimit,
51
51
  parseCliProjectAffinityOptions,
52
+ parseCliMetadataFilter,
52
53
  parseOptionalFloat,
53
54
  parsePositiveInt,
54
55
  } from "./options";
@@ -652,6 +653,7 @@ function wireSearchCommands(program: Command): void {
652
653
  "modified-at upper bound (ISO date/time or token)"
653
654
  )
654
655
  .option("--category <values>", "require category match (comma-separated)")
656
+ .option("--filter <json>", "typed custom metadata predicate (JSON)")
655
657
  .option("--author <text>", "filter by author (case-insensitive contains)")
656
658
  .option("--intent <text>", "disambiguating context for ambiguous queries")
657
659
  .option(
@@ -736,6 +738,7 @@ function wireSearchCommands(program: Command): void {
736
738
  until: cmdOpts.until as string | undefined,
737
739
  categories,
738
740
  author: cmdOpts.author as string | undefined,
741
+ filter: parseCliMetadataFilter(cmdOpts.filter),
739
742
  intent: cmdOpts.intent as string | undefined,
740
743
  exclude,
741
744
  tagsAll,
@@ -790,6 +793,7 @@ function wireSearchCommands(program: Command): void {
790
793
  "modified-at upper bound (ISO date/time or token)"
791
794
  )
792
795
  .option("--category <values>", "require category match (comma-separated)")
796
+ .option("--filter <json>", "typed custom metadata predicate (JSON)")
793
797
  .option("--author <text>", "filter by author (case-insensitive contains)")
794
798
  .option("--intent <text>", "disambiguating context for ambiguous queries")
795
799
  .option(
@@ -864,6 +868,7 @@ function wireSearchCommands(program: Command): void {
864
868
  until: cmdOpts.until as string | undefined,
865
869
  categories,
866
870
  author: cmdOpts.author as string | undefined,
871
+ filter: parseCliMetadataFilter(cmdOpts.filter),
867
872
  intent: cmdOpts.intent as string | undefined,
868
873
  exclude,
869
874
  tagsAll,
@@ -916,6 +921,7 @@ function wireSearchCommands(program: Command): void {
916
921
  "modified-at upper bound (ISO date/time or token)"
917
922
  )
918
923
  .option("--category <values>", "require category match (comma-separated)")
924
+ .option("--filter <json>", "typed custom metadata predicate (JSON)")
919
925
  .option("--author <text>", "filter by author (case-insensitive contains)")
920
926
  .option("--intent <text>", "disambiguating context for ambiguous queries")
921
927
  .option(
@@ -1080,6 +1086,7 @@ function wireSearchCommands(program: Command): void {
1080
1086
  until: cmdOpts.until as string | undefined,
1081
1087
  categories,
1082
1088
  author: cmdOpts.author as string | undefined,
1089
+ filter: parseCliMetadataFilter(cmdOpts.filter),
1083
1090
  intent: cmdOpts.intent as string | undefined,
1084
1091
  exclude,
1085
1092
  tagsAll,
@@ -1116,6 +1123,7 @@ function wireSearchCommands(program: Command): void {
1116
1123
  until: cmdOpts.until as string | undefined,
1117
1124
  categories,
1118
1125
  author: cmdOpts.author as string | undefined,
1126
+ filter: parseCliMetadataFilter(cmdOpts.filter),
1119
1127
  intent: cmdOpts.intent as string | undefined,
1120
1128
  exclude,
1121
1129
  tagsAll,
@@ -1219,6 +1227,7 @@ function wireSearchCommands(program: Command): void {
1219
1227
  "modified-at upper bound (ISO date/time or token)"
1220
1228
  )
1221
1229
  .option("--category <values>", "require category match (comma-separated)")
1230
+ .option("--filter <json>", "typed custom metadata predicate (JSON)")
1222
1231
  .option("--author <text>", "filter by author (case-insensitive contains)")
1223
1232
  .option("--intent <text>", "disambiguating context for ambiguous queries")
1224
1233
  .option(
@@ -1350,6 +1359,7 @@ function wireSearchCommands(program: Command): void {
1350
1359
  until: cmdOpts.until as string | undefined,
1351
1360
  categories,
1352
1361
  author: cmdOpts.author as string | undefined,
1362
+ filter: parseCliMetadataFilter(cmdOpts.filter),
1353
1363
  intent: cmdOpts.intent as string | undefined,
1354
1364
  exclude,
1355
1365
  minScore,
@@ -2695,6 +2705,7 @@ function wireManagementCommands(program: Command): void {
2695
2705
  .option("--tags-all <tags>", "require ALL tags (comma-separated)")
2696
2706
  .option("--tags-any <tags>", "require ANY tag (comma-separated)")
2697
2707
  .option("--category <values>", "require category match (comma-separated)")
2708
+ .option("--filter <json>", "typed custom metadata predicate (JSON)")
2698
2709
  .option("--author <text>", "filter by author")
2699
2710
  .option("--lang <code>", "language hint (BCP-47)")
2700
2711
  .option("--since <date>", "modified-at lower bound")
@@ -2765,6 +2776,7 @@ function wireManagementCommands(program: Command): void {
2765
2776
  tagsAny: parseCsvValues(cmdOpts.tagsAny),
2766
2777
  categories: parseCsvValues(cmdOpts.category),
2767
2778
  author: cmdOpts.author as string | undefined,
2779
+ filter: parseCliMetadataFilter(cmdOpts.filter),
2768
2780
  lang: cmdOpts.lang as string | undefined,
2769
2781
  since: cmdOpts.since as string | undefined,
2770
2782
  until: cmdOpts.until as string | undefined,
@@ -2791,6 +2803,76 @@ function wireManagementCommands(program: Command): void {
2791
2803
  await writeOutput(output, format === "json" ? "json" : "md");
2792
2804
  });
2793
2805
 
2806
+ const compiledCmd = contextCmd
2807
+ .command("compiled")
2808
+ .description("Compile, check and refresh verified project context");
2809
+ for (const operation of ["preview", "compile", "check", "refresh"] as const) {
2810
+ const command = compiledCmd
2811
+ .command(
2812
+ operation === "check" || operation === "refresh"
2813
+ ? `${operation} <file>`
2814
+ : operation
2815
+ )
2816
+ .description(`${operation} a separate managed context artifact`)
2817
+ .option("--json", "JSON output");
2818
+ if (operation === "preview" || operation === "compile") {
2819
+ command
2820
+ .requiredOption("--capsule <file>", "explicit Capsule JSON file")
2821
+ .requiredOption("--budget <tokens>", "whole-output token budget")
2822
+ .option("--bytes <bytes>", "whole-output byte budget");
2823
+ }
2824
+ if (operation === "compile")
2825
+ command.requiredOption(
2826
+ "--output <file>",
2827
+ "new *.gno-context.md artifact"
2828
+ );
2829
+ if (operation === "check")
2830
+ command.option("--capsule <file>", "relocated Capsule JSON file");
2831
+ if (operation === "refresh")
2832
+ command.requiredOption(
2833
+ "--capsule-output <file>",
2834
+ "explicit refreshed Capsule destination"
2835
+ );
2836
+ command.action(async (...args: unknown[]) => {
2837
+ const file =
2838
+ operation === "check" || operation === "refresh"
2839
+ ? (args[0] as string)
2840
+ : undefined;
2841
+ const opts = args[file === undefined ? 0 : 1] as Record<string, unknown>;
2842
+ const globals = getGlobals();
2843
+ const { contextCompiled } = await import("./commands/context-compiled");
2844
+ const result = await contextCompiled(operation, {
2845
+ configPath: globals.config,
2846
+ indexName: globals.index,
2847
+ capsulePath: opts.capsule as string | undefined,
2848
+ capsuleOutputPath: opts.capsuleOutput as string | undefined,
2849
+ outputPath: file ?? (opts.output as string | undefined),
2850
+ budgetTokens:
2851
+ opts.budget === undefined
2852
+ ? undefined
2853
+ : parseContextInteger("budget", opts.budget),
2854
+ budgetBytes:
2855
+ opts.bytes === undefined
2856
+ ? undefined
2857
+ : parseContextInteger("bytes", opts.bytes),
2858
+ json: Boolean(opts.json || globals.json),
2859
+ });
2860
+ await writeOutput(
2861
+ result.output,
2862
+ opts.json || globals.json ? "json" : "terminal"
2863
+ );
2864
+ if (result.status && result.status !== "current") {
2865
+ const code =
2866
+ result.status === "stale"
2867
+ ? "CONTEXT_STALE"
2868
+ : result.status === "conflict"
2869
+ ? "CONTEXT_CONFLICT"
2870
+ : "RUNTIME";
2871
+ throw new CliError(code, result.status, { silent: true });
2872
+ }
2873
+ });
2874
+ }
2875
+
2794
2876
  contextCmd
2795
2877
  .command("verify <file>")
2796
2878
  .description("Verify a saved Context Capsule without rebuilding it")
@@ -1,3 +1,4 @@
1
+ import type { TypedMetadata } from "../core/typed-metadata";
1
2
  /**
2
3
  * Converter subsystem types.
3
4
  * PRD §8.2 - Converter interfaces
@@ -157,6 +158,7 @@ export const RECORD_METADATA_LIMITS = {
157
158
 
158
159
  /** Metadata shared by export adapters and later search/get projections. */
159
160
  export interface RecordMetadata {
161
+ custom?: TypedMetadata;
160
162
  author?: string;
161
163
  participants?: string[];
162
164
  categories?: string[];