@specset/cli 2.0.1 → 2.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/README.md +21 -5
- package/dist/main.js +26499 -19158
- package/package.json +2 -2
- package/plugin/specset/.claude-plugin/plugin.json +1 -1
- package/plugin/specset/.codex-plugin/plugin.json +1 -1
- package/plugin/specset/skills/specset-admin/SKILL.md +15 -7
- package/plugin/specset/skills/specset-agent/SKILL.md +31 -20
- package/plugin/specset/skills/specset-closeout/SKILL.md +5 -2
- package/plugin/specset/skills/specset-issues/SKILL.md +1 -1
- package/plugin/specset/skills/specset-projects/SKILL.md +2 -0
- package/plugin/specset/skills/specset-submittals/SKILL.md +1 -1
- package/plugin/specset/skills/specset-tools/SKILL.md +3 -3
- package/skills/specset-admin/SKILL.md +15 -7
- package/skills/specset-agent/SKILL.md +31 -20
- package/skills/specset-closeout/SKILL.md +5 -2
- package/skills/specset-issues/SKILL.md +1 -1
- package/skills/specset-projects/SKILL.md +2 -0
- package/skills/specset-submittals/SKILL.md +1 -1
- package/skills/specset-tools/SKILL.md +3 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@specset/cli",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Command-line interface for Specset — authenticate, run GraphQL queries, and chat with Specset project agents.",
|
|
5
5
|
"license": "Elastic-2.0",
|
|
6
6
|
"homepage": "https://specset.com",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"@anthropic-ai/sdk": "0.93.0",
|
|
29
29
|
"@modelcontextprotocol/sdk": "^1.31.0",
|
|
30
30
|
"open": "^11.0.0",
|
|
31
|
-
"zod": "^4.
|
|
31
|
+
"zod": "^4.6.7"
|
|
32
32
|
},
|
|
33
33
|
"publishConfig": {
|
|
34
34
|
"access": "public"
|
|
@@ -12,15 +12,18 @@ Every operation here takes your org id — get it with `{ me { orgMembers { org
|
|
|
12
12
|
|
|
13
13
|
Roles (`OrgMemberTeamRole`): `Member`, `Manager`, `Admin`. Statuses (`OrgMemberStatus`): `PendingApproval`, `Active`, `Deactivated`. You need to be an org Admin for most mutations in this skill.
|
|
14
14
|
|
|
15
|
-
List members —
|
|
15
|
+
List active members — `--role` narrows to some built-in roles, `--privileged` to the privileged accounts, and `--json` adds each membership `id` the mutations below take:
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
specset
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
specset org members list --role Admin,Manager --json
|
|
19
|
+
specset org members list --privileged
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
An organization can define its own roles and change what Manager and Member allow (Members → Roles in the app). A member who holds one of the organization's own roles is listed under that role's name, and counts as `Member` for `--role`. `--privileged` lists everyone whose role is a privileged account, whatever it is called: Admins, Managers, and holders of any role that manages members, opens every project, or connects and configures integrations.
|
|
23
|
+
|
|
24
|
+
Search members through the API (`orgMembers(orgId:, statuses: [...])` lists members in other statuses):
|
|
23
25
|
|
|
26
|
+
```bash
|
|
24
27
|
specset api --query 'query($orgId: ID!, $search: String) {
|
|
25
28
|
paginatedOrgMembers(orgId: $orgId, search: $search, first: 20) {
|
|
26
29
|
edges { node { id role status user { email } } }
|
|
@@ -41,6 +44,10 @@ specset api --query 'mutation($id: ID!) {
|
|
|
41
44
|
}' -F id=<org-member-id>
|
|
42
45
|
```
|
|
43
46
|
|
|
47
|
+
## Security Settings
|
|
48
|
+
|
|
49
|
+
`specset org security show` lists the organization's seven security settings (single sign-on, authentication requirement, passwords, passkeys, authenticator codes, onboarding policy, session length) beside the values the Secure Configuration Guide (https://docs.specset.com/docs/secure-configuration) recommends for an organization that holds federal information or CUI, each marked `matches`, `stricter` or `differs`, with the identity providers. `--json` exports the same. Any member can read them; only an Admin changes them, in the web app under **Security & SSO** (the CLI cannot). A `differs` can be deliberate, such as single sign-on required with the provider's MFA confirmed by Specset support.
|
|
50
|
+
|
|
44
51
|
## Teams
|
|
45
52
|
|
|
46
53
|
A team is a named group of members. Assigning a team to a project puts everyone on it on that project, and changes to the team carry over to every project it's on. Teams don't carry a role: people keep their org role unless a project role is set on them.
|
|
@@ -56,8 +63,9 @@ Changing a team needs org Manager or Admin and management permission in every pr
|
|
|
56
63
|
Nobody joins an org on someone else's say-so. Both paths below leave the person with a pending invitation they must accept — in the app (existing Specset users see an invite badge), by registering through the invite link, or by signing in with the org's SSO. Until then an added user is an `Invited` member: visible in pickers and assignable, but with no access.
|
|
57
64
|
|
|
58
65
|
- `createOrgMember(orgId: ID!, email: String!, firstName: String!, lastName: String!, role: ..., projectIds: [ID!], teamIds: [ID!], sendInvite: Boolean)` — adds the person as an `Invited` seat right away so they can be assigned work before they've joined. `sendInvite: true` emails the invite link; existing Specset users get the in-app invitation either way. Returns the `OrgMember` (`status: Invited`).
|
|
66
|
+
- Directory contacts are people the org tracks and assigns (a subcontractor's PM, a received-from on a submittal) who don't sign in: `createOrgMember(..., memberType: DirectoryOnly)` adds one with no invite and no access. Every member and contact can carry `companyId` (a directory company; staff default to the org's own), `jobTitle`, `phone`, `mobilePhone`, and `notes`, on create and on `updateOrgMember`. `updateOrgMember(id, role: ..., memberType: Regular, sendInvite: true)` turns a contact into an invited member. `orgMembers(orgId, companyIds: [...])` lists a company's people.
|
|
59
67
|
- `createOrgInvite(orgId: ID!, email: String!, role: ..., projectIds: [ID!])` — invite-only (no seat until they accept; no `teamIds`, so add teams once they have a seat); also used to (re-)invite an existing `Invited` seat. `projectIds` (optional, inline the list) pre-scopes a Member to specific projects. Set `role` deliberately — don't default people to more access than they need. Share the returned `inviteUrl` if the email might not land.
|
|
60
|
-
- `orgInvites(orgId: ID!)` / `paginatedOrgInvites(orgId: ID!, first: Int, after: String)` — pending invites; select `{ id user { email } role inviteUrl }`.
|
|
68
|
+
- `orgInvites(orgId: ID!)` / `paginatedOrgInvites(orgId: ID!, first: Int, after: String)` — pending invites; select `{ id user { email } role inviteUrl }`. `inviteUrl` is null unless you may re-send that invitation (a Manager or Admin, up to your own role): whoever holds the link can register as the invitee.
|
|
61
69
|
- `updateOrgInvite(id: ID!, role: ..., projectIds: [ID!])` — `role` is required.
|
|
62
70
|
- `resendOrgInvite(id: ID!)` — rotates the link and re-sends the email.
|
|
63
71
|
- `deleteOrgInvite(id: ID!)` — revoke a pending invite (an `Invited` seat stays, as "Not invited"; `deleteOrgMember` removes the seat).
|
|
@@ -97,29 +97,39 @@ Inspect `getDrawingGraphOverview` bundles and quality flags before a benchmark;
|
|
|
97
97
|
building the graph does not reprocess stale source nodes. `--autoApprovePlan`
|
|
98
98
|
allows immediate execution when the brief is already approved.
|
|
99
99
|
`agent approve --deny --reason "…"` rejects a pending proposal for revision.
|
|
100
|
-
`agent stop <threadId>`
|
|
100
|
+
`agent stop <threadId>` stops a running review and everything under it.
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
A follow-up message to a busy review (`agent chat --thread <threadId>`) does not
|
|
103
|
+
stop the workers it is running: it waits for them and applies at the next step,
|
|
104
|
+
beside their reports. `agent chat` then returns at once with a `held` line
|
|
105
|
+
instead of polling, and `agent show` lists the message under `queued` until the
|
|
106
|
+
review reads it. Stop is what interrupts at once. To steer a busy review, list
|
|
107
|
+
what it is running and stop single workers; the review records a stopped worker
|
|
108
|
+
as stopped and carries on, and can read the stopped worker's thread to see what
|
|
109
|
+
it finished.
|
|
103
110
|
|
|
104
111
|
```bash
|
|
105
|
-
specset agent
|
|
112
|
+
specset agent workers list <threadId> # running workers: id, status, steps, type, title
|
|
113
|
+
specset agent workers list <threadId> --all --json # finished ones too, machine-readable
|
|
114
|
+
specset agent workers stop <workerThreadId> # stop one worker; the review carries on
|
|
115
|
+
specset agent stop <threadId> # stop the review and every worker
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Long-running agents — Design Quality Review, Project Review, Structured Review, Submittal Log — can be started as their own session without going through a Conversation thread. `specset agent start` creates the thread with the given `--type`, titles it, seeds it with your kickoff instructions, and returns immediately with the thread id; the run continues on the server.
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
specset agent start --type DesignQualityReview --project <projectId> \
|
|
106
122
|
--title "Building 720.1 - Design Review" --autoApprovePlan \
|
|
107
123
|
-m "Run a design review restricted to the Building 720.1 spec and drawing areas. Omit the master / no-area pass. Building 720.1's own specifications govern; Appendix A is a backup reference only."
|
|
108
124
|
|
|
109
125
|
specset agent show <threadId> # progress, transcript, findings so far
|
|
110
|
-
|
|
111
|
-
# A Drawing Review runs graph-planned coordination checks; select them up front.
|
|
112
|
-
specset agent start --type DrawingReview --project <projectId> \
|
|
113
|
-
--coordinationTaskTypes ElectricalDistribution,ArchitectureMepPenetrations \
|
|
114
|
-
--drawingArea "Building 720.1" --title "Building 720.1 - Coordination" \
|
|
115
|
-
-m "Coordinate the selected systems for Building 720.1."
|
|
116
126
|
```
|
|
117
127
|
|
|
118
|
-
- `--type` accepts any top-level agent type (`
|
|
119
|
-
-
|
|
120
|
-
-
|
|
121
|
-
- `--autoApprovePlan` skips plan sign-off. To inspect a plan before execution, pass `--noAutoApprovePlan`: this also disables the CLI's default blanket tool approval, so the session parks at `finalizePlan`. Approve it with `agent approve <threadId> --tool finalizePlan`. Omitting the flag preserves the existing plan setting, but blanket approval defaults on. `--isolateIssues` hides the project's pre-existing issues; `--noIsolateIssues` explicitly restores ordinary issue reads. `--reviewEffort Quick|Standard|Deep` sets a Project Review's effort: Quick investigates the strongest few leads on the root; Standard covers the brief within an approved worker budget; Deep covers the whole surveyed inventory. The plan states the expected worker count and estimated worker cost for approval. Omitting it keeps the thread's setting (Quick for a new thread). For a quick test of a Standard or Deep review, `--reviewMaxWorkers 3` caps the workers it starts (the server refuses more; the root records the rest as not examined). Native tools let a local run's harness use its own tools in the run's workspace and run sandboxed code over document and drawing reads, cropping and enlarging a detail. A thread that starts running locally has them on unless it already chose; `--noNativeTools` (both commands) or the conversation's Settings toggle turns them off.
|
|
128
|
+
- `--type` accepts any top-level agent type (`DesignQualityReview`, `ProjectReview`, `StructuredReview`, `SubmittalLog`, …). The kickoff message is the review's scope: name the building areas, disciplines, sections or sheets, what to exclude, and any special coordination checks — the same text a user would type when launching the review in the app.
|
|
129
|
+
- Deep Design Review (`DesignReview`) and Drawing Coordination Review (`DrawingReview`) are retired: the server refuses a new session of either, and checklists replace them. Their old sessions still take follow-ups with `agent chat --thread`.
|
|
130
|
+
- `--autoApprovePlan` skips plan sign-off. To inspect a plan before execution, pass `--noAutoApprovePlan`: this also disables the CLI's default blanket tool approval, so the session parks at `finalizePlan`. Approve it with `agent approve <threadId> --tool finalizePlan`. Omitting the flag preserves the existing plan setting, but blanket approval defaults on for a new thread; a message to an existing one (`agent chat --thread`) keeps that thread's approval setting unless `--autoApprove` or `--noAutoApprove` is passed. `--isolateIssues` hides the project's pre-existing issues; `--noIsolateIssues` explicitly restores ordinary issue reads. `--reviewEffort Quick|Standard|Deep` sets a Project Review's effort: Quick investigates the strongest few leads on the root; Standard covers the brief within an approved worker budget; Deep covers the whole surveyed inventory. The plan states the expected worker count and estimated worker cost for approval. Omitting it keeps the thread's setting (Quick for a new thread). For a quick test of a Standard or Deep review, `--reviewMaxWorkers 3` caps the workers it starts (the server refuses more; the root records the rest as not examined). Native tools let a local run's harness use its own tools in the run's workspace and run sandboxed code over document and drawing reads, cropping and enlarging a detail. A thread that starts running locally has them on unless it already chose; `--noNativeTools` (both commands) or the conversation's Settings toggle turns them off.
|
|
122
131
|
- `--title` names the session in Agent Sessions. `agent start` is `agent chat --detach` for a new thread; use `chat --type … --timeout …` instead when you want to wait for a short run to finish.
|
|
132
|
+
- `agent show <threadId> --wait` waits for a new thread's first turn (`agent start`, `agent serve --message` without `--thread`), or for a run already in progress, to finish or stop for input, then prints it, using the same completion check as `agent chat`. A turn queued on a thread whose last turn finished still reads as done until it starts, so send follow-ups with `agent chat --thread`, which waits for its own turn. `--timeout` sets the wait in seconds (default 600). It exits non-zero when the wait times out or the run ended in an error.
|
|
123
133
|
|
|
124
134
|
## Notes
|
|
125
135
|
|
|
@@ -145,15 +155,16 @@ Codex uses `CODEX_PATH` when set, then `codex` on PATH, then the managed runtime
|
|
|
145
155
|
```bash
|
|
146
156
|
specset agent serve --host <deployment> --org <slug> & # --account claude2,codex limits it to those accounts
|
|
147
157
|
specset agent show <thread link> # the transcript so far
|
|
158
|
+
specset agent show <thread link> --wait # wait for the run to finish or stop for input
|
|
148
159
|
```
|
|
149
160
|
|
|
150
161
|
What the harness spends is recorded on the thread as unbilled local usage (per model, priced for information only) and kept out of Specset's cost and credit figures.
|
|
151
162
|
|
|
152
163
|
### Native tools and the run workspace
|
|
153
164
|
|
|
154
|
-
Native tools are on for a thread that starts running locally, unless the thread already chose (an earlier on or off stands): the harness can then run shell commands and read, search or edit files in the run workspace. Turn them off with **Native tools** in the conversation's Settings or `--noNativeTools`; with them off, the harness only calls Specset functions, which the server authorizes and executes (approvals stay in the web app). Each action is mirrored into the conversation as a local action card, and the thread's **Permissions** setting
|
|
165
|
+
Native tools are on for a thread that starts running locally, unless the thread already chose (an earlier on or off stands): the harness can then run shell commands and read, search or edit files in the run workspace. Turn them off with **Native tools** in the conversation's Settings or `--noNativeTools`; with them off, the harness only calls Specset functions, which the server authorizes and executes (approvals stay in the web app). Each action is mirrored into the conversation as a local action card, and the thread's **Permissions** setting picks the tools: Read only (reads), Accept edits (and edits), Auto or Full access (and the shell; Full asks nothing). In every mode the commands and file tools are sealed in the run's workspace: commands read nothing of the machine but the system and its developer tools (Python, git, Homebrew), write only in the workspace and a private temp directory, reach no network, and never see a credential, so your home, `~/.ssh`, the keychains, every harness's home, `~/.config`, the runner's other directories and the shared temp directories stay closed. Claude Code runs Bash in its command sandbox, which also refuses writes to `.git/config` and `.git/hooks` (git works in an existing repository, but a new one can't be created there), and a hook holds its file tools to the workspace. That hook checks a path before Claude Code opens it, so in Auto and Full a command left running in the background could race it by swapping in a symlink; the commands themselves stay sealed. Codex runs under a permissions profile the runner builds and asks for no approval, so an escalation is declined. The seal needs macOS and, for Codex, a binary the runner checked enforces the profile; elsewhere a step runs with Specset tools only. Native sub-agents stay off; Specset's own sub-agent and fan-out tools run through the same machine. Native tools apply where the harness can run them, so turning them on or off never moves a running thread to another harness. Antigravity uses Specset tools only unless it is on macOS, its `agy --version` is a checked release (1.4.2), and the owner has set `runner.antigravityNativeTools.escapeTestedVersion: "1.4.2"` in `~/.config/specset/config.yml` after the sandbox escape tests pass. A different or unreadable version turns its native tools off again. Its commands use agy's sandbox. Recursive native searches (`grep_search`, `find_by_name`) are disabled; use sandboxed command searches in Auto/Full modes. Its file tools use the same real-path check with the same concurrent-symlink race limitation as Claude Code. It launches from a private read-only project so configuration left in the shared workspace cannot run as agy hooks or MCP servers; writes to `.agents/`, `AGENTS.md` and `GEMINI.md` are also refused by file tools. Clipboard and other macOS services remain reachable from the command sandbox. The Settings menu still labels Antigravity tiers as running without native tools; this opt-in capability is checked by the runner, and helper document reads on Antigravity still run without code.
|
|
155
166
|
|
|
156
|
-
Each run (a root thread and its children) works in one private directory under `--directory` (`~/.specset/personal-runs/<runner>/runs/<thread>/` by default) with `specs/`, `drawings/`, `docs/`, `inputs/` and `out/`. It starts empty
|
|
167
|
+
Each run (a root thread and its children) works in one private directory under `--directory` (`~/.specset/personal-runs/<runner>/runs/<thread>/` by default) with `specs/`, `drawings/`, `docs/`, `inputs/` and `out/`. It starts empty and has no network, so the harness cannot download into it: project material comes through Specset tools, and the model writes what it needs from their results into the workspace. On Antigravity, attachments and file results are saved separately in private read-only input storage at the absolute paths supplied to the model; copy them into the workspace before editing. Findings cite the Specset link, never the local path.
|
|
157
168
|
|
|
158
169
|
With Native tools on and Permissions Auto or Full, the vision tier's document and drawing reads (`examineDrawingSheets`, `examineDocument`, `examineFile` and the like) may also run code to look closer at their attachments: crop a dense detail from the full-resolution file and view it enlarged. Such a read runs in its own scratch directory holding only its attachments (`inputs/attachment-N.png|pdf`), never the run workspace. Its commands are sandboxed: no network, no credentials in their environment, and nothing readable but the system, developer tools, Homebrew (for Python and Pillow) and that directory. Your own temp directory, where other applications keep files, stays closed except, under Claude Code, for the scratch files macOS's `sips` saves there; the shared `/tmp` stays closed. Claude Code's command sandbox denies reading by default, a hook holds its Read tool to the directory, and zsh skips your startup files; Codex runs under a permissions profile that adds only Homebrew and its own binary, with the core environment, so it crops with Pillow and `sips` fails; the runner first checks once that the binary enforces the profile (Codex before 0.157.1 ignores its deny entries), and otherwise runs these reads without code. Code execution needs macOS: on Linux, Claude Code's bubblewrap sandbox leaves the credentials it removes readable to commands through its own process, so these reads run without code there. Its actions are printed in the `agent serve` terminal, because a helper read's actions never reach the conversation. These reads were evaluated on the cloud models without code execution, so treat code-execution results as unevaluated.
|
|
159
170
|
|
|
@@ -163,17 +174,17 @@ Sessions are ephemeral (`persistSession: false` for Claude, `ephemeral: true` fo
|
|
|
163
174
|
|
|
164
175
|
### Serve multiple local threads
|
|
165
176
|
|
|
166
|
-
Connect to an organization with `specset agent serve --host <host> --org <slug|id>`; no project or thread is required. Enable **Execute locally** in conversation Settings. A sole eligible connected runner automatically receives your queued local request; with multiple connected runners, choose **Machine** in Settings. The connection command disappears once a machine is connected. Completed, stopped, or expired runs offer **Start a new local run** above the composer; viewing them does not restart them. `--thread <id>` immediately assigns an existing root; `--message "Review electrical coordination" --type
|
|
177
|
+
Connect to an organization with `specset agent serve --host <host> --org <slug|id>`; no project or thread is required. Enable **Execute locally** in conversation Settings. A sole eligible connected runner automatically receives your queued local request; with multiple connected runners, choose **Machine** in Settings. The connection command disappears once a machine is connected. Completed, stopped, or expired runs offer **Start a new local run** above the composer; viewing them does not restart them. `--thread <id>` immediately assigns an existing root; `--message "Review electrical coordination" --type DesignQualityReview` creates a local root in the selected project. `--org <slug|id>` pins the org; otherwise the selected org or sole membership on that host is used. Omit `--project` for organization scope, or supply a comma-separated project allowlist. A new message-created thread requires exactly one project. It serves from the accounts `specset harness list` shows (config.yml's `runner:` block, or the logins this shell would use when none are listed); `specset harness add claude2 --harness claude --home ~/.claude2 --preferred` adds another login, and `--account` limits one run. Claude uses the CLI’s bundled SDK runtime, then native `claude` on PATH. Codex uses `CODEX_PATH`, native `codex` on PATH, then the managed native runtime from `specset harness install codex`. Startup checks each account's runtime before registering. Run a separate `serve --host <deployment>` process for each dev worktree. Each process prints and pins its host/org/accounts at startup; later CLI host/org changes cannot redirect it. Login and selected org are stored per host; execution limits apply per runner. For a bounded smoke test, add `--stopAfterIssues 2`; the root counts issues across its children and stops that tree when the threshold is reached (an in-flight batch can exceed it). Only this signed-in user can assign work, and children inherit the same machine, project scope, deadline and thread budget.
|
|
167
178
|
|
|
168
179
|
`agent serve` uses server-owned orchestration: Temporal executes the agent and its tools, while model requests run through the connected machine. This includes generic drawing-coordination assessments, image inputs and structured output. The runner keeps a native session alive across each complete parallel tool batch and sends back the server's authorized results. All Specset approvals remain in the web app; native tools follow the thread's **Native tools** setting (see above) and native delegation stays off. Independent model requests use independent sessions. Claude uses `persistSession: false`; Codex uses `ephemeral: true`.
|
|
169
180
|
|
|
170
|
-
For an unattended kickoff, `--autoApprovePlan` skips the plan sign-off and `--autoApprove` approves Specset tools for that message (default off for `serve`). `--isolateIssues` ignores pre-existing project issues when planning a bounded test. The command prints the created or assigned thread link. File attachments and file tool results preserve bytes, MIME type and optional filename through the same transport. Claude receives raw PDFs; Codex receives server-rendered PDF page images, capped at 20 pages per request across all PDFs. Both support image inputs and UTF-8 text attachments. Other binary formats fail explicitly until a compatible reader or conversion is available.
|
|
181
|
+
For an unattended kickoff, `--autoApprovePlan` skips the plan sign-off and `--autoApprove` approves Specset tools for that message (default off for a thread `serve` creates; with `--thread`, omit it to keep the thread's setting). `--isolateIssues` ignores pre-existing project issues when planning a bounded test. The command prints the created or assigned thread link. File attachments and file tool results preserve bytes, MIME type and optional filename through the same transport. Claude receives raw PDFs; Codex receives server-rendered PDF page images, capped at 20 pages per request across all PDFs. Both support image inputs and UTF-8 text attachments. Other binary formats fail explicitly until a compatible reader or conversion is available.
|
|
171
182
|
|
|
172
|
-
Pin both `--host <deployment>` and `--org <slug>` when using `agent chat`, `agent start`, `agent show`, `agent inspect`, `agent approve` or `agent
|
|
183
|
+
Pin both `--host <deployment>` and `--org <slug>` when using `agent chat`, `agent start`, `agent show`, `agent inspect`, `agent approve`, `agent stop` or `agent workers`; these overrides do not change the CLI’s saved selection.
|
|
173
184
|
|
|
174
|
-
Every model call carries its routing tier (conversation, orchestrator, planner, analyst, worker, lite, balanced, query, vision), and each tier names a model that any account whose harness offers it serves. The defaults match the app; `specset harness list` prints each tier's current model and the class it runs as. Antigravity is opt-in: it serves those tiers only after you add an account for it (`specset harness add agy --harness antigravity`); until then they run their model's class on Claude Code or Codex, or in the cloud with `harness fallback`. A model no account offers runs as its class (frontier, standard, fast) on a harness the machine has. Change a tier with `specset harness tier <tier> <model[:effort]>`, a class's model with `specset harness model <harness> <class> <model> [--effort e]`, or one run's with `agent serve --tier conversation=opus`. The conversation's Settings pins a class or one of the reported models per harness for that thread's own steps, plus a reasoning effort; children and helper calls keep the tiers. When the preferred accounts for a model are at their limits, `specset harness failover sameModel|any|off` decides where its calls go (default sameModel: other accounts with the same model; any: also the same class elsewhere); a call that already streamed fails instead and the next one goes elsewhere. A provider rate limit (HTTP 429) is waited out rather than failing the run: the server retries the call once the account's short wait passes (on another account if one has room), unless it already ran a native command or edit, which a retry could repeat; only every account being at its usage limit stops the run. `specset harness fallback <tier|all> cloud` (or `agent serve --fallback analyst=cloud`) sends a tier's calls to Specset's cloud model, billed to the organization, instead of another provider's whenever no account here can serve its model (none offers it, or its accounts are at their limits); `local` is the default. `--concurrency` defaults to 5 (at most 16) and limits active model calls; tool and approval waits release their slot so even 1 can complete nested fan-out. `--maxThreads` defaults to 100 and `--maxRunSeconds` to 3600 per root authorization. `specset harness limits --concurrency 16 --maxThreads 300 --maxRunSeconds 43200` saves them in config.yml for every `agent serve` (a flag still wins for one run; `default` clears one; no flags prints them). Specset’s `--autoApprove` message setting controls tool approval; the thread's Permissions setting bounds native tools when they are on. Ctrl-C revokes the registration and stops queued/running descendants. A restart needs a new assignment. A lost native continuation fails visibly rather than replaying writes or switching to a cloud model.
|
|
185
|
+
Every model call carries its routing tier (conversation, orchestrator, planner, analyst, worker, drafter, lite, balanced, query, vision), and each tier names a model that any account whose harness offers it serves. The defaults match the app; `specset harness list` prints each tier's current model and the class it runs as. Antigravity is opt-in: it serves those tiers only after you add an account for it (`specset harness add agy --harness antigravity`); until then they run their model's class on Claude Code or Codex, or in the cloud with `harness fallback`. A model no account offers runs as its class (frontier, standard, fast) on a harness the machine has. Change a tier with `specset harness tier <tier> <model[:effort]>`, a class's model with `specset harness model <harness> <class> <model> [--effort e]`, or one run's with `agent serve --tier conversation=opus`. The conversation's Settings pins a class or one of the reported models per harness for that thread's own steps, plus a reasoning effort; children and helper calls keep the tiers. When the preferred accounts for a model are at their limits, `specset harness failover sameModel|any|off` decides where its calls go (default sameModel: other accounts with the same model; any: also the same class elsewhere); a call that already streamed fails instead and the next one goes elsewhere. A provider rate limit (HTTP 429) is waited out rather than failing the run: the server retries the call once the account's short wait passes (on another account if one has room), unless it already ran a native command or edit, which a retry could repeat; only every account being at its usage limit stops the run. A call whose input a harness refuses for its size (Codex caps a turn's input at 1,048,576 characters) is never retried there: it moves to another harness the failover setting allows, else to its tier's cloud model when the tier falls back to the cloud, else fails naming the cause. `specset harness fallback <tier|all> cloud` (or `agent serve --fallback analyst=cloud`) sends a tier's calls to Specset's cloud model, billed to the organization, instead of another provider's whenever no account here can serve its model (none offers it, or its accounts are at their limits); `local` is the default. `--concurrency` defaults to 5 (at most 16) and limits active model calls; tool and approval waits release their slot so even 1 can complete nested fan-out. `--maxThreads` defaults to 100 and `--maxRunSeconds` to 3600 per root authorization. `specset harness limits --concurrency 16 --maxThreads 300 --maxRunSeconds 43200` saves them in config.yml for every `agent serve` (a flag still wins for one run; `default` clears one; no flags prints them). Specset’s `--autoApprove` message setting controls tool approval; the thread's Permissions setting bounds native tools when they are on. Ctrl-C revokes the registration and stops queued/running descendants. A restart needs a new assignment. A lost native continuation fails visibly rather than replaying writes or switching to a cloud model.
|
|
175
186
|
|
|
176
|
-
Harness tool callbacks can only wait for results supplied by the server. Each job receives private harness configuration and a minimal environment
|
|
187
|
+
Harness tool callbacks can only wait for results supplied by the server. Each job receives private harness configuration and a minimal environment, and no CLI login: a native session's commands reach no network, so project material comes through Specset tools. Claude shares the selected native credential store and its refresh lock. Codex uses an authentication-only native process to refresh the selected login, then supplies temporary access tokens to isolated inference sessions. Neither copies rotating refresh credentials into job homes. Settings, hooks, plugins and global MCP servers are excluded from inference sessions. Run `claude auth login` first if the harness reports authentication is required. The harness itself still runs as the local OS user, outside the seal its commands run in. Use a dedicated account or isolated host for untrusted project content; do not share this personal runner across users.
|
|
177
188
|
|
|
178
189
|
Use `specset agent runners list --json` to inspect your registrations.
|
|
179
190
|
Use `specset agent runners revoke <runner-id>` to stop and revoke one remotely, including an offline runner after a crash.
|
|
@@ -12,6 +12,8 @@ Run every operation with `specset api`. All ids are plain UUIDs. Inline enums, n
|
|
|
12
12
|
|
|
13
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
|
+
**Companies** belong to the organization's directory and are shared by its projects. A company works on a project through a **ProjectCompany** row, which holds that project's `role`, `notes`, `archived` state and closeout documentation. Assigning a directory company to a project record (an asset's responsible company, a part's vendor, a submittal's or RFI's responsible contractor) adds it to the project.
|
|
16
|
+
|
|
15
17
|
Maintenance has three layers:
|
|
16
18
|
|
|
17
19
|
- **Procedure** — reusable checklist content: ordered typed fields, descriptions, and source references.
|
|
@@ -53,6 +55,7 @@ Notable differences:
|
|
|
53
55
|
|
|
54
56
|
- Asset creation requires `projectId` and `locationId`. `paginatedAssets` accepts `search` and filters such as `statuses`, `locationIds`, `productIds`, `systemIds`, `responsibleCompanyIds`, `parentIds`, and `archived`.
|
|
55
57
|
- Warranty is asset-scoped; use `warrantiesByAsset(assetId)` to browse one asset's warranties.
|
|
58
|
+
- Company is org-wide: `paginatedCompanies(orgId: ...)` lists the directory and `searchCompanies(orgId, search)` finds a company by name. `paginatedProjectCompanies(projectId)` lists a project's companies with their `role`; `addCompaniesToProject(projectId, companyIds)`, `removeCompaniesFromProject(projectId, companyIds)` (refused while a project record still names the company) and `updateProjectCompany(input: { id, role, notes, archived })` manage the participation. `createCompany(input: { projectId, ... })` creates the directory company and puts it on that project.
|
|
56
59
|
- Location, Product, Company, System, and Asset updates support reversible `archived` state. Prefer archiving to deletion.
|
|
57
60
|
- Batch-delete availability varies. Introspect the mutation root rather than guessing an operation name.
|
|
58
61
|
|
|
@@ -175,7 +178,7 @@ specset api --query 'mutation { setProductDocumentRoles(productId: "<product-id>
|
|
|
175
178
|
|
|
176
179
|
Existing project Documents work too — a submittal attachment's `document { id }` (from `paginatedSubmittals … attachments { document { id title } }`) can be linked directly as `SubmittalPackage`/`Datasheet`; the only check is that the Document belongs to the same project. To bind a slice of a large file, `extractPagesFromFileResource(id: <fileResourceId>, pages: [3, 4, 5])` returns a new CloudFile to feed into `createDocumentsFromCloudFiles`. Only PDF files merge into the O&M package export; images and other types are listed as references.
|
|
177
180
|
|
|
178
|
-
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. Any later create/update/link on the entity (a new document pivot, a warranty document, a field edit) marks the documentation stale again, so when seeding a graph write documentation **last**. Calling `mark<Entity>DocumentationComplete` with an empty block sets the state to `NotApplicable`, which the package readiness report accepts — use it for entities that don't warrant prose rather than writing filler.
|
|
181
|
+
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. A company's block is per project: use `updateProjectCompanyDocumentation` / `markProjectCompanyDocumentationComplete` with the ProjectCompany id. Any later create/update/link on the entity (a new document pivot, a warranty document, a field edit) marks the documentation stale again, so when seeding a graph write documentation **last**. Calling `mark<Entity>DocumentationComplete` with an empty block sets the state to `NotApplicable`, which the package readiness report accepts — use it for entities that don't warrant prose rather than writing filler.
|
|
179
182
|
|
|
180
183
|
### Writing documentation that reads well in the O&M package export
|
|
181
184
|
|
|
@@ -324,7 +327,7 @@ For duplicate products P1 (loser) and P2 (survivor):
|
|
|
324
327
|
3. Re-run the filtered query and confirm zero remaining references.
|
|
325
328
|
4. Archive P1 with `updateProduct(input: { id: P1, archived: true })`.
|
|
326
329
|
|
|
327
|
-
Use the same impact-first pattern for duplicate companies,
|
|
330
|
+
Use the same impact-first pattern for duplicate locations and systems. Duplicate companies merge in one step: `mergeCompanies(targetId, sourceIds)` moves their projects, documents, record assignments, people and Procore links to the target, fills the target's blank profile fields from theirs (in `sourceIds` order), and archives the duplicates; it is not reversible, so confirm the pair first. `companyDuplicates(orgId)` lists likely pairs (names that differ only in case, punctuation or a legal form), and `companies(ids)` loads them side by side.
|
|
328
331
|
|
|
329
332
|
## Safety
|
|
330
333
|
|
|
@@ -6,7 +6,7 @@ allowed-tools: Bash Read AskUserQuestion
|
|
|
6
6
|
|
|
7
7
|
# Specset Issues
|
|
8
8
|
|
|
9
|
-
Design reviews (Design Review,
|
|
9
|
+
Design reviews (Design Quality Review, Project Review, checklist runs, and the retired Deep Design Review and Drawing Coordination Review) and issue-finding skills file **issues** on a project. Each issue can be **verified** by an adversarial agent that re-reads the cited sheets and sections and records a verdict — `Confirmed` or `Rejected` — with a short justification. New review issues are verified automatically; skill-run issues, issues filed before verification existed, and issues whose runs ended without a verdict are verified on demand.
|
|
10
10
|
|
|
11
11
|
## Reading the queue
|
|
12
12
|
|
|
@@ -63,6 +63,8 @@ specset projects ingest-status --project <project-id> --wait --timeout 1800 --js
|
|
|
63
63
|
|
|
64
64
|
`add-docs` returns upload metadata and the background bulk action immediately. `ingest-status --wait` follows processing and publication; omit `--wait` for a snapshot. 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. The status command checks workflow completion and file publication; use drawing-element or graph tools to inspect content quality.
|
|
65
65
|
|
|
66
|
+
The server skips identical files already in the project's Documents list and repeated files in the same request, then creates the rest. Closeout attachments do not count as existing copies. `add-docs` prints how many duplicates were skipped; JSON includes `skippedCount` and `bulkAction.skippedCloudFileIds` alongside the uploaded `files`.
|
|
67
|
+
|
|
66
68
|
### `autoImport`: publish automatically or curate
|
|
67
69
|
|
|
68
70
|
- `autoImport: true` — every generated spec/drawing file is classified and published into default sets automatically. Use when the user wants everything live immediately.
|
|
@@ -18,7 +18,7 @@ Two common flows have purpose-built commands; prefer them to hand-written mutati
|
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
20
|
specset submittals create --project <projectId> --file ./package.pdf --json # uploads the file and creates the submittal from it; --title, repeatable --specSection <id>
|
|
21
|
-
specset submittals review --submittal <submittalId|link> --wait --json # runs a compliance review and prints its issues; repeatable --specSection, --prompt, --timeout (default 3600)
|
|
21
|
+
specset submittals review --submittal <submittalId|link> --wait --json # runs a compliance review and prints its issues; exits non-zero when it times out or the review fails; repeatable --specSection, --prompt, --timeout (default 3600)
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
## Reading
|
|
@@ -65,15 +65,15 @@ The retrieval tools return **extracted text**, which is authoritative for schedu
|
|
|
65
65
|
| Entity | Command | Also accepts | Formats / options |
|
|
66
66
|
| -------------- | ----------------------------------------------------------------------- | ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
67
67
|
| 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) |
|
|
68
|
-
| Documents | `specset docs download 'https://app.specbook.ai/go/doc/<uuid>'` | | also covers RFI attachments and closeout documents
|
|
68
|
+
| Documents | `specset docs download 'https://app.specbook.ai/go/doc/<uuid>'` | | `--original` (uploaded file, not the converted PDF); also covers RFI attachments and closeout documents |
|
|
69
69
|
| 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) |
|
|
70
70
|
| 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` |
|
|
71
71
|
| RFIs | `specset rfis download 'https://app.specbook.ai/go/rfi/<uuid>'` | | question + response attachments; `--list`, `--all` |
|
|
72
|
-
| File resources | `specset files download 'https://app.specbook.ai/go/file/<uuid>'` | | escape hatch for a returned file source link
|
|
72
|
+
| File resources | `specset files download 'https://app.specbook.ai/go/file/<uuid>'` | | escape hatch for a returned file source link; `--original` (uploaded file, not the converted PDF) |
|
|
73
73
|
|
|
74
74
|
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.
|
|
75
75
|
|
|
76
|
-
`drawings download`, `files download` and `specs download` also accept `--org <slug|id>` to pin the source organization when parallel sessions might change the active organization, for example `specset drawings download P2.120 --project <id> --org <slug> --host <host> --format pdf` or `specset specs download '08 71 00' --project <id> --org <slug> --host <host> --out hardware.pdf`.
|
|
76
|
+
`docs download`, `drawings download`, `files download` and `specs download` also accept `--org <slug|id>` to pin the source organization when parallel sessions might change the active organization, for example `specset drawings download P2.120 --project <id> --org <slug> --host <host> --format pdf` or `specset specs download '08 71 00' --project <id> --org <slug> --host <host> --out hardware.pdf`.
|
|
77
77
|
|
|
78
78
|
To inspect an existing detail crop, add `--node <shortId|UUID>`, for example `specset drawings download <sheet-ref> --node d6 --format image --out detail.png`. Use `--format pdf` for the saved crop PDF. This downloads the current revision's stored crop without regenerating it; missing nodes or crops return an error. Node crops do not support `--format file`. JSON output adds the resolved `nodeId` to the normal sheet download metadata.
|
|
79
79
|
|
|
@@ -12,15 +12,18 @@ Every operation here takes your org id — get it with `{ me { orgMembers { org
|
|
|
12
12
|
|
|
13
13
|
Roles (`OrgMemberTeamRole`): `Member`, `Manager`, `Admin`. Statuses (`OrgMemberStatus`): `PendingApproval`, `Active`, `Deactivated`. You need to be an org Admin for most mutations in this skill.
|
|
14
14
|
|
|
15
|
-
List members —
|
|
15
|
+
List active members — `--role` narrows to some built-in roles, `--privileged` to the privileged accounts, and `--json` adds each membership `id` the mutations below take:
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
specset
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
specset org members list --role Admin,Manager --json
|
|
19
|
+
specset org members list --privileged
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
An organization can define its own roles and change what Manager and Member allow (Members → Roles in the app). A member who holds one of the organization's own roles is listed under that role's name, and counts as `Member` for `--role`. `--privileged` lists everyone whose role is a privileged account, whatever it is called: Admins, Managers, and holders of any role that manages members, opens every project, or connects and configures integrations.
|
|
23
|
+
|
|
24
|
+
Search members through the API (`orgMembers(orgId:, statuses: [...])` lists members in other statuses):
|
|
23
25
|
|
|
26
|
+
```bash
|
|
24
27
|
specset api --query 'query($orgId: ID!, $search: String) {
|
|
25
28
|
paginatedOrgMembers(orgId: $orgId, search: $search, first: 20) {
|
|
26
29
|
edges { node { id role status user { email } } }
|
|
@@ -41,6 +44,10 @@ specset api --query 'mutation($id: ID!) {
|
|
|
41
44
|
}' -F id=<org-member-id>
|
|
42
45
|
```
|
|
43
46
|
|
|
47
|
+
## Security Settings
|
|
48
|
+
|
|
49
|
+
`specset org security show` lists the organization's seven security settings (single sign-on, authentication requirement, passwords, passkeys, authenticator codes, onboarding policy, session length) beside the values the Secure Configuration Guide (https://docs.specset.com/docs/secure-configuration) recommends for an organization that holds federal information or CUI, each marked `matches`, `stricter` or `differs`, with the identity providers. `--json` exports the same. Any member can read them; only an Admin changes them, in the web app under **Security & SSO** (the CLI cannot). A `differs` can be deliberate, such as single sign-on required with the provider's MFA confirmed by Specset support.
|
|
50
|
+
|
|
44
51
|
## Teams
|
|
45
52
|
|
|
46
53
|
A team is a named group of members. Assigning a team to a project puts everyone on it on that project, and changes to the team carry over to every project it's on. Teams don't carry a role: people keep their org role unless a project role is set on them.
|
|
@@ -56,8 +63,9 @@ Changing a team needs org Manager or Admin and management permission in every pr
|
|
|
56
63
|
Nobody joins an org on someone else's say-so. Both paths below leave the person with a pending invitation they must accept — in the app (existing Specset users see an invite badge), by registering through the invite link, or by signing in with the org's SSO. Until then an added user is an `Invited` member: visible in pickers and assignable, but with no access.
|
|
57
64
|
|
|
58
65
|
- `createOrgMember(orgId: ID!, email: String!, firstName: String!, lastName: String!, role: ..., projectIds: [ID!], teamIds: [ID!], sendInvite: Boolean)` — adds the person as an `Invited` seat right away so they can be assigned work before they've joined. `sendInvite: true` emails the invite link; existing Specset users get the in-app invitation either way. Returns the `OrgMember` (`status: Invited`).
|
|
66
|
+
- Directory contacts are people the org tracks and assigns (a subcontractor's PM, a received-from on a submittal) who don't sign in: `createOrgMember(..., memberType: DirectoryOnly)` adds one with no invite and no access. Every member and contact can carry `companyId` (a directory company; staff default to the org's own), `jobTitle`, `phone`, `mobilePhone`, and `notes`, on create and on `updateOrgMember`. `updateOrgMember(id, role: ..., memberType: Regular, sendInvite: true)` turns a contact into an invited member. `orgMembers(orgId, companyIds: [...])` lists a company's people.
|
|
59
67
|
- `createOrgInvite(orgId: ID!, email: String!, role: ..., projectIds: [ID!])` — invite-only (no seat until they accept; no `teamIds`, so add teams once they have a seat); also used to (re-)invite an existing `Invited` seat. `projectIds` (optional, inline the list) pre-scopes a Member to specific projects. Set `role` deliberately — don't default people to more access than they need. Share the returned `inviteUrl` if the email might not land.
|
|
60
|
-
- `orgInvites(orgId: ID!)` / `paginatedOrgInvites(orgId: ID!, first: Int, after: String)` — pending invites; select `{ id user { email } role inviteUrl }`.
|
|
68
|
+
- `orgInvites(orgId: ID!)` / `paginatedOrgInvites(orgId: ID!, first: Int, after: String)` — pending invites; select `{ id user { email } role inviteUrl }`. `inviteUrl` is null unless you may re-send that invitation (a Manager or Admin, up to your own role): whoever holds the link can register as the invitee.
|
|
61
69
|
- `updateOrgInvite(id: ID!, role: ..., projectIds: [ID!])` — `role` is required.
|
|
62
70
|
- `resendOrgInvite(id: ID!)` — rotates the link and re-sends the email.
|
|
63
71
|
- `deleteOrgInvite(id: ID!)` — revoke a pending invite (an `Invited` seat stays, as "Not invited"; `deleteOrgMember` removes the seat).
|
|
@@ -97,29 +97,39 @@ Inspect `getDrawingGraphOverview` bundles and quality flags before a benchmark;
|
|
|
97
97
|
building the graph does not reprocess stale source nodes. `--autoApprovePlan`
|
|
98
98
|
allows immediate execution when the brief is already approved.
|
|
99
99
|
`agent approve --deny --reason "…"` rejects a pending proposal for revision.
|
|
100
|
-
`agent stop <threadId>`
|
|
100
|
+
`agent stop <threadId>` stops a running review and everything under it.
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
A follow-up message to a busy review (`agent chat --thread <threadId>`) does not
|
|
103
|
+
stop the workers it is running: it waits for them and applies at the next step,
|
|
104
|
+
beside their reports. `agent chat` then returns at once with a `held` line
|
|
105
|
+
instead of polling, and `agent show` lists the message under `queued` until the
|
|
106
|
+
review reads it. Stop is what interrupts at once. To steer a busy review, list
|
|
107
|
+
what it is running and stop single workers; the review records a stopped worker
|
|
108
|
+
as stopped and carries on, and can read the stopped worker's thread to see what
|
|
109
|
+
it finished.
|
|
103
110
|
|
|
104
111
|
```bash
|
|
105
|
-
specset agent
|
|
112
|
+
specset agent workers list <threadId> # running workers: id, status, steps, type, title
|
|
113
|
+
specset agent workers list <threadId> --all --json # finished ones too, machine-readable
|
|
114
|
+
specset agent workers stop <workerThreadId> # stop one worker; the review carries on
|
|
115
|
+
specset agent stop <threadId> # stop the review and every worker
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Long-running agents — Design Quality Review, Project Review, Structured Review, Submittal Log — can be started as their own session without going through a Conversation thread. `specset agent start` creates the thread with the given `--type`, titles it, seeds it with your kickoff instructions, and returns immediately with the thread id; the run continues on the server.
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
specset agent start --type DesignQualityReview --project <projectId> \
|
|
106
122
|
--title "Building 720.1 - Design Review" --autoApprovePlan \
|
|
107
123
|
-m "Run a design review restricted to the Building 720.1 spec and drawing areas. Omit the master / no-area pass. Building 720.1's own specifications govern; Appendix A is a backup reference only."
|
|
108
124
|
|
|
109
125
|
specset agent show <threadId> # progress, transcript, findings so far
|
|
110
|
-
|
|
111
|
-
# A Drawing Review runs graph-planned coordination checks; select them up front.
|
|
112
|
-
specset agent start --type DrawingReview --project <projectId> \
|
|
113
|
-
--coordinationTaskTypes ElectricalDistribution,ArchitectureMepPenetrations \
|
|
114
|
-
--drawingArea "Building 720.1" --title "Building 720.1 - Coordination" \
|
|
115
|
-
-m "Coordinate the selected systems for Building 720.1."
|
|
116
126
|
```
|
|
117
127
|
|
|
118
|
-
- `--type` accepts any top-level agent type (`
|
|
119
|
-
-
|
|
120
|
-
-
|
|
121
|
-
- `--autoApprovePlan` skips plan sign-off. To inspect a plan before execution, pass `--noAutoApprovePlan`: this also disables the CLI's default blanket tool approval, so the session parks at `finalizePlan`. Approve it with `agent approve <threadId> --tool finalizePlan`. Omitting the flag preserves the existing plan setting, but blanket approval defaults on. `--isolateIssues` hides the project's pre-existing issues; `--noIsolateIssues` explicitly restores ordinary issue reads. `--reviewEffort Quick|Standard|Deep` sets a Project Review's effort: Quick investigates the strongest few leads on the root; Standard covers the brief within an approved worker budget; Deep covers the whole surveyed inventory. The plan states the expected worker count and estimated worker cost for approval. Omitting it keeps the thread's setting (Quick for a new thread). For a quick test of a Standard or Deep review, `--reviewMaxWorkers 3` caps the workers it starts (the server refuses more; the root records the rest as not examined). Native tools let a local run's harness use its own tools in the run's workspace and run sandboxed code over document and drawing reads, cropping and enlarging a detail. A thread that starts running locally has them on unless it already chose; `--noNativeTools` (both commands) or the conversation's Settings toggle turns them off.
|
|
128
|
+
- `--type` accepts any top-level agent type (`DesignQualityReview`, `ProjectReview`, `StructuredReview`, `SubmittalLog`, …). The kickoff message is the review's scope: name the building areas, disciplines, sections or sheets, what to exclude, and any special coordination checks — the same text a user would type when launching the review in the app.
|
|
129
|
+
- Deep Design Review (`DesignReview`) and Drawing Coordination Review (`DrawingReview`) are retired: the server refuses a new session of either, and checklists replace them. Their old sessions still take follow-ups with `agent chat --thread`.
|
|
130
|
+
- `--autoApprovePlan` skips plan sign-off. To inspect a plan before execution, pass `--noAutoApprovePlan`: this also disables the CLI's default blanket tool approval, so the session parks at `finalizePlan`. Approve it with `agent approve <threadId> --tool finalizePlan`. Omitting the flag preserves the existing plan setting, but blanket approval defaults on for a new thread; a message to an existing one (`agent chat --thread`) keeps that thread's approval setting unless `--autoApprove` or `--noAutoApprove` is passed. `--isolateIssues` hides the project's pre-existing issues; `--noIsolateIssues` explicitly restores ordinary issue reads. `--reviewEffort Quick|Standard|Deep` sets a Project Review's effort: Quick investigates the strongest few leads on the root; Standard covers the brief within an approved worker budget; Deep covers the whole surveyed inventory. The plan states the expected worker count and estimated worker cost for approval. Omitting it keeps the thread's setting (Quick for a new thread). For a quick test of a Standard or Deep review, `--reviewMaxWorkers 3` caps the workers it starts (the server refuses more; the root records the rest as not examined). Native tools let a local run's harness use its own tools in the run's workspace and run sandboxed code over document and drawing reads, cropping and enlarging a detail. A thread that starts running locally has them on unless it already chose; `--noNativeTools` (both commands) or the conversation's Settings toggle turns them off.
|
|
122
131
|
- `--title` names the session in Agent Sessions. `agent start` is `agent chat --detach` for a new thread; use `chat --type … --timeout …` instead when you want to wait for a short run to finish.
|
|
132
|
+
- `agent show <threadId> --wait` waits for a new thread's first turn (`agent start`, `agent serve --message` without `--thread`), or for a run already in progress, to finish or stop for input, then prints it, using the same completion check as `agent chat`. A turn queued on a thread whose last turn finished still reads as done until it starts, so send follow-ups with `agent chat --thread`, which waits for its own turn. `--timeout` sets the wait in seconds (default 600). It exits non-zero when the wait times out or the run ended in an error.
|
|
123
133
|
|
|
124
134
|
## Notes
|
|
125
135
|
|
|
@@ -145,15 +155,16 @@ Codex uses `CODEX_PATH` when set, then `codex` on PATH, then the managed runtime
|
|
|
145
155
|
```bash
|
|
146
156
|
specset agent serve --host <deployment> --org <slug> & # --account claude2,codex limits it to those accounts
|
|
147
157
|
specset agent show <thread link> # the transcript so far
|
|
158
|
+
specset agent show <thread link> --wait # wait for the run to finish or stop for input
|
|
148
159
|
```
|
|
149
160
|
|
|
150
161
|
What the harness spends is recorded on the thread as unbilled local usage (per model, priced for information only) and kept out of Specset's cost and credit figures.
|
|
151
162
|
|
|
152
163
|
### Native tools and the run workspace
|
|
153
164
|
|
|
154
|
-
Native tools are on for a thread that starts running locally, unless the thread already chose (an earlier on or off stands): the harness can then run shell commands and read, search or edit files in the run workspace. Turn them off with **Native tools** in the conversation's Settings or `--noNativeTools`; with them off, the harness only calls Specset functions, which the server authorizes and executes (approvals stay in the web app). Each action is mirrored into the conversation as a local action card, and the thread's **Permissions** setting
|
|
165
|
+
Native tools are on for a thread that starts running locally, unless the thread already chose (an earlier on or off stands): the harness can then run shell commands and read, search or edit files in the run workspace. Turn them off with **Native tools** in the conversation's Settings or `--noNativeTools`; with them off, the harness only calls Specset functions, which the server authorizes and executes (approvals stay in the web app). Each action is mirrored into the conversation as a local action card, and the thread's **Permissions** setting picks the tools: Read only (reads), Accept edits (and edits), Auto or Full access (and the shell; Full asks nothing). In every mode the commands and file tools are sealed in the run's workspace: commands read nothing of the machine but the system and its developer tools (Python, git, Homebrew), write only in the workspace and a private temp directory, reach no network, and never see a credential, so your home, `~/.ssh`, the keychains, every harness's home, `~/.config`, the runner's other directories and the shared temp directories stay closed. Claude Code runs Bash in its command sandbox, which also refuses writes to `.git/config` and `.git/hooks` (git works in an existing repository, but a new one can't be created there), and a hook holds its file tools to the workspace. That hook checks a path before Claude Code opens it, so in Auto and Full a command left running in the background could race it by swapping in a symlink; the commands themselves stay sealed. Codex runs under a permissions profile the runner builds and asks for no approval, so an escalation is declined. The seal needs macOS and, for Codex, a binary the runner checked enforces the profile; elsewhere a step runs with Specset tools only. Native sub-agents stay off; Specset's own sub-agent and fan-out tools run through the same machine. Native tools apply where the harness can run them, so turning them on or off never moves a running thread to another harness. Antigravity uses Specset tools only unless it is on macOS, its `agy --version` is a checked release (1.4.2), and the owner has set `runner.antigravityNativeTools.escapeTestedVersion: "1.4.2"` in `~/.config/specset/config.yml` after the sandbox escape tests pass. A different or unreadable version turns its native tools off again. Its commands use agy's sandbox. Recursive native searches (`grep_search`, `find_by_name`) are disabled; use sandboxed command searches in Auto/Full modes. Its file tools use the same real-path check with the same concurrent-symlink race limitation as Claude Code. It launches from a private read-only project so configuration left in the shared workspace cannot run as agy hooks or MCP servers; writes to `.agents/`, `AGENTS.md` and `GEMINI.md` are also refused by file tools. Clipboard and other macOS services remain reachable from the command sandbox. The Settings menu still labels Antigravity tiers as running without native tools; this opt-in capability is checked by the runner, and helper document reads on Antigravity still run without code.
|
|
155
166
|
|
|
156
|
-
Each run (a root thread and its children) works in one private directory under `--directory` (`~/.specset/personal-runs/<runner>/runs/<thread>/` by default) with `specs/`, `drawings/`, `docs/`, `inputs/` and `out/`. It starts empty
|
|
167
|
+
Each run (a root thread and its children) works in one private directory under `--directory` (`~/.specset/personal-runs/<runner>/runs/<thread>/` by default) with `specs/`, `drawings/`, `docs/`, `inputs/` and `out/`. It starts empty and has no network, so the harness cannot download into it: project material comes through Specset tools, and the model writes what it needs from their results into the workspace. On Antigravity, attachments and file results are saved separately in private read-only input storage at the absolute paths supplied to the model; copy them into the workspace before editing. Findings cite the Specset link, never the local path.
|
|
157
168
|
|
|
158
169
|
With Native tools on and Permissions Auto or Full, the vision tier's document and drawing reads (`examineDrawingSheets`, `examineDocument`, `examineFile` and the like) may also run code to look closer at their attachments: crop a dense detail from the full-resolution file and view it enlarged. Such a read runs in its own scratch directory holding only its attachments (`inputs/attachment-N.png|pdf`), never the run workspace. Its commands are sandboxed: no network, no credentials in their environment, and nothing readable but the system, developer tools, Homebrew (for Python and Pillow) and that directory. Your own temp directory, where other applications keep files, stays closed except, under Claude Code, for the scratch files macOS's `sips` saves there; the shared `/tmp` stays closed. Claude Code's command sandbox denies reading by default, a hook holds its Read tool to the directory, and zsh skips your startup files; Codex runs under a permissions profile that adds only Homebrew and its own binary, with the core environment, so it crops with Pillow and `sips` fails; the runner first checks once that the binary enforces the profile (Codex before 0.157.1 ignores its deny entries), and otherwise runs these reads without code. Code execution needs macOS: on Linux, Claude Code's bubblewrap sandbox leaves the credentials it removes readable to commands through its own process, so these reads run without code there. Its actions are printed in the `agent serve` terminal, because a helper read's actions never reach the conversation. These reads were evaluated on the cloud models without code execution, so treat code-execution results as unevaluated.
|
|
159
170
|
|
|
@@ -163,17 +174,17 @@ Sessions are ephemeral (`persistSession: false` for Claude, `ephemeral: true` fo
|
|
|
163
174
|
|
|
164
175
|
### Serve multiple local threads
|
|
165
176
|
|
|
166
|
-
Connect to an organization with `specset agent serve --host <host> --org <slug|id>`; no project or thread is required. Enable **Execute locally** in conversation Settings. A sole eligible connected runner automatically receives your queued local request; with multiple connected runners, choose **Machine** in Settings. The connection command disappears once a machine is connected. Completed, stopped, or expired runs offer **Start a new local run** above the composer; viewing them does not restart them. `--thread <id>` immediately assigns an existing root; `--message "Review electrical coordination" --type
|
|
177
|
+
Connect to an organization with `specset agent serve --host <host> --org <slug|id>`; no project or thread is required. Enable **Execute locally** in conversation Settings. A sole eligible connected runner automatically receives your queued local request; with multiple connected runners, choose **Machine** in Settings. The connection command disappears once a machine is connected. Completed, stopped, or expired runs offer **Start a new local run** above the composer; viewing them does not restart them. `--thread <id>` immediately assigns an existing root; `--message "Review electrical coordination" --type DesignQualityReview` creates a local root in the selected project. `--org <slug|id>` pins the org; otherwise the selected org or sole membership on that host is used. Omit `--project` for organization scope, or supply a comma-separated project allowlist. A new message-created thread requires exactly one project. It serves from the accounts `specset harness list` shows (config.yml's `runner:` block, or the logins this shell would use when none are listed); `specset harness add claude2 --harness claude --home ~/.claude2 --preferred` adds another login, and `--account` limits one run. Claude uses the CLI’s bundled SDK runtime, then native `claude` on PATH. Codex uses `CODEX_PATH`, native `codex` on PATH, then the managed native runtime from `specset harness install codex`. Startup checks each account's runtime before registering. Run a separate `serve --host <deployment>` process for each dev worktree. Each process prints and pins its host/org/accounts at startup; later CLI host/org changes cannot redirect it. Login and selected org are stored per host; execution limits apply per runner. For a bounded smoke test, add `--stopAfterIssues 2`; the root counts issues across its children and stops that tree when the threshold is reached (an in-flight batch can exceed it). Only this signed-in user can assign work, and children inherit the same machine, project scope, deadline and thread budget.
|
|
167
178
|
|
|
168
179
|
`agent serve` uses server-owned orchestration: Temporal executes the agent and its tools, while model requests run through the connected machine. This includes generic drawing-coordination assessments, image inputs and structured output. The runner keeps a native session alive across each complete parallel tool batch and sends back the server's authorized results. All Specset approvals remain in the web app; native tools follow the thread's **Native tools** setting (see above) and native delegation stays off. Independent model requests use independent sessions. Claude uses `persistSession: false`; Codex uses `ephemeral: true`.
|
|
169
180
|
|
|
170
|
-
For an unattended kickoff, `--autoApprovePlan` skips the plan sign-off and `--autoApprove` approves Specset tools for that message (default off for `serve`). `--isolateIssues` ignores pre-existing project issues when planning a bounded test. The command prints the created or assigned thread link. File attachments and file tool results preserve bytes, MIME type and optional filename through the same transport. Claude receives raw PDFs; Codex receives server-rendered PDF page images, capped at 20 pages per request across all PDFs. Both support image inputs and UTF-8 text attachments. Other binary formats fail explicitly until a compatible reader or conversion is available.
|
|
181
|
+
For an unattended kickoff, `--autoApprovePlan` skips the plan sign-off and `--autoApprove` approves Specset tools for that message (default off for a thread `serve` creates; with `--thread`, omit it to keep the thread's setting). `--isolateIssues` ignores pre-existing project issues when planning a bounded test. The command prints the created or assigned thread link. File attachments and file tool results preserve bytes, MIME type and optional filename through the same transport. Claude receives raw PDFs; Codex receives server-rendered PDF page images, capped at 20 pages per request across all PDFs. Both support image inputs and UTF-8 text attachments. Other binary formats fail explicitly until a compatible reader or conversion is available.
|
|
171
182
|
|
|
172
|
-
Pin both `--host <deployment>` and `--org <slug>` when using `agent chat`, `agent start`, `agent show`, `agent inspect`, `agent approve` or `agent
|
|
183
|
+
Pin both `--host <deployment>` and `--org <slug>` when using `agent chat`, `agent start`, `agent show`, `agent inspect`, `agent approve`, `agent stop` or `agent workers`; these overrides do not change the CLI’s saved selection.
|
|
173
184
|
|
|
174
|
-
Every model call carries its routing tier (conversation, orchestrator, planner, analyst, worker, lite, balanced, query, vision), and each tier names a model that any account whose harness offers it serves. The defaults match the app; `specset harness list` prints each tier's current model and the class it runs as. Antigravity is opt-in: it serves those tiers only after you add an account for it (`specset harness add agy --harness antigravity`); until then they run their model's class on Claude Code or Codex, or in the cloud with `harness fallback`. A model no account offers runs as its class (frontier, standard, fast) on a harness the machine has. Change a tier with `specset harness tier <tier> <model[:effort]>`, a class's model with `specset harness model <harness> <class> <model> [--effort e]`, or one run's with `agent serve --tier conversation=opus`. The conversation's Settings pins a class or one of the reported models per harness for that thread's own steps, plus a reasoning effort; children and helper calls keep the tiers. When the preferred accounts for a model are at their limits, `specset harness failover sameModel|any|off` decides where its calls go (default sameModel: other accounts with the same model; any: also the same class elsewhere); a call that already streamed fails instead and the next one goes elsewhere. A provider rate limit (HTTP 429) is waited out rather than failing the run: the server retries the call once the account's short wait passes (on another account if one has room), unless it already ran a native command or edit, which a retry could repeat; only every account being at its usage limit stops the run. `specset harness fallback <tier|all> cloud` (or `agent serve --fallback analyst=cloud`) sends a tier's calls to Specset's cloud model, billed to the organization, instead of another provider's whenever no account here can serve its model (none offers it, or its accounts are at their limits); `local` is the default. `--concurrency` defaults to 5 (at most 16) and limits active model calls; tool and approval waits release their slot so even 1 can complete nested fan-out. `--maxThreads` defaults to 100 and `--maxRunSeconds` to 3600 per root authorization. `specset harness limits --concurrency 16 --maxThreads 300 --maxRunSeconds 43200` saves them in config.yml for every `agent serve` (a flag still wins for one run; `default` clears one; no flags prints them). Specset’s `--autoApprove` message setting controls tool approval; the thread's Permissions setting bounds native tools when they are on. Ctrl-C revokes the registration and stops queued/running descendants. A restart needs a new assignment. A lost native continuation fails visibly rather than replaying writes or switching to a cloud model.
|
|
185
|
+
Every model call carries its routing tier (conversation, orchestrator, planner, analyst, worker, drafter, lite, balanced, query, vision), and each tier names a model that any account whose harness offers it serves. The defaults match the app; `specset harness list` prints each tier's current model and the class it runs as. Antigravity is opt-in: it serves those tiers only after you add an account for it (`specset harness add agy --harness antigravity`); until then they run their model's class on Claude Code or Codex, or in the cloud with `harness fallback`. A model no account offers runs as its class (frontier, standard, fast) on a harness the machine has. Change a tier with `specset harness tier <tier> <model[:effort]>`, a class's model with `specset harness model <harness> <class> <model> [--effort e]`, or one run's with `agent serve --tier conversation=opus`. The conversation's Settings pins a class or one of the reported models per harness for that thread's own steps, plus a reasoning effort; children and helper calls keep the tiers. When the preferred accounts for a model are at their limits, `specset harness failover sameModel|any|off` decides where its calls go (default sameModel: other accounts with the same model; any: also the same class elsewhere); a call that already streamed fails instead and the next one goes elsewhere. A provider rate limit (HTTP 429) is waited out rather than failing the run: the server retries the call once the account's short wait passes (on another account if one has room), unless it already ran a native command or edit, which a retry could repeat; only every account being at its usage limit stops the run. A call whose input a harness refuses for its size (Codex caps a turn's input at 1,048,576 characters) is never retried there: it moves to another harness the failover setting allows, else to its tier's cloud model when the tier falls back to the cloud, else fails naming the cause. `specset harness fallback <tier|all> cloud` (or `agent serve --fallback analyst=cloud`) sends a tier's calls to Specset's cloud model, billed to the organization, instead of another provider's whenever no account here can serve its model (none offers it, or its accounts are at their limits); `local` is the default. `--concurrency` defaults to 5 (at most 16) and limits active model calls; tool and approval waits release their slot so even 1 can complete nested fan-out. `--maxThreads` defaults to 100 and `--maxRunSeconds` to 3600 per root authorization. `specset harness limits --concurrency 16 --maxThreads 300 --maxRunSeconds 43200` saves them in config.yml for every `agent serve` (a flag still wins for one run; `default` clears one; no flags prints them). Specset’s `--autoApprove` message setting controls tool approval; the thread's Permissions setting bounds native tools when they are on. Ctrl-C revokes the registration and stops queued/running descendants. A restart needs a new assignment. A lost native continuation fails visibly rather than replaying writes or switching to a cloud model.
|
|
175
186
|
|
|
176
|
-
Harness tool callbacks can only wait for results supplied by the server. Each job receives private harness configuration and a minimal environment
|
|
187
|
+
Harness tool callbacks can only wait for results supplied by the server. Each job receives private harness configuration and a minimal environment, and no CLI login: a native session's commands reach no network, so project material comes through Specset tools. Claude shares the selected native credential store and its refresh lock. Codex uses an authentication-only native process to refresh the selected login, then supplies temporary access tokens to isolated inference sessions. Neither copies rotating refresh credentials into job homes. Settings, hooks, plugins and global MCP servers are excluded from inference sessions. Run `claude auth login` first if the harness reports authentication is required. The harness itself still runs as the local OS user, outside the seal its commands run in. Use a dedicated account or isolated host for untrusted project content; do not share this personal runner across users.
|
|
177
188
|
|
|
178
189
|
Use `specset agent runners list --json` to inspect your registrations.
|
|
179
190
|
Use `specset agent runners revoke <runner-id>` to stop and revoke one remotely, including an offline runner after a crash.
|
|
@@ -12,6 +12,8 @@ Run every operation with `specset api`. All ids are plain UUIDs. Inline enums, n
|
|
|
12
12
|
|
|
13
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
|
+
**Companies** belong to the organization's directory and are shared by its projects. A company works on a project through a **ProjectCompany** row, which holds that project's `role`, `notes`, `archived` state and closeout documentation. Assigning a directory company to a project record (an asset's responsible company, a part's vendor, a submittal's or RFI's responsible contractor) adds it to the project.
|
|
16
|
+
|
|
15
17
|
Maintenance has three layers:
|
|
16
18
|
|
|
17
19
|
- **Procedure** — reusable checklist content: ordered typed fields, descriptions, and source references.
|
|
@@ -53,6 +55,7 @@ Notable differences:
|
|
|
53
55
|
|
|
54
56
|
- Asset creation requires `projectId` and `locationId`. `paginatedAssets` accepts `search` and filters such as `statuses`, `locationIds`, `productIds`, `systemIds`, `responsibleCompanyIds`, `parentIds`, and `archived`.
|
|
55
57
|
- Warranty is asset-scoped; use `warrantiesByAsset(assetId)` to browse one asset's warranties.
|
|
58
|
+
- Company is org-wide: `paginatedCompanies(orgId: ...)` lists the directory and `searchCompanies(orgId, search)` finds a company by name. `paginatedProjectCompanies(projectId)` lists a project's companies with their `role`; `addCompaniesToProject(projectId, companyIds)`, `removeCompaniesFromProject(projectId, companyIds)` (refused while a project record still names the company) and `updateProjectCompany(input: { id, role, notes, archived })` manage the participation. `createCompany(input: { projectId, ... })` creates the directory company and puts it on that project.
|
|
56
59
|
- Location, Product, Company, System, and Asset updates support reversible `archived` state. Prefer archiving to deletion.
|
|
57
60
|
- Batch-delete availability varies. Introspect the mutation root rather than guessing an operation name.
|
|
58
61
|
|
|
@@ -175,7 +178,7 @@ specset api --query 'mutation { setProductDocumentRoles(productId: "<product-id>
|
|
|
175
178
|
|
|
176
179
|
Existing project Documents work too — a submittal attachment's `document { id }` (from `paginatedSubmittals … attachments { document { id title } }`) can be linked directly as `SubmittalPackage`/`Datasheet`; the only check is that the Document belongs to the same project. To bind a slice of a large file, `extractPagesFromFileResource(id: <fileResourceId>, pages: [3, 4, 5])` returns a new CloudFile to feed into `createDocumentsFromCloudFiles`. Only PDF files merge into the O&M package export; images and other types are listed as references.
|
|
177
180
|
|
|
178
|
-
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. Any later create/update/link on the entity (a new document pivot, a warranty document, a field edit) marks the documentation stale again, so when seeding a graph write documentation **last**. Calling `mark<Entity>DocumentationComplete` with an empty block sets the state to `NotApplicable`, which the package readiness report accepts — use it for entities that don't warrant prose rather than writing filler.
|
|
181
|
+
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. A company's block is per project: use `updateProjectCompanyDocumentation` / `markProjectCompanyDocumentationComplete` with the ProjectCompany id. Any later create/update/link on the entity (a new document pivot, a warranty document, a field edit) marks the documentation stale again, so when seeding a graph write documentation **last**. Calling `mark<Entity>DocumentationComplete` with an empty block sets the state to `NotApplicable`, which the package readiness report accepts — use it for entities that don't warrant prose rather than writing filler.
|
|
179
182
|
|
|
180
183
|
### Writing documentation that reads well in the O&M package export
|
|
181
184
|
|
|
@@ -324,7 +327,7 @@ For duplicate products P1 (loser) and P2 (survivor):
|
|
|
324
327
|
3. Re-run the filtered query and confirm zero remaining references.
|
|
325
328
|
4. Archive P1 with `updateProduct(input: { id: P1, archived: true })`.
|
|
326
329
|
|
|
327
|
-
Use the same impact-first pattern for duplicate companies,
|
|
330
|
+
Use the same impact-first pattern for duplicate locations and systems. Duplicate companies merge in one step: `mergeCompanies(targetId, sourceIds)` moves their projects, documents, record assignments, people and Procore links to the target, fills the target's blank profile fields from theirs (in `sourceIds` order), and archives the duplicates; it is not reversible, so confirm the pair first. `companyDuplicates(orgId)` lists likely pairs (names that differ only in case, punctuation or a legal form), and `companies(ids)` loads them side by side.
|
|
328
331
|
|
|
329
332
|
## Safety
|
|
330
333
|
|