diff --git a/AGENTS.md b/AGENTS.md index 302b2b0a..aa52a031 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,6 +13,11 @@ This guide is for AI agents and human operators recovering context in the Graft Do not audit the repository by recursively walking the filesystem. Follow the authoritative manifests: +Design packets come first. Before implementation, repair, or RED/GREEN work on +a backlog item, pull the work into `docs/design/` and make the hill, acceptance +criteria, playback questions, and non-goals explicit. Implementation starts +from that packet, not from an unrecorded chat plan. + ### 1. The Entrance - **`README.md`**: Public front door, core value prop, and quick start. - **`GUIDE.md`**: Orientation, fast path, and system orchestration. diff --git a/CHANGELOG.md b/CHANGELOG.md index bd2ffad5..b6cb6b3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/). ## [Unreleased] +### Changed + +- **Structural reading boundary**: `graft_review` impact counts and + `graft_dead_symbols` now read through a Graft-owned + `StructuralReadingPort` with explicit Continuum-native vs translated + git-warp evidence status, preserving existing public response shapes. + +### Fixed + +- **Structural reading residual posture**: Reference-count readings now + report a partial residual posture when committed import-scan fallback + evidence is unavailable after a zero-count WARP graph reading. + ## [0.8.0] - 2026-05-13 ### Added diff --git a/METHOD.md b/METHOD.md index c3b7fa34..53320734 100644 --- a/METHOD.md +++ b/METHOD.md @@ -8,6 +8,8 @@ The Graft work doctrine: A backlog, a loop, and honest bookkeeping. - **The filesystem is the coordination layer.** Directories are priorities; filenames are identities; moves are decisions. - **Tests are the executable spec.** Design names the problem; tests prove the answer. - **Reproducibility is the definition of done.** Results must be re-runnable proof, not static artifacts. +- **Design packets come first.** Every implementation cycle starts by making + the design packet explicit before RED/GREEN work begins. ## Structure @@ -38,7 +40,8 @@ The Graft work doctrine: A backlog, a loop, and honest bookkeeping. stateDiagram-v2 direction LR [*] --> Pull: asap/ - Pull --> Branch: cycle/ + Pull --> Design: docs/design/ + Design --> Branch: cycle/ Branch --> Red: failing tests Red --> Green: passing tests Green --> Retro: findings/debt @@ -46,12 +49,15 @@ stateDiagram-v2 Ship --> [*] ``` -1. **Pull**: Move an item from `asap/` to `docs/design/`. -2. **Branch**: Create `cycle/`. -3. **Red**: Write failing tests based on the design's playback questions. -4. **Green**: Implement the solution until tests pass. -5. **Retro**: Document findings and follow-on debt in the cycle doc. -6. **Ship**: Open a PR to `main`. Update `BEARING.md` and `CHANGELOG.md` after merge. +1. **Pull**: Select the backlog item from `asap/` or `up-next/`. +2. **Design**: Create or update the `docs/design/` packet first. The packet + must name the hill, acceptance criteria, playback questions, non-goals, and + the expected test strategy before implementation begins. +3. **Branch**: Create `cycle/`. +4. **Red**: Write failing tests based on the design's playback questions. +5. **Green**: Implement the solution until tests pass. +6. **Retro**: Document findings and follow-on debt in the cycle doc. +7. **Ship**: Open a PR to `main`. Update `BEARING.md` and `CHANGELOG.md` after merge. ## Naming Convention Backlog and cycle files follow: `_.md` diff --git a/docs/design/CORE_continuum-structural-reading-port.md b/docs/design/CORE_continuum-structural-reading-port.md new file mode 100644 index 00000000..5831a51c --- /dev/null +++ b/docs/design/CORE_continuum-structural-reading-port.md @@ -0,0 +1,275 @@ +--- +title: "Continuum-shaped structural reading port" +legend: "CORE" +cycle: "CORE_continuum-structural-reading-port" +source_backlog: "docs/method/backlog/up-next/CORE_continuum-structural-reading-port.md" +--- + +# Continuum-shaped structural reading port + +Source backlog item: `docs/method/backlog/up-next/CORE_continuum-structural-reading-port.md` +Legend: CORE + +## Sponsors + +- Human: Backlog operator +- Agent: Implementation agent + +These labels are abstract roles. In this design, `user` means the served +perspective, like in a user story, not a literal named person or specific agent +instance. + +## Hill + +By the end of this cycle, Graft has one explicit structural reading boundary: +`StructuralReadingPort`. + +The port accepts Continuum-shaped boundary concepts, but it does not require +every backing substrate to publish native Continuum artifacts. Existing +git-warp committed-history reads must sit behind this port and be marked as +translated, non-Continuum-native evidence. Future Echo or other +Continuum-producing runtimes may enter through the same boundary as +Continuum-native evidence only when they provide real Continuum witness +artifacts. + +The phrase to preserve is: + +> Continuum-shaped, not Continuum-native. + +## Playback Questions + +### Human + +- [ ] Can a human point to one Graft-owned structural read boundary instead of + finding direct git-warp reads scattered through MCP tools and renderers? +- [ ] Can a human tell whether a reading is Continuum-native or merely + translated into a Continuum-compatible shape? +- [ ] Does the design prevent git-warp commit/range evidence from being + misrepresented as a Continuum witness? +- [ ] Can a future Echo-backed implementation plug in without changing the + public review, dead-symbol, or structural rendering contracts? + +### Agent + +- [ ] Does `src/ports/structural-reading.ts` define the only Graft-facing + structural read port and evidence union? +- [ ] Does the git-warp adapter mark every committed-history reading as + `translated-substrate` with `nativeContinuumWitness: false`? +- [ ] Does at least one deterministic fixture-backed test prove the + `continuum-native` evidence branch? +- [ ] Do `graft_review` and `graft_dead_symbols` consume normalized Graft + structural payloads instead of calling substrate-specific facts directly? +- [ ] Do public API, CLI, MCP, and renderer outputs remain compatible unless a + later design packet explicitly changes their schemas? + +## Doctrine + +Graft may normalize readings into Continuum-compatible shape. Only +Continuum-producing runtimes may claim Continuum-native witnesshood. + +The port must model evidentiary status as data, not prose: + +```ts +type StructuralReadingEvidence = + | ContinuumNativeEvidence + | TranslatedSubstrateEvidence; + +type ContinuumNativeEvidence = { + kind: "continuum-native"; + envelope: ReadingEnvelope; + witness?: WitnessedSuffixShell; +}; + +type TranslatedSubstrateEvidence = { + kind: "translated-substrate"; + substrate: "git-warp"; + basis: GitWarpCommittedBasis; + evidence: GitWarpEvidence; + nativeContinuumWitness: false; +}; +``` + +The `nativeContinuumWitness: false` marker is deliberately ugly. It makes fraud +hard at the type boundary. + +## Boundary Shape + +The first Graft-owned port should be narrower than a generic graph database and +more explicit than today's direct `WarpContext` helper calls: + +- basis identity: committed Git history, live frontier, or imported runtime + envelope +- observation request identity: the review, symbol, or structural query being + asked +- reading kind: reference impact, dead symbols, symbol history, structural test + coverage, or later live-frontier projection +- freshness: current, stale, or incomparable +- residual posture: complete, partial, plural, budget-limited, rights-limited, + or unavailable +- evidence status: Continuum-native or translated substrate +- typed Graft payload: the structural fact consumed by API, CLI, MCP, or + renderer code + +The implementation should begin with only the payloads needed by the current +slice: + +```ts +interface StructuralReadingPort { + countSymbolReferences(request: SymbolReferenceReadingRequest): + Promise>; + findDeadSymbols(request: DeadSymbolsReadingRequest): + Promise>; +} +``` + +Additional methods should be added only as existing surfaces are moved behind +the boundary. The port is not a license to invent a broad, speculative +abstraction. + +## Evidence Types + +Graft should define local structural DTOs rather than importing Echo, +git-warp, or warp-ttd concrete runtime types through API and MCP surfaces. + +The first type family should be: + +- `StructuralReadingEvidence` +- `ContinuumNativeEvidence` +- `TranslatedSubstrateEvidence` +- `StructuralReadingResult` +- `StructuralReadingFreshness` +- `StructuralReadingResidualPosture` +- `GitWarpCommittedBasis` +- `GitWarpEvidence` + +`ContinuumNativeEvidence` may use local structural references for Continuum +artifacts until generated Continuum contracts are ready in this repository. The +important invariant is that native evidence must carry a Continuum reading +envelope, and translated git-warp evidence must carry `nativeContinuumWitness: +false`. + +## First Adapter + +The first adapter is a git-warp committed-history adapter: + +```text +MCP / CLI / API use case + -> StructuralReadingPort + -> git-warp committed-history adapter + -> translated-substrate evidence + -> normalized Graft payload +``` + +It should call existing WARP helpers rather than rewriting structural logic: + +- `countSymbolReferencesFromGraph(...)` for review impact counts +- `countNamedImportReferencesAtRef(...)` as the current committed-history + fallback for review impact counts +- `findDeadSymbols(...)` for dead-symbol readings + +The fallback import-reference count is still translated substrate evidence. It +is based on committed repository content at a Git ref, not on a native +Continuum witness. + +## First Consumers + +Move the smallest set of substrate-specific consumers behind the port: + +- `graft_review`: reference impact counts should come from + `StructuralReadingPort.countSymbolReferences(...)` +- `graft_dead_symbols`: dead-symbol payloads should come from + `StructuralReadingPort.findDeadSymbols(...)` + +The first implementation should preserve the public response bodies for +`graft_review`, `struct_review`, and `graft_dead_symbols`. The normalized +reading and evidence metadata can remain inside the Graft boundary until a +separate API/schema design packet chooses how to expose it. + +Renderers must continue to consume the existing public models. They should not +learn git-warp facts directly. + +## Continuum-Native Fixture + +This slice must include one deterministic unit test proving that the +Continuum-native evidence branch is representable and cannot be confused with +translated git-warp evidence. + +Fixture requirements: + +- construct a `StructuralReadingResult` with + `evidence.kind === "continuum-native"` +- include a Continuum-shaped `envelope` +- include a Continuum-shaped `witness` +- assert that translated substrate evidence has + `nativeContinuumWitness === false` +- avoid wall-clock timing, randomness, stdout/stderr assertions, and code + introspection as behavioral proof + +The fixture may be local and minimal until generated Continuum runtime-boundary +artifacts are consumable from Graft. + +## Graft Registry Role + +This cycle should preserve Graft's proposed Continuum registry role without +making Graft the semantic owner of shared Continuum families: + +| Repo | Registry role | Must not become | +| :--- | :--- | :--- | +| Graft | Structural observer and review engine; consumes runtime-boundary, receipt, settlement, neighborhood, and observer families; produces code-aware structural reading payloads. | A runtime implementation, debugger product, shadow Continuum semantic owner, or permanent host-normalization layer. | + +If a Graft structural payload becomes useful to another repo, promote that +family into Continuum through the normal family-promotion process instead of +copying local DTOs across repositories. + +## Accessibility and Assistive Reading + +- Linear truth / reduced-complexity posture: Evidence status must be visible in + one structural type instead of explained through scattered comments. +- Non-visual or alternate-reading expectations: The port and fixture tests + should read as plain source and plain markdown. No diagrams are required to + understand whether evidence is native or translated. + +## Localization and Directionality + +- Locale / wording / formatting assumptions: Stable contract labels such as + `continuum-native`, `translated-substrate`, and `nativeContinuumWitness` + should not rely on English prose to carry semantics. +- Logical direction / layout assumptions: The adapter direction should remain + linear: surface code asks Graft for a reading; Graft chooses an adapter; the + adapter labels its evidence. + +## Agent Inspectability and Explainability + +- What must be explicit and deterministic for agents: Agents must be able to + inspect a `StructuralReadingResult` and determine the payload, basis, + freshness, residual posture, and evidence status without opening an adapter. +- What must be attributable, evidenced, or governed: Tests must prove that + git-warp readings are translated substrate evidence and that the + Continuum-native branch is separate. + +## Non-goals + +- [ ] Replacing git-warp in this cycle. +- [ ] Adding a direct Echo dependency before the port boundary exists. +- [ ] Modeling git-warp commit, range, graph, or import-reference evidence as a + Continuum witness. +- [ ] Making Graft the semantic owner of Continuum runtime-boundary nouns. +- [ ] Making warp-ttd the structural-review engine. +- [ ] Exposing new evidence metadata through MCP/API/CLI response schemas in + the first implementation slice. +- [ ] Migrating every WARP read path, including precision, churn, blame, stale + docs, and local-history graph reads, in one cycle. +- [ ] Solving the slice-first observer-geometry backlog while this port is + being introduced. + +## Expected Artifacts + +- `src/ports/structural-reading.ts` with the Graft-owned port, evidence union, + status types, and payload contracts. +- A git-warp committed-history adapter behind the port. +- `graft_review` routed through the port for symbol reference impact counts. +- `graft_dead_symbols` routed through the port for dead-symbol readings. +- Deterministic unit coverage for translated git-warp evidence and the + Continuum-native evidence branch. +- Public response compatibility coverage for the touched MCP/CLI surfaces. +- No tag, release, or public schema change as part of this slice. diff --git a/docs/method/backlog/bad-code/CLEAN_remaining-structural-warp-reads-bypass-structural-reading-port.md b/docs/method/backlog/bad-code/CLEAN_remaining-structural-warp-reads-bypass-structural-reading-port.md new file mode 100644 index 00000000..d86d7156 --- /dev/null +++ b/docs/method/backlog/bad-code/CLEAN_remaining-structural-warp-reads-bypass-structural-reading-port.md @@ -0,0 +1,52 @@ +--- +title: "Remaining structural WARP reads bypass StructuralReadingPort" +feature: core +kind: bad-code +legend: CLEAN +lane: bad-code +priority: 2 +effort: M +status: open +reported: 2026-05-15 +--- + +# Remaining structural WARP reads bypass StructuralReadingPort + +## Problem + +The first Continuum-shaped structural reading port slice routes +`graft_review` reference-impact counts and `graft_dead_symbols` through +`StructuralReadingPort`, but other structural WARP-backed surfaces still call +WARP helpers directly from tool or WARP-specific modules. + +Known remaining paths include: + +- `graft_blame` / symbol history +- `graft_difficulty` +- structural log and churn tools +- precision/code lookup paths that expose indexed WARP facts + +That is acceptable for the first port slice, but it means the long-term rule is +not fully true yet: Graft does not have every structural read behind a single +Continuum-shaped evidence boundary. + +## Risk + +If these paths remain as direct WARP reads, future Echo or Continuum-native +adapter work can drift surface by surface. That makes it easier to accidentally +treat Continuum-shaped compatibility data as native witness evidence. + +## Desired Outcome + +Move the remaining structural WARP-backed surfaces behind +`StructuralReadingPort` in follow-up slices, preserving public response schemas +unless a separate design packet explicitly changes them. + +## Acceptance Criteria + +- Symbol history/blame readings carry explicit structural evidence status. +- Refactor difficulty readings carry explicit structural evidence status. +- Structural log/churn readings carry explicit structural evidence status. +- Precision/code lookup paths either consume normalized structural readings or + are explicitly documented as non-structural WARP utilities. +- No git-warp committed-history evidence is modeled as a Continuum witness. diff --git a/docs/method/backlog/dependency-dag.dot b/docs/method/backlog/dependency-dag.dot index baa7fa2c..c83b9d4f 100644 --- a/docs/method/backlog/dependency-dag.dot +++ b/docs/method/backlog/dependency-dag.dot @@ -47,6 +47,12 @@ digraph backlog { v0_8_0_WARP_symbol_history_timeline [label="WARP-symbol-history-timeline - S" fillcolor="#C8E6C9" penwidth=2] } + subgraph cluster_bad_code { + label="bad-code (1)" labeljust=l fontsize=9 fontcolor="#555555" + style=rounded color="#cccccc" bgcolor="#fafafa" + bad_code_CLEAN_remaining_structural_warp_reads_bypass_structural_reading_port [label="CLEAN-remaining-structural-warp-reads-bypass-structural-reading-port - M" fillcolor="#F6B3B3" penwidth=2] + } + subgraph cluster_cool_ideas { label="Cool Ideas (74)" labeljust=l fontsize=9 fontcolor="#555555" style=rounded color="#cccccc" bgcolor="#fafafa" diff --git a/docs/method/backlog/dependency-dag.svg b/docs/method/backlog/dependency-dag.svg index 24b29f68..a2277e6f 100644 --- a/docs/method/backlog/dependency-dag.svg +++ b/docs/method/backlog/dependency-dag.svg @@ -4,12 +4,12 @@ - + backlog - -Active backlog graph generated from docs/method/backlog/*/*.md + +Active backlog graph generated from docs/method/backlog/*/*.md cluster_up_next @@ -21,24 +21,29 @@ v0.8.0 (26) +cluster_bad_code + +bad-code (1) + + cluster_cool_ideas Cool Ideas (74) - + cluster_external External blockers - + cluster_unresolved Unresolved internal refs - + cluster_legend - -Legend + +Legend @@ -203,7 +208,7 @@ WARP-symbol-history-timeline - S - + cool_ideas_WARP_temporal_structural_search WARP-temporal-structural-search - M @@ -215,170 +220,176 @@ blocked_by/blocking - + +bad_code_CLEAN_remaining_structural_warp_reads_bypass_structural_reading_port + +CLEAN-remaining-structural-warp-reads-bypass-structural-reading-port - M + + + cool_ideas_bounded_neighborhood_for_references bounded-neighborhood-for-references - S - + cool_ideas_CI_001_causal_collapse_visualizer CI-001-causal-collapse-visualizer - L - + cool_ideas_CI_002_deterministic_scenario_replay CI-002-deterministic-scenario-replay - L - + cool_ideas_CI_003_mcp_native_diff_protocol CI-003-mcp-native-diff-protocol - M - + cool_ideas_CLEAN_CODE_parallel_agent_merge_shared_file_loss CLEAN-CODE-parallel-agent-merge-shared-file-loss - M - + cool_ideas_CORE_agent_handoff_protocol CORE-agent-handoff-protocol - M - + cool_ideas_CORE_auto_focus CORE-auto-focus - L - + cool_ideas_CORE_capture_range CORE-capture-range - S - + cool_ideas_CORE_constructor_in_disguise_lint CORE-constructor-in-disguise-lint - M - + cool_ideas_CORE_context_budget_forecasting CORE-context-budget-forecasting - M - + cool_ideas_CORE_conversation_primer CORE-conversation-primer - M - + cool_ideas_CORE_cross_session_resume CORE-cross-session-resume - S - + cool_ideas_CORE_graft_as_teacher CORE-graft-as-teacher - S - + cool_ideas_CORE_graft_teach_learning_receipts CORE-graft-teach-learning-receipts - S - + cool_ideas_CORE_graft_tool_client CORE-graft-tool-client - M - + cool_ideas_CORE_horizon_of_readability CORE-horizon-of-readability - M - + cool_ideas_CORE_lagrangian_policy CORE-lagrangian-policy - XL - + cool_ideas_CORE_migrate_to_slice_first_reads CORE-migrate-to-slice-first-reads - + cool_ideas_CORE_multi_agent_conflict_detection CORE-multi-agent-conflict-detection - L - + cool_ideas_CORE_policy_playground CORE-policy-playground - S - + cool_ideas_CORE_policy_profiles CORE-policy-profiles - M - + cool_ideas_CORE_self_tuning_governor CORE-self-tuning-governor - M - + cool_ideas_CORE_session_knowledge_map CORE-session-knowledge-map - S - + cool_ideas_CORE_speculative_read_cost CORE-speculative-read-cost - S - + cool_ideas_CORE_structural_session_replay CORE-structural-session-replay - M - + cool_ideas_CORE_wire_primitives_into_runtime CORE-wire-primitives-into-runtime - M - + cool_ideas_monitor_tick_ceiling_tracking monitor-tick-ceiling-tracking - S - + cool_ideas_WARP_background_indexing WARP-background-indexing - M @@ -391,13 +402,13 @@ blocked_by/blocking - + cool_ideas_SURFACE_active_causal_workspace_status SURFACE-active-causal-workspace-status - M - + cool_ideas_SURFACE_ide_native_graft_integration SURFACE-ide-native-graft-integration - XL @@ -410,79 +421,79 @@ blocked_by/blocking - + cool_ideas_SURFACE_attach_to_existing_causal_session SURFACE-attach-to-existing-causal-session - M - + cool_ideas_SURFACE_bijou_daemon_control_plane_actions SURFACE-bijou-daemon-control-plane-actions - L - + cool_ideas_SURFACE_bijou_daemon_status_live_refresh SURFACE-bijou-daemon-status-live-refresh - M - + cool_ideas_SURFACE_git_graft_enhance_expanded_git_subcommands SURFACE-git-graft-enhance-expanded-git-subcommands - + cool_ideas_SURFACE_graft_review_pr_number_adapter SURFACE-graft-review-pr-number-adapter - M - + cool_ideas_SURFACE_init_dry_run SURFACE-init-dry-run - S - + cool_ideas_SURFACE_local_history_dag_render_mode_and_count_legend SURFACE-local-history-dag-render-mode-and-count-legend - S - + cool_ideas_SURFACE_non_codex_instruction_bootstrap_parity SURFACE-non-codex-instruction-bootstrap-parity - M - + cool_ideas_SURFACE_offer_rename_refactor SURFACE-offer-rename-refactor - L - + cool_ideas_SURFACE_terminal_activity_browser_tui SURFACE-terminal-activity-browser-tui - L - + cool_ideas_traverse_plus_query_hydration_helper traverse-plus-query-hydration-helper - S - + cool_ideas_WARP_adaptive_projection_selection WARP-adaptive-projection-selection - L - + cool_ideas_WARP_agent_action_provenance WARP-agent-action-provenance - XL @@ -495,7 +506,7 @@ blocked_by/blocking - + cool_ideas_WARP_causal_write_tracking WARP-causal-write-tracking - L @@ -508,7 +519,7 @@ blocked_by/blocking - + cool_ideas_WARP_intent_and_decision_events WARP-intent-and-decision-events - M @@ -521,7 +532,7 @@ blocked_by/blocking - + cool_ideas_WARP_provenance_dag WARP-provenance-dag - L @@ -534,31 +545,31 @@ blocked_by/blocking - + cool_ideas_WARP_auto_breaking_change_detection WARP-auto-breaking-change-detection - L - + cool_ideas_WARP_budget_elasticity WARP-budget-elasticity - M - + cool_ideas_WARP_causal_blame_for_staged_artifacts WARP-causal-blame-for-staged-artifacts - L - + cool_ideas_WARP_codebase_entropy_trajectory WARP-codebase-entropy-trajectory - M - + cool_ideas_WARP_counterfactual_refactoring WARP-counterfactual-refactoring - XL @@ -571,13 +582,13 @@ blocked_by/blocking - + cool_ideas_WARP_codebase_signature WARP-codebase-signature - L - + cool_ideas_WARP_structural_impact_prediction WARP-structural-impact-prediction - XL @@ -590,55 +601,55 @@ blocked_by/blocking - + cool_ideas_WARP_degeneracy_warning WARP-degeneracy-warning - M - + cool_ideas_WARP_drift_sentinel WARP-drift-sentinel - M - + cool_ideas_WARP_footprint_parallelism WARP-footprint-parallelism - XL - + cool_ideas_WARP_graft_pack WARP-graft-pack - M - + cool_ideas_WARP_grouped_aggregate_queries WARP-grouped-aggregate-queries - M - + cool_ideas_WARP_intentional_degeneracy_privacy WARP-intentional-degeneracy-privacy - M - + cool_ideas_WARP_minimum_viable_context WARP-minimum-viable-context - M - + cool_ideas_WARP_outline_diff_commit_trailer WARP-outline-diff-commit-trailer - S - + cool_ideas_WARP_projection_safety_classes WARP-projection-safety-classes - M @@ -651,7 +662,7 @@ blocked_by/blocking - + cool_ideas_WARP_reasoning_trace_replay WARP-reasoning-trace-replay - M @@ -664,43 +675,43 @@ blocked_by/blocking - + cool_ideas_WARP_rulial_heat_map WARP-rulial-heat-map - L - + cool_ideas_WARP_semantic_drift_in_sessions WARP-semantic-drift-in-sessions - M - + cool_ideas_WARP_semantic_merge_conflict_prediction WARP-semantic-merge-conflict-prediction - L - + cool_ideas_WARP_session_filtration WARP-session-filtration - L - + cool_ideas_WARP_shadow_structural_workspaces WARP-shadow-structural-workspaces - XL - + cool_ideas_WARP_speculative_merge WARP-speculative-merge - XL - + cool_ideas_WARP_stale_docs_checker WARP-stale-docs-checker - M @@ -713,25 +724,25 @@ blocked_by/blocking - + cool_ideas_WARP_structural_drift_detection WARP-structural-drift-detection - M - + cool_ideas_WARP_symbol_heatmap WARP-symbol-heatmap - M - + cool_ideas_WARP_technical_debt_curvature WARP-technical-debt-curvature - L - + external_git_warp_observer_geometry_ladder__Rung_2_4_ git-warp observer geometry ladder (Rung 2-4) @@ -744,7 +755,7 @@ blocked_by_external - + unresolved_CLEAN_CODE_export_diff_semver_signature_as_patch missing: CLEAN_CODE_export-diff-semver-signature-as-patch @@ -764,44 +775,44 @@ blocked_by - + leg_v08 - -v0.8.0 + +v0.8.0 - + leg_v07 - -v0.7.0 + +v0.7.0 - + leg_bad - -bad-code + +bad-code - + leg_idea - -cool-ideas + +cool-ideas - + leg_external - -external blocker + +external blocker - + leg_unresolved - -unresolved ref + +unresolved ref diff --git a/src/mcp/context.ts b/src/mcp/context.ts index 9a6e8b5b..b1d01c8e 100644 --- a/src/mcp/context.ts +++ b/src/mcp/context.ts @@ -11,6 +11,7 @@ import type { FileSystem } from "../ports/filesystem.js"; import type { JsonCodec } from "../ports/codec.js"; import type { ProcessRunner } from "../ports/process-runner.js"; import type { GitClient } from "../ports/git.js"; +import type { StructuralReadingPort } from "../ports/structural-reading.js"; import type { WarpContext } from "../warp/context.js"; import type { RepoObservation } from "./repo-state.js"; import type { RunCaptureConfig } from "./run-capture-config.js"; @@ -81,6 +82,7 @@ export interface ToolContext { }): void; resolvePath(relative: string): string; getWarp(): Promise; + getStructuralReadingPort(): StructuralReadingPort; getRepoState(): RepoObservation; getCausalContext(): RuntimeCausalContext; getWorkspaceOverlayFooting(): Promise; @@ -138,7 +140,15 @@ export function assertToolContext(value: unknown): asserts value is ToolContext } } - const methods = ["respond", "resolvePath", "getWarp", "getRepoState", "getCausalContext", "getWorkspaceStatus"] as const; + const methods = [ + "respond", + "resolvePath", + "getWarp", + "getStructuralReadingPort", + "getRepoState", + "getCausalContext", + "getWorkspaceStatus", + ] as const; for (const method of methods) { if (obj[method] === undefined || obj[method] === null) { throw new Error(`ToolContext missing method: ${method}`); diff --git a/src/mcp/repo-tool-worker-context.ts b/src/mcp/repo-tool-worker-context.ts index 0856c846..ce3d3712 100644 --- a/src/mcp/repo-tool-worker-context.ts +++ b/src/mcp/repo-tool-worker-context.ts @@ -1,9 +1,11 @@ import { CanonicalJsonCodec } from "../adapters/canonical-json.js"; import { nodeFs } from "../adapters/node-fs.js"; import { nodeGit } from "../adapters/node-git.js"; +import { nodePathOps } from "../adapters/node-paths.js"; import { nodeProcessRunner } from "../adapters/node-process-runner.js"; import { createRepoPathResolver } from "../adapters/repo-paths.js"; import { openWarp } from "../warp/open.js"; +import { createGitWarpStructuralReadingPort } from "../warp/structural-reading-adapter.js"; import { GovernorTracker } from "../session/tracker.js"; import { RefusedResult } from "../policy/types.js"; import type { WorkspaceStatus } from "./workspace-router.js"; @@ -121,6 +123,17 @@ export function buildRepoToolWorkerContext( async getWarp() { return { app: await openWarp({ cwd: job.projectRoot, writerId: job.writerId }), strandId: null }; }, + getStructuralReadingPort() { + return createGitWarpStructuralReadingPort({ + projectRoot: job.projectRoot, + git: nodeGit, + pathOps: nodePathOps, + getWarp: async () => ({ + app: await openWarp({ cwd: job.projectRoot, writerId: job.writerId }), + strandId: null, + }), + }); + }, getRepoState() { return job.repoState; }, diff --git a/src/mcp/server-context.ts b/src/mcp/server-context.ts index 48db692e..df68fb76 100644 --- a/src/mcp/server-context.ts +++ b/src/mcp/server-context.ts @@ -4,6 +4,8 @@ import type { FileSystem } from "../ports/filesystem.js"; import type { ProcessRunner } from "../ports/process-runner.js"; import type { GitClient } from "../ports/git.js"; import type { WarpContext } from "../warp/context.js"; +import { nodePathOps } from "../adapters/node-paths.js"; +import { createGitWarpStructuralReadingPort } from "../warp/structural-reading-adapter.js"; import type { ToolContext, ToolDefinition } from "./context.js"; import type { McpToolResult } from "./receipt.js"; import type { RunCaptureConfig } from "./run-capture-config.js"; @@ -95,6 +97,15 @@ export function buildToolContext(deps: ToolContextDeps): ToolContext { getWarp(): Promise { return getActiveExecutionContext()?.getWarp() ?? workspaceRouter.getWarp(); }, + getStructuralReadingPort() { + const execution = getActiveExecutionContext(); + return createGitWarpStructuralReadingPort({ + projectRoot: execution?.projectRoot ?? workspaceRouter.getProjectRoot(), + git: deps.git, + pathOps: nodePathOps, + getWarp: () => execution?.getWarp() ?? workspaceRouter.getWarp(), + }); + }, getRepoState() { return getActiveExecutionContext()?.repoState.getState() ?? workspaceRouter.getRepoState(); }, diff --git a/src/mcp/tools/dead-symbols.ts b/src/mcp/tools/dead-symbols.ts index 4dd90a7e..05251157 100644 --- a/src/mcp/tools/dead-symbols.ts +++ b/src/mcp/tools/dead-symbols.ts @@ -1,6 +1,5 @@ import { z } from "zod"; import type { ToolDefinition, ToolHandler } from "../context.js"; -import { findDeadSymbols } from "../../warp/dead-symbols.js"; export const deadSymbolsTool: ToolDefinition = { name: "graft_dead_symbols", @@ -13,10 +12,10 @@ export const deadSymbolsTool: ToolDefinition = { createHandler(): ToolHandler { return async (args, ctx) => { const maxCommits = args["maxCommits"]; - const warp = await ctx.getWarp(); - const symbols = await findDeadSymbols(warp, { + const reading = await ctx.getStructuralReadingPort().findDeadSymbols({ ...(typeof maxCommits === "number" ? { maxCommits } : {}), }); + const symbols = reading.payload.symbols; const sorted = [...symbols].sort((a, b) => a.filePath.localeCompare(b.filePath) || a.name.localeCompare(b.name) || diff --git a/src/mcp/tools/structural-review.ts b/src/mcp/tools/structural-review.ts index 537bc8fc..c8978811 100644 --- a/src/mcp/tools/structural-review.ts +++ b/src/mcp/tools/structural-review.ts @@ -2,9 +2,6 @@ import { z } from "zod"; import { structuralReview, type ReferenceCountResult } from "../../operations/structural-review.js"; import { toJsonObject } from "../../operations/result-dto.js"; import type { ToolContext, ToolDefinition, ToolHandler } from "../context.js"; -import { countSymbolReferencesFromGraph } from "../../warp/warp-reference-count.js"; -import { nodePathOps } from "../../adapters/node-paths.js"; -import { countNamedImportReferencesAtRef } from "../../operations/import-reference-impact.js"; async function countReviewReferences( ctx: ToolContext, @@ -12,25 +9,15 @@ async function countReviewReferences( filePath: string, headRef: string, ): Promise { - const warpCtx = await ctx.getWarp(); - const graphResult = await countSymbolReferencesFromGraph(warpCtx, symbolName, filePath); - if (graphResult.referenceCount > 0) { - return graphResult; - } - - try { - const fallbackResult = await countNamedImportReferencesAtRef({ - cwd: ctx.projectRoot, - git: ctx.git, - pathOps: nodePathOps, - symbolName, - filePath, - ref: headRef, - }); - return fallbackResult.referenceCount > 0 ? fallbackResult : graphResult; - } catch { - return graphResult; - } + const reading = await ctx.getStructuralReadingPort().countSymbolReferences({ + symbolName, + filePath, + ref: headRef, + }); + return { + referenceCount: reading.payload.referenceCount, + referencingFiles: reading.payload.referencingFiles, + }; } export const structuralReviewTool: ToolDefinition = { diff --git a/src/ports/structural-reading.ts b/src/ports/structural-reading.ts new file mode 100644 index 00000000..39477700 --- /dev/null +++ b/src/ports/structural-reading.ts @@ -0,0 +1,130 @@ +// --------------------------------------------------------------------------- +// StructuralReadingPort — Graft-owned structural read boundary. +// --------------------------------------------------------------------------- + +export type StructuralReadingKind = "symbol-reference-count" | "dead-symbols"; + +export type StructuralReadingFreshness = "current" | "stale" | "incomparable"; + +export type StructuralReadingResidualPosture = + | "complete" + | "partial" + | "plural" + | "budget-limited" + | "rights-limited" + | "unavailable"; + +export interface ContinuumReadingEnvelopeRef { + readonly family: string; + readonly readingId: string; + readonly basis: Readonly>; + readonly [key: string]: unknown; +} + +export interface ContinuumWitnessedSuffixShellRef { + readonly family: string; + readonly witnessId: string; + readonly suffixId: string; + readonly [key: string]: unknown; +} + +export interface ContinuumNativeEvidence { + readonly kind: "continuum-native"; + readonly envelope: ContinuumReadingEnvelopeRef; + readonly witness?: ContinuumWitnessedSuffixShellRef | undefined; +} + +export interface GitWarpCommittedBasis { + readonly kind: "git-committed-history"; + readonly projectRoot: string; + readonly ref?: string | undefined; + readonly base?: string | undefined; + readonly head?: string | undefined; + readonly maxCommits?: number | undefined; +} + +export type GitWarpEvidenceSource = "warp-graph" | "committed-import-scan"; + +export type GitWarpEvidence = + | { + readonly kind: "symbol-reference-count"; + readonly source: GitWarpEvidenceSource; + readonly symbolName: string; + readonly filePath: string; + } + | { + readonly kind: "dead-symbols"; + readonly source: "warp-graph"; + readonly maxCommits?: number | undefined; + }; + +export interface TranslatedSubstrateEvidence { + readonly kind: "translated-substrate"; + readonly substrate: "git-warp"; + readonly basis: GitWarpCommittedBasis; + readonly evidence: GitWarpEvidence; + readonly nativeContinuumWitness: false; +} + +export type StructuralReadingEvidence = + | ContinuumNativeEvidence + | TranslatedSubstrateEvidence; + +export interface StructuralReadingResult { + readonly kind: StructuralReadingKind; + readonly freshness: StructuralReadingFreshness; + readonly residualPosture: StructuralReadingResidualPosture; + readonly payload: TPayload; + readonly evidence: StructuralReadingEvidence; +} + +export interface SymbolReferenceReadingRequest { + readonly symbolName: string; + readonly filePath: string; + readonly ref?: string | undefined; +} + +export interface SymbolReferenceReadingPayload { + readonly symbol: string; + readonly referenceCount: number; + readonly referencingFiles: readonly string[]; +} + +export interface DeadSymbolsReadingRequest { + readonly maxCommits?: number | undefined; +} + +export interface DeadSymbolReadingPayload { + readonly name: string; + readonly kind: string; + readonly filePath: string; + readonly exported: boolean; + readonly removedInCommit: string; +} + +export interface DeadSymbolsReadingPayload { + readonly symbols: readonly DeadSymbolReadingPayload[]; + readonly total: number; +} + +export interface StructuralReadingPort { + countSymbolReferences( + request: SymbolReferenceReadingRequest, + ): Promise>; + + findDeadSymbols( + request?: DeadSymbolsReadingRequest, + ): Promise>; +} + +export function isContinuumNativeEvidence( + evidence: StructuralReadingEvidence, +): evidence is ContinuumNativeEvidence { + return evidence.kind === "continuum-native"; +} + +export function isTranslatedSubstrateEvidence( + evidence: StructuralReadingEvidence, +): evidence is TranslatedSubstrateEvidence { + return evidence.kind === "translated-substrate"; +} diff --git a/src/warp/structural-reading-adapter.ts b/src/warp/structural-reading-adapter.ts new file mode 100644 index 00000000..60ed8ed2 --- /dev/null +++ b/src/warp/structural-reading-adapter.ts @@ -0,0 +1,173 @@ +import type { GitClient } from "../ports/git.js"; +import type { PathOps } from "../ports/paths.js"; +import type { + DeadSymbolsReadingPayload, + DeadSymbolsReadingRequest, + GitWarpEvidence, + StructuralReadingResidualPosture, + StructuralReadingPort, + StructuralReadingResult, + SymbolReferenceReadingPayload, + SymbolReferenceReadingRequest, + TranslatedSubstrateEvidence, +} from "../ports/structural-reading.js"; +import type { ReferenceCountResult } from "../operations/structural-review.js"; +import { + countNamedImportReferencesAtRef as defaultCountNamedImportReferencesAtRef, +} from "../operations/import-reference-impact.js"; +import { + countSymbolReferencesFromGraph as defaultCountSymbolReferencesFromGraph, +} from "./warp-reference-count.js"; +import { + findDeadSymbols as defaultFindDeadSymbols, + type DeadSymbol, +} from "./dead-symbols.js"; +import type { WarpContext } from "./context.js"; + +export interface GitWarpStructuralReadingPortDeps { + readonly projectRoot: string; + readonly git: GitClient; + readonly pathOps: PathOps; + readonly getWarp: () => Promise; + readonly countSymbolReferencesFromGraph?: ( + ctx: WarpContext, + symbolName: string, + filePath?: string, + ) => Promise<{ readonly symbol: string; readonly referenceCount: number; readonly referencingFiles: readonly string[] }>; + readonly countNamedImportReferencesAtRef?: (opts: { + readonly cwd: string; + readonly git: GitClient; + readonly pathOps: PathOps; + readonly symbolName: string; + readonly filePath: string; + readonly ref: string; + }) => Promise; + readonly findDeadSymbols?: ( + ctx: WarpContext, + options?: DeadSymbolsReadingRequest, + ) => Promise; +} + +function translatedGitWarpEvidence( + deps: Pick, + basis: { readonly ref?: string | undefined; readonly maxCommits?: number | undefined }, + evidence: GitWarpEvidence, +): TranslatedSubstrateEvidence { + return { + kind: "translated-substrate", + substrate: "git-warp", + basis: { + kind: "git-committed-history", + projectRoot: deps.projectRoot, + ...(basis.ref !== undefined ? { ref: basis.ref } : {}), + ...(basis.maxCommits !== undefined ? { maxCommits: basis.maxCommits } : {}), + }, + evidence, + nativeContinuumWitness: false, + }; +} + +function symbolReferencePayload( + symbolName: string, + result: ReferenceCountResult | { readonly symbol: string; readonly referenceCount: number; readonly referencingFiles: readonly string[] }, +): SymbolReferenceReadingPayload { + return { + symbol: "symbol" in result ? result.symbol : symbolName, + referenceCount: result.referenceCount, + referencingFiles: result.referencingFiles, + }; +} + +export function createGitWarpStructuralReadingPort( + deps: GitWarpStructuralReadingPortDeps, +): StructuralReadingPort { + const countSymbolReferencesFromGraph = + deps.countSymbolReferencesFromGraph ?? defaultCountSymbolReferencesFromGraph; + const countNamedImportReferencesAtRef = + deps.countNamedImportReferencesAtRef ?? defaultCountNamedImportReferencesAtRef; + const findDeadSymbols = deps.findDeadSymbols ?? defaultFindDeadSymbols; + + return { + async countSymbolReferences( + request: SymbolReferenceReadingRequest, + ): Promise> { + const ref = request.ref ?? "HEAD"; + const warp = await deps.getWarp(); + const graphResult = await countSymbolReferencesFromGraph( + warp, + request.symbolName, + request.filePath, + ); + + let payload = symbolReferencePayload(request.symbolName, graphResult); + let source: "warp-graph" | "committed-import-scan" = "warp-graph"; + let residualPosture: StructuralReadingResidualPosture = "complete"; + + if (graphResult.referenceCount === 0) { + try { + const fallbackResult = await countNamedImportReferencesAtRef({ + cwd: deps.projectRoot, + git: deps.git, + pathOps: deps.pathOps, + symbolName: request.symbolName, + filePath: request.filePath, + ref, + }); + if (fallbackResult.referenceCount > 0) { + payload = symbolReferencePayload(request.symbolName, fallbackResult); + source = "committed-import-scan"; + } + } catch { + payload = symbolReferencePayload(request.symbolName, graphResult); + residualPosture = "partial"; + } + } + + return { + kind: "symbol-reference-count", + freshness: "current", + residualPosture, + payload, + evidence: translatedGitWarpEvidence( + deps, + { ref }, + { + kind: "symbol-reference-count", + source, + symbolName: request.symbolName, + filePath: request.filePath, + }, + ), + }; + }, + + async findDeadSymbols( + request: DeadSymbolsReadingRequest = {}, + ): Promise> { + const warp = await deps.getWarp(); + const options = request.maxCommits !== undefined + ? { maxCommits: request.maxCommits } + : undefined; + const symbols = await findDeadSymbols(warp, options); + + return { + kind: "dead-symbols", + freshness: "current", + residualPosture: "complete", + payload: { + symbols, + total: symbols.length, + }, + evidence: translatedGitWarpEvidence( + deps, + { maxCommits: request.maxCommits }, + { + kind: "dead-symbols", + source: "warp-graph", + ...(request.maxCommits !== undefined ? { maxCommits: request.maxCommits } : {}), + }, + ), + }; + }, + }; +} diff --git a/test/unit/mcp/context-guard.test.ts b/test/unit/mcp/context-guard.test.ts index 9addc88e..5c64dbed 100644 --- a/test/unit/mcp/context-guard.test.ts +++ b/test/unit/mcp/context-guard.test.ts @@ -26,6 +26,7 @@ function minimalContext(): Record { respond: () => ({ content: [] }), resolvePath: (r: string) => r, getWarp: () => Promise.resolve({}), + getStructuralReadingPort: () => ({}), getRepoState: () => ({}), getCausalContext: () => ({}), getWorkspaceStatus: () => ({}), diff --git a/test/unit/ports/structural-reading.test.ts b/test/unit/ports/structural-reading.test.ts new file mode 100644 index 00000000..78e85445 --- /dev/null +++ b/test/unit/ports/structural-reading.test.ts @@ -0,0 +1,88 @@ +import { describe, expect, it } from "vitest"; +import { + isContinuumNativeEvidence, + isTranslatedSubstrateEvidence, + type ContinuumNativeEvidence, + type StructuralReadingResult, + type TranslatedSubstrateEvidence, +} from "../../../src/ports/structural-reading.js"; + +describe("StructuralReadingEvidence", () => { + it("separates Continuum-native evidence from translated substrate evidence", () => { + const native = { + kind: "continuum-native", + envelope: { + family: "runtime-boundary", + readingId: "reading:echo:frontier:1", + basis: { kind: "echo-frontier", id: "frontier:1" }, + }, + witness: { + family: "runtime-boundary", + witnessId: "witness:suffix:1", + suffixId: "suffix:1", + }, + } satisfies ContinuumNativeEvidence; + + const translated = { + kind: "translated-substrate", + substrate: "git-warp", + basis: { + kind: "git-committed-history", + projectRoot: "/repo", + ref: "HEAD", + }, + evidence: { + kind: "symbol-reference-count", + source: "warp-graph", + symbolName: "buildThing", + filePath: "src/api.ts", + }, + nativeContinuumWitness: false, + } satisfies TranslatedSubstrateEvidence; + + expect(isContinuumNativeEvidence(native)).toBe(true); + expect(isContinuumNativeEvidence(translated)).toBe(false); + expect(isTranslatedSubstrateEvidence(translated)).toBe(true); + expect(isTranslatedSubstrateEvidence(native)).toBe(false); + expect(translated.nativeContinuumWitness).toBe(false); + expect("nativeContinuumWitness" in native).toBe(false); + }); + + it("allows normalized Graft payloads to carry Continuum-native evidence", () => { + const reading = { + kind: "symbol-reference-count", + freshness: "current", + residualPosture: "complete", + payload: { + symbol: "buildThing", + referenceCount: 2, + referencingFiles: ["src/a.ts", "src/b.ts"], + }, + evidence: { + kind: "continuum-native", + envelope: { + family: "runtime-boundary", + readingId: "reading:echo:references:buildThing", + basis: { kind: "echo-frontier", id: "frontier:buildThing" }, + }, + witness: { + family: "runtime-boundary", + witnessId: "witness:references:buildThing", + suffixId: "suffix:references:buildThing", + }, + }, + } satisfies StructuralReadingResult<{ + readonly symbol: string; + readonly referenceCount: number; + readonly referencingFiles: readonly string[]; + }>; + + expect(reading.payload).toEqual({ + symbol: "buildThing", + referenceCount: 2, + referencingFiles: ["src/a.ts", "src/b.ts"], + }); + expect(reading.evidence.kind).toBe("continuum-native"); + expect(isContinuumNativeEvidence(reading.evidence)).toBe(true); + }); +}); diff --git a/test/unit/warp/structural-reading-adapter.test.ts b/test/unit/warp/structural-reading-adapter.test.ts new file mode 100644 index 00000000..be94fc80 --- /dev/null +++ b/test/unit/warp/structural-reading-adapter.test.ts @@ -0,0 +1,201 @@ +import { describe, expect, it, vi } from "vitest"; +import type { GitClient } from "../../../src/ports/git.js"; +import type { PathOps } from "../../../src/ports/paths.js"; +import type { WarpContext } from "../../../src/warp/context.js"; +import { createGitWarpStructuralReadingPort } from "../../../src/warp/structural-reading-adapter.js"; + +const git = { + run: vi.fn(), +} satisfies GitClient; + +const pathOps = { + normalize: (p: string) => p, + isWithin: (filePath: string, directory: string) => + filePath === directory || filePath.startsWith(`${directory}/`), + join: (...segments: string[]) => segments.join("/").replaceAll("//", "/"), +} satisfies PathOps; + +const warp = { app: {}, strandId: null } as unknown as WarpContext; + +describe("git-warp structural reading adapter", () => { + it("labels WARP graph reference counts as translated non-Continuum-native evidence", async () => { + const port = createGitWarpStructuralReadingPort({ + projectRoot: "/repo", + git, + pathOps, + getWarp: () => Promise.resolve(warp), + countSymbolReferencesFromGraph: vi.fn(() => Promise.resolve({ + symbol: "buildThing", + referenceCount: 2, + referencingFiles: ["src/a.ts", "src/b.ts"], + })), + countNamedImportReferencesAtRef: vi.fn(), + findDeadSymbols: vi.fn(), + }); + + const reading = await port.countSymbolReferences({ + symbolName: "buildThing", + filePath: "src/api.ts", + ref: "HEAD", + }); + + expect(reading.payload).toEqual({ + symbol: "buildThing", + referenceCount: 2, + referencingFiles: ["src/a.ts", "src/b.ts"], + }); + expect(reading).toMatchObject({ + kind: "symbol-reference-count", + freshness: "current", + residualPosture: "complete", + evidence: { + kind: "translated-substrate", + substrate: "git-warp", + nativeContinuumWitness: false, + basis: { + kind: "git-committed-history", + projectRoot: "/repo", + ref: "HEAD", + }, + evidence: { + kind: "symbol-reference-count", + source: "warp-graph", + symbolName: "buildThing", + filePath: "src/api.ts", + }, + }, + }); + }); + + it("labels committed import-scan fallback counts as translated substrate evidence", async () => { + const port = createGitWarpStructuralReadingPort({ + projectRoot: "/repo", + git, + pathOps, + getWarp: () => Promise.resolve(warp), + countSymbolReferencesFromGraph: vi.fn(() => Promise.resolve({ + symbol: "buildThing", + referenceCount: 0, + referencingFiles: [], + })), + countNamedImportReferencesAtRef: vi.fn(() => Promise.resolve({ + referenceCount: 1, + referencingFiles: ["src/consumer.ts"], + })), + findDeadSymbols: vi.fn(), + }); + + const reading = await port.countSymbolReferences({ + symbolName: "buildThing", + filePath: "src/api.ts", + ref: "HEAD", + }); + + expect(reading.payload).toEqual({ + symbol: "buildThing", + referenceCount: 1, + referencingFiles: ["src/consumer.ts"], + }); + expect(reading.evidence).toMatchObject({ + kind: "translated-substrate", + substrate: "git-warp", + nativeContinuumWitness: false, + evidence: { + kind: "symbol-reference-count", + source: "committed-import-scan", + symbolName: "buildThing", + filePath: "src/api.ts", + }, + }); + }); + + it("marks reference-count readings partial when committed fallback evidence is unavailable", async () => { + const port = createGitWarpStructuralReadingPort({ + projectRoot: "/repo", + git, + pathOps, + getWarp: () => Promise.resolve(warp), + countSymbolReferencesFromGraph: vi.fn(() => Promise.resolve({ + symbol: "buildThing", + referenceCount: 0, + referencingFiles: [], + })), + countNamedImportReferencesAtRef: vi.fn(() => Promise.reject(new Error("git unavailable"))), + findDeadSymbols: vi.fn(), + }); + + const reading = await port.countSymbolReferences({ + symbolName: "buildThing", + filePath: "src/api.ts", + ref: "HEAD", + }); + + expect(reading.payload).toEqual({ + symbol: "buildThing", + referenceCount: 0, + referencingFiles: [], + }); + expect(reading.residualPosture).toBe("partial"); + expect(reading.evidence).toMatchObject({ + kind: "translated-substrate", + substrate: "git-warp", + nativeContinuumWitness: false, + evidence: { + kind: "symbol-reference-count", + source: "warp-graph", + symbolName: "buildThing", + filePath: "src/api.ts", + }, + }); + }); + + it("labels dead-symbol readings as translated non-Continuum-native evidence", async () => { + const port = createGitWarpStructuralReadingPort({ + projectRoot: "/repo", + git, + pathOps, + getWarp: () => Promise.resolve(warp), + countSymbolReferencesFromGraph: vi.fn(), + countNamedImportReferencesAtRef: vi.fn(), + findDeadSymbols: vi.fn(() => Promise.resolve([ + { + name: "oldThing", + kind: "function", + filePath: "src/api.ts", + exported: true, + removedInCommit: "abc123", + }, + ])), + }); + + const reading = await port.findDeadSymbols({ maxCommits: 3 }); + + expect(reading.payload).toEqual({ + symbols: [ + { + name: "oldThing", + kind: "function", + filePath: "src/api.ts", + exported: true, + removedInCommit: "abc123", + }, + ], + total: 1, + }); + expect(reading).toMatchObject({ + kind: "dead-symbols", + freshness: "current", + residualPosture: "complete", + evidence: { + kind: "translated-substrate", + substrate: "git-warp", + nativeContinuumWitness: false, + evidence: { + kind: "dead-symbols", + source: "warp-graph", + maxCommits: 3, + }, + }, + }); + }); +}); diff --git a/tests/playback/CORE_v060-bad-code-burndown.test.ts b/tests/playback/CORE_v060-bad-code-burndown.test.ts index fac4b1e7..eaee9649 100644 --- a/tests/playback/CORE_v060-bad-code-burndown.test.ts +++ b/tests/playback/CORE_v060-bad-code-burndown.test.ts @@ -86,6 +86,7 @@ describe("CORE_v060-bad-code-burndown", () => { respond: () => ({ content: [] }), resolvePath: (r: string) => r, getWarp: () => Promise.resolve({}), + getStructuralReadingPort: () => ({}), getRepoState: () => { return {}; }, getCausalContext: () => { return {}; }, getWorkspaceStatus: () => { return {}; },