@pugi/cli 0.1.0-beta.87 → 0.1.0-beta.89

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 (68) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/LICENSE +1 -1
  3. package/dist/core/agents/registry.js +1 -1
  4. package/dist/core/auth/env-provider.js +1 -1
  5. package/dist/core/checkpoints/shadow-git.js +1 -1
  6. package/dist/core/context/compaction.js +1 -1
  7. package/dist/core/context/markdown-traverse.js +1 -1
  8. package/dist/core/credentials.js +1 -1
  9. package/dist/core/denial-tracking/state.js +1 -1
  10. package/dist/core/edits/fuzzy-ladder.js +1 -1
  11. package/dist/core/edits/layer-a-fuzzy-apply.js +1 -1
  12. package/dist/core/engine/anvil-client.js +76 -2
  13. package/dist/core/engine/native-pugi.js +1 -1
  14. package/dist/core/engine/tool-bridge.js +436 -0
  15. package/dist/core/hooks/events.js +3 -1
  16. package/dist/core/hooks/registry.js +3 -0
  17. package/dist/core/hooks/worktree-events.js +158 -0
  18. package/dist/core/lsp/client.js +453 -0
  19. package/dist/core/lsp/server-detect.js +173 -0
  20. package/dist/core/lsp/symbol-cache.js +162 -0
  21. package/dist/core/lsp/symbol-tools.js +296 -4
  22. package/dist/core/mcp/server-tools.js +1 -1
  23. package/dist/core/mcp/server.js +1 -1
  24. package/dist/core/memory/secret-scanner.js +6 -6
  25. package/dist/core/onboarding/ensure-initialized.js +1 -1
  26. package/dist/core/plans/plan-artifact.js +2 -2
  27. package/dist/core/repl/ask.js +1 -1
  28. package/dist/core/repl/cap-warning.js +1 -1
  29. package/dist/core/repl/session.js +3 -3
  30. package/dist/core/repl/slash-commands.js +1 -1
  31. package/dist/core/routing/pre-flight-estimator.js +1 -1
  32. package/dist/core/settings.js +38 -0
  33. package/dist/core/worktree/include-parser.js +249 -0
  34. package/dist/index.js +8 -0
  35. package/dist/runtime/cli.js +176 -28
  36. package/dist/runtime/commands/agents.js +1 -1
  37. package/dist/runtime/commands/config.js +41 -7
  38. package/dist/runtime/commands/hooks.js +3 -0
  39. package/dist/runtime/commands/review-consensus.js +1 -1
  40. package/dist/runtime/sigint-guard.js +272 -0
  41. package/dist/runtime/version.js +1 -1
  42. package/dist/runtime/worktree-bootstrap.js +579 -0
  43. package/dist/skills/bundled/batch.js +2 -2
  44. package/dist/skills/bundled/index.js +3 -3
  45. package/dist/skills/bundled/loop.js +2 -2
  46. package/dist/skills/bundled/remember.js +1 -1
  47. package/dist/skills/bundled/simplify.js +1 -1
  48. package/dist/skills/bundled/skillify.js +2 -2
  49. package/dist/skills/bundled/stuck.js +1 -1
  50. package/dist/skills/bundled/verify.js +2 -2
  51. package/dist/testing/vcr.js +2 -2
  52. package/dist/tools/ask-user-question.js +66 -0
  53. package/dist/tools/bash.js +2 -2
  54. package/dist/tools/lsp-tools.js +377 -1
  55. package/dist/tools/powershell.js +1 -1
  56. package/dist/tools/registry.js +23 -0
  57. package/dist/tui/ask-user-question-chips.js +257 -0
  58. package/dist/tui/input-box.js +1 -1
  59. package/dist/tui/render.js +1 -1
  60. package/dist/tui/repl.js +1 -1
  61. package/dist/tui/status-bar.js +1 -1
  62. package/dist/tui/update-banner.js +1 -1
  63. package/dist/tui/welcome-data.js +4 -4
  64. package/package.json +4 -3
  65. package/test/scenarios/compact-force.scenario.txt +3 -2
  66. package/test/scenarios/identity.scenario.txt +6 -5
  67. package/test/scenarios/persona-handoff.scenario.txt +2 -1
  68. package/test/scenarios/walkback.scenario.txt +6 -6
package/CHANGELOG.md CHANGED
@@ -7,6 +7,42 @@ releases listed first. Section format: `## [<version>] - <YYYY-MM-DD>`.
7
7
  The bundled `pugi release-notes` command parses this file and renders sections
8
8
  strictly newer than `~/.pugi/.last-seen-version` after every upgrade.
9
9
 
10
+ ## [0.1.0-beta.88] - 2026-06-02
11
+
12
+ ### Fixed
13
+ - **Pugi identity intro no longer chants on later turns**. The output gate now
14
+ strips the canonical long-form intro ("I'm Pugi - your engineering copilot.
15
+ Tell me what you need...") and its Russian variant on mid-thread replies.
16
+ Operators were seeing the intro re-emit on turn 2+ after a session-resume
17
+ or autonomous tick.
18
+ - **Ctrl+C double-press exit guard**. A single Ctrl+C in the REPL no longer
19
+ kills the CLI. First press prompts "Press Ctrl+C again to exit (within 2s)
20
+ or any key to continue". Second press within 2s exits cleanly; any other
21
+ key cancels. Headless mode emits a `session-end` envelope and exits 0.
22
+ - **Persona no longer over-clarifies trivial creative tasks**. Asking for a
23
+ well-known game / demo / todo app now picks reasonable defaults and starts
24
+ building in one turn. Ambiguous tasks (auth, deploy targets) still ask
25
+ ≤ 3 short choices via the `ask_user_question` tool.
26
+
27
+ ### Added
28
+ - **Short-format AskUserQuestion chip renderer**. Up to 3 questions render
29
+ side-by-side as Ink chips with ▸ default highlight, ↑↓ in-question nav,
30
+ ←→/Tab between questions, 1-9 jump, [s] skip-with-default, [Esc] cancel,
31
+ [Enter] commit. Labels truncate at 5 words / 22 chars. Caller can opt
32
+ into a non-TTY numbered fallback via `forceFallback`.
33
+
34
+ ### Security
35
+ - **npm publish leak-gate hardening**. `prepublishOnly` now runs a
36
+ banned-string scan against the staged tarball (`tools/scrub/scan-tarball.sh`)
37
+ covering personal names, competitor refs, internal codenames, engineering
38
+ provenance, absolute paths, brand legacy, and secret-token shapes.
39
+ Per-line `[pugi-leak-ok]` allowlist marker for legitimate references;
40
+ path allowlist for LICENSE / THIRD_PARTY_NOTICES files where MIT requires
41
+ copyright-holder name. Synthetic injection regression test runs in CI.
42
+
43
+ ### Chore
44
+ - MIT LICENSE copyright holder set to `Pugi.io` (was personal name).
45
+
10
46
  ## [0.1.0-beta.26] - 2026-05-27
11
47
 
12
48
  ### Added
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 Yurii Bulakh
3
+ Copyright (c) 2026 Pugi.io
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -30,7 +30,7 @@ function requirePersona(slug) {
30
30
  /**
31
31
  * CLI-only role-to-persona mapping. Roles are dispatcher-facing strings;
32
32
  * personas come from the brand-canonical THE_TEN. Vera (qa) intentionally
33
- * dual-roles as verifier + reviewer per ADR-0056 — the cabinet's review
33
+ * dual-roles as verifier + reviewer per — the cabinet's review
34
34
  * pipeline already merges the two surfaces.
35
35
  */
36
36
  export const SUBAGENT_REGISTRY = [
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * `pugi login --provider env` — env-var auth path ().
3
3
  *
4
- * the upstream tool, Codex CLI, and gh CLI all ship a way to authenticate via
4
+ * the upstream tool, peer CLI, and gh CLI all ship a way to authenticate via
5
5
  * an environment variable so CI / container / scripted contexts can
6
6
  * skip the device flow entirely. This module backs that path:
7
7
  *
@@ -2,7 +2,7 @@
2
2
  * Per-task shadow git repo — file-state checkpoint surface.
3
3
  *
4
4
  * Inspired by Cline `CheckpointTracker.ts` (Apache-2.0).
5
- * Clean-room TypeScript implementation following Pugi conventions.
5
+ * independent implementation TypeScript implementation following Pugi conventions.
6
6
  *
7
7
  * Goal: every Pugi-orchestrated file mutation lands in a per-task
8
8
  * shadow git history kept entirely separate from the user's real
@@ -2,7 +2,7 @@
2
2
  * Six-tier context compaction engine for the Pugi CLI agent loop.
3
3
  *
4
4
  * Spec: `docs/research/pugi-cli-corpus/patterns/context-compaction.md`,
5
- * sprint slot: ADR-0056 §.
5
+ * sprint slot: §.
6
6
  *
7
7
  * Tiers and triggers (selectTier rules):
8
8
  *
@@ -2,7 +2,7 @@
2
2
  * Per-directory PUGI.md / AGENTS.md / CLAUDE.md / GEMINI.md traverse-up
3
3
  * loader — β5a R4+P5.
4
4
  *
5
- * the upstream tool, Codex CLI, and Gemini CLI all support a "walk up from
5
+ * the upstream tool, peer CLI, and Gemini CLI all support a "walk up from
6
6
  * cwd to the workspace root, pick up agent-context markdown at every
7
7
  * level" pattern. Without this, a `pugi explain` invoked from
8
8
  * `apps/admin-api/` cannot see project-local conventions encoded in
@@ -6,7 +6,7 @@ import { z } from 'zod';
6
6
  * Local credentials store for the Pugi CLI.
7
7
  *
8
8
  * Stored at `~/.pugi/credentials.json` (mode 0o600). Mirrors the convention
9
- * Codex CLI uses (`~/.codex/auth.json`) and matches gh CLI's per-host
9
+ * peer CLI uses (`~/.codex/auth.json`) and matches gh CLI's per-host
10
10
  * token model. The store is intentionally file-based, not OS keychain —
11
11
  * adding the native `keytar` dep would force per-platform native builds
12
12
  * across npm distribution and complicate the install path. The 0600
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * L11 — DenialTrackingState surface (the upstream tool parity).
3
3
  *
4
- * Per the upstream / anarchic-CC the upstream behavior (``
4
+ * Per the upstream / the upstream behavior (``
5
5
  * §5.2): the upstream tool's `QueryEngine.ts` maintains a per-session
6
6
  * `DenialTrackingState` that records every tool-dispatch denial.
7
7
  * Subsequent turns receive a compact reminder so the model does not
@@ -30,7 +30,7 @@
30
30
  * >= 0.8 to count as a match; below that we fail LOUD rather
31
31
  * than write the wrong region.
32
32
  *
33
- * Inspired by Aider editblock_coder.py (Apache-2.0). Clean-room
33
+ * Inspired by Aider editblock_coder.py (Apache-2.0). independent implementation
34
34
  * implementation; no Aider source code copied.
35
35
  *
36
36
  * Pure functions throughout — every tier returns a structured result
@@ -19,7 +19,7 @@
19
19
  * - `identical_replacement` — search and replace are identical;
20
20
  * would no-op; surfaced LOUD.
21
21
  *
22
- * Inspired by Aider editblock_coder.py (Apache-2.0). Clean-room
22
+ * Inspired by Aider editblock_coder.py (Apache-2.0). independent implementation
23
23
  * implementation; no Aider source code copied.
24
24
  */
25
25
  import { existsSync, readFileSync, renameSync, writeFileSync, unlinkSync } from 'node:fs';
@@ -64,11 +64,22 @@ export class AnvilEngineLoopClient {
64
64
  // PR-CLI-SERVER-VERSION-HANDSHAKE . Stamp the outbound
65
65
  // X-Pugi-Cli-Version header so the admin-api middleware can
66
66
  // decide whether to honour, soft-warn, or 426 this request.
67
- const outboundHeaders = injectClientVersionHeader({
67
+ // PUGI-260: also stamp `X-Pugi-Context-Tier: 1m` when the
68
+ // operator opted into the long-context lane. The server reads
69
+ // either the body's `contextTier` field OR this header (header is
70
+ // a fallback for older runtimes / non-CLI clients), so emitting
71
+ // both is harmless и belt-and-suspenders. Only emitted for the
72
+ // `'1m'` value — the absence of the header is wire-equivalent к
73
+ // `'standard'`, keeping the default-lane path header-free.
74
+ const baseHeaders = {
68
75
  'content-type': 'application/json',
69
76
  authorization: `Bearer ${this.config.apiKey}`,
70
77
  'user-agent': 'pugi-cli/0.0.1',
71
- }, PUGI_CLI_VERSION);
78
+ };
79
+ if (options.contextTier === '1m') {
80
+ baseHeaders['x-pugi-context-tier'] = '1m';
81
+ }
82
+ const outboundHeaders = injectClientVersionHeader(baseHeaders, PUGI_CLI_VERSION);
72
83
  const res = await fetch(url, {
73
84
  method: 'POST',
74
85
  headers: outboundHeaders,
@@ -246,6 +257,23 @@ export class AnvilEngineLoopClient {
246
257
  message: 'runtime rate limit reached for this tenant',
247
258
  };
248
259
  }
260
+ // PUGI-490 (2026-06-03): structured envelope for upstream-proxy
261
+ // static error pages. When the response body is HTML (the upstream
262
+ // proxy's static 5xx page rather than a NestJS JSON envelope), the
263
+ // raw truncated HTML is useless to operators — it cannot point at
264
+ // what failed. Parse the `cf-ray` request id so the operator can
265
+ // look the request up in the proxy dashboard, and surface a
266
+ // remediation hint pointing at the engine VM logs.
267
+ const cfDetails = detectUpstreamProxyError(res, text);
268
+ if (cfDetails) {
269
+ return {
270
+ stop: 'error',
271
+ code: 'failed',
272
+ message: `runtime error (cf-ray: ${cfDetails.cfRay ?? 'unknown'}) — ` +
273
+ `upstream proxy returned ${res.status} static page. ` +
274
+ `Check engine VM logs for the correlating request id.`,
275
+ };
276
+ }
249
277
  return {
250
278
  stop: 'error',
251
279
  code: 'failed',
@@ -267,4 +295,50 @@ export class AnvilEngineLoopClient {
267
295
  }
268
296
  }
269
297
  }
298
+ /**
299
+ * PUGI-490 (2026-06-03): detect when the response body is a static HTML
300
+ * error page emitted by the upstream proxy (rather than a JSON envelope
301
+ * produced by admin-api). Returns the cf-ray request id when matched, so
302
+ * the CLI can surface a meaningful runtime-error envelope instead of
303
+ * truncating raw HTML in front of the operator.
304
+ *
305
+ * Heuristic: the body starts with `<!DOCTYPE` or `<html`, or carries the
306
+ * `cf-ray` response header. We require BOTH conditions when the body is
307
+ * non-empty (defense against admin-api accidentally emitting an HTML
308
+ * fragment) and accept just the header when the body is empty (some
309
+ * proxy 5xx variants ship empty bodies). The function is intentionally
310
+ * permissive — false positives produce a clearer error than false
311
+ * negatives, and the message still includes "upstream proxy" so the
312
+ * operator knows the call did not reach a Pugi controller.
313
+ *
314
+ * Exported for unit testing; not for runtime callers.
315
+ */
316
+ export function detectUpstreamProxyError(res, body) {
317
+ if (res.status < 500)
318
+ return null;
319
+ // Pull cf-ray via the same getter shim the version-interceptor uses;
320
+ // it tolerates both real `Response.headers.get` and fixture/stub
321
+ // headers represented as plain objects.
322
+ const h = res.headers;
323
+ const readHeader = (name) => {
324
+ if (h && typeof h.get === 'function') {
325
+ return h.get(name);
326
+ }
327
+ if (h && typeof h === 'object') {
328
+ const lowered = h[name.toLowerCase()];
329
+ return lowered ?? null;
330
+ }
331
+ return null;
332
+ };
333
+ const cfRay = readHeader('cf-ray');
334
+ const bodyTrimmed = (body ?? '').trimStart();
335
+ const looksHtml = bodyTrimmed.startsWith('<!DOCTYPE') ||
336
+ bodyTrimmed.startsWith('<!doctype') ||
337
+ bodyTrimmed.startsWith('<html');
338
+ if (looksHtml)
339
+ return { cfRay };
340
+ if (cfRay && bodyTrimmed.length === 0)
341
+ return { cfRay };
342
+ return null;
343
+ }
270
344
  //# sourceMappingURL=anvil-client.js.map
@@ -1127,7 +1127,7 @@ function extractPathArg(raw) {
1127
1127
  const parsed = JSON.parse(raw);
1128
1128
  if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
1129
1129
  const obj = parsed;
1130
- // Accept canonical `path` OR the Claude-Code-trained `filePath`
1130
+ // Accept canonical `path` OR the peer-CLI-trained `filePath`
1131
1131
  // alias so the filesChanged summary captures writes regardless of
1132
1132
  // which key the model emitted. Without the alias the operator
1133
1133
  // sees "Files modified: none" even when a write actually landed,