greatminds 0.1.0__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.
- greatminds/__init__.py +8 -0
- greatminds/cli/__init__.py +6 -0
- greatminds/cli/coord_init.py +324 -0
- greatminds/cli/coord_launch.py +258 -0
- greatminds/cli/coord_tmux.py +153 -0
- greatminds/cli/coordd.py +611 -0
- greatminds/cli/gate_check.py +261 -0
- greatminds/cli/inbox.py +257 -0
- greatminds/cli/intent_clean.py +113 -0
- greatminds/cli/lint_tokens.py +170 -0
- greatminds/cli/migrate_task.py +379 -0
- greatminds/cli/notify_from_journal.py +280 -0
- greatminds/cli/plan.py +167 -0
- greatminds/cli/pty_launch.py +261 -0
- greatminds/cli/render_role.py +129 -0
- greatminds/cli/stand.py +178 -0
- greatminds/cli/start_agent.py +473 -0
- greatminds/cli/stop_decide.py +166 -0
- greatminds/cli/task.py +1255 -0
- greatminds/cli/wake_check.py +306 -0
- greatminds/cli/watchdog.py +198 -0
- greatminds/core/__init__.py +6 -0
- greatminds/core/paths.py +100 -0
- greatminds/core/util.py +50 -0
- greatminds/data/COORDINATE.md +405 -0
- greatminds/data/PROJECT_VARIABLES.md +89 -0
- greatminds/data/__init__.py +10 -0
- greatminds/data/codex/profiles/architect-reviewer.config.toml +17 -0
- greatminds/data/codex/profiles/explorer.config.toml +18 -0
- greatminds/data/codex/profiles/technical-writer.config.toml +14 -0
- greatminds/data/command_START.yaml +743 -0
- greatminds/data/mcp/README.md +16 -0
- greatminds/data/mcp/canon.json +18 -0
- greatminds/data/plugins/README.md +28 -0
- greatminds/data/plugins/coordination-protocol/.claude-plugin/plugin.json +7 -0
- greatminds/data/plugins/coordination-protocol/skills/fsm-mechanics/SKILL.md +91 -0
- greatminds/data/plugins/coordination-protocol/skills/impl-block-craft/SKILL.md +103 -0
- greatminds/data/plugins/coordination-protocol/skills/inbox-and-escalation/SKILL.md +99 -0
- greatminds/data/plugins/coordination-protocol/skills/iteration-and-blocking/SKILL.md +107 -0
- greatminds/data/plugins/coordination-protocol/skills/plan-block-protocol/SKILL.md +106 -0
- greatminds/data/plugins/coordination-protocol/skills/stand-protocol/SKILL.md +123 -0
- greatminds/data/plugins/role-architect-planner/.claude-plugin/plugin.json +7 -0
- greatminds/data/plugins/role-architect-planner/skills/task-decomposition/SKILL.md +123 -0
- greatminds/data/plugins/role-architect-reviewer/.claude-plugin/plugin.json +7 -0
- greatminds/data/plugins/role-architect-reviewer/skills/commit-and-push-protocol/SKILL.md +170 -0
- greatminds/data/plugins/role-architect-reviewer/skills/evidence-chain-verification/SKILL.md +161 -0
- greatminds/data/plugins/role-architect-reviewer/skills/review-block-craft/SKILL.md +161 -0
- greatminds/data/plugins/role-architect-reviewer/skills/wake-and-unblock/SKILL.md +155 -0
- greatminds/data/plugins/role-explorer/.claude-plugin/plugin.json +7 -0
- greatminds/data/plugins/role-explorer/skills/bug-as-mini-task/SKILL.md +175 -0
- greatminds/data/plugins/role-explorer/skills/exploratory-probing/SKILL.md +183 -0
- greatminds/data/plugins/role-explorer/skills/re-verify-loop/SKILL.md +154 -0
- greatminds/data/plugins/role-maintainer/.claude-plugin/plugin.json +7 -0
- greatminds/data/plugins/role-maintainer/skills/agent-lifecycle-and-diagnostics/SKILL.md +124 -0
- greatminds/data/plugins/role-maintainer/skills/canon-sync-and-cutover/SKILL.md +117 -0
- greatminds/data/plugins/role-maintainer/skills/infra-surface-separation/SKILL.md +84 -0
- greatminds/data/plugins/role-maintainer/skills/maintainer-vs-planner-routing/SKILL.md +77 -0
- greatminds/data/plugins/role-reader/.claude-plugin/plugin.json +7 -0
- greatminds/data/plugins/role-reader/skills/audit-path-vs-post-write-path/SKILL.md +155 -0
- greatminds/data/plugins/role-reader/skills/reader-review-block-craft/SKILL.md +143 -0
- greatminds/data/plugins/role-stand-keeper/.claude-plugin/plugin.json +7 -0
- greatminds/data/plugins/role-stand-keeper/skills/fault-isolation-on-stand/SKILL.md +183 -0
- greatminds/data/plugins/role-stand-keeper/skills/fresh-db-volume-wipes/SKILL.md +156 -0
- greatminds/data/plugins/role-stand-keeper/skills/stand-bring-up/SKILL.md +140 -0
- greatminds/data/plugins/role-tester/.claude-plugin/plugin.json +7 -0
- greatminds/data/plugins/role-tester/skills/ui-visual-verification/SKILL.md +173 -0
- greatminds/data/roles/ARCHITECT-PLANNER.md +93 -0
- greatminds/data/roles/ARCHITECT-REVIEWER.md +68 -0
- greatminds/data/roles/BOT-DEVELOPER.md +43 -0
- greatminds/data/roles/BOT-USER.md +41 -0
- greatminds/data/roles/DEVELOPER.md +66 -0
- greatminds/data/roles/EXPLORER.md +74 -0
- greatminds/data/roles/MAINTAINER.md +122 -0
- greatminds/data/roles/READER.md +85 -0
- greatminds/data/roles/STAND-KEEPER.md +120 -0
- greatminds/data/roles/TECHNICAL-WRITER.md +64 -0
- greatminds/data/roles/TESTER.md +73 -0
- greatminds/data/roles/UI-DEVELOPER.md +84 -0
- greatminds/data/roles/USER.md +31 -0
- greatminds/data/schema.yaml +618 -0
- greatminds/data/templates/PROJECT.md.template +75 -0
- greatminds/data/templates/coordination/archive/.gitkeep +0 -0
- greatminds/data/templates/coordination/bot_archive/.gitkeep +1 -0
- greatminds/data/templates/coordination/bot_done/_TEMPLATE.md +26 -0
- greatminds/data/templates/coordination/bot_inbox/_TEMPLATE.md +35 -0
- greatminds/data/templates/coordination/bot_verified/.gitkeep +1 -0
- greatminds/data/templates/coordination/bot_wip/.gitkeep +1 -0
- greatminds/data/templates/coordination/feature_blocked/.gitkeep +1 -0
- greatminds/data/templates/coordination/feature_blocked/_TEMPLATE.md +30 -0
- greatminds/data/templates/coordination/feature_dev/.gitkeep +0 -0
- greatminds/data/templates/coordination/feature_docs/_TEMPLATE.md +23 -0
- greatminds/data/templates/coordination/feature_docs_review/_TEMPLATE.md +21 -0
- greatminds/data/templates/coordination/feature_inbox/_TEMPLATE.md +29 -0
- greatminds/data/templates/coordination/feature_plan/_TEMPLATE.md +42 -0
- greatminds/data/templates/coordination/feature_review/_TEMPLATE.md +32 -0
- greatminds/data/templates/coordination/feature_test/_TEMPLATE.md +44 -0
- greatminds/data/templates/coordination/feature_ui_dev/.gitkeep +1 -0
- greatminds/data/templates/coordination/inbox/.gitkeep +0 -0
- greatminds/data/templates/coordination/intent/.gitkeep +0 -0
- greatminds/data/templates/coordination/review_sessions/.gitkeep +0 -0
- greatminds/data/templates/coordination/review_sessions/_TEMPLATE.md +54 -0
- greatminds/data/templates/coordination/stand_done/_TEMPLATE.md +38 -0
- greatminds/data/templates/coordination/stand_requests/_TEMPLATE.md +43 -0
- greatminds/data/templates/coordination/stand_wip/.gitkeep +1 -0
- greatminds/data/templates/coordination/user_feedback/_TEMPLATE.md +32 -0
- greatminds/data/templates/coordination/verified/.gitkeep +0 -0
- greatminds-0.1.0.dist-info/METADATA +75 -0
- greatminds-0.1.0.dist-info/RECORD +111 -0
- greatminds-0.1.0.dist-info/WHEEL +4 -0
- greatminds-0.1.0.dist-info/entry_points.txt +20 -0
- greatminds-0.1.0.dist-info/licenses/LICENSE +202 -0
greatminds/__init__.py
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""coord-init — bootstrap coordination into a fresh project.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
coord-init [--project-dir <dir>] [--force]
|
|
6
|
+
|
|
7
|
+
Creates under <project-dir>:
|
|
8
|
+
bin/ symlinks (or copies) to canon scripts
|
|
9
|
+
coord.yaml from canon/coord.example.yaml (edit before launch)
|
|
10
|
+
coordination/
|
|
11
|
+
PROJECT.md from canon/templates/PROJECT.md.template
|
|
12
|
+
schema.yaml copy of canon/schema.yaml
|
|
13
|
+
.gitignore journal.ndjson, intent/, .agent_registry/, .locks/, .id_counter
|
|
14
|
+
feature_inbox/_TEMPLATE.yaml (and other queues)
|
|
15
|
+
feature_plan/, feature_dev/, feature_ui_dev/, feature_docs/,
|
|
16
|
+
feature_test/, feature_docs_review/, feature_review/,
|
|
17
|
+
feature_blocked/, verified/, archive/, user_feedback/,
|
|
18
|
+
review_sessions/, stand_requests/, stand_wip/, stand_done/,
|
|
19
|
+
intent/, inbox/<role>/
|
|
20
|
+
|
|
21
|
+
Idempotent: re-running on an initialised project copies missing pieces
|
|
22
|
+
only, never overwrites coordination/PROJECT.md or coord.yaml unless
|
|
23
|
+
--force is given.
|
|
24
|
+
|
|
25
|
+
After init:
|
|
26
|
+
1. edit <project>/coord.yaml — set project_dir, tweak window list
|
|
27
|
+
2. edit <project>/coordination/PROJECT.md — fill in tokens
|
|
28
|
+
3. run bin/coord-tmux to spin up the tmux session
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
import argparse
|
|
34
|
+
import os
|
|
35
|
+
import shutil
|
|
36
|
+
import sys
|
|
37
|
+
from pathlib import Path
|
|
38
|
+
|
|
39
|
+
from greatminds.core.util import die as _die_canonical
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
QUEUES = [
|
|
43
|
+
"feature_inbox", "feature_plan", "feature_dev", "feature_ui_dev",
|
|
44
|
+
"feature_docs", "feature_test", "feature_docs_review",
|
|
45
|
+
"feature_review", "feature_blocked", "verified", "archive",
|
|
46
|
+
"user_feedback", "review_sessions",
|
|
47
|
+
"stand_requests", "stand_wip", "stand_done",
|
|
48
|
+
"bot_inbox", "bot_wip", "bot_done", "bot_verified", "bot_archive",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
ROLES_LOWER = [
|
|
52
|
+
"architect-planner", "architect-reviewer", "developer", "ui-developer",
|
|
53
|
+
"technical-writer", "tester", "reader", "explorer", "stand-keeper",
|
|
54
|
+
"user", "maintainer", "bot-user", "bot-developer",
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def die(msg: str) -> None:
|
|
59
|
+
"""Historical (msg-only) die signature preserved as a thin adapter."""
|
|
60
|
+
_die_canonical(1, msg)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def info(msg: str) -> None:
|
|
64
|
+
print(f" {msg}")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def ensure_dir(p: Path) -> str:
|
|
68
|
+
if p.is_dir():
|
|
69
|
+
return "exists"
|
|
70
|
+
p.mkdir(parents=True)
|
|
71
|
+
return "created"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def copy_if_missing(src: Path, dst: Path, force: bool = False) -> str:
|
|
75
|
+
if not src.is_file():
|
|
76
|
+
return "(canon source missing)"
|
|
77
|
+
if dst.is_file() and not force:
|
|
78
|
+
return "exists"
|
|
79
|
+
shutil.copyfile(src, dst)
|
|
80
|
+
if src.stat().st_mode & 0o111:
|
|
81
|
+
os.chmod(dst, dst.stat().st_mode | 0o755)
|
|
82
|
+
return "copied" if not dst.is_file() else "overwritten" if force else "copied"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def main(argv: list[str] | None = None) -> int:
|
|
86
|
+
"""Entry point — wired up as ``greatminds-coord-init`` in pyproject.toml.
|
|
87
|
+
|
|
88
|
+
Bootstraps a project directory to use the coordination protocol:
|
|
89
|
+
creates the runtime queue tree, copies the schema / command / role
|
|
90
|
+
docs from the packaged ``greatminds.data`` directory, and seeds the
|
|
91
|
+
inbox + plugin overlay layout.
|
|
92
|
+
|
|
93
|
+
No ``bin/*`` symlinks are created. With pip/pipx-installed
|
|
94
|
+
``greatminds``, the launcher and CLI tools (``greatminds-task``,
|
|
95
|
+
``greatminds-coordd``, …) live in the venv's ``bin/`` and are on
|
|
96
|
+
PATH; per-project shims are unnecessary.
|
|
97
|
+
"""
|
|
98
|
+
from greatminds.core.paths import find_canon_dir
|
|
99
|
+
|
|
100
|
+
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
101
|
+
ap.add_argument("--project-dir", type=Path, default=Path.cwd())
|
|
102
|
+
ap.add_argument("--force", action="store_true",
|
|
103
|
+
help="overwrite coord.yaml and PROJECT.md if present")
|
|
104
|
+
args = ap.parse_args(argv)
|
|
105
|
+
|
|
106
|
+
canon = find_canon_dir()
|
|
107
|
+
proj = args.project_dir.resolve()
|
|
108
|
+
proj.mkdir(parents=True, exist_ok=True)
|
|
109
|
+
print(f"coord-init: bootstrapping {proj} from canon {canon}")
|
|
110
|
+
|
|
111
|
+
# project-root config (schema, command_START, role docs — canon-data,
|
|
112
|
+
# kept locally so humans can `cat <project>/DEVELOPER.md` without
|
|
113
|
+
# importing the package).
|
|
114
|
+
print("\nproject-root config:")
|
|
115
|
+
coord_example = canon / "coord.example.yaml"
|
|
116
|
+
if coord_example.is_file():
|
|
117
|
+
info(f"coord.yaml: {copy_if_missing(coord_example, proj / 'coord.yaml', args.force)}")
|
|
118
|
+
info(f"schema.yaml: {copy_if_missing(canon / 'schema.yaml', proj / 'schema.yaml', force=True)}")
|
|
119
|
+
info(f"command_START.yaml: {copy_if_missing(canon / 'command_START.yaml', proj / 'command_START.yaml', force=True)}")
|
|
120
|
+
info(f"COORDINATE.md: {copy_if_missing(canon / 'COORDINATE.md', proj / 'COORDINATE.md', force=True)}")
|
|
121
|
+
# role docs at project root (sourced from packaged greatminds.data/roles/)
|
|
122
|
+
for role_md in ("ARCHITECT-PLANNER.md", "ARCHITECT-REVIEWER.md", "DEVELOPER.md",
|
|
123
|
+
"UI-DEVELOPER.md", "TECHNICAL-WRITER.md", "TESTER.md", "READER.md",
|
|
124
|
+
"EXPLORER.md", "STAND-KEEPER.md", "MAINTAINER.md", "USER.md",
|
|
125
|
+
"BOT-USER.md", "BOT-DEVELOPER.md"):
|
|
126
|
+
src = canon / "roles" / role_md
|
|
127
|
+
if src.is_file():
|
|
128
|
+
copy_if_missing(src, proj / role_md, force=True)
|
|
129
|
+
|
|
130
|
+
# coordination/ — runtime state (queues, journal, intent, inbox)
|
|
131
|
+
coord = proj / "coordination"
|
|
132
|
+
print(f"\ncoordination/ (runtime state):")
|
|
133
|
+
info(f"dir: {ensure_dir(coord)}")
|
|
134
|
+
info(f"PROJECT.md: {copy_if_missing(canon / 'templates' / 'PROJECT.md.template', coord / 'PROJECT.md', args.force)}")
|
|
135
|
+
# .gitignore
|
|
136
|
+
gi = coord / ".gitignore"
|
|
137
|
+
if not gi.is_file() or args.force:
|
|
138
|
+
gi.write_text(
|
|
139
|
+
"# Runtime churn — NOT version-controlled. Everything else\n"
|
|
140
|
+
"# under coordination/ (PROJECT.md, queue task files,\n"
|
|
141
|
+
"# verified/archive history, templates) IS tracked.\n"
|
|
142
|
+
"journal.ndjson\n"
|
|
143
|
+
".notify_state.json\n"
|
|
144
|
+
"intent/\n"
|
|
145
|
+
".agent_registry/\n"
|
|
146
|
+
".locks/\n"
|
|
147
|
+
".id_counter\n"
|
|
148
|
+
"heartbeat.*\n"
|
|
149
|
+
"inbox/*/*\n"
|
|
150
|
+
"!inbox/*/.gitkeep\n"
|
|
151
|
+
"*.legacy\n"
|
|
152
|
+
"PROJECT.env\n",
|
|
153
|
+
encoding="utf-8",
|
|
154
|
+
)
|
|
155
|
+
info(".gitignore: written")
|
|
156
|
+
else:
|
|
157
|
+
info(".gitignore: exists")
|
|
158
|
+
|
|
159
|
+
# queue dirs + gitkeep
|
|
160
|
+
print("\nqueues:")
|
|
161
|
+
for q in QUEUES:
|
|
162
|
+
st = ensure_dir(coord / q)
|
|
163
|
+
gk = coord / q / ".gitkeep"
|
|
164
|
+
if not gk.is_file():
|
|
165
|
+
gk.touch()
|
|
166
|
+
info(f"{q}: {st}")
|
|
167
|
+
ensure_dir(coord / "intent")
|
|
168
|
+
info("intent: created/exists")
|
|
169
|
+
|
|
170
|
+
# inbox per role
|
|
171
|
+
inbox = coord / "inbox"
|
|
172
|
+
ensure_dir(inbox)
|
|
173
|
+
print("\ninbox per role:")
|
|
174
|
+
for r in ROLES_LOWER:
|
|
175
|
+
d = inbox / r
|
|
176
|
+
ensure_dir(d)
|
|
177
|
+
gk = d / ".gitkeep"
|
|
178
|
+
if not gk.is_file():
|
|
179
|
+
gk.touch()
|
|
180
|
+
info(f"{r}: created/exists")
|
|
181
|
+
|
|
182
|
+
# locks + agent_registry dirs (gitignored)
|
|
183
|
+
ensure_dir(coord / ".locks")
|
|
184
|
+
ensure_dir(coord / ".agent_registry")
|
|
185
|
+
|
|
186
|
+
# Project-side plugin overlay. Canon plugins live in
|
|
187
|
+
# /opt/coordination/plugins/ and are loaded directly via --plugin-dir;
|
|
188
|
+
# this overlay is the project's per-install override layer.
|
|
189
|
+
print("\nplugin overlay (project-overrides):")
|
|
190
|
+
overlay = coord / "plugins.local" / "project-overrides"
|
|
191
|
+
overlay_meta = overlay / ".claude-plugin"
|
|
192
|
+
overlay_skills = overlay / "skills"
|
|
193
|
+
ensure_dir(overlay)
|
|
194
|
+
ensure_dir(overlay_meta)
|
|
195
|
+
ensure_dir(overlay_skills)
|
|
196
|
+
pj = overlay_meta / "plugin.json"
|
|
197
|
+
if not pj.is_file():
|
|
198
|
+
pj.write_text(
|
|
199
|
+
'{\n'
|
|
200
|
+
' "name": "project-overrides",\n'
|
|
201
|
+
' "version": "0.1.0",\n'
|
|
202
|
+
' "description": "Project-side overlay for canon coordination plugins. Add SKILL.md under skills/<name>/ to override (same name) or extend (new name) canon skills. To disable a canon skill instead, use skillOverrides in <project>/.claude/settings.local.json.",\n'
|
|
203
|
+
' "author": { "name": "project-local" }\n'
|
|
204
|
+
'}\n',
|
|
205
|
+
encoding="utf-8",
|
|
206
|
+
)
|
|
207
|
+
info("plugin.json: written")
|
|
208
|
+
else:
|
|
209
|
+
info("plugin.json: exists")
|
|
210
|
+
sg = overlay_skills / ".gitkeep"
|
|
211
|
+
if not sg.is_file():
|
|
212
|
+
sg.touch()
|
|
213
|
+
rdme = overlay / "README.md"
|
|
214
|
+
if not rdme.is_file():
|
|
215
|
+
rdme.write_text(
|
|
216
|
+
"# project-overrides\n\n"
|
|
217
|
+
"Project-side overlay for canon coordination plugins. Loaded LAST\n"
|
|
218
|
+
"by `bin/start_agent --plugin-dir` so last-wins precedence applies.\n\n"
|
|
219
|
+
"## Three usage patterns\n\n"
|
|
220
|
+
"**Replace a canon skill (override by name).** Create\n"
|
|
221
|
+
"`skills/<same-name>/SKILL.md` with the same `name:` in its\n"
|
|
222
|
+
"frontmatter as the canon skill — your project skill shadows\n"
|
|
223
|
+
"the canon one.\n\n"
|
|
224
|
+
"**Disable a canon skill.** Edit\n"
|
|
225
|
+
"`<project>/.claude/settings.local.json` and add a `skillOverrides`\n"
|
|
226
|
+
"field: `{\"<canon-skill-name>\": \"off\"}`.\n\n"
|
|
227
|
+
"**Add a project-only skill.** Create `skills/<new-name>/SKILL.md`\n"
|
|
228
|
+
"with a unique `name:`. It joins the loaded skill pool for this\n"
|
|
229
|
+
"project; no canon counterpart needed.\n",
|
|
230
|
+
encoding="utf-8",
|
|
231
|
+
)
|
|
232
|
+
info("README.md: written")
|
|
233
|
+
else:
|
|
234
|
+
info("README.md: exists")
|
|
235
|
+
|
|
236
|
+
# mcp.local.json — empty stub; projects add per-project MCP servers
|
|
237
|
+
# here. Canon MCPs come from /opt/coordination/mcp/canon.json.
|
|
238
|
+
mcpl = coord / "mcp.local.json"
|
|
239
|
+
if not mcpl.is_file():
|
|
240
|
+
mcpl.write_text('{\n "mcpServers": {}\n}\n', encoding="utf-8")
|
|
241
|
+
info("mcp.local.json: written")
|
|
242
|
+
else:
|
|
243
|
+
info("mcp.local.json: exists")
|
|
244
|
+
|
|
245
|
+
# PROJECT.env.example — committed template; real PROJECT.env (gitignored)
|
|
246
|
+
# is copied from this and filled in per-environment with secrets.
|
|
247
|
+
pe_ex = coord / "PROJECT.env.example"
|
|
248
|
+
if not pe_ex.is_file():
|
|
249
|
+
pe_ex.write_text(
|
|
250
|
+
"# coordination/PROJECT.env.example — TEMPLATE for the real PROJECT.env.\n"
|
|
251
|
+
"#\n"
|
|
252
|
+
"# Copy this file to PROJECT.env (gitignored) and fill in real values.\n"
|
|
253
|
+
"# bin/start_agent sources PROJECT.env before launching Claude/codex/cursor\n"
|
|
254
|
+
"# so MCP servers and skill Bash blocks resolve ${VAR} via the env.\n"
|
|
255
|
+
"#\n"
|
|
256
|
+
"# Canon token-contract (full list + semantics in\n"
|
|
257
|
+
"# <canon>/templates/PROJECT.md.template):\n"
|
|
258
|
+
"\n"
|
|
259
|
+
"# Project root (usually exported automatically by start_agent; set\n"
|
|
260
|
+
"# explicitly only if you need a different value than $COORD_PROJECT_DIR).\n"
|
|
261
|
+
"#PROJECT_ROOT=/opt/your_project\n"
|
|
262
|
+
"\n"
|
|
263
|
+
"# Postgres DSN with credentials (used by postgres MCP server).\n"
|
|
264
|
+
"# Example: postgresql://user:password@host:5432/dbname\n"
|
|
265
|
+
"COORD_POSTGRES_DSN=\n"
|
|
266
|
+
"\n"
|
|
267
|
+
"# Stand hostnames (if your project has a stand).\n"
|
|
268
|
+
"STAND_HOST_A=\n"
|
|
269
|
+
"STAND_HOST_B=\n"
|
|
270
|
+
"\n"
|
|
271
|
+
"# Stand REST API base URLs.\n"
|
|
272
|
+
"STAND_URL_A=\n"
|
|
273
|
+
"STAND_URL_B=\n",
|
|
274
|
+
encoding="utf-8",
|
|
275
|
+
)
|
|
276
|
+
info("PROJECT.env.example: written")
|
|
277
|
+
else:
|
|
278
|
+
info("PROJECT.env.example: exists")
|
|
279
|
+
|
|
280
|
+
# <project>/.claude/settings.local.json — Claude Code picks this up
|
|
281
|
+
# from cwd automatically. Create with standard coordination hooks
|
|
282
|
+
# only if absent; existing files are left alone.
|
|
283
|
+
#
|
|
284
|
+
# Hook commands reference the installed entry-points by name; they
|
|
285
|
+
# must be on PATH (which pip/pipx install puts them on for the env
|
|
286
|
+
# the user runs Claude Code from).
|
|
287
|
+
cclaude = proj / ".claude"
|
|
288
|
+
ensure_dir(cclaude)
|
|
289
|
+
sl = cclaude / "settings.local.json"
|
|
290
|
+
if not sl.is_file():
|
|
291
|
+
# .format() template: {{...}} → literal {...}, single braces are subs.
|
|
292
|
+
# The Stop hook command contains a shell expansion ${COORD_ROLE:-UNKNOWN}
|
|
293
|
+
# which must reach the final file as $X; we wrap each side as {{ }}.
|
|
294
|
+
settings_tpl = (
|
|
295
|
+
"{{\n"
|
|
296
|
+
' "permissions": {{ "allow": [] }},\n'
|
|
297
|
+
' "autoMode": {{ "allow": ["$defaults"] }},\n'
|
|
298
|
+
' "hooks": {{\n'
|
|
299
|
+
' "Stop": [{{\n'
|
|
300
|
+
' "matcher": "",\n'
|
|
301
|
+
' "hooks": [{{\n'
|
|
302
|
+
' "type": "command",\n'
|
|
303
|
+
' "command": "greatminds-stop-decide \\"${{COORD_ROLE:-UNKNOWN}}\\" --host claude --project-dir {proj}"\n'
|
|
304
|
+
' }}]\n'
|
|
305
|
+
' }}]\n'
|
|
306
|
+
' }}\n'
|
|
307
|
+
"}}\n"
|
|
308
|
+
)
|
|
309
|
+
sl.write_text(settings_tpl.format(proj=str(proj)), encoding="utf-8")
|
|
310
|
+
info(".claude/settings.local.json: written")
|
|
311
|
+
else:
|
|
312
|
+
info(".claude/settings.local.json: exists")
|
|
313
|
+
|
|
314
|
+
print("\ndone.")
|
|
315
|
+
print()
|
|
316
|
+
print("Next:")
|
|
317
|
+
print(f" 1. edit {proj}/coord.yaml — confirm project_dir, window list")
|
|
318
|
+
print(f" 2. edit {proj}/coordination/PROJECT.md — fill in tokens")
|
|
319
|
+
print(f" 3. run: cd {proj} && greatminds-coord-tmux")
|
|
320
|
+
return 0
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
if __name__ == "__main__":
|
|
324
|
+
sys.exit(main())
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"""coord-launch — multi-frontend launcher for the coordination fleet.
|
|
2
|
+
|
|
3
|
+
Currently supports:
|
|
4
|
+
|
|
5
|
+
``tmux`` Same as ``greatminds-coord-tmux`` — build a tmux session,
|
|
6
|
+
one window per role, agent launcher pre-typed. Use that
|
|
7
|
+
binary directly if you only need the tmux target.
|
|
8
|
+
|
|
9
|
+
``vscode`` Generate ``.vscode/tasks.json`` and a workspace file at
|
|
10
|
+
the project root. One task per agent role, each agent
|
|
11
|
+
runs in its own dedicated terminal panel. User opens
|
|
12
|
+
the project with ``code .`` then launches tasks via
|
|
13
|
+
``Cmd+Shift+P → Tasks: Run Task → agent: <role>``.
|
|
14
|
+
|
|
15
|
+
``cursor-ide`` Same generated files as ``vscode`` (Cursor IDE is a
|
|
16
|
+
VS Code fork and reads the same ``.vscode/`` config).
|
|
17
|
+
Prints ``cursor .`` as the open command instead.
|
|
18
|
+
|
|
19
|
+
The IDE targets are written as a foundation — terminals work, env vars
|
|
20
|
+
propagate, but advanced features like auto-attach to existing sessions or
|
|
21
|
+
panel grouping are best-effort. PRs welcome.
|
|
22
|
+
|
|
23
|
+
Usage::
|
|
24
|
+
|
|
25
|
+
greatminds-coord-launch --target {tmux,vscode,cursor-ide} [--config <coord.yaml>] [--project-dir <dir>]
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import argparse
|
|
31
|
+
import json
|
|
32
|
+
import shutil
|
|
33
|
+
import sys
|
|
34
|
+
from pathlib import Path
|
|
35
|
+
|
|
36
|
+
import yaml
|
|
37
|
+
|
|
38
|
+
from greatminds.core.util import die
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# Status colour for the agent terminals in VS Code (cycled per agent so they
|
|
42
|
+
# stand out side-by-side). Names from VS Code's standard theme colour set.
|
|
43
|
+
VSCODE_PANEL_COLOURS = [
|
|
44
|
+
"terminal.ansiBlue",
|
|
45
|
+
"terminal.ansiGreen",
|
|
46
|
+
"terminal.ansiYellow",
|
|
47
|
+
"terminal.ansiMagenta",
|
|
48
|
+
"terminal.ansiCyan",
|
|
49
|
+
"terminal.ansiRed",
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def load_coord_yaml(cfg_path: Path) -> dict:
|
|
54
|
+
"""Parse a coord.yaml file or die with a clear error."""
|
|
55
|
+
if not cfg_path.is_file():
|
|
56
|
+
die(1, f"coord.yaml not found at {cfg_path} "
|
|
57
|
+
"(pass --config or run from the project root)")
|
|
58
|
+
try:
|
|
59
|
+
data = yaml.safe_load(cfg_path.read_text(encoding="utf-8"))
|
|
60
|
+
except yaml.YAMLError as exc:
|
|
61
|
+
die(1, f"coord.yaml parse error: {exc}")
|
|
62
|
+
raise SystemExit
|
|
63
|
+
if not isinstance(data, dict):
|
|
64
|
+
die(1, "coord.yaml root must be a mapping")
|
|
65
|
+
raise SystemExit
|
|
66
|
+
return data
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def resolve_launcher() -> str:
|
|
70
|
+
"""Return the absolute path to ``greatminds-start-agent`` or die.
|
|
71
|
+
|
|
72
|
+
IDE-target tasks reference this command verbatim, so it must be on PATH
|
|
73
|
+
at coord-launch time too (we resolve it now to give a clear error rather
|
|
74
|
+
than letting VS Code's task runner fail opaquely later).
|
|
75
|
+
"""
|
|
76
|
+
p = shutil.which("greatminds-start-agent")
|
|
77
|
+
if p is None:
|
|
78
|
+
die(1, "greatminds-start-agent not on PATH. "
|
|
79
|
+
"Install greatminds (pip/pipx) first.")
|
|
80
|
+
raise SystemExit
|
|
81
|
+
return p
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def emit_vscode(project_dir: Path, cfg: dict, *, ide_label: str) -> None:
|
|
85
|
+
"""Write ``.vscode/tasks.json`` (and a workspace file) for VS Code / Cursor IDE."""
|
|
86
|
+
launcher = resolve_launcher()
|
|
87
|
+
|
|
88
|
+
windows = cfg.get("windows") or []
|
|
89
|
+
if not isinstance(windows, list) or not windows:
|
|
90
|
+
die(1, "coord.yaml: windows must be a non-empty list")
|
|
91
|
+
|
|
92
|
+
vscode_dir = project_dir / ".vscode"
|
|
93
|
+
vscode_dir.mkdir(exist_ok=True)
|
|
94
|
+
|
|
95
|
+
tasks: list[dict] = []
|
|
96
|
+
colour_idx = 0
|
|
97
|
+
for w in windows:
|
|
98
|
+
name = w.get("name") or ""
|
|
99
|
+
role = (w.get("role") or "").upper()
|
|
100
|
+
tool = w.get("tool") or "bash"
|
|
101
|
+
mode = w.get("mode") or ""
|
|
102
|
+
if not name:
|
|
103
|
+
die(1, f"window without name: {w}")
|
|
104
|
+
|
|
105
|
+
# Bash / no-role windows: just open a project shell, no agent launch.
|
|
106
|
+
if tool == "bash" or not role:
|
|
107
|
+
task = {
|
|
108
|
+
"label": f"shell: {name}",
|
|
109
|
+
"type": "shell",
|
|
110
|
+
"command": "${env:SHELL}",
|
|
111
|
+
"presentation": {
|
|
112
|
+
"echo": True,
|
|
113
|
+
"reveal": "always",
|
|
114
|
+
"panel": "dedicated",
|
|
115
|
+
"group": "agents",
|
|
116
|
+
"showReuseMessage": False,
|
|
117
|
+
"clear": False,
|
|
118
|
+
},
|
|
119
|
+
"options": {
|
|
120
|
+
"cwd": "${workspaceFolder}",
|
|
121
|
+
"env": {
|
|
122
|
+
"COORD_PROJECT_DIR": "${workspaceFolder}",
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
"problemMatcher": [],
|
|
126
|
+
"runOptions": {"runOn": "default"},
|
|
127
|
+
}
|
|
128
|
+
tasks.append(task)
|
|
129
|
+
continue
|
|
130
|
+
|
|
131
|
+
cmd_args = [role, tool]
|
|
132
|
+
if mode:
|
|
133
|
+
cmd_args += ["--mode", mode]
|
|
134
|
+
cmd_line = launcher + " " + " ".join(cmd_args)
|
|
135
|
+
|
|
136
|
+
colour = VSCODE_PANEL_COLOURS[colour_idx % len(VSCODE_PANEL_COLOURS)]
|
|
137
|
+
colour_idx += 1
|
|
138
|
+
|
|
139
|
+
task = {
|
|
140
|
+
"label": f"agent: {name}",
|
|
141
|
+
"detail": f"{role} via {tool}" + (f" ({mode})" if mode else ""),
|
|
142
|
+
"type": "shell",
|
|
143
|
+
"command": cmd_line,
|
|
144
|
+
"presentation": {
|
|
145
|
+
"echo": True,
|
|
146
|
+
"reveal": "always",
|
|
147
|
+
"panel": "dedicated",
|
|
148
|
+
"group": "agents",
|
|
149
|
+
"showReuseMessage": False,
|
|
150
|
+
"clear": False,
|
|
151
|
+
},
|
|
152
|
+
"options": {
|
|
153
|
+
"cwd": "${workspaceFolder}",
|
|
154
|
+
"env": {
|
|
155
|
+
"COORD_PROJECT_DIR": "${workspaceFolder}",
|
|
156
|
+
"COORD_ROLE": role,
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
"problemMatcher": [],
|
|
160
|
+
"runOptions": {"runOn": "default"},
|
|
161
|
+
# VS Code allows colorising the terminal status bar per task.
|
|
162
|
+
"icon": {"id": "terminal", "color": colour},
|
|
163
|
+
}
|
|
164
|
+
tasks.append(task)
|
|
165
|
+
|
|
166
|
+
tasks_doc = {
|
|
167
|
+
"version": "2.0.0",
|
|
168
|
+
"tasks": tasks,
|
|
169
|
+
}
|
|
170
|
+
tasks_file = vscode_dir / "tasks.json"
|
|
171
|
+
tasks_file.write_text(json.dumps(tasks_doc, indent=2) + "\n", encoding="utf-8")
|
|
172
|
+
|
|
173
|
+
# Workspace file — opens VS Code with the project root and an attractive title.
|
|
174
|
+
session_name = cfg.get("session") or "agents"
|
|
175
|
+
workspace = {
|
|
176
|
+
"folders": [{"path": "."}],
|
|
177
|
+
"settings": {
|
|
178
|
+
"window.title": f"{session_name} — greatminds fleet",
|
|
179
|
+
},
|
|
180
|
+
}
|
|
181
|
+
ws_file = project_dir / f"{session_name}.code-workspace"
|
|
182
|
+
ws_file.write_text(json.dumps(workspace, indent=2) + "\n", encoding="utf-8")
|
|
183
|
+
|
|
184
|
+
print(f"wrote {tasks_file.relative_to(project_dir)} "
|
|
185
|
+
f"({len(tasks)} tasks)")
|
|
186
|
+
print(f"wrote {ws_file.relative_to(project_dir)}")
|
|
187
|
+
print()
|
|
188
|
+
print(f"open in {ide_label}:")
|
|
189
|
+
open_cmd = "code" if ide_label == "VS Code" else "cursor"
|
|
190
|
+
print(f" {open_cmd} {ws_file.name}")
|
|
191
|
+
print()
|
|
192
|
+
print("then launch each agent terminal via:")
|
|
193
|
+
print(" Cmd/Ctrl+Shift+P → Tasks: Run Task → agent: <name>")
|
|
194
|
+
print()
|
|
195
|
+
print("each task opens a dedicated terminal panel with COORD_ROLE and "
|
|
196
|
+
"COORD_PROJECT_DIR pre-exported.")
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def emit_tmux_via_existing(args: argparse.Namespace) -> int:
|
|
200
|
+
"""Delegate to greatminds-coord-tmux (the existing tmux target)."""
|
|
201
|
+
from greatminds.cli.coord_tmux import main as tmux_main
|
|
202
|
+
|
|
203
|
+
fwd: list[str] = []
|
|
204
|
+
if args.config is not None:
|
|
205
|
+
fwd += ["--config", str(args.config)]
|
|
206
|
+
if args.project_dir is not None:
|
|
207
|
+
fwd += ["--project-dir", str(args.project_dir)]
|
|
208
|
+
if args.recreate:
|
|
209
|
+
fwd.append("--recreate")
|
|
210
|
+
return tmux_main(fwd)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def main(argv: list[str] | None = None) -> int:
|
|
214
|
+
"""Entry point — wired up as ``greatminds-coord-launch`` in pyproject.toml."""
|
|
215
|
+
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0] if __doc__ else "")
|
|
216
|
+
ap.add_argument("--target", choices=["tmux", "vscode", "cursor-ide"],
|
|
217
|
+
default="tmux",
|
|
218
|
+
help="frontend to launch the fleet in (default: tmux)")
|
|
219
|
+
ap.add_argument("--config", type=Path,
|
|
220
|
+
help="path to coord.yaml (default: <project>/coord.yaml)")
|
|
221
|
+
ap.add_argument("--project-dir", type=Path,
|
|
222
|
+
help="override config.project_dir / cwd")
|
|
223
|
+
ap.add_argument("--recreate", action="store_true",
|
|
224
|
+
help="(tmux target only) kill existing session and rebuild")
|
|
225
|
+
args = ap.parse_args(argv)
|
|
226
|
+
|
|
227
|
+
if args.target == "tmux":
|
|
228
|
+
return emit_tmux_via_existing(args)
|
|
229
|
+
|
|
230
|
+
# vscode / cursor-ide: locate coord.yaml.
|
|
231
|
+
cfg_path = args.config
|
|
232
|
+
if cfg_path is None:
|
|
233
|
+
for p in (Path.cwd() / "coord.yaml",
|
|
234
|
+
Path.cwd() / "coordination" / "coord.yaml"):
|
|
235
|
+
if p.is_file():
|
|
236
|
+
cfg_path = p
|
|
237
|
+
break
|
|
238
|
+
if cfg_path is None or not cfg_path.is_file():
|
|
239
|
+
die(1, "coord.yaml not found (pass --config or run from project root)")
|
|
240
|
+
return 1
|
|
241
|
+
|
|
242
|
+
cfg = load_coord_yaml(cfg_path)
|
|
243
|
+
project_dir = (args.project_dir or Path(cfg.get("project_dir") or ".")).resolve()
|
|
244
|
+
if not project_dir.is_dir():
|
|
245
|
+
die(1, f"project_dir {project_dir} not found")
|
|
246
|
+
return 1
|
|
247
|
+
if not (project_dir / "coordination").is_dir():
|
|
248
|
+
die(1, f"{project_dir}/coordination/ not found "
|
|
249
|
+
"(run greatminds-coord-init first)")
|
|
250
|
+
return 1
|
|
251
|
+
|
|
252
|
+
ide_label = "VS Code" if args.target == "vscode" else "Cursor IDE"
|
|
253
|
+
emit_vscode(project_dir, cfg, ide_label=ide_label)
|
|
254
|
+
return 0
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
if __name__ == "__main__":
|
|
258
|
+
sys.exit(main())
|