@phyzess/pappra 0.1.0-beta.11 → 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.
Files changed (37) hide show
  1. package/README.md +8 -2
  2. package/assets/integrations/skills/pappra/references/operations.md +5 -0
  3. package/dist/app/collab-from.d.ts +35 -0
  4. package/dist/app/collab-from.js +84 -0
  5. package/dist/app/host-mcp.d.ts +25 -0
  6. package/dist/app/host-mcp.js +227 -0
  7. package/dist/app/host-probe.d.ts +16 -0
  8. package/dist/app/host-probe.js +125 -0
  9. package/dist/app/init.d.ts +15 -0
  10. package/dist/app/init.js +82 -3
  11. package/dist/app/onboard.d.ts +60 -0
  12. package/dist/app/onboard.js +259 -0
  13. package/dist/app/task-pin.d.ts +23 -0
  14. package/dist/app/task-pin.js +113 -0
  15. package/dist/cli/commands/docs.js +12 -76
  16. package/dist/cli/commands/host.d.ts +3 -1
  17. package/dist/cli/commands/host.js +55 -1
  18. package/dist/cli/commands/init.d.ts +0 -8
  19. package/dist/cli/commands/init.js +5 -126
  20. package/dist/cli/commands/mcp.d.ts +8 -0
  21. package/dist/cli/commands/mcp.js +13 -0
  22. package/dist/cli/commands/onboard.d.ts +14 -0
  23. package/dist/cli/commands/onboard.js +225 -0
  24. package/dist/cli/commands/task.d.ts +10 -0
  25. package/dist/cli/commands/task.js +76 -0
  26. package/dist/cli/run.js +206 -7
  27. package/dist/hosts/integration/hook.js +4 -1
  28. package/dist/hosts/integration/pappra-extension-runtime.mjs +50 -8
  29. package/dist/hosts/integration/pappra-hook.mjs +50 -8
  30. package/dist/knowledge/scan.d.ts +30 -0
  31. package/dist/knowledge/scan.js +91 -0
  32. package/dist/mcp/server.d.ts +11 -0
  33. package/dist/mcp/server.js +518 -0
  34. package/dist/schemas/workspace.d.ts +6 -0
  35. package/dist/schemas/workspace.js +4 -0
  36. package/package.json +2 -1
  37. package/skills/pappra/references/operations.md +5 -0
package/dist/cli/run.js CHANGED
@@ -1,6 +1,9 @@
1
1
  import { parseArgs } from 'node:util';
2
2
  import { getPackageInfo } from '../package-info.js';
3
3
  import { initUsage, runInitCommand } from './commands/init.js';
4
+ import { onboardUsage, runOnboardCommand } from './commands/onboard.js';
5
+ import { mcpUsage, runMcpCommand } from './commands/mcp.js';
6
+ import { taskUsage, runTaskCommand } from './commands/task.js';
4
7
  import { doctorUsage, runDoctorCommand } from './commands/doctor.js';
5
8
  import { docsUsage, runDocsCommand } from './commands/docs.js';
6
9
  import { checkpointUsage, resumeUsage, runCheckpointCommand, runResumeCommand, } from './commands/continuity.js';
@@ -21,6 +24,11 @@ function parseOptions(args) {
21
24
  yes: { type: 'boolean', short: 'y' },
22
25
  'no-runtime': { type: 'boolean' },
23
26
  reconfigure: { type: 'boolean' },
27
+ hosts: { type: 'string' },
28
+ 'apply-host': { type: 'boolean' },
29
+ 'dry-run': { type: 'boolean' },
30
+ 'print-handoff': { type: 'boolean' },
31
+ 'auto-resume': { type: 'boolean' },
24
32
  from: { type: 'string' },
25
33
  tarball: { type: 'string' },
26
34
  out: { type: 'string' },
@@ -74,6 +82,25 @@ function parseOptions(args) {
74
82
  throw new Error('Use either --help or --version, not both.');
75
83
  }
76
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
+ }
77
104
  if (command === 'docs') {
78
105
  if (positionals.length > 3) {
79
106
  throw new Error('Unknown command; docs accepts one action and one argument.');
@@ -85,9 +112,12 @@ function parseOptions(args) {
85
112
  docsAction: positionals[1] ?? null,
86
113
  docsArgument: positionals[2] ?? null,
87
114
  hostAction: null,
115
+ hostSubAction: null,
88
116
  collaborationAction: null,
89
117
  resumeTaskId: null,
90
118
  runtimeAction: null,
119
+ taskAction: null,
120
+ taskArgument: null,
91
121
  };
92
122
  }
93
123
  if (command === 'resume') {
@@ -101,9 +131,12 @@ function parseOptions(args) {
101
131
  docsArgument: null,
102
132
  codeAction: null,
103
133
  hostAction: null,
134
+ hostSubAction: null,
104
135
  collaborationAction: null,
105
136
  resumeTaskId: positionals[1] ?? null,
106
137
  runtimeAction: null,
138
+ taskAction: null,
139
+ taskArgument: null,
107
140
  };
108
141
  }
109
142
  if (command === 'code') {
@@ -117,14 +150,17 @@ function parseOptions(args) {
117
150
  docsArgument: null,
118
151
  codeAction: positionals[1] ?? null,
119
152
  hostAction: null,
153
+ hostSubAction: null,
120
154
  collaborationAction: null,
121
155
  resumeTaskId: null,
122
156
  runtimeAction: null,
157
+ taskAction: null,
158
+ taskArgument: null,
123
159
  };
124
160
  }
125
161
  if (command === 'host') {
126
- if (positionals.length > 2) {
127
- 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.');
128
164
  }
129
165
  return {
130
166
  values,
@@ -133,9 +169,12 @@ function parseOptions(args) {
133
169
  docsArgument: null,
134
170
  codeAction: null,
135
171
  hostAction: positionals[1] ?? null,
172
+ hostSubAction: positionals[2] ?? null,
136
173
  collaborationAction: null,
137
174
  resumeTaskId: null,
138
175
  runtimeAction: null,
176
+ taskAction: null,
177
+ taskArgument: null,
139
178
  };
140
179
  }
141
180
  if (command === 'runtime') {
@@ -149,9 +188,12 @@ function parseOptions(args) {
149
188
  docsArgument: null,
150
189
  codeAction: null,
151
190
  hostAction: null,
191
+ hostSubAction: null,
152
192
  collaborationAction: null,
153
193
  resumeTaskId: null,
154
194
  runtimeAction: positionals[1] ?? null,
195
+ taskAction: null,
196
+ taskArgument: null,
155
197
  };
156
198
  }
157
199
  if (command === 'collab') {
@@ -165,9 +207,12 @@ function parseOptions(args) {
165
207
  docsArgument: null,
166
208
  codeAction: null,
167
209
  hostAction: null,
210
+ hostSubAction: null,
168
211
  collaborationAction: positionals[1] ?? null,
169
212
  resumeTaskId: null,
170
213
  runtimeAction: null,
214
+ taskAction: null,
215
+ taskArgument: null,
171
216
  };
172
217
  }
173
218
  if (positionals.length > 1) {
@@ -216,6 +261,10 @@ function hasContinuityOptions(values, includeSession = true) {
216
261
  (includeSession && values.session !== undefined));
217
262
  }
218
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' +
266
+ 'pappra onboard [--root <dir>] [--config <file>] [--hosts <list>] [--yes]\n' +
267
+ ' [--apply-host] [--dry-run] [--print-handoff] [--json]\n' +
219
268
  'pappra init [--config <file> | --yes] [--apply] [--json] [--print-choices]\n' +
220
269
  ' [--install-runtime (--runtime-tarball <file> | --runtime-version <version>)]\n' +
221
270
  'pappra doctor [--root <path>] [--json]\n' +
@@ -236,16 +285,31 @@ export async function runCli(args, output) {
236
285
  failure(output, jsonRequested, 'invalid_arguments', error);
237
286
  return 2;
238
287
  }
239
- 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;
240
289
  const json = values.json === true;
241
290
  const applyRequested = values.apply === true;
242
291
  if (command !== 'init' &&
243
- (values.yes === true ||
244
- values['no-interactive'] === true ||
292
+ (values['no-interactive'] === true ||
245
293
  values['print-choices'] === true ||
246
294
  values['no-runtime'] === true ||
247
295
  values.reconfigure === true)) {
248
- failure(output, json, 'invalid_arguments', '--yes, --no-interactive, --print-choices, --no-runtime and --reconfigure are only valid for init.');
296
+ failure(output, json, 'invalid_arguments', '--no-interactive, --print-choices, --no-runtime and --reconfigure are only valid for init.');
297
+ return 2;
298
+ }
299
+ if (command !== 'init' && command !== 'onboard' && values.yes === true) {
300
+ failure(output, json, 'invalid_arguments', '--yes is only valid for init and onboard.');
301
+ return 2;
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
+ }
307
+ if (command !== 'onboard' &&
308
+ (values.hosts !== undefined ||
309
+ values['apply-host'] === true ||
310
+ values['dry-run'] === true ||
311
+ values['print-handoff'] === true)) {
312
+ failure(output, json, 'invalid_arguments', '--hosts, --apply-host, --dry-run and --print-handoff are only valid for onboard.');
249
313
  return 2;
250
314
  }
251
315
  if (command !== 'host' && values.out !== undefined) {
@@ -279,6 +343,7 @@ export async function runCli(args, output) {
279
343
  return 2;
280
344
  }
281
345
  if (command !== 'runtime' &&
346
+ command !== 'host' &&
282
347
  (values.from !== undefined ||
283
348
  values.tarball !== undefined ||
284
349
  values.bind === true ||
@@ -398,6 +463,136 @@ export async function runCli(args, output) {
398
463
  json,
399
464
  }, output);
400
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
+ }
545
+ if (command === 'onboard') {
546
+ if (values.apply === true ||
547
+ values['install-runtime'] === true ||
548
+ values['runtime-tarball'] !== undefined ||
549
+ values['runtime-version'] !== undefined ||
550
+ values['host-root'] !== undefined ||
551
+ values['host-version'] !== undefined ||
552
+ values.mode !== undefined ||
553
+ values.from !== undefined ||
554
+ values.tarball !== undefined ||
555
+ values.bind === true ||
556
+ values.force === true ||
557
+ values.marketplace === true ||
558
+ values.scope !== undefined ||
559
+ values.repo !== undefined ||
560
+ values.kind !== undefined ||
561
+ values.query !== undefined ||
562
+ values.limit !== undefined ||
563
+ values['max-bytes'] !== undefined ||
564
+ values.rebuild === true ||
565
+ values['include-private'] === true ||
566
+ values['include-archived'] === true ||
567
+ hasContinuityOptions(values)) {
568
+ failure(output, json, 'invalid_arguments', 'onboard accepts --root, --config, --hosts, --yes, --apply-host, --dry-run, --print-handoff, --help, and --json.');
569
+ return 2;
570
+ }
571
+ if (values.help) {
572
+ if (json) {
573
+ output.stdout(`${JSON.stringify({ ...info, commands: ['onboard'], usage: onboardUsage })}\n`);
574
+ }
575
+ else {
576
+ output.stdout(onboardUsage);
577
+ }
578
+ return 0;
579
+ }
580
+ if (values.version) {
581
+ failure(output, json, 'invalid_arguments', '--version cannot be combined with onboard.');
582
+ return 2;
583
+ }
584
+ return runOnboardCommand({
585
+ cwd: process.cwd(),
586
+ root: values.root ?? null,
587
+ configPath: values.config ?? null,
588
+ hosts: values.hosts ?? null,
589
+ apply: values.yes === true,
590
+ applyHosts: values['apply-host'] === true,
591
+ dryRun: values['dry-run'] === true,
592
+ printHandoff: values['print-handoff'] === true,
593
+ json,
594
+ }, output);
595
+ }
401
596
  if (command === 'init') {
402
597
  if (values.root !== undefined ||
403
598
  values['include-private'] === true ||
@@ -654,6 +849,8 @@ export async function runCli(args, output) {
654
849
  'install',
655
850
  'verify',
656
851
  'bundle',
852
+ 'mcp',
853
+ 'probe',
657
854
  ];
658
855
  const action = allowedActions.find((item) => item === hostAction) ?? null;
659
856
  const parsedHost = hostNameSchema.safeParse(values.host);
@@ -673,7 +870,7 @@ export async function runCli(args, output) {
673
870
  values['max-bytes'] !== undefined ||
674
871
  values.rebuild === true ||
675
872
  hasContinuityOptions(values) ||
676
- (values.apply === true && action !== 'install') ||
873
+ (values.apply === true && action !== 'install' && action !== 'mcp') ||
677
874
  (values.mode !== undefined && action !== 'install') ||
678
875
  (values.marketplace === true && action !== 'bundle') ||
679
876
  (values.scope !== undefined &&
@@ -703,6 +900,8 @@ export async function runCli(args, output) {
703
900
  hostRoot: values['host-root'] ?? null,
704
901
  hostVersion: values['host-version'] ?? null,
705
902
  apply: values.apply === true,
903
+ force: values.force === true,
904
+ subAction: hostSubAction ?? null,
706
905
  out: values.out ?? null,
707
906
  mode: values.mode === 'project-settings' || values.mode === 'plugin'
708
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,
@@ -6,8 +6,8 @@ var __export = (target, all) => {
6
6
 
7
7
  // dist/hosts/integration/hook.js
8
8
  import { randomUUID as randomUUID3 } from "node:crypto";
9
- import { link as link2, mkdir as mkdir3, open as open5, rm as rm2 } from "node:fs/promises";
10
- import { isAbsolute as isAbsolute4, join as join8, relative as relative3, resolve as resolve6, sep as sep3 } from "node:path";
9
+ import { link as link2, mkdir as mkdir4, open as open5, rm as rm2 } from "node:fs/promises";
10
+ import { isAbsolute as isAbsolute4, join as join9, relative as relative3, resolve as resolve6, sep as sep3 } from "node:path";
11
11
 
12
12
  // dist/schemas/diagnostics.js
13
13
  function createDiagnostic(input2) {
@@ -20662,6 +20662,9 @@ var workspaceValidationSchema = external_exports.strictObject({
20662
20662
  executable: executableSchema,
20663
20663
  args: external_exports.array(argumentSchema).max(256)
20664
20664
  });
20665
+ var workspaceContinuitySchema = external_exports.strictObject({
20666
+ autoResume: external_exports.boolean()
20667
+ });
20665
20668
  var workspaceConfigSchema = external_exports.strictObject({
20666
20669
  schema: external_exports.literal(WORKSPACE_SCHEMA),
20667
20670
  workspaceId: external_exports.uuid(),
@@ -20672,7 +20675,8 @@ var workspaceConfigSchema = external_exports.strictObject({
20672
20675
  code: workspaceCodeSchema,
20673
20676
  validation: external_exports.array(workspaceValidationSchema).max(128),
20674
20677
  excludes: external_exports.array(portablePathSchema).max(128),
20675
- extensions: external_exports.record(external_exports.string().min(1).max(128), external_exports.unknown())
20678
+ extensions: external_exports.record(external_exports.string().min(1).max(128), external_exports.unknown()),
20679
+ continuity: workspaceContinuitySchema.optional()
20676
20680
  });
20677
20681
  function pathDiagnostic(value, source, fieldPointer, label) {
20678
20682
  const hint = 'Use a workspace-relative path such as "docs" or "repos/app".';
@@ -23928,6 +23932,42 @@ async function ensureStatePathBoundary(root, path) {
23928
23932
  }
23929
23933
  }
23930
23934
 
23935
+ // dist/app/task-pin.js
23936
+ import { mkdir as mkdir3, readFile, rename as rename2, writeFile } from "node:fs/promises";
23937
+ import { dirname as dirname5, join as join8 } from "node:path";
23938
+ var TASK_PIN_SCHEMA = "pappra/task-pin/1";
23939
+ var TASK_ID2 = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
23940
+ function pinPath(root) {
23941
+ return join8(root, ".pappra", "local", "current-task");
23942
+ }
23943
+ async function readTaskPin(root) {
23944
+ const read = await readTextFileBounded(pinPath(root), 64 * 1024);
23945
+ if (!read.ok)
23946
+ return null;
23947
+ try {
23948
+ const value = JSON.parse(read.value.text);
23949
+ if (typeof value !== "object" || value === null || !("schema" in value) || value.schema !== TASK_PIN_SCHEMA || !("taskId" in value) || typeof value.taskId !== "string" || !TASK_ID2.test(value.taskId)) {
23950
+ return null;
23951
+ }
23952
+ return value;
23953
+ } catch {
23954
+ return null;
23955
+ }
23956
+ }
23957
+ async function readAutoResume(root) {
23958
+ const read = await readFile(join8(root, ".pappra", "workspace.json"), "utf8");
23959
+ try {
23960
+ const raw = JSON.parse(read);
23961
+ if (typeof raw !== "object" || raw === null || !("continuity" in raw)) {
23962
+ return false;
23963
+ }
23964
+ const continuity = raw.continuity;
23965
+ return typeof continuity === "object" && continuity !== null && "autoResume" in continuity && continuity.autoResume === true;
23966
+ } catch {
23967
+ return false;
23968
+ }
23969
+ }
23970
+
23931
23971
  // dist/hosts/integration/support.js
23932
23972
  var HOST_SUPPORT = {
23933
23973
  pi: {
@@ -24015,7 +24055,7 @@ async function privateSegmentsSafe(root, directory) {
24015
24055
  }
24016
24056
  let current = root;
24017
24057
  for (const segment of rel.split(sep3)) {
24018
- current = join8(current, segment);
24058
+ current = join9(current, segment);
24019
24059
  const observed = await observePath(current);
24020
24060
  if (observed.kind === "missing")
24021
24061
  return true;
@@ -24256,7 +24296,7 @@ async function runHostHook(input2, options = {}) {
24256
24296
  });
24257
24297
  }
24258
24298
  const eventKey = value.eventId === null ? null : sha256Digest([value.host, value.event, value.sessionId ?? "", value.eventId].join("\0"));
24259
- const directory = join8(root, ".pappra", "local", "host-events", value.host);
24299
+ const directory = join9(root, ".pappra", "local", "host-events", value.host);
24260
24300
  if (!await privateSegmentsSafe(root, directory)) {
24261
24301
  return result2({
24262
24302
  ok: false,
@@ -24301,7 +24341,7 @@ async function runHostHook(input2, options = {}) {
24301
24341
  ]
24302
24342
  });
24303
24343
  }
24304
- await mkdir3(directory, { recursive: true, mode: 448 });
24344
+ await mkdir4(directory, { recursive: true, mode: 448 });
24305
24345
  const directoryAfter = await observePath(directory);
24306
24346
  const realDirectory = await resolveRealpath(directory);
24307
24347
  const privateAfter = await privateSegmentsSafe(root, directory);
@@ -24326,7 +24366,9 @@ async function runHostHook(input2, options = {}) {
24326
24366
  dev: directoryAfter.stats.dev,
24327
24367
  ino: directoryAfter.stats.ino
24328
24368
  };
24329
- const context = await boundedAdvisory(root, value.event, value.taskId, workspaceRead.value.name);
24369
+ const pinnedTask = await readTaskPin(root);
24370
+ const autoResume = pinnedTask !== null && await readAutoResume(root);
24371
+ const context = await boundedAdvisory(root, value.event, value.taskId ?? (autoResume ? pinnedTask.taskId : null), workspaceRead.value.name);
24330
24372
  if (clock() - started > deadline) {
24331
24373
  return result2({
24332
24374
  ok: false,
@@ -24342,7 +24384,7 @@ async function runHostHook(input2, options = {}) {
24342
24384
  });
24343
24385
  }
24344
24386
  const fileName = eventKey === null ? `${sha256Digest(`${value.observedAt}\0${randomUUID3()}`).slice(7)}.json` : `${eventKey.slice(7)}.json`;
24345
- const path = join8(directory, fileName);
24387
+ const path = join9(directory, fileName);
24346
24388
  const record2 = Buffer.from(`${JSON.stringify({
24347
24389
  schema: "pappra/host-event/1",
24348
24390
  host: value.host,
@@ -6,8 +6,8 @@ var __export = (target, all) => {
6
6
 
7
7
  // dist/hosts/integration/hook.js
8
8
  import { randomUUID as randomUUID3 } from "node:crypto";
9
- import { link as link2, mkdir as mkdir3, open as open5, rm as rm2 } from "node:fs/promises";
10
- import { isAbsolute as isAbsolute4, join as join8, relative as relative3, resolve as resolve6, sep as sep3 } from "node:path";
9
+ import { link as link2, mkdir as mkdir4, open as open5, rm as rm2 } from "node:fs/promises";
10
+ import { isAbsolute as isAbsolute4, join as join9, relative as relative3, resolve as resolve6, sep as sep3 } from "node:path";
11
11
 
12
12
  // dist/schemas/diagnostics.js
13
13
  function createDiagnostic(input2) {
@@ -20667,6 +20667,9 @@ var workspaceValidationSchema = external_exports.strictObject({
20667
20667
  executable: executableSchema,
20668
20668
  args: external_exports.array(argumentSchema).max(256)
20669
20669
  });
20670
+ var workspaceContinuitySchema = external_exports.strictObject({
20671
+ autoResume: external_exports.boolean()
20672
+ });
20670
20673
  var workspaceConfigSchema = external_exports.strictObject({
20671
20674
  schema: external_exports.literal(WORKSPACE_SCHEMA),
20672
20675
  workspaceId: external_exports.uuid(),
@@ -20677,7 +20680,8 @@ var workspaceConfigSchema = external_exports.strictObject({
20677
20680
  code: workspaceCodeSchema,
20678
20681
  validation: external_exports.array(workspaceValidationSchema).max(128),
20679
20682
  excludes: external_exports.array(portablePathSchema).max(128),
20680
- extensions: external_exports.record(external_exports.string().min(1).max(128), external_exports.unknown())
20683
+ extensions: external_exports.record(external_exports.string().min(1).max(128), external_exports.unknown()),
20684
+ continuity: workspaceContinuitySchema.optional()
20681
20685
  });
20682
20686
  function pathDiagnostic(value, source, fieldPointer, label) {
20683
20687
  const hint = 'Use a workspace-relative path such as "docs" or "repos/app".';
@@ -23933,6 +23937,42 @@ async function ensureStatePathBoundary(root, path) {
23933
23937
  }
23934
23938
  }
23935
23939
 
23940
+ // dist/app/task-pin.js
23941
+ import { mkdir as mkdir3, readFile, rename as rename2, writeFile } from "node:fs/promises";
23942
+ import { dirname as dirname5, join as join8 } from "node:path";
23943
+ var TASK_PIN_SCHEMA = "pappra/task-pin/1";
23944
+ var TASK_ID2 = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
23945
+ function pinPath(root) {
23946
+ return join8(root, ".pappra", "local", "current-task");
23947
+ }
23948
+ async function readTaskPin(root) {
23949
+ const read = await readTextFileBounded(pinPath(root), 64 * 1024);
23950
+ if (!read.ok)
23951
+ return null;
23952
+ try {
23953
+ const value = JSON.parse(read.value.text);
23954
+ if (typeof value !== "object" || value === null || !("schema" in value) || value.schema !== TASK_PIN_SCHEMA || !("taskId" in value) || typeof value.taskId !== "string" || !TASK_ID2.test(value.taskId)) {
23955
+ return null;
23956
+ }
23957
+ return value;
23958
+ } catch {
23959
+ return null;
23960
+ }
23961
+ }
23962
+ async function readAutoResume(root) {
23963
+ const read = await readFile(join8(root, ".pappra", "workspace.json"), "utf8");
23964
+ try {
23965
+ const raw = JSON.parse(read);
23966
+ if (typeof raw !== "object" || raw === null || !("continuity" in raw)) {
23967
+ return false;
23968
+ }
23969
+ const continuity = raw.continuity;
23970
+ return typeof continuity === "object" && continuity !== null && "autoResume" in continuity && continuity.autoResume === true;
23971
+ } catch {
23972
+ return false;
23973
+ }
23974
+ }
23975
+
23936
23976
  // dist/hosts/integration/support.js
23937
23977
  var HOST_SUPPORT = {
23938
23978
  pi: {
@@ -24038,7 +24078,7 @@ async function privateSegmentsSafe(root, directory) {
24038
24078
  }
24039
24079
  let current = root;
24040
24080
  for (const segment of rel.split(sep3)) {
24041
- current = join8(current, segment);
24081
+ current = join9(current, segment);
24042
24082
  const observed = await observePath(current);
24043
24083
  if (observed.kind === "missing")
24044
24084
  return true;
@@ -24337,7 +24377,7 @@ async function runHostHook(input2, options = {}) {
24337
24377
  });
24338
24378
  }
24339
24379
  const eventKey = value.eventId === null ? null : sha256Digest([value.host, value.event, value.sessionId ?? "", value.eventId].join("\0"));
24340
- const directory = join8(root, ".pappra", "local", "host-events", value.host);
24380
+ const directory = join9(root, ".pappra", "local", "host-events", value.host);
24341
24381
  if (!await privateSegmentsSafe(root, directory)) {
24342
24382
  return result2({
24343
24383
  ok: false,
@@ -24382,7 +24422,7 @@ async function runHostHook(input2, options = {}) {
24382
24422
  ]
24383
24423
  });
24384
24424
  }
24385
- await mkdir3(directory, { recursive: true, mode: 448 });
24425
+ await mkdir4(directory, { recursive: true, mode: 448 });
24386
24426
  const directoryAfter = await observePath(directory);
24387
24427
  const realDirectory = await resolveRealpath(directory);
24388
24428
  const privateAfter = await privateSegmentsSafe(root, directory);
@@ -24407,7 +24447,9 @@ async function runHostHook(input2, options = {}) {
24407
24447
  dev: directoryAfter.stats.dev,
24408
24448
  ino: directoryAfter.stats.ino
24409
24449
  };
24410
- const context = await boundedAdvisory(root, value.event, value.taskId, workspaceRead.value.name);
24450
+ const pinnedTask = await readTaskPin(root);
24451
+ const autoResume = pinnedTask !== null && await readAutoResume(root);
24452
+ const context = await boundedAdvisory(root, value.event, value.taskId ?? (autoResume ? pinnedTask.taskId : null), workspaceRead.value.name);
24411
24453
  if (clock() - started > deadline) {
24412
24454
  return result2({
24413
24455
  ok: false,
@@ -24423,7 +24465,7 @@ async function runHostHook(input2, options = {}) {
24423
24465
  });
24424
24466
  }
24425
24467
  const fileName = eventKey === null ? `${sha256Digest(`${value.observedAt}\0${randomUUID3()}`).slice(7)}.json` : `${eventKey.slice(7)}.json`;
24426
- const path = join8(directory, fileName);
24468
+ const path = join9(directory, fileName);
24427
24469
  const record2 = Buffer.from(`${JSON.stringify({
24428
24470
  schema: "pappra/host-event/1",
24429
24471
  host: value.host,
@@ -0,0 +1,30 @@
1
+ import type { Diagnostic } from '../schemas/diagnostics.js';
2
+ import type { DocumentKind } from '../schemas/document.js';
3
+ export interface DocumentScanCandidate {
4
+ readonly path: string;
5
+ readonly kind: DocumentKind;
6
+ readonly visibility: 'private' | 'shareable';
7
+ }
8
+ export interface DocumentScanResult {
9
+ readonly ok: boolean;
10
+ readonly schema: 'pappra/docs-scan/1';
11
+ readonly mode: 'plan' | 'apply';
12
+ readonly workspaceRoot: string;
13
+ readonly candidates: readonly DocumentScanCandidate[];
14
+ readonly registered: readonly unknown[];
15
+ readonly skipped: number;
16
+ readonly nextActions: readonly string[];
17
+ readonly diagnostics: readonly Diagnostic[];
18
+ }
19
+ export type DocumentScanFailure = {
20
+ readonly ok: false;
21
+ readonly message: string;
22
+ } | DocumentScanResult;
23
+ export declare function classifyScannedDocumentKind(path: string): DocumentKind;
24
+ /**
25
+ * Registers documents discovered under the configured knowledge sources.
26
+ * Without apply this is read-only and reports the candidates.
27
+ */
28
+ export declare function scanDiscoveredDocuments(root: string, options: {
29
+ readonly apply: boolean;
30
+ }): Promise<DocumentScanFailure>;