@pugi/cli 0.1.0-beta.2 → 0.1.0-beta.20
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 -40
- package/bin/run.js +33 -1
- package/dist/commands/jobs-watch.js +201 -0
- package/dist/commands/jobs.js +15 -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/compact/auto-trigger.js +96 -0
- package/dist/core/compact/buffer-rewriter.js +115 -0
- package/dist/core/compact/summarizer.js +196 -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/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/session.js +74 -0
- package/dist/core/diagnostics/probes/status-snapshot.js +442 -0
- package/dist/core/diagnostics/probes/workspace.js +63 -0
- package/dist/core/diagnostics/types.js +70 -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 +111 -18
- package/dist/core/engine/anvil-client.js +115 -5
- package/dist/core/engine/budgets.js +89 -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 +744 -210
- package/dist/core/engine/prompts.js +61 -6
- package/dist/core/engine/strip-internal-fields.js +124 -0
- package/dist/core/engine/tool-bridge.js +818 -31
- package/dist/core/file-cache.js +113 -1
- package/dist/core/init/scaffold.js +195 -0
- package/dist/core/lsp/client.js +174 -29
- package/dist/core/mcp/client.js +75 -6
- package/dist/core/mcp/http-server.js +553 -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/permissions/gate.js +187 -0
- package/dist/core/permissions/index.js +18 -0
- package/dist/core/permissions/mode.js +102 -0
- package/dist/core/permissions/state.js +160 -0
- package/dist/core/permissions/tool-class.js +93 -0
- package/dist/core/repl/codebase-survey.js +308 -0
- package/dist/core/repl/history.js +11 -1
- package/dist/core/repl/init-interview.js +457 -0
- package/dist/core/repl/model-pricing.js +135 -0
- package/dist/core/repl/onboarding-state.js +297 -0
- package/dist/core/repl/session.js +719 -29
- package/dist/core/repl/slash-commands.js +133 -9
- package/dist/core/retry-budget/budget.js +284 -0
- package/dist/core/retry-budget/index.js +5 -0
- package/dist/core/settings.js +71 -0
- package/dist/core/skills/defaults.js +457 -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/transport/version-interceptor.js +166 -0
- package/dist/index.js +28 -0
- package/dist/runtime/bootstrap.js +190 -0
- package/dist/runtime/cli.js +1588 -266
- package/dist/runtime/commands/compact.js +296 -0
- package/dist/runtime/commands/cost.js +199 -0
- package/dist/runtime/commands/delegate.js +289 -0
- package/dist/runtime/commands/doctor.js +369 -0
- package/dist/runtime/commands/lsp.js +187 -5
- package/dist/runtime/commands/mcp.js +824 -0
- package/dist/runtime/commands/patch.js +17 -0
- package/dist/runtime/commands/permissions.js +87 -0
- package/dist/runtime/commands/report.js +299 -0
- package/dist/runtime/commands/review-consensus.js +17 -2
- package/dist/runtime/commands/roster.js +117 -0
- package/dist/runtime/commands/status.js +178 -0
- package/dist/runtime/commands/worktree.js +50 -6
- 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 +206 -0
- package/dist/tools/apply-patch.js +281 -39
- 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/mcp-tool.js +260 -0
- package/dist/tools/multi-edit.js +361 -0
- package/dist/tools/registry.js +22 -2
- package/dist/tools/skill-tool.js +96 -0
- package/dist/tools/tasks.js +208 -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 +54 -0
- package/dist/tui/conversation-pane.js +69 -8
- package/dist/tui/cost-table.js +111 -0
- package/dist/tui/doctor-table.js +31 -0
- package/dist/tui/input-box.js +1 -1
- package/dist/tui/markdown-render.js +4 -4
- package/dist/tui/repl-render.js +276 -37
- package/dist/tui/repl-splash.js +2 -2
- package/dist/tui/repl.js +25 -6
- 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/tool-stream-pane.js +7 -0
- package/dist/tui/update-banner.js +20 -2
- package/docs/examples/codegraph.mcp.json +10 -0
- package/package.json +9 -6
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `pugi status` — concise session-state probe (Leak L34, 2026-05-27).
|
|
3
|
+
*
|
|
4
|
+
* Different from `pugi doctor` (environment health). The status
|
|
5
|
+
* command answers "what is this Pugi session doing right now?" —
|
|
6
|
+
* session id + age, cwd, permission mode, CLI version, token
|
|
7
|
+
* usage, dispatch counts, last command, compact boundaries, auth
|
|
8
|
+
* identity.
|
|
9
|
+
*
|
|
10
|
+
* # Module contract
|
|
11
|
+
*
|
|
12
|
+
* - This file owns the WIRING from CLI flags + ambient process
|
|
13
|
+
* state к the snapshot collector. The data collector itself
|
|
14
|
+
* lives in `core/diagnostics/probes/status-snapshot.ts` and
|
|
15
|
+
* has zero coupling к the CLI dispatch surface.
|
|
16
|
+
*
|
|
17
|
+
* - `runStatusCommand` is the single entry point. Both the
|
|
18
|
+
* top-level `pugi status` handler in `runtime/cli.ts` AND the
|
|
19
|
+
* in-REPL `/status` slash command call it. The function
|
|
20
|
+
* returns the `StatusSnapshot` so the REPL slash handler can
|
|
21
|
+
* mount the Ink renderer without re-collecting fields.
|
|
22
|
+
*
|
|
23
|
+
* - The credential resolver is captured behind a function so the
|
|
24
|
+
* spec can stub it without monkey-patching `core/credentials.ts`.
|
|
25
|
+
*
|
|
26
|
+
* - The permission state module is dynamic-imported with a
|
|
27
|
+
* try/catch so this command lands cleanly before the L6
|
|
28
|
+
* permission-mode work — when the module is absent the field
|
|
29
|
+
* degrades к "unknown" instead of crashing the snapshot.
|
|
30
|
+
*
|
|
31
|
+
* - Exit code is 0 unless the snapshot itself throws. The
|
|
32
|
+
* command is intentionally informational — even a fully
|
|
33
|
+
* "unavailable" snapshot (everything degraded к sentinels)
|
|
34
|
+
* exits 0 so monitoring scripts can rely on a stable contract.
|
|
35
|
+
*/
|
|
36
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
37
|
+
import { homedir } from 'node:os';
|
|
38
|
+
import { resolveActiveCredential } from '../../core/credentials.js';
|
|
39
|
+
import { PUGI_CLI_VERSION } from '../version.js';
|
|
40
|
+
import { collectStatusSnapshot, } from '../../core/diagnostics/probes/status-snapshot.js';
|
|
41
|
+
/**
|
|
42
|
+
* Default production filesystem stubs. Wraps node:fs so the
|
|
43
|
+
* snapshot collector can stay sync + structurally testable.
|
|
44
|
+
*/
|
|
45
|
+
const DEFAULT_FS = {
|
|
46
|
+
existsSync,
|
|
47
|
+
statSync,
|
|
48
|
+
readdirSync: (p) => readdirSync(p),
|
|
49
|
+
readFileSync: (p, encoding) => readFileSync(p, encoding),
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Default credential resolver. Wraps `resolveActiveCredential` so
|
|
53
|
+
* the snapshot only sees the trimmed `ResolvedCredentialSummary`
|
|
54
|
+
* shape and не the full store record.
|
|
55
|
+
*/
|
|
56
|
+
function defaultResolveCredential(env, home) {
|
|
57
|
+
const cred = resolveActiveCredential(env, home);
|
|
58
|
+
if (!cred)
|
|
59
|
+
return null;
|
|
60
|
+
// Tier is не yet recorded в the credentials file — set к null
|
|
61
|
+
// until a future sprint surfaces `tier` on the stored record.
|
|
62
|
+
// The renderer hides the parenthetical when tier is null.
|
|
63
|
+
return {
|
|
64
|
+
apiUrl: cred.apiUrl,
|
|
65
|
+
label: cred.label ?? null,
|
|
66
|
+
tier: null,
|
|
67
|
+
identity: cred.label ?? null,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Default permission-mode loader. Dynamic-imports
|
|
72
|
+
* `core/permissions/state.ts` (L6 in the leak-parity roadmap);
|
|
73
|
+
* returns null when the module is absent OR malformed, which the
|
|
74
|
+
* snapshot field degrades к the "unknown" sentinel.
|
|
75
|
+
*/
|
|
76
|
+
async function defaultResolvePermissionMode() {
|
|
77
|
+
try {
|
|
78
|
+
const mod = (await import('../../core/permissions/state.js').catch(() => null));
|
|
79
|
+
if (!mod || typeof mod.getCurrentPermissionMode !== 'function') {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
const mode = mod.getCurrentPermissionMode();
|
|
83
|
+
return typeof mode === 'string' && mode.length > 0 ? mode : null;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Collect the snapshot + emit the output via the supplied
|
|
91
|
+
* writeOutput sink. Returns the snapshot so REPL callers can
|
|
92
|
+
* route it к the Ink renderer instead of the plain-text fallback.
|
|
93
|
+
*/
|
|
94
|
+
export async function runStatusCommand(ctx) {
|
|
95
|
+
// Resolve the permission mode upfront — the snapshot is sync but
|
|
96
|
+
// the L6 loader is async (dynamic import). We materialise the
|
|
97
|
+
// value here and hand the snapshot a sync getter.
|
|
98
|
+
const permissionMode = ctx.resolvePermissionMode === undefined
|
|
99
|
+
? await defaultResolvePermissionMode()
|
|
100
|
+
: null;
|
|
101
|
+
const deps = {
|
|
102
|
+
cwd: ctx.cwd,
|
|
103
|
+
home: ctx.home,
|
|
104
|
+
cliVersion: PUGI_CLI_VERSION,
|
|
105
|
+
now: ctx.now ?? Date.now,
|
|
106
|
+
liveSessionId: ctx.liveSessionId ?? null,
|
|
107
|
+
sessionStartedAtEpochMs: ctx.sessionStartedAtEpochMs ?? null,
|
|
108
|
+
liveTokensUsed: ctx.liveTokensUsed ?? null,
|
|
109
|
+
lastCommand: ctx.lastCommand ?? null,
|
|
110
|
+
lastCommandAtEpochMs: ctx.lastCommandAtEpochMs ?? null,
|
|
111
|
+
fs: ctx.fs ?? DEFAULT_FS,
|
|
112
|
+
resolveCredential: ctx.resolveCredential ?? (() => defaultResolveCredential(ctx.env, ctx.home)),
|
|
113
|
+
resolvePermissionMode: ctx.resolvePermissionMode ?? (() => permissionMode),
|
|
114
|
+
};
|
|
115
|
+
let snapshot;
|
|
116
|
+
try {
|
|
117
|
+
snapshot = collectStatusSnapshot(deps);
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
// Defensive: the snapshot collector is fail-soft per field, but
|
|
121
|
+
// a structural crash (e.g. a hostile env that throws on
|
|
122
|
+
// `process.version` access) must не bring down `pugi status`.
|
|
123
|
+
// We synthesise a minimal envelope and surface the error в the
|
|
124
|
+
// text output so the operator can file a bug.
|
|
125
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
126
|
+
snapshot = {
|
|
127
|
+
command: 'status',
|
|
128
|
+
fields: [
|
|
129
|
+
{
|
|
130
|
+
key: 'session',
|
|
131
|
+
label: 'Session',
|
|
132
|
+
value: 'unknown',
|
|
133
|
+
available: false,
|
|
134
|
+
note: `snapshot collector crashed: ${message}`,
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
meta: {
|
|
138
|
+
cliVersion: PUGI_CLI_VERSION,
|
|
139
|
+
nodeVersion: process.version,
|
|
140
|
+
cwd: ctx.cwd,
|
|
141
|
+
capturedAt: new Date((ctx.now ?? Date.now)()).toISOString(),
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
const text = renderStatusTable(snapshot);
|
|
146
|
+
ctx.writeOutput(snapshot, text);
|
|
147
|
+
// Always exit 0 — `pugi status` is informational, never a gate.
|
|
148
|
+
process.exitCode = 0;
|
|
149
|
+
return snapshot;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Plain-text table renderer. Lays out the snapshot as a two-column
|
|
153
|
+
* (LABEL / VALUE) table with the brand-voice header `Pugi status`.
|
|
154
|
+
* Matches the column-light pattern used by `renderDoctorTable` so
|
|
155
|
+
* narrow terminals stay legible without a layout library.
|
|
156
|
+
*/
|
|
157
|
+
export function renderStatusTable(snapshot) {
|
|
158
|
+
const labelWidth = Math.max('Label'.length, ...snapshot.fields.map((f) => f.label.length));
|
|
159
|
+
const lines = [];
|
|
160
|
+
lines.push('Pugi status');
|
|
161
|
+
lines.push('═'.repeat(50));
|
|
162
|
+
for (const field of snapshot.fields) {
|
|
163
|
+
const labelPart = field.label.padEnd(labelWidth, ' ');
|
|
164
|
+
lines.push(`${labelPart} ${field.value}`);
|
|
165
|
+
}
|
|
166
|
+
lines.push('');
|
|
167
|
+
lines.push(`CLI ${snapshot.meta.cliVersion} Node ${snapshot.meta.nodeVersion} cwd ${snapshot.meta.cwd}`);
|
|
168
|
+
return lines.join('\n');
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Default home dir resolver. Centralised so the CLI handler can
|
|
172
|
+
* call `runStatusCommand` without re-importing `os.homedir`
|
|
173
|
+
* everywhere.
|
|
174
|
+
*/
|
|
175
|
+
export function defaultStatusHome() {
|
|
176
|
+
return homedir();
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -16,9 +16,21 @@
|
|
|
16
16
|
*
|
|
17
17
|
* Brand voice: ASCII only, no emoji, no banned words.
|
|
18
18
|
*/
|
|
19
|
-
import { resolve } from 'node:path';
|
|
20
19
|
import { spawnSync } from 'node:child_process';
|
|
20
|
+
import { resolve, sep } from 'node:path';
|
|
21
21
|
import { createWorktree, dropWorktree, promoteWorktree } from '../../core/edits/worktree.js';
|
|
22
|
+
/**
|
|
23
|
+
* R1 fix (2026-05-26, PR #413 r1, P2 #10): operator-facing path
|
|
24
|
+
* validation. The core `promoteWorktree` / `dropWorktree` primitives
|
|
25
|
+
* already gate their inputs, but mirroring the check at the CLI surface
|
|
26
|
+
* gives the operator a clean error message before we even attempt the
|
|
27
|
+
* git invocation (which would otherwise leak `git rev-parse HEAD` stderr).
|
|
28
|
+
*/
|
|
29
|
+
function isUnderScratchRoot(cwd, candidate) {
|
|
30
|
+
const scratchRoot = resolve(cwd, '.pugi', 'worktrees');
|
|
31
|
+
const abs = resolve(cwd, candidate);
|
|
32
|
+
return abs.startsWith(scratchRoot + sep) && abs !== scratchRoot;
|
|
33
|
+
}
|
|
22
34
|
export async function runWorktreeCommand(args, opts) {
|
|
23
35
|
const [op, ...rest] = args;
|
|
24
36
|
if (!op)
|
|
@@ -56,6 +68,19 @@ export async function runWorktreeCommand(args, opts) {
|
|
|
56
68
|
exitCode: 2,
|
|
57
69
|
};
|
|
58
70
|
}
|
|
71
|
+
if (!isUnderScratchRoot(opts.cwd, worktreePath)) {
|
|
72
|
+
return {
|
|
73
|
+
ok: false,
|
|
74
|
+
text: opts.json
|
|
75
|
+
? JSON.stringify({
|
|
76
|
+
ok: false,
|
|
77
|
+
reason: 'invalid_worktree_path',
|
|
78
|
+
detail: `worktree path must live under <cwd>/.pugi/worktrees/`,
|
|
79
|
+
}, null, 2)
|
|
80
|
+
: `promote failed: invalid_worktree_path: ${worktreePath} is not under .pugi/worktrees/`,
|
|
81
|
+
exitCode: 3,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
59
84
|
// Resolve the worktree path's base SHA from its own git HEAD so
|
|
60
85
|
// the operator never has to remember it after `worktree create`.
|
|
61
86
|
const abs = resolve(opts.cwd, worktreePath);
|
|
@@ -68,21 +93,27 @@ export async function runWorktreeCommand(args, opts) {
|
|
|
68
93
|
};
|
|
69
94
|
}
|
|
70
95
|
const baseSha = head.stdout.trim();
|
|
71
|
-
const result = promoteWorktree({
|
|
96
|
+
const result = promoteWorktree({
|
|
97
|
+
cwd: opts.cwd,
|
|
98
|
+
worktreePath: abs,
|
|
99
|
+
baseSha,
|
|
100
|
+
...(opts.dryRun ? { dryRun: true } : {}),
|
|
101
|
+
});
|
|
72
102
|
if (!result.ok) {
|
|
73
103
|
return {
|
|
74
104
|
ok: false,
|
|
75
105
|
text: opts.json
|
|
76
106
|
? JSON.stringify(result, null, 2)
|
|
77
107
|
: `promote failed: ${result.reason}: ${result.detail}`,
|
|
78
|
-
exitCode: 1,
|
|
108
|
+
exitCode: result.reason === 'protected_file_in_worktree' ? 3 : 1,
|
|
79
109
|
};
|
|
80
110
|
}
|
|
111
|
+
const prefix = opts.dryRun ? 'dry-run: would promote' : 'promoted';
|
|
81
112
|
return {
|
|
82
113
|
ok: true,
|
|
83
114
|
text: opts.json
|
|
84
|
-
? JSON.stringify({ filesChanged: result.value.filesChanged }, null, 2)
|
|
85
|
-
:
|
|
115
|
+
? JSON.stringify({ filesChanged: result.value.filesChanged, dryRun: opts.dryRun ?? false }, null, 2)
|
|
116
|
+
: `${prefix} ${result.value.filesChanged} files from ${abs}`,
|
|
86
117
|
exitCode: 0,
|
|
87
118
|
};
|
|
88
119
|
}
|
|
@@ -95,6 +126,19 @@ export async function runWorktreeCommand(args, opts) {
|
|
|
95
126
|
exitCode: 2,
|
|
96
127
|
};
|
|
97
128
|
}
|
|
129
|
+
if (!isUnderScratchRoot(opts.cwd, worktreePath)) {
|
|
130
|
+
return {
|
|
131
|
+
ok: false,
|
|
132
|
+
text: opts.json
|
|
133
|
+
? JSON.stringify({
|
|
134
|
+
ok: false,
|
|
135
|
+
reason: 'invalid_worktree_path',
|
|
136
|
+
detail: `worktree path must live under <cwd>/.pugi/worktrees/`,
|
|
137
|
+
}, null, 2)
|
|
138
|
+
: `drop failed: invalid_worktree_path: ${worktreePath} is not under .pugi/worktrees/`,
|
|
139
|
+
exitCode: 3,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
98
142
|
const abs = resolve(opts.cwd, worktreePath);
|
|
99
143
|
const result = dropWorktree(abs, opts.cwd);
|
|
100
144
|
if (!result.ok) {
|
|
@@ -103,7 +147,7 @@ export async function runWorktreeCommand(args, opts) {
|
|
|
103
147
|
text: opts.json
|
|
104
148
|
? JSON.stringify(result, null, 2)
|
|
105
149
|
: `drop failed: ${result.reason}: ${result.detail}`,
|
|
106
|
-
exitCode: 1,
|
|
150
|
+
exitCode: result.reason === 'invalid_worktree_path' ? 3 : 1,
|
|
107
151
|
};
|
|
108
152
|
}
|
|
109
153
|
return {
|