/** * `peaks code orchestrator-can-do`. * * Encodes the 2026-08-05 lesson (`.peaks/memory/2026-08-05-peaks-code- * orchestrator-capability-misjudgment.md`): the peaks-code orchestrator * MUST NOT Edit/Write `src/` files directly, but MUST delegate via * `peaks sub-agent dispatch`. The decision "can this slice run in the * current session" must be a structured probe — not a vibes call. * * The probe answers 4 boundary questions: * Q1 — Is the change to source code? (keywords: src/, *.ts, *.tsx, * *.js, package.json, tsconfig, workflows/). If yes → NOT a * blocker; orchestrator delegates to sub-agent. * Q2 — Can a sub-agent be dispatched? (probe `peaks sub-agent * dispatch --role rd --help`). If no → blocker. * Q3 — Does the slice require user decisions? (keywords: design, * decide, ?, 选择, 决定). If yes → soft warning, NOT a blocker * (the LLM should AskUserQuestion, which is cheap). * Q4 — Is context usage sustainable? (probe `peaks code context-now * --json`). ratio ≥ 0.95 → WARNING (red-line, ask-and-wait); * ≥ 0.85 → WARNING (pre-compact band). Never a blocker — see the * comment on the Q4 branch in `buildOrchestratorCanDoResult`, and * `evaluateCompactTrigger`, which is the face this one must agree with. * * Decision rule: * canDoInSession === (blockers.length === 0) * * Concrete suggestion when canDoInSession=true and slice touches * source code: * `peaks sub-agent dispatch rd --prompt "" --request-id * --project . --batch-id ` * * Pure-function module. The CLI shim (code-orchestrator-can-do.ts) * adapts the envelope into the program's `ResultEnvelope` shape. * * Slice c2w1 (strict-remediation-abc) hoisted two cohesive halves out of this * file into siblings, both re-exported here so nothing else moved: * `./orchestrator-can-do-probes.js` (the Q2/Q4 subprocess probes) and * `./orchestrator-can-do-advisories.js` (the Q1/Q2/Q3/Q4 message builders, * incl. the 0.95 / 0.85 thresholds). */ import { probeContextRatio, probeSubAgentAvailable, type ContextProbe } from './orchestrator-can-do-probes.js'; import { ORCHESTRATOR_PRECOMPACT_RATIO, ORCHESTRATOR_REDLINE_RATIO } from './orchestrator-can-do-advisories.js'; export { probeContextRatio, probeSubAgentAvailable }; export { ORCHESTRATOR_PRECOMPACT_RATIO, ORCHESTRATOR_REDLINE_RATIO }; export type { ContextProbe }; /** Source-code keywords that signal "do NOT Edit/Write directly". */ export declare const SOURCE_CODE_KEYWORDS: readonly string[]; /** * When ANY of these substrings appears in the slice-spec, the orchestrator * MUST NOT Edit/Write directly; the probe returns `canDoInSession: false` * with `blockers: ["requires-sub-agent-dispatch"]` to force * `peaks sub-agent dispatch rd`. The hook (`pre-tool-code-gate.sh`) * enforces the same deny at the PreToolUse layer. * * The allow-list (.peaks/**, .peaks/_runtime/**, skill files, docs/**) * is checked by the hook, NOT by this probe — the probe is content-side * (slice-spec text) and the hook is file-side (resolved path on Edit/Write). */ export declare const HARD_BLOCKED_PATH_FAMILIES: readonly string[]; /** Decision-marker keywords that signal "needs user AskUserQuestion". */ export declare const DECISION_KEYWORDS: readonly string[]; export interface OrchestratorCanDoInput { readonly sliceSpec: string; readonly projectRoot: string; /** * Test seam — caller injects probe results. Production CLI builds * these via `probeSubAgentAvailable` + `probeContextRatio`. When the * test seam is set, the CLI's actual probes are skipped. */ readonly probeSubAgentAvailable?: () => Promise; readonly probeContextRatio?: () => Promise; } export interface OrchestratorCanDoResult { /** canDoInSession === (blockers.length === 0). */ readonly canDoInSession: boolean; readonly blockers: readonly string[]; readonly warnings: readonly string[]; readonly suggestions: readonly string[]; readonly contextRatio: number; readonly subAgentAvailable: boolean; /** Diagnostic — which of the 4 boundary questions fired. */ readonly q1SourceCodeTouched: boolean; readonly q1HardBlockedPath: boolean; readonly q2SubAgentAvailable: boolean; readonly q3RequiresUserDecision: boolean; readonly q4ContextRatio: number; } export declare class OrchestratorCanDoError extends Error { readonly code: 'MISSING_SLICE_SPEC' | 'PROBE_FAILED'; constructor(message: string, code: 'MISSING_SLICE_SPEC' | 'PROBE_FAILED'); } /** * Q1: does the slice touch source code? Pure keyword scan over * the slice-spec string. Case-insensitive substring match. */ export declare function detectSourceCodeTouched(sliceSpec: string): boolean; /** * any hard-blocked path family? Pure substring match. When true, the * probe returns `canDoInSession: false` with `requires-sub-agent-dispatch`. * This is the LLM-side complement to the `pre-tool-code-gate.sh` hook * (which checks the actual Edit/Write/MultiEdit target path). */ export declare function detectHardBlockedPath(sliceSpec: string): boolean; /** * Q3: does the slice require user decisions? Pure keyword scan * over the slice-spec string. */ export declare function detectRequiresUserDecision(sliceSpec: string): boolean; /** * Build the structured OrchestratorCanDoResult. Pure over the 4 Q * signals + sliceSpec. Decision rule is: canDoInSession === !blockers. */ export declare function buildOrchestratorCanDoResult(input: OrchestratorCanDoInput, signals: { q1SourceCodeTouched: boolean; q1HardBlockedPath: boolean; q2SubAgentAvailable: boolean; q3RequiresUserDecision: boolean; q4ContextRatio: number; }): OrchestratorCanDoResult; /** * Evaluate a slice-spec end-to-end. Probes Q2/Q4 via subprocess * (overridable via test seams in `input`). Q1/Q3 are pure. */ export declare function evaluateOrchestratorCanDo(input: OrchestratorCanDoInput): Promise;