arcane-os 0.3.6 → 0.4.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.
- package/CHANGELOG.md +23 -0
- package/README.md +7 -7
- package/package.json +1 -1
- package/runtime/arcane/components/chat.html +13 -5
- package/runtime/arcane/components/directory-picker.html +2 -2
- package/runtime/arcane/components/file-drop.html +1 -12
- package/runtime/arcane/components/record-timeline.html +13 -11
- package/runtime/arcane/components/relationship-board.html +6 -6
- package/runtime/arcane/components/source-code-viewer.html +23 -27
- package/runtime/arcane/modules/ComponentContracts.js +2 -4
- package/runtime/arcane/modules/DevelopmentWorkspace.js +6 -10
- package/runtime/arcane/modules/MessageAdvisory.js +16 -14
- package/runtime/arcane/modules/RecordPassageIndex.js +12 -26
- package/runtime/arcane/modules/RecordReviewStore.js +16 -3
- package/runtime/arcane/modules/ScamRiskPolicy.js +8 -7
- package/src/import-map.mjs +1 -7
- package/runtime/arcane/entities/TWiNPolicyDecision.js +0 -995
- package/runtime/arcane/modules/CaseEvidenceIndexer.js +0 -130
- package/runtime/arcane/modules/RevocableProjectionLedger.js +0 -1623
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
- Preserved complete finite provider progress values and units as
|
|
6
|
+
informational state, including fractional and out-of-range observations,
|
|
7
|
+
while keeping the visual percentage bounded and exposing ARIA range values
|
|
8
|
+
only when the reported range is valid.
|
|
9
|
+
- Preserved complete file selections, timelines, relationship graphs, source
|
|
10
|
+
content, message advisories, record passages, and date findings without
|
|
11
|
+
arbitrary count or character clipping, and surfaced malformed stored-review
|
|
12
|
+
data instead of silently replacing it.
|
|
13
|
+
- Made workspace and scam-policy hardening explicitly opt in, with ordinary
|
|
14
|
+
complete workspace inputs and `secure:false` scam analysis remaining fully
|
|
15
|
+
functional without restrictive policy admission or frozen results.
|
|
16
|
+
- Removed the product-owned `CaseEvidenceIndexer.js` module and
|
|
17
|
+
`TWiNPolicyDecision.js` entity from the shared runtime payload and generated
|
|
18
|
+
browser import map so application policy remains owned by each consumer.
|
|
19
|
+
|
|
20
|
+
- Removed the unused `RevocableProjectionLedger.js` runtime artifact and its
|
|
21
|
+
public contract. No authored SDK or Arcane OS application source consumes it,
|
|
22
|
+
and its mandatory capacity, character, UTF-8 byte, and node budgets,
|
|
23
|
+
fingerprinting, freezing, and pristine-descriptor admission conflict with the
|
|
24
|
+
ordinary functional baseline.
|
|
25
|
+
|
|
3
26
|
## 0.3.6
|
|
4
27
|
|
|
5
28
|
- Replaced coherent selected-but-unregistered STT and TTS placeholders during
|
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ version-locked SDK runtime, while an integrated Arcane checkout uses its live
|
|
|
19
19
|
`arcane/` runtime. Both profiles preserve the same app URLs, theme, packaging,
|
|
20
20
|
event, cancellation, and browser run contracts.
|
|
21
21
|
|
|
22
|
-
This checkout defines the `0.
|
|
22
|
+
This checkout defines the `0.4.0` SDK contract. Applications pin one exact npm
|
|
23
23
|
version and lockfile; registry state is deliberately not baked into application
|
|
24
24
|
artifacts.
|
|
25
25
|
|
|
@@ -126,7 +126,7 @@ uses the same controller for automatic memory extraction.
|
|
|
126
126
|
Create a new repository-shaped Arcane application with the exact stable SDK:
|
|
127
127
|
|
|
128
128
|
```bash
|
|
129
|
-
npx arcane-os@0.
|
|
129
|
+
npx arcane-os@0.4.0 new my-app --path ./my-app --target portable --git
|
|
130
130
|
cd my-app
|
|
131
131
|
npm install
|
|
132
132
|
npm run check
|
|
@@ -137,7 +137,7 @@ To enroll an existing repository, install the exact SDK and initialize only
|
|
|
137
137
|
missing Arcane files:
|
|
138
138
|
|
|
139
139
|
```bash
|
|
140
|
-
npm install --save-dev --save-exact arcane-os@0.
|
|
140
|
+
npm install --save-dev --save-exact arcane-os@0.4.0
|
|
141
141
|
npm exec -- arcane init my-app --target portable
|
|
142
142
|
```
|
|
143
143
|
|
|
@@ -153,7 +153,7 @@ npm exec -- arcane-os targets
|
|
|
153
153
|
No global SDK install or standalone Arcane CLI is required. The application
|
|
154
154
|
repository's exact npm dependency and lockfile own the CLI and toolchain version.
|
|
155
155
|
|
|
156
|
-
Use `npx arcane-os@0.
|
|
156
|
+
Use `npx arcane-os@0.4.0` for the initial bootstrap because it names this npm
|
|
157
157
|
package explicitly; bare `npx arcane` outside an installed project could resolve
|
|
158
158
|
a different package. Both installed commands invoke the same headless toolchain.
|
|
159
159
|
Project-local npm scripts use the SDK pinned by that app's `package-lock.json`,
|
|
@@ -174,7 +174,7 @@ node ./bin/arcane.mjs new local-app --path ../local-app --target portable --git
|
|
|
174
174
|
|
|
175
175
|
# From the generated app repository
|
|
176
176
|
cd ../local-app
|
|
177
|
-
npm install --save-dev --save-exact ../arcane-os-sdk/arcane-os-0.
|
|
177
|
+
npm install --save-dev --save-exact ../arcane-os-sdk/arcane-os-0.4.0.tgz
|
|
178
178
|
npm run check
|
|
179
179
|
npm ci
|
|
180
180
|
```
|
|
@@ -184,7 +184,7 @@ same location. The lockfile retains the selected package dependency while
|
|
|
184
184
|
Arcane uses the installed package name and version. Local directory `file:` dependencies are not
|
|
185
185
|
accepted because npm may install them as links; use a packed `.tgz`. A GitHub
|
|
186
186
|
runner also needs that tarball at the locked path. After publication, replace
|
|
187
|
-
the local declaration with the exact `arcane-os@0.
|
|
187
|
+
the local declaration with the exact `arcane-os@0.4.0` registry package and
|
|
188
188
|
commit the regenerated lock.
|
|
189
189
|
|
|
190
190
|
Generated repositories use `npm ci --ignore-scripts` in CI. Run dependency
|
|
@@ -322,7 +322,7 @@ package installation, or assertions.
|
|
|
322
322
|
|
|
323
323
|
## Current target support
|
|
324
324
|
|
|
325
|
-
Version `0.
|
|
325
|
+
Version `0.4.0` exposes one browser target and five explicitly paired
|
|
326
326
|
native development targets: a non-runnable portable directory, a
|
|
327
327
|
Windows x64 unsigned-local-test EXE bundle, Linux x64 and Linux ARM64
|
|
328
328
|
unsigned-local-test DEBs, and an Android development-signed APK. The
|
package/package.json
CHANGED
|
@@ -1877,13 +1877,13 @@
|
|
|
1877
1877
|
){
|
|
1878
1878
|
const messageId=String(id);
|
|
1879
1879
|
const normalizedTotal=Number.isFinite(Number(total))
|
|
1880
|
-
?
|
|
1880
|
+
?Number(total)
|
|
1881
1881
|
:0;
|
|
1882
1882
|
const normalizedCurrent=Number.isFinite(Number(current))
|
|
1883
|
-
?
|
|
1883
|
+
?Number(current)
|
|
1884
1884
|
:0;
|
|
1885
1885
|
const normalizedFailed=Number.isFinite(Number(failed))
|
|
1886
|
-
?
|
|
1886
|
+
?Number(failed)
|
|
1887
1887
|
:0;
|
|
1888
1888
|
const message=messageId
|
|
1889
1889
|
?[...chatOutput.children].find(
|
|
@@ -1936,7 +1936,7 @@
|
|
|
1936
1936
|
?`${visibleStatus}${warningLabel}`
|
|
1937
1937
|
:`${normalizedCurrent} of ${normalizedTotal}${warningLabel}`;
|
|
1938
1938
|
const percent=normalizedTotal>0
|
|
1939
|
-
?normalizedCurrent/normalizedTotal*100
|
|
1939
|
+
?Math.min(100,Math.max(0,normalizedCurrent/normalizedTotal*100))
|
|
1940
1940
|
:0;
|
|
1941
1941
|
|
|
1942
1942
|
labelElement.textContent=visibleLabel;
|
|
@@ -1949,10 +1949,18 @@
|
|
|
1949
1949
|
progress.removeAttribute('aria-valuemin');
|
|
1950
1950
|
progress.removeAttribute('aria-valuemax');
|
|
1951
1951
|
progress.removeAttribute('aria-valuenow');
|
|
1952
|
-
}else
|
|
1952
|
+
}else if(
|
|
1953
|
+
normalizedTotal>0
|
|
1954
|
+
&&normalizedCurrent>=0
|
|
1955
|
+
&&normalizedCurrent<=normalizedTotal
|
|
1956
|
+
){
|
|
1953
1957
|
progress.setAttribute('aria-valuemin','0');
|
|
1954
1958
|
progress.setAttribute('aria-valuemax',String(normalizedTotal));
|
|
1955
1959
|
progress.setAttribute('aria-valuenow',String(normalizedCurrent));
|
|
1960
|
+
}else{
|
|
1961
|
+
progress.removeAttribute('aria-valuemin');
|
|
1962
|
+
progress.removeAttribute('aria-valuemax');
|
|
1963
|
+
progress.removeAttribute('aria-valuenow');
|
|
1956
1964
|
}
|
|
1957
1965
|
progress.setAttribute(
|
|
1958
1966
|
'aria-valuetext',
|
|
@@ -166,8 +166,8 @@
|
|
|
166
166
|
|
|
167
167
|
function normalizeValue(value=''){
|
|
168
168
|
const next=`${value??''}`.trim();
|
|
169
|
-
if(
|
|
170
|
-
throw new TypeError('The directory path must be
|
|
169
|
+
if(/[\u0000-\u001f\u007f]/.test(next)){
|
|
170
|
+
throw new TypeError('The directory path must be plain text.');
|
|
171
171
|
}
|
|
172
172
|
return next;
|
|
173
173
|
}
|
|
@@ -188,18 +188,7 @@
|
|
|
188
188
|
if(!files.length){
|
|
189
189
|
return false;
|
|
190
190
|
}
|
|
191
|
-
|
|
192
|
-
setError(
|
|
193
|
-
`Select no more than ${options.maxFiles} file${options.maxFiles===1?'':'s'}.`,
|
|
194
|
-
{
|
|
195
|
-
code:'ARCANE_FILE_DROP_FILE_COUNT_LIMIT_EXCEEDED',
|
|
196
|
-
reason:'file-count-limit-exceeded'
|
|
197
|
-
},
|
|
198
|
-
operationId
|
|
199
|
-
);
|
|
200
|
-
return false;
|
|
201
|
-
}
|
|
202
|
-
currentFiles=options.multiple?files:files.slice(0,1);
|
|
191
|
+
currentFiles=files;
|
|
203
192
|
setError('');
|
|
204
193
|
feedback.hidden=false;
|
|
205
194
|
statusElement.className='status';
|
|
@@ -50,27 +50,29 @@
|
|
|
50
50
|
|
|
51
51
|
function normalizeItem(item,index){
|
|
52
52
|
const source=item&&typeof item==='object'?item:{};
|
|
53
|
-
const date=String(source.date||source.isoDate||'')
|
|
53
|
+
const date=String(source.date||source.isoDate||'');
|
|
54
54
|
if(!source.id||!date) return null;
|
|
55
55
|
return {
|
|
56
56
|
...source,
|
|
57
|
-
id:String(source.id)
|
|
57
|
+
id:String(source.id),
|
|
58
58
|
date,
|
|
59
|
-
title:String(source.title||'Dated event')
|
|
60
|
-
summary:String(source.summary||'')
|
|
61
|
-
category:String(source.category||source.kind||'Event')
|
|
62
|
-
actor:String(source.actor||'Actor not assigned')
|
|
63
|
-
sourceLabel:String(source.sourceLabel||'')
|
|
64
|
-
status:String(source.status||'')
|
|
65
|
-
factualPosture:String(source.factualPosture||'')
|
|
66
|
-
sourceCount:
|
|
59
|
+
title:String(source.title||'Dated event'),
|
|
60
|
+
summary:String(source.summary||''),
|
|
61
|
+
category:String(source.category||source.kind||'Event'),
|
|
62
|
+
actor:String(source.actor||'Actor not assigned'),
|
|
63
|
+
sourceLabel:String(source.sourceLabel||''),
|
|
64
|
+
status:String(source.status||''),
|
|
65
|
+
factualPosture:String(source.factualPosture||''),
|
|
66
|
+
sourceCount:Number.isFinite(Number(source.sourceCount))&&Number(source.sourceCount)>=0
|
|
67
|
+
?Number(source.sourceCount)
|
|
68
|
+
:0,
|
|
67
69
|
order:index
|
|
68
70
|
};
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
function setItems(value=[],input={}){
|
|
72
74
|
options={...options,...(input&&typeof input==='object'?input:{})};
|
|
73
|
-
items=(Array.isArray(value)?value:[]).
|
|
75
|
+
items=(Array.isArray(value)?value:[]).map(normalizeItem).filter(Boolean)
|
|
74
76
|
.sort((left,right)=>left.date.localeCompare(right.date)||left.order-right.order);
|
|
75
77
|
render();
|
|
76
78
|
return items.map(item=>({...item}));
|
|
@@ -66,21 +66,21 @@
|
|
|
66
66
|
function normalizeNode(value,index){
|
|
67
67
|
const item=value&&typeof value==='object'?value:{};
|
|
68
68
|
if(!item.id) return null;
|
|
69
|
-
return {...item,id:String(item.id)
|
|
69
|
+
return {...item,id:String(item.id),label:String(item.label||item.title||item.id),summary:String(item.summary||''),lane:String(item.lane||item.kind||'Other'),order:Number.isFinite(Number(item.order))?Number(item.order):index};
|
|
70
70
|
}
|
|
71
71
|
function normalizeEdge(value,index,known){
|
|
72
72
|
const item=value&&typeof value==='object'?value:{};
|
|
73
|
-
const from=String(item.from||'')
|
|
73
|
+
const from=String(item.from||''); const to=String(item.to||'');
|
|
74
74
|
if(!from||!to||!known.has(from)||!known.has(to)) return null;
|
|
75
|
-
return {...item,id:String(item.id||`edge-${index+1}`)
|
|
75
|
+
return {...item,id:String(item.id||`edge-${index+1}`),from,to,label:String(item.label||'related to'),summary:String(item.summary||'')};
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
function setGraph(graph={},options={}){
|
|
79
|
-
nodes=(Array.isArray(graph.nodes)?graph.nodes:[]).
|
|
79
|
+
nodes=(Array.isArray(graph.nodes)?graph.nodes:[]).map(normalizeNode).filter(Boolean);
|
|
80
80
|
const known=new Set(nodes.map(item=>item.id));
|
|
81
|
-
edges=(Array.isArray(graph.edges)?graph.edges:[]).
|
|
81
|
+
edges=(Array.isArray(graph.edges)?graph.edges:[]).map((item,index)=>normalizeEdge(item,index,known)).filter(Boolean);
|
|
82
82
|
const supplied=Array.isArray(options.lanes)?options.lanes:Array.isArray(graph.lanes)?graph.lanes:[];
|
|
83
|
-
lanes=[...new Set([...supplied.map(String),...nodes.map(item=>item.lane)])]
|
|
83
|
+
lanes=[...new Set([...supplied.map(String),...nodes.map(item=>item.lane)])];
|
|
84
84
|
board.style.setProperty('--lane-count',String(Math.max(1,Math.min(6,lanes.length))));
|
|
85
85
|
if(selectedId&&!known.has(selectedId)) selectedId='';
|
|
86
86
|
render();
|
|
@@ -202,26 +202,24 @@
|
|
|
202
202
|
const sourceScroll=host.shadowRoot.querySelector('#sourceScroll');
|
|
203
203
|
const sourceLines=host.shadowRoot.querySelector('#sourceLines');
|
|
204
204
|
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
const defaultOptions=Object.freeze({
|
|
208
|
-
labels:Object.freeze({
|
|
205
|
+
const defaultOptions={
|
|
206
|
+
labels:{
|
|
209
207
|
content:'Source code',
|
|
210
208
|
empty:'Choose a source file from the catalog.',
|
|
211
209
|
error:'This source file could not be displayed.',
|
|
212
|
-
loading:'Loading
|
|
210
|
+
loading:'Loading source code…',
|
|
213
211
|
repository:'View repository source'
|
|
214
|
-
}
|
|
215
|
-
}
|
|
212
|
+
}
|
|
213
|
+
};
|
|
216
214
|
|
|
217
|
-
let current=
|
|
215
|
+
let current={
|
|
218
216
|
language:'text',
|
|
219
217
|
lineCount:0,
|
|
220
218
|
repositoryURL:'',
|
|
221
219
|
sourcePath:'',
|
|
222
220
|
text:'',
|
|
223
221
|
title:''
|
|
224
|
-
}
|
|
222
|
+
};
|
|
225
223
|
let destroyed=false;
|
|
226
224
|
let eventOperationSequence=0;
|
|
227
225
|
let loadSequence=0;
|
|
@@ -244,17 +242,17 @@
|
|
|
244
242
|
value:{get:()=>current.text,set:value=>render(value)}
|
|
245
243
|
});
|
|
246
244
|
|
|
247
|
-
function
|
|
245
|
+
function normalizedText(value,label,{optional=false}={}){
|
|
248
246
|
if(optional&&(value===undefined||value===null||value===''))return '';
|
|
249
|
-
if(typeof value!=='string'||!value.trim()
|
|
250
|
-
throw new TypeError(`${label} must be
|
|
247
|
+
if(typeof value!=='string'||!value.trim()||/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/.test(value)){
|
|
248
|
+
throw new TypeError(`${label} must be text.`);
|
|
251
249
|
}
|
|
252
250
|
return value.trim();
|
|
253
251
|
}
|
|
254
252
|
|
|
255
253
|
function safeRepositoryURL(value=''){
|
|
256
254
|
if(value===undefined||value===null||value==='')return '';
|
|
257
|
-
const input=
|
|
255
|
+
const input=normalizedText(value,'Source repositoryURL');
|
|
258
256
|
try{
|
|
259
257
|
const url=new URL(input,document.baseURI);
|
|
260
258
|
if(!['http:','https:'].includes(url.protocol)||url.username||url.password){
|
|
@@ -278,22 +276,22 @@
|
|
|
278
276
|
const labels={...previous.labels};
|
|
279
277
|
for(const name of ['content','empty','error','loading','repository']){
|
|
280
278
|
if(labelInput[name]!==undefined){
|
|
281
|
-
labels[name]=
|
|
279
|
+
labels[name]=normalizedText(labelInput[name],`Source viewer labels.${name}`);
|
|
282
280
|
}
|
|
283
281
|
}
|
|
284
|
-
return
|
|
282
|
+
return {labels};
|
|
285
283
|
}
|
|
286
284
|
|
|
287
285
|
function normalizeRenderOptions(input={}){
|
|
288
286
|
if(!input||typeof input!=='object'||Array.isArray(input)){
|
|
289
287
|
throw new TypeError('Source render options must be an object.');
|
|
290
288
|
}
|
|
291
|
-
return
|
|
292
|
-
language:
|
|
289
|
+
return {
|
|
290
|
+
language:normalizedText(input.language||'text','Source language').toLowerCase(),
|
|
293
291
|
repositoryURL:safeRepositoryURL(input.repositoryURL),
|
|
294
|
-
sourcePath:
|
|
295
|
-
title:
|
|
296
|
-
}
|
|
292
|
+
sourcePath:normalizedText(input.sourcePath||'Source file','Source path'),
|
|
293
|
+
title:normalizedText(input.title||input.sourcePath||'Source file','Source title')
|
|
294
|
+
};
|
|
297
295
|
}
|
|
298
296
|
|
|
299
297
|
function configure(input={}){
|
|
@@ -337,14 +335,12 @@
|
|
|
337
335
|
|
|
338
336
|
function renderSource(text='',renderOptions={}){
|
|
339
337
|
if(typeof text!=='string')throw new TypeError('Source content must be a string.');
|
|
340
|
-
if(text.length>MAXIMUM_CHARACTERS)throw new RangeError('Source content exceeds the viewer character limit.');
|
|
341
338
|
const metadata=normalizeRenderOptions(renderOptions);
|
|
342
339
|
const lines=text.replace(/\r\n?/g,'\n').split('\n');
|
|
343
|
-
if(lines.length>MAXIMUM_LINES)throw new RangeError('Source content exceeds the viewer line limit.');
|
|
344
340
|
|
|
345
341
|
sourceLines.replaceChildren();
|
|
346
342
|
if(!text){
|
|
347
|
-
current=
|
|
343
|
+
current={...metadata,lineCount:0,text:''};
|
|
348
344
|
transition('empty');
|
|
349
345
|
return true;
|
|
350
346
|
}
|
|
@@ -363,7 +359,7 @@
|
|
|
363
359
|
fragment.append(item);
|
|
364
360
|
});
|
|
365
361
|
sourceLines.replaceChildren(fragment);
|
|
366
|
-
current=
|
|
362
|
+
current={...metadata,lineCount:lines.length,text};
|
|
367
363
|
transition('ready');
|
|
368
364
|
return true;
|
|
369
365
|
}
|
|
@@ -371,14 +367,14 @@
|
|
|
371
367
|
function clear(){
|
|
372
368
|
++loadSequence;
|
|
373
369
|
sourceLines.replaceChildren();
|
|
374
|
-
current=
|
|
370
|
+
current={
|
|
375
371
|
language:'text',
|
|
376
372
|
lineCount:0,
|
|
377
373
|
repositoryURL:'',
|
|
378
374
|
sourcePath:'',
|
|
379
375
|
text:'',
|
|
380
376
|
title:''
|
|
381
|
-
}
|
|
377
|
+
};
|
|
382
378
|
transition('empty');
|
|
383
379
|
return true;
|
|
384
380
|
}
|
|
@@ -423,7 +419,7 @@
|
|
|
423
419
|
sourcePath:current.sourcePath,
|
|
424
420
|
state
|
|
425
421
|
};
|
|
426
|
-
if(error)detail.message=String(error?.message||'Source rendering failed.')
|
|
422
|
+
if(error)detail.message=String(error?.message||'Source rendering failed.');
|
|
427
423
|
const reason={
|
|
428
424
|
empty:'source-code-viewer-cleared',
|
|
429
425
|
error:'source-code-viewer-render-rejected',
|
|
@@ -412,11 +412,9 @@ function formatAIRuntimeProgress(progress,fallback){
|
|
|
412
412
|
const unit=typeof progress?.unit==='string'
|
|
413
413
|
?progress.unit
|
|
414
414
|
:'';
|
|
415
|
-
if(!Number.
|
|
416
|
-
|
|
417
|
-
||!Number.isSafeInteger(total)
|
|
415
|
+
if(!Number.isFinite(completed)
|
|
416
|
+
||!Number.isFinite(total)
|
|
418
417
|
||total<=0
|
|
419
|
-
||completed>total
|
|
420
418
|
||!unit){
|
|
421
419
|
return phase;
|
|
422
420
|
}
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
const ROOT_MAX_LENGTH=4096;
|
|
2
|
-
const QUERY_MAX_LENGTH=4096;
|
|
3
|
-
const TASK_ID_PATTERN=/^[a-z][a-z0-9-]{0,63}$/;
|
|
4
|
-
|
|
5
1
|
function workspaceRoot(value){
|
|
6
2
|
const root=String(value??'').trim();
|
|
7
|
-
if(!root
|
|
3
|
+
if(!root||/[\u0000-\u001f]/.test(root)){
|
|
8
4
|
throw new TypeError('Choose one existing development workspace directory.');
|
|
9
5
|
}
|
|
10
6
|
return root;
|
|
@@ -12,16 +8,16 @@ function workspaceRoot(value){
|
|
|
12
8
|
|
|
13
9
|
function contextQuery(value){
|
|
14
10
|
const query=String(value??'').trim();
|
|
15
|
-
if(
|
|
16
|
-
throw new TypeError('Development context queries must be
|
|
11
|
+
if(/[\u0000-\u0008\u000b\u000c\u000e-\u001f]/.test(query)){
|
|
12
|
+
throw new TypeError('Development context queries must be plain text.');
|
|
17
13
|
}
|
|
18
14
|
return query;
|
|
19
15
|
}
|
|
20
16
|
|
|
21
17
|
function setupTaskId(value){
|
|
22
|
-
const taskId=String(value??'').trim()
|
|
23
|
-
if(!
|
|
24
|
-
throw new TypeError('Choose a
|
|
18
|
+
const taskId=String(value??'').trim();
|
|
19
|
+
if(!taskId||/[\u0000-\u001f]/.test(taskId)){
|
|
20
|
+
throw new TypeError('Choose a development setup task.');
|
|
25
21
|
}
|
|
26
22
|
return taskId;
|
|
27
23
|
}
|
|
@@ -1,25 +1,27 @@
|
|
|
1
|
-
function
|
|
1
|
+
function completeText(value){return String(value??'');}
|
|
2
2
|
|
|
3
3
|
export function normalizeContentAdvisory(value){
|
|
4
4
|
if(!value||typeof value!=='object')return null;
|
|
5
|
-
return
|
|
5
|
+
return {
|
|
6
6
|
level:['critical','high','caution','low','unavailable'].includes(value.level)?value.level:'caution',
|
|
7
|
-
title:
|
|
8
|
-
summary:
|
|
9
|
-
signals:
|
|
10
|
-
actionLabel:
|
|
11
|
-
}
|
|
7
|
+
title:completeText(value.title??'Content advisory'),
|
|
8
|
+
summary:completeText(value.summary??'Review this message carefully.'),
|
|
9
|
+
signals:Array.from(value.signals||[],completeText),
|
|
10
|
+
actionLabel:completeText(value.actionLabel),
|
|
11
|
+
};
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
function unavailableAdvisory(){
|
|
15
|
+
return normalizeContentAdvisory({
|
|
16
|
+
level:'unavailable',
|
|
17
|
+
title:'Safety check unavailable',
|
|
18
|
+
summary:'No safety conclusion was made for this message. Pause and review it manually before replying.',
|
|
19
|
+
});
|
|
20
|
+
}
|
|
19
21
|
|
|
20
22
|
export function unavailableMessageInspection(messages){
|
|
21
23
|
const advisories=new Map();
|
|
22
|
-
for(const message of Array.from(messages||[]))advisories.set(message,unavailableAdvisory);
|
|
24
|
+
for(const message of Array.from(messages||[]))advisories.set(message,unavailableAdvisory());
|
|
23
25
|
return {advisories,failures:advisories.size};
|
|
24
26
|
}
|
|
25
27
|
|
|
@@ -32,7 +34,7 @@ export async function inspectMessageRecords(messages,inspector,{prepare}={}){
|
|
|
32
34
|
catch{return unavailableMessageInspection(records);}
|
|
33
35
|
for(const message of records){
|
|
34
36
|
try{const advisory=normalizeContentAdvisory(await inspector(message,context));if(advisory){if(advisory.level==='unavailable')failures+=1;advisories.set(message,advisory);}}
|
|
35
|
-
catch{failures+=1;advisories.set(message,unavailableAdvisory);}
|
|
37
|
+
catch{failures+=1;advisories.set(message,unavailableAdvisory());}
|
|
36
38
|
}
|
|
37
39
|
return {advisories,failures};
|
|
38
40
|
}
|
|
@@ -5,9 +5,9 @@ const MONTH_NUMBER={
|
|
|
5
5
|
|
|
6
6
|
const MONTH_TOKEN='January|February|March|April|May|June|July|August|September|October|November|December';
|
|
7
7
|
|
|
8
|
-
function
|
|
8
|
+
function normalizedInteger(value,{minimum=0,fallback=0}={}){
|
|
9
9
|
const number=Number(value);
|
|
10
|
-
return Number.isInteger(number)&&number>=minimum
|
|
10
|
+
return Number.isInteger(number)&&number>=minimum?number:fallback;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
function textLines(value=''){
|
|
@@ -35,16 +35,14 @@ function lineAtOffset(offsets=[],index=0){
|
|
|
35
35
|
return Math.max(0,high);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
function cleanExcerpt(lines=[],start=0,end=start
|
|
39
|
-
|
|
40
|
-
const value=lines.slice(start,end+1)
|
|
38
|
+
function cleanExcerpt(lines=[],start=0,end=start){
|
|
39
|
+
return lines.slice(start,end+1)
|
|
41
40
|
.filter(line=>!/^\s*(?:```|---)\s*$/.test(line))
|
|
42
41
|
.map(line=>line.replace(/^\s{0,4}(?:[-*+]\s+|>\s*)?/,'').trim())
|
|
43
42
|
.filter(Boolean)
|
|
44
43
|
.join(' ')
|
|
45
44
|
.replace(/\s+/g,' ')
|
|
46
45
|
.trim();
|
|
47
|
-
return value.length>maximum?`${value.slice(0,maximum-1).trimEnd()}…`:value;
|
|
48
46
|
}
|
|
49
47
|
|
|
50
48
|
function pageMarkers(lines=[]){
|
|
@@ -73,23 +71,18 @@ function globalPattern(pattern){
|
|
|
73
71
|
}
|
|
74
72
|
|
|
75
73
|
function passageKey(item={}){
|
|
76
|
-
return `${item.ruleId}|${item.page??''}|${item.lineStart}|${item.excerpt.toLocaleLowerCase().replace(/\W+/g,' ')
|
|
74
|
+
return `${item.ruleId}|${item.page??''}|${item.lineStart}|${item.excerpt.toLocaleLowerCase().replace(/\W+/g,' ')}`;
|
|
77
75
|
}
|
|
78
76
|
|
|
79
77
|
function findRulePassages(text='',rules=[],{
|
|
80
78
|
recordId='',
|
|
81
|
-
contextLines=2
|
|
82
|
-
maximumExcerptLength=1200,
|
|
83
|
-
maximumPerRule=40,
|
|
84
|
-
maximumResults=1200
|
|
79
|
+
contextLines=2
|
|
85
80
|
}={}){
|
|
86
81
|
const source=String(text??'').replace(/\r\n?/g,'\n');
|
|
87
82
|
const lines=textLines(source);
|
|
88
83
|
const offsets=lineOffsets(lines);
|
|
89
84
|
const markers=pageMarkers(lines);
|
|
90
|
-
const defaultContext=
|
|
91
|
-
const perRule=boundedInteger(maximumPerRule,{minimum:1,maximum:500,fallback:40});
|
|
92
|
-
const totalLimit=boundedInteger(maximumResults,{minimum:1,maximum:10000,fallback:1200});
|
|
85
|
+
const defaultContext=normalizedInteger(contextLines,{minimum:0,fallback:2});
|
|
93
86
|
const findings=[];
|
|
94
87
|
const seen=new Set();
|
|
95
88
|
|
|
@@ -100,13 +93,12 @@ function findRulePassages(text='',rules=[],{
|
|
|
100
93
|
for(const supplied of patterns.filter(Boolean)){
|
|
101
94
|
const pattern=globalPattern(supplied);
|
|
102
95
|
for(const match of source.matchAll(pattern)){
|
|
103
|
-
if(ruleCount>=perRule||findings.length>=totalLimit) break;
|
|
104
96
|
const matchIndex=match.index??0;
|
|
105
97
|
const line=lineAtOffset(offsets,matchIndex);
|
|
106
|
-
const localContext=
|
|
98
|
+
const localContext=normalizedInteger(definition.contextLines,{minimum:0,fallback:defaultContext});
|
|
107
99
|
const lineStart=Math.max(0,line-localContext);
|
|
108
100
|
const lineEnd=Math.min(lines.length-1,line+localContext);
|
|
109
|
-
const excerpt=cleanExcerpt(lines,lineStart,lineEnd
|
|
101
|
+
const excerpt=cleanExcerpt(lines,lineStart,lineEnd);
|
|
110
102
|
if(!excerpt) continue;
|
|
111
103
|
const candidate={
|
|
112
104
|
id:`${String(recordId||'record')}:${String(definition.id)}:${line+1}:${ruleCount+1}`,
|
|
@@ -129,7 +121,6 @@ function findRulePassages(text='',rules=[],{
|
|
|
129
121
|
ruleCount++;
|
|
130
122
|
}
|
|
131
123
|
}
|
|
132
|
-
if(findings.length>=totalLimit) break;
|
|
133
124
|
}
|
|
134
125
|
return findings;
|
|
135
126
|
}
|
|
@@ -162,16 +153,13 @@ function parseDateMention(value=''){
|
|
|
162
153
|
|
|
163
154
|
function extractDateMentions(text='',{
|
|
164
155
|
recordId='',
|
|
165
|
-
contextLines=1
|
|
166
|
-
maximumExcerptLength=900,
|
|
167
|
-
maximumResults=2000
|
|
156
|
+
contextLines=1
|
|
168
157
|
}={}){
|
|
169
158
|
const source=String(text??'').replace(/\r\n?/g,'\n');
|
|
170
159
|
const lines=textLines(source);
|
|
171
160
|
const offsets=lineOffsets(lines);
|
|
172
161
|
const markers=pageMarkers(lines);
|
|
173
|
-
const context=
|
|
174
|
-
const limit=boundedInteger(maximumResults,{minimum:1,maximum:20000,fallback:2000});
|
|
162
|
+
const context=normalizedInteger(contextLines,{minimum:0,fallback:1});
|
|
175
163
|
const patterns=[
|
|
176
164
|
new RegExp(`\\b(?:${MONTH_TOKEN})\\s+\\d{1,2}(?:st|nd|rd|th)?,?\\s+\\d{4}\\b`,'gi'),
|
|
177
165
|
/\b\d{4}-\d{1,2}-\d{1,2}\b/g,
|
|
@@ -183,7 +171,6 @@ function extractDateMentions(text='',{
|
|
|
183
171
|
const results=[];
|
|
184
172
|
for(const pattern of patterns){
|
|
185
173
|
for(const match of source.matchAll(pattern)){
|
|
186
|
-
if(results.length>=limit) break;
|
|
187
174
|
const start=match.index??0; const end=start+match[0].length;
|
|
188
175
|
if(occupied.some(range=>start<range.end&&end>range.start)) continue;
|
|
189
176
|
const parsed=parseDateMention(match[0]);
|
|
@@ -202,10 +189,9 @@ function extractDateMentions(text='',{
|
|
|
202
189
|
lineStart:lineStart+1,
|
|
203
190
|
lineEnd:lineEnd+1,
|
|
204
191
|
page:pageAtLine(markers,line),
|
|
205
|
-
excerpt:cleanExcerpt(lines,lineStart,lineEnd
|
|
192
|
+
excerpt:cleanExcerpt(lines,lineStart,lineEnd)
|
|
206
193
|
});
|
|
207
194
|
}
|
|
208
|
-
if(results.length>=limit) break;
|
|
209
195
|
}
|
|
210
196
|
return results.sort((left,right)=>left.isoDate.localeCompare(right.isoDate)||left.lineStart-right.lineStart);
|
|
211
197
|
}
|
|
@@ -148,7 +148,14 @@ function recordMap(records){
|
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
function normalizedStoredRecords(value){
|
|
151
|
-
if(!isPlainRecord(value))
|
|
151
|
+
if(!isPlainRecord(value)){
|
|
152
|
+
throw recordReviewStoreError(
|
|
153
|
+
RECORD_REVIEW_STORE_ERROR_CODES.storedRecordsInvalid,
|
|
154
|
+
'record-review-stored-records-invalid',
|
|
155
|
+
'Stored record reviews must be a plain object.',
|
|
156
|
+
TypeError
|
|
157
|
+
);
|
|
158
|
+
}
|
|
152
159
|
const records={};
|
|
153
160
|
for(const [recordId,review] of Object.entries(value)){
|
|
154
161
|
let id;
|
|
@@ -196,8 +203,14 @@ function localAdapter(namespace){
|
|
|
196
203
|
if(!raw) return {};
|
|
197
204
|
try{
|
|
198
205
|
return JSON.parse(raw);
|
|
199
|
-
}catch{
|
|
200
|
-
|
|
206
|
+
}catch(error){
|
|
207
|
+
throw recordReviewStoreError(
|
|
208
|
+
RECORD_REVIEW_STORE_ERROR_CODES.storedRecordsInvalid,
|
|
209
|
+
'record-review-stored-json-invalid',
|
|
210
|
+
'Stored record reviews contain invalid JSON.',
|
|
211
|
+
TypeError,
|
|
212
|
+
error
|
|
213
|
+
);
|
|
201
214
|
}
|
|
202
215
|
},
|
|
203
216
|
async set(value){
|