@chamba/core 0.1.0 → 0.2.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.
package/dist/index.d.ts CHANGED
@@ -1,12 +1,52 @@
1
+ import { z } from 'zod';
2
+
3
+ /** The roles a model can play when the editor delegates work. */
4
+ type AgentRole = 'orchestrator' | 'planner' | 'reviewer' | 'implementer' | 'tester' | 'summarizer' | 'researcher';
5
+ /** Canonical order — this is also the order the install wizard walks. */
6
+ declare const AGENT_ROLES: readonly AgentRole[];
1
7
  /**
2
- * Time behind a port so date-dependent logic (plan filenames, vault notes) is
3
- * testable and `@chamba/core` stays free of ambient `Date` calls.
8
+ * Abstract effort level. chamba is multi-editor, so this is a provider-neutral
9
+ * enum; each model in the catalog maps these four levels to its own native
10
+ * vocabulary (Claude Code `effort:`, OpenAI `reasoning_effort`, Gemini
11
+ * `thinkingLevel`, …). `extreme` is the ceiling — e.g. it becomes `max` in
12
+ * Claude Code and `xhigh` in OpenAI.
4
13
  */
5
- interface ClockPort {
6
- now(): Date;
7
- /** Current date as `YYYY-MM-DD`. */
8
- today(): string;
14
+ type Effort = 'low' | 'medium' | 'high' | 'extreme';
15
+ declare const EFFORT_LEVELS: readonly Effort[];
16
+ /** Semantic hint the editor's model can read to decide how to delegate. */
17
+ type ReasoningPriority = 'speed' | 'balanced' | 'thoroughness';
18
+ declare const REASONING_PRIORITIES: readonly ReasoningPriority[];
19
+ /** One-line description per role, shown in the wizard and `chamba-config show`. */
20
+ declare const ROLE_DESCRIPTIONS: Record<AgentRole, string>;
21
+
22
+ /** What model + effort to use for one role. */
23
+ interface AgentConfig {
24
+ /** A model id from the catalog (e.g. `claude-opus-4-8`). */
25
+ model: string;
26
+ effort: Effort;
27
+ reasoning_priority: ReasoningPriority;
28
+ }
29
+ /**
30
+ * The in-memory config. `defaults` always has all seven roles fully populated
31
+ * (the compiled `DEFAULT_CONFIG` provides them); `overrides` is an optional,
32
+ * partial set of per-role, per-field tweaks layered on top.
33
+ */
34
+ interface ChambaConfig {
35
+ version: 1;
36
+ defaults: Record<AgentRole, AgentConfig>;
37
+ overrides?: Partial<Record<AgentRole, Partial<AgentConfig>>>;
9
38
  }
39
+ /** A fully-resolved config: every role present, every field filled. */
40
+ type ResolvedConfig = Record<AgentRole, AgentConfig>;
41
+
42
+ /**
43
+ * The compiled, hardcoded defaults — the single source of truth for the
44
+ * recommended reparto of models per role. The philosophy: critical reasoning
45
+ * gets powerful models, mechanical execution gets fast/cheap ones. Users
46
+ * override these via `~/.chamba/config.json`; they are never written to disk
47
+ * unless the user runs the wizard or `chamba-config`.
48
+ */
49
+ declare const DEFAULT_CONFIG: ChambaConfig;
10
50
 
11
51
  /** A single entry returned when listing a directory. */
12
52
  interface DirEntry {
@@ -30,6 +70,124 @@ interface FilesystemPort {
30
70
  remove(path: string): Promise<void>;
31
71
  }
32
72
 
73
+ /** Raised when a config is invalid and the caller asked to fail hard. */
74
+ declare class ConfigError extends Error {
75
+ constructor(message: string);
76
+ }
77
+ type ConfigSourceKind = 'default' | 'global' | 'project';
78
+ /** Where a layer came from and whether it applied. */
79
+ interface ConfigSource {
80
+ kind: ConfigSourceKind;
81
+ path?: string;
82
+ status: 'applied' | 'missing' | 'invalid';
83
+ error?: string;
84
+ }
85
+ interface LoadConfigResult {
86
+ config: ResolvedConfig;
87
+ sources: ConfigSource[];
88
+ }
89
+ interface LoadConfigOptions {
90
+ /** Path to `~/.chamba/config.json`. */
91
+ globalPath?: string;
92
+ /** Path to `<cwd>/.chamba/config.json`. */
93
+ projectPath?: string;
94
+ }
95
+ /**
96
+ * Resolve the effective config by layering DEFAULT_CONFIG ← global ← project,
97
+ * per role and per field. A missing file is skipped; an invalid one (bad JSON
98
+ * or failed schema) is skipped with a recorded warning — loading never throws,
99
+ * so a corrupt config can never take down the MCP server.
100
+ */
101
+ declare function loadConfig(fs: FilesystemPort, opts?: LoadConfigOptions): Promise<LoadConfigResult>;
102
+
103
+ /** The resolved config for one role. */
104
+ declare function resolveRole(config: ResolvedConfig, role: AgentRole): AgentConfig;
105
+ /**
106
+ * A short, deterministic (no-LLM) hint the editor's model can read to decide
107
+ * how to delegate this role — e.g. "use a model optimized for deep reasoning;
108
+ * suggested: claude-opus-4-8 with maximum effort."
109
+ */
110
+ declare function buildHint(role: AgentRole, cfg: AgentConfig): string;
111
+
112
+ /**
113
+ * What a config file on disk may contain. Both `defaults` and `overrides` are
114
+ * optional and partial per role — a project file can carry just one role's one
115
+ * field. Unknown roles and unknown models are rejected with clear messages.
116
+ */
117
+ declare const configFileSchema: z.ZodObject<{
118
+ version: z.ZodLiteral<1>;
119
+ defaults: z.ZodOptional<z.ZodRecord<z.ZodEnum<[AgentRole, ...AgentRole[]]>, z.ZodObject<{
120
+ model: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
121
+ effort: z.ZodOptional<z.ZodEnum<[Effort, ...Effort[]]>>;
122
+ reasoning_priority: z.ZodOptional<z.ZodEnum<[ReasoningPriority, ...ReasoningPriority[]]>>;
123
+ }, "strip", z.ZodTypeAny, {
124
+ model?: string | undefined;
125
+ effort?: Effort | undefined;
126
+ reasoning_priority?: ReasoningPriority | undefined;
127
+ }, {
128
+ model?: string | undefined;
129
+ effort?: Effort | undefined;
130
+ reasoning_priority?: ReasoningPriority | undefined;
131
+ }>>>;
132
+ overrides: z.ZodOptional<z.ZodRecord<z.ZodEnum<[AgentRole, ...AgentRole[]]>, z.ZodObject<{
133
+ model: z.ZodOptional<z.ZodEffects<z.ZodString, string, string>>;
134
+ effort: z.ZodOptional<z.ZodEnum<[Effort, ...Effort[]]>>;
135
+ reasoning_priority: z.ZodOptional<z.ZodEnum<[ReasoningPriority, ...ReasoningPriority[]]>>;
136
+ }, "strip", z.ZodTypeAny, {
137
+ model?: string | undefined;
138
+ effort?: Effort | undefined;
139
+ reasoning_priority?: ReasoningPriority | undefined;
140
+ }, {
141
+ model?: string | undefined;
142
+ effort?: Effort | undefined;
143
+ reasoning_priority?: ReasoningPriority | undefined;
144
+ }>>>;
145
+ }, "strict", z.ZodTypeAny, {
146
+ version: 1;
147
+ defaults?: Partial<Record<AgentRole, {
148
+ model?: string | undefined;
149
+ effort?: Effort | undefined;
150
+ reasoning_priority?: ReasoningPriority | undefined;
151
+ }>> | undefined;
152
+ overrides?: Partial<Record<AgentRole, {
153
+ model?: string | undefined;
154
+ effort?: Effort | undefined;
155
+ reasoning_priority?: ReasoningPriority | undefined;
156
+ }>> | undefined;
157
+ }, {
158
+ version: 1;
159
+ defaults?: Partial<Record<AgentRole, {
160
+ model?: string | undefined;
161
+ effort?: Effort | undefined;
162
+ reasoning_priority?: ReasoningPriority | undefined;
163
+ }>> | undefined;
164
+ overrides?: Partial<Record<AgentRole, {
165
+ model?: string | undefined;
166
+ effort?: Effort | undefined;
167
+ reasoning_priority?: ReasoningPriority | undefined;
168
+ }>> | undefined;
169
+ }>;
170
+ type ConfigFile = z.infer<typeof configFileSchema>;
171
+ type ParseResult = {
172
+ ok: true;
173
+ value: ConfigFile;
174
+ } | {
175
+ ok: false;
176
+ error: string;
177
+ };
178
+ /** Validate raw (parsed-JSON) data as a chamba config file. */
179
+ declare function parseChambaConfig(raw: unknown): ParseResult;
180
+
181
+ /**
182
+ * Time behind a port so date-dependent logic (plan filenames, vault notes) is
183
+ * testable and `@chamba/core` stays free of ambient `Date` calls.
184
+ */
185
+ interface ClockPort {
186
+ now(): Date;
187
+ /** Current date as `YYYY-MM-DD`. */
188
+ today(): string;
189
+ }
190
+
33
191
  interface Memory {
34
192
  key: string;
35
193
  content: string;
@@ -77,6 +235,34 @@ declare class FilesystemMemoryStore implements MemoryStore {
77
235
  private read;
78
236
  }
79
237
 
238
+ type ModelProvider = 'anthropic' | 'openai' | 'google' | 'ollama';
239
+ interface ModelInfo {
240
+ /** The exact id used by the provider / editor (e.g. `claude-opus-4-8`). */
241
+ id: string;
242
+ provider: ModelProvider;
243
+ /** Human label for the wizard. */
244
+ label: string;
245
+ /** One-line description for the wizard. */
246
+ description: string;
247
+ /** Whether the model has an adjustable thinking/reasoning budget. */
248
+ supports_thinking: boolean;
249
+ /**
250
+ * Maps chamba's abstract effort to the provider's native value, or `null`
251
+ * when the provider has no effort knob (effort is then advisory only).
252
+ */
253
+ effortMap: Record<Effort, string | null>;
254
+ }
255
+ declare const MODEL_CATALOG: readonly ModelInfo[];
256
+ /** Look up a model by exact id. */
257
+ declare function getModel(id: string): ModelInfo | undefined;
258
+ /** All models for a given provider. */
259
+ declare function modelsByProvider(provider: ModelProvider): ModelInfo[];
260
+ /**
261
+ * Translate chamba's abstract effort to the model's native value. Returns
262
+ * `null` when the provider has no effort knob (effort is advisory).
263
+ */
264
+ declare function resolveEffort(model: ModelInfo, effort: Effort): string | null;
265
+
80
266
  interface NoteFields {
81
267
  title: string;
82
268
  /** `YYYY-MM-DD`. */
@@ -430,4 +616,4 @@ declare class WorktreeManager {
430
616
  cleanup(root: string, branch: string): Promise<CleanupResult>;
431
617
  }
432
618
 
433
- export { type BranchNameInput, type BuiltContext, type CleanupResult, type ClockPort, type ContextBuildInput, ContextBuilder, type CreateWorktreeInput, type DetectOptions, type DirEntry, FakeProcess, FilesystemMemoryStore, type FilesystemPort, type GeneratePlanInput, GitDetector, type Issue, type IssueSeverity, type ListedWorktree, MEMORY_DIR, type Memory, MemoryFilesystem, type MemoryStore, type NoteFields, ObsidianDetector, type PlanReview, type ProcessExecOptions, type ProcessHandler, type ProcessPort, type ProcessResult, type ProjectRef, type RecordedCall, type RelevantNote, type RememberInput, type ReviewInput, Reviewer, type SubtaskSpec, VAULT_NOTES_DIR, type ValidatePlanInput, type ValidationResult, type VaultDetection, VaultWriter, WORKSPACE_DIR, WORKSPACE_FILE, WORKSPACE_RELATIVE_PATH, type WorkerKind, type Workspace, WorkspaceScanner, WorktreeError, type WorktreeHandle, WorktreeManager, type WriteNoteInput, type WriteNoteResult, basename, buildBranchName, diffLines, dirname, extname, generatePlanTemplate, joinPath, renderNote, renderWorkspaceMarkdown, slugify, slugifyForGit, suggestFilesLikelyTouched, suggestSubtasks, textsEqual, validatePlan, worktreeRelativePath };
619
+ export { AGENT_ROLES, type AgentConfig, type AgentRole, type BranchNameInput, type BuiltContext, type ChambaConfig, type CleanupResult, type ClockPort, ConfigError, type ConfigFile, type ConfigSource, type ConfigSourceKind, type ContextBuildInput, ContextBuilder, type CreateWorktreeInput, DEFAULT_CONFIG, type DetectOptions, type DirEntry, EFFORT_LEVELS, type Effort, FakeProcess, FilesystemMemoryStore, type FilesystemPort, type GeneratePlanInput, GitDetector, type Issue, type IssueSeverity, type ListedWorktree, type LoadConfigOptions, type LoadConfigResult, MEMORY_DIR, MODEL_CATALOG, type Memory, MemoryFilesystem, type MemoryStore, type ModelInfo, type ModelProvider, type NoteFields, ObsidianDetector, type ParseResult, type PlanReview, type ProcessExecOptions, type ProcessHandler, type ProcessPort, type ProcessResult, type ProjectRef, REASONING_PRIORITIES, ROLE_DESCRIPTIONS, type ReasoningPriority, type RecordedCall, type RelevantNote, type RememberInput, type ResolvedConfig, type ReviewInput, Reviewer, type SubtaskSpec, VAULT_NOTES_DIR, type ValidatePlanInput, type ValidationResult, type VaultDetection, VaultWriter, WORKSPACE_DIR, WORKSPACE_FILE, WORKSPACE_RELATIVE_PATH, type WorkerKind, type Workspace, WorkspaceScanner, WorktreeError, type WorktreeHandle, WorktreeManager, type WriteNoteInput, type WriteNoteResult, basename, buildBranchName, buildHint, configFileSchema, diffLines, dirname, extname, generatePlanTemplate, getModel, joinPath, loadConfig, modelsByProvider, parseChambaConfig, renderNote, renderWorkspaceMarkdown, resolveEffort, resolveRole, slugify, slugifyForGit, suggestFilesLikelyTouched, suggestSubtasks, textsEqual, validatePlan, worktreeRelativePath };
package/dist/index.js CHANGED
@@ -1,3 +1,302 @@
1
+ // src/config/defaults.ts
2
+ var DEFAULT_CONFIG = {
3
+ version: 1,
4
+ defaults: {
5
+ // The brain: decomposes and decides. Worth the tokens.
6
+ orchestrator: { model: "claude-opus-4-8", effort: "high", reasoning_priority: "thoroughness" },
7
+ // Max reasoning when planning is delegated; invoked rarely, so it's cheap overall.
8
+ planner: { model: "claude-opus-4-8", effort: "extreme", reasoning_priority: "thoroughness" },
9
+ // Critical audit; deep reasoning but doesn't need the very latest model.
10
+ reviewer: { model: "claude-opus-4-7", effort: "high", reasoning_priority: "thoroughness" },
11
+ // Executes clear specs; speed matters, medium reasoning is enough.
12
+ implementer: { model: "claude-sonnet-4-6", effort: "medium", reasoning_priority: "balanced" },
13
+ // Tests over already-implemented code; same profile as the implementer.
14
+ tester: { model: "claude-sonnet-4-6", effort: "medium", reasoning_priority: "balanced" },
15
+ // Summaries are mechanical; a fast, cheap model is perfect.
16
+ summarizer: { model: "claude-haiku-4-5", effort: "low", reasoning_priority: "speed" },
17
+ // Research and synthesis; high reasoning, but doesn't need Opus 4.8.
18
+ researcher: { model: "claude-opus-4-7", effort: "high", reasoning_priority: "thoroughness" }
19
+ }
20
+ };
21
+
22
+ // src/config/roles.ts
23
+ var AGENT_ROLES = [
24
+ "orchestrator",
25
+ "planner",
26
+ "reviewer",
27
+ "implementer",
28
+ "tester",
29
+ "summarizer",
30
+ "researcher"
31
+ ];
32
+ var EFFORT_LEVELS = ["low", "medium", "high", "extreme"];
33
+ var REASONING_PRIORITIES = [
34
+ "speed",
35
+ "balanced",
36
+ "thoroughness"
37
+ ];
38
+ var ROLE_DESCRIPTIONS = {
39
+ orchestrator: "Decomposes the task, plans, and decides \u2014 the brain.",
40
+ planner: "Produces the detailed plan when the orchestrator delegates planning.",
41
+ reviewer: "Audits the plan and code with critical judgement.",
42
+ implementer: "Writes code against a clear, reviewed spec.",
43
+ tester: "Writes and runs tests over already-implemented code.",
44
+ summarizer: "Summarizes what happened \u2014 mechanical, cheap.",
45
+ researcher: "Investigates context, reads docs, synthesizes."
46
+ };
47
+
48
+ // src/config/schema.ts
49
+ import { z } from "zod";
50
+
51
+ // src/models/catalog.ts
52
+ var ANTHROPIC_EFFORT = {
53
+ low: "low",
54
+ medium: "medium",
55
+ high: "high",
56
+ extreme: "max"
57
+ };
58
+ var OPENAI_EFFORT = {
59
+ low: "low",
60
+ medium: "medium",
61
+ high: "high",
62
+ extreme: "xhigh"
63
+ };
64
+ var GEMINI_FLASH_EFFORT = {
65
+ low: "low",
66
+ medium: "medium",
67
+ high: "high",
68
+ extreme: "high"
69
+ };
70
+ var GEMINI_PRO_EFFORT = {
71
+ low: "low",
72
+ medium: "high",
73
+ high: "high",
74
+ extreme: "high"
75
+ };
76
+ var NO_EFFORT = {
77
+ low: null,
78
+ medium: null,
79
+ high: null,
80
+ extreme: null
81
+ };
82
+ var MODEL_CATALOG = [
83
+ {
84
+ id: "claude-opus-4-8",
85
+ provider: "anthropic",
86
+ label: "Claude Opus 4.8",
87
+ description: "Flagship reasoning. Best for critical decomposition and planning.",
88
+ supports_thinking: true,
89
+ effortMap: ANTHROPIC_EFFORT
90
+ },
91
+ {
92
+ id: "claude-opus-4-7",
93
+ provider: "anthropic",
94
+ label: "Claude Opus 4.7",
95
+ description: "Previous-gen reasoning, still strong. Good for review and research.",
96
+ supports_thinking: true,
97
+ effortMap: ANTHROPIC_EFFORT
98
+ },
99
+ {
100
+ id: "claude-sonnet-4-6",
101
+ provider: "anthropic",
102
+ label: "Claude Sonnet 4.6",
103
+ description: "Balanced and fast. Ideal for implementation against clear specs.",
104
+ supports_thinking: true,
105
+ effortMap: ANTHROPIC_EFFORT
106
+ },
107
+ {
108
+ id: "claude-haiku-4-5",
109
+ provider: "anthropic",
110
+ label: "Claude Haiku 4.5",
111
+ description: "Cheapest and fastest. Perfect for mechanical work like summaries.",
112
+ supports_thinking: true,
113
+ effortMap: ANTHROPIC_EFFORT
114
+ },
115
+ {
116
+ id: "gpt-5.5",
117
+ provider: "openai",
118
+ label: "GPT-5.5",
119
+ description: "OpenAI flagship reasoning model.",
120
+ supports_thinking: true,
121
+ effortMap: OPENAI_EFFORT
122
+ },
123
+ {
124
+ id: "gpt-5.5-mini",
125
+ provider: "openai",
126
+ label: "GPT-5.5 mini",
127
+ description: "Smaller, faster OpenAI model for execution.",
128
+ supports_thinking: true,
129
+ // `xhigh` is unconfirmed for the mini; cap `extreme` at `high` to be safe.
130
+ effortMap: { low: "low", medium: "medium", high: "high", extreme: "high" }
131
+ },
132
+ {
133
+ id: "gemini-3.1-pro-preview",
134
+ provider: "google",
135
+ label: "Gemini 3.1 Pro (preview)",
136
+ description: "Google flagship reasoning model. thinkingLevel: low | high.",
137
+ supports_thinking: true,
138
+ effortMap: GEMINI_PRO_EFFORT
139
+ },
140
+ {
141
+ id: "gemini-3.5-flash",
142
+ provider: "google",
143
+ label: "Gemini 3.5 Flash",
144
+ description: "Fast, cheap, GA. thinkingLevel: minimal | low | medium | high.",
145
+ supports_thinking: true,
146
+ effortMap: GEMINI_FLASH_EFFORT
147
+ },
148
+ {
149
+ id: "qwen2.5-coder:7b",
150
+ provider: "ollama",
151
+ label: "Qwen2.5 Coder 7B (Ollama)",
152
+ description: "Local coding model via Ollama. No adjustable thinking.",
153
+ supports_thinking: false,
154
+ effortMap: NO_EFFORT
155
+ },
156
+ {
157
+ id: "deepseek-r1:7b",
158
+ provider: "ollama",
159
+ label: "DeepSeek-R1 7B (Ollama)",
160
+ description: "Local reasoning model via Ollama. Thinks natively (always on).",
161
+ supports_thinking: true,
162
+ effortMap: NO_EFFORT
163
+ },
164
+ {
165
+ id: "llama3.1:8b",
166
+ provider: "ollama",
167
+ label: "Llama 3.1 8B (Ollama)",
168
+ description: "General-purpose local model via Ollama.",
169
+ supports_thinking: false,
170
+ effortMap: NO_EFFORT
171
+ }
172
+ ];
173
+ function getModel(id) {
174
+ return MODEL_CATALOG.find((m) => m.id === id);
175
+ }
176
+ function modelsByProvider(provider) {
177
+ return MODEL_CATALOG.filter((m) => m.provider === provider);
178
+ }
179
+ function resolveEffort(model, effort) {
180
+ return model.effortMap[effort];
181
+ }
182
+
183
+ // src/config/schema.ts
184
+ var effortSchema = z.enum([...EFFORT_LEVELS]);
185
+ var reasoningPrioritySchema = z.enum([...REASONING_PRIORITIES]);
186
+ var roleSchema = z.enum([...AGENT_ROLES]);
187
+ var modelSchema = z.string().refine(
188
+ (id) => getModel(id) !== void 0,
189
+ (id) => ({
190
+ message: `unknown model '${id}'; run the config 'models' command to list valid model ids`
191
+ })
192
+ );
193
+ var agentConfigSchema = z.object({
194
+ model: modelSchema,
195
+ effort: effortSchema,
196
+ reasoning_priority: reasoningPrioritySchema
197
+ });
198
+ var partialAgentConfigSchema = agentConfigSchema.partial();
199
+ var configFileSchema = z.object({
200
+ version: z.literal(1),
201
+ defaults: z.record(roleSchema, partialAgentConfigSchema).optional(),
202
+ overrides: z.record(roleSchema, partialAgentConfigSchema).optional()
203
+ }).strict();
204
+ function parseChambaConfig(raw) {
205
+ const result = configFileSchema.safeParse(raw);
206
+ if (result.success) return { ok: true, value: result.data };
207
+ const error = result.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
208
+ return { ok: false, error };
209
+ }
210
+
211
+ // src/config/loader.ts
212
+ var ConfigError = class extends Error {
213
+ constructor(message) {
214
+ super(message);
215
+ this.name = "ConfigError";
216
+ }
217
+ };
218
+ async function loadConfig(fs, opts = {}) {
219
+ const sources = [{ kind: "default", status: "applied" }];
220
+ const config = cloneDefaults();
221
+ const layers = [
222
+ { kind: "global", path: opts.globalPath },
223
+ { kind: "project", path: opts.projectPath }
224
+ ];
225
+ for (const layer of layers) {
226
+ if (!layer.path) continue;
227
+ const loaded = await readLayer(fs, layer.path);
228
+ if (loaded.status === "missing") {
229
+ sources.push({ kind: layer.kind, path: layer.path, status: "missing" });
230
+ continue;
231
+ }
232
+ if (loaded.status === "invalid") {
233
+ sources.push({ kind: layer.kind, path: layer.path, status: "invalid", error: loaded.error });
234
+ continue;
235
+ }
236
+ applyLayer(config, loaded.file);
237
+ sources.push({ kind: layer.kind, path: layer.path, status: "applied" });
238
+ }
239
+ return { config, sources };
240
+ }
241
+ function cloneDefaults() {
242
+ const out = {};
243
+ for (const role of AGENT_ROLES) {
244
+ out[role] = { ...DEFAULT_CONFIG.defaults[role] };
245
+ }
246
+ return out;
247
+ }
248
+ function applyLayer(config, file) {
249
+ for (const part of [file.defaults, file.overrides]) {
250
+ if (!part) continue;
251
+ for (const role of AGENT_ROLES) {
252
+ const patch = part[role];
253
+ if (!patch) continue;
254
+ config[role] = { ...config[role], ...stripUndefined(patch) };
255
+ }
256
+ }
257
+ }
258
+ function stripUndefined(patch) {
259
+ const out = {};
260
+ if (patch.model !== void 0) out.model = patch.model;
261
+ if (patch.effort !== void 0) out.effort = patch.effort;
262
+ if (patch.reasoning_priority !== void 0) out.reasoning_priority = patch.reasoning_priority;
263
+ return out;
264
+ }
265
+ async function readLayer(fs, path) {
266
+ let text;
267
+ try {
268
+ if (!await fs.exists(path)) return { status: "missing" };
269
+ text = await fs.readFile(path);
270
+ } catch {
271
+ return { status: "missing" };
272
+ }
273
+ let raw;
274
+ try {
275
+ raw = JSON.parse(text);
276
+ } catch (e) {
277
+ return { status: "invalid", error: `invalid JSON: ${e.message}` };
278
+ }
279
+ const parsed = parseChambaConfig(raw);
280
+ if (!parsed.ok) return { status: "invalid", error: parsed.error };
281
+ return { status: "applied", file: parsed.value };
282
+ }
283
+
284
+ // src/config/resolve.ts
285
+ function resolveRole(config, role) {
286
+ return config[role];
287
+ }
288
+ var PRIORITY_PHRASE = {
289
+ speed: "optimized for speed on mechanical work",
290
+ balanced: "balanced between speed and reasoning",
291
+ thoroughness: "optimized for deep reasoning"
292
+ };
293
+ function buildHint(role, cfg) {
294
+ const model = getModel(cfg.model);
295
+ const effortPhrase = cfg.effort === "extreme" ? "maximum" : cfg.effort;
296
+ const advisory = model && !model.supports_thinking ? " (this model has no adjustable thinking, so effort is advisory)" : "";
297
+ return `For the ${role} role, use a model ${PRIORITY_PHRASE[cfg.reasoning_priority]}; suggested: ${cfg.model} with ${effortPhrase} effort${advisory}.`;
298
+ }
299
+
1
300
  // src/util/path.ts
2
301
  function joinPath(...parts) {
3
302
  return parts.filter((p) => p.length > 0).join("/").replace(/\/{2,}/g, "/");
@@ -1183,13 +1482,20 @@ function parsePorcelain(output) {
1183
1482
  return result;
1184
1483
  }
1185
1484
  export {
1485
+ AGENT_ROLES,
1486
+ ConfigError,
1186
1487
  ContextBuilder,
1488
+ DEFAULT_CONFIG,
1489
+ EFFORT_LEVELS,
1187
1490
  FakeProcess,
1188
1491
  FilesystemMemoryStore,
1189
1492
  GitDetector,
1190
1493
  MEMORY_DIR,
1494
+ MODEL_CATALOG,
1191
1495
  MemoryFilesystem,
1192
1496
  ObsidianDetector,
1497
+ REASONING_PRIORITIES,
1498
+ ROLE_DESCRIPTIONS,
1193
1499
  Reviewer,
1194
1500
  VAULT_NOTES_DIR,
1195
1501
  VaultWriter,
@@ -1201,13 +1507,21 @@ export {
1201
1507
  WorktreeManager,
1202
1508
  basename,
1203
1509
  buildBranchName,
1510
+ buildHint,
1511
+ configFileSchema,
1204
1512
  diffLines,
1205
1513
  dirname,
1206
1514
  extname,
1207
1515
  generatePlanTemplate,
1516
+ getModel,
1208
1517
  joinPath,
1518
+ loadConfig,
1519
+ modelsByProvider,
1520
+ parseChambaConfig,
1209
1521
  renderNote,
1210
1522
  renderWorkspaceMarkdown,
1523
+ resolveEffort,
1524
+ resolveRole,
1211
1525
  slugify,
1212
1526
  slugifyForGit,
1213
1527
  suggestFilesLikelyTouched,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chamba/core",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Core logic for chamba: workspace scanner, plan + heuristic reviewer, git worktrees, Obsidian, memory — no Node APIs, no LLM",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -34,6 +34,9 @@
34
34
  "obsidian",
35
35
  "git-worktree"
36
36
  ],
37
+ "dependencies": {
38
+ "zod": "^3.23.0"
39
+ },
37
40
  "devDependencies": {
38
41
  "typescript": "^5.6.0",
39
42
  "vitest": "^2.0.0"