safeword 0.61.0 → 0.63.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/dist/{architecture-WFRA4ITA.js → architecture-T4RR7Q5J.js} +2 -2
- package/dist/{check-KHWH36AK.js → check-MUL6YRIO.js} +7 -7
- package/dist/{chunk-U2GSR4OM.js → chunk-4DUNUF4B.js} +110 -6
- package/dist/chunk-4DUNUF4B.js.map +1 -0
- package/dist/{chunk-XTLCJKGE.js → chunk-6BEAVNR2.js} +56 -16
- package/dist/chunk-6BEAVNR2.js.map +1 -0
- package/dist/{chunk-GEC7AHRF.js → chunk-7F523R3I.js} +678 -437
- package/dist/chunk-7F523R3I.js.map +1 -0
- package/dist/{chunk-D4UGNHLW.js → chunk-CYITXCZO.js} +2 -2
- package/dist/{chunk-HWOJR33A.js → chunk-DN67MPYH.js} +61 -22
- package/dist/chunk-DN67MPYH.js.map +1 -0
- package/dist/{chunk-HI7ANNQI.js → chunk-FTMQT2V3.js} +7 -1
- package/dist/chunk-FTMQT2V3.js.map +1 -0
- package/dist/{chunk-ILTC7KBQ.js → chunk-IDTVO3BJ.js} +11 -1
- package/dist/chunk-IDTVO3BJ.js.map +1 -0
- package/dist/{chunk-C4LNUGHF.js → chunk-OE6HYAWU.js} +2 -2
- package/dist/{chunk-XTGWCHWI.js → chunk-ZBFYIJNE.js} +62 -12
- package/dist/chunk-ZBFYIJNE.js.map +1 -0
- package/dist/cli.js +33 -21
- package/dist/cli.js.map +1 -1
- package/dist/{codify-BXBG5XPK.js → codify-SPIWSH2R.js} +4 -4
- package/dist/{diff-K3SYBGB7.js → diff-BZOC4JXC.js} +3 -3
- package/dist/{github-rest-36XJI5HD.js → github-rest-3BZDDOX6.js} +6 -5
- package/dist/github-rest-3BZDDOX6.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/{lint-gherkin-FLJEH3EJ.js → lint-gherkin-RKLOOSNO.js} +4 -2
- package/dist/{lint-gherkin-FLJEH3EJ.js.map → lint-gherkin-RKLOOSNO.js.map} +1 -1
- package/dist/{reset-4IGPE6TD.js → reset-646VL6X4.js} +3 -3
- package/dist/{retro-FCGFUDEA.js → retro-GGVK4BYL.js} +108 -41
- package/dist/retro-GGVK4BYL.js.map +1 -0
- package/dist/{retro-extract-XXZI3DFW.js → retro-extract-6IZLY25L.js} +12 -2
- package/dist/{self-report-XLBFP2NT.js → self-report-MZ2CP23A.js} +4 -2
- package/dist/{self-report-AQZKGDAS.js → self-report-SMDJD2UO.js} +2 -2
- package/dist/{setup-SECXSZ5K.js → setup-Q4Y7Y6PN.js} +21 -8
- package/dist/{setup-SECXSZ5K.js.map → setup-Q4Y7Y6PN.js.map} +1 -1
- package/dist/{sync-learnings-BVNUNLPD.js → sync-learnings-THGNXZCT.js} +2 -2
- package/dist/{sync-tickets-4TOJBDHW.js → sync-tickets-P2FFD7DH.js} +3 -3
- package/dist/{sync-tracker-U4TKFX4R.js → sync-tracker-SV6AEV2O.js} +3 -3
- package/dist/{ticket-new-PIHZGZGG.js → ticket-new-K4X2ZO6V.js} +2 -2
- package/dist/{upgrade-S5DTXDLC.js → upgrade-R52BFPBM.js} +15 -13
- package/dist/upgrade-R52BFPBM.js.map +1 -0
- package/package.json +1 -1
- package/templates/agents/safeword-retro-filer.md +50 -0
- package/templates/agents/safeword-retro-filer.toml +47 -0
- package/templates/codex/config.toml +21 -4
- package/templates/commands/audit.md +1 -397
- package/templates/commands/retro.md +5 -0
- package/templates/commands/self-review.md +4 -2
- package/templates/commands/verify.md +2 -304
- package/templates/cucumber/cucumber.mjs +42 -2
- package/templates/doc-templates/ticket-template.md +1 -1
- package/templates/guides/self-report-filing.md +31 -21
- package/templates/hooks/codex/post-tool-quality.ts +56 -0
- package/templates/hooks/codex/post-tool-skill-nudge.ts +2 -17
- package/templates/hooks/codex/pre-tool-quality-helpers.ts +24 -0
- package/templates/hooks/codex/pre-tool-quality.ts +21 -19
- package/templates/hooks/codex/stop.ts +88 -32
- package/templates/hooks/cursor/after-file-edit.ts +8 -5
- package/templates/hooks/cursor/before-shell-execution.ts +28 -6
- package/templates/hooks/cursor/gate-adapter.ts +13 -103
- package/templates/hooks/cursor/stop.ts +20 -7
- package/templates/hooks/lib/bash-ledger-writes.ts +216 -0
- package/templates/hooks/lib/cursor-run-identity.ts +114 -10
- package/templates/hooks/lib/cursor-state.ts +59 -0
- package/templates/hooks/lib/jtbd.ts +1 -1
- package/templates/hooks/lib/lint-config.ts +48 -1
- package/templates/hooks/lib/lint.ts +42 -4
- package/templates/hooks/lib/phase-provenance.ts +263 -0
- package/templates/hooks/lib/retro-draft-spool.ts +43 -3
- package/templates/hooks/lib/retro-extract.ts +188 -11
- package/templates/hooks/lib/retro-filing-gate.ts +192 -0
- package/templates/hooks/lib/retro-nudge.ts +4 -4
- package/templates/hooks/lib/self-report.ts +16 -0
- package/templates/hooks/lib/shell-segments.ts +116 -0
- package/templates/hooks/post-tool-quality.ts +59 -7
- package/templates/hooks/pre-tool-quality.ts +46 -5
- package/templates/hooks/prompt-retro-nudge.ts +9 -6
- package/templates/hooks/stop-quality.ts +1 -1
- package/templates/hooks/stop-retro-filing.ts +62 -0
- package/templates/hooks/write-review-stamp.ts +111 -18
- package/templates/skills/audit/SKILL.md +34 -33
- package/templates/skills/bdd/DISCOVERY.md +1 -0
- package/templates/skills/bdd/SCENARIOS.md +30 -0
- package/templates/skills/bdd/SKILL.md +3 -3
- package/templates/skills/bdd/TDD.md +2 -2
- package/templates/skills/retro/SKILL.md +104 -0
- package/templates/skills/self-review/SKILL.md +3 -2
- package/templates/skills/ticket-system/SKILL.md +2 -1
- package/templates/skills/verify/SKILL.md +13 -7
- package/templates/spec-template.md +4 -0
- package/dist/chunk-GEC7AHRF.js.map +0 -1
- package/dist/chunk-HI7ANNQI.js.map +0 -1
- package/dist/chunk-HWOJR33A.js.map +0 -1
- package/dist/chunk-ILTC7KBQ.js.map +0 -1
- package/dist/chunk-U2GSR4OM.js.map +0 -1
- package/dist/chunk-XTGWCHWI.js.map +0 -1
- package/dist/chunk-XTLCJKGE.js.map +0 -1
- package/dist/github-rest-36XJI5HD.js.map +0 -1
- package/dist/retro-FCGFUDEA.js.map +0 -1
- package/dist/upgrade-S5DTXDLC.js.map +0 -1
- /package/dist/{architecture-WFRA4ITA.js.map → architecture-T4RR7Q5J.js.map} +0 -0
- /package/dist/{check-KHWH36AK.js.map → check-MUL6YRIO.js.map} +0 -0
- /package/dist/{chunk-D4UGNHLW.js.map → chunk-CYITXCZO.js.map} +0 -0
- /package/dist/{chunk-C4LNUGHF.js.map → chunk-OE6HYAWU.js.map} +0 -0
- /package/dist/{codify-BXBG5XPK.js.map → codify-SPIWSH2R.js.map} +0 -0
- /package/dist/{diff-K3SYBGB7.js.map → diff-BZOC4JXC.js.map} +0 -0
- /package/dist/{reset-4IGPE6TD.js.map → reset-646VL6X4.js.map} +0 -0
- /package/dist/{retro-extract-XXZI3DFW.js.map → retro-extract-6IZLY25L.js.map} +0 -0
- /package/dist/{self-report-XLBFP2NT.js.map → self-report-MZ2CP23A.js.map} +0 -0
- /package/dist/{self-report-AQZKGDAS.js.map → self-report-SMDJD2UO.js.map} +0 -0
- /package/dist/{sync-learnings-BVNUNLPD.js.map → sync-learnings-THGNXZCT.js.map} +0 -0
- /package/dist/{sync-tickets-4TOJBDHW.js.map → sync-tickets-P2FFD7DH.js.map} +0 -0
- /package/dist/{sync-tracker-U4TKFX4R.js.map → sync-tracker-SV6AEV2O.js.map} +0 -0
- /package/dist/{ticket-new-PIHZGZGG.js.map → ticket-new-K4X2ZO6V.js.map} +0 -0
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
GENERATED_ARCHITECTURE_FILENAME,
|
|
3
3
|
isArchitectureDocumentEnforcementEnabled,
|
|
4
4
|
resolveGeneratedArchitecturePath
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-FTMQT2V3.js";
|
|
6
6
|
import {
|
|
7
7
|
exists,
|
|
8
8
|
isDirectory,
|
|
@@ -976,4 +976,4 @@ function architectureCheck(cwd) {
|
|
|
976
976
|
export {
|
|
977
977
|
architecture
|
|
978
978
|
};
|
|
979
|
-
//# sourceMappingURL=architecture-
|
|
979
|
+
//# sourceMappingURL=architecture-T4RR7Q5J.js.map
|
|
@@ -4,22 +4,22 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
checkHealth,
|
|
6
6
|
reportHealthSummary
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
9
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-ZBFYIJNE.js";
|
|
8
|
+
import "./chunk-DN67MPYH.js";
|
|
9
|
+
import "./chunk-6BEAVNR2.js";
|
|
10
10
|
import {
|
|
11
11
|
fetchRegistryLatestVersion,
|
|
12
12
|
isNewerVersion
|
|
13
13
|
} from "./chunk-LRYWFRPD.js";
|
|
14
|
-
import "./chunk-
|
|
14
|
+
import "./chunk-7F523R3I.js";
|
|
15
15
|
import {
|
|
16
16
|
detectPackageManager
|
|
17
17
|
} from "./chunk-OY7F4YNQ.js";
|
|
18
18
|
import {
|
|
19
19
|
syncTickets
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-CYITXCZO.js";
|
|
21
21
|
import "./chunk-NHXVS5FL.js";
|
|
22
|
-
import "./chunk-
|
|
22
|
+
import "./chunk-FTMQT2V3.js";
|
|
23
23
|
import "./chunk-CXGL65PS.js";
|
|
24
24
|
import "./chunk-HSC7TELY.js";
|
|
25
25
|
import "./chunk-2KELQTLE.js";
|
|
@@ -125,4 +125,4 @@ async function check(options) {
|
|
|
125
125
|
export {
|
|
126
126
|
check
|
|
127
127
|
};
|
|
128
|
-
//# sourceMappingURL=check-
|
|
128
|
+
//# sourceMappingURL=check-MUL6YRIO.js.map
|
|
@@ -3,18 +3,23 @@ import {
|
|
|
3
3
|
} from "./chunk-B6KVKEZA.js";
|
|
4
4
|
|
|
5
5
|
// templates/hooks/lib/retro-extract.ts
|
|
6
|
-
import { readFileSync } from "fs";
|
|
6
|
+
import { readFileSync, writeFileSync } from "fs";
|
|
7
7
|
import nodePath from "path";
|
|
8
8
|
var DIGEST_CAP = 18e4;
|
|
9
|
-
var
|
|
10
|
-
|
|
9
|
+
var DEFAULT_CLAUDE_RETRO_MODEL = "sonnet";
|
|
10
|
+
var DEFAULT_CODEX_RETRO_MODEL = "gpt-5.5";
|
|
11
|
+
var DEFAULT_RETRO_MODEL = DEFAULT_CLAUDE_RETRO_MODEL;
|
|
12
|
+
function defaultRetroModel(agent) {
|
|
13
|
+
return agent === "codex" ? DEFAULT_CODEX_RETRO_MODEL : DEFAULT_CLAUDE_RETRO_MODEL;
|
|
14
|
+
}
|
|
15
|
+
function resolveRetroModel(projectDirectory, agent = "claude") {
|
|
11
16
|
try {
|
|
12
17
|
const raw = readFileSync(nodePath.join(projectDirectory, ".safeword", "config.json"), "utf8");
|
|
13
18
|
const parsed = JSON.parse(raw);
|
|
14
19
|
const model = parsed.retro?.model;
|
|
15
|
-
return typeof model === "string" && model.length > 0 ? model :
|
|
20
|
+
return typeof model === "string" && model.length > 0 ? model : defaultRetroModel(agent);
|
|
16
21
|
} catch {
|
|
17
|
-
return
|
|
22
|
+
return defaultRetroModel(agent);
|
|
18
23
|
}
|
|
19
24
|
}
|
|
20
25
|
var OVERLAP_CHARS = 2048;
|
|
@@ -54,10 +59,68 @@ function buildExtractArgv(options) {
|
|
|
54
59
|
options.prompt
|
|
55
60
|
];
|
|
56
61
|
}
|
|
62
|
+
function buildCodexExtractArgv(options) {
|
|
63
|
+
return [
|
|
64
|
+
"exec",
|
|
65
|
+
"--ignore-user-config",
|
|
66
|
+
"--disable",
|
|
67
|
+
"hooks",
|
|
68
|
+
"-c",
|
|
69
|
+
"mcp_servers={}",
|
|
70
|
+
"--output-schema",
|
|
71
|
+
options.schemaPath,
|
|
72
|
+
"-o",
|
|
73
|
+
options.outputPath,
|
|
74
|
+
"--json",
|
|
75
|
+
"--sandbox",
|
|
76
|
+
"read-only",
|
|
77
|
+
"-m",
|
|
78
|
+
options.model,
|
|
79
|
+
options.prompt
|
|
80
|
+
];
|
|
81
|
+
}
|
|
57
82
|
var EXTRACT_SYSTEM_PROMPT = `You extract SAFEWORD's OWN friction from a session digest. Output ONLY a JSON array (no prose). Each item: {"category":"bug|rough-edge|gap","title":"canonical title of the SAFEWORD behavior","safeword_surface":"a real safeword path: hooks/\u2026, packages/cli/\u2026, templates/\u2026, dist/\u2026, or .safeword/\u2026","what_happened":"","why_friction":"","repro":"in terms of safeword commands"}. Rules: SAFEWORD's friction only (not the host project, not Claude Code itself); canonical behavior-titles; do not invent; [] if none.`;
|
|
83
|
+
var CODEX_RETRO_OUTPUT_SCHEMA = {
|
|
84
|
+
type: "object",
|
|
85
|
+
additionalProperties: false,
|
|
86
|
+
properties: {
|
|
87
|
+
findings: {
|
|
88
|
+
type: "array",
|
|
89
|
+
items: {
|
|
90
|
+
type: "object",
|
|
91
|
+
additionalProperties: false,
|
|
92
|
+
properties: {
|
|
93
|
+
category: { type: "string", enum: ["bug", "rough-edge", "gap"] },
|
|
94
|
+
title: { type: "string" },
|
|
95
|
+
safeword_surface: { type: "string" },
|
|
96
|
+
what_happened: { type: "string" },
|
|
97
|
+
why_friction: { type: "string" },
|
|
98
|
+
repro: { type: "string" }
|
|
99
|
+
},
|
|
100
|
+
required: [
|
|
101
|
+
"category",
|
|
102
|
+
"title",
|
|
103
|
+
"safeword_surface",
|
|
104
|
+
"what_happened",
|
|
105
|
+
"why_friction",
|
|
106
|
+
"repro"
|
|
107
|
+
]
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
required: ["findings"]
|
|
112
|
+
};
|
|
58
113
|
function buildExtractPrompt(digestPath) {
|
|
59
114
|
return `Read the file ${digestPath} and extract SAFEWORD's own friction as the JSON array described. Output only the JSON array.`;
|
|
60
115
|
}
|
|
116
|
+
function buildCodexExtractPrompt(digest) {
|
|
117
|
+
return `${EXTRACT_SYSTEM_PROMPT}
|
|
118
|
+
|
|
119
|
+
Return only JSON matching the provided output schema: {"findings":[...]}. Use an empty findings array when there is no safeword friction.
|
|
120
|
+
|
|
121
|
+
Transcript digest:
|
|
122
|
+
${digest}`;
|
|
123
|
+
}
|
|
61
124
|
function parseFindings(stdout) {
|
|
62
125
|
let envelopeResult;
|
|
63
126
|
try {
|
|
@@ -76,6 +139,14 @@ function parseFindings(stdout) {
|
|
|
76
139
|
return [];
|
|
77
140
|
}
|
|
78
141
|
}
|
|
142
|
+
function parseCodexFindingsOutput(rawOutput) {
|
|
143
|
+
try {
|
|
144
|
+
const parsed = JSON.parse(rawOutput);
|
|
145
|
+
return Array.isArray(parsed.findings) ? parsed.findings : void 0;
|
|
146
|
+
} catch {
|
|
147
|
+
return void 0;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
79
150
|
async function runHeadlessExtraction(transcript, dependencies) {
|
|
80
151
|
try {
|
|
81
152
|
const digestPath = dependencies.writeDigest(buildDigest(transcript));
|
|
@@ -94,6 +165,34 @@ async function runHeadlessExtraction(transcript, dependencies) {
|
|
|
94
165
|
return [];
|
|
95
166
|
}
|
|
96
167
|
}
|
|
168
|
+
async function runCodexHeadlessExtractionChecked(transcript, dependencies) {
|
|
169
|
+
try {
|
|
170
|
+
const schemaPath = dependencies.schemaPath ?? nodePath.join(dependencies.cwd, "schema.json");
|
|
171
|
+
const outputPath = dependencies.outputPath ?? nodePath.join(dependencies.cwd, "output.json");
|
|
172
|
+
const writeFile = dependencies.writeFile ?? writeFileSync;
|
|
173
|
+
const readFile = dependencies.readFile ?? ((path) => readFileSync(path, "utf8"));
|
|
174
|
+
writeFile(schemaPath, JSON.stringify(CODEX_RETRO_OUTPUT_SCHEMA));
|
|
175
|
+
const argv = buildCodexExtractArgv({
|
|
176
|
+
model: dependencies.model ?? DEFAULT_CODEX_RETRO_MODEL,
|
|
177
|
+
schemaPath,
|
|
178
|
+
outputPath,
|
|
179
|
+
prompt: buildCodexExtractPrompt(buildDigest(transcript))
|
|
180
|
+
});
|
|
181
|
+
const { code } = await dependencies.spawn(argv, {
|
|
182
|
+
cwd: dependencies.cwd,
|
|
183
|
+
env: { ...dependencies.env, [RETRO_CHILD_ENV]: "1" },
|
|
184
|
+
stdio: "ignore"
|
|
185
|
+
});
|
|
186
|
+
if (code !== 0) return { ok: false, findings: [] };
|
|
187
|
+
const findings = parseCodexFindingsOutput(readFile(outputPath));
|
|
188
|
+
return findings === void 0 ? { ok: false, findings: [] } : { ok: true, findings };
|
|
189
|
+
} catch {
|
|
190
|
+
return { ok: false, findings: [] };
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
async function runCodexHeadlessExtraction(transcript, dependencies) {
|
|
194
|
+
return (await runCodexHeadlessExtractionChecked(transcript, dependencies)).findings;
|
|
195
|
+
}
|
|
97
196
|
var SHORT_RESULT = 600;
|
|
98
197
|
var TOOL_USE_INPUT_CAP = 300;
|
|
99
198
|
var FRICTION = /error|fail|block|denied|stale|drift|guard|FAILED/i;
|
|
@@ -129,6 +228,8 @@ function buildDigest(rawTranscript, cap = DIGEST_CAP) {
|
|
|
129
228
|
|
|
130
229
|
export {
|
|
131
230
|
DIGEST_CAP,
|
|
231
|
+
DEFAULT_CLAUDE_RETRO_MODEL,
|
|
232
|
+
DEFAULT_CODEX_RETRO_MODEL,
|
|
132
233
|
DEFAULT_RETRO_MODEL,
|
|
133
234
|
resolveRetroModel,
|
|
134
235
|
OVERLAP_CHARS,
|
|
@@ -138,7 +239,10 @@ export {
|
|
|
138
239
|
isRetroChild,
|
|
139
240
|
RETRO_EXTRACT_CMD_ENV,
|
|
140
241
|
buildExtractArgv,
|
|
242
|
+
buildCodexExtractArgv,
|
|
141
243
|
runHeadlessExtraction,
|
|
244
|
+
runCodexHeadlessExtractionChecked,
|
|
245
|
+
runCodexHeadlessExtraction,
|
|
142
246
|
buildDigest
|
|
143
247
|
};
|
|
144
|
-
//# sourceMappingURL=chunk-
|
|
248
|
+
//# sourceMappingURL=chunk-4DUNUF4B.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../templates/hooks/lib/retro-extract.ts"],"sourcesContent":["// Safeword: retro headless-extraction helpers (ticket 7D8PJP).\n//\n// The invisible-retro path runs extraction in a separate headless `claude -p`\n// session launched by the Stop hook — never injected into the user's\n// conversation. This module holds the pieces that path needs: a transcript\n// DIGEST (transcripts are multi-MB, far past any model context), and — added in\n// later steps — the headless argv builder, the recursion-guard predicate, and the\n// synchronous runner. Agent-neutral where possible; Claude-specific bits are\n// named as such.\n\nimport { readFileSync, writeFileSync } from 'node:fs';\nimport nodePath from 'node:path';\n\nimport { iterateJsonlEntries } from './jsonl-spool.js';\n\n/**\n * Default cap (chars) for a transcript digest. A real session transcript is tens\n * of MB; the extractor needs a bounded, signal-dense slice, not the raw JSONL.\n */\nexport const DIGEST_CAP = 180_000;\n\n/**\n * Default Claude extraction model. Sonnet, not haiku: measured head-to-head on\n * the same transcript, haiku surfaced 1–3 weak findings vs sonnet's 9 strong\n * ones (ZFGWS1). Overridable per install via `.safeword/config.json` →\n * `retro.model`.\n */\nexport const DEFAULT_CLAUDE_RETRO_MODEL = 'sonnet';\n\n/** Default Codex extraction model. Stock Codex can only call OpenAI models. */\nexport const DEFAULT_CODEX_RETRO_MODEL = 'gpt-5.5';\n\n/** Backward-compatible alias for existing Claude call sites/tests. */\nexport const DEFAULT_RETRO_MODEL = DEFAULT_CLAUDE_RETRO_MODEL;\n\nexport type RetroAgent = 'claude' | 'codex';\n\nfunction defaultRetroModel(agent: RetroAgent): string {\n return agent === 'codex' ? DEFAULT_CODEX_RETRO_MODEL : DEFAULT_CLAUDE_RETRO_MODEL;\n}\n\n/**\n * Resolve the extraction model for an install: `retro.model` from\n * `.safeword/config.json`, else the per-agent default. Fail-open to the default\n * on any missing/unreadable/malformed config — model selection must never break\n * the out-of-band retro run.\n */\nexport function resolveRetroModel(projectDirectory: string, agent: RetroAgent = 'claude'): string {\n try {\n const raw = readFileSync(nodePath.join(projectDirectory, '.safeword', 'config.json'), 'utf8');\n const parsed = JSON.parse(raw) as { retro?: { model?: unknown } };\n const model = parsed.retro?.model;\n return typeof model === 'string' && model.length > 0 ? model : defaultRetroModel(agent);\n } catch {\n return defaultRetroModel(agent);\n }\n}\n\n/**\n * Overlap (chars) re-included before a delta window's start, so a finding\n * straddling a window boundary appears whole in one fire (ticket ZFGWS1). A\n * char-slice may cut the first JSONL line; `buildDigest` skips that malformed head\n * line, so whole boundary entries still survive. Duplicate findings from the\n * overlap are absorbed by signature dedupe (triage).\n */\nexport const OVERLAP_CHARS = 2048;\n\n/**\n * Slice the delta window a fire should digest: from `windowStart` (the previous\n * fire's recorded offset) minus a small overlap, to the end. The FIRST fire\n * (`windowStart <= 0`) returns the whole transcript so far. So `buildDigest`'s cap\n * applies to the WINDOW, not the chronological head — defeating the head-cap that\n * made plain re-arm inert. The overlap is clamped at the start of the transcript.\n */\nexport function windowFor(\n transcript: string,\n windowStart: number,\n overlap: number = OVERLAP_CHARS,\n): string {\n if (windowStart <= 0) return transcript;\n return transcript.slice(Math.max(0, windowStart - overlap));\n}\n\n/** A decision to run the retro child, carrying the delta window + stable session id. */\nexport interface RetroChildInvocation {\n transcriptPath: string;\n windowStart: number;\n sessionId: string;\n}\n\n/**\n * Build the `safeword retro` argv the Stop hook spawns out-of-band. Forwards the\n * delta window offset and the resolved session id (ZFGWS1) so the child digests\n * only the new window and attributes findings to the real session — not the\n * 'unknown' fallback its own env resolves to in cloud.\n */\nexport function retroChildArgs(invocation: RetroChildInvocation): string[] {\n return [\n 'retro',\n '--auto-extract',\n '--transcript',\n invocation.transcriptPath,\n '--window-start',\n String(invocation.windowStart),\n '--session-id',\n invocation.sessionId,\n ];\n}\n\n/**\n * The headless extractor only ever READS the digest — never writes, edits, or\n * runs shell. A read-only allow-list keeps the out-of-band child from mutating\n * the working tree or exfiltrating via a tool.\n */\nconst READ_ONLY_TOOLS = 'Read';\n\n/**\n * Env sentinel set on the headless child. The auth-working invocation does NOT\n * use `--bare`, so the child loads safeword's hooks; the retro Stop hook checks\n * this and early-returns, so the child can't re-trigger retro (infinite spawn) —\n * and stop-retro is the only hook that recursively spawns `claude -p`, so guarding\n * it is sufficient. NOT `--bare` (breaks cloud auth) and NOT `CLAUDE_CODE_CHILD_SESSION`\n * (already `1` in the normal tool context, so it can't distinguish a retro child).\n */\nexport const RETRO_CHILD_ENV = 'SAFEWORD_RETRO_CHILD';\n\n/** Whether the current process is a retro headless child (recursion guard). */\nexport function isRetroChild(env: Record<string, string | undefined>): boolean {\n return (env[RETRO_CHILD_ENV] ?? '').length > 0;\n}\n\n/**\n * Override for the command the Stop hook spawns to run the extraction CLI. When\n * set, the hook spawns this command verbatim instead of resolving `safeword retro\n * --auto-extract`. A test/advanced seam so the hook's invisibility can be proven\n * without launching a real headless `claude -p`.\n */\nexport const RETRO_EXTRACT_CMD_ENV = 'SAFEWORD_RETRO_EXTRACT_CMD';\n\nexport interface ExtractArgvOptions {\n /** Model for the headless extraction (cheap by default — see the caller). */\n model: string;\n /** System prompt appended to the extractor's default behavior. */\n systemPrompt: string;\n /** The task prompt (trailing positional) — instructs reading the digest. */\n prompt: string;\n}\n\n/**\n * Build the `claude` argv for a headless, isolated retro extraction. Print mode\n * + JSON output, a read-only tool set, and — load-bearing — NO `--bare`: `--bare`\n * skips the managed-provider setup a Claude cloud container authenticates through\n * (proven live: `--bare` → Authentication error). The returned array excludes the\n * `claude` binary itself; the runner supplies it.\n */\nexport function buildExtractArgv(options: ExtractArgvOptions): string[] {\n return [\n '-p',\n '--model',\n options.model,\n '--allowed-tools',\n READ_ONLY_TOOLS,\n '--output-format',\n 'json',\n '--append-system-prompt',\n options.systemPrompt,\n options.prompt,\n ];\n}\n\nexport interface CodexExtractArgvOptions {\n /** OpenAI model for the child `codex exec` process. */\n model: string;\n /** JSON schema path supplied to `--output-schema`. */\n schemaPath: string;\n /** JSON output path supplied to `-o`. */\n outputPath: string;\n /** Inline digest + task prompt. No Read/MCP tools are available. */\n prompt: string;\n}\n\n/**\n * Build the `codex exec` argv for a headless retro extraction. The digest is\n * inline in the prompt because `--output-schema` is ignored by Codex when the\n * task requires Read/MCP tool use. Hooks and MCP servers are explicitly disabled,\n * stdin is closed by the runner, and the sandbox is read-only.\n */\nexport function buildCodexExtractArgv(options: CodexExtractArgvOptions): string[] {\n return [\n 'exec',\n '--ignore-user-config',\n '--disable',\n 'hooks',\n '-c',\n 'mcp_servers={}',\n '--output-schema',\n options.schemaPath,\n '-o',\n options.outputPath,\n '--json',\n '--sandbox',\n 'read-only',\n '-m',\n options.model,\n options.prompt,\n ];\n}\n\n// The extraction rules, mirrored from templates/guides/retro.md: SAFEWORD's own\n// friction only, the constrained snake_case schema, no invention. The egress\n// guard sanitizes downstream, so the child writes plainly.\nconst EXTRACT_SYSTEM_PROMPT =\n \"You extract SAFEWORD's OWN friction from a session digest. Output ONLY a JSON \" +\n 'array (no prose). Each item: {\"category\":\"bug|rough-edge|gap\",\"title\":\"canonical ' +\n 'title of the SAFEWORD behavior\",\"safeword_surface\":\"a real safeword path: ' +\n 'hooks/…, packages/cli/…, templates/…, dist/…, or .safeword/…\",\"what_happened\":\"\",' +\n '\"why_friction\":\"\",\"repro\":\"in terms of safeword commands\"}. Rules: SAFEWORD\\'s ' +\n 'friction only (not the host project, not Claude Code itself); canonical ' +\n 'behavior-titles; do not invent; [] if none.';\n\nconst CODEX_RETRO_OUTPUT_SCHEMA = {\n type: 'object',\n additionalProperties: false,\n properties: {\n findings: {\n type: 'array',\n items: {\n type: 'object',\n additionalProperties: false,\n properties: {\n category: { type: 'string', enum: ['bug', 'rough-edge', 'gap'] },\n title: { type: 'string' },\n safeword_surface: { type: 'string' },\n what_happened: { type: 'string' },\n why_friction: { type: 'string' },\n repro: { type: 'string' },\n },\n required: [\n 'category',\n 'title',\n 'safeword_surface',\n 'what_happened',\n 'why_friction',\n 'repro',\n ],\n },\n },\n },\n required: ['findings'],\n};\n\n/** The task prompt: point the read-only child at the digest file. */\nfunction buildExtractPrompt(digestPath: string): string {\n return `Read the file ${digestPath} and extract SAFEWORD's own friction as the JSON array described. Output only the JSON array.`;\n}\n\nfunction buildCodexExtractPrompt(digest: string): string {\n return (\n `${EXTRACT_SYSTEM_PROMPT}\\n\\n` +\n 'Return only JSON matching the provided output schema: {\"findings\":[...]}. ' +\n 'Use an empty findings array when there is no safeword friction.\\n\\n' +\n `Transcript digest:\\n${digest}`\n );\n}\n\n/** Parse a findings array out of a `claude -p --output-format json` envelope. */\nfunction parseFindings(stdout: string): unknown[] {\n let envelopeResult: string;\n try {\n const parsed = JSON.parse(stdout) as { result?: unknown };\n if (typeof parsed.result !== 'string') return [];\n envelopeResult = parsed.result;\n } catch {\n return [];\n }\n // The model's text may be fenced (```json … ```); pull the first array literal.\n const match = envelopeResult.match(/\\[[\\S\\s]*\\]/);\n if (!match) return [];\n try {\n const findings = JSON.parse(match[0]) as unknown;\n return Array.isArray(findings) ? findings : [];\n } catch {\n return [];\n }\n}\n\nfunction parseCodexFindingsOutput(rawOutput: string): unknown[] | undefined {\n try {\n const parsed = JSON.parse(rawOutput) as { findings?: unknown };\n return Array.isArray(parsed.findings) ? parsed.findings : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** Result of spawning the headless extractor. */\nexport interface SpawnResult {\n code: number | null;\n stdout: string;\n}\n\n/** Dependencies for `runHeadlessExtraction` — the process boundaries, injected. */\nexport interface RunExtractionDeps {\n /** Spawn `claude` with argv; resolve with exit code + stdout. Awaited (sync). */\n spawn: (\n argv: string[],\n options: { cwd: string; env: Record<string, string | undefined> },\n ) => Promise<SpawnResult>;\n /** Persist the digest to a file the read-only child can Read; return its path. */\n writeDigest: (digest: string) => string;\n /** Base env for the child (the sentinel is added here). */\n env: Record<string, string | undefined>;\n /** Neutral cwd — NOT the user's project — so project hooks don't load. */\n cwd: string;\n /** Extraction model (cheap by default). */\n model?: string;\n}\n\nexport interface CodexSpawnOptions {\n cwd: string;\n env: Record<string, string | undefined>;\n stdio: 'ignore';\n}\n\n/** Dependencies for `runCodexHeadlessExtraction` — injected for tests. */\nexport interface RunCodexExtractionDeps {\n /** Spawn `codex` with argv; resolve after the child exits. Awaited (sync). */\n spawn: (argv: string[], options: CodexSpawnOptions) => Promise<SpawnResult>;\n /** Persist the output schema file read by `codex exec --output-schema`. */\n writeFile?: (path: string, content: string) => void;\n /** Read the `-o` JSON file written by `codex exec`. */\n readFile?: (path: string) => string;\n /** Base env for the child (the sentinel is added here). */\n env: Record<string, string | undefined>;\n /** Neutral cwd — NOT the user's project — so project hooks don't load. */\n cwd: string;\n /** Extraction model (OpenAI default for Codex). */\n model?: string;\n /** Test seam for deterministic schema/output paths. */\n schemaPath?: string;\n outputPath?: string;\n}\n\nexport interface CodexExtractionResult {\n /** True only when the child exited successfully and wrote schema-valid JSON. */\n ok: boolean;\n findings: unknown[];\n}\n\n/**\n * Run the retro extraction in a separate, isolated headless `claude -p` session\n * and return the raw findings array. Synchronous (awaits the spawn) and\n * fail-OPEN: any error — non-zero exit, unparseable output, a spawn throw —\n * yields `[]` and never throws, so the Stop hook that wraps this stays silent and\n * never blocks. Spawn contract: the digest is the input (referenced in the\n * prompt), the child runs from the neutral cwd, and the child env carries\n * `SAFEWORD_RETRO_CHILD=1` (recursion guard).\n */\nexport async function runHeadlessExtraction(\n transcript: string,\n dependencies: RunExtractionDeps,\n): Promise<unknown[]> {\n try {\n const digestPath = dependencies.writeDigest(buildDigest(transcript));\n const argv = buildExtractArgv({\n model: dependencies.model ?? DEFAULT_RETRO_MODEL,\n systemPrompt: EXTRACT_SYSTEM_PROMPT,\n prompt: buildExtractPrompt(digestPath),\n });\n const { code, stdout } = await dependencies.spawn(argv, {\n cwd: dependencies.cwd,\n env: { ...dependencies.env, [RETRO_CHILD_ENV]: '1' },\n });\n if (code !== 0) return []; // fail-open on a failed extraction\n return parseFindings(stdout);\n } catch {\n return []; // fail-open on any spawn/IO error\n }\n}\n\n/**\n * Run Codex retro extraction in a separate `codex exec` child and return the raw\n * findings array with a success bit. Synchronous (awaits the child) and\n * fail-OPEN: any non-zero exit, missing output, malformed JSON, or\n * spawn/read/write error yields `{ ok:false, findings:[] }`.\n */\nexport async function runCodexHeadlessExtractionChecked(\n transcript: string,\n dependencies: RunCodexExtractionDeps,\n): Promise<CodexExtractionResult> {\n try {\n const schemaPath = dependencies.schemaPath ?? nodePath.join(dependencies.cwd, 'schema.json');\n const outputPath = dependencies.outputPath ?? nodePath.join(dependencies.cwd, 'output.json');\n const writeFile = dependencies.writeFile ?? writeFileSync;\n const readFile = dependencies.readFile ?? ((path: string) => readFileSync(path, 'utf8'));\n\n writeFile(schemaPath, JSON.stringify(CODEX_RETRO_OUTPUT_SCHEMA));\n const argv = buildCodexExtractArgv({\n model: dependencies.model ?? DEFAULT_CODEX_RETRO_MODEL,\n schemaPath,\n outputPath,\n prompt: buildCodexExtractPrompt(buildDigest(transcript)),\n });\n const { code } = await dependencies.spawn(argv, {\n cwd: dependencies.cwd,\n env: { ...dependencies.env, [RETRO_CHILD_ENV]: '1' },\n stdio: 'ignore',\n });\n if (code !== 0) return { ok: false, findings: [] };\n const findings = parseCodexFindingsOutput(readFile(outputPath));\n return findings === undefined ? { ok: false, findings: [] } : { ok: true, findings };\n } catch {\n return { ok: false, findings: [] };\n }\n}\n\n/**\n * Backward-compatible fail-open wrapper for call sites that only need findings.\n */\nexport async function runCodexHeadlessExtraction(\n transcript: string,\n dependencies: RunCodexExtractionDeps,\n): Promise<unknown[]> {\n return (await runCodexHeadlessExtractionChecked(transcript, dependencies)).findings;\n}\n\n// A tool-result body is kept whole only when it's short OR carries a friction\n// signal (errors/failures/gate blocks are exactly what retro mines); larger\n// non-signal bodies are dropped so they can't crowd out text + tool-use names.\nconst SHORT_RESULT = 600;\n/** Cap for a tool_use's serialized input in the digest — names it enough, not the whole payload. */\nconst TOOL_USE_INPUT_CAP = 300;\nconst FRICTION = /error|fail|block|denied|stale|drift|guard|FAILED/i;\n\ninterface ContentItem {\n type?: string;\n text?: string;\n name?: string;\n input?: unknown;\n content?: unknown;\n}\n\ninterface TranscriptEntry {\n type?: string;\n message?: { role?: string; content?: ContentItem[] | string };\n}\n\nfunction lineFor(item: ContentItem, role: string): string | undefined {\n if (item.type === 'text' && item.text) return `[${role}] ${item.text}`;\n if (item.type === 'tool_use' && item.name)\n return `[tool_use] ${item.name}: ${JSON.stringify(item.input ?? {}).slice(0, TOOL_USE_INPUT_CAP)}`;\n if (item.type === 'tool_result') {\n const text =\n typeof item.content === 'string' ? item.content : JSON.stringify(item.content ?? '');\n // Keep short results, or longer ones only when they carry a friction signal.\n if (text && (text.length < SHORT_RESULT || FRICTION.test(text)))\n return `[tool_result] ${text.slice(0, SHORT_RESULT)}`;\n }\n return undefined;\n}\n\n/**\n * Reduce a raw JSONL transcript to a signal-dense digest under `cap` chars:\n * user/assistant text + tool-use names + short/error-ish tool results. Oversized\n * non-signal tool-result bodies are omitted (not just truncated), so they can't\n * crowd out the markers the extractor needs. Malformed lines are skipped, never\n * thrown — a hook must not crash on a partial transcript.\n */\nexport function buildDigest(rawTranscript: string, cap: number = DIGEST_CAP): string {\n const out: string[] = [];\n for (const raw of iterateJsonlEntries(rawTranscript)) {\n const entry = raw as TranscriptEntry;\n const role = entry.message?.role ?? entry.type ?? 'entry';\n const content = entry.message?.content;\n if (typeof content === 'string') {\n out.push(`[${role}] ${content}`);\n } else if (Array.isArray(content)) {\n for (const item of content) {\n const rendered = lineFor(item, role);\n if (rendered !== undefined) out.push(rendered);\n }\n }\n }\n const digest = out.join('\\n');\n return digest.length > cap ? digest.slice(0, cap) : digest;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveConfiguredLaneDirectory
|
|
3
|
+
} from "./chunk-FTMQT2V3.js";
|
|
4
|
+
|
|
1
5
|
// src/utils/feature-source.ts
|
|
2
6
|
import { existsSync, readdirSync } from "fs";
|
|
3
7
|
import nodePath from "path";
|
|
@@ -16,12 +20,17 @@ function collectExecutableFeatureFiles(cwd, fileName) {
|
|
|
16
20
|
);
|
|
17
21
|
}
|
|
18
22
|
function collectExecutableFeatureDirectories(cwd) {
|
|
19
|
-
|
|
23
|
+
const directories = [
|
|
20
24
|
nodePath.join(cwd, "features"),
|
|
21
25
|
...WORKSPACE_FEATURE_ROOTS.flatMap(
|
|
22
26
|
(root) => collectWorkspaceFeatureDirectories(nodePath.join(cwd, root))
|
|
23
27
|
)
|
|
24
28
|
];
|
|
29
|
+
const configured = resolveConfiguredLaneDirectory(cwd, "features");
|
|
30
|
+
if (configured !== void 0 && !directories.includes(configured)) {
|
|
31
|
+
directories.push(configured);
|
|
32
|
+
}
|
|
33
|
+
return directories;
|
|
25
34
|
}
|
|
26
35
|
function collectWorkspaceFeatureDirectories(workspaceDirectory) {
|
|
27
36
|
if (!existsSync(workspaceDirectory)) return [];
|
|
@@ -69,15 +78,15 @@ function parseFeatureScenarios(featureContent) {
|
|
|
69
78
|
const featureName = feature.name || "Feature";
|
|
70
79
|
return collectFeatureChildren(feature.children, featureName, featureTags);
|
|
71
80
|
}
|
|
72
|
-
function
|
|
73
|
-
const
|
|
81
|
+
function parseFeatureLineageReferences(featureContent) {
|
|
82
|
+
const byKind = { ac: /* @__PURE__ */ new Set(), rule: /* @__PURE__ */ new Set() };
|
|
74
83
|
for (const scenario of parseFeatureScenarios(featureContent)) {
|
|
75
84
|
for (const tag of scenario.tags) {
|
|
76
|
-
const ref =
|
|
77
|
-
if (ref !== void 0)
|
|
85
|
+
const ref = parseLineageReferenceFromTag(tag);
|
|
86
|
+
if (ref !== void 0) byKind[ref.kind].add(ref.reference);
|
|
78
87
|
}
|
|
79
88
|
}
|
|
80
|
-
return [...
|
|
89
|
+
return { ac: [...byKind.ac], rule: [...byKind.rule] };
|
|
81
90
|
}
|
|
82
91
|
function findGherkinLintIssues(featureContent, options = {}) {
|
|
83
92
|
const issues = findTextLintIssues(featureContent, options.filePath);
|
|
@@ -103,14 +112,16 @@ function findGherkinLintIssues(featureContent, options = {}) {
|
|
|
103
112
|
}
|
|
104
113
|
function findFeatureLineageIssues(featureContent) {
|
|
105
114
|
return parseFeatureScenarios(featureContent).flatMap((scenario) => {
|
|
106
|
-
const references =
|
|
115
|
+
const references = uniqueLineageReferences(scenario.tags);
|
|
107
116
|
if (references.length === 0) {
|
|
108
|
-
return [
|
|
117
|
+
return [
|
|
118
|
+
`Scenario "${scenario.title}" is missing lineage; add exactly one @<jtbd>.AC# or @<jtbd>.R# tag.`
|
|
119
|
+
];
|
|
109
120
|
}
|
|
110
121
|
if (references.length > 1) {
|
|
111
122
|
const tagList = references.map((reference) => `@${reference}`).join(", ");
|
|
112
123
|
return [
|
|
113
|
-
`Scenario "${scenario.title}" has multiple lineage tags after inheritance (${tagList}); keep exactly one @<jtbd>.AC# tag.`
|
|
124
|
+
`Scenario "${scenario.title}" has multiple lineage tags after inheritance (${tagList}); keep exactly one @<jtbd>.AC# or @<jtbd>.R# tag.`
|
|
114
125
|
];
|
|
115
126
|
}
|
|
116
127
|
return [];
|
|
@@ -278,7 +289,10 @@ function findDocumentLintIssues(feature) {
|
|
|
278
289
|
for (const child of feature.children) {
|
|
279
290
|
if (child.scenario) scenarios.push(child.scenario);
|
|
280
291
|
if (child.rule) {
|
|
281
|
-
issues.push(
|
|
292
|
+
issues.push(
|
|
293
|
+
...findDuplicateTagIssues(child.rule.tags, `Rule "${child.rule.name}"`),
|
|
294
|
+
...findRuleNameTagIssues(child.rule)
|
|
295
|
+
);
|
|
282
296
|
for (const ruleChild of child.rule.children) {
|
|
283
297
|
if (ruleChild.scenario) scenarios.push(ruleChild.scenario);
|
|
284
298
|
}
|
|
@@ -390,6 +404,19 @@ function collectPlaceholders(text, variables) {
|
|
|
390
404
|
}
|
|
391
405
|
}
|
|
392
406
|
}
|
|
407
|
+
function findRuleNameTagIssues(rule) {
|
|
408
|
+
const ruleReference = rule.tags.map((tag) => parseLineageReferenceFromTag(tag.name)).find((reference) => reference?.kind === "rule");
|
|
409
|
+
if (ruleReference === void 0) return [];
|
|
410
|
+
const nameToken = rule.name.trim().split(/[\s—–]+/, 1)[0] ?? "";
|
|
411
|
+
if (nameToken === ruleReference.reference) return [];
|
|
412
|
+
return [
|
|
413
|
+
issue(
|
|
414
|
+
"rule-name-tag-mismatch",
|
|
415
|
+
`Rule "${rule.name}" is tagged @${ruleReference.reference} but its name starts with a different id; make the name's first token match the tag.`,
|
|
416
|
+
rule.location.line
|
|
417
|
+
)
|
|
418
|
+
];
|
|
419
|
+
}
|
|
393
420
|
function findDuplicateTagIssues(tags, owner) {
|
|
394
421
|
const seen = /* @__PURE__ */ new Set();
|
|
395
422
|
const issues = [];
|
|
@@ -439,11 +466,23 @@ function parseAcReferenceFromTag(tag) {
|
|
|
439
466
|
if (!match) return void 0;
|
|
440
467
|
return `${match[1] ?? ""}.AC${match[2] ?? ""}`;
|
|
441
468
|
}
|
|
442
|
-
function
|
|
469
|
+
function parseRuleReferenceFromTag(tag) {
|
|
470
|
+
const match = /^@?(\S+)\.R(\d+)$/.exec(tag.trim());
|
|
471
|
+
if (!match) return void 0;
|
|
472
|
+
return `${match[1] ?? ""}.R${match[2] ?? ""}`;
|
|
473
|
+
}
|
|
474
|
+
function parseLineageReferenceFromTag(tag) {
|
|
475
|
+
const acReference = parseAcReferenceFromTag(tag);
|
|
476
|
+
if (acReference !== void 0) return { kind: "ac", reference: acReference };
|
|
477
|
+
const ruleReference = parseRuleReferenceFromTag(tag);
|
|
478
|
+
if (ruleReference !== void 0) return { kind: "rule", reference: ruleReference };
|
|
479
|
+
return void 0;
|
|
480
|
+
}
|
|
481
|
+
function uniqueLineageReferences(tags) {
|
|
443
482
|
const references = /* @__PURE__ */ new Set();
|
|
444
483
|
for (const tag of tags) {
|
|
445
|
-
const ref =
|
|
446
|
-
if (ref !== void 0) references.add(ref);
|
|
484
|
+
const ref = parseLineageReferenceFromTag(tag);
|
|
485
|
+
if (ref !== void 0) references.add(ref.reference);
|
|
447
486
|
}
|
|
448
487
|
return [...references];
|
|
449
488
|
}
|
|
@@ -457,8 +496,9 @@ export {
|
|
|
457
496
|
collectExecutableFeatureFiles,
|
|
458
497
|
FeatureParseError,
|
|
459
498
|
parseFeatureScenarios,
|
|
460
|
-
|
|
499
|
+
parseFeatureLineageReferences,
|
|
461
500
|
findGherkinLintIssues,
|
|
462
|
-
findFeatureLineageIssues
|
|
501
|
+
findFeatureLineageIssues,
|
|
502
|
+
parseLineageReferenceFromTag
|
|
463
503
|
};
|
|
464
|
-
//# sourceMappingURL=chunk-
|
|
504
|
+
//# sourceMappingURL=chunk-6BEAVNR2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/feature-source.ts","../src/utils/gherkin-feature.ts"],"sourcesContent":["import { existsSync, readdirSync } from 'node:fs';\nimport nodePath from 'node:path';\n\nimport { resolveConfiguredLaneDirectory } from './configured-paths.js';\n\nconst WORKSPACE_FEATURE_ROOTS = ['packages', 'apps', 'libs', 'modules'] as const;\n\n/** Ticket folder `ID-slug` -> `slug`; legacy `ID` -> `ID`. */\nfunction slugFromTicketFolder(ticketFolder: string): string {\n const dashIndex = ticketFolder.indexOf('-');\n return dashIndex === -1 ? ticketFolder : ticketFolder.slice(dashIndex + 1);\n}\n\n/**\n * Find the executable `.feature` file for a ticket by slug. The search mirrors\n * the Cucumber lane: all feature files under root `features/`, then under each\n * direct workspace package's `features/` directory in stable order.\n */\nexport function findFeatureSourcePath(cwd: string, ticketFolder: string): string | undefined {\n const fileName = `${slugFromTicketFolder(ticketFolder)}.feature`;\n return collectExecutableFeatureFiles(cwd, fileName)[0];\n}\n\nexport function collectExecutableFeatureFiles(cwd: string, fileName?: string): string[] {\n return collectExecutableFeatureDirectories(cwd).flatMap(directory =>\n findFeatureFiles(directory, fileName),\n );\n}\n\nfunction collectExecutableFeatureDirectories(cwd: string): string[] {\n const directories = [\n nodePath.join(cwd, 'features'),\n ...WORKSPACE_FEATURE_ROOTS.flatMap(root =>\n collectWorkspaceFeatureDirectories(nodePath.join(cwd, root)),\n ),\n ];\n\n // paths.features AUGMENTS the defaults (ticket 56JCFZ) — relocated/host\n // lanes become readable without abandoning root features/.\n const configured = resolveConfiguredLaneDirectory(cwd, 'features');\n if (configured !== undefined && !directories.includes(configured)) {\n directories.push(configured);\n }\n return directories;\n}\n\nfunction collectWorkspaceFeatureDirectories(workspaceDirectory: string): string[] {\n if (!existsSync(workspaceDirectory)) return [];\n return readdirSync(workspaceDirectory, { withFileTypes: true })\n .filter(entry => entry.isDirectory())\n .map(entry => nodePath.join(workspaceDirectory, entry.name, 'features'));\n}\n\nfunction findFeatureFiles(directory: string, fileName?: string): string[] {\n let entries;\n try {\n entries = readdirSync(directory, { withFileTypes: true });\n } catch {\n return [];\n }\n\n const matches: string[] = [];\n for (const entry of entries) {\n const absolute = nodePath.join(directory, entry.name);\n if (entry.isDirectory()) {\n matches.push(...findFeatureFiles(absolute, fileName));\n } else if (entry.isFile() && isMatchingFeatureFile(entry.name, fileName)) {\n matches.push(absolute);\n }\n }\n return matches.toSorted((a, b) => a.localeCompare(b));\n}\n\nfunction isMatchingFeatureFile(entryName: string, fileName: string | undefined): boolean {\n if (!entryName.endsWith('.feature')) return false;\n return fileName === undefined || entryName === fileName;\n}\n","import { AstBuilder, GherkinClassicTokenMatcher, Parser } from '@cucumber/gherkin';\nimport {\n type DataTable,\n type DocString as GherkinDocumentString,\n type Examples,\n type Feature,\n type FeatureChild,\n IdGenerator,\n type RuleChild,\n type Scenario,\n type Step,\n type TableRow,\n type Tag,\n} from '@cucumber/messages';\n\nimport type { ParsedScenario } from './test-skeleton.js';\n\nexport interface ParsedFeatureScenario extends ParsedScenario {\n /** Effective tags after applying Feature/Rule/Scenario inheritance. */\n tags: string[];\n}\n\nexport class FeatureParseError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'FeatureParseError';\n }\n}\n\nexport interface GherkinLintIssue {\n line?: number;\n message: string;\n rule: string;\n}\n\nexport function parseFeatureScenarios(featureContent: string): ParsedFeatureScenario[] {\n const document = parseFeature(featureContent);\n const feature = document.feature;\n if (feature === undefined) return [];\n\n const featureTags = feature.tags.map(tag => tag.name);\n const featureName = feature.name || 'Feature';\n return collectFeatureChildren(feature.children, featureName, featureTags);\n}\n\nexport function parseFeatureAcReferences(featureContent: string): string[] {\n return parseFeatureLineageReferences(featureContent).ac;\n}\n\nexport function parseFeatureRuleReferences(featureContent: string): string[] {\n return parseFeatureLineageReferences(featureContent).rule;\n}\n\n/**\n * The AC and numbered-Rule lineage references a feature's scenarios carry,\n * split by kind, from a single `parseFeatureScenarios` pass. Callers that need\n * both kinds (coverage) read them off one AST build instead of parsing twice.\n */\nexport function parseFeatureLineageReferences(featureContent: string): {\n ac: string[];\n rule: string[];\n} {\n const byKind: Record<LineageKind, Set<string>> = { ac: new Set(), rule: new Set() };\n for (const scenario of parseFeatureScenarios(featureContent)) {\n for (const tag of scenario.tags) {\n const ref = parseLineageReferenceFromTag(tag);\n if (ref !== undefined) byKind[ref.kind].add(ref.reference);\n }\n }\n return { ac: [...byKind.ac], rule: [...byKind.rule] };\n}\n\nexport function findGherkinLintIssues(\n featureContent: string,\n options: { filePath?: string } = {},\n): GherkinLintIssue[] {\n const issues = findTextLintIssues(featureContent, options.filePath);\n if (featureContent.trim() === '') {\n return [...issues, issue('no-empty-file', 'Feature file is empty.')];\n }\n\n try {\n const document = parseFeature(featureContent);\n const feature = document.feature;\n if (feature === undefined) {\n return [...issues, issue('no-feature', 'Feature file has no Feature.')];\n }\n return [...issues, ...findDocumentLintIssues(feature)];\n } catch (error: unknown) {\n if (error instanceof FeatureParseError) {\n return [\n ...issues,\n issue('parse', `Invalid Gherkin syntax: ${error.message}`, parseErrorLine(error.message)),\n ];\n }\n throw error;\n }\n}\n\nexport function findFeatureLineageIssues(featureContent: string): string[] {\n return parseFeatureScenarios(featureContent).flatMap(scenario => {\n const references = uniqueLineageReferences(scenario.tags);\n if (references.length === 0) {\n return [\n `Scenario \"${scenario.title}\" is missing lineage; add exactly one @<jtbd>.AC# or @<jtbd>.R# tag.`,\n ];\n }\n if (references.length > 1) {\n const tagList = references.map(reference => `@${reference}`).join(', ');\n return [\n `Scenario \"${scenario.title}\" has multiple lineage tags after inheritance (${tagList}); keep exactly one @<jtbd>.AC# or @<jtbd>.R# tag.`,\n ];\n }\n return [];\n });\n}\n\nfunction parseFeature(featureContent: string) {\n const parser = new Parser(new AstBuilder(IdGenerator.uuid()), new GherkinClassicTokenMatcher());\n try {\n return parser.parse(featureContent);\n } catch (error: unknown) {\n throw new FeatureParseError(formatParseError(error));\n }\n}\n\nfunction collectFeatureChildren(\n children: readonly FeatureChild[],\n featureName: string,\n inheritedTags: readonly string[],\n): ParsedFeatureScenario[] {\n const scenarios: ParsedFeatureScenario[] = [];\n let featureBackgroundSteps: readonly Step[] = [];\n for (const child of children) {\n if (child.background) {\n featureBackgroundSteps = child.background.steps;\n }\n if (child.scenario) {\n scenarios.push(\n ...toParsedScenarios(child.scenario, featureName, inheritedTags, featureBackgroundSteps),\n );\n }\n if (child.rule) {\n const ruleTags = [...inheritedTags, ...child.rule.tags.map(tag => tag.name)];\n scenarios.push(\n ...collectRuleChildren(\n child.rule.children,\n child.rule.name,\n ruleTags,\n featureBackgroundSteps,\n ),\n );\n }\n }\n return scenarios;\n}\n\nfunction collectRuleChildren(\n children: readonly RuleChild[],\n ruleName: string,\n inheritedTags: readonly string[],\n featureBackgroundSteps: readonly Step[],\n): ParsedFeatureScenario[] {\n const scenarios: ParsedFeatureScenario[] = [];\n let ruleBackgroundSteps: readonly Step[] = [];\n for (const child of children) {\n if (child.background) {\n ruleBackgroundSteps = child.background.steps;\n }\n if (child.scenario) {\n scenarios.push(\n ...toParsedScenarios(child.scenario, ruleName, inheritedTags, [\n ...featureBackgroundSteps,\n ...ruleBackgroundSteps,\n ]),\n );\n }\n }\n return scenarios;\n}\n\nfunction toParsedScenarios(\n scenario: Scenario,\n rule: string,\n inheritedTags: readonly string[],\n inheritedSteps: readonly Step[],\n): ParsedFeatureScenario[] {\n if (scenario.examples.length === 0)\n return [toParsedScenario(scenario, rule, inheritedTags, inheritedSteps)];\n return scenario.examples.flatMap(example =>\n toParsedExampleScenarios(scenario, rule, inheritedTags, inheritedSteps, example),\n );\n}\n\nfunction toParsedScenario(\n scenario: Scenario,\n rule: string,\n inheritedTags: readonly string[],\n inheritedSteps: readonly Step[],\n row?: ExampleRow,\n): ParsedFeatureScenario {\n const title = row === undefined ? scenario.name : exampleTitle(scenario.name, row);\n return {\n rule,\n title,\n steps: [...inheritedSteps, ...scenario.steps].flatMap(step => renderStep(step, row)),\n tags: [...inheritedTags, ...scenario.tags.map(tag => tag.name), ...(row?.tags ?? [])],\n };\n}\n\ninterface ExampleRow {\n cells: Map<string, string>;\n summary: string;\n tags: string[];\n}\n\nfunction toParsedExampleScenarios(\n scenario: Scenario,\n rule: string,\n inheritedTags: readonly string[],\n inheritedSteps: readonly Step[],\n example: Examples,\n): ParsedFeatureScenario[] {\n const headers = example.tableHeader?.cells.map(cell => cell.value) ?? [];\n return example.tableBody.map(row =>\n toParsedScenario(scenario, rule, inheritedTags, inheritedSteps, {\n cells: rowCells(headers, row),\n summary: rowSummary(headers, row),\n tags: example.tags.map(tag => tag.name),\n }),\n );\n}\n\nfunction renderStep(step: Step, row: ExampleRow | undefined): string[] {\n return [\n interpolate(`${step.keyword.trim()} ${step.text}`.trim(), row),\n ...renderStepArgument(step, row),\n ];\n}\n\nfunction renderStepArgument(step: Step, row: ExampleRow | undefined): string[] {\n if (step.dataTable) return renderDataTable(step.dataTable, row);\n if (step.docString) return renderDocumentString(step.docString, row);\n return [];\n}\n\nfunction renderDataTable(dataTable: DataTable, row: ExampleRow | undefined): string[] {\n return dataTable.rows.map(\n tableRow => `| ${tableRow.cells.map(cell => interpolate(cell.value, row)).join(' | ')} |`,\n );\n}\n\nfunction renderDocumentString(\n documentString: GherkinDocumentString,\n row: ExampleRow | undefined,\n): string[] {\n const opener =\n documentString.mediaType === undefined\n ? documentString.delimiter\n : `${documentString.delimiter}${documentString.mediaType}`;\n return [\n opener,\n ...interpolate(documentString.content, row).split('\\n'),\n documentString.delimiter,\n ];\n}\n\nfunction rowCells(headers: readonly string[], row: TableRow): Map<string, string> {\n return new Map(headers.map((header, index) => [header, row.cells[index]?.value ?? '']));\n}\n\nfunction rowSummary(headers: readonly string[], row: TableRow): string {\n return headers.map((header, index) => `${header}=${row.cells[index]?.value ?? ''}`).join(', ');\n}\n\nfunction exampleTitle(title: string, row: ExampleRow): string {\n const interpolated = interpolate(title, row);\n return row.summary === '' ? interpolated : `${interpolated} (${row.summary})`;\n}\n\nfunction interpolate(text: string, row: ExampleRow | undefined): string {\n if (row === undefined) return text;\n let interpolated = text;\n for (const [key, value] of row.cells) {\n interpolated = interpolated.split(`<${key}>`).join(value);\n }\n return interpolated;\n}\n\nfunction findTextLintIssues(content: string, filePath: string | undefined): GherkinLintIssue[] {\n const issues: GherkinLintIssue[] = [];\n if (filePath !== undefined) {\n const fileName = filePath.split(/[\\\\/]/).pop() ?? filePath;\n if (!isKebabCaseFeatureFileName(fileName)) {\n issues.push(\n issue(\n 'file-name',\n `Feature file name should be kebab-case, e.g. \"${kebabFileName(fileName)}\".`,\n ),\n );\n }\n }\n if (content !== '' && !content.endsWith('\\n')) {\n issues.push(issue('new-line-at-eof', 'Feature file should end with a newline.'));\n }\n\n let isPreviousBlank = false;\n for (const [index, line] of content.split('\\n').entries()) {\n const lineNumber = index + 1;\n if (/[ \\t]$/.test(line)) {\n issues.push(issue('no-trailing-spaces', 'Line has trailing whitespace.', lineNumber));\n }\n const isBlank = line.trim() === '';\n if (isBlank && isPreviousBlank) {\n issues.push(\n issue('no-multiple-empty-lines', 'Avoid multiple consecutive blank lines.', lineNumber),\n );\n }\n isPreviousBlank = isBlank;\n }\n return issues;\n}\n\nfunction findDocumentLintIssues(feature: Feature): GherkinLintIssue[] {\n const issues: GherkinLintIssue[] = [];\n if (feature.name.trim() === '') {\n issues.push(issue('no-unnamed-features', 'Feature must have a name.', feature.location.line));\n }\n issues.push(...findDuplicateTagIssues(feature.tags, 'Feature'));\n\n const scenarios: Scenario[] = [];\n for (const child of feature.children) {\n if (child.scenario) scenarios.push(child.scenario);\n if (child.rule) {\n issues.push(\n ...findDuplicateTagIssues(child.rule.tags, `Rule \"${child.rule.name}\"`),\n ...findRuleNameTagIssues(child.rule),\n );\n for (const ruleChild of child.rule.children) {\n if (ruleChild.scenario) scenarios.push(ruleChild.scenario);\n }\n }\n }\n\n if (scenarios.length === 0) {\n issues.push(\n issue('no-files-without-scenarios', 'Feature file must contain at least one scenario.'),\n );\n }\n issues.push(...findScenarioLintIssues(scenarios));\n return issues;\n}\n\nfunction findScenarioLintIssues(scenarios: readonly Scenario[]): GherkinLintIssue[] {\n const issues: GherkinLintIssue[] = [];\n const firstLineByName = new Map<string, number>();\n for (const scenario of scenarios) {\n const name = scenario.name.trim();\n if (name === '') {\n issues.push(\n issue('no-unnamed-scenarios', 'Scenario must have a name.', scenario.location.line),\n );\n } else if (firstLineByName.has(name)) {\n issues.push(\n issue(\n 'no-dupe-scenario-names',\n `Scenario name \"${name}\" is duplicated in this feature.`,\n scenario.location.line,\n ),\n );\n } else {\n firstLineByName.set(name, scenario.location.line);\n }\n\n if (scenario.keyword.toLowerCase().includes('outline') && scenario.examples.length === 0) {\n issues.push(\n issue(\n 'no-scenario-outlines-without-examples',\n 'Scenario Outline must include Examples.',\n scenario.location.line,\n ),\n );\n }\n issues.push(\n ...findScenarioOutlineVariableIssues(scenario),\n ...findDuplicateTagIssues(scenario.tags, `Scenario \"${scenario.name}\"`),\n ...scenario.examples.flatMap(example =>\n findDuplicateTagIssues(example.tags, `Examples \"${example.name}\"`),\n ),\n );\n }\n return issues;\n}\n\nfunction findScenarioOutlineVariableIssues(scenario: Scenario): GherkinLintIssue[] {\n if (!scenario.keyword.toLowerCase().includes('outline') || scenario.examples.length === 0) {\n return [];\n }\n\n const usedVariables = collectScenarioOutlineVariables(scenario);\n return scenario.examples.flatMap(example =>\n findExamplesVariableIssues(scenario, example, usedVariables),\n );\n}\n\nfunction findExamplesVariableIssues(\n scenario: Scenario,\n example: Examples,\n usedVariables: ReadonlySet<string>,\n): GherkinLintIssue[] {\n const headers = example.tableHeader?.cells.map(cell => cell.value.trim()).filter(Boolean) ?? [];\n const headerSet = new Set(headers);\n const exampleName = example.name || 'Examples';\n\n const missing = [...usedVariables]\n .filter(variable => !headerSet.has(variable))\n .map(variable =>\n issue(\n 'no-unused-variables',\n `Scenario Outline \"${scenario.name}\" uses <${variable}> but Examples \"${exampleName}\" has no \"${variable}\" column.`,\n example.location.line,\n ),\n );\n\n const unused = headers\n .filter(header => !usedVariables.has(header))\n .map(header =>\n issue(\n 'no-unused-variables',\n `Examples \"${exampleName}\" defines <${header}> but Scenario Outline \"${scenario.name}\" never uses it.`,\n example.tableHeader?.location.line ?? example.location.line,\n ),\n );\n\n return [...missing, ...unused];\n}\n\nfunction collectScenarioOutlineVariables(scenario: Scenario): Set<string> {\n const variables = new Set<string>();\n collectPlaceholders(scenario.name, variables);\n collectPlaceholders(scenario.description, variables);\n for (const step of scenario.steps) {\n collectPlaceholders(step.text, variables);\n if (step.docString) collectPlaceholders(step.docString.content, variables);\n if (step.dataTable) {\n for (const row of step.dataTable.rows) {\n for (const cell of row.cells) {\n collectPlaceholders(cell.value, variables);\n }\n }\n }\n }\n return variables;\n}\n\nfunction collectPlaceholders(text: string, variables: Set<string>): void {\n let start = -1;\n for (let index = 0; index < text.length; index += 1) {\n const character = text[index];\n if (character === '<') {\n start = index + 1;\n } else if (character === '>' && start !== -1) {\n const variable = text.slice(start, index).trim();\n if (variable) variables.add(variable);\n start = -1;\n }\n }\n}\n\n/**\n * A Rule block carrying a rule lineage tag must repeat that id as its name's\n * first token (the tag is authoritative; the name token is the human-readable\n * copy). Unnumbered grouping blocks carry no rule tag and are exempt.\n */\nfunction findRuleNameTagIssues(rule: {\n name: string;\n tags: readonly Tag[];\n location: { line: number };\n}): GherkinLintIssue[] {\n const ruleReference = rule.tags\n .map(tag => parseLineageReferenceFromTag(tag.name))\n .find(reference => reference?.kind === 'rule');\n if (ruleReference === undefined) return [];\n\n // The id ends at the first whitespace or dash separator. Split on em/en dash\n // too so a name glued as `demo.DEV1.R1—foo` still yields the id, not a false\n // mismatch; a plain `-` stays part of the token (kebab slugs contain it).\n const nameToken = rule.name.trim().split(/[\\s—–]+/, 1)[0] ?? '';\n if (nameToken === ruleReference.reference) return [];\n return [\n issue(\n 'rule-name-tag-mismatch',\n `Rule \"${rule.name}\" is tagged @${ruleReference.reference} but its name starts with a different id; make the name's first token match the tag.`,\n rule.location.line,\n ),\n ];\n}\n\nfunction findDuplicateTagIssues(tags: readonly Tag[], owner: string): GherkinLintIssue[] {\n const seen = new Set<string>();\n const issues: GherkinLintIssue[] = [];\n for (const tag of tags) {\n if (seen.has(tag.name)) {\n issues.push(\n issue('no-duplicate-tags', `${owner} has duplicate tag ${tag.name}.`, tag.location.line),\n );\n }\n seen.add(tag.name);\n }\n return issues;\n}\n\nfunction issue(rule: string, message: string, line?: number): GherkinLintIssue {\n return line === undefined ? { rule, message } : { rule, message, line };\n}\n\nfunction kebabFileName(fileName: string): string {\n const stem = fileName.endsWith('.feature') ? fileName.slice(0, -'.feature'.length) : fileName;\n return [\n stem\n .replaceAll('_', '-')\n .replaceAll(/[^a-zA-Z0-9]+/gu, '-')\n .replaceAll(/^-|-$/gu, '')\n .toLowerCase(),\n '.feature',\n ].join('');\n}\n\nfunction isKebabCaseFeatureFileName(fileName: string): boolean {\n if (!fileName.endsWith('.feature')) return false;\n const stem = fileName.slice(0, -'.feature'.length);\n if (stem === '') return false;\n return stem.split('-').every(segment => isLowercaseAlphaNumeric(segment));\n}\n\nfunction isLowercaseAlphaNumeric(segment: string): boolean {\n if (segment === '') return false;\n for (const character of segment) {\n const code = character.codePointAt(0);\n if (code === undefined) return false;\n const isDigit = code >= 48 && code <= 57;\n const isLowercaseLetter = code >= 97 && code <= 122;\n if (!isDigit && !isLowercaseLetter) return false;\n }\n return true;\n}\n\nfunction parseErrorLine(message: string): number | undefined {\n const match = /\\((\\d+):\\d+\\)/.exec(message);\n return match?.[1] === undefined ? undefined : Number(match[1]);\n}\n\ntype LineageKind = 'ac' | 'rule';\n\nexport interface LineageReference {\n kind: LineageKind;\n reference: string;\n}\n\nfunction parseAcReferenceFromTag(tag: string): string | undefined {\n const match = /^@?(\\S+?)\\.AC(\\d+)(?:\\.|$)/.exec(tag.trim());\n if (!match) return undefined;\n return `${match[1] ?? ''}.AC${match[2] ?? ''}`;\n}\n\nfunction parseRuleReferenceFromTag(tag: string): string | undefined {\n // Anchor on the *terminal* `.R<n>` (greedy prefix) so the parsed id matches\n // the spec-side declaration, which keeps the whole heading token\n // (`scenario-coverage.ts` `isRuleId` = `/\\.R\\d+$/`). A lazy first-match would\n // read `@feat.R1.R2` as `feat.R1` while the spec declares `feat.R1.R2`,\n // splitting one corpus into a spurious uncovered+orphan pair. Rule tags carry\n // no `.<scenario_name>` tail (unlike AC titles), so terminal anchoring is safe.\n const match = /^@?(\\S+)\\.R(\\d+)$/.exec(tag.trim());\n if (!match) return undefined;\n return `${match[1] ?? ''}.R${match[2] ?? ''}`;\n}\n\n/** Parse a tag's lineage reference. An AC match wins over a rule-shaped prefix\n * so persona codes like `R` stay unambiguous: `@feat.R1.AC1` is AC1 of JTBD\n * `feat.R1`, never rule `feat.R1`. */\nexport function parseLineageReferenceFromTag(tag: string): LineageReference | undefined {\n const acReference = parseAcReferenceFromTag(tag);\n if (acReference !== undefined) return { kind: 'ac', reference: acReference };\n const ruleReference = parseRuleReferenceFromTag(tag);\n if (ruleReference !== undefined) return { kind: 'rule', reference: ruleReference };\n return undefined;\n}\n\nfunction uniqueLineageReferences(tags: readonly string[]): string[] {\n const references = new Set<string>();\n for (const tag of tags) {\n const ref = parseLineageReferenceFromTag(tag);\n if (ref !== undefined) references.add(ref.reference);\n }\n return [...references];\n}\n\nfunction formatParseError(error: unknown): string {\n if (error instanceof Error) return error.message.replaceAll('\\n', ' ');\n return String(error);\n}\n"],"mappings":";;;;;AAAA,SAAS,YAAY,mBAAmB;AACxC,OAAO,cAAc;AAIrB,IAAM,0BAA0B,CAAC,YAAY,QAAQ,QAAQ,SAAS;AAGtE,SAAS,qBAAqB,cAA8B;AAC1D,QAAM,YAAY,aAAa,QAAQ,GAAG;AAC1C,SAAO,cAAc,KAAK,eAAe,aAAa,MAAM,YAAY,CAAC;AAC3E;AAOO,SAAS,sBAAsB,KAAa,cAA0C;AAC3F,QAAM,WAAW,GAAG,qBAAqB,YAAY,CAAC;AACtD,SAAO,8BAA8B,KAAK,QAAQ,EAAE,CAAC;AACvD;AAEO,SAAS,8BAA8B,KAAa,UAA6B;AACtF,SAAO,oCAAoC,GAAG,EAAE;AAAA,IAAQ,eACtD,iBAAiB,WAAW,QAAQ;AAAA,EACtC;AACF;AAEA,SAAS,oCAAoC,KAAuB;AAClE,QAAM,cAAc;AAAA,IAClB,SAAS,KAAK,KAAK,UAAU;AAAA,IAC7B,GAAG,wBAAwB;AAAA,MAAQ,UACjC,mCAAmC,SAAS,KAAK,KAAK,IAAI,CAAC;AAAA,IAC7D;AAAA,EACF;AAIA,QAAM,aAAa,+BAA+B,KAAK,UAAU;AACjE,MAAI,eAAe,UAAa,CAAC,YAAY,SAAS,UAAU,GAAG;AACjE,gBAAY,KAAK,UAAU;AAAA,EAC7B;AACA,SAAO;AACT;AAEA,SAAS,mCAAmC,oBAAsC;AAChF,MAAI,CAAC,WAAW,kBAAkB,EAAG,QAAO,CAAC;AAC7C,SAAO,YAAY,oBAAoB,EAAE,eAAe,KAAK,CAAC,EAC3D,OAAO,WAAS,MAAM,YAAY,CAAC,EACnC,IAAI,WAAS,SAAS,KAAK,oBAAoB,MAAM,MAAM,UAAU,CAAC;AAC3E;AAEA,SAAS,iBAAiB,WAAmB,UAA6B;AACxE,MAAI;AACJ,MAAI;AACF,cAAU,YAAY,WAAW,EAAE,eAAe,KAAK,CAAC;AAAA,EAC1D,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,UAAoB,CAAC;AAC3B,aAAW,SAAS,SAAS;AAC3B,UAAM,WAAW,SAAS,KAAK,WAAW,MAAM,IAAI;AACpD,QAAI,MAAM,YAAY,GAAG;AACvB,cAAQ,KAAK,GAAG,iBAAiB,UAAU,QAAQ,CAAC;AAAA,IACtD,WAAW,MAAM,OAAO,KAAK,sBAAsB,MAAM,MAAM,QAAQ,GAAG;AACxE,cAAQ,KAAK,QAAQ;AAAA,IACvB;AAAA,EACF;AACA,SAAO,QAAQ,SAAS,CAAC,GAAG,MAAM,EAAE,cAAc,CAAC,CAAC;AACtD;AAEA,SAAS,sBAAsB,WAAmB,UAAuC;AACvF,MAAI,CAAC,UAAU,SAAS,UAAU,EAAG,QAAO;AAC5C,SAAO,aAAa,UAAa,cAAc;AACjD;;;AC5EA,SAAS,YAAY,4BAA4B,cAAc;AAC/D;AAAA,EAME;AAAA,OAMK;AASA,IAAM,oBAAN,cAAgC,MAAM;AAAA,EAC3C,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAQO,SAAS,sBAAsB,gBAAiD;AACrF,QAAM,WAAW,aAAa,cAAc;AAC5C,QAAM,UAAU,SAAS;AACzB,MAAI,YAAY,OAAW,QAAO,CAAC;AAEnC,QAAM,cAAc,QAAQ,KAAK,IAAI,SAAO,IAAI,IAAI;AACpD,QAAM,cAAc,QAAQ,QAAQ;AACpC,SAAO,uBAAuB,QAAQ,UAAU,aAAa,WAAW;AAC1E;AAeO,SAAS,8BAA8B,gBAG5C;AACA,QAAM,SAA2C,EAAE,IAAI,oBAAI,IAAI,GAAG,MAAM,oBAAI,IAAI,EAAE;AAClF,aAAW,YAAY,sBAAsB,cAAc,GAAG;AAC5D,eAAW,OAAO,SAAS,MAAM;AAC/B,YAAM,MAAM,6BAA6B,GAAG;AAC5C,UAAI,QAAQ,OAAW,QAAO,IAAI,IAAI,EAAE,IAAI,IAAI,SAAS;AAAA,IAC3D;AAAA,EACF;AACA,SAAO,EAAE,IAAI,CAAC,GAAG,OAAO,EAAE,GAAG,MAAM,CAAC,GAAG,OAAO,IAAI,EAAE;AACtD;AAEO,SAAS,sBACd,gBACA,UAAiC,CAAC,GACd;AACpB,QAAM,SAAS,mBAAmB,gBAAgB,QAAQ,QAAQ;AAClE,MAAI,eAAe,KAAK,MAAM,IAAI;AAChC,WAAO,CAAC,GAAG,QAAQ,MAAM,iBAAiB,wBAAwB,CAAC;AAAA,EACrE;AAEA,MAAI;AACF,UAAM,WAAW,aAAa,cAAc;AAC5C,UAAM,UAAU,SAAS;AACzB,QAAI,YAAY,QAAW;AACzB,aAAO,CAAC,GAAG,QAAQ,MAAM,cAAc,8BAA8B,CAAC;AAAA,IACxE;AACA,WAAO,CAAC,GAAG,QAAQ,GAAG,uBAAuB,OAAO,CAAC;AAAA,EACvD,SAAS,OAAgB;AACvB,QAAI,iBAAiB,mBAAmB;AACtC,aAAO;AAAA,QACL,GAAG;AAAA,QACH,MAAM,SAAS,2BAA2B,MAAM,OAAO,IAAI,eAAe,MAAM,OAAO,CAAC;AAAA,MAC1F;AAAA,IACF;AACA,UAAM;AAAA,EACR;AACF;AAEO,SAAS,yBAAyB,gBAAkC;AACzE,SAAO,sBAAsB,cAAc,EAAE,QAAQ,cAAY;AAC/D,UAAM,aAAa,wBAAwB,SAAS,IAAI;AACxD,QAAI,WAAW,WAAW,GAAG;AAC3B,aAAO;AAAA,QACL,aAAa,SAAS,KAAK;AAAA,MAC7B;AAAA,IACF;AACA,QAAI,WAAW,SAAS,GAAG;AACzB,YAAM,UAAU,WAAW,IAAI,eAAa,IAAI,SAAS,EAAE,EAAE,KAAK,IAAI;AACtE,aAAO;AAAA,QACL,aAAa,SAAS,KAAK,kDAAkD,OAAO;AAAA,MACtF;AAAA,IACF;AACA,WAAO,CAAC;AAAA,EACV,CAAC;AACH;AAEA,SAAS,aAAa,gBAAwB;AAC5C,QAAM,SAAS,IAAI,OAAO,IAAI,WAAW,YAAY,KAAK,CAAC,GAAG,IAAI,2BAA2B,CAAC;AAC9F,MAAI;AACF,WAAO,OAAO,MAAM,cAAc;AAAA,EACpC,SAAS,OAAgB;AACvB,UAAM,IAAI,kBAAkB,iBAAiB,KAAK,CAAC;AAAA,EACrD;AACF;AAEA,SAAS,uBACP,UACA,aACA,eACyB;AACzB,QAAM,YAAqC,CAAC;AAC5C,MAAI,yBAA0C,CAAC;AAC/C,aAAW,SAAS,UAAU;AAC5B,QAAI,MAAM,YAAY;AACpB,+BAAyB,MAAM,WAAW;AAAA,IAC5C;AACA,QAAI,MAAM,UAAU;AAClB,gBAAU;AAAA,QACR,GAAG,kBAAkB,MAAM,UAAU,aAAa,eAAe,sBAAsB;AAAA,MACzF;AAAA,IACF;AACA,QAAI,MAAM,MAAM;AACd,YAAM,WAAW,CAAC,GAAG,eAAe,GAAG,MAAM,KAAK,KAAK,IAAI,SAAO,IAAI,IAAI,CAAC;AAC3E,gBAAU;AAAA,QACR,GAAG;AAAA,UACD,MAAM,KAAK;AAAA,UACX,MAAM,KAAK;AAAA,UACX;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,oBACP,UACA,UACA,eACA,wBACyB;AACzB,QAAM,YAAqC,CAAC;AAC5C,MAAI,sBAAuC,CAAC;AAC5C,aAAW,SAAS,UAAU;AAC5B,QAAI,MAAM,YAAY;AACpB,4BAAsB,MAAM,WAAW;AAAA,IACzC;AACA,QAAI,MAAM,UAAU;AAClB,gBAAU;AAAA,QACR,GAAG,kBAAkB,MAAM,UAAU,UAAU,eAAe;AAAA,UAC5D,GAAG;AAAA,UACH,GAAG;AAAA,QACL,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,kBACP,UACA,MACA,eACA,gBACyB;AACzB,MAAI,SAAS,SAAS,WAAW;AAC/B,WAAO,CAAC,iBAAiB,UAAU,MAAM,eAAe,cAAc,CAAC;AACzE,SAAO,SAAS,SAAS;AAAA,IAAQ,aAC/B,yBAAyB,UAAU,MAAM,eAAe,gBAAgB,OAAO;AAAA,EACjF;AACF;AAEA,SAAS,iBACP,UACA,MACA,eACA,gBACA,KACuB;AACvB,QAAM,QAAQ,QAAQ,SAAY,SAAS,OAAO,aAAa,SAAS,MAAM,GAAG;AACjF,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,OAAO,CAAC,GAAG,gBAAgB,GAAG,SAAS,KAAK,EAAE,QAAQ,UAAQ,WAAW,MAAM,GAAG,CAAC;AAAA,IACnF,MAAM,CAAC,GAAG,eAAe,GAAG,SAAS,KAAK,IAAI,SAAO,IAAI,IAAI,GAAG,GAAI,KAAK,QAAQ,CAAC,CAAE;AAAA,EACtF;AACF;AAQA,SAAS,yBACP,UACA,MACA,eACA,gBACA,SACyB;AACzB,QAAM,UAAU,QAAQ,aAAa,MAAM,IAAI,UAAQ,KAAK,KAAK,KAAK,CAAC;AACvE,SAAO,QAAQ,UAAU;AAAA,IAAI,SAC3B,iBAAiB,UAAU,MAAM,eAAe,gBAAgB;AAAA,MAC9D,OAAO,SAAS,SAAS,GAAG;AAAA,MAC5B,SAAS,WAAW,SAAS,GAAG;AAAA,MAChC,MAAM,QAAQ,KAAK,IAAI,SAAO,IAAI,IAAI;AAAA,IACxC,CAAC;AAAA,EACH;AACF;AAEA,SAAS,WAAW,MAAY,KAAuC;AACrE,SAAO;AAAA,IACL,YAAY,GAAG,KAAK,QAAQ,KAAK,CAAC,IAAI,KAAK,IAAI,GAAG,KAAK,GAAG,GAAG;AAAA,IAC7D,GAAG,mBAAmB,MAAM,GAAG;AAAA,EACjC;AACF;AAEA,SAAS,mBAAmB,MAAY,KAAuC;AAC7E,MAAI,KAAK,UAAW,QAAO,gBAAgB,KAAK,WAAW,GAAG;AAC9D,MAAI,KAAK,UAAW,QAAO,qBAAqB,KAAK,WAAW,GAAG;AACnE,SAAO,CAAC;AACV;AAEA,SAAS,gBAAgB,WAAsB,KAAuC;AACpF,SAAO,UAAU,KAAK;AAAA,IACpB,cAAY,KAAK,SAAS,MAAM,IAAI,UAAQ,YAAY,KAAK,OAAO,GAAG,CAAC,EAAE,KAAK,KAAK,CAAC;AAAA,EACvF;AACF;AAEA,SAAS,qBACP,gBACA,KACU;AACV,QAAM,SACJ,eAAe,cAAc,SACzB,eAAe,YACf,GAAG,eAAe,SAAS,GAAG,eAAe,SAAS;AAC5D,SAAO;AAAA,IACL;AAAA,IACA,GAAG,YAAY,eAAe,SAAS,GAAG,EAAE,MAAM,IAAI;AAAA,IACtD,eAAe;AAAA,EACjB;AACF;AAEA,SAAS,SAAS,SAA4B,KAAoC;AAChF,SAAO,IAAI,IAAI,QAAQ,IAAI,CAAC,QAAQ,UAAU,CAAC,QAAQ,IAAI,MAAM,KAAK,GAAG,SAAS,EAAE,CAAC,CAAC;AACxF;AAEA,SAAS,WAAW,SAA4B,KAAuB;AACrE,SAAO,QAAQ,IAAI,CAAC,QAAQ,UAAU,GAAG,MAAM,IAAI,IAAI,MAAM,KAAK,GAAG,SAAS,EAAE,EAAE,EAAE,KAAK,IAAI;AAC/F;AAEA,SAAS,aAAa,OAAe,KAAyB;AAC5D,QAAM,eAAe,YAAY,OAAO,GAAG;AAC3C,SAAO,IAAI,YAAY,KAAK,eAAe,GAAG,YAAY,KAAK,IAAI,OAAO;AAC5E;AAEA,SAAS,YAAY,MAAc,KAAqC;AACtE,MAAI,QAAQ,OAAW,QAAO;AAC9B,MAAI,eAAe;AACnB,aAAW,CAAC,KAAK,KAAK,KAAK,IAAI,OAAO;AACpC,mBAAe,aAAa,MAAM,IAAI,GAAG,GAAG,EAAE,KAAK,KAAK;AAAA,EAC1D;AACA,SAAO;AACT;AAEA,SAAS,mBAAmB,SAAiB,UAAkD;AAC7F,QAAM,SAA6B,CAAC;AACpC,MAAI,aAAa,QAAW;AAC1B,UAAM,WAAW,SAAS,MAAM,OAAO,EAAE,IAAI,KAAK;AAClD,QAAI,CAAC,2BAA2B,QAAQ,GAAG;AACzC,aAAO;AAAA,QACL;AAAA,UACE;AAAA,UACA,iDAAiD,cAAc,QAAQ,CAAC;AAAA,QAC1E;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,MAAI,YAAY,MAAM,CAAC,QAAQ,SAAS,IAAI,GAAG;AAC7C,WAAO,KAAK,MAAM,mBAAmB,yCAAyC,CAAC;AAAA,EACjF;AAEA,MAAI,kBAAkB;AACtB,aAAW,CAAC,OAAO,IAAI,KAAK,QAAQ,MAAM,IAAI,EAAE,QAAQ,GAAG;AACzD,UAAM,aAAa,QAAQ;AAC3B,QAAI,SAAS,KAAK,IAAI,GAAG;AACvB,aAAO,KAAK,MAAM,sBAAsB,iCAAiC,UAAU,CAAC;AAAA,IACtF;AACA,UAAM,UAAU,KAAK,KAAK,MAAM;AAChC,QAAI,WAAW,iBAAiB;AAC9B,aAAO;AAAA,QACL,MAAM,2BAA2B,2CAA2C,UAAU;AAAA,MACxF;AAAA,IACF;AACA,sBAAkB;AAAA,EACpB;AACA,SAAO;AACT;AAEA,SAAS,uBAAuB,SAAsC;AACpE,QAAM,SAA6B,CAAC;AACpC,MAAI,QAAQ,KAAK,KAAK,MAAM,IAAI;AAC9B,WAAO,KAAK,MAAM,uBAAuB,6BAA6B,QAAQ,SAAS,IAAI,CAAC;AAAA,EAC9F;AACA,SAAO,KAAK,GAAG,uBAAuB,QAAQ,MAAM,SAAS,CAAC;AAE9D,QAAM,YAAwB,CAAC;AAC/B,aAAW,SAAS,QAAQ,UAAU;AACpC,QAAI,MAAM,SAAU,WAAU,KAAK,MAAM,QAAQ;AACjD,QAAI,MAAM,MAAM;AACd,aAAO;AAAA,QACL,GAAG,uBAAuB,MAAM,KAAK,MAAM,SAAS,MAAM,KAAK,IAAI,GAAG;AAAA,QACtE,GAAG,sBAAsB,MAAM,IAAI;AAAA,MACrC;AACA,iBAAW,aAAa,MAAM,KAAK,UAAU;AAC3C,YAAI,UAAU,SAAU,WAAU,KAAK,UAAU,QAAQ;AAAA,MAC3D;AAAA,IACF;AAAA,EACF;AAEA,MAAI,UAAU,WAAW,GAAG;AAC1B,WAAO;AAAA,MACL,MAAM,8BAA8B,kDAAkD;AAAA,IACxF;AAAA,EACF;AACA,SAAO,KAAK,GAAG,uBAAuB,SAAS,CAAC;AAChD,SAAO;AACT;AAEA,SAAS,uBAAuB,WAAoD;AAClF,QAAM,SAA6B,CAAC;AACpC,QAAM,kBAAkB,oBAAI,IAAoB;AAChD,aAAW,YAAY,WAAW;AAChC,UAAM,OAAO,SAAS,KAAK,KAAK;AAChC,QAAI,SAAS,IAAI;AACf,aAAO;AAAA,QACL,MAAM,wBAAwB,8BAA8B,SAAS,SAAS,IAAI;AAAA,MACpF;AAAA,IACF,WAAW,gBAAgB,IAAI,IAAI,GAAG;AACpC,aAAO;AAAA,QACL;AAAA,UACE;AAAA,UACA,kBAAkB,IAAI;AAAA,UACtB,SAAS,SAAS;AAAA,QACpB;AAAA,MACF;AAAA,IACF,OAAO;AACL,sBAAgB,IAAI,MAAM,SAAS,SAAS,IAAI;AAAA,IAClD;AAEA,QAAI,SAAS,QAAQ,YAAY,EAAE,SAAS,SAAS,KAAK,SAAS,SAAS,WAAW,GAAG;AACxF,aAAO;AAAA,QACL;AAAA,UACE;AAAA,UACA;AAAA,UACA,SAAS,SAAS;AAAA,QACpB;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,MACL,GAAG,kCAAkC,QAAQ;AAAA,MAC7C,GAAG,uBAAuB,SAAS,MAAM,aAAa,SAAS,IAAI,GAAG;AAAA,MACtE,GAAG,SAAS,SAAS;AAAA,QAAQ,aAC3B,uBAAuB,QAAQ,MAAM,aAAa,QAAQ,IAAI,GAAG;AAAA,MACnE;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,kCAAkC,UAAwC;AACjF,MAAI,CAAC,SAAS,QAAQ,YAAY,EAAE,SAAS,SAAS,KAAK,SAAS,SAAS,WAAW,GAAG;AACzF,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,gBAAgB,gCAAgC,QAAQ;AAC9D,SAAO,SAAS,SAAS;AAAA,IAAQ,aAC/B,2BAA2B,UAAU,SAAS,aAAa;AAAA,EAC7D;AACF;AAEA,SAAS,2BACP,UACA,SACA,eACoB;AACpB,QAAM,UAAU,QAAQ,aAAa,MAAM,IAAI,UAAQ,KAAK,MAAM,KAAK,CAAC,EAAE,OAAO,OAAO,KAAK,CAAC;AAC9F,QAAM,YAAY,IAAI,IAAI,OAAO;AACjC,QAAM,cAAc,QAAQ,QAAQ;AAEpC,QAAM,UAAU,CAAC,GAAG,aAAa,EAC9B,OAAO,cAAY,CAAC,UAAU,IAAI,QAAQ,CAAC,EAC3C;AAAA,IAAI,cACH;AAAA,MACE;AAAA,MACA,qBAAqB,SAAS,IAAI,WAAW,QAAQ,mBAAmB,WAAW,aAAa,QAAQ;AAAA,MACxG,QAAQ,SAAS;AAAA,IACnB;AAAA,EACF;AAEF,QAAM,SAAS,QACZ,OAAO,YAAU,CAAC,cAAc,IAAI,MAAM,CAAC,EAC3C;AAAA,IAAI,YACH;AAAA,MACE;AAAA,MACA,aAAa,WAAW,cAAc,MAAM,2BAA2B,SAAS,IAAI;AAAA,MACpF,QAAQ,aAAa,SAAS,QAAQ,QAAQ,SAAS;AAAA,IACzD;AAAA,EACF;AAEF,SAAO,CAAC,GAAG,SAAS,GAAG,MAAM;AAC/B;AAEA,SAAS,gCAAgC,UAAiC;AACxE,QAAM,YAAY,oBAAI,IAAY;AAClC,sBAAoB,SAAS,MAAM,SAAS;AAC5C,sBAAoB,SAAS,aAAa,SAAS;AACnD,aAAW,QAAQ,SAAS,OAAO;AACjC,wBAAoB,KAAK,MAAM,SAAS;AACxC,QAAI,KAAK,UAAW,qBAAoB,KAAK,UAAU,SAAS,SAAS;AACzE,QAAI,KAAK,WAAW;AAClB,iBAAW,OAAO,KAAK,UAAU,MAAM;AACrC,mBAAW,QAAQ,IAAI,OAAO;AAC5B,8BAAoB,KAAK,OAAO,SAAS;AAAA,QAC3C;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,oBAAoB,MAAc,WAA8B;AACvE,MAAI,QAAQ;AACZ,WAAS,QAAQ,GAAG,QAAQ,KAAK,QAAQ,SAAS,GAAG;AACnD,UAAM,YAAY,KAAK,KAAK;AAC5B,QAAI,cAAc,KAAK;AACrB,cAAQ,QAAQ;AAAA,IAClB,WAAW,cAAc,OAAO,UAAU,IAAI;AAC5C,YAAM,WAAW,KAAK,MAAM,OAAO,KAAK,EAAE,KAAK;AAC/C,UAAI,SAAU,WAAU,IAAI,QAAQ;AACpC,cAAQ;AAAA,IACV;AAAA,EACF;AACF;AAOA,SAAS,sBAAsB,MAIR;AACrB,QAAM,gBAAgB,KAAK,KACxB,IAAI,SAAO,6BAA6B,IAAI,IAAI,CAAC,EACjD,KAAK,eAAa,WAAW,SAAS,MAAM;AAC/C,MAAI,kBAAkB,OAAW,QAAO,CAAC;AAKzC,QAAM,YAAY,KAAK,KAAK,KAAK,EAAE,MAAM,WAAW,CAAC,EAAE,CAAC,KAAK;AAC7D,MAAI,cAAc,cAAc,UAAW,QAAO,CAAC;AACnD,SAAO;AAAA,IACL;AAAA,MACE;AAAA,MACA,SAAS,KAAK,IAAI,gBAAgB,cAAc,SAAS;AAAA,MACzD,KAAK,SAAS;AAAA,IAChB;AAAA,EACF;AACF;AAEA,SAAS,uBAAuB,MAAsB,OAAmC;AACvF,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,SAA6B,CAAC;AACpC,aAAW,OAAO,MAAM;AACtB,QAAI,KAAK,IAAI,IAAI,IAAI,GAAG;AACtB,aAAO;AAAA,QACL,MAAM,qBAAqB,GAAG,KAAK,sBAAsB,IAAI,IAAI,KAAK,IAAI,SAAS,IAAI;AAAA,MACzF;AAAA,IACF;AACA,SAAK,IAAI,IAAI,IAAI;AAAA,EACnB;AACA,SAAO;AACT;AAEA,SAAS,MAAM,MAAc,SAAiB,MAAiC;AAC7E,SAAO,SAAS,SAAY,EAAE,MAAM,QAAQ,IAAI,EAAE,MAAM,SAAS,KAAK;AACxE;AAEA,SAAS,cAAc,UAA0B;AAC/C,QAAM,OAAO,SAAS,SAAS,UAAU,IAAI,SAAS,MAAM,GAAG,CAAC,WAAW,MAAM,IAAI;AACrF,SAAO;AAAA,IACL,KACG,WAAW,KAAK,GAAG,EACnB,WAAW,mBAAmB,GAAG,EACjC,WAAW,WAAW,EAAE,EACxB,YAAY;AAAA,IACf;AAAA,EACF,EAAE,KAAK,EAAE;AACX;AAEA,SAAS,2BAA2B,UAA2B;AAC7D,MAAI,CAAC,SAAS,SAAS,UAAU,EAAG,QAAO;AAC3C,QAAM,OAAO,SAAS,MAAM,GAAG,CAAC,WAAW,MAAM;AACjD,MAAI,SAAS,GAAI,QAAO;AACxB,SAAO,KAAK,MAAM,GAAG,EAAE,MAAM,aAAW,wBAAwB,OAAO,CAAC;AAC1E;AAEA,SAAS,wBAAwB,SAA0B;AACzD,MAAI,YAAY,GAAI,QAAO;AAC3B,aAAW,aAAa,SAAS;AAC/B,UAAM,OAAO,UAAU,YAAY,CAAC;AACpC,QAAI,SAAS,OAAW,QAAO;AAC/B,UAAM,UAAU,QAAQ,MAAM,QAAQ;AACtC,UAAM,oBAAoB,QAAQ,MAAM,QAAQ;AAChD,QAAI,CAAC,WAAW,CAAC,kBAAmB,QAAO;AAAA,EAC7C;AACA,SAAO;AACT;AAEA,SAAS,eAAe,SAAqC;AAC3D,QAAM,QAAQ,gBAAgB,KAAK,OAAO;AAC1C,SAAO,QAAQ,CAAC,MAAM,SAAY,SAAY,OAAO,MAAM,CAAC,CAAC;AAC/D;AASA,SAAS,wBAAwB,KAAiC;AAChE,QAAM,QAAQ,6BAA6B,KAAK,IAAI,KAAK,CAAC;AAC1D,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,GAAG,MAAM,CAAC,KAAK,EAAE,MAAM,MAAM,CAAC,KAAK,EAAE;AAC9C;AAEA,SAAS,0BAA0B,KAAiC;AAOlE,QAAM,QAAQ,oBAAoB,KAAK,IAAI,KAAK,CAAC;AACjD,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,MAAM,CAAC,KAAK,EAAE;AAC7C;AAKO,SAAS,6BAA6B,KAA2C;AACtF,QAAM,cAAc,wBAAwB,GAAG;AAC/C,MAAI,gBAAgB,OAAW,QAAO,EAAE,MAAM,MAAM,WAAW,YAAY;AAC3E,QAAM,gBAAgB,0BAA0B,GAAG;AACnD,MAAI,kBAAkB,OAAW,QAAO,EAAE,MAAM,QAAQ,WAAW,cAAc;AACjF,SAAO;AACT;AAEA,SAAS,wBAAwB,MAAmC;AAClE,QAAM,aAAa,oBAAI,IAAY;AACnC,aAAW,OAAO,MAAM;AACtB,UAAM,MAAM,6BAA6B,GAAG;AAC5C,QAAI,QAAQ,OAAW,YAAW,IAAI,IAAI,SAAS;AAAA,EACrD;AACA,SAAO,CAAC,GAAG,UAAU;AACvB;AAEA,SAAS,iBAAiB,OAAwB;AAChD,MAAI,iBAAiB,MAAO,QAAO,MAAM,QAAQ,WAAW,MAAM,GAAG;AACrE,SAAO,OAAO,KAAK;AACrB;","names":[]}
|