overlord-cli 0.2608180731.0 → 0.2608180913.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.
@@ -9,6 +9,6 @@
9
9
  - `overlord_record_work` (hosted MCP) and `ovld protocol record-work` (CLI) record already-completed chat work as a single review-column mission with a completed objective, file-change rationales, and the standard Gemini delivery summary — no `attach`/`deliver`. Hosted MCP requires `projectId` (it never chooses a project implicitly); the CLI resolves the project from the working directory or `--project-id`. The submission format is in [record-work.md](record-work.md). The route is `POST /api/protocol/record-work`.
10
10
  - `overlord_add_artifact` (hosted MCP) and `ovld protocol add-artifact` (CLI) create a mission artifact during a turn without delivering. Required: `missionId` / `--mission-id`, `type` / `--type`, `label` / `--label`, plus at least one of `contentText` / `--content-text`/`--content-text-file` or `externalUrl` / `--external-url`. Optional `sessionKey` / `--session-key` stamps session/objective provenance. Same service as REST `POST /api/missions/:id/artifacts`. The route is `POST /api/protocol/add-artifact`.
11
11
  - `overlord_update_artifact` (hosted MCP) and `ovld protocol update-artifact` (CLI) revise an existing mission artifact's label, Markdown content, and/or HTTP(S) URL in place. Required: `missionId` / `--mission-id`, `artifactId` / `--artifact-id`, `expectedRevision` / `--expected-revision`. Provide at least one editable field. No session key is required. Same service as REST `PATCH /api/missions/:id/artifacts/:artifactId`. The route is `POST /api/protocol/update-artifact`.
12
- - `overlord_create_mission` / `overlord_add_objectives` accept optional `autoAdvance` (boolean; default false). `overlord_update_objective` (`objectiveId` + `autoAdvance`) and `ovld protocol update-objective --objective-id <id> --auto-advance|--no-auto-advance` toggle it on an existing objective. The route is `POST /api/protocol/update-objective`.
12
+ - `overlord_create_mission` / `overlord_add_objectives` accept optional `autoAdvance` (boolean; default false). `overlord_update_objective` (`objectiveId` plus optional `autoAdvance` and/or `instructionText`) and `ovld protocol update-objective --objective-id <id>` with `--auto-advance|--no-auto-advance` and/or `--instruction-text|--instruction-text-file` update an existing objective. Instruction text is editable only in `draft` or `future` state. The route is `POST /api/protocol/update-objective`.
13
13
  - Objective attachment tools follow the `<verb>_<noun>` MCP naming: `list_attachments`, `prepare_attachment_upload`, `finalize_attachment_upload`, `get_attachment_download_url`, `upload_attachment_file`. CLI commands use `attachment-*`. `mission_id` / `missionId` is optional for attachment calls when the objective or attachment id lets the server derive mission scope.
14
14
  - "Artifacts" may be published mid-turn with `add-artifact` / `overlord_add_artifact`, revised with `update-artifact` / `overlord_update_artifact`, or attached at `deliver` time (next_steps, test_results, migration, decision, note, url) — not user-uploaded files. Files attached by users live on objectives via the attachment tools above.
@@ -198,17 +198,20 @@ const tools = [
198
198
  },
199
199
  {
200
200
  name: 'overlord_update_objective',
201
- title: 'Update objective auto-advance',
201
+ title: 'Update objective',
202
202
  description:
203
- 'Turn auto-advance on or off for an existing objective so delivery can queue the next one.',
203
+ 'Turn auto-advance on or off and/or edit instruction text on a draft or future objective.',
204
204
  inputSchema: objectSchema(
205
205
  {
206
- objectiveId: stringProperty('Objective UUID.'),
206
+ objectiveId: stringProperty('Objective UUID or display id.'),
207
207
  autoAdvance: booleanProperty(
208
208
  'When true, Overlord queues the next objective after this one is delivered. When false, delivery waits for approval.'
209
+ ),
210
+ instructionText: stringProperty(
211
+ 'Replacement instruction text. Allowed only when the objective is in draft or future state; blank clears the text in those states.'
209
212
  )
210
213
  },
211
- ['objectiveId', 'autoAdvance']
214
+ ['objectiveId']
212
215
  )
213
216
  },
214
217
  {
@@ -436,10 +439,15 @@ function callOverlordTool(name, args) {
436
439
  });
437
440
  }
438
441
  if (name === 'overlord_update_objective') {
439
- if (typeof args.autoAdvance !== 'boolean') throw new Error('autoAdvance must be a boolean');
442
+ const hasAutoAdvance = typeof args.autoAdvance === 'boolean';
443
+ const hasInstructionText = typeof args.instructionText === 'string';
444
+ if (!hasAutoAdvance && !hasInstructionText) {
445
+ throw new Error('Provide autoAdvance and/or instructionText');
446
+ }
440
447
  return runProtocol('update-objective', {
441
448
  'objective-id': requiredString(args, 'objectiveId'),
442
- ...(args.autoAdvance ? { 'auto-advance': true } : { 'no-auto-advance': true })
449
+ ...(hasAutoAdvance ? (args.autoAdvance ? { 'auto-advance': true } : { 'no-auto-advance': true }) : {}),
450
+ ...(hasInstructionText ? { 'instruction-text': requiredString(args, 'instructionText') } : {})
443
451
  });
444
452
  }
445
453
  if (name === 'overlord_attach_session') {
package/dist/index.js CHANGED
@@ -18952,7 +18952,7 @@ function withWorktreeFields(input, branch) {
18952
18952
  cycle: Number.isFinite(cycleMatch) && cycleMatch > 1 ? cycleMatch : 1
18953
18953
  };
18954
18954
  }
18955
- function planMissionBranch(input) {
18955
+ function planSharedMissionBranch(input) {
18956
18956
  const baseBranch = canonicalMissionBranch(input.mission);
18957
18957
  const allRefs = refSet([...input.refs.local, ...input.refs.remote]);
18958
18958
  const mergedRefs = refSet(input.refs.merged);
@@ -18982,6 +18982,46 @@ function planMissionBranch(input) {
18982
18982
  }
18983
18983
  return { action: "new_cycle", ...withWorktreeFields(input, candidate), from: input.base };
18984
18984
  }
18985
+ function isolationParent(sharedBranch, allRefs, base) {
18986
+ return allRefs.has(sharedBranch) ? sharedBranch : base;
18987
+ }
18988
+ function isolateDecision(input, shared) {
18989
+ const key = sanitizeBranchName(input.isolation?.objectiveKey ?? "", "");
18990
+ if (!key) return shared;
18991
+ const allRefs = refSet([...input.refs.local, ...input.refs.remote]);
18992
+ const mergedRefs = refSet(input.refs.merged);
18993
+ const checkedOutRefs = refSet(input.refs.checkedOut ?? []);
18994
+ const isolatedBase = sanitizeBranchName(`${shared.branch}-${key}`, shared.branch);
18995
+ if (isolatedBase === shared.branch) return shared;
18996
+ if (!allRefs.has(isolatedBase)) {
18997
+ return {
18998
+ action: "create",
18999
+ ...withWorktreeFields(input, isolatedBase),
19000
+ cycle: 1,
19001
+ from: isolationParent(shared.branch, allRefs, input.base)
19002
+ };
19003
+ }
19004
+ if (!mergedRefs.has(isolatedBase)) {
19005
+ return { action: "reuse", ...withWorktreeFields(input, isolatedBase), cycle: 1 };
19006
+ }
19007
+ let cycle = Math.max(2, highestExistingCycle(isolatedBase, allRefs) + 1);
19008
+ let candidate = `${isolatedBase}-${cycle}`;
19009
+ while (allRefs.has(candidate) || checkedOutRefs.has(candidate) || mergedRefs.has(candidate)) {
19010
+ cycle += 1;
19011
+ candidate = `${isolatedBase}-${cycle}`;
19012
+ }
19013
+ return {
19014
+ action: "new_cycle",
19015
+ ...withWorktreeFields(input, candidate),
19016
+ cycle,
19017
+ from: isolationParent(shared.branch, allRefs, input.base)
19018
+ };
19019
+ }
19020
+ function planMissionBranch(input) {
19021
+ const shared = planSharedMissionBranch(input);
19022
+ if (!input.isolation || input.overrideBranch) return shared;
19023
+ return isolateDecision(input, shared);
19024
+ }
18985
19025
  var TITLE_SLUG_MAX;
18986
19026
  var init_branch_planning = __esm({
18987
19027
  "src/branch-planning.ts"() {
@@ -19230,6 +19270,48 @@ function resolveLaunchResourceKey({
19230
19270
  }
19231
19271
  return deriveProjectResourceKey({ directoryPath: options.workingDirectory });
19232
19272
  }
19273
+ function readString(value) {
19274
+ return typeof value === "string" ? value.trim() : "";
19275
+ }
19276
+ async function resolveProjectPrimaryResourceKey({
19277
+ runtime,
19278
+ mission
19279
+ }) {
19280
+ const projectId = readString(mission.projectId);
19281
+ if (!projectId) return DEFAULT_PRIMARY_RESOURCE_KEY;
19282
+ try {
19283
+ const resources = await runtime.backend.get(
19284
+ `/api/projects/${encodeURIComponent(projectId)}/resources`
19285
+ );
19286
+ if (!Array.isArray(resources)) return DEFAULT_PRIMARY_RESOURCE_KEY;
19287
+ const primary = resources.find((resource) => resource?.isPrimary === true);
19288
+ return readString(primary?.resourceKey) || DEFAULT_PRIMARY_RESOURCE_KEY;
19289
+ } catch {
19290
+ return DEFAULT_PRIMARY_RESOURCE_KEY;
19291
+ }
19292
+ }
19293
+ async function resolveBranchIsolation({
19294
+ runtime,
19295
+ mission,
19296
+ options,
19297
+ useWorktree
19298
+ }) {
19299
+ if (!useWorktree) return null;
19300
+ if (mission.allowParallelObjectives !== true) return null;
19301
+ const objectiveId = readString(options.objectiveId);
19302
+ if (!objectiveId) return null;
19303
+ const objectives = Array.isArray(mission.objectives) ? mission.objectives : [];
19304
+ const self = objectives.find((candidate) => readString(candidate.id) === objectiveId);
19305
+ const objectiveKey = readString(self?.displayKey);
19306
+ if (!self || !objectiveKey) return null;
19307
+ const primaryResourceKey = await resolveProjectPrimaryResourceKey({ runtime, mission });
19308
+ const canonical = (value) => readString(value) || primaryResourceKey;
19309
+ const selfResourceKey = canonical(self.resourceKey);
19310
+ const hasLiveSibling = objectives.some(
19311
+ (candidate) => readString(candidate.id) !== objectiveId && PARALLEL_BLOCKING_OBJECTIVE_STATES.includes(readString(candidate.state)) && canonical(candidate.resourceKey) === selfResourceKey
19312
+ );
19313
+ return hasLiveSibling ? { objectiveKey } : null;
19314
+ }
19233
19315
  async function prepareMissionBranch({
19234
19316
  runtime,
19235
19317
  options
@@ -19256,6 +19338,7 @@ async function prepareMissionBranch({
19256
19338
  const overrideBranch = overrideFlag || missionOverrideBranch(mission);
19257
19339
  const projectSlug = await resolveMissionProjectSlug({ runtime, mission });
19258
19340
  const resourceKey = resolveLaunchResourceKey({ mission, options });
19341
+ const isolation = await resolveBranchIsolation({ runtime, mission, options, useWorktree });
19259
19342
  const decision = planMissionBranch({
19260
19343
  mission: {
19261
19344
  title: typeof mission.title === "string" ? mission.title : "mission",
@@ -19267,8 +19350,10 @@ async function prepareMissionBranch({
19267
19350
  base,
19268
19351
  refs,
19269
19352
  worktreeRoot: resolveWorktreeRoot(),
19270
- overrideBranch
19353
+ overrideBranch,
19354
+ isolation
19271
19355
  });
19356
+ const isolated = Boolean(isolation) && !overrideBranch;
19272
19357
  if (useWorktree) {
19273
19358
  ensureWorktree(gitRoot, decision);
19274
19359
  return {
@@ -19279,7 +19364,8 @@ async function prepareMissionBranch({
19279
19364
  worktreePath: decision.worktreePath,
19280
19365
  resourceKey,
19281
19366
  action: decision.action,
19282
- cycle: decision.cycle
19367
+ cycle: decision.cycle,
19368
+ isolated
19283
19369
  }
19284
19370
  };
19285
19371
  }
@@ -19292,15 +19378,19 @@ async function prepareMissionBranch({
19292
19378
  worktreePath: gitRoot,
19293
19379
  resourceKey,
19294
19380
  action: decision.action,
19295
- cycle: decision.cycle
19381
+ cycle: decision.cycle,
19382
+ isolated: false
19296
19383
  }
19297
19384
  };
19298
19385
  }
19386
+ var PARALLEL_BLOCKING_OBJECTIVE_STATES, DEFAULT_PRIMARY_RESOURCE_KEY;
19299
19387
  var init_branch_preparation = __esm({
19300
19388
  "src/branch-preparation.ts"() {
19301
19389
  "use strict";
19302
19390
  init_project_resource_key();
19303
19391
  init_branch_planning();
19392
+ PARALLEL_BLOCKING_OBJECTIVE_STATES = ["launching", "executing", "pending_delivery"];
19393
+ DEFAULT_PRIMARY_RESOURCE_KEY = "primary";
19304
19394
  }
19305
19395
  });
19306
19396
 
@@ -20708,7 +20798,7 @@ Subcommands:
20708
20798
  search-missions Find missions by keyword, status, or project
20709
20799
  discuss-objective Mark a draft objective as submitted (does not start execution)
20710
20800
  add-objectives Append ordered objectives to an existing mission
20711
- update-objective Set auto-advance on an existing objective
20801
+ update-objective Set auto-advance and/or instruction text on an objective
20712
20802
  create Create a draft mission without attaching
20713
20803
  prompt Create a mission and attach to it immediately
20714
20804
  record-work Record completed-from-chat work as a review mission (no attach)
@@ -20833,12 +20923,17 @@ add-objectives:
20833
20923
 
20834
20924
  update-objective:
20835
20925
  Purpose:
20836
- Turn auto-advance on or off for an existing objective.
20926
+ Turn auto-advance on or off and/or edit instruction text on an existing objective.
20837
20927
  Required:
20838
20928
  --objective-id <id>
20839
- --auto-advance or --no-auto-advance
20929
+ At least one of:
20930
+ --auto-advance or --no-auto-advance
20931
+ --instruction-text <text> or --instruction-text-file <path|->
20932
+ Rules:
20933
+ Instruction text may be edited only when the objective is in draft or future state.
20934
+ Blank instruction text is allowed in those states.
20840
20935
  Returns:
20841
- The updated objective JSON, including autoAdvance.
20936
+ The updated objective JSON, including autoAdvance and instructionText.
20842
20937
 
20843
20938
  create:
20844
20939
  Purpose: