Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .factory/prompts/bugfix-intake.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ You are running bugfix intake for `RiddimSoftware/epac`. Your job is to turn an
- observed behavior
- expected behavior
- reproduction steps
- estimate
- evidence plan
- validation plan

Expand All @@ -48,6 +49,7 @@ python3 scripts/intake/bugfix_spec.py new \
--expected "The card explains that epac currently shows past debates and archival data." \
--step "Launch epac." \
--step "Open the Home tab." \
--estimate 8 \
--evidence "Before/after screenshot of the Home status card." \
--validation "Reporter confirms the TestFlight build no longer implies live data."
```
Expand Down
6 changes: 6 additions & 0 deletions .factory/schemas/bugfix-spec.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"affectedSurface",
"observedBehavior",
"expectedBehavior",
"estimate",
"reproductionSteps",
"acceptanceCriteria",
"evidencePlan",
Expand All @@ -30,6 +31,11 @@
"affectedSurface": { "type": "string", "minLength": 1 },
"observedBehavior": { "type": "string", "minLength": 20 },
"expectedBehavior": { "type": "string", "minLength": 20 },
"estimate": {
"type": "integer",
"enum": [1, 2, 4, 8, 16, 32, 64],
"description": "SF effort estimate from the exponential ladder 1, 2, 4, 8, 16, 32, 64. Opus maps this to Linear complexity as 1->1, 2->2, 4->4, 8->8, 16->16, 32->24, 64->40."
},
"reproductionSteps": {
"type": "array",
"minItems": 1,
Expand Down
5 changes: 5 additions & 0 deletions .factory/templates/bugfix-SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ This bugfix SPEC turns a contributor or LLM-session report into a factory-ready
When [evidence plan runs]
Then [artifact proves the behavior]

## Effort estimate
- Estimate: [1, 2, 4, 8, 16, 32, 64]
- Confidence: [low|medium|high]
- Reasoning: [One-sentence rationale]

## Evidence Plan
[Screenshots, videos, XCTest/SnapshotTesting, Maestro flow, logs, manifest, or TestFlight validation needed to prove the fix.]

Expand Down
38 changes: 38 additions & 0 deletions scripts/intake/bugfix_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
ROOT = Path(__file__).resolve().parents[2]
DEFAULT_OUT_DIR = ROOT / ".factory" / "intake"
TARGET_REPO = "RiddimSoftware/epac"
EFFORT_ESTIMATE_LADDER = (1, 2, 4, 8, 16, 32, 64)
EFFORT_ESTIMATE_CHOICES = ", ".join(str(v) for v in EFFORT_ESTIMATE_LADDER)

REQUIRED_SECTIONS = [
"## Original Prompt",
Expand All @@ -25,6 +27,7 @@
"### Expected Behavior",
"## Reproduction Steps",
"## Acceptance Criteria",
"## Effort estimate",
"## Evidence Plan",
"## Validation Plan",
"## Non-goals",
Expand Down Expand Up @@ -55,6 +58,7 @@ class BugfixSpecInput:
steps: list[str]
evidence: str
validation: str
estimate: int
original_prompt: str
non_goals: str
trace_id: str
Expand Down Expand Up @@ -93,6 +97,16 @@ def collect_steps(args: argparse.Namespace) -> list[str]:
return collected


def parse_estimate(value: str) -> int:
try:
estimate = int(value)
except ValueError as error:
raise ValueError(f"--estimate must be one of {EFFORT_ESTIMATE_CHOICES}") from error
if estimate not in EFFORT_ESTIMATE_LADDER:
raise ValueError(f"--estimate must be one of {EFFORT_ESTIMATE_CHOICES}")
return estimate


def value_or_prompt(args: argparse.Namespace, attr: str, label: str) -> str:
value = getattr(args, attr)
if value:
Expand All @@ -111,6 +125,7 @@ def build_input(args: argparse.Namespace) -> BugfixSpecInput:
expected = value_or_prompt(args, "expected", "Expected behavior")
evidence = value_or_prompt(args, "evidence", "Required evidence")
validation = value_or_prompt(args, "validation", "Reporter/TestFlight validation")
estimate = parse_estimate(value_or_prompt(args, "estimate", f"Effort estimate ({EFFORT_ESTIMATE_CHOICES})"))
steps = collect_steps(args)

missing = [
Expand All @@ -124,6 +139,7 @@ def build_input(args: argparse.Namespace) -> BugfixSpecInput:
("--expected", expected),
("--evidence", evidence),
("--validation", validation),
("--estimate", str(estimate)),
]
if not value
]
Expand Down Expand Up @@ -152,6 +168,7 @@ def build_input(args: argparse.Namespace) -> BugfixSpecInput:
expected=expected,
steps=steps,
evidence=evidence,
estimate=estimate,
validation=validation,
original_prompt=original_prompt,
non_goals=non_goals,
Expand Down Expand Up @@ -200,6 +217,11 @@ def render_spec(spec: BugfixSpecInput) -> str:
When the reporter validates the affected surface
Then their approval or blocker is recorded before release promotion.

## Effort estimate
- Estimate: {spec.estimate}
- Confidence: medium
- Reasoning: Initial SF effort estimate captured during intake; Opus may revise this during enrichment.

## Evidence Plan
{spec.evidence}

Expand Down Expand Up @@ -295,6 +317,11 @@ def section_body(text: str, heading: str) -> str:
return match.group("body").strip() if match else ""


def section_field(section: str, label: str) -> str:
match = re.search(rf"^\s*-?\s*{re.escape(label)}:\s*(.+)$", section, re.MULTILINE)
return match.group(1).strip() if match else ""


def validate_text(text: str) -> list[str]:
errors: list[str] = []
if not text.startswith("# Bugfix SPEC: "):
Expand All @@ -316,6 +343,16 @@ def validate_text(text: str) -> list[str]:
if len(criteria) < 2:
errors.append("missing at least two acceptance criteria starting with '- Given'")

effort_section = section_body(text, "## Effort estimate")
estimate_text = section_field(effort_section, "Estimate")
if not estimate_text:
errors.append("missing required effort estimate field: Estimate")
else:
try:
parse_estimate(estimate_text)
except ValueError as error:
errors.append(f"invalid effort estimate: {error}")

if PLACEHOLDER_RE.search(text):
errors.append("SPEC contains placeholder text; replace TBD/TODO/FIXME/bracket placeholders before intake")

Expand Down Expand Up @@ -409,6 +446,7 @@ def build_parser() -> argparse.ArgumentParser:
new.add_argument("--surface")
new.add_argument("--observed")
new.add_argument("--expected")
new.add_argument("--estimate")
new.add_argument("--step", action="append", default=[])
new.add_argument("--evidence")
new.add_argument("--validation")
Expand Down
Loading
Loading