@younndai/lyt 0.9.3 → 0.9.5
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 +6 -0
- package/dist/automator-bodies/arc-builder.js +0 -18
- package/dist/automator-bodies/arc-builder.js.map +1 -1
- package/dist/automator-bodies/index.js +0 -18
- package/dist/automator-bodies/index.js.map +1 -1
- package/dist/automator-bodies/lane-builder.js +0 -19
- package/dist/automator-bodies/lane-builder.js.map +1 -1
- package/dist/automator-bodies/metadata-filler.js +0 -49
- package/dist/automator-bodies/metadata-filler.js.map +1 -1
- package/dist/automator-run.js +0 -50
- package/dist/automator-run.js.map +1 -1
- package/dist/bench/graded-corpus.js +0 -25
- package/dist/bench/graded-corpus.js.map +1 -1
- package/dist/bench/invariant-corpus.js +0 -33
- package/dist/bench/invariant-corpus.js.map +1 -1
- package/dist/bench/ir-metrics.js +0 -17
- package/dist/bench/ir-metrics.js.map +1 -1
- package/dist/bench/latency-bench.js +0 -23
- package/dist/bench/latency-bench.js.map +1 -1
- package/dist/bench/latency-corpus.js +2 -20
- package/dist/bench/latency-corpus.js.map +1 -1
- package/dist/bench/pod-harness.js +0 -48
- package/dist/bench/pod-harness.js.map +1 -1
- package/dist/bench/run-bench.js +0 -23
- package/dist/bench/run-bench.js.map +1 -1
- package/dist/cli-automator-run.js +0 -31
- package/dist/cli-automator-run.js.map +1 -1
- package/dist/cli.js +0 -109
- package/dist/cli.js.map +1 -1
- package/dist/commands/bench.js +0 -27
- package/dist/commands/bench.js.map +1 -1
- package/dist/commands/capture.js +0 -83
- package/dist/commands/capture.js.map +1 -1
- package/dist/commands/init.js +1 -233
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/primer.js +0 -44
- package/dist/commands/primer.js.map +1 -1
- package/dist/commands/reindex.js +0 -33
- package/dist/commands/reindex.js.map +1 -1
- package/dist/commands/search.js +0 -71
- package/dist/commands/search.js.map +1 -1
- package/dist/flows/heal.js +0 -46
- package/dist/flows/heal.js.map +1 -1
- package/dist/flows/init-bootstrap.js +0 -208
- package/dist/flows/init-bootstrap.js.map +1 -1
- package/dist/index.js +0 -18
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
package/dist/commands/init.js
CHANGED
|
@@ -1,35 +1,3 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright 2026 MARLINK TRADING SRL (YounndAI)
|
|
3
|
-
*
|
|
4
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
-
* you may not use this file except in compliance with the License.
|
|
6
|
-
* You may obtain a copy of the License at
|
|
7
|
-
*
|
|
8
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
-
*
|
|
10
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
-
* See the License for the specific language governing permissions and
|
|
14
|
-
* limitations under the License.
|
|
15
|
-
*/
|
|
16
|
-
// v1.B.4 — `lyt init [--auto | --custom | --discover] [--json]`.
|
|
17
|
-
//
|
|
18
|
-
// Top-level meta-CLI verb per OD-1 default + master-plan §v1.B.4:543.
|
|
19
|
-
// Composes the v1.B.4 initBootstrapFlow with mutually-exclusive flag
|
|
20
|
-
// validation + structured-error contract + readline/promises three-prompt
|
|
21
|
-
// walkthrough under --custom.
|
|
22
|
-
//
|
|
23
|
-
// Source: brief 2026-05-31-v1-b-4-lyt-init-bootstrap.md "What's to ship"
|
|
24
|
-
// Commit 1 + federation-design §5:228-234 (custom-init prompts) +
|
|
25
|
-
// commands/move.ts (closest CLI shape — readline/promises + mutually-
|
|
26
|
-
// exclusive flag validation + structured-error contract).
|
|
27
|
-
//
|
|
28
|
-
// Error contract (OD-9 + brief acceptance):
|
|
29
|
-
// --auto + --custom together → exit 2 + flag-conflict
|
|
30
|
-
// --custom under non-TTY (incl. --json) → exit 3 + custom-requires-tty
|
|
31
|
-
// re-init with ALL-failed integrity → exit 1 (matches v1.B.2 OD-6)
|
|
32
|
-
// otherwise → exit 0
|
|
33
1
|
import { Command } from "commander";
|
|
34
2
|
import { createInterface } from "node:readline/promises";
|
|
35
3
|
import { getHandleFromIdentity, materializePodLocal, readIdentityCache, reconcilePublishFlow, ReadlinePromptHandler, renderNextSteps, renderPodCard, runWizard, startSpinner, validateMeshName, } from "@younndai/lyt-vault";
|
|
@@ -45,11 +13,6 @@ export function buildLytInitCommand() {
|
|
|
45
13
|
.option("--dry-run", "Only valid with --wizard. Walks all 12 wizard phases without spawn invocations or filesystem writes.")
|
|
46
14
|
.option("--json", "Emit deterministic Lock 0.3 JSON")
|
|
47
15
|
.action(async (opts) => {
|
|
48
|
-
// v1.G.13 Gap 1 — no-flag fresh-state wizard auto-route. When the
|
|
49
|
-
// handler runs `lyt init` with NO mode flags AND the registry is
|
|
50
|
-
// empty (first-init), enter the wizard. Re-init state falls through
|
|
51
|
-
// to the existing --auto default. Non-TTY first-init errors out per
|
|
52
|
-
// the ratified default.
|
|
53
16
|
const noMode = opts.auto !== true &&
|
|
54
17
|
opts.custom !== true &&
|
|
55
18
|
opts.discover !== true &&
|
|
@@ -62,7 +25,6 @@ export function buildLytInitCommand() {
|
|
|
62
25
|
isFresh = await probeFreshState();
|
|
63
26
|
}
|
|
64
27
|
catch (err) {
|
|
65
|
-
// Probe failure (registry unreachable) — surface and exit.
|
|
66
28
|
const msg = err instanceof Error ? err.message : String(err);
|
|
67
29
|
emitError(false, { error: "first-init-probe-failed", message: msg });
|
|
68
30
|
process.exitCode = 1;
|
|
@@ -77,16 +39,9 @@ export function buildLytInitCommand() {
|
|
|
77
39
|
process.exitCode = 3;
|
|
78
40
|
return;
|
|
79
41
|
}
|
|
80
|
-
// TTY + fresh → route through the existing --wizard branch by
|
|
81
|
-
// flipping the flag. Single code path for both explicit
|
|
82
|
-
// `--wizard` and the no-flag first-init auto-route.
|
|
83
42
|
opts.wizard = true;
|
|
84
43
|
}
|
|
85
|
-
// Otherwise (re-init state) fall through to existing --auto default.
|
|
86
44
|
}
|
|
87
|
-
// v1.G.4 — --wizard takes a dedicated branch; mutually exclusive
|
|
88
|
-
// with the existing auto/custom/discover/json modes (the wizard
|
|
89
|
-
// composes mesh+vault+federation init itself).
|
|
90
45
|
if (opts.wizard === true) {
|
|
91
46
|
if (opts.auto === true ||
|
|
92
47
|
opts.custom === true ||
|
|
@@ -107,18 +62,12 @@ export function buildLytInitCommand() {
|
|
|
107
62
|
process.exitCode = 3;
|
|
108
63
|
return;
|
|
109
64
|
}
|
|
110
|
-
// Under --dry-run, use a non-interactive default handler so the
|
|
111
|
-
// wizard can be smoke-tested without stdin. Real interactive runs
|
|
112
|
-
// use the readline-backed handler.
|
|
113
65
|
const handler = opts.dryRun === true ? makeDryRunDefaultsHandler() : new ReadlinePromptHandler();
|
|
114
66
|
try {
|
|
115
67
|
const result = await runWizard({
|
|
116
68
|
promptHandler: handler,
|
|
117
69
|
dryRun: opts.dryRun === true,
|
|
118
70
|
});
|
|
119
|
-
// F3 (console-DX): scannable setup-summary block — a header/footer rule
|
|
120
|
-
// + status glyphs (✓ done · ⊘ skipped · ✗ failed) replacing the flat
|
|
121
|
-
// `[ok] Phase N (name): msg` lines that had no visible start/end.
|
|
122
71
|
const summaryLines = [
|
|
123
72
|
"",
|
|
124
73
|
"── Setup summary ──────────────────────────────────",
|
|
@@ -129,7 +78,6 @@ export function buildLytInitCommand() {
|
|
|
129
78
|
}),
|
|
130
79
|
"───────────────────────────────────────────────────",
|
|
131
80
|
];
|
|
132
|
-
// eslint-disable-next-line no-console
|
|
133
81
|
console.log(summaryLines.join("\n"));
|
|
134
82
|
if (result.status !== "completed") {
|
|
135
83
|
process.exitCode = 1;
|
|
@@ -145,8 +93,6 @@ export function buildLytInitCommand() {
|
|
|
145
93
|
}
|
|
146
94
|
return;
|
|
147
95
|
}
|
|
148
|
-
// --dry-run is only valid with --wizard (the wizard branch returned
|
|
149
|
-
// above; reaching here means --dry-run was passed without --wizard).
|
|
150
96
|
if (opts.dryRun === true) {
|
|
151
97
|
emitError(opts.json === true, {
|
|
152
98
|
error: "flag-conflict",
|
|
@@ -155,7 +101,6 @@ export function buildLytInitCommand() {
|
|
|
155
101
|
process.exitCode = 2;
|
|
156
102
|
return;
|
|
157
103
|
}
|
|
158
|
-
// Flag conflict: --auto + --custom.
|
|
159
104
|
if (opts.auto === true && opts.custom === true) {
|
|
160
105
|
emitError(opts.json === true, {
|
|
161
106
|
error: "flag-conflict",
|
|
@@ -164,7 +109,6 @@ export function buildLytInitCommand() {
|
|
|
164
109
|
process.exitCode = 2;
|
|
165
110
|
return;
|
|
166
111
|
}
|
|
167
|
-
// Flag conflict: --discover with prompts is nonsensical (read-only).
|
|
168
112
|
if (opts.discover === true && opts.custom === true) {
|
|
169
113
|
emitError(opts.json === true, {
|
|
170
114
|
error: "flag-conflict",
|
|
@@ -173,7 +117,6 @@ export function buildLytInitCommand() {
|
|
|
173
117
|
process.exitCode = 2;
|
|
174
118
|
return;
|
|
175
119
|
}
|
|
176
|
-
// --custom requires a TTY (incl. JSON mode where prompts make no sense).
|
|
177
120
|
if (opts.custom === true && (process.stdin.isTTY !== true || opts.json === true)) {
|
|
178
121
|
emitError(opts.json === true, {
|
|
179
122
|
error: "custom-requires-tty",
|
|
@@ -183,8 +126,6 @@ export function buildLytInitCommand() {
|
|
|
183
126
|
return;
|
|
184
127
|
}
|
|
185
128
|
const mode = opts.discover === true ? "discover" : opts.custom === true ? "custom" : "auto";
|
|
186
|
-
// --custom three-prompt walkthrough (per OD-6 default + federation-
|
|
187
|
-
// design §5:228-234; main vault name SKIPPED per naming-convention).
|
|
188
129
|
let customOverrides;
|
|
189
130
|
if (mode === "custom") {
|
|
190
131
|
try {
|
|
@@ -200,46 +141,17 @@ export function buildLytInitCommand() {
|
|
|
200
141
|
return;
|
|
201
142
|
}
|
|
202
143
|
}
|
|
203
|
-
// v1.GP F7-followup — phase-spanning init spinner. Drive a persistent
|
|
204
|
-
// spinner across the WHOLE bootstrap so the surrounding sync work (mesh
|
|
205
|
-
// forge, vault scaffold, libSQL writes, git init, pod.yon write)
|
|
206
|
-
// no longer runs with a dead/frozen indicator. Active only for the
|
|
207
|
-
// human-output FRESH/auto path: --json stays escape-code-free + the
|
|
208
|
-
// discovery/re-init branches are fast read-only paths with no silent
|
|
209
|
-
// gap to cover. The spinner's onPhase re-labels + yields to the event
|
|
210
|
-
// loop at each boundary; per-op gh/git spinners deep in the flow defer
|
|
211
|
-
// to it (single-spinner invariant in util/spinner.ts). Cursor restored
|
|
212
|
-
// on stop() AND on throw via the finally below.
|
|
213
144
|
const useSpinner = opts.json !== true && mode !== "discover";
|
|
214
145
|
const spinner = useSpinner ? startSpinner() : undefined;
|
|
215
146
|
const flowArgs = {
|
|
216
147
|
mode,
|
|
217
|
-
// W1.2 / OD-4 — heal on every `lyt init` bootstrap. The flow gates
|
|
218
|
-
// this to the fresh + re-init branches (discovery stays read-only),
|
|
219
|
-
// so a single `lyt init` re-aligns skills + agent manual + patterns.
|
|
220
|
-
// Wired ONLY at the command layer so flow/integration unit tests
|
|
221
|
-
// (which call initBootstrapFlow directly without `heal`) never write
|
|
222
|
-
// to the real ~/.claude / ~/.codex / ~/.agents.
|
|
223
148
|
heal: () => healPod(),
|
|
224
|
-
// Brief B (B.1) — materialize each vault into a publishable LOCAL state
|
|
225
|
-
// (git + initial commit + remote URL) and commit pod.yon, with push +
|
|
226
|
-
// gh-create HELD (push: false). Outward publish is the consented sync
|
|
227
|
-
// engine's job (B.2), triggered by the staged-HIL prompt (B.3). Wired
|
|
228
|
-
// ONLY here so flow/integration tests stay hermetic (no git subprocesses
|
|
229
|
-
// on temp vault dirs).
|
|
230
|
-
// D34 (OD-LOCALFIRST) — in a local-first context (no gh / provisional
|
|
231
|
-
// identity), hold the remote too (setRemote:false): the provisional
|
|
232
|
-
// handle must never land in a vault `origin` URL. Connect re-materializes
|
|
233
|
-
// with the real handle + setRemote:true.
|
|
234
149
|
materializePublish: (db) => materializePodLocal(db, { push: false, setRemote: !isLocalFirstContext() }),
|
|
235
150
|
...(customOverrides !== undefined ? { customOverrides } : {}),
|
|
236
151
|
...(spinner !== undefined
|
|
237
152
|
? {
|
|
238
153
|
onPhase: async (op, label) => {
|
|
239
154
|
spinner.phase(op, label);
|
|
240
|
-
// Yield so the render interval fires AT the boundary — the
|
|
241
|
-
// label + elapsed visibly advance even though frames can't
|
|
242
|
-
// animate inside a single blocking sync call.
|
|
243
155
|
await new Promise((r) => setImmediate(r));
|
|
244
156
|
},
|
|
245
157
|
}
|
|
@@ -247,50 +159,34 @@ export function buildLytInitCommand() {
|
|
|
247
159
|
};
|
|
248
160
|
try {
|
|
249
161
|
const result = await initBootstrapFlow(flowArgs);
|
|
250
|
-
// Stop the spanning spinner BEFORE printing the result/card so its
|
|
251
|
-
// teardown (clear-line + show-cursor) doesn't clobber the output.
|
|
252
162
|
spinner?.stop();
|
|
253
163
|
if (opts.json === true) {
|
|
254
164
|
emitJsonResult(result);
|
|
255
165
|
}
|
|
256
166
|
else {
|
|
257
167
|
emitHumanResult(result);
|
|
258
|
-
// W1.2 — surface the heal summary (skills/manual/patterns realign).
|
|
259
168
|
if (result.heal !== undefined) {
|
|
260
|
-
// eslint-disable-next-line no-console
|
|
261
169
|
console.log(summarizeHeal(result.heal));
|
|
262
170
|
}
|
|
263
|
-
// WS2 — render the pod card at the end of `lyt init --auto` too
|
|
264
|
-
// (previously wizard-only). Fresh branch only; the lyt-pod-map line
|
|
265
|
-
// is omitted because --auto does not generate a pod-map vault.
|
|
266
171
|
if (result.branch === "fresh") {
|
|
267
172
|
emitAutoPodCard(result);
|
|
268
173
|
}
|
|
269
174
|
else if (result.branch === "adopt" && result.adopt !== undefined) {
|
|
270
175
|
emitAdoptPodCard(result);
|
|
271
176
|
}
|
|
272
|
-
// Brief B (B.3) — staged-HIL publish prompt. After the honest
|
|
273
|
-
// (staged) card, ASK whether to publish now (default-Yes per OD-B2).
|
|
274
|
-
// On yes → the consented sync engine pushes pod + vaults. Outward
|
|
275
|
-
// effect ONLY behind this explicit consent.
|
|
276
177
|
await maybePromptAndPublish(result);
|
|
277
178
|
}
|
|
278
|
-
// Re-init with ALL-failed integrity → exit 1 (matches v1.B.2 OD-6
|
|
279
|
-
// skip-and-warn precedent + brief OD-4 default).
|
|
280
179
|
if (result.branch === "re-init" &&
|
|
281
180
|
result.integrityIssues !== undefined &&
|
|
282
181
|
result.integrityIssues.length > 0 &&
|
|
283
182
|
result.integrityIssues.every((i) => i.status !== "ok")) {
|
|
284
183
|
process.exitCode = 1;
|
|
285
184
|
}
|
|
286
|
-
// a review finding — adopt attempted but the clone failed: clean non-zero exit (the
|
|
287
|
-
// actionable error was already rendered by emitHumanResult/emitJsonResult).
|
|
288
185
|
if (result.branch === "adopt" && result.adoptError !== undefined) {
|
|
289
186
|
process.exitCode = 1;
|
|
290
187
|
}
|
|
291
188
|
}
|
|
292
189
|
catch (err) {
|
|
293
|
-
// Restore the cursor + clear the spinner line before the error path.
|
|
294
190
|
spinner?.stop();
|
|
295
191
|
const message = err instanceof Error ? err.message : String(err);
|
|
296
192
|
emitError(opts.json === true, {
|
|
@@ -304,7 +200,6 @@ export function buildLytInitCommand() {
|
|
|
304
200
|
async function runCustomPrompts() {
|
|
305
201
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
306
202
|
try {
|
|
307
|
-
// 1. Mesh name (default 'personal'; validateMeshName-gated).
|
|
308
203
|
let meshName = "personal";
|
|
309
204
|
while (true) {
|
|
310
205
|
const ans = (await rl.question("Mesh name [personal]: ")).trim();
|
|
@@ -316,14 +211,9 @@ async function runCustomPrompts() {
|
|
|
316
211
|
}
|
|
317
212
|
catch (err) {
|
|
318
213
|
const msg = err instanceof Error ? err.message : String(err);
|
|
319
|
-
// eslint-disable-next-line no-console
|
|
320
214
|
console.log(` ! ${msg}`);
|
|
321
|
-
// loop until valid OR user submits empty (defaults to 'personal').
|
|
322
215
|
}
|
|
323
216
|
}
|
|
324
|
-
// 2. Push target (default authenticated GH handle; accept handle or
|
|
325
|
-
// org:name; not structurally validated here — passed through to mesh
|
|
326
|
-
// init which validates).
|
|
327
217
|
let pushDefault;
|
|
328
218
|
try {
|
|
329
219
|
pushDefault = getHandleFromIdentity();
|
|
@@ -336,11 +226,7 @@ async function runCustomPrompts() {
|
|
|
336
226
|
: "Push target (handle or org:name) []: ";
|
|
337
227
|
const pushAns = (await rl.question(pushPrompt)).trim();
|
|
338
228
|
const pushTarget = pushAns.length === 0 ? pushDefault : pushAns;
|
|
339
|
-
// Main vault name SKIPPED per OD-6 + naming-convention §The main vault
|
|
340
|
-
// is locked. Surface the lock as an informational line.
|
|
341
|
-
// eslint-disable-next-line no-console
|
|
342
229
|
console.log("Main vault name: 'main' (locked; cannot be changed)");
|
|
343
|
-
// 3. Starter content (default y).
|
|
344
230
|
const starterAns = (await rl.question("Include starter content? [Y/n]: ")).trim().toLowerCase();
|
|
345
231
|
const starterFigment = starterAns === "" || starterAns === "y" || starterAns === "yes";
|
|
346
232
|
return { meshName, pushTarget, starterFigment };
|
|
@@ -349,17 +235,10 @@ async function runCustomPrompts() {
|
|
|
349
235
|
rl.close();
|
|
350
236
|
}
|
|
351
237
|
}
|
|
352
|
-
// a review finding (release review) — the honest adopt "expected" set: clone FAILURES only.
|
|
353
|
-
// Benign skips (tombstoned / already-registered) are excluded so a clean adopt of
|
|
354
|
-
// a pod that lists tombstoned vaults never reads as a partial restore. Shared by
|
|
355
|
-
// the human + --json emit (one classification, no drift) and unit-tested directly.
|
|
356
238
|
export function adoptCloneFailures(manifestSkipped) {
|
|
357
239
|
return manifestSkipped.filter((s) => s.reason !== "tombstoned" && s.reason !== "already-registered");
|
|
358
240
|
}
|
|
359
|
-
// Exported for the SC5 emit-shape test (Phase-D a review finding — the rendered adopt
|
|
360
|
-
// `--json` contract is pinned directly, not just the pure classifier).
|
|
361
241
|
export function emitJsonResult(res) {
|
|
362
|
-
// Lock 0.3 stable-key-ordered output (discriminated-union per `branch`).
|
|
363
242
|
const stable = {
|
|
364
243
|
branch: res.branch,
|
|
365
244
|
durationMs: res.durationMs,
|
|
@@ -396,9 +275,6 @@ export function emitJsonResult(res) {
|
|
|
396
275
|
alreadyInRegistry: r.alreadyInRegistry,
|
|
397
276
|
}));
|
|
398
277
|
}
|
|
399
|
-
// ADOPT branch (V-A-11). Stable-keyed; `vaultsExpectedFromManifest` is the a review finding
|
|
400
|
-
// honest denominator (excludes tombstoned/already-registered), `skipped` lists
|
|
401
|
-
// only real clone failures, `partialRestore` is the SC8 honesty flag.
|
|
402
278
|
if (res.adopt !== undefined) {
|
|
403
279
|
const a = res.adopt;
|
|
404
280
|
const failures = adoptCloneFailures(a.manifestSkipped);
|
|
@@ -412,17 +288,11 @@ export function emitJsonResult(res) {
|
|
|
412
288
|
firstVaultCreated: a.firstVaultCreated,
|
|
413
289
|
partialRestore: failures.length > 0,
|
|
414
290
|
skipped: failures.map((f) => ({ vaultName: f.vaultName, reason: f.reason })),
|
|
415
|
-
// (reconciledVaultPaths is carried once, at the top level — the cross-branch
|
|
416
|
-
// contract fresh/re-init also use; Phase-D a review finding dropped the nested copy.)
|
|
417
291
|
};
|
|
418
292
|
}
|
|
419
293
|
if (res.adoptError !== undefined) {
|
|
420
294
|
stable["adoptError"] = { reason: res.adoptError.reason };
|
|
421
295
|
}
|
|
422
|
-
// W1.2 release review fix-pass (R1-Minor) — the heal runs its filesystem
|
|
423
|
-
// side-effects under `--json` too; surface its outcome so an automation
|
|
424
|
-
// consumer (e.g. the deferred self-updater) can observe collision/divergent
|
|
425
|
-
// notes the handler is meant to see (D30.3/D30.4).
|
|
426
296
|
if (res.heal !== undefined) {
|
|
427
297
|
stable["heal"] = {
|
|
428
298
|
runtimes: res.heal.runtimes,
|
|
@@ -438,9 +308,6 @@ export function emitJsonResult(res) {
|
|
|
438
308
|
if (res.reconciledVaultPaths !== undefined) {
|
|
439
309
|
stable["reconciledVaultPaths"] = res.reconciledVaultPaths;
|
|
440
310
|
}
|
|
441
|
-
// Brief B (B.3) — surface the staged-vs-published posture. Under --json the
|
|
442
|
-
// run is non-interactive (no publish prompt), so state is always "staged":
|
|
443
|
-
// init materializes locally; publishing is the consented `lyt sync` step.
|
|
444
311
|
if (res.publish !== undefined && !res.publish.skipped) {
|
|
445
312
|
stable["publish"] = {
|
|
446
313
|
state: "staged",
|
|
@@ -448,32 +315,22 @@ export function emitJsonResult(res) {
|
|
|
448
315
|
podCommitted: res.publish.podCommitted,
|
|
449
316
|
};
|
|
450
317
|
}
|
|
451
|
-
// eslint-disable-next-line no-console
|
|
452
318
|
console.log(JSON.stringify(stable, null, 2));
|
|
453
319
|
}
|
|
454
320
|
function emitHumanResult(res) {
|
|
455
321
|
if (res.branch === "fresh") {
|
|
456
|
-
// eslint-disable-next-line no-console
|
|
457
322
|
console.log(`Forged mesh '${res.meshAssignment?.meshName}' + scaffolded main vault.`);
|
|
458
323
|
if (res.federation !== undefined) {
|
|
459
|
-
// WS3 / D25 — bridge pod ↔ federation on first surface in --auto output.
|
|
460
|
-
// Brief C (F3) — honest staged-HIL text: the pod CONTAINER repo was
|
|
461
|
-
// CREATED on GitHub (remoteCreated) per D31 §4 two-tier consent; CONTENT
|
|
462
|
-
// is staged (unpushed) until `lyt sync`. "local-only" was misleading and
|
|
463
|
-
// pointed at the retired `lyt federation rebuild --push` verb.
|
|
464
324
|
const fed = res.federation;
|
|
465
325
|
const podPosture = fed.remoteCreated
|
|
466
326
|
? "created on GitHub · content staged (unpushed) — run `lyt sync` to publish"
|
|
467
327
|
: fed.branch === "adopted"
|
|
468
328
|
? "on GitHub (adopted) · content staged — run `lyt sync` to publish"
|
|
469
329
|
: `${fed.branch} — run \`lyt sync\` to publish`;
|
|
470
|
-
// eslint-disable-next-line no-console
|
|
471
330
|
console.log(` pod (federation): ${fed.remoteFullName} — the identity layer behind your pod (${podPosture})`);
|
|
472
|
-
// eslint-disable-next-line no-console
|
|
473
331
|
console.log(` path: ${res.federation.localPath}`);
|
|
474
332
|
}
|
|
475
333
|
else {
|
|
476
|
-
// eslint-disable-next-line no-console
|
|
477
334
|
console.warn(" pod (federation): skipped (no authenticated handle resolvable)");
|
|
478
335
|
}
|
|
479
336
|
return;
|
|
@@ -482,27 +339,17 @@ function emitHumanResult(res) {
|
|
|
482
339
|
const issues = res.integrityIssues ?? [];
|
|
483
340
|
const ok = issues.filter((i) => i.status === "ok").length;
|
|
484
341
|
const failed = issues.filter((i) => i.status !== "ok");
|
|
485
|
-
// eslint-disable-next-line no-console
|
|
486
342
|
console.log(`Re-init: ${issues.length} vault${issues.length === 1 ? "" : "s"} checked; ${ok} ok, ${failed.length} with issues.`);
|
|
487
343
|
for (const f of failed) {
|
|
488
|
-
// eslint-disable-next-line no-console
|
|
489
344
|
console.warn(` ! ${f.vaultName} [${f.status}] ${f.error ?? ""}`);
|
|
490
345
|
}
|
|
491
346
|
if (failed.length === 0) {
|
|
492
|
-
// eslint-disable-next-line no-console
|
|
493
347
|
console.log(" integrity OK.");
|
|
494
348
|
}
|
|
495
349
|
return;
|
|
496
350
|
}
|
|
497
351
|
if (res.branch === "adopt") {
|
|
498
|
-
// a review finding — adopt failed (pod/vault clone threw). Render an AI-actionable error;
|
|
499
|
-
// the main flow sets a clean non-zero exit. Local state is left re-runnable.
|
|
500
352
|
if (res.adoptError !== undefined) {
|
|
501
|
-
// Phase-D a review finding — do NOT over-promise "registry empty / nothing scaffolded":
|
|
502
|
-
// that holds for the common pod-clone throw but not a rare post-recovery
|
|
503
|
-
// fault that leaves a partial registry. Point at `lyt doctor` for the
|
|
504
|
-
// half-set-up case instead of asserting an unconditional clean state.
|
|
505
|
-
// eslint-disable-next-line no-console
|
|
506
353
|
console.error(`Couldn't finish adopting your pod — ${res.adoptError.reason}\n` +
|
|
507
354
|
` This is usually a network drop or a GitHub-credentials issue on a private repo.\n` +
|
|
508
355
|
` • Check your login: gh auth status\n` +
|
|
@@ -513,49 +360,28 @@ function emitHumanResult(res) {
|
|
|
513
360
|
const a = res.adopt;
|
|
514
361
|
if (a === undefined)
|
|
515
362
|
return;
|
|
516
|
-
// a review finding — honest "expected" denominator EXCLUDES benign skips; only real clone
|
|
517
|
-
// failures count, so a clean adopt of a pod with tombstoned vaults never reads
|
|
518
|
-
// as a partial restore (shared classifier with the --json emit).
|
|
519
363
|
const failures = adoptCloneFailures(a.manifestSkipped);
|
|
520
364
|
const expected = a.vaultsRecoveredFromManifest + failures.length;
|
|
521
|
-
// eslint-disable-next-line no-console
|
|
522
365
|
console.log(`Adopted pod ${a.podHandle}/lyt-pod — restored ${a.vaultsRecoveredFromManifest}/${expected} vault(s); re-indexed ${a.reconciledVaultPaths.length}.`);
|
|
523
366
|
if (a.firstVaultCreated) {
|
|
524
|
-
// eslint-disable-next-line no-console
|
|
525
367
|
console.log(` pod had no vaults to restore — scaffolded ${a.primaryMeshName ?? "personal"}/main.`);
|
|
526
368
|
}
|
|
527
369
|
if (failures.length > 0) {
|
|
528
|
-
// MF4/SC8 — partial restore is LOUD (a 3-of-5 adopt is never reported as success).
|
|
529
|
-
// eslint-disable-next-line no-console
|
|
530
370
|
console.warn(` ! partial restore: ${failures.length} vault(s) did not clone:`);
|
|
531
371
|
for (const f of failures) {
|
|
532
|
-
// eslint-disable-next-line no-console
|
|
533
372
|
console.warn(` - ${f.vaultName}: ${f.reason}`);
|
|
534
373
|
}
|
|
535
|
-
// eslint-disable-next-line no-console
|
|
536
374
|
console.warn(" Re-run `lyt init --auto` to retry the missing vault(s).");
|
|
537
375
|
}
|
|
538
376
|
return;
|
|
539
377
|
}
|
|
540
|
-
// discovery
|
|
541
378
|
const repos = res.discoveredRepos ?? [];
|
|
542
|
-
// eslint-disable-next-line no-console
|
|
543
379
|
console.log(`Discovery: ${repos.length} lyt-* repo${repos.length === 1 ? "" : "s"} found.`);
|
|
544
380
|
for (const r of repos) {
|
|
545
381
|
const marker = r.alreadyInRegistry ? "[in registry]" : "[NEW]";
|
|
546
|
-
// eslint-disable-next-line no-console
|
|
547
382
|
console.log(` ${marker} ${r.fullName} (${r.kind})`);
|
|
548
383
|
}
|
|
549
384
|
}
|
|
550
|
-
// WS2 — render the end-of-init pod card on `lyt init --auto` (FRESH branch).
|
|
551
|
-
// Mirrors the wizard's emitPodCard sourcing but for the bootstrap result:
|
|
552
|
-
// pod repo full name + local path come from the federation chokepoint
|
|
553
|
-
// (res.federation), the mesh + main-vault row from res.meshAssignment. The
|
|
554
|
-
// lyt-pod-map line is OMITTED because `--auto` does not generate a pod-map
|
|
555
|
-
// vault (only the wizard's P11 does) — PodCardData simply leaves
|
|
556
|
-
// podMapVaultPath unset so renderPodCard skips that block. Best-effort: a
|
|
557
|
-
// missing federation (no handle) skips the card (the warn line already
|
|
558
|
-
// printed by emitHumanResult covers that case).
|
|
559
385
|
function emitAutoPodCard(res) {
|
|
560
386
|
const fed = res.federation;
|
|
561
387
|
const mesh = res.meshAssignment;
|
|
@@ -572,28 +398,12 @@ function emitAutoPodCard(res) {
|
|
|
572
398
|
podRepoFullName: fed.remoteFullName,
|
|
573
399
|
podLocalPath: fed.localPath,
|
|
574
400
|
hyperlinksEnabled: process.stdout.isTTY === true,
|
|
575
|
-
// Brief B (B.3) — init materializes LOCALLY (push held); the card is honest
|
|
576
|
-
// that the pod is staged, not published, and points at `lyt sync`. The HIL
|
|
577
|
-
// publish prompt (maybePromptAndPublish) runs after the card.
|
|
578
|
-
// D34 (OD-LOCALFIRST) — a no-gh / provisional pod is "local-only" (NOT
|
|
579
|
-
// connected), a stronger honesty than "staged" (which implies gh is wired).
|
|
580
401
|
publishState: isLocalFirstContext() ? "local-only" : "staged",
|
|
581
402
|
};
|
|
582
|
-
// eslint-disable-next-line no-console
|
|
583
403
|
console.log(renderPodCard(data));
|
|
584
|
-
// Brief C (F4) — `--auto` materializes locally (publishState "staged"), so the
|
|
585
|
-
// Next-steps lead with `lyt sync` whenever the pod isn't yet published.
|
|
586
|
-
// eslint-disable-next-line no-console
|
|
587
404
|
console.log(renderNextSteps({ unpublished: data.publishState !== "published" }));
|
|
588
|
-
// eslint-disable-next-line no-console
|
|
589
405
|
console.log("");
|
|
590
406
|
}
|
|
591
|
-
// V-A-11 — pod card for the ADOPT branch (a fresh machine that cloned an existing
|
|
592
|
-
// pod). Built from res.adopt (the federation/meshAssignment fields stay unset on
|
|
593
|
-
// adopt). publishState "staged": the pod + vaults came FROM GitHub, but the local
|
|
594
|
-
// registration/recovery commits are unpushed (noPush) → Next-steps nudge `lyt sync`
|
|
595
|
-
// to push them. A null primaryVaultPath (no vault recovered AND scaffold failed)
|
|
596
|
-
// skips the card — the human/json line already carried the outcome.
|
|
597
407
|
function emitAdoptPodCard(res) {
|
|
598
408
|
const a = res.adopt;
|
|
599
409
|
if (a === undefined || a.primaryVaultPath === null) {
|
|
@@ -612,34 +422,20 @@ function emitAdoptPodCard(res) {
|
|
|
612
422
|
hyperlinksEnabled: process.stdout.isTTY === true,
|
|
613
423
|
publishState: "staged",
|
|
614
424
|
};
|
|
615
|
-
// eslint-disable-next-line no-console
|
|
616
425
|
console.log(renderPodCard(data));
|
|
617
|
-
// eslint-disable-next-line no-console
|
|
618
426
|
console.log(renderNextSteps({ unpublished: true }));
|
|
619
|
-
// eslint-disable-next-line no-console
|
|
620
427
|
console.log("");
|
|
621
428
|
}
|
|
622
|
-
// Brief B (B.3) — the staged-HIL publish prompt. Runs after the honest card on
|
|
623
|
-
// the fresh + re-init human paths. Default-Yes ([Y/n]) per OD-B2: publishing is
|
|
624
|
-
// the expected end-state and the prompt itself is the explicit consent (no
|
|
625
|
-
// surprise push). On yes → the B.2 reconcile engine (push=true) does the outward
|
|
626
|
-
// gh-create + push, resumable via the outbox. Non-interactive (no TTY) leaves
|
|
627
|
-
// the pod staged + prints the honest `lyt sync` nudge (never blocks on stdin).
|
|
628
429
|
async function maybePromptAndPublish(res) {
|
|
629
430
|
const pub = res.publish;
|
|
630
431
|
if (pub === undefined || pub.skipped)
|
|
631
|
-
return;
|
|
632
|
-
// D34 (OD-LOCALFIRST) — a local-first (no-gh / provisional) pod has no gh to
|
|
633
|
-
// publish to; the publish prompt would fail. Connect is `lyt sync`'s job (the
|
|
634
|
-
// self-heal). Nudge there instead of prompting to publish.
|
|
432
|
+
return;
|
|
635
433
|
if (isLocalFirstContext()) {
|
|
636
|
-
// eslint-disable-next-line no-console
|
|
637
434
|
console.log("\nYour pod is local-only (not connected to GitHub). Run `lyt sync` to connect + back it up.");
|
|
638
435
|
return;
|
|
639
436
|
}
|
|
640
437
|
const vaultCount = pub.vaults.filter((v) => !v.skipped).length;
|
|
641
438
|
if (process.stdin.isTTY !== true) {
|
|
642
|
-
// eslint-disable-next-line no-console
|
|
643
439
|
console.log("\nYour pod is staged locally (not published). Run `lyt sync` to publish it to GitHub.");
|
|
644
440
|
return;
|
|
645
441
|
}
|
|
@@ -649,45 +445,34 @@ async function maybePromptAndPublish(res) {
|
|
|
649
445
|
const ans = (await rl.question(`\nPublish your pod to GitHub now? (pushes your pod + ${vaultCount} vault repo(s)) [Y/n]: `))
|
|
650
446
|
.trim()
|
|
651
447
|
.toLowerCase();
|
|
652
|
-
// OD-B2 default-Yes: empty (Enter) or y/yes → publish.
|
|
653
448
|
yes = ans === "" || ans === "y" || ans === "yes";
|
|
654
449
|
}
|
|
655
450
|
finally {
|
|
656
451
|
rl.close();
|
|
657
452
|
}
|
|
658
453
|
if (!yes) {
|
|
659
|
-
// eslint-disable-next-line no-console
|
|
660
454
|
console.log("Staged. Run `lyt sync` when you're ready to publish to GitHub.");
|
|
661
455
|
return;
|
|
662
456
|
}
|
|
663
|
-
// eslint-disable-next-line no-console
|
|
664
457
|
console.log("Publishing your pod to GitHub…");
|
|
665
458
|
const result = await reconcilePublishFlow({ push: true });
|
|
666
459
|
if (result.skipped) {
|
|
667
|
-
// eslint-disable-next-line no-console
|
|
668
460
|
console.log(`Publish skipped — ${result.reason ?? "no pod"}.`);
|
|
669
461
|
return;
|
|
670
462
|
}
|
|
671
463
|
const pushed = result.vaultOutcomes.filter((o) => o.pushed).length;
|
|
672
464
|
if (result.ok) {
|
|
673
|
-
// eslint-disable-next-line no-console
|
|
674
465
|
console.log(`✓ Published to GitHub — ${pushed} vault repo(s) + your pod.`);
|
|
675
466
|
}
|
|
676
467
|
else {
|
|
677
|
-
// eslint-disable-next-line no-console
|
|
678
468
|
console.log(`⚠ Partial publish — ${pushed} vault(s) pushed; ${result.outboxRemaining} op(s) pending. Re-run \`lyt sync\` to finish (resumable, no data lost).`);
|
|
679
469
|
for (const o of result.vaultOutcomes) {
|
|
680
470
|
if (o.status === "conflict" || o.status === "failed") {
|
|
681
|
-
// eslint-disable-next-line no-console
|
|
682
471
|
console.log(` ${o.status}: ${o.vaultName} — ${o.message}`);
|
|
683
472
|
}
|
|
684
473
|
}
|
|
685
474
|
}
|
|
686
475
|
}
|
|
687
|
-
// Non-interactive defaults handler used by `lyt init --wizard --dry-run`
|
|
688
|
-
// so the smoke test (and CI sanity invocations) never block on stdin.
|
|
689
|
-
// Returns: ask → defaultValue (or "" if absent); confirm → defaultValue
|
|
690
|
-
// (or true); select → first option. Matches the runWizard contract.
|
|
691
476
|
function makeDryRunDefaultsHandler() {
|
|
692
477
|
return {
|
|
693
478
|
async ask(_question, defaultValue) {
|
|
@@ -701,25 +486,10 @@ function makeDryRunDefaultsHandler() {
|
|
|
701
486
|
},
|
|
702
487
|
};
|
|
703
488
|
}
|
|
704
|
-
// D34 (OD-LOCALFIRST) — true when init should stay LOCAL: no gh handle resolves
|
|
705
|
-
// (gh absent/unauthed) OR the cached identity is provisional (a local pod). In
|
|
706
|
-
// both cases the materialize pass holds vault remotes (setRemote:false) so the
|
|
707
|
-
// provisional handle never reaches a remote URL — connect wires the real one.
|
|
708
|
-
//
|
|
709
|
-
// release review fix-pass: read the cache DIRECTLY (no getIdentity / TTL /
|
|
710
|
-
// gh-refresh). A provisional cache → local-first regardless of its age (connect,
|
|
711
|
-
// not a silent init refresh, is where it reconciles); a gh-cli cache →
|
|
712
|
-
// connected. Only when there is NO cache do we probe gh (the genuine
|
|
713
|
-
// connected-fresh-vs-no-gh decision). Mirrors init-bootstrap's local-first
|
|
714
|
-
// trigger so the command + flow agree.
|
|
715
489
|
function isLocalFirstContext() {
|
|
716
|
-
// MF1 — the provisional-cache determination is the shared resolveLocalFirst
|
|
717
|
-
// predicate (kills the triplication: router + doFreshBranch + here). When a
|
|
718
|
-
// cache exists its verdict is authoritative; only a NO-cache machine probes gh.
|
|
719
490
|
const cached = readIdentityCache();
|
|
720
491
|
if (cached !== null)
|
|
721
492
|
return resolveLocalFirst(cached);
|
|
722
|
-
// No cache → local-first iff no gh handle resolves (gh absent/unauthed).
|
|
723
493
|
try {
|
|
724
494
|
getHandleFromIdentity();
|
|
725
495
|
return false;
|
|
@@ -730,11 +500,9 @@ function isLocalFirstContext() {
|
|
|
730
500
|
}
|
|
731
501
|
function emitError(json, body) {
|
|
732
502
|
if (json) {
|
|
733
|
-
// eslint-disable-next-line no-console
|
|
734
503
|
console.error(JSON.stringify(body, null, 2));
|
|
735
504
|
}
|
|
736
505
|
else {
|
|
737
|
-
// eslint-disable-next-line no-console
|
|
738
506
|
console.error(`lyt init: ${String(body["message"] ?? body["error"])}`);
|
|
739
507
|
}
|
|
740
508
|
}
|