@soederpop/luca 0.0.23 → 0.0.26

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.
Files changed (67) hide show
  1. package/AGENTS.md +1 -1
  2. package/CLAUDE.md +6 -1
  3. package/assistants/codingAssistant/hooks.ts +0 -1
  4. package/assistants/lucaExpert/CORE.md +37 -0
  5. package/assistants/lucaExpert/hooks.ts +9 -0
  6. package/assistants/lucaExpert/tools.ts +177 -0
  7. package/commands/build-bootstrap.ts +41 -1
  8. package/docs/TABLE-OF-CONTENTS.md +0 -1
  9. package/docs/apis/clients/rest.md +5 -5
  10. package/docs/apis/features/agi/assistant.md +1 -1
  11. package/docs/apis/features/agi/conversation-history.md +6 -7
  12. package/docs/apis/features/agi/conversation.md +1 -1
  13. package/docs/apis/features/agi/semantic-search.md +1 -1
  14. package/docs/bootstrap/CLAUDE.md +1 -1
  15. package/docs/bootstrap/SKILL.md +7 -3
  16. package/docs/bootstrap/templates/luca-cli.ts +5 -0
  17. package/docs/mcp/readme.md +1 -1
  18. package/docs/tutorials/00-bootstrap.md +18 -0
  19. package/package.json +2 -2
  20. package/scripts/stamp-build.sh +12 -0
  21. package/scripts/test-docs-reader.ts +10 -0
  22. package/src/agi/container.server.ts +8 -5
  23. package/src/agi/features/assistant.ts +210 -55
  24. package/src/agi/features/assistants-manager.ts +138 -66
  25. package/src/agi/features/conversation.ts +46 -14
  26. package/src/agi/features/docs-reader.ts +166 -0
  27. package/src/agi/features/openapi.ts +1 -1
  28. package/src/agi/features/skills-library.ts +257 -313
  29. package/src/bootstrap/generated.ts +8163 -6
  30. package/src/cli/build-info.ts +4 -0
  31. package/src/cli/cli.ts +2 -1
  32. package/src/command.ts +75 -0
  33. package/src/commands/bootstrap.ts +16 -1
  34. package/src/commands/describe.ts +29 -1089
  35. package/src/commands/eval.ts +6 -1
  36. package/src/commands/sandbox-mcp.ts +17 -7
  37. package/src/container-describer.ts +1098 -0
  38. package/src/container.ts +11 -0
  39. package/src/helper.ts +56 -2
  40. package/src/introspection/generated.agi.ts +1684 -799
  41. package/src/introspection/generated.node.ts +964 -572
  42. package/src/introspection/generated.web.ts +9 -1
  43. package/src/node/container.ts +1 -1
  44. package/src/node/features/content-db.ts +268 -13
  45. package/src/node/features/fs.ts +18 -0
  46. package/src/node/features/git.ts +90 -0
  47. package/src/node/features/grep.ts +1 -1
  48. package/src/node/features/proc.ts +1 -0
  49. package/src/node/features/tts.ts +1 -1
  50. package/src/node/features/vm.ts +48 -0
  51. package/src/scaffolds/generated.ts +2 -2
  52. package/src/server.ts +40 -0
  53. package/src/servers/express.ts +2 -0
  54. package/src/servers/mcp.ts +1 -0
  55. package/src/servers/socket.ts +2 -0
  56. package/assistants/architect/CORE.md +0 -3
  57. package/assistants/architect/hooks.ts +0 -3
  58. package/assistants/architect/tools.ts +0 -10
  59. package/docs/apis/features/agi/skills-library.md +0 -234
  60. package/docs/reports/assistant-bugs.md +0 -38
  61. package/docs/reports/attach-pattern-usage.md +0 -18
  62. package/docs/reports/code-audit-results.md +0 -391
  63. package/docs/reports/console-hmr-design.md +0 -170
  64. package/docs/reports/helper-semantic-search.md +0 -72
  65. package/docs/reports/introspection-audit-tasks.md +0 -378
  66. package/docs/reports/luca-mcp-improvements.md +0 -128
  67. package/test-integration/skills-library.test.ts +0 -157
package/src/server.ts CHANGED
@@ -79,6 +79,44 @@ export class Server<T extends ServerState = ServerState, K extends ServerOptions
79
79
  return super.container as NodeContainer
80
80
  }
81
81
 
82
+ /** Async functions passed to `.use()` before `start()` — drained in `start()`. */
83
+ _pendingPlugins: Promise<void>[] = []
84
+
85
+ /**
86
+ * Register a setup function against this server. The function receives the
87
+ * server instance and can configure it however it likes.
88
+ *
89
+ * - If the server hasn't started yet, async return values are queued and
90
+ * awaited when `start()` is called.
91
+ * - If the server is already listening, the function is fire-and-forget.
92
+ * - Always returns `this` synchronously for chaining.
93
+ *
94
+ * @example
95
+ * ```typescript
96
+ * server
97
+ * .use((s) => s.app.use(cors()))
98
+ * .use(async (s) => { await loadRoutes(s) })
99
+ * await server.start()
100
+ * ```
101
+ */
102
+ use(fn: (server: this) => void | Promise<void>): this {
103
+ const result = fn(this)
104
+ if (result && typeof (result as any).then === 'function') {
105
+ if (!this.isListening) {
106
+ this._pendingPlugins.push(result as Promise<void>)
107
+ }
108
+ }
109
+ return this
110
+ }
111
+
112
+ /** Drain all pending `.use()` promises. Subclasses should call this at the top of `start()`. */
113
+ protected async _drainPendingPlugins() {
114
+ if (this._pendingPlugins.length) {
115
+ await Promise.all(this._pendingPlugins)
116
+ this._pendingPlugins = []
117
+ }
118
+ }
119
+
82
120
  get isListening() {
83
121
  return !!this.state.get('listening')
84
122
  }
@@ -117,6 +155,8 @@ export class Server<T extends ServerState = ServerState, K extends ServerOptions
117
155
  return this
118
156
  }
119
157
 
158
+ await this._drainPendingPlugins()
159
+
120
160
  if (options?.port) {
121
161
  this.state.set('port', options.port)
122
162
  }
@@ -104,6 +104,8 @@ export class ExpressServer<T extends ServerState = ServerState, K extends Expres
104
104
  return this
105
105
  }
106
106
 
107
+ await this._drainPendingPlugins()
108
+
107
109
  // Apply runtime port to state so this.port reflects the override
108
110
  if (options?.port) {
109
111
  this.state.set('port', options.port)
@@ -472,6 +472,7 @@ export class MCPServer extends Server<MCPServerState, MCPServerOptions> {
472
472
  stdioCompat?: StdioCompatMode
473
473
  }) {
474
474
  if (this.isListening) return this
475
+ await this._drainPendingPlugins()
475
476
  if (!this.isConfigured) await this.configure()
476
477
 
477
478
  const transport = options?.transport || this.options.transport || 'stdio'
@@ -89,6 +89,8 @@ export class WebsocketServer<T extends ServerState = ServerState, K extends Sock
89
89
  return this
90
90
  }
91
91
 
92
+ await this._drainPendingPlugins()
93
+
92
94
  // Apply runtime port to state before configure/wss touches it
93
95
  if (options?.port) {
94
96
  this.state.set('port', options.port)
@@ -1,3 +0,0 @@
1
- # Luca Framework Assistant
2
-
3
- Your job is to help people use the Luca framework
@@ -1,3 +0,0 @@
1
- export function started() {
2
- console.log('Assistant started!')
3
- }
@@ -1,10 +0,0 @@
1
- import { z } from 'zod'
2
-
3
- export const schemas = {
4
- README: z.object({}).describe('CALL THIS README FUNCTION AS EARLY AS POSSIBLE')
5
- }
6
-
7
- export function README(options: z.infer<typeof schemas.README>) {
8
- return 'YO YO'
9
- }
10
-
@@ -1,234 +0,0 @@
1
- # SkillsLibrary (features.skillsLibrary)
2
-
3
- Manages two contentbase collections of skills following the Claude Code SKILL.md format. Project-level skills live in .claude/skills/ and user-level skills live in ~/.luca/skills/. Skills can be discovered, searched, created, updated, and removed at runtime.
4
-
5
- ## Usage
6
-
7
- ```ts
8
- container.feature('skillsLibrary', {
9
- // Path to project-level skills directory
10
- projectSkillsPath,
11
- // Path to user-level global skills directory
12
- userSkillsPath,
13
- })
14
- ```
15
-
16
- ## Options (Zod v4 schema)
17
-
18
- | Property | Type | Description |
19
- |----------|------|-------------|
20
- | `projectSkillsPath` | `string` | Path to project-level skills directory |
21
- | `userSkillsPath` | `string` | Path to user-level global skills directory |
22
-
23
- ## Methods
24
-
25
- ### load
26
-
27
- Loads both project and user skill collections from disk. Gracefully handles missing directories.
28
-
29
- **Returns:** `Promise<SkillsLibrary>`
30
-
31
-
32
-
33
- ### list
34
-
35
- Lists all skills from both collections. Project skills come first.
36
-
37
- **Returns:** `SkillEntry[]`
38
-
39
-
40
-
41
- ### find
42
-
43
- Finds a skill by name. Project skills take precedence over user skills.
44
-
45
- **Parameters:**
46
-
47
- | Name | Type | Required | Description |
48
- |------|------|----------|-------------|
49
- | `name` | `string` | ✓ | The skill name to find (case-insensitive) |
50
-
51
- **Returns:** `SkillEntry | undefined`
52
-
53
-
54
-
55
- ### search
56
-
57
- Searches skills by substring match against name and description.
58
-
59
- **Parameters:**
60
-
61
- | Name | Type | Required | Description |
62
- |------|------|----------|-------------|
63
- | `query` | `string` | ✓ | The search query |
64
-
65
- **Returns:** `SkillEntry[]`
66
-
67
-
68
-
69
- ### getSkill
70
-
71
- Gets a skill by name. Alias for find().
72
-
73
- **Parameters:**
74
-
75
- | Name | Type | Required | Description |
76
- |------|------|----------|-------------|
77
- | `name` | `string` | ✓ | The skill name |
78
-
79
- **Returns:** `SkillEntry | undefined`
80
-
81
-
82
-
83
- ### create
84
-
85
- Creates a new SKILL.md file in the specified collection. Maintains the directory-per-skill structure (skill-name/SKILL.md).
86
-
87
- **Parameters:**
88
-
89
- | Name | Type | Required | Description |
90
- |------|------|----------|-------------|
91
- | `skill` | `{
92
- name: string
93
- description: string
94
- body: string
95
- meta?: Record<string, unknown>
96
- }` | ✓ | The skill to create |
97
- | `target` | `'project' | 'user'` | | Which collection to write to (default: 'project') |
98
-
99
- **Returns:** `Promise<SkillEntry>`
100
-
101
-
102
-
103
- ### update
104
-
105
- Updates an existing skill's content or metadata.
106
-
107
- **Parameters:**
108
-
109
- | Name | Type | Required | Description |
110
- |------|------|----------|-------------|
111
- | `name` | `string` | ✓ | The skill name to update |
112
- | `updates` | `{
113
- description?: string
114
- body?: string
115
- meta?: Record<string, unknown>
116
- }` | ✓ | Fields to update |
117
-
118
- **Returns:** `Promise<SkillEntry>`
119
-
120
-
121
-
122
- ### remove
123
-
124
- Removes a skill by name, deleting its SKILL.md and cleaning up the directory.
125
-
126
- **Parameters:**
127
-
128
- | Name | Type | Required | Description |
129
- |------|------|----------|-------------|
130
- | `name` | `string` | ✓ | The skill name to remove |
131
-
132
- **Returns:** `Promise<boolean>`
133
-
134
-
135
-
136
- ### toConversationTools
137
-
138
- Converts all skills into ConversationTool format for use with Conversation. Each skill becomes a tool that returns its instruction body when invoked.
139
-
140
- **Returns:** `Record<string, ConversationTool>`
141
-
142
-
143
-
144
- ### toSystemPromptBlock
145
-
146
- Generates a markdown block listing all available skills with names and descriptions. Suitable for injecting into a system prompt.
147
-
148
- **Returns:** `string`
149
-
150
-
151
-
152
- ## Getters
153
-
154
- | Property | Type | Description |
155
- |----------|------|-------------|
156
- | `projectCollection` | `Collection` | Returns the project-level contentbase Collection, lazily initialized. |
157
- | `userCollection` | `Collection` | Returns the user-level contentbase Collection, lazily initialized. |
158
- | `isLoaded` | `boolean` | Whether the skills library has been loaded. |
159
- | `skillNames` | `string[]` | Array of all skill names across both collections. |
160
-
161
- ## Events (Zod v4 schema)
162
-
163
- ### loaded
164
-
165
- Fired after both project and user skill collections are loaded
166
-
167
-
168
-
169
- ### skillCreated
170
-
171
- Fired after a new skill is written to disk
172
-
173
- **Event Arguments:**
174
-
175
- | Name | Type | Description |
176
- |------|------|-------------|
177
- | `arg0` | `any` | The created SkillEntry object |
178
-
179
-
180
-
181
- ### skillUpdated
182
-
183
- Fired after an existing skill is updated
184
-
185
- **Event Arguments:**
186
-
187
- | Name | Type | Description |
188
- |------|------|-------------|
189
- | `arg0` | `any` | The updated SkillEntry object |
190
-
191
-
192
-
193
- ### skillRemoved
194
-
195
- Fired after a skill is deleted
196
-
197
- **Event Arguments:**
198
-
199
- | Name | Type | Description |
200
- |------|------|-------------|
201
- | `arg0` | `string` | The name of the removed skill |
202
-
203
-
204
-
205
- ## State (Zod v4 schema)
206
-
207
- | Property | Type | Description |
208
- |----------|------|-------------|
209
- | `enabled` | `boolean` | Whether this feature is currently enabled |
210
- | `loaded` | `boolean` | Whether both collections have been loaded |
211
- | `projectSkillCount` | `number` | Number of skills in the project collection |
212
- | `userSkillCount` | `number` | Number of skills in the user-level collection |
213
- | `totalSkillCount` | `number` | Total number of skills across both collections |
214
-
215
- ## Examples
216
-
217
- **features.skillsLibrary**
218
-
219
- ```ts
220
- const skills = container.feature('skillsLibrary')
221
- await skills.load()
222
-
223
- // List and search
224
- const allSkills = skills.list()
225
- const matches = skills.search('code review')
226
-
227
- // Create a new skill
228
- await skills.create({
229
- name: 'summarize',
230
- description: 'Summarize a document',
231
- body: '## Instructions\nRead the document and produce a concise summary.'
232
- })
233
- ```
234
-
@@ -1,38 +0,0 @@
1
- # Assistant systemPrompt Bug Fix Verification
2
-
3
- The bug: class field initializers (`private _systemPrompt: string = ''`) on Feature subclasses
4
- overwrite values set during `afterInitialize()` because ES2022 field initializers run AFTER `super()` returns.
5
-
6
- Fix: use `declare private _systemPrompt: string` which emits no JavaScript, so afterInitialize values persist.
7
-
8
- ## Test
9
-
10
- ```ts
11
- const assistant = container.feature('assistant', {
12
- folder: '/Users/jon/@soederpop/commands/voice/assistant'
13
- })
14
- ```
15
-
16
- systemPrompt should contain the CORE.md content (not be empty):
17
-
18
- ```ts
19
- console.log("systemPrompt length:", assistant.systemPrompt.length)
20
- console.log("contains omar:", assistant.systemPrompt.toLowerCase().includes("omar"))
21
- console.log("first 80 chars:", assistant.systemPrompt.slice(0, 80))
22
- ```
23
-
24
- Tools should have been loaded from tools.ts:
25
-
26
- ```ts
27
- console.log("tools loaded:", Object.keys(assistant.tools))
28
- ```
29
-
30
- Starting the assistant should pass the systemPrompt into the conversation:
31
-
32
- ```ts
33
- await assistant.start()
34
- const systemMsg = assistant.conversation.messages[0]
35
- console.log("conversation has system message:", systemMsg.role === "system")
36
- console.log("system message has content:", systemMsg.content.length > 0)
37
- console.log("system message contains omar:", systemMsg.content.toLowerCase().includes("omar"))
38
- ```
@@ -1,18 +0,0 @@
1
- # Attach Pattern
2
-
3
- Here is some problematic code:
4
-
5
- ```ts skip
6
- const { container } = context
7
- const { VoiceRouter } = await import(resolve(import.meta.dir, 'voice/router.ts'))
8
- container.features.register('voiceRouter', VoiceRouter as any)
9
- const router = container.feature('voiceRouter' as any, { enable: true }) as InstanceType<typeof VoiceRouter>
10
- ```
11
-
12
- I'd rather see
13
-
14
- ```ts skip
15
- // we know since we're inside a command this has to be our path anyway, relative to the container's cwd is how we're discovered
16
- await import(container.paths.resolve('commands','voice','router.ts')).then(({ VoiceRouter }) => container.use(VoiceRouter))
17
- ```
18
-