@compilr-dev/sdk 0.11.0 → 0.13.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.
@@ -11,3 +11,5 @@ export { ensureFreshToken } from './token.js';
11
11
  export type { EnsureFreshTokenOptions, FreshToken } from './token.js';
12
12
  export { runDeviceFlow, nextPollInterval } from './device-flow.js';
13
13
  export type { DeviceFlowStatus, DeviceFlowCallbacks, LoginResult, RunDeviceFlowOptions, } from './device-flow.js';
14
+ export { loadProjectContextFiles, hasProjectContextFile, PROJECT_CONTEXT_FILES, } from './project-context.js';
15
+ export type { ProjectContextResult, LoadProjectContextOptions } from './project-context.js';
@@ -8,3 +8,5 @@ export { refreshToken, sendHeartbeat, getProfile, updateProfile, requestDeviceCo
8
8
  export { loadAuthData, saveAuthData, clearAuthData, updateSession, hasAuthData, checkAuthFilePermissions, readAuthFile, writeAuthFile, } from './auth-store.js';
9
9
  export { ensureFreshToken } from './token.js';
10
10
  export { runDeviceFlow, nextPollInterval } from './device-flow.js';
11
+ // ── Project context files (COMPILR.md / CLAUDE.md → system prompt) ────────────
12
+ export { loadProjectContextFiles, hasProjectContextFile, PROJECT_CONTEXT_FILES, } from './project-context.js';
@@ -0,0 +1,43 @@
1
+ /**
2
+ * loadProjectContextFiles — read a project's instruction file (COMPILR.md etc.)
3
+ * for injection into the system prompt.
4
+ *
5
+ * Shared by CLI and Desktop: the CLI had this logic in utils/project-memory.ts
6
+ * and Desktop hand-rolled a thinner inline 2-file version. This is the single
7
+ * implementation — searches a priority-ordered list, size-guards/truncates large
8
+ * files, and reports metadata.
9
+ *
10
+ * Pure file read; no fs writes, no electron. `estimatedTokens` is a cheap
11
+ * chars/4 heuristic (callers that need a real count tokenize themselves).
12
+ */
13
+ /** Instruction files searched, in priority order. */
14
+ export declare const PROJECT_CONTEXT_FILES: readonly ["COMPILR.md", ".compilr/instructions.md", "CLAUDE.md", ".claude/instructions.md"];
15
+ export interface ProjectContextResult {
16
+ /** Whether an instruction file was found. */
17
+ found: boolean;
18
+ /** Loaded content (possibly truncated); '' when not found. */
19
+ content: string;
20
+ /** Path of the file that was loaded, or null. */
21
+ filePath: string | null;
22
+ /** Original file size in bytes. */
23
+ originalSize: number;
24
+ /** Whether the content was truncated due to size. */
25
+ truncated: boolean;
26
+ /** Rough token estimate (chars / 4). */
27
+ estimatedTokens: number;
28
+ }
29
+ export interface LoadProjectContextOptions {
30
+ /** Directory to search (default: process.cwd()). */
31
+ cwd?: string;
32
+ /** Max content bytes before truncation (default: 100KB). */
33
+ maxSize?: number;
34
+ /** Override the filenames searched, in priority order. */
35
+ files?: readonly string[];
36
+ }
37
+ /**
38
+ * Load the first matching project instruction file from `cwd`. Returns a
39
+ * not-found result (never throws) if none exists or all are unreadable.
40
+ */
41
+ export declare function loadProjectContextFiles(options?: LoadProjectContextOptions): ProjectContextResult;
42
+ /** Quick existence check (no read) for any project instruction file. */
43
+ export declare function hasProjectContextFile(cwd?: string): boolean;
@@ -0,0 +1,76 @@
1
+ /**
2
+ * loadProjectContextFiles — read a project's instruction file (COMPILR.md etc.)
3
+ * for injection into the system prompt.
4
+ *
5
+ * Shared by CLI and Desktop: the CLI had this logic in utils/project-memory.ts
6
+ * and Desktop hand-rolled a thinner inline 2-file version. This is the single
7
+ * implementation — searches a priority-ordered list, size-guards/truncates large
8
+ * files, and reports metadata.
9
+ *
10
+ * Pure file read; no fs writes, no electron. `estimatedTokens` is a cheap
11
+ * chars/4 heuristic (callers that need a real count tokenize themselves).
12
+ */
13
+ import { existsSync, readFileSync, statSync } from 'node:fs';
14
+ import { join } from 'node:path';
15
+ /** Instruction files searched, in priority order. */
16
+ export const PROJECT_CONTEXT_FILES = [
17
+ 'COMPILR.md',
18
+ '.compilr/instructions.md',
19
+ 'CLAUDE.md',
20
+ '.claude/instructions.md',
21
+ ];
22
+ /** Default max content size before truncation: 100KB. */
23
+ const DEFAULT_MAX_SIZE = 100 * 1024;
24
+ /**
25
+ * Load the first matching project instruction file from `cwd`. Returns a
26
+ * not-found result (never throws) if none exists or all are unreadable.
27
+ */
28
+ export function loadProjectContextFiles(options = {}) {
29
+ const cwd = options.cwd ?? process.cwd();
30
+ const maxSize = options.maxSize ?? DEFAULT_MAX_SIZE;
31
+ const files = options.files ?? PROJECT_CONTEXT_FILES;
32
+ for (const filename of files) {
33
+ const filePath = join(cwd, filename);
34
+ if (!existsSync(filePath))
35
+ continue;
36
+ try {
37
+ const originalSize = statSync(filePath).size;
38
+ let content = readFileSync(filePath, 'utf-8');
39
+ let truncated = false;
40
+ if (content.length > maxSize) {
41
+ content = content.slice(0, maxSize);
42
+ // Prefer a line boundary if one is reasonably close to the cut.
43
+ const lastNewline = content.lastIndexOf('\n');
44
+ if (lastNewline > maxSize * 0.8) {
45
+ content = content.slice(0, lastNewline);
46
+ }
47
+ content += '\n\n[... truncated due to size ...]';
48
+ truncated = true;
49
+ }
50
+ return {
51
+ found: true,
52
+ content,
53
+ filePath,
54
+ originalSize,
55
+ truncated,
56
+ estimatedTokens: Math.ceil(content.length / 4),
57
+ };
58
+ }
59
+ catch {
60
+ // Exists but unreadable — try the next candidate.
61
+ continue;
62
+ }
63
+ }
64
+ return {
65
+ found: false,
66
+ content: '',
67
+ filePath: null,
68
+ originalSize: 0,
69
+ truncated: false,
70
+ estimatedTokens: 0,
71
+ };
72
+ }
73
+ /** Quick existence check (no read) for any project instruction file. */
74
+ export function hasProjectContextFile(cwd = process.cwd()) {
75
+ return PROJECT_CONTEXT_FILES.some((f) => existsSync(join(cwd, f)));
76
+ }
package/dist/index.d.ts CHANGED
@@ -64,8 +64,8 @@ export { detectProject, suggestProjectType, detectCommon } from './detection/ind
64
64
  export type { DetectProjectOptions, DetectionResult, ContentSummary } from './detection/index.js';
65
65
  export { createGuideTool, SHARED_GUIDE_ENTRIES, searchGuideEntries, topicToGuideEntry, } from './guide/index.js';
66
66
  export type { GuideEntry, ContentTopic, ContentSection, GuideToolConfig } from './guide/index.js';
67
- export { createPlatformTools, createProjectTools, createWorkItemTools, createDocumentTools, createPlanTools, createBacklogTools, createAnchorTools, createArtifactTools, createEpisodeTools, createImageTools, ProjectAnchorStore, } from './platform/index.js';
68
- export type { ProjectAnchorStoreConfig, ImageToolsConfig, ImageResizer } from './platform/index.js';
67
+ export { createPlatformTools, createProjectTools, createWorkItemTools, createDocumentTools, createPlanTools, createBacklogTools, createAnchorTools, createArtifactTools, createEpisodeTools, createImageTools, ProjectAnchorStore, FileArtifactService, } from './platform/index.js';
68
+ export type { ProjectAnchorStoreConfig, FileArtifactServiceConfig, ImageToolsConfig, ImageResizer, } from './platform/index.js';
69
69
  export { STEP_ORDER, GUIDED_STEP_CRITERIA, getNextStep, isValidTransition, getStepCriteria, formatStepDisplay, getStepNumber, } from './platform/index.js';
70
70
  export type { CustomSkill, CompilrSkillExtension, ForkedFromMarker, SkillEligibilityContext, SkillCollision, SkillDiffLine, SkillValidationIssue, ScopeConfig, SkillResolution, } from './skills/index.js';
71
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';
@@ -83,8 +83,8 @@ export { DEFAULT_PERMISSION_RULES, WRITE_TOOLS, findMatchingRule, permissionMode
83
83
  export type { PermissionRule, PermissionMode, PermissionLevel } from './permissions.js';
84
84
  export { SHARED_DEFAULTS, migrateRawSettings } from './host/index.js';
85
85
  export type { HostSettings, SettingsProviderType, TextSize, NotificationMode, Verbosity, CompactMode, ProjectSessionMode, MascotSetting, StartupMode, ProjectStartupMode, StartupBehavior, DefaultLeftPanel, TipsProficiency, } from './host/index.js';
86
- export { refreshToken, sendHeartbeat, getProfile, updateProfile, requestDeviceCode, pollDeviceToken, getAuthorizationUrl, loadAuthData, saveAuthData, clearAuthData, updateSession, hasAuthData, checkAuthFilePermissions, readAuthFile, writeAuthFile, ensureFreshToken, runDeviceFlow, nextPollInterval, } from './host/index.js';
87
- export type { AccountType, AuthUser, AuthSession, AuthData, RefreshTokenResponse, ProfileData, HeartbeatData, DeviceCodeResponse, DeviceTokenResponse, DeviceTokenError, AuthEndpoints, EnsureFreshTokenOptions, FreshToken, DeviceFlowStatus, DeviceFlowCallbacks, LoginResult, RunDeviceFlowOptions, } from './host/index.js';
86
+ export { refreshToken, sendHeartbeat, getProfile, updateProfile, requestDeviceCode, pollDeviceToken, getAuthorizationUrl, loadAuthData, saveAuthData, clearAuthData, updateSession, hasAuthData, checkAuthFilePermissions, readAuthFile, writeAuthFile, ensureFreshToken, runDeviceFlow, nextPollInterval, loadProjectContextFiles, hasProjectContextFile, PROJECT_CONTEXT_FILES, } from './host/index.js';
87
+ export type { AccountType, AuthUser, AuthSession, AuthData, RefreshTokenResponse, ProfileData, HeartbeatData, DeviceCodeResponse, DeviceTokenResponse, DeviceTokenError, AuthEndpoints, EnsureFreshTokenOptions, FreshToken, DeviceFlowStatus, DeviceFlowCallbacks, LoginResult, RunDeviceFlowOptions, ProjectContextResult, LoadProjectContextOptions, } from './host/index.js';
88
88
  export { DEFAULT_DELEGATION_CONFIG } from './delegation.js';
89
89
  export { FileEpisodeStore, EpisodeRecorder, isSignificantWork, extractAffectedFiles, extractLinesChanged, queryWorkAtRisk, buildWorkSummaryContent, updateWorkSummaryAnchor, } from './episodes/index.js';
90
90
  export type { FileEpisodeStoreOptions, EpisodeRecorderConfig, WorkSummaryAnchorConfig, PendingToolSignal, EpisodeFile, } from './episodes/index.js';
package/dist/index.js CHANGED
@@ -147,7 +147,7 @@ export { createGuideTool, SHARED_GUIDE_ENTRIES, searchGuideEntries, topicToGuide
147
147
  // =============================================================================
148
148
  // Platform Tools (runtime — createPlatformTools factory + individual factories)
149
149
  // =============================================================================
150
- export { createPlatformTools, createProjectTools, createWorkItemTools, createDocumentTools, createPlanTools, createBacklogTools, createAnchorTools, createArtifactTools, createEpisodeTools, createImageTools, ProjectAnchorStore, } from './platform/index.js';
150
+ export { createPlatformTools, createProjectTools, createWorkItemTools, createDocumentTools, createPlanTools, createBacklogTools, createAnchorTools, createArtifactTools, createEpisodeTools, createImageTools, ProjectAnchorStore, FileArtifactService, } from './platform/index.js';
151
151
  // =============================================================================
152
152
  // Platform Workflow (pure step-criteria logic)
153
153
  // =============================================================================
@@ -202,7 +202,7 @@ export { DEFAULT_PERMISSION_RULES, WRITE_TOOLS, findMatchingRule, permissionMode
202
202
  // =============================================================================
203
203
  export { SHARED_DEFAULTS, migrateRawSettings } from './host/index.js';
204
204
  // Auth: shared backend client, credential store, token refresh, device flow.
205
- export { refreshToken, sendHeartbeat, getProfile, updateProfile, requestDeviceCode, pollDeviceToken, getAuthorizationUrl, loadAuthData, saveAuthData, clearAuthData, updateSession, hasAuthData, checkAuthFilePermissions, readAuthFile, writeAuthFile, ensureFreshToken, runDeviceFlow, nextPollInterval, } from './host/index.js';
205
+ export { refreshToken, sendHeartbeat, getProfile, updateProfile, requestDeviceCode, pollDeviceToken, getAuthorizationUrl, loadAuthData, saveAuthData, clearAuthData, updateSession, hasAuthData, checkAuthFilePermissions, readAuthFile, writeAuthFile, ensureFreshToken, runDeviceFlow, nextPollInterval, loadProjectContextFiles, hasProjectContextFile, PROJECT_CONTEXT_FILES, } from './host/index.js';
206
206
  // =============================================================================
207
207
  // Shared Delegation Config Defaults
208
208
  // =============================================================================
@@ -0,0 +1,55 @@
1
+ /**
2
+ * FileArtifactService — file-backed IArtifactService shared by CLI and Desktop.
3
+ *
4
+ * Both hosts previously hand-rolled identical artifact CRUD on top of the SDK's
5
+ * ArtifactStore, reading/writing the SAME file:
6
+ * {sessionsDir}/{global | project-<id>}/artifacts/artifacts.json
7
+ * This consolidates that logic next to FileEpisodeStore / ProjectAnchorStore.
8
+ *
9
+ * Each operation does a fresh load → mutate → save (the file is the source of
10
+ * truth, shared per-project across terminals), matching the prior behavior.
11
+ *
12
+ * Hosts wire in:
13
+ * - getProjectId: resolved per call, so the service follows project switches
14
+ * - onSaved (optional): fired after a create/update persists — the CLI uses it
15
+ * to record team activity; Desktop omits it
16
+ * - onError (optional): load/save failures, so each host logs as it sees fit
17
+ */
18
+ import type { IArtifactService, ArtifactData, ArtifactSummaryData, ArtifactType } from './services.js';
19
+ export interface FileArtifactServiceConfig {
20
+ /** Sessions root, e.g. ~/.compilr-dev/sessions */
21
+ sessionsDir: string;
22
+ /** Active project id (null = global), resolved on every call. */
23
+ getProjectId: () => number | null;
24
+ /** Fired after a successful create/update persists. */
25
+ onSaved?: (artifact: ArtifactData, isUpdate: boolean) => void;
26
+ /** Fired on a load/save failure (host decides how to log). */
27
+ onError?: (err: unknown, op: 'load' | 'save') => void;
28
+ }
29
+ export declare class FileArtifactService implements IArtifactService {
30
+ private readonly config;
31
+ constructor(config: FileArtifactServiceConfig);
32
+ private artifactsDir;
33
+ private dataFile;
34
+ /** Load the artifact store for a project from disk (empty store if absent). */
35
+ private loadStore;
36
+ /** Persist the artifact store for a project to disk. */
37
+ private saveStore;
38
+ save(input: {
39
+ name: string;
40
+ type: ArtifactType;
41
+ content: string;
42
+ summary?: string;
43
+ agent?: string;
44
+ }): Promise<{
45
+ artifact: ArtifactData;
46
+ isUpdate: boolean;
47
+ }>;
48
+ getByName(name: string): Promise<ArtifactData | null>;
49
+ list(options?: {
50
+ type?: ArtifactType;
51
+ agent?: string;
52
+ search?: string;
53
+ }): Promise<ArtifactSummaryData[]>;
54
+ delete(name: string): Promise<ArtifactData | null>;
55
+ }
@@ -0,0 +1,140 @@
1
+ /**
2
+ * FileArtifactService — file-backed IArtifactService shared by CLI and Desktop.
3
+ *
4
+ * Both hosts previously hand-rolled identical artifact CRUD on top of the SDK's
5
+ * ArtifactStore, reading/writing the SAME file:
6
+ * {sessionsDir}/{global | project-<id>}/artifacts/artifacts.json
7
+ * This consolidates that logic next to FileEpisodeStore / ProjectAnchorStore.
8
+ *
9
+ * Each operation does a fresh load → mutate → save (the file is the source of
10
+ * truth, shared per-project across terminals), matching the prior behavior.
11
+ *
12
+ * Hosts wire in:
13
+ * - getProjectId: resolved per call, so the service follows project switches
14
+ * - onSaved (optional): fired after a create/update persists — the CLI uses it
15
+ * to record team activity; Desktop omits it
16
+ * - onError (optional): load/save failures, so each host logs as it sees fit
17
+ */
18
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
19
+ import { join } from 'path';
20
+ import { ArtifactStore } from '../team/artifacts.js';
21
+ function toArtifactData(a) {
22
+ return {
23
+ id: a.id,
24
+ name: a.name,
25
+ agent: a.agent,
26
+ type: a.type,
27
+ content: a.content,
28
+ summary: a.summary,
29
+ version: a.version,
30
+ createdAt: a.createdAt,
31
+ updatedAt: a.updatedAt,
32
+ };
33
+ }
34
+ export class FileArtifactService {
35
+ config;
36
+ constructor(config) {
37
+ this.config = config;
38
+ }
39
+ artifactsDir(projectId) {
40
+ const projectDir = projectId === null
41
+ ? join(this.config.sessionsDir, 'global')
42
+ : join(this.config.sessionsDir, `project-${String(projectId)}`);
43
+ return join(projectDir, 'artifacts');
44
+ }
45
+ dataFile(projectId) {
46
+ return join(this.artifactsDir(projectId), 'artifacts.json');
47
+ }
48
+ /** Load the artifact store for a project from disk (empty store if absent). */
49
+ loadStore(projectId) {
50
+ const store = new ArtifactStore();
51
+ const file = this.dataFile(projectId);
52
+ if (!existsSync(file))
53
+ return store;
54
+ try {
55
+ const data = JSON.parse(readFileSync(file, 'utf-8'));
56
+ store.restore(data);
57
+ }
58
+ catch (err) {
59
+ // Corrupt/unreadable — start fresh rather than crash the tool call.
60
+ this.config.onError?.(err, 'load');
61
+ }
62
+ return store;
63
+ }
64
+ /** Persist the artifact store for a project to disk. */
65
+ saveStore(projectId, store) {
66
+ const dir = this.artifactsDir(projectId);
67
+ if (!existsSync(dir))
68
+ mkdirSync(dir, { recursive: true });
69
+ try {
70
+ writeFileSync(this.dataFile(projectId), JSON.stringify(store.serialize(), null, 2), 'utf-8');
71
+ }
72
+ catch (err) {
73
+ this.config.onError?.(err, 'save');
74
+ }
75
+ }
76
+ save(input) {
77
+ const projectId = this.config.getProjectId();
78
+ const store = this.loadStore(projectId);
79
+ const agentId = input.agent ?? 'default';
80
+ const existing = store.getByName(input.name);
81
+ if (existing) {
82
+ const updated = store.update(existing.id, {
83
+ content: input.content,
84
+ summary: input.summary,
85
+ type: input.type,
86
+ });
87
+ if (!updated)
88
+ throw new Error(`Failed to update artifact "${input.name}"`);
89
+ this.saveStore(projectId, store);
90
+ const artifact = toArtifactData(updated);
91
+ this.config.onSaved?.(artifact, true);
92
+ return Promise.resolve({ artifact, isUpdate: true });
93
+ }
94
+ const created = store.create({
95
+ name: input.name,
96
+ agent: agentId,
97
+ type: input.type,
98
+ content: input.content,
99
+ summary: input.summary,
100
+ });
101
+ this.saveStore(projectId, store);
102
+ const artifact = toArtifactData(created);
103
+ this.config.onSaved?.(artifact, false);
104
+ return Promise.resolve({ artifact, isUpdate: false });
105
+ }
106
+ getByName(name) {
107
+ const store = this.loadStore(this.config.getProjectId());
108
+ const a = store.getByName(name);
109
+ return Promise.resolve(a ? toArtifactData(a) : null);
110
+ }
111
+ list(options) {
112
+ const store = this.loadStore(this.config.getProjectId());
113
+ let items = options?.search ? store.search(options.search) : store.list();
114
+ if (options?.type)
115
+ items = items.filter((a) => a.type === options.type);
116
+ if (options?.agent)
117
+ items = items.filter((a) => a.agent === options.agent);
118
+ const summaries = items.map((a) => ({
119
+ id: a.id,
120
+ name: a.name,
121
+ agent: a.agent,
122
+ type: a.type,
123
+ summary: a.summary,
124
+ version: a.version,
125
+ updatedAt: a.updatedAt,
126
+ }));
127
+ return Promise.resolve(summaries);
128
+ }
129
+ delete(name) {
130
+ const projectId = this.config.getProjectId();
131
+ const store = this.loadStore(projectId);
132
+ const a = store.getByName(name);
133
+ if (!a)
134
+ return Promise.resolve(null);
135
+ if (!store.delete(a.id))
136
+ return Promise.resolve(null);
137
+ this.saveStore(projectId, store);
138
+ return Promise.resolve(toArtifactData(a));
139
+ }
140
+ }
@@ -11,5 +11,7 @@ export { createSQLiteRepositories, SQLiteProjectRepository, SQLiteWorkItemReposi
11
11
  export type { SQLiteRepositories, CreateSQLiteRepositoriesOptions, ProjectDeleteHooks, ProjectRecord, WorkItemRecord, ProjectDocumentRecord, WorkItemCommentRecord, } from './sqlite/index.js';
12
12
  export { ProjectAnchorStore } from './file-anchor-service.js';
13
13
  export type { ProjectAnchorStoreConfig } from './file-anchor-service.js';
14
+ export { FileArtifactService } from './file-artifact-service.js';
15
+ export type { FileArtifactServiceConfig } from './file-artifact-service.js';
14
16
  export { STEP_ORDER, GUIDED_STEP_CRITERIA, getNextStep, isValidTransition, getStepCriteria, formatStepDisplay, getStepNumber, } from './workflow.js';
15
17
  export type { StepCriteria } from './workflow.js';
@@ -7,5 +7,7 @@ export { createPlatformTools, createProjectTools, createWorkItemTools, createDoc
7
7
  export { createSQLiteRepositories, SQLiteProjectRepository, SQLiteWorkItemRepository, SQLiteDocumentRepository, SQLitePlanRepository, SQLiteCommentRepository, getDatabase, closeDatabase, closeAllDatabases, databaseExists, SCHEMA_VERSION, SCHEMA_SQL, } from './sqlite/index.js';
8
8
  // File-based anchor service (shared by CLI and Desktop)
9
9
  export { ProjectAnchorStore } from './file-anchor-service.js';
10
+ // File-based artifact service (shared by CLI and Desktop)
11
+ export { FileArtifactService } from './file-artifact-service.js';
10
12
  // Workflow (pure step-criteria logic)
11
13
  export { STEP_ORDER, GUIDED_STEP_CRITERIA, getNextStep, isValidTransition, getStepCriteria, formatStepDisplay, getStepNumber, } from './workflow.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@compilr-dev/sdk",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "description": "Universal agent runtime for building AI-powered applications",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",