opencode-swarm 7.100.1 → 7.101.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.
@@ -1,4 +1,4 @@
1
- export type EffectiveSpecSource = 'swarm' | 'openspec_projection';
1
+ export type EffectiveSpecSource = 'swarm' | 'openspec_projection' | 'speckit_projection';
2
2
  export interface OpenSpecArtifact {
3
3
  relPath: string;
4
4
  bytes: number;
@@ -29,17 +29,218 @@ export interface SddStatus {
29
29
  errors: string[];
30
30
  warnings: string[];
31
31
  }
32
- export declare function loadSddStatusSync(directory: string): SddStatus;
32
+ /** A single Spec-Kit feature directory containing a spec.md. */
33
+ export interface SpeckitFeatureEntry {
34
+ /** Full directory name, e.g. `001-feature-name`. */
35
+ featureId: string;
36
+ /** Posix-normalized path relative to the repo root, e.g. `specs/001-feature-name/spec.md`. */
37
+ specRelPath: string;
38
+ }
39
+ /** Result returned by {@link detectSpeckit}. */
40
+ export interface SpeckitDetection {
41
+ /** Whether the `.specify/` marker directory is present at the repo root (A-001). */
42
+ markerPresent: boolean;
43
+ /**
44
+ * Detected feature directories, sorted lexicographically by {@link SpeckitFeatureEntry.featureId}.
45
+ * Empty when no `specs/<feature>/spec.md` files are found (or when markerPresent is false).
46
+ */
47
+ features: SpeckitFeatureEntry[];
48
+ }
49
+ /**
50
+ * Discriminated union returned by {@link resolveSpeckitProjection} (task 1.4).
51
+ *
52
+ * Each kind carries exactly the information the command layer (task 2.2) needs to
53
+ * produce the correct error message per FR-008, FR-012, FR-013 without re-detecting.
54
+ *
55
+ * - `not_speckit` — no `.specify/` marker at the repo root (A-001).
56
+ * - `empty` — marker present but no `specs/NNN/spec.md` feature dirs (FR-012).
57
+ * - `ambiguous` — more than one feature and no `options.feature` given (FR-008);
58
+ * `features` = sorted feature ids for naming in the error message.
59
+ * - `unknown_feature` — `options.feature` was given but matches no detected feature.
60
+ * - `zero_requirements`— the selected feature's spec.md yielded zero parsable functional
61
+ * requirements (covers unreadable/oversized *input* files too) (FR-013).
62
+ * - `too_large` — requirements parsed, but the projected *output* exceeds the byte
63
+ * cap and is refused; `bytes` is the projected size. Distinct from
64
+ * zero_requirements so the command layer reports the real reason.
65
+ * - `ok` — a valid projection was built; `spec` is ready for use, `feature`
66
+ * identifies the projected feature dir name.
67
+ */
68
+ export type SpeckitResolution = {
69
+ kind: 'not_speckit';
70
+ } | {
71
+ kind: 'empty';
72
+ } | {
73
+ kind: 'ambiguous';
74
+ features: string[];
75
+ } | {
76
+ kind: 'unknown_feature';
77
+ feature: string;
78
+ available: string[];
79
+ } | {
80
+ kind: 'zero_requirements';
81
+ feature: string;
82
+ } | {
83
+ kind: 'too_large';
84
+ feature: string;
85
+ bytes: number;
86
+ } | {
87
+ kind: 'ok';
88
+ spec: EffectiveSpec;
89
+ feature: string;
90
+ };
91
+ /**
92
+ * Detect whether `directory` is a GitHub Spec-Kit project (FR-001, A-001).
93
+ *
94
+ * Detection key: the `.specify/` marker directory at the repo root.
95
+ * A repo with `specs/` but no `.specify/` is NOT a Spec-Kit repo (A-001).
96
+ *
97
+ * When the marker is present, enumerates all direct children of `specs/` that
98
+ * contain a `spec.md` file. The enumeration is:
99
+ * - One level deep (depth is bounded by construction — stronger than MAX_WALK_DEPTH).
100
+ * - Bounded by MAX_SPEC_FILES.
101
+ * - Symlink-safe: directories and spec.md files that are symlinks are skipped.
102
+ * - Size-bounded: spec.md files exceeding MAX_SOURCE_BYTES are skipped.
103
+ * - Error-swallowing: unreadable directories are skipped silently.
104
+ * - Deterministic: feature entries are sorted lexicographically by featureId.
105
+ *
106
+ * Does NOT read or parse spec.md content — detection only (FR-001).
107
+ */
108
+ export declare function detectSpeckit(directory: string): SpeckitDetection;
109
+ /**
110
+ * Resolve a Spec-Kit feature projection with a discriminated result (FR-008, FR-012, FR-013).
111
+ *
112
+ * Returns one of six kinds so the command layer can produce the right error message without
113
+ * re-running detection:
114
+ * - `not_speckit` — no `.specify/` marker (A-001).
115
+ * - `empty` — marker present but no feature dirs (FR-012).
116
+ * - `ambiguous` — multiple features, no `options.feature` (FR-008).
117
+ * - `unknown_feature` — `options.feature` not among detected features.
118
+ * - `zero_requirements` — feature found but yields zero parsable FRs, or spec.md unreadable
119
+ * (FR-013; covers oversized files too).
120
+ * - `ok` — valid EffectiveSpec built (FR-002, FR-003, FR-004, FR-005).
121
+ *
122
+ * This function is the single source of truth for feature selection logic.
123
+ * {@link buildSpeckitProjectionSync} delegates here and maps `ok → spec | null`.
124
+ */
125
+ export declare function resolveSpeckitProjection(directory: string, options?: {
126
+ feature?: string;
127
+ }): SpeckitResolution;
128
+ /**
129
+ * Project a single Spec-Kit feature into an EffectiveSpec (FR-002, FR-003, FR-004, FR-005).
130
+ *
131
+ * Delegates all selection and build logic to {@link resolveSpeckitProjection} — that function
132
+ * is the single source of truth for feature selection. This wrapper preserves the existing
133
+ * `EffectiveSpec | null` contract so all call sites (task 2.2, tests) are unchanged.
134
+ *
135
+ * Returns null when resolution is anything other than `ok` (not_speckit, empty, ambiguous,
136
+ * unknown_feature, zero_requirements). Callers that need the failure reason should call
137
+ * {@link resolveSpeckitProjection} directly.
138
+ */
139
+ export declare function buildSpeckitProjectionSync(directory: string, options?: {
140
+ feature?: string;
141
+ }): EffectiveSpec | null;
142
+ export declare function loadSddStatusSync(directory: string, opts?: ReadEffectiveSpecOpts): SddStatus;
33
143
  export declare function buildOpenSpecProjectionSync(directory: string, options?: {
34
144
  changeId?: string;
35
145
  }): EffectiveSpec | null;
36
- export declare function readEffectiveSpecSync(directory: string): EffectiveSpec | null;
146
+ /**
147
+ * Resolver options for {@link readEffectiveSpecSync} (task 2.1, FR-009).
148
+ *
149
+ * Both fields are optional so that all 13 existing single-argument call sites
150
+ * continue to work unchanged.
151
+ */
152
+ export interface ReadEffectiveSpecOpts {
153
+ /**
154
+ * Explicit provider selection (FR-009 step b).
155
+ * When given, the specified provider is used directly after the native swarm
156
+ * spec check. A `'swarm'` selection with no `.swarm/spec.md` returns null.
157
+ */
158
+ source?: 'swarm' | 'openspec' | 'speckit';
159
+ /**
160
+ * Feature selector forwarded to {@link buildSpeckitProjectionSync} when the
161
+ * effective provider is Spec-Kit. Ignored for `source: 'openspec'` or
162
+ * `source: 'swarm'` (feature-vs-non-speckit source validation is enforced at
163
+ * the command layer in task 2.2, not here).
164
+ */
165
+ feature?: string;
166
+ }
167
+ /**
168
+ * Read (or build) the effective spec for a directory, applying deterministic
169
+ * source precedence (FR-009, FR-010, FR-011).
170
+ *
171
+ * **Precedence:**
172
+ * a. `.swarm/spec.md` present → return the native swarm spec. This branch is
173
+ * IDENTICAL to the pre-task-2.1 code and wins even over an explicit
174
+ * `opts.source` — native swarm spec always wins (FR-009).
175
+ * b. `opts.source` given → use that provider: `'openspec'` calls
176
+ * {@link buildOpenSpecProjectionSync}; `'speckit'` calls
177
+ * {@link buildSpeckitProjectionSync}; `'swarm'` with no `.swarm/spec.md`
178
+ * returns null.
179
+ * c. Auto-detect:
180
+ * - **No `.specify/` marker** → byte-identical to pre-task-2.1 behavior:
181
+ * call {@link buildOpenSpecProjectionSync} and return its result (FR-011).
182
+ * - **`.specify/` present but no feature dirs** (empty marker) → Spec-Kit is
183
+ * NOT counted as a competing source; behaves like the no-marker case.
184
+ * - **`.specify/` with ≥1 feature dir and no openspec projection** → return
185
+ * the Spec-Kit projection.
186
+ * - **`.specify/` with ≥1 feature dir AND openspec yields a projection** →
187
+ * AMBIGUOUS (FR-010): emit a concrete diagnostic via `console.warn` (the
188
+ * anti-silent-suppression requirement; critic Finding 2) and return null.
189
+ * The diagnostic is independent of the return value so it fires even when
190
+ * no consumer inspects the null return (e.g. the drift gate, which only
191
+ * checks for null to decide advisory-vs-blocking mode).
192
+ *
193
+ * **Backward-compat (FR-011):** when `.detectSpeckit(directory).markerPresent`
194
+ * is false, the function is byte-identical to pre-task-2.1. The additional
195
+ * `detectSpeckit` call (a single `fs.existsSync`) is the only overhead.
196
+ *
197
+ * **Deviation from task-2.1 literal text:** the task body says "Spec-Kit
198
+ * present (`detectSpeckit(dir).markerPresent`)" but plan.md task 3.1 and the
199
+ * test note both state that an empty `.specify/` "would never reach the
200
+ * ambiguity branch." `markerPresent` is true for empty-specify, so it cannot
201
+ * be the discriminator. We use `features.length > 0` instead, which is
202
+ * symmetric with "OpenSpec present (yields a projection)" and makes empty
203
+ * `.specify/` a non-competing source, matching both citations.
204
+ */
205
+ export declare function readEffectiveSpecSync(directory: string, opts?: ReadEffectiveSpecOpts): EffectiveSpec | null;
37
206
  export declare function writeProjectedSpecSync(directory: string, options?: {
38
207
  changeId?: string;
39
208
  dryRun?: boolean;
209
+ /** When `'speckit'`, builds a Spec-Kit projection instead of OpenSpec (task 2.2). */
210
+ source?: 'openspec' | 'speckit';
211
+ /** Feature selector forwarded to buildSpeckitProjectionSync when source is speckit. */
212
+ feature?: string;
40
213
  }): {
41
214
  written: boolean;
42
215
  projection: EffectiveSpec | null;
43
216
  archivePath?: string;
44
217
  path: string;
45
218
  };
219
+ /**
220
+ * Validate Spec-Kit artifacts READ-ONLY (FR-007, task 2.3).
221
+ *
222
+ * MUST NOT write or modify any Spec-Kit artifact — it only reads spec.md and
223
+ * tasks.md to report structural problems.
224
+ *
225
+ * Returns the {@link SpeckitResolution} from {@link resolveSpeckitProjection}
226
+ * alongside a flat `problems` array. Returning both avoids a second
227
+ * `resolveSpeckitProjection` call in the command layer (which would need the
228
+ * projection's hash / sourcePaths from `resolution.spec`).
229
+ *
230
+ * **Problems reported** (only when `resolution.kind` is `'ok'` or
231
+ * `'zero_requirements'` — i.e. when we have a feature to inspect):
232
+ * - Zero parsable functional requirements (`zero_requirements` kind, FR-013).
233
+ * - Missing required spec.md sections (`SPECKIT_REQUIRED_SECTIONS`).
234
+ * - `tasks.md` task lines that carry no `[US#]` user-story reference (FR-007).
235
+ *
236
+ * For other resolution kinds (`not_speckit`, `empty`, `ambiguous`,
237
+ * `unknown_feature`, `too_large`) the command layer uses
238
+ * {@link formatSpeckitError} on the returned resolution — same messaging path
239
+ * as task 2.2 (plan.md task 2.3 requirement: no second messaging scheme).
240
+ */
241
+ export declare function validateSpeckit(directory: string, options?: {
242
+ feature?: string;
243
+ }): {
244
+ resolution: SpeckitResolution;
245
+ problems: string[];
246
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-swarm",
3
- "version": "7.100.1",
3
+ "version": "7.101.0",
4
4
  "description": "Architect-centric agentic swarm plugin for OpenCode - hub-and-spoke orchestration with SME consultation, code generation, and QA review",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",