@dailephd/my-dev-kit-lab 0.4.5 → 0.4.7

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 (263) hide show
  1. package/CHANGELOG.md +26 -1
  2. package/README.md +43 -3
  3. package/dist/scripts/cli.js +9 -0
  4. package/dist/src/audits/core/typescriptJavaScriptAnalyzer.js +30 -12
  5. package/dist/src/browser/index.js +2 -0
  6. package/dist/src/browser/playwrightRuntime.js +74 -0
  7. package/dist/src/browser/types.js +10 -0
  8. package/dist/src/cli/help.js +376 -0
  9. package/dist/src/cli/index.js +2 -0
  10. package/dist/src/cli/runLabCli.js +257 -0
  11. package/dist/src/commands/runAuditCommand.js +132 -0
  12. package/dist/src/commands/runControlledExperimentCommand.js +7 -3
  13. package/dist/{scripts/experiments/describeExperiment.js → src/commands/runExperimentDescribeCommand.js} +14 -10
  14. package/dist/{scripts/experiments/listExperiments.js → src/commands/runExperimentListCommand.js} +3 -6
  15. package/dist/{scripts/experiments/runExperiment.js → src/commands/runExperimentRunCommand.js} +80 -22
  16. package/dist/src/commands/runFinalDemoCommand.js +2 -3
  17. package/dist/src/commands/runSecurityValidationCommand.js +271 -0
  18. package/dist/src/commands/runTutorialRunCommand.js +170 -0
  19. package/dist/src/commands/runTutorialValidateCommand.js +138 -0
  20. package/dist/src/core/processTree.js +52 -0
  21. package/dist/src/core/resolveCommand.js +34 -0
  22. package/dist/src/core/runMeasuredCommand.js +39 -31
  23. package/dist/src/runtime/index.js +4 -0
  24. package/dist/src/runtime/labExecutionContext.js +22 -0
  25. package/dist/src/runtime/managedProcess.js +380 -0
  26. package/dist/src/runtime/packageResource.js +22 -0
  27. package/dist/src/runtime/packageRoot.js +37 -0
  28. package/dist/src/screenshot/captureReportScreenshot.js +30 -27
  29. package/dist/src/securityValidation/packageChecks/requiredPackageContents.js +14 -1
  30. package/dist/src/tutorial/index.js +16 -0
  31. package/dist/src/tutorial/loadTutorialContracts.js +60 -0
  32. package/dist/src/tutorial/markdownTutorialWriter.js +53 -0
  33. package/dist/src/tutorial/runTutorial.js +465 -0
  34. package/dist/src/tutorial/scenarioValidation.js +383 -0
  35. package/dist/src/tutorial/subtitleWriter.js +88 -0
  36. package/dist/src/tutorial/targetContractValidation.js +212 -0
  37. package/dist/src/tutorial/tutorialActions.js +110 -0
  38. package/dist/src/tutorial/tutorialArtifacts.js +176 -0
  39. package/dist/src/tutorial/tutorialAssertions.js +249 -0
  40. package/dist/src/tutorial/tutorialCursor.js +149 -0
  41. package/dist/src/tutorial/tutorialLocators.js +37 -0
  42. package/dist/src/tutorial/tutorialManifest.js +74 -0
  43. package/dist/src/tutorial/tutorialOverlay.js +180 -0
  44. package/dist/src/tutorial/tutorialPaths.js +86 -0
  45. package/dist/src/tutorial/tutorialSession.js +398 -0
  46. package/dist/src/tutorial/types.js +48 -0
  47. package/dist/src/tutorial/validationPrimitives.js +141 -0
  48. package/examples/tutorial-browser/index.html +47 -0
  49. package/examples/tutorial-browser/prepare.mjs +14 -0
  50. package/examples/tutorial-browser/scenario.json +17 -0
  51. package/examples/tutorial-browser/server.mjs +14 -0
  52. package/package.json +13 -9
  53. package/dist/scripts/audits/runAudit.js +0 -135
  54. package/dist/scripts/build-gallery.js +0 -3
  55. package/dist/scripts/capture-demo-report.js +0 -3
  56. package/dist/scripts/evaluate-token-savings.js +0 -2
  57. package/dist/scripts/generate-experiment-plots.js +0 -3
  58. package/dist/scripts/generate-prompt-variants.js +0 -2
  59. package/dist/scripts/render-experiment-report.js +0 -2
  60. package/dist/scripts/report-context-integrity-smoke.js +0 -70
  61. package/dist/scripts/run-agent-prompt.js +0 -2
  62. package/dist/scripts/run-controlled-experiment.js +0 -2
  63. package/dist/scripts/run-lab-demo.js +0 -5
  64. package/dist/scripts/run-visualization-demos.js +0 -3
  65. package/dist/scripts/security/resolveToolRoot.js +0 -16
  66. package/dist/scripts/security/runCodeql.js +0 -58
  67. package/dist/scripts/security/runDependencyChecks.js +0 -58
  68. package/dist/scripts/security/runFuzzSmoke.js +0 -29
  69. package/dist/scripts/security/runPackageChecks.js +0 -57
  70. package/dist/scripts/security/runSemgrep.js +0 -64
  71. package/dist/scripts/security/validate.js +0 -266
  72. package/dist/scripts/verify-benchmarks.js +0 -202
  73. package/examples/demo-report-input.json +0 -78
  74. package/examples/lab-demo-cases.json +0 -35
  75. package/examples/real-agent-campaign-cases.json +0 -118
  76. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-a/src/debug/res/xml/ambiguous_backup_rules.xml +0 -4
  77. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-a/src/main/res/xml/ambiguous_backup_rules.xml +0 -4
  78. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-a/src/main/res/xml/backup_rules.xml +0 -5
  79. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-a/src/main/res/xml/broad_backup_rules.xml +0 -4
  80. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-a/src/main/res/xml/data_extraction_rules.xml +0 -10
  81. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-a/src/main/res/xml/unsupported_domain_rules.xml +0 -4
  82. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-b/src/main/res/xml/malformed_backup_rules.xml +0 -4
  83. package/tests/fixtures/android/advanced-security-fixtures/backup-configuration/module-b/src/main/res/xml/unsupported_root.xml +0 -4
  84. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/debug/res/xml/ambiguous_config.xml +0 -4
  85. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/main/res/xml/ambiguous_config.xml +0 -4
  86. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/main/res/xml/base_cleartext_false.xml +0 -8
  87. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/main/res/xml/broad_and_duplicate_domains.xml +0 -8
  88. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/main/res/xml/empty_domain.xml +0 -6
  89. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/main/res/xml/malformed_pin.xml +0 -11
  90. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-a/src/main/res/xml/network_security_config.xml +0 -26
  91. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-b/src/main/res/xml/malformed_network_security_config.xml +0 -6
  92. package/tests/fixtures/android/advanced-security-fixtures/network-security/module-b/src/main/res/xml/unsupported_root.xml +0 -4
  93. package/tests/fixtures/android/advanced-security-fixtures/release-configuration/groovy-release-debuggable-dynamic.gradle +0 -7
  94. package/tests/fixtures/android/advanced-security-fixtures/release-configuration/groovy-release-debuggable-false.gradle +0 -9
  95. package/tests/fixtures/android/advanced-security-fixtures/release-configuration/groovy-release-debuggable-true.gradle +0 -13
  96. package/tests/fixtures/android/advanced-security-fixtures/release-configuration/kotlin-release-debuggable-false.gradle.kts +0 -7
  97. package/tests/fixtures/android/advanced-security-fixtures/release-configuration/kotlin-release-debuggable-true.gradle.kts +0 -10
  98. package/tests/fixtures/android/advanced-security-fixtures/release-configuration/missing-release-build-type.gradle.kts +0 -7
  99. package/tests/fixtures/android/advanced-security-fixtures/resource-resolution/module-a/src/debug/res/xml/network_security_config.xml +0 -8
  100. package/tests/fixtures/android/advanced-security-fixtures/resource-resolution/module-a/src/main/res/xml/backup_rules.xml +0 -4
  101. package/tests/fixtures/android/advanced-security-fixtures/resource-resolution/module-a/src/main/res/xml/network_security_config.xml +0 -8
  102. package/tests/fixtures/android/advanced-security-fixtures/resource-resolution/module-b/src/main/res/xml/malformed_network_security_config.xml +0 -7
  103. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates/SampleActivity.java +0 -8
  104. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates/SampleActivity.kt +0 -9
  105. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates/build.gradle +0 -11
  106. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates/build.gradle.kts +0 -11
  107. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates/google-services.json +0 -16
  108. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates/local.properties +0 -3
  109. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates-bounds/module/binary.properties +0 -0
  110. package/tests/fixtures/android/advanced-security-fixtures/secret-candidates-bounds/module/oversized.json +0 -1
  111. package/tests/fixtures/android/advanced-security-fixtures/signing-configuration/app/fake-release-keystore.jks +0 -1
  112. package/tests/fixtures/android/advanced-security-fixtures/signing-configuration/build/generated-fake.keystore +0 -1
  113. package/tests/fixtures/android/compose-app/app/build.gradle.kts +0 -21
  114. package/tests/fixtures/android/compose-app/app/src/main/AndroidManifest.xml +0 -22
  115. package/tests/fixtures/android/compose-app/app/src/main/java/com/example/composeapp/MainActivity.kt +0 -11
  116. package/tests/fixtures/android/compose-app/build.gradle.kts +0 -4
  117. package/tests/fixtures/android/compose-app/gradle/wrapper/gradle-wrapper.properties +0 -4
  118. package/tests/fixtures/android/compose-app/settings.gradle.kts +0 -2
  119. package/tests/fixtures/android/gradle-metadata-fixtures/catalog-only-plugin/build.gradle.kts +0 -3
  120. package/tests/fixtures/android/gradle-metadata-fixtures/catalog-only-plugin/gradle/libs.versions.toml +0 -5
  121. package/tests/fixtures/android/gradle-metadata-fixtures/comment-safety.gradle.kts +0 -21
  122. package/tests/fixtures/android/gradle-metadata-fixtures/dynamic-values.gradle.kts +0 -17
  123. package/tests/fixtures/android/gradle-metadata-fixtures/groovy-literal.gradle +0 -23
  124. package/tests/fixtures/android/gradle-metadata-fixtures/wrapper-malformed.properties +0 -4
  125. package/tests/fixtures/android/gradle-metadata-fixtures/wrapper-missing-url.properties +0 -3
  126. package/tests/fixtures/android/gradle-metadata-fixtures/wrapper-with-checksum.properties +0 -5
  127. package/tests/fixtures/android/java-app/app/build.gradle +0 -16
  128. package/tests/fixtures/android/java-app/app/src/main/AndroidManifest.xml +0 -19
  129. package/tests/fixtures/android/java-app/app/src/main/java/com/example/javaapp/MainActivity.java +0 -4
  130. package/tests/fixtures/android/java-app/build.gradle +0 -3
  131. package/tests/fixtures/android/java-app/gradle/wrapper/gradle-wrapper.properties +0 -4
  132. package/tests/fixtures/android/java-app/settings.gradle +0 -2
  133. package/tests/fixtures/android/library/build.gradle.kts +0 -3
  134. package/tests/fixtures/android/library/gradle/wrapper/gradle-wrapper.properties +0 -4
  135. package/tests/fixtures/android/library/mylibrary/build.gradle.kts +0 -13
  136. package/tests/fixtures/android/library/mylibrary/src/main/AndroidManifest.xml +0 -2
  137. package/tests/fixtures/android/library/settings.gradle.kts +0 -2
  138. package/tests/fixtures/android/manifest-audit-fixtures/incomplete-intent-filter.xml +0 -14
  139. package/tests/fixtures/android/manifest-audit-fixtures/malformed-exported.xml +0 -10
  140. package/tests/fixtures/android/manifest-audit-fixtures/malformed.xml +0 -5
  141. package/tests/fixtures/android/manifest-audit-fixtures/missing-application.xml +0 -7
  142. package/tests/fixtures/android/manifest-audit-fixtures/missing-component-name.xml +0 -11
  143. package/tests/fixtures/android/manifest-audit-fixtures/permission-coverage.xml +0 -29
  144. package/tests/fixtures/android/manifest-audit-fixtures/risky-components.xml +0 -62
  145. package/tests/fixtures/android/manifest-audit-fixtures/risky-deep-links.xml +0 -45
  146. package/tests/fixtures/android/manifest-audit-fixtures/unresolved-placeholder.xml +0 -14
  147. package/tests/fixtures/android/manifest-audit-fixtures/unusual-namespace-prefix.xml +0 -11
  148. package/tests/fixtures/android/multi-module/app/build.gradle.kts +0 -22
  149. package/tests/fixtures/android/multi-module/app/src/androidTest/java/com/example/multimodule/app/ExampleInstrumentedTest.kt +0 -3
  150. package/tests/fixtures/android/multi-module/app/src/main/AndroidManifest.xml +0 -11
  151. package/tests/fixtures/android/multi-module/app/src/test/java/com/example/multimodule/app/ExampleUnitTest.kt +0 -3
  152. package/tests/fixtures/android/multi-module/build.gradle.kts +0 -5
  153. package/tests/fixtures/android/multi-module/core/build.gradle.kts +0 -14
  154. package/tests/fixtures/android/multi-module/core/src/main/AndroidManifest.xml +0 -2
  155. package/tests/fixtures/android/multi-module/feature-login/build.gradle.kts +0 -18
  156. package/tests/fixtures/android/multi-module/feature-login/src/main/AndroidManifest.xml +0 -2
  157. package/tests/fixtures/android/multi-module/gradle/libs.versions.toml +0 -8
  158. package/tests/fixtures/android/multi-module/gradle/wrapper/gradle-wrapper.properties +0 -4
  159. package/tests/fixtures/android/multi-module/settings.gradle.kts +0 -4
  160. package/tests/fixtures/android/non-android-gradle/build.gradle.kts +0 -3
  161. package/tests/fixtures/android/non-android-gradle/core/build.gradle.kts +0 -3
  162. package/tests/fixtures/android/non-android-gradle/core/src/main/java/com/example/core/Core.java +0 -4
  163. package/tests/fixtures/android/non-android-gradle/gradle/wrapper/gradle-wrapper.properties +0 -4
  164. package/tests/fixtures/android/non-android-gradle/settings.gradle.kts +0 -2
  165. package/tests/fixtures/android/partial/app/build.gradle.kts +0 -7
  166. package/tests/fixtures/android/partial/settings.gradle.kts +0 -2
  167. package/tests/fixtures/android/xml-view-app/app/build.gradle.kts +0 -17
  168. package/tests/fixtures/android/xml-view-app/app/src/main/AndroidManifest.xml +0 -31
  169. package/tests/fixtures/android/xml-view-app/app/src/main/kotlin/com/example/xmlviewapp/MainActivity.kt +0 -7
  170. package/tests/fixtures/android/xml-view-app/app/src/main/res/layout/activity_main.xml +0 -4
  171. package/tests/fixtures/android/xml-view-app/build.gradle.kts +0 -4
  172. package/tests/fixtures/android/xml-view-app/gradle/wrapper/gradle-wrapper.properties +0 -4
  173. package/tests/fixtures/android/xml-view-app/settings.gradle.kts +0 -2
  174. package/tests/fixtures/audits/mixed-language/mixed-docs-claims/README.md +0 -16
  175. package/tests/fixtures/audits/mixed-language/mixed-docs-claims/build.gradle.kts +0 -4
  176. package/tests/fixtures/audits/mixed-language/mixed-docs-claims/src/main/java/com/example/Widget.java +0 -7
  177. package/tests/fixtures/audits/mixed-language/mixed-docs-claims/src/main/kotlin/com/example/Gadget.kt +0 -7
  178. package/tests/fixtures/audits/mixed-language/mixed-generated-vendor/README.md +0 -10
  179. package/tests/fixtures/audits/mixed-language/mixed-generated-vendor/build/Main.class +0 -1
  180. package/tests/fixtures/audits/mixed-language/mixed-generated-vendor/dist/index.js +0 -1
  181. package/tests/fixtures/audits/mixed-language/mixed-generated-vendor/generated/schema.ts +0 -1
  182. package/tests/fixtures/audits/mixed-language/mixed-generated-vendor/src/index.ts +0 -3
  183. package/tests/fixtures/audits/mixed-language/mixed-generated-vendor/vendor/lib.py +0 -2
  184. package/tests/fixtures/audits/mixed-language/mixed-java-kotlin/README.md +0 -10
  185. package/tests/fixtures/audits/mixed-language/mixed-java-kotlin/build.gradle.kts +0 -8
  186. package/tests/fixtures/audits/mixed-language/mixed-java-kotlin/settings.gradle.kts +0 -1
  187. package/tests/fixtures/audits/mixed-language/mixed-java-kotlin/src/main/java/com/example/FooService.java +0 -7
  188. package/tests/fixtures/audits/mixed-language/mixed-java-kotlin/src/main/kotlin/com/example/BarService.kt +0 -7
  189. package/tests/fixtures/audits/mixed-language/mixed-java-kotlin/src/test/java/com/example/FooServiceTest.java +0 -8
  190. package/tests/fixtures/audits/mixed-language/mixed-java-kotlin/src/test/kotlin/com/example/BarServiceTest.kt +0 -8
  191. package/tests/fixtures/audits/mixed-language/mixed-python-ts/README.md +0 -8
  192. package/tests/fixtures/audits/mixed-language/mixed-python-ts/src/app.py +0 -7
  193. package/tests/fixtures/audits/mixed-language/mixed-python-ts/src/index.ts +0 -3
  194. package/tests/fixtures/audits/mixed-language/mixed-python-ts/tests/test_app.py +0 -5
  195. package/tests/fixtures/audits/mixed-language/mixed-ts-js/README.md +0 -7
  196. package/tests/fixtures/audits/mixed-language/mixed-ts-js/src/helper.js +0 -5
  197. package/tests/fixtures/audits/mixed-language/mixed-ts-js/src/index.ts +0 -3
  198. package/tests/fixtures/audits/mixed-language/mixed-ts-js/tests/index.sample.ts +0 -5
  199. package/tests/fixtures/audits/mixed-language/mixed-ts-python-jvm/README.md +0 -10
  200. package/tests/fixtures/audits/mixed-language/mixed-ts-python-jvm/build.gradle.kts +0 -4
  201. package/tests/fixtures/audits/mixed-language/mixed-ts-python-jvm/pytests/test_app.py +0 -5
  202. package/tests/fixtures/audits/mixed-language/mixed-ts-python-jvm/src/app.py +0 -2
  203. package/tests/fixtures/audits/mixed-language/mixed-ts-python-jvm/src/helper.js +0 -5
  204. package/tests/fixtures/audits/mixed-language/mixed-ts-python-jvm/src/index.ts +0 -3
  205. package/tests/fixtures/audits/mixed-language/mixed-ts-python-jvm/src/main/java/com/example/Multiplier.java +0 -7
  206. package/tests/fixtures/audits/mixed-language/mixed-ts-python-jvm/src/main/kotlin/com/example/Divider.kt +0 -7
  207. package/tests/fixtures/ecosystem/context-integrity/v0.4.5/corrected-replay/derived/run-integrity-evidence.json +0 -99
  208. package/tests/fixtures/ecosystem/context-integrity/v0.4.5/corrected-replay/producer/implementation-audit.json +0 -1585
  209. package/tests/fixtures/ecosystem/context-integrity/v0.4.5/corrected-replay/producer/implementation-capsule.json +0 -11455
  210. package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/00-request.txt +0 -1
  211. package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/artifacts/architecture-context-packet.txt +0 -82
  212. package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/artifacts/behavior-model.txt +0 -104
  213. package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/artifacts/final-report.txt +0 -41
  214. package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/artifacts/implementation-context-packet.txt +0 -114
  215. package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/artifacts/implementation-report.txt +0 -92
  216. package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/artifacts/judge-report.txt +0 -34
  217. package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/artifacts/pseudocode-packet.txt +0 -244
  218. package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/artifacts/request-brief.txt +0 -40
  219. package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/artifacts/test-context-packet.txt +0 -93
  220. package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/artifacts/test-implementation-report.txt +0 -69
  221. package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/artifacts/test-strategy-packet.txt +0 -354
  222. package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/artifacts/verification-report.txt +0 -85
  223. package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/derived/run-integrity-evidence.json +0 -98
  224. package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/producer/architecture-audit.json +0 -1169
  225. package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/producer/architecture-request.json +0 -29
  226. package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/producer/implementation-audit.json +0 -1494
  227. package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/producer/implementation-capsule.json +0 -11364
  228. package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/producer/implementation-request.json +0 -33
  229. package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/producer/test-request.json +0 -74
  230. package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/reports/architecture-context-retrieval-report.txt +0 -58
  231. package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/reports/implementation-context-retrieval-report.txt +0 -75
  232. package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/reports/test-context-retrieval-report.txt +0 -84
  233. package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/run.json +0 -62
  234. package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/tool-versions/my-dev-kit-version.txt +0 -1
  235. package/tests/fixtures/ecosystem/context-integrity/v0.4.5/failed-run/tool-versions/orchestrator-version.txt +0 -1
  236. package/tests/fixtures/ecosystem/context-integrity/v0.4.5/manifests/corrected-replay-manifest.json +0 -19
  237. package/tests/fixtures/ecosystem/context-integrity/v0.4.5/manifests/failed-run-manifest.json +0 -56
  238. package/tests/fixtures/fake-adversarial-cli.js +0 -152
  239. package/tests/fixtures/fake-my-dev-kit-cli.js +0 -83
  240. package/tests/fixtures/full-workflow-library/complete-v1.0.0.json +0 -24
  241. package/tests/fixtures/full-workflow-library/future-minor-v1.1.0-additive.json +0 -25
  242. package/tests/fixtures/stage-context-expectations/complete-v1.0.0.json +0 -230
  243. package/tests/fixtures/stage-context-expectations/future-minor-v1.1.0-additive.json +0 -232
  244. package/tests/fixtures/stage-context-expectations/invalid/malformed-json.txt +0 -1
  245. package/tests/fixtures/stage-context-expectations/invalid/non-object-root.json +0 -1
  246. package/tests/fixtures/stage-context-expectations/minimal-v1.0.0.json +0 -18
  247. package/tests/fixtures/upstream-artifacts/invalid/malformed-json.txt +0 -1
  248. package/tests/fixtures/upstream-artifacts/invalid/non-object-root.json +0 -1
  249. package/tests/fixtures/upstream-artifacts/my-dev-kit/1.10.2/context-capsule/complete-v1.0.0.json +0 -504
  250. package/tests/fixtures/upstream-artifacts/my-dev-kit/1.10.2/context-capsule/future-minor-v1.1.0-additive.json +0 -955
  251. package/tests/fixtures/upstream-artifacts/my-dev-kit/1.10.2/retrieval-audit-record/complete-v1.0.0.json +0 -190
  252. package/tests/fixtures/upstream-artifacts/my-dev-kit/1.10.2/retrieval-audit-record/future-minor-v1.1.0-additive.json +0 -327
  253. package/tests/fixtures/upstream-artifacts/my-dev-kit/1.10.4/PROVENANCE.txt +0 -18
  254. package/tests/fixtures/upstream-artifacts/my-dev-kit/1.10.4/context-capsule/complete-v1.0.0.json +0 -556
  255. package/tests/fixtures/upstream-artifacts/my-dev-kit/1.10.4/retrieval-audit-record/complete-v1.0.0.json +0 -238
  256. package/tests/fixtures/upstream-artifacts/my-dev-kit-1.10.2-identity/PROVENANCE.txt +0 -13
  257. package/tests/fixtures/upstream-artifacts/my-dev-kit-1.10.2-identity/context-capsule.json +0 -7874
  258. package/tests/fixtures/upstream-artifacts/my-dev-kit-1.10.2-identity/retrieval-audit-record.json +0 -1076
  259. package/tests/fixtures/upstream-artifacts/my-dev-kit-orchestrator/1.2.1/workflow-instruction-packet/complete-v1.0.0.json +0 -160
  260. package/tests/fixtures/upstream-artifacts/my-dev-kit-orchestrator/1.2.1/workflow-instruction-packet/future-minor-v1.1.0-additive.json +0 -186
  261. package/tests/fixtures/upstream-artifacts/my-dev-kit-orchestrator/1.2.3/run-integrity/PROVENANCE.txt +0 -31
  262. package/tests/fixtures/upstream-artifacts/my-dev-kit-orchestrator/1.2.3/run-integrity/ready-pass-final-pass.json +0 -132
  263. package/tests/fixtures/upstream-artifacts/my-dev-kit-orchestrator/1.2.3/run-integrity/refresh-required-need-context.json +0 -146
@@ -0,0 +1,271 @@
1
+ import path from "node:path";
2
+ import { runSecurityValidation } from "../securityValidation/validate/runSecurityValidation.js";
3
+ import { resolveValidationTarget, reportFilenamePrefix } from "../securityValidation/validate/resolveTarget.js";
4
+ import { buildSecurityReportFromSummary } from "../securityValidation/report/buildSecurityReport.js";
5
+ import { renderTextReport } from "../securityValidation/report/renderSecurityReport.js";
6
+ import { writeSecurityReportFiles } from "../securityValidation/report/writeSecurityReportFiles.js";
7
+ import { parseSecurityValidateArgs, normalizeSecurityValidateConfig, applyProfileDefaultChecksIfApplicable } from "../securityValidation/validate/cliOptions.js";
8
+ import { resolveAttackProfile } from "../securityValidation/attackScenarios/attackProfile.js";
9
+ import { findingsBreachFailOnThreshold } from "../securityValidation/validate/verdict.js";
10
+ import { validateAndroidTarget } from "../mobile/android/validate/validateAndroidTarget.js";
11
+ import { toAndroidReportModel } from "../mobile/android/report/model.js";
12
+ import { renderAndroidTextReport } from "../mobile/android/report/renderAndroidReport.js";
13
+ import { writeAndroidReportFiles } from "../mobile/android/report/writeAndroidReportFiles.js";
14
+ import { createLabExecutionContext } from "../runtime/index.js";
15
+ // ---------------------------------------------------------------------------
16
+ // v0.4.6 Batch 4 -- reusable security-validation command owner.
17
+ //
18
+ // Extracted from scripts/security/validate.ts (formerly a top-level script
19
+ // with inline try/catch + process.exit()) so both the contributor npm script
20
+ // and the installed CLI router (src/cli/) call the same argument-parsing/
21
+ // target-resolution/report-writing/exit-mapping path. Mirrors the audit
22
+ // command extraction in src/commands/runAuditCommand.ts (Batch 3). Detector/
23
+ // verdict/severity policy, the Android validator, and the classic check
24
+ // pipeline are untouched -- this module only owns CLI wiring.
25
+ // ---------------------------------------------------------------------------
26
+ export const SECURITY_VALIDATE_USAGE = "Usage: my-dev-kit-lab security validate [--target <path>] [--out <dir>] [--report-prefix <name>] " +
27
+ "[--checks deps,package,static,cli-adversarial,fuzz,boundary,subprocess,secrets,network] " +
28
+ "[--profile node-cli-package|local-tool|npm-package|android] [--format text,json] [--fail-on blocker|high|medium|low] " +
29
+ "[--android-gradle-operations wrapper-version,tasks,assemble-debug,unit-test-debug,lint-debug] " +
30
+ "[--android-external-tools semgrep,osv,android-lint,dependency-check] " +
31
+ "[--android-external-network deny|allow-requested]\n" +
32
+ "\n" +
33
+ "Android profile:\n" +
34
+ " --profile android performs static, read-only Android project detection, manifest parsing,\n" +
35
+ " permission/exported-component/intent-filter/deep-link audits, static Gradle metadata\n" +
36
+ " extraction, and (as of v0.4.1 Batch 8) eleven internal advanced security checks (network\n" +
37
+ " security config, backup/release configuration, hardcoded secrets, signing configuration,\n" +
38
+ " WebView, FileProvider, sensitive storage/logging, clipboard, and Firebase/Google services).\n" +
39
+ " Gradle and external tools are never executed by default.\n" +
40
+ " --android-gradle-operations explicitly opts into running ONLY the listed allowlisted Gradle\n" +
41
+ " operations (wrapper-version, tasks, assemble-debug, unit-test-debug, lint-debug) via the\n" +
42
+ " project's own Gradle wrapper. Arbitrary Gradle tasks cannot be passed. This option is only\n" +
43
+ " valid together with --profile android.\n" +
44
+ " --android-external-tools explicitly opts into running ONLY the listed allowlisted optional\n" +
45
+ " external security tools (semgrep, osv, android-lint, dependency-check). No arbitrary tool\n" +
46
+ " names, commands, or arguments can be passed. This option is only valid together with\n" +
47
+ " --profile android.\n" +
48
+ " --android-external-network controls whether a requested tool may use network access.\n" +
49
+ " Defaults to deny (osv is skipped unless supported offline data is available; semgrep,\n" +
50
+ " android-lint, and dependency-check always run offline/no-update regardless of this flag).\n" +
51
+ " allow-requested authorizes network only for an explicitly requested tool whose Batch 7\n" +
52
+ " contract supports it (currently only osv). This option is only valid together with\n" +
53
+ " --profile android.\n" +
54
+ " Example: my-dev-kit-lab security validate --target \"<android-project-path>\" --profile android\n" +
55
+ " Example: my-dev-kit-lab security validate --target \"<android-project-path>\" --profile android --android-gradle-operations wrapper-version,tasks\n" +
56
+ " Example: my-dev-kit-lab security validate --target \"<android-project-path>\" --profile android --android-external-tools semgrep,android-lint\n" +
57
+ " Example: my-dev-kit-lab security validate --target \"<android-project-path>\" --profile android --android-external-tools osv --android-external-network allow-requested";
58
+ export async function runSecurityValidationCommandFromArgs(argv, options = {}) {
59
+ const context = options.context ?? createLabExecutionContext();
60
+ const toolRoot = context.packageRoot;
61
+ const defaultOutRoot = options.defaultOutRoot ?? toolRoot;
62
+ // Help deliberately wins over every other argument. It must remain a
63
+ // no-work path: no target/default resolution, option normalization, report
64
+ // directory creation, validation orchestration, or subprocess execution.
65
+ if (argv.includes("--help") || argv.includes("-h")) {
66
+ console.log(SECURITY_VALIDATE_USAGE);
67
+ return 0;
68
+ }
69
+ let args;
70
+ let config;
71
+ try {
72
+ args = parseSecurityValidateArgs(argv);
73
+ config = normalizeSecurityValidateConfig(args, defaultOutRoot);
74
+ // v0.2.2 Batch 5: --profile with no --checks uses that profile's declared
75
+ // default checks. No-op unless the user passed --profile without --checks
76
+ // (see applyProfileDefaultChecksIfApplicable's guard) -- the true no-flag
77
+ // case and any explicit --checks case are always unaffected. Skipped for
78
+ // "android": that profile does not use the classic --checks pipeline.
79
+ if (config.profile !== "android") {
80
+ config = applyProfileDefaultChecksIfApplicable(config, resolveAttackProfile(config.profile).defaultCheckIds);
81
+ }
82
+ }
83
+ catch (err) {
84
+ const msg = err instanceof Error ? err.message : String(err);
85
+ console.error(`\nERROR: ${msg}`);
86
+ console.error(SECURITY_VALIDATE_USAGE);
87
+ return 2;
88
+ }
89
+ // Resolve and validate target early so we can fail fast with a clean error.
90
+ let target;
91
+ try {
92
+ target = resolveValidationTarget(args.target, toolRoot);
93
+ }
94
+ catch (err) {
95
+ const msg = err instanceof Error ? err.message : String(err);
96
+ console.error(`\nERROR: ${msg}`);
97
+ console.error(SECURITY_VALIDATE_USAGE);
98
+ return 1;
99
+ }
100
+ console.log("=".repeat(60));
101
+ console.log("my-dev-kit-lab security:validate");
102
+ console.log("=".repeat(60));
103
+ console.log(`Tool root : ${toolRoot}`);
104
+ if (!target.isSelf) {
105
+ console.log(`Target : ${target.targetRoot}`);
106
+ if (target.packageName)
107
+ console.log(`Package : ${target.packageName}${target.packageVersion ? `@${target.packageVersion}` : ""}`);
108
+ }
109
+ else {
110
+ console.log(`Mode : self-validation`);
111
+ }
112
+ console.log(`Profile : ${config.profile}`);
113
+ // ---------------------------------------------------------------------------
114
+ // v0.4.0 Batch 5 -- Android profile path.
115
+ //
116
+ // A separate orchestrator (not the classic check-group/attack-scenario
117
+ // pipeline below) because the Android check/finding/status vocabulary
118
+ // established in Batches 1-4 does not fit SecurityCheckId/SecurityCheckResult.
119
+ // Same command, same --profile flag, same reports/security/ output root.
120
+ // ---------------------------------------------------------------------------
121
+ if (config.profile === "android") {
122
+ if (config.androidGradleOperationIds.length > 0) {
123
+ console.log(`Gradle ops : ${config.androidGradleOperationIds.join(", ")} (explicitly requested — will execute via the target's Gradle wrapper)`);
124
+ }
125
+ else {
126
+ console.log(`Gradle ops : (none requested — static-only validation, zero Gradle process execution)`);
127
+ }
128
+ // v0.4.1 Batch 8 -- external tools remain explicit opt-in, mirroring Gradle
129
+ // operations exactly. "allow-requested" (the CLI-facing value) maps to the
130
+ // Batch 7 dispatcher's "allow-for-requested-tool" network policy value.
131
+ const externalNetworkPolicy = config.androidExternalNetworkPolicy === "allow-requested" ? "allow-for-requested-tool" : "deny";
132
+ if (config.androidExternalToolIds.length > 0) {
133
+ console.log(`External : ${config.androidExternalToolIds.join(", ")} (explicitly requested — network policy: ${config.androidExternalNetworkPolicy})`);
134
+ }
135
+ else {
136
+ console.log(`External : (none requested — zero external-tool process execution)`);
137
+ }
138
+ console.log("");
139
+ const reportsDir = config.out;
140
+ let result;
141
+ try {
142
+ result = await validateAndroidTarget({
143
+ toolRoot,
144
+ targetPath: args.target,
145
+ requestedGradleOperationIds: config.androidGradleOperationIds,
146
+ requestedExternalToolIds: config.androidExternalToolIds,
147
+ externalNetworkPolicy: externalNetworkPolicy,
148
+ externalToolArtifactRoot: path.join(reportsDir, "external-tools")
149
+ });
150
+ }
151
+ catch (err) {
152
+ const msg = err instanceof Error ? err.message : String(err);
153
+ console.error(`\nERROR: Android validation failed to run: ${msg}`);
154
+ return 3;
155
+ }
156
+ const reportModel = toAndroidReportModel(result, {
157
+ profile: config.profile,
158
+ requestedGradleOperations: config.androidGradleOperationIds,
159
+ requestedExternalTools: config.androidExternalToolIds,
160
+ externalNetworkPolicy: config.androidExternalNetworkPolicy
161
+ });
162
+ const textReport = renderAndroidTextReport(reportModel);
163
+ const prefix = args.reportPrefix ??
164
+ reportFilenamePrefix({
165
+ isSelf: result.target.local.isSelf,
166
+ packageName: result.target.local.packageName,
167
+ packageVersion: result.target.local.packageVersion,
168
+ targetRoot: result.target.local.targetRoot
169
+ });
170
+ let writtenPaths;
171
+ try {
172
+ ({ writtenPaths } = writeAndroidReportFiles({ outDir: reportsDir, prefix, report: reportModel, formats: config.formats }));
173
+ }
174
+ catch (err) {
175
+ const msg = err instanceof Error ? err.message : String(err);
176
+ console.error(`\nERROR: Failed to write Android report files: ${msg}`);
177
+ console.error("Validation completed but the report could not be written. The in-memory result was not persisted.");
178
+ return 4;
179
+ }
180
+ console.log(textReport);
181
+ console.log(`\nReports written:`);
182
+ for (const p of writtenPaths) {
183
+ console.log(` ${p}`);
184
+ }
185
+ const blockerExists = result.verdict === "not-ready-security-blocker-remains";
186
+ const inconclusive = result.verdict === "inconclusive-audit-environment-incomplete";
187
+ if (blockerExists) {
188
+ console.error("\nExit 1 — security blocker remains.");
189
+ return 1;
190
+ }
191
+ else if (inconclusive) {
192
+ console.warn("\nExit 2 — android environment incomplete.");
193
+ return 2;
194
+ }
195
+ else {
196
+ console.log("\nExit 0 — validation completed.");
197
+ return 0;
198
+ }
199
+ }
200
+ // ---------------------------------------------------------------------------
201
+ // Classic check-group/attack-scenario pipeline (unchanged from prior versions).
202
+ // ---------------------------------------------------------------------------
203
+ if (!config.checksWereDefault) {
204
+ console.log(`Checks : ${config.checks.join(", ")}`);
205
+ }
206
+ if (config.plannedChecksRequested.length > 0) {
207
+ console.log(`Note : ${config.plannedChecksRequested.join(", ")} are routed through the attack-scenario framework rather than the classic check-group runners. Review the attack-scenario section in the report for their concrete results.`);
208
+ }
209
+ console.log("");
210
+ const summary = await runSecurityValidation({
211
+ cwd: toolRoot,
212
+ targetPath: args.target,
213
+ fuzzIterations: parseInt(process.env["FUZZ_ITERATIONS"] ?? "50", 10),
214
+ fuzzSeed: parseInt(process.env["FUZZ_SEED"] ?? "0xDEADBEEF", 16),
215
+ // Full selection (implemented + planned/attack-scenario ids) -- the runner
216
+ // gates both implemented check groups and attack-scenario checks off the
217
+ // same selectedChecks set.
218
+ selectedChecks: config.checks,
219
+ profile: config.profile
220
+ });
221
+ // v0.2.2 Batch 5: --fail-on breach is additive to (never a substitute for)
222
+ // the existing verdict-based exit decision below -- it can only escalate
223
+ // exit 0 to exit 1 (e.g. --fail-on medium/low catching minor/informational
224
+ // findings the verdict policy doesn't already treat as blocking), never
225
+ // downgrade an existing blocker.
226
+ const failOnBreached = findingsBreachFailOnThreshold(summary.findings, config.failOnThreshold);
227
+ // Build report object
228
+ const report = buildSecurityReportFromSummary(summary, {
229
+ profile: config.profile,
230
+ selectedChecks: config.checks,
231
+ failOnThreshold: config.failOnThreshold,
232
+ formats: config.formats,
233
+ failOnBreached
234
+ });
235
+ const textReport = renderTextReport(report);
236
+ // Determine output directory
237
+ const reportsDir = config.out;
238
+ // Determine report filename prefix
239
+ const prefix = args.reportPrefix ?? reportFilenamePrefix(target);
240
+ const { writtenPaths } = writeSecurityReportFiles({
241
+ outDir: reportsDir,
242
+ prefix,
243
+ report,
244
+ formats: config.formats
245
+ });
246
+ // Print report to stdout
247
+ console.log(textReport);
248
+ console.log(`\nReports written:`);
249
+ for (const p of writtenPaths) {
250
+ console.log(` ${p}`);
251
+ }
252
+ // Exit code based on verdict, escalated (never downgraded) by --fail-on.
253
+ const blockerExists = summary.verdict === "not-ready-security-blocker-remains";
254
+ const inconclusive = summary.verdict === "inconclusive-audit-environment-incomplete";
255
+ if (blockerExists) {
256
+ console.error("\nExit 1 — security blocker remains.");
257
+ return 1;
258
+ }
259
+ else if (inconclusive) {
260
+ console.warn("\nExit 2 — audit environment incomplete.");
261
+ return 2;
262
+ }
263
+ else if (failOnBreached) {
264
+ console.error(`\nExit 1 — --fail-on ${config.failOnThreshold} threshold breached.`);
265
+ return 1;
266
+ }
267
+ else {
268
+ console.log("\nExit 0 — validation completed.");
269
+ return 0;
270
+ }
271
+ }
@@ -0,0 +1,170 @@
1
+ import { createLabExecutionContext } from "../runtime/index.js";
2
+ import { runTutorial } from "../tutorial/index.js";
3
+ // ---------------------------------------------------------------------------
4
+ // v0.4.7 Batch 2 -- tutorial run command owner.
5
+ //
6
+ // A thin adapter: parse a closed option set, resolve execution roots through
7
+ // LabExecutionContext, delegate to runTutorial(), render a summary, map the run
8
+ // status to an exit code. No tutorial semantics live here.
9
+ // ---------------------------------------------------------------------------
10
+ export const TUTORIAL_CLI_USAGE_EXIT_CODE = 2;
11
+ const RUN_USAGE = "Usage: my-dev-kit-lab tutorial run --scenario <path> --target-contract <path> [--out <dir>] [--json]";
12
+ export async function runTutorialRunCommandFromArgs(argv, options = {}) {
13
+ const writers = options.writers ?? {
14
+ stdout: (message) => console.log(message),
15
+ stderr: (message) => console.error(message)
16
+ };
17
+ const parsed = parseRunArgs(argv);
18
+ if (!parsed.ok) {
19
+ writers.stderr(parsed.error);
20
+ return TUTORIAL_CLI_USAGE_EXIT_CODE;
21
+ }
22
+ const context = options.context ?? createLabExecutionContext();
23
+ const execute = options.runTutorialImpl ?? runTutorial;
24
+ const result = await execute({
25
+ scenarioPath: parsed.scenarioPath,
26
+ targetContractPath: parsed.targetContractPath,
27
+ context,
28
+ ...(parsed.outDir !== undefined ? { outDir: parsed.outDir } : {}),
29
+ ...(options.launchBrowser ? { launchBrowser: options.launchBrowser } : {}),
30
+ ...(options.generateRunId ? { generateRunId: options.generateRunId } : {})
31
+ });
32
+ if (parsed.json) {
33
+ writers.stdout(JSON.stringify(result, null, 2));
34
+ }
35
+ else {
36
+ writers.stdout(renderRunSummary(result));
37
+ }
38
+ return result.status === "passed" ? 0 : 1;
39
+ }
40
+ /**
41
+ * Concise by design: identity, location, outcome, and the first thing that went
42
+ * wrong. Child-process logs are written to the run's logs directory and are
43
+ * never dumped to the terminal automatically.
44
+ */
45
+ function renderRunSummary(result) {
46
+ const lines = [];
47
+ lines.push(`Scenario ID: ${result.scenarioId ?? "(not loaded)"}`);
48
+ lines.push(`Target ID: ${result.targetId ?? "(not loaded)"}`);
49
+ lines.push(`Run ID: ${result.runId ?? "(not started)"}`);
50
+ lines.push(`Run root: ${result.paths?.runRoot ?? "(not created)"}`);
51
+ lines.push(`Status: ${result.status}`);
52
+ lines.push(`Duration: ${result.durationMs}ms`);
53
+ const stepCounts = summarizeSteps(result);
54
+ if (result.steps.length > 0) {
55
+ lines.push(`Steps: ${result.steps.length} (passed ${stepCounts.passed}, failed ${stepCounts.failed}, not-run ${stepCounts.notRun})`);
56
+ }
57
+ const failedStep = result.steps.find((step) => step.status === "failed");
58
+ if (failedStep) {
59
+ lines.push(`Failed step: ${failedStep.id}`);
60
+ }
61
+ lines.push(...renderArtifactLines(result));
62
+ if (result.error) {
63
+ lines.push(`Error: ${result.error}`);
64
+ }
65
+ for (const warning of result.warnings) {
66
+ lines.push(`Warning: ${warning}`);
67
+ }
68
+ for (const cleanupError of result.cleanupErrors) {
69
+ lines.push(`Cleanup error: ${cleanupError}`);
70
+ }
71
+ return lines.join("\n");
72
+ }
73
+ /**
74
+ * One line per canonical artifact that was actually written, plus a screenshot
75
+ * count. Failed or skipped artifacts are named so a reader sees why, but the
76
+ * manifest itself is never dumped here -- that is what --json is for.
77
+ */
78
+ function renderArtifactLines(result) {
79
+ const artifacts = result.artifacts;
80
+ const lines = [];
81
+ const named = [
82
+ ["Video", artifacts.video],
83
+ ["SRT", artifacts.srt],
84
+ ["VTT", artifacts.vtt],
85
+ ["Markdown", artifacts.markdown],
86
+ ["Manifest", artifacts.manifest]
87
+ ];
88
+ const written = named.filter(([, record]) => record?.status === "written");
89
+ for (const [label, record] of written) {
90
+ lines.push(`${label}: ${record.path ?? ""}`);
91
+ }
92
+ const writtenScreenshots = artifacts.screenshots.filter((record) => record.status === "written");
93
+ if (artifacts.screenshots.length > 0) {
94
+ lines.push(`Screenshots: ${writtenScreenshots.length} of ${artifacts.screenshots.length} written`);
95
+ }
96
+ for (const [label, record] of named) {
97
+ if (record && record.status !== "written") {
98
+ lines.push(`${label} artifact ${record.status}: ${record.error ?? "no detail"}`);
99
+ }
100
+ }
101
+ for (const record of artifacts.screenshots) {
102
+ if (record.status !== "written") {
103
+ lines.push(`Screenshot ${record.id ?? "?"} ${record.status}: ${record.error ?? "no detail"}`);
104
+ }
105
+ }
106
+ return lines;
107
+ }
108
+ function summarizeSteps(result) {
109
+ let passed = 0;
110
+ let failed = 0;
111
+ let notRun = 0;
112
+ for (const step of result.steps) {
113
+ if (step.status === "passed")
114
+ passed += 1;
115
+ else if (step.status === "failed")
116
+ failed += 1;
117
+ else
118
+ notRun += 1;
119
+ }
120
+ return { passed, failed, notRun };
121
+ }
122
+ function parseRunArgs(argv) {
123
+ let scenarioPath;
124
+ let targetContractPath;
125
+ let outDir;
126
+ let json = false;
127
+ for (let index = 0; index < argv.length; index += 1) {
128
+ const arg = argv[index];
129
+ if (arg === "--scenario") {
130
+ const value = argv[++index];
131
+ if (value === undefined) {
132
+ return { ok: false, error: `Missing value for --scenario. ${RUN_USAGE}` };
133
+ }
134
+ scenarioPath = value;
135
+ }
136
+ else if (arg === "--target-contract") {
137
+ const value = argv[++index];
138
+ if (value === undefined) {
139
+ return { ok: false, error: `Missing value for --target-contract. ${RUN_USAGE}` };
140
+ }
141
+ targetContractPath = value;
142
+ }
143
+ else if (arg === "--out") {
144
+ const value = argv[++index];
145
+ if (value === undefined) {
146
+ return { ok: false, error: `Missing value for --out. ${RUN_USAGE}` };
147
+ }
148
+ outDir = value;
149
+ }
150
+ else if (arg === "--json") {
151
+ json = true;
152
+ }
153
+ else {
154
+ return { ok: false, error: `Unknown argument for "tutorial run": ${arg}. ${RUN_USAGE}` };
155
+ }
156
+ }
157
+ if (scenarioPath === undefined) {
158
+ return { ok: false, error: `Missing required --scenario. ${RUN_USAGE}` };
159
+ }
160
+ if (targetContractPath === undefined) {
161
+ return { ok: false, error: `Missing required --target-contract. ${RUN_USAGE}` };
162
+ }
163
+ return {
164
+ ok: true,
165
+ scenarioPath,
166
+ targetContractPath,
167
+ ...(outDir !== undefined ? { outDir } : {}),
168
+ json
169
+ };
170
+ }
@@ -0,0 +1,138 @@
1
+ import { createLabExecutionContext } from "../runtime/index.js";
2
+ import { describeTargetIdMismatch, loadTutorialScenario, loadTutorialTargetContract, tutorialTargetIdsMatch } from "../tutorial/index.js";
3
+ // ---------------------------------------------------------------------------
4
+ // v0.4.7 Batch 2 -- tutorial contract validation command owner.
5
+ //
6
+ // Read-only by construction: it loads and validates contracts and nothing else.
7
+ // No run directory is created, no prepare command runs, no process starts and
8
+ // no browser launches, so this route is safe to run anywhere and needs neither
9
+ // Chromium nor a prepared target.
10
+ // ---------------------------------------------------------------------------
11
+ export const TUTORIAL_CLI_USAGE_EXIT_CODE = 2;
12
+ export async function runTutorialValidateCommandFromArgs(argv, options = {}) {
13
+ const writers = options.writers ?? {
14
+ stdout: (message) => console.log(message),
15
+ stderr: (message) => console.error(message)
16
+ };
17
+ const parsed = parseValidateArgs(argv);
18
+ if (!parsed.ok) {
19
+ writers.stderr(parsed.error);
20
+ return TUTORIAL_CLI_USAGE_EXIT_CODE;
21
+ }
22
+ const context = options.context ?? createLabExecutionContext();
23
+ const report = { status: "valid", scenarioPath: parsed.scenarioPath, errors: [] };
24
+ const loadedScenario = await loadTutorialScenario(context.invocationCwd, parsed.scenarioPath);
25
+ if (!loadedScenario.ok) {
26
+ report.status = "invalid";
27
+ report.errors.push(...loadedScenario.errors);
28
+ }
29
+ else {
30
+ report.scenarioPath = loadedScenario.value.scenarioPath;
31
+ report.scenarioId = loadedScenario.value.scenario.id;
32
+ report.scenarioTitle = loadedScenario.value.scenario.title;
33
+ report.targetIdDeclared = loadedScenario.value.scenario.targetId;
34
+ report.stepCount = loadedScenario.value.scenario.steps.length;
35
+ }
36
+ if (parsed.targetContractPath !== undefined) {
37
+ const loadedTarget = await loadTutorialTargetContract(context.invocationCwd, parsed.targetContractPath);
38
+ if (!loadedTarget.ok) {
39
+ report.status = "invalid";
40
+ report.targetContractPath = parsed.targetContractPath;
41
+ report.errors.push(...loadedTarget.errors);
42
+ }
43
+ else {
44
+ report.targetContractPath = loadedTarget.value.targetContractPath;
45
+ report.targetContractId = loadedTarget.value.targetContract.id;
46
+ report.processCount = loadedTarget.value.targetContract.processes.length;
47
+ report.applicationUrl = loadedTarget.value.targetContract.applicationUrl;
48
+ if (loadedScenario.ok) {
49
+ const matches = tutorialTargetIdsMatch(loadedScenario.value.scenario.targetId, loadedTarget.value.targetContract.id);
50
+ report.targetIdMatches = matches;
51
+ if (!matches) {
52
+ report.status = "invalid";
53
+ report.errors.push(describeTargetIdMismatch(loadedScenario.value.scenario.targetId, loadedTarget.value.targetContract.id));
54
+ }
55
+ }
56
+ }
57
+ }
58
+ if (parsed.json) {
59
+ writers.stdout(JSON.stringify(report, null, 2));
60
+ }
61
+ else {
62
+ writers.stdout(renderValidateSummary(report));
63
+ }
64
+ return report.status === "valid" ? 0 : 1;
65
+ }
66
+ function renderValidateSummary(report) {
67
+ const lines = [];
68
+ lines.push(`Scenario: ${report.scenarioPath}`);
69
+ if (report.scenarioId) {
70
+ lines.push(`Scenario ID: ${report.scenarioId}`);
71
+ lines.push(`Title: ${report.scenarioTitle ?? ""}`);
72
+ lines.push(`Declared target ID: ${report.targetIdDeclared ?? ""}`);
73
+ lines.push(`Steps: ${report.stepCount ?? 0}`);
74
+ }
75
+ if (report.targetContractPath) {
76
+ lines.push(`Target contract: ${report.targetContractPath}`);
77
+ }
78
+ if (report.targetContractId) {
79
+ lines.push(`Target contract ID: ${report.targetContractId}`);
80
+ lines.push(`Managed processes: ${report.processCount ?? 0}`);
81
+ lines.push(`Application URL: ${report.applicationUrl ?? ""}`);
82
+ }
83
+ lines.push(`Status: ${report.status}`);
84
+ if (report.errors.length > 0) {
85
+ lines.push("");
86
+ lines.push("Errors:");
87
+ for (const error of report.errors) {
88
+ lines.push(` - ${error}`);
89
+ }
90
+ }
91
+ return lines.join("\n");
92
+ }
93
+ function parseValidateArgs(argv) {
94
+ let scenarioPath;
95
+ let targetContractPath;
96
+ let json = false;
97
+ for (let index = 0; index < argv.length; index += 1) {
98
+ const arg = argv[index];
99
+ if (arg === "--scenario") {
100
+ const value = argv[++index];
101
+ if (value === undefined) {
102
+ return { ok: false, error: "Missing value for --scenario. Expected a path to a tutorial scenario JSON file." };
103
+ }
104
+ scenarioPath = value;
105
+ }
106
+ else if (arg === "--target-contract") {
107
+ const value = argv[++index];
108
+ if (value === undefined) {
109
+ return {
110
+ ok: false,
111
+ error: "Missing value for --target-contract. Expected a path to a tutorial target contract JSON file."
112
+ };
113
+ }
114
+ targetContractPath = value;
115
+ }
116
+ else if (arg === "--json") {
117
+ json = true;
118
+ }
119
+ else {
120
+ return {
121
+ ok: false,
122
+ error: `Unknown argument for "tutorial validate": ${arg}. Usage: my-dev-kit-lab tutorial validate --scenario <path> [--target-contract <path>] [--json]`
123
+ };
124
+ }
125
+ }
126
+ if (scenarioPath === undefined) {
127
+ return {
128
+ ok: false,
129
+ error: "Missing required --scenario. Usage: my-dev-kit-lab tutorial validate --scenario <path> [--target-contract <path>] [--json]"
130
+ };
131
+ }
132
+ return {
133
+ ok: true,
134
+ scenarioPath,
135
+ ...(targetContractPath !== undefined ? { targetContractPath } : {}),
136
+ json
137
+ };
138
+ }
@@ -0,0 +1,52 @@
1
+ import { spawn } from "node:child_process";
2
+ /**
3
+ * Asks a process to terminate normally.
4
+ *
5
+ * Sends SIGTERM on every platform. On Windows Node maps SIGTERM to
6
+ * TerminateProcess, so the named process is terminated but its descendants are
7
+ * not; this is a best-effort "please stop" step, not a tree operation.
8
+ *
9
+ * Never throws: the process may already have exited between the caller's
10
+ * liveness check and this call.
11
+ */
12
+ export function requestProcessTermination(pid) {
13
+ if (!pid) {
14
+ return;
15
+ }
16
+ try {
17
+ process.kill(pid, "SIGTERM");
18
+ }
19
+ catch {
20
+ // The process may have exited between the liveness check and this signal.
21
+ }
22
+ }
23
+ /**
24
+ * Forced cleanup for a process that did not stop on request.
25
+ *
26
+ * Windows: `taskkill /pid <pid> /T /F` with shell:false. `/T` makes this a real
27
+ * descendant-tree termination on Windows.
28
+ *
29
+ * Other platforms: a single `process.kill(pid, signal)` on the known process ID.
30
+ * This deliberately does NOT walk or signal the descendant tree -- no process
31
+ * group or `pgid` traversal is performed here -- so a child that spawned its own
32
+ * grandchildren may leave those grandchildren running. The file name reflects
33
+ * the Windows capability and the shared call site, not a POSIX tree guarantee.
34
+ *
35
+ * Never throws.
36
+ */
37
+ export function forceTerminateProcess(pid, options = {}) {
38
+ if (!pid) {
39
+ return;
40
+ }
41
+ if (process.platform === "win32") {
42
+ const killer = spawn("taskkill", ["/pid", String(pid), "/T", "/F"], { shell: false, stdio: "ignore" });
43
+ killer.on("error", () => undefined);
44
+ return;
45
+ }
46
+ try {
47
+ process.kill(pid, options.posixSignal ?? "SIGTERM");
48
+ }
49
+ catch {
50
+ // The process may have exited between the liveness check and this signal.
51
+ }
52
+ }
@@ -159,3 +159,37 @@ function isExecutableFile(candidatePath) {
159
159
  return false;
160
160
  }
161
161
  }
162
+ /**
163
+ * Builds the spawn-ready invocation for an already-resolved command.
164
+ *
165
+ * Exposed separately from `resolveCommandInvocation` so callers that legitimately
166
+ * bypass PATH resolution (for example `runMeasuredCommand({ resolveCommand: false })`)
167
+ * still share one argument-assembly rule.
168
+ *
169
+ * `windows-cmd-shim` resolutions run `cmd.exe /d /s /c call <resolvedPath> ...`,
170
+ * so the resolved shim path is inserted after the prefix. Every other resolution
171
+ * kind (including `windows-powershell-shim`, whose `argsPrefix` already ends with
172
+ * `-File <resolvedPath>`) only needs prefix + caller arguments.
173
+ */
174
+ export function buildResolvedCommandInvocation(resolvedCommand, args) {
175
+ const trailingArgs = [...args];
176
+ const invocationArgs = resolvedCommand.resolutionKind === "windows-cmd-shim" && resolvedCommand.resolvedPath
177
+ ? [...resolvedCommand.argsPrefix, resolvedCommand.resolvedPath, ...trailingArgs]
178
+ : [...resolvedCommand.argsPrefix, ...trailingArgs];
179
+ return {
180
+ executable: resolvedCommand.command,
181
+ args: invocationArgs,
182
+ resolvedCommand
183
+ };
184
+ }
185
+ /**
186
+ * Resolves `executable` through `resolveCommand()` and returns the spawn-ready
187
+ * executable/argument pair. Never introduces `shell: true`.
188
+ *
189
+ * An unavailable command is returned as a structured invocation whose
190
+ * `resolvedCommand.resolutionKind` is "unavailable"; callers decide whether that
191
+ * is a skip or a failure.
192
+ */
193
+ export function resolveCommandInvocation(executable, args, options = {}) {
194
+ return buildResolvedCommandInvocation(resolveCommand(executable, options), args);
195
+ }