@phyzess/pappra 0.1.0-beta.10 → 0.1.0-beta.12
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.
- package/README.md +8 -3
- package/assets/integrations/skills/pappra/references/operations.md +3 -0
- package/dist/app/doctor.js +24 -5
- package/dist/app/init.d.ts +15 -0
- package/dist/app/init.js +82 -3
- package/dist/app/onboard.d.ts +60 -0
- package/dist/app/onboard.js +259 -0
- package/dist/cli/commands/init.d.ts +0 -8
- package/dist/cli/commands/init.js +5 -126
- package/dist/cli/commands/onboard.d.ts +14 -0
- package/dist/cli/commands/onboard.js +225 -0
- package/dist/cli/run.js +72 -3
- package/dist/code/service.js +14 -8
- package/dist/hosts/pilot.d.ts +77 -77
- package/package.json +1 -1
- package/skills/pappra/references/operations.md +3 -0
|
@@ -2,12 +2,10 @@ import { basename, resolve } from 'node:path';
|
|
|
2
2
|
import * as prompts from '@clack/prompts';
|
|
3
3
|
import { diagnosticCodes } from '../../schemas/errors.js';
|
|
4
4
|
import { INIT_CHOICES_SCHEMA, parseInitChoices, } from '../../schemas/init.js';
|
|
5
|
-
import {
|
|
6
|
-
import { getPackageInfo
|
|
7
|
-
import { installRuntime
|
|
8
|
-
import {
|
|
9
|
-
import { inspectMachineConfig } from '../../workspace/config.js';
|
|
10
|
-
import { applyInitPlan, inspectInitContext, listKnowledgeCandidates, deriveInitChoices, listRepositoryCandidates, planWorkspaceInit, } from '../../app/init.js';
|
|
5
|
+
import { readTextFileBounded } from '../../infra/fs.js';
|
|
6
|
+
import { getPackageInfo } from '../../package-info.js';
|
|
7
|
+
import { installRuntime } from '../../install/runtime.js';
|
|
8
|
+
import { applyInitPlan, bindAndRecordRuntime, ensureDefaultRuntime, inspectInitContext, listKnowledgeCandidates, deriveInitChoices, listRepositoryCandidates, planWorkspaceInit, } from '../../app/init.js';
|
|
11
9
|
export const clackWizardUi = {
|
|
12
10
|
intro: (message) => prompts.intro(message),
|
|
13
11
|
outro: (message) => prompts.outro(message),
|
|
@@ -250,84 +248,6 @@ function runtimeOptionsFailure(message, nextAction) {
|
|
|
250
248
|
nextAction,
|
|
251
249
|
};
|
|
252
250
|
}
|
|
253
|
-
async function bindAndRecordRuntime(root, installed) {
|
|
254
|
-
if (!installed.ok || installed.manifest === null) {
|
|
255
|
-
return {
|
|
256
|
-
ok: false,
|
|
257
|
-
completion: false,
|
|
258
|
-
runtime: installed,
|
|
259
|
-
diagnostics: installed.diagnostics,
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
const updated = await updateMachineRuntime(root, installed.manifest);
|
|
263
|
-
if (!updated.ok) {
|
|
264
|
-
return {
|
|
265
|
-
ok: false,
|
|
266
|
-
completion: false,
|
|
267
|
-
runtime: installed,
|
|
268
|
-
diagnostics: updated.diagnostics,
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
const followUp = await planWorkspaceInit({ root });
|
|
272
|
-
if (!followUp.ok) {
|
|
273
|
-
return {
|
|
274
|
-
ok: false,
|
|
275
|
-
completion: false,
|
|
276
|
-
runtime: installed,
|
|
277
|
-
diagnostics: followUp.diagnostics,
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
if (followUp.bundle.plan.files.length > 0 ||
|
|
281
|
-
followUp.bundle.plan.externalActions.length > 0) {
|
|
282
|
-
return {
|
|
283
|
-
ok: false,
|
|
284
|
-
completion: false,
|
|
285
|
-
runtime: installed,
|
|
286
|
-
diagnostics: [
|
|
287
|
-
{
|
|
288
|
-
code: diagnosticCodes.initRuntimeSourceRequired,
|
|
289
|
-
severity: 'error',
|
|
290
|
-
message: 'Runtime installation succeeded but init still has pending file or external actions.',
|
|
291
|
-
source: 'init runtime',
|
|
292
|
-
pointer: null,
|
|
293
|
-
hint: 'Run pappra init again to review the remaining plan; initialization is not complete.',
|
|
294
|
-
},
|
|
295
|
-
],
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
return {
|
|
299
|
-
ok: true,
|
|
300
|
-
completion: true,
|
|
301
|
-
runtime: installed,
|
|
302
|
-
diagnostics: [],
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
/**
|
|
306
|
-
* Converges the runtime binding by installing a durable copy of the package that
|
|
307
|
-
* is currently executing. Reusing an already verified copy is cheap: the install
|
|
308
|
-
* machinery short-circuits before packing.
|
|
309
|
-
*/
|
|
310
|
-
async function ensureDefaultRuntime(root, targetVersion) {
|
|
311
|
-
const machine = await inspectMachineConfig(root);
|
|
312
|
-
const binding = machine.value?.runtime ?? null;
|
|
313
|
-
if (binding !== null && binding.version === targetVersion) {
|
|
314
|
-
const executable = await observePath(binding.executable);
|
|
315
|
-
if (executable.kind === 'file') {
|
|
316
|
-
return {
|
|
317
|
-
ok: true,
|
|
318
|
-
completion: true,
|
|
319
|
-
runtime: null,
|
|
320
|
-
diagnostics: [],
|
|
321
|
-
};
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
const installed = await installRuntime({
|
|
325
|
-
version: targetVersion,
|
|
326
|
-
source: { kind: 'self', path: getPackageRoot() },
|
|
327
|
-
packageName: getPackageInfo().name,
|
|
328
|
-
});
|
|
329
|
-
return bindAndRecordRuntime(root, installed);
|
|
330
|
-
}
|
|
331
251
|
async function installAndRecordRuntime(root, targetVersion, options) {
|
|
332
252
|
const tarball = options.runtimeTarball ?? null;
|
|
333
253
|
const requestedVersion = options.runtimeVersion ?? null;
|
|
@@ -400,48 +320,7 @@ async function installAndRecordRuntime(root, targetVersion, options) {
|
|
|
400
320
|
diagnostics: installed.diagnostics,
|
|
401
321
|
};
|
|
402
322
|
}
|
|
403
|
-
|
|
404
|
-
if (!updated.ok) {
|
|
405
|
-
return {
|
|
406
|
-
ok: false,
|
|
407
|
-
completion: false,
|
|
408
|
-
runtime: installed,
|
|
409
|
-
diagnostics: updated.diagnostics,
|
|
410
|
-
};
|
|
411
|
-
}
|
|
412
|
-
const followUp = await planWorkspaceInit({ root });
|
|
413
|
-
if (!followUp.ok) {
|
|
414
|
-
return {
|
|
415
|
-
ok: false,
|
|
416
|
-
completion: false,
|
|
417
|
-
runtime: installed,
|
|
418
|
-
diagnostics: followUp.diagnostics,
|
|
419
|
-
};
|
|
420
|
-
}
|
|
421
|
-
if (followUp.bundle.plan.files.length > 0 ||
|
|
422
|
-
followUp.bundle.plan.externalActions.length > 0) {
|
|
423
|
-
return {
|
|
424
|
-
ok: false,
|
|
425
|
-
completion: false,
|
|
426
|
-
runtime: installed,
|
|
427
|
-
diagnostics: [
|
|
428
|
-
{
|
|
429
|
-
code: diagnosticCodes.initRuntimeSourceRequired,
|
|
430
|
-
severity: 'error',
|
|
431
|
-
message: 'Runtime installation succeeded but init still has pending file or external actions.',
|
|
432
|
-
source: 'init runtime',
|
|
433
|
-
pointer: null,
|
|
434
|
-
hint: 'Run pappra init again to review the remaining plan; initialization is not complete.',
|
|
435
|
-
},
|
|
436
|
-
],
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
|
-
return {
|
|
440
|
-
ok: true,
|
|
441
|
-
completion: true,
|
|
442
|
-
runtime: installed,
|
|
443
|
-
diagnostics: [],
|
|
444
|
-
};
|
|
323
|
+
return bindAndRecordRuntime(root, installed);
|
|
445
324
|
}
|
|
446
325
|
function formatFailure(failure) {
|
|
447
326
|
switch (failure.status) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CliOutput } from '../output.js';
|
|
2
|
+
export declare const onboardUsage: string;
|
|
3
|
+
export interface OnboardCommandOptions {
|
|
4
|
+
readonly cwd: string;
|
|
5
|
+
readonly root: string | null;
|
|
6
|
+
readonly configPath: string | null;
|
|
7
|
+
readonly hosts: string | null;
|
|
8
|
+
readonly apply: boolean;
|
|
9
|
+
readonly applyHosts: boolean;
|
|
10
|
+
readonly dryRun: boolean;
|
|
11
|
+
readonly printHandoff: boolean;
|
|
12
|
+
readonly json: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function runOnboardCommand(options: OnboardCommandOptions, output: CliOutput): Promise<number>;
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { readTextFileBounded } from '../../infra/fs.js';
|
|
2
|
+
import { parseInitChoices } from '../../schemas/init.js';
|
|
3
|
+
import { getPackageInfo } from '../../package-info.js';
|
|
4
|
+
import { applyOnboard, parseHostList, planOnboard, } from '../../app/onboard.js';
|
|
5
|
+
export const onboardUsage = 'pappra onboard [--root <dir>] [--config <file>] [--hosts claude,codex,pi]\n' +
|
|
6
|
+
' [--yes] [--apply-host] [--dry-run] [--print-handoff] [--json]\n\n' +
|
|
7
|
+
'Plans (default) or applies a workspace setup: init files, durable runtime, and with\n' +
|
|
8
|
+
'--apply-host the verified host wiring. Trust prompts are never approved automatically.\n' +
|
|
9
|
+
'--print-handoff lists the files to copy to another machine and the commands to run there.';
|
|
10
|
+
function printDiagnostics(output, diagnostics) {
|
|
11
|
+
for (const entry of diagnostics) {
|
|
12
|
+
output.stderr(` ${entry.severity} ${entry.code}: ${entry.message}\n`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function failureExit(failure) {
|
|
16
|
+
return failure.reason === 'invalid'
|
|
17
|
+
? 2
|
|
18
|
+
: failure.reason === 'unavailable'
|
|
19
|
+
? 3
|
|
20
|
+
: 1;
|
|
21
|
+
}
|
|
22
|
+
function writeFailure(output, options, failure) {
|
|
23
|
+
if (options.json) {
|
|
24
|
+
output.stdout(`${JSON.stringify({
|
|
25
|
+
schema: 'pappra/onboard/1',
|
|
26
|
+
ok: false,
|
|
27
|
+
dryRun: !options.apply,
|
|
28
|
+
reason: failure.reason,
|
|
29
|
+
message: failure.message,
|
|
30
|
+
diagnostics: failure.diagnostics,
|
|
31
|
+
})}\n`);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
output.stderr(`pappra onboard: ${failure.message}\n`);
|
|
35
|
+
printDiagnostics(output, failure.diagnostics);
|
|
36
|
+
}
|
|
37
|
+
return failureExit(failure);
|
|
38
|
+
}
|
|
39
|
+
async function loadChoices(path) {
|
|
40
|
+
const read = await readTextFileBounded(path);
|
|
41
|
+
if (!read.ok) {
|
|
42
|
+
return {
|
|
43
|
+
ok: false,
|
|
44
|
+
failure: {
|
|
45
|
+
ok: false,
|
|
46
|
+
reason: 'unavailable',
|
|
47
|
+
message: read.message,
|
|
48
|
+
diagnostics: [],
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
let raw;
|
|
53
|
+
try {
|
|
54
|
+
raw = JSON.parse(read.value.text);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
return {
|
|
58
|
+
ok: false,
|
|
59
|
+
failure: {
|
|
60
|
+
ok: false,
|
|
61
|
+
reason: 'invalid',
|
|
62
|
+
message: `Invalid onboard config JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
63
|
+
diagnostics: [],
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const parsed = parseInitChoices(raw, path);
|
|
68
|
+
if (parsed.value === null) {
|
|
69
|
+
return {
|
|
70
|
+
ok: false,
|
|
71
|
+
failure: {
|
|
72
|
+
ok: false,
|
|
73
|
+
reason: 'invalid',
|
|
74
|
+
message: parsed.diagnostics[0]?.message ??
|
|
75
|
+
'Onboard config does not match pappra/init-choices/1.',
|
|
76
|
+
diagnostics: parsed.diagnostics,
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return { ok: true, choices: parsed.value };
|
|
81
|
+
}
|
|
82
|
+
function plannedHosts(plan) {
|
|
83
|
+
return plan.hosts.map((host) => ({
|
|
84
|
+
...host,
|
|
85
|
+
action: 'planned',
|
|
86
|
+
appliedFiles: [],
|
|
87
|
+
nextActions: ['Rerun with --apply-host to write this host integration.'],
|
|
88
|
+
diagnostics: [],
|
|
89
|
+
}));
|
|
90
|
+
}
|
|
91
|
+
function planReport(plan) {
|
|
92
|
+
const nextActions = ['Rerun with --yes to apply this plan.'];
|
|
93
|
+
if (plan.choices.knowledge.sources.length > 0) {
|
|
94
|
+
nextActions.push('pappra docs scan --apply');
|
|
95
|
+
}
|
|
96
|
+
if (plan.choices.code.graph !== 'none' &&
|
|
97
|
+
plan.choices.repositories.length > 0) {
|
|
98
|
+
nextActions.push('pappra code prepare --repo <repo> --apply');
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
schema: 'pappra/onboard/1',
|
|
102
|
+
ok: true,
|
|
103
|
+
dryRun: true,
|
|
104
|
+
workspaceRoot: plan.bundle.scope.workspaceRoot,
|
|
105
|
+
files: plan.bundle.plan.files.map((file) => ({
|
|
106
|
+
path: file.path,
|
|
107
|
+
operation: file.operation,
|
|
108
|
+
})),
|
|
109
|
+
runtime: {
|
|
110
|
+
version: plan.bundle.scope.runtimeVersion,
|
|
111
|
+
status: 'pending',
|
|
112
|
+
},
|
|
113
|
+
initComplete: false,
|
|
114
|
+
hosts: plannedHosts(plan),
|
|
115
|
+
manualSteps: plan.hosts.map((host) => `${host.command}: detected ${host.detectedVersion ?? '(missing)'}, pinned ${host.pinnedVersion} (${host.state})`),
|
|
116
|
+
nextActions,
|
|
117
|
+
diagnostics: plan.bundle.diagnostics.filter((entry) => entry.severity !== 'info'),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function printResult(output, result) {
|
|
121
|
+
output.stdout(`Workspace: ${result.workspaceRoot}\n`);
|
|
122
|
+
output.stdout(`Status: ${result.initComplete ? 'complete' : 'planned'}\n`);
|
|
123
|
+
for (const file of result.files) {
|
|
124
|
+
output.stdout(` ${file.operation} ${file.path}\n`);
|
|
125
|
+
}
|
|
126
|
+
if (result.runtime !== null) {
|
|
127
|
+
output.stdout(`Runtime: ${result.runtime.status} ${result.runtime.version}\n`);
|
|
128
|
+
}
|
|
129
|
+
for (const host of result.hosts) {
|
|
130
|
+
output.stdout(`Host ${host.host}: detected ${host.detectedVersion ?? '(missing)'} / pinned ${host.pinnedVersion} (${host.state}) -> ${host.action}\n`);
|
|
131
|
+
}
|
|
132
|
+
for (const step of result.manualSteps) {
|
|
133
|
+
output.stdout(` manual: ${step}\n`);
|
|
134
|
+
}
|
|
135
|
+
for (const action of result.nextActions) {
|
|
136
|
+
output.stdout(` next: ${action}\n`);
|
|
137
|
+
}
|
|
138
|
+
printDiagnostics(output, result.diagnostics);
|
|
139
|
+
}
|
|
140
|
+
function printHandoff(output, options, root) {
|
|
141
|
+
const report = {
|
|
142
|
+
schema: 'pappra/handoff/1',
|
|
143
|
+
workspaceRoot: root,
|
|
144
|
+
share: [
|
|
145
|
+
'.pappra/workspace.json',
|
|
146
|
+
'.pappra-choices.json (or the choices file used for onboard)',
|
|
147
|
+
'repository checkouts at the same relative paths',
|
|
148
|
+
],
|
|
149
|
+
doNotCopy: [
|
|
150
|
+
'.pappra/local/machine.json',
|
|
151
|
+
'.pappra/local/code/*.json',
|
|
152
|
+
'.pappra/local/knowledge/catalog.json',
|
|
153
|
+
'.pappra/local/host-events/**',
|
|
154
|
+
],
|
|
155
|
+
commands: [
|
|
156
|
+
`npm install -g @phyzess/pappra@${getPackageInfo().version}`,
|
|
157
|
+
`cd ${root}`,
|
|
158
|
+
'pappra onboard --yes --apply-host',
|
|
159
|
+
'pappra doctor --json',
|
|
160
|
+
],
|
|
161
|
+
};
|
|
162
|
+
if (options.json) {
|
|
163
|
+
output.stdout(`${JSON.stringify(report)}\n`);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
output.stdout('Share:\n');
|
|
167
|
+
for (const entry of report.share)
|
|
168
|
+
output.stdout(` ${entry}\n`);
|
|
169
|
+
output.stdout('Do not copy:\n');
|
|
170
|
+
for (const entry of report.doNotCopy)
|
|
171
|
+
output.stdout(` ${entry}\n`);
|
|
172
|
+
output.stdout('On the new machine:\n');
|
|
173
|
+
for (const entry of report.commands)
|
|
174
|
+
output.stdout(` ${entry}\n`);
|
|
175
|
+
}
|
|
176
|
+
return 0;
|
|
177
|
+
}
|
|
178
|
+
export async function runOnboardCommand(options, output) {
|
|
179
|
+
const root = options.root ?? options.cwd;
|
|
180
|
+
if (options.printHandoff) {
|
|
181
|
+
return printHandoff(output, options, root);
|
|
182
|
+
}
|
|
183
|
+
let hosts;
|
|
184
|
+
try {
|
|
185
|
+
hosts = parseHostList(options.hosts);
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
return writeFailure(output, options, {
|
|
189
|
+
ok: false,
|
|
190
|
+
reason: 'invalid',
|
|
191
|
+
message: error instanceof Error ? error.message : String(error),
|
|
192
|
+
diagnostics: [],
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
let choices = null;
|
|
196
|
+
if (options.configPath !== null) {
|
|
197
|
+
const loaded = await loadChoices(options.configPath);
|
|
198
|
+
if (!loaded.ok)
|
|
199
|
+
return writeFailure(output, options, loaded.failure);
|
|
200
|
+
choices = loaded.choices;
|
|
201
|
+
}
|
|
202
|
+
const planned = await planOnboard({ root, choices, hosts });
|
|
203
|
+
if ('reason' in planned)
|
|
204
|
+
return writeFailure(output, options, planned);
|
|
205
|
+
if (!options.apply || options.dryRun) {
|
|
206
|
+
const report = planReport(planned);
|
|
207
|
+
if (options.json) {
|
|
208
|
+
output.stdout(`${JSON.stringify(report)}\n`);
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
printResult(output, report);
|
|
212
|
+
}
|
|
213
|
+
return 0;
|
|
214
|
+
}
|
|
215
|
+
const result = await applyOnboard(planned, {
|
|
216
|
+
applyHosts: options.applyHosts,
|
|
217
|
+
});
|
|
218
|
+
if (options.json) {
|
|
219
|
+
output.stdout(`${JSON.stringify(result)}\n`);
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
printResult(output, result);
|
|
223
|
+
}
|
|
224
|
+
return result.ok ? 0 : 1;
|
|
225
|
+
}
|
package/dist/cli/run.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
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';
|
|
4
5
|
import { doctorUsage, runDoctorCommand } from './commands/doctor.js';
|
|
5
6
|
import { docsUsage, runDocsCommand } from './commands/docs.js';
|
|
6
7
|
import { checkpointUsage, resumeUsage, runCheckpointCommand, runResumeCommand, } from './commands/continuity.js';
|
|
@@ -21,6 +22,10 @@ function parseOptions(args) {
|
|
|
21
22
|
yes: { type: 'boolean', short: 'y' },
|
|
22
23
|
'no-runtime': { type: 'boolean' },
|
|
23
24
|
reconfigure: { type: 'boolean' },
|
|
25
|
+
hosts: { type: 'string' },
|
|
26
|
+
'apply-host': { type: 'boolean' },
|
|
27
|
+
'dry-run': { type: 'boolean' },
|
|
28
|
+
'print-handoff': { type: 'boolean' },
|
|
24
29
|
from: { type: 'string' },
|
|
25
30
|
tarball: { type: 'string' },
|
|
26
31
|
out: { type: 'string' },
|
|
@@ -216,6 +221,8 @@ function hasContinuityOptions(values, includeSession = true) {
|
|
|
216
221
|
(includeSession && values.session !== undefined));
|
|
217
222
|
}
|
|
218
223
|
const globalUsage = 'pappra [--help | --version] [--json]\n' +
|
|
224
|
+
'pappra onboard [--root <dir>] [--config <file>] [--hosts <list>] [--yes]\n' +
|
|
225
|
+
' [--apply-host] [--dry-run] [--print-handoff] [--json]\n' +
|
|
219
226
|
'pappra init [--config <file> | --yes] [--apply] [--json] [--print-choices]\n' +
|
|
220
227
|
' [--install-runtime (--runtime-tarball <file> | --runtime-version <version>)]\n' +
|
|
221
228
|
'pappra doctor [--root <path>] [--json]\n' +
|
|
@@ -240,12 +247,23 @@ export async function runCli(args, output) {
|
|
|
240
247
|
const json = values.json === true;
|
|
241
248
|
const applyRequested = values.apply === true;
|
|
242
249
|
if (command !== 'init' &&
|
|
243
|
-
(values
|
|
244
|
-
values['no-interactive'] === true ||
|
|
250
|
+
(values['no-interactive'] === true ||
|
|
245
251
|
values['print-choices'] === true ||
|
|
246
252
|
values['no-runtime'] === true ||
|
|
247
253
|
values.reconfigure === true)) {
|
|
248
|
-
failure(output, json, 'invalid_arguments', '--
|
|
254
|
+
failure(output, json, 'invalid_arguments', '--no-interactive, --print-choices, --no-runtime and --reconfigure are only valid for init.');
|
|
255
|
+
return 2;
|
|
256
|
+
}
|
|
257
|
+
if (command !== 'init' && command !== 'onboard' && values.yes === true) {
|
|
258
|
+
failure(output, json, 'invalid_arguments', '--yes is only valid for init and onboard.');
|
|
259
|
+
return 2;
|
|
260
|
+
}
|
|
261
|
+
if (command !== 'onboard' &&
|
|
262
|
+
(values.hosts !== undefined ||
|
|
263
|
+
values['apply-host'] === true ||
|
|
264
|
+
values['dry-run'] === true ||
|
|
265
|
+
values['print-handoff'] === true)) {
|
|
266
|
+
failure(output, json, 'invalid_arguments', '--hosts, --apply-host, --dry-run and --print-handoff are only valid for onboard.');
|
|
249
267
|
return 2;
|
|
250
268
|
}
|
|
251
269
|
if (command !== 'host' && values.out !== undefined) {
|
|
@@ -398,6 +416,57 @@ export async function runCli(args, output) {
|
|
|
398
416
|
json,
|
|
399
417
|
}, output);
|
|
400
418
|
}
|
|
419
|
+
if (command === 'onboard') {
|
|
420
|
+
if (values.apply === true ||
|
|
421
|
+
values['install-runtime'] === true ||
|
|
422
|
+
values['runtime-tarball'] !== undefined ||
|
|
423
|
+
values['runtime-version'] !== undefined ||
|
|
424
|
+
values['host-root'] !== undefined ||
|
|
425
|
+
values['host-version'] !== undefined ||
|
|
426
|
+
values.mode !== undefined ||
|
|
427
|
+
values.from !== undefined ||
|
|
428
|
+
values.tarball !== undefined ||
|
|
429
|
+
values.bind === true ||
|
|
430
|
+
values.force === true ||
|
|
431
|
+
values.marketplace === true ||
|
|
432
|
+
values.scope !== undefined ||
|
|
433
|
+
values.repo !== undefined ||
|
|
434
|
+
values.kind !== undefined ||
|
|
435
|
+
values.query !== undefined ||
|
|
436
|
+
values.limit !== undefined ||
|
|
437
|
+
values['max-bytes'] !== undefined ||
|
|
438
|
+
values.rebuild === true ||
|
|
439
|
+
values['include-private'] === true ||
|
|
440
|
+
values['include-archived'] === true ||
|
|
441
|
+
hasContinuityOptions(values)) {
|
|
442
|
+
failure(output, json, 'invalid_arguments', 'onboard accepts --root, --config, --hosts, --yes, --apply-host, --dry-run, --print-handoff, --help, and --json.');
|
|
443
|
+
return 2;
|
|
444
|
+
}
|
|
445
|
+
if (values.help) {
|
|
446
|
+
if (json) {
|
|
447
|
+
output.stdout(`${JSON.stringify({ ...info, commands: ['onboard'], usage: onboardUsage })}\n`);
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
output.stdout(onboardUsage);
|
|
451
|
+
}
|
|
452
|
+
return 0;
|
|
453
|
+
}
|
|
454
|
+
if (values.version) {
|
|
455
|
+
failure(output, json, 'invalid_arguments', '--version cannot be combined with onboard.');
|
|
456
|
+
return 2;
|
|
457
|
+
}
|
|
458
|
+
return runOnboardCommand({
|
|
459
|
+
cwd: process.cwd(),
|
|
460
|
+
root: values.root ?? null,
|
|
461
|
+
configPath: values.config ?? null,
|
|
462
|
+
hosts: values.hosts ?? null,
|
|
463
|
+
apply: values.yes === true,
|
|
464
|
+
applyHosts: values['apply-host'] === true,
|
|
465
|
+
dryRun: values['dry-run'] === true,
|
|
466
|
+
printHandoff: values['print-handoff'] === true,
|
|
467
|
+
json,
|
|
468
|
+
}, output);
|
|
469
|
+
}
|
|
401
470
|
if (command === 'init') {
|
|
402
471
|
if (values.root !== undefined ||
|
|
403
472
|
values['include-private'] === true ||
|
package/dist/code/service.js
CHANGED
|
@@ -177,7 +177,10 @@ async function observeCode(input) {
|
|
|
177
177
|
: fallbackHealth('unsupported', raw.code, versionValue);
|
|
178
178
|
if (!raw.ok)
|
|
179
179
|
diagnostics.push(outcomeDiagnostic(raw, repository.root));
|
|
180
|
-
|
|
180
|
+
// Registrations belong to the workspace, not to a repository checkout: writing
|
|
181
|
+
// <repo>/.pappra/local/code would make that checkout look like a nested workspace
|
|
182
|
+
// and hide it from the outer workspace discovery.
|
|
183
|
+
const registrationRead = await readCodeRegistration(input.root, input.repositoryId);
|
|
181
184
|
let registration = null;
|
|
182
185
|
let registrationFailure = null;
|
|
183
186
|
if (registrationRead.ok) {
|
|
@@ -193,7 +196,7 @@ async function observeCode(input) {
|
|
|
193
196
|
rawHealth,
|
|
194
197
|
health,
|
|
195
198
|
registration,
|
|
196
|
-
registrationPath: codeRegistrationPath(
|
|
199
|
+
registrationPath: codeRegistrationPath(input.root, input.repositoryId),
|
|
197
200
|
excludedRoots,
|
|
198
201
|
access: repository.access,
|
|
199
202
|
diagnostics,
|
|
@@ -345,9 +348,12 @@ export class CodeIntelligenceService {
|
|
|
345
348
|
diagnostics: [...observed.diagnostics],
|
|
346
349
|
};
|
|
347
350
|
}
|
|
348
|
-
const registrationTarget = codeRegistrationPath(
|
|
349
|
-
|
|
350
|
-
|
|
351
|
+
const registrationTarget = codeRegistrationPath(input.root, observed.identity.repositoryId);
|
|
352
|
+
const boundaries = await Promise.all([
|
|
353
|
+
ensureCodeMutationBoundary(observed.identity.root, plan.indexPath, observed.excludedRoots),
|
|
354
|
+
ensureCodeMutationBoundary(input.root, registrationTarget),
|
|
355
|
+
]);
|
|
356
|
+
for (const boundary of boundaries) {
|
|
351
357
|
if (!boundary.ok) {
|
|
352
358
|
return {
|
|
353
359
|
schema: 'pappra/code-prepare-result/1',
|
|
@@ -364,12 +370,12 @@ export class CodeIntelligenceService {
|
|
|
364
370
|
registrationPath: null,
|
|
365
371
|
diagnostics: [
|
|
366
372
|
...observed.diagnostics,
|
|
367
|
-
outcomeDiagnostic(boundary,
|
|
373
|
+
outcomeDiagnostic(boundary, registrationTarget),
|
|
368
374
|
],
|
|
369
375
|
};
|
|
370
376
|
}
|
|
371
377
|
}
|
|
372
|
-
const lock = await acquireCodePrepareLock(
|
|
378
|
+
const lock = await acquireCodePrepareLock(input.root);
|
|
373
379
|
if (!lock.ok) {
|
|
374
380
|
return {
|
|
375
381
|
schema: 'pappra/code-prepare-result/1',
|
|
@@ -507,7 +513,7 @@ export class CodeIntelligenceService {
|
|
|
507
513
|
preparedAt: new Date().toISOString(),
|
|
508
514
|
coverage: prepared.value.coverage,
|
|
509
515
|
};
|
|
510
|
-
const written = await writeCodeRegistration(
|
|
516
|
+
const written = await writeCodeRegistration(input.root, registration);
|
|
511
517
|
if (!written.ok) {
|
|
512
518
|
return {
|
|
513
519
|
schema: 'pappra/code-prepare-result/1',
|