vigiles 33.2.0 → 33.3.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.
@@ -16,7 +16,10 @@ export interface SetupPlan {
16
16
  test: boolean;
17
17
  /** Wire CI (the `zernie/vigiles@v1` Action; creates a workflow if none). */
18
18
  gha: boolean;
19
- /** Install the Claude Code plugin (hooks + skills). */
19
+ /**
20
+ * Install vigiles's skills + hooks for the agent: the skills LINKED into the
21
+ * repo (every clone), the Claude Code hooks via the per-machine plugin.
22
+ */
20
23
  plugin: boolean;
21
24
  /**
22
25
  * Adopt/scaffold the instruction file(s) into typed `.spec.ts` (the compiled
@@ -193,15 +196,18 @@ export interface InstallPlan {
193
196
  notes: string[];
194
197
  /** Whether this method writes files into the consumer's repo (vendoring). */
195
198
  vendors: boolean;
199
+ /**
200
+ * Link the package's shipped skills into the repo's skills home
201
+ * (`src/skill-links.ts`). True only where the vendor documents following a
202
+ * symlinked skill folder — a link the harness skips is a silent absence.
203
+ */
204
+ linkSkills: boolean;
205
+ /**
206
+ * The global install carries HOOKS as well as skills, so linked skills do not
207
+ * replace it (see {@link shouldRunGlobalInstall}).
208
+ */
209
+ globalInstallCarriesHooks: boolean;
196
210
  }
197
- /** Per-harness install plan. `hasClaude` gates the auto-run `claude plugin` CLI
198
- * (else the same two steps are printed as `/plugin` slash commands).
199
- *
200
- * Both methods install GLOBALLY, never into the repo: Claude through its plugin
201
- * marketplace (~/.claude/plugins/), Codex through the cross-agent `skills` CLI
202
- * with `-g -y` (the global store ~/.agents/skills/, which Codex reads). Codex
203
- * gets the skills but NOT hooks — Codex hook wiring (.codex/config.toml [hooks])
204
- * is not automated yet. */
205
211
  /**
206
212
  * The SHIPPED consumer skills (the `skills/` dir, published via `plugin.json`) —
207
213
  * the ONLY ones a user's install should get. The cross-agent `skills` CLI would
@@ -212,9 +218,43 @@ export interface InstallPlan {
212
218
  * dogfood test in `src/adapters/claude-code/skills-dogfood.test.ts`.
213
219
  */
214
220
  export declare const SHIPPED_SKILLS: readonly ["adopt-spec", "debug-my-harness", "edit-spec", "linter-docs", "strengthen", "test-harness"];
221
+ /**
222
+ * Per-harness install plan. `hasClaude` gates the auto-run `claude plugin` CLI
223
+ * (else the same two steps are printed as `/plugin` slash commands).
224
+ *
225
+ * TWO CARRIERS, by what each one reaches. The SKILLS are linked into the repo
226
+ * (`linkSkills`), so every clone and container has them after `npm install`. The
227
+ * GLOBAL installs reach one machine: Claude Code's plugin (~/.claude/plugins/)
228
+ * is still the only carrier of vigiles's Claude Code HOOKS, so it always runs;
229
+ * Codex's `skills` CLI install (~/.agents/skills/) carries skills only, so it
230
+ * runs only when the links could not be made (no `package.json` to install
231
+ * into). Codex's nudge hooks are written into `.codex/config.toml` directly.
232
+ *
233
+ * THE CODEX GLOBAL COMMAND. The cross-agent `skills` CLI with `-g -y` installs
234
+ * to the global store ~/.agents/skills/ (NOT the repo, and NOT ~/.codex/ —
235
+ * verified against the real CLI). It is scoped to the SHIPPED skills with `-s`
236
+ * — without it the `skills` CLI installs EVERY SKILL.md in the repo, leaking
237
+ * the contributor-only .claude/skills/ into the user's global store
238
+ * (#dogfood-I2; confirmed — the CLI's `-l` list shows 17 skills for this repo,
239
+ * incl. .claude/skills). The `-s` parser is SPACE-separated (it consumes
240
+ * consecutive non-dash args), NOT comma-separated — a comma list is read as one
241
+ * literal skill name, matches nothing, and the install exits 1 (verified against
242
+ * the real `skills@1.5.20` arg parser).
243
+ */
215
244
  export declare function planPluginInstall(harnesses: readonly string[], opts: {
216
245
  hasClaude: boolean;
217
246
  }): InstallPlan[];
247
+ /**
248
+ * Run a plan's GLOBAL install, given whether the skills were linked into the
249
+ * repo? Skip it only when linked skills make it redundant — when it carries
250
+ * nothing but skills. Running it anyway would list every skill twice (Codex:
251
+ * "If two skills share the same `name`, Codex doesn't merge them; both can
252
+ * appear in skill selectors"). Claude Code's plugin carries the hooks, so it
253
+ * always runs; its skills are namespaced `vigiles:<name>` and load beside the
254
+ * linked `<name>` (a known, documented duplicate until the hooks move into the
255
+ * repo too).
256
+ */
257
+ export declare function shouldRunGlobalInstall(plan: Readonly<Pick<InstallPlan, "globalInstallCarriesHooks">>, skillsLinked: boolean): boolean;
218
258
  /** One vigiles-managed Codex hook: a `[[hooks.<event>]]` entry. */
219
259
  export interface CodexPluginHook {
220
260
  readonly event: string;
@@ -18,6 +18,7 @@ exports.shouldPrompt = shouldPrompt;
18
18
  exports.collectSetupAnswers = collectSetupAnswers;
19
19
  exports.gateOnlyInvitation = gateOnlyInvitation;
20
20
  exports.planPluginInstall = planPluginInstall;
21
+ exports.shouldRunGlobalInstall = shouldRunGlobalInstall;
21
22
  exports.codexPluginHooks = codexPluginHooks;
22
23
  exports.applyCodexPluginHooks = applyCodexPluginHooks;
23
24
  exports.resolvePlan = resolvePlan;
@@ -225,7 +226,7 @@ async function collectSetupAnswers(ask) {
225
226
  }
226
227
  const pillars = (await ask("Set up which pillars? [both/lint/test] (both): ", "both")).toLowerCase();
227
228
  const gha = isYesAnswer(await ask("Wire CI (GitHub Action)? [Y/n]: ", "y"));
228
- const plugin = isYesAnswer(await ask("Install the Claude Code plugin (hooks + skills)? [Y/n]: ", "y"));
229
+ const plugin = isYesAnswer(await ask("Install vigiles's skills + hooks for your agent (skills linked into the repo, hooks via the Claude Code plugin)? [Y/n]: ", "y"));
229
230
  // Structural gating (broken tools/hooks/MCP/collisions) is always on. This asks
230
231
  // about the WORKFLOW tier — a spec per file + a test per surface — which a clean
231
232
  // repo can fail just for not having done the work yet, so it's the recommended
@@ -289,14 +290,6 @@ function gateOnlyInvitation(plan) {
289
290
  return null;
290
291
  return "→ Want your agent to maintain this + measure whether your skills fire? Run `npx vigiles init` and choose 'full' (installs the skills). Optional — the gate above already works.";
291
292
  }
292
- /** Per-harness install plan. `hasClaude` gates the auto-run `claude plugin` CLI
293
- * (else the same two steps are printed as `/plugin` slash commands).
294
- *
295
- * Both methods install GLOBALLY, never into the repo: Claude through its plugin
296
- * marketplace (~/.claude/plugins/), Codex through the cross-agent `skills` CLI
297
- * with `-g -y` (the global store ~/.agents/skills/, which Codex reads). Codex
298
- * gets the skills but NOT hooks — Codex hook wiring (.codex/config.toml [hooks])
299
- * is not automated yet. */
300
293
  /**
301
294
  * The SHIPPED consumer skills (the `skills/` dir, published via `plugin.json`) —
302
295
  * the ONLY ones a user's install should get. The cross-agent `skills` CLI would
@@ -314,70 +307,111 @@ exports.SHIPPED_SKILLS = [
314
307
  "strengthen",
315
308
  "test-harness",
316
309
  ];
310
+ /**
311
+ * Per-harness install plan. `hasClaude` gates the auto-run `claude plugin` CLI
312
+ * (else the same two steps are printed as `/plugin` slash commands).
313
+ *
314
+ * TWO CARRIERS, by what each one reaches. The SKILLS are linked into the repo
315
+ * (`linkSkills`), so every clone and container has them after `npm install`. The
316
+ * GLOBAL installs reach one machine: Claude Code's plugin (~/.claude/plugins/)
317
+ * is still the only carrier of vigiles's Claude Code HOOKS, so it always runs;
318
+ * Codex's `skills` CLI install (~/.agents/skills/) carries skills only, so it
319
+ * runs only when the links could not be made (no `package.json` to install
320
+ * into). Codex's nudge hooks are written into `.codex/config.toml` directly.
321
+ *
322
+ * THE CODEX GLOBAL COMMAND. The cross-agent `skills` CLI with `-g -y` installs
323
+ * to the global store ~/.agents/skills/ (NOT the repo, and NOT ~/.codex/ —
324
+ * verified against the real CLI). It is scoped to the SHIPPED skills with `-s`
325
+ * — without it the `skills` CLI installs EVERY SKILL.md in the repo, leaking
326
+ * the contributor-only .claude/skills/ into the user's global store
327
+ * (#dogfood-I2; confirmed — the CLI's `-l` list shows 17 skills for this repo,
328
+ * incl. .claude/skills). The `-s` parser is SPACE-separated (it consumes
329
+ * consecutive non-dash args), NOT comma-separated — a comma list is read as one
330
+ * literal skill name, matches nothing, and the install exits 1 (verified against
331
+ * the real `skills@1.5.20` arg parser).
332
+ */
317
333
  function planPluginInstall(harnesses, opts) {
318
- return harnesses.map((harness) => {
319
- if (harness === "claude") {
320
- return {
321
- harness,
322
- commands: opts.hasClaude
323
- ? [
324
- "claude plugin marketplace add zernie/vigiles",
325
- "claude plugin install vigiles@vigiles",
326
- ]
327
- : [],
328
- successMessage: "✓ Installed the vigiles plugin (hooks + skills) into ~/.claude/plugins/",
329
- manualSteps: [
330
- "/plugin marketplace add zernie/vigiles",
331
- "/plugin install vigiles@vigiles",
332
- ],
333
- notes: [
334
- "Installs globally to ~/.claude/plugins/ — nothing is added to your repo.",
335
- ],
336
- vendors: false,
337
- };
338
- }
339
- if (harness === "codex") {
340
- // The cross-agent `skills` CLI with `-g -y` installs to the global store
341
- // ~/.agents/skills/ (NOT the repo, and NOT ~/.codex/ — verified against
342
- // the real CLI). Skills install globally; the proactive NUDGE hooks
343
- // (eval-lock + refs) are wired into the repo's .codex/config.toml by
344
- // `init` (see codexPluginHooks / wireCodexHooks) — Codex config is
345
- // repo-committed, so that's the idiomatic place.
346
- // Scope to the SHIPPED skills with `-s` — without it the `skills` CLI
347
- // installs EVERY SKILL.md in the repo, leaking the contributor-only
348
- // .claude/skills/ into the user's global store (#dogfood-I2; confirmed —
349
- // the CLI's `-l` list shows 17 skills for this repo, incl. .claude/skills).
350
- // The `-s` parser is SPACE-separated (it consumes consecutive non-dash
351
- // args), NOT comma-separated — a comma list is read as one literal skill
352
- // name, matches nothing, and the install exits 1 (verified against the
353
- // real `skills@1.5.20` arg parser).
354
- const skillScope = `-s ${exports.SHIPPED_SKILLS.join(" ")}`;
355
- return {
356
- harness,
357
- commands: [
358
- `npx --yes skills add zernie/vigiles -a codex ${skillScope} -g -y`,
359
- ],
360
- successMessage: "✓ Installed the vigiles skills into ~/.agents/skills/ (global, not vendored)",
361
- manualSteps: [
362
- `npx skills add zernie/vigiles -a codex ${skillScope} -g -y`,
363
- ],
364
- notes: [
365
- "Codex reads AGENTS.md directly; the skills install globally to ~/.agents/skills/ (not the repo).",
366
- "The eval-lock + refs NUDGE hooks are wired into .codex/config.toml (repo-committed, the Codex norm).",
367
- "Still manual on Codex: the SessionStart lint summary + compile-on-edit/pre-edit guards (no harness-neutral entrypoint yet).",
368
- ],
369
- vendors: true,
370
- };
371
- }
334
+ return harnesses.map((harness) => ({
335
+ ...globalInstallFor(harness, opts),
336
+ ...skillCarriers(harness),
337
+ }));
338
+ }
339
+ /**
340
+ * How the skills and the hooks travel for one harness. Linking is on only where
341
+ * the vendor documents following a symlinked skill folder (quoted in
342
+ * `./skill-links.ts`); the global install "carries hooks" only for Claude Code,
343
+ * whose plugin is where its hooks live.
344
+ */
345
+ function skillCarriers(harness) {
346
+ if (harness === "claude")
347
+ return { linkSkills: true, globalInstallCarriesHooks: true };
348
+ // eslint-disable-next-line local/no-harness-names -- init's per-harness install plan is keyed by harness name, like the global install beside it
349
+ if (harness === "codex")
350
+ return { linkSkills: true, globalInstallCarriesHooks: false };
351
+ return { linkSkills: false, globalInstallCarriesHooks: false };
352
+ }
353
+ /** The global install half of {@link planPluginInstall}, per harness. */
354
+ function globalInstallFor(harness, opts) {
355
+ if (harness === "claude") {
372
356
  return {
373
357
  harness,
374
- commands: [],
375
- successMessage: "",
376
- manualSteps: [],
377
- notes: [`No plugin install path for harness '${harness}'.`],
358
+ commands: opts.hasClaude
359
+ ? [
360
+ "claude plugin marketplace add zernie/vigiles",
361
+ "claude plugin install vigiles@vigiles",
362
+ ]
363
+ : [],
364
+ successMessage: "✓ Installed the vigiles plugin (hooks + skills) into ~/.claude/plugins/",
365
+ manualSteps: [
366
+ "/plugin marketplace add zernie/vigiles",
367
+ "/plugin install vigiles@vigiles",
368
+ ],
369
+ notes: [
370
+ "The plugin installs globally to ~/.claude/plugins/ and carries the hooks (and a namespaced copy of the skills, listed as vigiles:<name>) on this machine only.",
371
+ ],
378
372
  vendors: false,
379
373
  };
380
- });
374
+ }
375
+ if (harness === "codex") {
376
+ const skillScope = `-s ${exports.SHIPPED_SKILLS.join(" ")}`;
377
+ return {
378
+ harness,
379
+ commands: [
380
+ `npx --yes skills add zernie/vigiles -a codex ${skillScope} -g -y`,
381
+ ],
382
+ successMessage: "✓ Installed the vigiles skills into ~/.agents/skills/ (global, not vendored)",
383
+ manualSteps: [
384
+ `npx skills add zernie/vigiles -a codex ${skillScope} -g -y`,
385
+ ],
386
+ notes: [
387
+ "Codex reads AGENTS.md directly; the skills install globally to ~/.agents/skills/ (not the repo).",
388
+ "The eval-lock + refs NUDGE hooks are wired into .codex/config.toml (repo-committed, the Codex norm).",
389
+ "Still manual on Codex: the SessionStart lint summary + compile-on-edit/pre-edit guards (no harness-neutral entrypoint yet).",
390
+ ],
391
+ vendors: true,
392
+ };
393
+ }
394
+ return {
395
+ harness,
396
+ commands: [],
397
+ successMessage: "",
398
+ manualSteps: [],
399
+ notes: [`No plugin install path for harness '${harness}'.`],
400
+ vendors: false,
401
+ };
402
+ }
403
+ /**
404
+ * Run a plan's GLOBAL install, given whether the skills were linked into the
405
+ * repo? Skip it only when linked skills make it redundant — when it carries
406
+ * nothing but skills. Running it anyway would list every skill twice (Codex:
407
+ * "If two skills share the same `name`, Codex doesn't merge them; both can
408
+ * appear in skill selectors"). Claude Code's plugin carries the hooks, so it
409
+ * always runs; its skills are namespaced `vigiles:<name>` and load beside the
410
+ * linked `<name>` (a known, documented duplicate until the hooks move into the
411
+ * repo too).
412
+ */
413
+ function shouldRunGlobalInstall(plan, skillsLinked) {
414
+ return plan.globalInstallCarriesHooks || !skillsLinked;
381
415
  }
382
416
  /**
383
417
  * vigiles's proactive nudges, wired into a Codex repo's `.codex/config.toml`.
@@ -0,0 +1,183 @@
1
+ /** The package whose skills are linked. */
2
+ export declare const VIGILES_PACKAGE = "vigiles";
3
+ /** What occupies `<skills home>/<name>` today, as the IO edge observed it. */
4
+ export type SkillEntry = {
5
+ readonly kind: "missing";
6
+ } | {
7
+ readonly kind: "link";
8
+ /** The link's stored target, exactly as `readlink` returns it. */
9
+ readonly target: string;
10
+ /** Where it resolves, or null when it dangles. */
11
+ readonly resolvesTo: string | null;
12
+ } | {
13
+ readonly kind: "directory";
14
+ } | {
15
+ readonly kind: "file";
16
+ };
17
+ /** What `init` should do about one skill. */
18
+ export type SkillLinkDecision = {
19
+ readonly name: string;
20
+ /** `replace`: an earlier `init`'s link that now points at the wrong place. */
21
+ readonly action: "create" | "replace";
22
+ readonly target: string;
23
+ } | {
24
+ readonly name: string;
25
+ readonly action: "keep";
26
+ } | {
27
+ readonly name: string;
28
+ readonly action: "refuse";
29
+ readonly reason: string;
30
+ };
31
+ /** The link `init` wants for one skill. */
32
+ export interface WantedLink {
33
+ /** The relative target to write (and to recognise on a re-run). */
34
+ readonly target: string;
35
+ /** The real path of the shipped skill, or null while it is not installed. */
36
+ readonly real: string | null;
37
+ }
38
+ /**
39
+ * Decide one entry. A link with OUR target is ours even while it dangles — that
40
+ * is exactly how a committed link looks in a clone that has not run
41
+ * `npm install` yet, and calling it foreign would make re-running `init` noisy
42
+ * in precisely the place it is meant to help.
43
+ */
44
+ export declare function decideSkillLink(name: string, entry: SkillEntry, want: WantedLink): SkillLinkDecision;
45
+ /** Where the package is, as the project spells it. */
46
+ export type PackageSite =
47
+ /** Installed: `<ancestor>/node_modules/vigiles`, inside the repository. */
48
+ {
49
+ readonly kind: "installed";
50
+ readonly dir: string;
51
+ }
52
+ /** Not installed yet: where npm puts the devDependency `init` declares. */
53
+ | {
54
+ readonly kind: "expected";
55
+ readonly dir: string;
56
+ }
57
+ /**
58
+ * Not installed, and the project is a member of a workspace: npm and yarn
59
+ * hoist its dependencies to the workspace ROOT's `node_modules`, pnpm keeps
60
+ * them in the member's own, and a hoist can be refused by a version
61
+ * conflict. Which one happens is decided by the install, so no link is made
62
+ * before it — a guessed link can dangle for good.
63
+ */
64
+ | {
65
+ readonly kind: "unsure";
66
+ readonly reason: string;
67
+ };
68
+ /** What the walk may ask of the filesystem. */
69
+ export interface PackageProbe {
70
+ /** Is `dir` an installed vigiles package (its `package.json` names it)? */
71
+ readonly isPackage: (dir: string) => boolean;
72
+ /** Is `dir` the repository root (it holds `.git`)? The walk stops there. */
73
+ readonly isRepoRoot: (dir: string) => boolean;
74
+ /** Is `dir` a workspace root (`workspaces` in package.json, or pnpm-workspace.yaml)? */
75
+ readonly isWorkspaceRoot: (dir: string) => boolean;
76
+ }
77
+ /**
78
+ * Node's own lookup — the first `node_modules/vigiles` up the tree — bounded by
79
+ * the repository root, because a committed link that leaves the repository is a
80
+ * link only this machine can follow.
81
+ */
82
+ export declare function locatePackage(project: string, probe: PackageProbe): PackageSite;
83
+ /**
84
+ * Is the real path `path` the project's own real path or somewhere under it?
85
+ * Both must be physical (symlink-free), or a link could pass for a child.
86
+ */
87
+ export declare function isWithinProject(project: string, path: string): boolean;
88
+ /** The facts that decide whether linking makes sense at all here. */
89
+ export interface LinkFacts {
90
+ /** The project IS vigiles — its skills are the plugin's own `skills/`. */
91
+ readonly isVigilesItself: boolean;
92
+ /** `package.json` declares vigiles (so `npm install` will put it in node_modules). */
93
+ readonly dependsOnVigiles: boolean;
94
+ /** The package already resolves from the project. */
95
+ readonly installed: boolean;
96
+ }
97
+ /**
98
+ * Why NOT to link, or null to go ahead. A link into a `node_modules/vigiles`
99
+ * that no install will ever create is a dangling link committed to the repo — a
100
+ * worse state than no link, because it looks like the job was done.
101
+ */
102
+ export declare function linkPrecondition(facts: LinkFacts): string | null;
103
+ /** Everything {@link planSkillLinks} needs, observed by the IO edge. */
104
+ export interface SkillLinkInput {
105
+ /** The skills the package ships. */
106
+ readonly names: readonly string[];
107
+ readonly site: Exclude<PackageSite, {
108
+ kind: "unsure";
109
+ }>;
110
+ /**
111
+ * The skills home as it PHYSICALLY exists (`.claude` may itself be a link).
112
+ * A relative link target resolves against the directory that holds the link.
113
+ */
114
+ readonly physicalHome: string;
115
+ readonly entries: (name: string) => SkillEntry;
116
+ /** The real path of the installed skill, or null. */
117
+ readonly realOf: (name: string) => string | null;
118
+ }
119
+ /** The decisions, and whether the links will dangle until `npm install`. */
120
+ export interface SkillLinkPlan {
121
+ readonly pending: boolean;
122
+ readonly decisions: readonly SkillLinkDecision[];
123
+ }
124
+ /**
125
+ * A link target as it is committed: `/` separators on every OS. `relative()` on
126
+ * Windows yields `..\\..\\node_modules\\…`, and a clone on Linux or macOS
127
+ * reads those backslashes as ordinary filename characters, so the link dangles.
128
+ */
129
+ export declare function portableTarget(target: string, separator?: string): string;
130
+ export declare function planSkillLinks(input: SkillLinkInput): SkillLinkPlan;
131
+ /** The three filesystem calls that write a link; the IO edge passes the real ones. */
132
+ export interface LinkIo {
133
+ readonly symlink: (target: string, path: string) => void;
134
+ readonly rename: (from: string, to: string) => void;
135
+ readonly unlink: (path: string) => void;
136
+ }
137
+ /** Where a replacement link is built before it takes the old one's place. */
138
+ export declare function relinkTempPath(entry: string): string;
139
+ /**
140
+ * Write the link `decision` asks for. A `replace` never leaves a gap: the new
141
+ * link is built under a temporary name beside the old one and renamed over it,
142
+ * which is atomic. Removing the old link first would lose it for good when the
143
+ * new one cannot be created (a Windows account without the symlink privilege,
144
+ * a full disk) — a skill that worked yesterday gone, and only a warning to show
145
+ * for it. Any failure leaves the old link in place and throws.
146
+ */
147
+ export declare function writeSkillLink(io: LinkIo, entry: string, target: string, action: "create" | "replace"): void;
148
+ /** Why a `symlink` call failed, with the fix when there is a known one. */
149
+ export declare function linkFailureReason(code: string, message: string): string;
150
+ /** One skill's result after the IO ran. */
151
+ export type SkillLinkResult = {
152
+ readonly name: string;
153
+ readonly status: "created" | "relinked" | "present";
154
+ } | {
155
+ readonly name: string;
156
+ readonly status: "skipped";
157
+ readonly reason: string;
158
+ };
159
+ /** What `init` did about the skills for one harness. */
160
+ export type SkillLinkOutcome = {
161
+ readonly kind: "linked";
162
+ /** Repo-relative skills home, e.g. `.claude/skills`. */
163
+ readonly home: string;
164
+ /**
165
+ * The same home with every symlink resolved, repo-relative with `/`.
166
+ * Git refuses a pathspec that runs through a symlink, so the commit hint
167
+ * names this one.
168
+ */
169
+ readonly physicalHome: string;
170
+ readonly pending: boolean;
171
+ readonly results: readonly SkillLinkResult[];
172
+ } | {
173
+ readonly kind: "not-linked";
174
+ readonly reason: string;
175
+ };
176
+ /** At least one shipped skill now reaches the agent through a link. */
177
+ export declare function skillLinksUsable(outcome: SkillLinkOutcome): boolean;
178
+ /**
179
+ * The lines `init` prints. Skipped skills are named one by one: the user loses
180
+ * that skill, and the reason is theirs to act on.
181
+ */
182
+ export declare function formatSkillLinks(outcome: SkillLinkOutcome): readonly string[];
183
+ //# sourceMappingURL=skill-links.d.ts.map