@a9i5k4/dsh-auto-memory 0.1.29 → 0.1.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +352 -266
- package/README.zh-CN.md +370 -264
- package/cordis.patch.yml +9 -9
- package/lib/activation-host.js +455 -0
- package/lib/activation-inbox-state.js +261 -0
- package/lib/activation-inbox.js +426 -0
- package/lib/client.js +1379 -50
- package/lib/context-bridge.js +619 -0
- package/lib/context-host.js +712 -0
- package/lib/context-sink-python.js +90 -0
- package/lib/episodic-store.js +316 -0
- package/lib/evidence-store.js +272 -0
- package/lib/fact-store.js +418 -0
- package/lib/index-sync.js +160 -0
- package/lib/index.js +2357 -102
- package/lib/intent-clean.js +74 -0
- package/lib/m4-corpus.js +169 -0
- package/lib/m7-index-sync-host.js +194 -0
- package/lib/m7-wire.js +268 -0
- package/lib/memory-anchor.js +451 -0
- package/lib/memory-hub.js +259 -0
- package/lib/memory-index.js +145 -0
- package/lib/memory-writer.js +391 -0
- package/lib/policies/activation_policy_v2.json +88 -0
- package/lib/policies/recall_intent_lr_v1.json +1 -0
- package/lib/procedure-store.js +406 -0
- package/lib/python-sidecar-client.js +326 -0
- package/lib/semantic-decide.js +265 -0
- package/lib/semantic-js.js +381 -0
- package/lib/shadow-host.js +361 -0
- package/lib/shadow-retrieval.js +673 -0
- package/lib/storage-manage.js +203 -0
- package/package.json +2 -2
package/lib/m7-wire.js
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* M7-0 Wire Protocol 纯核心(docs/PYTHON-SIDECAR-CONTRACT.md §7-§9,§13 M7-0)。
|
|
3
|
+
* 零 IO、零依赖(node:crypto);本模块不 spawn、不监听、不读文件、不改 M5/M6 schema。
|
|
4
|
+
*
|
|
5
|
+
* 组成:
|
|
6
|
+
* 1) 协议常量(m7_wire_v1 / 传输预算 / 帧类型两个不相交集合 / 请求→响应对应)
|
|
7
|
+
* 2) canonical JSON + SHA-256(JS 与 Python worker 的逐字节一致实现;排序键、无空白、UTF-8)
|
|
8
|
+
* 3) M7TransportFramePre envelope validator(fail closed;方向门)
|
|
9
|
+
* 4) SemanticRecordPre / IndexSyncBegin/Page/Commit payload validators(M7-1)
|
|
10
|
+
* 5) pageDigest/finalDigest/chunkId/syncId canonical identity(M7-1)
|
|
11
|
+
* 全部函数同输入逐字段确定;UTF-8 无 BOM。
|
|
12
|
+
*/
|
|
13
|
+
import { createHash } from 'node:crypto'
|
|
14
|
+
|
|
15
|
+
const sha256Hex = (buf) => createHash('sha256').update(buf).digest('hex')
|
|
16
|
+
const sha256Str = (s) => sha256Hex(Buffer.from(String(s), 'utf8'))
|
|
17
|
+
const first32 = (h) => h.slice(0, 32)
|
|
18
|
+
|
|
19
|
+
export const M7_WIRE_PROTOCOL_VERSION_V1 = 'm7_wire_v1'
|
|
20
|
+
export const M7_INDEX_POLICY_VERSION_V1 = 'index_sync_v1'
|
|
21
|
+
|
|
22
|
+
/** §7 传输预算(冻结;变更必须升级协议版本)。 */
|
|
23
|
+
export const M7_TRANSPORT_BUDGET_V1 = Object.freeze({
|
|
24
|
+
schemaVersion: 1,
|
|
25
|
+
maxLineBytes: 256 * 1024,
|
|
26
|
+
requestTimeoutMs: 5000,
|
|
27
|
+
maxPendingRequests: 64,
|
|
28
|
+
activationIdsCapacity: 256,
|
|
29
|
+
breakerFailureThreshold: 3,
|
|
30
|
+
breakerCooldownMs: 30000,
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
/** JS→Python frame 类型(§7.2;index_sync_* 属 M7-1)。 */
|
|
34
|
+
export const JS_FRAME_TYPES_V1 = Object.freeze([
|
|
35
|
+
'health', 'context_push', 'index_sync_begin', 'index_sync_page', 'index_sync_commit',
|
|
36
|
+
'cancel', 'close_session',
|
|
37
|
+
])
|
|
38
|
+
/** Python→JS frame 类型。 */
|
|
39
|
+
export const PY_FRAME_TYPES_V1 = Object.freeze([
|
|
40
|
+
'health_result', 'context_ack', 'index_ack', 'activation_request', 'error',
|
|
41
|
+
])
|
|
42
|
+
const ALL_FRAME_TYPES = new Set([...JS_FRAME_TYPES_V1, ...PY_FRAME_TYPES_V1])
|
|
43
|
+
/** 请求→响应 type 对应(cancel/close_session 刻意无响应帧)。 */
|
|
44
|
+
export const RESPONSE_TYPE_FOR_V1 = Object.freeze({
|
|
45
|
+
health: 'health_result',
|
|
46
|
+
context_push: 'context_ack',
|
|
47
|
+
index_sync_begin: 'index_ack',
|
|
48
|
+
index_sync_page: 'index_ack',
|
|
49
|
+
index_sync_commit: 'index_ack',
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
// ========== canonical JSON(与 python/worker_v1.py 逐字节一致) ==========
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 确定性 canonical JSON:对象键递归排序、无空白、非 ASCII 原样 UTF-8、undefined 剔除。
|
|
56
|
+
* 仅用于 digest 计算;参与 digest 的值必须限于 str/int/bool/null/list/dict(不含浮点)。
|
|
57
|
+
*/
|
|
58
|
+
export function canonicalJson(value) {
|
|
59
|
+
if (value === undefined) return 'null'
|
|
60
|
+
if (value === null || typeof value === 'boolean' || typeof value === 'number' || typeof value === 'string') {
|
|
61
|
+
return JSON.stringify(value)
|
|
62
|
+
}
|
|
63
|
+
if (Array.isArray(value)) return '[' + value.map((v) => canonicalJson(v)).join(',') + ']'
|
|
64
|
+
if (typeof value === 'object') {
|
|
65
|
+
const keys = Object.keys(value).sort()
|
|
66
|
+
return '{' + keys.map((k) => JSON.stringify(k) + ':' + canonicalJson(value[k])).join(',') + '}'
|
|
67
|
+
}
|
|
68
|
+
return 'null'
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** canonical JSON 的 SHA-256(hex64)。 */
|
|
72
|
+
export function sha256Canonical(value) {
|
|
73
|
+
return sha256Str(canonicalJson(value))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ========== envelope validator ==========
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* M7TransportFramePre 校验(§7.2):七字段全检;opts.direction 门控方向(in=PY_TO_JS/out=JS_TO_PY)。
|
|
80
|
+
*/
|
|
81
|
+
export function validateTransportFramePre(frame, opts = {}) {
|
|
82
|
+
const p = []
|
|
83
|
+
if (!frame || typeof frame !== 'object' || Array.isArray(frame)) return { ok: false, reason: 'not-object' }
|
|
84
|
+
if (frame.protocolVersion !== M7_WIRE_PROTOCOL_VERSION_V1) p.push('protocolVersion')
|
|
85
|
+
if (typeof frame.frameId !== 'string' || !frame.frameId) p.push('frameId')
|
|
86
|
+
if (typeof frame.requestId !== 'string') p.push('requestId')
|
|
87
|
+
if (typeof frame.workerEpoch !== 'string' || !frame.workerEpoch) p.push('workerEpoch')
|
|
88
|
+
if (!ALL_FRAME_TYPES.has(frame.type)) p.push('type')
|
|
89
|
+
if (!frame.payload || typeof frame.payload !== 'object' || Array.isArray(frame.payload)) p.push('payload')
|
|
90
|
+
if (typeof frame.sentAt !== 'number' || !Number.isFinite(frame.sentAt)) p.push('sentAt')
|
|
91
|
+
if (!p.length && opts.direction === 'in' && !PY_FRAME_TYPES_V1.includes(frame.type)) p.push('direction')
|
|
92
|
+
if (!p.length && opts.direction === 'out' && !JS_FRAME_TYPES_V1.includes(frame.type)) p.push('direction')
|
|
93
|
+
if (p.length) return { ok: false, reason: 'invalid:' + p.join(',') }
|
|
94
|
+
return { ok: true, frame }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 构造出站请求帧(确定性 frameId;sentAt 必填由调用方给时钟)。
|
|
99
|
+
*/
|
|
100
|
+
export function makeRequestFramePre(input) {
|
|
101
|
+
const type = input && input.type
|
|
102
|
+
const requestId = String((input && input.requestId) || '')
|
|
103
|
+
const sentAt = Number(input && input.sentAt)
|
|
104
|
+
const frame = {
|
|
105
|
+
protocolVersion: M7_WIRE_PROTOCOL_VERSION_V1,
|
|
106
|
+
frameId: 'frm_' + first32(sha256Str(JSON.stringify(['m7-frame-pre-v1', type, requestId, sentAt]))),
|
|
107
|
+
requestId,
|
|
108
|
+
workerEpoch: String((input && input.workerEpoch) || ''),
|
|
109
|
+
type,
|
|
110
|
+
payload: (input && input.payload) || {},
|
|
111
|
+
sentAt,
|
|
112
|
+
}
|
|
113
|
+
const v = validateTransportFramePre(frame, { direction: 'out' })
|
|
114
|
+
return v.ok ? { ok: true, frame } : { ok: false, reason: v.reason }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ========== M7-1 index_sync payload validators ==========
|
|
118
|
+
|
|
119
|
+
const MEMORY_ID_RE = /^mem_[0-9a-f]{32}$/
|
|
120
|
+
const HEX64_RE = /^[0-9a-f]{64}$/
|
|
121
|
+
const IDX_VERSION_RE = /^idx_[0-9a-f]{32}$/
|
|
122
|
+
const WORKSPACE_REF_RE = /^wsr_[0-9a-f]{32}$/
|
|
123
|
+
/** 与 M5/M6 同一相对引用白名单(user:/workspace:/workspace-log:+文件名)。 */
|
|
124
|
+
const SOURCE_REF_RE = new RegExp('^(user|workspace|workspace-log):[A-Za-z0-9._\\u4e00-\\u9fff-]+$')
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* SemanticRecordPre 校验(§8.4):全字段硬校验;授权 text 为必填字符串(边界由 JS 决定)。
|
|
128
|
+
*/
|
|
129
|
+
export function validateSemanticRecordPre(rec) {
|
|
130
|
+
const p = []
|
|
131
|
+
if (!rec || typeof rec !== 'object' || Array.isArray(rec)) return { ok: false, reason: 'not-object' }
|
|
132
|
+
if (typeof rec.memoryId !== 'string' || !MEMORY_ID_RE.test(rec.memoryId)) p.push('memoryId')
|
|
133
|
+
if (typeof rec.anchorId !== 'string' || !rec.anchorId) p.push('anchorId')
|
|
134
|
+
if (rec.scope !== 'Workspace' && rec.scope !== 'User') p.push('scope')
|
|
135
|
+
if (typeof rec.workspaceRef !== 'string' || !WORKSPACE_REF_RE.test(rec.workspaceRef)) p.push('workspaceRef')
|
|
136
|
+
if (typeof rec.sourceRef !== 'string' || !SOURCE_REF_RE.test(rec.sourceRef)) p.push('sourceRef')
|
|
137
|
+
if (typeof rec.sourceEpoch !== 'string' || !rec.sourceEpoch) p.push('sourceEpoch')
|
|
138
|
+
if (!Number.isInteger(rec.sourceVersion) || rec.sourceVersion < 1) p.push('sourceVersion')
|
|
139
|
+
if (typeof rec.fileDigest !== 'string' || !HEX64_RE.test(rec.fileDigest)) p.push('fileDigest')
|
|
140
|
+
if (typeof rec.recordDigest !== 'string' || !HEX64_RE.test(rec.recordDigest)) p.push('recordDigest')
|
|
141
|
+
if (rec.heading !== undefined && rec.heading !== null && typeof rec.heading !== 'string') p.push('heading')
|
|
142
|
+
if (typeof rec.text !== 'string') p.push('text')
|
|
143
|
+
if (rec.occurredAt !== undefined && rec.occurredAt !== null && !Number.isFinite(rec.occurredAt)) p.push('occurredAt')
|
|
144
|
+
if (typeof rec.chunkId !== 'string' || !rec.chunkId.startsWith('chk_')) p.push('chunkId')
|
|
145
|
+
if (!Number.isInteger(rec.chunkOrdinal) || rec.chunkOrdinal < 0) p.push('chunkOrdinal')
|
|
146
|
+
if (!Number.isInteger(rec.chunkCount) || rec.chunkCount < 1 || rec.chunkOrdinal >= rec.chunkCount) p.push('chunkCount')
|
|
147
|
+
if (p.length) return { ok: false, reason: 'invalid:' + p.join(',') }
|
|
148
|
+
return { ok: true, record: rec }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** IndexSyncBeginPre payload 校验(§8.4)。 */
|
|
152
|
+
export function validateIndexSyncBeginPre(pl) {
|
|
153
|
+
const p = []
|
|
154
|
+
if (!pl || typeof pl !== 'object') return { ok: false, reason: 'not-object' }
|
|
155
|
+
if (pl.schemaVersion !== 1) p.push('schemaVersion')
|
|
156
|
+
if (typeof pl.syncId !== 'string' || !pl.syncId.startsWith('syn_')) p.push('syncId')
|
|
157
|
+
if (typeof pl.workspaceRef !== 'string' || !WORKSPACE_REF_RE.test(pl.workspaceRef)) p.push('workspaceRef')
|
|
158
|
+
if (pl.scope !== 'Workspace' && pl.scope !== 'User') p.push('scope')
|
|
159
|
+
if (typeof pl.memoryIndexVersion !== 'string' || !IDX_VERSION_RE.test(pl.memoryIndexVersion)) p.push('memoryIndexVersion')
|
|
160
|
+
if (!Array.isArray(pl.sourceTuples)) p.push('sourceTuples')
|
|
161
|
+
else {
|
|
162
|
+
for (const t of pl.sourceTuples) {
|
|
163
|
+
if (!t || typeof t !== 'object' || Array.isArray(t)) { p.push('sourceTuples.entry'); break }
|
|
164
|
+
if (typeof t.sourceRef !== 'string' || !SOURCE_REF_RE.test(t.sourceRef)) { p.push('sourceTuples.sourceRef'); break }
|
|
165
|
+
if (typeof t.sourceEpoch !== 'string' || !t.sourceEpoch) { p.push('sourceTuples.sourceEpoch'); break }
|
|
166
|
+
if (!Number.isInteger(t.sourceVersion) || t.sourceVersion < 1) { p.push('sourceTuples.sourceVersion'); break }
|
|
167
|
+
if (typeof t.fileDigest !== 'string' || !HEX64_RE.test(t.fileDigest)) { p.push('sourceTuples.fileDigest'); break }
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (!Number.isInteger(pl.recordCount) || pl.recordCount < 0) p.push('recordCount')
|
|
171
|
+
if (!Number.isInteger(pl.pageCount) || pl.pageCount < 0) p.push('pageCount')
|
|
172
|
+
if (pl.indexPolicyVersion !== M7_INDEX_POLICY_VERSION_V1) p.push('indexPolicyVersion')
|
|
173
|
+
if (!p.length && (pl.recordCount === 0) !== (pl.pageCount === 0)) p.push('count-consistency')
|
|
174
|
+
if (p.length) return { ok: false, reason: 'invalid:' + p.join(',') }
|
|
175
|
+
return { ok: true, payload: pl }
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** IndexSyncPagePre payload 校验(digest 形状在此;内容一致性由 worker 端状态机判)。 */
|
|
179
|
+
export function validateIndexSyncPagePre(pl) {
|
|
180
|
+
const p = []
|
|
181
|
+
if (!pl || typeof pl !== 'object') return { ok: false, reason: 'not-object' }
|
|
182
|
+
if (pl.schemaVersion !== 1) p.push('schemaVersion')
|
|
183
|
+
if (typeof pl.syncId !== 'string' || !pl.syncId.startsWith('syn_')) p.push('syncId')
|
|
184
|
+
if (!Number.isInteger(pl.pageNo) || pl.pageNo < 0) p.push('pageNo')
|
|
185
|
+
if (!Number.isInteger(pl.pageCount) || pl.pageCount < 0) p.push('pageCount')
|
|
186
|
+
if (typeof pl.pageDigest !== 'string' || !HEX64_RE.test(pl.pageDigest)) p.push('pageDigest')
|
|
187
|
+
if (!Array.isArray(pl.records)) p.push('records')
|
|
188
|
+
else if (pl.records.some((r) => !validateSemanticRecordPre(r).ok)) p.push('records.entry')
|
|
189
|
+
if (p.length) return { ok: false, reason: 'invalid:' + p.join(',') }
|
|
190
|
+
return { ok: true, payload: pl }
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** IndexSyncCommitPre payload 校验。 */
|
|
194
|
+
export function validateIndexSyncCommitPre(pl) {
|
|
195
|
+
const p = []
|
|
196
|
+
if (!pl || typeof pl !== 'object') return { ok: false, reason: 'not-object' }
|
|
197
|
+
if (pl.schemaVersion !== 1) p.push('schemaVersion')
|
|
198
|
+
if (typeof pl.syncId !== 'string' || !pl.syncId.startsWith('syn_')) p.push('syncId')
|
|
199
|
+
if (typeof pl.memoryIndexVersion !== 'string' || !IDX_VERSION_RE.test(pl.memoryIndexVersion)) p.push('memoryIndexVersion')
|
|
200
|
+
if (typeof pl.finalDigest !== 'string' || !HEX64_RE.test(pl.finalDigest)) p.push('finalDigest')
|
|
201
|
+
if (p.length) return { ok: false, reason: 'invalid:' + p.join(',') }
|
|
202
|
+
return { ok: true, payload: pl }
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** index_ack payload 校验(phase ∈ begin|page|commit;accepted=false 时必有 reason)。 */
|
|
206
|
+
export function validateIndexAckPayloadPre(pl) {
|
|
207
|
+
const p = []
|
|
208
|
+
if (!pl || typeof pl !== 'object') return { ok: false, reason: 'not-object' }
|
|
209
|
+
if (pl.schemaVersion !== 1) p.push('schemaVersion')
|
|
210
|
+
if (typeof pl.syncId !== 'string' || !pl.syncId.startsWith('syn_')) p.push('syncId')
|
|
211
|
+
if (!['begin', 'page', 'commit'].includes(pl.phase)) p.push('phase')
|
|
212
|
+
if (typeof pl.accepted !== 'boolean') p.push('accepted')
|
|
213
|
+
if (pl.accepted === false && (typeof pl.reason !== 'string' || !pl.reason)) p.push('reason')
|
|
214
|
+
if (pl.pageNo !== undefined && !Number.isInteger(pl.pageNo)) p.push('pageNo')
|
|
215
|
+
if (typeof pl.memoryIndexVersion !== 'string' || !IDX_VERSION_RE.test(pl.memoryIndexVersion)) p.push('memoryIndexVersion')
|
|
216
|
+
if (typeof pl.workspaceRef !== 'string' || !WORKSPACE_REF_RE.test(pl.workspaceRef)) p.push('workspaceRef')
|
|
217
|
+
if (pl.scope !== 'Workspace' && pl.scope !== 'User') p.push('scope')
|
|
218
|
+
if (p.length) return { ok: false, reason: 'invalid:' + p.join(',') }
|
|
219
|
+
return { ok: true, payload: pl }
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ========== M7-1 canonical identity ==========
|
|
223
|
+
|
|
224
|
+
/** 派生 chunk 身份(占位 chunking=整记录单 chunk;M7-2 tokenizer 落地前冻结此派生规则)。 */
|
|
225
|
+
export function buildChunkIdPre(memoryId, recordDigest) {
|
|
226
|
+
return 'chk_' + first32(sha256Str(JSON.stringify(['semantic-chunk-pre-v1', String(memoryId || ''), String(recordDigest || '')])))
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** syncId:由 workspaceRef+scope+memoryIndexVersion+recordCount 确定(同快照重放同 id)。 */
|
|
230
|
+
export function buildSyncIdPre(workspaceRef, scope, memoryIndexVersion, recordCount) {
|
|
231
|
+
return 'syn_' + first32(sha256Str(JSON.stringify(['index-sync-pre-v1', String(workspaceRef || ''), String(scope || ''), String(memoryIndexVersion || ''), Number(recordCount) | 0])))
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** pageDigest = sha256(canonical(records 数组))。 */
|
|
235
|
+
export function computePageDigestPre(records) {
|
|
236
|
+
return sha256Canonical(Array.isArray(records) ? records : [])
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* finalDigest = sha256(canonical({kind,syncId,memoryIndexVersion,workspaceRef,scope,recordCount,pageCount,pageDigests}))。
|
|
241
|
+
* worker 端用收到的已验证页重算同一函数;不一致即拒绝整次 sync。
|
|
242
|
+
*/
|
|
243
|
+
export function computeFinalDigestPre(input) {
|
|
244
|
+
return sha256Canonical({
|
|
245
|
+
kind: 'index_sync_final_v1',
|
|
246
|
+
syncId: String(input.syncId || ''),
|
|
247
|
+
memoryIndexVersion: String(input.memoryIndexVersion || ''),
|
|
248
|
+
workspaceRef: String(input.workspaceRef || ''),
|
|
249
|
+
scope: String(input.scope || ''),
|
|
250
|
+
recordCount: Number(input.recordCount) | 0,
|
|
251
|
+
pageCount: Number(input.pageCount) | 0,
|
|
252
|
+
pageDigests: Array.isArray(input.pageDigests) ? input.pageDigests : [],
|
|
253
|
+
})
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** ContextAckPre 观测身份回显检查(sink 用;observationId 必须与请求一致才算有效 ack)。 */
|
|
257
|
+
export function ackMatchesObservationPre(ack, observationId) {
|
|
258
|
+
return !!(ack && typeof ack === 'object' && ack.observationId === observationId)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** 静态卫生自检(测试用):剥离注释后确认本模块无进程/网络原语(词面拆分避免自匹配)。 */
|
|
262
|
+
export function wireModuleHygieneOk(sourceText) {
|
|
263
|
+
const code = String(sourceText || '').replace(/\/\*[\s\S]*?\*\//g, ' ').replace(/^\s*\/\/.*$/gm, ' ')
|
|
264
|
+
const BS = String.fromCharCode(92)
|
|
265
|
+
const LPAREN = String.fromCharCode(40)
|
|
266
|
+
const pat = ['child' + '_process', 'node:' + 'net', 'node:' + 'http', 'http' + '.request', 'spaw' + 'n', 'exec' + 'File', 'fetch' + BS + LPAREN].join('|')
|
|
267
|
+
return !new RegExp(pat).test(code)
|
|
268
|
+
}
|