@compilr-dev/sdk 0.10.1 → 0.10.3

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.
package/dist/index.d.ts CHANGED
@@ -67,8 +67,9 @@ export type { GuideEntry, ContentTopic, ContentSection, GuideToolConfig } from '
67
67
  export { createPlatformTools, createProjectTools, createWorkItemTools, createDocumentTools, createPlanTools, createBacklogTools, createAnchorTools, createArtifactTools, createEpisodeTools, createImageTools, ProjectAnchorStore, } from './platform/index.js';
68
68
  export type { ProjectAnchorStoreConfig, ImageToolsConfig, ImageResizer } from './platform/index.js';
69
69
  export { STEP_ORDER, GUIDED_STEP_CRITERIA, getNextStep, isValidTransition, getStepCriteria, formatStepDisplay, getStepNumber, } from './platform/index.js';
70
- export type { CustomSkill, CompilrSkillExtension, ForkedFromMarker, SkillEligibilityContext, } from './skills/index.js';
71
- export { RESERVED_SKILL_NAMES, isReservedSkillName, parseSkillMarkdown, loadSkillsFromDir, resolveLayeredSkills, resolveSkillsForAgent, } from './skills/index.js';
70
+ export type { CustomSkill, CompilrSkillExtension, ForkedFromMarker, SkillEligibilityContext, SkillCollision, SkillDiffLine, SkillValidationIssue, ScopeConfig, SkillResolution, } from './skills/index.js';
71
+ export { RESERVED_SKILL_NAMES, isReservedSkillName, parseSkillMarkdown, loadSkillsFromDir, resolveLayeredSkills, resolveSkillsForAgent, detectCollisions, formatCollisionWarnings, diffForkVsUpstream, buildForkContent, buildNewSkillContent, validateSkill as validateSkillQuality, getSkillsDir, getSkillFolder, getSkillFile, ensureSkillsDir, isValidSkillName, getScopeConfigPath, readSkillScopeConfig, readSkillScopeConfigSync, writeSkillScopeConfig, getSkillBindings, resolveSkillBinding, } from './skills/index.js';
72
+ export type { SkillScope } from './skills/index.js';
72
73
  export { platformSkills, designSkill, sketchSkill, prdSkill, refineSkill, refineItemSkill, architectureSkill, sessionNotesSkill, buildSkill, scaffoldSkill, outlineSkill, literatureReviewSkill, draftSectionSkill, peerReviewSkill, researchScaffoldSkill, businessVisionSkill, marketAnalysisSkill, competitorAnalysisSkill, financialModelSkill, pitchOutlineSkill, businessReviewSkill, brandSetupSkill, contentStrategySkill, contentCalendarSkill, createContentSkill, contentReviewSkill, curriculumDesignSkill, lessonPlanSkill, assessmentDesignSkill, courseReviewSkill, bookOutlineSkill, characterDesignSkill, plotThreadsSkill, sceneBreakdownSkill, bookReviewSkill, } from './skills/index.js';
73
74
  export { ACTION_REGISTRY, getActionsForContext, getActionById, resolveActionPrompt, buildContextSummary, getSuggestedRole, } from './actions/index.js';
74
75
  export type { ActionContext, ActionDefinition } from './actions/index.js';
package/dist/index.js CHANGED
@@ -152,7 +152,7 @@ export { createPlatformTools, createProjectTools, createWorkItemTools, createDoc
152
152
  // Platform Workflow (pure step-criteria logic)
153
153
  // =============================================================================
154
154
  export { STEP_ORDER, GUIDED_STEP_CRITERIA, getNextStep, isValidTransition, getStepCriteria, formatStepDisplay, getStepNumber, } from './platform/index.js';
155
- export { RESERVED_SKILL_NAMES, isReservedSkillName, parseSkillMarkdown, loadSkillsFromDir, resolveLayeredSkills, resolveSkillsForAgent, } from './skills/index.js';
155
+ export { RESERVED_SKILL_NAMES, isReservedSkillName, parseSkillMarkdown, loadSkillsFromDir, resolveLayeredSkills, resolveSkillsForAgent, detectCollisions, formatCollisionWarnings, diffForkVsUpstream, buildForkContent, buildNewSkillContent, validateSkill as validateSkillQuality, getSkillsDir, getSkillFolder, getSkillFile, ensureSkillsDir, isValidSkillName, getScopeConfigPath, readSkillScopeConfig, readSkillScopeConfigSync, writeSkillScopeConfig, getSkillBindings, resolveSkillBinding, } from './skills/index.js';
156
156
  export { platformSkills, designSkill, sketchSkill, prdSkill, refineSkill, refineItemSkill, architectureSkill, sessionNotesSkill, buildSkill, scaffoldSkill, outlineSkill, literatureReviewSkill, draftSectionSkill, peerReviewSkill, researchScaffoldSkill, businessVisionSkill, marketAnalysisSkill, competitorAnalysisSkill, financialModelSkill, pitchOutlineSkill, businessReviewSkill, brandSetupSkill, contentStrategySkill, contentCalendarSkill, createContentSkill, contentReviewSkill, curriculumDesignSkill, lessonPlanSkill, assessmentDesignSkill, courseReviewSkill, bookOutlineSkill, characterDesignSkill, plotThreadsSkill, sceneBreakdownSkill, bookReviewSkill, } from './skills/index.js';
157
157
  // =============================================================================
158
158
  // Contextual Actions (skill invocations with context)
@@ -6,4 +6,8 @@ export { RESERVED_SKILL_NAMES, isReservedSkillName } from './types.js';
6
6
  export { parseSkillMarkdown, loadSkillsFromDir } from './loader.js';
7
7
  export type { SkillEligibilityContext } from './resolver.js';
8
8
  export { resolveLayeredSkills, resolveSkillsForAgent } from './resolver.js';
9
+ export type { SkillCollision, SkillDiffLine, SkillValidationIssue, ScopeConfig, SkillResolution, } from './operations.js';
10
+ export { detectCollisions, formatCollisionWarnings, diffForkVsUpstream, buildForkContent, buildNewSkillContent, validateSkill, } from './operations.js';
11
+ export type { SkillScope } from './paths.js';
12
+ export { getSkillsDir, getSkillFolder, getSkillFile, ensureSkillsDir, isValidSkillName, getScopeConfigPath, readScopeConfig as readSkillScopeConfig, readScopeConfigSync as readSkillScopeConfigSync, writeScopeConfig as writeSkillScopeConfig, getAllBindings as getSkillBindings, resolveBinding as resolveSkillBinding, } from './paths.js';
9
13
  export { platformSkills, designSkill, sketchSkill, prdSkill, refineSkill, refineItemSkill, architectureSkill, sessionNotesSkill, buildSkill, scaffoldSkill, outlineSkill, literatureReviewSkill, draftSectionSkill, peerReviewSkill, researchScaffoldSkill, businessVisionSkill, marketAnalysisSkill, competitorAnalysisSkill, financialModelSkill, pitchOutlineSkill, businessReviewSkill, brandSetupSkill, contentStrategySkill, contentCalendarSkill, createContentSkill, contentReviewSkill, curriculumDesignSkill, lessonPlanSkill, assessmentDesignSkill, courseReviewSkill, bookOutlineSkill, characterDesignSkill, plotThreadsSkill, sceneBreakdownSkill, bookReviewSkill, } from './platform-skills.js';
@@ -1,4 +1,6 @@
1
1
  export { RESERVED_SKILL_NAMES, isReservedSkillName } from './types.js';
2
2
  export { parseSkillMarkdown, loadSkillsFromDir } from './loader.js';
3
3
  export { resolveLayeredSkills, resolveSkillsForAgent } from './resolver.js';
4
+ export { detectCollisions, formatCollisionWarnings, diffForkVsUpstream, buildForkContent, buildNewSkillContent, validateSkill, } from './operations.js';
5
+ export { getSkillsDir, getSkillFolder, getSkillFile, ensureSkillsDir, isValidSkillName, getScopeConfigPath, readScopeConfig as readSkillScopeConfig, readScopeConfigSync as readSkillScopeConfigSync, writeScopeConfig as writeSkillScopeConfig, getAllBindings as getSkillBindings, resolveBinding as resolveSkillBinding, } from './paths.js';
4
6
  export { platformSkills, designSkill, sketchSkill, prdSkill, refineSkill, refineItemSkill, architectureSkill, sessionNotesSkill, buildSkill, scaffoldSkill, outlineSkill, literatureReviewSkill, draftSectionSkill, peerReviewSkill, researchScaffoldSkill, businessVisionSkill, marketAnalysisSkill, competitorAnalysisSkill, financialModelSkill, pitchOutlineSkill, businessReviewSkill, brandSetupSkill, contentStrategySkill, contentCalendarSkill, createContentSkill, contentReviewSkill, curriculumDesignSkill, lessonPlanSkill, assessmentDesignSkill, courseReviewSkill, bookOutlineSkill, characterDesignSkill, plotThreadsSkill, sceneBreakdownSkill, bookReviewSkill, } from './platform-skills.js';
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Skill operations — reusable logic for CLI and Desktop.
3
+ *
4
+ * - Collision detection (user skills vs SDK reserved names)
5
+ * - Diff (fork vs upstream SDK version)
6
+ * - Fork scaffold (build SKILL.md with forkedFrom marker)
7
+ * - Validation (quality checks beyond parseSkillMarkdown)
8
+ * - Binding resolution (config.json slash command remapping)
9
+ */
10
+ import type { CustomSkill, ForkedFromMarker } from './types.js';
11
+ export interface SkillCollision {
12
+ /** The colliding skill name */
13
+ name: string;
14
+ /** Path to the user's skill folder */
15
+ userPath: string;
16
+ /** Scope where the user skill lives */
17
+ scope: 'user' | 'project';
18
+ /** SDK package that owns the reserved name */
19
+ sdkSource: string;
20
+ }
21
+ /**
22
+ * Detect user/project skills that shadow SDK reserved names.
23
+ * Called at startup to warn users about collisions introduced by SDK updates.
24
+ *
25
+ * Note: Phase 1 blocks creation of reserved names via `/skill new`, but
26
+ * collisions can still happen if the SDK adds new skills after the user
27
+ * already has a skill with that name.
28
+ */
29
+ export declare function detectCollisions(userSkills: {
30
+ name: string;
31
+ scope: 'user' | 'project';
32
+ path: string;
33
+ }[]): SkillCollision[];
34
+ /**
35
+ * Format collision warnings for display.
36
+ */
37
+ export declare function formatCollisionWarnings(collisions: SkillCollision[]): string;
38
+ export interface SkillDiffLine {
39
+ type: 'same' | 'added' | 'removed';
40
+ content: string;
41
+ }
42
+ /**
43
+ * Compute a simple line-by-line diff between a forked skill and the
44
+ * upstream SDK skill it was forked from.
45
+ *
46
+ * Returns null if the upstream skill is not found.
47
+ */
48
+ export declare function diffForkVsUpstream(forkPrompt: string, upstreamSkillName: string): SkillDiffLine[] | null;
49
+ /**
50
+ * Build the SKILL.md content for a forked SDK skill.
51
+ */
52
+ export declare function buildForkContent(sdkSkill: {
53
+ name: string;
54
+ description: string;
55
+ prompt: string;
56
+ }, newName: string, sdkVersion: string): string;
57
+ /**
58
+ * Build the SKILL.md template for a new custom skill.
59
+ */
60
+ export declare function buildNewSkillContent(name: string, scope: string): string;
61
+ export interface SkillValidationIssue {
62
+ level: 'error' | 'warning';
63
+ message: string;
64
+ }
65
+ /**
66
+ * Validate a parsed custom skill beyond basic frontmatter parsing.
67
+ */
68
+ export declare function validateSkill(skill: CustomSkill, folderName: string): SkillValidationIssue[];
69
+ export interface ScopeConfig {
70
+ slashCommands?: Record<string, string>;
71
+ }
72
+ export interface SkillResolution {
73
+ /** The resolved skill prompt */
74
+ prompt: string;
75
+ /** Original command name the user typed */
76
+ commandName: string;
77
+ /** Actual skill name that provided the prompt (may differ if bound) */
78
+ skillName: string;
79
+ /** Whether this was resolved via a binding */
80
+ isBound: boolean;
81
+ /** Fork info if the skill is forked */
82
+ forkedFrom?: ForkedFromMarker;
83
+ /** Source layer */
84
+ source?: CustomSkill['source'];
85
+ }
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Skill operations — reusable logic for CLI and Desktop.
3
+ *
4
+ * - Collision detection (user skills vs SDK reserved names)
5
+ * - Diff (fork vs upstream SDK version)
6
+ * - Fork scaffold (build SKILL.md with forkedFrom marker)
7
+ * - Validation (quality checks beyond parseSkillMarkdown)
8
+ * - Binding resolution (config.json slash command remapping)
9
+ */
10
+ import { RESERVED_SKILL_NAMES } from './types.js';
11
+ import { platformSkills } from './platform-skills.js';
12
+ /**
13
+ * Detect user/project skills that shadow SDK reserved names.
14
+ * Called at startup to warn users about collisions introduced by SDK updates.
15
+ *
16
+ * Note: Phase 1 blocks creation of reserved names via `/skill new`, but
17
+ * collisions can still happen if the SDK adds new skills after the user
18
+ * already has a skill with that name.
19
+ */
20
+ export function detectCollisions(userSkills) {
21
+ const reserved = new Set(RESERVED_SKILL_NAMES);
22
+ const collisions = [];
23
+ for (const skill of userSkills) {
24
+ if (reserved.has(skill.name)) {
25
+ collisions.push({
26
+ name: skill.name,
27
+ userPath: skill.path,
28
+ scope: skill.scope,
29
+ sdkSource: '@compilr-dev/sdk',
30
+ });
31
+ }
32
+ }
33
+ return collisions;
34
+ }
35
+ /**
36
+ * Format collision warnings for display.
37
+ */
38
+ export function formatCollisionWarnings(collisions) {
39
+ if (collisions.length === 0)
40
+ return '';
41
+ const lines = [`⚠️ SDK update introduced new skills that collide with yours:`];
42
+ for (const c of collisions) {
43
+ lines.push(` '${c.name}' — your version: ${c.userPath} (${c.scope} scope)`, ` SDK version: ${c.sdkSource}`);
44
+ }
45
+ lines.push('', ' Run /skill to view details.', ' Options: rename your skill, delete it, or fork the SDK version.');
46
+ return lines.join('\n');
47
+ }
48
+ /**
49
+ * Compute a simple line-by-line diff between a forked skill and the
50
+ * upstream SDK skill it was forked from.
51
+ *
52
+ * Returns null if the upstream skill is not found.
53
+ */
54
+ export function diffForkVsUpstream(forkPrompt, upstreamSkillName) {
55
+ // Find the upstream SDK skill
56
+ // Check all platform skills + builtins
57
+ const allSdk = [...platformSkills];
58
+ const upstream = allSdk.find((s) => s.name === upstreamSkillName);
59
+ if (!upstream)
60
+ return null;
61
+ const upstreamLines = upstream.prompt.split('\n');
62
+ const forkLines = forkPrompt.split('\n');
63
+ return computeLineDiff(upstreamLines, forkLines);
64
+ }
65
+ /**
66
+ * Simple line diff (not a full Myers diff — sufficient for skill comparison).
67
+ * Uses longest common subsequence for reasonable output.
68
+ */
69
+ function computeLineDiff(oldLines, newLines) {
70
+ const result = [];
71
+ let oi = 0;
72
+ let ni = 0;
73
+ while (oi < oldLines.length && ni < newLines.length) {
74
+ if (oldLines[oi] === newLines[ni]) {
75
+ result.push({ type: 'same', content: oldLines[oi] });
76
+ oi++;
77
+ ni++;
78
+ }
79
+ else {
80
+ // Look ahead for the next matching line
81
+ const matchInNew = newLines.indexOf(oldLines[oi], ni);
82
+ const matchInOld = oldLines.indexOf(newLines[ni], oi);
83
+ if (matchInNew >= 0 && (matchInOld < 0 || matchInNew - ni <= matchInOld - oi)) {
84
+ // Lines added in new
85
+ while (ni < matchInNew) {
86
+ result.push({ type: 'added', content: newLines[ni] });
87
+ ni++;
88
+ }
89
+ }
90
+ else if (matchInOld >= 0) {
91
+ // Lines removed from old
92
+ while (oi < matchInOld) {
93
+ result.push({ type: 'removed', content: oldLines[oi] });
94
+ oi++;
95
+ }
96
+ }
97
+ else {
98
+ // No match found — treat as replace
99
+ result.push({ type: 'removed', content: oldLines[oi] });
100
+ result.push({ type: 'added', content: newLines[ni] });
101
+ oi++;
102
+ ni++;
103
+ }
104
+ }
105
+ }
106
+ // Remaining old lines
107
+ while (oi < oldLines.length) {
108
+ result.push({ type: 'removed', content: oldLines[oi] });
109
+ oi++;
110
+ }
111
+ // Remaining new lines
112
+ while (ni < newLines.length) {
113
+ result.push({ type: 'added', content: newLines[ni] });
114
+ ni++;
115
+ }
116
+ return result;
117
+ }
118
+ // =============================================================================
119
+ // Fork Scaffold
120
+ // =============================================================================
121
+ /**
122
+ * Build the SKILL.md content for a forked SDK skill.
123
+ */
124
+ export function buildForkContent(sdkSkill, newName, sdkVersion) {
125
+ const forkedFrom = {
126
+ source: '@compilr-dev/sdk',
127
+ skill: sdkSkill.name,
128
+ version: sdkVersion,
129
+ forkedAt: new Date().toISOString(),
130
+ };
131
+ const descLines = sdkSkill.description
132
+ .split('\n')
133
+ .map((l) => ` ${l}`)
134
+ .join('\n');
135
+ const fmLines = [
136
+ `name: ${newName}`,
137
+ 'description: |',
138
+ descLines,
139
+ 'version: 0.1.0',
140
+ 'forkedFrom:',
141
+ ` source: "${forkedFrom.source}"`,
142
+ ` skill: "${forkedFrom.skill}"`,
143
+ ` version: "${forkedFrom.version}"`,
144
+ ` forkedAt: "${forkedFrom.forkedAt}"`,
145
+ ];
146
+ return ['---', ...fmLines, '---', '', sdkSkill.prompt].join('\n');
147
+ }
148
+ // =============================================================================
149
+ // New Skill Scaffold
150
+ // =============================================================================
151
+ /**
152
+ * Build the SKILL.md template for a new custom skill.
153
+ */
154
+ export function buildNewSkillContent(name, scope) {
155
+ return `---
156
+ name: ${name}
157
+ description: |
158
+ Use this skill when … (replace with a paragraph-long description that names
159
+ the trigger conditions, the kinds of phrases the user will say, and the
160
+ expected output. Anthropic-style descriptions are keyword-dense and
161
+ intent-rich because the model reads them to decide when to load the skill.)
162
+ version: 0.1.0
163
+
164
+ # Optional — compilr extensions (ignored by Anthropic-compatible runtimes).
165
+ # Uncomment and edit to limit which agents and project types this skill applies to.
166
+ # compilr:
167
+ # targets:
168
+ # roles: [arch, qa]
169
+ # requires:
170
+ # tools: []
171
+ # projectTypes: []
172
+ # scope: ${scope}
173
+ ---
174
+
175
+ # ${name}
176
+
177
+ (Body markdown — the prompt the agent will receive when this skill activates.)
178
+
179
+ ## Step 1
180
+ Describe the first step.
181
+
182
+ ## Step 2
183
+
184
+ `;
185
+ }
186
+ /**
187
+ * Validate a parsed custom skill beyond basic frontmatter parsing.
188
+ */
189
+ export function validateSkill(skill, folderName) {
190
+ const issues = [];
191
+ if (skill.name !== folderName) {
192
+ issues.push({
193
+ level: 'error',
194
+ message: `Frontmatter name '${skill.name}' doesn't match folder '${folderName}'.`,
195
+ });
196
+ }
197
+ if (skill.description.length < 60) {
198
+ issues.push({
199
+ level: 'warning',
200
+ message: `Description is ${String(skill.description.length)} chars (recommend 60+).`,
201
+ });
202
+ }
203
+ if (!skill.prompt || skill.prompt.trim().length === 0) {
204
+ issues.push({
205
+ level: 'warning',
206
+ message: 'Body is empty.',
207
+ });
208
+ }
209
+ return issues;
210
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Skill path resolution and config I/O.
3
+ *
4
+ * Portable across CLI and Desktop — takes explicit paths instead of
5
+ * relying on CLI-specific project detection.
6
+ */
7
+ export type SkillScope = 'user' | 'project';
8
+ /** Resolve the skills directory for a given scope. */
9
+ export declare function getSkillsDir(scope: SkillScope, projectDir?: string): string;
10
+ /** Ensure the skills directory exists. Returns the path. */
11
+ export declare function ensureSkillsDir(scope: SkillScope, projectDir?: string): Promise<string>;
12
+ /** Path to a single skill folder (no creation). */
13
+ export declare function getSkillFolder(name: string, scope: SkillScope, projectDir?: string): string;
14
+ /** Path to a skill's SKILL.md file. */
15
+ export declare function getSkillFile(name: string, scope: SkillScope, projectDir?: string): string;
16
+ /**
17
+ * Validate a skill name. Lowercase letters, digits, and hyphens only.
18
+ */
19
+ export declare function isValidSkillName(name: string): boolean;
20
+ /** Shape of <scope>/.compilr/config.json */
21
+ export interface ScopeConfig {
22
+ slashCommands?: Record<string, string>;
23
+ }
24
+ /** Path to the scope config file. */
25
+ export declare function getScopeConfigPath(scope: SkillScope, projectDir?: string): string;
26
+ /** Read the scope config asynchronously. Returns empty object if file doesn't exist. */
27
+ export declare function readScopeConfig(scope: SkillScope, projectDir?: string): Promise<ScopeConfig>;
28
+ /** Read the scope config synchronously. Returns empty object if file doesn't exist. */
29
+ export declare function readScopeConfigSync(scope: SkillScope, projectDir?: string): ScopeConfig;
30
+ /** Write the scope config. Creates parent directories if needed. */
31
+ export declare function writeScopeConfig(scope: SkillScope, config: ScopeConfig, projectDir?: string): Promise<void>;
32
+ /** Get all bindings across both scopes (project takes priority). */
33
+ export declare function getAllBindings(projectDir?: string): Promise<Record<string, {
34
+ skillName: string;
35
+ scope: SkillScope;
36
+ }>>;
37
+ /**
38
+ * Resolve a command name to a bound skill name via config.json.
39
+ * Synchronous — config files are tiny.
40
+ */
41
+ export declare function resolveBinding(commandName: string, projectDir?: string): string | null;
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Skill path resolution and config I/O.
3
+ *
4
+ * Portable across CLI and Desktop — takes explicit paths instead of
5
+ * relying on CLI-specific project detection.
6
+ */
7
+ import * as os from 'node:os';
8
+ import * as path from 'node:path';
9
+ import { promises as fs } from 'node:fs';
10
+ import { readFileSync } from 'node:fs';
11
+ // =============================================================================
12
+ // Path Resolution
13
+ // =============================================================================
14
+ /** Resolve the skills directory for a given scope. */
15
+ export function getSkillsDir(scope, projectDir) {
16
+ if (scope === 'user') {
17
+ return path.join(os.homedir(), '.compilr-dev', 'skills');
18
+ }
19
+ if (!projectDir)
20
+ throw new Error('projectDir required for project scope');
21
+ return path.join(projectDir, '.compilr', 'skills');
22
+ }
23
+ /** Ensure the skills directory exists. Returns the path. */
24
+ export async function ensureSkillsDir(scope, projectDir) {
25
+ const dir = getSkillsDir(scope, projectDir);
26
+ await fs.mkdir(dir, { recursive: true });
27
+ return dir;
28
+ }
29
+ /** Path to a single skill folder (no creation). */
30
+ export function getSkillFolder(name, scope, projectDir) {
31
+ return path.join(getSkillsDir(scope, projectDir), name);
32
+ }
33
+ /** Path to a skill's SKILL.md file. */
34
+ export function getSkillFile(name, scope, projectDir) {
35
+ return path.join(getSkillFolder(name, scope, projectDir), 'SKILL.md');
36
+ }
37
+ /**
38
+ * Validate a skill name. Lowercase letters, digits, and hyphens only.
39
+ */
40
+ export function isValidSkillName(name) {
41
+ if (!name || name.length === 0 || name.length > 64)
42
+ return false;
43
+ return /^[a-z][a-z0-9-]*$/.test(name);
44
+ }
45
+ /** Path to the scope config file. */
46
+ export function getScopeConfigPath(scope, projectDir) {
47
+ if (scope === 'user') {
48
+ return path.join(os.homedir(), '.compilr-dev', 'config.json');
49
+ }
50
+ if (!projectDir)
51
+ throw new Error('projectDir required for project scope');
52
+ return path.join(projectDir, '.compilr', 'config.json');
53
+ }
54
+ /** Read the scope config asynchronously. Returns empty object if file doesn't exist. */
55
+ export async function readScopeConfig(scope, projectDir) {
56
+ try {
57
+ const content = await fs.readFile(getScopeConfigPath(scope, projectDir), 'utf-8');
58
+ return JSON.parse(content);
59
+ }
60
+ catch {
61
+ return {};
62
+ }
63
+ }
64
+ /** Read the scope config synchronously. Returns empty object if file doesn't exist. */
65
+ export function readScopeConfigSync(scope, projectDir) {
66
+ try {
67
+ const content = readFileSync(getScopeConfigPath(scope, projectDir), 'utf-8');
68
+ return JSON.parse(content);
69
+ }
70
+ catch {
71
+ return {};
72
+ }
73
+ }
74
+ /** Write the scope config. Creates parent directories if needed. */
75
+ export async function writeScopeConfig(scope, config, projectDir) {
76
+ const configPath = getScopeConfigPath(scope, projectDir);
77
+ await fs.mkdir(path.dirname(configPath), { recursive: true });
78
+ await fs.writeFile(configPath, JSON.stringify(config, null, 2) + '\n');
79
+ }
80
+ /** Get all bindings across both scopes (project takes priority). */
81
+ export async function getAllBindings(projectDir) {
82
+ const result = {};
83
+ const userConfig = await readScopeConfig('user');
84
+ if (userConfig.slashCommands) {
85
+ for (const [cmd, skill] of Object.entries(userConfig.slashCommands)) {
86
+ result[cmd] = { skillName: skill, scope: 'user' };
87
+ }
88
+ }
89
+ if (projectDir) {
90
+ const projectConfig = await readScopeConfig('project', projectDir);
91
+ if (projectConfig.slashCommands) {
92
+ for (const [cmd, skill] of Object.entries(projectConfig.slashCommands)) {
93
+ result[cmd] = { skillName: skill, scope: 'project' };
94
+ }
95
+ }
96
+ }
97
+ return result;
98
+ }
99
+ // =============================================================================
100
+ // Binding Resolution
101
+ // =============================================================================
102
+ /**
103
+ * Resolve a command name to a bound skill name via config.json.
104
+ * Synchronous — config files are tiny.
105
+ */
106
+ export function resolveBinding(commandName, projectDir) {
107
+ if (projectDir) {
108
+ const projectConfig = readScopeConfigSync('project', projectDir);
109
+ if (projectConfig.slashCommands?.[commandName]) {
110
+ return projectConfig.slashCommands[commandName];
111
+ }
112
+ }
113
+ const userConfig = readScopeConfigSync('user');
114
+ if (userConfig.slashCommands?.[commandName]) {
115
+ return userConfig.slashCommands[commandName];
116
+ }
117
+ return null;
118
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@compilr-dev/sdk",
3
- "version": "0.10.1",
3
+ "version": "0.10.3",
4
4
  "description": "Universal agent runtime for building AI-powered applications",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -58,6 +58,7 @@
58
58
  "dependencies": {
59
59
  "@compilr-dev/agents": "^0.5.7",
60
60
  "@compilr-dev/logger": "^0.1.0",
61
+ "@compilr-dev/sdk": "^0.10.2",
61
62
  "ajv": "^6.14.0",
62
63
  "yaml": "^2.8.4"
63
64
  },