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.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.plugin/plugin.json +1 -1
- package/agents/sdd-automaton.md +5 -1
- package/dist/align-spec/scripts/align-spec.js +102 -0
- package/dist/check-project-specs/scripts/check-project-specs.js +311 -0
- package/dist/check-scenario-overlap/scripts/check-scenario-overlap.js +175 -0
- package/dist/check-spec-references/scripts/check-spec-references.js +285 -0
- package/dist/check-spec-structure/scripts/check-spec-structure.js +241 -0
- package/dist/concept-index/scripts/concept-index.js +181 -0
- package/dist/discover-specs/scripts/discover-specs.js +318 -0
- package/dist/spec-gate/scripts/check-spec-state.js +420 -0
- package/dist/spec-gate/scripts/check-suite.js +317 -0
- package/dist/spec-gate/scripts/classify-edit-class.js +263 -0
- package/dist/spec-gate/vendor/gherkin-cli.js +6369 -0
- package/license +21 -0
- package/package.json +12 -3
- package/skills/check-project-specs/scripts/check-project-specs.mts +13 -2
- package/skills/concept-index/scripts/concept-index.mts +2 -2
- package/skills/spec-gate/README.md +9 -0
- package/skills/spec-gate/scripts/check-suite.mts +3 -2
- package/skills/spec-gate/scripts/classify-edit-class.mts +7 -7
- package/skills/spec-gate/vendor/gherkin-cli.d.mts +2 -0
- package/skills/spec-gate/vendor/gherkin-cli.mjs +6545 -0
- package/skills/start-mission/SKILL.md +1 -0
- package/skills/touch-set-correction/README.md +5 -0
- package/skills/touch-set-correction/scripts/touch-set-correction.mts +7 -7
- package/skills/verify-scenarios/README.md +6 -1
- 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.
|
|
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": "./
|
|
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.
|
|
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
|
|
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', [
|
|
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 =
|
|
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
|
-
|
|
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 } =
|
|
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
|
|
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 {
|
|
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` `
|
|
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 (`
|
|
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
|
-
// `
|
|
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):
|
|
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
|
-
|
|
252
|
+
gherkinDiff(paths, { base, full: true }, { readDiff: makeCwdReader(cwd) })
|
|
253
253
|
|
|
254
254
|
// ─── per-file classification ────────────────────────────────────────────────────
|
|
255
255
|
|