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.
- package/LICENSE +21 -21
- package/README.md +73 -10
- package/dist/adapters/cloud.d.ts +85 -10
- package/dist/adapters/cloud.js +118 -17
- package/dist/adapters/cloud.js.map +1 -1
- package/dist/adapters/factory.js +7 -0
- package/dist/adapters/factory.js.map +1 -1
- package/dist/adapters/local/adapter.d.ts +47 -8
- package/dist/adapters/local/adapter.js +37 -17
- package/dist/adapters/local/adapter.js.map +1 -1
- package/dist/adapters/local/context.d.ts +13 -1
- package/dist/adapters/local/context.js +7 -2
- package/dist/adapters/local/context.js.map +1 -1
- package/dist/adapters/local/entities/documents.d.ts +18 -4
- package/dist/adapters/local/entities/documents.js +46 -7
- package/dist/adapters/local/entities/documents.js.map +1 -1
- package/dist/adapters/local/entities/planning.d.ts +36 -7
- package/dist/adapters/local/entities/planning.js +245 -39
- package/dist/adapters/local/entities/planning.js.map +1 -1
- package/dist/adapters/local/entities/tasks.d.ts +16 -7
- package/dist/adapters/local/entities/tasks.js +55 -42
- package/dist/adapters/local/entities/tasks.js.map +1 -1
- package/dist/adapters/local/entities/work-items.d.ts +28 -7
- package/dist/adapters/local/entities/work-items.js +111 -39
- package/dist/adapters/local/entities/work-items.js.map +1 -1
- package/dist/adapters/local/lists.d.ts +139 -0
- package/dist/adapters/local/lists.js +230 -0
- package/dist/adapters/local/lists.js.map +1 -0
- package/dist/adapters/local/rules.d.ts +1 -1
- package/dist/adapters/local/rules.js +23 -0
- package/dist/adapters/local/rules.js.map +1 -1
- package/dist/adapters/local/shapes.d.ts +165 -22
- package/dist/adapters/local/shapes.js +338 -50
- package/dist/adapters/local/shapes.js.map +1 -1
- package/dist/adapters/local/types.d.ts +6 -1
- package/dist/adapters/local/types.js.map +1 -1
- package/dist/adapters/types.d.ts +72 -9
- package/dist/agent-instructions.d.ts +41 -11
- package/dist/agent-instructions.js +329 -128
- package/dist/agent-instructions.js.map +1 -1
- package/dist/commands/context.d.ts +62 -37
- package/dist/commands/context.js +105 -36
- package/dist/commands/context.js.map +1 -1
- package/dist/commands/docs.d.ts +4 -0
- package/dist/commands/docs.js +12 -5
- package/dist/commands/docs.js.map +1 -1
- package/dist/commands/emit-instructions.d.ts +26 -0
- package/dist/commands/emit-instructions.js +68 -0
- package/dist/commands/emit-instructions.js.map +1 -0
- package/dist/commands/ideas.d.ts +3 -0
- package/dist/commands/ideas.js +14 -7
- package/dist/commands/ideas.js.map +1 -1
- package/dist/commands/intent.d.ts +26 -0
- package/dist/commands/intent.js +117 -8
- package/dist/commands/intent.js.map +1 -1
- package/dist/commands/issues.d.ts +4 -0
- package/dist/commands/issues.js +15 -7
- package/dist/commands/issues.js.map +1 -1
- package/dist/commands/mission.d.ts +2 -0
- package/dist/commands/mission.js +9 -5
- package/dist/commands/mission.js.map +1 -1
- package/dist/commands/projects.d.ts +3 -0
- package/dist/commands/projects.js +28 -0
- package/dist/commands/projects.js.map +1 -0
- package/dist/commands/setup-agent.d.ts +101 -13
- package/dist/commands/setup-agent.js +377 -31
- package/dist/commands/setup-agent.js.map +1 -1
- package/dist/commands/start.d.ts +13 -0
- package/dist/commands/start.js +126 -13
- package/dist/commands/start.js.map +1 -1
- package/dist/commands/tasks.d.ts +23 -5
- package/dist/commands/tasks.js +46 -23
- package/dist/commands/tasks.js.map +1 -1
- package/dist/commands/vibes.d.ts +3 -0
- package/dist/commands/vibes.js +11 -5
- package/dist/commands/vibes.js.map +1 -1
- package/dist/errors.d.ts +23 -2
- package/dist/errors.js +31 -3
- package/dist/errors.js.map +1 -1
- package/dist/index.js +118 -38
- package/dist/index.js.map +1 -1
- package/dist/list-meta.d.ts +117 -0
- package/dist/list-meta.js +160 -0
- package/dist/list-meta.js.map +1 -0
- package/dist/list-options.d.ts +33 -0
- package/dist/list-options.js +70 -0
- package/dist/list-options.js.map +1 -0
- package/dist/workspace-resolve.d.ts +47 -1
- package/dist/workspace-resolve.js +74 -11
- package/dist/workspace-resolve.js.map +1 -1
- package/dist/write-target.d.ts +9 -0
- package/dist/write-target.js +11 -2
- package/dist/write-target.js.map +1 -1
- 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":"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"}
|
|
@@ -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
|
-
/**
|
|
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
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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,
|