@phyzess/pappra 0.1.0-beta.12 → 0.1.0-beta.13

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.
@@ -1,5 +1,5 @@
1
- import { discoverWorkspace } from '../../workspace/discover.js';
2
1
  import { resolveWorkspaceRoot } from '../../workspace/resolve.js';
2
+ import { scanDiscoveredDocuments } from '../../knowledge/scan.js';
3
3
  import { formatDiagnostic } from '../../schemas/diagnostics.js';
4
4
  import { docsArchiveReportSchema, docsCheckReportSchema, docsListReportSchema, } from '../../schemas/document.js';
5
5
  import { KnowledgeService, } from '../../knowledge/index.js';
@@ -10,96 +10,32 @@ export const docsUsage = 'pappra docs <list|check|archive|scan> [options]\n' +
10
10
  ' scan [--apply] [--json]\n\n' +
11
11
  'scan registers documents discovered under the configured knowledge sources; without\n' +
12
12
  '--apply it only reports the candidates.';
13
- function classifyScanKind(path) {
14
- const lower = path.toLowerCase();
15
- if (lower.endsWith('agents.md') || lower.endsWith('claude.md'))
16
- return 'rule';
17
- if (lower.includes('/research/'))
18
- return 'research';
19
- if (lower.includes('/adr/') || lower.includes('decision'))
20
- return 'decision';
21
- if (lower.includes('review') || lower.includes('retro'))
22
- return 'review';
23
- if (lower.includes('plan'))
24
- return 'plan';
25
- return 'architecture';
26
- }
27
13
  async function runDocsScan(options, output, root) {
28
- const discovery = await discoverWorkspace(root);
29
- const config = discovery.config;
30
- if (config === null) {
31
- return invalidArguments(output, options.json, 'docs scan requires a valid .pappra/workspace.json; run pappra init first.');
32
- }
33
- const service = new KnowledgeService(root);
34
- const listed = await service.list({ includePrivate: true });
35
- const registered = new Set(listed.records.flatMap((record) => record.location.kind === 'workspace' ? [record.location.path] : []));
36
- const sources = config.knowledge.sources;
37
- const visibility = config.knowledge.taskVisibility === 'shared' ? 'shareable' : 'private';
38
- const candidates = (discovery.documents ?? []).flatMap((document) => {
39
- const path = document.workspaceRelativePath;
40
- if (path === null)
41
- return [];
42
- if (!sources.some((source) => path === source || path.startsWith(`${source}/`))) {
43
- return [];
44
- }
45
- if (registered.has(path))
46
- return [];
47
- return [{ path, kind: classifyScanKind(path), visibility }];
48
- });
14
+ const result = await scanDiscoveredDocuments(root, { apply: options.apply });
15
+ if ('message' in result) {
16
+ return invalidArguments(output, options.json, result.message);
17
+ }
49
18
  if (!options.apply) {
50
19
  if (options.json) {
51
- output.stdout(`${JSON.stringify({
52
- ok: true,
53
- schema: 'pappra/docs-scan/1',
54
- mode: 'plan',
55
- workspaceRoot: root,
56
- candidates,
57
- registered: registered.size,
58
- nextActions: [
59
- 'Rerun with --apply to register these documents in the knowledge catalog.',
60
- ],
61
- })}\n`);
20
+ output.stdout(`${JSON.stringify(result)}\n`);
62
21
  }
63
22
  else {
64
- output.stdout(`Scan candidates: ${candidates.length}\n`);
65
- for (const candidate of candidates) {
23
+ output.stdout(`Scan candidates: ${result.candidates.length}\n`);
24
+ for (const candidate of result.candidates) {
66
25
  output.stdout(` ${candidate.kind} ${candidate.path}\n`);
67
26
  }
68
27
  output.stdout('Plan only; rerun with --apply to register them.\n');
69
28
  }
70
29
  return 0;
71
30
  }
72
- const registeredNow = [];
73
- const diagnostics = [];
74
- for (const candidate of candidates) {
75
- const result = await service.register({
76
- location: { kind: 'workspace', path: candidate.path },
77
- kind: candidate.kind,
78
- maturity: 'draft',
79
- owner: 'human',
80
- visibility: candidate.visibility,
81
- });
82
- diagnostics.push(...result.diagnostics);
83
- if (result.ok)
84
- registeredNow.push(result.record);
85
- }
86
- const ok = diagnostics.every((entry) => entry.severity !== 'error');
87
31
  if (options.json) {
88
- output.stdout(`${JSON.stringify({
89
- ok,
90
- schema: 'pappra/docs-scan/1',
91
- mode: 'apply',
92
- workspaceRoot: root,
93
- registered: registeredNow,
94
- skipped: candidates.length - registeredNow.length,
95
- diagnostics,
96
- })}\n`);
32
+ output.stdout(`${JSON.stringify(result)}\n`);
97
33
  }
98
34
  else {
99
- output.stdout(`Registered ${registeredNow.length} document(s).\n`);
100
- printDiagnostics(output, diagnostics);
35
+ output.stdout(`Registered ${result.registered.length} document(s).\n`);
36
+ printDiagnostics(output, result.diagnostics);
101
37
  }
102
- return ok ? 0 : 1;
38
+ return result.ok ? 0 : 1;
103
39
  }
104
40
  function printDiagnostics(output, diagnostics) {
105
41
  for (const diagnostic of diagnostics) {
@@ -4,11 +4,13 @@ export declare const hostUsage: string;
4
4
  export interface HostCommandOptions {
5
5
  readonly cwd: string;
6
6
  readonly json: boolean;
7
- readonly action: 'detect' | 'describe' | 'plan' | 'install' | 'verify' | 'bundle' | null;
7
+ readonly action: 'detect' | 'describe' | 'plan' | 'install' | 'verify' | 'bundle' | 'mcp' | 'probe' | null;
8
8
  readonly host: HostName | null;
9
9
  readonly hostRoot: string | null;
10
10
  readonly hostVersion: string | null;
11
11
  readonly apply: boolean;
12
+ readonly force: boolean;
13
+ readonly subAction: string | null;
12
14
  readonly out: string | null;
13
15
  readonly mode: 'project-settings' | 'plugin' | null;
14
16
  readonly marketplace: boolean;
@@ -2,9 +2,11 @@ import { homedir } from 'node:os';
2
2
  import { join, resolve } from 'node:path';
3
3
  import { HostIntegrationService } from '../../hosts/integration/service.js';
4
4
  import { exportHostPluginBundle } from '../../hosts/integration/bundle-export.js';
5
+ import { applyHostMcp, planHostMcp, verifyHostMcp, } from '../../app/host-mcp.js';
6
+ import { probeHost } from '../../app/host-probe.js';
5
7
  import { HOST_SUPPORT, isSupportedVersion, } from '../../hosts/integration/support.js';
6
8
  import { getPackageInfo } from '../../package-info.js';
7
- export const hostUsage = 'pappra host <detect|describe|plan|install|verify|bundle> --host <pi|codex|claude>\n' +
9
+ export const hostUsage = 'pappra host <detect|describe|plan|install|verify|bundle|mcp> --host <pi|codex|claude>\n' +
8
10
  ' [--host-root <dir>] [--host-version <version>] [--out <dir>]\n' +
9
11
  ' [--mode project-settings|plugin] [--apply] [--json]\n' +
10
12
  '\n' +
@@ -80,6 +82,58 @@ export async function runHostCommand(options, output) {
80
82
  ? {}
81
83
  : { hostVersion: options.hostVersion }),
82
84
  };
85
+ if (options.action === 'mcp') {
86
+ if (options.subAction !== 'plan' &&
87
+ options.subAction !== 'install' &&
88
+ options.subAction !== 'verify') {
89
+ output.stderr('pappra host mcp requires plan, install, or verify.\n');
90
+ return 2;
91
+ }
92
+ const mcpPlan = await planHostMcp(options.host, options.cwd);
93
+ const result = options.subAction === 'verify'
94
+ ? await verifyHostMcp(mcpPlan)
95
+ : options.subAction === 'install' && options.apply
96
+ ? await applyHostMcp(mcpPlan, { force: options.force })
97
+ : {
98
+ ...mcpPlan,
99
+ applied: false,
100
+ verified: false,
101
+ detail: null,
102
+ };
103
+ if (options.json) {
104
+ output.stdout(`${JSON.stringify({ schema: 'pappra/host-mcp/1', ...result })}\n`);
105
+ }
106
+ else {
107
+ output.stdout(`Host ${result.host} MCP: ${result.state}\n`);
108
+ if (result.target !== null)
109
+ output.stdout(` target: ${result.target}\n`);
110
+ if (result.command !== null) {
111
+ output.stdout(` command: ${result.command.join(' ')}\n`);
112
+ }
113
+ output.stdout(` ${result.message}\n`);
114
+ if (result.detail !== null)
115
+ output.stdout(` detail: ${result.detail}\n`);
116
+ if (options.subAction === 'install' && !options.apply) {
117
+ output.stdout(' plan only; rerun with --apply to write it.\n');
118
+ }
119
+ }
120
+ return result.verified || (!options.apply && options.subAction !== 'verify')
121
+ ? 0
122
+ : 1;
123
+ }
124
+ if (options.action === 'probe') {
125
+ const result = probeHost(options.host);
126
+ if (options.json) {
127
+ output.stdout(`${JSON.stringify({ schema: 'pappra/host-probe/1', ...result })}\n`);
128
+ }
129
+ else {
130
+ output.stdout(`Host ${result.host} probe: ${result.ok ? 'ok' : 'failed'}\n`);
131
+ output.stdout(` version: ${result.version ?? '(missing)'} / pinned ${result.pinnedVersion}\n`);
132
+ output.stdout(` protocol: ${result.protocol}\n`);
133
+ output.stdout(` ${result.message}\n`);
134
+ }
135
+ return result.ok ? 0 : 1;
136
+ }
83
137
  if (options.action === 'detect' || options.action === 'describe') {
84
138
  const capabilities = service.describe(options.host);
85
139
  if (options.json) {
@@ -0,0 +1,8 @@
1
+ import type { CliOutput } from '../output.js';
2
+ export declare const mcpUsage: string;
3
+ export interface McpCommandOptions {
4
+ readonly cwd: string;
5
+ readonly root: string | null;
6
+ readonly json: boolean;
7
+ }
8
+ export declare function runMcpCommand(options: McpCommandOptions, output: CliOutput): Promise<number>;
@@ -0,0 +1,13 @@
1
+ import { runMcpServer } from '../../mcp/server.js';
2
+ export const mcpUsage = 'pappra mcp [--root <dir>] [--json]\n\n' +
3
+ 'Starts the Pappra MCP server on stdio for the selected workspace. The server exposes\n' +
4
+ 'read-only tools (workspace_status, resume, docs_list, docs_check, code_status,\n' +
5
+ 'code_query, runtime_status). stdout carries MCP JSON-RPC; use --help outside a host.';
6
+ export async function runMcpCommand(options, output) {
7
+ const root = options.root ?? options.cwd;
8
+ if (options.json) {
9
+ output.stderr('pappra mcp: --json is not used; the server speaks MCP JSON-RPC on stdout.\n');
10
+ }
11
+ await runMcpServer(root);
12
+ return 0;
13
+ }
@@ -0,0 +1,10 @@
1
+ import type { CliOutput } from '../output.js';
2
+ export declare const taskUsage: string;
3
+ export interface TaskCommandOptions {
4
+ readonly cwd: string;
5
+ readonly action: 'pin' | 'unpin' | 'show' | null;
6
+ readonly taskId: string | null;
7
+ readonly autoResume: boolean;
8
+ readonly json: boolean;
9
+ }
10
+ export declare function runTaskCommand(options: TaskCommandOptions, output: CliOutput): Promise<number>;
@@ -0,0 +1,76 @@
1
+ import { resolveWorkspaceRoot } from '../../workspace/resolve.js';
2
+ import { pinTask, readAutoResume, readTaskPin, setAutoResume, unpinTask, } from '../../app/task-pin.js';
3
+ export const taskUsage = 'pappra task <pin|unpin|show> [taskId] [--auto-resume] [--json]\n\n' +
4
+ 'pin records the current task in .pappra/local/current-task. Hooks inject a resume\n' +
5
+ 'packet only when the workspace continuity.autoResume switch is on (--auto-resume).';
6
+ function failure(output, options, message) {
7
+ if (options.json) {
8
+ output.stdout(`${JSON.stringify({ ok: false, code: 'task_invalid', message })}\n`);
9
+ }
10
+ else {
11
+ output.stderr(`pappra task: ${message}\n`);
12
+ }
13
+ return 2;
14
+ }
15
+ export async function runTaskCommand(options, output) {
16
+ const resolution = await resolveWorkspaceRoot({ startPath: options.cwd });
17
+ const root = resolution.root;
18
+ if (root === null) {
19
+ return failure(output, options, 'task requires a registered Pappra workspace above the current directory.');
20
+ }
21
+ if (options.action === 'pin') {
22
+ if (options.taskId === null) {
23
+ return failure(output, options, 'task pin requires a task id.');
24
+ }
25
+ const pinned = await pinTask(root, options.taskId);
26
+ if ('message' in pinned) {
27
+ return failure(output, options, pinned.message);
28
+ }
29
+ if (options.autoResume) {
30
+ const switched = await setAutoResume(root, true);
31
+ if ('message' in switched) {
32
+ return failure(output, options, switched.message);
33
+ }
34
+ }
35
+ const report = {
36
+ ok: true,
37
+ action: 'pin',
38
+ task: pinned.taskId,
39
+ autoResume: options.autoResume ? true : await readAutoResume(root),
40
+ };
41
+ output.stdout(options.json
42
+ ? `${JSON.stringify(report)}\n`
43
+ : `Pinned task ${report.task}; autoResume ${report.autoResume ? 'on' : 'off'}.\n`);
44
+ return 0;
45
+ }
46
+ if (options.action === 'unpin') {
47
+ await unpinTask(root);
48
+ const report = { ok: true, action: 'unpin' };
49
+ output.stdout(options.json
50
+ ? `${JSON.stringify(report)}\n`
51
+ : 'Task pin removed; autoResume stays unchanged.\n');
52
+ return 0;
53
+ }
54
+ if (options.action === 'show') {
55
+ const pin = await readTaskPin(root);
56
+ const report = {
57
+ ok: true,
58
+ action: 'show',
59
+ pin,
60
+ autoResume: await readAutoResume(root),
61
+ };
62
+ output.stdout(options.json
63
+ ? `${JSON.stringify(report)}\n`
64
+ : pin === null
65
+ ? `No task pin; autoResume ${report.autoResume ? 'on' : 'off'}.\n`
66
+ : `Pinned task ${pin.taskId} at ${pin.pinnedAt}; autoResume ${report.autoResume ? 'on' : 'off'}.\n`);
67
+ return 0;
68
+ }
69
+ if (options.json) {
70
+ output.stdout(`${JSON.stringify({ commands: ['pin', 'unpin', 'show'], usage: taskUsage })}\n`);
71
+ }
72
+ else {
73
+ output.stdout(taskUsage);
74
+ }
75
+ return 0;
76
+ }
package/dist/cli/run.js CHANGED
@@ -2,6 +2,8 @@ import { parseArgs } from 'node:util';
2
2
  import { getPackageInfo } from '../package-info.js';
3
3
  import { initUsage, runInitCommand } from './commands/init.js';
4
4
  import { onboardUsage, runOnboardCommand } from './commands/onboard.js';
5
+ import { mcpUsage, runMcpCommand } from './commands/mcp.js';
6
+ import { taskUsage, runTaskCommand } from './commands/task.js';
5
7
  import { doctorUsage, runDoctorCommand } from './commands/doctor.js';
6
8
  import { docsUsage, runDocsCommand } from './commands/docs.js';
7
9
  import { checkpointUsage, resumeUsage, runCheckpointCommand, runResumeCommand, } from './commands/continuity.js';
@@ -26,6 +28,7 @@ function parseOptions(args) {
26
28
  'apply-host': { type: 'boolean' },
27
29
  'dry-run': { type: 'boolean' },
28
30
  'print-handoff': { type: 'boolean' },
31
+ 'auto-resume': { type: 'boolean' },
29
32
  from: { type: 'string' },
30
33
  tarball: { type: 'string' },
31
34
  out: { type: 'string' },
@@ -79,6 +82,25 @@ function parseOptions(args) {
79
82
  throw new Error('Use either --help or --version, not both.');
80
83
  }
81
84
  const command = positionals[0] ?? null;
85
+ if (command === 'task') {
86
+ if (positionals.length > 3) {
87
+ throw new Error('task accepts one action and an optional task id.');
88
+ }
89
+ return {
90
+ values,
91
+ command,
92
+ docsAction: null,
93
+ docsArgument: null,
94
+ codeAction: null,
95
+ hostAction: null,
96
+ hostSubAction: null,
97
+ collaborationAction: null,
98
+ resumeTaskId: null,
99
+ runtimeAction: null,
100
+ taskAction: positionals[1] ?? null,
101
+ taskArgument: positionals[2] ?? null,
102
+ };
103
+ }
82
104
  if (command === 'docs') {
83
105
  if (positionals.length > 3) {
84
106
  throw new Error('Unknown command; docs accepts one action and one argument.');
@@ -90,9 +112,12 @@ function parseOptions(args) {
90
112
  docsAction: positionals[1] ?? null,
91
113
  docsArgument: positionals[2] ?? null,
92
114
  hostAction: null,
115
+ hostSubAction: null,
93
116
  collaborationAction: null,
94
117
  resumeTaskId: null,
95
118
  runtimeAction: null,
119
+ taskAction: null,
120
+ taskArgument: null,
96
121
  };
97
122
  }
98
123
  if (command === 'resume') {
@@ -106,9 +131,12 @@ function parseOptions(args) {
106
131
  docsArgument: null,
107
132
  codeAction: null,
108
133
  hostAction: null,
134
+ hostSubAction: null,
109
135
  collaborationAction: null,
110
136
  resumeTaskId: positionals[1] ?? null,
111
137
  runtimeAction: null,
138
+ taskAction: null,
139
+ taskArgument: null,
112
140
  };
113
141
  }
114
142
  if (command === 'code') {
@@ -122,14 +150,17 @@ function parseOptions(args) {
122
150
  docsArgument: null,
123
151
  codeAction: positionals[1] ?? null,
124
152
  hostAction: null,
153
+ hostSubAction: null,
125
154
  collaborationAction: null,
126
155
  resumeTaskId: null,
127
156
  runtimeAction: null,
157
+ taskAction: null,
158
+ taskArgument: null,
128
159
  };
129
160
  }
130
161
  if (command === 'host') {
131
- if (positionals.length > 2) {
132
- throw new Error('host accepts one action: detect, describe, plan, install, or verify.');
162
+ if (positionals.length > 3) {
163
+ throw new Error('host accepts one action and an optional mcp sub-action.');
133
164
  }
134
165
  return {
135
166
  values,
@@ -138,9 +169,12 @@ function parseOptions(args) {
138
169
  docsArgument: null,
139
170
  codeAction: null,
140
171
  hostAction: positionals[1] ?? null,
172
+ hostSubAction: positionals[2] ?? null,
141
173
  collaborationAction: null,
142
174
  resumeTaskId: null,
143
175
  runtimeAction: null,
176
+ taskAction: null,
177
+ taskArgument: null,
144
178
  };
145
179
  }
146
180
  if (command === 'runtime') {
@@ -154,9 +188,12 @@ function parseOptions(args) {
154
188
  docsArgument: null,
155
189
  codeAction: null,
156
190
  hostAction: null,
191
+ hostSubAction: null,
157
192
  collaborationAction: null,
158
193
  resumeTaskId: null,
159
194
  runtimeAction: positionals[1] ?? null,
195
+ taskAction: null,
196
+ taskArgument: null,
160
197
  };
161
198
  }
162
199
  if (command === 'collab') {
@@ -170,9 +207,12 @@ function parseOptions(args) {
170
207
  docsArgument: null,
171
208
  codeAction: null,
172
209
  hostAction: null,
210
+ hostSubAction: null,
173
211
  collaborationAction: positionals[1] ?? null,
174
212
  resumeTaskId: null,
175
213
  runtimeAction: null,
214
+ taskAction: null,
215
+ taskArgument: null,
176
216
  };
177
217
  }
178
218
  if (positionals.length > 1) {
@@ -221,6 +261,8 @@ function hasContinuityOptions(values, includeSession = true) {
221
261
  (includeSession && values.session !== undefined));
222
262
  }
223
263
  const globalUsage = 'pappra [--help | --version] [--json]\n' +
264
+ 'pappra task <pin|unpin|show> [taskId] [--auto-resume] [--json]\n' +
265
+ 'pappra mcp [--root <dir>] [--help]\n' +
224
266
  'pappra onboard [--root <dir>] [--config <file>] [--hosts <list>] [--yes]\n' +
225
267
  ' [--apply-host] [--dry-run] [--print-handoff] [--json]\n' +
226
268
  'pappra init [--config <file> | --yes] [--apply] [--json] [--print-choices]\n' +
@@ -243,7 +285,7 @@ export async function runCli(args, output) {
243
285
  failure(output, jsonRequested, 'invalid_arguments', error);
244
286
  return 2;
245
287
  }
246
- const { values, command, docsAction, docsArgument, codeAction, hostAction, collaborationAction, resumeTaskId, runtimeAction, } = parsed;
288
+ const { values, command, docsAction, docsArgument, codeAction, hostAction, collaborationAction, resumeTaskId, runtimeAction, hostSubAction, taskAction, taskArgument, } = parsed;
247
289
  const json = values.json === true;
248
290
  const applyRequested = values.apply === true;
249
291
  if (command !== 'init' &&
@@ -258,6 +300,10 @@ export async function runCli(args, output) {
258
300
  failure(output, json, 'invalid_arguments', '--yes is only valid for init and onboard.');
259
301
  return 2;
260
302
  }
303
+ if (command !== 'task' && values['auto-resume'] === true) {
304
+ failure(output, json, 'invalid_arguments', '--auto-resume is only valid for task pin.');
305
+ return 2;
306
+ }
261
307
  if (command !== 'onboard' &&
262
308
  (values.hosts !== undefined ||
263
309
  values['apply-host'] === true ||
@@ -297,6 +343,7 @@ export async function runCli(args, output) {
297
343
  return 2;
298
344
  }
299
345
  if (command !== 'runtime' &&
346
+ command !== 'host' &&
300
347
  (values.from !== undefined ||
301
348
  values.tarball !== undefined ||
302
349
  values.bind === true ||
@@ -416,6 +463,85 @@ export async function runCli(args, output) {
416
463
  json,
417
464
  }, output);
418
465
  }
466
+ if (command === 'task') {
467
+ const action = taskAction === 'pin' || taskAction === 'unpin' || taskAction === 'show'
468
+ ? taskAction
469
+ : null;
470
+ if (values.config !== undefined ||
471
+ values.apply === true ||
472
+ values.yes === true ||
473
+ values['install-runtime'] === true ||
474
+ values['runtime-tarball'] !== undefined ||
475
+ values['runtime-version'] !== undefined ||
476
+ values.host !== undefined ||
477
+ values.repo !== undefined ||
478
+ values.kind !== undefined ||
479
+ values.query !== undefined ||
480
+ values.root !== undefined ||
481
+ hasContinuityOptions(values)) {
482
+ failure(output, json, 'invalid_arguments', 'task accepts pin|unpin|show, an optional task id, --auto-resume, --help, and --json.');
483
+ return 2;
484
+ }
485
+ if (values.help) {
486
+ if (json) {
487
+ output.stdout(`${JSON.stringify({ ...info, commands: ['task'], usage: taskUsage })}\n`);
488
+ }
489
+ else {
490
+ output.stdout(taskUsage);
491
+ }
492
+ return 0;
493
+ }
494
+ if (values.version) {
495
+ failure(output, json, 'invalid_arguments', '--version cannot be combined with task.');
496
+ return 2;
497
+ }
498
+ return runTaskCommand({
499
+ cwd: process.cwd(),
500
+ action,
501
+ taskId: taskArgument ?? null,
502
+ autoResume: values['auto-resume'] === true,
503
+ json,
504
+ }, output);
505
+ }
506
+ if (command === 'mcp') {
507
+ if (values.config !== undefined ||
508
+ values.apply === true ||
509
+ values.yes === true ||
510
+ values['install-runtime'] === true ||
511
+ values['runtime-tarball'] !== undefined ||
512
+ values['runtime-version'] !== undefined ||
513
+ values['include-private'] === true ||
514
+ values['include-archived'] === true ||
515
+ values['apply-host'] === true ||
516
+ values['dry-run'] === true ||
517
+ values['print-handoff'] === true ||
518
+ values.hosts !== undefined ||
519
+ values.repo !== undefined ||
520
+ values.kind !== undefined ||
521
+ values.query !== undefined ||
522
+ values.limit !== undefined ||
523
+ values['max-bytes'] !== undefined ||
524
+ values.rebuild === true ||
525
+ values.host !== undefined ||
526
+ hasContinuityOptions(values)) {
527
+ failure(output, json, 'invalid_arguments', 'mcp accepts only --root, --help, and --json.');
528
+ return 2;
529
+ }
530
+ if (values.help) {
531
+ if (json) {
532
+ output.stdout(`${JSON.stringify({ ...info, commands: ['mcp'], usage: mcpUsage })}\n`);
533
+ }
534
+ else {
535
+ output.stdout(mcpUsage);
536
+ }
537
+ return 0;
538
+ }
539
+ if (values.version) {
540
+ failure(output, json, 'invalid_arguments', '--version cannot be combined with mcp.');
541
+ return 2;
542
+ }
543
+ return runMcpCommand({ cwd: process.cwd(), root: values.root ?? null, json }, output);
544
+ }
419
545
  if (command === 'onboard') {
420
546
  if (values.apply === true ||
421
547
  values['install-runtime'] === true ||
@@ -723,6 +849,8 @@ export async function runCli(args, output) {
723
849
  'install',
724
850
  'verify',
725
851
  'bundle',
852
+ 'mcp',
853
+ 'probe',
726
854
  ];
727
855
  const action = allowedActions.find((item) => item === hostAction) ?? null;
728
856
  const parsedHost = hostNameSchema.safeParse(values.host);
@@ -742,7 +870,7 @@ export async function runCli(args, output) {
742
870
  values['max-bytes'] !== undefined ||
743
871
  values.rebuild === true ||
744
872
  hasContinuityOptions(values) ||
745
- (values.apply === true && action !== 'install') ||
873
+ (values.apply === true && action !== 'install' && action !== 'mcp') ||
746
874
  (values.mode !== undefined && action !== 'install') ||
747
875
  (values.marketplace === true && action !== 'bundle') ||
748
876
  (values.scope !== undefined &&
@@ -772,6 +900,8 @@ export async function runCli(args, output) {
772
900
  hostRoot: values['host-root'] ?? null,
773
901
  hostVersion: values['host-version'] ?? null,
774
902
  apply: values.apply === true,
903
+ force: values.force === true,
904
+ subAction: hostSubAction ?? null,
775
905
  out: values.out ?? null,
776
906
  mode: values.mode === 'project-settings' || values.mode === 'plugin'
777
907
  ? values.mode
@@ -10,6 +10,7 @@ import { resolveWorkspaceRoot } from '../../workspace/resolve.js';
10
10
  import { inspectMachineConfig, loadWorkspaceConfig, } from '../../workspace/config.js';
11
11
  import { CheckpointService } from '../../continuity/service.js';
12
12
  import { ensureStatePathBoundary } from '../../install/ownership.js';
13
+ import { readAutoResume, readTaskPin } from '../../app/task-pin.js';
13
14
  import { nativeEventFor } from './support.js';
14
15
  const DEFAULT_MAX_INPUT_BYTES = 1024 * 1024;
15
16
  const DEFAULT_DEADLINE_MS = 1000;
@@ -489,7 +490,9 @@ export async function runHostHook(input, options = {}) {
489
490
  dev: directoryAfter.stats.dev,
490
491
  ino: directoryAfter.stats.ino,
491
492
  };
492
- const context = await boundedAdvisory(root, value.event, value.taskId, workspaceRead.value.name);
493
+ const pinnedTask = await readTaskPin(root);
494
+ const autoResume = pinnedTask !== null && (await readAutoResume(root));
495
+ const context = await boundedAdvisory(root, value.event, value.taskId ?? (autoResume ? pinnedTask.taskId : null), workspaceRead.value.name);
493
496
  if (clock() - started > deadline) {
494
497
  return result({
495
498
  ok: false,