@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,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/**
|
|
4
|
+
* Choosing the precedent (D2).
|
|
5
|
+
*
|
|
6
|
+
* The prompt is entirely normative — facts, Gherkin, rules, definition of done.
|
|
7
|
+
* None of it shows an agent what an accepted implementation looks like *here*,
|
|
8
|
+
* so it invents a house style and the next attempt is spent correcting it.
|
|
9
|
+
*
|
|
10
|
+
* The rules below are the ones that decide whether a precedent helps or hurts:
|
|
11
|
+
* a precedent from another bounded context teaches conventions that do not
|
|
12
|
+
* apply, with the authority of "this was accepted".
|
|
13
|
+
*/
|
|
14
|
+
const test = require("node:test");
|
|
15
|
+
const assert = require("node:assert/strict");
|
|
16
|
+
const { choosePrecedent, excerpt } = require("../../src/domain/Precedents");
|
|
17
|
+
const row = (id, status, testArtifact = "", technicalArtifact = "") => ({
|
|
18
|
+
requirementId: id,
|
|
19
|
+
status,
|
|
20
|
+
testArtifact,
|
|
21
|
+
technicalArtifact,
|
|
22
|
+
});
|
|
23
|
+
test("the most recent accepted requirement in the same context wins", () => {
|
|
24
|
+
const rows = [
|
|
25
|
+
row("REQ-001", "Verified", "t/one.test.ts", "src/one.ts"),
|
|
26
|
+
row("REQ-002", "Verified", "t/two.test.ts", "src/two.ts"),
|
|
27
|
+
];
|
|
28
|
+
const contexts = { "REQ-001": "Billing", "REQ-002": "Billing", "REQ-003": "Billing" };
|
|
29
|
+
const choice = choosePrecedent(rows, contexts, "REQ-003");
|
|
30
|
+
assert.equal(choice.requirementId, "REQ-002");
|
|
31
|
+
assert.equal(choice.testArtifact, "t/two.test.ts");
|
|
32
|
+
assert.equal(choice.technicalArtifact, "src/two.ts");
|
|
33
|
+
});
|
|
34
|
+
test("a different bounded context is not a precedent", () => {
|
|
35
|
+
// Worse than none: it teaches conventions that do not apply, with the
|
|
36
|
+
// authority of having been accepted.
|
|
37
|
+
const rows = [row("REQ-001", "Verified", "t/one.test.ts", "src/one.ts")];
|
|
38
|
+
const contexts = { "REQ-001": "Billing", "REQ-002": "Shipping" };
|
|
39
|
+
assert.equal(choosePrecedent(rows, contexts, "REQ-002"), null);
|
|
40
|
+
});
|
|
41
|
+
test("a project with no contexts at all shares one", () => {
|
|
42
|
+
// A codebase nobody has divided yet is one context, not none.
|
|
43
|
+
const rows = [row("REQ-001", "Verified", "t/one.test.ts", "src/one.ts")];
|
|
44
|
+
assert.equal(choosePrecedent(rows, {}, "REQ-002").requirementId, "REQ-001");
|
|
45
|
+
});
|
|
46
|
+
test("Implemented is not Verified", () => {
|
|
47
|
+
// `Implemented` means the code exists; `Verified` means somebody checked it.
|
|
48
|
+
// Copying an unchecked implementation propagates whatever is wrong with it.
|
|
49
|
+
const rows = [row("REQ-001", "Implemented", "t/one.test.ts", "src/one.ts")];
|
|
50
|
+
assert.equal(choosePrecedent(rows, {}, "REQ-002"), null);
|
|
51
|
+
});
|
|
52
|
+
test("status matching ignores case and padding", () => {
|
|
53
|
+
const rows = [row("REQ-001", " verified ", "t/one.test.ts", "src/one.ts")];
|
|
54
|
+
assert.equal(choosePrecedent(rows, {}, "REQ-002").requirementId, "REQ-001");
|
|
55
|
+
});
|
|
56
|
+
test("a row with no artifacts is not a precedent", () => {
|
|
57
|
+
// There is nothing to show. A section naming a requirement and quoting
|
|
58
|
+
// nothing is worse than no section.
|
|
59
|
+
const rows = [row("REQ-001", "Verified", "TBD", "-")];
|
|
60
|
+
assert.equal(choosePrecedent(rows, {}, "REQ-002"), null);
|
|
61
|
+
});
|
|
62
|
+
test("one artifact is enough", () => {
|
|
63
|
+
const rows = [row("REQ-001", "Verified", "t/one.test.ts", "TBD")];
|
|
64
|
+
const choice = choosePrecedent(rows, {}, "REQ-002");
|
|
65
|
+
assert.equal(choice.testArtifact, "t/one.test.ts");
|
|
66
|
+
assert.equal(choice.technicalArtifact, "", "a placeholder must not become a path");
|
|
67
|
+
});
|
|
68
|
+
test("a requirement is never its own precedent", () => {
|
|
69
|
+
const rows = [row("REQ-002", "Verified", "t/two.test.ts", "src/two.ts")];
|
|
70
|
+
assert.equal(choosePrecedent(rows, {}, "REQ-002"), null);
|
|
71
|
+
});
|
|
72
|
+
test("a later requirement is not a precedent for an earlier one", () => {
|
|
73
|
+
// Precedent means already settled. Pointing REQ-001 at REQ-009 would make
|
|
74
|
+
// the example depend on work that may not exist when REQ-001 is attempted.
|
|
75
|
+
const rows = [row("REQ-009", "Verified", "t/nine.test.ts", "src/nine.ts")];
|
|
76
|
+
assert.equal(choosePrecedent(rows, {}, "REQ-001"), null);
|
|
77
|
+
});
|
|
78
|
+
test("backticks around a path are not part of the path", () => {
|
|
79
|
+
const rows = [row("REQ-001", "Verified", "`t/one.test.ts`", "`src/one.ts`")];
|
|
80
|
+
const choice = choosePrecedent(rows, {}, "REQ-002");
|
|
81
|
+
assert.equal(choice.testArtifact, "t/one.test.ts");
|
|
82
|
+
});
|
|
83
|
+
// ── excerpt ──────────────────────────────────────────────────────────────────
|
|
84
|
+
test("a short file is quoted whole", () => {
|
|
85
|
+
assert.equal(excerpt("a\nb\nc"), "a\nb\nc");
|
|
86
|
+
});
|
|
87
|
+
test("a long file is cut and says so", () => {
|
|
88
|
+
// A thousand-line class in the prompt costs the agent the budget it needs
|
|
89
|
+
// for its own work, and the part that teaches the convention is the top.
|
|
90
|
+
const out = excerpt(Array.from({ length: 100 }, (_, i) => `line ${i}`).join("\n"), 10);
|
|
91
|
+
assert.match(out, /^line 0\n/);
|
|
92
|
+
assert.match(out, /… \(90 more lines\)$/);
|
|
93
|
+
assert.doesNotMatch(out, /line 50/);
|
|
94
|
+
});
|
|
95
|
+
test("excerpt survives an empty file", () => {
|
|
96
|
+
assert.equal(excerpt(""), "");
|
|
97
|
+
assert.equal(excerpt(null), "");
|
|
98
|
+
});
|
|
99
|
+
//# sourceMappingURL=precedents.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"precedents.test.js","sourceRoot":"","sources":["../../../../../packages/core/test/unit/precedents.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Fase 4.1: `req add` had no counterpart, so two cold adoptions repaired the
|
|
4
|
+
* matrix by hand with a script.
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
const node_test_1 = require("node:test");
|
|
41
|
+
const assert = __importStar(require("node:assert/strict"));
|
|
42
|
+
const RequirementRemoval_1 = require("../../src/domain/RequirementRemoval");
|
|
43
|
+
const HEADER = "| Requirement | Scenario ID | Feature file | Use Case | Command/Query | Aggregate | Event | Technical artifact | Test artifact | Status |\n" +
|
|
44
|
+
"| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |";
|
|
45
|
+
const MATRIX = [
|
|
46
|
+
HEADER,
|
|
47
|
+
"| REQ-001 | SCN-001 | `features/core/health.feature` | UC-001 Health | - | - | - | src/h.js | tests/h.js | Verified |",
|
|
48
|
+
"| REQ-002 | SCN-002 | `features/billing/totals.feature` | UC-002 Totals | - | - | - | src/t.js | tests/t.js | Draft |",
|
|
49
|
+
"| REQ-003 | SCN-003 | `features/core/health.feature` | UC-003 Also health | - | - | - | src/x.js | tests/x.js | Draft |",
|
|
50
|
+
].join("\n");
|
|
51
|
+
const SPEC = [
|
|
52
|
+
"# Spec",
|
|
53
|
+
"",
|
|
54
|
+
"## 8. Key requirements",
|
|
55
|
+
"",
|
|
56
|
+
"| ID | Requirement | Priority | Status |",
|
|
57
|
+
"|---|---|---|---|",
|
|
58
|
+
"| REQ-002 | Totals are rounded | Must | Draft |",
|
|
59
|
+
"",
|
|
60
|
+
"### REQ-002 — Totals are rounded",
|
|
61
|
+
"",
|
|
62
|
+
"The system MUST satisfy: Totals are rounded.",
|
|
63
|
+
"",
|
|
64
|
+
"### REQ-003 — Also health",
|
|
65
|
+
"",
|
|
66
|
+
"kept",
|
|
67
|
+
].join("\n");
|
|
68
|
+
(0, node_test_1.test)("the plan names every row the id owns", () => {
|
|
69
|
+
const plan = (0, RequirementRemoval_1.planRemoval)(MATRIX, "REQ-002", SPEC);
|
|
70
|
+
assert.equal(plan.rows.length, 1);
|
|
71
|
+
assert.deepEqual(plan.statuses, ["Draft"]);
|
|
72
|
+
assert.equal(plan.hasProse, true);
|
|
73
|
+
});
|
|
74
|
+
(0, node_test_1.test)("a feature file another row still uses is not orphaned", () => {
|
|
75
|
+
// REQ-001 and REQ-003 share health.feature; removing one leaves it referenced.
|
|
76
|
+
assert.deepEqual((0, RequirementRemoval_1.planRemoval)(MATRIX, "REQ-001", SPEC).orphanedFeatures, []);
|
|
77
|
+
assert.deepEqual((0, RequirementRemoval_1.planRemoval)(MATRIX, "REQ-002", SPEC).orphanedFeatures, [
|
|
78
|
+
"features/billing/totals.feature",
|
|
79
|
+
]);
|
|
80
|
+
});
|
|
81
|
+
(0, node_test_1.test)("a delivered requirement is flagged, a draft is not", () => {
|
|
82
|
+
// Removing an Implemented row deletes the record that something shipped.
|
|
83
|
+
assert.equal((0, RequirementRemoval_1.isDelivered)("Verified"), true);
|
|
84
|
+
assert.equal((0, RequirementRemoval_1.isDelivered)("Implemented"), true);
|
|
85
|
+
assert.equal((0, RequirementRemoval_1.isDelivered)("Draft"), false);
|
|
86
|
+
assert.equal((0, RequirementRemoval_1.isDelivered)(""), false);
|
|
87
|
+
assert.equal((0, RequirementRemoval_1.planRemoval)(MATRIX, "REQ-001", SPEC).statuses.some(RequirementRemoval_1.isDelivered), true);
|
|
88
|
+
});
|
|
89
|
+
(0, node_test_1.test)("removing takes the row and leaves the others alone", () => {
|
|
90
|
+
const after = (0, RequirementRemoval_1.removeMatrixRows)(MATRIX, "REQ-002");
|
|
91
|
+
assert.ok(!after.includes("REQ-002"));
|
|
92
|
+
assert.ok(after.includes("REQ-001"));
|
|
93
|
+
assert.ok(after.includes("REQ-003"));
|
|
94
|
+
assert.ok(after.includes("| Requirement | Scenario ID |"), "the header survives");
|
|
95
|
+
});
|
|
96
|
+
(0, node_test_1.test)("duplicate rows under one id all go", () => {
|
|
97
|
+
const withDupe = MATRIX + "\n| REQ-002 | SCN-009 | - | UC-002 again | - | - | - | - | - | Draft |";
|
|
98
|
+
assert.equal((0, RequirementRemoval_1.planRemoval)(withDupe, "REQ-002", SPEC).rows.length, 2);
|
|
99
|
+
assert.ok(!(0, RequirementRemoval_1.removeMatrixRows)(withDupe, "REQ-002").includes("REQ-002"));
|
|
100
|
+
});
|
|
101
|
+
(0, node_test_1.test)("the prose goes: both the section and the table row", () => {
|
|
102
|
+
const after = (0, RequirementRemoval_1.removeSpecProse)(SPEC, "REQ-002");
|
|
103
|
+
assert.ok(!after.includes("REQ-002"));
|
|
104
|
+
assert.ok(!after.includes("Totals are rounded"), "the section body goes with its heading");
|
|
105
|
+
assert.ok(after.includes("### REQ-003 — Also health"), "the next requirement survives");
|
|
106
|
+
assert.ok(after.includes("kept"));
|
|
107
|
+
assert.ok(after.includes("## 8. Key requirements"), "the enclosing section survives");
|
|
108
|
+
});
|
|
109
|
+
(0, node_test_1.test)("removing the last requirement does not eat the rest of the document", () => {
|
|
110
|
+
const spec = "## 8. Key requirements\n\n### REQ-002 — Gone\n\nbody\n\n## 9. Non-functional\n\nkept\n";
|
|
111
|
+
const after = (0, RequirementRemoval_1.removeSpecProse)(spec, "REQ-002");
|
|
112
|
+
assert.ok(!after.includes("REQ-002"));
|
|
113
|
+
assert.ok(after.includes("## 9. Non-functional"));
|
|
114
|
+
assert.ok(after.includes("kept"));
|
|
115
|
+
});
|
|
116
|
+
(0, node_test_1.test)("an id that is not there changes nothing", () => {
|
|
117
|
+
assert.equal((0, RequirementRemoval_1.removeMatrixRows)(MATRIX, "REQ-999"), MATRIX);
|
|
118
|
+
assert.equal((0, RequirementRemoval_1.planRemoval)(MATRIX, "REQ-999", SPEC).rows.length, 0);
|
|
119
|
+
});
|
|
120
|
+
(0, node_test_1.test)("REQ-01 does not match REQ-014", () => {
|
|
121
|
+
const m = HEADER + "\n| REQ-014 | - | - | UC | - | - | - | - | - | Draft |";
|
|
122
|
+
assert.equal((0, RequirementRemoval_1.removeMatrixRows)(m, "REQ-01"), m);
|
|
123
|
+
});
|
|
124
|
+
//# sourceMappingURL=requirement-removal.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requirement-removal.test.js","sourceRoot":"","sources":["../../../../../packages/core/test/unit/requirement-removal.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* #168: a scenario declared in a feature file that nothing tests.
|
|
4
|
+
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
+
}) : function(o, v) {
|
|
19
|
+
o["default"] = v;
|
|
20
|
+
});
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const node_test_1 = require("node:test");
|
|
40
|
+
const assert = __importStar(require("node:assert/strict"));
|
|
41
|
+
const ScenarioCoverage_1 = require("../../src/domain/ScenarioCoverage");
|
|
42
|
+
const FEATURE = `Feature: Invoice totals
|
|
43
|
+
|
|
44
|
+
Scenario: SCN-010 subtotal is the sum of line amounts
|
|
45
|
+
Given two lines
|
|
46
|
+
When the invoice is totalled
|
|
47
|
+
Then the subtotal is their sum
|
|
48
|
+
|
|
49
|
+
Scenario: SCN-011 tax is applied per line
|
|
50
|
+
Given two rates
|
|
51
|
+
When the invoice is totalled
|
|
52
|
+
Then each line carries its own rate
|
|
53
|
+
|
|
54
|
+
Scenario: SCN-012 a fully discounted invoice carries no tax
|
|
55
|
+
Given a 100% discount
|
|
56
|
+
When the invoice is totalled
|
|
57
|
+
Then the tax is zero
|
|
58
|
+
`;
|
|
59
|
+
(0, node_test_1.test)("every scenario in the file is found, not just the one the row names", () => {
|
|
60
|
+
assert.deepEqual((0, ScenarioCoverage_1.scenariosIn)(FEATURE).map((s) => s.needles[0]), ["SCN-010", "SCN-011", "SCN-012"]);
|
|
61
|
+
});
|
|
62
|
+
(0, node_test_1.test)("the scenario nothing tests is the one reported", () => {
|
|
63
|
+
// The measured case: an agent wrote tests for the scenarios it could satisfy
|
|
64
|
+
// and skipped the one it could not.
|
|
65
|
+
const tests = [
|
|
66
|
+
`test("SCN-010 subtotal is the sum of line amounts", () => {});
|
|
67
|
+
test("SCN-011 tax is applied per line", () => {});`,
|
|
68
|
+
];
|
|
69
|
+
const missing = (0, ScenarioCoverage_1.uncoveredScenarios)(FEATURE, tests);
|
|
70
|
+
assert.equal(missing.length, 1);
|
|
71
|
+
assert.match(missing[0].title, /fully discounted/);
|
|
72
|
+
});
|
|
73
|
+
(0, node_test_1.test)("a fully covered feature reports nothing", () => {
|
|
74
|
+
const tests = ["SCN-010 SCN-011 SCN-012"];
|
|
75
|
+
assert.deepEqual((0, ScenarioCoverage_1.uncoveredScenarios)(FEATURE, tests), []);
|
|
76
|
+
});
|
|
77
|
+
(0, node_test_1.test)("a tag identifies a scenario even after the title is reworded", () => {
|
|
78
|
+
// The one edit that otherwise defeats every link to a scenario.
|
|
79
|
+
const tagged = `Feature: F
|
|
80
|
+
|
|
81
|
+
@REQ-001 @SCN-042
|
|
82
|
+
Scenario: something else entirely now
|
|
83
|
+
Given a thing
|
|
84
|
+
When it happens
|
|
85
|
+
Then it holds
|
|
86
|
+
`;
|
|
87
|
+
assert.deepEqual((0, ScenarioCoverage_1.scenariosIn)(tagged)[0].needles, ["SCN-042"]);
|
|
88
|
+
assert.deepEqual((0, ScenarioCoverage_1.uncoveredScenarios)(tagged, ["covers @SCN-042 here"]), []);
|
|
89
|
+
});
|
|
90
|
+
(0, node_test_1.test)("without an id anywhere, the title is the identity", () => {
|
|
91
|
+
const untagged = `Feature: F
|
|
92
|
+
|
|
93
|
+
Scenario: the cart rejects a negative quantity
|
|
94
|
+
Given a cart
|
|
95
|
+
When a negative quantity is added
|
|
96
|
+
Then it is rejected
|
|
97
|
+
`;
|
|
98
|
+
assert.deepEqual((0, ScenarioCoverage_1.scenariosIn)(untagged)[0].needles, ["the cart rejects a negative quantity"]);
|
|
99
|
+
assert.deepEqual((0, ScenarioCoverage_1.uncoveredScenarios)(untagged, ['test("The cart rejects a negative quantity!", ...)']), [], "punctuation and case must not break a title match");
|
|
100
|
+
assert.equal((0, ScenarioCoverage_1.uncoveredScenarios)(untagged, ["something unrelated"]).length, 1);
|
|
101
|
+
});
|
|
102
|
+
(0, node_test_1.test)("a Scenario Outline counts — the widest scenarios must not be the unchecked ones", () => {
|
|
103
|
+
const outline = `Feature: F
|
|
104
|
+
|
|
105
|
+
Scenario Outline: SCN-050 rounding across currencies
|
|
106
|
+
Given <amount>
|
|
107
|
+
When it is rounded
|
|
108
|
+
Then it is <result>
|
|
109
|
+
|
|
110
|
+
Examples:
|
|
111
|
+
| amount | result |
|
|
112
|
+
| 1.005 | 1.01 |
|
|
113
|
+
`;
|
|
114
|
+
assert.equal((0, ScenarioCoverage_1.scenariosIn)(outline).length, 1);
|
|
115
|
+
assert.equal((0, ScenarioCoverage_1.uncoveredScenarios)(outline, ["nothing"]).length, 1);
|
|
116
|
+
});
|
|
117
|
+
(0, node_test_1.test)("a scenario proved in any of several test files is proved", () => {
|
|
118
|
+
const missing = (0, ScenarioCoverage_1.uncoveredScenarios)(FEATURE, ["SCN-010", "SCN-011", "SCN-012 over here"]);
|
|
119
|
+
assert.deepEqual(missing, []);
|
|
120
|
+
});
|
|
121
|
+
(0, node_test_1.test)("no test sources at all leaves every scenario uncovered", () => {
|
|
122
|
+
assert.equal((0, ScenarioCoverage_1.uncoveredScenarios)(FEATURE, []).length, 3);
|
|
123
|
+
});
|
|
124
|
+
(0, node_test_1.test)("normalise strips what should not decide a match", () => {
|
|
125
|
+
assert.equal((0, ScenarioCoverage_1.normalise)("SCN-010: Subtotal!"), (0, ScenarioCoverage_1.normalise)("scn010 subtotal"));
|
|
126
|
+
});
|
|
127
|
+
// ── Fase 1.2: a link that exists but lies ────────────────────────────────────
|
|
128
|
+
(0, node_test_1.test)("a test artifact naming the requirement is evidence enough", () => {
|
|
129
|
+
assert.equal((0, ScenarioCoverage_1.linkIsUnevidenced)("REQ-014", "", ['test("REQ-014 vets are listed")']), false);
|
|
130
|
+
});
|
|
131
|
+
(0, node_test_1.test)("a test artifact naming one of the requirement's scenarios is evidence", () => {
|
|
132
|
+
assert.equal((0, ScenarioCoverage_1.linkIsUnevidenced)("REQ-010", FEATURE, ["SCN-011 tax"]), false);
|
|
133
|
+
});
|
|
134
|
+
(0, node_test_1.test)("the measured lie: a seeded row pointed at somebody else's test", () => {
|
|
135
|
+
// A "Vet" requirement declaring PetValidatorTests.java as its proof. The row
|
|
136
|
+
// has no scenario, so there is nothing for the scenario check to match on —
|
|
137
|
+
// which is exactly why those rows lie most easily.
|
|
138
|
+
assert.equal((0, ScenarioCoverage_1.linkIsUnevidenced)("REQ-014", "", ["class PetTypeFormatterTests { void formatsAName() {} }"]), true);
|
|
139
|
+
});
|
|
140
|
+
(0, node_test_1.test)("no test sources at all is not evidence", () => {
|
|
141
|
+
assert.equal((0, ScenarioCoverage_1.linkIsUnevidenced)("REQ-014", FEATURE, []), true);
|
|
142
|
+
});
|
|
143
|
+
(0, node_test_1.test)("a row with no requirement id is not this check's business", () => {
|
|
144
|
+
assert.equal((0, ScenarioCoverage_1.linkIsUnevidenced)("", FEATURE, ["anything"]), false);
|
|
145
|
+
});
|
|
146
|
+
//# sourceMappingURL=scenario-coverage.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scenario-coverage.test.js","sourceRoot":"","sources":["../../../../../packages/core/test/unit/scenario-coverage.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Fase 3.1: `req add` wrote a matrix row and no prose anywhere.
|
|
4
|
+
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
+
}) : function(o, v) {
|
|
19
|
+
o["default"] = v;
|
|
20
|
+
});
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const node_test_1 = require("node:test");
|
|
40
|
+
const assert = __importStar(require("node:assert/strict"));
|
|
41
|
+
const SpecSections_1 = require("../../src/domain/SpecSections");
|
|
42
|
+
(0, node_test_1.test)("the generated section carries an obligation keyword", () => {
|
|
43
|
+
// Otherwise the section fails --strict-requirements, and a project would be
|
|
44
|
+
// failing a gate over text the tool itself just wrote.
|
|
45
|
+
const section = (0, SpecSections_1.renderRequirementSection)("REQ-014", "Vets are listed alphabetically");
|
|
46
|
+
assert.match(section, /^## REQ-014 — Vets are listed alphabetically$/m);
|
|
47
|
+
assert.match(section, /\bMUST\b/);
|
|
48
|
+
});
|
|
49
|
+
(0, node_test_1.test)("the section says it is a draft, rather than pretending to be finished", () => {
|
|
50
|
+
assert.match((0, SpecSections_1.renderRequirementSection)("REQ-014", "Vets are listed"), /Replace this sentence/);
|
|
51
|
+
});
|
|
52
|
+
(0, node_test_1.test)("a title starting with an identifier still reads as a sentence", () => {
|
|
53
|
+
// `MUST res.redirect sets Location` is not English. `MUST satisfy:` is, for
|
|
54
|
+
// every title, without a heuristic that guesses at grammar.
|
|
55
|
+
assert.match((0, SpecSections_1.renderRequirementSection)("REQ-002", "res.redirect sets Location and status 302"), /MUST satisfy: res\.redirect sets Location and status 302\./);
|
|
56
|
+
assert.match((0, SpecSections_1.renderRequirementSection)("REQ-003", "HTTP caching is respected"), /MUST satisfy: HTTP caching is respected\./);
|
|
57
|
+
});
|
|
58
|
+
(0, node_test_1.test)("an existing section is found, and a different id is not", () => {
|
|
59
|
+
const spec = "# Spec\n\n## REQ-001 — One\n\ntext\n\n## REQ-014 — Fourteen\n\ntext\n";
|
|
60
|
+
assert.equal((0, SpecSections_1.hasRequirementSection)(spec, "REQ-014"), true);
|
|
61
|
+
assert.equal((0, SpecSections_1.hasRequirementSection)(spec, "REQ-01"), false, "REQ-01 must not match REQ-014");
|
|
62
|
+
assert.equal((0, SpecSections_1.hasRequirementSection)(spec, "REQ-999"), false);
|
|
63
|
+
assert.deepEqual((0, SpecSections_1.requirementSections)(spec), ["REQ-001", "REQ-014"]);
|
|
64
|
+
});
|
|
65
|
+
(0, node_test_1.test)("a mention in prose is not a section, but a nested heading is", () => {
|
|
66
|
+
// The shipped template puts requirements under "8. Key requirements", so
|
|
67
|
+
// insisting on top-level `##` would reject documents that are perfectly well
|
|
68
|
+
// organised. A sentence naming the id is still not a section.
|
|
69
|
+
assert.equal((0, SpecSections_1.hasRequirementSection)("# Spec\n\nSee REQ-014 for details.\n", "REQ-014"), false);
|
|
70
|
+
assert.equal((0, SpecSections_1.hasRequirementSection)("# Spec\n\n### REQ-014 — Nested\n\ntext\n", "REQ-014"), true);
|
|
71
|
+
assert.equal((0, SpecSections_1.hasRequirementSection)("# Spec\n\n##### REQ-014 — Too deep\n", "REQ-014"), false);
|
|
72
|
+
});
|
|
73
|
+
(0, node_test_1.test)("a nested section ends at the next heading of its own level", () => {
|
|
74
|
+
const spec = [
|
|
75
|
+
"## 8. Key requirements",
|
|
76
|
+
"",
|
|
77
|
+
"### REQ-001 — First",
|
|
78
|
+
"",
|
|
79
|
+
"The system MUST do the first thing.",
|
|
80
|
+
"",
|
|
81
|
+
"#### Notes",
|
|
82
|
+
"",
|
|
83
|
+
"still part of REQ-001",
|
|
84
|
+
"",
|
|
85
|
+
"### REQ-002 — Second",
|
|
86
|
+
"",
|
|
87
|
+
"not part of REQ-001",
|
|
88
|
+
].join("\n");
|
|
89
|
+
const section = (0, SpecSections_1.extractRequirementSection)(spec, "REQ-001");
|
|
90
|
+
assert.match(section, /MUST do the first thing/);
|
|
91
|
+
assert.match(section, /still part of REQ-001/, "a deeper sub-heading stays inside");
|
|
92
|
+
assert.doesNotMatch(section, /not part of REQ-001/);
|
|
93
|
+
});
|
|
94
|
+
(0, node_test_1.test)("appending adds the section once and is idempotent", () => {
|
|
95
|
+
const before = "# Spec\n\n## REQ-001 — One\n\ntext\n";
|
|
96
|
+
const first = (0, SpecSections_1.appendRequirementSection)(before, "REQ-002", "Totals are rounded");
|
|
97
|
+
assert.equal(first.added, true);
|
|
98
|
+
assert.equal((0, SpecSections_1.hasRequirementSection)(first.content, "REQ-002"), true);
|
|
99
|
+
const second = (0, SpecSections_1.appendRequirementSection)(first.content, "REQ-002", "Totals are rounded");
|
|
100
|
+
assert.equal(second.added, false);
|
|
101
|
+
assert.equal(second.content, first.content, "running req add twice must not duplicate prose");
|
|
102
|
+
});
|
|
103
|
+
(0, node_test_1.test)("appending does not reorder what a person wrote", () => {
|
|
104
|
+
// spec.md is a document someone writes in. Sorting their prose to satisfy an
|
|
105
|
+
// id order is a worse surprise than a section at the end.
|
|
106
|
+
const before = "# Spec\n\n## REQ-050 — Later\n\ntext\n";
|
|
107
|
+
const after = (0, SpecSections_1.appendRequirementSection)(before, "REQ-002", "Earlier").content;
|
|
108
|
+
assert.ok(after.indexOf("REQ-050") < after.indexOf("REQ-002"));
|
|
109
|
+
});
|
|
110
|
+
(0, node_test_1.test)("an empty spec gets a section without a leading blank run", () => {
|
|
111
|
+
const { content } = (0, SpecSections_1.appendRequirementSection)("", "REQ-001", "First");
|
|
112
|
+
assert.ok(content.startsWith("## REQ-001"));
|
|
113
|
+
});
|
|
114
|
+
// ── The shapes real documents turned out to use ──────────────────────────────
|
|
115
|
+
(0, node_test_1.test)("a labelled heading declares its requirement", () => {
|
|
116
|
+
// `### Requirement: REQ-100 — title` is what capability specs use. Four
|
|
117
|
+
// shapes turned up before this stopped enumerating them.
|
|
118
|
+
const spec = "## Requirements\n\n### Requirement: REQ-100 — A change is a folder\n\ntext\n";
|
|
119
|
+
assert.equal((0, SpecSections_1.hasRequirementSection)(spec, "REQ-100"), true);
|
|
120
|
+
assert.match((0, SpecSections_1.extractRequirementSection)(spec, "REQ-100"), /A change is a folder/);
|
|
121
|
+
});
|
|
122
|
+
(0, node_test_1.test)("the §8 table counts as the requirement's text", () => {
|
|
123
|
+
// The original `init` template, and this repository's own spec.md. Outlawing
|
|
124
|
+
// it would have turned every project built from that template red on
|
|
125
|
+
// upgrade, over text that is right there in the document.
|
|
126
|
+
const spec = [
|
|
127
|
+
"## 8. Key requirements",
|
|
128
|
+
"",
|
|
129
|
+
"| ID | Requirement | Priority | Status |",
|
|
130
|
+
"|---|---|---|---|",
|
|
131
|
+
"| REQ-001 | `init` generates a valid project skeleton. | Must | Verified |",
|
|
132
|
+
"| REQ-002 | - | Must | Draft |",
|
|
133
|
+
].join("\n");
|
|
134
|
+
assert.equal((0, SpecSections_1.hasRequirementSection)(spec, "REQ-001"), true);
|
|
135
|
+
assert.match((0, SpecSections_1.extractRequirementSection)(spec, "REQ-001"), /generates a valid project skeleton/);
|
|
136
|
+
// A row with no prose is an id with nothing behind it — the case worth catching.
|
|
137
|
+
assert.equal((0, SpecSections_1.hasRequirementSection)(spec, "REQ-002"), false);
|
|
138
|
+
assert.deepEqual((0, SpecSections_1.requirementSections)(spec), ["REQ-001"]);
|
|
139
|
+
});
|
|
140
|
+
(0, node_test_1.test)("a TBD or empty requirement cell is not text", () => {
|
|
141
|
+
for (const cell of ["TBD", "", " ", "-"]) {
|
|
142
|
+
const spec = `| ID | Requirement |\n|---|---|\n| REQ-005 | ${cell} |\n`;
|
|
143
|
+
assert.equal((0, SpecSections_1.hasRequirementSection)(spec, "REQ-005"), false, `'${cell}' should not count`);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
//# sourceMappingURL=spec-sections.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spec-sections.test.js","sourceRoot":"","sources":["../../../../../packages/core/test/unit/spec-sections.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Fase 2.3: two detectors disagreed on the same pom.xml.
|
|
4
|
+
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
+
}) : function(o, v) {
|
|
19
|
+
o["default"] = v;
|
|
20
|
+
});
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const node_test_1 = require("node:test");
|
|
40
|
+
const assert = __importStar(require("node:assert/strict"));
|
|
41
|
+
const TestCommand_1 = require("../../src/domain/TestCommand");
|
|
42
|
+
function files(map) {
|
|
43
|
+
return {
|
|
44
|
+
exists: (p) => Object.prototype.hasOwnProperty.call(map, p),
|
|
45
|
+
read: (p) => (typeof map[p] === "string" ? map[p] : null),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
(0, node_test_1.test)("the wrapper wins, because the harness runs in a bare worktree", () => {
|
|
49
|
+
// `./mvnw` is checked in and works there; `mvn` depends on the machine.
|
|
50
|
+
// Getting this wrong burns one of the agent's three attempts on
|
|
51
|
+
// "command not found".
|
|
52
|
+
assert.equal((0, TestCommand_1.detectTestCommand)(files({ "pom.xml": true, mvnw: true })), "./mvnw -B test");
|
|
53
|
+
assert.equal((0, TestCommand_1.detectTestCommand)(files({ "build.gradle": true, gradlew: true })), "./gradlew test");
|
|
54
|
+
});
|
|
55
|
+
(0, node_test_1.test)("without a wrapper, the system tool is the only option", () => {
|
|
56
|
+
assert.equal((0, TestCommand_1.detectTestCommand)(files({ "pom.xml": true })), "mvn -B test");
|
|
57
|
+
assert.equal((0, TestCommand_1.detectTestCommand)(files({ "build.gradle.kts": true })), "gradle test");
|
|
58
|
+
});
|
|
59
|
+
(0, node_test_1.test)("a declared npm script is the project's own answer", () => {
|
|
60
|
+
assert.equal((0, TestCommand_1.detectTestCommand)(files({ "package.json": '{"scripts":{"test":"vitest"}}' })), "npm test");
|
|
61
|
+
});
|
|
62
|
+
(0, node_test_1.test)("a malformed package.json does not crash the caller", () => {
|
|
63
|
+
assert.equal((0, TestCommand_1.detectTestCommand)(files({ "package.json": "{ not json" })), "npm test");
|
|
64
|
+
});
|
|
65
|
+
(0, node_test_1.test)("the other ecosystems", () => {
|
|
66
|
+
assert.equal((0, TestCommand_1.detectTestCommand)(files({ "Cargo.toml": true })), "cargo test");
|
|
67
|
+
assert.equal((0, TestCommand_1.detectTestCommand)(files({ "go.mod": true })), "go test ./...");
|
|
68
|
+
assert.equal((0, TestCommand_1.detectTestCommand)(files({ "pyproject.toml": true })), "pytest");
|
|
69
|
+
assert.equal((0, TestCommand_1.detectTestCommand)(files({ "setup.py": true })), "pytest");
|
|
70
|
+
});
|
|
71
|
+
(0, node_test_1.test)("nothing recognisable returns null, and null is a real answer", () => {
|
|
72
|
+
// Guessing `npm test` into a Python repository is how AI_RULES.md ended up
|
|
73
|
+
// reading `Testing: unknown` two lines above `Test command: python -m pytest`.
|
|
74
|
+
assert.equal((0, TestCommand_1.detectTestCommand)(files({ "README.md": true })), null);
|
|
75
|
+
});
|
|
76
|
+
(0, node_test_1.test)("both callers would now agree on the same project", () => {
|
|
77
|
+
const project = files({ "pom.xml": true, mvnw: true });
|
|
78
|
+
assert.equal((0, TestCommand_1.detectTestCommand)(project), (0, TestCommand_1.detectTestCommand)(project));
|
|
79
|
+
assert.equal((0, TestCommand_1.detectTestCommand)(project), "./mvnw -B test");
|
|
80
|
+
});
|
|
81
|
+
//# sourceMappingURL=test-command.test.js.map
|