@pugi/cli 0.1.0-beta.5 → 0.1.0-beta.51

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 (264) hide show
  1. package/THIRD_PARTY_NOTICES.md +40 -0
  2. package/assets/pugi-mascot.ansi +15 -25
  3. package/assets/pugi-prozr2-mascot.ansi +9 -0
  4. package/bin/run.js +33 -1
  5. package/dist/commands/jobs-watch.js +201 -0
  6. package/dist/commands/jobs.js +15 -0
  7. package/dist/commands/smoke.js +133 -0
  8. package/dist/core/agent-progress/cleanup.js +134 -0
  9. package/dist/core/agent-progress/schema.js +144 -0
  10. package/dist/core/agent-progress/writer.js +101 -0
  11. package/dist/core/artifact-chain/dispatcher.js +148 -0
  12. package/dist/core/artifact-chain/exporter.js +164 -0
  13. package/dist/core/artifact-chain/state.js +243 -0
  14. package/dist/core/artifact-chain/steps.js +169 -0
  15. package/dist/core/auth/ensure-authenticated.js +129 -0
  16. package/dist/core/auth/env-provider.js +238 -0
  17. package/dist/core/auto-update/channels.js +122 -0
  18. package/dist/core/auto-update/checker.js +241 -0
  19. package/dist/core/auto-update/state.js +235 -0
  20. package/dist/core/bare-mode/index.js +107 -0
  21. package/dist/core/bash-classifier.js +400 -4
  22. package/dist/core/checkpoint/resumer.js +149 -0
  23. package/dist/core/checkpoint/rewinder.js +291 -0
  24. package/dist/core/codegraph/decision-store.js +248 -0
  25. package/dist/core/codegraph/detect-repo.js +459 -0
  26. package/dist/core/codegraph/install.js +134 -0
  27. package/dist/core/codegraph/offer-hook.js +220 -0
  28. package/dist/core/compact/auto-trigger.js +96 -0
  29. package/dist/core/compact/buffer-rewriter.js +115 -0
  30. package/dist/core/compact/summarizer.js +208 -0
  31. package/dist/core/compact/token-counter.js +108 -0
  32. package/dist/core/consensus/diff-capture.js +112 -3
  33. package/dist/core/context/index.js +7 -0
  34. package/dist/core/context/markdown-traverse.js +255 -0
  35. package/dist/core/cost/rate-card.js +129 -0
  36. package/dist/core/cost/tracker.js +221 -0
  37. package/dist/core/denial-tracking/index.js +8 -0
  38. package/dist/core/denial-tracking/state.js +264 -0
  39. package/dist/core/diagnostics/probe-runner.js +93 -0
  40. package/dist/core/diagnostics/probes/api.js +46 -0
  41. package/dist/core/diagnostics/probes/auth.js +86 -0
  42. package/dist/core/diagnostics/probes/bare-mode.js +42 -0
  43. package/dist/core/diagnostics/probes/cli-version.js +127 -0
  44. package/dist/core/diagnostics/probes/config.js +72 -0
  45. package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
  46. package/dist/core/diagnostics/probes/disk.js +81 -0
  47. package/dist/core/diagnostics/probes/git.js +65 -0
  48. package/dist/core/diagnostics/probes/hooks.js +118 -0
  49. package/dist/core/diagnostics/probes/mcp.js +75 -0
  50. package/dist/core/diagnostics/probes/node.js +59 -0
  51. package/dist/core/diagnostics/probes/pnpm.js +36 -0
  52. package/dist/core/diagnostics/probes/pugi-md.js +89 -0
  53. package/dist/core/diagnostics/probes/sandbox.js +40 -0
  54. package/dist/core/diagnostics/probes/session.js +74 -0
  55. package/dist/core/diagnostics/probes/status-snapshot.js +488 -0
  56. package/dist/core/diagnostics/probes/workspace.js +63 -0
  57. package/dist/core/diagnostics/types.js +70 -0
  58. package/dist/core/dispatch/cache-cleanup.js +197 -0
  59. package/dist/core/dispatch/cache-handoff.js +295 -0
  60. package/dist/core/edits/dispatch.js +218 -2
  61. package/dist/core/edits/journal.js +199 -0
  62. package/dist/core/edits/layer-d-ast.js +557 -14
  63. package/dist/core/edits/verify-hook.js +273 -0
  64. package/dist/core/edits/worktree.js +322 -0
  65. package/dist/core/engine/anvil-client.js +115 -5
  66. package/dist/core/engine/auto-compact.js +179 -0
  67. package/dist/core/engine/budgets.js +155 -0
  68. package/dist/core/engine/context-prefix.js +155 -0
  69. package/dist/core/engine/intent.js +260 -0
  70. package/dist/core/engine/native-pugi.js +897 -211
  71. package/dist/core/engine/prompts.js +88 -2
  72. package/dist/core/engine/strip-internal-fields.js +124 -0
  73. package/dist/core/engine/tool-bridge.js +1045 -36
  74. package/dist/core/feedback/queue.js +177 -0
  75. package/dist/core/feedback/submitter.js +145 -0
  76. package/dist/core/file-cache.js +113 -1
  77. package/dist/core/hooks/events.js +44 -0
  78. package/dist/core/hooks/index.js +15 -0
  79. package/dist/core/hooks/registry.js +213 -0
  80. package/dist/core/hooks/runner.js +236 -0
  81. package/dist/core/hooks/v2/event-emitter.js +115 -0
  82. package/dist/core/hooks/v2/executor.js +282 -0
  83. package/dist/core/hooks/v2/index.js +25 -0
  84. package/dist/core/hooks/v2/lifecycle.js +104 -0
  85. package/dist/core/hooks/v2/loader.js +216 -0
  86. package/dist/core/hooks/v2/matcher.js +125 -0
  87. package/dist/core/hooks/v2/trust.js +143 -0
  88. package/dist/core/hooks/v2/types.js +86 -0
  89. package/dist/core/lsp/cache.js +105 -0
  90. package/dist/core/lsp/client.js +776 -0
  91. package/dist/core/lsp/language-detect.js +66 -0
  92. package/dist/core/lsp/post-edit-diagnostics.js +171 -0
  93. package/dist/core/mcp/client.js +75 -6
  94. package/dist/core/mcp/http-server.js +553 -0
  95. package/dist/core/mcp/orchestrator-tools.js +662 -0
  96. package/dist/core/mcp/permission.js +190 -0
  97. package/dist/core/mcp/registry.js +24 -2
  98. package/dist/core/mcp/server-tools.js +219 -0
  99. package/dist/core/mcp/server.js +397 -0
  100. package/dist/core/memory/dual-write.js +416 -0
  101. package/dist/core/memory/phase1-kinds.js +20 -0
  102. package/dist/core/memory-sync/queue.js +158 -0
  103. package/dist/core/onboarding/ensure-initialized.js +133 -0
  104. package/dist/core/onboarding/marker.js +111 -0
  105. package/dist/core/onboarding/telemetry-state.js +108 -0
  106. package/dist/core/output-style/presets.js +176 -0
  107. package/dist/core/output-style/state.js +185 -0
  108. package/dist/core/path-security.js +284 -2
  109. package/dist/core/permissions/auto-classifier.js +124 -0
  110. package/dist/core/permissions/circuit-breaker.js +83 -0
  111. package/dist/core/permissions/gate.js +278 -0
  112. package/dist/core/permissions/index.js +20 -0
  113. package/dist/core/permissions/mode.js +174 -0
  114. package/dist/core/permissions/state.js +241 -0
  115. package/dist/core/permissions/tool-class.js +93 -0
  116. package/dist/core/prd-check/parser.js +215 -0
  117. package/dist/core/prd-check/reporter.js +127 -0
  118. package/dist/core/prd-check/session-review.js +557 -0
  119. package/dist/core/prd-check/verifiers.js +223 -0
  120. package/dist/core/pugi-md/context-injector.js +76 -0
  121. package/dist/core/pugi-md/walk-up.js +207 -0
  122. package/dist/core/release-notes/parser.js +241 -0
  123. package/dist/core/release-notes/state.js +116 -0
  124. package/dist/core/repl/history.js +11 -1
  125. package/dist/core/repl/model-pricing.js +135 -0
  126. package/dist/core/repl/session.js +1897 -37
  127. package/dist/core/repl/slash-commands.js +430 -15
  128. package/dist/core/repl/store/session-store.js +31 -2
  129. package/dist/core/repl/workspace-context.js +22 -0
  130. package/dist/core/repo-map/build.js +125 -0
  131. package/dist/core/repo-map/cache.js +185 -0
  132. package/dist/core/repo-map/extractor.js +254 -0
  133. package/dist/core/repo-map/formatter.js +145 -0
  134. package/dist/core/repo-map/scanner.js +211 -0
  135. package/dist/core/retry-budget/budget.js +284 -0
  136. package/dist/core/retry-budget/index.js +5 -0
  137. package/dist/core/session.js +92 -0
  138. package/dist/core/settings.js +80 -0
  139. package/dist/core/share/formatter.js +271 -0
  140. package/dist/core/share/redactor.js +221 -0
  141. package/dist/core/share/uploader.js +267 -0
  142. package/dist/core/skills/defaults.js +457 -0
  143. package/dist/core/smoke/headless-driver.js +174 -0
  144. package/dist/core/smoke/orchestrator.js +194 -0
  145. package/dist/core/smoke/runner.js +238 -0
  146. package/dist/core/smoke/scenario-parser.js +316 -0
  147. package/dist/core/subagents/dispatcher-real.js +600 -0
  148. package/dist/core/subagents/dispatcher.js +113 -24
  149. package/dist/core/subagents/index.js +18 -5
  150. package/dist/core/subagents/isolation-matrix.js +213 -0
  151. package/dist/core/subagents/spawn.js +19 -4
  152. package/dist/core/telemetry/emitter.js +229 -0
  153. package/dist/core/telemetry/queue.js +251 -0
  154. package/dist/core/theme/context.js +91 -0
  155. package/dist/core/theme/presets.js +228 -0
  156. package/dist/core/theme/state.js +181 -0
  157. package/dist/core/todos/invariant.js +10 -0
  158. package/dist/core/todos/state.js +177 -0
  159. package/dist/core/transport/version-interceptor.js +166 -0
  160. package/dist/core/vim/keymap.js +288 -0
  161. package/dist/core/vim/state.js +92 -0
  162. package/dist/core/worktree-manager/cleanup.js +123 -0
  163. package/dist/core/worktree-manager/manager.js +303 -0
  164. package/dist/index.js +28 -0
  165. package/dist/runtime/bootstrap.js +190 -0
  166. package/dist/runtime/cli.js +3241 -343
  167. package/dist/runtime/commands/cancel.js +231 -0
  168. package/dist/runtime/commands/chain.js +489 -0
  169. package/dist/runtime/commands/codegraph-status.js +227 -0
  170. package/dist/runtime/commands/compact.js +297 -0
  171. package/dist/runtime/commands/cost.js +199 -0
  172. package/dist/runtime/commands/delegate.js +242 -11
  173. package/dist/runtime/commands/dispatch.js +126 -0
  174. package/dist/runtime/commands/doctor.js +412 -0
  175. package/dist/runtime/commands/feedback.js +184 -0
  176. package/dist/runtime/commands/hooks.js +184 -0
  177. package/dist/runtime/commands/lsp.js +368 -0
  178. package/dist/runtime/commands/mcp.js +879 -0
  179. package/dist/runtime/commands/memory.js +508 -0
  180. package/dist/runtime/commands/model.js +237 -0
  181. package/dist/runtime/commands/onboarding.js +275 -0
  182. package/dist/runtime/commands/patch.js +128 -0
  183. package/dist/runtime/commands/permissions.js +112 -0
  184. package/dist/runtime/commands/plan.js +143 -0
  185. package/dist/runtime/commands/prd-check.js +285 -0
  186. package/dist/runtime/commands/redo-blob-store.js +92 -0
  187. package/dist/runtime/commands/redo.js +361 -0
  188. package/dist/runtime/commands/release-notes.js +229 -0
  189. package/dist/runtime/commands/repo-map.js +95 -0
  190. package/dist/runtime/commands/report.js +299 -0
  191. package/dist/runtime/commands/resume.js +118 -0
  192. package/dist/runtime/commands/review-consensus.js +17 -2
  193. package/dist/runtime/commands/rewind.js +333 -0
  194. package/dist/runtime/commands/sessions.js +163 -0
  195. package/dist/runtime/commands/share.js +316 -0
  196. package/dist/runtime/commands/status.js +186 -0
  197. package/dist/runtime/commands/stickers.js +82 -0
  198. package/dist/runtime/commands/style.js +194 -0
  199. package/dist/runtime/commands/theme.js +196 -0
  200. package/dist/runtime/commands/undo.js +32 -0
  201. package/dist/runtime/commands/update.js +289 -0
  202. package/dist/runtime/commands/vim.js +140 -0
  203. package/dist/runtime/commands/worktree.js +177 -0
  204. package/dist/runtime/commands/worktrees.js +155 -0
  205. package/dist/runtime/headless-repl.js +195 -0
  206. package/dist/runtime/headless.js +543 -0
  207. package/dist/runtime/load-hooks-or-exit.js +71 -0
  208. package/dist/runtime/plan-decompose.js +531 -0
  209. package/dist/runtime/version.js +65 -0
  210. package/dist/tools/agent-tool.js +229 -0
  211. package/dist/tools/apply-patch.js +556 -0
  212. package/dist/tools/ask-user-question.js +213 -0
  213. package/dist/tools/ask-user.js +115 -0
  214. package/dist/tools/bash.js +203 -4
  215. package/dist/tools/file-tools.js +85 -14
  216. package/dist/tools/lsp-tools.js +189 -0
  217. package/dist/tools/mcp-tool.js +260 -0
  218. package/dist/tools/multi-edit.js +361 -0
  219. package/dist/tools/powershell.js +268 -0
  220. package/dist/tools/registry.js +51 -0
  221. package/dist/tools/skill-tool.js +96 -0
  222. package/dist/tools/tasks.js +208 -0
  223. package/dist/tools/todo-write.js +184 -0
  224. package/dist/tools/web-fetch.js +147 -2
  225. package/dist/tools/web-search.js +458 -0
  226. package/dist/tui/agent-progress-card.js +111 -0
  227. package/dist/tui/agent-tree.js +10 -0
  228. package/dist/tui/ask-modal.js +2 -2
  229. package/dist/tui/ask-user-question-prompt.js +192 -0
  230. package/dist/tui/compact-banner.js +81 -0
  231. package/dist/tui/conversation-pane.js +82 -8
  232. package/dist/tui/cost-table.js +111 -0
  233. package/dist/tui/doctor-table.js +46 -0
  234. package/dist/tui/feedback-prompt.js +156 -0
  235. package/dist/tui/input-box.js +218 -3
  236. package/dist/tui/markdown-render.js +4 -4
  237. package/dist/tui/onboarding-wizard.js +240 -0
  238. package/dist/tui/permissions-picker.js +86 -0
  239. package/dist/tui/render.js +35 -0
  240. package/dist/tui/repl-render.js +313 -35
  241. package/dist/tui/repl-splash-art.js +1 -1
  242. package/dist/tui/repl-splash-mascot.js +32 -8
  243. package/dist/tui/repl-splash.js +2 -2
  244. package/dist/tui/repl.js +85 -5
  245. package/dist/tui/splash.js +1 -1
  246. package/dist/tui/status-bar.js +94 -16
  247. package/dist/tui/status-table.js +7 -0
  248. package/dist/tui/stickers-art.js +136 -0
  249. package/dist/tui/style-table.js +28 -0
  250. package/dist/tui/theme-table.js +29 -0
  251. package/dist/tui/thinking-spinner.js +123 -0
  252. package/dist/tui/tool-stream-pane.js +52 -3
  253. package/dist/tui/update-banner.js +27 -2
  254. package/dist/tui/vim-input.js +267 -0
  255. package/dist/tui/welcome-banner.js +107 -0
  256. package/dist/tui/welcome-data.js +293 -0
  257. package/docs/examples/codegraph.mcp.json +10 -0
  258. package/package.json +13 -7
  259. package/test/scenarios/codegen-create-file.scenario.txt +13 -0
  260. package/test/scenarios/compact-force.scenario.txt +11 -0
  261. package/test/scenarios/identity.scenario.txt +11 -0
  262. package/test/scenarios/persona-handoff.scenario.txt +11 -0
  263. package/test/scenarios/walkback.scenario.txt +12 -0
  264. package/dist/core/engine/compaction-hook.js +0 -154
@@ -0,0 +1,235 @@
1
+ /**
2
+ * Leak L27 (2026-05-27) — Auto-update channel + last-check persistence.
3
+ *
4
+ * Two pieces of disk state are managed here:
5
+ *
6
+ * 1. **Channel selection** — `~/.pugi/config.json::updateChannel`.
7
+ * Persisted across sessions so `pugi update` keeps polling the
8
+ * same track the operator opted into via `pugi update --channel
9
+ * <name>`. Mirrors the read/write pattern used by
10
+ * `core/permissions/state.ts::getGlobalDefaultMode` (passthrough
11
+ * schema, atomic tmp+rename, defensive parse).
12
+ *
13
+ * 2. **Last-check timestamp** — `~/.pugi/.last-update-check` (ISO
14
+ * string, single-line). Read by the cold-start banner gate so
15
+ * operators only see the "update available" hint once per
16
+ * `UPDATE_CHECK_INTERVAL_HOURS` (default 24h). Living on its own
17
+ * file (NOT a JSON object inside config.json) is intentional:
18
+ * the timestamp is a hot path — every CLI invocation touches it —
19
+ * and a single-line read+write is materially faster than the
20
+ * JSON parse + serialise of the broader config doc, with no
21
+ * schema coupling cost.
22
+ *
23
+ * Module contract:
24
+ *
25
+ * - Every file path resolver accepts a `homeDir` override so the
26
+ * test suite can drive the module through a per-test mkdtemp
27
+ * directory without polluting the real `~/.pugi/`.
28
+ *
29
+ * - Parse / read helpers NEVER throw on a malformed file. A
30
+ * corrupted JSON blob, a missing field, or an unreadable file all
31
+ * collapse to "no persisted value" so the next layer (the CLI
32
+ * flag or the hard default `beta`) takes over. A future-self
33
+ * debugging an update flow against a corrupt config never has the
34
+ * CLI crash on them.
35
+ *
36
+ * - Write helpers use the atomic tmp+rename idiom so a kill mid-
37
+ * write never produces a half-flushed JSON document. The
38
+ * timestamp file is small enough that POSIX `rename` is itself
39
+ * atomic in practice, but we keep the idiom uniform with the
40
+ * config write so reviewers do not have to context-switch.
41
+ */
42
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync, } from 'node:fs';
43
+ import { homedir } from 'node:os';
44
+ import { resolve, dirname } from 'node:path';
45
+ import { z } from 'zod';
46
+ import { DEFAULT_UPDATE_CHANNEL, UPDATE_CHANNELS, } from './channels.js';
47
+ /**
48
+ * Default rate-limit window between registry probes. Operators see the
49
+ * cold-start banner at most once per window. Override per call via
50
+ * `shouldCheckForUpdate({ intervalHours })` — the cron-style scheduler
51
+ * passes 0 to force a check on every invocation, the doctor probe
52
+ * passes 24 to match the operator-visible cadence.
53
+ */
54
+ export const UPDATE_CHECK_INTERVAL_HOURS = 24;
55
+ /** Filename of the per-user channel + misc config. Mirrors L6 / L25. */
56
+ const CONFIG_FILE = '.pugi/config.json';
57
+ /** Filename of the standalone last-check ISO timestamp. */
58
+ const LAST_CHECK_FILE = '.pugi/.last-update-check';
59
+ /**
60
+ * Zod schema for the channel slice of `~/.pugi/config.json`. The
61
+ * passthrough lets sibling skills (L6 `defaultPermissionMode`, L25
62
+ * onboarding marker, etc.) coexist in the same JSON document without
63
+ * dropping their fields on a channel write.
64
+ */
65
+ const channelConfigSchema = z
66
+ .object({
67
+ updateChannel: z.enum(['stable', 'beta', 'canary']).optional(),
68
+ })
69
+ .partial()
70
+ .passthrough();
71
+ /**
72
+ * Resolve the absolute path of the per-user config file. Defaults to
73
+ * the real home dir, but every caller in the spec passes an explicit
74
+ * tmpdir so the persisted writes never escape the test sandbox.
75
+ */
76
+ export function configPath(homeDir = homedir()) {
77
+ return resolve(homeDir, CONFIG_FILE);
78
+ }
79
+ /**
80
+ * Resolve the absolute path of the single-line last-check file.
81
+ */
82
+ export function lastCheckPath(homeDir = homedir()) {
83
+ return resolve(homeDir, LAST_CHECK_FILE);
84
+ }
85
+ /**
86
+ * Read the persisted channel selection. Returns `null` when the
87
+ * config file is absent, the field is unset, or the file is unparse-
88
+ * able. The caller layers in the CLI flag + the hard default
89
+ * `DEFAULT_UPDATE_CHANNEL`.
90
+ *
91
+ * Defensive parse is intentional — a half-written config from a
92
+ * crashed session should never block `pugi update` from finishing the
93
+ * channel switch.
94
+ */
95
+ export function getUpdateChannel(homeDir = homedir()) {
96
+ const path = configPath(homeDir);
97
+ if (!existsSync(path))
98
+ return null;
99
+ try {
100
+ const raw = readFileSync(path, 'utf8');
101
+ const parsed = channelConfigSchema.parse(JSON.parse(raw));
102
+ return parsed.updateChannel ?? null;
103
+ }
104
+ catch {
105
+ return null;
106
+ }
107
+ }
108
+ /**
109
+ * Resolve the effective channel for an invocation. Resolution order:
110
+ *
111
+ * 1. `cliFlag` (when provided + parses to a known channel).
112
+ * 2. `~/.pugi/config.json::updateChannel`.
113
+ * 3. `DEFAULT_UPDATE_CHANNEL` (currently `beta`).
114
+ *
115
+ * An invalid `cliFlag` (e.g. `--channel yolo`) falls through to the
116
+ * next layer rather than crashing — the dispatcher already validates
117
+ * the flag up front and surfaces a deterministic error for unknown
118
+ * names. This helper exists for code paths (the doctor probe, the
119
+ * cold-start banner) where no CLI flag is in play and a silent fall-
120
+ * through is the correct behaviour.
121
+ */
122
+ export function resolveEffectiveChannel(options = {}) {
123
+ const cli = options.cliFlag;
124
+ if (cli && typeof cli === 'string') {
125
+ const trimmed = cli.trim().toLowerCase();
126
+ for (const channel of UPDATE_CHANNELS) {
127
+ if (channel === trimmed)
128
+ return channel;
129
+ }
130
+ }
131
+ const persisted = getUpdateChannel(options.homeDir ?? homedir());
132
+ if (persisted)
133
+ return persisted;
134
+ return DEFAULT_UPDATE_CHANNEL;
135
+ }
136
+ /**
137
+ * Persist the channel to `~/.pugi/config.json::updateChannel`. Creates
138
+ * `~/.pugi/` when missing; preserves any unrelated keys in the file
139
+ * (passthrough schema). Atomic tmp+rename so a kill mid-write never
140
+ * leaves the config half-flushed.
141
+ */
142
+ export function setUpdateChannel(channel, homeDir = homedir()) {
143
+ const path = configPath(homeDir);
144
+ mkdirSync(dirname(path), { recursive: true });
145
+ const existing = existsSync(path)
146
+ ? safeParseObject(readFileSync(path, 'utf8'))
147
+ : {};
148
+ const next = { ...existing, updateChannel: channel };
149
+ const tmpPath = `${path}.tmp`;
150
+ writeFileSync(tmpPath, `${JSON.stringify(next, null, 2)}\n`, {
151
+ encoding: 'utf8',
152
+ mode: 0o600,
153
+ });
154
+ renameSync(tmpPath, path);
155
+ }
156
+ /**
157
+ * Read the ISO timestamp of the most recent registry probe. Returns
158
+ * `null` when the file is absent or the contents do not parse as a
159
+ * valid Date. The caller treats `null` as "never checked" and runs an
160
+ * immediate probe.
161
+ */
162
+ export function readLastCheckedAt(homeDir = homedir()) {
163
+ const path = lastCheckPath(homeDir);
164
+ if (!existsSync(path))
165
+ return null;
166
+ try {
167
+ const raw = readFileSync(path, 'utf8').trim();
168
+ if (raw.length === 0)
169
+ return null;
170
+ const ts = Date.parse(raw);
171
+ if (!Number.isFinite(ts))
172
+ return null;
173
+ return new Date(ts);
174
+ }
175
+ catch {
176
+ return null;
177
+ }
178
+ }
179
+ /**
180
+ * Persist the timestamp of the most recent registry probe. Atomic
181
+ * tmp+rename for the same reasons as `setUpdateChannel` — the file is
182
+ * small but we keep the idiom uniform.
183
+ */
184
+ export function writeLastCheckedAt(when, homeDir = homedir()) {
185
+ const path = lastCheckPath(homeDir);
186
+ mkdirSync(dirname(path), { recursive: true });
187
+ const tmpPath = `${path}.tmp`;
188
+ writeFileSync(tmpPath, `${when.toISOString()}\n`, {
189
+ encoding: 'utf8',
190
+ mode: 0o600,
191
+ });
192
+ renameSync(tmpPath, path);
193
+ }
194
+ /**
195
+ * Decide whether the cold-start hint should run a fresh registry
196
+ * probe. Returns true when the last probe was more than
197
+ * `intervalHours` ago OR the timestamp file is missing entirely.
198
+ *
199
+ * Pass `intervalHours = 0` to force a probe on every call (used by
200
+ * the `pugi update --check` JSON surface where the operator is
201
+ * explicitly asking for a fresh result).
202
+ */
203
+ export function shouldCheckForUpdate(options = {}) {
204
+ const now = options.now ? options.now() : Date.now();
205
+ const intervalHours = options.intervalHours ?? UPDATE_CHECK_INTERVAL_HOURS;
206
+ if (intervalHours <= 0)
207
+ return true;
208
+ const last = readLastCheckedAt(options.homeDir ?? homedir());
209
+ if (!last)
210
+ return true;
211
+ const ageMs = now - last.getTime();
212
+ const windowMs = intervalHours * 60 * 60 * 1_000;
213
+ return ageMs >= windowMs;
214
+ }
215
+ /**
216
+ * Defensive helper — parse JSON to an object; non-object payloads
217
+ * (top-level array, primitive) collapse to an empty object so the
218
+ * channel-write merge does not surface a TypeError. Mirrors the
219
+ * `safeParseObject` in `core/permissions/state.ts` — duplicating the
220
+ * 10 lines is cheaper than threading a shared util module through
221
+ * two unrelated leak surfaces.
222
+ */
223
+ function safeParseObject(raw) {
224
+ try {
225
+ const parsed = JSON.parse(raw);
226
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
227
+ return parsed;
228
+ }
229
+ return {};
230
+ }
231
+ catch {
232
+ return {};
233
+ }
234
+ }
235
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Leak L22 (2026-05-27) — `--bare` mode predicate.
3
+ *
4
+ * Mirror of Claude Code's `--bare` flag: when active the CLI behaves
5
+ * like a plain LLM frontend with NO project auto-discovery. Useful for:
6
+ *
7
+ * - headless scripting where the operator wants deterministic, repo-
8
+ * independent behavior (`pugi --bare --print "..."`),
9
+ * - dropping into a workspace without auto-creating `.pugi/`,
10
+ * - REPL sessions that should NOT inject ambient `PUGI.md` / `CLAUDE.md`
11
+ * into the model prompt,
12
+ * - support / triage flows where the engineer needs the CLI to act
13
+ * like a fresh install regardless of where it's invoked.
14
+ *
15
+ * Discovery surfaces gated by `isBareMode()`:
16
+ *
17
+ * 1. `PUGI.md` / `AGENTS.md` / `CLAUDE.md` / `GEMINI.md` parent-dir
18
+ * walk-up (`loadTraversedMarkdown` in `core/context/markdown-traverse.ts`).
19
+ * 2. Workspace-root markdown context (`loadMarkdownContext` consumers).
20
+ * 3. Auto-init `.pugi/` scaffold on REPL boot in untouched dirs.
21
+ * 4. Persona / skill auto-load from `.pugi/skills/`.
22
+ * 5. Workspace summary (`readPugiSummary`) read on REPL session start.
23
+ *
24
+ * Activation precedence — the bare bit is "sticky" once set so any
25
+ * subprocess the CLI spawns inherits it without re-passing the flag:
26
+ *
27
+ * 1. Top-level `--bare` arg parsed by `parseArgs` in `runtime/cli.ts`.
28
+ * The parser sets `process.env.PUGI_BARE='1'` BEFORE the dispatch
29
+ * flows so callsites checking the env see the activated state.
30
+ * 2. `PUGI_BARE=1` env var (any value matching `/^(1|true|yes|on)$/i`).
31
+ * 3. Default: bare mode OFF — full auto-discovery as before.
32
+ *
33
+ * This mirrors the existing `PUGI_SKIP_SPLASH` / `PUGI_NO_AUTO_INIT`
34
+ * env-flag pattern so the bare module fits the rest of the runtime
35
+ * configuration grammar without inventing a new wire.
36
+ *
37
+ * Test surface: `apps/pugi-cli/test/bare-mode.spec.ts` exercises the
38
+ * env precedence, value parsing, and the explicit-set / clear helpers.
39
+ */
40
+ /**
41
+ * Env var consulted by `isBareMode()`. Kept as an export so the spec
42
+ * + the runtime CLI can use the same constant — no string-typing of
43
+ * the wire name across modules.
44
+ */
45
+ export const PUGI_BARE_ENV = 'PUGI_BARE';
46
+ /**
47
+ * Truthy values recognised on the `PUGI_BARE` env. Anything else
48
+ * (empty string, `0`, `false`, `no`, `off`, `disabled`, undefined) is
49
+ * treated as bare-mode OFF. The list is intentionally short — the
50
+ * value is set by the CLI parser and is not customer-typed prose, so
51
+ * we do not need a permissive boolean coercion.
52
+ */
53
+ const TRUTHY = new Set(['1', 'true', 'yes', 'on']);
54
+ /**
55
+ * Return true when bare mode is active for the current process. Reads
56
+ * `process.env[PUGI_BARE_ENV]` and applies the truthy-value match.
57
+ *
58
+ * Safe to call from any module (no FS, no side-effects). The runtime
59
+ * cost is a single env-var lookup + lower-case + set membership, so
60
+ * gating hot-path callsites with `if (isBareMode()) return ...` adds
61
+ * effectively zero overhead in the default (non-bare) case.
62
+ */
63
+ export function isBareMode(env = process.env) {
64
+ const raw = env[PUGI_BARE_ENV];
65
+ if (typeof raw !== 'string' || raw.length === 0)
66
+ return false;
67
+ return TRUTHY.has(raw.toLowerCase());
68
+ }
69
+ /**
70
+ * Explicitly activate bare mode for the current process. Called by
71
+ * `parseArgs` in `runtime/cli.ts` when `--bare` is seen on the command
72
+ * line so downstream modules (engine, REPL bootstrap, doctor probe)
73
+ * see a consistent activated state via `isBareMode()` regardless of
74
+ * whether the operator set the env var manually or used the flag.
75
+ *
76
+ * Subprocess inheritance is the reason we mutate `process.env` rather
77
+ * than threading a `bare: boolean` field through every call signature
78
+ * — every Node child_process spawn inherits `process.env` by default,
79
+ * so the bare bit propagates to MCP servers / hook scripts / git
80
+ * subprocesses without ceremony.
81
+ */
82
+ export function setBareMode(env = process.env) {
83
+ env[PUGI_BARE_ENV] = '1';
84
+ }
85
+ /**
86
+ * Clear bare mode for the current process. Provided primarily for the
87
+ * spec so adjacent tests do not leak state between cases. Production
88
+ * code does NOT call this — bare mode is a one-shot per process.
89
+ */
90
+ export function clearBareMode(env = process.env) {
91
+ delete env[PUGI_BARE_ENV];
92
+ }
93
+ /**
94
+ * Human-readable one-line banner printed by the dispatcher when bare
95
+ * mode is active and the invocation is NOT JSON-only. Kept as a single
96
+ * constant so the spec can assert the exact wording and downstream
97
+ * tools (status bars, doctor row, REPL header) stay in lockstep.
98
+ */
99
+ export const BARE_MODE_BANNER = 'Pugi --bare mode: project auto-discovery disabled.';
100
+ /**
101
+ * Short label rendered inside the `pugi doctor` table when bare mode
102
+ * is active. The doctor probe surfaces `BARE MODE` as a separate row
103
+ * so operators triaging "why is Pugi ignoring my PUGI.md" see the
104
+ * cause without grep'ing the env.
105
+ */
106
+ export const BARE_MODE_DOCTOR_LABEL = 'BARE MODE';
107
+ //# sourceMappingURL=index.js.map