@rsaglobaltech/specgate 0.8.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +98 -0
- package/README.md +8 -8
- package/bin/specgate.ts +13 -2
- package/dist/bin/specgate.js +11 -1
- package/dist/bin/specgate.js.map +1 -1
- package/dist/packages/core/src/application/GenerateAgentPromptUseCase.js +56 -0
- package/dist/packages/core/src/application/GenerateAgentPromptUseCase.js.map +1 -1
- package/dist/packages/core/src/application/ValidateProjectUseCase.js +54 -0
- package/dist/packages/core/src/application/ValidateProjectUseCase.js.map +1 -1
- package/dist/packages/core/src/domain/AdversarialProbe.js +75 -0
- package/dist/packages/core/src/domain/AdversarialProbe.js.map +1 -0
- package/dist/packages/core/src/domain/AgentPrompt.js +52 -3
- package/dist/packages/core/src/domain/AgentPrompt.js.map +1 -1
- package/dist/packages/core/src/domain/DeclaredArtifacts.js +25 -0
- package/dist/packages/core/src/domain/DeclaredArtifacts.js.map +1 -1
- package/dist/packages/core/src/domain/DoneVerification.js +68 -0
- package/dist/packages/core/src/domain/DoneVerification.js.map +1 -0
- package/dist/packages/core/src/domain/GateCommandIntegrity.js +139 -0
- package/dist/packages/core/src/domain/GateCommandIntegrity.js.map +1 -0
- package/dist/packages/core/src/domain/HarnessConfig.js +6 -0
- package/dist/packages/core/src/domain/HarnessConfig.js.map +1 -1
- package/dist/packages/core/src/domain/MultiStack.js +96 -0
- package/dist/packages/core/src/domain/MultiStack.js.map +1 -0
- package/dist/packages/core/src/domain/Precedents.js +96 -0
- package/dist/packages/core/src/domain/Precedents.js.map +1 -0
- package/dist/packages/core/src/domain/RequirementRemoval.js +161 -0
- package/dist/packages/core/src/domain/RequirementRemoval.js.map +1 -0
- package/dist/packages/core/src/domain/ScenarioCoverage.js +178 -0
- package/dist/packages/core/src/domain/ScenarioCoverage.js.map +1 -0
- package/dist/packages/core/src/domain/SpecSections.js +206 -0
- package/dist/packages/core/src/domain/SpecSections.js.map +1 -0
- package/dist/packages/core/src/domain/TestCommand.js +82 -0
- package/dist/packages/core/src/domain/TestCommand.js.map +1 -0
- package/dist/packages/core/src/domain/TraceabilityFormat.js +37 -1
- package/dist/packages/core/src/domain/TraceabilityFormat.js.map +1 -1
- package/dist/packages/core/src/infrastructure/HarnessConfigFile.js +31 -0
- package/dist/packages/core/src/infrastructure/HarnessConfigFile.js.map +1 -1
- package/dist/packages/core/test/unit/adversarial-probe.test.js +81 -0
- package/dist/packages/core/test/unit/adversarial-probe.test.js.map +1 -0
- package/dist/packages/core/test/unit/done-verification.test.js +83 -0
- package/dist/packages/core/test/unit/done-verification.test.js.map +1 -0
- package/dist/packages/core/test/unit/gate-command-integrity.test.js +114 -0
- package/dist/packages/core/test/unit/gate-command-integrity.test.js.map +1 -0
- package/dist/packages/core/test/unit/multi-stack.test.js +86 -0
- package/dist/packages/core/test/unit/multi-stack.test.js.map +1 -0
- package/dist/packages/core/test/unit/precedents.test.js +99 -0
- package/dist/packages/core/test/unit/precedents.test.js.map +1 -0
- package/dist/packages/core/test/unit/requirement-removal.test.js +124 -0
- package/dist/packages/core/test/unit/requirement-removal.test.js.map +1 -0
- package/dist/packages/core/test/unit/scenario-coverage.test.js +146 -0
- package/dist/packages/core/test/unit/scenario-coverage.test.js.map +1 -0
- package/dist/packages/core/test/unit/spec-sections.test.js +146 -0
- package/dist/packages/core/test/unit/spec-sections.test.js.map +1 -0
- package/dist/packages/core/test/unit/test-command.test.js +81 -0
- package/dist/packages/core/test/unit/test-command.test.js.map +1 -0
- package/dist/packages/core/test/unit/validate-project.test.js +21 -0
- package/dist/packages/core/test/unit/validate-project.test.js.map +1 -1
- package/dist/scripts/agents/commands.js +1 -1
- package/dist/scripts/agents/commands.js.map +1 -1
- package/dist/scripts/agents/init.js +1 -1
- package/dist/scripts/agents/init.js.map +1 -1
- package/dist/scripts/change/instructions.js +2 -2
- package/dist/scripts/change/instructions.js.map +1 -1
- package/dist/scripts/cli/commands/change/ChangeCommand.js +2 -2
- package/dist/scripts/cli/commands/change/ChangeCommand.js.map +1 -1
- package/dist/scripts/cli/commands/config/CiInitCommand.js +1 -1
- package/dist/scripts/cli/commands/config/CiInitCommand.js.map +1 -1
- package/dist/scripts/cli/commands/harness/RunCommand.js +203 -4
- package/dist/scripts/cli/commands/harness/RunCommand.js.map +1 -1
- package/dist/scripts/cli/commands/pack/InferPackCommand.js +331 -31
- package/dist/scripts/cli/commands/pack/InferPackCommand.js.map +1 -1
- package/dist/scripts/cli/commands/project/AdoptCommand.js +81 -4
- package/dist/scripts/cli/commands/project/AdoptCommand.js.map +1 -1
- package/dist/scripts/cli/commands/project/InitProjectCommand.js +245 -45
- package/dist/scripts/cli/commands/project/InitProjectCommand.js.map +1 -1
- package/dist/scripts/cli/commands/quality/DoctorCommand.js +20 -2
- package/dist/scripts/cli/commands/quality/DoctorCommand.js.map +1 -1
- package/dist/scripts/cli/commands/quality/FixCommand.js +1 -1
- package/dist/scripts/cli/commands/quality/FixCommand.js.map +1 -1
- package/dist/scripts/cli/commands/quality/ReportCommand.js +1 -1
- package/dist/scripts/cli/commands/quality/ReportCommand.js.map +1 -1
- package/dist/scripts/cli/commands/quality/ValidateSpecsCommand.js +340 -18
- package/dist/scripts/cli/commands/quality/ValidateSpecsCommand.js.map +1 -1
- package/dist/scripts/cli/commands/spec/DoneCommand.js +108 -0
- package/dist/scripts/cli/commands/spec/DoneCommand.js.map +1 -1
- package/dist/scripts/cli/commands/spec/PlanCommand.js +7 -1
- package/dist/scripts/cli/commands/spec/PlanCommand.js.map +1 -1
- package/dist/scripts/cli/commands/spec/ReqCommand.js +112 -2
- package/dist/scripts/cli/commands/spec/ReqCommand.js.map +1 -1
- package/dist/scripts/cli/commands/spec/StatusCommand.js +2 -2
- package/dist/scripts/cli/commands/spec/StatusCommand.js.map +1 -1
- package/dist/scripts/docs/build-site.js +145 -35
- package/dist/scripts/docs/build-site.js.map +1 -1
- package/dist/scripts/docs/components.js +208 -0
- package/dist/scripts/docs/components.js.map +1 -0
- package/dist/scripts/docs/highlight.js +180 -0
- package/dist/scripts/docs/highlight.js.map +1 -0
- package/dist/scripts/docs/icons.js +63 -0
- package/dist/scripts/docs/icons.js.map +1 -0
- package/dist/scripts/docs/nav.js +9 -3
- package/dist/scripts/docs/nav.js.map +1 -1
- package/dist/scripts/gen_mcp_tools.js +22 -2
- package/dist/scripts/gen_mcp_tools.js.map +1 -1
- package/dist/scripts/harness/init.js +101 -40
- package/dist/scripts/harness/init.js.map +1 -1
- package/dist/scripts/harness/worktree-lock.js +161 -0
- package/dist/scripts/harness/worktree-lock.js.map +1 -0
- package/dist/scripts/infer_pack.js +4 -1
- package/dist/scripts/infer_pack.js.map +1 -1
- package/dist/scripts/lib/surface.js +35 -7
- package/dist/scripts/lib/surface.js.map +1 -1
- package/package.json +5 -2
- package/templates/adopt/AI_RULES.md.tpl +1 -1
- package/templates/adopt/spec.md.tpl +4 -4
- package/templates/base/spec.md.tpl +12 -0
- package/templates/ci/azure.yml.tpl +2 -2
- package/templates/ci/github.yml.tpl +2 -2
- package/templates/ci/gitlab.yml.tpl +1 -1
- package/templates/ci/jenkins.tpl +1 -1
- package/templates/contracts/test-strategy.md.tpl +2 -2
- package/templates/harness/prompt-prefix.md.tpl +2 -5
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Taking a requirement back out (Fase 4.1).
|
|
4
|
+
*
|
|
5
|
+
* `req add` had no counterpart. When the matrix got into a state somebody
|
|
6
|
+
* wanted to undo, two of three cold adoptions repaired it by hand with a
|
|
7
|
+
* script, because nothing supported it: *"There is no `req rm`/renumber, so I
|
|
8
|
+
* repaired it with a script."*
|
|
9
|
+
*
|
|
10
|
+
* The trigger for that was a defect — `req add` reissuing an id that `adopt`
|
|
11
|
+
* had already seeded — and that defect is fixed. The gap is not: a matrix a
|
|
12
|
+
* team cannot delete a row from is not a document a team can maintain.
|
|
13
|
+
*
|
|
14
|
+
* ## Why there is no `req renumber`
|
|
15
|
+
*
|
|
16
|
+
* It was the other half of what they asked for, and it is deliberately not
|
|
17
|
+
* here. A requirement id is not only a cell in this table: it appears in
|
|
18
|
+
* `@REQ-014` Gherkin tags, in test names, in commit messages, in branch names
|
|
19
|
+
* the harness created, and in whatever a team wrote in their own tracker.
|
|
20
|
+
* Renumbering the two files this tool owns while every other mention keeps the
|
|
21
|
+
* old id would leave a project in a worse state than the one it was trying to
|
|
22
|
+
* fix, and a rename that silently covers 60% of the occurrences is exactly the
|
|
23
|
+
* kind of half-promise this repository keeps finding in itself.
|
|
24
|
+
*
|
|
25
|
+
* Deleting is honest because it is complete: the row and the prose go, and the
|
|
26
|
+
* caller is told what it did *not* touch.
|
|
27
|
+
*/
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.isDelivered = isDelivered;
|
|
30
|
+
exports.planRemoval = planRemoval;
|
|
31
|
+
exports.removeMatrixRows = removeMatrixRows;
|
|
32
|
+
exports.removeSpecProse = removeSpecProse;
|
|
33
|
+
const DRAFT = "Draft";
|
|
34
|
+
/** Statuses that mean somebody recorded this requirement as delivered. */
|
|
35
|
+
function isDelivered(status) {
|
|
36
|
+
const s = String(status || "").trim();
|
|
37
|
+
return s !== "" && s !== DRAFT;
|
|
38
|
+
}
|
|
39
|
+
function cells(line) {
|
|
40
|
+
return line.split("|").map((c) => c.trim());
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* What removing `reqId` would take out of the matrix.
|
|
44
|
+
*
|
|
45
|
+
* `otherRowsUseFeature` decides whether a declared feature file becomes an
|
|
46
|
+
* orphan: a feature still referenced by another row is nobody's problem.
|
|
47
|
+
*/
|
|
48
|
+
function planRemoval(traceContent, reqId, specContent) {
|
|
49
|
+
const wanted = String(reqId || "").trim();
|
|
50
|
+
const lines = String(traceContent || "")
|
|
51
|
+
.replace(/\r\n/g, "\n")
|
|
52
|
+
.split("\n");
|
|
53
|
+
const rows = [];
|
|
54
|
+
const statuses = [];
|
|
55
|
+
const declaredFeatures = [];
|
|
56
|
+
const featuresElsewhere = new Set();
|
|
57
|
+
for (const line of lines) {
|
|
58
|
+
const trimmed = line.trim();
|
|
59
|
+
if (!trimmed.startsWith("|") || trimmed.includes("---"))
|
|
60
|
+
continue;
|
|
61
|
+
const c = cells(trimmed);
|
|
62
|
+
if (c.length < 4)
|
|
63
|
+
continue;
|
|
64
|
+
const rowId = c[1];
|
|
65
|
+
if (!/^REQ-/.test(rowId))
|
|
66
|
+
continue;
|
|
67
|
+
const feature = (c[3] || "").replace(/^`|`$/g, "").split("#")[0].trim();
|
|
68
|
+
if (rowId === wanted) {
|
|
69
|
+
rows.push(line);
|
|
70
|
+
statuses.push(c[c.length - 2] || "");
|
|
71
|
+
if (feature && feature !== "-")
|
|
72
|
+
declaredFeatures.push(feature);
|
|
73
|
+
}
|
|
74
|
+
else if (feature && feature !== "-") {
|
|
75
|
+
featuresElsewhere.add(feature);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
rows,
|
|
80
|
+
statuses,
|
|
81
|
+
orphanedFeatures: [...new Set(declaredFeatures)].filter((f) => !featuresElsewhere.has(f)),
|
|
82
|
+
hasProse: hasProseFor(specContent, wanted),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/** Every requirement id a piece of text names. Static: never built from input. */
|
|
86
|
+
const REQ_ID = /\bREQ-[A-Za-z0-9.]+\b/g;
|
|
87
|
+
const HEADING = /^#{2,4}\s+/;
|
|
88
|
+
function namedIds(text) {
|
|
89
|
+
return text.match(REQ_ID) || [];
|
|
90
|
+
}
|
|
91
|
+
/** `| REQ-014 | …` — the id in a table row's first cell, or null. */
|
|
92
|
+
function firstCellId(trimmedLine) {
|
|
93
|
+
if (!trimmedLine.startsWith("|"))
|
|
94
|
+
return null;
|
|
95
|
+
const first = trimmedLine.split("|")[1];
|
|
96
|
+
return first === undefined ? null : first.trim();
|
|
97
|
+
}
|
|
98
|
+
function hasProseFor(specContent, reqId) {
|
|
99
|
+
if (!reqId)
|
|
100
|
+
return false;
|
|
101
|
+
for (const line of String(specContent || "")
|
|
102
|
+
.replace(/\r\n/g, "\n")
|
|
103
|
+
.split("\n")) {
|
|
104
|
+
const trimmed = line.trim();
|
|
105
|
+
if (HEADING.test(trimmed) && namedIds(trimmed).includes(reqId))
|
|
106
|
+
return true;
|
|
107
|
+
if (firstCellId(trimmed) === reqId)
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
/** The matrix without `reqId`'s rows. */
|
|
113
|
+
function removeMatrixRows(traceContent, reqId) {
|
|
114
|
+
const wanted = String(reqId || "").trim();
|
|
115
|
+
return String(traceContent || "")
|
|
116
|
+
.replace(/\r\n/g, "\n")
|
|
117
|
+
.split("\n")
|
|
118
|
+
.filter((line) => {
|
|
119
|
+
const trimmed = line.trim();
|
|
120
|
+
if (!trimmed.startsWith("|"))
|
|
121
|
+
return true;
|
|
122
|
+
const c = cells(trimmed);
|
|
123
|
+
return c.length < 2 || c[1] !== wanted;
|
|
124
|
+
})
|
|
125
|
+
.join("\n");
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* `spec.md` without `reqId`'s section or table row.
|
|
129
|
+
*
|
|
130
|
+
* A section runs until the next heading of the same level or higher, matching
|
|
131
|
+
* how `SpecSections` reads it back — the two have to agree or removal leaves
|
|
132
|
+
* half a section behind.
|
|
133
|
+
*/
|
|
134
|
+
function removeSpecProse(specContent, reqId) {
|
|
135
|
+
const wanted = String(reqId || "").trim();
|
|
136
|
+
const lines = String(specContent || "")
|
|
137
|
+
.replace(/\r\n/g, "\n")
|
|
138
|
+
.split("\n");
|
|
139
|
+
const kept = [];
|
|
140
|
+
let skipUntilLevel = 0;
|
|
141
|
+
for (const line of lines) {
|
|
142
|
+
const trimmed = line.trim();
|
|
143
|
+
const heading = /^(#{1,6})\s+\S/.exec(trimmed);
|
|
144
|
+
if (skipUntilLevel > 0) {
|
|
145
|
+
if (heading && heading[1].length <= skipUntilLevel)
|
|
146
|
+
skipUntilLevel = 0;
|
|
147
|
+
else
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
const reqHeading = /^(#{2,4})\s+(.*)$/.exec(trimmed);
|
|
151
|
+
if (reqHeading && namedIds(reqHeading[2]).includes(wanted)) {
|
|
152
|
+
skipUntilLevel = reqHeading[1].length;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (firstCellId(trimmed) === wanted)
|
|
156
|
+
continue;
|
|
157
|
+
kept.push(line);
|
|
158
|
+
}
|
|
159
|
+
return kept.join("\n").replace(/\n{3,}/g, "\n\n");
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=RequirementRemoval.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RequirementRemoval.js","sourceRoot":"","sources":["../../../../../packages/core/src/domain/RequirementRemoval.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Does anything actually prove this scenario? (#168)
|
|
4
|
+
*
|
|
5
|
+
* A matrix row names one `Scenario ID` and one feature file. The file may hold
|
|
6
|
+
* five scenarios. Nothing related the other four to anything, so a scenario
|
|
7
|
+
* could be declared and never tested while every gate stayed green — measured,
|
|
8
|
+
* with `--strict-tdd --strict-scenarios --strict-links` all on at once.
|
|
9
|
+
*
|
|
10
|
+
* That is not a theoretical hole. Handed five scenarios where one contradicted
|
|
11
|
+
* another, an agent wrote tests for four and silently skipped the impossible
|
|
12
|
+
* one; the gate approved and the row went to `Implemented`. The scenario it
|
|
13
|
+
* dropped was precisely the one worth knowing about.
|
|
14
|
+
*
|
|
15
|
+
* ## Why matching by name is the right amount of cleverness
|
|
16
|
+
*
|
|
17
|
+
* This tool does not run the test suite and cannot know which assertion
|
|
18
|
+
* corresponds to which scenario. What it can check is that the scenario is
|
|
19
|
+
* *named* in the artifact the matrix says proves it. That works because it is
|
|
20
|
+
* what people and agents already do without being asked — `test('SCN-012 money
|
|
21
|
+
* is rounded half-up')` was written by an agent nobody instructed to do so.
|
|
22
|
+
*
|
|
23
|
+
* It is a heuristic, and it is stated as one: this lives behind
|
|
24
|
+
* `--strict-coverage` rather than in the default gate, because a project that
|
|
25
|
+
* names its tests some other way would fail through no fault of its own.
|
|
26
|
+
*/
|
|
27
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
28
|
+
exports.normalise = normalise;
|
|
29
|
+
exports.scenariosIn = scenariosIn;
|
|
30
|
+
exports.uncoveredScenarios = uncoveredScenarios;
|
|
31
|
+
exports.linkIsUnevidenced = linkIsUnevidenced;
|
|
32
|
+
exports.usesNamingConvention = usesNamingConvention;
|
|
33
|
+
exports.skippedScenarios = skippedScenarios;
|
|
34
|
+
const SCENARIO_LINE = /^(\s*)(Scenario Outline|Scenario|Escenario|Example)\s*:\s*(.*)$/;
|
|
35
|
+
const TAG_LINE = /^\s*@/;
|
|
36
|
+
const SCN_ID = /\b(SCN-[A-Za-z0-9.]+)\b/;
|
|
37
|
+
/** Lowercase alphanumerics only, so punctuation and case cannot break a match. */
|
|
38
|
+
function normalise(text) {
|
|
39
|
+
return text.toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Every scenario in a feature file, with what would identify it in a test.
|
|
43
|
+
*
|
|
44
|
+
* Scenario Outlines count: an outline with Examples is still behaviour someone
|
|
45
|
+
* promised, and skipping it would leave the widest scenarios unchecked.
|
|
46
|
+
*/
|
|
47
|
+
function scenariosIn(featureSource) {
|
|
48
|
+
const lines = featureSource.replace(/\r\n/g, "\n").split("\n");
|
|
49
|
+
const out = [];
|
|
50
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
51
|
+
const match = SCENARIO_LINE.exec(lines[i]);
|
|
52
|
+
if (!match)
|
|
53
|
+
continue;
|
|
54
|
+
const title = match[3].trim();
|
|
55
|
+
if (title === "")
|
|
56
|
+
continue;
|
|
57
|
+
const needles = [];
|
|
58
|
+
// A tag on the line above is the strongest identity: it survives a rename,
|
|
59
|
+
// which is the one edit that otherwise defeats every link to a scenario.
|
|
60
|
+
if (i > 0 && TAG_LINE.test(lines[i - 1])) {
|
|
61
|
+
const tagged = SCN_ID.exec(lines[i - 1]);
|
|
62
|
+
if (tagged)
|
|
63
|
+
needles.push(tagged[1]);
|
|
64
|
+
}
|
|
65
|
+
const inTitle = SCN_ID.exec(title);
|
|
66
|
+
if (inTitle && !needles.includes(inTitle[1]))
|
|
67
|
+
needles.push(inTitle[1]);
|
|
68
|
+
// Without an id anywhere, the title is all there is.
|
|
69
|
+
if (needles.length === 0)
|
|
70
|
+
needles.push(title);
|
|
71
|
+
out.push({ title, needles });
|
|
72
|
+
}
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The scenarios none of `testSources` mentions.
|
|
77
|
+
*
|
|
78
|
+
* `testSources` is every file the matrix declares as proof for this row — one
|
|
79
|
+
* file usually, but a directory expands to several, and a scenario proved in
|
|
80
|
+
* any of them is proved.
|
|
81
|
+
*/
|
|
82
|
+
function uncoveredScenarios(featureSource, testSources) {
|
|
83
|
+
const haystack = testSources.map(normalise).join("\n");
|
|
84
|
+
if (haystack === "")
|
|
85
|
+
return scenariosIn(featureSource);
|
|
86
|
+
return scenariosIn(featureSource).filter((scenario) => !scenario.needles.some((needle) => haystack.includes(normalise(needle))));
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* A declared test artifact that names neither the requirement nor any of its
|
|
90
|
+
* scenarios (Fase 1.2).
|
|
91
|
+
*
|
|
92
|
+
* `--strict-links` is `fs.existsSync` and nothing more, so a link that merely
|
|
93
|
+
* *lies* passes: measured on a real adoption, a "Vet" requirement ended up
|
|
94
|
+
* declaring `PetValidatorTests.java` as its proof and every gate stayed green.
|
|
95
|
+
* `uncoveredScenarios` catches that whenever the row has scenarios to name —
|
|
96
|
+
* but the rows that lie most easily are the ones `adopt` seeds, which have a
|
|
97
|
+
* use case and no scenario at all, so there is nothing to match and the check
|
|
98
|
+
* skips them.
|
|
99
|
+
*
|
|
100
|
+
* For those, the requirement id is the only available evidence: a test file
|
|
101
|
+
* that mentions neither `REQ-014` nor any scenario of REQ-014 is not visibly
|
|
102
|
+
* proving REQ-014.
|
|
103
|
+
*
|
|
104
|
+
* Returns true when the link has no evidence in it.
|
|
105
|
+
*
|
|
106
|
+
* ## The limit, stated rather than hidden
|
|
107
|
+
*
|
|
108
|
+
* This says the artifact does not *mention* the requirement. It cannot say the
|
|
109
|
+
* artifact does not *prove* it — that needs the suite executed and coverage
|
|
110
|
+
* attributed per test, which this tool does not do. Naming is a proxy, and the
|
|
111
|
+
* flag it lives behind is opt-in for exactly that reason.
|
|
112
|
+
*/
|
|
113
|
+
function linkIsUnevidenced(requirementId, featureSource, testSources) {
|
|
114
|
+
if (!requirementId)
|
|
115
|
+
return false;
|
|
116
|
+
const haystack = testSources.map(normalise).join("\n");
|
|
117
|
+
if (haystack === "")
|
|
118
|
+
return true;
|
|
119
|
+
if (haystack.includes(normalise(requirementId)))
|
|
120
|
+
return false;
|
|
121
|
+
return !scenariosIn(featureSource).some((scenario) => scenario.needles.some((needle) => haystack.includes(normalise(needle))));
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Does this project name requirements or scenarios in its tests at all?
|
|
125
|
+
*
|
|
126
|
+
* ## Why the check has to ask this first
|
|
127
|
+
*
|
|
128
|
+
* `--strict-coverage` matches names, and that works because agents and people
|
|
129
|
+
* writing tests alongside scenarios do it without being asked. It does **not**
|
|
130
|
+
* work for the case this product leads with. Retro-fitting specs onto a
|
|
131
|
+
* brownfield repository means pointing a row at a test that already exists and
|
|
132
|
+
* was written years before anyone had heard of this tool — and the only way to
|
|
133
|
+
* satisfy a name match there is to edit that test.
|
|
134
|
+
*
|
|
135
|
+
* Which is exactly what `adopt` promises it will never make you do. Measured on
|
|
136
|
+
* a real adoption: every retro-fitted row failed `link_without_evidence`, and
|
|
137
|
+
* the only fix was to write `REQ-006` into someone's existing test file. A gate
|
|
138
|
+
* that contradicts the product's headline promise is not a gate, it is a
|
|
139
|
+
* reason to stop using the product.
|
|
140
|
+
*
|
|
141
|
+
* So the check calibrates on the project instead of assuming. If some tests
|
|
142
|
+
* name their requirements and others do not, the silent ones are worth
|
|
143
|
+
* reporting. If none do, the convention is not in use here and the honest
|
|
144
|
+
* answer is to say the check could not run — not to fail every row for
|
|
145
|
+
* following a convention nobody adopted.
|
|
146
|
+
*/
|
|
147
|
+
function usesNamingConvention(rows) {
|
|
148
|
+
return rows.some((row) => !linkIsUnevidenced(row.requirement || "", row.feature, row.tests));
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Which scenarios in one feature file the tests actually name.
|
|
152
|
+
*
|
|
153
|
+
* ## Why this is per file and not per project
|
|
154
|
+
*
|
|
155
|
+
* The first version calibrated on the whole project: if any test named any
|
|
156
|
+
* requirement, every row had to. A cold evaluator found what that feels like —
|
|
157
|
+
* adding one comment, `// Covers REQ-009`, to one test turned three unrelated
|
|
158
|
+
* rows red at once. They called it a one-comment CI landmine, and they were
|
|
159
|
+
* right: doing the correct thing produced a punishment, which teaches people
|
|
160
|
+
* not to do the correct thing.
|
|
161
|
+
*
|
|
162
|
+
* Per file, the signal is sharper and the blast radius is the file. If four of
|
|
163
|
+
* five scenarios in `totals.feature` are named by its test and the fifth is
|
|
164
|
+
* not, the fifth was skipped — that is exactly #168, an agent quietly dropping
|
|
165
|
+
* the scenario it could not satisfy. If none are named, nobody has adopted the
|
|
166
|
+
* convention here yet and there is nothing to conclude.
|
|
167
|
+
*/
|
|
168
|
+
function skippedScenarios(featureSource, testSources) {
|
|
169
|
+
const all = scenariosIn(featureSource);
|
|
170
|
+
if (all.length === 0)
|
|
171
|
+
return [];
|
|
172
|
+
const uncovered = uncoveredScenarios(featureSource, testSources);
|
|
173
|
+
// Nothing in this file is named: the convention is not in use here.
|
|
174
|
+
if (uncovered.length === all.length)
|
|
175
|
+
return [];
|
|
176
|
+
return uncovered;
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=ScenarioCoverage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ScenarioCoverage.js","sourceRoot":"","sources":["../../../../../packages/core/src/domain/ScenarioCoverage.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Requirement prose in `spec.md` (Fase 3.1).
|
|
4
|
+
*
|
|
5
|
+
* ## The gap
|
|
6
|
+
*
|
|
7
|
+
* `req add` wrote a matrix row and nothing else. The title went into the *Use
|
|
8
|
+
* Case* column and `spec.md` never gained a section, so the requirement existed
|
|
9
|
+
* as a table entry with no text anywhere.
|
|
10
|
+
*
|
|
11
|
+
* That is one cause with two symptoms, and the second is the expensive one:
|
|
12
|
+
*
|
|
13
|
+
* # Implement REQ-002
|
|
14
|
+
* - Feature file: -
|
|
15
|
+
* - Test artifact (write this first — TDD): -
|
|
16
|
+
* - Production artifact: -
|
|
17
|
+
* ## Gherkin scenario
|
|
18
|
+
* (empty)
|
|
19
|
+
*
|
|
20
|
+
* The harness prompt asks an agent to implement a requirement whose text is
|
|
21
|
+
* nowhere in the prompt. A cold evaluator's words: *"the loop doesn't close on
|
|
22
|
+
* a brownfield adopt"*. It cannot, because there is nothing to read.
|
|
23
|
+
*
|
|
24
|
+
* ## Why a placeholder obligation and not just the title
|
|
25
|
+
*
|
|
26
|
+
* `--strict-requirements` wants an RFC-2119 keyword, and a section that says
|
|
27
|
+
* only what the user typed would fail the project's own gate the moment they
|
|
28
|
+
* turned that flag on. Writing `MUST` into the sentence makes the generated
|
|
29
|
+
* section pass the gate it will be measured by — and marks itself as a draft
|
|
30
|
+
* the author is expected to rewrite, rather than pretending to be finished
|
|
31
|
+
* prose.
|
|
32
|
+
*/
|
|
33
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
34
|
+
exports.renderRequirementSection = renderRequirementSection;
|
|
35
|
+
exports.hasRequirementSection = hasRequirementSection;
|
|
36
|
+
exports.requirementSections = requirementSections;
|
|
37
|
+
exports.appendRequirementSection = appendRequirementSection;
|
|
38
|
+
exports.extractRequirementSection = extractRequirementSection;
|
|
39
|
+
/** `## REQ-014 — title`, in the shape the validator and `plan` already read. */
|
|
40
|
+
function renderRequirementSection(reqId, title) {
|
|
41
|
+
const heading = `## ${reqId} — ${title}`;
|
|
42
|
+
// `MUST satisfy:` rather than `MUST <title>`: a title can start with an
|
|
43
|
+
// identifier (`res.redirect sets …`) or an acronym, and gluing it onto a verb
|
|
44
|
+
// produces something ungrammatical often enough that it is not worth the
|
|
45
|
+
// heuristic that would try to guess.
|
|
46
|
+
const obligation = String(title || "").trim() || "the behaviour named above";
|
|
47
|
+
return (`${heading}\n\n` +
|
|
48
|
+
`The system MUST satisfy: ${obligation}.\n\n` +
|
|
49
|
+
`> Written by \`specgate req add\`. Replace this sentence with the real ` +
|
|
50
|
+
`obligation — what must hold, under which conditions, and how it is ` +
|
|
51
|
+
`observed.\n`);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A requirement heading, at any level from `##` to `####`.
|
|
55
|
+
*
|
|
56
|
+
* Requirements are commonly nested under a section — the shipped template puts
|
|
57
|
+
* them under "8. Key requirements" — so insisting on top-level `##` would fail
|
|
58
|
+
* documents that are perfectly well organised. The level is captured because
|
|
59
|
+
* the extractor needs to know where the section ends.
|
|
60
|
+
*/
|
|
61
|
+
const REQUIREMENT_HEADING = /^(#{2,4})\s+(.*)$/;
|
|
62
|
+
/**
|
|
63
|
+
* The requirement ids a heading line declares.
|
|
64
|
+
*
|
|
65
|
+
* Four shapes turned up in real documents before this stopped enumerating them:
|
|
66
|
+
* `## REQ-014 — title`, `### REQ-014`, `### Requirement: REQ-100 — title`, and
|
|
67
|
+
* a table row. Matching "a heading that names the id" covers the first three
|
|
68
|
+
* without a fifth special case the next document would break anyway.
|
|
69
|
+
*/
|
|
70
|
+
function headingRequirementIds(line) {
|
|
71
|
+
const m = REQUIREMENT_HEADING.exec(line.trim());
|
|
72
|
+
if (!m)
|
|
73
|
+
return null;
|
|
74
|
+
const ids = m[2].match(/\bREQ-[A-Za-z0-9.]+\b/g) || [];
|
|
75
|
+
return ids.length > 0 ? { level: m[1].length, ids } : null;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* A requirement row in the `| ID | Requirement | … |` table §8 of the shipped
|
|
79
|
+
* template uses.
|
|
80
|
+
*
|
|
81
|
+
* Two conventions are in the wild and both are legitimate: `adopt` writes
|
|
82
|
+
* `## REQ-NNN` sections, while the original `init` template — and this
|
|
83
|
+
* repository's own spec.md — put one row per requirement in a table. Outlawing
|
|
84
|
+
* the table would have turned every project built from that template red on
|
|
85
|
+
* upgrade, for text that is right there in the document.
|
|
86
|
+
*
|
|
87
|
+
* The prose has to be real: a row whose requirement cell is empty, `-` or `TBD`
|
|
88
|
+
* is an id with nothing behind it, which is the case worth catching.
|
|
89
|
+
*/
|
|
90
|
+
const REQUIREMENT_TABLE_ROW = /^\|\s*(REQ-[A-Za-z0-9.]+)\s*\|([^|]*)\|/;
|
|
91
|
+
function tableRowText(specSource, reqId) {
|
|
92
|
+
const wanted = reqId.trim();
|
|
93
|
+
for (const line of String(specSource || "")
|
|
94
|
+
.replace(/\r\n/g, "\n")
|
|
95
|
+
.split("\n")) {
|
|
96
|
+
const m = REQUIREMENT_TABLE_ROW.exec(line.trim());
|
|
97
|
+
if (!m || m[1] !== wanted)
|
|
98
|
+
continue;
|
|
99
|
+
const text = m[2].trim();
|
|
100
|
+
if (text === "" || text === "-" || text.toUpperCase() === "TBD")
|
|
101
|
+
return null;
|
|
102
|
+
return text;
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
/** Does `spec.md` carry text for this requirement, in either convention? */
|
|
107
|
+
function hasRequirementSection(specSource, reqId) {
|
|
108
|
+
if (!reqId)
|
|
109
|
+
return false;
|
|
110
|
+
const wanted = reqId.trim();
|
|
111
|
+
const hasHeading = String(specSource || "")
|
|
112
|
+
.replace(/\r\n/g, "\n")
|
|
113
|
+
.split("\n")
|
|
114
|
+
.some((line) => {
|
|
115
|
+
const heading = headingRequirementIds(line);
|
|
116
|
+
return heading ? heading.ids.includes(wanted) : false;
|
|
117
|
+
});
|
|
118
|
+
return hasHeading || tableRowText(specSource, reqId) !== null;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Every `REQ-NNN` that `spec.md` declares a section for.
|
|
122
|
+
*
|
|
123
|
+
* Used in the other direction by the gate: a matrix row whose requirement has
|
|
124
|
+
* no section is a row describing something the specification never says.
|
|
125
|
+
*/
|
|
126
|
+
function requirementSections(specSource) {
|
|
127
|
+
const found = [];
|
|
128
|
+
for (const line of String(specSource || "")
|
|
129
|
+
.replace(/\r\n/g, "\n")
|
|
130
|
+
.split("\n")) {
|
|
131
|
+
const heading = headingRequirementIds(line);
|
|
132
|
+
if (heading) {
|
|
133
|
+
for (const id of heading.ids)
|
|
134
|
+
if (!found.includes(id))
|
|
135
|
+
found.push(id);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const row = REQUIREMENT_TABLE_ROW.exec(line.trim());
|
|
139
|
+
if (!row || found.includes(row[1]))
|
|
140
|
+
continue;
|
|
141
|
+
const text = row[2].trim();
|
|
142
|
+
if (text === "" || text === "-" || text.toUpperCase() === "TBD")
|
|
143
|
+
continue;
|
|
144
|
+
found.push(row[1]);
|
|
145
|
+
}
|
|
146
|
+
return found;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* `spec.md` with a section for `reqId` appended, or unchanged if it has one.
|
|
150
|
+
*
|
|
151
|
+
* Appends rather than inserting in id order: `spec.md` is a document a person
|
|
152
|
+
* writes in, and reordering someone's prose to satisfy a sort is a worse
|
|
153
|
+
* surprise than a section at the end.
|
|
154
|
+
*/
|
|
155
|
+
function appendRequirementSection(specSource, reqId, title) {
|
|
156
|
+
if (hasRequirementSection(specSource, reqId)) {
|
|
157
|
+
return { content: specSource, added: false };
|
|
158
|
+
}
|
|
159
|
+
const body = String(specSource || "").replace(/\s+$/, "");
|
|
160
|
+
const separator = body === "" ? "" : "\n\n";
|
|
161
|
+
return {
|
|
162
|
+
content: `${body}${separator}${renderRequirementSection(reqId, title)}`,
|
|
163
|
+
added: true,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* The body of `## REQ-NNN` in `spec.md`, heading included, or `null`.
|
|
168
|
+
*
|
|
169
|
+
* A section runs until the next heading of the same or higher level, so a
|
|
170
|
+
* `### ` subsection belongs to the requirement and a `## ` does not.
|
|
171
|
+
*/
|
|
172
|
+
function extractRequirementSection(specSource, reqId) {
|
|
173
|
+
if (!reqId)
|
|
174
|
+
return null;
|
|
175
|
+
const lines = String(specSource || "")
|
|
176
|
+
.replace(/\r\n/g, "\n")
|
|
177
|
+
.split("\n");
|
|
178
|
+
let start = -1;
|
|
179
|
+
let level = 2;
|
|
180
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
181
|
+
const heading = headingRequirementIds(lines[i]);
|
|
182
|
+
if (heading && heading.ids.includes(reqId.trim())) {
|
|
183
|
+
start = i;
|
|
184
|
+
level = heading.level;
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// No heading: the table row is the requirement's text, and the prompt needs
|
|
189
|
+
// it just as much.
|
|
190
|
+
if (start === -1) {
|
|
191
|
+
const text = tableRowText(specSource, reqId);
|
|
192
|
+
return text === null ? null : `## ${reqId.trim()}\n\n${text}`;
|
|
193
|
+
}
|
|
194
|
+
// The section ends at the next heading of the same level or higher, so a
|
|
195
|
+
// `####` sub-heading stays inside the requirement and the next `###` does not.
|
|
196
|
+
const boundary = new RegExp(`^#{1,${level}}\\s+\\S`);
|
|
197
|
+
let end = lines.length;
|
|
198
|
+
for (let i = start + 1; i < lines.length; i += 1) {
|
|
199
|
+
if (boundary.test(lines[i])) {
|
|
200
|
+
end = i;
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return lines.slice(start, end).join("\n").trim();
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=SpecSections.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SpecSections.js","sourceRoot":"","sources":["../../../../../packages/core/src/domain/SpecSections.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* One answer to "how does this project run its tests" (Fase 2.3).
|
|
4
|
+
*
|
|
5
|
+
* There were two, and they disagreed on the same `pom.xml`: `onboard`/`adopt`
|
|
6
|
+
* said `./mvnw -B test`, `harness init` wrote `mvn -B test`. A cold evaluator
|
|
7
|
+
* found it by running both and noticing the mismatch, which is a bad way to
|
|
8
|
+
* learn that a tool has two opinions about your project.
|
|
9
|
+
*
|
|
10
|
+
* The wrapper wins, and not by coin toss. The harness runs each attempt in a
|
|
11
|
+
* bare git worktree; `./mvnw` and `./gradlew` are checked into the repository
|
|
12
|
+
* and work there, while `mvn` and `gradle` depend on what happens to be
|
|
13
|
+
* installed on the machine. Preferring the wrapper is the difference between a
|
|
14
|
+
* gate that runs and a gate that reports "command not found" as a failed
|
|
15
|
+
* attempt and burns one of the agent's three tries on it.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.detectTestCommand = detectTestCommand;
|
|
19
|
+
exports.hasGherkinRunner = hasGherkinRunner;
|
|
20
|
+
function detectTestCommand(files, opts = {}) {
|
|
21
|
+
if (files.exists("package.json")) {
|
|
22
|
+
try {
|
|
23
|
+
const parsed = JSON.parse(files.read("package.json") || "{}");
|
|
24
|
+
const scripts = (parsed && parsed.scripts) || {};
|
|
25
|
+
if (opts.preferVerify && typeof scripts.verify === "string")
|
|
26
|
+
return "npm run verify";
|
|
27
|
+
// A declared script is the project's own answer and beats any guess.
|
|
28
|
+
if (typeof scripts.test === "string")
|
|
29
|
+
return "npm test";
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// A malformed package.json is the user's problem, not a reason to fail
|
|
33
|
+
// scaffolding — but it is also not evidence of a test script.
|
|
34
|
+
}
|
|
35
|
+
return "npm test";
|
|
36
|
+
}
|
|
37
|
+
// Wrapper first, everywhere it exists: it is in the repository, so it works
|
|
38
|
+
// in a bare worktree where the system tool may not be installed.
|
|
39
|
+
if (files.exists("pom.xml")) {
|
|
40
|
+
return files.exists("mvnw") ? "./mvnw -B test" : "mvn -B test";
|
|
41
|
+
}
|
|
42
|
+
if (files.exists("build.gradle") || files.exists("build.gradle.kts")) {
|
|
43
|
+
return files.exists("gradlew") ? "./gradlew test" : "gradle test";
|
|
44
|
+
}
|
|
45
|
+
if (files.exists("Cargo.toml"))
|
|
46
|
+
return "cargo test";
|
|
47
|
+
if (files.exists("go.mod"))
|
|
48
|
+
return "go test ./...";
|
|
49
|
+
if (files.exists("pyproject.toml") || files.exists("setup.py"))
|
|
50
|
+
return "pytest";
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Does anything in this project execute a `.feature` file?
|
|
55
|
+
*
|
|
56
|
+
* `doctor` reported "every scenario runnable" from a Gherkin *quality* check —
|
|
57
|
+
* When, Then, three steps — on a repository with no BDD runner anywhere, while
|
|
58
|
+
* the harness prompt in the same project said "nothing executes a `.feature`
|
|
59
|
+
* here". A cold evaluator listed the pair as a contradiction, and it is: one of
|
|
60
|
+
* them was describing the file and calling it the run.
|
|
61
|
+
*
|
|
62
|
+
* Shared so the two cannot drift apart again.
|
|
63
|
+
*/
|
|
64
|
+
function hasGherkinRunner(files) {
|
|
65
|
+
if (files.exists("features/step_definitions"))
|
|
66
|
+
return true;
|
|
67
|
+
for (const manifest of [
|
|
68
|
+
"package.json",
|
|
69
|
+
"pom.xml",
|
|
70
|
+
"build.gradle",
|
|
71
|
+
"build.gradle.kts",
|
|
72
|
+
"pyproject.toml",
|
|
73
|
+
]) {
|
|
74
|
+
if (!files.exists(manifest))
|
|
75
|
+
continue;
|
|
76
|
+
const source = files.read(manifest) || "";
|
|
77
|
+
if (/cucumber|behave|pytest-bdd|godog|specflow|reqnroll/i.test(source))
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=TestCommand.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TestCommand.js","sourceRoot":"","sources":["../../../../../packages/core/src/domain/TestCommand.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;GAcG;;AAgCH,8CA4BC;AAaD,4CAcC;AAvDD,SAAgB,iBAAiB,CAAC,KAAmB,EAAE,OAAsB,EAAE;IAC7E,IAAI,KAAK,CAAC,MAAM,CAAC,cAAc,CAAC,EAAE,CAAC;QACjC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,IAAI,CAAC,CAAC;YAC9D,MAAM,OAAO,GAAG,CAAC,MAAM,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YACjD,IAAI,IAAI,CAAC,YAAY,IAAI,OAAO,OAAO,CAAC,MAAM,KAAK,QAAQ;gBAAE,OAAO,gBAAgB,CAAC;YACrF,qEAAqE;YACrE,IAAI,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ;gBAAE,OAAO,UAAU,CAAC;QAC1D,CAAC;QAAC,MAAM,CAAC;YACP,uEAAuE;YACvE,8DAA8D;QAChE,CAAC;QACD,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,4EAA4E;IAC5E,iEAAiE;IACjE,IAAI,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;QAC5B,OAAO,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,aAAa,CAAC;IACjE,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,CAAC,cAAc,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,kBAAkB,CAAC,EAAE,CAAC;QACrE,OAAO,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,aAAa,CAAC;IACpE,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC;QAAE,OAAO,YAAY,CAAC;IACpD,IAAI,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC;QAAE,OAAO,eAAe,CAAC;IACnD,IAAI,KAAK,CAAC,MAAM,CAAC,gBAAgB,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC;QAAE,OAAO,QAAQ,CAAC;IAEhF,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAgB,gBAAgB,CAAC,KAAmB;IAClD,IAAI,KAAK,CAAC,MAAM,CAAC,2BAA2B,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3D,KAAK,MAAM,QAAQ,IAAI;QACrB,cAAc;QACd,SAAS;QACT,cAAc;QACd,kBAAkB;QAClB,gBAAgB;KACjB,EAAE,CAAC;QACF,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC;YAAE,SAAS;QACtC,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QAC1C,IAAI,qDAAqD,CAAC,IAAI,CAAC,MAAM,CAAC;YAAE,OAAO,IAAI,CAAC;IACtF,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|