@wrongstack/core 0.5.3 → 0.5.6
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/{agent-bridge-EiUFe3if.d.ts → agent-bridge-B07AYFBk.d.ts} +1 -1
- package/dist/{compactor-BP4xhKVi.d.ts → compactor-BWhJXxsW.d.ts} +1 -1
- package/dist/{config-BOD_HQBw.d.ts → config-BgM0BIpz.d.ts} +1 -1
- package/dist/{context-PH4DvBZV.d.ts → context-CLZXPPYk.d.ts} +18 -1
- package/dist/coordination/index.d.ts +10 -9
- package/dist/coordination/index.js +269 -15
- package/dist/coordination/index.js.map +1 -1
- package/dist/defaults/index.d.ts +20 -19
- package/dist/defaults/index.js +459 -17
- package/dist/defaults/index.js.map +1 -1
- package/dist/director-state-BmYi3DGA.d.ts +108 -0
- package/dist/{events-oxn-Wkub.d.ts → events-qnDZbrtb.d.ts} +67 -2
- package/dist/execution/index.d.ts +12 -12
- package/dist/extension/index.d.ts +6 -6
- package/dist/{index-CcbWbcpy.d.ts → index-BDnUCRvL.d.ts} +113 -6
- package/dist/{index-a12jc7-r.d.ts → index-DPLJw_ZI.d.ts} +5 -5
- package/dist/index.d.ts +329 -27
- package/dist/index.js +2405 -27
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +6 -6
- package/dist/kernel/index.d.ts +9 -9
- package/dist/kernel/index.js +98 -1
- package/dist/kernel/index.js.map +1 -1
- package/dist/{mcp-servers-uPmBxZ1B.d.ts → mcp-servers-CSMfaBuL.d.ts} +3 -3
- package/dist/models/index.d.ts +2 -2
- package/dist/{multi-agent-D6S4Z7H8.d.ts → multi-agent-Cv8wk47i.d.ts} +2 -2
- package/dist/observability/index.d.ts +2 -2
- package/dist/{path-resolver-CprD5DhS.d.ts → path-resolver-DiCUvEg6.d.ts} +2 -2
- package/dist/{director-state-CVzkjKRZ.d.ts → plan-templates-DaxTCPfk.d.ts} +38 -77
- package/dist/{provider-runner-DGisz_lG.d.ts → provider-runner-3SHqk9zB.d.ts} +3 -3
- package/dist/{retry-policy-DUJ8-Qc_.d.ts → retry-policy-LLUxJmYY.d.ts} +1 -1
- package/dist/sdd/index.d.ts +3 -3
- package/dist/{secret-scrubber-CB11A2P7.d.ts → secret-scrubber-BhJTNr9v.d.ts} +4 -2
- package/dist/{secret-scrubber-EqFa0SyI.d.ts → secret-scrubber-Z_VXuXQT.d.ts} +1 -1
- package/dist/security/index.d.ts +13 -3
- package/dist/security/index.js +34 -1
- package/dist/security/index.js.map +1 -1
- package/dist/{selector-yqyxt-Ii.d.ts → selector-DB2-byKH.d.ts} +1 -1
- package/dist/{session-reader-1tOyoY1s.d.ts → session-reader-4jxsYLZ8.d.ts} +1 -1
- package/dist/storage/index.d.ts +7 -6
- package/dist/storage/index.js +222 -3
- package/dist/storage/index.js.map +1 -1
- package/dist/{system-prompt-BJlzKGO6.d.ts → system-prompt-DI4Dtc1I.d.ts} +1 -1
- package/dist/{tool-executor-B6kRcWeF.d.ts → tool-executor-DSvmOBe6.d.ts} +4 -4
- package/dist/types/index.d.ts +15 -15
- package/dist/types/index.js +9 -1
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +2 -2
- package/dist/{wstack-paths-BGu2INTm.d.ts → wstack-paths-86YPFktR.d.ts} +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,7 +2,9 @@ import * as crypto2 from 'crypto';
|
|
|
2
2
|
import { randomBytes, createCipheriv, createDecipheriv, randomUUID, createHash } from 'crypto';
|
|
3
3
|
import * as fs from 'fs';
|
|
4
4
|
import * as fsp2 from 'fs/promises';
|
|
5
|
+
import { readFile, readdir, writeFile, stat, mkdir } from 'fs/promises';
|
|
5
6
|
import * as path6 from 'path';
|
|
7
|
+
import { join, extname, relative } from 'path';
|
|
6
8
|
import * as os4 from 'os';
|
|
7
9
|
import { EventEmitter } from 'events';
|
|
8
10
|
import { createGunzip } from 'zlib';
|
|
@@ -10,6 +12,13 @@ import { Readable } from 'stream';
|
|
|
10
12
|
import { pipeline } from 'stream/promises';
|
|
11
13
|
import { spawn } from 'child_process';
|
|
12
14
|
|
|
15
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
16
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
17
|
+
}) : x)(function(x) {
|
|
18
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
19
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
20
|
+
});
|
|
21
|
+
|
|
13
22
|
// src/types/errors.ts
|
|
14
23
|
var WrongStackError = class extends Error {
|
|
15
24
|
code;
|
|
@@ -506,6 +515,103 @@ var EventBus = class {
|
|
|
506
515
|
return this.wildcards.length;
|
|
507
516
|
}
|
|
508
517
|
};
|
|
518
|
+
var ScopedEventBus = class extends EventBus {
|
|
519
|
+
// Track registrations by a unique counter key so that EventBus.once()'s
|
|
520
|
+
// internal listener-removal doesn't affect our tracking (once removes the
|
|
521
|
+
// fn from EventBus but we still need to call our unsub during teardown).
|
|
522
|
+
registrations = /* @__PURE__ */ new Map();
|
|
523
|
+
nextKey = 0;
|
|
524
|
+
/**
|
|
525
|
+
* Identical to `EventBus.on` but the listener is tracked so that
|
|
526
|
+
* `teardown()` will remove it automatically.
|
|
527
|
+
*/
|
|
528
|
+
on(event, fn) {
|
|
529
|
+
const key = this.nextKey++;
|
|
530
|
+
const unsub = super.on(event, fn);
|
|
531
|
+
this.registrations.set(key, unsub);
|
|
532
|
+
return () => {
|
|
533
|
+
this.registrations.delete(key);
|
|
534
|
+
unsub();
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Identical to `EventBus.once` but the listener is tracked so that
|
|
539
|
+
* `teardown()` will remove it automatically.
|
|
540
|
+
*
|
|
541
|
+
* Uses EventBus's public API directly to avoid triggering our own `on()`
|
|
542
|
+
* override (which would consume a key slot for the wrapper, then orphan
|
|
543
|
+
* our registration entry under a different key).
|
|
544
|
+
*/
|
|
545
|
+
once(event, fn) {
|
|
546
|
+
const key = this.nextKey++;
|
|
547
|
+
const wrapper = (payload) => {
|
|
548
|
+
EventBus.prototype.off.call(this, event, wrapper);
|
|
549
|
+
fn(payload);
|
|
550
|
+
};
|
|
551
|
+
EventBus.prototype.on.call(this, event, wrapper);
|
|
552
|
+
const unsub = () => {
|
|
553
|
+
this.registrations.delete(key);
|
|
554
|
+
EventBus.prototype.off.call(this, event, wrapper);
|
|
555
|
+
};
|
|
556
|
+
this.registrations.set(key, unsub);
|
|
557
|
+
return unsub;
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Identical to `EventBus.onPattern` but the listener is tracked so that
|
|
561
|
+
* `teardown()` will remove it automatically.
|
|
562
|
+
*/
|
|
563
|
+
onPattern(pattern, fn) {
|
|
564
|
+
const key = this.nextKey++;
|
|
565
|
+
const unsub = super.onPattern(pattern, fn);
|
|
566
|
+
this.registrations.set(key, unsub);
|
|
567
|
+
return () => {
|
|
568
|
+
this.registrations.delete(key);
|
|
569
|
+
unsub();
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Identical to `EventBus.onRegex` but the listener is tracked so that
|
|
574
|
+
* `teardown()` will remove it automatically.
|
|
575
|
+
*/
|
|
576
|
+
onRegex(regex, fn) {
|
|
577
|
+
const key = this.nextKey++;
|
|
578
|
+
const unsub = super.onRegex(regex, fn);
|
|
579
|
+
this.registrations.set(key, unsub);
|
|
580
|
+
return () => {
|
|
581
|
+
this.registrations.delete(key);
|
|
582
|
+
unsub();
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Remove every listener that was registered through this scoped bus.
|
|
587
|
+
* Idempotent — calling it multiple times is safe.
|
|
588
|
+
*
|
|
589
|
+
* Also available as `[Symbol.dispose]` for explicit resource management:
|
|
590
|
+
* ```ts
|
|
591
|
+
* using scope = new ScopedEventBus();
|
|
592
|
+
* scope.on('tool.executed', handler);
|
|
593
|
+
* // automatically teardown()'d when scope exits
|
|
594
|
+
* ```
|
|
595
|
+
*/
|
|
596
|
+
teardown() {
|
|
597
|
+
for (const unsub of this.registrations.values()) {
|
|
598
|
+
try {
|
|
599
|
+
unsub();
|
|
600
|
+
} catch {
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
this.registrations.clear();
|
|
604
|
+
this.clear();
|
|
605
|
+
}
|
|
606
|
+
/** Alias for `teardown()` — enables `using new ScopedEventBus()` in Node ≥ 22. */
|
|
607
|
+
[Symbol.dispose]() {
|
|
608
|
+
this.teardown();
|
|
609
|
+
}
|
|
610
|
+
/** Number of tracked registrations. */
|
|
611
|
+
get scopedListenerCount() {
|
|
612
|
+
return this.registrations.size;
|
|
613
|
+
}
|
|
614
|
+
};
|
|
509
615
|
function makePatternMatcher(pattern) {
|
|
510
616
|
if (pattern === "*") return () => true;
|
|
511
617
|
if (pattern.endsWith(".*")) {
|
|
@@ -1688,7 +1794,15 @@ var PATTERNS = [
|
|
|
1688
1794
|
type: "stripe_key",
|
|
1689
1795
|
regex: /(?<![A-Za-z0-9])sk_(?:live|test)_[A-Za-z0-9]{24,}(?![A-Za-z0-9])/g
|
|
1690
1796
|
},
|
|
1691
|
-
{
|
|
1797
|
+
{
|
|
1798
|
+
type: "twilio_sid",
|
|
1799
|
+
regex: /(?<![A-Za-z0-9])AC[a-f0-9]{32}(?![A-Za-z0-9])/g
|
|
1800
|
+
},
|
|
1801
|
+
{
|
|
1802
|
+
type: "telegram_bot_token",
|
|
1803
|
+
// Telegram tokens are of the form bot<digits>:<alphanum> in URL paths
|
|
1804
|
+
regex: /\/bot\d+:[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])/g
|
|
1805
|
+
},
|
|
1692
1806
|
{
|
|
1693
1807
|
type: "jwt",
|
|
1694
1808
|
// Anchored: look for literal "eyJ" which is unambiguous for JWT header
|
|
@@ -1786,8 +1900,8 @@ async function atomicWrite(targetPath, content, opts = {}) {
|
|
|
1786
1900
|
}
|
|
1787
1901
|
let mode;
|
|
1788
1902
|
try {
|
|
1789
|
-
const
|
|
1790
|
-
mode =
|
|
1903
|
+
const stat8 = await fsp2.stat(targetPath);
|
|
1904
|
+
mode = stat8.mode & 511;
|
|
1791
1905
|
} catch {
|
|
1792
1906
|
mode = opts.mode;
|
|
1793
1907
|
}
|
|
@@ -3827,8 +3941,8 @@ var DefaultSessionStore = class {
|
|
|
3827
3941
|
return JSON.parse(raw);
|
|
3828
3942
|
} catch {
|
|
3829
3943
|
const full = path6.join(this.dir, `${id}.jsonl`);
|
|
3830
|
-
const
|
|
3831
|
-
const summary = await this.summarize(id,
|
|
3944
|
+
const stat8 = await fsp2.stat(full);
|
|
3945
|
+
const summary = await this.summarize(id, stat8.mtime.toISOString());
|
|
3832
3946
|
await fsp2.writeFile(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
|
|
3833
3947
|
console.warn(
|
|
3834
3948
|
`[session-store] Failed to write manifest for "${id}":`,
|
|
@@ -4495,6 +4609,16 @@ function labelOf(scope) {
|
|
|
4495
4609
|
}
|
|
4496
4610
|
|
|
4497
4611
|
// src/storage/config-store.ts
|
|
4612
|
+
function stripEphemeralFields(cfg) {
|
|
4613
|
+
const env = cfg._envSource;
|
|
4614
|
+
if (!env?.size) return cfg;
|
|
4615
|
+
const out = { ...cfg };
|
|
4616
|
+
for (const field of env) {
|
|
4617
|
+
delete out[field];
|
|
4618
|
+
}
|
|
4619
|
+
delete out._envSource;
|
|
4620
|
+
return out;
|
|
4621
|
+
}
|
|
4498
4622
|
var DefaultConfigStore = class {
|
|
4499
4623
|
current;
|
|
4500
4624
|
watchers = /* @__PURE__ */ new Set();
|
|
@@ -4512,7 +4636,8 @@ var DefaultConfigStore = class {
|
|
|
4512
4636
|
return ext ? ext : FROZEN_EMPTY;
|
|
4513
4637
|
}
|
|
4514
4638
|
update(partial) {
|
|
4515
|
-
const
|
|
4639
|
+
const scrubbed = stripEphemeralFields(partial);
|
|
4640
|
+
const next = deepFreeze(structuredClone({ ...this.current, ...scrubbed }));
|
|
4516
4641
|
if (next.version !== 1) {
|
|
4517
4642
|
throw new Error(`ConfigStore.update: version must remain 1, got ${String(next.version)}`);
|
|
4518
4643
|
}
|
|
@@ -4617,15 +4742,19 @@ var BEHAVIOR_DEFAULTS = {
|
|
|
4617
4742
|
var ENV_MAP = {
|
|
4618
4743
|
WRONGSTACK_PROVIDER: (c, v) => {
|
|
4619
4744
|
c.provider = v;
|
|
4745
|
+
(c._envSource ??= /* @__PURE__ */ new Set()).add("provider");
|
|
4620
4746
|
},
|
|
4621
4747
|
WRONGSTACK_MODEL: (c, v) => {
|
|
4622
4748
|
c.model = v;
|
|
4749
|
+
(c._envSource ??= /* @__PURE__ */ new Set()).add("model");
|
|
4623
4750
|
},
|
|
4624
4751
|
WRONGSTACK_API_KEY: (c, v) => {
|
|
4625
4752
|
c.apiKey = v;
|
|
4753
|
+
(c._envSource ??= /* @__PURE__ */ new Set()).add("apiKey");
|
|
4626
4754
|
},
|
|
4627
4755
|
WRONGSTACK_BASE_URL: (c, v) => {
|
|
4628
4756
|
c.baseUrl = v;
|
|
4757
|
+
(c._envSource ??= /* @__PURE__ */ new Set()).add("baseUrl");
|
|
4629
4758
|
},
|
|
4630
4759
|
WRONGSTACK_LOG_LEVEL: (c, v) => {
|
|
4631
4760
|
if (!c.log) c.log = { level: "info" };
|
|
@@ -5219,9 +5348,149 @@ function matchIndex(plan, idOrIndex) {
|
|
|
5219
5348
|
const lower = idOrIndex.toLowerCase();
|
|
5220
5349
|
return plan.items.findIndex((it) => it.title.toLowerCase().includes(lower));
|
|
5221
5350
|
}
|
|
5351
|
+
function deriveTodosFromPlanItem(plan, idOrIndex, subtasks) {
|
|
5352
|
+
const idx = matchIndex(plan, idOrIndex);
|
|
5353
|
+
if (idx === -1) return null;
|
|
5354
|
+
const item = plan.items[idx];
|
|
5355
|
+
if (!item) return null;
|
|
5356
|
+
let updatedPlan = plan;
|
|
5357
|
+
if (item.status !== "done") {
|
|
5358
|
+
updatedPlan = setPlanItemStatus(plan, idOrIndex, "in_progress");
|
|
5359
|
+
}
|
|
5360
|
+
const todos = [];
|
|
5361
|
+
todos.push({
|
|
5362
|
+
id: `todo_${Date.now()}_plan`,
|
|
5363
|
+
content: item.title,
|
|
5364
|
+
status: "in_progress",
|
|
5365
|
+
activeForm: item.title
|
|
5366
|
+
});
|
|
5367
|
+
if (subtasks && subtasks.length > 0) {
|
|
5368
|
+
for (const st of subtasks) {
|
|
5369
|
+
todos.push({
|
|
5370
|
+
id: `todo_${Date.now()}_${randomUUID().slice(0, 6)}`,
|
|
5371
|
+
content: st,
|
|
5372
|
+
status: "pending"
|
|
5373
|
+
});
|
|
5374
|
+
}
|
|
5375
|
+
}
|
|
5376
|
+
return { plan: updatedPlan, todos };
|
|
5377
|
+
}
|
|
5222
5378
|
function attachPlanCheckpoint(_state, _filePath, _sessionId) {
|
|
5223
5379
|
return () => void 0;
|
|
5224
5380
|
}
|
|
5381
|
+
|
|
5382
|
+
// src/storage/plan-templates.ts
|
|
5383
|
+
var templates = {
|
|
5384
|
+
"new-feature": {
|
|
5385
|
+
name: "new-feature",
|
|
5386
|
+
description: "Standard workflow for adding a new feature",
|
|
5387
|
+
category: "development",
|
|
5388
|
+
items: [
|
|
5389
|
+
{ title: "Write specification / design doc", details: "Define scope, acceptance criteria, edge cases" },
|
|
5390
|
+
{ title: "Set up feature branch", details: "git checkout -b feature/..." },
|
|
5391
|
+
{ title: "Implement core logic", details: "TDD preferred \u2014 write tests first" },
|
|
5392
|
+
{ title: "Add unit tests", details: ">= 80% coverage for new code" },
|
|
5393
|
+
{ title: "Add integration tests", details: "End-to-end happy path + error paths" },
|
|
5394
|
+
{ title: "Update documentation", details: "README, API docs, changelog" },
|
|
5395
|
+
{ title: "Code review", details: "Self-review before requesting review" },
|
|
5396
|
+
{ title: "Merge and deploy", details: "CI green, tag release" }
|
|
5397
|
+
]
|
|
5398
|
+
},
|
|
5399
|
+
"bug-fix": {
|
|
5400
|
+
name: "bug-fix",
|
|
5401
|
+
description: "Systematic approach to fixing bugs",
|
|
5402
|
+
category: "maintenance",
|
|
5403
|
+
items: [
|
|
5404
|
+
{ title: "Reproduce the bug", details: "Minimal reproduction case" },
|
|
5405
|
+
{ title: "Root cause analysis", details: "Trace through logs, debugger" },
|
|
5406
|
+
{ title: "Write failing test", details: "Test must fail before fix" },
|
|
5407
|
+
{ title: "Implement fix", details: "Smallest possible change" },
|
|
5408
|
+
{ title: "Verify fix", details: "Test passes, reproduction no longer fails" },
|
|
5409
|
+
{ title: "Regression test", details: "Ensure no related tests broken" },
|
|
5410
|
+
{ title: "Document in changelog", details: "Brief description + issue link" }
|
|
5411
|
+
]
|
|
5412
|
+
},
|
|
5413
|
+
"refactor": {
|
|
5414
|
+
name: "refactor",
|
|
5415
|
+
description: "Safe refactoring workflow",
|
|
5416
|
+
category: "maintenance",
|
|
5417
|
+
items: [
|
|
5418
|
+
{ title: "Identify refactoring target", details: "Code smell, performance bottleneck, or tech debt" },
|
|
5419
|
+
{ title: "Ensure test coverage", details: "Existing tests must pass before and after" },
|
|
5420
|
+
{ title: "Write characterization tests", details: "Capture current behavior if tests weak" },
|
|
5421
|
+
{ title: "Apply refactoring", details: "Small steps, frequent commits" },
|
|
5422
|
+
{ title: "Run full test suite", details: "All tests must pass" },
|
|
5423
|
+
{ title: "Performance check", details: "Ensure no regression" },
|
|
5424
|
+
{ title: "Code review", details: "Explain the why, not just the what" }
|
|
5425
|
+
]
|
|
5426
|
+
},
|
|
5427
|
+
"release": {
|
|
5428
|
+
name: "release",
|
|
5429
|
+
description: "Preparing a new release",
|
|
5430
|
+
category: "release",
|
|
5431
|
+
items: [
|
|
5432
|
+
{ title: "Version bump", details: "package.json, lockfiles, tags" },
|
|
5433
|
+
{ title: "Update changelog", details: "All changes since last release" },
|
|
5434
|
+
{ title: "Run full test suite", details: "Unit + integration + e2e" },
|
|
5435
|
+
{ title: "Build artifacts", details: "Docker images, bundles, binaries" },
|
|
5436
|
+
{ title: "Staging smoke tests", details: "Deploy to staging, verify" },
|
|
5437
|
+
{ title: "Production deploy", details: "Blue-green or canary" },
|
|
5438
|
+
{ title: "Post-deploy verification", details: "Health checks, error rates" },
|
|
5439
|
+
{ title: "Announce release", details: "Slack, email, GitHub release notes" }
|
|
5440
|
+
]
|
|
5441
|
+
},
|
|
5442
|
+
"security-audit": {
|
|
5443
|
+
name: "security-audit",
|
|
5444
|
+
description: "Security review and hardening",
|
|
5445
|
+
category: "infrastructure",
|
|
5446
|
+
items: [
|
|
5447
|
+
{ title: "Dependency audit", details: "npm audit, Snyk, Dependabot alerts" },
|
|
5448
|
+
{ title: "Secret scan", details: "git-secrets, truffleHog, manual review" },
|
|
5449
|
+
{ title: "Access control review", details: "IAM, roles, least privilege" },
|
|
5450
|
+
{ title: "Input validation audit", details: "SQL injection, XSS, path traversal" },
|
|
5451
|
+
{ title: "Authentication review", details: "Session management, MFA, password policy" },
|
|
5452
|
+
{ title: "Logging and monitoring", details: "PII in logs, audit trails" },
|
|
5453
|
+
{ title: "Incident response plan", details: "Runbooks, contacts, escalation" }
|
|
5454
|
+
]
|
|
5455
|
+
},
|
|
5456
|
+
"onboarding": {
|
|
5457
|
+
name: "onboarding",
|
|
5458
|
+
description: "New developer onboarding checklist",
|
|
5459
|
+
category: "infrastructure",
|
|
5460
|
+
items: [
|
|
5461
|
+
{ title: "Repository access", details: "GitHub/GitLab permissions" },
|
|
5462
|
+
{ title: "Local environment setup", details: "Docker, dependencies, env files" },
|
|
5463
|
+
{ title: "Run tests locally", details: "Verify green suite" },
|
|
5464
|
+
{ title: "Read architecture docs", details: "ADR, README, onboarding guide" },
|
|
5465
|
+
{ title: "First commit", details: "Docs fix or small improvement" },
|
|
5466
|
+
{ title: "Pair programming session", details: "Walk through codebase with buddy" },
|
|
5467
|
+
{ title: "Deploy to staging", details: "Verify CI/CD access" }
|
|
5468
|
+
]
|
|
5469
|
+
}
|
|
5470
|
+
};
|
|
5471
|
+
function listPlanTemplates() {
|
|
5472
|
+
return Object.values(templates);
|
|
5473
|
+
}
|
|
5474
|
+
function getPlanTemplate(name) {
|
|
5475
|
+
return templates[name];
|
|
5476
|
+
}
|
|
5477
|
+
function formatPlanTemplates() {
|
|
5478
|
+
const cats = /* @__PURE__ */ new Map();
|
|
5479
|
+
for (const t2 of Object.values(templates)) {
|
|
5480
|
+
const arr = cats.get(t2.category) ?? [];
|
|
5481
|
+
arr.push(t2);
|
|
5482
|
+
cats.set(t2.category, arr);
|
|
5483
|
+
}
|
|
5484
|
+
const lines = ["Available plan templates:"];
|
|
5485
|
+
for (const [cat, items] of cats) {
|
|
5486
|
+
lines.push(`
|
|
5487
|
+
${cat}:`);
|
|
5488
|
+
for (const t2 of items) {
|
|
5489
|
+
lines.push(` ${t2.name.padEnd(18)} \u2014 ${t2.description}`);
|
|
5490
|
+
}
|
|
5491
|
+
}
|
|
5492
|
+
return lines.join("\n");
|
|
5493
|
+
}
|
|
5225
5494
|
async function loadDirectorState(filePath) {
|
|
5226
5495
|
let raw;
|
|
5227
5496
|
try {
|
|
@@ -5237,15 +5506,48 @@ async function loadDirectorState(filePath) {
|
|
|
5237
5506
|
return null;
|
|
5238
5507
|
}
|
|
5239
5508
|
}
|
|
5509
|
+
async function acquireDirectorStateLock(lockPath, processId = process.pid) {
|
|
5510
|
+
let existing;
|
|
5511
|
+
try {
|
|
5512
|
+
existing = await fsp2.readFile(lockPath, "utf8");
|
|
5513
|
+
} catch {
|
|
5514
|
+
}
|
|
5515
|
+
if (existing) {
|
|
5516
|
+
try {
|
|
5517
|
+
const lock2 = JSON.parse(existing);
|
|
5518
|
+
try {
|
|
5519
|
+
process.kill(lock2.pid, 0);
|
|
5520
|
+
return false;
|
|
5521
|
+
} catch {
|
|
5522
|
+
}
|
|
5523
|
+
} catch {
|
|
5524
|
+
}
|
|
5525
|
+
}
|
|
5526
|
+
const lock = {
|
|
5527
|
+
pid: processId,
|
|
5528
|
+
hostname: __require("os").hostname(),
|
|
5529
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
5530
|
+
};
|
|
5531
|
+
await atomicWrite(lockPath, JSON.stringify(lock), { mode: 384 });
|
|
5532
|
+
return true;
|
|
5533
|
+
}
|
|
5534
|
+
async function releaseDirectorStateLock(lockPath) {
|
|
5535
|
+
try {
|
|
5536
|
+
await fsp2.unlink(lockPath);
|
|
5537
|
+
} catch {
|
|
5538
|
+
}
|
|
5539
|
+
}
|
|
5240
5540
|
var DirectorStateCheckpoint = class {
|
|
5241
5541
|
snapshot;
|
|
5242
5542
|
filePath;
|
|
5543
|
+
lockPath;
|
|
5243
5544
|
timer = null;
|
|
5244
5545
|
debounceMs;
|
|
5245
5546
|
writing = false;
|
|
5246
5547
|
rewriteRequested = false;
|
|
5247
5548
|
constructor(filePath, init, debounceMs = 250) {
|
|
5248
5549
|
this.filePath = filePath;
|
|
5550
|
+
this.lockPath = `${filePath}.lock`;
|
|
5249
5551
|
this.debounceMs = debounceMs;
|
|
5250
5552
|
this.snapshot = {
|
|
5251
5553
|
version: 1,
|
|
@@ -5255,10 +5557,36 @@ var DirectorStateCheckpoint = class {
|
|
|
5255
5557
|
maxSpawns: init.maxSpawns,
|
|
5256
5558
|
spawnDepth: init.spawnDepth,
|
|
5257
5559
|
maxSpawnDepth: init.maxSpawnDepth,
|
|
5560
|
+
directorBudget: init.directorBudget,
|
|
5258
5561
|
subagents: [],
|
|
5259
5562
|
tasks: []
|
|
5260
5563
|
};
|
|
5261
5564
|
}
|
|
5565
|
+
/**
|
|
5566
|
+
* Attempt to acquire the lock for this checkpoint. Call this before
|
|
5567
|
+
* resuming a crashed director run. If it returns false, another
|
|
5568
|
+
* director process is still running this fleet — do not resume.
|
|
5569
|
+
*/
|
|
5570
|
+
async acquireLock() {
|
|
5571
|
+
return acquireDirectorStateLock(this.lockPath);
|
|
5572
|
+
}
|
|
5573
|
+
/**
|
|
5574
|
+
* Release the lock on graceful shutdown. Call `flush()` first to ensure
|
|
5575
|
+
* the final checkpoint state is on disk before removing the lock.
|
|
5576
|
+
* Without this, the next resume will see a stale-lock and refuse.
|
|
5577
|
+
*/
|
|
5578
|
+
async releaseLock() {
|
|
5579
|
+
return releaseDirectorStateLock(this.lockPath);
|
|
5580
|
+
}
|
|
5581
|
+
/**
|
|
5582
|
+
* Resume from a snapshot previously loaded via `loadDirectorState()`.
|
|
5583
|
+
* Use this when `--resume <runId>` is triggered — the snapshot has
|
|
5584
|
+
* the full fleet state (subagents, tasks) from before the crash; the
|
|
5585
|
+
* checkpoint continues from there.
|
|
5586
|
+
*/
|
|
5587
|
+
resume(snapshot) {
|
|
5588
|
+
this.snapshot = snapshot;
|
|
5589
|
+
}
|
|
5262
5590
|
current() {
|
|
5263
5591
|
return this.snapshot;
|
|
5264
5592
|
}
|
|
@@ -5346,6 +5674,7 @@ var DefaultPermissionPolicy = class {
|
|
|
5346
5674
|
loaded = false;
|
|
5347
5675
|
trustFile;
|
|
5348
5676
|
yolo;
|
|
5677
|
+
forceAllYolo;
|
|
5349
5678
|
/**
|
|
5350
5679
|
* Session-scoped "soft deny" map. When the user presses 'n' (block once),
|
|
5351
5680
|
* the tool+pattern is added here. If the LLM retries in the same session,
|
|
@@ -5378,6 +5707,7 @@ var DefaultPermissionPolicy = class {
|
|
|
5378
5707
|
constructor(opts) {
|
|
5379
5708
|
this.trustFile = opts.trustFile;
|
|
5380
5709
|
this.yolo = opts.yolo ?? false;
|
|
5710
|
+
this.forceAllYolo = opts.forceAllYolo ?? false;
|
|
5381
5711
|
this.promptDelegate = opts.promptDelegate;
|
|
5382
5712
|
}
|
|
5383
5713
|
/**
|
|
@@ -5397,6 +5727,14 @@ var DefaultPermissionPolicy = class {
|
|
|
5397
5727
|
getYolo() {
|
|
5398
5728
|
return this.yolo;
|
|
5399
5729
|
}
|
|
5730
|
+
/** Toggle force-all-YOLO at runtime. */
|
|
5731
|
+
setForceAllYolo(enabled) {
|
|
5732
|
+
this.forceAllYolo = enabled;
|
|
5733
|
+
}
|
|
5734
|
+
/** Check whether force-all-YOLO is active. */
|
|
5735
|
+
getForceAllYolo() {
|
|
5736
|
+
return this.forceAllYolo;
|
|
5737
|
+
}
|
|
5400
5738
|
async reload() {
|
|
5401
5739
|
try {
|
|
5402
5740
|
const raw = await fsp2.readFile(this.trustFile, "utf8");
|
|
@@ -5438,6 +5776,21 @@ var DefaultPermissionPolicy = class {
|
|
|
5438
5776
|
return { permission: "auto", source: "trust" };
|
|
5439
5777
|
}
|
|
5440
5778
|
if (this.yolo) {
|
|
5779
|
+
if (tool.riskTier === "destructive" && !this.forceAllYolo) {
|
|
5780
|
+
if (this.promptDelegate) {
|
|
5781
|
+
const decision = await this.promptDelegate(tool, input, subject ?? tool.name);
|
|
5782
|
+
if (decision === "always") {
|
|
5783
|
+
await this.trust({ tool: tool.name, pattern: subject ?? tool.name });
|
|
5784
|
+
return { permission: "auto", source: "user", reason: "destructive yolo always-allowed" };
|
|
5785
|
+
}
|
|
5786
|
+
if (decision === "deny") {
|
|
5787
|
+
await this.deny({ tool: tool.name, pattern: subject ?? tool.name });
|
|
5788
|
+
return { permission: "deny", source: "user", reason: "user denied destructive yolo" };
|
|
5789
|
+
}
|
|
5790
|
+
return { permission: decision === "yes" ? "auto" : "deny", source: "user" };
|
|
5791
|
+
}
|
|
5792
|
+
return { permission: "confirm", source: "yolo_destructive", riskTier: "destructive", reason: "destructive tool needs explicit approval even in yolo mode" };
|
|
5793
|
+
}
|
|
5441
5794
|
return { permission: "auto", source: "yolo" };
|
|
5442
5795
|
}
|
|
5443
5796
|
if (tool.name === "write" && subject) {
|
|
@@ -7968,6 +8321,9 @@ function makeSpawnTool(director, roster) {
|
|
|
7968
8321
|
if (err instanceof DirectorBudgetError) {
|
|
7969
8322
|
return { error: err.message, kind: err.kind, limit: err.limit, observed: err.observed };
|
|
7970
8323
|
}
|
|
8324
|
+
if (err instanceof DirectorCostCapError) {
|
|
8325
|
+
return { error: err.message, kind: err.kind, limit: err.limit, observed: err.observed };
|
|
8326
|
+
}
|
|
7971
8327
|
return { error: err instanceof Error ? err.message : String(err) };
|
|
7972
8328
|
}
|
|
7973
8329
|
}
|
|
@@ -8105,6 +8461,65 @@ function makeFleetUsageTool(director) {
|
|
|
8105
8461
|
}
|
|
8106
8462
|
};
|
|
8107
8463
|
}
|
|
8464
|
+
function makeFleetSessionTool(director) {
|
|
8465
|
+
return {
|
|
8466
|
+
name: "fleet_session",
|
|
8467
|
+
description: "Read a subagent's JSONL transcript and extract its last assistant text, stop reason, and tool-use count. Use this to see what a running or timed-out subagent actually produced.",
|
|
8468
|
+
permission: "auto",
|
|
8469
|
+
mutating: false,
|
|
8470
|
+
inputSchema: {
|
|
8471
|
+
type: "object",
|
|
8472
|
+
properties: {
|
|
8473
|
+
subagentId: { type: "string", description: "Subagent id to read the transcript of." },
|
|
8474
|
+
/** Number of trailing lines to return (last N JSONL lines). Default: all. */
|
|
8475
|
+
tail: { type: "number", description: "Number of trailing JSONL lines to return. Omit for the full transcript." }
|
|
8476
|
+
},
|
|
8477
|
+
required: ["subagentId"]
|
|
8478
|
+
},
|
|
8479
|
+
async execute(input) {
|
|
8480
|
+
const i = input;
|
|
8481
|
+
const result = await director.readSession(i.subagentId, i.tail);
|
|
8482
|
+
if (!result) {
|
|
8483
|
+
return {
|
|
8484
|
+
error: `fleet_session: transcript unavailable for "${i.subagentId}". Is sessionsRoot configured?`
|
|
8485
|
+
};
|
|
8486
|
+
}
|
|
8487
|
+
return result;
|
|
8488
|
+
}
|
|
8489
|
+
};
|
|
8490
|
+
}
|
|
8491
|
+
function makeFleetHealthTool(director) {
|
|
8492
|
+
return {
|
|
8493
|
+
name: "fleet_health",
|
|
8494
|
+
description: "Per-subagent health report: budget pressure (pct of limits consumed), last activity timestamp, and current status. Use to decide whether to assign more work to a subagent or spawn a fresh one.",
|
|
8495
|
+
permission: "auto",
|
|
8496
|
+
mutating: false,
|
|
8497
|
+
inputSchema: { type: "object", properties: {}, required: [] },
|
|
8498
|
+
async execute() {
|
|
8499
|
+
const status = director.status();
|
|
8500
|
+
const snapshot = director.snapshot();
|
|
8501
|
+
const subagents = status.subagents ?? [];
|
|
8502
|
+
const perSubagent = snapshot.perSubagent ?? {};
|
|
8503
|
+
return {
|
|
8504
|
+
subagents: subagents.map((s) => {
|
|
8505
|
+
const usage = perSubagent[s.id];
|
|
8506
|
+
return {
|
|
8507
|
+
id: s.id,
|
|
8508
|
+
status: s.status,
|
|
8509
|
+
lastEventAt: usage?.lastEventAt,
|
|
8510
|
+
// Budget pressure: fraction of each limit consumed if we have it.
|
|
8511
|
+
// BudgetWarning events carry used/limit ratios; surface them here.
|
|
8512
|
+
budgetPressure: {
|
|
8513
|
+
iterations: usage?.iterations,
|
|
8514
|
+
toolCalls: usage?.toolCalls,
|
|
8515
|
+
costUsd: usage?.cost
|
|
8516
|
+
}
|
|
8517
|
+
};
|
|
8518
|
+
})
|
|
8519
|
+
};
|
|
8520
|
+
}
|
|
8521
|
+
};
|
|
8522
|
+
}
|
|
8108
8523
|
|
|
8109
8524
|
// src/coordination/director.ts
|
|
8110
8525
|
var DirectorBudgetError = class extends Error {
|
|
@@ -8121,6 +8536,20 @@ var DirectorBudgetError = class extends Error {
|
|
|
8121
8536
|
this.observed = observed;
|
|
8122
8537
|
}
|
|
8123
8538
|
};
|
|
8539
|
+
var DirectorCostCapError = class extends Error {
|
|
8540
|
+
kind;
|
|
8541
|
+
limit;
|
|
8542
|
+
observed;
|
|
8543
|
+
constructor(limit, observed) {
|
|
8544
|
+
super(
|
|
8545
|
+
`Director cost cap exceeded: total fleet spend ${observed.toFixed(4)} exceeds maxCostUsd ${limit.toFixed(4)}`
|
|
8546
|
+
);
|
|
8547
|
+
this.name = "DirectorCostCapError";
|
|
8548
|
+
this.kind = "max_cost_usd";
|
|
8549
|
+
this.limit = limit;
|
|
8550
|
+
this.observed = observed;
|
|
8551
|
+
}
|
|
8552
|
+
};
|
|
8124
8553
|
var Director = class {
|
|
8125
8554
|
id;
|
|
8126
8555
|
fleet;
|
|
@@ -8175,6 +8604,14 @@ var Director = class {
|
|
|
8175
8604
|
/** Debounce timer for periodic manifest writes. */
|
|
8176
8605
|
manifestTimer = null;
|
|
8177
8606
|
manifestDebounceMs;
|
|
8607
|
+
/** Fleet-wide cost cap. Infinity means no cap. */
|
|
8608
|
+
maxCostUsd;
|
|
8609
|
+
/** Max auto-extensions per subagent per budget kind before denying. */
|
|
8610
|
+
maxBudgetExtensions;
|
|
8611
|
+
/** Sessions root for direct subagent JSONL reads (fleet_session tool). */
|
|
8612
|
+
sessionsRoot;
|
|
8613
|
+
/** Director run id for JSONL path resolution. */
|
|
8614
|
+
directorRunId;
|
|
8178
8615
|
/** Resolves task descriptions back from `assign()` so completion events
|
|
8179
8616
|
* can also carry a human-readable title. */
|
|
8180
8617
|
taskDescriptions = /* @__PURE__ */ new Map();
|
|
@@ -8201,12 +8638,17 @@ var Director = class {
|
|
|
8201
8638
|
this.spawnDepth = opts.spawnDepth ?? 0;
|
|
8202
8639
|
this.sessionWriter = opts.sessionWriter ?? null;
|
|
8203
8640
|
this.manifestDebounceMs = opts.manifestDebounceMs ?? 2e3;
|
|
8641
|
+
this.maxCostUsd = opts.directorBudget?.maxCostUsd ?? Number.POSITIVE_INFINITY;
|
|
8642
|
+
this.maxBudgetExtensions = opts.maxBudgetExtensions ?? 2;
|
|
8643
|
+
this.sessionsRoot = opts.sessionsRoot;
|
|
8644
|
+
this.directorRunId = opts.directorRunId ?? this.id;
|
|
8204
8645
|
this.stateCheckpoint = opts.stateCheckpointPath ? new DirectorStateCheckpoint(opts.stateCheckpointPath, {
|
|
8205
8646
|
directorRunId: this.id,
|
|
8206
8647
|
maxSpawns: opts.maxSpawns,
|
|
8207
8648
|
spawnDepth: this.spawnDepth,
|
|
8208
|
-
maxSpawnDepth: this.maxSpawnDepth
|
|
8209
|
-
|
|
8649
|
+
maxSpawnDepth: this.maxSpawnDepth,
|
|
8650
|
+
directorBudget: opts.directorBudget
|
|
8651
|
+
}, opts.checkpointDebounceMs ?? 250) : null;
|
|
8210
8652
|
if (this.sharedScratchpadPath) {
|
|
8211
8653
|
void fsp2.mkdir(this.sharedScratchpadPath, { recursive: true }).catch(() => void 0);
|
|
8212
8654
|
}
|
|
@@ -8267,7 +8709,7 @@ var Director = class {
|
|
|
8267
8709
|
const payload = e.payload;
|
|
8268
8710
|
const guardKey = `${e.subagentId}:${payload.kind}`;
|
|
8269
8711
|
const prior = extendCounts.get(guardKey) ?? 0;
|
|
8270
|
-
if (prior >=
|
|
8712
|
+
if (prior >= this.maxBudgetExtensions) {
|
|
8271
8713
|
payload.deny();
|
|
8272
8714
|
extendCounts.delete(guardKey);
|
|
8273
8715
|
return;
|
|
@@ -8328,6 +8770,12 @@ var Director = class {
|
|
|
8328
8770
|
if (this.spawnCount >= this.maxSpawns) {
|
|
8329
8771
|
throw new DirectorBudgetError("max_spawns", this.maxSpawns, this.spawnCount + 1);
|
|
8330
8772
|
}
|
|
8773
|
+
if (this.maxCostUsd < Number.POSITIVE_INFINITY) {
|
|
8774
|
+
const totalCost = this.usage.snapshot().total?.cost ?? 0;
|
|
8775
|
+
if (totalCost >= this.maxCostUsd) {
|
|
8776
|
+
throw new DirectorCostCapError(this.maxCostUsd, totalCost);
|
|
8777
|
+
}
|
|
8778
|
+
}
|
|
8331
8779
|
const result = await this.coordinator.spawn(config);
|
|
8332
8780
|
this.spawnCount += 1;
|
|
8333
8781
|
this.subagentMeta.set(result.subagentId, {
|
|
@@ -8507,6 +8955,7 @@ var Director = class {
|
|
|
8507
8955
|
if (this.stateCheckpoint) {
|
|
8508
8956
|
this.stateCheckpoint.setUsage(this.usage.snapshot());
|
|
8509
8957
|
await this.stateCheckpoint.flush().catch((err) => this.logShutdownError("state_checkpoint_flush", err));
|
|
8958
|
+
await this.stateCheckpoint.releaseLock().catch((err) => this.logShutdownError("state_checkpoint_lock_release", err));
|
|
8510
8959
|
}
|
|
8511
8960
|
}
|
|
8512
8961
|
/**
|
|
@@ -8610,6 +9059,58 @@ var Director = class {
|
|
|
8610
9059
|
completedResults() {
|
|
8611
9060
|
return Array.from(this.completed.values());
|
|
8612
9061
|
}
|
|
9062
|
+
/**
|
|
9063
|
+
* Inject a previously-saved checkpoint snapshot. Call this right after
|
|
9064
|
+
* constructing a Director during a `--resume` run so the in-memory state
|
|
9065
|
+
* (subagents, tasks, waiters) reflects the pre-crash reality instead of
|
|
9066
|
+
* starting from a blank slate. The director then resumes from there —
|
|
9067
|
+
* completing any in-flight tasks and ignoring tasks that already reached
|
|
9068
|
+
* a terminal state in the prior run.
|
|
9069
|
+
*/
|
|
9070
|
+
setCheckpointState(snapshot) {
|
|
9071
|
+
this.stateCheckpoint?.resume(snapshot);
|
|
9072
|
+
}
|
|
9073
|
+
/**
|
|
9074
|
+
* Read a subagent's JSONL transcript directly from disk (no bridge
|
|
9075
|
+
* round-trip needed). Returns the last assistant text, stop reason,
|
|
9076
|
+
* tool-use count, and line count — or null if the file is unavailable.
|
|
9077
|
+
* Requires `sessionsRoot` to be set on construction.
|
|
9078
|
+
*/
|
|
9079
|
+
async readSession(subagentId, tail) {
|
|
9080
|
+
if (!this.sessionsRoot) return null;
|
|
9081
|
+
const filePath = path6.join(this.sessionsRoot, this.directorRunId, `${subagentId}.jsonl`);
|
|
9082
|
+
let raw;
|
|
9083
|
+
try {
|
|
9084
|
+
raw = await fsp2.readFile(filePath, "utf8");
|
|
9085
|
+
} catch {
|
|
9086
|
+
return null;
|
|
9087
|
+
}
|
|
9088
|
+
const lines = raw.split("\n").filter((l) => l.trim());
|
|
9089
|
+
const targetLines = tail ? lines.slice(-tail) : lines;
|
|
9090
|
+
let lastAssistantText;
|
|
9091
|
+
let lastStopReason;
|
|
9092
|
+
let toolUses = 0;
|
|
9093
|
+
for (const line of targetLines) {
|
|
9094
|
+
try {
|
|
9095
|
+
const ev = JSON.parse(line);
|
|
9096
|
+
if (ev.type === "assistant" && typeof ev.text === "string") {
|
|
9097
|
+
lastAssistantText = ev.text;
|
|
9098
|
+
} else if (ev.type === "stop" && ev.stopReason) {
|
|
9099
|
+
lastStopReason = ev.stopReason;
|
|
9100
|
+
} else if (ev.type === "tool_use") {
|
|
9101
|
+
toolUses++;
|
|
9102
|
+
}
|
|
9103
|
+
} catch {
|
|
9104
|
+
}
|
|
9105
|
+
}
|
|
9106
|
+
return {
|
|
9107
|
+
lastAssistantText,
|
|
9108
|
+
lastStopReason,
|
|
9109
|
+
toolUsesObserved: toolUses,
|
|
9110
|
+
events: targetLines.length,
|
|
9111
|
+
path: filePath
|
|
9112
|
+
};
|
|
9113
|
+
}
|
|
8613
9114
|
snapshot() {
|
|
8614
9115
|
return this.usage.snapshot();
|
|
8615
9116
|
}
|
|
@@ -8688,10 +9189,28 @@ var Director = class {
|
|
|
8688
9189
|
makeRollUpTool(this),
|
|
8689
9190
|
makeTerminateTool(this),
|
|
8690
9191
|
makeFleetStatusTool(this),
|
|
8691
|
-
makeFleetUsageTool(this)
|
|
9192
|
+
makeFleetUsageTool(this),
|
|
9193
|
+
makeFleetSessionTool(this),
|
|
9194
|
+
makeFleetHealthTool(this)
|
|
8692
9195
|
];
|
|
8693
9196
|
return t2;
|
|
8694
9197
|
}
|
|
9198
|
+
/**
|
|
9199
|
+
* Attempt to acquire the checkpoint lock. Must be called before
|
|
9200
|
+
* resuming — if another director process is alive, this returns
|
|
9201
|
+
* false and the caller should not proceed with the resume.
|
|
9202
|
+
*/
|
|
9203
|
+
async acquireCheckpointLock() {
|
|
9204
|
+
return this.stateCheckpoint ? this.stateCheckpoint.acquireLock() : true;
|
|
9205
|
+
}
|
|
9206
|
+
/**
|
|
9207
|
+
* Resume from a prior checkpoint snapshot (loaded via
|
|
9208
|
+
* `loadDirectorState()`). Re-attach to the fleet mid-flight so
|
|
9209
|
+
* subsequent spawn/assign calls update the checkpoint normally.
|
|
9210
|
+
*/
|
|
9211
|
+
resumeFromCheckpoint(snapshot) {
|
|
9212
|
+
this.stateCheckpoint?.resume(snapshot);
|
|
9213
|
+
}
|
|
8695
9214
|
};
|
|
8696
9215
|
function createDelegateTool(opts) {
|
|
8697
9216
|
const defaultTimeoutMs = opts.defaultTimeoutMs ?? 4 * 60 * 60 * 1e3;
|
|
@@ -8797,7 +9316,7 @@ function createDelegateTool(opts) {
|
|
|
8797
9316
|
if (typeof i.maxToolCalls === "number" && i.maxToolCalls > 0) {
|
|
8798
9317
|
cfg.maxToolCalls = i.maxToolCalls;
|
|
8799
9318
|
}
|
|
8800
|
-
const SUBAGENT_TIMEOUT_BUFFER_MS = 3e4;
|
|
9319
|
+
const SUBAGENT_TIMEOUT_BUFFER_MS = opts.subagentTimeoutBufferMs ?? 3e4;
|
|
8801
9320
|
const desiredSubTimeout = Math.max(3e4, timeoutMs - SUBAGENT_TIMEOUT_BUFFER_MS);
|
|
8802
9321
|
if (!cfg.timeoutMs || cfg.timeoutMs > desiredSubTimeout) {
|
|
8803
9322
|
cfg.timeoutMs = desiredSubTimeout;
|
|
@@ -8849,7 +9368,7 @@ function createDelegateTool(opts) {
|
|
|
8849
9368
|
toolCalls: result.toolCalls,
|
|
8850
9369
|
durationMs: result.durationMs,
|
|
8851
9370
|
...partial ? { partial } : {},
|
|
8852
|
-
...hintForKind(errorKind, retryable, backoffMs) ? { hint: hintForKind(errorKind, retryable, backoffMs) } : {}
|
|
9371
|
+
...hintForKind(errorKind, retryable, backoffMs, partial) ? { hint: hintForKind(errorKind, retryable, backoffMs, partial) } : {}
|
|
8853
9372
|
};
|
|
8854
9373
|
} catch (err) {
|
|
8855
9374
|
return {
|
|
@@ -8869,7 +9388,7 @@ function instantiateRosterConfig2(role, base) {
|
|
|
8869
9388
|
id: `${role}-${randomUUID().slice(0, 8)}`
|
|
8870
9389
|
};
|
|
8871
9390
|
}
|
|
8872
|
-
function hintForKind(kind, retryable, backoffMs) {
|
|
9391
|
+
function hintForKind(kind, retryable, backoffMs, partial) {
|
|
8873
9392
|
if (!kind) return void 0;
|
|
8874
9393
|
switch (kind) {
|
|
8875
9394
|
case "provider_rate_limit":
|
|
@@ -8885,16 +9404,40 @@ function hintForKind(kind, retryable, backoffMs) {
|
|
|
8885
9404
|
case "budget_iterations":
|
|
8886
9405
|
case "budget_tool_calls":
|
|
8887
9406
|
case "budget_tokens":
|
|
8888
|
-
case "budget_cost":
|
|
8889
|
-
|
|
8890
|
-
|
|
8891
|
-
|
|
9407
|
+
case "budget_cost": {
|
|
9408
|
+
const base = "Subagent exhausted its budget. The coordinator may auto-extend; otherwise raise the matching `max*` field (e.g. maxToolCalls: 600) on the next delegate, or split the task.";
|
|
9409
|
+
if (partial?.lastAssistantText) {
|
|
9410
|
+
return `${base}
|
|
9411
|
+
|
|
9412
|
+
Partial output produced before budget hit:
|
|
9413
|
+
${partial.lastAssistantText}`;
|
|
9414
|
+
}
|
|
9415
|
+
return base;
|
|
9416
|
+
}
|
|
9417
|
+
case "budget_timeout": {
|
|
9418
|
+
const base = "Subagent hit its wall-clock budget. Raise `timeoutMs` on the next delegate or split the task.";
|
|
9419
|
+
if (partial?.lastAssistantText) {
|
|
9420
|
+
return `${base}
|
|
9421
|
+
|
|
9422
|
+
Partial output produced before timeout:
|
|
9423
|
+
${partial.lastAssistantText}`;
|
|
9424
|
+
}
|
|
9425
|
+
return base;
|
|
9426
|
+
}
|
|
8892
9427
|
case "aborted_by_parent":
|
|
8893
9428
|
return "Subagent was aborted (user Ctrl+C, parent unwound, or sibling failure cascade). Not retryable until the abort condition is resolved.";
|
|
8894
9429
|
case "empty_response":
|
|
8895
9430
|
return "Subagent ended its turn with no text and no tool calls. Almost always a prompt / config issue \u2014 clarify the task or check the model.";
|
|
8896
|
-
case "tool_failed":
|
|
8897
|
-
|
|
9431
|
+
case "tool_failed": {
|
|
9432
|
+
const base = "A tool inside the subagent returned ok:false. Retry with corrected inputs.";
|
|
9433
|
+
if (partial?.lastAssistantText) {
|
|
9434
|
+
return `${base}
|
|
9435
|
+
|
|
9436
|
+
Agent reasoning before failure:
|
|
9437
|
+
${partial.lastAssistantText}`;
|
|
9438
|
+
}
|
|
9439
|
+
return base;
|
|
9440
|
+
}
|
|
8898
9441
|
case "bridge_failed":
|
|
8899
9442
|
return "Parent-child bridge transport failed. This is rare \u2014 restart the session and retry.";
|
|
8900
9443
|
default:
|
|
@@ -9179,8 +9722,8 @@ async function loadProjectModes(modesDir) {
|
|
|
9179
9722
|
for (const entry of entries) {
|
|
9180
9723
|
if (!entry.endsWith(".md") && !entry.endsWith(".txt")) continue;
|
|
9181
9724
|
const filePath = path6.join(modesDir, entry);
|
|
9182
|
-
const
|
|
9183
|
-
if (!
|
|
9725
|
+
const stat8 = await fsp2.stat(filePath);
|
|
9726
|
+
if (!stat8.isFile()) continue;
|
|
9184
9727
|
const content = await fsp2.readFile(filePath, "utf8");
|
|
9185
9728
|
const id = path6.basename(entry, path6.extname(entry));
|
|
9186
9729
|
modes.push({
|
|
@@ -12762,10 +13305,10 @@ var SkillInstaller = class {
|
|
|
12762
13305
|
if (!resolved.startsWith(path6.resolve(destDir))) {
|
|
12763
13306
|
throw new Error(`Path traversal detected in skill file: ${file}`);
|
|
12764
13307
|
}
|
|
12765
|
-
const
|
|
12766
|
-
if (
|
|
13308
|
+
const stat8 = await fsp2.stat(srcPath);
|
|
13309
|
+
if (stat8.size > MAX_SKILL_FILE_SIZE) {
|
|
12767
13310
|
throw new Error(
|
|
12768
|
-
`Skill file "${file}" is too large (${(
|
|
13311
|
+
`Skill file "${file}" is too large (${(stat8.size / 1024).toFixed(1)}KB). Max: ${MAX_SKILL_FILE_SIZE / 1024}KB`
|
|
12769
13312
|
);
|
|
12770
13313
|
}
|
|
12771
13314
|
await fsp2.mkdir(path6.dirname(destPath), { recursive: true });
|
|
@@ -13161,6 +13704,1841 @@ async function revertSnapshots(snapshots) {
|
|
|
13161
13704
|
}
|
|
13162
13705
|
return { revertedFiles, errors };
|
|
13163
13706
|
}
|
|
13707
|
+
var MATCHERS = {
|
|
13708
|
+
pnpmWorkspace: (files) => files.includes("pnpm-workspace.yaml"),
|
|
13709
|
+
gradlew: (_files, dirs) => dirs.includes("gradlew"),
|
|
13710
|
+
mavenWrapper: (_files, dirs) => dirs.includes(".mvn"),
|
|
13711
|
+
dotnetSdk: (files) => files.some((f) => f.endsWith(".csproj") || f.endsWith(".fsproj")),
|
|
13712
|
+
yarnConfig: (files) => files.includes(".yarnrc") || files.includes("yarn.config.js"),
|
|
13713
|
+
notPoetryLock: (files) => !files.includes("poetry.lock")
|
|
13714
|
+
};
|
|
13715
|
+
var STACK_SIGNATURES = [
|
|
13716
|
+
// Node.js variants - checked in order, first match wins
|
|
13717
|
+
{
|
|
13718
|
+
stack: "nodejs",
|
|
13719
|
+
packageManager: "pnpm",
|
|
13720
|
+
manifestFiles: ["package.json"],
|
|
13721
|
+
lockFiles: ["pnpm-lock.yaml"],
|
|
13722
|
+
secondarySignatures: (f) => MATCHERS.pnpmWorkspace(f)
|
|
13723
|
+
},
|
|
13724
|
+
{
|
|
13725
|
+
stack: "nodejs",
|
|
13726
|
+
packageManager: "bun",
|
|
13727
|
+
manifestFiles: ["package.json"],
|
|
13728
|
+
lockFiles: ["bun.lockb"]
|
|
13729
|
+
},
|
|
13730
|
+
{
|
|
13731
|
+
stack: "nodejs",
|
|
13732
|
+
packageManager: "yarn",
|
|
13733
|
+
manifestFiles: ["package.json"],
|
|
13734
|
+
lockFiles: ["yarn.lock"],
|
|
13735
|
+
secondarySignatures: (f) => MATCHERS.yarnConfig(f)
|
|
13736
|
+
},
|
|
13737
|
+
{
|
|
13738
|
+
stack: "nodejs",
|
|
13739
|
+
packageManager: "npm",
|
|
13740
|
+
manifestFiles: ["package.json"],
|
|
13741
|
+
lockFiles: ["package-lock.json"]
|
|
13742
|
+
},
|
|
13743
|
+
// Python variants
|
|
13744
|
+
{
|
|
13745
|
+
stack: "python",
|
|
13746
|
+
packageManager: "poetry",
|
|
13747
|
+
manifestFiles: ["pyproject.toml"],
|
|
13748
|
+
lockFiles: ["poetry.lock"]
|
|
13749
|
+
},
|
|
13750
|
+
{
|
|
13751
|
+
stack: "python",
|
|
13752
|
+
packageManager: "pip",
|
|
13753
|
+
manifestFiles: ["requirements.txt", "setup.py"],
|
|
13754
|
+
lockFiles: ["requirements.txt"]
|
|
13755
|
+
},
|
|
13756
|
+
{
|
|
13757
|
+
stack: "python",
|
|
13758
|
+
packageManager: "pip",
|
|
13759
|
+
manifestFiles: ["pyproject.toml"],
|
|
13760
|
+
lockFiles: [],
|
|
13761
|
+
secondarySignatures: (f) => MATCHERS.notPoetryLock(f)
|
|
13762
|
+
},
|
|
13763
|
+
// Rust
|
|
13764
|
+
{
|
|
13765
|
+
stack: "rust",
|
|
13766
|
+
packageManager: "cargo",
|
|
13767
|
+
manifestFiles: ["Cargo.toml"],
|
|
13768
|
+
lockFiles: ["Cargo.lock"]
|
|
13769
|
+
},
|
|
13770
|
+
// Go
|
|
13771
|
+
{
|
|
13772
|
+
stack: "go",
|
|
13773
|
+
packageManager: "go",
|
|
13774
|
+
manifestFiles: ["go.mod"],
|
|
13775
|
+
lockFiles: ["go.sum"]
|
|
13776
|
+
},
|
|
13777
|
+
// Java variants
|
|
13778
|
+
{
|
|
13779
|
+
stack: "java",
|
|
13780
|
+
packageManager: "gradle",
|
|
13781
|
+
manifestFiles: ["build.gradle", "build.gradle.kts", "settings.gradle", "settings.gradle.kts"],
|
|
13782
|
+
lockFiles: [],
|
|
13783
|
+
secondarySignatures: (_f, d) => MATCHERS.gradlew(_f, d)
|
|
13784
|
+
},
|
|
13785
|
+
{
|
|
13786
|
+
stack: "java",
|
|
13787
|
+
packageManager: "maven",
|
|
13788
|
+
manifestFiles: ["pom.xml"],
|
|
13789
|
+
lockFiles: [],
|
|
13790
|
+
secondarySignatures: (_f, d) => MATCHERS.mavenWrapper(_f, d)
|
|
13791
|
+
},
|
|
13792
|
+
// .NET
|
|
13793
|
+
{
|
|
13794
|
+
stack: "dotnet",
|
|
13795
|
+
packageManager: "nuget",
|
|
13796
|
+
manifestFiles: ["Directory.Build.props", "Directory.Packages.props"],
|
|
13797
|
+
lockFiles: ["packages.lock.json"],
|
|
13798
|
+
secondarySignatures: (f) => MATCHERS.dotnetSdk(f)
|
|
13799
|
+
},
|
|
13800
|
+
{
|
|
13801
|
+
stack: "dotnet",
|
|
13802
|
+
packageManager: "nuget",
|
|
13803
|
+
manifestFiles: ["*.csproj", "*.fsproj", "*.xproj"],
|
|
13804
|
+
lockFiles: [],
|
|
13805
|
+
secondarySignatures: (f) => MATCHERS.dotnetSdk(f)
|
|
13806
|
+
},
|
|
13807
|
+
// PHP
|
|
13808
|
+
{
|
|
13809
|
+
stack: "php",
|
|
13810
|
+
packageManager: "composer",
|
|
13811
|
+
manifestFiles: ["composer.json"],
|
|
13812
|
+
lockFiles: ["composer.lock"]
|
|
13813
|
+
},
|
|
13814
|
+
// Ruby
|
|
13815
|
+
{
|
|
13816
|
+
stack: "ruby",
|
|
13817
|
+
packageManager: "bundler",
|
|
13818
|
+
manifestFiles: ["Gemfile"],
|
|
13819
|
+
lockFiles: ["Gemfile.lock"]
|
|
13820
|
+
},
|
|
13821
|
+
// C++
|
|
13822
|
+
{
|
|
13823
|
+
stack: "cpp",
|
|
13824
|
+
packageManager: "cmake",
|
|
13825
|
+
manifestFiles: ["CMakeLists.txt"],
|
|
13826
|
+
lockFiles: [],
|
|
13827
|
+
secondarySignatures: (f) => f.includes("CMakeCache.txt") || f.includes("CMakePresets.json")
|
|
13828
|
+
},
|
|
13829
|
+
// Swift
|
|
13830
|
+
{
|
|
13831
|
+
stack: "swift",
|
|
13832
|
+
packageManager: "swiftpm",
|
|
13833
|
+
manifestFiles: ["Package.swift"],
|
|
13834
|
+
lockFiles: []
|
|
13835
|
+
}
|
|
13836
|
+
];
|
|
13837
|
+
var MONOREPO_INDICATORS = {
|
|
13838
|
+
pnpm: ["pnpm-workspace.yaml"],
|
|
13839
|
+
npm: ["lerna.json", "nx.json"],
|
|
13840
|
+
yarn: [],
|
|
13841
|
+
bun: [],
|
|
13842
|
+
cargo: [],
|
|
13843
|
+
go: ["go.work"],
|
|
13844
|
+
maven: [],
|
|
13845
|
+
gradle: [],
|
|
13846
|
+
nuget: ["Directory.Build.props"],
|
|
13847
|
+
pip: [],
|
|
13848
|
+
poetry: [],
|
|
13849
|
+
bundler: [],
|
|
13850
|
+
cmake: [],
|
|
13851
|
+
swiftpm: [],
|
|
13852
|
+
unknown: []
|
|
13853
|
+
};
|
|
13854
|
+
var TechStackDetector = class {
|
|
13855
|
+
cachedResults = /* @__PURE__ */ new Map();
|
|
13856
|
+
async detect(projectRoot) {
|
|
13857
|
+
const cached = this.cachedResults.get(projectRoot);
|
|
13858
|
+
if (cached) return cached;
|
|
13859
|
+
const result = {
|
|
13860
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13861
|
+
projectRoot,
|
|
13862
|
+
detectedStacks: [],
|
|
13863
|
+
isMonorepo: false,
|
|
13864
|
+
workspaceConfigs: []
|
|
13865
|
+
};
|
|
13866
|
+
const entries = await readdir(projectRoot, { withFileTypes: true });
|
|
13867
|
+
const files = entries.filter((e) => e.isFile()).map((e) => e.name);
|
|
13868
|
+
const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
13869
|
+
const detectedStacks = /* @__PURE__ */ new Set();
|
|
13870
|
+
for (const signature of STACK_SIGNATURES) {
|
|
13871
|
+
const detected = this.matchSignature(signature, files, dirs);
|
|
13872
|
+
if (detected) {
|
|
13873
|
+
if (detectedStacks.has(signature.stack)) continue;
|
|
13874
|
+
detectedStacks.add(signature.stack);
|
|
13875
|
+
result.detectedStacks.push(detected);
|
|
13876
|
+
}
|
|
13877
|
+
}
|
|
13878
|
+
result.isMonorepo = this.detectMonorepo(result.detectedStacks, dirs, files);
|
|
13879
|
+
if (result.isMonorepo) {
|
|
13880
|
+
result.workspaceConfigs = this.findWorkspaceConfigs(result.detectedStacks, dirs, files);
|
|
13881
|
+
}
|
|
13882
|
+
this.cachedResults.set(projectRoot, result);
|
|
13883
|
+
return result;
|
|
13884
|
+
}
|
|
13885
|
+
matchSignature(signature, files, _dirs) {
|
|
13886
|
+
const manifestMatch = this.findMatchingManifest(signature.manifestFiles, files);
|
|
13887
|
+
if (!manifestMatch) return null;
|
|
13888
|
+
if (signature.lockFiles.length > 0 && signature.stack === "nodejs") {
|
|
13889
|
+
const hasLockFile = signature.lockFiles.some((lock) => {
|
|
13890
|
+
if (lock.includes("*")) {
|
|
13891
|
+
const regex = new RegExp("^" + lock.replace("*", ".*") + "$");
|
|
13892
|
+
return files.some((f) => regex.test(f));
|
|
13893
|
+
}
|
|
13894
|
+
return files.includes(lock);
|
|
13895
|
+
});
|
|
13896
|
+
if (!hasLockFile && signature.packageManager !== "npm") {
|
|
13897
|
+
return null;
|
|
13898
|
+
}
|
|
13899
|
+
}
|
|
13900
|
+
return {
|
|
13901
|
+
stack: signature.stack,
|
|
13902
|
+
packageManager: signature.packageManager,
|
|
13903
|
+
manifestFile: manifestMatch,
|
|
13904
|
+
dependencies: [],
|
|
13905
|
+
projectPath: ""
|
|
13906
|
+
};
|
|
13907
|
+
}
|
|
13908
|
+
findMatchingManifest(manifests, files) {
|
|
13909
|
+
for (const manifest of manifests) {
|
|
13910
|
+
if (manifest.includes("*")) {
|
|
13911
|
+
const regex = new RegExp("^" + manifest.replace("*", ".*") + "$");
|
|
13912
|
+
const match = files.find((f) => regex.test(f));
|
|
13913
|
+
if (match) return match;
|
|
13914
|
+
} else if (files.includes(manifest)) {
|
|
13915
|
+
return manifest;
|
|
13916
|
+
}
|
|
13917
|
+
}
|
|
13918
|
+
return null;
|
|
13919
|
+
}
|
|
13920
|
+
detectMonorepo(stacks, dirs, files) {
|
|
13921
|
+
if (stacks.length > 1) return true;
|
|
13922
|
+
if (stacks.some((s) => s.packageManager === "pnpm") && files.includes("pnpm-workspace.yaml")) {
|
|
13923
|
+
return true;
|
|
13924
|
+
}
|
|
13925
|
+
for (const stack of stacks) {
|
|
13926
|
+
const indicators = MONOREPO_INDICATORS[stack.packageManager];
|
|
13927
|
+
if (indicators && indicators.some((ind) => files.includes(ind) || dirs.includes(ind))) {
|
|
13928
|
+
return true;
|
|
13929
|
+
}
|
|
13930
|
+
}
|
|
13931
|
+
return false;
|
|
13932
|
+
}
|
|
13933
|
+
findWorkspaceConfigs(stacks, dirs, files) {
|
|
13934
|
+
const configs = [];
|
|
13935
|
+
for (const stack of stacks) {
|
|
13936
|
+
const indicators = MONOREPO_INDICATORS[stack.packageManager];
|
|
13937
|
+
if (indicators) {
|
|
13938
|
+
configs.push(...indicators.filter((ind) => files.includes(ind) || dirs.includes(ind)));
|
|
13939
|
+
}
|
|
13940
|
+
}
|
|
13941
|
+
return [...new Set(configs)];
|
|
13942
|
+
}
|
|
13943
|
+
clearCache() {
|
|
13944
|
+
this.cachedResults.clear();
|
|
13945
|
+
}
|
|
13946
|
+
};
|
|
13947
|
+
var defaultTechStackDetector = new TechStackDetector();
|
|
13948
|
+
|
|
13949
|
+
// src/security-scanner/skill-generator.ts
|
|
13950
|
+
var DEFAULT_OPTIONS = {
|
|
13951
|
+
includeSecrets: true,
|
|
13952
|
+
includeInjection: true,
|
|
13953
|
+
includeConfig: true,
|
|
13954
|
+
includeDependencies: true,
|
|
13955
|
+
severityThreshold: "medium"
|
|
13956
|
+
};
|
|
13957
|
+
var SkillGenerator = class {
|
|
13958
|
+
options;
|
|
13959
|
+
constructor(options = {}) {
|
|
13960
|
+
this.options = { ...DEFAULT_OPTIONS, ...options };
|
|
13961
|
+
}
|
|
13962
|
+
generate(techStack) {
|
|
13963
|
+
const patterns = this.getPatternsForStack(techStack.stack);
|
|
13964
|
+
const content = this.buildSkillContent(techStack, patterns);
|
|
13965
|
+
const targetFiles = this.getTargetFilesForStack(techStack);
|
|
13966
|
+
return {
|
|
13967
|
+
name: `security-scanner-${techStack.stack}`,
|
|
13968
|
+
description: `Security scanner for ${techStack.stack} projects`,
|
|
13969
|
+
version: "1.0.0",
|
|
13970
|
+
techStack: techStack.stack,
|
|
13971
|
+
content,
|
|
13972
|
+
patterns,
|
|
13973
|
+
metadata: {
|
|
13974
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13975
|
+
confidence: this.calculateConfidence(techStack),
|
|
13976
|
+
targetFiles
|
|
13977
|
+
}
|
|
13978
|
+
};
|
|
13979
|
+
}
|
|
13980
|
+
getPatternsForStack(stack) {
|
|
13981
|
+
const patterns = [];
|
|
13982
|
+
const severityOrder = ["critical", "high", "medium", "low"];
|
|
13983
|
+
const minIndex = severityOrder.indexOf(this.options.severityThreshold);
|
|
13984
|
+
if (this.options.includeSecrets) {
|
|
13985
|
+
const secretPatterns = this.getSecretPatterns(stack);
|
|
13986
|
+
patterns.push(...secretPatterns.filter((p) => severityOrder.indexOf(p.severity) <= minIndex));
|
|
13987
|
+
}
|
|
13988
|
+
if (this.options.includeInjection) {
|
|
13989
|
+
const injectionPatterns = this.getInjectionPatterns(stack);
|
|
13990
|
+
patterns.push(...injectionPatterns.filter((p) => severityOrder.indexOf(p.severity) <= minIndex));
|
|
13991
|
+
}
|
|
13992
|
+
if (this.options.includeConfig) {
|
|
13993
|
+
const configPatterns = this.getConfigPatterns(stack);
|
|
13994
|
+
patterns.push(...configPatterns.filter((p) => severityOrder.indexOf(p.severity) <= minIndex));
|
|
13995
|
+
}
|
|
13996
|
+
return patterns;
|
|
13997
|
+
}
|
|
13998
|
+
getSecretPatterns(stack) {
|
|
13999
|
+
const commonSecrets = {
|
|
14000
|
+
id: "hardcoded-secrets",
|
|
14001
|
+
name: "Hardcoded Secrets",
|
|
14002
|
+
severity: "critical",
|
|
14003
|
+
description: "Detects hardcoded API keys, tokens, passwords, and private keys",
|
|
14004
|
+
patterns: [
|
|
14005
|
+
/(?:api[_-]?key|apikey|api[_-]?secret)[^\w]*[=:]\s*["']([a-zA-Z0-9_-]{20,})["']/gi,
|
|
14006
|
+
/(?:password|passwd|pwd)[^\w]*[=:]\s*["'][^"']{6,64}["']/gi,
|
|
14007
|
+
/(?:secret|token|auth)[^\w]*[=:]\s*["'][a-zA-Z0-9_/-]{20,}["']/gi,
|
|
14008
|
+
/-----BEGIN\s+(?:RSA|DSA|EC|OPENSSH)?\s+PRIVATE\s+KEY-----/g
|
|
14009
|
+
],
|
|
14010
|
+
fileExtensions: [".ts", ".js", ".py", ".go", ".java", ".rb", ".php", ".env"],
|
|
14011
|
+
falsePositiveMarkers: ["process.env", "process.argv", "process.env.NODE_ENV"],
|
|
14012
|
+
remediation: "Use environment variables or a secrets manager. Never commit secrets to version control."
|
|
14013
|
+
};
|
|
14014
|
+
const stackSpecific = {
|
|
14015
|
+
nodejs: [
|
|
14016
|
+
{
|
|
14017
|
+
id: "npmrc-credentials",
|
|
14018
|
+
name: "npmrc Credentials",
|
|
14019
|
+
severity: "high",
|
|
14020
|
+
description: "Detects .npmrc authentication tokens",
|
|
14021
|
+
patterns: [
|
|
14022
|
+
// eslint-disable-next-line no-control-regex
|
|
14023
|
+
/_auth\s*=\s*[a-zA-Z0-9+/]{20,}/g,
|
|
14024
|
+
/registry\s*=\s*https:\/\/[a-zA-Z0-9.-]+\/auth/g
|
|
14025
|
+
],
|
|
14026
|
+
fileExtensions: [".npmrc"],
|
|
14027
|
+
falsePositiveMarkers: [],
|
|
14028
|
+
remediation: "Use npm token from environment variable, not hardcoded in .npmrc"
|
|
14029
|
+
}
|
|
14030
|
+
],
|
|
14031
|
+
python: [
|
|
14032
|
+
{
|
|
14033
|
+
id: "python-secret-env",
|
|
14034
|
+
name: "Python Environment Secrets",
|
|
14035
|
+
severity: "high",
|
|
14036
|
+
description: "Detects secrets loaded via os.environ in Python",
|
|
14037
|
+
patterns: [
|
|
14038
|
+
/os\.environ\[['"](?:API_KEY|SECRET|PASSWORD|TOKEN)/g,
|
|
14039
|
+
/getenv\(['"](?:API_KEY|SECRET|PASSWORD|TOKEN)/g
|
|
14040
|
+
],
|
|
14041
|
+
fileExtensions: [".py"],
|
|
14042
|
+
falsePositiveMarkers: ["os.environ.get", "getenv(", "os.getenv"],
|
|
14043
|
+
remediation: "Use python-dotenv or environment variable injection at runtime"
|
|
14044
|
+
}
|
|
14045
|
+
],
|
|
14046
|
+
rust: [
|
|
14047
|
+
{
|
|
14048
|
+
id: "rust-env-secrets",
|
|
14049
|
+
name: "Rust Environment Secrets",
|
|
14050
|
+
severity: "high",
|
|
14051
|
+
description: "Detects env! macro with hardcoded secrets",
|
|
14052
|
+
patterns: [/env!\s*\[['"](?:API_KEY|SECRET|PASSWORD|TOKEN)/g],
|
|
14053
|
+
fileExtensions: [".rs"],
|
|
14054
|
+
falsePositiveMarkers: [],
|
|
14055
|
+
remediation: "Use std::env::var and handle MissingKeyError properly"
|
|
14056
|
+
}
|
|
14057
|
+
],
|
|
14058
|
+
go: [
|
|
14059
|
+
{
|
|
14060
|
+
id: "go-hardcoded-secret",
|
|
14061
|
+
name: "Go Hardcoded Secrets",
|
|
14062
|
+
severity: "critical",
|
|
14063
|
+
description: "Detects hardcoded secrets in Go source",
|
|
14064
|
+
patterns: [
|
|
14065
|
+
/os\.Getenv\(['"](?:API_KEY|SECRET|PASSWORD|TOKEN)/g,
|
|
14066
|
+
/"[a-zA-Z0-9/+=]{40,}"\s*(?:&&|\|\|)\s*err\s*!=\s*nil/g
|
|
14067
|
+
],
|
|
14068
|
+
fileExtensions: [".go"],
|
|
14069
|
+
falsePositiveMarkers: ["os.Getenv", "os.LookupEnv"],
|
|
14070
|
+
remediation: "Use viper or os.Getenv with validation"
|
|
14071
|
+
}
|
|
14072
|
+
],
|
|
14073
|
+
java: [
|
|
14074
|
+
{
|
|
14075
|
+
id: "java-system-getenv",
|
|
14076
|
+
name: "Java System.getenv",
|
|
14077
|
+
severity: "medium",
|
|
14078
|
+
description: "Detects System.getenv() usage which may expose secrets",
|
|
14079
|
+
patterns: [/System\.getenv\(['"](?:API_KEY|SECRET|PASSWORD|TOKEN)/g],
|
|
14080
|
+
fileExtensions: [".java"],
|
|
14081
|
+
falsePositiveMarkers: [],
|
|
14082
|
+
remediation: "Use a secrets manager or environment-specific config"
|
|
14083
|
+
}
|
|
14084
|
+
],
|
|
14085
|
+
dotnet: [
|
|
14086
|
+
{
|
|
14087
|
+
id: "dotnet-config-secrets",
|
|
14088
|
+
name: ".NET Configuration Secrets",
|
|
14089
|
+
severity: "high",
|
|
14090
|
+
description: "Detects secrets in .NET Configuration managers",
|
|
14091
|
+
patterns: [
|
|
14092
|
+
/ConfigurationManager\.ConnectionStrings/g,
|
|
14093
|
+
/ConfigurationManager\.AppSettings/g
|
|
14094
|
+
],
|
|
14095
|
+
fileExtensions: [".cs", ".config"],
|
|
14096
|
+
falsePositiveMarkers: [],
|
|
14097
|
+
remediation: "Use Azure Key Vault or user secrets during development"
|
|
14098
|
+
}
|
|
14099
|
+
]
|
|
14100
|
+
};
|
|
14101
|
+
return [commonSecrets, ...stackSpecific[stack] ?? []];
|
|
14102
|
+
}
|
|
14103
|
+
getInjectionPatterns(stack) {
|
|
14104
|
+
const commonInjection = {
|
|
14105
|
+
id: "command-injection",
|
|
14106
|
+
name: "Command Injection",
|
|
14107
|
+
severity: "critical",
|
|
14108
|
+
description: "Detects shell command injection vulnerabilities",
|
|
14109
|
+
patterns: [
|
|
14110
|
+
/exec\s*\(\s*[`'"].*\$/g,
|
|
14111
|
+
/system\s*\(\s*[`'"].*\$/g,
|
|
14112
|
+
/shell_exec\s*\(\s*[`'"].*\$/g,
|
|
14113
|
+
/exec\s*\(\s*\$/g
|
|
14114
|
+
],
|
|
14115
|
+
fileExtensions: [".ts", ".js", ".php", ".py", ".rb"],
|
|
14116
|
+
falsePositiveMarkers: ["escapeshellarg", "escapeshellcmd", "sanitize"],
|
|
14117
|
+
remediation: "Use parameterized commands with argument arrays instead of string interpolation."
|
|
14118
|
+
};
|
|
14119
|
+
const stackSpecific = {
|
|
14120
|
+
nodejs: [
|
|
14121
|
+
{
|
|
14122
|
+
id: "eval-injection",
|
|
14123
|
+
name: "Eval Injection",
|
|
14124
|
+
severity: "critical",
|
|
14125
|
+
description: "Detects dangerous eval/Function usage with user input",
|
|
14126
|
+
patterns: [
|
|
14127
|
+
/eval\s*\(\s*(?:req|body|input|params|query)/g,
|
|
14128
|
+
/new\s+Function\s*\(\s*(?:req|body|input|params|query)/g
|
|
14129
|
+
],
|
|
14130
|
+
fileExtensions: [".ts", ".js"],
|
|
14131
|
+
falsePositiveMarkers: ["JSON.parse"],
|
|
14132
|
+
remediation: "Never eval user input. Use JSON.parse for data, or proper sandboxing."
|
|
14133
|
+
},
|
|
14134
|
+
{
|
|
14135
|
+
id: "sql-injection-template",
|
|
14136
|
+
name: "SQL Injection via Template",
|
|
14137
|
+
severity: "critical",
|
|
14138
|
+
description: "Detects SQL queries built with string concatenation",
|
|
14139
|
+
patterns: [
|
|
14140
|
+
/(?:query|execute|select)\s*\(\s*[`'"].*\+.*(?:req|body|params|query)/gi,
|
|
14141
|
+
/\.query\s*\(\s*`.*\$\{/g
|
|
14142
|
+
],
|
|
14143
|
+
fileExtensions: [".ts", ".js"],
|
|
14144
|
+
falsePositiveMarkers: ["parameterized", "prepared", "bind"],
|
|
14145
|
+
remediation: "Use parameterized queries or an ORM with proper query building."
|
|
14146
|
+
},
|
|
14147
|
+
{
|
|
14148
|
+
id: "nosql-injection",
|
|
14149
|
+
name: "NoSQL Injection",
|
|
14150
|
+
severity: "high",
|
|
14151
|
+
description: "Detects NoSQL query injection via user input",
|
|
14152
|
+
patterns: [
|
|
14153
|
+
/find\s*\(\s*\{.*\$where/g,
|
|
14154
|
+
/collection\.(?:find|aggregate)\s*\([^)]*\$/g
|
|
14155
|
+
],
|
|
14156
|
+
fileExtensions: [".ts", ".js"],
|
|
14157
|
+
falsePositiveMarkers: [],
|
|
14158
|
+
remediation: "Sanitize and validate all user input before NoSQL queries."
|
|
14159
|
+
}
|
|
14160
|
+
],
|
|
14161
|
+
python: [
|
|
14162
|
+
{
|
|
14163
|
+
id: "python-sql-injection",
|
|
14164
|
+
name: "Python SQL Injection",
|
|
14165
|
+
severity: "critical",
|
|
14166
|
+
description: "Detects SQL queries built with string formatting",
|
|
14167
|
+
patterns: [
|
|
14168
|
+
/execute\s*\(\s*f?["'].*%.*/g,
|
|
14169
|
+
/cursor\.execute\s*\([^)]*\+[^)]*\)/g
|
|
14170
|
+
],
|
|
14171
|
+
fileExtensions: [".py"],
|
|
14172
|
+
falsePositiveMarkers: ["%s", "%d", "?", "parameterized"],
|
|
14173
|
+
remediation: "Use parameterized queries with cursor.execute(query, params)."
|
|
14174
|
+
},
|
|
14175
|
+
{
|
|
14176
|
+
id: "pickle-deserialization",
|
|
14177
|
+
name: "Pickle Deserialization",
|
|
14178
|
+
severity: "critical",
|
|
14179
|
+
description: "Detects insecure pickle deserialization",
|
|
14180
|
+
patterns: [
|
|
14181
|
+
/pickle\.load\s*\(/g,
|
|
14182
|
+
/pickle\.loads\s*\(/g,
|
|
14183
|
+
/unpickle\.load\s*\(/g
|
|
14184
|
+
],
|
|
14185
|
+
fileExtensions: [".py"],
|
|
14186
|
+
falsePositiveMarkers: [],
|
|
14187
|
+
remediation: "Never unpickle data from untrusted sources. Use JSON or custom serialization."
|
|
14188
|
+
}
|
|
14189
|
+
],
|
|
14190
|
+
go: [
|
|
14191
|
+
{
|
|
14192
|
+
id: "go-sql-injection",
|
|
14193
|
+
name: "Go SQL Injection",
|
|
14194
|
+
severity: "critical",
|
|
14195
|
+
description: "Detects SQL queries with string concatenation",
|
|
14196
|
+
patterns: [
|
|
14197
|
+
/db\.Query\s*\([^)]*\+[^)]*\)/g,
|
|
14198
|
+
/QueryContext?\s*\([^)]*\+[^)]*\)/g
|
|
14199
|
+
],
|
|
14200
|
+
fileExtensions: [".go"],
|
|
14201
|
+
falsePositiveMarkers: ["$1", "$2", "?", "params"],
|
|
14202
|
+
remediation: 'Use parameterized queries: db.QueryContext(ctx, "SELECT * FROM users WHERE id=?", userID)'
|
|
14203
|
+
}
|
|
14204
|
+
],
|
|
14205
|
+
java: [
|
|
14206
|
+
{
|
|
14207
|
+
id: "java-sql-injection",
|
|
14208
|
+
name: "Java SQL Injection",
|
|
14209
|
+
severity: "critical",
|
|
14210
|
+
description: "Detects SQL with string concatenation in JDBC",
|
|
14211
|
+
patterns: [
|
|
14212
|
+
/createStatement\s*\(\s*\).*\.executeQuery\s*\([^)]*\+/g,
|
|
14213
|
+
/Statement\s*\([^)]*\+/g
|
|
14214
|
+
],
|
|
14215
|
+
fileExtensions: [".java"],
|
|
14216
|
+
falsePositiveMarkers: ["PreparedStatement", "?"],
|
|
14217
|
+
remediation: "Use PreparedStatement with parameters."
|
|
14218
|
+
}
|
|
14219
|
+
],
|
|
14220
|
+
rust: [
|
|
14221
|
+
{
|
|
14222
|
+
id: "rust-command-injection",
|
|
14223
|
+
name: "Rust Command Injection",
|
|
14224
|
+
severity: "critical",
|
|
14225
|
+
description: "Detects Command::new with string interpolation",
|
|
14226
|
+
patterns: [
|
|
14227
|
+
/Command::new\s*\([^)]*\)\s*\.(?:arg|args)\s*\([^)]*\+/g,
|
|
14228
|
+
/Command::from\s*\(/g
|
|
14229
|
+
],
|
|
14230
|
+
fileExtensions: [".rs"],
|
|
14231
|
+
falsePositiveMarkers: ["Command::new", "args\\("],
|
|
14232
|
+
remediation: "Use Command::new(array).args(&[...]) to avoid shell injection."
|
|
14233
|
+
}
|
|
14234
|
+
],
|
|
14235
|
+
dotnet: [
|
|
14236
|
+
{
|
|
14237
|
+
id: "csharp-sql-injection",
|
|
14238
|
+
name: "C# SQL Injection",
|
|
14239
|
+
severity: "critical",
|
|
14240
|
+
description: "Detects SQL with string concatenation in C#",
|
|
14241
|
+
patterns: [
|
|
14242
|
+
/SqlCommand\s*\([^)]*\+[^)]*\)/g,
|
|
14243
|
+
/\.ExecuteQuery\s*\([^)]*\+[^)]*\)/g
|
|
14244
|
+
],
|
|
14245
|
+
fileExtensions: [".cs"],
|
|
14246
|
+
falsePositiveMarkers: ["parameters.Add", "@", "SqlParameter"],
|
|
14247
|
+
remediation: "Use parameterized queries with SqlParameter."
|
|
14248
|
+
}
|
|
14249
|
+
]
|
|
14250
|
+
};
|
|
14251
|
+
return [commonInjection, ...stackSpecific[stack] ?? []];
|
|
14252
|
+
}
|
|
14253
|
+
getConfigPatterns(stack) {
|
|
14254
|
+
const commonConfig = [
|
|
14255
|
+
{
|
|
14256
|
+
id: "insecure-tls",
|
|
14257
|
+
name: "Insecure TLS Configuration",
|
|
14258
|
+
severity: "high",
|
|
14259
|
+
description: "Detects disabled TLS verification or weak TLS settings",
|
|
14260
|
+
patterns: [
|
|
14261
|
+
/rejectUnauthorized\s*[:=]\s*false/g,
|
|
14262
|
+
/secure\s*[:=]\s*false/g,
|
|
14263
|
+
/ssl\s*[:=]\s*false/g,
|
|
14264
|
+
/TLS\s*\[\s*['"]?1\.0['"]?\]/gi,
|
|
14265
|
+
/InsecureRequestWarning\.disable/g
|
|
14266
|
+
],
|
|
14267
|
+
fileExtensions: [".ts", ".js", ".py", ".go", ".java"],
|
|
14268
|
+
falsePositiveMarkers: ["NODE_TLS_REJECT_UNAUTHORIZED"],
|
|
14269
|
+
remediation: "Always verify TLS certificates in production. Use proper certificate stores."
|
|
14270
|
+
},
|
|
14271
|
+
{
|
|
14272
|
+
id: "debug-enabled",
|
|
14273
|
+
name: "Debug Mode Enabled",
|
|
14274
|
+
severity: "medium",
|
|
14275
|
+
description: "Detects debug flags that may expose sensitive information",
|
|
14276
|
+
patterns: [
|
|
14277
|
+
/debug\s*[:=]\s*true/g,
|
|
14278
|
+
/DEBUG\s*[:=]\s*true/g,
|
|
14279
|
+
/development\s*mode/g
|
|
14280
|
+
],
|
|
14281
|
+
fileExtensions: [".ts", ".js", ".py", ".env", ".json"],
|
|
14282
|
+
falsePositiveMarkers: ['process.env.NODE_ENV !== "production"', "if (process.env.DEBUG)"],
|
|
14283
|
+
remediation: "Disable debug mode in production. Use proper log levels."
|
|
14284
|
+
}
|
|
14285
|
+
];
|
|
14286
|
+
return commonConfig;
|
|
14287
|
+
}
|
|
14288
|
+
getTargetFilesForStack(techStack) {
|
|
14289
|
+
const filesByStack = {
|
|
14290
|
+
nodejs: [
|
|
14291
|
+
"**/*.ts",
|
|
14292
|
+
"**/*.js",
|
|
14293
|
+
"**/*.json",
|
|
14294
|
+
"**/.env*",
|
|
14295
|
+
"**/package.json",
|
|
14296
|
+
"**/tsconfig.json"
|
|
14297
|
+
],
|
|
14298
|
+
python: [
|
|
14299
|
+
"**/*.py",
|
|
14300
|
+
"**/requirements*.txt",
|
|
14301
|
+
"**/setup.py",
|
|
14302
|
+
"**/pyproject.toml",
|
|
14303
|
+
"**/.env*"
|
|
14304
|
+
],
|
|
14305
|
+
rust: ["**/*.rs", "**/Cargo.toml", "**/Cargo.lock"],
|
|
14306
|
+
go: ["**/*.go", "**/go.mod", "**/go.sum"],
|
|
14307
|
+
java: ["**/*.java", "**/pom.xml", "**/build.gradle", "**/*.properties"],
|
|
14308
|
+
dotnet: ["**/*.cs", "**/*.csproj", "**/*.config", "**/appsettings.json"],
|
|
14309
|
+
php: ["**/*.php", "**/.env*", "**/composer.json"],
|
|
14310
|
+
ruby: ["**/*.rb", "**/Gemfile", "**/.env*"],
|
|
14311
|
+
cpp: ["**/*.cpp", "**/*.hpp", "**/CMakeLists.txt"],
|
|
14312
|
+
c: ["**/*.c", "**/*.h"],
|
|
14313
|
+
kotlin: ["**/*.kt", "**/*.kts", "**/build.gradle.kts"],
|
|
14314
|
+
swift: ["**/*.swift", "**/Package.swift"],
|
|
14315
|
+
unknown: ["**/*"]
|
|
14316
|
+
};
|
|
14317
|
+
return filesByStack[techStack.stack] || filesByStack.unknown;
|
|
14318
|
+
}
|
|
14319
|
+
buildSkillContent(techStack, patterns) {
|
|
14320
|
+
const lines = [
|
|
14321
|
+
"---",
|
|
14322
|
+
`name: security-scanner-${techStack.stack}`,
|
|
14323
|
+
`description: |`,
|
|
14324
|
+
` Auto-generated security scanner for ${techStack.stack} projects.`,
|
|
14325
|
+
` Scans for secrets, injection vectors, and configuration issues.`,
|
|
14326
|
+
`version: 1.0.0`,
|
|
14327
|
+
"---",
|
|
14328
|
+
"",
|
|
14329
|
+
`# Security Scanner \u2014 ${techStack.stack.toUpperCase()}`,
|
|
14330
|
+
"",
|
|
14331
|
+
`Scans ${techStack.stack} codebase for security vulnerabilities.`,
|
|
14332
|
+
"",
|
|
14333
|
+
"## Scan Targets",
|
|
14334
|
+
"",
|
|
14335
|
+
"### Code Vulnerabilities",
|
|
14336
|
+
patterns.filter((p) => p.fileExtensions.some((ext) => [".ts", ".js", ".py", ".go", ".java", ".cs", ".rs"].includes(ext))).map((p) => `- **${p.name}** (${p.severity}): ${p.description}`).join("\n"),
|
|
14337
|
+
"",
|
|
14338
|
+
"### Configuration Issues",
|
|
14339
|
+
patterns.filter((p) => p.fileExtensions.some((ext) => [".json", ".yaml", ".yml", ".env", ".config"].includes(ext))).map((p) => `- **${p.name}** (${p.severity}): ${p.description}`).join("\n"),
|
|
14340
|
+
"",
|
|
14341
|
+
"## Severity Levels",
|
|
14342
|
+
"",
|
|
14343
|
+
"- **CRITICAL**: Remote code execution, SQL injection, hardcoded secrets",
|
|
14344
|
+
"- **HIGH**: Command injection, XXE, authentication bypass",
|
|
14345
|
+
"- **MEDIUM**: Information disclosure, weak crypto, debug mode",
|
|
14346
|
+
"- **LOW**: Code quality issues, missing headers",
|
|
14347
|
+
"",
|
|
14348
|
+
"## Report Format",
|
|
14349
|
+
"",
|
|
14350
|
+
"```",
|
|
14351
|
+
"## Security Scan Report",
|
|
14352
|
+
"",
|
|
14353
|
+
"### CRITICAL",
|
|
14354
|
+
"1. **[CRITICAL]** `file:line` \u2014 Description",
|
|
14355
|
+
" ```",
|
|
14356
|
+
" // vulnerable code",
|
|
14357
|
+
" ```",
|
|
14358
|
+
" **Remediation**: Fix description",
|
|
14359
|
+
"",
|
|
14360
|
+
"### Summary",
|
|
14361
|
+
"| Severity | Count |",
|
|
14362
|
+
"|----------|-------|",
|
|
14363
|
+
"| Critical | X |",
|
|
14364
|
+
"| High | X |",
|
|
14365
|
+
"| Medium | X |",
|
|
14366
|
+
"| Low | X |",
|
|
14367
|
+
"```",
|
|
14368
|
+
"",
|
|
14369
|
+
"## Remediation",
|
|
14370
|
+
"",
|
|
14371
|
+
patterns.map((p) => `- **${p.name}**: ${p.remediation}`).join("\n")
|
|
14372
|
+
];
|
|
14373
|
+
return {
|
|
14374
|
+
type: "skill",
|
|
14375
|
+
content: lines.join("\n")
|
|
14376
|
+
};
|
|
14377
|
+
}
|
|
14378
|
+
calculateConfidence(techStack) {
|
|
14379
|
+
let confidence = 0.7;
|
|
14380
|
+
if (techStack.dependencies.length > 0) confidence += 0.1;
|
|
14381
|
+
if (techStack.manifestFile) confidence += 0.1;
|
|
14382
|
+
if (techStack.packageManager !== "unknown") confidence += 0.1;
|
|
14383
|
+
return Math.min(confidence, 1);
|
|
14384
|
+
}
|
|
14385
|
+
};
|
|
14386
|
+
var defaultSkillGenerator = new SkillGenerator();
|
|
14387
|
+
var DEFAULT_SCAN_OPTIONS = {
|
|
14388
|
+
includeSecrets: true,
|
|
14389
|
+
includeInjection: true,
|
|
14390
|
+
includeConfig: true,
|
|
14391
|
+
includeDependencies: true,
|
|
14392
|
+
excludePaths: ["node_modules", "dist", ".git", "coverage", "build", "target"],
|
|
14393
|
+
fileExtensions: [],
|
|
14394
|
+
depth: "standard"
|
|
14395
|
+
};
|
|
14396
|
+
var SecurityScanner = class {
|
|
14397
|
+
options;
|
|
14398
|
+
constructor(options = {}) {
|
|
14399
|
+
this.options = { ...DEFAULT_SCAN_OPTIONS, ...options };
|
|
14400
|
+
}
|
|
14401
|
+
async scan(projectRoot, skill, techStack) {
|
|
14402
|
+
const startTime = Date.now();
|
|
14403
|
+
const findings = [];
|
|
14404
|
+
const errors = [];
|
|
14405
|
+
let scannedFiles = 0;
|
|
14406
|
+
const targetExtensions = this.getTargetExtensions(skill, techStack);
|
|
14407
|
+
const files = await this.gatherFiles(projectRoot, targetExtensions);
|
|
14408
|
+
for (const file of files) {
|
|
14409
|
+
try {
|
|
14410
|
+
const content = await readFile(file, "utf-8");
|
|
14411
|
+
const fileFindings = this.scanFile(content, file, skill.patterns);
|
|
14412
|
+
findings.push(...fileFindings);
|
|
14413
|
+
scannedFiles++;
|
|
14414
|
+
} catch (err) {
|
|
14415
|
+
errors.push(`Failed to read ${file}: ${err}`);
|
|
14416
|
+
}
|
|
14417
|
+
}
|
|
14418
|
+
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
14419
|
+
findings.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
|
|
14420
|
+
const summary = this.calculateSummary(findings);
|
|
14421
|
+
const scanDurationMs = Date.now() - startTime;
|
|
14422
|
+
return {
|
|
14423
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14424
|
+
projectRoot,
|
|
14425
|
+
techStack,
|
|
14426
|
+
findings,
|
|
14427
|
+
summary,
|
|
14428
|
+
scannedFiles,
|
|
14429
|
+
scanDurationMs,
|
|
14430
|
+
errors
|
|
14431
|
+
};
|
|
14432
|
+
}
|
|
14433
|
+
async gatherFiles(root, extensions) {
|
|
14434
|
+
const files = [];
|
|
14435
|
+
const maxDepth = this.options.depth === "quick" ? 2 : this.options.depth === "deep" ? 20 : 5;
|
|
14436
|
+
await this.gatherFilesRecursive(root, files, extensions, 0, maxDepth);
|
|
14437
|
+
return files;
|
|
14438
|
+
}
|
|
14439
|
+
async gatherFilesRecursive(dir, files, extensions, currentDepth, maxDepth) {
|
|
14440
|
+
if (currentDepth > maxDepth) return;
|
|
14441
|
+
try {
|
|
14442
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
14443
|
+
for (const entry of entries) {
|
|
14444
|
+
const fullPath = join(dir, entry.name);
|
|
14445
|
+
if (entry.isDirectory()) {
|
|
14446
|
+
if (this.shouldExclude(entry.name)) continue;
|
|
14447
|
+
await this.gatherFilesRecursive(fullPath, files, extensions, currentDepth + 1, maxDepth);
|
|
14448
|
+
} else if (entry.isFile()) {
|
|
14449
|
+
if (extensions.length === 0 || extensions.includes(extname(entry.name))) {
|
|
14450
|
+
files.push(fullPath);
|
|
14451
|
+
}
|
|
14452
|
+
}
|
|
14453
|
+
}
|
|
14454
|
+
} catch {
|
|
14455
|
+
}
|
|
14456
|
+
}
|
|
14457
|
+
shouldExclude(name) {
|
|
14458
|
+
return this.options.excludePaths.some(
|
|
14459
|
+
(exclude) => name === exclude || name.startsWith(exclude + "/") || name.startsWith(exclude + "\\")
|
|
14460
|
+
);
|
|
14461
|
+
}
|
|
14462
|
+
getTargetExtensions(skill, _techStack) {
|
|
14463
|
+
if (this.options.fileExtensions.length > 0) {
|
|
14464
|
+
return this.options.fileExtensions;
|
|
14465
|
+
}
|
|
14466
|
+
const extensions = /* @__PURE__ */ new Set();
|
|
14467
|
+
for (const pattern of skill.patterns) {
|
|
14468
|
+
for (const ext of pattern.fileExtensions) {
|
|
14469
|
+
if (ext.startsWith(".")) {
|
|
14470
|
+
extensions.add(ext.toLowerCase());
|
|
14471
|
+
}
|
|
14472
|
+
}
|
|
14473
|
+
}
|
|
14474
|
+
return [...extensions];
|
|
14475
|
+
}
|
|
14476
|
+
scanFile(content, filePath, patterns) {
|
|
14477
|
+
const findings = [];
|
|
14478
|
+
const lines = content.split("\n");
|
|
14479
|
+
for (const pattern of patterns) {
|
|
14480
|
+
if (!this.matchesCategory(pattern)) continue;
|
|
14481
|
+
for (const regex of pattern.patterns) {
|
|
14482
|
+
regex.lastIndex = 0;
|
|
14483
|
+
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
|
|
14484
|
+
const line = lines[lineNum];
|
|
14485
|
+
if (!line) continue;
|
|
14486
|
+
if (regex.test(line)) {
|
|
14487
|
+
if (this.isFalsePositive(line, pattern.falsePositiveMarkers)) {
|
|
14488
|
+
continue;
|
|
14489
|
+
}
|
|
14490
|
+
findings.push({
|
|
14491
|
+
id: `${pattern.id}-${filePath}-${lineNum}`,
|
|
14492
|
+
severity: pattern.severity,
|
|
14493
|
+
category: this.getCategoryFromPattern(pattern),
|
|
14494
|
+
title: pattern.name,
|
|
14495
|
+
description: pattern.description,
|
|
14496
|
+
file: relative(process.cwd(), filePath),
|
|
14497
|
+
line: lineNum + 1,
|
|
14498
|
+
snippet: line.trim(),
|
|
14499
|
+
remediation: pattern.remediation,
|
|
14500
|
+
patternId: pattern.id,
|
|
14501
|
+
confidence: "high"
|
|
14502
|
+
});
|
|
14503
|
+
}
|
|
14504
|
+
}
|
|
14505
|
+
}
|
|
14506
|
+
}
|
|
14507
|
+
return findings;
|
|
14508
|
+
}
|
|
14509
|
+
matchesCategory(pattern) {
|
|
14510
|
+
if (pattern.id.includes("secret") || pattern.id.includes("npmrc") || pattern.id.includes("env")) {
|
|
14511
|
+
return this.options.includeSecrets;
|
|
14512
|
+
}
|
|
14513
|
+
if (pattern.id.includes("injection") || pattern.id.includes("sql") || pattern.id.includes("command") || pattern.id.includes("eval")) {
|
|
14514
|
+
return this.options.includeInjection;
|
|
14515
|
+
}
|
|
14516
|
+
if (pattern.id.includes("config") || pattern.id.includes("tls") || pattern.id.includes("debug")) {
|
|
14517
|
+
return this.options.includeConfig;
|
|
14518
|
+
}
|
|
14519
|
+
return true;
|
|
14520
|
+
}
|
|
14521
|
+
getCategoryFromPattern(pattern) {
|
|
14522
|
+
if (pattern.id.includes("secret")) return "secrets";
|
|
14523
|
+
if (pattern.id.includes("injection") || pattern.id.includes("sql") || pattern.id.includes("command")) return "injection";
|
|
14524
|
+
if (pattern.id.includes("config") || pattern.id.includes("tls") || pattern.id.includes("debug")) return "config";
|
|
14525
|
+
if (pattern.id.includes("dependency")) return "dependency";
|
|
14526
|
+
return "filesystem";
|
|
14527
|
+
}
|
|
14528
|
+
isFalsePositive(line, markers) {
|
|
14529
|
+
for (const marker of markers) {
|
|
14530
|
+
if (line.includes(marker)) {
|
|
14531
|
+
return true;
|
|
14532
|
+
}
|
|
14533
|
+
}
|
|
14534
|
+
return false;
|
|
14535
|
+
}
|
|
14536
|
+
calculateSummary(findings) {
|
|
14537
|
+
return {
|
|
14538
|
+
critical: findings.filter((f) => f.severity === "critical").length,
|
|
14539
|
+
high: findings.filter((f) => f.severity === "high").length,
|
|
14540
|
+
medium: findings.filter((f) => f.severity === "medium").length,
|
|
14541
|
+
low: findings.filter((f) => f.severity === "low").length,
|
|
14542
|
+
total: findings.length
|
|
14543
|
+
};
|
|
14544
|
+
}
|
|
14545
|
+
};
|
|
14546
|
+
var defaultSecurityScanner = new SecurityScanner();
|
|
14547
|
+
var DEFAULT_REPORT_OPTIONS = {
|
|
14548
|
+
outputDir: "security-reports",
|
|
14549
|
+
format: "markdown",
|
|
14550
|
+
includeRemediation: true,
|
|
14551
|
+
groupBySeverity: true
|
|
14552
|
+
};
|
|
14553
|
+
var ReportGenerator = class {
|
|
14554
|
+
options;
|
|
14555
|
+
constructor(options = {}) {
|
|
14556
|
+
this.options = { ...DEFAULT_REPORT_OPTIONS, ...options };
|
|
14557
|
+
}
|
|
14558
|
+
async generate(scanResult) {
|
|
14559
|
+
await this.ensureOutputDir();
|
|
14560
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
14561
|
+
const filename = `security-report-${timestamp}.${this.options.format}`;
|
|
14562
|
+
const filepath = join(this.options.outputDir, filename);
|
|
14563
|
+
let content;
|
|
14564
|
+
switch (this.options.format) {
|
|
14565
|
+
case "json":
|
|
14566
|
+
content = this.generateJson(scanResult);
|
|
14567
|
+
break;
|
|
14568
|
+
case "html":
|
|
14569
|
+
content = this.generateHtml(scanResult);
|
|
14570
|
+
break;
|
|
14571
|
+
default:
|
|
14572
|
+
content = this.generateMarkdown(scanResult);
|
|
14573
|
+
}
|
|
14574
|
+
await writeFile(filepath, content, "utf-8");
|
|
14575
|
+
return filepath;
|
|
14576
|
+
}
|
|
14577
|
+
async ensureOutputDir() {
|
|
14578
|
+
try {
|
|
14579
|
+
await stat(this.options.outputDir);
|
|
14580
|
+
} catch {
|
|
14581
|
+
const { mkdir: mkdir10 } = await import('fs/promises');
|
|
14582
|
+
await mkdir10(this.options.outputDir, { recursive: true });
|
|
14583
|
+
}
|
|
14584
|
+
}
|
|
14585
|
+
generateMarkdown(result) {
|
|
14586
|
+
const lines = [];
|
|
14587
|
+
lines.push("# Security Scan Report");
|
|
14588
|
+
lines.push("");
|
|
14589
|
+
lines.push(`**Generated:** ${new Date(result.timestamp).toLocaleString()}`);
|
|
14590
|
+
lines.push(`**Project:** ${result.projectRoot}`);
|
|
14591
|
+
lines.push(`**Tech Stack:** ${result.techStack.stack} (${result.techStack.packageManager})`);
|
|
14592
|
+
lines.push(`**Scanned Files:** ${result.scannedFiles}`);
|
|
14593
|
+
lines.push(`**Scan Duration:** ${result.scanDurationMs}ms`);
|
|
14594
|
+
lines.push("");
|
|
14595
|
+
lines.push("## Summary");
|
|
14596
|
+
lines.push("");
|
|
14597
|
+
lines.push("| Severity | Count |");
|
|
14598
|
+
lines.push("|----------|-------|");
|
|
14599
|
+
lines.push(`| \u{1F534} Critical | ${result.summary.critical} |`);
|
|
14600
|
+
lines.push(`| \u{1F7E0} High | ${result.summary.high} |`);
|
|
14601
|
+
lines.push(`| \u{1F7E1} Medium | ${result.summary.medium} |`);
|
|
14602
|
+
lines.push(`| \u{1F7E2} Low | ${result.summary.low} |`);
|
|
14603
|
+
lines.push(`| **Total** | **${result.summary.total}** |`);
|
|
14604
|
+
lines.push("");
|
|
14605
|
+
if (this.options.groupBySeverity) {
|
|
14606
|
+
const severityGroups = this.groupBySeverity(result.findings);
|
|
14607
|
+
for (const severity of ["critical", "high", "medium", "low"]) {
|
|
14608
|
+
const findings = severityGroups[severity] ?? [];
|
|
14609
|
+
if (findings.length === 0) continue;
|
|
14610
|
+
const emoji = severity === "critical" ? "\u{1F534}" : severity === "high" ? "\u{1F7E0}" : severity === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
14611
|
+
lines.push(`## ${emoji} ${severity.toUpperCase()} (${findings.length})`);
|
|
14612
|
+
lines.push("");
|
|
14613
|
+
for (const finding of findings) {
|
|
14614
|
+
lines.push(this.formatFinding(finding));
|
|
14615
|
+
}
|
|
14616
|
+
lines.push("");
|
|
14617
|
+
}
|
|
14618
|
+
} else {
|
|
14619
|
+
const categoryGroups = this.groupByCategory(result.findings);
|
|
14620
|
+
for (const [category, findings] of Object.entries(categoryGroups)) {
|
|
14621
|
+
lines.push(`## ${category.toUpperCase()} (${findings.length})`);
|
|
14622
|
+
lines.push("");
|
|
14623
|
+
for (const finding of findings) {
|
|
14624
|
+
lines.push(this.formatFinding(finding));
|
|
14625
|
+
}
|
|
14626
|
+
lines.push("");
|
|
14627
|
+
}
|
|
14628
|
+
}
|
|
14629
|
+
if (result.errors.length > 0) {
|
|
14630
|
+
lines.push("## Scan Errors");
|
|
14631
|
+
lines.push("");
|
|
14632
|
+
for (const error of result.errors) {
|
|
14633
|
+
lines.push(`- ${error}`);
|
|
14634
|
+
}
|
|
14635
|
+
lines.push("");
|
|
14636
|
+
}
|
|
14637
|
+
lines.push("---");
|
|
14638
|
+
lines.push(`*Report generated by WrongStack Security Scanner*`);
|
|
14639
|
+
return lines.join("\n");
|
|
14640
|
+
}
|
|
14641
|
+
formatFinding(finding) {
|
|
14642
|
+
const lines = [];
|
|
14643
|
+
lines.push(`### ${finding.title}`);
|
|
14644
|
+
lines.push("");
|
|
14645
|
+
lines.push(`**File:** \`${finding.file}${finding.line ? `:${finding.line}` : ""}\``);
|
|
14646
|
+
lines.push(`**Severity:** ${finding.severity.toUpperCase()}`);
|
|
14647
|
+
lines.push(`**Category:** ${finding.category}`);
|
|
14648
|
+
lines.push("");
|
|
14649
|
+
if (finding.snippet) {
|
|
14650
|
+
lines.push("```");
|
|
14651
|
+
lines.push(finding.snippet);
|
|
14652
|
+
lines.push("```");
|
|
14653
|
+
lines.push("");
|
|
14654
|
+
}
|
|
14655
|
+
if (this.options.includeRemediation) {
|
|
14656
|
+
lines.push(`**Remediation:** ${finding.remediation}`);
|
|
14657
|
+
lines.push("");
|
|
14658
|
+
}
|
|
14659
|
+
lines.push("---");
|
|
14660
|
+
lines.push("");
|
|
14661
|
+
return lines.join("\n");
|
|
14662
|
+
}
|
|
14663
|
+
groupBySeverity(findings) {
|
|
14664
|
+
const groups = {
|
|
14665
|
+
critical: [],
|
|
14666
|
+
high: [],
|
|
14667
|
+
medium: [],
|
|
14668
|
+
low: []
|
|
14669
|
+
};
|
|
14670
|
+
for (const finding of findings) {
|
|
14671
|
+
const group = groups[finding.severity];
|
|
14672
|
+
if (group) {
|
|
14673
|
+
group.push(finding);
|
|
14674
|
+
}
|
|
14675
|
+
}
|
|
14676
|
+
return groups;
|
|
14677
|
+
}
|
|
14678
|
+
groupByCategory(findings) {
|
|
14679
|
+
const groups = {};
|
|
14680
|
+
for (const finding of findings) {
|
|
14681
|
+
const group = groups[finding.category] ?? (groups[finding.category] = []);
|
|
14682
|
+
group.push(finding);
|
|
14683
|
+
}
|
|
14684
|
+
return groups;
|
|
14685
|
+
}
|
|
14686
|
+
generateJson(result) {
|
|
14687
|
+
return JSON.stringify(
|
|
14688
|
+
{
|
|
14689
|
+
meta: {
|
|
14690
|
+
timestamp: result.timestamp,
|
|
14691
|
+
projectRoot: result.projectRoot,
|
|
14692
|
+
techStack: result.techStack,
|
|
14693
|
+
scannedFiles: result.scannedFiles,
|
|
14694
|
+
scanDurationMs: result.scanDurationMs
|
|
14695
|
+
},
|
|
14696
|
+
summary: result.summary,
|
|
14697
|
+
findings: result.findings,
|
|
14698
|
+
errors: result.errors
|
|
14699
|
+
},
|
|
14700
|
+
null,
|
|
14701
|
+
2
|
|
14702
|
+
);
|
|
14703
|
+
}
|
|
14704
|
+
generateHtml(result) {
|
|
14705
|
+
const severityColors = {
|
|
14706
|
+
critical: "#dc2626",
|
|
14707
|
+
high: "#ea580c",
|
|
14708
|
+
medium: "#ca8a04",
|
|
14709
|
+
low: "#16a34a"
|
|
14710
|
+
};
|
|
14711
|
+
const rows = result.findings.map(
|
|
14712
|
+
(f) => `
|
|
14713
|
+
<tr>
|
|
14714
|
+
<td style="color: ${severityColors[f.severity]}; font-weight: bold;">${f.severity.toUpperCase()}</td>
|
|
14715
|
+
<td>${f.category}</td>
|
|
14716
|
+
<td>${f.title}</td>
|
|
14717
|
+
<td><code>${f.file}${f.line ? `:${f.line}` : ""}</code></td>
|
|
14718
|
+
<td>${f.remediation}</td>
|
|
14719
|
+
</tr>
|
|
14720
|
+
`
|
|
14721
|
+
).join("");
|
|
14722
|
+
return `
|
|
14723
|
+
<!DOCTYPE html>
|
|
14724
|
+
<html>
|
|
14725
|
+
<head>
|
|
14726
|
+
<title>Security Scan Report - ${new Date(result.timestamp).toLocaleDateString()}</title>
|
|
14727
|
+
<style>
|
|
14728
|
+
body { font-family: system-ui, sans-serif; margin: 2rem; }
|
|
14729
|
+
table { border-collapse: collapse; width: 100%; }
|
|
14730
|
+
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
|
|
14731
|
+
th { background: #f5f5f5; }
|
|
14732
|
+
code { background: #f0f0f0; padding: 2px 4px; }
|
|
14733
|
+
</style>
|
|
14734
|
+
</head>
|
|
14735
|
+
<body>
|
|
14736
|
+
<h1>Security Scan Report</h1>
|
|
14737
|
+
<p><strong>Generated:</strong> ${new Date(result.timestamp).toLocaleString()}</p>
|
|
14738
|
+
<p><strong>Project:</strong> ${result.projectRoot}</p>
|
|
14739
|
+
<p><strong>Tech Stack:</strong> ${result.techStack.stack} (${result.techStack.packageManager})</p>
|
|
14740
|
+
|
|
14741
|
+
<h2>Summary</h2>
|
|
14742
|
+
<ul>
|
|
14743
|
+
<li>\u{1F534} Critical: ${result.summary.critical}</li>
|
|
14744
|
+
<li>\u{1F7E0} High: ${result.summary.high}</li>
|
|
14745
|
+
<li>\u{1F7E1} Medium: ${result.summary.medium}</li>
|
|
14746
|
+
<li>\u{1F7E2} Low: ${result.summary.low}</li>
|
|
14747
|
+
</ul>
|
|
14748
|
+
|
|
14749
|
+
<h2>Findings</h2>
|
|
14750
|
+
<table>
|
|
14751
|
+
<tr>
|
|
14752
|
+
<th>Severity</th>
|
|
14753
|
+
<th>Category</th>
|
|
14754
|
+
<th>Title</th>
|
|
14755
|
+
<th>Location</th>
|
|
14756
|
+
<th>Remediation</th>
|
|
14757
|
+
</tr>
|
|
14758
|
+
${rows}
|
|
14759
|
+
</table>
|
|
14760
|
+
</body>
|
|
14761
|
+
</html>
|
|
14762
|
+
`.trim();
|
|
14763
|
+
}
|
|
14764
|
+
};
|
|
14765
|
+
var defaultReportGenerator = new ReportGenerator();
|
|
14766
|
+
var DEFAULT_OPTIONS2 = {
|
|
14767
|
+
gitignorePath: ".gitignore",
|
|
14768
|
+
entries: ["security-reports/", "security-reports/*"]
|
|
14769
|
+
};
|
|
14770
|
+
var GitignoreUpdater = class {
|
|
14771
|
+
options;
|
|
14772
|
+
constructor(options = {}) {
|
|
14773
|
+
this.options = { ...DEFAULT_OPTIONS2, ...options };
|
|
14774
|
+
}
|
|
14775
|
+
async update() {
|
|
14776
|
+
const added = [];
|
|
14777
|
+
const existing = [];
|
|
14778
|
+
const errors = [];
|
|
14779
|
+
try {
|
|
14780
|
+
const content = await readFile(this.options.gitignorePath, "utf-8");
|
|
14781
|
+
const lines = new Set(content.split(/\r?\n/).map((l) => l.trim()));
|
|
14782
|
+
for (const entry of this.options.entries) {
|
|
14783
|
+
if (lines.has(entry)) {
|
|
14784
|
+
existing.push(entry);
|
|
14785
|
+
} else {
|
|
14786
|
+
lines.add(entry);
|
|
14787
|
+
added.push(entry);
|
|
14788
|
+
}
|
|
14789
|
+
}
|
|
14790
|
+
if (added.length > 0) {
|
|
14791
|
+
const newContent = [...lines].filter(Boolean).join("\n") + "\n";
|
|
14792
|
+
await writeFile(this.options.gitignorePath, newContent, "utf-8");
|
|
14793
|
+
}
|
|
14794
|
+
} catch (err) {
|
|
14795
|
+
if (err.code === "ENOENT") {
|
|
14796
|
+
const content = this.options.entries.join("\n") + "\n";
|
|
14797
|
+
await writeFile(this.options.gitignorePath, content, "utf-8");
|
|
14798
|
+
added.push(...this.options.entries);
|
|
14799
|
+
} else {
|
|
14800
|
+
errors.push(`Failed to update .gitignore: ${err}`);
|
|
14801
|
+
}
|
|
14802
|
+
}
|
|
14803
|
+
return { added, existing, errors };
|
|
14804
|
+
}
|
|
14805
|
+
async isEntryIgnored(entry) {
|
|
14806
|
+
try {
|
|
14807
|
+
const content = await readFile(this.options.gitignorePath, "utf-8");
|
|
14808
|
+
const lines = content.split(/\r?\n/).map((l) => l.trim());
|
|
14809
|
+
return lines.includes(entry);
|
|
14810
|
+
} catch {
|
|
14811
|
+
return false;
|
|
14812
|
+
}
|
|
14813
|
+
}
|
|
14814
|
+
};
|
|
14815
|
+
var defaultGitignoreUpdater = new GitignoreUpdater();
|
|
14816
|
+
var NETWORK_ERR_RE2 = /ECONN|ETIMEDOUT|ETIME|ENOTFOUND|EAI_AGAIN|fetch failed/i;
|
|
14817
|
+
var SecurityScannerOrchestrator = class {
|
|
14818
|
+
constructor(retryPolicy, errorHandler) {
|
|
14819
|
+
this.retryPolicy = retryPolicy;
|
|
14820
|
+
this.errorHandler = errorHandler;
|
|
14821
|
+
}
|
|
14822
|
+
retryPolicy;
|
|
14823
|
+
errorHandler;
|
|
14824
|
+
detector = defaultTechStackDetector;
|
|
14825
|
+
reportGenerator = defaultReportGenerator;
|
|
14826
|
+
gitignoreUpdater = defaultGitignoreUpdater;
|
|
14827
|
+
/**
|
|
14828
|
+
* Wraps provider.complete with retry logic using the injected RetryPolicy.
|
|
14829
|
+
*/
|
|
14830
|
+
async completeWithRetry(provider, request, abortController, attempt = 0) {
|
|
14831
|
+
const signal = abortController.signal;
|
|
14832
|
+
try {
|
|
14833
|
+
return await provider.complete(request, { signal });
|
|
14834
|
+
} catch (err) {
|
|
14835
|
+
if (signal.aborted) throw err;
|
|
14836
|
+
const isProviderErr = err instanceof ProviderError;
|
|
14837
|
+
const policy = this.retryPolicy;
|
|
14838
|
+
const errAsErr = isProviderErr ? err : err instanceof Error ? err : new Error(String(err));
|
|
14839
|
+
if (!policy || !isProviderErr && !NETWORK_ERR_RE2.test(errAsErr.message)) {
|
|
14840
|
+
throw err;
|
|
14841
|
+
}
|
|
14842
|
+
const canRetry = policy.shouldRetry(errAsErr, attempt);
|
|
14843
|
+
if (!canRetry) throw err;
|
|
14844
|
+
if (this.errorHandler) {
|
|
14845
|
+
const classified = this.errorHandler.classify(err);
|
|
14846
|
+
if (!classified.retryable) throw err;
|
|
14847
|
+
}
|
|
14848
|
+
const delay = Math.round(policy.delayMs(attempt));
|
|
14849
|
+
const status = isProviderErr ? err.status : 0;
|
|
14850
|
+
console.warn(`[SecurityScanner] retry ${attempt + 1} after ${delay}ms (status=${status}) \u2014 ${errAsErr.message}`);
|
|
14851
|
+
await new Promise((resolve5) => setTimeout(resolve5, delay));
|
|
14852
|
+
return this.completeWithRetry(provider, request, abortController, attempt + 1);
|
|
14853
|
+
}
|
|
14854
|
+
}
|
|
14855
|
+
/**
|
|
14856
|
+
* Run full security scan with LLM assistance.
|
|
14857
|
+
* Accepts a full Context (active agent run) or just provider+model (pre-agent session).
|
|
14858
|
+
*/
|
|
14859
|
+
async run(ctx, options) {
|
|
14860
|
+
const { projectRoot, reportOptions, skipGitignore, model: explicitModel } = options;
|
|
14861
|
+
const provider = "provider" in ctx ? ctx.provider : ctx;
|
|
14862
|
+
const model = explicitModel ?? ("model" in ctx ? ctx.model : void 0);
|
|
14863
|
+
const detectionResult = await this.detector.detect(projectRoot);
|
|
14864
|
+
if (detectionResult.detectedStacks.length === 0) {
|
|
14865
|
+
throw new Error(`No supported tech stack detected in ${projectRoot}`);
|
|
14866
|
+
}
|
|
14867
|
+
const techStack = detectionResult.detectedStacks[0];
|
|
14868
|
+
const generatedSkill = await this.generateSkillLLM(provider, model, projectRoot, techStack);
|
|
14869
|
+
const scanResult = await this.scanWithLLM(provider, model, projectRoot, generatedSkill, techStack, options);
|
|
14870
|
+
const synthesizedReport = await this.synthesizeReportLLM(provider, model, projectRoot, techStack, scanResult);
|
|
14871
|
+
const reportPath = await this.writeReport(synthesizedReport, reportOptions);
|
|
14872
|
+
let gitignoreResult;
|
|
14873
|
+
if (!skipGitignore) {
|
|
14874
|
+
gitignoreResult = await this.gitignoreUpdater.update();
|
|
14875
|
+
}
|
|
14876
|
+
return {
|
|
14877
|
+
detectionResult,
|
|
14878
|
+
generatedSkill,
|
|
14879
|
+
scanResult,
|
|
14880
|
+
reportPath,
|
|
14881
|
+
synthesizedReport,
|
|
14882
|
+
gitignoreResult
|
|
14883
|
+
};
|
|
14884
|
+
}
|
|
14885
|
+
/**
|
|
14886
|
+
* Generate a project-specific security skill using LLM.
|
|
14887
|
+
* The LLM analyzes the project structure and creates tailored security patterns.
|
|
14888
|
+
*/
|
|
14889
|
+
async generateSkillLLM(provider, model, projectRoot, techStack) {
|
|
14890
|
+
const projectInfo = await this.gatherProjectInfo(projectRoot, techStack);
|
|
14891
|
+
const prompt = `You are a security expert generating a customized security scanning skill for a specific project.
|
|
14892
|
+
|
|
14893
|
+
Analyze the following project and generate a detailed security skill with:
|
|
14894
|
+
1. Project-specific vulnerability patterns based on the tech stack and structure
|
|
14895
|
+
2. Language/framework specific security concerns
|
|
14896
|
+
3. Common attack vectors for this type of application
|
|
14897
|
+
4. File patterns to scan
|
|
14898
|
+
|
|
14899
|
+
## Project Information:
|
|
14900
|
+
${projectInfo}
|
|
14901
|
+
|
|
14902
|
+
## Tech Stack:
|
|
14903
|
+
- Language: ${techStack.stack}
|
|
14904
|
+
- Package Manager: ${techStack.packageManager}
|
|
14905
|
+
- Manifest: ${techStack.manifestFile}
|
|
14906
|
+
|
|
14907
|
+
## Dependencies (first 20):
|
|
14908
|
+
${techStack.dependencies.slice(0, 20).map((d) => `- ${d.name}@${d.version}`).join("\n")}
|
|
14909
|
+
|
|
14910
|
+
## Your Task:
|
|
14911
|
+
Generate a JSON security skill with the following structure:
|
|
14912
|
+
{
|
|
14913
|
+
"name": "security-scanner-${techStack.stack}",
|
|
14914
|
+
"description": "Custom security scanner for this project",
|
|
14915
|
+
"techStack": "${techStack.stack}",
|
|
14916
|
+
"patterns": [
|
|
14917
|
+
{
|
|
14918
|
+
"id": "unique-pattern-id",
|
|
14919
|
+
"name": "Pattern Name",
|
|
14920
|
+
"severity": "critical|high|medium|low",
|
|
14921
|
+
"description": "What this detects",
|
|
14922
|
+
"fileExtensions": [".ts", ".js"],
|
|
14923
|
+
"remediation": "How to fix"
|
|
14924
|
+
}
|
|
14925
|
+
],
|
|
14926
|
+
"targetFiles": ["**/*.ts", "**/*.js"],
|
|
14927
|
+
"scanInstructions": "Detailed instructions for scanning this codebase"
|
|
14928
|
+
}
|
|
14929
|
+
|
|
14930
|
+
Focus on:
|
|
14931
|
+
- ${techStack.stack === "nodejs" ? "Node.js specific: eval, prototype pollution, npm script injection, express middleware issues, passport.js misconfigs" : ""}
|
|
14932
|
+
- ${techStack.stack === "python" ? "Python specific: pickle deserialization, SQL injection in ORMs, template injection, insecure Django/Flask settings" : ""}
|
|
14933
|
+
- Common: hardcoded secrets, SQL injection, command injection, XSS, path traversal, XXE
|
|
14934
|
+
|
|
14935
|
+
Return ONLY the JSON object, no markdown, no explanation.`;
|
|
14936
|
+
const request = {
|
|
14937
|
+
model: model ?? "unknown",
|
|
14938
|
+
system: [{ type: "text", text: "You are a security expert. Return ONLY valid JSON." }],
|
|
14939
|
+
messages: [{ role: "user", content: prompt }],
|
|
14940
|
+
maxTokens: 4096
|
|
14941
|
+
};
|
|
14942
|
+
try {
|
|
14943
|
+
const abortController = new AbortController();
|
|
14944
|
+
const response = await this.completeWithRetry(provider, request, abortController);
|
|
14945
|
+
const text = response.content.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
14946
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
14947
|
+
if (jsonMatch) {
|
|
14948
|
+
const sanitized = sanitizeJsonString(jsonMatch[0]) || jsonMatch[0];
|
|
14949
|
+
const skillData = JSON.parse(sanitized);
|
|
14950
|
+
return {
|
|
14951
|
+
name: skillData.name || `security-scanner-${techStack.stack}`,
|
|
14952
|
+
description: skillData.description || `Security scanner for ${techStack.stack}`,
|
|
14953
|
+
version: "1.0.0",
|
|
14954
|
+
techStack: techStack.stack,
|
|
14955
|
+
content: { type: "skill", content: JSON.stringify(skillData, null, 2) },
|
|
14956
|
+
patterns: skillData.patterns || [],
|
|
14957
|
+
metadata: {
|
|
14958
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14959
|
+
confidence: 0.85,
|
|
14960
|
+
targetFiles: skillData.targetFiles || []
|
|
14961
|
+
}
|
|
14962
|
+
};
|
|
14963
|
+
}
|
|
14964
|
+
} catch (err) {
|
|
14965
|
+
console.error("LLM skill generation failed, using fallback:", err);
|
|
14966
|
+
}
|
|
14967
|
+
return this.generateFallbackSkill(techStack);
|
|
14968
|
+
}
|
|
14969
|
+
/**
|
|
14970
|
+
* Scan code using LLM with the generated skill as context.
|
|
14971
|
+
* The LLM analyzes files and reports security findings.
|
|
14972
|
+
*/
|
|
14973
|
+
async scanWithLLM(provider, model, projectRoot, skill, techStack, options) {
|
|
14974
|
+
const startTime = Date.now();
|
|
14975
|
+
const findings = [];
|
|
14976
|
+
const errors = [];
|
|
14977
|
+
let scannedFiles = 0;
|
|
14978
|
+
const files = await this.gatherFiles(projectRoot, skill.metadata.targetFiles, options.scanOptions?.depth || "standard");
|
|
14979
|
+
const batchSize = 10;
|
|
14980
|
+
for (let i = 0; i < files.length; i += batchSize) {
|
|
14981
|
+
const batch = files.slice(i, i + batchSize);
|
|
14982
|
+
const batchFindings = await this.scanFileBatchLLM(provider, model, batch, skill, techStack);
|
|
14983
|
+
findings.push(...batchFindings);
|
|
14984
|
+
scannedFiles += batch.length;
|
|
14985
|
+
}
|
|
14986
|
+
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
14987
|
+
findings.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
|
|
14988
|
+
const summary = {
|
|
14989
|
+
critical: findings.filter((f) => f.severity === "critical").length,
|
|
14990
|
+
high: findings.filter((f) => f.severity === "high").length,
|
|
14991
|
+
medium: findings.filter((f) => f.severity === "medium").length,
|
|
14992
|
+
low: findings.filter((f) => f.severity === "low").length,
|
|
14993
|
+
total: findings.length
|
|
14994
|
+
};
|
|
14995
|
+
return {
|
|
14996
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14997
|
+
projectRoot,
|
|
14998
|
+
techStack,
|
|
14999
|
+
findings,
|
|
15000
|
+
summary,
|
|
15001
|
+
scannedFiles,
|
|
15002
|
+
scanDurationMs: Date.now() - startTime,
|
|
15003
|
+
errors
|
|
15004
|
+
};
|
|
15005
|
+
}
|
|
15006
|
+
/**
|
|
15007
|
+
* Scan a batch of files using LLM.
|
|
15008
|
+
*/
|
|
15009
|
+
async scanFileBatchLLM(provider, model, files, skill, techStack) {
|
|
15010
|
+
const fileContents = [];
|
|
15011
|
+
for (const file of files) {
|
|
15012
|
+
try {
|
|
15013
|
+
const content = await readFile(file, "utf-8");
|
|
15014
|
+
const relativePath = relative(process.cwd(), file);
|
|
15015
|
+
fileContents.push(`
|
|
15016
|
+
=== ${relativePath} ===
|
|
15017
|
+
${content.slice(0, 2e3)}`);
|
|
15018
|
+
} catch {
|
|
15019
|
+
}
|
|
15020
|
+
}
|
|
15021
|
+
if (fileContents.length === 0) return [];
|
|
15022
|
+
const prompt = `You are a security expert analyzing code for vulnerabilities.
|
|
15023
|
+
|
|
15024
|
+
## Security Patterns to Detect (from generated skill):
|
|
15025
|
+
${skill.patterns.map((p) => `- ${p.name} (${p.severity}): ${p.description}`).join("\n")}
|
|
15026
|
+
|
|
15027
|
+
## Files to Analyze:
|
|
15028
|
+
${fileContents.join("\n")}
|
|
15029
|
+
|
|
15030
|
+
## Your Task:
|
|
15031
|
+
Analyze each file for security vulnerabilities matching the patterns above.
|
|
15032
|
+
For each finding, provide:
|
|
15033
|
+
1. File path (relative path from === markers)
|
|
15034
|
+
2. Line number if identifiable
|
|
15035
|
+
3. Severity (critical/high/medium/low)
|
|
15036
|
+
4. Category (secrets/injection/config/dependency)
|
|
15037
|
+
5. Description of the issue
|
|
15038
|
+
6. Code snippet showing the vulnerability
|
|
15039
|
+
7. Remediation steps
|
|
15040
|
+
|
|
15041
|
+
Return a JSON array of findings:
|
|
15042
|
+
[
|
|
15043
|
+
{
|
|
15044
|
+
"file": "path/to/file.ts",
|
|
15045
|
+
"line": 42,
|
|
15046
|
+
"severity": "high",
|
|
15047
|
+
"category": "injection",
|
|
15048
|
+
"title": "SQL Injection Risk",
|
|
15049
|
+
"description": "...",
|
|
15050
|
+
"snippet": "actual code...",
|
|
15051
|
+
"remediation": "..."
|
|
15052
|
+
}
|
|
15053
|
+
]
|
|
15054
|
+
|
|
15055
|
+
Return ONLY the JSON array. If no issues found, return [].`;
|
|
15056
|
+
try {
|
|
15057
|
+
const request = {
|
|
15058
|
+
model: model ?? "unknown",
|
|
15059
|
+
system: [{ type: "text", text: "You are a security expert. Return ONLY valid JSON." }],
|
|
15060
|
+
messages: [{ role: "user", content: prompt }],
|
|
15061
|
+
maxTokens: 4096
|
|
15062
|
+
};
|
|
15063
|
+
const abortController = new AbortController();
|
|
15064
|
+
const response = await this.completeWithRetry(provider, request, abortController);
|
|
15065
|
+
const text = response.content.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
15066
|
+
const jsonMatch = text.match(/\[[\s\S]*\]/);
|
|
15067
|
+
if (jsonMatch) {
|
|
15068
|
+
const sanitized = sanitizeJsonString(jsonMatch[0]) || jsonMatch[0];
|
|
15069
|
+
const parsed = JSON.parse(sanitized);
|
|
15070
|
+
return parsed.map((item, idx) => ({
|
|
15071
|
+
id: `llm-finding-${idx}-${Date.now()}`,
|
|
15072
|
+
severity: item.severity,
|
|
15073
|
+
category: item.category || "injection",
|
|
15074
|
+
title: item.title,
|
|
15075
|
+
description: item.description,
|
|
15076
|
+
file: item.file,
|
|
15077
|
+
line: item.line,
|
|
15078
|
+
snippet: item.snippet,
|
|
15079
|
+
remediation: item.remediation,
|
|
15080
|
+
patternId: "llm-analysis",
|
|
15081
|
+
confidence: "high"
|
|
15082
|
+
}));
|
|
15083
|
+
}
|
|
15084
|
+
} catch (err) {
|
|
15085
|
+
console.error("LLM scan batch failed:", err);
|
|
15086
|
+
}
|
|
15087
|
+
return [];
|
|
15088
|
+
}
|
|
15089
|
+
/**
|
|
15090
|
+
* Synthesize a comprehensive security report using LLM.
|
|
15091
|
+
*/
|
|
15092
|
+
async synthesizeReportLLM(provider, model, projectRoot, techStack, scanResult) {
|
|
15093
|
+
const prompt = `You are a security expert writing a comprehensive security report.
|
|
15094
|
+
|
|
15095
|
+
## Scan Results:
|
|
15096
|
+
- Scanned Files: ${scanResult.scannedFiles}
|
|
15097
|
+
- Total Findings: ${scanResult.summary.total}
|
|
15098
|
+
- Critical: ${scanResult.summary.critical}
|
|
15099
|
+
- High: ${scanResult.summary.high}
|
|
15100
|
+
- Medium: ${scanResult.summary.medium}
|
|
15101
|
+
- Low: ${scanResult.summary.low}
|
|
15102
|
+
|
|
15103
|
+
## Detailed Findings:
|
|
15104
|
+
${scanResult.findings.map((f, i) => `
|
|
15105
|
+
${i + 1}. [${f.severity.toUpperCase()}] ${f.title}
|
|
15106
|
+
File: ${f.file}${f.line ? `:${f.line}` : ""}
|
|
15107
|
+
Category: ${f.category}
|
|
15108
|
+
Description: ${f.description}
|
|
15109
|
+
${f.snippet ? `Code: \`\`\`
|
|
15110
|
+
${f.snippet}
|
|
15111
|
+
\`\`\`` : ""}
|
|
15112
|
+
Remediation: ${f.remediation}
|
|
15113
|
+
`).join("\n")}
|
|
15114
|
+
|
|
15115
|
+
## Project:
|
|
15116
|
+
- Root: ${projectRoot}
|
|
15117
|
+
- Tech Stack: ${techStack.stack} (${techStack.packageManager})
|
|
15118
|
+
|
|
15119
|
+
## Your Task:
|
|
15120
|
+
Write a comprehensive markdown security report with:
|
|
15121
|
+
1. Executive Summary (overall security posture)
|
|
15122
|
+
2. Critical Issues (with detailed analysis and remediation)
|
|
15123
|
+
3. High Priority Issues
|
|
15124
|
+
4. Medium Priority Issues
|
|
15125
|
+
5. Low Priority / Informational
|
|
15126
|
+
6. Security Recommendations (prioritized action items)
|
|
15127
|
+
7. Summary Table
|
|
15128
|
+
|
|
15129
|
+
Format with proper markdown, emojis for severity, and actionable remediation steps.
|
|
15130
|
+
|
|
15131
|
+
Be specific about the vulnerabilities found and how to fix them.`;
|
|
15132
|
+
try {
|
|
15133
|
+
const request = {
|
|
15134
|
+
model: model ?? "unknown",
|
|
15135
|
+
system: [{ type: "text", text: "You are a security expert writing detailed reports." }],
|
|
15136
|
+
messages: [{ role: "user", content: prompt }],
|
|
15137
|
+
maxTokens: 8192
|
|
15138
|
+
};
|
|
15139
|
+
const abortController = new AbortController();
|
|
15140
|
+
const response = await this.completeWithRetry(provider, request, abortController);
|
|
15141
|
+
return response.content.filter((b) => b.type === "text").map((b) => b.text).join("");
|
|
15142
|
+
} catch (err) {
|
|
15143
|
+
console.error("LLM report synthesis failed:", err);
|
|
15144
|
+
return this.generateBasicReport(projectRoot, techStack, scanResult);
|
|
15145
|
+
}
|
|
15146
|
+
}
|
|
15147
|
+
/**
|
|
15148
|
+
* Generate a basic fallback report when LLM synthesis fails.
|
|
15149
|
+
*/
|
|
15150
|
+
generateBasicReport(projectRoot, techStack, scanResult) {
|
|
15151
|
+
const lines = [];
|
|
15152
|
+
lines.push("# Security Scan Report");
|
|
15153
|
+
lines.push("");
|
|
15154
|
+
lines.push(`**Generated:** ${new Date(scanResult.timestamp).toLocaleString()}`);
|
|
15155
|
+
lines.push(`**Project:** ${projectRoot}`);
|
|
15156
|
+
lines.push(`**Tech Stack:** ${techStack.stack} (${techStack.packageManager})`);
|
|
15157
|
+
lines.push(`**Scanned Files:** ${scanResult.scannedFiles}`);
|
|
15158
|
+
lines.push("");
|
|
15159
|
+
lines.push("## Summary");
|
|
15160
|
+
lines.push("");
|
|
15161
|
+
lines.push("| Severity | Count |");
|
|
15162
|
+
lines.push("|----------|-------|");
|
|
15163
|
+
lines.push(`| \u{1F534} Critical | ${scanResult.summary.critical} |`);
|
|
15164
|
+
lines.push(`| \u{1F7E0} High | ${scanResult.summary.high} |`);
|
|
15165
|
+
lines.push(`| \u{1F7E1} Medium | ${scanResult.summary.medium} |`);
|
|
15166
|
+
lines.push(`| \u{1F7E2} Low | ${scanResult.summary.low} |`);
|
|
15167
|
+
lines.push("");
|
|
15168
|
+
for (const finding of scanResult.findings) {
|
|
15169
|
+
const emoji = finding.severity === "critical" ? "\u{1F534}" : finding.severity === "high" ? "\u{1F7E0}" : finding.severity === "medium" ? "\u{1F7E1}" : "\u{1F7E2}";
|
|
15170
|
+
lines.push(`## ${emoji} ${finding.title}`);
|
|
15171
|
+
lines.push("");
|
|
15172
|
+
lines.push(`**File:** \`${finding.file}${finding.line ? `:${finding.line}` : ""}\``);
|
|
15173
|
+
lines.push(`**Severity:** ${finding.severity.toUpperCase()}`);
|
|
15174
|
+
lines.push(`**Category:** ${finding.category}`);
|
|
15175
|
+
lines.push("");
|
|
15176
|
+
if (finding.snippet) {
|
|
15177
|
+
lines.push("```");
|
|
15178
|
+
lines.push(finding.snippet);
|
|
15179
|
+
lines.push("```");
|
|
15180
|
+
lines.push("");
|
|
15181
|
+
}
|
|
15182
|
+
lines.push(`**Remediation:** ${finding.remediation}`);
|
|
15183
|
+
lines.push("");
|
|
15184
|
+
}
|
|
15185
|
+
return lines.join("\n");
|
|
15186
|
+
}
|
|
15187
|
+
/**
|
|
15188
|
+
* Write synthesized report to file.
|
|
15189
|
+
*/
|
|
15190
|
+
async writeReport(content, reportOptions) {
|
|
15191
|
+
const outputDir = reportOptions?.outputDir || "security-reports";
|
|
15192
|
+
const format = reportOptions?.format || "markdown";
|
|
15193
|
+
try {
|
|
15194
|
+
await mkdir(outputDir, { recursive: true });
|
|
15195
|
+
} catch {
|
|
15196
|
+
}
|
|
15197
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
15198
|
+
const filename = `security-report-${timestamp}.${format}`;
|
|
15199
|
+
const filepath = join(outputDir, filename);
|
|
15200
|
+
await writeFile(filepath, content, "utf-8");
|
|
15201
|
+
return filepath;
|
|
15202
|
+
}
|
|
15203
|
+
/**
|
|
15204
|
+
* Gather project info for skill generation.
|
|
15205
|
+
*/
|
|
15206
|
+
async gatherProjectInfo(projectRoot, techStack) {
|
|
15207
|
+
const info = [];
|
|
15208
|
+
const keyFiles = [
|
|
15209
|
+
"package.json",
|
|
15210
|
+
"tsconfig.json",
|
|
15211
|
+
".env.example",
|
|
15212
|
+
"README.md",
|
|
15213
|
+
"CONTRIBUTING.md"
|
|
15214
|
+
];
|
|
15215
|
+
for (const file of keyFiles) {
|
|
15216
|
+
try {
|
|
15217
|
+
const content = await readFile(join(projectRoot, file), "utf-8");
|
|
15218
|
+
const displayName = file === "README.md" || file === "CONTRIBUTING.md" ? "README" : file;
|
|
15219
|
+
info.push(`
|
|
15220
|
+
--- ${displayName} ---
|
|
15221
|
+
${content.slice(0, 1e3)}`);
|
|
15222
|
+
} catch {
|
|
15223
|
+
}
|
|
15224
|
+
}
|
|
15225
|
+
try {
|
|
15226
|
+
const entries = await readdir(projectRoot, { withFileTypes: true });
|
|
15227
|
+
const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name).slice(0, 20);
|
|
15228
|
+
info.push(`
|
|
15229
|
+
--- Project Directories ---
|
|
15230
|
+
${dirs.join(", ")}`);
|
|
15231
|
+
} catch {
|
|
15232
|
+
}
|
|
15233
|
+
return info.join("\n");
|
|
15234
|
+
}
|
|
15235
|
+
/**
|
|
15236
|
+
* Gather files to scan based on patterns.
|
|
15237
|
+
*/
|
|
15238
|
+
async gatherFiles(root, patterns, depth) {
|
|
15239
|
+
const files = [];
|
|
15240
|
+
const maxDepth = depth === "quick" ? 2 : depth === "deep" ? 20 : 5;
|
|
15241
|
+
const extensions = [".ts", ".js", ".jsx", ".tsx", ".py", ".go", ".java", ".cs", ".rs"];
|
|
15242
|
+
await this.gatherFilesRecursive(root, files, extensions, 0, maxDepth);
|
|
15243
|
+
return files;
|
|
15244
|
+
}
|
|
15245
|
+
async gatherFilesRecursive(dir, files, extensions, currentDepth, maxDepth) {
|
|
15246
|
+
if (currentDepth > maxDepth) return;
|
|
15247
|
+
try {
|
|
15248
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
15249
|
+
for (const entry of entries) {
|
|
15250
|
+
if (entry.isDirectory()) {
|
|
15251
|
+
const name = entry.name;
|
|
15252
|
+
if (name === "node_modules" || name === "dist" || name === "build" || name === ".git" || name === "coverage" || name.startsWith(".")) continue;
|
|
15253
|
+
await this.gatherFilesRecursive(
|
|
15254
|
+
join(dir, entry.name),
|
|
15255
|
+
files,
|
|
15256
|
+
extensions,
|
|
15257
|
+
currentDepth + 1,
|
|
15258
|
+
maxDepth
|
|
15259
|
+
);
|
|
15260
|
+
} else if (entry.isFile()) {
|
|
15261
|
+
const ext = entry.name.lastIndexOf(".");
|
|
15262
|
+
if (ext > 0 && extensions.includes(entry.name.slice(ext))) {
|
|
15263
|
+
files.push(join(dir, entry.name));
|
|
15264
|
+
}
|
|
15265
|
+
}
|
|
15266
|
+
}
|
|
15267
|
+
} catch {
|
|
15268
|
+
}
|
|
15269
|
+
}
|
|
15270
|
+
/**
|
|
15271
|
+
* Generate fallback skill when LLM fails.
|
|
15272
|
+
*/
|
|
15273
|
+
generateFallbackSkill(techStack) {
|
|
15274
|
+
return {
|
|
15275
|
+
name: `security-scanner-${techStack.stack}`,
|
|
15276
|
+
description: `Security scanner for ${techStack.stack} projects`,
|
|
15277
|
+
version: "1.0.0",
|
|
15278
|
+
techStack: techStack.stack,
|
|
15279
|
+
content: { type: "skill", content: "Fallback static skill" },
|
|
15280
|
+
patterns: [
|
|
15281
|
+
{
|
|
15282
|
+
id: "hardcoded-secrets",
|
|
15283
|
+
name: "Hardcoded Secrets",
|
|
15284
|
+
severity: "critical",
|
|
15285
|
+
description: "Detects hardcoded API keys, tokens, passwords",
|
|
15286
|
+
patterns: [],
|
|
15287
|
+
fileExtensions: [".ts", ".js", ".env"],
|
|
15288
|
+
falsePositiveMarkers: [],
|
|
15289
|
+
remediation: "Use environment variables"
|
|
15290
|
+
}
|
|
15291
|
+
],
|
|
15292
|
+
metadata: {
|
|
15293
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15294
|
+
confidence: 0.5,
|
|
15295
|
+
targetFiles: [`**/*.${techStack.stack === "nodejs" ? "ts" : techStack.stack === "python" ? "py" : "ts"}`]
|
|
15296
|
+
}
|
|
15297
|
+
};
|
|
15298
|
+
}
|
|
15299
|
+
/**
|
|
15300
|
+
* Quick scan - legacy compatibility.
|
|
15301
|
+
* NOTE: This won't use LLM as it doesn't have access to ctx.
|
|
15302
|
+
*/
|
|
15303
|
+
async quickScan(projectRoot) {
|
|
15304
|
+
const detectionResult = await this.detector.detect(projectRoot);
|
|
15305
|
+
if (detectionResult.detectedStacks.length === 0) {
|
|
15306
|
+
throw new Error(`No supported tech stack detected in ${projectRoot}`);
|
|
15307
|
+
}
|
|
15308
|
+
const techStack = detectionResult.detectedStacks[0];
|
|
15309
|
+
return {
|
|
15310
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15311
|
+
projectRoot,
|
|
15312
|
+
techStack,
|
|
15313
|
+
findings: [],
|
|
15314
|
+
summary: { critical: 0, high: 0, medium: 0, low: 0, total: 0 },
|
|
15315
|
+
scannedFiles: 0,
|
|
15316
|
+
scanDurationMs: 0,
|
|
15317
|
+
errors: ["Quick scan without LLM context is not fully supported. Use run(ctx, options) for full scan."]
|
|
15318
|
+
};
|
|
15319
|
+
}
|
|
15320
|
+
};
|
|
15321
|
+
var defaultOrchestrator = new SecurityScannerOrchestrator();
|
|
15322
|
+
function createSecuritySlashCommand() {
|
|
15323
|
+
return {
|
|
15324
|
+
name: "security",
|
|
15325
|
+
description: "Security scanning commands: scan, audit, report",
|
|
15326
|
+
argsHint: "[scan|audit|report] [options]",
|
|
15327
|
+
help: `
|
|
15328
|
+
# /security \u2014 Security Scanner
|
|
15329
|
+
|
|
15330
|
+
Security scanning with automatic tech stack detection.
|
|
15331
|
+
|
|
15332
|
+
## Commands
|
|
15333
|
+
|
|
15334
|
+
### /security scan [options]
|
|
15335
|
+
Run a full security scan on the current project.
|
|
15336
|
+
Options:
|
|
15337
|
+
--depth quick|standard|deep Scan depth (default: standard)
|
|
15338
|
+
--format markdown|json|html Report format (default: markdown)
|
|
15339
|
+
|
|
15340
|
+
### /security audit
|
|
15341
|
+
Run dependency audit + security scan.
|
|
15342
|
+
Checks for known vulnerabilities in dependencies.
|
|
15343
|
+
|
|
15344
|
+
### /security report [id]
|
|
15345
|
+
Show a previous security report.
|
|
15346
|
+
/security report - List available reports
|
|
15347
|
+
/security report <id> - Show specific report
|
|
15348
|
+
|
|
15349
|
+
## Examples
|
|
15350
|
+
|
|
15351
|
+
/security scan
|
|
15352
|
+
/security scan --depth deep --format html
|
|
15353
|
+
/security audit
|
|
15354
|
+
/security report
|
|
15355
|
+
/security report 2025-01-15
|
|
15356
|
+
`,
|
|
15357
|
+
async run(args, ctx) {
|
|
15358
|
+
const parts = args.trim().split(/\s+/);
|
|
15359
|
+
const subcommand = parts[0] || "";
|
|
15360
|
+
const subArgs = parts.slice(1).join(" ");
|
|
15361
|
+
switch (subcommand) {
|
|
15362
|
+
case "scan":
|
|
15363
|
+
return handleScan(subArgs, ctx);
|
|
15364
|
+
case "audit":
|
|
15365
|
+
return handleAudit(ctx);
|
|
15366
|
+
case "report":
|
|
15367
|
+
return handleReport(subArgs);
|
|
15368
|
+
default:
|
|
15369
|
+
return { message: getHelpMessage() };
|
|
15370
|
+
}
|
|
15371
|
+
}
|
|
15372
|
+
};
|
|
15373
|
+
}
|
|
15374
|
+
async function handleScan(args, ctx) {
|
|
15375
|
+
const options = parseArgs(args);
|
|
15376
|
+
const projectRoot = ctx.projectRoot || ctx.cwd || process.cwd();
|
|
15377
|
+
try {
|
|
15378
|
+
const result = await defaultOrchestrator.run(ctx, {
|
|
15379
|
+
projectRoot,
|
|
15380
|
+
scanOptions: {
|
|
15381
|
+
depth: options.depth || "standard",
|
|
15382
|
+
includeSecrets: true,
|
|
15383
|
+
includeInjection: true,
|
|
15384
|
+
includeConfig: true
|
|
15385
|
+
},
|
|
15386
|
+
reportOptions: {
|
|
15387
|
+
format: options.format || "markdown"
|
|
15388
|
+
}
|
|
15389
|
+
});
|
|
15390
|
+
const summary = result.scanResult.summary;
|
|
15391
|
+
const status = summary.total === 0 ? "\u2705 No issues found" : `\u26A0\uFE0F Found ${summary.total} issues`;
|
|
15392
|
+
const reportContent = result.synthesizedReport || `# Security Scan Complete
|
|
15393
|
+
|
|
15394
|
+
**Project:** ${projectRoot}
|
|
15395
|
+
**Tech Stack:** ${result.detectionResult.detectedStacks[0]?.stack || "unknown"}
|
|
15396
|
+
**Scanned Files:** ${result.scanResult.scannedFiles}
|
|
15397
|
+
**Duration:** ${result.scanResult.scanDurationMs}ms
|
|
15398
|
+
|
|
15399
|
+
## Summary
|
|
15400
|
+
|
|
15401
|
+
| Severity | Count |
|
|
15402
|
+
|----------|-------|
|
|
15403
|
+
| \u{1F534} Critical | ${summary.critical} |
|
|
15404
|
+
| \u{1F7E0} High | ${summary.high} |
|
|
15405
|
+
| \u{1F7E1} Medium | ${summary.medium} |
|
|
15406
|
+
| \u{1F7E2} Low | ${summary.low} |
|
|
15407
|
+
|
|
15408
|
+
**Status:** ${status}
|
|
15409
|
+
|
|
15410
|
+
**Report:** ${result.reportPath}
|
|
15411
|
+
`;
|
|
15412
|
+
return {
|
|
15413
|
+
message: reportContent,
|
|
15414
|
+
metadata: {
|
|
15415
|
+
scanResult: result.scanResult,
|
|
15416
|
+
reportPath: result.reportPath,
|
|
15417
|
+
techStack: result.detectionResult.detectedStacks[0]
|
|
15418
|
+
}
|
|
15419
|
+
};
|
|
15420
|
+
} catch (error) {
|
|
15421
|
+
return { message: `\u274C Scan failed: ${error}` };
|
|
15422
|
+
}
|
|
15423
|
+
}
|
|
15424
|
+
async function handleAudit(ctx) {
|
|
15425
|
+
const projectRoot = ctx.projectRoot || ctx.cwd || process.cwd();
|
|
15426
|
+
try {
|
|
15427
|
+
const result = await defaultOrchestrator.run(ctx, {
|
|
15428
|
+
projectRoot,
|
|
15429
|
+
reportOptions: { format: "markdown" }
|
|
15430
|
+
});
|
|
15431
|
+
const depIssues = result.scanResult.summary.critical + result.scanResult.summary.high;
|
|
15432
|
+
const reportContent = result.synthesizedReport || `
|
|
15433
|
+
# Security Audit Complete
|
|
15434
|
+
|
|
15435
|
+
**Project:** ${projectRoot}
|
|
15436
|
+
**Tech Stack:** ${result.detectionResult.detectedStacks[0]?.stack || "unknown"}
|
|
15437
|
+
|
|
15438
|
+
## Dependency Health
|
|
15439
|
+
|
|
15440
|
+
| Status | Count |
|
|
15441
|
+
|--------|-------|
|
|
15442
|
+
| Critical Issues | ${result.scanResult.summary.critical} |
|
|
15443
|
+
| High Priority | ${result.scanResult.summary.high} |
|
|
15444
|
+
| Medium Priority | ${result.scanResult.summary.medium} |
|
|
15445
|
+
| Low Priority | ${result.scanResult.summary.low} |
|
|
15446
|
+
|
|
15447
|
+
${depIssues === 0 ? "\u2705 No known vulnerabilities detected" : `\u26A0\uFE0F ${depIssues} vulnerabilities need attention`}
|
|
15448
|
+
|
|
15449
|
+
**Full Report:** ${result.reportPath}
|
|
15450
|
+
`;
|
|
15451
|
+
return {
|
|
15452
|
+
message: reportContent,
|
|
15453
|
+
metadata: {
|
|
15454
|
+
scanResult: result.scanResult,
|
|
15455
|
+
reportPath: result.reportPath
|
|
15456
|
+
}
|
|
15457
|
+
};
|
|
15458
|
+
} catch (error) {
|
|
15459
|
+
return { message: `\u274C Audit failed: ${error}` };
|
|
15460
|
+
}
|
|
15461
|
+
}
|
|
15462
|
+
async function handleReport(reportId) {
|
|
15463
|
+
const reportsDir = "security-reports";
|
|
15464
|
+
try {
|
|
15465
|
+
const files = await readdir(reportsDir);
|
|
15466
|
+
const reports = files.filter((f) => f.startsWith("security-report-") && (f.endsWith(".md") || f.endsWith(".json"))).sort().reverse();
|
|
15467
|
+
if (!reportId) {
|
|
15468
|
+
if (reports.length === 0) {
|
|
15469
|
+
return { message: "\u{1F4ED} No security reports found. Run `/security scan` first." };
|
|
15470
|
+
}
|
|
15471
|
+
const list = reports.map((r, i) => {
|
|
15472
|
+
const date = r.replace("security-report-", "").replace(/\.(md|json)$/, "");
|
|
15473
|
+
return ` ${i + 1}. ${date}`;
|
|
15474
|
+
}).join("\n");
|
|
15475
|
+
return { message: `# Available Security Reports
|
|
15476
|
+
|
|
15477
|
+
${list}
|
|
15478
|
+
|
|
15479
|
+
Use \`/security report <number>\` to view a specific report.` };
|
|
15480
|
+
}
|
|
15481
|
+
const index = parseInt(reportId, 10) - 1;
|
|
15482
|
+
if (!isNaN(index) && reports[index]) {
|
|
15483
|
+
const { readFile: readFile28 } = await import('fs/promises');
|
|
15484
|
+
const content = await readFile28(join(reportsDir, reports[index]), "utf-8");
|
|
15485
|
+
return { message: `# Security Report
|
|
15486
|
+
|
|
15487
|
+
${content}` };
|
|
15488
|
+
}
|
|
15489
|
+
const match = reports.find((r) => r.includes(reportId));
|
|
15490
|
+
if (match) {
|
|
15491
|
+
const { readFile: readFile28 } = await import('fs/promises');
|
|
15492
|
+
const content = await readFile28(join(reportsDir, match), "utf-8");
|
|
15493
|
+
return { message: `# Security Report
|
|
15494
|
+
|
|
15495
|
+
${content}` };
|
|
15496
|
+
}
|
|
15497
|
+
return { message: `\u274C Report "${reportId}" not found. Use \`/security report\` to see available reports.` };
|
|
15498
|
+
} catch (error) {
|
|
15499
|
+
return { message: "\u{1F4ED} No security reports found. Run `/security scan` first." };
|
|
15500
|
+
}
|
|
15501
|
+
}
|
|
15502
|
+
function parseArgs(args) {
|
|
15503
|
+
const result = {};
|
|
15504
|
+
const parts = args.split(/\s+/);
|
|
15505
|
+
for (let i = 0; i < parts.length; i++) {
|
|
15506
|
+
const part = parts[i];
|
|
15507
|
+
if (!part || !part.startsWith("--")) continue;
|
|
15508
|
+
const key = part.slice(2);
|
|
15509
|
+
const next = parts[i + 1];
|
|
15510
|
+
if (next && !next.startsWith("--")) {
|
|
15511
|
+
result[key] = next;
|
|
15512
|
+
i++;
|
|
15513
|
+
} else {
|
|
15514
|
+
result[key] = "true";
|
|
15515
|
+
}
|
|
15516
|
+
}
|
|
15517
|
+
return result;
|
|
15518
|
+
}
|
|
15519
|
+
function getHelpMessage() {
|
|
15520
|
+
return `
|
|
15521
|
+
# /security \u2014 Security Scanner
|
|
15522
|
+
|
|
15523
|
+
**Available Commands:**
|
|
15524
|
+
|
|
15525
|
+
1. **/security scan** \u2014 Run full security scan
|
|
15526
|
+
Options: --depth quick|standard|deep, --format markdown|json|html
|
|
15527
|
+
|
|
15528
|
+
2. **/security audit** \u2014 Run dependency audit + security scan
|
|
15529
|
+
|
|
15530
|
+
3. **/security report** \u2014 List or view security reports
|
|
15531
|
+
|
|
15532
|
+
**Examples:**
|
|
15533
|
+
\`\`\`
|
|
15534
|
+
/security scan
|
|
15535
|
+
/security scan --depth deep --format html
|
|
15536
|
+
/security audit
|
|
15537
|
+
/security report
|
|
15538
|
+
\`\`\`
|
|
15539
|
+
`;
|
|
15540
|
+
}
|
|
15541
|
+
var securitySlashCommand = createSecuritySlashCommand();
|
|
13164
15542
|
|
|
13165
15543
|
// src/extension/registry.ts
|
|
13166
15544
|
var ExtensionRegistry = class {
|
|
@@ -14619,8 +16997,8 @@ ${mem}`);
|
|
|
14619
16997
|
}
|
|
14620
16998
|
async dirExists(p) {
|
|
14621
16999
|
try {
|
|
14622
|
-
const
|
|
14623
|
-
return
|
|
17000
|
+
const stat8 = await fsp2.stat(p);
|
|
17001
|
+
return stat8.isDirectory();
|
|
14624
17002
|
} catch {
|
|
14625
17003
|
return false;
|
|
14626
17004
|
}
|
|
@@ -15371,6 +17749,6 @@ function wrapApiForCapabilityCheck(plugin, api, log, enforce = false) {
|
|
|
15371
17749
|
});
|
|
15372
17750
|
}
|
|
15373
17751
|
|
|
15374
|
-
export { AISpecBuilder, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, Agent, AgentError, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, CONTEXT_WINDOW_MODES, ConfigError, ConfigMigrationError, Container, Context, ConversationState, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_CONTEXT_WINDOW_MODE_ID, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_MAX_ITERATIONS, DEFAULT_MODES, DEFAULT_RECOVERY_STRATEGIES, DEFAULT_SPEC_TEMPLATE, DEFAULT_SUBAGENT_BASELINE, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPathResolver, DefaultPermissionPolicy, DefaultPluginAPI, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultTaskStore, DefaultTokenCounter, Director, DirectorBudgetError, DirectorStateCheckpoint, DoneConditionChecker, EventBus, ExtensionRegistry, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FleetBus, FleetUsageAggregator, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, InputBuilder, IntelligentCompactor, KERNEL_API_VERSION, LAYER_1_IDENTITY, LLMSelector, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, Pipeline, PluginError, ProviderError, ProviderRegistry, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, RunController, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, SelectiveCompactor, SessionAnalyzer, SessionError, SkillInstaller, SkillManifestStore, SlashCommandRegistry, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, SubagentBudget, TOKENS, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, ToolError, ToolExecutor, ToolRegistry, WrongStackError, addPlanItem, allServers, analyzeCriticalPath, applyRosterBudget, asBlocks, asText, atomicWrite, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildChildEnv, buildOtlpMetricsRequest, buildOtlpTracesRequest, buildRecoveryStrategies, classifyFamily, clearPlan, color, compileGlob, compileUserRegex, composeDirectorPrompt, composeSubagentPrompt, computeTaskProgress, context7Server, contextManagerTool, createAutoExecutor, createContextManagerTool, createDefaultPipelines, createDelegateTool, createMessage, createToolOutputSerializer, decryptConfigSecrets, detectNewlineStyle, downloadGitHubTarball, emptyPlan, encryptConfigSecrets, ensureDir, estimateRequestTokens, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, everArtServer, extractRunEnv, filesystemServer, findCriticalPath, formatContextWindowModeList, formatPlan, formatTodosList, getContextWindowMode, getTemplate, githubServer, googleMapsServer, isAgentError, isConfigError, isContextWindowModeId, isImageBlock, isPluginError, isSessionError, isTextBlock, isThinkingBlock, isToolError, isToolResultBlock, isToolUseBlock, isWrongStackError, listContextWindowModes, listTemplates, loadDirectorState, loadPlan, loadPlugins, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeDirectorSessionFactory, matchAny, matchGlob, migratePlaintextSecrets, miniMaxVisionServer, normalizeToLf, parseSkillRef, projectHash, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, repairToolUseAdjacency, resolveContextWindowPolicy, resolveWstackPaths, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, safeParse, safeStringify, sanitizeJsonString, savePlan, saveTodosCheckpoint, sentinelServer, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, stripAnsi, templateToMarkdown, toStyle, toWrongStackError, topologicalSort, unifiedDiff, unloadPlugins, validateAgainstSchema, wireMetricsToEvents, wrapAsState, zaiVisionServer };
|
|
17752
|
+
export { AISpecBuilder, ALL_FLEET_AGENTS, AUDIT_LOG_AGENT, Agent, AgentError, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutonomousRunner, BUG_HUNTER_AGENT, BudgetExceededError, CONTEXT_WINDOW_MODES, ConfigError, ConfigMigrationError, Container, Context, ConversationState, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_CONTEXT_WINDOW_MODE_ID, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_MAX_ITERATIONS, DEFAULT_MODES, DEFAULT_RECOVERY_STRATEGIES, DEFAULT_SPEC_TEMPLATE, DEFAULT_SUBAGENT_BASELINE, DefaultAttachmentStore, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPathResolver, DefaultPermissionPolicy, DefaultPluginAPI, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultTaskStore, DefaultTokenCounter, Director, DirectorBudgetError, DirectorStateCheckpoint, DoneConditionChecker, EventBus, ExtensionRegistry, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FleetBus, FleetUsageAggregator, GitignoreUpdater, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, InputBuilder, IntelligentCompactor, KERNEL_API_VERSION, LAYER_1_IDENTITY, LLMSelector, NoopMetricsSink, NoopTracer, OTelTracer, PROMETHEUS_CONTENT_TYPE, Pipeline, PluginError, ProviderError, ProviderRegistry, QueueStore, REFACTOR_PLANNER_AGENT, RecoveryLock, ReportGenerator, RunController, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, ScopedEventBus, SecurityScanner, SecurityScannerOrchestrator, SelectiveCompactor, SessionAnalyzer, SessionError, SkillGenerator, SkillInstaller, SkillManifestStore, SlashCommandRegistry, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, SubagentBudget, TOKENS, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, TechStackDetector, ToolError, ToolExecutor, ToolRegistry, WrongStackError, addPlanItem, allServers, analyzeCriticalPath, applyRosterBudget, asBlocks, asText, atomicWrite, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, braveSearchServer, buildChildEnv, buildOtlpMetricsRequest, buildOtlpTracesRequest, buildRecoveryStrategies, classifyFamily, clearPlan, color, compileGlob, compileUserRegex, composeDirectorPrompt, composeSubagentPrompt, computeTaskProgress, context7Server, contextManagerTool, createAutoExecutor, createContextManagerTool, createDefaultPipelines, createDelegateTool, createMessage, createSecuritySlashCommand, createToolOutputSerializer, decryptConfigSecrets, defaultGitignoreUpdater, defaultOrchestrator, defaultReportGenerator, defaultSecurityScanner, defaultSkillGenerator, defaultTechStackDetector, deriveTodosFromPlanItem, detectNewlineStyle, downloadGitHubTarball, emptyPlan, encryptConfigSecrets, ensureDir, estimateRequestTokens, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, everArtServer, extractRunEnv, filesystemServer, findCriticalPath, formatContextWindowModeList, formatPlan, formatPlanTemplates, formatTodosList, getContextWindowMode, getPlanTemplate, getTemplate, githubServer, googleMapsServer, isAgentError, isConfigError, isContextWindowModeId, isImageBlock, isPluginError, isSessionError, isTextBlock, isThinkingBlock, isToolError, isToolResultBlock, isToolUseBlock, isWrongStackError, listContextWindowModes, listPlanTemplates, listTemplates, loadDirectorState, loadPlan, loadPlugins, loadProjectModes, loadTodosCheckpoint, loadUserModes, makeAgentSubagentRunner, makeDirectorSessionFactory, matchAny, matchGlob, migratePlaintextSecrets, miniMaxVisionServer, normalizeToLf, parseSkillRef, projectHash, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, repairToolUseAdjacency, resolveContextWindowPolicy, resolveWstackPaths, rewriteConfigEncrypted, rosterSummaryFromConfigs, runConfigMigrations, safeParse, safeStringify, sanitizeJsonString, savePlan, saveTodosCheckpoint, securitySlashCommand, sentinelServer, setPlanItemStatus, slackServer, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, stripAnsi, templateToMarkdown, toStyle, toWrongStackError, topologicalSort, unifiedDiff, unloadPlugins, validateAgainstSchema, wireMetricsToEvents, wrapAsState, zaiVisionServer };
|
|
15375
17753
|
//# sourceMappingURL=index.js.map
|
|
15376
17754
|
//# sourceMappingURL=index.js.map
|