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