@clear-capabilities/agentic-security-scanner 0.184.0 → 0.186.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/bin/agentic-security.js +111 -1
  3. package/dist/1128.index.js +912 -0
  4. package/dist/1665.index.js +828 -0
  5. package/dist/2584.index.js +1 -1
  6. package/dist/4171.index.js +2 -2
  7. package/dist/5752.index.js +466 -0
  8. package/dist/7405.index.js +1 -1
  9. package/dist/8158.index.js +1713 -0
  10. package/dist/8798.index.js +1 -1
  11. package/dist/9382.index.js +2 -2
  12. package/dist/9579.index.js +1 -1
  13. package/dist/agentic-security.mjs +29 -29
  14. package/dist/agentic-security.mjs.sha256 +1 -1
  15. package/dist/{dad58d1113a12c23a142.json → ca62ee93079be4c24eee.json} +29 -12
  16. package/dist/frontend/src/shell.js +12 -2
  17. package/package.json +29 -12
  18. package/src/dataflow/CLAUDE.md +46 -1
  19. package/src/dataflow/access-paths.js +12 -2
  20. package/src/dataflow/backward.js +42 -7
  21. package/src/dataflow/cross-service-taint.js +83 -1
  22. package/src/dataflow/demand-query.js +1168 -0
  23. package/src/dataflow/dep-fingerprint.js +718 -0
  24. package/src/dataflow/engine.js +48 -6
  25. package/src/dataflow/exploit-prover.js +6 -2
  26. package/src/dataflow/ifds-precise.js +22 -0
  27. package/src/dataflow/incremental-replay.js +118 -0
  28. package/src/dataflow/incremental-session.js +257 -0
  29. package/src/dataflow/incremental.js +237 -35
  30. package/src/dataflow/index.js +25 -94
  31. package/src/dataflow/ir-cache.js +268 -0
  32. package/src/dataflow/privacy-taint.js +7 -0
  33. package/src/dataflow/summaries.js +72 -6
  34. package/src/dataflow/tabulation.js +80 -24
  35. package/src/engine.js +113 -8
  36. package/src/graph/CLAUDE.md +31 -0
  37. package/src/graph/adapters/artifact.js +81 -0
  38. package/src/graph/adapters/crossrepo.js +75 -0
  39. package/src/graph/adapters/dataflow.js +76 -0
  40. package/src/graph/adapters/provenance.js +92 -0
  41. package/src/graph/adapters/semantic.js +89 -0
  42. package/src/graph/crosswalk-builders.js +152 -0
  43. package/src/graph/crosswalk.js +146 -0
  44. package/src/graph/index.js +15 -0
  45. package/src/graph/observation.js +57 -0
  46. package/src/graph/refs.js +105 -0
  47. package/src/graph/registry.js +129 -0
  48. package/src/graph/relations.js +81 -0
  49. package/src/graph/scc.js +241 -0
  50. package/src/graph/view.js +254 -0
  51. package/src/ir/CLAUDE.md +4 -0
  52. package/src/ir/callgraph.js +36 -8
  53. package/src/ir/index.js +18 -4
  54. package/src/ir/parser-js.js +10 -0
  55. package/src/language/CLAUDE.md +13 -0
  56. package/src/language/nix-option-types.js +170 -0
  57. package/src/language/nix-script-taint.js +46 -3
  58. package/src/language/nix-secrets.js +20 -2
  59. package/src/lineage/CLAUDE.md +72 -8
  60. package/src/lineage/DESIGN_GRAPH_BUILDER.md +1 -1
  61. package/src/lineage/DESIGN_PATH_PROVENANCE.md +6 -0
  62. package/src/lineage/boundary-contract.js +273 -0
  63. package/src/lineage/completion.js +205 -0
  64. package/src/lineage/continuation.js +140 -0
  65. package/src/lineage/control-credit.js +313 -0
  66. package/src/lineage/correlation.js +199 -0
  67. package/src/lineage/coverage.js +8 -1
  68. package/src/lineage/cross-repo-link.js +22 -0
  69. package/src/lineage/cut-planner.js +548 -0
  70. package/src/lineage/driver.js +219 -66
  71. package/src/lineage/engine.js +166 -21
  72. package/src/lineage/export-json.js +5 -0
  73. package/src/lineage/graph-builder.js +84 -7
  74. package/src/lineage/haskell-view.js +59 -0
  75. package/src/lineage/index.js +3 -0
  76. package/src/lineage/nix-view.js +35 -0
  77. package/src/lineage/owned-build.js +77 -0
  78. package/src/lineage/owned-worker-entry.js +22 -0
  79. package/src/lineage/owned-worker.js +95 -0
  80. package/src/lineage/path-evidence.js +123 -0
  81. package/src/lineage/path-predicates.js +529 -0
  82. package/src/lineage/path-query.js +189 -20
  83. package/src/lineage/path-store.js +191 -17
  84. package/src/lineage/scc-solver.js +186 -0
  85. package/src/lineage/smt-orchestrator.js +174 -0
  86. package/src/lineage/smt-z3-backend.js +294 -0
  87. package/src/lineage/source-seeding.js +36 -2
  88. package/src/lineage/summaries.js +66 -4
  89. package/src/lineage/value-versions.js +130 -0
  90. package/src/lineage/witness-selection.js +95 -0
  91. package/src/pipeline/scan-health.js +18 -0
  92. package/src/posture/CLAUDE.md +17 -0
  93. package/src/posture/business-logic.js +16 -7
  94. package/src/posture/dag-commands.js +442 -0
  95. package/src/posture/evidence-bundle.js +1 -1
  96. package/src/posture/finding-defaults.js +19 -0
  97. package/src/posture/git-history.js +91 -1
  98. package/src/posture/witness-bundle.js +573 -0
  99. package/src/posture/witness-runtime.js +276 -0
  100. package/src/report/index.js +2 -0
  101. package/src/sast/CLAUDE.md +2 -0
  102. package/src/sast/_call-args.js +70 -0
  103. package/src/sast/init-scope.js +87 -0
  104. package/src/sast/listen-bind-address.js +49 -0
  105. package/src/sast/redirect-target.js +106 -0
  106. package/src/sast/reflected-input.js +76 -0
  107. package/src/sast/send-file-root.js +27 -0
  108. package/src/sast/sensitive-data-logging.js +38 -1
  109. package/dist/119.index.js +0 -197
  110. package/dist/128.index.js +0 -129
  111. package/dist/1293.index.js +0 -5689
  112. package/dist/1336.index.js +0 -16182
  113. package/dist/1351.index.js +0 -133
  114. package/dist/1387.index.js +0 -271
  115. package/dist/1437.index.js +0 -23
  116. package/dist/1445.index.js +0 -110
  117. package/dist/1505.index.js +0 -151
  118. package/dist/1557.index.js +0 -293
  119. package/dist/1621.index.js +0 -311
  120. package/dist/1751.index.js +0 -160
  121. package/dist/1765.index.js +0 -727
  122. package/dist/1861.index.js +0 -690
  123. package/dist/1906.index.js +0 -433
  124. package/dist/208.index.js +0 -1631
  125. package/dist/2223.index.js +0 -159
  126. package/dist/2238.index.js +0 -295
  127. package/dist/2530.index.js +0 -152
  128. package/dist/2565.index.js +0 -116
  129. package/dist/2574.index.js +0 -127
  130. package/dist/2620.index.js +0 -345
  131. package/dist/2773.index.js +0 -98
  132. package/dist/2775.index.js +0 -32
  133. package/dist/2966.index.js +0 -171
  134. package/dist/2972.index.js +0 -87
  135. package/dist/2991.index.js +0 -131
  136. package/dist/3063.index.js +0 -833
  137. package/dist/3075.index.js +0 -227
  138. package/dist/3121.index.js +0 -297
  139. package/dist/3314.index.js +0 -263
  140. package/dist/3480.index.js +0 -1822
  141. package/dist/3481.index.js +0 -24
  142. package/dist/3506.index.js +0 -268
  143. package/dist/3533.index.js +0 -591
  144. package/dist/3653.index.js +0 -450
  145. package/dist/3740.index.js +0 -722
  146. package/dist/3822.index.js +0 -131
  147. package/dist/3868.index.js +0 -292
  148. package/dist/3918.index.js +0 -192
  149. package/dist/4090.index.js +0 -288
  150. package/dist/4113.index.js +0 -1286
  151. package/dist/4121.index.js +0 -1190
  152. package/dist/4142.index.js +0 -690
  153. package/dist/4168.index.js +0 -509
  154. package/dist/4281.index.js +0 -993
  155. package/dist/4336.index.js +0 -436
  156. package/dist/4387.index.js +0 -532
  157. package/dist/4413.index.js +0 -302
  158. package/dist/4476.index.js +0 -4510
  159. package/dist/4491.index.js +0 -133
  160. package/dist/4538.index.js +0 -185
  161. package/dist/4684.index.js +0 -319
  162. package/dist/4740.index.js +0 -165
  163. package/dist/4780.index.js +0 -209
  164. package/dist/4802.index.js +0 -225
  165. package/dist/4812.index.js +0 -379
  166. package/dist/4835.index.js +0 -126
  167. package/dist/494.index.js +0 -226
  168. package/dist/4991.index.js +0 -549
  169. package/dist/5199.index.js +0 -305
  170. package/dist/5216.index.js +0 -109
  171. package/dist/5439.index.js +0 -301
  172. package/dist/5443.index.js +0 -97
  173. package/dist/5496.index.js +0 -377
  174. package/dist/5738.index.js +0 -240
  175. package/dist/5801.index.js +0 -588
  176. package/dist/5817.index.js +0 -714
  177. package/dist/5858.index.js +0 -223
  178. package/dist/5933.index.js +0 -143
  179. package/dist/601.index.js +0 -23
  180. package/dist/6173.index.js +0 -273
  181. package/dist/6291.index.js +0 -1871
  182. package/dist/6312.index.js +0 -666
  183. package/dist/634.index.js +0 -16182
  184. package/dist/644.index.js +0 -87
  185. package/dist/6508.index.js +0 -283
  186. package/dist/6560.index.js +0 -645
  187. package/dist/6576.index.js +0 -422
  188. package/dist/6579.index.js +0 -271
  189. package/dist/6655.index.js +0 -3221
  190. package/dist/6745.index.js +0 -305
  191. package/dist/6818.index.js +0 -21642
  192. package/dist/6932.index.js +0 -1190
  193. package/dist/6972.index.js +0 -167
  194. package/dist/6995.index.js +0 -163
  195. package/dist/7249.index.js +0 -191
  196. package/dist/7372.index.js +0 -1197
  197. package/dist/7546.index.js +0 -541
  198. package/dist/76.index.js +0 -448
  199. package/dist/7879.index.js +0 -163
  200. package/dist/8025.index.js +0 -130
  201. package/dist/8054.index.js +0 -248
  202. package/dist/808.index.js +0 -1330
  203. package/dist/8097.index.js +0 -201
  204. package/dist/8126.index.js +0 -191
  205. package/dist/8178.index.js +0 -78
  206. package/dist/8382.index.js +0 -93
  207. package/dist/8408.index.js +0 -261
  208. package/dist/8540.index.js +0 -174
  209. package/dist/8635.index.js +0 -802
  210. package/dist/8688.index.js +0 -410
  211. package/dist/8699.index.js +0 -779
  212. package/dist/8891.index.js +0 -457
  213. package/dist/8916.index.js +0 -885
  214. package/dist/9146.index.js +0 -170
  215. package/dist/9168.index.js +0 -841
  216. package/dist/9292.index.js +0 -369
  217. package/dist/9422.index.js +0 -866
  218. package/dist/9440.index.js +0 -453
  219. package/dist/9479.index.js +0 -152
  220. package/dist/9481.index.js +0 -220
  221. package/dist/9539.index.js +0 -100
  222. package/dist/9547.index.js +0 -103
  223. package/dist/9692.index.js +0 -90
  224. package/dist/9800.index.js +0 -462
  225. package/dist/9899.index.js +0 -138
  226. package/dist/9981.index.js +0 -206
  227. package/dist/999.index.js +0 -117
  228. package/dist/9999.index.js +0 -1698
@@ -1,541 +0,0 @@
1
- export const id = 7546;
2
- export const ids = [7546,3868];
3
- export const modules = {
4
-
5
- /***/ 33868:
6
- /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
-
8
- /* harmony export */ __webpack_require__.d(__webpack_exports__, {
9
- /* harmony export */ MEMORY_PROFILES: () => (/* binding */ MEMORY_PROFILES),
10
- /* harmony export */ capabilitiesFromFamilyHint: () => (/* binding */ capabilitiesFromFamilyHint),
11
- /* harmony export */ classifyModelFamily: () => (/* binding */ classifyModelFamily),
12
- /* harmony export */ detectMemoryTier: () => (/* binding */ detectMemoryTier),
13
- /* harmony export */ detectSystemMemory: () => (/* binding */ detectSystemMemory),
14
- /* harmony export */ recommendAdmission: () => (/* binding */ recommendAdmission)
15
- /* harmony export */ });
16
- /* unused harmony exports KNOWN_MODEL_SIZE_GB, evaluateMemoryAdmission */
17
- /* harmony import */ var node_os__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(48161);
18
- /* harmony import */ var _oom_feedback_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(67181);
19
- // Model family hints, RAM-aware memory profiles, and the memory-admission
20
- // check for the Ollama provider (agentic-security-ollama-offline-prd.md
21
- // §13, §14, §15, §22.3, §30).
22
- //
23
- // FAMILY HINTS ARE DEFAULTS, NEVER AUTHORITY (PRD §12/§13). A name like
24
- // `gemma4:e2b` tells us nothing Ollama itself won't confirm — it only lets the
25
- // harness suggest a sane default before any network call. If a model actually
26
- // installed under a family-hinted name lacks a capability the hint implied,
27
- // the runtime probe (model-probe.js, added when tool-calling/structured-output
28
- // probing lands) always wins. This module only classifies and estimates; it
29
- // never asserts a capability is present.
30
- //
31
- // MEMORY NUMBERS ARE ESTIMATES, NOT PROMISES (PRD §22.3, §14.1). Ollama
32
- // artifact sizes and this module's headroom reserves are best-effort figures
33
- // sourced from what Ollama currently publishes; they exist so the harness can
34
- // fail BEFORE an OS-level OOM, not so it can claim an exact answer. Every
35
- // admission decision leaves a stated safety margin rather than trying to pack
36
- // memory to the byte.
37
-
38
-
39
-
40
-
41
- // PRD §12/§13 FR-1203 — non-authoritative family hint from a model name.
42
- // Longest/most-specific pattern first so `qwen3.5:4b` doesn't fall through to
43
- // the bare `qwen` bucket.
44
- const FAMILY_PATTERNS = [
45
- [/^qwen3\.5/i, 'qwen3.5'],
46
- [/^qwen3-coder-next/i, 'qwen3-coder-next'],
47
- [/^qwen3-coder/i, 'qwen3-coder'],
48
- [/^qwen2\.5-coder/i, 'qwen2.5-coder'],
49
- [/^qwen3/i, 'qwen3'],
50
- [/^qwen/i, 'qwen'],
51
- [/^gemma4/i, 'gemma4'],
52
- [/^functiongemma/i, 'functiongemma'],
53
- [/^gemma3/i, 'gemma3'],
54
- [/^gemma/i, 'gemma'],
55
- ];
56
-
57
- /** Non-authoritative family classification for defaults/messaging only. */
58
- function classifyModelFamily(modelName) {
59
- const name = String(modelName || '').trim();
60
- for (const [re, family] of FAMILY_PATTERNS) if (re.test(name)) return family;
61
- return 'unknown';
62
- }
63
-
64
- // PRD §13.1 — non-authoritative defaults per family, overridden by any real
65
- // runtime probe result (model-probe.js). `tools`/`structuredJson`/`thinking`
66
- // are 'unknown' where Ollama's own behavior varies by specific tag/quant
67
- // rather than by family alone.
68
- const FAMILY_CAPABILITY_HINTS = {
69
- 'qwen3.5': { chat: true, structuredJson: true, tools: true, thinking: 'unknown' },
70
- qwen3: { chat: true, structuredJson: true, tools: true, thinking: 'unknown' },
71
- 'qwen3-coder': { chat: true, structuredJson: true, tools: true, thinking: false },
72
- 'qwen3-coder-next': { chat: true, structuredJson: true, tools: true, thinking: false },
73
- 'qwen2.5-coder': { chat: true, structuredJson: true, tools: 'unknown', thinking: false },
74
- qwen: { chat: true, structuredJson: 'unknown', tools: 'unknown', thinking: 'unknown' },
75
- gemma4: { chat: true, structuredJson: true, tools: true, thinking: 'unknown' },
76
- functiongemma: { chat: true, structuredJson: 'unknown', tools: true, thinking: false },
77
- gemma3: { chat: true, structuredJson: true, tools: false, thinking: false },
78
- gemma: { chat: true, structuredJson: 'unknown', tools: 'unknown', thinking: 'unknown' },
79
- unknown: { chat: true, structuredJson: 'unknown', tools: 'unknown', thinking: 'unknown' },
80
- };
81
-
82
- /**
83
- * Build the PRD §13.1 ModelCapabilities object from a family hint alone
84
- * (Layer B). Layer A (Ollama's own /api/show metadata) and Layer C (runtime
85
- * probes) are applied by the caller and override these fields — this
86
- * function only ever sets `source.familyHint: true`.
87
- */
88
- function capabilitiesFromFamilyHint(modelName) {
89
- const family = classifyModelFamily(modelName);
90
- const hint = FAMILY_CAPABILITY_HINTS[family] || FAMILY_CAPABILITY_HINTS.unknown;
91
- return {
92
- chat: hint.chat,
93
- structuredJson: hint.structuredJson,
94
- tools: hint.tools,
95
- thinking: hint.thinking,
96
- vision: false,
97
- contextTokens: undefined,
98
- source: { metadata: false, familyHint: true, runtimeProbe: false },
99
- };
100
- }
101
-
102
- // ── RAM-aware memory profiles (PRD §14.4, §15.2, §22.3, §30) ───────────────
103
-
104
- const MB = 1024 * 1024;
105
- const GB = 1024 * MB;
106
-
107
- // Best-effort artifact sizes as currently distributed by Ollama, used only to
108
- // pick a SENSIBLE STARTING recommendation — the real admission decision below
109
- // uses actually-free memory, not this table. Keep in sync with the PRD's own
110
- // cited figures; a stale entry only affects the suggested default, never the
111
- // admission math (which reads real os.freemem()).
112
- const KNOWN_MODEL_SIZE_GB = Object.freeze({
113
- 'qwen3.5:2b': 1.7,
114
- 'qwen3.5:4b': 3.4,
115
- 'qwen3.5:9b': 6.6,
116
- 'gemma4:e2b': 7.2,
117
- 'gemma4:12b': 7.6,
118
- 'gemma4:latest': 9.6,
119
- });
120
-
121
- /** PRD §30 profile presets. `auto` picks between these by detected RAM. */
122
- const MEMORY_PROFILES = Object.freeze({
123
- '8gb': {
124
- label: '8gb',
125
- preferredModel: 'qwen3.5:4b',
126
- fallbackModel: 'qwen3.5:2b',
127
- initialContextTokens: 4096,
128
- targetContextTokens: 8192,
129
- maxConcurrency: 1,
130
- minFreeRamMb: 1536,
131
- },
132
- '16gb-qwen': {
133
- label: '16gb-qwen',
134
- preferredModel: 'qwen3.5:9b',
135
- fallbackModel: 'qwen3.5:4b',
136
- initialContextTokens: 16384,
137
- targetContextTokens: 32768,
138
- maxConcurrency: 1,
139
- minFreeRamMb: 2048,
140
- },
141
- '16gb-gemma': {
142
- label: '16gb-gemma',
143
- preferredModel: 'gemma4:e2b',
144
- fallbackModel: 'qwen3.5:4b',
145
- initialContextTokens: 8192,
146
- targetContextTokens: 16384,
147
- maxConcurrency: 1,
148
- minFreeRamMb: 2048,
149
- },
150
- });
151
-
152
- /**
153
- * PRD §22.3 — detect total/available system RAM. Thin wrapper over `os` so
154
- * tests can inject fake values without mocking the `os` module globally.
155
- */
156
- function detectSystemMemory({ totalBytes, freeBytes } = {}) {
157
- return {
158
- totalBytes: Number.isFinite(totalBytes) ? totalBytes : node_os__WEBPACK_IMPORTED_MODULE_0__.totalmem(),
159
- freeBytes: Number.isFinite(freeBytes) ? freeBytes : node_os__WEBPACK_IMPORTED_MODULE_0__.freemem(),
160
- };
161
- }
162
-
163
- /**
164
- * Pick the RAM tier ('8gb' | '16gb') a machine falls into. Anything under
165
- * ~9 GB total is treated as the 8 GB tier — real "8 GB" machines report
166
- * slightly less than 8*1024^3 bytes to userspace (firmware/GPU reservations),
167
- * so a hard `< 8*GB` cutoff would misclassify real 8 GB hardware as unknown.
168
- */
169
- function detectMemoryTier(totalBytes) {
170
- if (!Number.isFinite(totalBytes) || totalBytes <= 0) return 'unknown';
171
- if (totalBytes < 9 * GB) return '8gb';
172
- return '16gb';
173
- }
174
-
175
- /**
176
- * PRD §22.3 admission algorithm: does `contextTokens` at `model` fit in
177
- * currently-free memory with the configured reserve intact?
178
- *
179
- * This is deliberately conservative and coarse (PRD "avoid pretending memory
180
- * estimates are exact"): model residency is estimated from KNOWN_MODEL_SIZE_GB
181
- * when available (falling back to a pessimistic 8 GB assumption for an
182
- * unrecognized tag so an unknown model never LOOKS safer than a known large
183
- * one), and KV-cache growth is approximated as a fixed per-1K-token cost
184
- * rather than modeled per-architecture — real KV cache size depends on layer
185
- * count/head count/quantization the harness cannot know without Ollama's own
186
- * runtime numbers.
187
- */
188
- const ESTIMATED_KV_CACHE_MB_PER_1K_TOKENS = 32; // conservative, model-independent approximation
189
- const RUNTIME_OVERHEAD_MB = 512; // Ollama server + OS scheduler slack, independent of model size
190
-
191
- function evaluateMemoryAdmission({
192
- modelName,
193
- contextTokens,
194
- freeBytes,
195
- minFreeRamMb,
196
- modelSizeGb,
197
- } = {}) {
198
- const sizeGb = Number.isFinite(modelSizeGb) ? modelSizeGb : (KNOWN_MODEL_SIZE_GB[modelName] ?? 8);
199
- const modelMb = sizeGb * 1024;
200
- const kvCacheMb = (Number(contextTokens) || 0) / 1000 * ESTIMATED_KV_CACHE_MB_PER_1K_TOKENS;
201
- const requiredMb = modelMb + kvCacheMb + RUNTIME_OVERHEAD_MB + (Number(minFreeRamMb) || 0);
202
- const freeMb = (Number(freeBytes) || 0) / MB;
203
- const admitted = freeMb >= requiredMb;
204
- return {
205
- admitted,
206
- freeMb: Math.round(freeMb),
207
- requiredMb: Math.round(requiredMb),
208
- modelEstimateMb: Math.round(modelMb),
209
- kvCacheEstimateMb: Math.round(kvCacheMb),
210
- reserveMb: Number(minFreeRamMb) || 0,
211
- };
212
- }
213
-
214
- /**
215
- * Full recommendation flow (PRD §22 "Memory admission algorithm"):
216
- * try the profile's preferred context, shrink it, then fall back to the
217
- * profile's smaller model, before ever declaring the profile unusable.
218
- * Never recommends cloud — the worst outcome this function can return is
219
- * `{admitted:false}` with a human-readable explanation, which callers treat
220
- * as "run deterministic-only" (PRD §23.4).
221
- */
222
- function recommendAdmission(opts = {}) {
223
- const result = _recommendAdmissionCore(opts);
224
- // Adversarial-review fix (2026-09): a memory-admission ESTIMATE that
225
- // actually caused a real OOM (ollama-provider.js's callOllamaChat now
226
- // records this via oom-feedback.js) used to have no way to affect a
227
- // future admission decision for the SAME model on the SAME machine — the
228
- // exact same "admitted: true" would repeat forever. This does not
229
- // recalibrate the underlying estimate (that needs real hardware variety
230
- // one machine's observed failures can't substitute for); it attaches an
231
- // honest warning so the decision is no longer presented with unqualified
232
- // confidence.
233
- const chosenModel = result.model || opts.requestedModel;
234
- const prior = chosenModel ? (0,_oom_feedback_js__WEBPACK_IMPORTED_MODULE_1__/* .priorOOMFor */ .NL)(chosenModel) : null;
235
- if (prior) {
236
- return {
237
- ...result,
238
- priorOOMWarning: `'${chosenModel}' has previously failed with an out-of-memory error on this machine ` +
239
- `(${prior.count} time${prior.count === 1 ? '' : 's'}, most recently ${new Date(prior.lastAt).toISOString()}). ` +
240
- 'The memory estimate below may be optimistic for your hardware.',
241
- };
242
- }
243
- return result;
244
- }
245
-
246
- function _recommendAdmissionCore({ profile, freeBytes, requestedContextTokens, requestedModel } = {}) {
247
- const p = MEMORY_PROFILES[profile];
248
- if (!p) return { admitted: false, reason: `unknown memory profile '${profile}'` };
249
-
250
- const model = requestedModel || p.preferredModel;
251
- const attempts = [];
252
-
253
- // 1. Requested (or target) context at the requested/preferred model.
254
- const primaryContext = Number.isFinite(requestedContextTokens) ? requestedContextTokens : p.targetContextTokens;
255
- let check = evaluateMemoryAdmission({ modelName: model, contextTokens: primaryContext, freeBytes, minFreeRamMb: p.minFreeRamMb });
256
- attempts.push({ model, contextTokens: primaryContext, ...check });
257
- if (check.admitted) return { admitted: true, model, contextTokens: primaryContext, attempts };
258
-
259
- // 2. Reduce context to the profile's conservative initial value first —
260
- // PRD FR-2104: "shrink context before declaring an otherwise compatible
261
- // model unusable."
262
- if (primaryContext !== p.initialContextTokens) {
263
- check = evaluateMemoryAdmission({ modelName: model, contextTokens: p.initialContextTokens, freeBytes, minFreeRamMb: p.minFreeRamMb });
264
- attempts.push({ model, contextTokens: p.initialContextTokens, ...check });
265
- if (check.admitted) return { admitted: true, model, contextTokens: p.initialContextTokens, attempts, reducedContext: true };
266
- }
267
-
268
- // 3. Fall back to the profile's smaller model at its initial context.
269
- if (p.fallbackModel && p.fallbackModel !== model) {
270
- check = evaluateMemoryAdmission({ modelName: p.fallbackModel, contextTokens: p.initialContextTokens, freeBytes, minFreeRamMb: p.minFreeRamMb });
271
- attempts.push({ model: p.fallbackModel, contextTokens: p.initialContextTokens, ...check });
272
- if (check.admitted) {
273
- return {
274
- admitted: true, model: p.fallbackModel, contextTokens: p.initialContextTokens, attempts,
275
- reducedContext: true, fellBackToSmallerModel: true,
276
- };
277
- }
278
- }
279
-
280
- // 4. Nothing fits — deterministic-only, never cloud.
281
- return {
282
- admitted: false,
283
- attempts,
284
- reason: `No local model/context combination fit in available memory with the configured reserve. ` +
285
- `Recommend deterministic-only scanning, or free memory before retrying.`,
286
- };
287
- }
288
-
289
-
290
- /***/ }),
291
-
292
- /***/ 87546:
293
- /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
294
-
295
- /* harmony export */ __webpack_require__.d(__webpack_exports__, {
296
- /* harmony export */ getModelCapabilities: () => (/* binding */ getModelCapabilities)
297
- /* harmony export */ });
298
- /* unused harmony exports DEFAULT_CACHE_TTL_MS, capabilitiesFromShowMetadata, probeStructuredOutput, probeToolCalling, _internals */
299
- /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(73024);
300
- /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(76760);
301
- /* harmony import */ var node_os__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(48161);
302
- /* harmony import */ var node_crypto__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(77598);
303
- /* harmony import */ var _ollama_provider_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(46860);
304
- /* harmony import */ var _model_capabilities_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(33868);
305
- // PRD §13.2 — the three-layer model capability detection strategy.
306
- //
307
- // LAYER A (metadata) is the cheapest and most authoritative: Ollama's own
308
- // `/api/show` response, when it reports a `capabilities` array, is not a
309
- // guess. LAYER B (model-capabilities.js's family hint) is a non-authoritative
310
- // default used only where Layer A is silent. LAYER C (this module's
311
- // `probeStructuredOutput`/`probeToolCalling`) is the most expensive — it
312
- // consumes real inference time — so it is OPT-IN (the caller decides when
313
- // "necessary" per the PRD's own wording), never run implicitly on every
314
- // `models doctor`/`models inspect` invocation.
315
- //
316
- // PRECEDENCE: Layer C overrides Layer A overrides Layer B, field by field. A
317
- // field only ever gets overridden by a MORE authoritative layer that actually
318
- // has an opinion — a probe that couldn't run (offline/timeout) leaves the
319
- // field exactly as the layer below it set it, it never downgrades to
320
- // 'unknown'.
321
- //
322
- // CACHE KEY = Ollama version + model digest + model name (PRD §13.2 exactly).
323
- // Digest is load-bearing: `ollama pull` replacing a tag's underlying weights
324
- // must invalidate the cache even though the name/tag string is unchanged.
325
- // Same disk-cache directory convention as sca/sigstore-verify.js and
326
- // engine.js's OSV cache (`~/.claude/agentic-security/<name>/`).
327
- //
328
- // TTL + force-reprobe (adversarial-review fix, 2026-09). The key-based
329
- // invalidation above is real but not complete: this module's own comment
330
- // used to claim the entry is safe "forever" because the key changes when
331
- // the model does — but `/api/show` doesn't expose a digest on every Ollama
332
- // version (falls back to model NAME alone then, a few lines below), so a
333
- // same-tag re-pull, or simply an unlucky single-trial probe the first time
334
- // (see probeStructuredOutput/probeToolCalling's own single-call design),
335
- // had no way to ever self-correct short of a user manually deleting a file
336
- // under `~/.claude/agentic-security/`. Two independent fixes, since either
337
- // alone leaves a real gap: a default TTL as a safety net for the case
338
- // nobody notices, and an explicit `force` option (`models test --force`)
339
- // for the case someone DOES suspect a stale answer and wants it right now.
340
-
341
-
342
-
343
-
344
-
345
-
346
-
347
-
348
- const CACHE_DIR = node_path__WEBPACK_IMPORTED_MODULE_1__.join(node_os__WEBPACK_IMPORTED_MODULE_2__.homedir(), '.claude', 'agentic-security', 'ollama-capability-cache');
349
-
350
- // Default safety-net TTL: 30 days. Not the primary invalidation mechanism
351
- // (the key is) — a backstop for the cases the key can't see: a same-tag
352
- // re-pull on an Ollama version that doesn't expose a digest, or a single
353
- // unlucky probe trial that happened to pass/fail against the model's true
354
- // behavior. Overridable for anyone who wants a tighter or looser bound.
355
- const DEFAULT_CACHE_TTL_MS = 30 * 24 * 60 * 60 * 1000;
356
-
357
- function _ensureCacheDir() { try { node_fs__WEBPACK_IMPORTED_MODULE_0__.mkdirSync(CACHE_DIR, { recursive: true }); } catch {} }
358
- function _cacheKey(ollamaVersion, modelDigest, modelName) {
359
- return node_crypto__WEBPACK_IMPORTED_MODULE_3__.createHash('sha256').update(`${ollamaVersion}::${modelDigest}::${modelName}`).digest('hex');
360
- }
361
- function _cachePath(key) { return node_path__WEBPACK_IMPORTED_MODULE_1__.join(CACHE_DIR, key + '.json'); }
362
-
363
- /**
364
- * @returns {object|null} the cached probe RESULT (not the envelope), or
365
- * `null` on a miss, a parse failure, OR an entry older than `ttlMs`.
366
- */
367
- function _readProbeCache(key, ttlMs) {
368
- let envelope;
369
- try { envelope = JSON.parse(node_fs__WEBPACK_IMPORTED_MODULE_0__.readFileSync(_cachePath(key), 'utf8')); } catch { return null; }
370
- // Backward-compatible with a pre-TTL cache file that was just the bare
371
- // result object (no `probedAt`) — treat an entry with no timestamp as
372
- // fresh rather than discarding every cache written before this fix.
373
- if (envelope && typeof envelope === 'object' && 'probedAt' in envelope && 'result' in envelope) {
374
- if (Number.isFinite(ttlMs) && ttlMs > 0 && Date.now() - envelope.probedAt > ttlMs) return null;
375
- return envelope.result;
376
- }
377
- return envelope;
378
- }
379
- function _writeProbeCache(key, value) {
380
- _ensureCacheDir();
381
- try { node_fs__WEBPACK_IMPORTED_MODULE_0__.writeFileSync(_cachePath(key), JSON.stringify({ probedAt: Date.now(), result: value })); } catch {}
382
- }
383
-
384
- /**
385
- * PRD §13.2 Layer A — parse `/api/show`'s response into the subset of
386
- * ModelCapabilities it can actually speak to. A field this layer has no
387
- * opinion on is omitted (not set to `false`) so the caller's merge never
388
- * mistakes silence for a negative.
389
- */
390
- function capabilitiesFromShowMetadata(show) {
391
- const out = { source: { metadata: true } };
392
- if (Array.isArray(show?.capabilities) && show.capabilities.length > 0) {
393
- const caps = show.capabilities;
394
- out.chat = caps.includes('completion') || caps.includes('chat');
395
- out.tools = caps.includes('tools');
396
- out.vision = caps.includes('vision');
397
- out.thinking = caps.includes('thinking');
398
- }
399
- const modelInfo = show?.modelInfo;
400
- if (modelInfo && typeof modelInfo === 'object') {
401
- const ctxKey = Object.keys(modelInfo).find((k) => k.endsWith('.context_length'));
402
- if (ctxKey && Number.isFinite(modelInfo[ctxKey])) out.contextTokens = modelInfo[ctxKey];
403
- }
404
- return out;
405
- }
406
-
407
- /**
408
- * PRD §13.2 Layer C — structured-output probe. A tiny schema, a request for
409
- * `{"ok": true}`, verified end to end through the SAME
410
- * callOllamaStructured() bounded-retry path every real structured call uses
411
- * (not a bespoke lighter-weight check that could disagree with production
412
- * behavior).
413
- */
414
- const PROBE_SCHEMA = { type: 'object', required: ['ok'], properties: { ok: { type: 'boolean' } } };
415
-
416
- async function probeStructuredOutput({ host, model, timeouts, keepAlive } = {}) {
417
- const r = await (0,_ollama_provider_js__WEBPACK_IMPORTED_MODULE_4__/* .callOllamaStructured */ .uM)({
418
- host, model,
419
- messages: [{ role: 'user', content: 'Reply with ONLY a JSON object: {"ok": true}' }],
420
- schema: PROBE_SCHEMA,
421
- validateFn: (obj) => (obj && obj.ok === true ? { ok: true, value: obj } : { ok: false }),
422
- keepAlive, timeouts,
423
- });
424
- if (r.ok) return { supported: true };
425
- // A transport-level failure (server unreachable, timed out) tells us
426
- // nothing about the MODEL's capability — leave it 'unknown' rather than
427
- // reporting a false negative for an offline/slow server.
428
- if (['ollama-unreachable', 'ollama-not-running', 'ollama-timeout', 'ollama-model-not-installed'].includes(r.code)) {
429
- return { supported: 'unknown', reason: r.reason || r.code };
430
- }
431
- return { supported: false, reason: r.reason || r.code };
432
- }
433
-
434
- /**
435
- * PRD §13.2 Layer C — tool-calling probe. One harmless `echo_capability_probe`
436
- * function; success is Ollama returning a structured `tool_calls` entry
437
- * naming it, not a check on what the model chose to reply with in prose.
438
- */
439
- const PROBE_TOOL = {
440
- type: 'function',
441
- function: {
442
- name: 'echo_capability_probe',
443
- description: 'Echo back the given value. Used only to test whether this model supports tool calling.',
444
- parameters: { type: 'object', required: ['value'], properties: { value: { type: 'string' } } },
445
- },
446
- };
447
-
448
- async function probeToolCalling({ host, model, timeouts, keepAlive } = {}) {
449
- const r = await (0,_ollama_provider_js__WEBPACK_IMPORTED_MODULE_4__/* .callOllamaChat */ .L5)({
450
- host, model,
451
- messages: [{ role: 'user', content: 'Call the echo_capability_probe function with value set to "probe-ok". Reply with nothing else.' }],
452
- tools: [PROBE_TOOL],
453
- keepAlive, timeouts,
454
- });
455
- if (!r.ok) {
456
- if (['ollama-unreachable', 'ollama-not-running', 'ollama-timeout', 'ollama-model-not-installed'].includes(r.code)) {
457
- return { supported: 'unknown', reason: r.reason || r.code };
458
- }
459
- return { supported: false, reason: r.reason || r.code };
460
- }
461
- const calls = r.result.toolCalls || [];
462
- const called = calls.some((c) => c?.function?.name === 'echo_capability_probe');
463
- return called ? { supported: true } : { supported: false, reason: 'model did not emit a tool_calls entry for the probe function' };
464
- }
465
-
466
- function _mergeLayer(base, overlay, sourceFlag) {
467
- const merged = { ...base };
468
- let touched = false;
469
- for (const field of ['chat', 'structuredJson', 'tools', 'thinking', 'vision', 'contextTokens']) {
470
- if (overlay[field] !== undefined) { merged[field] = overlay[field]; touched = true; }
471
- }
472
- if (touched) merged.source = { ...merged.source, [sourceFlag]: true };
473
- return merged;
474
- }
475
-
476
- /**
477
- * Orchestrates all three layers (PRD §13.2) with caching (PRD: "so startup
478
- * does not repeatedly consume inference time"). `probe: true` opts into
479
- * Layer C — omitted or false, this returns Layer A+B only, which is what
480
- * every non-probing caller (models list/inspect/doctor's default path)
481
- * should use, since Layer C spends real inference time on the user's
482
- * machine.
483
- *
484
- * `force: true` (adversarial-review fix, 2026-09 — `models test --force`)
485
- * skips reading the cache — always runs a fresh probe and overwrites
486
- * whatever was there. `ttlMs` (default 30 days, `DEFAULT_CACHE_TTL_MS`)
487
- * bounds how long a cached entry is trusted without either; pass `0`/
488
- * `Infinity` to disable the TTL safety net entirely and rely on the key
489
- * alone, matching this module's original design intent.
490
- *
491
- * @returns {{ok:true, capabilities:object, cached:boolean} | {ok:false, code, reason}}
492
- */
493
- async function getModelCapabilities({ host, model, env = process.env, probe = false, force = false, ttlMs = DEFAULT_CACHE_TTL_MS, timeouts, keepAlive } = {}) {
494
- let capabilities = (0,_model_capabilities_js__WEBPACK_IMPORTED_MODULE_5__.capabilitiesFromFamilyHint)(model);
495
-
496
- const show = await (0,_ollama_provider_js__WEBPACK_IMPORTED_MODULE_4__/* .showOllamaModel */ .$G)({ host, model, timeouts });
497
- if (show.ok) {
498
- capabilities = _mergeLayer(capabilities, capabilitiesFromShowMetadata(show), 'metadata');
499
- }
500
-
501
- if (!probe) {
502
- return { ok: true, capabilities, cached: false };
503
- }
504
-
505
- const versionResult = await (0,_ollama_provider_js__WEBPACK_IMPORTED_MODULE_4__/* .getOllamaVersion */ .zm)({ host, timeouts });
506
- const ollamaVersion = versionResult.ok ? versionResult.version : 'unknown-version';
507
- // The digest is whatever Layer A's /api/show reported under `details`
508
- // (Ollama does not expose it on /api/show consistently across versions —
509
- // fall back to the model name alone, which still invalidates on a tag
510
- // change, just not on a same-tag re-pull).
511
- const modelDigest = show.ok && show.details?.digest ? show.details.digest : 'unknown-digest';
512
- const cacheKey = _cacheKey(ollamaVersion, modelDigest, model);
513
-
514
- const cached = force ? null : _readProbeCache(cacheKey, ttlMs);
515
- if (cached) {
516
- return { ok: true, capabilities: _mergeLayer(capabilities, cached, 'runtimeProbe'), cached: true };
517
- }
518
-
519
- const [structured, tools] = await Promise.all([
520
- probeStructuredOutput({ host, model, timeouts, keepAlive }),
521
- probeToolCalling({ host, model, timeouts, keepAlive }),
522
- ]);
523
-
524
- const probeResult = {};
525
- if (structured.supported !== 'unknown') probeResult.structuredJson = structured.supported;
526
- if (tools.supported !== 'unknown') probeResult.tools = tools.supported;
527
-
528
- // Only cache a probe that actually resolved something — an all-'unknown'
529
- // result (server unreachable mid-probe) would otherwise poison the cache
530
- // with a permanent non-answer.
531
- if (Object.keys(probeResult).length > 0) _writeProbeCache(cacheKey, probeResult);
532
-
533
- return { ok: true, capabilities: _mergeLayer(capabilities, probeResult, 'runtimeProbe'), cached: false };
534
- }
535
-
536
- const _internals = { CACHE_DIR, _cacheKey, _cachePath };
537
-
538
-
539
- /***/ })
540
-
541
- };