@clear-capabilities/agentic-security-scanner 0.144.0 → 0.147.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/CHANGELOG.md +390 -0
  2. package/bin/agentic-security.js +3813 -83
  3. package/dist/1122.index.js +702 -0
  4. package/dist/{301.index.js → 1301.index.js} +2 -2
  5. package/dist/1379.index.js +591 -0
  6. package/dist/{444.index.js → 1444.index.js} +13 -4
  7. package/dist/{660.index.js → 1660.index.js} +2 -2
  8. package/dist/{700.index.js → 1700.index.js} +2 -2
  9. package/dist/{905.index.js → 1905.index.js} +3 -3
  10. package/dist/{920.index.js → 1920.index.js} +3 -3
  11. package/dist/{238.index.js → 2238.index.js} +3 -3
  12. package/dist/2271.index.js +165 -0
  13. package/dist/{985.index.js → 2376.index.js} +1260 -340
  14. package/dist/2432.index.js +793 -0
  15. package/dist/2659.index.js +93 -0
  16. package/dist/{826.index.js → 2826.index.js} +2 -2
  17. package/dist/{830.index.js → 2830.index.js} +2 -2
  18. package/dist/2923.index.js +298 -0
  19. package/dist/{1.index.js → 3001.index.js} +5 -5
  20. package/dist/{117.index.js → 3117.index.js} +3 -3
  21. package/dist/3180.index.js +307 -0
  22. package/dist/3276.index.js +117 -0
  23. package/dist/{415.index.js → 3415.index.js} +2 -2
  24. package/dist/{499.index.js → 3499.index.js} +2 -2
  25. package/dist/3518.index.js +450 -0
  26. package/dist/{526.index.js → 3526.index.js} +14 -6
  27. package/dist/{736.index.js → 3736.index.js} +4 -4
  28. package/dist/{839.index.js → 3839.index.js} +4 -4
  29. package/dist/{113.index.js → 4113.index.js} +14 -6
  30. package/dist/{265.index.js → 4265.index.js} +2 -2
  31. package/dist/{384.index.js → 4384.index.js} +3 -3
  32. package/dist/4547.index.js +268 -0
  33. package/dist/4863.index.js +422 -0
  34. package/dist/{970.index.js → 4970.index.js} +67 -3
  35. package/dist/5051.index.js +770 -0
  36. package/dist/{144.index.js → 5144.index.js} +5 -5
  37. package/dist/{333.index.js → 5333.index.js} +3 -3
  38. package/dist/5343.index.js +185 -0
  39. package/dist/5350.index.js +866 -0
  40. package/dist/5561.index.js +436 -0
  41. package/dist/{637.index.js → 5637.index.js} +29 -7
  42. package/dist/{449.index.js → 5830.index.js} +78 -14
  43. package/dist/6626.index.js +532 -0
  44. package/dist/6662.index.js +297 -0
  45. package/dist/{675.index.js → 6675.index.js} +5 -5
  46. package/dist/{730.index.js → 6730.index.js} +6 -6
  47. package/dist/6829.index.js +225 -0
  48. package/dist/6944.index.js +130 -0
  49. package/dist/{178.index.js → 7178.index.js} +26 -8
  50. package/dist/{227.index.js → 7227.index.js} +2 -2
  51. package/dist/7310.index.js +520 -0
  52. package/dist/{552.index.js → 7552.index.js} +4 -4
  53. package/dist/7709.index.js +78 -0
  54. package/dist/8218.index.js +160 -0
  55. package/dist/{476.index.js → 8476.index.js} +4 -4
  56. package/dist/{513.index.js → 8513.index.js} +5 -5
  57. package/dist/{520.index.js → 8520.index.js} +2 -2
  58. package/dist/{718.index.js → 8718.index.js} +2 -2
  59. package/dist/{752.index.js → 8752.index.js} +2 -2
  60. package/dist/8846.index.js +100 -0
  61. package/dist/{435.index.js → 9091.index.js} +580 -187
  62. package/dist/{207.index.js → 9207.index.js} +2 -2
  63. package/dist/{220.index.js → 9220.index.js} +2 -2
  64. package/dist/9390.index.js +163 -0
  65. package/dist/{503.index.js → 9503.index.js} +2 -2
  66. package/dist/{801.index.js → 9801.index.js} +2 -2
  67. package/dist/{824.index.js → 9824.index.js} +2 -2
  68. package/dist/agentic-security.mjs +16 -16
  69. package/dist/agentic-security.mjs.sha256 +1 -1
  70. package/dist/compliance-frameworks/hipaa-security-rule.json +3 -2
  71. package/package.json +23 -10
  72. package/src/compare.js +6 -1
  73. package/src/dataflow/CLAUDE.md +2 -2
  74. package/src/dataflow/catalog.js +42 -0
  75. package/src/dataflow/orm-write-catalog.js +175 -0
  76. package/src/engine.js +580 -30
  77. package/src/fix/apply-fix-service.js +1 -0
  78. package/src/history-scan.js +22 -5
  79. package/src/ir/CLAUDE.md +2 -1
  80. package/src/ir/chrome-probe.mjs +150 -0
  81. package/src/ir/parser-js.js +94 -7
  82. package/src/lineage/CLAUDE.md +1203 -0
  83. package/src/lineage/DESIGN_DESTINATION_RESOLVER.md +156 -0
  84. package/src/lineage/DESIGN_GRAPH_BUILDER.md +938 -0
  85. package/src/lineage/DESIGN_HANDLING_ANALYZER.md +355 -0
  86. package/src/lineage/DESIGN_INTRAPROCEDURAL.md +628 -0
  87. package/src/lineage/DESIGN_PATH_PROVENANCE.md +3451 -0
  88. package/src/lineage/DESIGN_QUEUE_DETAIL.md +120 -0
  89. package/src/lineage/DESIGN_REGISTRIES.md +880 -0
  90. package/src/lineage/DESIGN_STORE_DETAIL.md +143 -0
  91. package/src/lineage/DESIGN_TRANSIT_PROTECTION.md +245 -0
  92. package/src/lineage/classification.js +56 -0
  93. package/src/lineage/coverage.js +658 -0
  94. package/src/lineage/cross-repo-link.js +107 -0
  95. package/src/lineage/dataflow-graph.schema.json +184 -0
  96. package/src/lineage/decision-story.js +206 -0
  97. package/src/lineage/drift-policy.js +279 -0
  98. package/src/lineage/driver.js +135 -0
  99. package/src/lineage/engine.js +992 -0
  100. package/src/lineage/export-briefing.js +628 -0
  101. package/src/lineage/export-csv.js +62 -0
  102. package/src/lineage/export-json.js +238 -0
  103. package/src/lineage/export-privacy.js +258 -0
  104. package/src/lineage/federation-loader.js +111 -0
  105. package/src/lineage/field-identity.js +78 -0
  106. package/src/lineage/fixtures/build-flagship-fixture.mjs +272 -0
  107. package/src/lineage/fixtures/flagship-graph.json +1453 -0
  108. package/src/lineage/flow-grade.js +221 -0
  109. package/src/lineage/governance-edit.js +169 -0
  110. package/src/lineage/graph-builder.js +1114 -0
  111. package/src/lineage/graph-diff.js +431 -0
  112. package/src/lineage/graph-snapshot.js +180 -0
  113. package/src/lineage/handling-analyzer.js +168 -0
  114. package/src/lineage/ids.js +349 -0
  115. package/src/lineage/impact-assessment.js +76 -0
  116. package/src/lineage/impact-engine.js +268 -0
  117. package/src/lineage/index.js +281 -0
  118. package/src/lineage/language-coverage-tiers.js +58 -0
  119. package/src/lineage/obligation-mapping.js +126 -0
  120. package/src/lineage/obligation-predicates.js +235 -0
  121. package/src/lineage/observation-adapters.js +282 -0
  122. package/src/lineage/observation-correlation.js +622 -0
  123. package/src/lineage/observation-store.js +497 -0
  124. package/src/lineage/path-query.js +410 -0
  125. package/src/lineage/path-store.js +400 -0
  126. package/src/lineage/protection.js +53 -0
  127. package/src/lineage/recipient-profile.js +192 -0
  128. package/src/lineage/recipient-registry.js +394 -0
  129. package/src/lineage/redact-graph.js +224 -0
  130. package/src/lineage/remediation.js +417 -0
  131. package/src/lineage/resolve-destination.js +91 -0
  132. package/src/lineage/runtime-observation.js +464 -0
  133. package/src/lineage/scenario-diff.js +84 -0
  134. package/src/lineage/scenario-engine.js +251 -0
  135. package/src/lineage/scenario.js +101 -0
  136. package/src/lineage/schema.js +167 -0
  137. package/src/lineage/sink-registry.js +427 -0
  138. package/src/lineage/source-registry.js +357 -0
  139. package/src/lineage/source-seeding.js +212 -0
  140. package/src/lineage/summaries.js +590 -0
  141. package/src/lineage/transform-catalog.js +397 -0
  142. package/src/lineage/transit-protection.js +150 -0
  143. package/src/lineage/validate.js +285 -0
  144. package/src/lsp/server.js +49 -2
  145. package/src/mcp/CLAUDE.md +7 -1
  146. package/src/mcp/dataflow-tools.js +160 -0
  147. package/src/mcp/server.js +1 -1
  148. package/src/mcp/tools.js +22 -1
  149. package/src/pipeline/assurance-mode.js +64 -1
  150. package/src/pipeline/finding-schema.js +8 -1
  151. package/src/pipeline/scan-health.js +19 -1
  152. package/src/posture/CLAUDE.md +140 -0
  153. package/src/posture/accuracy-scorecard.js +60 -0
  154. package/src/posture/artifact-registry.js +76 -0
  155. package/src/posture/auditor-walkthrough.js +192 -13
  156. package/src/posture/compliance-frameworks/hipaa-security-rule.json +3 -2
  157. package/src/posture/compliance-policy.js +12 -2
  158. package/src/posture/cross-repo-memory.js +7 -2
  159. package/src/posture/fix-history.js +25 -2
  160. package/src/posture/fix-verify.js +9 -1
  161. package/src/posture/fleet.js +0 -0
  162. package/src/posture/git-history.js +13 -5
  163. package/src/posture/material-change.js +21 -2
  164. package/src/posture/mttr.js +75 -12
  165. package/src/posture/obligation-evidence-pack.js +202 -0
  166. package/src/posture/pre-incident-archaeology.js +39 -7
  167. package/src/posture/privacy-framework.js +14 -0
  168. package/src/posture/provenance/ai-authorship.js +68 -0
  169. package/src/posture/provenance/branch-entry.js +80 -0
  170. package/src/posture/provenance/cache.js +143 -0
  171. package/src/posture/provenance/confidence.js +36 -0
  172. package/src/posture/provenance/coordinator.js +786 -0
  173. package/src/posture/provenance/dag-walk.js +249 -0
  174. package/src/posture/provenance/evidence-attribution.js +59 -0
  175. package/src/posture/provenance/git-evidence.js +310 -0
  176. package/src/posture/provenance/lifecycle.js +208 -0
  177. package/src/posture/provenance/missing-control-resolver.js +137 -0
  178. package/src/posture/provenance/origin-resolver.js +342 -0
  179. package/src/posture/provenance/predicate-replay.js +133 -0
  180. package/src/posture/provenance/providers/config.js +39 -0
  181. package/src/posture/provenance/providers/github.js +62 -0
  182. package/src/posture/provenance/providers/gitlab.js +58 -0
  183. package/src/posture/provenance/repo-lineage.js +74 -0
  184. package/src/posture/provenance/sca-origin.js +139 -0
  185. package/src/posture/provenance/schema.js +255 -0
  186. package/src/posture/provenance/transitive-sca.js +147 -0
  187. package/src/posture/provenance/validate.js +30 -0
  188. package/src/posture/provenance-evidence-bundle.js +144 -0
  189. package/src/posture/remediation-ledger.js +337 -0
  190. package/src/posture/sbom-diff.js +15 -2
  191. package/src/posture/secret-history.js +10 -2
  192. package/src/posture/state-dir.js +38 -14
  193. package/src/posture/vuln-archaeology.js +8 -2
  194. package/src/pr-delta.js +25 -4
  195. package/src/report/index.js +197 -3
  196. package/src/runScan.js +34 -5
  197. package/src/sast/rate-limit.js +33 -3
  198. package/src/server/CLAUDE.md +47 -0
  199. package/src/server/graph-loader.js +141 -0
  200. package/src/server/http-server.js +325 -0
  201. package/src/server/routes.js +129 -0
  202. package/src/server/security.js +111 -0
  203. package/src/server/static-assets.js +139 -0
  204. package/src/util/git-hardening.js +128 -0
  205. package/dist/11.index.js +0 -353
  206. package/dist/259.index.js +0 -975
  207. package/dist/317.index.js +0 -300
  208. package/dist/609.index.js +0 -741
  209. package/dist/838.index.js +0 -152
@@ -1,5 +1,5 @@
1
- export const id = 301;
2
- export const ids = [301];
1
+ export const id = 1301;
2
+ export const ids = [1301];
3
3
  export const modules = {
4
4
 
5
5
  /***/ 1301:
@@ -0,0 +1,591 @@
1
+ export const id = 1379;
2
+ export const ids = [1379];
3
+ export const modules = {
4
+
5
+ /***/ 1379:
6
+ /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
+
8
+
9
+ // EXPORTS
10
+ __webpack_require__.d(__webpack_exports__, {
11
+ emitGraphDpiaArtifact: () => (/* binding */ emitGraphDpiaArtifact),
12
+ emitGraphRopaArtifact: () => (/* binding */ emitGraphRopaArtifact)
13
+ });
14
+
15
+ ;// CONCATENATED MODULE: ../frontend/src/lib/flow-path.js
16
+ // Shared per-flow path/topology helpers, used by every view that needs to
17
+ // know "which nodes does this flow actually touch" — extracted here after
18
+ // Architecture View independently reimplemented the same node-collection
19
+ // logic inline in computeFlowSummary, to avoid a third divergent copy when
20
+ // Privacy View needed it too.
21
+
22
+ function flowPathNodeIds(graph, flow) {
23
+ const ids = new Set([flow.source, flow.sink]);
24
+ for (const edgeId of flow.edgeIds) {
25
+ const edge = graph.edges.find((e) => e.id === edgeId);
26
+ if (edge) {
27
+ ids.add(edge.from);
28
+ ids.add(edge.to);
29
+ }
30
+ }
31
+ return ids;
32
+ }
33
+
34
+ // Backend AI node-subtype vocabulary (scanner/src/lineage/schema.js's
35
+ // SOURCE_CATEGORIES/SINK_CATEGORIES, AI-flavored entries only). This list
36
+ // must be hand-kept in sync with that schema file — the frontend never
37
+ // imports scanner/src/lineage/ at runtime (see frontend/CLAUDE.md).
38
+ // Corrected this increment: the previous set ('ai-assistant',
39
+ // 'vector-store') matched neither real enum and silently never matched
40
+ // any real node.
41
+ const AI_SUBTYPES = new Set([
42
+ 'ai-model-provider', 'ai-local-model', 'ai-agent', 'ai-tool',
43
+ 'ai-vector-store', 'ai-memory', 'ai-training', 'ai-evaluation', 'ai-telemetry',
44
+ 'ai-model-output', 'ai-tool-result', 'ai-retrieved-document',
45
+ ]);
46
+
47
+ // AI relevance is computed from flow/node TOPOLOGY (does the path touch an
48
+ // AI-kind node), never from dataElement.aiContexts — that field is never
49
+ // populated by name-only classification (see scanner/src/lineage's
50
+ // classification.js), so an aiContexts-based filter would show zero AI
51
+ // relevance despite real AI-processing flows existing in the graph.
52
+ function isAiRelevantFlow(graph, flow) {
53
+ const pathNodeIds = flowPathNodeIds(graph, flow);
54
+ return graph.nodes.some((n) => pathNodeIds.has(n.id) && AI_SUBTYPES.has(n.subtype));
55
+ }
56
+
57
+ ;// CONCATENATED MODULE: ../frontend/src/lib/protection-visual.js
58
+ // The single source of truth for how a protection verdict renders — every
59
+ // view/component must call protectionVisual() rather than hardcoding a
60
+ // color or glyph, so AC-20 (verdicts distinguishable without color) holds
61
+ // everywhere by construction, not by convention.
62
+ //
63
+ // These verdict strings are NOT imported from scanner/src/lineage/protection.js
64
+ // — the browser bundle never imports anything under scanner/src/lineage/ at
65
+ // runtime (see frontend/CLAUDE.md). Keep this list in sync by hand if the
66
+ // backend's PROTECTION_VERDICTS/FLOW_SUMMARY_VALUES enums ever change.
67
+
68
+ // PRD §8.4, exact quoted precedence: "unprotected/prohibited → mixed →
69
+ // unknown/manual_required → protected/permitted → not_assessed". Read as
70
+ // display priority when aggregating multiple verdicts into one, not a raw
71
+ // severity order: not_assessed is LOWEST priority because it means "no
72
+ // information," and any real signal (even a protected one) should be shown
73
+ // instead of "not assessed." not_applicable isn't named in the PRD list;
74
+ // treated at the same lowest priority as not_assessed (both mean "nothing
75
+ // to prioritize").
76
+ const VERDICT_PRECEDENCE = Object.freeze([
77
+ 'unprotected',
78
+ 'mixed',
79
+ 'unknown',
80
+ 'protected',
81
+ 'not_assessed',
82
+ 'not_applicable',
83
+ ]);
84
+
85
+ const VISUALS = Object.freeze({
86
+ protected: { verdict: 'protected', label: 'Protected', glyph: '✓', lineStyle: 'solid', colorVar: '--status-protected' },
87
+ unprotected: { verdict: 'unprotected', label: 'Unprotected', glyph: '✗', lineStyle: 'solid', colorVar: '--status-unprotected' },
88
+ mixed: { verdict: 'mixed', label: 'Mixed', glyph: '±', lineStyle: 'solid', colorVar: '--status-unprotected' },
89
+ unknown: { verdict: 'unknown', label: 'Unknown', glyph: '?', lineStyle: 'dashed', colorVar: '--status-unknown' },
90
+ not_applicable: { verdict: 'not_applicable', label: 'Not applicable', glyph: '·', lineStyle: 'dotted', colorVar: '--text-secondary' },
91
+ not_assessed: { verdict: 'not_assessed', label: 'Not assessed', glyph: '–', lineStyle: 'dotted', colorVar: '--status-unknown' },
92
+ });
93
+
94
+ function protection_visual_protectionVisual(verdict) {
95
+ return VISUALS[verdict] ?? VISUALS.not_assessed;
96
+ }
97
+
98
+ function worstVerdict(verdicts) {
99
+ for (const tier of VERDICT_PRECEDENCE) {
100
+ if (verdicts.includes(tier)) return tier;
101
+ }
102
+ return 'not_assessed';
103
+ }
104
+
105
+ ;// CONCATENATED MODULE: ../frontend/src/lib/row-filters.js
106
+ // Shared, deduplicated row-vs-active-filters matcher — was previously two
107
+ // near-identical private copies (privacy-view.js's and inventory-view.js's
108
+ // own rowMatchesFilters). Every check here reads a PRE-ATTACHED row
109
+ // property (never the graph directly) — the caller's own row-computation
110
+ // step is responsible for attaching whichever of these properties make
111
+ // sense for that row's own shape (see lib/filter-rail.js's own facet list
112
+ // and each view's own row-building code). A facet whose property the row
113
+ // does not carry AT ALL is skipped (never a hide) — this is what makes it
114
+ // safe for a single shared function to serve row shapes as different as a
115
+ // Privacy flow-row and an Inventory dataElement-row.
116
+ const LIST_FACETS = [
117
+ ['dataClass', 'dataClasses', true], // true = row property is itself an array (dataClasses), match if ANY overlaps
118
+ ['protection', 'protectionSummary', false], // false = row property is a single value, match if included in the filter's list
119
+ ['transitVerdict', 'transitVerdict', false],
120
+ ['atRestVerdict', 'atRestVerdict', false],
121
+ ['handlingVerdict', 'handlingVerdict', false],
122
+ ['sourceCategory', 'sourceCategory', false],
123
+ ['sinkCategory', 'sinkCategory', false],
124
+ ['destinationExternality', 'destinationExternality', false],
125
+ ['policyVerdict', 'policyVerdict', false],
126
+ ];
127
+
128
+ function matchesFilters(row, filters) {
129
+ for (const [filterKey, rowProp, rowIsArray] of LIST_FACETS) {
130
+ const activeValues = filters[filterKey];
131
+ if (!activeValues?.length) continue; // this facet isn't active at all
132
+ if (!(rowProp in row)) continue; // row doesn't carry this property — unaffected, not hidden
133
+ if (rowIsArray) {
134
+ if (!(row[rowProp] ?? []).some((v) => activeValues.includes(v))) return false;
135
+ } else {
136
+ if (row[rowProp] !== undefined && !activeValues.includes(row[rowProp])) return false;
137
+ }
138
+ }
139
+ // Unlike the 9 facets above (list-of-selected-values), `ai` is a single
140
+ // boolean toggle, matching its existing shape in both views — kept as
141
+ // its own explicit check, not folded into LIST_FACETS's generic loop,
142
+ // since it's structurally different (a boolean flag, not a multi-select).
143
+ // Checked for ANY row carrying `isAiRelevant` — this is the real fix for
144
+ // Inventory's own previously-missing AI check (its private
145
+ // rowMatchesFilters never checked `ai` at all; Privacy's did).
146
+ if (filters.ai && 'isAiRelevant' in row && !row.isAiRelevant) return false;
147
+ return true;
148
+ }
149
+
150
+ ;// CONCATENATED MODULE: ../frontend/src/views/privacy-view.js
151
+
152
+
153
+
154
+
155
+
156
+ const LIFECYCLE_STAGES = Object.freeze(['collection', 'processing', 'storage', 'sharing', 'retention', 'deletion']);
157
+
158
+ function stageForNode(node) {
159
+ return LIFECYCLE_STAGES.includes(node.lifecycleStages?.[0]) ? node.lifecycleStages[0] : 'processing';
160
+ }
161
+
162
+ function computePrivacyRow(graph, flow) {
163
+ const dataElement = graph.dataElements.find((d) => flow.dataElementIds.includes(d.id));
164
+ const pathNodeIds = flowPathNodeIds(graph, flow);
165
+ const pathNodes = graph.nodes.filter((n) => pathNodeIds.has(n.id));
166
+ // This flow's own resolved edges — the same per-edgeId lookup
167
+ // flowPathNodeIds() already performs internally, applied here to keep the
168
+ // edge objects (rather than just the node ids) so the three protection
169
+ // dimensions below can be aggregated per-flow via worstVerdict().
170
+ const pathEdges = flow.edgeIds.map((edgeId) => graph.edges.find((e) => e.id === edgeId)).filter(Boolean);
171
+
172
+ const stageCells = LIFECYCLE_STAGES.map((stage) => ({
173
+ stage,
174
+ nodeLabels: pathNodes.filter((n) => stageForNode(n) === stage).map((n) => n.label),
175
+ }));
176
+
177
+ const sourceNode = graph.nodes.find((n) => n.id === flow.source);
178
+ const sinkNode = graph.nodes.find((n) => n.id === flow.sink);
179
+
180
+ return {
181
+ flowId: flow.id,
182
+ dataElementName: dataElement?.name ?? 'unknown field',
183
+ dataClasses: dataElement?.dataClasses ?? [],
184
+ stageCells,
185
+ governanceRefs: flow.governanceRefs ?? {},
186
+ protectionSummary: flow.protectionSummary,
187
+ policyVerdict: flow.policyVerdict,
188
+ isAiRelevant: isAiRelevantFlow(graph, flow),
189
+ // worstVerdict() always returns a real verdict string (falling back to
190
+ // 'not_assessed', never null/undefined) even for an empty edge list, so
191
+ // these three are always set unconditionally — unlike sourceCategory/
192
+ // sinkCategory/destinationExternality below, there is no genuinely-absent
193
+ // case to guard against.
194
+ transitVerdict: worstVerdict(pathEdges.map((e) => e.protection.transit.verdict)),
195
+ atRestVerdict: worstVerdict(pathEdges.map((e) => e.protection.atRest.verdict)),
196
+ handlingVerdict: worstVerdict(pathEdges.map((e) => e.protection.handling.verdict)),
197
+ // Only set when a real, non-null value exists — keeps matchesFilters's
198
+ // own "property absent = unaffected, never a hide" semantics clean
199
+ // rather than introducing a third (present-but-null) state.
200
+ ...(sourceNode?.subtype ? { sourceCategory: sourceNode.subtype } : {}),
201
+ ...(sinkNode?.subtype ? { sinkCategory: sinkNode.subtype } : {}),
202
+ ...(sinkNode?.externality?.value ? { destinationExternality: sinkNode.externality.value } : {}),
203
+ };
204
+ }
205
+
206
+ /**
207
+ * @param {object} graph
208
+ * @param {object} state
209
+ * @param {((flow: object) => boolean) | null} [queryPredicate] - Milestone 3,
210
+ * sub-project M3-UX-Query, Task 4: the query language's compiled predicate
211
+ * (lib/query-language.js's `compileQuery`), applied here as an ADDITIONAL
212
+ * condition alongside the existing dataClass/protection/ai filters — a row
213
+ * must pass BOTH to stay visible. Omitted/null (every pre-existing
214
+ * caller/test) means "no query active," matching every row, so behavior
215
+ * is unchanged for anyone not passing it.
216
+ */
217
+ function computePrivacyViewModel(graph, state, queryPredicate = null) {
218
+ const matchesQuery = queryPredicate ?? (() => true);
219
+ const rows = graph.flows.map((flow) => {
220
+ const row = computePrivacyRow(graph, flow);
221
+ return {
222
+ ...row,
223
+ selected: row.flowId === state.selectedId,
224
+ visible: matchesFilters(row, state.filters ?? {}) && matchesQuery(flow),
225
+ };
226
+ });
227
+ return { stages: LIFECYCLE_STAGES, rows };
228
+ }
229
+
230
+ const CLASS_BADGE_COLOR_VAR = { PII: '--class-pii', PHI: '--class-phi', PCI: '--class-pci' };
231
+
232
+ /**
233
+ * @param {ReturnType<typeof computePrivacyViewModel>} viewModel
234
+ * @param {HTMLElement} canvasEl
235
+ * @param {(flowId: string) => void} onSelectFlow
236
+ */
237
+ function renderPrivacyView(viewModel, canvasEl, onSelectFlow) {
238
+ clear(canvasEl);
239
+
240
+ const headerRow = el('tr', {}, [
241
+ el('th', {}, 'Field'),
242
+ el('th', {}, 'Protection'),
243
+ ...viewModel.stages.map((stage) => el('th', {}, stage.charAt(0).toUpperCase() + stage.slice(1))),
244
+ ]);
245
+
246
+ const bodyRows = viewModel.rows.map((row) => renderPrivacyRow(row, onSelectFlow));
247
+
248
+ const table = el('table', { class: 'privacy-table' }, [el('thead', {}, headerRow), el('tbody', {}, bodyRows)]);
249
+
250
+ canvasEl.appendChild(el('div', { class: 'privacy-view' }, table));
251
+ }
252
+
253
+ function renderPrivacyRow(row, onSelectFlow) {
254
+ const classBadges = row.dataClasses.map((cls) =>
255
+ el('span', { class: 'privacy-class-badge', style: `color: var(${CLASS_BADGE_COLOR_VAR[cls] ?? '--text-secondary'}); border-color: var(${CLASS_BADGE_COLOR_VAR[cls] ?? '--border-default'})` }, cls),
256
+ );
257
+
258
+ const fieldCell = el('td', { class: 'privacy-field-cell' }, [
259
+ el('div', {}, row.dataElementName),
260
+ el('div', {}, classBadges),
261
+ row.isAiRelevant ? el('span', { class: 'privacy-governance-badge', style: 'border-color: var(--context-ai); color: var(--context-ai)' }, 'AI processing') : null,
262
+ ]);
263
+
264
+ const visual = protectionVisual(row.protectionSummary);
265
+ const protectionCell = el(
266
+ 'td',
267
+ { class: 'privacy-protection-cell' },
268
+ el('span', { style: `border-color: var(${visual.colorVar}); color: var(${visual.colorVar})` }, `${visual.glyph} ${visual.label}`),
269
+ );
270
+
271
+ const stageCells = row.stageCells.map((cell) => renderStageCell(cell, row));
272
+
273
+ return el(
274
+ 'tr',
275
+ {
276
+ class: 'privacy-row',
277
+ 'data-selected': String(row.selected),
278
+ 'data-visible': String(row.visible),
279
+ tabindex: '0',
280
+ role: 'button',
281
+ 'aria-label': `${row.dataElementName} lifecycle, ${row.protectionSummary}${row.selected ? ', selected' : ''}`,
282
+ onClick: () => onSelectFlow(row.flowId),
283
+ onKeydown: (evt) => {
284
+ if (evt.key === 'Enter' || evt.key === ' ') {
285
+ evt.preventDefault();
286
+ onSelectFlow(row.flowId);
287
+ }
288
+ },
289
+ },
290
+ [fieldCell, protectionCell, ...stageCells],
291
+ );
292
+ }
293
+
294
+ function renderStageCell(cell, row) {
295
+ const children = [];
296
+ if (cell.nodeLabels.length > 0) {
297
+ children.push(el('div', {}, cell.nodeLabels.join(', ')));
298
+ } else {
299
+ children.push(el('div', { class: 'privacy-stage-cell-empty-label' }, '—'));
300
+ }
301
+
302
+ // Governance facts are shown once, on whichever stage cell is the most
303
+ // relevant home for them — sharing (recipient/purpose/lawfulBasis/transfer)
304
+ // or retention/deletion — rather than repeating them on every cell. This
305
+ // loop runs unconditionally (independent of whether nodeLabels is empty)
306
+ // so a fact like deletion:not_found is never structurally unreachable just
307
+ // because a flow's path happens not to touch a deletion-stage node.
308
+ const governanceKeysForStage = {
309
+ sharing: ['recipient', 'purpose', 'lawfulBasis', 'transfer'],
310
+ retention: ['retention'],
311
+ deletion: ['deletion'],
312
+ };
313
+ const relevantKeys = governanceKeysForStage[cell.stage] ?? [];
314
+ for (const key of relevantKeys) {
315
+ if (key in row.governanceRefs) {
316
+ const value = row.governanceRefs[key];
317
+ children.push(el('div', { class: 'privacy-governance-badge' }, `${key}: ${value}`));
318
+ }
319
+ }
320
+
321
+ return el('td', { class: cell.nodeLabels.length === 0 ? 'privacy-stage-cell privacy-stage-cell-empty' : 'privacy-stage-cell' }, children);
322
+ }
323
+
324
+ // EXTERNAL MODULE: ./src/dataflow/privacy-governance.js
325
+ var privacy_governance = __webpack_require__(3308);
326
+ // EXTERNAL MODULE: ./src/lineage/export-json.js
327
+ var export_json = __webpack_require__(859);
328
+ ;// CONCATENATED MODULE: ./src/lineage/export-privacy.js
329
+ // export-privacy.js — Milestone 4 deliverable #10 (DFG-020): graph-derived
330
+ // DPIA/RoPA export, migrating off the Layer-2 taint engine's
331
+ // dataflow/privacy-taint.js#emitDpiaArtifact / privacy-governance.js#emitRopaArtifact
332
+ // (which derive from piiFields, name-in-argument classification with no
333
+ // path/alias/field-mapping precision) onto the real DataFlowGraph v1's
334
+ // field-identity-tracked flows.
335
+ //
336
+ // Reuses frontend/src/views/privacy-view.js#computePrivacyViewModel
337
+ // DIRECTLY (the first live scanner/ -> frontend/ module import in this
338
+ // codebase, confirmed safe: that module only touches document/window
339
+ // inside renderPrivacyView's own function body, never at module top
340
+ // level, so importing it from plain Node never executes any DOM code)
341
+ // rather than re-deriving lifecycle-stage/dataClass/protection-verdict
342
+ // row computation a second time. This mirrors this package's own
343
+ // established discipline of reusing an already-correct, already-tested
344
+ // computation rather than a parallel copy that can drift.
345
+ //
346
+ // Both emit functions mirror their taint-engine-era predecessors'
347
+ // structure/wording (GDPR Art. 35 framing for the DPIA, the
348
+ // register-not-narrative RoPA table) but are populated from real graph
349
+ // rows.
350
+ //
351
+ // Filter wiring: opts.filter is the SAME {nodeIds, edgeIds} shape every
352
+ // other `dataflow export` format uses (export-json.js's own convention).
353
+ // It filters the GRAPH itself, via export-json.js's _filterGraph, BEFORE
354
+ // computePrivacyViewModel ever runs — never threaded into
355
+ // computePrivacyViewModel's own state.filters parameter, which expects a
356
+ // completely different shape (per-facet selected-value arrays, e.g.
357
+ // {dataClass: [...]}) and would silently no-op if handed {nodeIds, edgeIds}.
358
+
359
+
360
+
361
+
362
+
363
+ function _emptyState() {
364
+ return { selectedId: null };
365
+ }
366
+
367
+ /**
368
+ * Collapse embedded newlines to spaces. Every value interpolated into a
369
+ * Markdown line below can originate from operator-supplied prose (a
370
+ * governance field from .agentic-security/privacy-governance.json) or
371
+ * from source-derived identifiers — neither is trusted not to contain a
372
+ * literal newline, which would otherwise let the value break out of its
373
+ * line and inject arbitrary Markdown (e.g. a fake heading) mid-document.
374
+ */
375
+ function _mdInline(value) {
376
+ return String(value).replace(/\r\n|\r|\n/g, ' ');
377
+ }
378
+
379
+ /** _mdInline, plus pipe-escaping for a Markdown table cell — an
380
+ * unescaped `|` in a cell value shifts every later column in that row.
381
+ * Backslashes are escaped FIRST: a value already containing a literal
382
+ * `\|` (e.g. a Windows path fragment, a regex snippet) would otherwise
383
+ * become `\\|` — in Markdown that reads as an escaped backslash followed
384
+ * by a still-live, still-unescaped `|` column delimiter. */
385
+ function _mdCell(value) {
386
+ return _mdInline(value).replace(/\\/g, '\\\\').replace(/\|/g, '\\|');
387
+ }
388
+
389
+ /** Wrap a value in a Markdown inline-code span, safe even when the value
390
+ * itself contains backticks. CommonMark's own general rule: the fence
391
+ * must be one backtick longer than the longest run of consecutive
392
+ * backticks anywhere in the content, padded with a space on each side —
393
+ * a fixed double-backtick fence (this function's own earlier version)
394
+ * is not safe against a value containing 2+ CONSECUTIVE backticks, since
395
+ * the value's own run would then close the span early. */
396
+ function _mdCode(value) {
397
+ const s = _mdInline(value);
398
+ const runs = s.match(/`+/g);
399
+ const maxRun = runs ? Math.max(...runs.map((r) => r.length)) : 0;
400
+ if (maxRun === 0) return `\`${s}\``;
401
+ const fence = '`'.repeat(maxRun + 1);
402
+ return `${fence} ${s} ${fence}`;
403
+ }
404
+
405
+ function _scopedViewModel(graph, filter) {
406
+ const scopedGraph = filter ? (0,export_json/* _filterGraph */.e)(graph, filter) : graph;
407
+ return computePrivacyViewModel(scopedGraph, _emptyState(), null);
408
+ }
409
+
410
+ /**
411
+ * Group computePrivacyViewModel's rows by data class, mirroring
412
+ * emitDpiaArtifact's own grouping.
413
+ *
414
+ * Task-2 review finding (non-blocking, fixed): the old taint-engine
415
+ * emitDpiaArtifact's identical grouping code was safe only because its
416
+ * input (piiFields) was pre-filtered to classified fields by
417
+ * construction — it never saw an unclassified field at all. This
418
+ * function draws from EVERY graph flow via computePrivacyViewModel, so a
419
+ * bare `for (const cls of row.dataClasses)` silently drops any flow
420
+ * whose dataClasses is [] from the whole document — not merely
421
+ * ungrouped, genuinely INVISIBLE, with no count, no mention, nothing —
422
+ * for a document whose entire purpose is a complete inventory. RoPA's
423
+ * own sibling loop already falls back to a '(unclassified)' bucket
424
+ * (mirroring the same sentinel resolveGovernanceRefs's default hook
425
+ * uses in coverage.js, Task 1's own fix round); mirrored here so the two
426
+ * artifacts never disagree about how many real flows exist in scope.
427
+ */
428
+ function _groupRowsByClass(rows) {
429
+ const grouped = new Map();
430
+ for (const row of rows) {
431
+ const classes = row.dataClasses.length ? row.dataClasses : ['(unclassified)'];
432
+ for (const cls of classes) {
433
+ let g = grouped.get(cls);
434
+ if (!g) { g = []; grouped.set(cls, g); }
435
+ g.push(row);
436
+ }
437
+ }
438
+ return grouped;
439
+ }
440
+
441
+ /**
442
+ * Graph-derived DPIA (Data Protection Impact Assessment) — narrative
443
+ * Markdown, mirroring dataflow/privacy-taint.js#emitDpiaArtifact's own
444
+ * structure. opts.filter scopes to a subset of the graph (the "filtered
445
+ * ... from the selected graph scope" requirement, PRD 7.4).
446
+ */
447
+ function emitGraphDpiaArtifact(graph, opts = {}) {
448
+ const viewModel = _scopedViewModel(graph, opts.filter);
449
+ const visibleRows = viewModel.rows.filter((r) => r.visible !== false);
450
+ const grouped = _groupRowsByClass(visibleRows);
451
+
452
+ const lines = [];
453
+ lines.push('# Data Protection Impact Assessment (DPIA)');
454
+ lines.push('');
455
+ lines.push(`Generated by agentic-security scanner (graph-derived) on ${(opts.generatedAt ?? new Date().toISOString()).slice(0, 10)}.`);
456
+ lines.push('');
457
+ lines.push('This is an automated DPIA scaffold derived from real data-flow analysis');
458
+ lines.push('(field identity, path provenance, and protection verdicts). It must be');
459
+ lines.push('reviewed and completed by a privacy officer before use.');
460
+ lines.push('');
461
+ lines.push('Governance fields reflect .agentic-security/privacy-governance.json as');
462
+ lines.push('of the scan that produced this graph, not as of the moment this document');
463
+ lines.push('was exported — re-scan (with lineage deep mode enabled) after editing');
464
+ lines.push('that config, then re-export, to pick up the change.');
465
+ lines.push('');
466
+ if (grouped.size === 0) {
467
+ lines.push('No regulated data classes were identified in this graph scope.');
468
+ return lines.join('\n');
469
+ }
470
+ lines.push('## Data classes identified');
471
+ lines.push('');
472
+ for (const [cls, rows] of grouped) {
473
+ lines.push(`### ${_mdInline(cls)} (${rows.length} flow(s))`);
474
+ lines.push('');
475
+ for (const row of rows.slice(0, 20)) {
476
+ const crossClassNote = row.dataClasses.length > 1 ? ` (also: ${row.dataClasses.filter((c) => c !== cls).map(_mdInline).join(', ')})` : '';
477
+ lines.push(`- ${_mdCode(row.dataElementName)} — ${_mdInline(row.sourceCategory ?? 'unknown source')} -> ${_mdInline(row.sinkCategory ?? 'unknown sink')} (protection: ${_mdInline(row.protectionSummary)})${crossClassNote}`);
478
+ }
479
+ if (rows.length > 20) lines.push(`- ... and ${rows.length - 20} more`);
480
+ lines.push('');
481
+ lines.push(`**Governance fields for ${_mdInline(cls)}:**`);
482
+ lines.push('');
483
+ // Worst-case-wins across this class's own rows, the same tie-break
484
+ // Task 1's resolveGovernanceRefs default uses — never silently pick
485
+ // whichever row happened to be iterated last.
486
+ const merged = {};
487
+ for (const row of rows) {
488
+ for (const field of privacy_governance/* GOVERNANCE_FIELDS */.n4) {
489
+ const r = row.governanceRefs?.[field];
490
+ if (!r) continue;
491
+ if (!merged[field] || merged[field].source === 'manual_required') merged[field] = r;
492
+ }
493
+ }
494
+ // A flow with >1 dataClasses already carries a single governanceRefs
495
+ // record merged (worst-case-wins) across ALL of its own classes back
496
+ // at mint time (coverage.js#resolveGovernanceRefs) — this section can
497
+ // only re-merge that already-merged record across rows, never recover
498
+ // per-class distinctness that was lost earlier. Disclose it rather
499
+ // than silently presenting a cross-class value as if it were specific
500
+ // to this one class.
501
+ if (rows.some((row) => row.dataClasses.length > 1)) {
502
+ lines.push(`_Note: some flows in this section also belong to other data classes; the governance values below are each flow's own record merged across ALL of its classes, not verified as specific to ${_mdInline(cls)} alone._`);
503
+ lines.push('');
504
+ }
505
+ for (const field of privacy_governance/* GOVERNANCE_FIELDS */.n4) {
506
+ const r = merged[field] ?? { value: privacy_governance/* MANUAL_REQUIRED */.Dh, source: 'manual_required' };
507
+ lines.push(`- ${field}: ${_mdCode(r.value)}${r.source === 'operator_provided' ? ' (operator-provided)' : ''}`);
508
+ }
509
+ lines.push('');
510
+ }
511
+ lines.push('## Regulatory framework mapping');
512
+ lines.push('');
513
+ lines.push('- **GDPR Art. 35** — DPIA required when processing is likely to result in high risk to data subjects.');
514
+ lines.push('- **CCPA 1798.130** — Notice + access rights for collected personal information.');
515
+ if (grouped.has('PHI')) lines.push('- **HIPAA 164.308** — Administrative safeguards for ePHI access.');
516
+ if (grouped.has('PCI')) lines.push('- **PCI DSS Req. 3** — Protect stored cardholder data.');
517
+ lines.push('');
518
+ lines.push('This document organizes automated technical assessment evidence. It does');
519
+ lines.push("not certify compliance and is not a legal determination — see the graph's");
520
+ lines.push('own `disclaimer`/`limitations` fields for the full caveat.');
521
+ return lines.join('\n');
522
+ }
523
+
524
+ /**
525
+ * Graph-derived RoPA (Record of Processing Activities, GDPR Art. 30) —
526
+ * tabular Markdown register, mirroring
527
+ * dataflow/privacy-governance.js#emitRopaArtifact's own format, but ONE
528
+ * ROW PER (dataClass x flow) rather than per dataClass alone — a real,
529
+ * disclosed precision improvement the graph's own per-flow sink/
530
+ * destination resolution makes possible (the taint-engine version has no
531
+ * per-flow destination-resolution concept at all).
532
+ */
533
+ function emitGraphRopaArtifact(graph, opts = {}) {
534
+ const viewModel = _scopedViewModel(graph, opts.filter);
535
+ const visibleRows = viewModel.rows.filter((r) => r.visible !== false);
536
+
537
+ const lines = [];
538
+ lines.push('# Record of Processing Activities (RoPA) — GDPR Art. 30, graph-derived');
539
+ lines.push('');
540
+ lines.push(`Generated by agentic-security scanner (graph-derived) on ${(opts.generatedAt ?? new Date().toISOString()).slice(0, 10)}. Every governance`);
541
+ lines.push('field below is either supplied by an operator');
542
+ lines.push('(.agentic-security/privacy-governance.json) or marked');
543
+ lines.push(`\`${privacy_governance/* MANUAL_REQUIRED */.Dh}\` — none are inferable from source code, and none are`);
544
+ lines.push(`guessed. A privacy officer must fill in every \`${privacy_governance/* MANUAL_REQUIRED */.Dh}\` cell`);
545
+ lines.push('before this document is usable as a real RoPA.');
546
+ lines.push('');
547
+ lines.push('Governance fields reflect .agentic-security/privacy-governance.json as');
548
+ lines.push('of the scan that produced this graph, not as of the moment this document');
549
+ lines.push('was exported — re-scan (with lineage deep mode enabled) after editing');
550
+ lines.push('that config, then re-export, to pick up the change.');
551
+ lines.push('');
552
+ if (visibleRows.length === 0) {
553
+ lines.push('No regulated data flows were identified in this graph scope.');
554
+ return lines.join('\n');
555
+ }
556
+ const header = ['Data class', 'Field', 'Source', 'Destination', 'Protection', ...privacy_governance/* GOVERNANCE_FIELDS */.n4];
557
+ lines.push(`| ${header.join(' | ')} |`);
558
+ lines.push(`|${header.map(() => '---').join('|')}|`);
559
+ let gapCount = 0;
560
+ let rowCount = 0;
561
+ let crossClassRows = false;
562
+ for (const row of visibleRows) {
563
+ for (const cls of row.dataClasses.length ? row.dataClasses : ['(unclassified)']) {
564
+ rowCount++;
565
+ const fieldValues = privacy_governance/* GOVERNANCE_FIELDS */.n4.map((f) => {
566
+ const r = row.governanceRefs?.[f] ?? { value: privacy_governance/* MANUAL_REQUIRED */.Dh, source: 'manual_required' };
567
+ if (r.source === 'manual_required') gapCount++;
568
+ return r.value;
569
+ });
570
+ const isCrossClass = row.dataClasses.length > 1;
571
+ if (isCrossClass) crossClassRows = true;
572
+ const clsCell = isCrossClass ? `${cls}*` : cls;
573
+ const cells = [clsCell, row.dataElementName, row.sourceCategory ?? 'unknown source', row.sinkCategory ?? 'unknown destination', row.protectionSummary, ...fieldValues];
574
+ lines.push(`| ${cells.map(_mdCell).join(' | ')} |`);
575
+ }
576
+ }
577
+ lines.push('');
578
+ if (crossClassRows) {
579
+ lines.push('\\* This flow also belongs to other data classes; governance fields are');
580
+ lines.push("this flow's own record merged across ALL of its classes, not verified as");
581
+ lines.push('specific to the data class shown in this row alone.');
582
+ lines.push('');
583
+ }
584
+ lines.push(`${gapCount} field(s) across ${rowCount} row(s) (${visibleRows.length} flow(s)) require manual input.`);
585
+ return lines.join('\n');
586
+ }
587
+
588
+
589
+ /***/ })
590
+
591
+ };
@@ -1,5 +1,5 @@
1
- export const id = 444;
2
- export const ids = [444];
1
+ export const id = 1444;
2
+ export const ids = [1444];
3
3
  export const modules = {
4
4
 
5
5
  /***/ 1444:
@@ -10,6 +10,7 @@ export const modules = {
10
10
  /* harmony export */ });
11
11
  /* unused harmony exports extractAddedLines, scanHistoryDiff */
12
12
  /* harmony import */ var node_child_process__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1421);
13
+ /* harmony import */ var _util_git_hardening_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(8844);
13
14
  // R15 (PRD §5) — git-history secret sweep.
14
15
  //
15
16
  // A secret removed from HEAD but present in any past commit is still
@@ -25,6 +26,7 @@ export const modules = {
25
26
 
26
27
 
27
28
 
29
+
28
30
  // Pull the post-image (added) lines out of a unified diff: lines starting with
29
31
  // a single '+' (not the '+++' file header). Returns reconstructed text.
30
32
  function extractAddedLines(diffText) {
@@ -74,8 +76,15 @@ function sweepGitHistory(scanRoot, detectFn, { maxCommits = 50, timeoutMs = 2000
74
76
  if (!scanRoot || typeof detectFn !== 'function') return [];
75
77
  let out;
76
78
  try {
77
- out = (0,node_child_process__WEBPACK_IMPORTED_MODULE_0__.execFileSync)('git', ['-C', scanRoot, 'log', '-p', '-n', String(maxCommits), '--no-color', '--no-merges', '--no-textconv'],
78
- { encoding: 'utf8', maxBuffer: 96 * 1024 * 1024, timeout: timeoutMs, stdio: ['ignore', 'pipe', 'ignore'] });
79
+ // Second independent Finding Provenance PRD audit (FR-PROV-024): this
80
+ // scanRoot is a scanned repository, not this project's own trusted
81
+ // checkout. `--no-textconv` alone (the pre-existing hardening here) closes
82
+ // the .gitattributes textconv surface but NOT `core.fsmonitor` /
83
+ // `core.hooksPath` — this `log -p` call renders every historical commit's
84
+ // diff content, the same shape verified exploitable in
85
+ // provenance/git-evidence.js, so it gets the full hardening too.
86
+ out = (0,node_child_process__WEBPACK_IMPORTED_MODULE_0__.execFileSync)('git', (0,_util_git_hardening_js__WEBPACK_IMPORTED_MODULE_1__/* .hardenGitArgs */ .Ax)(['-C', scanRoot, 'log', '-p', '-n', String(maxCommits), '--no-color', '--no-merges', '--no-textconv']),
87
+ { encoding: 'utf8', maxBuffer: 96 * 1024 * 1024, timeout: timeoutMs, stdio: ['ignore', 'pipe', 'ignore'], env: (0,_util_git_hardening_js__WEBPACK_IMPORTED_MODULE_1__/* .hardenGitEnv */ .Si)() });
79
88
  } catch { return []; }
80
89
  const parts = out.split(/^commit ([0-9a-f]{7,40})/m); // [pre, sha, body, sha, body, ...]
81
90
  const findings = [];
@@ -1,5 +1,5 @@
1
- export const id = 660;
2
- export const ids = [660];
1
+ export const id = 1660;
2
+ export const ids = [1660];
3
3
  export const modules = {
4
4
 
5
5
  /***/ 1660:
@@ -1,5 +1,5 @@
1
- export const id = 700;
2
- export const ids = [700];
1
+ export const id = 1700;
2
+ export const ids = [1700];
3
3
  export const modules = {
4
4
 
5
5
  /***/ 1700: