overlord-cli 0.2605160931.0 → 0.2605170853.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.
@@ -2,6 +2,9 @@
2
2
 
3
3
  import { spawnSync } from 'node:child_process';
4
4
  import { createRequire } from 'node:module';
5
+ import fs from 'node:fs';
6
+ import os from 'node:os';
7
+ import path from 'node:path';
5
8
 
6
9
  const require = createRequire(import.meta.url);
7
10
  const cliPackage = require('../../package.json');
@@ -50,6 +53,41 @@ export async function fetchLatestCliVersion({
50
53
  }
51
54
  }
52
55
 
56
+ function pruneOldCliVersions() {
57
+ const cliInstallRoot = path.join(os.homedir(), '.ovld', 'cli');
58
+ if (!fs.existsSync(cliInstallRoot)) return;
59
+
60
+ let entries;
61
+ try {
62
+ entries = fs.readdirSync(cliInstallRoot);
63
+ } catch {
64
+ return;
65
+ }
66
+
67
+ const versionDirs = entries
68
+ .filter(name => name.startsWith('overlord-cli-'))
69
+ .flatMap(name => {
70
+ const dirPath = path.join(cliInstallRoot, name);
71
+ try {
72
+ const stat = fs.statSync(dirPath);
73
+ if (!stat.isDirectory()) return [];
74
+ return [{ dirPath, mtime: stat.mtimeMs }];
75
+ } catch {
76
+ return [];
77
+ }
78
+ });
79
+
80
+ // Sort newest-first, keep current + 1 previous, delete the rest
81
+ versionDirs.sort((a, b) => b.mtime - a.mtime);
82
+ for (const { dirPath } of versionDirs.slice(2)) {
83
+ try {
84
+ fs.rmSync(dirPath, { recursive: true, force: true });
85
+ } catch {
86
+ // Best-effort cleanup
87
+ }
88
+ }
89
+ }
90
+
53
91
  export async function checkForCliUpdate(options = {}) {
54
92
  const currentVersion = options.currentVersion ?? CURRENT_CLI_VERSION;
55
93
  const latestVersion = await fetchLatestCliVersion(options);
@@ -115,5 +153,7 @@ export async function runCliUpdateCommand({
115
153
  logger.log('Overlord CLI update complete. Run `ovld version` to confirm the installed version.');
116
154
  }
117
155
 
156
+ pruneOldCliVersions();
157
+
118
158
  return { alreadyLatest: false, currentVersion, latestVersion, result };
119
159
  }
@@ -0,0 +1,91 @@
1
+ // Read/merge/write logic for the per-folder `overlord.json` file.
2
+ // Mirrors lib/overlord-config/local-config.ts for CLI use.
3
+
4
+ import { promises as fs } from 'node:fs';
5
+ import path from 'node:path';
6
+
7
+ export const OVERLORD_CONFIG_FILENAME = 'overlord.json';
8
+
9
+ const FILE_COMMENT =
10
+ 'This file lets Overlord (https://www.ovld.ai) identify which project this folder belongs to. It is created automatically when the folder is added as a project resource. DO NOT EDIT MANUALLY.';
11
+
12
+ function buildConfig(projects) {
13
+ return { _comment: FILE_COMMENT, projects };
14
+ }
15
+
16
+ function isProjectArray(value) {
17
+ return (
18
+ Array.isArray(value) &&
19
+ value.every(
20
+ entry =>
21
+ entry &&
22
+ typeof entry === 'object' &&
23
+ typeof entry.id === 'string' &&
24
+ typeof entry.name === 'string'
25
+ )
26
+ );
27
+ }
28
+
29
+ async function readExistingProjects(filePath) {
30
+ try {
31
+ const raw = await fs.readFile(filePath, 'utf8');
32
+ const parsed = JSON.parse(raw);
33
+ if (parsed && typeof parsed === 'object' && isProjectArray(parsed.projects)) {
34
+ return parsed.projects;
35
+ }
36
+ return [];
37
+ } catch (error) {
38
+ if (error && error.code === 'ENOENT') return null;
39
+ throw error;
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Ensures `directoryPath/overlord.json` lists the project. Returns
45
+ * `{ filePath, action: 'created' | 'added-project' | 'unchanged' }`.
46
+ */
47
+ export async function upsertLocalOverlordConfig({ directoryPath, project }) {
48
+ const filePath = path.join(directoryPath, OVERLORD_CONFIG_FILENAME);
49
+ const existing = await readExistingProjects(filePath);
50
+
51
+ if (existing === null) {
52
+ await fs.writeFile(filePath, `${JSON.stringify(buildConfig([project]), null, 2)}\n`, 'utf8');
53
+ return { filePath, action: 'created' };
54
+ }
55
+
56
+ const idx = existing.findIndex(entry => entry.id === project.id);
57
+ if (idx >= 0) {
58
+ if (existing[idx].name === project.name) return { filePath, action: 'unchanged' };
59
+ const updated = existing.slice();
60
+ updated[idx] = project;
61
+ await fs.writeFile(filePath, `${JSON.stringify(buildConfig(updated), null, 2)}\n`, 'utf8');
62
+ return { filePath, action: 'unchanged' };
63
+ }
64
+
65
+ const merged = [...existing, project];
66
+ await fs.writeFile(filePath, `${JSON.stringify(buildConfig(merged), null, 2)}\n`, 'utf8');
67
+ return { filePath, action: 'added-project' };
68
+ }
69
+
70
+ /**
71
+ * Removes `projectId` from `directoryPath/overlord.json`. Deletes the file
72
+ * when the resulting projects list is empty. Returns
73
+ * `{ filePath, action: 'removed-project' | 'deleted-file' | 'not-found' | 'unchanged' }`.
74
+ */
75
+ export async function removeProjectFromLocalOverlordConfig({ directoryPath, projectId }) {
76
+ const filePath = path.join(directoryPath, OVERLORD_CONFIG_FILENAME);
77
+ const existing = await readExistingProjects(filePath);
78
+
79
+ if (existing === null) return { filePath, action: 'not-found' };
80
+
81
+ const filtered = existing.filter(entry => entry.id !== projectId);
82
+ if (filtered.length === existing.length) return { filePath, action: 'unchanged' };
83
+
84
+ if (filtered.length === 0) {
85
+ await fs.unlink(filePath);
86
+ return { filePath, action: 'deleted-file' };
87
+ }
88
+
89
+ await fs.writeFile(filePath, `${JSON.stringify(buildConfig(filtered), null, 2)}\n`, 'utf8');
90
+ return { filePath, action: 'removed-project' };
91
+ }
@@ -1035,6 +1035,42 @@ async function protocolAsk(args) {
1035
1035
  console.log(JSON.stringify(data, null, 2));
1036
1036
  }
1037
1037
 
1038
+ // ---------------------------------------------------------------------------
1039
+ // request-approval-gate
1040
+ // ---------------------------------------------------------------------------
1041
+
1042
+ async function protocolRequestApprovalGate(args) {
1043
+ const flags = parseFlags(args);
1044
+ const { sessionKey, ticketId } = resolveSessionFlags(flags);
1045
+ if (!sessionKey) throw new Error('--session-key is required (or set SESSION_KEY)');
1046
+ if (!ticketId) throw new Error('--ticket-id is required (or set TICKET_ID)');
1047
+ const reason = flags['reason-file']
1048
+ ? await readTextFileOrStdin(String(flags['reason-file']), '--reason-file')
1049
+ : requireFlag(flags, 'reason', undefined);
1050
+
1051
+ const { platformUrl, bearerToken, localSecret, organizationId } =
1052
+ await resolveProtocolAuthForFlags(flags, ticketId);
1053
+ const timeoutMs = resolveTimeout(flags);
1054
+
1055
+ const body = {
1056
+ sessionKey,
1057
+ ticketId,
1058
+ reason,
1059
+ ...(flags['objective-id'] ? { objectiveId: String(flags['objective-id']) } : {})
1060
+ };
1061
+
1062
+ const data = await apiPost(
1063
+ platformUrl,
1064
+ bearerToken,
1065
+ localSecret,
1066
+ organizationId,
1067
+ '/api/protocol/request-approval-gate',
1068
+ body,
1069
+ timeoutMs
1070
+ );
1071
+ console.log(JSON.stringify(data, null, 2));
1072
+ }
1073
+
1038
1074
  // ---------------------------------------------------------------------------
1039
1075
  // permission-request
1040
1076
  // ---------------------------------------------------------------------------
@@ -1684,6 +1720,21 @@ async function protocolAddProjectResource(args) {
1684
1720
  if (data.resource?.id) {
1685
1721
  process.stderr.write(`\nRESOURCE_ID=${data.resource.id}\n`);
1686
1722
  }
1723
+
1724
+ if (data.project?.id && data.project?.name) {
1725
+ try {
1726
+ const { upsertLocalOverlordConfig } = await import('./local-config.mjs');
1727
+ const result = await upsertLocalOverlordConfig({
1728
+ directoryPath,
1729
+ project: { id: data.project.id, name: data.project.name }
1730
+ });
1731
+ process.stderr.write(`OVERLORD_CONFIG=${result.filePath} (${result.action})\n`);
1732
+ } catch (configError) {
1733
+ process.stderr.write(
1734
+ `\nWarning: could not write overlord.json: ${configError instanceof Error ? configError.message : configError}\n`
1735
+ );
1736
+ }
1737
+ }
1687
1738
  }
1688
1739
 
1689
1740
  // ---------------------------------------------------------------------------
@@ -2238,6 +2289,7 @@ Subcommands:
2238
2289
  update Post progress, activity events, and optional change rationales
2239
2290
  record-change-rationales Persist structured change rationales without a progress update
2240
2291
  ask Post a blocking question and move the ticket to review
2292
+ request-approval-gate Flip auto_advance=false on the next queued future objective
2241
2293
  permission-request Notify Overlord that the agent is requesting tool permission
2242
2294
  hook-event Record a lifecycle hook event without a session key
2243
2295
  read-context Read shared persistent context for this ticket
@@ -2779,6 +2831,10 @@ EOF
2779
2831
  await protocolAsk(args);
2780
2832
  return;
2781
2833
  }
2834
+ if (subcommand === 'request-approval-gate') {
2835
+ await protocolRequestApprovalGate(args);
2836
+ return;
2837
+ }
2782
2838
  if (subcommand === 'permission-request') {
2783
2839
  await protocolPermissionRequest(args);
2784
2840
  return;
@@ -407,6 +407,24 @@ disable-model-invocation: true
407
407
  ---
408
408
 
409
409
  Run \`ovld protocol prompt --agent claude-code\` with \`$ARGUMENTS\`. If no flags are present, treat the arguments as the objective and call \`ovld protocol prompt --agent claude-code --objective "<objective>"\`.`
410
+ },
411
+ {
412
+ path: path.join(base, 'record-work.md'),
413
+ content: `---
414
+ description: Record completed-from-chat work as a ticket in review + feed post (no attach)
415
+ argument-hint: [optional additional context]
416
+ disable-model-invocation: true
417
+ ---
418
+
419
+ Immediately record the work you just completed in this chat as a new Overlord ticket via \`ovld protocol record-work\`. No agent session is opened — the work is already done.
420
+
421
+ Synthesize from the current conversation: \`objective\` (what was asked/done), \`summary\` (reviewer-friendly narrative), \`changeRationales\` (one entry per meaningful git-tracked file change — \`label\`, \`file_path\`, \`summary\`, \`why\`, \`impact\`, optional \`hunks\`; use \`git status\`/\`git diff\` to enumerate), and optional \`artifacts\` (\`next_steps\`, \`test_results\`, \`decision\`, \`note\`, \`url\`).
422
+
423
+ If \`$ARGUMENTS\` is non-empty, treat it as additional context for the summary.
424
+
425
+ Run \`ovld protocol record-work --payload-file -\` and stream the JSON payload \`{ "objective": "...", "summary": "...", "artifacts": [...], "changeRationales": [...] }\` on stdin via a single-quoted heredoc. Report the new \`TICKET_ID\`.
426
+
427
+ Do NOT use this for in-progress work — use \`/prompt\` for that. The CLI validates that every changed git-tracked file is represented in \`changeRationales\` unless \`--skip-file-change-check\` is passed. If project resolution fails, re-run with \`--project-id <id>\` or \`--personal\`.`
410
428
  }
411
429
  ];
412
430
  }
@@ -433,6 +451,11 @@ Run \`ovld protocol prompt --agent claude-code\` with \`$ARGUMENTS\`. If no flag
433
451
  path: path.join(base, 'prompt.md'),
434
452
  content:
435
453
  'Create a new Overlord ticket.\n\nRun `ovld protocol prompt --agent cursor --objective "<objective>"` using the text after `/prompt` unless raw flags were provided. If raw flags were provided, pass them after `ovld protocol prompt --agent cursor`.\n'
454
+ },
455
+ {
456
+ path: path.join(base, 'record-work.md'),
457
+ content:
458
+ 'Record completed-from-chat work as a ticket in review + feed post (no attach).\n\nImmediately record the work you just completed in this chat as a new Overlord ticket via `ovld protocol record-work`. No agent session is opened — the work is already done.\n\nSynthesize from the current conversation: `objective` (what was asked/done), `summary` (reviewer-friendly narrative), `changeRationales` (one entry per meaningful git-tracked file change — `label`, `file_path`, `summary`, `why`, `impact`, optional `hunks`; use `git status`/`git diff` to enumerate), and optional `artifacts` (`next_steps`, `test_results`, `decision`, `note`, `url`).\n\nIf text was provided after `/record-work`, treat it as additional context for the summary.\n\nRun `ovld protocol record-work --payload-file -` and stream the JSON payload on stdin via a single-quoted heredoc. Report the new TICKET_ID.\n\nDo NOT use for in-progress work — use `/prompt` for that. The CLI validates that every changed git-tracked file is represented in `changeRationales` unless `--skip-file-change-check` is passed. If project resolution fails, re-run with `--project-id <id>` or `--personal`.\n'
436
459
  }
437
460
  ];
438
461
  }
@@ -459,6 +482,11 @@ Run \`ovld protocol prompt --agent claude-code\` with \`$ARGUMENTS\`. If no flag
459
482
  path: path.join(base, 'prompt.toml'),
460
483
  content:
461
484
  'description = "Create a new Overlord ticket from the current conversation."\nprompt = """\nRun `ovld protocol prompt --agent gemini --objective "<objective>"` using `{{args}}` as the objective unless raw flags were provided. If raw flags were provided, pass them after `ovld protocol prompt --agent gemini`.\n"""\n'
485
+ },
486
+ {
487
+ path: path.join(base, 'record-work.toml'),
488
+ content:
489
+ 'description = "Record completed-from-chat work as a ticket in review + feed post (no attach)."\nprompt = """\nImmediately record the work you just completed in this chat as a new Overlord ticket via `ovld protocol record-work`. No agent session is opened — the work is already done.\n\nSynthesize from the current conversation: `objective` (what was asked/done), `summary` (reviewer-friendly narrative), `changeRationales` (one entry per meaningful git-tracked file change — `label`, `file_path`, `summary`, `why`, `impact`, optional `hunks`; use `git status`/`git diff` to enumerate), and optional `artifacts` (`next_steps`, `test_results`, `decision`, `note`, `url`).\n\nIf `{{args}}` is non-empty, treat it as additional context for the summary.\n\nRun `ovld protocol record-work --payload-file -` and stream the JSON payload on stdin via a single-quoted heredoc. Report the new TICKET_ID.\n\nDo NOT use for in-progress work — use `/prompt` for that. The CLI validates that every changed git-tracked file is represented in `changeRationales` unless `--skip-file-change-check` is passed. If project resolution fails, re-run with `--project-id <id>` or `--personal`.\n"""\n'
462
490
  }
463
491
  ];
464
492
  }
@@ -500,6 +528,23 @@ agent: build
500
528
  ---
501
529
 
502
530
  Run \`ovld protocol prompt --agent opencode\` with \`$ARGUMENTS\`. If no flags are present, treat the arguments as the objective and call \`ovld protocol prompt --agent opencode --objective "<objective>"\`.`
531
+ },
532
+ {
533
+ path: path.join(base, 'record-work.md'),
534
+ content: `---
535
+ description: Record completed-from-chat work as a ticket in review + feed post (no attach)
536
+ agent: build
537
+ ---
538
+
539
+ Immediately record the work you just completed in this chat as a new Overlord ticket via \`ovld protocol record-work\`. No agent session is opened — the work is already done.
540
+
541
+ Synthesize from the current conversation: \`objective\` (what was asked/done), \`summary\` (reviewer-friendly narrative), \`changeRationales\` (one entry per meaningful git-tracked file change — \`label\`, \`file_path\`, \`summary\`, \`why\`, \`impact\`, optional \`hunks\`; use \`git status\`/\`git diff\` to enumerate), and optional \`artifacts\` (\`next_steps\`, \`test_results\`, \`decision\`, \`note\`, \`url\`).
542
+
543
+ If \`$ARGUMENTS\` is non-empty, treat it as additional context for the summary.
544
+
545
+ Run \`ovld protocol record-work --payload-file -\` and stream the JSON payload on stdin via a single-quoted heredoc. Report the new \`TICKET_ID\`.
546
+
547
+ Do NOT use for in-progress work — use \`/prompt\` for that. The CLI validates that every changed git-tracked file is represented in \`changeRationales\` unless \`--skip-file-change-check\` is passed. If project resolution fails, re-run with \`--project-id <id>\` or \`--personal\`.`
503
548
  }
504
549
  ];
505
550
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "overlord-cli",
3
- "version": "0.2605160931.0",
3
+ "version": "0.2605170853.0",
4
4
  "description": "Overlord CLI — launch AI agents on tickets from anywhere",
5
5
  "homepage": "https://www.ovld.ai/docs/",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "overlord",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Overlord ticket protocol workflow for Claude Code — attach/update/ask/deliver, slash commands, and hook-driven activity capture.",
5
5
  "author": {
6
6
  "name": "Cooperativ",
@@ -0,0 +1,28 @@
1
+ ---
2
+ description: Record completed-from-chat work as a ticket in review + feed post (no attach)
3
+ argument-hint: [optional additional context]
4
+ disable-model-invocation: true
5
+ ---
6
+
7
+ Immediately record the work you just completed in this chat as a new Overlord ticket via `ovld protocol record-work`. No agent session is opened — the work is already done.
8
+
9
+ Synthesize from the current conversation:
10
+ - `objective`: what was asked / what was done (1–3 sentences).
11
+ - `summary`: reviewer-friendly narrative of what changed and why.
12
+ - `changeRationales`: one entry per meaningful git-tracked file change (`label`, `file_path`, `summary`, `why`, `impact`, optional `hunks`). Use `git status` and `git diff` to enumerate changed files.
13
+ - `artifacts` (optional): `next_steps`, `test_results`, `decision`, `note`, `url`.
14
+
15
+ If `$ARGUMENTS` is non-empty, treat it as additional context to weave into the summary.
16
+
17
+ Run:
18
+ `ovld protocol record-work --payload-file -`
19
+
20
+ and stream a JSON object `{ "objective": "...", "summary": "...", "artifacts": [...], "changeRationales": [...] }` on stdin via a single-quoted heredoc (`<<'EOF'`).
21
+
22
+ After the command succeeds, report the new `TICKET_ID`.
23
+
24
+ Rules:
25
+ - Do NOT use this for in-progress work. Use `/prompt` for that.
26
+ - The CLI validates that every changed git-tracked file is represented in `changeRationales` unless `--skip-file-change-check` is passed.
27
+ - If project resolution fails, re-run with `--project-id <id>` or `--personal`.
28
+ - If `ovld` reports `OVERLORD_URL` is unreachable, stop and request permission escalation or network access before retrying.
@@ -94,8 +94,7 @@ Use this mode when the conversation starts normally and the user asks Claude to
94
94
  1. If the user wants to create tickets (and does not ask to start execution), run `ovld protocol create --agent claude-code --objective "..."`.
95
95
  - When `--session-key` and `--ticket-id` are provided, it creates a follow-up draft.
96
96
  - When session flags are omitted, it resolves the project by matching current working directory (or `--working-directory`) to Overlord `local_working_directory`, then creates a standalone draft.
97
- 2. Default to `create` for new draft tickets. Only use `/overlord:prompt` or `ovld protocol prompt --agent claude-code --objective "..."` when the user explicitly asks to create and execute immediately. If the work is already complete in chat and just needs to be recorded, use `ovld protocol record-work` instead.
98
- `prompt` creates the ticket in `execute` status and attaches immediately.
97
+ 2. Default to `create` for new draft tickets. Only use `/overlord:prompt` or `ovld protocol prompt --agent claude-code --objective "..."` when the user explicitly asks to create and execute immediately. If the work is already complete in chat and just needs to be recorded, use `ovld protocol record-work` instead.`prompt` creates the ticket in `execute` status and attaches immediately.
99
98
  3. If the user already has a ticket ID and only wants to inspect it, use `/overlord:load` or run `ovld protocol load-context --ticket-id <ticket_id>`.
100
99
  4. If the user wants to route the current session onto an existing ticket by ID, use `/overlord:connect` or run `ovld protocol connect --ticket-id <ticket_id>`.
101
100
  5. If the user wants to establish a persistent session with a ticket by ID, use `/overlord:attach` or run `ovld protocol attach --ticket-id <ticket_id>`.
@@ -175,9 +174,6 @@ When creating tickets from within a repository:
175
174
 
176
175
  ```bash
177
176
  ovld protocol create --agent claude-code --objective "Capture follow-up work from this repository"
178
- ```
179
-
180
- ```bash
181
177
  ovld protocol prompt --agent claude-code --objective "Implement feature X" --priority medium
182
178
  ```
183
179
 
@@ -189,6 +185,24 @@ ovld protocol discover-project
189
185
 
190
186
  You can override with `--project-id` or `--working-directory` if needed.
191
187
 
188
+ ### Resolving the project ID when you don't have one
189
+
190
+ When you need a project ID for a protocol command and the ticket prompt did not supply one, resolve it in this order.
191
+
192
+ **Locally (CLI inside a shell on the user's machine):**
193
+
194
+ 1. `--project-id` if explicitly provided.
195
+ 2. Otherwise, let the CLI match the current working directory (the default behavior of `create`, `prompt`, `discover-project`).
196
+ 3. If working-directory resolution returns nothing, read `overlord.json` from the cwd (or any ancestor you have access to) and pass its project id via `--project-id`.
197
+
198
+ **Over MCP (web agents and hosted tools, where the server cannot see the agent's cwd):**
199
+
200
+ 1. `--project-id` / `projectId` if explicitly provided.
201
+ 2. Read `overlord.json` from the directory the user is accessing and pass its project id as `projectId`.
202
+ 3. As a last resort, try `workingDirectory` resolution.
203
+
204
+ If `overlord.json` contains more than one project, show the user the project **names** from that file and ask which one to use before calling any protocol command — never silently pick one.
205
+
192
206
  ### Choosing `--execution-target`
193
207
 
194
208
  Pass `--execution-target agent` or `--execution-target human` (default: `human`) when creating tickets.
@@ -210,6 +224,10 @@ ovld protocol attachment-download-url --session-key <sessionKey> --ticket-id $TI
210
224
 
211
225
  The `attach` and `load-context` responses already include `attachments` and `objectives` arrays — use those for `<attachment-id>` and `<objective-id>` values. Run `attachment-list` mid-session if new files have been uploaded since attach.
212
226
 
227
+ Objective attachment uploads also expose two-step variants — `attachment-prepare-upload` and `attachment-finalize-upload` — for callers that need a signed URL directly. Prefer `attachment-upload-file` for one-shot uploads.
228
+
229
+ "Artifacts" in `deliver` are the structured records an agent submits at delivery time (next_steps, test_results, migration, decision, note, url) — not user-uploaded files.
230
+
213
231
  ## Large Artifacts
214
232
 
215
233
  For large artifacts such as planning documents, architecture decisions, research summaries, or design documents: **save the full content as a markdown file in the linked repository, then summarize it in the artifact returned to the ticket.**
@@ -226,8 +244,6 @@ This keeps the ticket feed readable while preserving the full document in versio
226
244
  - Hosted Overlord MCP (`/functions/v1/mcp`) uses the same canonical tool names as any local MCP shim that shells into `ovld protocol` (`attach`, `update`, `deliver`, `record_work`, …). Hosted calls use camelCase JSON keys (`ticketId`, `sessionKey`) matching `POST /api/protocol/*` bodies; the local shim uses snake_case keys mapped to CLI flags (`ticket_id`, `session_key`).
227
245
  - `permission-request` is invoked by the Claude Code permission hook installed by the bundle. Agents do not normally call it directly.
228
246
  - `record_change_rationales` (MCP) and `ovld protocol record-change-rationales` (CLI) both write to the same `file_changes` table. The dedicated CLI route is `POST /api/protocol/record-change-rationales`.
229
- - 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-*` and require `--objective-id` for upload/finalize.
230
- - "Artifacts" in `deliver` are the structured records an agent submits at delivery 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.
231
247
 
232
248
  ## Rules
233
249
 
@@ -242,4 +258,4 @@ This keeps the ticket feed readable while preserving the full document in versio
242
258
  - Do not add or commit changes unless the user explicitly asks you to commit.
243
259
  - Delivery is the concluding step. After delivering, stop unless the user follows up or the ticket is reopened.
244
260
 
245
- <!-- version: 0.4.10 -->
261
+ <!-- version: 0.4.11 -->
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "overlord",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Local Overlord workflow plugin for Cursor terminal agents.",
5
5
  "author": {
6
6
  "name": "Cooperativ",
@@ -0,0 +1,9 @@
1
+ Run `ovld protocol record-work --payload-file -` to record completed-from-chat work as a ticket in review + feed post (no attach).
2
+
3
+ Synthesize from the current conversation: `objective` (what was asked/done), `summary` (reviewer-friendly narrative), `changeRationales` (one entry per meaningful git-tracked file change — `label`, `file_path`, `summary`, `why`, `impact`, optional `hunks`; use `git status`/`git diff` to enumerate), and optional `artifacts` (`next_steps`, `test_results`, `decision`, `note`, `url`).
4
+
5
+ If text was provided after `/record-work`, treat it as additional context for the summary.
6
+
7
+ Stream the JSON payload on stdin via a single-quoted heredoc. Report the new TICKET_ID.
8
+
9
+ Do NOT use for in-progress work — use `/prompt` for that. If `ovld` reports `OVERLORD_URL` is unreachable, stop and request permission escalation or network access before retrying.
@@ -90,6 +90,36 @@ ovld protocol revert --objective-id <objective-id>
90
90
 
91
91
  Use `ovld protocol record-work` when the user has done work directly in chat (no attached ticket) and wants to record it as a ticket plus publish a feed post. This is distinct from `create` (drafts future work), `prompt` (creates + executes), and `deliver` (concludes an attached session). `record-work` creates a ticket in `review` with a completed objective and triggers the feed-post generator atomically. Project resolution mirrors `prompt`: cwd is matched against `project_user.local_working_directory`; if no match, ask the user for `--project-id` or pass `--personal`. Do NOT use for in-progress work.
92
92
 
93
+ Do NOT use `record-work` for in-progress work. Use it only when the work is already done in the chat.
94
+
95
+ ```bash
96
+ ovld protocol record-work \
97
+ --objective "User asked me to X; I did Y by..." \
98
+ --summary "Narrative for the feed post and reviewer." \
99
+ --change-rationales-file -
100
+ ```
101
+
102
+ Or stream the full payload via stdin to avoid quote escaping:
103
+
104
+ ```bash
105
+ ovld protocol record-work --payload-file - <<'EOF'
106
+ {
107
+ "objective": "...",
108
+ "summary": "...",
109
+ "artifacts": [{"type": "next_steps", "label": "...", "content": "..."}],
110
+ "changeRationales": [{"label": "...", "file_path": "...", "summary": "...", "why": "...", "impact": "...", "hunks": [{"header": "@@ ..."}]}]
111
+ }
112
+ EOF
113
+ ```
114
+
115
+ Project resolution mirrors `prompt`/`create`: cwd is matched against the caller's `project_user.local_working_directory`. If no match:
116
+
117
+ 1. **Ask the user for a `--project-id`** if the work is project-related. Show them a brief description of what you're about to record so they can pick the right project.
118
+ 2. If the user confirms the work is not tied to any project, pass `--personal` to create a private ticket.
119
+
120
+ Validation: in a git workspace, `record-work` checks that uncommitted changes are represented by `changeRationales` and rejects if they aren't (same check as `deliver`). Pass `--skip-file-change-check` only when intentionally bypassing.
121
+
122
+
93
123
  ## Change Rationales
94
124
 
95
125
  Always include `changeRationales` when delivering. Optionally include them on updates during long-running work.
@@ -120,9 +150,32 @@ When creating tickets from within a repository:
120
150
  ```bash
121
151
  ovld protocol create --agent cursor --objective "Capture follow-up work from this repository"
122
152
  ovld protocol prompt --agent cursor --objective "Implement feature X" --priority medium
153
+ ```
154
+
155
+ To inspect project resolution explicitly:
156
+
157
+ ```bash
123
158
  ovld protocol discover-project
124
159
  ```
125
160
 
161
+ ### Resolving the project ID when you don't have one
162
+
163
+ When you need a project ID for a protocol command and the ticket prompt did not supply one, resolve it in this order.
164
+
165
+ **Locally (CLI inside a shell on the user's machine):**
166
+
167
+ 1. `--project-id` if explicitly provided.
168
+ 2. Otherwise, let the CLI match the current working directory (the default behavior of `create`, `prompt`, `discover-project`).
169
+ 3. If working-directory resolution returns nothing, read `overlord.json` from the cwd (or any ancestor you have access to) and pass its project id via `--project-id`.
170
+
171
+ **Over MCP (web agents and hosted tools, where the server cannot see the agent's cwd):**
172
+
173
+ 1. `--project-id` / `projectId` if explicitly provided.
174
+ 2. Read `overlord.json` from the directory the user is accessing and pass its project id as `projectId`.
175
+ 3. As a last resort, try `workingDirectory` resolution.
176
+
177
+ If `overlord.json` contains more than one project, show the user the project **names** from that file and ask which one to use before calling any protocol command — never silently pick one.
178
+
126
179
  ### Choosing `--execution-target`
127
180
 
128
181
  Pass `--execution-target agent` or `--execution-target human` (default: `human`) when creating tickets.
@@ -174,6 +227,7 @@ This keeps the ticket feed readable while preserving the full document in versio
174
227
  - The `summary` in deliver is what the PM reads first, so write it as a narrative, not a command list.
175
228
  - If a protocol or MCP call fails with auth/session errors, run `ovld auth repair` yourself before asking the user to log in again or proceed without Overlord updates.
176
229
  - If you must run `ovld auth login`, always include `--organization-id <id>` — use the organization ID from the ticket prompt context to select the organization non-interactively and avoid a blocking TTY prompt.
230
+ - Do not add or commit changes unless the user explicitly asks you to commit.
177
231
  - Delivery is the concluding step. After delivering, stop unless the user follows up or the ticket is reopened.
178
232
 
179
- <!-- version: 0.4.10 -->
233
+ <!-- version: 0.4.11 -->
@@ -292,6 +292,31 @@ const tools = [
292
292
  }),
293
293
  subcommand: 'ask'
294
294
  },
295
+ {
296
+ name: 'request_approval_gate',
297
+ description:
298
+ 'Flip auto_advance=false on the next queued future objective so a human must approve before it runs.',
299
+ inputSchema: {
300
+ type: 'object',
301
+ properties: {
302
+ session_key: { type: 'string' },
303
+ ticket_id: {
304
+ type: 'string',
305
+ description: 'Ticket identifier (e.g. 1:899). Also accepts UUID.'
306
+ },
307
+ reason: { type: 'string' },
308
+ objective_id: { type: 'string' }
309
+ },
310
+ required: ['session_key', 'ticket_id', 'reason']
311
+ },
312
+ toCliFlags: args => ({
313
+ 'session-key': args.session_key,
314
+ 'ticket-id': args.ticket_id,
315
+ reason: args.reason,
316
+ 'objective-id': args.objective_id
317
+ }),
318
+ subcommand: 'request-approval-gate'
319
+ },
295
320
  {
296
321
  name: 'read_context',
297
322
  description: 'Read persistent shared context entries for a ticket.',
@@ -106,6 +106,24 @@ ovld protocol create --agent codex --objective "Capture follow-up work from this
106
106
  ovld protocol prompt --agent codex --objective "Implement feature X" --priority medium
107
107
  ```
108
108
 
109
+ ### Resolving the project ID when you don't have one
110
+
111
+ When you need a project ID for a protocol command and the ticket prompt did not supply one, resolve it in this order.
112
+
113
+ **Locally (CLI inside a shell on the user's machine):**
114
+
115
+ 1. `--project-id` if explicitly provided.
116
+ 2. Otherwise, let the CLI match the current working directory (the default behavior of `create`, `prompt`, `discover-project`).
117
+ 3. If working-directory resolution returns nothing, read `overlord.json` from the cwd (or any ancestor you have access to) and pass its project id via `--project-id`.
118
+
119
+ **Over MCP (web agents and hosted tools, where the server cannot see the agent's cwd):**
120
+
121
+ 1. `--project-id` / `projectId` if explicitly provided.
122
+ 2. Read `overlord.json` from the directory the user is accessing and pass its project id as `projectId`.
123
+ 3. As a last resort, try `workingDirectory` resolution.
124
+
125
+ If `overlord.json` contains more than one project, show the user the project **names** from that file and ask which one to use before calling any protocol command — never silently pick one.
126
+
109
127
  ### Choosing `--execution-target`
110
128
 
111
129
  Pass `--execution-target agent` or `--execution-target human` (default: `human`) when creating tickets.
@@ -195,4 +213,4 @@ This keeps the ticket feed readable while preserving the full document in versio
195
213
  - If a protocol or MCP call fails with auth/session errors, run `ovld auth repair` yourself before asking the user to log in again or proceed without Overlord updates.
196
214
  - If you must run `ovld auth login`, always include `--organization-id <id>` — use the organization ID from the ticket prompt context to select the organization non-interactively and avoid a blocking TTY prompt.
197
215
 
198
- <!-- version: 0.4.10 -->
216
+ <!-- version: 0.4.11 -->