@harmonyos-arkts/d2h 0.1.11 → 0.1.12

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,60 +1,60 @@
1
- #!/usr/bin/env python3
2
- """Exercise a generated offline UTG in a local Chromium browser; no devices."""
3
- import argparse
4
- import json
5
- from pathlib import Path
6
- import re
7
- import subprocess
8
- import tempfile
9
-
10
-
11
- def main():
12
- parser = argparse.ArgumentParser()
13
- parser.add_argument('--html', required=True, type=Path)
14
- parser.add_argument('--browser', required=True, type=Path)
15
- args = parser.parse_args()
16
- original = args.html.read_bytes()
17
- html = original.decode('utf-8')
18
- # Add a test-only self-edge to the in-memory visualization, leaving facts intact.
19
- html = html.replace('const REVIEW =', "GRAPH.links.push({...GRAPH.links[0], id:'visual-self-loop', source:GRAPH.nodes[1].id, target:GRAPH.nodes[1].id});\nconst REVIEW =", 1)
20
- probe = r"""<script>
21
- setTimeout(() => {
22
- const checks = {};
23
- const links = [...document.querySelectorAll('path.link')];
24
- checks.distinctPaths = links.length > 1 && new Set(links.map(link => link.getAttribute('d'))).size === links.length;
25
- checks.selfLoop = links.some(link => link.getAttribute('d').includes(' C'));
26
- checks.logicalEntry = document.getElementById('legend-items').textContent.includes('逻辑入口');
27
- document.querySelector('path.link-hit').dispatchEvent(new MouseEvent('click', {bubbles:true}));
28
- checks.edgeDetail = document.getElementById('detail-content').style.display !== 'none';
29
- checks.edgeEvidence = document.getElementById('detail-content').textContent.includes('appgraph');
30
- const filter = document.getElementById('source-filter');
31
- for (const source of ['ai-source-review', 'appgraph']) {
32
- filter.value = source; filter.dispatchEvent(new Event('change'));
33
- checks[source] = [...document.querySelectorAll('g.node')].every(node =>
34
- (node.style.display !== 'none') === (node.__data__.provenance === source));
35
- }
36
- const result = document.createElement('pre'); result.id='utg-qa-result'; result.textContent=JSON.stringify(checks); document.body.appendChild(result);
37
- }, 1500);
38
- </script>"""
39
- html = html.replace('</body>', probe + '</body>', 1)
40
- with tempfile.TemporaryDirectory(prefix='d2h-offline-html-') as directory:
41
- folder = Path(directory).resolve()
42
- test_html = folder / 'utg.html'
43
- test_html.write_text(html, encoding='utf-8')
44
- result = subprocess.run([str(args.browser.resolve()), '--headless', '--disable-gpu',
45
- '--no-first-run', '--no-default-browser-check', '--disable-background-networking',
46
- '--disable-component-update', '--disable-extensions', '--proxy-server=127.0.0.1:9',
47
- f'--user-data-dir={folder / "profile"}', '--virtual-time-budget=2500',
48
- '--dump-dom', test_html.as_uri()], capture_output=True, timeout=45, check=True)
49
- match = re.search(r'<pre id="utg-qa-result">(.*?)</pre>', result.stdout.decode('utf-8', errors='replace'))
50
- if not match:
51
- raise AssertionError('Offline renderer or browser probe did not execute')
52
- checks = json.loads(match.group(1))
53
- if not all(checks.values()):
54
- raise AssertionError(checks)
55
- assert args.html.read_bytes() == original, 'Browser verification must not alter the input HTML'
56
- print(json.dumps({'status': 'passed', 'checks': checks}, ensure_ascii=False))
57
-
58
-
59
- if __name__ == '__main__':
60
- main()
1
+ #!/usr/bin/env python3
2
+ """Exercise a generated offline UTG in a local Chromium browser; no devices."""
3
+ import argparse
4
+ import json
5
+ from pathlib import Path
6
+ import re
7
+ import subprocess
8
+ import tempfile
9
+
10
+
11
+ def main():
12
+ parser = argparse.ArgumentParser()
13
+ parser.add_argument('--html', required=True, type=Path)
14
+ parser.add_argument('--browser', required=True, type=Path)
15
+ args = parser.parse_args()
16
+ original = args.html.read_bytes()
17
+ html = original.decode('utf-8')
18
+ # Add a test-only self-edge to the in-memory visualization, leaving facts intact.
19
+ html = html.replace('const REVIEW =', "GRAPH.links.push({...GRAPH.links[0], id:'visual-self-loop', source:GRAPH.nodes[1].id, target:GRAPH.nodes[1].id});\nconst REVIEW =", 1)
20
+ probe = r"""<script>
21
+ setTimeout(() => {
22
+ const checks = {};
23
+ const links = [...document.querySelectorAll('path.link')];
24
+ checks.distinctPaths = links.length > 1 && new Set(links.map(link => link.getAttribute('d'))).size === links.length;
25
+ checks.selfLoop = links.some(link => link.getAttribute('d').includes(' C'));
26
+ checks.logicalEntry = document.getElementById('legend-items').textContent.includes('逻辑入口');
27
+ document.querySelector('path.link-hit').dispatchEvent(new MouseEvent('click', {bubbles:true}));
28
+ checks.edgeDetail = document.getElementById('detail-content').style.display !== 'none';
29
+ checks.edgeEvidence = document.getElementById('detail-content').textContent.includes('appgraph');
30
+ const filter = document.getElementById('source-filter');
31
+ for (const source of ['ai-source-review', 'appgraph']) {
32
+ filter.value = source; filter.dispatchEvent(new Event('change'));
33
+ checks[source] = [...document.querySelectorAll('g.node')].every(node =>
34
+ (node.style.display !== 'none') === (node.__data__.provenance === source));
35
+ }
36
+ const result = document.createElement('pre'); result.id='utg-qa-result'; result.textContent=JSON.stringify(checks); document.body.appendChild(result);
37
+ }, 1500);
38
+ </script>"""
39
+ html = html.replace('</body>', probe + '</body>', 1)
40
+ with tempfile.TemporaryDirectory(prefix='d2h-offline-html-') as directory:
41
+ folder = Path(directory).resolve()
42
+ test_html = folder / 'utg.html'
43
+ test_html.write_text(html, encoding='utf-8')
44
+ result = subprocess.run([str(args.browser.resolve()), '--headless', '--disable-gpu',
45
+ '--no-first-run', '--no-default-browser-check', '--disable-background-networking',
46
+ '--disable-component-update', '--disable-extensions', '--proxy-server=127.0.0.1:9',
47
+ f'--user-data-dir={folder / "profile"}', '--virtual-time-budget=2500',
48
+ '--dump-dom', test_html.as_uri()], capture_output=True, timeout=45, check=True)
49
+ match = re.search(r'<pre id="utg-qa-result">(.*?)</pre>', result.stdout.decode('utf-8', errors='replace'))
50
+ if not match:
51
+ raise AssertionError('Offline renderer or browser probe did not execute')
52
+ checks = json.loads(match.group(1))
53
+ if not all(checks.values()):
54
+ raise AssertionError(checks)
55
+ assert args.html.read_bytes() == original, 'Browser verification must not alter the input HTML'
56
+ print(json.dumps({'status': 'passed', 'checks': checks}, ensure_ascii=False))
57
+
58
+
59
+ if __name__ == '__main__':
60
+ main()
@@ -1,131 +1,131 @@
1
- """Explicit local AppGraph CLI -> UTG integration; never clones or updates repositories."""
2
- import argparse
3
- import hashlib
4
- import json
5
- import shutil
6
- import subprocess
7
- import sys
8
- import tempfile
9
- from pathlib import Path
10
-
11
-
12
- def sha(path):
13
- return hashlib.sha256(path.read_bytes()).hexdigest()
14
-
15
-
16
- def main():
17
- parser = argparse.ArgumentParser(description=__doc__)
18
- parser.add_argument('--appgraph-repo', required=True, type=Path)
19
- args = parser.parse_args()
20
- repo = args.appgraph_repo.resolve()
21
- cli = repo / 'dist/appgraph/cli.js'
22
- fixture = repo / '__tests__/appgraph/interaction-fixtures'
23
- if not cli.is_file() or not fixture.is_dir():
24
- raise RuntimeError('explicit AppGraph checkout must be built and contain the alignment fixture')
25
- commit = subprocess.check_output(['git', '-C', str(repo), 'rev-parse', 'HEAD'], text=True).strip()
26
- generate = Path(__file__).resolve().parents[1] / 'scripts/generate.py'
27
- with tempfile.TemporaryDirectory(prefix='d2h-produced-fixture-') as temporary:
28
- folder = Path(temporary)
29
- source = folder / 'interaction-fixtures'
30
- shutil.copytree(fixture, source, ignore=shutil.ignore_patterns('.codegraph*', '.migration', '.appgraph', 'build'))
31
- source_files = [p for p in source.rglob('*') if p.is_file()]
32
- source_before = {str(p.relative_to(source)): sha(p) for p in source_files}
33
- graph_file = folder / 'app-graph.json'
34
- workspace = folder / 'utg'
35
- build = ['node', str(cli), 'build', str(source), '--platform', 'android', '--out', str(graph_file)]
36
- subprocess.run(build, cwd=repo, check=True, capture_output=True)
37
- graph_before = sha(graph_file)
38
- graph = json.loads(graph_file.read_text(encoding='utf-8'))
39
- interaction = graph['interactionGraph']
40
- assert len(interaction['surfaces']) == 4, 'coarse/fine Activity and Compose identities must align'
41
- navigation = [t for t in interaction['transitions'] if t['operation'] == 'navigate' and t['resolution'] == 'resolved']
42
- assert len(navigation) == 2, 'two controls must retain two transitions without duplicate trace edges'
43
- assert len({t['actionRef'] for t in navigation}) == 2
44
- assert any(t['operation'] == 'back' and t['target']['kind'] == 'back' for t in interaction['transitions'])
45
- assert any(t['operation'] == 'navhost-start' for t in interaction['transitions'])
46
- command = [sys.executable, str(generate), '--project-root', str(source), '--appgraph', str(graph_file), '--workspace', str(workspace)]
47
- subprocess.run(command, check=True, capture_output=True)
48
- outputs = {p.name: sha(p) for p in workspace.iterdir() if p.is_file()}
49
- subprocess.run(command + ['--force'], check=True, capture_output=True)
50
- assert outputs == {p.name: sha(p) for p in workspace.iterdir() if p.is_file()}, 'derived outputs must be byte deterministic'
51
- assert sha(graph_file) == graph_before, 'UTG must not modify AppGraph input'
52
- assert source_before == {str(p.relative_to(source)): sha(p) for p in source_files}, 'source files must remain unchanged'
53
- utg = json.loads((workspace / 'utg.json').read_text(encoding='utf-8'))
54
- plan = json.loads((workspace / 'page.json').read_text(encoding='utf-8'))
55
- assert utg['graph']['root'] == 'utg:entry-root'
56
- assert len(utg['nodes']) == 5 and len(utg['links']) == 5
57
- assert all(link.get('id') and link['status'] == 'static-only' for link in utg['links'])
58
- assert plan['summary']['capture_planned'] == 0, 'missing arrival/selectors cannot become executable'
59
- capture = generate.parents[2] / 'd2h-utg-ai-generate/scripts/capture_from_page_json.ts'
60
- dry_run = subprocess.run(['node', str(capture), '--page-json', str(workspace / 'page.json'),
61
- '--output-workspace', str(folder / 'captures'), '--dry-run'],
62
- check=True, capture_output=True, text=True)
63
- assert not (folder / 'captures').exists(), 'dry-run must not create runtime artifacts'
64
- preview = json.loads(dry_run.stdout)
65
- assert preview['selected_count'] == 0 and preview['nodes'] == [], 'capture CLI must agree on the non-executable plan'
66
- assert 'boundary_transitions' in (workspace / 'utg.html').read_text(encoding='utf-8')
67
- subprocess.run(build, cwd=repo, check=True, capture_output=True)
68
- assert sha(graph_file) == graph_before, 'AppGraph repeated analysis must be deterministic'
69
- # Coordinate-only edits must not change the interaction identity sets.
70
- main_source = source / 'app/src/main/java/fixture/alignment/MainActivity.kt'
71
- main_source.write_text('\n\n' + main_source.read_text(encoding='utf-8'), encoding='utf-8')
72
- subprocess.run(build, cwd=repo, check=True, capture_output=True)
73
- moved = json.loads(graph_file.read_text(encoding='utf-8'))['interactionGraph']
74
- for collection in ('surfaces', 'controlBindings', 'entries', 'transitions'):
75
- assert {o['id'] for o in interaction[collection]} == {o['id'] for o in moved[collection]}, json.dumps({
76
- 'collection': collection, 'before': interaction[collection], 'after': moved[collection],
77
- 'original_screens': [(n['id'], n.get('attrs')) for n in graph['nodes'] if n['kind'] == 'Screen'],
78
- 'changed_screens': [(n['id'], n.get('attrs')) for n in json.loads(graph_file.read_text(encoding='utf-8'))['nodes'] if n['kind'] == 'Screen'],
79
- })
80
-
81
- # Prove AST branch facts survive rule execution, lifting and the UTG consumer.
82
- guarded_source = main_source.read_text(encoding='utf-8').replace(
83
- 'onClick = { nav.navigate("details") }', 'onClick = { if (allowed) nav.navigate("details") }', 1)
84
- main_source.write_text(guarded_source, encoding='utf-8')
85
- subprocess.run(build, cwd=repo, check=True, capture_output=True)
86
- guarded_graph = json.loads(graph_file.read_text(encoding='utf-8'))['interactionGraph']
87
- guarded_navigation = [t for t in guarded_graph['transitions']
88
- if t['operation'] == 'navigate' and t['resolution'] == 'resolved']
89
- assert len(guarded_navigation) == 2, 'guard reconciliation cannot duplicate or erase navigation facts'
90
- assert any(t['conditions'] and t['conditions'][0]['expression'] == 'allowed' for t in guarded_navigation)
91
- guarded_workspace = folder / 'utg-guarded'
92
- guarded_command = [sys.executable, str(generate), '--project-root', str(source), '--appgraph', str(graph_file), '--workspace', str(guarded_workspace)]
93
- subprocess.run(guarded_command, check=True, capture_output=True)
94
- guarded_utg = json.loads((guarded_workspace / 'utg.json').read_text(encoding='utf-8'))
95
- assert any(link.get('guard', {}).get('text') == '(allowed)' for link in guarded_utg['links'] if link.get('guard'))
96
-
97
- # Source-derived overlay facts must become actual interaction and UTG edges.
98
- overlay_source = guarded_source.replace('import androidx.compose.material3.Button',
99
- 'import androidx.compose.material3.Button\nimport androidx.compose.material3.AlertDialog\nimport androidx.compose.material3.ModalBottomSheet')
100
- overlay_source = overlay_source.replace('@Composable fun HomePage(nav: NavController) {',
101
- '@Composable fun HomePage(nav: NavController) {\n'
102
- ' Button(onClick = { if (allowed) uiState = uiState.copy(showDialog = true) }) {}\n'
103
- ' Button(onClick = { uiState = uiState.copy(showSheet = true) }) {}\n'
104
- ' if (uiState.showDialog) ConfirmDialog()\n'
105
- ' if (uiState.showSheet) DetailsBottomSheet()')
106
- overlay_source += '\n@Composable fun ConfirmDialog() { AlertDialog(onDismissRequest = {}, confirmButton = {}) }\n'
107
- overlay_source += '@Composable fun DetailsBottomSheet() { ModalBottomSheet(onDismissRequest = {}) {} }\n'
108
- main_source.write_text(overlay_source, encoding='utf-8')
109
- subprocess.run(build, cwd=repo, check=True, capture_output=True)
110
- overlay_graph = json.loads(graph_file.read_text(encoding='utf-8'))['interactionGraph']
111
- shown = [t for t in overlay_graph['transitions'] if t['operation'] == 'show' and t['resolution'] == 'resolved']
112
- overlay_kinds = {surface['id']: surface['kind'] for surface in overlay_graph['surfaces']}
113
- assert {'dialog', 'bottom-sheet'} <= {overlay_kinds[t['target']['surfaceId']] for t in shown}
114
- assert any(t['conditions'] and t['conditions'][0]['expression'] == 'allowed' for t in shown)
115
- overlay_workspace = folder / 'utg-overlays'
116
- subprocess.run([sys.executable, str(generate), '--project-root', str(source), '--appgraph', str(graph_file),
117
- '--workspace', str(overlay_workspace)], check=True, capture_output=True)
118
- overlay_utg = json.loads((overlay_workspace / 'utg.json').read_text(encoding='utf-8'))
119
- assert {t['id'] for t in shown} <= {link['id'] for link in overlay_utg['links']}
120
- main_source.write_text('\n\n' + overlay_source, encoding='utf-8')
121
- subprocess.run(build, cwd=repo, check=True, capture_output=True)
122
- moved_overlays = json.loads(graph_file.read_text(encoding='utf-8'))['interactionGraph']
123
- for collection in ('surfaces', 'controlBindings', 'entries', 'transitions'):
124
- assert {fact['id'] for fact in overlay_graph[collection]} == {fact['id'] for fact in moved_overlays[collection]}, collection + ' overlay coordinate stability'
125
- print(json.dumps({'status': 'passed', 'appgraph_commit': commit, 'appgraph_input_sha256': graph_before,
126
- 'surfaces': 4, 'parallel_navigation': 2, 'utg_nodes': 5, 'utg_edges': 5,
127
- 'deterministic': True, 'readonly': True, 'coordinate_stability': True, 'guarded_navigation': True, 'overlay_projection': True}))
128
-
129
-
130
- if __name__ == '__main__':
131
- main()
1
+ """Explicit local AppGraph CLI -> UTG integration; never clones or updates repositories."""
2
+ import argparse
3
+ import hashlib
4
+ import json
5
+ import shutil
6
+ import subprocess
7
+ import sys
8
+ import tempfile
9
+ from pathlib import Path
10
+
11
+
12
+ def sha(path):
13
+ return hashlib.sha256(path.read_bytes()).hexdigest()
14
+
15
+
16
+ def main():
17
+ parser = argparse.ArgumentParser(description=__doc__)
18
+ parser.add_argument('--appgraph-repo', required=True, type=Path)
19
+ args = parser.parse_args()
20
+ repo = args.appgraph_repo.resolve()
21
+ cli = repo / 'dist/appgraph/cli.js'
22
+ fixture = repo / '__tests__/appgraph/interaction-fixtures'
23
+ if not cli.is_file() or not fixture.is_dir():
24
+ raise RuntimeError('explicit AppGraph checkout must be built and contain the alignment fixture')
25
+ commit = subprocess.check_output(['git', '-C', str(repo), 'rev-parse', 'HEAD'], text=True).strip()
26
+ generate = Path(__file__).resolve().parents[1] / 'scripts/generate.py'
27
+ with tempfile.TemporaryDirectory(prefix='d2h-produced-fixture-') as temporary:
28
+ folder = Path(temporary)
29
+ source = folder / 'interaction-fixtures'
30
+ shutil.copytree(fixture, source, ignore=shutil.ignore_patterns('.codegraph*', '.migration', '.appgraph', 'build'))
31
+ source_files = [p for p in source.rglob('*') if p.is_file()]
32
+ source_before = {str(p.relative_to(source)): sha(p) for p in source_files}
33
+ graph_file = folder / 'app-graph.json'
34
+ workspace = folder / 'utg'
35
+ build = ['node', str(cli), 'build', str(source), '--platform', 'android', '--out', str(graph_file)]
36
+ subprocess.run(build, cwd=repo, check=True, capture_output=True)
37
+ graph_before = sha(graph_file)
38
+ graph = json.loads(graph_file.read_text(encoding='utf-8'))
39
+ interaction = graph['interactionGraph']
40
+ assert len(interaction['surfaces']) == 4, 'coarse/fine Activity and Compose identities must align'
41
+ navigation = [t for t in interaction['transitions'] if t['operation'] == 'navigate' and t['resolution'] == 'resolved']
42
+ assert len(navigation) == 2, 'two controls must retain two transitions without duplicate trace edges'
43
+ assert len({t['actionRef'] for t in navigation}) == 2
44
+ assert any(t['operation'] == 'back' and t['target']['kind'] == 'back' for t in interaction['transitions'])
45
+ assert any(t['operation'] == 'navhost-start' for t in interaction['transitions'])
46
+ command = [sys.executable, str(generate), '--project-root', str(source), '--appgraph', str(graph_file), '--workspace', str(workspace)]
47
+ subprocess.run(command, check=True, capture_output=True)
48
+ outputs = {p.name: sha(p) for p in workspace.iterdir() if p.is_file()}
49
+ subprocess.run(command + ['--force'], check=True, capture_output=True)
50
+ assert outputs == {p.name: sha(p) for p in workspace.iterdir() if p.is_file()}, 'derived outputs must be byte deterministic'
51
+ assert sha(graph_file) == graph_before, 'UTG must not modify AppGraph input'
52
+ assert source_before == {str(p.relative_to(source)): sha(p) for p in source_files}, 'source files must remain unchanged'
53
+ utg = json.loads((workspace / 'utg.json').read_text(encoding='utf-8'))
54
+ plan = json.loads((workspace / 'page.json').read_text(encoding='utf-8'))
55
+ assert utg['graph']['root'] == 'utg:entry-root'
56
+ assert len(utg['nodes']) == 5 and len(utg['links']) == 5
57
+ assert all(link.get('id') and link['status'] == 'static-only' for link in utg['links'])
58
+ assert plan['summary']['capture_planned'] == 0, 'missing arrival/selectors cannot become executable'
59
+ capture = generate.parents[2] / 'd2h-utg-ai-generate/scripts/capture_from_page_json.ts'
60
+ dry_run = subprocess.run(['node', str(capture), '--page-json', str(workspace / 'page.json'),
61
+ '--output-workspace', str(folder / 'captures'), '--dry-run'],
62
+ check=True, capture_output=True, text=True)
63
+ assert not (folder / 'captures').exists(), 'dry-run must not create runtime artifacts'
64
+ preview = json.loads(dry_run.stdout)
65
+ assert preview['selected_count'] == 0 and preview['nodes'] == [], 'capture CLI must agree on the non-executable plan'
66
+ assert 'boundary_transitions' in (workspace / 'utg.html').read_text(encoding='utf-8')
67
+ subprocess.run(build, cwd=repo, check=True, capture_output=True)
68
+ assert sha(graph_file) == graph_before, 'AppGraph repeated analysis must be deterministic'
69
+ # Coordinate-only edits must not change the interaction identity sets.
70
+ main_source = source / 'app/src/main/java/fixture/alignment/MainActivity.kt'
71
+ main_source.write_text('\n\n' + main_source.read_text(encoding='utf-8'), encoding='utf-8')
72
+ subprocess.run(build, cwd=repo, check=True, capture_output=True)
73
+ moved = json.loads(graph_file.read_text(encoding='utf-8'))['interactionGraph']
74
+ for collection in ('surfaces', 'controlBindings', 'entries', 'transitions'):
75
+ assert {o['id'] for o in interaction[collection]} == {o['id'] for o in moved[collection]}, json.dumps({
76
+ 'collection': collection, 'before': interaction[collection], 'after': moved[collection],
77
+ 'original_screens': [(n['id'], n.get('attrs')) for n in graph['nodes'] if n['kind'] == 'Screen'],
78
+ 'changed_screens': [(n['id'], n.get('attrs')) for n in json.loads(graph_file.read_text(encoding='utf-8'))['nodes'] if n['kind'] == 'Screen'],
79
+ })
80
+
81
+ # Prove AST branch facts survive rule execution, lifting and the UTG consumer.
82
+ guarded_source = main_source.read_text(encoding='utf-8').replace(
83
+ 'onClick = { nav.navigate("details") }', 'onClick = { if (allowed) nav.navigate("details") }', 1)
84
+ main_source.write_text(guarded_source, encoding='utf-8')
85
+ subprocess.run(build, cwd=repo, check=True, capture_output=True)
86
+ guarded_graph = json.loads(graph_file.read_text(encoding='utf-8'))['interactionGraph']
87
+ guarded_navigation = [t for t in guarded_graph['transitions']
88
+ if t['operation'] == 'navigate' and t['resolution'] == 'resolved']
89
+ assert len(guarded_navigation) == 2, 'guard reconciliation cannot duplicate or erase navigation facts'
90
+ assert any(t['conditions'] and t['conditions'][0]['expression'] == 'allowed' for t in guarded_navigation)
91
+ guarded_workspace = folder / 'utg-guarded'
92
+ guarded_command = [sys.executable, str(generate), '--project-root', str(source), '--appgraph', str(graph_file), '--workspace', str(guarded_workspace)]
93
+ subprocess.run(guarded_command, check=True, capture_output=True)
94
+ guarded_utg = json.loads((guarded_workspace / 'utg.json').read_text(encoding='utf-8'))
95
+ assert any(link.get('guard', {}).get('text') == '(allowed)' for link in guarded_utg['links'] if link.get('guard'))
96
+
97
+ # Source-derived overlay facts must become actual interaction and UTG edges.
98
+ overlay_source = guarded_source.replace('import androidx.compose.material3.Button',
99
+ 'import androidx.compose.material3.Button\nimport androidx.compose.material3.AlertDialog\nimport androidx.compose.material3.ModalBottomSheet')
100
+ overlay_source = overlay_source.replace('@Composable fun HomePage(nav: NavController) {',
101
+ '@Composable fun HomePage(nav: NavController) {\n'
102
+ ' Button(onClick = { if (allowed) uiState = uiState.copy(showDialog = true) }) {}\n'
103
+ ' Button(onClick = { uiState = uiState.copy(showSheet = true) }) {}\n'
104
+ ' if (uiState.showDialog) ConfirmDialog()\n'
105
+ ' if (uiState.showSheet) DetailsBottomSheet()')
106
+ overlay_source += '\n@Composable fun ConfirmDialog() { AlertDialog(onDismissRequest = {}, confirmButton = {}) }\n'
107
+ overlay_source += '@Composable fun DetailsBottomSheet() { ModalBottomSheet(onDismissRequest = {}) {} }\n'
108
+ main_source.write_text(overlay_source, encoding='utf-8')
109
+ subprocess.run(build, cwd=repo, check=True, capture_output=True)
110
+ overlay_graph = json.loads(graph_file.read_text(encoding='utf-8'))['interactionGraph']
111
+ shown = [t for t in overlay_graph['transitions'] if t['operation'] == 'show' and t['resolution'] == 'resolved']
112
+ overlay_kinds = {surface['id']: surface['kind'] for surface in overlay_graph['surfaces']}
113
+ assert {'dialog', 'bottom-sheet'} <= {overlay_kinds[t['target']['surfaceId']] for t in shown}
114
+ assert any(t['conditions'] and t['conditions'][0]['expression'] == 'allowed' for t in shown)
115
+ overlay_workspace = folder / 'utg-overlays'
116
+ subprocess.run([sys.executable, str(generate), '--project-root', str(source), '--appgraph', str(graph_file),
117
+ '--workspace', str(overlay_workspace)], check=True, capture_output=True)
118
+ overlay_utg = json.loads((overlay_workspace / 'utg.json').read_text(encoding='utf-8'))
119
+ assert {t['id'] for t in shown} <= {link['id'] for link in overlay_utg['links']}
120
+ main_source.write_text('\n\n' + overlay_source, encoding='utf-8')
121
+ subprocess.run(build, cwd=repo, check=True, capture_output=True)
122
+ moved_overlays = json.loads(graph_file.read_text(encoding='utf-8'))['interactionGraph']
123
+ for collection in ('surfaces', 'controlBindings', 'entries', 'transitions'):
124
+ assert {fact['id'] for fact in overlay_graph[collection]} == {fact['id'] for fact in moved_overlays[collection]}, collection + ' overlay coordinate stability'
125
+ print(json.dumps({'status': 'passed', 'appgraph_commit': commit, 'appgraph_input_sha256': graph_before,
126
+ 'surfaces': 4, 'parallel_navigation': 2, 'utg_nodes': 5, 'utg_edges': 5,
127
+ 'deterministic': True, 'readonly': True, 'coordinate_stability': True, 'guarded_navigation': True, 'overlay_projection': True}))
128
+
129
+
130
+ if __name__ == '__main__':
131
+ main()
@@ -1,83 +1,83 @@
1
- ---
2
- name: harmonyos-migration-playbook
3
- description: Diagnose Android-to-HarmonyOS migration failures, check whether verified lessons apply, and return the smallest scoped fix or recommendation for ArkTS compilation, HarmonyOS runtime, UI automation, protocol E2E, and Windows toolchains. Use during implementation, failure recovery, validation, or retrospectives; do not control checkpoints or outer workflow state.
4
- ---
5
-
6
- # HarmonyOS migration playbook
7
-
8
- Use verified experience to shorten diagnosis without replacing current-project evidence.
9
-
10
- ## Inputs and domain result
11
-
12
- Accept the current symptom, in-scope files, SDK/tool versions, relevant logs or managed evidence, and any project lesson candidates. Do not infer success from an earlier project.
13
-
14
- Return the same result shape whether called directly or from an orchestrator:
15
-
16
- ```json
17
- {
18
- "status": "completed | partial | failed",
19
- "changedFiles": [],
20
- "sourceFiles": [],
21
- "evidence": [],
22
- "domainChecks": [],
23
- "unresolved": [],
24
- "verificationRequests": [],
25
- "suggestedRemediation": [],
26
- "details": {
27
- "validatedRootCause": null,
28
- "applicability": {},
29
- "lessonCandidates": []
30
- }
31
- }
32
- ```
33
-
34
- `status` describes diagnosis and any in-scope fix only. `validatedRootCause` requires current-project evidence. Put repository-level build, test, device, or review needs in `verificationRequests`; do not choose the caller's checkpoint or workflow transition.
35
-
36
- ## Required operating loop
37
-
38
- 1. Identify the current symptom and task boundary.
39
- 2. Read only the relevant reference below.
40
- 3. Check that the recorded applicability conditions match the current SDK, UI stack, runtime and code path.
41
- 4. Reproduce or inspect evidence before applying a fix. Historical experience is a hypothesis until confirmed in the current project.
42
- 5. If the request authorizes a change, apply the smallest in-scope fix; otherwise return the smallest concrete recommendation.
43
- 6. Perform only domain-local checks available within scope. Return repository-level verification as `verificationRequests` and a reusable `lessonCandidates` entry only when the root cause and fix are supported by current-project evidence.
44
-
45
- ## Reference routing
46
-
47
- - ArkTS compiler errors or language restrictions: read [references/arkts-compile.md](references/arkts-compile.md).
48
- - Socket, TLS, IME, asynchronous execution, component state or theme problems: read [references/harmony-runtime.md](references/harmony-runtime.md).
49
- - Emulator interaction, screenshots, `dumpLayout`, input or scrolling failures: read [references/ui-automation.md](references/ui-automation.md).
50
- - IMAP/SMTP or other protocol-level end-to-end verification: read [references/protocol-e2e.md](references/protocol-e2e.md).
51
- - Windows, Gradle, SDK, proxy, encoding or background-service process hangs: read [references/windows-environment.md](references/windows-environment.md).
52
- - DevEco CLI login callback refused (`localhost 拒绝了连接`) or auth gate retries: read [references/windows-environment.md](references/windows-environment.md).
53
- - Recording or promoting migration experience: read [references/lesson-lifecycle.md](references/lesson-lifecycle.md).
54
-
55
- ## Evidence levels
56
-
57
- - **Platform rule**: supported by the active SDK/compiler behavior or repeatable minimal reproduction.
58
- - **Reusable pattern**: reproduced in more than one relevant project or independent environment.
59
- - **Project-validated**: fully proven in one project and useful beyond that project. It may enter the public playbook immediately, but every new project must revalidate it before relying on it.
60
- - **Candidate observation**: plausible but not yet proven; never use as a completion claim.
61
-
62
- Prefer narrower classifications. Public does not mean universal: a project-validated lesson is visible for reuse but remains a hypothesis outside its source project. Record SDK/tool versions when behavior could change.
63
-
64
- ## Non-negotiable boundaries
65
-
66
- - Android source and observable behavior remain authoritative.
67
- - Do not bypass AppGraph, compilation, tests or device validation merely because an earlier project was slow or unreliable. Diagnose the current failure; any explicit degradation must preserve a visible coverage gap.
68
- - Do not convert implementation defects into platform limitations or external blockers.
69
- - Do not claim a lesson is verified from a report, comment, code existence or compile success alone when runtime behavior is relevant.
70
- - Do not modify the public playbook from every task. First record the lesson in the current project; a reviewed, non-obvious and well-verified single-project lesson may be promoted as `project-validated` under `lesson-lifecycle.md` instead of waiting invisibly for another project.
71
- - Do not stage or commit files, create a checkpoint, update the migration report, call `complete`, schedule full regression, or decide an outer workflow state. The caller owns those actions.
72
-
73
- ## Quick triage
74
-
75
- ```text
76
- Compile failure -> ArkTS error code and exact source construct
77
- Button does nothing -> reachability, UI tree, callback log, async context
78
- Socket failure -> bind/connect/TLS stage and network logs
79
- Login callback refused-> login process lifecycle (windows-environment.md)
80
- Input corruption -> focus, IME state, input command and current value
81
- Visual mismatch -> element-by-element layout/style/content comparison
82
- Repeated task failure -> read project lessons, then verify applicability
83
- ```
1
+ ---
2
+ name: harmonyos-migration-playbook
3
+ description: Diagnose Android-to-HarmonyOS migration failures, check whether verified lessons apply, and return the smallest scoped fix or recommendation for ArkTS compilation, HarmonyOS runtime, UI automation, protocol E2E, and Windows toolchains. Use during implementation, failure recovery, validation, or retrospectives; do not control checkpoints or outer workflow state.
4
+ ---
5
+
6
+ # HarmonyOS migration playbook
7
+
8
+ Use verified experience to shorten diagnosis without replacing current-project evidence.
9
+
10
+ ## Inputs and domain result
11
+
12
+ Accept the current symptom, in-scope files, SDK/tool versions, relevant logs or managed evidence, and any project lesson candidates. Do not infer success from an earlier project.
13
+
14
+ Return the same result shape whether called directly or from an orchestrator:
15
+
16
+ ```json
17
+ {
18
+ "status": "completed | partial | failed",
19
+ "changedFiles": [],
20
+ "sourceFiles": [],
21
+ "evidence": [],
22
+ "domainChecks": [],
23
+ "unresolved": [],
24
+ "verificationRequests": [],
25
+ "suggestedRemediation": [],
26
+ "details": {
27
+ "validatedRootCause": null,
28
+ "applicability": {},
29
+ "lessonCandidates": []
30
+ }
31
+ }
32
+ ```
33
+
34
+ `status` describes diagnosis and any in-scope fix only. `validatedRootCause` requires current-project evidence. Put repository-level build, test, device, or review needs in `verificationRequests`; do not choose the caller's checkpoint or workflow transition.
35
+
36
+ ## Required operating loop
37
+
38
+ 1. Identify the current symptom and task boundary.
39
+ 2. Read only the relevant reference below.
40
+ 3. Check that the recorded applicability conditions match the current SDK, UI stack, runtime and code path.
41
+ 4. Reproduce or inspect evidence before applying a fix. Historical experience is a hypothesis until confirmed in the current project.
42
+ 5. If the request authorizes a change, apply the smallest in-scope fix; otherwise return the smallest concrete recommendation.
43
+ 6. Perform only domain-local checks available within scope. Return repository-level verification as `verificationRequests` and a reusable `lessonCandidates` entry only when the root cause and fix are supported by current-project evidence.
44
+
45
+ ## Reference routing
46
+
47
+ - ArkTS compiler errors or language restrictions: read [references/arkts-compile.md](references/arkts-compile.md).
48
+ - Socket, TLS, IME, asynchronous execution, component state or theme problems: read [references/harmony-runtime.md](references/harmony-runtime.md).
49
+ - Emulator interaction, screenshots, `dumpLayout`, input or scrolling failures: read [references/ui-automation.md](references/ui-automation.md).
50
+ - IMAP/SMTP or other protocol-level end-to-end verification: read [references/protocol-e2e.md](references/protocol-e2e.md).
51
+ - Windows, Gradle, SDK, proxy, encoding or background-service process hangs: read [references/windows-environment.md](references/windows-environment.md).
52
+ - DevEco CLI login callback refused (`localhost 拒绝了连接`) or auth gate retries: read [references/windows-environment.md](references/windows-environment.md).
53
+ - Recording or promoting migration experience: read [references/lesson-lifecycle.md](references/lesson-lifecycle.md).
54
+
55
+ ## Evidence levels
56
+
57
+ - **Platform rule**: supported by the active SDK/compiler behavior or repeatable minimal reproduction.
58
+ - **Reusable pattern**: reproduced in more than one relevant project or independent environment.
59
+ - **Project-validated**: fully proven in one project and useful beyond that project. It may enter the public playbook immediately, but every new project must revalidate it before relying on it.
60
+ - **Candidate observation**: plausible but not yet proven; never use as a completion claim.
61
+
62
+ Prefer narrower classifications. Public does not mean universal: a project-validated lesson is visible for reuse but remains a hypothesis outside its source project. Record SDK/tool versions when behavior could change.
63
+
64
+ ## Non-negotiable boundaries
65
+
66
+ - Android source and observable behavior remain authoritative.
67
+ - Do not bypass AppGraph, compilation, tests or device validation merely because an earlier project was slow or unreliable. Diagnose the current failure; any explicit degradation must preserve a visible coverage gap.
68
+ - Do not convert implementation defects into platform limitations or external blockers.
69
+ - Do not claim a lesson is verified from a report, comment, code existence or compile success alone when runtime behavior is relevant.
70
+ - Do not modify the public playbook from every task. First record the lesson in the current project; a reviewed, non-obvious and well-verified single-project lesson may be promoted as `project-validated` under `lesson-lifecycle.md` instead of waiting invisibly for another project.
71
+ - Do not stage or commit files, create a checkpoint, update the migration report, call `complete`, schedule full regression, or decide an outer workflow state. The caller owns those actions.
72
+
73
+ ## Quick triage
74
+
75
+ ```text
76
+ Compile failure -> ArkTS error code and exact source construct
77
+ Button does nothing -> reachability, UI tree, callback log, async context
78
+ Socket failure -> bind/connect/TLS stage and network logs
79
+ Login callback refused-> login process lifecycle (windows-environment.md)
80
+ Input corruption -> focus, IME state, input command and current value
81
+ Visual mismatch -> element-by-element layout/style/content comparison
82
+ Repeated task failure -> read project lessons, then verify applicability
83
+ ```
@@ -1,7 +1,7 @@
1
- interface:
2
- display_name: "HarmonyOS Migration Playbook"
3
- short_description: "复用经验证的鸿蒙迁移诊断与测试经验"
4
- default_prompt: "Use $harmonyos-migration-playbook to diagnose this migration issue and verify that the historical lesson applies here."
5
-
6
- policy:
7
- allow_implicit_invocation: true
1
+ interface:
2
+ display_name: "HarmonyOS Migration Playbook"
3
+ short_description: "复用经验证的鸿蒙迁移诊断与测试经验"
4
+ default_prompt: "Use $harmonyos-migration-playbook to diagnose this migration issue and verify that the historical lesson applies here."
5
+
6
+ policy:
7
+ allow_implicit_invocation: true