/** * Machinery shared by both variants of the implement-feature algorithm * (implement-feature-tdd.ts and implement-feature-basic.ts): the roles * container, execution routing (local vs. direct runs on the remote * runner hardware), the * contract-renegotiation mechanics around the fan-out, the join barrier, * real-API wiring with fake-vs-real verification, the mobile smoke phase, * and the loop helpers whose shape does not depend on tddMode. * * The verification loops (repo equivalence, fake-vs-real contract) live * here because they are IDENTICAL in both modes: their test is itself the * deliverable, so "test first vs. test alongside" does not apply to them. */ import type { AcceptanceTestAuthor } from "./acceptance-test-author.js"; import type { BackendAgent } from "./backend-agent.js"; import type { BootstrapRules } from "./bootstrap-rules.js"; import type { Bootstrapper } from "./bootstrapper.js"; import type { ClientAgent } from "./client-agent.js"; import type { CommandCatalog, CommandRunner } from "./commands.js"; import type { ContractAuthor } from "./contract-author.js"; import type { Escalation } from "./escalation.js"; import type { FeatureMemory } from "./feature-memory.js"; import type { RunHistory } from "./run-history.js"; import type { FlagsAdminAgent } from "./flags-admin-agent.js"; import type { FrontendAgent } from "./frontend-agent.js"; import type { GateRepair } from "./gate-repair.js"; import type { HandOff, Trunk } from "./hand-off.js"; import type { MobileAppAgent } from "./mobile-app-agent.js"; import { type ModelConfiguration, type WorkKind } from "./model-roster.js"; import type { ProjectSettings } from "./project-config.js"; import { type PromptCatalog, type WorkOrder } from "./prompts.js"; import type { Orienter } from "./orienter.js"; import type { RepoLayout } from "./repo-layout.js"; import type { RunCheckpoint, RunJournal } from "./run-journal.js"; import type { TreeCoordination } from "./coordination.js"; import { type RemoteJobName, type RemoteRunJudge } from "./remote-executor.js"; import type { CoverageReviewer } from "./reviewer.js"; import { type BackendWorkUnit, type ChecklistItem, type ClientPlatform, type ContractAmendment, type CoverageChecklist, type CoverageMap, type FeatureFile, type IterationBudgets, type Scenario, type OpenApiChange, type ScenarioSlice, type TddLoopName, type TestRun } from "./types.js"; export interface ImplementFeatureDeps { /** * WHERE everything lives in the repository. Every role implementation * is constructed over this layout and resolves all file operations * through it — no role hardcodes a path. The orchestrator itself never * touches files, so it only carries the layout to the roles. */ layout: RepoLayout; /** * WHICH MODEL does each kind of leaf work: the roster names the * backends per tier (hard/intermediate/easy), the policy maps work * kinds to tiers. Role implementations resolve every Claude Code * launch through this — e.g. domain design on the hard tier, * use cases on the intermediate tier, repositories on the easy tier. */ models: ModelConfiguration; /** * The known commands (migrations, code generation, suites, stacks). * The algorithm never invents shell commands — it runs catalog * entries through the CommandRunner. */ commands: CommandCatalog; commandRunner: CommandRunner; /** * Per-work-kind instructions; DEFAULT_PROMPTS when omitted. The * orchestrator resolves each step's prompt and model into a WorkOrder * and passes it to the role method — instructions are arguments, not * comments. */ prompts?: PromptCatalog; orienter: Orienter; acceptance: AcceptanceTestAuthor; contract: ContractAuthor; frontend: FrontendAgent; mobile: MobileAppAgent; flagsAdmin: FlagsAdminAgent; backend: BackendAgent; reviewer: CoverageReviewer; /** Budgeted repair of red gate commands — state fixes, never gate edits. */ gateRepair: GateRepair; escalation: Escalation; /** The project's committed autonomy record (.farketari/run-history.jsonl). */ runHistory: RunHistory; /** * Cross-run memory: the last run's terminal failure for a feature, so a * re-run does not start blind. Injected into the lanes on re-entry; cleared * when the feature completes. */ featureMemory: FeatureMemory; /** * Remote execution itself is deterministic composition over the * catalog's remote command group (runJobOnRunnerOnce); this role is * only the judgment it cannot make mechanically — interference vs. a * genuine failure on a code-bound step. */ remote: RemoteRunJudge; coordination: TreeCoordination; journal: RunJournal; trunk: Trunk; handOff: HandOff; budgets?: IterationBudgets; /** The bootstrap command's role (file operations + the judgment steps). */ bootstrapper: Bootstrapper; /** What of the template survives a bootstrap; the boilerplate rules when omitted. */ bootstrapRules?: BootstrapRules; /** * The project's non-merging settings from farketari.ts (name, client * narrowing, standing instructions, journal dir, trunk, escalation). * Absent when the run has no project config. */ settings?: ProjectSettings; } /** * The deps enriched with the validated, REQUIRED run input. Every * internal function takes THIS type, so the compiler itself guarantees * no step can execute without an example path — there is no optional * field and no runtime fallback. */ export type RunDeps = ImplementFeatureDeps & { examplePath: string; }; /** * Every UI client agent this run knows about. A project's farketari.ts * may NARROW the template's platforms (settings.clients) — a project * without a mobile app never runs mobile phases at all. */ export declare function clientAgents(deps: RunDeps): ClientAgent[]; /** * Resolve a step's WorkOrder: its instructions from the prompt catalog * and its model backend from the policy and roster. Every * model-performing role call receives one of these as its first argument. */ export declare function workOrderFor(deps: RunDeps, kind: WorkKind): WorkOrder; /** * The agents for the clients a slice actually targets (resolved from the * Gherkin tags by the Orienter). Every per-client phase of a slice runs * over THIS list, never over all agents. */ export declare function sliceClientAgents(deps: RunDeps, slice: ScenarioSlice): ClientAgent[]; export declare function isRunnerInfraSignal(output: string): boolean; export declare function looksLikeInfraFailure(output: string): boolean; export declare function runRemoteJob(deps: RunDeps, budgets: IterationBudgets, job: RemoteJobName, description: string): Promise; /** * Execute a client's e2e test(s). Local for EVERY client since the mobile * fake-backend suite moved in-process (RNTL over the FakeBackend) — no * device, no runner, no remote job. Device hardware remains an * ACCEPTANCE/SMOKE concern (runRemoteJob). */ export declare function runClientE2e(deps: RunDeps, budgets: IterationBudgets, client: ClientAgent, item?: ChecklistItem): Promise; /** * Run the backend and client fan-out work in parallel, renegotiating the * contract whenever any agent reports a gap: amend once for all sides, * regenerate, notify every agent, re-enter. On re-entry, checklist items * keep their covered status EXCEPT items whose verification the * amendment touched — applyContractChange un-covers those, so they are * redone against the new contract instead of shipping stale coverage. * The mode-specific work is passed in as functions. Returns the * finally-agreed contract. */ export declare function runFanOutWithRenegotiation(deps: RunDeps, budgets: IterationBudgets, slice: ScenarioSlice, initialContract: OpenApiChange, backendWork: (contract: OpenApiChange) => Promise, clientWork: (client: ClientAgent, contract: OpenApiChange) => Promise): Promise; /** * Run a catalog command; a red gate is routed to a BUDGETED repair * session before it can stop the run. Gate reds are usually state * problems (stale generated artifacts, orphans of an interrupted run, a * missing registration) that a session fixes in a turn or two — every * one of which used to kill the run at the first checkpoint. The repair * fixes the state, never the gate (the guardrail lives in the repair * prompt); a gate still red past the budget escalates with the ORIGINAL * failure, because recurrence means the gate found something structural. */ export declare function runCatalogCommand(deps: RunDeps, command: string, description: string): Promise; /** * The local gates every slice must pass IMMEDIATELY before its commit: * the whole-repo typecheck and the localization verifier. The suites are * already green by now; these catch what suites cannot — a type error in * a package no suite compiled, and a presenter or component whose * co-located localization catalog is missing a language, a key, or a * value. Cheap, deterministic, and the same checks the slice pipeline * runs first, so a red pipeline after a green gate really means drift. */ export declare function runPreCommitGates(deps: RunDeps, slice: ScenarioSlice): Promise; /** * THE TRUNK IS NEVER LEFT RED. A red slice pipeline gets budgeted repair * sessions armed with the failed jobs' actual logs (fetched from GitLab); * each fix is gated, checkpointed and pushed, and the fresh pipeline is * awaited again. When the budget runs dry the slice's commits are * REVERTED on the trunk (a forward revert — history keeps the work) so * main is green again, and only then does the run stop and flag. */ export declare function keepPipelineGreen(deps: RunDeps, budgets: IterationBudgets, slice: ScenarioSlice, firstRed: TestRun): Promise; /** * A mid-slice CHECKPOINT commit: the pre-commit gates first (the same * cheap deterministic checks the final commit gets), then commit and push * without awaiting the pipeline. Called only at points the tree is green * by construction — after the wip-marked spec, the additive contract, the * join barrier, the real-API wiring — so each one is an honest * integration point, not a snapshot of work in flight. */ export declare function checkpointCommit(deps: RunDeps, slice: ScenarioSlice, label: string): Promise; /** * Work preservation inside the fan-out: after every checklist item a * lane proves green, commit THAT LANE'S working set — the context-scope * paths of its implementation kind — so hours of parallel work never sit * uncommitted while sibling lanes are mid-red (a tree reset once cost a * night's fixes; never again). Mid-work subject (the guard and CI defer * to the slice's final commit); the trunk branch is not pushed — each * lane commit is a partial tree — but every one is mirrored off-box to * refs/farketari/wip/ (no pipeline runs there), and the next * barrier checkpoint pushes the branch for real, sweeping anything a * lane wrote outside its scope while chasing the compiler. */ /** * Probe one scenario on one client: does its acceptance test PASS ("pass"), * fail ("fail"), or is there no test at all ("absent")? The deterministic * signal behind the assessment — running the real test, not a model guess. */ export type ScenarioProbe = (scenario: Scenario, platform: ClientPlatform) => Promise<"pass" | "fail" | "absent">; /** * Classify every scenario as already-done or a gap, GAP-BIASED: a scenario is * "done" ONLY when its acceptance test passes on EVERY client it targets; * anything absent or failing on any target client is a "gap" to build. A * scenario with no resolved target client is a gap (nothing to prove it). * Pure over the injected probe/resolver so it is unit-tested without a stack. */ export declare function assessCoverage(scenarios: readonly Scenario[], clientsOf: (scenario: Scenario) => readonly ClientPlatform[], probe: ScenarioProbe): Promise; /** * Decide THIS run's slice plan and load its resume checkpoint — the single * entry point both variants use so the assess-vs-resume decision can never * drift between them. * * A surviving checkpoint means a prior run of this feature did not finish (a * finished run clears its journal). On resume we NEVER re-assess coverage: * a half-built slice's acceptance test passes against the fake, so assessment * would mark it "done" and drop it before its real-API wiring, @wip removal * and trunk commit ever run — the exact "reported done while unfinished" * failure the report exists to prevent. Instead we rebuild the saved plan * verbatim; the slice loop skips already-completed slices and resumes the * in-flight one from its last recorded phase, and each slice's inner judges * cheaply re-verify (not rebuild) work that already exists in the tree. * * A fresh run (no checkpoint) assesses what earlier FINISHED runs or hand * work already built, plans slices only for the gaps, and PERSISTS that plan * at once — so a death from that point on resumes it rather than re-assessing. */ export declare function planOrResumeSlices(deps: RunDeps, featureFilePath: string, feature: FeatureFile, instructions: readonly string[], report: FeatureRunReport): Promise<{ slices: ScenarioSlice[]; checkpoint: RunCheckpoint | null; }>; /** * Printed at the END of every run — on success AND on every stop/crash. A * run's terminal history event says WHAT happened; this says WHETHER THE * FEATURE IS FINISHED: which of the feature file's scenarios are implemented * and which are not. The feature file is the contract, so an unfinished run * MUST be impossible to mistake for a finished one — a partial run that reads * as success is the exact failure this closes. Populated by the orchestrator * as it goes; emitted from implementFeature's finally so it survives a * RunStopped throw and a crash alike. */ /** * Frame the prior run's failure as CONTEXT for this run's sessions. Appended * to the run instructions AFTER reconciliation (so it is never deny-screened — * it is memory, not a directive), it reaches every role, and the lane working * in that area recognizes its own locus in the text and does not re-hit the * same wall blind. */ export declare function priorRunMemoryNote(lastFailure: string): string; export interface FeatureRunReport { setFeature(feature: FeatureFile): void; /** The coverage assessment: which scenarios were already done at run start. */ assessed(map: CoverageMap): void; planned(slices: readonly ScenarioSlice[]): void; sliceCompleted(name: string): void; emit(): void; } export declare function featureRunReport(): FeatureRunReport; export declare function resolveScenarioClients(feature: FeatureFile, scenario: Scenario): ClientPlatform[]; export declare function laneProgressPreserver(deps: RunDeps, loop: TddLoopName, slice: ScenarioSlice): (item: ChecklistItem) => Promise; /** * The persistence ripple, run AFTER domain distillation so it is decided * against the FINAL domain shape. Steps (agent work interleaved with * catalog commands): * 1. read the snapshots (extending them where the domain gained fields) * 2. determine the repository methods needed * 3. determine whether the schema must change * 4. update the schema (if needed) * 5. generate the migration — commands.db.generateMigration * 6. apply the migrations — commands.db.applyMigrations * 7. write/update the SQL queries in layout.backend.sqlQueries * 8. regenerate typed query code — commands.db.generateQueryCode * then implement the methods in BOTH repositories * 9. prove fake and Postgres repositories equivalent — that is the * repo-equivalence loop the caller runs immediately after. */ export declare function runPersistenceRipple(deps: RunDeps, slice: ScenarioSlice): Promise; /** * Runs only after the use-case loop is fully green. The interactor is * hunted for imperative logic written in the domain language of the DSL; * each find is moved into the domain (existing aggregate method, new * method, value object, or a new aggregate sized by * aggregate-design-prompt.md) as a behavior-preserving refactor — the * unit suite must be green again after every single move. The loop closes * when the independent reviewer finds no domain language left in the * interactor: it should only orchestrate. */ export declare function distillUseCaseIntoDomain(deps: RunDeps, budgets: IterationBudgets, slice: ScenarioSlice, unit: BackendWorkUnit): Promise; export declare function joinBarrier(deps: RunDeps, budgets: IterationBudgets, slice: ScenarioSlice): Promise; /** * Steps 1–2 of the client contract lifecycle, run BEFORE the e2e work so * the contract test DEFINES the API the client is built against: * 1. author one contract test per operation, running against the FAKE; * 2. build the fake so those tests pass. * TDD authors the tests first and observes the suite RED before the fake * exists (a green-before-fake suite asserts nothing — escalate). Basic * collapses 1 and 2. The real backend is added to these SAME tests later, * in wireRealApi (step 5). Fake-vs-real contract tests are pure Node. */ export declare function authorContractTestsAndFake(deps: RunDeps, budgets: IterationBudgets, slice: ScenarioSlice, client: ClientAgent, contract: OpenApiChange, testFirst: boolean): Promise; export declare function wireRealApi(deps: RunDeps, budgets: IterationBudgets, slice: ScenarioSlice, client: ClientAgent, apiChange: OpenApiChange): Promise; export declare function updateAndRunMobileSmoke(deps: RunDeps, budgets: IterationBudgets, slice: ScenarioSlice): Promise; /** * The test-first acceptance discipline: the slice opens for this * platform only once its acceptance test has been observed failing * BECAUSE THE BEHAVIOR IS MISSING — not a typo, not a setup bug, and * never passing (a pass before implementation is a guardrail violation: * the test is not testing the new behavior). */ export declare function ensureAcceptanceRedForRightReason(deps: RunDeps, budgets: IterationBudgets, slice: ScenarioSlice, client: ClientAgent): Promise; /** * The tests-with-code acceptance discipline: the acceptance test was * authored as the slice's specification and is never judged before * hand-off — but now that the UI exists, its DRIVER must be proven to * execute (every step drives the UI without crashing). A wrong selector * or broken Given step found here is a genuine defect that must not * wait for the user. The test's verdict is deliberately ignored. */ export declare function ensureDriverExecutes(deps: RunDeps, budgets: IterationBudgets, slice: ScenarioSlice, client: ClientAgent): Promise; /** * Checklist loop for VERIFICATION tests (repo equivalence, fake-vs-real * contract), where writing the test and making it pass are one act — * identical in both tddMode variants. Each red cycle spends budget; the * independent reviewer decides when coverage is complete. */ export declare function runVerificationLoop(opts: { deps: RunDeps; budgets: IterationBudgets; loop: TddLoopName; slice: ScenarioSlice; buildChecklist: () => Promise; cycle: (item: ChecklistItem) => Promise; /** Lane-scoped work-preservation commit, called per green item. */ preserveProgress?: (item: ChecklistItem) => Promise; /** Reviewer-vs-writer arbiter for the review rounds (reviewCoverage). */ judgeAlreadyImplemented?: (item: ChecklistItem) => Promise; }): Promise; /** * One independent-review round for any checklist loop. Returns the missing * items to add, or null when the reviewer finds nothing (loop may close). * Escalates when the review-round budget is spent. */ export declare function reviewCoverage(opts: { deps: RunDeps; budgets: IterationBudgets; loop: TddLoopName; slice: ScenarioSlice; /** * The loops' already-implemented judge, reused as the ARBITER between * reviewer and writer: every finding is verified against the current * tree before the loop adopts it. Without arbitration, two sessions * can disagree forever about a case one of them half-covered — and * that stalemate escalated a live run over a wording nuance. */ judgeAlreadyImplemented?: (item: ChecklistItem) => Promise; /** * Scopes the review to what THIS loop-phase can close. The contract loop's * fake-authoring phase passes it so the reviewer judges FAKE coverage only * — real-backend parity is added by wireRealApi later, so demanding it here * would livelock the loop against a gap it structurally cannot close. */ reviewGuidance?: string; }, checklist: CoverageChecklist, roundsSoFar: number): Promise; /** Run a suite; while red, route the failure to its owning agent's fix. */ export declare function repairUntilGreen(suiteName: string, runSuite: () => Promise, fix: (run: TestRun) => Promise, budget: number, escalation: Escalation): Promise; /** Turn a thrown ContractGapDiscovered into a returned amendment. */ export declare function catchingContractGap(work: () => Promise): Promise;