@kimbho/kimbho-cli 0.1.14 → 0.1.18

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.cjs CHANGED
@@ -1147,7 +1147,7 @@ var require_command = __commonJS({
1147
1147
  "../../node_modules/commander/lib/command.js"(exports2) {
1148
1148
  var EventEmitter = require("node:events").EventEmitter;
1149
1149
  var childProcess = require("node:child_process");
1150
- var path21 = require("node:path");
1150
+ var path22 = require("node:path");
1151
1151
  var fs = require("node:fs");
1152
1152
  var process20 = require("node:process");
1153
1153
  var { Argument: Argument2, humanReadableArgName } = require_argument();
@@ -2147,9 +2147,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
2147
2147
  let launchWithNode = false;
2148
2148
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
2149
2149
  function findFile(baseDir, baseName) {
2150
- const localBin = path21.resolve(baseDir, baseName);
2150
+ const localBin = path22.resolve(baseDir, baseName);
2151
2151
  if (fs.existsSync(localBin)) return localBin;
2152
- if (sourceExt.includes(path21.extname(baseName))) return void 0;
2152
+ if (sourceExt.includes(path22.extname(baseName))) return void 0;
2153
2153
  const foundExt = sourceExt.find(
2154
2154
  (ext) => fs.existsSync(`${localBin}${ext}`)
2155
2155
  );
@@ -2167,17 +2167,17 @@ Expecting one of '${allowedValues.join("', '")}'`);
2167
2167
  } catch {
2168
2168
  resolvedScriptPath = this._scriptPath;
2169
2169
  }
2170
- executableDir = path21.resolve(
2171
- path21.dirname(resolvedScriptPath),
2170
+ executableDir = path22.resolve(
2171
+ path22.dirname(resolvedScriptPath),
2172
2172
  executableDir
2173
2173
  );
2174
2174
  }
2175
2175
  if (executableDir) {
2176
2176
  let localFile = findFile(executableDir, executableFile);
2177
2177
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
2178
- const legacyName = path21.basename(
2178
+ const legacyName = path22.basename(
2179
2179
  this._scriptPath,
2180
- path21.extname(this._scriptPath)
2180
+ path22.extname(this._scriptPath)
2181
2181
  );
2182
2182
  if (legacyName !== this._name) {
2183
2183
  localFile = findFile(
@@ -2188,7 +2188,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2188
2188
  }
2189
2189
  executableFile = localFile || executableFile;
2190
2190
  }
2191
- launchWithNode = sourceExt.includes(path21.extname(executableFile));
2191
+ launchWithNode = sourceExt.includes(path22.extname(executableFile));
2192
2192
  let proc;
2193
2193
  if (process20.platform !== "win32") {
2194
2194
  if (launchWithNode) {
@@ -3035,7 +3035,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
3035
3035
  * @return {Command}
3036
3036
  */
3037
3037
  nameFromFilename(filename) {
3038
- this._name = path21.basename(filename, path21.extname(filename));
3038
+ this._name = path22.basename(filename, path22.extname(filename));
3039
3039
  return this;
3040
3040
  }
3041
3041
  /**
@@ -3049,9 +3049,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
3049
3049
  * @param {string} [path]
3050
3050
  * @return {(string|null|Command)}
3051
3051
  */
3052
- executableDir(path22) {
3053
- if (path22 === void 0) return this._executableDir;
3054
- this._executableDir = path22;
3052
+ executableDir(path23) {
3053
+ if (path23 === void 0) return this._executableDir;
3054
+ this._executableDir = path23;
3055
3055
  return this;
3056
3056
  }
3057
3057
  /**
@@ -6516,8 +6516,8 @@ var require_utils = __commonJS({
6516
6516
  }
6517
6517
  return ind;
6518
6518
  }
6519
- function removeDotSegments(path21) {
6520
- let input = path21;
6519
+ function removeDotSegments(path22) {
6520
+ let input = path22;
6521
6521
  const output = [];
6522
6522
  let nextSlash = -1;
6523
6523
  let len = 0;
@@ -6716,8 +6716,8 @@ var require_schemes = __commonJS({
6716
6716
  wsComponent.secure = void 0;
6717
6717
  }
6718
6718
  if (wsComponent.resourceName) {
6719
- const [path21, query] = wsComponent.resourceName.split("?");
6720
- wsComponent.path = path21 && path21 !== "/" ? path21 : void 0;
6719
+ const [path22, query] = wsComponent.resourceName.split("?");
6720
+ wsComponent.path = path22 && path22 !== "/" ? path22 : void 0;
6721
6721
  wsComponent.query = query;
6722
6722
  wsComponent.resourceName = void 0;
6723
6723
  }
@@ -10098,7 +10098,7 @@ var require_windows = __commonJS({
10098
10098
  module2.exports = isexe;
10099
10099
  isexe.sync = sync;
10100
10100
  var fs = require("fs");
10101
- function checkPathExt(path21, options) {
10101
+ function checkPathExt(path22, options) {
10102
10102
  var pathext = options.pathExt !== void 0 ? options.pathExt : process.env.PATHEXT;
10103
10103
  if (!pathext) {
10104
10104
  return true;
@@ -10109,25 +10109,25 @@ var require_windows = __commonJS({
10109
10109
  }
10110
10110
  for (var i = 0; i < pathext.length; i++) {
10111
10111
  var p = pathext[i].toLowerCase();
10112
- if (p && path21.substr(-p.length).toLowerCase() === p) {
10112
+ if (p && path22.substr(-p.length).toLowerCase() === p) {
10113
10113
  return true;
10114
10114
  }
10115
10115
  }
10116
10116
  return false;
10117
10117
  }
10118
- function checkStat(stat2, path21, options) {
10118
+ function checkStat(stat2, path22, options) {
10119
10119
  if (!stat2.isSymbolicLink() && !stat2.isFile()) {
10120
10120
  return false;
10121
10121
  }
10122
- return checkPathExt(path21, options);
10122
+ return checkPathExt(path22, options);
10123
10123
  }
10124
- function isexe(path21, options, cb) {
10125
- fs.stat(path21, function(er, stat2) {
10126
- cb(er, er ? false : checkStat(stat2, path21, options));
10124
+ function isexe(path22, options, cb) {
10125
+ fs.stat(path22, function(er, stat2) {
10126
+ cb(er, er ? false : checkStat(stat2, path22, options));
10127
10127
  });
10128
10128
  }
10129
- function sync(path21, options) {
10130
- return checkStat(fs.statSync(path21), path21, options);
10129
+ function sync(path22, options) {
10130
+ return checkStat(fs.statSync(path22), path22, options);
10131
10131
  }
10132
10132
  }
10133
10133
  });
@@ -10138,13 +10138,13 @@ var require_mode = __commonJS({
10138
10138
  module2.exports = isexe;
10139
10139
  isexe.sync = sync;
10140
10140
  var fs = require("fs");
10141
- function isexe(path21, options, cb) {
10142
- fs.stat(path21, function(er, stat2) {
10141
+ function isexe(path22, options, cb) {
10142
+ fs.stat(path22, function(er, stat2) {
10143
10143
  cb(er, er ? false : checkStat(stat2, options));
10144
10144
  });
10145
10145
  }
10146
- function sync(path21, options) {
10147
- return checkStat(fs.statSync(path21), options);
10146
+ function sync(path22, options) {
10147
+ return checkStat(fs.statSync(path22), options);
10148
10148
  }
10149
10149
  function checkStat(stat2, options) {
10150
10150
  return stat2.isFile() && checkMode(stat2, options);
@@ -10177,7 +10177,7 @@ var require_isexe = __commonJS({
10177
10177
  }
10178
10178
  module2.exports = isexe;
10179
10179
  isexe.sync = sync;
10180
- function isexe(path21, options, cb) {
10180
+ function isexe(path22, options, cb) {
10181
10181
  if (typeof options === "function") {
10182
10182
  cb = options;
10183
10183
  options = {};
@@ -10187,7 +10187,7 @@ var require_isexe = __commonJS({
10187
10187
  throw new TypeError("callback not provided");
10188
10188
  }
10189
10189
  return new Promise(function(resolve, reject) {
10190
- isexe(path21, options || {}, function(er, is) {
10190
+ isexe(path22, options || {}, function(er, is) {
10191
10191
  if (er) {
10192
10192
  reject(er);
10193
10193
  } else {
@@ -10196,7 +10196,7 @@ var require_isexe = __commonJS({
10196
10196
  });
10197
10197
  });
10198
10198
  }
10199
- core(path21, options || {}, function(er, is) {
10199
+ core(path22, options || {}, function(er, is) {
10200
10200
  if (er) {
10201
10201
  if (er.code === "EACCES" || options && options.ignoreErrors) {
10202
10202
  er = null;
@@ -10206,9 +10206,9 @@ var require_isexe = __commonJS({
10206
10206
  cb(er, is);
10207
10207
  });
10208
10208
  }
10209
- function sync(path21, options) {
10209
+ function sync(path22, options) {
10210
10210
  try {
10211
- return core.sync(path21, options || {});
10211
+ return core.sync(path22, options || {});
10212
10212
  } catch (er) {
10213
10213
  if (options && options.ignoreErrors || er.code === "EACCES") {
10214
10214
  return false;
@@ -10224,7 +10224,7 @@ var require_isexe = __commonJS({
10224
10224
  var require_which = __commonJS({
10225
10225
  "../../node_modules/which/which.js"(exports2, module2) {
10226
10226
  var isWindows = process.platform === "win32" || process.env.OSTYPE === "cygwin" || process.env.OSTYPE === "msys";
10227
- var path21 = require("path");
10227
+ var path22 = require("path");
10228
10228
  var COLON = isWindows ? ";" : ":";
10229
10229
  var isexe = require_isexe();
10230
10230
  var getNotFoundError = (cmd) => Object.assign(new Error(`not found: ${cmd}`), { code: "ENOENT" });
@@ -10262,7 +10262,7 @@ var require_which = __commonJS({
10262
10262
  return opt.all && found.length ? resolve(found) : reject(getNotFoundError(cmd));
10263
10263
  const ppRaw = pathEnv[i];
10264
10264
  const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
10265
- const pCmd = path21.join(pathPart, cmd);
10265
+ const pCmd = path22.join(pathPart, cmd);
10266
10266
  const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
10267
10267
  resolve(subStep(p, i, 0));
10268
10268
  });
@@ -10289,7 +10289,7 @@ var require_which = __commonJS({
10289
10289
  for (let i = 0; i < pathEnv.length; i++) {
10290
10290
  const ppRaw = pathEnv[i];
10291
10291
  const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
10292
- const pCmd = path21.join(pathPart, cmd);
10292
+ const pCmd = path22.join(pathPart, cmd);
10293
10293
  const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
10294
10294
  for (let j = 0; j < pathExt.length; j++) {
10295
10295
  const cur = p + pathExt[j];
@@ -10337,7 +10337,7 @@ var require_path_key = __commonJS({
10337
10337
  var require_resolveCommand = __commonJS({
10338
10338
  "../../node_modules/cross-spawn/lib/util/resolveCommand.js"(exports2, module2) {
10339
10339
  "use strict";
10340
- var path21 = require("path");
10340
+ var path22 = require("path");
10341
10341
  var which = require_which();
10342
10342
  var getPathKey = require_path_key();
10343
10343
  function resolveCommandAttempt(parsed, withoutPathExt) {
@@ -10355,7 +10355,7 @@ var require_resolveCommand = __commonJS({
10355
10355
  try {
10356
10356
  resolved = which.sync(parsed.command, {
10357
10357
  path: env[getPathKey({ env })],
10358
- pathExt: withoutPathExt ? path21.delimiter : void 0
10358
+ pathExt: withoutPathExt ? path22.delimiter : void 0
10359
10359
  });
10360
10360
  } catch (e) {
10361
10361
  } finally {
@@ -10364,7 +10364,7 @@ var require_resolveCommand = __commonJS({
10364
10364
  }
10365
10365
  }
10366
10366
  if (resolved) {
10367
- resolved = path21.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
10367
+ resolved = path22.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
10368
10368
  }
10369
10369
  return resolved;
10370
10370
  }
@@ -10418,8 +10418,8 @@ var require_shebang_command = __commonJS({
10418
10418
  if (!match) {
10419
10419
  return null;
10420
10420
  }
10421
- const [path21, argument] = match[0].replace(/#! ?/, "").split(" ");
10422
- const binary = path21.split("/").pop();
10421
+ const [path22, argument] = match[0].replace(/#! ?/, "").split(" ");
10422
+ const binary = path22.split("/").pop();
10423
10423
  if (binary === "env") {
10424
10424
  return argument;
10425
10425
  }
@@ -10454,7 +10454,7 @@ var require_readShebang = __commonJS({
10454
10454
  var require_parse = __commonJS({
10455
10455
  "../../node_modules/cross-spawn/lib/parse.js"(exports2, module2) {
10456
10456
  "use strict";
10457
- var path21 = require("path");
10457
+ var path22 = require("path");
10458
10458
  var resolveCommand = require_resolveCommand();
10459
10459
  var escape2 = require_escape();
10460
10460
  var readShebang = require_readShebang();
@@ -10479,7 +10479,7 @@ var require_parse = __commonJS({
10479
10479
  const needsShell = !isExecutableRegExp.test(commandFile);
10480
10480
  if (parsed.options.forceShell || needsShell) {
10481
10481
  const needsDoubleEscapeMetaChars = isCmdShimRegExp.test(commandFile);
10482
- parsed.command = path21.normalize(parsed.command);
10482
+ parsed.command = path22.normalize(parsed.command);
10483
10483
  parsed.command = escape2.command(parsed.command);
10484
10484
  parsed.args = parsed.args.map((arg) => escape2.argument(arg, needsDoubleEscapeMetaChars));
10485
10485
  const shellCommand = [parsed.command].concat(parsed.args).join(" ");
@@ -10612,7 +10612,7 @@ var {
10612
10612
  // package.json
10613
10613
  var package_default = {
10614
10614
  name: "@kimbho/kimbho-cli",
10615
- version: "0.1.14",
10615
+ version: "0.1.18",
10616
10616
  description: "Kimbho CLI is a terminal-native coding agent for planning, execution, and verification.",
10617
10617
  type: "module",
10618
10618
  engines: {
@@ -11406,8 +11406,8 @@ function getErrorMap() {
11406
11406
 
11407
11407
  // ../../node_modules/zod/v3/helpers/parseUtil.js
11408
11408
  var makeIssue = (params) => {
11409
- const { data, path: path21, errorMaps, issueData } = params;
11410
- const fullPath = [...path21, ...issueData.path || []];
11409
+ const { data, path: path22, errorMaps, issueData } = params;
11410
+ const fullPath = [...path22, ...issueData.path || []];
11411
11411
  const fullIssue = {
11412
11412
  ...issueData,
11413
11413
  path: fullPath
@@ -11523,11 +11523,11 @@ var errorUtil;
11523
11523
 
11524
11524
  // ../../node_modules/zod/v3/types.js
11525
11525
  var ParseInputLazyPath = class {
11526
- constructor(parent, value, path21, key) {
11526
+ constructor(parent, value, path22, key) {
11527
11527
  this._cachedPath = [];
11528
11528
  this.parent = parent;
11529
11529
  this.data = value;
11530
- this._path = path21;
11530
+ this._path = path22;
11531
11531
  this._key = key;
11532
11532
  }
11533
11533
  get path() {
@@ -15026,7 +15026,12 @@ var PlanTaskSchema = external_exports.object({
15026
15026
  filesLikelyTouched: external_exports.array(external_exports.string()).default([]),
15027
15027
  riskLevel: RiskLevelSchema.default("medium"),
15028
15028
  originTaskId: external_exports.string().min(1).optional(),
15029
- swarmDepth: external_exports.number().int().nonnegative().optional()
15029
+ swarmDepth: external_exports.number().int().nonnegative().optional(),
15030
+ customAgentId: external_exports.string().min(1).optional(),
15031
+ teamId: external_exports.string().min(1).optional(),
15032
+ teamMemberIds: external_exports.array(external_exports.string()).optional(),
15033
+ subagentLabel: external_exports.string().min(1).optional(),
15034
+ subagentInstructions: external_exports.string().min(1).optional()
15030
15035
  });
15031
15036
  var PlanMilestoneSchema = external_exports.object({
15032
15037
  id: external_exports.string().min(1),
@@ -15437,6 +15442,7 @@ function summarizeStructuredOutputError(context, error2) {
15437
15442
 
15438
15443
  // ../core/dist/config/config.js
15439
15444
  var import_promises = require("node:fs/promises");
15445
+ var import_node_os = __toESM(require("node:os"), 1);
15440
15446
  var import_node_path = __toESM(require("node:path"), 1);
15441
15447
  var KIMBHO_DIR_NAME = ".kimbho";
15442
15448
  function resolveKimbhoDir(cwd = process.cwd()) {
@@ -15445,6 +15451,9 @@ function resolveKimbhoDir(cwd = process.cwd()) {
15445
15451
  function resolveConfigPath(cwd = process.cwd()) {
15446
15452
  return import_node_path.default.join(resolveKimbhoDir(cwd), "config.json");
15447
15453
  }
15454
+ function resolveGlobalConfigPath() {
15455
+ return import_node_path.default.join(import_node_os.default.homedir(), ".kimbho", "config.json");
15456
+ }
15448
15457
  var LegacyProviderNameSchema = external_exports.enum([
15449
15458
  "openai",
15450
15459
  "anthropic",
@@ -15599,8 +15608,7 @@ async function ensureKimbhoDir(cwd = process.cwd()) {
15599
15608
  await (0, import_promises.mkdir)(import_node_path.default.join(root, "teams"), { recursive: true });
15600
15609
  return root;
15601
15610
  }
15602
- async function loadConfig(cwd = process.cwd()) {
15603
- const configPath = resolveConfigPath(cwd);
15611
+ async function loadConfigFile(configPath) {
15604
15612
  try {
15605
15613
  const raw = await (0, import_promises.readFile)(configPath, "utf8");
15606
15614
  return KimbhoConfigSchema.parse(normalizeConfigInput(JSON.parse(raw)));
@@ -15612,6 +15620,77 @@ async function loadConfig(cwd = process.cwd()) {
15612
15620
  throw error2;
15613
15621
  }
15614
15622
  }
15623
+ async function loadProjectConfig(cwd = process.cwd()) {
15624
+ return loadConfigFile(resolveConfigPath(cwd));
15625
+ }
15626
+ async function loadUserConfig() {
15627
+ return loadConfigFile(resolveGlobalConfigPath());
15628
+ }
15629
+ function mergeConfigCatalog(lower, upper) {
15630
+ const merged = /* @__PURE__ */ new Map();
15631
+ for (const provider of lower) {
15632
+ merged.set(provider.id, provider);
15633
+ }
15634
+ for (const provider of upper) {
15635
+ merged.set(provider.id, provider);
15636
+ }
15637
+ return Array.from(merged.values());
15638
+ }
15639
+ function mergeConfigLayers(base, override) {
15640
+ return KimbhoConfigSchema.parse({
15641
+ providers: mergeConfigCatalog(base.providers, override.providers),
15642
+ brains: {
15643
+ planner: {
15644
+ ...base.brains.planner,
15645
+ ...override.brains.planner
15646
+ },
15647
+ coder: {
15648
+ ...base.brains.coder,
15649
+ ...override.brains.coder
15650
+ },
15651
+ reviewer: {
15652
+ ...base.brains.reviewer,
15653
+ ...override.brains.reviewer
15654
+ },
15655
+ fast: {
15656
+ ...base.brains.fast,
15657
+ ...override.brains.fast
15658
+ }
15659
+ },
15660
+ approvalMode: override.approvalMode ?? base.approvalMode,
15661
+ sandboxMode: override.sandboxMode ?? base.sandboxMode,
15662
+ stackPresets: Array.from(/* @__PURE__ */ new Set([
15663
+ ...base.stackPresets,
15664
+ ...override.stackPresets
15665
+ ])),
15666
+ trustedDirectories: Array.from(/* @__PURE__ */ new Set([
15667
+ ...base.trustedDirectories,
15668
+ ...override.trustedDirectories
15669
+ ]))
15670
+ });
15671
+ }
15672
+ async function loadConfigLayers(cwd = process.cwd()) {
15673
+ const [user, project] = await Promise.all([
15674
+ loadUserConfig(),
15675
+ loadProjectConfig(cwd)
15676
+ ]);
15677
+ if (!user && !project) {
15678
+ return {
15679
+ user: null,
15680
+ project: null,
15681
+ resolved: null
15682
+ };
15683
+ }
15684
+ return {
15685
+ user,
15686
+ project,
15687
+ resolved: user && project ? mergeConfigLayers(user, project) : project ?? user
15688
+ };
15689
+ }
15690
+ async function loadConfig(cwd = process.cwd()) {
15691
+ const layers = await loadConfigLayers(cwd);
15692
+ return layers.resolved;
15693
+ }
15615
15694
  async function saveConfig(config2, cwd = process.cwd()) {
15616
15695
  await ensureKimbhoDir(cwd);
15617
15696
  const configPath = resolveConfigPath(cwd);
@@ -15622,6 +15701,16 @@ async function saveConfig(config2, cwd = process.cwd()) {
15622
15701
  await (0, import_promises.rename)(tempPath, configPath);
15623
15702
  return configPath;
15624
15703
  }
15704
+ async function saveUserConfig(config2) {
15705
+ const configPath = resolveGlobalConfigPath();
15706
+ await (0, import_promises.mkdir)(import_node_path.default.dirname(configPath), { recursive: true });
15707
+ const normalized = KimbhoConfigSchema.parse(config2);
15708
+ const tempPath = `${configPath}.tmp-${process.pid}-${Date.now()}`;
15709
+ await (0, import_promises.writeFile)(tempPath, `${JSON.stringify(normalized, null, 2)}
15710
+ `, "utf8");
15711
+ await (0, import_promises.rename)(tempPath, configPath);
15712
+ return configPath;
15713
+ }
15625
15714
  function findProviderById(config2, providerId) {
15626
15715
  return config2.providers.find((provider) => provider.id === providerId);
15627
15716
  }
@@ -15740,10 +15829,10 @@ async function loadLatestSession(cwd = process.cwd()) {
15740
15829
 
15741
15830
  // ../core/dist/memory.js
15742
15831
  var import_promises3 = require("node:fs/promises");
15743
- var import_node_os = __toESM(require("node:os"), 1);
15832
+ var import_node_os2 = __toESM(require("node:os"), 1);
15744
15833
  var import_node_path3 = __toESM(require("node:path"), 1);
15745
15834
  function resolveGlobalKimbhoHome() {
15746
- return import_node_path3.default.join(import_node_os.default.homedir(), ".kimbho");
15835
+ return import_node_path3.default.join(import_node_os2.default.homedir(), ".kimbho");
15747
15836
  }
15748
15837
  function resolveUserMemoryPath() {
15749
15838
  return import_node_path3.default.join(resolveGlobalKimbhoHome(), "memory.md");
@@ -15807,6 +15896,54 @@ ${block}` : `# ${import_node_path3.default.basename(filePath, import_node_path3.
15807
15896
 
15808
15897
  ${block}`);
15809
15898
  }
15899
+ function uniqueOrdered(values) {
15900
+ return Array.from(new Set(values));
15901
+ }
15902
+ function candidateMemoryFilesForDirectory(directory) {
15903
+ return [
15904
+ import_node_path3.default.join(directory, "KIMBHO.md"),
15905
+ import_node_path3.default.join(directory, "KIMBHO.local.md"),
15906
+ import_node_path3.default.join(directory, "CLAUDE.md"),
15907
+ import_node_path3.default.join(directory, "CLAUDE.local.md")
15908
+ ];
15909
+ }
15910
+ function discoverMarkdownMemoryPaths(cwd = process.cwd()) {
15911
+ const discovered = [];
15912
+ let current = import_node_path3.default.resolve(cwd);
15913
+ const root = import_node_path3.default.parse(current).root;
15914
+ while (true) {
15915
+ discovered.push(...candidateMemoryFilesForDirectory(current));
15916
+ if (current === root) {
15917
+ break;
15918
+ }
15919
+ current = import_node_path3.default.dirname(current);
15920
+ }
15921
+ discovered.push(import_node_path3.default.join(cwd, "kimbho_init.md"));
15922
+ discovered.push(resolveProjectMemoryPath(cwd));
15923
+ discovered.push(resolveUserMemoryPath());
15924
+ return uniqueOrdered(discovered);
15925
+ }
15926
+ async function loadMarkdownMemoryContext(cwd = process.cwd(), options = {}) {
15927
+ const candidates = discoverMarkdownMemoryPaths(cwd);
15928
+ const records = [];
15929
+ const maxFiles = options.maxFiles ?? 12;
15930
+ const maxCharsPerFile = options.maxCharsPerFile ?? 6e3;
15931
+ for (const filePath of candidates) {
15932
+ if (records.length >= maxFiles) {
15933
+ break;
15934
+ }
15935
+ const content = await readMarkdownIfExists(filePath);
15936
+ if (!content || content.trim().length === 0) {
15937
+ continue;
15938
+ }
15939
+ records.push({
15940
+ filePath,
15941
+ content: content.length > maxCharsPerFile ? `${content.slice(0, maxCharsPerFile)}
15942
+ ... [truncated]` : content
15943
+ });
15944
+ }
15945
+ return records;
15946
+ }
15810
15947
 
15811
15948
  // ../core/dist/mcp.js
15812
15949
  var import_promises4 = require("node:fs/promises");
@@ -15996,6 +16133,8 @@ async function loadAgentTeams(cwd) {
15996
16133
  id,
15997
16134
  label: typeof attributes.label === "string" ? attributes.label : id,
15998
16135
  agents: asStringArray(attributes.agents),
16136
+ match: asStringArray(attributes.match),
16137
+ strategy: typeof attributes.strategy === "string" && attributes.strategy === "parallel" ? "parallel" : "sequential",
15999
16138
  filePath,
16000
16139
  ...body.trim() ? {
16001
16140
  description: body.trim()
@@ -16044,8 +16183,11 @@ async function createAgentTeamFile(cwd, id, agentIds, label) {
16044
16183
  "---",
16045
16184
  `id: ${id}`,
16046
16185
  `label: ${label ?? id}`,
16186
+ "match:",
16187
+ " - feature",
16047
16188
  "agents:",
16048
16189
  ...agentIds.map((agentId) => ` - ${agentId}`),
16190
+ "strategy: sequential",
16049
16191
  "---",
16050
16192
  "",
16051
16193
  `# ${label ?? id}`,
@@ -16072,11 +16214,46 @@ function scoreCustomAgentMatch(definition, task, request) {
16072
16214
  }
16073
16215
  return definition.match.reduce((score, keyword) => haystack.includes(keyword.toLowerCase()) ? score + 1 : score, 0);
16074
16216
  }
16217
+ function scoreAgentTeamMatch(definition, task, request) {
16218
+ const haystack = [
16219
+ request.goal,
16220
+ task.title,
16221
+ task.description,
16222
+ ...task.acceptanceCriteria
16223
+ ].join(" ").toLowerCase();
16224
+ if (definition.match.length === 0) {
16225
+ return definition.agents.length > 0 ? 1 : -1;
16226
+ }
16227
+ return definition.match.reduce((score, keyword) => haystack.includes(keyword.toLowerCase()) ? score + 1 : score, 0);
16228
+ }
16229
+ async function selectAgentTeam(cwd, task, request) {
16230
+ const [teams, agents] = await Promise.all([
16231
+ loadAgentTeams(cwd),
16232
+ loadCustomAgents(cwd)
16233
+ ]);
16234
+ const selected = teams.map((team) => ({
16235
+ team,
16236
+ score: scoreAgentTeamMatch(team, task, request)
16237
+ })).filter((candidate) => candidate.score > 0).sort((left, right) => right.score - left.score || left.team.id.localeCompare(right.team.id))[0]?.team;
16238
+ if (!selected) {
16239
+ return null;
16240
+ }
16241
+ const members = selected.agents.map((agentId) => agents.find((candidate) => candidate.id === agentId)).filter((candidate) => Boolean(candidate));
16242
+ return {
16243
+ team: selected,
16244
+ members
16245
+ };
16246
+ }
16075
16247
  async function selectCustomAgentOverlay(cwd, task, request, builtInTools) {
16076
- const definitions = await loadCustomAgents(cwd);
16248
+ const selectedTeam = task.teamId ? await selectAgentTeam(cwd, task, request) : await selectAgentTeam(cwd, task, request);
16249
+ const allDefinitions = await loadCustomAgents(cwd);
16250
+ const preferredDefinition = task.customAgentId ? allDefinitions.find((definition) => definition.id === task.customAgentId) : null;
16251
+ const definitions = preferredDefinition ? [
16252
+ preferredDefinition
16253
+ ] : selectedTeam?.members.length ? selectedTeam.members : allDefinitions;
16077
16254
  const ranked = definitions.map((definition) => ({
16078
16255
  definition,
16079
- score: scoreCustomAgentMatch(definition, task, request)
16256
+ score: preferredDefinition && definition.id === preferredDefinition.id ? Number.MAX_SAFE_INTEGER : scoreCustomAgentMatch(definition, task, request)
16080
16257
  })).filter((candidate) => candidate.score > 0).sort((left, right) => right.score - left.score || left.definition.id.localeCompare(right.definition.id));
16081
16258
  const selected = ranked[0]?.definition;
16082
16259
  if (!selected) {
@@ -16090,6 +16267,12 @@ async function selectCustomAgentOverlay(cwd, task, request, builtInTools) {
16090
16267
  purpose: selected.purpose ?? baseProfile.purpose,
16091
16268
  brainRole: selected.brainRole ?? baseProfile.brainRole,
16092
16269
  promptPreamble: [
16270
+ ...selectedTeam ? [
16271
+ `Agent team active: ${selectedTeam.team.label} (${selectedTeam.team.id}).`,
16272
+ `Team strategy: ${selectedTeam.team.strategy}.`,
16273
+ `Team roster: ${selectedTeam.members.map((member) => member.id).join(", ") || "(none)"}.`,
16274
+ selectedTeam.team.description?.trim() ?? ""
16275
+ ].filter((line) => line.length > 0) : [],
16093
16276
  `Custom agent overlay: ${selected.label} (${selected.id}).`,
16094
16277
  `Base role: ${selected.baseRole}.`,
16095
16278
  selected.instructions.trim()
@@ -17311,7 +17494,7 @@ var import_promises11 = require("node:fs/promises");
17311
17494
  var import_node_path11 = __toESM(require("node:path"), 1);
17312
17495
  var import_node_process2 = __toESM(require("node:process"), 1);
17313
17496
  var import_node_child_process2 = require("node:child_process");
17314
- var import_node_os2 = require("node:os");
17497
+ var import_node_os3 = require("node:os");
17315
17498
 
17316
17499
  // ../tools/dist/browser-manager.js
17317
17500
  var import_node_crypto2 = require("node:crypto");
@@ -18109,6 +18292,47 @@ var KNOWN_SCAFFOLD_PRESETS = [
18109
18292
  ];
18110
18293
  function looksLikeStaticLandingGoal(value) {
18111
18294
  const normalized = value.toLowerCase();
18295
+ const backendSignals = [
18296
+ "api",
18297
+ "database",
18298
+ "backend",
18299
+ "server",
18300
+ "migration",
18301
+ "schema",
18302
+ "prisma",
18303
+ "postgres",
18304
+ "sqlite",
18305
+ "auth",
18306
+ "billing",
18307
+ "webhook"
18308
+ ];
18309
+ const pageSignals = [
18310
+ "page",
18311
+ "landing",
18312
+ "lnding",
18313
+ "homepage",
18314
+ "home page",
18315
+ "site",
18316
+ "website",
18317
+ "ui",
18318
+ "hero",
18319
+ "layout",
18320
+ "screen"
18321
+ ];
18322
+ const polishSignals = [
18323
+ "animate",
18324
+ "animated",
18325
+ "animation",
18326
+ "motion",
18327
+ "rich",
18328
+ "richer",
18329
+ "polish",
18330
+ "styled",
18331
+ "styling",
18332
+ "visual",
18333
+ "redesign",
18334
+ "revamp"
18335
+ ];
18112
18336
  if (normalized.includes("landing page") || normalized.includes("landing") || normalized.includes("lnding") || normalized.includes("homepage") || normalized.includes("home page") || normalized.includes("marketing page") || normalized.includes("storefront") || normalized.includes("static")) {
18113
18337
  return true;
18114
18338
  }
@@ -18118,6 +18342,12 @@ function looksLikeStaticLandingGoal(value) {
18118
18342
  if (normalized.includes("restaurant") && (normalized.includes("page") || normalized.includes("site") || normalized.includes("website") || normalized.includes("landing") || normalized.includes("lnding"))) {
18119
18343
  return true;
18120
18344
  }
18345
+ if (pageSignals.some((signal) => normalized.includes(signal)) && polishSignals.some((signal) => normalized.includes(signal)) && !backendSignals.some((signal) => normalized.includes(signal))) {
18346
+ return true;
18347
+ }
18348
+ if (normalized.includes("blog") && (normalized.includes("make") || normalized.includes("improve") || normalized.includes("enhance") || normalized.includes("redesign")) && !backendSignals.some((signal) => normalized.includes(signal))) {
18349
+ return true;
18350
+ }
18121
18351
  return false;
18122
18352
  }
18123
18353
  function sanitizeName(value) {
@@ -19019,7 +19249,7 @@ async function createUnifiedDiffPreview(cwd, targetPath, beforeContent, afterCon
19019
19249
  if (beforeContent === afterContent) {
19020
19250
  return void 0;
19021
19251
  }
19022
- const tempDir = await (0, import_promises11.mkdtemp)(import_node_path11.default.join((0, import_node_os2.tmpdir)(), "kimbho-diff-"));
19252
+ const tempDir = await (0, import_promises11.mkdtemp)(import_node_path11.default.join((0, import_node_os3.tmpdir)(), "kimbho-diff-"));
19023
19253
  const beforePath = import_node_path11.default.join(tempDir, "before");
19024
19254
  const afterPath = import_node_path11.default.join(tempDir, "after");
19025
19255
  const relativeTarget = import_node_path11.default.relative(cwd, targetPath) || import_node_path11.default.basename(targetPath);
@@ -19051,10 +19281,17 @@ async function createUnifiedDiffPreview(cwd, targetPath, beforeContent, afterCon
19051
19281
  await (0, import_promises11.rm)(tempDir, { recursive: true, force: true });
19052
19282
  }
19053
19283
  }
19054
- function resolveWorkspacePath(cwd, filePath) {
19284
+ function isTrustedWorkspacePath(resolved, trustedDirectories = []) {
19285
+ return trustedDirectories.some((trustedDirectory) => {
19286
+ const trustedRoot = import_node_path11.default.resolve(trustedDirectory);
19287
+ const relative = import_node_path11.default.relative(trustedRoot, resolved);
19288
+ return relative === "" || !relative.startsWith("..") && !import_node_path11.default.isAbsolute(relative);
19289
+ });
19290
+ }
19291
+ function resolveWorkspacePath(cwd, filePath, trustedDirectories = []) {
19055
19292
  const resolved = import_node_path11.default.resolve(cwd, filePath);
19056
19293
  const relative = import_node_path11.default.relative(cwd, resolved);
19057
- if (relative.startsWith("..") || import_node_path11.default.isAbsolute(relative)) {
19294
+ if ((relative.startsWith("..") || import_node_path11.default.isAbsolute(relative)) && !isTrustedWorkspacePath(resolved, trustedDirectories)) {
19058
19295
  throw new Error(`Path "${filePath}" escapes the workspace.`);
19059
19296
  }
19060
19297
  return resolved;
@@ -19137,10 +19374,10 @@ function normalizeArtifactRelativePath(cwd, absolutePath) {
19137
19374
  function isProtectedWritePath(relativePath) {
19138
19375
  return PROTECTED_WRITE_PREFIXES.some((prefix) => relativePath === prefix || relativePath.startsWith(`${prefix}/`));
19139
19376
  }
19140
- function extractProspectiveWriteTargets(toolId, input, cwd) {
19377
+ function extractProspectiveWriteTargets(toolId, input, cwd, trustedDirectories = []) {
19141
19378
  if (toolId === "file.write" && typeof input.path === "string") {
19142
19379
  return [
19143
- normalizeArtifactRelativePath(cwd, resolveWorkspacePath(cwd, input.path))
19380
+ normalizeArtifactRelativePath(cwd, resolveWorkspacePath(cwd, input.path, trustedDirectories))
19144
19381
  ];
19145
19382
  }
19146
19383
  if (toolId === "file.patch" && typeof input.patch === "string") {
@@ -19212,7 +19449,7 @@ function enforceToolPolicy(toolId, input, descriptor, context) {
19212
19449
  return null;
19213
19450
  }
19214
19451
  if ((toolId === "file.write" || toolId === "file.patch") && sandboxMode === "workspace-write") {
19215
- const targets = extractProspectiveWriteTargets(toolId, input, context.cwd);
19452
+ const targets = extractProspectiveWriteTargets(toolId, input, context.cwd, context.trustedDirectories);
19216
19453
  const protectedTargets = targets.filter((target) => isProtectedWritePath(target));
19217
19454
  if (protectedTargets.length > 0) {
19218
19455
  return createPolicyFailure(toolId, `Blocked write to protected workspace paths: ${protectedTargets.join(", ")}`);
@@ -19319,7 +19556,7 @@ async function executeFileRead(input, context) {
19319
19556
  if (!rawPath) {
19320
19557
  throw new Error("file.read requires a string path.");
19321
19558
  }
19322
- const targetPath = resolveWorkspacePath(context.cwd, rawPath);
19559
+ const targetPath = resolveWorkspacePath(context.cwd, rawPath, context.trustedDirectories);
19323
19560
  const contents = await (0, import_promises11.readFile)(targetPath, "utf8");
19324
19561
  return ToolResultSchema.parse({
19325
19562
  toolId: "file.read",
@@ -19340,7 +19577,7 @@ async function executeFileList(input, context, timeoutMs) {
19340
19577
  const root = typeof input.path === "string" && input.path.trim().length > 0 ? input.path : ".";
19341
19578
  const pattern = typeof input.pattern === "string" && input.pattern.trim().length > 0 ? input.pattern : null;
19342
19579
  const limit = parseLimitValue(input.limit, 200);
19343
- const searchRoot = resolveWorkspacePath(context.cwd, root);
19580
+ const searchRoot = resolveWorkspacePath(context.cwd, root, context.trustedDirectories);
19344
19581
  const relativeRoot = import_node_path11.default.relative(context.cwd, searchRoot) || ".";
19345
19582
  const rootArg = relativeRoot === "." ? "." : relativeRoot;
19346
19583
  let result;
@@ -19388,7 +19625,7 @@ async function executeFileSearch(input, context, timeoutMs) {
19388
19625
  if (!pattern) {
19389
19626
  throw new Error("file.search requires a non-empty pattern.");
19390
19627
  }
19391
- const searchRoot = resolveWorkspacePath(context.cwd, root);
19628
+ const searchRoot = resolveWorkspacePath(context.cwd, root, context.trustedDirectories);
19392
19629
  const relativeRoot = import_node_path11.default.relative(context.cwd, searchRoot) || ".";
19393
19630
  const rootArg = relativeRoot === "." ? "." : relativeRoot;
19394
19631
  let result;
@@ -19444,7 +19681,7 @@ async function executeFileWrite(input, context) {
19444
19681
  if (content === null) {
19445
19682
  throw new Error("file.write requires a string content field.");
19446
19683
  }
19447
- const targetPath = resolveWorkspacePath(context.cwd, rawPath);
19684
+ const targetPath = resolveWorkspacePath(context.cwd, rawPath, context.trustedDirectories);
19448
19685
  let existed = true;
19449
19686
  let previousContent = "";
19450
19687
  try {
@@ -19522,7 +19759,7 @@ async function executeGitDiff(input, context, timeoutMs) {
19522
19759
  "--unified=3"
19523
19760
  ];
19524
19761
  if (rawPath) {
19525
- const targetPath = resolveWorkspacePath(context.cwd, rawPath);
19762
+ const targetPath = resolveWorkspacePath(context.cwd, rawPath, context.trustedDirectories);
19526
19763
  const relativeTarget = import_node_path11.default.relative(context.cwd, targetPath) || ".";
19527
19764
  args.push("--", relativeTarget);
19528
19765
  }
@@ -19859,7 +20096,7 @@ async function executeProcessStart(input, context) {
19859
20096
  if (!command) {
19860
20097
  throw new Error("process.start requires a command string.");
19861
20098
  }
19862
- const processCwd = rawPath ? resolveWorkspacePath(context.cwd, rawPath) : context.cwd;
20099
+ const processCwd = rawPath ? resolveWorkspacePath(context.cwd, rawPath, context.trustedDirectories) : context.cwd;
19863
20100
  const record2 = await startManagedProcess(processCwd, command, name, context.signal);
19864
20101
  return ToolResultSchema.parse({
19865
20102
  toolId: "process.start",
@@ -19957,7 +20194,7 @@ async function executeFilePatch(input, context, timeoutMs) {
19957
20194
  if (!patch) {
19958
20195
  throw new Error("file.patch requires a unified diff in the patch field.");
19959
20196
  }
19960
- const tempDir = await (0, import_promises11.mkdtemp)(import_node_path11.default.join((0, import_node_os2.tmpdir)(), "kimbho-patch-"));
20197
+ const tempDir = await (0, import_promises11.mkdtemp)(import_node_path11.default.join((0, import_node_os3.tmpdir)(), "kimbho-patch-"));
19961
20198
  const patchPath = import_node_path11.default.join(tempDir, "change.patch");
19962
20199
  try {
19963
20200
  await (0, import_promises11.writeFile)(patchPath, patch, "utf8");
@@ -20310,10 +20547,10 @@ function assignProp(target, prop, value) {
20310
20547
  configurable: true
20311
20548
  });
20312
20549
  }
20313
- function getElementAtPath(obj, path21) {
20314
- if (!path21)
20550
+ function getElementAtPath(obj, path22) {
20551
+ if (!path22)
20315
20552
  return obj;
20316
- return path21.reduce((acc, key) => acc?.[key], obj);
20553
+ return path22.reduce((acc, key) => acc?.[key], obj);
20317
20554
  }
20318
20555
  function promiseAllObject(promisesObj) {
20319
20556
  const keys = Object.keys(promisesObj);
@@ -20633,11 +20870,11 @@ function aborted(x, startIndex = 0) {
20633
20870
  }
20634
20871
  return false;
20635
20872
  }
20636
- function prefixIssues(path21, issues) {
20873
+ function prefixIssues(path22, issues) {
20637
20874
  return issues.map((iss) => {
20638
20875
  var _a;
20639
20876
  (_a = iss).path ?? (_a.path = []);
20640
- iss.path.unshift(path21);
20877
+ iss.path.unshift(path22);
20641
20878
  return iss;
20642
20879
  });
20643
20880
  }
@@ -27385,6 +27622,7 @@ var MCP_ALLOWED_ROLES = [
27385
27622
  "reviewer",
27386
27623
  "integrator"
27387
27624
  ];
27625
+ var MCP_INVENTORY_TIMEOUT_MS = 8e3;
27388
27626
  function toMcpToolId(serverName, toolName) {
27389
27627
  return `mcp.${serverName}.${toolName}`;
27390
27628
  }
@@ -27427,7 +27665,7 @@ function renderMcpContent(result) {
27427
27665
  }
27428
27666
  return sections.length > 0 ? sections.join("\n\n") : void 0;
27429
27667
  }
27430
- async function withMcpClient(cwd, name, server, handler) {
27668
+ async function withMcpClient(cwd, name, server, handler, options = {}) {
27431
27669
  const transport = new StdioClientTransport({
27432
27670
  command: server.command,
27433
27671
  args: server.args,
@@ -27446,10 +27684,31 @@ async function withMcpClient(cwd, name, server, handler) {
27446
27684
  name: "kimbho",
27447
27685
  version: "0.1.13"
27448
27686
  });
27449
- await client.connect(transport);
27687
+ const run = async () => {
27688
+ await client.connect(transport);
27689
+ return handler(client);
27690
+ };
27450
27691
  try {
27451
- return await handler(client);
27692
+ if (typeof options.timeoutMs === "number" && options.timeoutMs > 0) {
27693
+ let timer;
27694
+ try {
27695
+ return await Promise.race([
27696
+ run(),
27697
+ new Promise((_, reject) => {
27698
+ timer = setTimeout(() => {
27699
+ reject(new Error(`${options.timeoutLabel ?? `MCP call for ${name}`} timed out after ${options.timeoutMs}ms.`));
27700
+ }, options.timeoutMs);
27701
+ })
27702
+ ]);
27703
+ } finally {
27704
+ if (timer) {
27705
+ clearTimeout(timer);
27706
+ }
27707
+ }
27708
+ }
27709
+ return await run();
27452
27710
  } finally {
27711
+ await client.close().catch(() => void 0);
27453
27712
  await transport.close().catch(() => void 0);
27454
27713
  }
27455
27714
  }
@@ -27458,31 +27717,65 @@ async function loadMcpServerInventory(cwd) {
27458
27717
  const servers = Object.entries(config2.mcpServers).filter(([, server]) => server.enabled !== false).sort(([left], [right]) => left.localeCompare(right));
27459
27718
  const inventory = [];
27460
27719
  for (const [name, server] of servers) {
27461
- const record2 = await withMcpClient(cwd, name, server, async (client) => {
27462
- const [toolsResponse, promptsResponse, resourcesResponse] = await Promise.all([
27463
- client.listTools().catch(() => ({ tools: [] })),
27464
- client.listPrompts().catch(() => ({ prompts: [] })),
27465
- client.listResources().catch(() => ({ resources: [] }))
27466
- ]);
27467
- const instructions = client.getInstructions();
27468
- return {
27720
+ try {
27721
+ const record2 = await withMcpClient(cwd, name, server, async (client) => {
27722
+ const [toolsResponse, promptsResponse, resourcesResponse, resourceTemplatesResponse] = await Promise.all([
27723
+ client.listTools().catch(() => ({ tools: [] })),
27724
+ client.listPrompts().catch(() => ({ prompts: [] })),
27725
+ client.listResources().catch(() => ({ resources: [] })),
27726
+ client.listResourceTemplates().catch(() => ({ resourceTemplates: [] }))
27727
+ ]);
27728
+ const instructions = client.getInstructions();
27729
+ return {
27730
+ name,
27731
+ config: server,
27732
+ tools: (toolsResponse.tools ?? []).map((tool) => ({
27733
+ serverName: name,
27734
+ toolName: tool.name,
27735
+ toolId: toMcpToolId(name, tool.name),
27736
+ description: tool.description ?? `MCP tool ${tool.name} from ${name}.`,
27737
+ permission: permissionFromAnnotations(tool.annotations)
27738
+ })),
27739
+ prompts: (promptsResponse.prompts ?? []).map((prompt) => ({
27740
+ serverName: name,
27741
+ name: prompt.name,
27742
+ description: prompt.description ?? `MCP prompt ${prompt.name} from ${name}.`,
27743
+ arguments: prompt.arguments ?? []
27744
+ })),
27745
+ resources: (resourcesResponse.resources ?? []).map((resource) => ({
27746
+ serverName: name,
27747
+ uri: resource.uri,
27748
+ name: resource.name,
27749
+ description: resource.description,
27750
+ mimeType: resource.mimeType
27751
+ })),
27752
+ resourceTemplates: (resourceTemplatesResponse.resourceTemplates ?? []).map((resourceTemplate) => ({
27753
+ serverName: name,
27754
+ uriTemplate: resourceTemplate.uriTemplate,
27755
+ name: resourceTemplate.name,
27756
+ description: resourceTemplate.description,
27757
+ mimeType: resourceTemplate.mimeType
27758
+ })),
27759
+ ...instructions ? {
27760
+ instructions
27761
+ } : {}
27762
+ };
27763
+ }, {
27764
+ timeoutMs: MCP_INVENTORY_TIMEOUT_MS,
27765
+ timeoutLabel: `MCP inventory for ${name}`
27766
+ });
27767
+ inventory.push(record2);
27768
+ } catch (error2) {
27769
+ inventory.push({
27469
27770
  name,
27470
27771
  config: server,
27471
- tools: (toolsResponse.tools ?? []).map((tool) => ({
27472
- serverName: name,
27473
- toolName: tool.name,
27474
- toolId: toMcpToolId(name, tool.name),
27475
- description: tool.description ?? `MCP tool ${tool.name} from ${name}.`,
27476
- permission: permissionFromAnnotations(tool.annotations)
27477
- })),
27478
- prompts: (promptsResponse.prompts ?? []).map((prompt) => prompt.name),
27479
- resources: (resourcesResponse.resources ?? []).map((resource) => resource.name ?? resource.uri),
27480
- ...instructions ? {
27481
- instructions
27482
- } : {}
27483
- };
27484
- });
27485
- inventory.push(record2);
27772
+ tools: [],
27773
+ prompts: [],
27774
+ resources: [],
27775
+ resourceTemplates: [],
27776
+ warning: error2 instanceof Error ? error2.message : String(error2)
27777
+ });
27778
+ }
27486
27779
  }
27487
27780
  return inventory;
27488
27781
  }
@@ -27513,6 +27806,59 @@ async function callMcpTool(cwd, serverName, toolName, input) {
27513
27806
  artifacts: []
27514
27807
  };
27515
27808
  }
27809
+ function renderPromptContent(result) {
27810
+ return result.messages.map((message) => {
27811
+ if (message.content.type === "text") {
27812
+ return `[${message.role}] ${message.content.text}`;
27813
+ }
27814
+ if (message.content.type === "resource") {
27815
+ const resource = message.content.resource;
27816
+ return `[${message.role}] resource ${resource.uri}
27817
+ ${"text" in resource ? resource.text : `[binary ${resource.mimeType ?? "resource"}]`}`;
27818
+ }
27819
+ if (message.content.type === "resource_link") {
27820
+ return `[${message.role}] resource-link ${message.content.uri}`;
27821
+ }
27822
+ return `[${message.role}] ${JSON.stringify(message.content, null, 2)}`;
27823
+ }).join("\n\n");
27824
+ }
27825
+ async function invokeMcpPrompt(cwd, serverName, promptName, args) {
27826
+ const config2 = await loadMcpConfig(cwd);
27827
+ const server = config2.mcpServers[serverName];
27828
+ if (!server || server.enabled === false) {
27829
+ throw new Error(`MCP server ${serverName} is not configured or is disabled.`);
27830
+ }
27831
+ const result = await withMcpClient(cwd, serverName, server, async (client) => client.getPrompt({
27832
+ name: promptName,
27833
+ arguments: args
27834
+ }));
27835
+ return {
27836
+ serverName,
27837
+ promptName,
27838
+ ...result.description ? {
27839
+ description: result.description
27840
+ } : {},
27841
+ content: renderPromptContent(result)
27842
+ };
27843
+ }
27844
+ async function readMcpResource(cwd, serverName, uri) {
27845
+ const config2 = await loadMcpConfig(cwd);
27846
+ const server = config2.mcpServers[serverName];
27847
+ if (!server || server.enabled === false) {
27848
+ throw new Error(`MCP server ${serverName} is not configured or is disabled.`);
27849
+ }
27850
+ const result = await withMcpClient(cwd, serverName, server, async (client) => client.readResource({
27851
+ uri
27852
+ }));
27853
+ const content = result.contents.map((item) => "text" in item ? `resource: ${item.uri}
27854
+ ${item.text}` : `resource: ${item.uri}
27855
+ [binary ${item.mimeType ?? "resource"}]`).join("\n\n");
27856
+ return {
27857
+ serverName,
27858
+ uri,
27859
+ content
27860
+ };
27861
+ }
27516
27862
  async function registerMcpToolsForWorkspace(cwd, registry2, runtime) {
27517
27863
  const tools = await discoverMcpTools(cwd);
27518
27864
  for (const tool of tools) {
@@ -27536,7 +27882,7 @@ async function registerMcpToolsForWorkspace(cwd, registry2, runtime) {
27536
27882
 
27537
27883
  // ../agent-runtime/dist/autonomous.js
27538
27884
  var MAX_TOOL_OUTPUT_CHARS = 4e3;
27539
- var MAX_PARSE_RETRIES = 2;
27885
+ var MAX_PARSE_RETRIES = 1;
27540
27886
  var DEFAULT_MAX_REPAIR_ATTEMPTS = 2;
27541
27887
  var CONTEXT_FILE_READ_LIMIT = 3;
27542
27888
  var SESSION_NOTE_LIMIT = 4;
@@ -27552,6 +27898,14 @@ var BOOTSTRAP_CONTEXT_FILES = [
27552
27898
  "README.md",
27553
27899
  "package.json"
27554
27900
  ];
27901
+ async function fileExists(cwd, filePath) {
27902
+ try {
27903
+ await (0, import_promises12.access)(import_node_path12.default.join(cwd, filePath));
27904
+ return true;
27905
+ } catch {
27906
+ return false;
27907
+ }
27908
+ }
27555
27909
  function createAgentActionSchema(allowedTools) {
27556
27910
  return external_exports.discriminatedUnion("type", [
27557
27911
  external_exports.object({
@@ -27718,6 +28072,9 @@ function buildSystemPrompt(agent, task, request, allowedTools, plan, extraInstru
27718
28072
  return [
27719
28073
  `You are Kimbho's ${agent.role}.`,
27720
28074
  `Purpose: ${agent.purpose}`,
28075
+ ...task.subagentLabel ? [
28076
+ `Delegated worker: ${task.subagentLabel}`
28077
+ ] : [],
27721
28078
  `Goal: ${request.goal}`,
27722
28079
  `Current task: ${task.id} - ${task.title}`,
27723
28080
  `Task description: ${task.description}`,
@@ -27751,6 +28108,10 @@ function buildSystemPrompt(agent, task, request, allowedTools, plan, extraInstru
27751
28108
  `- After changing code, run verification with tests.run or shell.exec when appropriate.`,
27752
28109
  `- Do not claim success unless the task acceptance criteria are satisfied.`,
27753
28110
  `- If the task is underspecified, make a pragmatic implementation choice and continue.`,
28111
+ ...task.subagentInstructions ? [
28112
+ `Delegation instructions:`,
28113
+ task.subagentInstructions
28114
+ ] : [],
27754
28115
  ...extraInstructions ? [
27755
28116
  `Custom instructions:`,
27756
28117
  extraInstructions
@@ -28165,6 +28526,7 @@ var AutonomousTaskExecutor = class {
28165
28526
  signal: options.signal,
28166
28527
  approvalMode: this.config.approvalMode,
28167
28528
  sandboxMode: this.config.sandboxMode,
28529
+ trustedDirectories: this.config.trustedDirectories,
28168
28530
  agentRole: task.agentRole,
28169
28531
  sessionId,
28170
28532
  taskId: task.id,
@@ -28174,6 +28536,7 @@ var AutonomousTaskExecutor = class {
28174
28536
  cwd: request.cwd,
28175
28537
  approvalMode: this.config.approvalMode,
28176
28538
  sandboxMode: this.config.sandboxMode,
28539
+ trustedDirectories: this.config.trustedDirectories,
28177
28540
  agentRole: task.agentRole,
28178
28541
  sessionId,
28179
28542
  taskId: task.id,
@@ -28244,7 +28607,13 @@ var AutonomousTaskExecutor = class {
28244
28607
  ...BOOTSTRAP_CONTEXT_FILES,
28245
28608
  ...filesToRead
28246
28609
  ] : BOOTSTRAP_CONTEXT_FILES)).slice(0, CONTEXT_FILE_READ_LIMIT + 4);
28610
+ const existingContextFiles = [];
28247
28611
  for (const filePath of fallbackFiles) {
28612
+ if (await fileExists(request.cwd, filePath)) {
28613
+ existingContextFiles.push(filePath);
28614
+ }
28615
+ }
28616
+ for (const filePath of existingContextFiles) {
28248
28617
  const readResult = await runPreflightTool("file.read", {
28249
28618
  path: filePath
28250
28619
  }, "Load an existing repo-defining file before the first model action.");
@@ -28256,12 +28625,23 @@ var AutonomousTaskExecutor = class {
28256
28625
  const contextRootCwd = options.contextRootCwd ?? request.cwd;
28257
28626
  const savedSession = await loadLatestSession(contextRootCwd).catch(() => null);
28258
28627
  const savedSessionSummary = summarizeSavedSessionContext(sessionId, savedSession);
28628
+ const recursiveMemory = await loadMarkdownMemoryContext(request.cwd, {
28629
+ maxFiles: 10,
28630
+ maxCharsPerFile: 4e3
28631
+ });
28259
28632
  const userMemory = await readMarkdownIfExists(resolveUserMemoryPath());
28260
28633
  const projectMemory = await readMarkdownIfExists(resolveProjectMemoryPath(request.cwd));
28261
28634
  const customAgentMemory = customOverlay ? await readMarkdownIfExists(import_node_path12.default.join(resolveAgentMemoryDir(request.cwd), `${customOverlay.definition.id}.md`)) : null;
28262
28635
  if (savedSessionSummary.length > 0) {
28263
28636
  contextMessages.unshift(savedSessionSummary.join("\n"));
28264
28637
  }
28638
+ if (recursiveMemory.length > 0) {
28639
+ contextMessages.unshift([
28640
+ "Recursive markdown memory context:",
28641
+ ...recursiveMemory.map((record2) => `File: ${record2.filePath}
28642
+ ${truncateForModel(record2.content)}`)
28643
+ ].join("\n\n"));
28644
+ }
28265
28645
  if (userMemory) {
28266
28646
  contextMessages.unshift(`Global user memory:
28267
28647
  ${truncateForModel(userMemory)}`);
@@ -28306,6 +28686,7 @@ ${truncateForModel(customAgentMemory)}`);
28306
28686
  signal: options.signal,
28307
28687
  approvalMode: this.config.approvalMode,
28308
28688
  sandboxMode: this.config.sandboxMode,
28689
+ trustedDirectories: this.config.trustedDirectories,
28309
28690
  agentRole: task.agentRole,
28310
28691
  operatorApproved,
28311
28692
  sessionId,
@@ -28318,6 +28699,7 @@ ${truncateForModel(customAgentMemory)}`);
28318
28699
  cwd: request.cwd,
28319
28700
  approvalMode: this.config.approvalMode,
28320
28701
  sandboxMode: this.config.sandboxMode,
28702
+ trustedDirectories: this.config.trustedDirectories,
28321
28703
  agentRole: task.agentRole,
28322
28704
  operatorApproved,
28323
28705
  sessionId,
@@ -28516,14 +28898,16 @@ ${truncateForModel(customAgentMemory)}`);
28516
28898
  let responseText = "";
28517
28899
  let parsedAction = null;
28518
28900
  for (let attempt = 0; attempt <= MAX_PARSE_RETRIES; attempt += 1) {
28519
- await emitProgress({
28520
- type: "task-note",
28521
- sessionId,
28522
- taskId: task.id,
28523
- agentRole: task.agentRole,
28524
- step,
28525
- message: attempt === 0 ? "Thinking through the next safe action." : `Re-asking the model for a stricter machine-readable action (${attempt + 1}/${MAX_PARSE_RETRIES + 1}).`
28526
- });
28901
+ if (attempt === 0) {
28902
+ await emitProgress({
28903
+ type: "task-note",
28904
+ sessionId,
28905
+ taskId: task.id,
28906
+ agentRole: task.agentRole,
28907
+ step,
28908
+ message: "Thinking through the next safe action."
28909
+ });
28910
+ }
28527
28911
  let response;
28528
28912
  try {
28529
28913
  response = await brain.client.generateText({
@@ -28591,20 +28975,20 @@ ${truncateForModel(customAgentMemory)}`);
28591
28975
  response: response.text,
28592
28976
  runtimeNote: `${parseSummary} ${error2 instanceof Error ? error2.message : String(error2)}`
28593
28977
  });
28978
+ const inferredFallbackAction = inferFallbackActionFromResponse(response.text, task, request, allowedTools);
28979
+ if (inferredFallbackAction) {
28980
+ parsedAction = inferredFallbackAction;
28981
+ await emitProgress({
28982
+ type: "task-note",
28983
+ sessionId,
28984
+ taskId: task.id,
28985
+ agentRole: task.agentRole,
28986
+ step,
28987
+ message: `Interpreted the model draft as ${inferredFallbackAction.tool}.`
28988
+ });
28989
+ break;
28990
+ }
28594
28991
  if (attempt === MAX_PARSE_RETRIES) {
28595
- const inferredFallbackAction = inferFallbackActionFromResponse(response.text, task, request, allowedTools);
28596
- if (inferredFallbackAction) {
28597
- parsedAction = inferredFallbackAction;
28598
- await emitProgress({
28599
- type: "task-note",
28600
- sessionId,
28601
- taskId: task.id,
28602
- agentRole: task.agentRole,
28603
- step,
28604
- message: `Model stayed out of structured mode; inferred ${inferredFallbackAction.tool} from the response.`
28605
- });
28606
- break;
28607
- }
28608
28992
  const transcriptPath2 = await writeTranscriptArtifact(request.cwd, sessionId, task.id, transcript);
28609
28993
  artifacts.add(transcriptPath2);
28610
28994
  await emitProgress({
@@ -28629,7 +29013,7 @@ ${truncateForModel(customAgentMemory)}`);
28629
29013
  taskId: task.id,
28630
29014
  agentRole: task.agentRole,
28631
29015
  step,
28632
- message: `Reformatting model output to match Kimbho's action schema (${attempt + 1}/${MAX_PARSE_RETRIES + 1}).`
29016
+ message: "Normalizing the model draft into one safe machine-readable action."
28633
29017
  });
28634
29018
  messages.push({
28635
29019
  role: "assistant",
@@ -28795,6 +29179,47 @@ function normalizeGoal(goal) {
28795
29179
  }
28796
29180
  function looksLikeStaticSiteGoal(goal) {
28797
29181
  const lower = goal.toLowerCase();
29182
+ const backendSignals = [
29183
+ "api",
29184
+ "database",
29185
+ "backend",
29186
+ "server",
29187
+ "migration",
29188
+ "schema",
29189
+ "prisma",
29190
+ "postgres",
29191
+ "sqlite",
29192
+ "auth",
29193
+ "billing",
29194
+ "webhook"
29195
+ ];
29196
+ const pageSignals = [
29197
+ "page",
29198
+ "landing",
29199
+ "lnding",
29200
+ "homepage",
29201
+ "home page",
29202
+ "site",
29203
+ "website",
29204
+ "ui",
29205
+ "hero",
29206
+ "layout",
29207
+ "screen"
29208
+ ];
29209
+ const polishSignals = [
29210
+ "animate",
29211
+ "animated",
29212
+ "animation",
29213
+ "motion",
29214
+ "rich",
29215
+ "richer",
29216
+ "polish",
29217
+ "styled",
29218
+ "styling",
29219
+ "visual",
29220
+ "redesign",
29221
+ "revamp"
29222
+ ];
28798
29223
  if (lower.includes("landing page") || lower.includes("landing") || lower.includes("lnding") || lower.includes("homepage") || lower.includes("home page") || lower.includes("marketing page") || lower.includes("storefront") || lower.includes("restaurant website") || lower.includes("restaurant site") || lower.includes("static site")) {
28799
29224
  return true;
28800
29225
  }
@@ -28804,6 +29229,12 @@ function looksLikeStaticSiteGoal(goal) {
28804
29229
  if (lower.includes("restaurant") && (lower.includes("page") || lower.includes("site") || lower.includes("website") || lower.includes("landing") || lower.includes("lnding"))) {
28805
29230
  return true;
28806
29231
  }
29232
+ if (pageSignals.some((signal) => lower.includes(signal)) && polishSignals.some((signal) => lower.includes(signal)) && !backendSignals.some((signal) => lower.includes(signal))) {
29233
+ return true;
29234
+ }
29235
+ if (lower.includes("blog") && (lower.includes("make") || lower.includes("improve") || lower.includes("enhance") || lower.includes("redesign")) && !backendSignals.some((signal) => lower.includes(signal))) {
29236
+ return true;
29237
+ }
28807
29238
  return false;
28808
29239
  }
28809
29240
  function inferProjectShape(goal) {
@@ -29577,7 +30008,7 @@ function terminalTaskIds(tasks) {
29577
30008
  const terminalIds = tasks.filter((task) => !referencedIds.has(task.id)).map((task) => task.id);
29578
30009
  return terminalIds.length > 0 ? terminalIds : tasks.map((task) => task.id);
29579
30010
  }
29580
- function integrateExpandedTasks(plan, taskId, expandedTasks, note) {
30011
+ function integratePlanTaskExpansion(plan, taskId, expandedTasks, note) {
29581
30012
  const terminalIds = terminalTaskIds(expandedTasks);
29582
30013
  const milestones = plan.milestones.map((milestone) => ({
29583
30014
  ...milestone,
@@ -29687,7 +30118,7 @@ async function generateExpandedTasks(request, plan, task, invocation, repoSummar
29687
30118
  const normalizedTasks = normalizeExpandedTasks(plan, task, payload, "model");
29688
30119
  const note = `Planner expanded ${task.id} into ${normalizedTasks.length} subtasks via ${invocation.modelLabel ?? "planner model"}: ${normalizedTasks.map((candidate) => candidate.id).join(", ")}.`;
29689
30120
  return {
29690
- plan: integrateExpandedTasks(plan, task.id, normalizedTasks, note),
30121
+ plan: integratePlanTaskExpansion(plan, task.id, normalizedTasks, note),
29691
30122
  source: "model",
29692
30123
  expandedTaskId: task.id,
29693
30124
  createdTaskIds: normalizedTasks.map((candidate) => candidate.id),
@@ -29716,7 +30147,7 @@ async function generateExpandedTasks(request, plan, task, invocation, repoSummar
29716
30147
  }
29717
30148
  const note = `Planner fell back to deterministic subtask expansion for ${task.id}: ${fallbackTasks.map((candidate) => candidate.id).join(", ")}.`;
29718
30149
  return {
29719
- plan: integrateExpandedTasks(plan, task.id, fallbackTasks, note),
30150
+ plan: integratePlanTaskExpansion(plan, task.id, fallbackTasks, note),
29720
30151
  source: "fallback",
29721
30152
  expandedTaskId: task.id,
29722
30153
  createdTaskIds: fallbackTasks.map((candidate) => candidate.id),
@@ -29761,7 +30192,7 @@ function expandPlanTaskFallback(plan, taskId) {
29761
30192
  }
29762
30193
  const note = `Planner deterministically expanded ${task.id} into ${fallbackTasks.length} subtasks: ${fallbackTasks.map((candidate) => candidate.id).join(", ")}.`;
29763
30194
  return {
29764
- plan: integrateExpandedTasks(plan, task.id, fallbackTasks, note),
30195
+ plan: integratePlanTaskExpansion(plan, task.id, fallbackTasks, note),
29765
30196
  source: "fallback",
29766
30197
  expandedTaskId: task.id,
29767
30198
  createdTaskIds: fallbackTasks.map((candidate) => candidate.id),
@@ -30695,6 +31126,19 @@ function maybeAppendNote(notes, note) {
30695
31126
  note
30696
31127
  ];
30697
31128
  }
31129
+ function slugify2(value) {
31130
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/-{2,}/g, "-");
31131
+ }
31132
+ function createUniqueTaskId2(baseId, existingIds) {
31133
+ let candidate = baseId;
31134
+ let index = 2;
31135
+ while (existingIds.has(candidate)) {
31136
+ candidate = `${baseId}-${index}`;
31137
+ index += 1;
31138
+ }
31139
+ existingIds.add(candidate);
31140
+ return candidate;
31141
+ }
30698
31142
  function mergeUnique(items) {
30699
31143
  return Array.from(new Set(items.filter((item) => item.trim().length > 0)));
30700
31144
  }
@@ -30707,6 +31151,280 @@ function isStaticSiteTask(task, request) {
30707
31151
  }
30708
31152
  return task.filesLikelyTouched.some((filePath) => filePath === "src/app/page.tsx" || filePath === "src/app/layout.tsx" || filePath === "src/pages/index.tsx" || filePath === "index.html" || filePath === "styles.css");
30709
31153
  }
31154
+ function shouldDelegateTask(task) {
31155
+ return (task.swarmDepth ?? 0) === 0 && task.type === "implementation" && ![
31156
+ "repo-analyst",
31157
+ "planner",
31158
+ "reviewer",
31159
+ "integrator",
31160
+ "test-debugger",
31161
+ "session-orchestrator",
31162
+ "execution-manager"
31163
+ ].includes(task.agentRole);
31164
+ }
31165
+ function terminalExpansionTaskIds(tasks) {
31166
+ const referencedIds = new Set(tasks.flatMap((task) => task.dependsOn));
31167
+ const terminalIds = tasks.filter((task) => !referencedIds.has(task.id)).map((task) => task.id);
31168
+ return terminalIds.length > 0 ? terminalIds : tasks.map((task) => task.id);
31169
+ }
31170
+ function createDefaultDelegationPlan(task, request) {
31171
+ if (!shouldDelegateTask(task)) {
31172
+ return null;
31173
+ }
31174
+ if (task.agentRole === "frontend-specialist" || isStaticSiteTask(task, request)) {
31175
+ return {
31176
+ label: "auto-frontend-swarm",
31177
+ strategy: "parallel",
31178
+ members: [
31179
+ {
31180
+ id: "content-architect",
31181
+ label: "content-architect",
31182
+ agentRole: "frontend-specialist",
31183
+ description: "Own information architecture, page composition, navigation, copy structure, and section flow.",
31184
+ instructions: "Prioritize semantic layout, page structure, routes, content hierarchy, and component composition. Leave purely visual polish to the visual worker unless structure blocks it.",
31185
+ acceptanceCriteria: [
31186
+ "The page structure, sections, and navigation flow are clear and coherent.",
31187
+ "Content hierarchy reflects the requested product, brand, or venue."
31188
+ ],
31189
+ outputs: [
31190
+ "Structured page/content update"
31191
+ ]
31192
+ },
31193
+ {
31194
+ id: "visual-polisher",
31195
+ label: "visual-polisher",
31196
+ agentRole: "frontend-specialist",
31197
+ description: "Own styling, gradients, motion, responsiveness, spacing, and visual finishing.",
31198
+ instructions: "Focus on visual quality, gradients, motion, responsiveness, spacing, and interaction polish after inspecting the current structure. Keep edits aligned with the existing design language when present.",
31199
+ acceptanceCriteria: [
31200
+ "Visual styling and responsiveness are meaningfully improved.",
31201
+ "Motion and polish remain consistent with the requested UI direction."
31202
+ ],
31203
+ outputs: [
31204
+ "Visual polish update"
31205
+ ]
31206
+ }
31207
+ ]
31208
+ };
31209
+ }
31210
+ if (task.agentRole === "backend-specialist") {
31211
+ return {
31212
+ label: "auto-backend-swarm",
31213
+ strategy: "sequential",
31214
+ members: [
31215
+ {
31216
+ id: "contract-architect",
31217
+ label: "contract-architect",
31218
+ agentRole: "backend-specialist",
31219
+ description: "Define the API/service surface, request-response shapes, and failure behavior.",
31220
+ instructions: "Focus on service and API contracts, input/output shapes, handlers, and edge cases before broad implementation changes.",
31221
+ acceptanceCriteria: [
31222
+ "Contracts and failure cases are explicit.",
31223
+ "The implementation path is narrowed to concrete handlers or services."
31224
+ ],
31225
+ outputs: [
31226
+ "Backend contract design"
31227
+ ]
31228
+ },
31229
+ {
31230
+ id: "service-implementer",
31231
+ label: "service-implementer",
31232
+ agentRole: "backend-specialist",
31233
+ description: "Implement handlers, services, and domain logic against the agreed contract.",
31234
+ instructions: "Implement the core backend behavior against the established contract. Prefer targeted file edits and verification over broad rewrites.",
31235
+ acceptanceCriteria: [
31236
+ "Business logic is implemented against the contract.",
31237
+ "The backend behavior satisfies the task acceptance criteria."
31238
+ ],
31239
+ outputs: [
31240
+ "Backend implementation"
31241
+ ]
31242
+ }
31243
+ ]
31244
+ };
31245
+ }
31246
+ if (task.agentRole === "database-specialist") {
31247
+ return {
31248
+ label: "auto-database-swarm",
31249
+ strategy: "sequential",
31250
+ members: [
31251
+ {
31252
+ id: "schema-designer",
31253
+ label: "schema-designer",
31254
+ agentRole: "database-specialist",
31255
+ description: "Design models, fields, relations, constraints, and persistence boundaries.",
31256
+ instructions: "Focus on schema structure, data modeling, constraints, and identifiers. Keep the migration path safe and explicit.",
31257
+ acceptanceCriteria: [
31258
+ "Schema design reflects the requested domain.",
31259
+ "Constraints and data relationships are explicit."
31260
+ ],
31261
+ outputs: [
31262
+ "Schema design"
31263
+ ]
31264
+ },
31265
+ {
31266
+ id: "migration-planner",
31267
+ label: "migration-planner",
31268
+ agentRole: "database-specialist",
31269
+ description: "Apply or prepare migrations and persistence wiring around the approved schema.",
31270
+ instructions: "Translate the schema design into migrations or persistence updates. Keep destructive changes obvious and verification-friendly.",
31271
+ acceptanceCriteria: [
31272
+ "Persistence updates align with the intended schema.",
31273
+ "Migration risk and verification needs are explicit."
31274
+ ],
31275
+ outputs: [
31276
+ "Migration/persistence update"
31277
+ ]
31278
+ }
31279
+ ]
31280
+ };
31281
+ }
31282
+ if (task.agentRole === "infra-specialist") {
31283
+ return {
31284
+ label: "auto-infra-swarm",
31285
+ strategy: "sequential",
31286
+ members: [
31287
+ {
31288
+ id: "workspace-scaffolder",
31289
+ label: "workspace-scaffolder",
31290
+ agentRole: "infra-specialist",
31291
+ description: "Set up required files, scripts, package wiring, and environment plumbing.",
31292
+ instructions: "Focus on scaffolding, package manifests, scripts, environment wiring, and local runtime setup. Keep the workspace runnable.",
31293
+ acceptanceCriteria: [
31294
+ "Required scaffolding and environment wiring are in place.",
31295
+ "The workspace can proceed into implementation or verification."
31296
+ ],
31297
+ outputs: [
31298
+ "Workspace scaffolding"
31299
+ ]
31300
+ },
31301
+ {
31302
+ id: "runtime-verifier",
31303
+ label: "runtime-verifier",
31304
+ agentRole: "infra-specialist",
31305
+ description: "Verify build/runtime setup and tighten any configuration or tooling gaps.",
31306
+ instructions: "Focus on runtime, toolchain, script, and build verification after the initial setup work has landed.",
31307
+ acceptanceCriteria: [
31308
+ "The environment or app can be verified locally.",
31309
+ "Tooling and configuration gaps are closed."
31310
+ ],
31311
+ outputs: [
31312
+ "Runtime/config verification"
31313
+ ]
31314
+ }
31315
+ ]
31316
+ };
31317
+ }
31318
+ return null;
31319
+ }
31320
+ function materializeDelegatedTasks(plan, task, label, strategy, members) {
31321
+ const existingIds = new Set(flattenPlanTasks(plan).filter((candidate) => candidate.id !== task.id).map((candidate) => candidate.id));
31322
+ const teamMemberIds = members.map((member) => member.id);
31323
+ const generatedTaskIds = members.map((member, index) => createUniqueTaskId2(`${task.id}-${slugify2(member.id) || `worker-${index + 1}`}`, existingIds));
31324
+ return members.map((member, index) => {
31325
+ const taskId = generatedTaskIds[index];
31326
+ const dependsOn = strategy === "parallel" ? [
31327
+ ...task.dependsOn
31328
+ ] : index === 0 ? [
31329
+ ...task.dependsOn
31330
+ ] : [
31331
+ generatedTaskIds[index - 1]
31332
+ ];
31333
+ return {
31334
+ ...task,
31335
+ id: taskId,
31336
+ title: `${task.title} (${member.label})`,
31337
+ description: [
31338
+ task.description,
31339
+ `Delegated worker: ${member.label}.`,
31340
+ member.description
31341
+ ].join("\n\n"),
31342
+ agentRole: member.agentRole,
31343
+ dependsOn,
31344
+ acceptanceCriteria: member.acceptanceCriteria?.length ? member.acceptanceCriteria : index === members.length - 1 ? task.acceptanceCriteria : [
31345
+ `Advance ${task.title.toLowerCase()} from the ${member.label} perspective.`,
31346
+ "Leave concrete implementation evidence for the next worker."
31347
+ ],
31348
+ outputs: member.outputs?.length ? member.outputs : index === members.length - 1 ? task.outputs : [
31349
+ `Delegated handoff from ${member.id}`
31350
+ ],
31351
+ filesLikelyTouched: member.filesLikelyTouched?.length ? member.filesLikelyTouched : task.filesLikelyTouched,
31352
+ originTaskId: task.id,
31353
+ swarmDepth: (task.swarmDepth ?? 0) + 1,
31354
+ teamId: label,
31355
+ teamMemberIds,
31356
+ subagentLabel: member.label,
31357
+ subagentInstructions: member.instructions
31358
+ };
31359
+ });
31360
+ }
31361
+ function appendReviewAndIntegrationTasks(plan, originalTask, expandedTasks, delegationLabel) {
31362
+ const existingIds = new Set(flattenPlanTasks(plan).filter((candidate) => candidate.id !== originalTask.id).map((candidate) => candidate.id));
31363
+ for (const task of expandedTasks) {
31364
+ existingIds.add(task.id);
31365
+ }
31366
+ const terminalIds = terminalExpansionTaskIds(expandedTasks);
31367
+ const reviewerId = createUniqueTaskId2(`${originalTask.id}-review`, existingIds);
31368
+ const integratorId = createUniqueTaskId2(`${originalTask.id}-integrate`, existingIds);
31369
+ const reviewerTask = {
31370
+ ...originalTask,
31371
+ id: reviewerId,
31372
+ title: `Review ${originalTask.title.toLowerCase()}`,
31373
+ description: `Inspect delegated worker output from ${delegationLabel} before integration and call out correctness or verification risk.`,
31374
+ type: "verification",
31375
+ agentRole: "reviewer",
31376
+ dependsOn: terminalIds,
31377
+ acceptanceCriteria: [
31378
+ "Inspect the delegated changes for regressions or missing coverage.",
31379
+ "Confirm whether the worker output is ready for integration."
31380
+ ],
31381
+ outputs: [
31382
+ "Review summary"
31383
+ ],
31384
+ originTaskId: originalTask.id,
31385
+ swarmDepth: (originalTask.swarmDepth ?? 0) + 1,
31386
+ teamId: delegationLabel,
31387
+ teamMemberIds: [
31388
+ ...originalTask.teamMemberIds ?? [],
31389
+ "reviewer"
31390
+ ],
31391
+ subagentLabel: "reviewer",
31392
+ subagentInstructions: "Inspect the delegated changes, diff, and verification evidence. Surface concrete risks before integration."
31393
+ };
31394
+ const integratorTask = {
31395
+ ...originalTask,
31396
+ id: integratorId,
31397
+ title: `Integrate ${originalTask.title.toLowerCase()}`,
31398
+ description: `Finalize and integrate delegated worker output after reviewer sign-off for ${delegationLabel}.`,
31399
+ type: "integration",
31400
+ agentRole: "integrator",
31401
+ dependsOn: [
31402
+ reviewerId
31403
+ ],
31404
+ acceptanceCriteria: originalTask.acceptanceCriteria,
31405
+ outputs: mergeUnique([
31406
+ ...originalTask.outputs,
31407
+ "Integrated change set"
31408
+ ]),
31409
+ originTaskId: originalTask.id,
31410
+ swarmDepth: (originalTask.swarmDepth ?? 0) + 1,
31411
+ teamId: delegationLabel,
31412
+ teamMemberIds: [
31413
+ ...originalTask.teamMemberIds ?? [],
31414
+ "integrator"
31415
+ ],
31416
+ subagentLabel: "integrator",
31417
+ subagentInstructions: "Finalize the accepted worker output, preserve repository consistency, and leave the workspace ready for downstream verification."
31418
+ };
31419
+ return {
31420
+ tasks: [
31421
+ ...expandedTasks,
31422
+ reviewerTask,
31423
+ integratorTask
31424
+ ],
31425
+ noteSuffix: `review tail ${reviewerId} -> integration tail ${integratorId}`
31426
+ };
31427
+ }
30710
31428
  function renderToolResultSection(results) {
30711
31429
  return results.map((result) => {
30712
31430
  const lines = [
@@ -31764,20 +32482,49 @@ var ExecutionOrchestrator = class {
31764
32482
  }
31765
32483
  async maybeExpandReadyTaskGraph(sessionId, request, plan, events, emitProgress) {
31766
32484
  const envelope = this.buildEnvelope(request, plan, sessionId);
31767
- const candidate = envelope.readyTasks.find((task) => (task.swarmDepth ?? 0) === 0 && task.type === "implementation" && ![
31768
- "repo-analyst",
31769
- "planner",
31770
- "reviewer",
31771
- "integrator",
31772
- "test-debugger",
31773
- "session-orchestrator",
31774
- "execution-manager"
31775
- ].includes(task.agentRole));
32485
+ const candidate = envelope.readyTasks.find((task) => shouldDelegateTask(task));
31776
32486
  if (!candidate) {
31777
32487
  return {
31778
32488
  plan
31779
32489
  };
31780
32490
  }
32491
+ const matchedTeam = await selectAgentTeam(request.cwd, candidate, request);
32492
+ if (matchedTeam && matchedTeam.members.length > 1) {
32493
+ const teamTasks = materializeDelegatedTasks(plan, candidate, matchedTeam.team.id, matchedTeam.team.strategy, matchedTeam.members.map((member) => ({
32494
+ id: member.id,
32495
+ label: member.label,
32496
+ agentRole: member.baseRole,
32497
+ description: [
32498
+ `Assigned team member: ${member.label} (${member.id}).`,
32499
+ member.purpose ? `Purpose: ${member.purpose}` : "",
32500
+ matchedTeam.team.description ? `Team context: ${matchedTeam.team.description}` : ""
32501
+ ].filter((part) => part.length > 0).join("\n\n"),
32502
+ instructions: member.instructions
32503
+ }))).map((task) => ({
32504
+ ...task,
32505
+ customAgentId: task.customAgentId ?? task.subagentLabel ? matchedTeam.members.find((member) => member.label === task.subagentLabel)?.id : void 0,
32506
+ teamId: matchedTeam.team.id,
32507
+ teamMemberIds: matchedTeam.members.map((member) => member.id)
32508
+ }));
32509
+ const augmented = appendReviewAndIntegrationTasks(plan, candidate, teamTasks, matchedTeam.team.label);
32510
+ const note = `Agent team ${matchedTeam.team.label} expanded ${candidate.id} into ${teamTasks.length} worker subtasks with reviewer/integrator follow-through: ${augmented.tasks.map((task) => task.id).join(", ")}.`;
32511
+ return {
32512
+ plan: integratePlanTaskExpansion(plan, candidate.id, augmented.tasks, note),
32513
+ note,
32514
+ expandedTaskId: candidate.id
32515
+ };
32516
+ }
32517
+ const defaultDelegation = createDefaultDelegationPlan(candidate, request);
32518
+ if (defaultDelegation && defaultDelegation.members.length > 1) {
32519
+ const delegatedTasks = materializeDelegatedTasks(plan, candidate, defaultDelegation.label, defaultDelegation.strategy, defaultDelegation.members);
32520
+ const augmented = appendReviewAndIntegrationTasks(plan, candidate, delegatedTasks, defaultDelegation.label);
32521
+ const note = `Execution manager delegated ${candidate.id} into ${delegatedTasks.length} subagents plus reviewer/integrator: ${augmented.tasks.map((task) => task.id).join(", ")}.`;
32522
+ return {
32523
+ plan: integratePlanTaskExpansion(plan, candidate.id, augmented.tasks, note),
32524
+ note,
32525
+ expandedTaskId: candidate.id
32526
+ };
32527
+ }
31781
32528
  const repoContext = latestRepoAnalysisContext(events);
31782
32529
  const config2 = await loadConfig(request.cwd);
31783
32530
  if (!config2) {
@@ -31835,12 +32582,14 @@ var ExecutionOrchestrator = class {
31835
32582
  plan
31836
32583
  };
31837
32584
  }
32585
+ const expandedTasks = flattenPlanTasks(result.plan).filter((task) => result.createdTaskIds.includes(task.id));
32586
+ const augmented = appendReviewAndIntegrationTasks(plan, candidate, expandedTasks, "planner-swarm");
31838
32587
  const noteParts = [
31839
- result.note,
32588
+ result.note ? `${result.note} | Added reviewer/integrator pass: ${augmented.noteSuffix}.` : `Added reviewer/integrator pass: ${augmented.noteSuffix}.`,
31840
32589
  result.warning && result.source !== "model" ? `expander note: ${result.warning}` : ""
31841
32590
  ].filter(Boolean);
31842
32591
  return {
31843
- plan: result.plan,
32592
+ plan: integratePlanTaskExpansion(plan, candidate.id, augmented.tasks, noteParts.join(" | ")),
31844
32593
  ...noteParts.length > 0 ? {
31845
32594
  note: noteParts.join(" | ")
31846
32595
  } : {},
@@ -32395,7 +33144,15 @@ async function generateProjectInitFile(cwd, outputFileName = "kimbho_init.md") {
32395
33144
  return {
32396
33145
  outputPath,
32397
33146
  indexArtifactPath: artifactPath,
32398
- summary: `Generated ${outputFileName} with indexed project context for ${import_node_path15.default.basename(cwd)}.`
33147
+ summary: `Generated ${outputFileName} with indexed project context for ${import_node_path15.default.basename(cwd)}.`,
33148
+ purpose,
33149
+ frameworks,
33150
+ entrypoints,
33151
+ verificationCommands,
33152
+ indexedFiles: index.summary.totalFiles,
33153
+ sourceFiles: index.summary.sourceFiles,
33154
+ routes: index.summary.routes,
33155
+ schemas: index.summary.schemas
32399
33156
  };
32400
33157
  }
32401
33158
 
@@ -32452,7 +33209,15 @@ async function refreshMemoryFiles(cwd) {
32452
33209
  initFilePath: init.outputPath,
32453
33210
  projectMemoryPath,
32454
33211
  userMemoryPath,
32455
- summary: `Refreshed project bootstrap and memory files for ${import_node_path16.default.basename(cwd)}.`
33212
+ summary: `Refreshed project bootstrap and memory files for ${import_node_path16.default.basename(cwd)}.`,
33213
+ purpose: init.purpose,
33214
+ frameworks: init.frameworks,
33215
+ entrypoints: init.entrypoints,
33216
+ verificationCommands: init.verificationCommands,
33217
+ indexedFiles: init.indexedFiles,
33218
+ sourceFiles: init.sourceFiles,
33219
+ routes: init.routes,
33220
+ schemas: init.schemas
32456
33221
  };
32457
33222
  }
32458
33223
  async function appendMemoryNote(cwd, scope, text, agentId) {
@@ -32486,21 +33251,24 @@ async function listMemoryPaths(cwd) {
32486
33251
  function createConfigCommand() {
32487
33252
  return new Command("config").description("Show the current config, memory, and MCP file locations.").action(async () => {
32488
33253
  const cwd = import_node_process5.default.cwd();
32489
- const config2 = await loadConfig(cwd);
33254
+ const layers = await loadConfigLayers(cwd);
32490
33255
  const memory = await listMemoryPaths(cwd);
32491
- console.log(`config: ${resolveConfigPath(cwd)}`);
33256
+ console.log(`config.user: ${resolveGlobalConfigPath()}`);
33257
+ console.log(`config.project: ${resolveConfigPath(cwd)}`);
32492
33258
  console.log(`mcp: ${resolveMcpConfigPath(cwd)}`);
32493
33259
  console.log(`memory.project: ${memory.projectMemoryPath}`);
32494
33260
  console.log(`memory.user: ${memory.userMemoryPath}`);
32495
33261
  console.log(`memory.agents: ${memory.agentMemoryDir}`);
32496
- if (!config2) {
33262
+ if (!layers.resolved) {
32497
33263
  console.log("No config found.");
32498
33264
  return;
32499
33265
  }
32500
- console.log(`providers: ${config2.providers.length}`);
32501
- console.log(`approval: ${config2.approvalMode}`);
32502
- console.log(`sandbox: ${config2.sandboxMode}`);
32503
- console.log(`trusted directories: ${config2.trustedDirectories.length}`);
33266
+ console.log(`layers.user: ${layers.user ? "present" : "missing"}`);
33267
+ console.log(`layers.project: ${layers.project ? "present" : "missing"}`);
33268
+ console.log(`providers: ${layers.resolved.providers.length}`);
33269
+ console.log(`approval: ${layers.resolved.approvalMode}`);
33270
+ console.log(`sandbox: ${layers.resolved.sandboxMode}`);
33271
+ console.log(`trusted directories: ${layers.resolved.trustedDirectories.length}`);
32504
33272
  });
32505
33273
  }
32506
33274
 
@@ -32591,6 +33359,22 @@ function createDoctorCommand() {
32591
33359
 
32592
33360
  // src/commands/init.ts
32593
33361
  var import_node_process7 = __toESM(require("node:process"), 1);
33362
+ function printProjectUnderstanding(result) {
33363
+ console.log(result.summary);
33364
+ console.log("Now I have a durable understanding of the current project structure.");
33365
+ console.log(`Purpose: ${result.purpose}`);
33366
+ console.log(`Indexed: ${result.indexedFiles} files (${result.sourceFiles} source files), ${result.routes} routes, ${result.schemas} schemas`);
33367
+ console.log(`Frameworks: ${result.frameworks.length > 0 ? result.frameworks.join(", ") : "unknown"}`);
33368
+ if (result.entrypoints.length > 0) {
33369
+ console.log(`Entrypoints: ${result.entrypoints.slice(0, 5).join(", ")}`);
33370
+ }
33371
+ if (result.verificationCommands.length > 0) {
33372
+ console.log(`Verification: ${result.verificationCommands.join(" -> ")}`);
33373
+ }
33374
+ console.log(`Wrote ${result.initFilePath}`);
33375
+ console.log(`Wrote ${result.projectMemoryPath}`);
33376
+ console.log(`Wrote ${result.userMemoryPath}`);
33377
+ }
32594
33378
  function createInitCommand() {
32595
33379
  return new Command("init").description("Create or refresh local Kimbho config and project bootstrap memory.").option("--template <template>", "Provider template to use for the initial provider").option("--provider-id <id>", "Identifier for the initial provider").option("--driver <driver>", "Provider driver to use when no template is supplied").option("--label <label>", "Human-readable provider label").option("--model <model>", "Default model to use for planner/coder/reviewer brains").option("--fast-model <model>", "Model to use for the fast utility brain").option("--api-key-env <env>", "Environment variable that stores the provider API key").option("--base-url <url>", "Base URL or endpoint for the provider driver").option("--module-path <path>", "Module path for a custom provider driver").option("--config-only", "Only write .kimbho/config.json and skip project memory generation", false).option("--memory-only", "Only generate kimbho_init.md and keep the existing config untouched", false).option("--memory-file <file>", "Project memory file to write", "kimbho_init.md").option("--force", "Overwrite an existing config file", false).action(async (options) => {
32596
33380
  const existing = await loadConfig(import_node_process7.default.cwd());
@@ -32656,10 +33440,7 @@ function createInitCommand() {
32656
33440
  }
32657
33441
  if (!options.configOnly) {
32658
33442
  const result = await refreshMemoryFiles(import_node_process7.default.cwd());
32659
- console.log(result.summary);
32660
- console.log(`Wrote ${result.initFilePath}`);
32661
- console.log(`Wrote ${result.projectMemoryPath}`);
32662
- console.log(`Wrote ${result.userMemoryPath}`);
33443
+ printProjectUnderstanding(result);
32663
33444
  }
32664
33445
  });
32665
33446
  }
@@ -32734,14 +33515,90 @@ async function renderMcpInventory(cwd, name) {
32734
33515
  }
32735
33516
  return filtered.flatMap((server) => [
32736
33517
  `${server.name}`,
33518
+ ...server.warning ? [
33519
+ ` warning: ${server.warning}`
33520
+ ] : [],
32737
33521
  ` tools: ${server.tools.length}`,
32738
33522
  ...server.tools.slice(0, 20).map((tool) => ` - ${tool.toolName} (${tool.permission})`),
32739
33523
  ` prompts: ${server.prompts.length}`,
32740
- ...server.prompts.slice(0, 10).map((prompt) => ` - ${prompt}`),
33524
+ ...server.prompts.slice(0, 10).flatMap((prompt) => [
33525
+ ` - ${prompt.name}${prompt.arguments.length > 0 ? ` (${prompt.arguments.map((argument) => `${argument.name}${argument.required ? "*" : ""}`).join(", ")})` : ""}`,
33526
+ ` command: /mcp__${server.name}__${prompt.name}`
33527
+ ]),
32741
33528
  ` resources: ${server.resources.length}`,
32742
- ...server.resources.slice(0, 10).map((resource) => ` - ${resource}`)
33529
+ ...server.resources.slice(0, 10).flatMap((resource) => [
33530
+ ` - ${resource.name} -> ${resource.uri}`,
33531
+ ` attach: @${server.name}:${resource.uri}`
33532
+ ]),
33533
+ ` resource templates: ${server.resourceTemplates.length}`,
33534
+ ...server.resourceTemplates.slice(0, 10).map((resourceTemplate) => ` - ${resourceTemplate.name} -> ${resourceTemplate.uriTemplate}`)
33535
+ ]);
33536
+ }
33537
+ async function renderMcpPromptList(cwd, name) {
33538
+ const inventory = await loadMcpServerInventory(cwd);
33539
+ const filtered = name ? inventory.filter((server) => server.name === name) : inventory;
33540
+ if (filtered.length === 0) {
33541
+ return [
33542
+ name ? `No MCP prompt inventory found for ${name}.` : "No MCP prompt inventory available."
33543
+ ];
33544
+ }
33545
+ return filtered.flatMap((server) => [
33546
+ `${server.name}`,
33547
+ ...server.warning ? [
33548
+ ` warning: ${server.warning}`
33549
+ ] : [],
33550
+ ...server.prompts.length > 0 ? server.prompts.flatMap((prompt) => [
33551
+ ` - ${prompt.name}${prompt.arguments.length > 0 ? ` (${prompt.arguments.map((argument) => `${argument.name}${argument.required ? "*" : ""}`).join(", ")})` : ""}`,
33552
+ ` command: /mcp__${server.name}__${prompt.name}`
33553
+ ]) : [
33554
+ " - no prompts"
33555
+ ]
32743
33556
  ]);
32744
33557
  }
33558
+ async function renderMcpResourceList(cwd, name) {
33559
+ const inventory = await loadMcpServerInventory(cwd);
33560
+ const filtered = name ? inventory.filter((server) => server.name === name) : inventory;
33561
+ if (filtered.length === 0) {
33562
+ return [
33563
+ name ? `No MCP resource inventory found for ${name}.` : "No MCP resource inventory available."
33564
+ ];
33565
+ }
33566
+ return filtered.flatMap((server) => [
33567
+ `${server.name}`,
33568
+ ...server.warning ? [
33569
+ ` warning: ${server.warning}`
33570
+ ] : [],
33571
+ ...server.resources.length > 0 ? server.resources.flatMap((resource) => [
33572
+ ` - ${resource.name} -> ${resource.uri}`,
33573
+ ` attach: @${server.name}:${resource.uri}`
33574
+ ]) : [
33575
+ " - no resources"
33576
+ ],
33577
+ ...server.resourceTemplates.length > 0 ? [
33578
+ " templates:",
33579
+ ...server.resourceTemplates.map((resourceTemplate) => ` - ${resourceTemplate.name} -> ${resourceTemplate.uriTemplate}`)
33580
+ ] : []
33581
+ ]);
33582
+ }
33583
+ async function renderMcpPromptInvocation(cwd, serverName, promptName, args) {
33584
+ const result = await invokeMcpPrompt(cwd, serverName, promptName, args);
33585
+ return [
33586
+ `${result.serverName}/${result.promptName}`,
33587
+ ...result.description ? [
33588
+ `description: ${result.description}`
33589
+ ] : [],
33590
+ "",
33591
+ result.content
33592
+ ];
33593
+ }
33594
+ async function renderMcpResourceRead(cwd, serverName, uri) {
33595
+ const result = await readMcpResource(cwd, serverName, uri);
33596
+ return [
33597
+ `${result.serverName} ${result.uri}`,
33598
+ "",
33599
+ result.content
33600
+ ];
33601
+ }
32745
33602
 
32746
33603
  // src/commands/mcp.ts
32747
33604
  function collectValues(value, previous = []) {
@@ -32791,6 +33648,34 @@ function createMcpCommand() {
32791
33648
  console.log(line);
32792
33649
  }
32793
33650
  });
33651
+ command.command("discover").description("Show MCP tools, prompts-as-commands, and resource attachment syntax.").argument("[name]", "Optional server name").action(async (name) => {
33652
+ for (const line of await renderMcpInventory(import_node_process8.default.cwd(), name)) {
33653
+ console.log(line);
33654
+ }
33655
+ });
33656
+ command.command("prompts").description("List MCP prompts.").argument("[name]", "Optional server name").action(async (name) => {
33657
+ for (const line of await renderMcpPromptList(import_node_process8.default.cwd(), name)) {
33658
+ console.log(line);
33659
+ }
33660
+ });
33661
+ command.command("resources").description("List MCP resources and resource templates.").argument("[name]", "Optional server name").action(async (name) => {
33662
+ for (const line of await renderMcpResourceList(import_node_process8.default.cwd(), name)) {
33663
+ console.log(line);
33664
+ }
33665
+ });
33666
+ command.command("prompt").description("Invoke an MCP prompt and print the rendered content.").argument("<server>", "Server name").argument("<prompt>", "Prompt name").argument("[args...]", "Prompt args in key=value form").action(async (server, prompt, rawArgs = []) => {
33667
+ const args = Object.fromEntries(
33668
+ rawArgs.map((entry) => entry.split("=", 2)).filter((parts) => parts.length === 2 && parts[0].length > 0)
33669
+ );
33670
+ for (const line of await renderMcpPromptInvocation(import_node_process8.default.cwd(), server, prompt, args)) {
33671
+ console.log(line);
33672
+ }
33673
+ });
33674
+ command.command("read").description("Read one MCP resource by URI.").argument("<server>", "Server name").argument("<uri>", "Resource URI").action(async (server, uri) => {
33675
+ for (const line of await renderMcpResourceRead(import_node_process8.default.cwd(), server, uri)) {
33676
+ console.log(line);
33677
+ }
33678
+ });
32794
33679
  return command;
32795
33680
  }
32796
33681
 
@@ -33252,7 +34137,7 @@ function createModelsCommand() {
33252
34137
  const exists = models.some((item) => item.id === model) || provider.models.includes(model);
33253
34138
  if (!exists) {
33254
34139
  throw new Error(
33255
- `Model "${model}" was not found for provider "${provider.id}". Run \`kimbho /models ${provider.id} --search ${model}\` or re-run with --force.`
34140
+ `Model "${model}" was not found for provider "${provider.id}". Run \`kimbho model find ${model}\` or re-run with --force.`
33256
34141
  );
33257
34142
  }
33258
34143
  } catch (error2) {
@@ -33595,27 +34480,49 @@ function createPlanCommand() {
33595
34480
 
33596
34481
  // src/commands/permissions.ts
33597
34482
  var import_node_process13 = __toESM(require("node:process"), 1);
34483
+ var import_node_path19 = __toESM(require("node:path"), 1);
34484
+ function getScope(options) {
34485
+ return options.scope === "user" ? "user" : "project";
34486
+ }
34487
+ async function loadScopedConfig(scope, cwd) {
34488
+ const direct = scope === "user" ? await loadUserConfig() : await loadProjectConfig(cwd);
34489
+ const resolved = await loadConfig(cwd);
34490
+ return {
34491
+ direct,
34492
+ resolved
34493
+ };
34494
+ }
34495
+ async function saveScopedConfig(scope, cwd, config2) {
34496
+ return scope === "user" ? saveUserConfig(config2) : saveConfig(config2, cwd);
34497
+ }
33598
34498
  function createPermissionsCommand() {
33599
34499
  const command = new Command("permissions").description("Manage approval mode, sandbox mode, and trusted directories.");
33600
- command.command("status").description("Show permission settings.").action(async () => {
33601
- const config2 = await loadConfig(import_node_process13.default.cwd());
33602
- if (!config2) {
34500
+ command.command("status").description("Show permission settings.").option("--scope <scope>", "project or user", "project").action(async (options) => {
34501
+ const scope = getScope(options);
34502
+ const { direct, resolved } = await loadScopedConfig(scope, import_node_process13.default.cwd());
34503
+ console.log(`scope: ${scope}`);
34504
+ console.log(`direct config: ${direct ? "present" : "missing"}`);
34505
+ if (!resolved) {
33603
34506
  throw new Error("No config found. Run `kimbho init` first.");
33604
34507
  }
33605
- console.log(`approval: ${config2.approvalMode}`);
33606
- console.log(`sandbox: ${config2.sandboxMode}`);
33607
- console.log(`trusted directories: ${config2.trustedDirectories.length}`);
33608
- for (const directory of config2.trustedDirectories) {
34508
+ const display = direct ?? resolved;
34509
+ console.log(`approval: ${display.approvalMode}`);
34510
+ console.log(`sandbox: ${display.sandboxMode}`);
34511
+ console.log(`trusted directories: ${display.trustedDirectories.length}`);
34512
+ for (const directory of display.trustedDirectories) {
33609
34513
  console.log(` - ${directory}`);
33610
34514
  }
33611
34515
  });
33612
- command.command("set").description("Set approval mode or sandbox mode.").option("--approval <mode>", "manual or auto").option("--sandbox <mode>", "read-only, workspace-write, or full").action(async (options) => {
33613
- const config2 = await loadConfig(import_node_process13.default.cwd());
33614
- if (!config2) {
34516
+ command.command("set").description("Set approval mode or sandbox mode.").option("--scope <scope>", "project or user", "project").option("--approval <mode>", "manual or auto").option("--sandbox <mode>", "read-only, workspace-write, or full").action(async (options) => {
34517
+ const scope = getScope(options);
34518
+ const cwd = import_node_process13.default.cwd();
34519
+ const { direct, resolved } = await loadScopedConfig(scope, cwd);
34520
+ if (!resolved && !direct) {
33615
34521
  throw new Error("No config found. Run `kimbho init` first.");
33616
34522
  }
34523
+ const base = direct ?? resolved ?? createDefaultConfig();
33617
34524
  const next = {
33618
- ...config2,
34525
+ ...base,
33619
34526
  ...options.approval ? {
33620
34527
  approvalMode: options.approval
33621
34528
  } : {},
@@ -33623,32 +34530,40 @@ function createPermissionsCommand() {
33623
34530
  sandboxMode: options.sandbox
33624
34531
  } : {}
33625
34532
  };
33626
- const outputPath = await saveConfig(next, import_node_process13.default.cwd());
34533
+ const outputPath = await saveScopedConfig(scope, cwd, next);
33627
34534
  console.log(`Updated ${outputPath}`);
33628
34535
  });
33629
- command.command("trust").description("Add a trusted directory.").argument("<path>", "Directory to trust").action(async (trustedPath) => {
33630
- const config2 = await loadConfig(import_node_process13.default.cwd());
33631
- if (!config2) {
34536
+ command.command("trust").description("Add a trusted directory.").argument("<path>", "Directory to trust").option("--scope <scope>", "project or user", "project").action(async (trustedPath, options) => {
34537
+ const scope = getScope(options);
34538
+ const cwd = import_node_process13.default.cwd();
34539
+ const { direct, resolved } = await loadScopedConfig(scope, cwd);
34540
+ if (!resolved && !direct) {
33632
34541
  throw new Error("No config found. Run `kimbho init` first.");
33633
34542
  }
33634
- const outputPath = await saveConfig({
33635
- ...config2,
34543
+ const base = direct ?? resolved ?? createDefaultConfig();
34544
+ const normalizedPath = import_node_path19.default.resolve(cwd, trustedPath);
34545
+ const outputPath = await saveScopedConfig(scope, cwd, {
34546
+ ...base,
33636
34547
  trustedDirectories: Array.from(/* @__PURE__ */ new Set([
33637
- ...config2.trustedDirectories,
33638
- trustedPath
34548
+ ...base.trustedDirectories,
34549
+ normalizedPath
33639
34550
  ]))
33640
- }, import_node_process13.default.cwd());
34551
+ });
33641
34552
  console.log(`Updated ${outputPath}`);
33642
34553
  });
33643
- command.command("untrust").description("Remove a trusted directory.").argument("<path>", "Directory to remove").action(async (trustedPath) => {
33644
- const config2 = await loadConfig(import_node_process13.default.cwd());
33645
- if (!config2) {
34554
+ command.command("untrust").description("Remove a trusted directory.").argument("<path>", "Directory to remove").option("--scope <scope>", "project or user", "project").action(async (trustedPath, options) => {
34555
+ const scope = getScope(options);
34556
+ const cwd = import_node_process13.default.cwd();
34557
+ const { direct, resolved } = await loadScopedConfig(scope, cwd);
34558
+ if (!resolved && !direct) {
33646
34559
  throw new Error("No config found. Run `kimbho init` first.");
33647
34560
  }
33648
- const outputPath = await saveConfig({
33649
- ...config2,
33650
- trustedDirectories: config2.trustedDirectories.filter((directory) => directory !== trustedPath)
33651
- }, import_node_process13.default.cwd());
34561
+ const base = direct ?? resolved ?? createDefaultConfig();
34562
+ const normalizedPath = import_node_path19.default.resolve(cwd, trustedPath);
34563
+ const outputPath = await saveScopedConfig(scope, cwd, {
34564
+ ...base,
34565
+ trustedDirectories: base.trustedDirectories.filter((directory) => directory !== trustedPath && directory !== normalizedPath)
34566
+ });
33652
34567
  console.log(`Updated ${outputPath}`);
33653
34568
  });
33654
34569
  return command;
@@ -33762,7 +34677,7 @@ function createProvidersCommand() {
33762
34677
 
33763
34678
  // src/commands/review.ts
33764
34679
  var import_promises17 = require("node:fs/promises");
33765
- var import_node_path19 = __toESM(require("node:path"), 1);
34680
+ var import_node_path20 = __toESM(require("node:path"), 1);
33766
34681
  var import_node_process15 = __toESM(require("node:process"), 1);
33767
34682
  function summarizeChangedFiles2(diff) {
33768
34683
  const files = /* @__PURE__ */ new Set();
@@ -33846,7 +34761,7 @@ ${diff.stdout}`
33846
34761
  }
33847
34762
  }
33848
34763
  await ensureKimbhoDir(cwd);
33849
- const artifactPath = import_node_path19.default.join(resolveKimbhoDir(cwd), "logs", `review-${Date.now()}.md`);
34764
+ const artifactPath = import_node_path20.default.join(resolveKimbhoDir(cwd), "logs", `review-${Date.now()}.md`);
33850
34765
  await (0, import_promises17.writeFile)(artifactPath, [
33851
34766
  `# Review`,
33852
34767
  ``,
@@ -34080,6 +34995,12 @@ function normalizeCliTokens(tokens) {
34080
34995
  }
34081
34996
  function createProgram(onOpenShell) {
34082
34997
  const program2 = new Command();
34998
+ const legacyProviders = createProvidersCommand();
34999
+ const legacyModels = createModelsCommand();
35000
+ const legacyBrains = createBrainsCommand();
35001
+ legacyProviders._hidden = true;
35002
+ legacyModels._hidden = true;
35003
+ legacyBrains._hidden = true;
34083
35004
  program2.name("kimbho").description(KIMBHO_DESCRIPTION).version(KIMBHO_VERSION);
34084
35005
  program2.addCommand(createInitCommand());
34085
35006
  program2.addCommand(createPlanCommand());
@@ -34090,9 +35011,9 @@ function createProgram(onOpenShell) {
34090
35011
  program2.addCommand(createMcpCommand());
34091
35012
  program2.addCommand(createModelCommand());
34092
35013
  program2.addCommand(createAgentsCommand());
34093
- program2.addCommand(createProvidersCommand());
34094
- program2.addCommand(createModelsCommand());
34095
- program2.addCommand(createBrainsCommand());
35014
+ program2.addCommand(legacyProviders);
35015
+ program2.addCommand(legacyModels);
35016
+ program2.addCommand(legacyBrains);
34096
35017
  program2.addCommand(createRunCommand());
34097
35018
  program2.addCommand(createResumeCommand());
34098
35019
  program2.addCommand(createFixCommand());
@@ -34108,7 +35029,7 @@ function createProgram(onOpenShell) {
34108
35029
  // src/shell.ts
34109
35030
  var import_node_readline = require("node:readline");
34110
35031
  var import_promises18 = require("node:readline/promises");
34111
- var import_node_path20 = __toESM(require("node:path"), 1);
35032
+ var import_node_path21 = __toESM(require("node:path"), 1);
34112
35033
  var import_node_process18 = __toESM(require("node:process"), 1);
34113
35034
 
34114
35035
  // src/agent-management.ts
@@ -34162,6 +35083,7 @@ var TOP_LEVEL_COMMANDS = /* @__PURE__ */ new Set([
34162
35083
  "help",
34163
35084
  "init",
34164
35085
  "mcp",
35086
+ "mcp__",
34165
35087
  "memory",
34166
35088
  "model",
34167
35089
  "models",
@@ -34263,9 +35185,67 @@ var PLAN_PREFIXES = [
34263
35185
  "outline ",
34264
35186
  "propose "
34265
35187
  ];
35188
+ function looksLikeExecutionRequest(input) {
35189
+ return /\b(build|create|make|scaffold|implement|fix|refactor|setup|set up|generate|add|change|update|edit|rewrite|restyle|redesign|improve|enhance)\b/.test(input);
35190
+ }
34266
35191
  function color(code, value) {
34267
35192
  return `${code}${value}${RESET}`;
34268
35193
  }
35194
+ function extractMcpResourceReferences(prompt) {
35195
+ const matches = prompt.match(/@[A-Za-z0-9_-]+:[^\s]+/g) ?? [];
35196
+ return matches.flatMap((token) => {
35197
+ const withoutAt = token.slice(1);
35198
+ const separatorIndex = withoutAt.indexOf(":");
35199
+ if (separatorIndex === -1) {
35200
+ return [];
35201
+ }
35202
+ return [
35203
+ {
35204
+ token,
35205
+ serverName: withoutAt.slice(0, separatorIndex),
35206
+ uri: withoutAt.slice(separatorIndex + 1)
35207
+ }
35208
+ ];
35209
+ });
35210
+ }
35211
+ async function hydratePromptWithMcpResources(cwd, prompt) {
35212
+ const references = extractMcpResourceReferences(prompt);
35213
+ if (references.length === 0) {
35214
+ return {
35215
+ prompt,
35216
+ notes: []
35217
+ };
35218
+ }
35219
+ const blocks = [];
35220
+ const notes = [];
35221
+ for (const reference of references) {
35222
+ try {
35223
+ const rendered = await renderMcpResourceRead(cwd, reference.serverName, reference.uri);
35224
+ notes.push(`attached MCP resource ${reference.token}`);
35225
+ blocks.push([
35226
+ `Attached MCP resource ${reference.token}:`,
35227
+ ...rendered
35228
+ ].join("\n"));
35229
+ } catch (error2) {
35230
+ const message = error2 instanceof Error ? error2.message : String(error2);
35231
+ notes.push(`failed to attach ${reference.token}: ${message}`);
35232
+ }
35233
+ }
35234
+ if (blocks.length === 0) {
35235
+ return {
35236
+ prompt,
35237
+ notes
35238
+ };
35239
+ }
35240
+ return {
35241
+ prompt: [
35242
+ prompt,
35243
+ "",
35244
+ ...blocks
35245
+ ].join("\n"),
35246
+ notes
35247
+ };
35248
+ }
34269
35249
  function createExecutionTelemetry() {
34270
35250
  return {
34271
35251
  toolCalls: 0,
@@ -34535,6 +35515,11 @@ function renderHelp() {
34535
35515
  "/mcp add <name> --command <cmd> [--arg <value>] [--env KEY=VALUE]",
34536
35516
  " Add an MCP stdio server to .mcp.json.",
34537
35517
  "/mcp tools [server] Discover tools/prompts/resources from MCP servers.",
35518
+ "/mcp prompts [server] List MCP prompts.",
35519
+ "/mcp resources [server] List MCP resources and templates.",
35520
+ "/mcp prompt <server> <prompt> [key=value ...]",
35521
+ " Render an MCP prompt.",
35522
+ "/mcp read <server> <uri> Read one MCP resource.",
34538
35523
  "/agents List custom agents and teams.",
34539
35524
  "/agents create <id> --base <role> Create a markdown custom agent.",
34540
35525
  "/agents team create <id> <agent...> Create a markdown team file.",
@@ -34571,7 +35556,7 @@ function renderStartupCard(cwd, state) {
34571
35556
  renderCardLine("shortcuts", "/ask /run /model /permissions /memory /mcp /quit")
34572
35557
  ];
34573
35558
  if (!state.configured) {
34574
- cardLines.push("setup: run /init or /providers add <template> to create .kimbho/config.json");
35559
+ cardLines.push("setup: run /init or /model add <template> to create .kimbho/config.json");
34575
35560
  }
34576
35561
  return renderBox(cardLines);
34577
35562
  }
@@ -34596,20 +35581,20 @@ function inferPromptIntent(input) {
34596
35581
  if (/^(hi|hello|hey|yo|sup)\b/.test(normalized)) {
34597
35582
  return "chat";
34598
35583
  }
34599
- if (normalized.endsWith("?")) {
34600
- return "chat";
34601
- }
34602
35584
  if (PLAN_PREFIXES.some((prefix) => normalized.startsWith(prefix))) {
34603
35585
  return "plan";
34604
35586
  }
34605
35587
  if (EXECUTION_PREFIXES.some((prefix) => normalized.startsWith(prefix))) {
34606
35588
  return "run";
34607
35589
  }
34608
- if (CHAT_PREFIXES.some((prefix) => normalized.startsWith(prefix))) {
35590
+ if (looksLikeExecutionRequest(normalized)) {
35591
+ return "run";
35592
+ }
35593
+ if (normalized.endsWith("?")) {
34609
35594
  return "chat";
34610
35595
  }
34611
- if (/\b(build|create|make|scaffold|implement|fix|refactor|setup|set up|generate)\b/.test(normalized)) {
34612
- return "run";
35596
+ if (CHAT_PREFIXES.some((prefix) => normalized.startsWith(prefix))) {
35597
+ return "chat";
34613
35598
  }
34614
35599
  return normalized.split(/\s+/).length >= 4 ? "plan" : "chat";
34615
35600
  }
@@ -34863,6 +35848,12 @@ function hasRenderableDiff(toolId, output) {
34863
35848
  }
34864
35849
  return false;
34865
35850
  }
35851
+ function isMutationTool(toolId) {
35852
+ return toolId === "file.write" || toolId === "file.patch" || toolId === "scaffold.generate";
35853
+ }
35854
+ function renderMutationArtifacts(artifacts, label = "changed", maxItems = 4) {
35855
+ return artifacts.slice(0, maxItems).map((artifact) => ` ${color(DIM, `${label}: ${shortenMiddle(artifact, 120)}`)}`);
35856
+ }
34866
35857
  function colorDiffLine(line) {
34867
35858
  if (line.startsWith("diff --git ")) {
34868
35859
  const match = line.match(/^diff --git a\/(.+?) b\/(.+)$/);
@@ -34921,14 +35912,26 @@ function renderShellSessionSummary(snapshot, planPath, sessionPath) {
34921
35912
  lines.push(color(BOLD, "Activity"));
34922
35913
  for (const event of snapshot.events.slice(-8)) {
34923
35914
  lines.push(`- ${renderEventType(event.type)} | ${event.agentRole ?? "session"} | ${event.message}`);
35915
+ let landedMutation = false;
34924
35916
  for (const toolResult of event.toolResults.slice(-4)) {
34925
35917
  lines.push(` ${toolResult.success ? "ok" : "fail"} ${toolResult.toolId}: ${toolResult.summary}`);
34926
35918
  if (toolResult.stdout && hasRenderableDiff(toolResult.toolId, toolResult.stdout)) {
35919
+ landedMutation = landedMutation || isMutationTool(toolResult.toolId);
34927
35920
  lines.push(...renderDiffPreview(toolResult.stdout, 6));
34928
35921
  }
34929
35922
  if ((toolResult.toolId.startsWith("process.") || toolResult.toolId === "http.fetch" || toolResult.toolId.startsWith("browser.") || toolResult.toolId.startsWith("repo.")) && toolResult.stdout) {
34930
35923
  lines.push(...renderTextPreview(toolResult.stdout, 6));
34931
35924
  }
35925
+ if (toolResult.success && isMutationTool(toolResult.toolId) && toolResult.artifacts.length > 0) {
35926
+ landedMutation = true;
35927
+ lines.push(...renderMutationArtifacts(
35928
+ toolResult.artifacts,
35929
+ toolResult.toolId === "scaffold.generate" ? "created" : "changed"
35930
+ ));
35931
+ }
35932
+ }
35933
+ if (event.type === "task-blocked" && !landedMutation) {
35934
+ lines.push(` ${color(DIM, "no safe edit landed before this task blocked.")}`);
34932
35935
  }
34933
35936
  for (const artifact of event.artifacts.slice(-3)) {
34934
35937
  lines.push(` artifact: ${artifact}`);
@@ -35041,7 +36044,11 @@ function renderLiveExecutionEvent(event) {
35041
36044
  case "tool-finished":
35042
36045
  return [
35043
36046
  ` ${event.toolResult.success ? color(CYAN, "ok") : color(AMBER, "fail")} ${event.toolResult.toolId} ${event.toolResult.summary}`,
35044
- ...event.toolResult.stdout && hasRenderableDiff(event.toolResult.toolId, event.toolResult.stdout) ? renderDiffPreview(event.toolResult.stdout, 8) : (event.toolResult.toolId.startsWith("process.") || event.toolResult.toolId === "http.fetch" || event.toolResult.toolId.startsWith("browser.") || event.toolResult.toolId.startsWith("repo.")) && event.toolResult.stdout ? renderTextPreview(event.toolResult.stdout, 8) : []
36047
+ ...event.toolResult.stdout && hasRenderableDiff(event.toolResult.toolId, event.toolResult.stdout) ? renderDiffPreview(event.toolResult.stdout, 8) : (event.toolResult.toolId.startsWith("process.") || event.toolResult.toolId === "http.fetch" || event.toolResult.toolId.startsWith("browser.") || event.toolResult.toolId.startsWith("repo.")) && event.toolResult.stdout ? renderTextPreview(event.toolResult.stdout, 8) : [],
36048
+ ...event.toolResult.success && isMutationTool(event.toolResult.toolId) && event.toolResult.artifacts.length > 0 ? renderMutationArtifacts(
36049
+ event.toolResult.artifacts,
36050
+ event.toolResult.toolId === "scaffold.generate" ? "created" : "changed"
36051
+ ) : []
35045
36052
  ];
35046
36053
  default:
35047
36054
  return [];
@@ -35190,13 +36197,18 @@ function clearAllConversations(runtime) {
35190
36197
  }
35191
36198
  }
35192
36199
  async function handleChatPrompt(cwd, prompt, runtime) {
36200
+ const hydrated = await hydratePromptWithMcpResources(cwd, prompt);
35193
36201
  const config2 = await loadConfig(cwd);
35194
36202
  if (!config2) {
35195
- throw new Error("No config found. Run /init or /providers add <template> first.");
36203
+ throw new Error("No config found. Run /init or /model add <template> first.");
35196
36204
  }
35197
36205
  const registry2 = createDefaultBrainProviderRegistry(cwd);
35198
36206
  const resolver = new BrainResolver(config2, registry2);
35199
36207
  const brain = await resolver.resolve(runtime.focusRole);
36208
+ const memoryContext = await loadMarkdownMemoryContext(cwd, {
36209
+ maxFiles: 8,
36210
+ maxCharsPerFile: 3e3
36211
+ });
35200
36212
  const compactionPath = await compactConversationIfNeeded(cwd, runtime, runtime.focusRole);
35201
36213
  if (compactionPath) {
35202
36214
  console.log(color(DIM, `[compacting] condensed ${runtime.focusRole} chat context -> ${compactionPath}`));
@@ -35206,7 +36218,7 @@ async function handleChatPrompt(cwd, prompt, runtime) {
35206
36218
  ...history,
35207
36219
  {
35208
36220
  role: "user",
35209
- content: prompt
36221
+ content: hydrated.prompt
35210
36222
  }
35211
36223
  ]);
35212
36224
  let result;
@@ -35219,6 +36231,11 @@ async function handleChatPrompt(cwd, prompt, runtime) {
35219
36231
  ...brain.settings.promptPreamble || runtime.conversationSummaries[runtime.focusRole] ? {
35220
36232
  systemPrompt: [
35221
36233
  brain.settings.promptPreamble,
36234
+ memoryContext.length > 0 ? [
36235
+ "Workspace memory:",
36236
+ ...memoryContext.map((record2) => `File: ${record2.filePath}
36237
+ ${record2.content}`)
36238
+ ].join("\n\n") : null,
35222
36239
  runtime.conversationSummaries[runtime.focusRole] ? `Compacted conversation summary:
35223
36240
  ${runtime.conversationSummaries[runtime.focusRole]}` : null
35224
36241
  ].filter((value) => Boolean(value)).join("\n\n")
@@ -35245,16 +36262,20 @@ ${runtime.conversationSummaries[runtime.focusRole]}` : null
35245
36262
  ]);
35246
36263
  runtime.conversations[runtime.focusRole] = nextConversation;
35247
36264
  console.log(color(DIM, `[${brain.role}] ${brain.provider.id}/${brain.model}`));
36265
+ for (const note of hydrated.notes) {
36266
+ console.log(color(DIM, note));
36267
+ }
35248
36268
  if (result.usage) {
35249
36269
  console.log(color(DIM, `tokens: ${result.usage.inputTokens} in / ${result.usage.outputTokens} out`));
35250
36270
  }
35251
36271
  console.log(renderTerminalMarkdown(result.text));
35252
36272
  }
35253
36273
  async function runGoalExecution(cwd, goal, runtime) {
36274
+ const hydrated = await hydratePromptWithMcpResources(cwd, goal);
35254
36275
  const orchestrator = new ExecutionOrchestrator();
35255
- const workspace = await resolveExecutionWorkspace(cwd, goal);
36276
+ const workspace = await resolveExecutionWorkspace(cwd, hydrated.prompt);
35256
36277
  const request = {
35257
- goal,
36278
+ goal: hydrated.prompt,
35258
36279
  mode: "run",
35259
36280
  cwd: workspace.cwd,
35260
36281
  workspaceState: workspace.workspaceState,
@@ -35269,6 +36290,9 @@ async function runGoalExecution(cwd, goal, runtime) {
35269
36290
  };
35270
36291
  const planningSpinner = new ShellActivityIndicator("planning");
35271
36292
  console.log(color(DIM, `Working on: ${goal}`));
36293
+ for (const note of hydrated.notes) {
36294
+ console.log(color(DIM, note));
36295
+ }
35272
36296
  for (const note of workspace.notes) {
35273
36297
  console.log(color(DIM, note));
35274
36298
  }
@@ -35529,7 +36553,7 @@ async function printLatestPlanSummary(cwd) {
35529
36553
  async function handleApprovalModeCommand(cwd, tokens) {
35530
36554
  const config2 = await loadConfig(cwd);
35531
36555
  if (!config2) {
35532
- throw new Error("No config found. Run /init or /providers add <template> first.");
36556
+ throw new Error("No config found. Run /init or /model add <template> first.");
35533
36557
  }
35534
36558
  const subcommand = tokens[1]?.trim().toLowerCase();
35535
36559
  if (!subcommand || subcommand === "status") {
@@ -35549,44 +36573,55 @@ async function handleApprovalModeCommand(cwd, tokens) {
35549
36573
  console.log(`approval mode: ${subcommand}`);
35550
36574
  }
35551
36575
  async function printConfigSummary(cwd) {
35552
- const config2 = await loadConfig(cwd);
36576
+ const layers = await loadConfigLayers(cwd);
35553
36577
  const paths = await listMemoryPaths(cwd);
35554
- console.log(`config: ${resolveConfigPath(cwd)}`);
36578
+ console.log(`config.project: ${resolveConfigPath(cwd)}`);
36579
+ console.log(`config.user: ${resolveGlobalConfigPath()}`);
35555
36580
  console.log(`mcp: ${resolveMcpConfigPath(cwd)}`);
35556
36581
  console.log(`memory.project: ${paths.projectMemoryPath}`);
35557
36582
  console.log(`memory.user: ${paths.userMemoryPath}`);
35558
36583
  console.log(`memory.agents: ${paths.agentMemoryDir}`);
35559
- if (!config2) {
36584
+ if (!layers.resolved) {
35560
36585
  console.log("No config found. Run /init first.");
35561
36586
  return;
35562
36587
  }
36588
+ console.log(`layers: user=${layers.user ? "yes" : "no"} project=${layers.project ? "yes" : "no"}`);
36589
+ const config2 = layers.resolved;
35563
36590
  console.log(`providers: ${config2.providers.length}`);
35564
36591
  console.log(`approval: ${config2.approvalMode}`);
35565
36592
  console.log(`sandbox: ${config2.sandboxMode}`);
35566
36593
  console.log(`trusted directories: ${config2.trustedDirectories.length}`);
35567
36594
  }
35568
36595
  async function handlePermissionsCommand(cwd, tokens) {
35569
- const config2 = await loadConfig(cwd);
35570
- if (!config2) {
36596
+ const subcommand = tokens[1]?.trim().toLowerCase();
36597
+ const scopeToken = tokens.find((token, index) => index > 0 && (token === "user" || token === "project"));
36598
+ const scope = scopeToken === "user" ? "user" : "project";
36599
+ const direct = scope === "user" ? await loadUserConfig() : await loadProjectConfig(cwd);
36600
+ const resolved = await loadConfig(cwd);
36601
+ if (!resolved && !direct) {
35571
36602
  throw new Error("No config found. Run /init or /model add <template> first.");
35572
36603
  }
35573
- const subcommand = tokens[1]?.trim().toLowerCase();
36604
+ const effective = direct ?? resolved ?? createDefaultConfig();
36605
+ const saveScoped = async (next) => scope === "user" ? saveUserConfig(next) : saveConfig(next, cwd);
35574
36606
  if (!subcommand || subcommand === "show" || subcommand === "status") {
35575
- console.log(`approval: ${config2.approvalMode}`);
35576
- console.log(`sandbox: ${config2.sandboxMode}`);
35577
- console.log(`trusted directories: ${config2.trustedDirectories.length}`);
35578
- for (const directory of config2.trustedDirectories) {
36607
+ console.log(`scope: ${scope}`);
36608
+ console.log(`direct config: ${direct ? "present" : "missing"}`);
36609
+ console.log(`approval: ${effective.approvalMode}`);
36610
+ console.log(`sandbox: ${effective.sandboxMode}`);
36611
+ console.log(`trusted directories: ${effective.trustedDirectories.length}`);
36612
+ for (const directory of effective.trustedDirectories) {
35579
36613
  console.log(` - ${directory}`);
35580
36614
  }
35581
36615
  return;
35582
36616
  }
35583
36617
  if (subcommand === "auto" || subcommand === "manual") {
35584
- const outputPath = await saveConfig({
35585
- ...config2,
36618
+ const outputPath = await saveScoped({
36619
+ ...effective,
35586
36620
  approvalMode: subcommand
35587
- }, cwd);
36621
+ });
35588
36622
  console.log(`Updated ${outputPath}`);
35589
36623
  console.log(`approval mode: ${subcommand}`);
36624
+ console.log(`scope: ${scope}`);
35590
36625
  return;
35591
36626
  }
35592
36627
  if (subcommand === "sandbox") {
@@ -35598,12 +36633,13 @@ async function handlePermissionsCommand(cwd, tokens) {
35598
36633
  ].includes(mode)) {
35599
36634
  throw new Error("Usage: /permissions sandbox <read-only|workspace-write|full>");
35600
36635
  }
35601
- const outputPath = await saveConfig({
35602
- ...config2,
36636
+ const outputPath = await saveScoped({
36637
+ ...effective,
35603
36638
  sandboxMode: mode
35604
- }, cwd);
36639
+ });
35605
36640
  console.log(`Updated ${outputPath}`);
35606
36641
  console.log(`sandbox: ${mode}`);
36642
+ console.log(`scope: ${scope}`);
35607
36643
  return;
35608
36644
  }
35609
36645
  if (subcommand === "trust" || subcommand === "untrust") {
@@ -35611,19 +36647,21 @@ async function handlePermissionsCommand(cwd, tokens) {
35611
36647
  if (!target) {
35612
36648
  throw new Error(`Usage: /permissions ${subcommand} <path>`);
35613
36649
  }
36650
+ const normalizedTarget = import_node_path21.default.resolve(cwd, target);
35614
36651
  const trustedDirectories = subcommand === "trust" ? Array.from(/* @__PURE__ */ new Set([
35615
- ...config2.trustedDirectories,
35616
- target
35617
- ])) : config2.trustedDirectories.filter((directory) => directory !== target);
35618
- const outputPath = await saveConfig({
35619
- ...config2,
36652
+ ...effective.trustedDirectories,
36653
+ normalizedTarget
36654
+ ])) : effective.trustedDirectories.filter((directory) => directory !== target && directory !== normalizedTarget);
36655
+ const outputPath = await saveScoped({
36656
+ ...effective,
35620
36657
  trustedDirectories
35621
- }, cwd);
36658
+ });
35622
36659
  console.log(`Updated ${outputPath}`);
35623
- console.log(`${subcommand === "trust" ? "Trusted" : "Removed"} ${target}`);
36660
+ console.log(`${subcommand === "trust" ? "Trusted" : "Removed"} ${normalizedTarget}`);
36661
+ console.log(`scope: ${scope}`);
35624
36662
  return;
35625
36663
  }
35626
- throw new Error("Usage: /permissions [show|auto|manual|sandbox <mode>|trust <path>|untrust <path>]");
36664
+ throw new Error("Usage: /permissions [show|auto|manual|sandbox <mode>|trust <path>|untrust <path>] [project|user]");
35627
36665
  }
35628
36666
  async function handleMemoryCommand(cwd, tokens) {
35629
36667
  const subcommand = tokens[1]?.trim().toLowerCase();
@@ -35638,6 +36676,16 @@ async function handleMemoryCommand(cwd, tokens) {
35638
36676
  if (subcommand === "refresh") {
35639
36677
  const result = await refreshMemoryFiles(cwd);
35640
36678
  console.log(result.summary);
36679
+ console.log("Now I have a durable understanding of the current project structure.");
36680
+ console.log(`Purpose: ${result.purpose}`);
36681
+ console.log(`Indexed: ${result.indexedFiles} files (${result.sourceFiles} source files), ${result.routes} routes, ${result.schemas} schemas`);
36682
+ console.log(`Frameworks: ${result.frameworks.length > 0 ? result.frameworks.join(", ") : "unknown"}`);
36683
+ if (result.entrypoints.length > 0) {
36684
+ console.log(`Entrypoints: ${result.entrypoints.slice(0, 5).join(", ")}`);
36685
+ }
36686
+ if (result.verificationCommands.length > 0) {
36687
+ console.log(`Verification: ${result.verificationCommands.join(" -> ")}`);
36688
+ }
35641
36689
  console.log(`Wrote ${result.initFilePath}`);
35642
36690
  console.log(`Wrote ${result.projectMemoryPath}`);
35643
36691
  console.log(`Wrote ${result.userMemoryPath}`);
@@ -35650,7 +36698,7 @@ async function handleMemoryCommand(cwd, tokens) {
35650
36698
  }
35651
36699
  let filePath;
35652
36700
  if (scope === "init") {
35653
- filePath = import_node_path20.default.join(cwd, "kimbho_init.md");
36701
+ filePath = import_node_path21.default.join(cwd, "kimbho_init.md");
35654
36702
  } else if (scope === "project") {
35655
36703
  filePath = resolveProjectMemoryPath(cwd);
35656
36704
  } else if (scope === "user") {
@@ -35660,7 +36708,7 @@ async function handleMemoryCommand(cwd, tokens) {
35660
36708
  if (!agentId) {
35661
36709
  throw new Error("Usage: /memory show agent <agent-id>");
35662
36710
  }
35663
- filePath = import_node_path20.default.join(resolveAgentMemoryDir(cwd), `${agentId}.md`);
36711
+ filePath = import_node_path21.default.join(resolveAgentMemoryDir(cwd), `${agentId}.md`);
35664
36712
  } else {
35665
36713
  throw new Error("Usage: /memory show <init|project|user|agent> [id]");
35666
36714
  }
@@ -35728,6 +36776,45 @@ async function handleMcpCommand(cwd, tokens) {
35728
36776
  }
35729
36777
  return;
35730
36778
  }
36779
+ if (subcommand === "prompts") {
36780
+ const name = tokens[2]?.trim();
36781
+ for (const line of await renderMcpPromptList(cwd, name)) {
36782
+ console.log(line);
36783
+ }
36784
+ return;
36785
+ }
36786
+ if (subcommand === "resources") {
36787
+ const name = tokens[2]?.trim();
36788
+ for (const line of await renderMcpResourceList(cwd, name)) {
36789
+ console.log(line);
36790
+ }
36791
+ return;
36792
+ }
36793
+ if (subcommand === "prompt") {
36794
+ const serverName = tokens[2]?.trim();
36795
+ const promptName = tokens[3]?.trim();
36796
+ if (!serverName || !promptName) {
36797
+ throw new Error("Usage: /mcp prompt <server> <prompt> [key=value ...]");
36798
+ }
36799
+ const args = Object.fromEntries(
36800
+ tokens.slice(4).map((entry) => entry.split("=", 2)).filter((parts) => parts.length === 2 && parts[0].length > 0)
36801
+ );
36802
+ for (const line of await renderMcpPromptInvocation(cwd, serverName, promptName, args)) {
36803
+ console.log(line);
36804
+ }
36805
+ return;
36806
+ }
36807
+ if (subcommand === "read") {
36808
+ const serverName = tokens[2]?.trim();
36809
+ const uri = tokens.slice(3).join(" ").trim();
36810
+ if (!serverName || !uri) {
36811
+ throw new Error("Usage: /mcp read <server> <uri>");
36812
+ }
36813
+ for (const line of await renderMcpResourceRead(cwd, serverName, uri)) {
36814
+ console.log(line);
36815
+ }
36816
+ return;
36817
+ }
35731
36818
  if (subcommand === "remove") {
35732
36819
  const name = tokens[2]?.trim();
35733
36820
  if (!name) {
@@ -35791,7 +36878,7 @@ async function handleMcpCommand(cwd, tokens) {
35791
36878
  console.log(`Added MCP server ${name}`);
35792
36879
  return;
35793
36880
  }
35794
- throw new Error("Usage: /mcp [list|tools [server]|inspect <server>|add <name> --command <cmd>|remove <name>|enable <name>|disable <name>]");
36881
+ throw new Error("Usage: /mcp [list|tools [server]|prompts [server]|resources [server]|prompt <server> <prompt>|read <server> <uri>|add <name> --command <cmd>|remove <name>|enable <name>|disable <name>]");
35795
36882
  }
35796
36883
  async function handleAgentsCommand(cwd, tokens) {
35797
36884
  const subcommand = tokens[1]?.trim().toLowerCase();
@@ -35922,11 +37009,12 @@ async function handleModelSurfaceCommand(cwd, tokens, runtime) {
35922
37009
  throw new Error("Usage: /model [show|providers|templates|add <tpl>|use <provider> [model]|find [search]|select <n>|focus <role>|check]");
35923
37010
  }
35924
37011
  async function createPlanOnly(cwd, goal) {
37012
+ const hydrated = await hydratePromptWithMcpResources(cwd, goal);
35925
37013
  const request = {
35926
- goal,
37014
+ goal: hydrated.prompt,
35927
37015
  mode: "plan",
35928
37016
  cwd,
35929
- workspaceState: await inferPlanningWorkspaceState(cwd, goal),
37017
+ workspaceState: await inferPlanningWorkspaceState(cwd, hydrated.prompt),
35930
37018
  constraints: []
35931
37019
  };
35932
37020
  const activity = new ShellActivityIndicator("planning");
@@ -35939,6 +37027,9 @@ async function createPlanOnly(cwd, goal) {
35939
37027
  }
35940
37028
  const plan = planResult.plan;
35941
37029
  const planPath = await savePlan(plan, cwd);
37030
+ for (const note of hydrated.notes) {
37031
+ console.log(color(DIM, note));
37032
+ }
35942
37033
  for (const line of renderPlanGenerationNotes(planResult)) {
35943
37034
  console.log(line);
35944
37035
  }
@@ -36111,7 +37202,7 @@ function renderModelLine2(model) {
36111
37202
  async function printProviderList(cwd, focusRole) {
36112
37203
  const config2 = await loadConfig(cwd);
36113
37204
  if (!config2) {
36114
- console.log("No config found. Run /init or /providers add <template> first.");
37205
+ console.log("No config found. Run /init or /model add <template> first.");
36115
37206
  return;
36116
37207
  }
36117
37208
  const activeProviderId = config2.brains[focusRole].providerId;
@@ -36163,7 +37254,7 @@ async function addProviderFromTemplate(cwd, options) {
36163
37254
  async function printProviderHealth(cwd) {
36164
37255
  const config2 = await loadConfig(cwd);
36165
37256
  if (!config2) {
36166
- console.log("No config found. Run /init or /providers add <template> first.");
37257
+ console.log("No config found. Run /init or /model add <template> first.");
36167
37258
  return;
36168
37259
  }
36169
37260
  const registry2 = createDefaultBrainProviderRegistry(cwd);
@@ -36180,7 +37271,7 @@ async function printProviderHealth(cwd) {
36180
37271
  async function printBrainAssignments(cwd) {
36181
37272
  const config2 = await loadConfig(cwd);
36182
37273
  if (!config2) {
36183
- console.log("No config found. Run /init or /providers add <template> first.");
37274
+ console.log("No config found. Run /init or /model add <template> first.");
36184
37275
  return;
36185
37276
  }
36186
37277
  for (const role of BRAIN_ROLES) {
@@ -36218,7 +37309,7 @@ async function handleProvidersCommand(cwd, tokens, runtime) {
36218
37309
  if (options.baseUrl) {
36219
37310
  console.log(`Base URL ${options.baseUrl}`);
36220
37311
  }
36221
- console.log(`Next: /providers use ${resolvedProviderId}`);
37312
+ console.log(`Next: /model use ${resolvedProviderId}`);
36222
37313
  return;
36223
37314
  }
36224
37315
  if (subcommand === "use") {
@@ -36258,7 +37349,7 @@ async function handleBrainCommand(cwd, tokens, runtime) {
36258
37349
  async function handleModelsCommand(cwd, tokens, runtime) {
36259
37350
  const config2 = await loadConfig(cwd);
36260
37351
  if (!config2) {
36261
- throw new Error("No config found. Run /init or /providers add <template> first.");
37352
+ throw new Error("No config found. Run /init or /model add <template> first.");
36262
37353
  }
36263
37354
  const providerId = config2.brains[runtime.focusRole].providerId;
36264
37355
  const provider = findProviderById(config2, providerId);
@@ -36301,12 +37392,12 @@ async function handleModelsCommand(cwd, tokens, runtime) {
36301
37392
  console.log(` ${index + 1}. ${renderModelLine2(model)}`);
36302
37393
  }
36303
37394
  console.log(``);
36304
- console.log("Use /select <number> or /use-model <model-id> to assign one to all roles.");
37395
+ console.log("Use /model select <number> or /model use <provider-id> <model-id> to assign one to all roles.");
36305
37396
  }
36306
37397
  async function handleModelSelection(cwd, modelId, runtime) {
36307
37398
  const config2 = await loadConfig(cwd);
36308
37399
  if (!config2) {
36309
- throw new Error("No config found. Run /init or /providers add <template> first.");
37400
+ throw new Error("No config found. Run /init or /model add <template> first.");
36310
37401
  }
36311
37402
  const providerId = config2.brains[runtime.focusRole].providerId;
36312
37403
  const provider = findProviderById(config2, providerId);
@@ -36415,6 +37506,23 @@ async function handleShellCommand(cwd, input, state, runtime, execute) {
36415
37506
  if (!head) {
36416
37507
  return cwd;
36417
37508
  }
37509
+ if (head.startsWith("mcp__")) {
37510
+ const parts = head.split("__").filter((part) => part.length > 0);
37511
+ if (parts.length < 3) {
37512
+ throw new Error("Usage: /mcp__<server>__<prompt> [key=value ...]");
37513
+ }
37514
+ const serverName = parts[1];
37515
+ const promptName = parts[2];
37516
+ if (!serverName || !promptName) {
37517
+ throw new Error("Usage: /mcp__<server>__<prompt> [key=value ...]");
37518
+ }
37519
+ const args = Object.fromEntries(
37520
+ tokens.slice(1).map((entry) => entry.split("=", 2)).filter((pair) => pair.length === 2 && pair[0].length > 0)
37521
+ );
37522
+ const rendered = await renderMcpPromptInvocation(cwd, serverName, promptName, args);
37523
+ await handleChatPrompt(cwd, rendered.join("\n"), runtime);
37524
+ return cwd;
37525
+ }
36418
37526
  if (!firstToken?.startsWith("/") && !TOP_LEVEL_COMMANDS.has(head) && !head.startsWith("-")) {
36419
37527
  const intent = inferPromptIntent(trimmed);
36420
37528
  if (intent === "run") {