@harmonyos-arkts/d2h 0.1.11 → 0.1.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.
Files changed (126) hide show
  1. package/.claude-plugin/marketplace.json +15 -13
  2. package/.claude-plugin/plugin.json +13 -7
  3. package/AGENTS.md +5 -5
  4. package/README.md +212 -212
  5. package/agents/android-to-hmos-00-orchestrator.md +318 -318
  6. package/agents/d2h.md +157 -157
  7. package/dist/bin/install-opencode.js +30 -30
  8. package/docs/environment-check.md +86 -86
  9. package/install-opencode.sh +160 -160
  10. package/opencode/agents.json +16 -16
  11. package/opencode/vscode-d2h-package.json +7 -7
  12. package/opencode/vscode-d2h-plugin.mjs +29 -29
  13. package/package.json +54 -54
  14. package/schemas/android-source-manifest.schema.json +25 -25
  15. package/schemas/checkpoint-provenance.schema.json +67 -67
  16. package/schemas/final-acceptance.schema.json +29 -29
  17. package/schemas/managed-evidence-index.schema.json +15 -15
  18. package/schemas/managed-evidence.schema.json +114 -114
  19. package/schemas/migration-config.schema.json +51 -51
  20. package/schemas/migration-report-index.schema.json +39 -39
  21. package/schemas/migration-report-item.schema.json +51 -51
  22. package/schemas/migration-report-summary.schema.json +25 -25
  23. package/schemas/migration-status.schema.json +202 -202
  24. package/schemas/preflight.schema.json +60 -60
  25. package/schemas/source-order-audit.schema.json +16 -16
  26. package/schemas/source-provenance-event.schema.json +19 -19
  27. package/schemas/spec-app-shard.schema.json +45 -45
  28. package/schemas/spec-fact-corrections-shard.schema.json +13 -13
  29. package/schemas/spec-features-shard.schema.json +43 -43
  30. package/schemas/spec-index.schema.json +26 -26
  31. package/schemas/spec-interactions-shard.schema.json +40 -40
  32. package/schemas/spec-page.schema.json +98 -98
  33. package/schemas/spec-pages-index.schema.json +1 -1
  34. package/schemas/spec-unresolved-shard.schema.json +1 -1
  35. package/schemas/task-envelope.schema.json +109 -109
  36. package/schemas/task-plan.schema.json +341 -341
  37. package/scripts/check-environment.mjs +320 -320
  38. package/skills/android2hmos_resources_convert/SKILL.md +180 -180
  39. package/skills/android2hmos_resources_convert/references/image-conversion-rules.md +230 -230
  40. package/skills/android2hmos_resources_convert/references/svg-fix-patterns.md +175 -175
  41. package/skills/android2hmos_resources_convert/references/xml-drawable-to-svg-rules.md +513 -513
  42. package/skills/arkts-app-identity/SKILL.md +238 -238
  43. package/skills/arkts-i18n/SKILL.md +496 -496
  44. package/skills/arkts-i18n/evals/evals.json +84 -84
  45. package/skills/arkts-i18n/references/code-examples.md +302 -302
  46. package/skills/arkts-i18n/references/common-pitfalls.md +391 -391
  47. package/skills/arkts-i18n/references/dynamic-language-switch.md +604 -604
  48. package/skills/arkts-i18n/references/hardcoded-string-scanner.md +348 -348
  49. package/skills/arkts-i18n/references/language-codes.md +104 -104
  50. package/skills/arkts-i18n/references/resource-file-structure.md +775 -775
  51. package/skills/arkts-i18n/references/static-vs-dynamic.md +242 -242
  52. package/skills/arkts-i18n/references/v1-compat.md +244 -244
  53. package/skills/arkts-i18n/scripts/audit_i18n_completeness.sh +174 -174
  54. package/skills/arkts-icon-sizing/SKILL.md +211 -211
  55. package/skills/arkts-icon-sizing/scripts/icon_audit.py +131 -131
  56. package/skills/arkts-icon-sizing/scripts/icon_autofix.py +88 -88
  57. package/skills/arkts-icon-sizing/scripts/icon_dims.py +179 -179
  58. package/skills/arkts-icon-sizing/scripts/icon_fix.py +119 -119
  59. package/skills/arkts-mvvm-architecture/SKILL.md +613 -613
  60. package/skills/d2h-hypium-forge/SKILL.md +309 -309
  61. package/skills/d2h-hypium-forge/references/functional-spec-template.md +140 -140
  62. package/skills/d2h-hypium-forge/references/official-api.md +135 -135
  63. package/skills/d2h-hypium-forge/references/pitfalls.md +98 -98
  64. package/skills/d2h-hypium-forge/references/testability-template.md +71 -71
  65. package/skills/d2h-hypium-forge/scripts/_common_template.py +659 -659
  66. package/skills/d2h-hypium-forge/scripts/analyze_results.py +165 -165
  67. package/skills/d2h-hypium-forge/scripts/assert_audit.py +107 -107
  68. package/skills/d2h-hypium-forge/scripts/collect_runtime_pages.py +169 -169
  69. package/skills/d2h-hypium-forge/scripts/forge_report.py +325 -325
  70. package/skills/d2h-hypium-forge/scripts/inject_resource_ids.py +114 -114
  71. package/skills/d2h-hypium-forge/scripts/lint_asserts.py +80 -80
  72. package/skills/d2h-hypium-forge/scripts/lint_locate.py +60 -60
  73. package/skills/d2h-hypium-forge/scripts/merge_appgraph.py +249 -249
  74. package/skills/d2h-hypium-forge/scripts/precondition.py +140 -140
  75. package/skills/d2h-hypium-forge/scripts/preinject_ids.py +232 -232
  76. package/skills/d2h-hypium-forge/scripts/probe.py +197 -197
  77. package/skills/d2h-hypium-forge/scripts/run_direct.py +823 -823
  78. package/skills/d2h-hypium-forge/scripts/run_parallel.py +121 -121
  79. package/skills/d2h-hypium-forge/scripts/scan_features.py +255 -255
  80. package/skills/d2h-hypium-forge/scripts/upgrade_asserts.py +66 -66
  81. package/skills/d2h-hypium-forge/scripts/validate_scenarios.py +84 -84
  82. package/skills/d2h-page-develop/SKILL.md +112 -112
  83. package/skills/d2h-page-develop/agents/openai.yaml +7 -7
  84. package/skills/d2h-page-spec-generate/SKILL.md +206 -206
  85. package/skills/d2h-page-spec-generate/scripts/build_batch_report.py +227 -227
  86. package/skills/d2h-utg-ai-generate/SKILL.md +128 -128
  87. package/skills/d2h-utg-ai-generate/references/click-paths-schema.json +76 -76
  88. package/skills/d2h-utg-ai-generate/references/extraction-guide.md +34 -34
  89. package/skills/d2h-utg-ai-generate/references/facts-contract.md +96 -96
  90. package/skills/d2h-utg-ai-generate/references/page-schema.json +61 -61
  91. package/skills/d2h-utg-ai-generate/references/utg-schema.json +89 -89
  92. package/skills/d2h-utg-ai-generate/scripts/build_page_json.py +314 -314
  93. package/skills/d2h-utg-ai-generate/scripts/build_utg.py +123 -123
  94. package/skills/d2h-utg-ai-generate/scripts/capture_from_page_json.ts +280 -280
  95. package/skills/d2h-utg-ai-generate/scripts/utg_common.py +524 -524
  96. package/skills/d2h-utg-ai-generate/scripts/utg_html.py +489 -489
  97. package/skills/d2h-utg-ai-generate/scripts/validate_utg.py +69 -69
  98. package/skills/d2h-utg-ai-generate/scripts/vendor/D3-LICENSE +13 -13
  99. package/skills/d2h-utg-ai-generate/scripts/vendor/d3-7.9.0.min.js +2 -2
  100. package/skills/d2h-utg-ai-generate/tests/test_pipeline.py +369 -369
  101. package/skills/d2h-utg-static-generate/SKILL.md +73 -73
  102. package/skills/d2h-utg-static-generate/references/discoveries.md +38 -38
  103. package/skills/d2h-utg-static-generate/scripts/generate.py +499 -499
  104. package/skills/d2h-utg-static-generate/tests/test_static.py +335 -335
  105. package/skills/d2h-utg-static-generate/tests/verify-offline-html.py +60 -60
  106. package/skills/d2h-utg-static-generate/tests/verify-produced-fixture.py +131 -131
  107. package/skills/harmonyos-migration-playbook/SKILL.md +83 -83
  108. package/skills/harmonyos-migration-playbook/agents/openai.yaml +7 -7
  109. package/skills/harmonyos-migration-playbook/references/arkts-compile.md +24 -24
  110. package/skills/harmonyos-migration-playbook/references/harmony-runtime.md +53 -53
  111. package/skills/harmonyos-migration-playbook/references/lesson-lifecycle.md +45 -45
  112. package/skills/harmonyos-migration-playbook/references/protocol-e2e.md +23 -23
  113. package/skills/harmonyos-migration-playbook/references/ui-automation.md +53 -53
  114. package/skills/harmonyos-migration-playbook/references/windows-environment.md +38 -38
  115. package/skills/maintaining-migration-report/SKILL.md +144 -144
  116. package/skills/maintaining-migration-report/references/final-acceptance.md +48 -48
  117. package/skills/maintaining-migration-report/references/source-provenance.md +66 -66
  118. package/skills/managing-d2h-toolchain/SKILL.md +24 -24
  119. package/skills/managing-d2h-toolchain/environment-requirements.json +16 -16
  120. package/skills/managing-d2h-toolchain/scripts/manage-toolchain.mjs +98 -98
  121. package/skills/managing-d2h-toolchain/toolchain.json +16 -16
  122. package/skills/native-library-substitution/SKILL.md +384 -384
  123. package/skills/native-library-substitution/references/native-library-substitution.json +56905 -56905
  124. package/skills/native-library-substitution/references/native-library-substitution.md +163 -163
  125. package/skills/preparing-migration-workspace/SKILL.md +131 -131
  126. package/skills/reviewing-migration-process/SKILL.md +62 -62
@@ -1,174 +1,174 @@
1
- #!/usr/bin/env bash
2
- # audit_i18n_completeness.sh — i18n cross-locale key consistency audit.
3
- #
4
- # 单一职责:检测跨 locale 的 string.json / plural.json / strarray.json 的 key 一致性。
5
- # base 的全部 key 必须在每个 language locale(zh_CN / en_US / ...)都存在;
6
- # mode locale(dark / horizontal / vertical 等)按差异覆盖即可,豁免。
7
- #
8
- # 资源 value 内的 [TODO: translate] / 裸 TODO 检测不在本脚本范围;调用者必须在
9
- # 当前请求范围收尾时另行扫描并把发现项返回为 unresolved。本脚本只管跨 locale 完整性。
10
- #
11
- # 用法(由 arkts-i18n 任务完成后自调,不被其他 skill 跨引用):
12
- # bash audit_i18n_completeness.sh \
13
- # --project-root <abs> \
14
- # --output-json docs/i18n-audit.json
15
- #
16
- # 退出码:
17
- # 0 全 PASS(key 一致)
18
- # 1 有 key_missing FAIL 项
19
- # 2 脚本错误(参数缺失等)
20
- #
21
- # 输出 JSON 结构:
22
- # {
23
- # "locales": ["base", "zh_CN", ...],
24
- # "language_locales": ["zh_CN", ...], # 参与 key 一致性校验的 locale
25
- # "mode_locales": ["dark", ...], # 豁免 locale
26
- # "key_count_per_locale": { "base": N, "zh_CN": N, ... },
27
- # "findings": [
28
- # { "type": "key-missing", "locale": "zh_CN", "key": "home_feed", "base_locale": "base", "suggested_action": "..." }
29
- # ],
30
- # "summary": { "key_missing": N, "FAIL": N }
31
- # }
32
-
33
- set -euo pipefail
34
-
35
- PROJECT_ROOT=""
36
- OUTPUT_JSON=""
37
-
38
- while [[ $# -gt 0 ]]; do
39
- case "$1" in
40
- --project-root) PROJECT_ROOT="$2"; shift 2 ;;
41
- --output-json) OUTPUT_JSON="$2"; shift 2 ;;
42
- --help|-h)
43
- grep '^#' "$0" | head -40
44
- exit 0
45
- ;;
46
- *)
47
- echo "unknown arg: $1" >&2
48
- exit 2
49
- ;;
50
- esac
51
- done
52
-
53
- if [[ -z "$PROJECT_ROOT" ]]; then
54
- echo "error: --project-root required" >&2
55
- exit 2
56
- fi
57
-
58
- # Resolve to absolute path; on Git Bash use `pwd -W` to get Windows-style for Python interop
59
- if pwd -W >/dev/null 2>&1; then
60
- PROJECT_ROOT="$(cd "$PROJECT_ROOT" && pwd -W)"
61
- else
62
- PROJECT_ROOT="$(cd "$PROJECT_ROOT" && pwd)"
63
- fi
64
- OUTPUT_JSON="${OUTPUT_JSON:-/dev/stdout}"
65
-
66
- RESOURCES_ROOT="$PROJECT_ROOT/entry/src/main/resources"
67
-
68
- if [[ ! -d "$RESOURCES_ROOT" ]]; then
69
- echo "error: resources root not found: $RESOURCES_ROOT" >&2
70
- exit 2
71
- fi
72
-
73
- # ─── Python helper: cross-locale key consistency only ───────────────────────
74
- PY_HELPER=$(cat <<'PYEOF'
75
- import json, os, sys
76
-
77
- project_root = sys.argv[1]
78
- output_path = sys.argv[2]
79
- resources_root = os.path.join(project_root, 'entry', 'src', 'main', 'resources')
80
-
81
- # 仅对翻译类 element 文件做校验
82
- TRANSLATION_ELEMENT_FILES = {'string.json', 'plural.json', 'strarray.json'}
83
-
84
- # Mode locale 豁免(按差异覆盖即可)
85
- MODE_LOCALES = {'dark', 'light', 'horizontal', 'vertical', 'land', 'port'}
86
-
87
- def is_language_locale(name: str) -> bool:
88
- if name in MODE_LOCALES:
89
- return False
90
- if '_' in name:
91
- return True
92
- if 2 <= len(name) <= 3 and name.isalpha():
93
- return True
94
- return False
95
-
96
- # Collect locales + key sets
97
- locales = []
98
- key_per_locale = {}
99
- for entry in sorted(os.listdir(resources_root)):
100
- locale_dir = os.path.join(resources_root, entry)
101
- elem_dir = os.path.join(locale_dir, 'element')
102
- if not os.path.isdir(elem_dir):
103
- continue
104
- if entry in ('rawfile', 'profile'):
105
- continue
106
- locales.append(entry)
107
- key_per_locale[entry] = set()
108
- for fname in os.listdir(elem_dir):
109
- if fname not in TRANSLATION_ELEMENT_FILES:
110
- continue
111
- fpath = os.path.join(elem_dir, fname)
112
- try:
113
- with open(fpath, encoding='utf-8') as f:
114
- data = json.load(f)
115
- except Exception:
116
- continue
117
- for items in data.values():
118
- if not isinstance(items, list):
119
- continue
120
- for item in items:
121
- if isinstance(item, dict) and 'name' in item:
122
- key_per_locale[entry].add(item['name'])
123
-
124
- # Language vs mode locales
125
- language_locales = [l for l in locales if is_language_locale(l)]
126
- mode_locales = [l for l in locales if l in MODE_LOCALES]
127
-
128
- # Key consistency check: base keys must exist in every language locale
129
- findings = []
130
- base_locale = 'base' if 'base' in key_per_locale else (locales[0] if locales else None)
131
- if base_locale:
132
- base_keys = key_per_locale[base_locale]
133
- for locale in language_locales:
134
- if locale == base_locale:
135
- continue
136
- missing = base_keys - key_per_locale[locale]
137
- for k in sorted(missing):
138
- findings.append({
139
- "type": "key-missing",
140
- "locale": locale,
141
- "key": k,
142
- "base_locale": base_locale,
143
- "suggested_action": f"Add key `{k}` to {locale}/element/<file>.json with locale-appropriate value.",
144
- })
145
-
146
- summary = {
147
- "key_missing": len(findings),
148
- "FAIL": len(findings),
149
- }
150
-
151
- out = {
152
- "locales": locales,
153
- "language_locales": language_locales,
154
- "mode_locales": mode_locales,
155
- "key_count_per_locale": {l: len(k) for l, k in key_per_locale.items()},
156
- "summary": summary,
157
- "findings": findings,
158
- }
159
-
160
- if output_path == '/dev/stdout':
161
- print(json.dumps(out, ensure_ascii=False, indent=2))
162
- else:
163
- os.makedirs(os.path.dirname(output_path), exist_ok=True)
164
- with open(output_path, 'w', encoding='utf-8') as f:
165
- json.dump(out, f, ensure_ascii=False, indent=2)
166
- print(f"i18n audit complete: {output_path}")
167
- print(f" locales={locales} language={language_locales} mode={mode_locales}")
168
- print(f" FAIL={summary['FAIL']} (key_missing={summary['key_missing']})")
169
-
170
- sys.exit(1 if summary["FAIL"] > 0 else 0)
171
- PYEOF
172
- )
173
-
174
- python3 -c "$PY_HELPER" "$PROJECT_ROOT" "$OUTPUT_JSON"
1
+ #!/usr/bin/env bash
2
+ # audit_i18n_completeness.sh — i18n cross-locale key consistency audit.
3
+ #
4
+ # 单一职责:检测跨 locale 的 string.json / plural.json / strarray.json 的 key 一致性。
5
+ # base 的全部 key 必须在每个 language locale(zh_CN / en_US / ...)都存在;
6
+ # mode locale(dark / horizontal / vertical 等)按差异覆盖即可,豁免。
7
+ #
8
+ # 资源 value 内的 [TODO: translate] / 裸 TODO 检测不在本脚本范围;调用者必须在
9
+ # 当前请求范围收尾时另行扫描并把发现项返回为 unresolved。本脚本只管跨 locale 完整性。
10
+ #
11
+ # 用法(由 arkts-i18n 任务完成后自调,不被其他 skill 跨引用):
12
+ # bash audit_i18n_completeness.sh \
13
+ # --project-root <abs> \
14
+ # --output-json docs/i18n-audit.json
15
+ #
16
+ # 退出码:
17
+ # 0 全 PASS(key 一致)
18
+ # 1 有 key_missing FAIL 项
19
+ # 2 脚本错误(参数缺失等)
20
+ #
21
+ # 输出 JSON 结构:
22
+ # {
23
+ # "locales": ["base", "zh_CN", ...],
24
+ # "language_locales": ["zh_CN", ...], # 参与 key 一致性校验的 locale
25
+ # "mode_locales": ["dark", ...], # 豁免 locale
26
+ # "key_count_per_locale": { "base": N, "zh_CN": N, ... },
27
+ # "findings": [
28
+ # { "type": "key-missing", "locale": "zh_CN", "key": "home_feed", "base_locale": "base", "suggested_action": "..." }
29
+ # ],
30
+ # "summary": { "key_missing": N, "FAIL": N }
31
+ # }
32
+
33
+ set -euo pipefail
34
+
35
+ PROJECT_ROOT=""
36
+ OUTPUT_JSON=""
37
+
38
+ while [[ $# -gt 0 ]]; do
39
+ case "$1" in
40
+ --project-root) PROJECT_ROOT="$2"; shift 2 ;;
41
+ --output-json) OUTPUT_JSON="$2"; shift 2 ;;
42
+ --help|-h)
43
+ grep '^#' "$0" | head -40
44
+ exit 0
45
+ ;;
46
+ *)
47
+ echo "unknown arg: $1" >&2
48
+ exit 2
49
+ ;;
50
+ esac
51
+ done
52
+
53
+ if [[ -z "$PROJECT_ROOT" ]]; then
54
+ echo "error: --project-root required" >&2
55
+ exit 2
56
+ fi
57
+
58
+ # Resolve to absolute path; on Git Bash use `pwd -W` to get Windows-style for Python interop
59
+ if pwd -W >/dev/null 2>&1; then
60
+ PROJECT_ROOT="$(cd "$PROJECT_ROOT" && pwd -W)"
61
+ else
62
+ PROJECT_ROOT="$(cd "$PROJECT_ROOT" && pwd)"
63
+ fi
64
+ OUTPUT_JSON="${OUTPUT_JSON:-/dev/stdout}"
65
+
66
+ RESOURCES_ROOT="$PROJECT_ROOT/entry/src/main/resources"
67
+
68
+ if [[ ! -d "$RESOURCES_ROOT" ]]; then
69
+ echo "error: resources root not found: $RESOURCES_ROOT" >&2
70
+ exit 2
71
+ fi
72
+
73
+ # ─── Python helper: cross-locale key consistency only ───────────────────────
74
+ PY_HELPER=$(cat <<'PYEOF'
75
+ import json, os, sys
76
+
77
+ project_root = sys.argv[1]
78
+ output_path = sys.argv[2]
79
+ resources_root = os.path.join(project_root, 'entry', 'src', 'main', 'resources')
80
+
81
+ # 仅对翻译类 element 文件做校验
82
+ TRANSLATION_ELEMENT_FILES = {'string.json', 'plural.json', 'strarray.json'}
83
+
84
+ # Mode locale 豁免(按差异覆盖即可)
85
+ MODE_LOCALES = {'dark', 'light', 'horizontal', 'vertical', 'land', 'port'}
86
+
87
+ def is_language_locale(name: str) -> bool:
88
+ if name in MODE_LOCALES:
89
+ return False
90
+ if '_' in name:
91
+ return True
92
+ if 2 <= len(name) <= 3 and name.isalpha():
93
+ return True
94
+ return False
95
+
96
+ # Collect locales + key sets
97
+ locales = []
98
+ key_per_locale = {}
99
+ for entry in sorted(os.listdir(resources_root)):
100
+ locale_dir = os.path.join(resources_root, entry)
101
+ elem_dir = os.path.join(locale_dir, 'element')
102
+ if not os.path.isdir(elem_dir):
103
+ continue
104
+ if entry in ('rawfile', 'profile'):
105
+ continue
106
+ locales.append(entry)
107
+ key_per_locale[entry] = set()
108
+ for fname in os.listdir(elem_dir):
109
+ if fname not in TRANSLATION_ELEMENT_FILES:
110
+ continue
111
+ fpath = os.path.join(elem_dir, fname)
112
+ try:
113
+ with open(fpath, encoding='utf-8') as f:
114
+ data = json.load(f)
115
+ except Exception:
116
+ continue
117
+ for items in data.values():
118
+ if not isinstance(items, list):
119
+ continue
120
+ for item in items:
121
+ if isinstance(item, dict) and 'name' in item:
122
+ key_per_locale[entry].add(item['name'])
123
+
124
+ # Language vs mode locales
125
+ language_locales = [l for l in locales if is_language_locale(l)]
126
+ mode_locales = [l for l in locales if l in MODE_LOCALES]
127
+
128
+ # Key consistency check: base keys must exist in every language locale
129
+ findings = []
130
+ base_locale = 'base' if 'base' in key_per_locale else (locales[0] if locales else None)
131
+ if base_locale:
132
+ base_keys = key_per_locale[base_locale]
133
+ for locale in language_locales:
134
+ if locale == base_locale:
135
+ continue
136
+ missing = base_keys - key_per_locale[locale]
137
+ for k in sorted(missing):
138
+ findings.append({
139
+ "type": "key-missing",
140
+ "locale": locale,
141
+ "key": k,
142
+ "base_locale": base_locale,
143
+ "suggested_action": f"Add key `{k}` to {locale}/element/<file>.json with locale-appropriate value.",
144
+ })
145
+
146
+ summary = {
147
+ "key_missing": len(findings),
148
+ "FAIL": len(findings),
149
+ }
150
+
151
+ out = {
152
+ "locales": locales,
153
+ "language_locales": language_locales,
154
+ "mode_locales": mode_locales,
155
+ "key_count_per_locale": {l: len(k) for l, k in key_per_locale.items()},
156
+ "summary": summary,
157
+ "findings": findings,
158
+ }
159
+
160
+ if output_path == '/dev/stdout':
161
+ print(json.dumps(out, ensure_ascii=False, indent=2))
162
+ else:
163
+ os.makedirs(os.path.dirname(output_path), exist_ok=True)
164
+ with open(output_path, 'w', encoding='utf-8') as f:
165
+ json.dump(out, f, ensure_ascii=False, indent=2)
166
+ print(f"i18n audit complete: {output_path}")
167
+ print(f" locales={locales} language={language_locales} mode={mode_locales}")
168
+ print(f" FAIL={summary['FAIL']} (key_missing={summary['key_missing']})")
169
+
170
+ sys.exit(1 if summary["FAIL"] > 0 else 0)
171
+ PYEOF
172
+ )
173
+
174
+ python3 -c "$PY_HELPER" "$PROJECT_ROOT" "$OUTPUT_JSON"