feat(mayfly): add depth-tiered research-campaign notebook - #247
feat(mayfly): add depth-tiered research-campaign notebook#247marcpickett1 wants to merge 2 commits into
Conversation
Integrates the Mayfly v2 protocol as an automatic background system that tracks all research context without requiring explicit user commands. ## Components **`gpd-mayfly` MCP server** (`src/gpd/mcp/servers/mayfly_server.py`) 8th built-in server, auto-registered by `gpd install`. Manages the notebook at `GPD/mayfly/` with a depth-tiered hierarchy: FRONTIER.md (surface) → knowledge/ (topics) → epochs/ → sessions/ (raw) Tools: bootstrap_mayfly, read_frontier, update_frontier, read_map, update_map, upsert_knowledge, read_knowledge, append_journal_row, write_session_notes, write_epoch_summary, search_notebook, read_session_log **Stop hook** (`src/gpd/hooks/mayfly_capture.py`) Pure Python, no LLM. Fires after each Claude response. When GPD commands have run since the last capture (detected via execution-lineage.jsonl), appends a structured record to `GPD/mayfly/session-log.jsonl` containing the session ID, commands run, and transcript path. Also bootstraps the notebook directory structure on first use. **Command maintenance sections** `execute-phase`, `research-phase`, and `discuss-phase` now include a `<mayfly_maintenance>` completion duty instructing the agent to call upsert_knowledge + update_frontier + append_journal_row before returning. Fails silently when gpd-mayfly is unavailable. **Infrastructure** - `MAYFLY_DIR_NAME` constant + 8 `ProjectLayout` properties in constants.py - `ensure_mayfly_stop_hook()` in install_utils.py (same idempotent upsert pattern as ensure_update_hook, with uninstall cleanup) - `gpd-mcp-mayfly` entry point in pyproject.toml - SKILL.md documents the Mayfly v2 protocol (portable, harness-agnostic) ## What runs automatically After `gpd install`: Stop hook and MCP server are wired into Claude Code. On first session: notebook bootstrapped at GPD/mayfly/. During execute/research/discuss: agent updates knowledge graph as part of command completion (no user action required). After every session with GPD commands: Stop hook writes a raw capture record linking the transcript — even if agent maintenance was skipped. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
|
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (8)
✅ Files skipped from review due to trivial changes (3)
🚧 Files skipped from review as they are similar to previous changes (3)
📝 WalkthroughWalkthroughThis pull request introduces the complete Mayfly v2 research protocol: a fresh-context, disk-backed notebook system for structured research campaigns. Changes include specification documentation, MCP server for notebook management, session capture hook, hook wiring, command workflow integrations, and supporting infrastructure. ChangesMayfly Research Protocol and Integration
Sequence Diagram(s)sequenceDiagram
participant CLI as gpd-mcp-mayfly
participant Server as mayfly_server.py
participant Notebook as GPD/mayfly/
CLI->>Server: bootstrap_mayfly(project_dir)
Server->>Notebook: Create directory tree
Server->>Notebook: Seed FRONTIER.md, MAP.md, JOURNAL.md
CLI->>Server: read_frontier(project_dir)
Notebook-->>Server: Return frontier content
Server-->>CLI: frontier data
CLI->>Server: upsert_knowledge(project_dir, topic, content)
Server->>Notebook: Write knowledge/topic.md atomically
Server-->>CLI: created_now boolean
CLI->>Server: append_journal_row(project_dir, step, outcome, summary, ...)
Server->>Notebook: Update or append JOURNAL.md row
Server-->>CLI: success
sequenceDiagram
participant Claude as Claude Code
participant Hook as mayfly_capture.py
participant Lineage as execution-lineage.jsonl
participant SessionLog as session-log.jsonl
Claude->>Hook: Call stop hook with JSON payload
Hook->>Hook: Parse stdin JSON
Hook->>Hook: Find GPD project root
Hook->>Notebook: Check if mayfly/ exists
alt Mayfly missing and GPD initialized
Hook->>Hook: Bootstrap directories and files
end
Hook->>SessionLog: Read last capture timestamp
Hook->>Lineage: Scan for commands since timestamp
Lineage-->>Hook: Recent command entries
Hook->>SessionLog: Append new session record
Hook-->>Claude: Return 0 (success or silent skip)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
|
Caution Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted. Error details |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (3)
src/gpd/core/constants.py (1)
561-591: ⚡ Quick winAdd missing path helper for POST_IT.md.
POST_IT.md is a core protocol file referenced throughout SKILL.md (lines 106, 237, 397, 436-437, 532, 707, 718, 954) and used by every PI role invocation, but there's no
mayfly_post_itpath helper. This makes it harder for downstream code to consistently reference this file.➕ Proposed addition
`@property` def mayfly_session_log(self) -> Path: return self.gpd / MAYFLY_DIR_NAME / "session-log.jsonl" + + `@property` + def mayfly_post_it(self) -> Path: + return self.gpd / MAYFLY_DIR_NAME / "POST_IT.md" `@property` def research_map_dir(self) -> Path:🤖 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/constants.py` around lines 561 - 591, Add a new Path helper property named mayfly_post_it on the same object that defines mayfly_dir/mayfly_knowledge_dir/etc.; implement it to return the canonical path to the POST_IT.md file (e.g., return self.gpd / MAYFLY_DIR_NAME / "POST_IT.md"), keeping naming and style consistent with existing properties like mayfly_journal and mayfly_map so downstream code can reference self.mayfly_post_it.src/gpd/hooks/mayfly_capture.py (2)
44-63: 💤 Low valueConsider optimizing for large session logs in the future.
The function reads the entire session-log.jsonl file to find the most recent timestamp. For long-running campaigns with many sessions, this could become slow. While the current best-effort design is acceptable, consider optimizing if performance degrades (e.g., maintaining a separate timestamp file or seeking from the end).
🤖 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/hooks/mayfly_capture.py` around lines 44 - 63, The current _read_last_capture_ts(session_log) implementation reads the entire file into memory which can be slow for very large session_log files; update it to avoid full-file reads by either (A) reading from the end: open session_log in binary, seek from the end and scan backwards to find the last newline(s) and parse only the last JSONL line(s) until a valid "ts" is found, or (B) maintain and read a lightweight companion file (e.g., session_log.with_suffix('.last_ts')) that stores the most recent timestamp and update that file whenever you append captures; implement one of these strategies in _read_last_capture_ts and ensure it still handles missing files and JSON errors like the current function.
97-105: 💤 Low valueNote the limitation: no file locking for concurrent appends.
The function appends to
session-log.jsonlwithout file locking. If multiple hook invocations run concurrently, writes could interleave. Given the best-effort design and the low likelihood of concurrent Stop hooks, this is acceptable. However, if concurrent sessions become common, consider adding file locking or buffering.🤖 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/hooks/mayfly_capture.py` around lines 97 - 105, The _append_session_log function appends JSON lines without file locking, risking interleaved writes under concurrent hook runs; update _append_session_log to acquire an exclusive file lock around the open/write (use a cross-platform locking lib such as fcntl/portalocker/filelock) for session_log before writing the JSONL line, fall back to the current best-effort behavior on lock acquisition failure, and keep the OSError handling so this remains non-fatal.
🤖 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 `@SKILL.md`:
- Around line 104-116: The spec's directory name "attempts/" conflicts with the
code which uses "sessions/" (see constants.py mayfly_sessions_dir and the
hook/server usage), so update SKILL.md to match implementation: replace every
"attempts" reference (e.g., list_attempts, read_attempt, write_attempt_notes and
other occurrence points) with "sessions" and adjust related endpoint/command
names accordingly, and add the missing session-log.jsonl entry to the layout
diagram so the documented layout matches constants.py and the hook-managed
capture log.
In `@src/gpd/mcp/servers/mayfly_server.py`:
- Around line 80-86: _sanitize_epoch_name currently strips suffix with
name.strip().rstrip(".md") and enforces exactly 3 digits per side
(re.fullmatch(r"\d{3}-\d{3}", ...)), which rejects epoch names produced by
write_epoch_summary when values >=1000; change _sanitize_epoch_name to (1)
remove the literal ".md" suffix safely (use str.removesuffix(".md") or an
endswith check and slice) instead of rstrip(".md"), and (2) relax the regex to
allow three-or-more digits on each side (e.g. re.fullmatch(r"\d{3,}-\d{3,}",
stripped) or simply re.fullmatch(r"\d+-\d+", stripped)) so it accepts names
generated by write_epoch_summary; update _sanitize_epoch_name accordingly and
ensure write_epoch_summary remains compatible.
- Around line 431-436: Calculate the total match count before you truncate the
results: capture total_count = len(results) prior to the truncation block (using
the variables shown: results, _SEARCH_MAX_LINES, truncated), then perform the
slicing and append the truncation note but return stable_mcp_response with
"count": total_count (or alternatively add a separate "message" field for the
truncation note instead of appending it to results) so the count reflects actual
matches not the informational line.
- Around line 581-582: The _clean function currently only escapes pipe
characters but must also sanitize newlines so Markdown table rows don't break;
update def _clean(s: str) to replace newline and carriage-return characters with
a single space (e.g., s = s.replace("\r", " ").replace("\n", " ")), then perform
the existing pipe escape and strip, and ensure callers that pass outcome,
summary, knowledge_updated, files, metric continue to use _clean so all row
fields are normalized.
- Around line 170-177: The seeded JOURNAL.md table is missing the metric column
which causes malformed rows from append_journal_row; update the atomic_write
call that writes layout.mayfly_journal to include a metric header (e.g., add "|
metric |" to the header row and adjust the separator row to six columns) and
ensure append_journal_row (the function that formats journal lines) always emits
six columns (use an empty string for metric when not provided) so the table
structure is consistent.
---
Nitpick comments:
In `@src/gpd/core/constants.py`:
- Around line 561-591: Add a new Path helper property named mayfly_post_it on
the same object that defines mayfly_dir/mayfly_knowledge_dir/etc.; implement it
to return the canonical path to the POST_IT.md file (e.g., return self.gpd /
MAYFLY_DIR_NAME / "POST_IT.md"), keeping naming and style consistent with
existing properties like mayfly_journal and mayfly_map so downstream code can
reference self.mayfly_post_it.
In `@src/gpd/hooks/mayfly_capture.py`:
- Around line 44-63: The current _read_last_capture_ts(session_log)
implementation reads the entire file into memory which can be slow for very
large session_log files; update it to avoid full-file reads by either (A)
reading from the end: open session_log in binary, seek from the end and scan
backwards to find the last newline(s) and parse only the last JSONL line(s)
until a valid "ts" is found, or (B) maintain and read a lightweight companion
file (e.g., session_log.with_suffix('.last_ts')) that stores the most recent
timestamp and update that file whenever you append captures; implement one of
these strategies in _read_last_capture_ts and ensure it still handles missing
files and JSON errors like the current function.
- Around line 97-105: The _append_session_log function appends JSON lines
without file locking, risking interleaved writes under concurrent hook runs;
update _append_session_log to acquire an exclusive file lock around the
open/write (use a cross-platform locking lib such as fcntl/portalocker/filelock)
for session_log before writing the JSONL line, fall back to the current
best-effort behavior on lock acquisition failure, and keep the OSError handling
so this remains non-fatal.
🪄 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: 1420dd0e-4b42-4f2c-8586-ce3c662942ac
📒 Files selected for processing (11)
SKILL.mdpyproject.tomlsrc/gpd/adapters/claude_code.pysrc/gpd/adapters/install_utils.pysrc/gpd/commands/discuss-phase.mdsrc/gpd/commands/execute-phase.mdsrc/gpd/commands/research-phase.mdsrc/gpd/core/constants.pysrc/gpd/hooks/mayfly_capture.pysrc/gpd/mcp/builtin_servers.pysrc/gpd/mcp/servers/mayfly_server.py
| ``` | ||
| campaign/ | ||
| ├── POST_IT.md # active PI brief (overwritten each step) | ||
| ├── FRONTIER.md # current knowledge state (researcher updates each step) | ||
| ├── knowledge/ | ||
| │ ├── MAP.md # navigable index: topics + epoch list + open threads | ||
| │ └── <topic-slug>.md # per-topic synthesis with links to supporting attempts | ||
| ├── epochs/ | ||
| │ └── <start>-<end>.md # compressed summary of K attempts (Summarizer writes) | ||
| ├── JOURNAL.md # append-only one-row-per-attempt log (archive at scale) | ||
| └── attempts/ | ||
| └── <NNN>.md # full per-attempt notes (deepest corner) | ||
| ``` |
There was a problem hiding this comment.
Critical: Directory naming mismatch between spec and implementation.
Line 114 specifies the directory as attempts/, but the implementation consistently uses sessions/ (see constants.py line 570-571, mayfly_capture.py context snippet line 113, and mayfly_server.py bootstrap). This naming inconsistency propagates through the entire document (50+ references to "attempts", "list_attempts", "read_attempt", "write_attempt_notes", etc.) and will cause confusion when porting the protocol.
Additionally, session-log.jsonl is missing from this layout diagram but exists in the implementation (constants.py line 590-591) and is mentioned in the PR description as a hook-managed capture log.
📋 Recommended fixes
Option 1 (align spec to implementation): Replace all references to "attempts/" with "sessions/" throughout this document (lines 84, 114-115, 125, 328, 403, 405-406, 450-451, 567, 721, 736, 746, 763-764, 824, 848, 861).
Option 2 (align implementation to spec): Rename mayfly_sessions_dir to mayfly_attempts_dir in constants.py and update all downstream code (bootstrap, MCP server, hook) to use "attempts/" instead of "sessions/".
Additionally, add session-log.jsonl to the layout diagram:
campaign/
├── POST_IT.md # active PI brief (overwritten each step)
├── FRONTIER.md # current knowledge state (researcher updates each step)
+├── session-log.jsonl # hook-managed capture records (one per session)
├── knowledge/🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 104-104: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 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 `@SKILL.md` around lines 104 - 116, The spec's directory name "attempts/"
conflicts with the code which uses "sessions/" (see constants.py
mayfly_sessions_dir and the hook/server usage), so update SKILL.md to match
implementation: replace every "attempts" reference (e.g., list_attempts,
read_attempt, write_attempt_notes and other occurrence points) with "sessions"
and adjust related endpoint/command names accordingly, and add the missing
session-log.jsonl entry to the layout diagram so the documented layout matches
constants.py and the hook-managed capture log.
| def _sanitize_epoch_name(name: str) -> str | None: | ||
| """Return a safe filename for an epoch, or None if invalid.""" | ||
| # Expect format: NNN-NNN (e.g. "000-019") | ||
| stripped = name.strip().rstrip(".md") | ||
| if not re.fullmatch(r"\d{3}-\d{3}", stripped): | ||
| return None | ||
| return stripped |
There was a problem hiding this comment.
Critical: Epoch name validation rejects values >= 1000 written by write_epoch_summary.
The regex \d{3}-\d{3} requires exactly 3 digits on each side, but write_epoch_summary (line 637) uses {start:03d}-{end:03d} which produces 4+ digits for values >= 1000 (e.g., "1000-1019.md"). This means epochs written with start or end >= 1000 cannot be read back with read_epoch.
🔧 Proposed fix
def _sanitize_epoch_name(name: str) -> str | None:
"""Return a safe filename for an epoch, or None if invalid."""
- # Expect format: NNN-NNN (e.g. "000-019")
+ # Expect format: NNN-NNN or more digits (e.g. "000-019", "1000-1099")
stripped = name.strip().rstrip(".md")
- if not re.fullmatch(r"\d{3}-\d{3}", stripped):
+ if not re.fullmatch(r"\d{3,}-\d{3,}", stripped):
return None
return stripped📝 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.
| def _sanitize_epoch_name(name: str) -> str | None: | |
| """Return a safe filename for an epoch, or None if invalid.""" | |
| # Expect format: NNN-NNN (e.g. "000-019") | |
| stripped = name.strip().rstrip(".md") | |
| if not re.fullmatch(r"\d{3}-\d{3}", stripped): | |
| return None | |
| return stripped | |
| def _sanitize_epoch_name(name: str) -> str | None: | |
| """Return a safe filename for an epoch, or None if invalid.""" | |
| # Expect format: NNN-NNN or more digits (e.g. "000-019", "1000-1099") | |
| stripped = name.strip().rstrip(".md") | |
| if not re.fullmatch(r"\d{3,}-\d{3,}", stripped): | |
| return None | |
| return stripped |
🤖 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/mcp/servers/mayfly_server.py` around lines 80 - 86,
_sanitize_epoch_name currently strips suffix with name.strip().rstrip(".md") and
enforces exactly 3 digits per side (re.fullmatch(r"\d{3}-\d{3}", ...)), which
rejects epoch names produced by write_epoch_summary when values >=1000; change
_sanitize_epoch_name to (1) remove the literal ".md" suffix safely (use
str.removesuffix(".md") or an endswith check and slice) instead of
rstrip(".md"), and (2) relax the regex to allow three-or-more digits on each
side (e.g. re.fullmatch(r"\d{3,}-\d{3,}", stripped) or simply
re.fullmatch(r"\d+-\d+", stripped)) so it accepts names generated by
write_epoch_summary; update _sanitize_epoch_name accordingly and ensure
write_epoch_summary remains compatible.
| # Seed JOURNAL.md | ||
| atomic_write( | ||
| layout.mayfly_journal, | ||
| "# Mayfly Research Journal\n\n" | ||
| "Append-only log of research sessions on this campaign. One row per session.\n\n" | ||
| "| step | outcome | summary | knowledge_updated | files |\n" | ||
| "|---:|---|---|---|---|\n", | ||
| ) |
There was a problem hiding this comment.
Critical: Journal table header missing metric column, breaking structure when append_journal_row includes metrics.
The bootstrap seeds a 5-column table (step, outcome, summary, knowledge_updated, files), but append_journal_row (lines 584-587) conditionally produces:
- 6 columns when
metricis provided:| step | metric | outcome | summary | knowledge_updated | files | - 5 columns when
metricis empty:| step | outcome | summary | knowledge_updated | files |
This creates an inconsistent markdown table that renders incorrectly.
🔧 Proposed fix: Always include metric column
Add a metric column header in the bootstrap table and always include it in append_journal_row (use empty string if not provided):
atomic_write(
layout.mayfly_journal,
"# Mayfly Research Journal\n\n"
"Append-only log of research sessions on this campaign. One row per session.\n\n"
- "| step | outcome | summary | knowledge_updated | files |\n"
- "|---:|---|---|---|---|\n",
+ "| step | metric | outcome | summary | knowledge_updated | files |\n"
+ "|---:|---|---|---|---|---|\n",
)Then in append_journal_row (lines 584-587), always use the 6-column format:
- if metric:
- new_row = f"| {step_str} | {_clean(metric)} | {_clean(outcome)} | {_clean(summary)} | {_clean(knowledge_updated)} | {_clean(files)} |\n"
- else:
- new_row = f"| {step_str} | {_clean(outcome)} | {_clean(summary)} | {_clean(knowledge_updated)} | {_clean(files)} |\n"
+ new_row = f"| {step_str} | {_clean(metric)} | {_clean(outcome)} | {_clean(summary)} | {_clean(knowledge_updated)} | {_clean(files)} |\n"🤖 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/mcp/servers/mayfly_server.py` around lines 170 - 177, The seeded
JOURNAL.md table is missing the metric column which causes malformed rows from
append_journal_row; update the atomic_write call that writes
layout.mayfly_journal to include a metric header (e.g., add "| metric |" to the
header row and adjust the separator row to six columns) and ensure
append_journal_row (the function that formats journal lines) always emits six
columns (use an empty string for metric when not provided) so the table
structure is consistent.
| if len(results) > _SEARCH_MAX_LINES: | ||
| truncated = len(results) - _SEARCH_MAX_LINES | ||
| results = results[:_SEARCH_MAX_LINES] | ||
| results.append(f"... {truncated} more lines truncated. Refine your query.") | ||
|
|
||
| return stable_mcp_response({"results": results, "count": len(results)}) |
There was a problem hiding this comment.
Minor: count includes truncation message line, making it ambiguous.
When results are truncated (lines 432-434), the truncation message is appended to results, then count is set to len(results). This makes count include the informational message as if it's a search result. Users cannot easily distinguish between the number of actual matches and the final array length.
💡 Proposed fix: Calculate count before truncation
+ original_count = len(results)
if len(results) > _SEARCH_MAX_LINES:
truncated = len(results) - _SEARCH_MAX_LINES
results = results[:_SEARCH_MAX_LINES]
results.append(f"... {truncated} more lines truncated. Refine your query.")
- return stable_mcp_response({"results": results, "count": len(results)})
+ return stable_mcp_response({"results": results, "count": original_count})Alternatively, put the truncation message in a separate "message" field instead of appending to results.
📝 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.
| if len(results) > _SEARCH_MAX_LINES: | |
| truncated = len(results) - _SEARCH_MAX_LINES | |
| results = results[:_SEARCH_MAX_LINES] | |
| results.append(f"... {truncated} more lines truncated. Refine your query.") | |
| return stable_mcp_response({"results": results, "count": len(results)}) | |
| original_count = len(results) | |
| if len(results) > _SEARCH_MAX_LINES: | |
| truncated = len(results) - _SEARCH_MAX_LINES | |
| results = results[:_SEARCH_MAX_LINES] | |
| results.append(f"... {truncated} more lines truncated. Refine your query.") | |
| return stable_mcp_response({"results": results, "count": original_count}) |
🤖 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/mcp/servers/mayfly_server.py` around lines 431 - 436, Calculate the
total match count before you truncate the results: capture total_count =
len(results) prior to the truncation block (using the variables shown: results,
_SEARCH_MAX_LINES, truncated), then perform the slicing and append the
truncation note but return stable_mcp_response with "count": total_count (or
alternatively add a separate "message" field for the truncation note instead of
appending it to results) so the count reflects actual matches not the
informational line.
| def _clean(s: str) -> str: | ||
| return s.replace("|", "\\|").strip() |
There was a problem hiding this comment.
Minor: Newlines in journal row fields break markdown table structure.
The _clean function escapes pipe characters but doesn't sanitize newlines. If any of the string parameters (outcome, summary, knowledge_updated, files, metric) contain newlines, the table row will span multiple lines and break the markdown table.
🛡️ Proposed fix: Replace newlines with spaces
def _clean(s: str) -> str:
- return s.replace("|", "\\|").strip()
+ return s.replace("|", "\\|").replace("\n", " ").replace("\r", " ").strip()📝 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.
| def _clean(s: str) -> str: | |
| return s.replace("|", "\\|").strip() | |
| def _clean(s: str) -> str: | |
| return s.replace("|", "\\|").replace("\n", " ").replace("\r", " ").strip() |
🤖 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/mcp/servers/mayfly_server.py` around lines 581 - 582, The _clean
function currently only escapes pipe characters but must also sanitize newlines
so Markdown table rows don't break; update def _clean(s: str) to replace newline
and carriage-return characters with a single space (e.g., s = s.replace("\r", "
").replace("\n", " ")), then perform the existing pipe escape and strip, and
ensure callers that pass outcome, summary, knowledge_updated, files, metric
continue to use _clean so all row fields are normalized.
- Sort imports in hooks/mayfly_capture.py and drop the unused MAYFLY_DIR_NAME import in mcp/servers/mayfly_server.py (ruff). - Make the Mayfly capture hook docstring/comments runtime-agnostic so the non-adapter sources no longer hardcode a runtime name. - Add the generated infra/gpd-mayfly.json public descriptor so the committed infra inventory matches build_public_descriptors(). - Regenerate the repo graph contract (hooks +1, mcp servers +1, infra/gpd-*.json now 9). - Move the execute-phase and research-phase Mayfly notebook maintenance steps out of the thin staged command wrappers into their workflow closeout/handoff stages, keeping the feature while staying within the staged-command projection budgets.
|
🤖 RoastBot: The author's GitHub display name is "Marctar The Mad." The PR title has the word "depth-tiered." These facts are related. |
Summary
gpd-mayflyMCP server (8th built-in) managing a depth-tiered knowledge graph atGPD/mayfly/: FRONTIER → MAP → topics → epochs → raw sessions. Agents can read/write the notebook via standard MCP tool calls.Stophook (mayfly_capture.py) that fires after every Claude response, detects when GPD commands ran via the lineage ledger, and appends a raw capture record tosession-log.jsonl— no LLM call, pure Python.<mayfly_maintenance>completion duties toexecute-phase,research-phase, anddiscuss-phaseso agents automatically update FRONTIER + knowledge graph as part of finishing work.gpd install(builtin_servers, entry point, hook registration, uninstall cleanup).SKILL.md— the portable Mayfly v2 protocol spec (harness-agnostic reference).How it runs automatically
After
gpd installre-runs, Claude Code gets thegpd-mayflyMCP server in.mcp.jsonand theStophook insettings.json. No user action needed:GPD/mayfly/structure silentlyupsert_knowledge+update_frontier+append_journal_rowas part of command teardownNotebook structure
Separate from the existing
GPD/knowledge/system (K-* YAML format). Can be merged later if the formats converge.Test plan
mayfly_serverandmayfly_captureimport cleanlygpd-mayflyappears inbuild_mcp_servers_dict()outputclaude_code.pyimportsensure_mayfly_stop_hookwithout errormain(pre-existing failures from unrelatedgpd-result-solveragent issue)🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
gpd-mcp-mayflyCLI command to access notebook toolsDocumentation