@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/AGENTS.md CHANGED
@@ -36,7 +36,7 @@ On the frontend the browser container is perfect for highly reactive, stateful w
36
36
  - The container is intended to provide a collection of blessed, approved, audited modules that we've built and curated together. It is intended to be the primary API and interface through the system
37
37
  - The container should provide you with everything you need, and you should not need to be importing dependencies or other modules. If you find yourself stuck by this constraint, raise this concern, and we can work on finding a way to bring in a feature or client
38
38
  - When trying to find paths in the project, use `container.paths.resolve()` or `container.paths.join()` instead of `import { resolve } from 'path'`
39
- - **NEVER import from `fs`, `path`, or other Node builtins when the container provides equivalents.** Use `container.feature('fs')` for file operations, `container.paths` for path operations. This applies to command handlers, scripts, and any code that has access to a container. The only exception is inside feature implementations themselves (e.g. `proc.ts`, `fs.ts`) where you ARE building the container primitive — those may use Node builtins directly since they can't depend on themselves.
39
+ - **NEVER import from `fs`, `path`, or other Node builtins when the container provides equivalents.** Use `container.feature('fs')` for file operations, `container.paths` for path operations. This applies everywhere command handlers, scripts, and feature implementations alike. If a container feature wraps the functionality, use it.
40
40
 
41
41
  ## Container Utilities
42
42
 
package/CLAUDE.md CHANGED
@@ -31,12 +31,17 @@ On the frontend the browser container is perfect for highly reactive, stateful w
31
31
 
32
32
  **IMPORTANT NOTE** When trying to investigate features, clients, servers, etc, see if these tools can help you first instead of searching for files and reading them that way. If youw ant to understand what they do, vs how theyre actually implemented
33
33
 
34
+ ## OpenAI Tool Schemas (Zod → JSON Schema)
35
+
36
+ **OpenAI requires `required` to list ALL property keys in `properties`**, even optional ones. Zod's `toJSONSchema()` only puts non-optional fields in `required`, which OpenAI rejects with "Missing 'X'" errors. The `assistant.addTool()` method handles this automatically by always setting `required: Object.keys(properties)`. Do NOT use `z.any()` or `z.record(z.any())` in tool schemas — Zod v4's `toJSONSchema()` cannot serialize `z.any()` and will throw `schema._zod is undefined`. Use concrete types like `z.string()` instead (e.g. accept a JSON string and parse it at runtime).
37
+
34
38
  ## Coding style and guidelines
35
39
 
36
40
  - The container is intended to provide a collection of blessed, approved, audited modules that we've built and curated together. It is intended to be the primary API and interface through the system
37
41
  - The container should provide you with everything you need, and you should not need to be importing dependencies or other modules. If you find yourself stuck by this constraint, raise this concern, and we can work on finding a way to bring in a feature or client
38
42
  - When trying to find paths in the project, use `container.paths.resolve()` or `container.paths.join()` instead of `import { resolve } from 'path'`
39
- - **NEVER import from `fs`, `path`, or other Node builtins when the container provides equivalents.** Use `container.feature('fs')` for file operations, `container.paths` for path operations. This applies to command handlers, scripts, and any code that has access to a container. The only exception is inside feature implementations themselves (e.g. `proc.ts`, `fs.ts`) where you ARE building the container primitive — those may use Node builtins directly since they can't depend on themselves.
43
+ - **`paths.join()` vs `paths.resolve()` gotcha:** `paths.join()` always prepends `container.cwd` even if you pass an absolute path as the first arg. Use `paths.resolve(absolutePath, 'sub')` when the base is already absolute (e.g. `os.tmpdir`). `resolve` respects absolute first args just like Node's `path.resolve`.
44
+ - **NEVER import from `fs`, `path`, or other Node builtins when the container provides equivalents.** Use `container.feature('fs')` for file operations, `container.paths` for path operations. This applies everywhere — command handlers, scripts, and feature implementations alike. If a container feature wraps the functionality, use it.
40
45
 
41
46
  ## Container Utilities
42
47
 
@@ -1,3 +1,2 @@
1
1
  export function started() {
2
- console.log('Assistant started!')
3
2
  }
@@ -0,0 +1,37 @@
1
+ # Luca Framework Expert
2
+
3
+ You are a Luca framework expert. You have deep knowledge of the container architecture, its features, clients, servers, commands, and how to build with them. Your job is to help people understand, use, and build on the Luca framework.
4
+
5
+ You have powerful tools to explore the framework at runtime — use them liberally before answering questions. Don't guess when you can look it up.
6
+
7
+ ## Your Tools
8
+
9
+ ### Discovery & Documentation
10
+
11
+ - **readSkill** — Read the SKILL.md learning guide and follow referenced documentation. Start here when orienting yourself.
12
+ - **readDoc** — Read any document from the docs/ folder: examples, tutorials, API references, scaffolds. Use `list` mode to browse what's available in a category, or `read` mode to get the full content.
13
+ - **lucaDescribe** — Run `luca describe` to get live documentation for any feature, client, server, or specific method/getter. This is generated from the actual source code and is always current. Use dot notation (`fs.readFile`, `ui.banner`) to drill into specific members.
14
+
15
+ ### Live Code Execution
16
+
17
+ - **lucaEval** — Execute JavaScript/TypeScript in a live container sandbox. All features are available as top-level variables (fs, git, proc, vm, etc). The last expression's value is returned. For async code, put the await call as the last expression.
18
+
19
+ ### Deep Research
20
+
21
+ - **askCodingAssistant** — Delegate deep codebase research to the coding assistant, who has ripgrep, cat, ls, sed, and awk at its disposal. Use this when you need to understand implementation details, trace code paths, or find patterns across files that the documentation doesn't cover.
22
+
23
+ ## How to Work
24
+
25
+ 1. **Start with describe.** When asked about any feature, client, or server — run `lucaDescribe` first. It's the fastest path to accurate information.
26
+ 2. **Consult docs for depth.** Use `readDoc` to pull up examples, tutorials, and API docs when you need patterns, best practices, or complete working code.
27
+ 3. **Eval to verify.** When uncertain about behavior, run it. `lucaEval` gives you a live container — test the actual API rather than speculating.
28
+ 4. **Delegate research.** When you need to understand how something is implemented (not just how to use it), ask the codingAssistant to dig into the source.
29
+ 5. **Read the SKILL.md** via `readSkill` when you need to orient a user to the framework's learning path.
30
+
31
+ ## Response Style
32
+
33
+ - Lead with working code examples when possible
34
+ - Reference specific methods and their signatures
35
+ - Explain the "why" behind patterns, not just the "what"
36
+ - When a user's approach conflicts with framework conventions, explain the idiomatic way and why it matters
37
+ - Be concise but thorough — framework users need precision
@@ -0,0 +1,9 @@
1
+ import type { Assistant } from '@soederpop/luca/agi'
2
+
3
+ declare global {
4
+ var assistant: Assistant
5
+ }
6
+
7
+ export function started() {
8
+ console.log('Luca Expert assistant started')
9
+ }
@@ -0,0 +1,177 @@
1
+ import { z } from 'zod'
2
+ import type { Assistant, AGIContainer } from '@soederpop/luca/agi'
3
+
4
+ declare global {
5
+ var assistant: Assistant
6
+ var container: AGIContainer
7
+ }
8
+
9
+ const proc = () => container.feature('proc')
10
+ const fs = () => container.feature('fs')
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // readSkill — Read the SKILL.md and progressively discover referenced docs
14
+ // ---------------------------------------------------------------------------
15
+
16
+ export const schemas = {
17
+ readSkill: z.object({
18
+ section: z.string().default('').describe(
19
+ 'Section to focus on (e.g. "Phase 1", "Phase 2", "Key Concepts"). Leave empty for the full SKILL.md.'
20
+ ),
21
+ }).describe('Read the SKILL.md learning guide for the Luca framework. Use this to orient yourself or a user to how the framework is learned and used.'),
22
+
23
+ readDoc: z.object({
24
+ mode: z.enum(['list', 'read']).describe(
25
+ '"list" to browse available documents in a category, "read" to get full content of a specific document'
26
+ ),
27
+ category: z.string().default('').describe(
28
+ 'Document category: examples, tutorials, apis, apis/features/node, apis/features/agi, apis/features/web, apis/clients, apis/servers, challenges, prompts, scaffolds, sessions, bootstrap. Leave empty when using mode "read".'
29
+ ),
30
+ path: z.string().default('').describe(
31
+ 'Relative path within docs/ to read (e.g. "examples/fs.md", "tutorials/00-bootstrap.md"). Required when mode is "read". Leave empty when using mode "list".'
32
+ ),
33
+ }).describe('Browse and read documentation from the docs/ folder — examples, tutorials, API references, scaffolds, and more.'),
34
+
35
+ lucaDescribe: z.object({
36
+ args: z.string().describe(
37
+ 'Arguments to pass to luca describe. Examples: "fs", "git", "features", "clients", "servers", "fs.readFile", "ui.banner", "express --options", "fs --methods", "fs git --examples"'
38
+ ),
39
+ }).describe('Run the luca describe CLI to get live, source-generated documentation for any feature, client, server, or specific method/getter. This is always current.'),
40
+
41
+ lucaEval: z.object({
42
+ code: z.string().describe(
43
+ 'JavaScript/TypeScript code to run in a live container. All features available as top-level vars (fs, git, proc, vm, etc). The last expression value is returned. For async: put the await expression on the last line.'
44
+ ),
45
+ }).describe('Execute code in a live Luca container sandbox. Use this to test APIs, verify behavior, and prototype ideas. Top-level await is supported — put async calls as the last expression to capture their result.'),
46
+
47
+ askCodingAssistant: z.object({
48
+ question: z.string().describe(
49
+ 'A research question for the coding assistant. Be specific about what you want to find: file paths, function implementations, patterns, class hierarchies, etc.'
50
+ ),
51
+ }).describe('Delegate deep codebase research to the coding assistant who has ripgrep, cat, ls, sed, and awk. Use for implementation details, tracing code paths, or finding patterns the docs do not cover.'),
52
+ }
53
+
54
+ export function readSkill({ section }: z.infer<typeof schemas.readSkill>): string {
55
+ const skillPath = 'docs/bootstrap/SKILL.md'
56
+
57
+ if (!fs().exists(skillPath)) {
58
+ return 'SKILL.md not found at docs/bootstrap/SKILL.md'
59
+ }
60
+
61
+ const content = fs().readFile(skillPath)
62
+
63
+ if (!section || !section.trim()) return content
64
+
65
+ // Extract section by heading match
66
+ const lines = content.split('\n')
67
+ const sectionLines: string[] = []
68
+ let capturing = false
69
+ let sectionLevel = 0
70
+
71
+ for (const line of lines) {
72
+ const headingMatch = line.match(/^(#{1,6})\s+(.*)/)
73
+
74
+ if (headingMatch) {
75
+ const level = headingMatch[1].length
76
+ const title = headingMatch[2]
77
+
78
+ if (capturing && level <= sectionLevel) break
79
+
80
+ if (title.toLowerCase().includes(section.toLowerCase())) {
81
+ capturing = true
82
+ sectionLevel = level
83
+ }
84
+ }
85
+
86
+ if (capturing) sectionLines.push(line)
87
+ }
88
+
89
+ return sectionLines.length > 0
90
+ ? sectionLines.join('\n')
91
+ : `Section "${section}" not found in SKILL.md. Available sections:\n${lines.filter(l => l.startsWith('#')).join('\n')}`
92
+ }
93
+
94
+ export function readDoc({ mode, category, path: docPath }: z.infer<typeof schemas.readDoc>): string {
95
+ if (mode === 'list') {
96
+ const dir = category ? `docs/${category}` : 'docs'
97
+
98
+ if (!fs().exists(dir)) {
99
+ return `Directory "${dir}" not found. Available top-level categories:\n${fs().readdirSync('docs').join('\n')}`
100
+ }
101
+
102
+ const entries = fs().readdirSync(dir)
103
+ return `Contents of ${dir}/:\n${entries.join('\n')}`
104
+ }
105
+
106
+ // mode === 'read'
107
+ if (!docPath) {
108
+ return 'Error: path is required when mode is "read". Use mode "list" to browse available docs first.'
109
+ }
110
+
111
+ const fullPath = docPath.startsWith('docs/') ? docPath : `docs/${docPath}`
112
+
113
+ if (!fs().exists(fullPath)) {
114
+ return `Document not found: ${fullPath}`
115
+ }
116
+
117
+ return fs().readFile(fullPath)
118
+ }
119
+
120
+ export function lucaDescribe({ args }: z.infer<typeof schemas.lucaDescribe>): string {
121
+ try {
122
+ return proc().exec(`bun run src/cli/cli.ts describe ${args}`)
123
+ } catch (err: any) {
124
+ return `Error running luca describe ${args}: ${err.message || err}`
125
+ }
126
+ }
127
+
128
+ export async function lucaEval({ code }: z.infer<typeof schemas.lucaEval>): Promise<string> {
129
+ try {
130
+ const vm = container.feature('vm')
131
+ const { result, console: calls } = await vm.runCaptured(code, {
132
+ container,
133
+ fs: container.feature('fs'),
134
+ git: container.feature('git'),
135
+ proc: container.feature('proc'),
136
+ vm,
137
+ ui: container.feature('ui'),
138
+ os: container.feature('os'),
139
+ grep: container.feature('grep'),
140
+ networking: container.feature('networking'),
141
+ })
142
+
143
+ const parts: string[] = []
144
+
145
+ if (calls.length > 0) {
146
+ const consoleLines = calls.map(c => {
147
+ const prefix = c.method === 'log' ? '' : `[${c.method}] `
148
+ return prefix + c.args.map(a => typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a)).join(' ')
149
+ })
150
+ parts.push(consoleLines.join('\n'))
151
+ }
152
+
153
+ if (result === undefined) {
154
+ parts.push('(undefined)')
155
+ } else if (result === null) {
156
+ parts.push('(null)')
157
+ } else if (typeof result === 'string') {
158
+ parts.push(result)
159
+ } else {
160
+ try { parts.push(JSON.stringify(result, null, 2)) } catch { parts.push(String(result)) }
161
+ }
162
+
163
+ return parts.join('\n')
164
+ } catch (err: any) {
165
+ return `Eval error: ${err.message || err}`
166
+ }
167
+ }
168
+
169
+ export async function askCodingAssistant({ question }: z.infer<typeof schemas.askCodingAssistant>): Promise<string> {
170
+ try {
171
+ const sub = await assistant.subagent('codingAssistant')
172
+ const answer = await sub.ask(question)
173
+ return answer
174
+ } catch (err: any) {
175
+ return `Error asking coding assistant: ${err.message || err}`
176
+ }
177
+ }
@@ -41,6 +41,30 @@ async function buildBootstrap(options: z.infer<typeof argsSchema>, context: Cont
41
41
  }
42
42
  }
43
43
 
44
+ // 3. Collect docs/examples/*.md
45
+ const examples: Record<string, string> = {}
46
+ const examplesDir = 'docs/examples'
47
+ if (fs.exists(examplesDir)) {
48
+ const exampleFiles = (await fs.readdir(examplesDir)).filter((f: string) => f.endsWith('.md'))
49
+ for (const file of exampleFiles) {
50
+ const content = await fs.readFileAsync(`${examplesDir}/${file}`)
51
+ examples[file] = content
52
+ console.log(` example/${file}: ${content.length} chars`)
53
+ }
54
+ }
55
+
56
+ // 4. Collect docs/tutorials/*.md
57
+ const tutorials: Record<string, string> = {}
58
+ const tutorialsDir = 'docs/tutorials'
59
+ if (fs.exists(tutorialsDir)) {
60
+ const tutorialFiles = (await fs.readdir(tutorialsDir)).filter((f: string) => f.endsWith('.md'))
61
+ for (const file of tutorialFiles) {
62
+ const content = await fs.readFileAsync(`${tutorialsDir}/${file}`)
63
+ tutorials[file] = content
64
+ console.log(` tutorial/${file}: ${content.length} chars`)
65
+ }
66
+ }
67
+
44
68
  const escapeForTemplate = (s: string) => s.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$\{/g, '\\${')
45
69
 
46
70
  const fileEntries = Object.entries(entries).map(([name, content]) =>
@@ -51,9 +75,17 @@ async function buildBootstrap(options: z.infer<typeof argsSchema>, context: Cont
51
75
  ` ${JSON.stringify(name)}: \`${escapeForTemplate(content)}\``
52
76
  ).join(',\n')
53
77
 
78
+ const exampleEntries = Object.entries(examples).map(([name, content]) =>
79
+ ` ${JSON.stringify(name)}: \`${escapeForTemplate(content)}\``
80
+ ).join(',\n')
81
+
82
+ const tutorialEntries = Object.entries(tutorials).map(([name, content]) =>
83
+ ` ${JSON.stringify(name)}: \`${escapeForTemplate(content)}\``
84
+ ).join(',\n')
85
+
54
86
  const output = `// Auto-generated bootstrap content
55
87
  // Generated at: ${new Date().toISOString()}
56
- // Source: docs/bootstrap/*.md, docs/bootstrap/templates/*
88
+ // Source: docs/bootstrap/*.md, docs/bootstrap/templates/*, docs/examples/*.md, docs/tutorials/*.md
57
89
  //
58
90
  // Do not edit manually. Run: luca build-bootstrap
59
91
 
@@ -64,6 +96,14 @@ ${fileEntries}
64
96
  export const bootstrapTemplates: Record<string, string> = {
65
97
  ${templateEntries}
66
98
  }
99
+
100
+ export const bootstrapExamples: Record<string, string> = {
101
+ ${exampleEntries}
102
+ }
103
+
104
+ export const bootstrapTutorials: Record<string, string> = {
105
+ ${tutorialEntries}
106
+ }
67
107
  `
68
108
 
69
109
  fs.ensureFolder('src/bootstrap')
@@ -120,7 +120,6 @@
120
120
  - [OpenAICodex (features.openaiCodex)](./apis/features/agi/openai-codex.md)
121
121
  - [OpenAPI (features.openapi)](./apis/features/agi/openapi.md)
122
122
  - [SemanticSearch (features.semanticSearch)](./apis/features/agi/semantic-search.md)
123
- - [SkillsLibrary (features.skillsLibrary)](./apis/features/agi/skills-library.md)
124
123
  - [ContainerLink (features.containerLink)](./apis/features/node/container-link.md)
125
124
  - [ContentDb (features.contentDb)](./apis/features/node/content-db.md)
126
125
  - [DiskCache (features.diskCache)](./apis/features/node/disk-cache.md)
@@ -40,7 +40,7 @@ Send a PATCH request.
40
40
  | `data` | `any` | | Request body |
41
41
  | `options` | `AxiosRequestConfig` | | Additional axios request config |
42
42
 
43
- **Returns:** `void`
43
+ **Returns:** `Promise<any>`
44
44
 
45
45
 
46
46
 
@@ -56,7 +56,7 @@ Send a PUT request.
56
56
  | `data` | `any` | | Request body |
57
57
  | `options` | `AxiosRequestConfig` | | Additional axios request config |
58
58
 
59
- **Returns:** `void`
59
+ **Returns:** `Promise<any>`
60
60
 
61
61
 
62
62
 
@@ -72,7 +72,7 @@ Send a POST request.
72
72
  | `data` | `any` | | Request body |
73
73
  | `options` | `AxiosRequestConfig` | | Additional axios request config |
74
74
 
75
- **Returns:** `void`
75
+ **Returns:** `Promise<any>`
76
76
 
77
77
 
78
78
 
@@ -88,7 +88,7 @@ Send a DELETE request.
88
88
  | `params` | `any` | | Query parameters |
89
89
  | `options` | `AxiosRequestConfig` | | Additional axios request config |
90
90
 
91
- **Returns:** `void`
91
+ **Returns:** `Promise<any>`
92
92
 
93
93
 
94
94
 
@@ -104,7 +104,7 @@ Send a GET request.
104
104
  | `params` | `any` | | Query parameters |
105
105
  | `options` | `AxiosRequestConfig` | | Additional axios request config |
106
106
 
107
- **Returns:** `void`
107
+ **Returns:** `Promise<any>`
108
108
 
109
109
 
110
110
 
@@ -1,6 +1,6 @@
1
1
  # Assistant (features.assistant)
2
2
 
3
- An Assistant is a combination of a system prompt and tool calls that has a conversation with an LLM. You define an assistant by creating a folder with CORE.md (system prompt), tools.ts (tool implementations), and hooks.ts (event handlers).
3
+ An Assistant is a wrapper around an LLM chat thread where you supply a system prompt and tools. You can optionally use a folder to load the system prompt (CORE.md), tool implementations (tools.ts), and event hooks (hooks.ts) from disk. The AssistantsManager feature will automatically discover any assistant definitions that live on disk in your project.
4
4
 
5
5
  ## Usage
6
6
 
@@ -1,14 +1,14 @@
1
1
  # ConversationHistory (features.conversationHistory)
2
2
 
3
- Persists conversations to disk using the diskCache feature (cacache). Each conversation is stored as a JSON blob keyed by ID, with metadata stored alongside for efficient listing and search without loading full message arrays.
3
+ A queryable store, backed by the file system, for maintaining conversation history and easily restoring it. Each conversation is stored as a JSON record keyed by ID, with lightweight metadata stored alongside for efficient listing and search without loading full message arrays.
4
4
 
5
5
  ## Usage
6
6
 
7
7
  ```ts
8
8
  container.feature('conversationHistory', {
9
- // Custom cache directory for conversation storage
9
+ // Directory for conversation storage
10
10
  cachePath,
11
- // Namespace prefix for cache keys to isolate datasets
11
+ // Namespace prefix to isolate datasets
12
12
  namespace,
13
13
  })
14
14
  ```
@@ -17,8 +17,8 @@ container.feature('conversationHistory', {
17
17
 
18
18
  | Property | Type | Description |
19
19
  |----------|------|-------------|
20
- | `cachePath` | `string` | Custom cache directory for conversation storage |
21
- | `namespace` | `string` | Namespace prefix for cache keys to isolate datasets |
20
+ | `cachePath` | `string` | Directory for conversation storage |
21
+ | `namespace` | `string` | Namespace prefix to isolate datasets |
22
22
 
23
23
  ## Methods
24
24
 
@@ -307,8 +307,7 @@ Delete all conversations matching a thread prefix.
307
307
 
308
308
  | Property | Type | Description |
309
309
  |----------|------|-------------|
310
- | `diskCache` | `DiskCache` | |
311
- | `namespace` | `string` | |
310
+ | `namespace` | `string` | The namespace prefix used for key isolation |
312
311
 
313
312
  ## Events (Zod v4 schema)
314
313
 
@@ -1,6 +1,6 @@
1
1
  # Conversation (features.conversation)
2
2
 
3
- A self-contained conversation with OpenAI that supports streaming, tool calling, and message state management.
3
+ An observable conversation class that enables long-running chat threads with an LLM. Supports streaming responses, tool calling with automatic handler dispatch, message state management, and context window compaction. Works with both local (Ollama) and remote (OpenAI) backends.
4
4
 
5
5
  ## Usage
6
6
 
@@ -1,6 +1,6 @@
1
1
  # SemanticSearch (features.semanticSearch)
2
2
 
3
- No description provided
3
+ A vector search engine backed by SQLite that indexes documents with embeddings for semantic, keyword, and hybrid search. Supports local GGUF embedding models and configurable chunking strategies for splitting documents into searchable segments.
4
4
 
5
5
  ## Usage
6
6
 
@@ -32,7 +32,7 @@ The `luca` binary is available in the path. Key commands:
32
32
  1. **Discover** — Run `luca describe features`, `luca describe clients`, `luca describe servers` to see what's available. Then `luca describe <name>` for full docs on any helper, or `luca describe <name>.<member>` to drill into a specific method or getter. This is your first move, always. (See `.claude/skills/luca-framework/SKILL.md` for the full mental model.)
33
33
  2. **Build** — Run `luca scaffold <type> --tutorial` before creating a new helper. It covers the full guide for that type.
34
34
  3. **Prototype** — Use `luca eval "expression"` to test container code before wiring up full handlers. Reach for eval when you're stuck — it gives you full runtime access.
35
- 4. **Reference** — Browse `.claude/skills/luca-framework/references/api-docs/` for pre-generated API docs
35
+ 4. **Reference** — Browse `.claude/skills/luca-framework/references/` for pre-generated API docs, runnable examples, and tutorials
36
36
 
37
37
  ## Project Structure
38
38
 
@@ -1,17 +1,19 @@
1
1
  ---
2
2
  name: Using the luca framework
3
- description: Learn the luca container discover what's available with luca describe, build new helpers with luca scaffold, and prototype with luca eval
3
+ description: The @soederpop/luca framework, when you see a project with docs/ commands/ features/ luca.cli.ts endpoints/ folders, or @soederpop/luca is in the package.json, or the user is asking you to develop a new Luca feature, use this skill to learn about the APIs and how to learn the framework at runtime. The luca cli bundles all of the documentation in a searchable, progressively learnable interface designed for students and AI assistants alike
4
4
  ---
5
5
  # Luca: Learning the Container
6
6
 
7
7
  The Luca framework `@soederpop/luca` ships a `luca` binary — a bun-based CLI for a dependency injection container. This project is based on it if this skill is present. The container auto-discovers modules in `commands/`, `clients/`, `servers/`, `features/`, and `endpoints/` folders.
8
8
 
9
+ The `luca` cli loads typescript modules in through its VM which injects a `container` global that is a singleton object from which you can learn about, and access all different kinds of utils and Helpers (features, clients, servers, commands, and compositions thereof)
10
+
9
11
  There are three things to learn, in this order:
10
12
 
11
13
  1. **Discover** what the container can do — `luca describe`
12
14
  2. **Build** new helpers when your project needs them — `luca scaffold`
13
15
  3. **Prototype** and debug with live code — `luca eval`
14
-
16
+ 4. **Write Runnable Markdown** a great usecase is `luca run markdown.md` where the markdown codeblocks are executed inside the Luca VM.
15
17
  ---
16
18
 
17
19
  ## Phase 1: Discover with `luca describe`
@@ -248,4 +250,6 @@ This is useful inside commands and scripts where you need introspection data pro
248
250
 
249
251
  ## Reference
250
252
 
251
- See `references/api-docs/` for the full pre-generated API reference for every built-in feature, client, and server.
253
+ - `references/api-docs/` full pre-generated API reference for every built-in feature, client, and server
254
+ - `references/examples/` — runnable example docs for each feature (e.g. `fs.md`, `git.md`, `proc.md`)
255
+ - `references/tutorials/` — step-by-step tutorials covering the container, helpers, commands, endpoints, and more
@@ -22,4 +22,9 @@
22
22
  export async function main(container: any) {
23
23
  // Discover project-level helpers (commands/, features/, endpoints/)
24
24
  await container.helpers.discoverAll()
25
+
26
+ // Handle unknown commands gracefully instead of silently failing
27
+ container.onMissingCommand(async ({ phrase }: { phrase: string }) => {
28
+ container.command('help').dispatch()
29
+ })
25
30
  }
@@ -17,7 +17,7 @@ import { Server, servers, ServerStateSchema } from '@soederpop/luca'
17
17
  import { commands, CommandOptionsSchema } from '@soederpop/luca'
18
18
  ```
19
19
 
20
- Never import from `fs`, `path`, `crypto`, or other Node builtins. Never import third-party packages in consumer code. The only exception is inside helper implementations themselves — a feature that wraps a library may import it.
20
+ Never import from `fs`, `path`, `crypto`, or other Node builtins. Never import third-party packages in consumer code. If a container feature wraps the functionality, use it.
21
21
 
22
22
  ## Zod v4
23
23
 
@@ -140,6 +140,24 @@ luca eval "grep.search('.', 'TODO')"
140
140
  | Full container overview? | `container.inspectAsText()` |
141
141
  | CLI docs for a helper? | `luca describe <name>` |
142
142
 
143
+ ## Gotchas
144
+
145
+ ### `paths.join()` vs `paths.resolve()`
146
+
147
+ `container.paths.join()` and `container.paths.resolve()` are Node's `path.join` and `path.resolve` curried with `container.cwd`. This means `paths.join()` always prepends `cwd` — even if you pass an absolute path as the first argument.
148
+
149
+ ```js
150
+ // WRONG — paths.join will prepend cwd to the absolute tmpdir path
151
+ const bad = container.paths.join(os.tmpdir, 'mydir')
152
+ // => "/your/project/tmp/mydir" (not what you want)
153
+
154
+ // RIGHT — paths.resolve respects absolute first args
155
+ const good = container.paths.resolve(os.tmpdir, 'mydir')
156
+ // => "/tmp/mydir"
157
+ ```
158
+
159
+ **Rule of thumb:** Use `paths.join()` for project-relative paths, `paths.resolve()` when the base is already absolute.
160
+
143
161
  ## What's Next
144
162
 
145
163
  - [The Container](./02-container.md) — deep dive into state, events, and lifecycle
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soederpop/luca",
3
- "version": "0.0.23",
3
+ "version": "0.0.26",
4
4
  "website": "https://luca.soederpop.com",
5
5
  "description": "lightweight universal conversational architecture AKA Le Ultimate Component Architecture AKA Last Universal Common Ancestor, part AI part Human",
6
6
  "author": "jon soeder aka the people's champ <jon@soederpop.com>",
@@ -48,7 +48,7 @@
48
48
  "clean": "rm -rf dist build package",
49
49
  "build:web": "bun run scripts/build-web.ts",
50
50
  "build:introspection": "bun run src/cli/cli.ts introspect",
51
- "compile": "bun run build:introspection && bun run build:scaffolds && bun run build:bootstrap && bun build ./src/cli/cli.ts --compile --outfile dist/luca --external node-llama-cpp",
51
+ "compile": "bun run build:introspection && bun run build:scaffolds && bun run build:bootstrap && bash scripts/stamp-build.sh && bun build ./src/cli/cli.ts --compile --outfile dist/luca --external node-llama-cpp",
52
52
  "typecheck": "tsc -p tsconfig.json --noEmit",
53
53
  "build:scaffolds": "bun run src/cli/cli.ts build-scaffolds",
54
54
  "build:bootstrap": "bun run src/cli/cli.ts build-bootstrap",
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env bash
2
+ # Stamps git SHA, branch, and date into src/cli/build-info.ts before compile
3
+ SHA=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")
4
+ BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
5
+ DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
6
+
7
+ cat > src/cli/build-info.ts <<EOF
8
+ // Generated at compile time — do not edit manually
9
+ export const BUILD_SHA = '${SHA}'
10
+ export const BUILD_BRANCH = '${BRANCH}'
11
+ export const BUILD_DATE = '${DATE}'
12
+ EOF
@@ -0,0 +1,10 @@
1
+ import container from '@soederpop/luca/agi'
2
+
3
+ const reader = container.feature('docsReader', {
4
+ contentDb: (await container.docs.load())
5
+ })
6
+
7
+ const resp = await reader.ask('What tutorials are available? What are they about?')
8
+
9
+ console.log(resp)
10
+
@@ -6,10 +6,11 @@ import { ElevenLabsClient } from '../clients/elevenlabs'
6
6
  import { ClaudeCode } from './features/claude-code'
7
7
  import { OpenAICodex } from './features/openai-codex'
8
8
  import { Conversation } from './features/conversation'
9
- import { SkillsLibrary } from './features/skills-library'
10
9
  import { ConversationHistory } from './features/conversation-history'
11
10
  import { Assistant } from './features/assistant'
12
11
  import { AssistantsManager } from './features/assistants-manager'
12
+ import { DocsReader } from './features/docs-reader'
13
+ import { SkillsLibrary } from './features/skills-library'
13
14
  import { SemanticSearch } from '@soederpop/luca/node/features/semantic-search'
14
15
  import { ContentDb } from '@soederpop/luca/node/features/content-db'
15
16
 
@@ -20,10 +21,11 @@ export {
20
21
  ClaudeCode,
21
22
  OpenAICodex,
22
23
  Conversation,
23
- SkillsLibrary,
24
24
  ConversationHistory,
25
25
  Assistant,
26
26
  AssistantsManager,
27
+ DocsReader,
28
+ SkillsLibrary,
27
29
  SemanticSearch,
28
30
  ContentDb,
29
31
  NodeContainer,
@@ -42,10 +44,11 @@ export interface AGIFeatures extends NodeFeatures {
42
44
  conversation: typeof Conversation
43
45
  claudeCode: typeof ClaudeCode
44
46
  openaiCodex: typeof OpenAICodex
45
- skillsLibrary: typeof SkillsLibrary
46
47
  conversationHistory: typeof ConversationHistory
47
48
  assistant: typeof Assistant
48
49
  assistantsManager: typeof AssistantsManager
50
+ docsReader: typeof DocsReader
51
+ skillsLibrary: typeof SkillsLibrary
49
52
  }
50
53
 
51
54
  export interface ConversationFactoryOptions {
@@ -73,7 +76,6 @@ export class AGIContainer<
73
76
  openai!: OpenAIClient
74
77
  claudeCode?: ClaudeCode
75
78
  openaiCodex?: OpenAICodex
76
- skillsLibrary?: SkillsLibrary
77
79
  conversationHistory?: ConversationHistory
78
80
  docs!: ContentDb
79
81
 
@@ -114,10 +116,11 @@ const container = new AGIContainer()
114
116
  .use(ClaudeCode)
115
117
  .use(OpenAICodex)
116
118
  .use(Conversation)
117
- .use(SkillsLibrary)
118
119
  .use(ConversationHistory)
119
120
  .use(Assistant)
120
121
  .use(AssistantsManager)
122
+ .use(DocsReader)
123
+ .use(SkillsLibrary)
121
124
  .use(SemanticSearch)
122
125
 
123
126
  container.docs = container.feature('contentDb', {