@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.
Files changed (130) hide show
  1. package/THIRD_PARTY_NOTICES.md +40 -0
  2. package/assets/pugi-mascot.ansi +15 -40
  3. package/bin/run.js +33 -1
  4. package/dist/commands/jobs-watch.js +201 -0
  5. package/dist/commands/jobs.js +15 -0
  6. package/dist/core/agent-progress/cleanup.js +134 -0
  7. package/dist/core/agent-progress/schema.js +144 -0
  8. package/dist/core/agent-progress/writer.js +101 -0
  9. package/dist/core/compact/auto-trigger.js +96 -0
  10. package/dist/core/compact/buffer-rewriter.js +115 -0
  11. package/dist/core/compact/summarizer.js +196 -0
  12. package/dist/core/compact/token-counter.js +108 -0
  13. package/dist/core/consensus/diff-capture.js +73 -0
  14. package/dist/core/context/index.js +7 -0
  15. package/dist/core/context/markdown-traverse.js +255 -0
  16. package/dist/core/cost/rate-card.js +129 -0
  17. package/dist/core/cost/tracker.js +221 -0
  18. package/dist/core/denial-tracking/index.js +8 -0
  19. package/dist/core/denial-tracking/state.js +264 -0
  20. package/dist/core/diagnostics/probe-runner.js +93 -0
  21. package/dist/core/diagnostics/probes/api.js +46 -0
  22. package/dist/core/diagnostics/probes/auth.js +86 -0
  23. package/dist/core/diagnostics/probes/cli-version.js +127 -0
  24. package/dist/core/diagnostics/probes/config.js +72 -0
  25. package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
  26. package/dist/core/diagnostics/probes/disk.js +81 -0
  27. package/dist/core/diagnostics/probes/git.js +65 -0
  28. package/dist/core/diagnostics/probes/mcp.js +75 -0
  29. package/dist/core/diagnostics/probes/node.js +59 -0
  30. package/dist/core/diagnostics/probes/pnpm.js +36 -0
  31. package/dist/core/diagnostics/probes/session.js +74 -0
  32. package/dist/core/diagnostics/probes/status-snapshot.js +442 -0
  33. package/dist/core/diagnostics/probes/workspace.js +63 -0
  34. package/dist/core/diagnostics/types.js +70 -0
  35. package/dist/core/edits/dispatch.js +218 -2
  36. package/dist/core/edits/journal.js +199 -0
  37. package/dist/core/edits/layer-d-ast.js +557 -14
  38. package/dist/core/edits/verify-hook.js +273 -0
  39. package/dist/core/edits/worktree.js +111 -18
  40. package/dist/core/engine/anvil-client.js +115 -5
  41. package/dist/core/engine/budgets.js +89 -0
  42. package/dist/core/engine/context-prefix.js +155 -0
  43. package/dist/core/engine/intent.js +260 -0
  44. package/dist/core/engine/native-pugi.js +744 -210
  45. package/dist/core/engine/prompts.js +61 -6
  46. package/dist/core/engine/strip-internal-fields.js +124 -0
  47. package/dist/core/engine/tool-bridge.js +818 -31
  48. package/dist/core/file-cache.js +113 -1
  49. package/dist/core/init/scaffold.js +195 -0
  50. package/dist/core/lsp/client.js +174 -29
  51. package/dist/core/mcp/client.js +75 -6
  52. package/dist/core/mcp/http-server.js +553 -0
  53. package/dist/core/mcp/permission.js +190 -0
  54. package/dist/core/mcp/registry.js +24 -2
  55. package/dist/core/mcp/server-tools.js +219 -0
  56. package/dist/core/mcp/server.js +397 -0
  57. package/dist/core/permissions/gate.js +187 -0
  58. package/dist/core/permissions/index.js +18 -0
  59. package/dist/core/permissions/mode.js +102 -0
  60. package/dist/core/permissions/state.js +160 -0
  61. package/dist/core/permissions/tool-class.js +93 -0
  62. package/dist/core/repl/codebase-survey.js +308 -0
  63. package/dist/core/repl/history.js +11 -1
  64. package/dist/core/repl/init-interview.js +457 -0
  65. package/dist/core/repl/model-pricing.js +135 -0
  66. package/dist/core/repl/onboarding-state.js +297 -0
  67. package/dist/core/repl/session.js +719 -29
  68. package/dist/core/repl/slash-commands.js +133 -9
  69. package/dist/core/retry-budget/budget.js +284 -0
  70. package/dist/core/retry-budget/index.js +5 -0
  71. package/dist/core/settings.js +71 -0
  72. package/dist/core/skills/defaults.js +457 -0
  73. package/dist/core/subagents/dispatcher-real.js +600 -0
  74. package/dist/core/subagents/dispatcher.js +113 -24
  75. package/dist/core/subagents/index.js +18 -5
  76. package/dist/core/subagents/isolation-matrix.js +213 -0
  77. package/dist/core/subagents/spawn.js +19 -4
  78. package/dist/core/transport/version-interceptor.js +166 -0
  79. package/dist/index.js +28 -0
  80. package/dist/runtime/bootstrap.js +190 -0
  81. package/dist/runtime/cli.js +1588 -266
  82. package/dist/runtime/commands/compact.js +296 -0
  83. package/dist/runtime/commands/cost.js +199 -0
  84. package/dist/runtime/commands/delegate.js +289 -0
  85. package/dist/runtime/commands/doctor.js +369 -0
  86. package/dist/runtime/commands/lsp.js +187 -5
  87. package/dist/runtime/commands/mcp.js +824 -0
  88. package/dist/runtime/commands/patch.js +17 -0
  89. package/dist/runtime/commands/permissions.js +87 -0
  90. package/dist/runtime/commands/report.js +299 -0
  91. package/dist/runtime/commands/review-consensus.js +17 -2
  92. package/dist/runtime/commands/roster.js +117 -0
  93. package/dist/runtime/commands/status.js +178 -0
  94. package/dist/runtime/commands/worktree.js +50 -6
  95. package/dist/runtime/headless.js +543 -0
  96. package/dist/runtime/load-hooks-or-exit.js +71 -0
  97. package/dist/runtime/plan-decompose.js +531 -0
  98. package/dist/runtime/version.js +65 -0
  99. package/dist/tools/agent-tool.js +206 -0
  100. package/dist/tools/apply-patch.js +281 -39
  101. package/dist/tools/ask-user-question.js +213 -0
  102. package/dist/tools/ask-user.js +115 -0
  103. package/dist/tools/file-tools.js +85 -14
  104. package/dist/tools/mcp-tool.js +260 -0
  105. package/dist/tools/multi-edit.js +361 -0
  106. package/dist/tools/registry.js +22 -2
  107. package/dist/tools/skill-tool.js +96 -0
  108. package/dist/tools/tasks.js +208 -0
  109. package/dist/tools/web-fetch.js +147 -2
  110. package/dist/tools/web-search.js +458 -0
  111. package/dist/tui/agent-progress-card.js +111 -0
  112. package/dist/tui/agent-tree.js +10 -0
  113. package/dist/tui/ask-modal.js +2 -2
  114. package/dist/tui/ask-user-question-prompt.js +192 -0
  115. package/dist/tui/compact-banner.js +54 -0
  116. package/dist/tui/conversation-pane.js +69 -8
  117. package/dist/tui/cost-table.js +111 -0
  118. package/dist/tui/doctor-table.js +31 -0
  119. package/dist/tui/input-box.js +1 -1
  120. package/dist/tui/markdown-render.js +4 -4
  121. package/dist/tui/repl-render.js +276 -37
  122. package/dist/tui/repl-splash.js +2 -2
  123. package/dist/tui/repl.js +25 -6
  124. package/dist/tui/splash.js +1 -1
  125. package/dist/tui/status-bar.js +94 -16
  126. package/dist/tui/status-table.js +7 -0
  127. package/dist/tui/tool-stream-pane.js +7 -0
  128. package/dist/tui/update-banner.js +20 -2
  129. package/docs/examples/codegraph.mcp.json +10 -0
  130. 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({ cwd: opts.cwd, worktreePath: abs, baseSha });
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
- : `promoted ${result.value.filesChanged} files from ${abs}`,
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 {