@docyrus/docyrus 0.0.42 → 0.0.44

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/server-loader.js CHANGED
@@ -22018,6 +22018,23 @@ function normalizeDeploymentName(params) {
22018
22018
  return params.defaultModel;
22019
22019
  }
22020
22020
  async function resolveKnowledgeProviderFromAgentRuntime(params) {
22021
+ if (params.docyrusApi) {
22022
+ const { apiBaseUrl, accessToken } = params.docyrusApi;
22023
+ const gatewayBase = apiBaseUrl.replace(/\/+$/u, "") + "/ai/gateway";
22024
+ return {
22025
+ provider: {
22026
+ providerId: "docyrus-api",
22027
+ apiBase: gatewayBase,
22028
+ model: DEFAULT_EMBEDDING_MODEL,
22029
+ dimensions: DEFAULT_DIMENSIONS,
22030
+ key: accessToken,
22031
+ headers: (key) => ({
22032
+ Authorization: `Bearer ${key}`,
22033
+ "Content-Type": "application/json"
22034
+ })
22035
+ }
22036
+ };
22037
+ }
22021
22038
  const agentRootPath = resolveDocyrusPiAgentRootPath(params.settingsRootPath);
22022
22039
  const authState = readJsonFile((0, import_node_path10.join)(agentRootPath, "auth.json")) || {};
22023
22040
  const envStore = new AgentEnvStore((0, import_node_path10.join)(agentRootPath, "env.json"));
@@ -22560,9 +22577,9 @@ async function checkKnowledgeSections(knowledgeDir) {
22560
22577
  }
22561
22578
 
22562
22579
  // src/knowledge/init.ts
22563
- var import_node_fs9 = require("node:fs");
22564
- var import_promises11 = require("node:fs/promises");
22565
- var import_node_path15 = require("node:path");
22580
+ var import_node_fs7 = require("node:fs");
22581
+ var import_promises9 = require("node:fs/promises");
22582
+ var import_node_path13 = require("node:path");
22566
22583
  init_graph();
22567
22584
 
22568
22585
  // src/knowledge/bootstrap.ts
@@ -22974,29 +22991,265 @@ async function generateInitialKnowledge(params) {
22974
22991
  };
22975
22992
  }
22976
22993
 
22994
+ // src/knowledge/init.ts
22995
+ var MARKER_BEGIN = "%% docyrus-knowledge:begin %%";
22996
+ var MARKER_END = "%% docyrus-knowledge:end %%";
22997
+ var HOOK_MARKER_BEGIN = "# docyrus-knowledge:begin";
22998
+ var HOOK_MARKER_END = "# docyrus-knowledge:end";
22999
+ function wrapManagedBlock(content3) {
23000
+ return `${MARKER_BEGIN}
23001
+ ${content3}${content3.endsWith("\n") ? "" : "\n"}${MARKER_END}
23002
+ `;
23003
+ }
23004
+ async function ensureDirectory(pathValue) {
23005
+ await (0, import_promises9.mkdir)(pathValue, {
23006
+ recursive: true,
23007
+ mode: 493
23008
+ });
23009
+ }
23010
+ async function writeIfMissing(filePath, content3) {
23011
+ if ((0, import_node_fs7.existsSync)(filePath)) {
23012
+ return "kept";
23013
+ }
23014
+ await ensureDirectory((0, import_node_path13.dirname)(filePath));
23015
+ await (0, import_promises9.writeFile)(filePath, content3, "utf8");
23016
+ return "created";
23017
+ }
23018
+ async function writeOrUpdate(filePath, content3) {
23019
+ if (!(0, import_node_fs7.existsSync)(filePath)) {
23020
+ await ensureDirectory((0, import_node_path13.dirname)(filePath));
23021
+ await (0, import_promises9.writeFile)(filePath, content3, "utf8");
23022
+ return "created";
23023
+ }
23024
+ const current = await (0, import_promises9.readFile)(filePath, "utf8");
23025
+ if (current === content3) {
23026
+ return "kept";
23027
+ }
23028
+ await ensureDirectory((0, import_node_path13.dirname)(filePath));
23029
+ await (0, import_promises9.writeFile)(filePath, content3, "utf8");
23030
+ return "updated";
23031
+ }
23032
+ async function upsertManagedBlock(filePath, content3) {
23033
+ const wrapped = wrapManagedBlock(content3);
23034
+ if (!(0, import_node_fs7.existsSync)(filePath)) {
23035
+ await ensureDirectory((0, import_node_path13.dirname)(filePath));
23036
+ await (0, import_promises9.writeFile)(filePath, wrapped, "utf8");
23037
+ return "created";
23038
+ }
23039
+ const current = await (0, import_promises9.readFile)(filePath, "utf8");
23040
+ const beginIndex = current.indexOf(MARKER_BEGIN);
23041
+ const endIndex = current.indexOf(MARKER_END);
23042
+ if (beginIndex !== -1 && endIndex !== -1 && endIndex > beginIndex) {
23043
+ const replaceEnd = current[endIndex + MARKER_END.length] === "\n" ? endIndex + MARKER_END.length + 1 : endIndex + MARKER_END.length;
23044
+ const updated = `${current.slice(0, beginIndex)}${wrapped}${current.slice(replaceEnd)}`;
23045
+ await (0, import_promises9.writeFile)(filePath, updated, "utf8");
23046
+ return "updated";
23047
+ }
23048
+ const separator = current.endsWith("\n") ? "\n" : "\n\n";
23049
+ await (0, import_promises9.writeFile)(filePath, `${current}${separator}${wrapped}`, "utf8");
23050
+ return "appended";
23051
+ }
23052
+ function normalizeObject(value2) {
23053
+ return typeof value2 === "object" && value2 !== null && !Array.isArray(value2) ? { ...value2 } : {};
23054
+ }
23055
+ async function updateJsonFile(filePath, updater) {
23056
+ const current = (0, import_node_fs7.existsSync)(filePath) ? normalizeObject(JSON.parse((0, import_node_fs7.readFileSync)(filePath, "utf8"))) : {};
23057
+ await ensureDirectory((0, import_node_path13.dirname)(filePath));
23058
+ await (0, import_promises9.writeFile)(filePath, `${JSON.stringify(updater(current), null, 2)}
23059
+ `, "utf8");
23060
+ }
23061
+ function wrapHookBlock(content3) {
23062
+ return `${HOOK_MARKER_BEGIN}
23063
+ ${content3}${content3.endsWith("\n") ? "" : "\n"}${HOOK_MARKER_END}
23064
+ `;
23065
+ }
23066
+ async function upsertShellHook(filePath, content3, shellHeader) {
23067
+ const wrapped = wrapHookBlock(content3);
23068
+ if (!(0, import_node_fs7.existsSync)(filePath)) {
23069
+ await ensureDirectory((0, import_node_path13.dirname)(filePath));
23070
+ const initial = `${shellHeader || ""}${wrapped}`;
23071
+ await (0, import_promises9.writeFile)(filePath, initial, {
23072
+ encoding: "utf8",
23073
+ mode: 493
23074
+ });
23075
+ await (0, import_promises9.chmod)(filePath, 493);
23076
+ return "created";
23077
+ }
23078
+ const current = await (0, import_promises9.readFile)(filePath, "utf8");
23079
+ const beginIndex = current.indexOf(HOOK_MARKER_BEGIN);
23080
+ const endIndex = current.indexOf(HOOK_MARKER_END);
23081
+ if (beginIndex !== -1 && endIndex !== -1 && endIndex > beginIndex) {
23082
+ const replaceEnd = current[endIndex + HOOK_MARKER_END.length] === "\n" ? endIndex + HOOK_MARKER_END.length + 1 : endIndex + HOOK_MARKER_END.length;
23083
+ const nextContent = `${current.slice(0, beginIndex)}${wrapped}${current.slice(replaceEnd)}`;
23084
+ if (nextContent === current) {
23085
+ return "kept";
23086
+ }
23087
+ await (0, import_promises9.writeFile)(filePath, nextContent, "utf8");
23088
+ await (0, import_promises9.chmod)(filePath, 493);
23089
+ return "updated";
23090
+ }
23091
+ const separator = current.endsWith("\n") ? "\n" : "\n\n";
23092
+ await (0, import_promises9.writeFile)(filePath, `${current}${separator}${wrapped}`, "utf8");
23093
+ await (0, import_promises9.chmod)(filePath, 493);
23094
+ return "appended";
23095
+ }
23096
+ function buildKnowledgeDocumentTemplate() {
23097
+ return [
23098
+ "# Knowledge",
23099
+ "",
23100
+ "This directory captures the architecture, constraints, workflows, and test expectations that coding agents should read before changing the repo.",
23101
+ "",
23102
+ "## Working Agreement",
23103
+ "",
23104
+ "Search this knowledge graph before coding, keep it updated when behavior changes, and use \\[\\[wiki links\\]\\] plus `@docyrus` backlinks to connect decisions to implementation.",
23105
+ "",
23106
+ "## Backlinks",
23107
+ "",
23108
+ "Use `// @docyrus: \\[\\[knowledge#Section Name\\]\\]` or `# @docyrus: \\[\\[knowledge#Section Name\\]\\]` near the code or tests that implement a documented behavior.",
23109
+ ""
23110
+ ].join("\n");
23111
+ }
23112
+ function buildAgentsTemplate(commandPrefix) {
23113
+ return [
23114
+ "# Knowledge Graph Workflow",
23115
+ "",
23116
+ `- If \`docyrus/knowledge/\` exists, run \`${commandPrefix} knowledge search\` before coding so you start from documented intent instead of rediscovering it from source files.`,
23117
+ `- Use \`${commandPrefix} knowledge expand\` when prompts contain \`[[refs]]\` so section names resolve to real file locations and summaries.`,
23118
+ "- Keep `docyrus/knowledge/` in sync whenever you change functionality, architecture, tests, or behavior.",
23119
+ `- Before finishing, run \`${commandPrefix} knowledge check\` and fix any broken links, stale references, or missing \`@docyrus\` backlinks.`,
23120
+ ""
23121
+ ].join("\n");
23122
+ }
23123
+ function buildCursorRulesTemplate(commandPrefix) {
23124
+ return [
23125
+ "# Knowledge Graph Workflow",
23126
+ "",
23127
+ `- Use \`${commandPrefix} knowledge search\` to find relevant documentation before reading or editing code.`,
23128
+ `- Use \`${commandPrefix} knowledge section\` to read the full section once you have a relevant section id.`,
23129
+ `- Use \`${commandPrefix} knowledge expand\` whenever the prompt includes \`[[refs]]\`.`,
23130
+ `- Keep \`docyrus/knowledge/\` updated and finish with \`${commandPrefix} knowledge check\`.`,
23131
+ ""
23132
+ ].join("\n");
23133
+ }
23134
+ function buildClaudeHookCommand(commandPrefix, event) {
23135
+ return `${commandPrefix} knowledge hook claude ${event}`;
23136
+ }
23137
+ function syncClaudeHooks(settings, commandPrefix) {
23138
+ const hooks = normalizeObject(settings.hooks);
23139
+ const nextHooks = { ...hooks };
23140
+ for (const [eventName, entries] of Object.entries(nextHooks)) {
23141
+ if (!Array.isArray(entries)) {
23142
+ continue;
23143
+ }
23144
+ nextHooks[eventName] = entries.filter((entry) => {
23145
+ const hooksList = Array.isArray(entry.hooks) ? entry.hooks || [] : [];
23146
+ return !hooksList.some((hook) => typeof hook.command === "string" && hook.command.includes("knowledge hook claude"));
23147
+ });
23148
+ }
23149
+ const addHook = (eventName) => {
23150
+ const current = Array.isArray(nextHooks[eventName]) ? nextHooks[eventName] : [];
23151
+ current.push({
23152
+ hooks: [
23153
+ {
23154
+ type: "command",
23155
+ command: buildClaudeHookCommand(commandPrefix, eventName)
23156
+ }
23157
+ ]
23158
+ });
23159
+ nextHooks[eventName] = current;
23160
+ };
23161
+ addHook("UserPromptSubmit");
23162
+ addHook("Stop");
23163
+ return {
23164
+ ...settings,
23165
+ hooks: nextHooks
23166
+ };
23167
+ }
23168
+ function syncCursorHooks(settings, commandPrefix) {
23169
+ const hooks = normalizeObject(settings.hooks);
23170
+ const stopEntries = Array.isArray(hooks.stop) ? hooks.stop : [];
23171
+ const filtered = stopEntries.filter((entry) => typeof entry.command !== "string" || !entry.command.includes("knowledge hook cursor"));
23172
+ return {
23173
+ ...settings.version ? settings : { version: 1 },
23174
+ ...settings,
23175
+ hooks: {
23176
+ ...hooks,
23177
+ stop: [
23178
+ ...filtered,
23179
+ {
23180
+ command: `${commandPrefix} knowledge hook cursor stop`
23181
+ }
23182
+ ]
23183
+ }
23184
+ };
23185
+ }
23186
+ async function initializeKnowledgeRepo(params) {
23187
+ const root = (0, import_node_path13.resolve)(params.root || process.cwd());
23188
+ const knowledgeDir = getKnowledgeDirectory(root);
23189
+ const knowledgeFile = (0, import_node_path13.join)(knowledgeDir, "knowledge.md");
23190
+ await ensureDirectory(knowledgeDir);
23191
+ const knowledgeFileStatus = await writeIfMissing(knowledgeFile, `${buildKnowledgeDocumentTemplate()}
23192
+ `);
23193
+ const agentsStatus = await upsertManagedBlock((0, import_node_path13.join)(root, "AGENTS.md"), buildAgentsTemplate(params.commandPrefix));
23194
+ const claudeStatus = await upsertManagedBlock((0, import_node_path13.join)(root, "CLAUDE.md"), buildAgentsTemplate(params.commandPrefix));
23195
+ const cursorRulesStatus = await writeOrUpdate((0, import_node_path13.join)(root, ".cursor", "rules", "docyrus-knowledge.md"), `${buildCursorRulesTemplate(params.commandPrefix)}
23196
+ `);
23197
+ const bootstrap = (await generateInitialKnowledge({
23198
+ root,
23199
+ brief: params.brief
23200
+ })).files;
23201
+ await updateJsonFile((0, import_node_path13.join)(root, ".claude", "settings.json"), (value2) => syncClaudeHooks(value2, params.commandPrefix));
23202
+ await updateJsonFile((0, import_node_path13.join)(root, ".cursor", "hooks.json"), (value2) => syncCursorHooks(value2, params.commandPrefix));
23203
+ let huskyHookStatus;
23204
+ const huskyDir = (0, import_node_path13.join)(root, ".husky");
23205
+ if ((0, import_node_fs7.existsSync)(huskyDir)) {
23206
+ const huskyHookPath = (0, import_node_path13.join)(huskyDir, "pre-commit");
23207
+ const huskyHeader = (0, import_node_fs7.existsSync)((0, import_node_path13.join)(huskyDir, "_", "h")) ? '#!/usr/bin/env sh\n. "$(dirname -- "$0")/_/h"\n\n' : void 0;
23208
+ huskyHookStatus = await upsertShellHook(huskyHookPath, `${params.commandPrefix} knowledge pre-commit
23209
+ `, huskyHeader);
23210
+ }
23211
+ let gitHookStatus;
23212
+ if (!huskyHookStatus && params.installGitHook) {
23213
+ const gitHookPath = (0, import_node_path13.join)(root, ".git", "hooks", "pre-commit");
23214
+ gitHookStatus = await upsertShellHook(gitHookPath, `${params.commandPrefix} knowledge pre-commit
23215
+ `, "#!/usr/bin/env sh\n\n");
23216
+ }
23217
+ return {
23218
+ root,
23219
+ knowledgeDir,
23220
+ knowledgeFile,
23221
+ agentsStatus,
23222
+ claudeStatus,
23223
+ cursorRulesStatus,
23224
+ knowledgeFileStatus,
23225
+ bootstrap,
23226
+ huskyHookStatus,
23227
+ gitHookStatus
23228
+ };
23229
+ }
23230
+
22977
23231
  // src/project-plan/graph.ts
22978
23232
  var import_node_crypto3 = require("node:crypto");
22979
- var import_node_fs8 = require("node:fs");
22980
- var import_promises10 = __toESM(require("node:fs/promises"));
22981
- var import_node_path14 = __toESM(require("node:path"));
22982
- init_graph();
23233
+ var import_node_fs9 = require("node:fs");
23234
+ var import_promises11 = __toESM(require("node:fs/promises"));
23235
+ var import_node_path15 = __toESM(require("node:path"));
22983
23236
 
22984
23237
  // src/project-plan/localTodos.ts
22985
23238
  var import_node_crypto2 = __toESM(require("node:crypto"));
22986
- var import_node_fs7 = require("node:fs");
22987
- var import_promises9 = __toESM(require("node:fs/promises"));
22988
- var import_node_path13 = __toESM(require("node:path"));
23239
+ var import_node_fs8 = require("node:fs");
23240
+ var import_promises10 = __toESM(require("node:fs/promises"));
23241
+ var import_node_path14 = __toESM(require("node:path"));
22989
23242
  var TODO_DIR_NAME = ".pi/todos";
22990
23243
  var TODO_PATH_ENV = "PI_TODO_PATH";
22991
23244
  function resolveTodosDirectory(root) {
22992
23245
  const overridePath = process.env[TODO_PATH_ENV]?.trim();
22993
23246
  if (overridePath) {
22994
- return import_node_path13.default.resolve(root, overridePath);
23247
+ return import_node_path14.default.resolve(root, overridePath);
22995
23248
  }
22996
- return import_node_path13.default.resolve(root, TODO_DIR_NAME);
23249
+ return import_node_path14.default.resolve(root, TODO_DIR_NAME);
22997
23250
  }
22998
23251
  function getTodoFilePath(todosDir, id) {
22999
- return import_node_path13.default.join(todosDir, `${id}.md`);
23252
+ return import_node_path14.default.join(todosDir, `${id}.md`);
23000
23253
  }
23001
23254
  function splitFrontMatter(content3) {
23002
23255
  if (!content3.startsWith("{")) {
@@ -23102,7 +23355,7 @@ ${trimmedBody}
23102
23355
  async function generateTodoId(todosDir) {
23103
23356
  for (let attempt = 0; attempt < 10; attempt += 1) {
23104
23357
  const id = import_node_crypto2.default.randomBytes(4).toString("hex");
23105
- if (!(0, import_node_fs7.existsSync)(getTodoFilePath(todosDir, id))) {
23358
+ if (!(0, import_node_fs8.existsSync)(getTodoFilePath(todosDir, id))) {
23106
23359
  return id;
23107
23360
  }
23108
23361
  }
@@ -23113,7 +23366,7 @@ async function listLinkedTodosByTask(root) {
23113
23366
  const result = /* @__PURE__ */ new Map();
23114
23367
  let entries = [];
23115
23368
  try {
23116
- entries = await import_promises9.default.readdir(todosDir);
23369
+ entries = await import_promises10.default.readdir(todosDir);
23117
23370
  } catch {
23118
23371
  return result;
23119
23372
  }
@@ -23124,7 +23377,7 @@ async function listLinkedTodosByTask(root) {
23124
23377
  const id = entry.slice(0, -3);
23125
23378
  const filePath = getTodoFilePath(todosDir, id);
23126
23379
  try {
23127
- const raw2 = await import_promises9.default.readFile(filePath, "utf8");
23380
+ const raw2 = await import_promises10.default.readFile(filePath, "utf8");
23128
23381
  const { frontMatter } = splitFrontMatter(raw2);
23129
23382
  const parsed = parseLocalTodoFrontMatter(frontMatter, id);
23130
23383
  if (!parsed.parent_task_id) {
@@ -23135,7 +23388,7 @@ async function listLinkedTodosByTask(root) {
23135
23388
  id,
23136
23389
  title: parsed.title,
23137
23390
  status: parsed.status,
23138
- filePath: import_node_path13.default.relative(root, filePath),
23391
+ filePath: import_node_path14.default.relative(root, filePath),
23139
23392
  parentTaskId: parsed.parent_task_id
23140
23393
  });
23141
23394
  result.set(parsed.parent_task_id, existing);
@@ -23153,7 +23406,7 @@ async function listAllLinkedTodos(root) {
23153
23406
  }
23154
23407
  async function createLinkedTodo(params) {
23155
23408
  const todosDir = resolveTodosDirectory(params.root);
23156
- await import_promises9.default.mkdir(todosDir, {
23409
+ await import_promises10.default.mkdir(todosDir, {
23157
23410
  recursive: true
23158
23411
  });
23159
23412
  const id = await generateTodoId(todosDir);
@@ -23167,12 +23420,12 @@ async function createLinkedTodo(params) {
23167
23420
  parent_task_id: params.parentTaskId,
23168
23421
  body: params.body ?? ""
23169
23422
  };
23170
- await import_promises9.default.writeFile(filePath, serializeLocalTodo(todo), "utf8");
23423
+ await import_promises10.default.writeFile(filePath, serializeLocalTodo(todo), "utf8");
23171
23424
  return {
23172
23425
  id,
23173
23426
  title: todo.title,
23174
23427
  status: todo.status,
23175
- filePath: import_node_path13.default.relative(params.root, filePath),
23428
+ filePath: import_node_path14.default.relative(params.root, filePath),
23176
23429
  parentTaskId: params.parentTaskId
23177
23430
  };
23178
23431
  }
@@ -23200,6 +23453,9 @@ var PROJECT_TASK_STATUSES = [
23200
23453
  var PROJECT_PLAN_DIR_SEGMENTS = ["docyrus", "project-plan"];
23201
23454
  var PROJECT_PLAN_JSON_FILE_NAME = "project-plan.json";
23202
23455
  var PROJECT_PLAN_MARKDOWN_FILE_NAME = "PROJECT_PLAN.md";
23456
+ var KNOWLEDGE_FEATURES_PATH_SEGMENTS = ["docyrus", "knowledge", "features", "features.md"];
23457
+ var FEATURES_MARKER_BEGIN = "<!-- docyrus-project-plan:features:begin -->";
23458
+ var FEATURES_MARKER_END = "<!-- docyrus-project-plan:features:end -->";
23203
23459
  function isRecord3(value2) {
23204
23460
  return typeof value2 === "object" && value2 !== null && !Array.isArray(value2);
23205
23461
  }
@@ -23219,6 +23475,9 @@ function slugify(value2) {
23219
23475
  function hashParts(parts2) {
23220
23476
  return (0, import_node_crypto3.createHash)("sha1").update(parts2.join("::")).digest("hex").slice(0, 12);
23221
23477
  }
23478
+ function normalizeSectionId(slug) {
23479
+ return `section-${hashParts([slug])}`;
23480
+ }
23222
23481
  function normalizeFeatureId(sectionId, slug) {
23223
23482
  return `feature-${hashParts([sectionId, slug])}`;
23224
23483
  }
@@ -23231,7 +23490,7 @@ function compareStrings(left, right) {
23231
23490
  function sortGraph(graph) {
23232
23491
  return {
23233
23492
  version: 1,
23234
- sections: [...graph.sections].sort((left, right) => compareStrings(left.sectionId, right.sectionId)),
23493
+ sections: [...graph.sections].sort((left, right) => compareStrings(left.id, right.id)),
23235
23494
  features: [...graph.features].sort((left, right) => {
23236
23495
  return compareStrings(left.sectionId, right.sectionId) || compareStrings(left.title, right.title) || compareStrings(left.id, right.id);
23237
23496
  }),
@@ -23241,13 +23500,13 @@ function sortGraph(graph) {
23241
23500
  };
23242
23501
  }
23243
23502
  function resolveProjectPlanDirectory(root) {
23244
- return import_node_path14.default.join(root, ...PROJECT_PLAN_DIR_SEGMENTS);
23503
+ return import_node_path15.default.join(root, ...PROJECT_PLAN_DIR_SEGMENTS);
23245
23504
  }
23246
23505
  function resolveProjectPlanGraphPath(root) {
23247
- return import_node_path14.default.join(resolveProjectPlanDirectory(root), PROJECT_PLAN_JSON_FILE_NAME);
23506
+ return import_node_path15.default.join(resolveProjectPlanDirectory(root), PROJECT_PLAN_JSON_FILE_NAME);
23248
23507
  }
23249
23508
  function resolveProjectPlanMarkdownPath(root) {
23250
- return import_node_path14.default.join(resolveProjectPlanDirectory(root), PROJECT_PLAN_MARKDOWN_FILE_NAME);
23509
+ return import_node_path15.default.join(resolveProjectPlanDirectory(root), PROJECT_PLAN_MARKDOWN_FILE_NAME);
23251
23510
  }
23252
23511
  function createEmptyProjectPlanGraph() {
23253
23512
  return {
@@ -23257,20 +23516,16 @@ function createEmptyProjectPlanGraph() {
23257
23516
  tasks: []
23258
23517
  };
23259
23518
  }
23260
- async function readKnowledgeSections(root) {
23261
- const knowledgeDir = import_node_path14.default.join(root, "docyrus", "knowledge");
23262
- if (!(0, import_node_fs8.existsSync)(knowledgeDir)) {
23263
- return [];
23264
- }
23265
- return flattenSections(await loadAllSections(knowledgeDir));
23266
- }
23267
23519
  function parseProjectPlanGraph(rawValue) {
23268
23520
  if (!isRecord3(rawValue)) {
23269
23521
  return createEmptyProjectPlanGraph();
23270
23522
  }
23271
23523
  const sections = Array.isArray(rawValue.sections) ? rawValue.sections.filter((value2) => isRecord3(value2)).map((value2) => ({
23272
- sectionId: isNonEmptyString2(value2.sectionId) ? value2.sectionId.trim() : ""
23273
- })).filter((value2) => value2.sectionId.length > 0) : [];
23524
+ id: isNonEmptyString2(value2.id) ? value2.id.trim() : "",
23525
+ title: isNonEmptyString2(value2.title) ? value2.title.trim() : "",
23526
+ slug: isNonEmptyString2(value2.slug) ? value2.slug.trim() : "",
23527
+ summary: typeof value2.summary === "string" ? value2.summary.trim() : ""
23528
+ })).filter((value2) => value2.id.length > 0) : [];
23274
23529
  const features = Array.isArray(rawValue.features) ? rawValue.features.filter((value2) => isRecord3(value2)).map((value2) => ({
23275
23530
  id: isNonEmptyString2(value2.id) ? value2.id.trim() : "",
23276
23531
  title: isNonEmptyString2(value2.title) ? value2.title.trim() : "",
@@ -23298,10 +23553,10 @@ function parseProjectPlanGraph(rawValue) {
23298
23553
  }
23299
23554
  async function readProjectPlanGraph(root) {
23300
23555
  const graphPath = resolveProjectPlanGraphPath(root);
23301
- if (!(0, import_node_fs8.existsSync)(graphPath)) {
23556
+ if (!(0, import_node_fs9.existsSync)(graphPath)) {
23302
23557
  return createEmptyProjectPlanGraph();
23303
23558
  }
23304
- const raw2 = await import_promises10.default.readFile(graphPath, "utf8");
23559
+ const raw2 = await import_promises11.default.readFile(graphPath, "utf8");
23305
23560
  return parseProjectPlanGraph(JSON.parse(raw2));
23306
23561
  }
23307
23562
  function deriveFeatureStatus(tasks) {
@@ -23334,21 +23589,7 @@ function deriveSectionStatus(features) {
23334
23589
  }
23335
23590
  return "planned";
23336
23591
  }
23337
- function findKnowledgeSectionTitle(knowledgeSections, sectionId) {
23338
- const section = knowledgeSections.find((item) => item.id === sectionId);
23339
- if (!section) {
23340
- return {
23341
- heading: sectionId,
23342
- filePath: ""
23343
- };
23344
- }
23345
- return {
23346
- heading: section.heading,
23347
- filePath: section.filePath
23348
- };
23349
- }
23350
23592
  async function buildProjectPlanHierarchy(root, graph) {
23351
- const knowledgeSections = await readKnowledgeSections(root);
23352
23593
  const currentGraph = graph ?? await readProjectPlanGraph(root);
23353
23594
  const linkedTodos = await listLinkedTodosByTask(root);
23354
23595
  const featureMap = /* @__PURE__ */ new Map();
@@ -23369,13 +23610,13 @@ async function buildProjectPlanHierarchy(root, graph) {
23369
23610
  });
23370
23611
  }
23371
23612
  const sections = currentGraph.sections.map((section) => {
23372
- const features = currentGraph.features.filter((feature) => feature.sectionId === section.sectionId).map((feature) => featureMap.get(feature.id)).filter((feature) => Boolean(feature));
23373
- const knowledgeMetadata = findKnowledgeSectionTitle(knowledgeSections, section.sectionId);
23613
+ const features = currentGraph.features.filter((feature) => feature.sectionId === section.id).map((feature) => featureMap.get(feature.id)).filter((feature) => Boolean(feature));
23374
23614
  const taskCount = features.reduce((count, feature) => count + feature.taskCount, 0);
23375
23615
  return {
23376
- sectionId: section.sectionId,
23377
- heading: knowledgeMetadata.heading,
23378
- filePath: knowledgeMetadata.filePath,
23616
+ id: section.id,
23617
+ title: section.title,
23618
+ slug: section.slug,
23619
+ summary: section.summary,
23379
23620
  status: deriveSectionStatus(features),
23380
23621
  featureCount: features.length,
23381
23622
  taskCount,
@@ -23407,9 +23648,9 @@ async function renderProjectPlanMarkdown(root, graph) {
23407
23648
  return `${lines.join("\n")}`;
23408
23649
  }
23409
23650
  for (const section of populatedSections) {
23410
- lines.push(`## ${section.heading}`);
23651
+ lines.push(`## ${section.title}`);
23411
23652
  lines.push("");
23412
- lines.push(`- Section ID: \`${section.sectionId}\``);
23653
+ lines.push(`- Section ID: \`${section.id}\``);
23413
23654
  lines.push(`- Status: \`${section.status}\``);
23414
23655
  lines.push(`- Features: ${section.featureCount}`);
23415
23656
  lines.push(`- Tasks: ${section.taskCount}`);
@@ -23443,16 +23684,84 @@ async function renderProjectPlanMarkdown(root, graph) {
23443
23684
  return `${lines.join("\n").trimEnd()}
23444
23685
  `;
23445
23686
  }
23687
+ function buildKnowledgeFeaturesSection(graph) {
23688
+ const sectionMap = new Map(graph.sections.map((section) => [section.id, section]));
23689
+ const featuresBySection = /* @__PURE__ */ new Map();
23690
+ for (const feature of graph.features) {
23691
+ const list2 = featuresBySection.get(feature.sectionId) || [];
23692
+ list2.push(feature);
23693
+ featuresBySection.set(feature.sectionId, list2);
23694
+ }
23695
+ const lines = [
23696
+ "## Planned Features",
23697
+ "",
23698
+ "Features tracked in the project plan.",
23699
+ ""
23700
+ ];
23701
+ if (graph.features.length === 0) {
23702
+ lines.push("No planned features yet.");
23703
+ lines.push("");
23704
+ return lines.join("\n");
23705
+ }
23706
+ for (const section of graph.sections) {
23707
+ const features = featuresBySection.get(section.id);
23708
+ if (!features || features.length === 0) {
23709
+ continue;
23710
+ }
23711
+ lines.push(`### ${section.title}`);
23712
+ lines.push("");
23713
+ for (const feature of features) {
23714
+ const summary = feature.summary ? ` \u2014 ${feature.summary}` : "";
23715
+ lines.push(`- **${feature.title}**${summary}`);
23716
+ }
23717
+ lines.push("");
23718
+ }
23719
+ const ungrouped = graph.features.filter((feature) => !sectionMap.has(feature.sectionId));
23720
+ if (ungrouped.length > 0) {
23721
+ for (const feature of ungrouped) {
23722
+ const summary = feature.summary ? ` \u2014 ${feature.summary}` : "";
23723
+ lines.push(`- **${feature.title}**${summary}`);
23724
+ }
23725
+ lines.push("");
23726
+ }
23727
+ return lines.join("\n");
23728
+ }
23729
+ async function syncFeaturesToKnowledge(root, graph) {
23730
+ const featuresPath = import_node_path15.default.join(root, ...KNOWLEDGE_FEATURES_PATH_SEGMENTS);
23731
+ if (!(0, import_node_fs9.existsSync)(featuresPath)) {
23732
+ return;
23733
+ }
23734
+ const content3 = buildKnowledgeFeaturesSection(graph);
23735
+ const wrapped = `${FEATURES_MARKER_BEGIN}
23736
+ ${content3.trimEnd()}
23737
+
23738
+ ${FEATURES_MARKER_END}
23739
+ `;
23740
+ const current = await import_promises11.default.readFile(featuresPath, "utf8");
23741
+ const beginIndex = current.indexOf(FEATURES_MARKER_BEGIN);
23742
+ const endIndex = current.indexOf(FEATURES_MARKER_END);
23743
+ if (beginIndex !== -1 && endIndex !== -1 && endIndex > beginIndex) {
23744
+ const replaceEnd = current[endIndex + FEATURES_MARKER_END.length] === "\n" ? endIndex + FEATURES_MARKER_END.length + 1 : endIndex + FEATURES_MARKER_END.length;
23745
+ const next = `${current.slice(0, beginIndex)}${wrapped}${current.slice(replaceEnd)}`;
23746
+ if (next !== current) {
23747
+ await import_promises11.default.writeFile(featuresPath, next, "utf8");
23748
+ }
23749
+ return;
23750
+ }
23751
+ const separator = current.endsWith("\n") ? "\n" : "\n\n";
23752
+ await import_promises11.default.writeFile(featuresPath, `${current}${separator}${wrapped}`, "utf8");
23753
+ }
23446
23754
  async function writeProjectPlanFiles(root, graph) {
23447
23755
  const graphPath = resolveProjectPlanGraphPath(root);
23448
23756
  const markdownPath = resolveProjectPlanMarkdownPath(root);
23449
- await import_promises10.default.mkdir(import_node_path14.default.dirname(graphPath), {
23757
+ await import_promises11.default.mkdir(import_node_path15.default.dirname(graphPath), {
23450
23758
  recursive: true
23451
23759
  });
23452
23760
  const sortedGraph = sortGraph(graph);
23453
- await import_promises10.default.writeFile(graphPath, `${JSON.stringify(sortedGraph, null, 2)}
23761
+ await import_promises11.default.writeFile(graphPath, `${JSON.stringify(sortedGraph, null, 2)}
23454
23762
  `, "utf8");
23455
- await import_promises10.default.writeFile(markdownPath, await renderProjectPlanMarkdown(root, sortedGraph), "utf8");
23763
+ await import_promises11.default.writeFile(markdownPath, await renderProjectPlanMarkdown(root, sortedGraph), "utf8");
23764
+ await syncFeaturesToKnowledge(root, sortedGraph);
23456
23765
  return {
23457
23766
  graph: sortedGraph,
23458
23767
  graphPath,
@@ -23469,27 +23778,24 @@ function buildProjectPlanCheckError(code, message, target) {
23469
23778
  async function validateProjectPlanGraph(root, graph) {
23470
23779
  const currentGraph = graph ?? await readProjectPlanGraph(root);
23471
23780
  const errors = [];
23472
- const knowledgeSections = await readKnowledgeSections(root);
23473
- const knowledgeSectionIds = new Set(knowledgeSections.map((section) => section.id));
23474
23781
  const sectionIds = /* @__PURE__ */ new Set();
23475
23782
  const featureIds = /* @__PURE__ */ new Set();
23476
23783
  const featureMap = /* @__PURE__ */ new Map();
23477
23784
  for (const section of currentGraph.sections) {
23478
- if (!section.sectionId) {
23479
- errors.push(buildProjectPlanCheckError("section_missing_id", "Project plan section is missing `sectionId`."));
23785
+ if (!section.id) {
23786
+ errors.push(buildProjectPlanCheckError("section_missing_id", "Project plan section is missing `id`."));
23480
23787
  continue;
23481
23788
  }
23482
- if (sectionIds.has(section.sectionId)) {
23483
- errors.push(buildProjectPlanCheckError("duplicate_section", `Duplicate section id "${section.sectionId}"`, section.sectionId));
23789
+ if (sectionIds.has(section.id)) {
23790
+ errors.push(buildProjectPlanCheckError("duplicate_section", `Duplicate section id "${section.id}"`, section.id));
23484
23791
  continue;
23485
23792
  }
23486
- sectionIds.add(section.sectionId);
23487
- if (!knowledgeSectionIds.has(section.sectionId)) {
23488
- errors.push(buildProjectPlanCheckError(
23489
- "unknown_knowledge_section",
23490
- `Project plan section "${section.sectionId}" does not exist in docyrus/knowledge.`,
23491
- section.sectionId
23492
- ));
23793
+ sectionIds.add(section.id);
23794
+ if (!section.title.trim()) {
23795
+ errors.push(buildProjectPlanCheckError("section_missing_title", `Section "${section.id}" is missing a title.`, section.id));
23796
+ }
23797
+ if (!section.slug.trim()) {
23798
+ errors.push(buildProjectPlanCheckError("section_missing_slug", `Section "${section.id}" is missing a slug.`, section.id));
23493
23799
  }
23494
23800
  }
23495
23801
  const allowedTypes = new Set(PROJECT_TASK_TYPES);
@@ -23584,350 +23890,114 @@ async function validateProjectPlanGraph(root, graph) {
23584
23890
  }
23585
23891
  }
23586
23892
  return {
23587
- ok: errors.length === 0,
23588
- errors
23589
- };
23590
- }
23591
- async function resolveKnowledgeSectionIds(root) {
23592
- const sections = await readKnowledgeSections(root);
23593
- return sections.map((section) => section.id).sort(compareStrings);
23594
- }
23595
- async function syncProjectPlanSectionsFromKnowledge(root) {
23596
- const currentGraph = await readProjectPlanGraph(root);
23597
- const knowledgeSectionIds = await resolveKnowledgeSectionIds(root);
23598
- const syncedGraph = {
23599
- ...currentGraph,
23600
- sections: knowledgeSectionIds.map((sectionId) => ({
23601
- sectionId
23602
- }))
23603
- };
23604
- return writeProjectPlanFiles(root, syncedGraph);
23605
- }
23606
- async function ensureProjectPlanGraph(root) {
23607
- const graphPath = resolveProjectPlanGraphPath(root);
23608
- if (!(0, import_node_fs8.existsSync)(graphPath)) {
23609
- return syncProjectPlanSectionsFromKnowledge(root);
23610
- }
23611
- const graph = await readProjectPlanGraph(root);
23612
- return writeProjectPlanFiles(root, graph);
23613
- }
23614
- async function upsertProjectPlanFeature(params) {
23615
- const state = await ensureProjectPlanGraph(params.root);
23616
- const slug = slugify(params.slug?.trim() || params.title);
23617
- const featureId = params.featureId?.trim() || normalizeFeatureId(params.sectionId, slug);
23618
- const graph = state.graph;
23619
- const existing = graph.features.find((feature) => feature.id === featureId) || graph.features.find((feature) => feature.sectionId === params.sectionId && feature.slug === slug);
23620
- const nextFeature = {
23621
- id: existing?.id || featureId,
23622
- title: params.title.trim(),
23623
- slug,
23624
- summary: params.summary?.trim() || "",
23625
- sectionId: params.sectionId
23626
- };
23627
- const nextFeatures = existing ? graph.features.map((feature) => feature.id === existing.id ? nextFeature : feature) : [...graph.features, nextFeature];
23628
- await writeProjectPlanFiles(params.root, {
23629
- ...graph,
23630
- features: nextFeatures
23631
- });
23632
- return nextFeature;
23633
- }
23634
- async function getProjectPlanTask(params) {
23635
- const graph = await readProjectPlanGraph(params.root);
23636
- const linkedTodos = await listLinkedTodosByTask(params.root);
23637
- const task = graph.tasks.find((item) => item.id === params.taskId);
23638
- if (!task) {
23639
- return null;
23640
- }
23641
- const taskLinkedTodos = linkedTodos.get(task.id) || [];
23642
- return {
23643
- ...task,
23644
- linkedTodos: taskLinkedTodos,
23645
- linkedTodoCount: taskLinkedTodos.length
23646
- };
23647
- }
23648
- async function upsertProjectPlanTask(params) {
23649
- const state = await ensureProjectPlanGraph(params.root);
23650
- const graph = state.graph;
23651
- const feature = graph.features.find((item) => item.id === params.featureId);
23652
- if (!feature) {
23653
- throw new Error(`Feature "${params.featureId}" not found.`);
23654
- }
23655
- const sectionId = params.sectionId?.trim() || feature.sectionId;
23656
- const taskId = params.taskId?.trim() || normalizeTaskId(feature.id, params.type, params.title);
23657
- const existing = graph.tasks.find((task) => task.id === taskId) || graph.tasks.find((task) => task.featureId === feature.id && task.type === params.type && task.title === params.title.trim());
23658
- const nextTask = {
23659
- id: existing?.id || taskId,
23660
- title: params.title.trim(),
23661
- summary: params.summary?.trim() || "",
23662
- type: params.type,
23663
- assignee: params.assignee,
23664
- status: params.status || "planned",
23665
- acceptanceCriteria: [...new Set((params.acceptanceCriteria || []).map((item) => item.trim()).filter(Boolean))],
23666
- featureId: feature.id,
23667
- sectionId
23668
- };
23669
- const nextTasks = existing ? graph.tasks.map((task) => task.id === existing.id ? nextTask : task) : [...graph.tasks, nextTask];
23670
- await writeProjectPlanFiles(params.root, {
23671
- ...graph,
23672
- tasks: nextTasks
23673
- });
23674
- return nextTask;
23675
- }
23676
- async function updateProjectPlanTaskStatus(params) {
23677
- const graph = await readProjectPlanGraph(params.root);
23678
- const task = graph.tasks.find((item) => item.id === params.taskId);
23679
- if (!task) {
23680
- throw new Error(`Task "${params.taskId}" not found.`);
23681
- }
23682
- const nextTask = {
23683
- ...task,
23684
- status: params.status
23685
- };
23686
- await writeProjectPlanFiles(params.root, {
23687
- ...graph,
23688
- tasks: graph.tasks.map((item) => item.id === task.id ? nextTask : item)
23689
- });
23690
- return nextTask;
23691
- }
23692
-
23693
- // src/knowledge/init.ts
23694
- var MARKER_BEGIN = "%% docyrus-knowledge:begin %%";
23695
- var MARKER_END = "%% docyrus-knowledge:end %%";
23696
- var HOOK_MARKER_BEGIN = "# docyrus-knowledge:begin";
23697
- var HOOK_MARKER_END = "# docyrus-knowledge:end";
23698
- function wrapManagedBlock(content3) {
23699
- return `${MARKER_BEGIN}
23700
- ${content3}${content3.endsWith("\n") ? "" : "\n"}${MARKER_END}
23701
- `;
23702
- }
23703
- async function ensureDirectory(pathValue) {
23704
- await (0, import_promises11.mkdir)(pathValue, {
23705
- recursive: true,
23706
- mode: 493
23707
- });
23708
- }
23709
- async function writeIfMissing(filePath, content3) {
23710
- if ((0, import_node_fs9.existsSync)(filePath)) {
23711
- return "kept";
23712
- }
23713
- await ensureDirectory((0, import_node_path15.dirname)(filePath));
23714
- await (0, import_promises11.writeFile)(filePath, content3, "utf8");
23715
- return "created";
23716
- }
23717
- async function writeOrUpdate(filePath, content3) {
23718
- if (!(0, import_node_fs9.existsSync)(filePath)) {
23719
- await ensureDirectory((0, import_node_path15.dirname)(filePath));
23720
- await (0, import_promises11.writeFile)(filePath, content3, "utf8");
23721
- return "created";
23722
- }
23723
- const current = await (0, import_promises11.readFile)(filePath, "utf8");
23724
- if (current === content3) {
23725
- return "kept";
23726
- }
23727
- await ensureDirectory((0, import_node_path15.dirname)(filePath));
23728
- await (0, import_promises11.writeFile)(filePath, content3, "utf8");
23729
- return "updated";
23730
- }
23731
- async function upsertManagedBlock(filePath, content3) {
23732
- const wrapped = wrapManagedBlock(content3);
23733
- if (!(0, import_node_fs9.existsSync)(filePath)) {
23734
- await ensureDirectory((0, import_node_path15.dirname)(filePath));
23735
- await (0, import_promises11.writeFile)(filePath, wrapped, "utf8");
23736
- return "created";
23737
- }
23738
- const current = await (0, import_promises11.readFile)(filePath, "utf8");
23739
- const beginIndex = current.indexOf(MARKER_BEGIN);
23740
- const endIndex = current.indexOf(MARKER_END);
23741
- if (beginIndex !== -1 && endIndex !== -1 && endIndex > beginIndex) {
23742
- const replaceEnd = current[endIndex + MARKER_END.length] === "\n" ? endIndex + MARKER_END.length + 1 : endIndex + MARKER_END.length;
23743
- const updated = `${current.slice(0, beginIndex)}${wrapped}${current.slice(replaceEnd)}`;
23744
- await (0, import_promises11.writeFile)(filePath, updated, "utf8");
23745
- return "updated";
23746
- }
23747
- const separator = current.endsWith("\n") ? "\n" : "\n\n";
23748
- await (0, import_promises11.writeFile)(filePath, `${current}${separator}${wrapped}`, "utf8");
23749
- return "appended";
23750
- }
23751
- function normalizeObject(value2) {
23752
- return typeof value2 === "object" && value2 !== null && !Array.isArray(value2) ? { ...value2 } : {};
23753
- }
23754
- async function updateJsonFile(filePath, updater) {
23755
- const current = (0, import_node_fs9.existsSync)(filePath) ? normalizeObject(JSON.parse((0, import_node_fs9.readFileSync)(filePath, "utf8"))) : {};
23756
- await ensureDirectory((0, import_node_path15.dirname)(filePath));
23757
- await (0, import_promises11.writeFile)(filePath, `${JSON.stringify(updater(current), null, 2)}
23758
- `, "utf8");
23759
- }
23760
- function wrapHookBlock(content3) {
23761
- return `${HOOK_MARKER_BEGIN}
23762
- ${content3}${content3.endsWith("\n") ? "" : "\n"}${HOOK_MARKER_END}
23763
- `;
23764
- }
23765
- async function upsertShellHook(filePath, content3, shellHeader) {
23766
- const wrapped = wrapHookBlock(content3);
23767
- if (!(0, import_node_fs9.existsSync)(filePath)) {
23768
- await ensureDirectory((0, import_node_path15.dirname)(filePath));
23769
- const initial = `${shellHeader || ""}${wrapped}`;
23770
- await (0, import_promises11.writeFile)(filePath, initial, {
23771
- encoding: "utf8",
23772
- mode: 493
23773
- });
23774
- await (0, import_promises11.chmod)(filePath, 493);
23775
- return "created";
23776
- }
23777
- const current = await (0, import_promises11.readFile)(filePath, "utf8");
23778
- const beginIndex = current.indexOf(HOOK_MARKER_BEGIN);
23779
- const endIndex = current.indexOf(HOOK_MARKER_END);
23780
- if (beginIndex !== -1 && endIndex !== -1 && endIndex > beginIndex) {
23781
- const replaceEnd = current[endIndex + HOOK_MARKER_END.length] === "\n" ? endIndex + HOOK_MARKER_END.length + 1 : endIndex + HOOK_MARKER_END.length;
23782
- const nextContent = `${current.slice(0, beginIndex)}${wrapped}${current.slice(replaceEnd)}`;
23783
- if (nextContent === current) {
23784
- return "kept";
23785
- }
23786
- await (0, import_promises11.writeFile)(filePath, nextContent, "utf8");
23787
- await (0, import_promises11.chmod)(filePath, 493);
23788
- return "updated";
23789
- }
23790
- const separator = current.endsWith("\n") ? "\n" : "\n\n";
23791
- await (0, import_promises11.writeFile)(filePath, `${current}${separator}${wrapped}`, "utf8");
23792
- await (0, import_promises11.chmod)(filePath, 493);
23793
- return "appended";
23794
- }
23795
- function buildKnowledgeDocumentTemplate() {
23796
- return [
23797
- "# Knowledge",
23798
- "",
23799
- "This directory captures the architecture, constraints, workflows, and test expectations that coding agents should read before changing the repo.",
23800
- "",
23801
- "## Working Agreement",
23802
- "",
23803
- "Search this knowledge graph before coding, keep it updated when behavior changes, and use \\[\\[wiki links\\]\\] plus `@docyrus` backlinks to connect decisions to implementation.",
23804
- "",
23805
- "## Backlinks",
23806
- "",
23807
- "Use `// @docyrus: \\[\\[knowledge#Section Name\\]\\]` or `# @docyrus: \\[\\[knowledge#Section Name\\]\\]` near the code or tests that implement a documented behavior.",
23808
- ""
23809
- ].join("\n");
23893
+ ok: errors.length === 0,
23894
+ errors
23895
+ };
23810
23896
  }
23811
- function buildAgentsTemplate(commandPrefix) {
23812
- return [
23813
- "# Knowledge Graph Workflow",
23814
- "",
23815
- `- If \`docyrus/knowledge/\` exists, run \`${commandPrefix} knowledge search\` before coding so you start from documented intent instead of rediscovering it from source files.`,
23816
- `- Use \`${commandPrefix} knowledge expand\` when prompts contain \`[[refs]]\` so section names resolve to real file locations and summaries.`,
23817
- "- Keep `docyrus/knowledge/` in sync whenever you change functionality, architecture, tests, or behavior.",
23818
- `- Before finishing, run \`${commandPrefix} knowledge check\` and fix any broken links, stale references, or missing \`@docyrus\` backlinks.`,
23819
- ""
23820
- ].join("\n");
23897
+ async function ensureProjectPlanGraph(root) {
23898
+ const graphPath = resolveProjectPlanGraphPath(root);
23899
+ if (!(0, import_node_fs9.existsSync)(graphPath)) {
23900
+ return writeProjectPlanFiles(root, createEmptyProjectPlanGraph());
23901
+ }
23902
+ const graph = await readProjectPlanGraph(root);
23903
+ return writeProjectPlanFiles(root, graph);
23821
23904
  }
23822
- function buildCursorRulesTemplate(commandPrefix) {
23823
- return [
23824
- "# Knowledge Graph Workflow",
23825
- "",
23826
- `- Use \`${commandPrefix} knowledge search\` to find relevant documentation before reading or editing code.`,
23827
- `- Use \`${commandPrefix} knowledge section\` to read the full section once you have a relevant section id.`,
23828
- `- Use \`${commandPrefix} knowledge expand\` whenever the prompt includes \`[[refs]]\`.`,
23829
- `- Keep \`docyrus/knowledge/\` updated and finish with \`${commandPrefix} knowledge check\`.`,
23830
- ""
23831
- ].join("\n");
23905
+ async function upsertProjectPlanSection(params) {
23906
+ const state = await ensureProjectPlanGraph(params.root);
23907
+ const slug = slugify(params.slug?.trim() || params.title);
23908
+ const sectionId = params.id?.trim() || normalizeSectionId(slug);
23909
+ const graph = state.graph;
23910
+ const existing = graph.sections.find((section) => section.id === sectionId) || graph.sections.find((section) => section.slug === slug);
23911
+ const nextSection = {
23912
+ id: existing?.id || sectionId,
23913
+ title: params.title.trim(),
23914
+ slug,
23915
+ summary: params.summary?.trim() || ""
23916
+ };
23917
+ const nextSections = existing ? graph.sections.map((section) => section.id === existing.id ? nextSection : section) : [...graph.sections, nextSection];
23918
+ await writeProjectPlanFiles(params.root, {
23919
+ ...graph,
23920
+ sections: nextSections
23921
+ });
23922
+ return nextSection;
23832
23923
  }
23833
- function buildClaudeHookCommand(commandPrefix, event) {
23834
- return `${commandPrefix} knowledge hook claude ${event}`;
23924
+ async function upsertProjectPlanFeature(params) {
23925
+ const state = await ensureProjectPlanGraph(params.root);
23926
+ const slug = slugify(params.slug?.trim() || params.title);
23927
+ const featureId = params.featureId?.trim() || normalizeFeatureId(params.sectionId, slug);
23928
+ const graph = state.graph;
23929
+ const existing = graph.features.find((feature) => feature.id === featureId) || graph.features.find((feature) => feature.sectionId === params.sectionId && feature.slug === slug);
23930
+ const nextFeature = {
23931
+ id: existing?.id || featureId,
23932
+ title: params.title.trim(),
23933
+ slug,
23934
+ summary: params.summary?.trim() || "",
23935
+ sectionId: params.sectionId
23936
+ };
23937
+ const nextFeatures = existing ? graph.features.map((feature) => feature.id === existing.id ? nextFeature : feature) : [...graph.features, nextFeature];
23938
+ await writeProjectPlanFiles(params.root, {
23939
+ ...graph,
23940
+ features: nextFeatures
23941
+ });
23942
+ return nextFeature;
23835
23943
  }
23836
- function syncClaudeHooks(settings, commandPrefix) {
23837
- const hooks = normalizeObject(settings.hooks);
23838
- const nextHooks = { ...hooks };
23839
- for (const [eventName, entries] of Object.entries(nextHooks)) {
23840
- if (!Array.isArray(entries)) {
23841
- continue;
23842
- }
23843
- nextHooks[eventName] = entries.filter((entry) => {
23844
- const hooksList = Array.isArray(entry.hooks) ? entry.hooks || [] : [];
23845
- return !hooksList.some((hook) => typeof hook.command === "string" && hook.command.includes("knowledge hook claude"));
23846
- });
23944
+ async function getProjectPlanTask(params) {
23945
+ const graph = await readProjectPlanGraph(params.root);
23946
+ const linkedTodos = await listLinkedTodosByTask(params.root);
23947
+ const task = graph.tasks.find((item) => item.id === params.taskId);
23948
+ if (!task) {
23949
+ return null;
23847
23950
  }
23848
- const addHook = (eventName) => {
23849
- const current = Array.isArray(nextHooks[eventName]) ? nextHooks[eventName] : [];
23850
- current.push({
23851
- hooks: [
23852
- {
23853
- type: "command",
23854
- command: buildClaudeHookCommand(commandPrefix, eventName)
23855
- }
23856
- ]
23857
- });
23858
- nextHooks[eventName] = current;
23859
- };
23860
- addHook("UserPromptSubmit");
23861
- addHook("Stop");
23951
+ const taskLinkedTodos = linkedTodos.get(task.id) || [];
23862
23952
  return {
23863
- ...settings,
23864
- hooks: nextHooks
23953
+ ...task,
23954
+ linkedTodos: taskLinkedTodos,
23955
+ linkedTodoCount: taskLinkedTodos.length
23865
23956
  };
23866
23957
  }
23867
- function syncCursorHooks(settings, commandPrefix) {
23868
- const hooks = normalizeObject(settings.hooks);
23869
- const stopEntries = Array.isArray(hooks.stop) ? hooks.stop : [];
23870
- const filtered = stopEntries.filter((entry) => typeof entry.command !== "string" || !entry.command.includes("knowledge hook cursor"));
23871
- return {
23872
- ...settings.version ? settings : { version: 1 },
23873
- ...settings,
23874
- hooks: {
23875
- ...hooks,
23876
- stop: [
23877
- ...filtered,
23878
- {
23879
- command: `${commandPrefix} knowledge hook cursor stop`
23880
- }
23881
- ]
23882
- }
23958
+ async function upsertProjectPlanTask(params) {
23959
+ const state = await ensureProjectPlanGraph(params.root);
23960
+ const graph = state.graph;
23961
+ const feature = graph.features.find((item) => item.id === params.featureId);
23962
+ if (!feature) {
23963
+ throw new Error(`Feature "${params.featureId}" not found.`);
23964
+ }
23965
+ const sectionId = params.sectionId?.trim() || feature.sectionId;
23966
+ const taskId = params.taskId?.trim() || normalizeTaskId(feature.id, params.type, params.title);
23967
+ const existing = graph.tasks.find((task) => task.id === taskId) || graph.tasks.find((task) => task.featureId === feature.id && task.type === params.type && task.title === params.title.trim());
23968
+ const nextTask = {
23969
+ id: existing?.id || taskId,
23970
+ title: params.title.trim(),
23971
+ summary: params.summary?.trim() || "",
23972
+ type: params.type,
23973
+ assignee: params.assignee,
23974
+ status: params.status || "planned",
23975
+ acceptanceCriteria: [...new Set((params.acceptanceCriteria || []).map((item) => item.trim()).filter(Boolean))],
23976
+ featureId: feature.id,
23977
+ sectionId
23883
23978
  };
23979
+ const nextTasks = existing ? graph.tasks.map((task) => task.id === existing.id ? nextTask : task) : [...graph.tasks, nextTask];
23980
+ await writeProjectPlanFiles(params.root, {
23981
+ ...graph,
23982
+ tasks: nextTasks
23983
+ });
23984
+ return nextTask;
23884
23985
  }
23885
- async function initializeKnowledgeRepo(params) {
23886
- const root = (0, import_node_path15.resolve)(params.root || process.cwd());
23887
- const knowledgeDir = getKnowledgeDirectory(root);
23888
- const knowledgeFile = (0, import_node_path15.join)(knowledgeDir, "knowledge.md");
23889
- await ensureDirectory(knowledgeDir);
23890
- const knowledgeFileStatus = await writeIfMissing(knowledgeFile, `${buildKnowledgeDocumentTemplate()}
23891
- `);
23892
- const agentsStatus = await upsertManagedBlock((0, import_node_path15.join)(root, "AGENTS.md"), buildAgentsTemplate(params.commandPrefix));
23893
- const claudeStatus = await upsertManagedBlock((0, import_node_path15.join)(root, "CLAUDE.md"), buildAgentsTemplate(params.commandPrefix));
23894
- const cursorRulesStatus = await writeOrUpdate((0, import_node_path15.join)(root, ".cursor", "rules", "docyrus-knowledge.md"), `${buildCursorRulesTemplate(params.commandPrefix)}
23895
- `);
23896
- const bootstrap = (await generateInitialKnowledge({
23897
- root,
23898
- brief: params.brief
23899
- })).files;
23900
- const projectPlan = await syncProjectPlanSectionsFromKnowledge(root);
23901
- await updateJsonFile((0, import_node_path15.join)(root, ".claude", "settings.json"), (value2) => syncClaudeHooks(value2, params.commandPrefix));
23902
- await updateJsonFile((0, import_node_path15.join)(root, ".cursor", "hooks.json"), (value2) => syncCursorHooks(value2, params.commandPrefix));
23903
- let huskyHookStatus;
23904
- const huskyDir = (0, import_node_path15.join)(root, ".husky");
23905
- if ((0, import_node_fs9.existsSync)(huskyDir)) {
23906
- const huskyHookPath = (0, import_node_path15.join)(huskyDir, "pre-commit");
23907
- const huskyHeader = (0, import_node_fs9.existsSync)((0, import_node_path15.join)(huskyDir, "_", "h")) ? '#!/usr/bin/env sh\n. "$(dirname -- "$0")/_/h"\n\n' : void 0;
23908
- huskyHookStatus = await upsertShellHook(huskyHookPath, `${params.commandPrefix} knowledge pre-commit
23909
- `, huskyHeader);
23910
- }
23911
- let gitHookStatus;
23912
- if (!huskyHookStatus && params.installGitHook) {
23913
- const gitHookPath = (0, import_node_path15.join)(root, ".git", "hooks", "pre-commit");
23914
- gitHookStatus = await upsertShellHook(gitHookPath, `${params.commandPrefix} knowledge pre-commit
23915
- `, "#!/usr/bin/env sh\n\n");
23986
+ async function updateProjectPlanTaskStatus(params) {
23987
+ const graph = await readProjectPlanGraph(params.root);
23988
+ const task = graph.tasks.find((item) => item.id === params.taskId);
23989
+ if (!task) {
23990
+ throw new Error(`Task "${params.taskId}" not found.`);
23916
23991
  }
23917
- return {
23918
- root,
23919
- knowledgeDir,
23920
- knowledgeFile,
23921
- projectPlanGraphPath: projectPlan.graphPath,
23922
- projectPlanMarkdownPath: projectPlan.markdownPath,
23923
- agentsStatus,
23924
- claudeStatus,
23925
- cursorRulesStatus,
23926
- knowledgeFileStatus,
23927
- bootstrap,
23928
- huskyHookStatus,
23929
- gitHookStatus
23992
+ const nextTask = {
23993
+ ...task,
23994
+ status: params.status
23930
23995
  };
23996
+ await writeProjectPlanFiles(params.root, {
23997
+ ...graph,
23998
+ tasks: graph.tasks.map((item) => item.id === task.id ? nextTask : item)
23999
+ });
24000
+ return nextTask;
23931
24001
  }
23932
24002
 
23933
24003
  // src/agent/authFlows.ts
@@ -25366,6 +25436,49 @@ async function waitForIdle(session, timeoutMs = 3e4) {
25366
25436
  function resolveServerSettingsRootPath(agentDir) {
25367
25437
  return (0, import_node_path18.resolve)(agentDir, "..", "..");
25368
25438
  }
25439
+ var SESSION_MODE_COMMANDS = {
25440
+ "read-only": "read-only",
25441
+ "end-read-only": "normal"
25442
+ };
25443
+ var SESSION_MODE_COMMAND_LABELS = {
25444
+ "read-only": "Read-only mode activated. Write and edit tools are disabled. Use /end-read-only to resume normal operation.",
25445
+ "end-read-only": "Read-only mode deactivated. Normal operation resumed."
25446
+ };
25447
+ function parseModeSlashCommand(text3) {
25448
+ const match2 = /^\/([a-z][a-z0-9-]*)(?:\s+([\s\S]+))?$/u.exec(text3.trim());
25449
+ if (!match2) {
25450
+ return null;
25451
+ }
25452
+ const command = match2[1];
25453
+ if (!(command in SESSION_MODE_COMMANDS)) {
25454
+ return null;
25455
+ }
25456
+ return {
25457
+ command,
25458
+ remainder: match2[2]?.trim() ?? ""
25459
+ };
25460
+ }
25461
+ function makeModeNotificationStream(params) {
25462
+ const { command, messageId, encoder } = params;
25463
+ const label = SESSION_MODE_COMMAND_LABELS[command] ?? `/${command} executed.`;
25464
+ return new ReadableStream({
25465
+ start(controller) {
25466
+ function write(chunk) {
25467
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(chunk)}
25468
+
25469
+ `));
25470
+ }
25471
+ write({ type: "start" });
25472
+ write({ type: "start-step" });
25473
+ write({ type: "text-start", id: messageId });
25474
+ write({ type: "text-delta", id: messageId, delta: label });
25475
+ write({ type: "text-end", id: messageId });
25476
+ write({ type: "finish-step" });
25477
+ write({ type: "finish" });
25478
+ controller.close();
25479
+ }
25480
+ });
25481
+ }
25369
25482
  function serializeKnowledgeMatch(match2) {
25370
25483
  return {
25371
25484
  id: match2.section.id,
@@ -25660,6 +25773,7 @@ async function walkFiles(params) {
25660
25773
  async function createAgentServer(params) {
25661
25774
  const { port, sessionManager, modelRegistry, authRuntime, context, onCreateSession, onResumeSession, authToken } = params;
25662
25775
  let activeSession = params.session;
25776
+ let sessionMode = "normal";
25663
25777
  const pendingAskUserRequests = /* @__PURE__ */ new Map();
25664
25778
  const pendingDocyrusWebBrowserRequests = /* @__PURE__ */ new Map();
25665
25779
  const oauthFlowManager = new OAuthFlowManager();
@@ -25700,15 +25814,17 @@ async function createAgentServer(params) {
25700
25814
  app.get("/api/status", (c) => {
25701
25815
  return c.json({
25702
25816
  isStreaming: activeSession.isStreaming,
25703
- model: activeSession.model ? { provider: activeSession.model.provider, id: activeSession.model.id } : null
25817
+ model: activeSession.model ? { provider: activeSession.model.provider, id: activeSession.model.id } : null,
25818
+ mode: sessionMode
25704
25819
  });
25705
25820
  });
25706
25821
  app.post("/api/chat", async (c) => {
25707
25822
  const body2 = await c.req.json();
25708
25823
  const messages = body2.messages ?? [];
25709
- if (body2.sessionId) {
25824
+ if (body2.sessionId && body2.sessionId.trim() !== activeSession.id?.trim()) {
25710
25825
  try {
25711
25826
  activeSession = await onResumeSession(body2.sessionId);
25827
+ sessionMode = "normal";
25712
25828
  } catch (error) {
25713
25829
  const msg = error instanceof Error ? error.message : String(error);
25714
25830
  return c.json({ error: `Failed to resume session: ${msg}` }, 400);
@@ -25745,6 +25861,34 @@ async function createAgentServer(params) {
25745
25861
  pendingDocyrusWebBrowserRequests.delete(sessionId);
25746
25862
  }
25747
25863
  }
25864
+ let promptText = userMessage;
25865
+ if (!askUserResponse && !docyrusWebBrowserResponse) {
25866
+ const modeCmd = parseModeSlashCommand(userMessage);
25867
+ if (modeCmd) {
25868
+ if (activeSession.isStreaming) {
25869
+ await activeSession.abort();
25870
+ await waitForIdle(activeSession);
25871
+ }
25872
+ await activeSession.prompt(`/${modeCmd.command}`);
25873
+ sessionMode = SESSION_MODE_COMMANDS[modeCmd.command] ?? sessionMode;
25874
+ if (!modeCmd.remainder) {
25875
+ const notifEncoder = new TextEncoder();
25876
+ const notifMessageId = generateMessageId();
25877
+ return c.body(
25878
+ makeModeNotificationStream({ command: modeCmd.command, messageId: notifMessageId, encoder: notifEncoder }),
25879
+ {
25880
+ headers: {
25881
+ "Content-Type": "text/event-stream",
25882
+ "Cache-Control": "no-cache",
25883
+ "Connection": "keep-alive",
25884
+ "x-vercel-ai-ui-message-stream": "v1"
25885
+ }
25886
+ }
25887
+ );
25888
+ }
25889
+ promptText = modeCmd.remainder;
25890
+ }
25891
+ }
25748
25892
  if (activeSession.isStreaming) {
25749
25893
  await activeSession.abort();
25750
25894
  await waitForIdle(activeSession);
@@ -25789,7 +25933,7 @@ async function createAgentServer(params) {
25789
25933
  unsubscribe();
25790
25934
  }
25791
25935
  });
25792
- activeSession.prompt(userMessage).then(() => {
25936
+ activeSession.prompt(promptText).then(() => {
25793
25937
  if (!activeSession.isStreaming) {
25794
25938
  unsubscribe();
25795
25939
  writeChunk({ type: "finish-step" });
@@ -25815,6 +25959,19 @@ async function createAgentServer(params) {
25815
25959
  }
25816
25960
  });
25817
25961
  });
25962
+ app.post("/api/chat/abort", async (c) => {
25963
+ if (!activeSession.isStreaming) {
25964
+ return c.json({ ok: true, wasStreaming: false });
25965
+ }
25966
+ try {
25967
+ await activeSession.abort();
25968
+ await waitForIdle(activeSession);
25969
+ return c.json({ ok: true, wasStreaming: true });
25970
+ } catch (error) {
25971
+ const message = error instanceof Error ? error.message : String(error);
25972
+ return c.json({ error: message }, 500);
25973
+ }
25974
+ });
25818
25975
  app.get("/api/sessions", async (c) => {
25819
25976
  try {
25820
25977
  const sessions = await sessionManager.list();
@@ -25829,6 +25986,21 @@ async function createAgentServer(params) {
25829
25986
  messageCount: s.messageCount,
25830
25987
  firstMessage: s.firstMessage
25831
25988
  })).sort((a, b) => b.modified.localeCompare(a.modified));
25989
+ const activeId = activeSession.id?.trim();
25990
+ if (activeId && !mapped.some((s) => s.id === activeId)) {
25991
+ const now = (/* @__PURE__ */ new Date()).toISOString();
25992
+ mapped.unshift({
25993
+ id: activeId,
25994
+ path: "",
25995
+ cwd: context.cwd,
25996
+ name: null,
25997
+ parentSessionPath: null,
25998
+ created: now,
25999
+ modified: now,
26000
+ messageCount: 0,
26001
+ firstMessage: null
26002
+ });
26003
+ }
25832
26004
  return c.json({ sessions: mapped });
25833
26005
  } catch (error) {
25834
26006
  const message = error instanceof Error ? error.message : String(error);
@@ -25842,6 +26014,7 @@ async function createAgentServer(params) {
25842
26014
  await waitForIdle(activeSession);
25843
26015
  }
25844
26016
  activeSession = await onCreateSession();
26017
+ sessionMode = "normal";
25845
26018
  const sessionId = activeSession.id?.trim();
25846
26019
  if (!sessionId) {
25847
26020
  return c.json({ error: "Created session is missing an id" }, 500);
@@ -26049,8 +26222,7 @@ async function createAgentServer(params) {
26049
26222
  const codeRefs = await checkKnowledgeCodeRefs(knowledgeDir);
26050
26223
  const index2 = await checkKnowledgeIndex(knowledgeDir);
26051
26224
  const sections = await checkKnowledgeSections(knowledgeDir);
26052
- const projectPlan = await validateProjectPlanGraph(context.cwd);
26053
- const totalErrors = markdown.errors.length + codeRefs.errors.length + index2.length + sections.length + projectPlan.errors.length;
26225
+ const totalErrors = markdown.errors.length + codeRefs.errors.length + index2.length + sections.length;
26054
26226
  return c.json({
26055
26227
  ok: totalErrors === 0,
26056
26228
  knowledgeDir,
@@ -26058,8 +26230,7 @@ async function createAgentServer(params) {
26058
26230
  markdown: markdown.errors,
26059
26231
  codeRefs: codeRefs.errors,
26060
26232
  index: index2,
26061
- sections,
26062
- projectPlan: projectPlan.errors
26233
+ sections
26063
26234
  },
26064
26235
  files: {
26065
26236
  markdown: markdown.files,
@@ -26095,12 +26266,7 @@ async function createAgentServer(params) {
26095
26266
  brief: body2.brief?.trim() || void 0,
26096
26267
  targetSectionIds: Array.isArray(body2.sectionIds) ? body2.sectionIds : void 0
26097
26268
  });
26098
- const projectPlan = await syncProjectPlanSectionsFromKnowledge(context.cwd);
26099
- return c.json({
26100
- ...result,
26101
- projectPlanGraphPath: projectPlan.graphPath,
26102
- projectPlanMarkdownPath: projectPlan.markdownPath
26103
- });
26269
+ return c.json(result);
26104
26270
  } catch (error) {
26105
26271
  const message = error instanceof Error ? error.message : String(error);
26106
26272
  return c.json({ error: message }, 500);
@@ -26144,17 +26310,23 @@ async function createAgentServer(params) {
26144
26310
  return c.json({ error: message }, 500);
26145
26311
  }
26146
26312
  });
26147
- app.post("/api/project-plan/sync-sections", async (c) => {
26313
+ app.post("/api/project-plan/sections", async (c) => {
26314
+ const body2 = await c.req.json().catch(() => ({}));
26315
+ if (!body2.title?.trim()) {
26316
+ return c.json({ error: "Missing required field: title" }, 400);
26317
+ }
26148
26318
  try {
26149
- const result = await syncProjectPlanSectionsFromKnowledge(context.cwd);
26150
- return c.json({
26151
- graph: result.graph,
26152
- graphPath: result.graphPath,
26153
- markdownPath: result.markdownPath
26319
+ const section = await upsertProjectPlanSection({
26320
+ root: context.cwd,
26321
+ id: body2.id?.trim() || void 0,
26322
+ title: body2.title.trim(),
26323
+ slug: body2.slug?.trim() || void 0,
26324
+ summary: body2.summary?.trim() || void 0
26154
26325
  });
26326
+ return c.json(section);
26155
26327
  } catch (error) {
26156
26328
  const message = error instanceof Error ? error.message : String(error);
26157
- return c.json({ error: message }, 500);
26329
+ return c.json({ error: message }, 400);
26158
26330
  }
26159
26331
  });
26160
26332
  app.get("/api/project-plan/tasks/:taskId", async (c) => {
@@ -26434,11 +26606,13 @@ async function createAgentServer(params) {
26434
26606
  app.post("/api/sessions/:sessionId/resume", async (c) => {
26435
26607
  const sessionId = c.req.param("sessionId");
26436
26608
  try {
26437
- if (activeSession.isStreaming) {
26438
- await activeSession.abort();
26439
- await waitForIdle(activeSession);
26609
+ if (sessionId.trim() !== activeSession.id?.trim()) {
26610
+ if (activeSession.isStreaming) {
26611
+ await activeSession.abort();
26612
+ await waitForIdle(activeSession);
26613
+ }
26614
+ activeSession = await onResumeSession(sessionId);
26440
26615
  }
26441
- activeSession = await onResumeSession(sessionId);
26442
26616
  return c.json({
26443
26617
  ok: true,
26444
26618
  sessionId,
@@ -26449,6 +26623,20 @@ async function createAgentServer(params) {
26449
26623
  return c.json({ error: message }, 500);
26450
26624
  }
26451
26625
  });
26626
+ app.post("/api/sessions/rename", async (c) => {
26627
+ const body2 = await c.req.json();
26628
+ const name2 = typeof body2.name === "string" ? body2.name.trim() : "";
26629
+ if (!name2) {
26630
+ return c.json({ error: "Missing required field: name" }, 400);
26631
+ }
26632
+ try {
26633
+ activeSession.setSessionName(name2);
26634
+ return c.json({ ok: true, name: name2 });
26635
+ } catch (error) {
26636
+ const message = error instanceof Error ? error.message : String(error);
26637
+ return c.json({ error: message }, 500);
26638
+ }
26639
+ });
26452
26640
  app.get("/api/fs/tree", async (c) => {
26453
26641
  const requestPath = c.req.query("path") ?? ".";
26454
26642
  const maxDepth = Math.min(Number(c.req.query("depth") ?? 4), 10);
@@ -26953,6 +27141,29 @@ async function createAgentServer(params) {
26953
27141
  return c.json({ error: message }, 500);
26954
27142
  }
26955
27143
  });
27144
+ app.get("/api/git/commits", async (c) => {
27145
+ const cwd = context.cwd;
27146
+ const limit = Math.min(Math.max(Number(c.req.query("limit")) || 50, 1), 500);
27147
+ try {
27148
+ let raw2 = "";
27149
+ try {
27150
+ raw2 = await gitExec(
27151
+ ["log", `--max-count=${limit}`, "--format=%H%x00%an%x00%ae%x00%aI%x00%s"],
27152
+ cwd
27153
+ );
27154
+ } catch {
27155
+ return c.json({ commits: [] });
27156
+ }
27157
+ const commits = raw2.trim().split("\n").filter(Boolean).map((line) => {
27158
+ const [hash, authorName, authorEmail, date, message] = line.split("\0");
27159
+ return { hash, authorName, authorEmail, date, message };
27160
+ });
27161
+ return c.json({ commits });
27162
+ } catch (error) {
27163
+ const message = error instanceof Error ? error.message : String(error);
27164
+ return c.json({ error: message }, 500);
27165
+ }
27166
+ });
26956
27167
  app.get("/api/mcp/servers", async (c) => {
26957
27168
  try {
26958
27169
  const [config, cache, provenance] = await Promise.all([
@@ -27236,6 +27447,8 @@ async function createAgentServer(params) {
27236
27447
 
27237
27448
  `);
27238
27449
  process.stderr.write(` POST /api/chat \u2014 send chat messages (SSE UIMessage stream)
27450
+ `);
27451
+ process.stderr.write(` POST /api/chat/abort \u2014 abort active chat stream
27239
27452
  `);
27240
27453
  process.stderr.write(` GET /api/health \u2014 health check
27241
27454
  `);
@@ -27248,6 +27461,8 @@ async function createAgentServer(params) {
27248
27461
  process.stderr.write(` GET /api/sessions/:sessionId/messages \u2014 session messages
27249
27462
  `);
27250
27463
  process.stderr.write(` POST /api/sessions/:sessionId/resume \u2014 resume a session
27464
+ `);
27465
+ process.stderr.write(` POST /api/sessions/rename \u2014 rename active session
27251
27466
  `);
27252
27467
  process.stderr.write(` GET /api/context \u2014 server context
27253
27468
  `);
@@ -27263,11 +27478,11 @@ async function createAgentServer(params) {
27263
27478
  `);
27264
27479
  process.stderr.write(` GET /api/knowledge/search \u2014 semantic knowledge search
27265
27480
  `);
27266
- process.stderr.write(` GET /api/knowledge/check \u2014 knowledge + project-plan validation
27481
+ process.stderr.write(` GET /api/knowledge/check \u2014 knowledge validation
27267
27482
  `);
27268
- process.stderr.write(` POST /api/knowledge/init \u2014 initialize knowledge graph and project plan
27483
+ process.stderr.write(` POST /api/knowledge/init \u2014 initialize knowledge graph
27269
27484
  `);
27270
- process.stderr.write(` POST /api/knowledge/refresh \u2014 refresh managed knowledge files and project-plan sections
27485
+ process.stderr.write(` POST /api/knowledge/refresh \u2014 refresh managed knowledge files
27271
27486
  `);
27272
27487
  process.stderr.write(` GET /api/project-plan \u2014 canonical project-plan graph and hierarchy
27273
27488
  `);
@@ -27275,7 +27490,7 @@ async function createAgentServer(params) {
27275
27490
  `);
27276
27491
  process.stderr.write(` POST /api/project-plan/ensure \u2014 ensure project-plan files exist
27277
27492
  `);
27278
- process.stderr.write(` POST /api/project-plan/sync-sections \u2014 sync project-plan sections from knowledge ids
27493
+ process.stderr.write(` POST /api/project-plan/sections \u2014 upsert project-plan section
27279
27494
  `);
27280
27495
  process.stderr.write(` GET /api/project-plan/tasks/:taskId \u2014 project task detail
27281
27496
  `);
@@ -27512,6 +27727,12 @@ function createServerSessionAdapter(params) {
27512
27727
  supportsThinking() {
27513
27728
  return params.session.supportsThinking();
27514
27729
  },
27730
+ get sessionName() {
27731
+ return params.session.sessionName;
27732
+ },
27733
+ setSessionName(name2) {
27734
+ params.session.setSessionName?.(name2);
27735
+ },
27515
27736
  listCommands() {
27516
27737
  const getCommands = params.extensionsResult.runtime.getCommands;
27517
27738
  return typeof getCommands === "function" ? getCommands() : [];