@pugi/cli 0.1.0-beta.21 → 0.1.0-beta.23

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 (63) hide show
  1. package/dist/core/auth/env-provider.js +238 -0
  2. package/dist/core/bare-mode/index.js +107 -0
  3. package/dist/core/diagnostics/probes/bare-mode.js +42 -0
  4. package/dist/core/diagnostics/probes/pugi-md.js +89 -0
  5. package/dist/core/engine/native-pugi.js +55 -11
  6. package/dist/core/engine/prompts.js +30 -2
  7. package/dist/core/engine/tool-bridge.js +32 -0
  8. package/dist/core/feedback/queue.js +177 -0
  9. package/dist/core/feedback/submitter.js +145 -0
  10. package/dist/core/onboarding/marker.js +111 -0
  11. package/dist/core/onboarding/telemetry-state.js +108 -0
  12. package/dist/core/output-style/presets.js +176 -0
  13. package/dist/core/output-style/state.js +185 -0
  14. package/dist/core/permissions/index.js +1 -1
  15. package/dist/core/permissions/state.js +55 -0
  16. package/dist/core/pugi-md/context-injector.js +76 -0
  17. package/dist/core/pugi-md/walk-up.js +207 -0
  18. package/dist/core/release-notes/parser.js +241 -0
  19. package/dist/core/release-notes/state.js +116 -0
  20. package/dist/core/repl/session.js +482 -12
  21. package/dist/core/repl/slash-commands.js +134 -1
  22. package/dist/core/repl/workspace-context.js +22 -0
  23. package/dist/core/share/formatter.js +271 -0
  24. package/dist/core/share/redactor.js +221 -0
  25. package/dist/core/share/uploader.js +267 -0
  26. package/dist/core/theme/context.js +91 -0
  27. package/dist/core/theme/presets.js +228 -0
  28. package/dist/core/theme/state.js +181 -0
  29. package/dist/core/todos/invariant.js +10 -0
  30. package/dist/core/todos/state.js +177 -0
  31. package/dist/core/vim/keymap.js +288 -0
  32. package/dist/core/vim/state.js +92 -0
  33. package/dist/runtime/cli.js +603 -15
  34. package/dist/runtime/commands/doctor.js +21 -0
  35. package/dist/runtime/commands/feedback.js +184 -0
  36. package/dist/runtime/commands/onboarding.js +275 -0
  37. package/dist/runtime/commands/plan.js +143 -0
  38. package/dist/runtime/commands/release-notes.js +229 -0
  39. package/dist/runtime/commands/share.js +316 -0
  40. package/dist/runtime/commands/stickers.js +82 -0
  41. package/dist/runtime/commands/style.js +194 -0
  42. package/dist/runtime/commands/theme.js +196 -0
  43. package/dist/runtime/commands/vim.js +140 -0
  44. package/dist/runtime/version.js +1 -1
  45. package/dist/tools/registry.js +8 -0
  46. package/dist/tools/todo-write.js +184 -0
  47. package/dist/tui/compact-banner.js +28 -1
  48. package/dist/tui/conversation-pane.js +13 -0
  49. package/dist/tui/doctor-table.js +32 -17
  50. package/dist/tui/feedback-prompt.js +156 -0
  51. package/dist/tui/onboarding-wizard.js +240 -0
  52. package/dist/tui/repl-render.js +26 -3
  53. package/dist/tui/repl.js +9 -1
  54. package/dist/tui/stickers-art.js +136 -0
  55. package/dist/tui/style-table.js +28 -0
  56. package/dist/tui/theme-table.js +29 -0
  57. package/dist/tui/vim-input.js +267 -0
  58. package/package.json +2 -2
  59. package/dist/core/engine/compaction-hook.js +0 -154
  60. package/dist/core/init/scaffold.js +0 -195
  61. package/dist/core/repl/codebase-survey.js +0 -308
  62. package/dist/core/repl/init-interview.js +0 -457
  63. package/dist/core/repl/onboarding-state.js +0 -297
@@ -1,308 +0,0 @@
1
- /**
2
- * Codebase survey for the `/init` interview - Phase 2.
3
- *
4
- * Inspired by Claude Code's /init Phase 2 (the upstream spawns a
5
- * Task-tool subagent to read manifest files + CI config + existing
6
- * agent rules and produce a structured "what this repo is" digest).
7
- * Independent implementation: Pugi's subagent infra is admin-api
8
- * scheduled and not available in the local REPL boot path, so the
9
- * survey runs as a direct filesystem scan instead. The shape of the
10
- * output matches the upstream pattern - manifest, languages, build /
11
- * test / lint commands, existing AI-tool configs - so the downstream
12
- * Phase 3 / Phase 4 logic stays portable.
13
- *
14
- * # Design notes
15
- *
16
- * - Pure fs reads. No spawn, no network, no LLM call. Safe to run on
17
- * every `/init` invocation without rate-limit concern.
18
- * - Bounded: every read caps at 16 KB to defend against an enormous
19
- * manifest pinning memory. Real package.json / pyproject.toml are
20
- * well under that.
21
- * - Defensive: a missing or unreadable file maps to `undefined` in the
22
- * returned record. Phase 3 treats unknowns as "ask the operator".
23
- * - Manifest grammar is closed: package.json (Node) and pyproject.toml
24
- * (Python) are recognised explicitly because Pugi customers ship one
25
- * of those nine times out of ten. Cargo.toml / go.mod / pom.xml are
26
- * detected by filename only - we surface "rust"/"go"/"java" as the
27
- * language hint but do not parse them, because the Phase 3 question
28
- * set asks for build commands directly when the manifest is opaque.
29
- *
30
- * # What we collect
31
- *
32
- * 1. `manifest`: which manifest file was found (closed enum).
33
- * 2. `languages`: deduped list inferred from manifest + file extension
34
- * heuristics under the workspace root (one-level deep scan).
35
- * 3. `packageManager`: pnpm / npm / yarn (Node only) or `unknown`.
36
- * 4. `buildCommand` / `testCommand` / `lintCommand`: parsed out of
37
- * `package.json` scripts when a Node manifest is present.
38
- * 5. `aiToolConfigs`: a record of which sibling-agent config files
39
- * already exist (CLAUDE.md, AGENTS.md, .cursorrules,
40
- * .github/copilot-instructions.md, .windsurfrules, .clinerules,
41
- * .mcp.json). Phase 4 mines these for "important parts" without
42
- * duplicating them into PUGI.md.
43
- * 6. `hasReadme`, `hasGit`, `hasCi`: simple booleans for the
44
- * gap-question logic.
45
- * 7. `hasExistingPugiMd`: true when re-running `/init` against a
46
- * workspace that already produced PUGI.md.
47
- *
48
- * # Why not spawn a Pugi subagent
49
- *
50
- * The Pugi subagent dispatcher (apps/pugi-cli/src/core/subagents/)
51
- * speaks to admin-api over the SSE transport. Running the codebase
52
- * survey through that path would (a) burn a tenant token quota on
53
- * every `/init`, (b) require an online connection, and (c) round-trip
54
- * structured data through the persona prompt - which is the wrong
55
- * tool for "list which files exist". A direct fs scan is faster,
56
- * deterministic, and works offline. The upstream Task-tool decision
57
- * makes sense in a hosted product where every operation is metered;
58
- * Pugi runs locally so we keep the survey local too.
59
- */
60
- import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
61
- import { join } from 'node:path';
62
- /**
63
- * Maximum bytes the survey reads from any single file. Real manifests
64
- * are tiny (<8 KB); this cap defends against a hostile or accidental
65
- * gigabyte JSON pinning the REPL boot.
66
- */
67
- const MAX_READ_BYTES = 16 * 1024;
68
- /**
69
- * Top-level directory scan budget. The survey looks at the workspace
70
- * root + at most this many entries when inferring languages from file
71
- * extensions. Deep walks are not needed - the manifest is the source
72
- * of truth for the active stack.
73
- */
74
- const MAX_TOP_LEVEL_ENTRIES = 200;
75
- /**
76
- * Run a codebase survey against `workspaceRoot`. Pure: returns a
77
- * snapshot record; the caller decides how to render it.
78
- */
79
- export function surveyCodebase(workspaceRoot) {
80
- const errors = [];
81
- const manifest = detectManifest(workspaceRoot);
82
- const packageJson = manifest === 'package.json'
83
- ? safeReadJson(join(workspaceRoot, 'package.json'), errors)
84
- : undefined;
85
- const packageManager = inferPackageManager(workspaceRoot, packageJson);
86
- const languages = inferLanguages(workspaceRoot, manifest, errors);
87
- const scripts = (packageJson && typeof packageJson === 'object' && packageJson !== null
88
- ? packageJson.scripts
89
- : undefined) ?? {};
90
- const buildCommand = pickScript(scripts, ['build', 'compile']);
91
- const testCommand = pickScript(scripts, ['test', 'tests']);
92
- const lintCommand = pickScript(scripts, ['lint', 'check']);
93
- const formatCommand = pickScript(scripts, ['format', 'fmt', 'prettier']);
94
- const aiToolConfigs = scanAiToolConfigs(workspaceRoot);
95
- return {
96
- workspaceRoot,
97
- manifest,
98
- packageManager,
99
- languages,
100
- buildCommand,
101
- testCommand,
102
- lintCommand,
103
- formatCommand,
104
- aiToolConfigs,
105
- hasReadme: existsSafe(join(workspaceRoot, 'README.md')) ||
106
- existsSafe(join(workspaceRoot, 'readme.md')),
107
- hasGit: existsSafe(join(workspaceRoot, '.git')),
108
- hasCi: detectCi(workspaceRoot),
109
- hasExistingPugiMd: aiToolConfigs['PUGI.md'],
110
- readErrors: errors,
111
- };
112
- }
113
- /* ------------------------------------------------------------------ */
114
- /* Manifest detection */
115
- /* ------------------------------------------------------------------ */
116
- const MANIFEST_PROBE_ORDER = Object.freeze([
117
- 'package.json',
118
- 'pyproject.toml',
119
- 'Cargo.toml',
120
- 'go.mod',
121
- 'pom.xml',
122
- 'Gemfile',
123
- 'composer.json',
124
- ]);
125
- function detectManifest(root) {
126
- for (const candidate of MANIFEST_PROBE_ORDER) {
127
- if (existsSafe(join(root, candidate)))
128
- return candidate;
129
- }
130
- return 'unknown';
131
- }
132
- function inferPackageManager(root, packageJson) {
133
- // package.json `packageManager` field wins when present (corepack convention).
134
- if (packageJson &&
135
- typeof packageJson === 'object' &&
136
- packageJson !== null &&
137
- 'packageManager' in packageJson) {
138
- const declared = packageJson.packageManager;
139
- if (typeof declared === 'string') {
140
- if (declared.startsWith('pnpm@'))
141
- return 'pnpm';
142
- if (declared.startsWith('yarn@'))
143
- return 'yarn';
144
- if (declared.startsWith('npm@'))
145
- return 'npm';
146
- if (declared.startsWith('bun@'))
147
- return 'bun';
148
- }
149
- }
150
- // Lockfile fallback.
151
- if (existsSafe(join(root, 'pnpm-lock.yaml')))
152
- return 'pnpm';
153
- if (existsSafe(join(root, 'yarn.lock')))
154
- return 'yarn';
155
- if (existsSafe(join(root, 'bun.lockb')) || existsSafe(join(root, 'bun.lock')))
156
- return 'bun';
157
- if (existsSafe(join(root, 'package-lock.json')))
158
- return 'npm';
159
- return 'unknown';
160
- }
161
- /* ------------------------------------------------------------------ */
162
- /* Language inference */
163
- /* ------------------------------------------------------------------ */
164
- const EXT_TO_LANG = Object.freeze({
165
- '.ts': 'typescript',
166
- '.tsx': 'typescript',
167
- '.js': 'javascript',
168
- '.jsx': 'javascript',
169
- '.mjs': 'javascript',
170
- '.cjs': 'javascript',
171
- '.py': 'python',
172
- '.rs': 'rust',
173
- '.go': 'go',
174
- '.java': 'java',
175
- '.kt': 'kotlin',
176
- '.swift': 'swift',
177
- '.rb': 'ruby',
178
- '.php': 'php',
179
- '.cs': 'csharp',
180
- '.cpp': 'cpp',
181
- '.c': 'c',
182
- });
183
- const MANIFEST_TO_LANG = Object.freeze({
184
- 'package.json': ['javascript'],
185
- 'pyproject.toml': ['python'],
186
- 'Cargo.toml': ['rust'],
187
- 'go.mod': ['go'],
188
- 'pom.xml': ['java'],
189
- 'Gemfile': ['ruby'],
190
- 'composer.json': ['php'],
191
- 'unknown': [],
192
- });
193
- function inferLanguages(root, manifest, errors) {
194
- const collected = new Set(MANIFEST_TO_LANG[manifest]);
195
- // Top-level extension scan, bounded.
196
- try {
197
- const entries = readdirSync(root);
198
- let scanned = 0;
199
- for (const entry of entries) {
200
- if (scanned >= MAX_TOP_LEVEL_ENTRIES)
201
- break;
202
- scanned += 1;
203
- // Skip dotfiles + common dependency dirs - they pollute the
204
- // language inference with build/cache content.
205
- if (entry.startsWith('.') || entry === 'node_modules' || entry === 'dist')
206
- continue;
207
- const dot = entry.lastIndexOf('.');
208
- if (dot <= 0)
209
- continue;
210
- const ext = entry.slice(dot);
211
- const lang = EXT_TO_LANG[ext];
212
- if (lang)
213
- collected.add(lang);
214
- }
215
- }
216
- catch (error) {
217
- errors.push(`readdir ${root}: ${normalizeError(error)}`);
218
- }
219
- // `typescript` implies `javascript` runtime; keep both so the
220
- // interview can ask "compiled-with vs run-with" if needed.
221
- return Array.from(collected).sort();
222
- }
223
- /* ------------------------------------------------------------------ */
224
- /* Script picker */
225
- /* ------------------------------------------------------------------ */
226
- function pickScript(scripts, candidates) {
227
- for (const key of candidates) {
228
- const value = scripts[key];
229
- if (typeof value === 'string' && value.trim().length > 0) {
230
- // Surface the npm-style invocation so Phase 4 can quote it
231
- // verbatim. The package manager name is filled in by the caller
232
- // once it has resolved `packageManager`.
233
- return key;
234
- }
235
- }
236
- return undefined;
237
- }
238
- /* ------------------------------------------------------------------ */
239
- /* AI tool config scan */
240
- /* ------------------------------------------------------------------ */
241
- const AI_TOOL_CONFIG_PATHS = Object.freeze([
242
- 'CLAUDE.md',
243
- 'CLAUDE.local.md',
244
- 'AGENTS.md',
245
- '.cursorrules',
246
- '.cursor/rules',
247
- '.github/copilot-instructions.md',
248
- '.windsurfrules',
249
- '.clinerules',
250
- '.mcp.json',
251
- 'PUGI.md',
252
- 'PUGI.local.md',
253
- ]);
254
- function scanAiToolConfigs(root) {
255
- const result = {};
256
- for (const rel of AI_TOOL_CONFIG_PATHS) {
257
- result[rel] = existsSafe(join(root, rel));
258
- }
259
- return Object.freeze(result);
260
- }
261
- /* ------------------------------------------------------------------ */
262
- /* CI detection */
263
- /* ------------------------------------------------------------------ */
264
- const CI_PROBE_PATHS = Object.freeze([
265
- '.github/workflows',
266
- '.gitlab-ci.yml',
267
- '.circleci/config.yml',
268
- 'azure-pipelines.yml',
269
- '.travis.yml',
270
- '.buildkite',
271
- ]);
272
- function detectCi(root) {
273
- return CI_PROBE_PATHS.some((rel) => existsSafe(join(root, rel)));
274
- }
275
- /* ------------------------------------------------------------------ */
276
- /* Safe IO helpers */
277
- /* ------------------------------------------------------------------ */
278
- function existsSafe(path) {
279
- try {
280
- return existsSync(path);
281
- }
282
- catch {
283
- return false;
284
- }
285
- }
286
- function safeReadJson(path, errors) {
287
- try {
288
- const stats = statSync(path);
289
- if (!stats.isFile())
290
- return undefined;
291
- if (stats.size > MAX_READ_BYTES) {
292
- errors.push(`oversize ${path}: ${stats.size} bytes`);
293
- return undefined;
294
- }
295
- const raw = readFileSync(path, 'utf8');
296
- return JSON.parse(raw);
297
- }
298
- catch (error) {
299
- errors.push(`read ${path}: ${normalizeError(error)}`);
300
- return undefined;
301
- }
302
- }
303
- function normalizeError(error) {
304
- if (error instanceof Error)
305
- return error.message;
306
- return String(error);
307
- }
308
- //# sourceMappingURL=codebase-survey.js.map