cyber-sdd 0.4.0 → 0.4.1

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.
@@ -6,7 +6,7 @@
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
+ import { validate } from 'gherkin-cli'
10
10
 
11
11
  // ─── types ────────────────────────────────────────────────────────────────────
12
12
 
@@ -143,7 +143,7 @@ export interface ParseError {
143
143
  // Maps the pinned parser's per-file report to its errors (empty array when it parses) so callers
144
144
  // can look a path up directly.
145
145
  export function runGherkinValidate(paths: string[]): Map<string, ParseError[]> {
146
- const { files } = validateFeatures(paths)
146
+ const { files } = validate(paths)
147
147
  const out = new Map<string, ParseError[]>()
148
148
  for (const f of files) {
149
149
  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 'gherkin-cli'
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
 
@@ -144,6 +144,7 @@ Once landed, **do not spawn** the formation Warden. Surface a **one-line nudge**
144
144
  ## Autonomy, provenance, and the hard floor (baked in)
145
145
 
146
146
  - **Dispatch transport.** Every spawn beyond this session states a **dispatch intent** — role, brief, expected verdict schema — never a pinned command. When a harness-agnostic dispatch capability is available (detected at runtime; the concrete case is the Legate's `dispatch-governance` composing `cyberlegion` primitives — `agent resolve` + `unit spawn` + `mail await` — with **no** `dispatch` CLI verb, the seam named in the SDD project spec's `design/harness-spawning` node, repo-only), route through its intent seam and let it pick `subagent | channel | run-inline`, **preferring a warm unit** over a cold one-shot spawn; with no capability present, fall back to the portable cold subagent (depth-1) default — grader independence intact either way. **Warmth is a property of the unit/process; coldness of the context**: a judge's fresh-context guarantee (ADR-0016) is transport-agnostic — satisfied by a newly spawned cold subagent **or** a warm unit **context-cleared** to a fresh context before **each** judgment (re-deriving its oracle, carrying none of a prior round's context). Clear a warm unit with **`npx cyberlegion@0.3.1 unit clear <ref>`** (`<ref>` = unit id / handle / worktree branch or CR ref) — it injects the harness's own fresh-context command (`/clear` on Claude/Codex/Copilot, `/new-chat` on Cursor; fail-loud on a harness with no honest reset) so the **pane stays warm** while the **context goes cold**; it tears nothing down. The **impl-producer builder** instead stays warm and **keeps** its context across the explore spikes and the deliver build (never cleared between those uses). Warm units stay warm for **one mission** — reused within it, then **`unit clear`**'d or torn down at **handoff**, never carrying this mission's context into the next.
147
+ - **SDD's own judges go to the seam by file, not by name.** A dispatch capability may resolve a definition by name only in the project's own agent folder (cyberlegion's `agent resolve` does), so it cannot find one SDD ships. When the judge role resolves to SDD's own `sdd-spec-judge` or `sdd-impl-judge`, locate the definition yourself at **`agents/<name>.md` under the SDD plugin root** — two levels above this skill's own base directory (`<skill dir>/../../agents/<name>.md`) — and hand the capability **that path** (cyberlegion: `agent resolve --file <path>`, `unit spawn --agent-file <path>`), never the bare name. If no file is there, send the capability **no request for that judge** (a by-name one would miss) and take the no-capability route: spawn it as a portable cold subagent through the harness's own plugin-agent spawn (`sdd:sdd-spec-judge` / `sdd:sdd-impl-judge`), which knows the plugin. A plugin-delegated judge is outside this rule.
147
148
  - **Initial strategy** (run start): assess blast radius + the other dimensions and emit a run-level `kind: leash` block to **your own ledger shard** (`ledger/<cr-ref>.<hash>.jsonl` — mint `<hash>` as 6 random hex **once per session** and reuse it for every line you append; `sdd:combat-log-governance`) — `leash` (`auto-none | auto-spec | auto-all`), `by: derived | user`, `approach[]`. It may be user-specified. This block is `kind: leash`, **not** `strategy` — `strategy` is the doctrine Scanner's alone. Ledger lines carry **no `ts`**.
148
149
  - **Per-gate verdict.** At each gate, derive the leash against discovered state and either **self-assert within leash** (write `approval.<gate>: { verdict: approve, by: agent, why }`; the spec lands in the async review queue) or **stop** with a verdict packet for the human. **Never advance** when any judge fails, any open marker remains, or (at the impl gate) any frozen scenario's verification does not pass. Human ratification (`by: <name>`, advance `status`) is reserved to the in-session position holding the user channel — by default you, in-session; a headless `automaton` emits the verdict packet and stops, **even when a coordinator relays "the user approved."**
149
150
  - **Combat log.** Append `report` / `correction` lines (and the halt that stopped you) to the plan's `*.log.jsonl` (these carry a UTC `ts`); your run-start `leash` block, self-asserted `gate` lines, and the handoff `followup` records go to **your own shard** in the durable `ledger/` directory sibling to `spec.md` — never another writer's shard, never a shared file (`strategy` there is the Scanner's alone). Free text is commit-message-grade — never code, prompts, secrets, or literal values.
@@ -13,7 +13,7 @@
13
13
  // a live git diff or the live mission-graph store.
14
14
  // - readChangedFiles / resolveArtifactType / changedScenarios / collectChangedFiles /
15
15
  // discoverLayouts are the thin IO SEAM: they shell out to `git`, `resolve-governances.mts`, and
16
- // the pinned `gherkin-cli@0.0.2` `diffFeatures` (the same differ classify-edit-class.mts uses —
16
+ // the pinned `gherkin-cli` `diff` (the same differ classify-edit-class.mts uses —
17
17
  // this tool never reimplements a differ). NOT unit-tested (binary/fs boundary) — the tested
18
18
  // logic is everything downstream of the file list.
19
19
  // - main() is a thin CLI: argv -> collectChangedFiles + assembleCorrection, rendering TOON by
@@ -30,7 +30,7 @@ import { execFileSync } from 'node:child_process'
30
30
  import { readFileSync, realpathSync } from 'node:fs'
31
31
  import { dirname, join, relative, resolve, sep } from 'node:path'
32
32
  import { fileURLToPath, pathToFileURL } from 'node:url'
33
- import { type DiffReader, diffFeatures } from 'gherkin-cli'
33
+ import { diff as gherkinDiff, type ReadsGitDiff } from 'gherkin-cli'
34
34
 
35
35
  // ── Types ──
36
36
 
@@ -226,7 +226,7 @@ export function resolveArtifactType(path: string, root: string, cwd: string): st
226
226
  }
227
227
  }
228
228
 
229
- // `diffFeatures`'s default reader resolves paths against `process.cwd()` and derives git's own cwd
229
+ // `diff`'s default reader resolves paths against `process.cwd()` and derives git's own cwd
230
230
  // from that resolved location (`git ls-files --full-name` to recover the repo-relative path) —
231
231
  // this reader is that same algorithm, re-pointed at `cwd` (same seam classify-edit-class.mts
232
232
  // uses), so it stays robust to a caller whose relative-path bookkeeping doesn't line up with its
@@ -234,7 +234,7 @@ export function resolveArtifactType(path: string, root: string, cwd: string): st
234
234
  // reads as "absent" — the outer `changedScenarios` catch-all is this call site's real fail-open
235
235
  // boundary, so a thrown `GitError` here is caught there rather than escalated.
236
236
  const cwdReader =
237
- (cwd: string): DiffReader =>
237
+ (cwd: string): ReadsGitDiff['readDiff'] =>
238
238
  (file, base) => {
239
239
  const abs = resolve(cwd, file)
240
240
  const dir = dirname(abs)
@@ -272,15 +272,15 @@ const cwdReader =
272
272
  return { head, base: baseText }
273
273
  }
274
274
 
275
- /** The changed scenario names of a touched `.feature`, via the pinned `gherkin-cli@0.0.2`
276
- * `diffFeatures` (same tool classify-edit-class.mts uses — never a reimplemented differ). Gated
275
+ /** The changed scenario names of a touched `.feature`, via the pinned `gherkin-cli`
276
+ * `diff` (same tool classify-edit-class.mts uses — never a reimplemented differ). Gated
277
277
  * by isFeature — a non-.feature never calls out. On any failure returns []. Reads any `.feature`
278
278
  * regardless of freeze — the freeze gate is a separate concern (spec-gate), not this tool's
279
279
  * business. */
280
280
  export function changedScenarios(base: string, path: string, cwd: string): string[] {
281
281
  if (!isFeature(path)) return []
282
282
  try {
283
- const { files } = diffFeatures([path], { base, reader: cwdReader(cwd) })
283
+ const { files } = gherkinDiff([path], { base, full: true }, { readDiff: cwdReader(cwd) })
284
284
  const fileResult = files.find((f) => f.file === path) ?? files[0]
285
285
  return (fileResult?.scenarios ?? []).filter((s) => s.change !== 'unchanged').map((s) => s.name)
286
286
  } catch {
@@ -49,7 +49,7 @@ import { execSync } from 'node:child_process'
49
49
  import { existsSync, readFileSync, realpathSync } from 'node:fs'
50
50
  import { isAbsolute, join } from 'node:path'
51
51
  import { pathToFileURL } from 'node:url'
52
- import { parseFeatures } from 'gherkin-cli'
52
+ import { parse } from 'gherkin-cli'
53
53
 
54
54
  // Resolves a path argument against `--root`: relative paths join beneath root (which defaults to the
55
55
  // current directory); an absolute path is used verbatim, never double-prefixed under root.
@@ -132,7 +132,7 @@ export function scenarioKeysFromParse(parsed: GherkinParseOutput): ScenarioKey[]
132
132
  // `--feature-root` — see main()).
133
133
  export function getScenarioKeys(root: string, featurePath: string, featureRoot: string = root): ScenarioKey[] {
134
134
  const abs = underRoot(featureRoot, featurePath)
135
- return scenarioKeysFromParse(parseFeatures([abs]))
135
+ return scenarioKeysFromParse(parse([abs]))
136
136
  }
137
137
 
138
138
  // ── JUnit parsing (hand-rolled, no xml dep) ──