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.
- package/README.md +112 -18
- package/dist/adapters/cloud.d.ts +63 -21
- package/dist/adapters/cloud.js +108 -29
- package/dist/adapters/cloud.js.map +1 -1
- package/dist/adapters/factory.d.ts +3 -0
- package/dist/adapters/factory.js +8 -1
- package/dist/adapters/factory.js.map +1 -1
- package/dist/adapters/local/adapter.d.ts +17 -20
- package/dist/adapters/local/adapter.js +28 -21
- package/dist/adapters/local/adapter.js.map +1 -1
- package/dist/adapters/local/context.d.ts +1 -1
- package/dist/adapters/local/context.js +10 -2
- package/dist/adapters/local/context.js.map +1 -1
- package/dist/adapters/local/entities/planning.d.ts +5 -1
- package/dist/adapters/local/entities/planning.js +16 -3
- package/dist/adapters/local/entities/planning.js.map +1 -1
- package/dist/adapters/local/entities/tasks.d.ts +9 -1
- package/dist/adapters/local/entities/tasks.js +47 -5
- package/dist/adapters/local/entities/tasks.js.map +1 -1
- package/dist/adapters/local/entities/work-items.d.ts +14 -30
- package/dist/adapters/local/entities/work-items.js +63 -47
- package/dist/adapters/local/entities/work-items.js.map +1 -1
- package/dist/adapters/local/rules.d.ts +4 -4
- package/dist/adapters/local/rules.js +4 -19
- package/dist/adapters/local/rules.js.map +1 -1
- package/dist/adapters/local/shapes.d.ts +27 -7
- package/dist/adapters/local/shapes.js +41 -5
- package/dist/adapters/local/shapes.js.map +1 -1
- package/dist/adapters/local/store.d.ts +55 -6
- package/dist/adapters/local/store.js +122 -8
- package/dist/adapters/local/store.js.map +1 -1
- package/dist/adapters/local/types.d.ts +11 -2
- package/dist/adapters/local/types.js +9 -2
- package/dist/adapters/local/types.js.map +1 -1
- package/dist/adapters/types.d.ts +30 -20
- package/dist/agent-instructions.d.ts +43 -12
- package/dist/agent-instructions.js +1133 -170
- package/dist/agent-instructions.js.map +1 -1
- package/dist/area-directories.d.ts +75 -0
- package/dist/area-directories.js +162 -0
- package/dist/area-directories.js.map +1 -0
- package/dist/area-grouping.d.ts +55 -0
- package/dist/area-grouping.js +72 -0
- package/dist/area-grouping.js.map +1 -0
- package/dist/commands/areas.js +30 -2
- package/dist/commands/areas.js.map +1 -1
- package/dist/commands/assign.d.ts +1 -0
- package/dist/commands/assign.js +2 -0
- package/dist/commands/assign.js.map +1 -1
- package/dist/commands/context.d.ts +95 -0
- package/dist/commands/context.js +104 -2
- package/dist/commands/context.js.map +1 -1
- package/dist/commands/create.d.ts +1 -0
- package/dist/commands/create.js +19 -14
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/dep.js +5 -8
- package/dist/commands/dep.js.map +1 -1
- package/dist/commands/doc-import.d.ts +1 -0
- package/dist/commands/doc-import.js +8 -26
- package/dist/commands/doc-import.js.map +1 -1
- package/dist/commands/doc-patch.js +16 -52
- package/dist/commands/doc-patch.js.map +1 -1
- package/dist/commands/doc-share.js +4 -0
- package/dist/commands/doc-share.js.map +1 -1
- package/dist/commands/doc-update.js +4 -8
- package/dist/commands/doc-update.js.map +1 -1
- package/dist/commands/doc.js +7 -12
- package/dist/commands/doc.js.map +1 -1
- package/dist/commands/docs-cache.js +23 -10
- package/dist/commands/docs-cache.js.map +1 -1
- package/dist/commands/docs.js +29 -10
- package/dist/commands/docs.js.map +1 -1
- package/dist/commands/get.js +8 -10
- package/dist/commands/get.js.map +1 -1
- package/dist/commands/ideas.d.ts +3 -1
- package/dist/commands/ideas.js +26 -49
- package/dist/commands/ideas.js.map +1 -1
- package/dist/commands/init.d.ts +24 -1
- package/dist/commands/init.js +0 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/intent.d.ts +16 -0
- package/dist/commands/intent.js +61 -18
- package/dist/commands/intent.js.map +1 -1
- package/dist/commands/issues.d.ts +3 -1
- package/dist/commands/issues.js +47 -72
- package/dist/commands/issues.js.map +1 -1
- package/dist/commands/log.d.ts +14 -2
- package/dist/commands/log.js +35 -20
- package/dist/commands/log.js.map +1 -1
- package/dist/commands/mission.d.ts +8 -0
- package/dist/commands/mission.js +58 -18
- package/dist/commands/mission.js.map +1 -1
- package/dist/commands/orbit.d.ts +7 -1
- package/dist/commands/orbit.js +13 -24
- package/dist/commands/orbit.js.map +1 -1
- package/dist/commands/setup-agent.d.ts +97 -4
- package/dist/commands/setup-agent.js +273 -40
- package/dist/commands/setup-agent.js.map +1 -1
- package/dist/commands/setup-mcp.d.ts +2 -0
- package/dist/commands/setup-mcp.js +17 -40
- package/dist/commands/setup-mcp.js.map +1 -1
- package/dist/commands/start.d.ts +43 -0
- package/dist/commands/start.js +158 -3
- package/dist/commands/start.js.map +1 -1
- package/dist/commands/status.d.ts +1 -0
- package/dist/commands/status.js +5 -15
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/subtask.d.ts +1 -0
- package/dist/commands/subtask.js +9 -2
- package/dist/commands/subtask.js.map +1 -1
- package/dist/commands/task-edit-content.js +4 -8
- package/dist/commands/task-edit-content.js.map +1 -1
- package/dist/commands/task.d.ts +7 -0
- package/dist/commands/task.js +5 -2
- package/dist/commands/task.js.map +1 -1
- package/dist/commands/tasks.d.ts +35 -0
- package/dist/commands/tasks.js +45 -9
- package/dist/commands/tasks.js.map +1 -1
- package/dist/commands/vibes.d.ts +3 -1
- package/dist/commands/vibes.js +41 -31
- package/dist/commands/vibes.js.map +1 -1
- package/dist/config.d.ts +11 -0
- package/dist/config.js +14 -1
- package/dist/config.js.map +1 -1
- package/dist/doc-cache.d.ts +74 -5
- package/dist/doc-cache.js +106 -22
- package/dist/doc-cache.js.map +1 -1
- package/dist/enums.d.ts +26 -0
- package/dist/enums.js +53 -0
- package/dist/enums.js.map +1 -0
- package/dist/errors.d.ts +67 -0
- package/dist/errors.js +82 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.js +169 -60
- package/dist/index.js.map +1 -1
- package/dist/mcp-config.d.ts +42 -1
- package/dist/mcp-config.js +82 -2
- package/dist/mcp-config.js.map +1 -1
- package/dist/oauth.js +10 -10
- package/dist/output.d.ts +12 -0
- package/dist/output.js +39 -1
- package/dist/output.js.map +1 -1
- package/dist/paths.d.ts +40 -0
- package/dist/paths.js +174 -9
- package/dist/paths.js.map +1 -1
- package/dist/project-config.d.ts +42 -1
- package/dist/project-config.js +51 -0
- package/dist/project-config.js.map +1 -1
- package/dist/work-log.d.ts +104 -0
- package/dist/work-log.js +181 -0
- package/dist/work-log.js.map +1 -0
- package/dist/workspace-resolve.d.ts +179 -9
- package/dist/workspace-resolve.js +288 -20
- package/dist/workspace-resolve.js.map +1 -1
- package/dist/write-target.d.ts +56 -0
- package/dist/write-target.js +170 -0
- package/dist/write-target.js.map +1 -0
- package/package.json +3 -2
- package/dist/commands/create-project.d.ts +0 -6
- package/dist/commands/create-project.js +0 -28
- package/dist/commands/create-project.js.map +0 -1
- package/dist/commands/projects.d.ts +0 -3
- package/dist/commands/projects.js +0 -28
- 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
|
-
|
|
148
|
-
|
|
149
|
-
!globals.has(canonical(
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
252
|
+
break;
|
|
155
253
|
const parent = dirname(dir);
|
|
156
254
|
if (parent === dir)
|
|
157
|
-
|
|
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":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"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"}
|
package/dist/project-config.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
package/dist/project-config.js
CHANGED
|
@@ -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;
|
|
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;
|
package/dist/work-log.js
ADDED
|
@@ -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
|