@specset/cli 0.5.1 → 1.0.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 +103 -201
- package/README.md +28 -4
- package/dist/main.js +4790 -5626
- package/package.json +2 -2
- package/plugin/specset/.claude-plugin/plugin.json +18 -0
- package/plugin/specset/.codex-plugin/plugin.json +42 -0
- package/plugin/specset/.mcp.json +8 -0
- package/plugin/specset/README.md +11 -0
- package/plugin/specset/skills/specset/SKILL.md +120 -0
- package/plugin/specset/skills/specset-admin/SKILL.md +112 -0
- package/plugin/specset/skills/specset-agent/SKILL.md +50 -0
- package/plugin/specset/skills/specset-closeout/SKILL.md +188 -0
- package/plugin/specset/skills/specset-projects/SKILL.md +110 -0
- package/plugin/specset/skills/specset-rfis/SKILL.md +96 -0
- package/plugin/specset/skills/specset-search/SKILL.md +122 -0
- package/plugin/specset/skills/specset-submittals/SKILL.md +114 -0
- package/plugin/specset/skills/specset-tools/SKILL.md +105 -0
- package/skills/specset/SKILL.md +25 -41
- package/skills/specset-agent/SKILL.md +2 -0
- package/skills/specset-closeout/SKILL.md +146 -52
- package/skills/specset-search/SKILL.md +23 -5
- package/skills/specset-tools/SKILL.md +27 -17
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: specset-projects
|
|
3
|
+
description: Use when standing up a new Specset project or adding document sets to one — create the project, upload spec/drawing PDFs, monitor processing, and publish spec sets and drawing sets.
|
|
4
|
+
allowed-tools: Bash Read AskUserQuestion
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Specset Projects
|
|
8
|
+
|
|
9
|
+
This skill mutates data — confirm the project name and which files belong to it before creating anything. Inline enums, ints, lists, and input objects in the operation text (`-F` is strings-only; Variable Limitations and Schema Discovery live in the `specset` skill).
|
|
10
|
+
|
|
11
|
+
## Reading Projects
|
|
12
|
+
|
|
13
|
+
```graphql
|
|
14
|
+
projects(orgId: ID!, archived: Boolean, includeAll: Boolean = false): [Project!]!
|
|
15
|
+
paginatedProjects(orgId: ID!, search: String, limit: Int, offset: Int, archived: Boolean = false): ProjectPage! # { items totalFilteredCount }
|
|
16
|
+
project(id: ID!): Project!
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**`projects` is membership-filtered by default** — it returns only projects where the caller is a project member. Inline `includeAll: true` to list every project in the org. Use `paginatedProjects` with a `search` string to find a project by name.
|
|
20
|
+
|
|
21
|
+
## Creating a Project
|
|
22
|
+
|
|
23
|
+
`createProject` does **not** add the creator as a project member, so the new project won't appear in the default `projects` list — fetch it by `project(id)`, and add membership as a separate step, mirroring the UI.
|
|
24
|
+
|
|
25
|
+
1. Create it:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
specset api --query 'mutation($orgId: ID!, $name: String!) {
|
|
29
|
+
createProject(orgId: $orgId, name: $name) { id name }
|
|
30
|
+
}' -F orgId=<org-id> -F name='Riverside Medical Center'
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
2. Get your own user id with `{ me { id } }`, then add yourself (and anyone else the user names) as a member. `role` is an optional `OrgMemberTeamRole` enum — `Member`, `Manager`, or `Admin` — inlined when specified:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
specset api --query 'mutation($projectId: ID!, $userId: ID!) {
|
|
37
|
+
addProjectMember(projectId: $projectId, userId: $userId, role: Member) { id }
|
|
38
|
+
}' -F projectId=<project-id> -F userId=<user-id>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Importing Documents
|
|
42
|
+
|
|
43
|
+
1. Upload each PDF following Uploading Files in the `specset` skill. Each `completeUpload` returns a **cloud file id** — collect them.
|
|
44
|
+
2. Register the uploads on the project in a single call. The input object is inlined; ID variables are fine as its leaves:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
specset api --query 'mutation($projectId: ID!, $f1: ID!, $f2: ID!) {
|
|
48
|
+
createProjectDocuments(input: {
|
|
49
|
+
projectId: $projectId
|
|
50
|
+
cloudFileIds: [$f1, $f2]
|
|
51
|
+
autoImport: true
|
|
52
|
+
}) { id totalCount completedCount failedCount }
|
|
53
|
+
}' -F projectId=<project-id> -F f1=<cloud-file-id> -F f2=<cloud-file-id>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
3. It returns a **BulkAction** — poll it per Waiting on Background Processing in the `specset` skill. Processing extracts each document's outline and content, indexes it for search, and splits it into candidate spec files and drawing files by page range.
|
|
57
|
+
|
|
58
|
+
### `autoImport`: publish automatically or curate
|
|
59
|
+
|
|
60
|
+
- `autoImport: true` — every generated spec/drawing file is classified and published into default sets automatically. Use when the user wants everything live immediately.
|
|
61
|
+
- Omit it (or `false`) — files land **unpromoted** for manual curation below. Use when the user wants to control set titles, membership, or ordering.
|
|
62
|
+
|
|
63
|
+
Ask the user when their intent is ambiguous — this choice changes what gets published. The input also supports folders: `parentFolderId` / `newFolderName` for flat imports, or `folderTree` + `files` to mirror a directory structure — introspect `CreateProjectDocumentsInput` for those shapes.
|
|
64
|
+
|
|
65
|
+
## Publishing Spec and Drawing Sets
|
|
66
|
+
|
|
67
|
+
Publishing promotes processed files into a SpecSet or DrawingSet container. After a non-autoImport run finishes:
|
|
68
|
+
|
|
69
|
+
1. Review what processing generated, and summarize it to the user before publishing:
|
|
70
|
+
|
|
71
|
+
```graphql
|
|
72
|
+
unpromotedSpecFiles(projectId: ID!): [SpecFile!]! # { id title }
|
|
73
|
+
unpromotedDrawingFiles(projectId: ID!): [DrawingFile!]! # { id title }
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Empty lists mean processing hasn't finished, or `autoImport` already promoted everything.
|
|
77
|
+
|
|
78
|
+
2. Create a set and assign files into it (`issuedAt` is an optional DateTime — inline an ISO literal like `issuedAt: "2026-06-01T00:00:00Z"`):
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
specset api --query 'mutation($projectId: ID!, $title: String!) {
|
|
82
|
+
createSpecSet(title: $title, projectId: $projectId) { id title }
|
|
83
|
+
}' -F projectId=<project-id> -F title='Issued for Construction'
|
|
84
|
+
|
|
85
|
+
specset api --query 'mutation($specSetId: ID!, $f1: ID!) {
|
|
86
|
+
assignSpecFilesToSpecSet(specSetId: $specSetId, specFileIds: [$f1]) { id }
|
|
87
|
+
}' -F specSetId=<spec-set-id> -F f1=<spec-file-id>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The drawing side is symmetric: `createDrawingSet(title: String!, projectId: ID!, issuedAt: DateTime)` then `assignDrawingFilesToDrawingSet(drawingSetId: ID!, drawingFileIds: [ID!]!)`.
|
|
91
|
+
|
|
92
|
+
3. When a project has multiple sets (e.g. an addendum on top of the base issue), set which one governs. Pass the **complete** list of set ids, highest precedence first:
|
|
93
|
+
|
|
94
|
+
```graphql
|
|
95
|
+
updateSpecSetPrecedence(projectId: ID!, specSetIds: [ID!]!): [SpecSet!]!
|
|
96
|
+
updateDrawingSetPrecedence(projectId: ID!, drawingSetIds: [ID!]!): [DrawingSet!]!
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
4. Verify and summarize — query `project(id)` (not the membership-filtered `projects` list) with `specSets { id title }` and `drawingSets { id title }`, and report what was created.
|
|
100
|
+
|
|
101
|
+
## Archiving and Deleting
|
|
102
|
+
|
|
103
|
+
- **Prefer `archiveProject(id: ID!)`** — reversible via `unarchiveProject(id: ID!)`. Archived projects drop out of default lists but keep all data.
|
|
104
|
+
- **`deleteProject(id: ID!)` permanently destroys the entire project** — documents, specs, drawings, submittals, RFIs, everything. Confirm with the user, naming the exact project, before running it. If they just want it out of the way, archive instead.
|
|
105
|
+
|
|
106
|
+
## Troubleshooting
|
|
107
|
+
|
|
108
|
+
- Authorization error on a mutation — the user lacks that permission; the same rules as the UI apply. Surface the message, don't retry.
|
|
109
|
+
- A freshly created project "missing" from `projects` — that's the membership gotcha above, not a failure.
|
|
110
|
+
- A document reports a failed processing status on the BulkAction — report it to the user; re-uploading is their call.
|
|
@@ -0,0 +1,96 @@
|
|
|
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
|
+
How the data is shaped:
|
|
10
|
+
|
|
11
|
+
- An RFI is a `subject` + `question`, numbered within the project, with a lifecycle `status` (RfiStatus: `Draft | Open | Closed`).
|
|
12
|
+
- `rfiNumber` is unique within a project — `createRfi` rejects a duplicate outright.
|
|
13
|
+
- Responses accumulate on the RFI; exactly one can be marked **official** — the answer of record that justifies closing the RFI.
|
|
14
|
+
|
|
15
|
+
The sketches below are minimal — inline enums, ints, and lists in the operation text (`-F` is strings-only; Variable Limitations and Schema Discovery live in the `specset` skill).
|
|
16
|
+
|
|
17
|
+
## Reading
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# One RFI with its responses
|
|
21
|
+
specset api --query 'query($id: ID!) {
|
|
22
|
+
rfi(id: $id) {
|
|
23
|
+
id rfiNumber subject question status dueDate costImpact scheduleImpact
|
|
24
|
+
responses { id body isOfficialResponse createdAt }
|
|
25
|
+
attachments { id }
|
|
26
|
+
}
|
|
27
|
+
}' -F id=<rfi-id>
|
|
28
|
+
|
|
29
|
+
# Filtered list — enums inlined, never via -F
|
|
30
|
+
specset api --query 'query($projectId: ID!) {
|
|
31
|
+
rfis(projectId: $projectId, statuses: [Open]) {
|
|
32
|
+
id rfiNumber subject status dueDate
|
|
33
|
+
}
|
|
34
|
+
}' -F projectId=<project-id>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Also available:
|
|
38
|
+
|
|
39
|
+
- `paginatedRfis(projectId, search, limit: 25, offset: 0) { totalFilteredCount items { ... } }` — offset pagination; `search` matches subject and number. Use it for dedupe checks.
|
|
40
|
+
- `nextAvailableRfiNumber(projectId)` — the next number in the project's sequence, as a string.
|
|
41
|
+
- `rfiResponses(projectId) { id body isOfficialResponse }` — every response across the project.
|
|
42
|
+
|
|
43
|
+
## Creating
|
|
44
|
+
|
|
45
|
+
Dedupe first: search `paginatedRfis` with the RFI number from the document. A match by `rfiNumber` within the project is authoritative — never create a duplicate.
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
specset api --query 'query($projectId: ID!, $search: String) {
|
|
49
|
+
paginatedRfis(projectId: $projectId, search: $search, limit: 5, offset: 0) {
|
|
50
|
+
totalFilteredCount
|
|
51
|
+
items { id rfiNumber subject status }
|
|
52
|
+
}
|
|
53
|
+
}' -F projectId=<project-id> -F search=RFI-000017
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
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.
|
|
57
|
+
|
|
58
|
+
`createRfi(input: { projectId, subject, question, status, specSectionIds: [...], drawingSheetIds: [...], dueDate }) { id rfiNumber subject status }`
|
|
59
|
+
|
|
60
|
+
Field conventions:
|
|
61
|
+
|
|
62
|
+
- `status` defaults to `Draft`. Promote to `Open` when the RFI is formally issued; move to `Closed` only once the official response is recorded.
|
|
63
|
+
- `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.
|
|
64
|
+
- `rfiManagerId` assigns the managing org member.
|
|
65
|
+
|
|
66
|
+
Update with `updateRfi(input: { id, ... })` — same fields minus `projectId`; pass only what changes:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
specset api --query 'mutation($id: ID!) {
|
|
70
|
+
updateRfi(input: { id: $id, status: Open }) { id status }
|
|
71
|
+
}' -F id=<rfi-id>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Responses
|
|
75
|
+
|
|
76
|
+
Responses form the answer trail. Attach the reply document via `attachmentCloudFileIds` (file ids come from Uploading Files in the `specset` skill):
|
|
77
|
+
|
|
78
|
+
- `createRfiResponse(input: { rfiId, body, attachmentCloudFileIds: [...] }) { id isOfficialResponse }`
|
|
79
|
+
- `updateRfiResponse(input: { id, body })` — edit a response body.
|
|
80
|
+
- `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. The official response is normally the signed reply of record (typically the architect's or engineer's), not a discussion comment.
|
|
81
|
+
- `deleteRfiResponse(id)` — confirm with the user first.
|
|
82
|
+
|
|
83
|
+
The typical close-out sequence is: `createRfiResponse` → `markRfiResponseAsOfficial` → `updateRfi(input: { id, status: Closed })`.
|
|
84
|
+
|
|
85
|
+
## Attachments
|
|
86
|
+
|
|
87
|
+
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.
|
|
88
|
+
|
|
89
|
+
- `createRfiAttachment(input: { rfiId, cloudFileId })`
|
|
90
|
+
- `deleteRfiAttachment(id)`
|
|
91
|
+
- `reorderRfiAttachments(input: { rfiId, attachmentIds: [...] })`
|
|
92
|
+
|
|
93
|
+
## Safety
|
|
94
|
+
|
|
95
|
+
- `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.
|
|
96
|
+
- 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,122 @@
|
|
|
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
|
+
This skill is read-only — everything here is a query, so nothing needs user confirmation before running.
|
|
10
|
+
|
|
11
|
+
`-F` sends strings only: inline enums, booleans, ints, and lists as literals in the operation text (Variable Limitations and Schema Discovery live in the `specset` skill).
|
|
12
|
+
|
|
13
|
+
## Choosing an Operation
|
|
14
|
+
|
|
15
|
+
- **`search`** — the main tool: semantic (natural-language) and/or keyword search over indexed content, results grouped by record type.
|
|
16
|
+
- **`quickSearch`** — fast flat matching against titles and identifiers; use when the user roughly knows the record's name or number.
|
|
17
|
+
- **Precise lookups** — when the user already has a spec section number or drawing sheet number, skip search and fetch directly.
|
|
18
|
+
|
|
19
|
+
> Need the actual content, not just previews and IDs? The `specset-tools` skill exposes Specset's own retrieval tools (`specset tools run searchSpecSections`, `searchDrawingSheets`, `searchDocuments`, …), which return expanded, citation-anchored markdown. Use `search`/`quickSearch` here to locate records cheaply; reach for `specset-tools` when you need to read and reason over the content.
|
|
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
|
+
{
|
|
41
|
+
groups {
|
|
42
|
+
referenceType
|
|
43
|
+
totalCount
|
|
44
|
+
items {
|
|
45
|
+
id
|
|
46
|
+
url
|
|
47
|
+
title
|
|
48
|
+
previewText
|
|
49
|
+
score
|
|
50
|
+
projectId
|
|
51
|
+
projectName
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
nextOffset
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Project Scope (`ProjectScope`)
|
|
59
|
+
|
|
60
|
+
- `CurrentProject` — default; `projectId` is required.
|
|
61
|
+
- `MyProjects` — every project the user is a member of in the org.
|
|
62
|
+
- `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.
|
|
63
|
+
|
|
64
|
+
### Record Types (`SourceReferenceType`)
|
|
65
|
+
|
|
66
|
+
Filter semantic `search` with the record types it materializes: `SpecSection`, `DrawingSheet`, `Document`, `Submittal`, and `Rfi`. Other values exist in the shared `SourceReferenceType` enum but are not necessarily supported by this operation.
|
|
67
|
+
|
|
68
|
+
## Quick Search: `quickSearch`
|
|
69
|
+
|
|
70
|
+
Substring matching against titles and identifiers — cheap and fast, no semantic ranking.
|
|
71
|
+
|
|
72
|
+
```graphql
|
|
73
|
+
quickSearch(
|
|
74
|
+
orgId: ID!
|
|
75
|
+
projectId: ID
|
|
76
|
+
query: String!
|
|
77
|
+
referenceTypes: [SourceReferenceType!]
|
|
78
|
+
projectScope: ProjectScope = CurrentProject
|
|
79
|
+
limit: Int = 20
|
|
80
|
+
): QuickSearchResults!
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Results: `{ items { id url number title referenceType score projectId projectName summary } totalCount }`
|
|
84
|
+
|
|
85
|
+
`quickSearch` also supports explicitly requested closeout title records: `Company`, `Location`, `System`, `Product`, `Asset`, and `MaintenancePlan`. It does not currently return `Warranty`, `Procedure`, or `WorkOrder` rows. `MaintenanceTask` is a deprecated legacy-link redirect, not a searchable record type. Omit `referenceTypes` for the standard global-search defaults, or introspect `SourceReferenceType` and use only a type supported by the selected operation.
|
|
86
|
+
|
|
87
|
+
## Precise Lookups
|
|
88
|
+
|
|
89
|
+
When the user hands you a section or sheet number, look it up directly:
|
|
90
|
+
|
|
91
|
+
```graphql
|
|
92
|
+
specSectionByNumber(projectId: ID!, sectionNumber: String!): SpecSection # nullable
|
|
93
|
+
drawingSheetByNumber(projectId: ID!, sheetNumber: String!): DrawingSheet # nullable
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
To browse or filter within a project, the paginated lists take `projectId`, an optional `search` string, and `limit`/`offset` ints; both return `{ items totalFilteredCount }`:
|
|
97
|
+
|
|
98
|
+
```graphql
|
|
99
|
+
paginatedSpecSections(projectId: ID!, search: String, limit: Int, offset: Int): SpecSectionPage!
|
|
100
|
+
paginatedDrawingSheets(projectId: ID!, search: String, limit: Int, offset: Int): DrawingSheetPage!
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Useful item fields: `id sectionNumber title numberAndTitle` on SpecSection, `id sheetNumber title` on DrawingSheet.
|
|
104
|
+
|
|
105
|
+
## Example
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
specset api --query 'query($orgId: ID!) {
|
|
109
|
+
search(orgId: $orgId, keywords: ["AHU-1"],
|
|
110
|
+
referenceTypes: [Submittal],
|
|
111
|
+
projectScope: MyProjects, limit: 20) {
|
|
112
|
+
groups { referenceType totalCount
|
|
113
|
+
items { id url title previewText projectName } }
|
|
114
|
+
}
|
|
115
|
+
}' -F orgId=<org-id>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Working With Results
|
|
119
|
+
|
|
120
|
+
- Each result's `id` is the UUID for follow-up GraphQL calls. Its `url` is the clickable source link (`https://app.specbook.ai/go/spec/<uuid>`, `https://app.specbook.ai/go/sub/<uuid>`, …) and can be passed directly into Specset tools and download commands.
|
|
121
|
+
- In user-facing answers, include the most relevant returned `url` values as descriptive Markdown source links so the user can open the result in Specset. Never synthesize a `/go` link from a UUID that was not returned as a source link.
|
|
122
|
+
- 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,114 @@
|
|
|
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
|
+
How the data is shaped:
|
|
10
|
+
|
|
11
|
+
- `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.
|
|
12
|
+
- Revisions are separate submittal records sharing a `submittalNumber`; list and search queries return only the current revision.
|
|
13
|
+
- A submittal number is only unique per (spec section, number) within a project — the bare number alone is not.
|
|
14
|
+
|
|
15
|
+
The sketches below are minimal — inline enums, ints, and lists in the operation text (`-F` is strings-only; Variable Limitations and Schema Discovery live in the `specset` skill).
|
|
16
|
+
|
|
17
|
+
## Reading
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# One submittal with its relationships
|
|
21
|
+
specset api --query 'query($id: ID!) {
|
|
22
|
+
submittal(id: $id) {
|
|
23
|
+
id submittalNumber sectionNumber title revision status workingStatus
|
|
24
|
+
attachments { id description }
|
|
25
|
+
approvers { id status comment returnedDate }
|
|
26
|
+
submittalReviews { id status oneLiner lastReviewedAt }
|
|
27
|
+
}
|
|
28
|
+
}' -F id=<submittal-id>
|
|
29
|
+
|
|
30
|
+
# Filtered list — enums inlined, never via -F
|
|
31
|
+
specset api --query 'query($projectId: ID!) {
|
|
32
|
+
submittals(projectId: $projectId, statuses: [Open], workingStatuses: [Pending, Submitted]) {
|
|
33
|
+
id submittalNumber sectionNumber title workingStatus submitByDate
|
|
34
|
+
}
|
|
35
|
+
}' -F projectId=<project-id>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Also available:
|
|
39
|
+
|
|
40
|
+
- `paginatedSubmittals(projectId, search, limit: 25, offset: 0) { totalFilteredCount items { ... } }` — offset pagination with text search.
|
|
41
|
+
- `searchSubmittals(projectId, search)` — lightweight lookup by number or title; use it for dedupe checks.
|
|
42
|
+
- `submittalRevisions(projectId, submittalNumber, specSectionId)` — every revision of one number.
|
|
43
|
+
- `submittalTypes(projectId) { id name label }` — the project's configured types, for `submittalTypeId`.
|
|
44
|
+
|
|
45
|
+
## Creating
|
|
46
|
+
|
|
47
|
+
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.
|
|
48
|
+
|
|
49
|
+
`createSubmittal(input: { projectId, title, submittalNumber, specSectionIds: [...], submittalTypeId, status }) { id submittalNumber title }` — omit `submittalNumber` to auto-number in the project's sequence.
|
|
50
|
+
|
|
51
|
+
**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`).
|
|
52
|
+
|
|
53
|
+
Update with `updateSubmittal(input: { id, ... })` — same fields as create, minus `projectId`/`cloudFileId`. Pass only what changes.
|
|
54
|
+
|
|
55
|
+
## Revisions
|
|
56
|
+
|
|
57
|
+
`createSubmittalRevision` supersedes the current revision with a fresh one reset to Draft:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
specset api --query 'mutation($id: ID!) {
|
|
61
|
+
createSubmittalRevision(input: { id: $id, cloudFileIds: ["<file-id>"] }) {
|
|
62
|
+
id revision status
|
|
63
|
+
}
|
|
64
|
+
}' -F id=<submittal-id>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
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.
|
|
68
|
+
|
|
69
|
+
## Attachments
|
|
70
|
+
|
|
71
|
+
File ids come from Uploading Files in the `specset` skill.
|
|
72
|
+
|
|
73
|
+
- `createSubmittalAttachment(input: { submittalId, cloudFileId, type, description, order })`
|
|
74
|
+
- `createSubmittalAttachments(input: { submittalId, cloudFileIds: [...], autoReviewMode })` — batch form
|
|
75
|
+
- `updateSubmittalAttachment(input: { id, type, description, order })`
|
|
76
|
+
- `deleteSubmittalAttachment(id)`
|
|
77
|
+
|
|
78
|
+
`type` is a SubmittalAttachmentType enum (`ProductData`, `ShopDrawing`, `Certifications`, …) — introspect for the full list and inline the value.
|
|
79
|
+
|
|
80
|
+
## Approvers
|
|
81
|
+
|
|
82
|
+
Track who reviews the submittal and what they returned:
|
|
83
|
+
|
|
84
|
+
- `createSubmittalApprover(input: { submittalId, userId, dueDate, responseRequired }) { id order status }`
|
|
85
|
+
|
|
86
|
+
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)`.
|
|
87
|
+
|
|
88
|
+
## AI Compliance Reviews
|
|
89
|
+
|
|
90
|
+
Three steps — arguments are top-level (no input object):
|
|
91
|
+
|
|
92
|
+
1. `createSubmittalReview(submittalId, createMode: GeneratePlanAndApprove) { id }` — optional: `prompt` (focus instructions, a plain string), and `specSectionIds` / `drawingSheetIds` (inline lists) to pin the spec basis.
|
|
93
|
+
2. Poll `submittalReview(id) { id status oneLiner lastReviewedAt }` until `lastReviewedAt` is set — reviews take minutes; see Waiting on Background Processing in the `specset` skill for cadence. `status` is the verdict, ReviewStatus: `Compliant | NonCompliant | MissingInfo | ClarificationsNeeded | NotApplicable`.
|
|
94
|
+
3. `compileSubmittalReview(id, includeIssueToc: true) { id compiledReview { id } }` compiles the annotated, marked-up PDF. `excludedIssueIds: [...]` (inlined) drops chosen issues from the compiled PDF and TOC.
|
|
95
|
+
|
|
96
|
+
`updateSubmittalReview(id, comments)` records reviewer comments; `deleteSubmittalReview(id)` removes a review.
|
|
97
|
+
|
|
98
|
+
## Drafting a Whole Submittal Log
|
|
99
|
+
|
|
100
|
+
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:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
specset api --query 'mutation($artifactId: ID!) {
|
|
104
|
+
applySubmittalLog(input: { artifactId: $artifactId, selectedSubmittalLogIds: [1, 2, 3] }) {
|
|
105
|
+
id content
|
|
106
|
+
}
|
|
107
|
+
}' -F artifactId=<artifact-id>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
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.
|
|
111
|
+
|
|
112
|
+
## Safety
|
|
113
|
+
|
|
114
|
+
`deleteSubmittal(id)` removes one revision; `deleteSubmittal(id, deleteAllRevisions: true)` removes the entire revision chain — state that difference when confirming the delete.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: specset-tools
|
|
3
|
+
description: Use when an agent should call Specset's own retrieval tools directly — semantic/keyword search and structured reads over specs, drawings, submittals, RFIs, documents, closeout, and schedule — via `specset tools` or a `specset mcp` server. Deeper than raw GraphQL search; lighter than delegating to the in-app agent.
|
|
4
|
+
allowed-tools: Bash Read AskUserQuestion
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Specset Agent Tools (`specset tools`, `specset mcp`)
|
|
8
|
+
|
|
9
|
+
These are the same read-only retrieval tools Specset's own in-app agent uses — exposed so external agents can call them directly. Everything here is read-only; nothing needs user confirmation.
|
|
10
|
+
|
|
11
|
+
## When to use which surface
|
|
12
|
+
|
|
13
|
+
There are three tiers of Specset retrieval. Pick the lightest one that answers the question:
|
|
14
|
+
|
|
15
|
+
1. **Typed GraphQL search** (`specset-search` skill: `search`, `quickSearch`, `specSectionByNumber`, …) — cheapest, returns previews and IDs. Best for "does X exist / find the record."
|
|
16
|
+
2. **Agent tools** (this skill) — the middle tier. `searchSpecSections` / `searchDrawingSheets` / `searchDocuments` return expanded, citation-anchored markdown (the real retrieval), and the `get*Content` tools read full sections. Best when you need the actual content to reason over.
|
|
17
|
+
3. **In-app agent** (`specset-agent` skill: `specset agent chat`) — heaviest. Delegates the whole question to Specset's agent, which reasons and synthesizes. Best when the answer needs cross-document judgment, not just retrieval.
|
|
18
|
+
|
|
19
|
+
## Listing tools
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
specset tools list
|
|
23
|
+
specset tools list --project <projectId|https://app.specbook.ai/go/project/...>
|
|
24
|
+
specset tools list --json # full input schemas
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The listing includes usage guidance and marks project-scoped tools. The exact set depends on the org (closeout and schedule tools require their SKUs) and on whether a project is in scope.
|
|
28
|
+
|
|
29
|
+
## Running a tool
|
|
30
|
+
|
|
31
|
+
Provide input as a JSON object (`--input`) or as repeated `-F key=value` fields. **Unlike `specset api -F` (strings only), `specset tools run -F` JSON-parses each value**, so numbers, booleans, and lists work:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# JSON input
|
|
35
|
+
specset tools run searchSpecSections \
|
|
36
|
+
--project <projectId> \
|
|
37
|
+
--input '{"query": "concrete curing requirements"}'
|
|
38
|
+
|
|
39
|
+
# Field form (values are JSON-parsed, string fallback)
|
|
40
|
+
specset tools run searchSpecSections --project <projectId> \
|
|
41
|
+
-F query="fire rating" -F keywords='["ASTM E119"]' -F limit=5
|
|
42
|
+
|
|
43
|
+
# Read a specific section's full content using a source link from a search result
|
|
44
|
+
specset tools run getSpecSectionContent \
|
|
45
|
+
-F id='https://app.specbook.ai/go/spec/<uuid>'
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Output is the tool's JSON payload (`{ success, message?, result? }`); a `{ success: false }` payload exits non-zero.
|
|
49
|
+
|
|
50
|
+
### Source links
|
|
51
|
+
|
|
52
|
+
Tool results expose clickable links shaped like `https://app.specbook.ai/go/spec/<uuid>` or `https://app.specbook.ai/go/drawing/<uuid>`. Pass them back into other tools verbatim (for example, into `getSpecSectionContent` or `getDrawingSheet`). Include the most relevant returned links as descriptive Markdown citations in user-facing answers. Never invent a link or construct one from an unrelated UUID. An identifier that is not an `https://` link (a thread-local short id or `doc:`-prefixed identifier) is internal — pass it back into tools verbatim, but never present it to the user as a clickable link.
|
|
53
|
+
|
|
54
|
+
### Project scoping
|
|
55
|
+
|
|
56
|
+
Most tools are project-scoped. Either pass `--project` (a UUID or returned `/go/project/...` link), or include `projectId` in the tool input. Use `listProjects` to discover projects. With `--project` set, per-call `projectId` becomes optional.
|
|
57
|
+
|
|
58
|
+
## Downloading source files (drawings, documents, specs, submittals, RFIs)
|
|
59
|
+
|
|
60
|
+
The retrieval tools return **extracted text**, which is authoritative for schedules, notes, and spec paragraphs — but cannot answer spatial/visual questions (routing, clearances, what a plan view actually shows) and never carries the original file. Every entity has a download command, and **each accepts its returned Specset `/go` link verbatim** (citation focus parameters are ignored for downloads) or a bare UUID:
|
|
61
|
+
|
|
62
|
+
| Entity | Command | Also accepts | Formats / options |
|
|
63
|
+
| -------------- | ----------------------------------------------------------------------- | ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
64
|
+
| Drawing sheets | `specset drawings download 'https://app.specbook.ai/go/drawing/<uuid>'` | sheet number + `--project` | `--format image` (rendered PNG, default), `pdf` (single sheet), `file` (whole set) |
|
|
65
|
+
| Documents | `specset docs download 'https://app.specbook.ai/go/doc/<uuid>'` | | also covers RFI attachments and closeout documents |
|
|
66
|
+
| Spec sections | `specset specs download 'https://app.specbook.ai/go/spec/<uuid>'` | section number (e.g. `'23 31 19'`) + `--project` | per-section PDF slice (default), `--asset book` (whole source spec file) |
|
|
67
|
+
| Submittals | `specset submittals download 'https://app.specbook.ai/go/sub/<uuid>'` | | compiled package (default when present); `--list` shows the asset inventory; `--asset attachment --index 2`, `--asset attachment --all` |
|
|
68
|
+
| RFIs | `specset rfis download 'https://app.specbook.ai/go/rfi/<uuid>'` | | question + response attachments; `--list`, `--all` |
|
|
69
|
+
| File resources | `specset files download 'https://app.specbook.ai/go/file/<uuid>'` | | escape hatch for a returned file source link |
|
|
70
|
+
|
|
71
|
+
All commands support `--json` (structured `{files: [{path, bytes, ...}]}` output) and `--out` (file path, or directory for multi-file downloads). A citation passed to the wrong command errors with the right one to use.
|
|
72
|
+
|
|
73
|
+
Typical loop for drawings: search or `getDrawingSheetContent` to find the sheet → hit a visual question the text can't settle → `drawings download` → read the PNG to inspect the geometry.
|
|
74
|
+
|
|
75
|
+
## MCP servers (local and hosted)
|
|
76
|
+
|
|
77
|
+
The same server-side tool registry is available through two transports:
|
|
78
|
+
|
|
79
|
+
- `specset mcp` is the local stdio transport for Claude Code, Codex, and other clients with shell access. It reuses the CLI login and active organization.
|
|
80
|
+
- `https://<specset-host>/mcp` is the stateless Streamable HTTP transport for hosted MCP clients. Its OAuth 2.1 consent flow selects an organization and binds it into the grant, so hosted clients must not put `orgId` in the connection URL. Add `projectId=<project-id>` to pin project scope.
|
|
81
|
+
- First-party clients may instead supply a Specset session bearer token and select the organization with `x-org-id` (or the legacy `orgId` query parameter).
|
|
82
|
+
|
|
83
|
+
Register the local transport once and the client can call the tools directly:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# Claude Code
|
|
87
|
+
claude mcp add specset -- specset mcp
|
|
88
|
+
# Optionally pin a project and/or host:
|
|
89
|
+
claude mcp add specset -- specset mcp --project <projectId> --host https://app.specbook.ai
|
|
90
|
+
|
|
91
|
+
# Codex
|
|
92
|
+
codex mcp add specset -- specset mcp
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Notes:
|
|
96
|
+
|
|
97
|
+
- The server writes only the MCP protocol to stdout; diagnostics go to stderr.
|
|
98
|
+
- Scope (org, optional project, host) is fixed for the life of the server — restart it to change scope.
|
|
99
|
+
- If the session expires mid-run, tool calls return an actionable error; run `specset login` and restart the server.
|
|
100
|
+
- The hosted transport additionally exposes `uploadFile` (OpenAI host file parameters, streamed into Specset storage) and `getFileDownload` (an MCP resource link backed by a short-lived signed URL). The underlying retrieval tools are still the same allowlisted registry used by stdio.
|
|
101
|
+
- The hosted transport advertises standards-compliant MCP OAuth 2.1 protected-resource and authorization-server metadata. It supports Client ID Metadata Documents, PKCE, resource/audience binding, scoped tools, short-lived access tokens, and rotating refresh tokens.
|
|
102
|
+
|
|
103
|
+
## Stability
|
|
104
|
+
|
|
105
|
+
This surface is experimental — tool names and input schemas may change. For automation you need to keep stable, prefer the typed `search` / `quickSearch` GraphQL queries in the `specset-search` skill.
|