@laitszkin/apollo-toolkit 2.14.23 → 3.0.1

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.
Files changed (58) hide show
  1. package/AGENTS.md +3 -0
  2. package/CHANGELOG.md +17 -0
  3. package/README.md +9 -0
  4. package/analyse-app-logs/README.md +5 -5
  5. package/analyse-app-logs/SKILL.md +7 -5
  6. package/analyse-app-logs/scripts/__pycache__/filter_logs_by_time.cpython-312.pyc +0 -0
  7. package/analyse-app-logs/scripts/__pycache__/log_cli_utils.cpython-312.pyc +0 -0
  8. package/analyse-app-logs/scripts/__pycache__/search_logs.cpython-312.pyc +0 -0
  9. package/codex/codex-memory-manager/README.md +2 -2
  10. package/codex/codex-memory-manager/SKILL.md +5 -5
  11. package/codex/codex-memory-manager/tests/test_extract_recent_conversations.py +3 -2
  12. package/codex/learn-skill-from-conversations/README.md +1 -1
  13. package/codex/learn-skill-from-conversations/SKILL.md +2 -2
  14. package/codex/learn-skill-from-conversations/tests/test_extract_recent_conversations.py +3 -2
  15. package/docs-to-voice/README.md +3 -3
  16. package/docs-to-voice/SKILL.md +4 -4
  17. package/docs-to-voice/scripts/__pycache__/docs_to_voice.cpython-312.pyc +0 -0
  18. package/docs-to-voice/tests/test_docs_to_voice_shell_wrapper.py +51 -0
  19. package/feature-propose/SKILL.md +1 -0
  20. package/generate-spec/README.md +3 -6
  21. package/generate-spec/SKILL.md +2 -3
  22. package/generate-spec/scripts/__pycache__/create-specscpython-312.pyc +0 -0
  23. package/generate-spec/tests/test_create_specs.py +166 -0
  24. package/jupiter-development/SKILL.md +5 -0
  25. package/katex/SKILL.md +3 -3
  26. package/katex/scripts/__pycache__/render_katex.cpython-312.pyc +0 -0
  27. package/katex/tests/test_render_katex.py +174 -0
  28. package/learning-error-book/SKILL.md +2 -2
  29. package/learning-error-book/tests/test_render_error_book_json_to_pdf.py +134 -0
  30. package/lib/cli.js +66 -0
  31. package/lib/tool-runner.js +214 -0
  32. package/maintain-project-constraints/SKILL.md +3 -3
  33. package/maintain-skill-catalog/SKILL.md +2 -2
  34. package/novel-to-short-video/SKILL.md +2 -2
  35. package/open-github-issue/README.md +31 -22
  36. package/open-github-issue/SKILL.md +54 -40
  37. package/open-github-issue/scripts/__pycache__/open_github_issue.cpython-312.pyc +0 -0
  38. package/open-github-issue/scripts/open_github_issue.py +130 -3
  39. package/open-github-issue/tests/test_open_github_issue.py +95 -0
  40. package/openai-text-to-image-storyboard/README.md +1 -1
  41. package/openai-text-to-image-storyboard/SKILL.md +1 -1
  42. package/openai-text-to-image-storyboard/tests/test_generate_storyboard_images.py +177 -0
  43. package/package.json +1 -1
  44. package/read-github-issue/SKILL.md +9 -9
  45. package/read-github-issue/scripts/__pycache__/find_issues.cpython-312.pyc +0 -0
  46. package/read-github-issue/scripts/__pycache__/read_issue.cpython-312.pyc +0 -0
  47. package/resolve-review-comments/SKILL.md +8 -8
  48. package/resolve-review-comments/scripts/__pycache__/review_threads.cpython-312.pyc +0 -0
  49. package/review-codebases/README.md +2 -0
  50. package/review-codebases/SKILL.md +1 -0
  51. package/scheduled-runtime-health-check/SKILL.md +3 -0
  52. package/systematic-debug/SKILL.md +3 -0
  53. package/text-to-short-video/README.md +1 -1
  54. package/text-to-short-video/SKILL.md +1 -1
  55. package/text-to-short-video/scripts/__pycache__/enforce_video_aspect_ratio.cpython-312.pyc +0 -0
  56. package/text-to-short-video/tests/test_enforce_video_aspect_ratio.py +194 -0
  57. package/weekly-financial-event-report/SKILL.md +2 -2
  58. package/weekly-financial-event-report/tests/test_extract_pdf_text_pdfkit.py +64 -0
package/AGENTS.md CHANGED
@@ -21,6 +21,7 @@ This repository enables users to install and run a curated set of reusable agent
21
21
  - Users can research the latest completed market week and produce a PDF watchlist of tradeable instruments for the coming week.
22
22
  - Users can turn a marked weekly finance PDF into a concise evidence-based financial event report.
23
23
  - Users can install Apollo Toolkit through npm or npx and interactively choose one or more target skill directories to populate with copied skills.
24
+ - Users can run bundled helper tools through `apltk tools` and direct `apltk <tool>` commands for selected packaged skill scripts.
24
25
  - Users can design and implement new features through a spec-first workflow.
25
26
  - Users can generate shared feature planning artifacts for approval-gated workflows, including parallel multi-spec batches coordinated through one batch-level `coordination.md`.
26
27
  - Users can convert text or documents into audio files with subtitle timelines.
@@ -64,6 +65,8 @@ This repository enables users to install and run a curated set of reusable agent
64
65
  - `npm test` - 執行 Node 測試套件。
65
66
  - `node bin/apollo-toolkit.js` - 直接從倉庫啟動 Apollo Toolkit CLI。
66
67
  - `node bin/apollo-toolkit.js codex openclaw trae` - 以非互動方式將技能安裝到指定目標。
68
+ - `node bin/apollo-toolkit.js tools` - 列出 Apollo Toolkit 內建 CLI 工具。
69
+ - `node bin/apollo-toolkit.js filter-logs app.log --start 2026-03-24T10:00:00Z` - 透過內建工具包裝器執行技能腳本。
67
70
  - `python3 scripts/validate_skill_frontmatter.py` - 驗證所有頂層技能 `SKILL.md` 的 frontmatter。
68
71
  - `python3 scripts/validate_openai_agent_config.py` - 驗證所有技能 `agents/openai.yaml` 設定。
69
72
  - `./scripts/install_skills.sh codex` - 用本地安裝腳本把技能安裝到 Codex 目錄。
package/CHANGELOG.md CHANGED
@@ -7,6 +7,23 @@ All notable changes to this repository are documented in this file.
7
7
  ### Changed
8
8
  - None yet.
9
9
 
10
+ ## [v3.0.1] - 2026-04-19
11
+
12
+ ### Changed
13
+ - Strengthen `jupiter-development` so Jupiter program registries are treated as discovery and observability inputs rather than automatic signing allowlists, preserving fail-closed local transaction grammar for wallet flows.
14
+ - Strengthen `scheduled-runtime-health-check` and `systematic-debug` so bounded runtime follow-ups compare only complete like-for-like run artifacts, derive missing-business-event causes from structured funnels, and report per-stage latency instead of vague wall-clock duration.
15
+
16
+ ## [v3.0.0] - 2026-04-18
17
+
18
+ ### Changed
19
+ - Add bundled `apltk` tool dispatch so packaged skill scripts can be listed with `apltk tools` and executed directly through `apltk <tool> ...`.
20
+ - Update skill and repository docs to prefer bundled `apltk` tool commands over direct script paths for log filtering, spec generation, KaTeX rendering, audio generation, error-book rendering, GitHub issue publishing, and related helpers.
21
+ - Harden `open-github-issue` with `--payload-file` and `@file` support so Markdown-rich fields containing backticks or shell metacharacters survive CLI invocation without shell corruption.
22
+ - Skip Python tests that require optional media/PDF modules when those dependencies are unavailable so release CI stays aligned with the repository's optional tooling contract.
23
+
24
+ ### Added
25
+ - Add `lib/tool-runner.js` plus Node and Python regression tests that cover bundled tool discovery, CLI dispatch, safe wrapper behavior, and new helper entrypoints.
26
+
10
27
  ## [v2.14.23] - 2026-04-18
11
28
 
12
29
  ### Changed
package/README.md CHANGED
@@ -77,6 +77,15 @@ apollo-toolkit
77
77
 
78
78
  Global install 後,`apltk` 與 `apollo-toolkit` 都會啟動同一個 Apollo Toolkit CLI。直接執行 `apltk` 會打開互動安裝頁,並在互動模式下先檢查 npm registry 是否有新版可用;若有,CLI 會先詢問,再自動執行全域更新。
79
79
 
80
+ 除了安裝模式之外,`apltk` 也會把技能內常用腳本暴露成簡單 CLI 工具,例如:
81
+
82
+ ```bash
83
+ apltk tools
84
+ apltk filter-logs app.log --start "2026-03-24T10:00:00Z"
85
+ apltk create-specs "Membership upgrade flow" --change-name membership-upgrade-flow
86
+ apltk open-github-issue --help
87
+ ```
88
+
80
89
  ### Non-interactive install
81
90
 
82
91
  ```bash
@@ -21,8 +21,8 @@ This skill helps agents analyze logs end-to-end, correlate runtime signals with
21
21
  - `agents/openai.yaml`: Agent interface metadata and default prompt.
22
22
  - `references/investigation-checklist.md`: Investigation validation checklist.
23
23
  - `references/log-signal-patterns.md`: Log signal pattern reference.
24
- - `scripts/filter_logs_by_time.py`: Time-window log filtering helper.
25
- - `scripts/search_logs.py`: Keyword / regex search helper with optional context.
24
+ - `scripts/filter_logs_by_time.py`: Time-window log filtering helper, exposed as `apltk filter-logs`.
25
+ - `scripts/search_logs.py`: Keyword / regex search helper with optional context, exposed as `apltk search-logs`.
26
26
  - `scripts/log_cli_utils.py`: Shared timestamp parsing utilities.
27
27
  - Dependency skill: `open-github-issue` for deterministic issue publishing.
28
28
 
@@ -72,9 +72,9 @@ When the time window is not explicitly provided, the skill should first derive a
72
72
  Useful bundled commands:
73
73
 
74
74
  ```bash
75
- python3 scripts/filter_logs_by_time.py app.log --start "2026-03-24T10:00:00Z" --end "2026-03-24T10:30:00Z"
76
- python3 scripts/search_logs.py app.log --keyword timeout --keyword payment --mode all --after-context 3
77
- python3 scripts/search_logs.py app.log --regex "request_id=ab12.*ERROR" --start "2026-03-24T10:00:00Z" --end "2026-03-24T10:15:00Z"
75
+ apltk filter-logs app.log --start "2026-03-24T10:00:00Z" --end "2026-03-24T10:30:00Z"
76
+ apltk search-logs app.log --keyword timeout --keyword payment --mode all --after-context 3
77
+ apltk search-logs app.log --regex "request_id=ab12.*ERROR" --start "2026-03-24T10:00:00Z" --end "2026-03-24T10:15:00Z"
78
78
  ```
79
79
 
80
80
  ## Example
@@ -15,7 +15,7 @@ description: Comprehensive application log investigation workflow that reads log
15
15
  ## Standards
16
16
 
17
17
  - Evidence: Use a bounded investigation window and correlate log lines with code, runtime context, and concrete identifiers.
18
- - Execution: Scope the incident, use the bundled scripts to cut logs down by time window or search terms, build a timeline, validate candidate issues, then prioritize and optionally publish them.
18
+ - Execution: Scope the incident, use the bundled CLI tools to cut logs down by time window or search terms, build a timeline, validate candidate issues, then prioritize and optionally publish them.
19
19
  - Quality: Separate confirmed issues from hypotheses and include time-window, log, code, impact, and confidence evidence for each report.
20
20
  - Output: Return incident summary, confirmed issues, hypotheses, monitoring improvements, and publication status.
21
21
 
@@ -38,11 +38,11 @@ Use this skill to analyze application logs systematically with the codebase and
38
38
  - If the user does not provide a trustworthy window, derive one from a concrete runtime boundary first, such as the last container restart, pod recreation, deploy start, worker boot, or first failure after a known healthy state.
39
39
  - Prefer analyzing logs only inside that bounded window first (for example, from the last restart until now) to avoid stale logs polluting the diagnosis; widen the window only when the bounded slice cannot explain the symptom.
40
40
  - Identify relevant identifiers (trace ID, request ID, user ID, job ID, tx hash).
41
- - Use `scripts/filter_logs_by_time.py` first when the raw log set is large and the incident window can be bounded.
41
+ - Use `apltk filter-logs` first when the raw log set is large and the incident window can be bounded.
42
42
  2. Build a timeline from logs
43
43
  - Extract key events in chronological order within the chosen window: deploys, config changes, warnings, errors, retries, and recoveries.
44
44
  - Group repeated symptoms by signature (error type, message prefix, stack frame, endpoint).
45
- - Use `scripts/search_logs.py` to narrow by error signature, IDs, endpoint names, or repeated keywords before summarizing the timeline.
45
+ - Use `apltk search-logs` to narrow by error signature, IDs, endpoint names, or repeated keywords before summarizing the timeline.
46
46
  3. Correlate across context
47
47
  - Link related log lines using identifiers and timestamps.
48
48
  - Map stack traces and log messages to exact code locations.
@@ -83,6 +83,8 @@ Pass these fields to the dependency skill:
83
83
  - `reproduction`: steps/conditions if known; otherwise leave empty
84
84
  - `repo`: target repository in `owner/repo` format when known
85
85
 
86
+ If invoking the publisher CLI directly, pass these fields through `apltk open-github-issue --payload-file <json>` or `@file` inputs rather than inline shell arguments, because log evidence can contain backticks or shell metacharacters.
87
+
86
88
  Issue body sections must always include these three parts:
87
89
 
88
90
  - Chinese-language repositories: use localized equivalents of
@@ -120,7 +122,7 @@ Use this structure in responses:
120
122
 
121
123
  - `references/investigation-checklist.md`: Step-by-step checklist for evidence-driven log investigations.
122
124
  - `references/log-signal-patterns.md`: Common log signatures, likely causes, validation hints, and false-positive guards.
123
- - `scripts/filter_logs_by_time.py`: Filter raw logs to a bounded incident window from files or stdin.
124
- - `scripts/search_logs.py`: Search logs by keyword or regex with optional time-window filtering and context lines.
125
+ - `scripts/filter_logs_by_time.py`: Filter raw logs to a bounded incident window from files or stdin; exposed as `apltk filter-logs`.
126
+ - `scripts/search_logs.py`: Search logs by keyword or regex with optional time-window filtering and context lines; exposed as `apltk search-logs`.
125
127
  - `scripts/log_cli_utils.py`: Shared timestamp parsing and stdin/file iteration utilities for the bundled log scripts.
126
128
  - Dependency skill: `open-github-issue` for deterministic issue publishing with auth fallback and README language detection.
@@ -45,13 +45,13 @@ Persist durable user preferences from recent Codex conversations into reusable,
45
45
  Extract the recent conversations:
46
46
 
47
47
  ```bash
48
- python3 scripts/extract_recent_conversations.py --lookback-minutes 1440
48
+ apltk extract-codex-conversations --lookback-minutes 1440
49
49
  ```
50
50
 
51
51
  Refresh the AGENTS memory index after updating the memory files:
52
52
 
53
53
  ```bash
54
- python3 scripts/sync_memory_index.py --agents-file ~/.codex/AGENTS.md --memory-dir ~/.codex/memory
54
+ apltk sync-codex-memory-index --agents-file ~/.codex/AGENTS.md --memory-dir ~/.codex/memory
55
55
  ```
56
56
 
57
57
  Use the bundled memory template when creating or refactoring category files:
@@ -25,8 +25,8 @@ Keep a durable, categorized memory of user preferences so future agents can quic
25
25
 
26
26
  ## Required Resources
27
27
 
28
- - `scripts/extract_recent_conversations.py` to read the last 24 hours of Codex sessions, including archived sessions.
29
- - `scripts/sync_memory_index.py` to maintain a normalized memory index section at the end of `~/.codex/AGENTS.md`.
28
+ - `scripts/extract_recent_conversations.py` to read the last 24 hours of Codex sessions, including archived sessions, exposed as `apltk extract-codex-conversations`.
29
+ - `scripts/sync_memory_index.py` to maintain a normalized memory index section at the end of `~/.codex/AGENTS.md`, exposed as `apltk sync-codex-memory-index`.
30
30
 
31
31
  ## Workflow
32
32
 
@@ -35,7 +35,7 @@ Keep a durable, categorized memory of user preferences so future agents can quic
35
35
  - Run:
36
36
 
37
37
  ```bash
38
- python3 ~/.codex/skills/codex-memory-manager/scripts/extract_recent_conversations.py --lookback-minutes 1440
38
+ apltk extract-codex-conversations --lookback-minutes 1440
39
39
  ```
40
40
 
41
41
  - The extractor reads both `~/.codex/sessions` and `~/.codex/archived_sessions`.
@@ -97,14 +97,14 @@ User preferences about how engineering tasks should be investigated, planned, im
97
97
  ### 4) Refresh the AGENTS memory index at the end of `~/.codex/AGENTS.md`
98
98
 
99
99
  - First inspect `~/.codex/AGENTS.md` and mirror its existing language in the memory section instructions.
100
- - After updating memory files, run `scripts/sync_memory_index.py` to rewrite the managed section at the end of the file.
100
+ - After updating memory files, run `apltk sync-codex-memory-index` to rewrite the managed section at the end of the file.
101
101
  - The section must do both of these things explicitly:
102
102
  - instruct future agents to review the index before starting work
103
103
  - instruct future agents to update the matching memory files and refresh the index when a new category appears
104
104
  - Example command in English AGENTS files:
105
105
 
106
106
  ```bash
107
- python3 ~/.codex/skills/codex-memory-manager/scripts/sync_memory_index.py \
107
+ apltk sync-codex-memory-index \
108
108
  --agents-file ~/.codex/AGENTS.md \
109
109
  --memory-dir ~/.codex/memory \
110
110
  --section-title "## User Memory Index" \
@@ -43,14 +43,15 @@ def run_extractor(
43
43
  archived_dir: Path | None = None,
44
44
  *extra_args: str,
45
45
  ) -> str:
46
+ effective_archived_dir = archived_dir or (sessions_dir.parent / "__isolated_archived_sessions__")
46
47
  cmd = [
47
48
  sys.executable,
48
49
  str(SCRIPT_PATH),
49
50
  "--sessions-dir",
50
51
  str(sessions_dir),
52
+ "--archived-sessions-dir",
53
+ str(effective_archived_dir),
51
54
  ]
52
- if archived_dir is not None:
53
- cmd.extend(["--archived-sessions-dir", str(archived_dir)])
54
55
  cmd.extend(extra_args)
55
56
  result = subprocess.run(cmd, capture_output=True, text=True, check=True)
56
57
  return result.stdout
@@ -41,7 +41,7 @@ This skill extracts the latest conversations from `~/.codex/sessions` and `~/.co
41
41
  Run the extractor:
42
42
 
43
43
  ```bash
44
- python3 scripts/extract_recent_conversations.py --lookback-minutes 1440
44
+ apltk extract-skill-conversations --lookback-minutes 1440
45
45
  ```
46
46
 
47
47
  - If output is `NO_RECENT_CONVERSATIONS`, no action is required.
@@ -25,7 +25,7 @@ Extract recent conversations, identify reusable lessons, and convert those lesso
25
25
 
26
26
  ## Required Resources
27
27
 
28
- - `scripts/extract_recent_conversations.py` for deterministic session extraction.
28
+ - `scripts/extract_recent_conversations.py` for deterministic session extraction, exposed as `apltk extract-skill-conversations`.
29
29
  - `$skill-creator` for all skill creation/update work.
30
30
 
31
31
  ## Workflow
@@ -35,7 +35,7 @@ Extract recent conversations, identify reusable lessons, and convert those lesso
35
35
  - Run:
36
36
 
37
37
  ```bash
38
- python3 ~/.codex/skills/learn-skill-from-conversations/scripts/extract_recent_conversations.py --lookback-minutes 1440
38
+ apltk extract-skill-conversations --lookback-minutes 1440
39
39
  ```
40
40
 
41
41
  - If output is exactly `NO_RECENT_CONVERSATIONS`, stop immediately and report that no action is required.
@@ -43,14 +43,15 @@ def run_extractor(
43
43
  archived_dir: Path | None = None,
44
44
  *extra_args: str,
45
45
  ) -> str:
46
+ effective_archived_dir = archived_dir or (sessions_dir.parent / "__isolated_archived_sessions__")
46
47
  cmd = [
47
48
  sys.executable,
48
49
  str(SCRIPT_PATH),
49
50
  "--sessions-dir",
50
51
  str(sessions_dir),
52
+ "--archived-sessions-dir",
53
+ str(effective_archived_dir),
51
54
  ]
52
- if archived_dir is not None:
53
- cmd.extend(["--archived-sessions-dir", str(archived_dir)])
54
55
  cmd.extend(extra_args)
55
56
  result = subprocess.run(cmd, capture_output=True, text=True, check=True)
56
57
  return result.stdout
@@ -39,7 +39,7 @@ Two modes are supported:
39
39
  ### 1) say mode
40
40
 
41
41
  ```bash
42
- python3 scripts/docs_to_voice.py \
42
+ apltk docs-to-voice \
43
43
  --project-dir "/path/to/project" \
44
44
  --mode say \
45
45
  --text "Hello, this is a voice synthesis test."
@@ -48,13 +48,13 @@ python3 scripts/docs_to_voice.py \
48
48
  ### 2) api mode (Model Studio)
49
49
 
50
50
  ```bash
51
- python3 scripts/docs_to_voice.py \
51
+ apltk docs-to-voice \
52
52
  --project-dir "/path/to/project" \
53
53
  --mode api \
54
54
  --text "Hello, this is a qwen3-tts test."
55
55
  ```
56
56
 
57
- > Compatibility note: `scripts/docs_to_voice.sh` still works and internally delegates to the Python script.
57
+ > Compatibility note: `scripts/docs_to_voice.sh` still works, while `apltk docs-to-voice` is the preferred entrypoint.
58
58
 
59
59
  ## `.env` settings
60
60
 
@@ -15,13 +15,13 @@ description: Convert text and document content into audio files and sentence-lev
15
15
  ## Standards
16
16
 
17
17
  - Evidence: Confirm `project_dir`, input source, mode, and environment-backed settings before generation.
18
- - Execution: Use `scripts/docs_to_voice.py` to write audio plus matching timeline and subtitle files under `project_dir/audio/{project_name}/`.
18
+ - Execution: Use `apltk docs-to-voice` to write audio plus matching timeline and subtitle files under `project_dir/audio/{project_name}/`.
19
19
  - Quality: Respect mode-specific options, sentence splitting rules, and post-process requirements such as `ffmpeg` for speed changes.
20
20
  - Output: Return the absolute output audio path together with the generated `.timeline.json` and `.srt` companions.
21
21
 
22
22
  ## Overview
23
23
 
24
- Use `scripts/docs_to_voice.py` to convert raw text or text files into audio and always save under:
24
+ Use `apltk docs-to-voice` to convert raw text or text files into audio and always save under:
25
25
 
26
26
  `project_dir/audio/{project_name}/`
27
27
 
@@ -69,7 +69,7 @@ Modes:
69
69
 
70
70
  ## Script Reference
71
71
 
72
- `scripts/docs_to_voice.py` flags:
72
+ `apltk docs-to-voice` flags:
73
73
 
74
74
  - `--project-dir` (required)
75
75
  - `--project-name` (optional)
@@ -104,4 +104,4 @@ Environment variables:
104
104
  - `api` mode: confirm `command -v python3` and valid `DASHSCOPE_API_KEY`.
105
105
  - Long-text chunk merge (especially AIFF output): recommend `command -v ffmpeg`.
106
106
  - If output exists, use `--force` or a new `--output-name`.
107
- - `scripts/docs_to_voice.sh` is kept as a compatibility wrapper for existing workflows.
107
+ - `scripts/docs_to_voice.sh` is kept as a compatibility wrapper for existing workflows, but prefer `apltk docs-to-voice`.
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env python3
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import subprocess
8
+ import sys
9
+ import tempfile
10
+ import unittest
11
+ from pathlib import Path
12
+
13
+
14
+ SCRIPT_PATH = Path(__file__).resolve().parents[1] / "scripts" / "docs_to_voice.py"
15
+ SHELL_SCRIPT_PATH = Path(__file__).resolve().parents[1] / "scripts" / "docs_to_voice.sh"
16
+
17
+
18
+ class DocsToVoiceShellWrapperTests(unittest.TestCase):
19
+ def test_shell_wrapper_execs_python_script_with_same_arguments(self) -> None:
20
+ with tempfile.TemporaryDirectory() as temp_dir:
21
+ capture_path = Path(temp_dir) / "argv.json"
22
+ fake_python = Path(temp_dir) / "python3"
23
+ fake_python.write_text(
24
+ f"#!{sys.executable}\n"
25
+ "import json, os, sys\n"
26
+ "with open(os.environ['CAPTURE_PATH'], 'w', encoding='utf-8') as handle:\n"
27
+ " json.dump(sys.argv[1:], handle)\n",
28
+ encoding="utf-8",
29
+ )
30
+ fake_python.chmod(0o755)
31
+
32
+ env = dict(os.environ)
33
+ env["PATH"] = f"{temp_dir}:{env['PATH']}"
34
+ env["CAPTURE_PATH"] = str(capture_path)
35
+
36
+ result = subprocess.run(
37
+ ["bash", str(SHELL_SCRIPT_PATH), "--input", "notes.md", "--project-dir", "/tmp/project"],
38
+ capture_output=True,
39
+ text=True,
40
+ env=env,
41
+ check=False,
42
+ )
43
+
44
+ self.assertEqual(result.returncode, 0, result.stderr)
45
+ argv = json.loads(capture_path.read_text(encoding="utf-8"))
46
+ self.assertEqual(argv[0], str(SCRIPT_PATH))
47
+ self.assertEqual(argv[1:], ["--input", "notes.md", "--project-dir", "/tmp/project"])
48
+
49
+
50
+ if __name__ == "__main__":
51
+ unittest.main()
@@ -92,6 +92,7 @@ Load these references as needed during classification:
92
92
  - `reason`: why this feature should exist now
93
93
  - `suggested-architecture`: minimal architecture and module plan
94
94
  - `repo`: target repository in `owner/repo` format when known
95
+ - If invoking the publisher CLI directly, pass accepted proposal details through `apltk open-github-issue --payload-file <json>` or `@file` inputs rather than inline shell arguments so Markdown and code identifiers remain literal.
95
96
  - Reuse the returned `mode`, `issue_url`, and `publish_error` in the response.
96
97
  - After the related feature is implemented, remove that feature entry from `## Accepted Feature Proposals` in `AGENTS.md`.
97
98
  - Remove only implemented items; keep unimplemented accepted items untouched.
@@ -36,11 +36,9 @@ A shared planning skill for feature work. It centralizes creation and maintenanc
36
36
  ## Quick start
37
37
 
38
38
  ```bash
39
- SKILL_ROOT=/path/to/generate-spec
40
39
  WORKSPACE_ROOT=/path/to/target-project
41
- python3 "$SKILL_ROOT/scripts/create-specs" "Membership upgrade flow" \
40
+ apltk create-specs "Membership upgrade flow" \
42
41
  --change-name membership-upgrade-flow \
43
- --template-dir "$SKILL_ROOT/references/templates" \
44
42
  --output-dir "$WORKSPACE_ROOT/docs/plans"
45
43
  ```
46
44
 
@@ -58,11 +56,10 @@ docs/plans/<today>/membership-upgrade-flow/
58
56
  Parallel batch output:
59
57
 
60
58
  ```bash
61
- python3 "$SKILL_ROOT/scripts/create-specs" "Membership write path" \
59
+ apltk create-specs "Membership write path" \
62
60
  --change-name membership-write-path \
63
61
  --batch-name membership-cutover \
64
62
  --with-coordination \
65
- --template-dir "$SKILL_ROOT/references/templates" \
66
63
  --output-dir "$WORKSPACE_ROOT/docs/plans"
67
64
  ```
68
65
 
@@ -89,6 +86,6 @@ docs/plans/<today>/membership-cutover/
89
86
 
90
87
  ## Notes
91
88
 
92
- - `scripts/...` and `references/...` are skill-folder paths, not project-folder paths.
89
+ - `scripts/...` and `references/...` remain skill-folder paths when you need the raw assets, but `apltk create-specs` is the preferred command surface.
93
90
  - The generator replaces `[YYYY-MM-DD]`, `[Feature Name]`, `[功能名稱]`, `[change_name]`, and `[batch_name]` placeholders.
94
91
  - If a batch split produces specs that must land in a functional sequence, or still leaves unresolved shared-file collisions, re-slice the work so each spec becomes independently implementable, testable, mergeable, and parallel-safe before coding starts.
@@ -50,9 +50,8 @@ Own the shared planning-doc lifecycle for feature work so other skills can reuse
50
50
  - Treat any unresolved shared-file collision, overlapping ownership, or incompatible contract change across spec sets as a planning bug to resolve before approval, not an implementation-time surprise.
51
51
  - If two candidate spec sets would still need to edit the same non-additive surface, either merge them into one spec set or record a concrete ownership split plus additive-only rule that makes parallel work safe.
52
52
  - Use:
53
- - `SKILL_ROOT=<path_to_generate-spec_skill>`
54
53
  - `WORKSPACE_ROOT=<target_project_root>`
55
- - `python3 "$SKILL_ROOT/scripts/create-specs" "<feature_name>" --change-name <kebab-case> --template-dir "$SKILL_ROOT/references/templates" --output-dir "$WORKSPACE_ROOT/docs/plans"`
54
+ - `apltk create-specs "<feature_name>" --change-name <kebab-case> --output-dir "$WORKSPACE_ROOT/docs/plans"`
56
55
  - For parallel multi-spec generation, also use:
57
56
  - `--batch-name <kebab-case-batch-name>`
58
57
  - `--with-coordination`
@@ -170,7 +169,7 @@ Own the shared planning-doc lifecycle for feature work so other skills can reuse
170
169
 
171
170
  ## References
172
171
 
173
- - `scripts/create-specs`: shared planning file generator.
172
+ - `scripts/create-specs`: shared planning file generator, exposed as `apltk create-specs`.
174
173
  - `references/templates/spec.md`: BDD requirement template.
175
174
  - `references/templates/tasks.md`: task breakdown template.
176
175
  - `references/templates/checklist.md`: behavior-to-test alignment template.
@@ -0,0 +1,166 @@
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_coordination_file(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
+
84
+ argv = [
85
+ "create-specs",
86
+ "My Feature",
87
+ "--batch-name",
88
+ "parallel-batch",
89
+ "--with-coordination",
90
+ "--output-dir",
91
+ str(output_dir),
92
+ "--template-dir",
93
+ str(template_dir),
94
+ ]
95
+
96
+ with patch.object(MODULE, "date", FixedDate), patch.object(sys, "argv", argv), patch(
97
+ "sys.stdout", new_callable=io.StringIO
98
+ ) as stdout:
99
+ exit_code = MODULE.main()
100
+
101
+ self.assertEqual(exit_code, 0)
102
+ change_root = output_dir / "2026-04-18" / "parallel-batch" / "my-feature"
103
+ for name in MODULE.TEMPLATE_FILENAMES:
104
+ content = (change_root / name).read_text(encoding="utf-8")
105
+ self.assertIn("2026-04-18", content)
106
+ self.assertIn("My Feature", content)
107
+ self.assertIn("my-feature", content)
108
+ self.assertIn("parallel-batch", content)
109
+
110
+ coordination = output_dir / "2026-04-18" / "parallel-batch" / "coordination.md"
111
+ self.assertTrue(coordination.is_file())
112
+ self.assertIn(str(coordination), stdout.getvalue())
113
+
114
+ def test_main_rejects_existing_files_without_force(self) -> None:
115
+ with tempfile.TemporaryDirectory() as temp_dir:
116
+ root = Path(temp_dir)
117
+ template_dir = root / "templates"
118
+ output_dir = root / "docs" / "plans"
119
+ template_dir.mkdir(parents=True)
120
+ for name in MODULE.TEMPLATE_FILENAMES:
121
+ (template_dir / name).write_text("template\n", encoding="utf-8")
122
+
123
+ existing_root = output_dir / "2026-04-18" / "existing-change"
124
+ existing_root.mkdir(parents=True)
125
+ (existing_root / "spec.md").write_text("existing\n", encoding="utf-8")
126
+
127
+ argv = [
128
+ "create-specs",
129
+ "Existing Change",
130
+ "--change-name",
131
+ "existing-change",
132
+ "--output-dir",
133
+ str(output_dir),
134
+ "--template-dir",
135
+ str(template_dir),
136
+ ]
137
+
138
+ with patch.object(MODULE, "date", FixedDate), patch.object(sys, "argv", argv):
139
+ with self.assertRaises(SystemExit) as context:
140
+ MODULE.main()
141
+
142
+ self.assertIn("Files already exist", str(context.exception))
143
+
144
+ def test_main_requires_explicit_ascii_change_name_when_slug_is_empty(self) -> None:
145
+ with tempfile.TemporaryDirectory() as temp_dir:
146
+ template_dir = Path(temp_dir) / "templates"
147
+ template_dir.mkdir(parents=True)
148
+ for name in MODULE.TEMPLATE_FILENAMES:
149
+ (template_dir / name).write_text("template\n", encoding="utf-8")
150
+
151
+ argv = [
152
+ "create-specs",
153
+ "功能名稱",
154
+ "--template-dir",
155
+ str(template_dir),
156
+ ]
157
+
158
+ with patch.object(sys, "argv", argv):
159
+ with self.assertRaises(SystemExit) as context:
160
+ MODULE.main()
161
+
162
+ self.assertIn("Unable to build change_name", str(context.exception))
163
+
164
+
165
+ if __name__ == "__main__":
166
+ unittest.main()
@@ -66,6 +66,10 @@ Implement Jupiter-backed Solana features safely by following the current officia
66
66
  - Treat Jupiter token and price data as curated but evolving.
67
67
  - Tokens V2 responses can change as Jupiter improves the schema.
68
68
  - Price V3 intentionally withholds unreliable prices.
69
+ - Treat Jupiter routing program metadata as discovery data, not signer policy.
70
+ - Official program-label mappings such as `program-id-to-label` may help build observability, drift detection, and review queues for newly observed router programs.
71
+ - Do not automatically convert a Jupiter-maintained program list into a signing allowlist for wallet, hot-wallet, or `/swap-to-sol` style flows.
72
+ - Keep local transaction grammar fail-closed around allowed program classes, signer/writable scope, fee/output policy, instruction discriminators, and receiver semantics; only promote newly discovered programs after the local safety contract is understood and tested.
69
73
  - For Jupiter Lend advanced recipes, expect versioned transactions, address lookup tables, and sometimes extra compute budget.
70
74
  - Never commit private keys. Use environment variables, wallet adapters, secure signers, or managed key systems.
71
75
 
@@ -74,6 +78,7 @@ Implement Jupiter-backed Solana features safely by following the current officia
74
78
  - Confirm the base URL, auth header, and required parameters match the official docs you used.
75
79
  - Verify that any routing, payer, referral, or fee assumptions still hold after optional parameters are added.
76
80
  - When building transactions manually, verify quote endpoint compatibility, instruction order, compute budget, address lookup tables, and signing flow.
81
+ - When using Jupiter-maintained program registries, verify that registry drift handling is observability-first: record unknown program labels and route context, alert or fail closed on unsafe transaction shapes, and keep signing decisions owned by the local policy layer rather than by the remote registry response.
77
82
  - When the task involves on-chain actions, report any remaining environment needs clearly, such as API keys, RPC endpoints, or wallet secrets that were intentionally not embedded.
78
83
 
79
84
  ## Reference Files