weavatrix 0.3.13 → 1.0.0
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 +1 -0
- package/README.md +231 -199
- package/bin/native/darwin-arm64/weavatrix +0 -0
- package/bin/native/darwin-x64/weavatrix +0 -0
- package/bin/native/linux-arm64/weavatrix +0 -0
- package/bin/native/linux-x64/weavatrix +0 -0
- package/bin/native/win32-arm64/weavatrix.exe +0 -0
- package/bin/native/win32-x64/weavatrix.exe +0 -0
- package/bin/resolve-binary.mjs +73 -0
- package/bin/run-native.mjs +26 -0
- package/bin/weavatrix-mcp.mjs +7 -6
- package/bin/weavatrix.mjs +5 -0
- package/package.json +8 -65
- package/skill/SKILL.md +62 -374
- package/skill/agents/openai.yaml +4 -0
- package/skill/references/tool-routing.md +45 -0
- package/SECURITY.md +0 -101
- package/docs/adr/0001-v0.3-offline-online-split.md +0 -60
- package/docs/agent-task-benchmark.md +0 -58
- package/docs/releases/v0.2.19.md +0 -33
- package/docs/releases/v0.3.0.md +0 -43
- package/docs/releases/v0.3.1.md +0 -30
- package/docs/releases/v0.3.10.md +0 -76
- package/docs/releases/v0.3.11.md +0 -55
- package/docs/releases/v0.3.12.md +0 -44
- package/docs/releases/v0.3.13.md +0 -34
- package/docs/releases/v0.3.2.md +0 -15
- package/docs/releases/v0.3.3.md +0 -25
- package/docs/releases/v0.3.4.md +0 -40
- package/docs/releases/v0.3.5.md +0 -50
- package/docs/releases/v0.3.6.md +0 -44
- package/docs/releases/v0.3.7.md +0 -37
- package/docs/releases/v0.3.8.md +0 -21
- package/docs/releases/v0.3.9.md +0 -36
- package/scripts/run-agent-task-benchmark.mjs +0 -118
- package/src/analysis/allowed-test-runner.js +0 -70
- package/src/analysis/architecture/contract-graph.js +0 -119
- package/src/analysis/architecture/contract-schema.js +0 -110
- package/src/analysis/architecture/contract-storage.js +0 -35
- package/src/analysis/architecture/contract-verification.js +0 -168
- package/src/analysis/architecture-contract.js +0 -16
- package/src/analysis/audit-debt.js +0 -94
- package/src/analysis/audit-extensions.js +0 -60
- package/src/analysis/cargo-dependency-evidence.js +0 -108
- package/src/analysis/cargo-manifests.js +0 -81
- package/src/analysis/change-classification/diff-parser.js +0 -103
- package/src/analysis/change-classification/options.js +0 -40
- package/src/analysis/change-classification/symbol-classifier.js +0 -177
- package/src/analysis/change-classification.js +0 -188
- package/src/analysis/coverage-reports.js +0 -235
- package/src/analysis/cycle-route.js +0 -14
- package/src/analysis/data-flow-evidence.js +0 -114
- package/src/analysis/dead-check.js +0 -300
- package/src/analysis/dead-code-review/candidates.js +0 -194
- package/src/analysis/dead-code-review/policy.js +0 -23
- package/src/analysis/dead-code-review.js +0 -119
- package/src/analysis/dep-check-ecosystems.js +0 -205
- package/src/analysis/dep-check.js +0 -290
- package/src/analysis/dep-rules.js +0 -10
- package/src/analysis/dependency/conventions.js +0 -16
- package/src/analysis/dependency/scoped-dependencies.js +0 -51
- package/src/analysis/dependency/source-references.js +0 -21
- package/src/analysis/duplicate-groups.js +0 -104
- package/src/analysis/duplicates-worker.js +0 -11
- package/src/analysis/duplicates.compute.js +0 -269
- package/src/analysis/duplicates.js +0 -15
- package/src/analysis/duplicates.run.js +0 -52
- package/src/analysis/duplicates.tokenize.js +0 -199
- package/src/analysis/endpoints/common.js +0 -62
- package/src/analysis/endpoints/extract.js +0 -107
- package/src/analysis/endpoints/inventory.js +0 -84
- package/src/analysis/endpoints/mounts.js +0 -129
- package/src/analysis/endpoints-java.js +0 -260
- package/src/analysis/endpoints-rust.js +0 -124
- package/src/analysis/endpoints.js +0 -3
- package/src/analysis/findings.js +0 -70
- package/src/analysis/git-history/analytics.js +0 -177
- package/src/analysis/git-history/collector.js +0 -67
- package/src/analysis/git-history/options.js +0 -66
- package/src/analysis/git-history.js +0 -143
- package/src/analysis/git-ref-graph.js +0 -76
- package/src/analysis/go-dependency-evidence.js +0 -62
- package/src/analysis/graph-analysis.aggregate.js +0 -295
- package/src/analysis/graph-analysis.edges.js +0 -24
- package/src/analysis/graph-analysis.js +0 -7
- package/src/analysis/graph-analysis.refs.js +0 -154
- package/src/analysis/graph-analysis.summaries.js +0 -65
- package/src/analysis/health-capabilities.js +0 -83
- package/src/analysis/hot-path-review.js +0 -266
- package/src/analysis/http-contract-wrappers.js +0 -241
- package/src/analysis/http-contracts/analysis.js +0 -179
- package/src/analysis/http-contracts/client-call-detection.js +0 -82
- package/src/analysis/http-contracts/client-call-parser.js +0 -273
- package/src/analysis/http-contracts/graph-context.js +0 -143
- package/src/analysis/http-contracts/matching.js +0 -61
- package/src/analysis/http-contracts/shared.js +0 -85
- package/src/analysis/http-contracts.js +0 -6
- package/src/analysis/internal-audit/dependency-health.js +0 -113
- package/src/analysis/internal-audit/python-manifests.js +0 -78
- package/src/analysis/internal-audit/repo-files.js +0 -50
- package/src/analysis/internal-audit/supply-chain.js +0 -149
- package/src/analysis/internal-audit.collect.js +0 -213
- package/src/analysis/internal-audit.js +0 -9
- package/src/analysis/internal-audit.reach.js +0 -173
- package/src/analysis/internal-audit.run.js +0 -256
- package/src/analysis/java-source.js +0 -36
- package/src/analysis/jvm-artifact-index.js +0 -87
- package/src/analysis/jvm-dependency-evidence.js +0 -138
- package/src/analysis/jvm-manifests.js +0 -114
- package/src/analysis/manifests.js +0 -123
- package/src/analysis/package-reachability.js +0 -39
- package/src/analysis/source-complexity.ast.js +0 -97
- package/src/analysis/source-complexity.constants.js +0 -55
- package/src/analysis/source-complexity.js +0 -14
- package/src/analysis/source-complexity.report.js +0 -81
- package/src/analysis/source-complexity.walk.js +0 -228
- package/src/analysis/source-correctness/retry-patterns.js +0 -93
- package/src/analysis/source-correctness.js +0 -222
- package/src/analysis/static-test-reachability.js +0 -150
- package/src/analysis/structure/dependency-graph.js +0 -151
- package/src/analysis/structure/findings.js +0 -162
- package/src/analysis/task-retrieval.js +0 -107
- package/src/analysis/transport-contracts.js +0 -224
- package/src/analysis/transport-runtime-evidence.js +0 -155
- package/src/analysis-kit.mjs +0 -17
- package/src/bounds.js +0 -4
- package/src/build-graph.js +0 -209
- package/src/child-env.js +0 -7
- package/src/config.js +0 -5
- package/src/extension/local-services.mjs +0 -75
- package/src/git-exec.js +0 -67
- package/src/graph/build-worker.js +0 -49
- package/src/graph/builder/go-receiver-resolution.js +0 -112
- package/src/graph/builder/js/dynamic-import.js +0 -26
- package/src/graph/builder/js/queries.js +0 -48
- package/src/graph/builder/lang-csharp.js +0 -40
- package/src/graph/builder/lang-css.js +0 -29
- package/src/graph/builder/lang-go.js +0 -140
- package/src/graph/builder/lang-html.js +0 -29
- package/src/graph/builder/lang-java-calls.js +0 -142
- package/src/graph/builder/lang-java.js +0 -215
- package/src/graph/builder/lang-js.js +0 -291
- package/src/graph/builder/lang-python.js +0 -235
- package/src/graph/builder/lang-rust-calls.js +0 -63
- package/src/graph/builder/lang-rust.js +0 -299
- package/src/graph/builder/lang-solidity.js +0 -126
- package/src/graph/builder/lang-sql.js +0 -263
- package/src/graph/builder/pass2-resolution.js +0 -81
- package/src/graph/builder/spec-pkg.js +0 -87
- package/src/graph/community.js +0 -27
- package/src/graph/edge-provenance.js +0 -31
- package/src/graph/file-lock.js +0 -69
- package/src/graph/freshness-probe.js +0 -142
- package/src/graph/graph-filter.js +0 -63
- package/src/graph/incremental-refresh.js +0 -235
- package/src/graph/internal-builder.barrels.js +0 -235
- package/src/graph/internal-builder.build.js +0 -300
- package/src/graph/internal-builder.java.js +0 -33
- package/src/graph/internal-builder.js +0 -12
- package/src/graph/internal-builder.langs.js +0 -157
- package/src/graph/internal-builder.pass2.js +0 -163
- package/src/graph/internal-builder.resolvers.js +0 -286
- package/src/graph/layout.js +0 -51
- package/src/graph/node-id.js +0 -7
- package/src/graph/parser-artifact-boundary.js +0 -55
- package/src/graph/relations.js +0 -4
- package/src/graph/repo-registry.js +0 -124
- package/src/graph/resolvers/rust.js +0 -117
- package/src/infra/infra-items.js +0 -255
- package/src/infra/infra-registry.js +0 -184
- package/src/infra/infra.detect.js +0 -119
- package/src/infra/infra.js +0 -38
- package/src/infra/infra.match.js +0 -102
- package/src/infra/infra.scan.js +0 -93
- package/src/mcp/actions/graph-lifecycle.mjs +0 -148
- package/src/mcp/architecture-bootstrap.mjs +0 -168
- package/src/mcp/architecture-starter.mjs +0 -234
- package/src/mcp/catalog.mjs +0 -233
- package/src/mcp/company-contract-page.mjs +0 -142
- package/src/mcp/company-contract-reconcile.mjs +0 -87
- package/src/mcp/company-contract-verdict.mjs +0 -42
- package/src/mcp/evidence/bun-lock-graph.mjs +0 -209
- package/src/mcp/evidence/duplicate-member-order.mjs +0 -8
- package/src/mcp/evidence/package-graph-common.mjs +0 -115
- package/src/mcp/evidence/package-lock-graph.mjs +0 -92
- package/src/mcp/evidence-snapshot.architecture.mjs +0 -80
- package/src/mcp/evidence-snapshot.common.mjs +0 -158
- package/src/mcp/evidence-snapshot.duplicates.mjs +0 -213
- package/src/mcp/evidence-snapshot.health.mjs +0 -102
- package/src/mcp/evidence-snapshot.inventory.mjs +0 -148
- package/src/mcp/evidence-snapshot.mjs +0 -50
- package/src/mcp/evidence-snapshot.package-graph.mjs +0 -34
- package/src/mcp/evidence-snapshot.structure.mjs +0 -84
- package/src/mcp/extension-api.mjs +0 -77
- package/src/mcp/git-output.mjs +0 -7
- package/src/mcp/graph/context-core.mjs +0 -111
- package/src/mcp/graph/context-seeds.mjs +0 -207
- package/src/mcp/graph/context-state.mjs +0 -85
- package/src/mcp/graph/reverse-reach.mjs +0 -42
- package/src/mcp/graph/tools-core.mjs +0 -143
- package/src/mcp/graph/tools-query.mjs +0 -276
- package/src/mcp/graph-context.mjs +0 -5
- package/src/mcp/graph-diff.mjs +0 -218
- package/src/mcp/health/audit-format.mjs +0 -199
- package/src/mcp/health/audit.mjs +0 -174
- package/src/mcp/health/dead-code.mjs +0 -110
- package/src/mcp/health/duplicates.mjs +0 -85
- package/src/mcp/health/endpoints.mjs +0 -71
- package/src/mcp/health/structure.mjs +0 -219
- package/src/mcp/runtime-version.mjs +0 -31
- package/src/mcp/server/auto-refresh.mjs +0 -66
- package/src/mcp/server/runtime-config.mjs +0 -43
- package/src/mcp/server/shutdown.mjs +0 -40
- package/src/mcp/staleness-notice.mjs +0 -20
- package/src/mcp/sync/evidence-architecture.mjs +0 -54
- package/src/mcp/sync/evidence-common.mjs +0 -88
- package/src/mcp/sync/evidence-duplicates.mjs +0 -97
- package/src/mcp/sync/evidence-health.mjs +0 -56
- package/src/mcp/sync/evidence-packages.mjs +0 -95
- package/src/mcp/sync/payload-common.mjs +0 -97
- package/src/mcp/sync/payload-v2.mjs +0 -54
- package/src/mcp/sync/payload-v3.mjs +0 -79
- package/src/mcp/sync-evidence.mjs +0 -23
- package/src/mcp/sync-payload.mjs +0 -70
- package/src/mcp/tool-result.mjs +0 -54
- package/src/mcp/tools-actions.mjs +0 -11
- package/src/mcp/tools-architecture.mjs +0 -177
- package/src/mcp/tools-company.mjs +0 -251
- package/src/mcp/tools-context.mjs +0 -222
- package/src/mcp/tools-endpoints.mjs +0 -207
- package/src/mcp/tools-graph-hubs.mjs +0 -78
- package/src/mcp/tools-graph.mjs +0 -17
- package/src/mcp/tools-health.mjs +0 -24
- package/src/mcp/tools-history.mjs +0 -28
- package/src/mcp/tools-impact-change.mjs +0 -220
- package/src/mcp/tools-impact-precision.mjs +0 -89
- package/src/mcp/tools-impact.mjs +0 -169
- package/src/mcp/tools-source.mjs +0 -238
- package/src/mcp/tools-verified-change.mjs +0 -200
- package/src/mcp-rg.mjs +0 -50
- package/src/mcp-runtime.mjs +0 -2
- package/src/mcp-server.mjs +0 -298
- package/src/mcp-source-tools.mjs +0 -163
- package/src/path-classification.js +0 -220
- package/src/path-ignore.js +0 -69
- package/src/precision/lsp-client/constants.js +0 -12
- package/src/precision/lsp-client/environment.js +0 -20
- package/src/precision/lsp-client/errors.js +0 -15
- package/src/precision/lsp-client/lifecycle.js +0 -114
- package/src/precision/lsp-client/message-parser.js +0 -81
- package/src/precision/lsp-client/protocol.js +0 -212
- package/src/precision/lsp-client/registry.js +0 -59
- package/src/precision/lsp-client/repo-uri.js +0 -60
- package/src/precision/lsp-client/stdio-client.js +0 -140
- package/src/precision/lsp-client.js +0 -10
- package/src/precision/lsp-overlay/budget-policy.js +0 -78
- package/src/precision/lsp-overlay/build.js +0 -261
- package/src/precision/lsp-overlay/contract.js +0 -61
- package/src/precision/lsp-overlay/reference-results.js +0 -108
- package/src/precision/lsp-overlay/semantic-inputs.js +0 -62
- package/src/precision/lsp-overlay/source-session.js +0 -142
- package/src/precision/lsp-overlay/store.js +0 -150
- package/src/precision/lsp-overlay/target-index.js +0 -152
- package/src/precision/lsp-overlay/target-query.js +0 -55
- package/src/precision/lsp-overlay.js +0 -11
- package/src/precision/symbol-query.js +0 -224
- package/src/precision/typescript-lsp-provider.js +0 -12
- package/src/precision/typescript-provider/client.js +0 -89
- package/src/precision/typescript-provider/discovery.js +0 -124
- package/src/precision/typescript-provider/isolated-runtime.js +0 -39
- package/src/precision/typescript-provider/project-host.js +0 -254
- package/src/precision/typescript-provider/project-safety.js +0 -166
- package/src/precision/typescript-provider/reference-usage.js +0 -53
- package/src/process.js +0 -95
- package/src/repo-path.js +0 -53
- package/src/scan/discover.inventory.js +0 -78
- package/src/scan/discover.js +0 -5
- package/src/scan/discover.list.js +0 -79
- package/src/scan/discover.stack.js +0 -227
- package/src/scan/search.core.js +0 -24
- package/src/scan/search.git.js +0 -102
- package/src/scan/search.js +0 -9
- package/src/scan/search.node.js +0 -83
- package/src/scan/search.preview.js +0 -49
- package/src/scan/search.rg.js +0 -145
- package/src/security/advisory-store.js +0 -172
- package/src/security/installed-jvm-rust.js +0 -46
- package/src/security/installed.js +0 -280
- package/src/security/malware-file-heuristics.js +0 -121
- package/src/security/malware-heuristics.exclusions.js +0 -134
- package/src/security/malware-heuristics.js +0 -15
- package/src/security/malware-heuristics.roots.js +0 -145
- package/src/security/malware-heuristics.scan.js +0 -89
- package/src/security/malware-heuristics.sweep.js +0 -122
- package/src/security/malware-scoring.js +0 -105
- package/src/security/match.js +0 -85
- package/src/security/registry-sig.classify.js +0 -137
- package/src/security/registry-sig.js +0 -18
- package/src/security/registry-sig.rules.js +0 -192
- package/src/security/rust-advisory-report.js +0 -60
- package/src/security/typosquat.js +0 -81
- package/src/util.js +0 -40
- package/src/version.js +0 -7
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import {isWeavatrixIgnored} from '../../path-ignore.js'
|
|
2
|
-
import {
|
|
3
|
-
boundedHistoryInteger,
|
|
4
|
-
GIT_HISTORY_DEFAULTS as DEFAULTS,
|
|
5
|
-
GIT_HISTORY_HARD_CAPS as HARD_CAPS,
|
|
6
|
-
safeHistoryPath,
|
|
7
|
-
} from './options.js'
|
|
8
|
-
|
|
9
|
-
const HEADER_SEPARATOR = '\x1e'
|
|
10
|
-
const FIELD_SEPARATOR = '\x1f'
|
|
11
|
-
|
|
12
|
-
// git subprocess execution (sync runGit + streaming boundedGitCommand) lives in ../../git-exec.js.
|
|
13
|
-
|
|
14
|
-
const statNumber = (value) => value === '-'
|
|
15
|
-
? {value: 0, binary: true}
|
|
16
|
-
: {value: Number(value), binary: false}
|
|
17
|
-
|
|
18
|
-
export function parseGitNumstatLog(raw, options = {}) {
|
|
19
|
-
const maxFiles = boundedHistoryInteger(options.maxFilesPerCommit, DEFAULTS.maxFilesPerCommit, 2, HARD_CAPS.maxFilesPerCommit)
|
|
20
|
-
const ignoreRules = options.ignoreRules || []
|
|
21
|
-
const text = Buffer.isBuffer(raw) ? raw.toString('utf8') : String(raw || '')
|
|
22
|
-
const segments = text.split(HEADER_SEPARATOR).slice(1)
|
|
23
|
-
if (options.dropLastIncomplete && segments.length) segments.pop()
|
|
24
|
-
const commits = []
|
|
25
|
-
for (const segment of segments) {
|
|
26
|
-
const firstNul = segment.indexOf('\0')
|
|
27
|
-
if (firstNul < 0) continue
|
|
28
|
-
const header = segment.slice(0, firstNul).replace(/^[\r\n]+/, '')
|
|
29
|
-
const separator = header.indexOf(FIELD_SEPARATOR)
|
|
30
|
-
if (separator < 0) continue
|
|
31
|
-
const hash = header.slice(0, separator), timestamp = Number(header.slice(separator + 1))
|
|
32
|
-
if (!/^[a-f0-9]{40,64}$/i.test(hash) || !Number.isInteger(timestamp) || timestamp < 0) continue
|
|
33
|
-
const tokens = segment.slice(firstNul + 1).split('\0'), files = new Map()
|
|
34
|
-
let fileCount = 0, ignoredFiles = 0, invalidPaths = 0, oversized = false
|
|
35
|
-
for (let index = 0; index < tokens.length; index += 1) {
|
|
36
|
-
const token = tokens[index].replace(/^[\r\n]+/, '')
|
|
37
|
-
const match = /^(\d+|-)\t(\d+|-)\t(.*)$/s.exec(token)
|
|
38
|
-
if (!match) continue
|
|
39
|
-
let rawPath = match[3], renamedFrom = null
|
|
40
|
-
if (!rawPath) {
|
|
41
|
-
renamedFrom = safeHistoryPath(tokens[index + 1])
|
|
42
|
-
rawPath = tokens[index + 2]
|
|
43
|
-
index += 2
|
|
44
|
-
}
|
|
45
|
-
const path = safeHistoryPath(rawPath)
|
|
46
|
-
if (!path) { invalidPaths++; continue }
|
|
47
|
-
if (isWeavatrixIgnored(path, ignoreRules)) { ignoredFiles++; continue }
|
|
48
|
-
const additions = statNumber(match[1]), deletions = statNumber(match[2])
|
|
49
|
-
fileCount++
|
|
50
|
-
if (fileCount > maxFiles) { oversized = true; files.clear(); continue }
|
|
51
|
-
if (oversized) continue
|
|
52
|
-
const previous = files.get(path)
|
|
53
|
-
files.set(path, {
|
|
54
|
-
file: path,
|
|
55
|
-
additions: (previous?.additions || 0) + additions.value,
|
|
56
|
-
deletions: (previous?.deletions || 0) + deletions.value,
|
|
57
|
-
binary: Boolean(previous?.binary || additions.binary || deletions.binary),
|
|
58
|
-
...(renamedFrom ? {renamedFrom} : previous?.renamedFrom ? {renamedFrom: previous.renamedFrom} : {}),
|
|
59
|
-
})
|
|
60
|
-
}
|
|
61
|
-
commits.push({
|
|
62
|
-
hash, timestamp, fileCount, ignoredFiles, invalidPaths, oversized,
|
|
63
|
-
files: oversized ? [] : [...files.values()].sort((a, b) => a.file.localeCompare(b.file)),
|
|
64
|
-
})
|
|
65
|
-
}
|
|
66
|
-
return commits
|
|
67
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import {isAbsolute} from 'node:path'
|
|
2
|
-
import {boundedInteger} from '../../util.js'
|
|
3
|
-
|
|
4
|
-
export const GIT_HISTORY_V = 1
|
|
5
|
-
export const GIT_HISTORY_WINDOWS = Object.freeze([3, 6, 12])
|
|
6
|
-
export const GIT_HISTORY_DEFAULTS = Object.freeze({
|
|
7
|
-
months: 6,
|
|
8
|
-
maxCommits: 500,
|
|
9
|
-
maxFilesPerCommit: 80,
|
|
10
|
-
maxPairs: 100,
|
|
11
|
-
minPairCount: 2,
|
|
12
|
-
maxPairCandidates: 100_000,
|
|
13
|
-
maxOutputBytes: 16 * 1024 * 1024,
|
|
14
|
-
timeoutMs: 20_000,
|
|
15
|
-
})
|
|
16
|
-
export const GIT_HISTORY_HARD_CAPS = Object.freeze({
|
|
17
|
-
maxCommits: 2_000,
|
|
18
|
-
maxFilesPerCommit: 200,
|
|
19
|
-
maxPairs: 500,
|
|
20
|
-
maxPairCandidates: 250_000,
|
|
21
|
-
maxOutputBytes: 64 * 1024 * 1024,
|
|
22
|
-
timeoutMs: 60_000,
|
|
23
|
-
})
|
|
24
|
-
export const GIT_FORMAT = '%x1e%H%x1f%ct'
|
|
25
|
-
|
|
26
|
-
export const graphEndpoint = (value) => value && typeof value === 'object' ? value.id : value
|
|
27
|
-
export const roundHistoryNumber = (value, digits = 4) => {
|
|
28
|
-
if (!Number.isFinite(value)) return 0
|
|
29
|
-
const scale = 10 ** digits
|
|
30
|
-
return Math.round(value * scale) / scale
|
|
31
|
-
}
|
|
32
|
-
export const boundedHistoryInteger = boundedInteger
|
|
33
|
-
|
|
34
|
-
export function safeHistoryPath(value) {
|
|
35
|
-
const path = String(value || '').replace(/\\/g, '/').replace(/^\.\//, '')
|
|
36
|
-
if (!path || path.includes('\0') || isAbsolute(path) || /^[a-z]:\//i.test(path)) return null
|
|
37
|
-
if (/[\x00-\x1f\x7f]/.test(path)) return null
|
|
38
|
-
const parts = path.split('/')
|
|
39
|
-
return parts.some((part) => !part || part === '.' || part === '..') ? null : path
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function utcMonthsBefore(date, months) {
|
|
43
|
-
const source = new Date(date)
|
|
44
|
-
if (!Number.isFinite(source.getTime())) throw new Error('now must be a valid date')
|
|
45
|
-
const targetMonth = source.getUTCMonth() - months
|
|
46
|
-
const first = new Date(Date.UTC(source.getUTCFullYear(), targetMonth, 1, source.getUTCHours(), source.getUTCMinutes(), source.getUTCSeconds(), source.getUTCMilliseconds()))
|
|
47
|
-
const lastDay = new Date(Date.UTC(first.getUTCFullYear(), first.getUTCMonth() + 1, 0)).getUTCDate()
|
|
48
|
-
first.setUTCDate(Math.min(source.getUTCDate(), lastDay))
|
|
49
|
-
return first
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function normalizeGitHistoryOptions(input = {}) {
|
|
53
|
-
const defaults = GIT_HISTORY_DEFAULTS, caps = GIT_HISTORY_HARD_CAPS
|
|
54
|
-
const months = Number(input.months ?? defaults.months)
|
|
55
|
-
if (!GIT_HISTORY_WINDOWS.includes(months)) throw new Error('months must be one of 3, 6 or 12')
|
|
56
|
-
return {
|
|
57
|
-
months,
|
|
58
|
-
maxCommits: boundedHistoryInteger(input.maxCommits, defaults.maxCommits, 1, caps.maxCommits),
|
|
59
|
-
maxFilesPerCommit: boundedHistoryInteger(input.maxFilesPerCommit, defaults.maxFilesPerCommit, 2, caps.maxFilesPerCommit),
|
|
60
|
-
maxPairs: boundedHistoryInteger(input.maxPairs, defaults.maxPairs, 1, caps.maxPairs),
|
|
61
|
-
minPairCount: boundedHistoryInteger(input.minPairCount, defaults.minPairCount, 1, 100),
|
|
62
|
-
maxPairCandidates: boundedHistoryInteger(input.maxPairCandidates, defaults.maxPairCandidates, 100, caps.maxPairCandidates),
|
|
63
|
-
maxOutputBytes: boundedHistoryInteger(input.maxOutputBytes, defaults.maxOutputBytes, 64 * 1024, caps.maxOutputBytes),
|
|
64
|
-
timeoutMs: boundedHistoryInteger(input.timeoutMs, defaults.timeoutMs, 1_000, caps.timeoutMs),
|
|
65
|
-
}
|
|
66
|
-
}
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
// Public facade and bounded local collector for behavioral Git-history intelligence.
|
|
2
|
-
import {childProcessEnv} from '../child-env.js'
|
|
3
|
-
import {createRepoBoundary} from '../repo-path.js'
|
|
4
|
-
import {loadWeavatrixIgnore} from '../path-ignore.js'
|
|
5
|
-
import {buildGitHistoryAnalytics} from './git-history/analytics.js'
|
|
6
|
-
import {parseGitNumstatLog} from './git-history/collector.js'
|
|
7
|
-
import {boundedGitCommand} from '../git-exec.js'
|
|
8
|
-
import {
|
|
9
|
-
boundedHistoryInteger,
|
|
10
|
-
GIT_FORMAT,
|
|
11
|
-
GIT_HISTORY_V,
|
|
12
|
-
normalizeGitHistoryOptions,
|
|
13
|
-
utcMonthsBefore,
|
|
14
|
-
} from './git-history/options.js'
|
|
15
|
-
|
|
16
|
-
export {buildGitHistoryAnalytics} from './git-history/analytics.js'
|
|
17
|
-
export {parseGitNumstatLog} from './git-history/collector.js'
|
|
18
|
-
export {GIT_HISTORY_V, GIT_HISTORY_WINDOWS} from './git-history/options.js'
|
|
19
|
-
|
|
20
|
-
const unavailableResult = (window, limits, reason) => ({
|
|
21
|
-
gitHistoryV: GIT_HISTORY_V,
|
|
22
|
-
status: 'unavailable',
|
|
23
|
-
window,
|
|
24
|
-
limits,
|
|
25
|
-
completeness: {complete: false, reasons: [reason]},
|
|
26
|
-
totals: {
|
|
27
|
-
commitsRead: 0, commitsAnalyzed: 0, oversizedCommitsSkipped: 0, files: 0,
|
|
28
|
-
additions: 0, deletions: 0, churn: 0, binaryChanges: 0, ignoredFiles: 0,
|
|
29
|
-
invalidPaths: 0, graphFiles: 0,
|
|
30
|
-
},
|
|
31
|
-
fileChurn: [],
|
|
32
|
-
hotspots: [],
|
|
33
|
-
coupling: {eligibleCommits: 0, totalCandidates: 0, candidatesTruncated: false, observed: [], expectedTestSource: [], hidden: []},
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
export async function analyzeGitHistory(input = {}) {
|
|
37
|
-
const options = normalizeGitHistoryOptions(input)
|
|
38
|
-
const now = new Date(input.now ?? Date.now())
|
|
39
|
-
if (!Number.isFinite(now.getTime())) throw new Error('now must be a valid date')
|
|
40
|
-
const since = utcMonthsBefore(now, options.months)
|
|
41
|
-
const window = {months: options.months, since: since.toISOString(), until: now.toISOString()}
|
|
42
|
-
const limits = {
|
|
43
|
-
maxCommits: options.maxCommits,
|
|
44
|
-
maxFilesPerCommit: options.maxFilesPerCommit,
|
|
45
|
-
maxPairs: options.maxPairs,
|
|
46
|
-
minPairCount: options.minPairCount,
|
|
47
|
-
maxPairCandidates: options.maxPairCandidates,
|
|
48
|
-
}
|
|
49
|
-
const boundary = createRepoBoundary(input.repoRoot)
|
|
50
|
-
if (!boundary.root) return unavailableResult(window, limits, 'repository root is unavailable')
|
|
51
|
-
const args = [
|
|
52
|
-
'log', '--no-merges', '--numstat', '-z', `--format=${GIT_FORMAT}`,
|
|
53
|
-
`--since=${window.since}`, `--until=${window.until}`, `--max-count=${options.maxCommits + 1}`,
|
|
54
|
-
'--', '.',
|
|
55
|
-
]
|
|
56
|
-
let execution
|
|
57
|
-
try {
|
|
58
|
-
execution = await (input.runner || boundedGitCommand)('git', args, {
|
|
59
|
-
cwd: boundary.root,
|
|
60
|
-
env: childProcessEnv(),
|
|
61
|
-
timeoutMs: options.timeoutMs,
|
|
62
|
-
maxOutputBytes: options.maxOutputBytes,
|
|
63
|
-
})
|
|
64
|
-
} catch (error) {
|
|
65
|
-
return unavailableResult(window, limits, String(error?.message || 'git history collection failed').slice(0, 200))
|
|
66
|
-
}
|
|
67
|
-
if (execution.exitCode !== 0 && !execution.truncated) return unavailableResult(window, limits, 'git log failed')
|
|
68
|
-
const raw = Buffer.isBuffer(execution.stdout) ? execution.stdout : Buffer.from(String(execution.stdout || ''))
|
|
69
|
-
const tooLarge = raw.length > options.maxOutputBytes
|
|
70
|
-
const truncated = Boolean(execution.truncated || tooLarge)
|
|
71
|
-
const bounded = tooLarge ? raw.subarray(0, options.maxOutputBytes) : raw
|
|
72
|
-
const parsed = parseGitNumstatLog(bounded, {
|
|
73
|
-
maxFilesPerCommit: options.maxFilesPerCommit,
|
|
74
|
-
ignoreRules: loadWeavatrixIgnore(boundary.root),
|
|
75
|
-
dropLastIncomplete: truncated,
|
|
76
|
-
})
|
|
77
|
-
const commitsTruncated = parsed.length > options.maxCommits
|
|
78
|
-
return buildGitHistoryAnalytics({
|
|
79
|
-
commits: parsed.slice(0, options.maxCommits),
|
|
80
|
-
graph: input.graph || {},
|
|
81
|
-
window,
|
|
82
|
-
limits,
|
|
83
|
-
status: truncated || commitsTruncated ? 'partial' : 'complete',
|
|
84
|
-
})
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export function formatGitHistoryAnalytics(result, options = {}) {
|
|
88
|
-
const topN = boundedHistoryInteger(options.topN, 10, 1, 50)
|
|
89
|
-
if (!result || result.status === 'unavailable') {
|
|
90
|
-
return `Git history intelligence: UNAVAILABLE — ${result?.completeness?.reasons?.[0] || 'history is unavailable'}`
|
|
91
|
-
}
|
|
92
|
-
const window = result.window
|
|
93
|
-
? `${result.window.months} months (${result.window.since.slice(0, 10)} → ${result.window.until.slice(0, 10)})`
|
|
94
|
-
: 'configured window'
|
|
95
|
-
const lines = [
|
|
96
|
-
`Git history intelligence — ${window}`,
|
|
97
|
-
`Status: ${String(result.status).toUpperCase()} · ${result.totals.commitsAnalyzed}/${result.totals.commitsRead} commits analyzed · ${result.totals.files} files · ${result.totals.churn} changed lines`,
|
|
98
|
-
'',
|
|
99
|
-
'Hotspots (churn percentile × graph-connectivity percentile):',
|
|
100
|
-
]
|
|
101
|
-
const hotspots = result.hotspots.slice(0, topN)
|
|
102
|
-
if (!hotspots.length) lines.push('- none')
|
|
103
|
-
for (const item of hotspots) lines.push(`- ${item.file}: score ${item.hotspotScore.toFixed(4)} · churn ${item.churn} in ${item.commits} commits · connectivity ${item.connectivity}`)
|
|
104
|
-
lines.push('', 'Hidden co-change coupling (no graph path within 2 hops):')
|
|
105
|
-
const hidden = result.coupling.hidden.slice(0, topN)
|
|
106
|
-
if (!hidden.length) lines.push('- none')
|
|
107
|
-
for (const pair of hidden) lines.push(`- ${pair.left} ↔ ${pair.right}: ${pair.count} commits · Jaccard ${pair.jaccard.toFixed(4)} · lift ${pair.lift.toFixed(4)} · confidence ${pair.confidence.toFixed(4)}`)
|
|
108
|
-
lines.push('', 'Expected test/source co-change:')
|
|
109
|
-
const expected = result.coupling.expectedTestSource.slice(0, topN)
|
|
110
|
-
if (!expected.length) lines.push('- none')
|
|
111
|
-
for (const pair of expected) lines.push(`- ${pair.test} ↔ ${pair.source}: ${pair.count} commits · confidence ${pair.confidence.toFixed(4)}`)
|
|
112
|
-
if (result.completeness.reasons.length) lines.push('', `Partial: ${result.completeness.reasons.join('; ')}.`)
|
|
113
|
-
return lines.join('\n')
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export function boundGitHistoryAnalytics(result, options = {}) {
|
|
117
|
-
const topN = boundedHistoryInteger(options.topN, 10, 1, 50)
|
|
118
|
-
const source = result && typeof result === 'object' ? result : {}
|
|
119
|
-
const coupling = source.coupling && typeof source.coupling === 'object' ? source.coupling : {}
|
|
120
|
-
const collections = {}
|
|
121
|
-
const cap = (name, value) => {
|
|
122
|
-
const items = Array.isArray(value) ? value : []
|
|
123
|
-
const bounded = items.slice(0, topN)
|
|
124
|
-
collections[name] = {total: items.length, returned: bounded.length, truncated: items.length > bounded.length}
|
|
125
|
-
return bounded
|
|
126
|
-
}
|
|
127
|
-
const bounded = {
|
|
128
|
-
...source,
|
|
129
|
-
limits: {...(source.limits || {}), topN},
|
|
130
|
-
fileChurn: cap('fileChurn', source.fileChurn),
|
|
131
|
-
hotspots: cap('hotspots', source.hotspots),
|
|
132
|
-
coupling: {
|
|
133
|
-
...coupling,
|
|
134
|
-
observed: cap('coupling.observed', coupling.observed),
|
|
135
|
-
expectedTestSource: cap('coupling.expectedTestSource', coupling.expectedTestSource),
|
|
136
|
-
hidden: cap('coupling.hidden', coupling.hidden),
|
|
137
|
-
},
|
|
138
|
-
}
|
|
139
|
-
return {
|
|
140
|
-
result: bounded,
|
|
141
|
-
page: {limit: topN, truncated: Object.values(collections).some((entry) => entry.truncated), collections},
|
|
142
|
-
}
|
|
143
|
-
}
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
// Build a source graph for an immutable Git commit without mutating the user's worktree.
|
|
2
|
-
// The commit is first resolved to a full object id, then exported into a private temporary
|
|
3
|
-
// directory. No hooks are run and no branch/worktree metadata is changed.
|
|
4
|
-
import {mkdtempSync, mkdirSync, rmSync} from 'node:fs'
|
|
5
|
-
import {join} from 'node:path'
|
|
6
|
-
import {tmpdir} from 'node:os'
|
|
7
|
-
import {runGit} from '../git-exec.js'
|
|
8
|
-
import {runCommandSync} from '../process.js'
|
|
9
|
-
import {buildInternalGraph} from '../graph/internal-builder.js'
|
|
10
|
-
import {filterGraphForMode} from '../graph/graph-filter.js'
|
|
11
|
-
|
|
12
|
-
const SAFE_REF = /^(?!-)[A-Za-z0-9][A-Za-z0-9._\/@{}+~^-]{0,199}$/
|
|
13
|
-
|
|
14
|
-
function git(repoRoot, args, timeout = 15_000) {
|
|
15
|
-
return runGit(repoRoot, args, {timeout, maxBuffer: 4 * 1024 * 1024})
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function resolveGitCommit(repoRoot, requestedRef) {
|
|
19
|
-
const ref = String(requestedRef || '').trim()
|
|
20
|
-
if (!SAFE_REF.test(ref)) return {ok: false, error: 'base_ref contains unsupported characters'}
|
|
21
|
-
const result = git(repoRoot, ['rev-parse', '--verify', '--quiet', `${ref}^{commit}`])
|
|
22
|
-
const commit = String(result.stdout || '').trim()
|
|
23
|
-
if (result.status !== 0 || !/^[a-f0-9]{40,64}$/i.test(commit)) {
|
|
24
|
-
return {ok: false, error: `could not resolve Git ref "${ref}" to a commit`}
|
|
25
|
-
}
|
|
26
|
-
return {ok: true, ref, commit}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export async function withGitRefCheckout(repoRoot, requestedRef, operation) {
|
|
30
|
-
const resolved = resolveGitCommit(repoRoot, requestedRef)
|
|
31
|
-
if (!resolved.ok) return resolved
|
|
32
|
-
const temp = mkdtempSync(join(tmpdir(), 'weavatrix-git-ref-'))
|
|
33
|
-
const checkout = join(temp, 'repo')
|
|
34
|
-
const archive = join(temp, 'source.tar')
|
|
35
|
-
mkdirSync(checkout, {recursive: true})
|
|
36
|
-
try {
|
|
37
|
-
const archived = git(repoRoot, ['archive', '--format=tar', `--output=${archive}`, resolved.commit], 60_000)
|
|
38
|
-
if (archived.status !== 0) {
|
|
39
|
-
return {ok: false, error: `git archive failed for ${resolved.ref}: ${String(archived.stderr || '').trim() || 'unknown error'}`}
|
|
40
|
-
}
|
|
41
|
-
const extracted = runCommandSync('tar', ['-xf', archive, '-C', checkout], {timeout: 60_000, maxBuffer: 4 * 1024 * 1024})
|
|
42
|
-
if (extracted.status !== 0) {
|
|
43
|
-
return {ok: false, error: `temporary Git archive extraction failed: ${String(extracted.stderr || '').trim() || 'tar unavailable'}`}
|
|
44
|
-
}
|
|
45
|
-
// A normal build asks Git for tracked + non-ignored files. A plain archive has no .git
|
|
46
|
-
// directory, so the fallback walker deliberately skips tracked dot paths such as
|
|
47
|
-
// .github/workflows. Recreate only an index over the immutable archive (no commit, hooks or
|
|
48
|
-
// checkout) so live and baseline graphs use the same file-universe semantics.
|
|
49
|
-
const initialized = git(checkout, ['init', '--quiet'])
|
|
50
|
-
if (initialized.status !== 0) {
|
|
51
|
-
return {ok: false, error: `temporary Git index initialization failed: ${String(initialized.stderr || '').trim() || 'unknown error'}`}
|
|
52
|
-
}
|
|
53
|
-
const indexed = git(checkout, ['add', '--all', '--force'], 60_000)
|
|
54
|
-
if (indexed.status !== 0) {
|
|
55
|
-
return {ok: false, error: `temporary Git index population failed: ${String(indexed.stderr || '').trim() || 'unknown error'}`}
|
|
56
|
-
}
|
|
57
|
-
const value = await operation(checkout, resolved)
|
|
58
|
-
return {ok: true, ref: resolved.ref, commit: resolved.commit, value}
|
|
59
|
-
} catch (error) {
|
|
60
|
-
return {ok: false, error: error instanceof Error ? error.message : String(error)}
|
|
61
|
-
} finally {
|
|
62
|
-
rmSync(temp, {recursive: true, force: true})
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export async function buildGraphAtGitRef(repoRoot, requestedRef, {mode = 'full'} = {}) {
|
|
67
|
-
const buildMode = ['full', 'no-tests', 'tests-only'].includes(mode) ? mode : 'full'
|
|
68
|
-
const result = await withGitRefCheckout(repoRoot, requestedRef, async (checkout) => {
|
|
69
|
-
const fullGraph = await buildInternalGraph(checkout)
|
|
70
|
-
return {
|
|
71
|
-
...filterGraphForMode(fullGraph, buildMode, {repoRoot: checkout}),
|
|
72
|
-
graphBuildMode: buildMode,
|
|
73
|
-
}
|
|
74
|
-
})
|
|
75
|
-
return result.ok ? {...result, graph: result.value} : result
|
|
76
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { createRepoBoundary } from "../repo-path.js";
|
|
2
|
-
import { computeGoDepFindings } from "./dep-check-ecosystems.js";
|
|
3
|
-
import { listRepoFiles, readRepoText } from "./internal-audit.collect.js";
|
|
4
|
-
import { parseGoMod } from "./manifests.js";
|
|
5
|
-
import { makeFinding } from "./findings.js";
|
|
6
|
-
import { dependencyScopeOwnsFile, dependencyScopeRoot } from "./dependency/scoped-dependencies.js";
|
|
7
|
-
|
|
8
|
-
export function collectGoDependencyEvidence(repoRoot, { files = listRepoFiles(repoRoot), externalImports = [], nonRuntimeRoots = [] } = {}) {
|
|
9
|
-
const boundary = createRepoBoundary(repoRoot);
|
|
10
|
-
const manifests = files.filter((file) => /(^|\/)go\.mod$/i.test(file));
|
|
11
|
-
const scopes = manifests.map((file) => ({ file, root: dependencyScopeRoot(file), parsed: parseGoMod(readRepoText(boundary, file)) }))
|
|
12
|
-
.sort((left, right) => right.root.length - left.root.length || left.root.localeCompare(right.root));
|
|
13
|
-
const importsByScope = new Map(scopes.map((scope) => [scope, []]));
|
|
14
|
-
for (const entry of externalImports) {
|
|
15
|
-
if (entry.ecosystem !== "Go") continue;
|
|
16
|
-
const owner = scopes.find((scope) => dependencyScopeOwnsFile(scope.root, entry.file));
|
|
17
|
-
if (owner) importsByScope.get(owner).push(entry);
|
|
18
|
-
}
|
|
19
|
-
const findings = [], declared = new Set(), issues = [];
|
|
20
|
-
if (!scopes.length) {
|
|
21
|
-
const missing = new Map();
|
|
22
|
-
for (const entry of externalImports.filter((item) => item.ecosystem === "Go" && item.pkg && !item.builtin && !item.unresolved)) {
|
|
23
|
-
if (!missing.has(entry.pkg)) missing.set(entry.pkg, entry);
|
|
24
|
-
}
|
|
25
|
-
for (const [name, entry] of missing) findings.push(makeFinding({
|
|
26
|
-
category: "unused", rule: "missing-dep", severity: "low", confidence: "high",
|
|
27
|
-
title: `Go import without a module manifest: ${name}`,
|
|
28
|
-
reason: "An indexed external Go import exists, but no go.mod was discovered.",
|
|
29
|
-
detail: `"${entry.spec || name}" is imported by ${entry.file}, but no go.mod exists in the repository.`,
|
|
30
|
-
package: name, file: entry.file, line: entry.line || 0, source: "internal",
|
|
31
|
-
verification: { evidenceModel: "MANIFEST_PLUS_INDEXED_SOURCE", decision: "ACTION_REQUIRED", manifestDeclaration: { status: "NOT_PRESENT" }, indexedSourceImports: { status: "FOUND", count: 1, files: [entry.file] }, mapping: "Go module prefix" },
|
|
32
|
-
fixHint: "initialize or restore the owning Go module manifest",
|
|
33
|
-
}));
|
|
34
|
-
}
|
|
35
|
-
for (const scope of scopes) {
|
|
36
|
-
if (!scope.parsed.module) issues.push(`${scope.file}: module directive is missing or unreadable`);
|
|
37
|
-
const result = computeGoDepFindings({ externalImports: importsByScope.get(scope), goMod: scope.parsed, nonRuntimeRoots });
|
|
38
|
-
findings.push(...result.findings.map((finding) => ({
|
|
39
|
-
...finding,
|
|
40
|
-
manifest: scope.file,
|
|
41
|
-
verification: finding.rule === "missing-dep"
|
|
42
|
-
? { evidenceModel: "MANIFEST_PLUS_INDEXED_SOURCE", decision: "ACTION_REQUIRED", manifestDeclaration: { status: "NOT_FOUND", file: scope.file }, indexedSourceImports: { status: "FOUND", count: finding.evidence?.length || 1, files: (finding.evidence || []).map((item) => item.file) }, mapping: "longest go.mod module prefix" }
|
|
43
|
-
: { evidenceModel: "MANIFEST_PLUS_INDEXED_SOURCE", decision: "REVIEW_REQUIRED", manifestDeclaration: { status: "FOUND", file: scope.file }, indexedSourceImports: { status: "ZERO_FOUND", completeness: "COMPLETE_FOR_GRAPH_SCOPE", count: 0, files: [] }, mapping: "longest go.mod module prefix" },
|
|
44
|
-
})));
|
|
45
|
-
for (const name of result.declared) declared.add(`${scope.root || "."}:${name}`);
|
|
46
|
-
}
|
|
47
|
-
const present = manifests.length > 0;
|
|
48
|
-
return {
|
|
49
|
-
present,
|
|
50
|
-
status: present ? "CHECKED" : "NOT_PRESENT",
|
|
51
|
-
completeness: present ? (issues.length ? "PARTIAL" : "COMPLETE") : "NOT_APPLICABLE",
|
|
52
|
-
manifests,
|
|
53
|
-
declared,
|
|
54
|
-
findings,
|
|
55
|
-
reasons: issues,
|
|
56
|
-
reason: !present
|
|
57
|
-
? "No go.mod was discovered."
|
|
58
|
-
: issues.length
|
|
59
|
-
? `Go imports and requirements were checked across ${manifests.length} module(s), but ${issues.length} module descriptor(s) were incomplete.`
|
|
60
|
-
: `Every discovered go.mod scope (${manifests.length}) was compared with indexed Go imports, including direct/indirect requirements and replace directives.`,
|
|
61
|
-
};
|
|
62
|
-
}
|