@getbourdon/participants 0.5.6 → 0.6.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.
@@ -15,8 +15,9 @@
15
15
  * federation. Entities are deduped by case-insensitive name and sorted
16
16
  * alphabetically.
17
17
  */
18
- import { type L5ManifestModel, type SessionModel } from "@getbourdon/l5";
18
+ import { type L5ManifestModel, type SessionModel, type SkillRefModel } from "@getbourdon/l5";
19
19
  import { type AgentStore, type BourdonParticipant, type HealthStatus } from "../base.js";
20
+ import { type SkillScanReport, type SkillSource } from "../skills.js";
20
21
  /** Directory names for the knowledge repo. Only these are scanned. */
21
22
  export declare const BRAIN_DIR_NAMES: readonly [string];
22
23
  /** Env override pointing at the knowledge repo explicitly. */
@@ -32,7 +33,25 @@ export declare class ClaudeCodeParticipant implements BourdonParticipant {
32
33
  autoMemoryPath: string | null;
33
34
  knowledgeGraphPath: string | null;
34
35
  static defaultNativePath(home?: string): string;
35
- constructor();
36
+ /**
37
+ * Explicit skill directories; `null` = the defaults (see skills.ts), read
38
+ * only for shared skills unless `BOURDON_SKILLS_ADVERTISE` lists all.
39
+ * Explicit sources list every skill in them.
40
+ */
41
+ private readonly explicitSkillSources;
42
+ constructor(options?: {
43
+ skillSources?: readonly SkillSource[] | null;
44
+ });
45
+ /**
46
+ * `~/.claude/skills` (or the config-dir override) and the cross-agent store,
47
+ * plus the opted-in projects' `.claude/skills` and `.agents/skills`.
48
+ */
49
+ /** Built with explicit skill sources: this participant lists all of them. */
50
+ hasExplicitSkillSources(): boolean;
51
+ skillSources(): SkillSource[];
52
+ /** Advertised skills plus the skills that could not be advertised. */
53
+ scanSkills(): SkillScanReport;
54
+ exportSkills(): SkillRefModel[];
36
55
  discover(): AgentStore;
37
56
  exportL5(since?: Date): L5ManifestModel;
38
57
  exportSessions(since?: Date, limit?: number): SessionModel[];
@@ -23,6 +23,7 @@ import { parse as yamlParse } from "yaml";
23
23
  import { Visibility, filterForFederation, makeAgentInfo, makeEntity, makeManifest, makeSession, makeVisibilityPolicy, } from "@getbourdon/l5";
24
24
  import { containsSecret } from "@getbourdon/redaction";
25
25
  import { ParticipantDiscoveryError, SPEC_VERSION, } from "../base.js";
26
+ import { advertisedSkills, claudeCodeSkillSources, crossAgentSkillSources, scanSkillSources, } from "../skills.js";
26
27
  const AGENT_ID = "claude-code";
27
28
  const AGENT_TYPE = "code-assistant";
28
29
  const ROLE_NARRATIVE = "Agentic manager and code-assistant. Coordinates the agent fleet, " +
@@ -478,11 +479,39 @@ export class ClaudeCodeParticipant {
478
479
  static defaultNativePath(home) {
479
480
  return join(home ?? homeDir(), ".claude");
480
481
  }
481
- constructor() {
482
+ /**
483
+ * Explicit skill directories; `null` = the defaults (see skills.ts), read
484
+ * only for shared skills unless `BOURDON_SKILLS_ADVERTISE` lists all.
485
+ * Explicit sources list every skill in them.
486
+ */
487
+ explicitSkillSources;
488
+ constructor(options = {}) {
482
489
  this.nativePath = join(homeDir(), BRAIN_DIR_NAMES[0]); // primary anchor
483
490
  this.brainPath = resolveBrainPath();
484
491
  this.autoMemoryPath = resolveAutoMemoryPath();
485
492
  this.knowledgeGraphPath = resolveKnowledgeGraphPath();
493
+ this.explicitSkillSources = options.skillSources == null ? null : [...options.skillSources];
494
+ }
495
+ // -- Skills (plan 005: discovery only) -----------------------------------------
496
+ /**
497
+ * `~/.claude/skills` (or the config-dir override) and the cross-agent store,
498
+ * plus the opted-in projects' `.claude/skills` and `.agents/skills`.
499
+ */
500
+ /** Built with explicit skill sources: this participant lists all of them. */
501
+ hasExplicitSkillSources() {
502
+ return this.explicitSkillSources !== null;
503
+ }
504
+ skillSources() {
505
+ if (this.explicitSkillSources !== null)
506
+ return [...this.explicitSkillSources];
507
+ return [...claudeCodeSkillSources(), ...crossAgentSkillSources()];
508
+ }
509
+ /** Advertised skills plus the skills that could not be advertised. */
510
+ scanSkills() {
511
+ return scanSkillSources(this.skillSources());
512
+ }
513
+ exportSkills() {
514
+ return this.scanSkills().refs;
486
515
  }
487
516
  discover() {
488
517
  const sources = {
@@ -531,6 +560,7 @@ export class ClaudeCodeParticipant {
531
560
  recent_sessions: sessions,
532
561
  known_entities: visibleEntities,
533
562
  visibility_policy: DEFAULT_POLICY,
563
+ skills: advertisedSkills(this),
534
564
  });
535
565
  }
536
566
  exportSessions(since, limit = 100) {
@@ -18,8 +18,9 @@
18
18
  * deterministic, visibility-enforced-before-emission, redaction on every native
19
19
  * string; `healthCheck` never throws.
20
20
  */
21
- import { type L5ManifestModel, type SessionModel } from "@getbourdon/l5";
21
+ import { type L5ManifestModel, type SessionModel, type SkillRefModel } from "@getbourdon/l5";
22
22
  import { type AgentStore, type BourdonParticipant, type HealthStatus } from "../base.js";
23
+ import { type SkillScanReport, type SkillSource } from "../skills.js";
23
24
  /**
24
25
  * Platform-specific Cursor data dir (the SQLite workspace state lives here),
25
26
  * `$CURSOR_DIR` override first. Returns null on an unrecognized platform (or
@@ -40,7 +41,27 @@ export declare class CursorParticipant implements BourdonParticipant {
40
41
  * extraction intentionally use different anchors.
41
42
  */
42
43
  static defaultNativePath(home?: string): string;
43
- constructor(cursorDir?: string | null, workspaceRoot?: string | null);
44
+ /**
45
+ * Explicit skill directories; `null` = the defaults (see skills.ts), read
46
+ * only for shared skills unless `BOURDON_SKILLS_ADVERTISE` lists all.
47
+ * Explicit sources list every skill in them.
48
+ * `workspaceRoot` does not opt a project in: that takes an explicit
49
+ * `BOURDON_SKILL_PROJECTS` entry, or explicit sources.
50
+ */
51
+ private readonly explicitSkillSources;
52
+ constructor(cursorDir?: string | null, workspaceRoot?: string | null, options?: {
53
+ skillSources?: readonly SkillSource[] | null;
54
+ });
55
+ /**
56
+ * `~/.cursor/skills` (not the built-in `skills-cursor`) and the cross-agent
57
+ * store, plus the opted-in projects' `.cursor/skills` and `.agents/skills`.
58
+ */
59
+ /** Built with explicit skill sources: this participant lists all of them. */
60
+ hasExplicitSkillSources(): boolean;
61
+ skillSources(): SkillSource[];
62
+ /** Advertised skills plus the skills that could not be advertised. */
63
+ scanSkills(): SkillScanReport;
64
+ exportSkills(): SkillRefModel[];
44
65
  get nativePath(): string;
45
66
  discover(): AgentStore;
46
67
  exportSessions(since?: Date, limit?: number): SessionModel[];
@@ -25,6 +25,7 @@ import { basename, join } from "node:path";
25
25
  import { Visibility, filterForFederation, makeAgentInfo, makeEntity, makeManifest, makeSession, makeVisibilityPolicy, } from "@getbourdon/l5";
26
26
  import { redactText } from "@getbourdon/redaction";
27
27
  import { ParticipantDiscoveryError, SPEC_VERSION, } from "../base.js";
28
+ import { advertisedSkills, crossAgentSkillSources, cursorSkillSources, scanSkillSources, } from "../skills.js";
28
29
  import { tableExists, tryOpenReadonly } from "../sqlite-base.js";
29
30
  const AGENT_ID = "cursor";
30
31
  const AGENT_TYPE = "code-assistant";
@@ -529,9 +530,39 @@ export class CursorParticipant {
529
530
  static defaultNativePath(home) {
530
531
  return join(home ?? homeDir(), ".cursor");
531
532
  }
532
- constructor(cursorDir, workspaceRoot) {
533
+ /**
534
+ * Explicit skill directories; `null` = the defaults (see skills.ts), read
535
+ * only for shared skills unless `BOURDON_SKILLS_ADVERTISE` lists all.
536
+ * Explicit sources list every skill in them.
537
+ * `workspaceRoot` does not opt a project in: that takes an explicit
538
+ * `BOURDON_SKILL_PROJECTS` entry, or explicit sources.
539
+ */
540
+ explicitSkillSources;
541
+ constructor(cursorDir, workspaceRoot, options = {}) {
533
542
  this.cursorDir = cursorDir ?? null;
534
543
  this.workspaceRoot = workspaceRoot ?? null;
544
+ this.explicitSkillSources = options.skillSources == null ? null : [...options.skillSources];
545
+ }
546
+ // -- Skills (plan 005: discovery only) -----------------------------------------
547
+ /**
548
+ * `~/.cursor/skills` (not the built-in `skills-cursor`) and the cross-agent
549
+ * store, plus the opted-in projects' `.cursor/skills` and `.agents/skills`.
550
+ */
551
+ /** Built with explicit skill sources: this participant lists all of them. */
552
+ hasExplicitSkillSources() {
553
+ return this.explicitSkillSources !== null;
554
+ }
555
+ skillSources() {
556
+ if (this.explicitSkillSources !== null)
557
+ return [...this.explicitSkillSources];
558
+ return [...cursorSkillSources(), ...crossAgentSkillSources()];
559
+ }
560
+ /** Advertised skills plus the skills that could not be advertised. */
561
+ scanSkills() {
562
+ return scanSkillSources(this.skillSources());
563
+ }
564
+ exportSkills() {
565
+ return this.scanSkills().refs;
535
566
  }
536
567
  get nativePath() {
537
568
  const path = this.cursorDir ?? defaultCursorDir();
@@ -609,6 +640,7 @@ export class CursorParticipant {
609
640
  recent_sessions: sessions,
610
641
  known_entities: visibleEntities,
611
642
  visibility_policy: this.policy,
643
+ skills: advertisedSkills(this),
612
644
  });
613
645
  }
614
646
  healthCheck() {
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Reader-side skill folder scan (plan 005). Port of the parts of the engine's
3
+ * `core/skill_index.py` a participant needs to advertise a local skill: the
4
+ * text-only content rules, the no-follow folder walk, front-matter parsing,
5
+ * the `content_sha256` framing and `scan_local_skill` (skip and flag).
6
+ *
7
+ * `readSkillFolder` is the promotion-side twin of `scanLocalSkill`: it refuses
8
+ * a folder that breaks any rule instead of skipping files. The library-level
9
+ * shared store (index rows, versions, promotion) is in `skill-store.ts`. Both
10
+ * use the same limits and name rule, so a hash computed here compares
11
+ * directly with a shared skill's.
12
+ *
13
+ * Rules a v1 shared skill must meet (anything else is skipped and flagged):
14
+ * - `SKILL.md` plus markdown, plain text and template files only;
15
+ * - no links, no hidden entries, bounded depth and path length, no paths that
16
+ * differ only in case;
17
+ * - UTF-8 text without NUL bytes, no interpreter line, no credential-like text;
18
+ * - per-file and total size caps, at most {@link MAX_REFERENCE_FILES} reference files.
19
+ */
20
+ import { type Stats } from "node:fs";
21
+ export declare const SKILL_BODY_NAME = "SKILL.md";
22
+ /** Reference-file types a v1 shared skill may carry: markdown, plain text and templates. */
23
+ export declare const TEXT_FILE_SUFFIXES: ReadonlySet<string>;
24
+ export declare const MAX_SKILL_BODY_BYTES: number;
25
+ export declare const MAX_REFERENCE_FILE_BYTES: number;
26
+ export declare const MAX_REFERENCE_FILES = 64;
27
+ export declare const MAX_SKILL_TOTAL_BYTES: number;
28
+ /**
29
+ * Why a reader left a file out of an advertised skill. `limit` means the
30
+ * file-count or total-size cap was reached; every other code names the content
31
+ * rule the file breaks.
32
+ */
33
+ export declare const SKIP_REASONS: ReadonlySet<string>;
34
+ /** Longest skill name, in code points. */
35
+ export declare const SKILL_NAME_MAX: number;
36
+ /** Longest front-matter description, in code points. */
37
+ export declare const SKILL_DESCRIPTION_MAX: number;
38
+ /**
39
+ * A skill folder cannot be advertised (or shared) as it stands. `problems`
40
+ * lists every offending path with the reason; content is never echoed back.
41
+ */
42
+ export declare class SkillContentError extends Error {
43
+ readonly problems: string[];
44
+ constructor(problems: string[]);
45
+ }
46
+ /** Python `str.strip()` with no argument. */
47
+ export declare function pyStrip(text: string): string;
48
+ /** Length in code points, as Python's `len(str)`. */
49
+ export declare function codePointLength(text: string): number;
50
+ /** The first `limit` code points, as Python's `text[:limit]`. */
51
+ export declare function codePointSlice(text: string, limit: number): string;
52
+ /** Code-point order (Python's str order), via the UTF-8 bytes. */
53
+ export declare function compareUtf8(a: string, b: string): number;
54
+ /** Whether `path` is a symbolic link (on Windows, a junction or symlink reparse point). */
55
+ export declare function isPathLinkOrReparse(path: string, stats?: Stats): boolean;
56
+ export interface SkillFrontMatter {
57
+ name: string;
58
+ description: string;
59
+ }
60
+ /** Why `value` is not a valid skill name (`label` names the field), or null. */
61
+ export declare function skillNameError(value: unknown, label?: string): string | null;
62
+ /** Extract `name` and `description` from a `SKILL.md` body. Throws {@link SkillContentError}. */
63
+ export declare function parseFrontMatter(body: Uint8Array | string): SkillFrontMatter;
64
+ /**
65
+ * Hash a skill's files as the schema's `content_sha256` defines: entries sorted
66
+ * by the UTF-8 bytes of their POSIX path relative to the skill folder, each
67
+ * framed as `path \0 len(bytes) \0 bytes`.
68
+ */
69
+ export declare function computeContentSha256(entries: Iterable<readonly [string, Uint8Array]>): string;
70
+ /** One file a reader left out of a local skill, and why (a {@link SKIP_REASONS} code). */
71
+ export interface SkippedFile {
72
+ path: string;
73
+ reason: string;
74
+ }
75
+ /**
76
+ * What a participant reader can advertise about one local skill folder. `files`
77
+ * is the shareable part (`SKILL.md` plus every reference file that passes the
78
+ * v1 content rules); `skipped` lists the rest. The hash covers `files` only.
79
+ */
80
+ export interface LocalSkillScan {
81
+ frontMatter: SkillFrontMatter;
82
+ files: ReadonlyArray<readonly [string, Uint8Array]>;
83
+ skipped: readonly SkippedFile[];
84
+ contentSha256: string;
85
+ }
86
+ /**
87
+ * Scan one local skill folder for discovery: skip and flag, don't refuse.
88
+ *
89
+ * A disallowed reference file (a script, a binary, a link, a hidden file,
90
+ * credential-like text) is left out and reported in `skipped` instead of making
91
+ * the whole skill disappear. Only a `SKILL.md` that is missing, unreadable or
92
+ * itself breaks a content rule throws {@link SkillContentError}, because then
93
+ * there is nothing safe to advertise. Nothing is followed through a link and
94
+ * nothing is modified.
95
+ */
96
+ export declare function scanLocalSkill(folder: string): LocalSkillScan;
97
+ /** A validated skill: front matter plus every file, `SKILL.md` included, sorted by path. */
98
+ export interface SkillContent {
99
+ frontMatter: SkillFrontMatter;
100
+ files: ReadonlyArray<readonly [string, Uint8Array]>;
101
+ }
102
+ /** The `SKILL.md` bytes of `content`. */
103
+ export declare function skillContentBody(content: SkillContent): Uint8Array;
104
+ /**
105
+ * Read a skill folder and enforce every v1 content rule (the oracle's
106
+ * `read_skill_folder`). Throws {@link SkillContentError} listing every problem
107
+ * found. The folder is read in place and never modified; links are reported,
108
+ * never followed.
109
+ */
110
+ export declare function readSkillFolder(folder: string): SkillContent;