agentic-dev 0.2.11 → 0.2.13
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/README.md +72 -54
- package/bin/agentic-dev.mjs +162 -11
- package/lib/github.mjs +246 -0
- package/lib/orchestration-assets.mjs +249 -0
- package/lib/scaffold.mjs +89 -0
- package/package.json +8 -19
- package/.dockerignore +0 -8
- package/.env.example +0 -50
- package/.gitignore +0 -16
- package/AGENTS.md +0 -86
- package/SDD_SKILL.md +0 -589
- package/compose.yml +0 -206
- package/infra/compose/.env.dev.example +0 -28
- package/infra/compose/.env.prod.example +0 -29
- package/infra/compose/README.md +0 -35
- package/infra/compose/dev.yml +0 -125
- package/infra/compose/prod.yml +0 -126
- package/infra/terraform/README.md +0 -34
- package/infra/terraform/aws/data/.terraform.lock.hcl +0 -25
- package/infra/terraform/aws/data/README.md +0 -18
- package/infra/terraform/aws/data/main.tf +0 -147
- package/infra/terraform/aws/data/outputs.tf +0 -14
- package/infra/terraform/aws/data/variables.tf +0 -57
- package/infra/terraform/aws/data/versions.tf +0 -10
- package/infra/terraform/aws/domain/.terraform.lock.hcl +0 -25
- package/infra/terraform/aws/domain/README.md +0 -20
- package/infra/terraform/aws/domain/env/dev.tfvars.example +0 -6
- package/infra/terraform/aws/domain/env/prod.tfvars.example +0 -7
- package/infra/terraform/aws/domain/main.tf +0 -149
- package/infra/terraform/aws/domain/outputs.tf +0 -29
- package/infra/terraform/aws/domain/variables.tf +0 -58
- package/infra/terraform/aws/domain/versions.tf +0 -10
- package/infra/terraform/openstack/README.md +0 -38
- package/infra/terraform/openstack/dev/.terraform.lock.hcl +0 -24
- package/infra/terraform/openstack/dev/README.md +0 -18
- package/infra/terraform/openstack/dev/main.tf +0 -49
- package/infra/terraform/openstack/dev/providers.tf +0 -15
- package/infra/terraform/openstack/dev/terraform.tfvars.example +0 -54
- package/infra/terraform/openstack/dev/variables.tf +0 -210
- package/infra/terraform/openstack/dev/versions.tf +0 -10
- package/infra/terraform/openstack/modules/environment_host/main.tf +0 -143
- package/infra/terraform/openstack/modules/environment_host/outputs.tf +0 -25
- package/infra/terraform/openstack/modules/environment_host/templates/docker-host-user-data.sh.tftpl +0 -40
- package/infra/terraform/openstack/modules/environment_host/variables.tf +0 -145
- package/infra/terraform/openstack/modules/environment_host/versions.tf +0 -7
- package/infra/terraform/openstack/prod/.terraform.lock.hcl +0 -24
- package/infra/terraform/openstack/prod/README.md +0 -18
- package/infra/terraform/openstack/prod/main.tf +0 -49
- package/infra/terraform/openstack/prod/providers.tf +0 -15
- package/infra/terraform/openstack/prod/terraform.tfvars.example +0 -55
- package/infra/terraform/openstack/prod/variables.tf +0 -210
- package/infra/terraform/openstack/prod/versions.tf +0 -10
- package/infra/terraform/openstack/server/.terraform.lock.hcl +0 -45
- package/infra/terraform/openstack/server/README.md +0 -47
- package/infra/terraform/openstack/server/main.tf +0 -161
- package/infra/terraform/openstack/server/outputs.tf +0 -30
- package/infra/terraform/openstack/server/providers.tf +0 -30
- package/infra/terraform/openstack/server/templates/server-user-data.sh.tftpl +0 -50
- package/infra/terraform/openstack/server/variables.tf +0 -233
- package/infra/terraform/openstack/server/zz_aspace.auto.tfvars.example.json +0 -29
- package/pnpm-workspace.yaml +0 -2
- package/scripts/dev/audit_sdd_build_ast.py +0 -277
- package/sdd/01_planning/01_feature/INDEX.md +0 -16
- package/sdd/01_planning/01_feature/README.md +0 -76
- package/sdd/01_planning/01_feature/alerts_feature_spec.md +0 -55
- package/sdd/01_planning/01_feature/auth_feature_spec.md +0 -57
- package/sdd/01_planning/01_feature/catalog_feature_spec.md +0 -61
- package/sdd/01_planning/01_feature/fulfillment_feature_spec.md +0 -58
- package/sdd/01_planning/01_feature/health_feature_spec.md +0 -52
- package/sdd/01_planning/01_feature/inventory_feature_spec.md +0 -60
- package/sdd/01_planning/01_feature/order_feature_spec.md +0 -63
- package/sdd/01_planning/01_feature/shipping_feature_spec.md +0 -55
- package/sdd/01_planning/01_feature/support_feature_spec.md +0 -53
- package/sdd/01_planning/01_feature/user_feature_spec.md +0 -54
- package/sdd/01_planning/02_screen/INDEX.md +0 -13
- package/sdd/01_planning/02_screen/README.md +0 -41
- package/sdd/01_planning/02_screen/admin_screen_spec.pdf +0 -0
- package/sdd/01_planning/02_screen/assets/README.md +0 -16
- package/sdd/01_planning/02_screen/assets/example/README.md +0 -13
- package/sdd/01_planning/02_screen/landing_screen_spec.pdf +0 -0
- package/sdd/01_planning/02_screen/mobile_screen_spec.pdf +0 -0
- package/sdd/01_planning/02_screen/web_screen_spec.pdf +0 -0
- package/sdd/01_planning/03_architecture/INDEX.md +0 -9
- package/sdd/01_planning/03_architecture/README.md +0 -25
- package/sdd/01_planning/03_architecture/architecture_document_structure.md +0 -77
- package/sdd/01_planning/03_architecture/backend/README.md +0 -10
- package/sdd/01_planning/03_architecture/frontend/README.md +0 -12
- package/sdd/01_planning/03_architecture/infra/README.md +0 -10
- package/sdd/01_planning/03_architecture/tech-research/README.md +0 -4
- package/sdd/01_planning/03_architecture/templates_system_architecture.md +0 -84
- package/sdd/01_planning/04_data/INDEX.md +0 -4
- package/sdd/01_planning/04_data/README.md +0 -10
- package/sdd/01_planning/04_data/templates_data_modeling.md +0 -119
- package/sdd/01_planning/05_api/README.md +0 -12
- package/sdd/01_planning/05_api/templates_api_contract.md +0 -90
- package/sdd/01_planning/06_iac/README.md +0 -11
- package/sdd/01_planning/06_iac/templates_runtime_and_cicd_baseline.md +0 -46
- package/sdd/01_planning/07_integration/README.md +0 -11
- package/sdd/01_planning/07_integration/templates_frontend_api_integration.md +0 -46
- package/sdd/01_planning/08_nonfunctional/README.md +0 -7
- package/sdd/01_planning/09_security/README.md +0 -7
- package/sdd/01_planning/10_test/README.md +0 -12
- package/sdd/01_planning/10_test/templates_test_strategy.md +0 -60
- package/sdd/01_planning/INDEX.md +0 -19
- package/sdd/01_planning/README.md +0 -17
- package/sdd/02_plan/01_feature/README.md +0 -34
- package/sdd/02_plan/01_feature/_feature_todo_template.md +0 -29
- package/sdd/02_plan/02_screen/INDEX.md +0 -19
- package/sdd/02_plan/02_screen/README.md +0 -39
- package/sdd/02_plan/02_screen/_screen_todo_template.md +0 -60
- package/sdd/02_plan/03_architecture/README.md +0 -23
- package/sdd/02_plan/03_architecture/architecture_document_governance.md +0 -40
- package/sdd/02_plan/03_architecture/build_ast_runtime_tree_governance.md +0 -53
- package/sdd/02_plan/03_architecture/repository_governance.md +0 -39
- package/sdd/02_plan/03_architecture/runtime_and_structure_governance.md +0 -38
- package/sdd/02_plan/03_architecture/templates-hexagonal-template-architecture.md +0 -9
- package/sdd/02_plan/03_architecture/toolchain_governance.md +0 -98
- package/sdd/02_plan/04_data/README.md +0 -5
- package/sdd/02_plan/05_api/README.md +0 -5
- package/sdd/02_plan/06_iac/README.md +0 -11
- package/sdd/02_plan/06_iac/dev_runtime_delivery.md +0 -36
- package/sdd/02_plan/06_iac/template_runtime_delivery.md +0 -50
- package/sdd/02_plan/07_integration/README.md +0 -5
- package/sdd/02_plan/07_integration/frontend_live_integration.md +0 -31
- package/sdd/02_plan/08_nonfunctional/README.md +0 -5
- package/sdd/02_plan/08_nonfunctional/repository_hygiene.md +0 -26
- package/sdd/02_plan/09_security/README.md +0 -5
- package/sdd/02_plan/10_test/README.md +0 -11
- package/sdd/02_plan/10_test/regression_verification.md +0 -39
- package/sdd/02_plan/10_test/templates/README.md +0 -8
- package/sdd/02_plan/10_test/templates/ui_parity_web_contract.template.yaml +0 -23
- package/sdd/02_plan/10_test/verification_strategy.md +0 -43
- package/sdd/02_plan/99_generated/from_planning/ui_parity/.gitkeep +0 -1
- package/sdd/02_plan/README.md +0 -40
- package/sdd/03_build/01_feature/README.md +0 -20
- package/sdd/03_build/01_feature/domain/README.md +0 -3
- package/sdd/03_build/01_feature/domain/account_and_access.md +0 -20
- package/sdd/03_build/01_feature/domain/catalog_and_inventory.md +0 -20
- package/sdd/03_build/01_feature/domain/ordering_and_fulfillment.md +0 -21
- package/sdd/03_build/01_feature/domain/support_and_observability.md +0 -21
- package/sdd/03_build/01_feature/domain_surfaces.md +0 -28
- package/sdd/03_build/01_feature/service/README.md +0 -3
- package/sdd/03_build/01_feature/service/admin_surface.md +0 -15
- package/sdd/03_build/01_feature/service/landing_surface.md +0 -13
- package/sdd/03_build/01_feature/service/mobile_surface.md +0 -14
- package/sdd/03_build/01_feature/service/web_surface.md +0 -14
- package/sdd/03_build/02_screen/README.md +0 -25
- package/sdd/03_build/02_screen/_screen_build_template.md +0 -26
- package/sdd/03_build/02_screen/admin/README.md +0 -5
- package/sdd/03_build/02_screen/landing/README.md +0 -5
- package/sdd/03_build/02_screen/mobile/README.md +0 -5
- package/sdd/03_build/02_screen/web/README.md +0 -5
- package/sdd/03_build/03_architecture/README.md +0 -10
- package/sdd/03_build/03_architecture/architecture_document_governance.md +0 -30
- package/sdd/03_build/03_architecture/build_ast_runtime_tree_governance.md +0 -24
- package/sdd/03_build/03_architecture/repository_governance.md +0 -18
- package/sdd/03_build/03_architecture/toolchain_governance.md +0 -36
- package/sdd/03_build/06_iac/README.md +0 -3
- package/sdd/03_build/06_iac/dev_runtime_delivery.md +0 -10
- package/sdd/03_build/06_iac/template_runtime_delivery.md +0 -49
- package/sdd/03_build/07_integration/README.md +0 -3
- package/sdd/03_build/07_integration/frontend_live_integration.md +0 -11
- package/sdd/03_build/08_nonfunctional/README.md +0 -3
- package/sdd/03_build/08_nonfunctional/repository_hygiene.md +0 -10
- package/sdd/03_build/10_test/README.md +0 -9
- package/sdd/03_build/10_test/regression_verification.md +0 -16
- package/sdd/03_build/10_test/verification_harness.md +0 -11
- package/sdd/03_build/README.md +0 -35
- package/sdd/03_verify/01_feature/README.md +0 -5
- package/sdd/03_verify/01_feature/domain_verification.md +0 -14
- package/sdd/03_verify/01_feature/service_verification.md +0 -22
- package/sdd/03_verify/02_screen/README.md +0 -6
- package/sdd/03_verify/02_screen/_screen_verify_template.md +0 -20
- package/sdd/03_verify/02_screen/admin/README.md +0 -4
- package/sdd/03_verify/02_screen/landing/README.md +0 -4
- package/sdd/03_verify/02_screen/mobile/README.md +0 -4
- package/sdd/03_verify/02_screen/web/README.md +0 -4
- package/sdd/03_verify/03_architecture/README.md +0 -10
- package/sdd/03_verify/03_architecture/architecture_document_governance.md +0 -15
- package/sdd/03_verify/03_architecture/build_ast_runtime_tree_governance.md +0 -28
- package/sdd/03_verify/03_architecture/repository_governance.md +0 -16
- package/sdd/03_verify/03_architecture/toolchain_governance.md +0 -58
- package/sdd/03_verify/06_iac/README.md +0 -3
- package/sdd/03_verify/06_iac/dev_runtime_delivery.md +0 -10
- package/sdd/03_verify/06_iac/template_runtime_delivery.md +0 -42
- package/sdd/03_verify/07_integration/README.md +0 -3
- package/sdd/03_verify/07_integration/frontend_live_integration.md +0 -16
- package/sdd/03_verify/08_nonfunctional/README.md +0 -3
- package/sdd/03_verify/08_nonfunctional/repository_hygiene.md +0 -14
- package/sdd/03_verify/10_test/README.md +0 -9
- package/sdd/03_verify/10_test/regression_verification.md +0 -16
- package/sdd/03_verify/10_test/ui_parity/README.md +0 -4
- package/sdd/03_verify/10_test/ui_parity/loop_runs/.gitkeep +0 -0
- package/sdd/03_verify/10_test/ui_parity/reference/.gitkeep +0 -0
- package/sdd/03_verify/10_test/ui_parity/staged_runs/.gitkeep +0 -0
- package/sdd/03_verify/10_test/verification_harness.md +0 -17
- package/sdd/03_verify/README.md +0 -22
- package/sdd/05_operate/01_runbooks/.gitkeep +0 -1
- package/sdd/05_operate/01_runbooks/README.md +0 -4
- package/sdd/05_operate/02_delivery_status/README.md +0 -4
- package/sdd/05_operate/02_delivery_status/service_status.md +0 -16
- package/sdd/05_operate/README.md +0 -12
- package/sdd/99_toolchain/01_automation/.gitkeep +0 -1
- package/sdd/99_toolchain/01_automation/README.md +0 -76
- package/sdd/99_toolchain/01_automation/agentic-dev/analyze_proof_results.py +0 -132
- package/sdd/99_toolchain/01_automation/agentic-dev/analyze_route_gap.py +0 -85
- package/sdd/99_toolchain/01_automation/agentic-dev/assets/repo-contract.template.json +0 -75
- package/sdd/99_toolchain/01_automation/agentic-dev/bootstrap_frontend_parity.sh +0 -84
- package/sdd/99_toolchain/01_automation/agentic-dev/init_frontend_parity.sh +0 -33
- package/sdd/99_toolchain/01_automation/agentic-dev/init_repo_contract.sh +0 -51
- package/sdd/99_toolchain/01_automation/agentic-dev/repo-contract.json +0 -76
- package/sdd/99_toolchain/01_automation/agentic-dev/resolve_frontend_target.py +0 -52
- package/sdd/99_toolchain/01_automation/agentic-dev/resolve_repo_contract.py +0 -56
- package/sdd/99_toolchain/01_automation/agentic-dev/run_frontend_target.sh +0 -100
- package/sdd/99_toolchain/01_automation/agentic-dev/run_repo_phase.sh +0 -140
- package/sdd/99_toolchain/01_automation/agentic-dev/validate_json_schema.py +0 -39
- package/sdd/99_toolchain/01_automation/agentic-parity-harness-design.md +0 -291
- package/sdd/99_toolchain/01_automation/assets/admin_screen_capture/dashboard.png +0 -0
- package/sdd/99_toolchain/01_automation/assets/admin_screen_capture/login.png +0 -0
- package/sdd/99_toolchain/01_automation/assets/admin_screen_capture/queue.png +0 -0
- package/sdd/99_toolchain/01_automation/assets/admin_screen_capture/support.png +0 -0
- package/sdd/99_toolchain/01_automation/assets/landing_screen_capture/home.png +0 -0
- package/sdd/99_toolchain/01_automation/assets/landing_screen_capture/login.png +0 -0
- package/sdd/99_toolchain/01_automation/assets/landing_screen_capture/workspace.png +0 -0
- package/sdd/99_toolchain/01_automation/assets/mobile_screen_capture/dashboard.png +0 -0
- package/sdd/99_toolchain/01_automation/assets/mobile_screen_capture/fulfillment.png +0 -0
- package/sdd/99_toolchain/01_automation/assets/mobile_screen_capture/login.png +0 -0
- package/sdd/99_toolchain/01_automation/assets/web_screen_capture/dashboard.png +0 -0
- package/sdd/99_toolchain/01_automation/assets/web_screen_capture/login.png +0 -0
- package/sdd/99_toolchain/01_automation/assets/web_screen_capture/orders.png +0 -0
- package/sdd/99_toolchain/01_automation/build_asset_recipes.py +0 -10
- package/sdd/99_toolchain/01_automation/build_screen_spec_pdf.py +0 -427
- package/sdd/99_toolchain/01_automation/capture_screen_assets.mjs +0 -148
- package/sdd/99_toolchain/01_automation/harness-layout.md +0 -34
- package/sdd/99_toolchain/01_automation/parity-execution-tooling-design.md +0 -319
- package/sdd/99_toolchain/01_automation/playwright_exactness_manifest.py +0 -21
- package/sdd/99_toolchain/01_automation/run_playwright_exactness.py +0 -87
- package/sdd/99_toolchain/01_automation/screen_spec_manifest.py +0 -321
- package/sdd/99_toolchain/01_automation/spec_asset_builder.py +0 -274
- package/sdd/99_toolchain/01_automation/ui-contract-projection.md +0 -79
- package/sdd/99_toolchain/01_automation/ui-parity/README.md +0 -60
- package/sdd/99_toolchain/01_automation/ui-parity/cli/extract-reference-pages.mjs +0 -2
- package/sdd/99_toolchain/01_automation/ui-parity/cli/materialize-reference-assets.mjs +0 -58
- package/sdd/99_toolchain/01_automation/ui-parity/cli/normalize-reference-assets.mjs +0 -2
- package/sdd/99_toolchain/01_automation/ui-parity/cli/route-gap-report.mjs +0 -187
- package/sdd/99_toolchain/01_automation/ui-parity/cli/run-proof.mjs +0 -50
- package/sdd/99_toolchain/01_automation/ui-parity/cli/scaffold-contract.mjs +0 -62
- package/sdd/99_toolchain/01_automation/ui-parity/cli/upload-parity1.mjs +0 -2
- package/sdd/99_toolchain/01_automation/ui-parity/contracts/collector-metadata.schema.json +0 -33
- package/sdd/99_toolchain/01_automation/ui-parity/contracts/proof-result.schema.json +0 -76
- package/sdd/99_toolchain/01_automation/ui-parity/contracts/route-gap-report.schema.json +0 -95
- package/sdd/99_toolchain/01_automation/ui-parity/core/capture-runner.mjs +0 -55
- package/sdd/99_toolchain/01_automation/ui-parity/core/load-adapter.mjs +0 -25
- package/sdd/99_toolchain/01_automation/ui-parity/core/load-contract.mjs +0 -81
- package/sdd/99_toolchain/01_automation/ui-parity/core/paths.mjs +0 -23
- package/sdd/99_toolchain/01_automation/ui-parity/core/proof-runner.mjs +0 -255
- package/sdd/99_toolchain/01_automation/ui-parity/interfaces/ui-parity-artifact-layout.md +0 -23
- package/sdd/99_toolchain/01_automation/ui-parity/interfaces/ui-parity-proof-interface.md +0 -60
- package/sdd/99_toolchain/01_automation/ui-parity/interfaces/ui-parity-route-gap-interface.md +0 -82
- package/sdd/99_toolchain/01_automation/ui-parity/runtime/playwright-runtime.mjs +0 -16
- package/sdd/99_toolchain/01_automation/ui-parity/runtime/static-runtime.mjs +0 -6
- package/sdd/99_toolchain/02_policies/.gitkeep +0 -1
- package/sdd/99_toolchain/02_policies/build-ast-governance-policy.md +0 -22
- package/sdd/99_toolchain/02_policies/compose-runtime-baseline-policy.md +0 -24
- package/sdd/99_toolchain/02_policies/convention-storage-policy.md +0 -26
- package/sdd/99_toolchain/02_policies/main-push-before-dev-deploy-policy.md +0 -27
- package/sdd/99_toolchain/02_policies/regression-verification-policy.md +0 -22
- package/sdd/99_toolchain/03_templates/.gitkeep +0 -1
- package/sdd/99_toolchain/03_templates/asset_recipe_manifest.example.py +0 -38
- package/sdd/99_toolchain/03_templates/generated_assets/README.md +0 -11
- package/sdd/99_toolchain/03_templates/generated_assets/example-brand-lockup.svg +0 -3
- package/sdd/99_toolchain/03_templates/generated_assets/example-brand-mark.svg +0 -3
- package/sdd/99_toolchain/03_templates/generated_assets/example-brand-wordmark.svg +0 -3
- package/sdd/99_toolchain/03_templates/playwright_exactness_manifest.example.py +0 -21
- package/sdd/99_toolchain/README.md +0 -23
- package/sdd/README.md +0 -21
|
@@ -1,321 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
ROOT = Path(__file__).resolve().parents[3]
|
|
7
|
-
SCREEN_DIR = ROOT / "sdd/01_planning/02_screen"
|
|
8
|
-
ASSET_DIR = ROOT / "sdd/99_toolchain/01_automation/assets"
|
|
9
|
-
DESKTOP_CAPTURE_POLICY = {
|
|
10
|
-
"reference_visible_area": "1710x951",
|
|
11
|
-
"default_viewport": "1690x940",
|
|
12
|
-
"capture_mode": "viewport-first",
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def screen(
|
|
17
|
-
*,
|
|
18
|
-
code: str,
|
|
19
|
-
name: str,
|
|
20
|
-
route: str,
|
|
21
|
-
access: str,
|
|
22
|
-
features: list[str],
|
|
23
|
-
asset: str,
|
|
24
|
-
requires_auth: bool,
|
|
25
|
-
callouts: list[tuple[tuple[float, float], str, str]],
|
|
26
|
-
crop_box: tuple[int, int, int, int] | None = None,
|
|
27
|
-
segments: list[dict] | None = None,
|
|
28
|
-
) -> dict:
|
|
29
|
-
item = {
|
|
30
|
-
"code": code,
|
|
31
|
-
"name": name,
|
|
32
|
-
"route": route,
|
|
33
|
-
"access": access,
|
|
34
|
-
"features": features,
|
|
35
|
-
"asset": asset,
|
|
36
|
-
"requires_auth": requires_auth,
|
|
37
|
-
"callouts": callouts,
|
|
38
|
-
}
|
|
39
|
-
if crop_box:
|
|
40
|
-
item["crop_box"] = crop_box
|
|
41
|
-
if segments:
|
|
42
|
-
item["segments"] = segments
|
|
43
|
-
return item
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
SCREEN_MANIFESTS = {
|
|
47
|
-
"web": {
|
|
48
|
-
"title": "Templates Web Screen Spec",
|
|
49
|
-
"service_label": "web",
|
|
50
|
-
"output": str(SCREEN_DIR / "web_screen_spec.pdf"),
|
|
51
|
-
"asset_dir": str(ASSET_DIR / "web_screen_capture"),
|
|
52
|
-
"base_url": "http://127.0.0.1:3001",
|
|
53
|
-
"api_base": "http://127.0.0.1:8000/api/v1",
|
|
54
|
-
"storage_key": "web.auth.token",
|
|
55
|
-
"capture_policy": DESKTOP_CAPTURE_POLICY,
|
|
56
|
-
"trim_background": False,
|
|
57
|
-
"cover_note": "첫 페이지는 web 화면코드 인덱스다. 이후 페이지는 좌측 화면 캡처와 우측 번호형 기능 테이블을 함께 제공한다.",
|
|
58
|
-
"source_refs": [
|
|
59
|
-
{"label": "implementation", "path": "client/web/src/app/App.tsx"},
|
|
60
|
-
{"label": "implementation", "path": "client/web/src/pages"},
|
|
61
|
-
{"label": "capture assets", "path": "sdd/99_toolchain/01_automation/assets/web_screen_capture"},
|
|
62
|
-
],
|
|
63
|
-
"screens": [
|
|
64
|
-
screen(
|
|
65
|
-
code="WEB-S001",
|
|
66
|
-
name="Web Login",
|
|
67
|
-
route="/login",
|
|
68
|
-
access="public",
|
|
69
|
-
features=["AUT-F001", "AUT-F002"],
|
|
70
|
-
asset="login.png",
|
|
71
|
-
requires_auth=False,
|
|
72
|
-
callouts=[
|
|
73
|
-
((0.25, 0.31), "서비스 소개 카피", "실제 API 인증 흐름과 로그인 이후 앱 셸 진입 방식을 좌측 설명 영역에서 안내한다."),
|
|
74
|
-
((0.78, 0.21), "Sign in 헤더", "web 로그인 목적과 예시 계정 프리셋 상태를 보여준다."),
|
|
75
|
-
((0.78, 0.38), "자격 증명 입력", "이메일과 비밀번호를 입력하고 오류 상태를 같은 카드 안에서 확인한다."),
|
|
76
|
-
((0.78, 0.48), "Continue CTA", "인증 성공 시 저장된 토큰으로 `auth/me`를 다시 호출한 뒤 보호 라우트로 이동한다."),
|
|
77
|
-
],
|
|
78
|
-
),
|
|
79
|
-
screen(
|
|
80
|
-
code="WEB-S002",
|
|
81
|
-
name="Web Dashboard",
|
|
82
|
-
route="/",
|
|
83
|
-
access="protected",
|
|
84
|
-
features=["AUT-F002", "ORD-F001"],
|
|
85
|
-
asset="dashboard.png",
|
|
86
|
-
requires_auth=True,
|
|
87
|
-
callouts=[
|
|
88
|
-
((0.17, 0.09), "상단 앱 셸", "브랜드, Overview/Orders 전환, 검색, 알림, 사용자 액션을 한 줄 헤더로 제공한다."),
|
|
89
|
-
((0.26, 0.25), "운영 통계 카드", "주문 overview 핵심 지표와 로딩 또는 오류 상태를 상단 카드 묶음으로 보여준다."),
|
|
90
|
-
((0.34, 0.60), "Recent activity 표", "최근 주문 이벤트를 주문 ID, 일자, customer, 상태 기준으로 읽는 메인 데이터 테이블이다."),
|
|
91
|
-
((0.83, 0.55), "Selected detail 패널", "선택 주문의 product, customer, status, amount를 보조 패널에서 확인한다."),
|
|
92
|
-
],
|
|
93
|
-
),
|
|
94
|
-
screen(
|
|
95
|
-
code="WEB-S003",
|
|
96
|
-
name="Web Orders",
|
|
97
|
-
route="/orders",
|
|
98
|
-
access="protected",
|
|
99
|
-
features=["AUT-F002", "ORD-F002"],
|
|
100
|
-
asset="orders.png",
|
|
101
|
-
requires_auth=True,
|
|
102
|
-
callouts=[
|
|
103
|
-
((0.17, 0.09), "상단 앱 셸", "Overview와 Orders 간 전환, 검색, 알림, 로그아웃 등 공통 워크스페이스 셸을 유지한다."),
|
|
104
|
-
((0.36, 0.25), "검색/필터 바", "Search orders 입력값으로 product, customer, 상태, 주문 ID를 클라이언트 필터링한다."),
|
|
105
|
-
((0.78, 0.25), "액션 버튼", "Reset으로 필터를 초기화하고 New order CTA로 신규 흐름 진입점을 노출한다."),
|
|
106
|
-
((0.44, 0.61), "주문 목록 테이블", "주문 ID, product, customer, 상태를 기준으로 현재 결과 집합을 표 형태로 보여준다."),
|
|
107
|
-
],
|
|
108
|
-
),
|
|
109
|
-
],
|
|
110
|
-
},
|
|
111
|
-
"admin": {
|
|
112
|
-
"title": "Templates Admin Screen Spec",
|
|
113
|
-
"service_label": "admin",
|
|
114
|
-
"output": str(SCREEN_DIR / "admin_screen_spec.pdf"),
|
|
115
|
-
"asset_dir": str(ASSET_DIR / "admin_screen_capture"),
|
|
116
|
-
"base_url": "http://127.0.0.1:4000",
|
|
117
|
-
"api_base": "http://127.0.0.1:8000/api/v1",
|
|
118
|
-
"storage_key": "admin.auth.token",
|
|
119
|
-
"capture_policy": DESKTOP_CAPTURE_POLICY,
|
|
120
|
-
"trim_background": False,
|
|
121
|
-
"cover_note": "첫 페이지는 admin 화면코드 인덱스다. 이후 페이지는 좌측 화면 캡처와 우측 번호형 기능 테이블을 함께 제공한다.",
|
|
122
|
-
"source_refs": [
|
|
123
|
-
{"label": "implementation", "path": "client/admin/src/app/App.tsx"},
|
|
124
|
-
{"label": "implementation", "path": "client/admin/src/pages"},
|
|
125
|
-
{"label": "capture assets", "path": "sdd/99_toolchain/01_automation/assets/admin_screen_capture"},
|
|
126
|
-
],
|
|
127
|
-
"screens": [
|
|
128
|
-
screen(
|
|
129
|
-
code="ADM-S001",
|
|
130
|
-
name="Admin Login",
|
|
131
|
-
route="/login",
|
|
132
|
-
access="public",
|
|
133
|
-
features=["AUT-F001", "AUT-F002"],
|
|
134
|
-
asset="login.png",
|
|
135
|
-
requires_auth=False,
|
|
136
|
-
callouts=[
|
|
137
|
-
((0.24, 0.34), "운영 콘솔 소개 영역", "좌측 다크 패널에서 Admin console 컨텍스트와 실로그인 인증 흐름을 설명한다."),
|
|
138
|
-
((0.79, 0.18), "Restricted access 헤더", "관리자 접근 제어 맥락과 Sign in 제목을 우측 카드 상단에 배치한다."),
|
|
139
|
-
((0.79, 0.36), "자격 증명 입력", "이메일, 비밀번호, 오류 메시지를 한 폼 안에서 처리한다."),
|
|
140
|
-
((0.79, 0.48), "Open admin console CTA", "성공 시 관리자 토큰 저장 후 운영 셸로 진입한다."),
|
|
141
|
-
],
|
|
142
|
-
),
|
|
143
|
-
screen(
|
|
144
|
-
code="ADM-S002",
|
|
145
|
-
name="Admin Dashboard",
|
|
146
|
-
route="/",
|
|
147
|
-
access="protected",
|
|
148
|
-
features=["AUT-F002", "ORD-F003"],
|
|
149
|
-
asset="dashboard.png",
|
|
150
|
-
requires_auth=True,
|
|
151
|
-
callouts=[
|
|
152
|
-
((0.08, 0.35), "좌측 운영 레일", "대시보드, 거래 관리, 고객지원으로 이동하는 관리자 전용 내비게이션 레일이다."),
|
|
153
|
-
((0.40, 0.18), "운영 요약 카드", "운영 핵심 수치를 카드 단위로 먼저 요약해 전체 상태를 빠르게 파악하게 한다."),
|
|
154
|
-
((0.37, 0.58), "거래 상태별 현황", "단계별 거래 건수를 리스트형 카드로 보여주는 메인 분석 영역이다."),
|
|
155
|
-
((0.79, 0.58), "운영 알림", "위험/일반 알림을 tone별 카드로 묶어 후속 조치를 안내한다."),
|
|
156
|
-
],
|
|
157
|
-
),
|
|
158
|
-
screen(
|
|
159
|
-
code="ADM-S003",
|
|
160
|
-
name="Admin Queue",
|
|
161
|
-
route="/queue",
|
|
162
|
-
access="protected",
|
|
163
|
-
features=["AUT-F002", "ORD-F003"],
|
|
164
|
-
asset="queue.png",
|
|
165
|
-
requires_auth=True,
|
|
166
|
-
callouts=[
|
|
167
|
-
((0.08, 0.35), "좌측 운영 레일", "현재 거래 관리 섹션 위치를 강조하면서 다른 운영 화면으로 이동할 수 있다."),
|
|
168
|
-
((0.33, 0.24), "거래관리 헤더", "현재 큐 화면 제목과 전체 거래 건수를 요약해 상단에 고정한다."),
|
|
169
|
-
((0.44, 0.60), "거래 큐 테이블", "주문 ID, 상품, 고객 정보를 기준으로 관리자 작업 대상을 표로 노출한다."),
|
|
170
|
-
((0.82, 0.60), "상태/SLA 열", "운영 우선순위 판단에 필요한 상태와 SLA 정보를 같은 행에서 확인하게 한다."),
|
|
171
|
-
],
|
|
172
|
-
),
|
|
173
|
-
screen(
|
|
174
|
-
code="ADM-S004",
|
|
175
|
-
name="Admin Support",
|
|
176
|
-
route="/support",
|
|
177
|
-
access="protected",
|
|
178
|
-
features=["AUT-F002", "SUP-F001"],
|
|
179
|
-
asset="support.png",
|
|
180
|
-
requires_auth=True,
|
|
181
|
-
callouts=[
|
|
182
|
-
((0.08, 0.35), "좌측 운영 레일", "고객지원 섹션과 다른 관리자 기능을 오가는 공통 운영 내비게이션이다."),
|
|
183
|
-
((0.37, 0.21), "FAQ 관리 헤더", "현재 화면이 FAQ 운영 surface임을 제목으로 표시한다."),
|
|
184
|
-
((0.46, 0.55), "FAQ 목록", "질문과 노출 상태를 row 단위 카드로 보여주는 읽기 모델 영역이다."),
|
|
185
|
-
((0.86, 0.09), "상단 글로벌 액션", "알림, 현재 사용자, 로그아웃 등 관리자 공통 액션을 상단 바에 유지한다."),
|
|
186
|
-
],
|
|
187
|
-
),
|
|
188
|
-
],
|
|
189
|
-
},
|
|
190
|
-
"mobile": {
|
|
191
|
-
"title": "Templates Mobile Screen Spec",
|
|
192
|
-
"service_label": "mobile",
|
|
193
|
-
"output": str(SCREEN_DIR / "mobile_screen_spec.pdf"),
|
|
194
|
-
"asset_dir": str(ASSET_DIR / "mobile_screen_capture"),
|
|
195
|
-
"base_url": "http://127.0.0.1:3002",
|
|
196
|
-
"api_base": "http://127.0.0.1:8000/api/v1",
|
|
197
|
-
"storage_key": "mobile.auth.token",
|
|
198
|
-
"capture_policy": DESKTOP_CAPTURE_POLICY,
|
|
199
|
-
"trim_background": False,
|
|
200
|
-
"cover_note": "첫 페이지는 mobile 화면코드 인덱스다. 이후 페이지는 좌측 화면 캡처와 우측 번호형 기능 테이블을 함께 제공한다.",
|
|
201
|
-
"source_refs": [
|
|
202
|
-
{"label": "implementation", "path": "client/mobile/src/app/App.tsx"},
|
|
203
|
-
{"label": "implementation", "path": "client/mobile/src/pages"},
|
|
204
|
-
{"label": "capture assets", "path": "sdd/99_toolchain/01_automation/assets/mobile_screen_capture"},
|
|
205
|
-
],
|
|
206
|
-
"screens": [
|
|
207
|
-
screen(
|
|
208
|
-
code="MOB-S001",
|
|
209
|
-
name="Mobile Login",
|
|
210
|
-
route="/login",
|
|
211
|
-
access="public",
|
|
212
|
-
features=["AUT-F001", "AUT-F002"],
|
|
213
|
-
asset="login.png",
|
|
214
|
-
requires_auth=False,
|
|
215
|
-
callouts=[
|
|
216
|
-
((0.25, 0.32), "IN 소개 영역", "상담 흐름과 운영 상태를 동시에 여는 템플릿 맥락을 좌측 hero에서 설명한다."),
|
|
217
|
-
((0.79, 0.19), "Sign in 헤더", "IN workspace 진입 목적과 operator 기본 계정을 우측 로그인 카드 상단에 표시한다."),
|
|
218
|
-
((0.79, 0.39), "자격 증명 입력", "operator@example.com 기준 로그인 입력과 오류 상태를 한 폼에서 처리한다."),
|
|
219
|
-
((0.79, 0.49), "Continue CTA", "로그인 성공 시 저장된 세션으로 IN 보호 라우트에 진입한다."),
|
|
220
|
-
],
|
|
221
|
-
),
|
|
222
|
-
screen(
|
|
223
|
-
code="MOB-S002",
|
|
224
|
-
name="Mobile Dashboard",
|
|
225
|
-
route="/",
|
|
226
|
-
access="protected",
|
|
227
|
-
features=["AUT-F002", "FUL-F001"],
|
|
228
|
-
asset="dashboard.png",
|
|
229
|
-
requires_auth=True,
|
|
230
|
-
callouts=[
|
|
231
|
-
((0.15, 0.11), "상단 IN 셸", "Overview/Fulfillment 전환, relay status pill, 사용자 정보, 로그아웃을 묶은 공통 헤더다."),
|
|
232
|
-
((0.34, 0.25), "Units in motion hero", "현재 이행 중인 unit 수와 모바일 워크스페이스 목적을 한눈에 보여주는 대형 hero section이다."),
|
|
233
|
-
((0.27, 0.48), "운영 통계 카드", "Open tasks, blocked, outbound ready 지표를 카드 묶음으로 제공한다."),
|
|
234
|
-
((0.33, 0.79), "Relay timeline", "패킹, 예외 검토, 출고 준비 이벤트를 시간축 카드로 정리한다."),
|
|
235
|
-
((0.83, 0.60), "Stage load", "단계별 작업량과 병목 구간을 우측 보조 패널에서 보여준다."),
|
|
236
|
-
],
|
|
237
|
-
),
|
|
238
|
-
screen(
|
|
239
|
-
code="MOB-S003",
|
|
240
|
-
name="Mobile Fulfillment",
|
|
241
|
-
route="/fulfillment",
|
|
242
|
-
access="protected",
|
|
243
|
-
features=["AUT-F002", "FUL-F002"],
|
|
244
|
-
asset="fulfillment.png",
|
|
245
|
-
requires_auth=True,
|
|
246
|
-
callouts=[
|
|
247
|
-
((0.15, 0.11), "상단 IN 셸", "Fulfillment 화면에서도 Overview/Fulfillment 전환과 사용자 액션이 동일한 헤더 구조를 유지한다."),
|
|
248
|
-
((0.36, 0.23), "Fulfillment board 헤더", "현재 이행 보드 화면의 제목과 task surface 설명을 상단에서 제공한다."),
|
|
249
|
-
((0.39, 0.58), "Fulfillment board 테이블", "Order, task, assignee, status, SLA를 행 단위로 정리한 메인 운영 테이블이다."),
|
|
250
|
-
((0.83, 0.59), "Fulfillment notes", "이행 메모와 운영 체크포인트를 별도 패널로 분리해 보조 컨텍스트를 제공한다."),
|
|
251
|
-
],
|
|
252
|
-
),
|
|
253
|
-
],
|
|
254
|
-
},
|
|
255
|
-
"landing": {
|
|
256
|
-
"title": "Templates Landing Screen Spec",
|
|
257
|
-
"service_label": "landing",
|
|
258
|
-
"output": str(SCREEN_DIR / "landing_screen_spec.pdf"),
|
|
259
|
-
"asset_dir": str(ASSET_DIR / "landing_screen_capture"),
|
|
260
|
-
"base_url": "http://127.0.0.1:3000",
|
|
261
|
-
"api_base": "http://127.0.0.1:8000/api/v1",
|
|
262
|
-
"storage_key": "landing.auth.token",
|
|
263
|
-
"capture_policy": DESKTOP_CAPTURE_POLICY,
|
|
264
|
-
"trim_background": False,
|
|
265
|
-
"cover_note": "첫 페이지는 landing 화면코드 인덱스다. 이후 페이지는 좌측 화면 캡처와 우측 번호형 기능 테이블을 함께 제공한다.",
|
|
266
|
-
"source_refs": [
|
|
267
|
-
{"label": "implementation", "path": "client/landing/src/App.tsx"},
|
|
268
|
-
{"label": "implementation", "path": "client/landing/src/pages"},
|
|
269
|
-
{"label": "capture assets", "path": "sdd/99_toolchain/01_automation/assets/landing_screen_capture"},
|
|
270
|
-
],
|
|
271
|
-
"screens": [
|
|
272
|
-
screen(
|
|
273
|
-
code="LND-S001",
|
|
274
|
-
name="Landing Home",
|
|
275
|
-
route="/",
|
|
276
|
-
access="public",
|
|
277
|
-
features=["CAT-F001", "AUT-F002"],
|
|
278
|
-
asset="home.png",
|
|
279
|
-
requires_auth=False,
|
|
280
|
-
callouts=[
|
|
281
|
-
((0.16, 0.05), "글로벌 헤더", "브랜드, 섹션 앵커, 로그인 또는 workspace handoff 링크를 상단 헤더에 배치한다."),
|
|
282
|
-
((0.27, 0.20), "히어로 메시지와 CTA", "서비스 가치 제안과 Sign in/See examples CTA를 첫 섹션에서 강조한다."),
|
|
283
|
-
((0.77, 0.25), "카탈로그 스냅샷", "실시간 product 수, active count, highlighted catalog 상태를 요약 카드로 보여준다."),
|
|
284
|
-
((0.30, 0.56), "Features grid", "라이브 catalog product를 카드 그리드로 노출하는 예시 섹션이다."),
|
|
285
|
-
((0.50, 0.75), "Live proof 섹션", "`/api/v1/catalog/products` 기반 sync 결과를 집계 카드와 함께 증빙한다."),
|
|
286
|
-
((0.50, 0.92), "마무리 CTA", "템플릿 시작 CTA로 로그인 또는 workspace 진입을 유도하는 하단 배너다."),
|
|
287
|
-
],
|
|
288
|
-
),
|
|
289
|
-
screen(
|
|
290
|
-
code="LND-S002",
|
|
291
|
-
name="Landing Login",
|
|
292
|
-
route="/login",
|
|
293
|
-
access="public",
|
|
294
|
-
features=["AUT-F001", "AUT-F002"],
|
|
295
|
-
asset="login.png",
|
|
296
|
-
requires_auth=False,
|
|
297
|
-
callouts=[
|
|
298
|
-
((0.24, 0.34), "랜딩 소개 패널", "로그인 후 `/workspace`로 이어지는 흐름과 landing 복귀 링크를 좌측 소개 영역에서 설명한다."),
|
|
299
|
-
((0.78, 0.20), "Sign in 헤더", "멤버 접근 맥락과 workspace 진입 목적을 우측 로그인 폼 상단에 둔다."),
|
|
300
|
-
((0.78, 0.39), "자격 증명 입력", "이메일, 비밀번호, 오류 메시지를 같은 카드 안에서 처리한다."),
|
|
301
|
-
((0.78, 0.50), "Continue CTA", "성공 시 토큰 저장 후 보호된 `/workspace`로 이동한다."),
|
|
302
|
-
],
|
|
303
|
-
),
|
|
304
|
-
screen(
|
|
305
|
-
code="LND-S003",
|
|
306
|
-
name="Landing Workspace",
|
|
307
|
-
route="/workspace",
|
|
308
|
-
access="protected",
|
|
309
|
-
features=["AUT-F002", "CAT-F001"],
|
|
310
|
-
asset="workspace.png",
|
|
311
|
-
requires_auth=True,
|
|
312
|
-
callouts=[
|
|
313
|
-
((0.16, 0.07), "멤버 셸 헤더", "Workspace 링크, 현재 사용자, 로그아웃을 포함한 보호 라우트 전용 헤더다."),
|
|
314
|
-
((0.30, 0.32), "Protected workspace 카드", "인증된 사용자 전용 shell 설명과 catalog sync 상태를 메인 카드에 배치한다."),
|
|
315
|
-
((0.77, 0.32), "Current member 카드", "현재 로그인 사용자의 email, role, status를 우측 패널에서 보여준다."),
|
|
316
|
-
((0.30, 0.78), "Workspace product grid", "보호 라우트 안에서 조회한 product 목록을 카드 그리드로 렌더링한다."),
|
|
317
|
-
],
|
|
318
|
-
),
|
|
319
|
-
],
|
|
320
|
-
},
|
|
321
|
-
}
|
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import argparse
|
|
4
|
-
import atexit
|
|
5
|
-
import base64
|
|
6
|
-
import importlib.util
|
|
7
|
-
import re
|
|
8
|
-
import shutil
|
|
9
|
-
import subprocess
|
|
10
|
-
import tempfile
|
|
11
|
-
from io import BytesIO
|
|
12
|
-
from pathlib import Path
|
|
13
|
-
from typing import Sequence
|
|
14
|
-
|
|
15
|
-
from PIL import Image, ImageChops
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
TEMP_DIR = Path(tempfile.mkdtemp(prefix="spec-asset-builder-"))
|
|
19
|
-
PAGE_CACHE: dict[tuple[str, int, int], Image.Image] = {}
|
|
20
|
-
SVG_IMAGE_HREF_PATTERN = re.compile(r'href="data:image/png;base64,([^"]+)"')
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def cleanup_tempdir() -> None:
|
|
24
|
-
shutil.rmtree(TEMP_DIR, ignore_errors=True)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
atexit.register(cleanup_tempdir)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def parse_args(
|
|
31
|
-
argv: Sequence[str] | None = None,
|
|
32
|
-
*,
|
|
33
|
-
default_manifest: Path | None = None,
|
|
34
|
-
default_recipes_var: str = "ASSET_RECIPES",
|
|
35
|
-
) -> argparse.Namespace:
|
|
36
|
-
parser = argparse.ArgumentParser(description="Build static design assets from a recipe manifest.")
|
|
37
|
-
parser.add_argument("--manifest", default=str(default_manifest) if default_manifest else None, help="Path to the Python recipe manifest.")
|
|
38
|
-
parser.add_argument("--recipes-var", default=default_recipes_var, help="Manifest variable name that contains the recipe list.")
|
|
39
|
-
parser.add_argument("--asset", action="append", dest="assets", help="Asset id to build. Repeatable. Default builds all assets.")
|
|
40
|
-
parser.add_argument("--list", action="store_true", help="List available asset ids and exit.")
|
|
41
|
-
parser.add_argument("--verify-exact", action="store_true", help="Verify that each generated output is pixel-identical to the resolved source crop.")
|
|
42
|
-
args = parser.parse_args(argv)
|
|
43
|
-
|
|
44
|
-
if not args.manifest:
|
|
45
|
-
parser.error("--manifest is required")
|
|
46
|
-
|
|
47
|
-
return args
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def load_manifest(manifest_path: Path, recipes_var: str) -> list[dict]:
|
|
51
|
-
spec = importlib.util.spec_from_file_location("asset_recipe_manifest", manifest_path)
|
|
52
|
-
if spec is None or spec.loader is None:
|
|
53
|
-
raise SystemExit(f"Unable to load manifest: {manifest_path}")
|
|
54
|
-
|
|
55
|
-
module = importlib.util.module_from_spec(spec)
|
|
56
|
-
spec.loader.exec_module(module)
|
|
57
|
-
recipes = getattr(module, recipes_var, None)
|
|
58
|
-
if recipes is None:
|
|
59
|
-
raise SystemExit(f"Manifest {manifest_path} does not define {recipes_var}")
|
|
60
|
-
if not isinstance(recipes, list):
|
|
61
|
-
raise SystemExit(f"{recipes_var} in {manifest_path} must be a list")
|
|
62
|
-
return recipes
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def load_pdf_page(path: Path, page: int, dpi: int) -> Image.Image:
|
|
66
|
-
key = (str(path), page, dpi)
|
|
67
|
-
cached = PAGE_CACHE.get(key)
|
|
68
|
-
if cached is not None:
|
|
69
|
-
return cached.copy()
|
|
70
|
-
|
|
71
|
-
output_prefix = TEMP_DIR / f"{path.stem}-p{page}-{dpi}"
|
|
72
|
-
subprocess.run(
|
|
73
|
-
[
|
|
74
|
-
"pdftoppm",
|
|
75
|
-
"-png",
|
|
76
|
-
"-singlefile",
|
|
77
|
-
"-f",
|
|
78
|
-
str(page),
|
|
79
|
-
"-l",
|
|
80
|
-
str(page),
|
|
81
|
-
"-r",
|
|
82
|
-
str(dpi),
|
|
83
|
-
str(path),
|
|
84
|
-
str(output_prefix),
|
|
85
|
-
],
|
|
86
|
-
capture_output=True,
|
|
87
|
-
text=True,
|
|
88
|
-
check=True,
|
|
89
|
-
)
|
|
90
|
-
image = Image.open(output_prefix.with_suffix(".png")).convert("RGBA")
|
|
91
|
-
PAGE_CACHE[key] = image.copy()
|
|
92
|
-
return image
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def load_image(path: Path) -> Image.Image:
|
|
96
|
-
return Image.open(path).convert("RGBA")
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def resolve_source(source: dict) -> Image.Image:
|
|
100
|
-
kind = source["kind"]
|
|
101
|
-
if kind == "pdf_page":
|
|
102
|
-
return load_pdf_page(Path(source["path"]), int(source["page"]), int(source["dpi"]))
|
|
103
|
-
if kind == "image":
|
|
104
|
-
return load_image(Path(source["path"]))
|
|
105
|
-
raise ValueError(f"Unsupported source kind: {kind}")
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
def make_white_transparent(image: Image.Image, threshold: int) -> Image.Image:
|
|
109
|
-
converted = image.convert("RGBA")
|
|
110
|
-
pixels = []
|
|
111
|
-
for red, green, blue, alpha in converted.getdata():
|
|
112
|
-
if alpha and red >= threshold and green >= threshold and blue >= threshold:
|
|
113
|
-
pixels.append((255, 255, 255, 0))
|
|
114
|
-
else:
|
|
115
|
-
pixels.append((red, green, blue, alpha))
|
|
116
|
-
converted.putdata(pixels)
|
|
117
|
-
return converted
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
def transform_image(image: Image.Image, recipe: dict) -> Image.Image:
|
|
121
|
-
working = image.copy()
|
|
122
|
-
|
|
123
|
-
crop_box = recipe.get("crop_box")
|
|
124
|
-
if crop_box is not None:
|
|
125
|
-
working = working.crop(tuple(crop_box))
|
|
126
|
-
|
|
127
|
-
threshold = recipe.get("transparent_white_threshold")
|
|
128
|
-
if threshold is not None:
|
|
129
|
-
working = make_white_transparent(working, int(threshold))
|
|
130
|
-
|
|
131
|
-
if recipe.get("trim"):
|
|
132
|
-
bbox = working.getbbox()
|
|
133
|
-
if not bbox:
|
|
134
|
-
raise ValueError(f"Recipe {recipe['id']} trimmed to empty image")
|
|
135
|
-
working = working.crop(bbox)
|
|
136
|
-
|
|
137
|
-
return working
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
def encode_svg(image: Image.Image) -> str:
|
|
141
|
-
buffer = BytesIO()
|
|
142
|
-
image.save(buffer, format="PNG")
|
|
143
|
-
payload = base64.b64encode(buffer.getvalue()).decode("ascii")
|
|
144
|
-
return (
|
|
145
|
-
f'<svg xmlns="http://www.w3.org/2000/svg" width="{image.width}" height="{image.height}" '
|
|
146
|
-
f'viewBox="0 0 {image.width} {image.height}" fill="none" role="img" aria-hidden="true">\n'
|
|
147
|
-
f' <image width="{image.width}" height="{image.height}" href="data:image/png;base64,{payload}" />\n'
|
|
148
|
-
"</svg>\n"
|
|
149
|
-
)
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
def write_output(image: Image.Image, output_path: Path, output_format: str) -> None:
|
|
153
|
-
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
154
|
-
if output_format == "svg":
|
|
155
|
-
output_path.write_text(encode_svg(image), encoding="utf-8")
|
|
156
|
-
return
|
|
157
|
-
if output_format == "png":
|
|
158
|
-
image.save(output_path, format="PNG")
|
|
159
|
-
return
|
|
160
|
-
raise ValueError(f"Unsupported output format: {output_format}")
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
def read_output_image(output_path: Path, output_format: str) -> Image.Image:
|
|
164
|
-
if output_format == "png":
|
|
165
|
-
return Image.open(output_path).convert("RGBA")
|
|
166
|
-
if output_format == "svg":
|
|
167
|
-
raw = output_path.read_text(encoding="utf-8")
|
|
168
|
-
matched = SVG_IMAGE_HREF_PATTERN.search(raw)
|
|
169
|
-
if not matched:
|
|
170
|
-
raise ValueError(f"Unable to decode embedded PNG from {output_path}")
|
|
171
|
-
buffer = BytesIO(base64.b64decode(matched.group(1)))
|
|
172
|
-
return Image.open(buffer).convert("RGBA")
|
|
173
|
-
raise ValueError(f"Unsupported output format: {output_format}")
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
def verify_exact_output(output_path: Path, output_format: str, expected_image: Image.Image) -> None:
|
|
177
|
-
actual = read_output_image(output_path, output_format)
|
|
178
|
-
expected = expected_image.convert("RGBA")
|
|
179
|
-
|
|
180
|
-
if actual.size != expected.size:
|
|
181
|
-
raise SystemExit(f"Exact verification failed for {output_path}: size mismatch {actual.size} != {expected.size}")
|
|
182
|
-
|
|
183
|
-
if ImageChops.difference(actual, expected).getbbox() is not None:
|
|
184
|
-
raise SystemExit(f"Exact verification failed for {output_path}: pixel mismatch")
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
def infer_output_format(recipe: dict, output_path: Path) -> str:
|
|
188
|
-
if recipe.get("output_format"):
|
|
189
|
-
return str(recipe["output_format"])
|
|
190
|
-
if output_path.suffix.lower() == ".png":
|
|
191
|
-
return "png"
|
|
192
|
-
return "svg"
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
def emit_recipe(
|
|
196
|
-
recipe: dict,
|
|
197
|
-
*,
|
|
198
|
-
source_image: Image.Image | None = None,
|
|
199
|
-
selected_assets: set[str] | None = None,
|
|
200
|
-
verify_exact: bool = False,
|
|
201
|
-
) -> list[Path]:
|
|
202
|
-
source = source_image if source_image is not None else resolve_source(recipe["source"])
|
|
203
|
-
rendered = transform_image(source, recipe)
|
|
204
|
-
written: list[Path] = []
|
|
205
|
-
|
|
206
|
-
output = recipe.get("output")
|
|
207
|
-
if output is not None and (selected_assets is None or recipe["id"] in selected_assets):
|
|
208
|
-
output_path = Path(output)
|
|
209
|
-
output_format = infer_output_format(recipe, output_path)
|
|
210
|
-
write_output(rendered, output_path, output_format)
|
|
211
|
-
if verify_exact:
|
|
212
|
-
verify_exact_output(output_path, output_format, rendered)
|
|
213
|
-
written.append(output_path)
|
|
214
|
-
|
|
215
|
-
for child in recipe.get("children", []):
|
|
216
|
-
written.extend(
|
|
217
|
-
emit_recipe(
|
|
218
|
-
child,
|
|
219
|
-
source_image=rendered,
|
|
220
|
-
selected_assets=selected_assets,
|
|
221
|
-
verify_exact=verify_exact,
|
|
222
|
-
)
|
|
223
|
-
)
|
|
224
|
-
|
|
225
|
-
return written
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
def collect_asset_ids(recipes: list[dict]) -> list[str]:
|
|
229
|
-
asset_ids: list[str] = []
|
|
230
|
-
for recipe in recipes:
|
|
231
|
-
if recipe.get("output") is not None:
|
|
232
|
-
asset_ids.append(recipe["id"])
|
|
233
|
-
asset_ids.extend(collect_asset_ids(recipe.get("children", [])))
|
|
234
|
-
return asset_ids
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
def main(
|
|
238
|
-
argv: Sequence[str] | None = None,
|
|
239
|
-
*,
|
|
240
|
-
default_manifest: Path | None = None,
|
|
241
|
-
default_recipes_var: str = "ASSET_RECIPES",
|
|
242
|
-
) -> None:
|
|
243
|
-
args = parse_args(argv, default_manifest=default_manifest, default_recipes_var=default_recipes_var)
|
|
244
|
-
manifest_path = Path(args.manifest).resolve()
|
|
245
|
-
recipes = load_manifest(manifest_path, args.recipes_var)
|
|
246
|
-
asset_ids = collect_asset_ids(recipes)
|
|
247
|
-
|
|
248
|
-
if args.list:
|
|
249
|
-
for asset_id in asset_ids:
|
|
250
|
-
print(asset_id)
|
|
251
|
-
return
|
|
252
|
-
|
|
253
|
-
selected_assets = set(args.assets) if args.assets else None
|
|
254
|
-
if selected_assets is not None:
|
|
255
|
-
unknown = sorted(selected_assets.difference(asset_ids))
|
|
256
|
-
if unknown:
|
|
257
|
-
raise SystemExit(f"Unknown asset ids: {', '.join(unknown)}")
|
|
258
|
-
|
|
259
|
-
written: list[Path] = []
|
|
260
|
-
for recipe in recipes:
|
|
261
|
-
written.extend(
|
|
262
|
-
emit_recipe(
|
|
263
|
-
recipe,
|
|
264
|
-
selected_assets=selected_assets,
|
|
265
|
-
verify_exact=args.verify_exact,
|
|
266
|
-
)
|
|
267
|
-
)
|
|
268
|
-
|
|
269
|
-
for path in written:
|
|
270
|
-
print(path)
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
if __name__ == "__main__":
|
|
274
|
-
main()
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
# UI Contract Projection
|
|
2
|
-
|
|
3
|
-
`UI Contract Projection`은 화면설계서, 계약 문서, reference asset을 제품 UI 구조 위로 투영하고 검증하는 템플릿/스킬 계층의 공통 명칭이다.
|
|
4
|
-
|
|
5
|
-
## Canonical Naming
|
|
6
|
-
|
|
7
|
-
- capability: `UI Contract Projection`
|
|
8
|
-
- modes:
|
|
9
|
-
- `strict projection`
|
|
10
|
-
- `soft projection`
|
|
11
|
-
- common actions:
|
|
12
|
-
- `project`
|
|
13
|
-
- `verify`
|
|
14
|
-
- `report`
|
|
15
|
-
|
|
16
|
-
## Ownership Boundary
|
|
17
|
-
|
|
18
|
-
- 이 문서는 Claude/Codex 스킬과 CLI 래퍼가 공유할 일반 템플릿 계약만 정의한다.
|
|
19
|
-
- 제품별 screen registry, contract rows, evidence 경로는 각 제품 저장소의 `sdd`에서 확장한다.
|
|
20
|
-
- 제품 런타임 route, auth, layout은 이 템플릿을 이유로 분기하지 않는다.
|
|
21
|
-
|
|
22
|
-
## Skill Prompt Skeleton
|
|
23
|
-
|
|
24
|
-
```md
|
|
25
|
-
Goal: run UI Contract Projection for {product} {screen_or_flow}
|
|
26
|
-
Mode: {strict projection | soft projection}
|
|
27
|
-
Inputs:
|
|
28
|
-
- contract: {path}
|
|
29
|
-
- source artifact: {pdf|figma export|reference image|yaml}
|
|
30
|
-
- product target: {repo path or route}
|
|
31
|
-
|
|
32
|
-
Rules:
|
|
33
|
-
- Preserve product runtime behavior.
|
|
34
|
-
- Use React + shadcn/ui primitives and CSS token surface first.
|
|
35
|
-
- Keep projection assets in sdd/toolchain only.
|
|
36
|
-
- Do not add spec/proof/parity-only branches to runtime.
|
|
37
|
-
|
|
38
|
-
Outputs:
|
|
39
|
-
- updated product UI
|
|
40
|
-
- updated sdd contract/evidence
|
|
41
|
-
- projection report
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
## CLI Contract Template
|
|
45
|
-
|
|
46
|
-
```bash
|
|
47
|
-
ui-contract-projection project \
|
|
48
|
-
--mode strict \
|
|
49
|
-
--contract <path> \
|
|
50
|
-
--source <path-or-id> \
|
|
51
|
-
--target <repo-or-route>
|
|
52
|
-
|
|
53
|
-
ui-contract-projection verify \
|
|
54
|
-
--mode strict \
|
|
55
|
-
--contract <path> \
|
|
56
|
-
--target <url-or-route> \
|
|
57
|
-
--evidence-root <path>
|
|
58
|
-
|
|
59
|
-
ui-contract-projection report \
|
|
60
|
-
--contract <path> \
|
|
61
|
-
--evidence-root <path> \
|
|
62
|
-
--format markdown
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
## Required Gates
|
|
66
|
-
|
|
67
|
-
1. `Structure`
|
|
68
|
-
2. `Behavior`
|
|
69
|
-
3. `Data`
|
|
70
|
-
4. `UI Contract Projection`
|
|
71
|
-
|
|
72
|
-
`strict projection`은 앞선 세 gate를 통과한 뒤에만 사용한다.
|
|
73
|
-
|
|
74
|
-
## Frontend Defaults
|
|
75
|
-
|
|
76
|
-
- `React + shadcn/ui`
|
|
77
|
-
- CSS token surface
|
|
78
|
-
- style expansion via `scss`, `styled-components`, or equivalent modern layer
|
|
79
|
-
- shared primitives before screen-specific markup
|