@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.
Files changed (121) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/README.md +8 -8
  3. package/bin/specgate.ts +13 -2
  4. package/dist/bin/specgate.js +11 -1
  5. package/dist/bin/specgate.js.map +1 -1
  6. package/dist/packages/core/src/application/GenerateAgentPromptUseCase.js +56 -0
  7. package/dist/packages/core/src/application/GenerateAgentPromptUseCase.js.map +1 -1
  8. package/dist/packages/core/src/application/ValidateProjectUseCase.js +54 -0
  9. package/dist/packages/core/src/application/ValidateProjectUseCase.js.map +1 -1
  10. package/dist/packages/core/src/domain/AdversarialProbe.js +75 -0
  11. package/dist/packages/core/src/domain/AdversarialProbe.js.map +1 -0
  12. package/dist/packages/core/src/domain/AgentPrompt.js +52 -3
  13. package/dist/packages/core/src/domain/AgentPrompt.js.map +1 -1
  14. package/dist/packages/core/src/domain/DeclaredArtifacts.js +25 -0
  15. package/dist/packages/core/src/domain/DeclaredArtifacts.js.map +1 -1
  16. package/dist/packages/core/src/domain/DoneVerification.js +68 -0
  17. package/dist/packages/core/src/domain/DoneVerification.js.map +1 -0
  18. package/dist/packages/core/src/domain/GateCommandIntegrity.js +139 -0
  19. package/dist/packages/core/src/domain/GateCommandIntegrity.js.map +1 -0
  20. package/dist/packages/core/src/domain/HarnessConfig.js +6 -0
  21. package/dist/packages/core/src/domain/HarnessConfig.js.map +1 -1
  22. package/dist/packages/core/src/domain/MultiStack.js +96 -0
  23. package/dist/packages/core/src/domain/MultiStack.js.map +1 -0
  24. package/dist/packages/core/src/domain/Precedents.js +96 -0
  25. package/dist/packages/core/src/domain/Precedents.js.map +1 -0
  26. package/dist/packages/core/src/domain/RequirementRemoval.js +161 -0
  27. package/dist/packages/core/src/domain/RequirementRemoval.js.map +1 -0
  28. package/dist/packages/core/src/domain/ScenarioCoverage.js +178 -0
  29. package/dist/packages/core/src/domain/ScenarioCoverage.js.map +1 -0
  30. package/dist/packages/core/src/domain/SpecSections.js +206 -0
  31. package/dist/packages/core/src/domain/SpecSections.js.map +1 -0
  32. package/dist/packages/core/src/domain/TestCommand.js +82 -0
  33. package/dist/packages/core/src/domain/TestCommand.js.map +1 -0
  34. package/dist/packages/core/src/domain/TraceabilityFormat.js +37 -1
  35. package/dist/packages/core/src/domain/TraceabilityFormat.js.map +1 -1
  36. package/dist/packages/core/src/infrastructure/HarnessConfigFile.js +31 -0
  37. package/dist/packages/core/src/infrastructure/HarnessConfigFile.js.map +1 -1
  38. package/dist/packages/core/test/unit/adversarial-probe.test.js +81 -0
  39. package/dist/packages/core/test/unit/adversarial-probe.test.js.map +1 -0
  40. package/dist/packages/core/test/unit/done-verification.test.js +83 -0
  41. package/dist/packages/core/test/unit/done-verification.test.js.map +1 -0
  42. package/dist/packages/core/test/unit/gate-command-integrity.test.js +114 -0
  43. package/dist/packages/core/test/unit/gate-command-integrity.test.js.map +1 -0
  44. package/dist/packages/core/test/unit/multi-stack.test.js +86 -0
  45. package/dist/packages/core/test/unit/multi-stack.test.js.map +1 -0
  46. package/dist/packages/core/test/unit/precedents.test.js +99 -0
  47. package/dist/packages/core/test/unit/precedents.test.js.map +1 -0
  48. package/dist/packages/core/test/unit/requirement-removal.test.js +124 -0
  49. package/dist/packages/core/test/unit/requirement-removal.test.js.map +1 -0
  50. package/dist/packages/core/test/unit/scenario-coverage.test.js +146 -0
  51. package/dist/packages/core/test/unit/scenario-coverage.test.js.map +1 -0
  52. package/dist/packages/core/test/unit/spec-sections.test.js +146 -0
  53. package/dist/packages/core/test/unit/spec-sections.test.js.map +1 -0
  54. package/dist/packages/core/test/unit/test-command.test.js +81 -0
  55. package/dist/packages/core/test/unit/test-command.test.js.map +1 -0
  56. package/dist/packages/core/test/unit/validate-project.test.js +21 -0
  57. package/dist/packages/core/test/unit/validate-project.test.js.map +1 -1
  58. package/dist/scripts/agents/commands.js +1 -1
  59. package/dist/scripts/agents/commands.js.map +1 -1
  60. package/dist/scripts/agents/init.js +1 -1
  61. package/dist/scripts/agents/init.js.map +1 -1
  62. package/dist/scripts/change/instructions.js +2 -2
  63. package/dist/scripts/change/instructions.js.map +1 -1
  64. package/dist/scripts/cli/commands/change/ChangeCommand.js +2 -2
  65. package/dist/scripts/cli/commands/change/ChangeCommand.js.map +1 -1
  66. package/dist/scripts/cli/commands/config/CiInitCommand.js +1 -1
  67. package/dist/scripts/cli/commands/config/CiInitCommand.js.map +1 -1
  68. package/dist/scripts/cli/commands/harness/RunCommand.js +203 -4
  69. package/dist/scripts/cli/commands/harness/RunCommand.js.map +1 -1
  70. package/dist/scripts/cli/commands/pack/InferPackCommand.js +331 -31
  71. package/dist/scripts/cli/commands/pack/InferPackCommand.js.map +1 -1
  72. package/dist/scripts/cli/commands/project/AdoptCommand.js +81 -4
  73. package/dist/scripts/cli/commands/project/AdoptCommand.js.map +1 -1
  74. package/dist/scripts/cli/commands/project/InitProjectCommand.js +245 -45
  75. package/dist/scripts/cli/commands/project/InitProjectCommand.js.map +1 -1
  76. package/dist/scripts/cli/commands/quality/DoctorCommand.js +20 -2
  77. package/dist/scripts/cli/commands/quality/DoctorCommand.js.map +1 -1
  78. package/dist/scripts/cli/commands/quality/FixCommand.js +1 -1
  79. package/dist/scripts/cli/commands/quality/FixCommand.js.map +1 -1
  80. package/dist/scripts/cli/commands/quality/ReportCommand.js +1 -1
  81. package/dist/scripts/cli/commands/quality/ReportCommand.js.map +1 -1
  82. package/dist/scripts/cli/commands/quality/ValidateSpecsCommand.js +340 -18
  83. package/dist/scripts/cli/commands/quality/ValidateSpecsCommand.js.map +1 -1
  84. package/dist/scripts/cli/commands/spec/DoneCommand.js +108 -0
  85. package/dist/scripts/cli/commands/spec/DoneCommand.js.map +1 -1
  86. package/dist/scripts/cli/commands/spec/PlanCommand.js +7 -1
  87. package/dist/scripts/cli/commands/spec/PlanCommand.js.map +1 -1
  88. package/dist/scripts/cli/commands/spec/ReqCommand.js +112 -2
  89. package/dist/scripts/cli/commands/spec/ReqCommand.js.map +1 -1
  90. package/dist/scripts/cli/commands/spec/StatusCommand.js +2 -2
  91. package/dist/scripts/cli/commands/spec/StatusCommand.js.map +1 -1
  92. package/dist/scripts/docs/build-site.js +145 -35
  93. package/dist/scripts/docs/build-site.js.map +1 -1
  94. package/dist/scripts/docs/components.js +208 -0
  95. package/dist/scripts/docs/components.js.map +1 -0
  96. package/dist/scripts/docs/highlight.js +180 -0
  97. package/dist/scripts/docs/highlight.js.map +1 -0
  98. package/dist/scripts/docs/icons.js +63 -0
  99. package/dist/scripts/docs/icons.js.map +1 -0
  100. package/dist/scripts/docs/nav.js +9 -3
  101. package/dist/scripts/docs/nav.js.map +1 -1
  102. package/dist/scripts/gen_mcp_tools.js +22 -2
  103. package/dist/scripts/gen_mcp_tools.js.map +1 -1
  104. package/dist/scripts/harness/init.js +101 -40
  105. package/dist/scripts/harness/init.js.map +1 -1
  106. package/dist/scripts/harness/worktree-lock.js +161 -0
  107. package/dist/scripts/harness/worktree-lock.js.map +1 -0
  108. package/dist/scripts/infer_pack.js +4 -1
  109. package/dist/scripts/infer_pack.js.map +1 -1
  110. package/dist/scripts/lib/surface.js +35 -7
  111. package/dist/scripts/lib/surface.js.map +1 -1
  112. package/package.json +5 -2
  113. package/templates/adopt/AI_RULES.md.tpl +1 -1
  114. package/templates/adopt/spec.md.tpl +4 -4
  115. package/templates/base/spec.md.tpl +12 -0
  116. package/templates/ci/azure.yml.tpl +2 -2
  117. package/templates/ci/github.yml.tpl +2 -2
  118. package/templates/ci/gitlab.yml.tpl +1 -1
  119. package/templates/ci/jenkins.tpl +1 -1
  120. package/templates/contracts/test-strategy.md.tpl +2 -2
  121. 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"}