@rsaglobaltech/specgate 0.8.1 → 0.10.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 +196 -0
- package/README.md +17 -25
- package/bin/specgate.ts +23 -5
- package/dist/bin/specgate.js +19 -4
- 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 +89 -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/GherkinQuality.js +19 -3
- package/dist/packages/core/src/domain/GherkinQuality.js.map +1 -1
- 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/NewRequirement.js +46 -0
- package/dist/packages/core/src/domain/NewRequirement.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/RequirementPlan.js +1 -0
- package/dist/packages/core/src/domain/RequirementPlan.js.map +1 -1
- 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 +45 -2
- 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 +84 -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/gherkin-quality.test.js +28 -0
- package/dist/packages/core/test/unit/gherkin-quality.test.js.map +1 -1
- 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/new-requirement.test.js +67 -0
- package/dist/packages/core/test/unit/new-requirement.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 +59 -15
- 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/check.js +10 -0
- package/dist/scripts/check.js.map +1 -0
- 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 +214 -9
- 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 +86 -9
- package/dist/scripts/cli/commands/project/AdoptCommand.js.map +1 -1
- package/dist/scripts/cli/commands/project/InitProjectCommand.js +286 -45
- package/dist/scripts/cli/commands/project/InitProjectCommand.js.map +1 -1
- package/dist/scripts/cli/commands/project/UpdateCommand.js +118 -8
- package/dist/scripts/cli/commands/project/UpdateCommand.js.map +1 -1
- package/dist/scripts/cli/commands/quality/CheckCommand.js +184 -0
- package/dist/scripts/cli/commands/quality/CheckCommand.js.map +1 -0
- 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 +406 -24
- 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/NewCommand.js +178 -0
- package/dist/scripts/cli/commands/spec/NewCommand.js.map +1 -0
- package/dist/scripts/cli/commands/spec/PlanCommand.js +8 -2
- package/dist/scripts/cli/commands/spec/PlanCommand.js.map +1 -1
- package/dist/scripts/cli/commands/spec/ReqCommand.js +124 -4
- package/dist/scripts/cli/commands/spec/ReqCommand.js.map +1 -1
- package/dist/scripts/cli/commands/spec/StatusCommand.js +84 -32
- 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 +88 -38
- package/dist/scripts/lib/surface.js.map +1 -1
- package/dist/scripts/new.js +10 -0
- package/dist/scripts/new.js.map +1 -0
- package/dist/scripts/plugin/gate-hook.js +2 -2
- package/dist/scripts/plugin/gate-hook.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/harness.config.yaml.tpl +3 -3
- package/templates/harness/prompt-prefix.md.tpl +2 -5
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* What `init --multi-stack` decides, with no filesystem underneath it.
|
|
4
|
+
*
|
|
5
|
+
* The shape this produces exists because of one constraint that is easy to get
|
|
6
|
+
* wrong: three stacks implementing the same product share their *requirements*
|
|
7
|
+
* and their *scenarios*, and share nothing else. A requirement is the same
|
|
8
|
+
* sentence whether Spring or Quarkus implements it, and a Gherkin scenario
|
|
9
|
+
* describes behaviour, which is the point of writing it in Gherkin. But the
|
|
10
|
+
* traceability matrix maps a requirement to the file that implements it and the
|
|
11
|
+
* file that proves it, and those are different files in every stack — so the
|
|
12
|
+
* matrix cannot be shared, and neither can AI_RULES.md, which is the rulebook
|
|
13
|
+
* for one toolchain.
|
|
14
|
+
*
|
|
15
|
+
* Sharing the first set and splitting the second is the whole design. Copying
|
|
16
|
+
* everything would be simpler and would be a lie: editing the spec in one
|
|
17
|
+
* project would leave the other two describing a different product.
|
|
18
|
+
*/
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.InvalidStackList = exports.PER_STACK_PATHS = exports.SHARED_PATHS = void 0;
|
|
21
|
+
exports.planStacks = planStacks;
|
|
22
|
+
exports.renderSpecopsConfig = renderSpecopsConfig;
|
|
23
|
+
exports.relativeToRoot = relativeToRoot;
|
|
24
|
+
/** Paths that are one artifact for the whole tree, held at the root. */
|
|
25
|
+
exports.SHARED_PATHS = ["spec.md", "features", "docs/specs/adr"];
|
|
26
|
+
/** Paths each stack owns outright — they name files that differ per toolchain. */
|
|
27
|
+
exports.PER_STACK_PATHS = ["AI_RULES.md", "README.md", "docs/specs/traceability.md"];
|
|
28
|
+
class InvalidStackList extends Error {
|
|
29
|
+
}
|
|
30
|
+
exports.InvalidStackList = InvalidStackList;
|
|
31
|
+
/**
|
|
32
|
+
* Turn `--multi-stack spring,quarkus,micronaut` into the plan.
|
|
33
|
+
*
|
|
34
|
+
* Each name becomes a directory, so it has to survive being one. A stack called
|
|
35
|
+
* `../etc` would write outside the root the user named, and `projects:` already
|
|
36
|
+
* accepts relative paths that nobody checks stay inside the repository (#103) —
|
|
37
|
+
* no reason to open a second door to the same place.
|
|
38
|
+
*/
|
|
39
|
+
function planStacks(raw) {
|
|
40
|
+
if (typeof raw !== "string" || raw.trim() === "") {
|
|
41
|
+
throw new InvalidStackList("--multi-stack needs a comma-separated list, e.g. spring,quarkus");
|
|
42
|
+
}
|
|
43
|
+
const names = raw
|
|
44
|
+
.split(",")
|
|
45
|
+
.map((s) => s.trim())
|
|
46
|
+
.filter((s) => s !== "");
|
|
47
|
+
if (names.length < 2) {
|
|
48
|
+
throw new InvalidStackList(`--multi-stack needs at least two stacks (got ${names.length === 1 ? `'${names[0]}'` : "none"}) — ` +
|
|
49
|
+
"for one stack, plain `specgate init` is the command");
|
|
50
|
+
}
|
|
51
|
+
const seen = new Set();
|
|
52
|
+
for (const name of names) {
|
|
53
|
+
if (!/^[a-z0-9][a-z0-9._-]*$/i.test(name)) {
|
|
54
|
+
throw new InvalidStackList(`'${name}' is not usable as a directory name — use letters, digits, dot, dash or underscore`);
|
|
55
|
+
}
|
|
56
|
+
if (name === "." || name === ".." || name.includes("..")) {
|
|
57
|
+
throw new InvalidStackList(`'${name}' would write outside the project root`);
|
|
58
|
+
}
|
|
59
|
+
const key = name.toLowerCase();
|
|
60
|
+
if (seen.has(key)) {
|
|
61
|
+
throw new InvalidStackList(`'${name}' is listed twice — each stack is one directory`);
|
|
62
|
+
}
|
|
63
|
+
seen.add(key);
|
|
64
|
+
}
|
|
65
|
+
return { stacks: names.map((name) => ({ name, entry: `./${name}` })) };
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The `projects:` list `validate`, `plan`, `status` and `report` already fan out
|
|
69
|
+
* over. Written by hand rather than through a YAML library because the rest of
|
|
70
|
+
* this project reads it with `parseYamlLite`, and a writer that can emit
|
|
71
|
+
* something its own reader rejects is worse than no writer.
|
|
72
|
+
*/
|
|
73
|
+
function renderSpecopsConfig(plan, projectName) {
|
|
74
|
+
return (`# Generated by \`specgate init --multi-stack\`.\n` +
|
|
75
|
+
`#\n` +
|
|
76
|
+
`# Every entry is validated as a project in its own right. The spec and the\n` +
|
|
77
|
+
`# scenarios live once, at this root; each stack below carries its own\n` +
|
|
78
|
+
`# traceability matrix, because the files that implement and prove a\n` +
|
|
79
|
+
`# requirement are different files in every stack.\n` +
|
|
80
|
+
`name: ${projectName}\n` +
|
|
81
|
+
`projects:\n` +
|
|
82
|
+
plan.stacks.map((s) => ` - ${s.entry}\n`).join(""));
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Where a shared path sits, seen from inside a stack directory.
|
|
86
|
+
*
|
|
87
|
+
* `docs/specs/adr` is two levels down, so the link back is not the same depth
|
|
88
|
+
* as the one for `spec.md`. Getting this wrong produces a link that resolves to
|
|
89
|
+
* nothing and a validate run that reports a missing required file, which is a
|
|
90
|
+
* confusing way to learn about an off-by-one.
|
|
91
|
+
*/
|
|
92
|
+
function relativeToRoot(sharedPath) {
|
|
93
|
+
const depth = sharedPath.split("/").length;
|
|
94
|
+
return "../".repeat(depth) + sharedPath;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=MultiStack.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MultiStack.js","sourceRoot":"","sources":["../../../../../packages/core/src/domain/MultiStack.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* What `specgate new` writes for a requirement: a slug for its feature file
|
|
4
|
+
* and a tagged scenario of placeholders for a person to rewrite.
|
|
5
|
+
*
|
|
6
|
+
* The placeholders are deliberate. A scenario the tool invents would read as
|
|
7
|
+
* a decision nobody made; `<the observable outcome>` reads as a question. The
|
|
8
|
+
* gate leaves them alone while the requirement is Draft and refuses them once
|
|
9
|
+
* it is delivered (`scenario_placeholder_step`).
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.slugFor = slugFor;
|
|
13
|
+
exports.scenarioBlock = scenarioBlock;
|
|
14
|
+
exports.featureWithScenario = featureWithScenario;
|
|
15
|
+
const MAX_SLUG_WORDS = 6;
|
|
16
|
+
/** `Totals are rounded half-up` → `totals-are-rounded-half-up`. */
|
|
17
|
+
function slugFor(title) {
|
|
18
|
+
const words = title
|
|
19
|
+
.normalize("NFKD")
|
|
20
|
+
.replace(/[̀-ͯ]/g, "")
|
|
21
|
+
.toLowerCase()
|
|
22
|
+
.replace(/[^a-z0-9]+/g, " ")
|
|
23
|
+
.trim()
|
|
24
|
+
.split(/\s+/)
|
|
25
|
+
.filter(Boolean)
|
|
26
|
+
.slice(0, MAX_SLUG_WORDS);
|
|
27
|
+
return words.join("-") || "requirement";
|
|
28
|
+
}
|
|
29
|
+
/** The scenario, tagged so `validate` can tie it to its matrix row. */
|
|
30
|
+
function scenarioBlock(reqId, scenarioId, title) {
|
|
31
|
+
return [
|
|
32
|
+
` @${reqId} @${scenarioId}`,
|
|
33
|
+
` Scenario: ${title}`,
|
|
34
|
+
" Given <the state before the action>",
|
|
35
|
+
" When <the action under test>",
|
|
36
|
+
" Then <the observable outcome>",
|
|
37
|
+
"",
|
|
38
|
+
].join("\n");
|
|
39
|
+
}
|
|
40
|
+
/** A new feature file, or the scenario appended to an existing one. */
|
|
41
|
+
function featureWithScenario(existing, title, block) {
|
|
42
|
+
if (existing === null)
|
|
43
|
+
return `Feature: ${title}\n\n${block}`;
|
|
44
|
+
return `${existing.replace(/\s*$/, "")}\n\n${block}`;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=NewRequirement.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NewRequirement.js","sourceRoot":"","sources":["../../../../../packages/core/src/domain/NewRequirement.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;AAKH,0BAWC;AAGD,sCASC;AAGD,kDAGC;AAhCD,MAAM,cAAc,GAAG,CAAC,CAAC;AAEzB,mEAAmE;AACnE,SAAgB,OAAO,CAAC,KAAa;IACnC,MAAM,KAAK,GAAG,KAAK;SAChB,SAAS,CAAC,MAAM,CAAC;SACjB,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC;SACrB,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,IAAI,EAAE;SACN,KAAK,CAAC,KAAK,CAAC;SACZ,MAAM,CAAC,OAAO,CAAC;SACf,KAAK,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC;IAC5B,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,aAAa,CAAC;AAC1C,CAAC;AAED,uEAAuE;AACvE,SAAgB,aAAa,CAAC,KAAa,EAAE,UAAkB,EAAE,KAAa;IAC5E,OAAO;QACL,MAAM,KAAK,KAAK,UAAU,EAAE;QAC5B,eAAe,KAAK,EAAE;QACtB,yCAAyC;QACzC,kCAAkC;QAClC,mCAAmC;QACnC,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,uEAAuE;AACvE,SAAgB,mBAAmB,CAAC,QAAuB,EAAE,KAAa,EAAE,KAAa;IACvF,IAAI,QAAQ,KAAK,IAAI;QAAE,OAAO,YAAY,KAAK,OAAO,KAAK,EAAE,CAAC;IAC9D,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,OAAO,KAAK,EAAE,CAAC;AACvD,CAAC"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Which already-accepted requirement an agent should be shown as an example (D2).
|
|
4
|
+
*
|
|
5
|
+
* ## What the prompt is missing
|
|
6
|
+
*
|
|
7
|
+
* `buildPrompt` injects the requirement's facts, its Gherkin, `AI_RULES.md` and
|
|
8
|
+
* the definition of done. Every one of those is **normative** — it says what
|
|
9
|
+
* must be true. None of them is an *example*: an agent with no conversation
|
|
10
|
+
* history has never seen what an accepted implementation looks like in this
|
|
11
|
+
* repository, so it invents a house style and the reviewer spends the next
|
|
12
|
+
* attempt correcting it.
|
|
13
|
+
*
|
|
14
|
+
* A precedent is the cheapest possible answer: here is a requirement that was
|
|
15
|
+
* accepted, here is its test, here is its production code.
|
|
16
|
+
*
|
|
17
|
+
* ## Why the same bounded context
|
|
18
|
+
*
|
|
19
|
+
* A precedent from a different part of the system is worse than none — it
|
|
20
|
+
* teaches conventions that do not apply here, with the authority of "this was
|
|
21
|
+
* accepted". D1 already derives each requirement's bounded context and records
|
|
22
|
+
* it on the trace line, so "the same context" is a question the tool can answer
|
|
23
|
+
* rather than a guess from a path.
|
|
24
|
+
*
|
|
25
|
+
* When a project has no contexts at all, every requirement shares the empty
|
|
26
|
+
* one, which is the right answer for a codebase that has not been divided yet.
|
|
27
|
+
*
|
|
28
|
+
* ## Why only Verified
|
|
29
|
+
*
|
|
30
|
+
* `Implemented` means the code exists. `Verified` means somebody checked it.
|
|
31
|
+
* Showing an agent an unverified implementation as a precedent would propagate
|
|
32
|
+
* whatever is wrong with it, and do so as an example to copy.
|
|
33
|
+
*
|
|
34
|
+
* ## Why this module reads nothing
|
|
35
|
+
*
|
|
36
|
+
* Choosing the precedent is a rule; reading the files is I/O. Keeping them
|
|
37
|
+
* apart is what lets every case below be tested without a repository.
|
|
38
|
+
*/
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.choosePrecedent = choosePrecedent;
|
|
41
|
+
exports.excerpt = excerpt;
|
|
42
|
+
/** A path cell that names nothing. The matrix uses several spellings of "no". */
|
|
43
|
+
function isPlaceholder(cell) {
|
|
44
|
+
const value = String(cell || "")
|
|
45
|
+
.replace(/`/g, "")
|
|
46
|
+
.trim();
|
|
47
|
+
return value === "" || value === "-" || value === "—" || /^tbd$/i.test(value);
|
|
48
|
+
}
|
|
49
|
+
function clean(cell) {
|
|
50
|
+
return String(cell || "")
|
|
51
|
+
.replace(/`/g, "")
|
|
52
|
+
.trim();
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The most recent accepted requirement in `forRequirement`'s bounded context.
|
|
56
|
+
*
|
|
57
|
+
* "Most recent" is the highest requirement id below the current one, which is
|
|
58
|
+
* the order they were written in. Returns `null` when there is nothing worth
|
|
59
|
+
* showing — no verified sibling, or one with no artifacts to point at.
|
|
60
|
+
*/
|
|
61
|
+
function choosePrecedent(rows, contexts, forRequirement) {
|
|
62
|
+
const own = contexts[forRequirement] || "";
|
|
63
|
+
const candidates = rows
|
|
64
|
+
.filter((row) => row.requirementId && row.requirementId !== forRequirement)
|
|
65
|
+
.filter((row) => /^verified$/i.test(String(row.status || "").trim()))
|
|
66
|
+
.filter((row) => (contexts[row.requirementId] || "") === own)
|
|
67
|
+
// A precedent with neither a test nor an implementation to look at is not a
|
|
68
|
+
// precedent; it is a row.
|
|
69
|
+
.filter((row) => !isPlaceholder(row.testArtifact) || !isPlaceholder(row.technicalArtifact))
|
|
70
|
+
// Ids sort lexically the way they were written: REQ-002 before REQ-010
|
|
71
|
+
// requires the padding the format already guarantees.
|
|
72
|
+
.filter((row) => row.requirementId < forRequirement)
|
|
73
|
+
.sort((a, b) => b.requirementId.localeCompare(a.requirementId));
|
|
74
|
+
const best = candidates[0];
|
|
75
|
+
if (!best)
|
|
76
|
+
return null;
|
|
77
|
+
return {
|
|
78
|
+
requirementId: best.requirementId,
|
|
79
|
+
testArtifact: isPlaceholder(best.testArtifact) ? "" : clean(best.testArtifact),
|
|
80
|
+
technicalArtifact: isPlaceholder(best.technicalArtifact) ? "" : clean(best.technicalArtifact),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The first `lines` lines of a file's content, marked when it was cut short.
|
|
85
|
+
*
|
|
86
|
+
* A precedent is an example of shape, not a file to read: a thousand-line class
|
|
87
|
+
* in the prompt costs tokens the agent needs for its own work, and the part
|
|
88
|
+
* that teaches the convention is the top.
|
|
89
|
+
*/
|
|
90
|
+
function excerpt(content, lines = 40) {
|
|
91
|
+
const all = String(content || "").split("\n");
|
|
92
|
+
if (all.length <= lines)
|
|
93
|
+
return all.join("\n").trimEnd();
|
|
94
|
+
return `${all.slice(0, lines).join("\n").trimEnd()}\n… (${all.length - lines} more lines)`;
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=Precedents.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Precedents.js","sourceRoot":"","sources":["../../../../../packages/core/src/domain/Precedents.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RequirementPlan.js","sourceRoot":"","sources":["../../../../../packages/core/src/domain/RequirementPlan.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"RequirementPlan.js","sourceRoot":"","sources":["../../../../../packages/core/src/domain/RequirementPlan.ts"],"names":[],"mappings":"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"}
|
|
@@ -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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"}
|