@scemoon/cdh 1.0.0 → 1.0.3
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.
- package/cdh/__pycache__/__main__.cpython-314.pyc +0 -0
- package/cdh/__pycache__/cli.cpython-314.pyc +0 -0
- package/cdh/__pycache__/scaffold.cpython-314.pyc +0 -0
- package/cdh/cli.py +473 -87
- package/cdh/scaffold.py +386 -121
- package/onecode/__pycache__/cli.cpython-314.pyc +0 -0
- package/onecode/__pycache__/commands.cpython-314.pyc +0 -0
- package/onecode/__pycache__/config.cpython-314.pyc +0 -0
- package/onecode/__pycache__/config_screen.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/cdh_loader.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/engine.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/onecode_agent_acp.cpython-314.pyc +0 -0
- package/onecode/agent/__pycache__/session.cpython-314.pyc +0 -0
- package/onecode/agent/agents/__pycache__/types.cpython-314.pyc +0 -0
- package/onecode/agent/agents/types.py +123 -79
- package/onecode/agent/cdh_loader.py +26 -13
- package/onecode/agent/engine.py +426 -235
- package/onecode/agent/onecode_agent_acp.py +200 -73
- package/onecode/agent/session.py +8 -13
- package/onecode/agent/tools/__pycache__/agent_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/task_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/__pycache__/todo_tools.cpython-314.pyc +0 -0
- package/onecode/agent/tools/agent_tools.py +4 -4
- package/onecode/agent/tools/todo_tools.py +188 -0
- package/onecode/cli.py +3 -3
- package/onecode/commands.py +439 -194
- package/onecode/config.py +3 -3
- package/onecode/config_screen.py +4 -2
- package/onecode/models/__pycache__/errors.cpython-314.pyc +0 -0
- package/onecode/models/__pycache__/messages.cpython-314.pyc +0 -0
- package/onecode/models/__pycache__/provider.cpython-314.pyc +0 -0
- package/onecode/models/errors.py +11 -1
- package/onecode/models/messages.py +35 -16
- package/onecode/models/provider.py +243 -12
- package/onecode/models/providers/__pycache__/anthropic_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/deepseek_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/glm_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/minimax_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/minimaxi_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/ollama_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/__pycache__/openai_provider.cpython-314.pyc +0 -0
- package/onecode/models/providers/anthropic_provider.py +4 -2
- package/onecode/models/providers/deepseek_provider.py +2 -0
- package/onecode/models/providers/glm_provider.py +2 -1
- package/onecode/models/providers/minimax_provider.py +2 -1
- package/onecode/models/providers/minimaxi_provider.py +76 -25
- package/onecode/models/providers/ollama_provider.py +2 -1
- package/onecode/models/providers/openai_provider.py +4 -0
- package/onecode/skills/__pycache__/manager.cpython-314.pyc +0 -0
- package/onecode/tasks/__pycache__/__init__.cpython-314.pyc +0 -0
- package/onecode/tasks/__pycache__/manager.cpython-314.pyc +0 -0
- package/onecode/tasks/__pycache__/models.cpython-314.pyc +0 -0
- package/onecode/tasks/manager.py +1 -1
- package/package.json +1 -1
- package/pyproject.toml +10 -0
- package/run.js +24 -7
- package/tui/__pycache__/app.cpython-314.pyc +0 -0
- package/tui/__pycache__/cli.cpython-314.pyc +0 -0
- package/tui/__pycache__/db.cpython-314.pyc +0 -0
- package/tui/__pycache__/directory_suggester.cpython-314.pyc +0 -0
- package/tui/__pycache__/directory_watcher.cpython-314.pyc +0 -0
- package/tui/__pycache__/messages.cpython-314.pyc +0 -0
- package/tui/__pycache__/paths.cpython-314.pyc +0 -0
- package/tui/acp/__pycache__/agent.cpython-314.pyc +0 -0
- package/tui/acp/__pycache__/api.cpython-314.pyc +0 -0
- package/tui/acp/__pycache__/messages.cpython-314.pyc +0 -0
- package/tui/acp/__pycache__/prompt.cpython-314.pyc +0 -0
- package/tui/acp/__pycache__/protocol.cpython-314.pyc +0 -0
- package/tui/acp/agent.py +60 -11
- package/tui/acp/api.py +6 -0
- package/tui/acp/messages.py +11 -0
- package/tui/acp/prompt.py +1 -1
- package/tui/acp/protocol.py +31 -0
- package/tui/app.py +60 -8
- package/tui/db.py +23 -0
- package/tui/directory_watcher.py +35 -1
- package/tui/messages.py +4 -1
- package/tui/paths.py +3 -3
- package/tui/screens/__pycache__/agent_modal.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/component_picker.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/log.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/main.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/projects_app.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/projects_screen.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/settings.cpython-314.pyc +0 -0
- package/tui/screens/__pycache__/store.cpython-314.pyc +0 -0
- package/tui/screens/component_picker.py +148 -0
- package/tui/screens/main.py +47 -95
- package/tui/screens/projects.tcss +10 -0
- package/tui/screens/projects_app.py +33 -5
- package/tui/screens/projects_screen.py +171 -9
- package/tui/widgets/__pycache__/acp_content.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/ask_user.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/conversation.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/diff_view.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/directory_input.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/mandelbrot.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/modified_files.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/plan.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/project_directory_tree.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/project_grid_select.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/question.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/subagent.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/subagent_screen.cpython-314.pyc +0 -0
- package/tui/widgets/__pycache__/tool_call.cpython-314.pyc +0 -0
- package/tui/widgets/ask_user.py +1 -1
- package/tui/widgets/conversation.py +99 -42
- package/tui/widgets/modified_files.py +155 -23
- package/tui/widgets/plan.py +22 -0
- package/tui/widgets/project_directory_tree.py +22 -10
- package/tui/widgets/project_grid_select.py +23 -1
- package/tui/widgets/question.py +1 -1
- package/tui/widgets/subagent.py +202 -36
- package/tui/widgets/subagent_screen.py +186 -0
- package/tui/widgets/tool_call.py +5 -8
- package/onecode/agent/tools/task_tools.py +0 -249
package/cdh/scaffold.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from dataclasses import dataclass, field
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
from typing import Optional
|
|
5
6
|
|
|
@@ -8,15 +9,152 @@ import yaml
|
|
|
8
9
|
|
|
9
10
|
SKILL_NAME = "ai-dlc-skill"
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class ComponentSpec:
|
|
15
|
+
id: str
|
|
16
|
+
kind: str
|
|
17
|
+
owns: str
|
|
18
|
+
fr_prefix: str
|
|
19
|
+
tech: str
|
|
20
|
+
label: str
|
|
21
|
+
description: str
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class CrossCutSpec:
|
|
26
|
+
id: str
|
|
27
|
+
paths: tuple[str, ...]
|
|
28
|
+
label: str
|
|
29
|
+
description: str
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
COMPONENTS: tuple[ComponentSpec, ...] = (
|
|
33
|
+
ComponentSpec(
|
|
34
|
+
id="native",
|
|
35
|
+
kind="mobile",
|
|
36
|
+
owns="apps/native",
|
|
37
|
+
fr_prefix="NATIVE",
|
|
38
|
+
tech="react-native | flutter",
|
|
39
|
+
label="Mobile App",
|
|
40
|
+
description="iOS/Android cross-platform (RN/Flutter)",
|
|
41
|
+
),
|
|
42
|
+
ComponentSpec(
|
|
43
|
+
id="desktop",
|
|
44
|
+
kind="desktop",
|
|
45
|
+
owns="apps/desktop",
|
|
46
|
+
fr_prefix="DESKTOP",
|
|
47
|
+
tech="electron | tauri",
|
|
48
|
+
label="Desktop App",
|
|
49
|
+
description="Cross-platform desktop (Electron/Tauri)",
|
|
50
|
+
),
|
|
51
|
+
ComponentSpec(
|
|
52
|
+
id="web",
|
|
53
|
+
kind="frontend",
|
|
54
|
+
owns="apps/web",
|
|
55
|
+
fr_prefix="WEB",
|
|
56
|
+
tech="react | vue | svelte",
|
|
57
|
+
label="Web Frontend",
|
|
58
|
+
description="Browser SPA/SSR (React/Vue/Svelte)",
|
|
59
|
+
),
|
|
60
|
+
ComponentSpec(
|
|
61
|
+
id="backend",
|
|
62
|
+
kind="service",
|
|
63
|
+
owns="apps/backend",
|
|
64
|
+
fr_prefix="BE",
|
|
65
|
+
tech="python | node | go",
|
|
66
|
+
label="Backend Service",
|
|
67
|
+
description="HTTP API/microservice (Python/Node/Go)",
|
|
68
|
+
),
|
|
69
|
+
ComponentSpec(
|
|
70
|
+
id="wxa",
|
|
71
|
+
kind="mini-program",
|
|
72
|
+
owns="apps/wxa",
|
|
73
|
+
fr_prefix="WXA",
|
|
74
|
+
tech="miniprogram",
|
|
75
|
+
label="WeChat Mini-Program",
|
|
76
|
+
description="WeChat ecosystem mini-program",
|
|
77
|
+
),
|
|
78
|
+
ComponentSpec(
|
|
79
|
+
id="mya",
|
|
80
|
+
kind="mini-program",
|
|
81
|
+
owns="apps/mya",
|
|
82
|
+
fr_prefix="MYA",
|
|
83
|
+
tech="miniprogram",
|
|
84
|
+
label="Alipay Mini-Program",
|
|
85
|
+
description="Alipay ecosystem mini-program",
|
|
86
|
+
),
|
|
87
|
+
ComponentSpec(
|
|
88
|
+
id="tta",
|
|
89
|
+
kind="mini-program",
|
|
90
|
+
owns="apps/tta",
|
|
91
|
+
fr_prefix="TTA",
|
|
92
|
+
tech="miniprogram",
|
|
93
|
+
label="TikTok Mini-Program",
|
|
94
|
+
description="TikTok/Douyin ecosystem mini-program",
|
|
95
|
+
),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
COMPONENT_BY_ID: dict[str, ComponentSpec] = {c.id: c for c in COMPONENTS}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
CROSS_CUTTING: tuple[CrossCutSpec, ...] = (
|
|
103
|
+
CrossCutSpec(
|
|
104
|
+
id="contracts",
|
|
105
|
+
paths=("contracts/api", "contracts/events", "contracts/CHANGELOG.md"),
|
|
106
|
+
label="Interface Contracts",
|
|
107
|
+
description="API/event contracts and changelog",
|
|
108
|
+
),
|
|
109
|
+
CrossCutSpec(
|
|
110
|
+
id="shared",
|
|
111
|
+
paths=("packages/shared",),
|
|
112
|
+
label="Shared Types",
|
|
113
|
+
description="Cross-component shared types package",
|
|
114
|
+
),
|
|
115
|
+
CrossCutSpec(
|
|
116
|
+
id="openspec",
|
|
117
|
+
paths=("openspec/changes",),
|
|
118
|
+
label="OpenSpec Changes",
|
|
119
|
+
description="OpenSpec change proposals",
|
|
120
|
+
),
|
|
121
|
+
CrossCutSpec(
|
|
122
|
+
id="cross_stack_features",
|
|
123
|
+
paths=("features/cross-stack",),
|
|
124
|
+
label="Cross-Stack BDD",
|
|
125
|
+
description="End-to-end BDD features across components",
|
|
126
|
+
),
|
|
127
|
+
CrossCutSpec(
|
|
128
|
+
id="cross_stack_tests",
|
|
129
|
+
paths=("tests/contract", "tests/cross-stack"),
|
|
130
|
+
label="Cross-Stack Tests",
|
|
131
|
+
description="Contract tests and cross-stack integration tests",
|
|
132
|
+
),
|
|
133
|
+
CrossCutSpec(
|
|
134
|
+
id="provider",
|
|
135
|
+
paths=(
|
|
136
|
+
"providers/tcb/provider.yaml",
|
|
137
|
+
"providers/tcb/deployment.yaml",
|
|
138
|
+
"providers/tcb/preview.yaml",
|
|
139
|
+
),
|
|
140
|
+
label="Cloud Provider",
|
|
141
|
+
description="TCB (Tencent CloudBase) provider config",
|
|
142
|
+
),
|
|
143
|
+
CrossCutSpec(
|
|
144
|
+
id="tools",
|
|
145
|
+
paths=(
|
|
146
|
+
"tools/deploy_stack.py",
|
|
147
|
+
"tools/contract_diff.py",
|
|
148
|
+
"tools/generate_shared.py",
|
|
149
|
+
),
|
|
150
|
+
label="Tooling Scripts",
|
|
151
|
+
description="deploy_stack / contract_diff / generate_shared stubs",
|
|
152
|
+
),
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
CROSS_CUTTING_BY_ID: dict[str, CrossCutSpec] = {c.id: c for c in CROSS_CUTTING}
|
|
157
|
+
|
|
20
158
|
|
|
21
159
|
TCB_PROVIDER_YAML = """provider:
|
|
22
160
|
name: tcb
|
|
@@ -149,61 +287,91 @@ def _write(path: Path, content: str) -> None:
|
|
|
149
287
|
path.write_text(content.lstrip("\n"), encoding="utf-8")
|
|
150
288
|
|
|
151
289
|
|
|
152
|
-
def
|
|
290
|
+
def _component_to_dict(c: ComponentSpec) -> dict:
|
|
291
|
+
out = {
|
|
292
|
+
"id": c.id,
|
|
293
|
+
"kind": c.kind,
|
|
294
|
+
"tech": c.tech,
|
|
295
|
+
"owns": c.owns,
|
|
296
|
+
"fr_prefix": c.fr_prefix,
|
|
297
|
+
}
|
|
298
|
+
return out
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _build_project_yaml(active: list[ComponentSpec], cross_cutting_ids: list[str]) -> dict:
|
|
302
|
+
cross_cutting: dict = {"fr_prefix": "INT"}
|
|
303
|
+
if "contracts" in cross_cutting_ids:
|
|
304
|
+
cross_cutting["contracts"] = "contracts/"
|
|
305
|
+
if "shared" in cross_cutting_ids:
|
|
306
|
+
cross_cutting["shared_types"] = "packages/shared/"
|
|
153
307
|
return {
|
|
154
308
|
"stack": {
|
|
155
309
|
"topology": "monorepo",
|
|
156
|
-
"components": [
|
|
157
|
-
|
|
158
|
-
"id": c["id"],
|
|
159
|
-
"kind": c["kind"],
|
|
160
|
-
"tech": c["tech"],
|
|
161
|
-
"owns": c["owns"],
|
|
162
|
-
"fr_prefix": c["fr_prefix"],
|
|
163
|
-
}
|
|
164
|
-
| ({"default_language": c["default_language"]} if c.get("default_language") else {})
|
|
165
|
-
| ({"default_ui_framework": c["default_ui_framework"]} if c.get("default_ui_framework") else {})
|
|
166
|
-
for c in active
|
|
167
|
-
],
|
|
168
|
-
"cross_cutting": {
|
|
169
|
-
"fr_prefix": "INT",
|
|
170
|
-
"contracts": "contracts/",
|
|
171
|
-
"shared_types": "packages/shared/",
|
|
172
|
-
},
|
|
310
|
+
"components": [_component_to_dict(c) for c in active],
|
|
311
|
+
"cross_cutting": cross_cutting,
|
|
173
312
|
}
|
|
174
313
|
}
|
|
175
314
|
|
|
176
315
|
|
|
177
|
-
def _build_component_table(active: list[
|
|
316
|
+
def _build_component_table(active: list[ComponentSpec]) -> str:
|
|
317
|
+
if not active:
|
|
318
|
+
return "| (none) | - | - | - | - |"
|
|
178
319
|
return "\n".join(
|
|
179
|
-
f"| {c
|
|
320
|
+
f"| {c.id:8s} | {c.fr_prefix + '-FR-*':14s} | {'-':17s} | {'-':21s} | {c.owns:20s} |"
|
|
180
321
|
for c in active
|
|
181
322
|
)
|
|
182
323
|
|
|
183
324
|
|
|
184
|
-
def
|
|
185
|
-
|
|
325
|
+
def _scaffold_component(c: ComponentSpec, root: Path) -> None:
|
|
326
|
+
comp_dir = root / c.owns
|
|
327
|
+
_mkdir(comp_dir / "src")
|
|
328
|
+
_mkdir(comp_dir / "tests" / "unit")
|
|
329
|
+
_mkdir(comp_dir / "tests" / "e2e")
|
|
330
|
+
_mkdir(comp_dir / "features")
|
|
331
|
+
_gitkeep(comp_dir / "src")
|
|
332
|
+
_gitkeep(comp_dir / "tests" / "unit")
|
|
333
|
+
_gitkeep(comp_dir / "tests" / "e2e")
|
|
334
|
+
_gitkeep(comp_dir / "features")
|
|
335
|
+
_gitkeep(comp_dir)
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _scaffold_cross_cutting(cross_ids: list[str], root: Path) -> None:
|
|
339
|
+
for cid in cross_ids:
|
|
340
|
+
spec = CROSS_CUTTING_BY_ID.get(cid)
|
|
341
|
+
if spec is None:
|
|
342
|
+
continue
|
|
343
|
+
for p in spec.paths:
|
|
344
|
+
target = root / p
|
|
345
|
+
if p.endswith((".yaml", ".py", ".md")):
|
|
346
|
+
if p == "contracts/CHANGELOG.md":
|
|
347
|
+
_write(target, CHANGELOG_MD)
|
|
348
|
+
elif p == "providers/tcb/provider.yaml":
|
|
349
|
+
_write(target, TCB_PROVIDER_YAML)
|
|
350
|
+
elif p == "providers/tcb/deployment.yaml":
|
|
351
|
+
_write(target, TCB_DEPLOYMENT_YAML)
|
|
352
|
+
elif p == "providers/tcb/preview.yaml":
|
|
353
|
+
_write(target, TCB_PREVIEW_YAML)
|
|
354
|
+
elif p.startswith("tools/") and p.endswith(".py"):
|
|
355
|
+
_write(target, TOOL_STUB)
|
|
356
|
+
target.chmod(0o755)
|
|
357
|
+
else:
|
|
358
|
+
_write(target, "")
|
|
359
|
+
else:
|
|
360
|
+
_mkdir(target)
|
|
361
|
+
_gitkeep(target)
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def _write_project_yaml(
|
|
365
|
+
root: Path,
|
|
186
366
|
project_name: str,
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
is not available and the project was left untouched.
|
|
196
|
-
"""
|
|
197
|
-
if not _detect_dlc_skill(workspace_root):
|
|
198
|
-
return False
|
|
199
|
-
|
|
200
|
-
root = workspace_root.resolve()
|
|
201
|
-
active = list(SKILL_YAML_COMPONENTS)
|
|
202
|
-
|
|
203
|
-
# --- project.yaml (definition only — no app dirs yet) ---
|
|
204
|
-
_write(root / "project.yaml", yaml.dump(_build_project_yaml(active), default_flow_style=False))
|
|
205
|
-
|
|
206
|
-
# --- requirements.md ---
|
|
367
|
+
active: list[ComponentSpec],
|
|
368
|
+
cross_cutting_ids: list[str],
|
|
369
|
+
description: str,
|
|
370
|
+
) -> None:
|
|
371
|
+
_write(
|
|
372
|
+
root / "project.yaml",
|
|
373
|
+
yaml.dump(_build_project_yaml(active, cross_cutting_ids), default_flow_style=False),
|
|
374
|
+
)
|
|
207
375
|
_write(
|
|
208
376
|
root / "requirements.md",
|
|
209
377
|
REQUIREMENTS_MD.format(
|
|
@@ -212,101 +380,198 @@ def init_dlc_project(
|
|
|
212
380
|
component_table=_build_component_table(active),
|
|
213
381
|
),
|
|
214
382
|
)
|
|
215
|
-
|
|
216
|
-
# --- .gitignore ---
|
|
217
383
|
_write(root / ".gitignore", GITIGNORE_CONTENT)
|
|
218
|
-
|
|
219
|
-
# --- CHANGELOG.md (root-level metadata) ---
|
|
220
384
|
_write(root / "CHANGELOG.md", CHANGELOG_MD)
|
|
221
385
|
|
|
386
|
+
|
|
387
|
+
def init_dlc_project(
|
|
388
|
+
workspace_root: Path,
|
|
389
|
+
project_name: str,
|
|
390
|
+
description: str = "",
|
|
391
|
+
) -> bool:
|
|
392
|
+
"""Scaffold project metadata only (no apps/*, no cross-cutting dirs).
|
|
393
|
+
|
|
394
|
+
Writes project.yaml (with empty components list), requirements.md,
|
|
395
|
+
.gitignore, and CHANGELOG.md. The user is expected to add components
|
|
396
|
+
and cross-cutting items later via add_component / add_cross_cutting.
|
|
397
|
+
|
|
398
|
+
Returns True if scaffolding was performed.
|
|
399
|
+
|
|
400
|
+
Raises RuntimeError if ai-dlc-skill is not available — the directory
|
|
401
|
+
is left untouched in that case so callers don't end up with a
|
|
402
|
+
half-initialized project (.cdh/ written but no project.yaml).
|
|
403
|
+
"""
|
|
404
|
+
if not _detect_dlc_skill(workspace_root):
|
|
405
|
+
raise RuntimeError(
|
|
406
|
+
"ai-dlc-skill is not available; install it before "
|
|
407
|
+
"initializing a cdh project."
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
root = workspace_root.resolve()
|
|
411
|
+
_write_project_yaml(
|
|
412
|
+
root=root,
|
|
413
|
+
project_name=project_name,
|
|
414
|
+
active=[],
|
|
415
|
+
cross_cutting_ids=[],
|
|
416
|
+
description=description,
|
|
417
|
+
)
|
|
222
418
|
return True
|
|
223
419
|
|
|
224
420
|
|
|
225
421
|
def scaffold_dlc_project(
|
|
226
422
|
workspace_root: Path,
|
|
227
423
|
project_name: str,
|
|
228
|
-
components:
|
|
424
|
+
components: list[str],
|
|
229
425
|
description: str = "",
|
|
230
426
|
) -> bool:
|
|
231
427
|
"""Scaffold a full ai-dlc-skill monorepo project structure.
|
|
232
428
|
|
|
429
|
+
The user must explicitly select at least one application component.
|
|
430
|
+
All cross-cutting items (contracts, shared types, openspec, etc.)
|
|
431
|
+
are always created.
|
|
432
|
+
|
|
433
|
+
Args:
|
|
434
|
+
components: list of component ids (e.g. ["web", "backend"]).
|
|
435
|
+
Must be non-empty.
|
|
436
|
+
|
|
233
437
|
Returns True if scaffolding was performed, False if ai-dlc-skill
|
|
234
438
|
is not available and the project was left untouched.
|
|
439
|
+
|
|
440
|
+
Raises ValueError if components is empty or contains unknown ids.
|
|
235
441
|
"""
|
|
442
|
+
if not components:
|
|
443
|
+
raise ValueError(
|
|
444
|
+
"At least one application component is required "
|
|
445
|
+
"(e.g. 'web', 'backend', 'native')."
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
unknown = [c for c in components if c not in COMPONENT_BY_ID]
|
|
449
|
+
if unknown:
|
|
450
|
+
raise ValueError(
|
|
451
|
+
f"Unknown component id(s): {', '.join(unknown)}. "
|
|
452
|
+
f"Valid ids: {', '.join(COMPONENT_BY_ID)}."
|
|
453
|
+
)
|
|
454
|
+
|
|
236
455
|
if not _detect_dlc_skill(workspace_root):
|
|
237
456
|
return False
|
|
238
457
|
|
|
239
458
|
root = workspace_root.resolve()
|
|
459
|
+
active = [COMPONENT_BY_ID[cid] for cid in components]
|
|
460
|
+
all_cross_ids = [c.id for c in CROSS_CUTTING]
|
|
461
|
+
|
|
462
|
+
_write_project_yaml(
|
|
463
|
+
root=root,
|
|
464
|
+
project_name=project_name,
|
|
465
|
+
active=active,
|
|
466
|
+
cross_cutting_ids=all_cross_ids,
|
|
467
|
+
description=description,
|
|
468
|
+
)
|
|
240
469
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
else:
|
|
244
|
-
active = list(SKILL_YAML_COMPONENTS)
|
|
470
|
+
for c in active:
|
|
471
|
+
_scaffold_component(c, root)
|
|
245
472
|
|
|
246
|
-
|
|
247
|
-
_write(root / "project.yaml", yaml.dump(_build_project_yaml(active), default_flow_style=False))
|
|
473
|
+
_scaffold_cross_cutting(all_cross_ids, root)
|
|
248
474
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
475
|
+
return True
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def add_component(
|
|
479
|
+
workspace_root: Path,
|
|
480
|
+
component_id: str,
|
|
481
|
+
) -> bool:
|
|
482
|
+
"""Add a single application component to an existing project.
|
|
483
|
+
|
|
484
|
+
Creates apps/<owns>/{src,tests/unit,tests/e2e,features} with
|
|
485
|
+
.gitkeep files and updates project.yaml's stack.components list.
|
|
486
|
+
|
|
487
|
+
Returns True if the component was added, False if it was already
|
|
488
|
+
present. Raises ValueError on unknown component id.
|
|
489
|
+
"""
|
|
490
|
+
spec = COMPONENT_BY_ID.get(component_id)
|
|
491
|
+
if spec is None:
|
|
492
|
+
raise ValueError(
|
|
493
|
+
f"Unknown component id: {component_id}. "
|
|
494
|
+
f"Valid ids: {', '.join(COMPONENT_BY_ID)}."
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
root = workspace_root.resolve()
|
|
498
|
+
project_yaml = root / "project.yaml"
|
|
499
|
+
if not project_yaml.exists():
|
|
500
|
+
raise FileNotFoundError(
|
|
501
|
+
f"project.yaml not found at {root}. Run 'cdh project init' first."
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
data = yaml.safe_load(project_yaml.read_text(encoding="utf-8")) or {}
|
|
505
|
+
components = data.get("stack", {}).get("components", []) or []
|
|
506
|
+
if any(c.get("id") == component_id for c in components):
|
|
507
|
+
return False
|
|
508
|
+
|
|
509
|
+
_scaffold_component(spec, root)
|
|
510
|
+
|
|
511
|
+
components.append(_component_to_dict(spec))
|
|
512
|
+
data["stack"]["components"] = components
|
|
513
|
+
project_yaml.write_text(
|
|
514
|
+
yaml.dump(data, default_flow_style=False),
|
|
515
|
+
encoding="utf-8",
|
|
257
516
|
)
|
|
517
|
+
return True
|
|
258
518
|
|
|
259
|
-
# --- .gitignore ---
|
|
260
|
-
_write(root / ".gitignore", GITIGNORE_CONTENT)
|
|
261
519
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
_mkdir(comp_dir / "tests" / "e2e")
|
|
268
|
-
_mkdir(comp_dir / "features")
|
|
269
|
-
_gitkeep(comp_dir / "src")
|
|
270
|
-
_gitkeep(comp_dir / "tests" / "unit")
|
|
271
|
-
_gitkeep(comp_dir / "tests" / "e2e")
|
|
272
|
-
_gitkeep(comp_dir / "features")
|
|
273
|
-
_gitkeep(comp_dir)
|
|
274
|
-
|
|
275
|
-
# --- contracts/{api,events}/ + CHANGELOG.md ---
|
|
276
|
-
_mkdir(root / "contracts" / "api")
|
|
277
|
-
_mkdir(root / "contracts" / "events")
|
|
278
|
-
_gitkeep(root / "contracts" / "api")
|
|
279
|
-
_gitkeep(root / "contracts" / "events")
|
|
280
|
-
_write(root / "contracts" / "CHANGELOG.md", CHANGELOG_MD)
|
|
281
|
-
|
|
282
|
-
# --- packages/shared/ ---
|
|
283
|
-
_mkdir(root / "packages" / "shared")
|
|
284
|
-
_gitkeep(root / "packages" / "shared")
|
|
285
|
-
|
|
286
|
-
# --- features/cross-stack/ ---
|
|
287
|
-
_mkdir(root / "features" / "cross-stack")
|
|
288
|
-
_gitkeep(root / "features" / "cross-stack")
|
|
289
|
-
|
|
290
|
-
# --- tests/{contract,cross-stack}/ ---
|
|
291
|
-
_mkdir(root / "tests" / "contract")
|
|
292
|
-
_mkdir(root / "tests" / "cross-stack")
|
|
293
|
-
_gitkeep(root / "tests" / "contract")
|
|
294
|
-
_gitkeep(root / "tests" / "cross-stack")
|
|
295
|
-
|
|
296
|
-
# --- openspec/changes/ ---
|
|
297
|
-
_mkdir(root / "openspec" / "changes")
|
|
298
|
-
_gitkeep(root / "openspec" / "changes")
|
|
299
|
-
|
|
300
|
-
# --- providers/tcb/ ---
|
|
301
|
-
_mkdir(root / "providers" / "tcb")
|
|
302
|
-
_write(root / "providers" / "tcb" / "provider.yaml", TCB_PROVIDER_YAML)
|
|
303
|
-
_write(root / "providers" / "tcb" / "deployment.yaml", TCB_DEPLOYMENT_YAML)
|
|
304
|
-
_write(root / "providers" / "tcb" / "preview.yaml", TCB_PREVIEW_YAML)
|
|
305
|
-
|
|
306
|
-
# --- tools/ stub scripts ---
|
|
307
|
-
_mkdir(root / "tools")
|
|
308
|
-
for tool in ("deploy_stack.py", "contract_diff.py", "generate_shared.py"):
|
|
309
|
-
_write(root / "tools" / tool, TOOL_STUB)
|
|
310
|
-
(root / "tools" / tool).chmod(0o755)
|
|
520
|
+
def add_cross_cutting(
|
|
521
|
+
workspace_root: Path,
|
|
522
|
+
cross_id: str,
|
|
523
|
+
) -> bool:
|
|
524
|
+
"""Add a single cross-cutting item to an existing project.
|
|
311
525
|
|
|
526
|
+
Creates the relevant directories/files and updates project.yaml
|
|
527
|
+
so that stack.cross_cutting references the new path.
|
|
528
|
+
|
|
529
|
+
Returns True if the item was added, False if it was already present.
|
|
530
|
+
Raises ValueError on unknown cross-cutting id.
|
|
531
|
+
"""
|
|
532
|
+
spec = CROSS_CUTTING_BY_ID.get(cross_id)
|
|
533
|
+
if spec is None:
|
|
534
|
+
raise ValueError(
|
|
535
|
+
f"Unknown cross-cutting id: {cross_id}. "
|
|
536
|
+
f"Valid ids: {', '.join(CROSS_CUTTING_BY_ID)}."
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
root = workspace_root.resolve()
|
|
540
|
+
project_yaml = root / "project.yaml"
|
|
541
|
+
if not project_yaml.exists():
|
|
542
|
+
raise FileNotFoundError(
|
|
543
|
+
f"project.yaml not found at {root}. Run 'cdh project init' first."
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
cross_paths = [root / p for p in spec.paths]
|
|
547
|
+
if all(p.exists() for p in cross_paths):
|
|
548
|
+
return False
|
|
549
|
+
|
|
550
|
+
_scaffold_cross_cutting([cross_id], root)
|
|
551
|
+
|
|
552
|
+
data = yaml.safe_load(project_yaml.read_text(encoding="utf-8")) or {}
|
|
553
|
+
cross_cutting = data.setdefault("stack", {}).setdefault("cross_cutting", {})
|
|
554
|
+
if cross_id == "contracts":
|
|
555
|
+
cross_cutting["contracts"] = "contracts/"
|
|
556
|
+
elif cross_id == "shared":
|
|
557
|
+
cross_cutting["shared_types"] = "packages/shared/"
|
|
558
|
+
|
|
559
|
+
project_yaml.write_text(
|
|
560
|
+
yaml.dump(data, default_flow_style=False),
|
|
561
|
+
encoding="utf-8",
|
|
562
|
+
)
|
|
312
563
|
return True
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
__all__ = [
|
|
567
|
+
"COMPONENTS",
|
|
568
|
+
"COMPONENT_BY_ID",
|
|
569
|
+
"CROSS_CUTTING",
|
|
570
|
+
"CROSS_CUTTING_BY_ID",
|
|
571
|
+
"ComponentSpec",
|
|
572
|
+
"CrossCutSpec",
|
|
573
|
+
"init_dlc_project",
|
|
574
|
+
"scaffold_dlc_project",
|
|
575
|
+
"add_component",
|
|
576
|
+
"add_cross_cutting",
|
|
577
|
+
]
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|