File size: 9,388 Bytes
6684dac
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
"""
Stub definitions and the @reason_first decorator.

A stub is a function skeleton with:
  - A decorator @reason_first(spec="...") that declares the informal specification
  - An optional #> comment inside the body with additional constraints
  - Surrounding context: imports, type hints, downstream uses

The decorator captures all of this metadata to define the valid program space.
"""

from __future__ import annotations

import ast
import inspect
import textwrap
import hashlib
from dataclasses import dataclass, field
from typing import Any, Callable, Optional
from pathlib import Path


@dataclass
class StubConstraints:
    """Formal constraints derived from the stub's surrounding context."""

    imports: list[str] = field(default_factory=list)
    type_hints: dict[str, str] = field(default_factory=dict)
    return_type: Optional[str] = None
    inline_specs: list[str] = field(default_factory=list)  # #> comments
    downstream_uses: list[str] = field(default_factory=list)
    decorator_spec: str = ""
    preconditions: list[str] = field(default_factory=list)
    postconditions: list[str] = field(default_factory=list)

    def to_prompt_context(self) -> str:
        """Render constraints as natural language for LLM prompting."""
        parts = []
        if self.decorator_spec:
            parts.append(f"Specification: {self.decorator_spec}")
        if self.inline_specs:
            parts.append(f"Inline constraints: {'; '.join(self.inline_specs)}")
        if self.type_hints:
            hints = ", ".join(f"{k}: {v}" for k, v in self.type_hints.items())
            parts.append(f"Type hints: {hints}")
        if self.return_type:
            parts.append(f"Return type: {self.return_type}")
        if self.imports:
            parts.append(f"Available imports: {', '.join(self.imports)}")
        if self.preconditions:
            parts.append(f"Preconditions: {'; '.join(self.preconditions)}")
        if self.postconditions:
            parts.append(f"Postconditions: {'; '.join(self.postconditions)}")
        return "\n".join(parts)


@dataclass
class Stub:
    """
    A program stub: a function skeleton that defines a valid program space.

    The stub captures:
      - name: function name
      - source: the full source code of the decorated function
      - constraints: formal constraints extracted from context
      - module_source: the full module source (for imports, downstream uses)
      - test_inputs: optional test inputs for execution-based validation
    """

    name: str
    source: str
    signature: str
    constraints: StubConstraints
    module_source: Optional[str] = None
    test_inputs: Optional[list[dict[str, Any]]] = None
    test_outputs: Optional[list[Any]] = None
    property_tests: Optional[list[Callable]] = None

    @property
    def stub_id(self) -> str:
        """Unique identifier for this stub."""
        content = f"{self.name}:{self.signature}:{self.constraints.decorator_spec}"
        return hashlib.sha256(content.encode()).hexdigest()[:12]

    def to_completion_prompt(self, style: str = "direct") -> str:
        """
        Generate an LLM prompt for completing this stub.

        Args:
            style: 'direct' for straightforward completion,
                   'diverse' for encouraging algorithmic diversity (SFS scattering),
                   'concept_guided' for concept-directed generation
        """
        if style == "direct":
            return self._direct_prompt()
        elif style == "diverse":
            return self._diverse_prompt()
        elif style == "concept_guided":
            return self._concept_guided_prompt()
        else:
            raise ValueError(f"Unknown prompt style: {style}")

    def _direct_prompt(self) -> str:
        return (
            f"Complete the following Python function. "
            f"Only output the function body.\n\n"
            f"{self.constraints.to_prompt_context()}\n\n"
            f"```python\n{self.source}\n```"
        )

    def _diverse_prompt(self) -> str:
        """SFS-inspired scattering: ask for diverse algorithmic directions first."""
        return (
            f"Consider this Python function stub:\n\n"
            f"```python\n{self.source}\n```\n\n"
            f"{self.constraints.to_prompt_context()}\n\n"
            f"First, list 3-5 fundamentally different algorithmic approaches "
            f"to implement this function (e.g., iterative vs recursive, "
            f"different data structures, different time/space tradeoffs).\n\n"
            f"Then implement ONE of these approaches. Choose a different approach "
            f"than you normally would. Only output the function body."
        )

    def _concept_guided_prompt(self) -> str:
        return (
            f"Complete this function stub:\n\n"
            f"```python\n{self.source}\n```\n\n"
            f"{self.constraints.to_prompt_context()}\n\n"
            f"Implement the function. Only output the function body."
        )


class StubRegistry:
    """Global registry of all stubs defined with @reason_first."""

    _stubs: dict[str, Stub] = {}

    @classmethod
    def register(cls, stub: Stub) -> None:
        cls._stubs[stub.stub_id] = stub

    @classmethod
    def get(cls, stub_id: str) -> Optional[Stub]:
        return cls._stubs.get(stub_id)

    @classmethod
    def list_all(cls) -> list[Stub]:
        return list(cls._stubs.values())

    @classmethod
    def clear(cls) -> None:
        cls._stubs.clear()


def _extract_inline_specs(source: str) -> list[str]:
    """Extract #> comments from source code."""
    specs = []
    for line in source.split("\n"):
        stripped = line.strip()
        if stripped.startswith("#>"):
            specs.append(stripped[2:].strip())
    return specs


def _extract_imports(module_source: str) -> list[str]:
    """Extract import statements from module source."""
    imports = []
    try:
        tree = ast.parse(module_source)
        for node in ast.walk(tree):
            if isinstance(node, ast.Import):
                for alias in node.names:
                    imports.append(alias.name)
            elif isinstance(node, ast.ImportFrom):
                module = node.module or ""
                for alias in node.names:
                    imports.append(f"{module}.{alias.name}")
    except SyntaxError:
        pass
    return imports


def _extract_type_hints(func: Callable) -> tuple[dict[str, str], Optional[str]]:
    """Extract type hints from function signature."""
    hints = {}
    return_type = None
    try:
        sig = inspect.signature(func)
        for name, param in sig.parameters.items():
            if param.annotation != inspect.Parameter.empty:
                hints[name] = str(param.annotation)
            if sig.return_annotation != inspect.Signature.empty:
                return_type = str(sig.return_annotation)
    except (ValueError, TypeError):
        pass
    return hints, return_type


def reason_first(
    spec: str = "",
    preconditions: Optional[list[str]] = None,
    postconditions: Optional[list[str]] = None,
    test_inputs: Optional[list[dict[str, Any]]] = None,
    test_outputs: Optional[list[Any]] = None,
):
    """
    Decorator that marks a function as a reason-first program stub.

    Args:
        spec: Natural language specification of what the function should do
        preconditions: List of precondition descriptions
        postconditions: List of postcondition descriptions
        test_inputs: Optional list of test input dicts for validation
        test_outputs: Optional list of expected outputs

    Example:
        @reason_first(
            spec="Sort items by priority, breaking ties by recency",
            postconditions=["output is sorted", "all input items present in output"]
        )
        def process_queue(items: list[Item]) -> list[Item]:
            #> stable sort; O(n log n); must preserve Item identity
            ...
    """

    def decorator(func: Callable) -> Callable:
        source = inspect.getsource(func)
        source = textwrap.dedent(source)
        signature = str(inspect.signature(func))
        inline_specs = _extract_inline_specs(source)

        # Try to get module source for import/context extraction
        module_source = None
        try:
            module_file = inspect.getfile(func)
            if module_file:
                module_source = Path(module_file).read_text()
        except (TypeError, OSError):
            pass

        imports = _extract_imports(module_source) if module_source else []
        type_hints, return_type = _extract_type_hints(func)

        constraints = StubConstraints(
            imports=imports,
            type_hints=type_hints,
            return_type=return_type,
            inline_specs=inline_specs,
            decorator_spec=spec,
            preconditions=preconditions or [],
            postconditions=postconditions or [],
        )

        stub = Stub(
            name=func.__name__,
            source=source,
            signature=signature,
            constraints=constraints,
            module_source=module_source,
            test_inputs=test_inputs,
            test_outputs=test_outputs,
        )

        StubRegistry.register(stub)

        # Attach stub metadata to the function
        func._rfp_stub = stub
        return func

    return decorator