/** * PRI-467 — safeReadIntentDoc: plugin I/O wrapper for reading INTENT.md. * * Reads `.principles/INTENT.md` with: * - Feature flag check FIRST (SPEC §12: flag off → flag_disabled without fs) * - TTL + mtime cache (60s TTL, mtime check) mirroring prompt.ts cachedReadFile * - 32KB size cap (INTENT_MAX_BYTES) * - Never throws — all errors return structured `reason` + `nextAction` * * Trust boundary (SPEC §12.2): * - Raw content is returned for the pure builder to escape; this reader does * NOT escape or bound the content for prompt injection. The pure builder * `buildIntentFrictionBlock` handles escaping + bounding. * * ERR checklist: * EP-01 / ERR-001, ERR-005: raw content validated with typeof, never `as` * EP-02 / ERR-025, ERR-070: production path; plugin I/O file in the whitelist * EP-03 / ERR-002, ERR-014: every degraded path returns structured reason + nextAction * EP-09: tests use real fs writes in temp dirs * * Architecture: this file is I/O (fs, path). It is whitelisted in * architecture-regression.test.ts KNOWN_PLUGIN_CORE_FILES. */ import { type IntentDocSections, type IntentDocWarning, type IntentLang } from '@principles/core/runtime-v2'; export type SafeReadIntentDocReason = 'flag_disabled' | 'not_found' | 'oversized' | 'read_error'; export interface IntentDoc { /** Raw INTENT.md content (unescaped — caller escapes for prompt injection). */ raw: string; /** Parsed sections from the raw content. */ sections: IntentDocSections; /** SHA-256 content hash for deduplication and audit. */ contentHash: string; /** Absolute path to the INTENT.md file. */ path: string; /** ISO timestamp when the doc was read. */ readAt: string; /** Validation warnings (missing/empty/too_vague sections). */ warnings: IntentDocWarning[]; } export interface SafeReadIntentDocResult { /** True when the doc was successfully read and parsed. */ ok: boolean; /** True when the INTENT.md file exists on disk. */ found: boolean; /** True when the intent_engineering flag is enabled. */ flagEnabled: boolean; /** The parsed IntentDoc, present only when ok=true. */ doc?: IntentDoc; /** Structured reason for a degraded path (present when ok=false). */ reason?: SafeReadIntentDocReason; /** Next action for the operator (present when ok=false). */ nextAction?: string; /** Validation warnings (always present, empty when no warnings). */ warnings: IntentDocWarning[]; } /** * Reset the intent doc cache for test isolation. * * - No args: clear the whole cache. * - workspaceDir only: delete all entries for that workspace (any lang). * Keys are formatted as `${workspaceDir}:${lang}`, so we match by prefix. * - workspaceDir + lang: delete the single `${workspaceDir}:${lang}` entry. * * Note: prompt.ts resetPromptStateForTest passes workspaceDir only — the * previous signature treated it as a literal cache key, which never matched * the `${workspaceDir}:${lang}` format and silently left entries behind. */ export declare function resetIntentDocCacheForTest(workspaceDir?: string, lang?: IntentLang): void; /** * Safely read INTENT.md for prompt injection. * * Contract (SPEC §12): * 1. Flag check FIRST via loadFeatureFlagFromConfig. Flag off → flag_disabled * WITHOUT any fs access to INTENT.md. * 2. Flag on → check TTL + mtime cache. If cached and fresh → return cached. * 3. Otherwise → stat the file (oversized check), read, parse, validate, cache. * 4. Never throws — all errors return structured reason + nextAction. * * @param workspaceDir - Absolute path to the workspace root * @param lang - Mandatory language code ('zh-CN' | 'en'); selects INTENT.${lang}.md * @param options - Optional logger for debug-level diagnostics * @returns SafeReadIntentDocResult (never throws) */ export declare function safeReadIntentDoc(workspaceDir: string, lang: IntentLang, options?: { logger?: { debug?: (msg: string) => void; warn?: (msg: string) => void; }; }): SafeReadIntentDocResult;