@pugi/cli 0.1.0-beta.4 → 0.1.0-beta.41
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/THIRD_PARTY_NOTICES.md +40 -0
- package/assets/pugi-mascot.ansi +15 -25
- package/bin/run.js +33 -1
- package/dist/commands/jobs-watch.js +201 -0
- package/dist/commands/jobs.js +15 -0
- package/dist/commands/smoke.js +133 -0
- package/dist/core/agent-progress/cleanup.js +134 -0
- package/dist/core/agent-progress/schema.js +144 -0
- package/dist/core/agent-progress/writer.js +101 -0
- package/dist/core/artifact-chain/dispatcher.js +148 -0
- package/dist/core/artifact-chain/exporter.js +164 -0
- package/dist/core/artifact-chain/state.js +243 -0
- package/dist/core/artifact-chain/steps.js +169 -0
- package/dist/core/auth/ensure-authenticated.js +129 -0
- package/dist/core/auth/env-provider.js +238 -0
- package/dist/core/auto-update/channels.js +122 -0
- package/dist/core/auto-update/checker.js +241 -0
- package/dist/core/auto-update/state.js +235 -0
- package/dist/core/bare-mode/index.js +107 -0
- package/dist/core/bash-classifier.js +108 -1
- package/dist/core/checkpoint/resumer.js +149 -0
- package/dist/core/checkpoint/rewinder.js +291 -0
- package/dist/core/codegraph/decision-store.js +248 -0
- package/dist/core/codegraph/detect-repo.js +459 -0
- package/dist/core/codegraph/install.js +134 -0
- package/dist/core/codegraph/offer-hook.js +220 -0
- package/dist/core/compact/auto-trigger.js +96 -0
- package/dist/core/compact/buffer-rewriter.js +115 -0
- package/dist/core/compact/summarizer.js +208 -0
- package/dist/core/compact/token-counter.js +108 -0
- package/dist/core/consensus/diff-capture.js +73 -0
- package/dist/core/context/index.js +7 -0
- package/dist/core/context/markdown-traverse.js +255 -0
- package/dist/core/cost/rate-card.js +129 -0
- package/dist/core/cost/tracker.js +221 -0
- package/dist/core/denial-tracking/index.js +8 -0
- package/dist/core/denial-tracking/state.js +264 -0
- package/dist/core/diagnostics/probe-runner.js +93 -0
- package/dist/core/diagnostics/probes/api.js +46 -0
- package/dist/core/diagnostics/probes/auth.js +86 -0
- package/dist/core/diagnostics/probes/bare-mode.js +42 -0
- package/dist/core/diagnostics/probes/cli-version.js +127 -0
- package/dist/core/diagnostics/probes/config.js +72 -0
- package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
- package/dist/core/diagnostics/probes/disk.js +81 -0
- package/dist/core/diagnostics/probes/git.js +65 -0
- package/dist/core/diagnostics/probes/mcp.js +75 -0
- package/dist/core/diagnostics/probes/node.js +59 -0
- package/dist/core/diagnostics/probes/pnpm.js +36 -0
- package/dist/core/diagnostics/probes/pugi-md.js +89 -0
- package/dist/core/diagnostics/probes/session.js +74 -0
- package/dist/core/diagnostics/probes/status-snapshot.js +488 -0
- package/dist/core/diagnostics/probes/workspace.js +63 -0
- package/dist/core/diagnostics/types.js +70 -0
- package/dist/core/dispatch/cache-cleanup.js +197 -0
- package/dist/core/dispatch/cache-handoff.js +295 -0
- package/dist/core/edits/dispatch.js +218 -2
- package/dist/core/edits/journal.js +199 -0
- package/dist/core/edits/layer-d-ast.js +557 -14
- package/dist/core/edits/verify-hook.js +273 -0
- package/dist/core/edits/worktree.js +322 -0
- package/dist/core/engine/anvil-client.js +115 -5
- package/dist/core/engine/budgets.js +98 -0
- package/dist/core/engine/context-prefix.js +155 -0
- package/dist/core/engine/intent.js +260 -0
- package/dist/core/engine/native-pugi.js +860 -211
- package/dist/core/engine/prompts.js +88 -2
- package/dist/core/engine/strip-internal-fields.js +124 -0
- package/dist/core/engine/tool-bridge.js +1045 -36
- package/dist/core/feedback/queue.js +177 -0
- package/dist/core/feedback/submitter.js +145 -0
- package/dist/core/file-cache.js +113 -1
- package/dist/core/hooks/events.js +44 -0
- package/dist/core/hooks/index.js +15 -0
- package/dist/core/hooks/registry.js +213 -0
- package/dist/core/hooks/runner.js +236 -0
- package/dist/core/hooks/v2/event-emitter.js +115 -0
- package/dist/core/hooks/v2/executor.js +282 -0
- package/dist/core/hooks/v2/index.js +25 -0
- package/dist/core/hooks/v2/lifecycle.js +104 -0
- package/dist/core/hooks/v2/loader.js +216 -0
- package/dist/core/hooks/v2/matcher.js +125 -0
- package/dist/core/hooks/v2/trust.js +143 -0
- package/dist/core/hooks/v2/types.js +86 -0
- package/dist/core/lsp/cache.js +105 -0
- package/dist/core/lsp/client.js +776 -0
- package/dist/core/lsp/language-detect.js +66 -0
- package/dist/core/lsp/post-edit-diagnostics.js +171 -0
- package/dist/core/mcp/client.js +75 -6
- package/dist/core/mcp/http-server.js +553 -0
- package/dist/core/mcp/orchestrator-tools.js +662 -0
- package/dist/core/mcp/permission.js +190 -0
- package/dist/core/mcp/registry.js +24 -2
- package/dist/core/mcp/server-tools.js +219 -0
- package/dist/core/mcp/server.js +397 -0
- package/dist/core/memory/dual-write.js +416 -0
- package/dist/core/memory/phase1-kinds.js +20 -0
- package/dist/core/memory-sync/queue.js +158 -0
- package/dist/core/onboarding/ensure-initialized.js +133 -0
- package/dist/core/onboarding/marker.js +111 -0
- package/dist/core/onboarding/telemetry-state.js +108 -0
- package/dist/core/output-style/presets.js +176 -0
- package/dist/core/output-style/state.js +185 -0
- package/dist/core/permissions/auto-classifier.js +124 -0
- package/dist/core/permissions/circuit-breaker.js +83 -0
- package/dist/core/permissions/gate.js +278 -0
- package/dist/core/permissions/index.js +20 -0
- package/dist/core/permissions/mode.js +174 -0
- package/dist/core/permissions/state.js +241 -0
- package/dist/core/permissions/tool-class.js +93 -0
- package/dist/core/prd-check/parser.js +215 -0
- package/dist/core/prd-check/reporter.js +127 -0
- package/dist/core/prd-check/session-review.js +557 -0
- package/dist/core/prd-check/verifiers.js +223 -0
- package/dist/core/pugi-md/context-injector.js +76 -0
- package/dist/core/pugi-md/walk-up.js +207 -0
- package/dist/core/release-notes/parser.js +241 -0
- package/dist/core/release-notes/state.js +116 -0
- package/dist/core/repl/history.js +11 -1
- package/dist/core/repl/model-pricing.js +135 -0
- package/dist/core/repl/session.js +1899 -38
- package/dist/core/repl/slash-commands.js +406 -21
- package/dist/core/repl/store/session-store.js +31 -2
- package/dist/core/repl/workspace-context.js +22 -0
- package/dist/core/repo-map/build.js +125 -0
- package/dist/core/repo-map/cache.js +185 -0
- package/dist/core/repo-map/extractor.js +254 -0
- package/dist/core/repo-map/formatter.js +145 -0
- package/dist/core/repo-map/scanner.js +211 -0
- package/dist/core/retry-budget/budget.js +284 -0
- package/dist/core/retry-budget/index.js +5 -0
- package/dist/core/session.js +92 -0
- package/dist/core/settings.js +80 -0
- package/dist/core/share/formatter.js +271 -0
- package/dist/core/share/redactor.js +221 -0
- package/dist/core/share/uploader.js +267 -0
- package/dist/core/skills/defaults.js +457 -0
- package/dist/core/smoke/headless-driver.js +174 -0
- package/dist/core/smoke/orchestrator.js +194 -0
- package/dist/core/smoke/runner.js +238 -0
- package/dist/core/smoke/scenario-parser.js +316 -0
- package/dist/core/subagents/dispatcher-real.js +600 -0
- package/dist/core/subagents/dispatcher.js +113 -24
- package/dist/core/subagents/index.js +18 -5
- package/dist/core/subagents/isolation-matrix.js +213 -0
- package/dist/core/subagents/spawn.js +19 -4
- package/dist/core/telemetry/emitter.js +229 -0
- package/dist/core/telemetry/queue.js +251 -0
- package/dist/core/theme/context.js +91 -0
- package/dist/core/theme/presets.js +228 -0
- package/dist/core/theme/state.js +181 -0
- package/dist/core/todos/invariant.js +10 -0
- package/dist/core/todos/state.js +177 -0
- package/dist/core/transport/version-interceptor.js +166 -0
- package/dist/core/vim/keymap.js +288 -0
- package/dist/core/vim/state.js +92 -0
- package/dist/index.js +28 -0
- package/dist/runtime/bootstrap.js +190 -0
- package/dist/runtime/cli.js +3073 -321
- package/dist/runtime/commands/cancel.js +231 -0
- package/dist/runtime/commands/chain.js +489 -0
- package/dist/runtime/commands/codegraph-status.js +227 -0
- package/dist/runtime/commands/compact.js +297 -0
- package/dist/runtime/commands/cost.js +199 -0
- package/dist/runtime/commands/delegate.js +242 -11
- package/dist/runtime/commands/dispatch.js +126 -0
- package/dist/runtime/commands/doctor.js +390 -0
- package/dist/runtime/commands/feedback.js +184 -0
- package/dist/runtime/commands/hooks.js +184 -0
- package/dist/runtime/commands/lsp.js +368 -0
- package/dist/runtime/commands/mcp.js +879 -0
- package/dist/runtime/commands/memory.js +508 -0
- package/dist/runtime/commands/model.js +237 -0
- package/dist/runtime/commands/onboarding.js +275 -0
- package/dist/runtime/commands/patch.js +128 -0
- package/dist/runtime/commands/permissions.js +112 -0
- package/dist/runtime/commands/plan.js +143 -0
- package/dist/runtime/commands/prd-check.js +285 -0
- package/dist/runtime/commands/redo-blob-store.js +92 -0
- package/dist/runtime/commands/redo.js +361 -0
- package/dist/runtime/commands/release-notes.js +229 -0
- package/dist/runtime/commands/repo-map.js +95 -0
- package/dist/runtime/commands/report.js +299 -0
- package/dist/runtime/commands/resume.js +118 -0
- package/dist/runtime/commands/review-consensus.js +17 -2
- package/dist/runtime/commands/rewind.js +333 -0
- package/dist/runtime/commands/sessions.js +163 -0
- package/dist/runtime/commands/share.js +316 -0
- package/dist/runtime/commands/status.js +186 -0
- package/dist/runtime/commands/stickers.js +82 -0
- package/dist/runtime/commands/style.js +194 -0
- package/dist/runtime/commands/theme.js +196 -0
- package/dist/runtime/commands/undo.js +32 -0
- package/dist/runtime/commands/update.js +289 -0
- package/dist/runtime/commands/vim.js +140 -0
- package/dist/runtime/commands/worktree.js +177 -0
- package/dist/runtime/headless-repl.js +195 -0
- package/dist/runtime/headless.js +543 -0
- package/dist/runtime/load-hooks-or-exit.js +71 -0
- package/dist/runtime/plan-decompose.js +531 -0
- package/dist/runtime/version.js +65 -0
- package/dist/tools/agent-tool.js +229 -0
- package/dist/tools/apply-patch.js +556 -0
- package/dist/tools/ask-user-question.js +213 -0
- package/dist/tools/ask-user.js +115 -0
- package/dist/tools/file-tools.js +85 -14
- package/dist/tools/lsp-tools.js +189 -0
- package/dist/tools/mcp-tool.js +260 -0
- package/dist/tools/multi-edit.js +361 -0
- package/dist/tools/powershell.js +156 -0
- package/dist/tools/registry.js +51 -0
- package/dist/tools/skill-tool.js +96 -0
- package/dist/tools/tasks.js +208 -0
- package/dist/tools/todo-write.js +184 -0
- package/dist/tools/web-fetch.js +147 -2
- package/dist/tools/web-search.js +458 -0
- package/dist/tui/agent-progress-card.js +111 -0
- package/dist/tui/agent-tree.js +10 -0
- package/dist/tui/ask-modal.js +2 -2
- package/dist/tui/ask-user-question-prompt.js +192 -0
- package/dist/tui/compact-banner.js +81 -0
- package/dist/tui/conversation-pane.js +82 -8
- package/dist/tui/cost-table.js +111 -0
- package/dist/tui/doctor-table.js +46 -0
- package/dist/tui/feedback-prompt.js +156 -0
- package/dist/tui/input-box.js +69 -2
- package/dist/tui/markdown-render.js +4 -4
- package/dist/tui/onboarding-wizard.js +240 -0
- package/dist/tui/permissions-picker.js +86 -0
- package/dist/tui/render.js +35 -0
- package/dist/tui/repl-render.js +303 -13
- package/dist/tui/repl-splash.js +2 -2
- package/dist/tui/repl.js +72 -14
- package/dist/tui/splash.js +1 -1
- package/dist/tui/status-bar.js +94 -16
- package/dist/tui/status-table.js +7 -0
- package/dist/tui/stickers-art.js +136 -0
- package/dist/tui/style-table.js +28 -0
- package/dist/tui/theme-table.js +29 -0
- package/dist/tui/tool-stream-pane.js +52 -3
- package/dist/tui/update-banner.js +20 -2
- package/dist/tui/vim-input.js +267 -0
- package/docs/examples/codegraph.mcp.json +10 -0
- package/package.json +12 -6
- package/test/scenarios/codegen-create-file.scenario.txt +13 -0
- package/test/scenarios/compact-force.scenario.txt +11 -0
- package/test/scenarios/identity.scenario.txt +11 -0
- package/test/scenarios/persona-handoff.scenario.txt +11 -0
- package/test/scenarios/walkback.scenario.txt +12 -0
- package/dist/core/engine/compaction-hook.js +0 -154
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Leak L26 (2026-05-27) — Vim-mode-aware REPL input.
|
|
4
|
+
*
|
|
5
|
+
* Thin wrapper that sits BETWEEN the REPL session and the legacy
|
|
6
|
+
* `InputBox`. When vim mode is off it forwards every prop unchanged
|
|
7
|
+
* so existing operators see zero behavioural delta. When vim mode is
|
|
8
|
+
* on it intercepts keystrokes through `useInput` BEFORE Ink's normal
|
|
9
|
+
* dispatch and either:
|
|
10
|
+
*
|
|
11
|
+
* - in `insert` mode, lets the keystroke fall through to `InputBox`
|
|
12
|
+
* so the legacy buffer / cursor / history / palette code stays
|
|
13
|
+
* authoritative (we do NOT re-implement insert-mode editing);
|
|
14
|
+
* - in `normal` mode, routes the key through `core/vim/keymap.ts`
|
|
15
|
+
* and applies the result to a shadow buffer + cursor mirror that
|
|
16
|
+
* it then pushes back into `InputBox` via the existing `initial`
|
|
17
|
+
* prop on remount.
|
|
18
|
+
*
|
|
19
|
+
* Why a wrapper instead of inlining into `InputBox`?
|
|
20
|
+
*
|
|
21
|
+
* - Keeps the legacy input surface untouched for non-vim operators
|
|
22
|
+
* (the L26 ship risks zero regression to ~year-old code).
|
|
23
|
+
* - The wrapper renders a thin status row ABOVE the input frame so
|
|
24
|
+
* the active mode + pending sequence + cheat sheet are visible.
|
|
25
|
+
* - Tests can exercise the wrapper without dragging in the full
|
|
26
|
+
* ink + clipboard stack of `InputBox` (the spec drives the
|
|
27
|
+
* keymap directly; the wrapper is exercised via the runtime
|
|
28
|
+
* command tests + manual smoke from the REPL).
|
|
29
|
+
*
|
|
30
|
+
* The component intentionally only models the SINGLE-LINE REPL prompt
|
|
31
|
+
* buffer — that is the surface Claude Code's `/vim` covers, and that
|
|
32
|
+
* is what the leak research validated. Multi-line + visual-mode +
|
|
33
|
+
* counts are out of scope for this sprint.
|
|
34
|
+
*
|
|
35
|
+
* ─── Closure-staleness contract (post-L26 fix, 2026-05-27) ───
|
|
36
|
+
*
|
|
37
|
+
* Three-of-three reviewers flagged the original `setShadowLine`
|
|
38
|
+
* functional updater for reading `shadowCursor` and `pending` from
|
|
39
|
+
* the closure, which goes stale across consecutive keystrokes
|
|
40
|
+
* (React batches dispatch, so the second `d` of `dd` saw the
|
|
41
|
+
* `pending` value from the render that scheduled the first `d`,
|
|
42
|
+
* not the post-first-d value). Same problem for cursor advancement
|
|
43
|
+
* across chained `l`/`h`/`w`/`b` presses.
|
|
44
|
+
*
|
|
45
|
+
* Additionally, calling `props.onSubmit` inline inside a setState
|
|
46
|
+
* updater is double-fired by React strict mode (updaters MUST be
|
|
47
|
+
* pure — strict mode runs them twice to surface impurity).
|
|
48
|
+
*
|
|
49
|
+
* The fix moves shadow cursor + pending + mode into `useRef`
|
|
50
|
+
* (refs survive across renders, are read synchronously, and are
|
|
51
|
+
* not subject to closure capture), drives the keymap dispatch
|
|
52
|
+
* OUTSIDE any setState callback, and defers `props.onSubmit` /
|
|
53
|
+
* `props.onExit` invocations until AFTER the render via the
|
|
54
|
+
* `useEffect` queued by toggling a transition ref.
|
|
55
|
+
*
|
|
56
|
+
* The `line` state is the only React state we mutate per keystroke
|
|
57
|
+
* because the inner `InputBox` re-reads it via the `initial` prop
|
|
58
|
+
* on remount — refs alone cannot trigger that remount. Cursor + mode
|
|
59
|
+
* + pending are surfaced to the render path through the same
|
|
60
|
+
* `line`/`tick` rerender, so the status bar stays in sync without
|
|
61
|
+
* itself being captured by a stale closure.
|
|
62
|
+
*/
|
|
63
|
+
import { useEffect, useRef, useState } from 'react';
|
|
64
|
+
import { Box, Text, useInput } from 'ink';
|
|
65
|
+
import { handleNormalKey, PENDING_NONE, describePending, } from '../core/vim/keymap.js';
|
|
66
|
+
import { InputBox } from './input-box.js';
|
|
67
|
+
/**
|
|
68
|
+
* Render the mode badge + pending sequence + cheat sheet above the
|
|
69
|
+
* input frame. Two lines:
|
|
70
|
+
*
|
|
71
|
+
* ─ NORMAL ─ d: Esc=normal · i=insert · :w=submit · :q=cancel
|
|
72
|
+
* ─ INSERT ─ (mode-specific tail dropped when there's nothing to show)
|
|
73
|
+
*
|
|
74
|
+
* Plain ASCII + dim accents to match the rest of the REPL's chrome.
|
|
75
|
+
*/
|
|
76
|
+
function VimStatusBar(props) {
|
|
77
|
+
const modeLabel = props.mode === 'normal' ? '-- NORMAL --' : '-- INSERT --';
|
|
78
|
+
const pendingLabel = describePending(props.pending);
|
|
79
|
+
const hint = props.mode === 'normal'
|
|
80
|
+
? 'h/j/k/l move · i/a insert · x del · dd line · :w submit · :q cancel'
|
|
81
|
+
: 'Esc → normal mode';
|
|
82
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: "#3da9fc", bold: true, children: modeLabel }), pendingLabel.length > 0 ? (_jsxs(_Fragment, { children: [_jsx(Text, { children: " " }), _jsx(Text, { color: "yellow", children: `${pendingLabel}` })] })) : null, _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: hint })] }));
|
|
83
|
+
}
|
|
84
|
+
export function VimInput(props) {
|
|
85
|
+
const { vimEnabled, initialMode, ...inputBoxProps } = props;
|
|
86
|
+
// When vim mode is off we DO NOT mount the `useInput` overlay — the
|
|
87
|
+
// legacy `InputBox` keeps full ownership of the keystrokes and
|
|
88
|
+
// behaves byte-for-byte the way it always did. This is the same
|
|
89
|
+
// pattern `output-style` followed (off → pass through; on → activate
|
|
90
|
+
// the modal surface).
|
|
91
|
+
if (!vimEnabled) {
|
|
92
|
+
return _jsx(InputBox, { ...inputBoxProps });
|
|
93
|
+
}
|
|
94
|
+
// ─── Render-driving state ─────────────────────────────────────
|
|
95
|
+
//
|
|
96
|
+
// `line` + `tick` are the only pieces of state the render path
|
|
97
|
+
// reads — the inner `InputBox` is remounted on every `tick` bump
|
|
98
|
+
// so the `initial` prop is honoured. Cursor / pending / mode are
|
|
99
|
+
// held in refs (see below) and mirrored into render state via the
|
|
100
|
+
// same setLine/setTick burst at the end of each keystroke so the
|
|
101
|
+
// status bar stays in lockstep without ever being captured by a
|
|
102
|
+
// stale closure inside a setState updater.
|
|
103
|
+
const [line, setLine] = useState(props.initial ?? '');
|
|
104
|
+
const [mode, setMode] = useState(initialMode ?? 'normal');
|
|
105
|
+
const [pending, setPending] = useState(PENDING_NONE);
|
|
106
|
+
const [remountTick, setRemountTick] = useState(0);
|
|
107
|
+
// ─── Closure-safe scratch refs ────────────────────────────────
|
|
108
|
+
//
|
|
109
|
+
// These refs are written synchronously inside the `useInput`
|
|
110
|
+
// handler and read on the NEXT keystroke. They survive across
|
|
111
|
+
// renders, are not captured by stale closures, and are NEVER
|
|
112
|
+
// mutated inside a setState updater — so React strict-mode's
|
|
113
|
+
// double-invocation cannot corrupt them.
|
|
114
|
+
//
|
|
115
|
+
// Why not `useReducer`? A reducer would also see fresh state on
|
|
116
|
+
// every dispatch, but the keymap result is a tagged union whose
|
|
117
|
+
// side effects (mode flip, submit, cancel, remount) are easier
|
|
118
|
+
// to read as imperative steps after the pure `handleNormalKey`
|
|
119
|
+
// call. Refs keep the dispatcher linear and the diff vs the
|
|
120
|
+
// pre-fix file minimal.
|
|
121
|
+
const lineRef = useRef(props.initial ?? '');
|
|
122
|
+
const cursorRef = useRef(props.initial?.length ?? 0);
|
|
123
|
+
const pendingRef = useRef(PENDING_NONE);
|
|
124
|
+
const modeRef = useRef(initialMode ?? 'normal');
|
|
125
|
+
// Deferred side-effect queue — submit / exit must NOT run inline
|
|
126
|
+
// inside a setState updater (strict mode double-invokes them).
|
|
127
|
+
// We stash the payload here and flush it from a `useEffect` after
|
|
128
|
+
// the render commits.
|
|
129
|
+
const pendingSubmitRef = useRef(null);
|
|
130
|
+
const pendingExitRef = useRef(false);
|
|
131
|
+
const [sideEffectTick, setSideEffectTick] = useState(0);
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
// Flush any deferred host callback exactly once per scheduled
|
|
134
|
+
// burst. The refs are nulled BEFORE the call so a re-entrant
|
|
135
|
+
// host onSubmit handler that re-renders us cannot re-fire the
|
|
136
|
+
// same payload.
|
|
137
|
+
const submit = pendingSubmitRef.current;
|
|
138
|
+
if (submit !== null) {
|
|
139
|
+
pendingSubmitRef.current = null;
|
|
140
|
+
props.onSubmit(submit);
|
|
141
|
+
}
|
|
142
|
+
if (pendingExitRef.current) {
|
|
143
|
+
pendingExitRef.current = false;
|
|
144
|
+
props.onExit();
|
|
145
|
+
}
|
|
146
|
+
// Intentionally only depends on `sideEffectTick`: we want this
|
|
147
|
+
// effect to fire ONLY when the keystroke handler asked for it
|
|
148
|
+
// (via setSideEffectTick), not on every prop change.
|
|
149
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
150
|
+
}, [sideEffectTick]);
|
|
151
|
+
useInput((input, key) => {
|
|
152
|
+
// Esc always returns to normal mode (and clears any pending
|
|
153
|
+
// sequence). This binding wins over `InputBox`'s own Esc handling
|
|
154
|
+
// because we own the `useInput` hook earlier in the render tree.
|
|
155
|
+
if (key.escape) {
|
|
156
|
+
modeRef.current = 'normal';
|
|
157
|
+
pendingRef.current = PENDING_NONE;
|
|
158
|
+
setMode('normal');
|
|
159
|
+
setPending(PENDING_NONE);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
// In insert mode we relinquish dispatch entirely so the legacy
|
|
163
|
+
// input box owns typing / palette / history / clipboard / kill
|
|
164
|
+
// ring without interference. Returning `undefined` from a
|
|
165
|
+
// `useInput` callback is a no-op — InputBox's own `useInput` will
|
|
166
|
+
// receive the same event on the next frame.
|
|
167
|
+
if (modeRef.current === 'insert')
|
|
168
|
+
return;
|
|
169
|
+
// In normal mode we drive the buffer through the keymap.
|
|
170
|
+
//
|
|
171
|
+
// CRITICAL: all inputs to `handleNormalKey` are read from refs,
|
|
172
|
+
// not from the React state closure captured at render time.
|
|
173
|
+
// This is what makes consecutive keystrokes ("ll", "dd", ":w")
|
|
174
|
+
// observe the post-previous-keystroke state instead of the
|
|
175
|
+
// pre-batch render snapshot.
|
|
176
|
+
const ch = input ?? '';
|
|
177
|
+
const curLine = lineRef.current;
|
|
178
|
+
const curCursor = cursorRef.current;
|
|
179
|
+
const curPending = pendingRef.current;
|
|
180
|
+
const out = handleNormalKey({
|
|
181
|
+
line: curLine,
|
|
182
|
+
cursor: curCursor,
|
|
183
|
+
pending: curPending,
|
|
184
|
+
ch,
|
|
185
|
+
enter: key.return,
|
|
186
|
+
escape: false,
|
|
187
|
+
backspace: key.backspace || key.delete,
|
|
188
|
+
});
|
|
189
|
+
// Step 1: write the new pending state to BOTH the ref (read by
|
|
190
|
+
// the next keystroke synchronously) AND the React state (drives
|
|
191
|
+
// the status bar render).
|
|
192
|
+
pendingRef.current = out.pending;
|
|
193
|
+
setPending(out.pending);
|
|
194
|
+
// Step 2: apply the discriminated result. Each branch updates
|
|
195
|
+
// the refs first (synchronous, closure-safe) and then schedules
|
|
196
|
+
// any required React state update or deferred side effect.
|
|
197
|
+
switch (out.result.kind) {
|
|
198
|
+
case 'move': {
|
|
199
|
+
cursorRef.current = out.result.cursor;
|
|
200
|
+
// Cursor lives in the ref; the inner InputBox does not need
|
|
201
|
+
// a remount for a pure motion, so we skip the tick bump.
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
case 'edit': {
|
|
205
|
+
cursorRef.current = out.result.cursor;
|
|
206
|
+
lineRef.current = out.result.line;
|
|
207
|
+
setLine(out.result.line);
|
|
208
|
+
setRemountTick((t) => t + 1);
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
case 'mode': {
|
|
212
|
+
cursorRef.current = out.result.cursor;
|
|
213
|
+
modeRef.current = out.result.mode;
|
|
214
|
+
setMode(out.result.mode);
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
case 'submit': {
|
|
218
|
+
// Forward to the host's onSubmit and clear the shadow buffer
|
|
219
|
+
// so the next prompt starts empty. We DO NOT re-route
|
|
220
|
+
// through InputBox's Enter handler — that path also writes
|
|
221
|
+
// history; submitting from `:w` should mirror that, so we
|
|
222
|
+
// call props.onSubmit indirectly via the deferred-effect
|
|
223
|
+
// queue. (The host's history append happens inside InputBox;
|
|
224
|
+
// for the modal path the host can observe via props.onSubmit
|
|
225
|
+
// and append manually if needed. The leak-parity surface for
|
|
226
|
+
// L26 documents `:w` as equivalent to Enter so this is fine.)
|
|
227
|
+
const payload = out.result.payload;
|
|
228
|
+
if (payload.trim().length > 0) {
|
|
229
|
+
// Queue the host callback; useEffect flushes it after the
|
|
230
|
+
// render commits. This avoids React strict-mode's
|
|
231
|
+
// double-invocation of setState updaters double-firing
|
|
232
|
+
// onSubmit (which would, e.g., submit the same prompt
|
|
233
|
+
// twice to the agent on every `:w`).
|
|
234
|
+
pendingSubmitRef.current = payload;
|
|
235
|
+
}
|
|
236
|
+
cursorRef.current = 0;
|
|
237
|
+
lineRef.current = '';
|
|
238
|
+
setLine('');
|
|
239
|
+
setRemountTick((t) => t + 1);
|
|
240
|
+
setSideEffectTick((t) => t + 1);
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
case 'cancel': {
|
|
244
|
+
cursorRef.current = 0;
|
|
245
|
+
lineRef.current = '';
|
|
246
|
+
setLine('');
|
|
247
|
+
setRemountTick((t) => t + 1);
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
case 'noop': {
|
|
251
|
+
// No-op result still may have advanced `pending` (e.g. the
|
|
252
|
+
// first `d` of `dd` arms the pending state). The pending ref
|
|
253
|
+
// and state were already updated above, so nothing else to
|
|
254
|
+
// do here.
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(VimStatusBar, { mode: mode, pending: pending }), _jsx(InputBox, { ...inputBoxProps,
|
|
260
|
+
// In normal mode we seed the (possibly mutated) shadow line +
|
|
261
|
+
// suppress the blink so the operator's caret is unambiguously
|
|
262
|
+
// controlled by h/l/0/$/w/b. In insert mode we hand the
|
|
263
|
+
// buffer back to InputBox as-is so the legacy typing path
|
|
264
|
+
// takes over.
|
|
265
|
+
initial: mode === 'normal' ? line : (props.initial ?? line), blinkCursor: mode === 'normal' ? false : (props.blinkCursor ?? true) }, `vim-${remountTick}`)] }));
|
|
266
|
+
}
|
|
267
|
+
//# sourceMappingURL=vim-input.js.map
|
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.41",
|
|
4
4
|
"description": "Pugi CLI - terminal-native software execution system",
|
|
5
5
|
"homepage": "https://pugi.io",
|
|
6
6
|
"repository": {
|
|
@@ -29,8 +29,11 @@
|
|
|
29
29
|
"bin/run.js",
|
|
30
30
|
"dist/**/*.js",
|
|
31
31
|
"assets/**/*.ansi",
|
|
32
|
+
"docs/examples/**/*.json",
|
|
33
|
+
"test/scenarios/**/*.scenario.txt",
|
|
32
34
|
"README.md",
|
|
33
|
-
"LICENSE"
|
|
35
|
+
"LICENSE",
|
|
36
|
+
"THIRD_PARTY_NOTICES.md"
|
|
34
37
|
],
|
|
35
38
|
"engines": {
|
|
36
39
|
"node": ">=22.5.0"
|
|
@@ -46,13 +49,13 @@
|
|
|
46
49
|
"ink": "^5.0.1",
|
|
47
50
|
"linkedom": "^0.18.12",
|
|
48
51
|
"react": "^18.3.1",
|
|
49
|
-
"tar": "^
|
|
52
|
+
"tar": "^7.5.11",
|
|
50
53
|
"tinyglobby": "^0.2.16",
|
|
51
54
|
"turndown": "^7.2.4",
|
|
52
55
|
"undici": "^8.3.0",
|
|
53
56
|
"zod": "^3.23.0",
|
|
54
57
|
"@pugi/personas": "0.1.2",
|
|
55
|
-
"@pugi/sdk": "0.1.0-beta.
|
|
58
|
+
"@pugi/sdk": "0.1.0-beta.41"
|
|
56
59
|
},
|
|
57
60
|
"devDependencies": {
|
|
58
61
|
"@types/node": "^22.0.0",
|
|
@@ -67,9 +70,12 @@
|
|
|
67
70
|
"build": "pnpm --filter @pugi/personas --filter @pugi/sdk build && tsc -p tsconfig.json && node scripts/make-bin-executable.mjs",
|
|
68
71
|
"dev": "tsx src/index.ts",
|
|
69
72
|
"typecheck": "pnpm --filter @pugi/personas --filter @pugi/sdk build && tsc -p tsconfig.json --noEmit",
|
|
70
|
-
"test": "pnpm run build && node --test --import tsx 'test/**/*.spec.ts' 'test/**/*.spec.tsx'",
|
|
73
|
+
"test": "pnpm run check:version-lockstep && pnpm run build && node --test --import tsx 'test/**/*.spec.ts' 'test/**/*.spec.tsx' 'src/**/*.spec.ts' 'src/**/*.spec.tsx'",
|
|
74
|
+
"test:integration": "INTEGRATION=1 pnpm run build && node --test --import tsx 'test/**/*.spec.ts' 'test/**/*.spec.tsx' 'src/**/*.spec.ts' 'src/**/*.spec.tsx'",
|
|
71
75
|
"version:cli": "tsx src/index.ts version",
|
|
72
76
|
"doctor": "tsx src/index.ts doctor --json",
|
|
73
|
-
"
|
|
77
|
+
"check:version-lockstep": "bash ../../scripts/check-version-lockstep.sh",
|
|
78
|
+
"pack:smoke": "pnpm run check:version-lockstep && node scripts/pack-smoke.mjs",
|
|
79
|
+
"release-gate": "node scripts/secret-scanner.mjs"
|
|
74
80
|
}
|
|
75
81
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# scenario: codegen-create-file
|
|
2
|
+
# title: Pugi actually writes a file (not just describes it)
|
|
3
|
+
|
|
4
|
+
# Anti-regression for task #265 — Pugi must call Write, not describe what
|
|
5
|
+
# it would write. The headless stub responder parses
|
|
6
|
+
# "create FILE with content 'TEXT'" and runs the real fs write so the
|
|
7
|
+
# scenario validates the wiring end-to-end. When Phase 2 lands the live
|
|
8
|
+
# engine path the same scenario carries over verbatim.
|
|
9
|
+
|
|
10
|
+
> "create hello.txt with content 'hello world'"
|
|
11
|
+
EXPECT: tool-call kind=Write file=hello.txt
|
|
12
|
+
EXPECT: persona-turn contains "wrote hello.txt"
|
|
13
|
+
EXPECT_FILE: hello.txt exists with content "hello world"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# scenario: compact-force
|
|
2
|
+
# title: /compact --force is acknowledged
|
|
3
|
+
|
|
4
|
+
# Phase 1: the stub responder echoes the directive verbatim — Phase 2
|
|
5
|
+
# wires the slash command through to the live compaction loop. Until
|
|
6
|
+
# then this scenario protects the contract between scenario authoring
|
|
7
|
+
# and the harness envelope shape.
|
|
8
|
+
|
|
9
|
+
> "/compact --force"
|
|
10
|
+
EXPECT: persona-turn contains "/compact"
|
|
11
|
+
EXPECT_NOT: persona-turn contains "Mira"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# scenario: identity
|
|
2
|
+
# title: Pugi self-identifies as Pugi (never as Mira)
|
|
3
|
+
|
|
4
|
+
# CEO directive feedback_live_console_test_every_publish: every published
|
|
5
|
+
# beta must still answer "ты кто?" with the Pugi persona, never the legacy
|
|
6
|
+
# Mira name. This is the single most-asked dialog in CEO dogfood and the
|
|
7
|
+
# regression that has bitten us most often.
|
|
8
|
+
|
|
9
|
+
> "ты кто?"
|
|
10
|
+
EXPECT: persona-turn contains "Pugi" OR "Пуджи"
|
|
11
|
+
EXPECT_NOT: persona-turn contains "Mira" OR "Мира"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# scenario: persona-handoff
|
|
2
|
+
# title: Pugi hands off to Hiroshi when the operator asks for dev work
|
|
3
|
+
|
|
4
|
+
# Phase 1: the headless stub currently echoes input via the Pugi persona
|
|
5
|
+
# (real dispatch routing wires in Phase 2). The scenario is authored
|
|
6
|
+
# against the Phase 2 contract — for now it documents the intent and
|
|
7
|
+
# the harness keeps it as a soft EXPECT so the corpus stays loadable.
|
|
8
|
+
|
|
9
|
+
> "Hiroshi, please review the authentication module"
|
|
10
|
+
EXPECT: persona-turn contains "Pugi" OR "Hiroshi" OR "Хироси"
|
|
11
|
+
EXPECT_NOT: persona-turn contains "Mira"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# scenario: walkback
|
|
2
|
+
# title: Esc-Esc walkback / rewind directive recognized
|
|
3
|
+
|
|
4
|
+
# CEO parity ask (BIG TRACK 8): the operator must be able to walk back
|
|
5
|
+
# the last turn. Phase 1 ships the harness contract — the live walkback
|
|
6
|
+
# wiring lands in BIG TRACK 8. The scenario asserts the stub responder
|
|
7
|
+
# acknowledges the directive verbatim so the contract holds while the
|
|
8
|
+
# real implementation lands behind it.
|
|
9
|
+
|
|
10
|
+
> "/rewind 1"
|
|
11
|
+
EXPECT: persona-turn contains "/rewind"
|
|
12
|
+
EXPECT_NOT: persona-turn contains "Mira"
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Engine loop integration point for the six-tier compaction engine.
|
|
3
|
-
*
|
|
4
|
-
* `maybeCompactAfterTool` is the single function the engine loop calls
|
|
5
|
-
* after each tool result has been appended to the transcript. It:
|
|
6
|
-
*
|
|
7
|
-
* 1. Estimates current context-window pressure (transcript bytes
|
|
8
|
-
* against the model's budget, plus the static blocks).
|
|
9
|
-
* 2. Calls `selectTier` on the snapshot.
|
|
10
|
-
* 3. Runs the tier. Microcompact / cached_microcompact are sync;
|
|
11
|
-
* reactive_summary / session_memory / full_compaction / reset
|
|
12
|
-
* are async-shaped (the call returns before commit when run
|
|
13
|
-
* against a long transcript) but currently run inline — the
|
|
14
|
-
* engine loop is single-threaded today, so true backgrounding
|
|
15
|
-
* waits for the SSE consumer refactor in α5.7.
|
|
16
|
-
* 4. Runs invariant checks against the result. On any violation,
|
|
17
|
-
* emits `compaction.invariant_violated` and returns the
|
|
18
|
-
* pre-compaction transcript untouched.
|
|
19
|
-
* 5. On success, emits `compaction.completed` with reclaim numbers
|
|
20
|
-
* and returns the new transcript for the caller to adopt.
|
|
21
|
-
* 6. On no-op, emits `compaction.skipped` and returns the original.
|
|
22
|
-
*
|
|
23
|
-
* Why a separate file (not inlined into `native-pugi.ts`):
|
|
24
|
-
*
|
|
25
|
-
* Sprint α5.3 (feat/pugi-cli-hooks-lifecycle-m1-gap-c) is in flight
|
|
26
|
-
* and already modifies session.ts + tool-bridge + permission. Editing
|
|
27
|
-
* native-pugi.ts in this PR risks a merge conflict against α5.3's
|
|
28
|
-
* landing PR. Keeping the wiring as an exported helper means the
|
|
29
|
-
* one-line callsite in native-pugi.ts can be added in a tiny
|
|
30
|
-
* follow-up after both α5.3 and α5.5 have landed.
|
|
31
|
-
*
|
|
32
|
-
* Expected callsite in `apps/pugi-cli/src/core/engine/native-pugi.ts`,
|
|
33
|
-
* inside `onToolResult`:
|
|
34
|
-
*
|
|
35
|
-
* ```ts
|
|
36
|
-
* const compactionOutcome = await maybeCompactAfterTool({
|
|
37
|
-
* session,
|
|
38
|
-
* transcript: currentTranscript,
|
|
39
|
-
* toolOutputs: recentToolOutputs,
|
|
40
|
-
* contextBudgetUsed: estimatedTokens,
|
|
41
|
-
* contextBudgetMax: budget.maxTokens,
|
|
42
|
-
* workspaceRoot: root,
|
|
43
|
-
* contextStaticHash: {
|
|
44
|
-
* instructionsHash,
|
|
45
|
-
* toolSchemaHash,
|
|
46
|
-
* },
|
|
47
|
-
* });
|
|
48
|
-
* if (compactionOutcome.committed) {
|
|
49
|
-
* currentTranscript = compactionOutcome.newTranscript;
|
|
50
|
-
* }
|
|
51
|
-
* ```
|
|
52
|
-
*/
|
|
53
|
-
import { runCompaction, selectTier, } from '../context/compaction.js';
|
|
54
|
-
import { checkInvariants } from '../context/invariants.js';
|
|
55
|
-
import { emitCompactionCompleted, emitCompactionInvariantViolated, emitCompactionSkipped, emitCompactionStarted, } from '../context/compaction-events.js';
|
|
56
|
-
/**
|
|
57
|
-
* Engine-loop callback. See file header for the expected callsite shape.
|
|
58
|
-
*
|
|
59
|
-
* Contract:
|
|
60
|
-
* - Never throws. All errors degrade to `committed: false` with the
|
|
61
|
-
* original transcript and an event record.
|
|
62
|
-
* - On `committed: true`, the caller MUST adopt `newTranscript` as
|
|
63
|
-
* the live working transcript for the next model turn.
|
|
64
|
-
* - On `committed: false`, the caller MUST keep the input transcript
|
|
65
|
-
* and try again on the next tool turn (compaction will retry once
|
|
66
|
-
* pressure stays above threshold).
|
|
67
|
-
*/
|
|
68
|
-
export async function maybeCompactAfterTool(input) {
|
|
69
|
-
const compactionInput = {
|
|
70
|
-
sessionId: input.session.id,
|
|
71
|
-
contextBudgetUsed: input.contextBudgetUsed,
|
|
72
|
-
contextBudgetMax: input.contextBudgetMax,
|
|
73
|
-
toolOutputs: input.toolOutputs,
|
|
74
|
-
transcript: input.transcript,
|
|
75
|
-
workspaceRoot: input.workspaceRoot,
|
|
76
|
-
};
|
|
77
|
-
const tier = selectTier(compactionInput);
|
|
78
|
-
emitCompactionStarted(input.session, tier, {
|
|
79
|
-
budgetUsed: input.contextBudgetUsed,
|
|
80
|
-
budgetMax: input.contextBudgetMax,
|
|
81
|
-
});
|
|
82
|
-
let result;
|
|
83
|
-
try {
|
|
84
|
-
result = await runCompaction(compactionInput, tier);
|
|
85
|
-
}
|
|
86
|
-
catch (error) {
|
|
87
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
88
|
-
emitCompactionSkipped(input.session, tier, `compaction crashed: ${reason}`);
|
|
89
|
-
return {
|
|
90
|
-
committed: false,
|
|
91
|
-
tier,
|
|
92
|
-
newTranscript: input.transcript,
|
|
93
|
-
bytesReclaimed: 0,
|
|
94
|
-
newContextSize: byteSize(input.transcript),
|
|
95
|
-
violations: [],
|
|
96
|
-
skipped: true,
|
|
97
|
-
skipReason: `crashed: ${reason}`,
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
if (result.skipped) {
|
|
101
|
-
emitCompactionSkipped(input.session, tier, result.skipReason || 'no work');
|
|
102
|
-
return {
|
|
103
|
-
committed: false,
|
|
104
|
-
tier,
|
|
105
|
-
newTranscript: input.transcript,
|
|
106
|
-
bytesReclaimed: 0,
|
|
107
|
-
newContextSize: byteSize(input.transcript),
|
|
108
|
-
violations: [],
|
|
109
|
-
skipped: true,
|
|
110
|
-
skipReason: result.skipReason,
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
// Invariant gate: static-hash-unchanged is enforced by passing the
|
|
114
|
-
// same hashes in for `before` and `after` — compaction never touches
|
|
115
|
-
// static blocks, so the hashes are equal by construction. We pass
|
|
116
|
-
// both so the contract is explicit; if a future tier introduces a
|
|
117
|
-
// bug that overwrites static state, the check still catches it.
|
|
118
|
-
const violations = checkInvariants({
|
|
119
|
-
before: compactionInput,
|
|
120
|
-
after: result,
|
|
121
|
-
summaryText: result.summaryText,
|
|
122
|
-
staticHashBefore: input.contextStaticHash,
|
|
123
|
-
staticHashAfter: input.contextStaticHash,
|
|
124
|
-
});
|
|
125
|
-
if (violations.length > 0) {
|
|
126
|
-
for (const v of violations)
|
|
127
|
-
emitCompactionInvariantViolated(input.session, v);
|
|
128
|
-
return {
|
|
129
|
-
committed: false,
|
|
130
|
-
tier,
|
|
131
|
-
newTranscript: input.transcript,
|
|
132
|
-
bytesReclaimed: 0,
|
|
133
|
-
newContextSize: byteSize(input.transcript),
|
|
134
|
-
violations,
|
|
135
|
-
skipped: false,
|
|
136
|
-
skipReason: '',
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
emitCompactionCompleted(input.session, tier, result.bytesReclaimed, result.newContextSize, result.artifactsCreated);
|
|
140
|
-
return {
|
|
141
|
-
committed: true,
|
|
142
|
-
tier,
|
|
143
|
-
newTranscript: result.newTranscript,
|
|
144
|
-
bytesReclaimed: result.bytesReclaimed,
|
|
145
|
-
newContextSize: result.newContextSize,
|
|
146
|
-
violations: [],
|
|
147
|
-
skipped: false,
|
|
148
|
-
skipReason: '',
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
function byteSize(transcript) {
|
|
152
|
-
return transcript.reduce((sum, t) => sum + Buffer.byteLength(t.content, 'utf8'), 0);
|
|
153
|
-
}
|
|
154
|
-
//# sourceMappingURL=compaction-hook.js.map
|