Skip to content

Add source-grounded gpd:ideate MVP#249

Open
adamlrlevine92 wants to merge 3 commits into
mainfrom
feature/gpd-ideate-mvp
Open

Add source-grounded gpd:ideate MVP#249
adamlrlevine92 wants to merge 3 commits into
mainfrom
feature/gpd-ideate-mvp

Conversation

@adamlrlevine92

@adamlrlevine92 adamlrlevine92 commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • add the project-aware gpd:ideate command and workflow for source-grounded research ideation
  • add gpd-paper-digester, gpd-ideator, and gpd-ideation-critic internal agents
  • add deterministic source-manifest and blackboard/session helpers that write blackboard, transcript, and report artifacts under GPD/blackboards
  • add templates, docs, generated help/repo graph updates, model-profile wiring, and focused tests

Verification

  • uv run pytest tests/core/test_ideate_sources.py tests/core/test_ideate_blackboard.py tests/core/test_ideate_prompt_contract.py -q
  • uv run pytest tests/test_registry.py tests/core/test_prompt_wiring.py tests/core/test_command_prompt_budget.py -q
  • uv run pytest tests/core/test_agent_prompt_budget.py tests/core/test_agent_role_prompting.py tests/core/test_agent_spawn_policy.py tests/core/test_config.py tests/test_metadata_consistency.py -q
  • uv run pytest tests/core/test_help_inventory_contract.py tests/core/test_help_public_surface_sync.py tests/core/test_help_renderer.py -q
  • uv run pytest tests/test_metadata_consistency.py tests/core/test_repo_interdependency_graph.py tests/core/test_generated_surface_target_registry.py -q
  • uv run pytest tests/adapters/test_runtime_projected_prompt_parity.py tests/adapters/test_runtime_projected_command_requirement_coverage.py tests/adapters/test_frontmatter_projection.py -q
  • uv run python scripts/render_help_surface.py --check
  • uv run python scripts/render_public_surface.py --check
  • uv run python scripts/sync_repo_graph_contract.py --check
  • uv run ruff check src/gpd/core/ideate_sources.py src/gpd/core/ideate_blackboard.py tests/core/test_ideate_sources.py tests/core/test_ideate_blackboard.py tests/core/test_ideate_prompt_contract.py src/gpd/core/constants.py
  • git diff --check

Demo

  • docs/dev/gpd-ideate-local-demo.md contains a local CLI demo flow using a small TeX source corpus and expected GPD/blackboards outputs.

Summary by CodeRabbit

  • New Features

    • Added a source-grounded ideation command with depth modes, durable artifacts (blackboard/transcript/report), collision-safe session naming, source-gate preventing final ranked ideas without non-topic sources, and three internal ideation roles with critic veto handling.
  • Documentation

    • Added MVP plan, local demo runbook, templates, command/workflow help, and model-profile reference updates for the ideation flow.
  • Tests

    • Added/updated comprehensive tests for ideation sources, artifact lifecycle, prompt/contracts, spawn contracts, budgets, and metadata consistency.
  • Chores

    • Extended model-profile and return-field registries and added durable artifact naming constants.

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


Adam Levine seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fd45b186-f87f-42d2-8ef5-efbb98478b46

📥 Commits

Reviewing files that changed from the base of the PR and between 7f0f1e7 and 47c12c7.

📒 Files selected for processing (4)
  • src/gpd/agents/gpd-ideation-critic.md
  • src/gpd/agents/gpd-ideator.md
  • src/gpd/agents/gpd-paper-digester.md
  • tests/core/test_ideate_prompt_contract.py
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/gpd/agents/gpd-ideation-critic.md
  • src/gpd/agents/gpd-ideator.md
  • src/gpd/agents/gpd-paper-digester.md
  • tests/core/test_ideate_prompt_contract.py

📝 Walkthrough

Walkthrough

Adds the gpd:ideate MVP: command/workflow, source normalization, durable blackboard/transcript/report artifacts, three internal agent specs (paper-digester, ideator, critic), templates, tests, and supporting docs/runbooks.

Changes

gpd:ideate MVP

Layer / File(s) Summary
Constants, configuration, and return-field wiring
src/gpd/core/constants.py, src/gpd/core/config.py, src/gpd/core/return_fields.py, src/gpd/registry.py, CHANGELOG.md
Introduces BLACKBOARDS_DIR_NAME, IDEATE_FILE_PREFIX, ProjectLayout.blackboards_dir; extends MODEL_PROFILES with gpd-paper-digester, gpd-ideator, gpd-ideation-critic; updates skill-category map and expands allowed gpd_return extension fields.
Source normalization and argument parsing
src/gpd/core/ideate_sources.py, tests/core/test_ideate_sources.py
New immutable types and functions to parse/normalize ideate arguments into a IdeateSourceManifest with SRC-### ids, knowledge-doc indexing, directory scanning, dedupe, and --max-papers handling, plus unit tests.
Durable blackboard/transcript/report artifacts
src/gpd/core/ideate_blackboard.py, src/gpd/specs/templates/ideate-*.md, tests/core/test_ideate_blackboard.py
Deterministic session allocation, template-driven initialization, collision-safe naming, locked append/merge helpers, rendering strategies, and tests validating artifact contents and stateful updates.
Command & workflow specs
src/gpd/commands/ideate.md, src/gpd/specs/workflows/ideate.md, help docs
Adds gpd:ideate wrapper and full workflow spec: source-gating, depth modes (fast/balanced/deep), three-session artifacts, per-source digestion spawn contracts, ideator/critic rounds, veto handling, and final-report constraints.
Agent specifications
src/gpd/agents/gpd-paper-digester.md, src/gpd/agents/gpd-ideator.md, src/gpd/agents/gpd-ideation-critic.md
Adds three internal agent spec files defining tools, authority boundaries (scoped_write, return_only), role metadata, and canonical gpd_return envelopes.
Templates, docs, and runbooks
src/gpd/specs/templates/*, docs/dev/gpd-ideate-*.md, src/gpd/specs/references/*
Adds blackboard/transcript/report templates, detailed command/workflow help entries, MVP plan, and local demo runbook describing artifact expectations and verification steps.
Tests, budgets, and repo metadata
tests/core/*, tests/repo_graph_contract.json, tests/README.md
Adds contract and unit tests for ideate flow, updates prompt/budget baselines and hard caps for new command/agents, adjusts spawn-contract inventory and repository graph/README metadata for added prompt stems.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • madeleinesong
  • cmaloney111

Poem

A rabbit hops through ideate's glade,
With slugs and templates neatly laid;
Three agents whisper, draft, and test,
Blackboard keeps the grounded best;
Ideas sprout safe within the shade. 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.27% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add source-grounded gpd:ideate MVP' clearly and concisely summarizes the main change: adding a new MVP (minimum viable product) command and workflow for source-grounded research ideation.
Description check ✅ Passed The PR description covers all required template sections: What changed (detailed summary of additions), Why (implied through the feature scope), Testing done (comprehensive verification steps), and Checklist (tests pass, lint clean, no secrets, user docs updated).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/gpd-ideate-mvp

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
src/gpd/specs/workflows/ideate.md (2)

210-306: 💤 Low value

Optional: Add language specifiers to code fences.

Static analysis flagged code blocks at lines 237 and 272 for missing language specifiers. Adding text or python would silence the warnings.

📝 Optional fix

Line 237:

-```
+```text
 task(

Line 272:

-```
+```text
 task(
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/gpd/specs/workflows/ideate.md` around lines 210 - 306, Two fenced code
blocks containing the task(...) specs for the ideation and critic spawns (the
blocks that use IDEATOR_MODEL and CRITIC_MODEL / subagent_type="gpd-ideator" and
subagent_type="gpd-ideation-critic") are missing language specifiers; add a
language tag (e.g., ```text) to the opening fence of each of those two blocks so
static analysis warnings are silenced and the blocks render consistently.

161-208: 💤 Low value

Optional: Add language specifier to code fence.

Static analysis flagged the code block at line 172 for missing a language specifier. While this is low priority since the block contains structured spawn syntax rather than executable shell, adding text or python would silence the warning.

📝 Optional fix
-```
+```text
 task(
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/gpd/specs/workflows/ideate.md` around lines 161 - 208, Add a language
specifier to the fenced code block that begins with the spawn/task manifest (the
block containing task( ... <spawn_contract> ... ) so the Markdown linter stops
flagging it; replace the opening ``` with a language tag such as ```text (or
```ini/```yaml if you prefer a more specific hint) for the block that includes
the task( and <spawn_contract> constructs, leaving the block contents unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/gpd/core/ideate_blackboard.py`:
- Around line 78-79: The _TURN_HEADING_RE is too permissive and matches "## Turn
N" inside message bodies; change it to only match top-level markdown turn
headings by requiring a document start or blank line before the heading and a
blank line (or end-of-string) after it — e.g. replace _TURN_HEADING_RE with a
pattern that uses a lookbehind for start-or-newline ((?<=\A|\n)) and a lookahead
for a blank line or end ((?=\n\s*\n|\Z)) around "##[ \t]+Turn[ \t]+(\d+)\b", and
update the other occurrence referenced in the comment (the second
_TURN_HEADING_RE usage) to use the same stricter regex so turn parsing ignores
in-body matches.
- Around line 748-752: The function _append_turn_body currently calls
body.replace("No turns recorded.", "") which can delete that phrase from earlier
turns; instead only remove that placeholder when the entire body equals (after
stripping) "No turns recorded." so historical content is preserved. Change the
logic in _append_turn_body to test if body.strip() == "No turns recorded." and
if so set body = "" (or body = body.rstrip() -> ""), otherwise leave body
unchanged, then proceed to concatenate "\n\n" + rendered_turn.rstrip() and call
_ensure_trailing_newline as before.

In `@src/gpd/core/ideate_sources.py`:
- Around line 247-249: The code is currently re-tokenizing a single-item
Sequence[str] that contains spaces; change the condition so we only call
_argument_tokens when the original input was a single raw string (not an
already-tokenized Sequence[str]) — e.g., detect whether the caller passed a str
vs a Sequence[str] and if the input is an already-provided sequence (like
list/tuple) preserve arguments as-is by returning tuple(arguments) instead of
calling _argument_tokens; update the branch around arguments and the
_argument_tokens call accordingly.
- Around line 395-435: _scan_directory_sources currently calls
directory.iterdir() which can raise OSError/PermissionError and crash
normalization; wrap the iteration in a try/except that catches OSError and
PermissionError, and on exception return a _DirectoryScan with selected=() and
warnings containing a descriptive message that includes the exception text (so
the caller branch that creates a blocked _NormalizedInput will run). Update
_scan_directory_sources (and any place that builds the warning tuple) to include
the directory path (use _relative_posix(directory, root) if helpful) and the
exception details in the warning so callers of _scan_directory_sources and the
caller logic that checks .selected and .warnings (the code that builds the
blocked IdeateSourceRecord) will produce the blocked row with warnings instead
of crashing.

---

Nitpick comments:
In `@src/gpd/specs/workflows/ideate.md`:
- Around line 210-306: Two fenced code blocks containing the task(...) specs for
the ideation and critic spawns (the blocks that use IDEATOR_MODEL and
CRITIC_MODEL / subagent_type="gpd-ideator" and
subagent_type="gpd-ideation-critic") are missing language specifiers; add a
language tag (e.g., ```text) to the opening fence of each of those two blocks so
static analysis warnings are silenced and the blocks render consistently.
- Around line 161-208: Add a language specifier to the fenced code block that
begins with the spawn/task manifest (the block containing task( ...
<spawn_contract> ... ) so the Markdown linter stops flagging it; replace the
opening ``` with a language tag such as ```text (or ```ini/```yaml if you prefer
a more specific hint) for the block that includes the task( and <spawn_contract>
constructs, leaving the block contents unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d347a32f-d902-4ff8-95b1-f3c32ee1d575

📥 Commits

Reviewing files that changed from the base of the PR and between 0f41769 and cacb6a8.

📒 Files selected for processing (31)
  • CHANGELOG.md
  • docs/dev/gpd-ideate-local-demo.md
  • docs/dev/gpd-ideate-mvp-plan.md
  • src/gpd/agents/gpd-ideation-critic.md
  • src/gpd/agents/gpd-ideator.md
  • src/gpd/agents/gpd-paper-digester.md
  • src/gpd/commands/ideate.md
  • src/gpd/core/config.py
  • src/gpd/core/constants.py
  • src/gpd/core/ideate_blackboard.py
  • src/gpd/core/ideate_sources.py
  • src/gpd/registry.py
  • src/gpd/specs/references/help/detailed-command-reference.md
  • src/gpd/specs/references/orchestration/model-profiles.md
  • src/gpd/specs/templates/ideate-blackboard.md
  • src/gpd/specs/templates/ideate-transcript.md
  • src/gpd/specs/templates/ideation-report.md
  • src/gpd/specs/workflows/help.md
  • src/gpd/specs/workflows/ideate.md
  • src/gpd/specs/workflows/set-profile.md
  • tests/README.md
  • tests/core/test_agent_prompt_budget.py
  • tests/core/test_agent_spawn_policy.py
  • tests/core/test_command_prompt_budget.py
  • tests/core/test_config.py
  • tests/core/test_ideate_blackboard.py
  • tests/core/test_ideate_prompt_contract.py
  • tests/core/test_ideate_sources.py
  • tests/core/test_spawn_contract_inventory.py
  • tests/repo_graph_contract.json
  • tests/test_metadata_consistency.py

Comment on lines +78 to +79
_TURN_HEADING_RE = re.compile(r"(?m)^##[ \t]+Turn[ \t]+(\d+)\b.*$")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Turn-number parsing is too broad and can reject valid updates.

The current turn regex matches any ## Turn N line anywhere in the transcript body, including user/model message content. That can trigger false non-contiguous-turn failures during append.

Proposed fix
-_TURN_HEADING_RE = re.compile(r"(?m)^##[ \t]+Turn[ \t]+(\d+)\b.*$")
+_TURN_HEADING_RE = re.compile(r"(?m)^##[ \t]+Turn[ \t]+(\d+)[ \t]*$(?=\n\nMetadata:\n)")

Also applies to: 755-756

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/gpd/core/ideate_blackboard.py` around lines 78 - 79, The _TURN_HEADING_RE
is too permissive and matches "## Turn N" inside message bodies; change it to
only match top-level markdown turn headings by requiring a document start or
blank line before the heading and a blank line (or end-of-string) after it —
e.g. replace _TURN_HEADING_RE with a pattern that uses a lookbehind for
start-or-newline ((?<=\A|\n)) and a lookahead for a blank line or end
((?=\n\s*\n|\Z)) around "##[ \t]+Turn[ \t]+(\d+)\b", and update the other
occurrence referenced in the comment (the second _TURN_HEADING_RE usage) to use
the same stricter regex so turn parsing ignores in-body matches.

Comment on lines +748 to +752
def _append_turn_body(body: str, rendered_turn: str) -> str:
body = body.rstrip()
if "No turns recorded." in body:
body = body.replace("No turns recorded.", "").rstrip()
return _ensure_trailing_newline(body + "\n\n" + rendered_turn.rstrip())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Appending a new turn can silently mutate prior turn content.

_append_turn_body removes "No turns recorded." globally. If earlier turn text contains that phrase, it gets deleted on later appends, which rewrites historical transcript content.

Proposed fix
 def _append_turn_body(body: str, rendered_turn: str) -> str:
     body = body.rstrip()
-    if "No turns recorded." in body:
-        body = body.replace("No turns recorded.", "").rstrip()
+    body = re.sub(
+        r"(?ms)(^##[ \t]+Turns[ \t]*\n\n)No turns recorded\.[ \t]*\n?",
+        r"\1",
+        body,
+        count=1,
+    ).rstrip()
     return _ensure_trailing_newline(body + "\n\n" + rendered_turn.rstrip())
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/gpd/core/ideate_blackboard.py` around lines 748 - 752, The function
_append_turn_body currently calls body.replace("No turns recorded.", "") which
can delete that phrase from earlier turns; instead only remove that placeholder
when the entire body equals (after stripping) "No turns recorded." so historical
content is preserved. Change the logic in _append_turn_body to test if
body.strip() == "No turns recorded." and if so set body = "" (or body =
body.rstrip() -> ""), otherwise leave body unchanged, then proceed to
concatenate "\n\n" + rendered_turn.rstrip() and call _ensure_trailing_newline as
before.

Comment on lines +247 to +249
if len(arguments) == 1 and isinstance(arguments[0], str) and " " in arguments[0]:
return _argument_tokens(arguments[0])
return tuple(str(item) for item in arguments)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve already-tokenized Sequence[str] inputs.

At Line 247, a single sequence item containing spaces is re-tokenized, which can break valid one-argument inputs (like quoted file paths with spaces) passed as Sequence[str].

Proposed fix
 def _argument_tokens(arguments: str | Sequence[str] | None) -> tuple[str, ...]:
     if arguments is None:
         return ()
     if isinstance(arguments, str):
         try:
             return tuple(shlex.split(arguments))
         except ValueError:
             return tuple(part for part in arguments.split() if part)
-    if len(arguments) == 1 and isinstance(arguments[0], str) and " " in arguments[0]:
-        return _argument_tokens(arguments[0])
     return tuple(str(item) for item in arguments)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if len(arguments) == 1 and isinstance(arguments[0], str) and " " in arguments[0]:
return _argument_tokens(arguments[0])
return tuple(str(item) for item in arguments)
def _argument_tokens(arguments: str | Sequence[str] | None) -> tuple[str, ...]:
if arguments is None:
return ()
if isinstance(arguments, str):
try:
return tuple(shlex.split(arguments))
except ValueError:
return tuple(part for part in arguments.split() if part)
return tuple(str(item) for item in arguments)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/gpd/core/ideate_sources.py` around lines 247 - 249, The code is currently
re-tokenizing a single-item Sequence[str] that contains spaces; change the
condition so we only call _argument_tokens when the original input was a single
raw string (not an already-tokenized Sequence[str]) — e.g., detect whether the
caller passed a str vs a Sequence[str] and if the input is an already-provided
sequence (like list/tuple) preserve arguments as-is by returning
tuple(arguments) instead of calling _argument_tokens; update the branch around
arguments and the _argument_tokens call accordingly.

Comment on lines +395 to +435
scan = _scan_directory_sources(directory, root=root, max_papers=max_papers)
if not scan.selected:
return _NormalizedInput(
records=(
IdeateSourceRecord(
source_id="",
kind="blocked",
input=raw,
normalized_ref=_relative_posix(directory, root),
status="blocked",
warnings=("Directory contains no supported .tex or .pdf paper sources.", *scan.warnings),
),
),
warnings=scan.warnings,
)

records = [
_normalize_paper_path(
_relative_posix(path, root),
path,
suffix=path.suffix.lower(),
root=root,
kdoc_index=kdoc_index,
kind="directory_item",
)
for path in scan.selected
]
if scan.warnings:
records[0] = _with_warnings(records[0], scan.warnings)
return _NormalizedInput(records=tuple(records), warnings=scan.warnings)


def _scan_directory_sources(directory: Path, *, root: Path, max_papers: int | None) -> _DirectoryScan:
paper_candidates = [
path
for path in sorted(directory.iterdir(), key=lambda item: item.name.casefold())
if path.is_file()
and not path.name.startswith(".")
and path.name.lower() not in _IGNORED_DIRECTORY_FILENAMES
and path.suffix.lower() in _SUPPORTED_PAPER_SUFFIXES
]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle unreadable directories without crashing source normalization.

At Line 430, directory.iterdir() can raise OSError/PermissionError for valid-but-unreadable paths, which currently aborts normalization instead of returning a blocked row with warnings.

Proposed fix
 def _normalize_directory(
@@
 ) -> _NormalizedInput:
     scan = _scan_directory_sources(directory, root=root, max_papers=max_papers)
     if not scan.selected:
+        base_warning: tuple[str, ...] = ()
+        if not scan.warnings:
+            base_warning = ("Directory contains no supported .tex or .pdf paper sources.",)
         return _NormalizedInput(
             records=(
                 IdeateSourceRecord(
                     source_id="",
                     kind="blocked",
                     input=raw,
                     normalized_ref=_relative_posix(directory, root),
                     status="blocked",
-                    warnings=("Directory contains no supported .tex or .pdf paper sources.", *scan.warnings),
+                    warnings=(*base_warning, *scan.warnings),
                 ),
             ),
             warnings=scan.warnings,
         )
@@
 def _scan_directory_sources(directory: Path, *, root: Path, max_papers: int | None) -> _DirectoryScan:
+    try:
+        entries = sorted(directory.iterdir(), key=lambda item: item.name.casefold())
+    except OSError as exc:
+        return _DirectoryScan(
+            selected=(),
+            warnings=(f"Unable to read ideate source directory {_relative_posix(directory, root)}: {exc}",),
+        )
     paper_candidates = [
         path
-        for path in sorted(directory.iterdir(), key=lambda item: item.name.casefold())
+        for path in entries
         if path.is_file()
         and not path.name.startswith(".")
         and path.name.lower() not in _IGNORED_DIRECTORY_FILENAMES
         and path.suffix.lower() in _SUPPORTED_PAPER_SUFFIXES
     ]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/gpd/core/ideate_sources.py` around lines 395 - 435,
_scan_directory_sources currently calls directory.iterdir() which can raise
OSError/PermissionError and crash normalization; wrap the iteration in a
try/except that catches OSError and PermissionError, and on exception return a
_DirectoryScan with selected=() and warnings containing a descriptive message
that includes the exception text (so the caller branch that creates a blocked
_NormalizedInput will run). Update _scan_directory_sources (and any place that
builds the warning tuple) to include the directory path (use
_relative_posix(directory, root) if helpful) and the exception details in the
warning so callers of _scan_directory_sources and the caller logic that checks
.selected and .warnings (the code that builds the blocked IdeateSourceRecord)
will produce the blocked row with warnings instead of crashing.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/gpd/specs/workflows/ideate.md (1)

148-181: 💤 Low value

Add language identifiers to code fences for markdown consistency.

The code fences at lines 148 and 208 are missing language identifiers. While these blocks show template/pseudo-code patterns rather than executable code, adding an identifier (e.g., text or yaml) would satisfy markdown linting rules and improve consistency.

📝 Suggested language identifier addition

For line 148:

-```
+```text
 task(
   subagent_type="gpd-paper-digester",

For line 208:

-```
+```text
 task(
   subagent_type="gpd-ideator",

Also applies to: 208-241

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/gpd/specs/workflows/ideate.md` around lines 148 - 181, Add language
identifiers to the fenced code blocks wrapping the task definitions to satisfy
markdown linting: locate the task(...) block that begins with
subagent_type="gpd-paper-digester" (the first task block) and the later
task(...) block with subagent_type="gpd-ideator", and change their opening
fences from ``` to ```text (or ```yaml if you prefer structured rendering) so
both code fences declare a language identifier for consistency.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/gpd/specs/workflows/ideate.md`:
- Around line 148-181: Add language identifiers to the fenced code blocks
wrapping the task definitions to satisfy markdown linting: locate the task(...)
block that begins with subagent_type="gpd-paper-digester" (the first task block)
and the later task(...) block with subagent_type="gpd-ideator", and change their
opening fences from ``` to ```text (or ```yaml if you prefer structured
rendering) so both code fences declare a language identifier for consistency.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2c8327e9-79cd-4189-bb53-89e25bd711f4

📥 Commits

Reviewing files that changed from the base of the PR and between cacb6a8 and 7f0f1e7.

📒 Files selected for processing (14)
  • src/gpd/agents/gpd-ideation-critic.md
  • src/gpd/agents/gpd-ideator.md
  • src/gpd/agents/gpd-paper-digester.md
  • src/gpd/commands/ideate.md
  • src/gpd/core/return_fields.py
  • src/gpd/specs/workflows/ideate.md
  • tests/adapters/test_runtime_projection_diagnostics_budget.py
  • tests/core/test_agent_prompt_budget.py
  • tests/core/test_cli.py
  • tests/core/test_command_prompt_budget.py
  • tests/core/test_ideate_blackboard.py
  • tests/core/test_ideate_prompt_contract.py
  • tests/core/test_ideate_sources.py
  • tests/core/test_prompt_surface_diagnostics_budget.py
✅ Files skipped from review due to trivial changes (1)
  • src/gpd/commands/ideate.md
🚧 Files skipped from review as they are similar to previous changes (6)
  • tests/core/test_command_prompt_budget.py
  • tests/core/test_agent_prompt_budget.py
  • tests/core/test_ideate_prompt_contract.py
  • tests/core/test_ideate_sources.py
  • src/gpd/agents/gpd-ideator.md
  • tests/core/test_ideate_blackboard.py

@marcpickett1

Copy link
Copy Markdown
Collaborator

🤖 RoastBot: "Source-grounded ideation." That's just reading, Adam.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants