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/dist/paths.js CHANGED
@@ -1,6 +1,6 @@
1
- import { existsSync, realpathSync } from 'node:fs';
1
+ import { existsSync, readFileSync, realpathSync } from 'node:fs';
2
2
  import { homedir } from 'node:os';
3
- import { dirname, join, resolve } from 'node:path';
3
+ import { basename, dirname, join, resolve } from 'node:path';
4
4
  /** Name of the per-project OrbitMap directory (`<project>/.orbitmap`). */
5
5
  export const ORBITMAP_DIR = '.orbitmap';
6
6
  /**
@@ -12,6 +12,28 @@ export const ORBITMAP_DIR = '.orbitmap';
12
12
  export const CONFIG_FILENAME = 'config.json';
13
13
  /** Filename of the machine-local secret store (`~/.orbitmap/credentials.json`). */
14
14
  export const CREDENTIALS_FILENAME = 'credentials.json';
15
+ /**
16
+ * Filename of the gitignored, machine-specific link file (`<project>/.orbitmap/link.json`).
17
+ *
18
+ * Duplicated from `workspace-resolve.ts`'s `LINK_FILE` rather than imported: that module
19
+ * sits above this one, and this is a leaf.
20
+ */
21
+ const LINK_FILENAME = 'link.json';
22
+ /**
23
+ * The one directory holding documents, in **both** modes (IN-rwb5qu).
24
+ *
25
+ * Cloud mode: `<project>/.orbitmap/docs/` is the download cache. Local mode: the same
26
+ * `docs/` inside the workspace directory is the domain data itself — and for the usual
27
+ * in-project workspace (`<project>/.orbitmap`) that is literally the same path. Hence one
28
+ * name here rather than one per layer: there is exactly ONE copy of a document on disk,
29
+ * globally, with **no per-area subdirectories** (the data model does not record a
30
+ * document's home area, so the directory tree must not pretend it does).
31
+ */
32
+ export const DOCS_DIR = 'docs';
33
+ /** Pre-v2 local-workspace name of {@link DOCS_DIR}; migrated away by `LocalStore.migrate()`. */
34
+ export const LEGACY_DOCUMENTS_DIR = 'documents';
35
+ /** Cache bookkeeping inside {@link DOCS_DIR}; written in cloud mode only. */
36
+ export const DOC_CACHE_MANIFEST = '.manifest.json';
15
37
  let homeCache;
16
38
  /**
17
39
  * The resolved OrbitMap home, memoised.
@@ -141,22 +163,165 @@ export function findProjectConfigFiles(startDir = process.cwd()) {
141
163
  const found = [];
142
164
  const globals = new Set(globalConfigPaths().map(canonical));
143
165
  const homeStop = canonical(homedir());
166
+ const seenFiles = new Set();
167
+ const seenStarts = new Set();
168
+ // Directories whose walk-up still has to run: `startDir` first, then any workspace root a
169
+ // `link.json` back-pointer found along the way names (see the doc comment above).
170
+ const queue = [resolve(startDir)];
171
+ while (queue.length > 0) {
172
+ const start = queue.shift();
173
+ if (seenStarts.has(canonical(start)))
174
+ continue;
175
+ seenStarts.add(canonical(start));
176
+ let dir = start;
177
+ for (;;) {
178
+ const orbitmapDir = join(dir, ORBITMAP_DIR);
179
+ const candidate = join(orbitmapDir, CONFIG_FILENAME);
180
+ if (existsSync(candidate) &&
181
+ !globals.has(canonical(candidate)) &&
182
+ !isOrbitMapHomeDir(orbitmapDir)) {
183
+ const key = canonical(candidate);
184
+ if (!seenFiles.has(key)) {
185
+ seenFiles.add(key);
186
+ found.push(candidate);
187
+ }
188
+ }
189
+ // An out-of-tree area cannot reach its workspace root by walking up; its machine-local
190
+ // `link.json` says where the root is, so the walk hops there and continues rather than
191
+ // ending with `agent`/`mode`/`workspace` unresolved (IS-ktfg5z).
192
+ const pointer = workspacePointer(orbitmapDir);
193
+ if (pointer && !seenStarts.has(canonical(pointer)))
194
+ queue.push(pointer);
195
+ if (canonical(dir) === homeStop)
196
+ break;
197
+ const parent = dirname(dir);
198
+ if (parent === dir)
199
+ break;
200
+ dir = parent;
201
+ }
202
+ }
203
+ return found;
204
+ }
205
+ /**
206
+ * The nearest directory at or above `startDir` that owns an `.orbitmap/` — the project (or
207
+ * workspace) root the doc cache and the `.gitignore` entries belong to. `undefined` when the
208
+ * walk finds none.
209
+ *
210
+ * The same walk and the same guards as {@link findProjectConfigFiles}, for the same reasons:
211
+ * an OrbitMap *home* is never a project (a relocated or mis-`$HOME`-ed `~/.orbitmap` sits on
212
+ * the walk path as an ordinary ancestor), and nothing above the user's home is that user's
213
+ * project. Only the *directory* is required to exist here — a project that has never had a
214
+ * `config.json` still owns its `.orbitmap/link.json` or `.orbitmap/docs/`.
215
+ *
216
+ * This is what replaces the doc cache's old `join('.orbitmap', 'docs')`, which resolved
217
+ * against `process.cwd()` with no walk-up at all and therefore scattered a fresh copy of
218
+ * every document into every subdirectory a command happened to run from.
219
+ *
220
+ * **One workspace = one directory = one `.orbitmap`.** The walk is not a search for any
221
+ * `.orbitmap` that happens to look like a workspace: it stops at the first ancestor that
222
+ * declares one ({@link declaresWorkspace}) and returns it, or follows an explicit
223
+ * back-pointer ({@link workspacePointer}) when the area lives outside the tree. Climbing
224
+ * past the first declaration used to hand the deepest directory to the *furthest* ancestor
225
+ * declaring a workspace — by construction not its owner — so two workspaces side by side
226
+ * under a third pooled their documents into the outermost one's flat `<slug>.md` namespace
227
+ * and overwrote each other (IS-8hv2wn).
228
+ */
229
+ export function findProjectOrbitMapDir(startDir = process.cwd()) {
230
+ const globals = new Set(globalConfigPaths().map(canonical));
231
+ const homeStop = canonical(homedir());
232
+ let nearest;
144
233
  let dir = resolve(startDir);
145
234
  for (;;) {
146
235
  const orbitmapDir = join(dir, ORBITMAP_DIR);
147
- const candidate = join(orbitmapDir, CONFIG_FILENAME);
148
- if (existsSync(candidate) &&
149
- !globals.has(canonical(candidate)) &&
150
- !isOrbitMapHomeDir(orbitmapDir)) {
151
- found.push(candidate);
236
+ if (existsSync(orbitmapDir) &&
237
+ !isOrbitMapHomeDir(orbitmapDir) &&
238
+ !globals.has(canonical(join(orbitmapDir, CONFIG_FILENAME)))) {
239
+ nearest ??= dir;
240
+ // A back-pointer wins outright: this directory has been told, in writing, where its
241
+ // workspace lives, so there is nothing left to search for.
242
+ const pointer = workspacePointer(orbitmapDir);
243
+ if (pointer)
244
+ return pointer;
245
+ // The FIRST declaring config is the owner. Climbing further would hand `startDir`
246
+ // to whichever unrelated ancestor happens to declare a workspace too — by
247
+ // definition not the one that owns it (IS-8hv2wn).
248
+ if (declaresWorkspace(orbitmapDir))
249
+ return dir;
152
250
  }
153
251
  if (canonical(dir) === homeStop)
154
- return found;
252
+ break;
155
253
  const parent = dirname(dir);
156
254
  if (parent === dir)
157
- return found;
255
+ break;
158
256
  dir = parent;
159
257
  }
258
+ return nearest;
259
+ }
260
+ /** Parse a JSON file into a plain object, or `undefined` when it is missing/unreadable. */
261
+ function readJsonObject(file) {
262
+ try {
263
+ const raw = JSON.parse(readFileSync(file, 'utf-8'));
264
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw))
265
+ return undefined;
266
+ return raw;
267
+ }
268
+ catch {
269
+ return undefined;
270
+ }
271
+ }
272
+ /**
273
+ * The workspace **directory** this `.orbitmap/` points at, when it points at one elsewhere.
274
+ *
275
+ * An area directory living outside the workspace tree cannot reach the workspace root by
276
+ * walking up — there is nothing above it but unrelated directories. `init` therefore writes
277
+ * `{"workspace": "<path>"}` into that area's own machine-local `link.json` (the same field,
278
+ * the same meaning, as in local mode: *where* the workspace is). Following it is what keeps
279
+ * such an area sharing the one global `docs/` instead of quietly starting a second copy.
280
+ *
281
+ * Accepts either the workspace root or its `.orbitmap/` directory — `resolveWorkspacePath`
282
+ * hands back the latter for a `workspace.yml`-marked local workspace, so both spellings of
283
+ * "the workspace is over there" exist in the wild, and both must mean the same directory
284
+ * here. Relative paths resolve against the link file, exactly as in `resolveWorkspacePath`.
285
+ */
286
+ function workspacePointer(orbitmapDir) {
287
+ const link = readJsonObject(join(orbitmapDir, LINK_FILENAME));
288
+ const target = link?.['workspace'];
289
+ if (typeof target !== 'string' || !target)
290
+ return undefined;
291
+ const resolved = resolve(orbitmapDir, target);
292
+ return basename(resolved) === ORBITMAP_DIR ? dirname(resolved) : resolved;
293
+ }
294
+ /**
295
+ * Does this `.orbitmap/` belong to a *workspace* root rather than an area directory?
296
+ *
297
+ * The distinction is **location**, not identity. An out-of-tree area marker also carries a
298
+ * `workspace` field, because it needs the slug for the `X-Orbitmap-Workspace` header and has
299
+ * no root config to inherit it from — but naming your workspace is not the same as being its
300
+ * root, and reading it as one gave that directory its own second copy of every document.
301
+ * A file that pins an `area` is a marker, full stop; a root is what maps `areas` (or names a
302
+ * workspace without claiming to be an area). Read directly rather than through
303
+ * `project-config.ts`, which sits above this module.
304
+ *
305
+ * `link.json` is deliberately *not* enough on its own: its `workspace` field is a pointer at
306
+ * a workspace somewhere else (see {@link workspacePointer}) — the opposite of being one. Only
307
+ * its `areas` map, which lists the out-of-tree areas *of this workspace*, marks a root.
308
+ */
309
+ function declaresWorkspace(orbitmapDir) {
310
+ const link = readJsonObject(join(orbitmapDir, LINK_FILENAME));
311
+ if (isPlainObject(link?.['areas']))
312
+ return true;
313
+ const raw = readJsonObject(join(orbitmapDir, CONFIG_FILENAME));
314
+ if (!raw)
315
+ return false;
316
+ if (isPlainObject(raw['areas']))
317
+ return true;
318
+ // `typeof null === 'object'` used to make `"areas": null` a workspace root; hence the
319
+ // explicit shape check above and the `area`-marker exclusion here.
320
+ return typeof raw['workspace'] === 'string' && typeof raw['area'] !== 'string';
321
+ }
322
+ /** A non-null, non-array object — what an `areas` map has to be to mean anything. */
323
+ function isPlainObject(value) {
324
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
160
325
  }
161
326
  /**
162
327
  * Per-field walk-up: hand each project config file, nearest first, to `read` and return the
package/dist/paths.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"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"}
@@ -41,11 +41,52 @@ export interface OrbitMapConfig {
41
41
  workspace_path?: string;
42
42
  /** Name of the agent profile to use — looked up in `~/.orbitmap/credentials.json`. */
43
43
  agent?: string;
44
- /** Default area slug/id. */
44
+ /**
45
+ * Workspace this config belongs to — a workspace **slug or UUID**, never a path (the
46
+ * *directory* is `workspace_path` / the `link.json` pointer / the marker walk-up).
47
+ *
48
+ * Written into the workspace directory's own `.orbitmap/config.json` (schema v2), which is
49
+ * committable and holds no secrets. Purely declarative here: this field says *which*
50
+ * workspace the tree belongs to, it does not participate in the workspace-directory ladder.
51
+ */
52
+ workspace?: string;
53
+ /**
54
+ * Directory → area slug map (schema v2): "the code for area `x` lives in this directory".
55
+ *
56
+ * **Keys are relative directory paths, resolved against the parent of the `.orbitmap`
57
+ * directory that holds this config file** — i.e. against the workspace/project root, not
58
+ * against the current directory and not against `.orbitmap/` itself. `"."` is that root.
59
+ * Keys are normalised on read ({@link normalizeAreaPathKey}): backslashes become `/`,
60
+ * `./` prefixes and trailing slashes are dropped, and an empty result becomes `"."`.
61
+ *
62
+ * Values are area slugs.
63
+ *
64
+ * **Absolute keys and keys that escape upward (`..`) are rejected**, deliberately: a
65
+ * directory outside the tree is machine-specific and therefore belongs in the gitignored
66
+ * `link.json` (see `WorkspaceLink.areas` in `workspace-resolve.ts`), not in a file meant to
67
+ * be committed and to mean the same thing in every checkout.
68
+ */
69
+ areas?: Record<string, string>;
70
+ /**
71
+ * Default area slug/id.
72
+ *
73
+ * Still read on every rung for back-compat — an existing project config that pins `area`
74
+ * keeps working unchanged. Schema v2 replaces it with the directory-scoped {@link areas}
75
+ * map plus a per-area marker config, so `orbitmap init` no longer *writes* this field.
76
+ */
45
77
  area?: string;
46
78
  /** @deprecated Legacy secret, read once for migration then dropped. Never written. */
47
79
  api_key?: string;
48
80
  }
81
+ /**
82
+ * Canonical form of one {@link OrbitMapConfig.areas} key, or `undefined` when the key is not
83
+ * a usable relative directory path.
84
+ *
85
+ * Normalises what a hand-written (or Windows-written) config may contain — `.\packages\api`,
86
+ * `packages/api/`, `./` — into one comparable form (`packages/api`, `.`). Rejects absolute
87
+ * paths and any `..` segment; see {@link OrbitMapConfig.areas} for why.
88
+ */
89
+ export declare function normalizeAreaPathKey(key: string): string | undefined;
49
90
  /**
50
91
  * Parse and validate a raw config object (either file) — mistyped fields drop out instead
51
92
  * of propagating a garbage value into the resolution ladders. Unknown keys are not
@@ -1,5 +1,51 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import { resolveProjectField } from './paths.js';
3
+ /** Matches a Windows drive prefix (`C:`), which `node:path`'s POSIX `isAbsolute` does not —
4
+ * an `areas` key is a portable, committable path and must be rejected on every platform. */
5
+ const WINDOWS_DRIVE = /^[a-zA-Z]:/;
6
+ /**
7
+ * Canonical form of one {@link OrbitMapConfig.areas} key, or `undefined` when the key is not
8
+ * a usable relative directory path.
9
+ *
10
+ * Normalises what a hand-written (or Windows-written) config may contain — `.\packages\api`,
11
+ * `packages/api/`, `./` — into one comparable form (`packages/api`, `.`). Rejects absolute
12
+ * paths and any `..` segment; see {@link OrbitMapConfig.areas} for why.
13
+ */
14
+ export function normalizeAreaPathKey(key) {
15
+ const slashed = key.replace(/\\/g, '/').trim();
16
+ if (!slashed)
17
+ return undefined;
18
+ if (slashed.startsWith('/') || WINDOWS_DRIVE.test(slashed))
19
+ return undefined;
20
+ const segments = slashed.split('/').filter((part) => part.length > 0 && part !== '.');
21
+ if (segments.some((part) => part === '..'))
22
+ return undefined;
23
+ return segments.length === 0 ? '.' : segments.join('/');
24
+ }
25
+ /**
26
+ * Validate the `areas` map: a plain object of relative-directory → non-empty area slug.
27
+ *
28
+ * **Rejected per entry, not per file.** One unusable key (absolute, `..`, non-string value)
29
+ * must not take the rest of a repository's mapping down with it — the same reasoning as the
30
+ * per-field validation in {@link parseConfig}. When nothing valid is left the field itself is
31
+ * dropped, so a ladder sees "not set" rather than an empty map. On a collision (two keys
32
+ * normalising to the same directory) the first entry wins.
33
+ */
34
+ function parseAreasMap(raw) {
35
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw))
36
+ return undefined;
37
+ const areas = {};
38
+ for (const [key, value] of Object.entries(raw)) {
39
+ if (typeof value !== 'string' || !value)
40
+ continue;
41
+ const dir = normalizeAreaPathKey(key);
42
+ if (dir === undefined)
43
+ continue;
44
+ if (!(dir in areas))
45
+ areas[dir] = value;
46
+ }
47
+ return Object.keys(areas).length > 0 ? areas : undefined;
48
+ }
3
49
  /**
4
50
  * Parse and validate a raw config object (either file) — mistyped fields drop out instead
5
51
  * of propagating a garbage value into the resolution ladders. Unknown keys are not
@@ -19,6 +65,11 @@ export function parseConfig(raw) {
19
65
  }
20
66
  if (typeof r['agent'] === 'string' && r['agent'])
21
67
  config.agent = r['agent'];
68
+ if (typeof r['workspace'] === 'string' && r['workspace'])
69
+ config.workspace = r['workspace'];
70
+ const areas = parseAreasMap(r['areas']);
71
+ if (areas)
72
+ config.areas = areas;
22
73
  if (typeof r['area'] === 'string' && r['area'])
23
74
  config.area = r['area'];
24
75
  if (typeof r['api_key'] === 'string' && r['api_key'])
@@ -1 +1 @@
1
- {"version":3,"file":"project-config.js","sourceRoot":"","sources":["../src/project-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAqDjD;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,GAAY;IACtC,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,EAAE,CAAC;IACvD,MAAM,CAAC,GAAG,GAA8B,CAAC;IACzC,MAAM,MAAM,GAAmB,EAAE,CAAC;IAElC,IAAI,OAAO,CAAC,CAAC,gBAAgB,CAAC,KAAK,QAAQ;QAAE,MAAM,CAAC,cAAc,GAAG,CAAC,CAAC,gBAAgB,CAAC,CAAC;IACzF,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,OAAO,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,OAAO;QAAE,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC;IAC5E,IAAI,OAAO,CAAC,CAAC,gBAAgB,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,gBAAgB,CAAC,EAAE,CAAC;QACnE,MAAM,CAAC,cAAc,GAAG,CAAC,CAAC,gBAAgB,CAAC,CAAC;IAC9C,CAAC;IACD,IAAI,OAAO,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,OAAO,CAAC;QAAE,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC;IAC5E,IAAI,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,CAAC;QAAE,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC;IACxE,IAAI,OAAO,CAAC,CAAC,SAAS,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,SAAS,CAAC;QAAE,MAAM,CAAC,OAAO,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC;IAEpF,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;sFACsF;AACtF,MAAM,UAAU,qBAAqB,CAAC,IAAY;IAChD,IAAI,CAAC;QACH,OAAO,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAC9D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AASD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,yBAAyB,CACvC,KAAQ,EACR,WAAmB,OAAO,CAAC,GAAG,EAAE;IAEhC,MAAM,GAAG,GAAG,mBAAmB,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,qBAAqB,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IAC1F,OAAO,GAAG;QACR,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAuC,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE;QACxE,CAAC,CAAC,SAAS,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"project-config.js","sourceRoot":"","sources":["../src/project-config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Work log — the vocabulary, the reference → target dispatch and the `--log` batch parser.
3
+ *
4
+ * A work log entry targets exactly ONE of a task, an intent or a mission (intent IN-pjrs6e).
5
+ * Tasks were the only target until now, so `orbitmap log` assumed one; this module is the
6
+ * single place that decides which of the three a reference means, so the decision cannot
7
+ * drift between `orbitmap log` and the `--log` batches carried by `intent update`,
8
+ * `mission update` and `mission status`.
9
+ *
10
+ * Two rules the API owns and the CLI must NOT reimplement:
11
+ *
12
+ * - **`status_change` is server-authored.** The server knows `from` and `to` and composes
13
+ * that line itself; the Agent API rejects the type from a client. It is therefore absent
14
+ * from {@link WORK_LOG_TYPES} and rejected here with an explanation rather than forwarded
15
+ * for a 422.
16
+ * - **A status transition must carry at least one entry.** That is enforced server-side
17
+ * (422) — the CLI does not pre-check it, because it cannot know whether the status is
18
+ * actually changing without an extra round trip. {@link describeWorkLogRequirement} turns
19
+ * the server's rejection into something the caller can act on.
20
+ *
21
+ * References travel verbatim: `/intents/{id_or_number}/logs` and
22
+ * `/missions/{id_or_number}/logs` resolve display numbers server-side (see
23
+ * `tests/reference-passthrough.test.ts`), so nothing here rewrites an identifier — it only
24
+ * reads the prefix to pick the endpoint.
25
+ */
26
+ /**
27
+ * The log types a CLIENT may author (`WorkLog::CALLER_TYPES` server-side).
28
+ *
29
+ * Deliberately narrower than the column vocabulary: `status_change` is missing on purpose
30
+ * (see the module comment). Also deliberately narrower than the vibe types — the two groups
31
+ * do not overlap, mirroring `log_work` in orbitmap-mcp.
32
+ */
33
+ export declare const WORK_LOG_TYPES: readonly ["note", "code_change", "decision", "blocker"];
34
+ export type WorkLogType = (typeof WORK_LOG_TYPES)[number];
35
+ /** Log types only the server may write. Rejected here with an explanation, never forwarded. */
36
+ export declare const SERVER_AUTHORED_LOG_TYPES: readonly ["status_change"];
37
+ /** Vibe-only types: valid for `orbitmap vibe log`, rejected by the work-log endpoints. */
38
+ export declare const VIBE_ONLY_TYPES: readonly ["discovery", "exploration", "fix"];
39
+ /** The three objects a work log entry can attach to. */
40
+ export declare const WORK_LOG_TARGETS: readonly ["task", "intent", "mission"];
41
+ export type WorkLogTarget = (typeof WORK_LOG_TARGETS)[number];
42
+ /** One entry of a `--log` batch, shaped as the API's `logs[]` elements. */
43
+ export interface WorkLogEntry {
44
+ type: string;
45
+ content: string;
46
+ }
47
+ /**
48
+ * One entry as the API RETURNS it, which is a wider shape than the `logs[]`
49
+ * elements we send: `show` embeds the stored row, not the request body.
50
+ */
51
+ export interface WorkLogRecord {
52
+ id: string;
53
+ type: string;
54
+ content: string;
55
+ metadata?: Record<string, unknown> | null;
56
+ created_at: string | null;
57
+ }
58
+ /**
59
+ * Render the `work_logs` a detail payload embeds.
60
+ *
61
+ * The API returns them oldest-first so they read as a narrative; this preserves
62
+ * that order rather than imposing a newest-first view. Returns an empty string
63
+ * when there is nothing to show, so callers can print unconditionally.
64
+ */
65
+ export declare function formatWorkLogs(logs: readonly WorkLogRecord[] | undefined): string;
66
+ /** The human-readable list of types, used verbatim in every message and in `--help`. */
67
+ export declare const WORK_LOG_TYPE_LIST: string;
68
+ /**
69
+ * Which object a `orbitmap log` reference names.
70
+ *
71
+ * A prefixed display number answers it outright (`TS-` → task, `IN-` → intent, `MS-` →
72
+ * mission). A bare uuid or bare object code carries no type at all, so it stays a **task** —
73
+ * the behaviour every existing caller has — unless `--target` says otherwise. That flag is
74
+ * the only way to log against an intent or a mission by uuid, and it is rejected when it
75
+ * contradicts a prefix rather than silently overriding it.
76
+ */
77
+ export declare function resolveWorkLogTarget(ref: string, explicit?: string): WorkLogTarget;
78
+ /**
79
+ * Validate a single work-log type, with the two wrong-vocabulary cases named rather than
80
+ * lumped into "invalid". `undefined` is valid: the API defaults an absent type to `note`.
81
+ */
82
+ export declare function assertWorkLogType(type: string, label?: string): void;
83
+ /**
84
+ * Parse a repeated `--log <type>:<content>` flag into the API's `logs[]` array, preserving
85
+ * the order the flags were given in — the entries read as a narrative, so their order is
86
+ * part of the payload.
87
+ *
88
+ * Split on the FIRST colon only: log content is prose and routinely contains colons
89
+ * (`--log decision:"chose 3 FKs: one per target"` must keep `chose 3 FKs: one per target`).
90
+ */
91
+ export declare function parseLogFlags(values: readonly string[] | undefined, flag?: string): WorkLogEntry[];
92
+ /**
93
+ * Turn the API's "a status change needs a reason" 422 into an actionable instruction.
94
+ *
95
+ * The server rejects a status transition that carries no `logs[]`, and nothing is written at
96
+ * all when it does — so the caller has to re-run the command, and the message has to say
97
+ * with what. Every other error is returned untouched, so an unrelated 422 (a bad status
98
+ * value, a missing field) still prints exactly what the server said.
99
+ */
100
+ export declare function describeWorkLogRequirement(error: unknown, context: {
101
+ entity: 'intent' | 'mission';
102
+ statusChanged: boolean;
103
+ logCount: number;
104
+ }): unknown;
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Work log — the vocabulary, the reference → target dispatch and the `--log` batch parser.
3
+ *
4
+ * A work log entry targets exactly ONE of a task, an intent or a mission (intent IN-pjrs6e).
5
+ * Tasks were the only target until now, so `orbitmap log` assumed one; this module is the
6
+ * single place that decides which of the three a reference means, so the decision cannot
7
+ * drift between `orbitmap log` and the `--log` batches carried by `intent update`,
8
+ * `mission update` and `mission status`.
9
+ *
10
+ * Two rules the API owns and the CLI must NOT reimplement:
11
+ *
12
+ * - **`status_change` is server-authored.** The server knows `from` and `to` and composes
13
+ * that line itself; the Agent API rejects the type from a client. It is therefore absent
14
+ * from {@link WORK_LOG_TYPES} and rejected here with an explanation rather than forwarded
15
+ * for a 422.
16
+ * - **A status transition must carry at least one entry.** That is enforced server-side
17
+ * (422) — the CLI does not pre-check it, because it cannot know whether the status is
18
+ * actually changing without an extra round trip. {@link describeWorkLogRequirement} turns
19
+ * the server's rejection into something the caller can act on.
20
+ *
21
+ * References travel verbatim: `/intents/{id_or_number}/logs` and
22
+ * `/missions/{id_or_number}/logs` resolve display numbers server-side (see
23
+ * `tests/reference-passthrough.test.ts`), so nothing here rewrites an identifier — it only
24
+ * reads the prefix to pick the endpoint.
25
+ */
26
+ import { OrbitMapAPIError } from './errors.js';
27
+ import { OBJECT_PREFIXES, objectPrefix } from './id-resolve.js';
28
+ /**
29
+ * The log types a CLIENT may author (`WorkLog::CALLER_TYPES` server-side).
30
+ *
31
+ * Deliberately narrower than the column vocabulary: `status_change` is missing on purpose
32
+ * (see the module comment). Also deliberately narrower than the vibe types — the two groups
33
+ * do not overlap, mirroring `log_work` in orbitmap-mcp.
34
+ */
35
+ export const WORK_LOG_TYPES = ['note', 'code_change', 'decision', 'blocker'];
36
+ /** Log types only the server may write. Rejected here with an explanation, never forwarded. */
37
+ export const SERVER_AUTHORED_LOG_TYPES = ['status_change'];
38
+ /** Vibe-only types: valid for `orbitmap vibe log`, rejected by the work-log endpoints. */
39
+ export const VIBE_ONLY_TYPES = ['discovery', 'exploration', 'fix'];
40
+ /** The three objects a work log entry can attach to. */
41
+ export const WORK_LOG_TARGETS = ['task', 'intent', 'mission'];
42
+ /** Display-number prefix → the object a work log entry would attach to. */
43
+ const PREFIX_TARGETS = Object.freeze({
44
+ TS: 'task',
45
+ IN: 'intent',
46
+ MS: 'mission',
47
+ });
48
+ /**
49
+ * Render the `work_logs` a detail payload embeds.
50
+ *
51
+ * The API returns them oldest-first so they read as a narrative; this preserves
52
+ * that order rather than imposing a newest-first view. Returns an empty string
53
+ * when there is nothing to show, so callers can print unconditionally.
54
+ */
55
+ export function formatWorkLogs(logs) {
56
+ if (!logs || logs.length === 0)
57
+ return '';
58
+ const lines = logs.map((log) => {
59
+ const stamp = log.created_at ? log.created_at.slice(0, 16).replace('T', ' ') : '';
60
+ // Content is free text and frequently multi-line; indent continuation lines
61
+ // so an entry stays visually one block under its header.
62
+ const body = log.content.split('\n').join('\n ');
63
+ return ` [${log.type}] ${stamp}\n ${body}`;
64
+ });
65
+ return `\nWork log (${logs.length}):\n${lines.join('\n')}`;
66
+ }
67
+ /** The human-readable list of types, used verbatim in every message and in `--help`. */
68
+ export const WORK_LOG_TYPE_LIST = WORK_LOG_TYPES.join(', ');
69
+ /**
70
+ * Which object a `orbitmap log` reference names.
71
+ *
72
+ * A prefixed display number answers it outright (`TS-` → task, `IN-` → intent, `MS-` →
73
+ * mission). A bare uuid or bare object code carries no type at all, so it stays a **task** —
74
+ * the behaviour every existing caller has — unless `--target` says otherwise. That flag is
75
+ * the only way to log against an intent or a mission by uuid, and it is rejected when it
76
+ * contradicts a prefix rather than silently overriding it.
77
+ */
78
+ export function resolveWorkLogTarget(ref, explicit) {
79
+ const prefix = objectPrefix(ref);
80
+ const fromPrefix = prefix ? PREFIX_TARGETS[prefix] : undefined;
81
+ if (prefix && !fromPrefix) {
82
+ throw unsupportedPrefix(prefix, ref);
83
+ }
84
+ if (explicit === undefined) {
85
+ return fromPrefix ?? 'task';
86
+ }
87
+ const target = explicit.trim();
88
+ if (!WORK_LOG_TARGETS.includes(target)) {
89
+ throw OrbitMapAPIError.validation(`Invalid --target "${explicit}". Valid: ${WORK_LOG_TARGETS.join(', ')}`);
90
+ }
91
+ if (fromPrefix && fromPrefix !== target) {
92
+ throw OrbitMapAPIError.validation(`--target ${target} contradicts "${ref.trim()}", which is a ${fromPrefix} reference. ` +
93
+ `Drop --target, or pass the ${target}'s own id.`);
94
+ }
95
+ return target;
96
+ }
97
+ /** `IS-`/`ID-`/`VB-` and friends: a real object, but not one a work log entry can attach to. */
98
+ function unsupportedPrefix(prefix, ref) {
99
+ const kind = OBJECT_PREFIXES[prefix];
100
+ const base = `Work log entries attach to tasks, intents and missions. "${ref.trim()}" is ` +
101
+ `${kind ? `a ${kind}` : 'not one of them'}`;
102
+ return OrbitMapAPIError.validation(kind === 'vibe'
103
+ ? `${base} — use \`orbitmap vibe log --content "…"\` instead.`
104
+ : `${base} — pass a TS-, IN- or MS- reference instead.`);
105
+ }
106
+ /**
107
+ * Validate a single work-log type, with the two wrong-vocabulary cases named rather than
108
+ * lumped into "invalid". `undefined` is valid: the API defaults an absent type to `note`.
109
+ */
110
+ export function assertWorkLogType(type, label = 'log type') {
111
+ if (WORK_LOG_TYPES.includes(type)) {
112
+ return;
113
+ }
114
+ if (SERVER_AUTHORED_LOG_TYPES.includes(type)) {
115
+ throw OrbitMapAPIError.validation(`The ${label} "${type}" is written by the server on a status change — a client cannot ` +
116
+ `author it. Valid: ${WORK_LOG_TYPE_LIST}.`);
117
+ }
118
+ if (VIBE_ONLY_TYPES.includes(type)) {
119
+ throw OrbitMapAPIError.validation(`Log type "${type}" belongs to vibes, not work logs. Use ` +
120
+ `\`orbitmap vibe log --content "…" --type ${type}\`, or pick one of: ` +
121
+ `${WORK_LOG_TYPE_LIST}.`);
122
+ }
123
+ throw OrbitMapAPIError.validation(`Invalid ${label} "${type}". Valid: ${WORK_LOG_TYPE_LIST}`);
124
+ }
125
+ /**
126
+ * Parse a repeated `--log <type>:<content>` flag into the API's `logs[]` array, preserving
127
+ * the order the flags were given in — the entries read as a narrative, so their order is
128
+ * part of the payload.
129
+ *
130
+ * Split on the FIRST colon only: log content is prose and routinely contains colons
131
+ * (`--log decision:"chose 3 FKs: one per target"` must keep `chose 3 FKs: one per target`).
132
+ */
133
+ export function parseLogFlags(values, flag = '--log') {
134
+ if (!values || values.length === 0) {
135
+ return [];
136
+ }
137
+ return values.map((raw) => {
138
+ const separator = raw.indexOf(':');
139
+ if (separator === -1) {
140
+ throw OrbitMapAPIError.validation(`Invalid ${flag} value "${raw}" — expected <type>:<content>, e.g. ` +
141
+ `${flag} decision:"chose 3 FKs over a pivot". Types: ${WORK_LOG_TYPE_LIST}.`);
142
+ }
143
+ const type = raw.slice(0, separator).trim();
144
+ const content = raw.slice(separator + 1).trim();
145
+ assertWorkLogType(type, `${flag} type`);
146
+ if (content.length === 0) {
147
+ throw OrbitMapAPIError.validation(`Empty content in ${flag} "${raw}" — write what happened after the colon, e.g. ` +
148
+ `${flag} ${type}:"…".`);
149
+ }
150
+ return { type, content };
151
+ });
152
+ }
153
+ /**
154
+ * Turn the API's "a status change needs a reason" 422 into an actionable instruction.
155
+ *
156
+ * The server rejects a status transition that carries no `logs[]`, and nothing is written at
157
+ * all when it does — so the caller has to re-run the command, and the message has to say
158
+ * with what. Every other error is returned untouched, so an unrelated 422 (a bad status
159
+ * value, a missing field) still prints exactly what the server said.
160
+ */
161
+ export function describeWorkLogRequirement(error, context) {
162
+ if (!(error instanceof OrbitMapAPIError) || error.status !== 422) {
163
+ return error;
164
+ }
165
+ // Three independent signals, because only the first is guaranteed: the server names the
166
+ // rejection with its own code (`INTENT_STATUS_LOG_REQUIRED` / `MISSION_STATUS_LOG_REQUIRED`,
167
+ // observed on the provider), the message says so, or the shape of the call makes it the
168
+ // only possible reading — a transition was requested and no entry accompanied it.
169
+ const looksLikeMissingLogs = error.code.endsWith('_STATUS_LOG_REQUIRED') ||
170
+ /work log/i.test(error.message) ||
171
+ Object.keys(error.details ?? {}).some((field) => field === 'logs' || field.startsWith('logs.')) ||
172
+ (context.statusChanged && context.logCount === 0);
173
+ if (!looksLikeMissingLogs) {
174
+ return error;
175
+ }
176
+ return new OrbitMapAPIError(`${error.message.trim()}\n` +
177
+ `Changing this ${context.entity}'s status requires at least one work log entry, and ` +
178
+ `nothing was written. Re-run with --log <type>:<content>, e.g. ` +
179
+ `--log decision:"why this transition". Types: ${WORK_LOG_TYPE_LIST}.`, error.code, error.status, error.details);
180
+ }
181
+ //# sourceMappingURL=work-log.js.map