@wrongstack/techstack 0.306.4 → 0.307.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/dist/index.d.ts +3 -1
- package/dist/index.js +299 -3
- package/dist/policy/license.d.ts +26 -0
- package/dist/policy/misalignment.d.ts +26 -0
- package/dist/sbom.d.ts +4 -0
- package/dist/service/report-generator.d.ts +2 -1
- package/dist/service/techstack-engine.d.ts +3 -1
- package/dist/service.d.ts +1 -1
- package/dist/store/schema.d.ts +1 -1
- package/dist/store/sqlite.d.ts +9 -1
- package/package.json +4 -4
- package/LICENSE +0 -21
package/dist/index.d.ts
CHANGED
|
@@ -42,8 +42,10 @@ export { createAuditRunner, isNativeAuditAvailable, runNativeAudit, runNpmAudit,
|
|
|
42
42
|
export type { AuditCommandResult, AuditCommandRunner, ConfiguredAuditRunner, NativeAdvisory, NativeAuditResult, } from './advisory/native-audit.js';
|
|
43
43
|
export { classifyStatus, compareVersions, failedLookupStatus, privateOrUnresolvedStatus, } from './policy/status.js';
|
|
44
44
|
export type { AdvisoryStatusData, RegistryStatusData } from './policy/status.js';
|
|
45
|
+
export { assessLicense, createLicenseFinding, normalizeLicenseId, type LicenseCategory, type LicenseRiskAssessment, } from './policy/license.js';
|
|
46
|
+
export { detectWorkspaceMisalignments, type VersionMisalignment, } from './policy/misalignment.js';
|
|
45
47
|
export { TechStackEngine } from './service.js';
|
|
46
|
-
export type { AnalyzeOptions, EnrichOptions } from './service.js';
|
|
48
|
+
export type { AnalyzeOptions, EnrichOptions, ReportFormat } from './service.js';
|
|
47
49
|
export { clusterCandidates, createProviderLlm, createResearcher, createToolSearch, parseResearchJson, triageCandidates, DEFAULT_TRIAGE_LIMIT, } from './research/index.js';
|
|
48
50
|
export type { CreateResearcherOptions, LlmAccessor, ResearchCluster, ResearchLlm, ResearchLlmRequest, ResearchOptions, ResearchSearch, ResearchSearchResult, SearchToolOptions, TechStackResearcher, TriageCandidate, TriageOptions, } from './research/index.js';
|
|
49
51
|
export { TechStackStore } from './store/sqlite.js';
|
package/dist/index.js
CHANGED
|
@@ -2274,7 +2274,11 @@ function toSpdx(snapshot) {
|
|
|
2274
2274
|
SPDXID: `SPDXRef-Package-${index}`,
|
|
2275
2275
|
versionInfo: dep.locked ?? dep.requested,
|
|
2276
2276
|
downloadLocation: dep.purl ? `https://purl.io/${dep.purl}` : "NOASSERTION",
|
|
2277
|
-
|
|
2277
|
+
filesAnalyzed: false,
|
|
2278
|
+
licenseConcluded: dep.license ?? "NOASSERTION",
|
|
2279
|
+
licenseDeclared: dep.license ?? "NOASSERTION",
|
|
2280
|
+
supplier: "NOASSERTION",
|
|
2281
|
+
copyrightText: "NOASSERTION"
|
|
2278
2282
|
}))
|
|
2279
2283
|
};
|
|
2280
2284
|
}
|
|
@@ -3585,6 +3589,229 @@ function failedLookupStatus(source, error) {
|
|
|
3585
3589
|
};
|
|
3586
3590
|
}
|
|
3587
3591
|
|
|
3592
|
+
// src/policy/license.ts
|
|
3593
|
+
var PERMISSIVE_LICENSES = /* @__PURE__ */ new Set([
|
|
3594
|
+
"mit",
|
|
3595
|
+
"apache-2.0",
|
|
3596
|
+
"bsd-2-clause",
|
|
3597
|
+
"bsd-3-clause",
|
|
3598
|
+
"isc",
|
|
3599
|
+
"unlicense",
|
|
3600
|
+
"cc0-1.0",
|
|
3601
|
+
"0bsd",
|
|
3602
|
+
"zlib",
|
|
3603
|
+
"wtfpl"
|
|
3604
|
+
]);
|
|
3605
|
+
var WEAK_COPYLEFT_LICENSES = /* @__PURE__ */ new Set([
|
|
3606
|
+
"lgpl-2.0",
|
|
3607
|
+
"lgpl-2.1",
|
|
3608
|
+
"lgpl-3.0",
|
|
3609
|
+
"mpl-2.0",
|
|
3610
|
+
"cddl-1.0",
|
|
3611
|
+
"epl-1.0",
|
|
3612
|
+
"epl-2.0"
|
|
3613
|
+
]);
|
|
3614
|
+
var STRONG_COPYLEFT_LICENSES = /* @__PURE__ */ new Set([
|
|
3615
|
+
"gpl-2.0",
|
|
3616
|
+
"gpl-2.0-only",
|
|
3617
|
+
"gpl-2.0-or-later",
|
|
3618
|
+
"gpl-3.0",
|
|
3619
|
+
"gpl-3.0-only",
|
|
3620
|
+
"gpl-3.0-or-later",
|
|
3621
|
+
"eupl-1.1",
|
|
3622
|
+
"eupl-1.2",
|
|
3623
|
+
"osl-3.0"
|
|
3624
|
+
]);
|
|
3625
|
+
var NETWORK_COPYLEFT_LICENSES = /* @__PURE__ */ new Set([
|
|
3626
|
+
"agpl-3.0",
|
|
3627
|
+
"agpl-3.0-only",
|
|
3628
|
+
"agpl-3.0-or-later",
|
|
3629
|
+
"sspl-1.0"
|
|
3630
|
+
]);
|
|
3631
|
+
var RESTRICTIVE_LICENSES = /* @__PURE__ */ new Set([
|
|
3632
|
+
"bsl-1.1",
|
|
3633
|
+
"busl-1.1",
|
|
3634
|
+
"cc-by-nc-4.0",
|
|
3635
|
+
"cc-by-nc-sa-4.0",
|
|
3636
|
+
"commons-clause"
|
|
3637
|
+
]);
|
|
3638
|
+
function normalizeLicenseId(rawLicense) {
|
|
3639
|
+
if (!rawLicense) return "";
|
|
3640
|
+
return rawLicense.trim().toLowerCase().replace(/[()]/g, "").replace(/\s+or\s+/g, "/").replace(/\s+and\s+/g, "+");
|
|
3641
|
+
}
|
|
3642
|
+
function assessLicense(rawLicense) {
|
|
3643
|
+
if (!rawLicense || rawLicense.trim() === "") {
|
|
3644
|
+
return {
|
|
3645
|
+
license: rawLicense ?? "None",
|
|
3646
|
+
category: "unknown",
|
|
3647
|
+
severity: "low",
|
|
3648
|
+
isCopyleft: false,
|
|
3649
|
+
isCommercialSafe: false,
|
|
3650
|
+
rationale: "No license declared. Default copyright restrictions apply."
|
|
3651
|
+
};
|
|
3652
|
+
}
|
|
3653
|
+
const normalized = normalizeLicenseId(rawLicense);
|
|
3654
|
+
if (normalized === "unlicensed" || normalized === "proprietary") {
|
|
3655
|
+
return {
|
|
3656
|
+
license: rawLicense,
|
|
3657
|
+
category: "unlicensed",
|
|
3658
|
+
severity: "medium",
|
|
3659
|
+
isCopyleft: false,
|
|
3660
|
+
isCommercialSafe: false,
|
|
3661
|
+
rationale: "Unlicensed or proprietary dependency. May require explicit vendor permission."
|
|
3662
|
+
};
|
|
3663
|
+
}
|
|
3664
|
+
if (NETWORK_COPYLEFT_LICENSES.has(normalized)) {
|
|
3665
|
+
return {
|
|
3666
|
+
license: rawLicense,
|
|
3667
|
+
category: "network_copyleft",
|
|
3668
|
+
severity: "high",
|
|
3669
|
+
isCopyleft: true,
|
|
3670
|
+
isCommercialSafe: false,
|
|
3671
|
+
rationale: `${rawLicense} carries strict network copyleft terms. Network access may trigger source disclosure requirements.`
|
|
3672
|
+
};
|
|
3673
|
+
}
|
|
3674
|
+
if (STRONG_COPYLEFT_LICENSES.has(normalized)) {
|
|
3675
|
+
return {
|
|
3676
|
+
license: rawLicense,
|
|
3677
|
+
category: "strong_copyleft",
|
|
3678
|
+
severity: "high",
|
|
3679
|
+
isCopyleft: true,
|
|
3680
|
+
isCommercialSafe: false,
|
|
3681
|
+
rationale: `${rawLicense} is viral copyleft. Distributing software including this package may require open sourcing the entire codebase under GPL.`
|
|
3682
|
+
};
|
|
3683
|
+
}
|
|
3684
|
+
if (RESTRICTIVE_LICENSES.has(normalized)) {
|
|
3685
|
+
return {
|
|
3686
|
+
license: rawLicense,
|
|
3687
|
+
category: "restrictive",
|
|
3688
|
+
severity: "high",
|
|
3689
|
+
isCopyleft: false,
|
|
3690
|
+
isCommercialSafe: false,
|
|
3691
|
+
rationale: `${rawLicense} contains non-commercial or source-available restrictions. Check commercial eligibility.`
|
|
3692
|
+
};
|
|
3693
|
+
}
|
|
3694
|
+
if (WEAK_COPYLEFT_LICENSES.has(normalized)) {
|
|
3695
|
+
return {
|
|
3696
|
+
license: rawLicense,
|
|
3697
|
+
category: "weak_copyleft",
|
|
3698
|
+
severity: "info",
|
|
3699
|
+
isCopyleft: true,
|
|
3700
|
+
isCommercialSafe: true,
|
|
3701
|
+
rationale: `${rawLicense} is weak copyleft (file/module level). Safe for proprietary projects when dynamically linked.`
|
|
3702
|
+
};
|
|
3703
|
+
}
|
|
3704
|
+
if (PERMISSIVE_LICENSES.has(normalized)) {
|
|
3705
|
+
return {
|
|
3706
|
+
license: rawLicense,
|
|
3707
|
+
category: "permissive",
|
|
3708
|
+
severity: "info",
|
|
3709
|
+
isCopyleft: false,
|
|
3710
|
+
isCommercialSafe: true,
|
|
3711
|
+
rationale: `${rawLicense} is a standard permissive license. Safe for commercial and closed-source use.`
|
|
3712
|
+
};
|
|
3713
|
+
}
|
|
3714
|
+
if (normalized.includes("gpl") || normalized.includes("agpl")) {
|
|
3715
|
+
return {
|
|
3716
|
+
license: rawLicense,
|
|
3717
|
+
category: "strong_copyleft",
|
|
3718
|
+
severity: "medium",
|
|
3719
|
+
isCopyleft: true,
|
|
3720
|
+
isCommercialSafe: false,
|
|
3721
|
+
rationale: `License expression "${rawLicense}" appears to reference GPL/AGPL terms. Review compliance obligations.`
|
|
3722
|
+
};
|
|
3723
|
+
}
|
|
3724
|
+
return {
|
|
3725
|
+
license: rawLicense,
|
|
3726
|
+
category: "unknown",
|
|
3727
|
+
severity: "info",
|
|
3728
|
+
isCopyleft: false,
|
|
3729
|
+
isCommercialSafe: true,
|
|
3730
|
+
rationale: `Custom or non-standard license string "${rawLicense}". Verify terms if distributing binary releases.`
|
|
3731
|
+
};
|
|
3732
|
+
}
|
|
3733
|
+
function createLicenseFinding(dependencyId, dependencyName, rawLicense) {
|
|
3734
|
+
const assessment = assessLicense(rawLicense);
|
|
3735
|
+
if (assessment.category === "permissive" || assessment.category === "weak_copyleft") {
|
|
3736
|
+
return null;
|
|
3737
|
+
}
|
|
3738
|
+
return {
|
|
3739
|
+
id: `finding-${dependencyId}-license`,
|
|
3740
|
+
dependencyId,
|
|
3741
|
+
type: "license",
|
|
3742
|
+
severity: assessment.severity,
|
|
3743
|
+
action: assessment.isCopyleft ? "investigate" : "none",
|
|
3744
|
+
confidence: 1,
|
|
3745
|
+
rationale: `[${assessment.license}] ${dependencyName} \u2014 ${assessment.rationale}`,
|
|
3746
|
+
evidence: [
|
|
3747
|
+
{
|
|
3748
|
+
kind: "registry",
|
|
3749
|
+
source: "license-classifier",
|
|
3750
|
+
retrievedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3751
|
+
detail: `Category: ${assessment.category}, Commercial-Safe: ${assessment.isCommercialSafe}`
|
|
3752
|
+
}
|
|
3753
|
+
]
|
|
3754
|
+
};
|
|
3755
|
+
}
|
|
3756
|
+
|
|
3757
|
+
// src/policy/misalignment.ts
|
|
3758
|
+
function detectWorkspaceMisalignments(dependencies, workspaces) {
|
|
3759
|
+
if (workspaces.length <= 1 || dependencies.length === 0) {
|
|
3760
|
+
return [];
|
|
3761
|
+
}
|
|
3762
|
+
const workspaceRootById = /* @__PURE__ */ new Map();
|
|
3763
|
+
for (const ws of workspaces) {
|
|
3764
|
+
workspaceRootById.set(ws.id, ws.relativeRoot || ".");
|
|
3765
|
+
}
|
|
3766
|
+
const byPackage = /* @__PURE__ */ new Map();
|
|
3767
|
+
for (const dep of dependencies) {
|
|
3768
|
+
if (dep.sourceType === "path" || dep.sourceType === "git") continue;
|
|
3769
|
+
const key = `${dep.ecosystem}:${dep.name}`;
|
|
3770
|
+
const list = byPackage.get(key) ?? [];
|
|
3771
|
+
list.push(dep);
|
|
3772
|
+
byPackage.set(key, list);
|
|
3773
|
+
}
|
|
3774
|
+
const findings = [];
|
|
3775
|
+
for (const [_key, deps] of byPackage) {
|
|
3776
|
+
if (deps.length <= 1) continue;
|
|
3777
|
+
const versionByWs = /* @__PURE__ */ new Map();
|
|
3778
|
+
for (const dep of deps) {
|
|
3779
|
+
const version = dep.locked ?? dep.requested ?? "unknown";
|
|
3780
|
+
versionByWs.set(dep.workspaceId, version);
|
|
3781
|
+
}
|
|
3782
|
+
const uniqueVersions = new Set(versionByWs.values());
|
|
3783
|
+
if (uniqueVersions.size <= 1) continue;
|
|
3784
|
+
const summaryParts = [];
|
|
3785
|
+
for (const [wsId, ver] of versionByWs) {
|
|
3786
|
+
const root = workspaceRootById.get(wsId) ?? wsId;
|
|
3787
|
+
summaryParts.push(`${root} (${ver})`);
|
|
3788
|
+
}
|
|
3789
|
+
const versionSummary = summaryParts.join(", ");
|
|
3790
|
+
for (const dep of deps) {
|
|
3791
|
+
const currentVer = dep.locked ?? dep.requested ?? "unknown";
|
|
3792
|
+
const root = workspaceRootById.get(dep.workspaceId) ?? dep.workspaceId;
|
|
3793
|
+
findings.push({
|
|
3794
|
+
id: `finding-${dep.id}-misalignment`,
|
|
3795
|
+
dependencyId: dep.id,
|
|
3796
|
+
type: "investigate",
|
|
3797
|
+
severity: "info",
|
|
3798
|
+
action: "investigate",
|
|
3799
|
+
confidence: 1,
|
|
3800
|
+
rationale: `Version mismatch across workspaces: ${root} uses ${currentVer} while other packages differ [${versionSummary}]`,
|
|
3801
|
+
evidence: [
|
|
3802
|
+
{
|
|
3803
|
+
kind: "manifest",
|
|
3804
|
+
source: "monorepo-alignment",
|
|
3805
|
+
retrievedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3806
|
+
detail: `Workspace distribution: ${versionSummary}`
|
|
3807
|
+
}
|
|
3808
|
+
]
|
|
3809
|
+
});
|
|
3810
|
+
}
|
|
3811
|
+
}
|
|
3812
|
+
return findings;
|
|
3813
|
+
}
|
|
3814
|
+
|
|
3588
3815
|
// src/service/techstack-engine.ts
|
|
3589
3816
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
3590
3817
|
|
|
@@ -3668,20 +3895,26 @@ async function runEnrichPhase(snapshot, options = {}) {
|
|
|
3668
3895
|
if (dependency.name !== name) continue;
|
|
3669
3896
|
const status = classifyStatus(dependency, registryStatus, advisoryStatus);
|
|
3670
3897
|
const evidence = [...dependency.evidence, ...registryStatus?.evidence ?? [], ...advisoryStatus?.evidence ?? []];
|
|
3898
|
+
const license = registryEntry?.license ?? dependency.license;
|
|
3671
3899
|
enriched.set(dependency.id, {
|
|
3672
3900
|
...dependency,
|
|
3673
3901
|
latestStable: registryEntry?.latestStable ?? dependency.latestStable,
|
|
3674
|
-
license
|
|
3902
|
+
license,
|
|
3675
3903
|
deprecated: registryEntry?.deprecated ?? dependency.deprecated,
|
|
3676
3904
|
yanked: registryEntry?.yanked ?? dependency.yanked,
|
|
3677
3905
|
status,
|
|
3678
3906
|
evidence
|
|
3679
3907
|
});
|
|
3680
3908
|
if (status !== "current" && status !== "local_path" && status !== "git_dependency") findings.push(createFindingForStatus(dependency.id, status));
|
|
3909
|
+
const licenseFinding = createLicenseFinding(dependency.id, dependency.name, license);
|
|
3910
|
+
if (licenseFinding) findings.push(licenseFinding);
|
|
3681
3911
|
}
|
|
3682
3912
|
}
|
|
3683
3913
|
}
|
|
3684
|
-
|
|
3914
|
+
const enrichedDeps = snapshot.dependencies.map((dependency) => enriched.get(dependency.id) ?? dependency);
|
|
3915
|
+
const misalignmentFindings = detectWorkspaceMisalignments(enrichedDeps, snapshot.workspaces);
|
|
3916
|
+
findings.push(...misalignmentFindings);
|
|
3917
|
+
return { ...snapshot, dependencies: enrichedDeps, findings };
|
|
3685
3918
|
}
|
|
3686
3919
|
|
|
3687
3920
|
// src/service/inventory-phase.ts
|
|
@@ -3742,6 +3975,8 @@ async function runInventoryPhase(store, projectId, targetRoot, options = {}) {
|
|
|
3742
3975
|
// src/service/report-generator.ts
|
|
3743
3976
|
function generateReport(snapshot, format = "md") {
|
|
3744
3977
|
if (format === "json") return JSON.stringify(snapshot, null, 2);
|
|
3978
|
+
if (format === "spdx") return JSON.stringify(toSpdx(snapshot), null, 2);
|
|
3979
|
+
if (format === "cyclonedx") return JSON.stringify(toCycloneDX(snapshot), null, 2);
|
|
3745
3980
|
const lines = [
|
|
3746
3981
|
"# TechStack Report",
|
|
3747
3982
|
"",
|
|
@@ -4353,6 +4588,15 @@ CREATE TABLE IF NOT EXISTS outbox (
|
|
|
4353
4588
|
);
|
|
4354
4589
|
|
|
4355
4590
|
CREATE INDEX IF NOT EXISTS idx_outbox_status ON outbox(status);
|
|
4591
|
+
|
|
4592
|
+
CREATE TABLE IF NOT EXISTS research_cache (
|
|
4593
|
+
cache_key TEXT PRIMARY KEY,
|
|
4594
|
+
findings_json TEXT NOT NULL,
|
|
4595
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
4596
|
+
expires_at TEXT NOT NULL
|
|
4597
|
+
);
|
|
4598
|
+
|
|
4599
|
+
CREATE INDEX IF NOT EXISTS idx_research_cache_expires_at ON research_cache(expires_at);
|
|
4356
4600
|
`;
|
|
4357
4601
|
function applySchema(db) {
|
|
4358
4602
|
for (const statement of DDL.split(";")) {
|
|
@@ -4600,6 +4844,54 @@ var TechStackStore = class {
|
|
|
4600
4844
|
const rows = stmt.all(status);
|
|
4601
4845
|
return rows.map((r) => this.rowToOutbox(r));
|
|
4602
4846
|
}
|
|
4847
|
+
// ── Research Cache Operations ───────────────────────────────────────────
|
|
4848
|
+
/** Get cached research findings by cache key (returns null if missing or expired). */
|
|
4849
|
+
getCachedResearch(cacheKey) {
|
|
4850
|
+
const stmt = this.stmt(`
|
|
4851
|
+
SELECT findings_json, expires_at FROM research_cache
|
|
4852
|
+
WHERE cache_key = ?
|
|
4853
|
+
`);
|
|
4854
|
+
const row = stmt.get(cacheKey);
|
|
4855
|
+
if (!row) return null;
|
|
4856
|
+
if (new Date(row.expires_at).getTime() <= Date.now()) {
|
|
4857
|
+
this.deleteCachedResearch(cacheKey);
|
|
4858
|
+
return null;
|
|
4859
|
+
}
|
|
4860
|
+
try {
|
|
4861
|
+
return JSON.parse(row.findings_json);
|
|
4862
|
+
} catch {
|
|
4863
|
+
return null;
|
|
4864
|
+
}
|
|
4865
|
+
}
|
|
4866
|
+
/** Store research findings in cache with a TTL (defaults to 7 days). */
|
|
4867
|
+
setCachedResearch(cacheKey, findings, ttlMs = 7 * 24 * 60 * 60 * 1e3) {
|
|
4868
|
+
const expiresAt = new Date(Date.now() + ttlMs).toISOString();
|
|
4869
|
+
const findingsJson = JSON.stringify(findings);
|
|
4870
|
+
const stmt = this.stmt(`
|
|
4871
|
+
INSERT INTO research_cache (cache_key, findings_json, created_at, expires_at)
|
|
4872
|
+
VALUES (?, ?, datetime('now'), ?)
|
|
4873
|
+
ON CONFLICT(cache_key) DO UPDATE SET
|
|
4874
|
+
findings_json = excluded.findings_json,
|
|
4875
|
+
created_at = datetime('now'),
|
|
4876
|
+
expires_at = excluded.expires_at
|
|
4877
|
+
`);
|
|
4878
|
+
stmt.run(cacheKey, findingsJson, expiresAt);
|
|
4879
|
+
}
|
|
4880
|
+
/** Delete a specific cached research entry. */
|
|
4881
|
+
deleteCachedResearch(cacheKey) {
|
|
4882
|
+
const stmt = this.stmt(`
|
|
4883
|
+
DELETE FROM research_cache WHERE cache_key = ?
|
|
4884
|
+
`);
|
|
4885
|
+
stmt.run(cacheKey);
|
|
4886
|
+
}
|
|
4887
|
+
/** Prune all expired research cache entries. */
|
|
4888
|
+
pruneExpiredResearchCache() {
|
|
4889
|
+
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
4890
|
+
const stmt = this.stmt(`
|
|
4891
|
+
DELETE FROM research_cache WHERE expires_at <= ?
|
|
4892
|
+
`);
|
|
4893
|
+
stmt.run(nowIso);
|
|
4894
|
+
}
|
|
4603
4895
|
// ── Row mapping helpers ─────────────────────────────────────────────────
|
|
4604
4896
|
rowToJob(row) {
|
|
4605
4897
|
let progress;
|
|
@@ -4718,6 +5010,7 @@ export {
|
|
|
4718
5010
|
TrendStore,
|
|
4719
5011
|
applyPlan,
|
|
4720
5012
|
applySchema,
|
|
5013
|
+
assessLicense,
|
|
4721
5014
|
attemptDelivery,
|
|
4722
5015
|
buildPurl,
|
|
4723
5016
|
classifyStatus,
|
|
@@ -4728,10 +5021,12 @@ export {
|
|
|
4728
5021
|
coverageForEcosystem,
|
|
4729
5022
|
cppAdapter,
|
|
4730
5023
|
createAuditRunner,
|
|
5024
|
+
createLicenseFinding,
|
|
4731
5025
|
createProviderLlm,
|
|
4732
5026
|
createResearcher,
|
|
4733
5027
|
createToolSearch,
|
|
4734
5028
|
dartAdapter,
|
|
5029
|
+
detectWorkspaceMisalignments,
|
|
4735
5030
|
diffSnapshots,
|
|
4736
5031
|
discoverWorkspaces,
|
|
4737
5032
|
dotNetAdapter,
|
|
@@ -4748,6 +5043,7 @@ export {
|
|
|
4748
5043
|
lookupRegistryBatch,
|
|
4749
5044
|
mapDetectedWorkspace,
|
|
4750
5045
|
mavenAdapter,
|
|
5046
|
+
normalizeLicenseId,
|
|
4751
5047
|
npmAdapter,
|
|
4752
5048
|
parsePurl,
|
|
4753
5049
|
parsePurlEcosystem,
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TechStack — License Risk & Compliance Classification.
|
|
3
|
+
*
|
|
4
|
+
* Categorizes open source software licenses by risk profile (Permissive,
|
|
5
|
+
* Weak Copyleft, Strong Viral Copyleft, Restrictive/Commercial, Unknown)
|
|
6
|
+
* and generates deterministic compliance findings.
|
|
7
|
+
*
|
|
8
|
+
* @see docs/specs/techstack-sdd.md §4.1, §7
|
|
9
|
+
*/
|
|
10
|
+
import type { Finding } from '../types.js';
|
|
11
|
+
export type LicenseCategory = 'permissive' | 'weak_copyleft' | 'strong_copyleft' | 'network_copyleft' | 'restrictive' | 'unlicensed' | 'unknown';
|
|
12
|
+
export interface LicenseRiskAssessment {
|
|
13
|
+
readonly license: string;
|
|
14
|
+
readonly category: LicenseCategory;
|
|
15
|
+
readonly severity: 'info' | 'low' | 'medium' | 'high' | 'critical';
|
|
16
|
+
readonly isCopyleft: boolean;
|
|
17
|
+
readonly isCommercialSafe: boolean;
|
|
18
|
+
readonly rationale: string;
|
|
19
|
+
}
|
|
20
|
+
/** Normalize raw license identifier string for SPDX lookup. */
|
|
21
|
+
export declare function normalizeLicenseId(rawLicense: string | undefined): string;
|
|
22
|
+
/** Assess the compliance and legal risk of a license. */
|
|
23
|
+
export declare function assessLicense(rawLicense: string | undefined): LicenseRiskAssessment;
|
|
24
|
+
/** Generate a License Compliance Finding if the license represents notable risk. */
|
|
25
|
+
export declare function createLicenseFinding(dependencyId: string, dependencyName: string, rawLicense: string | undefined): Finding | null;
|
|
26
|
+
//# sourceMappingURL=license.d.ts.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TechStack — Cross-Workspace Version Drift & Misalignment Detector.
|
|
3
|
+
*
|
|
4
|
+
* In monorepo projects, different workspaces (packages/apps) may declare
|
|
5
|
+
* or resolve different versions of the same dependency, leading to bundle
|
|
6
|
+
* bloat, runtime inconsistencies, and duplicate lockfile resolutions.
|
|
7
|
+
*
|
|
8
|
+
* @see docs/specs/techstack-sdd.md §4.1, §7
|
|
9
|
+
*/
|
|
10
|
+
import type { DependencyObservation, Finding, Workspace } from '../types.js';
|
|
11
|
+
export interface VersionMisalignment {
|
|
12
|
+
readonly ecosystem: string;
|
|
13
|
+
readonly name: string;
|
|
14
|
+
readonly distinctVersions: readonly string[];
|
|
15
|
+
readonly occurrences: ReadonlyArray<{
|
|
16
|
+
readonly dependencyId: string;
|
|
17
|
+
readonly workspaceId: string;
|
|
18
|
+
readonly workspaceRoot: string;
|
|
19
|
+
readonly version: string;
|
|
20
|
+
}>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Detects third-party dependency version mismatches across multiple workspaces.
|
|
24
|
+
*/
|
|
25
|
+
export declare function detectWorkspaceMisalignments(dependencies: readonly DependencyObservation[], workspaces: readonly Workspace[]): readonly Finding[];
|
|
26
|
+
//# sourceMappingURL=misalignment.d.ts.map
|
package/dist/sbom.d.ts
CHANGED
|
@@ -21,7 +21,11 @@ export interface SpdxDocument {
|
|
|
21
21
|
SPDXID: string;
|
|
22
22
|
versionInfo?: string | undefined;
|
|
23
23
|
downloadLocation?: string | undefined;
|
|
24
|
+
filesAnalyzed: boolean;
|
|
24
25
|
licenseConcluded?: string | undefined;
|
|
26
|
+
licenseDeclared?: string | undefined;
|
|
27
|
+
supplier: string;
|
|
28
|
+
copyrightText: string;
|
|
25
29
|
}>;
|
|
26
30
|
}
|
|
27
31
|
export declare function toSpdx(snapshot: Snapshot): SpdxDocument;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { Snapshot } from '../types.js';
|
|
2
|
-
export
|
|
2
|
+
export type ReportFormat = 'md' | 'json' | 'spdx' | 'cyclonedx';
|
|
3
|
+
export declare function generateReport(snapshot: Snapshot, format?: ReportFormat): string;
|
|
3
4
|
//# sourceMappingURL=report-generator.d.ts.map
|
|
@@ -3,8 +3,10 @@ import type { TechStackResearcher } from '../research/types.js';
|
|
|
3
3
|
import type { TechStackStore } from '../store/sqlite.js';
|
|
4
4
|
import type { Snapshot, TechStackJob } from '../types.js';
|
|
5
5
|
import { type EnrichOptions } from './enrich-phase.js';
|
|
6
|
+
import { type ReportFormat } from './report-generator.js';
|
|
6
7
|
import { type ResearchPhaseOptions } from './research-phase.js';
|
|
7
8
|
export type { EnrichOptions } from './enrich-phase.js';
|
|
9
|
+
export type { ReportFormat } from './report-generator.js';
|
|
8
10
|
export interface AnalyzeOptions {
|
|
9
11
|
readonly targetRoot: string;
|
|
10
12
|
readonly sessionId?: string | undefined;
|
|
@@ -24,7 +26,7 @@ export declare class TechStackEngine {
|
|
|
24
26
|
inventory(projectId: string, targetRoot: string, _jobId?: string, onProgress?: (phase: string, completed: number, total: number) => void, options?: InventoryOptions): Promise<Snapshot>;
|
|
25
27
|
enrich(snapshot: Snapshot, options?: EnrichOptions): Promise<Snapshot>;
|
|
26
28
|
research(snapshot: Snapshot, options?: ResearchPhaseOptions): Promise<Snapshot>;
|
|
27
|
-
generateReport(snapshot: Snapshot, format?:
|
|
29
|
+
generateReport(snapshot: Snapshot, format?: ReportFormat): string;
|
|
28
30
|
analyze(projectId: string, options: AnalyzeOptions): Promise<{
|
|
29
31
|
snapshot: Snapshot;
|
|
30
32
|
job: TechStackJob;
|
package/dist/service.d.ts
CHANGED
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
* `service/`; existing imports from `service.js` remain stable.
|
|
6
6
|
*/
|
|
7
7
|
export { TechStackEngine } from './service/techstack-engine.js';
|
|
8
|
-
export type { AnalyzeOptions, EnrichOptions } from './service/techstack-engine.js';
|
|
8
|
+
export type { AnalyzeOptions, EnrichOptions, ReportFormat } from './service/techstack-engine.js';
|
|
9
9
|
//# sourceMappingURL=service.d.ts.map
|
package/dist/store/schema.d.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* @see docs/specs/techstack-sdd.md §3.2, §4.1
|
|
13
13
|
*/
|
|
14
14
|
export declare const SCHEMA_VERSION = 1;
|
|
15
|
-
export declare const DDL = "\nCREATE TABLE IF NOT EXISTS techstack_schema_version (\n version INTEGER NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS snapshots (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL,\n target_root TEXT NOT NULL,\n fingerprint TEXT NOT NULL,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n raw_json TEXT NOT NULL,\n adapter_version TEXT NOT NULL DEFAULT ''\n);\n\nCREATE INDEX IF NOT EXISTS idx_snapshots_project_id ON snapshots(project_id);\nCREATE INDEX IF NOT EXISTS idx_snapshots_created_at ON snapshots(created_at DESC);\n\nCREATE TABLE IF NOT EXISTS jobs (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL,\n target_root TEXT NOT NULL,\n kind TEXT NOT NULL CHECK(kind IN ('inventory', 'analyze')),\n status TEXT NOT NULL DEFAULT 'queued'\n CHECK(status IN ('queued','discovering','inventorying','enriching','researching','synthesizing','completed','failed','cancelled')),\n fingerprint TEXT NOT NULL DEFAULT '',\n requested_by TEXT NOT NULL DEFAULT '',\n session_id TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n completed_at TEXT,\n error TEXT,\n progress_json TEXT\n);\n\nCREATE INDEX IF NOT EXISTS idx_jobs_project_id ON jobs(project_id);\nCREATE INDEX IF NOT EXISTS idx_jobs_status ON jobs(status);\n\nCREATE TABLE IF NOT EXISTS outbox (\n delivery_id TEXT PRIMARY KEY,\n report_id TEXT NOT NULL,\n session_id TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'pending'\n CHECK(status IN ('pending', 'claimed', 'delivered', 'failed')),\n attempts INTEGER NOT NULL DEFAULT 0,\n claimed_at TEXT,\n delivered_at TEXT\n);\n\nCREATE INDEX IF NOT EXISTS idx_outbox_status ON outbox(status);\n";
|
|
15
|
+
export declare const DDL = "\nCREATE TABLE IF NOT EXISTS techstack_schema_version (\n version INTEGER NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS snapshots (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL,\n target_root TEXT NOT NULL,\n fingerprint TEXT NOT NULL,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n raw_json TEXT NOT NULL,\n adapter_version TEXT NOT NULL DEFAULT ''\n);\n\nCREATE INDEX IF NOT EXISTS idx_snapshots_project_id ON snapshots(project_id);\nCREATE INDEX IF NOT EXISTS idx_snapshots_created_at ON snapshots(created_at DESC);\n\nCREATE TABLE IF NOT EXISTS jobs (\n id TEXT PRIMARY KEY,\n project_id TEXT NOT NULL,\n target_root TEXT NOT NULL,\n kind TEXT NOT NULL CHECK(kind IN ('inventory', 'analyze')),\n status TEXT NOT NULL DEFAULT 'queued'\n CHECK(status IN ('queued','discovering','inventorying','enriching','researching','synthesizing','completed','failed','cancelled')),\n fingerprint TEXT NOT NULL DEFAULT '',\n requested_by TEXT NOT NULL DEFAULT '',\n session_id TEXT,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n completed_at TEXT,\n error TEXT,\n progress_json TEXT\n);\n\nCREATE INDEX IF NOT EXISTS idx_jobs_project_id ON jobs(project_id);\nCREATE INDEX IF NOT EXISTS idx_jobs_status ON jobs(status);\n\nCREATE TABLE IF NOT EXISTS outbox (\n delivery_id TEXT PRIMARY KEY,\n report_id TEXT NOT NULL,\n session_id TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'pending'\n CHECK(status IN ('pending', 'claimed', 'delivered', 'failed')),\n attempts INTEGER NOT NULL DEFAULT 0,\n claimed_at TEXT,\n delivered_at TEXT\n);\n\nCREATE INDEX IF NOT EXISTS idx_outbox_status ON outbox(status);\n\nCREATE TABLE IF NOT EXISTS research_cache (\n cache_key TEXT PRIMARY KEY,\n findings_json TEXT NOT NULL,\n created_at TEXT NOT NULL DEFAULT (datetime('now')),\n expires_at TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_research_cache_expires_at ON research_cache(expires_at);\n";
|
|
16
16
|
/**
|
|
17
17
|
* Run the schema DDL and check/migrate version.
|
|
18
18
|
*/
|
package/dist/store/sqlite.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* @see docs/specs/techstack-sdd.md §3.2, §4.1
|
|
10
10
|
*/
|
|
11
|
-
import type { DeliveryOutbox, DeliveryStatus, Snapshot, TechStackJob, TechStackJobStatus, TechStackJobProgress } from '../types.js';
|
|
11
|
+
import type { DeliveryOutbox, DeliveryStatus, Finding, Snapshot, TechStackJob, TechStackJobStatus, TechStackJobProgress } from '../types.js';
|
|
12
12
|
export interface StoreOptions {
|
|
13
13
|
/** Project slug used for the store directory path. */
|
|
14
14
|
readonly projectSlug: string;
|
|
@@ -55,6 +55,14 @@ export declare class TechStackStore {
|
|
|
55
55
|
failOutbox(deliveryId: string): void;
|
|
56
56
|
/** List outbox entries by status. */
|
|
57
57
|
listOutboxByStatus(status: DeliveryStatus): DeliveryOutbox[];
|
|
58
|
+
/** Get cached research findings by cache key (returns null if missing or expired). */
|
|
59
|
+
getCachedResearch(cacheKey: string): readonly Finding[] | null;
|
|
60
|
+
/** Store research findings in cache with a TTL (defaults to 7 days). */
|
|
61
|
+
setCachedResearch(cacheKey: string, findings: readonly Finding[], ttlMs?: number): void;
|
|
62
|
+
/** Delete a specific cached research entry. */
|
|
63
|
+
deleteCachedResearch(cacheKey: string): void;
|
|
64
|
+
/** Prune all expired research cache entries. */
|
|
65
|
+
pruneExpiredResearchCache(): void;
|
|
58
66
|
private rowToJob;
|
|
59
67
|
private rowToOutbox;
|
|
60
68
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wrongstack/techstack",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.307.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "WrongStack TechStack — cross-language dependency intelligence for the active target project: discover, inventory, enrich, analyze, and report.",
|
|
6
6
|
"repository": {
|
|
@@ -26,11 +26,11 @@
|
|
|
26
26
|
"!dist/**/*.map"
|
|
27
27
|
],
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@wrongstack/core": "0.
|
|
30
|
-
"@wrongstack/tools": "0.
|
|
29
|
+
"@wrongstack/core": "0.307.1",
|
|
30
|
+
"@wrongstack/tools": "0.307.1"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@types/node": "^26.
|
|
33
|
+
"@types/node": "^26.2.0",
|
|
34
34
|
"typescript": "^7.0.2"
|
|
35
35
|
},
|
|
36
36
|
"publishConfig": {
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 ECOSTACK TECHNOLOGY OÜ
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|