JacobLinCool Codex commited on
Commit
3edd42b
·
verified ·
1 Parent(s): 9392ef3

fix: guard empty board commands

Browse files

Co-authored-by: Codex <noreply@openai.com>

README.md CHANGED
@@ -81,6 +81,8 @@ the private Field Notes artifact.
81
  The `Rank` command rescans the saved idea board, recalculates each seal against the current targets, selects the
82
  strongest page as the active idea, and drafts the next build step. The app then moves that page to the top of the Idea
83
  Board and refreshes the seal, wood map, plan, and PNG artifact around the chosen direction.
 
 
84
 
85
  ## Gap Exploration
86
 
 
81
  The `Rank` command rescans the saved idea board, recalculates each seal against the current targets, selects the
82
  strongest page as the active idea, and drafts the next build step. The app then moves that page to the top of the Idea
83
  Board and refreshes the seal, wood map, plan, and PNG artifact around the chosen direction.
84
+ If the board is empty, `Plan` and `Rank` do not create placeholder pages; they prompt the user to write an idea or press
85
+ `Gap` first.
86
 
87
  ## Gap Exploration
88
 
hackathon_advisor/agent.py CHANGED
@@ -245,9 +245,12 @@ class AdvisorEngine:
245
  ) -> TurnResult:
246
  idea = self._idea_for_optional_id(call, state)
247
  if idea is None:
248
- title, pitch = idea_from_text(normalized)
249
- idea, event = self.tools.save_idea(state, title, pitch)
250
- tool_events.append(event)
 
 
 
251
  score, event = self.tools.score_idea(idea)
252
  score = self._align_score_from_state(score, idea, state)
253
  idea.score = score.to_dict()
@@ -269,7 +272,10 @@ class AdvisorEngine:
269
  ranked = self._rank_ideas(state)
270
  if not ranked:
271
  tool_events.append(ToolEvent("compare_ideas", "No idea pages were available to rank."))
272
- response = "There are no written pages on the board yet."
 
 
 
273
  return self._result(normalized, corrections, response, state, tool_events, [], [], None, [], {})
274
 
275
  ideas = [idea for idea, _score in ranked]
 
245
  ) -> TurnResult:
246
  idea = self._idea_for_optional_id(call, state)
247
  if idea is None:
248
+ tool_events.append(ToolEvent("make_plan", "No idea page was available to plan."))
249
+ response = (
250
+ "Write one project instinct first, or press Gap for a starting direction. "
251
+ "Then I can draft a build path."
252
+ )
253
+ return self._result(normalized, corrections, response, state, tool_events, [], [], None, [], {})
254
  score, event = self.tools.score_idea(idea)
255
  score = self._align_score_from_state(score, idea, state)
256
  idea.score = score.to_dict()
 
272
  ranked = self._rank_ideas(state)
273
  if not ranked:
274
  tool_events.append(ToolEvent("compare_ideas", "No idea pages were available to rank."))
275
+ response = (
276
+ "No idea pages are on the board yet. Write one project instinct first, "
277
+ "or press Gap to seed a direction."
278
+ )
279
  return self._result(normalized, corrections, response, state, tool_events, [], [], None, [], {})
280
 
281
  ideas = [idea for idea, _score in ranked]
hackathon_advisor/model_runtime.py CHANGED
@@ -44,9 +44,9 @@ class RuleBasedPlanner:
44
  lower = text.lower()
45
  if not text:
46
  output = '<function name="list_projects">{"sort":"likes"}</function>'
47
- elif any(term in lower for term in ("compare", "choose", "rank")) and state.get("ideas"):
48
  output = '<function name="compare_ideas">{}</function>'
49
- elif any(term in lower for term in ("plan", "roadmap", "next step", "milestone")) and state.get("ideas"):
50
  output = '<function name="make_plan">{}</function>'
51
  elif any(term in lower for term in ("whitespace", "original", "new", "bolder", "unwritten", "gap")):
52
  output = '<function name="find_whitespace">{}</function>'
 
44
  lower = text.lower()
45
  if not text:
46
  output = '<function name="list_projects">{"sort":"likes"}</function>'
47
+ elif any(term in lower for term in ("compare", "choose", "rank")):
48
  output = '<function name="compare_ideas">{}</function>'
49
+ elif any(term in lower for term in ("plan", "roadmap", "next step", "milestone")):
50
  output = '<function name="make_plan">{}</function>'
51
  elif any(term in lower for term in ("whitespace", "original", "new", "bolder", "unwritten", "gap")):
52
  output = '<function name="find_whitespace">{}</function>'
tests/test_agent.py CHANGED
@@ -83,6 +83,21 @@ def test_plan_command_uses_current_idea() -> None:
83
  assert planned.state["ideas"][0]["title"] == first.artifact["title"]
84
 
85
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  def test_plan_uses_profile_context() -> None:
87
  index = ProjectIndex.from_files(Path("data/projects.json"), Path("data/project_index.json"))
88
  engine = AdvisorEngine(index)
 
83
  assert planned.state["ideas"][0]["title"] == first.artifact["title"]
84
 
85
 
86
+ def test_plan_and_rank_do_not_create_placeholder_ideas() -> None:
87
+ index = ProjectIndex.from_files(Path("data/projects.json"), Path("data/project_index.json"))
88
+ engine = AdvisorEngine(index)
89
+
90
+ planned = engine.turn("make a build plan", {})
91
+ ranked = engine.turn("compare ideas", planned.state)
92
+
93
+ assert planned.state["ideas"] == []
94
+ assert ranked.state["ideas"] == []
95
+ assert "Write one project instinct first" in planned.response
96
+ assert "No idea pages" in ranked.response
97
+ assert planned.tool_events[0].name == "make_plan"
98
+ assert ranked.tool_events[0].name == "compare_ideas"
99
+
100
+
101
  def test_plan_uses_profile_context() -> None:
102
  index = ProjectIndex.from_files(Path("data/projects.json"), Path("data/project_index.json"))
103
  engine = AdvisorEngine(index)
tests/test_model_runtime.py CHANGED
@@ -28,6 +28,18 @@ def test_rule_planner_uses_plan_when_idea_exists() -> None:
28
  assert resolution.call.name == "make_plan"
29
 
30
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  def test_rule_planner_defaults_blank_to_list_projects() -> None:
32
  planner = RuleBasedPlanner()
33
 
 
28
  assert resolution.call.name == "make_plan"
29
 
30
 
31
+ def test_rule_planner_keeps_empty_board_commands_as_commands() -> None:
32
+ planner = RuleBasedPlanner()
33
+
34
+ plan = planner.plan("make a build plan", {})
35
+ rank = planner.plan("compare ideas", {})
36
+
37
+ assert plan.status == "valid"
38
+ assert plan.call.name == "make_plan"
39
+ assert rank.status == "valid"
40
+ assert rank.call.name == "compare_ideas"
41
+
42
+
43
  def test_rule_planner_defaults_blank_to_list_projects() -> None:
44
  planner = RuleBasedPlanner()
45