orbitmap 0.4.2 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/README.md +112 -18
  2. package/dist/adapters/cloud.d.ts +63 -21
  3. package/dist/adapters/cloud.js +108 -29
  4. package/dist/adapters/cloud.js.map +1 -1
  5. package/dist/adapters/factory.d.ts +3 -0
  6. package/dist/adapters/factory.js +8 -1
  7. package/dist/adapters/factory.js.map +1 -1
  8. package/dist/adapters/local/adapter.d.ts +17 -20
  9. package/dist/adapters/local/adapter.js +28 -21
  10. package/dist/adapters/local/adapter.js.map +1 -1
  11. package/dist/adapters/local/context.d.ts +1 -1
  12. package/dist/adapters/local/context.js +10 -2
  13. package/dist/adapters/local/context.js.map +1 -1
  14. package/dist/adapters/local/entities/planning.d.ts +5 -1
  15. package/dist/adapters/local/entities/planning.js +16 -3
  16. package/dist/adapters/local/entities/planning.js.map +1 -1
  17. package/dist/adapters/local/entities/tasks.d.ts +9 -1
  18. package/dist/adapters/local/entities/tasks.js +47 -5
  19. package/dist/adapters/local/entities/tasks.js.map +1 -1
  20. package/dist/adapters/local/entities/work-items.d.ts +14 -30
  21. package/dist/adapters/local/entities/work-items.js +63 -47
  22. package/dist/adapters/local/entities/work-items.js.map +1 -1
  23. package/dist/adapters/local/rules.d.ts +4 -4
  24. package/dist/adapters/local/rules.js +4 -19
  25. package/dist/adapters/local/rules.js.map +1 -1
  26. package/dist/adapters/local/shapes.d.ts +27 -7
  27. package/dist/adapters/local/shapes.js +41 -5
  28. package/dist/adapters/local/shapes.js.map +1 -1
  29. package/dist/adapters/local/store.d.ts +55 -6
  30. package/dist/adapters/local/store.js +122 -8
  31. package/dist/adapters/local/store.js.map +1 -1
  32. package/dist/adapters/local/types.d.ts +11 -2
  33. package/dist/adapters/local/types.js +9 -2
  34. package/dist/adapters/local/types.js.map +1 -1
  35. package/dist/adapters/types.d.ts +30 -20
  36. package/dist/agent-instructions.d.ts +43 -12
  37. package/dist/agent-instructions.js +1133 -170
  38. package/dist/agent-instructions.js.map +1 -1
  39. package/dist/area-directories.d.ts +75 -0
  40. package/dist/area-directories.js +162 -0
  41. package/dist/area-directories.js.map +1 -0
  42. package/dist/area-grouping.d.ts +55 -0
  43. package/dist/area-grouping.js +72 -0
  44. package/dist/area-grouping.js.map +1 -0
  45. package/dist/commands/areas.js +30 -2
  46. package/dist/commands/areas.js.map +1 -1
  47. package/dist/commands/assign.d.ts +1 -0
  48. package/dist/commands/assign.js +2 -0
  49. package/dist/commands/assign.js.map +1 -1
  50. package/dist/commands/context.d.ts +95 -0
  51. package/dist/commands/context.js +104 -2
  52. package/dist/commands/context.js.map +1 -1
  53. package/dist/commands/create.d.ts +1 -0
  54. package/dist/commands/create.js +19 -14
  55. package/dist/commands/create.js.map +1 -1
  56. package/dist/commands/dep.js +5 -8
  57. package/dist/commands/dep.js.map +1 -1
  58. package/dist/commands/doc-import.d.ts +1 -0
  59. package/dist/commands/doc-import.js +8 -26
  60. package/dist/commands/doc-import.js.map +1 -1
  61. package/dist/commands/doc-patch.js +16 -52
  62. package/dist/commands/doc-patch.js.map +1 -1
  63. package/dist/commands/doc-share.js +4 -0
  64. package/dist/commands/doc-share.js.map +1 -1
  65. package/dist/commands/doc-update.js +4 -8
  66. package/dist/commands/doc-update.js.map +1 -1
  67. package/dist/commands/doc.js +7 -12
  68. package/dist/commands/doc.js.map +1 -1
  69. package/dist/commands/docs-cache.js +23 -10
  70. package/dist/commands/docs-cache.js.map +1 -1
  71. package/dist/commands/docs.js +29 -10
  72. package/dist/commands/docs.js.map +1 -1
  73. package/dist/commands/get.js +8 -10
  74. package/dist/commands/get.js.map +1 -1
  75. package/dist/commands/ideas.d.ts +3 -1
  76. package/dist/commands/ideas.js +26 -49
  77. package/dist/commands/ideas.js.map +1 -1
  78. package/dist/commands/init.d.ts +24 -1
  79. package/dist/commands/init.js +0 -0
  80. package/dist/commands/init.js.map +1 -1
  81. package/dist/commands/intent.d.ts +16 -0
  82. package/dist/commands/intent.js +61 -18
  83. package/dist/commands/intent.js.map +1 -1
  84. package/dist/commands/issues.d.ts +3 -1
  85. package/dist/commands/issues.js +47 -72
  86. package/dist/commands/issues.js.map +1 -1
  87. package/dist/commands/log.d.ts +14 -2
  88. package/dist/commands/log.js +35 -20
  89. package/dist/commands/log.js.map +1 -1
  90. package/dist/commands/mission.d.ts +8 -0
  91. package/dist/commands/mission.js +58 -18
  92. package/dist/commands/mission.js.map +1 -1
  93. package/dist/commands/orbit.d.ts +7 -1
  94. package/dist/commands/orbit.js +13 -24
  95. package/dist/commands/orbit.js.map +1 -1
  96. package/dist/commands/setup-agent.d.ts +97 -4
  97. package/dist/commands/setup-agent.js +273 -40
  98. package/dist/commands/setup-agent.js.map +1 -1
  99. package/dist/commands/setup-mcp.d.ts +2 -0
  100. package/dist/commands/setup-mcp.js +17 -40
  101. package/dist/commands/setup-mcp.js.map +1 -1
  102. package/dist/commands/start.d.ts +43 -0
  103. package/dist/commands/start.js +158 -3
  104. package/dist/commands/start.js.map +1 -1
  105. package/dist/commands/status.d.ts +1 -0
  106. package/dist/commands/status.js +5 -15
  107. package/dist/commands/status.js.map +1 -1
  108. package/dist/commands/subtask.d.ts +1 -0
  109. package/dist/commands/subtask.js +9 -2
  110. package/dist/commands/subtask.js.map +1 -1
  111. package/dist/commands/task-edit-content.js +4 -8
  112. package/dist/commands/task-edit-content.js.map +1 -1
  113. package/dist/commands/task.d.ts +7 -0
  114. package/dist/commands/task.js +5 -2
  115. package/dist/commands/task.js.map +1 -1
  116. package/dist/commands/tasks.d.ts +35 -0
  117. package/dist/commands/tasks.js +45 -9
  118. package/dist/commands/tasks.js.map +1 -1
  119. package/dist/commands/vibes.d.ts +3 -1
  120. package/dist/commands/vibes.js +41 -31
  121. package/dist/commands/vibes.js.map +1 -1
  122. package/dist/config.d.ts +11 -0
  123. package/dist/config.js +14 -1
  124. package/dist/config.js.map +1 -1
  125. package/dist/doc-cache.d.ts +74 -5
  126. package/dist/doc-cache.js +106 -22
  127. package/dist/doc-cache.js.map +1 -1
  128. package/dist/enums.d.ts +26 -0
  129. package/dist/enums.js +53 -0
  130. package/dist/enums.js.map +1 -0
  131. package/dist/errors.d.ts +67 -0
  132. package/dist/errors.js +82 -0
  133. package/dist/errors.js.map +1 -1
  134. package/dist/index.js +169 -60
  135. package/dist/index.js.map +1 -1
  136. package/dist/mcp-config.d.ts +42 -1
  137. package/dist/mcp-config.js +82 -2
  138. package/dist/mcp-config.js.map +1 -1
  139. package/dist/oauth.js +10 -10
  140. package/dist/output.d.ts +12 -0
  141. package/dist/output.js +39 -1
  142. package/dist/output.js.map +1 -1
  143. package/dist/paths.d.ts +40 -0
  144. package/dist/paths.js +174 -9
  145. package/dist/paths.js.map +1 -1
  146. package/dist/project-config.d.ts +42 -1
  147. package/dist/project-config.js +51 -0
  148. package/dist/project-config.js.map +1 -1
  149. package/dist/work-log.d.ts +104 -0
  150. package/dist/work-log.js +181 -0
  151. package/dist/work-log.js.map +1 -0
  152. package/dist/workspace-resolve.d.ts +179 -9
  153. package/dist/workspace-resolve.js +288 -20
  154. package/dist/workspace-resolve.js.map +1 -1
  155. package/dist/write-target.d.ts +56 -0
  156. package/dist/write-target.js +170 -0
  157. package/dist/write-target.js.map +1 -0
  158. package/package.json +3 -2
  159. package/dist/commands/create-project.d.ts +0 -6
  160. package/dist/commands/create-project.js +0 -28
  161. package/dist/commands/create-project.js.map +0 -1
  162. package/dist/commands/projects.d.ts +0 -3
  163. package/dist/commands/projects.js +0 -28
  164. package/dist/commands/projects.js.map +0 -1
package/README.md CHANGED
@@ -94,7 +94,8 @@ for the local path.
94
94
  missions/MS-<num>.md
95
95
  intents/IN-<num>.md
96
96
  ideas/ID-<num>.md
97
- documents/<slug>.md
97
+ docs/<slug>.md # one copy of each document, same path and format as the
98
+ # cloud doc cache (`<project>/.orbitmap/docs/`)
98
99
  issues/IS-<num>.md # workspace-level issues (no area)
99
100
  areas/
100
101
  <area-slug>/
@@ -194,10 +195,15 @@ pass `--type task|issue|idea|intent` for bare UUIDs and bare codes.
194
195
 
195
196
  | Command | Description |
196
197
  |---------|-------------|
197
- | `orbitmap log <id-or-number> <message>` | Log work on a task |
198
+ | `orbitmap log <id-or-number> <message>` | Log work on a task, intent or mission |
199
+
200
+ The target follows from the reference: `TS-…` writes against the task, `IN-…` against the
201
+ intent, `MS-…` against the mission. A bare UUID or bare code means a **task** unless
202
+ `--target` says otherwise.
198
203
 
199
204
  **Options:**
200
- - `--type <type>` — Log type: `note` (default), `code_change`, `decision`, `blocker`, `discovery`, `exploration`, `fix`
205
+ - `--type <type>` — Log type: `note` (default), `code_change`, `decision`, `blocker`. For `discovery`, `exploration` and `fix` use `orbitmap vibe log` — those are vibe types, not work-log types. `status_change` is written by the server on a status change and cannot be authored by a client.
206
+ - `--target <kind>` — Which object a bare UUID names: `task` (default), `intent`, `mission`
201
207
  - `--meta <json>` — JSON metadata, e.g. `'{"files":["src/app.ts"]}'`
202
208
 
203
209
  **Examples:**
@@ -205,8 +211,25 @@ pass `--type task|issue|idea|intent` for bare UUIDs and bare codes.
205
211
  orbitmap log abc123 "Implemented user auth endpoint" --type code_change
206
212
  orbitmap log abc123 "Using JWT over sessions for stateless auth" --type decision
207
213
  orbitmap log abc123 "Blocked on missing API credentials" --type blocker
214
+ orbitmap log IN-a1b2c3 "Chose 3 FKs + a CHECK over a polymorphic target" --type decision
215
+ orbitmap log MS-a1b2c3 "Mission scope trimmed to the two gating intents" --type note
208
216
  ```
209
217
 
218
+ #### Work log entries on a status change
219
+
220
+ `intent update`, `mission update` and `mission status` take a **repeatable** `--log
221
+ <type>:<content>`, sent as one batch with the update. The content is split on the FIRST
222
+ colon only, so it may contain colons of its own.
223
+
224
+ ```bash
225
+ orbitmap intent update IN-a1b2c3 --status plan \
226
+ --log decision:"chose 3 FKs + CHECK" \
227
+ --log note:"resume: CLI next"
228
+ ```
229
+
230
+ The API **requires at least one entry when the status changes** and writes nothing at all
231
+ when there is none — so a rejected transition is re-run with `--log`, not retried as-is.
232
+
210
233
  ### Documents
211
234
 
212
235
  | Command | Description |
@@ -287,8 +310,8 @@ Workspace-level desired outcomes. Tasks and issues hang off an intent; an intent
287
310
  |---------|-------------|
288
311
  | `orbitmap intents` | List intents (shortcut for `intent list`) |
289
312
  | `orbitmap intent show <id-or-number>` | Show an intent (UUID or `IN-xxxxxx`) |
290
- | `orbitmap intent create <title>` | Create an intent (`--outcome`, `--status`, `--orbit`) |
291
- | `orbitmap intent update <id>` | Update title, outcome, or status |
313
+ | `orbitmap intent create <title>` | Create an intent (`--outcome`, `--design`, `--plan`, `--status`, `--orbit`) |
314
+ | `orbitmap intent update <id>` | Update title, outcome, design, plan, or status (`--log <type>:<content>`, repeatable) |
292
315
  | `orbitmap intent link <id> --type task\|issue --id <uuid>` | Link a task or issue |
293
316
  | `orbitmap intent unlink <id> --type task\|issue --id <uuid>` | Unlink a task or issue |
294
317
  | `orbitmap intent orbit <id> [orbit-id]` | Set the intent's orbit (`--clear` to remove) |
@@ -298,9 +321,16 @@ Workspace-level desired outcomes. Tasks and issues hang off an intent; an intent
298
321
  ```bash
299
322
  orbitmap intent create "Cut onboarding time in half" \
300
323
  --outcome "New users reach first value in under 5 minutes" \
324
+ --design "Two-step wizard replacing the current five-step form." \
325
+ --plan "Phase 1: wizard skeleton. Phase 2: inline validation." \
301
326
  --status design --orbit growth-q3
302
327
 
303
328
  orbitmap intent link IN-a1b2c3 --type task --id <task-uuid>
329
+
330
+ # A status change carries its reason — the API rejects it (422) without at least one entry.
331
+ orbitmap intent update IN-a1b2c3 --status plan \
332
+ --log decision:"chose 3 FKs + CHECK" \
333
+ --log note:"resume: CLI next"
304
334
  ```
305
335
 
306
336
  `intent create --orbit` is create-then-attach. If the attach step fails the intent is still created — the CLI reports the failure and does not retry.
@@ -314,8 +344,8 @@ Time-boxed workspace focus. Only one mission can be `active` at a time.
314
344
  | `orbitmap missions` | List missions (shortcut for `mission list`) |
315
345
  | `orbitmap mission show <id-or-number>` | Show a mission (UUID or `MS-xxxxxx`) |
316
346
  | `orbitmap mission create <title>` | Create a mission (`--desc`, `--outcome`, `--end-date`) |
317
- | `orbitmap mission update <id>` | Update title, description, outcome, or end date |
318
- | `orbitmap mission status <id> <status>` | Set lifecycle status (`--move-to` to relocate unfinished work) |
347
+ | `orbitmap mission update <id>` | Update title, description, outcome, or end date (`--log <type>:<content>`, repeatable) |
348
+ | `orbitmap mission status <id> <status>` | Set lifecycle status (`--move-to` to relocate unfinished work; `--log` required by the API) |
319
349
  | `orbitmap mission link <id> --type <t> --id <uuid>` | Link a task, issue, idea, or intent |
320
350
  | `orbitmap mission unlink <id> --type <t> --id <uuid>` | Unlink a directly-linked item |
321
351
 
@@ -369,7 +399,7 @@ orbitmap status abc123 done --delivery released
369
399
 
370
400
  | Command | Description |
371
401
  |---------|-------------|
372
- | `orbitmap init` | Full setup — mode, then credentials or workspace, then the agent skills (`--key`, `--area`, `--profile`, `--advanced`, `--global`; `--local`, `--workspace`, `--workspace-name`; `--agent <type>`, `--no-skills`) |
402
+ | `orbitmap init` | Full setup — mode, then the workspace + its area directories (cloud) or a local workspace, then `.mcp.json` and the agent skills (`--key`, `--workspace-id`, `--area`, `--profile`, `--allow-nested`, `--advanced`, `--global`; `--local`, `--workspace`, `--workspace-name`; `--agent <type>`, `--no-skills`) |
373
403
  | `orbitmap login` / `orbitmap logout` | Browser-based OAuth login / token revocation |
374
404
  | `orbitmap setup-agent` | Refresh the installed instructions (after a CLI upgrade) or add another agent type — `init` installs them once already |
375
405
  | `orbitmap setup-mcp` | Write/merge an `orbitmap` entry into `.mcp.json` (no API calls) |
@@ -390,11 +420,15 @@ that, see the ADR's Consequences section).
390
420
 
391
421
  ### Setup
392
422
 
393
- Run `npx orbitmap init` for interactive setup. For cloud mode, it writes
394
- `<project>/.orbitmap/config.json` (committable, no secrets) by default; pass `--global` to
395
- write `~/.orbitmap/config.json` instead (a machine-wide default, like `git config --global`).
396
- The API key always goes to `~/.orbitmap/credentials.json`, under a named **agent profile**
397
- never into a config file, either way.
423
+ Run `npx orbitmap init` for interactive setup. Cloud setup is **workspace-first**: the key is
424
+ validated with `GET /areas`, you pick the workspace (`--workspace-id` in a script), and `init`
425
+ works out which directory holds which area — by directory name, by the `origin` git remote
426
+ matched against the area's repository URL, by the slugified name, or by asking. It writes
427
+ `<root>/.orbitmap/config.json` (committable, no secrets) by default; pass `--global` to write
428
+ `~/.orbitmap/config.json` instead (a machine-wide default, like `git config --global`). The API
429
+ key always goes to `~/.orbitmap/credentials.json`, under a named **agent profile** — never into
430
+ a config file, either way. Re-running `init` merges, so running it inside a newly cloned area
431
+ adds that area to the workspace map that already exists above it.
398
432
 
399
433
  ### Project config file — `<project>/.orbitmap/config.json`
400
434
 
@@ -403,15 +437,22 @@ never into a config file, either way.
403
437
  "schema_version": 1,
404
438
  "mode": "cloud",
405
439
  "agent": "orbitmap-app-bot",
406
- "area": "my-area-slug"
440
+ "workspace": "my-workspace",
441
+ "areas": { "orbitmap-cli": "orbitmap-cli", "packages/api": "api" }
407
442
  }
408
443
  ```
409
444
 
410
445
  `mode` is `"cloud"` (the default when the key is absent) or `"local"`. `agent` names a
411
446
  profile in `~/.orbitmap/credentials.json` — the project says *which* agent to use, the
412
- credentials file (below) holds *that agent's* key. This file is meant to be committed: a
413
- team shares the `agent` name and `area` pinning through version control, and each member's
414
- own copy of `credentials.json` supplies their own agent's key.
447
+ credentials file (below) holds *that agent's* key. `workspace` is a workspace slug or UUID,
448
+ never a path. `areas` maps directories (relative to this file's parent; `"."` is that
449
+ directory) to area slugs, which is how one root can hold several areas; each mapped
450
+ subdirectory also gets a marker config of its own holding nothing but `{"area": "<slug>"}`.
451
+ The older single `area` field is still read for back-compat. This file is meant to be
452
+ committed: a team shares the workspace, the `agent` name and the area layout through version
453
+ control, and each member's own copy of `credentials.json` supplies their own agent's key.
454
+ Areas living **outside** the tree cannot be expressed portably, so they go to the gitignored
455
+ `<root>/.orbitmap/link.json` instead (`"areas": { "<slug>": "/abs/path" }`).
415
456
 
416
457
  ### Global config file — `~/.orbitmap/config.json`
417
458
 
@@ -528,13 +569,66 @@ npx orbitmap setup-agent # auto-detects your agent
528
569
  npx orbitmap setup-agent --agent claude
529
570
  ```
530
571
 
572
+ ### Where it installs: `--scope user` (global) vs `--scope project`
573
+
574
+ Run interactively, both commands **ask** where the skills should go — global (your home directory)
575
+ or project (this directory only) — right after the agent-type question. Global is recommended and
576
+ is what Enter picks; `--scope` skips the question; a scripted run takes global without prompting
577
+ and says so in its output.
578
+
579
+ Global (user) scope means `~/.claude/skills/` and `~/.claude/CLAUDE.md`
580
+ for Claude Code, `~/.gemini/GEMINI.md` and `~/.codex/AGENTS.md` for Gemini and Codex. Agent skills
581
+ do **not** traverse directories: a project-level `.claude/skills/` is invisible to a session
582
+ started in a sibling or child repository, while `CLAUDE.md` *is* inherited downwards. A user-scope
583
+ install is the only one that is consistent everywhere — and it matters now that one workspace
584
+ routinely spans several area directories, where a single project install would cover just one.
585
+
586
+ A user-scope install writes **nothing** into your project directory.
587
+
588
+ ```bash
589
+ npx orbitmap setup-agent --scope project # this directory only (the old behaviour)
590
+ npx orbitmap init --agent claude --scope project
591
+ ```
592
+
593
+ At project scope the hook says so explicitly, so an agent that inherits `CLAUDE.md` from a parent
594
+ directory but cannot load the skills knows why, and knows to re-run with `--scope user`.
595
+
596
+ **Upgrading from an older setup?** If you already have skills in `./.claude/skills/`, that project
597
+ copy silently wins in this directory. A user-scope install detects it and names the paths; it
598
+ offers to clean up when run interactively, and otherwise tells you to run:
599
+
600
+ ```bash
601
+ npx orbitmap setup-agent --agent claude --scope user --clean-project
602
+ ```
603
+
604
+ Cleanup removes only the six OrbitMap skill directories and OrbitMap's own marker block — never
605
+ your own skills, never unrelated `CLAUDE.md` content, never a whole `.claude` directory.
606
+
531
607
  Either way it installs two layers, mirroring the OrbitMap MCP server:
532
608
 
533
609
  - A **thin always-on hook** (~10 lines) in your agent config (`CLAUDE.md`, `AGENTS.md`, …) so the agent always knows the project uses OrbitMap and knows the workflow skeleton.
534
- - The **deep, on-demand instructions** — full parity with the MCP `full` profile. For **Claude Code** these are installed as loaded-on-demand skills (`.claude/skills/orbitmap`, `orbitmap-working`, `orbitmap-planning`, `orbitmap-docs`, zero per-turn context cost); for other agents they're inlined as a markdown fallback.
610
+ - The **deep, on-demand instructions** — full parity with the MCP `full` profile (plus `orbitmap-brainstorm` and `orbitmap-design`, CLI-native, no MCP counterpart). For **Claude Code** these are installed as loaded-on-demand skills (`orbitmap`, `orbitmap-execute`, `orbitmap-plan`, `orbitmap-brainstorm`, `orbitmap-design`, `orbitmap-docs`, zero per-turn context cost) under `~/.claude/skills/` (or `./.claude/skills/` at project scope); for other agents they're inlined as a markdown fallback.
611
+
612
+ A skill is not always a single file: `orbitmap-execute` ships `SKILL.md` **plus**
613
+ `references/worker-contract.md` (the rules a dispatched worker subagent follows). `init` and
614
+ `setup-agent` install every file a skill carries, and refresh them all on a re-run.
535
615
 
536
616
  The instructions are mode-agnostic: they reference CLI commands only, so the same skills drive an agent whether the data goes to the cloud or to local files.
537
617
 
618
+ ### Executing work: two flows
619
+
620
+ The installed `orbitmap-execute` skill covers two ways of working, chosen by what you ask for:
621
+
622
+ - **"work on TS-x"** runs a single tracked task — start or resume it, implement, log, and
623
+ finish with the correct status (`in_review` by default, so a human verifies before closing).
624
+ - **"execute IN-x"** delivers a **whole planned intent** in the background: tasks are built by
625
+ parallel workers in dependency-ordered waves, each task gated by its own tests, then the
626
+ intent-level test and an impact pass diffed against a test baseline captured before the run,
627
+ with a goal-based fix loop (no retry cap) until the gates are green. A stuck task ends
628
+ `blocked` with a logged blocker while the rest of the intent completes. The run ends with
629
+ every completed task `in_review` on an `intent/<number>-<slug>` branch — **merging is left
630
+ to you**: the run never merges and never pushes to the default branch.
631
+
538
632
  See [Agent Instructions](docs/agent-instructions.md) for details.
539
633
 
540
634
  ## Requirements
@@ -1,17 +1,40 @@
1
1
  import { type OrbitMapConfig } from '../config.js';
2
+ import type { WorkLogEntry } from '../work-log.js';
2
3
  import type { WorkspaceAdapter } from './types.js';
3
4
  export declare class CloudAdapter implements WorkspaceAdapter {
4
5
  private readonly apiKey;
5
6
  private readonly baseUrl;
6
7
  private readonly area?;
8
+ /**
9
+ * Workspace **identity** (slug or uuid, never a path) from the separate workspace ladder
10
+ * (`--workspace-id` → `ORBITMAP_WORKSPACE_ID` → project config `workspace` → global config
11
+ * `workspace`; see `resolveWorkspaceId`). Independent of {@link CloudAdapter.area}: either,
12
+ * both or neither may be set.
13
+ */
14
+ private readonly workspace?;
7
15
  /**
8
16
  * Task reference → uuid, for the lifetime of this adapter. A command that touches the same
9
17
  * task twice (`dep add TS-a TS-b` resolves two, `status` then reads back) pays for one
10
18
  * `/tasks/by-number/{n}` lookup per distinct reference, not per call site.
11
19
  */
12
20
  private readonly taskIdCache;
13
- constructor(apiKey: string, area?: string, apiUrl?: string);
21
+ constructor(apiKey: string, area?: string, apiUrl?: string, workspace?: string);
14
22
  private request;
23
+ /**
24
+ * Add the client half of the story to the workspace-context errors the server can raise.
25
+ * The API knows what it received; only the CLI knows what it *sent* and which knobs could
26
+ * change that, and a bare "workspace context is required" is unactionable without it.
27
+ * Every other error is returned untouched.
28
+ *
29
+ * `AREA_CONTEXT_REQUIRED` is here too (TS-jt6q36 follow-up). The CLI raises that code
30
+ * itself — with the areas of the current workspace spelled out — but the *server* raises
31
+ * it as well, in HTTP terms ("set the X-Orbitmap-Area header. Call GET /api/agent/areas"),
32
+ * and that text used to reach the terminal verbatim: identical situation, two different
33
+ * answers depending on which side noticed first. The client-side advice is now attached to
34
+ * both, and the server's own sentence is kept (parenthesised) rather than dropped, because
35
+ * it is the only part that says what the API actually objected to.
36
+ */
37
+ private explainContextError;
15
38
  getContext(areaId?: string): Promise<unknown>;
16
39
  /**
17
40
  * The area this client was constructed with (`--area` → `ORBITMAP_AREA` →
@@ -20,6 +43,12 @@ export declare class CloudAdapter implements WorkspaceAdapter {
20
43
  * `workspace-resolve.ts`, applied by `createClient`). No request is made.
21
44
  */
22
45
  getDefaultArea(): Promise<string | undefined>;
46
+ /**
47
+ * The workspace identity this client sends as `X-Orbitmap-Workspace`, or `undefined` when
48
+ * none resolved (in which case the server derives one from the area or auto-detects it).
49
+ * No request is made.
50
+ */
51
+ getWorkspaceId(): string | undefined;
23
52
  getTasks(options?: {
24
53
  status?: string;
25
54
  priority?: string;
@@ -78,6 +107,8 @@ export declare class CloudAdapter implements WorkspaceAdapter {
78
107
  }): Promise<unknown>;
79
108
  removeTaskDependency(taskId: string, targetTaskId: string, type?: string): Promise<unknown>;
80
109
  logWork(taskId: string, type: string, content: string, metadata?: Record<string, unknown>): Promise<unknown>;
110
+ logIntentWork(idOrNumber: string, type: string, content: string, metadata?: Record<string, unknown>): Promise<unknown>;
111
+ logMissionWork(idOrNumber: string, type: string, content: string, metadata?: Record<string, unknown>): Promise<unknown>;
81
112
  getDocuments(options?: {
82
113
  type?: string;
83
114
  search?: string;
@@ -192,28 +223,21 @@ export declare class CloudAdapter implements WorkspaceAdapter {
192
223
  severity?: string;
193
224
  limit?: string;
194
225
  }): Promise<unknown>;
226
+ /**
227
+ * `GET /issues/{id_or_number}` — the single issue-read route. It accepts a uuid, a bare
228
+ * six-char code or the prefixed `IS-xxxxxx` form; the server strips the prefix and resolves
229
+ * the number workspace-scoped. There is no `/issues/by-number/{n}` route — never add one.
230
+ */
195
231
  getIssue(idOrNumber: string): Promise<unknown>;
196
- getIssueByNumber(issueNumber: string): Promise<unknown>;
197
- resolveIssueByNumber(issueNumber: string, data: {
198
- status: string;
199
- resolution?: string;
200
- resolved_by_task_id?: string;
201
- }): Promise<unknown>;
202
- resolveIssue(idOrNumber: string, data: {
203
- status: string;
204
- resolution?: string;
205
- resolved_by_task_id?: string;
206
- }): Promise<unknown>;
232
+ /**
233
+ * `PATCH /issues/{id_or_number}/status` — the only issue-write route besides `/link` and
234
+ * `/assign`. `issue resolve` rides on it too: the API has no `/issues/{id}/resolve`.
235
+ */
207
236
  updateIssueStatus(idOrNumber: string, data: {
208
237
  status: string;
209
238
  resolution?: string;
210
239
  resolved_task_id?: string;
211
240
  }): Promise<unknown>;
212
- updateIssueStatusByNumber(issueNumber: string, data: {
213
- status: string;
214
- resolution?: string;
215
- resolved_task_id?: string;
216
- }): Promise<unknown>;
217
241
  logVibe(data: {
218
242
  content: string;
219
243
  type: string;
@@ -237,20 +261,21 @@ export declare class CloudAdapter implements WorkspaceAdapter {
237
261
  priority?: string;
238
262
  limit?: string;
239
263
  }): Promise<unknown>;
264
+ /**
265
+ * `GET /ideas/{id_or_number}` — accepts a uuid, a bare code or `ID-xxxxxx`; the server
266
+ * strips the prefix and resolves the number. There is no `/ideas/by-number/{n}` route.
267
+ */
240
268
  getIdea(idOrNumber: string): Promise<unknown>;
241
- getIdeaByNumber(ideaNumber: string): Promise<unknown>;
242
269
  createIdea(data: {
243
270
  content: string;
244
271
  title?: string;
245
272
  priority?: string;
246
273
  metadata?: Record<string, unknown>;
247
274
  }): Promise<unknown>;
275
+ /** `PATCH /ideas/{id_or_number}/status`. */
248
276
  updateIdeaStatus(idOrNumber: string, data: {
249
277
  status: string;
250
278
  }): Promise<unknown>;
251
- updateIdeaStatusByNumber(ideaNumber: string, data: {
252
- status: string;
253
- }): Promise<unknown>;
254
279
  listOrbits(options?: {
255
280
  areaId?: string;
256
281
  workspaceId?: string;
@@ -294,13 +319,18 @@ export declare class CloudAdapter implements WorkspaceAdapter {
294
319
  createIntent(data: {
295
320
  name: string;
296
321
  outcome?: string;
322
+ design?: string;
323
+ plan?: string;
297
324
  status?: string;
298
325
  area_id?: string;
299
326
  }): Promise<unknown>;
300
327
  updateIntent(idOrNumber: string, data: {
301
328
  name?: string;
302
329
  outcome?: string;
330
+ design?: string;
331
+ plan?: string;
303
332
  status?: string;
333
+ logs?: WorkLogEntry[];
304
334
  }): Promise<unknown>;
305
335
  linkIntentItem(idOrNumber: string, data: {
306
336
  item_type: string;
@@ -328,10 +358,12 @@ export declare class CloudAdapter implements WorkspaceAdapter {
328
358
  description?: string;
329
359
  outcome?: string;
330
360
  end_date?: string;
361
+ logs?: WorkLogEntry[];
331
362
  }): Promise<unknown>;
332
363
  setMissionStatus(idOrNumber: string, data: {
333
364
  status: string;
334
365
  move_target_mission_id?: string;
366
+ logs?: WorkLogEntry[];
335
367
  }): Promise<unknown>;
336
368
  linkMissionItem(idOrNumber: string, data: {
337
369
  item_type: string;
@@ -353,6 +385,12 @@ export interface CreateClientOptions {
353
385
  * once per command instead of twice.
354
386
  */
355
387
  area?: string;
388
+ /**
389
+ * Workspace identity already produced by the workspace ladder (`resolveWorkspaceId`, via
390
+ * `resolveWorkspaceContext`). Same reasoning as {@link CreateClientOptions.area}: pass it
391
+ * when the caller has it so the ladder runs once per command.
392
+ */
393
+ workspace?: string;
356
394
  /** Global config the caller already loaded, to avoid a second read of the same file. */
357
395
  config?: OrbitMapConfig;
358
396
  }
@@ -363,5 +401,9 @@ export interface CreateClientOptions {
363
401
  * 0002 §2). `resolveAuth` used to carry a second, shorter copy of it that was missing the
364
402
  * `link.json` rung, so a cloud project with a `.orbitmap/link.json` got a different area
365
403
  * depending on which entry point it came through.
404
+ *
405
+ * The workspace identity comes from its own, separate ladder (`resolveWorkspaceId`) and is
406
+ * resolved here too: an area is no longer required, so a command run outside every area
407
+ * directory still has a context to send.
366
408
  */
367
409
  export declare function createClient(explicitArea?: string, options?: CreateClientOptions): Promise<CloudAdapter>;
@@ -1,22 +1,30 @@
1
1
  import { createReadStream, statSync } from 'node:fs';
2
2
  import { basename, extname } from 'node:path';
3
- import { OrbitMapAPIError } from '../errors.js';
3
+ import { AREA_CONTEXT_REQUIRED, AREA_WORKSPACE_MISMATCH, OrbitMapAPIError, WORKSPACE_CONTEXT_REQUIRED, } from '../errors.js';
4
4
  import { loadConfig, resolveAuth, DEFAULT_API_URL } from '../config.js';
5
- import { resolveArea } from '../workspace-resolve.js';
5
+ import { areaContextHint, ORBITMAP_DIR, resolveArea, resolveWorkspaceId, } from '../workspace-resolve.js';
6
6
  import { isObjectNumber, resolveTaskRef, stripPrefix } from '../id-resolve.js';
7
7
  export class CloudAdapter {
8
8
  apiKey;
9
9
  baseUrl;
10
10
  area;
11
+ /**
12
+ * Workspace **identity** (slug or uuid, never a path) from the separate workspace ladder
13
+ * (`--workspace-id` → `ORBITMAP_WORKSPACE_ID` → project config `workspace` → global config
14
+ * `workspace`; see `resolveWorkspaceId`). Independent of {@link CloudAdapter.area}: either,
15
+ * both or neither may be set.
16
+ */
17
+ workspace;
11
18
  /**
12
19
  * Task reference → uuid, for the lifetime of this adapter. A command that touches the same
13
20
  * task twice (`dep add TS-a TS-b` resolves two, `status` then reads back) pays for one
14
21
  * `/tasks/by-number/{n}` lookup per distinct reference, not per call site.
15
22
  */
16
23
  taskIdCache = new Map();
17
- constructor(apiKey, area, apiUrl) {
24
+ constructor(apiKey, area, apiUrl, workspace) {
18
25
  this.apiKey = apiKey;
19
26
  this.area = area;
27
+ this.workspace = workspace;
20
28
  this.baseUrl = (apiUrl ?? DEFAULT_API_URL).replace(/\/$/, '');
21
29
  }
22
30
  async request(method, path, options) {
@@ -32,6 +40,14 @@ export class CloudAdapter {
32
40
  Authorization: `Bearer ${this.apiKey}`,
33
41
  Accept: 'application/json',
34
42
  };
43
+ // The two context headers are independent and both optional. The server resolves the
44
+ // workspace from `X-Orbitmap-Workspace` first, then from the area, then by auto-detecting
45
+ // a unique one — so a workspace-level command (intent, mission, idea, docs) run outside
46
+ // any area directory works with the workspace header alone, and an area-only client
47
+ // (everything before schema v2) keeps behaving exactly as it did.
48
+ if (this.workspace) {
49
+ headers['X-Orbitmap-Workspace'] = this.workspace;
50
+ }
35
51
  if (this.area) {
36
52
  headers['X-Orbitmap-Area'] = this.area;
37
53
  }
@@ -64,7 +80,7 @@ export class CloudAdapter {
64
80
  }
65
81
  if (!response.ok) {
66
82
  if (OrbitMapAPIError.isApiErrorBody(json)) {
67
- throw OrbitMapAPIError.fromResponse(json);
83
+ throw this.explainContextError(OrbitMapAPIError.fromResponse(json));
68
84
  }
69
85
  // Billing/usage-limit rejections (402, 429) use a flat shape where
70
86
  // `error` is a string — dispatch on the type of `error`, not status.
@@ -87,6 +103,53 @@ export class CloudAdapter {
87
103
  clearTimeout(timeout);
88
104
  }
89
105
  }
106
+ /**
107
+ * Add the client half of the story to the workspace-context errors the server can raise.
108
+ * The API knows what it received; only the CLI knows what it *sent* and which knobs could
109
+ * change that, and a bare "workspace context is required" is unactionable without it.
110
+ * Every other error is returned untouched.
111
+ *
112
+ * `AREA_CONTEXT_REQUIRED` is here too (TS-jt6q36 follow-up). The CLI raises that code
113
+ * itself — with the areas of the current workspace spelled out — but the *server* raises
114
+ * it as well, in HTTP terms ("set the X-Orbitmap-Area header. Call GET /api/agent/areas"),
115
+ * and that text used to reach the terminal verbatim: identical situation, two different
116
+ * answers depending on which side noticed first. The client-side advice is now attached to
117
+ * both, and the server's own sentence is kept (parenthesised) rather than dropped, because
118
+ * it is the only part that says what the API actually objected to.
119
+ */
120
+ explainContextError(error) {
121
+ // Dispatch on the canonical code, not the wire code: the API is renaming `PROJECT_*` to
122
+ // `AREA_*` and both spellings must land here (see ERROR_CODE_ALIASES in errors.ts).
123
+ if (error.canonicalCode !== WORKSPACE_CONTEXT_REQUIRED &&
124
+ error.canonicalCode !== AREA_WORKSPACE_MISMATCH &&
125
+ error.canonicalCode !== AREA_CONTEXT_REQUIRED) {
126
+ return error;
127
+ }
128
+ const sent = {};
129
+ if (this.workspace)
130
+ sent['X-Orbitmap-Workspace'] = this.workspace;
131
+ if (this.area)
132
+ sent['X-Orbitmap-Area'] = this.area;
133
+ if (error.canonicalCode === AREA_CONTEXT_REQUIRED) {
134
+ const { message, mappedDirectories } = areaContextHint();
135
+ return new OrbitMapAPIError(`${message} (The API added: ${error.message})`, error.code, error.status, {
136
+ ...error.details,
137
+ ...(mappedDirectories.length > 0
138
+ ? { mapped_directories: mappedDirectories }
139
+ : {}),
140
+ ...(Object.keys(sent).length > 0 ? { sent_context: sent } : {}),
141
+ });
142
+ }
143
+ const hint = error.canonicalCode === WORKSPACE_CONTEXT_REQUIRED
144
+ ? 'Name a workspace with --workspace-id <slug-or-uuid>, ORBITMAP_WORKSPACE_ID, or a ' +
145
+ `"workspace" in ${ORBITMAP_DIR}/config.json.`
146
+ : 'The workspace and the area disagree — check --workspace-id/--area, ' +
147
+ `ORBITMAP_WORKSPACE_ID/ORBITMAP_AREA and ${ORBITMAP_DIR}/config.json.`;
148
+ return new OrbitMapAPIError(`${error.message} ${hint}`, error.code, error.status, {
149
+ ...error.details,
150
+ ...(Object.keys(sent).length > 0 ? { sent_context: sent } : {}),
151
+ });
152
+ }
90
153
  // ── Context ──
91
154
  async getContext(areaId) {
92
155
  return this.request('GET', '/context', {
@@ -102,6 +165,14 @@ export class CloudAdapter {
102
165
  async getDefaultArea() {
103
166
  return this.area;
104
167
  }
168
+ /**
169
+ * The workspace identity this client sends as `X-Orbitmap-Workspace`, or `undefined` when
170
+ * none resolved (in which case the server derives one from the area or auto-detects it).
171
+ * No request is made.
172
+ */
173
+ getWorkspaceId() {
174
+ return this.workspace;
175
+ }
105
176
  // ── Tasks ──
106
177
  async getTasks(options) {
107
178
  return this.request('GET', '/tasks', {
@@ -225,6 +296,19 @@ export class CloudAdapter {
225
296
  body: { type, content, metadata },
226
297
  });
227
298
  }
299
+ async logIntentWork(idOrNumber, type, content, metadata) {
300
+ // No client-side resolution: `/intents/{id_or_number}/logs` accepts a uuid, an `IN-`
301
+ // display number or a bare code and resolves it server-side, exactly like every other
302
+ // intent route (see `tests/reference-passthrough.test.ts`).
303
+ return this.request('POST', `/intents/${idOrNumber}/logs`, {
304
+ body: { type, content, metadata },
305
+ });
306
+ }
307
+ async logMissionWork(idOrNumber, type, content, metadata) {
308
+ return this.request('POST', `/missions/${idOrNumber}/logs`, {
309
+ body: { type, content, metadata },
310
+ });
311
+ }
228
312
  // ── Documents ──
229
313
  async getDocuments(options) {
230
314
  return this.request('GET', '/documents', {
@@ -388,32 +472,23 @@ export class CloudAdapter {
388
472
  },
389
473
  });
390
474
  }
475
+ /**
476
+ * `GET /issues/{id_or_number}` — the single issue-read route. It accepts a uuid, a bare
477
+ * six-char code or the prefixed `IS-xxxxxx` form; the server strips the prefix and resolves
478
+ * the number workspace-scoped. There is no `/issues/by-number/{n}` route — never add one.
479
+ */
391
480
  async getIssue(idOrNumber) {
392
481
  return this.request('GET', `/issues/${idOrNumber}`);
393
482
  }
394
- async getIssueByNumber(issueNumber) {
395
- return this.request('GET', `/issues/by-number/${issueNumber}`);
396
- }
397
- async resolveIssueByNumber(issueNumber, data) {
398
- return this.request('PATCH', `/issues/by-number/${issueNumber}/resolve`, {
399
- body: data,
400
- });
401
- }
402
- async resolveIssue(idOrNumber, data) {
403
- return this.request('PATCH', `/issues/${idOrNumber}/resolve`, {
404
- body: data,
405
- });
406
- }
483
+ /**
484
+ * `PATCH /issues/{id_or_number}/status` — the only issue-write route besides `/link` and
485
+ * `/assign`. `issue resolve` rides on it too: the API has no `/issues/{id}/resolve`.
486
+ */
407
487
  async updateIssueStatus(idOrNumber, data) {
408
488
  return this.request('PATCH', `/issues/${idOrNumber}/status`, {
409
489
  body: data,
410
490
  });
411
491
  }
412
- async updateIssueStatusByNumber(issueNumber, data) {
413
- return this.request('PATCH', `/issues/by-number/${issueNumber}/status`, {
414
- body: data,
415
- });
416
- }
417
492
  // ── Vibes ──
418
493
  async logVibe(data) {
419
494
  return this.request('POST', '/vibes', { body: data });
@@ -439,21 +514,20 @@ export class CloudAdapter {
439
514
  params: { status: options?.status, priority: options?.priority, limit: options?.limit },
440
515
  });
441
516
  }
517
+ /**
518
+ * `GET /ideas/{id_or_number}` — accepts a uuid, a bare code or `ID-xxxxxx`; the server
519
+ * strips the prefix and resolves the number. There is no `/ideas/by-number/{n}` route.
520
+ */
442
521
  async getIdea(idOrNumber) {
443
522
  return this.request('GET', `/ideas/${idOrNumber}`);
444
523
  }
445
- async getIdeaByNumber(ideaNumber) {
446
- return this.request('GET', `/ideas/by-number/${ideaNumber}`);
447
- }
448
524
  async createIdea(data) {
449
525
  return this.request('POST', '/ideas', { body: data });
450
526
  }
527
+ /** `PATCH /ideas/{id_or_number}/status`. */
451
528
  async updateIdeaStatus(idOrNumber, data) {
452
529
  return this.request('PATCH', `/ideas/${idOrNumber}/status`, { body: data });
453
530
  }
454
- async updateIdeaStatusByNumber(ideaNumber, data) {
455
- return this.request('PATCH', `/ideas/by-number/${ideaNumber}/status`, { body: data });
456
- }
457
531
  // ── Orbits ──
458
532
  async listOrbits(options) {
459
533
  return this.request('GET', '/orbits', {
@@ -572,11 +646,16 @@ export { CloudAdapter as OrbitMapClient };
572
646
  * 0002 §2). `resolveAuth` used to carry a second, shorter copy of it that was missing the
573
647
  * `link.json` rung, so a cloud project with a `.orbitmap/link.json` got a different area
574
648
  * depending on which entry point it came through.
649
+ *
650
+ * The workspace identity comes from its own, separate ladder (`resolveWorkspaceId`) and is
651
+ * resolved here too: an area is no longer required, so a command run outside every area
652
+ * directory still has a context to send.
575
653
  */
576
654
  export async function createClient(explicitArea, options = {}) {
577
655
  const config = options.config ?? (await loadConfig());
578
656
  const auth = await resolveAuth({ config });
579
657
  const area = explicitArea ?? options.area ?? resolveArea({ config }).area;
580
- return new CloudAdapter(auth.apiKey, area, auth.apiUrl);
658
+ const workspace = options.workspace ?? resolveWorkspaceId({ config }).workspace;
659
+ return new CloudAdapter(auth.apiKey, area, auth.apiUrl, workspace);
581
660
  }
582
661
  //# sourceMappingURL=cloud.js.map