orbitmap 0.4.6 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +73 -10
  3. package/dist/adapters/cloud.d.ts +85 -10
  4. package/dist/adapters/cloud.js +118 -17
  5. package/dist/adapters/cloud.js.map +1 -1
  6. package/dist/adapters/factory.js +7 -0
  7. package/dist/adapters/factory.js.map +1 -1
  8. package/dist/adapters/local/adapter.d.ts +47 -8
  9. package/dist/adapters/local/adapter.js +37 -17
  10. package/dist/adapters/local/adapter.js.map +1 -1
  11. package/dist/adapters/local/context.d.ts +13 -1
  12. package/dist/adapters/local/context.js +7 -2
  13. package/dist/adapters/local/context.js.map +1 -1
  14. package/dist/adapters/local/entities/documents.d.ts +18 -4
  15. package/dist/adapters/local/entities/documents.js +46 -7
  16. package/dist/adapters/local/entities/documents.js.map +1 -1
  17. package/dist/adapters/local/entities/planning.d.ts +36 -7
  18. package/dist/adapters/local/entities/planning.js +245 -39
  19. package/dist/adapters/local/entities/planning.js.map +1 -1
  20. package/dist/adapters/local/entities/tasks.d.ts +16 -7
  21. package/dist/adapters/local/entities/tasks.js +55 -42
  22. package/dist/adapters/local/entities/tasks.js.map +1 -1
  23. package/dist/adapters/local/entities/work-items.d.ts +28 -7
  24. package/dist/adapters/local/entities/work-items.js +111 -39
  25. package/dist/adapters/local/entities/work-items.js.map +1 -1
  26. package/dist/adapters/local/lists.d.ts +139 -0
  27. package/dist/adapters/local/lists.js +230 -0
  28. package/dist/adapters/local/lists.js.map +1 -0
  29. package/dist/adapters/local/rules.d.ts +1 -1
  30. package/dist/adapters/local/rules.js +23 -0
  31. package/dist/adapters/local/rules.js.map +1 -1
  32. package/dist/adapters/local/shapes.d.ts +165 -22
  33. package/dist/adapters/local/shapes.js +338 -50
  34. package/dist/adapters/local/shapes.js.map +1 -1
  35. package/dist/adapters/local/types.d.ts +6 -1
  36. package/dist/adapters/local/types.js.map +1 -1
  37. package/dist/adapters/types.d.ts +72 -9
  38. package/dist/agent-instructions.d.ts +41 -11
  39. package/dist/agent-instructions.js +329 -128
  40. package/dist/agent-instructions.js.map +1 -1
  41. package/dist/commands/context.d.ts +62 -37
  42. package/dist/commands/context.js +105 -36
  43. package/dist/commands/context.js.map +1 -1
  44. package/dist/commands/docs.d.ts +4 -0
  45. package/dist/commands/docs.js +12 -5
  46. package/dist/commands/docs.js.map +1 -1
  47. package/dist/commands/emit-instructions.d.ts +26 -0
  48. package/dist/commands/emit-instructions.js +68 -0
  49. package/dist/commands/emit-instructions.js.map +1 -0
  50. package/dist/commands/ideas.d.ts +3 -0
  51. package/dist/commands/ideas.js +14 -7
  52. package/dist/commands/ideas.js.map +1 -1
  53. package/dist/commands/intent.d.ts +26 -0
  54. package/dist/commands/intent.js +117 -8
  55. package/dist/commands/intent.js.map +1 -1
  56. package/dist/commands/issues.d.ts +4 -0
  57. package/dist/commands/issues.js +15 -7
  58. package/dist/commands/issues.js.map +1 -1
  59. package/dist/commands/mission.d.ts +2 -0
  60. package/dist/commands/mission.js +9 -5
  61. package/dist/commands/mission.js.map +1 -1
  62. package/dist/commands/projects.d.ts +3 -0
  63. package/dist/commands/projects.js +28 -0
  64. package/dist/commands/projects.js.map +1 -0
  65. package/dist/commands/setup-agent.d.ts +101 -13
  66. package/dist/commands/setup-agent.js +377 -31
  67. package/dist/commands/setup-agent.js.map +1 -1
  68. package/dist/commands/start.d.ts +13 -0
  69. package/dist/commands/start.js +126 -13
  70. package/dist/commands/start.js.map +1 -1
  71. package/dist/commands/tasks.d.ts +23 -5
  72. package/dist/commands/tasks.js +46 -23
  73. package/dist/commands/tasks.js.map +1 -1
  74. package/dist/commands/vibes.d.ts +3 -0
  75. package/dist/commands/vibes.js +11 -5
  76. package/dist/commands/vibes.js.map +1 -1
  77. package/dist/errors.d.ts +23 -2
  78. package/dist/errors.js +31 -3
  79. package/dist/errors.js.map +1 -1
  80. package/dist/index.js +118 -38
  81. package/dist/index.js.map +1 -1
  82. package/dist/list-meta.d.ts +117 -0
  83. package/dist/list-meta.js +160 -0
  84. package/dist/list-meta.js.map +1 -0
  85. package/dist/list-options.d.ts +33 -0
  86. package/dist/list-options.js +70 -0
  87. package/dist/list-options.js.map +1 -0
  88. package/dist/workspace-resolve.d.ts +47 -1
  89. package/dist/workspace-resolve.js +74 -11
  90. package/dist/workspace-resolve.js.map +1 -1
  91. package/dist/write-target.d.ts +9 -0
  92. package/dist/write-target.js +11 -2
  93. package/dist/write-target.js.map +1 -1
  94. package/package.json +1 -1
@@ -0,0 +1,117 @@
1
+ /**
2
+ * The honest half of a list: what the API actually answered, and what it left out.
3
+ *
4
+ * Agent API 9.0.0 (IN-nahetc) made every list span every reachable area and every assignee,
5
+ * paged it with an opaque keyset `cursor`, and put the whole story in `meta`: the grand
6
+ * `total`, the per-area shown-of-total split, and an `applied_filters` block that is ALWAYS
7
+ * present — not only when the result is empty or truncated. That last part is the point. A
8
+ * short list used to be ambiguous between "nothing exists" and "you did not ask for it", and
9
+ * the reported bug (`orbitmap tasks` showing one area's worth of one agent's work) was
10
+ * invisible precisely because nothing on screen said what had been filtered out.
11
+ *
12
+ * So every list command prints the same three lines under its rows:
13
+ *
14
+ * - the **scope** line — the filters that produced this result, spelled out;
15
+ * - the **truncation** line — `30 of 143 shown across 7 areas`, and how to get the rest;
16
+ * - the **hint** line — `meta.hint`, when the server noticed the page is mostly one intent.
17
+ *
18
+ * Everything here degrades to silence: local mode answers `{count}` and nothing else, and an
19
+ * older API answers without the new keys. A missing key prints no line rather than a lie.
20
+ */
21
+ /** One area's contribution to a page: what it showed, and what it holds. */
22
+ export interface PerAreaCount {
23
+ slug: string | null;
24
+ shown: number;
25
+ total: number;
26
+ }
27
+ /** `meta.applied_filters` — the caller's question, echoed back by the server. */
28
+ export interface AppliedFilters {
29
+ /** `all reachable (7)` or `demo,web (2 of 7)`; null for a list with no area dimension. */
30
+ areas?: string | null;
31
+ /** The statuses kept, comma-joined, or `all`. */
32
+ status?: string | null;
33
+ /** How many rows the status filter removed. */
34
+ hidden_by_status?: number;
35
+ /** `any`, `me`, `unassigned`, or an agent slug. */
36
+ assignee?: string | null;
37
+ intent?: string | null;
38
+ mission?: string | null;
39
+ limit?: number;
40
+ }
41
+ /** `meta.hint` — the intent nudge. It never reorders or filters rows; it only says a thing. */
42
+ export interface ListHint {
43
+ type?: string;
44
+ intent_number?: string;
45
+ intent_name?: string;
46
+ count?: number;
47
+ flag?: string;
48
+ message?: string;
49
+ }
50
+ /** The list envelope's `meta`, as of API 9.0.0. Every field past `count` is optional. */
51
+ export interface ListMeta {
52
+ count?: number;
53
+ total?: number;
54
+ next_cursor?: string | null;
55
+ per_area?: PerAreaCount[];
56
+ applied_filters?: AppliedFilters;
57
+ scope?: string;
58
+ workspace?: {
59
+ id?: string;
60
+ slug?: string;
61
+ name?: string;
62
+ };
63
+ hint?: ListHint;
64
+ }
65
+ /** The list envelope itself. */
66
+ export interface ListEnvelope<T> {
67
+ data: T[];
68
+ meta?: ListMeta;
69
+ }
70
+ /**
71
+ * The scope line: `applied_filters`, rendered for a human.
72
+ *
73
+ * `undefined` when the server said nothing about its filters (local mode, or an API older
74
+ * than 9.0.0) — silence beats inventing a scope nobody asserted.
75
+ */
76
+ export declare function formatScopeLine(meta: ListMeta | undefined): string | undefined;
77
+ /**
78
+ * The truncation line, or `undefined` when the whole result is on screen.
79
+ *
80
+ * Driven by `total` vs the rows actually printed, so it is right for a single page AND for
81
+ * an assembled `--all` run (where `next_cursor` has been followed to null and nothing is
82
+ * missing any more).
83
+ */
84
+ export declare function formatTruncationLine(meta: ListMeta | undefined, shown: number): string | undefined;
85
+ /** `meta.hint`'s message, when the server sent one. */
86
+ export declare function formatHintLine(meta: ListMeta | undefined): string | undefined;
87
+ /**
88
+ * Print the three honest lines under a result, in a fixed order: what was hidden by paging,
89
+ * what the server noticed, then the scope that produced all of it.
90
+ *
91
+ * The hint is printed BETWEEN them rather than woven into the rows on purpose — it must
92
+ * never reorder or filter what was listed (the provider is explicit about this, and implicit
93
+ * session scoping was rejected twice on this intent).
94
+ */
95
+ export declare function printListMeta(meta: ListMeta | undefined, shown: number, log?: (line: string) => void): void;
96
+ /**
97
+ * The empty answer, with the reason attached: `No tasks. Scope: all 7 reachable areas,
98
+ * status todo,in_progress (46 hidden, add --status all), assignee any.`
99
+ *
100
+ * One line, deliberately: "No tasks." alone is what made the reported bug survive so long.
101
+ */
102
+ export declare function formatEmptyLine(noun: string, meta: ListMeta | undefined): string;
103
+ /** How many pages `fetchAllPages` will ever ask for, however the server behaves. */
104
+ export declare const MAX_PAGES = 200;
105
+ /**
106
+ * Follow `meta.next_cursor` until it is null and return the assembled list.
107
+ *
108
+ * This is the ONLY way to see a workspace with more rows than the page cap (100) in full —
109
+ * raising the limit is a bigger number, not an exit. The assembled `meta` keeps the first
110
+ * page's `total` and `applied_filters` (they describe the whole query, not the page),
111
+ * reports `count` as everything fetched, recomputes each area's `shown` from the assembled
112
+ * rows, and clears `next_cursor`, because after this there is genuinely no next page.
113
+ *
114
+ * A server that keeps handing back the same cursor, or one that pages forever, is stopped by
115
+ * {@link MAX_PAGES} rather than hanging the terminal.
116
+ */
117
+ export declare function fetchAllPages<T>(fetchPage: (cursor?: string) => Promise<ListEnvelope<T>>, areaSlugOf?: (row: T) => string | null | undefined): Promise<ListEnvelope<T>>;
@@ -0,0 +1,160 @@
1
+ /**
2
+ * The honest half of a list: what the API actually answered, and what it left out.
3
+ *
4
+ * Agent API 9.0.0 (IN-nahetc) made every list span every reachable area and every assignee,
5
+ * paged it with an opaque keyset `cursor`, and put the whole story in `meta`: the grand
6
+ * `total`, the per-area shown-of-total split, and an `applied_filters` block that is ALWAYS
7
+ * present — not only when the result is empty or truncated. That last part is the point. A
8
+ * short list used to be ambiguous between "nothing exists" and "you did not ask for it", and
9
+ * the reported bug (`orbitmap tasks` showing one area's worth of one agent's work) was
10
+ * invisible precisely because nothing on screen said what had been filtered out.
11
+ *
12
+ * So every list command prints the same three lines under its rows:
13
+ *
14
+ * - the **scope** line — the filters that produced this result, spelled out;
15
+ * - the **truncation** line — `30 of 143 shown across 7 areas`, and how to get the rest;
16
+ * - the **hint** line — `meta.hint`, when the server noticed the page is mostly one intent.
17
+ *
18
+ * Everything here degrades to silence: local mode answers `{count}` and nothing else, and an
19
+ * older API answers without the new keys. A missing key prints no line rather than a lie.
20
+ */
21
+ /**
22
+ * The scope line: `applied_filters`, rendered for a human.
23
+ *
24
+ * `undefined` when the server said nothing about its filters (local mode, or an API older
25
+ * than 9.0.0) — silence beats inventing a scope nobody asserted.
26
+ */
27
+ export function formatScopeLine(meta) {
28
+ const filters = meta?.applied_filters;
29
+ if (!filters)
30
+ return undefined;
31
+ const parts = [];
32
+ if (typeof filters.areas === 'string' && filters.areas !== '') {
33
+ // `all reachable (7)` reads as a machine field; `all 7 reachable areas` reads as an
34
+ // answer. An explicit narrowing (`demo,web (2 of 7)`) is already legible, so it is
35
+ // passed through verbatim — including the count that says how much it hid.
36
+ const everything = /^all reachable \((\d+)\)$/.exec(filters.areas);
37
+ parts.push(everything ? `all ${everything[1]} reachable areas` : `areas ${filters.areas}`);
38
+ }
39
+ if (typeof filters.status === 'string' && filters.status !== '') {
40
+ const hidden = filters.hidden_by_status ?? 0;
41
+ parts.push(hidden > 0
42
+ ? `status ${filters.status} (${hidden} hidden, add --status all)`
43
+ : `status ${filters.status}`);
44
+ }
45
+ if (typeof filters.assignee === 'string' && filters.assignee !== '') {
46
+ parts.push(`assignee ${filters.assignee}`);
47
+ }
48
+ if (filters.intent)
49
+ parts.push(`intent ${filters.intent}`);
50
+ if (filters.mission)
51
+ parts.push(`mission ${filters.mission}`);
52
+ if (parts.length === 0)
53
+ return undefined;
54
+ return `Scope: ${parts.join(', ')}.`;
55
+ }
56
+ /**
57
+ * The truncation line, or `undefined` when the whole result is on screen.
58
+ *
59
+ * Driven by `total` vs the rows actually printed, so it is right for a single page AND for
60
+ * an assembled `--all` run (where `next_cursor` has been followed to null and nothing is
61
+ * missing any more).
62
+ */
63
+ export function formatTruncationLine(meta, shown) {
64
+ const total = meta?.total;
65
+ if (typeof total !== 'number' || shown >= total)
66
+ return undefined;
67
+ const areas = meta?.per_area?.length ?? 0;
68
+ const where = areas > 1 ? ` across ${areas} areas` : '';
69
+ return `${shown} of ${total} shown${where} — run with --all for every row.`;
70
+ }
71
+ /** `meta.hint`'s message, when the server sent one. */
72
+ export function formatHintLine(meta) {
73
+ const hint = meta?.hint;
74
+ if (!hint)
75
+ return undefined;
76
+ if (typeof hint.message === 'string' && hint.message !== '')
77
+ return hint.message;
78
+ if (typeof hint.flag === 'string' && hint.flag !== '')
79
+ return `Focus with ${hint.flag}.`;
80
+ return undefined;
81
+ }
82
+ /**
83
+ * Print the three honest lines under a result, in a fixed order: what was hidden by paging,
84
+ * what the server noticed, then the scope that produced all of it.
85
+ *
86
+ * The hint is printed BETWEEN them rather than woven into the rows on purpose — it must
87
+ * never reorder or filter what was listed (the provider is explicit about this, and implicit
88
+ * session scoping was rejected twice on this intent).
89
+ */
90
+ export function printListMeta(meta, shown, log = console.log) {
91
+ const lines = [
92
+ formatTruncationLine(meta, shown),
93
+ formatHintLine(meta),
94
+ formatScopeLine(meta),
95
+ ].filter((line) => line !== undefined);
96
+ if (lines.length === 0)
97
+ return;
98
+ log('');
99
+ for (const line of lines)
100
+ log(line);
101
+ }
102
+ /**
103
+ * The empty answer, with the reason attached: `No tasks. Scope: all 7 reachable areas,
104
+ * status todo,in_progress (46 hidden, add --status all), assignee any.`
105
+ *
106
+ * One line, deliberately: "No tasks." alone is what made the reported bug survive so long.
107
+ */
108
+ export function formatEmptyLine(noun, meta) {
109
+ const scope = formatScopeLine(meta);
110
+ return scope ? `No ${noun}. ${scope}` : `No ${noun} found.`;
111
+ }
112
+ /** How many pages `fetchAllPages` will ever ask for, however the server behaves. */
113
+ export const MAX_PAGES = 200;
114
+ /**
115
+ * Follow `meta.next_cursor` until it is null and return the assembled list.
116
+ *
117
+ * This is the ONLY way to see a workspace with more rows than the page cap (100) in full —
118
+ * raising the limit is a bigger number, not an exit. The assembled `meta` keeps the first
119
+ * page's `total` and `applied_filters` (they describe the whole query, not the page),
120
+ * reports `count` as everything fetched, recomputes each area's `shown` from the assembled
121
+ * rows, and clears `next_cursor`, because after this there is genuinely no next page.
122
+ *
123
+ * A server that keeps handing back the same cursor, or one that pages forever, is stopped by
124
+ * {@link MAX_PAGES} rather than hanging the terminal.
125
+ */
126
+ export async function fetchAllPages(fetchPage, areaSlugOf = () => undefined) {
127
+ const rows = [];
128
+ const seenCursors = new Set();
129
+ let first;
130
+ let cursor;
131
+ let pages = 0;
132
+ do {
133
+ const page = await fetchPage(cursor);
134
+ first ??= page;
135
+ rows.push(...(page.data ?? []));
136
+ const next = page.meta?.next_cursor;
137
+ cursor = typeof next === 'string' && next !== '' ? next : undefined;
138
+ // A repeated cursor means the server is not advancing; taking it again would loop.
139
+ if (cursor !== undefined && seenCursors.has(cursor))
140
+ cursor = undefined;
141
+ if (cursor !== undefined)
142
+ seenCursors.add(cursor);
143
+ } while (cursor !== undefined && ++pages < MAX_PAGES);
144
+ const meta = { ...(first?.meta ?? {}) };
145
+ meta.count = rows.length;
146
+ meta.next_cursor = null;
147
+ if (meta.per_area) {
148
+ const shown = new Map();
149
+ for (const row of rows) {
150
+ const slug = areaSlugOf(row) ?? null;
151
+ shown.set(slug, (shown.get(slug) ?? 0) + 1);
152
+ }
153
+ meta.per_area = meta.per_area.map((entry) => ({
154
+ ...entry,
155
+ shown: shown.get(entry.slug) ?? 0,
156
+ }));
157
+ }
158
+ return { data: rows, meta };
159
+ }
160
+ //# sourceMappingURL=list-meta.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-meta.js","sourceRoot":"","sources":["../src/list-meta.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAoDH;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,IAA0B;IACxD,MAAM,OAAO,GAAG,IAAI,EAAE,eAAe,CAAC;IACtC,IAAI,CAAC,OAAO;QAAE,OAAO,SAAS,CAAC;IAE/B,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,IAAI,OAAO,OAAO,CAAC,KAAK,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,KAAK,EAAE,EAAE,CAAC;QAC9D,oFAAoF;QACpF,mFAAmF;QACnF,2EAA2E;QAC3E,MAAM,UAAU,GAAG,2BAA2B,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACnE,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,OAAO,UAAU,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,SAAS,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;IAC7F,CAAC;IAED,IAAI,OAAO,OAAO,CAAC,MAAM,KAAK,QAAQ,IAAI,OAAO,CAAC,MAAM,KAAK,EAAE,EAAE,CAAC;QAChE,MAAM,MAAM,GAAG,OAAO,CAAC,gBAAgB,IAAI,CAAC,CAAC;QAC7C,KAAK,CAAC,IAAI,CACR,MAAM,GAAG,CAAC;YACR,CAAC,CAAC,UAAU,OAAO,CAAC,MAAM,KAAK,MAAM,4BAA4B;YACjE,CAAC,CAAC,UAAU,OAAO,CAAC,MAAM,EAAE,CAC/B,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,OAAO,CAAC,QAAQ,KAAK,QAAQ,IAAI,OAAO,CAAC,QAAQ,KAAK,EAAE,EAAE,CAAC;QACpE,KAAK,CAAC,IAAI,CAAC,YAAY,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC7C,CAAC;IAED,IAAI,OAAO,CAAC,MAAM;QAAE,KAAK,CAAC,IAAI,CAAC,UAAU,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC3D,IAAI,OAAO,CAAC,OAAO;QAAE,KAAK,CAAC,IAAI,CAAC,WAAW,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IAE9D,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAEzC,OAAO,UAAU,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;AACvC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAClC,IAA0B,EAC1B,KAAa;IAEb,MAAM,KAAK,GAAG,IAAI,EAAE,KAAK,CAAC;IAC1B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,IAAI,KAAK;QAAE,OAAO,SAAS,CAAC;IAElE,MAAM,KAAK,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC,CAAC;IAC1C,MAAM,KAAK,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;IAExD,OAAO,GAAG,KAAK,OAAO,KAAK,SAAS,KAAK,kCAAkC,CAAC;AAC9E,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,cAAc,CAAC,IAA0B;IACvD,MAAM,IAAI,GAAG,IAAI,EAAE,IAAI,CAAC;IACxB,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAC5B,IAAI,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ,IAAI,IAAI,CAAC,OAAO,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC,OAAO,CAAC;IACjF,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,KAAK,EAAE;QAAE,OAAO,cAAc,IAAI,CAAC,IAAI,GAAG,CAAC;IACzF,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAC3B,IAA0B,EAC1B,KAAa,EACb,MAA8B,OAAO,CAAC,GAAG;IAEzC,MAAM,KAAK,GAAG;QACZ,oBAAoB,CAAC,IAAI,EAAE,KAAK,CAAC;QACjC,cAAc,CAAC,IAAI,CAAC;QACpB,eAAe,CAAC,IAAI,CAAC;KACtB,CAAC,MAAM,CAAC,CAAC,IAAI,EAAkB,EAAE,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC;IAEvD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO;IAE/B,GAAG,CAAC,EAAE,CAAC,CAAC;IACR,KAAK,MAAM,IAAI,IAAI,KAAK;QAAE,GAAG,CAAC,IAAI,CAAC,CAAC;AACtC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY,EAAE,IAA0B;IACtE,MAAM,KAAK,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;IACpC,OAAO,KAAK,CAAC,CAAC,CAAC,MAAM,IAAI,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,IAAI,SAAS,CAAC;AAC9D,CAAC;AAED,oFAAoF;AACpF,MAAM,CAAC,MAAM,SAAS,GAAG,GAAG,CAAC;AAE7B;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,SAAwD,EACxD,aAAoD,GAAG,EAAE,CAAC,SAAS;IAEnE,MAAM,IAAI,GAAQ,EAAE,CAAC;IACrB,MAAM,WAAW,GAAG,IAAI,GAAG,EAAU,CAAC;IAEtC,IAAI,KAAkC,CAAC;IACvC,IAAI,MAA0B,CAAC;IAC/B,IAAI,KAAK,GAAG,CAAC,CAAC;IAEd,GAAG,CAAC;QACF,MAAM,IAAI,GAAoB,MAAM,SAAS,CAAC,MAAM,CAAC,CAAC;QACtD,KAAK,KAAK,IAAI,CAAC;QACf,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC;QAEhC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC;QACpC,MAAM,GAAG,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;QAEpE,mFAAmF;QACnF,IAAI,MAAM,KAAK,SAAS,IAAI,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC;YAAE,MAAM,GAAG,SAAS,CAAC;QACxE,IAAI,MAAM,KAAK,SAAS;YAAE,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACpD,CAAC,QAAQ,MAAM,KAAK,SAAS,IAAI,EAAE,KAAK,GAAG,SAAS,EAAE;IAEtD,MAAM,IAAI,GAAa,EAAE,GAAG,CAAC,KAAK,EAAE,IAAI,IAAI,EAAE,CAAC,EAAE,CAAC;IAClD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC;IACzB,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;IAExB,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAClB,MAAM,KAAK,GAAG,IAAI,GAAG,EAAyB,CAAC;QAC/C,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,MAAM,IAAI,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC;YACrC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC9C,CAAC;QACD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YAC5C,GAAG,KAAK;YACR,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;SAClC,CAAC,CAAC,CAAC;IACN,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AAC9B,CAAC"}
@@ -0,0 +1,33 @@
1
+ /** The sentence every area-scoped list carries: what it spans, and how to narrow it. */
2
+ export declare const LIST_SCOPE_NOTE: string;
3
+ export declare const LIMIT_HELP = "Rows per page (default 30, max 100)";
4
+ export declare const ALL_HELP = "Follow every page to the end and print every row";
5
+ export declare const TASKS_DESCRIPTION: string;
6
+ export declare const ISSUES_DESCRIPTION = "List issues. Spans every area you can reach, not just this directory's \u2014 pass --area <slug[,slug]> to narrow";
7
+ export declare const IDEAS_DESCRIPTION = "List ideas. Spans every area you can reach, not just this directory's \u2014 pass --area <slug[,slug]> to narrow";
8
+ export declare const VIBES_DESCRIPTION = "List vibes. Spans every area you can reach, not just this directory's \u2014 pass --area <slug[,slug]> to narrow";
9
+ export declare const DOCS_DESCRIPTION: string;
10
+ export declare const INTENTS_DESCRIPTION = "List intents \u2014 workspace-wide, every assignee";
11
+ export declare const MISSIONS_DESCRIPTION = "List missions \u2014 workspace-wide, every assignee";
12
+ /** `all` is accepted by every list since API 9.0.0 and is the one spelling of "do not filter". */
13
+ export declare const TASK_STATUS_HELP = "Filter by status (comma-separated, or `all`; default todo,in_progress)";
14
+ export declare const ISSUE_STATUS_HELP = "Filter by status (comma-separated, or `all`; default open,investigating)";
15
+ export declare const IDEA_STATUS_HELP = "Filter by status (comma-separated, or `all`; default pending,accepted)";
16
+ export declare const INTENT_STATUS_HELP = "Filter by status (comma-separated, or `all`; default new,design,plan,build,in_review)";
17
+ export declare const MISSION_STATUS_HELP = "Filter by status (comma-separated, or `all`; default new,active)";
18
+ /**
19
+ * The commands that accept `--area a,b`, i.e. the ones where an area is a FILTER.
20
+ *
21
+ * Everywhere else an area is the write target or the lookup context and exactly one is
22
+ * required: silently taking the first of several would create an object in an area the caller
23
+ * did not name — the IS-vse335 failure with extra steps. Names are `command` or
24
+ * `parent command`, matching how a user types them.
25
+ */
26
+ export declare const MULTI_AREA_COMMANDS: readonly string[];
27
+ /** `tasks`, or `issue list` — the path a user would type, for the check below. */
28
+ export declare function commandPath(name: string, parent?: string): string;
29
+ /**
30
+ * Reject `--area a,b` on a command that needs exactly one area, naming what to do instead.
31
+ * Returns the message, or `undefined` when the invocation is fine.
32
+ */
33
+ export declare function multiAreaRejection(path: string, area: string | undefined): string | undefined;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * The shared option surface of the list commands (TS-rhpn2n).
3
+ *
4
+ * Lives outside `src/index.ts` so it can be read by a test: `index.ts` calls
5
+ * `program.parse()` at import time, so nothing can import it, and the help text is part of
6
+ * this deliverable rather than decoration. The `tasks` description used to read "List your
7
+ * assigned tasks" while the API hard-filtered `agent_id = caller` — an agent that could not
8
+ * see a task it had been given had no way to learn that the list was lying by omission. That
9
+ * is exactly how the bug behind IN-nahetc was reported, which is why every list now states
10
+ * what it spans and what would narrow it.
11
+ */
12
+ import { ALL_AREAS, splitAreaRefs } from './workspace-resolve.js';
13
+ /** The sentence every area-scoped list carries: what it spans, and how to narrow it. */
14
+ export const LIST_SCOPE_NOTE = "Spans every area you can reach, not just this directory's — pass --area <slug[,slug]> " +
15
+ 'to narrow';
16
+ export const LIMIT_HELP = 'Rows per page (default 30, max 100)';
17
+ export const ALL_HELP = 'Follow every page to the end and print every row';
18
+ export const TASKS_DESCRIPTION = 'List tasks across EVERY area you can reach and EVERY assignee — not just your own, and ' +
19
+ "not just this directory's area. Grouped by area, 30 rows per page. Narrow with --area / " +
20
+ '--agent / --unassigned / --intent / --mission, or take the whole thing with --all';
21
+ export const ISSUES_DESCRIPTION = `List issues. ${LIST_SCOPE_NOTE}`;
22
+ export const IDEAS_DESCRIPTION = `List ideas. ${LIST_SCOPE_NOTE}`;
23
+ export const VIBES_DESCRIPTION = `List vibes. ${LIST_SCOPE_NOTE}`;
24
+ export const DOCS_DESCRIPTION = 'List documents (with sections count and context). Spans every area you can reach plus the ' +
25
+ 'workspace-level ones — pass --area <slug[,slug]> to narrow';
26
+ export const INTENTS_DESCRIPTION = 'List intents — workspace-wide, every assignee';
27
+ export const MISSIONS_DESCRIPTION = 'List missions — workspace-wide, every assignee';
28
+ /** `all` is accepted by every list since API 9.0.0 and is the one spelling of "do not filter". */
29
+ export const TASK_STATUS_HELP = 'Filter by status (comma-separated, or `all`; default todo,in_progress)';
30
+ export const ISSUE_STATUS_HELP = 'Filter by status (comma-separated, or `all`; default open,investigating)';
31
+ export const IDEA_STATUS_HELP = 'Filter by status (comma-separated, or `all`; default pending,accepted)';
32
+ export const INTENT_STATUS_HELP = 'Filter by status (comma-separated, or `all`; default new,design,plan,build,in_review)';
33
+ export const MISSION_STATUS_HELP = 'Filter by status (comma-separated, or `all`; default new,active)';
34
+ /**
35
+ * The commands that accept `--area a,b`, i.e. the ones where an area is a FILTER.
36
+ *
37
+ * Everywhere else an area is the write target or the lookup context and exactly one is
38
+ * required: silently taking the first of several would create an object in an area the caller
39
+ * did not name — the IS-vse335 failure with extra steps. Names are `command` or
40
+ * `parent command`, matching how a user types them.
41
+ */
42
+ export const MULTI_AREA_COMMANDS = [
43
+ 'tasks',
44
+ 'issues',
45
+ 'ideas',
46
+ 'vibes',
47
+ 'docs',
48
+ 'issue list',
49
+ 'idea list',
50
+ 'vibe list',
51
+ ];
52
+ /** `tasks`, or `issue list` — the path a user would type, for the check below. */
53
+ export function commandPath(name, parent) {
54
+ return parent && parent !== 'orbitmap' ? `${parent} ${name}` : name;
55
+ }
56
+ /**
57
+ * Reject `--area a,b` on a command that needs exactly one area, naming what to do instead.
58
+ * Returns the message, or `undefined` when the invocation is fine.
59
+ */
60
+ export function multiAreaRejection(path, area) {
61
+ if (!area)
62
+ return undefined;
63
+ const refs = splitAreaRefs(area).filter((ref) => ref.toLowerCase() !== ALL_AREAS);
64
+ if (refs.length < 2 || MULTI_AREA_COMMANDS.includes(path))
65
+ return undefined;
66
+ return (`--area named ${refs.length} areas (${refs.join(', ')}), but \`${path}\` needs exactly ` +
67
+ 'one: it is the area this command writes to, or looks the object up in. Several areas ' +
68
+ `are a list filter — they work on ${MULTI_AREA_COMMANDS.join(', ')}.`);
69
+ }
70
+ //# sourceMappingURL=list-options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-options.js","sourceRoot":"","sources":["../src/list-options.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAElE,wFAAwF;AACxF,MAAM,CAAC,MAAM,eAAe,GAC1B,wFAAwF;IACxF,WAAW,CAAC;AAEd,MAAM,CAAC,MAAM,UAAU,GAAG,qCAAqC,CAAC;AAEhE,MAAM,CAAC,MAAM,QAAQ,GAAG,kDAAkD,CAAC;AAE3E,MAAM,CAAC,MAAM,iBAAiB,GAC5B,yFAAyF;IACzF,0FAA0F;IAC1F,mFAAmF,CAAC;AAEtF,MAAM,CAAC,MAAM,kBAAkB,GAAG,gBAAgB,eAAe,EAAE,CAAC;AACpE,MAAM,CAAC,MAAM,iBAAiB,GAAG,eAAe,eAAe,EAAE,CAAC;AAClE,MAAM,CAAC,MAAM,iBAAiB,GAAG,eAAe,eAAe,EAAE,CAAC;AAClE,MAAM,CAAC,MAAM,gBAAgB,GAC3B,4FAA4F;IAC5F,4DAA4D,CAAC;AAC/D,MAAM,CAAC,MAAM,mBAAmB,GAAG,+CAA+C,CAAC;AACnF,MAAM,CAAC,MAAM,oBAAoB,GAAG,gDAAgD,CAAC;AAErF,kGAAkG;AAClG,MAAM,CAAC,MAAM,gBAAgB,GAC3B,wEAAwE,CAAC;AAC3E,MAAM,CAAC,MAAM,iBAAiB,GAC5B,0EAA0E,CAAC;AAC7E,MAAM,CAAC,MAAM,gBAAgB,GAC3B,wEAAwE,CAAC;AAC3E,MAAM,CAAC,MAAM,kBAAkB,GAC7B,uFAAuF,CAAC;AAC1F,MAAM,CAAC,MAAM,mBAAmB,GAC9B,kEAAkE,CAAC;AAErE;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAsB;IACpD,OAAO;IACP,QAAQ;IACR,OAAO;IACP,OAAO;IACP,MAAM;IACN,YAAY;IACZ,WAAW;IACX,WAAW;CACZ,CAAC;AAEF,kFAAkF;AAClF,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,MAAe;IACvD,OAAO,MAAM,IAAI,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AACtE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY,EAAE,IAAwB;IACvE,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAC;IAE5B,MAAM,IAAI,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,SAAS,CAAC,CAAC;IAClF,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,mBAAmB,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IAE5E,OAAO,CACL,gBAAgB,IAAI,CAAC,MAAM,WAAW,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,IAAI,mBAAmB;QACxF,uFAAuF;QACvF,oCAAoC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CACtE,CAAC;AACJ,CAAC"}
@@ -193,11 +193,48 @@ export declare function isWithin(dir: string, child: string): boolean;
193
193
  * exactly the directories that would have resolved an area.
194
194
  */
195
195
  export declare function resolveAreaDirectoryMap(options?: ResolveOptions): AreaDirectoryMapping[];
196
- /** An area slug together with the rung it came from. */
196
+ /**
197
+ * The literal `--area` / `ORBITMAP_AREA` value that means "do not narrow at all".
198
+ *
199
+ * Needed because narrowing is now a *filter* rather than the ambient context (API 9.0.0):
200
+ * with `ORBITMAP_AREA` exported in a shell, or a `--area` in a wrapper script, there has to
201
+ * be a spelling of "this one command spans the whole workspace" that does not require
202
+ * unsetting an environment variable. `--area all` is that spelling; it clears the explicit
203
+ * narrowing and leaves the rest of the ladder (which only ever supplies the *context* area)
204
+ * untouched.
205
+ */
206
+ export declare const ALL_AREAS = "all";
207
+ /**
208
+ * An area slug together with the rung it came from, plus the full list the caller asked for.
209
+ *
210
+ * `area` is the CONTEXT area — one area, the `X-Orbitmap-Area` header, what a write lands in
211
+ * and what a single-entity read is looked up in. It is resolved exactly as it always was.
212
+ *
213
+ * `areas` is the LIST FILTER — every area the caller *explicitly* named, in the order typed
214
+ * (`--area a,b` → `['a','b']`). It is empty unless someone named one: since API 9.0.0 the
215
+ * `X-Orbitmap-Area` header no longer narrows a list, so a directory-derived area must not
216
+ * silently narrow one either — that is the bug this whole intent exists to fix. Only the
217
+ * `flag` and `env` rungs (someone typed it) fill it.
218
+ *
219
+ * `allAreas` records an explicit {@link ALL_AREAS}: "I know something set an area, span the
220
+ * workspace anyway".
221
+ */
197
222
  export interface ResolvedAreaSlug {
198
223
  area?: string;
199
224
  source?: AreaSource;
225
+ /** Explicitly requested area refs — the `area_id` filter. Empty means "the whole reach". */
226
+ areas: string[];
227
+ /** True when the caller spelled out {@link ALL_AREAS}. */
228
+ allAreas: boolean;
200
229
  }
230
+ /**
231
+ * Split an area reference — one ref, several comma-separated refs, or an array of either —
232
+ * into a list of non-empty refs.
233
+ *
234
+ * Deliberately the same parser the API uses (`BaseAgentApiController::splitAreaRefs`), so
235
+ * `--area a,b`, `ORBITMAP_AREA=a,b` and `area_id=a,b` cannot drift apart.
236
+ */
237
+ export declare function splitAreaRefs(value: string | string[] | undefined | null): string[];
201
238
  /**
202
239
  * Area resolution (ADR 0002, extending spec §3.3), explicit sources only:
203
240
  * `--area` → `ORBITMAP_AREA` → `link.json.area` → nearest `.orbitmap/config.json`
@@ -315,6 +352,15 @@ export interface ResolvedWorkspaceContext {
315
352
  area?: string;
316
353
  /** Which rung of the area ladder produced {@link ResolvedWorkspaceContext.area}. */
317
354
  areaSource?: AreaSource;
355
+ /**
356
+ * The areas the caller EXPLICITLY named (`--area a,b`, `ORBITMAP_AREA=a,b`) — the list
357
+ * filter, not the context. Empty means "every area the key reaches": since API 9.0.0 a
358
+ * list is narrowed by the `area_id` parameter alone, never by the `X-Orbitmap-Area`
359
+ * header, so {@link ResolvedWorkspaceContext.area} must not leak into one.
360
+ */
361
+ areas: string[];
362
+ /** True when the caller spelled out `--area all` — narrowing explicitly cleared. */
363
+ allAreas: boolean;
318
364
  /** `'area'` when an area resolved, `'workspace'` otherwise. Never absent. */
319
365
  scope: ContextScope;
320
366
  link?: FoundWorkspaceLink;
@@ -339,6 +339,59 @@ function resolveAreaFromCwd(options) {
339
339
  const cwd = resolve(options.cwd ?? process.cwd());
340
340
  return resolveAreaDirectoryMap(options).find((mapping) => isWithin(mapping.dir, cwd))?.area;
341
341
  }
342
+ /**
343
+ * The literal `--area` / `ORBITMAP_AREA` value that means "do not narrow at all".
344
+ *
345
+ * Needed because narrowing is now a *filter* rather than the ambient context (API 9.0.0):
346
+ * with `ORBITMAP_AREA` exported in a shell, or a `--area` in a wrapper script, there has to
347
+ * be a spelling of "this one command spans the whole workspace" that does not require
348
+ * unsetting an environment variable. `--area all` is that spelling; it clears the explicit
349
+ * narrowing and leaves the rest of the ladder (which only ever supplies the *context* area)
350
+ * untouched.
351
+ */
352
+ export const ALL_AREAS = 'all';
353
+ /**
354
+ * Split an area reference — one ref, several comma-separated refs, or an array of either —
355
+ * into a list of non-empty refs.
356
+ *
357
+ * Deliberately the same parser the API uses (`BaseAgentApiController::splitAreaRefs`), so
358
+ * `--area a,b`, `ORBITMAP_AREA=a,b` and `area_id=a,b` cannot drift apart.
359
+ */
360
+ export function splitAreaRefs(value) {
361
+ if (value === undefined || value === null)
362
+ return [];
363
+ const refs = [];
364
+ for (const entry of Array.isArray(value) ? value : [value]) {
365
+ if (typeof entry !== 'string')
366
+ continue;
367
+ for (const part of entry.split(',')) {
368
+ const trimmed = part.trim();
369
+ if (trimmed !== '' && !refs.includes(trimmed))
370
+ refs.push(trimmed);
371
+ }
372
+ }
373
+ return refs;
374
+ }
375
+ /**
376
+ * The explicit half of the area ladder: what `--area` / `ORBITMAP_AREA` asked for.
377
+ *
378
+ * Returns the refs (empty when nothing was named or when {@link ALL_AREAS} was), whether
379
+ * `all` was spelled out, and which of the two rungs spoke.
380
+ */
381
+ function explicitAreaRefs(options) {
382
+ const fromFlag = splitAreaRefs(options.area);
383
+ const fromEnv = fromFlag.length > 0 ? [] : splitAreaRefs(process.env['ORBITMAP_AREA']);
384
+ const refs = fromFlag.length > 0 ? fromFlag : fromEnv;
385
+ if (refs.length === 0)
386
+ return { refs: [], allAreas: false };
387
+ const source = fromFlag.length > 0 ? 'flag' : 'env';
388
+ if (refs.some((ref) => ref.toLowerCase() === ALL_AREAS)) {
389
+ // `all` is not an area; it is the absence of narrowing. It also does not name a context
390
+ // area, so the lower rungs keep supplying one for writes and single-entity reads.
391
+ return { refs: [], allAreas: true, source };
392
+ }
393
+ return { refs, allAreas: false, source };
394
+ }
342
395
  /**
343
396
  * Area resolution (ADR 0002, extending spec §3.3), explicit sources only:
344
397
  * `--area` → `ORBITMAP_AREA` → `link.json.area` → nearest `.orbitmap/config.json`
@@ -369,26 +422,34 @@ function resolveAreaFromCwd(options) {
369
422
  * explicit request apart from the machine-wide default.
370
423
  */
371
424
  export function resolveArea(options = {}) {
372
- if (options.area)
373
- return { area: options.area, source: 'flag' };
374
- const env = process.env['ORBITMAP_AREA'];
375
- if (env)
376
- return { area: env, source: 'env' };
425
+ const explicit = explicitAreaRefs(options);
426
+ const filter = { areas: explicit.refs, allAreas: explicit.allAreas };
427
+ // One explicit ref is both the filter and the context area — the historical behaviour.
428
+ // SEVERAL refs name a filter and no context area: there is no single area to put in the
429
+ // `X-Orbitmap-Area` header, and picking one of them silently would be a write landing
430
+ // somewhere the caller did not name. Commands that need one area say so (see
431
+ // `assertSingleArea` in `src/index.ts`).
432
+ if (explicit.refs.length === 1) {
433
+ return { area: explicit.refs[0], source: explicit.source, ...filter };
434
+ }
435
+ if (explicit.refs.length > 1) {
436
+ return { source: explicit.source, ...filter };
437
+ }
377
438
  const cwd = resolve(options.cwd ?? process.cwd());
378
439
  // Everything below reads a file, and a file above the workspace boundary names an area of
379
440
  // a different workspace (IS-e6q7je). No config sets `workspace` ⇒ no boundary ⇒ unchanged.
380
441
  const boundary = workspaceBoundary(cwd);
381
442
  const found = options.link !== undefined ? options.link : findLinkFile(cwd);
382
443
  if (found?.link.area && withinWorkspace(boundary, found.file)) {
383
- return { area: found.link.area, source: 'link' };
444
+ return { area: found.link.area, source: 'link', ...filter };
384
445
  }
385
446
  const projectArea = resolveProjectConfigField('area', cwd);
386
447
  if (projectArea && withinWorkspace(boundary, projectArea.file)) {
387
- return { area: projectArea.value, source: 'project-config' };
448
+ return { area: projectArea.value, source: 'project-config', ...filter };
388
449
  }
389
450
  const mapped = resolveAreaFromCwd({ ...options, cwd, link: found });
390
451
  if (mapped)
391
- return { area: mapped, source: 'cwd-map' };
452
+ return { area: mapped, source: 'cwd-map', ...filter };
392
453
  // The machine-wide rung is bounded too, and for the same reason: a slug written once in
393
454
  // `~/.orbitmap/config.json` cannot be known to name an area of *this* workspace, and when a
394
455
  // project has declared which workspace it is, taking it anyway reproduces IS-e6q7je one rung
@@ -396,9 +457,9 @@ export function resolveArea(options = {}) {
396
457
  const configured = options.config?.area;
397
458
  if (configured && !boundary) {
398
459
  warnDeprecatedGlobalArea();
399
- return { area: configured, source: 'config' };
460
+ return { area: configured, source: 'config', ...filter };
400
461
  }
401
- return {};
462
+ return { ...filter };
402
463
  }
403
464
  let warnedDeprecatedGlobalArea = false;
404
465
  /**
@@ -532,7 +593,7 @@ export async function resolveWorkspaceContext(options = {}) {
532
593
  const link = options.link !== undefined ? options.link : findLinkFile(cwd);
533
594
  const opts = { ...options, cwd, config, link };
534
595
  const mode = resolveMode(opts);
535
- const { area, source: areaSource } = resolveArea(opts);
596
+ const { area, source: areaSource, areas, allAreas } = resolveArea(opts);
536
597
  const { workspace, source: workspaceSource } = resolveWorkspaceId(opts);
537
598
  const scope = area ? 'area' : 'workspace';
538
599
  const base = {
@@ -541,6 +602,8 @@ export async function resolveWorkspaceContext(options = {}) {
541
602
  workspaceSource,
542
603
  area,
543
604
  areaSource,
605
+ areas,
606
+ allAreas,
544
607
  scope,
545
608
  link,
546
609
  config,