@specset/cli 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.
- package/LICENSE +201 -0
- package/README.md +82 -0
- package/THIRD_PARTY_NOTICES.md +259 -0
- package/dist/main.js +30769 -0
- package/package.json +32 -0
- package/skills/specset/SKILL.md +171 -0
- package/skills/specset-admin/SKILL.md +116 -0
- package/skills/specset-agent/SKILL.md +50 -0
- package/skills/specset-closeout/SKILL.md +114 -0
- package/skills/specset-projects/SKILL.md +113 -0
- package/skills/specset-rfis/SKILL.md +124 -0
- package/skills/specset-search/SKILL.md +131 -0
- package/skills/specset-submittals/SKILL.md +164 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: specset-rfis
|
|
3
|
+
description: Manage Specset RFIs — list, search, create, update, and record responses including the official answer of record. Use for any RFI logging or tracking task.
|
|
4
|
+
allowed-tools: Bash, Read, AskUserQuestion
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Specset RFIs
|
|
8
|
+
|
|
9
|
+
Requires the `specset` CLI, logged in with an active org. If a command fails with `command not found`, `Not logged in`, or `No active organization`, follow First-Run Setup in the `specset` skill.
|
|
10
|
+
|
|
11
|
+
How the data is shaped:
|
|
12
|
+
|
|
13
|
+
- An RFI is a `subject` + `question`, numbered within the project, with a lifecycle `status` (RfiStatus: `Draft | Open | Closed`).
|
|
14
|
+
- `rfiNumber` is unique within a project — `createRfi` rejects a duplicate outright.
|
|
15
|
+
- Responses accumulate on the RFI; exactly one can be marked **official** — the answer of record that justifies closing the RFI.
|
|
16
|
+
|
|
17
|
+
The sketches below are minimal. When an operation or argument doesn't match, introspect rather than guess (see Schema Discovery in the `specset` skill). Enums, booleans, ints, and lists must be inlined in the operation text — reserve `-F` for IDs and plain strings (see Variable Limitations there).
|
|
18
|
+
|
|
19
|
+
## Reading
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# One RFI with its responses
|
|
23
|
+
specset api --query 'query($id: ID!) {
|
|
24
|
+
rfi(id: $id) {
|
|
25
|
+
id rfiNumber subject question status dueDate costImpact scheduleImpact
|
|
26
|
+
responses { id body isOfficialResponse createdAt }
|
|
27
|
+
attachments { id }
|
|
28
|
+
}
|
|
29
|
+
}' -F id=<rfi-id>
|
|
30
|
+
|
|
31
|
+
# Filtered list — enums inlined, never via -F
|
|
32
|
+
specset api --query 'query($projectId: ID!) {
|
|
33
|
+
rfis(projectId: $projectId, statuses: [Open]) {
|
|
34
|
+
id rfiNumber subject status dueDate
|
|
35
|
+
}
|
|
36
|
+
}' -F projectId=<project-id>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Also available:
|
|
40
|
+
|
|
41
|
+
- `paginatedRfis(projectId, search, limit: 25, offset: 0) { totalFilteredCount items { ... } }` — offset pagination; `search` matches subject and number. Use it for dedupe checks.
|
|
42
|
+
- `nextAvailableRfiNumber(projectId)` — the next number in the project's sequence, as a string.
|
|
43
|
+
- `rfiResponses(projectId) { id body isOfficialResponse }` — every response across the project.
|
|
44
|
+
|
|
45
|
+
## Creating
|
|
46
|
+
|
|
47
|
+
Dedupe first: search `paginatedRfis` with the RFI number from the document. A match by `rfiNumber` within the project is authoritative — never create a duplicate.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
specset api --query 'query($projectId: ID!, $search: String) {
|
|
51
|
+
paginatedRfis(projectId: $projectId, search: $search, limit: 5, offset: 0) {
|
|
52
|
+
totalFilteredCount
|
|
53
|
+
items { id rfiNumber subject status }
|
|
54
|
+
}
|
|
55
|
+
}' -F projectId=<project-id> -F search=RFI-000017
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
If the source document carries no number, omit `rfiNumber` and the project's numbering scheme assigns the next one (fetch `nextAvailableRfiNumber` first if the user wants to see it). Numbers you do supply are canonicalized — generic `RFI-` prefixes are normalized — so the stored value may differ slightly from your input; read `rfiNumber` back from the mutation result.
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
specset api <<'GQL'
|
|
62
|
+
mutation {
|
|
63
|
+
createRfi(input: {
|
|
64
|
+
projectId: "<project-id>"
|
|
65
|
+
subject: "Clarify hollow metal frame anchor spacing"
|
|
66
|
+
question: "Detail 5/A-501 shows anchors at 16 inches; spec 08 11 13 says 24. Which governs?"
|
|
67
|
+
status: Draft
|
|
68
|
+
specSectionIds: ["<spec-section-id>"]
|
|
69
|
+
drawingSheetIds: ["<drawing-sheet-id>"]
|
|
70
|
+
dueDate: "2026-08-01T00:00:00Z"
|
|
71
|
+
}) { id rfiNumber subject status }
|
|
72
|
+
}
|
|
73
|
+
GQL
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Field conventions:
|
|
77
|
+
|
|
78
|
+
- `status` defaults to `Draft`. Promote to `Open` when the RFI is formally issued; move to `Closed` only once the official response is recorded.
|
|
79
|
+
- `costImpact` and `scheduleImpact` are RfiImpact enums (`Yes | No`) — set them only when the user or source document states an impact explicitly; leave them unset otherwise.
|
|
80
|
+
- `rfiManagerId` assigns the managing org member.
|
|
81
|
+
|
|
82
|
+
Update with `updateRfi(input: { id, ... })` — same fields minus `projectId`; pass only what changes:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
specset api --query 'mutation($id: ID!) {
|
|
86
|
+
updateRfi(input: { id: $id, status: Open }) { id status }
|
|
87
|
+
}' -F id=<rfi-id>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Responses
|
|
91
|
+
|
|
92
|
+
Responses form the answer trail. Attach the reply document via `attachmentCloudFileIds` (file ids come from Uploading Files in the `specset` skill):
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
specset api <<'GQL'
|
|
96
|
+
mutation {
|
|
97
|
+
createRfiResponse(input: {
|
|
98
|
+
rfiId: "<rfi-id>"
|
|
99
|
+
body: "Use 16-inch anchor spacing per detail 5/A-501; the spec table is superseded."
|
|
100
|
+
attachmentCloudFileIds: ["<file-id>"]
|
|
101
|
+
}) { id isOfficialResponse }
|
|
102
|
+
}
|
|
103
|
+
GQL
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
- `updateRfiResponse(input: { id, body })` — edit a response body.
|
|
107
|
+
- `markRfiResponseAsOfficial(id)` / `unmarkRfiResponseAsOfficial(id)` — designate or revoke the answer of record. There is no official flag on create: create the response first, then mark it. Mark a response official only when it is the signed reply of record (typically the architect's or engineer's), not a discussion comment.
|
|
108
|
+
- `deleteRfiResponse(id)` — confirm with the user first.
|
|
109
|
+
|
|
110
|
+
The typical close-out sequence is: `createRfiResponse` → `markRfiResponseAsOfficial` → `updateRfi(input: { id, status: Closed })`.
|
|
111
|
+
|
|
112
|
+
## Attachments
|
|
113
|
+
|
|
114
|
+
Attachments on the RFI itself carry the question-side documents (sketches, photos, the original request PDF); response documents ride on the response via `attachmentCloudFileIds` above. Upload files per Uploading Files in the `specset` skill.
|
|
115
|
+
|
|
116
|
+
- `createRfiAttachment(input: { rfiId, cloudFileId })`
|
|
117
|
+
- `deleteRfiAttachment(id)`
|
|
118
|
+
- `reorderRfiAttachments(input: { rfiId, attachmentIds: [...] })`
|
|
119
|
+
|
|
120
|
+
## Safety
|
|
121
|
+
|
|
122
|
+
- Confirm with the user before every mutation (see Safety Rules in the `specset` skill) — creates included.
|
|
123
|
+
- `deleteRfi(id)` removes the RFI along with its responses and attachments. Prefer closing an RFI over deleting it; delete only on explicit instruction, after showing what will be lost.
|
|
124
|
+
- Marking or unmarking an official response changes the project's answer of record — name the response you're promoting before running it.
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: specset-search
|
|
3
|
+
description: Use when the user asks to find, look up, or locate anything in their Specset projects — semantic and keyword search across specs, drawing sheets, submittals, RFIs, documents, and closeout records.
|
|
4
|
+
allowed-tools: Bash, Read, AskUserQuestion
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Specset Search
|
|
8
|
+
|
|
9
|
+
Requires the `specset` CLI, logged in with an active org. If a command fails with `command not found`, `Not logged in`, or `No active organization`, follow First-Run Setup in the `specset` skill.
|
|
10
|
+
|
|
11
|
+
This skill is read-only — everything here is a query, so nothing needs user confirmation before running.
|
|
12
|
+
|
|
13
|
+
Remember Variable Limitations from the `specset` skill: `-F` sends **strings only**. Inline every enum, boolean, int, and list as a literal in the operation text; reserve `-F` for IDs and plain strings. If an operation or argument below doesn't match what the server accepts, introspect rather than guess — see Schema Discovery in the `specset` skill.
|
|
14
|
+
|
|
15
|
+
## Choosing an Operation
|
|
16
|
+
|
|
17
|
+
- **`search`** — the main tool: semantic (natural-language) and/or keyword search over indexed content, results grouped by record type.
|
|
18
|
+
- **`quickSearch`** — fast flat matching against titles and identifiers; use when the user roughly knows the record's name or number.
|
|
19
|
+
- **Precise lookups** — when the user already has a spec section number or drawing sheet number, skip search and fetch directly.
|
|
20
|
+
|
|
21
|
+
## Unified Search: `search`
|
|
22
|
+
|
|
23
|
+
```graphql
|
|
24
|
+
search(
|
|
25
|
+
orgId: ID!
|
|
26
|
+
projectId: ID # required when projectScope is CurrentProject
|
|
27
|
+
query: String # natural-language semantic query
|
|
28
|
+
keywords: [String!] # exact term matching — inline the list
|
|
29
|
+
referenceTypes: [SourceReferenceType!] # inline; omit to search all types
|
|
30
|
+
projectScope: ProjectScope = CurrentProject
|
|
31
|
+
titleOnly: Boolean = false
|
|
32
|
+
limit: Int = 10
|
|
33
|
+
offset: Int = 0
|
|
34
|
+
): SearchResults!
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
At least one of `query` or `keywords` is required; they can be combined. Results:
|
|
38
|
+
|
|
39
|
+
```graphql
|
|
40
|
+
{ groups { referenceType totalCount items { url title previewText score projectId projectName } } nextOffset }
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Project Scope (`ProjectScope`, inline it)
|
|
44
|
+
|
|
45
|
+
- `CurrentProject` — default; `projectId` is required.
|
|
46
|
+
- `MyProjects` — every project the user is a member of in the org.
|
|
47
|
+
- `AllProjects` — every project in the org. Requires the org **Manager** or **Admin** role — plain Members get an authorization error, so fall back to `MyProjects` when that happens.
|
|
48
|
+
|
|
49
|
+
### Record Types (`SourceReferenceType`, inline it)
|
|
50
|
+
|
|
51
|
+
Filter with `referenceTypes`. Common values: `SpecSection`, `DrawingSheet`, `Document`, `Submittal`, `SubmittalAttachment`, `Rfi`, and the closeout types `Company`, `Location`, `Product`, `Asset`, `Warranty`, `MaintenanceTask`. Introspect the `SourceReferenceType` enum for the full list.
|
|
52
|
+
|
|
53
|
+
## Quick Search: `quickSearch`
|
|
54
|
+
|
|
55
|
+
Substring matching against titles and identifiers — cheap and fast, no semantic ranking.
|
|
56
|
+
|
|
57
|
+
```graphql
|
|
58
|
+
quickSearch(
|
|
59
|
+
orgId: ID!
|
|
60
|
+
projectId: ID
|
|
61
|
+
query: String!
|
|
62
|
+
referenceTypes: [SourceReferenceType!]
|
|
63
|
+
projectScope: ProjectScope = CurrentProject
|
|
64
|
+
limit: Int = 20
|
|
65
|
+
): QuickSearchResults!
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Results: `{ items { id url number title referenceType score projectId projectName summary } totalCount }`
|
|
69
|
+
|
|
70
|
+
## Precise Lookups
|
|
71
|
+
|
|
72
|
+
When the user hands you a section or sheet number, look it up directly:
|
|
73
|
+
|
|
74
|
+
```graphql
|
|
75
|
+
specSectionByNumber(projectId: ID!, sectionNumber: String!): SpecSection # nullable
|
|
76
|
+
drawingSheetByNumber(projectId: ID!, sheetNumber: String!): DrawingSheet # nullable
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
To browse or filter within a project, the paginated lists take `projectId`, an optional `search` string, and `limit`/`offset` ints (inline the ints); both return `{ items totalFilteredCount }`:
|
|
80
|
+
|
|
81
|
+
```graphql
|
|
82
|
+
paginatedSpecSections(projectId: ID!, search: String, limit: Int, offset: Int): SpecSectionPage!
|
|
83
|
+
paginatedDrawingSheets(projectId: ID!, search: String, limit: Int, offset: Int): DrawingSheetPage!
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Useful item fields: `id sectionNumber title numberAndTitle` on SpecSection, `id sheetNumber title` on DrawingSheet.
|
|
87
|
+
|
|
88
|
+
## Examples
|
|
89
|
+
|
|
90
|
+
Semantic search scoped to one project:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
specset api --query 'query($orgId: ID!, $projectId: ID!, $q: String!) {
|
|
94
|
+
search(orgId: $orgId, projectId: $projectId, query: $q,
|
|
95
|
+
projectScope: CurrentProject, limit: 10) {
|
|
96
|
+
groups { referenceType totalCount
|
|
97
|
+
items { url title previewText score } }
|
|
98
|
+
nextOffset
|
|
99
|
+
}
|
|
100
|
+
}' -F orgId=<org-id> -F projectId=<project-id> \
|
|
101
|
+
-F q='vibration isolation requirements for rooftop mechanical equipment'
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Keyword search filtered to submittals, across all of the user's projects (note the inlined list, enums, and int):
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
specset api --query 'query($orgId: ID!) {
|
|
108
|
+
search(orgId: $orgId, keywords: ["AHU-1"],
|
|
109
|
+
referenceTypes: [Submittal, SubmittalAttachment],
|
|
110
|
+
projectScope: MyProjects, limit: 20) {
|
|
111
|
+
groups { referenceType totalCount
|
|
112
|
+
items { url title previewText projectName } }
|
|
113
|
+
}
|
|
114
|
+
}' -F orgId=<org-id>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Direct lookup by section number:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
specset api --query 'query($projectId: ID!, $n: String!) {
|
|
121
|
+
specSectionByNumber(projectId: $projectId, sectionNumber: $n) {
|
|
122
|
+
id sectionNumber title numberAndTitle
|
|
123
|
+
}
|
|
124
|
+
}' -F projectId=<project-id> -F n='23 05 48'
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Working With Results
|
|
128
|
+
|
|
129
|
+
- Each result's `url` is a Specset reference URL — `sb://spec/<id>`, `sb://drawing/<id>`, `sb://sub/<id>`, `sb://rfi/<id>`, `sb://doc/<id>`, and so on. The UUID inside is the record's GraphQL `id`; extract it for follow-up queries on that record.
|
|
130
|
+
- Lookups by id are scoped to the **active org**: an id from a different org returns `null` rather than an error. If a known-good id comes back null, check `specset auth status` and switch orgs.
|
|
131
|
+
- For follow-up work on what you found — updating submittals, answering RFIs, closeout records, or project/document changes — hand off to the `specset-submittals`, `specset-rfis`, `specset-closeout`, or `specset-projects` skill.
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: specset-submittals
|
|
3
|
+
description: Manage Specset submittals — list, search, create, revise, attach files, track approvers, and run AI spec-compliance reviews. Use for any submittal log or submittal review task.
|
|
4
|
+
allowed-tools: Bash, Read, AskUserQuestion
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Specset Submittals
|
|
8
|
+
|
|
9
|
+
Requires the `specset` CLI, logged in with an active org. If a command fails with `command not found`, `Not logged in`, or `No active organization`, follow First-Run Setup in the `specset` skill.
|
|
10
|
+
|
|
11
|
+
How the data is shaped:
|
|
12
|
+
|
|
13
|
+
- `status` (SubmittalStatus: `Draft | Open | Closed`) is the log lifecycle; `workingStatus` (SubmittalWorkingStatus: `Draft | Submitted | Pending | Approved | ApprovedAsNoted | Rejected | ReviseAndResubmit | ForRecordOnly | Void`) is the ball-in-court state. Set either — the server reconciles the other.
|
|
14
|
+
- Revisions are separate submittal records sharing a `submittalNumber`; list and search queries return only the current revision.
|
|
15
|
+
- A submittal number is only unique per (spec section, number) within a project — the bare number alone is not.
|
|
16
|
+
|
|
17
|
+
The sketches below are minimal. When an operation or argument doesn't match, introspect rather than guess (see Schema Discovery in the `specset` skill). Enums, booleans, ints, and lists must be inlined in the operation text — reserve `-F` for IDs and plain strings (see Variable Limitations there).
|
|
18
|
+
|
|
19
|
+
## Reading
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# One submittal with its relationships
|
|
23
|
+
specset api --query 'query($id: ID!) {
|
|
24
|
+
submittal(id: $id) {
|
|
25
|
+
id submittalNumber sectionNumber title revision status workingStatus
|
|
26
|
+
attachments { id description }
|
|
27
|
+
approvers { id status comment returnedDate }
|
|
28
|
+
submittalReviews { id status oneLiner lastReviewedAt }
|
|
29
|
+
}
|
|
30
|
+
}' -F id=<submittal-id>
|
|
31
|
+
|
|
32
|
+
# Filtered list — enums inlined, never via -F
|
|
33
|
+
specset api --query 'query($projectId: ID!) {
|
|
34
|
+
submittals(projectId: $projectId, statuses: [Open], workingStatuses: [Pending, Submitted]) {
|
|
35
|
+
id submittalNumber sectionNumber title workingStatus submitByDate
|
|
36
|
+
}
|
|
37
|
+
}' -F projectId=<project-id>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Also available:
|
|
41
|
+
|
|
42
|
+
- `paginatedSubmittals(projectId, search, limit: 25, offset: 0) { totalFilteredCount items { ... } }` — offset pagination with text search.
|
|
43
|
+
- `searchSubmittals(projectId, search)` — lightweight lookup by number or title; use it for dedupe checks.
|
|
44
|
+
- `submittalRevisions(projectId, submittalNumber, specSectionId)` — every revision of one number.
|
|
45
|
+
- `submittalTypes(projectId) { id name label }` — the project's configured types, for `submittalTypeId`.
|
|
46
|
+
|
|
47
|
+
## Creating
|
|
48
|
+
|
|
49
|
+
Dedupe first: run `searchSubmittals` with the submittal number (or title plus section). A match on (spec section, number) is authoritative — never create a duplicate; the API does not enforce this for you. Omit `submittalNumber` to auto-number in the project's sequence.
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
specset api <<'GQL'
|
|
53
|
+
mutation {
|
|
54
|
+
createSubmittal(input: {
|
|
55
|
+
projectId: "<project-id>"
|
|
56
|
+
title: "Hollow Metal Doors and Frames - Product Data"
|
|
57
|
+
submittalNumber: "08 11 13-001"
|
|
58
|
+
specSectionIds: ["<spec-section-id>"]
|
|
59
|
+
submittalTypeId: "<submittal-type-id>"
|
|
60
|
+
status: Draft
|
|
61
|
+
}) { id submittalNumber title }
|
|
62
|
+
}
|
|
63
|
+
GQL
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**From a PDF**: upload it first (see Uploading Files in the `specset` skill) and pass the resulting file id as `cloudFileId` — Specset extracts the number, title, and section from the document, so you can omit them. Add `autoReviewMode: GeneratePlanAndApprove` to start an AI compliance review in the same call (`GeneratePlan` pauses at the plan for approval; default `None`).
|
|
67
|
+
|
|
68
|
+
Update with `updateSubmittal(input: { id, ... })` — same fields as create, minus `projectId`/`cloudFileId`. Pass only what changes.
|
|
69
|
+
|
|
70
|
+
## Revisions
|
|
71
|
+
|
|
72
|
+
`createSubmittalRevision` supersedes the current revision with a fresh one reset to Draft:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
specset api --query 'mutation($id: ID!) {
|
|
76
|
+
createSubmittalRevision(input: { id: $id, cloudFileIds: ["<file-id>"] }) {
|
|
77
|
+
id revision status
|
|
78
|
+
}
|
|
79
|
+
}' -F id=<submittal-id>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The revision string auto-increments (`0` → `1`, `A` → `B`); pass `revision` only when the document shows one that doesn't match. `autoReviewMode` works here too.
|
|
83
|
+
|
|
84
|
+
## Attachments
|
|
85
|
+
|
|
86
|
+
File ids come from Uploading Files in the `specset` skill.
|
|
87
|
+
|
|
88
|
+
- `createSubmittalAttachment(input: { submittalId, cloudFileId, type, description, order })`
|
|
89
|
+
- `createSubmittalAttachments(input: { submittalId, cloudFileIds: [...], autoReviewMode })` — batch form
|
|
90
|
+
- `updateSubmittalAttachment(input: { id, type, description, order })`
|
|
91
|
+
- `deleteSubmittalAttachment(id)`
|
|
92
|
+
|
|
93
|
+
`type` is a SubmittalAttachmentType enum (`ProductData`, `ShopDrawing`, `Certifications`, …) — introspect for the full list and inline the value.
|
|
94
|
+
|
|
95
|
+
## Approvers
|
|
96
|
+
|
|
97
|
+
Track who reviews the submittal and what they returned:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
specset api <<'GQL'
|
|
101
|
+
mutation {
|
|
102
|
+
createSubmittalApprover(input: {
|
|
103
|
+
submittalId: "<submittal-id>"
|
|
104
|
+
userId: "<user-id>"
|
|
105
|
+
dueDate: "2026-08-01T00:00:00Z"
|
|
106
|
+
responseRequired: true
|
|
107
|
+
}) { id order status }
|
|
108
|
+
}
|
|
109
|
+
GQL
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Record a returned response with `updateSubmittalApprover(input: { id, status: ApprovedAsNoted, comment: "...", returnedDate: "...", cloudFileIds: [...] })`. `status` is SubmittalApproverResponseStatus: `Approved | ApprovedAsNoted | ForRecordOnly | Pending | Rejected | ReviseAndResubmit | Void | Submitted`. `approverType` distinguishes `Approver` (default) from `Submitter`; `sentDate` and `distributed` track routing. Remove with `deleteSubmittalApprover(id)`.
|
|
113
|
+
|
|
114
|
+
## AI Compliance Reviews
|
|
115
|
+
|
|
116
|
+
Three steps: create, poll, compile. Arguments are top-level (no input object).
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# 1. Start the review
|
|
120
|
+
specset api --query 'mutation($submittalId: ID!) {
|
|
121
|
+
createSubmittalReview(submittalId: $submittalId, createMode: GeneratePlanAndApprove) { id }
|
|
122
|
+
}' -F submittalId=<submittal-id>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Optional: `prompt` (focus instructions, a plain string), and `specSectionIds` / `drawingSheetIds` (inline lists) to pin the spec basis.
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
# 2. Poll until lastReviewedAt is set — reviews take minutes; see
|
|
129
|
+
# Waiting on Background Processing in the specset skill for cadence
|
|
130
|
+
specset api --query 'query($id: ID!) {
|
|
131
|
+
submittalReview(id: $id) { id status oneLiner lastReviewedAt }
|
|
132
|
+
}' -F id=<review-id>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`status` is the verdict, ReviewStatus: `Compliant | NonCompliant | MissingInfo | ClarificationsNeeded | NotApplicable`.
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
# 3. Compile the annotated, marked-up PDF
|
|
139
|
+
specset api --query 'mutation($id: ID!) {
|
|
140
|
+
compileSubmittalReview(id: $id, includeIssueToc: true) { id compiledReview { id } }
|
|
141
|
+
}' -F id=<review-id>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`excludedIssueIds: [...]` (inlined) drops chosen issues from the compiled PDF and TOC. `updateSubmittalReview(id, comments)` records reviewer comments; `deleteSubmittalReview(id)` removes a review.
|
|
145
|
+
|
|
146
|
+
## Drafting a Whole Submittal Log
|
|
147
|
+
|
|
148
|
+
Generating a full log from the specs is an agent job, not raw GraphQL. Use the `specset-agent` skill and ask the in-app agent to draft a submittal log for the project — the run produces a SubmittalLog artifact on its thread. Read the drafted rows (each entry has a numeric id) with `agentArtifact(id) { id title content }`, let the user pick, then apply:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
specset api --query 'mutation($artifactId: ID!) {
|
|
152
|
+
applySubmittalLog(input: { artifactId: $artifactId, selectedSubmittalLogIds: [1, 2, 3] }) {
|
|
153
|
+
id content
|
|
154
|
+
}
|
|
155
|
+
}' -F artifactId=<artifact-id>
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Applying skips rows whose (section, number) already exists and writes per-row results back onto the artifact — re-read `content` to report errors and warnings.
|
|
159
|
+
|
|
160
|
+
## Safety
|
|
161
|
+
|
|
162
|
+
- Confirm with the user before every mutation (see Safety Rules in the `specset` skill) — creates included.
|
|
163
|
+
- `deleteSubmittal(id)` removes one revision. `deleteSubmittal(id, deleteAllRevisions: true)` removes the entire revision chain — state that difference and get explicit confirmation before using it.
|
|
164
|
+
- Never loop deletes over a list the user hasn't seen.
|