diff --git a/AGON.md b/AGON.md index b2135134..1ca23f33 100644 --- a/AGON.md +++ b/AGON.md @@ -42,6 +42,12 @@ npm run build # tsc -b --force npm run test # vitest run npm run typecheck # tsc -b +## Git Workflow — NEVER commit/push to main + +- NEVER commit or push directly to `main`/`master`. Always: feature branch → push → open PR. This applies to **Cesar/builder auto-commits too** — an autonomous build leaves work for a human merge gate; it does not land on main. +- Stage explicit paths; never `git add -A` in the shared working tree (it sweeps other sessions' WIP). +- Run the gate (`npm run kern:compile && npm run test`) green before committing; "done" from a builder is unverified until the gate passes. + ## Conventions - ESM only, .js extensions in imports diff --git a/packages/cli/src/generated/cesar/session.ts b/packages/cli/src/generated/cesar/session.ts index c3f697cb..49c90f7c 100644 --- a/packages/cli/src/generated/cesar/session.ts +++ b/packages/cli/src/generated/cesar/session.ts @@ -66,6 +66,7 @@ RULE 2b — INTAKE FLOW: Every normal turn includes ROUTING CONTEXT with INTAKE forge-slice / forge-full: define the target clearly first; forge only the hard slice or full broad task that benefits from competing implementations. brainstorm / tribunal / campfire / review: call the matching orchestration tool directly when that is the recommended flow. answer: answer directly. +RULE 2c — MODE IS A CHOICE, NOT A REFLEX (decisions, not retrieval): Before firing ANY orchestration mode, ask what the task actually needs, then pick the mode that fits — never the one your reflex reaches for. Orchestration (Brainstorm/Tribunal/Campfire/Council) exists to resolve TRADEOFFS and DECISIONS: competing approaches, contested correctness, hard-to-reverse calls. It is NOT a way to FIND an answer you can produce yourself by reading code. If the ask is a lookup, an analysis, or already in hand → answer directly; convening a panel to discover what one investigation would reveal burns the user's tokens and time. "Find me X / are there issues in Y / how does Z work" = investigate and answer, NOT brainstorm. Escalate by stakes and reversibility: QuickNero = one adversary · Tribunal = two-side debate · Council = whole-panel + chair · Forge = competitive build. When the user explicitly names a mode ("brainstorm it", "tribunal this"), honor it — this rule governs the turns where YOU own the routing choice. CRITICAL: You have orchestration modes as DIRECT TOOL CALLS. NEVER use Bash to run CLI commands for orchestration. Call the tools directly: @@ -177,7 +178,7 @@ RULE 10 — TURN CLOSURE: End every turn with one clear closing line so the user /** * Build the full Cesar system prompt with project context, engine list, and mode flags. */ -// @kern-source: session:162 +// @kern-source: session:163 export function buildCesarSystemPrompt(ctx: HandlerContext): string { const config = ctx.config; const cesarCwd = resolveWorkingDir(); @@ -355,19 +356,19 @@ export function buildCesarSystemPrompt(ctx: HandlerContext): string { return systemParts.join('\n\n'); } -// @kern-source: session:341 +// @kern-source: session:342 export const CESAR_SNAPSHOT_MSG_CHAR_CAP: number = 4000; -// @kern-source: session:343 +// @kern-source: session:344 export const CONFIDENCE_BLOCK_LIMIT: number = 2; -// @kern-source: session:345 +// @kern-source: session:346 export const SEARCH_NUDGE_THRESHOLD: number = 40; /** * Bound one message's text to CESAR_SNAPSHOT_MSG_CHAR_CAP with a truncation marker. Applied on BOTH snapshot paths (direct session history AND the chat-transcript fallback) so oversized content never floods Cesar's continuity context regardless of which path produced it. */ -// @kern-source: session:347 +// @kern-source: session:348 export function capSnapshotMessageContent(content: string): string { if (content.length <= CESAR_SNAPSHOT_MSG_CHAR_CAP) return content; return `${content.slice(0, CESAR_SNAPSHOT_MSG_CHAR_CAP)}\n… [${content.length - CESAR_SNAPSHOT_MSG_CHAR_CAP} chars truncated for Cesar context]`; @@ -376,7 +377,7 @@ export function capSnapshotMessageContent(content: string): string { /** * Build a normalized continuity snapshot. Prefer the session's internal history; fall back to the visible chat transcript. Per-message string content is capped on EITHER path so review/brainstorm spam (or a huge tool result) doesn't flood Cesar's context; tool_calls/tool_call_id and non-string content are preserved untouched. */ -// @kern-source: session:354 +// @kern-source: session:355 export function buildCesarConversationSnapshot(session: PersistentSession|null, chatSession: any): Array<{role:string,content:any,tool_calls?:any[],tool_call_id?:string}> { const directHistory = session?.getMessageHistory?.() ?? []; if (directHistory.length > 0) { @@ -409,7 +410,7 @@ export function buildCesarConversationSnapshot(session: PersistentSession|null, /** * Persist the active Cesar conversation before the session is discarded. */ -// @kern-source: session:379 +// @kern-source: session:380 export function saveCesarConversationSnapshot(session: PersistentSession|null, chatSession: any): void { if (!session) return; const snapshot = buildCesarConversationSnapshot(session, chatSession); @@ -424,7 +425,7 @@ export function saveCesarConversationSnapshot(session: PersistentSession|null, c /** * Build the onToolCall callback for API engines with native function calling. */ -// @kern-source: session:392 +// @kern-source: session:393 export function buildOnToolCall(ctx: HandlerContext, toolRegistry: ToolRegistry, config: any): ((name:string, args:Record, callId:string) => Promise) | undefined { const cwd = resolveWorkingDir(); const fsc = getProjectFileStateCache(cwd); @@ -728,7 +729,7 @@ export function buildOnToolCall(ctx: HandlerContext, toolRegistry: ToolRegistry, /** * Build the onApproval callback for engine tool approvals. Returns true to approve, false to deny silently, or a string to deny with a reason the engine can see. */ -// @kern-source: session:694 +// @kern-source: session:695 export function buildOnApproval(ctx: HandlerContext, engineId: string): (tool:string, command:string) => Promise { const engine = ctx.registry.get(engineId); return async (tool: string, command: string): Promise => { @@ -911,7 +912,7 @@ export function buildOnApproval(ctx: HandlerContext, engineId: string): (tool:st }; } -// @kern-source: session:878 +// @kern-source: session:879 export function normalizeCesarMcpServers(raw: unknown): Array> { const isRecord = (value: unknown): value is Record => !!value && typeof value === 'object' && !Array.isArray(value); @@ -945,7 +946,7 @@ export function normalizeCesarMcpServers(raw: unknown): Array>|undefined { if (!(config as any).cesarMcpEnabled) return undefined; @@ -969,7 +970,7 @@ export function loadCesarMcpServers(config: any, cwd: string): Array/mcp/index.js (see tsup.config.ts), so the published install is self-contained — no @kernlang/agon-mcp npm dependency. Resolution order: (0) the bundled sibling /mcp/index.js (the published, self-contained path), (1) node module resolution of @kernlang/agon-mcp (monorepo-via-symlink / legacy installs), (2) walk up to the repo root containing packages/mcp/dist/index.js (monorepo without a symlink), (3) the original relative guess as a last resort. `fromUrl` is for tests; defaults to this module's URL. */ -// @kern-source: session:961 +// @kern-source: session:962 export function resolveAgonMcpServerPath(fromUrl?: string): string { const raw = fromUrl ?? import.meta.url; // Accept either a file: URL (normal) or a bare path (defensive): fileURLToPath @@ -1035,7 +1036,7 @@ export function resolveAgonMcpServerPath(fromUrl?: string): string { /** * Single source of truth for which backend a Cesar engine will actually use. Honours config.cesarBackend preference ('auto' | 'cli' | 'api'). Pure — no side effects beyond registry lookups. Returns backend='none' when the engine has neither a usable binary nor an API key; callers decide how to handle that. */ -// @kern-source: session:993 +// @kern-source: session:994 export function resolveCesarBackend(ctx: HandlerContext, engineId?: string): { backend: 'cli'|'api'|'none', binaryPath: string, hasBinary: boolean, hasApi: boolean, engine: any } { const config = ctx.config; const cesarEngineId = engineId ?? (config as any).cesarEngine ?? config.forgeFixedStarter ?? 'claude'; @@ -1060,7 +1061,7 @@ export function resolveCesarBackend(ctx: HandlerContext, engineId?: string): { b return { backend: 'none', binaryPath: '', hasBinary, hasApi, engine }; } -// @kern-source: session:1019 +// @kern-source: session:1020 export async function ensureCesarSession(ctx: HandlerContext): Promise { const config = ctx.config; const cesarEngineId = (config as any).cesarEngine ?? config.forgeFixedStarter ?? 'claude'; diff --git a/packages/cli/src/kern/cesar/session.kern b/packages/cli/src/kern/cesar/session.kern index 324b6638..697861b5 100644 --- a/packages/cli/src/kern/cesar/session.kern +++ b/packages/cli/src/kern/cesar/session.kern @@ -50,6 +50,7 @@ RULE 2b — INTAKE FLOW: Every normal turn includes ROUTING CONTEXT with INTAKE forge-slice / forge-full: define the target clearly first; forge only the hard slice or full broad task that benefits from competing implementations. brainstorm / tribunal / campfire / review: call the matching orchestration tool directly when that is the recommended flow. answer: answer directly. +RULE 2c — MODE IS A CHOICE, NOT A REFLEX (decisions, not retrieval): Before firing ANY orchestration mode, ask what the task actually needs, then pick the mode that fits — never the one your reflex reaches for. Orchestration (Brainstorm/Tribunal/Campfire/Council) exists to resolve TRADEOFFS and DECISIONS: competing approaches, contested correctness, hard-to-reverse calls. It is NOT a way to FIND an answer you can produce yourself by reading code. If the ask is a lookup, an analysis, or already in hand → answer directly; convening a panel to discover what one investigation would reveal burns the user's tokens and time. "Find me X / are there issues in Y / how does Z work" = investigate and answer, NOT brainstorm. Escalate by stakes and reversibility: QuickNero = one adversary · Tribunal = two-side debate · Council = whole-panel + chair · Forge = competitive build. When the user explicitly names a mode ("brainstorm it", "tribunal this"), honor it — this rule governs the turns where YOU own the routing choice. CRITICAL: You have orchestration modes as DIRECT TOOL CALLS. NEVER use Bash to run CLI commands for orchestration. Call the tools directly: