@specset/cli 0.6.0 → 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.
@@ -1,94 +1,188 @@
1
1
  ---
2
2
  name: specset-closeout
3
- description: Use when managing Specset closeout and facilities data — assets, locations, products, companies, warranties, and maintenance tasks, plus their documents and links — for closeout package or O&M/facilities tasks.
3
+ description: Use when managing Specset closeout and facilities data — assets, locations, products, companies, warranties, procedures, maintenance plans, work orders, and their documents and links.
4
4
  allowed-tools: Bash Read AskUserQuestion
5
5
  ---
6
6
 
7
7
  # Closeout & Facilities Data
8
8
 
9
- Run every operation with `specset api`. All ids are plain UUIDs. Inline enums, numbers, lists, and input objects in the operation text (`-F` is strings-only; Variable Limitations and Schema Discovery live in the `specset` skill).
9
+ Run every operation with `specset api`. All ids are plain UUIDs. Inline enums, numbers, lists, and input objects in the operation text (`-F` sends strings only; Variable Limitations and Schema Discovery live in the `specset` skill).
10
10
 
11
11
  ## Entity Model
12
12
 
13
- **Locations** are the spatial scaffold (site → building → level → room), nesting via `parentId`. **Systems** are disciplines and equipment groups (HVAC, electrical, fire protection), also nested. **Products** are the spec'd make/models. **Assets** are installed instances: each asset sits at one location, is optionally an instance of one product with one responsible **company**, and can join multiple systems through role-tagged memberships. **Warranties** and **maintenance tasks** hang off assets, and maintenance tasks accumulate **completions** (service-log entries). Everything is scoped to a project.
13
+ **Locations** are the spatial scaffold (site → building → level → room), nesting through `parentId`. **Systems** are disciplines and equipment groups, also nested. **Products** are specified make/models. **Assets** are installed instances: each asset has a location, may reference a product and responsible company, and can join multiple systems. **Warranties** attach to assets.
14
14
 
15
- ## The Per-Entity Pattern
15
+ Maintenance has three layers:
16
16
 
17
- Asset, Location, Product, Company, System, Warranty, and MaintenanceTask share one surface:
17
+ - **Procedure** — reusable checklist content: ordered typed fields, descriptions, and source references.
18
+ - **MaintenancePlan** — a schedule on exactly one Product, Asset, Location, or System. Product plans fan out to installed assets. Its `tiers` are independent schedule items that pair a Procedure with a calendar cadence, meter trigger, or both.
19
+ - **WorkOrder** — a concrete occurrence or ad-hoc job. Generated work orders snapshot their checklist fields, so history remains stable when a Procedure changes.
18
20
 
19
- | Operation | Shape |
20
- |---|---|
21
- | `<entity>(id: ID!)` | Single record (`null` if missing or in another org) |
22
- | `paginated<Entities>(projectId: ID!, limit: Int, offset: Int, ...)` | Returns `{ items { ... } totalFilteredCount }` |
23
- | `search<Entities>(projectId: ID!, search: String)` | Fuzzy text lookup |
24
- | `create<Entity>(input: ...)` / `update<Entity>(input: ...)` | Update inputs take `id` plus only the fields to change |
25
- | `delete<Entity>(id: ID!)` | Hard delete — see Safety |
21
+ `MaintenanceTask` and its completion API no longer exist. Use Procedure, MaintenancePlan, and WorkOrder operations below.
26
22
 
27
- Real deviations:
23
+ ## Discover Before Mutating
28
24
 
29
- - **Asset** — create requires `projectId` + `locationId`. `paginatedAssets` also takes `search` and a `filters` input (`statuses`, `locationIds`, `productIds`, `systemIds`, `responsibleCompanyIds`, `parentIds`, `archived`) for cross-entity tracing.
30
- - **Warranty / MaintenanceTask** — asset-scoped: create inputs take `assetId` (not `projectId`); no `search<Entities>` query, and `search` on their paginated lists is ignored — scope by asset with `warrantiesByAsset(assetId)` / `maintenanceTasksByAsset(assetId)` instead.
31
- - Batch deletes exist for some entities (`deleteLocations(ids)`, `deleteCompanies(ids)`, `deleteSystems(ids)`, `deleteWarranties(ids)`, `deleteMaintenanceTasks(ids)`) — same confirmation rules as single deletes.
25
+ Input objects evolve, especially checklist fields and plan tiers. Introspect the relevant input before composing a mutation:
32
26
 
33
- Introspect the input object before composing any create or update, e.g. `{ __type(name: "CreateAssetInput") { inputFields { name type { name kind ofType { name } } } } }`.
27
+ ```bash
28
+ specset api --query '{
29
+ createPlan: __type(name: "CreateMaintenancePlanInput") {
30
+ inputFields { name type { kind name ofType { kind name } } }
31
+ }
32
+ tier: __type(name: "MaintenancePlanTierInput") {
33
+ inputFields { name type { kind name ofType { kind name } } }
34
+ }
35
+ complete: __type(name: "CompleteWorkOrderInput") {
36
+ inputFields { name type { kind name ofType { kind name } } }
37
+ }
38
+ }'
39
+ ```
40
+
41
+ Inline input objects and enums in the operation text. Use `-F` for UUIDs, dates, and other scalar strings.
42
+
43
+ ## Core Closeout Records
44
+
45
+ Asset, Location, Product, Company, System, and Warranty retain the standard surface:
34
46
 
35
- Prefer `update<Entity>` for changes: pass `id` plus only what changes; pass `null` to clear an optional relationship (e.g. `productId: null`). Update inputs for Asset, Location, Product, Company, and System accept `archived: true` — a reversible soft-archive that drops the record from default lists. Prefer it over delete.
47
+ | Operation | Shape |
48
+ | ------------------------------------------------------------------- | ------------------------------------------- |
49
+ | `<entity>(id: ID!)` | Single record; nullable when missing |
50
+ | `paginated<Entities>(projectId: ID!, limit: Int, offset: Int, ...)` | `{ items { ... } totalFilteredCount }` |
51
+ | `create<Entity>(input: ...)` / `update<Entity>(input: ...)` | Update inputs take `id` plus changed fields |
52
+ | `delete<Entity>(id: ID!)` | Hard delete; follow Safety below |
36
53
 
37
- ## Asset Links
54
+ Notable differences:
38
55
 
39
- Three `set*` mutations each **replace** the full id set for the given `(assetId, role)` tuple — anything previously linked at that role and not in the new list is removed; pass `[]` to clear. Inline the enum and the id list:
56
+ - Asset creation requires `projectId` and `locationId`. `paginatedAssets` accepts `search` and filters such as `statuses`, `locationIds`, `productIds`, `systemIds`, `responsibleCompanyIds`, `parentIds`, and `archived`.
57
+ - Warranty is asset-scoped; use `warrantiesByAsset(assetId)` to browse one asset's warranties.
58
+ - Location, Product, Company, System, and Asset updates support reversible `archived` state. Prefer archiving to deletion.
59
+ - Batch-delete availability varies. Introspect the mutation root rather than guessing an operation name.
40
60
 
41
- - `setAssetSystems(assetId: ID!, role: ..., systemIds: [ID!]!)` — roles: `Primary`, `Secondary`, `Control`, `MonitoredBy`, `Other`. Read back with `assetSystemMemberships(assetId)`.
42
- - `setAssetSubmittals(assetId: ID!, relationship: ..., submittalIds: [ID!]!)` — relationships: `Source`, `Revision`, `Substitution`, `Addendum`. Read back with `assetSubmittalLinks(assetId)`.
43
- - `setAssetDrawingSheets(assetId: ID!, role: ..., drawingSheetIds: [ID!]!)` — roles include `Plan`, `Schedule`, `Diagram`, `Detail`, `AsBuilt`, `Other`. Read back with `assetDrawingSheetLinks(assetId)`.
61
+ Asset link setters replace the complete set for the given role/relationship; omitted ids are removed and `[]` clears the set:
62
+
63
+ - `setAssetSystems(assetId, role, systemIds)` / `assetSystemMemberships(assetId)`
64
+ - `setAssetSubmittals(assetId, relationship, submittalIds)` / `assetSubmittalLinks(assetId)`
65
+ - `setAssetDrawingSheets(assetId, role, drawingSheetIds)` / `assetDrawingSheetLinks(assetId)`
66
+
67
+ ## Procedures
68
+
69
+ Use `procedure(id)` and `paginatedProcedures(projectId, filters, limit, offset)`. Procedure list filters include `search` and `archived`.
70
+
71
+ Create a reusable checklist with its fields atomically:
44
72
 
45
73
  ```bash
46
- specset api --query 'mutation($assetId: ID!, $systemId: ID!) {
47
- setAssetSystems(assetId: $assetId, role: Primary, systemIds: [$systemId]) {
48
- id role system { id name }
74
+ specset api --query 'mutation($projectId: ID!) {
75
+ createProcedure(input: {
76
+ projectId: $projectId
77
+ name: "Quarterly AHU service"
78
+ fields: [
79
+ { name: "Inspect belts", fieldType: Checkbox }
80
+ { name: "Record supply temperature", fieldType: Number, unit: "°F" }
81
+ ]
82
+ }) {
83
+ id name updatedAt
84
+ fields { id name fieldType position required }
49
85
  }
50
- }' -F assetId=<asset-id> -F systemId=<system-id>
86
+ }' -F projectId=<project-id>
51
87
  ```
52
88
 
53
- ## Entity Documents
89
+ The fields' array order sets their `position`; neither `CreateProcedureFieldInput` nor `UpdateProcedureFieldInput` accepts a `position` property. `updateProcedure(input: { id, ... })` is partial for scalar fields. Supplying `fields` performs a full checklist replacement; first read the current fields and include the `id` of every row that should retain its identity. `deleteProcedure(id, force)` is guarded when active plans use it. Do not set `force: true` without naming the affected plans and confirming the destructive change.
54
90
 
55
- Each entity links to project documents through a role-tagged pivot. Find document ids with `documents(projectId: ID!) { id title }`. The pattern (uniform across Location, Product, Company, System, Warranty, MaintenanceTask; substitute the entity name):
91
+ ## Maintenance Plans and Tiers
56
92
 
57
- - `<entity>Documents(<entity>Id: ID!)` — list the pivots. Select `{ id roles document { id title } }`.
58
- - `set<Entity>DocumentRoles(<entity>Id: ID!, documentId: ID!, roles: [...]!)` — upsert the linkage; an empty `roles` list removes it (and the document itself if orphaned). Roles are per-entity enums — e.g. `ProductDocumentRole` is `Datasheet`, `InstallationManual`, `OperationManual`, `MaintenanceManual`, `Warranty`, `SubmittalPackage`, `Other`; introspect each entity's enum before use.
59
- - `delete<Entity>Document(id: ID!)` — takes the **pivot** id from the list query, not the document id.
93
+ Read with `maintenancePlan(id)`, `paginatedMaintenancePlans(projectId, filters, limit, offset)`, or `maintenancePlansForAsset(assetId, includeProductPlans)`.
60
94
 
61
- Asset is the exception: it has `assetDocuments(assetId)` and `deleteAssetDocument(id)` but no set-roles mutation.
95
+ Create requires exactly one subject id (`productId`, `assetId`, `locationId`, or `systemId`) and a `frequencyKind`. Prefer `tiers`; the top-level procedure and interval fields remain a single-item compatibility surface. Supply either:
62
96
 
63
- ## Documentation Blocks
97
+ - single-tier compatibility fields: `procedureId` (or inline `procedure`) plus `intervalValue` / `intervalUnit`; or
98
+ - `tiers: [MaintenancePlanTierInput!]` for independent schedule items. Each item selects one `procedureId` or creates one inline.
99
+
100
+ Do not mix `tiers` with the single-tier procedure/cadence fields. Calendar item units are `Days`, `Weeks`, `Months`, or `Years`. Optional meter conditions live on each item as the pair `meterUnit` (`Hours` or `Cycles`) and `meterThreshold`; provide both or neither. Calendar cadences are independent and do not need to be multiples of one another. Set `skipLowerTiersOnSharedDate: true` to suppress a shorter calendar item when it naturally shares a date with a longer one; each work order still contains only its own Procedure. A suppressed item's meter condition stays armed and may create that item's work order later. `gateOnOpen` prevents repeated meter-generated work while an occurrence remains open.
101
+
102
+ ```bash
103
+ specset api --query 'mutation($assetId: ID!, $monthlyId: ID!, $quarterlyId: ID!) {
104
+ createMaintenancePlan(input: {
105
+ assetId: $assetId
106
+ name: "AHU PM program"
107
+ frequencyKind: Calendar
108
+ skipLowerTiersOnSharedDate: true
109
+ gateOnOpen: true
110
+ tiers: [
111
+ { procedureId: $monthlyId, intervalValue: 1, intervalUnit: Months }
112
+ {
113
+ procedureId: $quarterlyId
114
+ intervalValue: 3
115
+ intervalUnit: Months
116
+ meterUnit: Hours
117
+ meterThreshold: 500
118
+ }
119
+ ]
120
+ }) {
121
+ id name frequencyKind skipLowerTiersOnSharedDate gateOnOpen
122
+ tiers {
123
+ id intervalValue intervalUnit meterUnit meterThreshold
124
+ procedure { id name }
125
+ }
126
+ openWorkOrderCount
127
+ }
128
+ }' -F assetId=<asset-id> -F monthlyId=<procedure-id> -F quarterlyId=<procedure-id>
129
+ ```
130
+
131
+ For a meter-only plan, prefer the typed input: `schedule: { meter: { unit: Hours|Cycles, threshold: <n>, gateOnOpen } }` with a single tier. The legacy encoding (`frequencyKind: RuntimeHours` or `Cycles`) additionally requires plan-level `intervalValue` set to the threshold and `intervalUnit: Hours`/`Cycles` — a threshold on the item alone is rejected. For an on-demand plan, use `AsNeeded`, `EventTriggered`, or `Other` and omit calendar and meter fields.
132
+
133
+ `updateMaintenancePlan(input: { id, ... })` can retarget the subject: pass exactly one of `productId`/`assetId`/`locationId`/`systemId` (same project). Open work orders for the old subject are retired — untouched ones removed, in-progress ones canceled — and fresh chains generate for the new subject. Supplying `tiers` fully replaces the schedule-item set: include the `id` of every existing item to update/retain, omit an item only when it should be removed, and omit `id` only for new items. Read the plan and its item ids immediately before updating.
134
+
135
+ Use `createWorkOrderFromPlan(planId, targetAssetId, dueDate, coveredTierIds)` for an early/manual occurrence of a plan. Product plans require the concrete `targetAssetId`. `coveredTierIds` is optional; when supplied it states exactly which tiers the occurrence performs.
136
+
137
+ ## Work Orders
138
+
139
+ Use `workOrder(id)`, `paginatedWorkOrders(projectId, filters, limit, offset)`, and `paginatedGroupedWorkOrders(projectId, groupBy, groupKeys, filters, limit, offset)`. Grouped reads require both the grouping dimensions and the current group-key path. Useful fields include `status`, `dueDate`, `performedDate`, `priority`, `assignee`, `plan`, `planTier`, `coveredTiers`, `fields`, and `documents`.
140
+
141
+ `createWorkOrder(input)` creates ad-hoc work and requires exactly one of `assetId`, `locationId`, or `systemId`, plus `name`. Optional `fields` create a reusable inline Procedure; optional `recurrence` also creates a MaintenancePlan. Use `updateWorkOrder(input: { id, ... })` for editable metadata.
142
+
143
+ Checklist responses use `setWorkOrderFieldResponse(input)` or `setWorkOrderFieldResponses(inputs)`. Introspect `SetWorkOrderFieldResponseInput`: answer columns vary by field type (`valueNumber`, `valueText`, `valueChoice`, `status`, and `note`).
144
+
145
+ Complete work through the batch mutation even for one row:
146
+
147
+ ```bash
148
+ specset api --query 'mutation($id: ID!, $performedDate: DateTime!) {
149
+ completeWorkOrders(inputs: [{ id: $id, performedDate: $performedDate }]) {
150
+ workOrder { id status performedDate }
151
+ nextWorkOrder { id status dueDate }
152
+ }
153
+ }' -F id=<work-order-id> -F performedDate=2026-08-04
154
+ ```
64
155
 
65
- Asset, Location, Product, Company, and System each carry a markdown documentation block used by closeout exports:
156
+ Other lifecycle operations:
66
157
 
67
- - `update<Entity>Documentation(id: ID!, markdown: String!)` — replace the block. Pass `markdown` via `-F` (it's a plain string).
68
- - `mark<Entity>DocumentationComplete(id: ID!)` — attest the block is current (clears it from stale-documentation rollups).
158
+ - `skipWorkOrder(id, notes, mode)` — closes the occurrence as skipped; omit `mode` for normal independent-item plans. Non-default modes exist only for historical merged work orders.
159
+ - `reopenWorkOrder(id)` — reopens completed/skipped work when allowed.
160
+ - `makeWorkOrderRecurring(workOrderId, recurrence)` — converts eligible ad-hoc work into a recurring plan.
161
+ - `deleteWorkOrder(id)` — hard delete.
162
+ - `bulkAssignWorkOrders(selection, assigneeId)` and `bulkDeleteWorkOrders(selection)` operate on an explicit selection object.
163
+ - `addWorkOrderDocument(workOrderId, documentId, workOrderFieldId)` and `removeWorkOrderDocument(id)` manage evidence. Removal takes the WorkOrderDocument pivot id.
69
164
 
70
- ## Maintenance Completions
165
+ ## Entity Documents and Documentation
71
166
 
72
- Service history lives on completions under each task:
167
+ Location, Product, Company, System, and Warranty link project Documents through entity-specific role-tagged pivots. Read `<entity>Documents(<entity>Id)`, then call `set<Entity>DocumentRoles(<entity>Id, documentId, roles)` after introspecting that entity's role enum. An empty role list removes the pivot and may delete the underlying Document when it becomes orphaned. Asset is the exception: it exposes `assetDocuments(assetId)` and `deleteAssetDocument(id)`, but no set-roles mutation.
73
168
 
74
- - `maintenanceTaskCompletions(maintenanceTaskId: ID!)` — select `{ id performedDate dueDate performedByName result }`.
75
- - `createMaintenanceTaskCompletion(input: { maintenanceTaskId, performedDate, ... })` — `performedDate` is required; optional `dueDate`, `performedByName`, `performedByCompanyId`, `result`. Introspect `CreateMaintenanceTaskCompletionInput` for the full set.
76
- - `updateMaintenanceTaskCompletion(input: { id, ... })` / `deleteMaintenanceTaskCompletion(id: ID!)`.
77
- - Completion documents: `addMaintenanceTaskCompletionDocument(completionId: ID!, documentId: ID!)` and `deleteMaintenanceTaskCompletionDocument(id: ID!)` (pivot id).
169
+ Asset, Location, Product, Company, and System also have markdown documentation blocks. `update<Entity>Documentation(id, markdown)` replaces the block; `mark<Entity>DocumentationComplete(id)` attests that it is current.
78
170
 
79
- ## Dedup / Re-Pointing Recipe
171
+ ## Dedup / Re-pointing
80
172
 
81
173
  For duplicate products P1 (loser) and P2 (survivor):
82
174
 
83
- 1. `paginatedAssets(projectId: ..., filters: { productIds: ["<p1-id>"] })` — collect affected assets.
84
- 2. `updateAsset(input: { id: ..., productId: "<p2-id>" })` per asset.
85
- 3. Re-run step 1 and confirm zero results.
86
- 4. `updateProduct(input: { id: "<p1-id>", archived: true })` — archive the loser; delete only if it is clearly an unreferenced mistake.
175
+ 1. Query `paginatedAssets(projectId, filters: { productIds: [P1] })` and collect impacted assets.
176
+ 2. `updateAsset(input: { id, productId: P2 })` for each asset.
177
+ 3. Re-run the filtered query and confirm zero remaining references.
178
+ 4. Archive P1 with `updateProduct(input: { id: P1, archived: true })`.
87
179
 
88
- The same shape works for duplicate companies, locations, and systems.
180
+ Use the same impact-first pattern for duplicate companies, locations, and systems.
89
181
 
90
182
  ## Safety
91
183
 
92
- - Deletes here are hard deletes with no undo — name the exact record (tag/name + id) when confirming.
93
- - Prefer `update` over delete-and-recreate, and `archived: true` over delete: deleting a product, location, or company silently nulls the references on assets that pointed at it. Check impact first with the filtered list queries.
94
- - Removing a document linkage can delete the underlying document if nothing else references it — say so when confirming.
184
+ - Deletes are hard and have no undo. Before executing, name the exact record and id and confirm the impact.
185
+ - Prefer partial updates, and prefer `archived: true` to delete where supported.
186
+ - Read Procedure fields and MaintenancePlan tiers before any full-replacement update.
187
+ - Deleting plans or force-deleting Procedures can alter future generated work. Check active plan/open-work-order counts first.
188
+ - Removing a document linkage can delete an orphaned underlying Document; disclose that before confirming.
@@ -37,7 +37,22 @@ search(
37
37
  At least one of `query` or `keywords` is required; they can be combined. Results:
38
38
 
39
39
  ```graphql
40
- { groups { referenceType totalCount items { url title previewText score projectId projectName } } nextOffset }
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
+ }
41
56
  ```
42
57
 
43
58
  ### Project Scope (`ProjectScope`)
@@ -48,7 +63,7 @@ At least one of `query` or `keywords` is required; they can be combined. Results
48
63
 
49
64
  ### Record Types (`SourceReferenceType`)
50
65
 
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.
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.
52
67
 
53
68
  ## Quick Search: `quickSearch`
54
69
 
@@ -67,6 +82,8 @@ quickSearch(
67
82
 
68
83
  Results: `{ items { id url number title referenceType score projectId projectName summary } totalCount }`
69
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
+
70
87
  ## Precise Lookups
71
88
 
72
89
  When the user hands you a section or sheet number, look it up directly:
@@ -90,15 +107,16 @@ Useful item fields: `id sectionNumber title numberAndTitle` on SpecSection, `id
90
107
  ```bash
91
108
  specset api --query 'query($orgId: ID!) {
92
109
  search(orgId: $orgId, keywords: ["AHU-1"],
93
- referenceTypes: [Submittal, SubmittalAttachment],
110
+ referenceTypes: [Submittal],
94
111
  projectScope: MyProjects, limit: 20) {
95
112
  groups { referenceType totalCount
96
- items { url title previewText projectName } }
113
+ items { id url title previewText projectName } }
97
114
  }
98
115
  }' -F orgId=<org-id>
99
116
  ```
100
117
 
101
118
  ## Working With Results
102
119
 
103
- - Each result's `url` is a Specset reference URL (`sb://spec/<id>`, `sb://sub/<id>`, …); the UUID inside is the record's GraphQL `id` — extract it for follow-up queries. The scheme is documented in the `specset-tools` skill.
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.
104
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.
@@ -20,7 +20,7 @@ There are three tiers of Specset retrieval. Pick the lightest one that answers t
20
20
 
21
21
  ```bash
22
22
  specset tools list
23
- specset tools list --project <projectId|sb://project/...>
23
+ specset tools list --project <projectId|https://app.specbook.ai/go/project/...>
24
24
  specset tools list --json # full input schemas
25
25
  ```
26
26
 
@@ -40,32 +40,33 @@ specset tools run searchSpecSections \
40
40
  specset tools run searchSpecSections --project <projectId> \
41
41
  -F query="fire rating" -F keywords='["ASTM E119"]' -F limit=5
42
42
 
43
- # Read a specific section's full content using an ID from a search result
44
- specset tools run getSpecSectionContent -F id='sb://spec/....'
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>'
45
46
  ```
46
47
 
47
48
  Output is the tool's JSON payload (`{ success, message?, result? }`); a `{ success: false }` payload exits non-zero.
48
49
 
49
- ### Identifiers are citation IDs, not links
50
+ ### Source links
50
51
 
51
- Tool results embed identifiers shaped like `sb://spec/<uuid>` or `sb://drawing/<uuid>`. Pass them back into other tools verbatim (e.g. into `getSpecSectionContent`, `getDrawingSheet`). They are citation identifiers, not web URLs.
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.
52
53
 
53
54
  ### Project scoping
54
55
 
55
- Most tools are project-scoped. Either pass `--project` (a UUID or `sb://project/...`), or include `projectId` in the tool input. Use `listProjects` to discover project IDs. With `--project` set, per-call `projectId` becomes optional.
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.
56
57
 
57
58
  ## Downloading source files (drawings, documents, specs, submittals, RFIs)
58
59
 
59
- 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 `sb://` citation URL verbatim** (as returned by issues, search, and tool results — any `#page=`/`#nodeId=` fragment is ignored) or a bare UUID:
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:
60
61
 
61
- | Entity | Command | Also accepts | Formats / options |
62
- | -------------- | ------------------------------------------------- | ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
63
- | Drawing sheets | `specset drawings download 'sb://drawing/<uuid>'` | sheet number + `--project` | `--format image` (rendered PNG, default), `pdf` (single sheet), `file` (whole set) |
64
- | Documents | `specset docs download 'sb://doc/<uuid>'` | | also covers RFI attachments and closeout documents |
65
- | Spec sections | `specset specs download 'sb://spec/<uuid>'` | section number (e.g. `'23 31 19'`) + `--project` | per-section PDF slice (default), `--asset book` (whole source spec file) |
66
- | Submittals | `specset submittals download 'sb://sub/<uuid>'` | | compiled package (default when present); `--list` shows the asset inventory; `--asset attachment --index 2`, `--asset attachment --all` |
67
- | RFIs | `specset rfis download 'sb://rfi/<uuid>'` | | question + response attachments; `--list`, `--all` |
68
- | File resources | `specset files download 'sb://file/<uuid>'` | | escape hatch when you already hold an `sb://file` id |
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 |
69
70
 
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.
71
72