@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.
- package/LICENSE +21 -0
- package/README.md +518 -0
- package/bin/depverdict.js +7 -0
- package/bin/upgradelens.js +7 -0
- package/docs/GR-01-Semantic-Grounding-Failure-Analysis.md +309 -0
- package/docs/GR-02-Versioned-Action-Evaluation-Criteria.md +187 -0
- package/docs/GR-03-Extractive-Contract-Safety-Experiment.md +242 -0
- package/docs/GR-04-Versioned-Production-Extractive-Contract.md +154 -0
- package/docs/IA-01-Repository-Usage-Discovery.md +122 -0
- package/docs/IA-02-Repository-Impact-Analysis.md +118 -0
- package/docs/IA-03-Repository-Impact-Evidence.md +160 -0
- package/docs/IA-04-CLI-Orchestration.md +235 -0
- package/docs/IA-05-Real-Provider-Validation.md +235 -0
- package/docs/IA-05-VinGrade-Validation.md +339 -0
- package/docs/MVP-01.md +48 -0
- package/docs/MVP-02-Architecture.md +536 -0
- package/docs/MVP-02-CLI-HTTP-Runtime.md +50 -0
- package/docs/MVP-02-HTTP-Lifecycle.md +37 -0
- package/docs/MVP-02-Knowledge-Manifest-Generation.md +75 -0
- package/docs/MVP-02-Knowledge-Manifest.md +279 -0
- package/docs/MVP-02-Knowledge-Research-Orchestration.md +62 -0
- package/docs/MVP-02-Knowledge-Store.md +87 -0
- package/docs/MVP-02-PyPI-Registry-Adapter.md +97 -0
- package/docs/MVP-02-Research-Planning.md +196 -0
- package/docs/MVP-02-Source-Provenance.md +81 -0
- package/docs/MVP-02-npm-Registry-Adapter.md +99 -0
- package/docs/RR-01-End-to-End-and-Real-Provider-Validation.md +393 -0
- package/docs/RR-01-RERUN-Extractive-Contract-Validation.md +566 -0
- package/docs/RR-02-Full-Product-Workflow-and-Developer-CLI-UX-Review.md +35 -0
- package/docs/RR02-FIX-01-Persistent-Qualification-Resolution.md +32 -0
- package/docs/RR02-FIX-02-Stage-aware-CLI-Progress-and-Heartbeat.md +28 -0
- package/docs/RR02-FIX-03-npm-Capture-Evidence-Exclusion.md +33 -0
- package/docs/RR02-FIX-03A-Complete-Package-Exclusion-and-Evidence-Commit.md +223 -0
- package/docs/RR02-FIX-04-Event-loop-safe-Heartbeat.md +393 -0
- package/docs/RR02-FIX-05-Materialize-Persisted-Qualification.md +207 -0
- package/docs/RR02-RERUN-CLI-Qualification-Progress-UX-and-Package-Validation.md +342 -0
- package/docs/VinGrade-MVP-02-Validation.md +480 -0
- package/docs/VinGrade-RC02-Live-Validation.md +273 -0
- package/docs/ai-capability-discovery.md +573 -0
- package/docs/ai-engineering-production-readiness.md +390 -0
- package/docs/ai-engineering-review.md +251 -0
- package/docs/ai-runtime-governance-discovery.md +754 -0
- package/docs/architecture-overview.md +78 -0
- package/docs/cli-progress.md +111 -0
- package/docs/decisions/diff-01-brand-distribution-identity.md +450 -0
- package/docs/decisions/diff-02-identity-compatibility-contract.md +274 -0
- package/docs/decisions/diff-03-repository-docs-community-migration.md +165 -0
- package/docs/decisions/diff-04-release-evidence-gap-acceptance.md +110 -0
- package/docs/discovery/mvp-05-ai-migration-planning-discovery.md +350 -0
- package/docs/gateway-runtime-discovery.md +614 -0
- package/docs/live-ai-validation.md +299 -0
- package/docs/migration-planning-qualification-resolution.md +99 -0
- package/docs/migrations/upgradelens-to-depverdict.md +97 -0
- package/docs/mp-r03-deterministic-upgrade-decision-architecture.md +135 -0
- package/docs/mp-r04-evidence-bounded-migration-handoff-architecture.md +116 -0
- package/docs/mp-r05-product-completion-and-decision-first-cli-architecture.md +162 -0
- package/docs/mvp-05-deterministic-context-runtime.md +127 -0
- package/docs/mvp-05-migration-checklist-contract.md +166 -0
- package/docs/mvp-05-migration-checklist-orchestration.md +105 -0
- package/docs/mvp-05-migration-evaluation-and-qualification.md +135 -0
- package/docs/mvp-05-provider-neutral-generator.md +79 -0
- package/docs/ollama-local-smoke-validation.md +156 -0
- package/docs/openai-compatible-runtime-discovery.md +789 -0
- package/docs/openrouter-one-dependency-validation.md +205 -0
- package/docs/oss-02-package-guard-hardening-architecture.md +131 -0
- package/docs/oss-04-public-ci-package-metadata-architecture.md +142 -0
- package/docs/package-content-policy.md +98 -0
- package/docs/releases/v0.5.0-technical-preview.md +114 -0
- package/docs/releases/v0.6.0-alpha.1-depverdict-preview.md +101 -0
- package/docs/reviews/diff-02-identity-contract-compatibility.md +413 -0
- package/docs/reviews/diff-03-repository-docs-community-migration.md +364 -0
- package/docs/reviews/diff-04-depverdict-distribution-identity-readiness-rereview.md +453 -0
- package/docs/reviews/diff-04-fix-post-rename-identity-release-remediation.md +310 -0
- package/docs/reviews/diff-05-final-preview-distribution-qualification.md +561 -0
- package/docs/reviews/mvp-05-final-product-value-workflow-rereview.md +471 -0
- package/docs/reviews/mvp-05-product-workflow-review.md +605 -0
- package/docs/reviews/oss-01-duplicate-artifact-investigation-cleanup.md +411 -0
- package/docs/reviews/oss-02-package-guard-hardening.md +303 -0
- package/docs/reviews/oss-03-community-scaffolding.md +378 -0
- package/docs/reviews/oss-04-public-ci-package-metadata.md +439 -0
- package/docs/reviews/oss-05-technical-preview-qualification.md +482 -0
- package/docs/reviews/upgradelens-vs-upgradedepdetective-source-comparison.md +355 -0
- package/docs/reviews/v0.5.0-pre-release-smoke.md +263 -0
- package/docs/reviews/v0.5.0-version-bump-release-verification.md +306 -0
- package/docs/runtime-contract-discovery.md +521 -0
- package/docs/structured-output-compatibility-report.md +100 -0
- package/docs/ts-fix-01-exact-duplicate-occurrence-target-selection-architecture.md +111 -0
- package/docs/version-analysis-architecture.md +827 -0
- package/eval/README.md +86 -0
- package/eval/datasets/generic/declared-constraint.json +59 -0
- package/eval/datasets/generic/evidence-conflict.json +73 -0
- package/eval/datasets/generic/major-breaking-release.json +66 -0
- package/eval/datasets/generic/missing-evidence.json +46 -0
- package/eval/datasets/generic/patch-release-low.json +59 -0
- package/eval/datasets/node/axios-patch-low.json +59 -0
- package/eval/datasets/node/react-major-breaking.json +66 -0
- package/eval/datasets/node/react-minor-compatibility.json +66 -0
- package/eval/datasets/python/fastapi-deprecation.json +66 -0
- package/eval/datasets/python/pydantic-major-breaking.json +66 -0
- package/eval/migration-planning/golden-dataset-v2.json +306 -0
- package/eval/migration-planning/golden-dataset.json +214 -0
- package/eval/schemas/expected-result.schema.json +88 -0
- package/eval/schemas/golden-case.schema.json +181 -0
- package/package.json +57 -0
- package/schemas/ai-scorecard.schema.json +132 -0
- package/schemas/benchmark-report.schema.json +189 -0
- package/schemas/benchmark.schema.json +55 -0
- package/schemas/capability-profile.schema.json +44 -0
- package/schemas/conformance-report.schema.json +150 -0
- package/schemas/deployment-profile.schema.json +64 -0
- package/schemas/evaluation-report.schema.json +158 -0
- package/schemas/knowledge-evidence-bundle.schema.json +150 -0
- package/schemas/knowledge-manifest.schema.json +548 -0
- package/schemas/metrics.schema.json +178 -0
- package/schemas/migration-checklist-extractive-candidate.schema.json +42 -0
- package/schemas/migration-checklist.schema.json +706 -0
- package/schemas/migration-evaluation-dataset-v2.schema.json +208 -0
- package/schemas/migration-evaluation-dataset.schema.json +204 -0
- package/schemas/migration-planning-qualification-record.schema.json +234 -0
- package/schemas/project-manifest.schema.json +308 -0
- package/schemas/qualification-record.schema.json +56 -0
- package/schemas/repository-impact-evidence.schema.json +232 -0
- package/schemas/repository-impact.schema.json +202 -0
- package/schemas/upgrade-decision.schema.json +273 -0
- package/schemas/usage-index.schema.json +179 -0
- package/schemas/version-analysis.schema.json +449 -0
- package/src/ai-runtime-debug.js +325 -0
- package/src/ai-runtime-error.js +42 -0
- package/src/ai-runtime.js +174 -0
- package/src/ai-scorecard.js +204 -0
- package/src/ai-version-analysis.js +484 -0
- package/src/artifact-root-compatibility.js +91 -0
- package/src/benchmark-report.js +111 -0
- package/src/benchmark-runner.js +191 -0
- package/src/canonical-json.js +69 -0
- package/src/cli.js +1299 -0
- package/src/conformance-report.js +158 -0
- package/src/conformance-runner.js +253 -0
- package/src/constants.js +73 -0
- package/src/cooperative-scheduler.js +79 -0
- package/src/dependencies.js +44 -0
- package/src/dependency-ai-context.js +625 -0
- package/src/detectors.js +253 -0
- package/src/discovery.js +234 -0
- package/src/ecosystem-version-adapter.js +294 -0
- package/src/environment-compatibility.js +77 -0
- package/src/evaluation-comparator.js +158 -0
- package/src/evaluation-report.js +76 -0
- package/src/evaluation-runner.js +248 -0
- package/src/evidence-source-adapter.js +472 -0
- package/src/files.js +89 -0
- package/src/governance-diagnostics.js +64 -0
- package/src/governance-loader.js +63 -0
- package/src/governance-metadata.js +346 -0
- package/src/governance-validator.js +360 -0
- package/src/http/bounded-fetch.js +278 -0
- package/src/http/cli-http-runtime.js +44 -0
- package/src/impact/input-loader.js +157 -0
- package/src/impact/matcher.js +40 -0
- package/src/impact/repository-impact.js +199 -0
- package/src/impact/runtime.js +24 -0
- package/src/impact/status.js +62 -0
- package/src/impact/writer.js +30 -0
- package/src/impact-evidence/input-loader.js +202 -0
- package/src/impact-evidence/repository-impact-evidence.js +234 -0
- package/src/impact-evidence/runtime.js +16 -0
- package/src/impact-evidence/writer.js +30 -0
- package/src/index.js +563 -0
- package/src/installed-version-baseline.js +196 -0
- package/src/knowledge-cache.js +324 -0
- package/src/knowledge-evidence-bundle.js +101 -0
- package/src/knowledge-evidence-producer.js +233 -0
- package/src/knowledge-manifest-builder.js +188 -0
- package/src/knowledge-manifest-writer.js +32 -0
- package/src/knowledge-manifest.js +255 -0
- package/src/knowledge-research.js +615 -0
- package/src/metrics-engine.js +205 -0
- package/src/migration-checklist/ai-candidate.js +320 -0
- package/src/migration-checklist/assembler.js +37 -0
- package/src/migration-checklist/context-runtime.js +828 -0
- package/src/migration-checklist/evaluation/action-criteria.js +244 -0
- package/src/migration-checklist/evaluation/comparator-v2.js +332 -0
- package/src/migration-checklist/evaluation/comparator.js +279 -0
- package/src/migration-checklist/evaluation/dataset-v2.js +227 -0
- package/src/migration-checklist/evaluation/dataset.js +336 -0
- package/src/migration-checklist/evaluation/extractive-fixtures-v2.js +148 -0
- package/src/migration-checklist/evaluation/metrics-v2.js +226 -0
- package/src/migration-checklist/evaluation/metrics.js +158 -0
- package/src/migration-checklist/evaluation/qualification-v2.js +321 -0
- package/src/migration-checklist/evaluation/qualification.js +239 -0
- package/src/migration-checklist/evaluation/runner-v2.js +294 -0
- package/src/migration-checklist/evaluation/runner.js +194 -0
- package/src/migration-checklist/evaluation/scorecard-v2.js +106 -0
- package/src/migration-checklist/evaluation/scorecard.js +86 -0
- package/src/migration-checklist/extractive-candidate.js +166 -0
- package/src/migration-checklist/extractive-prompt.js +62 -0
- package/src/migration-checklist/generator.js +702 -0
- package/src/migration-checklist/grounding-policy.js +117 -0
- package/src/migration-checklist/input-loader.js +613 -0
- package/src/migration-checklist/migration-checklist.js +635 -0
- package/src/migration-checklist/presentation.js +292 -0
- package/src/migration-checklist/progress.js +96 -0
- package/src/migration-checklist/prompt.js +83 -0
- package/src/migration-checklist/qualification-guard.js +462 -0
- package/src/migration-checklist/qualification-resolution.js +122 -0
- package/src/migration-checklist/qualification-store.js +225 -0
- package/src/migration-checklist/runtime.js +205 -0
- package/src/migration-checklist/verification.js +134 -0
- package/src/migration-checklist/writer.js +35 -0
- package/src/openai-compatible-provider.js +451 -0
- package/src/orchestration/failure-log.js +32 -0
- package/src/orchestration/pipeline.js +200 -0
- package/src/orchestration/progress-events.js +337 -0
- package/src/orchestration/progress-reporter.js +131 -0
- package/src/orchestration/text-writer.js +22 -0
- package/src/portable.js +13 -0
- package/src/product-completion.js +249 -0
- package/src/project-manifest-input.js +90 -0
- package/src/project-manifest.js +141 -0
- package/src/python-requirements.js +137 -0
- package/src/registry/npm-packument.js +256 -0
- package/src/registry/npm-registry-adapter.js +262 -0
- package/src/registry/pypi-project.js +300 -0
- package/src/registry/pypi-registry-adapter.js +235 -0
- package/src/registry/sanitize-registry-body.js +51 -0
- package/src/renderers/console.js +160 -0
- package/src/renderers/impact-presentation.js +278 -0
- package/src/renderers/markdown.js +172 -0
- package/src/research-plan.js +455 -0
- package/src/runtime-conformance.js +275 -0
- package/src/source-provenance.js +393 -0
- package/src/source-url.js +62 -0
- package/src/structured-output-schema.js +66 -0
- package/src/target-selector.js +306 -0
- package/src/upgrade-decision/input-loader.js +107 -0
- package/src/upgrade-decision/presentation.js +43 -0
- package/src/upgrade-decision/runtime.js +21 -0
- package/src/upgrade-decision/upgrade-decision.js +626 -0
- package/src/upgrade-decision/writer.js +30 -0
- package/src/usage/analyzer-registry.js +63 -0
- package/src/usage/coverage.js +116 -0
- package/src/usage/input-loader.js +139 -0
- package/src/usage/js/analyzer.js +240 -0
- package/src/usage/js/parser.js +21 -0
- package/src/usage/runtime.js +187 -0
- package/src/usage/scope.js +44 -0
- package/src/usage/source-files.js +50 -0
- package/src/usage/usage-index.js +217 -0
- package/src/usage/writer.js +31 -0
- package/src/version-analysis-loader.js +203 -0
- package/src/version-analysis-manifest.js +314 -0
- package/src/version-analysis-writer.js +30 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { PRODUCT_NAME } from '../constants.js';
|
|
2
|
+
import { renderMigrationChecklistConsole } from '../migration-checklist/presentation.js';
|
|
3
|
+
import { renderUpgradeDecisionConsole } from '../upgrade-decision/presentation.js';
|
|
4
|
+
|
|
5
|
+
function requireViewModel(viewModel) {
|
|
6
|
+
if (!viewModel?.summary || !Array.isArray(viewModel?.dependencies)) {
|
|
7
|
+
throw new Error('Console renderer requires an Impact Presentation View Model.');
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function renderConsoleSummary({
|
|
12
|
+
viewModel,
|
|
13
|
+
completion,
|
|
14
|
+
reportPath,
|
|
15
|
+
upgradeDecision,
|
|
16
|
+
upgradeDecisionPath,
|
|
17
|
+
migrationChecklistViewModel,
|
|
18
|
+
migrationChecklistPath
|
|
19
|
+
}) {
|
|
20
|
+
requireViewModel(viewModel);
|
|
21
|
+
const summary = viewModel.summary;
|
|
22
|
+
if (completion) {
|
|
23
|
+
const decisionLabels = {
|
|
24
|
+
KEEP_CURRENT: 'Keep current',
|
|
25
|
+
UPGRADE_NOW: 'Upgrade now',
|
|
26
|
+
PLAN_UPGRADE: 'Plan upgrade',
|
|
27
|
+
INVESTIGATE: 'Investigate',
|
|
28
|
+
INSUFFICIENT_EVIDENCE: 'Insufficient evidence',
|
|
29
|
+
NOT_ANALYZED: 'Not analyzed'
|
|
30
|
+
};
|
|
31
|
+
const lines = [
|
|
32
|
+
`${PRODUCT_NAME} product outcome`,
|
|
33
|
+
'',
|
|
34
|
+
`Overall: ${completion.status}`,
|
|
35
|
+
`Next step: ${completion.nextStep}`,
|
|
36
|
+
'',
|
|
37
|
+
'Upgrade decisions',
|
|
38
|
+
'',
|
|
39
|
+
`Keep current: ${completion.decisionCounts.KEEP_CURRENT}`,
|
|
40
|
+
`Plan upgrade: ${completion.decisionCounts.PLAN_UPGRADE}`,
|
|
41
|
+
`Upgrade now: ${completion.decisionCounts.UPGRADE_NOW}`,
|
|
42
|
+
`Investigate: ${completion.decisionCounts.INVESTIGATE}`,
|
|
43
|
+
`Insufficient evidence: ${completion.decisionCounts.INSUFFICIENT_EVIDENCE}`,
|
|
44
|
+
`Not analyzed: ${completion.decisionCounts.NOT_ANALYZED}`,
|
|
45
|
+
'',
|
|
46
|
+
'Dependency occurrence | Installed -> Target | Decision | Risk / coverage | Why / next step'
|
|
47
|
+
];
|
|
48
|
+
for (const decision of completion.decisions) {
|
|
49
|
+
lines.push(
|
|
50
|
+
`${decision.dependency} [${decision.projectId}] | `
|
|
51
|
+
+ `${decision.installedVersion ?? 'unknown'} -> ${decision.targetVersion ?? 'unknown'} | `
|
|
52
|
+
+ `${decisionLabels[decision.decision]} | ${decision.coverage} | `
|
|
53
|
+
+ `${decision.explanation} Next: ${decision.nextStep}`
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
if (migrationChecklistViewModel) {
|
|
57
|
+
lines.push(
|
|
58
|
+
'',
|
|
59
|
+
'Migration handoff',
|
|
60
|
+
'',
|
|
61
|
+
`Actionable with review: ${completion.handoffCounts.ACTIONABLE_WITH_REVIEW}`,
|
|
62
|
+
`No version change required: ${completion.handoffCounts.NO_VERSION_CHANGE_REQUIRED}`,
|
|
63
|
+
`Investigation required: ${completion.handoffCounts.INVESTIGATION_REQUIRED}`,
|
|
64
|
+
`Insufficient evidence: ${completion.handoffCounts.INSUFFICIENT_EVIDENCE}`,
|
|
65
|
+
`Not analyzed: ${completion.handoffCounts.NOT_ANALYZED}`,
|
|
66
|
+
`No grounded action: ${completion.handoffCounts.NO_GROUNDED_ACTION}`,
|
|
67
|
+
`Action generation failed: ${completion.handoffCounts.ACTION_GENERATION_FAILED}`,
|
|
68
|
+
`Provider qualification: ${migrationChecklistViewModel.qualification.status}`,
|
|
69
|
+
`Qualification ID: ${migrationChecklistViewModel.qualification.qualificationId ?? 'none'}`,
|
|
70
|
+
`Qualification source: ${migrationChecklistViewModel.qualification.sourceKind}`,
|
|
71
|
+
`Experimental override: ${migrationChecklistViewModel.qualification.experimentalOverrideUsed ? 'YES' : 'NO'}`,
|
|
72
|
+
`Runtime identity: ${migrationChecklistViewModel.qualification.runtimeIdentity.provider} / `
|
|
73
|
+
+ `${migrationChecklistViewModel.qualification.runtimeIdentity.model} / `
|
|
74
|
+
+ `${migrationChecklistViewModel.qualification.runtimeIdentity.adapter}`,
|
|
75
|
+
'Migration Checklist remains experimental; every action requires human review.'
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
if (completion.failedOccurrences.length > 0) {
|
|
79
|
+
lines.push('', 'Failed dependency occurrences');
|
|
80
|
+
for (const failure of completion.failedOccurrences) {
|
|
81
|
+
lines.push(
|
|
82
|
+
`${failure.dependency} [${failure.projectId}] — ${failure.stage}: `
|
|
83
|
+
+ `${failure.recovery}`
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
lines.push(
|
|
88
|
+
'',
|
|
89
|
+
'Artifacts',
|
|
90
|
+
'',
|
|
91
|
+
`Report: ${reportPath}`,
|
|
92
|
+
`Upgrade Decision: ${upgradeDecisionPath}`
|
|
93
|
+
);
|
|
94
|
+
if (migrationChecklistPath) lines.push(`Migration Checklist: ${migrationChecklistPath}`);
|
|
95
|
+
lines.push(
|
|
96
|
+
'',
|
|
97
|
+
'Analysis diagnostics',
|
|
98
|
+
'',
|
|
99
|
+
`Dependencies: ${summary.dependencyCount}`,
|
|
100
|
+
`Analyzed: ${summary.analyzedCount}`,
|
|
101
|
+
`Skipped: ${summary.skippedCount}`,
|
|
102
|
+
`Failed: ${summary.failedCount}`,
|
|
103
|
+
`Requires human review: ${summary.requiresHumanReviewCount}`,
|
|
104
|
+
`Impacted: ${summary.impactedCount}`,
|
|
105
|
+
`Coverage unavailable: ${summary.coverageUnavailableCount}`,
|
|
106
|
+
`Not analyzed: ${summary.notAnalyzedCount}`,
|
|
107
|
+
''
|
|
108
|
+
);
|
|
109
|
+
return lines.join('\n');
|
|
110
|
+
}
|
|
111
|
+
const lines = [
|
|
112
|
+
'Repository',
|
|
113
|
+
'',
|
|
114
|
+
viewModel.repositoryName,
|
|
115
|
+
'',
|
|
116
|
+
`Analysis status: ${viewModel.analysisStatus}`,
|
|
117
|
+
'',
|
|
118
|
+
`Dependencies: ${summary.dependencyCount}`,
|
|
119
|
+
`Analyzed: ${summary.analyzedCount}`,
|
|
120
|
+
`Skipped: ${summary.skippedCount}`,
|
|
121
|
+
`Failed: ${summary.failedCount}`,
|
|
122
|
+
`Requires human review: ${summary.requiresHumanReviewCount}`,
|
|
123
|
+
'',
|
|
124
|
+
`Impacted: ${summary.impactedCount}`,
|
|
125
|
+
`Not impacted: ${summary.notImpactedCount}`,
|
|
126
|
+
`Usage not found: ${summary.usageNotFoundCount}`,
|
|
127
|
+
`Coverage unavailable: ${summary.coverageUnavailableCount}`,
|
|
128
|
+
`Not analyzed: ${summary.notAnalyzedCount}`
|
|
129
|
+
];
|
|
130
|
+
if (viewModel.analysisStatus === 'INCOMPLETE') {
|
|
131
|
+
lines.push(
|
|
132
|
+
'',
|
|
133
|
+
'Impact conclusions are incomplete because usage coverage is unavailable or some dependencies were not analyzed.'
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
lines.push(
|
|
137
|
+
'',
|
|
138
|
+
`Breaking findings: ${summary.breakingFindingCount}`,
|
|
139
|
+
`Evidence records: ${summary.evidenceRecordCount}`,
|
|
140
|
+
'',
|
|
141
|
+
'Markdown Report',
|
|
142
|
+
'',
|
|
143
|
+
reportPath,
|
|
144
|
+
''
|
|
145
|
+
);
|
|
146
|
+
let output = lines.join('\n');
|
|
147
|
+
if (upgradeDecision) {
|
|
148
|
+
output += `\n${renderUpgradeDecisionConsole({
|
|
149
|
+
artifact: upgradeDecision,
|
|
150
|
+
artifactPath: upgradeDecisionPath
|
|
151
|
+
})}`;
|
|
152
|
+
}
|
|
153
|
+
if (migrationChecklistViewModel) {
|
|
154
|
+
output += `\n${renderMigrationChecklistConsole({
|
|
155
|
+
viewModel: migrationChecklistViewModel,
|
|
156
|
+
artifactPath: migrationChecklistPath
|
|
157
|
+
})}`;
|
|
158
|
+
}
|
|
159
|
+
return output;
|
|
160
|
+
}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
export const ANALYSIS_PRESENTATION_STATUSES = Object.freeze(['COMPLETE', 'INCOMPLETE']);
|
|
2
|
+
export const DEPENDENCY_IMPACT_STATUSES = Object.freeze([
|
|
3
|
+
'IMPACTED',
|
|
4
|
+
'NOT_IMPACTED',
|
|
5
|
+
'USAGE_NOT_FOUND',
|
|
6
|
+
'COVERAGE_UNAVAILABLE',
|
|
7
|
+
'NOT_ANALYZED'
|
|
8
|
+
]);
|
|
9
|
+
|
|
10
|
+
const VERSION_ANALYSIS_STATUSES = new Set(['analyzed', 'skipped', 'failed']);
|
|
11
|
+
|
|
12
|
+
function inputError(message) {
|
|
13
|
+
return new Error(`Impact presentation input error: ${message}`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function requireInputs(projectManifest, versionAnalysis, repositoryImpact, impactEvidence) {
|
|
17
|
+
if (!projectManifest?.repository?.name) throw inputError('Project Manifest repository is required.');
|
|
18
|
+
if (!Array.isArray(versionAnalysis?.results) || !versionAnalysis?.summary) {
|
|
19
|
+
throw inputError('Version Analysis results and summary are required.');
|
|
20
|
+
}
|
|
21
|
+
if (!Array.isArray(repositoryImpact?.dependencies) || !repositoryImpact?.summary) {
|
|
22
|
+
throw inputError('Repository Impact dependencies and summary are required.');
|
|
23
|
+
}
|
|
24
|
+
if (!Array.isArray(impactEvidence?.dependencies) || !impactEvidence?.summary) {
|
|
25
|
+
throw inputError('Repository Impact Evidence dependencies and summary are required.');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function indexUnique(items, key, label) {
|
|
30
|
+
const indexed = new Map();
|
|
31
|
+
for (const item of items) {
|
|
32
|
+
const value = item?.[key];
|
|
33
|
+
if (typeof value !== 'string' || value.length === 0) throw inputError(`${label} has no ${key}.`);
|
|
34
|
+
if (indexed.has(value)) throw inputError(`${label} has duplicate ${key} ${value}.`);
|
|
35
|
+
indexed.set(value, item);
|
|
36
|
+
}
|
|
37
|
+
return indexed;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function requireCount(actual, expected, field) {
|
|
41
|
+
if (actual !== expected) throw inputError(`${field} is ${actual}; expected ${expected}.`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function validateVersionSummary(versionAnalysis, counts) {
|
|
45
|
+
requireCount(versionAnalysis.summary.resultCount, counts.dependencyCount, 'Version Analysis summary.resultCount');
|
|
46
|
+
requireCount(versionAnalysis.summary.analyzedCount, counts.analyzedCount, 'Version Analysis summary.analyzedCount');
|
|
47
|
+
requireCount(versionAnalysis.summary.skippedCount, counts.skippedCount, 'Version Analysis summary.skippedCount');
|
|
48
|
+
requireCount(versionAnalysis.summary.failedCount, counts.failedCount, 'Version Analysis summary.failedCount');
|
|
49
|
+
requireCount(
|
|
50
|
+
versionAnalysis.summary.requiresHumanReviewCount,
|
|
51
|
+
counts.requiresHumanReviewCount,
|
|
52
|
+
'Version Analysis summary.requiresHumanReviewCount'
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function validateDependencyIdentity(versionResult, impact, evidence) {
|
|
57
|
+
const expected = {
|
|
58
|
+
projectId: versionResult.dependency.projectId,
|
|
59
|
+
packageId: versionResult.dependency.packageId,
|
|
60
|
+
name: versionResult.dependency.declaredName
|
|
61
|
+
};
|
|
62
|
+
for (const [field, value] of Object.entries(expected)) {
|
|
63
|
+
if (impact[field] !== value) {
|
|
64
|
+
throw inputError(`Repository Impact ${impact.analysisResultId} ${field} does not match Version Analysis.`);
|
|
65
|
+
}
|
|
66
|
+
if (evidence[field] !== value) {
|
|
67
|
+
throw inputError(`Impact Evidence ${evidence.analysisResultId} ${field} does not match Version Analysis.`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (evidence.impacted !== impact.impacted) {
|
|
71
|
+
throw inputError(`Impact Evidence ${evidence.analysisResultId} impacted state does not match Repository Impact.`);
|
|
72
|
+
}
|
|
73
|
+
if (impact.status !== undefined && evidence.status !== impact.status) {
|
|
74
|
+
throw inputError(`Impact Evidence ${evidence.analysisResultId} status does not match Repository Impact.`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function validateFindingReferences(impact, evidence) {
|
|
79
|
+
const impactFindings = indexUnique(impact.findings, 'id', `Repository Impact ${impact.analysisResultId} findings`);
|
|
80
|
+
const evidenceFindings = indexUnique(
|
|
81
|
+
evidence.findings,
|
|
82
|
+
'findingId',
|
|
83
|
+
`Impact Evidence ${evidence.analysisResultId} findings`
|
|
84
|
+
);
|
|
85
|
+
if (impactFindings.size !== evidenceFindings.size) {
|
|
86
|
+
throw inputError(`finding count differs for analysis result ${impact.analysisResultId}.`);
|
|
87
|
+
}
|
|
88
|
+
for (const [findingId, findingEvidence] of evidenceFindings) {
|
|
89
|
+
const finding = impactFindings.get(findingId);
|
|
90
|
+
if (!finding) throw inputError(`Impact Evidence references missing finding ${findingId}.`);
|
|
91
|
+
for (const field of ['kind', 'summary', 'impacted']) {
|
|
92
|
+
if (findingEvidence[field] !== finding[field]) {
|
|
93
|
+
throw inputError(`Impact Evidence finding ${findingId} ${field} does not match Repository Impact.`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (finding.status !== undefined && findingEvidence.status !== finding.status) {
|
|
97
|
+
throw inputError(`Impact Evidence finding ${findingId} status does not match Repository Impact.`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function impactStatus(versionStatus, impact) {
|
|
103
|
+
if (versionStatus !== 'analyzed') return 'NOT_ANALYZED';
|
|
104
|
+
if (impact.status) return impact.status;
|
|
105
|
+
return impact.impacted ? 'IMPACTED' : 'COVERAGE_UNAVAILABLE';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function statusMessage(versionStatus, impact) {
|
|
109
|
+
if (versionStatus === 'skipped') {
|
|
110
|
+
return 'Impact could not be evaluated because Version Analysis was skipped.';
|
|
111
|
+
}
|
|
112
|
+
if (versionStatus === 'failed') {
|
|
113
|
+
return 'Impact could not be evaluated because Version Analysis failed.';
|
|
114
|
+
}
|
|
115
|
+
if (impact.status === 'COVERAGE_UNAVAILABLE' || !impact.status) {
|
|
116
|
+
return `Repository usage coverage is unavailable (${impact.reasonCode ?? 'COVERAGE_METADATA_MISSING'}); no not-impacted conclusion is available.`;
|
|
117
|
+
}
|
|
118
|
+
if (impact.status === 'USAGE_NOT_FOUND') {
|
|
119
|
+
return 'Usage analysis completed, but no usage occurrence was recorded for this dependency.';
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function deepFreeze(value) {
|
|
125
|
+
if (value && typeof value === 'object' && !Object.isFrozen(value)) {
|
|
126
|
+
for (const child of Object.values(value)) deepFreeze(child);
|
|
127
|
+
Object.freeze(value);
|
|
128
|
+
}
|
|
129
|
+
return value;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function buildImpactPresentationViewModel({
|
|
133
|
+
projectManifest,
|
|
134
|
+
versionAnalysis,
|
|
135
|
+
repositoryImpact,
|
|
136
|
+
impactEvidence
|
|
137
|
+
}) {
|
|
138
|
+
requireInputs(projectManifest, versionAnalysis, repositoryImpact, impactEvidence);
|
|
139
|
+
const versionResults = indexUnique(versionAnalysis.results, 'id', 'Version Analysis results');
|
|
140
|
+
const impacts = indexUnique(repositoryImpact.dependencies, 'analysisResultId', 'Repository Impact dependencies');
|
|
141
|
+
const evidence = indexUnique(impactEvidence.dependencies, 'analysisResultId', 'Impact Evidence dependencies');
|
|
142
|
+
const dependencyCount = versionResults.size;
|
|
143
|
+
requireCount(impacts.size, dependencyCount, 'Repository Impact dependency count');
|
|
144
|
+
requireCount(evidence.size, dependencyCount, 'Impact Evidence dependency count');
|
|
145
|
+
|
|
146
|
+
const statusCounts = { analyzed: 0, skipped: 0, failed: 0 };
|
|
147
|
+
let requiresHumanReviewCount = 0;
|
|
148
|
+
for (const result of versionAnalysis.results) {
|
|
149
|
+
if (!VERSION_ANALYSIS_STATUSES.has(result.status)) {
|
|
150
|
+
throw inputError(`Version Analysis result ${result.id} has unsupported status ${result.status}.`);
|
|
151
|
+
}
|
|
152
|
+
if (!result.dependency || !Array.isArray(result.findings)) {
|
|
153
|
+
throw inputError(`Version Analysis result ${result.id} is missing dependency facts or findings.`);
|
|
154
|
+
}
|
|
155
|
+
statusCounts[result.status] += 1;
|
|
156
|
+
if (result.requiresHumanReview) requiresHumanReviewCount += 1;
|
|
157
|
+
if (!impacts.has(result.id)) throw inputError(`Version Analysis result ${result.id} has no Repository Impact record.`);
|
|
158
|
+
if (!evidence.has(result.id)) throw inputError(`Version Analysis result ${result.id} has no Impact Evidence record.`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const counts = {
|
|
162
|
+
dependencyCount,
|
|
163
|
+
analyzedCount: statusCounts.analyzed,
|
|
164
|
+
skippedCount: statusCounts.skipped,
|
|
165
|
+
failedCount: statusCounts.failed,
|
|
166
|
+
requiresHumanReviewCount
|
|
167
|
+
};
|
|
168
|
+
validateVersionSummary(versionAnalysis, counts);
|
|
169
|
+
|
|
170
|
+
const dependencies = repositoryImpact.dependencies.map((impact) => {
|
|
171
|
+
const versionResult = versionResults.get(impact.analysisResultId);
|
|
172
|
+
const dependencyEvidence = evidence.get(impact.analysisResultId);
|
|
173
|
+
if (!versionResult) throw inputError(`Repository Impact references missing result ${impact.analysisResultId}.`);
|
|
174
|
+
if (!dependencyEvidence) throw inputError(`Repository Impact has no evidence for result ${impact.analysisResultId}.`);
|
|
175
|
+
if (!Array.isArray(impact.findings) || !Array.isArray(dependencyEvidence.findings)) {
|
|
176
|
+
throw inputError(`analysis result ${impact.analysisResultId} has invalid finding collections.`);
|
|
177
|
+
}
|
|
178
|
+
validateDependencyIdentity(versionResult, impact, dependencyEvidence);
|
|
179
|
+
validateFindingReferences(impact, dependencyEvidence);
|
|
180
|
+
const status = impactStatus(versionResult.status, impact);
|
|
181
|
+
if (status === 'NOT_ANALYZED' && (impact.impacted || impact.findings.length > 0)) {
|
|
182
|
+
throw inputError(`not-analyzed result ${impact.analysisResultId} contains an impact conclusion.`);
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
analysisResultId: impact.analysisResultId,
|
|
186
|
+
projectId: impact.projectId,
|
|
187
|
+
packageId: impact.packageId,
|
|
188
|
+
name: impact.name,
|
|
189
|
+
versionAnalysisStatus: versionResult.status,
|
|
190
|
+
impactStatus: status,
|
|
191
|
+
impactReasonCode: impact.reasonCode ?? (status === 'COVERAGE_UNAVAILABLE'
|
|
192
|
+
? 'COVERAGE_METADATA_MISSING'
|
|
193
|
+
: null),
|
|
194
|
+
coverage: structuredClone(impact.coverage ?? null),
|
|
195
|
+
message: statusMessage(versionResult.status, impact),
|
|
196
|
+
findings: structuredClone(dependencyEvidence.findings).map((finding) => (
|
|
197
|
+
status === 'COVERAGE_UNAVAILABLE' && finding.status === undefined
|
|
198
|
+
? {
|
|
199
|
+
...finding,
|
|
200
|
+
status: 'COVERAGE_UNAVAILABLE',
|
|
201
|
+
reasonCode: 'COVERAGE_METADATA_MISSING'
|
|
202
|
+
}
|
|
203
|
+
: finding
|
|
204
|
+
))
|
|
205
|
+
};
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const impactedCount = dependencies.filter((item) => item.impactStatus === 'IMPACTED').length;
|
|
209
|
+
const notImpactedCount = dependencies.filter((item) => item.impactStatus === 'NOT_IMPACTED').length;
|
|
210
|
+
const usageNotFoundCount = dependencies.filter((item) => item.impactStatus === 'USAGE_NOT_FOUND').length;
|
|
211
|
+
const coverageUnavailableCount = dependencies
|
|
212
|
+
.filter((item) => item.impactStatus === 'COVERAGE_UNAVAILABLE').length;
|
|
213
|
+
const notAnalyzedCount = dependencies.filter((item) => item.impactStatus === 'NOT_ANALYZED').length;
|
|
214
|
+
const impactFindingCount = repositoryImpact.dependencies
|
|
215
|
+
.reduce((count, item) => count + item.findings.length, 0);
|
|
216
|
+
const impactedFindingCount = repositoryImpact.dependencies
|
|
217
|
+
.reduce((count, item) => count + item.findings.filter((finding) => finding.impacted).length, 0);
|
|
218
|
+
const evidenceRecordCount = impactEvidence.dependencies
|
|
219
|
+
.reduce((count, item) => count + item.findings.length, 0);
|
|
220
|
+
requireCount(
|
|
221
|
+
impactedCount + notImpactedCount + usageNotFoundCount + coverageUnavailableCount + notAnalyzedCount,
|
|
222
|
+
dependencyCount,
|
|
223
|
+
'presentation impact status count'
|
|
224
|
+
);
|
|
225
|
+
requireCount(
|
|
226
|
+
repositoryImpact.summary.dependencyCount,
|
|
227
|
+
dependencyCount,
|
|
228
|
+
'Repository Impact summary.dependencyCount'
|
|
229
|
+
);
|
|
230
|
+
requireCount(
|
|
231
|
+
repositoryImpact.summary.impactedDependencyCount,
|
|
232
|
+
impactedCount,
|
|
233
|
+
'Repository Impact summary.impactedDependencyCount'
|
|
234
|
+
);
|
|
235
|
+
requireCount(
|
|
236
|
+
repositoryImpact.summary.findingCount,
|
|
237
|
+
impactFindingCount,
|
|
238
|
+
'Repository Impact summary.findingCount'
|
|
239
|
+
);
|
|
240
|
+
requireCount(
|
|
241
|
+
repositoryImpact.summary.impactedFindingCount,
|
|
242
|
+
impactedFindingCount,
|
|
243
|
+
'Repository Impact summary.impactedFindingCount'
|
|
244
|
+
);
|
|
245
|
+
requireCount(
|
|
246
|
+
impactEvidence.summary.dependencyCount,
|
|
247
|
+
dependencyCount,
|
|
248
|
+
'Impact Evidence summary.dependencyCount'
|
|
249
|
+
);
|
|
250
|
+
requireCount(
|
|
251
|
+
impactEvidence.summary.findingCount,
|
|
252
|
+
evidenceRecordCount,
|
|
253
|
+
'Impact Evidence summary.findingCount'
|
|
254
|
+
);
|
|
255
|
+
requireCount(
|
|
256
|
+
impactEvidence.summary.impactedFindingCount,
|
|
257
|
+
impactedFindingCount,
|
|
258
|
+
'Impact Evidence summary.impactedFindingCount'
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
return deepFreeze({
|
|
262
|
+
repositoryName: projectManifest.repository.name,
|
|
263
|
+
analysisStatus: notAnalyzedCount + coverageUnavailableCount > 0 ? 'INCOMPLETE' : 'COMPLETE',
|
|
264
|
+
summary: {
|
|
265
|
+
...counts,
|
|
266
|
+
impactedCount,
|
|
267
|
+
notImpactedCount,
|
|
268
|
+
usageNotFoundCount,
|
|
269
|
+
coverageUnavailableCount,
|
|
270
|
+
notAnalyzedCount,
|
|
271
|
+
breakingFindingCount: impactFindingCount,
|
|
272
|
+
impactedFindingCount,
|
|
273
|
+
evidenceRecordCount,
|
|
274
|
+
affectedFileCount: impactEvidence.summary.affectedFileCount
|
|
275
|
+
},
|
|
276
|
+
dependencies
|
|
277
|
+
});
|
|
278
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { PRODUCT_NAME } from '../constants.js';
|
|
2
|
+
import { renderMigrationChecklistMarkdownSection } from '../migration-checklist/presentation.js';
|
|
3
|
+
import { renderUpgradeDecisionMarkdownSection } from '../upgrade-decision/presentation.js';
|
|
4
|
+
|
|
5
|
+
function inlineCode(value) {
|
|
6
|
+
return `\`${String(value).replaceAll('`', '\\`')}\``;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function headingText(value) {
|
|
10
|
+
return String(value).replace(/[\r\n]+/g, ' ').replaceAll('\\', '\\\\').replaceAll('#', '\\#');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function tableText(value) {
|
|
14
|
+
return String(value).replaceAll('|', '\\|').replace(/[\r\n]+/g, ' ');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function renderProductCompletion(completion) {
|
|
18
|
+
const labels = {
|
|
19
|
+
KEEP_CURRENT: 'Keep current',
|
|
20
|
+
UPGRADE_NOW: 'Upgrade now',
|
|
21
|
+
PLAN_UPGRADE: 'Plan upgrade',
|
|
22
|
+
INVESTIGATE: 'Investigate',
|
|
23
|
+
INSUFFICIENT_EVIDENCE: 'Insufficient evidence',
|
|
24
|
+
NOT_ANALYZED: 'Not analyzed'
|
|
25
|
+
};
|
|
26
|
+
const lines = [
|
|
27
|
+
'## Product Outcome',
|
|
28
|
+
'',
|
|
29
|
+
`- Overall: ${inlineCode(completion.status)}`,
|
|
30
|
+
`- Next step: ${completion.nextStep}`,
|
|
31
|
+
`- Human-review occurrences: ${completion.reviewRequiredCount}`,
|
|
32
|
+
'',
|
|
33
|
+
'## Dependency Decisions',
|
|
34
|
+
'',
|
|
35
|
+
'| Dependency occurrence | Installed → Target | Decision | Risk / coverage | Why / next step |',
|
|
36
|
+
'| --- | --- | --- | --- | --- |'
|
|
37
|
+
];
|
|
38
|
+
for (const decision of completion.decisions) {
|
|
39
|
+
lines.push(
|
|
40
|
+
`| ${inlineCode(`${decision.dependency} — ${decision.projectId}`)} | `
|
|
41
|
+
+ `${inlineCode(`${decision.installedVersion ?? 'unknown'} → ${decision.targetVersion ?? 'unknown'}`)} | `
|
|
42
|
+
+ `${tableText(labels[decision.decision])} | ${tableText(decision.coverage)} | `
|
|
43
|
+
+ `${tableText(decision.explanation)} Next: ${tableText(decision.nextStep)} |`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
if (completion.failedOccurrences.length > 0) {
|
|
47
|
+
lines.push('', '## Failed Dependency Occurrences', '');
|
|
48
|
+
for (const failure of completion.failedOccurrences) {
|
|
49
|
+
lines.push(
|
|
50
|
+
`- ${inlineCode(`${failure.dependency} — ${failure.projectId}`)}: `
|
|
51
|
+
+ `${failure.stage}. ${failure.recovery}`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
lines.push('');
|
|
56
|
+
return lines;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function requireViewModel(viewModel) {
|
|
60
|
+
if (!viewModel?.summary || !Array.isArray(viewModel?.dependencies)) {
|
|
61
|
+
throw new Error('Markdown renderer requires an Impact Presentation View Model.');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function renderFinding(finding) {
|
|
66
|
+
const lines = [
|
|
67
|
+
`#### ${headingText(finding.summary)}`,
|
|
68
|
+
'',
|
|
69
|
+
`- Finding ID: ${inlineCode(finding.findingId)}`,
|
|
70
|
+
`- Impact status: ${inlineCode(finding.status ?? (finding.impacted ? 'IMPACTED' : 'COVERAGE_UNAVAILABLE'))}`,
|
|
71
|
+
`- Evidence reason: ${inlineCode(finding.reasonCode)}`
|
|
72
|
+
];
|
|
73
|
+
if (finding.matchedSymbols.length === 0) {
|
|
74
|
+
lines.push('- Matched symbols: None', '');
|
|
75
|
+
return lines;
|
|
76
|
+
}
|
|
77
|
+
lines.push('- Matched symbols:');
|
|
78
|
+
for (const match of finding.matchedSymbols) {
|
|
79
|
+
lines.push(` - ${inlineCode(match.symbol)}`);
|
|
80
|
+
for (const usage of match.usages) lines.push(` - ${inlineCode(usage.file)}`);
|
|
81
|
+
}
|
|
82
|
+
lines.push('');
|
|
83
|
+
return lines;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function renderDependency(dependency) {
|
|
87
|
+
const lines = [
|
|
88
|
+
`### ${inlineCode(dependency.name)} (${inlineCode(dependency.packageId)})`,
|
|
89
|
+
'',
|
|
90
|
+
`- Impact status: ${inlineCode(dependency.impactStatus)}`,
|
|
91
|
+
`- Impact reason: ${inlineCode(dependency.impactReasonCode ?? 'NONE')}`,
|
|
92
|
+
`- Version Analysis status: ${inlineCode(dependency.versionAnalysisStatus)}`,
|
|
93
|
+
''
|
|
94
|
+
];
|
|
95
|
+
if (['NOT_ANALYZED', 'COVERAGE_UNAVAILABLE', 'USAGE_NOT_FOUND'].includes(dependency.impactStatus)
|
|
96
|
+
&& dependency.message) {
|
|
97
|
+
lines.push(dependency.message, '');
|
|
98
|
+
if (dependency.impactStatus === 'NOT_ANALYZED') return lines;
|
|
99
|
+
}
|
|
100
|
+
if (dependency.findings.length === 0) {
|
|
101
|
+
lines.push('No breaking findings.', '');
|
|
102
|
+
} else {
|
|
103
|
+
for (const finding of dependency.findings) lines.push(...renderFinding(finding));
|
|
104
|
+
}
|
|
105
|
+
return lines;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function renderMarkdownReport({
|
|
109
|
+
viewModel,
|
|
110
|
+
upgradeDecision,
|
|
111
|
+
migrationChecklistViewModel,
|
|
112
|
+
completion
|
|
113
|
+
}) {
|
|
114
|
+
requireViewModel(viewModel);
|
|
115
|
+
const summary = viewModel.summary;
|
|
116
|
+
const lines = [
|
|
117
|
+
`# ${PRODUCT_NAME} Repository Impact Report`,
|
|
118
|
+
'',
|
|
119
|
+
...(completion ? renderProductCompletion(completion) : []),
|
|
120
|
+
'## Repository',
|
|
121
|
+
'',
|
|
122
|
+
inlineCode(viewModel.repositoryName),
|
|
123
|
+
'',
|
|
124
|
+
'## Analysis Completeness',
|
|
125
|
+
'',
|
|
126
|
+
`- Status: ${viewModel.analysisStatus}`,
|
|
127
|
+
`- Analyzed dependencies: ${summary.analyzedCount}`,
|
|
128
|
+
`- Skipped dependencies: ${summary.skippedCount}`,
|
|
129
|
+
`- Failed dependencies: ${summary.failedCount}`,
|
|
130
|
+
`- Requires human review: ${summary.requiresHumanReviewCount}`,
|
|
131
|
+
''
|
|
132
|
+
];
|
|
133
|
+
if (viewModel.analysisStatus === 'INCOMPLETE') {
|
|
134
|
+
lines.push(
|
|
135
|
+
'> Impact conclusions are incomplete because usage coverage is unavailable or some dependencies were not analyzed.',
|
|
136
|
+
''
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
lines.push(
|
|
140
|
+
'## Summary',
|
|
141
|
+
'',
|
|
142
|
+
'| Metric | Value |',
|
|
143
|
+
'| --- | ---: |',
|
|
144
|
+
`| Dependencies | ${summary.dependencyCount} |`,
|
|
145
|
+
`| Analyzed | ${summary.analyzedCount} |`,
|
|
146
|
+
`| Skipped | ${summary.skippedCount} |`,
|
|
147
|
+
`| Failed | ${summary.failedCount} |`,
|
|
148
|
+
`| Requires human review | ${summary.requiresHumanReviewCount} |`,
|
|
149
|
+
`| Impacted | ${summary.impactedCount} |`,
|
|
150
|
+
`| Not impacted | ${summary.notImpactedCount} |`,
|
|
151
|
+
`| Usage not found | ${summary.usageNotFoundCount} |`,
|
|
152
|
+
`| Coverage unavailable | ${summary.coverageUnavailableCount} |`,
|
|
153
|
+
`| Not analyzed | ${summary.notAnalyzedCount} |`,
|
|
154
|
+
`| Breaking findings | ${summary.breakingFindingCount} |`,
|
|
155
|
+
`| Impacted findings | ${summary.impactedFindingCount} |`,
|
|
156
|
+
`| Evidence records | ${summary.evidenceRecordCount} |`,
|
|
157
|
+
`| Affected files | ${summary.affectedFileCount} |`,
|
|
158
|
+
'',
|
|
159
|
+
'## Dependencies',
|
|
160
|
+
''
|
|
161
|
+
);
|
|
162
|
+
if (viewModel.dependencies.length === 0) lines.push('No dependency impact records.', '');
|
|
163
|
+
else for (const dependency of viewModel.dependencies) lines.push(...renderDependency(dependency));
|
|
164
|
+
let output = `${lines.join('\n')}\n`;
|
|
165
|
+
if (upgradeDecision) {
|
|
166
|
+
output += `\n${renderUpgradeDecisionMarkdownSection(upgradeDecision)}`;
|
|
167
|
+
}
|
|
168
|
+
if (migrationChecklistViewModel) {
|
|
169
|
+
output += `\n${renderMigrationChecklistMarkdownSection({ viewModel: migrationChecklistViewModel })}`;
|
|
170
|
+
}
|
|
171
|
+
return output;
|
|
172
|
+
}
|