@gpzhang2001/sharpkit-analysis 0.2.1 → 0.2.2

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/lib/index.d.ts CHANGED
@@ -3,6 +3,7 @@ import { Context } from "@deepseek-ai/cordis";
3
3
  var [JsonMirrorStore] = [
4
4
  8,
5
5
  () => [
6
+ Promise,
6
7
  Promise,
7
8
  Promise,
8
9
  Record,
@@ -29,6 +30,9 @@ var [JsonMirrorStore] = [
29
30
  "",
30
31
  "",
31
32
  "",
33
+ "",
34
+ "",
35
+ "",
32
36
  ""
33
37
  ]
34
38
  ];
@@ -1 +1 @@
1
- {"version":3,"file":"index.ts","names":[],"sources":["../src/stores.d.ts","../src/index.d.ts"],"sourcesContent":["/**\n * Run-scoped JSON mirror stores (strix .state/ mirrors): atomic writes\n * (temp-in-dir + rename), hydrate-on-start, per-store in-memory maps.\n * One instance per scan, owned by the analysis service.\n * @module @gpzhang2001/sharpkit-analysis/stores\n */\n/** A JSON file mirror with atomic write and hydrate. */\nexport declare class JsonMirrorStore {\n private readonly path;\n private readonly map;\n constructor(path: string | null);\n /** Load the mirror file; corrupt JSON is tolerated as empty on hydrate (resume aid only). */\n hydrate(): Promise<void>;\n /** Persist the whole map atomically (strix `_persist_locked`). */\n persist(): Promise<void>;\n get(key: string): Record<string, unknown> | undefined;\n set(key: string, value: Record<string, unknown>): void;\n delete(key: string): boolean;\n values(): Record<string, unknown>[];\n get size(): number;\n}\n/** Generate a 6-hex id with bounded collision retries (strix parity). */\nexport declare function generateId(existing: ReadonlySet<string>): string | null;\n/**\n * Normalize a remote target to its identity (threat_model/tools.py\n * `_normalize_remote_target` + `_normalize_git_remote`): scp-style and\n * scheme'd git remotes map to https URLs, `.git` stripped, trailing slash\n * stripped, host lowercased with default ports collapsed.\n * @param target - the raw target string.\n */\nexport declare function normalizeTargetIdentity(target: string): string;\n","/**\n * Analysis tools — port of strix tools/{threat_model,coverage,notes,\n * thinking}/ + finish_scan: get/save/amend_threat_model (validated\n * sections, size gates, append-only amendments), record/update/\n * list_coverage (duplicate guard, evidence requirements, history),\n * create/list/get/update/delete_note, think, and finish_scan (root-only,\n * four required sections, coverage summary, final artifacts). State lives\n * in per-scan mirror stores under the run dir (.state parity) and is\n * provided as `pentestAnalysis` for the reporting package's coverage\n * document and SARIF bridge.\n * @module @gpzhang2001/sharpkit-analysis\n */\nimport type { Context } from '@deepseek-ai/cordis';\nimport { JsonMirrorStore } from './stores.ts';\nexport { normalizeTargetIdentity } from './stores.ts';\n/** Coverage outcome values (strix VALID_OUTCOMES). */\nexport declare const VALID_OUTCOMES: readonly [\"reported\", \"no_issue_found\", \"ruled_out\", \"not_applicable\", \"needs_follow_up\"];\n/** Note categories (strix `_VALID_NOTE_CATEGORIES`). */\nexport declare const VALID_NOTE_CATEGORIES: readonly [\"general\", \"findings\", \"methodology\", \"questions\", \"plan\", \"wiki\"];\n/** Deployment-tunable configuration. */\nexport interface Config {\n /** Sandbox session cache key shared with the other tool packages. */\n readonly scanId?: string;\n /** Scan targets (threat-model identity snapping). */\n readonly scanTargets?: readonly string[];\n /** Run directory for the .state mirrors (default sharpkit_runs/<scanId>/.state). */\n readonly runDir?: string;\n /** Whether this composition's agent may call finish_scan (root guard). */\n readonly allowFinish?: boolean;\n}\nexport declare const name = \"pentest-tool-analysis\";\nexport declare const inject: string[];\n/** The per-scan analysis stores exposed to other packages. */\nexport interface AnalysisHandle {\n readonly coverage: JsonMirrorStore;\n readonly threatModels: JsonMirrorStore;\n readonly notes: JsonMirrorStore;\n /** Coverage entries in list order with ids (reporting package consumes). */\n coverageEntries(): Array<Record<string, unknown> & {\n entry_id: string;\n }>;\n outcomeCounts(): Record<string, number>;\n}\ndeclare module '@deepseek-ai/cordis' {\n interface Context {\n pentestAnalysis: AnalysisHandle;\n }\n}\nexport declare function apply(ctx: Context, config?: Config): AnalysisHandle;\n"],"mappings":";;AAAA,IAAW,CAAC,mBAAmB;CAAC;OAAS;EAAC;EAAS;EAAS;EAAQ;EAAQ;CAAM;CAAG;EAAC;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;CAAE;AAAC;AAErK,IAAW,CAAC,2BAA2B;CAAC;OAAU,CAAC;CAAG,CAAC,EAAE;AAAC;;;ACC1D,IAAW,CAAC,kBAAkB;CAAC;OAAS,CAAC;CAAG,CAAC;AAAC;AAC9C,IAAW,CAAC,yBAAyB;CAAC;OAAS,CAAC;CAAG,CAAC;AAAC;AACrD,IAAW,CAAC,UAAU;CAAC;OAAS,CAAC;CAAG;EAAC;EAAI;EAAI;EAAI;CAAE;AAAC;AACpD,IAAW,CAAC,QAAQ;CAAC;OAAS,CAAC;CAAG,CAAC;AAAC;AACpC,IAAW,CAAC,UAAU;CAAC;OAAS,CAAC;CAAG,CAAC;AAAC;AACtC,IAAW,CAAC,kBAAkB;CAAC;OAAS;EAAC;EAAiB;EAAiB;EAAiB;EAAQ;EAAO;CAAM;CAAG;EAAC;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;CAAE;AAAC;AACpK,IAAI,CAAC,MAAM;CAAC;OAAS,CAAC,cAAc;CAAG;EAAC;EAAI;EAAI;CAAE;CAAG,WAAW,EAAE;AAAC;AACnE,IAAW,CAAC,SAAS;CAAC;OAAS;EAAC;EAAS;EAAQ;CAAc;CAAG;EAAC;EAAI;EAAI;EAAI;EAAI;CAAE;AAAC"}
1
+ {"version":3,"file":"index.ts","names":[],"sources":["../src/stores.d.ts","../src/index.d.ts"],"sourcesContent":["/**\n * Run-scoped JSON mirror stores (strix .state/ mirrors): atomic writes\n * (temp-in-dir + rename), hydrate-on-start, per-store in-memory maps.\n * One instance per scan, owned by the analysis service.\n * @module @gpzhang2001/sharpkit-analysis/stores\n */\n/** A JSON file mirror with atomic write and hydrate. The path may be lazy: a\n * provider is resolved (and re-read) on every persist/hydrate so a store can\n * follow the per-scan run directory resolved at tool-execute time. */\nexport declare class JsonMirrorStore {\n private readonly pathOrProvider;\n private readonly map;\n /** Monotonic per-store counter: unique temp names within one process even\n * under concurrent persist() calls (same pid would otherwise collide and\n * one rename would fail with ENOENT — observed once on 2026-09-22). */\n private tempCounter;\n constructor(path: string | null | (() => string | null | Promise<string | null>));\n private path;\n /** Load the mirror file; corrupt JSON is tolerated as empty on hydrate (resume aid only). */\n hydrate(): Promise<void>;\n /** Persist the whole map atomically (strix `_persist_locked`). A rename\n * loss to a concurrent writer of the same store is tolerated: the in-memory\n * map stays authoritative and the mirror is a resume aid. */\n persist(): Promise<void>;\n get(key: string): Record<string, unknown> | undefined;\n set(key: string, value: Record<string, unknown>): void;\n delete(key: string): boolean;\n values(): Record<string, unknown>[];\n get size(): number;\n}\n/** Generate a 6-hex id with bounded collision retries (strix parity). */\nexport declare function generateId(existing: ReadonlySet<string>): string | null;\n/**\n * Normalize a remote target to its identity (threat_model/tools.py\n * `_normalize_remote_target` + `_normalize_git_remote`): scp-style and\n * scheme'd git remotes map to https URLs, `.git` stripped, trailing slash\n * stripped, host lowercased with default ports collapsed.\n * @param target - the raw target string.\n */\nexport declare function normalizeTargetIdentity(target: string): string;\n","/**\n * Analysis tools — port of strix tools/{threat_model,coverage,notes,\n * thinking}/ + finish_scan: get/save/amend_threat_model (validated\n * sections, size gates, append-only amendments), record/update/\n * list_coverage (duplicate guard, evidence requirements, history),\n * create/list/get/update/delete_note, think, and finish_scan (root-only,\n * four required sections, coverage summary, final artifacts). State lives\n * in per-scan mirror stores under the run dir (.state parity) and is\n * provided as `pentestAnalysis` for the reporting package's coverage\n * document and SARIF bridge.\n * @module @gpzhang2001/sharpkit-analysis\n */\nimport type { Context } from '@deepseek-ai/cordis';\nimport { JsonMirrorStore } from './stores.ts';\nexport { normalizeTargetIdentity } from './stores.ts';\n/** Coverage outcome values (strix VALID_OUTCOMES). */\nexport declare const VALID_OUTCOMES: readonly [\"reported\", \"no_issue_found\", \"ruled_out\", \"not_applicable\", \"needs_follow_up\"];\n/** Note categories (strix `_VALID_NOTE_CATEGORIES`). */\nexport declare const VALID_NOTE_CATEGORIES: readonly [\"general\", \"findings\", \"methodology\", \"questions\", \"plan\", \"wiki\"];\n/** Deployment-tunable configuration. */\nexport interface Config {\n /** Sandbox session cache key shared with the other tool packages. */\n readonly scanId?: string;\n /** Scan targets (threat-model identity snapping). */\n readonly scanTargets?: readonly string[];\n /** Run directory for the .state mirrors (default: the reporting package's\n * per-scan runDirFor() — session cwd + multi-scan isolation; the mirrors sit\n * in <that>/.state alongside the artifacts). */\n readonly runDir?: string;\n /** Whether this composition's agent may call finish_scan (root guard). */\n readonly allowFinish?: boolean;\n}\nexport declare const name = \"pentest-tool-analysis\";\nexport declare const inject: string[];\n/** The per-scan analysis stores exposed to other packages. */\nexport interface AnalysisHandle {\n readonly coverage: JsonMirrorStore;\n readonly threatModels: JsonMirrorStore;\n readonly notes: JsonMirrorStore;\n /** Coverage entries in list order with ids (reporting package consumes). */\n coverageEntries(): Array<Record<string, unknown> & {\n entry_id: string;\n }>;\n outcomeCounts(): Record<string, number>;\n}\ndeclare module '@deepseek-ai/cordis' {\n interface Context {\n pentestAnalysis: AnalysisHandle;\n }\n}\nexport declare function apply(ctx: Context, config?: Config): AnalysisHandle;\n"],"mappings":";;AAAA,IAAW,CAAC,mBAAmB;CAAC;OAAS;EAAC;EAAS;EAAS;EAAS;EAAQ;EAAQ;CAAM;CAAG;EAAC;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;CAAE;AAAC;AAE1L,IAAW,CAAC,2BAA2B;CAAC;OAAU,CAAC;CAAG,CAAC,EAAE;AAAC;;;ACC1D,IAAW,CAAC,kBAAkB;CAAC;OAAS,CAAC;CAAG,CAAC;AAAC;AAC9C,IAAW,CAAC,yBAAyB;CAAC;OAAS,CAAC;CAAG,CAAC;AAAC;AACrD,IAAW,CAAC,UAAU;CAAC;OAAS,CAAC;CAAG;EAAC;EAAI;EAAI;EAAI;CAAE;AAAC;AACpD,IAAW,CAAC,QAAQ;CAAC;OAAS,CAAC;CAAG,CAAC;AAAC;AACpC,IAAW,CAAC,UAAU;CAAC;OAAS,CAAC;CAAG,CAAC;AAAC;AACtC,IAAW,CAAC,kBAAkB;CAAC;OAAS;EAAC;EAAiB;EAAiB;EAAiB;EAAQ;EAAO;CAAM;CAAG;EAAC;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;EAAI;CAAE;AAAC;AACpK,IAAI,CAAC,MAAM;CAAC;OAAS,CAAC,cAAc;CAAG;EAAC;EAAI;EAAI;CAAE;CAAG,WAAW,EAAE;AAAC;AACnE,IAAW,CAAC,SAAS;CAAC;OAAS;EAAC;EAAS;EAAQ;CAAc;CAAG;EAAC;EAAI;EAAI;EAAI;EAAI;CAAE;AAAC"}
package/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import { dirname, join } from "node:path";
1
+ import { dirname, join, resolve } from "node:path";
2
2
  import { defineTool } from "@deepseek-ai/dsh-tools";
3
- import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
3
+ import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
4
4
  //#region src/stores.ts
5
5
  /**
6
6
  * Run-scoped JSON mirror stores (strix .state/ mirrors): atomic writes
@@ -8,31 +8,49 @@ import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
8
8
  * One instance per scan, owned by the analysis service.
9
9
  * @module @gpzhang2001/sharpkit-analysis/stores
10
10
  */
11
- /** A JSON file mirror with atomic write and hydrate. */
11
+ /** A JSON file mirror with atomic write and hydrate. The path may be lazy: a
12
+ * provider is resolved (and re-read) on every persist/hydrate so a store can
13
+ * follow the per-scan run directory resolved at tool-execute time. */
12
14
  var JsonMirrorStore = class {
13
- path;
15
+ pathOrProvider;
14
16
  map = /* @__PURE__ */ new Map();
17
+ /** Monotonic per-store counter: unique temp names within one process even
18
+ * under concurrent persist() calls (same pid would otherwise collide and
19
+ * one rename would fail with ENOENT — observed once on 2026-09-22). */
20
+ tempCounter = 0;
15
21
  constructor(path) {
16
- this.path = path;
22
+ this.pathOrProvider = path;
23
+ }
24
+ async path() {
25
+ return typeof this.pathOrProvider === "function" ? await Promise.resolve(this.pathOrProvider()) : this.pathOrProvider;
17
26
  }
18
27
  /** Load the mirror file; corrupt JSON is tolerated as empty on hydrate (resume aid only). */
19
28
  async hydrate() {
20
- if (this.path === null) return;
29
+ const path = await this.path();
30
+ if (path === null) return;
21
31
  try {
22
- const raw = await readFile(this.path, "utf8");
32
+ const raw = await readFile(path, "utf8");
23
33
  const parsed = JSON.parse(raw);
24
34
  if (typeof parsed !== "object" || parsed === null) return;
25
35
  for (const [key, value] of Object.entries(parsed)) if (typeof value === "object" && value !== null) this.map.set(key, value);
26
36
  } catch {}
27
37
  }
28
- /** Persist the whole map atomically (strix `_persist_locked`). */
38
+ /** Persist the whole map atomically (strix `_persist_locked`). A rename
39
+ * loss to a concurrent writer of the same store is tolerated: the in-memory
40
+ * map stays authoritative and the mirror is a resume aid. */
29
41
  async persist() {
30
- if (this.path === null) return;
42
+ const path = await this.path();
43
+ if (path === null) return;
31
44
  const payload = JSON.stringify(Object.fromEntries(this.map), null, 2);
32
- await mkdir(dirname(this.path), { recursive: true });
33
- const temp = `${dirname(this.path)}/.${join("", this.path.split("/").pop() ?? "store")}.${process.pid}.tmp`;
45
+ await mkdir(dirname(path), { recursive: true });
46
+ const temp = `${dirname(path)}/.${join("", path.split("/").pop() ?? "store")}.${process.pid}.${this.tempCounter++}.tmp`;
34
47
  await writeFile(temp, payload, "utf8");
35
- await rename(temp, this.path);
48
+ try {
49
+ await rename(temp, path);
50
+ } catch (error) {
51
+ await rm(temp, { force: true }).catch(() => void 0);
52
+ if (this.map.size > 0) throw error;
53
+ }
36
54
  }
37
55
  get(key) {
38
56
  return this.map.get(key);
@@ -143,14 +161,35 @@ function isoNow() {
143
161
  }
144
162
  function apply(ctx, config = {}) {
145
163
  const scanId = config.scanId ?? "pentest";
146
- const runDir = config.runDir ?? join("sharpkit_runs", scanId);
147
- const stateDir = join(runDir, ".state");
148
- const coverage = new JsonMirrorStore(join(stateDir, "coverage.json"));
149
- const threatModels = new JsonMirrorStore(join(stateDir, "threat_models.json"));
150
- const notes = new JsonMirrorStore(join(stateDir, "notes.json"));
151
- coverage.hydrate();
152
- threatModels.hydrate();
153
- notes.hydrate();
164
+ let activeSession;
165
+ let hydratedFor;
166
+ let hydratePromise;
167
+ const noteExec = async (rawExec) => {
168
+ const session = rawExec?.agent?.session;
169
+ if (session === void 0) return;
170
+ activeSession = session;
171
+ if (hydratedFor === session || hydratePromise !== void 0) return;
172
+ hydratedFor = session;
173
+ hydratePromise = Promise.all([
174
+ coverage.hydrate(),
175
+ threatModels.hydrate(),
176
+ notes.hydrate()
177
+ ]).then(() => void 0);
178
+ await hydratePromise;
179
+ hydratePromise = void 0;
180
+ };
181
+ const stateDirOf = async () => {
182
+ if (config.runDir !== void 0) return join(resolve(config.runDir), ".state");
183
+ const runDir = await ctx.pentestReporting?.runDirFor?.(activeSession);
184
+ return runDir === void 0 ? null : join(runDir, ".state");
185
+ };
186
+ const mirrorPath = (name) => async () => {
187
+ const dir = await stateDirOf();
188
+ return dir === null ? null : join(dir, name);
189
+ };
190
+ const coverage = new JsonMirrorStore(mirrorPath("coverage.json"));
191
+ const threatModels = new JsonMirrorStore(mirrorPath("threat_models.json"));
192
+ const notes = new JsonMirrorStore(mirrorPath("notes.json"));
154
193
  const coverageEntries = () => coverage.values().map((entry) => ({
155
194
  ...entry,
156
195
  entry_id: String(entry["id"] ?? "")
@@ -224,7 +263,8 @@ function apply(ctx, config = {}) {
224
263
  }];
225
264
  }
226
265
  },
227
- execute: (async (rawArgs) => {
266
+ execute: (async (rawArgs, rawExec) => {
267
+ await noteExec(rawExec);
228
268
  const resolved = resolveTarget(rawArgs.target);
229
269
  if ("error" in resolved) return {
230
270
  success: false,
@@ -287,7 +327,8 @@ function apply(ctx, config = {}) {
287
327
  }];
288
328
  }
289
329
  },
290
- execute: (async (rawArgs) => {
330
+ execute: (async (rawArgs, rawExec) => {
331
+ await noteExec(rawExec);
291
332
  const args = rawArgs;
292
333
  const resolved = resolveTarget(args.target);
293
334
  if ("error" in resolved) return {
@@ -363,7 +404,8 @@ function apply(ctx, config = {}) {
363
404
  }];
364
405
  }
365
406
  },
366
- execute: (async (rawArgs) => {
407
+ execute: (async (rawArgs, rawExec) => {
408
+ await noteExec(rawExec);
367
409
  const args = rawArgs;
368
410
  const resolved = resolveTarget(args.target);
369
411
  if ("error" in resolved) return {
@@ -470,7 +512,8 @@ function apply(ctx, config = {}) {
470
512
  }];
471
513
  }
472
514
  },
473
- execute: (async (rawArgs) => {
515
+ execute: (async (rawArgs, rawExec) => {
516
+ await noteExec(rawExec);
474
517
  const args = rawArgs;
475
518
  const surface = (args.surface ?? "").trim();
476
519
  const riskArea = (args.risk_area ?? "").trim();
@@ -564,7 +607,8 @@ function apply(ctx, config = {}) {
564
607
  }];
565
608
  }
566
609
  },
567
- execute: (async (rawArgs) => {
610
+ execute: (async (rawArgs, rawExec) => {
611
+ await noteExec(rawExec);
568
612
  const args = rawArgs;
569
613
  const entryId = (args.entry_id ?? "").trim();
570
614
  const entry = coverage.get(entryId);
@@ -668,7 +712,8 @@ function apply(ctx, config = {}) {
668
712
  }];
669
713
  }
670
714
  },
671
- execute: (async (rawArgs) => {
715
+ execute: (async (rawArgs, rawExec) => {
716
+ await noteExec(rawExec);
672
717
  const args = rawArgs;
673
718
  let outcomeFilter;
674
719
  if (args.outcome !== void 0 && args.outcome !== "") {
@@ -758,7 +803,8 @@ function apply(ctx, config = {}) {
758
803
  }];
759
804
  }
760
805
  },
761
- execute: (async (rawArgs) => {
806
+ execute: (async (rawArgs, rawExec) => {
807
+ await noteExec(rawExec);
762
808
  const args = rawArgs;
763
809
  const title = (args.title ?? "").trim();
764
810
  const content = (args.content ?? "").trim();
@@ -856,7 +902,8 @@ function apply(ctx, config = {}) {
856
902
  }];
857
903
  }
858
904
  },
859
- execute: (async (rawArgs) => {
905
+ execute: (async (rawArgs, rawExec) => {
906
+ await noteExec(rawExec);
860
907
  const args = rawArgs;
861
908
  const category = args.category?.trim() ?? "";
862
909
  const search = args.search?.toLowerCase() ?? "";
@@ -915,7 +962,8 @@ function apply(ctx, config = {}) {
915
962
  }];
916
963
  }
917
964
  },
918
- execute: (async (rawArgs) => {
965
+ execute: (async (rawArgs, rawExec) => {
966
+ await noteExec(rawExec);
919
967
  const noteId = (rawArgs.note_id ?? "").trim();
920
968
  if (noteId === "") return {
921
969
  success: false,
@@ -981,7 +1029,8 @@ function apply(ctx, config = {}) {
981
1029
  }];
982
1030
  }
983
1031
  },
984
- execute: (async (rawArgs) => {
1032
+ execute: (async (rawArgs, rawExec) => {
1033
+ await noteExec(rawExec);
985
1034
  const args = rawArgs;
986
1035
  const noteId = (args.note_id ?? "").trim();
987
1036
  const note = notes.get(noteId);
@@ -1041,7 +1090,8 @@ function apply(ctx, config = {}) {
1041
1090
  }];
1042
1091
  }
1043
1092
  },
1044
- execute: (async (rawArgs) => {
1093
+ execute: (async (rawArgs, rawExec) => {
1094
+ await noteExec(rawExec);
1045
1095
  const noteId = (rawArgs.note_id ?? "").trim();
1046
1096
  const note = notes.get(noteId);
1047
1097
  if (note === void 0) return {
@@ -1087,7 +1137,8 @@ function apply(ctx, config = {}) {
1087
1137
  }];
1088
1138
  }
1089
1139
  },
1090
- execute: (async (rawArgs) => {
1140
+ execute: (async (rawArgs, rawExec) => {
1141
+ await noteExec(rawExec);
1091
1142
  if ((rawArgs.thought ?? "").trim() === "") return {
1092
1143
  success: false,
1093
1144
  error: "Thought cannot be empty"
@@ -1098,6 +1149,45 @@ function apply(ctx, config = {}) {
1098
1149
  };
1099
1150
  })
1100
1151
  }));
1152
+ /**
1153
+ * Best-effort capture reconciliation at scan close: count the Caido proxy
1154
+ * history for this scan's sandbox session. Structural access (no hard
1155
+ * dependency on the proxy package): pentestSandbox → caidoEndpoint → one
1156
+ * GraphQL count query. Reconciliation must never block or fail scan
1157
+ * completion — but a failure is never silent either: the reason travels
1158
+ * back in `note` (into the tool result, the finish_scan render, and
1159
+ * run.json). The previous `catch → undefined` swallowed a live breakage
1160
+ * for a full day unnoticed (2026-09-22: run.json carried no capture
1161
+ * fields at all while capture_summary worked seconds earlier).
1162
+ */
1163
+ const proxyCapturedRequests = async () => {
1164
+ const fail = (reason) => {
1165
+ ctx.logger?.warn(`pentest-analysis: capture reconcile failed: ${reason}`);
1166
+ return { note: `Proxy capture count unknown: ${reason}` };
1167
+ };
1168
+ try {
1169
+ const sandbox = ctx.get("pentestSandbox");
1170
+ if (sandbox === void 0) return fail("sandbox service unavailable");
1171
+ const endpoint = await (await sandbox.createSession({ scanId })).caidoEndpoint();
1172
+ const response = await fetch(`${endpoint.baseUrl.replace(/\/$/, "")}/graphql`, {
1173
+ method: "POST",
1174
+ headers: {
1175
+ "content-type": "application/json",
1176
+ authorization: `Bearer ${endpoint.token}`
1177
+ },
1178
+ body: JSON.stringify({ query: "query CaptureReconcile { requests(first: 1, order: {by: CREATED_AT, ordering: DESC}) { count { value } } }" }),
1179
+ signal: AbortSignal.timeout(5e3)
1180
+ });
1181
+ if (!response.ok) return fail(`Caido answered HTTP ${String(response.status)}`);
1182
+ const payload = await response.json();
1183
+ if (payload.errors !== void 0 && payload.errors !== null && payload.errors.length > 0) return fail(`Caido: ${String(payload.errors[0]?.message ?? "GraphQL error")}`);
1184
+ const value = payload.data?.requests?.count?.value;
1185
+ if (value === null || value === void 0) return fail("Caido returned no request count");
1186
+ return { captured: Number(value) };
1187
+ } catch (error) {
1188
+ return fail(error instanceof Error ? error.message : String(error));
1189
+ }
1190
+ };
1101
1191
  ctx.tools.register(defineTool({
1102
1192
  name: "finish_scan",
1103
1193
  description: "Complete the scan: validates the four report sections, records the coverage summary, writes the final artifacts, and marks the scan completed. Root agent only.",
@@ -1149,6 +1239,12 @@ function apply(ctx, config = {}) {
1149
1239
  additionalProperties: true
1150
1240
  }
1151
1241
  },
1242
+ proxy_captured_requests: {
1243
+ type: "integer",
1244
+ description: "Requests in the Caido proxy history at close time (the queryable evidence trail)."
1245
+ },
1246
+ capture_warning: { type: "string" },
1247
+ capture_note: { type: "string" },
1152
1248
  warning: { type: "string" },
1153
1249
  error: { type: "string" },
1154
1250
  errors: {
@@ -1161,13 +1257,18 @@ function apply(ctx, config = {}) {
1161
1257
  render: (_args, value) => {
1162
1258
  const result = value;
1163
1259
  const reason = result.error ?? result.errors?.join("; ") ?? "unknown";
1260
+ if (!result.success) return [{
1261
+ type: "text",
1262
+ text: `finish_scan failed: ${reason}`
1263
+ }];
1164
1264
  return [{
1165
1265
  type: "text",
1166
- text: result.success ? "scan completed" : `finish_scan failed: ${reason}`
1266
+ text: `scan completed; ${result.capture_warning !== void 0 ? result.capture_warning : result.proxy_captured_requests !== void 0 ? `proxy capture history holds ${String(result.proxy_captured_requests)} requests` : result.capture_note ?? "proxy capture count unknown"}`
1167
1267
  }];
1168
1268
  }
1169
1269
  },
1170
- execute: (async (rawArgs) => {
1270
+ execute: (async (rawArgs, rawExec) => {
1271
+ await noteExec(rawExec);
1171
1272
  const args = rawArgs;
1172
1273
  if (config.allowFinish === false) return {
1173
1274
  success: false,
@@ -1205,6 +1306,19 @@ function apply(ctx, config = {}) {
1205
1306
  summary["unresolved_surfaces"] = unresolved;
1206
1307
  }
1207
1308
  }
1309
+ const reconcile = await proxyCapturedRequests();
1310
+ const captureExtras = {};
1311
+ if (reconcile.captured !== void 0) {
1312
+ summary["proxy_captured_requests"] = reconcile.captured;
1313
+ captureExtras["proxy_captured_requests"] = reconcile.captured;
1314
+ if (reconcile.captured === 0) {
1315
+ summary["capture_warning"] = "The Caido proxy captured 0 requests for this scan: no queryable traffic history exists, so PoC requests cannot be replayed from history. Report this as a capture-line gap; plaintext HTTP should be captured transparently in sandbox images >= 1.1.0.";
1316
+ captureExtras["capture_warning"] = summary["capture_warning"];
1317
+ }
1318
+ } else {
1319
+ summary["capture_note"] = reconcile.note ?? "Proxy capture count unknown.";
1320
+ captureExtras["capture_note"] = summary["capture_note"];
1321
+ }
1208
1322
  const reporting = ctx.pentestReporting;
1209
1323
  if (reporting === void 0) return {
1210
1324
  success: true,
@@ -1213,7 +1327,7 @@ function apply(ctx, config = {}) {
1213
1327
  warning: "Results could not be persisted - report state unavailable",
1214
1328
  ...summary
1215
1329
  };
1216
- await reporting.finishScan(sections);
1330
+ await reporting.finishScan(sections, "completed", captureExtras);
1217
1331
  return {
1218
1332
  success: true,
1219
1333
  scan_completed: true,
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["args","result"],"sources":["../src/stores.ts","../src/index.ts"],"sourcesContent":["/**\n * Run-scoped JSON mirror stores (strix .state/ mirrors): atomic writes\n * (temp-in-dir + rename), hydrate-on-start, per-store in-memory maps.\n * One instance per scan, owned by the analysis service.\n * @module @gpzhang2001/sharpkit-analysis/stores\n */\n\nimport { mkdir, readFile, rename, writeFile } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\n\n/** A JSON file mirror with atomic write and hydrate. */\nexport class JsonMirrorStore {\n private readonly path: string | null\n private readonly map = new Map<string, Record<string, unknown>>()\n\n constructor(path: string | null) {\n this.path = path\n }\n\n /** Load the mirror file; corrupt JSON is tolerated as empty on hydrate (resume aid only). */\n async hydrate(): Promise<void> {\n if (this.path === null) return\n try {\n const raw = await readFile(this.path, 'utf8')\n const parsed = JSON.parse(raw) as unknown\n if (typeof parsed !== 'object' || parsed === null) return\n for (const [key, value] of Object.entries(parsed as Record<string, unknown>)) {\n if (typeof value === 'object' && value !== null) this.map.set(key, value as Record<string, unknown>)\n }\n } catch {\n // Missing or unreadable mirror: start empty.\n }\n }\n\n /** Persist the whole map atomically (strix `_persist_locked`). */\n async persist(): Promise<void> {\n if (this.path === null) return\n const payload = JSON.stringify(Object.fromEntries(this.map), null, 2)\n await mkdir(dirname(this.path), { recursive: true })\n const temp = `${dirname(this.path)}/.${join('', this.path.split('/').pop() ?? 'store')}.${process.pid}.tmp`\n await writeFile(temp, payload, 'utf8')\n await rename(temp, this.path)\n }\n\n get(key: string): Record<string, unknown> | undefined {\n return this.map.get(key)\n }\n\n set(key: string, value: Record<string, unknown>): void {\n this.map.set(key, value)\n }\n\n delete(key: string): boolean {\n return this.map.delete(key)\n }\n\n values(): Record<string, unknown>[] {\n return [...this.map.values()]\n }\n\n get size(): number {\n return this.map.size\n }\n}\n\n/** Generate a 6-hex id with bounded collision retries (strix parity). */\nexport function generateId(existing: ReadonlySet<string>): string | null {\n for (let attempt = 0; attempt < 1024; attempt++) {\n const id = Math.random().toString(16).slice(2, 8).padEnd(6, '0')\n if (!existing.has(id)) return id\n }\n return null\n}\n\n/**\n * Normalize a remote target to its identity (threat_model/tools.py\n * `_normalize_remote_target` + `_normalize_git_remote`): scp-style and\n * scheme'd git remotes map to https URLs, `.git` stripped, trailing slash\n * stripped, host lowercased with default ports collapsed.\n * @param target - the raw target string.\n */\nexport function normalizeTargetIdentity(target: string): string {\n const trimmed = target.trim()\n const scp = /^git@([^:]+):(.+?)(?:\\.git)?$/.exec(trimmed)\n if (scp !== null) return `https://${scp[1]?.toLowerCase()}/${scp[2]}`\n const withScheme = /^([a-z][a-z0-9+.-]*):\\/\\/([^/?#]+)([^#]*)/i.exec(trimmed)\n if (withScheme !== null) {\n const scheme = withScheme[1] ?? ''\n const authority = withScheme[2] ?? ''\n const path = withScheme[3] ?? ''\n const defaultPort = scheme.toLowerCase() === 'https' ? '443' : scheme.toLowerCase() === 'http' ? '80' : null\n let host = authority.toLowerCase()\n if (defaultPort !== null && host.endsWith(`:${defaultPort}`)) host = host.slice(0, -defaultPort.length - 1)\n return `https://${host}${path.replace(/\\/$/, '')}`.replace(/\\.git$/, '')\n }\n return trimmed.replace(/\\/$/, '').replace(/\\.git$/, '')\n}\n","/**\n * Analysis tools — port of strix tools/{threat_model,coverage,notes,\n * thinking}/ + finish_scan: get/save/amend_threat_model (validated\n * sections, size gates, append-only amendments), record/update/\n * list_coverage (duplicate guard, evidence requirements, history),\n * create/list/get/update/delete_note, think, and finish_scan (root-only,\n * four required sections, coverage summary, final artifacts). State lives\n * in per-scan mirror stores under the run dir (.state parity) and is\n * provided as `pentestAnalysis` for the reporting package's coverage\n * document and SARIF bridge.\n * @module @gpzhang2001/sharpkit-analysis\n */\n\nimport { join } from 'node:path'\nimport type { Context } from '@deepseek-ai/cordis'\nimport { defineTool } from '@deepseek-ai/dsh-tools'\nimport { JsonMirrorStore, generateId, normalizeTargetIdentity } from './stores.ts'\n\nexport { normalizeTargetIdentity } from './stores.ts'\n\n/** Coverage outcome values (strix VALID_OUTCOMES). */\nexport const VALID_OUTCOMES = ['reported', 'no_issue_found', 'ruled_out', 'not_applicable', 'needs_follow_up'] as const\n\n/** Outcomes that require evidence text (strix `_OUTCOMES_REQUIRING_EVIDENCE`). */\nconst OUTCOMES_REQUIRING_EVIDENCE = new Set(['ruled_out', 'not_applicable', 'needs_follow_up'])\n\n/** Note categories (strix `_VALID_NOTE_CATEGORIES`). */\nexport const VALID_NOTE_CATEGORIES = ['general', 'findings', 'methodology', 'questions', 'plan', 'wiki'] as const\n\n/** Required threat-model sections, matched as lowercase substrings (strix parity). */\nconst REQUIRED_SECTIONS = ['overview', 'trust boundaries', 'attack surface', 'severity calibration'] as const\n\n/** Threat model gates (strix constants). */\nconst MAX_MODEL_BYTES = 512 * 1024\nconst MIN_MODEL_CHARS = 400\nconst MIN_AMENDMENT_CHARS = 80\nconst MAX_AMENDMENTS = 40\n\n/** Display timestamp in the coverage format. */\nfunction displayTimestamp(date: Date): string {\n const pad = (value: number): string => String(value).padStart(2, '0')\n return `${date.getUTCFullYear()}-${pad(date.getUTCMonth() + 1)}-${pad(date.getUTCDate())} ${pad(date.getUTCHours())}:${pad(date.getUTCMinutes())}:${pad(date.getUTCSeconds())} UTC`\n}\n\n/** Deployment-tunable configuration. */\nexport interface Config {\n /** Sandbox session cache key shared with the other tool packages. */\n readonly scanId?: string\n /** Scan targets (threat-model identity snapping). */\n readonly scanTargets?: readonly string[]\n /** Run directory for the .state mirrors (default sharpkit_runs/<scanId>/.state). */\n readonly runDir?: string\n /** Whether this composition's agent may call finish_scan (root guard). */\n readonly allowFinish?: boolean\n}\n\nexport const name = 'pentest-tool-analysis'\n\nexport const inject = ['tools', 'pentestReporting']\n\n/** The per-scan analysis stores exposed to other packages. */\nexport interface AnalysisHandle {\n readonly coverage: JsonMirrorStore\n readonly threatModels: JsonMirrorStore\n readonly notes: JsonMirrorStore\n /** Coverage entries in list order with ids (reporting package consumes). */\n coverageEntries(): Array<Record<string, unknown> & { entry_id: string }>\n outcomeCounts(): Record<string, number>\n}\n\ndeclare module '@deepseek-ai/cordis' {\n interface Context {\n pentestAnalysis: AnalysisHandle\n }\n}\n\n/** ISO-8601 timestamp (threat model + notes stores). */\nfunction isoNow(): string {\n return new Date().toISOString().replace('Z', '+00:00')\n}\n\nexport function apply(ctx: Context, config: Config = {}): AnalysisHandle {\n const scanId = config.scanId ?? 'pentest'\n const runDir = config.runDir ?? join('sharpkit_runs', scanId)\n const stateDir = join(runDir, '.state')\n const coverage = new JsonMirrorStore(join(stateDir, 'coverage.json'))\n const threatModels = new JsonMirrorStore(join(stateDir, 'threat_models.json'))\n const notes = new JsonMirrorStore(join(stateDir, 'notes.json'))\n void coverage.hydrate()\n void threatModels.hydrate()\n void notes.hydrate()\n\n const coverageEntries = (): Array<Record<string, unknown> & { entry_id: string }> =>\n coverage.values().map(entry => ({ ...entry, entry_id: String(entry['id'] ?? '') })) as Array<Record<string, unknown> & { entry_id: string }>\n\n const outcomeCounts = (): Record<string, number> => {\n const counts: Record<string, number> = {}\n for (const outcome of VALID_OUTCOMES) {\n const count = coverage.values().filter(entry => entry['outcome'] === outcome).length\n if (count > 0) counts[outcome] = count\n }\n return counts\n }\n\n const handle: AnalysisHandle = { coverage, threatModels, notes, coverageEntries, outcomeCounts }\n ctx.provide('pentestAnalysis', handle)\n\n const resolveTarget = (target: string | undefined): { readonly identity: string } | { readonly error: string } => {\n const cleaned = target?.trim() ?? ''\n if (cleaned === '') {\n const targets = config.scanTargets ?? []\n if (targets.length === 1) return { identity: normalizeTargetIdentity(targets[0] ?? '') }\n return { error: 'target cannot be empty - name the target this threat model describes' }\n }\n return { identity: normalizeTargetIdentity(cleaned) }\n }\n\n ctx.tools.register(defineTool({\n name: 'get_threat_model',\n description: 'Fetch the threat model shared for a target in this scan (found=false when none exists yet). Read-only.',\n parameters: {\n target: { type: 'string', description: 'Target the model describes; omit when the scan has exactly one target.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n found: { type: 'boolean' },\n target: { type: 'string' },\n content: { type: 'string' },\n amendments: { type: 'array', items: { type: 'object', properties: {}, additionalProperties: true } },\n amendments_note: { type: 'string' },\n message: { type: 'string' },\n error: { type: 'string' },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; found?: boolean; error?: string }\n if (!result.success) return [{ type: 'text', text: `get_threat_model failed: ${result.error ?? 'unknown'}` }]\n return [{ type: 'text', text: result.found === true ? 'threat model found' : 'no threat model yet' }]\n },\n },\n execute: (async (rawArgs: never) => {\n const args = rawArgs as never as { target?: string }\n const resolved = resolveTarget(args.target)\n if ('error' in resolved) return { success: false, error: resolved.error }\n const model = threatModels.get(resolved.identity)\n if (model === undefined || String(model['content'] ?? '').trim() === '') {\n return { success: true, found: false, target: resolved.identity, message: 'No threat model exists for this target yet. Save one with save_threat_model.' }\n }\n const amendments = model['amendments'] as unknown[] | undefined\n return {\n success: true,\n found: true,\n target: resolved.identity,\n content: String(model['content']),\n ...(amendments !== undefined && amendments.length > 0 ? { amendments, amendments_note: 'Addenda recorded by agents after the model was saved.' } : {}),\n }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'save_threat_model',\n description: `Share your threat model for a target with the whole scan team (full replace; clears amendments). Must cover Overview, Trust Boundaries, Attack Surface, and Severity Calibration; minimum ${String(MIN_MODEL_CHARS)} characters, maximum 512KB.`,\n parameters: {\n target: { type: 'string', description: 'Target the model describes; omit when the scan has exactly one target.' },\n content: { type: 'string', required: true, description: 'The full markdown threat model.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n target: { type: 'string' },\n amendments_cleared: { type: 'integer' },\n message: { type: 'string' },\n error: { type: 'string' },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; error?: string }\n return [{ type: 'text', text: result.success ? 'threat model saved' : `save_threat_model failed: ${result.error ?? 'unknown'}` }]\n },\n },\n execute: (async (rawArgs: never) => {\n const args = rawArgs as never as { target?: string; content: string }\n const resolved = resolveTarget(args.target)\n if ('error' in resolved) return { success: false, error: resolved.error }\n const content = args.content.trim()\n if (content.length < MIN_MODEL_CHARS) {\n return { success: false, error: `Threat model is too thin (${String(content.length)} chars). It has to be usable by every agent in this scan.` }\n }\n if (Buffer.byteLength(content, 'utf8') > MAX_MODEL_BYTES) return { success: false, error: 'Threat model exceeds 512KB; tighten it.' }\n const lower = content.toLowerCase()\n const missing = REQUIRED_SECTIONS.filter(section => !lower.includes(section))\n if (missing.length > 0) {\n return { success: false, error: `Threat model is missing required section(s): ${missing.join(', ')}. Cover Overview, Trust Boundaries, Attack Surface, and Severity Calibration.` }\n }\n const previous = threatModels.get(resolved.identity)\n const previousAmendments = previous?.['amendments']\n const amendmentsCleared = Array.isArray(previousAmendments) ? previousAmendments.length : 0\n threatModels.set(resolved.identity, {\n target: resolved.identity,\n written_at: isoNow(),\n written_by: null,\n content,\n })\n await threatModels.persist()\n return {\n success: true,\n target: resolved.identity,\n amendments_cleared: amendmentsCleared,\n message: amendmentsCleared > 0\n ? `Threat model shared with this scan. It replaced a previous model, folding ${String(amendmentsCleared)} amendment(s) into the full rewrite.`\n : 'Threat model shared with this scan.',\n }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'amend_threat_model',\n description: `Append an addendum to the existing threat model without rewriting it (minimum ${String(MIN_AMENDMENT_CHARS)} characters). Use save_threat_model for a full rewrite that folds amendments.`,\n parameters: {\n target: { type: 'string', description: 'Target the model describes; omit when the scan has exactly one target.' },\n addendum: { type: 'string', required: true, description: 'The markdown addendum.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n target: { type: 'string' },\n amendment_count: { type: 'integer' },\n message: { type: 'string' },\n error: { type: 'string' },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; error?: string }\n return [{ type: 'text', text: result.success ? 'amendment recorded' : `amend_threat_model failed: ${result.error ?? 'unknown'}` }]\n },\n },\n execute: (async (rawArgs: never) => {\n const args = rawArgs as never as { target?: string; addendum: string }\n const resolved = resolveTarget(args.target)\n if ('error' in resolved) return { success: false, error: resolved.error }\n const addendum = args.addendum.trim()\n if (addendum.length < MIN_AMENDMENT_CHARS) {\n return { success: false, error: `Amendment is too thin (${String(addendum.length)} chars). Give the new knowledge in full.` }\n }\n const model = threatModels.get(resolved.identity)\n if (model === undefined) return { success: false, error: 'No threat model exists for this target yet. Save the full model with save_threat_model first.' }\n const amendments = (model['amendments'] as unknown[] | undefined) ?? []\n if (amendments.length >= MAX_AMENDMENTS) {\n return { success: false, error: `This threat model already carries ${String(amendments.length)} amendments. Fold them into a full save_threat_model rewrite.` }\n }\n if (Buffer.byteLength(String(model['content']) + addendum, 'utf8') > MAX_MODEL_BYTES) {\n return { success: false, error: 'Combined threat model exceeds 512KB; fold amendments with save_threat_model.' }\n }\n amendments.push({ at: isoNow(), by: null, content: addendum })\n model['amendments'] = amendments\n await threatModels.persist()\n return { success: true, target: resolved.identity, amendment_count: amendments.length, message: 'Amendment recorded. Every agent reading the threat model will see it.' }\n }) as never,\n }))\n\n // ---- coverage ----\n\n interface CoverageValidateInput {\n readonly surface: string\n readonly riskArea: string\n readonly outcome: string\n readonly evidence: string\n }\n\n /** strix coverage `_validate`: normalization + per-field errors. */\n function validateCoverage(input: CoverageValidateInput): { readonly outcome: string } | { readonly errors: string[] } {\n const errors: string[] = []\n if (input.surface === '') errors.push('surface cannot be empty - name the endpoint, route, file, or component')\n if (input.riskArea === '') errors.push('risk_area cannot be empty - name what you were testing for')\n const outcome = input.outcome.trim().toLowerCase().replaceAll('-', '_').replaceAll(' ', '_')\n if (!(VALID_OUTCOMES as readonly string[]).includes(outcome)) {\n errors.push(`Invalid outcome: '${input.outcome}'. Must be one of: [${VALID_OUTCOMES.join(', ')}]`)\n }\n if (OUTCOMES_REQUIRING_EVIDENCE.has(outcome) && input.evidence === '') {\n errors.push(`evidence is required for outcome '${outcome}' - name the specific control, response, or observation that justifies it`)\n }\n return errors.length > 0 ? { errors } : { outcome }\n }\n\n const findCoverageDuplicate = (surface: string, riskArea: string): Record<string, unknown> | undefined =>\n coverage.values().find(entry => String(entry['surface']).toLowerCase() === surface.toLowerCase() && String(entry['risk_area']).toLowerCase() === riskArea.toLowerCase())\n\n ctx.tools.register(defineTool({\n name: 'record_coverage',\n description: \"Record that you exercised an attack surface for a risk area, with an outcome. Outcomes: reported / no_issue_found / ruled_out / not_applicable / needs_follow_up. ruled_out, not_applicable, and needs_follow_up REQUIRE evidence. One entry per (surface, risk_area).\",\n parameters: {\n surface: { type: 'string', required: true, description: 'The endpoint, route, file, or component you exercised.' },\n risk_area: { type: 'string', required: true, description: 'What you were testing for (e.g. \"SQL injection in login\").' },\n outcome: { type: 'string', required: true, enum: [...VALID_OUTCOMES], description: 'The testing outcome.' },\n evidence: { type: 'string', description: 'Concrete observation justifying the outcome (required for some outcomes).' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n entry_id: { type: 'string' },\n outcome: { type: 'string' },\n message: { type: 'string' },\n error: { type: 'string' },\n errors: { type: 'array', items: { type: 'string' } },\n existing_entry_id: { type: 'string' },\n existing_outcome: { type: 'string' },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; error?: string; errors?: string[] }\n const reason = result.error ?? result.errors?.join('; ') ?? 'unknown'\n return [{ type: 'text', text: result.success ? 'coverage recorded' : `record_coverage failed: ${reason}` }]\n },\n },\n execute: (async (rawArgs: never) => {\n const args = rawArgs as never as { surface?: string; risk_area?: string; outcome?: string; evidence?: string }\n const surface = (args.surface ?? '').trim()\n const riskArea = (args.risk_area ?? '').trim()\n const evidence = (args.evidence ?? '').trim()\n const checked = validateCoverage({ surface, riskArea, outcome: args.outcome ?? '', evidence })\n if ('errors' in checked) return { success: false, error: 'Validation failed', errors: checked.errors }\n const duplicate = findCoverageDuplicate(surface, riskArea)\n if (duplicate !== undefined) {\n return {\n success: false,\n error: `'${surface}' (${riskArea}) already has coverage entry ${String(duplicate['id'])}, recorded by ${String(duplicate['agent_name'] ?? 'an agent')} as '${String(duplicate['outcome'])}'. Two rows for the same surface and risk area are never allowed; update_coverage to change the outcome.`,\n existing_entry_id: String(duplicate['id']),\n existing_outcome: String(duplicate['outcome']),\n }\n }\n const id = generateId(new Set(coverage.values().map(entry => String(entry['id'] ?? ''))))\n if (id === null) return { success: false, error: 'could not allocate a coverage entry id' }\n coverage.set(id, {\n id,\n surface,\n risk_area: riskArea,\n outcome: checked.outcome,\n created_at: displayTimestamp(new Date()),\n ...(evidence !== '' ? { evidence } : {}),\n })\n await coverage.persist()\n return { success: true, entry_id: id, outcome: checked.outcome, message: `Coverage recorded for '${surface}' (${checked.outcome})` }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'update_coverage',\n description: \"Move an existing coverage entry to a new outcome (surface and risk_area are never editable). The previous state is kept as history.\",\n parameters: {\n entry_id: { type: 'string', required: true, description: 'Id from record_coverage or list_coverage.' },\n outcome: { type: 'string', required: true, enum: [...VALID_OUTCOMES], description: 'The new outcome.' },\n evidence: { type: 'string', description: 'Evidence for the new outcome (required for some outcomes).' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n entry_id: { type: 'string' },\n previous_outcome: { type: 'string' },\n outcome: { type: 'string' },\n message: { type: 'string' },\n error: { type: 'string' },\n errors: { type: 'array', items: { type: 'string' } },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; error?: string; errors?: string[] }\n const reason = result.error ?? result.errors?.join('; ') ?? 'unknown'\n return [{ type: 'text', text: result.success ? 'coverage updated' : `update_coverage failed: ${reason}` }]\n },\n },\n execute: (async (rawArgs: never) => {\n const args = rawArgs as never as { entry_id?: string; outcome?: string; evidence?: string }\n const entryId = (args.entry_id ?? '').trim()\n const entry = coverage.get(entryId)\n if (entry === undefined) return { success: false, error: `No coverage entry '${entryId}'. Call list_coverage to see recorded entries.` }\n const surface = String(entry['surface'] ?? '')\n const riskArea = String(entry['risk_area'] ?? '')\n const evidence = (args.evidence ?? '').trim()\n const checked = validateCoverage({ surface, riskArea, outcome: args.outcome ?? '', evidence })\n if ('errors' in checked) return { success: false, error: 'Validation failed', errors: checked.errors }\n const previousOutcome = String(entry['outcome'])\n const history = (entry['history'] as Array<Record<string, unknown>> | undefined) ?? []\n const prior: Record<string, unknown> = { outcome: previousOutcome, recorded_at: String(entry['created_at']) }\n if (entry['evidence'] !== undefined) prior['evidence'] = entry['evidence']\n history.push(prior)\n entry['history'] = history\n entry['outcome'] = checked.outcome\n entry['updated_at'] = displayTimestamp(new Date())\n if (evidence !== '') entry['evidence'] = evidence\n await coverage.persist()\n return {\n success: true,\n entry_id: entryId,\n previous_outcome: previousOutcome,\n outcome: checked.outcome,\n message: `'${surface}' (${riskArea}) moved from ${previousOutcome} to ${checked.outcome}. The previous state is kept as history.`,\n }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'list_coverage',\n description: 'List coverage entries recorded in this scan (filters compose; outcome counts are over all entries).',\n parameters: {\n outcome: { type: 'string', description: 'Filter to one outcome.' },\n surface: { type: 'string', description: 'Case-insensitive substring match on the surface.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n entries: { type: 'array', items: { type: 'object', properties: {}, additionalProperties: true }, required: true },\n filtered_count: { type: 'integer', required: true },\n total_count: { type: 'integer', required: true },\n outcome_counts: { type: 'object', properties: {}, additionalProperties: true, required: true },\n error: { type: 'string' },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { entries?: unknown[]; error?: string }\n if (result.error !== undefined) return [{ type: 'text', text: `list_coverage failed: ${result.error}` }]\n return [{ type: 'text', text: `${String(result.entries?.length ?? 0)} coverage entry(ies)` }]\n },\n },\n execute: (async (rawArgs: never) => {\n const args = rawArgs as never as { outcome?: string; surface?: string }\n let outcomeFilter: string | undefined\n if (args.outcome !== undefined && args.outcome !== '') {\n outcomeFilter = args.outcome.trim().toLowerCase().replaceAll('-', '_').replaceAll(' ', '_')\n if (outcomeFilter !== undefined && !(VALID_OUTCOMES as readonly string[]).includes(outcomeFilter)) {\n return {\n success: false,\n error: `Invalid outcome: '${args.outcome}'. Must be one of: [${VALID_OUTCOMES.join(', ')}]`,\n entries: [],\n filtered_count: 0,\n total_count: coverage.size,\n outcome_counts: {},\n }\n }\n }\n const surfaceFilter = args.surface?.toLowerCase() ?? ''\n const entries = coverageEntries()\n .filter(entry => (outcomeFilter === undefined || entry['outcome'] === outcomeFilter))\n .filter(entry => surfaceFilter === '' || String(entry['surface']).toLowerCase().includes(surfaceFilter))\n .sort((a, b) => String(a['created_at']).localeCompare(String(b['created_at'])))\n .map(entry => {\n const listing: Record<string, unknown> = {\n entry_id: entry.entry_id,\n surface: entry['surface'],\n risk_area: entry['risk_area'],\n outcome: entry['outcome'],\n created_at: entry['created_at'],\n }\n const evidence = entry['evidence']\n if (typeof evidence === 'string' && evidence !== '') {\n listing['evidence'] = evidence.length > 240 ? `${evidence.slice(0, 240)}...` : evidence\n }\n const history = entry['history'] as Array<Record<string, unknown>> | undefined\n if (history !== undefined && history.length > 0) {\n listing['previous_outcomes'] = history.map(item => item['outcome'])\n }\n return listing\n })\n return {\n success: true,\n entries,\n filtered_count: entries.length,\n total_count: coverage.size,\n outcome_counts: outcomeCounts(),\n }\n }) as never,\n }))\n\n // ---- notes ----\n\n const noteList = (): Array<Record<string, unknown> & { id: string }> =>\n notes.values().map(entry => ({ ...entry, id: String(entry['id'] ?? '') })) as Array<Record<string, unknown> & { id: string }>\n\n ctx.tools.register(defineTool({\n name: 'create_note',\n description: 'Create a persistent note shared across the scan team (categories: general/findings/methodology/questions/plan/wiki).',\n parameters: {\n title: { type: 'string', required: true, description: 'Short note title.' },\n content: { type: 'string', required: true, description: 'The note body.' },\n category: { type: 'string', enum: [...VALID_NOTE_CATEGORIES], description: 'Note category (default general).' },\n tags: { type: 'array', items: { type: 'string' }, description: 'Optional tags.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n note_id: { type: 'string' },\n message: { type: 'string' },\n total_count: { type: 'integer' },\n error: { type: 'string' },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; error?: string }\n return [{ type: 'text', text: result.success ? 'note created' : `create_note failed: ${result.error ?? 'unknown'}` }]\n },\n },\n execute: (async (rawArgs: never) => {\n const args = rawArgs as never as { title?: string; content?: string; category?: string; tags?: string[] }\n const title = (args.title ?? '').trim()\n const content = (args.content ?? '').trim()\n const category = (args.category ?? 'general').trim()\n if (title === '') return { success: false, error: 'Title cannot be empty' }\n if (content === '') return { success: false, error: 'Content cannot be empty' }\n if (!(VALID_NOTE_CATEGORIES as readonly string[]).includes(category)) {\n return { success: false, error: `Invalid category. Must be one of: ${VALID_NOTE_CATEGORIES.join(', ')}` }\n }\n const id = generateId(new Set(noteList().map(note => note.id)))\n if (id === null) return { success: false, error: 'could not allocate a note id' }\n const now = isoNow()\n notes.set(id, {\n id,\n title,\n content,\n category,\n tags: args.tags ?? [],\n created_at: now,\n updated_at: now,\n })\n await notes.persist()\n return { success: true, note_id: id, message: `Note '${title}' created successfully`, total_count: notes.size }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'list_notes',\n description: 'List notes (filters compose; newest first).',\n parameters: {\n category: { type: 'string', description: 'Exact category filter.' },\n tags: { type: 'array', items: { type: 'string' }, description: 'ANY-match tag filter.' },\n search: { type: 'string', description: 'Substring match on title or content.' },\n include_content: { type: 'boolean', description: 'Full content instead of a 280-char preview.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n notes: { type: 'array', items: { type: 'object', properties: {}, additionalProperties: true }, required: true },\n filtered_count: { type: 'integer', required: true },\n total_count: { type: 'integer', required: true },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { notes?: unknown[] }\n return [{ type: 'text', text: `${String(result.notes?.length ?? 0)} note(s)` }]\n },\n },\n execute: (async (rawArgs: never) => {\n const args = rawArgs as never as { category?: string; tags?: string[]; search?: string; include_content?: boolean }\n const category = args.category?.trim() ?? ''\n const search = args.search?.toLowerCase() ?? ''\n const tags = args.tags ?? []\n const entries = noteList()\n .filter(note => category === '' || note['category'] === category)\n .filter(note => tags.length === 0 || tags.some(tag => (note['tags'] as string[] | undefined)?.includes(tag) === true))\n .filter(note => search === '' || String(note['title']).toLowerCase().includes(search) || String(note['content']).toLowerCase().includes(search))\n .sort((a, b) => String(b['created_at']).localeCompare(String(a['created_at'])))\n .map(note => {\n const listing: Record<string, unknown> = {\n note_id: note.id,\n title: note['title'],\n category: note['category'],\n tags: note['tags'],\n created_at: note['created_at'],\n updated_at: note['updated_at'],\n }\n const content = String(note['content'] ?? '')\n listing[args.include_content === true ? 'content' : 'content_preview'] = args.include_content === true ? content : content.length > 280 ? `${content.slice(0, 280)}...` : content\n return listing\n })\n return { success: true, notes: entries, filtered_count: entries.length, total_count: notes.size }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'get_note',\n description: 'Fetch one note by id.',\n parameters: {\n note_id: { type: 'string', required: true, description: 'Note id from list_notes or create_note.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n note: { type: 'object', properties: {}, additionalProperties: true },\n error: { type: 'string' },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; error?: string }\n return [{ type: 'text', text: result.success ? 'note returned' : `get_note failed: ${result.error ?? 'unknown'}` }]\n },\n },\n execute: (async (rawArgs: never) => {\n const args = rawArgs as never as { note_id?: string }\n const noteId = (args.note_id ?? '').trim()\n if (noteId === '') return { success: false, error: 'Note ID cannot be empty' }\n const note = notes.get(noteId)\n if (note === undefined) return { success: false, error: `Note with ID '${noteId}' not found` }\n return { success: true, note: { ...note, note_id: noteId } }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'update_note',\n description: 'Revise a note (only the fields you pass change; updated_at always bumps).',\n parameters: {\n note_id: { type: 'string', required: true, description: 'Note id.' },\n title: { type: 'string', description: 'Replacement title.' },\n content: { type: 'string', description: 'Replacement content.' },\n tags: { type: 'array', items: { type: 'string' }, description: 'Replacement tags (full replace).' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n note_id: { type: 'string' },\n message: { type: 'string' },\n total_count: { type: 'integer' },\n error: { type: 'string' },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; error?: string }\n return [{ type: 'text', text: result.success ? 'note updated' : `update_note failed: ${result.error ?? 'unknown'}` }]\n },\n },\n execute: (async (rawArgs: never) => {\n const args = rawArgs as never as { note_id?: string; title?: string; content?: string; tags?: string[] }\n const noteId = (args.note_id ?? '').trim()\n const note = notes.get(noteId)\n if (note === undefined) return { success: false, error: `Note with ID '${noteId}' not found` }\n if (args.title !== undefined && args.title.trim() === '') return { success: false, error: 'Title cannot be empty' }\n if (args.content !== undefined && args.content.trim() === '') return { success: false, error: 'Content cannot be empty' }\n if (args.title !== undefined) note['title'] = args.title.trim()\n if (args.content !== undefined) note['content'] = args.content.trim()\n if (args.tags !== undefined) note['tags'] = args.tags\n note['updated_at'] = isoNow()\n await notes.persist()\n return { success: true, note_id: noteId, message: `Note '${String(note['title'])}' updated successfully`, total_count: notes.size }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'delete_note',\n description: 'Delete a note by id.',\n parameters: {\n note_id: { type: 'string', required: true, description: 'Note id.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n note_id: { type: 'string' },\n message: { type: 'string' },\n total_count: { type: 'integer' },\n error: { type: 'string' },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; error?: string }\n return [{ type: 'text', text: result.success ? 'note deleted' : `delete_note failed: ${result.error ?? 'unknown'}` }]\n },\n },\n execute: (async (rawArgs: never) => {\n const args = rawArgs as never as { note_id?: string }\n const noteId = (args.note_id ?? '').trim()\n const note = notes.get(noteId)\n if (note === undefined) return { success: false, error: `Note with ID '${noteId}' not found` }\n notes.delete(noteId)\n await notes.persist()\n return { success: true, note_id: noteId, message: `Note '${String(note['title'])}' deleted successfully`, total_count: notes.size }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'think',\n description: 'Record a private reasoning step (no storage; use notes for persistent knowledge).',\n parameters: {\n thought: { type: 'string', required: true, description: 'The reasoning step.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n message: { type: 'string' },\n error: { type: 'string' },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; error?: string }\n return [{ type: 'text', text: result.success ? 'recorded' : `think failed: ${result.error ?? 'unknown'}` }]\n },\n },\n execute: (async (rawArgs: never) => {\n const args = rawArgs as never as { thought?: string }\n if ((args.thought ?? '').trim() === '') return { success: false, error: 'Thought cannot be empty' }\n return { success: true, message: 'Thought recorded' }\n }) as never,\n }))\n\n // ---- finish_scan ----\n\n ctx.tools.register(defineTool({\n name: 'finish_scan',\n description: 'Complete the scan: validates the four report sections, records the coverage summary, writes the final artifacts, and marks the scan completed. Root agent only.',\n parameters: {\n executive_summary: { type: 'string', required: true, description: 'Non-technical summary for stakeholders.' },\n methodology: { type: 'string', required: true, description: 'How the scan was conducted.' },\n technical_analysis: { type: 'string', required: true, description: 'Technical findings analysis.' },\n recommendations: { type: 'string', required: true, description: 'Prioritized remediation recommendations.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n scan_completed: { type: 'boolean' },\n message: { type: 'string' },\n vulnerabilities_found: { type: 'integer' },\n coverage_recorded: { type: 'integer' },\n coverage_outcomes: { type: 'object', properties: {}, additionalProperties: true },\n coverage_warning: { type: 'string' },\n unresolved_surfaces: { type: 'array', items: { type: 'object', properties: {}, additionalProperties: true } },\n warning: { type: 'string' },\n error: { type: 'string' },\n errors: { type: 'array', items: { type: 'string' } },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; error?: string; errors?: string[] }\n const reason = result.error ?? result.errors?.join('; ') ?? 'unknown'\n return [{ type: 'text', text: result.success ? 'scan completed' : `finish_scan failed: ${reason}` }]\n },\n },\n execute: (async (rawArgs: never) => {\n const args = rawArgs as never as { executive_summary?: string; methodology?: string; technical_analysis?: string; recommendations?: string }\n if (config.allowFinish === false) {\n return { success: false, scan_completed: false, error: 'This tool can only be used by the root/main agent. If you are a subagent, use agent_finish instead' }\n }\n const sections = {\n executiveSummary: (args.executive_summary ?? '').trim(),\n methodology: (args.methodology ?? '').trim(),\n technicalAnalysis: (args.technical_analysis ?? '').trim(),\n recommendations: (args.recommendations ?? '').trim(),\n }\n const errors: string[] = []\n if (sections.executiveSummary === '') errors.push('Executive summary cannot be empty')\n if (sections.methodology === '') errors.push('Methodology cannot be empty')\n if (sections.technicalAnalysis === '') errors.push('Technical analysis cannot be empty')\n if (sections.recommendations === '') errors.push('Recommendations cannot be empty')\n if (errors.length > 0) return { success: false, error: 'Validation failed', errors }\n const summary: Record<string, unknown> = {\n coverage_recorded: coverage.size,\n coverage_outcomes: outcomeCounts(),\n }\n if (coverage.size === 0) {\n summary['coverage_warning'] = 'No coverage was recorded for this scan. The report cannot state what was and was not tested; record coverage with record_coverage in future scans.'\n } else {\n const unresolved = coverageEntries()\n .filter(entry => entry['outcome'] === 'needs_follow_up')\n .map(entry => ({ surface: entry['surface'], risk_area: entry['risk_area'] }))\n if (unresolved.length > 0) {\n summary['coverage_warning'] = `${String(unresolved.length)} surface(s) still need follow-up; they are listed in unresolved_surfaces.`\n summary['unresolved_surfaces'] = unresolved\n }\n }\n const reporting = (ctx as unknown as { pentestReporting?: { finishScan(sections: { executiveSummary: string; methodology: string; technicalAnalysis: string; recommendations: string }, status?: string): Promise<void>; state: { vulnerabilityReports: unknown[] } } }).pentestReporting\n if (reporting === undefined) {\n return { success: true, scan_completed: true, message: 'Scan completed (not persisted)', warning: 'Results could not be persisted - report state unavailable', ...summary }\n }\n await reporting.finishScan(sections)\n return { success: true, scan_completed: true, message: 'Scan completed successfully', vulnerabilities_found: reporting.state.vulnerabilityReports.length, ...summary }\n }) as never,\n }))\n\n return handle\n}\n\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["args","result"],"sources":["../src/stores.ts","../src/index.ts"],"sourcesContent":["/**\n * Run-scoped JSON mirror stores (strix .state/ mirrors): atomic writes\n * (temp-in-dir + rename), hydrate-on-start, per-store in-memory maps.\n * One instance per scan, owned by the analysis service.\n * @module @gpzhang2001/sharpkit-analysis/stores\n */\n\nimport { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\n\n/** A JSON file mirror with atomic write and hydrate. The path may be lazy: a\n * provider is resolved (and re-read) on every persist/hydrate so a store can\n * follow the per-scan run directory resolved at tool-execute time. */\nexport class JsonMirrorStore {\n private readonly pathOrProvider: string | null | (() => string | null | Promise<string | null>)\n private readonly map = new Map<string, Record<string, unknown>>()\n /** Monotonic per-store counter: unique temp names within one process even\n * under concurrent persist() calls (same pid would otherwise collide and\n * one rename would fail with ENOENT — observed once on 2026-09-22). */\n private tempCounter = 0\n\n constructor(path: string | null | (() => string | null | Promise<string | null>)) {\n this.pathOrProvider = path\n }\n\n private async path(): Promise<string | null> {\n return typeof this.pathOrProvider === 'function' ? await Promise.resolve(this.pathOrProvider()) : this.pathOrProvider\n }\n\n /** Load the mirror file; corrupt JSON is tolerated as empty on hydrate (resume aid only). */\n async hydrate(): Promise<void> {\n const path = await this.path()\n if (path === null) return\n try {\n const raw = await readFile(path, 'utf8')\n const parsed = JSON.parse(raw) as unknown\n if (typeof parsed !== 'object' || parsed === null) return\n for (const [key, value] of Object.entries(parsed as Record<string, unknown>)) {\n if (typeof value === 'object' && value !== null) this.map.set(key, value as Record<string, unknown>)\n }\n } catch {\n // Missing or unreadable mirror: start empty.\n }\n }\n\n /** Persist the whole map atomically (strix `_persist_locked`). A rename\n * loss to a concurrent writer of the same store is tolerated: the in-memory\n * map stays authoritative and the mirror is a resume aid. */\n async persist(): Promise<void> {\n const path = await this.path()\n if (path === null) return\n const payload = JSON.stringify(Object.fromEntries(this.map), null, 2)\n await mkdir(dirname(path), { recursive: true })\n const temp = `${dirname(path)}/.${join('', path.split('/').pop() ?? 'store')}.${process.pid}.${this.tempCounter++}.tmp`\n await writeFile(temp, payload, 'utf8')\n try {\n await rename(temp, path)\n } catch (error) {\n await rm(temp, { force: true }).catch(() => undefined)\n if (this.map.size > 0) throw error\n }\n }\n\n get(key: string): Record<string, unknown> | undefined {\n return this.map.get(key)\n }\n\n set(key: string, value: Record<string, unknown>): void {\n this.map.set(key, value)\n }\n\n delete(key: string): boolean {\n return this.map.delete(key)\n }\n\n values(): Record<string, unknown>[] {\n return [...this.map.values()]\n }\n\n get size(): number {\n return this.map.size\n }\n}\n\n/** Generate a 6-hex id with bounded collision retries (strix parity). */\nexport function generateId(existing: ReadonlySet<string>): string | null {\n for (let attempt = 0; attempt < 1024; attempt++) {\n const id = Math.random().toString(16).slice(2, 8).padEnd(6, '0')\n if (!existing.has(id)) return id\n }\n return null\n}\n\n/**\n * Normalize a remote target to its identity (threat_model/tools.py\n * `_normalize_remote_target` + `_normalize_git_remote`): scp-style and\n * scheme'd git remotes map to https URLs, `.git` stripped, trailing slash\n * stripped, host lowercased with default ports collapsed.\n * @param target - the raw target string.\n */\nexport function normalizeTargetIdentity(target: string): string {\n const trimmed = target.trim()\n const scp = /^git@([^:]+):(.+?)(?:\\.git)?$/.exec(trimmed)\n if (scp !== null) return `https://${scp[1]?.toLowerCase()}/${scp[2]}`\n const withScheme = /^([a-z][a-z0-9+.-]*):\\/\\/([^/?#]+)([^#]*)/i.exec(trimmed)\n if (withScheme !== null) {\n const scheme = withScheme[1] ?? ''\n const authority = withScheme[2] ?? ''\n const path = withScheme[3] ?? ''\n const defaultPort = scheme.toLowerCase() === 'https' ? '443' : scheme.toLowerCase() === 'http' ? '80' : null\n let host = authority.toLowerCase()\n if (defaultPort !== null && host.endsWith(`:${defaultPort}`)) host = host.slice(0, -defaultPort.length - 1)\n return `https://${host}${path.replace(/\\/$/, '')}`.replace(/\\.git$/, '')\n }\n return trimmed.replace(/\\/$/, '').replace(/\\.git$/, '')\n}\n","/**\n * Analysis tools — port of strix tools/{threat_model,coverage,notes,\n * thinking}/ + finish_scan: get/save/amend_threat_model (validated\n * sections, size gates, append-only amendments), record/update/\n * list_coverage (duplicate guard, evidence requirements, history),\n * create/list/get/update/delete_note, think, and finish_scan (root-only,\n * four required sections, coverage summary, final artifacts). State lives\n * in per-scan mirror stores under the run dir (.state parity) and is\n * provided as `pentestAnalysis` for the reporting package's coverage\n * document and SARIF bridge.\n * @module @gpzhang2001/sharpkit-analysis\n */\n\nimport { join, resolve } from 'node:path'\nimport type { Context } from '@deepseek-ai/cordis'\nimport { defineTool } from '@deepseek-ai/dsh-tools'\nimport { JsonMirrorStore, generateId, normalizeTargetIdentity } from './stores.ts'\n\nexport { normalizeTargetIdentity } from './stores.ts'\n\n/** Coverage outcome values (strix VALID_OUTCOMES). */\nexport const VALID_OUTCOMES = ['reported', 'no_issue_found', 'ruled_out', 'not_applicable', 'needs_follow_up'] as const\n\n/** Outcomes that require evidence text (strix `_OUTCOMES_REQUIRING_EVIDENCE`). */\nconst OUTCOMES_REQUIRING_EVIDENCE = new Set(['ruled_out', 'not_applicable', 'needs_follow_up'])\n\n/** Note categories (strix `_VALID_NOTE_CATEGORIES`). */\nexport const VALID_NOTE_CATEGORIES = ['general', 'findings', 'methodology', 'questions', 'plan', 'wiki'] as const\n\n/** Required threat-model sections, matched as lowercase substrings (strix parity). */\nconst REQUIRED_SECTIONS = ['overview', 'trust boundaries', 'attack surface', 'severity calibration'] as const\n\n/** Threat model gates (strix constants). */\nconst MAX_MODEL_BYTES = 512 * 1024\nconst MIN_MODEL_CHARS = 400\nconst MIN_AMENDMENT_CHARS = 80\nconst MAX_AMENDMENTS = 40\n\n/** Display timestamp in the coverage format. */\nfunction displayTimestamp(date: Date): string {\n const pad = (value: number): string => String(value).padStart(2, '0')\n return `${date.getUTCFullYear()}-${pad(date.getUTCMonth() + 1)}-${pad(date.getUTCDate())} ${pad(date.getUTCHours())}:${pad(date.getUTCMinutes())}:${pad(date.getUTCSeconds())} UTC`\n}\n\n/** Deployment-tunable configuration. */\nexport interface Config {\n /** Sandbox session cache key shared with the other tool packages. */\n readonly scanId?: string\n /** Scan targets (threat-model identity snapping). */\n readonly scanTargets?: readonly string[]\n /** Run directory for the .state mirrors (default: the reporting package's\n * per-scan runDirFor() — session cwd + multi-scan isolation; the mirrors sit\n * in <that>/.state alongside the artifacts). */\n readonly runDir?: string\n /** Whether this composition's agent may call finish_scan (root guard). */\n readonly allowFinish?: boolean\n}\n\nexport const name = 'pentest-tool-analysis'\n\nexport const inject = ['tools', 'pentestReporting']\n\n/** The per-scan analysis stores exposed to other packages. */\nexport interface AnalysisHandle {\n readonly coverage: JsonMirrorStore\n readonly threatModels: JsonMirrorStore\n readonly notes: JsonMirrorStore\n /** Coverage entries in list order with ids (reporting package consumes). */\n coverageEntries(): Array<Record<string, unknown> & { entry_id: string }>\n outcomeCounts(): Record<string, number>\n}\n\ndeclare module '@deepseek-ai/cordis' {\n interface Context {\n pentestAnalysis: AnalysisHandle\n }\n}\n\n/** ISO-8601 timestamp (threat model + notes stores). */\nfunction isoNow(): string {\n return new Date().toISOString().replace('Z', '+00:00')\n}\n\nexport function apply(ctx: Context, config: Config = {}): AnalysisHandle {\n const scanId = config.scanId ?? 'pentest'\n\n /**\n * The .state mirrors live in the SAME run directory as the reporting\n * artifacts (they previously split off to a fixed 'sharpkit_runs/pentest'\n * while reporting used its configured runName — two directories for one\n * scan). The path resolves lazily per tool execution: an explicit\n * config.runDir wins (tests, E2E); otherwise the reporting package's\n * runDirFor() decides (session cwd + per-scan isolation).\n */\n interface ReportingSource { runDirFor?(session: unknown): Promise<string> }\n let activeSession: unknown\n let hydratedFor: unknown\n let hydratePromise: Promise<void> | undefined\n const noteExec = async (rawExec: never): Promise<void> => {\n const session = (rawExec as { agent?: { session?: unknown } } | undefined)?.agent?.session\n if (session === undefined) return\n activeSession = session\n if (hydratedFor === session || hydratePromise !== undefined) return\n hydratedFor = session\n hydratePromise = Promise.all([coverage.hydrate(), threatModels.hydrate(), notes.hydrate()]).then(() => undefined)\n await hydratePromise\n hydratePromise = undefined\n }\n const stateDirOf = async (): Promise<string | null> => {\n if (config.runDir !== undefined) return join(resolve(config.runDir), '.state')\n const reporting = (ctx as unknown as { pentestReporting?: ReportingSource }).pentestReporting\n const runDir = await reporting?.runDirFor?.(activeSession)\n return runDir === undefined ? null : join(runDir, '.state')\n }\n const mirrorPath = (name: string): (() => Promise<string | null>) => async () => {\n const dir = await stateDirOf()\n return dir === null ? null : join(dir, name)\n }\n const coverage = new JsonMirrorStore(mirrorPath('coverage.json'))\n const threatModels = new JsonMirrorStore(mirrorPath('threat_models.json'))\n const notes = new JsonMirrorStore(mirrorPath('notes.json'))\n\n const coverageEntries = (): Array<Record<string, unknown> & { entry_id: string }> =>\n coverage.values().map(entry => ({ ...entry, entry_id: String(entry['id'] ?? '') })) as Array<Record<string, unknown> & { entry_id: string }>\n\n const outcomeCounts = (): Record<string, number> => {\n const counts: Record<string, number> = {}\n for (const outcome of VALID_OUTCOMES) {\n const count = coverage.values().filter(entry => entry['outcome'] === outcome).length\n if (count > 0) counts[outcome] = count\n }\n return counts\n }\n\n const handle: AnalysisHandle = { coverage, threatModels, notes, coverageEntries, outcomeCounts }\n ctx.provide('pentestAnalysis', handle)\n\n const resolveTarget = (target: string | undefined): { readonly identity: string } | { readonly error: string } => {\n const cleaned = target?.trim() ?? ''\n if (cleaned === '') {\n const targets = config.scanTargets ?? []\n if (targets.length === 1) return { identity: normalizeTargetIdentity(targets[0] ?? '') }\n return { error: 'target cannot be empty - name the target this threat model describes' }\n }\n return { identity: normalizeTargetIdentity(cleaned) }\n }\n\n ctx.tools.register(defineTool({\n name: 'get_threat_model',\n description: 'Fetch the threat model shared for a target in this scan (found=false when none exists yet). Read-only.',\n parameters: {\n target: { type: 'string', description: 'Target the model describes; omit when the scan has exactly one target.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n found: { type: 'boolean' },\n target: { type: 'string' },\n content: { type: 'string' },\n amendments: { type: 'array', items: { type: 'object', properties: {}, additionalProperties: true } },\n amendments_note: { type: 'string' },\n message: { type: 'string' },\n error: { type: 'string' },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; found?: boolean; error?: string }\n if (!result.success) return [{ type: 'text', text: `get_threat_model failed: ${result.error ?? 'unknown'}` }]\n return [{ type: 'text', text: result.found === true ? 'threat model found' : 'no threat model yet' }]\n },\n },\n execute: (async (rawArgs: never, rawExec: never) => {\n await noteExec(rawExec)\n const args = rawArgs as never as { target?: string }\n const resolved = resolveTarget(args.target)\n if ('error' in resolved) return { success: false, error: resolved.error }\n const model = threatModels.get(resolved.identity)\n if (model === undefined || String(model['content'] ?? '').trim() === '') {\n return { success: true, found: false, target: resolved.identity, message: 'No threat model exists for this target yet. Save one with save_threat_model.' }\n }\n const amendments = model['amendments'] as unknown[] | undefined\n return {\n success: true,\n found: true,\n target: resolved.identity,\n content: String(model['content']),\n ...(amendments !== undefined && amendments.length > 0 ? { amendments, amendments_note: 'Addenda recorded by agents after the model was saved.' } : {}),\n }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'save_threat_model',\n description: `Share your threat model for a target with the whole scan team (full replace; clears amendments). Must cover Overview, Trust Boundaries, Attack Surface, and Severity Calibration; minimum ${String(MIN_MODEL_CHARS)} characters, maximum 512KB.`,\n parameters: {\n target: { type: 'string', description: 'Target the model describes; omit when the scan has exactly one target.' },\n content: { type: 'string', required: true, description: 'The full markdown threat model.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n target: { type: 'string' },\n amendments_cleared: { type: 'integer' },\n message: { type: 'string' },\n error: { type: 'string' },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; error?: string }\n return [{ type: 'text', text: result.success ? 'threat model saved' : `save_threat_model failed: ${result.error ?? 'unknown'}` }]\n },\n },\n execute: (async (rawArgs: never, rawExec: never) => {\n await noteExec(rawExec)\n const args = rawArgs as never as { target?: string; content: string }\n const resolved = resolveTarget(args.target)\n if ('error' in resolved) return { success: false, error: resolved.error }\n const content = args.content.trim()\n if (content.length < MIN_MODEL_CHARS) {\n return { success: false, error: `Threat model is too thin (${String(content.length)} chars). It has to be usable by every agent in this scan.` }\n }\n if (Buffer.byteLength(content, 'utf8') > MAX_MODEL_BYTES) return { success: false, error: 'Threat model exceeds 512KB; tighten it.' }\n const lower = content.toLowerCase()\n const missing = REQUIRED_SECTIONS.filter(section => !lower.includes(section))\n if (missing.length > 0) {\n return { success: false, error: `Threat model is missing required section(s): ${missing.join(', ')}. Cover Overview, Trust Boundaries, Attack Surface, and Severity Calibration.` }\n }\n const previous = threatModels.get(resolved.identity)\n const previousAmendments = previous?.['amendments']\n const amendmentsCleared = Array.isArray(previousAmendments) ? previousAmendments.length : 0\n threatModels.set(resolved.identity, {\n target: resolved.identity,\n written_at: isoNow(),\n written_by: null,\n content,\n })\n await threatModels.persist()\n return {\n success: true,\n target: resolved.identity,\n amendments_cleared: amendmentsCleared,\n message: amendmentsCleared > 0\n ? `Threat model shared with this scan. It replaced a previous model, folding ${String(amendmentsCleared)} amendment(s) into the full rewrite.`\n : 'Threat model shared with this scan.',\n }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'amend_threat_model',\n description: `Append an addendum to the existing threat model without rewriting it (minimum ${String(MIN_AMENDMENT_CHARS)} characters). Use save_threat_model for a full rewrite that folds amendments.`,\n parameters: {\n target: { type: 'string', description: 'Target the model describes; omit when the scan has exactly one target.' },\n addendum: { type: 'string', required: true, description: 'The markdown addendum.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n target: { type: 'string' },\n amendment_count: { type: 'integer' },\n message: { type: 'string' },\n error: { type: 'string' },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; error?: string }\n return [{ type: 'text', text: result.success ? 'amendment recorded' : `amend_threat_model failed: ${result.error ?? 'unknown'}` }]\n },\n },\n execute: (async (rawArgs: never, rawExec: never) => {\n await noteExec(rawExec)\n const args = rawArgs as never as { target?: string; addendum: string }\n const resolved = resolveTarget(args.target)\n if ('error' in resolved) return { success: false, error: resolved.error }\n const addendum = args.addendum.trim()\n if (addendum.length < MIN_AMENDMENT_CHARS) {\n return { success: false, error: `Amendment is too thin (${String(addendum.length)} chars). Give the new knowledge in full.` }\n }\n const model = threatModels.get(resolved.identity)\n if (model === undefined) return { success: false, error: 'No threat model exists for this target yet. Save the full model with save_threat_model first.' }\n const amendments = (model['amendments'] as unknown[] | undefined) ?? []\n if (amendments.length >= MAX_AMENDMENTS) {\n return { success: false, error: `This threat model already carries ${String(amendments.length)} amendments. Fold them into a full save_threat_model rewrite.` }\n }\n if (Buffer.byteLength(String(model['content']) + addendum, 'utf8') > MAX_MODEL_BYTES) {\n return { success: false, error: 'Combined threat model exceeds 512KB; fold amendments with save_threat_model.' }\n }\n amendments.push({ at: isoNow(), by: null, content: addendum })\n model['amendments'] = amendments\n await threatModels.persist()\n return { success: true, target: resolved.identity, amendment_count: amendments.length, message: 'Amendment recorded. Every agent reading the threat model will see it.' }\n }) as never,\n }))\n\n // ---- coverage ----\n\n interface CoverageValidateInput {\n readonly surface: string\n readonly riskArea: string\n readonly outcome: string\n readonly evidence: string\n }\n\n /** strix coverage `_validate`: normalization + per-field errors. */\n function validateCoverage(input: CoverageValidateInput): { readonly outcome: string } | { readonly errors: string[] } {\n const errors: string[] = []\n if (input.surface === '') errors.push('surface cannot be empty - name the endpoint, route, file, or component')\n if (input.riskArea === '') errors.push('risk_area cannot be empty - name what you were testing for')\n const outcome = input.outcome.trim().toLowerCase().replaceAll('-', '_').replaceAll(' ', '_')\n if (!(VALID_OUTCOMES as readonly string[]).includes(outcome)) {\n errors.push(`Invalid outcome: '${input.outcome}'. Must be one of: [${VALID_OUTCOMES.join(', ')}]`)\n }\n if (OUTCOMES_REQUIRING_EVIDENCE.has(outcome) && input.evidence === '') {\n errors.push(`evidence is required for outcome '${outcome}' - name the specific control, response, or observation that justifies it`)\n }\n return errors.length > 0 ? { errors } : { outcome }\n }\n\n const findCoverageDuplicate = (surface: string, riskArea: string): Record<string, unknown> | undefined =>\n coverage.values().find(entry => String(entry['surface']).toLowerCase() === surface.toLowerCase() && String(entry['risk_area']).toLowerCase() === riskArea.toLowerCase())\n\n ctx.tools.register(defineTool({\n name: 'record_coverage',\n description: \"Record that you exercised an attack surface for a risk area, with an outcome. Outcomes: reported / no_issue_found / ruled_out / not_applicable / needs_follow_up. ruled_out, not_applicable, and needs_follow_up REQUIRE evidence. One entry per (surface, risk_area).\",\n parameters: {\n surface: { type: 'string', required: true, description: 'The endpoint, route, file, or component you exercised.' },\n risk_area: { type: 'string', required: true, description: 'What you were testing for (e.g. \"SQL injection in login\").' },\n outcome: { type: 'string', required: true, enum: [...VALID_OUTCOMES], description: 'The testing outcome.' },\n evidence: { type: 'string', description: 'Concrete observation justifying the outcome (required for some outcomes).' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n entry_id: { type: 'string' },\n outcome: { type: 'string' },\n message: { type: 'string' },\n error: { type: 'string' },\n errors: { type: 'array', items: { type: 'string' } },\n existing_entry_id: { type: 'string' },\n existing_outcome: { type: 'string' },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; error?: string; errors?: string[] }\n const reason = result.error ?? result.errors?.join('; ') ?? 'unknown'\n return [{ type: 'text', text: result.success ? 'coverage recorded' : `record_coverage failed: ${reason}` }]\n },\n },\n execute: (async (rawArgs: never, rawExec: never) => {\n await noteExec(rawExec)\n const args = rawArgs as never as { surface?: string; risk_area?: string; outcome?: string; evidence?: string }\n const surface = (args.surface ?? '').trim()\n const riskArea = (args.risk_area ?? '').trim()\n const evidence = (args.evidence ?? '').trim()\n const checked = validateCoverage({ surface, riskArea, outcome: args.outcome ?? '', evidence })\n if ('errors' in checked) return { success: false, error: 'Validation failed', errors: checked.errors }\n const duplicate = findCoverageDuplicate(surface, riskArea)\n if (duplicate !== undefined) {\n return {\n success: false,\n error: `'${surface}' (${riskArea}) already has coverage entry ${String(duplicate['id'])}, recorded by ${String(duplicate['agent_name'] ?? 'an agent')} as '${String(duplicate['outcome'])}'. Two rows for the same surface and risk area are never allowed; update_coverage to change the outcome.`,\n existing_entry_id: String(duplicate['id']),\n existing_outcome: String(duplicate['outcome']),\n }\n }\n const id = generateId(new Set(coverage.values().map(entry => String(entry['id'] ?? ''))))\n if (id === null) return { success: false, error: 'could not allocate a coverage entry id' }\n coverage.set(id, {\n id,\n surface,\n risk_area: riskArea,\n outcome: checked.outcome,\n created_at: displayTimestamp(new Date()),\n ...(evidence !== '' ? { evidence } : {}),\n })\n await coverage.persist()\n return { success: true, entry_id: id, outcome: checked.outcome, message: `Coverage recorded for '${surface}' (${checked.outcome})` }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'update_coverage',\n description: \"Move an existing coverage entry to a new outcome (surface and risk_area are never editable). The previous state is kept as history.\",\n parameters: {\n entry_id: { type: 'string', required: true, description: 'Id from record_coverage or list_coverage.' },\n outcome: { type: 'string', required: true, enum: [...VALID_OUTCOMES], description: 'The new outcome.' },\n evidence: { type: 'string', description: 'Evidence for the new outcome (required for some outcomes).' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n entry_id: { type: 'string' },\n previous_outcome: { type: 'string' },\n outcome: { type: 'string' },\n message: { type: 'string' },\n error: { type: 'string' },\n errors: { type: 'array', items: { type: 'string' } },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; error?: string; errors?: string[] }\n const reason = result.error ?? result.errors?.join('; ') ?? 'unknown'\n return [{ type: 'text', text: result.success ? 'coverage updated' : `update_coverage failed: ${reason}` }]\n },\n },\n execute: (async (rawArgs: never, rawExec: never) => {\n await noteExec(rawExec)\n const args = rawArgs as never as { entry_id?: string; outcome?: string; evidence?: string }\n const entryId = (args.entry_id ?? '').trim()\n const entry = coverage.get(entryId)\n if (entry === undefined) return { success: false, error: `No coverage entry '${entryId}'. Call list_coverage to see recorded entries.` }\n const surface = String(entry['surface'] ?? '')\n const riskArea = String(entry['risk_area'] ?? '')\n const evidence = (args.evidence ?? '').trim()\n const checked = validateCoverage({ surface, riskArea, outcome: args.outcome ?? '', evidence })\n if ('errors' in checked) return { success: false, error: 'Validation failed', errors: checked.errors }\n const previousOutcome = String(entry['outcome'])\n const history = (entry['history'] as Array<Record<string, unknown>> | undefined) ?? []\n const prior: Record<string, unknown> = { outcome: previousOutcome, recorded_at: String(entry['created_at']) }\n if (entry['evidence'] !== undefined) prior['evidence'] = entry['evidence']\n history.push(prior)\n entry['history'] = history\n entry['outcome'] = checked.outcome\n entry['updated_at'] = displayTimestamp(new Date())\n if (evidence !== '') entry['evidence'] = evidence\n await coverage.persist()\n return {\n success: true,\n entry_id: entryId,\n previous_outcome: previousOutcome,\n outcome: checked.outcome,\n message: `'${surface}' (${riskArea}) moved from ${previousOutcome} to ${checked.outcome}. The previous state is kept as history.`,\n }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'list_coverage',\n description: 'List coverage entries recorded in this scan (filters compose; outcome counts are over all entries).',\n parameters: {\n outcome: { type: 'string', description: 'Filter to one outcome.' },\n surface: { type: 'string', description: 'Case-insensitive substring match on the surface.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n entries: { type: 'array', items: { type: 'object', properties: {}, additionalProperties: true }, required: true },\n filtered_count: { type: 'integer', required: true },\n total_count: { type: 'integer', required: true },\n outcome_counts: { type: 'object', properties: {}, additionalProperties: true, required: true },\n error: { type: 'string' },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { entries?: unknown[]; error?: string }\n if (result.error !== undefined) return [{ type: 'text', text: `list_coverage failed: ${result.error}` }]\n return [{ type: 'text', text: `${String(result.entries?.length ?? 0)} coverage entry(ies)` }]\n },\n },\n execute: (async (rawArgs: never, rawExec: never) => {\n await noteExec(rawExec)\n const args = rawArgs as never as { outcome?: string; surface?: string }\n let outcomeFilter: string | undefined\n if (args.outcome !== undefined && args.outcome !== '') {\n outcomeFilter = args.outcome.trim().toLowerCase().replaceAll('-', '_').replaceAll(' ', '_')\n if (outcomeFilter !== undefined && !(VALID_OUTCOMES as readonly string[]).includes(outcomeFilter)) {\n return {\n success: false,\n error: `Invalid outcome: '${args.outcome}'. Must be one of: [${VALID_OUTCOMES.join(', ')}]`,\n entries: [],\n filtered_count: 0,\n total_count: coverage.size,\n outcome_counts: {},\n }\n }\n }\n const surfaceFilter = args.surface?.toLowerCase() ?? ''\n const entries = coverageEntries()\n .filter(entry => (outcomeFilter === undefined || entry['outcome'] === outcomeFilter))\n .filter(entry => surfaceFilter === '' || String(entry['surface']).toLowerCase().includes(surfaceFilter))\n .sort((a, b) => String(a['created_at']).localeCompare(String(b['created_at'])))\n .map(entry => {\n const listing: Record<string, unknown> = {\n entry_id: entry.entry_id,\n surface: entry['surface'],\n risk_area: entry['risk_area'],\n outcome: entry['outcome'],\n created_at: entry['created_at'],\n }\n const evidence = entry['evidence']\n if (typeof evidence === 'string' && evidence !== '') {\n listing['evidence'] = evidence.length > 240 ? `${evidence.slice(0, 240)}...` : evidence\n }\n const history = entry['history'] as Array<Record<string, unknown>> | undefined\n if (history !== undefined && history.length > 0) {\n listing['previous_outcomes'] = history.map(item => item['outcome'])\n }\n return listing\n })\n return {\n success: true,\n entries,\n filtered_count: entries.length,\n total_count: coverage.size,\n outcome_counts: outcomeCounts(),\n }\n }) as never,\n }))\n\n // ---- notes ----\n\n const noteList = (): Array<Record<string, unknown> & { id: string }> =>\n notes.values().map(entry => ({ ...entry, id: String(entry['id'] ?? '') })) as Array<Record<string, unknown> & { id: string }>\n\n ctx.tools.register(defineTool({\n name: 'create_note',\n description: 'Create a persistent note shared across the scan team (categories: general/findings/methodology/questions/plan/wiki).',\n parameters: {\n title: { type: 'string', required: true, description: 'Short note title.' },\n content: { type: 'string', required: true, description: 'The note body.' },\n category: { type: 'string', enum: [...VALID_NOTE_CATEGORIES], description: 'Note category (default general).' },\n tags: { type: 'array', items: { type: 'string' }, description: 'Optional tags.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n note_id: { type: 'string' },\n message: { type: 'string' },\n total_count: { type: 'integer' },\n error: { type: 'string' },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; error?: string }\n return [{ type: 'text', text: result.success ? 'note created' : `create_note failed: ${result.error ?? 'unknown'}` }]\n },\n },\n execute: (async (rawArgs: never, rawExec: never) => {\n await noteExec(rawExec)\n const args = rawArgs as never as { title?: string; content?: string; category?: string; tags?: string[] }\n const title = (args.title ?? '').trim()\n const content = (args.content ?? '').trim()\n const category = (args.category ?? 'general').trim()\n if (title === '') return { success: false, error: 'Title cannot be empty' }\n if (content === '') return { success: false, error: 'Content cannot be empty' }\n if (!(VALID_NOTE_CATEGORIES as readonly string[]).includes(category)) {\n return { success: false, error: `Invalid category. Must be one of: ${VALID_NOTE_CATEGORIES.join(', ')}` }\n }\n const id = generateId(new Set(noteList().map(note => note.id)))\n if (id === null) return { success: false, error: 'could not allocate a note id' }\n const now = isoNow()\n notes.set(id, {\n id,\n title,\n content,\n category,\n tags: args.tags ?? [],\n created_at: now,\n updated_at: now,\n })\n await notes.persist()\n return { success: true, note_id: id, message: `Note '${title}' created successfully`, total_count: notes.size }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'list_notes',\n description: 'List notes (filters compose; newest first).',\n parameters: {\n category: { type: 'string', description: 'Exact category filter.' },\n tags: { type: 'array', items: { type: 'string' }, description: 'ANY-match tag filter.' },\n search: { type: 'string', description: 'Substring match on title or content.' },\n include_content: { type: 'boolean', description: 'Full content instead of a 280-char preview.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n notes: { type: 'array', items: { type: 'object', properties: {}, additionalProperties: true }, required: true },\n filtered_count: { type: 'integer', required: true },\n total_count: { type: 'integer', required: true },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { notes?: unknown[] }\n return [{ type: 'text', text: `${String(result.notes?.length ?? 0)} note(s)` }]\n },\n },\n execute: (async (rawArgs: never, rawExec: never) => {\n await noteExec(rawExec)\n const args = rawArgs as never as { category?: string; tags?: string[]; search?: string; include_content?: boolean }\n const category = args.category?.trim() ?? ''\n const search = args.search?.toLowerCase() ?? ''\n const tags = args.tags ?? []\n const entries = noteList()\n .filter(note => category === '' || note['category'] === category)\n .filter(note => tags.length === 0 || tags.some(tag => (note['tags'] as string[] | undefined)?.includes(tag) === true))\n .filter(note => search === '' || String(note['title']).toLowerCase().includes(search) || String(note['content']).toLowerCase().includes(search))\n .sort((a, b) => String(b['created_at']).localeCompare(String(a['created_at'])))\n .map(note => {\n const listing: Record<string, unknown> = {\n note_id: note.id,\n title: note['title'],\n category: note['category'],\n tags: note['tags'],\n created_at: note['created_at'],\n updated_at: note['updated_at'],\n }\n const content = String(note['content'] ?? '')\n listing[args.include_content === true ? 'content' : 'content_preview'] = args.include_content === true ? content : content.length > 280 ? `${content.slice(0, 280)}...` : content\n return listing\n })\n return { success: true, notes: entries, filtered_count: entries.length, total_count: notes.size }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'get_note',\n description: 'Fetch one note by id.',\n parameters: {\n note_id: { type: 'string', required: true, description: 'Note id from list_notes or create_note.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n note: { type: 'object', properties: {}, additionalProperties: true },\n error: { type: 'string' },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; error?: string }\n return [{ type: 'text', text: result.success ? 'note returned' : `get_note failed: ${result.error ?? 'unknown'}` }]\n },\n },\n execute: (async (rawArgs: never, rawExec: never) => {\n await noteExec(rawExec)\n const args = rawArgs as never as { note_id?: string }\n const noteId = (args.note_id ?? '').trim()\n if (noteId === '') return { success: false, error: 'Note ID cannot be empty' }\n const note = notes.get(noteId)\n if (note === undefined) return { success: false, error: `Note with ID '${noteId}' not found` }\n return { success: true, note: { ...note, note_id: noteId } }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'update_note',\n description: 'Revise a note (only the fields you pass change; updated_at always bumps).',\n parameters: {\n note_id: { type: 'string', required: true, description: 'Note id.' },\n title: { type: 'string', description: 'Replacement title.' },\n content: { type: 'string', description: 'Replacement content.' },\n tags: { type: 'array', items: { type: 'string' }, description: 'Replacement tags (full replace).' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n note_id: { type: 'string' },\n message: { type: 'string' },\n total_count: { type: 'integer' },\n error: { type: 'string' },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; error?: string }\n return [{ type: 'text', text: result.success ? 'note updated' : `update_note failed: ${result.error ?? 'unknown'}` }]\n },\n },\n execute: (async (rawArgs: never, rawExec: never) => {\n await noteExec(rawExec)\n const args = rawArgs as never as { note_id?: string; title?: string; content?: string; tags?: string[] }\n const noteId = (args.note_id ?? '').trim()\n const note = notes.get(noteId)\n if (note === undefined) return { success: false, error: `Note with ID '${noteId}' not found` }\n if (args.title !== undefined && args.title.trim() === '') return { success: false, error: 'Title cannot be empty' }\n if (args.content !== undefined && args.content.trim() === '') return { success: false, error: 'Content cannot be empty' }\n if (args.title !== undefined) note['title'] = args.title.trim()\n if (args.content !== undefined) note['content'] = args.content.trim()\n if (args.tags !== undefined) note['tags'] = args.tags\n note['updated_at'] = isoNow()\n await notes.persist()\n return { success: true, note_id: noteId, message: `Note '${String(note['title'])}' updated successfully`, total_count: notes.size }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'delete_note',\n description: 'Delete a note by id.',\n parameters: {\n note_id: { type: 'string', required: true, description: 'Note id.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n note_id: { type: 'string' },\n message: { type: 'string' },\n total_count: { type: 'integer' },\n error: { type: 'string' },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; error?: string }\n return [{ type: 'text', text: result.success ? 'note deleted' : `delete_note failed: ${result.error ?? 'unknown'}` }]\n },\n },\n execute: (async (rawArgs: never, rawExec: never) => {\n await noteExec(rawExec)\n const args = rawArgs as never as { note_id?: string }\n const noteId = (args.note_id ?? '').trim()\n const note = notes.get(noteId)\n if (note === undefined) return { success: false, error: `Note with ID '${noteId}' not found` }\n notes.delete(noteId)\n await notes.persist()\n return { success: true, note_id: noteId, message: `Note '${String(note['title'])}' deleted successfully`, total_count: notes.size }\n }) as never,\n }))\n\n ctx.tools.register(defineTool({\n name: 'think',\n description: 'Record a private reasoning step (no storage; use notes for persistent knowledge).',\n parameters: {\n thought: { type: 'string', required: true, description: 'The reasoning step.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n message: { type: 'string' },\n error: { type: 'string' },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; error?: string }\n return [{ type: 'text', text: result.success ? 'recorded' : `think failed: ${result.error ?? 'unknown'}` }]\n },\n },\n execute: (async (rawArgs: never, rawExec: never) => {\n await noteExec(rawExec)\n const args = rawArgs as never as { thought?: string }\n if ((args.thought ?? '').trim() === '') return { success: false, error: 'Thought cannot be empty' }\n return { success: true, message: 'Thought recorded' }\n }) as never,\n }))\n\n // ---- finish_scan ----\n\n /**\n * Best-effort capture reconciliation at scan close: count the Caido proxy\n * history for this scan's sandbox session. Structural access (no hard\n * dependency on the proxy package): pentestSandbox → caidoEndpoint → one\n * GraphQL count query. Reconciliation must never block or fail scan\n * completion — but a failure is never silent either: the reason travels\n * back in `note` (into the tool result, the finish_scan render, and\n * run.json). The previous `catch → undefined` swallowed a live breakage\n * for a full day unnoticed (2026-09-22: run.json carried no capture\n * fields at all while capture_summary worked seconds earlier).\n */\n const proxyCapturedRequests = async (): Promise<{ captured?: number; note?: string }> => {\n const fail = (reason: string): { note: string } => {\n ctx.logger?.warn(`pentest-analysis: capture reconcile failed: ${reason}`)\n return { note: `Proxy capture count unknown: ${reason}` }\n }\n try {\n // ctx.get (not property access): the cordis property proxy throws\n // `cannot get property \"pentestSandbox\" without inject` in a real\n // runtime when the service is not in `inject` — this exact breakage\n // hid behind the old silent catch for a full day (2026-09-22).\n // ctx.get reads the service store without the inject requirement\n // (same seam as tool-reporting's ctx.get('pentestAnalysis')).\n const sandbox = ctx.get('pentestSandbox') as\n | { createSession(options: { scanId: string }): Promise<{ caidoEndpoint(): Promise<{ baseUrl: string; token: string }> }> }\n | undefined\n if (sandbox === undefined) return fail('sandbox service unavailable')\n const session = await sandbox.createSession({ scanId })\n const endpoint = await session.caidoEndpoint()\n const response = await fetch(`${endpoint.baseUrl.replace(/\\/$/, '')}/graphql`, {\n method: 'POST',\n headers: { 'content-type': 'application/json', authorization: `Bearer ${endpoint.token}` },\n body: JSON.stringify({\n query: 'query CaptureReconcile { requests(first: 1, order: {by: CREATED_AT, ordering: DESC}) { count { value } } }',\n }),\n signal: AbortSignal.timeout(5_000),\n })\n if (!response.ok) return fail(`Caido answered HTTP ${String(response.status)}`)\n const payload = await response.json() as { data?: { requests?: { count?: { value?: unknown } | null } | null }; errors?: Array<{ message?: unknown }> | null }\n if (payload.errors !== undefined && payload.errors !== null && payload.errors.length > 0) return fail(`Caido: ${String(payload.errors[0]?.message ?? 'GraphQL error')}`)\n const value = payload.data?.requests?.count?.value\n if (value === null || value === undefined) return fail('Caido returned no request count')\n return { captured: Number(value) }\n } catch (error) {\n return fail(error instanceof Error ? error.message : String(error))\n }\n }\n\n ctx.tools.register(defineTool({\n name: 'finish_scan',\n description: 'Complete the scan: validates the four report sections, records the coverage summary, writes the final artifacts, and marks the scan completed. Root agent only.',\n parameters: {\n executive_summary: { type: 'string', required: true, description: 'Non-technical summary for stakeholders.' },\n methodology: { type: 'string', required: true, description: 'How the scan was conducted.' },\n technical_analysis: { type: 'string', required: true, description: 'Technical findings analysis.' },\n recommendations: { type: 'string', required: true, description: 'Prioritized remediation recommendations.' },\n },\n output: {\n schema: {\n type: 'object',\n properties: {\n success: { type: 'boolean', required: true },\n scan_completed: { type: 'boolean' },\n message: { type: 'string' },\n vulnerabilities_found: { type: 'integer' },\n coverage_recorded: { type: 'integer' },\n coverage_outcomes: { type: 'object', properties: {}, additionalProperties: true },\n coverage_warning: { type: 'string' },\n unresolved_surfaces: { type: 'array', items: { type: 'object', properties: {}, additionalProperties: true } },\n proxy_captured_requests: { type: 'integer', description: 'Requests in the Caido proxy history at close time (the queryable evidence trail).' },\n capture_warning: { type: 'string' },\n capture_note: { type: 'string' },\n warning: { type: 'string' },\n error: { type: 'string' },\n errors: { type: 'array', items: { type: 'string' } },\n },\n additionalProperties: false,\n },\n render: (_args, value) => {\n const result = value as { success: boolean; error?: string; errors?: string[]; proxy_captured_requests?: number; capture_warning?: string; capture_note?: string }\n const reason = result.error ?? result.errors?.join('; ') ?? 'unknown'\n if (!result.success) return [{ type: 'text', text: `finish_scan failed: ${reason}` }]\n // The capture reconciliation travels with the completion line so the\n // agent closes its summary against the real evidence-trail size.\n const capture = result.capture_warning !== undefined\n ? result.capture_warning\n : result.proxy_captured_requests !== undefined\n ? `proxy capture history holds ${String(result.proxy_captured_requests)} requests`\n : (result.capture_note ?? 'proxy capture count unknown')\n return [{ type: 'text', text: `scan completed; ${capture}` }]\n },\n },\n execute: (async (rawArgs: never, rawExec: never) => {\n await noteExec(rawExec)\n const args = rawArgs as never as { executive_summary?: string; methodology?: string; technical_analysis?: string; recommendations?: string }\n if (config.allowFinish === false) {\n return { success: false, scan_completed: false, error: 'This tool can only be used by the root/main agent. If you are a subagent, use agent_finish instead' }\n }\n const sections = {\n executiveSummary: (args.executive_summary ?? '').trim(),\n methodology: (args.methodology ?? '').trim(),\n technicalAnalysis: (args.technical_analysis ?? '').trim(),\n recommendations: (args.recommendations ?? '').trim(),\n }\n const errors: string[] = []\n if (sections.executiveSummary === '') errors.push('Executive summary cannot be empty')\n if (sections.methodology === '') errors.push('Methodology cannot be empty')\n if (sections.technicalAnalysis === '') errors.push('Technical analysis cannot be empty')\n if (sections.recommendations === '') errors.push('Recommendations cannot be empty')\n if (errors.length > 0) return { success: false, error: 'Validation failed', errors }\n const summary: Record<string, unknown> = {\n coverage_recorded: coverage.size,\n coverage_outcomes: outcomeCounts(),\n }\n if (coverage.size === 0) {\n summary['coverage_warning'] = 'No coverage was recorded for this scan. The report cannot state what was and was not tested; record coverage with record_coverage in future scans.'\n } else {\n const unresolved = coverageEntries()\n .filter(entry => entry['outcome'] === 'needs_follow_up')\n .map(entry => ({ surface: entry['surface'], risk_area: entry['risk_area'] }))\n if (unresolved.length > 0) {\n summary['coverage_warning'] = `${String(unresolved.length)} surface(s) still need follow-up; they are listed in unresolved_surfaces.`\n summary['unresolved_surfaces'] = unresolved\n }\n }\n // Capture reconciliation: the proxy history is the hard evidence trail;\n // surface its size (or its absence) instead of letting it stay invisible.\n // Both the count and every failure reason reach run.json via extras.\n const reconcile = await proxyCapturedRequests()\n const captureExtras: Record<string, unknown> = {}\n if (reconcile.captured !== undefined) {\n summary['proxy_captured_requests'] = reconcile.captured\n captureExtras['proxy_captured_requests'] = reconcile.captured\n if (reconcile.captured === 0) {\n summary['capture_warning'] = 'The Caido proxy captured 0 requests for this scan: no queryable traffic history exists, so PoC requests cannot be replayed from history. Report this as a capture-line gap; plaintext HTTP should be captured transparently in sandbox images >= 1.1.0.'\n captureExtras['capture_warning'] = summary['capture_warning']\n }\n } else {\n summary['capture_note'] = reconcile.note ?? 'Proxy capture count unknown.'\n captureExtras['capture_note'] = summary['capture_note']\n }\n const reporting = (ctx as unknown as { pentestReporting?: { finishScan(sections: { executiveSummary: string; methodology: string; technicalAnalysis: string; recommendations: string }, status?: string, extras?: Readonly<Record<string, unknown>>): Promise<void>; state: { vulnerabilityReports: unknown[] } } }).pentestReporting\n if (reporting === undefined) {\n return { success: true, scan_completed: true, message: 'Scan completed (not persisted)', warning: 'Results could not be persisted - report state unavailable', ...summary }\n }\n await reporting.finishScan(sections, 'completed', captureExtras)\n return { success: true, scan_completed: true, message: 'Scan completed successfully', vulnerabilities_found: reporting.state.vulnerabilityReports.length, ...summary }\n }) as never,\n }))\n\n return handle\n}\n\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gpzhang2001/sharpkit-analysis",
3
3
  "description": "Threat model / coverage / notes / think tools + finish_scan over per-scan mirror stores",
4
- "version": "0.2.1",
4
+ "version": "0.2.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -28,7 +28,7 @@
28
28
  ],
29
29
  "dependencies": {
30
30
  "@deepseek-ai/schemastery": "3.18.2",
31
- "@gpzhang2001/sharpkit-reporting": "^0.2.1"
31
+ "@gpzhang2001/sharpkit-reporting": "^0.2.2"
32
32
  },
33
33
  "peerDependencies": {
34
34
  "@deepseek-ai/cordis": "^4.0.2",
@@ -38,7 +38,7 @@
38
38
  "devDependencies": {
39
39
  "@deepseek-ai/cordis": "4.0.2",
40
40
  "@deepseek-ai/dsh-tools": "0.1.2-rc.1",
41
- "@gpzhang2001/sharpkit-reporting": "^0.2.1"
41
+ "@gpzhang2001/sharpkit-reporting": "^0.2.2"
42
42
  },
43
43
  "main": "lib/index.js",
44
44
  "types": "lib/index.d.ts",
package/src/index.ts CHANGED
@@ -11,7 +11,7 @@
11
11
  * @module @gpzhang2001/sharpkit-analysis
12
12
  */
13
13
 
14
- import { join } from 'node:path'
14
+ import { join, resolve } from 'node:path'
15
15
  import type { Context } from '@deepseek-ai/cordis'
16
16
  import { defineTool } from '@deepseek-ai/dsh-tools'
17
17
  import { JsonMirrorStore, generateId, normalizeTargetIdentity } from './stores.ts'
@@ -48,7 +48,9 @@ export interface Config {
48
48
  readonly scanId?: string
49
49
  /** Scan targets (threat-model identity snapping). */
50
50
  readonly scanTargets?: readonly string[]
51
- /** Run directory for the .state mirrors (default sharpkit_runs/<scanId>/.state). */
51
+ /** Run directory for the .state mirrors (default: the reporting package's
52
+ * per-scan runDirFor() — session cwd + multi-scan isolation; the mirrors sit
53
+ * in <that>/.state alongside the artifacts). */
52
54
  readonly runDir?: string
53
55
  /** Whether this composition's agent may call finish_scan (root guard). */
54
56
  readonly allowFinish?: boolean
@@ -81,14 +83,42 @@ function isoNow(): string {
81
83
 
82
84
  export function apply(ctx: Context, config: Config = {}): AnalysisHandle {
83
85
  const scanId = config.scanId ?? 'pentest'
84
- const runDir = config.runDir ?? join('sharpkit_runs', scanId)
85
- const stateDir = join(runDir, '.state')
86
- const coverage = new JsonMirrorStore(join(stateDir, 'coverage.json'))
87
- const threatModels = new JsonMirrorStore(join(stateDir, 'threat_models.json'))
88
- const notes = new JsonMirrorStore(join(stateDir, 'notes.json'))
89
- void coverage.hydrate()
90
- void threatModels.hydrate()
91
- void notes.hydrate()
86
+
87
+ /**
88
+ * The .state mirrors live in the SAME run directory as the reporting
89
+ * artifacts (they previously split off to a fixed 'sharpkit_runs/pentest'
90
+ * while reporting used its configured runName — two directories for one
91
+ * scan). The path resolves lazily per tool execution: an explicit
92
+ * config.runDir wins (tests, E2E); otherwise the reporting package's
93
+ * runDirFor() decides (session cwd + per-scan isolation).
94
+ */
95
+ interface ReportingSource { runDirFor?(session: unknown): Promise<string> }
96
+ let activeSession: unknown
97
+ let hydratedFor: unknown
98
+ let hydratePromise: Promise<void> | undefined
99
+ const noteExec = async (rawExec: never): Promise<void> => {
100
+ const session = (rawExec as { agent?: { session?: unknown } } | undefined)?.agent?.session
101
+ if (session === undefined) return
102
+ activeSession = session
103
+ if (hydratedFor === session || hydratePromise !== undefined) return
104
+ hydratedFor = session
105
+ hydratePromise = Promise.all([coverage.hydrate(), threatModels.hydrate(), notes.hydrate()]).then(() => undefined)
106
+ await hydratePromise
107
+ hydratePromise = undefined
108
+ }
109
+ const stateDirOf = async (): Promise<string | null> => {
110
+ if (config.runDir !== undefined) return join(resolve(config.runDir), '.state')
111
+ const reporting = (ctx as unknown as { pentestReporting?: ReportingSource }).pentestReporting
112
+ const runDir = await reporting?.runDirFor?.(activeSession)
113
+ return runDir === undefined ? null : join(runDir, '.state')
114
+ }
115
+ const mirrorPath = (name: string): (() => Promise<string | null>) => async () => {
116
+ const dir = await stateDirOf()
117
+ return dir === null ? null : join(dir, name)
118
+ }
119
+ const coverage = new JsonMirrorStore(mirrorPath('coverage.json'))
120
+ const threatModels = new JsonMirrorStore(mirrorPath('threat_models.json'))
121
+ const notes = new JsonMirrorStore(mirrorPath('notes.json'))
92
122
 
93
123
  const coverageEntries = (): Array<Record<string, unknown> & { entry_id: string }> =>
94
124
  coverage.values().map(entry => ({ ...entry, entry_id: String(entry['id'] ?? '') })) as Array<Record<string, unknown> & { entry_id: string }>
@@ -142,7 +172,8 @@ export function apply(ctx: Context, config: Config = {}): AnalysisHandle {
142
172
  return [{ type: 'text', text: result.found === true ? 'threat model found' : 'no threat model yet' }]
143
173
  },
144
174
  },
145
- execute: (async (rawArgs: never) => {
175
+ execute: (async (rawArgs: never, rawExec: never) => {
176
+ await noteExec(rawExec)
146
177
  const args = rawArgs as never as { target?: string }
147
178
  const resolved = resolveTarget(args.target)
148
179
  if ('error' in resolved) return { success: false, error: resolved.error }
@@ -185,7 +216,8 @@ export function apply(ctx: Context, config: Config = {}): AnalysisHandle {
185
216
  return [{ type: 'text', text: result.success ? 'threat model saved' : `save_threat_model failed: ${result.error ?? 'unknown'}` }]
186
217
  },
187
218
  },
188
- execute: (async (rawArgs: never) => {
219
+ execute: (async (rawArgs: never, rawExec: never) => {
220
+ await noteExec(rawExec)
189
221
  const args = rawArgs as never as { target?: string; content: string }
190
222
  const resolved = resolveTarget(args.target)
191
223
  if ('error' in resolved) return { success: false, error: resolved.error }
@@ -244,7 +276,8 @@ export function apply(ctx: Context, config: Config = {}): AnalysisHandle {
244
276
  return [{ type: 'text', text: result.success ? 'amendment recorded' : `amend_threat_model failed: ${result.error ?? 'unknown'}` }]
245
277
  },
246
278
  },
247
- execute: (async (rawArgs: never) => {
279
+ execute: (async (rawArgs: never, rawExec: never) => {
280
+ await noteExec(rawExec)
248
281
  const args = rawArgs as never as { target?: string; addendum: string }
249
282
  const resolved = resolveTarget(args.target)
250
283
  if ('error' in resolved) return { success: false, error: resolved.error }
@@ -325,7 +358,8 @@ export function apply(ctx: Context, config: Config = {}): AnalysisHandle {
325
358
  return [{ type: 'text', text: result.success ? 'coverage recorded' : `record_coverage failed: ${reason}` }]
326
359
  },
327
360
  },
328
- execute: (async (rawArgs: never) => {
361
+ execute: (async (rawArgs: never, rawExec: never) => {
362
+ await noteExec(rawExec)
329
363
  const args = rawArgs as never as { surface?: string; risk_area?: string; outcome?: string; evidence?: string }
330
364
  const surface = (args.surface ?? '').trim()
331
365
  const riskArea = (args.risk_area ?? '').trim()
@@ -384,7 +418,8 @@ export function apply(ctx: Context, config: Config = {}): AnalysisHandle {
384
418
  return [{ type: 'text', text: result.success ? 'coverage updated' : `update_coverage failed: ${reason}` }]
385
419
  },
386
420
  },
387
- execute: (async (rawArgs: never) => {
421
+ execute: (async (rawArgs: never, rawExec: never) => {
422
+ await noteExec(rawExec)
388
423
  const args = rawArgs as never as { entry_id?: string; outcome?: string; evidence?: string }
389
424
  const entryId = (args.entry_id ?? '').trim()
390
425
  const entry = coverage.get(entryId)
@@ -440,7 +475,8 @@ export function apply(ctx: Context, config: Config = {}): AnalysisHandle {
440
475
  return [{ type: 'text', text: `${String(result.entries?.length ?? 0)} coverage entry(ies)` }]
441
476
  },
442
477
  },
443
- execute: (async (rawArgs: never) => {
478
+ execute: (async (rawArgs: never, rawExec: never) => {
479
+ await noteExec(rawExec)
444
480
  const args = rawArgs as never as { outcome?: string; surface?: string }
445
481
  let outcomeFilter: string | undefined
446
482
  if (args.outcome !== undefined && args.outcome !== '') {
@@ -520,7 +556,8 @@ export function apply(ctx: Context, config: Config = {}): AnalysisHandle {
520
556
  return [{ type: 'text', text: result.success ? 'note created' : `create_note failed: ${result.error ?? 'unknown'}` }]
521
557
  },
522
558
  },
523
- execute: (async (rawArgs: never) => {
559
+ execute: (async (rawArgs: never, rawExec: never) => {
560
+ await noteExec(rawExec)
524
561
  const args = rawArgs as never as { title?: string; content?: string; category?: string; tags?: string[] }
525
562
  const title = (args.title ?? '').trim()
526
563
  const content = (args.content ?? '').trim()
@@ -572,7 +609,8 @@ export function apply(ctx: Context, config: Config = {}): AnalysisHandle {
572
609
  return [{ type: 'text', text: `${String(result.notes?.length ?? 0)} note(s)` }]
573
610
  },
574
611
  },
575
- execute: (async (rawArgs: never) => {
612
+ execute: (async (rawArgs: never, rawExec: never) => {
613
+ await noteExec(rawExec)
576
614
  const args = rawArgs as never as { category?: string; tags?: string[]; search?: string; include_content?: boolean }
577
615
  const category = args.category?.trim() ?? ''
578
616
  const search = args.search?.toLowerCase() ?? ''
@@ -620,7 +658,8 @@ export function apply(ctx: Context, config: Config = {}): AnalysisHandle {
620
658
  return [{ type: 'text', text: result.success ? 'note returned' : `get_note failed: ${result.error ?? 'unknown'}` }]
621
659
  },
622
660
  },
623
- execute: (async (rawArgs: never) => {
661
+ execute: (async (rawArgs: never, rawExec: never) => {
662
+ await noteExec(rawExec)
624
663
  const args = rawArgs as never as { note_id?: string }
625
664
  const noteId = (args.note_id ?? '').trim()
626
665
  if (noteId === '') return { success: false, error: 'Note ID cannot be empty' }
@@ -656,7 +695,8 @@ export function apply(ctx: Context, config: Config = {}): AnalysisHandle {
656
695
  return [{ type: 'text', text: result.success ? 'note updated' : `update_note failed: ${result.error ?? 'unknown'}` }]
657
696
  },
658
697
  },
659
- execute: (async (rawArgs: never) => {
698
+ execute: (async (rawArgs: never, rawExec: never) => {
699
+ await noteExec(rawExec)
660
700
  const args = rawArgs as never as { note_id?: string; title?: string; content?: string; tags?: string[] }
661
701
  const noteId = (args.note_id ?? '').trim()
662
702
  const note = notes.get(noteId)
@@ -695,7 +735,8 @@ export function apply(ctx: Context, config: Config = {}): AnalysisHandle {
695
735
  return [{ type: 'text', text: result.success ? 'note deleted' : `delete_note failed: ${result.error ?? 'unknown'}` }]
696
736
  },
697
737
  },
698
- execute: (async (rawArgs: never) => {
738
+ execute: (async (rawArgs: never, rawExec: never) => {
739
+ await noteExec(rawExec)
699
740
  const args = rawArgs as never as { note_id?: string }
700
741
  const noteId = (args.note_id ?? '').trim()
701
742
  const note = notes.get(noteId)
@@ -727,7 +768,8 @@ export function apply(ctx: Context, config: Config = {}): AnalysisHandle {
727
768
  return [{ type: 'text', text: result.success ? 'recorded' : `think failed: ${result.error ?? 'unknown'}` }]
728
769
  },
729
770
  },
730
- execute: (async (rawArgs: never) => {
771
+ execute: (async (rawArgs: never, rawExec: never) => {
772
+ await noteExec(rawExec)
731
773
  const args = rawArgs as never as { thought?: string }
732
774
  if ((args.thought ?? '').trim() === '') return { success: false, error: 'Thought cannot be empty' }
733
775
  return { success: true, message: 'Thought recorded' }
@@ -736,6 +778,54 @@ export function apply(ctx: Context, config: Config = {}): AnalysisHandle {
736
778
 
737
779
  // ---- finish_scan ----
738
780
 
781
+ /**
782
+ * Best-effort capture reconciliation at scan close: count the Caido proxy
783
+ * history for this scan's sandbox session. Structural access (no hard
784
+ * dependency on the proxy package): pentestSandbox → caidoEndpoint → one
785
+ * GraphQL count query. Reconciliation must never block or fail scan
786
+ * completion — but a failure is never silent either: the reason travels
787
+ * back in `note` (into the tool result, the finish_scan render, and
788
+ * run.json). The previous `catch → undefined` swallowed a live breakage
789
+ * for a full day unnoticed (2026-09-22: run.json carried no capture
790
+ * fields at all while capture_summary worked seconds earlier).
791
+ */
792
+ const proxyCapturedRequests = async (): Promise<{ captured?: number; note?: string }> => {
793
+ const fail = (reason: string): { note: string } => {
794
+ ctx.logger?.warn(`pentest-analysis: capture reconcile failed: ${reason}`)
795
+ return { note: `Proxy capture count unknown: ${reason}` }
796
+ }
797
+ try {
798
+ // ctx.get (not property access): the cordis property proxy throws
799
+ // `cannot get property "pentestSandbox" without inject` in a real
800
+ // runtime when the service is not in `inject` — this exact breakage
801
+ // hid behind the old silent catch for a full day (2026-09-22).
802
+ // ctx.get reads the service store without the inject requirement
803
+ // (same seam as tool-reporting's ctx.get('pentestAnalysis')).
804
+ const sandbox = ctx.get('pentestSandbox') as
805
+ | { createSession(options: { scanId: string }): Promise<{ caidoEndpoint(): Promise<{ baseUrl: string; token: string }> }> }
806
+ | undefined
807
+ if (sandbox === undefined) return fail('sandbox service unavailable')
808
+ const session = await sandbox.createSession({ scanId })
809
+ const endpoint = await session.caidoEndpoint()
810
+ const response = await fetch(`${endpoint.baseUrl.replace(/\/$/, '')}/graphql`, {
811
+ method: 'POST',
812
+ headers: { 'content-type': 'application/json', authorization: `Bearer ${endpoint.token}` },
813
+ body: JSON.stringify({
814
+ query: 'query CaptureReconcile { requests(first: 1, order: {by: CREATED_AT, ordering: DESC}) { count { value } } }',
815
+ }),
816
+ signal: AbortSignal.timeout(5_000),
817
+ })
818
+ if (!response.ok) return fail(`Caido answered HTTP ${String(response.status)}`)
819
+ const payload = await response.json() as { data?: { requests?: { count?: { value?: unknown } | null } | null }; errors?: Array<{ message?: unknown }> | null }
820
+ if (payload.errors !== undefined && payload.errors !== null && payload.errors.length > 0) return fail(`Caido: ${String(payload.errors[0]?.message ?? 'GraphQL error')}`)
821
+ const value = payload.data?.requests?.count?.value
822
+ if (value === null || value === undefined) return fail('Caido returned no request count')
823
+ return { captured: Number(value) }
824
+ } catch (error) {
825
+ return fail(error instanceof Error ? error.message : String(error))
826
+ }
827
+ }
828
+
739
829
  ctx.tools.register(defineTool({
740
830
  name: 'finish_scan',
741
831
  description: 'Complete the scan: validates the four report sections, records the coverage summary, writes the final artifacts, and marks the scan completed. Root agent only.',
@@ -757,6 +847,9 @@ export function apply(ctx: Context, config: Config = {}): AnalysisHandle {
757
847
  coverage_outcomes: { type: 'object', properties: {}, additionalProperties: true },
758
848
  coverage_warning: { type: 'string' },
759
849
  unresolved_surfaces: { type: 'array', items: { type: 'object', properties: {}, additionalProperties: true } },
850
+ proxy_captured_requests: { type: 'integer', description: 'Requests in the Caido proxy history at close time (the queryable evidence trail).' },
851
+ capture_warning: { type: 'string' },
852
+ capture_note: { type: 'string' },
760
853
  warning: { type: 'string' },
761
854
  error: { type: 'string' },
762
855
  errors: { type: 'array', items: { type: 'string' } },
@@ -764,12 +857,21 @@ export function apply(ctx: Context, config: Config = {}): AnalysisHandle {
764
857
  additionalProperties: false,
765
858
  },
766
859
  render: (_args, value) => {
767
- const result = value as { success: boolean; error?: string; errors?: string[] }
860
+ const result = value as { success: boolean; error?: string; errors?: string[]; proxy_captured_requests?: number; capture_warning?: string; capture_note?: string }
768
861
  const reason = result.error ?? result.errors?.join('; ') ?? 'unknown'
769
- return [{ type: 'text', text: result.success ? 'scan completed' : `finish_scan failed: ${reason}` }]
862
+ if (!result.success) return [{ type: 'text', text: `finish_scan failed: ${reason}` }]
863
+ // The capture reconciliation travels with the completion line so the
864
+ // agent closes its summary against the real evidence-trail size.
865
+ const capture = result.capture_warning !== undefined
866
+ ? result.capture_warning
867
+ : result.proxy_captured_requests !== undefined
868
+ ? `proxy capture history holds ${String(result.proxy_captured_requests)} requests`
869
+ : (result.capture_note ?? 'proxy capture count unknown')
870
+ return [{ type: 'text', text: `scan completed; ${capture}` }]
770
871
  },
771
872
  },
772
- execute: (async (rawArgs: never) => {
873
+ execute: (async (rawArgs: never, rawExec: never) => {
874
+ await noteExec(rawExec)
773
875
  const args = rawArgs as never as { executive_summary?: string; methodology?: string; technical_analysis?: string; recommendations?: string }
774
876
  if (config.allowFinish === false) {
775
877
  return { success: false, scan_completed: false, error: 'This tool can only be used by the root/main agent. If you are a subagent, use agent_finish instead' }
@@ -801,11 +903,27 @@ export function apply(ctx: Context, config: Config = {}): AnalysisHandle {
801
903
  summary['unresolved_surfaces'] = unresolved
802
904
  }
803
905
  }
804
- const reporting = (ctx as unknown as { pentestReporting?: { finishScan(sections: { executiveSummary: string; methodology: string; technicalAnalysis: string; recommendations: string }, status?: string): Promise<void>; state: { vulnerabilityReports: unknown[] } } }).pentestReporting
906
+ // Capture reconciliation: the proxy history is the hard evidence trail;
907
+ // surface its size (or its absence) instead of letting it stay invisible.
908
+ // Both the count and every failure reason reach run.json via extras.
909
+ const reconcile = await proxyCapturedRequests()
910
+ const captureExtras: Record<string, unknown> = {}
911
+ if (reconcile.captured !== undefined) {
912
+ summary['proxy_captured_requests'] = reconcile.captured
913
+ captureExtras['proxy_captured_requests'] = reconcile.captured
914
+ if (reconcile.captured === 0) {
915
+ summary['capture_warning'] = 'The Caido proxy captured 0 requests for this scan: no queryable traffic history exists, so PoC requests cannot be replayed from history. Report this as a capture-line gap; plaintext HTTP should be captured transparently in sandbox images >= 1.1.0.'
916
+ captureExtras['capture_warning'] = summary['capture_warning']
917
+ }
918
+ } else {
919
+ summary['capture_note'] = reconcile.note ?? 'Proxy capture count unknown.'
920
+ captureExtras['capture_note'] = summary['capture_note']
921
+ }
922
+ const reporting = (ctx as unknown as { pentestReporting?: { finishScan(sections: { executiveSummary: string; methodology: string; technicalAnalysis: string; recommendations: string }, status?: string, extras?: Readonly<Record<string, unknown>>): Promise<void>; state: { vulnerabilityReports: unknown[] } } }).pentestReporting
805
923
  if (reporting === undefined) {
806
924
  return { success: true, scan_completed: true, message: 'Scan completed (not persisted)', warning: 'Results could not be persisted - report state unavailable', ...summary }
807
925
  }
808
- await reporting.finishScan(sections)
926
+ await reporting.finishScan(sections, 'completed', captureExtras)
809
927
  return { success: true, scan_completed: true, message: 'Scan completed successfully', vulnerabilities_found: reporting.state.vulnerabilityReports.length, ...summary }
810
928
  }) as never,
811
929
  }))
package/src/stores.ts CHANGED
@@ -5,23 +5,34 @@
5
5
  * @module @gpzhang2001/sharpkit-analysis/stores
6
6
  */
7
7
 
8
- import { mkdir, readFile, rename, writeFile } from 'node:fs/promises'
8
+ import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises'
9
9
  import { dirname, join } from 'node:path'
10
10
 
11
- /** A JSON file mirror with atomic write and hydrate. */
11
+ /** A JSON file mirror with atomic write and hydrate. The path may be lazy: a
12
+ * provider is resolved (and re-read) on every persist/hydrate so a store can
13
+ * follow the per-scan run directory resolved at tool-execute time. */
12
14
  export class JsonMirrorStore {
13
- private readonly path: string | null
15
+ private readonly pathOrProvider: string | null | (() => string | null | Promise<string | null>)
14
16
  private readonly map = new Map<string, Record<string, unknown>>()
17
+ /** Monotonic per-store counter: unique temp names within one process even
18
+ * under concurrent persist() calls (same pid would otherwise collide and
19
+ * one rename would fail with ENOENT — observed once on 2026-09-22). */
20
+ private tempCounter = 0
15
21
 
16
- constructor(path: string | null) {
17
- this.path = path
22
+ constructor(path: string | null | (() => string | null | Promise<string | null>)) {
23
+ this.pathOrProvider = path
24
+ }
25
+
26
+ private async path(): Promise<string | null> {
27
+ return typeof this.pathOrProvider === 'function' ? await Promise.resolve(this.pathOrProvider()) : this.pathOrProvider
18
28
  }
19
29
 
20
30
  /** Load the mirror file; corrupt JSON is tolerated as empty on hydrate (resume aid only). */
21
31
  async hydrate(): Promise<void> {
22
- if (this.path === null) return
32
+ const path = await this.path()
33
+ if (path === null) return
23
34
  try {
24
- const raw = await readFile(this.path, 'utf8')
35
+ const raw = await readFile(path, 'utf8')
25
36
  const parsed = JSON.parse(raw) as unknown
26
37
  if (typeof parsed !== 'object' || parsed === null) return
27
38
  for (const [key, value] of Object.entries(parsed as Record<string, unknown>)) {
@@ -32,14 +43,22 @@ export class JsonMirrorStore {
32
43
  }
33
44
  }
34
45
 
35
- /** Persist the whole map atomically (strix `_persist_locked`). */
46
+ /** Persist the whole map atomically (strix `_persist_locked`). A rename
47
+ * loss to a concurrent writer of the same store is tolerated: the in-memory
48
+ * map stays authoritative and the mirror is a resume aid. */
36
49
  async persist(): Promise<void> {
37
- if (this.path === null) return
50
+ const path = await this.path()
51
+ if (path === null) return
38
52
  const payload = JSON.stringify(Object.fromEntries(this.map), null, 2)
39
- await mkdir(dirname(this.path), { recursive: true })
40
- const temp = `${dirname(this.path)}/.${join('', this.path.split('/').pop() ?? 'store')}.${process.pid}.tmp`
53
+ await mkdir(dirname(path), { recursive: true })
54
+ const temp = `${dirname(path)}/.${join('', path.split('/').pop() ?? 'store')}.${process.pid}.${this.tempCounter++}.tmp`
41
55
  await writeFile(temp, payload, 'utf8')
42
- await rename(temp, this.path)
56
+ try {
57
+ await rename(temp, path)
58
+ } catch (error) {
59
+ await rm(temp, { force: true }).catch(() => undefined)
60
+ if (this.map.size > 0) throw error
61
+ }
43
62
  }
44
63
 
45
64
  get(key: string): Record<string, unknown> | undefined {