@laitszkin/apollo-toolkit 3.13.2 → 3.14.0
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/AGENTS.md +7 -7
- package/CHANGELOG.md +27 -0
- package/CLAUDE.md +8 -8
- package/analyse-app-logs/SKILL.md +3 -3
- package/bin/apollo-toolkit.ts +7 -0
- package/codex/codex-memory-manager/SKILL.md +2 -2
- package/codex/learn-skill-from-conversations/SKILL.md +3 -3
- package/dist/bin/apollo-toolkit.d.ts +2 -0
- package/dist/bin/apollo-toolkit.js +7 -0
- package/dist/lib/cli.d.ts +41 -0
- package/dist/lib/cli.js +655 -0
- package/dist/lib/installer.d.ts +59 -0
- package/dist/lib/installer.js +404 -0
- package/dist/lib/tool-runner.d.ts +19 -0
- package/dist/lib/tool-runner.js +536 -0
- package/dist/lib/tools/architecture.d.ts +2 -0
- package/dist/lib/tools/architecture.js +34 -0
- package/dist/lib/tools/create-specs.d.ts +2 -0
- package/dist/lib/tools/create-specs.js +175 -0
- package/dist/lib/tools/docs-to-voice.d.ts +2 -0
- package/dist/lib/tools/docs-to-voice.js +705 -0
- package/dist/lib/tools/enforce-video-aspect-ratio.d.ts +2 -0
- package/dist/lib/tools/enforce-video-aspect-ratio.js +312 -0
- package/dist/lib/tools/extract-conversations.d.ts +2 -0
- package/dist/lib/tools/extract-conversations.js +105 -0
- package/dist/lib/tools/extract-pdf-text.d.ts +2 -0
- package/dist/lib/tools/extract-pdf-text.js +92 -0
- package/dist/lib/tools/filter-logs.d.ts +2 -0
- package/dist/lib/tools/filter-logs.js +94 -0
- package/dist/lib/tools/find-github-issues.d.ts +2 -0
- package/dist/lib/tools/find-github-issues.js +176 -0
- package/dist/lib/tools/generate-storyboard-images.d.ts +2 -0
- package/dist/lib/tools/generate-storyboard-images.js +419 -0
- package/dist/lib/tools/log-cli-utils.d.ts +35 -0
- package/dist/lib/tools/log-cli-utils.js +233 -0
- package/dist/lib/tools/open-github-issue.d.ts +2 -0
- package/dist/lib/tools/open-github-issue.js +750 -0
- package/dist/lib/tools/read-github-issue.d.ts +2 -0
- package/dist/lib/tools/read-github-issue.js +134 -0
- package/dist/lib/tools/render-error-book.d.ts +2 -0
- package/dist/lib/tools/render-error-book.js +265 -0
- package/dist/lib/tools/render-katex.d.ts +2 -0
- package/dist/lib/tools/render-katex.js +294 -0
- package/dist/lib/tools/review-threads.d.ts +2 -0
- package/dist/lib/tools/review-threads.js +491 -0
- package/dist/lib/tools/search-logs.d.ts +2 -0
- package/dist/lib/tools/search-logs.js +164 -0
- package/dist/lib/tools/sync-memory-index.d.ts +2 -0
- package/dist/lib/tools/sync-memory-index.js +113 -0
- package/dist/lib/tools/validate-openai-agent-config.d.ts +2 -0
- package/dist/lib/tools/validate-openai-agent-config.js +184 -0
- package/dist/lib/tools/validate-skill-frontmatter.d.ts +2 -0
- package/dist/lib/tools/validate-skill-frontmatter.js +118 -0
- package/dist/lib/types.d.ts +82 -0
- package/dist/lib/types.js +2 -0
- package/dist/lib/updater.d.ts +34 -0
- package/dist/lib/updater.js +112 -0
- package/dist/lib/utils/format.d.ts +2 -0
- package/dist/lib/utils/format.js +6 -0
- package/dist/lib/utils/terminal.d.ts +12 -0
- package/dist/lib/utils/terminal.js +26 -0
- package/docs-to-voice/SKILL.md +0 -1
- package/generate-spec/SKILL.md +1 -1
- package/katex/SKILL.md +1 -2
- package/lib/cli.ts +780 -0
- package/lib/installer.ts +466 -0
- package/lib/tool-runner.ts +561 -0
- package/lib/tools/architecture.ts +34 -0
- package/lib/tools/create-specs.ts +204 -0
- package/lib/tools/docs-to-voice.ts +799 -0
- package/lib/tools/enforce-video-aspect-ratio.ts +368 -0
- package/lib/tools/extract-conversations.ts +114 -0
- package/lib/tools/extract-pdf-text.ts +99 -0
- package/lib/tools/filter-logs.ts +118 -0
- package/lib/tools/find-github-issues.ts +211 -0
- package/lib/tools/generate-storyboard-images.ts +455 -0
- package/lib/tools/log-cli-utils.ts +262 -0
- package/lib/tools/open-github-issue.ts +930 -0
- package/lib/tools/read-github-issue.ts +179 -0
- package/lib/tools/render-error-book.ts +300 -0
- package/lib/tools/render-katex.ts +325 -0
- package/lib/tools/review-threads.ts +590 -0
- package/lib/tools/search-logs.ts +200 -0
- package/lib/tools/sync-memory-index.ts +114 -0
- package/lib/tools/validate-openai-agent-config.ts +209 -0
- package/lib/tools/validate-skill-frontmatter.ts +124 -0
- package/lib/types.ts +90 -0
- package/lib/updater.ts +165 -0
- package/lib/utils/format.ts +7 -0
- package/lib/utils/terminal.ts +22 -0
- package/open-github-issue/SKILL.md +2 -2
- package/optimise-skill/SKILL.md +1 -1
- package/package.json +13 -4
- package/resources/project-architecture/assets/architecture.css +764 -0
- package/resources/project-architecture/assets/viewer.client.js +144 -0
- package/resources/project-architecture/index.html +42 -0
- package/review-spec-related-changes/SKILL.md +1 -1
- package/solve-issues-found-during-review/SKILL.md +2 -1
- package/tsconfig.json +28 -0
- package/analyse-app-logs/scripts/__pycache__/filter_logs_by_time.cpython-312.pyc +0 -0
- package/analyse-app-logs/scripts/__pycache__/log_cli_utils.cpython-312.pyc +0 -0
- package/analyse-app-logs/scripts/__pycache__/search_logs.cpython-312.pyc +0 -0
- package/analyse-app-logs/scripts/filter_logs_by_time.py +0 -64
- package/analyse-app-logs/scripts/log_cli_utils.py +0 -112
- package/analyse-app-logs/scripts/search_logs.py +0 -137
- package/analyse-app-logs/tests/test_filter_logs_by_time.py +0 -95
- package/analyse-app-logs/tests/test_search_logs.py +0 -100
- package/codex/codex-memory-manager/scripts/extract_recent_conversations.py +0 -369
- package/codex/codex-memory-manager/scripts/sync_memory_index.py +0 -130
- package/codex/codex-memory-manager/tests/test_extract_recent_conversations.py +0 -177
- package/codex/codex-memory-manager/tests/test_memory_template.py +0 -37
- package/codex/codex-memory-manager/tests/test_sync_memory_index.py +0 -84
- package/codex/learn-skill-from-conversations/scripts/extract_recent_conversations.py +0 -369
- package/codex/learn-skill-from-conversations/tests/test_extract_recent_conversations.py +0 -177
- package/docs-to-voice/scripts/__pycache__/docs_to_voice.cpython-312.pyc +0 -0
- package/docs-to-voice/scripts/docs_to_voice.py +0 -1385
- package/docs-to-voice/scripts/docs_to_voice.sh +0 -11
- package/docs-to-voice/tests/test_docs_to_voice_api_max_chars.py +0 -210
- package/docs-to-voice/tests/test_docs_to_voice_sentence_timeline.py +0 -115
- package/docs-to-voice/tests/test_docs_to_voice_settings.py +0 -43
- package/docs-to-voice/tests/test_docs_to_voice_shell_wrapper.py +0 -51
- package/docs-to-voice/tests/test_docs_to_voice_speech_rate.py +0 -57
- package/generate-spec/scripts/__pycache__/create-specscpython-312.pyc +0 -0
- package/generate-spec/scripts/create-specs +0 -215
- package/generate-spec/tests/test_create_specs.py +0 -200
- package/init-project-html/scripts/architecture-bootstrap-render.js +0 -16
- package/init-project-html/scripts/architecture.js +0 -296
- package/katex/scripts/__pycache__/render_katex.cpython-312.pyc +0 -0
- package/katex/scripts/render_katex.py +0 -247
- package/katex/scripts/render_katex.sh +0 -11
- package/katex/tests/test_render_katex.py +0 -174
- package/learning-error-book/scripts/render_error_book_json_to_pdf.py +0 -590
- package/learning-error-book/tests/test_render_error_book_json_to_pdf.py +0 -134
- package/open-github-issue/scripts/__pycache__/open_github_issue.cpython-312.pyc +0 -0
- package/open-github-issue/scripts/open_github_issue.py +0 -705
- package/open-github-issue/tests/test_open_github_issue.py +0 -381
- package/openai-text-to-image-storyboard/scripts/generate_storyboard_images.py +0 -763
- package/openai-text-to-image-storyboard/tests/test_generate_storyboard_images.py +0 -177
- package/read-github-issue/scripts/__pycache__/find_issues.cpython-312.pyc +0 -0
- package/read-github-issue/scripts/__pycache__/read_issue.cpython-312.pyc +0 -0
- package/read-github-issue/scripts/find_issues.py +0 -148
- package/read-github-issue/scripts/read_issue.py +0 -108
- package/read-github-issue/tests/test_find_issues.py +0 -127
- package/read-github-issue/tests/test_read_issue.py +0 -109
- package/resolve-review-comments/scripts/__pycache__/review_threads.cpython-312.pyc +0 -0
- package/resolve-review-comments/scripts/review_threads.py +0 -425
- package/resolve-review-comments/tests/test_review_threads.py +0 -74
- package/scripts/validate_openai_agent_config.py +0 -209
- package/scripts/validate_skill_frontmatter.py +0 -131
- package/text-to-short-video/scripts/__pycache__/enforce_video_aspect_ratio.cpython-312.pyc +0 -0
- package/text-to-short-video/scripts/enforce_video_aspect_ratio.py +0 -350
- package/text-to-short-video/tests/test_enforce_video_aspect_ratio.py +0 -194
- package/weekly-financial-event-report/scripts/extract_pdf_text_pdfkit.swift +0 -99
- package/weekly-financial-event-report/tests/test_extract_pdf_text_pdfkit.py +0 -64
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
from __future__ import annotations
|
|
3
|
-
|
|
4
|
-
import argparse
|
|
5
|
-
import re
|
|
6
|
-
from datetime import date
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
|
|
9
|
-
TEMPLATE_FILENAMES = (
|
|
10
|
-
"spec.md",
|
|
11
|
-
"tasks.md",
|
|
12
|
-
"checklist.md",
|
|
13
|
-
"contract.md",
|
|
14
|
-
"design.md",
|
|
15
|
-
)
|
|
16
|
-
COORDINATION_TEMPLATE = "coordination.md"
|
|
17
|
-
PREPARATION_TEMPLATE = "preparation.md"
|
|
18
|
-
PLACEHOLDERS = ("[Feature Name]", "[功能名稱]")
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def _slugify(text: str) -> str:
|
|
22
|
-
slug = text.lower().strip()
|
|
23
|
-
slug = re.sub(r"[^a-z0-9]+", "-", slug)
|
|
24
|
-
slug = re.sub(r"-+", "-", slug).strip("-")
|
|
25
|
-
return slug
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def _default_template_dir() -> Path:
|
|
29
|
-
return Path(__file__).resolve().parent.parent / "references" / "templates"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def _render(
|
|
33
|
-
content: str,
|
|
34
|
-
today: str,
|
|
35
|
-
feature_name: str,
|
|
36
|
-
change_name: str,
|
|
37
|
-
batch_name: str | None,
|
|
38
|
-
) -> str:
|
|
39
|
-
rendered = content.replace("[YYYY-MM-DD]", today)
|
|
40
|
-
for placeholder in PLACEHOLDERS:
|
|
41
|
-
rendered = rendered.replace(placeholder, feature_name)
|
|
42
|
-
rendered = rendered.replace("[change_name]", change_name)
|
|
43
|
-
rendered = rendered.replace("[batch_name]", batch_name or "None")
|
|
44
|
-
return rendered
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def main() -> int:
|
|
48
|
-
parser = argparse.ArgumentParser(
|
|
49
|
-
description=(
|
|
50
|
-
"Create planning docs (spec.md, tasks.md, checklist.md, contract.md, design.md) "
|
|
51
|
-
"from templates with folder format docs/plans/{date}/{change_name} "
|
|
52
|
-
"or docs/plans/{date}/{batch_name}/{change_name}, including coordination "
|
|
53
|
-
"and optional preparation templates for batch specs that need shared "
|
|
54
|
-
"pre-work before parallel implementation."
|
|
55
|
-
),
|
|
56
|
-
)
|
|
57
|
-
parser.add_argument("feature_name", help="Display name used in generated documents")
|
|
58
|
-
parser.add_argument(
|
|
59
|
-
"--change-name",
|
|
60
|
-
"--slug",
|
|
61
|
-
dest="change_name",
|
|
62
|
-
help=(
|
|
63
|
-
"Folder name part used after date. "
|
|
64
|
-
"Defaults to slugified feature_name when omitted."
|
|
65
|
-
),
|
|
66
|
-
)
|
|
67
|
-
parser.add_argument(
|
|
68
|
-
"--batch-name",
|
|
69
|
-
help=(
|
|
70
|
-
"Optional batch folder used for parallel spec generation. "
|
|
71
|
-
"When provided, specs are written under docs/plans/{date}/{batch_name}/{change_name}."
|
|
72
|
-
),
|
|
73
|
-
)
|
|
74
|
-
parser.add_argument(
|
|
75
|
-
"--with-coordination",
|
|
76
|
-
action="store_true",
|
|
77
|
-
help=(
|
|
78
|
-
"When used with --batch-name, also create or update "
|
|
79
|
-
"docs/plans/{date}/{batch_name}/coordination.md from the shared template "
|
|
80
|
-
"for up-front ownership and collision coordination."
|
|
81
|
-
),
|
|
82
|
-
)
|
|
83
|
-
parser.add_argument(
|
|
84
|
-
"--with-preparation",
|
|
85
|
-
action="store_true",
|
|
86
|
-
help=(
|
|
87
|
-
"When used with --batch-name, also create or update "
|
|
88
|
-
"docs/plans/{date}/{batch_name}/preparation.md from the shared template "
|
|
89
|
-
"for prerequisite work that must be completed before parallel implementation."
|
|
90
|
-
),
|
|
91
|
-
)
|
|
92
|
-
parser.add_argument(
|
|
93
|
-
"--output-dir",
|
|
94
|
-
default="docs/plans",
|
|
95
|
-
help="Output directory (default: docs/plans)",
|
|
96
|
-
)
|
|
97
|
-
parser.add_argument(
|
|
98
|
-
"--template-dir",
|
|
99
|
-
default=str(_default_template_dir()),
|
|
100
|
-
help="Directory containing planning document templates",
|
|
101
|
-
)
|
|
102
|
-
parser.add_argument(
|
|
103
|
-
"--force",
|
|
104
|
-
action="store_true",
|
|
105
|
-
help="Overwrite existing files if present",
|
|
106
|
-
)
|
|
107
|
-
args = parser.parse_args()
|
|
108
|
-
|
|
109
|
-
feature_name = args.feature_name.strip()
|
|
110
|
-
if not feature_name:
|
|
111
|
-
raise SystemExit("feature_name cannot be empty")
|
|
112
|
-
|
|
113
|
-
change_name = (
|
|
114
|
-
args.change_name.strip() if args.change_name else _slugify(feature_name)
|
|
115
|
-
)
|
|
116
|
-
if not change_name:
|
|
117
|
-
raise SystemExit(
|
|
118
|
-
"Unable to build change_name. Provide --change-name with ASCII letters/numbers."
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
batch_name = args.batch_name.strip() if args.batch_name else None
|
|
122
|
-
if args.with_coordination and not batch_name:
|
|
123
|
-
raise SystemExit("--with-coordination requires --batch-name")
|
|
124
|
-
if args.with_preparation and not batch_name:
|
|
125
|
-
raise SystemExit("--with-preparation requires --batch-name")
|
|
126
|
-
|
|
127
|
-
template_dir = Path(args.template_dir).expanduser().resolve()
|
|
128
|
-
if not template_dir.exists() or not template_dir.is_dir():
|
|
129
|
-
raise SystemExit(f"Template directory not found: {template_dir}")
|
|
130
|
-
|
|
131
|
-
missing_templates = [
|
|
132
|
-
name for name in TEMPLATE_FILENAMES if not (template_dir / name).exists()
|
|
133
|
-
]
|
|
134
|
-
if args.with_coordination and not (template_dir / COORDINATION_TEMPLATE).exists():
|
|
135
|
-
missing_templates.append(COORDINATION_TEMPLATE)
|
|
136
|
-
if args.with_preparation and not (template_dir / PREPARATION_TEMPLATE).exists():
|
|
137
|
-
missing_templates.append(PREPARATION_TEMPLATE)
|
|
138
|
-
if missing_templates:
|
|
139
|
-
missing = ", ".join(missing_templates)
|
|
140
|
-
raise SystemExit(f"Missing template files in {template_dir}: {missing}")
|
|
141
|
-
|
|
142
|
-
output_dir = Path(args.output_dir).expanduser().resolve()
|
|
143
|
-
today = date.today().isoformat()
|
|
144
|
-
date_root = output_dir / today
|
|
145
|
-
batch_root = date_root / batch_name if batch_name else None
|
|
146
|
-
output_root = (batch_root / change_name) if batch_root else (date_root / change_name)
|
|
147
|
-
|
|
148
|
-
output_paths = [output_root / name for name in TEMPLATE_FILENAMES]
|
|
149
|
-
coordination_path = (
|
|
150
|
-
batch_root / COORDINATION_TEMPLATE if args.with_coordination and batch_root else None
|
|
151
|
-
)
|
|
152
|
-
preparation_path = (
|
|
153
|
-
batch_root / PREPARATION_TEMPLATE if args.with_preparation and batch_root else None
|
|
154
|
-
)
|
|
155
|
-
existing_files = [path for path in output_paths if path.exists()]
|
|
156
|
-
if existing_files and not args.force:
|
|
157
|
-
existing = ", ".join(str(path) for path in existing_files)
|
|
158
|
-
raise SystemExit(
|
|
159
|
-
f"Files already exist: {existing}. Use --force to overwrite existing files."
|
|
160
|
-
)
|
|
161
|
-
|
|
162
|
-
output_root.mkdir(parents=True, exist_ok=True)
|
|
163
|
-
|
|
164
|
-
for filename in TEMPLATE_FILENAMES:
|
|
165
|
-
template_path = template_dir / filename
|
|
166
|
-
output_path = output_root / filename
|
|
167
|
-
content = template_path.read_text(encoding="utf-8")
|
|
168
|
-
output_path.write_text(
|
|
169
|
-
_render(
|
|
170
|
-
content=content,
|
|
171
|
-
today=today,
|
|
172
|
-
feature_name=feature_name,
|
|
173
|
-
change_name=change_name,
|
|
174
|
-
batch_name=batch_name,
|
|
175
|
-
),
|
|
176
|
-
encoding="utf-8",
|
|
177
|
-
)
|
|
178
|
-
|
|
179
|
-
if coordination_path and (args.force or not coordination_path.exists()):
|
|
180
|
-
coordination_template = template_dir / COORDINATION_TEMPLATE
|
|
181
|
-
coordination_path.write_text(
|
|
182
|
-
_render(
|
|
183
|
-
content=coordination_template.read_text(encoding="utf-8"),
|
|
184
|
-
today=today,
|
|
185
|
-
feature_name=feature_name,
|
|
186
|
-
change_name=change_name,
|
|
187
|
-
batch_name=batch_name,
|
|
188
|
-
),
|
|
189
|
-
encoding="utf-8",
|
|
190
|
-
)
|
|
191
|
-
|
|
192
|
-
if preparation_path and (args.force or not preparation_path.exists()):
|
|
193
|
-
preparation_template = template_dir / PREPARATION_TEMPLATE
|
|
194
|
-
preparation_path.write_text(
|
|
195
|
-
_render(
|
|
196
|
-
content=preparation_template.read_text(encoding="utf-8"),
|
|
197
|
-
today=today,
|
|
198
|
-
feature_name=feature_name,
|
|
199
|
-
change_name=change_name,
|
|
200
|
-
batch_name=batch_name,
|
|
201
|
-
),
|
|
202
|
-
encoding="utf-8",
|
|
203
|
-
)
|
|
204
|
-
|
|
205
|
-
for output_path in output_paths:
|
|
206
|
-
print(output_path)
|
|
207
|
-
if coordination_path:
|
|
208
|
-
print(coordination_path)
|
|
209
|
-
if preparation_path:
|
|
210
|
-
print(preparation_path)
|
|
211
|
-
return 0
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
if __name__ == "__main__":
|
|
215
|
-
raise SystemExit(main())
|
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import importlib.machinery
|
|
6
|
-
import importlib.util
|
|
7
|
-
import io
|
|
8
|
-
import random
|
|
9
|
-
import re
|
|
10
|
-
import string
|
|
11
|
-
import sys
|
|
12
|
-
import tempfile
|
|
13
|
-
import unittest
|
|
14
|
-
from datetime import date
|
|
15
|
-
from pathlib import Path
|
|
16
|
-
from unittest.mock import patch
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
SCRIPT_PATH = Path(__file__).resolve().parents[1] / "scripts" / "create-specs"
|
|
20
|
-
LOADER = importlib.machinery.SourceFileLoader("create_specs", str(SCRIPT_PATH))
|
|
21
|
-
SPEC = importlib.util.spec_from_loader("create_specs", LOADER)
|
|
22
|
-
MODULE = importlib.util.module_from_spec(SPEC)
|
|
23
|
-
SPEC.loader.exec_module(MODULE)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
class FixedDate(date):
|
|
27
|
-
@classmethod
|
|
28
|
-
def today(cls) -> "FixedDate":
|
|
29
|
-
return cls(2026, 4, 18)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class CreateSpecsTests(unittest.TestCase):
|
|
33
|
-
def test_slugify_property_keeps_safe_slug_shape(self) -> None:
|
|
34
|
-
alphabet = string.ascii_letters + string.digits + string.punctuation + " \t中文"
|
|
35
|
-
generator = random.Random(20260418)
|
|
36
|
-
|
|
37
|
-
for _ in range(250):
|
|
38
|
-
raw = "".join(generator.choice(alphabet) for _ in range(generator.randint(0, 40)))
|
|
39
|
-
slug = MODULE._slugify(raw)
|
|
40
|
-
with self.subTest(raw=raw, slug=slug):
|
|
41
|
-
self.assertEqual(slug, slug.lower())
|
|
42
|
-
self.assertNotIn("--", slug)
|
|
43
|
-
self.assertFalse(slug.startswith("-"))
|
|
44
|
-
self.assertFalse(slug.endswith("-"))
|
|
45
|
-
self.assertRegex(slug, r"^[a-z0-9-]*$")
|
|
46
|
-
|
|
47
|
-
def test_render_replaces_all_placeholders(self) -> None:
|
|
48
|
-
rendered = MODULE._render(
|
|
49
|
-
content=(
|
|
50
|
-
"[YYYY-MM-DD]\n"
|
|
51
|
-
"[Feature Name]\n"
|
|
52
|
-
"[功能名稱]\n"
|
|
53
|
-
"[change_name]\n"
|
|
54
|
-
"[batch_name]\n"
|
|
55
|
-
),
|
|
56
|
-
today="2026-04-18",
|
|
57
|
-
feature_name="Batch-safe planner",
|
|
58
|
-
change_name="batch-safe-planner",
|
|
59
|
-
batch_name="parallel-batch",
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
self.assertIn("2026-04-18", rendered)
|
|
63
|
-
self.assertEqual(rendered.count("Batch-safe planner"), 2)
|
|
64
|
-
self.assertIn("batch-safe-planner", rendered)
|
|
65
|
-
self.assertIn("parallel-batch", rendered)
|
|
66
|
-
|
|
67
|
-
def test_main_creates_spec_files_and_shared_batch_files(self) -> None:
|
|
68
|
-
with tempfile.TemporaryDirectory() as temp_dir:
|
|
69
|
-
root = Path(temp_dir)
|
|
70
|
-
template_dir = root / "templates"
|
|
71
|
-
output_dir = root / "docs" / "plans"
|
|
72
|
-
template_dir.mkdir(parents=True)
|
|
73
|
-
|
|
74
|
-
for name in MODULE.TEMPLATE_FILENAMES:
|
|
75
|
-
(template_dir / name).write_text(
|
|
76
|
-
f"{name} [YYYY-MM-DD] [Feature Name] [change_name] [batch_name]\n",
|
|
77
|
-
encoding="utf-8",
|
|
78
|
-
)
|
|
79
|
-
(template_dir / MODULE.COORDINATION_TEMPLATE).write_text(
|
|
80
|
-
"coordination [YYYY-MM-DD] [Feature Name] [change_name] [batch_name]\n",
|
|
81
|
-
encoding="utf-8",
|
|
82
|
-
)
|
|
83
|
-
(template_dir / MODULE.PREPARATION_TEMPLATE).write_text(
|
|
84
|
-
"preparation [YYYY-MM-DD] [Feature Name] [change_name] [batch_name]\n",
|
|
85
|
-
encoding="utf-8",
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
argv = [
|
|
89
|
-
"create-specs",
|
|
90
|
-
"My Feature",
|
|
91
|
-
"--batch-name",
|
|
92
|
-
"parallel-batch",
|
|
93
|
-
"--with-coordination",
|
|
94
|
-
"--with-preparation",
|
|
95
|
-
"--output-dir",
|
|
96
|
-
str(output_dir),
|
|
97
|
-
"--template-dir",
|
|
98
|
-
str(template_dir),
|
|
99
|
-
]
|
|
100
|
-
|
|
101
|
-
with patch.object(MODULE, "date", FixedDate), patch.object(sys, "argv", argv), patch(
|
|
102
|
-
"sys.stdout", new_callable=io.StringIO
|
|
103
|
-
) as stdout:
|
|
104
|
-
exit_code = MODULE.main()
|
|
105
|
-
|
|
106
|
-
self.assertEqual(exit_code, 0)
|
|
107
|
-
change_root = output_dir / "2026-04-18" / "parallel-batch" / "my-feature"
|
|
108
|
-
for name in MODULE.TEMPLATE_FILENAMES:
|
|
109
|
-
content = (change_root / name).read_text(encoding="utf-8")
|
|
110
|
-
self.assertIn("2026-04-18", content)
|
|
111
|
-
self.assertIn("My Feature", content)
|
|
112
|
-
self.assertIn("my-feature", content)
|
|
113
|
-
self.assertIn("parallel-batch", content)
|
|
114
|
-
|
|
115
|
-
coordination = output_dir / "2026-04-18" / "parallel-batch" / "coordination.md"
|
|
116
|
-
self.assertTrue(coordination.is_file())
|
|
117
|
-
self.assertIn(str(coordination), stdout.getvalue())
|
|
118
|
-
preparation = output_dir / "2026-04-18" / "parallel-batch" / "preparation.md"
|
|
119
|
-
self.assertTrue(preparation.is_file())
|
|
120
|
-
self.assertIn("preparation", preparation.read_text(encoding="utf-8"))
|
|
121
|
-
self.assertIn(str(preparation), stdout.getvalue())
|
|
122
|
-
|
|
123
|
-
def test_main_requires_batch_name_for_preparation_file(self) -> None:
|
|
124
|
-
with tempfile.TemporaryDirectory() as temp_dir:
|
|
125
|
-
template_dir = Path(temp_dir) / "templates"
|
|
126
|
-
template_dir.mkdir(parents=True)
|
|
127
|
-
for name in MODULE.TEMPLATE_FILENAMES:
|
|
128
|
-
(template_dir / name).write_text("template\n", encoding="utf-8")
|
|
129
|
-
(template_dir / MODULE.PREPARATION_TEMPLATE).write_text(
|
|
130
|
-
"preparation\n",
|
|
131
|
-
encoding="utf-8",
|
|
132
|
-
)
|
|
133
|
-
|
|
134
|
-
argv = [
|
|
135
|
-
"create-specs",
|
|
136
|
-
"My Feature",
|
|
137
|
-
"--with-preparation",
|
|
138
|
-
"--template-dir",
|
|
139
|
-
str(template_dir),
|
|
140
|
-
]
|
|
141
|
-
|
|
142
|
-
with patch.object(sys, "argv", argv):
|
|
143
|
-
with self.assertRaises(SystemExit) as context:
|
|
144
|
-
MODULE.main()
|
|
145
|
-
|
|
146
|
-
self.assertIn("--with-preparation requires --batch-name", str(context.exception))
|
|
147
|
-
|
|
148
|
-
def test_main_rejects_existing_files_without_force(self) -> None:
|
|
149
|
-
with tempfile.TemporaryDirectory() as temp_dir:
|
|
150
|
-
root = Path(temp_dir)
|
|
151
|
-
template_dir = root / "templates"
|
|
152
|
-
output_dir = root / "docs" / "plans"
|
|
153
|
-
template_dir.mkdir(parents=True)
|
|
154
|
-
for name in MODULE.TEMPLATE_FILENAMES:
|
|
155
|
-
(template_dir / name).write_text("template\n", encoding="utf-8")
|
|
156
|
-
|
|
157
|
-
existing_root = output_dir / "2026-04-18" / "existing-change"
|
|
158
|
-
existing_root.mkdir(parents=True)
|
|
159
|
-
(existing_root / "spec.md").write_text("existing\n", encoding="utf-8")
|
|
160
|
-
|
|
161
|
-
argv = [
|
|
162
|
-
"create-specs",
|
|
163
|
-
"Existing Change",
|
|
164
|
-
"--change-name",
|
|
165
|
-
"existing-change",
|
|
166
|
-
"--output-dir",
|
|
167
|
-
str(output_dir),
|
|
168
|
-
"--template-dir",
|
|
169
|
-
str(template_dir),
|
|
170
|
-
]
|
|
171
|
-
|
|
172
|
-
with patch.object(MODULE, "date", FixedDate), patch.object(sys, "argv", argv):
|
|
173
|
-
with self.assertRaises(SystemExit) as context:
|
|
174
|
-
MODULE.main()
|
|
175
|
-
|
|
176
|
-
self.assertIn("Files already exist", str(context.exception))
|
|
177
|
-
|
|
178
|
-
def test_main_requires_explicit_ascii_change_name_when_slug_is_empty(self) -> None:
|
|
179
|
-
with tempfile.TemporaryDirectory() as temp_dir:
|
|
180
|
-
template_dir = Path(temp_dir) / "templates"
|
|
181
|
-
template_dir.mkdir(parents=True)
|
|
182
|
-
for name in MODULE.TEMPLATE_FILENAMES:
|
|
183
|
-
(template_dir / name).write_text("template\n", encoding="utf-8")
|
|
184
|
-
|
|
185
|
-
argv = [
|
|
186
|
-
"create-specs",
|
|
187
|
-
"功能名稱",
|
|
188
|
-
"--template-dir",
|
|
189
|
-
str(template_dir),
|
|
190
|
-
]
|
|
191
|
-
|
|
192
|
-
with patch.object(sys, "argv", argv):
|
|
193
|
-
with self.assertRaises(SystemExit) as context:
|
|
194
|
-
MODULE.main()
|
|
195
|
-
|
|
196
|
-
self.assertIn("Unable to build change_name", str(context.exception))
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
if __name__ == "__main__":
|
|
200
|
-
unittest.main()
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict';
|
|
3
|
-
|
|
4
|
-
// One-shot helper: `node architecture-bootstrap-render.js render --project <root> ...`
|
|
5
|
-
// Invoked synchronously from architecture.js (legacy open) when index.html is missing
|
|
6
|
-
// so the sync `main()` can still bootstrap an empty tree without duplicating elk layout.
|
|
7
|
-
|
|
8
|
-
const cli = require('../lib/atlas/cli');
|
|
9
|
-
|
|
10
|
-
(async () => {
|
|
11
|
-
const code = await cli.dispatch(process.argv.slice(2));
|
|
12
|
-
process.exit(typeof code === 'number' ? code : 1);
|
|
13
|
-
})().catch((err) => {
|
|
14
|
-
process.stderr.write(`${err && err.stack ? err.stack : err}\n`);
|
|
15
|
-
process.exit(1);
|
|
16
|
-
});
|