@laitszkin/apollo-toolkit 2.14.22 → 3.0.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 +3 -0
- package/CHANGELOG.md +18 -0
- package/README.md +9 -0
- package/analyse-app-logs/README.md +5 -5
- package/analyse-app-logs/SKILL.md +7 -5
- 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/codex/codex-memory-manager/README.md +2 -2
- package/codex/codex-memory-manager/SKILL.md +5 -5
- package/codex/codex-memory-manager/tests/test_extract_recent_conversations.py +3 -2
- package/codex/learn-skill-from-conversations/README.md +1 -1
- package/codex/learn-skill-from-conversations/SKILL.md +2 -2
- package/codex/learn-skill-from-conversations/tests/test_extract_recent_conversations.py +3 -2
- package/docs-to-voice/README.md +3 -3
- package/docs-to-voice/SKILL.md +4 -4
- package/docs-to-voice/scripts/__pycache__/docs_to_voice.cpython-312.pyc +0 -0
- package/docs-to-voice/tests/test_docs_to_voice_shell_wrapper.py +51 -0
- package/feature-propose/SKILL.md +1 -0
- package/generate-spec/README.md +3 -6
- package/generate-spec/SKILL.md +2 -3
- package/generate-spec/scripts/__pycache__/create-specscpython-312.pyc +0 -0
- package/generate-spec/tests/test_create_specs.py +166 -0
- package/improve-observability/SKILL.md +12 -1
- package/katex/SKILL.md +3 -3
- package/katex/scripts/__pycache__/render_katex.cpython-312.pyc +0 -0
- package/katex/tests/test_render_katex.py +174 -0
- package/learning-error-book/SKILL.md +2 -2
- package/learning-error-book/tests/test_render_error_book_json_to_pdf.py +134 -0
- package/lib/cli.js +66 -0
- package/lib/tool-runner.js +214 -0
- package/maintain-project-constraints/SKILL.md +3 -3
- package/maintain-skill-catalog/SKILL.md +2 -2
- package/novel-to-short-video/SKILL.md +2 -2
- package/open-github-issue/README.md +31 -22
- package/open-github-issue/SKILL.md +54 -40
- package/open-github-issue/scripts/__pycache__/open_github_issue.cpython-312.pyc +0 -0
- package/open-github-issue/scripts/open_github_issue.py +130 -3
- package/open-github-issue/tests/test_open_github_issue.py +95 -0
- package/openai-text-to-image-storyboard/README.md +1 -1
- package/openai-text-to-image-storyboard/SKILL.md +1 -1
- package/openai-text-to-image-storyboard/tests/test_generate_storyboard_images.py +177 -0
- package/package.json +1 -1
- package/read-github-issue/SKILL.md +9 -9
- 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/resolve-review-comments/SKILL.md +8 -8
- package/resolve-review-comments/scripts/__pycache__/review_threads.cpython-312.pyc +0 -0
- package/review-codebases/README.md +2 -0
- package/review-codebases/SKILL.md +1 -0
- package/scheduled-runtime-health-check/SKILL.md +4 -1
- package/systematic-debug/SKILL.md +3 -2
- package/text-to-short-video/README.md +1 -1
- package/text-to-short-video/SKILL.md +1 -1
- package/text-to-short-video/scripts/__pycache__/enforce_video_aspect_ratio.cpython-312.pyc +0 -0
- package/text-to-short-video/tests/test_enforce_video_aspect_ratio.py +194 -0
- package/weekly-financial-event-report/SKILL.md +2 -2
- package/weekly-financial-event-report/tests/test_extract_pdf_text_pdfkit.py +64 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib.util
|
|
6
|
+
import io
|
|
7
|
+
import json
|
|
8
|
+
import random
|
|
9
|
+
import string
|
|
10
|
+
import tempfile
|
|
11
|
+
import types
|
|
12
|
+
import unittest
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from unittest.mock import patch
|
|
15
|
+
|
|
16
|
+
PIL_AVAILABLE = importlib.util.find_spec("PIL") is not None
|
|
17
|
+
|
|
18
|
+
if PIL_AVAILABLE:
|
|
19
|
+
from PIL import Image
|
|
20
|
+
SCRIPT_PATH = Path(__file__).resolve().parents[1] / "scripts" / "generate_storyboard_images.py"
|
|
21
|
+
SPEC = importlib.util.spec_from_file_location("generate_storyboard_images", SCRIPT_PATH)
|
|
22
|
+
MODULE = importlib.util.module_from_spec(SPEC)
|
|
23
|
+
SPEC.loader.exec_module(MODULE)
|
|
24
|
+
else:
|
|
25
|
+
Image = None
|
|
26
|
+
MODULE = None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def png_bytes(width: int, height: int, color: tuple[int, int, int] = (64, 128, 255)) -> bytes:
|
|
30
|
+
buffer = io.BytesIO()
|
|
31
|
+
Image.new("RGB", (width, height), color).save(buffer, format="PNG")
|
|
32
|
+
return buffer.getvalue()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@unittest.skipUnless(PIL_AVAILABLE, "Pillow is required for storyboard image tests")
|
|
36
|
+
class GenerateStoryboardImagesTests(unittest.TestCase):
|
|
37
|
+
def test_sanitize_component_property_removes_invalid_path_characters(self) -> None:
|
|
38
|
+
generator = random.Random(20260418)
|
|
39
|
+
alphabet = string.ascii_letters + string.digits + string.punctuation + " \t中文"
|
|
40
|
+
|
|
41
|
+
for _ in range(200):
|
|
42
|
+
raw = "".join(generator.choice(alphabet) for _ in range(generator.randint(0, 30)))
|
|
43
|
+
sanitized = MODULE.sanitize_component(raw, "fallback")
|
|
44
|
+
with self.subTest(raw=raw, sanitized=sanitized):
|
|
45
|
+
self.assertTrue(sanitized)
|
|
46
|
+
self.assertNotRegex(sanitized, MODULE.INVALID_PATH_CHARS)
|
|
47
|
+
self.assertNotIn(" ", sanitized)
|
|
48
|
+
|
|
49
|
+
def test_parse_image_dimensions_supports_png_and_jpeg(self) -> None:
|
|
50
|
+
png = png_bytes(320, 180)
|
|
51
|
+
self.assertEqual(MODULE.parse_image_dimensions(png), (320, 180))
|
|
52
|
+
|
|
53
|
+
buffer = io.BytesIO()
|
|
54
|
+
Image.new("RGB", (160, 90), (0, 0, 0)).save(buffer, format="JPEG")
|
|
55
|
+
self.assertEqual(MODULE.parse_image_dimensions(buffer.getvalue()), (160, 90))
|
|
56
|
+
|
|
57
|
+
def test_parse_prompts_file_supports_structured_scene_mode(self) -> None:
|
|
58
|
+
payload = {
|
|
59
|
+
"characters": [
|
|
60
|
+
{
|
|
61
|
+
"id": "hero",
|
|
62
|
+
"name": "Kai",
|
|
63
|
+
"appearance": "short black hair",
|
|
64
|
+
"outfit": "blue jacket",
|
|
65
|
+
"description": "determined teenage inventor",
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
"scenes": [
|
|
69
|
+
{
|
|
70
|
+
"title": "Workshop",
|
|
71
|
+
"description": "Kai repairs a flickering lantern inside a cramped workshop.",
|
|
72
|
+
"character_ids": ["hero"],
|
|
73
|
+
"character_descriptions": {"hero": "focused and covered with grease"},
|
|
74
|
+
"camera": "medium shot",
|
|
75
|
+
}
|
|
76
|
+
],
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
80
|
+
prompts_file = Path(temp_dir) / "prompts.json"
|
|
81
|
+
prompts_file.write_text(json.dumps(payload, ensure_ascii=False), encoding="utf-8")
|
|
82
|
+
items = MODULE.parse_prompts_file(prompts_file)
|
|
83
|
+
|
|
84
|
+
self.assertEqual(len(items), 1)
|
|
85
|
+
self.assertEqual(items[0]["title"], "Workshop")
|
|
86
|
+
prompt_payload = json.loads(items[0]["prompt"])
|
|
87
|
+
self.assertEqual(prompt_payload["characters"][0]["description"], "focused and covered with grease")
|
|
88
|
+
self.assertEqual(prompt_payload["camera"], "medium shot")
|
|
89
|
+
|
|
90
|
+
def test_build_scene_json_prompt_rejects_unknown_override_character(self) -> None:
|
|
91
|
+
scene = {
|
|
92
|
+
"title": "Workshop",
|
|
93
|
+
"description": "Lantern repair",
|
|
94
|
+
"character_ids": ["hero"],
|
|
95
|
+
"character_descriptions": {"ghost": "not listed"},
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
with self.assertRaises(SystemExit) as context:
|
|
99
|
+
MODULE.build_scene_json_prompt(
|
|
100
|
+
scene=scene,
|
|
101
|
+
scene_index=1,
|
|
102
|
+
prompts_file=Path("prompts.json"),
|
|
103
|
+
character_profiles={
|
|
104
|
+
"hero": {
|
|
105
|
+
"id": "hero",
|
|
106
|
+
"name": "Kai",
|
|
107
|
+
"appearance": "short black hair",
|
|
108
|
+
"outfit": "blue jacket",
|
|
109
|
+
"description": "determined teenage inventor",
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
self.assertIn("ids not listed in character_ids", str(context.exception))
|
|
115
|
+
|
|
116
|
+
def test_generate_image_supports_b64_payload(self) -> None:
|
|
117
|
+
raw = png_bytes(100, 60)
|
|
118
|
+
|
|
119
|
+
with patch.object(
|
|
120
|
+
MODULE,
|
|
121
|
+
"post_json",
|
|
122
|
+
return_value={"data": [{"b64_json": MODULE.base64.b64encode(raw).decode("ascii"), "revised_prompt": "ok"}]},
|
|
123
|
+
):
|
|
124
|
+
image_bytes, revised_prompt = MODULE.generate_image(
|
|
125
|
+
base_url="https://example.com",
|
|
126
|
+
api_key="token",
|
|
127
|
+
image_model="gpt-image-1",
|
|
128
|
+
prompt="scene",
|
|
129
|
+
aspect_ratio=None,
|
|
130
|
+
image_size=None,
|
|
131
|
+
quality=None,
|
|
132
|
+
style=None,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
self.assertEqual(image_bytes, raw)
|
|
136
|
+
self.assertEqual(revised_prompt, "ok")
|
|
137
|
+
|
|
138
|
+
def test_main_generates_cropped_images_and_summary(self) -> None:
|
|
139
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
140
|
+
project_dir = Path(temp_dir)
|
|
141
|
+
args = types.SimpleNamespace(
|
|
142
|
+
content_name="Lantern / Story",
|
|
143
|
+
project_dir=str(project_dir),
|
|
144
|
+
env_file=str(project_dir / ".env"),
|
|
145
|
+
api_url="https://example.com",
|
|
146
|
+
api_key="token",
|
|
147
|
+
prompts_file=None,
|
|
148
|
+
prompt=["Lantern workshop at dusk"],
|
|
149
|
+
image_model="gpt-image-1",
|
|
150
|
+
aspect_ratio="1:1",
|
|
151
|
+
image_size=None,
|
|
152
|
+
quality=None,
|
|
153
|
+
style=None,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
with patch.object(MODULE, "parse_args", return_value=args), patch.object(
|
|
157
|
+
MODULE, "load_dotenv_file", return_value=False
|
|
158
|
+
), patch.object(
|
|
159
|
+
MODULE,
|
|
160
|
+
"generate_image",
|
|
161
|
+
return_value=(png_bytes(200, 100), "Lantern workshop at dusk, improved"),
|
|
162
|
+
):
|
|
163
|
+
exit_code = MODULE.main()
|
|
164
|
+
|
|
165
|
+
self.assertEqual(exit_code, 0)
|
|
166
|
+
output_dir = project_dir / "pictures" / "Lantern_Story"
|
|
167
|
+
summary = json.loads((output_dir / "storyboard.json").read_text(encoding="utf-8"))
|
|
168
|
+
self.assertEqual(summary["aspect_ratio"], "1:1")
|
|
169
|
+
self.assertEqual(summary["images"][0]["width"], 100)
|
|
170
|
+
self.assertEqual(summary["images"][0]["height"], 100)
|
|
171
|
+
self.assertEqual(summary["images"][0]["source_width"], 200)
|
|
172
|
+
self.assertEqual(summary["images"][0]["source_height"], 100)
|
|
173
|
+
self.assertEqual(summary["images"][0]["revised_prompt"], "Lantern workshop at dusk, improved")
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
if __name__ == "__main__":
|
|
177
|
+
unittest.main()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: read-github-issue
|
|
3
|
-
description: Read and search remote GitHub issues via GitHub CLI (`gh`). Use when users ask to list issues, filter issue candidates, inspect a specific issue with comments, or gather issue context before planning follow-up work. Prefer the bundled
|
|
3
|
+
description: Read and search remote GitHub issues via GitHub CLI (`gh`). Use when users ask to list issues, filter issue candidates, inspect a specific issue with comments, or gather issue context before planning follow-up work. Prefer the bundled CLI tools when they are present and working, but fall back to direct `gh issue list` / `gh issue view` commands when the tools are missing or fail for repository-specific reasons.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Read GitHub Issue
|
|
@@ -10,12 +10,12 @@ description: Read and search remote GitHub issues via GitHub CLI (`gh`). Use whe
|
|
|
10
10
|
- Required: none.
|
|
11
11
|
- Conditional: none.
|
|
12
12
|
- Optional: none.
|
|
13
|
-
- Fallback: If the bundled
|
|
13
|
+
- Fallback: If the bundled CLI tools are missing or fail but `gh` is available, continue with raw `gh issue list` / `gh issue view`; only stop when `gh` itself is unavailable or unauthenticated.
|
|
14
14
|
|
|
15
15
|
## Standards
|
|
16
16
|
|
|
17
17
|
- Evidence: Verify repository context first, then read remote issue data directly from `gh issue list` / `gh issue view` instead of paraphrasing from memory.
|
|
18
|
-
- Execution: Confirm the target repo, prefer the bundled
|
|
18
|
+
- Execution: Confirm the target repo, prefer the bundled CLI tools for deterministic output, then fall back to raw `gh` commands whenever the tools are unavailable or broken in the target repository.
|
|
19
19
|
- Quality: Keep the skill focused on issue discovery and retrieval only; do not embed any hardcoded fixing, branching, PR, or push workflow.
|
|
20
20
|
- Output: Return candidate issues, selected issue details, comments summary, and any missing information needed before follow-up work.
|
|
21
21
|
|
|
@@ -35,12 +35,12 @@ Use this skill to gather trustworthy GitHub issue context with `gh`: discover op
|
|
|
35
35
|
- Run `gh repo view --json nameWithOwner,isPrivate,defaultBranchRef` to confirm target repo.
|
|
36
36
|
- If the user specifies another repository, always use `--repo <owner>/<repo>` in issue commands.
|
|
37
37
|
|
|
38
|
-
### 2) Find candidate issues with the bundled
|
|
38
|
+
### 2) Find candidate issues with the bundled CLI
|
|
39
39
|
|
|
40
40
|
- Preferred command:
|
|
41
41
|
|
|
42
42
|
```bash
|
|
43
|
-
|
|
43
|
+
apltk find-github-issues --limit 50 --state open
|
|
44
44
|
```
|
|
45
45
|
|
|
46
46
|
- Optional filters:
|
|
@@ -61,7 +61,7 @@ gh issue list --limit 50 --state open
|
|
|
61
61
|
- Preferred command:
|
|
62
62
|
|
|
63
63
|
```bash
|
|
64
|
-
|
|
64
|
+
apltk read-github-issue 123 --comments
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
- Optional flags:
|
|
@@ -82,16 +82,16 @@ gh issue view 123 --comments
|
|
|
82
82
|
- Identify missing acceptance criteria, repro details, affected components, or environment context.
|
|
83
83
|
- If issue text and comments are insufficient, state exactly what is missing instead of inventing a fix plan.
|
|
84
84
|
|
|
85
|
-
## Included
|
|
85
|
+
## Included CLI
|
|
86
86
|
|
|
87
|
-
### `
|
|
87
|
+
### `apltk find-github-issues`
|
|
88
88
|
|
|
89
89
|
- Purpose: consistent remote issue listing via `gh issue list`.
|
|
90
90
|
- Outputs a readable table by default, or JSON with `--output json`.
|
|
91
91
|
- Uses only GitHub CLI so it reflects remote GitHub state.
|
|
92
92
|
- Treat it as a convenience wrapper, not a hard dependency.
|
|
93
93
|
|
|
94
|
-
### `
|
|
94
|
+
### `apltk read-github-issue`
|
|
95
95
|
|
|
96
96
|
- Purpose: deterministic issue detail retrieval via `gh issue view`.
|
|
97
97
|
- Outputs either a human-readable summary or full JSON for downstream automation.
|
|
Binary file
|
|
@@ -46,8 +46,8 @@ Use this skill to run an end-to-end GitHub PR review loop: collect review thread
|
|
|
46
46
|
- If user does not provide PR number, infer from current branch context.
|
|
47
47
|
|
|
48
48
|
```bash
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
apltk review-threads list --repo <owner>/<repo> --pr <number>
|
|
50
|
+
apltk review-threads list
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
## 2) Read unresolved review threads
|
|
@@ -55,8 +55,8 @@ python3 scripts/review_threads.py list
|
|
|
55
55
|
Use table view for quick scan, then JSON when you need full details.
|
|
56
56
|
|
|
57
57
|
```bash
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
apltk review-threads list --pr <number> --state unresolved --output table
|
|
59
|
+
apltk review-threads list --pr <number> --state unresolved --output json > /tmp/pr_threads.json
|
|
60
60
|
```
|
|
61
61
|
|
|
62
62
|
The JSON output contains `thread_id`, `path`, `line`, and comment bodies for decision and resolution.
|
|
@@ -98,13 +98,13 @@ Track adopted thread IDs in a JSON file:
|
|
|
98
98
|
Resolve only threads you actually addressed in code.
|
|
99
99
|
|
|
100
100
|
```bash
|
|
101
|
-
|
|
101
|
+
apltk review-threads resolve --pr <number> --thread-id-file adopted_threads.json
|
|
102
102
|
```
|
|
103
103
|
|
|
104
104
|
Optional preview without mutating GitHub state:
|
|
105
105
|
|
|
106
106
|
```bash
|
|
107
|
-
|
|
107
|
+
apltk review-threads resolve --pr <number> --thread-id-file adopted_threads.json --dry-run
|
|
108
108
|
```
|
|
109
109
|
|
|
110
110
|
## 8) Handle non-adopted comments
|
|
@@ -113,9 +113,9 @@ python3 scripts/review_threads.py resolve --pr <number> --thread-id-file adopted
|
|
|
113
113
|
- Reply with a concise technical reason and, if needed, a proposed follow-up.
|
|
114
114
|
- Never resolve rejected or unhandled feedback threads.
|
|
115
115
|
|
|
116
|
-
##
|
|
116
|
+
## CLI
|
|
117
117
|
|
|
118
|
-
### `
|
|
118
|
+
### `apltk review-threads`
|
|
119
119
|
|
|
120
120
|
- `list`: fetch PR review threads via GitHub GraphQL (`gh api graphql`), supports repo/PR inference.
|
|
121
121
|
- `resolve`: resolve selected review threads by thread IDs.
|
|
Binary file
|
|
@@ -50,6 +50,8 @@ For each confirmed finding, delegate publication to `$open-github-issue` with:
|
|
|
50
50
|
- file references and causal reasoning in `suspected-cause`
|
|
51
51
|
- reproduction conditions when known
|
|
52
52
|
|
|
53
|
+
When invoking the publisher CLI directly, use `apltk open-github-issue --payload-file <json>` or `@file` inputs for Markdown-rich fields so shell parsing cannot consume backticks or code snippets.
|
|
54
|
+
|
|
53
55
|
If issue publication is unavailable, return draft issue content instead of switching to an ad-hoc publishing path.
|
|
54
56
|
|
|
55
57
|
## Output expectations
|
|
@@ -74,6 +74,7 @@ Only continue to the next level when the current level has no confirmed findings
|
|
|
74
74
|
- `suspected-cause`: file references, causal chain, and confidence
|
|
75
75
|
- `reproduction`: concrete trigger or conditions when known; otherwise leave empty
|
|
76
76
|
- `repo`: target repository in `owner/repo` format when known
|
|
77
|
+
- If invoking the publisher CLI directly, pass finding details through `apltk open-github-issue --payload-file <json>` or `@file` inputs rather than inline shell arguments so code snippets and backticks survive unchanged.
|
|
77
78
|
|
|
78
79
|
## Evidence standard
|
|
79
80
|
|
|
@@ -15,7 +15,7 @@ description: Use a background terminal to run a user-specified command immediate
|
|
|
15
15
|
## Standards
|
|
16
16
|
|
|
17
17
|
- Evidence: Anchor every conclusion to the requested command, execution window, startup/shutdown timestamps, one canonical run folder or artifact root, captured logs, and concrete runtime signals.
|
|
18
|
-
- Execution: Collect the run contract, verify the real stop mechanism before launch, use a background terminal, optionally update the code only when the user asks, execute the requested command immediately or in the requested window, record the canonical run folder once the process materializes it, capture logs, stop cleanly when bounded, then delegate log review to `analyse-app-logs` only when findings are requested or needed.
|
|
18
|
+
- Execution: Collect the run contract, verify the real stop mechanism before launch, choose the highest-fidelity execution mode that matches the user's intent, use a background terminal, optionally update the code only when the user asks, execute the requested command immediately or in the requested window, record the canonical run folder once the process materializes it, capture logs, stop cleanly when bounded, then delegate log review to `analyse-app-logs` only when findings are requested or needed.
|
|
19
19
|
- Quality: Keep scheduling, execution, and shutdown deterministic; separate confirmed findings from hypotheses; and mark each assessed module healthy/degraded/failed/unknown with reasons.
|
|
20
20
|
- Output: Return the run configuration, execution status, log locations, optional code-update result, optional module health by area, confirmed issues, potential issues, observability gaps, and scheduler status when applicable.
|
|
21
21
|
|
|
@@ -46,6 +46,7 @@ This skill is an orchestration layer. It owns the background terminal session, o
|
|
|
46
46
|
- Prefer one bounded observation window over open-ended monitoring.
|
|
47
47
|
- Use one dedicated background terminal session per requested run so execution and logs stay correlated.
|
|
48
48
|
- Record the canonical run directory, artifact root, or other generated output location as soon as it exists, and use that as the source of truth for later analysis.
|
|
49
|
+
- When a repository exposes both synthetic harnesses and production-like runtime entrypoints, prefer the production-like path for claims about real runtime, market, or operator behavior; use the lower-fidelity harness only when the user explicitly asked for it or when it is the only safe reproduction surface.
|
|
49
50
|
- Treat code update as optional and only perform it when the user explicitly requests it.
|
|
50
51
|
- Treat startup, steady-state, and shutdown as part of the same investigation.
|
|
51
52
|
- Do not call a module healthy unless there is at least one positive signal for it.
|
|
@@ -58,6 +59,7 @@ This skill is an orchestration layer. It owns the background terminal session, o
|
|
|
58
59
|
1. Define the run contract
|
|
59
60
|
- Confirm or derive the workspace, execution command, optional code-update step, optional schedule, optional duration, readiness signal, log locations, and whether post-run findings are required.
|
|
60
61
|
- Derive commands from trustworthy sources first: `package.json`, `Makefile`, `docker-compose.yml`, `Procfile`, scripts, or project docs.
|
|
62
|
+
- If multiple commands exist for the same workflow, rank them by fidelity and state explicitly which mode you are choosing: production-like runtime, bounded integration harness, or synthetic scenario replay.
|
|
61
63
|
- If no trustworthy execution command or stop method can be found, stop and ask only for the missing command rather than guessing.
|
|
62
64
|
2. Prepare the background terminal run
|
|
63
65
|
- Use a dedicated background terminal session for the whole workflow.
|
|
@@ -77,6 +79,7 @@ This skill is an orchestration layer. It owns the background terminal session, o
|
|
|
77
79
|
5. Run and capture readiness
|
|
78
80
|
- Execute the requested command in the same background terminal.
|
|
79
81
|
- As soon as the command emits or creates its canonical run directory, artifact root, or equivalent output location, record that path and reuse it for every later check.
|
|
82
|
+
- Report the exact runtime mode used in the evidence record so later analysis does not accidentally treat synthetic-harness results as proof about production behavior.
|
|
80
83
|
- Wait for a concrete readiness signal when the command is expected to stay up, such as a health endpoint, listening-port log, worker boot line, or queue-consumer ready message.
|
|
81
84
|
- If readiness never arrives, stop the run, preserve logs, and treat it as a failed startup window.
|
|
82
85
|
6. Observe and stop when bounded
|
|
@@ -15,7 +15,7 @@ description: "Systematic debugging workflow for program issues: understand obser
|
|
|
15
15
|
## Standards
|
|
16
16
|
|
|
17
17
|
- Evidence: Gather expected versus observed behavior from code and runtime facts before deciding on a cause, and when the issue involves a runtime pipeline or bounded run, anchor the investigation to one canonical artifact root or run directory instead of mixed terminal snippets from multiple runs.
|
|
18
|
-
- Execution: Inspect the relevant paths, reproduce every plausible cause with tests or bounded reruns, map each observed failure to a concrete pipeline stage, distinguish toolchain/platform faults from application-logic faults, classify failing tests as stale test contract vs test-harness interference vs real product bug, then apply the minimal fix at the true owner.
|
|
18
|
+
- Execution: Inspect the relevant paths, reproduce every plausible cause with tests or bounded reruns, choose a reproduction mode whose fidelity matches the user's claim, map each observed failure to a concrete pipeline stage, distinguish toolchain/platform faults from application-logic faults, classify failing tests as stale test contract vs test-harness interference vs real product bug, then apply the minimal fix at the true owner.
|
|
19
19
|
- Quality: Keep scope focused on the bug, prefer existing test patterns, explicitly rule out hypotheses that could not be reproduced, and when failures disappear in isolated reruns treat shared-state or parallel-test interference as a first-class hypothesis instead of silently dismissing the original failure.
|
|
20
20
|
- Output: Deliver the plausible-cause list, the canonical evidence source, reproduction tests or reruns, the final failure classification for each investigated symptom, validated fix summary, and passing-test confirmation.
|
|
21
21
|
|
|
@@ -25,6 +25,7 @@ description: "Systematic debugging workflow for program issues: understand obser
|
|
|
25
25
|
- Cover all plausible causes with reproducible tests instead of guessing a single cause.
|
|
26
26
|
- Keep fixes minimal, focused, and validated by passing tests.
|
|
27
27
|
- When logs or runtime artifacts exist, treat one run as canonical and compare every conclusion against that same run's generated artifacts, not against ad hoc console recollection.
|
|
28
|
+
- When a repository has both scenario or harness runs and a production-like runtime, do not treat the lower-fidelity mode as proof about the higher-fidelity mode unless you explicitly state that limitation and the user agrees.
|
|
28
29
|
- When the failing flow crosses multiple layers, identify the last confirmed successful stage before assigning blame.
|
|
29
30
|
- When tests fail, separate stale assertions and fixture drift from real implementation regressions before changing product code.
|
|
30
31
|
- If failures only appear under parallel execution or shared shell-out paths, investigate test isolation, shared locks, temp directories, run-name collisions, and environment leakage before blaming the product.
|
|
@@ -45,7 +46,7 @@ Also auto-invoke this skill when mismatch evidence appears during normal executi
|
|
|
45
46
|
|
|
46
47
|
1. **Understand and inspect**: Parse expected vs observed behavior, explore relevant code paths, record the canonical failing run or artifact root when runtime output is involved, and build a list of plausible root causes.
|
|
47
48
|
2. **Map the failure boundary**: Break the flow into concrete stages such as setup, startup, readiness, steady-state execution, persistence, and shutdown, then identify the last stage that is confirmed to have succeeded.
|
|
48
|
-
3. **Reproduce with tests or bounded reruns**: Write or extend tests that reproduce every plausible cause, and when the bug depends on runtime orchestration rerun the same bounded command or
|
|
49
|
+
3. **Reproduce with tests or bounded reruns**: Write or extend tests that reproduce every plausible cause, and when the bug depends on runtime orchestration rerun the same bounded command or the same runtime mode instead of switching contexts mid-investigation. If the user is asking about real runtime or market behavior, prefer the production-like bounded run over a synthetic scenario replay unless safety or tooling constraints make that impossible. When a failing test passes in isolation, rerun it under the original suite shape to determine whether the real cause is stale expectations, fixture drift, or shared-state interference.
|
|
49
50
|
4. **Diagnose and confirm**: Use reproduction evidence to confirm the true root cause, explicitly rule out non-causes, and classify whether each investigated failure belongs to the toolchain/platform layer, test contract drift, test-harness interference, orchestration, or application logic.
|
|
50
51
|
5. **Fix and validate**: Implement focused fixes and iterate until all reproduction tests or bounded reruns pass.
|
|
51
52
|
|
|
@@ -56,7 +56,7 @@ cp ~/.codex/skills/text-to-short-video/.env.example \
|
|
|
56
56
|
If API-generated video ratio/size does not match the target, run:
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
|
-
|
|
59
|
+
apltk enforce-video-aspect-ratio \
|
|
60
60
|
--input-video "<downloaded_video_path>" \
|
|
61
61
|
--output-video "<final_output_video_path>" \
|
|
62
62
|
--env-file ~/.codex/skills/text-to-short-video/.env \
|
|
@@ -173,7 +173,7 @@ If provider returns multiple outputs, keep the best one that matches requested s
|
|
|
173
173
|
When output ratio or resolution differs from target, run:
|
|
174
174
|
|
|
175
175
|
```bash
|
|
176
|
-
|
|
176
|
+
apltk enforce-video-aspect-ratio \
|
|
177
177
|
--input-video "<downloaded_video_path>" \
|
|
178
178
|
--output-video "<final_output_video_path>" \
|
|
179
179
|
--env-file ~/.codex/skills/text-to-short-video/.env \
|
|
Binary file
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib.util
|
|
6
|
+
import io
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import random
|
|
10
|
+
import re
|
|
11
|
+
import tempfile
|
|
12
|
+
import types
|
|
13
|
+
import unittest
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from unittest.mock import patch
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
SCRIPT_PATH = Path(__file__).resolve().parents[1] / "scripts" / "enforce_video_aspect_ratio.py"
|
|
19
|
+
SPEC = importlib.util.spec_from_file_location("enforce_video_aspect_ratio", SCRIPT_PATH)
|
|
20
|
+
MODULE = importlib.util.module_from_spec(SPEC)
|
|
21
|
+
SPEC.loader.exec_module(MODULE)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def parse_crop(expression: str) -> tuple[int, int, int, int] | None:
|
|
25
|
+
match = re.search(r"crop=(\d+):(\d+):(\d+):(\d+)", expression)
|
|
26
|
+
if not match:
|
|
27
|
+
return None
|
|
28
|
+
return tuple(int(value) for value in match.groups())
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class EnforceVideoAspectRatioTests(unittest.TestCase):
|
|
32
|
+
def test_parse_size_property_round_trips_positive_dimensions(self) -> None:
|
|
33
|
+
generator = random.Random(20260418)
|
|
34
|
+
for _ in range(150):
|
|
35
|
+
width = generator.randint(1, 9999)
|
|
36
|
+
height = generator.randint(1, 9999)
|
|
37
|
+
raw = f"{width}x{height}"
|
|
38
|
+
with self.subTest(raw=raw):
|
|
39
|
+
self.assertEqual(MODULE.parse_size(raw), (width, height))
|
|
40
|
+
|
|
41
|
+
def test_load_dotenv_file_parses_quotes_and_respects_override(self) -> None:
|
|
42
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
43
|
+
env_file = Path(temp_dir) / ".env"
|
|
44
|
+
env_file.write_text(
|
|
45
|
+
'TEXT_TO_SHORT_VIDEO_WIDTH="720"\n'
|
|
46
|
+
"TEXT_TO_SHORT_VIDEO_HEIGHT=1280 # comment\n",
|
|
47
|
+
encoding="utf-8",
|
|
48
|
+
)
|
|
49
|
+
original = os.environ.get("TEXT_TO_SHORT_VIDEO_WIDTH")
|
|
50
|
+
os.environ["TEXT_TO_SHORT_VIDEO_WIDTH"] = "999"
|
|
51
|
+
try:
|
|
52
|
+
loaded = MODULE.load_dotenv_file(env_file, override=False)
|
|
53
|
+
self.assertTrue(loaded)
|
|
54
|
+
self.assertEqual(os.environ["TEXT_TO_SHORT_VIDEO_WIDTH"], "999")
|
|
55
|
+
self.assertEqual(os.environ["TEXT_TO_SHORT_VIDEO_HEIGHT"], "1280")
|
|
56
|
+
MODULE.load_dotenv_file(env_file, override=True)
|
|
57
|
+
self.assertEqual(os.environ["TEXT_TO_SHORT_VIDEO_WIDTH"], "720")
|
|
58
|
+
finally:
|
|
59
|
+
if original is None:
|
|
60
|
+
os.environ.pop("TEXT_TO_SHORT_VIDEO_WIDTH", None)
|
|
61
|
+
else:
|
|
62
|
+
os.environ["TEXT_TO_SHORT_VIDEO_WIDTH"] = original
|
|
63
|
+
os.environ.pop("TEXT_TO_SHORT_VIDEO_HEIGHT", None)
|
|
64
|
+
|
|
65
|
+
def test_build_video_filter_property_keeps_crop_within_bounds(self) -> None:
|
|
66
|
+
generator = random.Random(20260418)
|
|
67
|
+
for _ in range(200):
|
|
68
|
+
input_width = generator.randint(32, 4096)
|
|
69
|
+
input_height = generator.randint(32, 4096)
|
|
70
|
+
target_width = generator.randint(32, 2160)
|
|
71
|
+
target_height = generator.randint(32, 3840)
|
|
72
|
+
|
|
73
|
+
expression, crop_applied = MODULE.build_video_filter(
|
|
74
|
+
input_width=input_width,
|
|
75
|
+
input_height=input_height,
|
|
76
|
+
target_width=target_width,
|
|
77
|
+
target_height=target_height,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
with self.subTest(
|
|
81
|
+
input_width=input_width,
|
|
82
|
+
input_height=input_height,
|
|
83
|
+
target_width=target_width,
|
|
84
|
+
target_height=target_height,
|
|
85
|
+
expression=expression,
|
|
86
|
+
):
|
|
87
|
+
if expression is None:
|
|
88
|
+
self.assertFalse(crop_applied)
|
|
89
|
+
self.assertEqual((input_width, input_height), (target_width, target_height))
|
|
90
|
+
continue
|
|
91
|
+
|
|
92
|
+
self.assertIn(f"scale={target_width}:{target_height}", expression)
|
|
93
|
+
crop = parse_crop(expression)
|
|
94
|
+
if crop is None:
|
|
95
|
+
self.assertFalse(crop_applied)
|
|
96
|
+
self.assertEqual(input_width * target_height, input_height * target_width)
|
|
97
|
+
continue
|
|
98
|
+
|
|
99
|
+
crop_width, crop_height, offset_x, offset_y = crop
|
|
100
|
+
self.assertTrue(crop_applied)
|
|
101
|
+
self.assertEqual(crop_width % 2, 0)
|
|
102
|
+
self.assertEqual(crop_height % 2, 0)
|
|
103
|
+
self.assertGreaterEqual(crop_width, 2)
|
|
104
|
+
self.assertGreaterEqual(crop_height, 2)
|
|
105
|
+
self.assertLessEqual(crop_width, input_width)
|
|
106
|
+
self.assertLessEqual(crop_height, input_height)
|
|
107
|
+
self.assertGreaterEqual(offset_x, 0)
|
|
108
|
+
self.assertGreaterEqual(offset_y, 0)
|
|
109
|
+
self.assertLessEqual(offset_x + crop_width, input_width)
|
|
110
|
+
self.assertLessEqual(offset_y + crop_height, input_height)
|
|
111
|
+
|
|
112
|
+
def test_resolve_output_path_enforces_in_place_rules(self) -> None:
|
|
113
|
+
input_video = Path("/tmp/input.mp4")
|
|
114
|
+
args = types.SimpleNamespace(in_place=False, output_video=None)
|
|
115
|
+
self.assertEqual(MODULE.resolve_output_path(args, input_video), Path("/tmp/input_aspect_fixed.mp4"))
|
|
116
|
+
|
|
117
|
+
args = types.SimpleNamespace(in_place=True, output_video=None)
|
|
118
|
+
self.assertEqual(MODULE.resolve_output_path(args, input_video), input_video)
|
|
119
|
+
|
|
120
|
+
args = types.SimpleNamespace(in_place=True, output_video="/tmp/out.mp4")
|
|
121
|
+
with self.assertRaises(SystemExit):
|
|
122
|
+
MODULE.resolve_output_path(args, input_video)
|
|
123
|
+
|
|
124
|
+
def test_main_copies_file_when_video_already_matches_target(self) -> None:
|
|
125
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
126
|
+
input_video = Path(temp_dir) / "input.mp4"
|
|
127
|
+
input_video.write_bytes(b"video")
|
|
128
|
+
output_video = Path(temp_dir) / "copy.mp4"
|
|
129
|
+
args = types.SimpleNamespace(
|
|
130
|
+
input_video=str(input_video),
|
|
131
|
+
output_video=str(output_video),
|
|
132
|
+
in_place=False,
|
|
133
|
+
target_size="640x360",
|
|
134
|
+
target_width=None,
|
|
135
|
+
target_height=None,
|
|
136
|
+
env_file=str(Path(temp_dir) / ".env"),
|
|
137
|
+
force=False,
|
|
138
|
+
ffmpeg_bin="ffmpeg",
|
|
139
|
+
ffprobe_bin="ffprobe",
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
with patch.object(MODULE, "parse_args", return_value=args), patch.object(
|
|
143
|
+
MODULE, "load_dotenv_file", return_value=False
|
|
144
|
+
), patch.object(MODULE, "required_command"), patch.object(
|
|
145
|
+
MODULE, "probe_video_size", return_value=(640, 360)
|
|
146
|
+
), patch("sys.stdout", new_callable=io.StringIO) as stdout:
|
|
147
|
+
exit_code = MODULE.main()
|
|
148
|
+
|
|
149
|
+
self.assertEqual(exit_code, 0)
|
|
150
|
+
self.assertEqual(output_video.read_bytes(), b"video")
|
|
151
|
+
self.assertIn("already matches target size", stdout.getvalue())
|
|
152
|
+
|
|
153
|
+
def test_main_runs_ffmpeg_and_reports_output_dimensions(self) -> None:
|
|
154
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
155
|
+
input_video = Path(temp_dir) / "input.mp4"
|
|
156
|
+
output_video = Path(temp_dir) / "output.mp4"
|
|
157
|
+
input_video.write_bytes(b"video")
|
|
158
|
+
args = types.SimpleNamespace(
|
|
159
|
+
input_video=str(input_video),
|
|
160
|
+
output_video=str(output_video),
|
|
161
|
+
in_place=False,
|
|
162
|
+
target_size="1080x1920",
|
|
163
|
+
target_width=None,
|
|
164
|
+
target_height=None,
|
|
165
|
+
env_file=str(Path(temp_dir) / ".env"),
|
|
166
|
+
force=True,
|
|
167
|
+
ffmpeg_bin="ffmpeg",
|
|
168
|
+
ffprobe_bin="ffprobe",
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
def fake_run(command, check):
|
|
172
|
+
self.assertIn("-vf", command)
|
|
173
|
+
Path(command[-1]).write_bytes(b"processed")
|
|
174
|
+
return types.SimpleNamespace(returncode=0)
|
|
175
|
+
|
|
176
|
+
with patch.object(MODULE, "parse_args", return_value=args), patch.object(
|
|
177
|
+
MODULE, "load_dotenv_file", return_value=False
|
|
178
|
+
), patch.object(MODULE, "required_command"), patch.object(
|
|
179
|
+
MODULE,
|
|
180
|
+
"probe_video_size",
|
|
181
|
+
side_effect=[(1920, 1080), (1080, 1920)],
|
|
182
|
+
), patch.object(MODULE.subprocess, "run", side_effect=fake_run), patch(
|
|
183
|
+
"sys.stdout", new_callable=io.StringIO
|
|
184
|
+
) as stdout:
|
|
185
|
+
exit_code = MODULE.main()
|
|
186
|
+
|
|
187
|
+
self.assertEqual(exit_code, 0)
|
|
188
|
+
self.assertTrue(output_video.is_file())
|
|
189
|
+
self.assertIn("Center crop applied: yes", stdout.getvalue())
|
|
190
|
+
self.assertIn("Output size: 1080x1920", stdout.getvalue())
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
if __name__ == "__main__":
|
|
194
|
+
unittest.main()
|
|
@@ -10,7 +10,7 @@ description: Read a user-specified PDF that marks the week's key financial event
|
|
|
10
10
|
- Required: `pdf` to render the final report.
|
|
11
11
|
- Conditional: `document-vision-reader` when the source PDF's highlighted markers are visible in layout but not recoverable from machine-readable text alone.
|
|
12
12
|
- Optional: none.
|
|
13
|
-
- Fallback: If source-PDF extraction through `pdf` is unavailable or fails on macOS, use the bundled `
|
|
13
|
+
- Fallback: If source-PDF extraction through `pdf` is unavailable or fails on macOS, use the bundled `apltk extract-pdf-text-pdfkit` helper before giving up; only stop when neither `pdf` nor the local PDFKit fallback can recover the marked events, or when final PDF rendering itself cannot be completed.
|
|
14
14
|
|
|
15
15
|
## Standards
|
|
16
16
|
|
|
@@ -72,7 +72,7 @@ Do not guess any input that materially changes the research window or report sco
|
|
|
72
72
|
- If `pdf` extraction is unavailable or fails because the current machine lacks local PDF tooling, and the host is macOS, run:
|
|
73
73
|
|
|
74
74
|
```bash
|
|
75
|
-
|
|
75
|
+
apltk extract-pdf-text-pdfkit /absolute/path/to/source.pdf
|
|
76
76
|
```
|
|
77
77
|
|
|
78
78
|
- The bundled extractor prints page-delimited text directly from PDFKit so the agent can still build the source-event table without adding Python PDF packages ad hoc.
|