@sous-io/sous 0.1.0 → 0.2.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 (206) hide show
  1. package/README.md +121 -35
  2. package/bin/run.js +10 -1
  3. package/docs/markdown/README.md +27 -0
  4. package/docs/markdown/_sidebar.md +18 -0
  5. package/docs/markdown/commands.md +308 -0
  6. package/docs/markdown/config-discovery.md +74 -0
  7. package/docs/markdown/config-inspection.md +69 -0
  8. package/docs/markdown/config-layers.md +92 -0
  9. package/docs/markdown/config-variables.md +79 -0
  10. package/docs/markdown/configuration.md +71 -0
  11. package/docs/markdown/design-principles.md +59 -0
  12. package/docs/markdown/repositories-authoring.md +408 -0
  13. package/docs/markdown/repositories-consuming.md +580 -0
  14. package/docs/markdown/repositories-file-formats.md +1084 -0
  15. package/docs/markdown/repositories-variables.md +387 -0
  16. package/docs/markdown/repositories.md +303 -0
  17. package/docs/markdown/skill-categories.md +58 -0
  18. package/package.json +73 -9
  19. package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/SKILL.tpl.md +20 -20
  20. package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/examples/about-something.md +2 -2
  21. package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/examples/do-something.md +1 -1
  22. package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/references/advanced-patterns.md +6 -6
  23. package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/references/commands.md +5 -5
  24. package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/references/frontmatter.md +3 -3
  25. package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-liquid-templates/SKILL.tpl.md +40 -25
  26. package/recipes/core/sous-skills/skills/about-sous/SKILL.tpl.md +70 -0
  27. package/recipes/core/sous-skills/skills/about-sous-configuration/SKILL.tpl.md +75 -0
  28. package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/create-skill/SKILL.tpl.md +8 -9
  29. package/recipes/core/sous-skills/sous.recipe.yaml +45 -0
  30. package/sous.config.schema.json +337 -0
  31. package/src/base-command.ts +220 -67
  32. package/src/commands/build.ts +150 -73
  33. package/src/commands/clear.ts +23 -15
  34. package/src/commands/compile.ts +74 -16
  35. package/src/commands/config/get.ts +110 -0
  36. package/src/commands/config/show.ts +32 -0
  37. package/src/commands/config/validate.ts +53 -0
  38. package/src/commands/help.ts +46 -0
  39. package/src/commands/launch.ts +36 -14
  40. package/src/commands/lock/rebuild.ts +241 -0
  41. package/src/commands/lock/show.ts +115 -0
  42. package/src/commands/namespace/list.ts +117 -0
  43. package/src/commands/namespace/show.ts +110 -0
  44. package/src/commands/prune.ts +3 -11
  45. package/src/commands/recipe/list.ts +95 -0
  46. package/src/commands/recipe/show.ts +301 -0
  47. package/src/commands/repo/add.ts +145 -0
  48. package/src/commands/repo/gc.ts +172 -0
  49. package/src/commands/repo/init.ts +136 -0
  50. package/src/commands/repo/link.ts +500 -0
  51. package/src/commands/repo/list.ts +179 -0
  52. package/src/commands/repo/release.ts +619 -0
  53. package/src/commands/repo/remove.ts +193 -0
  54. package/src/commands/repo/search.ts +189 -0
  55. package/src/commands/repo/submit.ts +133 -0
  56. package/src/commands/repo/unlink.ts +147 -0
  57. package/src/commands/subscription/add.ts +285 -0
  58. package/src/commands/subscription/list.ts +129 -0
  59. package/src/commands/subscription/remove.ts +181 -0
  60. package/src/commands/vars/ask.ts +374 -0
  61. package/src/commands/vars/index.ts +79 -0
  62. package/src/commands/vars/list.ts +67 -0
  63. package/src/commands/vars/show.ts +77 -0
  64. package/src/config-command.ts +30 -0
  65. package/src/lib/build-service.ts +206 -54
  66. package/src/lib/config-discovery.ts +220 -27
  67. package/src/lib/config-inspect.ts +145 -0
  68. package/src/lib/config-kernel.mjs +377 -0
  69. package/src/lib/config-schema.ts +361 -0
  70. package/src/lib/env-file.ts +328 -0
  71. package/src/lib/env-local.ts +18 -1
  72. package/src/lib/errors.ts +32 -0
  73. package/src/lib/include-resolver.ts +108 -15
  74. package/src/lib/interactive.ts +165 -0
  75. package/src/lib/markdown-compiler.ts +118 -37
  76. package/src/lib/package-info.ts +25 -0
  77. package/src/lib/pid-service.ts +32 -21
  78. package/src/lib/refs/find.ts +589 -0
  79. package/src/lib/refs/index.ts +12 -0
  80. package/src/lib/refs/pick.ts +147 -0
  81. package/src/lib/refs/scopes.ts +61 -0
  82. package/src/lib/repos/catalog-display.ts +116 -0
  83. package/src/lib/repos/catalog-inputs.ts +160 -0
  84. package/src/lib/repos/catalog.ts +722 -0
  85. package/src/lib/repos/core-recipe.ts +105 -0
  86. package/src/lib/repos/defaults.ts +175 -0
  87. package/src/lib/repos/formats/common.ts +389 -0
  88. package/src/lib/repos/formats/index-file.ts +215 -0
  89. package/src/lib/repos/formats/links-map.ts +96 -0
  90. package/src/lib/repos/formats/lockfile.ts +167 -0
  91. package/src/lib/repos/formats/patterns.ts +57 -0
  92. package/src/lib/repos/formats/recipe-manifest.ts +395 -0
  93. package/src/lib/repos/formats/repo-manifest.ts +88 -0
  94. package/src/lib/repos/formats/store-entry.ts +84 -0
  95. package/src/lib/repos/freshness.ts +208 -0
  96. package/src/lib/repos/git-clone.ts +312 -0
  97. package/src/lib/repos/identity.ts +89 -0
  98. package/src/lib/repos/index.ts +58 -0
  99. package/src/lib/repos/links.ts +353 -0
  100. package/src/lib/repos/load-manifest.ts +236 -0
  101. package/src/lib/repos/lock-service.ts +453 -0
  102. package/src/lib/repos/locked-namespace-resolver.ts +90 -0
  103. package/src/lib/repos/locked-recipes.ts +254 -0
  104. package/src/lib/repos/managed-layer.ts +422 -0
  105. package/src/lib/repos/namespace-resolver.ts +370 -0
  106. package/src/lib/repos/providers/base.ts +206 -0
  107. package/src/lib/repos/providers/git.ts +233 -0
  108. package/src/lib/repos/providers/github.ts +294 -0
  109. package/src/lib/repos/providers/gitlab.ts +263 -0
  110. package/src/lib/repos/providers/http.ts +102 -0
  111. package/src/lib/repos/providers/index-cache.ts +382 -0
  112. package/src/lib/repos/providers/index.ts +106 -0
  113. package/src/lib/repos/providers/local.ts +391 -0
  114. package/src/lib/repos/providers/provider.ts +401 -0
  115. package/src/lib/repos/recipe-config-layers.ts +287 -0
  116. package/src/lib/repos/recipe-targets.ts +223 -0
  117. package/src/lib/repos/ref-search.ts +46 -0
  118. package/src/lib/repos/ref.ts +513 -0
  119. package/src/lib/repos/reference-report.ts +122 -0
  120. package/src/lib/repos/release/bump.ts +161 -0
  121. package/src/lib/repos/release/git-state.ts +305 -0
  122. package/src/lib/repos/release/index-builder.ts +635 -0
  123. package/src/lib/repos/release/index.ts +16 -0
  124. package/src/lib/repos/release/plan.ts +512 -0
  125. package/src/lib/repos/release/submit-service.ts +496 -0
  126. package/src/lib/repos/release/tags.ts +243 -0
  127. package/src/lib/repos/release/validate.ts +463 -0
  128. package/src/lib/repos/resolver.ts +789 -0
  129. package/src/lib/repos/scaffold/index.ts +238 -0
  130. package/src/lib/repos/scaffold/templates.ts +413 -0
  131. package/src/lib/repos/seed.ts +414 -0
  132. package/src/lib/repos/store/contract.ts +64 -0
  133. package/src/lib/repos/store/hash.ts +114 -0
  134. package/src/lib/repos/store/recipe-store.ts +599 -0
  135. package/src/lib/repos/store/settings.ts +58 -0
  136. package/src/lib/repos/subscription-service.ts +2678 -0
  137. package/src/lib/repos/trust.ts +447 -0
  138. package/src/lib/settings.ts +546 -189
  139. package/src/lib/sous-home.ts +104 -0
  140. package/src/lib/state.ts +52 -20
  141. package/src/lib/vars/ask.ts +1152 -0
  142. package/src/lib/vars/definition-source.ts +252 -0
  143. package/src/lib/vars/display.ts +233 -0
  144. package/src/lib/vars/index.ts +18 -0
  145. package/src/lib/vars/ladder.ts +282 -0
  146. package/src/lib/vars/mappings.ts +265 -0
  147. package/src/lib/vars/names.ts +94 -0
  148. package/src/lib/vars/preanswers.ts +395 -0
  149. package/src/lib/vars/question-plan.ts +218 -0
  150. package/src/lib/vars/report.ts +228 -0
  151. package/src/lib/vars/safe-regex.ts +235 -0
  152. package/src/lib/vars/validate.ts +312 -0
  153. package/src/lib/watch-loop.ts +148 -0
  154. package/src/templating/init-liquid-engine.ts +58 -16
  155. package/src/utils/choice-prompt.ts +143 -0
  156. package/src/utils/command-errors.ts +186 -0
  157. package/src/utils/command-help.ts +45 -0
  158. package/src/utils/confirm-prompt.ts +110 -0
  159. package/src/utils/flags.ts +153 -0
  160. package/src/utils/formatting.ts +540 -55
  161. package/src/utils/prompts.ts +35 -1
  162. package/src/utils/sous-directory.ts +245 -0
  163. package/src/utils/table.ts +603 -0
  164. package/src/utils/value-prompt.ts +119 -0
  165. package/bin/xcv +0 -5
  166. package/shared-prompts/_partials/resume-task.md +0 -51
  167. package/shared-prompts/_partials/sub-agent-delegation.md +0 -32
  168. package/shared-prompts/_partials/update-task-file.md +0 -52
  169. package/shared-prompts/memories/automated-browser-tasks/INDEX.tpl.md +0 -52
  170. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/SKILL.tpl.md +0 -102
  171. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/examples/auth-failure-handling.mjs +0 -81
  172. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/examples/chained-workflow.mjs +0 -126
  173. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/examples/simple-fetch.mjs +0 -92
  174. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/architecture.md +0 -61
  175. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/auth-and-sessions.md +0 -65
  176. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/ctx-api.md +0 -96
  177. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/installation.md +0 -104
  178. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/script-conventions.md +0 -243
  179. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/chrome-state.mjs +0 -148
  180. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/debug.mjs +0 -383
  181. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/debug.spec.mjs +0 -267
  182. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/eslint.config.mjs +0 -56
  183. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/harness.mjs +0 -169
  184. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/keyring.mjs +0 -59
  185. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/logger.mjs +0 -25
  186. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/params.mjs +0 -140
  187. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/run.mjs +0 -140
  188. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/settings.tpl.mjs +0 -1
  189. package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/utils.mjs +0 -185
  190. package/shared-prompts/skills/automated-browser-tasks/create-automated-browser-task/SKILL.tpl.md +0 -52
  191. package/shared-prompts/skills/automated-browser-tasks/running-automated-browser-tasks/SKILL.tpl.md +0 -59
  192. package/shared-prompts/skills/automated-browser-tasks/update-automated-browser-task/SKILL.tpl.md +0 -47
  193. package/shared-prompts/skills/control-flow/approve/SKILL.tpl.md +0 -26
  194. package/shared-prompts/skills/control-flow/opine/SKILL.tpl.md +0 -58
  195. package/shared-prompts/skills/control-flow/repeat/SKILL.tpl.md +0 -27
  196. package/shared-prompts/skills/control-flow/research/SKILL.tpl.md +0 -34
  197. package/shared-prompts/skills/sous-skills/about-sous/SKILL.tpl.md +0 -51
  198. package/shared-prompts/skills/task-files/about-task-files/SKILL.tpl.md +0 -122
  199. package/shared-prompts/skills/task-files/continue-task-in-new-branch/SKILL.tpl.md +0 -80
  200. package/shared-prompts/skills/task-files/go/SKILL.tpl.md +0 -14
  201. package/shared-prompts/skills/task-files/resume-task/SKILL.tpl.md +0 -13
  202. package/shared-prompts/skills/task-files/start-task/SKILL.tpl.md +0 -93
  203. package/shared-prompts/skills/task-files/update/SKILL.tpl.md +0 -14
  204. package/shared-prompts/skills/task-files/update-task-file/SKILL.tpl.md +0 -13
  205. /package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/references/substitutions.md +0 -0
  206. /package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-liquid-templates/references/liquid-filters.md +0 -0
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Turning subscribed recipes into compile targets.
3
+ *
4
+ * A recipe's manifest lists what it contributes: skills, memories, prompts and
5
+ * config layers. Everything but the config layers is compiled exactly the way a
6
+ * project's own `entryGlob` target is: the recipe's directory is the glob root,
7
+ * the static part of each include pattern is the base the output tree mirrors,
8
+ * and the `.tpl.` convention applies unchanged. Nothing about a recipe's files
9
+ * is special once they are on disk.
10
+ *
11
+ * Two rules decide what is compiled at all:
12
+ *
13
+ * - Only recipes held through `subscribes` contribute files. A recipe held
14
+ * only through `depends` is fetched, pinned and addressable from the recipe
15
+ * that declared it, but its files never enter the project's output; that is
16
+ * the whole difference between the two dependency kinds.
17
+ * - A content kind with nowhere to go is skipped, with ONE warning naming the
18
+ * `recipeOutputs` config key. Only `skills` has a default, because
19
+ * `.claude/skills` is where every agent looks; sous cannot guess where a
20
+ * project wants its memories or its prompts.
21
+ *
22
+ * A recipe's `config` contents are not handled here. They are config layers, and
23
+ * config layers are loaded before any of this runs; see
24
+ * `listRecipeConfigLayers` in `config-discovery.ts`.
25
+ */
26
+
27
+ import fs from "node:fs";
28
+ import path from "node:path";
29
+ import { globSync } from "glob";
30
+ import type { CompilationTarget } from "../markdown-compiler.js";
31
+ import { inferGlobBase } from "../markdown-compiler.js";
32
+ import type { Settings, VarScope } from "../settings.js";
33
+ import { normalizeConfigPath, substituteVarsStrict } from "../settings.js";
34
+ import type { ContentKind } from "./formats/recipe-manifest.js";
35
+ import {
36
+ listLockedRecipes,
37
+ readRecipeManifestIn,
38
+ type LockedRecipeLocation,
39
+ } from "./locked-recipes.js";
40
+
41
+ /** The content kinds that produce files in a project. `config` is a layer, not a file. */
42
+ export const WRITABLE_CONTENT_KINDS = ["skills", "memories", "prompts"] as const;
43
+
44
+ /** One content kind whose files land somewhere in the project. */
45
+ export type WritableContentKind = (typeof WRITABLE_CONTENT_KINDS)[number];
46
+
47
+ /** How the recipe targets are built. */
48
+ export type RecipeTargetOptions = {
49
+ /** The project's `.sous/` directory. */
50
+ sousDir: string;
51
+ /** The merged project config, read for `recipeOutputs`. */
52
+ settings: Settings;
53
+ /** The resolved settings scope, used to substitute `${var}` in destinations. */
54
+ scope?: VarScope;
55
+ /** The environment to read; decides where the store is. */
56
+ env?: NodeJS.ProcessEnv;
57
+ /** The locked recipes, when the caller has already located them. */
58
+ locked?: LockedRecipeLocation[];
59
+ };
60
+
61
+ /** What the recipe targets add to a build. */
62
+ export type RecipeTargets = {
63
+ /** One compile target per file a subscribed recipe contributes. */
64
+ targets: CompilationTarget[];
65
+ /** Every destination directory the targets write into, for prune's benefit. */
66
+ destinations: string[];
67
+ /** Directories a watch should follow: the linked checkouts contributing files. */
68
+ watchDirs: string[];
69
+ /** Complete, plain-language sentences a build should print before it compiles. */
70
+ warnings: string[];
71
+ };
72
+
73
+ /**
74
+ * The project's root directory: the parent of its `.sous/` directory. Used only
75
+ * for the one destination sous defaults, `.claude/skills`.
76
+ *
77
+ * @param sousDir - The project's `.sous/` directory.
78
+ */
79
+ export function projectRootFor(sousDir: string): string {
80
+ return path.dirname(path.resolve(sousDir));
81
+ }
82
+
83
+ /**
84
+ * Where one content kind's files are written, with `${var}` substituted and each
85
+ * path normalized. An empty list means "nowhere configured", which is what makes
86
+ * the caller warn and skip.
87
+ *
88
+ * @param kind - The content kind.
89
+ * @param options - The project's config, scope and `.sous/` directory.
90
+ */
91
+ export function destinationsFor(
92
+ kind: WritableContentKind,
93
+ options: RecipeTargetOptions
94
+ ): string[] {
95
+ const configured = options.settings.recipeOutputs?.[kind];
96
+
97
+ if (configured === undefined || configured.length === 0) {
98
+ // `.claude/skills` is where every agent looks, so it is worth defaulting.
99
+ // Nothing else has an obvious home.
100
+ return kind === "skills"
101
+ ? [path.join(projectRootFor(options.sousDir), ".claude", "skills")]
102
+ : [];
103
+ }
104
+
105
+ return configured.map((destination, index) =>
106
+ normalizeConfigPath(
107
+ substituteVarsStrict(
108
+ destination,
109
+ options.scope ?? {},
110
+ `recipeOutputs.${kind}[${index}]`
111
+ )
112
+ )
113
+ );
114
+ }
115
+
116
+ /**
117
+ * Builds the compile targets that the recipes this project subscribes to
118
+ * contribute. Returns an empty result for a project that locks nothing, so a
119
+ * project using no repositories pays nothing for this.
120
+ *
121
+ * @param options - The project's `.sous/` directory, its config and its scope.
122
+ */
123
+ export function buildRecipeTargets(options: RecipeTargetOptions): RecipeTargets {
124
+ const locked =
125
+ options.locked ??
126
+ listLockedRecipes({
127
+ sousDir: options.sousDir,
128
+ ...(options.env === undefined ? {} : { env: options.env }),
129
+ });
130
+
131
+ const result: RecipeTargets = {
132
+ targets: [],
133
+ destinations: [],
134
+ watchDirs: [],
135
+ warnings: [],
136
+ };
137
+ if (locked.length === 0) return result;
138
+
139
+ const destinationCache = new Map<WritableContentKind, string[]>();
140
+ const kindsWithNowhereToGo = new Set<WritableContentKind>();
141
+ const destinations = new Set<string>();
142
+ const watchDirs = new Set<string>();
143
+
144
+ for (const recipe of locked) {
145
+ // Only a co-subscription contributes files; a build dependency is fetched
146
+ // and addressable, and that is all.
147
+ if (recipe.kind !== "subscribes") continue;
148
+ if (!recipe.present) continue;
149
+
150
+ const manifest = readRecipeManifestIn(recipe.dir);
151
+ if (manifest === undefined) continue;
152
+
153
+ for (const content of manifest.contents) {
154
+ const kind = content.kind;
155
+ if (!isWritableKind(kind)) continue;
156
+
157
+ if (!destinationCache.has(kind)) {
158
+ destinationCache.set(kind, destinationsFor(kind, options));
159
+ }
160
+ const kindDestinations = destinationCache.get(kind)!;
161
+
162
+ if (kindDestinations.length === 0) {
163
+ kindsWithNowhereToGo.add(kind);
164
+ continue;
165
+ }
166
+
167
+ for (const destination of kindDestinations) destinations.add(destination);
168
+ if (recipe.linked) watchDirs.add(recipe.dir);
169
+
170
+ const ignore = (content.exclude ?? []).map((pattern) =>
171
+ path.join(recipe.dir, pattern)
172
+ );
173
+
174
+ for (const include of content.include) {
175
+ const pattern = path.join(recipe.dir, include);
176
+ const matched = globSync(pattern, { absolute: true, ignore }).filter(isFile);
177
+ const globBase = normalizeConfigPath(inferGlobBase(pattern));
178
+
179
+ for (const filePath of matched) {
180
+ result.targets.push({
181
+ rootInputPath: filePath,
182
+ globBase,
183
+ outputs: kindDestinations.map((destination) => ({
184
+ destinationDir: destination,
185
+ vars: options.scope ?? {},
186
+ })),
187
+ });
188
+ }
189
+ }
190
+ }
191
+ }
192
+
193
+ if (kindsWithNowhereToGo.size > 0) {
194
+ const kinds = [...kindsWithNowhereToGo].sort();
195
+ result.warnings.push(
196
+ `Some subscribed recipes contribute ${kinds.join(" and ")} files, and this ` +
197
+ `project has nowhere to put them, so they were skipped.\n` +
198
+ `Name a destination directory for each kind under the 'recipeOutputs' key of ` +
199
+ `your sous config, for example:\n` +
200
+ ` recipeOutputs: { ${kinds
201
+ .map((kind) => `${kind}: ["\${projectRoot}/${kind}"]`)
202
+ .join(", ")} }`
203
+ );
204
+ }
205
+
206
+ result.destinations = [...destinations].sort();
207
+ result.watchDirs = [...watchDirs].sort();
208
+ return result;
209
+ }
210
+
211
+ /** True when a content kind produces files rather than a config layer. */
212
+ function isWritableKind(kind: ContentKind): kind is WritableContentKind {
213
+ return (WRITABLE_CONTENT_KINDS as readonly string[]).includes(kind);
214
+ }
215
+
216
+ /** True when the path is a regular file. */
217
+ function isFile(candidate: string): boolean {
218
+ try {
219
+ return fs.statSync(candidate).isFile();
220
+ } catch {
221
+ return false;
222
+ }
223
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * What was searched when a ref matched nothing.
3
+ *
4
+ * Resolving a ref itself lives in `src/lib/refs/`, which every command shares.
5
+ * All that is left here is the one thing only a repository search can say: which
6
+ * repositories were actually looked in, and which were trusted but unreadable,
7
+ * so the reader can tell "the name is wrong" from "the repository holding it
8
+ * was never fetched".
9
+ */
10
+
11
+ import type { IndexFile } from "./formats/index-file.js";
12
+
13
+ /** The repositories a ref was searched in, and the indexes that were available. */
14
+ export type IndexSearchInputs = {
15
+ /** The name that was searched for. */
16
+ name: string;
17
+ /** The repositories the search covered, in search order. */
18
+ repoOrder: string[];
19
+ /** The cached index of each repository, keyed by short name. */
20
+ indexes: Map<string, IndexFile>;
21
+ };
22
+
23
+ /**
24
+ * The lines a name that matched nothing is explained with: every repository
25
+ * whose index was searched, and every trusted repository whose index could not
26
+ * be read.
27
+ *
28
+ * @param inputs - The same inputs the search was given.
29
+ */
30
+ export function describeIndexSearch(inputs: IndexSearchInputs): string[] {
31
+ const searched = inputs.repoOrder.filter((repo) => inputs.indexes.has(repo));
32
+ const missing = inputs.repoOrder.filter((repo) => !inputs.indexes.has(repo));
33
+
34
+ const lines = [
35
+ searched.length === 0
36
+ ? ` No repository index could be read, so there was nothing to search.`
37
+ : ` Searched the namespaces and the recipe names of: ${searched.join(", ")}.`,
38
+ ];
39
+ if (missing.length > 0) {
40
+ lines.push(
41
+ ` These repositories are trusted but their index could not be read, so nothing ` +
42
+ `in them was searched: ${missing.join(", ")}.`
43
+ );
44
+ }
45
+ return lines;
46
+ }