@phyzess/pappra 0.1.0-beta.2 → 0.1.0-beta.4
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 +24 -7
- package/dist/app/doctor.js +2 -2
- package/dist/app/init.d.ts +28 -0
- package/dist/app/init.js +419 -88
- package/dist/cli/commands/docs.d.ts +1 -0
- package/dist/cli/commands/docs.js +104 -3
- package/dist/cli/commands/host.d.ts +3 -1
- package/dist/cli/commands/host.js +50 -2
- package/dist/cli/commands/init.d.ts +8 -0
- package/dist/cli/commands/init.js +186 -9
- package/dist/cli/commands/runtime.d.ts +14 -0
- package/dist/cli/commands/runtime.js +263 -0
- package/dist/cli/run.js +134 -7
- package/dist/hosts/detect.d.ts +1 -0
- package/dist/hosts/detect.js +1 -1
- package/dist/hosts/integration/bundle-export.d.ts +22 -0
- package/dist/hosts/integration/bundle-export.js +114 -0
- package/dist/hosts/integration/bundle.d.ts +4 -1
- package/dist/hosts/integration/bundle.js +20 -2
- package/dist/hosts/integration/pappra-extension-runtime.mjs +24605 -0
- package/dist/hosts/integration/pappra-hook.mjs +24723 -0
- package/dist/hosts/integration/service.d.ts +8 -0
- package/dist/hosts/integration/service.js +62 -30
- package/dist/hosts/integration/templates.d.ts +3 -3
- package/dist/hosts/integration/templates.js +15 -10
- package/dist/hosts/pilot.d.ts +77 -77
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/install/runtime.d.ts +1 -1
- package/dist/install/runtime.js +117 -10
- package/dist/package-info.d.ts +2 -0
- package/dist/package-info.js +6 -0
- package/dist/schemas/host.js +4 -2
- package/dist/schemas/install.d.ts +14 -0
- package/dist/schemas/install.js +8 -0
- package/package.json +8 -2
|
@@ -1,11 +1,106 @@
|
|
|
1
|
+
import { discoverWorkspace } from '../../workspace/discover.js';
|
|
1
2
|
import { resolveWorkspaceRoot } from '../../workspace/resolve.js';
|
|
2
3
|
import { formatDiagnostic } from '../../schemas/diagnostics.js';
|
|
3
4
|
import { docsArchiveReportSchema, docsCheckReportSchema, docsListReportSchema, } from '../../schemas/document.js';
|
|
4
5
|
import { KnowledgeService, } from '../../knowledge/index.js';
|
|
5
|
-
export const docsUsage = 'pappra docs <list|check|archive> [options]\n' +
|
|
6
|
+
export const docsUsage = 'pappra docs <list|check|archive|scan> [options]\n' +
|
|
6
7
|
' list [--include-private] [--include-archived] [--json]\n' +
|
|
7
8
|
' check [--include-private] [--json]\n' +
|
|
8
|
-
' archive <documentId> [--json]'
|
|
9
|
+
' archive <documentId> [--json]\n' +
|
|
10
|
+
' scan [--apply] [--json]\n\n' +
|
|
11
|
+
'scan registers documents discovered under the configured knowledge sources; without\n' +
|
|
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
|
+
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
|
+
});
|
|
49
|
+
if (!options.apply) {
|
|
50
|
+
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`);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
output.stdout(`Scan candidates: ${candidates.length}\n`);
|
|
65
|
+
for (const candidate of candidates) {
|
|
66
|
+
output.stdout(` ${candidate.kind} ${candidate.path}\n`);
|
|
67
|
+
}
|
|
68
|
+
output.stdout('Plan only; rerun with --apply to register them.\n');
|
|
69
|
+
}
|
|
70
|
+
return 0;
|
|
71
|
+
}
|
|
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
|
+
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`);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
output.stdout(`Registered ${registeredNow.length} document(s).\n`);
|
|
100
|
+
printDiagnostics(output, diagnostics);
|
|
101
|
+
}
|
|
102
|
+
return ok ? 0 : 1;
|
|
103
|
+
}
|
|
9
104
|
function printDiagnostics(output, diagnostics) {
|
|
10
105
|
for (const diagnostic of diagnostics) {
|
|
11
106
|
output.stderr(`${formatDiagnostic(diagnostic)}\n`);
|
|
@@ -55,7 +150,7 @@ export async function runDocsCommand(options, output) {
|
|
|
55
150
|
}
|
|
56
151
|
const resolution = await resolveWorkspaceRoot({ startPath: options.cwd });
|
|
57
152
|
const serviceRoot = resolution.root ?? options.cwd;
|
|
58
|
-
if (!['list', 'check', 'archive'].includes(options.action)) {
|
|
153
|
+
if (!['list', 'check', 'archive', 'scan'].includes(options.action)) {
|
|
59
154
|
return invalidArguments(output, options.json, `unknown docs action: ${options.action}`);
|
|
60
155
|
}
|
|
61
156
|
if (options.action === 'archive' && options.argument === null) {
|
|
@@ -64,6 +159,12 @@ export async function runDocsCommand(options, output) {
|
|
|
64
159
|
if (options.action !== 'archive' && options.argument !== null) {
|
|
65
160
|
return invalidArguments(output, options.json, `docs ${options.action} does not accept an argument`);
|
|
66
161
|
}
|
|
162
|
+
if (options.action === 'scan') {
|
|
163
|
+
return runDocsScan(options, output, serviceRoot);
|
|
164
|
+
}
|
|
165
|
+
if (options.apply) {
|
|
166
|
+
return invalidArguments(output, options.json, 'only docs scan accepts --apply');
|
|
167
|
+
}
|
|
67
168
|
if (options.action === 'archive' && options.argument !== null) {
|
|
68
169
|
const service = new KnowledgeService(serviceRoot);
|
|
69
170
|
const result = await service.archive(options.argument);
|
|
@@ -4,10 +4,12 @@ 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' | null;
|
|
7
|
+
readonly action: 'detect' | 'describe' | 'plan' | 'install' | 'verify' | 'bundle' | 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 out: string | null;
|
|
13
|
+
readonly mode: 'project-settings' | 'plugin' | null;
|
|
12
14
|
}
|
|
13
15
|
export declare function runHostCommand(options: HostCommandOptions, output: CliOutput): Promise<number>;
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { homedir } from 'node:os';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
3
|
import { HostIntegrationService } from '../../hosts/integration/service.js';
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
import { exportHostPluginBundle } from '../../hosts/integration/bundle-export.js';
|
|
5
|
+
import { HOST_SUPPORT, isSupportedVersion, } from '../../hosts/integration/support.js';
|
|
6
|
+
import { getPackageInfo } from '../../package-info.js';
|
|
7
|
+
export const hostUsage = 'pappra host <detect|describe|plan|install|verify|bundle> --host <pi|codex|claude>\n' +
|
|
8
|
+
' [--host-root <dir>] [--host-version <version>] [--out <dir>]\n' +
|
|
9
|
+
' [--mode project-settings|plugin] [--apply] [--json]\n' +
|
|
10
|
+
'\n' +
|
|
11
|
+
'bundle exports a host-native plugin/package directory and never registers it or\n' +
|
|
12
|
+
'claims host callability.';
|
|
6
13
|
function printDiagnostics(output, diagnostics) {
|
|
7
14
|
for (const entry of diagnostics) {
|
|
8
15
|
if (typeof entry === 'object' &&
|
|
@@ -96,6 +103,7 @@ export async function runHostCommand(options, output) {
|
|
|
96
103
|
const result = await service.install(options.host, {
|
|
97
104
|
...target,
|
|
98
105
|
apply: options.apply,
|
|
106
|
+
...(options.mode === null ? {} : { mode: options.mode }),
|
|
99
107
|
});
|
|
100
108
|
if (options.json) {
|
|
101
109
|
output.stdout(`${JSON.stringify(result)}\n`);
|
|
@@ -110,6 +118,46 @@ export async function runHostCommand(options, output) {
|
|
|
110
118
|
}
|
|
111
119
|
return diagnosticStatus(result.status);
|
|
112
120
|
}
|
|
121
|
+
if (options.action === 'bundle') {
|
|
122
|
+
if (options.out === null) {
|
|
123
|
+
output.stderr('pappra host bundle requires --out <dir>.\n');
|
|
124
|
+
return 2;
|
|
125
|
+
}
|
|
126
|
+
if (!isSupportedVersion(options.host, options.hostVersion)) {
|
|
127
|
+
output.stderr(`Host ${options.host} version ${options.hostVersion ?? '(unknown)'} is outside the pinned ${HOST_SUPPORT[options.host].version} support matrix.\n`);
|
|
128
|
+
return 1;
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
const exported = await exportHostPluginBundle({
|
|
132
|
+
host: options.host,
|
|
133
|
+
hostVersion: options.hostVersion ?? HOST_SUPPORT[options.host].version,
|
|
134
|
+
outDir: options.out,
|
|
135
|
+
packageVersion: getPackageInfo().version,
|
|
136
|
+
});
|
|
137
|
+
if (options.json) {
|
|
138
|
+
output.stdout(`${JSON.stringify({ ok: true, command: 'host', action: 'bundle', ...exported })}\n`);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
output.stdout(`Exported ${exported.host} bundle to ${exported.outDir}\n`);
|
|
142
|
+
for (const file of exported.files)
|
|
143
|
+
output.stdout(` ${file}\n`);
|
|
144
|
+
for (const hint of exported.registration)
|
|
145
|
+
output.stdout(` note: ${hint}\n`);
|
|
146
|
+
output.stdout('Registration and host callability remain unverified.\n');
|
|
147
|
+
}
|
|
148
|
+
return 0;
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
152
|
+
if (options.json) {
|
|
153
|
+
output.stdout(`${JSON.stringify({ ok: false, command: 'host', action: 'bundle', message })}\n`);
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
output.stderr(`pappra host bundle: ${message}\n`);
|
|
157
|
+
}
|
|
158
|
+
return 1;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
113
161
|
const result = await service.verify(options.host, target);
|
|
114
162
|
if (options.json) {
|
|
115
163
|
output.stdout(`${JSON.stringify(result)}\n`);
|
|
@@ -32,6 +32,14 @@ export interface InitCommandOptions {
|
|
|
32
32
|
readonly installRuntime?: boolean;
|
|
33
33
|
readonly runtimeTarball?: string | null;
|
|
34
34
|
readonly runtimeVersion?: string | null;
|
|
35
|
+
/** Derives deterministic selections from discovery instead of prompting. */
|
|
36
|
+
readonly yes?: boolean;
|
|
37
|
+
/** Prints the derived selections and writes nothing. */
|
|
38
|
+
readonly printChoices?: boolean;
|
|
39
|
+
/** Leaves the runtime action pending instead of installing a durable copy. */
|
|
40
|
+
readonly noRuntime?: boolean;
|
|
41
|
+
/** Rebuilds workspace.json from the selections even when a valid config exists. */
|
|
42
|
+
readonly reconfigure?: boolean;
|
|
35
43
|
}
|
|
36
44
|
export interface RuntimeFlowResult {
|
|
37
45
|
readonly ok: boolean;
|
|
@@ -2,11 +2,12 @@ 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 { readTextFileBounded } from '../../infra/fs.js';
|
|
6
|
-
import { getPackageInfo } from '../../package-info.js';
|
|
5
|
+
import { observePath, readTextFileBounded } from '../../infra/fs.js';
|
|
6
|
+
import { getPackageInfo, getPackageRoot } from '../../package-info.js';
|
|
7
7
|
import { installRuntime, } from '../../install/runtime.js';
|
|
8
8
|
import { updateMachineRuntime } from '../../install/apply.js';
|
|
9
|
-
import {
|
|
9
|
+
import { inspectMachineConfig } from '../../workspace/config.js';
|
|
10
|
+
import { applyInitPlan, inspectInitContext, listKnowledgeCandidates, deriveInitChoices, listRepositoryCandidates, planWorkspaceInit, } from '../../app/init.js';
|
|
10
11
|
export const clackWizardUi = {
|
|
11
12
|
intro: (message) => prompts.intro(message),
|
|
12
13
|
outro: (message) => prompts.outro(message),
|
|
@@ -249,6 +250,84 @@ function runtimeOptionsFailure(message, nextAction) {
|
|
|
249
250
|
nextAction,
|
|
250
251
|
};
|
|
251
252
|
}
|
|
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
|
+
}
|
|
252
331
|
async function installAndRecordRuntime(root, targetVersion, options) {
|
|
253
332
|
const tarball = options.runtimeTarball ?? null;
|
|
254
333
|
const requestedVersion = options.runtimeVersion ?? null;
|
|
@@ -385,6 +464,7 @@ function writeFailure(output, options, failure) {
|
|
|
385
464
|
message: failure.message,
|
|
386
465
|
nextAction: failure.nextAction,
|
|
387
466
|
diagnostics: failure.diagnostics,
|
|
467
|
+
...(failure.details ?? {}),
|
|
388
468
|
})}\n`);
|
|
389
469
|
}
|
|
390
470
|
else {
|
|
@@ -506,12 +586,26 @@ function printApply(output, result, runtimeRequested) {
|
|
|
506
586
|
: 'Only planned config files were applied. Initialization is not complete until runtime/provider actions are authorized and executed.\n');
|
|
507
587
|
}
|
|
508
588
|
}
|
|
589
|
+
function advisoryNextActions(bundle) {
|
|
590
|
+
const actions = [];
|
|
591
|
+
const repositoryId = bundle.scope.repositories[0]?.id;
|
|
592
|
+
if (bundle.scope.code.graph !== 'none' &&
|
|
593
|
+
bundle.scope.repositories.length > 0 &&
|
|
594
|
+
repositoryId !== undefined) {
|
|
595
|
+
actions.push(`pappra code prepare --repo ${repositoryId} --apply # register the ${bundle.scope.code.graph} index for this checkout`);
|
|
596
|
+
}
|
|
597
|
+
if (bundle.scope.knowledge.sources.length > 0) {
|
|
598
|
+
actions.push('pappra docs scan --apply # register discovered knowledge documents in the catalog');
|
|
599
|
+
}
|
|
600
|
+
return actions;
|
|
601
|
+
}
|
|
509
602
|
function planJson(bundle) {
|
|
510
603
|
return {
|
|
511
604
|
command: 'init',
|
|
512
605
|
mode: 'plan',
|
|
513
606
|
plan: bundle.plan,
|
|
514
607
|
scope: bundle.scope,
|
|
608
|
+
nextActions: advisoryNextActions(bundle),
|
|
515
609
|
diagnostics: bundle.diagnostics,
|
|
516
610
|
};
|
|
517
611
|
}
|
|
@@ -520,7 +614,11 @@ function planJson(bundle) {
|
|
|
520
614
|
* exactly one JSON document. Interactive selection is injected for tests.
|
|
521
615
|
*/
|
|
522
616
|
export async function runInitCommand(options, output, deps = {}) {
|
|
617
|
+
const yes = options.yes === true;
|
|
618
|
+
const printChoices = options.printChoices === true;
|
|
523
619
|
const interactive = !options.json &&
|
|
620
|
+
!yes &&
|
|
621
|
+
!printChoices &&
|
|
524
622
|
options.configPath === null &&
|
|
525
623
|
(deps.interactive ??
|
|
526
624
|
(process.stdin.isTTY === true && process.stdout.isTTY === true));
|
|
@@ -542,29 +640,82 @@ export async function runInitCommand(options, output, deps = {}) {
|
|
|
542
640
|
options.runtimeVersion !== undefined) {
|
|
543
641
|
return writeFailure(output, options, runtimeOptionsFailure('Use either --runtime-tarball or --runtime-version, not both.', 'Choose one explicit runtime source.'));
|
|
544
642
|
}
|
|
643
|
+
if (printChoices &&
|
|
644
|
+
(options.configPath !== null ||
|
|
645
|
+
options.apply ||
|
|
646
|
+
wantsRuntime ||
|
|
647
|
+
hasRuntimeSource)) {
|
|
648
|
+
return writeFailure(output, options, {
|
|
649
|
+
ok: false,
|
|
650
|
+
status: 'invalid',
|
|
651
|
+
code: diagnosticCodes.initInvalidInput,
|
|
652
|
+
message: '--print-choices cannot be combined with --config, --apply, or runtime flags.',
|
|
653
|
+
diagnostics: [],
|
|
654
|
+
nextAction: 'Run pappra init --print-choices on its own to print the derived selections.',
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
if (yes && options.configPath !== null) {
|
|
658
|
+
return writeFailure(output, options, {
|
|
659
|
+
ok: false,
|
|
660
|
+
status: 'invalid',
|
|
661
|
+
code: diagnosticCodes.initInvalidInput,
|
|
662
|
+
message: 'Use either --config or --yes, not both.',
|
|
663
|
+
diagnostics: [],
|
|
664
|
+
nextAction: 'Pass explicit selections with --config, or derive them with --yes.',
|
|
665
|
+
});
|
|
666
|
+
}
|
|
545
667
|
const contextResult = await inspectInitContext(options.cwd);
|
|
546
668
|
if (!contextResult.ok) {
|
|
547
669
|
return writeFailure(output, options, contextResult);
|
|
548
670
|
}
|
|
549
671
|
const context = contextResult.context;
|
|
550
672
|
let choices = null;
|
|
673
|
+
if (printChoices) {
|
|
674
|
+
const derived = await deriveInitChoices(context.discovery);
|
|
675
|
+
if (options.json) {
|
|
676
|
+
output.stdout(`${JSON.stringify({
|
|
677
|
+
ok: true,
|
|
678
|
+
command: 'init',
|
|
679
|
+
mode: 'choices',
|
|
680
|
+
choices: derived.choices,
|
|
681
|
+
rules: derived.rules,
|
|
682
|
+
candidates: derived.candidates,
|
|
683
|
+
})}\n`);
|
|
684
|
+
}
|
|
685
|
+
else {
|
|
686
|
+
output.stdout(`${JSON.stringify(derived.choices, null, 2)}\n`);
|
|
687
|
+
for (const rule of derived.rules) {
|
|
688
|
+
output.stderr(` ${rule.field} = ${rule.value} (${rule.reason})\n`);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
return 0;
|
|
692
|
+
}
|
|
551
693
|
if (options.configPath !== null) {
|
|
552
694
|
const loaded = await loadChoices(options.configPath);
|
|
553
695
|
if (!loaded.ok)
|
|
554
696
|
return writeFailure(output, options, loaded.failure);
|
|
555
697
|
choices = loaded.choices;
|
|
556
698
|
}
|
|
699
|
+
else if (yes) {
|
|
700
|
+
choices = (await deriveInitChoices(context.discovery)).choices;
|
|
701
|
+
}
|
|
557
702
|
else {
|
|
558
703
|
const needsChoices = context.discovery.configState !== 'valid' ||
|
|
559
704
|
context.discovery.machineState !== 'valid';
|
|
560
705
|
if (needsChoices && !interactive) {
|
|
706
|
+
const derived = await deriveInitChoices(context.discovery);
|
|
561
707
|
return writeFailure(output, options, {
|
|
562
708
|
ok: false,
|
|
563
709
|
status: 'needs_input',
|
|
564
710
|
code: diagnosticCodes.initNoTty,
|
|
565
711
|
message: 'Missing init selections in a non-interactive session.',
|
|
566
712
|
diagnostics: context.diagnostics,
|
|
567
|
-
nextAction: '
|
|
713
|
+
nextAction: 'Rerun with --yes to apply the derived selections, provide --config with pappra/init-choices/1, or run in a TTY for the interactive wizard.',
|
|
714
|
+
details: {
|
|
715
|
+
candidates: derived.candidates,
|
|
716
|
+
suggestedChoices: derived.choices,
|
|
717
|
+
rules: derived.rules,
|
|
718
|
+
},
|
|
568
719
|
});
|
|
569
720
|
}
|
|
570
721
|
if (needsChoices && interactive) {
|
|
@@ -579,6 +730,7 @@ export async function runInitCommand(options, output, deps = {}) {
|
|
|
579
730
|
root: context.root,
|
|
580
731
|
choices,
|
|
581
732
|
context,
|
|
733
|
+
...(options.reconfigure === true ? { reconfigure: true } : {}),
|
|
582
734
|
});
|
|
583
735
|
if (!prepared.ok)
|
|
584
736
|
return writeFailure(output, options, prepared);
|
|
@@ -592,13 +744,16 @@ export async function runInitCommand(options, output, deps = {}) {
|
|
|
592
744
|
return 0;
|
|
593
745
|
}
|
|
594
746
|
const applied = await applyInitPlan(bundle);
|
|
595
|
-
const
|
|
596
|
-
|
|
747
|
+
const runtimeRequested = wantsRuntime || options.noRuntime !== true;
|
|
748
|
+
const runtimeFlow = runtimeRequested && applied.ok && applied.residualPaths.length === 0
|
|
749
|
+
? wantsRuntime
|
|
750
|
+
? await installAndRecordRuntime(bundle.scope.workspaceRoot, bundle.scope.runtimeVersion, options)
|
|
751
|
+
: await ensureDefaultRuntime(bundle.scope.workspaceRoot, bundle.scope.runtimeVersion)
|
|
597
752
|
: null;
|
|
598
753
|
const initComplete = runtimeFlow?.completion === true;
|
|
599
754
|
const ok = applied.ok &&
|
|
600
755
|
applied.residualPaths.length === 0 &&
|
|
601
|
-
(!
|
|
756
|
+
(!runtimeRequested || (runtimeFlow?.ok === true && initComplete));
|
|
602
757
|
output.stdout(`${JSON.stringify({
|
|
603
758
|
ok,
|
|
604
759
|
command: 'init',
|
|
@@ -608,6 +763,7 @@ export async function runInitCommand(options, output, deps = {}) {
|
|
|
608
763
|
apply: applied,
|
|
609
764
|
runtime: runtimeFlow?.runtime ?? null,
|
|
610
765
|
initComplete,
|
|
766
|
+
nextActions: advisoryNextActions(bundle),
|
|
611
767
|
diagnostics: [
|
|
612
768
|
...bundle.diagnostics,
|
|
613
769
|
...(runtimeFlow?.diagnostics ?? []),
|
|
@@ -615,7 +771,7 @@ export async function runInitCommand(options, output, deps = {}) {
|
|
|
615
771
|
})}\n`);
|
|
616
772
|
if (!applied.ok || applied.residualPaths.length > 0)
|
|
617
773
|
return 1;
|
|
618
|
-
if (
|
|
774
|
+
if (runtimeRequested)
|
|
619
775
|
return ok ? 0 : 1;
|
|
620
776
|
return applied.status === 'external-actions-pending' ? 1 : 0;
|
|
621
777
|
}
|
|
@@ -632,7 +788,7 @@ export async function runInitCommand(options, output, deps = {}) {
|
|
|
632
788
|
}
|
|
633
789
|
}
|
|
634
790
|
const applied = await applyInitPlan(bundle);
|
|
635
|
-
printApply(output, applied, wantsRuntime);
|
|
791
|
+
printApply(output, applied, wantsRuntime || options.noRuntime !== true);
|
|
636
792
|
if (!applied.ok) {
|
|
637
793
|
return applied.status === 'failed' ? 3 : 1;
|
|
638
794
|
}
|
|
@@ -658,6 +814,27 @@ export async function runInitCommand(options, output, deps = {}) {
|
|
|
658
814
|
output.stdout('Runtime installed and verified. Initialization complete for P04 scope; provider/index preferences were not executed.\n');
|
|
659
815
|
return 0;
|
|
660
816
|
}
|
|
817
|
+
if (options.noRuntime !== true) {
|
|
818
|
+
const runtimeFlow = await ensureDefaultRuntime(bundle.scope.workspaceRoot, bundle.scope.runtimeVersion);
|
|
819
|
+
if (runtimeFlow.runtime !== null) {
|
|
820
|
+
output.stdout(`Runtime: ${runtimeFlow.runtime.status} ${runtimeFlow.runtime.version}${runtimeFlow.runtime.runtimeRoot === null
|
|
821
|
+
? ''
|
|
822
|
+
: ` at ${runtimeFlow.runtime.runtimeRoot}`}\n`);
|
|
823
|
+
if (runtimeFlow.runtime.residualPaths.length > 0) {
|
|
824
|
+
output.stderr(`Runtime residual paths: ${runtimeFlow.runtime.residualPaths.join(', ')}\n`);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
if (!runtimeFlow.ok || !runtimeFlow.completion) {
|
|
828
|
+
output.stderr(`${diagnosticLines(runtimeFlow.diagnostics)}\n`);
|
|
829
|
+
output.stderr('The durable runtime copy could not be installed or bound. Initialization is not complete; --no-runtime leaves this action pending intentionally.\n');
|
|
830
|
+
return 1;
|
|
831
|
+
}
|
|
832
|
+
output.stdout('Durable runtime copy installed and bound. Initialization complete.\n');
|
|
833
|
+
for (const action of advisoryNextActions(bundle)) {
|
|
834
|
+
output.stdout(` next: ${action}\n`);
|
|
835
|
+
}
|
|
836
|
+
return 0;
|
|
837
|
+
}
|
|
661
838
|
return applied.status === 'external-actions-pending' ? 1 : 0;
|
|
662
839
|
}
|
|
663
840
|
export const initUsage = 'pappra init [--config <file>] [--apply] [--json]\n' +
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CliOutput } from '../output.js';
|
|
2
|
+
export declare const runtimeUsage: string;
|
|
3
|
+
export interface RuntimeCommandOptions {
|
|
4
|
+
readonly cwd: string;
|
|
5
|
+
readonly action: 'install' | 'status' | 'verify' | 'remove' | null;
|
|
6
|
+
readonly json: boolean;
|
|
7
|
+
readonly from: 'self' | 'tarball' | 'registry' | null;
|
|
8
|
+
readonly tarball: string | null;
|
|
9
|
+
readonly version: string | null;
|
|
10
|
+
readonly apply: boolean;
|
|
11
|
+
readonly bind: boolean;
|
|
12
|
+
readonly force: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function runRuntimeCommand(options: RuntimeCommandOptions, output: CliOutput): Promise<number>;
|