@leejungkiin/awkit 1.4.0 → 1.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/awk.js +458 -7
- package/bin/claude-generators.js +122 -0
- package/core/AGENTS.md +16 -0
- package/core/CLAUDE.md +155 -0
- package/core/GEMINI.md +44 -9
- package/package.json +1 -1
- package/skills/ai-sprite-maker/SKILL.md +81 -0
- package/skills/ai-sprite-maker/scripts/animate_sprite.py +102 -0
- package/skills/ai-sprite-maker/scripts/process_sprites.py +140 -0
- package/skills/code-review/SKILL.md +21 -33
- package/skills/lucylab-tts/SKILL.md +64 -0
- package/skills/lucylab-tts/resources/voices_library.json +908 -0
- package/skills/lucylab-tts/scripts/.env +1 -0
- package/skills/lucylab-tts/scripts/lucylab_tts.py +506 -0
- package/skills/orchestrator/SKILL.md +5 -0
- package/skills/short-maker/SKILL.md +150 -0
- package/skills/short-maker/_backup/storyboard.html +106 -0
- package/skills/short-maker/_backup/video_mixer.py +296 -0
- package/skills/short-maker/outputs/fitbite-promo/background.jpg +0 -0
- package/skills/short-maker/outputs/fitbite-promo/final/promo-final.mp4 +0 -0
- package/skills/short-maker/outputs/fitbite-promo/script.md +19 -0
- package/skills/short-maker/outputs/fitbite-promo/segments/scene-01.mp4 +0 -0
- package/skills/short-maker/outputs/fitbite-promo/segments/scene-02.mp4 +0 -0
- package/skills/short-maker/outputs/fitbite-promo/segments/scene-03.mp4 +0 -0
- package/skills/short-maker/outputs/fitbite-promo/segments/scene-04.mp4 +0 -0
- package/skills/short-maker/outputs/fitbite-promo/storyboard/scene-01.png +0 -0
- package/skills/short-maker/outputs/fitbite-promo/storyboard/scene-02.png +0 -0
- package/skills/short-maker/outputs/fitbite-promo/storyboard/scene-03.png +0 -0
- package/skills/short-maker/outputs/fitbite-promo/storyboard/scene-04.png +0 -0
- package/skills/short-maker/outputs/fitbite-promo/storyboard.html +133 -0
- package/skills/short-maker/outputs/fitbite-promo/storyboard.json +38 -0
- package/skills/short-maker/outputs/fitbite-promo/temp/merged_chroma.mp4 +0 -0
- package/skills/short-maker/outputs/fitbite-promo/temp/merged_crossfaded.mp4 +0 -0
- package/skills/short-maker/outputs/fitbite-promo/temp/ready_00.mp4 +0 -0
- package/skills/short-maker/outputs/fitbite-promo/temp/ready_01.mp4 +0 -0
- package/skills/short-maker/outputs/fitbite-promo/temp/ready_02.mp4 +0 -0
- package/skills/short-maker/outputs/fitbite-promo/temp/ready_03.mp4 +0 -0
- package/skills/short-maker/outputs/fitbite-promo/tts/manifest.json +31 -0
- package/skills/short-maker/outputs/fitbite-promo/tts/scene-01.wav +0 -0
- package/skills/short-maker/outputs/fitbite-promo/tts/scene-02.wav +0 -0
- package/skills/short-maker/outputs/fitbite-promo/tts/scene-03.wav +0 -0
- package/skills/short-maker/outputs/fitbite-promo/tts/scene-04.wav +0 -0
- package/skills/short-maker/outputs/fitbite-promo/tts_script.txt +11 -0
- package/skills/short-maker/scripts/google-flow-cli/.project-identity +41 -0
- package/skills/short-maker/scripts/google-flow-cli/.trae/rules/project_rules.md +52 -0
- package/skills/short-maker/scripts/google-flow-cli/CODEBASE.md +67 -0
- package/skills/short-maker/scripts/google-flow-cli/GoogleFlowCli.code-workspace +29 -0
- package/skills/short-maker/scripts/google-flow-cli/README.md +168 -0
- package/skills/short-maker/scripts/google-flow-cli/docs/specs/PROJECT.md +12 -0
- package/skills/short-maker/scripts/google-flow-cli/docs/specs/REQUIREMENTS.md +22 -0
- package/skills/short-maker/scripts/google-flow-cli/docs/specs/ROADMAP.md +16 -0
- package/skills/short-maker/scripts/google-flow-cli/docs/specs/TECH-SPEC.md +13 -0
- package/skills/short-maker/scripts/google-flow-cli/gflow/__init__.py +3 -0
- package/skills/short-maker/scripts/google-flow-cli/gflow/api/__init__.py +19 -0
- package/skills/short-maker/scripts/google-flow-cli/gflow/api/client.py +1921 -0
- package/skills/short-maker/scripts/google-flow-cli/gflow/api/models.py +64 -0
- package/skills/short-maker/scripts/google-flow-cli/gflow/api/rpc_ids.py +98 -0
- package/skills/short-maker/scripts/google-flow-cli/gflow/auth/__init__.py +15 -0
- package/skills/short-maker/scripts/google-flow-cli/gflow/auth/browser_auth.py +692 -0
- package/skills/short-maker/scripts/google-flow-cli/gflow/auth/humanizer.py +417 -0
- package/skills/short-maker/scripts/google-flow-cli/gflow/auth/proxy_ext.py +120 -0
- package/skills/short-maker/scripts/google-flow-cli/gflow/auth/recaptcha.py +482 -0
- package/skills/short-maker/scripts/google-flow-cli/gflow/batchexecute/__init__.py +5 -0
- package/skills/short-maker/scripts/google-flow-cli/gflow/batchexecute/client.py +414 -0
- package/skills/short-maker/scripts/google-flow-cli/gflow/cli/__init__.py +1 -0
- package/skills/short-maker/scripts/google-flow-cli/gflow/cli/main.py +1075 -0
- package/skills/short-maker/scripts/google-flow-cli/pyproject.toml +36 -0
- package/skills/short-maker/scripts/google-flow-cli/script.txt +22 -0
- package/skills/short-maker/scripts/google-flow-cli/tests/__init__.py +0 -0
- package/skills/short-maker/scripts/google-flow-cli/tests/test_batchexecute.py +113 -0
- package/skills/short-maker/scripts/google-flow-cli/tests/test_client.py +190 -0
- package/skills/short-maker/templates/aida_script.md +40 -0
- package/skills/short-maker/templates/mimic_analyzer.md +29 -0
- package/skills/single-flow-task-execution/SKILL.md +9 -6
- package/skills/skill-creator/SKILL.md +44 -0
- package/skills/spm-build-analysis/SKILL.md +92 -0
- package/skills/spm-build-analysis/references/build-optimization-sources.md +155 -0
- package/skills/spm-build-analysis/references/recommendation-format.md +85 -0
- package/skills/spm-build-analysis/references/spm-analysis-checks.md +105 -0
- package/skills/spm-build-analysis/scripts/check_spm_pins.py +118 -0
- package/skills/symphony-enforcer/SKILL.md +51 -83
- package/skills/symphony-orchestrator/SKILL.md +1 -1
- package/skills/trello-sync/SKILL.md +27 -28
- package/skills/verification-gate/SKILL.md +13 -2
- package/skills/xcode-build-benchmark/SKILL.md +88 -0
- package/skills/xcode-build-benchmark/references/benchmark-artifacts.md +94 -0
- package/skills/xcode-build-benchmark/references/benchmarking-workflow.md +67 -0
- package/skills/xcode-build-benchmark/schemas/build-benchmark.schema.json +230 -0
- package/skills/xcode-build-benchmark/scripts/benchmark_builds.py +308 -0
- package/skills/xcode-build-fixer/SKILL.md +218 -0
- package/skills/xcode-build-fixer/references/build-settings-best-practices.md +216 -0
- package/skills/xcode-build-fixer/references/fix-patterns.md +290 -0
- package/skills/xcode-build-fixer/references/recommendation-format.md +85 -0
- package/skills/xcode-build-fixer/scripts/benchmark_builds.py +308 -0
- package/skills/xcode-build-orchestrator/SKILL.md +156 -0
- package/skills/xcode-build-orchestrator/references/benchmark-artifacts.md +94 -0
- package/skills/xcode-build-orchestrator/references/build-settings-best-practices.md +216 -0
- package/skills/xcode-build-orchestrator/references/orchestration-report-template.md +143 -0
- package/skills/xcode-build-orchestrator/references/recommendation-format.md +85 -0
- package/skills/xcode-build-orchestrator/scripts/benchmark_builds.py +308 -0
- package/skills/xcode-build-orchestrator/scripts/diagnose_compilation.py +273 -0
- package/skills/xcode-build-orchestrator/scripts/generate_optimization_report.py +533 -0
- package/skills/xcode-compilation-analyzer/SKILL.md +89 -0
- package/skills/xcode-compilation-analyzer/references/build-optimization-sources.md +155 -0
- package/skills/xcode-compilation-analyzer/references/code-compilation-checks.md +106 -0
- package/skills/xcode-compilation-analyzer/references/recommendation-format.md +85 -0
- package/skills/xcode-compilation-analyzer/scripts/diagnose_compilation.py +273 -0
- package/skills/xcode-project-analyzer/SKILL.md +76 -0
- package/skills/xcode-project-analyzer/references/build-optimization-sources.md +155 -0
- package/skills/xcode-project-analyzer/references/build-settings-best-practices.md +216 -0
- package/skills/xcode-project-analyzer/references/project-audit-checks.md +101 -0
- package/skills/xcode-project-analyzer/references/recommendation-format.md +85 -0
- package/templates/project-identity/android.json +0 -10
- package/templates/project-identity/backend-nestjs.json +0 -10
- package/templates/project-identity/expo.json +0 -10
- package/templates/project-identity/ios.json +0 -10
- package/templates/project-identity/web-nextjs.json +0 -10
- package/workflows/_uncategorized/ship-to-code.md +85 -0
- package/workflows/context/codebase-sync.md +10 -87
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "gflow"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A command-line interface to Google Flow (AI image & video generation)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"requests>=2.31",
|
|
14
|
+
"click>=8.1",
|
|
15
|
+
"rich>=13.0",
|
|
16
|
+
"pydantic>=2.0",
|
|
17
|
+
"selenium>=4.15",
|
|
18
|
+
"webdriver-manager>=4.0",
|
|
19
|
+
"websocket-client>=1.6",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.optional-dependencies]
|
|
23
|
+
dev = [
|
|
24
|
+
"pytest>=7.0",
|
|
25
|
+
"pytest-cov",
|
|
26
|
+
"ruff",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.scripts]
|
|
30
|
+
gflow = "gflow.cli.main:cli"
|
|
31
|
+
|
|
32
|
+
[tool.setuptools.packages.find]
|
|
33
|
+
include = ["gflow*"]
|
|
34
|
+
|
|
35
|
+
[tool.ruff]
|
|
36
|
+
line-length = 120
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
|
|
2
|
+
TITLE: Nếu cả đời này không rực rỡ thì sao?
|
|
3
|
+
|
|
4
|
+
--- SCRIPT (VOICEOVER) ---
|
|
5
|
+
HOOK: Gần đây, mình hay thấy một câu hỏi trên mạng: Nếu cả đời này mình không rực rỡ thì sao nhỉ?
|
|
6
|
+
PROBLEM: Chúng mình ở tuổi hai mươi, ba mươi, hay bị áp lực phải thành công, phải lấp lánh như người ta. Nhiều lúc nhìn lại, tự dưng thấy chạnh lòng một chút vì mình cứ mãi nhạt nhòa, thế này thì chán quá.
|
|
7
|
+
SOLUTION: Nhưng bạn xem nào, nhà Phật có câu 'Bình thường tâm thị đạo'. Tâm trí bình lặng mới là đáng quý nhất. Không rực rỡ như mặt trời thì mình làm một chiếc lá xanh tươi, sống trọn vẹn ở hiện tại, thế cũng đẹp mà, thật đấy.
|
|
8
|
+
CALL TO ACTION: Thả lỏng vai ra một chút nhé. Cứ bình thường thôi, miễn là tâm mình an. Hôm nay bạn có niềm vui nhỏ nào không, kể mình nghe với nhé!
|
|
9
|
+
|
|
10
|
+
--- VISUAL PROMPTS ---
|
|
11
|
+
SCENE 1: Một nữ người Việt Nam, độ tuổi thanh niên (20-30 tuổi), phong cách trang phục áo tràng tu sĩ màu nâu sòng, đầu cạo trọc, phong cách giản dị và thanh khiết với nụ cười nhẹ nhàng., bối cảnh là một phòng đọc sách truyền thống với kệ sách cao chứa nhiều kinh sách, một bức thư pháp treo tường. phía trước là bàn gỗ có bộ ấm trà, nhang đang tỏa khói và các quyển sổ ghi chép. ánh sáng vàng ấm áp từ đèn bàn tạo không gian thanh tịnh và cổ điển., nói giọng miền bắc (hà nội), tông giọng nhẹ nhàng, thấu cảm: 'Gần đây, mình hay thấy một câu hỏi trên mạng: Nếu cả đời này mình không rực rỡ thì sao nhỉ?'. Sư cô hơi nghiêng đầu, ánh mắt nhìn thẳng vào ống kính đầy thấu cảm, khẽ mỉm cười, khói nhang bay nhẹ phía trước. (Giới hạn: 8 giây)
|
|
12
|
+
|
|
13
|
+
SCENE 2: Một nữ người Việt Nam, độ tuổi thanh niên (20-30 tuổi), phong cách trang phục áo tràng tu sĩ màu nâu sòng, đầu cạo trọc, phong cách giản dị và thanh khiết với nụ cười nhẹ nhàng., bối cảnh là một phòng đọc sách truyền thống với kệ sách cao chứa nhiều kinh sách, một bức thư pháp treo tường. phía trước là bàn gỗ có bộ ấm trà, nhang đang tỏa khói và các quyển sổ ghi chép. ánh sáng vàng ấm áp từ đèn bàn tạo không gian thanh tịnh và cổ điển., nói giọng miền bắc (hà nội), tông giọng nhẹ nhàng, thấu cảm: 'Chúng mình ở tuổi hai mươi, ba mươi, hay bị áp lực phải thành công, phải lấp lánh như người ta.'. Sư cô khẽ cúi xuống rót một chén trà nhỏ, động tác khoan thai, rồi ngước lên nhìn người xem. (Giới hạn: 8 giây)
|
|
14
|
+
|
|
15
|
+
SCENE 3: Một nữ người Việt Nam, độ tuổi thanh niên (20-30 tuổi), phong cách trang phục áo tràng tu sĩ màu nâu sòng, đầu cạo trọc, phong cách giản dị và thanh khiết với nụ cười nhẹ nhàng., bối cảnh là một phòng đọc sách truyền thống với kệ sách cao chứa nhiều kinh sách, một bức thư pháp treo tường. phía trước là bàn gỗ có bộ ấm trà, nhang đang tỏa khói và các quyển sổ ghi chép. ánh sáng vàng ấm áp từ đèn bàn tạo không gian thanh tịnh và cổ điển., nói giọng miền bắc (hà nội), tông giọng nhẹ nhàng, thấu cảm: 'Nhiều lúc nhìn lại, tự dưng thấy chạnh lòng một chút vì mình cứ mãi nhạt nhòa, thế này thì chán quá.'. Đôi mắt sư cô chùng xuống một chút, thể hiện sự đồng cảm sâu sắc, hai tay úp nhẹ lên cuốn sổ ghi chép. (Giới hạn: 8 giây)
|
|
16
|
+
|
|
17
|
+
SCENE 4: Một nữ người Việt Nam, độ tuổi thanh niên (20-30 tuổi), phong cách trang phục áo tràng tu sĩ màu nâu sòng, đầu cạo trọc, phong cách giản dị và thanh khiết với nụ cười nhẹ nhàng., bối cảnh là một phòng đọc sách truyền thống với kệ sách cao chứa nhiều kinh sách, một bức thư pháp treo tường. phía trước là bàn gỗ có bộ ấm trà, nhang đang tỏa khói và các quyển sổ ghi chép. ánh sáng vàng ấm áp từ đèn bàn tạo không gian thanh tịnh và cổ điển., nói giọng miền bắc (hà nội), tông giọng nhẹ nhàng, thấu cảm: 'Nhưng bạn xem nào, nhà Phật có câu Bình thường tâm thị đạo. Tâm trí bình lặng mới là đáng quý nhất.'. Sư cô mỉm cười rạng rỡ hơn, ánh mắt ánh lên sự bình an, đưa một tay lên làm cử chỉ xoa dịu. (Giới hạn: 8 giây)
|
|
18
|
+
|
|
19
|
+
SCENE 5: Một nữ người Việt Nam, độ tuổi thanh niên (20-30 tuổi), phong cách trang phục áo tràng tu sĩ màu nâu sòng, đầu cạo trọc, phong cách giản dị và thanh khiết với nụ cười nhẹ nhàng., bối cảnh là một phòng đọc sách truyền thống với kệ sách cao chứa nhiều kinh sách, một bức thư pháp treo tường. phía trước là bàn gỗ có bộ ấm trà, nhang đang tỏa khói và các quyển sổ ghi chép. ánh sáng vàng ấm áp từ đèn bàn tạo không gian thanh tịnh và cổ điển., nói giọng miền bắc (hà nội), tông giọng nhẹ nhàng, thấu cảm: 'Không rực rỡ như mặt trời thì mình làm một chiếc lá xanh tươi, sống trọn vẹn ở hiện tại, thế cũng đẹp mà, thật đấy.'. Sư cô nâng chén trà lên ngang ngực, hơi nghiêng người về phía trước tạo sự gần gũi. (Giới hạn: 8 giây)
|
|
20
|
+
|
|
21
|
+
SCENE 6: Một nữ người Việt Nam, độ tuổi thanh niên (20-30 tuổi), phong cách trang phục áo tràng tu sĩ màu nâu sòng, đầu cạo trọc, phong cách giản dị và thanh khiết với nụ cười nhẹ nhàng., bối cảnh là một phòng đọc sách truyền thống với kệ sách cao chứa nhiều kinh sách, một bức thư pháp treo tường. phía trước là bàn gỗ có bộ ấm trà, nhang đang tỏa khói và các quyển sổ ghi chép. ánh sáng vàng ấm áp từ đèn bàn tạo không gian thanh tịnh và cổ điển., nói giọng miền bắc (hà nội), tông giọng nhẹ nhàng, thấu cảm: 'Thả lỏng vai ra một chút nhé. Cứ bình thường thôi, miễn là tâm mình an. Hôm nay bạn có niềm vui nhỏ nào không, kể mình nghe với nhé!'. Sư cô chắp tay hình búp sen, nở nụ cười tươi tắn chào tạm biệt, ánh sáng vàng hắt lên khuôn mặt thanh tú. (Giới hạn: 8 giây)
|
|
22
|
+
|
|
File without changes
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Tests for the BatchExecute protocol client."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from gflow.batchexecute.client import (
|
|
7
|
+
BatchExecuteClient,
|
|
8
|
+
RPC,
|
|
9
|
+
_unwrap_json,
|
|
10
|
+
_extract_sapisid,
|
|
11
|
+
_generate_sapisidhash,
|
|
12
|
+
ReqIDGenerator,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TestReqIDGenerator:
|
|
17
|
+
def test_generates_incrementing_ids(self):
|
|
18
|
+
gen = ReqIDGenerator()
|
|
19
|
+
id1 = int(gen.next())
|
|
20
|
+
id2 = int(gen.next())
|
|
21
|
+
assert id2 > id1
|
|
22
|
+
assert id2 - id1 == 100_000
|
|
23
|
+
|
|
24
|
+
def test_base_is_in_expected_range(self):
|
|
25
|
+
gen = ReqIDGenerator()
|
|
26
|
+
val = int(gen.next())
|
|
27
|
+
assert 1_500_000_000 <= val <= 1_700_000_000
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class TestExtractSAPISID:
|
|
31
|
+
def test_extracts_sapisid(self):
|
|
32
|
+
cookies = "SID=abc; HSID=def; SAPISID=my_sapisid_value; SSID=ghi"
|
|
33
|
+
assert _extract_sapisid(cookies) == "my_sapisid_value"
|
|
34
|
+
|
|
35
|
+
def test_returns_none_when_missing(self):
|
|
36
|
+
cookies = "SID=abc; HSID=def"
|
|
37
|
+
assert _extract_sapisid(cookies) is None
|
|
38
|
+
|
|
39
|
+
def test_empty_cookies(self):
|
|
40
|
+
assert _extract_sapisid("") is None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class TestGenerateSAPISIDHASH:
|
|
44
|
+
def test_format(self):
|
|
45
|
+
result = _generate_sapisidhash("test_sapisid", "https://flow.google")
|
|
46
|
+
assert result.startswith("SAPISIDHASH ")
|
|
47
|
+
parts = result.split(" ")[1].split("_")
|
|
48
|
+
assert len(parts) == 2
|
|
49
|
+
# First part is timestamp, second is hex hash
|
|
50
|
+
assert parts[0].isdigit()
|
|
51
|
+
assert all(c in "0123456789abcdef" for c in parts[1])
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class TestUnwrapJSON:
|
|
55
|
+
def test_plain_string(self):
|
|
56
|
+
assert _unwrap_json("hello") == "hello"
|
|
57
|
+
|
|
58
|
+
def test_json_array(self):
|
|
59
|
+
result = _unwrap_json('[1, 2, 3]')
|
|
60
|
+
assert result == [1, 2, 3]
|
|
61
|
+
|
|
62
|
+
def test_json_object(self):
|
|
63
|
+
result = _unwrap_json('{"key": "value"}')
|
|
64
|
+
assert result == {"key": "value"}
|
|
65
|
+
|
|
66
|
+
def test_double_encoded(self):
|
|
67
|
+
inner = json.dumps([1, 2, 3])
|
|
68
|
+
outer = json.dumps(inner)
|
|
69
|
+
result = _unwrap_json(outer)
|
|
70
|
+
assert result == [1, 2, 3]
|
|
71
|
+
|
|
72
|
+
def test_non_json(self):
|
|
73
|
+
result = _unwrap_json("not json at all")
|
|
74
|
+
assert result == "not json at all"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class TestBuildRPCData:
|
|
78
|
+
def test_basic_rpc(self):
|
|
79
|
+
rpc = RPC(id="test123", args=["hello", 42])
|
|
80
|
+
data = BatchExecuteClient._build_rpc_data(rpc)
|
|
81
|
+
assert data[0] == "test123"
|
|
82
|
+
assert json.loads(data[1]) == ["hello", 42]
|
|
83
|
+
assert data[2] is None
|
|
84
|
+
assert data[3] == "generic"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class TestDecodeResponse:
|
|
88
|
+
def _make_client(self):
|
|
89
|
+
return BatchExecuteClient(
|
|
90
|
+
host="test.example.com",
|
|
91
|
+
app="TestApp",
|
|
92
|
+
auth_token="token",
|
|
93
|
+
cookies="",
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def test_chunked_response(self):
|
|
97
|
+
# Simulate a chunked batchexecute response
|
|
98
|
+
chunk_data = json.dumps([
|
|
99
|
+
["wrb.fr", "rpc123", '["result_data"]', None, None, None, "generic"]
|
|
100
|
+
])
|
|
101
|
+
prefix = ")]}\'"
|
|
102
|
+
raw = "{}\n\n{}\n{}".format(prefix, len(chunk_data), chunk_data)
|
|
103
|
+
|
|
104
|
+
client = self._make_client()
|
|
105
|
+
responses = client._decode_response(raw)
|
|
106
|
+
|
|
107
|
+
assert len(responses) == 1
|
|
108
|
+
assert responses[0].id == "rpc123"
|
|
109
|
+
|
|
110
|
+
def test_empty_response_raises(self):
|
|
111
|
+
client = self._make_client()
|
|
112
|
+
with pytest.raises(Exception):
|
|
113
|
+
client._decode_response(")]}'")
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""Tests for the Flow API client."""
|
|
2
|
+
|
|
3
|
+
from unittest.mock import patch, MagicMock
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from gflow.api.client import (
|
|
8
|
+
FlowClient, FlowAPIError,
|
|
9
|
+
IMAGE_ASPECT_MAP, VIDEO_ASPECT_MAP,
|
|
10
|
+
IMAGE_MODEL, VIDEO_MODEL, TOOL_NAME,
|
|
11
|
+
)
|
|
12
|
+
from gflow.api.models import AssetType, GenerateImageRequest, GenerateVideoRequest
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TestConstants:
|
|
16
|
+
"""Test internal constants match what we discovered."""
|
|
17
|
+
|
|
18
|
+
def test_image_model(self):
|
|
19
|
+
assert IMAGE_MODEL == "NARWHAL"
|
|
20
|
+
|
|
21
|
+
def test_video_model(self):
|
|
22
|
+
assert VIDEO_MODEL == "veo_3_1_t2v_fast_ultra"
|
|
23
|
+
|
|
24
|
+
def test_tool_name(self):
|
|
25
|
+
assert TOOL_NAME == "PINHOLE"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class TestAspectRatioMapping:
|
|
29
|
+
"""Test the CLI-friendly aspect ratio mapping."""
|
|
30
|
+
|
|
31
|
+
def test_image_square(self):
|
|
32
|
+
assert IMAGE_ASPECT_MAP["square"] == "IMAGE_ASPECT_RATIO_SQUARE"
|
|
33
|
+
assert IMAGE_ASPECT_MAP["1:1"] == "IMAGE_ASPECT_RATIO_SQUARE"
|
|
34
|
+
|
|
35
|
+
def test_image_portrait(self):
|
|
36
|
+
assert IMAGE_ASPECT_MAP["portrait"] == "IMAGE_ASPECT_RATIO_PORTRAIT"
|
|
37
|
+
|
|
38
|
+
def test_image_landscape(self):
|
|
39
|
+
assert IMAGE_ASPECT_MAP["landscape"] == "IMAGE_ASPECT_RATIO_LANDSCAPE"
|
|
40
|
+
|
|
41
|
+
def test_video_landscape(self):
|
|
42
|
+
assert VIDEO_ASPECT_MAP["landscape"] == "VIDEO_ASPECT_RATIO_LANDSCAPE"
|
|
43
|
+
|
|
44
|
+
def test_video_portrait(self):
|
|
45
|
+
assert VIDEO_ASPECT_MAP["portrait"] == "VIDEO_ASPECT_RATIO_PORTRAIT"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class TestParseImageResponse:
|
|
49
|
+
"""Test image response parsing."""
|
|
50
|
+
|
|
51
|
+
def setup_method(self):
|
|
52
|
+
self.client = FlowClient.__new__(FlowClient)
|
|
53
|
+
self.client.debug = False
|
|
54
|
+
|
|
55
|
+
def test_parses_responses_format(self):
|
|
56
|
+
data = {
|
|
57
|
+
"responses": [{
|
|
58
|
+
"generatedImages": [
|
|
59
|
+
{
|
|
60
|
+
"encodedImage": "abc123base64",
|
|
61
|
+
"mediaGenerationId": "media-001",
|
|
62
|
+
"prompt": "a cat",
|
|
63
|
+
"modelNameType": "NARWHAL",
|
|
64
|
+
},
|
|
65
|
+
]
|
|
66
|
+
}]
|
|
67
|
+
}
|
|
68
|
+
assets = self.client._parse_image_response(data, "a cat")
|
|
69
|
+
assert len(assets) == 1
|
|
70
|
+
assert assets[0].id == "media-001"
|
|
71
|
+
assert assets[0].asset_type == AssetType.IMAGE
|
|
72
|
+
assert assets[0].raw["encodedImage"] == "abc123base64"
|
|
73
|
+
|
|
74
|
+
def test_parses_flat_format(self):
|
|
75
|
+
data = {
|
|
76
|
+
"generatedImages": [
|
|
77
|
+
{"mediaGenerationId": "flat-001", "encodedImage": "xyz"},
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
assets = self.client._parse_image_response(data, "test")
|
|
81
|
+
assert len(assets) == 1
|
|
82
|
+
assert assets[0].id == "flat-001"
|
|
83
|
+
|
|
84
|
+
def test_error_response(self):
|
|
85
|
+
data = {"error": {"message": "bad prompt"}}
|
|
86
|
+
with pytest.raises(FlowAPIError, match="Image generation failed"):
|
|
87
|
+
self.client._parse_image_response(data, "test")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class TestParseVideoResponse:
|
|
91
|
+
"""Test video response parsing."""
|
|
92
|
+
|
|
93
|
+
def setup_method(self):
|
|
94
|
+
self.client = FlowClient.__new__(FlowClient)
|
|
95
|
+
self.client.debug = False
|
|
96
|
+
|
|
97
|
+
def test_parses_operations(self):
|
|
98
|
+
data = {
|
|
99
|
+
"operations": [
|
|
100
|
+
{"name": "operations/video-abc123", "done": False},
|
|
101
|
+
]
|
|
102
|
+
}
|
|
103
|
+
assets = self.client._parse_video_response(data, "a sunset")
|
|
104
|
+
assert len(assets) == 1
|
|
105
|
+
assert assets[0].id == "operations/video-abc123"
|
|
106
|
+
assert assets[0].asset_type == AssetType.VIDEO
|
|
107
|
+
|
|
108
|
+
def test_error_response(self):
|
|
109
|
+
data = {"error": {"message": "video failed"}}
|
|
110
|
+
with pytest.raises(FlowAPIError, match="Video generation failed"):
|
|
111
|
+
self.client._parse_video_response(data, "test")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class TestClientInit:
|
|
115
|
+
"""Test client initialization."""
|
|
116
|
+
|
|
117
|
+
def test_creates_with_cookies(self):
|
|
118
|
+
client = FlowClient(cookies="SID=abc; HSID=def")
|
|
119
|
+
assert client.cookies == "SID=abc; HSID=def"
|
|
120
|
+
assert client._access_token == ""
|
|
121
|
+
assert client._project_id == ""
|
|
122
|
+
|
|
123
|
+
def test_session_id_format(self):
|
|
124
|
+
client = FlowClient(cookies="test")
|
|
125
|
+
assert client._session_id.startswith(";")
|
|
126
|
+
assert len(client._session_id) > 5
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class TestImagePayload:
|
|
130
|
+
"""Test that generate_image builds correct payload."""
|
|
131
|
+
|
|
132
|
+
@patch.object(FlowClient, '_get_recaptcha_token', return_value="fake-recaptcha-token")
|
|
133
|
+
@patch.object(FlowClient, '_sandbox_request')
|
|
134
|
+
@patch.object(FlowClient, '_ensure_project', return_value="proj-123")
|
|
135
|
+
@patch.object(FlowClient, '_ensure_token')
|
|
136
|
+
def test_payload_structure(self, mock_token, mock_proj, mock_request, mock_captcha):
|
|
137
|
+
mock_resp = MagicMock()
|
|
138
|
+
mock_resp.json.return_value = {"responses": [{"generatedImages": []}]}
|
|
139
|
+
mock_request.return_value = mock_resp
|
|
140
|
+
|
|
141
|
+
client = FlowClient(cookies="test")
|
|
142
|
+
req = GenerateImageRequest(prompt="a cat in space", num_images=2, seed=42)
|
|
143
|
+
client.generate_image(req)
|
|
144
|
+
|
|
145
|
+
call_args = mock_request.call_args
|
|
146
|
+
assert call_args[0][0] == "POST"
|
|
147
|
+
assert "proj-123/flowMedia:batchGenerateImages" in call_args[0][1]
|
|
148
|
+
|
|
149
|
+
payload = call_args[1]["json_payload"]
|
|
150
|
+
assert payload["clientContext"]["projectId"] == "proj-123"
|
|
151
|
+
assert payload["clientContext"]["tool"] == "PINHOLE"
|
|
152
|
+
assert payload["clientContext"]["recaptchaContext"]["token"] == "fake-recaptcha-token"
|
|
153
|
+
assert payload["useNewMedia"] is True
|
|
154
|
+
assert len(payload["requests"]) == 2
|
|
155
|
+
assert payload["requests"][0]["imageModelName"] == "NARWHAL"
|
|
156
|
+
assert payload["requests"][0]["structuredPrompt"]["parts"][0]["text"] == "a cat in space"
|
|
157
|
+
assert payload["requests"][0]["seed"] == 42
|
|
158
|
+
assert payload["requests"][0]["imageAspectRatio"] == "IMAGE_ASPECT_RATIO_LANDSCAPE"
|
|
159
|
+
assert payload["requests"][0]["imageInputs"] == []
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class TestVideoPayload:
|
|
163
|
+
"""Test that generate_video builds correct payload."""
|
|
164
|
+
|
|
165
|
+
@patch.object(FlowClient, '_get_recaptcha_token', return_value="fake-recaptcha-token")
|
|
166
|
+
@patch.object(FlowClient, '_sandbox_request')
|
|
167
|
+
@patch.object(FlowClient, '_ensure_project', return_value="proj-456")
|
|
168
|
+
@patch.object(FlowClient, '_ensure_token')
|
|
169
|
+
def test_payload_structure(self, mock_token, mock_proj, mock_request, mock_captcha):
|
|
170
|
+
mock_resp = MagicMock()
|
|
171
|
+
mock_resp.json.return_value = {"operations": [{"name": "op/1"}]}
|
|
172
|
+
mock_request.return_value = mock_resp
|
|
173
|
+
|
|
174
|
+
client = FlowClient(cookies="test")
|
|
175
|
+
req = GenerateVideoRequest(prompt="a sunset", seed=100)
|
|
176
|
+
client.generate_video(req)
|
|
177
|
+
|
|
178
|
+
call_args = mock_request.call_args
|
|
179
|
+
assert call_args[0][0] == "POST"
|
|
180
|
+
assert "video:batchAsyncGenerateVideoText" in call_args[0][1]
|
|
181
|
+
|
|
182
|
+
payload = call_args[1]["json_payload"]
|
|
183
|
+
assert payload["clientContext"]["projectId"] == "proj-456"
|
|
184
|
+
assert payload["clientContext"]["tool"] == "PINHOLE"
|
|
185
|
+
assert payload["clientContext"]["recaptchaContext"]["token"] == "fake-recaptcha-token"
|
|
186
|
+
assert payload["useV2ModelConfig"] is True
|
|
187
|
+
assert len(payload["requests"]) == 1
|
|
188
|
+
assert payload["requests"][0]["videoModelKey"] == "veo_3_1_t2v_fast_ultra"
|
|
189
|
+
assert payload["requests"][0]["textInput"]["structuredPrompt"]["parts"][0]["text"] == "a sunset"
|
|
190
|
+
assert payload["requests"][0]["aspectRatio"] == "VIDEO_ASPECT_RATIO_LANDSCAPE"
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<system_prompt>
|
|
2
|
+
Bạn là một Đạo diễn Video Quảng Cáo (App Promo Director) kiêm Chuyên gia Copywriter.
|
|
3
|
+
Nhiệm vụ của bạn là lấy [Tên App + Tính năng/USP] do người dùng cung cấp và biến thành một **Kịch bản Video Ngắn (Short Video)** chuẩn AIDA với thời lượng khoảng 30s.
|
|
4
|
+
|
|
5
|
+
### Quy tắc AIDA (Bắt buộc)
|
|
6
|
+
1. **[A] Attention (Hook)**: 3 giây đầu tiên phải đánh thẳng vào nỗi đau hoặc khơi gợi sự tò mò.
|
|
7
|
+
2. **[I] Interest**: 5-10 giây tiếp theo, phóng đại nỗi đau hoặc giới thiệu giải pháp (tên ứng dụng).
|
|
8
|
+
3. **[D] Desire**: 10s tiếp theo, show cụ thể lợi ích lớn nhất (UI/UX của App).
|
|
9
|
+
4. **[A] Action**: 5s cuối, kêu gọi tải app (Call to Action).
|
|
10
|
+
|
|
11
|
+
### Định dạng Output (JSON)
|
|
12
|
+
Bạn PHẢI trả về ĐÚNG định dạng JSON sau để hệ thống tự động sinh Storyboard. Không giải thích thêm.
|
|
13
|
+
|
|
14
|
+
**Hiệu ứng chuyển cảnh (transition)** được hỗ trợ: `fade`, `slideleft`, `slideright`, `circlecrop`, `dissolve`, `wipeleft`, `wiperight`, `none`.
|
|
15
|
+
Mặc định dùng `fade`. Chọn hiệu ứng phù hợp với nhịp điệu kịch bản (VD: fade cho thay đổi cảm xúc, slideleft cho chuyển bước nhanh, dissolve cho nhẹ nhàng/mộng ảo).
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"title": "<Tên chiến dịch video>",
|
|
20
|
+
"target_format": "16:9",
|
|
21
|
+
"duration": 30,
|
|
22
|
+
"character_prompt": "<Mô tả CỰC KỲ CHI TIẾT nhân vật chính bằng tiếng Anh. VD: A 25 year old Asian female fitness coach named Sarah, oval face, brown high ponytail hair, wearing a teal sports bra and black yoga leggings>",
|
|
23
|
+
"character_seed": 123456,
|
|
24
|
+
"scenes": [
|
|
25
|
+
{
|
|
26
|
+
"id": 1,
|
|
27
|
+
"type": "video",
|
|
28
|
+
"duration": 5,
|
|
29
|
+
"script": "<Câu thoại tiếng Việt cho TTS>",
|
|
30
|
+
"prompt": "<Prompt tiếng ANH siêu chi tiết cho hành động/bối cảnh cảnh này. character_prompt sẽ tự động được ghép vào đầu khi render. VD: standing confidently in a modern kitchen, arms crossed, cinematic wide shot, ultra clear, dramatic lighting...>",
|
|
31
|
+
"transition": "fade"
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
Lưu ý:
|
|
37
|
+
- Prompt phải tối ưu cho AI Image/Video generator (Imagen 4 / Veo 3.1).
|
|
38
|
+
- `character_prompt` mô tả nhân vật cố định xuyên suốt — KHÔNG lặp lại trong `prompt` của từng scene.
|
|
39
|
+
- `character_seed` dùng để khóa cứng Seed khi render, đảm bảo nhân vật nhất quán.
|
|
40
|
+
</system_prompt>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<system_prompt>
|
|
2
|
+
Bạn là Chuyên gia "Giải phẫu Video Viral" (Viral Video Analyst).
|
|
3
|
+
Nhiệm vụ của bạn là nhận Transcript của một video ngắn thành công trên YouTube/TikTok, phân tích cấu trúc nhịp điệu (Pacing & Hook) của nó, sau đó **"Cover" lại kịch bản đó** để dùng cho App được chỉ định.
|
|
4
|
+
|
|
5
|
+
### Hướng dẫn "Mimic" (Bắt chước)
|
|
6
|
+
- Bóc tách cấu trúc Hook của video gốc: Họ dùng câu hỏi? Họ liệt kê 3 điều? Họ kể chuyện?
|
|
7
|
+
- Giữ nguyên CẤU TRÚC tâm lý đó để giữ chân người xem.
|
|
8
|
+
- Thay thế hoàn toàn NỘI DUNG bằng sản phẩm/app của user.
|
|
9
|
+
- Tốc độ câu chữ (Pacing) phải chuyển biến tương đương video mẫu.
|
|
10
|
+
|
|
11
|
+
### Định dạng Output (JSON)
|
|
12
|
+
Bạn PHẢI trả về ĐÚNG định dạng JSON sau để hệ thống tự động sinh Storyboard. Không giải thích thêm.
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"title": "<Tên chiến dịch video - Mimic Version>",
|
|
16
|
+
"target_format": "16:9",
|
|
17
|
+
"duration": 30,
|
|
18
|
+
"scenes": [
|
|
19
|
+
{
|
|
20
|
+
"id": 1,
|
|
21
|
+
"type": "video",
|
|
22
|
+
"duration": 5,
|
|
23
|
+
"script": "<Câu thoại tiếng Việt Cover cho TTS>",
|
|
24
|
+
"prompt": "<Prompt tiếng ANH siêu chi tiết miêu tả cảnh quay...>"
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
</system_prompt>
|
|
@@ -133,10 +133,10 @@ Examples:
|
|
|
133
133
|
- Implement navigation between screens
|
|
134
134
|
- Add animations, transitions, loading/empty/error states
|
|
135
135
|
- Wire up UI components (no real API/DB calls)
|
|
136
|
-
Gate: 🧪 USER TEST CHECKPOINT
|
|
137
|
-
→
|
|
138
|
-
→
|
|
139
|
-
→
|
|
136
|
+
Gate: 🧪 USER TEST CHECKPOINT or 🤖 AUTO DEVICE CHECKPOINT
|
|
137
|
+
→ Check `.project-identity` for `autoVerification` flag.
|
|
138
|
+
→ If `false` (default): Stop & block user for manual test (BlockedOnUser=true).
|
|
139
|
+
→ If `true`: AI auto-tests on device (Maestro MCP) & takes UI screenshot. Proceed to Phase C WITHOUT blocking user if build/launch OK.
|
|
140
140
|
```
|
|
141
141
|
|
|
142
142
|
### Phase C: Logic Tasks (Per Feature)
|
|
@@ -147,8 +147,8 @@ Examples:
|
|
|
147
147
|
- Implement business logic, validation
|
|
148
148
|
- Add error handling, retry, offline support
|
|
149
149
|
- Wire up hardware features (camera, GPS, sensors)
|
|
150
|
-
Gate: 🧪
|
|
151
|
-
→
|
|
150
|
+
Gate: 🧪 MANUAL or 🤖 AUTO CHECKPOINT per feature (batch small ones)
|
|
151
|
+
→ Depends on `autoVerification` setting. Auto uses Build & Maestro. Manual waits for user confirmation.
|
|
152
152
|
```
|
|
153
153
|
|
|
154
154
|
### Task Sorting Rule
|
|
@@ -240,6 +240,9 @@ At logical boundaries (after each task, at major milestones), report:
|
|
|
240
240
|
- **What remains** — remaining tasks, known issues
|
|
241
241
|
|
|
242
242
|
Update Symphony task progress with current status.
|
|
243
|
+
**Crucially (Inter-task Flow):**
|
|
244
|
+
- If `.project-identity` has `"autoVerification": true`: Do NOT block the user (`BlockedOnUser=false`) between tasks/batches. Auto-proceed immediately to the next task if auto-verification (Maestro) passes.
|
|
245
|
+
- If `"autoVerification": false` (default): MUST stop and wait for user's manual validation (`BlockedOnUser=true`) before proceeding to the next batch.
|
|
243
246
|
|
|
244
247
|
## Common Mistakes
|
|
245
248
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: skill-creator
|
|
3
|
+
description: "Chuyên gia tạo mới và chỉnh sửa Antigravity Skills. ĐẶC BIỆT: LUÔN tự động đồng bộ chéo (dual-sync) mọi file của skill đang sửa/tạo từ ~/.gemini/antigravity sang thư mục dự án /Users/trungkientn/Dev/NodeJS/main-awf để backup."
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
trigger: conditional
|
|
6
|
+
activation_keywords:
|
|
7
|
+
- "cập nhật skill"
|
|
8
|
+
- "tạo skill mới"
|
|
9
|
+
- "viết skill"
|
|
10
|
+
- "chỉnh sửa skill"
|
|
11
|
+
- "bổ sung skill"
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# 🛠️ Skill Creator (with Auto-Sync to main-awf)
|
|
15
|
+
|
|
16
|
+
**Purpose**: Hỗ trợ người dùng thiết kế, lập trình và tinh chỉnh các Antigravity Skills theo chuẩn.
|
|
17
|
+
**Core Rule**: Mọi tác vụ trên skill bất kỳ (tạo mới, thay đổi nội dung thư mục) BẮT BUỘC phải được nhân bản tự động sang repo `main-awf` ngay lập tức để giữ đồng bộ phiên bản mà không cần user nhắc nhở.
|
|
18
|
+
|
|
19
|
+
## 🚀 Luồng tương tác (Workflow)
|
|
20
|
+
|
|
21
|
+
### Bước 1: Thu thập yêu cầu (Requirements)
|
|
22
|
+
- Nắm bắt thông tin về Skill user muốn tạo/sửa: Tên skill, công dụng cốt lõi, từ khoá kích hoạt (triggers), và các script chuyên biệt (Python, Bash, Node) nếu có.
|
|
23
|
+
- Nếu là skill mới, định hình các thư mục cần thiết (vd: `scripts/`, `templates/`, `resources/`).
|
|
24
|
+
|
|
25
|
+
### Bước 2: Thiết kế và Lập trình (Implementation)
|
|
26
|
+
- Tạo thư mục gốc của skill tại `~/.gemini/antigravity/skills/<skill-name>`.
|
|
27
|
+
- Khởi tạo (hoặc cập nhật) file điều hướng cốt lõi `SKILL.md` tuân thủ YAML frontmatter.
|
|
28
|
+
- Lập trình các script logic chuyên biệt và đặt đúng vào các thư mục tương ứng bên trong.
|
|
29
|
+
|
|
30
|
+
### Bước 3: ĐỒNG BỘ HOÁ BẮT BUỘC (The Mandatory Sync)
|
|
31
|
+
Ngay sau khi thao tác sinh file nội bộ thư mục `.gemini` hoàn tất (và trước khi báo cáo kết thúc cho user), AI **BẮT BUỘC CHẠY BASH COMMAND ĐỂ BACKUP SANG MAIN-AWF**. Đây là nguyên tắc sống còn của skill này:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# 1. Khởi tạo thư mục đích tại main-awf để tránh lỗi NotFound
|
|
35
|
+
mkdir -p /Users/trungkientn/Dev/NodeJS/main-awf/skills/<skill-name>
|
|
36
|
+
|
|
37
|
+
# 2. Quét và chép đè mọi thứ thuộc skill từ Antigravity nội bộ sang repo Node
|
|
38
|
+
cp -R ~/.gemini/antigravity/skills/<skill-name>/* /Users/trungkientn/Dev/NodeJS/main-awf/skills/<skill-name>/
|
|
39
|
+
```
|
|
40
|
+
*(AI chú ý: Nhớ thay chữ `<skill-name>` bằng đúng tên thư mục skill đang xử lý. Không được bỏ sót thư mục con như scripts).*
|
|
41
|
+
|
|
42
|
+
### Bước 4: Nghiệm thu (Review)
|
|
43
|
+
- Báo cáo cho người dùng tình trạng tạo/sửa skill.
|
|
44
|
+
- Xác nhận bằng định dạng Check-box rằng: `✅ Đã đồng bộ an toàn sang repo main-awf`.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: spm-build-analysis
|
|
3
|
+
description: Phân tích Swift Package Manager dependencies, package plugins, tình trạng trùng lặp module và các vấn đề CI làm chậm quá trình Xcode build. Sử dụng khi nghi ngờ SPM packages, plugins hoặc cấu trúc dependency làm chậm quá trình clean/incremental build, gặp lỗi SPM build chậm, resolve packages lâu, module bị build lặp, lỗi circular dependencies, package quá lớn cần chia nhỏ, hoặc cần tối ưu modularization.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# SPM Build Analysis
|
|
7
|
+
|
|
8
|
+
Use this skill when package structure, plugins, or dependency configuration are likely contributing to slow Xcode builds.
|
|
9
|
+
|
|
10
|
+
## Core Rules
|
|
11
|
+
|
|
12
|
+
- Treat package analysis as evidence gathering first, not a mandate to replace dependencies.
|
|
13
|
+
- Separate package-graph issues from project-setting issues.
|
|
14
|
+
- Do not rewrite package manifests or dependency sources without explicit approval.
|
|
15
|
+
|
|
16
|
+
## What To Inspect
|
|
17
|
+
|
|
18
|
+
- `Package.swift` and `Package.resolved`
|
|
19
|
+
- local packages vs remote packages
|
|
20
|
+
- package plugin and build-tool usage
|
|
21
|
+
- binary target footprint
|
|
22
|
+
- dependency layering, repeated imports, and potential cycles
|
|
23
|
+
- build logs or timing summaries that show package-related work
|
|
24
|
+
|
|
25
|
+
## Verification Before Recommending
|
|
26
|
+
|
|
27
|
+
Before including any local package in a recommendation, verify that it is actually part of the project's dependency graph. A `Vendor/` directory may contain packages that are not linked to any target.
|
|
28
|
+
|
|
29
|
+
- Check `project.pbxproj` for `XCLocalSwiftPackageReference` entries that reference the package path.
|
|
30
|
+
- Check `XCSwiftPackageProductDependency` entries to confirm the package's product is linked to at least one target.
|
|
31
|
+
- If a local package exists on disk but is not referenced in the project, do not include it in build-time recommendations.
|
|
32
|
+
|
|
33
|
+
When recommending version pins for branch-tracked dependencies:
|
|
34
|
+
|
|
35
|
+
- Use the helper script to scan all branch-pinned dependencies at once:
|
|
36
|
+
```bash
|
|
37
|
+
python3 scripts/check_spm_pins.py --project App.xcodeproj
|
|
38
|
+
```
|
|
39
|
+
This checks `git ls-remote --tags` for each branch-pinned package and reports which have tags available for pinning.
|
|
40
|
+
- If no tags exist, recommend pinning to a specific commit revision hash for determinism instead.
|
|
41
|
+
- Note which packages are branch-pinned because the upstream simply has no tags, versus packages that have tags but are intentionally tracking a branch.
|
|
42
|
+
|
|
43
|
+
## Focus Areas
|
|
44
|
+
|
|
45
|
+
- package graph shape and how much work changes trigger downstream
|
|
46
|
+
- plugin overhead during local development and CI
|
|
47
|
+
- checkout or fetch cost signals that show up in clean environments
|
|
48
|
+
- configuration drift that forces duplicate module builds
|
|
49
|
+
- risks from package targets that use different macros or options while sharing dependencies
|
|
50
|
+
- dependency direction violations (features depending on each other instead of shared lower layers)
|
|
51
|
+
- circular dependencies between modules (extract shared contracts into a protocol module)
|
|
52
|
+
- oversized modules (200+ files) that widen incremental rebuild scope
|
|
53
|
+
- umbrella modules using `@_exported import` that create hidden dependency chains
|
|
54
|
+
- missing interface/implementation separation that blocks build parallelism
|
|
55
|
+
- test targets depending on the app target instead of the module under test
|
|
56
|
+
- Swift macro rebuild cascading: heavy use of Swift macros (e.g., TCA, swift-syntax-based libraries) can cause a trivial source change to cascade into near-full rebuilds because macro expansion invalidates downstream modules
|
|
57
|
+
- `swift-syntax` building universally (all architectures) when no prebuilt binary is available, adding significant clean-build overhead
|
|
58
|
+
- multi-platform build multiplication: adding a secondary platform target (e.g., watchOS) can cause shared SPM packages to build multiple times (e.g., iOS arm64, iOS x86_64, watchOS arm64), multiplying `SwiftCompile`, `SwiftEmitModule`, and `ScanDependencies` tasks
|
|
59
|
+
|
|
60
|
+
## Modular SDK Migration Caveat
|
|
61
|
+
|
|
62
|
+
Migrating a dependency from a monolithic target to a modular multi-target SDK (e.g., replacing one umbrella library with separate Core, RUM, Logs, Trace modules) does not automatically reduce build time. Modular targets increase the number of `SwiftCompile`, `SwiftEmitModule`, and `ScanDependencies` tasks because each target must be compiled, scanned, and emit its module independently. The build-time trade-off depends on the project's parallelism headroom and how many of the modular targets are actually needed.
|
|
63
|
+
|
|
64
|
+
When considering a modular SDK migration:
|
|
65
|
+
|
|
66
|
+
- Compare the total `SwiftCompile` task count before and after.
|
|
67
|
+
- Benchmark both configurations before recommending the migration for build speed.
|
|
68
|
+
- If the motivation is API surface reduction (importing only what you use), note that build time may stay flat or increase while import hygiene improves.
|
|
69
|
+
- Only recommend modular SDK migration for build speed when the project currently compiles large portions of the monolithic SDK that it does not use, and the modular alternative lets it skip those unused portions entirely.
|
|
70
|
+
|
|
71
|
+
## Explicit Module Dependency Angle
|
|
72
|
+
|
|
73
|
+
When the same module appears multiple times in timing output, investigate whether different package or target options are forcing extra module variants. Uniform options often matter more than shaving a small amount of source code.
|
|
74
|
+
|
|
75
|
+
## Reporting Format
|
|
76
|
+
|
|
77
|
+
For each finding, include:
|
|
78
|
+
|
|
79
|
+
- evidence
|
|
80
|
+
- affected package or plugin
|
|
81
|
+
- likely clean-build vs incremental-build impact
|
|
82
|
+
- CI impact if relevant
|
|
83
|
+
- estimated impact
|
|
84
|
+
- approval requirement
|
|
85
|
+
|
|
86
|
+
If the main problem is not package-related, hand off to [`xcode-project-analyzer`](../xcode-project-analyzer/SKILL.md) or [`xcode-compilation-analyzer`](../xcode-compilation-analyzer/SKILL.md) by reading the target skill's SKILL.md and applying its workflow to the same project context.
|
|
87
|
+
|
|
88
|
+
## Additional Resources
|
|
89
|
+
|
|
90
|
+
- For the detailed audit checklist, see [references/spm-analysis-checks.md](references/spm-analysis-checks.md)
|
|
91
|
+
- For the shared recommendation structure, see [references/recommendation-format.md](references/recommendation-format.md)
|
|
92
|
+
- For source citations, see [references/build-optimization-sources.md](references/build-optimization-sources.md)
|