@compilr-dev/sdk 0.10.41 → 0.12.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.
@@ -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';
@@ -60,10 +60,14 @@ When you have enough information:
60
60
  - Nice-to-haves (type: feature, priority: medium/low)
61
61
  - Technical setup tasks (type: chore, priority: high)
62
62
  - Known risks/unknowns (type: tech-debt, priority: medium)
63
- 4. Update the PRD.md file with gathered requirements:
64
- - Use the edit tool to update PRD.md (located in .compilr/requirements/PRD.md or {project}-docs/00-requirements/PRD.md)
63
+ 4. Save the PRD to the project DATABASE (never a file):
64
+ - Check for an existing PRD first: project_document_get({ doc_type: "prd" })
65
+ - If none exists, create it:
66
+ project_document_add({ doc_type: "prd", title: "Product Requirements Document", content: "<full markdown>" })
67
+ - If one exists, update it section by section:
68
+ project_document_patch({ doc_type: "prd", operation: "replace_section", section_heading: "## <heading>", content: "..." })
65
69
  - Fill in: Problem Statement, Goals, User Stories, Functional Requirements
66
- - Keep the existing structure, just replace placeholder text
70
+ - CRITICAL: project documents live in the database. NEVER use write_file / edit / bash mkdir to create a PRD.md file on disk.
67
71
  5. Present the backlog and PRD summary to user for confirmation
68
72
  - If CHORE-001 was added, mention: "Run /scaffold to create the project foundation"
69
73
  6. If the project involves managing entities (CRUD operations on things like users, orders, products):
@@ -83,7 +87,7 @@ When you have enough information:
83
87
  ✓ MVP features are defined
84
88
  ✓ Backlog has 5-15 items
85
89
  ✓ For fresh projects: CHORE-001 scaffolding item is first (critical priority)
86
- ✓ PRD.md is populated with requirements
90
+ ✓ PRD saved to the database (doc_type: prd) — not written to a file
87
91
  ✓ If entity-based: user informed about /scaffold auto-generation
88
92
  ✓ User has approved the backlog`,
89
93
  tags: ['planning', 'requirements'],
@@ -440,11 +444,12 @@ export const prdSkill = defineSkill({
440
444
  - "Read current PRD"
441
445
  - "Identify section to update"
442
446
  - "Gather updates"
443
- - "Write updated PRD"
447
+ - "Save updated PRD"
444
448
 
445
- 2. Read the existing PRD.md:
446
- - Location: .compilr/requirements/PRD.md or {project}-docs/00-requirements/PRD.md
447
- - If no PRD exists, inform the user to run /design first
449
+ 2. Read the existing PRD from the project DATABASE:
450
+ - Use project_document_get({ doc_type: "prd" })
451
+ - If it returns no document, inform the user to run /design first
452
+ - The PRD is a database document, NOT a file — never read PRD.md from disk
448
453
 
449
454
  3. Present current PRD sections to user using ask_user_simple:
450
455
  - Question: "Which section would you like to update?"
@@ -482,21 +487,23 @@ Based on section selected:
482
487
  - What to add/change
483
488
  - What to remove
484
489
  - Any clarifications
485
- 3. Use the edit tool to update PRD.md
490
+ 3. Save the change to the database with project_document_patch:
491
+ - project_document_patch({ doc_type: "prd", operation: "replace_section", section_heading: "## <heading>", content: "..." })
492
+ - Use operation "append" / "prepend" to add new content without replacing
486
493
  4. Show the updated section for confirmation
487
494
 
488
495
  ## RULES
489
496
 
490
- 1. ALWAYS read the existing PRD first
497
+ 1. ALWAYS read the existing PRD first (project_document_get)
491
498
  2. Use ask_user_simple for section selection
492
499
  3. Use ask_user for gathering detailed updates
493
- 4. Preserve sections you're not updating
494
- 5. Use edit tool (not write_file) to update specific sections
500
+ 4. Preserve sections you're not updating (patch one section at a time)
501
+ 5. CRITICAL: the PRD lives in the database. Save with project_document_patch / project_document_add — NEVER write_file / edit / a PRD.md file on disk
495
502
  6. Keep formatting consistent with existing document
496
503
  7. If scope changes affect backlog, suggest running /refine
497
504
 
498
505
  ## COMPLETION CRITERIA
499
- ✓ PRD.md is updated with changes
506
+ ✓ PRD is updated in the database (doc_type: prd)
500
507
  ✓ User has reviewed the updates
501
508
  ✓ Related backlog updates are suggested if needed`,
502
509
  tags: ['planning', 'requirements'],
@@ -532,7 +539,7 @@ export const sessionNotesSkill = defineSkill({
532
539
 
533
540
  ## SESSION NOTE STRUCTURE
534
541
 
535
- Create a markdown file with this structure:
542
+ Compose a markdown note with this structure (it will be saved to the database, not a file):
536
543
 
537
544
  \`\`\`markdown
538
545
  # Session Note - {{title}}
@@ -573,13 +580,12 @@ Create a markdown file with this structure:
573
580
  *Generated by /note*
574
581
  \`\`\`
575
582
 
576
- ## FILE LOCATION
583
+ ## WHERE TO SAVE
577
584
 
578
- Save the note to:
579
- - Single repo: \`.compilr/sessions/{{YYYY-MM-DD}}-{{slug}}.md\`
580
- - Two repo: \`{{project}}-docs/04-session-notes/{{YYYY-MM-DD}}-{{slug}}.md\`
585
+ Save the note to the project DATABASE with:
586
+ project_document_add({ doc_type: "notes", title: "Session Note — {{title}}", content: "<full markdown>" })
581
587
 
582
- Create the directory if it doesn't exist.
588
+ CRITICAL: session notes are database documents. NEVER write a .md file (no write_file / edit / bash mkdir, no .compilr/sessions/ or {{project}}-docs/ paths).
583
589
 
584
590
  ## RULES
585
591
 
@@ -591,7 +597,7 @@ Create the directory if it doesn't exist.
591
597
  6. If user provides a title, use it; otherwise generate one from the summary
592
598
 
593
599
  ## COMPLETION CRITERIA
594
- ✓ Session note file is created
600
+ ✓ Session note saved to the database (doc_type: notes)
595
601
  ✓ All sections are filled in
596
602
  ✓ User has reviewed the note`,
597
603
  tags: ['documentation', 'session'],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@compilr-dev/sdk",
3
- "version": "0.10.41",
3
+ "version": "0.12.0",
4
4
  "description": "Universal agent runtime for building AI-powered applications",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",