@gonovus-ai/spec-flow-cli 0.0.0-stage → 0.2.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 (59) hide show
  1. package/README.md +807 -2
  2. package/dist/authentication-command-topology.d.ts +7 -0
  3. package/dist/authentication-command-topology.js +9 -0
  4. package/dist/authentication-command-topology.js.map +1 -0
  5. package/dist/bin.d.ts +2 -0
  6. package/dist/bin.js +52 -0
  7. package/dist/bin.js.map +1 -0
  8. package/dist/cli.d.ts +16 -0
  9. package/dist/cli.js +3339 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/client-identity.d.ts +12 -0
  12. package/dist/client-identity.js +29 -0
  13. package/dist/client-identity.js.map +1 -0
  14. package/dist/credential-store.d.ts +20 -0
  15. package/dist/credential-store.js +146 -0
  16. package/dist/credential-store.js.map +1 -0
  17. package/dist/default-api-origin.d.ts +1 -0
  18. package/dist/default-api-origin.js +1 -0
  19. package/dist/default-api-origin.js.map +1 -0
  20. package/dist/github-account-commands.d.ts +25 -0
  21. package/dist/github-account-commands.js +117 -0
  22. package/dist/github-account-commands.js.map +1 -0
  23. package/dist/github-connection-commands.d.ts +4 -0
  24. package/dist/github-connection-commands.js +95 -0
  25. package/dist/github-connection-commands.js.map +1 -0
  26. package/dist/github-import-commands.d.ts +3 -0
  27. package/dist/github-import-commands.js +59 -0
  28. package/dist/github-import-commands.js.map +1 -0
  29. package/dist/http-origin.d.ts +13 -0
  30. package/dist/http-origin.js +39 -0
  31. package/dist/http-origin.js.map +1 -0
  32. package/dist/index.d.ts +22 -0
  33. package/dist/index.js +238 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/node-version.d.ts +1 -0
  36. package/dist/node-version.js +8 -0
  37. package/dist/node-version.js.map +1 -0
  38. package/dist/safe-cli-error.d.ts +2 -0
  39. package/dist/safe-cli-error.js +3 -0
  40. package/dist/safe-cli-error.js.map +1 -0
  41. package/dist/user-agent.d.ts +3 -0
  42. package/dist/user-agent.js +10 -0
  43. package/dist/user-agent.js.map +1 -0
  44. package/dist/user-api-key-commands.d.ts +29 -0
  45. package/dist/user-api-key-commands.js +128 -0
  46. package/dist/user-api-key-commands.js.map +1 -0
  47. package/dist/work-item-asset-commands.d.ts +24 -0
  48. package/dist/work-item-asset-commands.js +211 -0
  49. package/dist/work-item-asset-commands.js.map +1 -0
  50. package/dist/work-item-exchange-file.d.ts +4 -0
  51. package/dist/work-item-exchange-file.js +66 -0
  52. package/dist/work-item-exchange-file.js.map +1 -0
  53. package/dist/work-item-resource-link-commands.d.ts +23 -0
  54. package/dist/work-item-resource-link-commands.js +85 -0
  55. package/dist/work-item-resource-link-commands.js.map +1 -0
  56. package/dist/work-item-time-entry-input.d.ts +4 -0
  57. package/dist/work-item-time-entry-input.js +27 -0
  58. package/dist/work-item-time-entry-input.js.map +1 -0
  59. package/package.json +53 -3
package/README.md CHANGED
@@ -1,3 +1,808 @@
1
- # Temporary Holding Version
1
+ # SpecFlow CLI
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The next CLI contract is **0.2.0**, coordinated with the official Skill's exact
4
+ pin. Directory, Spec and Product Map results can return `capabilityId: null`;
5
+ unassigned search results have `capabilityPath: []`. Consumers of 0.1.x must
6
+ handle these absent placements before upgrading. Omit `--capability-id` on
7
+ creation, and use `--capability-id none` on `spec update` to clear placement;
8
+ omitting that update option preserves the current placement. Existing grouped
9
+ requests and nested Capability paths retain their meaning. The HTTP service
10
+ continues under `/v1` with regenerated OpenAPI inputs; clients must update their
11
+ nullable response handling together. This source change does not publish either
12
+ artifact or change already released Skill tags.
13
+
14
+ `@gonovus-ai/spec-flow-cli` uses OAuth Device Authorization to create an independent
15
+ seven-day Better Auth CLI Session. It supports the Node.js 24 runtime line;
16
+ Node.js 22 and unverified future major versions are intentionally unsupported.
17
+
18
+ ```bash
19
+ npm install --global @gonovus-ai/spec-flow-cli@0.2.0
20
+ specflow auth login
21
+ specflow auth status
22
+ specflow auth logout
23
+ ```
24
+
25
+ The published package's default service origin comes from the GitHub repository
26
+ Actions variable `SPEC_FLOW_DEFAULT_API_URL`, embedded at build time. Both authentication
27
+ and resource commands resolve `--server` first, then runtime `SPEC_FLOW_API_URL`, then
28
+ that packaged default. Keep the same origin for login and subsequent commands.
29
+ Credentials are isolated by origin; a different environment requires its own login.
30
+
31
+ Changing the repository variable requires publishing a new CLI version; existing
32
+ installations keep their packaged default. The release workflow rejects a missing,
33
+ empty or invalid origin. Skill instructions do not need a deployment address.
34
+
35
+ Local builds without `SPEC_FLOW_DEFAULT_API_URL` have no implicit service target;
36
+ pass `--server` or set runtime `SPEC_FLOW_API_URL`. To build a local package with a
37
+ default, provide `SPEC_FLOW_DEFAULT_API_URL` to the CLI build command. The packed
38
+ installation smoke uses an isolated `.invalid` test origin when no build value is
39
+ provided; it never substitutes a deployment URL into source.
40
+
41
+ After login, resource commands reuse the stored CLI Session and emit structured JSON
42
+ on stdout. Existing-Project commands always require an explicit `--project-id`:
43
+
44
+ ```bash
45
+ SPEC_FLOW_API_URL=https://specflow.example specflow workspace list
46
+ SPEC_FLOW_API_URL=https://specflow.example specflow workspace create --name "Core Platform"
47
+ SPEC_FLOW_API_URL=https://specflow.example specflow project list
48
+ SPEC_FLOW_API_URL=https://specflow.example specflow project list --workspace-id <workspace-id>
49
+ SPEC_FLOW_API_URL=https://specflow.example specflow project list --status Archived
50
+ SPEC_FLOW_API_URL=https://specflow.example specflow project list --status Trashed
51
+ SPEC_FLOW_API_URL=https://specflow.example specflow project create --name "Payments" --project-id PAY --workspace-id <workspace-id>
52
+ SPEC_FLOW_API_URL=https://specflow.example specflow project create --name "Payments" --project-id PAY --workspace-id <workspace-id> --spec-language zh-CN
53
+ SPEC_FLOW_API_URL=https://specflow.example specflow project archive --project-id <project-id> --expected-version '"1"'
54
+ SPEC_FLOW_API_URL=https://specflow.example specflow project restore --project-id <project-id> --expected-version '"2"'
55
+ SPEC_FLOW_API_URL=https://specflow.example specflow project trash --project-id <project-id> --expected-version '"2"'
56
+ SPEC_FLOW_API_URL=https://specflow.example specflow project directory --project-id <project-id>
57
+ SPEC_FLOW_API_URL=https://specflow.example specflow spec read --project-id <project-id> --spec-id <spec-id>
58
+ SPEC_FLOW_API_URL=https://specflow.example specflow spec current --project-id <project-id> --spec-id <spec-id>
59
+ SPEC_FLOW_API_URL=https://specflow.example specflow spec revision --project-id <project-id> --spec-id <spec-id> --revision-id <revision-id>
60
+ SPEC_FLOW_API_URL=https://specflow.example specflow spec changelog --project-id <project-id> --spec-id <spec-id>
61
+ SPEC_FLOW_API_URL=https://specflow.example specflow spec edit --project-id <project-id> --spec-id <spec-id> --content "$(cat payments.md)"
62
+ SPEC_FLOW_API_URL=https://specflow.example specflow spec assign-scenario-ids --project-id <project-id> --spec-id <spec-id>
63
+ SPEC_FLOW_API_URL=https://specflow.example specflow spec publish --project-id <project-id> --spec-id <spec-id>
64
+ SPEC_FLOW_API_URL=https://specflow.example specflow glossary edit --project-id <project-id> --entries '[{"id":"payment","canonicalTerm":"Payment","definition":"A completed transfer.","usageNote":null}]'
65
+ SPEC_FLOW_API_URL=https://specflow.example specflow proposal create-spec --project-id <project-id> --spec-id <spec-id> --baseline-content "$(cat payments.md)" --proposed-content "$(cat payments.md)" --agent-revision <revision> --agent-model <model> --source-reference-ids '["source-id"]'
66
+ SPEC_FLOW_API_URL=https://specflow.example specflow proposal list --project-id <project-id> --target-kind Spec --spec-id <spec-id> --status Open
67
+ SPEC_FLOW_API_URL=https://specflow.example specflow proposal apply --project-id <project-id> --proposal-id <proposal-id> --change-ids '["hunk-1"]'
68
+ SPEC_FLOW_API_URL=https://specflow.example specflow proposal discard --project-id <project-id> --proposal-id <proposal-id>
69
+ SPEC_FLOW_API_URL=https://specflow.example specflow authoring-history --project-id <project-id>
70
+ ```
71
+
72
+ Every official CLI service request sends the versioned `@gonovus-ai/spec-flow-cli/<version>`
73
+ User-Agent. Diagnostics use stderr, and stable Skill Errors use documented reason and
74
+ exit-status categories.
75
+
76
+ ## Optional Task resource links
77
+
78
+ Use `work-item resource-link list --project-id PAY --work-item-id PAY-7` to read
79
+ current authorized references. Add a stable Spec with `add --kind spec --spec-id
80
+ PAY-8`; `--spec-project-id` selects another accessible Project in the same Workspace
81
+ and otherwise defaults to the Task Project. Add a document or other locator with
82
+ `add --kind document|other --title <name> --url <http-or-https-url>`. Every add requires
83
+ `--idempotency-key <uuid>` and the owning Project/Task flags. Retain that key for an
84
+ ambiguous retry of the same input.
85
+
86
+ Unlink with `remove --link-id <uuid> --expected-version <opaque-version>
87
+ --idempotency-key <uuid>` and the Project/Task flags. The target and Task remain
88
+ independent. `spec work-items --project-id <Spec-project> --spec-id <id> [--limit <n>]
89
+ [--cursor <opaque-cursor>]` reads paged authorized reverse Task references.
90
+ These commands read current metadata without pinning a Revision, importing content,
91
+ or changing Task completion or Spec publication. External locator names are
92
+ user-authored Task data; opening a destination uses that destination's authorization.
93
+
94
+ ## Machine contract
95
+
96
+ Resource-command success writes exactly one newline-terminated JSON object to stdout.
97
+ stderr is newline-delimited JSON (NDJSON): zero or more diagnostics use
98
+ `{ "diagnostic": { "level": "warning", "message": string } }`. These
99
+ command/result pairs are the supported schemas:
100
+
101
+ | Command | Success object |
102
+ | ------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
103
+ | `workspace list` | `{ workspaces: WorkspaceSummary[] }` |
104
+ | `workspace create` | `{ workspace: WorkspaceSummary }` |
105
+ | `workspace member list --workspace-id <id>` | `{ members: WorkspaceMember[] }` |
106
+ | `workspace member search --workspace-id <id> --query <q>` | `{ candidates: WorkspaceDirectoryCandidate[] }` |
107
+ | `workspace member add --workspace-id <id> --user-id <userId> --role <role>` | `{ member: WorkspaceMember }` |
108
+ | `workspace member set-role --workspace-id <id> --user-id <userId> --role <role> --expected-version <v>` | `{ member: WorkspaceMember }` |
109
+ | `workspace member remove --workspace-id <id> --user-id <userId> --expected-version <v>` | `{ removed: { userId: string } }` |
110
+ | `workspace role list --workspace-id <id>` | `{ roles: WorkspaceRole[] }` |
111
+ | `workspace role create --workspace-id <id> --name <name> --statements <json>` | `{ role: WorkspaceRole }` |
112
+ | `workspace role update --workspace-id <id> --name <name> --statements <json> --expected-version <v>` | `{ role: WorkspaceRole }` |
113
+ | `workspace role delete --workspace-id <id> --name <name> --expected-version <v>` | `{ deleted: { name: string } }` |
114
+ | `project list [--status Active\|Archived\|Trashed] [--workspace-id <workspace-id>]` | `{ projects: ProjectSummary[] }` |
115
+ | `project create` | `{ project: ProjectSummary }` |
116
+ | `project archive` | `{ project: ProjectSummary }` |
117
+ | `project restore` | `{ project: ProjectSummary }` |
118
+ | `project trash` | `{ project: ProjectSummary }` |
119
+ | `project context` | `{ project: ProjectContext, publishedGlossary: PublishedGlossary \| null }` |
120
+ | `project directory` | `{ capabilities: Capability[], permissions: Permissions, specs: SpecSummary[] }` |
121
+ | `spec search` | `{ nextCursor: string \| null, results: SearchResult[], totalCount: number }` |
122
+ | `work-item resource-link list` | `{ links: WorkItemResourceLink[], canCreateLink: boolean, canReadSpecs: boolean }` |
123
+ | `work-item resource-link add` | `{ linkId: string, link: WorkItemResourceLink \| null, replayed: boolean }` |
124
+ | `work-item resource-link remove` | `{ deleted: true, replayed: boolean }` |
125
+ | `spec work-items` | `{ workItems: SpecWorkItemReference[], nextCursor: string \| null, canReadWorkItems: boolean }` |
126
+ | `spec read` | `{ currentRevision: SpecRevision \| null, permissions: Permissions, spec: SpecDetails }` |
127
+ | `spec current` | `{ revision: SpecRevision, spec: SpecSummary }` |
128
+ | `spec revision` | `{ revision: SpecRevision }` |
129
+ | `spec changelog` | `{ entries: SpecChangelogEntry[] }` |
130
+ | `spec create` | `{ spec: SpecDetails }` |
131
+ | `spec update` | `{ spec: SpecDetails }` |
132
+ | `spec draft` | `{ spec: SpecDetails }` |
133
+ | `spec edit` | `{ draft: { draftSessionId: string, content: string, specId: string } }` |
134
+ | `spec assign-scenario-ids` | `{ assignedScenarioIds: string[], draft: { draftSessionId: string, content: string, specId: string } }` |
135
+ | `spec publish` | `{ revision: SpecRevision, spec: SpecSummary }` |
136
+ | `spec state` | `{ spec: SpecSummary }` |
137
+ | `spec rollback` | `{ revision: SpecRevision, spec: SpecSummary }` |
138
+ | `spec delete` | `{ deleted: { specId: string } }` |
139
+ | `glossary read` | `{ currentRevision: GlossaryRevision \| null, draftSession: GlossaryDraftSession \| null, permissions: Permissions }` |
140
+ | `glossary changelog` | `{ entries: GlossaryChangelogEntry[] }` |
141
+ | `glossary revision` | `{ revision: GlossaryRevision }` |
142
+ | `glossary draft` | `{ glossary: GlossaryDetails }` |
143
+ | `glossary edit` | `{ draft: { draftSessionId: string, entries: GlossaryEntry[] } }` |
144
+ | `glossary publish` | `{ revision: GlossaryRevision }` |
145
+ | `glossary rollback` | `{ revision: GlossaryRevision }` |
146
+ | `proposal create-spec\|create-glossary` | `{ proposal: DraftProposal }` |
147
+ | `proposal list` | `{ proposals: DraftProposal[] }` |
148
+ | `proposal apply` | `{ mergeOutcome: "Applied", proposal: DraftProposal }` |
149
+ | `proposal discard` | `{ proposal: DraftProposal }` |
150
+ | `authoring-history` | `{ operations: AuthoringOperation[] }` |
151
+ | `capability create` | `{ capability: Capability }` |
152
+ | `capability update` | `{ capability: Capability }` |
153
+ | `capability delete` | `{ deleted: { capabilityId: string } }` |
154
+ | `token issue` | `{ credential: string, token: ProjectReadToken }` |
155
+ | `token list` | `{ tokens: ProjectReadToken[] }` |
156
+ | `token revoke` | `{ revoked: { tokenId: string } }` |
157
+ | `review request` | `{ review: ManualReviewRecord }` |
158
+ | `review read` | `{ review: ManualReviewRecord }` |
159
+ | `finding list` | `{ findings: ReviewFinding[], nextCursor: string \| null, permissions: FindingPermissions }` |
160
+ | `finding read` | `{ finding: ReviewFinding, permissions: FindingPermissions }` |
161
+ | `finding create` | `{ finding: ReviewFinding, permissions: FindingPermissions }` |
162
+ | `finding confirm\|dismiss\|resolve` | `{ finding: ReviewFinding, permissions: FindingPermissions }` |
163
+
164
+ The named JSON shapes are:
165
+
166
+ ```ts
167
+ type WorkItemResourceLink = {
168
+ id: string;
169
+ version: string; // opaque ETag
170
+ createdAt: string;
171
+ target:
172
+ | {
173
+ kind: "spec";
174
+ projectId: string;
175
+ specId: string;
176
+ title: string;
177
+ state: "Draft" | "Ready" | "Unavailable";
178
+ unavailabilityReason: string | null;
179
+ }
180
+ | { kind: "document" | "other"; title: string; url: string };
181
+ };
182
+ type SpecWorkItemReference = {
183
+ linkId: string;
184
+ projectId: string;
185
+ workItemId: string;
186
+ title: string;
187
+ status: string;
188
+ };
189
+ type WorkspaceSummary = {
190
+ capabilities: {
191
+ canReadMembers: boolean;
192
+ canReadRoles: boolean;
193
+ };
194
+ id: string;
195
+ name: string;
196
+ role: string;
197
+ };
198
+ type WorkspaceMember = {
199
+ accessStatus: "Enabled" | "Disabled";
200
+ email: string | null;
201
+ image: string | null;
202
+ name: string;
203
+ role: string;
204
+ userId: string;
205
+ version: string; // opaque ETag
206
+ };
207
+ type WorkspaceDirectoryCandidate = {
208
+ eligible: boolean;
209
+ ineligibleReason: string | null;
210
+ issuer: string;
211
+ name: string;
212
+ subject: string;
213
+ userId: string | null;
214
+ };
215
+ type WorkspaceRole = {
216
+ name: string;
217
+ statements: Record<string, string[]>;
218
+ version: string; // opaque ETag
219
+ };
220
+ type ProjectSummary = {
221
+ effectiveCapabilities: {
222
+ canAdministerGitHubConnection: boolean;
223
+ canAdministerProject: boolean;
224
+ canEditProject: boolean;
225
+ canReadProject: boolean;
226
+ };
227
+ specLanguage: string;
228
+ id: string;
229
+ name: string;
230
+ role: string;
231
+ status: "Active" | "Archived" | "Trashed";
232
+ version: string; // opaque ETag
233
+ workspaceId: string;
234
+ workspaceName: string;
235
+ };
236
+ type ProjectContext = {
237
+ specLanguage: string;
238
+ id: string;
239
+ name: string;
240
+ status: "Active" | "Archived";
241
+ version: string; // opaque ETag
242
+ };
243
+ type GlossaryEntry = {
244
+ canonicalTerm: string;
245
+ definition: string;
246
+ id: string;
247
+ usageNote: string | null;
248
+ };
249
+ type PublishedGlossary = {
250
+ entries: GlossaryEntry[];
251
+ id: string;
252
+ publishedAt: string; // ISO 8601 date-time
253
+ };
254
+ type Capability = {
255
+ id: string;
256
+ name: string;
257
+ parentId: string | null;
258
+ version: string; // opaque ETag, for example: "\"1\""
259
+ };
260
+ type Permissions = { canEdit: boolean };
261
+ type FindingPermissions = { canWrite: boolean };
262
+ type SourceReference = {
263
+ externalRevision?: string;
264
+ id: string;
265
+ section?: string;
266
+ title: string;
267
+ url: string;
268
+ };
269
+ type SpecSummary = {
270
+ capabilityId: string;
271
+ featureName: string;
272
+ id: string;
273
+ sourceReferences: SourceReference[];
274
+ state: "Draft" | "Ready" | "Unavailable";
275
+ unavailabilityReason: string | null;
276
+ version: string; // opaque ETag
277
+ };
278
+ type SpecDetails = SpecSummary & {
279
+ draftSession: {
280
+ documentName: string;
281
+ id: string;
282
+ rootName: "spec-outline";
283
+ } | null;
284
+ };
285
+ type SpecRevision = {
286
+ content: string;
287
+ id: string;
288
+ publishedAt: string; // ISO 8601 date-time
289
+ };
290
+ type CapabilityPathEntry = { id: string; name: string };
291
+ type SearchResult = {
292
+ capabilityPath: CapabilityPathEntry[];
293
+ spec: SpecSummary;
294
+ };
295
+ type SourceReferenceInput = Omit<SourceReference, "id">;
296
+ type SpecChangelogEntry = {
297
+ acceptedAt: string; // ISO 8601 date-time
298
+ action:
299
+ | "Published"
300
+ | "RolledBack"
301
+ | "StateChanged"
302
+ | "MetadataChanged"
303
+ | "FindingCreated"
304
+ | "FindingConfirmed"
305
+ | "FindingDismissed"
306
+ | "FindingResolved"
307
+ | "FindingStale";
308
+ actorSessionId: string;
309
+ actorUserId: string;
310
+ changes: {
311
+ content: { from: string | null; to: string } | null;
312
+ finding: {
313
+ findingId: string;
314
+ status: "Confirmed" | "Dismissed" | "Proposed" | "Resolved" | "Stale";
315
+ } | null;
316
+ metadata: {
317
+ capabilityId?: { from: string; to: string };
318
+ sourceReferences?: {
319
+ from: SourceReferenceInput[];
320
+ to: SourceReferenceInput[];
321
+ };
322
+ } | null;
323
+ state: {
324
+ from: "Draft" | "Ready" | "Unavailable";
325
+ fromUnavailabilityReason: string | null;
326
+ to: "Draft" | "Ready" | "Unavailable";
327
+ toUnavailabilityReason: string | null;
328
+ } | null;
329
+ };
330
+ id: string;
331
+ previousRevisionId: string | null;
332
+ revisionId: string | null;
333
+ };
334
+ type GlossaryRevision = {
335
+ entries: GlossaryEntry[];
336
+ id: string;
337
+ publishedAt: string; // ISO 8601 date-time
338
+ };
339
+ type GlossaryDraftSession = {
340
+ documentName: string;
341
+ entriesName: "entries";
342
+ id: string;
343
+ };
344
+ type GlossaryDetails = {
345
+ currentRevision: GlossaryRevision | null;
346
+ draftSession: GlossaryDraftSession;
347
+ };
348
+ type GlossaryChangelogEntry = {
349
+ acceptedAt: string; // ISO 8601 date-time
350
+ action: "Published" | "RolledBack";
351
+ actorSessionId: string;
352
+ actorUserId: string;
353
+ changes: {
354
+ added: GlossaryEntry[];
355
+ removed: GlossaryEntry[];
356
+ updated: { from: GlossaryEntry; to: GlossaryEntry }[];
357
+ };
358
+ id: string;
359
+ previousRevisionId: string | null;
360
+ revisionId: string;
361
+ };
362
+ type ProjectReadToken = {
363
+ createdAt: string; // ISO 8601 date-time
364
+ expiresAt: string | null; // ISO 8601 date-time
365
+ id: string;
366
+ issuer: { id: string; name: string };
367
+ lastUsedAt: string | null; // ISO 8601 date-time
368
+ name: string;
369
+ revokedAt: string | null; // ISO 8601 date-time
370
+ };
371
+ type FindingType =
372
+ | "Contradiction"
373
+ | "Conflict"
374
+ | "Duplicate"
375
+ | "Ambiguity"
376
+ | "Terminology"
377
+ | "Unverifiable";
378
+ type ManualReviewRecord = {
379
+ agentRevision: string;
380
+ completedAt: string | null; // ISO 8601 date-time
381
+ createdAt: string; // ISO 8601 date-time
382
+ draft: { contentHash: string; draftSessionId: string; specId: string };
383
+ enabledFindingTypes: FindingType[];
384
+ failureCategory:
385
+ | "InvalidOutput"
386
+ | "ProjectInactive"
387
+ | "TerminalExecutionFailure"
388
+ | "Timeout"
389
+ | null;
390
+ findingIds: string[];
391
+ glossaryRevisionId: string | null;
392
+ id: string;
393
+ knownFindingIds: string[];
394
+ mastraExecutionVersion: string;
395
+ model: string | null;
396
+ projectId: string;
397
+ requestedBy: { sessionId: string; userId: string };
398
+ reviewContractVersion: string;
399
+ startedAt: string | null; // ISO 8601 date-time
400
+ status:
401
+ | "Queued"
402
+ | "Running"
403
+ | "ProposedFindings"
404
+ | "Superseded"
405
+ | "Cancelled"
406
+ | "Failed";
407
+ trigger: "ManualDraft";
408
+ updatedAt: string; // ISO 8601 date-time
409
+ };
410
+ type FindingReference = {
411
+ kind: "Feature" | "Rule" | "Background" | "Scenario" | "Examples" | "Step";
412
+ text: string;
413
+ };
414
+ type FindingTarget = {
415
+ binding:
416
+ | { kind: "Revision"; revisionId: string }
417
+ | {
418
+ contentHash: string;
419
+ draftSessionId: string;
420
+ kind: "WorkingDraftSnapshot";
421
+ };
422
+ references: FindingReference[];
423
+ specId: string;
424
+ };
425
+ type FindingEvidence =
426
+ | { incompatibleClaims: [string, string]; kind: "Contradiction" }
427
+ | { incompatibleRequirements: [string, string]; kind: "Conflict" }
428
+ | { duplicatedBehavior: string; kind: "Duplicate" }
429
+ | {
430
+ interpretations: [
431
+ { behavior: string; meaning: string },
432
+ { behavior: string; meaning: string },
433
+ ];
434
+ kind: "Ambiguity";
435
+ }
436
+ | {
437
+ glossaryEntryIds: string[];
438
+ issue: "MissingDefinition" | "GlossaryConflict" | "MultipleMeanings";
439
+ kind: "Terminology";
440
+ term: string;
441
+ }
442
+ | { kind: "Unverifiable"; missingCriterion: string };
443
+ type ReviewFinding = {
444
+ affectedScenarios: string[];
445
+ author: { sessionId: string; userId: string };
446
+ conclusion: string;
447
+ createdAt: string; // ISO 8601 date-time
448
+ evidence: FindingEvidence;
449
+ explanation: string;
450
+ glossaryReferences: {
451
+ entry: GlossaryEntry;
452
+ entryId: string;
453
+ revisionId: string;
454
+ }[];
455
+ id: string;
456
+ projectId: string;
457
+ reviewRecordId: string | null;
458
+ status: "Proposed" | "Confirmed" | "Dismissed" | "Resolved" | "Stale";
459
+ targets: FindingTarget[];
460
+ type: FindingType;
461
+ updatedAt: string; // ISO 8601 date-time
462
+ version: string; // opaque ETag
463
+ };
464
+ ```
465
+
466
+ A failed resource command writes nothing to stdout. After any diagnostic lines, the
467
+ final stderr line is exactly `{ "error": { "message": string, "reason":
468
+ SkillErrorReason, "retryAfter"?: string, "retryable": boolean,
469
+ "diagnostics"?: SkillErrorDiagnostic[] } }` where
470
+ `type SkillErrorDiagnostic = { column?: number; line?: number; message: string;
471
+ reason: string }`. `diagnostics` is present whenever the server reports
472
+ positioned source problems: Spec Publish and Rollback rejections from the Spec Content
473
+ Publish Gate carry one entry per `feature-count`, `language-mismatch`,
474
+ `parse-error`, `scenario-required`, or `scenario-id-*` reason,
475
+ `spec assign-scenario-ids` carries the same shape when the Working Draft source is
476
+ invalid, and other Invalid Operation rejections may carry it too. `SkillErrorReason` is `Authentication Failed`,
477
+ `Project Not Accessible`, `Permission Denied`, `Resource Not Found`,
478
+ `Resource Changed`, `Invalid Operation`, or `Temporary Failure`. Exit status `2`
479
+ covers authentication, validation, inaccessible/not-found resources, conflicts, and
480
+ invalid operations; `3` is permission denial; `4` is a `Temporary Failure`, whose
481
+ `retryable` field determines whether automatic retry is safe. Exit status `0` is
482
+ success. Exit status `1` is reserved for startup/runtime failure before a
483
+ resource-command contract can run.
484
+
485
+ HTTP `429` is a retryable `Temporary Failure`. When the server supplies
486
+ `Retry-After`, the CLI copies its exact value to `retryAfter` and does not retry the
487
+ request automatically. Generated `--help` is a separate controlled success path:
488
+ top-level help includes authentication and every resource group, nested help includes
489
+ the selected group's operations and options, and all help writes to stdout, exits
490
+ zero, and does not resolve credentials or call the service.
491
+
492
+ `ProjectSummary.effectiveCapabilities` incorporates lifecycle status. Archived
493
+ Projects always report `canEditProject: false`, including for Admins and Editors; an
494
+ Archived Admin still reports `canAdministerProject: true` for Restore and access
495
+ reduction. A Trashed Project reports `canReadProject: false` even for its Admin:
496
+ it appears only in the explicit `--status Trashed` list, and every read or write
497
+ path rejects it; its only accepted mutation is `project restore`, which lands it
498
+ on Archived, so reaching Active again takes a second explicit restore.
499
+
500
+ Workspace membership is the only source of Project access. `ProjectSummary.role`
501
+ is the caller's Role in the owning Workspace, and `workspace create` grants the
502
+ creator the `Owner` Workspace Role. `project create` requires the owning
503
+ Workspace's ID and at least the `Editor` Role inside it.
504
+
505
+ `workspace member` and `workspace role` administer that source. `member search`
506
+ queries the bound identity directory and reports each candidate's eligibility;
507
+ `member add` provisions the selected User — including before first login — and
508
+ `member set-role`/`member remove` require the Membership's opaque `version` as
509
+ `--expected-version`. `role` commands manage the Workspace's statement sets:
510
+ `--statements` takes a JSON object mapping resources to action arrays, a Role
511
+ never grants statements the caller does not hold, `Owner` is immutable, and a
512
+ Role still assigned to a member cannot be deleted. Role updates and deletes
513
+ require the Role's `version` as `--expected-version`.
514
+
515
+ `project list` stays stateless: pass `--workspace-id <workspace-id>` — from
516
+ `workspace list` or `project create` output — to select one Workspace and list
517
+ only its Projects, combinable with `--status`. The CLI keeps no implicit
518
+ current Workspace. An unknown or inaccessible Workspace returns an empty list
519
+ rather than revealing that it exists, and a CLI Session approved for a Project
520
+ subset only ever returns Projects inside that scope, even when the filter names
521
+ a Workspace that contains no scoped Project.
522
+
523
+ Every non-idempotent create, Draft-session, Publish, Rollback,
524
+ token issuance, Finding disposition, Manual Review request, and Project lifecycle
525
+ command performs at most one fetch-transport retry. It clones the request and reuses
526
+ the same `Idempotency-Key`; an HTTP response, including a response whose body cannot
527
+ be decoded, is never dispatched again. Archive, Restore, and Trash require the opaque
528
+ Project
529
+ version returned by discovery or context. If neither transport attempt yields a usable
530
+ response, the final `Temporary Failure` has `retryable: false` and directs the caller
531
+ to inspect the resource or Changelog before deciding whether another logical mutation
532
+ is appropriate.
533
+
534
+ Token issuance returns the plaintext credential only for the first committed
535
+ response. If a same-key retry confirms the token with `credential: null`, the CLI
536
+ exits nonzero, prints no success payload, and directs the caller to list or revoke the
537
+ token before deliberately issuing another.
538
+
539
+ Source Reference URLs accept only HTTP or HTTPS and must not contain URL userinfo
540
+ (`username` or `password`). Keep external credentials in the Agent's own authorized
541
+ source-reading tool; never embed them in SpecFlow metadata.
542
+
543
+ The repository-owned official Skill is `skills/specflow/SKILL.md`. It pins this exact
544
+ CLI release, requires explicit User confirmation immediately before Spec or Glossary
545
+ Publish and Rollback, and does not send proof of that confirmation to the CLI or API.
546
+
547
+ The CLI stores its Session credential in macOS Keychain, Windows Credential
548
+ Manager, or Linux Secret Service. It does not print or persist the credential in
549
+ environment variables. If the system store is unavailable on a POSIX platform,
550
+ local-file storage is used only after `--allow-file-fallback` is explicitly
551
+ selected; the CLI warns and enforces directory mode `0700` and file mode `0600`.
552
+
553
+ ## Work Item creation labels
554
+
555
+ `specflow work-item create --project-id PAY --title "Retry alert" --label <definitionId>`
556
+ sets existing Workspace labels in the same transaction as creation. Repeat `--label`
557
+ for multiple labels; duplicate IDs are ignored. The response includes
558
+ `workItem.labels`. Missing, foreign-Workspace, copy, or deleting labels reject the
559
+ entire creation with `INVALID_WORK_ITEM_LABEL`.
560
+
561
+ ## Work Item JSON exchange
562
+
563
+ ```bash
564
+ specflow work-item export --project-id PAY --status done > tasks.json
565
+ specflow work-item import --project-id OPS --file tasks.json --idempotency-key release-tasks-1
566
+ ```
567
+
568
+ Export supports the same selection flags as `work-item list` and returns a JSON
569
+ `{ project: { name, slug, description, exportedAt }, tasks: [...] }` document on
570
+ stdout. Each task contains `title`, `description`, `status`, `priority`,
571
+ `startDate`, `dueDate`, `userId`, and `labels: [{ name, color }]`. Dates are ISO
572
+ strings or null. Only readable tasks from the explicit Project are included.
573
+ An export over 1000 tasks or 1 MiB fails; narrow the selection instead of expecting
574
+ a truncated document.
575
+
576
+ Import accepts a JSON file with a `tasks` array, at most 1000 tasks and 1 MiB.
577
+ File Project metadata is descriptive; `--project-id` always selects the target.
578
+ The server applies ordinary creation and assignment authorization, membership,
579
+ workflow, default Custom Field, and identity rules. Unknown statuses become
580
+ `planned`; unknown priorities become `no-priority`; absent or empty priority
581
+ becomes `low`. Labels are exported but not imported, matching pinned Kaneo;
582
+ nonempty imported labels produce an explicit warning.
583
+
584
+ Import stdout contains `importedAt`, target `project`, and `results` with
585
+ `total`, `successful`, `failed`, and indexed `tasks`. Each row has `success` and
586
+ `warnings`; successful rows include `workItemId` and opaque `version`, while
587
+ failed rows include an error code. If any row fails, the CLI retains this complete
588
+ receipt on stdout, emits a nonretryable `Invalid Operation` on stderr, and exits
589
+ `2`. Correct and import only failed rows with a new key. Successful rows and
590
+ business refusals commit together; an unexpected database error rolls back the
591
+ whole batch.
592
+
593
+ Retry the same file with the same `--idempotency-key` after an interrupted request.
594
+ Within the existing 24-hour replay window it returns the original receipt without
595
+ duplicating tasks. A changed task payload with that key conflicts. Read Tokens may
596
+ export their single Project; they cannot import. No CSV or legacy migration format
597
+ is provided.
598
+
599
+ ### Task relations
600
+
601
+ Relations link two tasks in the same Workspace, including across Projects.
602
+ `subtask` means source parent → target child; `blocks` means source blocker →
603
+ blocked target; `related` is symmetric. Reading either endpoint returns both task
604
+ identities and their current Projects. Self-links and duplicate pairs of the same
605
+ relation type in either direction are rejected; longer cycles are allowed, as in
606
+ the pinned Kaneo contract. These links are separate from Spec/document links.
607
+
608
+ ```bash
609
+ specflow work-item relation list --project-id PAY --work-item-id PAY-7
610
+ specflow work-item relation create --project-id PAY --work-item-id PAY-7 \
611
+ --target-project-id OPS --target-work-item-id OPS-2 --type blocks \
612
+ --idempotency-key relation-command-1
613
+ specflow work-item relation delete --project-id OPS --work-item-id OPS-2 \
614
+ --peer-project-id PAY --relation-id <relation-id> --expected-version '"1"' \
615
+ --idempotency-key remove-relation-command-1
616
+ ```
617
+
618
+ Keep the same command key and inputs after an interrupted response. Receipts
619
+ contain only the relation identifier; a create replay returns `relation: null`
620
+ when the relation has been deleted or its current endpoints are outside the
621
+ original authority. It never restores a deleted link. After a task transfer,
622
+ reread to obtain the endpoint's new task identity and Project before removing its
623
+ relation. Both endpoints require current task update permission for writes;
624
+ Archived endpoints are read-only. Lists omit inaccessible peers and their counts.
625
+ Project Read Tokens can read only relations wholly inside their single Project
626
+ and cannot create or delete relations.
627
+
628
+ ### Task comments and activity
629
+
630
+ Read comments oldest first, or read the mixed discussion/activity feed newest
631
+ first. Both commands return an opaque `nextCursor`; pass it back with `--cursor`.
632
+ Pages accept `--limit` from 1 through 100.
633
+
634
+ ```bash
635
+ specflow work-item comment list --project-id PAY --work-item-id PAY-7 --limit 20
636
+ specflow work-item discussion --project-id PAY --work-item-id PAY-7 --limit 20
637
+ specflow work-item comment create --project-id PAY --work-item-id PAY-7 \
638
+ --content '**Review** the rollout' --idempotency-key <uuid>
639
+ specflow work-item comment update --project-id PAY --work-item-id PAY-7 \
640
+ --comment-id <comment-uuid> --content 'Updated review' \
641
+ --expected-version '"1"' --idempotency-key <uuid>
642
+ specflow work-item comment delete --project-id PAY --work-item-id PAY-7 \
643
+ --comment-id <comment-uuid> --expected-version '"2"' --idempotency-key <uuid>
644
+ ```
645
+
646
+ Comment mutations require a CLI Session, current task update permission, and a
647
+ UUID command key. Only the comment's author may edit or delete it, including
648
+ when another Member is an Admin. Reuse the same command key and inputs after a
649
+ lost response; receipts retain only the comment identifier and resolve its
650
+ current content. A replay after deletion returns `comment: null`.
651
+
652
+ Take the opaque comment `version` from the read result. An edit or deletion
653
+ against an older version reports `Resource Changed`; reread before deciding
654
+ whether to resubmit. Project Read Tokens may read comments and activity under
655
+ `WorkItemRead`, and cannot mutate comments. Activities are produced by the
656
+ server's actual task operations; there is no activity creation command.
657
+
658
+ ### Task time recording
659
+
660
+ List entries oldest first with an opaque `nextCursor`, or retrieve one entry:
661
+
662
+ ```bash
663
+ specflow work-item time-entry list --project-id PAY --work-item-id PAY-7 --limit 20
664
+ specflow work-item time-entry get --project-id PAY --work-item-id PAY-7 --entry-id <uuid>
665
+ specflow work-item time-entry create --project-id PAY --work-item-id PAY-7 \
666
+ --start-time '2026-07-01T10:00:00+08:00' --end-time '2026-07-01T11:00:00+08:00' \
667
+ --description 'Reviewed rollout' --idempotency-key <uuid>
668
+ specflow work-item time-entry update --project-id PAY --work-item-id PAY-7 \
669
+ --entry-id <uuid> --start-time '2026-07-01T10:00:00+08:00' \
670
+ --end-time '2026-07-01T11:30:00+08:00' --expected-version '"1"' --idempotency-key <uuid>
671
+ ```
672
+
673
+ Timestamps require an ISO 8601 offset. Omit `--end-time` on creation to record an
674
+ unfinished entry; on update, omission preserves the existing end time. Omitted
675
+ descriptions are preserved on update. The server computes whole duration seconds;
676
+ unfinished entries have no duration and are excluded from `closedDurationSeconds`.
677
+ The list returns that exact cumulative value as a decimal string plus `runningCount`.
678
+
679
+ Writes require a CLI Session, current task update permission and a UUID command
680
+ key. Only the author may update an entry, including when another Member is an
681
+ Admin. Reuse the same key and inputs after an interrupted response. Updates require
682
+ the read result's opaque `version`; a conflict requires a deliberate reread.
683
+ Project Read Tokens can list and retrieve entries but cannot write. Archived
684
+ Projects are read-only. There is no time-entry deletion command.
685
+
686
+ ### Personal notifications
687
+
688
+ Use the existing User Session; a Project Read Token cannot access this inbox.
689
+
690
+ ```bash
691
+ specflow notification list
692
+ specflow notification read --notification-id <uuid> --idempotency-key <uuid>
693
+ specflow notification read-all --idempotency-key <uuid>
694
+ specflow notification clear --idempotency-key <uuid>
695
+ specflow notification preferences get
696
+ specflow notification preferences update --expected-version <version> --preferences '{"taskCommentEnabled":false}' --idempotency-key <uuid>
697
+ specflow notification create --type info --title "Review ready" --message "Evidence attached" --idempotency-key <uuid>
698
+ ```
699
+
700
+ Task or Workspace creation targets use paired `--related-entity-id` and
701
+ `--related-entity-type task|workspace` options; Task identifiers are current public
702
+ IDs such as `PAY-7`. Optional `--event-data` accepts a bounded JSON object. Manual
703
+ creation is for the current User only and returns a null notification when the
704
+ category is muted or the target is inaccessible.
705
+
706
+ The feed returns the latest 50 currently accessible entries and their unread
707
+ count. A read command returns the fresh target after a transfer. Reuse identical
708
+ inputs and the same UUID key after an ambiguous write outcome, particularly for
709
+ read-all and clear, so a retry cannot alter later arrivals. A CLI Session restricted
710
+ to Projects can read only admitted targets; read-all, clear, and preferences require
711
+ an unrestricted Session. Preference reads return an opaque version; saves require `--expected-version`. A
712
+ stale save returns Resource Changed and requires a deliberate reread and reapply.
713
+ Preferences accept partial assignment/comment/status/due
714
+ booleans and `dueDateReminderMinutes` from 5 to 43200. No command schedules reminders
715
+ or configures external delivery channels.
716
+
717
+ ### Public task sharing
718
+
719
+ ```bash
720
+ specflow project sharing read --project-id PAY
721
+ specflow project sharing set --project-id PAY --public true \
722
+ --expected-version '"1"' --idempotency-key <uuid>
723
+ specflow project sharing set --project-id PAY --public false \
724
+ --expected-version '"2"' --idempotency-key <uuid>
725
+ ```
726
+
727
+ Use the sharing read's opaque version, which is independent of the Project's
728
+ lifecycle version. Changes require a CLI Session with current `project:share`
729
+ permission on an Active Project. Reuse the same UUID key, boolean and expected
730
+ version after a lost response; a replay returns its original command result without
731
+ changing current sharing. Read the settings again to obtain the current state. A stale fresh command requires a
732
+ reread and an explicit decision. Project Read Tokens cannot manage sharing.
733
+
734
+ Public sharing exposes only Task/workflow fields and finalized assets currently
735
+ referenced by Task descriptions. It creates no Workspace membership and grants
736
+ no Spec, Glossary, resource association, comment or private attachment access.
737
+ Archive and Trash close sharing; Restore does not reopen it. The anonymous Web
738
+ entry is `/public/projects/<PROJECT>`.
739
+
740
+ ## User API Keys in the current checkout
741
+
742
+ Use an authenticated CLI Session to manage the current User’s explicitly scoped
743
+ keys. Creating a key saves its one-time secret directly to the origin-scoped system
744
+ credential store; stdout contains metadata and `credentialStored`, never the secret.
745
+ The warned owner-only file fallback remains opt-in with `--allow-file-fallback`.
746
+ These commands describe the repository implementation; a published npm release
747
+ must be verified separately.
748
+
749
+ ```bash
750
+ specflow --server https://specflow.example api-key create --name "Task automation" --project-ids PAY --scopes '{"workItem":["read","create"]}' --expires-at never --idempotency-key <uuid>
751
+ specflow --server https://specflow.example api-key list --limit 50
752
+ specflow --server https://specflow.example api-key list --cursor <nextCursor>
753
+ specflow --server https://specflow.example --api-key-id <key-id> work-item list --project-id PAY
754
+ specflow --server https://specflow.example api-key revoke --key-id <key-id> --expected-version '"1"' --idempotency-key <uuid>
755
+ ```
756
+
757
+ An expiry can instead be an explicit UTC ISO timestamp. Creation requires explicit
758
+ Project IDs and resource-to-actions scope statements. Spec/Glossary `publish` is a
759
+ separate grant; unknown or ungranted scopes fail closed. Every business request
760
+ checks current User eligibility, membership, Role, explicit key scope and lifecycle.
761
+ Global account/Workspace and credential-management routes require a User Session.
762
+
763
+ Keep the same creation command ID and intent when recovering an uncertain response.
764
+ A replay cannot redisclose a lost secret; revoke that key before deliberately issuing
765
+ a replacement. Saving failure triggers a versioned compensating revoke; an uncertain
766
+ revoke reports the key ID for operator reconciliation. Revocation removes only the
767
+ selected local key slot after the server confirms success. Session credentials stay
768
+ independent.
769
+
770
+ `SPEC_FLOW_API_KEY` accepts an explicitly provided key for CI. Do not place raw
771
+ secrets in command arguments or use multiple credential selectors. An absent selected
772
+ key never falls back to the Session. API Keys and Project Read Tokens cannot manage
773
+ credentials.
774
+
775
+ ## Explicit GitHub import in the current checkout
776
+
777
+ ```bash
778
+ specflow github connection status --project-id PAY
779
+ specflow github import status --project-id PAY
780
+ specflow github import run --project-id PAY --expected-version '"1"' --idempotency-key <uuid>
781
+ specflow github import run --project-id PAY --expected-version '"1"' --idempotency-key <new-uuid> --run-id <saved-run-id>
782
+ ```
783
+
784
+ Use the connected repository's observed version. Starting and reading import progress
785
+ require a real User Session with current GitHub connection management permission on
786
+ an Active Project. Each page separately checks the connection's current delegated
787
+ API Key and the Task/label operations it needs. Passing `--api-key-id` cannot grant
788
+ connection management.
789
+
790
+ The command processes bounded batches until completion, retrying rate limits up to
791
+ five times with the same command identity. Stdout contains the final run identity and
792
+ new/updated/skipped counts. A failed batch leaves committed progress intact; retry a
793
+ lost initial response with the same UUID and identical options. Once a run ID is known,
794
+ resume it explicitly. A completed explicit run returns its saved counts; starting
795
+ without a run ID after completion begins a new import.
796
+
797
+ Import reads open Issues, copies ordinary labels and non-bot historical comments, and
798
+ associates matching Pull Requests. Historical comments remain read-only. Explicit
799
+ import refreshes existing linked Tasks without applying webhook workflow rules; PR
800
+ association does not move a Task. The Project settings Web surface offers the same
801
+ start/resume capability. Closing it stops future batches; an in-flight batch may still
802
+ commit before the pause takes effect.
803
+
804
+ Specs may be created without `--capability-id`. They appear in Unassigned and retain
805
+ the same editing and publication contract. To move a Spec to Unassigned, use
806
+ `specflow spec update --project-id <id> --spec-id <id> --capability-id none
807
+ --expected-version <version>`. Capability deletion rejects child Capabilities;
808
+ deleting a leaf moves its Specs to Unassigned and records their placement changes.