hive-lite 0.1.9 → 0.2.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/README.md +3 -1
- package/docs/cli-semantics.md +53 -1
- package/docs/skills/hive-lite-bootstrap/SKILL.md +28 -0
- package/docs/skills/hive-lite-discovery/SKILL.md +151 -0
- package/docs/skills/hive-lite-discovery/agents/openai.yaml +4 -0
- package/docs/skills/hive-lite-finish/SKILL.md +62 -5
- package/docs/skills/hive-lite-map-maintainer/SKILL.md +28 -0
- package/docs/skills/hive-lite-map-maintainer/references/lifecycle.md +1 -1
- package/docs/skills/hive-lite-map-maintainer/references/repair-rules.md +7 -0
- package/docs/skills/hive-lite-start-prompt/SKILL.md +175 -52
- package/docs/skills/hive-lite-start-prompt/references/preflight.md +70 -20
- package/package.json +3 -3
- package/src/cli.js +146 -3
- package/src/lib/change.js +93 -0
- package/src/lib/context.js +304 -31
- package/src/lib/git.js +1 -0
- package/src/lib/health.js +208 -0
- package/src/lib/intent.js +620 -0
- package/src/lib/map.js +57 -0
- package/src/lib/operator.js +1067 -0
- package/src/lib/skills.js +1 -0
package/src/cli.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
1
2
|
const path = require('path');
|
|
2
3
|
const { version: packageVersion } = require('../package.json');
|
|
3
4
|
const { requireGitRepo } = require('./lib/git');
|
|
4
5
|
const { evaluateMapHealth } = require('./lib/health');
|
|
6
|
+
const { readIntentTarget, saveStartIntent, validateIntentTarget } = require('./lib/intent');
|
|
5
7
|
const { createMapPrompt, initProject, loadProjectMap, suggestMap, verifyProjectMap } = require('./lib/map');
|
|
6
8
|
const { createContextPacket } = require('./lib/context');
|
|
7
9
|
const { evaluateNextAction } = require('./lib/next');
|
|
10
|
+
const { runOperator } = require('./lib/operator');
|
|
8
11
|
const { installSkills, skillsDoctor, syncSkills } = require('./lib/skills');
|
|
9
12
|
const { patternDisplay } = require('./lib/scope');
|
|
10
13
|
const { evaluateWorkspaceStatus } = require('./lib/status');
|
|
@@ -73,6 +76,12 @@ Usage:
|
|
|
73
76
|
hive-lite skills doctor [--agent codex|claude|gemini|all] [--path <skills-dir>] [--json]
|
|
74
77
|
hive-lite skills install --agent codex|claude|gemini|all [--force] [--dry-run] [--json]
|
|
75
78
|
hive-lite skills sync --agent codex|claude|gemini|all [--dry-run] [--json]
|
|
79
|
+
hive-lite intent save --from <file> --json
|
|
80
|
+
hive-lite intent save --stdin --json
|
|
81
|
+
hive-lite intent validate <intent_id_or_path> --json
|
|
82
|
+
hive-lite intent show <intent_id_or_path> --json
|
|
83
|
+
hive-lite operator run "<clear requirement>" [--operator-agent-command "..."] [--operator-agent-mode stdin|prompt_file]
|
|
84
|
+
hive-lite operator run --intent <intent_id_or_path> [--operator-agent-command "..."] [--operator-agent-mode stdin|prompt_file]
|
|
76
85
|
hive-lite find "<intent>" [--area <id>] [--max-files 8] [--explain] [--json]
|
|
77
86
|
hive-lite check [changeId] [--context ctx_id] [--json]
|
|
78
87
|
hive-lite validate [changeId] [--cmd "..."] [--profile <id>]
|
|
@@ -223,10 +232,12 @@ function printContext(result) {
|
|
|
223
232
|
console.log('');
|
|
224
233
|
console.log('Candidate Phase Seeds:');
|
|
225
234
|
for (const item of packet.candidatePhaseSeeds) {
|
|
235
|
+
const fromSplit = packet.splitNote && packet.splitNote.id ? ` --from-split ${packet.splitNote.id}` : '';
|
|
226
236
|
console.log(` - ${item.id} (${item.areaId})`);
|
|
227
|
-
console.log(
|
|
228
|
-
|
|
229
|
-
if (item.
|
|
237
|
+
console.log(' copyable find command:');
|
|
238
|
+
console.log(` hive-lite find ${JSON.stringify(item.findIntent)}${fromSplit} --phase ${item.id} --area ${item.areaId}`);
|
|
239
|
+
if (item.evidenceClasses && item.evidenceClasses.length > 0) console.log(` expected evidence: ${item.evidenceClasses.join(', ')}`);
|
|
240
|
+
if (item.validationProfiles && item.validationProfiles.length > 0) console.log(` expected validation: ${item.validationProfiles.join(', ')}`);
|
|
230
241
|
console.log(` risk: ${item.risk}`);
|
|
231
242
|
}
|
|
232
243
|
}
|
|
@@ -240,6 +251,15 @@ function printContext(result) {
|
|
|
240
251
|
console.log(' blocking:');
|
|
241
252
|
for (const item of packet.writePlan.blockingWarnings) console.log(` - ${item.code}: ${item.message}`);
|
|
242
253
|
}
|
|
254
|
+
if (packet.writePlan.selectedWritableCertificates && packet.writePlan.selectedWritableCertificates.length > 0) {
|
|
255
|
+
console.log(' certified direct:');
|
|
256
|
+
for (const item of packet.writePlan.selectedWritableCertificates) {
|
|
257
|
+
const artifact = item.artifact ? ` ${item.artifact}` : '';
|
|
258
|
+
const target = item.targetIdentity ? ` for ${item.targetIdentity}` : '';
|
|
259
|
+
console.log(` - ${item.path}: ${item.operation}${artifact}${target}`);
|
|
260
|
+
if (item.certifiedBy && item.certifiedBy.mapEntry) console.log(` certified by: ${item.certifiedBy.mapEntry}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
243
263
|
}
|
|
244
264
|
if (packet.writePlan && packet.writePlan.referenceFiles && packet.writePlan.referenceFiles.length > 0) {
|
|
245
265
|
console.log('');
|
|
@@ -396,6 +416,18 @@ function printCheck(root, result) {
|
|
|
396
416
|
console.log(' selected direct:');
|
|
397
417
|
for (const item of change.writePlanStatus.selectedWritableDirect) console.log(` - ${item}`);
|
|
398
418
|
}
|
|
419
|
+
if (change.writePlanStatus.selectedWritableCertificates && change.writePlanStatus.selectedWritableCertificates.length > 0) {
|
|
420
|
+
console.log(' certificates:');
|
|
421
|
+
for (const item of change.writePlanStatus.selectedWritableCertificates) {
|
|
422
|
+
const artifact = item.artifact ? ` ${item.artifact}` : '';
|
|
423
|
+
const target = item.targetIdentity ? ` for ${item.targetIdentity}` : '';
|
|
424
|
+
console.log(` - ${item.path}: ${item.operation}${artifact}${target}`);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if (change.writePlanStatus.uncertifiedWrites && change.writePlanStatus.uncertifiedWrites.length > 0) {
|
|
428
|
+
console.log(' uncertified writes:');
|
|
429
|
+
for (const item of change.writePlanStatus.uncertifiedWrites) console.log(` - ${item.path}: ${item.operation}`);
|
|
430
|
+
}
|
|
399
431
|
if (change.writePlanStatus.referenceOnlyTouched && change.writePlanStatus.referenceOnlyTouched.length > 0) {
|
|
400
432
|
console.log(' reference-only touched:');
|
|
401
433
|
for (const item of change.writePlanStatus.referenceOnlyTouched) console.log(` - ${item.path}: ${item.reason || item.reference}`);
|
|
@@ -792,6 +824,56 @@ function printSkillsMutation(result) {
|
|
|
792
824
|
}
|
|
793
825
|
}
|
|
794
826
|
|
|
827
|
+
function printIntentErrors(result) {
|
|
828
|
+
if (result.errors && result.errors.length > 0) {
|
|
829
|
+
console.log('');
|
|
830
|
+
console.log('Errors:');
|
|
831
|
+
for (const error of result.errors) console.log(` - ${error.code}: ${error.message}`);
|
|
832
|
+
}
|
|
833
|
+
if (result.warnings && result.warnings.length > 0) {
|
|
834
|
+
console.log('');
|
|
835
|
+
console.log('Warnings:');
|
|
836
|
+
for (const warning of result.warnings) console.log(` - ${warning.code}: ${warning.message}`);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
function printIntentSave(result) {
|
|
841
|
+
if (!result.saved) {
|
|
842
|
+
console.log('Start Intent Handoff not saved.');
|
|
843
|
+
printIntentErrors(result);
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
console.log('Start Intent Handoff saved.');
|
|
847
|
+
console.log('');
|
|
848
|
+
console.log(`Intent: ${result.intent_id}`);
|
|
849
|
+
console.log(`Path: ${result.paths.markdown}`);
|
|
850
|
+
console.log(`JSON: ${result.paths.json}`);
|
|
851
|
+
if (result.warnings && result.warnings.length > 0) printIntentErrors({ errors: [], warnings: result.warnings });
|
|
852
|
+
console.log('');
|
|
853
|
+
console.log('Next (run from this repo root in your terminal):');
|
|
854
|
+
console.log(` hive-lite operator run --intent ${result.intent_id}`);
|
|
855
|
+
console.log('');
|
|
856
|
+
console.log('Equivalent path form:');
|
|
857
|
+
console.log(` hive-lite operator run --intent ${result.paths.markdown}`);
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
function printIntentValidate(result) {
|
|
861
|
+
console.log(`Start Intent Handoff: ${result.intent_id || '(unknown)'}`);
|
|
862
|
+
console.log(`Status: ${result.valid ? 'valid' : 'invalid'}`);
|
|
863
|
+
console.log(`Path: ${result.paths.markdown}`);
|
|
864
|
+
printIntentErrors(result);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
function printIntentShow(result) {
|
|
868
|
+
console.log(`Start Intent Handoff: ${result.intent_id || '(unknown)'}`);
|
|
869
|
+
console.log(`Status: ${result.status || '(unknown)'}`);
|
|
870
|
+
console.log(`Path: ${result.paths.markdown}`);
|
|
871
|
+
console.log('');
|
|
872
|
+
console.log('Find Intent:');
|
|
873
|
+
console.log(result.findIntent || '(none)');
|
|
874
|
+
printIntentErrors(result.validation || {});
|
|
875
|
+
}
|
|
876
|
+
|
|
795
877
|
async function main(argv) {
|
|
796
878
|
const [command, subcommand, subsubcommand, ...rest] = argv;
|
|
797
879
|
if (!command || isHelpArg(command)) {
|
|
@@ -862,6 +944,67 @@ async function main(argv) {
|
|
|
862
944
|
throw new Error('skills requires a subcommand: doctor, install, or sync');
|
|
863
945
|
}
|
|
864
946
|
|
|
947
|
+
if (command === 'intent') {
|
|
948
|
+
const root = requireGitRepo(process.cwd(), 'hive-lite intent');
|
|
949
|
+
const parsed = parseArgs([subsubcommand, ...rest].filter((item) => item !== undefined));
|
|
950
|
+
|
|
951
|
+
if (subcommand === 'save') {
|
|
952
|
+
if (parsed.flags.from && parsed.flags.stdin) throw new Error('intent save accepts either --from <file> or --stdin, not both');
|
|
953
|
+
if (!parsed.flags.from && !parsed.flags.stdin) throw new Error('intent save requires --from <file> or --stdin');
|
|
954
|
+
const source = parsed.flags.stdin ? 'stdin' : parsed.flags.from;
|
|
955
|
+
const markdown = parsed.flags.stdin
|
|
956
|
+
? fs.readFileSync(0, 'utf8')
|
|
957
|
+
: fs.readFileSync(path.resolve(process.cwd(), parsed.flags.from), 'utf8');
|
|
958
|
+
const result = saveStartIntent(root, markdown, { source });
|
|
959
|
+
if (parsed.flags.json) console.log(JSON.stringify(result, null, 2));
|
|
960
|
+
else printIntentSave(result);
|
|
961
|
+
if (!result.saved) process.exitCode = 1;
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
if (subcommand === 'validate') {
|
|
966
|
+
const target = parsed.positional[0];
|
|
967
|
+
if (!target) throw new Error('intent validate requires an intent id or path');
|
|
968
|
+
const result = validateIntentTarget(root, process.cwd(), target);
|
|
969
|
+
if (parsed.flags.json) console.log(JSON.stringify(result, null, 2));
|
|
970
|
+
else printIntentValidate(result);
|
|
971
|
+
if (!result.valid) process.exitCode = 1;
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
if (subcommand === 'show') {
|
|
976
|
+
const target = parsed.positional[0];
|
|
977
|
+
if (!target) throw new Error('intent show requires an intent id or path');
|
|
978
|
+
const result = readIntentTarget(root, process.cwd(), target);
|
|
979
|
+
if (parsed.flags.json) console.log(JSON.stringify(result, null, 2));
|
|
980
|
+
else printIntentShow(result);
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
throw new Error('intent requires a subcommand: save, validate, or show');
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
if (command === 'operator') {
|
|
988
|
+
if (subcommand !== 'run') throw new Error('operator requires a subcommand: run');
|
|
989
|
+
const root = requireGitRepo(process.cwd(), 'hive-lite operator run');
|
|
990
|
+
const parsed = parseArgs([subsubcommand, ...rest].filter((item) => item !== undefined));
|
|
991
|
+
if (parsed.flags.intent && parsed.positional.length > 0) {
|
|
992
|
+
throw new Error('operator run accepts either a clear requirement or --intent, not both');
|
|
993
|
+
}
|
|
994
|
+
await runOperator(process.cwd(), {
|
|
995
|
+
root,
|
|
996
|
+
requirement: parsed.positional.join(' ').trim(),
|
|
997
|
+
intent: parsed.flags.intent || null,
|
|
998
|
+
operatorAgentCommand: parsed.flags.operatorAgentCommand || parsed.flags.agentCommand,
|
|
999
|
+
operatorAgentMode: parsed.flags.operatorAgentMode || parsed.flags.agentMode,
|
|
1000
|
+
}, {
|
|
1001
|
+
stdin: process.stdin,
|
|
1002
|
+
stdout: process.stdout,
|
|
1003
|
+
stderr: process.stderr,
|
|
1004
|
+
});
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
865
1008
|
if (command === 'find') {
|
|
866
1009
|
const parsed = parseArgs([subcommand, subsubcommand, ...rest].filter((item) => item !== undefined));
|
|
867
1010
|
const intent = parsed.positional.join(' ').trim();
|
package/src/lib/change.js
CHANGED
|
@@ -16,6 +16,12 @@ function sha256(value) {
|
|
|
16
16
|
return createHash('sha256').update(value || '').digest('hex');
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
function looksLikeFilePattern(pattern) {
|
|
20
|
+
if (String(pattern || '').includes('*')) return false;
|
|
21
|
+
const base = path.basename(String(pattern || ''));
|
|
22
|
+
return /\.[A-Za-z0-9]+$/.test(base);
|
|
23
|
+
}
|
|
24
|
+
|
|
19
25
|
function changeDir(root, id) {
|
|
20
26
|
return path.join(hiveDir(root), 'changes', id);
|
|
21
27
|
}
|
|
@@ -314,6 +320,61 @@ function validationPlanFromContextOrMap(context, map) {
|
|
|
314
320
|
}] : [];
|
|
315
321
|
}
|
|
316
322
|
|
|
323
|
+
function createPatternsFromContext(context) {
|
|
324
|
+
const scope = context && context.scope ? context.scope : {};
|
|
325
|
+
return normalizePatternList(scope.writableCreatePatterns || [], {
|
|
326
|
+
source: 'writable_create_patterns',
|
|
327
|
+
operations: ['create_file'],
|
|
328
|
+
}).map(itemPattern).filter(Boolean);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function selectedWritableCertificatesFromContext(context) {
|
|
332
|
+
const writePlan = context && context.writePlan ? context.writePlan : null;
|
|
333
|
+
return writePlan && Array.isArray(writePlan.selectedWritableCertificates)
|
|
334
|
+
? writePlan.selectedWritableCertificates
|
|
335
|
+
: [];
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function certificateOperation(certificate) {
|
|
339
|
+
return certificate.operation || certificate.operationKind || null;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function certificatePath(certificate) {
|
|
343
|
+
return certificate.path || certificate.scope || certificate.pattern || null;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function certificateCoversFile(certificate, file, operation) {
|
|
347
|
+
const pattern = certificatePath(certificate);
|
|
348
|
+
if (!pattern || !matchesPattern(file, pattern)) return false;
|
|
349
|
+
const certifiedOperation = certificateOperation(certificate);
|
|
350
|
+
if (!certifiedOperation) return true;
|
|
351
|
+
return certifiedOperation === operation;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function matchesReviewGatedScope(file, context) {
|
|
355
|
+
const scope = context && context.scope ? context.scope : {};
|
|
356
|
+
const reviewGated = [
|
|
357
|
+
...normalizePatternList(scope.writableConditional || [], { requiresReview: true }),
|
|
358
|
+
...normalizePatternList(scope.writableBroadFallback || [], { requiresReview: true }),
|
|
359
|
+
];
|
|
360
|
+
return reviewGated.some((item) => matchesPattern(file, itemPattern(item)));
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function selectedHasCreatePermission(file, context, selectedWritableDirect, selectedWritableCertificates = []) {
|
|
364
|
+
if (selectedWritableCertificates.length > 0) {
|
|
365
|
+
return selectedWritableCertificates.some((certificate) => certificateCoversFile(certificate, file, 'create_file'));
|
|
366
|
+
}
|
|
367
|
+
const selected = selectedWritableDirect || [];
|
|
368
|
+
const selectedSet = new Set(selected);
|
|
369
|
+
const createPatterns = createPatternsFromContext(context);
|
|
370
|
+
if (createPatterns.some((pattern) => selectedSet.has(pattern) && matchesPattern(file, pattern))) return true;
|
|
371
|
+
|
|
372
|
+
return selected.some((pattern) => (
|
|
373
|
+
!looksLikeFilePattern(pattern)
|
|
374
|
+
&& matchesPattern(file, pattern)
|
|
375
|
+
));
|
|
376
|
+
}
|
|
377
|
+
|
|
317
378
|
function evaluateWritePlanStatus(files, context) {
|
|
318
379
|
if (!context) {
|
|
319
380
|
return {
|
|
@@ -321,7 +382,10 @@ function evaluateWritePlanStatus(files, context) {
|
|
|
321
382
|
contextMode: null,
|
|
322
383
|
blockingReasons: [],
|
|
323
384
|
selectedWritableDirect: [],
|
|
385
|
+
selectedWritableCertificates: [],
|
|
324
386
|
referenceOnlyTouched: [],
|
|
387
|
+
unknownNewFiles: [],
|
|
388
|
+
uncertifiedWrites: [],
|
|
325
389
|
requiredWrites: [],
|
|
326
390
|
blockingWarnings: [],
|
|
327
391
|
};
|
|
@@ -332,9 +396,12 @@ function evaluateWritePlanStatus(files, context) {
|
|
|
332
396
|
? writePlan.blockingWarnings
|
|
333
397
|
: [];
|
|
334
398
|
const selectedWritableDirect = Array.isArray(context.writableScope) ? context.writableScope : [];
|
|
399
|
+
const selectedWritableCertificates = selectedWritableCertificatesFromContext(context);
|
|
335
400
|
const changedPaths = files.map((item) => item.path || item);
|
|
336
401
|
const referenceFiles = writePlan && Array.isArray(writePlan.referenceFiles) ? writePlan.referenceFiles : [];
|
|
337
402
|
const referenceOnlyTouched = [];
|
|
403
|
+
const unknownNewFiles = [];
|
|
404
|
+
const uncertifiedWrites = [];
|
|
338
405
|
for (const file of changedPaths) {
|
|
339
406
|
const reference = referenceFiles.find((item) => matchesPattern(file, item.path || item.pattern));
|
|
340
407
|
if (reference) {
|
|
@@ -345,6 +412,23 @@ function evaluateWritePlanStatus(files, context) {
|
|
|
345
412
|
});
|
|
346
413
|
}
|
|
347
414
|
}
|
|
415
|
+
for (const file of files) {
|
|
416
|
+
const filePath = file.path || file;
|
|
417
|
+
if (file.status !== 'untracked') continue;
|
|
418
|
+
if (!selectedHasCreatePermission(filePath, context, selectedWritableDirect, selectedWritableCertificates)) unknownNewFiles.push(filePath);
|
|
419
|
+
}
|
|
420
|
+
if (selectedWritableCertificates.length > 0) {
|
|
421
|
+
for (const file of files) {
|
|
422
|
+
const filePath = file.path || file;
|
|
423
|
+
const operation = file.status === 'untracked' ? 'create_file' : 'update_existing';
|
|
424
|
+
if (
|
|
425
|
+
!(operation === 'update_existing' && matchesReviewGatedScope(filePath, context))
|
|
426
|
+
&& !selectedWritableCertificates.some((certificate) => certificateCoversFile(certificate, filePath, operation))
|
|
427
|
+
) {
|
|
428
|
+
uncertifiedWrites.push({ path: filePath, operation });
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
348
432
|
|
|
349
433
|
const blockingReasons = [];
|
|
350
434
|
if (context.mode && context.mode !== 'edit_context') {
|
|
@@ -356,6 +440,12 @@ function evaluateWritePlanStatus(files, context) {
|
|
|
356
440
|
for (const item of referenceOnlyTouched) {
|
|
357
441
|
blockingReasons.push(`${item.path} touched reference-only file ${item.reference}`);
|
|
358
442
|
}
|
|
443
|
+
for (const file of unknownNewFiles) {
|
|
444
|
+
blockingReasons.push(`UNKNOWN_NEW_FILE: ${file} is not covered by selected create-capable Direct Writable scope`);
|
|
445
|
+
}
|
|
446
|
+
for (const item of uncertifiedWrites) {
|
|
447
|
+
blockingReasons.push(`UNCERTIFIED_WRITE: ${item.path} ${item.operation} is not covered by a selected writable certificate`);
|
|
448
|
+
}
|
|
359
449
|
if (selectedWritableDirect.length === 0 && changedPaths.length > 0) {
|
|
360
450
|
blockingReasons.push('context has no selected Direct Writable scope');
|
|
361
451
|
}
|
|
@@ -365,7 +455,10 @@ function evaluateWritePlanStatus(files, context) {
|
|
|
365
455
|
contextMode: context.mode || null,
|
|
366
456
|
blockingReasons: [...new Set(blockingReasons)],
|
|
367
457
|
selectedWritableDirect,
|
|
458
|
+
selectedWritableCertificates,
|
|
368
459
|
referenceOnlyTouched,
|
|
460
|
+
unknownNewFiles,
|
|
461
|
+
uncertifiedWrites,
|
|
369
462
|
requiredWrites: writePlan && Array.isArray(writePlan.hypotheses) ? writePlan.hypotheses : [],
|
|
370
463
|
blockingWarnings,
|
|
371
464
|
};
|