@principles/core 1.73.0 → 1.75.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 (101) hide show
  1. package/dist/runtime-v2/__tests__/architecture-regression.test.js +3 -3
  2. package/dist/runtime-v2/__tests__/architecture-regression.test.js.map +1 -1
  3. package/dist/runtime-v2/activation/index.d.ts +0 -2
  4. package/dist/runtime-v2/activation/index.d.ts.map +1 -1
  5. package/dist/runtime-v2/activation/index.js +0 -1
  6. package/dist/runtime-v2/activation/index.js.map +1 -1
  7. package/dist/runtime-v2/adapter/__tests__/schema-prompt-adapter.test.d.ts +2 -0
  8. package/dist/runtime-v2/adapter/__tests__/schema-prompt-adapter.test.d.ts.map +1 -0
  9. package/dist/runtime-v2/adapter/__tests__/schema-prompt-adapter.test.js +270 -0
  10. package/dist/runtime-v2/adapter/__tests__/schema-prompt-adapter.test.js.map +1 -0
  11. package/dist/runtime-v2/adapter/pi-ai-runtime-adapter.d.ts +1 -1
  12. package/dist/runtime-v2/adapter/pi-ai-runtime-adapter.d.ts.map +1 -1
  13. package/dist/runtime-v2/adapter/pi-ai-runtime-adapter.js +4 -3
  14. package/dist/runtime-v2/adapter/pi-ai-runtime-adapter.js.map +1 -1
  15. package/dist/runtime-v2/adapter/schema-prompt-adapter.d.ts +13 -0
  16. package/dist/runtime-v2/adapter/schema-prompt-adapter.d.ts.map +1 -0
  17. package/dist/runtime-v2/adapter/schema-prompt-adapter.js +266 -0
  18. package/dist/runtime-v2/adapter/schema-prompt-adapter.js.map +1 -0
  19. package/dist/runtime-v2/adapter/tools/diagnostician-tool.d.ts +3 -7
  20. package/dist/runtime-v2/adapter/tools/diagnostician-tool.d.ts.map +1 -1
  21. package/dist/runtime-v2/adapter/tools/diagnostician-tool.js +7 -19
  22. package/dist/runtime-v2/adapter/tools/diagnostician-tool.js.map +1 -1
  23. package/dist/runtime-v2/diagnostician/__tests__/diagnostician-prompt-builder.test.js +97 -15
  24. package/dist/runtime-v2/diagnostician/__tests__/diagnostician-prompt-builder.test.js.map +1 -1
  25. package/dist/runtime-v2/diagnostician-output.d.ts +0 -10
  26. package/dist/runtime-v2/diagnostician-output.d.ts.map +1 -1
  27. package/dist/runtime-v2/diagnostician-output.js +5 -10
  28. package/dist/runtime-v2/diagnostician-output.js.map +1 -1
  29. package/dist/runtime-v2/diagnostician-prompt-builder.d.ts +6 -1
  30. package/dist/runtime-v2/diagnostician-prompt-builder.d.ts.map +1 -1
  31. package/dist/runtime-v2/diagnostician-prompt-builder.js +23 -15
  32. package/dist/runtime-v2/diagnostician-prompt-builder.js.map +1 -1
  33. package/dist/runtime-v2/index.d.ts +4 -4
  34. package/dist/runtime-v2/index.d.ts.map +1 -1
  35. package/dist/runtime-v2/index.js +2 -2
  36. package/dist/runtime-v2/index.js.map +1 -1
  37. package/dist/runtime-v2/types/event-types.d.ts +3 -24
  38. package/dist/runtime-v2/types/event-types.d.ts.map +1 -1
  39. package/dist/runtime-v2/types/event-types.js +0 -10
  40. package/dist/runtime-v2/types/event-types.js.map +1 -1
  41. package/package.json +1 -1
  42. package/dist/runtime-v2/__tests__/nocturnal-compliance-trust-boundary.test.d.ts +0 -2
  43. package/dist/runtime-v2/__tests__/nocturnal-compliance-trust-boundary.test.d.ts.map +0 -1
  44. package/dist/runtime-v2/__tests__/nocturnal-compliance-trust-boundary.test.js +0 -215
  45. package/dist/runtime-v2/__tests__/nocturnal-compliance-trust-boundary.test.js.map +0 -1
  46. package/dist/runtime-v2/__tests__/sqlite-confirm-first-state-store.test.d.ts +0 -2
  47. package/dist/runtime-v2/__tests__/sqlite-confirm-first-state-store.test.d.ts.map +0 -1
  48. package/dist/runtime-v2/__tests__/sqlite-confirm-first-state-store.test.js +0 -148
  49. package/dist/runtime-v2/__tests__/sqlite-confirm-first-state-store.test.js.map +0 -1
  50. package/dist/runtime-v2/activation/sqlite-confirm-first-state-store.d.ts +0 -22
  51. package/dist/runtime-v2/activation/sqlite-confirm-first-state-store.d.ts.map +0 -1
  52. package/dist/runtime-v2/activation/sqlite-confirm-first-state-store.js +0 -136
  53. package/dist/runtime-v2/activation/sqlite-confirm-first-state-store.js.map +0 -1
  54. package/dist/runtime-v2/idle-trigger/__tests__/idle-trigger-config-resolve.test.d.ts +0 -2
  55. package/dist/runtime-v2/idle-trigger/__tests__/idle-trigger-config-resolve.test.d.ts.map +0 -1
  56. package/dist/runtime-v2/idle-trigger/__tests__/idle-trigger-config-resolve.test.js +0 -92
  57. package/dist/runtime-v2/idle-trigger/__tests__/idle-trigger-config-resolve.test.js.map +0 -1
  58. package/dist/runtime-v2/idle-trigger/__tests__/idle-trigger-decision.test.d.ts +0 -2
  59. package/dist/runtime-v2/idle-trigger/__tests__/idle-trigger-decision.test.d.ts.map +0 -1
  60. package/dist/runtime-v2/idle-trigger/__tests__/idle-trigger-decision.test.js +0 -134
  61. package/dist/runtime-v2/idle-trigger/__tests__/idle-trigger-decision.test.js.map +0 -1
  62. package/dist/runtime-v2/idle-trigger/__tests__/idle-trigger-edge-cases.test.d.ts +0 -2
  63. package/dist/runtime-v2/idle-trigger/__tests__/idle-trigger-edge-cases.test.d.ts.map +0 -1
  64. package/dist/runtime-v2/idle-trigger/__tests__/idle-trigger-edge-cases.test.js +0 -226
  65. package/dist/runtime-v2/idle-trigger/__tests__/idle-trigger-edge-cases.test.js.map +0 -1
  66. package/dist/runtime-v2/idle-trigger/idle-trigger-decision.d.ts +0 -3
  67. package/dist/runtime-v2/idle-trigger/idle-trigger-decision.d.ts.map +0 -1
  68. package/dist/runtime-v2/idle-trigger/idle-trigger-decision.js +0 -5
  69. package/dist/runtime-v2/idle-trigger/idle-trigger-decision.js.map +0 -1
  70. package/dist/runtime-v2/idle-trigger/idle-trigger-policy.d.ts +0 -4
  71. package/dist/runtime-v2/idle-trigger/idle-trigger-policy.d.ts.map +0 -1
  72. package/dist/runtime-v2/idle-trigger/idle-trigger-policy.js +0 -81
  73. package/dist/runtime-v2/idle-trigger/idle-trigger-policy.js.map +0 -1
  74. package/dist/runtime-v2/idle-trigger/idle-trigger-types.d.ts +0 -29
  75. package/dist/runtime-v2/idle-trigger/idle-trigger-types.d.ts.map +0 -1
  76. package/dist/runtime-v2/idle-trigger/idle-trigger-types.js +0 -15
  77. package/dist/runtime-v2/idle-trigger/idle-trigger-types.js.map +0 -1
  78. package/dist/runtime-v2/idle-trigger/index.d.ts +0 -5
  79. package/dist/runtime-v2/idle-trigger/index.d.ts.map +0 -1
  80. package/dist/runtime-v2/idle-trigger/index.js +0 -4
  81. package/dist/runtime-v2/idle-trigger/index.js.map +0 -1
  82. package/dist/runtime-v2/nocturnal/candidate-scoring.d.ts +0 -486
  83. package/dist/runtime-v2/nocturnal/candidate-scoring.d.ts.map +0 -1
  84. package/dist/runtime-v2/nocturnal/candidate-scoring.js +0 -493
  85. package/dist/runtime-v2/nocturnal/candidate-scoring.js.map +0 -1
  86. package/dist/runtime-v2/nocturnal/index.d.ts +0 -9
  87. package/dist/runtime-v2/nocturnal/index.d.ts.map +0 -1
  88. package/dist/runtime-v2/nocturnal/index.js +0 -5
  89. package/dist/runtime-v2/nocturnal/index.js.map +0 -1
  90. package/dist/runtime-v2/nocturnal/nocturnal-compliance.d.ts +0 -185
  91. package/dist/runtime-v2/nocturnal/nocturnal-compliance.d.ts.map +0 -1
  92. package/dist/runtime-v2/nocturnal/nocturnal-compliance.js +0 -912
  93. package/dist/runtime-v2/nocturnal/nocturnal-compliance.js.map +0 -1
  94. package/dist/runtime-v2/nocturnal/snapshot-contract.d.ts +0 -192
  95. package/dist/runtime-v2/nocturnal/snapshot-contract.d.ts.map +0 -1
  96. package/dist/runtime-v2/nocturnal/snapshot-contract.js +0 -185
  97. package/dist/runtime-v2/nocturnal/snapshot-contract.js.map +0 -1
  98. package/dist/runtime-v2/nocturnal/trinity-types.d.ts +0 -421
  99. package/dist/runtime-v2/nocturnal/trinity-types.d.ts.map +0 -1
  100. package/dist/runtime-v2/nocturnal/trinity-types.js +0 -182
  101. package/dist/runtime-v2/nocturnal/trinity-types.js.map +0 -1
@@ -1,912 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-use-before-define */
2
- /**
3
- * Nocturnal Compliance Engine — Opportunity-Based Principle Evaluation
4
- * =====================================================================
5
- *
6
- * Replaces session-average compliance with opportunity-based compliance.
7
- *
8
- * CORE CONCEPTS:
9
- *
10
- * Opportunity — a session context where a principle COULD have been applied.
11
- * An opportunity exists when the agent's action (or planned action)
12
- * falls within the principle's applicability scope.
13
- *
14
- * Compliance — the principle was followed in an opportunity.
15
- * Determined by absence of violation signals, not presence of
16
- * positive confirmation (avoids LLM scoring).
17
- *
18
- * Violation — strong evidence the principle was NOT followed.
19
- * Detected through deterministic event signals (pain, tool failures,
20
- * gate blocks) — no LLM involved.
21
- *
22
- * Dilution prevention — compliance is computed ONLY over sessions where the
23
- * principle had an opportunity. Unrelated sessions
24
- * (where T-05's risky operations never occurred) do NOT
25
- * dilute the compliance rate.
26
- *
27
- * DESIGN CONSTRAINTS (Phase 1):
28
- * - T-xx principles only (deterministic / weak-heuristic evaluability)
29
- * - No P_xxx automation (requires detector metadata — Task 1.3 scope)
30
- * - No LLM-based scoring
31
- * - No training logic
32
- *
33
- * FILE: No file persistence — stateless computation over event stream.
34
- * Caller is responsible for writing results to principle-training-state.ts.
35
- */
36
- // ---------------------------------------------------------------------------
37
- // Risky Operation Registry
38
- // ---------------------------------------------------------------------------
39
- /**
40
- * Tools and operations that constitute risky actions.
41
- * Gate blocks on these map to T-05 (Safety Rails) violations.
42
- */
43
- const RISKY_TOOLS = new Set([
44
- 'delete_file',
45
- 'move_file',
46
- 'rename_file',
47
- 'delete_directory',
48
- 'bash',
49
- 'MultiExec',
50
- ]);
51
- /**
52
- * Bash command patterns that constitute dangerous operations.
53
- * Matched against bash command text in tool_call events.
54
- */
55
- const DANGEROUS_BASH_PATTERNS = [
56
- /rm\s+(-[a-z]*r[a-z]*f?|-rf)/i, // rm -rf / rm -r
57
- /del\s+\/[s/q]/i, // Windows del /s
58
- /rmdir\s+\/s/i, // rmdir /s
59
- /git\s+push\s+.*--force/i, // git push --force
60
- /git\s+reset\s+--hard/i, // git reset --hard
61
- /git\s+clean\s+-f[dx]/i, // git clean -fd
62
- /npm\s+publish/i, // npm publish
63
- /pip\s+upload/i, // pip upload
64
- /docker\s+push/i, // docker push
65
- /curl.+\|\s*(ba)?sh/i, // curl | bash
66
- /wget.+\|\s*(ba)?sh/i, // wget | bash
67
- /^make\s+[^-|]+$/i, // bare make (destructive)
68
- ];
69
- /**
70
- * Keywords in gate block reason that indicate a dangerous/risky operation.
71
- * Used as a fallback when the tool itself is risky but the reason is free text.
72
- */
73
- const RISKY_KEYWORDS_IN_REASON = [
74
- /delete|remove|destroy|drop/i,
75
- /force|unsafe|dangerous/i,
76
- /format|truncate|overwrite/i,
77
- /exec|eval|shell|command/i,
78
- /credential|secret|password|token/i,
79
- ];
80
- /**
81
- * Edit/write tool names.
82
- */
83
- const EDIT_TOOLS = new Set([
84
- 'edit_file',
85
- 'edit_file_batch',
86
- 'write_to_file',
87
- 'create_file',
88
- 'apply_patch',
89
- ]);
90
- /**
91
- * Read tool names.
92
- */
93
- const READ_TOOLS = new Set([
94
- 'read_file',
95
- 'read_multiple_files',
96
- 'grep',
97
- 'search_files',
98
- 'list_directory',
99
- 'glob',
100
- ]);
101
- // ---------------------------------------------------------------------------
102
- // Path Normalization (cross-platform)
103
- // ---------------------------------------------------------------------------
104
- /**
105
- * Normalizes a file path to POSIX forward-slash format for consistent matching.
106
- * Handles Windows backslash paths on any platform.
107
- */
108
- function normalizePathPosix(filePath) {
109
- return filePath.replace(/\\/g, '/');
110
- }
111
- // ---------------------------------------------------------------------------
112
- // Opportunity Detection
113
- // ---------------------------------------------------------------------------
114
- /**
115
- * Detects whether a given session presents an APPLICABLE OPPORTUNITY
116
- * for a specific principle.
117
- *
118
- * An opportunity exists when the session context falls within the
119
- * principle's applicability scope — regardless of whether the agent
120
- * followed the principle.
121
- *
122
- * IMPORTANT: This does NOT assess compliance. It only answers:
123
- * "Could the principle have applied here?"
124
- *
125
- * #216: For P_* principles (not T-xx), uses generic detection based on
126
- * pain events and tool calls — any session with a pain signal is considered
127
- * an opportunity for a pain-derived principle.
128
- */
129
- export function detectOpportunity(principleId, session) {
130
- // #216: P_* principles (pain-derived) — generic opportunity detection
131
- if (principleId.startsWith('P_')) {
132
- // Any session with pain signals, tool failures, or gate blocks is an opportunity
133
- // for a pain-derived principle. This is conservative: better to over-count
134
- // opportunities than to miss real violations.
135
- const hasPainSignal = session.painSignals.length > 0;
136
- const hasToolFailure = session.toolCalls.some((tc) => tc.outcome === 'failure');
137
- const hasGateBlock = session.gateBlocks.length > 0;
138
- if (hasPainSignal || hasToolFailure || hasGateBlock) {
139
- return { applicable: true, reason: `P_* principle — session has ${hasPainSignal ? 'pain signal' : hasToolFailure ? 'tool failure' : 'gate block'}` };
140
- }
141
- return { applicable: false, reason: `P_* principle — no pain/tool-failure/gate-block in session` };
142
- }
143
- // T-xx principles — specific deterministic detection
144
- switch (principleId) {
145
- case 'T-01':
146
- return detectT01Opportunity(session);
147
- case 'T-02':
148
- return detectT02Opportunity(session);
149
- case 'T-03':
150
- return detectT03Opportunity(session);
151
- case 'T-04':
152
- return detectT04Opportunity(session);
153
- case 'T-05':
154
- return detectT05Opportunity(session);
155
- case 'T-06':
156
- return detectT06Opportunity(session);
157
- case 'T-07':
158
- return detectT07Opportunity(session);
159
- case 'T-08':
160
- return detectT08Opportunity(session);
161
- case 'T-09':
162
- return detectT09Opportunity(session);
163
- default:
164
- return { applicable: false, reason: `Unknown principle: ${principleId}` };
165
- }
166
- }
167
- /**
168
- * T-01 "Survey Before Acting" — Understand the structure first before making changes.
169
- *
170
- * APPLICABLE when: Agent performs edit/write operations.
171
- * Rationale: Any edit to code is an opportunity to survey first.
172
- * Excluded: Read-only sessions (no applicable opportunity).
173
- */
174
- function detectT01Opportunity(session) {
175
- const hasEdit = session.toolCalls.some((call) => EDIT_TOOLS.has(call.toolName));
176
- if (hasEdit) {
177
- return { applicable: true, reason: 'Edit operations present — opportunity to survey before acting' };
178
- }
179
- return { applicable: false, reason: 'No edit operations in session — T-01 not applicable' };
180
- }
181
- /**
182
- * T-02 "Respect Constraints" — Explicitly reason about contracts, tests, schemas.
183
- *
184
- * APPLICABLE when: Agent interacts with type/test/schema/contract files.
185
- */
186
- function detectT02Opportunity(session) {
187
- const hasConstraintInteraction = session.toolCalls.some((call) => {
188
- if (!call.filePath)
189
- return false;
190
- const normalized = normalizePathPosix(call.filePath);
191
- return (/\.(ts|tsx|js|jsx)$/.test(normalized) || // type-aware files
192
- /\b(test|spec|contract|schema|interface|type)\b/i.test(normalized));
193
- });
194
- if (hasConstraintInteraction) {
195
- return { applicable: true, reason: 'Type/test/contract interaction — opportunity to respect constraints' };
196
- }
197
- return { applicable: false, reason: 'No type/test/contract interaction — T-02 not applicable' };
198
- }
199
- /**
200
- * T-03 "Evidence Over Assumption" — Use logs, code, and outputs before inferring.
201
- *
202
- * APPLICABLE when: Pain signals or tool failures follow an edit/write operation.
203
- * Rationale: When a change causes something to go wrong, there's an opportunity
204
- * to gather evidence instead of assuming. Read-only failures are less relevant.
205
- * Narrowed: requires an edit/write in the session before the failure/pain signal.
206
- */
207
- function detectT03Opportunity(session) {
208
- const hasWriteBeforeFailure = session.toolCalls.some((call, i) => {
209
- if (call.outcome !== 'failure')
210
- return false;
211
- // Check that at least one prior call was an edit/write
212
- const priorCalls = session.toolCalls.slice(0, i);
213
- return priorCalls.some((c) => EDIT_TOOLS.has(c.toolName));
214
- });
215
- if (hasWriteBeforeFailure) {
216
- return { applicable: true, reason: 'Write operation followed by failure — opportunity to gather evidence before retry' };
217
- }
218
- // Also applicable: pain signal with severity moderate+ (indicating something went wrong after a change)
219
- const hasSignificantPain = session.painSignals.some((p) => p.severity === 'moderate' || p.severity === 'severe');
220
- if (hasSignificantPain) {
221
- return { applicable: true, reason: 'Significant pain signal — opportunity to use evidence over assumption' };
222
- }
223
- return { applicable: false, reason: 'No pain or failure on write operations — T-03 not applicable' };
224
- }
225
- /**
226
- * T-04 "Reversible First" — Prefer changes that are safe to roll back.
227
- *
228
- * APPLICABLE when: Risky or destructive operations are attempted.
229
- */
230
- function detectT04Opportunity(session) {
231
- const hasRisky = session.toolCalls.some((call) => RISKY_TOOLS.has(call.toolName) || call.toolName === 'bash');
232
- if (hasRisky) {
233
- return { applicable: true, reason: 'Risky/destructive operations — opportunity to prefer reversible changes' };
234
- }
235
- return { applicable: false, reason: 'No risky operations — T-04 not applicable' };
236
- }
237
- /**
238
- * T-05 "Safety Rails" — Call out guardrails, prohibitions, failure-prevention constraints.
239
- *
240
- * APPLICABLE when: A gate block fires on a risky operation.
241
- * Rationale: The gate block IS the safety rail being tested. An opportunity
242
- * exists when the system judged an operation risky enough to block.
243
- * This makes T-05 applicable ONLY when gate blocks fire — preventing dilution
244
- * by unrelated sessions.
245
- *
246
- * IMPORTANT: T-05's compliance is tied to gate blocks specifically.
247
- * A risky operation without a gate block may still be a T-05 opportunity
248
- * if the reason mentions safety-relevant terms.
249
- */
250
- function detectT05Opportunity(session) {
251
- const hasGateBlock = session.gateBlocks.length > 0;
252
- if (hasGateBlock) {
253
- return {
254
- applicable: true,
255
- reason: 'Gate block present — opportunity to call out safety rails',
256
- };
257
- }
258
- // Also applicable when a risky operation is attempted
259
- // (even if not yet blocked — the agent should self-censor)
260
- const hasRisky = session.toolCalls.some((call) => {
261
- if (RISKY_TOOLS.has(call.toolName))
262
- return true;
263
- // Check bash for dangerous patterns
264
- if (call.toolName === 'bash' && call.errorMessage) {
265
- const msg = call.errorMessage;
266
- return DANGEROUS_BASH_PATTERNS.some((p) => p.test(msg));
267
- }
268
- return false;
269
- });
270
- if (hasRisky) {
271
- return {
272
- applicable: true,
273
- reason: 'Risky operation attempted — opportunity to apply safety rails',
274
- };
275
- }
276
- return {
277
- applicable: false,
278
- reason: 'No gate blocks or risky operations — T-05 not applicable in this session',
279
- };
280
- }
281
- /**
282
- * T-06 "Simplicity First" — Prefer the smallest understandable solution.
283
- *
284
- * APPLICABLE when: The task involves non-trivial code creation or refactoring.
285
- */
286
- function detectT06Opportunity(session) {
287
- const hasNonTrivialWrite = session.toolCalls.some((call) => call.toolName === 'create_file' ||
288
- call.toolName === 'write_to_file' ||
289
- (call.toolName === 'bash' && /\b(refactor|rewrite|overhaul)\b/i.test(call.errorMessage ?? '')));
290
- if (hasNonTrivialWrite) {
291
- return {
292
- applicable: true,
293
- reason: 'Non-trivial code creation — opportunity to prefer simplicity',
294
- };
295
- }
296
- return { applicable: false, reason: 'No non-trivial writes — T-06 not applicable' };
297
- }
298
- /**
299
- * T-07 "Minimal Change Surface" — Limit the blast radius.
300
- *
301
- * APPLICABLE when: Multiple files are touched in a single session.
302
- */
303
- function detectT07Opportunity(session) {
304
- const filePaths = session.toolCalls
305
- .map((call) => call.filePath)
306
- .filter((p) => p !== undefined)
307
- .map((p) => normalizePathPosix(p));
308
- const uniqueFiles = new Set(filePaths);
309
- if (uniqueFiles.size >= 3) {
310
- return {
311
- applicable: true,
312
- reason: `Multiple files touched (${uniqueFiles.size}) — opportunity to minimize change surface`,
313
- };
314
- }
315
- return { applicable: false, reason: 'Few files touched — T-07 not applicable' };
316
- }
317
- /**
318
- * T-08 "Pain As Signal" — Treat failures and friction as clues.
319
- *
320
- * APPLICABLE when: Pain signals are present after a failure.
321
- */
322
- function detectT08Opportunity(session) {
323
- const hasPain = session.painSignals.length > 0;
324
- const hasFailure = session.toolCalls.some((call) => call.outcome === 'failure');
325
- if (hasPain && hasFailure) {
326
- return {
327
- applicable: true,
328
- reason: 'Pain signals following failures — opportunity to treat pain as signal',
329
- };
330
- }
331
- return { applicable: false, reason: 'No pain-after-failure — T-08 not applicable' };
332
- }
333
- /**
334
- * T-09 "Divide And Conquer" — Split the task into smaller phases before execution.
335
- *
336
- * APPLICABLE when: Complex operations are attempted (multi-file edits, refactors,
337
- * architecture changes) OR when pain events occur on complex tasks.
338
- *
339
- * COMPLEXITY INDICATORS:
340
- * - 5+ tool calls in a session (indicates multi-step task)
341
- * - Multiple file paths touched
342
- * - Pain events on multi-step tasks
343
- * - Explicit "complex" or "refactor" or "architecture" in operations
344
- */
345
- function detectT09Opportunity(session) {
346
- const toolCallCount = session.toolCalls.length;
347
- const uniqueFiles = new Set(session.toolCalls
348
- .map((call) => call.filePath)
349
- .filter((p) => p !== undefined)
350
- .map((p) => normalizePathPosix(p)));
351
- const hasComplexity = toolCallCount >= 5 || uniqueFiles.size >= 3;
352
- const hasPain = session.painSignals.length > 0;
353
- const hasFailure = session.toolCalls.some((call) => call.outcome === 'failure');
354
- if (hasComplexity) {
355
- return {
356
- applicable: true,
357
- reason: `Complex task detected (${toolCallCount} calls, ${uniqueFiles.size} files) — opportunity to decompose`,
358
- };
359
- }
360
- if (hasPain || hasFailure) {
361
- // Pain/failure may indicate the task was too complex without decomposition
362
- return {
363
- applicable: true,
364
- reason: 'Pain or failure present — opportunity to decompose before retry',
365
- };
366
- }
367
- return {
368
- applicable: false,
369
- reason: 'No complexity indicators — T-09 not applicable in this session',
370
- };
371
- }
372
- // ---------------------------------------------------------------------------
373
- // Violation Detection
374
- // ---------------------------------------------------------------------------
375
- /**
376
- * Detects whether a principle was VIOLATED in a session where an
377
- * opportunity was applicable.
378
- *
379
- * Returns a ViolationMatch with violated=true if violation signals are present.
380
- *
381
- * #216: For P_* principles (pain-derived), violation is detected when the session
382
- * has pain signals, tool failures, or gate blocks that match the principle's
383
- * trigger pattern. Since P_* principles don't have T-xx specific detectors,
384
- * we use the presence of negative signals as violation evidence.
385
- */
386
- export function detectViolation(principleId, session) {
387
- // #216: P_* principles (pain-derived) — generic violation detection
388
- if (principleId.startsWith('P_')) {
389
- // For pain-derived principles, a violation is indicated when the session
390
- // contains pain signals, tool failures, or gate blocks — these are the
391
- // same signals that triggered principle creation in the first place.
392
- // A principle was violated if the bad outcome recurred after it was created.
393
- const painSignals = session.painSignals.filter((p) => p.score >= 50);
394
- const toolFailures = session.toolCalls.filter((tc) => tc.outcome === 'failure');
395
- const { gateBlocks } = session;
396
- if (painSignals.length > 0) {
397
- return { violated: true, reason: `P_* principle — ${painSignals.length} pain signal(s) detected (max score: ${Math.max(...painSignals.map(p => p.score))})` };
398
- }
399
- if (toolFailures.length > 0) {
400
- return { violated: true, reason: `P_* principle — ${toolFailures.length} tool failure(s) detected` };
401
- }
402
- if (gateBlocks.length > 0) {
403
- return { violated: true, reason: `P_* principle — ${gateBlocks.length} gate block(s) detected` };
404
- }
405
- return { violated: false, reason: `P_* principle — no violation signals in session` };
406
- }
407
- // T-xx principles — specific deterministic detection
408
- switch (principleId) {
409
- case 'T-01':
410
- return detectT01Violation(session);
411
- case 'T-02':
412
- return detectT02Violation(session);
413
- case 'T-03':
414
- return detectT03Violation(session);
415
- case 'T-04':
416
- return detectT04Violation(session);
417
- case 'T-05':
418
- return detectT05Violation(session);
419
- case 'T-06':
420
- return detectT06Violation(session);
421
- case 'T-07':
422
- return detectT07Violation(session);
423
- case 'T-08':
424
- return detectT08Violation(session);
425
- case 'T-09':
426
- return detectT09Violation(session);
427
- default:
428
- console.warn(`[PD:Compliance] Unknown principle ID: ${principleId} — treating as no violation. Check for typos (P-001 vs P_001).`);
429
- return { violated: false, reason: `Unknown principle: ${principleId}` };
430
- }
431
- }
432
- /**
433
- * T-01 violation:
434
- * - Pain signal or tool failure on an edit where the file was NOT read first
435
- * - Pain signal with source indicating structural misunderstanding
436
- */
437
- function detectT01Violation(session) {
438
- // Build set of files that were read (normalized for cross-platform consistency)
439
- const readFiles = new Set(session.toolCalls
440
- .filter((call) => READ_TOOLS.has(call.toolName))
441
- .map((call) => call.filePath)
442
- .filter((p) => p !== undefined)
443
- .map((p) => normalizePathPosix(p)));
444
- // Find edits to files that were NOT read first
445
- const unreadEdits = session.toolCalls.filter((call) => EDIT_TOOLS.has(call.toolName) &&
446
- call.filePath !== undefined &&
447
- !readFiles.has(normalizePathPosix(call.filePath)));
448
- // If there were edits to unread files AND pain/failure followed → T-01 likely violated
449
- if (unreadEdits.length > 0) {
450
- const painOnUnreadEdit = session.painSignals.some((p) => unreadEdits.some((e) => e.filePath !== undefined && p.source.includes(e.filePath)) ||
451
- /structure|architecture|dependency|context|before.*edit|survey/i.test(p.reason ?? ''));
452
- if (painOnUnreadEdit) {
453
- return {
454
- violated: true,
455
- reason: `Edits to unread files (${unreadEdits.length}) followed by pain — T-01 violated: agent acted without surveying first`,
456
- };
457
- }
458
- // If edits to unread files AND tool failures → likely violated
459
- const failuresOnUnread = unreadEdits.some((e) => e.outcome === 'failure');
460
- if (failuresOnUnread) {
461
- return {
462
- violated: true,
463
- reason: `Edits to unread files (${unreadEdits.length}) followed by failures — T-01 violated: agent acted without understanding`,
464
- };
465
- }
466
- }
467
- // Also check for pain signals specifically mentioning T-01-relevant themes
468
- // without any prior read
469
- const hasPainTheme = /structure|architecture|context|before.*acting|didn't.*survey|didn't.*read.*first/i.test(session.painSignals.map((p) => p.reason ?? '').join(' '));
470
- if (hasPainTheme && unreadEdits.length > 0) {
471
- return {
472
- violated: true,
473
- reason: 'Pain signals mentioning structure/context themes after edits to unread files — T-01 violated',
474
- };
475
- }
476
- return {
477
- violated: false,
478
- reason: 'No violation signals detected for T-01',
479
- };
480
- }
481
- /**
482
- * T-02 violation:
483
- * - Tool failures on type/test/contract interactions without prior verification
484
- */
485
- function detectT02Violation(session) {
486
- const constraintFailures = session.toolCalls.filter((call) => call.outcome === 'failure' &&
487
- call.filePath !== undefined &&
488
- (/\b(test|spec|contract|schema|interface|type)\b/i.test(call.filePath) ||
489
- /\b(type|test|contract)\b/i.test(call.errorMessage ?? '')));
490
- if (constraintFailures.length > 0) {
491
- return {
492
- violated: true,
493
- reason: `Tool failures on type/test/contract interactions (${constraintFailures.length}) — T-02 violated: constraints not verified`,
494
- };
495
- }
496
- return { violated: false, reason: 'No violation signals for T-02' };
497
- }
498
- /**
499
- * T-03 violation:
500
- * - Tool failures without prior evidence gathering (no read calls before failure)
501
- */
502
- function detectT03Violation(session) {
503
- const failureIndices = session.toolCalls
504
- .map((call, i) => (call.outcome === 'failure' ? i : -1))
505
- .filter((i) => i >= 0);
506
- for (const failIdx of failureIndices) {
507
- const priorCalls = session.toolCalls.slice(0, failIdx);
508
- const hasPriorRead = priorCalls.some((call) => READ_TOOLS.has(call.toolName) && call.filePath !== undefined);
509
- if (!hasPriorRead) {
510
- return {
511
- violated: true,
512
- reason: `Tool failure at index ${failIdx} without prior read operations — T-03 violated: assumption made without evidence`,
513
- };
514
- }
515
- }
516
- return { violated: false, reason: 'No violation signals for T-03' };
517
- }
518
- /**
519
- * T-04 violation:
520
- * - Pain signals following risky operations (the operation succeeded but caused issues)
521
- */
522
- function detectT04Violation(session) {
523
- const riskyIndices = session.toolCalls
524
- .map((call, i) => (RISKY_TOOLS.has(call.toolName) || call.toolName === 'bash' ? i : -1))
525
- .filter((i) => i >= 0);
526
- if (riskyIndices.length === 0)
527
- return { violated: false, reason: 'No risky operations — T-04 not violated' };
528
- // If risky operations AND pain signals are present in the same session,
529
- // that indicates the risky operation caused negative consequences.
530
- const hasPain = session.painSignals.length > 0;
531
- if (hasPain) {
532
- return {
533
- violated: true,
534
- reason: 'Pain signals present alongside risky operations — T-04 violated: irreversible consequences',
535
- };
536
- }
537
- return { violated: false, reason: 'No violation signals for T-04' };
538
- }
539
- /**
540
- * T-05 violation:
541
- * - Gate block fires → the agent tried a risky operation without first applying
542
- * safety reasoning. The gate block IS the violation signal.
543
- * - Gate block on a dangerous bash command is an explicit violation.
544
- */
545
- function detectT05Violation(session) {
546
- if (session.gateBlocks.length > 0) {
547
- // Check if any gate block was on a dangerous operation.
548
- // A block is dangerous if:
549
- // 1. The tool is in RISKY_TOOLS (delete_file, bash, MultiExec, etc.)
550
- // 2. The tool is 'bash' AND the reason mentions a dangerous pattern
551
- // 3. The reason contains risky keywords (delete, force, credential, exec, etc.)
552
- const dangerousBlocks = session.gateBlocks.filter((block) => {
553
- if (RISKY_TOOLS.has(block.toolName))
554
- return true;
555
- if (block.toolName === 'bash' && DANGEROUS_BASH_PATTERNS.some((p) => p.test(block.reason)))
556
- return true;
557
- // Fallback: scan reason for risky keywords
558
- if (RISKY_KEYWORDS_IN_REASON.some((p) => p.test(block.reason)))
559
- return true;
560
- return false;
561
- });
562
- if (dangerousBlocks.length > 0) {
563
- return {
564
- violated: true,
565
- reason: `Gate blocks on dangerous operations (${dangerousBlocks.length}) — T-05 violated: safety rail not called out`,
566
- };
567
- }
568
- return {
569
- violated: true,
570
- reason: `Gate blocks present (${session.gateBlocks.length}) — T-05 violated: safety rail not respected`,
571
- };
572
- }
573
- return { violated: false, reason: 'No gate blocks — T-05 not violated' };
574
- }
575
- /**
576
- * T-06 violation:
577
- * - Over-engineering signals: pain from overly complex solutions
578
- */
579
- function detectT06Violation(session) {
580
- const hasOverEngineerPain = session.painSignals.some((p) => /over.*engineer|over.*complicat|too.*complex|unnecessarily.*complex/i.test(p.reason ?? '') &&
581
- p.severity === 'severe');
582
- if (hasOverEngineerPain) {
583
- return {
584
- violated: true,
585
- reason: 'Severe pain from over-engineering — T-06 violated: simplicity not preferred',
586
- };
587
- }
588
- return { violated: false, reason: 'No over-engineering signals — T-06 not violated' };
589
- }
590
- /**
591
- * T-07 violation:
592
- * - Pain from wide blast radius: many files modified, cascading failures
593
- */
594
- function detectT07Violation(session) {
595
- const modifiedFiles = new Set(session.toolCalls
596
- .filter((call) => EDIT_TOOLS.has(call.toolName))
597
- .map((call) => call.filePath)
598
- .filter((p) => p !== undefined)
599
- .map((p) => normalizePathPosix(p)));
600
- const failures = session.toolCalls.filter((call) => call.outcome === 'failure');
601
- if (modifiedFiles.size >= 5 && failures.length >= 2) {
602
- return {
603
- violated: true,
604
- reason: `Wide blast radius (${modifiedFiles.size} files, ${failures.length} failures) — T-07 violated: change surface not minimized`,
605
- };
606
- }
607
- return { violated: false, reason: 'No blast radius violations — T-07 not violated' };
608
- }
609
- /**
610
- * T-08 violation:
611
- * - Pain signal present but no reflection/self-correction behavior
612
- * (This is harder to detect without explicit reflection events.
613
- * We use pain-without-correction as a proxy.)
614
- */
615
- function detectT08Violation(session) {
616
- const hasPain = session.painSignals.length > 0;
617
- const hasFailure = session.toolCalls.some((call) => call.outcome === 'failure');
618
- // If pain and failure, but the agent immediately retries without pause/reflect
619
- if (hasPain && hasFailure) {
620
- // Find the first failure index and check if the agent continued without reflecting
621
- const failureIdx = session.toolCalls.findIndex((c) => c.outcome === 'failure');
622
- if (failureIdx >= 0) {
623
- const postFailure = session.toolCalls.slice(failureIdx + 1, failureIdx + 4);
624
- // If the agent immediately continues without a read/reflect call, T-08 may be violated
625
- const continuesImmediately = postFailure.length > 0 && !postFailure.some((c) => READ_TOOLS.has(c.toolName));
626
- if (continuesImmediately) {
627
- return {
628
- violated: true,
629
- reason: 'Failure followed immediately by continued operations without pause/reflect — T-08 violated: pain not treated as signal',
630
- };
631
- }
632
- }
633
- }
634
- return { violated: false, reason: 'No T-08 violation signals detected' };
635
- }
636
- /**
637
- * T-09 violation:
638
- * - Pain or failures on complex tasks that should have been decomposed.
639
- * Signal: pain/failure on multi-step task without prior planning calls.
640
- */
641
- function detectT09Violation(session) {
642
- const toolCallCount = session.toolCalls.length;
643
- const uniqueFiles = new Set(session.toolCalls
644
- .map((call) => call.filePath)
645
- .filter((p) => p !== undefined)
646
- .map((p) => normalizePathPosix(p)));
647
- // Only applies if the session was complex
648
- if (toolCallCount < 5 && uniqueFiles.size < 3) {
649
- return { violated: false, reason: 'Session not complex enough for T-09 applicability' };
650
- }
651
- // Check: failures on complex task without prior planning
652
- const hasFailures = session.toolCalls.some((call) => call.outcome === 'failure');
653
- const hasPain = session.painSignals.length > 0;
654
- if (hasFailures || hasPain) {
655
- // Check if the agent showed decomposition/planning behavior
656
- const hasPlanApproval = session.planApprovals.length > 0;
657
- const hasReadFirst = session.toolCalls.some((call) => READ_TOOLS.has(call.toolName));
658
- if (!hasPlanApproval && !hasReadFirst) {
659
- return {
660
- violated: true,
661
- reason: `Complex task with failures/pain but no planning or decomposition signals — T-09 violated: task not divided`,
662
- };
663
- }
664
- }
665
- return { violated: false, reason: 'No T-09 violation signals' };
666
- }
667
- // ---------------------------------------------------------------------------
668
- // Compliance Computation
669
- // ---------------------------------------------------------------------------
670
- /**
671
- * Computes compliance metrics for a single T-xx principle across a batch of sessions.
672
- *
673
- * DILUTION PREVENTION:
674
- * - Sessions where the principle had NO opportunity are EXCLUDED from
675
- * applicableOpportunityCount and do not affect complianceRate.
676
- * - Example: T-05 sessions with no risky operations do not dilute
677
- * the compliance rate computed from T-05 sessions with gate blocks.
678
- *
679
- * TREND COMPUTATION:
680
- * - Sessions are ordered chronologically (most recent first).
681
- * - Current window: last 3 applicable sessions.
682
- * - Previous window: sessions 4-6 (if available).
683
- * - If either window has < 1 applicable session, trend = 0 (insufficient data).
684
- * - Otherwise: trend = prevViolationRate - currentViolationRate
685
- * (+1 = improving, 0 = stable, -1 = worsening).
686
- */
687
- /**
688
- * Computes violation trend across the applicable session list.
689
- *
690
- * Trend is positive (+1) when violations are DECREASING (improving).
691
- * Trend is negative (-1) when violations are INCREASING (worsening).
692
- *
693
- * Sessions are ordered most-recent-first.
694
- * currentWindow = first windowSize sessions (most recent)
695
- * previousWindow = next windowSize sessions
696
- */
697
- function computeViolationTrend(applicableSessions, windowSize) {
698
- if (applicableSessions.length < 2) {
699
- // Not enough data for trend
700
- return 0;
701
- }
702
- // Sessions are ordered most-recent-first in the input array.
703
- // currentWindow = most recent N sessions
704
- // previousWindow = N sessions before that (older)
705
- const currentWindow = applicableSessions.slice(0, windowSize);
706
- const previousWindow = applicableSessions.slice(windowSize, windowSize * 2);
707
- if (currentWindow.length === 0)
708
- return 0;
709
- const currentViolationRate = currentWindow.filter((s) => s.violated).length / currentWindow.length;
710
- if (previousWindow.length === 0) {
711
- // No previous window — compare to overall rate
712
- const overallRate = applicableSessions.filter((s) => s.violated).length / applicableSessions.length;
713
- if (currentViolationRate < overallRate - 0.1)
714
- return 1; // improving
715
- if (currentViolationRate > overallRate + 0.1)
716
- return -1; // worsening
717
- return 0;
718
- }
719
- const previousViolationRate = previousWindow.filter((s) => s.violated).length / previousWindow.length;
720
- const delta = previousViolationRate - currentViolationRate;
721
- if (delta > 0.1)
722
- return 1; // violations decreasing → improving
723
- if (delta < -0.1)
724
- return -1; // violations increasing → worsening
725
- return 0; // stable
726
- }
727
- /**
728
- * Builds a human-readable explanation for the compliance result.
729
- */
730
- function buildExplanation(params) {
731
- const { principleId, applicableOpportunityCount, observedViolationCount, complianceRate, violationTrend, applicableSessions, } = params;
732
- const trendStr = violationTrend === 1
733
- ? '↑ improving'
734
- : violationTrend === -1
735
- ? '↓ worsening'
736
- : '→ stable';
737
- if (applicableOpportunityCount === 0) {
738
- return `${principleId}: No applicable opportunities in provided sessions — compliance cannot be assessed.`;
739
- }
740
- const violationExamples = applicableSessions
741
- .filter((s) => s.violated)
742
- .slice(0, 2)
743
- .map((s) => ` • ${s.reason}`)
744
- .join('\n');
745
- return [
746
- `${principleId}: ${applicableOpportunityCount} applicable opportunities, ${observedViolationCount} violations.`,
747
- `Compliance rate: ${(complianceRate * 100).toFixed(1)}%. Trend: ${trendStr}.`,
748
- violationExamples ? `Sample violation signals:\n${violationExamples}` : 'No violations detected in recent sessions.',
749
- ].join('\n');
750
- }
751
- /**
752
- * Computes compliance metrics for a single T-xx principle across a batch of sessions.
753
- *
754
- * DILUTION PREVENTION:
755
- * - Sessions where the principle had NO opportunity are EXCLUDED from
756
- * applicableOpportunityCount and do not affect complianceRate.
757
- * - Example: T-05 sessions with no risky operations do not dilute
758
- * the compliance rate computed from T-05 sessions with gate blocks.
759
- *
760
- * TREND COMPUTATION:
761
- * - Sessions are ordered chronologically (most recent first).
762
- * - Current window: last 3 applicable sessions.
763
- * - Previous window: sessions 4-6 (if available).
764
- * - If either window has < 1 applicable session, trend = 0 (insufficient data).
765
- * - Otherwise: trend = prevViolationRate - currentViolationRate
766
- * (+1 = improving, 0 = stable, -1 = worsening).
767
- */
768
- export function computeCompliance(principleId, sessions, options = {}) {
769
- const windowSize = options.trendWindowSize ?? 3;
770
- let applicableOpportunityCount = 0;
771
- let observedViolationCount = 0;
772
- const applicableSessions = [];
773
- for (const session of sessions) {
774
- const opp = detectOpportunity(principleId, session);
775
- if (!opp.applicable) {
776
- // Principle had no opportunity in this session — skip entirely.
777
- // This is the key dilution-prevention mechanism.
778
- continue;
779
- }
780
- applicableOpportunityCount++;
781
- const violation = detectViolation(principleId, session);
782
- if (violation.violated) {
783
- observedViolationCount++;
784
- }
785
- applicableSessions.push({
786
- session,
787
- violated: violation.violated,
788
- reason: violation.reason,
789
- });
790
- }
791
- // Compute complianceRate
792
- const complianceRate = applicableOpportunityCount > 0
793
- ? (applicableOpportunityCount - observedViolationCount) / applicableOpportunityCount
794
- : 0;
795
- // Compute violationTrend using windows
796
- const violationTrend = computeViolationTrend(applicableSessions, windowSize);
797
- // Build explanation
798
- const explanation = buildExplanation({
799
- principleId,
800
- applicableOpportunityCount,
801
- observedViolationCount,
802
- complianceRate,
803
- violationTrend,
804
- applicableSessions,
805
- });
806
- return {
807
- principleId,
808
- applicableOpportunityCount,
809
- observedViolationCount,
810
- complianceRate,
811
- violationTrend,
812
- explanation,
813
- };
814
- }
815
- // ---------------------------------------------------------------------------
816
- // Batch Update Helpers
817
- // ---------------------------------------------------------------------------
818
- /**
819
- * Computes compliance results for all T-01 through T-09 principles
820
- * across the provided sessions.
821
- *
822
- * Sessions are assumed to be ordered most-recent-first.
823
- */
824
- export function computeAllCompliance(sessions, options = {}) {
825
- const results = [];
826
- for (const id of ['T-01', 'T-02', 'T-03', 'T-04', 'T-05', 'T-06', 'T-07', 'T-08', 'T-09']) {
827
- results.push(computeCompliance(id, sessions, options));
828
- }
829
- return results;
830
- }
831
- /**
832
- * Converts raw EventLogEntry[] from event-types.ts into SessionEvents.
833
- *
834
- * Groups events by sessionId and maps to the SessionEvents interface.
835
- * Events with no sessionId are grouped under sessionId = 'unknown'.
836
- */
837
- export function groupEventsIntoSessions(events) {
838
- const sessionMap = new Map();
839
- for (const event of events) {
840
- const sessionId = event.sessionId ?? 'unknown';
841
- if (!sessionMap.has(sessionId)) {
842
- sessionMap.set(sessionId, {
843
- sessionId,
844
- toolCalls: [],
845
- painSignals: [],
846
- gateBlocks: [],
847
- userCorrections: [],
848
- planApprovals: [],
849
- });
850
- }
851
- const session = sessionMap.get(sessionId);
852
- if (!session)
853
- continue;
854
- switch (event.type) {
855
- case 'tool_call': {
856
- const { toolName } = event.data;
857
- if (typeof toolName !== 'string' || toolName.length === 0)
858
- break;
859
- session.toolCalls.push({
860
- toolName,
861
- filePath: typeof event.data.filePath === 'string' ? event.data.filePath : undefined,
862
- outcome: event.data.error ? 'failure' : 'success',
863
- errorType: typeof event.data.errorType === 'string' ? event.data.errorType : undefined,
864
- errorMessage: typeof event.data.error === 'string' ? event.data.error : undefined,
865
- });
866
- break;
867
- }
868
- case 'pain_signal': {
869
- const { score } = event.data;
870
- if (typeof score !== 'number' || !Number.isFinite(score))
871
- break;
872
- session.painSignals.push({
873
- source: typeof event.data.source === 'string' ? event.data.source : 'unknown',
874
- score,
875
- severity: event.data.severity === 'mild' || event.data.severity === 'moderate' || event.data.severity === 'severe' ? event.data.severity : undefined,
876
- reason: typeof event.data.reason === 'string' ? event.data.reason : undefined,
877
- });
878
- break;
879
- }
880
- case 'gate_block': {
881
- const { toolName, reason } = event.data;
882
- if (typeof toolName !== 'string' || toolName.length === 0)
883
- break;
884
- if (typeof reason !== 'string' || reason.length === 0)
885
- break;
886
- session.gateBlocks.push({
887
- toolName,
888
- filePath: typeof event.data.filePath === 'string' ? event.data.filePath : undefined,
889
- reason,
890
- });
891
- break;
892
- }
893
- case 'empathy_rollback':
894
- session.userCorrections.push({
895
- correctionCue: typeof event.data.reason === 'string' ? event.data.reason : undefined,
896
- });
897
- break;
898
- case 'plan_approval': {
899
- const { toolName } = event.data;
900
- if (typeof toolName !== 'string' || toolName.length === 0)
901
- break;
902
- session.planApprovals.push({
903
- toolName,
904
- filePath: typeof event.data.filePath === 'string' ? event.data.filePath : undefined,
905
- });
906
- break;
907
- }
908
- }
909
- }
910
- return sessionMap;
911
- }
912
- //# sourceMappingURL=nocturnal-compliance.js.map