diff --git a/docs/proto.md b/docs/proto.md index dd02ca5..67c19b9 100644 --- a/docs/proto.md +++ b/docs/proto.md @@ -6,7 +6,19 @@ This document describes the `cueme proto` command family. `cueme proto` injects the shared `protocol.md` into a specific agent file by composing: -`final_proto = prefix(agent) + "\n\n" + protocol.md` +`final_proto = prefix(agent) + "\n\n" + proto_block + runtime_block` + +`proto_block` and `runtime_block` are separate managed blocks: + +``` + +... protocol.md ... + + + +... runtime-specific guidance ... + +``` The injected content is managed between sentinel markers and may be overwritten by `cueme proto apply`. @@ -23,6 +35,8 @@ Required keys: - supports `~` and `%ENV%` expansions (e.g. `%APPDATA%`, `%USERPROFILE%`) - `cueme.proto.prefix`: map of prefix by `` - can be a string or string array (joined with `\n`) +- `cueme.proto.runtime`: map of runtime-specific guidance by `` + - can be a string or string array (joined with `\n`) - `cueme.proto.protocol_path`: absolute or relative path to the shared `protocol.md` - supports `~` and `%ENV%` expansions @@ -38,7 +52,11 @@ Example: "windows.windsurf": "%USERPROFILE%\\.codeium\\windsurf\\memories\\global_rules.md", "linux.vscode": "~/.config/Code/User/prompts/human_proto.md", - "linux.windsurf": "~/.codeium/windsurf/memories/global_rules.md" + "linux.windsurf": "~/.codeium/windsurf/memories/global_rules.md", + + "macos.codex": "~/.codex/AGENTS.md", + "windows.codex": "%USERPROFILE%\\.codex\\AGENTS.md", + "linux.codex": "~/.codex/AGENTS.md" }, "cueme.proto.prefix": { "vscode": [ @@ -46,8 +64,15 @@ Example: "applyTo: '**'", "---" ], + "codex": [], "windsurf": [] }, + "cueme.proto.runtime": { + "codex": [ + "When calling cueme cue/pause, set the tool wait timeout to 24h to avoid premature tool timeouts.", + "不要误解“更新 todo_list”的指令,因为你是 codex,你需要做的是:把 update_plan 当作 todo_list 来维护任务,而不是创建本地 todo_list 文件。" + ] + }, "cueme.proto.protocol_path": "~/path/to/protocol.md" } ``` @@ -60,6 +85,10 @@ Injected content is managed between these markers: ... managed content ... + + +... managed content ... + ``` Notes: @@ -101,6 +130,7 @@ Auto-detect (current platform only): - `vscode`: `.vscode/prompts/human_proto.md` (workspace) then platform user path - `windsurf`: `.codeium/windsurf/memories/global_rules.md` (workspace) then platform user path +- `codex`: uses `~/.codex/AGENTS.md` by default (not auto-detected) ### Helpers diff --git a/src/proto.js b/src/proto.js index eaddc98..a4935d5 100644 --- a/src/proto.js +++ b/src/proto.js @@ -4,9 +4,13 @@ const path = require('path'); const BEGIN_MARKER = ''; const END_MARKER = ''; +const RUNTIME_BEGIN_MARKER = ''; +const RUNTIME_END_MARKER = ''; const BEGIN_MARKER_RE = //; const END_MARKER_RE = //; +const RUNTIME_BEGIN_MARKER_RE = //; +const RUNTIME_END_MARKER_RE = //; function getPlatformKey() { const p = process.platform; @@ -146,6 +150,11 @@ function defaultPathMapTemplate() { out['windows.kiro'] = path.join(userProfile, '.kiro', 'steering', 'cueme_proto.md'); out['linux.kiro'] = path.join(home, '.kiro', 'steering', 'cueme_proto.md'); + // Codex (global) + out['macos.codex'] = path.join(home, '.codex', 'AGENTS.md'); + out['windows.codex'] = path.join(userProfile, '.codex', 'AGENTS.md'); + out['linux.codex'] = path.join(home, '.codex', 'AGENTS.md'); + return out; } @@ -154,10 +163,17 @@ function defaultConfigTemplate() { return { 'cueme.proto.path': defaultPathMapTemplate(), 'cueme.proto.prefix': { + codex: [], windsurf: [], vscode: ['---', 'applyTo: "**"', '---'], kiro: [], }, + 'cueme.proto.runtime': { + codex: [ + 'When calling cueme cue/pause, set the tool wait timeout to 24h to avoid premature tool timeouts.', + '不要误解“更新 todo_list”的指令,因为你是 codex,你需要做的是:把 update_plan 当作 todo_list 来维护任务,而不是创建本地 todo_list 文件。', + ], + }, 'cueme.proto.protocol_path': protocolPath, }; } @@ -259,6 +275,7 @@ function ensureDirForFile(filePath) { function buildFinalProto({ cfg, agent }) { const prefixMap = cfg['cueme.proto.prefix'] || {}; + const runtimeMap = cfg['cueme.proto.runtime'] || {}; const protocolPath = cfg['cueme.proto.protocol_path']; const prefixRaw = prefixMap[agent]; @@ -271,6 +288,18 @@ function buildFinalProto({ cfg, agent }) { throw new Error(`error: prefix not configured: cueme.proto.prefix["${agent}"]`); } + const runtimeRaw = runtimeMap[agent]; + let runtime = ''; + if (runtimeRaw == null || runtimeRaw === '') { + runtime = ''; + } else if (typeof runtimeRaw === 'string') { + runtime = runtimeRaw; + } else if (Array.isArray(runtimeRaw) && runtimeRaw.every((x) => typeof x === 'string')) { + runtime = runtimeRaw.join('\n'); + } else { + throw new Error(`error: runtime not configured: cueme.proto.runtime["${agent}"]`); + } + if (typeof protocolPath !== 'string' || protocolPath.trim().length === 0) { throw new Error('error: cannot read protocol.md'); } @@ -287,7 +316,7 @@ function buildFinalProto({ cfg, agent }) { throw new Error('error: cannot read protocol.md'); } - return { prefix, protocol }; + return { prefix, protocol, runtime }; } function resolveTargetPath({ cfg, agent }) { @@ -302,41 +331,114 @@ function resolveTargetPath({ cfg, agent }) { return path.isAbsolute(expanded) ? expanded : path.resolve(process.cwd(), expanded); } -function makeManagedBlock({ prefix, protocol, eol }) { +function makeProtoBlock({ protocol, eol }) { const normalizedProto = String(protocol || '').replace(/\r\n/g, '\n').replace(/\r/g, '\n'); const protoLines = normalizedProto.split('\n'); - const managedBlock = [BEGIN_MARKER, ...protoLines, END_MARKER].join(eol) + eol; - + return [BEGIN_MARKER, ...protoLines, END_MARKER].join(eol) + eol; +} + +function makeRuntimeBlock({ runtime, eol }) { + const runtimeText = String(runtime || '').replace(/\r\n/g, '\n').replace(/\r/g, '\n').trim(); + if (!runtimeText) return ''; + const runtimeLines = runtimeText.split('\n'); + return [RUNTIME_BEGIN_MARKER, ...runtimeLines, RUNTIME_END_MARKER].join(eol) + eol; +} + +function findBlockRange(text, beginRe, endRe) { + const beginMatch = text.match(beginRe); + const endMatch = text.match(endRe); + if (!beginMatch || !endMatch || endMatch.index <= beginMatch.index) return null; + return { beginIdx: beginMatch.index, endIdx: endMatch.index, endLen: endMatch[0].length }; +} + +function extractBlock({ text, range, eol }) { + let block = text.slice(range.beginIdx, range.endIdx + range.endLen); + const after = text.slice(range.endIdx + range.endLen); + if (after.startsWith(eol)) block += eol; + return block; +} + +function replaceBlock({ text, range, block, eol }) { + const before = text.slice(0, range.beginIdx); + const after = text.slice(range.endIdx + range.endLen); + const afterTrim = after.startsWith(eol) ? after.slice(eol.length) : after; + if (!block) return before + afterTrim; + return before + block + afterTrim; +} + +function appendBlock({ text, block, eol }) { + if (!block) return text; + let out = text || ''; + if (out && !out.endsWith(eol)) out += eol; + out += block; + return out; +} + +function makeCombinedBlock({ prefix, protoBlock, runtimeBlock, eol }) { + let block = protoBlock + (runtimeBlock || ''); if (prefix && prefix.trim()) { const normalizedPrefix = String(prefix).replace(/\r\n/g, '\n').replace(/\r/g, '\n'); - return normalizedPrefix + eol + eol + managedBlock; + return normalizedPrefix + eol + eol + block; } - - return managedBlock; + return block; } -function applyManagedBlock({ existing, prefix, protocol }) { - const eol = detectEol(existing); - const block = makeManagedBlock({ prefix, protocol, eol }); - - const beginMatch = existing.match(BEGIN_MARKER_RE); - const endMatch = existing.match(END_MARKER_RE); - if (beginMatch && endMatch && endMatch.index > beginMatch.index) { - const beginIdx = beginMatch.index; - const endIdx = endMatch.index; - const endLen = endMatch[0].length; +function applyManagedBlocks({ existing, prefix, protocol, runtime }) { + const eol = existing ? detectEol(existing) : os.EOL; + const protoBlock = makeProtoBlock({ protocol, eol }); + const runtimeBlock = makeRuntimeBlock({ runtime, eol }); + const hasProto = findBlockRange(existing, BEGIN_MARKER_RE, END_MARKER_RE); + const hasRuntime = findBlockRange(existing, RUNTIME_BEGIN_MARKER_RE, RUNTIME_END_MARKER_RE); + const hasAny = Boolean(hasProto || hasRuntime); + + if (!hasAny) { + const combinedBlock = makeCombinedBlock({ prefix, protoBlock, runtimeBlock, eol }); + return { + out: appendBlock({ text: existing, block: combinedBlock, eol }), + prefix_added: Boolean(prefix && prefix.trim()), + proto_action: 'added', + runtime_action: runtimeBlock ? 'added' : 'skipped', + }; + } - const before = existing.slice(0, beginIdx); - const after = existing.slice(endIdx + endLen); + let out = existing; + let proto_action = 'skipped'; + let runtime_action = 'skipped'; + + const protoRange = findBlockRange(out, BEGIN_MARKER_RE, END_MARKER_RE); + if (protoRange) { + const existingBlock = extractBlock({ text: out, range: protoRange, eol }); + if (existingBlock === protoBlock) { + proto_action = 'unchanged'; + } else { + out = replaceBlock({ text: out, range: protoRange, block: protoBlock, eol }); + proto_action = 'updated'; + } + } else { + out = appendBlock({ text: out, block: protoBlock, eol }); + proto_action = 'added'; + } - const afterTrim = after.startsWith(eol) ? after.slice(eol.length) : after; - return before + block + afterTrim; + const runtimeRange = findBlockRange(out, RUNTIME_BEGIN_MARKER_RE, RUNTIME_END_MARKER_RE); + if (runtimeRange) { + if (runtimeBlock) { + const existingBlock = extractBlock({ text: out, range: runtimeRange, eol }); + if (existingBlock === runtimeBlock) { + runtime_action = 'unchanged'; + } else { + out = replaceBlock({ text: out, range: runtimeRange, block: runtimeBlock, eol }); + runtime_action = 'updated'; + } + } else { + out = replaceBlock({ text: out, range: runtimeRange, block: '', eol }); + runtime_action = 'removed'; + } + } else if (runtimeBlock) { + out = appendBlock({ text: out, block: runtimeBlock, eol }); + runtime_action = 'added'; } - let out = existing; - if (!out.endsWith(eol)) out += eol; - out += block; - return out; + return { out, prefix_added: false, proto_action, runtime_action }; } function listAgents({ cfg }) { @@ -371,7 +473,7 @@ function protoLs() { function protoApply(agent) { const cfg = readConfigOrThrow({ auto_init: true }); const targetPath = resolveTargetPath({ cfg, agent }); - const { prefix, protocol } = buildFinalProto({ cfg, agent }); + const { prefix, protocol, runtime } = buildFinalProto({ cfg, agent }); let existing = ''; let exists = false; @@ -383,20 +485,22 @@ function protoApply(agent) { exists = false; } - const eol = exists ? detectEol(existing) : os.EOL; - const managedBlock = makeManagedBlock({ prefix, protocol, eol }); - - let out; - if (!exists) { - out = managedBlock; - } else { - out = applyManagedBlock({ existing, prefix, protocol }); - } + const { out, prefix_added, proto_action, runtime_action } = applyManagedBlocks({ + existing, + prefix, + protocol, + runtime, + }); ensureDirForFile(targetPath); fs.writeFileSync(targetPath, out, 'utf8'); - return `ok: applied to ${targetPath}`; + const parts = [ + `prefix=${prefix_added ? 'added' : 'skipped'}`, + `proto=${proto_action}`, + `runtime=${runtime_action}`, + ]; + return `ok: applied to ${targetPath} (${parts.join(', ')})`; } function protoRemove(agent) {