@boyingliu01/xp-gate 0.27.6 → 0.28.1
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/adapter-common.sh +0 -0
- package/adapters/cpp.sh +0 -0
- package/adapters/dart.sh +0 -0
- package/adapters/flutter.sh +0 -0
- package/adapters/gate-9.sh +13 -2
- package/adapters/go.sh +0 -0
- package/adapters/iac.sh +0 -0
- package/adapters/java.sh +0 -0
- package/adapters/kotlin.sh +0 -0
- package/adapters/objectivec.sh +0 -0
- package/adapters/plugins/book299-20132-python/scripts/install-gradle.sh +0 -0
- package/adapters/plugins/book299-20132-python/scripts/install-maven.sh +0 -0
- package/adapters/plugins/book299-4081-c/scripts/install-gradle.sh +0 -0
- package/adapters/plugins/book299-4081-c/scripts/install-maven.sh +0 -0
- package/adapters/plugins/book299-4083-javascriptes5/scripts/install-gradle.sh +0 -0
- package/adapters/plugins/book299-4083-javascriptes5/scripts/install-maven.sh +0 -0
- package/adapters/powershell.sh +0 -0
- package/adapters/python.sh +0 -0
- package/adapters/shell.sh +0 -0
- package/adapters/swift.sh +0 -0
- package/adapters/typescript.sh +0 -0
- package/bin/xp-gate.js +0 -0
- package/build-integrity/gate-10.ts +290 -263
- package/gate-10.sh +25 -6
- package/gate-12-file-hygiene.sh +35 -1
- package/gate-3.sh +78 -2
- package/gate-4.sh +0 -0
- package/gate-7.sh +0 -0
- package/gate-8.sh +0 -0
- package/gate-9.sh +13 -2
- package/hooks/adapter-common.sh +0 -0
- package/hooks/gate-10.sh +25 -6
- package/hooks/gate-12-file-hygiene.sh +35 -1
- package/hooks/gate-3.sh +78 -2
- package/hooks/gate-4.sh +0 -0
- package/hooks/gate-7.sh +0 -0
- package/hooks/gate-8.sh +0 -0
- package/hooks/gate-9.sh +13 -2
- package/hooks/lib/audit-sanitize.sh +51 -0
- package/hooks/lib/ccn-ratchet.sh +183 -0
- package/hooks/lib/docs-only.sh +49 -0
- package/hooks/post-merge +0 -0
- package/hooks/pre-commit +74 -15
- package/hooks/pre-push +36 -5
- package/hooks/sprint-gate.sh +0 -0
- package/lib/__tests__/sprint-audit.test.js +207 -207
- package/lib/gate-audit.ts +9 -4
- package/lib/mattpocock-license-verdict.json +1 -1
- package/lib/retro.js +27 -6
- package/lib/sprint-audit.js +235 -235
- package/mock-policy/AGENTS.md +3 -3
- package/mutation/AGENTS.md +3 -3
- package/package.json +1 -1
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-code/bin/delphi-review-guard.sh +0 -0
- package/plugins/claude-code/bin/xp-gate-check +0 -0
- package/plugins/claude-code/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/claude-code/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/dsh/package.json +1 -1
- package/plugins/dsh/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/dsh/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/dsh/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/opencode/package.json +1 -1
- package/plugins/opencode/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/opencode/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +3 -3
- package/plugins/qoder/bin/sprint-flow-guard.sh +0 -0
- package/plugins/qoder/bin/xp-gate-check +0 -0
- package/plugins/qoder/plugin.json +1 -1
- package/plugins/qoder/skills/delphi-review/AGENTS.md +3 -3
- package/plugins/qoder/skills/sprint-flow/AGENTS.md +3 -3
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +3 -3
- package/principles/AGENTS.md +3 -3
- package/principles/__tests__/many-exports-reexport.test.ts +40 -0
- package/principles/adapters/typescript.ts +61 -30
- package/principles/boy-scout.ts +22 -17
- package/scripts/delphi-external-review.cjs +195 -132
- package/skills/delphi-review/AGENTS.md +3 -3
- package/skills/sprint-flow/AGENTS.md +3 -3
- package/skills/test-specification-alignment/AGENTS.md +3 -3
- package/sprint-gate.sh +0 -0
- package/plugins/dsh/lib/command.d.ts +0 -32
- package/plugins/dsh/lib/command.d.ts.map +0 -1
- package/plugins/dsh/lib/command.js +0 -81
- package/plugins/dsh/lib/delphi-config.d.ts +0 -75
- package/plugins/dsh/lib/delphi-config.d.ts.map +0 -1
- package/plugins/dsh/lib/delphi-config.js +0 -183
- package/plugins/dsh/lib/delphi-run.d.ts +0 -24
- package/plugins/dsh/lib/delphi-run.d.ts.map +0 -1
- package/plugins/dsh/lib/delphi-run.js +0 -92
- package/plugins/dsh/lib/delphi.d.ts +0 -88
- package/plugins/dsh/lib/delphi.d.ts.map +0 -1
- package/plugins/dsh/lib/delphi.js +0 -161
- package/plugins/dsh/lib/dialect.d.ts +0 -24
- package/plugins/dsh/lib/dialect.d.ts.map +0 -1
- package/plugins/dsh/lib/dialect.js +0 -36
- package/plugins/dsh/lib/gate-runner.d.ts +0 -18
- package/plugins/dsh/lib/gate-runner.d.ts.map +0 -1
- package/plugins/dsh/lib/gate-runner.js +0 -46
- package/plugins/dsh/lib/index.d.ts +0 -5
- package/plugins/dsh/lib/index.d.ts.map +0 -1
- package/plugins/dsh/lib/index.js +0 -162
- package/plugins/dsh/node_modules/.vite/vitest/results.json +0 -1
|
@@ -25,66 +25,87 @@ function isValidTimeoutMs(value) {
|
|
|
25
25
|
return Number.isInteger(value) && value >= 1000 && value <= 600000;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
// Flag -> args key. `null` marks flags that take no value. Keeping this as data
|
|
29
|
+
// means a new flag is one table row instead of one more switch arm (the switch
|
|
30
|
+
// had grown to ten arms plus a special case for --timeout-ms).
|
|
31
|
+
const VALUE_FLAGS = {
|
|
32
|
+
'--expert': 'expert',
|
|
33
|
+
'--input': 'input',
|
|
34
|
+
'--input-file': 'inputFile',
|
|
35
|
+
'--round': 'round',
|
|
36
|
+
'--config': 'config',
|
|
37
|
+
'--mode': 'mode',
|
|
38
|
+
'--profile': 'profile',
|
|
39
|
+
'--other-experts-file': 'otherExpertsFile',
|
|
40
|
+
};
|
|
41
|
+
const BOOLEAN_FLAGS = { '--fallback-local': 'fallbackLocal' };
|
|
42
|
+
const INTEGER_FLAGS = { '--round': (v) => parseInt(v) };
|
|
43
|
+
|
|
44
|
+
function collectRawArgs(argv) {
|
|
29
45
|
const args = {};
|
|
30
46
|
let timeoutMsRaw;
|
|
31
47
|
for (let i = 0; i < argv.length; i++) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
case '--fallback-local': args.fallbackLocal = true; break;
|
|
43
|
-
default: break;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
48
|
+
const flag = argv[i];
|
|
49
|
+
if (flag === '--timeout-ms') { timeoutMsRaw = argv[++i]; continue; }
|
|
50
|
+
if (BOOLEAN_FLAGS[flag]) { args[BOOLEAN_FLAGS[flag]] = true; continue; }
|
|
51
|
+
const key = VALUE_FLAGS[flag];
|
|
52
|
+
if (!key) continue;
|
|
53
|
+
const value = argv[++i];
|
|
54
|
+
args[key] = INTEGER_FLAGS[flag] ? INTEGER_FLAGS[flag](value) : value;
|
|
55
|
+
}
|
|
56
|
+
return { args, timeoutMsRaw };
|
|
57
|
+
}
|
|
46
58
|
|
|
47
|
-
|
|
59
|
+
function fail(message) {
|
|
60
|
+
console.error(`[delphi-review] ERROR: ${message}`);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Every rejection path used to inline its own console.error + exit. Collected
|
|
65
|
+
// here so the rules read as rules and each message stays verbatim.
|
|
66
|
+
function missingArgumentList(args) {
|
|
48
67
|
const missing = [];
|
|
49
68
|
if (!args.expert) missing.push('--expert');
|
|
50
69
|
if (!args.input && !args.inputFile) missing.push('--input or --input-file');
|
|
51
70
|
if (!args.round) missing.push('--round');
|
|
52
71
|
if (!args.config) missing.push('--config');
|
|
72
|
+
return missing;
|
|
73
|
+
}
|
|
53
74
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
75
|
+
function applyTimeoutFlag(args, timeoutMsRaw) {
|
|
76
|
+
if (timeoutMsRaw === undefined) return;
|
|
77
|
+
const parsed = /^\d+$/.test(timeoutMsRaw) ? Number(timeoutMsRaw) : NaN;
|
|
78
|
+
if (!isValidTimeoutMs(parsed)) {
|
|
79
|
+
fail('--timeout-ms must be an integer between 1000 and 600000.');
|
|
57
80
|
}
|
|
81
|
+
args.timeoutMs = parsed;
|
|
82
|
+
}
|
|
58
83
|
|
|
59
|
-
|
|
60
|
-
if (!
|
|
61
|
-
|
|
62
|
-
|
|
84
|
+
function resolveMode(args) {
|
|
85
|
+
if (!args.mode) args.mode = 'design';
|
|
86
|
+
if (!VALID_MODES.includes(args.mode)) {
|
|
87
|
+
fail(`Invalid mode "${args.mode}". Must be one of: ${VALID_MODES.join(', ')}`);
|
|
63
88
|
}
|
|
89
|
+
}
|
|
64
90
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
91
|
+
function requireArgs(args, timeoutMsRaw) {
|
|
92
|
+
const missing = missingArgumentList(args);
|
|
93
|
+
if (missing.length > 0) {
|
|
94
|
+
fail(`Missing required arguments: ${missing.join(', ')}`);
|
|
69
95
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (timeoutMsRaw !== undefined) {
|
|
73
|
-
const parsed = /^\d+$/.test(timeoutMsRaw) ? Number(timeoutMsRaw) : NaN;
|
|
74
|
-
if (!isValidTimeoutMs(parsed)) {
|
|
75
|
-
console.error('[delphi-review] ERROR: --timeout-ms must be an integer between 1000 and 600000.');
|
|
76
|
-
process.exit(1);
|
|
77
|
-
}
|
|
78
|
-
args.timeoutMs = parsed;
|
|
96
|
+
if (!VALID_EXPERTS.includes(args.expert)) {
|
|
97
|
+
fail(`Invalid expert role "${args.expert}". Must be one of: ${VALID_EXPERTS.join(', ')}`);
|
|
79
98
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (!args.mode) args.mode = 'design';
|
|
83
|
-
if (!VALID_MODES.includes(args.mode)) {
|
|
84
|
-
console.error(`[delphi-review] ERROR: Invalid mode "${args.mode}". Must be one of: ${VALID_MODES.join(', ')}`);
|
|
85
|
-
process.exit(1);
|
|
99
|
+
if (args.input && args.inputFile) {
|
|
100
|
+
fail('--input and --input-file are mutually exclusive. Use only one.');
|
|
86
101
|
}
|
|
102
|
+
applyTimeoutFlag(args, timeoutMsRaw);
|
|
103
|
+
resolveMode(args);
|
|
104
|
+
}
|
|
87
105
|
|
|
106
|
+
function parseArgs(argv) {
|
|
107
|
+
const { args, timeoutMsRaw } = collectRawArgs(argv);
|
|
108
|
+
requireArgs(args, timeoutMsRaw);
|
|
88
109
|
return args;
|
|
89
110
|
}
|
|
90
111
|
|
|
@@ -223,61 +244,75 @@ function readConfig(configPath, profileOverride) {
|
|
|
223
244
|
// ── Distinct-model validation ───────────────────────────────────────────
|
|
224
245
|
const REQUIRED_EXPERT_ROLES = ['architecture', 'technical', 'feasibility'];
|
|
225
246
|
|
|
226
|
-
function
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
const expertRoles = Object.keys(expertMap);
|
|
230
|
-
const normalizedModels = [];
|
|
247
|
+
function isNonEmptyString(value) {
|
|
248
|
+
return typeof value === 'string' && value.trim() !== '';
|
|
249
|
+
}
|
|
231
250
|
|
|
251
|
+
// Roles must be EXACTLY the three required ones: a missing role means the
|
|
252
|
+
// review would silently run with two experts, an extra one means the config
|
|
253
|
+
// no longer matches the three-model policy. One pass reports both.
|
|
254
|
+
function collectModelAssignments(expertMap) {
|
|
255
|
+
const assignments = [];
|
|
232
256
|
for (const role of REQUIRED_EXPERT_ROLES) {
|
|
233
|
-
const expert =
|
|
234
|
-
if (!expert) {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
if (typeof expert.model !== 'string' || expert.model.trim() === '') {
|
|
238
|
-
return { valid: false, reason: `Expert ${role} must define a non-empty model.` };
|
|
257
|
+
const expert = expertMap[role];
|
|
258
|
+
if (!expert) return { error: `Missing required expert role: ${role}.` };
|
|
259
|
+
if (!isNonEmptyString(expert.model)) {
|
|
260
|
+
return { error: `Expert ${role} must define a non-empty model.` };
|
|
239
261
|
}
|
|
240
|
-
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
const unexpectedRole = expertRoles.find(role => !REQUIRED_EXPERT_ROLES.includes(role));
|
|
244
|
-
if (unexpectedRole) {
|
|
245
|
-
return { valid: false, reason: `Unsupported expert role: ${unexpectedRole}.` };
|
|
262
|
+
assignments.push({ role, model: expert.model.trim() });
|
|
246
263
|
}
|
|
264
|
+
const unexpectedRole = Object.keys(expertMap).find(role => !REQUIRED_EXPERT_ROLES.includes(role));
|
|
265
|
+
if (unexpectedRole) return { error: `Unsupported expert role: ${unexpectedRole}.` };
|
|
266
|
+
return { assignments };
|
|
267
|
+
}
|
|
247
268
|
|
|
269
|
+
function findDuplicateModel(assignments) {
|
|
248
270
|
const seen = new Map();
|
|
249
|
-
for (const
|
|
250
|
-
const previousRole = seen.get(
|
|
271
|
+
for (const { role, model } of assignments) {
|
|
272
|
+
const previousRole = seen.get(model);
|
|
251
273
|
if (previousRole) {
|
|
252
|
-
return {
|
|
253
|
-
valid: false,
|
|
254
|
-
reason: `duplicate model "${assignment.model}" assigned to ${previousRole} and ${assignment.role}.`,
|
|
255
|
-
};
|
|
274
|
+
return `duplicate model "${model}" assigned to ${previousRole} and ${role}.`;
|
|
256
275
|
}
|
|
257
|
-
seen.set(
|
|
276
|
+
seen.set(model, role);
|
|
277
|
+
}
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Everything an expert's provider must carry to be callable. Returns the
|
|
282
|
+
// human-readable reason instead of throwing, so the caller keeps one shape.
|
|
283
|
+
function providerConfigError(role, expert, providerMap) {
|
|
284
|
+
if (!isNonEmptyString(expert.provider) || expert.provider === 'local') {
|
|
285
|
+
return `Expert ${role} must define a non-local callable provider.`;
|
|
286
|
+
}
|
|
287
|
+
const provider = providerMap[expert.provider];
|
|
288
|
+
if (!provider || typeof provider !== 'object' || Array.isArray(provider)) {
|
|
289
|
+
return `Expert ${role} provider configuration must be an object.`;
|
|
290
|
+
}
|
|
291
|
+
if (!isNonEmptyString(provider.base_url)) {
|
|
292
|
+
return `Expert ${role} provider must define a non-empty base_url.`;
|
|
258
293
|
}
|
|
294
|
+
if (!isNonEmptyString(provider.api_key)) {
|
|
295
|
+
return `Expert ${role} provider must define a non-empty api_key.`;
|
|
296
|
+
}
|
|
297
|
+
if (provider.timeout_ms !== undefined && !isValidTimeoutMs(provider.timeout_ms)) {
|
|
298
|
+
return `Expert ${role} provider ${expert.provider} timeout_ms must be an integer between 1000 and 600000.`;
|
|
299
|
+
}
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function validateDistinctModels(experts, providers, consensus = {}) {
|
|
304
|
+
const expertMap = experts && typeof experts === 'object' ? experts : {};
|
|
305
|
+
const providerMap = providers && typeof providers === 'object' ? providers : {};
|
|
306
|
+
|
|
307
|
+
const collected = collectModelAssignments(expertMap);
|
|
308
|
+
if (collected.error) return { valid: false, reason: collected.error };
|
|
309
|
+
|
|
310
|
+
const duplicate = findDuplicateModel(collected.assignments);
|
|
311
|
+
if (duplicate) return { valid: false, reason: duplicate };
|
|
259
312
|
|
|
260
313
|
for (const role of REQUIRED_EXPERT_ROLES) {
|
|
261
|
-
const
|
|
262
|
-
if (
|
|
263
|
-
return { valid: false, reason: `Expert ${role} must define a non-local callable provider.` };
|
|
264
|
-
}
|
|
265
|
-
const provider = providerMap[expert.provider];
|
|
266
|
-
if (!provider || typeof provider !== 'object' || Array.isArray(provider)) {
|
|
267
|
-
return { valid: false, reason: `Expert ${role} provider configuration must be an object.` };
|
|
268
|
-
}
|
|
269
|
-
if (typeof provider.base_url !== 'string' || provider.base_url.trim() === '') {
|
|
270
|
-
return { valid: false, reason: `Expert ${role} provider must define a non-empty base_url.` };
|
|
271
|
-
}
|
|
272
|
-
if (typeof provider.api_key !== 'string' || provider.api_key.trim() === '') {
|
|
273
|
-
return { valid: false, reason: `Expert ${role} provider must define a non-empty api_key.` };
|
|
274
|
-
}
|
|
275
|
-
if (provider.timeout_ms !== undefined && !isValidTimeoutMs(provider.timeout_ms)) {
|
|
276
|
-
return {
|
|
277
|
-
valid: false,
|
|
278
|
-
reason: `Expert ${role} provider ${expert.provider} timeout_ms must be an integer between 1000 and 600000.`,
|
|
279
|
-
};
|
|
280
|
-
}
|
|
314
|
+
const error = providerConfigError(role, expertMap[role], providerMap);
|
|
315
|
+
if (error) return { valid: false, reason: error };
|
|
281
316
|
}
|
|
282
317
|
|
|
283
318
|
if (consensus.cross_provider_required === true) {
|
|
@@ -454,25 +489,32 @@ function describePromptBudgetFailure({ bytes, maxBytes, expert }) {
|
|
|
454
489
|
// routed seat, and a ~60 byte smoke probe on the "dead" seat passed in 7s.
|
|
455
490
|
// The optional per-seat `prompt_calibration` record carries the measured
|
|
456
491
|
// bounds so failures can be classified against evidence instead of guessed.
|
|
492
|
+
// A calibration record is only usable when it carries a measured PASS bound
|
|
493
|
+
// and, if it also carries a FAIL bound, that bound is strictly larger. Each
|
|
494
|
+
// broken shape names itself so the operator knows which field to correct.
|
|
495
|
+
const CALIBRATION_SHAPE_HINT = 'needs measured_at and a finite last_pass_bytes > 0 (last_fail_bytes, if present, must be larger)';
|
|
496
|
+
|
|
497
|
+
function isPositiveFinite(value) {
|
|
498
|
+
return Number.isFinite(value) && value > 0;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function calibrationProblem(raw) {
|
|
502
|
+
if (!isPlainObject(raw)) return 'must be an object';
|
|
503
|
+
const pass = raw.last_pass_bytes;
|
|
504
|
+
const fail = raw.last_fail_bytes;
|
|
505
|
+
const failOk = fail === undefined || isPositiveFinite(fail);
|
|
506
|
+
if (!isPositiveFinite(pass) || !failOk) return CALIBRATION_SHAPE_HINT;
|
|
507
|
+
if (fail !== undefined && !(fail > pass)) return 'has last_fail_bytes <= last_pass_bytes';
|
|
508
|
+
if (!isNonEmptyString(raw.measured_at)) return CALIBRATION_SHAPE_HINT;
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
|
|
457
512
|
function resolveSeatCalibration(expertConfig) {
|
|
458
513
|
const raw = expertConfig && expertConfig.prompt_calibration;
|
|
459
514
|
if (raw === undefined) return { calibration: null };
|
|
460
|
-
const
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
if (!isPlainObject(raw)) {
|
|
464
|
-
warn('must be an object');
|
|
465
|
-
return { calibration: null, warning: 'prompt_calibration_malformed' };
|
|
466
|
-
}
|
|
467
|
-
const hasPass = Number.isFinite(raw.last_pass_bytes) && raw.last_pass_bytes > 0;
|
|
468
|
-
const hasFail = raw.last_fail_bytes === undefined
|
|
469
|
-
|| (Number.isFinite(raw.last_fail_bytes) && raw.last_fail_bytes > 0);
|
|
470
|
-
const ordered = hasPass && hasFail
|
|
471
|
-
&& (raw.last_fail_bytes === undefined || raw.last_fail_bytes > raw.last_pass_bytes);
|
|
472
|
-
if (!hasPass || !hasFail || typeof raw.measured_at !== 'string' || raw.measured_at.trim() === '' || !ordered) {
|
|
473
|
-
warn(raw.last_fail_bytes !== undefined && hasPass && raw.last_fail_bytes <= raw.last_pass_bytes
|
|
474
|
-
? 'has last_fail_bytes <= last_pass_bytes'
|
|
475
|
-
: 'needs measured_at and a finite last_pass_bytes > 0 (last_fail_bytes, if present, must be larger)');
|
|
515
|
+
const problem = calibrationProblem(raw);
|
|
516
|
+
if (problem) {
|
|
517
|
+
console.error(`[delphi-review] WARNING: prompt_calibration ${problem}; ignoring the record.`);
|
|
476
518
|
return { calibration: null, warning: 'prompt_calibration_malformed' };
|
|
477
519
|
}
|
|
478
520
|
return {
|
|
@@ -492,40 +534,61 @@ function resolveSeatCalibration(expertConfig) {
|
|
|
492
534
|
// sends operators chasing a cause that is not there (#481). Everything else
|
|
493
535
|
// gets a stable error_type so orchestrators can assert on the class instead of
|
|
494
536
|
// parsing prose.
|
|
537
|
+
// The calibrated ceiling for a seat, or null when the seat carries no usable
|
|
538
|
+
// record. Null is meaningful: it is the "no evidence to reclassify on" branch.
|
|
539
|
+
function calibratedCeiling(calibration) {
|
|
540
|
+
if (calibration && Number.isFinite(calibration.last_pass_bytes)) return calibration.last_pass_bytes;
|
|
541
|
+
return null;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// `error_kind` is the primary signal; the message literal stays as a
|
|
545
|
+
// backward-compatible fallback for callers that hand-build results.
|
|
546
|
+
function isPreResponseDrop(result) {
|
|
547
|
+
return !result.retryable
|
|
548
|
+
&& (result.error_kind === 'pre_response_drop' || result.message === 'Network error.');
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function sizeExceededResult(result, promptBytes, threshold) {
|
|
552
|
+
return {
|
|
553
|
+
...result,
|
|
554
|
+
error_type: 'SIZE_EXCEEDED',
|
|
555
|
+
retryable: false,
|
|
556
|
+
bytes: promptBytes,
|
|
557
|
+
calibrated_pass_bytes: threshold,
|
|
558
|
+
message: `Request dropped before any response: ${promptBytes} bytes exceeds this seat's calibrated passing size `
|
|
559
|
+
+ `(${threshold} bytes). Classified SIZE_EXCEEDED, not a network fault — trim the payload or recalibrate the seat.`,
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// No calibration means no evidence to reclassify on — but an uncalibrated
|
|
564
|
+
// seat dying on a full-budget payload is exactly how #481 started. Keep the
|
|
565
|
+
// class honest (network_error) and point the operator at the protocol.
|
|
566
|
+
function uncalibratedBudgetHint(result, promptBytes) {
|
|
567
|
+
return {
|
|
568
|
+
...result,
|
|
569
|
+
error_type: 'network_error',
|
|
570
|
+
bytes: promptBytes,
|
|
571
|
+
message: `${result.message} Note: this seat has no prompt_calibration record and the payload is at the `
|
|
572
|
+
+ 'default budget — this pattern matches an uncalibrated size ceiling. Recalibrate per the #481 protocol.',
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
|
|
495
576
|
function classifySeatFailure({ result, promptBytes, calibration }) {
|
|
496
577
|
if (!result || !result.error) return result;
|
|
497
|
-
|
|
498
|
-
?
|
|
499
|
-
: null;
|
|
500
|
-
// `error_kind` is the primary signal; the message literal stays as a
|
|
501
|
-
// backward-compatible fallback for callers that hand-build results.
|
|
502
|
-
const isPreResponseDrop = !result.retryable
|
|
503
|
-
&& (result.error_kind === 'pre_response_drop' || result.message === 'Network error.');
|
|
504
|
-
if (isPreResponseDrop && threshold !== null && Number.isFinite(promptBytes) && promptBytes > threshold) {
|
|
505
|
-
return {
|
|
506
|
-
...result,
|
|
507
|
-
error_type: 'SIZE_EXCEEDED',
|
|
508
|
-
retryable: false,
|
|
509
|
-
bytes: promptBytes,
|
|
510
|
-
calibrated_pass_bytes: threshold,
|
|
511
|
-
message: `Request dropped before any response: ${promptBytes} bytes exceeds this seat's calibrated passing size `
|
|
512
|
-
+ `(${threshold} bytes). Classified SIZE_EXCEEDED, not a network fault — trim the payload or recalibrate the seat.`,
|
|
513
|
-
};
|
|
578
|
+
if (!isPreResponseDrop(result)) {
|
|
579
|
+
return { ...result, error_type: result.retryable ? 'retryable_error' : 'network_error' };
|
|
514
580
|
}
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
return
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
message: `${result.message} Note: this seat has no prompt_calibration record and the payload is at the `
|
|
525
|
-
+ 'default budget — this pattern matches an uncalibrated size ceiling. Recalibrate per the #481 protocol.',
|
|
526
|
-
};
|
|
581
|
+
const threshold = calibratedCeiling(calibration);
|
|
582
|
+
if (!Number.isFinite(promptBytes)) {
|
|
583
|
+
return { ...result, error_type: 'network_error' };
|
|
584
|
+
}
|
|
585
|
+
if (threshold !== null && promptBytes > threshold) {
|
|
586
|
+
return sizeExceededResult(result, promptBytes, threshold);
|
|
587
|
+
}
|
|
588
|
+
if (threshold === null && promptBytes >= DEFAULT_PROMPT_BUDGET_BYTES) {
|
|
589
|
+
return uncalibratedBudgetHint(result, promptBytes);
|
|
527
590
|
}
|
|
528
|
-
return { ...result, error_type:
|
|
591
|
+
return { ...result, error_type: 'network_error' };
|
|
529
592
|
}
|
|
530
593
|
|
|
531
594
|
// ── Schema retry (#414 REQ-5) ──────────────────────────────────────────
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/DELPHI-REVIEW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-10-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-10-19
|
|
4
|
+
**Commit:** 9b545de5
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.
|
|
6
|
+
**Version:** 0.28.1.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Delphi Consensus Review — multi-round anonymous expert review (≥90% threshold, exactly 3 experts with distinct executable model IDs). Supports design, requirements, and code-walkthrough modes.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/SPRINT-FLOW KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-10-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-10-19
|
|
4
|
+
**Commit:** 9b545de5
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.
|
|
6
|
+
**Version:** 0.28.1.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
**6-phase** development pipeline (v2.0 compact redesign, Issue #290): PREP → DESIGN → BUILD → VERIFY → SHIP → CLOSE. Phase 3/6 BUILD default build mode is **ralph-loop** (REQ-level iteration, 40-67% token savings vs parallel). HARD-GATE between DESIGN (2/6) and BUILD (3/6): design must pass Delphi review (≥90% consensus) before any coding.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# SKILLS/TEST-SPECIFICATION-ALIGNMENT KNOWLEDGE BASE
|
|
2
2
|
|
|
3
|
-
**Generated:** 2026-10-
|
|
4
|
-
**Commit:**
|
|
3
|
+
**Generated:** 2026-10-19
|
|
4
|
+
**Commit:** 9b545de5
|
|
5
5
|
**Branch:** main
|
|
6
|
-
**Version:** 0.
|
|
6
|
+
**Version:** 0.28.1.0
|
|
7
7
|
|
|
8
8
|
## OVERVIEW
|
|
9
9
|
Test-Specification Alignment Engine — two-stage validation ensuring tests accurately reflect requirements and design specs.
|
package/sprint-gate.sh
CHANGED
|
File without changes
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { type ShellDialect } from "./dialect.js";
|
|
2
|
-
export { type ShellDialect, detectDialect, shellQuote, shq } from "./dialect.js";
|
|
3
|
-
/** Shown when the global `xp-gate` CLI is not on PATH. */
|
|
4
|
-
export declare const FALLBACK_MESSAGE = "[XP-Gate] xp-gate CLI not installed. Install it with: npm install -g @boyingliu01/xp-gate";
|
|
5
|
-
/**
|
|
6
|
-
* Allowlist of xp-gate gate aliases that are safe to invoke standalone
|
|
7
|
-
* (non-preCommitOnly gates + common aliases). Anything outside this set is
|
|
8
|
-
* rejected before reaching the shell.
|
|
9
|
-
*/
|
|
10
|
-
export declare const GATE_WHITELIST: readonly ["duplicates", "complexity", "principles", "arch", "architecture", "iac", "secrets", "sast"];
|
|
11
|
-
export type GateId = (typeof GATE_WHITELIST)[number];
|
|
12
|
-
export declare function isGateAllowed(gate: string): boolean;
|
|
13
|
-
export interface BuildCommandOptions {
|
|
14
|
-
subcommand: "check" | "principles" | "arch";
|
|
15
|
-
target?: string;
|
|
16
|
-
gates?: readonly string[];
|
|
17
|
-
config?: string;
|
|
18
|
-
/**
|
|
19
|
-
* Executor dialect. Defaults to `"posix"` so existing callers keep working;
|
|
20
|
-
* callers running under DSH on Windows must pass `"powershell"`.
|
|
21
|
-
*/
|
|
22
|
-
dialect?: ShellDialect;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Build a single guarded shell command: runs `xp-gate …` when the global CLI
|
|
26
|
-
* is present, otherwise prints the graceful-degradation hint. A single
|
|
27
|
-
* presence check avoids a separate probe/run race.
|
|
28
|
-
*/
|
|
29
|
-
export declare function buildCommand(options: BuildCommandOptions): string;
|
|
30
|
-
/** Resolve a model-supplied path against the session cwd; absolutes pass through. */
|
|
31
|
-
export declare function resolveTarget(rawPath: string, cwd: string): string;
|
|
32
|
-
//# sourceMappingURL=command.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../src/command.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,YAAY,EAAc,MAAM,cAAc,CAAA;AAI5D,OAAO,EAAE,KAAK,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AAEhF,0DAA0D;AAC1D,eAAO,MAAM,gBAAgB,8FACgE,CAAA;AAE7F;;;;GAIG;AACH,eAAO,MAAM,cAAc,uGASjB,CAAA;AAEV,MAAM,MAAM,MAAM,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAA;AAEpD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,OAAO,GAAG,YAAY,GAAG,MAAM,CAAA;IAC3C,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACzB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;OAGG;IACH,OAAO,CAAC,EAAE,YAAY,CAAA;CACvB;AA+BD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CAmBjE;AAED,qFAAqF;AACrF,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAElE"}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { isAbsolute, resolve } from "node:path";
|
|
2
|
-
import { shellQuote } from "./dialect.js";
|
|
3
|
-
// Re-exported so existing importers of this module keep working; the dialect
|
|
4
|
-
// primitives themselves live in ./dialect.ts.
|
|
5
|
-
export { detectDialect, shellQuote, shq } from "./dialect.js";
|
|
6
|
-
/** Shown when the global `xp-gate` CLI is not on PATH. */
|
|
7
|
-
export const FALLBACK_MESSAGE = "[XP-Gate] xp-gate CLI not installed. Install it with: npm install -g @boyingliu01/xp-gate";
|
|
8
|
-
/**
|
|
9
|
-
* Allowlist of xp-gate gate aliases that are safe to invoke standalone
|
|
10
|
-
* (non-preCommitOnly gates + common aliases). Anything outside this set is
|
|
11
|
-
* rejected before reaching the shell.
|
|
12
|
-
*/
|
|
13
|
-
export const GATE_WHITELIST = [
|
|
14
|
-
"duplicates",
|
|
15
|
-
"complexity",
|
|
16
|
-
"principles",
|
|
17
|
-
"arch",
|
|
18
|
-
"architecture",
|
|
19
|
-
"iac",
|
|
20
|
-
"secrets",
|
|
21
|
-
"sast",
|
|
22
|
-
];
|
|
23
|
-
export function isGateAllowed(gate) {
|
|
24
|
-
return GATE_WHITELIST.includes(gate);
|
|
25
|
-
}
|
|
26
|
-
function buildInner(options, dialect) {
|
|
27
|
-
const { subcommand, target, gates, config } = options;
|
|
28
|
-
const q = (value) => shellQuote(value, dialect);
|
|
29
|
-
if (subcommand === "check") {
|
|
30
|
-
const tokens = ["xp-gate", "check"];
|
|
31
|
-
if (target !== undefined)
|
|
32
|
-
tokens.push(q(target));
|
|
33
|
-
if (gates !== undefined && gates.length > 0) {
|
|
34
|
-
// Defense-in-depth: the JSON-schema enum already rejects unknown gate ids,
|
|
35
|
-
// but we also whitelist here so a non-whitelisted value can never reach the shell.
|
|
36
|
-
const allowed = gates.filter(isGateAllowed);
|
|
37
|
-
if (allowed.length > 0)
|
|
38
|
-
tokens.push("--gates", q(allowed.join(",")));
|
|
39
|
-
}
|
|
40
|
-
return tokens.join(" ");
|
|
41
|
-
}
|
|
42
|
-
if (subcommand === "principles") {
|
|
43
|
-
const tokens = ["xp-gate", "principles"];
|
|
44
|
-
if (target !== undefined)
|
|
45
|
-
tokens.push(q(target));
|
|
46
|
-
return tokens.join(" ");
|
|
47
|
-
}
|
|
48
|
-
// subcommand === "arch" — the only remaining member of the union.
|
|
49
|
-
// #447: the default path runs the pinned local @archlinter/cli with its
|
|
50
|
-
// built-in rules — no config needed. Forcing `--config architecture.yaml`
|
|
51
|
-
// (the old default) hit the pre-0.16 schema that archlint itself rejects.
|
|
52
|
-
// Only forward an explicitly provided config, via the opt-in --use-config.
|
|
53
|
-
const tokens = ["xp-gate", "arch"];
|
|
54
|
-
if (config !== undefined)
|
|
55
|
-
tokens.push("--use-config", q(config));
|
|
56
|
-
return tokens.join(" ");
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Build a single guarded shell command: runs `xp-gate …` when the global CLI
|
|
60
|
-
* is present, otherwise prints the graceful-degradation hint. A single
|
|
61
|
-
* presence check avoids a separate probe/run race.
|
|
62
|
-
*/
|
|
63
|
-
export function buildCommand(options) {
|
|
64
|
-
const dialect = options.dialect ?? "posix";
|
|
65
|
-
const inner = buildInner(options, dialect);
|
|
66
|
-
if (dialect === "posix") {
|
|
67
|
-
return `if command -v xp-gate >/dev/null 2>&1; then ${inner}; else printf '%s\\n' ${shellQuote(FALLBACK_MESSAGE, dialect)}; fi`;
|
|
68
|
-
}
|
|
69
|
-
// PowerShell: `Get-Command` resolves the .cmd/.ps1 shims npm writes to
|
|
70
|
-
// %APPDATA%\npm. Those shims are invisible to a Git-Bash PATH lookup, so this
|
|
71
|
-
// branch is more reliable than `command -v` on Windows.
|
|
72
|
-
//
|
|
73
|
-
// The hint branch leaves $LASTEXITCODE untouched at 0, matching the POSIX
|
|
74
|
-
// branch's `printf` behaviour.
|
|
75
|
-
return `if (Get-Command xp-gate -ErrorAction SilentlyContinue) { ${inner} } else { Write-Output ${shellQuote(FALLBACK_MESSAGE, dialect)} }`;
|
|
76
|
-
}
|
|
77
|
-
/** Resolve a model-supplied path against the session cwd; absolutes pass through. */
|
|
78
|
-
export function resolveTarget(rawPath, cwd) {
|
|
79
|
-
return isAbsolute(rawPath) ? rawPath : resolve(cwd, rawPath);
|
|
80
|
-
}
|
|
81
|
-
//# sourceMappingURL=command.js.map
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Delphi configuration loading for the DSH plugin.
|
|
3
|
-
*
|
|
4
|
-
* Reads the same `.delphi-config.json` the xp-gate skill already uses, so a
|
|
5
|
-
* project configures its reviewers once and both the skill and the DSH tool
|
|
6
|
-
* agree on who they are.
|
|
7
|
-
*
|
|
8
|
-
* @test REQ-DSH-016
|
|
9
|
-
* @intent 复用项目既有的 .delphi-config.json,把 provider/api_key/专家席位解析成
|
|
10
|
-
* DSH 工具可用的结构,并对 ${ENV} 占位符与缺失密钥给出可操作的报错。
|
|
11
|
-
* @covers AC-DSH-016-02
|
|
12
|
-
*/
|
|
13
|
-
import type { ExpertSeat, DelphiOptions } from "./delphi.js";
|
|
14
|
-
/** Shape of one expert entry in .delphi-config.json. */
|
|
15
|
-
interface RawExpert {
|
|
16
|
-
id?: string;
|
|
17
|
-
role?: string;
|
|
18
|
-
name?: string;
|
|
19
|
-
model?: string;
|
|
20
|
-
requested_model?: string;
|
|
21
|
-
max_tokens?: number;
|
|
22
|
-
maxTokens?: number;
|
|
23
|
-
/** Per-expert provider override; falls back to the profile default. */
|
|
24
|
-
provider?: string;
|
|
25
|
-
}
|
|
26
|
-
interface RawProfile {
|
|
27
|
-
provider?: string;
|
|
28
|
-
providers?: Record<string, {
|
|
29
|
-
base_url?: string;
|
|
30
|
-
baseUrl?: string;
|
|
31
|
-
api_key?: string;
|
|
32
|
-
apiKey?: string;
|
|
33
|
-
}>;
|
|
34
|
-
/**
|
|
35
|
-
* Experts appear in two shapes in the wild: an object keyed by role (what
|
|
36
|
-
* xp-gate actually writes) and an array. Both are accepted.
|
|
37
|
-
*/
|
|
38
|
-
experts?: RawExpert[] | Record<string, RawExpert>;
|
|
39
|
-
consensus?: {
|
|
40
|
-
threshold_percent?: number;
|
|
41
|
-
max_review_rounds?: number;
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
export interface RawDelphiConfig extends RawProfile {
|
|
45
|
-
active_profile?: string;
|
|
46
|
-
profiles?: Record<string, RawProfile>;
|
|
47
|
-
}
|
|
48
|
-
export interface ResolvedDelphiConfig {
|
|
49
|
-
readonly seats: ExpertSeat[];
|
|
50
|
-
readonly baseUrl: string;
|
|
51
|
-
readonly apiKey: string;
|
|
52
|
-
readonly thresholdPercent: number;
|
|
53
|
-
readonly maxRounds: number;
|
|
54
|
-
readonly provider: string;
|
|
55
|
-
}
|
|
56
|
-
/** Raised when config on disk cannot be turned into a usable review setup. */
|
|
57
|
-
export declare class DelphiSetupError extends Error {
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Expand `${VAR}` and `{env:VAR}` placeholders from the environment.
|
|
61
|
-
* Supports the two spellings already present in xp-gate config files.
|
|
62
|
-
*/
|
|
63
|
-
export declare function expandEnv(value: string, env?: NodeJS.ProcessEnv): string;
|
|
64
|
-
/**
|
|
65
|
-
* Turn raw config into a ready-to-run review setup.
|
|
66
|
-
* Fails loudly and specifically: a missing key or an unexpanded placeholder is
|
|
67
|
-
* the most common way a Delphi run silently degrades.
|
|
68
|
-
*/
|
|
69
|
-
export declare function resolveConfig(cfg: RawDelphiConfig, env?: NodeJS.ProcessEnv): ResolvedDelphiConfig;
|
|
70
|
-
/** Read and resolve `.delphi-config.json` from a project root. */
|
|
71
|
-
export declare function loadDelphiConfig(cwd: string, env?: NodeJS.ProcessEnv, globalPath?: string): ResolvedDelphiConfig;
|
|
72
|
-
/** Build the transport options a round needs from a resolved config. */
|
|
73
|
-
export declare function toDelphiOptions(cfg: ResolvedDelphiConfig): DelphiOptions;
|
|
74
|
-
export {};
|
|
75
|
-
//# sourceMappingURL=delphi-config.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"delphi-config.d.ts","sourceRoot":"","sources":["../src/delphi-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAG5D,wDAAwD;AACxD,UAAU,SAAS;IACjB,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,UAAU,UAAU;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACtG;;;OAGG;IACH,OAAO,CAAC,EAAE,SAAS,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;IACjD,SAAS,CAAC,EAAE;QAAE,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CACvE;AAED,MAAM,WAAW,eAAgB,SAAQ,UAAU;IACjD,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;CACtC;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,KAAK,EAAE,UAAU,EAAE,CAAA;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAC1B;AAED,8EAA8E;AAC9E,qBAAa,gBAAiB,SAAQ,KAAK;CAAG;AAE9C;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAIrF;AA0ED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,eAAe,EACpB,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,oBAAoB,CAuBtB;AAsCD,kEAAkE;AAClE,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,UAAU,GAAE,MAAqE,GAChF,oBAAoB,CAkCtB;AAED,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,GAAG,EAAE,oBAAoB,GAAG,aAAa,CAQxE"}
|