import from local vendored copy (2026-05-06)
Some checks failed
CI / validate (push) Failing after 0s

This commit is contained in:
Hongming Wang 2026-05-06 13:53:31 -07:00
commit a3cb0a7aba
16 changed files with 493 additions and 0 deletions

8
.gitattributes vendored Normal file
View File

@ -0,0 +1,8 @@
# Shell scripts and Python hooks are executed by Linux containers.
# Force LF so Windows checkouts (core.autocrlf=true) don't break the
# hook dispatch path — see Molecule-AI/molecule-core#507 where CRLF
# line endings made claude-code try to exec `session-start-context.py\r`
# and the SessionStart hook failed silently, producing
# "(no response generated)" on every agent A2A call.
*.sh text eol=lf
*.py text eol=lf

5
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,5 @@
name: CI
on: [push, pull_request]
jobs:
validate:
uses: Molecule-AI/molecule-ci/.github/workflows/validate-plugin.yml@main

25
.gitignore vendored Normal file
View File

@ -0,0 +1,25 @@
# Credentials — never commit. Use .env.example as the template.
.env
.env.local
.env.*.local
.env.*
!.env.example
!.env.sample
# Private keys + certs
*.pem
*.key
*.crt
*.p12
*.pfx
# Secret directories
.secrets/
# Workspace auth tokens
.auth-token
.auth_token
# Python bytecode
__pycache__/
*.py[cod]

View File

@ -0,0 +1 @@
pyyaml>=6.0

View File

@ -0,0 +1,50 @@
#!/usr/bin/env python3
"""Validate a Molecule AI plugin repo."""
import os, sys, yaml
errors = []
if not os.path.isfile("plugin.yaml"):
print("::error::plugin.yaml not found at repo root")
sys.exit(1)
with open("plugin.yaml") as f:
plugin = yaml.safe_load(f)
for field in ["name", "version", "description"]:
if not plugin.get(field):
errors.append(f"Missing required field: {field}")
v = str(plugin.get("version", ""))
if v and not all(c in "0123456789." for c in v):
errors.append(f"Invalid version format: {v}")
runtimes = plugin.get("runtimes")
if runtimes is not None and not isinstance(runtimes, list):
errors.append(f"runtimes must be a list, got {type(runtimes).__name__}")
content_paths = ["SKILL.md", "hooks", "skills", "rules"]
found = [p for p in content_paths if os.path.exists(p)]
if not found:
errors.append("Plugin must contain at least one of: SKILL.md, hooks/, skills/, rules/")
if os.path.isfile("SKILL.md"):
with open("SKILL.md") as f:
first_line = f.readline().strip()
if first_line and not first_line.startswith("#"):
print("::warning::SKILL.md should start with a markdown heading (e.g., # Plugin Name)")
if errors:
for e in errors:
print(f"::error::{e}")
sys.exit(1)
pn = plugin["name"]; pv = plugin["version"]
print(f"\u2713 plugin.yaml valid: {pn} v{pv}")
if found:
print(f" Content: {', '.join(found)}")
if runtimes:
print(f" Runtimes: {', '.join(runtimes)}")

106
CLAUDE.md Normal file
View File

@ -0,0 +1,106 @@
# molecule-session-context — Session Start Context Loader
`molecule-session-context` is a **session-initialisation hook plugin** that
auto-loads recent cron-learnings and repo PR/issue counts at `SessionStart`.
Pairs with `molecule-cron-learnings`.
**Version:** 1.0.0
**Runtime:** `claude_code`
---
## Repository Layout
```
molecule-session-context/
├── plugin.yaml — Plugin manifest
├── hooks/
│ └── session-start-context/
│ └── hook.json — SessionStart hook definition
└── adapters/ — Harness adaptors
```
---
## What It Does
At the start of every session, this hook:
1. Reads the last N lines of `~/.claude/projects/<project>/cron-learnings.jsonl`
2. Loads current PR/issue counts for the workspace repo
3. Surfaces this context to the agent in the first response
This means the agent enters every session already knowing:
- What went wrong last time (from cron-learnings)
- How many open PRs and issues exist (context before acting)
---
## SessionStart Hook
The hook fires on every new session for a workspace. Configure how many
learnings to load via workspace settings:
```json
{
"session_context": {
"learnings_lines": 20,
"include_pr_counts": true,
"include_issue_counts": true
}
}
```
---
## Development
### Prerequisites
- Python 3.11+
- `gh` CLI authenticated
- Write access to `Molecule-AI/molecule-ai-plugin-molecule-session-context`
### Setup
```bash
git clone https://github.com/Molecule-AI/molecule-ai-plugin-molecule-session-context.git
cd molecule-ai-plugin-molecule-session-context
# YAML validation
python3 -c "import yaml; yaml.safe_load(open('plugin.yaml'))"
```
### Pre-Commit Checklist
```bash
# YAML structure
python3 -c "import yaml; yaml.safe_load(open('plugin.yaml'))"
# Credential scan
python3 -c "
import re, sys
with open('plugin.yaml') as f:
content = f.read()
patterns = [r'sk.ant', r'ghp.', r'AKIA[A-Z0-9]']
if any(re.search(p, content) for p in patterns):
print('FAIL: possible credentials found')
sys.exit(1)
print('No credentials: OK')
"
```
---
## Release Process
1. Review changes: `git log origin/main..HEAD --oneline`
2. Bump `version` in `plugin.yaml` (semver)
3. Commit: `chore: bump version to X.Y.Z`
4. Tag and push: `git tag vX.Y.Z && git push origin main --tags`
5. Create GitHub Release with changelog
---
## Known Issues
See `known-issues.md` at the repo root.

19
README.md Normal file
View File

@ -0,0 +1,19 @@
# molecule-session-context
Molecule AI plugin. Install via the Molecule AI platform plugin system.
## Usage
### In org template (org.yaml)
```yaml
plugins:
- molecule-session-context
```
### From URL (community install)
```
github://Molecule-AI/molecule-ai-plugin-molecule-session-context
```
## License
Business Source License 1.1 — © Molecule AI.

0
adapters/__init__.py Normal file
View File

2
adapters/claude_code.py Normal file
View File

@ -0,0 +1,2 @@
"""Claude Code adaptor — uses the generic rule+skill+hooks installer."""
from plugins_registry.builtins import AgentskillsAdaptor as Adaptor # noqa: F401

46
hooks/_lib.py Executable file
View File

@ -0,0 +1,46 @@
"""Common helpers for Claude Code hooks. Imported by the .py hook scripts.
Hooks receive JSON on stdin per the Claude Code hook spec, and may emit
JSON on stdout or exit with code 2 to block. This module wraps both.
"""
import json
import sys
def read_input() -> dict:
"""Parse stdin JSON. Empty input → empty dict."""
raw = sys.stdin.read().strip()
if not raw:
return {}
try:
return json.loads(raw)
except json.JSONDecodeError:
return {}
def emit(payload: dict) -> None:
"""Print JSON payload to stdout for the harness to interpret."""
print(json.dumps(payload))
def deny_pretooluse(reason: str) -> None:
"""Emit a PreToolUse denial with reason and exit 0."""
emit({
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": reason,
}
})
sys.exit(0)
def add_context(text: str) -> None:
"""Emit additionalContext for SessionStart / UserPromptSubmit hooks."""
if text and text.strip():
emit({"additionalContext": text})
def warn_to_stderr(msg: str) -> None:
"""Non-blocking warning visible to the next agent turn via stderr."""
print(msg, file=sys.stderr)

71
hooks/session-start-context.py Executable file
View File

@ -0,0 +1,71 @@
#!/usr/bin/env python3
"""SessionStart hook — auto-load recent cron-learnings, freeze status,
and a one-line repo snapshot into Claude's context.
"""
import os
import subprocess
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from _lib import add_context, warn_to_stderr # noqa
REPO = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
LEARNINGS = os.path.expanduser(
"~/.claude/projects/-Users-hongming-Documents-GitHub-molecule-monorepo/memory/cron-learnings.jsonl"
)
FREEZE = os.path.join(REPO, ".claude", "freeze")
def tail(path: str, n: int) -> str:
if not os.path.isfile(path):
return ""
try:
with open(path) as f:
lines = f.readlines()
return "".join(lines[-n:]).rstrip()
except Exception:
return ""
def gh_count(args: list) -> str:
try:
out = subprocess.run(
["gh"] + args + ["--json", "number"],
capture_output=True, text=True, timeout=4,
)
if out.returncode != 0:
return "?"
import json
return str(len(json.loads(out.stdout or "[]")))
except Exception:
return "?"
def main() -> None:
parts = []
learnings = tail(LEARNINGS, 20)
if learnings:
parts.append(f"## Recent cron learnings (last 20)\n{learnings}")
if os.path.isfile(FREEZE):
try:
with open(FREEZE) as f:
frozen = f.readline().strip()
parts.append(f"## ⚠ FREEZE ACTIVE\nEdits restricted to: {frozen}\nRemove .claude/freeze to unlock.")
except Exception:
pass
pr = gh_count(["pr", "list", "--repo", "Molecule-AI/molecule-monorepo", "--state", "open"])
iss = gh_count(["issue", "list", "--repo", "Molecule-AI/molecule-monorepo", "--state", "open"])
parts.append(f"## Repo state\nOpen PRs: {pr} · Open issues: {iss}")
if parts:
add_context("\n\n".join(parts))
if __name__ == "__main__":
try:
main()
except Exception as e:
warn_to_stderr(f"[session-start hook error] {e}")
sys.exit(0)

2
hooks/session-start-context.sh Executable file
View File

@ -0,0 +1,2 @@
#!/usr/bin/env bash
exec python3 "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/session-start-context.py"

54
known-issues.md Normal file
View File

@ -0,0 +1,54 @@
# Known Issues — molecule-session-context
---
## Active Issues
*(None currently open. This section is updated when issues are filed.)*
---
## Recently Resolved
*(No recently resolved issues.)*
---
## How to Update This File
When a new issue is identified:
1. Add it under **Active Issues** using the template below
2. Include: symptom, cause (if known), workaround
3. When fixed, move to **Recently Resolved** and note the fix version
### Issue Template
```markdown
## [TICKET-NUMBER] <Short Title>
**Severity:** P0 / P1 / P2 / P3
**Status:** Workaround / Fix in progress / Fix available
**Affected versions:** All / vX.Y.Z+
**Symptoms:**
**Cause:**
**Workaround:**
**Fix (if available):**
```
---
## Severity Definitions
| Level | Description |
|---|---|
| P0 | Session start crashes; no context loaded |
| P1 | Context loaded but wrong workspace |
| P2 | Context stale or missing learnings |
| P3 | Cosmetic or documentation issue |
---
## Reporting
Use the Molecule-AI/internal issue tracker. Tag with `plugin-molecule-session-context`.

11
plugin.yaml Normal file
View File

@ -0,0 +1,11 @@
name: molecule-session-context
version: 1.0.0
description: Auto-load recent cron-learnings + repo PR/issue counts at SessionStart. Pairs well with molecule-cron-learnings.
author: Molecule AI
tags: [molecule, guardrails]
runtimes:
- claude_code
hooks:
- session-start-context

View File

@ -0,0 +1,92 @@
# Local Development Setup
This runbook covers setting up a local development environment for
`molecule-session-context`.
---
## Prerequisites
- Python 3.11+
- `gh` CLI authenticated
- Write access to `Molecule-AI/molecule-ai-plugin-molecule-session-context`
---
## Clone & Bootstrap
```bash
git clone https://github.com/Molecule-AI/molecule-ai-plugin-molecule-session-context.git
cd molecule-ai-plugin-molecule-session-context
```
---
## Validating Plugin Structure
```bash
# YAML structure
python3 -c "import yaml; yaml.safe_load(open('plugin.yaml'))"
echo "plugin.yaml OK"
# Check all hook paths exist
python3 -c "
import yaml, os
with open('plugin.yaml') as f:
data = yaml.safe_load(f)
for hook in data.get('hooks', []):
path = f'hooks/{hook}/hook.json'
exists = os.path.exists(path)
print(f'[{\"OK\" if exists else \"MISSING\"}] {path}')
"
```
---
## Testing the SessionStart Hook
The harness wrapper is provided by the Molecule AI platform at runtime. To test:
1. Install the plugin in a test workspace
2. Start a new session
3. Verify the first agent response references recent learnings
4. Check the cron-learnings JSONL file has entries
---
## Simulating Session Context
To test without a live workspace:
```bash
# Create a mock learnings file
mkdir -p ~/.claude/projects/test-project
cat > ~/.claude/projects/test-project/cron-learnings.jsonl << 'EOF'
{"tick": "2026-04-21T00:00Z", "role": "test-lead", "learnings": ["GH_TOKEN expired — refresh needed", "PR template missing Testing section"]}
EOF
# Simulate session start (hook reads this file)
cat ~/.claude/projects/test-project/cron-learnings.jsonl
```
---
## Troubleshooting
### No context loaded at session start
- Verify `hooks/session-start-context/hook.json` is correctly named and placed
- Check the hook is registered in `plugin.yaml`
- Verify the workspace has read access to `~/.claude/projects/`
### Stale learnings
- The hook reads the JSONL file on every session start — check file permissions
- If learnings are not being appended, check `molecule-cron-learnings` is installed
---
## Related
- `molecule-cron-learnings` — appends learnings at end of each tick
- `molecule-workflow-retro` — generates weekly retrospectives from learnings

1
settings-fragment.json Normal file
View File

@ -0,0 +1 @@
{"hooks":{"SessionStart":[{"hooks":[{"type":"command","command":"bash ${CLAUDE_DIR}/hooks/session-start-context.sh"}]}]}}