overlord-cli 0.2608191441.0 → 0.2608191901.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.
@@ -80,14 +80,45 @@ function booleanProperty(description) {
80
80
  return { type: 'boolean', description };
81
81
  }
82
82
 
83
+ function compactSearchResponse(response) {
84
+ const value = response?.structuredContent;
85
+ if (!value || value.version !== 3 || !Array.isArray(value.results)) return response;
86
+ const compact = {
87
+ ...value,
88
+ results: value.results.map(result => ({
89
+ ...result,
90
+ matches: Array.isArray(result.matches)
91
+ ? result.matches
92
+ .slice(0, 2)
93
+ .map(match => {
94
+ const compactMatch = { ...match };
95
+ delete compactMatch.snippet;
96
+ delete compactMatch.matchedTerms;
97
+ delete compactMatch.createdAt;
98
+ delete compactMatch.updatedAt;
99
+ return compactMatch;
100
+ })
101
+ : result.matches
102
+ }))
103
+ };
104
+ return {
105
+ ...response,
106
+ content: [{ type: 'text', text: JSON.stringify(compact, null, 2) }],
107
+ structuredContent: compact
108
+ };
109
+ }
110
+
83
111
  const tools = [
84
112
  {
85
113
  name: 'overlord_resolve_project',
86
114
  title: 'Resolve Overlord project',
87
115
  description:
88
- 'Resolve a project by id, slug, name, or linked repository directory metadata exposed to the MCP client.',
116
+ "Resolve a project by id, slug, name, or linked repository directory metadata exposed to the MCP client. Names are matched case-insensitively across every workspace the caller can reach; a name matching in more than one returns a 'project_selection_required' result listing the candidates with their workspaces — ask the user, then retry with workspaceId.",
89
117
  inputSchema: objectSchema({
90
118
  projectId: stringProperty('Explicit Overlord project id, slug, or project name.'),
119
+ workspaceId: stringProperty(
120
+ "Optional workspace (id, slug, or name) narrowing an ambiguous projectId. Use this to retry after a 'project_selection_required' result."
121
+ ),
91
122
  directory: stringProperty(
92
123
  'Optional repository directory path when the MCP client can expose one with .overlord/project.json.'
93
124
  )
@@ -117,7 +148,12 @@ const tools = [
117
148
  "Read one project's board columns. Status names and order are defined per project, so two projects in the same workspace can label the same lifecycle differently; each status also carries a stable type (draft, next, execute, review, complete, blocked, cancelled) that does not vary by project.",
118
149
  inputSchema: objectSchema(
119
150
  {
120
- projectId: stringProperty('Overlord project id, slug, or name.')
151
+ projectId: stringProperty(
152
+ "Overlord project id, slug, or name. An ambiguous name returns 'project_selection_required'; retry with workspaceId."
153
+ ),
154
+ workspaceId: stringProperty(
155
+ 'Optional workspace (id, slug, or name) narrowing an ambiguous projectId.'
156
+ )
121
157
  },
122
158
  ['projectId']
123
159
  )
@@ -126,24 +162,46 @@ const tools = [
126
162
  name: 'overlord_search_missions',
127
163
  title: 'Search Overlord missions',
128
164
  description:
129
- "Search missions across the caller's authorized workspaces in one organization. Returns applied filters and truncation metadata; complete and cancelled missions remain eligible by default.",
165
+ "Search mission anchors with their matching objectives and deliveries across authorized workspaces. An objective displayId can be passed directly to overlord_load_mission_context; matches are matched-only, not the full objective list. Artifacts are not indexed. Use absolute ISO from/to bounds (no relative dates or implicit window), inspect workspaceCounts, entityCounts, and truncatedCandidates before claiming completeness, and treat appliedFilters.mode 'fallback' as a recency listing. Compact detail is default; request full for child snippets and metadata.",
130
166
  inputSchema: objectSchema({
131
167
  query: stringProperty('Search query text.'),
132
168
  status: stringProperty(
133
169
  'Comma-separated status TYPES, such as draft,next,execute,review. Types are workspace-invariant (draft, next, execute, review, complete, blocked, cancelled). Project-defined status names are not accepted here.'
134
170
  ),
135
171
  projectId: stringProperty(
136
- 'Optional stable Overlord project UUID. Resolve a human project reference first.'
172
+ "Optional project reference: a stable UUID, a slug, or the project name as the user said it. Names are matched case-insensitively against the projects the caller can reach. If the name matches in more than one workspace the call returns a 'project_selection_required' result listing the candidates with their workspaces — ask the user which one, then retry with workspaceId set."
173
+ ),
174
+ workspaceId: stringProperty(
175
+ "Optional workspace (id, slug, or name) narrowing an ambiguous projectId. Use this to retry after a 'project_selection_required' result."
137
176
  ),
138
177
  resourceKey: stringProperty(
139
178
  'Optional logical resource key, matched by name within selected projects.'
140
179
  ),
141
- dateField: stringProperty('Date column for an explicit range: createdAt or updatedAt.'),
180
+ dateField: stringProperty(
181
+ 'Date column for an explicit range: createdAt, updatedAt, or dueDatetime. Use dueDatetime for questions about what is scheduled or due; missions with no due date are excluded from a dueDatetime range.'
182
+ ),
142
183
  from: stringProperty('Optional inclusive ISO-8601 date/time lower bound.'),
143
184
  to: stringProperty('Optional exclusive ISO-8601 date/time upper bound.'),
144
185
  limit: {
145
186
  type: 'number',
146
187
  description: 'Maximum result count. Defaults to 25.'
188
+ },
189
+ entityTypes: stringProperty(
190
+ 'Optional comma-separated v3 entity types: mission, objective, delivery.'
191
+ ),
192
+ objectiveStates: stringProperty(
193
+ 'Optional comma-separated v3 objective states for matching objectives.'
194
+ ),
195
+ matchesPerResult: {
196
+ type: 'number',
197
+ description:
198
+ 'Optional v3 child-match cap per mission (default 3, max 10; compact returns at most 2).'
199
+ },
200
+ detail: {
201
+ type: 'string',
202
+ enum: ['compact', 'full'],
203
+ description:
204
+ 'Result detail: compact (default) keeps child navigation fields and removes child snippets/metadata; full returns complete v3 child matches.'
147
205
  }
148
206
  })
149
207
  },
@@ -422,17 +480,6 @@ function requiredString(args, name) {
422
480
  return value;
423
481
  }
424
482
 
425
- function optionalProjectUuid(args) {
426
- const projectId = optionalString(args, 'projectId');
427
- if (!projectId) return null;
428
- if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(projectId)) {
429
- throw new Error(
430
- 'projectId must be a stable Overlord project UUID; resolve human references first'
431
- );
432
- }
433
- return projectId;
434
- }
435
-
436
483
  /**
437
484
  * Forward mission/objective addressing to the CLI. The CLI owns the shared
438
485
  * `@overlord/contract` parser and derives a mission from a display id before it
@@ -453,12 +500,15 @@ function missionScopeFlags(args) {
453
500
  };
454
501
  }
455
502
 
456
- function callOverlordTool(name, args) {
503
+ async function callOverlordTool(name, args) {
457
504
  if (name === 'overlord_resolve_project') {
458
505
  return runProtocol('discover-project', {
459
506
  ...(optionalString(args, 'projectId')
460
507
  ? { 'project-id': requiredString(args, 'projectId') }
461
508
  : {}),
509
+ ...(optionalString(args, 'workspaceId')
510
+ ? { 'workspace-id': requiredString(args, 'workspaceId') }
511
+ : {}),
462
512
  ...(optionalString(args, 'directory') ? { directory: requiredString(args, 'directory') } : {})
463
513
  });
464
514
  }
@@ -476,15 +526,27 @@ function callOverlordTool(name, args) {
476
526
  }
477
527
  if (name === 'overlord_list_project_statuses') {
478
528
  return runProtocol('statuses', {
479
- 'project-id': requiredString(args, 'projectId')
529
+ 'project-id': requiredString(args, 'projectId'),
530
+ ...(optionalString(args, 'workspaceId')
531
+ ? { 'workspace-id': requiredString(args, 'workspaceId') }
532
+ : {})
480
533
  });
481
534
  }
482
535
  if (name === 'overlord_search_missions') {
483
- return runProtocol('search-missions', {
484
- 'response-version': '2',
536
+ const detail = optionalString(args, 'detail') || 'compact';
537
+ if (detail !== 'compact' && detail !== 'full') {
538
+ throw new Error("detail must be 'compact' or 'full'");
539
+ }
540
+ const response = await runProtocol('search', {
541
+ 'response-version': '3',
485
542
  ...(optionalString(args, 'query') ? { query: requiredString(args, 'query') } : {}),
486
543
  ...(optionalString(args, 'status') ? { status: requiredString(args, 'status') } : {}),
487
- ...(optionalProjectUuid(args) ? { 'project-id': optionalProjectUuid(args) } : {}),
544
+ ...(optionalString(args, 'projectId')
545
+ ? { 'project-id': requiredString(args, 'projectId') }
546
+ : {}),
547
+ ...(optionalString(args, 'workspaceId')
548
+ ? { 'workspace-id': requiredString(args, 'workspaceId') }
549
+ : {}),
488
550
  ...(optionalString(args, 'resourceKey')
489
551
  ? { 'resource-key': requiredString(args, 'resourceKey') }
490
552
  : {}),
@@ -495,8 +557,18 @@ function callOverlordTool(name, args) {
495
557
  ...(optionalString(args, 'to') ? { to: requiredString(args, 'to') } : {}),
496
558
  ...(typeof args.limit === 'number' && Number.isFinite(args.limit)
497
559
  ? { limit: String(Math.trunc(args.limit)) }
560
+ : {}),
561
+ ...(optionalString(args, 'entityTypes')
562
+ ? { 'entity-types': requiredString(args, 'entityTypes') }
563
+ : {}),
564
+ ...(optionalString(args, 'objectiveStates')
565
+ ? { 'objective-states': requiredString(args, 'objectiveStates') }
566
+ : {}),
567
+ ...(typeof args.matchesPerResult === 'number' && Number.isFinite(args.matchesPerResult)
568
+ ? { 'matches-per-result': String(Math.trunc(args.matchesPerResult)) }
498
569
  : {})
499
570
  });
571
+ return detail === 'compact' ? compactSearchResponse(response) : response;
500
572
  }
501
573
  if (name === 'overlord_create_mission') {
502
574
  return runProtocol('create', {
package/dist/index.js CHANGED
@@ -6675,7 +6675,15 @@ var init_resource_paths = __esm({
6675
6675
  }
6676
6676
  });
6677
6677
 
6678
+ // ../packages/contract/dist/search.js
6679
+ var init_search = __esm({
6680
+ "../packages/contract/dist/search.js"() {
6681
+ "use strict";
6682
+ }
6683
+ });
6684
+
6678
6685
  // ../packages/contract/dist/index.js
6686
+ var MY_MISSIONS_COLUMNS, MY_MISSIONS_COLUMN_TYPES;
6679
6687
  var init_dist2 = __esm({
6680
6688
  "../packages/contract/dist/index.js"() {
6681
6689
  "use strict";
@@ -6683,6 +6691,14 @@ var init_dist2 = __esm({
6683
6691
  init_launch_variables();
6684
6692
  init_objective_ref();
6685
6693
  init_resource_paths();
6694
+ init_search();
6695
+ MY_MISSIONS_COLUMNS = [
6696
+ { type: "next", label: "Next" },
6697
+ { type: "execute", label: "Executing" },
6698
+ { type: "review", label: "Review" },
6699
+ { type: "complete", label: "Completed" }
6700
+ ];
6701
+ MY_MISSIONS_COLUMN_TYPES = MY_MISSIONS_COLUMNS.map((column) => column.type);
6686
6702
  }
6687
6703
  });
6688
6704
 
@@ -19496,7 +19512,8 @@ function discoverProjectJsonFromFilesystem({
19496
19512
  }
19497
19513
  async function resolveProjectByIdOrName({
19498
19514
  backend,
19499
- projectRef
19515
+ projectRef,
19516
+ workspaceRef
19500
19517
  }) {
19501
19518
  const trimmed8 = projectRef.trim();
19502
19519
  try {
@@ -19505,11 +19522,45 @@ async function resolveProjectByIdOrName({
19505
19522
  if (!(error instanceof CliError) || !/project not found/i.test(error.message)) throw error;
19506
19523
  }
19507
19524
  const projects = await listAccessibleProjects({ backend });
19508
- const match = projects.find((project) => project.name === trimmed8) ?? projects.find((project) => project.slug === trimmed8);
19509
- if (!match) {
19525
+ const needle = trimmed8.toLowerCase();
19526
+ const byName = projects.filter((project) => project.name.toLowerCase() === needle);
19527
+ const matches = byName.length > 0 ? byName : projects.filter((project) => project.slug.toLowerCase() === needle);
19528
+ if (matches.length === 0) {
19510
19529
  throw new CliError({ message: `Project not found: ${trimmed8}` });
19511
19530
  }
19512
- return match;
19531
+ const narrowed = await narrowByWorkspace({ backend, matches, workspaceRef });
19532
+ if (narrowed.length === 1) return narrowed[0];
19533
+ const workspaces = await workspaceLabels({ backend });
19534
+ const candidates = narrowed.map(
19535
+ (project) => ` ${project.id} (workspace: ${workspaces.get(project.workspaceId) ?? project.workspaceId})`
19536
+ ).join("\n");
19537
+ throw new CliError({
19538
+ message: `Project reference matches ${narrowed.length} projects: ${trimmed8}
19539
+ ${candidates}
19540
+ Re-run with --workspace-id <id|slug|name>, or pass the project id directly.`
19541
+ });
19542
+ }
19543
+ async function workspaceLabels({
19544
+ backend
19545
+ }) {
19546
+ const workspaces = await backend.get("/api/workspaces");
19547
+ return new Map(workspaces.map((workspace) => [workspace.id, workspace.name]));
19548
+ }
19549
+ async function narrowByWorkspace({
19550
+ backend,
19551
+ matches,
19552
+ workspaceRef
19553
+ }) {
19554
+ const hint = workspaceRef?.trim().toLowerCase();
19555
+ if (!hint || matches.length <= 1) return matches;
19556
+ const workspaces = await backend.get("/api/workspaces");
19557
+ const targets = new Set(
19558
+ workspaces.filter(
19559
+ (workspace) => workspace.id.toLowerCase() === hint || workspace.slug.toLowerCase() === hint || workspace.name.toLowerCase() === hint
19560
+ ).map((workspace) => workspace.id)
19561
+ );
19562
+ const narrowed = matches.filter((project) => targets.has(project.workspaceId));
19563
+ return narrowed.length > 0 ? narrowed : matches;
19513
19564
  }
19514
19565
  async function tryResolveProjectByIdOrName({
19515
19566
  backend,
@@ -20866,7 +20917,8 @@ Subcommands:
20866
20917
  attach Start a mission session and return full working context
20867
20918
  connect Start a lightweight session without full context assembly
20868
20919
  load-context Read mission context without creating a session
20869
- search-missions Find missions by keyword, status, or project
20920
+ search Find grouped mission/objective/delivery matches (canonical)
20921
+ search-missions Compatibility alias for search
20870
20922
  discuss-objective Mark a draft objective as submitted (does not start execution)
20871
20923
  add-objectives Append ordered objectives to an existing mission
20872
20924
  update-objective Set auto-advance and/or instruction text on an objective
@@ -20975,9 +21027,9 @@ load-context:
20975
21027
  rediscovering the mission's active objective. Required
20976
21028
  on a mission running objectives in parallel.
20977
21029
 
20978
- search-missions:
21030
+ search:
20979
21031
  Purpose:
20980
- Find missions by keyword, status type, or project.
21032
+ Find grouped mission, objective, and delivery matches by keyword, status type, or project.
20981
21033
  Optional:
20982
21034
  --query <text> Free-text search
20983
21035
  --status <csv> Comma-separated status TYPES (e.g. execute,review).
@@ -20985,18 +21037,30 @@ search-missions:
20985
21037
  complete, blocked, cancelled. Project-defined status
20986
21038
  names (board column labels) are not accepted here \u2014
20987
21039
  use \`statuses\` to read one project's names.
20988
- --project-id <id|slug|name> Restrict to one project (CLI resolves human references)
21040
+ --project-id <id|slug|name> Restrict to one project. Names are matched
21041
+ case-insensitively across the workspaces you can reach;
21042
+ a name matching in two workspaces returns
21043
+ project_selection_required listing the candidates.
21044
+ --workspace-id <id|slug|name>
21045
+ Narrow an ambiguous --project-id
20989
21046
  --resource-key <csv> Restrict to logical resource key names
20990
- --date-field <createdAt|updatedAt>
20991
- Date column for explicit range filtering
21047
+ --date-field <createdAt|updatedAt|dueDatetime>
21048
+ Date column for explicit range filtering. Use dueDatetime
21049
+ for "scheduled"/"due" questions; missions with no due date
21050
+ are excluded from a dueDatetime range.
20992
21051
  --from <ISO-8601> Inclusive date/time lower bound
20993
21052
  --to <ISO-8601> Exclusive date/time upper bound
20994
21053
  --limit <n> Max results (default: 25)
20995
- --response-version <1|2> 1 (default) returns the legacy array; 2 returns
21054
+ --response-version <1|2|3> 1 (default) returns the legacy array; 2 returns
20996
21055
  SearchMissionsResponseV2 with snippets, match
20997
- evidence, appliedFilters, and truncation counts
21056
+ evidence, appliedFilters, and truncation counts;
21057
+ 3 returns SearchResponseV3 with grouped objective
21058
+ and delivery matches
21059
+ --entity-types <csv> V3 only: mission,objective,delivery (default all)
21060
+ --objective-states <csv> V3 only: restrict matching objective states
21061
+ --matches-per-result <n> V3 only: child matches per mission (default 3, max 10)
20998
21062
  Returns:
20999
- JSON with matching missions (v1) or the versioned search envelope (v2).
21063
+ JSON with matching missions (v1) or the versioned search envelope (v2/v3).
21000
21064
 
21001
21065
  statuses:
21002
21066
  Purpose:
@@ -21352,6 +21416,7 @@ var init_protocol_help = __esm({
21352
21416
  "record-touched",
21353
21417
  "record-work",
21354
21418
  "resume-follow-up",
21419
+ "search",
21355
21420
  "search-missions",
21356
21421
  "update",
21357
21422
  "update-artifact",
@@ -24021,12 +24086,17 @@ async function runManagementCommand({
24021
24086
  const sub = parsed.positional[0];
24022
24087
  if (sub !== "list") {
24023
24088
  throw new CliError({
24024
- message: "Usage: ovld missions list [--status <csv>] [--project-id <id>] [--json]"
24089
+ message: "Usage: ovld missions list [--status <csv>] [--project-id <id|slug|name>] [--workspace-id <id|slug|name>] [--json]"
24025
24090
  });
24026
24091
  }
24027
24092
  const params = new URLSearchParams();
24028
24093
  const query = flagValue(parsed.flags, "--query");
24029
- const projectId = flagValue(parsed.flags, "--project-id");
24094
+ const projectRef = flagValue(parsed.flags, "--project-id");
24095
+ const projectId = projectRef ? (await resolveProjectByIdOrName({
24096
+ backend: runtime.backend,
24097
+ projectRef,
24098
+ workspaceRef: flagValue(parsed.flags, "--workspace-id")
24099
+ })).id : void 0;
24030
24100
  const limit = flagValue(parsed.flags, "--limit");
24031
24101
  const statusTypes = (flagValue(parsed.flags, "--status") ?? "").split(",").map((value) => value.trim()).filter((value) => value !== "");
24032
24102
  if (query) params.set("q", query);
@@ -24068,7 +24138,8 @@ async function runManagementCommand({
24068
24138
  }
24069
24139
  const project = await resolveProjectByIdOrName({
24070
24140
  backend: runtime.backend,
24071
- projectRef
24141
+ projectRef,
24142
+ workspaceRef: flagValue(parsed.flags, "--workspace-id")
24072
24143
  });
24073
24144
  const statuses = await runtime.backend.get(
24074
24145
  `/api/projects/${encodeURIComponent(project.id)}/statuses`
@@ -24975,7 +25046,7 @@ Protocol (JSON output by default):
24975
25046
  ${primaryCommand} protocol heartbeat --mission-id <id> --session-key <key>
24976
25047
  ${primaryCommand} protocol ask --mission-id <id> --session-key <key> --question "..."
24977
25048
  ${primaryCommand} protocol deliver --mission-id <id> --session-key <key> --summary "..."
24978
- ${primaryCommand} protocol search-missions --query "<text>" --status execute,review
25049
+ ${primaryCommand} protocol search --query "<text>" --status execute,review
24979
25050
  ${primaryCommand} protocol statuses --project-id <id|slug|name>
24980
25051
  ${primaryCommand} protocol load-context --mission-id <id>
24981
25052
  ${primaryCommand} protocol help