@thomasminh1995/depverdict 0.6.0-alpha.1

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 (252) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +518 -0
  3. package/bin/depverdict.js +7 -0
  4. package/bin/upgradelens.js +7 -0
  5. package/docs/GR-01-Semantic-Grounding-Failure-Analysis.md +309 -0
  6. package/docs/GR-02-Versioned-Action-Evaluation-Criteria.md +187 -0
  7. package/docs/GR-03-Extractive-Contract-Safety-Experiment.md +242 -0
  8. package/docs/GR-04-Versioned-Production-Extractive-Contract.md +154 -0
  9. package/docs/IA-01-Repository-Usage-Discovery.md +122 -0
  10. package/docs/IA-02-Repository-Impact-Analysis.md +118 -0
  11. package/docs/IA-03-Repository-Impact-Evidence.md +160 -0
  12. package/docs/IA-04-CLI-Orchestration.md +235 -0
  13. package/docs/IA-05-Real-Provider-Validation.md +235 -0
  14. package/docs/IA-05-VinGrade-Validation.md +339 -0
  15. package/docs/MVP-01.md +48 -0
  16. package/docs/MVP-02-Architecture.md +536 -0
  17. package/docs/MVP-02-CLI-HTTP-Runtime.md +50 -0
  18. package/docs/MVP-02-HTTP-Lifecycle.md +37 -0
  19. package/docs/MVP-02-Knowledge-Manifest-Generation.md +75 -0
  20. package/docs/MVP-02-Knowledge-Manifest.md +279 -0
  21. package/docs/MVP-02-Knowledge-Research-Orchestration.md +62 -0
  22. package/docs/MVP-02-Knowledge-Store.md +87 -0
  23. package/docs/MVP-02-PyPI-Registry-Adapter.md +97 -0
  24. package/docs/MVP-02-Research-Planning.md +196 -0
  25. package/docs/MVP-02-Source-Provenance.md +81 -0
  26. package/docs/MVP-02-npm-Registry-Adapter.md +99 -0
  27. package/docs/RR-01-End-to-End-and-Real-Provider-Validation.md +393 -0
  28. package/docs/RR-01-RERUN-Extractive-Contract-Validation.md +566 -0
  29. package/docs/RR-02-Full-Product-Workflow-and-Developer-CLI-UX-Review.md +35 -0
  30. package/docs/RR02-FIX-01-Persistent-Qualification-Resolution.md +32 -0
  31. package/docs/RR02-FIX-02-Stage-aware-CLI-Progress-and-Heartbeat.md +28 -0
  32. package/docs/RR02-FIX-03-npm-Capture-Evidence-Exclusion.md +33 -0
  33. package/docs/RR02-FIX-03A-Complete-Package-Exclusion-and-Evidence-Commit.md +223 -0
  34. package/docs/RR02-FIX-04-Event-loop-safe-Heartbeat.md +393 -0
  35. package/docs/RR02-FIX-05-Materialize-Persisted-Qualification.md +207 -0
  36. package/docs/RR02-RERUN-CLI-Qualification-Progress-UX-and-Package-Validation.md +342 -0
  37. package/docs/VinGrade-MVP-02-Validation.md +480 -0
  38. package/docs/VinGrade-RC02-Live-Validation.md +273 -0
  39. package/docs/ai-capability-discovery.md +573 -0
  40. package/docs/ai-engineering-production-readiness.md +390 -0
  41. package/docs/ai-engineering-review.md +251 -0
  42. package/docs/ai-runtime-governance-discovery.md +754 -0
  43. package/docs/architecture-overview.md +78 -0
  44. package/docs/cli-progress.md +111 -0
  45. package/docs/decisions/diff-01-brand-distribution-identity.md +450 -0
  46. package/docs/decisions/diff-02-identity-compatibility-contract.md +274 -0
  47. package/docs/decisions/diff-03-repository-docs-community-migration.md +165 -0
  48. package/docs/decisions/diff-04-release-evidence-gap-acceptance.md +110 -0
  49. package/docs/discovery/mvp-05-ai-migration-planning-discovery.md +350 -0
  50. package/docs/gateway-runtime-discovery.md +614 -0
  51. package/docs/live-ai-validation.md +299 -0
  52. package/docs/migration-planning-qualification-resolution.md +99 -0
  53. package/docs/migrations/upgradelens-to-depverdict.md +97 -0
  54. package/docs/mp-r03-deterministic-upgrade-decision-architecture.md +135 -0
  55. package/docs/mp-r04-evidence-bounded-migration-handoff-architecture.md +116 -0
  56. package/docs/mp-r05-product-completion-and-decision-first-cli-architecture.md +162 -0
  57. package/docs/mvp-05-deterministic-context-runtime.md +127 -0
  58. package/docs/mvp-05-migration-checklist-contract.md +166 -0
  59. package/docs/mvp-05-migration-checklist-orchestration.md +105 -0
  60. package/docs/mvp-05-migration-evaluation-and-qualification.md +135 -0
  61. package/docs/mvp-05-provider-neutral-generator.md +79 -0
  62. package/docs/ollama-local-smoke-validation.md +156 -0
  63. package/docs/openai-compatible-runtime-discovery.md +789 -0
  64. package/docs/openrouter-one-dependency-validation.md +205 -0
  65. package/docs/oss-02-package-guard-hardening-architecture.md +131 -0
  66. package/docs/oss-04-public-ci-package-metadata-architecture.md +142 -0
  67. package/docs/package-content-policy.md +98 -0
  68. package/docs/releases/v0.5.0-technical-preview.md +114 -0
  69. package/docs/releases/v0.6.0-alpha.1-depverdict-preview.md +101 -0
  70. package/docs/reviews/diff-02-identity-contract-compatibility.md +413 -0
  71. package/docs/reviews/diff-03-repository-docs-community-migration.md +364 -0
  72. package/docs/reviews/diff-04-depverdict-distribution-identity-readiness-rereview.md +453 -0
  73. package/docs/reviews/diff-04-fix-post-rename-identity-release-remediation.md +310 -0
  74. package/docs/reviews/diff-05-final-preview-distribution-qualification.md +561 -0
  75. package/docs/reviews/mvp-05-final-product-value-workflow-rereview.md +471 -0
  76. package/docs/reviews/mvp-05-product-workflow-review.md +605 -0
  77. package/docs/reviews/oss-01-duplicate-artifact-investigation-cleanup.md +411 -0
  78. package/docs/reviews/oss-02-package-guard-hardening.md +303 -0
  79. package/docs/reviews/oss-03-community-scaffolding.md +378 -0
  80. package/docs/reviews/oss-04-public-ci-package-metadata.md +439 -0
  81. package/docs/reviews/oss-05-technical-preview-qualification.md +482 -0
  82. package/docs/reviews/upgradelens-vs-upgradedepdetective-source-comparison.md +355 -0
  83. package/docs/reviews/v0.5.0-pre-release-smoke.md +263 -0
  84. package/docs/reviews/v0.5.0-version-bump-release-verification.md +306 -0
  85. package/docs/runtime-contract-discovery.md +521 -0
  86. package/docs/structured-output-compatibility-report.md +100 -0
  87. package/docs/ts-fix-01-exact-duplicate-occurrence-target-selection-architecture.md +111 -0
  88. package/docs/version-analysis-architecture.md +827 -0
  89. package/eval/README.md +86 -0
  90. package/eval/datasets/generic/declared-constraint.json +59 -0
  91. package/eval/datasets/generic/evidence-conflict.json +73 -0
  92. package/eval/datasets/generic/major-breaking-release.json +66 -0
  93. package/eval/datasets/generic/missing-evidence.json +46 -0
  94. package/eval/datasets/generic/patch-release-low.json +59 -0
  95. package/eval/datasets/node/axios-patch-low.json +59 -0
  96. package/eval/datasets/node/react-major-breaking.json +66 -0
  97. package/eval/datasets/node/react-minor-compatibility.json +66 -0
  98. package/eval/datasets/python/fastapi-deprecation.json +66 -0
  99. package/eval/datasets/python/pydantic-major-breaking.json +66 -0
  100. package/eval/migration-planning/golden-dataset-v2.json +306 -0
  101. package/eval/migration-planning/golden-dataset.json +214 -0
  102. package/eval/schemas/expected-result.schema.json +88 -0
  103. package/eval/schemas/golden-case.schema.json +181 -0
  104. package/package.json +57 -0
  105. package/schemas/ai-scorecard.schema.json +132 -0
  106. package/schemas/benchmark-report.schema.json +189 -0
  107. package/schemas/benchmark.schema.json +55 -0
  108. package/schemas/capability-profile.schema.json +44 -0
  109. package/schemas/conformance-report.schema.json +150 -0
  110. package/schemas/deployment-profile.schema.json +64 -0
  111. package/schemas/evaluation-report.schema.json +158 -0
  112. package/schemas/knowledge-evidence-bundle.schema.json +150 -0
  113. package/schemas/knowledge-manifest.schema.json +548 -0
  114. package/schemas/metrics.schema.json +178 -0
  115. package/schemas/migration-checklist-extractive-candidate.schema.json +42 -0
  116. package/schemas/migration-checklist.schema.json +706 -0
  117. package/schemas/migration-evaluation-dataset-v2.schema.json +208 -0
  118. package/schemas/migration-evaluation-dataset.schema.json +204 -0
  119. package/schemas/migration-planning-qualification-record.schema.json +234 -0
  120. package/schemas/project-manifest.schema.json +308 -0
  121. package/schemas/qualification-record.schema.json +56 -0
  122. package/schemas/repository-impact-evidence.schema.json +232 -0
  123. package/schemas/repository-impact.schema.json +202 -0
  124. package/schemas/upgrade-decision.schema.json +273 -0
  125. package/schemas/usage-index.schema.json +179 -0
  126. package/schemas/version-analysis.schema.json +449 -0
  127. package/src/ai-runtime-debug.js +325 -0
  128. package/src/ai-runtime-error.js +42 -0
  129. package/src/ai-runtime.js +174 -0
  130. package/src/ai-scorecard.js +204 -0
  131. package/src/ai-version-analysis.js +484 -0
  132. package/src/artifact-root-compatibility.js +91 -0
  133. package/src/benchmark-report.js +111 -0
  134. package/src/benchmark-runner.js +191 -0
  135. package/src/canonical-json.js +69 -0
  136. package/src/cli.js +1299 -0
  137. package/src/conformance-report.js +158 -0
  138. package/src/conformance-runner.js +253 -0
  139. package/src/constants.js +73 -0
  140. package/src/cooperative-scheduler.js +79 -0
  141. package/src/dependencies.js +44 -0
  142. package/src/dependency-ai-context.js +625 -0
  143. package/src/detectors.js +253 -0
  144. package/src/discovery.js +234 -0
  145. package/src/ecosystem-version-adapter.js +294 -0
  146. package/src/environment-compatibility.js +77 -0
  147. package/src/evaluation-comparator.js +158 -0
  148. package/src/evaluation-report.js +76 -0
  149. package/src/evaluation-runner.js +248 -0
  150. package/src/evidence-source-adapter.js +472 -0
  151. package/src/files.js +89 -0
  152. package/src/governance-diagnostics.js +64 -0
  153. package/src/governance-loader.js +63 -0
  154. package/src/governance-metadata.js +346 -0
  155. package/src/governance-validator.js +360 -0
  156. package/src/http/bounded-fetch.js +278 -0
  157. package/src/http/cli-http-runtime.js +44 -0
  158. package/src/impact/input-loader.js +157 -0
  159. package/src/impact/matcher.js +40 -0
  160. package/src/impact/repository-impact.js +199 -0
  161. package/src/impact/runtime.js +24 -0
  162. package/src/impact/status.js +62 -0
  163. package/src/impact/writer.js +30 -0
  164. package/src/impact-evidence/input-loader.js +202 -0
  165. package/src/impact-evidence/repository-impact-evidence.js +234 -0
  166. package/src/impact-evidence/runtime.js +16 -0
  167. package/src/impact-evidence/writer.js +30 -0
  168. package/src/index.js +563 -0
  169. package/src/installed-version-baseline.js +196 -0
  170. package/src/knowledge-cache.js +324 -0
  171. package/src/knowledge-evidence-bundle.js +101 -0
  172. package/src/knowledge-evidence-producer.js +233 -0
  173. package/src/knowledge-manifest-builder.js +188 -0
  174. package/src/knowledge-manifest-writer.js +32 -0
  175. package/src/knowledge-manifest.js +255 -0
  176. package/src/knowledge-research.js +615 -0
  177. package/src/metrics-engine.js +205 -0
  178. package/src/migration-checklist/ai-candidate.js +320 -0
  179. package/src/migration-checklist/assembler.js +37 -0
  180. package/src/migration-checklist/context-runtime.js +828 -0
  181. package/src/migration-checklist/evaluation/action-criteria.js +244 -0
  182. package/src/migration-checklist/evaluation/comparator-v2.js +332 -0
  183. package/src/migration-checklist/evaluation/comparator.js +279 -0
  184. package/src/migration-checklist/evaluation/dataset-v2.js +227 -0
  185. package/src/migration-checklist/evaluation/dataset.js +336 -0
  186. package/src/migration-checklist/evaluation/extractive-fixtures-v2.js +148 -0
  187. package/src/migration-checklist/evaluation/metrics-v2.js +226 -0
  188. package/src/migration-checklist/evaluation/metrics.js +158 -0
  189. package/src/migration-checklist/evaluation/qualification-v2.js +321 -0
  190. package/src/migration-checklist/evaluation/qualification.js +239 -0
  191. package/src/migration-checklist/evaluation/runner-v2.js +294 -0
  192. package/src/migration-checklist/evaluation/runner.js +194 -0
  193. package/src/migration-checklist/evaluation/scorecard-v2.js +106 -0
  194. package/src/migration-checklist/evaluation/scorecard.js +86 -0
  195. package/src/migration-checklist/extractive-candidate.js +166 -0
  196. package/src/migration-checklist/extractive-prompt.js +62 -0
  197. package/src/migration-checklist/generator.js +702 -0
  198. package/src/migration-checklist/grounding-policy.js +117 -0
  199. package/src/migration-checklist/input-loader.js +613 -0
  200. package/src/migration-checklist/migration-checklist.js +635 -0
  201. package/src/migration-checklist/presentation.js +292 -0
  202. package/src/migration-checklist/progress.js +96 -0
  203. package/src/migration-checklist/prompt.js +83 -0
  204. package/src/migration-checklist/qualification-guard.js +462 -0
  205. package/src/migration-checklist/qualification-resolution.js +122 -0
  206. package/src/migration-checklist/qualification-store.js +225 -0
  207. package/src/migration-checklist/runtime.js +205 -0
  208. package/src/migration-checklist/verification.js +134 -0
  209. package/src/migration-checklist/writer.js +35 -0
  210. package/src/openai-compatible-provider.js +451 -0
  211. package/src/orchestration/failure-log.js +32 -0
  212. package/src/orchestration/pipeline.js +200 -0
  213. package/src/orchestration/progress-events.js +337 -0
  214. package/src/orchestration/progress-reporter.js +131 -0
  215. package/src/orchestration/text-writer.js +22 -0
  216. package/src/portable.js +13 -0
  217. package/src/product-completion.js +249 -0
  218. package/src/project-manifest-input.js +90 -0
  219. package/src/project-manifest.js +141 -0
  220. package/src/python-requirements.js +137 -0
  221. package/src/registry/npm-packument.js +256 -0
  222. package/src/registry/npm-registry-adapter.js +262 -0
  223. package/src/registry/pypi-project.js +300 -0
  224. package/src/registry/pypi-registry-adapter.js +235 -0
  225. package/src/registry/sanitize-registry-body.js +51 -0
  226. package/src/renderers/console.js +160 -0
  227. package/src/renderers/impact-presentation.js +278 -0
  228. package/src/renderers/markdown.js +172 -0
  229. package/src/research-plan.js +455 -0
  230. package/src/runtime-conformance.js +275 -0
  231. package/src/source-provenance.js +393 -0
  232. package/src/source-url.js +62 -0
  233. package/src/structured-output-schema.js +66 -0
  234. package/src/target-selector.js +306 -0
  235. package/src/upgrade-decision/input-loader.js +107 -0
  236. package/src/upgrade-decision/presentation.js +43 -0
  237. package/src/upgrade-decision/runtime.js +21 -0
  238. package/src/upgrade-decision/upgrade-decision.js +626 -0
  239. package/src/upgrade-decision/writer.js +30 -0
  240. package/src/usage/analyzer-registry.js +63 -0
  241. package/src/usage/coverage.js +116 -0
  242. package/src/usage/input-loader.js +139 -0
  243. package/src/usage/js/analyzer.js +240 -0
  244. package/src/usage/js/parser.js +21 -0
  245. package/src/usage/runtime.js +187 -0
  246. package/src/usage/scope.js +44 -0
  247. package/src/usage/source-files.js +50 -0
  248. package/src/usage/usage-index.js +217 -0
  249. package/src/usage/writer.js +31 -0
  250. package/src/version-analysis-loader.js +203 -0
  251. package/src/version-analysis-manifest.js +314 -0
  252. package/src/version-analysis-writer.js +30 -0
package/src/cli.js ADDED
@@ -0,0 +1,1299 @@
1
+ import { mkdir, rename, writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+
4
+ import {
5
+ CLI_NAME,
6
+ CAPABILITY_PROFILE_FILENAME,
7
+ DEFAULT_ANALYSIS_LOG_PATH,
8
+ DEFAULT_AI_SCORECARD_PATH,
9
+ DEFAULT_BENCHMARK_CONFIG_PATH,
10
+ DEFAULT_BENCHMARK_REPORT_PATH,
11
+ DEFAULT_CONFORMANCE_REPORT_PATH,
12
+ DEPLOYMENT_PROFILE_FILENAME,
13
+ DEFAULT_GOVERNANCE_DIRECTORY,
14
+ DEFAULT_KNOWLEDGE_MANIFEST_PATH,
15
+ DEFAULT_MANIFEST_PATH,
16
+ DEFAULT_EVALUATION_REPORT_PATH,
17
+ DEFAULT_METRICS_PATH,
18
+ QUALIFICATION_RECORD_FILENAME,
19
+ DEFAULT_VERSION_ANALYSIS_PATH,
20
+ DEFAULT_REPOSITORY_IMPACT_EVIDENCE_PATH,
21
+ DEFAULT_UPGRADE_DECISION_PATH,
22
+ DEFAULT_REPOSITORY_IMPACT_PATH,
23
+ DEFAULT_REPOSITORY_IMPACT_REPORT_PATH,
24
+ DEFAULT_MIGRATION_CHECKLIST_PATH,
25
+ DEFAULT_MIGRATION_PLANNING_QUALIFICATION_PATH,
26
+ DEFAULT_USAGE_INDEX_PATH,
27
+ PRODUCT_NAME,
28
+ VERSION
29
+ } from './constants.js';
30
+ import { resolveArtifactRootChain } from './artifact-root-compatibility.js';
31
+ import { resolveIdentityEnvironment } from './environment-compatibility.js';
32
+ import {
33
+ buildAiScorecard,
34
+ serializeAiScorecard,
35
+ writeAiScorecard
36
+ } from './ai-scorecard.js';
37
+ import {
38
+ analyzeDependencyAiContext,
39
+ buildVersionAnalysisPrompt
40
+ } from './ai-version-analysis.js';
41
+ import {
42
+ createHttpJsonAiProvider,
43
+ createProviderAiRuntime
44
+ } from './ai-runtime.js';
45
+ import {
46
+ DEFAULT_AI_TIMEOUT_MS,
47
+ createOpenAiCompatibleProvider
48
+ } from './openai-compatible-provider.js';
49
+ import { isAiRuntimeDebugEnabled } from './ai-runtime-debug.js';
50
+ import {
51
+ buildDependencyAiContext,
52
+ resolveDependencyAnalysisInputs
53
+ } from './dependency-ai-context.js';
54
+ import { createEvidenceSourceAdapter } from './evidence-source-adapter.js';
55
+ import { discoverProject } from './discovery.js';
56
+ import {
57
+ DEFAULT_EVALUATION_DATASET_PATH,
58
+ runEvaluation,
59
+ writeEvaluationReport
60
+ } from './evaluation-runner.js';
61
+ import { serializeEvaluationReport } from './evaluation-report.js';
62
+ import {
63
+ loadBenchmarkConfig,
64
+ runBenchmark,
65
+ serializeBenchmarkReport,
66
+ writeBenchmarkReport
67
+ } from './benchmark-runner.js';
68
+ import { runConformance } from './conformance-runner.js';
69
+ import {
70
+ serializeConformanceReport,
71
+ writeConformanceReport
72
+ } from './conformance-report.js';
73
+ import {
74
+ createDefaultGovernanceArtifacts,
75
+ serializeGovernanceArtifacts,
76
+ writeGovernanceArtifacts
77
+ } from './governance-metadata.js';
78
+ import {
79
+ buildMetrics,
80
+ loadEvaluationReportForMetrics,
81
+ writeMetrics
82
+ } from './metrics-engine.js';
83
+ import { createCliHttpRuntime } from './http/cli-http-runtime.js';
84
+ import {
85
+ DEFAULT_KNOWLEDGE_CACHE_DIRECTORY,
86
+ createKnowledgeCache
87
+ } from './knowledge-cache.js';
88
+ import {
89
+ buildKnowledgeEvidenceBundle,
90
+ writeKnowledgeEvidenceBundle
91
+ } from './knowledge-evidence-producer.js';
92
+ import { buildKnowledgeManifest } from './knowledge-manifest-builder.js';
93
+ import { serializeKnowledgeManifest, writeKnowledgeManifest } from './knowledge-manifest-writer.js';
94
+ import { createKnowledgeResearchOrchestrator } from './knowledge-research.js';
95
+ import { runImpactEvidenceGeneration } from './impact-evidence/runtime.js';
96
+ import { writeRepositoryImpactEvidence } from './impact-evidence/writer.js';
97
+ import { runImpactAnalysis } from './impact/runtime.js';
98
+ import { writeRepositoryImpact } from './impact/writer.js';
99
+ import {
100
+ PipelineCancellationError,
101
+ PipelineStageError,
102
+ createAnalysisStages,
103
+ runAnalysisPipeline
104
+ } from './orchestration/pipeline.js';
105
+ import { writeAnalysisFailureLog } from './orchestration/failure-log.js';
106
+ import { createProgressReporter } from './orchestration/progress-reporter.js';
107
+ import { writeTextArtifact } from './orchestration/text-writer.js';
108
+ import { loadProjectManifestInput } from './project-manifest-input.js';
109
+ import { createResearchPlan } from './research-plan.js';
110
+ import { createNpmRegistryAdapter } from './registry/npm-registry-adapter.js';
111
+ import { createPypiRegistryAdapter } from './registry/pypi-registry-adapter.js';
112
+ import { renderConsoleSummary } from './renderers/console.js';
113
+ import { buildImpactPresentationViewModel } from './renderers/impact-presentation.js';
114
+ import { renderMarkdownReport } from './renderers/markdown.js';
115
+ import { isPortableRelativePath } from './portable.js';
116
+ import {
117
+ buildProductCompletion,
118
+ productCompletionExitCode
119
+ } from './product-completion.js';
120
+ import {
121
+ parseTargetSelector,
122
+ resolveTargetSelectors,
123
+ targetOccurrenceKey
124
+ } from './target-selector.js';
125
+ import {
126
+ DEFAULT_KNOWLEDGE_EVIDENCE_BUNDLE_PATH,
127
+ loadVersionAnalysisArtifacts
128
+ } from './version-analysis-loader.js';
129
+ import { buildVersionAnalysisManifest } from './version-analysis-manifest.js';
130
+ import { serializeVersionAnalysisManifest, writeVersionAnalysisManifest } from './version-analysis-writer.js';
131
+ import { runUsageDiscovery } from './usage/runtime.js';
132
+ import { writeUsageIndex } from './usage/writer.js';
133
+ import { runMigrationChecklistStage } from './migration-checklist/runtime.js';
134
+ import { runUpgradeDecisionStage } from './upgrade-decision/runtime.js';
135
+ import { resolveMigrationQualification } from './migration-checklist/qualification-resolution.js';
136
+
137
+ const legacyInvocationWarnings = new Set();
138
+
139
+ const HELP = `${PRODUCT_NAME} ${VERSION}
140
+
141
+ Decide whether evaluated dependency targets should be kept, planned, or investigated.
142
+ The default registry candidate is a fact, not an upgrade recommendation.
143
+
144
+ Usage:
145
+ ${CLI_NAME} analyze <repository> [options]
146
+ ${CLI_NAME} discover [path] [options]
147
+ ${CLI_NAME} research [path] [options]
148
+ ${CLI_NAME} analyze-version [path] [options]
149
+ ${CLI_NAME} eval [options]
150
+ ${CLI_NAME} scorecard [options]
151
+ ${CLI_NAME} benchmark [options]
152
+ ${CLI_NAME} conformance [options]
153
+ ${CLI_NAME} governance [options]
154
+ ${CLI_NAME} [path] [options]
155
+
156
+ Analyze options:
157
+ --offline Use fresh research cache entries only; never request registries
158
+ --max-depth <n> Maximum directory depth for discovery and usage scanning
159
+ --target <selector>
160
+ Select a caller-owned target. Repeatable.
161
+ Required fields: package=<canonical-id>,target=<version>
162
+ Optional occurrence fields: project,manifest,type,occurrence
163
+ occurrence is a stable sha256 ID shown in ambiguity guidance
164
+ Example: package=npm:react,target=20.0.0
165
+ Scoped example: package=npm:@scope/pkg,target=3.0.0
166
+ --fail-on-incomplete
167
+ Exit 2 for review-required or insufficient-data outcomes.
168
+ Partial provider/output/runtime results always exit 2.
169
+ --stdout Print only the machine-readable product completion summary
170
+ --experimental-migration-checklist
171
+ Generate an evidence-grounded migration checklist.
172
+ Experimental. Every migration action requires human review.
173
+ --migration-qualification <path>
174
+ Migration Planning v2 qualification record relative to the
175
+ repository (default: ${DEFAULT_MIGRATION_PLANNING_QUALIFICATION_PATH})
176
+ --progress <mode> Control analysis progress: auto, interactive, or plain
177
+ auto uses interactive output on a TTY and stable plain
178
+ lines otherwise. Heartbeats show real elapsed time;
179
+ no percentage or ETA is inferred. (default: auto)
180
+
181
+ Discover options:
182
+ -o, --output <path> Project Manifest path relative to the project root
183
+ (default: ${DEFAULT_MANIFEST_PATH})
184
+ --stdout Print the manifest instead of writing a file
185
+ --no-pretty Emit compact JSON
186
+ --max-depth <n> Maximum directory depth to scan
187
+ --fail-on-warning Return exit code 2 when discovery has warnings
188
+
189
+ Research options:
190
+ -o, --output <path> Knowledge Manifest path relative to the project root
191
+ (default: ${DEFAULT_KNOWLEDGE_MANIFEST_PATH})
192
+ --stdout Print only the Knowledge Manifest JSON
193
+ --offline Use fresh cache entries only; never request registries
194
+
195
+ Analyze-version options:
196
+ -o, --output <path> Version Analysis artifact path relative to the project root
197
+ (default: ${DEFAULT_VERSION_ANALYSIS_PATH})
198
+ --package <id> Analyze one exact canonical package ID (for example, pypi:langsmith)
199
+ --target <selector>
200
+ Select explicit targets using the same analyze selector syntax
201
+ --stdout Print only the Version Analysis JSON
202
+
203
+ Eval options:
204
+ --dataset <path> Golden Dataset file or directory
205
+ (default: ${DEFAULT_EVALUATION_DATASET_PATH})
206
+ -o, --output <path> Evaluation report path
207
+ (default: ${DEFAULT_EVALUATION_REPORT_PATH})
208
+ --stdout Print only the Evaluation Report JSON
209
+
210
+ Scorecard options:
211
+ --report <path> Evaluation Report path
212
+ (default: ${DEFAULT_EVALUATION_REPORT_PATH})
213
+ --metrics-output <path>
214
+ Metrics artifact path
215
+ (default: ${DEFAULT_METRICS_PATH})
216
+ -o, --output <path> AI Scorecard path
217
+ (default: ${DEFAULT_AI_SCORECARD_PATH})
218
+ --stdout Print only the AI Scorecard JSON
219
+
220
+ Benchmark options:
221
+ --config <path> Benchmark config path
222
+ (default: ${DEFAULT_BENCHMARK_CONFIG_PATH})
223
+ -o, --output <path> Benchmark Report path
224
+ (default: ${DEFAULT_BENCHMARK_REPORT_PATH})
225
+ --stdout Print only the Benchmark Report JSON
226
+
227
+ Conformance options:
228
+ -o, --output <path> Offline Conformance Report path
229
+ (default: ${DEFAULT_CONFORMANCE_REPORT_PATH})
230
+ --stdout Print only the Offline Conformance Report JSON
231
+
232
+ Governance options:
233
+ -o, --output <path> Directory for portable governance artifacts
234
+ (default: ${DEFAULT_GOVERNANCE_DIRECTORY})
235
+ --stdout Print a bundle containing the three governance artifacts
236
+ -h, --help Show help
237
+ -v, --version Show version
238
+ `;
239
+
240
+ function takeValue(args, index, option) {
241
+ const value = args[index + 1];
242
+ if (value === undefined || value.startsWith('-')) throw new Error(`${option} requires a value`);
243
+ return value;
244
+ }
245
+
246
+ function outputPath(root, output) {
247
+ if (!isPortableRelativePath(output)) throw new Error('--output must be a portable path relative to the repository root');
248
+ return path.resolve(root, output);
249
+ }
250
+
251
+ function compatibilityDiagnostic(io, message) {
252
+ (io.stderr ?? process.stderr).write(`${message}\n`);
253
+ }
254
+
255
+ function withResolvedEnvironment(io) {
256
+ if (io.environmentResolved) return io;
257
+ const env = Object.hasOwn(io, 'env') ? io.env : process.env;
258
+ return {
259
+ ...io,
260
+ env: resolveIdentityEnvironment(env, {
261
+ overrides: io.environmentOverrides,
262
+ diagnosticState: io.environmentDiagnosticState,
263
+ onDiagnostic: (message) => compatibilityDiagnostic(io, message)
264
+ }),
265
+ environmentResolved: true
266
+ };
267
+ }
268
+
269
+ export function parseArguments(argv) {
270
+ const args = [...argv];
271
+ const command = ['analyze', 'discover', 'research', 'analyze-version', 'eval', 'scorecard', 'benchmark', 'conformance', 'governance'].includes(args[0])
272
+ ? args.shift()
273
+ : 'discover';
274
+ const options = {
275
+ command,
276
+ root: '.',
277
+ output: command === 'analyze'
278
+ ? DEFAULT_REPOSITORY_IMPACT_REPORT_PATH
279
+ : command === 'research'
280
+ ? DEFAULT_KNOWLEDGE_MANIFEST_PATH
281
+ : command === 'analyze-version'
282
+ ? DEFAULT_VERSION_ANALYSIS_PATH
283
+ : command === 'eval'
284
+ ? DEFAULT_EVALUATION_REPORT_PATH
285
+ : command === 'scorecard'
286
+ ? DEFAULT_AI_SCORECARD_PATH
287
+ : command === 'benchmark'
288
+ ? DEFAULT_BENCHMARK_REPORT_PATH
289
+ : command === 'conformance'
290
+ ? DEFAULT_CONFORMANCE_REPORT_PATH
291
+ : command === 'governance'
292
+ ? DEFAULT_GOVERNANCE_DIRECTORY
293
+ : DEFAULT_MANIFEST_PATH,
294
+ dataset: DEFAULT_EVALUATION_DATASET_PATH,
295
+ report: DEFAULT_EVALUATION_REPORT_PATH,
296
+ metricsOutput: DEFAULT_METRICS_PATH,
297
+ config: DEFAULT_BENCHMARK_CONFIG_PATH,
298
+ pretty: true,
299
+ stdout: false,
300
+ failOnWarning: false,
301
+ failOnIncomplete: false,
302
+ offline: false,
303
+ experimentalMigrationChecklist: false,
304
+ targets: [],
305
+ progress: 'auto'
306
+ };
307
+ let rootSet = false;
308
+ for (let index = 0; index < args.length; index += 1) {
309
+ const argument = args[index];
310
+ if (argument === '--help' || argument === '-h') options.help = true;
311
+ else if (argument === '--version' || argument === '-v') options.version = true;
312
+ else if (argument === '--stdout') options.stdout = true;
313
+ else if (argument === '--no-pretty') options.pretty = false;
314
+ else if (argument === '--fail-on-warning') options.failOnWarning = true;
315
+ else if (argument === '--fail-on-incomplete') options.failOnIncomplete = true;
316
+ else if (argument === '--offline') options.offline = true;
317
+ else if (argument === '--target') {
318
+ options.targets.push(parseTargetSelector(takeValue(args, index, argument)));
319
+ index += 1;
320
+ }
321
+ else if (argument === '--experimental-migration-checklist') {
322
+ options.experimentalMigrationChecklist = true;
323
+ } else if (argument === '--progress') {
324
+ options.progress = takeValue(args, index, argument);
325
+ index += 1;
326
+ } else if (argument === '--migration-qualification') {
327
+ options.migrationQualificationPath = takeValue(args, index, argument);
328
+ index += 1;
329
+ }
330
+ else if (argument === '--output' || argument === '-o') {
331
+ options.output = takeValue(args, index, argument);
332
+ index += 1;
333
+ } else if (argument === '--dataset') {
334
+ options.dataset = takeValue(args, index, argument);
335
+ index += 1;
336
+ } else if (argument === '--report') {
337
+ options.report = takeValue(args, index, argument);
338
+ index += 1;
339
+ } else if (argument === '--metrics-output') {
340
+ options.metricsOutput = takeValue(args, index, argument);
341
+ index += 1;
342
+ } else if (argument === '--config') {
343
+ options.config = takeValue(args, index, argument);
344
+ index += 1;
345
+ } else if (argument === '--package') {
346
+ if (options.packageId !== undefined) {
347
+ throw new Error('--package accepts exactly one canonical package ID.');
348
+ }
349
+ options.packageId = takeValue(args, index, argument);
350
+ index += 1;
351
+ } else if (argument === '--max-depth') {
352
+ const raw = takeValue(args, index, argument);
353
+ const depth = Number(raw);
354
+ if (!Number.isInteger(depth) || depth < 0) throw new Error('--max-depth must be a non-negative integer');
355
+ options.maxDepth = depth;
356
+ index += 1;
357
+ } else if (argument.startsWith('-')) throw new Error(`Unknown option: ${argument}`);
358
+ else if (!rootSet && command !== 'eval' && command !== 'scorecard' && command !== 'benchmark'
359
+ && command !== 'conformance' && command !== 'governance') {
360
+ options.root = argument;
361
+ rootSet = true;
362
+ } else if (!rootSet && command === 'eval') {
363
+ options.dataset = argument;
364
+ rootSet = true;
365
+ } else if (!rootSet && command === 'scorecard') {
366
+ options.report = argument;
367
+ rootSet = true;
368
+ } else if (!rootSet && command === 'benchmark') {
369
+ options.config = argument;
370
+ rootSet = true;
371
+ } else if (!rootSet && command === 'conformance') {
372
+ throw new Error(`Unexpected argument: ${argument}`);
373
+ } else if (!rootSet && command === 'governance') {
374
+ throw new Error(`Unexpected argument: ${argument}`);
375
+ } else throw new Error(`Unexpected argument: ${argument}`);
376
+ }
377
+ if (!['research', 'analyze'].includes(command) && options.offline) {
378
+ throw new Error('--offline is only supported by research and analyze');
379
+ }
380
+ if (command !== 'analyze' && options.experimentalMigrationChecklist) {
381
+ throw new Error('--experimental-migration-checklist is only supported by analyze');
382
+ }
383
+ if (options.migrationQualificationPath !== undefined) {
384
+ if (command !== 'analyze' || !options.experimentalMigrationChecklist) {
385
+ throw new Error('--migration-qualification requires analyze with --experimental-migration-checklist');
386
+ }
387
+ if (!isPortableRelativePath(options.migrationQualificationPath)) {
388
+ throw new Error('--migration-qualification must be a portable path relative to the repository root');
389
+ }
390
+ }
391
+ if (command !== 'analyze' && options.progress !== 'auto') {
392
+ throw new Error('--progress is only supported by analyze');
393
+ }
394
+ if (!['auto', 'interactive', 'plain'].includes(options.progress)) {
395
+ throw new Error('--progress must be auto, interactive, or plain');
396
+ }
397
+ if (!['discover', 'analyze'].includes(command) && options.maxDepth !== undefined) {
398
+ throw new Error('--max-depth is only supported by discover and analyze');
399
+ }
400
+ if (command !== 'discover' && options.failOnWarning) throw new Error('--fail-on-warning is only supported by discover');
401
+ if (command !== 'analyze' && options.failOnIncomplete) {
402
+ throw new Error('--fail-on-incomplete is only supported by analyze');
403
+ }
404
+ if (!['analyze', 'analyze-version'].includes(command) && options.targets.length > 0) {
405
+ throw new Error('--target is only supported by analyze and analyze-version');
406
+ }
407
+ if (command !== 'discover' && !options.pretty) throw new Error('--no-pretty is only supported by discover');
408
+ if (command !== 'eval' && options.dataset !== DEFAULT_EVALUATION_DATASET_PATH) throw new Error('--dataset is only supported by eval');
409
+ if (command !== 'scorecard' && options.report !== DEFAULT_EVALUATION_REPORT_PATH) throw new Error('--report is only supported by scorecard');
410
+ if (command !== 'scorecard' && options.metricsOutput !== DEFAULT_METRICS_PATH) {
411
+ throw new Error('--metrics-output is only supported by scorecard');
412
+ }
413
+ if (command !== 'benchmark' && options.config !== DEFAULT_BENCHMARK_CONFIG_PATH) throw new Error('--config is only supported by benchmark');
414
+ if (command !== 'analyze-version' && options.packageId !== undefined) {
415
+ throw new Error('--package is only supported by analyze-version');
416
+ }
417
+ if (options.packageId !== undefined) {
418
+ const separator = options.packageId.indexOf(':');
419
+ const ecosystem = options.packageId.slice(0, separator);
420
+ const name = options.packageId.slice(separator + 1);
421
+ if (separator <= 0 || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(ecosystem) || name.length === 0 || /\s/.test(name)) {
422
+ throw new Error('--package must be an exact canonical package ID such as npm:react or pypi:langsmith.');
423
+ }
424
+ }
425
+ return options;
426
+ }
427
+
428
+ async function writeProjectManifest(root, output, contents) {
429
+ const target = outputPath(root, output);
430
+ await mkdir(path.dirname(target), { recursive: true });
431
+ const temporary = `${target}.${process.pid}.tmp`;
432
+ await writeFile(temporary, contents, 'utf8');
433
+ await rename(temporary, target);
434
+ return target;
435
+ }
436
+
437
+ function researchComponents(root, options, io) {
438
+ if (io.adapters) {
439
+ return {
440
+ adapters: io.adapters,
441
+ evidenceSourceAdapter: io.evidenceSourceAdapter ?? null
442
+ };
443
+ }
444
+ const clock = io.clock ?? (() => new Date());
445
+ const cache = createKnowledgeCache({
446
+ rootDirectory: path.join(root, DEFAULT_KNOWLEDGE_CACHE_DIRECTORY),
447
+ clock
448
+ });
449
+ const adapterOptions = { cache, clock, fetch: io.fetch, offline: options.offline };
450
+ return {
451
+ adapters: {
452
+ npm: createNpmRegistryAdapter(adapterOptions),
453
+ pypi: createPypiRegistryAdapter(adapterOptions)
454
+ },
455
+ evidenceSourceAdapter: io.evidenceSourceAdapter !== undefined
456
+ ? io.evidenceSourceAdapter
457
+ : io.defaultEvidenceEnrichment === false
458
+ ? null
459
+ : createEvidenceSourceAdapter({
460
+ cache,
461
+ clock,
462
+ fetch: io.fetch,
463
+ offline: options.offline
464
+ })
465
+ };
466
+ }
467
+
468
+ async function runResearch(options, io) {
469
+ const defaultEvidenceEnrichment = io.evidenceSourceAdapter !== undefined
470
+ || (!io.adapters && !io.fetch);
471
+ const runtime = options.offline || io.fetch
472
+ ? null
473
+ : (io.createHttpRuntime ?? createCliHttpRuntime)();
474
+ let result;
475
+ let primaryError;
476
+ try {
477
+ result = await executeResearch(options, {
478
+ ...io,
479
+ fetch: runtime?.fetch ?? io.fetch,
480
+ defaultEvidenceEnrichment
481
+ });
482
+ } catch (error) {
483
+ primaryError = error;
484
+ }
485
+ if (runtime) {
486
+ try {
487
+ await runtime.close();
488
+ } catch {
489
+ if (!primaryError) throw new Error('Unable to close the CLI HTTP runtime.');
490
+ }
491
+ }
492
+ if (primaryError) throw primaryError;
493
+ return result;
494
+ }
495
+
496
+ async function executeResearch(options, io) {
497
+ const root = path.resolve(options.root);
498
+ const selection = await resolveArtifactRootChain(root, [DEFAULT_MANIFEST_PATH], {
499
+ onDiagnostic: (message) => compatibilityDiagnostic(io, message)
500
+ });
501
+ const [projectManifestArtifact] = selection.artifacts;
502
+ const loaded = await loadProjectManifestInput(path.join(root, projectManifestArtifact), {
503
+ artifact: projectManifestArtifact
504
+ });
505
+ const plan = createResearchPlan(loaded);
506
+ const clock = io.clock ?? (() => new Date());
507
+ const components = researchComponents(root, options, io);
508
+ const orchestrator = createKnowledgeResearchOrchestrator({
509
+ adapters: components.adapters,
510
+ evidenceSourceAdapter: components.evidenceSourceAdapter,
511
+ clock,
512
+ concurrency: io.concurrency ?? 4
513
+ });
514
+ const result = await orchestrator.run(plan);
515
+ if (io.signal?.aborted) throw new PipelineCancellationError(null, io.signal.reason);
516
+ const manifest = (io.buildKnowledgeManifest ?? buildKnowledgeManifest)(result, {
517
+ policy: {
518
+ mode: options.offline ? 'offline' : 'online',
519
+ registryBases: { npm: 'https://registry.npmjs.org', pypi: 'https://pypi.org' }
520
+ }
521
+ });
522
+ const contents = serializeKnowledgeManifest(manifest);
523
+ if (options.stdout) {
524
+ io.stdout.write(contents);
525
+ return 0;
526
+ }
527
+ const manifestBytes = Buffer.from(contents, 'utf8');
528
+ const evidenceBundle = (io.buildKnowledgeEvidenceBundle ?? buildKnowledgeEvidenceBundle)(manifest, {
529
+ knowledgeManifestArtifact: options.output,
530
+ knowledgeManifestBytes: manifestBytes,
531
+ generatedAt: manifest.generatedAt,
532
+ enrichedEvidence: result.evidence
533
+ });
534
+ if (io.signal?.aborted) throw new PipelineCancellationError(null, io.signal.reason);
535
+ const target = await (io.writeKnowledgeManifest ?? writeKnowledgeManifest)(outputPath(root, options.output), manifest);
536
+ const evidenceTarget = await (io.writeKnowledgeEvidenceBundle ?? writeKnowledgeEvidenceBundle)(
537
+ outputPath(root, DEFAULT_KNOWLEDGE_EVIDENCE_BUNDLE_PATH),
538
+ evidenceBundle
539
+ );
540
+ io.stderr.write('✓ Loaded Project Manifest\n');
541
+ io.stderr.write(`✓ Planned research (${plan.packages.length} packages)\n`);
542
+ io.stderr.write('✓ Research complete\n');
543
+ io.stderr.write('✓ Knowledge Manifest validated\n');
544
+ io.stderr.write(`✓ Wrote:\n${target}\n`);
545
+ io.stderr.write(`✓ Knowledge Evidence Bundle validated\n`);
546
+ io.stderr.write(`✓ Wrote:\n${evidenceTarget}\n`);
547
+ return 0;
548
+ }
549
+
550
+ function createDefaultAiRuntime(io) {
551
+ const normalizedIo = withResolvedEnvironment(io);
552
+ const env = normalizedIo.env;
553
+ const providerName = env.DEPVERDICT_AI_PROVIDER ?? 'http-json';
554
+ if (!env.DEPVERDICT_AI_ENDPOINT) {
555
+ throw new Error('AI runtime is not configured. Set DEPVERDICT_AI_ENDPOINT or provide an AiRuntime.');
556
+ }
557
+ if (providerName === 'openai-compatible') {
558
+ const provider = createOpenAiCompatibleProvider({
559
+ endpoint: env.DEPVERDICT_AI_ENDPOINT,
560
+ model: env.DEPVERDICT_AI_MODEL,
561
+ authorization: env.DEPVERDICT_AI_AUTHORIZATION,
562
+ fetchImplementation: normalizedIo.fetch,
563
+ timeoutMs: configuredAiTimeoutMs(env),
564
+ debug: isAiRuntimeDebugEnabled(env),
565
+ debugWriter: normalizedIo.aiDebugWriter ?? normalizedIo.stderr ?? process.stderr
566
+ });
567
+ return createProviderAiRuntime({ provider });
568
+ }
569
+ const headers = {};
570
+ if (env.DEPVERDICT_AI_AUTHORIZATION) headers.authorization = env.DEPVERDICT_AI_AUTHORIZATION;
571
+ const provider = createHttpJsonAiProvider({
572
+ endpoint: env.DEPVERDICT_AI_ENDPOINT,
573
+ fetchImplementation: normalizedIo.fetch,
574
+ headers,
575
+ provider: providerName,
576
+ model: env.DEPVERDICT_AI_MODEL ?? 'unknown'
577
+ });
578
+ return createProviderAiRuntime({
579
+ provider,
580
+ promptBuilder: buildVersionAnalysisPrompt
581
+ });
582
+ }
583
+
584
+ function configuredAiTimeoutMs(env) {
585
+ const raw = env.DEPVERDICT_AI_TIMEOUT_MS;
586
+ if (raw === undefined) return DEFAULT_AI_TIMEOUT_MS;
587
+ const timeoutMs = Number(raw);
588
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) {
589
+ throw new Error('DEPVERDICT_AI_TIMEOUT_MS must be a positive integer.');
590
+ }
591
+ return timeoutMs;
592
+ }
593
+
594
+ function failureRecoveryGuidance(stage, error) {
595
+ const message = error?.message ?? '';
596
+ const code = error?.code ?? '';
597
+ if (/(?:DEPVERDICT|UPGRADELENS)_AI_(?:ENDPOINT|MODEL)|AI runtime/i.test(message)) {
598
+ return [
599
+ `Stage: ${stage?.label ?? 'Version Analysis'}`,
600
+ 'Required configuration: DEPVERDICT_AI_ENDPOINT and DEPVERDICT_AI_MODEL; authorization is optional.',
601
+ 'Next action: configure one supported provider runtime and rerun. No credential value was printed.'
602
+ ];
603
+ }
604
+ if (/LINEAGE|lineage|digest mismatch|stale/i.test(`${code} ${message}`)) {
605
+ return [
606
+ `Stage: ${stage?.label ?? 'Artifact validation'}`,
607
+ 'The input artifact chain is stale or mismatched.',
608
+ 'Next action: rerun the upstream DepVerdict workflow to regenerate the affected artifacts.'
609
+ ];
610
+ }
611
+ if (code === 'ENOENT' || /ENOENT|not found|missing artifact/i.test(message)) {
612
+ return [
613
+ `Stage: ${stage?.label ?? 'Artifact loading'}`,
614
+ 'A required upstream artifact is missing.',
615
+ 'Next action: run `depverdict analyze <repository>` to regenerate the supported artifact chain.'
616
+ ];
617
+ }
618
+ return [];
619
+ }
620
+
621
+ async function executeAnalyzeVersion(options, io) {
622
+ io = withResolvedEnvironment(io);
623
+ const root = path.resolve(options.root);
624
+ const selection = await resolveArtifactRootChain(root, [
625
+ DEFAULT_MANIFEST_PATH,
626
+ DEFAULT_KNOWLEDGE_MANIFEST_PATH,
627
+ DEFAULT_KNOWLEDGE_EVIDENCE_BUNDLE_PATH
628
+ ], {
629
+ onDiagnostic: (message) => compatibilityDiagnostic(io, message)
630
+ });
631
+ const [
632
+ projectManifestArtifact,
633
+ knowledgeManifestArtifact,
634
+ evidenceBundleArtifact
635
+ ] = selection.artifacts;
636
+ const artifacts = await loadVersionAnalysisArtifacts({
637
+ projectManifest: path.join(root, projectManifestArtifact),
638
+ knowledgeManifest: path.join(root, knowledgeManifestArtifact),
639
+ evidenceBundle: path.join(root, evidenceBundleArtifact)
640
+ }, {
641
+ projectManifestArtifact,
642
+ knowledgeManifestArtifact,
643
+ evidenceBundleArtifact
644
+ });
645
+ const allInputs = resolveDependencyAnalysisInputs(artifacts);
646
+ let inputs = allInputs;
647
+ if (options.packageId !== undefined) {
648
+ const packageExists = artifacts.knowledgeManifest.packages.some((item) => item.id === options.packageId);
649
+ if (!packageExists) {
650
+ throw new Error(`Selected package ${options.packageId} was not found in the Knowledge Manifest; no runtime call was made.`);
651
+ }
652
+ inputs = allInputs.filter((input) => input.packageRecord.id === options.packageId);
653
+ if (inputs.length === 0) {
654
+ throw new Error(`Selected package ${options.packageId} is not eligible for Version Analysis because it has no parsed dependency occurrence; no runtime call was made.`);
655
+ }
656
+ if (inputs.length > 1) {
657
+ throw new Error(`Selected package ${options.packageId} matches ${inputs.length} dependency occurrences; one-dependency selection is ambiguous and no runtime call was made.`);
658
+ }
659
+ }
660
+ const selectedTargets = resolveTargetSelectors(inputs, options.targets ?? []);
661
+ const contexts = inputs.map((input) => {
662
+ const selected = selectedTargets.get(targetOccurrenceKey(input));
663
+ return buildDependencyAiContext(artifacts, {
664
+ input,
665
+ target: selected?.target ?? { policy: 'registryLatest' }
666
+ });
667
+ });
668
+ const needsRuntime = contexts.some((context) => context.knowledge.evidence.length > 0
669
+ && context.versions.analysisMode !== 'unsupportedBaseline'
670
+ && context.versions.targetVersion !== null);
671
+ const runtime = io.aiRuntime ?? (needsRuntime ? createDefaultAiRuntime(io) : null);
672
+ const results = [];
673
+ for (let index = 0; index < contexts.length; index += 1) {
674
+ const context = contexts[index];
675
+ if (io.signal?.aborted) throw new PipelineCancellationError(null, io.signal.reason);
676
+ try {
677
+ io.onProgress?.({
678
+ activityKind: 'WAIT_FOR_ANALYSIS_RESPONSE',
679
+ subject: `Waiting for analysis response: ${context.dependency.declaredName}`,
680
+ completed: index,
681
+ total: contexts.length
682
+ });
683
+ } catch {
684
+ // Progress callbacks are presentation-only.
685
+ }
686
+ results.push(await analyzeDependencyAiContext(context, {
687
+ runtime,
688
+ signal: io.signal
689
+ }));
690
+ if (io.signal?.aborted) throw new PipelineCancellationError(null, io.signal.reason);
691
+ }
692
+ const manifest = (io.buildVersionAnalysisManifest ?? buildVersionAnalysisManifest)({
693
+ input: artifacts.input,
694
+ contexts,
695
+ results,
696
+ generatedAt: io.clock ? io.clock() : new Date()
697
+ });
698
+ const contents = serializeVersionAnalysisManifest(manifest);
699
+ if (options.stdout) {
700
+ io.stdout.write(contents);
701
+ return manifest;
702
+ }
703
+ const target = await (io.writeVersionAnalysisManifest ?? writeVersionAnalysisManifest)(outputPath(root, options.output), manifest);
704
+ io.stderr.write('✓ Loaded Project, Knowledge, and Evidence artifacts\n');
705
+ io.stderr.write(`✓ Built AI contexts (${contexts.length} dependencies)\n`);
706
+ io.stderr.write('✓ AI Version Analysis complete\n');
707
+ io.stderr.write('✓ Version Analysis artifact validated\n');
708
+ io.stderr.write(`✓ Wrote:\n${target}\n`);
709
+ return manifest;
710
+ }
711
+
712
+ function evaluationRuntime(options, io) {
713
+ if (io.aiRuntime) return {
714
+ runtime: io.aiRuntime,
715
+ model: io.model ?? { provider: 'injected', name: 'injected' }
716
+ };
717
+ const env = withResolvedEnvironment(io).env;
718
+ if (env.DEPVERDICT_AI_ENDPOINT) {
719
+ return {
720
+ runtime: createDefaultAiRuntime(io),
721
+ model: {
722
+ provider: env.DEPVERDICT_AI_PROVIDER ?? 'http-json',
723
+ name: env.DEPVERDICT_AI_MODEL ?? 'unknown'
724
+ }
725
+ };
726
+ }
727
+ return {
728
+ runtime: null,
729
+ model: { provider: 'golden-fake', name: 'golden-fake' }
730
+ };
731
+ }
732
+
733
+ async function executeEval(options, io) {
734
+ const selected = evaluationRuntime(options, io);
735
+ const report = await (io.runEvaluation ?? runEvaluation)({
736
+ datasetPath: options.dataset,
737
+ runtime: selected.runtime,
738
+ model: selected.model,
739
+ generatedAt: io.clock ? io.clock() : new Date()
740
+ });
741
+ const contents = serializeEvaluationReport(report);
742
+ if (options.stdout) {
743
+ io.stdout.write(contents);
744
+ return report.summary.failed > 0 ? 2 : 0;
745
+ }
746
+ const target = await (io.writeEvaluationReport ?? writeEvaluationReport)(outputPath('.', options.output), report);
747
+ io.stderr.write(`✓ Loaded Golden Dataset (${report.summary.totalCases} cases)\n`);
748
+ io.stderr.write('✓ Evaluation complete\n');
749
+ io.stderr.write(`✓ Passed: ${report.summary.passed}; Failed: ${report.summary.failed}\n`);
750
+ io.stderr.write(`✓ Wrote:\n${target}\n`);
751
+ return report.summary.failed > 0 ? 2 : 0;
752
+ }
753
+
754
+ async function executeScorecard(options, io) {
755
+ const report = await (io.loadEvaluationReportForMetrics ?? loadEvaluationReportForMetrics)(path.resolve(options.report));
756
+ const metrics = (io.buildMetrics ?? buildMetrics)(report, {
757
+ generatedAt: io.clock ? io.clock() : new Date()
758
+ });
759
+ const scorecard = (io.buildAiScorecard ?? buildAiScorecard)(metrics, {
760
+ generatedAt: io.clock ? io.clock() : new Date()
761
+ });
762
+ if (options.stdout) {
763
+ io.stdout.write(serializeAiScorecard(scorecard));
764
+ return 0;
765
+ }
766
+ const metricsTarget = await (io.writeMetrics ?? writeMetrics)(path.resolve(options.metricsOutput), metrics);
767
+ const scorecardTarget = await (io.writeAiScorecard ?? writeAiScorecard)(outputPath('.', options.output), scorecard);
768
+ io.stderr.write(`✓ Loaded Evaluation Report (${report.summary.totalCases} cases)\n`);
769
+ io.stderr.write('✓ Metrics complete\n');
770
+ io.stderr.write(`✓ Scorecard: ${scorecard.overallScore}/100\n`);
771
+ io.stderr.write(`✓ Wrote metrics:\n${metricsTarget}\n`);
772
+ io.stderr.write(`✓ Wrote scorecard:\n${scorecardTarget}\n`);
773
+ return 0;
774
+ }
775
+
776
+ function benchmarkRuntimeFactory(io) {
777
+ return async (run, config) => {
778
+ if (io.benchmarkRuntimeFactory) return io.benchmarkRuntimeFactory(run, config);
779
+ if (run.runtime.type === 'goldenFake') return null;
780
+ if (run.runtime.type === 'environment') return createDefaultAiRuntime(io);
781
+ throw new Error(`Benchmark runtime ${run.runtime.type} is not supported by the CLI.`);
782
+ };
783
+ }
784
+
785
+ async function executeBenchmark(options, io) {
786
+ const configPath = path.resolve(options.config);
787
+ const config = await (io.loadBenchmarkConfig ?? loadBenchmarkConfig)(configPath);
788
+ const report = await (io.runBenchmark ?? runBenchmark)(config, {
789
+ configPath,
790
+ runtimeFactory: benchmarkRuntimeFactory(io),
791
+ generatedAt: io.clock ? io.clock() : new Date()
792
+ });
793
+ const contents = serializeBenchmarkReport(report);
794
+ if (options.stdout) {
795
+ io.stdout.write(contents);
796
+ return 0;
797
+ }
798
+ const target = await (io.writeBenchmarkReport ?? writeBenchmarkReport)(outputPath('.', options.output), report);
799
+ io.stderr.write(`✓ Loaded Benchmark Config (${report.benchmark.runCount} runs)\n`);
800
+ io.stderr.write('✓ Benchmark complete\n');
801
+ io.stderr.write(`✓ Top run: ${report.ranking[0]?.runId ?? 'none'}\n`);
802
+ io.stderr.write(`✓ Wrote:\n${target}\n`);
803
+ return 0;
804
+ }
805
+
806
+ async function executeConformance(options, io) {
807
+ const env = withResolvedEnvironment(io).env;
808
+ const report = await (io.runConformance ?? runConformance)({
809
+ runtime: {
810
+ provider: env.DEPVERDICT_AI_PROVIDER ?? 'openai-compatible',
811
+ model: env.DEPVERDICT_AI_MODEL ?? 'offline-fixture'
812
+ },
813
+ generatedAt: io.clock ? io.clock() : new Date()
814
+ });
815
+ const contents = serializeConformanceReport(report);
816
+ if (options.stdout) {
817
+ io.stdout.write(contents);
818
+ return report.summary.failed > 0 ? 2 : 0;
819
+ }
820
+ const target = await (io.writeConformanceReport ?? writeConformanceReport)(
821
+ outputPath('.', options.output),
822
+ report
823
+ );
824
+ io.stderr.write(`✓ Offline conformance complete (${report.summary.total} cases)\n`);
825
+ io.stderr.write(`✓ Passed: ${report.summary.passed}; Failed: ${report.summary.failed}\n`);
826
+ io.stderr.write(`✓ Recommendation: ${report.recommendation}\n`);
827
+ io.stderr.write(`✓ Wrote:\n${target}\n`);
828
+ return report.summary.failed > 0 ? 2 : 0;
829
+ }
830
+
831
+ function governancePositiveInteger(value, fallback, name) {
832
+ if (value === undefined) return fallback;
833
+ const number = Number(value);
834
+ if (!Number.isSafeInteger(number) || number <= 0) {
835
+ throw new Error(`${name} must be a positive integer.`);
836
+ }
837
+ return number;
838
+ }
839
+
840
+ async function executeGovernance(options, io) {
841
+ const env = withResolvedEnvironment(io).env;
842
+ const artifacts = (io.createDefaultGovernanceArtifacts ?? createDefaultGovernanceArtifacts)({
843
+ provider: env.DEPVERDICT_AI_PROVIDER ?? 'openai-compatible',
844
+ endpoint: env.DEPVERDICT_AI_ENDPOINT,
845
+ model: env.DEPVERDICT_AI_MODEL ?? 'offline-fixture',
846
+ timeoutSeconds: governancePositiveInteger(
847
+ env.DEPVERDICT_AI_TIMEOUT_SECONDS,
848
+ 180,
849
+ 'DEPVERDICT_AI_TIMEOUT_SECONDS'
850
+ ),
851
+ maxResponseBytes: governancePositiveInteger(
852
+ env.DEPVERDICT_AI_MAX_RESPONSE_BYTES,
853
+ 1_048_576,
854
+ 'DEPVERDICT_AI_MAX_RESPONSE_BYTES'
855
+ )
856
+ });
857
+ if (options.stdout) {
858
+ io.stdout.write(serializeGovernanceArtifacts(artifacts));
859
+ return 0;
860
+ }
861
+ const targets = await (io.writeGovernanceArtifacts ?? writeGovernanceArtifacts)(
862
+ outputPath('.', options.output),
863
+ artifacts
864
+ );
865
+ io.stderr.write('✓ Governance metadata validated\n');
866
+ io.stderr.write(`✓ Wrote ${CAPABILITY_PROFILE_FILENAME}:\n${targets.capabilityProfile}\n`);
867
+ io.stderr.write(`✓ Wrote ${DEPLOYMENT_PROFILE_FILENAME}:\n${targets.deploymentProfile}\n`);
868
+ io.stderr.write(`✓ Wrote ${QUALIFICATION_RECORD_FILENAME}:\n${targets.qualificationRecord}\n`);
869
+ return 0;
870
+ }
871
+
872
+ function silentStream() {
873
+ return Object.freeze({ write: () => true });
874
+ }
875
+
876
+ function migrationRuntimeMetadata(io) {
877
+ if (io.migrationRuntimeMetadata) return io.migrationRuntimeMetadata;
878
+ const env = withResolvedEnvironment(io).env;
879
+ const provider = env.DEPVERDICT_AI_PROVIDER ?? 'unknown';
880
+ return {
881
+ provider,
882
+ model: env.DEPVERDICT_AI_MODEL ?? 'unknown',
883
+ adapter: provider === 'openai-compatible' ? 'openai-compatible' : provider
884
+ };
885
+ }
886
+
887
+ function migrationEventListener(options, io, progress) {
888
+ if (!options.experimentalMigrationChecklist) return undefined;
889
+ return (event) => {
890
+ try {
891
+ if (event.type === 'stage:start') {
892
+ progress?.({
893
+ activityKind: 'PREPARE_MIGRATION_CONTEXTS',
894
+ subject: 'Prepared eligible Migration Checklist contexts',
895
+ completed: 0,
896
+ total: event.total
897
+ });
898
+ } else if (event.type === 'migration:context-start') {
899
+ progress?.({
900
+ activityKind: 'WAIT_FOR_MIGRATION_RESPONSE',
901
+ subject: `Waiting for migration response: ${event.packageName}`,
902
+ completed: event.processed,
903
+ total: event.total
904
+ });
905
+ } else if ([
906
+ 'migration:context-complete',
907
+ 'migration:abstained',
908
+ 'migration:trust-rejected',
909
+ 'migration:fallback'
910
+ ].includes(event.type)) {
911
+ progress?.({
912
+ activityKind: 'SELECT_OFFICIAL_GUIDANCE',
913
+ subject: `Processed migration guidance: ${event.packageName}`,
914
+ completed: event.processed,
915
+ total: event.total
916
+ });
917
+ } else if (event.type === 'migration:artifact-written') {
918
+ progress?.({
919
+ activityKind: 'WRITE_MIGRATION_ARTIFACT',
920
+ subject: 'Validated and wrote Migration Checklist artifact'
921
+ });
922
+ }
923
+ } catch {
924
+ // Pipeline progress remains isolated from Migration Checklist semantics.
925
+ }
926
+ try {
927
+ if (typeof io.migrationProgressListener === 'function') io.migrationProgressListener(event);
928
+ } catch {
929
+ // External progress observation is presentation-only.
930
+ }
931
+ };
932
+ }
933
+
934
+ export function createCliAnalysisStageRunners(options, io) {
935
+ const root = path.resolve(options.root);
936
+ const quiet = silentStream();
937
+ const clockOptions = io.clock ? { clock: io.clock } : {};
938
+ return {
939
+ async projectDiscovery({ progress, signal } = {}) {
940
+ if (signal?.aborted) throw new PipelineCancellationError(null, signal.reason);
941
+ const manifest = await discoverProject(root, {
942
+ maxDepth: options.maxDepth,
943
+ signal
944
+ });
945
+ if (signal?.aborted) throw new PipelineCancellationError(null, signal.reason);
946
+ progress?.({
947
+ activityKind: 'WRITE_PROJECT_MANIFEST',
948
+ subject: 'Writing Project Manifest'
949
+ });
950
+ await writeProjectManifest(root, DEFAULT_MANIFEST_PATH, `${JSON.stringify(manifest, null, 2)}\n`);
951
+ return manifest;
952
+ },
953
+ async knowledgeResearch({ progress, signal } = {}) {
954
+ if (signal?.aborted) throw new PipelineCancellationError(null, signal.reason);
955
+ await runResearch({
956
+ ...options,
957
+ command: 'research',
958
+ root,
959
+ output: DEFAULT_KNOWLEDGE_MANIFEST_PATH,
960
+ stdout: false
961
+ }, { ...io, stdout: quiet, stderr: quiet, signal });
962
+ progress?.({
963
+ activityKind: 'WRITE_KNOWLEDGE_ARTIFACTS',
964
+ subject: 'Validated Knowledge Research artifacts'
965
+ });
966
+ return DEFAULT_KNOWLEDGE_MANIFEST_PATH;
967
+ },
968
+ async versionAnalysis({ progress, signal } = {}) {
969
+ return executeAnalyzeVersion({
970
+ ...options,
971
+ command: 'analyze-version',
972
+ root,
973
+ output: DEFAULT_VERSION_ANALYSIS_PATH,
974
+ stdout: false,
975
+ packageId: undefined
976
+ }, { ...io, stdout: quiet, stderr: quiet, signal, onProgress: progress });
977
+ },
978
+ async usageDiscovery({ progress, signal } = {}) {
979
+ if (signal?.aborted) throw new PipelineCancellationError(null, signal.reason);
980
+ const usageIndex = await runUsageDiscovery({
981
+ repositoryRoot: root,
982
+ maxDepth: options.maxDepth,
983
+ signal,
984
+ ...clockOptions
985
+ });
986
+ if (signal?.aborted) throw new PipelineCancellationError(null, signal.reason);
987
+ progress?.({
988
+ activityKind: 'WRITE_USAGE_INDEX',
989
+ subject: 'Writing Repository Usage Index'
990
+ });
991
+ await writeUsageIndex(path.join(root, DEFAULT_USAGE_INDEX_PATH), usageIndex);
992
+ return usageIndex;
993
+ },
994
+ async impactAnalysis({ progress, signal } = {}) {
995
+ if (signal?.aborted) throw new PipelineCancellationError(null, signal.reason);
996
+ const repositoryImpact = await runImpactAnalysis({
997
+ sources: {
998
+ projectManifest: path.join(root, DEFAULT_MANIFEST_PATH),
999
+ versionAnalysis: path.join(root, DEFAULT_VERSION_ANALYSIS_PATH),
1000
+ usageIndex: path.join(root, DEFAULT_USAGE_INDEX_PATH)
1001
+ },
1002
+ ...clockOptions
1003
+ });
1004
+ if (signal?.aborted) throw new PipelineCancellationError(null, signal.reason);
1005
+ progress?.({
1006
+ activityKind: 'WRITE_REPOSITORY_IMPACT',
1007
+ subject: 'Writing Repository Impact artifact'
1008
+ });
1009
+ await writeRepositoryImpact(path.join(root, DEFAULT_REPOSITORY_IMPACT_PATH), repositoryImpact);
1010
+ return repositoryImpact;
1011
+ },
1012
+ async impactEvidence({ progress, signal } = {}) {
1013
+ if (signal?.aborted) throw new PipelineCancellationError(null, signal.reason);
1014
+ const impactEvidence = await runImpactEvidenceGeneration({
1015
+ sources: {
1016
+ projectManifest: path.join(root, DEFAULT_MANIFEST_PATH),
1017
+ versionAnalysis: path.join(root, DEFAULT_VERSION_ANALYSIS_PATH),
1018
+ usageIndex: path.join(root, DEFAULT_USAGE_INDEX_PATH),
1019
+ repositoryImpact: path.join(root, DEFAULT_REPOSITORY_IMPACT_PATH)
1020
+ },
1021
+ ...clockOptions
1022
+ });
1023
+ if (signal?.aborted) throw new PipelineCancellationError(null, signal.reason);
1024
+ progress?.({
1025
+ activityKind: 'WRITE_IMPACT_EVIDENCE',
1026
+ subject: 'Writing Repository Impact Evidence artifact'
1027
+ });
1028
+ await writeRepositoryImpactEvidence(
1029
+ path.join(root, DEFAULT_REPOSITORY_IMPACT_EVIDENCE_PATH),
1030
+ impactEvidence
1031
+ );
1032
+ return impactEvidence;
1033
+ },
1034
+ async upgradeDecision({ progress, signal } = {}) {
1035
+ if (signal?.aborted) throw new PipelineCancellationError(null, signal.reason);
1036
+ const decision = await (io.runUpgradeDecisionStage ?? runUpgradeDecisionStage)({
1037
+ repositoryRoot: root,
1038
+ artifactPath: DEFAULT_UPGRADE_DECISION_PATH,
1039
+ onCompatibilityDiagnostic: (message) => compatibilityDiagnostic(io, message)
1040
+ });
1041
+ if (signal?.aborted) throw new PipelineCancellationError(null, signal.reason);
1042
+ progress?.({
1043
+ activityKind: 'WRITE_UPGRADE_DECISION',
1044
+ subject: 'Writing deterministic Upgrade Decision artifact'
1045
+ });
1046
+ return decision;
1047
+ },
1048
+ async migrationChecklist({ progress, signal } = {}) {
1049
+ if (signal?.aborted) throw new PipelineCancellationError(null, signal.reason);
1050
+ const onMigrationEvent = migrationEventListener(options, io, progress);
1051
+ const runtimeMetadata = migrationRuntimeMetadata(io);
1052
+ const resolver = io.resolveMigrationQualification ?? resolveMigrationQualification;
1053
+ const qualificationOptions = {
1054
+ repositoryRoot: root,
1055
+ runtimeMetadata,
1056
+ allowExperimental: true,
1057
+ qualificationPath: options.migrationQualificationPath,
1058
+ onCompatibilityDiagnostic: (message) => compatibilityDiagnostic(io, message)
1059
+ };
1060
+ if (Object.hasOwn(io, 'migrationQualification')) {
1061
+ qualificationOptions.qualification = io.migrationQualification;
1062
+ }
1063
+ const qualificationDecision = await resolver(qualificationOptions);
1064
+ return (io.runMigrationChecklistStage ?? runMigrationChecklistStage)({
1065
+ repositoryRoot: root,
1066
+ aiRuntime: io.migrationAiRuntime ?? io.aiRuntime ?? null,
1067
+ createAiRuntime: () => createDefaultAiRuntime(io),
1068
+ runtimeMetadata,
1069
+ qualificationDecision,
1070
+ allowExperimental: true,
1071
+ generatedAt: io.clock ? io.clock() : new Date(),
1072
+ artifactPath: DEFAULT_MIGRATION_CHECKLIST_PATH,
1073
+ onCompatibilityDiagnostic: (message) => compatibilityDiagnostic(io, message),
1074
+ onEvent: onMigrationEvent,
1075
+ signal
1076
+ });
1077
+ },
1078
+ async markdownReport({ artifacts, progress, signal }) {
1079
+ if (signal?.aborted) throw new PipelineCancellationError(null, signal.reason);
1080
+ const viewModel = buildImpactPresentationViewModel({
1081
+ projectManifest: artifacts.projectDiscovery,
1082
+ versionAnalysis: artifacts.versionAnalysis,
1083
+ repositoryImpact: artifacts.impactAnalysis,
1084
+ impactEvidence: artifacts.impactEvidence
1085
+ });
1086
+ const completion = buildProductCompletion({
1087
+ upgradeDecision: artifacts.upgradeDecision,
1088
+ versionAnalysis: artifacts.versionAnalysis,
1089
+ migrationChecklistViewModel: artifacts.migrationChecklist?.viewModel,
1090
+ artifactPaths: {
1091
+ report: options.output,
1092
+ upgradeDecision: DEFAULT_UPGRADE_DECISION_PATH,
1093
+ migrationChecklist: artifacts.migrationChecklist?.artifactPath ?? null
1094
+ }
1095
+ });
1096
+ const contents = renderMarkdownReport({
1097
+ viewModel,
1098
+ upgradeDecision: artifacts.upgradeDecision,
1099
+ migrationChecklistViewModel: artifacts.migrationChecklist?.viewModel,
1100
+ completion
1101
+ });
1102
+ if (signal?.aborted) throw new PipelineCancellationError(null, signal.reason);
1103
+ progress?.({
1104
+ activityKind: 'WRITE_MARKDOWN_REPORT',
1105
+ subject: 'Writing Markdown report'
1106
+ });
1107
+ const target = await (io.writeMarkdownReport ?? writeTextArtifact)(
1108
+ outputPath(root, options.output),
1109
+ contents
1110
+ );
1111
+ return Object.freeze({
1112
+ target,
1113
+ viewModel,
1114
+ completion,
1115
+ migrationChecklistViewModel: artifacts.migrationChecklist?.viewModel ?? null
1116
+ });
1117
+ }
1118
+ };
1119
+ }
1120
+
1121
+ export async function executeAnalyze(options, io) {
1122
+ io = withResolvedEnvironment(io);
1123
+ const root = path.resolve(options.root);
1124
+ const runners = {
1125
+ ...createCliAnalysisStageRunners(options, io),
1126
+ ...io.analysisStageRunners
1127
+ };
1128
+ const env = io.env ?? process.env;
1129
+ const progressReporter = io.progressReporter ?? createProgressReporter(io.stderr, {
1130
+ mode: options.progress,
1131
+ noColor: Object.hasOwn(env, 'NO_COLOR')
1132
+ });
1133
+ const abortController = io.signal ? null : (io.abortController ?? new AbortController());
1134
+ const signal = io.signal ?? abortController.signal;
1135
+ const signalHost = io.signalHost ?? process;
1136
+ const installSignalHandler = !io.signal && io.handleSignals !== false
1137
+ && typeof signalHost?.once === 'function';
1138
+ const onSigint = () => {
1139
+ if (!abortController.signal.aborted) {
1140
+ abortController.abort(new Error('User requested cancellation.'));
1141
+ }
1142
+ };
1143
+ if (installSignalHandler) signalHost.once('SIGINT', onSigint);
1144
+ const progressOptions = {};
1145
+ if (io.progressMonotonicClock) progressOptions.monotonicClock = io.progressMonotonicClock;
1146
+ if (io.progressWallClock) progressOptions.wallClock = io.progressWallClock;
1147
+ if (io.progressScheduler) progressOptions.scheduler = io.progressScheduler;
1148
+ if (io.heartbeatIntervalMs !== undefined) {
1149
+ progressOptions.heartbeatIntervalMs = io.heartbeatIntervalMs;
1150
+ }
1151
+ let result;
1152
+ try {
1153
+ result = await (io.runAnalysisPipeline ?? runAnalysisPipeline)({
1154
+ repositoryRoot: root,
1155
+ runners,
1156
+ progressReporter,
1157
+ progressListener: io.progressListener,
1158
+ progressOptions,
1159
+ signal,
1160
+ stages: createAnalysisStages({
1161
+ migrationChecklist: options.experimentalMigrationChecklist
1162
+ })
1163
+ });
1164
+ } catch (error) {
1165
+ if (error instanceof PipelineCancellationError || signal.aborted) {
1166
+ io.stderr.write(
1167
+ `\n${PRODUCT_NAME} product outcome: CANCELLED\n`
1168
+ + 'Next action: rerun when you are ready; no successful completion was published.\n'
1169
+ );
1170
+ return 130;
1171
+ }
1172
+ if (!(error instanceof PipelineStageError)) throw error;
1173
+ let logTarget;
1174
+ try {
1175
+ logTarget = await (io.writeAnalysisFailureLog ?? writeAnalysisFailureLog)(root, error);
1176
+ } catch {
1177
+ io.stderr.write(
1178
+ `\n${PRODUCT_NAME} product outcome: FAILED\n${error.stage.label} failed.\n\n`
1179
+ + 'Unable to write analysis log.\n'
1180
+ );
1181
+ return 1;
1182
+ }
1183
+ const displayLog = path.relative(root, logTarget).split(path.sep).join('/');
1184
+ const decision = error.cause?.decision;
1185
+ if (decision) {
1186
+ const lines = [
1187
+ '',
1188
+ `${PRODUCT_NAME} product outcome: FAILED`,
1189
+ `${error.stage.label} failed.`,
1190
+ '',
1191
+ `Qualification status: ${decision.status}`,
1192
+ `Reason: ${decision.reasonCode}`,
1193
+ `Qualification source: ${decision.sourceKind}`,
1194
+ `Expected runtime: ${decision.runtimeIdentity.provider} / ${decision.runtimeIdentity.model} / ${decision.runtimeIdentity.adapter}`
1195
+ ];
1196
+ if (decision.sourcePath) lines.push(`Qualification path: ${decision.sourcePath}`);
1197
+ if (decision.recordRuntimeIdentity) {
1198
+ lines.push(
1199
+ `Record runtime: ${decision.recordRuntimeIdentity.provider} / ${decision.recordRuntimeIdentity.model} / ${decision.recordRuntimeIdentity.adapter}`
1200
+ );
1201
+ }
1202
+ lines.push(`Next action: ${decision.nextAction}`, '', 'See:', displayLog || DEFAULT_ANALYSIS_LOG_PATH);
1203
+ io.stderr.write(`${lines.join('\n')}\n`);
1204
+ } else {
1205
+ const guidance = failureRecoveryGuidance(error.stage, error.cause);
1206
+ const guidanceText = guidance.length > 0 ? `\n${guidance.join('\n')}\n` : '';
1207
+ io.stderr.write(
1208
+ `\n${PRODUCT_NAME} product outcome: FAILED\n${error.stage.label} failed.\n`
1209
+ + `${guidanceText}\nSee:\n`
1210
+ + `${displayLog || DEFAULT_ANALYSIS_LOG_PATH}\n`
1211
+ );
1212
+ }
1213
+ return 1;
1214
+ } finally {
1215
+ if (installSignalHandler) signalHost.removeListener?.('SIGINT', onSigint);
1216
+ }
1217
+
1218
+ const completion = result.artifacts.markdownReport.completion;
1219
+ if (options.stdout) {
1220
+ io.stdout.write(`${JSON.stringify(completion, null, 2)}\n`);
1221
+ } else {
1222
+ io.stdout.write(renderConsoleSummary({
1223
+ viewModel: result.artifacts.markdownReport.viewModel,
1224
+ completion,
1225
+ reportPath: options.output,
1226
+ upgradeDecision: result.artifacts.upgradeDecision,
1227
+ upgradeDecisionPath: DEFAULT_UPGRADE_DECISION_PATH,
1228
+ migrationChecklistViewModel: result.artifacts.migrationChecklist?.viewModel,
1229
+ migrationChecklistPath: result.artifacts.migrationChecklist?.artifactPath
1230
+ }));
1231
+ }
1232
+ return productCompletionExitCode(completion, { strict: options.failOnIncomplete });
1233
+ }
1234
+
1235
+ export async function runCli(argv, io = {}) {
1236
+ const stdout = io.stdout ?? process.stdout;
1237
+ const stderr = io.stderr ?? process.stderr;
1238
+ const invocationName = io.invocationName ?? CLI_NAME;
1239
+ const deprecationState = io.deprecationState ?? legacyInvocationWarnings;
1240
+ if (invocationName === 'upgradelens' && !deprecationState.has('legacy-cli')) {
1241
+ deprecationState.add('legacy-cli');
1242
+ stderr.write(
1243
+ 'The `upgradelens` command is deprecated and will be removed after the '
1244
+ + '0.6.x preview compatibility window. Use `depverdict` instead.\n'
1245
+ );
1246
+ }
1247
+ try {
1248
+ const options = parseArguments(argv);
1249
+ if (options.help) {
1250
+ stdout.write(HELP);
1251
+ return 0;
1252
+ }
1253
+ if (options.version) {
1254
+ stdout.write(`${VERSION}\n`);
1255
+ return 0;
1256
+ }
1257
+ const commandIo = [
1258
+ 'analyze',
1259
+ 'analyze-version',
1260
+ 'eval',
1261
+ 'benchmark',
1262
+ 'conformance',
1263
+ 'governance'
1264
+ ].includes(options.command)
1265
+ ? withResolvedEnvironment({ ...io, stdout, stderr })
1266
+ : { ...io, stdout, stderr };
1267
+ if (options.command === 'analyze') return await executeAnalyze(options, commandIo);
1268
+ if (options.command === 'research') return await runResearch(options, { ...io, stdout, stderr });
1269
+ if (options.command === 'analyze-version') {
1270
+ await executeAnalyzeVersion(options, commandIo);
1271
+ return 0;
1272
+ }
1273
+ if (options.command === 'eval') return await executeEval(options, commandIo);
1274
+ if (options.command === 'scorecard') return await executeScorecard(options, { ...io, stdout, stderr });
1275
+ if (options.command === 'benchmark') return await executeBenchmark(options, commandIo);
1276
+ if (options.command === 'conformance') return await executeConformance(options, commandIo);
1277
+ if (options.command === 'governance') return await executeGovernance(options, commandIo);
1278
+
1279
+ const manifest = await discoverProject(options.root, { maxDepth: options.maxDepth });
1280
+ const contents = `${JSON.stringify(manifest, null, options.pretty ? 2 : 0)}\n`;
1281
+ if (options.stdout) stdout.write(contents);
1282
+ else {
1283
+ const target = await writeProjectManifest(options.root, options.output, contents);
1284
+ stderr.write(`Discovered ${manifest.summary.projectCount} project(s).\n`);
1285
+ stderr.write(`Manifest: ${target}\n`);
1286
+ if (manifest.warnings.length) stderr.write(`Warnings: ${manifest.warnings.length}\n`);
1287
+ }
1288
+ return options.failOnWarning && manifest.warnings.length > 0 ? 2 : 0;
1289
+ } catch (error) {
1290
+ const guidance = failureRecoveryGuidance(null, error);
1291
+ stderr.write(
1292
+ `${CLI_NAME}: ${error.message}\n`
1293
+ + (guidance.length > 0 ? `${guidance.join('\n')}\n` : '')
1294
+ );
1295
+ return 1;
1296
+ }
1297
+ }
1298
+
1299
+ export { HELP };