@pugi/cli 0.1.0-beta.89 → 0.1.0-beta.90
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/dist/core/edits/dispatch.js +40 -0
- package/dist/core/edits/format-detector.js +260 -0
- package/dist/core/edits/index.js +2 -0
- package/dist/runtime/cli.js +55 -17
- package/dist/runtime/version.js +1 -1
- package/dist/tools/ask-user-question.js +57 -8
- package/dist/tui/ask-user-question-chips.js +58 -0
- package/package.json +2 -2
|
@@ -47,6 +47,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
47
47
|
import { readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
48
48
|
import { resolve, sep } from 'node:path';
|
|
49
49
|
import { commitCheckpoint, formatCheckpointMessage, initShadowRepo, ShadowGitUnavailableError, } from '../checkpoints/shadow-git.js';
|
|
50
|
+
import { detectEditFormat } from './format-detector.js';
|
|
50
51
|
import { LayerDDeferredError, applyLayerD } from './layer-d-ast.js';
|
|
51
52
|
import { applyLayerA } from './layer-a-apply.js';
|
|
52
53
|
import { applyLayerAFuzzy } from './layer-a-fuzzy-apply.js';
|
|
@@ -62,6 +63,15 @@ import { appendEntry, snapshotForDispatch, } from './journal.js';
|
|
|
62
63
|
*/
|
|
63
64
|
export async function dispatchEdit(raw, opts) {
|
|
64
65
|
const family = resolveFamily(opts.modelTag);
|
|
66
|
+
// PUGI-79 — resolve the preferred-layer chain ONCE per dispatch.
|
|
67
|
+
// Caller supplied wins; otherwise the detector derives from modelTag.
|
|
68
|
+
// The chain is informational here (the parser still routes per
|
|
69
|
+
// envelope); a future fuzzy-ladder change can read it to influence
|
|
70
|
+
// retry ordering without revisiting the dispatcher's escalation
|
|
71
|
+
// contract.
|
|
72
|
+
const preferredChain = resolvePreferredChain(opts);
|
|
73
|
+
if (preferredChain)
|
|
74
|
+
opts.onPreferredLayer?.(preferredChain);
|
|
65
75
|
let parsed;
|
|
66
76
|
try {
|
|
67
77
|
parsed = parseMarkers(raw, family);
|
|
@@ -75,6 +85,7 @@ export async function dispatchEdit(raw, opts) {
|
|
|
75
85
|
bytesWritten: 0,
|
|
76
86
|
reason: 'marker_parse_error',
|
|
77
87
|
detail: `${error.message}${error.atLine ? ` (line ${error.atLine})` : ''} — modelHint=${error.modelHint}`,
|
|
88
|
+
...(preferredChain ? { expectedLayer: preferredChain.primary } : {}),
|
|
78
89
|
};
|
|
79
90
|
opts.onResult?.(result);
|
|
80
91
|
return [result];
|
|
@@ -141,6 +152,7 @@ export async function dispatchEdit(raw, opts) {
|
|
|
141
152
|
opts.checkpoint.onCheckpointError?.(err, '');
|
|
142
153
|
}
|
|
143
154
|
}
|
|
155
|
+
const expectedLayer = preferredChain?.primary;
|
|
144
156
|
const out = [];
|
|
145
157
|
let crashError = null;
|
|
146
158
|
for (const edit of parsed) {
|
|
@@ -166,6 +178,13 @@ export async function dispatchEdit(raw, opts) {
|
|
|
166
178
|
detail: `dispatch threw: ${msg}`,
|
|
167
179
|
};
|
|
168
180
|
}
|
|
181
|
+
if (expectedLayer !== undefined) {
|
|
182
|
+
// PUGI-79: stamp the expected layer on every result so
|
|
183
|
+
// observability surfaces can compute drift (expected vs actual)
|
|
184
|
+
// per family. The actual `layer` field is unchanged — the
|
|
185
|
+
// hint is informational, not a routing override.
|
|
186
|
+
result = { ...result, expectedLayer };
|
|
187
|
+
}
|
|
169
188
|
out.push(result);
|
|
170
189
|
opts.onResult?.(result);
|
|
171
190
|
if (result.ok && checkpointEnabled && opts.checkpoint && !opts.dryRun) {
|
|
@@ -339,6 +358,27 @@ function listTrackedFiles(cwd, paths) {
|
|
|
339
358
|
}
|
|
340
359
|
return out;
|
|
341
360
|
}
|
|
361
|
+
/**
|
|
362
|
+
* PUGI-79 helper — resolve the preferred-layer chain for this
|
|
363
|
+
* dispatch. Caller-supplied `preferredLayer` wins; otherwise the
|
|
364
|
+
* detector derives from `modelTag`. Returns null when neither path
|
|
365
|
+
* yields a chain (no caller hint AND no model tag) — the dispatcher
|
|
366
|
+
* skips the observability hook in that case so we do not emit a
|
|
367
|
+
* misleading wildcard reading.
|
|
368
|
+
*/
|
|
369
|
+
function resolvePreferredChain(opts) {
|
|
370
|
+
if (opts.preferredLayer) {
|
|
371
|
+
// Defensive clone so caller mutations after dispatchEdit returns
|
|
372
|
+
// cannot poison subsequent calls that share the chain reference.
|
|
373
|
+
return {
|
|
374
|
+
primary: opts.preferredLayer.primary,
|
|
375
|
+
fallback: [...opts.preferredLayer.fallback],
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
if (opts.modelTag)
|
|
379
|
+
return detectEditFormat(opts.modelTag);
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
342
382
|
/**
|
|
343
383
|
* Public helper exposed for the marker parser tests + CLI surface that
|
|
344
384
|
* may want to know the resolved family without re-running the auto
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edit-format auto-select (PUGI-79).
|
|
3
|
+
*
|
|
4
|
+
* The 4-layer diff dispatcher (Layer A single-block, B ordered batch,
|
|
5
|
+
* C sha256-gated rewrite, D AST-aware) is layer-agnostic at the wire
|
|
6
|
+
* — the model emits whichever marker family it was prompted with, and
|
|
7
|
+
* `dispatch.ts` routes per envelope. Empirically though, different
|
|
8
|
+
* model families have markedly different success rates per layer:
|
|
9
|
+
*
|
|
10
|
+
* - Anthropic Claude family — native function-calling shape;
|
|
11
|
+
* produces clean single-block Layer A edits with high recall
|
|
12
|
+
* against `+++ NEW / --- OLD / ===` markers.
|
|
13
|
+
* - Open-weight Qwen3 / Kimi / DeepSeek — historically stronger
|
|
14
|
+
* with whole-file Layer C rewrites OR sha256-gated patches; the
|
|
15
|
+
* conflict-marker (Gemini-style) Layer A surface has higher
|
|
16
|
+
* ambiguity rates on these models.
|
|
17
|
+
* - DeepSeek-coder family — anchors well on Layer C primary with
|
|
18
|
+
* Layer A as fallback (their RLHF set leaned hard on patch-style
|
|
19
|
+
* emit).
|
|
20
|
+
* - Reasoning models (o-series, gpt-5) — handle Layer D AST-aware
|
|
21
|
+
* operations when an LSP bridge is available; Layer C primary
|
|
22
|
+
* otherwise.
|
|
23
|
+
*
|
|
24
|
+
* The existing `format-matrix.ts` keyed exact slugs only — fine for
|
|
25
|
+
* the canonical set but blind to vendor-prefixed slugs that come back
|
|
26
|
+
* from the Anvil gateway (e.g. `anthropic/claude-sonnet-4-20250514`,
|
|
27
|
+
* `qwen/qwen3-coder`, `deepseek/deepseek-chat-v3.1`). This detector
|
|
28
|
+
* layers ON TOP of the matrix:
|
|
29
|
+
*
|
|
30
|
+
* 1. exact-key match against `EDIT_FORMAT_MATRIX` — wins outright.
|
|
31
|
+
* 2. vendor-prefix strip → second exact-key probe (so
|
|
32
|
+
* `anthropic/claude-sonnet-4-6` resolves the same as `claude-sonnet-4-6`).
|
|
33
|
+
* 3. family inference from the (possibly prefixed) slug — returns
|
|
34
|
+
* the family-default chain (see `FAMILY_DEFAULTS`).
|
|
35
|
+
* 4. unknown — wildcard `*` from the matrix.
|
|
36
|
+
*
|
|
37
|
+
* The output is the same `EditFormatChain` shape the matrix already
|
|
38
|
+
* exposes so callers (dispatcher hint, persona prompt hint) consume a
|
|
39
|
+
* single contract.
|
|
40
|
+
*
|
|
41
|
+
* NOT a routing decision: the dispatcher's per-layer routing is still
|
|
42
|
+
* driven by what the model actually emits. The detector's output is a
|
|
43
|
+
* HINT — surfaced to the persona prompt so the model has the right
|
|
44
|
+
* marker template loaded, and surfaced to the dispatcher's fuzzy ladder
|
|
45
|
+
* / fallback ordering so an ambiguous response gets retried in an order
|
|
46
|
+
* that matches the model's empirical strengths.
|
|
47
|
+
*
|
|
48
|
+
* Spec: this is the PUGI-79 implementation; the issue ships the
|
|
49
|
+
* detector + dispatcher hint + persona hint together.
|
|
50
|
+
*/
|
|
51
|
+
import { EDIT_FORMAT_MATRIX, } from './format-matrix.js';
|
|
52
|
+
/**
|
|
53
|
+
* Family-level default chains. These are deliberately CONSERVATIVE —
|
|
54
|
+
* each family's primary is the layer with the highest observed apply-
|
|
55
|
+
* rate across the 2026-05 dogfood corpus; fallbacks rank in observed
|
|
56
|
+
* success order. The exact-slug entries in `EDIT_FORMAT_MATRIX` can
|
|
57
|
+
* override per-model; family defaults are the floor.
|
|
58
|
+
*
|
|
59
|
+
* Anthropic:
|
|
60
|
+
* The Anvil corpus shows Claude family hitting ~95% Layer A apply on
|
|
61
|
+
* first try; Layer C is the rescue when the file is large enough
|
|
62
|
+
* that the operator's region selection ambiguates. Matrix entries
|
|
63
|
+
* for `claude-opus-4-7` + `claude-sonnet-4-6` override to Layer C
|
|
64
|
+
* primary because the larger context windows let those models hold
|
|
65
|
+
* the whole file comfortably.
|
|
66
|
+
*
|
|
67
|
+
* Open-weight (qwen / deepseek / kimi):
|
|
68
|
+
* These ship whole-file rewrites more reliably than partial diffs —
|
|
69
|
+
* the patch-style envelopes their fine-tune sets prefer match Layer
|
|
70
|
+
* C semantics. Layer A is the fallback because they can still emit
|
|
71
|
+
* sensible search/replace pairs when prompted explicitly.
|
|
72
|
+
*
|
|
73
|
+
* Reasoning (gpt-5 / o-series):
|
|
74
|
+
* Layer C primary because the longer reasoning trace burns through
|
|
75
|
+
* the partial-diff format's positional constraints. Layer D rides
|
|
76
|
+
* the wire when an LSP is bridged; the matrix entries override per-
|
|
77
|
+
* model when that's available (see `qwen3-coder-480b → D`).
|
|
78
|
+
*
|
|
79
|
+
* Unknown:
|
|
80
|
+
* Defers to the wildcard chain from the matrix (Layer A primary).
|
|
81
|
+
*/
|
|
82
|
+
const FAMILY_DEFAULTS = {
|
|
83
|
+
anthropic: { primary: 'A', fallback: ['C', 'B'] },
|
|
84
|
+
openai: { primary: 'C', fallback: ['A', 'B'] },
|
|
85
|
+
gemini: { primary: 'A', fallback: ['C', 'B'] },
|
|
86
|
+
qwen: { primary: 'A', fallback: ['C', 'B'] },
|
|
87
|
+
deepseek: { primary: 'C', fallback: ['A', 'B'] },
|
|
88
|
+
kimi: { primary: 'C', fallback: ['A', 'B'] },
|
|
89
|
+
unknown: { primary: 'A', fallback: ['B', 'C'] },
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Detect the model family from a slug. Handles vendor-prefixed forms
|
|
93
|
+
* (`<vendor>/<model>` and `<provider>:<model>`) plus bare-name forms.
|
|
94
|
+
* Case-insensitive on the family name; case-preserving on the rest.
|
|
95
|
+
*
|
|
96
|
+
* Order matters: vendor prefix wins when present (the gateway is
|
|
97
|
+
* authoritative about which family routed). Otherwise the bare slug
|
|
98
|
+
* gets prefix-matched against well-known family stems. Unknown slugs
|
|
99
|
+
* surface as `unknown` — the caller falls back to the matrix wildcard.
|
|
100
|
+
*/
|
|
101
|
+
export function detectModelFamily(modelSlug) {
|
|
102
|
+
if (!modelSlug)
|
|
103
|
+
return 'unknown';
|
|
104
|
+
const raw = modelSlug.trim();
|
|
105
|
+
if (raw.length === 0)
|
|
106
|
+
return 'unknown';
|
|
107
|
+
const lower = raw.toLowerCase();
|
|
108
|
+
// Vendor-prefix form: `<vendor>/<model>`. The vendor wins outright
|
|
109
|
+
// because the gateway is authoritative — `anthropic/<anything>`
|
|
110
|
+
// routes Claude family regardless of the suffix.
|
|
111
|
+
const slashIdx = lower.indexOf('/');
|
|
112
|
+
if (slashIdx > 0) {
|
|
113
|
+
const vendor = lower.slice(0, slashIdx);
|
|
114
|
+
switch (vendor) {
|
|
115
|
+
case 'anthropic':
|
|
116
|
+
return 'anthropic';
|
|
117
|
+
case 'openai':
|
|
118
|
+
return 'openai';
|
|
119
|
+
case 'google':
|
|
120
|
+
case 'gemini':
|
|
121
|
+
case 'xai':
|
|
122
|
+
return 'gemini';
|
|
123
|
+
case 'qwen':
|
|
124
|
+
case 'alibaba':
|
|
125
|
+
return 'qwen';
|
|
126
|
+
case 'deepseek':
|
|
127
|
+
return 'deepseek';
|
|
128
|
+
case 'moonshot':
|
|
129
|
+
case 'kimi':
|
|
130
|
+
return 'kimi';
|
|
131
|
+
default:
|
|
132
|
+
// Unknown vendor — fall through to bare-name detection on the
|
|
133
|
+
// suffix. Some gateways (OpenRouter style) put the family
|
|
134
|
+
// name AFTER the vendor (e.g. `together/qwen-coder`), so the
|
|
135
|
+
// suffix still carries signal.
|
|
136
|
+
return detectFamilyFromBareName(lower.slice(slashIdx + 1));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Provider-prefix form: `ollama:<model>` (engine-VM local runtime).
|
|
140
|
+
// The prefix is informational; strip and continue.
|
|
141
|
+
const colonIdx = lower.indexOf(':');
|
|
142
|
+
if (colonIdx > 0) {
|
|
143
|
+
const prefix = lower.slice(0, colonIdx);
|
|
144
|
+
if (prefix === 'ollama' || prefix === 'lmstudio' || prefix === 'llama-cpp') {
|
|
145
|
+
return detectFamilyFromBareName(lower.slice(colonIdx + 1));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return detectFamilyFromBareName(lower);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Bare-name detector. Lower-case input. Prefix-match against family
|
|
152
|
+
* stems; longest match wins so `claude-opus-4-7` doesn't accidentally
|
|
153
|
+
* route through the generic `c*` lane.
|
|
154
|
+
*/
|
|
155
|
+
function detectFamilyFromBareName(name) {
|
|
156
|
+
if (name.startsWith('claude'))
|
|
157
|
+
return 'anthropic';
|
|
158
|
+
if (name.startsWith('gpt') || name.startsWith('o1') || name.startsWith('o3') || name.startsWith('o4')) {
|
|
159
|
+
return 'openai';
|
|
160
|
+
}
|
|
161
|
+
if (name.startsWith('gemini') || name.startsWith('grok'))
|
|
162
|
+
return 'gemini';
|
|
163
|
+
if (name.startsWith('qwen'))
|
|
164
|
+
return 'qwen';
|
|
165
|
+
if (name.startsWith('deepseek'))
|
|
166
|
+
return 'deepseek';
|
|
167
|
+
if (name.startsWith('kimi') || name.startsWith('moonshot'))
|
|
168
|
+
return 'kimi';
|
|
169
|
+
return 'unknown';
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Family-default chain. Exported for callers that want the family-
|
|
173
|
+
* level recommendation without running the full detector pipeline
|
|
174
|
+
* (e.g. observability surfaces that already resolved the family).
|
|
175
|
+
*/
|
|
176
|
+
export function familyDefaultChain(family) {
|
|
177
|
+
// Defensive clone: the FAMILY_DEFAULTS table is readonly conceptually
|
|
178
|
+
// but the EditFormatChain type allows fallback array mutation. Clone
|
|
179
|
+
// so callers cannot accidentally mutate the table.
|
|
180
|
+
const base = FAMILY_DEFAULTS[family];
|
|
181
|
+
return { primary: base.primary, fallback: [...base.fallback] };
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Resolve the preferred edit-format chain for a model slug.
|
|
185
|
+
*
|
|
186
|
+
* Resolution order:
|
|
187
|
+
* 1. Exact slug match in EDIT_FORMAT_MATRIX — overrides everything.
|
|
188
|
+
* This lets per-model tuning (e.g. `qwen3-coder-480b → D`) win
|
|
189
|
+
* over the family default.
|
|
190
|
+
* 2. Vendor-prefix strip + exact match — so `anthropic/claude-...`
|
|
191
|
+
* resolves to the bare `claude-...` entry when present.
|
|
192
|
+
* 3. Family inference — `FAMILY_DEFAULTS[detectModelFamily(slug)]`.
|
|
193
|
+
* 4. Wildcard fallback — `EDIT_FORMAT_MATRIX['*']`.
|
|
194
|
+
*
|
|
195
|
+
* The returned chain is always a fresh object so caller mutations
|
|
196
|
+
* cannot leak back into the matrix.
|
|
197
|
+
*/
|
|
198
|
+
export function detectEditFormat(modelSlug) {
|
|
199
|
+
const fallbackWildcard = EDIT_FORMAT_MATRIX['*'] ?? { primary: 'A', fallback: [] };
|
|
200
|
+
if (!modelSlug || modelSlug.trim().length === 0) {
|
|
201
|
+
return { primary: fallbackWildcard.primary, fallback: [...fallbackWildcard.fallback] };
|
|
202
|
+
}
|
|
203
|
+
const raw = modelSlug.trim();
|
|
204
|
+
// Step 1 — exact match wins.
|
|
205
|
+
const exact = EDIT_FORMAT_MATRIX[raw];
|
|
206
|
+
if (exact)
|
|
207
|
+
return { primary: exact.primary, fallback: [...exact.fallback] };
|
|
208
|
+
// Step 2 — vendor-prefix strip (e.g. `anthropic/claude-sonnet-4-6`).
|
|
209
|
+
const slashIdx = raw.indexOf('/');
|
|
210
|
+
if (slashIdx > 0) {
|
|
211
|
+
const suffix = raw.slice(slashIdx + 1);
|
|
212
|
+
const stripped = EDIT_FORMAT_MATRIX[suffix];
|
|
213
|
+
if (stripped)
|
|
214
|
+
return { primary: stripped.primary, fallback: [...stripped.fallback] };
|
|
215
|
+
}
|
|
216
|
+
// Step 3 — family inference.
|
|
217
|
+
const family = detectModelFamily(raw);
|
|
218
|
+
if (family !== 'unknown')
|
|
219
|
+
return familyDefaultChain(family);
|
|
220
|
+
// Step 4 — wildcard.
|
|
221
|
+
return { primary: fallbackWildcard.primary, fallback: [...fallbackWildcard.fallback] };
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Render a one-line marker-format hint for the persona prompt. The
|
|
225
|
+
* dispatcher already accepts every marker family (anthropic / gemini /
|
|
226
|
+
* openai), so the hint is preference, not gating — surface the
|
|
227
|
+
* preferred template + alternative so the model knows which envelope
|
|
228
|
+
* has the highest apply-rate for its family.
|
|
229
|
+
*
|
|
230
|
+
* Returns `null` when the slug is unknown / unset; the caller drops
|
|
231
|
+
* the hint module on null so the prompt is not polluted with empty
|
|
232
|
+
* sections.
|
|
233
|
+
*/
|
|
234
|
+
export function renderEditFormatHint(modelSlug) {
|
|
235
|
+
if (!modelSlug || modelSlug.trim().length === 0)
|
|
236
|
+
return null;
|
|
237
|
+
const family = detectModelFamily(modelSlug);
|
|
238
|
+
if (family === 'unknown')
|
|
239
|
+
return null;
|
|
240
|
+
const chain = detectEditFormat(modelSlug);
|
|
241
|
+
const markerStyle = FAMILY_MARKER_TEMPLATE[family];
|
|
242
|
+
return `# Edit-format hint (auto-selected for ${family})
|
|
243
|
+
Preferred layer: Layer ${chain.primary}${chain.fallback.length > 0 ? ` (fallback: ${chain.fallback.map((l) => `Layer ${l}`).join(' -> ')})` : ''}.
|
|
244
|
+
Marker template: ${markerStyle}
|
|
245
|
+
The dispatcher accepts any marker family; this hint optimises apply-rate for the resolved model.`;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Marker template literal per family. Mirrors the three envelopes the
|
|
249
|
+
* marker-parser accepts; used by `renderEditFormatHint` so the model
|
|
250
|
+
* sees a concrete shape next to the layer recommendation.
|
|
251
|
+
*/
|
|
252
|
+
const FAMILY_MARKER_TEMPLATE = {
|
|
253
|
+
anthropic: '+++ NEW <file> / <new contents> / --- OLD <file> / <old contents> / ===',
|
|
254
|
+
openai: '@@@ REWRITE <file> sha256=<hex> / <full new contents> / @@@ END',
|
|
255
|
+
gemini: '<<<<<<< SEARCH <file> / <old> / ======= / <new> / >>>>>>> REPLACE',
|
|
256
|
+
qwen: '<<<<<<< SEARCH <file> / <old> / ======= / <new> / >>>>>>> REPLACE',
|
|
257
|
+
deepseek: '@@@ REWRITE <file> sha256=<hex> / <full new contents> / @@@ END',
|
|
258
|
+
kimi: '@@@ REWRITE <file> sha256=<hex> / <full new contents> / @@@ END',
|
|
259
|
+
};
|
|
260
|
+
//# sourceMappingURL=format-detector.js.map
|
package/dist/core/edits/index.js
CHANGED
|
@@ -14,4 +14,6 @@ export { applyLayerC, sha256OfUtf8, } from './layer-c-apply.js';
|
|
|
14
14
|
export { applyLayerD, LayerDDeferredError, } from './layer-d-ast.js';
|
|
15
15
|
export { MarkerParseError, detectFamily, parseMarkers, } from './marker-parser.js';
|
|
16
16
|
export { dispatchEdit, resolveFamily, } from './dispatch.js';
|
|
17
|
+
export { EDIT_FORMAT_MATRIX, pickEditFormat, } from './format-matrix.js';
|
|
18
|
+
export { detectEditFormat, detectModelFamily, familyDefaultChain, renderEditFormatHint, } from './format-detector.js';
|
|
17
19
|
//# sourceMappingURL=index.js.map
|
package/dist/runtime/cli.js
CHANGED
|
@@ -1801,6 +1801,11 @@ function parseArgs(argv) {
|
|
|
1801
1801
|
? process.env.PUGI_HIDE_TOOL_STREAM === '1'
|
|
1802
1802
|
: true,
|
|
1803
1803
|
noDefaults: process.env.PUGI_INIT_NO_DEFAULTS === '1',
|
|
1804
|
+
// #82 — opt-out for the codebase scan + PUGI.md auto-gen step that
|
|
1805
|
+
// runs as part of `pugi init`. Default OFF (scan runs); env var
|
|
1806
|
+
// PUGI_INIT_NO_SCAN=1 lets wrappers force-disable globally without
|
|
1807
|
+
// editing every invocation.
|
|
1808
|
+
noScan: process.env.PUGI_INIT_NO_SCAN === '1',
|
|
1804
1809
|
// UX : auto-init / auto-login opt-outs. Default
|
|
1805
1810
|
// OFF (auto-init + auto-login are on by default on an interactive
|
|
1806
1811
|
// TTY). PUGI_NO_AUTO_* env vars provide a per-shell escape hatch
|
|
@@ -1930,6 +1935,19 @@ function parseArgs(argv) {
|
|
|
1930
1935
|
// at the global level for consistency with --no-splash / --no-tool-stream.
|
|
1931
1936
|
flags.noDefaults = true;
|
|
1932
1937
|
}
|
|
1938
|
+
else if (arg === '--no-scan') {
|
|
1939
|
+
// #82 init-only flag: skip the codebase scan + PUGI.md auto-gen.
|
|
1940
|
+
// Parsed globally for symmetry with the other init opt-outs.
|
|
1941
|
+
flags.noScan = true;
|
|
1942
|
+
}
|
|
1943
|
+
else if (arg === '--scan') {
|
|
1944
|
+
// #82 init-only flag: explicit opt-in for the codebase scan.
|
|
1945
|
+
// Scan is on by default, so this is a no-op alias kept for
|
|
1946
|
+
// documentation / discoverability via `--help`. The flip exists
|
|
1947
|
+
// so a wrapper that previously exported PUGI_INIT_NO_SCAN=1 can
|
|
1948
|
+
// re-enable scanning per-invocation without unsetting the env.
|
|
1949
|
+
flags.noScan = false;
|
|
1950
|
+
}
|
|
1933
1951
|
else if (arg === '--live') {
|
|
1934
1952
|
flags.live = true;
|
|
1935
1953
|
}
|
|
@@ -2285,13 +2303,20 @@ const COMMAND_HELP_BODIES = {
|
|
|
2285
2303
|
'pugi init — bootstrap a new Pugi workspace in the current directory.',
|
|
2286
2304
|
'',
|
|
2287
2305
|
'Creates .pugi/{PUGI.md, mcp.json, index.json, artifacts/, sessions/} and',
|
|
2288
|
-
'seeds the 6 default skills.
|
|
2306
|
+
'seeds the 6 default skills. After scaffolding, scans the codebase and',
|
|
2307
|
+
'auto-generates a project-aware PUGI.md (stack, frameworks, commands,',
|
|
2308
|
+
'layout). Idempotent — running again only fills gaps.',
|
|
2289
2309
|
'',
|
|
2290
2310
|
'Flags:',
|
|
2291
2311
|
' --no-defaults Skip the bundled default-skills install.',
|
|
2312
|
+
' --scan Run the codebase scan + PUGI.md auto-gen (default).',
|
|
2313
|
+
' --no-scan Skip the codebase scan + PUGI.md auto-gen.',
|
|
2314
|
+
' --force Overwrite an existing PUGI.md when scanning.',
|
|
2315
|
+
' --style=<tier> PUGI.md verbosity: minimal | standard | detailed.',
|
|
2292
2316
|
'',
|
|
2293
2317
|
'Env:',
|
|
2294
2318
|
' PUGI_INIT_NO_DEFAULTS=1 Same as --no-defaults.',
|
|
2319
|
+
' PUGI_INIT_NO_SCAN=1 Same as --no-scan.',
|
|
2295
2320
|
],
|
|
2296
2321
|
explain: [
|
|
2297
2322
|
'pugi explain "<question>" — read-only Q&A about the workspace.',
|
|
@@ -2710,6 +2735,8 @@ async function help(args, flags, _session) {
|
|
|
2710
2735
|
' Pairs with PUGI_HIDE_TOOL_STREAM=1.',
|
|
2711
2736
|
' --no-defaults Skip bundled default-skills install on',
|
|
2712
2737
|
' `pugi init`. Pairs with PUGI_INIT_NO_DEFAULTS=1.',
|
|
2738
|
+
' --no-scan Skip codebase scan + PUGI.md auto-gen on',
|
|
2739
|
+
' `pugi init`. Pairs with PUGI_INIT_NO_SCAN=1.',
|
|
2713
2740
|
' --bare Disable project auto-discovery — no PUGI.md /',
|
|
2714
2741
|
' AGENTS.md / CLAUDE.md / GEMINI.md walk-up, no',
|
|
2715
2742
|
' auto-init of .pugi/, no persona auto-load.',
|
|
@@ -3170,7 +3197,10 @@ async function init(args, flags, _session) {
|
|
|
3170
3197
|
// auto-generate PUGI.md. The runner writes its own envelope/output so
|
|
3171
3198
|
// the standalone summary stays compact. `--json` mode collects the
|
|
3172
3199
|
// envelope and merges it into the init payload instead of double-emitting.
|
|
3173
|
-
|
|
3200
|
+
// The scan is opt-out via `--no-scan` / PUGI_INIT_NO_SCAN=1 — keeps
|
|
3201
|
+
// the scaffold half of `pugi init` working in CI fixtures that should
|
|
3202
|
+
// not write a PUGI.md sibling to the workspace root.
|
|
3203
|
+
const initScanArgs = args.filter((a) => a !== '--no-defaults' && a !== '--no-scan' && a !== '--scan');
|
|
3174
3204
|
const style = parseInitStyle(initScanArgs);
|
|
3175
3205
|
const useYes = args.includes('--yes') || args.includes('-y') || flags.json;
|
|
3176
3206
|
const force = args.includes('--force');
|
|
@@ -3185,22 +3215,30 @@ async function init(args, flags, _session) {
|
|
|
3185
3215
|
scanLines.push(` ${line}`);
|
|
3186
3216
|
}
|
|
3187
3217
|
};
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
yes: useYes,
|
|
3194
|
-
json: flags.json,
|
|
3195
|
-
writeOutput: captureWriteOutput,
|
|
3196
|
-
});
|
|
3218
|
+
if (flags.noScan) {
|
|
3219
|
+
if (!flags.json) {
|
|
3220
|
+
scanLines.push('');
|
|
3221
|
+
scanLines.push('PUGI.md scan: skipped (--no-scan)');
|
|
3222
|
+
}
|
|
3197
3223
|
}
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3224
|
+
else {
|
|
3225
|
+
try {
|
|
3226
|
+
scanEnvelope = await runInitScanCommand(initScanArgs, {
|
|
3227
|
+
cwd,
|
|
3228
|
+
force,
|
|
3229
|
+
style,
|
|
3230
|
+
yes: useYes,
|
|
3231
|
+
json: flags.json,
|
|
3232
|
+
writeOutput: captureWriteOutput,
|
|
3233
|
+
});
|
|
3234
|
+
}
|
|
3235
|
+
catch (error) {
|
|
3236
|
+
// Init scan failure must NOT break the scaffold step. Surface a
|
|
3237
|
+
// single warning line so the operator can re-run `pugi init` later.
|
|
3238
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3239
|
+
scanLines.push('');
|
|
3240
|
+
scanLines.push(`PUGI.md scan failed: ${message}`);
|
|
3241
|
+
}
|
|
3204
3242
|
}
|
|
3205
3243
|
writeOutput(flags, { ...result, codegraph: codegraphLines.envelope, pugiMd: scanEnvelope }, [
|
|
3206
3244
|
'Pugi initialized',
|
package/dist/runtime/version.js
CHANGED
|
@@ -44,7 +44,7 @@ export function sanitizeSemver(raw) {
|
|
|
44
44
|
* during import). When bumping the CLI version BOTH literals must be
|
|
45
45
|
* updated; the release smoke-test (`pack:smoke`) verifies they agree.
|
|
46
46
|
*/
|
|
47
|
-
export const PUGI_CLI_VERSION = sanitizeSemver('0.1.0-beta.
|
|
47
|
+
export const PUGI_CLI_VERSION = sanitizeSemver('0.1.0-beta.90');
|
|
48
48
|
/**
|
|
49
49
|
* Outbound: the CLI's installed semver. Read at request time by
|
|
50
50
|
* `version-interceptor.ts` and injected on every `fetch` call.
|
|
@@ -48,6 +48,23 @@ export const ASK_USER_QUESTION_OPTION_DESC_MAX = 200;
|
|
|
48
48
|
/** Option count: 2-4 strict. UI adds "Other" automatically. */
|
|
49
49
|
export const ASK_USER_QUESTION_OPTIONS_MIN = 2;
|
|
50
50
|
export const ASK_USER_QUESTION_OPTIONS_MAX = 4;
|
|
51
|
+
/**
|
|
52
|
+
* Preview pane cap (PUGI-130). Lets the model attach a multi-line
|
|
53
|
+
* ASCII / code / diagram preview to an option. When ANY option в a
|
|
54
|
+
* single-select question carries `preview`, the UI switches к
|
|
55
|
+
* side-by-side layout (options column left, preview pane right).
|
|
56
|
+
*
|
|
57
|
+
* Capped at 5000 chars так что a single payload can stash a full
|
|
58
|
+
* component sketch / SQL migration / diagram, but не enough rope для
|
|
59
|
+
* a model to dump the entire codebase в a preview field.
|
|
60
|
+
*
|
|
61
|
+
* **multiSelect rule:** previews are REJECTED on multi-select questions.
|
|
62
|
+
* Rendering N preview panes simultaneously breaks the layout invariant
|
|
63
|
+
* (single 80-col terminal can host ONE side panel, not N). The schema
|
|
64
|
+
* validator enforces this at the tool boundary с a clear error message.
|
|
65
|
+
*/
|
|
66
|
+
export const ASK_USER_QUESTION_OPTION_PREVIEW_MIN = 1;
|
|
67
|
+
export const ASK_USER_QUESTION_OPTION_PREVIEW_MAX = 5000;
|
|
51
68
|
/** PUGI-480 short-format chip rules: ≤ 5 words / option label, ≤ 3 questions / call. */
|
|
52
69
|
export const ASK_USER_QUESTION_CHIP_LABEL_WORD_MAX = 5;
|
|
53
70
|
export const ASK_USER_QUESTION_CHIPS_MAX = 3;
|
|
@@ -83,15 +100,18 @@ export const askUserQuestionOptionSchema = z.strictObject({
|
|
|
83
100
|
.describe('What this option means / implications.'),
|
|
84
101
|
preview: z
|
|
85
102
|
.string()
|
|
86
|
-
.min(
|
|
87
|
-
.max(
|
|
103
|
+
.min(ASK_USER_QUESTION_OPTION_PREVIEW_MIN)
|
|
104
|
+
.max(ASK_USER_QUESTION_OPTION_PREVIEW_MAX)
|
|
88
105
|
.optional()
|
|
89
|
-
.describe('Optional ASCII / code preview
|
|
90
|
-
'this field, the UI switches к
|
|
91
|
-
'left, preview pane right).
|
|
92
|
-
'config snippets, diagram
|
|
106
|
+
.describe('Optional ASCII / code preview (≤ 5000 chars, multi-line OK). When ' +
|
|
107
|
+
'ANY option in the set carries this field, the UI switches к ' +
|
|
108
|
+
'side-by-side layout (options column left, preview pane right). ' +
|
|
109
|
+
'Use для visual comparison of mockups, config snippets, diagram ' +
|
|
110
|
+
'variations. REJECTED on multiSelect questions (one pane fits one ' +
|
|
111
|
+
'preview).'),
|
|
93
112
|
});
|
|
94
|
-
export const askUserQuestionSchema = z
|
|
113
|
+
export const askUserQuestionSchema = z
|
|
114
|
+
.strictObject({
|
|
95
115
|
question: z
|
|
96
116
|
.string()
|
|
97
117
|
.min(ASK_USER_QUESTION_MIN)
|
|
@@ -115,6 +135,26 @@ export const askUserQuestionSchema = z.strictObject({
|
|
|
115
135
|
.optional()
|
|
116
136
|
.default(false)
|
|
117
137
|
.describe('Allow multiple selections. Default false.'),
|
|
138
|
+
})
|
|
139
|
+
.superRefine((payload, ctx) => {
|
|
140
|
+
// PUGI-130 invariant: previews are single-pane only. Side-by-side
|
|
141
|
+
// layout can host ONE preview at a time; multiSelect implies the
|
|
142
|
+
// operator may toggle several options and each would want its own
|
|
143
|
+
// pane. Reject at the schema boundary so the model gets immediate
|
|
144
|
+
// feedback instead of a silently dropped preview field at render.
|
|
145
|
+
if (payload.multiSelect === true) {
|
|
146
|
+
payload.options.forEach((opt, idx) => {
|
|
147
|
+
if (typeof opt.preview === 'string' && opt.preview.length > 0) {
|
|
148
|
+
ctx.addIssue({
|
|
149
|
+
code: z.ZodIssueCode.custom,
|
|
150
|
+
path: ['options', idx, 'preview'],
|
|
151
|
+
message: 'preview is not allowed on multiSelect questions — ' +
|
|
152
|
+
'side-by-side layout supports ONE preview pane at a time. ' +
|
|
153
|
+
'Drop the preview field OR switch к single-select.',
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
118
158
|
});
|
|
119
159
|
/**
|
|
120
160
|
* PUGI-480 multi-question chip payload — a bundle of up to 3 short-format
|
|
@@ -275,13 +315,22 @@ export const askUserQuestionJsonSchema = {
|
|
|
275
315
|
maxLength: ASK_USER_QUESTION_OPTION_DESC_MAX,
|
|
276
316
|
description: 'What this option means / implications.',
|
|
277
317
|
},
|
|
318
|
+
preview: {
|
|
319
|
+
type: 'string',
|
|
320
|
+
minLength: ASK_USER_QUESTION_OPTION_PREVIEW_MIN,
|
|
321
|
+
maxLength: ASK_USER_QUESTION_OPTION_PREVIEW_MAX,
|
|
322
|
+
description: 'Optional multi-line ASCII / code / diagram preview (≤ 5000 chars). ' +
|
|
323
|
+
'When ANY option carries this, UI switches к side-by-side layout. ' +
|
|
324
|
+
'Single-select only — REJECTED if multiSelect is true.',
|
|
325
|
+
},
|
|
278
326
|
},
|
|
279
327
|
},
|
|
280
328
|
description: '2-4 mutually-exclusive options. NEVER add "Other" — UI auto-adds.',
|
|
281
329
|
},
|
|
282
330
|
multiSelect: {
|
|
283
331
|
type: 'boolean',
|
|
284
|
-
description: 'Allow multiple selections. Default false.'
|
|
332
|
+
description: 'Allow multiple selections. Default false. When true, option.preview ' +
|
|
333
|
+
'is forbidden (one terminal pane fits one preview at a time).',
|
|
285
334
|
},
|
|
286
335
|
},
|
|
287
336
|
};
|
|
@@ -58,6 +58,39 @@ export const ASK_CHIPS_LABEL_WORD_CAP = 5;
|
|
|
58
58
|
export const ASK_CHIPS_LABEL_CHAR_CAP = 22;
|
|
59
59
|
export const ASK_CHIPS_QUESTION_CAP = 3;
|
|
60
60
|
export const ASK_CHIPS_SKIP_LABEL = 'Skip — use defaults';
|
|
61
|
+
/**
|
|
62
|
+
* PUGI-130 preview pane width (columns). Sized for a default 80-col
|
|
63
|
+
* terminal: option column ≈ 32, preview pane ≈ 44 with 4 gutters/borders.
|
|
64
|
+
* The pane wraps long lines defensively but the schema-level 5000-char
|
|
65
|
+
* cap is the primary guard against runaway payloads.
|
|
66
|
+
*/
|
|
67
|
+
export const ASK_CHIPS_PREVIEW_PANE_WIDTH = 44;
|
|
68
|
+
/**
|
|
69
|
+
* Defensive char cap applied at render time. Schema enforces 5000 chars
|
|
70
|
+
* at the tool boundary; renderer mirrors the cap so a legacy / malformed
|
|
71
|
+
* payload sneaking past Zod still cannot exhaust the terminal scrollback.
|
|
72
|
+
*/
|
|
73
|
+
export const ASK_CHIPS_PREVIEW_CHAR_CAP = 5000;
|
|
74
|
+
/**
|
|
75
|
+
* True if ANY option в ANY question of the bundle carries a non-empty
|
|
76
|
+
* `preview` field. The renderer uses this gate to switch к side-by-side
|
|
77
|
+
* layout (vertical option list + preview pane). When false, the legacy
|
|
78
|
+
* horizontal chip layout is preserved (zero regression for callers
|
|
79
|
+
* shipping previewless payloads — PR #851 contract intact).
|
|
80
|
+
*/
|
|
81
|
+
export function hasAnyPreview(questions) {
|
|
82
|
+
return questions.some((q) => q.options.some((opt) => typeof opt.preview === 'string' && opt.preview.length > 0));
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Defensive char cap on preview content. Schema enforces 5000 chars at
|
|
86
|
+
* the tool boundary; we mirror the cap here so legacy / malformed
|
|
87
|
+
* payloads slipping past Zod still cannot exhaust the terminal.
|
|
88
|
+
*/
|
|
89
|
+
export function clampPreview(raw) {
|
|
90
|
+
if (raw.length <= ASK_CHIPS_PREVIEW_CHAR_CAP)
|
|
91
|
+
return raw;
|
|
92
|
+
return `${raw.slice(0, ASK_CHIPS_PREVIEW_CHAR_CAP - 1)}…`;
|
|
93
|
+
}
|
|
61
94
|
/**
|
|
62
95
|
* Truncate a label к the configured word / character caps. Always
|
|
63
96
|
* append "…" when truncation happens so the operator knows the chip
|
|
@@ -219,6 +252,31 @@ export function AskUserQuestionChips(props) {
|
|
|
219
252
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { children: _jsx(Text, { bold: true, children: `Pugi: ${questions.length} quick ${questions.length === 1 ? 'choice' : 'choices'}` }) }), questions.map((q, qIdx) => (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: `${q.header}` }), q.options.map((opt, oIdx) => (_jsx(Text, { children: ` ${oIdx + 1}. ${truncateLabel(opt.label)}${oIdx === 0 ? ' (default)' : ''}` }, `q-${qIdx}-o-${oIdx}`)))] }, `q-${qIdx}-${q.header}`))), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, italic: true, children: `(non-interactive — defaults apply)` }) })] }));
|
|
220
253
|
}
|
|
221
254
|
// ─── Interactive Ink render ─────────────────────────────────────────
|
|
255
|
+
// PUGI-130: when ANY option carries `preview`, switch layout — chips
|
|
256
|
+
// stack vertically (option list) and a preview pane mounts on the
|
|
257
|
+
// right, updating as the cursor moves. Otherwise fall back to the
|
|
258
|
+
// existing horizontal chip layout (zero-regression contract).
|
|
259
|
+
const previewMode = hasAnyPreview(questions);
|
|
260
|
+
if (previewMode) {
|
|
261
|
+
const focusedQ = questions[focusedQuestion];
|
|
262
|
+
const focusedCursor = cursorPerQuestion[focusedQuestion] ?? 0;
|
|
263
|
+
const focusedOpt = focusedQ?.options[focusedCursor];
|
|
264
|
+
const rawPreview = focusedOpt?.preview ?? '';
|
|
265
|
+
const previewText = clampPreview(rawPreview);
|
|
266
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { children: _jsx(Text, { bold: true, children: `Pugi: ${questions.length} quick ${questions.length === 1 ? 'choice' : 'choices'}` }) }), _jsxs(Box, { flexDirection: "row", marginTop: 1, children: [_jsx(Box, { flexDirection: "column", marginRight: 2, children: questions.map((q, qIdx) => {
|
|
267
|
+
const isFocused = qIdx === focusedQuestion;
|
|
268
|
+
const cursor = cursorPerQuestion[qIdx] ?? 0;
|
|
269
|
+
const isSkipped = skipped[qIdx] === true;
|
|
270
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: isFocused ? 'cyan' : 'gray', paddingX: 1, marginBottom: qIdx < questions.length - 1 ? 1 : 0, minWidth: 28, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: isFocused ? 'cyan' : undefined, children: q.header }), isSkipped ? (_jsx(Text, { dimColor: true, italic: true, children: ' (skipped)' })) : null] }), q.options.map((opt, oIdx) => {
|
|
271
|
+
const isHighlighted = isFocused && oIdx === cursor;
|
|
272
|
+
const label = truncateLabel(opt.label);
|
|
273
|
+
const isSkipOption = label === ASK_CHIPS_SKIP_LABEL ||
|
|
274
|
+
opt.label === ASK_CHIPS_SKIP_LABEL;
|
|
275
|
+
const hasPreview = typeof opt.preview === 'string' && opt.preview.length > 0;
|
|
276
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: isHighlighted ? 'cyan' : undefined, bold: isHighlighted, children: isHighlighted ? '▸ ' : ' ' }), _jsx(Text, { color: isHighlighted ? 'cyan' : undefined, children: `${oIdx + 1} ` }), isSkipOption ? (_jsx(Text, { dimColor: true, italic: true, children: label })) : (_jsx(Text, { bold: isHighlighted, children: label })), hasPreview ? (_jsx(Text, { dimColor: true, children: ' ⊕' })) : null] }, `opt-${qIdx}-${oIdx}-${opt.label}`));
|
|
277
|
+
})] }, `chip-${qIdx}-${q.header}`));
|
|
278
|
+
}) }), _jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", paddingX: 1, minWidth: ASK_CHIPS_PREVIEW_PANE_WIDTH, flexGrow: 1, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, dimColor: true, children: 'Preview' }), focusedOpt ? (_jsx(Text, { dimColor: true, children: ` · ${truncateLabel(focusedOpt.label)}` })) : null] }), previewText.length > 0 ? (previewText.split('\n').map((line, lineIdx) => (_jsx(Text, { children: line.length > 0 ? line : ' ' }, `pv-${focusedQuestion}-${focusedCursor}-${lineIdx}`)))) : (_jsx(Text, { dimColor: true, italic: true, children: '(no preview for this option)' }))] })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: `[Enter] use highlighted defaults · [↑↓] navigate · [←→] switch · [s] skip · [Esc] cancel` }) })] }));
|
|
279
|
+
}
|
|
222
280
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { children: _jsx(Text, { bold: true, children: `Pugi: ${questions.length} quick ${questions.length === 1 ? 'choice' : 'choices'}` }) }), _jsx(Box, { flexDirection: "row", marginTop: 1, children: questions.map((q, qIdx) => {
|
|
223
281
|
const isFocused = qIdx === focusedQuestion;
|
|
224
282
|
const cursor = cursorPerQuestion[qIdx] ?? 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pugi/cli",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.90",
|
|
4
4
|
"description": "Pugi CLI - terminal-native software execution system",
|
|
5
5
|
"homepage": "https://pugi.io",
|
|
6
6
|
"repository": {
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"which": "^6.0.0",
|
|
64
64
|
"zod": "^3.23.0",
|
|
65
65
|
"@pugi/personas": "0.1.2",
|
|
66
|
-
"@pugi/sdk": "0.1.0-beta.
|
|
66
|
+
"@pugi/sdk": "0.1.0-beta.90"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
69
|
"@types/node": "^22.0.0",
|