@docyrus/docyrus 0.0.43 → 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,53 +23684,118 @@ async function renderProjectPlanMarkdown(root, graph) {
23443
23684
  return `${lines.join("\n").trimEnd()}
23444
23685
  `;
23445
23686
  }
23446
- async function writeProjectPlanFiles(root, graph) {
23447
- const graphPath = resolveProjectPlanGraphPath(root);
23448
- const markdownPath = resolveProjectPlanMarkdownPath(root);
23449
- await import_promises10.default.mkdir(import_node_path14.default.dirname(graphPath), {
23450
- recursive: true
23451
- });
23452
- const sortedGraph = sortGraph(graph);
23453
- await import_promises10.default.writeFile(graphPath, `${JSON.stringify(sortedGraph, null, 2)}
23454
- `, "utf8");
23455
- await import_promises10.default.writeFile(markdownPath, await renderProjectPlanMarkdown(root, sortedGraph), "utf8");
23456
- return {
23457
- graph: sortedGraph,
23458
- graphPath,
23459
- markdownPath
23460
- };
23461
- }
23462
- function buildProjectPlanCheckError(code, message, target) {
23463
- return {
23464
- code,
23465
- message,
23466
- target
23467
- };
23468
- }
23469
- async function validateProjectPlanGraph(root, graph) {
23470
- const currentGraph = graph ?? await readProjectPlanGraph(root);
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
+ }
23754
+ async function writeProjectPlanFiles(root, graph) {
23755
+ const graphPath = resolveProjectPlanGraphPath(root);
23756
+ const markdownPath = resolveProjectPlanMarkdownPath(root);
23757
+ await import_promises11.default.mkdir(import_node_path15.default.dirname(graphPath), {
23758
+ recursive: true
23759
+ });
23760
+ const sortedGraph = sortGraph(graph);
23761
+ await import_promises11.default.writeFile(graphPath, `${JSON.stringify(sortedGraph, null, 2)}
23762
+ `, "utf8");
23763
+ await import_promises11.default.writeFile(markdownPath, await renderProjectPlanMarkdown(root, sortedGraph), "utf8");
23764
+ await syncFeaturesToKnowledge(root, sortedGraph);
23765
+ return {
23766
+ graph: sortedGraph,
23767
+ graphPath,
23768
+ markdownPath
23769
+ };
23770
+ }
23771
+ function buildProjectPlanCheckError(code, message, target) {
23772
+ return {
23773
+ code,
23774
+ message,
23775
+ target
23776
+ };
23777
+ }
23778
+ async function validateProjectPlanGraph(root, graph) {
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);
@@ -23539,395 +23845,159 @@ async function validateProjectPlanGraph(root, graph) {
23539
23845
  if (!feature) {
23540
23846
  errors.push(buildProjectPlanCheckError(
23541
23847
  "task_unknown_feature",
23542
- `Task "${task.id}" references unknown feature "${task.featureId}".`,
23543
- task.id
23544
- ));
23545
- continue;
23546
- }
23547
- if (!task.title.trim()) {
23548
- errors.push(buildProjectPlanCheckError("task_missing_title", `Task "${task.id}" is missing a title.`, task.id));
23549
- }
23550
- if (!allowedTypes.has(task.type)) {
23551
- errors.push(buildProjectPlanCheckError(
23552
- "task_invalid_type",
23553
- `Task "${task.id}" has invalid type "${task.type}".`,
23554
- task.id
23555
- ));
23556
- }
23557
- if (!allowedAssignees.has(task.assignee)) {
23558
- errors.push(buildProjectPlanCheckError(
23559
- "task_invalid_assignee",
23560
- `Task "${task.id}" has invalid assignee "${task.assignee}".`,
23561
- task.id
23562
- ));
23563
- }
23564
- if (!allowedStatuses.has(task.status)) {
23565
- errors.push(buildProjectPlanCheckError(
23566
- "task_invalid_status",
23567
- `Task "${task.id}" has invalid status "${task.status}".`,
23568
- task.id
23569
- ));
23570
- }
23571
- if (!sectionIds.has(task.sectionId)) {
23572
- errors.push(buildProjectPlanCheckError(
23573
- "task_unknown_section",
23574
- `Task "${task.id}" references unknown section "${task.sectionId}".`,
23575
- task.id
23576
- ));
23577
- }
23578
- if (task.sectionId !== feature.sectionId) {
23579
- errors.push(buildProjectPlanCheckError(
23580
- "task_section_mismatch",
23581
- `Task "${task.id}" section "${task.sectionId}" does not match feature "${feature.id}" section "${feature.sectionId}".`,
23582
- task.id
23583
- ));
23584
- }
23585
- }
23586
- 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");
23810
- }
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");
23821
- }
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");
23832
- }
23833
- function buildClaudeHookCommand(commandPrefix, event) {
23834
- return `${commandPrefix} knowledge hook claude ${event}`;
23835
- }
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)) {
23848
+ `Task "${task.id}" references unknown feature "${task.featureId}".`,
23849
+ task.id
23850
+ ));
23841
23851
  continue;
23842
23852
  }
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
- });
23853
+ if (!task.title.trim()) {
23854
+ errors.push(buildProjectPlanCheckError("task_missing_title", `Task "${task.id}" is missing a title.`, task.id));
23855
+ }
23856
+ if (!allowedTypes.has(task.type)) {
23857
+ errors.push(buildProjectPlanCheckError(
23858
+ "task_invalid_type",
23859
+ `Task "${task.id}" has invalid type "${task.type}".`,
23860
+ task.id
23861
+ ));
23862
+ }
23863
+ if (!allowedAssignees.has(task.assignee)) {
23864
+ errors.push(buildProjectPlanCheckError(
23865
+ "task_invalid_assignee",
23866
+ `Task "${task.id}" has invalid assignee "${task.assignee}".`,
23867
+ task.id
23868
+ ));
23869
+ }
23870
+ if (!allowedStatuses.has(task.status)) {
23871
+ errors.push(buildProjectPlanCheckError(
23872
+ "task_invalid_status",
23873
+ `Task "${task.id}" has invalid status "${task.status}".`,
23874
+ task.id
23875
+ ));
23876
+ }
23877
+ if (!sectionIds.has(task.sectionId)) {
23878
+ errors.push(buildProjectPlanCheckError(
23879
+ "task_unknown_section",
23880
+ `Task "${task.id}" references unknown section "${task.sectionId}".`,
23881
+ task.id
23882
+ ));
23883
+ }
23884
+ if (task.sectionId !== feature.sectionId) {
23885
+ errors.push(buildProjectPlanCheckError(
23886
+ "task_section_mismatch",
23887
+ `Task "${task.id}" section "${task.sectionId}" does not match feature "${feature.id}" section "${feature.sectionId}".`,
23888
+ task.id
23889
+ ));
23890
+ }
23847
23891
  }
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");
23862
23892
  return {
23863
- ...settings,
23864
- hooks: nextHooks
23893
+ ok: errors.length === 0,
23894
+ errors
23865
23895
  };
23866
23896
  }
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"));
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);
23904
+ }
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;
23923
+ }
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;
23943
+ }
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;
23950
+ }
23951
+ const taskLinkedTodos = linkedTodos.get(task.id) || [];
23871
23952
  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
- }
23953
+ ...task,
23954
+ linkedTodos: taskLinkedTodos,
23955
+ linkedTodoCount: taskLinkedTodos.length
23883
23956
  };
23884
23957
  }
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);
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.`);
23910
23964
  }
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");
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
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;
23985
+ }
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,7 +25814,8 @@ 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) => {
@@ -25709,6 +25824,7 @@ async function createAgentServer(params) {
25709
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();
@@ -25857,6 +26014,7 @@ async function createAgentServer(params) {
25857
26014
  await waitForIdle(activeSession);
25858
26015
  }
25859
26016
  activeSession = await onCreateSession();
26017
+ sessionMode = "normal";
25860
26018
  const sessionId = activeSession.id?.trim();
25861
26019
  if (!sessionId) {
25862
26020
  return c.json({ error: "Created session is missing an id" }, 500);
@@ -26064,8 +26222,7 @@ async function createAgentServer(params) {
26064
26222
  const codeRefs = await checkKnowledgeCodeRefs(knowledgeDir);
26065
26223
  const index2 = await checkKnowledgeIndex(knowledgeDir);
26066
26224
  const sections = await checkKnowledgeSections(knowledgeDir);
26067
- const projectPlan = await validateProjectPlanGraph(context.cwd);
26068
- 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;
26069
26226
  return c.json({
26070
26227
  ok: totalErrors === 0,
26071
26228
  knowledgeDir,
@@ -26073,8 +26230,7 @@ async function createAgentServer(params) {
26073
26230
  markdown: markdown.errors,
26074
26231
  codeRefs: codeRefs.errors,
26075
26232
  index: index2,
26076
- sections,
26077
- projectPlan: projectPlan.errors
26233
+ sections
26078
26234
  },
26079
26235
  files: {
26080
26236
  markdown: markdown.files,
@@ -26110,12 +26266,7 @@ async function createAgentServer(params) {
26110
26266
  brief: body2.brief?.trim() || void 0,
26111
26267
  targetSectionIds: Array.isArray(body2.sectionIds) ? body2.sectionIds : void 0
26112
26268
  });
26113
- const projectPlan = await syncProjectPlanSectionsFromKnowledge(context.cwd);
26114
- return c.json({
26115
- ...result,
26116
- projectPlanGraphPath: projectPlan.graphPath,
26117
- projectPlanMarkdownPath: projectPlan.markdownPath
26118
- });
26269
+ return c.json(result);
26119
26270
  } catch (error) {
26120
26271
  const message = error instanceof Error ? error.message : String(error);
26121
26272
  return c.json({ error: message }, 500);
@@ -26159,17 +26310,23 @@ async function createAgentServer(params) {
26159
26310
  return c.json({ error: message }, 500);
26160
26311
  }
26161
26312
  });
26162
- 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
+ }
26163
26318
  try {
26164
- const result = await syncProjectPlanSectionsFromKnowledge(context.cwd);
26165
- return c.json({
26166
- graph: result.graph,
26167
- graphPath: result.graphPath,
26168
- 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
26169
26325
  });
26326
+ return c.json(section);
26170
26327
  } catch (error) {
26171
26328
  const message = error instanceof Error ? error.message : String(error);
26172
- return c.json({ error: message }, 500);
26329
+ return c.json({ error: message }, 400);
26173
26330
  }
26174
26331
  });
26175
26332
  app.get("/api/project-plan/tasks/:taskId", async (c) => {
@@ -26466,6 +26623,20 @@ async function createAgentServer(params) {
26466
26623
  return c.json({ error: message }, 500);
26467
26624
  }
26468
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
+ });
26469
26640
  app.get("/api/fs/tree", async (c) => {
26470
26641
  const requestPath = c.req.query("path") ?? ".";
26471
26642
  const maxDepth = Math.min(Number(c.req.query("depth") ?? 4), 10);
@@ -27276,6 +27447,8 @@ async function createAgentServer(params) {
27276
27447
 
27277
27448
  `);
27278
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
27279
27452
  `);
27280
27453
  process.stderr.write(` GET /api/health \u2014 health check
27281
27454
  `);
@@ -27288,6 +27461,8 @@ async function createAgentServer(params) {
27288
27461
  process.stderr.write(` GET /api/sessions/:sessionId/messages \u2014 session messages
27289
27462
  `);
27290
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
27291
27466
  `);
27292
27467
  process.stderr.write(` GET /api/context \u2014 server context
27293
27468
  `);
@@ -27303,11 +27478,11 @@ async function createAgentServer(params) {
27303
27478
  `);
27304
27479
  process.stderr.write(` GET /api/knowledge/search \u2014 semantic knowledge search
27305
27480
  `);
27306
- process.stderr.write(` GET /api/knowledge/check \u2014 knowledge + project-plan validation
27481
+ process.stderr.write(` GET /api/knowledge/check \u2014 knowledge validation
27307
27482
  `);
27308
- 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
27309
27484
  `);
27310
- 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
27311
27486
  `);
27312
27487
  process.stderr.write(` GET /api/project-plan \u2014 canonical project-plan graph and hierarchy
27313
27488
  `);
@@ -27315,7 +27490,7 @@ async function createAgentServer(params) {
27315
27490
  `);
27316
27491
  process.stderr.write(` POST /api/project-plan/ensure \u2014 ensure project-plan files exist
27317
27492
  `);
27318
- 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
27319
27494
  `);
27320
27495
  process.stderr.write(` GET /api/project-plan/tasks/:taskId \u2014 project task detail
27321
27496
  `);
@@ -27552,6 +27727,12 @@ function createServerSessionAdapter(params) {
27552
27727
  supportsThinking() {
27553
27728
  return params.session.supportsThinking();
27554
27729
  },
27730
+ get sessionName() {
27731
+ return params.session.sessionName;
27732
+ },
27733
+ setSessionName(name2) {
27734
+ params.session.setSessionName?.(name2);
27735
+ },
27555
27736
  listCommands() {
27556
27737
  const getCommands = params.extensionsResult.runtime.getCommands;
27557
27738
  return typeof getCommands === "function" ? getCommands() : [];