eai-supinit 0.5.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.
@@ -0,0 +1,3 @@
1
+ """eai-supinit: greenfield multi-repo / superbuild init assistant."""
2
+
3
+ __version__ = "0.5.0"
@@ -0,0 +1,6 @@
1
+ """Module entry: ``python -m eai_supinit``."""
2
+
3
+ from eai_supinit.cli import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -0,0 +1,57 @@
1
+ """Copy-paste agent phrases for eai-supinit."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ if TYPE_CHECKING:
8
+ from eai_supinit.workflow_state import WorkflowState
9
+
10
+ PHRASE_BOOTSTRAP = (
11
+ "Run `eai-supinit bootstrap --with-ai-rules --with-cursor --yes` for this workspace."
12
+ )
13
+ PHRASE_ASSESS = "Run `eai-supinit assess` for this workspace."
14
+ PHRASE_PLAN = "Run `eai-supinit plan` for this workspace."
15
+ PHRASE_PROMPT = "Run `eai-supinit prompt` for this workspace."
16
+ PHRASE_DOCTOR = "Run `eai-supinit doctor` for this workspace."
17
+ PHRASE_SIGN_OFF = (
18
+ "Run `eai-supinit workflow sign-off` after reviewing init deliverables "
19
+ "(do not edit workflow-state.yaml by hand)."
20
+ )
21
+
22
+
23
+ def agent_phrase_rules_markdown() -> str:
24
+ return """## Agent phrase rules
25
+
26
+ Prefer the **Tell your agent** lines printed by `eai-supinit workflow`.
27
+ Do not invent alternate CLI flags when a printed phrase exists.
28
+ """
29
+
30
+
31
+ def format_suggested_tell_agent_line(state: WorkflowState) -> str | None:
32
+ if state.next_milestone is None:
33
+ if state.deliverables.signed_off:
34
+ return None
35
+ if state.deliverables.deliverables_complete:
36
+ return PHRASE_SIGN_OFF
37
+ return None
38
+ gate = state.next_milestone.gate_id
39
+ if gate == "layout_ready" or gate == "ai_rules_mounted":
40
+ return PHRASE_BOOTSTRAP
41
+ if gate == "prompts_ready":
42
+ return PHRASE_PROMPT
43
+ if gate == "doctor_clean":
44
+ return PHRASE_DOCTOR
45
+ if gate == "init_signed_off":
46
+ return PHRASE_SIGN_OFF
47
+ return None
48
+
49
+
50
+ NEXT_COMMAND_PHRASE_KEYS = {
51
+ "bootstrap": PHRASE_BOOTSTRAP,
52
+ "assess": PHRASE_ASSESS,
53
+ "plan": PHRASE_PLAN,
54
+ "prompt": PHRASE_PROMPT,
55
+ "doctor": PHRASE_DOCTOR,
56
+ "sign-off": PHRASE_SIGN_OFF,
57
+ }
eai_supinit/assess.py ADDED
@@ -0,0 +1,105 @@
1
+ """Inspect workspace and write greenfield workspace contract."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import UTC, datetime
6
+ from pathlib import Path
7
+
8
+ from eai_supcore.git_ops import is_git_repository
9
+ from eai_supcore.io_util import write_text
10
+ from eai_supinit import __version__
11
+ from eai_supinit.model.contract import (
12
+ LayoutPresence,
13
+ WorkspaceContract,
14
+ WorkspaceMetadata,
15
+ WorkspaceReadiness,
16
+ )
17
+ from eai_supinit.model.serialize import contract_to_yaml
18
+ from eai_supinit.paths import InitPaths
19
+ from eai_supinit.workflow_state import refresh_workflow_state
20
+
21
+
22
+ def _layout_presence(workspace: Path) -> LayoutPresence:
23
+ return LayoutPresence(
24
+ src=(workspace / "src").is_dir(),
25
+ libs=(workspace / "libs").is_dir(),
26
+ docs_adr=(workspace / "docs" / "adr").is_dir(),
27
+ agents_md=(workspace / "AGENTS.md").is_file(),
28
+ ai_rules=(workspace / "tools" / "ai-rules").is_dir()
29
+ and is_git_repository(workspace / "tools" / "ai-rules"),
30
+ cursor_rules=(workspace / ".cursor" / "rules").is_dir(),
31
+ cmake_root=(workspace / "CMakeLists.txt").is_file(),
32
+ conan_root=(workspace / "conanfile.py").is_file() or (workspace / "conan").is_dir(),
33
+ )
34
+
35
+
36
+ def render_summary(contract: WorkspaceContract) -> str:
37
+ layout = contract.layout
38
+ lines = [
39
+ "# Workspace summary",
40
+ "",
41
+ f"Generated by eai-supinit {contract.metadata.tool_version} at "
42
+ f"{contract.metadata.generated_at}.",
43
+ "",
44
+ "## Layout presence",
45
+ "",
46
+ f"- `src/`: {'yes' if layout.src else 'no'}",
47
+ f"- `libs/`: {'yes' if layout.libs else 'no'}",
48
+ f"- `docs/adr/`: {'yes' if layout.docs_adr else 'no'}",
49
+ f"- `AGENTS.md`: {'yes' if layout.agents_md else 'no'}",
50
+ f"- `tools/ai-rules/`: {'yes' if layout.ai_rules else 'no'}",
51
+ f"- `.cursor/rules/`: {'yes' if layout.cursor_rules else 'no'}",
52
+ f"- Root `CMakeLists.txt`: {'yes' if layout.cmake_root else 'no'}",
53
+ f"- Conan entry: {'yes' if layout.conan_root else 'no'}",
54
+ "",
55
+ f"**Readiness:** {contract.readiness.completeness}",
56
+ "",
57
+ f"**Next:** {contract.readiness.recommended_next_action}",
58
+ "",
59
+ ]
60
+ if contract.readiness.blockers:
61
+ lines.extend(["## Blockers", ""])
62
+ lines.extend(f"- {b}" for b in contract.readiness.blockers)
63
+ lines.append("")
64
+ if contract.notes:
65
+ lines.extend(["## Notes", ""])
66
+ lines.extend(f"- {n}" for n in contract.notes)
67
+ lines.append("")
68
+ return "\n".join(lines)
69
+
70
+
71
+ def run_assessment(paths: InitPaths) -> WorkspaceContract:
72
+ paths.ensure_tool_state_dirs()
73
+ layout = _layout_presence(paths.workspace)
74
+ notes: list[str] = [
75
+ "Greenfield assess — no legacy Google-repo inventory.",
76
+ "Target steady-state layout: src/, libs/, docs/adr/, AGENTS.md, tools/ai-rules/.",
77
+ ]
78
+ blockers: list[str] = []
79
+ if not paths.workspace.is_dir():
80
+ blockers.append("Workspace directory missing")
81
+
82
+ recommended = "eai-supinit plan"
83
+ if not layout.src or not layout.libs or not layout.docs_adr:
84
+ recommended = "eai-supinit bootstrap --with-ai-rules --with-cursor --yes"
85
+ notes.append("Layout dirs incomplete — bootstrap will scaffold steady-state dirs.")
86
+
87
+ completeness = "good" if not blockers else "partial"
88
+ contract = WorkspaceContract(
89
+ metadata=WorkspaceMetadata(
90
+ generated_at=datetime.now(UTC).isoformat(),
91
+ tool_version=__version__,
92
+ workspace_path=str(paths.workspace),
93
+ ),
94
+ layout=layout,
95
+ notes=notes,
96
+ readiness=WorkspaceReadiness(
97
+ completeness=completeness,
98
+ blockers=blockers,
99
+ recommended_next_action=recommended,
100
+ ),
101
+ )
102
+ write_text(paths.contract_path, contract_to_yaml(contract))
103
+ write_text(paths.summary_path, render_summary(contract))
104
+ refresh_workflow_state(paths)
105
+ return contract
File without changes
@@ -0,0 +1,201 @@
1
+ """Bootstrap greenfield steady-state layout via eai-supcore."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+
7
+ from eai_supcore.bootstrap import (
8
+ SharedBootstrapResult,
9
+ ensure_steady_state_dirs,
10
+ materialize_cursor_rules,
11
+ mount_ai_rules,
12
+ scaffold_consumer_dev_rails,
13
+ write_cursor_overlay,
14
+ )
15
+ from eai_supcore.io_util import write_text
16
+ from eai_supcore.paths import DEFAULT_AI_RULES_URL
17
+ from eai_supinit.agent_phrases import agent_phrase_rules_markdown
18
+ from eai_supinit.paths import LOCAL_CURSOR_OVERLAY, InitPaths
19
+ from eai_supinit.plan import load_contract
20
+
21
+
22
+ @dataclass
23
+ class BootstrapResult:
24
+ created_dirs: list[str] = field(default_factory=list)
25
+ created_files: list[str] = field(default_factory=list)
26
+ submodule_added: bool = False
27
+ ai_rules_found: bool = False
28
+ cursor_rules_materialized: list[str] = field(default_factory=list)
29
+ warnings: list[str] = field(default_factory=list)
30
+ errors: list[str] = field(default_factory=list)
31
+
32
+ def merge_shared(self, shared: SharedBootstrapResult) -> None:
33
+ self.created_dirs.extend(shared.created_dirs)
34
+ self.created_files.extend(shared.created_files)
35
+ self.submodule_added = self.submodule_added or shared.submodule_added
36
+ self.ai_rules_found = self.ai_rules_found or shared.ai_rules_found
37
+ self.cursor_rules_materialized.extend(shared.cursor_rules_materialized)
38
+ self.warnings.extend(shared.warnings)
39
+ self.errors.extend(shared.errors)
40
+
41
+
42
+ def _scaffold_agents(paths: InitPaths, result: BootstrapResult) -> None:
43
+ if paths.agents_path.exists():
44
+ return
45
+ content = f"""# AGENTS.md
46
+
47
+ Local superbuild workspace. Read this file first, then `tools/ai-rules/AGENTS.md`.
48
+
49
+ ## Workspace role
50
+
51
+ Greenfield multi-repo / superbuild. Steady-state layout:
52
+
53
+ - `src/` — first-party member repositories
54
+ - `libs/` — third-party or patched deps not from Conan
55
+ - `docs/adr/` — workspace ADRs
56
+ - `tools/ai-rules/` — shared superbuild-workflow policy (submodule)
57
+
58
+ ## Init artifacts
59
+
60
+ Journey state: `.tools/esys/eai-supinit/` (committed tool state).
61
+ Prompts: `prompts/` — commit after `eai-supinit prompt`.
62
+
63
+ ## Tell your agent
64
+
65
+ Use phrases in `prompts/` or run `eai-supinit workflow`.
66
+
67
+ {agent_phrase_rules_markdown()}
68
+
69
+ See `tools/ai-rules/docs/init-assistant.md` when mounted.
70
+ """
71
+ write_text(paths.agents_path, content)
72
+ result.created_files.append("AGENTS.md")
73
+
74
+
75
+ def _write_init_overlay(paths: InitPaths, shared: SharedBootstrapResult) -> None:
76
+ content = f"""---
77
+ description: Local init overlay — workspace-specific exceptions
78
+ alwaysApply: true
79
+ ---
80
+
81
+ # Local init overlay
82
+
83
+ Read `.tools/esys/eai-supinit/workspace-contract.yaml` and root `AGENTS.md`.
84
+ Shared policy: `tools/ai-rules/AGENTS.md`.
85
+
86
+ Document exceptions here; do not fork shared policy under `tools/ai-rules/`.
87
+
88
+ {agent_phrase_rules_markdown()}
89
+
90
+ ## Version control at checkpoints
91
+
92
+ When `eai-supinit` prints **Commit checkpoint**, remind the user to commit (or confirm
93
+ auto-commit ran). Commit **`prompts/`** with **`.tools/esys/eai-supinit/`**.
94
+ """
95
+ write_cursor_overlay(
96
+ paths.workspace,
97
+ shared,
98
+ overlay_name=LOCAL_CURSOR_OVERLAY,
99
+ content=content,
100
+ )
101
+
102
+
103
+ def run_bootstrap(
104
+ paths: InitPaths,
105
+ *,
106
+ with_ai_rules: bool = False,
107
+ ai_rules_url: str = DEFAULT_AI_RULES_URL,
108
+ with_cursor: bool = False,
109
+ with_dev_hooks: bool = True,
110
+ with_esysrepo_manifest: bool = True,
111
+ install_hooks: bool = False,
112
+ dry_run: bool = False,
113
+ ) -> BootstrapResult:
114
+ result = BootstrapResult()
115
+ paths.ensure_tool_state_dirs()
116
+ result.created_dirs.append(".tools/esys/eai-supinit")
117
+ result.created_dirs.append("prompts")
118
+
119
+ if dry_run:
120
+ result.warnings.append("Dry run — no files or submodules modified")
121
+ return result
122
+
123
+ try:
124
+ load_contract(paths)
125
+ except FileNotFoundError:
126
+ result.warnings.append(
127
+ "No workspace contract; run `eai-supinit assess` for richer bootstrap"
128
+ )
129
+
130
+ shared = SharedBootstrapResult()
131
+ ensure_steady_state_dirs(paths.workspace, shared)
132
+ scaffold_consumer_dev_rails(
133
+ paths.workspace,
134
+ shared,
135
+ with_hooks=with_dev_hooks,
136
+ with_manifest=with_esysrepo_manifest,
137
+ install_hooks=install_hooks,
138
+ )
139
+ result.merge_shared(shared)
140
+
141
+ _scaffold_agents(paths, result)
142
+
143
+ shared2 = SharedBootstrapResult()
144
+ if with_ai_rules:
145
+ mount_ai_rules(paths.workspace, shared2, ai_rules_url=ai_rules_url)
146
+ if with_cursor:
147
+ materialize_cursor_rules(paths.workspace, shared2)
148
+ _write_init_overlay(paths, shared2)
149
+ result.merge_shared(shared2)
150
+
151
+ next_steps = ["# Next steps", ""]
152
+ step = 1
153
+ if not paths.contract_path.is_file():
154
+ next_steps.append(f"{step}. Run `eai-supinit assess`")
155
+ step += 1
156
+ else:
157
+ next_steps.append(f"{step}. Review `.tools/esys/eai-supinit/workspace-summary.md`")
158
+ step += 1
159
+ if not paths.plan_path.is_file():
160
+ next_steps.append(f"{step}. Run `eai-supinit plan`")
161
+ step += 1
162
+ next_steps.append(f"{step}. Run `eai-supinit prompt` for AI handoff")
163
+ step += 1
164
+ if result.submodule_added:
165
+ next_steps.append(f"{step}. `git submodule update --init tools/ai-rules`")
166
+ step += 1
167
+ if with_dev_hooks and not install_hooks:
168
+ next_steps.append(f"{step}. `python scripts/setup-dev.py` (pre-commit + gitlint)")
169
+ step += 1
170
+ next_steps.append(
171
+ f"{step}. Optional: `eai-supinit extension add cmake` when using CMake builds"
172
+ )
173
+ step += 1
174
+ next_steps.append(f"{step}. Run `eai-supinit doctor` then `eai-supinit workflow sign-off`")
175
+ next_steps.append("")
176
+ write_text(paths.next_steps_path, "\n".join(next_steps))
177
+ result.created_files.append(".tools/esys/eai-supinit/next-steps.md")
178
+
179
+ summary_lines = [
180
+ "# Bootstrap summary",
181
+ "",
182
+ f"- AI rules checkout found: {result.ai_rules_found}",
183
+ f"- Submodule added: {result.submodule_added}",
184
+ f"- Cursor rules materialized: {len(result.cursor_rules_materialized)}",
185
+ f"- Dev hooks scaffolded: {with_dev_hooks}",
186
+ f"- esysrepo manifest scaffolded: {with_esysrepo_manifest}",
187
+ "",
188
+ "## Created directories",
189
+ "",
190
+ ]
191
+ summary_lines.extend(f"- `{d}`" for d in result.created_dirs)
192
+ summary_lines.extend(["", "## Created files", ""])
193
+ summary_lines.extend(f"- `{f}`" for f in result.created_files)
194
+ if result.warnings:
195
+ summary_lines.extend(["", "## Warnings", ""])
196
+ summary_lines.extend(f"- {w}" for w in result.warnings)
197
+ if result.errors:
198
+ summary_lines.extend(["", "## Errors", ""])
199
+ summary_lines.extend(f"- {e}" for e in result.errors)
200
+ write_text(paths.bootstrap_summary_path, "\n".join(summary_lines))
201
+ return result