@wrongstack/plugins 0.293.0 → 0.295.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/README.md +7 -71
- package/dist/accessibility-auditor/index.d.ts +1 -1
- package/dist/accessibility-auditor/index.d.ts.map +1 -1
- package/dist/accessibility-auditor.js +50 -33
- package/dist/agent-handoff/index.d.ts +1 -1
- package/dist/agent-handoff/index.d.ts.map +1 -1
- package/dist/agent-handoff.js +30 -1
- package/dist/api-compatibility-gate/index.d.ts +1 -1
- package/dist/api-compatibility-gate/index.d.ts.map +1 -1
- package/dist/api-compatibility-gate.js +21 -16
- package/dist/audit/index.d.ts +386 -0
- package/dist/audit/index.d.ts.map +1 -0
- package/dist/audit.d.ts +1 -0
- package/dist/audit.js +454 -0
- package/dist/auto-doc/index.d.ts +1 -1
- package/dist/auto-doc/index.d.ts.map +1 -1
- package/dist/auto-doc.js +3 -1
- package/dist/auto-escalate/index.d.ts +1 -1
- package/dist/auto-escalate/index.d.ts.map +1 -1
- package/dist/auto-i18n-extractor/index.d.ts +1 -1
- package/dist/auto-i18n-extractor/index.d.ts.map +1 -1
- package/dist/auto-i18n-extractor.js +7 -1
- package/dist/branch-guard/index.d.ts +1 -1
- package/dist/branch-guard/index.d.ts.map +1 -1
- package/dist/branch-guard.js +111 -1
- package/dist/catalog.d.ts +5 -30
- package/dist/catalog.d.ts.map +1 -1
- package/dist/changelog-writer/index.d.ts +1 -1
- package/dist/changelog-writer/index.d.ts.map +1 -1
- package/dist/changelog-writer.js +137 -3
- package/dist/checkpoint/index.d.ts +3 -1
- package/dist/checkpoint/index.d.ts.map +1 -1
- package/dist/checkpoint.js +41 -16
- package/dist/code-metrics/index.d.ts +1 -1
- package/dist/code-metrics/index.d.ts.map +1 -1
- package/dist/code-metrics.js +34 -29
- package/dist/commit-validator/index.d.ts +1 -1
- package/dist/commit-validator/index.d.ts.map +1 -1
- package/dist/commit-validator.js +17 -7
- package/dist/config-validator/index.d.ts +1 -1
- package/dist/config-validator/index.d.ts.map +1 -1
- package/dist/context-pins/index.d.ts +1 -1
- package/dist/context-pins/index.d.ts.map +1 -1
- package/dist/context-pins.js +6 -5
- package/dist/cost-tracker/index.d.ts +1 -1
- package/dist/cost-tracker/index.d.ts.map +1 -1
- package/dist/cost-tracker.js +109 -2
- package/dist/cron/index.d.ts +1 -1
- package/dist/cron/index.d.ts.map +1 -1
- package/dist/cron.js +1 -0
- package/dist/dead-code-detector/index.d.ts +1 -1
- package/dist/dead-code-detector/index.d.ts.map +1 -1
- package/dist/dead-code-detector.js +31 -18
- package/dist/dep-guard/index.d.ts +1 -1
- package/dist/dep-guard/index.d.ts.map +1 -1
- package/dist/dependency-vulnerability-gate/index.d.ts +1 -1
- package/dist/dependency-vulnerability-gate/index.d.ts.map +1 -1
- package/dist/dependency-vulnerability-gate.js +67 -20
- package/dist/diff-summary/index.d.ts +1 -1
- package/dist/diff-summary/index.d.ts.map +1 -1
- package/dist/diff-summary.js +128 -30
- package/dist/doc-sync-guard/index.d.ts +1 -1
- package/dist/doc-sync-guard/index.d.ts.map +1 -1
- package/dist/doc-sync-guard.js +7 -1
- package/dist/duplicate-code-detector/index.d.ts +26 -1
- package/dist/duplicate-code-detector/index.d.ts.map +1 -1
- package/dist/duplicate-code-detector.js +280 -69
- package/dist/error-lens/index.d.ts +1 -1
- package/dist/error-lens/index.d.ts.map +1 -1
- package/dist/factories/index.d.ts +4 -0
- package/dist/factories/index.d.ts.map +1 -0
- package/dist/factories.d.ts +1 -0
- package/dist/factories.js +74 -0
- package/dist/feature-flag-tracker/index.d.ts +1 -1
- package/dist/feature-flag-tracker/index.d.ts.map +1 -1
- package/dist/feature-flag-tracker.js +59 -33
- package/dist/file-watcher/index.d.ts +1 -1
- package/dist/file-watcher/index.d.ts.map +1 -1
- package/dist/file-watcher.js +6 -7
- package/dist/format-on-save/index.d.ts +3 -1
- package/dist/format-on-save/index.d.ts.map +1 -1
- package/dist/format-on-save.js +289 -59
- package/dist/generated-plugin-exports.d.ts +65 -0
- package/dist/generated-plugin-exports.d.ts.map +1 -0
- package/dist/git-autocommit/index.d.ts +1 -1
- package/dist/git-autocommit/index.d.ts.map +1 -1
- package/dist/import-organizer/index.d.ts +1 -1
- package/dist/import-organizer/index.d.ts.map +1 -1
- package/dist/import-organizer.js +141 -20
- package/dist/index.d.ts +4 -183
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18298 -17279
- package/dist/injection-shield/index.d.ts +1 -1
- package/dist/injection-shield/index.d.ts.map +1 -1
- package/dist/interface-contract-guard/index.d.ts +1 -1
- package/dist/interface-contract-guard/index.d.ts.map +1 -1
- package/dist/interface-contract-guard.js +87 -53
- package/dist/knowledge-graph/index.d.ts +1 -1
- package/dist/knowledge-graph/index.d.ts.map +1 -1
- package/dist/knowledge-graph.js +7 -6
- package/dist/license-audit-gate/index.d.ts +1 -1
- package/dist/license-audit-gate/index.d.ts.map +1 -1
- package/dist/lint-gate/index.d.ts +1 -1
- package/dist/lint-gate/index.d.ts.map +1 -1
- package/dist/lint-gate.js +112 -1
- package/dist/llm-cache/index.d.ts +5 -1
- package/dist/llm-cache/index.d.ts.map +1 -1
- package/dist/llm-cache.js +16 -8
- package/dist/loop-breaker/index.d.ts +1 -1
- package/dist/loop-breaker/index.d.ts.map +1 -1
- package/dist/loop-breaker.js +25 -9
- package/dist/manifest/index.d.ts +16 -0
- package/dist/manifest/index.d.ts.map +1 -0
- package/dist/manifest.d.ts +1 -0
- package/dist/manifest.js +152 -0
- package/dist/migration-planner/index.d.ts +1 -1
- package/dist/migration-planner/index.d.ts.map +1 -1
- package/dist/migration-planner.js +5 -1
- package/dist/model-router/index.d.ts +1 -1
- package/dist/model-router/index.d.ts.map +1 -1
- package/dist/notify-hub/index.d.ts +1 -1
- package/dist/notify-hub/index.d.ts.map +1 -1
- package/dist/notify-hub/webhook-channel.d.ts +1 -1
- package/dist/notify-hub/webhook-channel.d.ts.map +1 -1
- package/dist/notify-hub.js +41 -4
- package/dist/path-guard/index.d.ts +1 -1
- package/dist/path-guard/index.d.ts.map +1 -1
- package/dist/performance-regression-gate/index.d.ts +1 -1
- package/dist/performance-regression-gate/index.d.ts.map +1 -1
- package/dist/plugin-stack-observer/index.d.ts +1 -1
- package/dist/plugin-stack-observer/index.d.ts.map +1 -1
- package/dist/pr-drafter/index.d.ts +1 -1
- package/dist/pr-drafter/index.d.ts.map +1 -1
- package/dist/pr-drafter.js +28 -29
- package/dist/process-guard/index.d.ts +1 -1
- package/dist/process-guard/index.d.ts.map +1 -1
- package/dist/prompt-firewall/index.d.ts +1 -1
- package/dist/prompt-firewall/index.d.ts.map +1 -1
- package/dist/prompt-firewall.js +140 -11
- package/dist/refactor-suggester/index.d.ts +1 -1
- package/dist/refactor-suggester/index.d.ts.map +1 -1
- package/dist/refactor-suggester.js +148 -33
- package/dist/release-notes-generator/index.d.ts +1 -1
- package/dist/release-notes-generator/index.d.ts.map +1 -1
- package/dist/release-notes-generator.js +29 -22
- package/dist/runtime/bounded-map.d.ts +86 -0
- package/dist/runtime/bounded-map.d.ts.map +1 -0
- package/dist/runtime/credential-patterns.d.ts +42 -0
- package/dist/runtime/credential-patterns.d.ts.map +1 -0
- package/dist/runtime/index.d.ts +18 -0
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/llm.d.ts +1 -1
- package/dist/runtime/llm.d.ts.map +1 -1
- package/dist/runtime/local-bin.d.ts +120 -0
- package/dist/runtime/local-bin.d.ts.map +1 -0
- package/dist/runtime/safe-json.d.ts +25 -0
- package/dist/runtime/safe-json.d.ts.map +1 -0
- package/dist/runtime.js +332 -22
- package/dist/schema-evolution-guard/index.d.ts +1 -1
- package/dist/schema-evolution-guard/index.d.ts.map +1 -1
- package/dist/schema-evolution-guard.js +7 -1
- package/dist/secret-scanner/index.d.ts +1 -1
- package/dist/secret-scanner/index.d.ts.map +1 -1
- package/dist/secret-scanner.js +181 -48
- package/dist/security-hotspot-scanner/index.d.ts +1 -1
- package/dist/security-hotspot-scanner/index.d.ts.map +1 -1
- package/dist/security-hotspot-scanner.js +158 -20
- package/dist/semantic-search-indexer/index.d.ts +1 -1
- package/dist/semantic-search-indexer/index.d.ts.map +1 -1
- package/dist/semantic-search-indexer.js +7 -1
- package/dist/semver-bump/index.d.ts +1 -1
- package/dist/semver-bump/index.d.ts.map +1 -1
- package/dist/semver-bump.js +97 -43
- package/dist/session-recap/index.d.ts +1 -1
- package/dist/session-recap/index.d.ts.map +1 -1
- package/dist/shell-check/index.d.ts +1 -1
- package/dist/shell-check/index.d.ts.map +1 -1
- package/dist/smart-rename/index.d.ts +1 -1
- package/dist/smart-rename/index.d.ts.map +1 -1
- package/dist/spec-linker/index.d.ts +1 -1
- package/dist/spec-linker/index.d.ts.map +1 -1
- package/dist/spec-linker.js +222 -20772
- package/dist/template-engine/index.d.ts +1 -1
- package/dist/template-engine/index.d.ts.map +1 -1
- package/dist/template-engine.js +34 -8
- package/dist/test-coverage-gate/index.d.ts +1 -1
- package/dist/test-coverage-gate/index.d.ts.map +1 -1
- package/dist/test-coverage-gate.js +34 -14
- package/dist/test-flake-detector/index.d.ts +1 -1
- package/dist/test-flake-detector/index.d.ts.map +1 -1
- package/dist/test-flake-detector.js +31 -20
- package/dist/test-generator/index.d.ts +1 -1
- package/dist/test-generator/index.d.ts.map +1 -1
- package/dist/test-runner-gate/index.d.ts +1 -1
- package/dist/test-runner-gate/index.d.ts.map +1 -1
- package/dist/test-runner-gate.js +132 -9
- package/dist/todo-listener/index.d.ts +1 -32
- package/dist/todo-listener/index.d.ts.map +1 -1
- package/dist/todo-listener.js +30 -1
- package/dist/todo-tracker/index.d.ts +1 -1
- package/dist/todo-tracker/index.d.ts.map +1 -1
- package/dist/todo-tracker.js +3 -4
- package/dist/token-budget/index.d.ts +1 -1
- package/dist/token-budget/index.d.ts.map +1 -1
- package/dist/token-throttle/index.d.ts +1 -1
- package/dist/token-throttle/index.d.ts.map +1 -1
- package/dist/token-throttle.js +17 -2
- package/dist/type-gate/index.d.ts +1 -1
- package/dist/type-gate/index.d.ts.map +1 -1
- package/dist/type-gate.js +106 -19
- package/package.json +80 -68
package/dist/prompt-firewall.js
CHANGED
|
@@ -1,23 +1,152 @@
|
|
|
1
|
+
// src/runtime/credential-patterns.ts
|
|
2
|
+
var CREDENTIAL_PATTERNS = [
|
|
3
|
+
// LLM provider keys
|
|
4
|
+
{
|
|
5
|
+
type: "anthropic_key",
|
|
6
|
+
regex: /(?<![A-Za-z0-9])sk-ant-api\d+-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9])/g
|
|
7
|
+
},
|
|
8
|
+
{ type: "openai_key", regex: /(?<![A-Za-z0-9])sk-(?!ant)(?:proj-)?[A-Za-z0-9_-]{20,}(?![A-Za-z0-9])/g },
|
|
9
|
+
// GitHub. `ghp_` is only the personal-access-token prefix — the OAuth
|
|
10
|
+
// (`gho_`), user-to-server (`ghu_`), server-to-server (`ghs_`) and
|
|
11
|
+
// refresh (`ghr_`) tokens grant the same or broader access and were
|
|
12
|
+
// previously not detected at all.
|
|
13
|
+
{ type: "github_pat", regex: /(?<![A-Za-z0-9])ghp_[A-Za-z0-9]{36,}(?![A-Za-z0-9])/g },
|
|
14
|
+
{
|
|
15
|
+
type: "github_oauth_token",
|
|
16
|
+
regex: /(?<![A-Za-z0-9])gh[ousr]_[A-Za-z0-9]{36,}(?![A-Za-z0-9])/g
|
|
17
|
+
},
|
|
18
|
+
{ type: "github_pat_v2", regex: /(?<![A-Za-z0-9])github_pat_[A-Za-z0-9_]{50,}(?![A-Za-z0-9])/g },
|
|
19
|
+
// GitLab
|
|
20
|
+
{ type: "gitlab_pat", regex: /(?<![A-Za-z0-9])glpat-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g },
|
|
21
|
+
{
|
|
22
|
+
type: "gitlab_runner_token",
|
|
23
|
+
regex: /(?<![A-Za-z0-9])glrt-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g
|
|
24
|
+
},
|
|
25
|
+
// npm — a leaked publish token is a supply-chain compromise.
|
|
26
|
+
{ type: "npm_token", regex: /(?<![A-Za-z0-9])npm_[A-Za-z0-9]{36}(?![A-Za-z0-9])/g },
|
|
27
|
+
// AWS
|
|
28
|
+
{ type: "aws_access_key", regex: /(?<![A-Za-z0-9])AKIA[0-9A-Z]{16}(?![A-Za-z0-9])/g },
|
|
29
|
+
// GCP
|
|
30
|
+
{ type: "gcp_key", regex: /(?<![A-Za-z0-9])AIza[0-9A-Za-z_-]{35}(?![A-Za-z0-9])/g },
|
|
31
|
+
// Slack. `xoxe` (token-rotation) and `xapp` (app-level) were missing;
|
|
32
|
+
// both are as sensitive as the bot/user tokens already covered.
|
|
33
|
+
{
|
|
34
|
+
type: "slack_token",
|
|
35
|
+
regex: /(?<![A-Za-z0-9-])xox[abposer]-[A-Za-z0-9-]{10,}(?![A-Za-z0-9-])/g
|
|
36
|
+
},
|
|
37
|
+
{ type: "slack_app_token", regex: /(?<![A-Za-z0-9-])xapp-\d-[A-Za-z0-9-]{10,}(?![A-Za-z0-9-])/g },
|
|
38
|
+
{
|
|
39
|
+
type: "slack_webhook",
|
|
40
|
+
regex: /https:\/\/hooks\.slack\.com\/services\/T[A-Za-z0-9_-]+\/B[A-Za-z0-9_-]+\/[A-Za-z0-9]{16,}/g
|
|
41
|
+
},
|
|
42
|
+
// Stripe
|
|
43
|
+
{
|
|
44
|
+
type: "stripe_key",
|
|
45
|
+
regex: /(?<![A-Za-z0-9])sk_(?:live|test)_[A-Za-z0-9]{24,}(?![A-Za-z0-9])/g
|
|
46
|
+
},
|
|
47
|
+
// Twilio
|
|
48
|
+
{ type: "twilio_sid", regex: /(?<![A-Za-z0-9])AC[a-f0-9]{32}(?![A-Za-z0-9])/g },
|
|
49
|
+
// Telegram
|
|
50
|
+
{
|
|
51
|
+
type: "telegram_bot_token",
|
|
52
|
+
regex: /(?:(?<![A-Za-z0-9_])|(?<=(?:^|[^A-Za-z0-9_])bot))\d+:[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g
|
|
53
|
+
},
|
|
54
|
+
// JWT
|
|
55
|
+
{
|
|
56
|
+
type: "jwt",
|
|
57
|
+
regex: /(?<![A-Za-z0-9/+=])eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}(?![A-Za-z0-9/+=])/g
|
|
58
|
+
},
|
|
59
|
+
// Private keys
|
|
60
|
+
{
|
|
61
|
+
type: "private_key",
|
|
62
|
+
regex: /(?:^|\n)(?:-----BEGIN (?:RSA|EC|OPENSSH|DSA)? ?PRIVATE KEY-----[\s\S]*?-----END (?:RSA|EC|OPENSSH|DSA)? ?PRIVATE KEY-----|-----BEGIN PGP PRIVATE KEY BLOCK-----[\s\S]*?-----END PGP PRIVATE KEY BLOCK-----)(?!\S)/g
|
|
63
|
+
},
|
|
64
|
+
// AI/ML provider tokens
|
|
65
|
+
{ type: "huggingface_token", regex: /(?<![A-Za-z0-9])hf_[A-Za-z0-9]{34}(?![A-Za-z0-9])/g },
|
|
66
|
+
{ type: "replicate_token", regex: /(?<![A-Za-z0-9])r8_[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g },
|
|
67
|
+
{ type: "perplexity_key", regex: /(?<![A-Za-z0-9])pplx-[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g },
|
|
68
|
+
{ type: "groq_key", regex: /(?<![A-Za-z0-9])gsk_[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g },
|
|
69
|
+
// SaaS / infrastructure tokens — each grants API access on the user's
|
|
70
|
+
// account, and each was previously invisible to this gate.
|
|
71
|
+
{ type: "sendgrid_key", regex: /(?<![A-Za-z0-9])SG\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g },
|
|
72
|
+
{ type: "digitalocean_token", regex: /(?<![A-Za-z0-9])dop_v1_[a-f0-9]{64}(?![A-Za-z0-9])/g },
|
|
73
|
+
{ type: "doppler_token", regex: /(?<![A-Za-z0-9])dp\.(?:pt|st|sa|scim|audit)\.[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g },
|
|
74
|
+
{ type: "shopify_token", regex: /(?<![A-Za-z0-9])shp(?:at|ca|pa|ss)_[a-fA-F0-9]{32}(?![A-Za-z0-9])/g },
|
|
75
|
+
{ type: "docker_pat", regex: /(?<![A-Za-z0-9])dckr_pat_[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g },
|
|
76
|
+
{ type: "linear_key", regex: /(?<![A-Za-z0-9])lin_api_[A-Za-z0-9]{40,}(?![A-Za-z0-9])/g },
|
|
77
|
+
{ type: "atlassian_token", regex: /(?<![A-Za-z0-9])ATATT3[A-Za-z0-9_\-=]{40,}(?![A-Za-z0-9_\-=])/g },
|
|
78
|
+
{ type: "square_token", regex: /(?<![A-Za-z0-9])(?:sq0(?:atp|csp)-|EAAA)[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g },
|
|
79
|
+
{
|
|
80
|
+
type: "azure_storage_key",
|
|
81
|
+
regex: /AccountKey=[A-Za-z0-9+/]{80,}={0,2}/g
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
type: "google_oauth_client_secret",
|
|
85
|
+
regex: /(?<![A-Za-z0-9_-])GOCSPX-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g
|
|
86
|
+
},
|
|
87
|
+
// Bearer tokens
|
|
88
|
+
{
|
|
89
|
+
type: "bearer_token",
|
|
90
|
+
regex: /(?<![A-Za-z0-9_.~+/-])Bearer\s+[A-Za-z0-9._~+/-]{12,512}=*(?![A-Za-z0-9._~+/-])/g
|
|
91
|
+
},
|
|
92
|
+
// Database URIs. Require password-bearing user-info; credential-free values stay scannable.
|
|
93
|
+
{ type: "mongodb_uri", regex: /mongodb(?:\+srv)?:\/\/[^\s:/@"'`]*:[^\s/@"'`]+@[^\s"'`]+/g },
|
|
94
|
+
{
|
|
95
|
+
type: "postgres_uri",
|
|
96
|
+
// Query parsers decode percent-encoded parameter names. Recognize each
|
|
97
|
+
// encoded character in `password` so mixed forms such as `pass%77ord`
|
|
98
|
+
// cannot bypass detection while keeping the scan strictly bounded.
|
|
99
|
+
regex: /postgres(?:ql)?:\/\/(?:[^\s:/@"'`]*:[^\s/@"'`]+@[^\s"'`]+|[^&\s?"'`#]{1,2048}\?(?:(?!(?:p|%70)(?:a|%61)(?:s|%73)(?:s|%73)(?:w|%77)(?:o|%6[fF])(?:r|%72)(?:d|%64)=)[^&\s#"'`]{1,256}&){0,32}(?:p|%70)(?:a|%61)(?:s|%73)(?:s|%73)(?:w|%77)(?:o|%6[fF])(?:r|%72)(?:d|%64)=[^&\s#"'`]{1,4096})/g
|
|
100
|
+
},
|
|
101
|
+
{ type: "mysql_uri", regex: /mysql:\/\/[^\s:/@"'`]*:[^\s/@"'`]+@[^\s"'`]+/g },
|
|
102
|
+
{ type: "redis_uri", regex: /redis:\/\/[^\s:/@"'`]*:[^\s/@"'`]+@[^\s"'`]+/g }
|
|
103
|
+
];
|
|
104
|
+
function cloneCredentialPatterns() {
|
|
105
|
+
return CREDENTIAL_PATTERNS.map((p) => ({
|
|
106
|
+
type: p.type,
|
|
107
|
+
regex: new RegExp(p.regex.source, p.regex.flags)
|
|
108
|
+
}));
|
|
109
|
+
}
|
|
110
|
+
|
|
1
111
|
// src/prompt-firewall/index.ts
|
|
2
|
-
var
|
|
3
|
-
|
|
112
|
+
var KIND_ALIASES = {
|
|
113
|
+
aws_access_key: "aws-access-key",
|
|
114
|
+
private_key: "private-key-block",
|
|
115
|
+
github_pat: "github-token",
|
|
116
|
+
github_oauth_token: "github-token",
|
|
117
|
+
openai_key: "openai-key",
|
|
118
|
+
anthropic_key: "anthropic-key",
|
|
119
|
+
slack_token: "slack-token",
|
|
120
|
+
gcp_key: "google-api-key",
|
|
121
|
+
bearer_token: "bearer-token"
|
|
122
|
+
};
|
|
123
|
+
var EXTRA_PATTERNS = [
|
|
4
124
|
{
|
|
125
|
+
// AWS secret access keys have no prefix — only a 40-char base64-ish
|
|
126
|
+
// shape — so they need a nearby `aws`/`secret` mention to be worth
|
|
127
|
+
// acting on.
|
|
128
|
+
//
|
|
129
|
+
// The previous spelling required the word "aws" to appear AFTER the
|
|
130
|
+
// key and wrapped the run in `\b`. Both are wrong: the real layout is
|
|
131
|
+
// `AWS_SECRET_ACCESS_KEY=<key>` (context first), and a `\b` cannot
|
|
132
|
+
// hold next to the `+` or `/` that base64 keys routinely end with. The
|
|
133
|
+
// pattern therefore never fired on a real key. Anchor on the context
|
|
134
|
+
// token instead and let the key follow it.
|
|
5
135
|
kind: "aws-secret-key",
|
|
6
|
-
re:
|
|
136
|
+
re: /(?:aws|amazon)[A-Za-z0-9_-]*(?:secret|key)[A-Za-z0-9_-]*\s*[:=]\s*['"]?([A-Za-z0-9/+]{40})(?![A-Za-z0-9/+])/gi
|
|
7
137
|
},
|
|
8
|
-
{ kind: "private-key-block", re: /-----BEGIN (?:RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----/g },
|
|
9
|
-
{ kind: "github-token", re: /\bgh[pousr]_[A-Za-z0-9]{36,}\b/g },
|
|
10
|
-
{ kind: "openai-key", re: /\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}\b/g },
|
|
11
|
-
{ kind: "anthropic-key", re: /\bsk-ant-[A-Za-z0-9_-]{20,}\b/g },
|
|
12
|
-
{ kind: "slack-token", re: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g },
|
|
13
|
-
{ kind: "google-api-key", re: /\bAIza[0-9A-Za-z_-]{35}\b/g },
|
|
14
|
-
{ kind: "jwt", re: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g },
|
|
15
|
-
{ kind: "bearer-token", re: /\bBearer\s+[A-Za-z0-9._-]{20,}\b/g },
|
|
16
138
|
{
|
|
17
139
|
kind: "generic-secret-assignment",
|
|
18
140
|
re: /\b(?:api[_-]?key|secret|password|passwd|token)\s*[:=]\s*['"]?[A-Za-z0-9._/+-]{12,}['"]?/gi
|
|
19
141
|
}
|
|
20
142
|
];
|
|
143
|
+
var PATTERNS = [
|
|
144
|
+
...cloneCredentialPatterns().map((p) => ({
|
|
145
|
+
kind: KIND_ALIASES[p.type] ?? p.type,
|
|
146
|
+
re: p.regex
|
|
147
|
+
})),
|
|
148
|
+
...EXTRA_PATTERNS
|
|
149
|
+
];
|
|
21
150
|
function detectSecrets(text, allow) {
|
|
22
151
|
const counts = /* @__PURE__ */ new Map();
|
|
23
152
|
for (const p of PATTERNS) {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
*
|
|
24
24
|
* @public
|
|
25
25
|
*/
|
|
26
|
-
import type { Plugin } from '@wrongstack/core';
|
|
26
|
+
import type { Plugin } from '@wrongstack/core/types';
|
|
27
27
|
export type SmellType = 'long-function' | 'deep-nesting' | 'many-parameters' | 'magic-number' | 'console-log';
|
|
28
28
|
export interface RefactorSuggestion {
|
|
29
29
|
file: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/refactor-suggester/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/refactor-suggester/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AASrD,MAAM,MAAM,SAAS,GACjB,eAAe,GACf,cAAc,GACd,iBAAiB,GACjB,cAAc,GACd,aAAa,CAAC;AASlB,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AA8PD,QAAA,MAAM,MAAM,EAAE,MAwOb,CAAC;eAEa,MAAM"}
|
|
@@ -1,10 +1,108 @@
|
|
|
1
1
|
// src/refactor-suggester/index.ts
|
|
2
|
-
import {
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2 } from "node:path";
|
|
4
4
|
|
|
5
5
|
// src/runtime/index.ts
|
|
6
|
-
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
7
6
|
import { basename, extname, isAbsolute, relative, resolve } from "node:path";
|
|
7
|
+
|
|
8
|
+
// src/runtime/local-bin.ts
|
|
9
|
+
import { buildWin32CmdShimInvocation, resolveWin32Command } from "@wrongstack/tools/win32";
|
|
10
|
+
|
|
11
|
+
// src/runtime/bounded-map.ts
|
|
12
|
+
var BoundedMap = class {
|
|
13
|
+
map = /* @__PURE__ */ new Map();
|
|
14
|
+
max;
|
|
15
|
+
ttlMs;
|
|
16
|
+
now;
|
|
17
|
+
/** Entries dropped to stay under `max`. Surfaced by plugin health(). */
|
|
18
|
+
evictions = 0;
|
|
19
|
+
constructor(options) {
|
|
20
|
+
const normalizedMax = Math.floor(options.max);
|
|
21
|
+
this.max = Number.isSafeInteger(normalizedMax) ? Math.max(1, normalizedMax) : 1;
|
|
22
|
+
this.ttlMs = options.ttlMs;
|
|
23
|
+
this.now = options.now ?? Date.now;
|
|
24
|
+
}
|
|
25
|
+
expired(entry) {
|
|
26
|
+
return this.ttlMs !== void 0 && this.now() - entry.storedAt > this.ttlMs;
|
|
27
|
+
}
|
|
28
|
+
get(key) {
|
|
29
|
+
const entry = this.map.get(key);
|
|
30
|
+
if (entry === void 0) return void 0;
|
|
31
|
+
if (this.expired(entry)) {
|
|
32
|
+
this.map.delete(key);
|
|
33
|
+
return void 0;
|
|
34
|
+
}
|
|
35
|
+
this.map.delete(key);
|
|
36
|
+
this.map.set(key, entry);
|
|
37
|
+
return entry.value;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Read without promoting the key to most-recently-used. Use for
|
|
41
|
+
* diagnostics that must not perturb the eviction order.
|
|
42
|
+
*/
|
|
43
|
+
peek(key) {
|
|
44
|
+
const entry = this.map.get(key);
|
|
45
|
+
if (entry === void 0 || this.expired(entry)) return void 0;
|
|
46
|
+
return entry.value;
|
|
47
|
+
}
|
|
48
|
+
has(key) {
|
|
49
|
+
const entry = this.map.get(key);
|
|
50
|
+
if (entry === void 0) return false;
|
|
51
|
+
if (this.expired(entry)) {
|
|
52
|
+
this.map.delete(key);
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
set(key, value) {
|
|
58
|
+
this.map.delete(key);
|
|
59
|
+
this.map.set(key, { value, storedAt: this.now() });
|
|
60
|
+
while (this.map.size > this.max) {
|
|
61
|
+
const coldest = this.map.keys().next().value;
|
|
62
|
+
if (coldest === void 0) break;
|
|
63
|
+
this.map.delete(coldest);
|
|
64
|
+
this.evictions += 1;
|
|
65
|
+
}
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
delete(key) {
|
|
69
|
+
return this.map.delete(key);
|
|
70
|
+
}
|
|
71
|
+
clear() {
|
|
72
|
+
this.map.clear();
|
|
73
|
+
this.evictions = 0;
|
|
74
|
+
}
|
|
75
|
+
get size() {
|
|
76
|
+
return this.map.size;
|
|
77
|
+
}
|
|
78
|
+
/** How many entries have been dropped to respect `max`, since the last clear. */
|
|
79
|
+
get evictionCount() {
|
|
80
|
+
return this.evictions;
|
|
81
|
+
}
|
|
82
|
+
/** Drop every expired entry. Cheap enough to call from a status tool. */
|
|
83
|
+
prune() {
|
|
84
|
+
if (this.ttlMs === void 0) return 0;
|
|
85
|
+
let removed = 0;
|
|
86
|
+
for (const [key, entry] of this.map) {
|
|
87
|
+
if (this.expired(entry)) {
|
|
88
|
+
this.map.delete(key);
|
|
89
|
+
removed += 1;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return removed;
|
|
93
|
+
}
|
|
94
|
+
/** Live (non-expired) entries, coldest first. */
|
|
95
|
+
*entries() {
|
|
96
|
+
for (const [key, entry] of this.map) {
|
|
97
|
+
if (!this.expired(entry)) yield [key, entry.value];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
[Symbol.iterator]() {
|
|
101
|
+
return this.entries();
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// src/runtime/index.ts
|
|
8
106
|
var MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
9
107
|
function hasLeadingDash(arg) {
|
|
10
108
|
return arg.length > 0 && arg.startsWith("-");
|
|
@@ -17,44 +115,45 @@ function withinProjectPath(projectRoot, candidate) {
|
|
|
17
115
|
return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
|
|
18
116
|
}
|
|
19
117
|
function withinProject(p) {
|
|
20
|
-
|
|
118
|
+
const cwd = process.cwd();
|
|
119
|
+
return withinProjectPath(cwd, p) || relative(cwd, p) === ".";
|
|
21
120
|
}
|
|
22
121
|
var DEFAULT_EXCLUDE_DIRS = ["node_modules", "dist", ".git", "coverage"];
|
|
23
|
-
function
|
|
122
|
+
async function collectSourceFilesAsync(root, opts) {
|
|
123
|
+
const { readdir, stat } = await import("node:fs/promises");
|
|
24
124
|
const files = [];
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
125
|
+
try {
|
|
126
|
+
const s = await stat(root);
|
|
127
|
+
if (s.isFile()) {
|
|
128
|
+
if (matchesExtension(root, opts.extensions)) files.push(root);
|
|
129
|
+
return files;
|
|
130
|
+
}
|
|
131
|
+
if (!s.isDirectory()) return files;
|
|
132
|
+
} catch {
|
|
29
133
|
return files;
|
|
30
134
|
}
|
|
31
|
-
if (!s.isDirectory()) return files;
|
|
32
135
|
const exclude = opts.excludeDirs ?? DEFAULT_EXCLUDE_DIRS;
|
|
33
|
-
|
|
136
|
+
const excludeSet = new Set(exclude);
|
|
137
|
+
async function walk(dir, depth) {
|
|
34
138
|
if (opts.maxDepth !== void 0 && depth > opts.maxDepth) return;
|
|
35
139
|
let entries;
|
|
36
140
|
try {
|
|
37
|
-
entries =
|
|
141
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
38
142
|
} catch {
|
|
39
143
|
return;
|
|
40
144
|
}
|
|
145
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
41
146
|
for (const entry of entries) {
|
|
42
|
-
if (
|
|
43
|
-
const full = resolve(dir, entry);
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
} catch {
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
if (st.isDirectory()) {
|
|
51
|
-
walk(full, depth + 1);
|
|
52
|
-
} else if (st.isFile() && matchesExtension(full, opts.extensions)) {
|
|
147
|
+
if (excludeSet.has(entry.name)) continue;
|
|
148
|
+
const full = resolve(dir, entry.name);
|
|
149
|
+
if (entry.isDirectory()) {
|
|
150
|
+
await walk(full, depth + 1);
|
|
151
|
+
} else if (entry.isFile() && matchesExtension(full, opts.extensions)) {
|
|
53
152
|
files.push(full);
|
|
54
153
|
}
|
|
55
154
|
}
|
|
56
155
|
}
|
|
57
|
-
walk(root, 0);
|
|
156
|
+
await walk(root, 0);
|
|
58
157
|
return files;
|
|
59
158
|
}
|
|
60
159
|
function matchesExtension(p, exts) {
|
|
@@ -63,6 +162,7 @@ function matchesExtension(p, exts) {
|
|
|
63
162
|
|
|
64
163
|
// src/refactor-suggester/index.ts
|
|
65
164
|
var API_VERSION = "^0.1.10";
|
|
165
|
+
var HOOK_WARNING_COOLDOWN_MS = 6e4;
|
|
66
166
|
var state = {
|
|
67
167
|
scanCount: 0,
|
|
68
168
|
suggestionCount: 0,
|
|
@@ -70,7 +170,7 @@ var state = {
|
|
|
70
170
|
warningCount: 0,
|
|
71
171
|
errorCount: 0,
|
|
72
172
|
hookUnregister: null,
|
|
73
|
-
lastHookWarning:
|
|
173
|
+
lastHookWarning: new BoundedMap({ max: 512, ttlMs: HOOK_WARNING_COOLDOWN_MS })
|
|
74
174
|
};
|
|
75
175
|
var DEFAULTS = {
|
|
76
176
|
enabled: false,
|
|
@@ -189,21 +289,32 @@ function detectSmells(filePath, content, rules) {
|
|
|
189
289
|
}
|
|
190
290
|
return suggestions;
|
|
191
291
|
}
|
|
192
|
-
function scanPath(rawPath, cfg) {
|
|
292
|
+
async function scanPath(rawPath, cfg) {
|
|
193
293
|
const root = process.cwd();
|
|
194
294
|
const resolved = isAbsolute2(rawPath) ? resolve2(rawPath) : resolve2(root, rawPath);
|
|
195
295
|
const exts = normalizeExtensions(cfg.extensions);
|
|
196
|
-
const files =
|
|
296
|
+
const files = await collectSourceFilesAsync(resolved, { extensions: exts });
|
|
197
297
|
const suggestions = [];
|
|
298
|
+
let scannedFiles = 0;
|
|
299
|
+
let truncated = false;
|
|
198
300
|
for (const p of files) {
|
|
199
301
|
try {
|
|
200
|
-
const content =
|
|
302
|
+
const content = await readFile(p, "utf-8");
|
|
303
|
+
scannedFiles += 1;
|
|
201
304
|
suggestions.push(...detectSmells(p, content, cfg.rules));
|
|
202
|
-
if (suggestions.length >= cfg.maxSuggestions)
|
|
305
|
+
if (suggestions.length >= cfg.maxSuggestions) {
|
|
306
|
+
truncated = scannedFiles < files.length;
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
203
309
|
} catch {
|
|
204
310
|
}
|
|
205
311
|
}
|
|
206
|
-
return {
|
|
312
|
+
return {
|
|
313
|
+
suggestions: suggestions.slice(0, cfg.maxSuggestions),
|
|
314
|
+
scannedFiles,
|
|
315
|
+
discoveredFiles: files.length,
|
|
316
|
+
truncated
|
|
317
|
+
};
|
|
207
318
|
}
|
|
208
319
|
var plugin = {
|
|
209
320
|
name: "refactor-suggester",
|
|
@@ -254,7 +365,7 @@ var plugin = {
|
|
|
254
365
|
state.hookUnregister = null;
|
|
255
366
|
}
|
|
256
367
|
const cfg = readConfig(api.config.extensions?.["refactor-suggester"]);
|
|
257
|
-
const hook = (input) => {
|
|
368
|
+
const hook = async (input) => {
|
|
258
369
|
if (!cfg.enabled) return;
|
|
259
370
|
if (input.toolResult?.isError) return;
|
|
260
371
|
const inp = input.toolInput ?? {};
|
|
@@ -266,11 +377,11 @@ var plugin = {
|
|
|
266
377
|
state.hookInvocationCount += 1;
|
|
267
378
|
const now = Date.now();
|
|
268
379
|
const lastWarning = state.lastHookWarning.get(sourcePath);
|
|
269
|
-
if (lastWarning !== void 0 && now - lastWarning <
|
|
380
|
+
if (lastWarning !== void 0 && now - lastWarning < HOOK_WARNING_COOLDOWN_MS) return;
|
|
270
381
|
const resolved = resolve2(process.cwd(), sourcePath);
|
|
271
382
|
let content;
|
|
272
383
|
try {
|
|
273
|
-
content =
|
|
384
|
+
content = await readFile(resolved, "utf-8");
|
|
274
385
|
} catch {
|
|
275
386
|
state.errorCount += 1;
|
|
276
387
|
return;
|
|
@@ -306,7 +417,7 @@ var plugin = {
|
|
|
306
417
|
state.scanCount += 1;
|
|
307
418
|
let result;
|
|
308
419
|
try {
|
|
309
|
-
result = scanPath(rawPath, cfg);
|
|
420
|
+
result = await scanPath(rawPath, cfg);
|
|
310
421
|
} catch (err) {
|
|
311
422
|
state.errorCount += 1;
|
|
312
423
|
return { ok: false, error: String(err) };
|
|
@@ -316,6 +427,10 @@ var plugin = {
|
|
|
316
427
|
ok: true,
|
|
317
428
|
path: relativePath(resolve2(process.cwd(), rawPath)),
|
|
318
429
|
scannedFiles: result.scannedFiles,
|
|
430
|
+
discoveredFiles: result.discoveredFiles,
|
|
431
|
+
// Say so when the cap stopped the walk early: a partial scan
|
|
432
|
+
// that reports few findings must not read as a clean result.
|
|
433
|
+
truncated: result.truncated,
|
|
319
434
|
suggestions: result.suggestions,
|
|
320
435
|
rules: cfg.rules
|
|
321
436
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/release-notes-generator/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/release-notes-generator/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAsPrD,QAAA,MAAM,MAAM,EAAE,MAwLb,CAAC;eAEa,MAAM"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/release-notes-generator/index.ts
|
|
2
|
-
import {
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
3
|
|
|
4
4
|
// src/runtime/llm.ts
|
|
5
5
|
function stripOuterMarkdownFence(text) {
|
|
@@ -88,40 +88,47 @@ function formatCommit(c, includeScope) {
|
|
|
88
88
|
line += ` ${c.description}`;
|
|
89
89
|
return line;
|
|
90
90
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
91
|
+
function runGit(args, signal) {
|
|
92
|
+
return new Promise((resolveOutput, reject) => {
|
|
93
|
+
execFile(
|
|
94
|
+
"git",
|
|
95
|
+
args,
|
|
96
|
+
{
|
|
97
|
+
encoding: "utf8",
|
|
98
|
+
shell: false,
|
|
99
|
+
windowsHide: true,
|
|
100
|
+
timeout: 1e4,
|
|
101
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
102
|
+
signal
|
|
103
|
+
},
|
|
104
|
+
(error, stdout) => error ? reject(error) : resolveOutput(stdout)
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
async function resolveFromRef(defaultFrom, inputFrom, signal) {
|
|
97
109
|
if (inputFrom) return inputFrom;
|
|
98
110
|
if (defaultFrom === "latest-tag") {
|
|
99
111
|
try {
|
|
100
|
-
return
|
|
112
|
+
return (await runGit(["describe", "--tags", "--abbrev=0"], signal)).trim();
|
|
101
113
|
} catch {
|
|
102
114
|
return "";
|
|
103
115
|
}
|
|
104
116
|
}
|
|
105
117
|
return defaultFrom;
|
|
106
118
|
}
|
|
107
|
-
function resolveCommit(ref) {
|
|
108
|
-
const resolved =
|
|
109
|
-
"git",
|
|
110
|
-
["rev-parse", "--verify", "--end-of-options", `${ref}^{commit}`],
|
|
111
|
-
GIT_OPTIONS
|
|
112
|
-
).trim();
|
|
119
|
+
async function resolveCommit(ref, signal) {
|
|
120
|
+
const resolved = (await runGit(["rev-parse", "--verify", "--end-of-options", `${ref}^{commit}`], signal)).trim();
|
|
113
121
|
if (!/^[0-9a-f]{40,64}$/i.test(resolved)) {
|
|
114
122
|
throw new Error(`git returned an invalid commit id for ref ${JSON.stringify(ref)}`);
|
|
115
123
|
}
|
|
116
124
|
return resolved;
|
|
117
125
|
}
|
|
118
|
-
function getCommits(from, to) {
|
|
119
|
-
const toCommit = resolveCommit(to);
|
|
120
|
-
const range = from ? `${resolveCommit(from)}..${toCommit}` : toCommit;
|
|
121
|
-
const output =
|
|
122
|
-
"git",
|
|
126
|
+
async function getCommits(from, to, signal) {
|
|
127
|
+
const toCommit = await resolveCommit(to, signal);
|
|
128
|
+
const range = from ? `${await resolveCommit(from, signal)}..${toCommit}` : toCommit;
|
|
129
|
+
const output = await runGit(
|
|
123
130
|
["log", "--pretty=format:%H%x09%s", "--end-of-options", range],
|
|
124
|
-
|
|
131
|
+
signal
|
|
125
132
|
);
|
|
126
133
|
if (!output.trim()) return [];
|
|
127
134
|
const commits = [];
|
|
@@ -277,11 +284,11 @@ var plugin = {
|
|
|
277
284
|
if (!cfg.enabled) return { ok: false, error: "release-notes-generator is disabled" };
|
|
278
285
|
execOpts?.signal?.throwIfAborted();
|
|
279
286
|
const toRef = typeof input.to === "string" ? input.to : "HEAD";
|
|
280
|
-
const fromRef = resolveFromRef(cfg.defaultFrom, input.from);
|
|
287
|
+
const fromRef = await resolveFromRef(cfg.defaultFrom, input.from, execOpts?.signal);
|
|
281
288
|
state.generateCount += 1;
|
|
282
289
|
let commits;
|
|
283
290
|
try {
|
|
284
|
-
commits = getCommits(fromRef, toRef);
|
|
291
|
+
commits = await getCommits(fromRef, toRef, execOpts?.signal);
|
|
285
292
|
} catch (err) {
|
|
286
293
|
state.errorCount += 1;
|
|
287
294
|
return { ok: false, error: String(err) };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @wrongstack/plugins — bounded key/value cache.
|
|
3
|
+
*
|
|
4
|
+
* Several plugins memoise per-path or per-model work in a module-scope
|
|
5
|
+
* `Map` that is only ever cleared at teardown. In a short test that is
|
|
6
|
+
* invisible; in a long session over a large repository it is a slow leak —
|
|
7
|
+
* one entry per file touched, per branch resolved, per model seen — held
|
|
8
|
+
* for the lifetime of the process.
|
|
9
|
+
*
|
|
10
|
+
* `BoundedMap` is a drop-in replacement for those maps: same `get`/`set`/
|
|
11
|
+
* `delete`/`clear`/`size` surface, plus a hard entry cap with
|
|
12
|
+
* least-recently-used eviction and an optional per-entry TTL. Nothing
|
|
13
|
+
* about the call sites has to change beyond the constructor.
|
|
14
|
+
*
|
|
15
|
+
* Why LRU rather than insertion-order eviction: the access pattern for
|
|
16
|
+
* these caches is "a few hot paths, a long tail of cold ones". Evicting by
|
|
17
|
+
* insertion order throws away the hot entries first, which is exactly
|
|
18
|
+
* backwards; re-reading a hot entry should keep it.
|
|
19
|
+
*/
|
|
20
|
+
export interface BoundedMapOptions {
|
|
21
|
+
/** Hard cap on retained entries. Must be a safe integer >= 1. */
|
|
22
|
+
max: number;
|
|
23
|
+
/**
|
|
24
|
+
* Optional per-entry lifetime in ms. An entry older than this is treated
|
|
25
|
+
* as absent by `get`/`has` and dropped on access. Omit for no expiry.
|
|
26
|
+
*/
|
|
27
|
+
ttlMs?: number | undefined;
|
|
28
|
+
/** Clock injection point — tests override this instead of faking timers. */
|
|
29
|
+
now?: (() => number) | undefined;
|
|
30
|
+
}
|
|
31
|
+
export declare class BoundedMap<K, V> {
|
|
32
|
+
private readonly map;
|
|
33
|
+
private readonly max;
|
|
34
|
+
private readonly ttlMs;
|
|
35
|
+
private readonly now;
|
|
36
|
+
/** Entries dropped to stay under `max`. Surfaced by plugin health(). */
|
|
37
|
+
private evictions;
|
|
38
|
+
constructor(options: BoundedMapOptions);
|
|
39
|
+
private expired;
|
|
40
|
+
get(key: K): V | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Read without promoting the key to most-recently-used. Use for
|
|
43
|
+
* diagnostics that must not perturb the eviction order.
|
|
44
|
+
*/
|
|
45
|
+
peek(key: K): V | undefined;
|
|
46
|
+
has(key: K): boolean;
|
|
47
|
+
set(key: K, value: V): this;
|
|
48
|
+
delete(key: K): boolean;
|
|
49
|
+
clear(): void;
|
|
50
|
+
get size(): number;
|
|
51
|
+
/** How many entries have been dropped to respect `max`, since the last clear. */
|
|
52
|
+
get evictionCount(): number;
|
|
53
|
+
/** Drop every expired entry. Cheap enough to call from a status tool. */
|
|
54
|
+
prune(): number;
|
|
55
|
+
/** Live (non-expired) entries, coldest first. */
|
|
56
|
+
entries(): IterableIterator<[K, V]>;
|
|
57
|
+
[Symbol.iterator](): IterableIterator<[K, V]>;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* A `Set` with the same hard cap and LRU eviction as {@link BoundedMap}.
|
|
61
|
+
*
|
|
62
|
+
* Plugins use module-scope sets to remember "already reported" keys so a
|
|
63
|
+
* finding is surfaced once rather than on every scan. That set has to
|
|
64
|
+
* outlive a single scan, which is why it lives at module scope — but
|
|
65
|
+
* without a bound it grows one entry per distinct finding for the life of
|
|
66
|
+
* the process.
|
|
67
|
+
*
|
|
68
|
+
* Eviction means a very old key may eventually be reported a second time.
|
|
69
|
+
* That is the right trade: re-reporting a finding the user last saw
|
|
70
|
+
* thousands of findings ago is reasonable behaviour, whereas unbounded
|
|
71
|
+
* growth is not.
|
|
72
|
+
*/
|
|
73
|
+
export declare class BoundedSet<T> {
|
|
74
|
+
private readonly inner;
|
|
75
|
+
constructor(options: BoundedMapOptions);
|
|
76
|
+
has(value: T): boolean;
|
|
77
|
+
add(value: T): this;
|
|
78
|
+
delete(value: T): boolean;
|
|
79
|
+
clear(): void;
|
|
80
|
+
get size(): number;
|
|
81
|
+
/** How many entries have been dropped to respect `max`, since the last clear. */
|
|
82
|
+
get evictionCount(): number;
|
|
83
|
+
values(): IterableIterator<T>;
|
|
84
|
+
[Symbol.iterator](): IterableIterator<T>;
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=bounded-map.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bounded-map.d.ts","sourceRoot":"","sources":["../../src/runtime/bounded-map.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,WAAW,iBAAiB;IAChC,iEAAiE;IACjE,GAAG,EAAE,MAAM,CAAC;IACZ;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,4EAA4E;IAC5E,GAAG,CAAC,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC;CAClC;AAOD,qBAAa,UAAU,CAAC,CAAC,EAAE,CAAC;IAC1B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA0B;IAC9C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqB;IAC3C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,wEAAwE;IACxE,OAAO,CAAC,SAAS,CAAK;IAEtB,YAAY,OAAO,EAAE,iBAAiB,EAKrC;IAED,OAAO,CAAC,OAAO;IAIf,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,SAAS,CAazB;IAED;;;OAGG;IACH,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,SAAS,CAI1B;IAED,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO,CAQnB;IAED,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,CAW1B;IAED,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO,CAEtB;IAED,KAAK,IAAI,IAAI,CAGZ;IAED,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,iFAAiF;IACjF,IAAI,aAAa,IAAI,MAAM,CAE1B;IAED,yEAAyE;IACzE,KAAK,IAAI,MAAM,CAUd;IAED,iDAAiD;IAChD,OAAO,IAAI,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAInC;IAED,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,gBAAgB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAE5C;CACF;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,UAAU,CAAC,CAAC;IACvB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAsB;IAE5C,YAAY,OAAO,EAAE,iBAAiB,EAErC;IAED,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,OAAO,CAErB;IAED,GAAG,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI,CAGlB;IAED,MAAM,CAAC,KAAK,EAAE,CAAC,GAAG,OAAO,CAExB;IAED,KAAK,IAAI,IAAI,CAEZ;IAED,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,iFAAiF;IACjF,IAAI,aAAa,IAAI,MAAM,CAE1B;IAEA,MAAM,IAAI,gBAAgB,CAAC,CAAC,CAAC,CAE7B;IAED,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,gBAAgB,CAAC,CAAC,CAAC,CAEvC;CACF"}
|