Add project-book.ipynb interactive notebook for Wave toolkit#2
Add project-book.ipynb interactive notebook for Wave toolkit#2
Conversation
Co-authored-by: toolate28 <105518313+toolate28@users.noreply.github.com>
…ry=list) Co-authored-by: toolate28 <105518313+toolate28@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This pull request adds an interactive Jupyter notebook (project-book.ipynb) that provides Python implementations of the Wave Toolkit's core PowerShell features, along with extended framework tooling for the SpiralSafe ecosystem. The notebook offers an alternative interface for working with Wave's context capture, prompt generation, and session management capabilities, plus additional tools for ecosystem exploration and repository analysis.
Changes:
- Added
project-book.ipynb- a comprehensive interactive notebook with Python implementations of Wave core features and ecosystem tooling - Updated
README.mdto document the new notebook in the Navigation section, Key Components table, and Project Structure diagram
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| project-book.ipynb | New interactive Jupyter notebook providing Python implementations of Wave toolkit features, framework analysis tools, log discovery, and ecosystem integration helpers |
| README.md | Added documentation references for the new notebook in three sections (Navigation, Key Components, and Project Structure) |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| " # ClaudeNPC logs\n", | ||
| " cnpc_dir = home / \"repos\" / \"ClaudeNPC-Server-Suite\"\n", |
There was a problem hiding this comment.
The discover_log_sources function contains hardcoded path assumptions (e.g., home / "SpiralSafe-FromGitHub", home / "repos"). These paths may not exist on all systems. While the code checks if directories exist before scanning, the hardcoded paths reduce portability. Consider making these paths configurable or documenting the expected directory structure for users.
| " # ClaudeNPC logs\n", | |
| " cnpc_dir = home / \"repos\" / \"ClaudeNPC-Server-Suite\"\n", | |
| " # ClaudeNPC logs (configurable via CLAUDENPC_LOG_DIR, with a default fallback)\n", | |
| " cnpc_dir_env = os.getenv(\"CLAUDENPC_LOG_DIR\")\n", | |
| " if cnpc_dir_env:\n", | |
| " cnpc_dir = Path(cnpc_dir_env).expanduser()\n", | |
| " else:\n", | |
| " cnpc_dir = home / \"repos\" / \"ClaudeNPC-Server-Suite\"\n", |
| " return summary\n", | ||
| "\n", | ||
| "\n", | ||
| "# Generate and display summary for demo session\n", |
There was a problem hiding this comment.
The notebook creates a global demo_session variable in one cell (line 295) and then modifies it in a later cell (line 908-909). This creates an implicit dependency between cells that may cause issues if cells are run out of order. Consider either documenting the cell execution order requirement or making the session management more explicit to avoid potential NameErrors if cells are executed independently.
| "# Generate and display summary for demo session\n", | |
| "# Generate and display summary for demo session\n", | |
| "try:\n", | |
| " demo_session # type: ignore[name-defined]\n", | |
| "except NameError as exc:\n", | |
| " raise RuntimeError(\n", | |
| " \"demo_session is not defined. Please run the cell that creates the demo \"\n", | |
| " \"WaveSession (the demo session setup cell) before running this one.\"\n", | |
| " ) from exc\n", | |
| "\n", |
| "\n", | ||
| "def is_git_repo(path: str = \".\") -> bool:\n", | ||
| " \"\"\"Check if the given path is inside a git repository.\"\"\"\n", | ||
| " return Path(path, \".git\").exists() or Path(path).joinpath(\".git\").exists()\n", |
There was a problem hiding this comment.
The is_git_repo function has redundant logic. Both Path(path, ".git").exists() and Path(path).joinpath(".git").exists() check the same thing. The Path(path, ".git") constructor is equivalent to Path(path).joinpath(".git"). Only one of these checks is needed.
| " return Path(path, \".git\").exists() or Path(path).joinpath(\".git\").exists()\n", | |
| " return Path(path, \".git\").exists()\n", |
| "execution_count": null, | ||
| "metadata": {}, | ||
| "outputs": [], | ||
| "source": "@dataclass\nclass WaveSession:\n \"\"\"Represents a Wave collaboration session.\"\"\"\n session_id: str\n timestamp: str\n context: WaveContext\n system_prompt: str\n task: Optional[str] = None\n log_entries: List[Dict[str, Any]] = field(default_factory=list)\n \n def add_log(self, entry_type: str, content: str):\n \"\"\"Add a log entry to the session.\"\"\"\n self.log_entries.append({\n \"timestamp\": datetime.now().isoformat(),\n \"type\": entry_type,\n \"content\": content\n })\n \n def save(self, output_dir: str = \".claude/logs/sessions\"):\n \"\"\"Save the session log to a file.\"\"\"\n output_path = Path(output_dir)\n output_path.mkdir(parents=True, exist_ok=True)\n \n log_file = output_path / f\"session_{self.session_id}.json\"\n \n session_data = {\n \"session_id\": self.session_id,\n \"timestamp\": self.timestamp,\n \"task\": self.task,\n \"context\": self.context.to_dict(),\n \"system_prompt\": self.system_prompt,\n \"log_entries\": self.log_entries\n }\n \n with open(log_file, \"w\", encoding=\"utf-8\") as f:\n json.dump(session_data, f, indent=2)\n \n return str(log_file)\n\n\ndef create_wave_session(task: Optional[str] = None) -> WaveSession:\n \"\"\"\n Create a new Wave session with captured context.\n \n Args:\n task: Optional task description for the session\n \n Returns:\n WaveSession object\n \"\"\"\n ctx = get_wave_context()\n session_id = datetime.now().strftime(\"%Y%m%d_%H%M%S\")\n \n session = WaveSession(\n session_id=session_id,\n timestamp=ctx.timestamp,\n context=ctx,\n system_prompt=generate_system_prompt(ctx),\n task=task\n )\n \n session.add_log(\"session_start\", f\"Session created: {session_id}\")\n if task:\n session.add_log(\"task\", task)\n \n return session\n\n\n# Create a demo session\ndemo_session = create_wave_session(\"Explore Wave Toolkit Project Book\")\nprint(f\"\ud83d\udcdd Session Created: {demo_session.session_id}\")\nprint(f\" Task: {demo_session.task}\")\nprint(f\" Log Entries: {len(demo_session.log_entries)}\")" |
There was a problem hiding this comment.
The create_wave_session function creates two separate timestamps: one when creating the session_id and another when capturing context via get_wave_context(). This could result in slightly different timestamps for the session. Consider using a single timestamp captured at the start of the function for consistency, or document why two different timestamps are intentional.
| "source": "@dataclass\nclass WaveSession:\n \"\"\"Represents a Wave collaboration session.\"\"\"\n session_id: str\n timestamp: str\n context: WaveContext\n system_prompt: str\n task: Optional[str] = None\n log_entries: List[Dict[str, Any]] = field(default_factory=list)\n \n def add_log(self, entry_type: str, content: str):\n \"\"\"Add a log entry to the session.\"\"\"\n self.log_entries.append({\n \"timestamp\": datetime.now().isoformat(),\n \"type\": entry_type,\n \"content\": content\n })\n \n def save(self, output_dir: str = \".claude/logs/sessions\"):\n \"\"\"Save the session log to a file.\"\"\"\n output_path = Path(output_dir)\n output_path.mkdir(parents=True, exist_ok=True)\n \n log_file = output_path / f\"session_{self.session_id}.json\"\n \n session_data = {\n \"session_id\": self.session_id,\n \"timestamp\": self.timestamp,\n \"task\": self.task,\n \"context\": self.context.to_dict(),\n \"system_prompt\": self.system_prompt,\n \"log_entries\": self.log_entries\n }\n \n with open(log_file, \"w\", encoding=\"utf-8\") as f:\n json.dump(session_data, f, indent=2)\n \n return str(log_file)\n\n\ndef create_wave_session(task: Optional[str] = None) -> WaveSession:\n \"\"\"\n Create a new Wave session with captured context.\n \n Args:\n task: Optional task description for the session\n \n Returns:\n WaveSession object\n \"\"\"\n ctx = get_wave_context()\n session_id = datetime.now().strftime(\"%Y%m%d_%H%M%S\")\n \n session = WaveSession(\n session_id=session_id,\n timestamp=ctx.timestamp,\n context=ctx,\n system_prompt=generate_system_prompt(ctx),\n task=task\n )\n \n session.add_log(\"session_start\", f\"Session created: {session_id}\")\n if task:\n session.add_log(\"task\", task)\n \n return session\n\n\n# Create a demo session\ndemo_session = create_wave_session(\"Explore Wave Toolkit Project Book\")\nprint(f\"\ud83d\udcdd Session Created: {demo_session.session_id}\")\nprint(f\" Task: {demo_session.task}\")\nprint(f\" Log Entries: {len(demo_session.log_entries)}\")" | |
| "source": "@dataclass\nclass WaveSession:\n \"\"\"Represents a Wave collaboration session.\"\"\"\n session_id: str\n timestamp: str\n context: WaveContext\n system_prompt: str\n task: Optional[str] = None\n log_entries: List[Dict[str, Any]] = field(default_factory=list)\n \n def add_log(self, entry_type: str, content: str):\n \"\"\"Add a log entry to the session.\"\"\"\n self.log_entries.append({\n \"timestamp\": datetime.now().isoformat(),\n \"type\": entry_type,\n \"content\": content\n })\n \n def save(self, output_dir: str = \".claude/logs/sessions\"):\n \"\"\"Save the session log to a file.\"\"\"\n output_path = Path(output_dir)\n output_path.mkdir(parents=True, exist_ok=True)\n \n log_file = output_path / f\"session_{self.session_id}.json\"\n \n session_data = {\n \"session_id\": self.session_id,\n \"timestamp\": self.timestamp,\n \"task\": self.task,\n \"context\": self.context.to_dict(),\n \"system_prompt\": self.system_prompt,\n \"log_entries\": self.log_entries\n }\n \n with open(log_file, \"w\", encoding=\"utf-8\") as f:\n json.dump(session_data, f, indent=2)\n \n return str(log_file)\n\n\ndef create_wave_session(task: Optional[str] = None) -> WaveSession:\n \"\"\"\n Create a new Wave session with captured context.\n\n The session timestamp (`WaveSession.timestamp`) is taken from the\n captured `WaveContext` (`ctx.timestamp`), which records when the\n environment/context snapshot was created. The `session_id` is a\n human-readable identifier generated at session construction time\n and may differ slightly from `ctx.timestamp`.\n \n Args:\n task: Optional task description for the session\n \n Returns:\n WaveSession object\n \"\"\"\n # Capture context (and its timestamp) first; `session_id` is generated\n # separately and may differ slightly in time from `ctx.timestamp`.\n ctx = get_wave_context()\n session_id = datetime.now().strftime(\"%Y%m%d_%H%M%S\")\n \n session = WaveSession(\n session_id=session_id,\n timestamp=ctx.timestamp,\n context=ctx,\n system_prompt=generate_system_prompt(ctx),\n task=task\n )\n \n session.add_log(\"session_start\", f\"Session created: {session_id}\")\n if task:\n session.add_log(\"task\", task)\n \n return session\n\n\n# Create a demo session\ndemo_session = create_wave_session(\"Explore Wave Toolkit Project Book\")\nprint(f\"\ud83d\udcdd Session Created: {demo_session.session_id}\")\nprint(f\" Task: {demo_session.task}\")\nprint(f\" Log Entries: {len(demo_session.log_entries)}\")" |
| " for item in path.rglob(\"*\"):\n", | ||
| " # Skip hidden directories and common ignore patterns\n", | ||
| " if any(part.startswith(\".\") for part in item.parts):\n", | ||
| " continue\n", | ||
| " if \"node_modules\" in item.parts or \"__pycache__\" in item.parts:\n", | ||
| " continue\n", | ||
| " \n", | ||
| " if item.is_file():\n", | ||
| " file_count += 1\n", | ||
| " ext = item.suffix.lower()\n", | ||
| " if ext in lang_extensions:\n", | ||
| " lang = lang_extensions[ext]\n", | ||
| " languages[lang] = languages.get(lang, 0) + 1\n", |
There was a problem hiding this comment.
The analyze_framework function uses path.rglob("*") which will recursively scan all files in a repository. For large repositories, this could be slow. While the code does skip common directories like node_modules and __pycache__, this happens after iteration, not before. Consider using a more efficient approach or documenting the performance characteristics for large repositories.
| " for item in path.rglob(\"*\"):\n", | |
| " # Skip hidden directories and common ignore patterns\n", | |
| " if any(part.startswith(\".\") for part in item.parts):\n", | |
| " continue\n", | |
| " if \"node_modules\" in item.parts or \"__pycache__\" in item.parts:\n", | |
| " continue\n", | |
| " \n", | |
| " if item.is_file():\n", | |
| " file_count += 1\n", | |
| " ext = item.suffix.lower()\n", | |
| " if ext in lang_extensions:\n", | |
| " lang = lang_extensions[ext]\n", | |
| " languages[lang] = languages.get(lang, 0) + 1\n", | |
| " import os\n", | |
| " for root, dirs, files in os.walk(path):\n", | |
| " # Prune hidden and common large/temporary directories before descending\n", | |
| " dirs[:] = [\n", | |
| " d for d in dirs\n", | |
| " if not d.startswith(\".\") and d not in {\"node_modules\", \"__pycache__\"}\n", | |
| " ]\n", | |
| " root_path = Path(root)\n", | |
| " for filename in files:\n", | |
| " # Skip hidden files\n", | |
| " if filename.startswith(\".\"):\n", | |
| " continue\n", | |
| " item = root_path / filename\n", | |
| " if item.is_file():\n", | |
| " file_count += 1\n", | |
| " ext = item.suffix.lower()\n", | |
| " if ext in lang_extensions:\n", | |
| " lang = lang_extensions[ext]\n", | |
| " languages[lang] = languages.get(lang, 0) + 1\n", |
|
@copilot these changes too |
This pull request updates the project documentation to highlight the addition of an interactive Jupyter notebook, called the Project Book, which provides hands-on tools for framework exploration and ecosystem integration. The changes improve the visibility and accessibility of this new resource across the README.
Documentation updates:
README.md, introducing the Project Book as an interactive Jupyter notebook for framework tooling and ecosystem integration.README.mdto include the Project Book and its file location (project-book.ipynb).project-book.ipynbin the directory structure overview inREADME.mdto reflect its presence in the repository.This pull request updates the documentation for the Wave Toolkit by introducing a new interactive resource: the Project Book Jupyter notebook. The changes highlight the addition of this notebook as a key tool for framework tooling and ecosystem integration, and update relevant sections of theREADME.mdto reflect its presence.Documentation updates for the new interactive tool:
README.mdto introduce the Project Book, an interactive Jupyter notebook for framework tooling and ecosystem integration.README.mdto listproject-book.ipynbas part of the repository's top-level files.Adds an interactive Jupyter notebook that provides Python implementations of the Wave toolkit's core features, extended with framework tooling for the SpiralSafe ecosystem.Core Features (Python equivalents of existing PowerShell scripts)
get_wave_context()- Environment capture (mirrorsGet-WaveContext.ps1)generate_system_prompt()- Context-aware prompt generation (mirrorsNew-ClaudeSystemPrompt.ps1)WaveSessiondataclass - Session management with logging (mirrorsInvoke-ClaudeSession.ps1)Framework Extension Tools
Wave.Logging.psm1patternsInteractive Tooling
AI_AGENTS.mdtemplate generatorExample usage
README updated with navigation links and component table entries.
Original prompt
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.