orbitmap 0.4.3 → 0.4.6

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.
@@ -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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"work-log.js","sourceRoot":"","sources":["../src/work-log.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orbitmap",
3
- "version": "0.4.3",
3
+ "version": "0.4.6",
4
4
  "description": "Project management CLI for AI coding agents — works with Gemini CLI, GPT Codex, Claude Code, and any agent with shell access.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -47,6 +47,7 @@
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/node": "^25.3.0",
50
+ "@vitest/coverage-v8": "^3.2.7",
50
51
  "typescript": "^5.9.3",
51
52
  "vitest": "^3.2.4"
52
53
  }