@gmickel/gno 1.21.0 → 1.23.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 (47) hide show
  1. package/README.md +26 -2
  2. package/assets/skill/SKILL.md +15 -0
  3. package/package.json +2 -1
  4. package/spec/cli.md +80 -20
  5. package/spec/evals-agentic.md +83 -0
  6. package/spec/evals.md +6 -0
  7. package/spec/mcp.md +18 -0
  8. package/spec/output-schemas/publish-artifact.schema.json +284 -0
  9. package/spec/output-schemas/query-diagnose-v1.schema.json +123 -0
  10. package/spec/output-schemas/query-diagnose.schema.json +89 -2
  11. package/src/app/context-runtime-types.ts +3 -0
  12. package/src/app/context-runtime.ts +1 -0
  13. package/src/app/context-surface.ts +4 -2
  14. package/src/cli/commands/ask.ts +31 -20
  15. package/src/cli/commands/context-build.ts +17 -7
  16. package/src/cli/commands/query.ts +58 -37
  17. package/src/cli/commands/search.ts +29 -19
  18. package/src/cli/commands/vsearch.ts +31 -22
  19. package/src/cli/options.ts +39 -0
  20. package/src/cli/program.ts +48 -0
  21. package/src/config/defaults.ts +10 -1
  22. package/src/config/types.ts +71 -0
  23. package/src/core/project-affinity-surface.ts +114 -0
  24. package/src/core/project-affinity.ts +330 -0
  25. package/src/core/validation.ts +20 -1
  26. package/src/mcp/tools/ask.ts +10 -1
  27. package/src/mcp/tools/context.ts +18 -0
  28. package/src/mcp/tools/index.ts +13 -2
  29. package/src/mcp/tools/query.ts +12 -0
  30. package/src/mcp/tools/search.ts +7 -0
  31. package/src/mcp/tools/vsearch.ts +7 -0
  32. package/src/pipeline/diagnose.ts +48 -3
  33. package/src/pipeline/explain.ts +54 -13
  34. package/src/pipeline/hybrid.ts +100 -59
  35. package/src/pipeline/project-affinity.ts +162 -0
  36. package/src/pipeline/search.ts +76 -10
  37. package/src/pipeline/types.ts +9 -0
  38. package/src/pipeline/vsearch.ts +117 -91
  39. package/src/publish/artifact-validation.ts +259 -0
  40. package/src/publish/artifact.ts +234 -118
  41. package/src/publish/export-service.ts +5 -9
  42. package/src/publish/metadata.ts +195 -0
  43. package/src/sdk/client.ts +80 -20
  44. package/src/sdk/index.ts +2 -0
  45. package/src/sdk/types.ts +20 -7
  46. package/src/serve/context-capsule.ts +18 -1
  47. package/src/serve/routes/api.ts +69 -0
@@ -1,6 +1,7 @@
1
1
  /** Context Capsule build command over the shared application runtime. */
2
2
 
3
3
  import type { ContextCapsuleBuildInput } from "../../app/context-runtime";
4
+ import type { CliProjectAffinityRequest } from "../../core/project-affinity-surface";
4
5
  import type { RetrievalTraceSession } from "../../core/retrieval-trace-session";
5
6
  import type { EmbeddingPort, RerankPort } from "../../llm/types";
6
7
  import type { VectorIndexPort } from "../../store/vector";
@@ -11,6 +12,7 @@ import {
11
12
  canonicalBuiltContextCapsuleJson,
12
13
  validateContextCapsuleBuildInput,
13
14
  } from "../../app/context-runtime";
15
+ import { resolveCliProjectAffinity } from "../../core/project-affinity-surface";
14
16
  import {
15
17
  finishRetrievalTraceAfterError,
16
18
  startRetrievalTraceRequest,
@@ -27,10 +29,8 @@ import {
27
29
  } from "../progress";
28
30
  import { initStore } from "./shared";
29
31
 
30
- export interface ContextBuildCommandOptions extends Omit<
31
- ContextCapsuleBuildInput,
32
- "goal"
33
- > {
32
+ export interface ContextBuildCommandOptions
33
+ extends Omit<ContextCapsuleBuildInput, "goal">, CliProjectAffinityRequest {
34
34
  configPath?: string;
35
35
  format: "json" | "md";
36
36
  }
@@ -64,8 +64,12 @@ export const contextBuild = async (
64
64
  goal: string,
65
65
  options: ContextBuildCommandOptions
66
66
  ): Promise<string> => {
67
+ const { projectAffinityDisabled, projectRoots, ...contextOptions } = options;
67
68
  try {
68
- validateContextCapsuleBuildInput({ goal, ...options }, options.indexName);
69
+ validateContextCapsuleBuildInput(
70
+ { goal, ...contextOptions },
71
+ options.indexName
72
+ );
69
73
  } catch (error) {
70
74
  throw contextCliError(error);
71
75
  }
@@ -84,8 +88,13 @@ export const contextBuild = async (
84
88
  let vectorIndex: VectorIndexPort | null = null;
85
89
  let traceSession: RetrievalTraceSession | undefined;
86
90
  try {
91
+ const projectAffinity = await resolveCliProjectAffinity(config, {
92
+ cwd: process.cwd(),
93
+ disabled: projectAffinityDisabled,
94
+ projectRoots,
95
+ });
87
96
  validateContextCapsuleBuildInput(
88
- { goal, ...options },
97
+ { goal, ...contextOptions },
89
98
  options.indexName,
90
99
  config.collections.map((collection) => collection.name)
91
100
  );
@@ -163,7 +172,7 @@ export const contextBuild = async (
163
172
  if (showProgress && progress) process.stderr.write("\n");
164
173
  }
165
174
  const capsule = await buildContextCapsule(
166
- { goal, ...options },
175
+ { goal, ...contextOptions },
167
176
  {
168
177
  store,
169
178
  config,
@@ -171,6 +180,7 @@ export const contextBuild = async (
171
180
  vectorIndex,
172
181
  embedPort,
173
182
  rerankPort,
183
+ projectAffinity,
174
184
  traceSession,
175
185
  }
176
186
  );
@@ -5,6 +5,7 @@
5
5
  * @module src/cli/commands/query
6
6
  */
7
7
 
8
+ import type { CliProjectAffinityRequest } from "../../core/project-affinity-surface";
8
9
  import type { RetrievalTraceSurfaceMetadata } from "../../core/retrieval-trace-session";
9
10
  import type { RetrievalTraceSession } from "../../core/retrieval-trace-session";
10
11
  import type {
@@ -18,6 +19,7 @@ import {
18
19
  fingerprintContentTypeRules,
19
20
  normalizeContentTypes,
20
21
  } from "../../config";
22
+ import { resolveCliProjectAffinity } from "../../core/project-affinity-surface";
21
23
  import {
22
24
  finishRetrievalTraceAfterError,
23
25
  retrievalTraceFilters,
@@ -46,30 +48,31 @@ import { decorateSearchResultsForIndex, initStore } from "./shared";
46
48
  // Types
47
49
  // ─────────────────────────────────────────────────────────────────────────────
48
50
 
49
- export type QueryCommandOptions = HybridSearchOptions & {
50
- /** Override config path */
51
- configPath?: string;
52
- /** Index name */
53
- indexName?: string;
54
- /** Override embedding model */
55
- embedModel?: string;
56
- /** Override expansion model */
57
- expandModel?: string;
58
- /** Deprecated alias for expansion model */
59
- genModel?: string;
60
- /** Override rerank model */
61
- rerankModel?: string;
62
- /** Output as JSON */
63
- json?: boolean;
64
- /** Output as Markdown */
65
- md?: boolean;
66
- /** Output as CSV */
67
- csv?: boolean;
68
- /** Output as XML */
69
- xml?: boolean;
70
- /** Output files only */
71
- files?: boolean;
72
- };
51
+ export type QueryCommandOptions = Omit<HybridSearchOptions, "projectAffinity"> &
52
+ CliProjectAffinityRequest & {
53
+ /** Override config path */
54
+ configPath?: string;
55
+ /** Index name */
56
+ indexName?: string;
57
+ /** Override embedding model */
58
+ embedModel?: string;
59
+ /** Override expansion model */
60
+ expandModel?: string;
61
+ /** Deprecated alias for expansion model */
62
+ genModel?: string;
63
+ /** Override rerank model */
64
+ rerankModel?: string;
65
+ /** Output as JSON */
66
+ json?: boolean;
67
+ /** Output as Markdown */
68
+ md?: boolean;
69
+ /** Output as CSV */
70
+ csv?: boolean;
71
+ /** Output as XML */
72
+ xml?: boolean;
73
+ /** Output files only */
74
+ files?: boolean;
75
+ };
73
76
 
74
77
  export interface QueryFormatOptions {
75
78
  format: "terminal" | "json" | "files" | "csv" | "md" | "xml";
@@ -86,16 +89,20 @@ export type QueryResult =
86
89
  }
87
90
  | { success: false; error: string };
88
91
 
89
- export type QueryDiagnoseCommandOptions = HybridSearchOptions & {
90
- target: string;
91
- configPath?: string;
92
- indexName?: string;
93
- embedModel?: string;
94
- expandModel?: string;
95
- genModel?: string;
96
- rerankModel?: string;
97
- json?: boolean;
98
- };
92
+ export type QueryDiagnoseCommandOptions = Omit<
93
+ HybridSearchOptions,
94
+ "projectAffinity"
95
+ > &
96
+ CliProjectAffinityRequest & {
97
+ target: string;
98
+ configPath?: string;
99
+ indexName?: string;
100
+ embedModel?: string;
101
+ expandModel?: string;
102
+ genModel?: string;
103
+ rerankModel?: string;
104
+ json?: boolean;
105
+ };
99
106
 
100
107
  export interface QueryDiagnoseFormatOptions {
101
108
  format: "terminal" | "json";
@@ -140,6 +147,12 @@ export async function query(
140
147
  let traceSession: RetrievalTraceSession | undefined;
141
148
 
142
149
  try {
150
+ const { projectAffinityDisabled, projectRoots, ...queryOptions } = options;
151
+ const projectAffinity = await resolveCliProjectAffinity(config, {
152
+ cwd: process.cwd(),
153
+ disabled: projectAffinityDisabled,
154
+ projectRoots,
155
+ });
143
156
  const embedUri = resolveModelUri(
144
157
  config,
145
158
  "embed",
@@ -167,7 +180,7 @@ export async function query(
167
180
  store,
168
181
  config,
169
182
  query: queryText,
170
- filters: retrievalTraceFilters({ ...options, limit }),
183
+ filters: retrievalTraceFilters({ ...queryOptions, limit }),
171
184
  pipeline: "hybrid",
172
185
  indexName: options.indexName,
173
186
  modelUris: [embedUri, expandUri, rerankUri].filter(
@@ -261,8 +274,9 @@ export async function query(
261
274
  rerankPort,
262
275
  };
263
276
  const result = await searchHybrid(deps, queryText, {
264
- ...options,
277
+ ...queryOptions,
265
278
  limit,
279
+ projectAffinity,
266
280
  traceSession,
267
281
  });
268
282
 
@@ -318,6 +332,12 @@ export async function queryDiagnose(
318
332
  let rerankPort: RerankPort | null = null;
319
333
 
320
334
  try {
335
+ const { projectAffinityDisabled, projectRoots, ...queryOptions } = options;
336
+ const projectAffinity = await resolveCliProjectAffinity(config, {
337
+ cwd: process.cwd(),
338
+ disabled: projectAffinityDisabled,
339
+ projectRoots,
340
+ });
321
341
  const globals = getGlobals();
322
342
  const policy = resolveDownloadPolicy(process.env, {
323
343
  offline: globals.offline,
@@ -415,7 +435,8 @@ export async function queryDiagnose(
415
435
  rerankPort,
416
436
  };
417
437
  const result = await diagnoseQueryTarget(deps, queryText, {
418
- ...options,
438
+ ...queryOptions,
439
+ projectAffinity,
419
440
  contentTypeRules,
420
441
  contentTypeRulesFingerprint:
421
442
  fingerprintContentTypeRules(contentTypeRules),
@@ -5,12 +5,14 @@
5
5
  * @module src/cli/commands/search
6
6
  */
7
7
 
8
+ import type { CliProjectAffinityRequest } from "../../core/project-affinity-surface";
8
9
  import type {
9
10
  RetrievalTraceSession,
10
11
  RetrievalTraceSurfaceMetadata,
11
12
  } from "../../core/retrieval-trace-session";
12
13
  import type { SearchOptions, SearchResults } from "../../pipeline/types";
13
14
 
15
+ import { resolveCliProjectAffinity } from "../../core/project-affinity-surface";
14
16
  import {
15
17
  finishRetrievalTraceAfterError,
16
18
  startRetrievalTraceRequest,
@@ -26,24 +28,25 @@ import { decorateSearchResultsForIndex, initStore } from "./shared";
26
28
  // Types
27
29
  // ─────────────────────────────────────────────────────────────────────────────
28
30
 
29
- export type SearchCommandOptions = SearchOptions & {
30
- /** Override config path */
31
- configPath?: string;
32
- /** Index name */
33
- indexName?: string;
34
- /** Output as JSON */
35
- json?: boolean;
36
- /** Output as Markdown */
37
- md?: boolean;
38
- /** Output as CSV */
39
- csv?: boolean;
40
- /** Output as XML */
41
- xml?: boolean;
42
- /** Output files only */
43
- files?: boolean;
44
- /** Terminal hyperlink policy */
45
- terminalLinks?: FormatOptions["terminalLinks"];
46
- };
31
+ export type SearchCommandOptions = Omit<SearchOptions, "projectAffinity"> &
32
+ CliProjectAffinityRequest & {
33
+ /** Override config path */
34
+ configPath?: string;
35
+ /** Index name */
36
+ indexName?: string;
37
+ /** Output as JSON */
38
+ json?: boolean;
39
+ /** Output as Markdown */
40
+ md?: boolean;
41
+ /** Output as CSV */
42
+ csv?: boolean;
43
+ /** Output as XML */
44
+ xml?: boolean;
45
+ /** Output files only */
46
+ files?: boolean;
47
+ /** Terminal hyperlink policy */
48
+ terminalLinks?: FormatOptions["terminalLinks"];
49
+ };
47
50
 
48
51
  export type SearchResult =
49
52
  | {
@@ -84,6 +87,12 @@ export async function search(
84
87
  let traceSession: RetrievalTraceSession | undefined;
85
88
 
86
89
  try {
90
+ const { projectAffinityDisabled, projectRoots, ...searchOptions } = options;
91
+ const projectAffinity = await resolveCliProjectAffinity(config, {
92
+ cwd: process.cwd(),
93
+ disabled: projectAffinityDisabled,
94
+ projectRoots,
95
+ });
87
96
  const started = await startRetrievalTraceRequest({
88
97
  store,
89
98
  config,
@@ -109,8 +118,9 @@ export async function search(
109
118
  if (!started.ok) return { success: false, error: started.error.message };
110
119
  traceSession = started.value ?? undefined;
111
120
  const result = await searchBm25(store, query, {
112
- ...options,
121
+ ...searchOptions,
113
122
  limit,
123
+ projectAffinity,
114
124
  traceSession,
115
125
  });
116
126
 
@@ -5,6 +5,7 @@
5
5
  * @module src/cli/commands/vsearch
6
6
  */
7
7
 
8
+ import type { CliProjectAffinityRequest } from "../../core/project-affinity-surface";
8
9
  import type {
9
10
  RetrievalTraceSession,
10
11
  RetrievalTraceSurfaceMetadata,
@@ -12,6 +13,7 @@ import type {
12
13
  import type { EmbeddingPort } from "../../llm/types";
13
14
  import type { SearchOptions, SearchResults } from "../../pipeline/types";
14
15
 
16
+ import { resolveCliProjectAffinity } from "../../core/project-affinity-surface";
15
17
  import {
16
18
  finishRetrievalTraceAfterError,
17
19
  retrievalTraceFilters,
@@ -35,26 +37,27 @@ import { decorateSearchResultsForIndex, initStore } from "./shared";
35
37
  // Types
36
38
  // ─────────────────────────────────────────────────────────────────────────────
37
39
 
38
- export type VsearchCommandOptions = SearchOptions & {
39
- /** Override config path */
40
- configPath?: string;
41
- /** Index name */
42
- indexName?: string;
43
- /** Override model URI */
44
- model?: string;
45
- /** Output as JSON */
46
- json?: boolean;
47
- /** Output as Markdown */
48
- md?: boolean;
49
- /** Output as CSV */
50
- csv?: boolean;
51
- /** Output as XML */
52
- xml?: boolean;
53
- /** Output files only */
54
- files?: boolean;
55
- /** Terminal hyperlink policy */
56
- terminalLinks?: FormatOptions["terminalLinks"];
57
- };
40
+ export type VsearchCommandOptions = Omit<SearchOptions, "projectAffinity"> &
41
+ CliProjectAffinityRequest & {
42
+ /** Override config path */
43
+ configPath?: string;
44
+ /** Index name */
45
+ indexName?: string;
46
+ /** Override model URI */
47
+ model?: string;
48
+ /** Output as JSON */
49
+ json?: boolean;
50
+ /** Output as Markdown */
51
+ md?: boolean;
52
+ /** Output as CSV */
53
+ csv?: boolean;
54
+ /** Output as XML */
55
+ xml?: boolean;
56
+ /** Output files only */
57
+ files?: boolean;
58
+ /** Terminal hyperlink policy */
59
+ terminalLinks?: FormatOptions["terminalLinks"];
60
+ };
58
61
 
59
62
  export type VsearchResult =
60
63
  | {
@@ -96,6 +99,12 @@ export async function vsearch(
96
99
  let traceSession: RetrievalTraceSession | undefined;
97
100
 
98
101
  try {
102
+ const { projectAffinityDisabled, projectRoots, ...searchOptions } = options;
103
+ const projectAffinity = await resolveCliProjectAffinity(config, {
104
+ cwd: process.cwd(),
105
+ disabled: projectAffinityDisabled,
106
+ projectRoots,
107
+ });
99
108
  // Get model URI from preset
100
109
  const modelUri = resolveModelUri(
101
110
  config,
@@ -107,7 +116,7 @@ export async function vsearch(
107
116
  store,
108
117
  config,
109
118
  query,
110
- filters: retrievalTraceFilters({ ...options, limit }),
119
+ filters: retrievalTraceFilters({ ...searchOptions, limit }),
111
120
  pipeline: "vector",
112
121
  indexName: options.indexName,
113
122
  modelUris: [modelUri],
@@ -152,7 +161,7 @@ export async function vsearch(
152
161
  deps,
153
162
  query,
154
163
  queryEmbedding,
155
- { ...options, limit, traceSession }
164
+ { ...searchOptions, limit, projectAffinity, traceSession }
156
165
  );
157
166
  if (!result.ok) {
158
167
  await traceSession?.finish("failed");
@@ -5,6 +5,10 @@
5
5
  * @module src/cli/options
6
6
  */
7
7
 
8
+ import {
9
+ normalizeProjectAffinityValues,
10
+ ProjectAffinityInputError,
11
+ } from "../core/project-affinity-surface";
8
12
  import { CliError } from "./errors";
9
13
 
10
14
  // ─────────────────────────────────────────────────────────────────────────────
@@ -13,6 +17,41 @@ import { CliError } from "./errors";
13
17
 
14
18
  export type OutputFormat = "terminal" | "json" | "files" | "csv" | "md" | "xml";
15
19
 
20
+ export interface CliProjectAffinityOptions {
21
+ projectAffinityDisabled: boolean;
22
+ projectRoots: string[];
23
+ }
24
+
25
+ export const collectRepeatableValue = (
26
+ value: string,
27
+ previous: string[] = []
28
+ ): string[] => [...previous, value];
29
+
30
+ export const parseCliProjectAffinityOptions = (
31
+ options: Record<string, unknown>
32
+ ): CliProjectAffinityOptions => {
33
+ try {
34
+ const projectRoots = normalizeProjectAffinityValues(
35
+ Array.isArray(options.projectRoot)
36
+ ? (options.projectRoot as string[])
37
+ : undefined,
38
+ "project roots"
39
+ );
40
+ const projectAffinityDisabled = options.projectAffinity === false;
41
+ if (projectAffinityDisabled && projectRoots.length > 0) {
42
+ throw new ProjectAffinityInputError(
43
+ "--no-project-affinity cannot be combined with --project-root"
44
+ );
45
+ }
46
+ return { projectAffinityDisabled, projectRoots };
47
+ } catch (error) {
48
+ throw new CliError(
49
+ "VALIDATION",
50
+ error instanceof Error ? error.message : "Invalid project affinity input"
51
+ );
52
+ }
53
+ };
54
+
16
55
  // ─────────────────────────────────────────────────────────────────────────────
17
56
  // Format Support Matrix (per spec/cli.md)
18
57
  // ─────────────────────────────────────────────────────────────────────────────
@@ -32,7 +32,9 @@ import { CliError } from "./errors";
32
32
  import {
33
33
  assertFormatSupported,
34
34
  CMD,
35
+ collectRepeatableValue,
35
36
  getDefaultLimit,
37
+ parseCliProjectAffinityOptions,
36
38
  parseOptionalFloat,
37
39
  parsePositiveInt,
38
40
  } from "./options";
@@ -594,6 +596,13 @@ function wireSearchCommands(program: Command): void {
594
596
  )
595
597
  .option("--tags-all <tags>", "require ALL tags (comma-separated)")
596
598
  .option("--tags-any <tags>", "require ANY tag (comma-separated)")
599
+ .option(
600
+ "--project-root <path>",
601
+ "trusted project root (repeatable; replaces cwd affinity)",
602
+ collectRepeatableValue,
603
+ []
604
+ )
605
+ .option("--no-project-affinity", "disable project-aware ranking")
597
606
  .option("--full", "include full content")
598
607
  .option("--line-numbers", "include line numbers in output")
599
608
  .option("--json", "JSON output")
@@ -641,6 +650,7 @@ function wireSearchCommands(program: Command): void {
641
650
  : getDefaultLimit(format);
642
651
  const categories = parseCsvValues(cmdOpts.category);
643
652
  const exclude = parseCsvValues(cmdOpts.exclude);
653
+ const projectAffinity = parseCliProjectAffinityOptions(cmdOpts);
644
654
 
645
655
  const { search, formatSearch } = await import("./commands/search");
646
656
  const result = await search(queryText, {
@@ -658,6 +668,7 @@ function wireSearchCommands(program: Command): void {
658
668
  exclude,
659
669
  tagsAll,
660
670
  tagsAny,
671
+ ...projectAffinity,
661
672
  full: Boolean(cmdOpts.full),
662
673
  lineNumbers: Boolean(cmdOpts.lineNumbers),
663
674
  json: format === "json",
@@ -714,6 +725,13 @@ function wireSearchCommands(program: Command): void {
714
725
  )
715
726
  .option("--tags-all <tags>", "require ALL tags (comma-separated)")
716
727
  .option("--tags-any <tags>", "require ANY tag (comma-separated)")
728
+ .option(
729
+ "--project-root <path>",
730
+ "trusted project root (repeatable; replaces cwd affinity)",
731
+ collectRepeatableValue,
732
+ []
733
+ )
734
+ .option("--no-project-affinity", "disable project-aware ranking")
717
735
  .option("--full", "include full content")
718
736
  .option("--line-numbers", "include line numbers in output")
719
737
  .option("--json", "JSON output")
@@ -759,6 +777,7 @@ function wireSearchCommands(program: Command): void {
759
777
  : getDefaultLimit(format);
760
778
  const categories = parseCsvValues(cmdOpts.category);
761
779
  const exclude = parseCsvValues(cmdOpts.exclude);
780
+ const projectAffinity = parseCliProjectAffinityOptions(cmdOpts);
762
781
 
763
782
  const { vsearch, formatVsearch } = await import("./commands/vsearch");
764
783
  const result = await vsearch(queryText, {
@@ -776,6 +795,7 @@ function wireSearchCommands(program: Command): void {
776
795
  exclude,
777
796
  tagsAll,
778
797
  tagsAny,
798
+ ...projectAffinity,
779
799
  full: Boolean(cmdOpts.full),
780
800
  lineNumbers: Boolean(cmdOpts.lineNumbers),
781
801
  json: format === "json",
@@ -827,6 +847,13 @@ function wireSearchCommands(program: Command): void {
827
847
  )
828
848
  .option("--tags-all <tags>", "require ALL tags (comma-separated)")
829
849
  .option("--tags-any <tags>", "require ANY tag (comma-separated)")
850
+ .option(
851
+ "--project-root <path>",
852
+ "trusted project root (repeatable; replaces cwd affinity)",
853
+ collectRepeatableValue,
854
+ []
855
+ )
856
+ .option("--no-project-affinity", "disable project-aware ranking")
830
857
  .option("--full", "include full content")
831
858
  .option("--line-numbers", "include line numbers in output")
832
859
  .option("--fast", "skip expansion and reranking (fastest, ~0.7s)")
@@ -931,6 +958,7 @@ function wireSearchCommands(program: Command): void {
931
958
  : undefined;
932
959
  const categories = parseCsvValues(cmdOpts.category);
933
960
  const exclude = parseCsvValues(cmdOpts.exclude);
961
+ const projectAffinity = parseCliProjectAffinityOptions(cmdOpts);
934
962
 
935
963
  const depthPolicy = resolveDepthPolicy({
936
964
  presetId: activePresetId,
@@ -960,6 +988,7 @@ function wireSearchCommands(program: Command): void {
960
988
  exclude,
961
989
  tagsAll,
962
990
  tagsAny,
991
+ ...projectAffinity,
963
992
  noExpand: depthPolicy.noExpand,
964
993
  noRerank: depthPolicy.noRerank,
965
994
  graph: Boolean(cmdOpts.graph),
@@ -995,6 +1024,7 @@ function wireSearchCommands(program: Command): void {
995
1024
  exclude,
996
1025
  tagsAll,
997
1026
  tagsAny,
1027
+ ...projectAffinity,
998
1028
  full: Boolean(cmdOpts.full),
999
1029
  lineNumbers: Boolean(cmdOpts.lineNumbers),
1000
1030
  noExpand: depthPolicy.noExpand,
@@ -1121,6 +1151,13 @@ function wireSearchCommands(program: Command): void {
1121
1151
  .option("--context-budget-bytes <num>", "verified Context byte budget")
1122
1152
  .option("--min-score <score>", "minimum retrieval score (0-1)")
1123
1153
  .option("--graph", "include bounded graph expansion")
1154
+ .option(
1155
+ "--project-root <path>",
1156
+ "trusted project root (repeatable; replaces cwd affinity)",
1157
+ collectRepeatableValue,
1158
+ []
1159
+ )
1160
+ .option("--no-project-affinity", "disable project-aware ranking")
1124
1161
  .option("--show-sources", "show all retrieved sources (not just cited)")
1125
1162
  .option("--json", "JSON output")
1126
1163
  .option("--md", "Markdown output")
@@ -1169,6 +1206,7 @@ function wireSearchCommands(program: Command): void {
1169
1206
  }
1170
1207
  const categories = parseCsvValues(cmdOpts.category);
1171
1208
  const exclude = parseCsvValues(cmdOpts.exclude);
1209
+ const projectAffinity = parseCliProjectAffinityOptions(cmdOpts);
1172
1210
 
1173
1211
  let queryModes: import("../pipeline/types").QueryModeInput[] | undefined;
1174
1212
  if (Array.isArray(cmdOpts.queryMode) && cmdOpts.queryMode.length > 0) {
@@ -1229,6 +1267,7 @@ function wireSearchCommands(program: Command): void {
1229
1267
  maxAnswerTokens,
1230
1268
  contextBudgetTokens,
1231
1269
  contextBudgetBytes,
1270
+ ...projectAffinity,
1232
1271
  showSources,
1233
1272
  json: format === "json",
1234
1273
  md: format === "md",
@@ -1974,6 +2013,13 @@ function wireManagementCommands(program: Command): void {
1974
2013
  .option("--since <date>", "modified-at lower bound")
1975
2014
  .option("--until <date>", "modified-at upper bound")
1976
2015
  .option("--graph", "enable graph neighbor expansion")
2016
+ .option(
2017
+ "--project-root <path>",
2018
+ "trusted project root (repeatable; replaces cwd affinity)",
2019
+ collectRepeatableValue,
2020
+ []
2021
+ )
2022
+ .option("--no-project-affinity", "disable project-aware ranking")
1977
2023
  .option("--fast", "use lexical-first fast retrieval")
1978
2024
  .option("--thorough", "use a wider retrieval pool")
1979
2025
  .option("-n, --limit <num>", "maximum retrieved results")
@@ -1991,6 +2037,7 @@ function wireManagementCommands(program: Command): void {
1991
2037
  throw new CliError("VALIDATION", "Choose either --fast or --thorough");
1992
2038
  }
1993
2039
  const globals = getGlobals();
2040
+ const projectAffinity = parseCliProjectAffinityOptions(cmdOpts);
1994
2041
  const { contextBuild } = await import("./commands/context-build");
1995
2042
  let queryModes: import("../pipeline/types").QueryModeInput[] | undefined;
1996
2043
  if (Array.isArray(cmdOpts.queryMode) && cmdOpts.queryMode.length > 0) {
@@ -2022,6 +2069,7 @@ function wireManagementCommands(program: Command): void {
2022
2069
  true
2023
2070
  ),
2024
2071
  query: cmdOpts.query as string | undefined,
2072
+ ...projectAffinity,
2025
2073
  queryModes,
2026
2074
  collections: cmdOpts.collection as string[],
2027
2075
  uriPrefix: cmdOpts.uriPrefix as string | undefined,
@@ -4,7 +4,12 @@
4
4
  * @module src/config/defaults
5
5
  */
6
6
 
7
- import { CONFIG_VERSION, type Config, DEFAULT_FTS_TOKENIZER } from "./types";
7
+ import {
8
+ CONFIG_VERSION,
9
+ type Config,
10
+ DEFAULT_FTS_TOKENIZER,
11
+ PROJECT_AFFINITY_MAX_CONTRIBUTION,
12
+ } from "./types";
8
13
 
9
14
  /**
10
15
  * Create a default config object.
@@ -17,5 +22,9 @@ export function createDefaultConfig(): Config {
17
22
  collections: [],
18
23
  contexts: [],
19
24
  contentTypes: [],
25
+ projectAffinity: {
26
+ enabled: true,
27
+ contribution: PROJECT_AFFINITY_MAX_CONTRIBUTION,
28
+ },
20
29
  };
21
30
  }