cyber-sdd 0.4.0 → 0.4.2

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 (29) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/.plugin/plugin.json +1 -1
  4. package/agents/sdd-automaton.md +5 -1
  5. package/dist/align-spec/scripts/align-spec.js +102 -0
  6. package/dist/check-project-specs/scripts/check-project-specs.js +311 -0
  7. package/dist/check-scenario-overlap/scripts/check-scenario-overlap.js +175 -0
  8. package/dist/check-spec-references/scripts/check-spec-references.js +285 -0
  9. package/dist/check-spec-structure/scripts/check-spec-structure.js +241 -0
  10. package/dist/concept-index/scripts/concept-index.js +181 -0
  11. package/dist/discover-specs/scripts/discover-specs.js +318 -0
  12. package/dist/spec-gate/scripts/check-spec-state.js +420 -0
  13. package/dist/spec-gate/scripts/check-suite.js +317 -0
  14. package/dist/spec-gate/scripts/classify-edit-class.js +263 -0
  15. package/dist/spec-gate/vendor/gherkin-cli.js +6369 -0
  16. package/license +21 -0
  17. package/package.json +12 -3
  18. package/skills/check-project-specs/scripts/check-project-specs.mts +13 -2
  19. package/skills/concept-index/scripts/concept-index.mts +2 -2
  20. package/skills/spec-gate/README.md +9 -0
  21. package/skills/spec-gate/scripts/check-suite.mts +3 -2
  22. package/skills/spec-gate/scripts/classify-edit-class.mts +7 -7
  23. package/skills/spec-gate/vendor/gherkin-cli.d.mts +2 -0
  24. package/skills/spec-gate/vendor/gherkin-cli.mjs +6545 -0
  25. package/skills/start-mission/SKILL.md +1 -0
  26. package/skills/touch-set-correction/README.md +5 -0
  27. package/skills/touch-set-correction/scripts/touch-set-correction.mts +7 -7
  28. package/skills/verify-scenarios/README.md +6 -1
  29. package/skills/verify-scenarios/scripts/verify-scenarios.mts +2 -2
package/license ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Unional
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cyber-sdd",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Spec-Driven Development. Scaffold, validate, and maintain behavioral specs (spec.md + .feature files) for software features.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -10,9 +10,10 @@
10
10
  "license": "MIT",
11
11
  "type": "module",
12
12
  "bin": {
13
- "sdd-check-specs": "./skills/check-project-specs/scripts/check-project-specs.mts"
13
+ "sdd-check-specs": "./dist/check-project-specs/scripts/check-project-specs.js"
14
14
  },
15
15
  "files": [
16
+ "dist",
16
17
  "skills",
17
18
  "agents",
18
19
  ".plugin",
@@ -21,10 +22,18 @@
21
22
  "!skills/**/*.test.mts"
22
23
  ],
23
24
  "dependencies": {
24
- "gherkin-cli": "0.0.2"
25
+ "gherkin-cli": "0.2.1"
25
26
  },
26
27
  "homepage": "https://github.com/cyberuni/cyber-sdd",
28
+ "devDependencies": {
29
+ "tsdown": "^0.23.0"
30
+ },
31
+ "engines": {
32
+ "node": ">=22.11"
33
+ },
27
34
  "scripts": {
35
+ "build": "tsdown",
36
+ "build:vendor": "tsdown -c tsdown.vendor.config.ts",
28
37
  "check:spec": "node ./skills/check-project-specs/scripts/check-project-specs.mts",
29
38
  "test": "node --test \"skills/*/scripts/*.test.mts\"",
30
39
  "typecheck": "tsc -p tsconfig.json"
@@ -20,7 +20,7 @@ import {
20
20
  type SpecRecord,
21
21
  } from '../../discover-specs/scripts/discover-specs.mts'
22
22
 
23
- const SKILLS_DIR = resolve(dirname(fileURLToPath(import.meta.url)), '../..')
23
+ const HERE = fileURLToPath(import.meta.url)
24
24
 
25
25
  // ─── the engine set ───────────────────────────────────────────────────────────
26
26
 
@@ -61,6 +61,17 @@ export const ENGINES: Engine[] = [
61
61
  },
62
62
  ]
63
63
 
64
+ /**
65
+ * Where an engine runs from. The .mts source runs its siblings in the skills tree; the
66
+ * published bin runs from dist/, which mirrors that tree compiled to .js (tsdown.config.ts)
67
+ * — Node will not strip types under node_modules, so the bin must never reach back into
68
+ * the .mts tree.
69
+ */
70
+ export function enginePath(e: Engine, here: string = HERE): string {
71
+ const skillsDir = resolve(dirname(here), '../..')
72
+ return join(skillsDir, here.endsWith('.mts') ? e.script : e.script.replace(/\.mts$/, '.js'))
73
+ }
74
+
64
75
  // ─── repo root ────────────────────────────────────────────────────────────────
65
76
 
66
77
  /** Walk up from `start` for the workspace marker. Returns '' when not found. */
@@ -200,7 +211,7 @@ function runEngines(repoRoot: string, specDir: string): number {
200
211
  // cwd is the repo root, not the project dir: the engines resolve
201
212
  // repo-root-relative references against process.cwd().
202
213
  try {
203
- execFileSync('node', [join(SKILLS_DIR, e.script), ...e.args(specDir)], {
214
+ execFileSync('node', [enginePath(e), ...e.args(specDir)], {
204
215
  cwd: repoRoot,
205
216
  stdio: 'inherit',
206
217
  })
@@ -10,7 +10,7 @@
10
10
  // are exported for node:test; running the file directly drives the CLI.
11
11
 
12
12
  import { existsSync, readdirSync, readFileSync, realpathSync, writeFileSync } from 'node:fs'
13
- import { join } from 'node:path'
13
+ import { join, relative } from 'node:path'
14
14
  import { pathToFileURL } from 'node:url'
15
15
 
16
16
  export const BEGIN_MARKER = '<!-- BEGIN generated: by-concept (project-spec/concept-index) -->'
@@ -120,7 +120,7 @@ function walk(dir: string, specDir: string, out: NodeRecord[]): void {
120
120
  } else if (entry.name.endsWith('.md')) {
121
121
  const fm = parseFrontmatter(readFileSync(full, 'utf8'))
122
122
  if (!fm || fm.concepts.length === 0) continue
123
- const relPath = full.slice(specDir.length + 1).replace(/\\/g, '/')
123
+ const relPath = relative(specDir, full).replace(/\\/g, '/')
124
124
  out.push({
125
125
  relPath,
126
126
  display: displayPath(relPath),
@@ -24,3 +24,12 @@ References `sdd:lifecycle-governance`, `sdd:ownership-governance`, `sdd:gate-val
24
24
  reconcile); run via `pnpm verify:specs-new`. Tested by `check-spec-state.test.mts`.
25
25
  - `check-suite.mts` — deterministic `.feature`-form validator (Gherkin validity, boolean-`Then`
26
26
  form, scenario ordering); run via `pnpm verify:specs-new`. Tested by `check-suite.test.mts`.
27
+ It imports gherkin-cli from `../vendor/gherkin-cli.mjs`, a committed self-contained bundle, so it
28
+ runs from an installed plugin, which has no `node_modules`. `pnpm build:vendor` rebuilds the
29
+ bundle; `check-suite-installed.test.mts` runs the engine from a copy of the plugin's files and
30
+ fails while the bundle is stale.
31
+ - `classify-edit-class.mts` — the structural edit-class classifier for touched frozen `.feature`
32
+ files. It imports the same `../vendor/gherkin-cli.mjs` bundle; `classify-edit-class-installed.test.mts`
33
+ runs it from a copy of the plugin's files.
34
+ - `vendor/gherkin-cli.mjs` is shared: `touch-set-correction` and `verify-scenarios` import it from
35
+ here too, so the plugin ships one copy.
@@ -6,7 +6,8 @@
6
6
  import { type Dirent, readdirSync, readFileSync, realpathSync } from 'node:fs'
7
7
  import { basename, dirname, join } from 'node:path'
8
8
  import { pathToFileURL } from 'node:url'
9
- import { validateFeatures } from 'gherkin-cli'
9
+ // The bundled copy, not the package: an installed plugin has no node_modules to resolve it from.
10
+ import { validate } from '../vendor/gherkin-cli.mjs'
10
11
 
11
12
  // ─── types ────────────────────────────────────────────────────────────────────
12
13
 
@@ -143,7 +144,7 @@ export interface ParseError {
143
144
  // Maps the pinned parser's per-file report to its errors (empty array when it parses) so callers
144
145
  // can look a path up directly.
145
146
  export function runGherkinValidate(paths: string[]): Map<string, ParseError[]> {
146
- const { files } = validateFeatures(paths)
147
+ const { files } = validate(paths)
147
148
  const out = new Map<string, ParseError[]>()
148
149
  for (const f of files) {
149
150
  out.set(
@@ -9,7 +9,7 @@
9
9
  // The classification is STRUCTURAL, never a raw git line-diff. A raw line-diff is fooled by a
10
10
  // trailing step orphaned off a frozen scenario onto a newly added adjacent scenario: the orphan
11
11
  // shows no `-` line and reads as purely additive, so a narrowing self-clears silently and
12
- // Clearance never fires. The pinned `gherkin-cli@0.0.2` `diffFeatures(paths, {base})` is AST-level
12
+ // Clearance never fires. The pinned `gherkin-cli` `diffFeatures(paths, {base})` is AST-level
13
13
  // and is not fooled — it reports the losing baseline scenario as `modified` (`addOnly: false`).
14
14
  //
15
15
  // The pin is load-bearing, not incidental. Through `0.0.1` the differ's scenario identity covered
@@ -36,7 +36,7 @@ import { execFileSync } from 'node:child_process'
36
36
  import { readFileSync, realpathSync } from 'node:fs'
37
37
  import { dirname, join, relative, resolve, sep } from 'node:path'
38
38
  import { pathToFileURL } from 'node:url'
39
- import { type DiffReader, diffFeatures, GitError } from 'gherkin-cli'
39
+ import { GitError, diff as gherkinDiff, type ReadsGitDiff } from '../vendor/gherkin-cli.mjs'
40
40
 
41
41
  // ─── types ────────────────────────────────────────────────────────────────────
42
42
 
@@ -193,13 +193,13 @@ function readGitShow(base: string, path: string, cwd: string): string {
193
193
  }
194
194
  }
195
195
 
196
- // Thin boundary around the pinned `gherkin-cli` `diffFeatures` — never a re-implemented differ.
196
+ // Thin boundary around the pinned `gherkin-cli` `diff` — never a re-implemented differ.
197
197
  // classifyFromDiff / classifyFromFileResult carry the tested logic; this only wires the engine in.
198
- // Takes a batch of paths (`diffFeatures` is already variadic over paths) so a multi-file caller
198
+ // Takes a batch of paths (`diff` is already variadic over paths) so a multi-file caller
199
199
  // pays for one parse pass, not one per file.
200
200
  export type GherkinDiffRunner = (base: string, paths: string[], cwd: string) => GherkinDiffOutput
201
201
 
202
- // `diffFeatures`'s default reader resolves each path via `path.resolve(file)` against
202
+ // `diff`'s default reader resolves each path via `path.resolve(file)` against
203
203
  // `process.cwd()` and derives git's own cwd from THAT resolved location (`dirname` of the
204
204
  // resolved path, then `git ls-files --full-name` to recover the repo-relative path) — it never
205
205
  // trusts a caller-supplied cwd for the git commands at all. That self-derivation is why the
@@ -208,7 +208,7 @@ export type GherkinDiffRunner = (base: string, paths: string[], cwd: string) =>
208
208
  // This reader is the library's own extension seam ("Injectable so tests can skip git"), replicated
209
209
  // verbatim with the one substitution that matters here — `resolve(cwd, file)` instead of
210
210
  // `resolve(file)` — so a caller's `cwd` participates without discarding that self-correction.
211
- function makeCwdReader(cwd: string): DiffReader {
211
+ function makeCwdReader(cwd: string): ReadsGitDiff['readDiff'] {
212
212
  return (file, base) => {
213
213
  const abs = resolve(cwd, file)
214
214
  const dir = dirname(abs)
@@ -249,7 +249,7 @@ function makeCwdReader(cwd: string): DiffReader {
249
249
  }
250
250
 
251
251
  export const runGherkinDiff: GherkinDiffRunner = (base, paths, cwd) =>
252
- diffFeatures(paths, { base, reader: makeCwdReader(cwd) })
252
+ gherkinDiff(paths, { base, full: true }, { readDiff: makeCwdReader(cwd) })
253
253
 
254
254
  // ─── per-file classification ────────────────────────────────────────────────────
255
255
 
@@ -0,0 +1,2 @@
1
+ // Types for the bundled gherkin-cli beside this file: the same module, so the same types.
2
+ export * from 'gherkin-cli'