monoco-toolkit 0.1.1__py3-none-any.whl → 0.2.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- monoco/cli/__init__.py +0 -0
- monoco/cli/project.py +87 -0
- monoco/cli/workspace.py +46 -0
- monoco/core/agent/__init__.py +5 -0
- monoco/core/agent/action.py +144 -0
- monoco/core/agent/adapters.py +106 -0
- monoco/core/agent/protocol.py +31 -0
- monoco/core/agent/state.py +106 -0
- monoco/core/config.py +152 -17
- monoco/core/execution.py +62 -0
- monoco/core/feature.py +58 -0
- monoco/core/git.py +51 -2
- monoco/core/injection.py +196 -0
- monoco/core/integrations.py +234 -0
- monoco/core/lsp.py +61 -0
- monoco/core/output.py +13 -2
- monoco/core/registry.py +36 -0
- monoco/core/resources/en/AGENTS.md +8 -0
- monoco/core/resources/en/SKILL.md +66 -0
- monoco/core/resources/zh/AGENTS.md +8 -0
- monoco/core/resources/zh/SKILL.md +66 -0
- monoco/core/setup.py +88 -110
- monoco/core/skills.py +444 -0
- monoco/core/state.py +53 -0
- monoco/core/sync.py +224 -0
- monoco/core/telemetry.py +4 -1
- monoco/core/workspace.py +85 -20
- monoco/daemon/app.py +127 -58
- monoco/daemon/models.py +4 -0
- monoco/daemon/services.py +56 -155
- monoco/features/agent/commands.py +166 -0
- monoco/features/agent/doctor.py +30 -0
- monoco/features/config/commands.py +125 -44
- monoco/features/i18n/adapter.py +29 -0
- monoco/features/i18n/commands.py +89 -10
- monoco/features/i18n/core.py +113 -27
- monoco/features/i18n/resources/en/AGENTS.md +8 -0
- monoco/features/i18n/resources/en/SKILL.md +94 -0
- monoco/features/i18n/resources/zh/AGENTS.md +8 -0
- monoco/features/i18n/resources/zh/SKILL.md +94 -0
- monoco/features/issue/adapter.py +34 -0
- monoco/features/issue/commands.py +183 -65
- monoco/features/issue/core.py +172 -77
- monoco/features/issue/linter.py +215 -116
- monoco/features/issue/migration.py +134 -0
- monoco/features/issue/models.py +23 -19
- monoco/features/issue/monitor.py +94 -0
- monoco/features/issue/resources/en/AGENTS.md +15 -0
- monoco/features/issue/resources/en/SKILL.md +87 -0
- monoco/features/issue/resources/zh/AGENTS.md +15 -0
- monoco/features/issue/resources/zh/SKILL.md +114 -0
- monoco/features/issue/validator.py +269 -0
- monoco/features/pty/core.py +185 -0
- monoco/features/pty/router.py +138 -0
- monoco/features/pty/server.py +56 -0
- monoco/features/spike/adapter.py +30 -0
- monoco/features/spike/commands.py +45 -24
- monoco/features/spike/core.py +4 -21
- monoco/features/spike/resources/en/AGENTS.md +7 -0
- monoco/features/spike/resources/en/SKILL.md +74 -0
- monoco/features/spike/resources/zh/AGENTS.md +7 -0
- monoco/features/spike/resources/zh/SKILL.md +74 -0
- monoco/main.py +115 -2
- {monoco_toolkit-0.1.1.dist-info → monoco_toolkit-0.2.5.dist-info}/METADATA +2 -2
- monoco_toolkit-0.2.5.dist-info/RECORD +77 -0
- monoco_toolkit-0.1.1.dist-info/RECORD +0 -33
- {monoco_toolkit-0.1.1.dist-info → monoco_toolkit-0.2.5.dist-info}/WHEEL +0 -0
- {monoco_toolkit-0.1.1.dist-info → monoco_toolkit-0.2.5.dist-info}/entry_points.txt +0 -0
- {monoco_toolkit-0.1.1.dist-info → monoco_toolkit-0.2.5.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
|
|
2
|
+
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Query
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
from typing import Optional, Dict
|
|
5
|
+
import json
|
|
6
|
+
import asyncio
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from monoco.features.pty.core import PTYManager
|
|
11
|
+
from monoco.core.config import get_config
|
|
12
|
+
|
|
13
|
+
# We will use dependency injection or a global singleton for now
|
|
14
|
+
# Ideally attached to app state
|
|
15
|
+
pty_manager = PTYManager()
|
|
16
|
+
|
|
17
|
+
router = APIRouter(prefix="/api/v1/pty", tags=["pty"])
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger("monoco.pty")
|
|
20
|
+
|
|
21
|
+
@router.websocket("/ws/{session_id}")
|
|
22
|
+
async def websocket_pty_endpoint(
|
|
23
|
+
websocket: WebSocket,
|
|
24
|
+
session_id: str,
|
|
25
|
+
cwd: Optional[str] = Query(None),
|
|
26
|
+
cols: int = Query(80),
|
|
27
|
+
rows: int = Query(24),
|
|
28
|
+
env: Optional[str] = Query(None) # JSON-encoded env vars
|
|
29
|
+
):
|
|
30
|
+
await websocket.accept()
|
|
31
|
+
|
|
32
|
+
# Determine working directory
|
|
33
|
+
# 1. Provide explicit CWD in query
|
|
34
|
+
# 2. Or fallback to ProjectRoot from env (if integrated)
|
|
35
|
+
# 3. Or fallback to process CWD
|
|
36
|
+
|
|
37
|
+
# Since monoco pty runs as a separate service, we expect CWD to be passed
|
|
38
|
+
# or we default to where monoco pty was started
|
|
39
|
+
working_dir = cwd if cwd else os.getcwd()
|
|
40
|
+
|
|
41
|
+
# Prepare environment
|
|
42
|
+
env_vars = os.environ.copy()
|
|
43
|
+
env_vars["TERM"] = "xterm-256color"
|
|
44
|
+
env_vars["COLORTERM"] = "truecolor"
|
|
45
|
+
if "SHELL" not in env_vars:
|
|
46
|
+
env_vars["SHELL"] = "/bin/zsh"
|
|
47
|
+
if "HOME" not in env_vars:
|
|
48
|
+
import pathlib
|
|
49
|
+
env_vars["HOME"] = str(pathlib.Path.home())
|
|
50
|
+
|
|
51
|
+
# Filter out Trae/Gemini specific variables to avoid shell integration conflicts
|
|
52
|
+
# This prevents the shell from trying to write to IDE-specific logs which causes EPERM
|
|
53
|
+
keys_to_remove = [k for k in env_vars.keys() if k.startswith("TRAE_") or k.startswith("GEMINI_") or k == "AI_AGENT"]
|
|
54
|
+
for k in keys_to_remove:
|
|
55
|
+
del env_vars[k]
|
|
56
|
+
|
|
57
|
+
if env:
|
|
58
|
+
try:
|
|
59
|
+
custom_env = json.loads(env)
|
|
60
|
+
env_vars.update(custom_env)
|
|
61
|
+
except:
|
|
62
|
+
logger.warning("Failed to parse custom env vars")
|
|
63
|
+
|
|
64
|
+
# Start Session
|
|
65
|
+
try:
|
|
66
|
+
session = pty_manager.create_session(
|
|
67
|
+
session_id=session_id,
|
|
68
|
+
cwd=working_dir,
|
|
69
|
+
cmd=["/bin/zsh", "-l"], # Use login shell to ensure full user environment
|
|
70
|
+
env=env_vars
|
|
71
|
+
)
|
|
72
|
+
session.start(cols, rows)
|
|
73
|
+
except Exception as e:
|
|
74
|
+
logger.error(f"Failed to start session: {e}")
|
|
75
|
+
await websocket.close(code=1011)
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
# Pipe Loop
|
|
79
|
+
reader_task = None
|
|
80
|
+
try:
|
|
81
|
+
# Task to read from PTY and send to WebSocket
|
|
82
|
+
async def pty_reader():
|
|
83
|
+
while session.running:
|
|
84
|
+
data = await session.read()
|
|
85
|
+
if not data:
|
|
86
|
+
break
|
|
87
|
+
# xterm.js expects string or binary. We send string/bytes.
|
|
88
|
+
# Usually text is fine, but binary is safer for control codes.
|
|
89
|
+
await websocket.send_bytes(data)
|
|
90
|
+
|
|
91
|
+
# If PTY exits, close WS
|
|
92
|
+
await websocket.close()
|
|
93
|
+
|
|
94
|
+
reader_task = asyncio.create_task(pty_reader())
|
|
95
|
+
|
|
96
|
+
# Main loop: Read from WebSocket and write to PTY
|
|
97
|
+
try:
|
|
98
|
+
while True:
|
|
99
|
+
# Receive message from Client (xterm.js)
|
|
100
|
+
# Message can be simple input string, or a JSON command (resize)
|
|
101
|
+
message = await websocket.receive()
|
|
102
|
+
|
|
103
|
+
if message["type"] == "websocket.disconnect":
|
|
104
|
+
raise WebSocketDisconnect(code=message.get("code", 1000))
|
|
105
|
+
|
|
106
|
+
if "text" in message:
|
|
107
|
+
payload = message["text"]
|
|
108
|
+
|
|
109
|
+
# Check if it's a control message (Hack: usually client sends raw input)
|
|
110
|
+
# We can enforce a protocol: binary for Input, text JSON for Control.
|
|
111
|
+
try:
|
|
112
|
+
# Try parsing as JSON control message
|
|
113
|
+
cmd = json.loads(payload)
|
|
114
|
+
if cmd.get("type") == "resize":
|
|
115
|
+
session.resize(cmd["cols"], cmd["rows"])
|
|
116
|
+
continue
|
|
117
|
+
except:
|
|
118
|
+
pass # Not JSON, treat as raw input
|
|
119
|
+
|
|
120
|
+
session.write(payload.encode())
|
|
121
|
+
|
|
122
|
+
elif "bytes" in message:
|
|
123
|
+
session.write(message["bytes"])
|
|
124
|
+
except RuntimeError:
|
|
125
|
+
# Handle "Cannot call 'receive' once a disconnect message has been received"
|
|
126
|
+
# This happens if Starlette/FastAPI already processed the disconnect internally
|
|
127
|
+
# but we called receive() again.
|
|
128
|
+
logger.info(f"Runtime disconnect for session {session_id}")
|
|
129
|
+
|
|
130
|
+
except WebSocketDisconnect:
|
|
131
|
+
logger.info(f"Client disconnected for session {session_id}")
|
|
132
|
+
except Exception as e:
|
|
133
|
+
logger.error(f"WebSocket error: {e}")
|
|
134
|
+
finally:
|
|
135
|
+
# Cleanup
|
|
136
|
+
pty_manager.close_session(session_id)
|
|
137
|
+
if reader_task and not reader_task.done():
|
|
138
|
+
reader_task.cancel()
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import signal
|
|
3
|
+
import sys
|
|
4
|
+
from typing import Optional
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from contextlib import asynccontextmanager
|
|
7
|
+
import uvicorn
|
|
8
|
+
from fastapi import FastAPI
|
|
9
|
+
from monoco.features.pty.router import router as pty_router, pty_manager
|
|
10
|
+
|
|
11
|
+
@asynccontextmanager
|
|
12
|
+
async def lifespan(app: FastAPI):
|
|
13
|
+
# Startup
|
|
14
|
+
yield
|
|
15
|
+
# Shutdown
|
|
16
|
+
logging.info("Shutting down PTY manager and cleaning up sessions...")
|
|
17
|
+
pty_manager.close_all_sessions()
|
|
18
|
+
|
|
19
|
+
def run_pty_server(host: str = "127.0.0.1", port: int = 3124, cwd: Optional[Path] = None):
|
|
20
|
+
"""
|
|
21
|
+
Entry point for the 'monoco pty' command.
|
|
22
|
+
"""
|
|
23
|
+
# Configure Logging
|
|
24
|
+
logging.basicConfig(level=logging.INFO)
|
|
25
|
+
|
|
26
|
+
# Register a manual signal handler to ensure we catch termination even if uvicorn misses it
|
|
27
|
+
# or if we are stuck before uvicorn starts.
|
|
28
|
+
def handle_signal(signum, frame):
|
|
29
|
+
logging.info(f"Received signal {signum}, initiating shutdown...")
|
|
30
|
+
# We rely on uvicorn to handle the actual exit loop for SIGINT/SIGTERM usually,
|
|
31
|
+
# but having this log confirms propagation.
|
|
32
|
+
# If uvicorn is running, it should catch this first.
|
|
33
|
+
# If not, we exit manually.
|
|
34
|
+
sys.exit(0)
|
|
35
|
+
|
|
36
|
+
# Note: Uvicorn overwrites SIGINT/SIGTERM handlers by default.
|
|
37
|
+
# relying on lifespan is the standard "Uvicorn way".
|
|
38
|
+
|
|
39
|
+
app = FastAPI(title="Monoco PTY Service", lifespan=lifespan)
|
|
40
|
+
app.include_router(pty_router)
|
|
41
|
+
|
|
42
|
+
# If cwd is provided, we might want to set it as current process CWD
|
|
43
|
+
# so that new sessions default to it.
|
|
44
|
+
if cwd and cwd.exists():
|
|
45
|
+
import os
|
|
46
|
+
os.chdir(cwd)
|
|
47
|
+
logging.info(f"PTY Service Root: {cwd}")
|
|
48
|
+
|
|
49
|
+
logging.info(f"Starting Monoco PTY Service on ws://{host}:{port}")
|
|
50
|
+
try:
|
|
51
|
+
uvicorn.run(app, host=host, port=port)
|
|
52
|
+
except KeyboardInterrupt:
|
|
53
|
+
pass
|
|
54
|
+
finally:
|
|
55
|
+
# Final safety net
|
|
56
|
+
pty_manager.close_all_sessions()
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Dict
|
|
3
|
+
from monoco.core.feature import MonocoFeature, IntegrationData
|
|
4
|
+
from monoco.features.spike import core
|
|
5
|
+
|
|
6
|
+
class SpikeFeature(MonocoFeature):
|
|
7
|
+
@property
|
|
8
|
+
def name(self) -> str:
|
|
9
|
+
return "spike"
|
|
10
|
+
|
|
11
|
+
def initialize(self, root: Path, config: Dict) -> None:
|
|
12
|
+
spikes_name = config.get("paths", {}).get("spikes", ".references")
|
|
13
|
+
core.init(root, spikes_name)
|
|
14
|
+
|
|
15
|
+
def integrate(self, root: Path, config: Dict) -> IntegrationData:
|
|
16
|
+
# Determine language from config, default to 'en'
|
|
17
|
+
lang = config.get("i18n", {}).get("source_lang", "en")
|
|
18
|
+
base_dir = Path(__file__).parent / "resources"
|
|
19
|
+
|
|
20
|
+
prompt_file = base_dir / lang / "AGENTS.md"
|
|
21
|
+
if not prompt_file.exists():
|
|
22
|
+
prompt_file = base_dir / "en" / "AGENTS.md"
|
|
23
|
+
|
|
24
|
+
content = ""
|
|
25
|
+
if prompt_file.exists():
|
|
26
|
+
content = prompt_file.read_text(encoding="utf-8").strip()
|
|
27
|
+
|
|
28
|
+
return IntegrationData(
|
|
29
|
+
system_prompts={"Spike (Research)": content}
|
|
30
|
+
)
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import typer
|
|
2
2
|
from pathlib import Path
|
|
3
3
|
from rich.console import Console
|
|
4
|
+
from typing import Annotated
|
|
4
5
|
|
|
5
6
|
from monoco.core.config import get_config
|
|
7
|
+
from monoco.core.output import AgentOutput, OutputManager
|
|
6
8
|
from . import core
|
|
7
9
|
|
|
8
10
|
app = typer.Typer(help="Spike & Repo Management.")
|
|
9
|
-
console = Console()
|
|
10
11
|
|
|
11
12
|
@app.command("init")
|
|
12
|
-
def init(
|
|
13
|
+
def init(
|
|
14
|
+
json: AgentOutput = False,
|
|
15
|
+
):
|
|
13
16
|
"""Initialize the Spike environment (gitignore setup)."""
|
|
14
17
|
config = get_config()
|
|
15
18
|
root_dir = Path(config.paths.root)
|
|
@@ -20,11 +23,16 @@ def init():
|
|
|
20
23
|
# Create the directory
|
|
21
24
|
(root_dir / spikes_dir_name).mkdir(exist_ok=True)
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+
OutputManager.print({
|
|
27
|
+
"status": "initialized",
|
|
28
|
+
"directory": spikes_dir_name,
|
|
29
|
+
"gitignore_updated": True
|
|
30
|
+
})
|
|
24
31
|
|
|
25
32
|
@app.command("add")
|
|
26
33
|
def add_repo(
|
|
27
34
|
url: str = typer.Argument(..., help="Git Repository URL"),
|
|
35
|
+
json: AgentOutput = False,
|
|
28
36
|
):
|
|
29
37
|
"""Add a new research repository."""
|
|
30
38
|
config = get_config()
|
|
@@ -38,13 +46,18 @@ def add_repo(
|
|
|
38
46
|
name = name[:-4]
|
|
39
47
|
|
|
40
48
|
core.update_config_repos(root_dir, name, url)
|
|
41
|
-
|
|
42
|
-
|
|
49
|
+
OutputManager.print({
|
|
50
|
+
"status": "added",
|
|
51
|
+
"name": name,
|
|
52
|
+
"url": url,
|
|
53
|
+
"message": f"Run 'monoco spike sync' to download content."
|
|
54
|
+
})
|
|
43
55
|
|
|
44
56
|
@app.command("remove")
|
|
45
57
|
def remove_repo(
|
|
46
58
|
name: str = typer.Argument(..., help="Repository Name"),
|
|
47
59
|
force: bool = typer.Option(False, "--force", "-f", help="Force delete physical directory without asking"),
|
|
60
|
+
json: AgentOutput = False,
|
|
48
61
|
):
|
|
49
62
|
"""Remove a repository from configuration."""
|
|
50
63
|
config = get_config()
|
|
@@ -52,30 +65,34 @@ def remove_repo(
|
|
|
52
65
|
spikes_dir = root_dir / config.paths.spikes
|
|
53
66
|
|
|
54
67
|
if name not in config.project.spike_repos:
|
|
55
|
-
|
|
68
|
+
OutputManager.error(f"Repo {name} not found in configuration.")
|
|
56
69
|
return
|
|
57
70
|
|
|
58
71
|
# Remove from config
|
|
59
72
|
core.update_config_repos(root_dir, name, "", remove=True)
|
|
60
|
-
console.print(f"[green]✔[/green] Removed [bold]{name}[/bold] from configuration.")
|
|
61
73
|
|
|
62
74
|
target_path = spikes_dir / name
|
|
75
|
+
deleted = False
|
|
63
76
|
if target_path.exists():
|
|
64
77
|
if force or typer.confirm(f"Do you want to delete the directory {target_path}?", default=False):
|
|
65
78
|
core.remove_repo_dir(spikes_dir, name)
|
|
66
|
-
|
|
79
|
+
deleted = True
|
|
67
80
|
else:
|
|
68
|
-
|
|
81
|
+
deleted = False
|
|
82
|
+
|
|
83
|
+
OutputManager.print({
|
|
84
|
+
"status": "removed",
|
|
85
|
+
"name": name,
|
|
86
|
+
"directory_deleted": deleted
|
|
87
|
+
})
|
|
69
88
|
|
|
70
89
|
@app.command("sync")
|
|
71
|
-
def sync_repos(
|
|
90
|
+
def sync_repos(
|
|
91
|
+
json: AgentOutput = False,
|
|
92
|
+
):
|
|
72
93
|
"""Sync (Clone/Pull) all configured repositories."""
|
|
73
94
|
# Force reload config to get latest updates
|
|
74
95
|
config = get_config()
|
|
75
|
-
# Note: get_config is a singleton, so for 'add' then 'sync' in same process,
|
|
76
|
-
# we rely on 'add' writing to disk and us reading from memory?
|
|
77
|
-
# Actually, if we run standard CLI "monoco spike add" then "monoco spike sync",
|
|
78
|
-
# they are separate processes, so config loads fresh.
|
|
79
96
|
|
|
80
97
|
root_dir = Path(config.paths.root)
|
|
81
98
|
spikes_dir = root_dir / config.paths.spikes
|
|
@@ -84,27 +101,31 @@ def sync_repos():
|
|
|
84
101
|
repos = config.project.spike_repos
|
|
85
102
|
|
|
86
103
|
if not repos:
|
|
87
|
-
|
|
104
|
+
OutputManager.print({"status": "empty", "message": "No repositories configured."}, title="Sync")
|
|
88
105
|
return
|
|
89
106
|
|
|
90
|
-
|
|
107
|
+
results = []
|
|
91
108
|
|
|
92
109
|
for name, url in repos.items():
|
|
93
|
-
|
|
110
|
+
try:
|
|
111
|
+
core.sync_repo(root_dir, spikes_dir, name, url)
|
|
112
|
+
results.append({"name": name, "status": "synced", "url": url})
|
|
113
|
+
except Exception as e:
|
|
114
|
+
results.append({"name": name, "status": "failed", "error": str(e), "url": url})
|
|
94
115
|
|
|
95
|
-
|
|
116
|
+
OutputManager.print(results, title="Sync Results")
|
|
96
117
|
|
|
97
|
-
# Alias for list (showing configured repos) could be useful but not strictly asked for.
|
|
98
|
-
# Let's add a simple list command to see what we have.
|
|
99
118
|
@app.command("list")
|
|
100
|
-
def list_repos(
|
|
119
|
+
def list_repos(
|
|
120
|
+
json: AgentOutput = False,
|
|
121
|
+
):
|
|
101
122
|
"""List configured repositories."""
|
|
102
123
|
config = get_config()
|
|
103
124
|
repos = config.project.spike_repos
|
|
104
125
|
|
|
105
126
|
if not repos:
|
|
106
|
-
|
|
127
|
+
OutputManager.print([], title="Repositories")
|
|
107
128
|
return
|
|
108
129
|
|
|
109
|
-
for name, url in repos.items()
|
|
110
|
-
|
|
130
|
+
data = [{"name": name, "url": url} for name, url in repos.items()]
|
|
131
|
+
OutputManager.print(data, title="Repositories")
|
monoco/features/spike/core.py
CHANGED
|
@@ -31,17 +31,10 @@ def run_git_command(cmd: List[str], cwd: Path) -> bool:
|
|
|
31
31
|
|
|
32
32
|
def get_config_file_path(root: Path) -> Path:
|
|
33
33
|
"""Determine the config file to update."""
|
|
34
|
-
#
|
|
35
|
-
hidden = root / ".monoco" / "
|
|
36
|
-
if hidden.exists():
|
|
37
|
-
return hidden
|
|
34
|
+
# Standard: .monoco/project.yaml
|
|
35
|
+
hidden = root / ".monoco" / "project.yaml"
|
|
38
36
|
|
|
39
|
-
#
|
|
40
|
-
visible = root / "monoco.yaml"
|
|
41
|
-
if visible.exists():
|
|
42
|
-
return visible
|
|
43
|
-
|
|
44
|
-
# Default to .monoco/config.yaml for new files
|
|
37
|
+
# Ensure parent exists
|
|
45
38
|
hidden.parent.mkdir(exist_ok=True)
|
|
46
39
|
return hidden
|
|
47
40
|
|
|
@@ -131,24 +124,14 @@ This skill normalizes how we introduce external code repositories.
|
|
|
131
124
|
3. **Remove**: `monoco spike remove <name>`
|
|
132
125
|
"""
|
|
133
126
|
|
|
134
|
-
PROMPT_CONTENT = """### Spike (Research)
|
|
135
|
-
Manage external reference repositories.
|
|
136
|
-
- **Add Repo**: `monoco spike add <url>` (Available in `.reference/<name>` for reading)
|
|
137
|
-
- **Sync**: `monoco spike sync` (Run to download content)
|
|
138
|
-
- **Constraint**: Never edit files in `.reference/`. Treat them as read-only external knowledge."""
|
|
139
|
-
|
|
140
127
|
def init(root: Path, spikes_dir_name: str):
|
|
141
128
|
"""Initialize Spike environment."""
|
|
142
129
|
ensure_gitignore(root, spikes_dir_name)
|
|
143
130
|
(root / spikes_dir_name).mkdir(exist_ok=True)
|
|
144
131
|
|
|
145
|
-
def get_resources() -> Dict[str, Any]:
|
|
146
132
|
return {
|
|
147
133
|
"skills": {
|
|
148
134
|
"git-repo-spike": SKILL_CONTENT
|
|
149
135
|
},
|
|
150
|
-
"prompts": {
|
|
151
|
-
"spike": PROMPT_CONTENT
|
|
152
|
-
}
|
|
136
|
+
"prompts": {} # Handled by adapter via resource files
|
|
153
137
|
}
|
|
154
|
-
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
### Spike (Research)
|
|
2
|
+
|
|
3
|
+
Manage external reference repositories.
|
|
4
|
+
|
|
5
|
+
- **Add Repo**: `monoco spike add <url>` (Available in `.reference/<name>` for reading)
|
|
6
|
+
- **Sync**: `monoco spike sync` (Run to download content)
|
|
7
|
+
- **Constraint**: Never edit files in `.reference/`. Treat them as read-only external knowledge.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: monoco-spike
|
|
3
|
+
description: Manage external reference repositories for research and learning. Provides read-only access to curated codebases.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Spike (Research)
|
|
7
|
+
|
|
8
|
+
Manage external reference repositories in Monoco projects.
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
The Spike feature allows you to:
|
|
13
|
+
|
|
14
|
+
- **Add external repositories** as read-only references
|
|
15
|
+
- **Sync repository content** to local `.references/` directory
|
|
16
|
+
- **Access curated knowledge** without modifying source code
|
|
17
|
+
|
|
18
|
+
## Key Commands
|
|
19
|
+
|
|
20
|
+
### Add Repository
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
monoco spike add <url>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Adds an external repository as a reference. The repository will be cloned to `.references/<name>/` where `<name>` is derived from the repository URL.
|
|
27
|
+
|
|
28
|
+
**Example**:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
monoco spike add https://github.com/example/awesome-project
|
|
32
|
+
# Available at: .references/awesome-project/
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Sync Repositories
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
monoco spike sync
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Downloads or updates all configured spike repositories from `.monoco/config.yaml`.
|
|
42
|
+
|
|
43
|
+
### List Spikes
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
monoco spike list
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Shows all configured spike repositories and their sync status.
|
|
50
|
+
|
|
51
|
+
## Configuration
|
|
52
|
+
|
|
53
|
+
Spike repositories are configured in `.monoco/config.yaml`:
|
|
54
|
+
|
|
55
|
+
```yaml
|
|
56
|
+
project:
|
|
57
|
+
spike_repos:
|
|
58
|
+
awesome-project: https://github.com/example/awesome-project
|
|
59
|
+
another-ref: https://github.com/example/another-ref
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Best Practices
|
|
63
|
+
|
|
64
|
+
1. **Read-Only Access**: Never edit files in `.references/`. Treat them as external knowledge.
|
|
65
|
+
2. **Curated Selection**: Only add high-quality, relevant repositories.
|
|
66
|
+
3. **Regular Sync**: Run `monoco spike sync` periodically to get updates.
|
|
67
|
+
4. **Commit Configuration**: Add spike repo URLs to version control for team consistency.
|
|
68
|
+
|
|
69
|
+
## Use Cases
|
|
70
|
+
|
|
71
|
+
- **Learning from Examples**: Study well-architected codebases
|
|
72
|
+
- **API Reference**: Keep framework documentation locally
|
|
73
|
+
- **Pattern Library**: Maintain a collection of design patterns
|
|
74
|
+
- **Competitive Analysis**: Reference similar projects
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: monoco-spike
|
|
3
|
+
description: 管理用于研究和学习的外部参考仓库。提供对精选代码库的只读访问。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Spike (研究)
|
|
7
|
+
|
|
8
|
+
在 Monoco 项目中管理外部参考仓库。
|
|
9
|
+
|
|
10
|
+
## 概述
|
|
11
|
+
|
|
12
|
+
Spike 功能允许你:
|
|
13
|
+
|
|
14
|
+
- **添加外部仓库**作为只读参考
|
|
15
|
+
- **同步仓库内容**到本地 `.references/` 目录
|
|
16
|
+
- **访问精选知识**而不修改源代码
|
|
17
|
+
|
|
18
|
+
## 核心命令
|
|
19
|
+
|
|
20
|
+
### 添加仓库
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
monoco spike add <url>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
将外部仓库添加为参考。仓库将被克隆到 `.references/<name>/`,其中 `<name>` 从仓库 URL 派生。
|
|
27
|
+
|
|
28
|
+
**示例**:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
monoco spike add https://github.com/example/awesome-project
|
|
32
|
+
# 可在以下位置访问: .references/awesome-project/
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 同步仓库
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
monoco spike sync
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
从 `.monoco/config.yaml` 下载或更新所有配置的 spike 仓库。
|
|
42
|
+
|
|
43
|
+
### 列出 Spikes
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
monoco spike list
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
显示所有配置的 spike 仓库及其同步状态。
|
|
50
|
+
|
|
51
|
+
## 配置
|
|
52
|
+
|
|
53
|
+
Spike 仓库在 `.monoco/config.yaml` 中配置:
|
|
54
|
+
|
|
55
|
+
```yaml
|
|
56
|
+
project:
|
|
57
|
+
spike_repos:
|
|
58
|
+
awesome-project: https://github.com/example/awesome-project
|
|
59
|
+
another-ref: https://github.com/example/another-ref
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## 最佳实践
|
|
63
|
+
|
|
64
|
+
1. **只读访问**: 永远不要编辑 `.references/` 中的文件。将它们视为外部知识。
|
|
65
|
+
2. **精选选择**: 只添加高质量、相关的仓库。
|
|
66
|
+
3. **定期同步**: 定期运行 `monoco spike sync` 以获取更新。
|
|
67
|
+
4. **提交配置**: 将 spike 仓库 URL 添加到版本控制以保持团队一致性。
|
|
68
|
+
|
|
69
|
+
## 使用场景
|
|
70
|
+
|
|
71
|
+
- **从示例中学习**: 研究架构良好的代码库
|
|
72
|
+
- **API 参考**: 在本地保存框架文档
|
|
73
|
+
- **模式库**: 维护设计模式集合
|
|
74
|
+
- **竞品分析**: 参考类似项目
|