/** * Implement-stage discipline signal collector. * * Runs at the implement → implemented transition (i.e. before /senai-approve * advances the run). Collects discipline signals: * 1. Test smell scan report (deterministic, via test-discipline.ts). * 2. Project coverage (read from the hard-coded `coverage/coverage-summary.json` * path — there is no `coveragePath` config field). * 3. Mission verification re-run (parses `` `## Verification`). * 4. Bugfix missions only: the brief's `## Regression test plan` must name * a real test file covered by the plan's `## Files` manifest. * * The collector is advisory by default; strict mode promotes blocking findings * to a hard gate. Strict mode is opt-in via the SENAI_TEST_DISCIPLINE_STRICT * environment variable, so this is safe to enable on day one and harden later. */ import { type Finding, type ScanReport } from "./discipline.js"; import { type ManifestDiff } from "./manifest.js"; import { type ArchRulesResult } from "./arch-rules-check.js"; import { type FrameworkRulesResult } from "./framework-rules-check.js"; import type { SenaiState } from "../core/state.js"; export interface VerificationStep { step: string; passed: boolean | null; output?: string; } /** Bugfix pack signal: the brief's `## Regression test plan` must name a * test file that exists on disk and is covered by the plan's `## Files` * manifest. Every problem is a blocking reason at the implement → * implemented gate (confirm-overridable, same style as verification * failures). Undefined on ImplementSignals for non-bugfix runs. */ export interface BugfixRegressionTestResult { /** Test file(s) named in the brief's `## Regression test plan`. */ candidates: string[]; /** Problems found; empty = signal passes. */ problems: string[]; } export interface ImplementSignals { implementArtifactsPresent: boolean; testSmellScan: ScanReport | null; coveragePct: number | null; coverageFloor: number; criticalPathCoveragePct: number | null; verificationSteps: VerificationStep[]; blockingFindings: Finding[]; actionableFindings: Finding[]; informationalFindings: Finding[]; /** Phase 3: planned create-files vs disk. Null when the plan has no * valid `## Files` manifest to diff against. */ manifestDiff: ManifestDiff | null; /** Phase 3: emitted arch-rules config run. Null when the check failed. */ archRules: ArchRulesResult | null; /** Phase 4: locked-framework rules run. Null when the check failed. */ frameworkRules: FrameworkRulesResult | null; /** Bugfix pack: regression-test mapping check. Undefined for non-bugfix * runs (optional so existing literal constructions keep compiling). */ bugfixRegressionTest?: BugfixRegressionTestResult; strictMode: boolean; } export declare const DEFAULT_COVERAGE_FLOOR = 80; /** * Read the `## Verification` section from a plan markdown body. * Returns a list of steps, each either a command (line starts with `$`) or a * manual check (any other line). Empty list if no section is found. */ export declare function parseVerificationSteps(planContent: string): string[]; export declare function isExecutableStep(step: string): boolean; /** * Count the verification steps in a plan body that the orchestrator can * actually execute. Prose-only or missing sections return 0. */ export declare function countExecutableVerificationSteps(planContent: string): number; /** * Hard gate for the planning → planned approval. Three checks, in order: * 1. The plan must contain at least one executable verification step. A * missing plan file or a missing / prose-only `## Verification` section * blocks the advance. * 2. The plan must carry a valid `## Files` manifest (parsed via * manifest.ts). A missing section or any validation error blocks the * advance with the exact errors listed as plan.md:. * 3. Framework lock: when the codebase profiler detected a framework or * the plan's `## Tech Stack` names one, the plan's `## Framework` field * must record the same framework id. A missing field, `none`, or a * contradicting id blocks the advance. An unknown id is a parse error. * When nothing is detected or named, the field is optional and `none` * means no framework checks run downstream. A framework locked in * `.pi/senai/framework.json` (chirpi) must match the field exactly — * a mismatch blocks the advance regardless of the other signals. */ export declare function checkPlanVerificationGate(cwd: string, runId: string): { blocked: boolean; reason: string | null; }; /** * Baseline verification steps derived from the project itself, independent of * what the planner wrote. Reads `/package.json` scripts: `build` maps to * `npm run build`, `test` maps to `npm test`. Empty when no package.json or * no matching scripts exist. */ export declare function deriveBaselineVerificationSteps(cwd: string): string[]; /** * Run a verification step via bash. Returns passed=true/false or passed=null * when the step was a manual check (does not start with a recognizable shell * command and the runner cannot execute it). */ export declare function runVerificationStep(step: string, cwd: string, timeoutMs?: number): Promise; /** * Read a coverage report from a known location. Today this is a stub: returns * null unless the project has a coverage file at `/coverage/coverage-summary.json`. * The implementer sub-agent runs the project's own coverage tool and writes * the result; this reader picks it up if present. */ export declare function readCoverageFromProject(cwd: string): number | null; /** Persisted implement/manifest-diff-report.md — the Phase-3 manifest diff * as a run artifact, so the approve gate and doctor can verify it like any * other stage artifact. Deterministic; no LLM involved. */ export declare function formatManifestDiffReport(diff: ManifestDiff | null): string; /** Persisted implement/arch-rules-report.md — the Phase-3 arch-rules run * as a run artifact. Tool-absent is recorded honestly as a skip. */ export declare function formatArchRulesReport(result: ArchRulesResult | null): string; /** Persisted implement/framework-rules-report.md — the Phase-4 framework * rules run as a run artifact. Skips (`none`, no field, no derivable rules) * and tool-absent runs are recorded honestly. */ export declare function formatFrameworkRulesReport(result: FrameworkRulesResult | null): string; /** Bugfix pack signal: the brief's `## Regression test plan` must name a * test file that exists on disk and is covered by the plan's `## Files` * manifest. Returns undefined for non-bugfix runs. */ export declare function checkBugfixRegressionTest(cwd: string, state: SenaiState): BugfixRegressionTestResult | undefined; /** * Collect the three discipline signals for the implement stage. * Failures inside any individual collector must not throw — return null * for that signal so the other signals still surface. */ export declare function collectImplementSignals(cwd: string, state: SenaiState): Promise; /** * Format the signals as a short notification string for the approval summary. * Pure — no side effects, no I/O. */ export declare function formatImplementSignals(signals: ImplementSignals): string; /** * Collects the blocking reasons for an implement → implemented advance. * Actual enforcement: /senai-approve shows these reasons and asks the user to * confirm — the user MAY override ("Advance anyway?"). The only no-override * gate is checkPlanVerificationGate (planning → planned). In non-strict mode, * only verification failures, manifest diff violations, tool-enforced * arch/framework rule violations, and bugfix regression-test problems * contribute reasons — coverage and smell findings are advisory. */ export declare function signalsBlockAdvance(signals: ImplementSignals): { blocked: boolean; reasons: string[]; };