@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.
- package/THIRD_PARTY_NOTICES.md +40 -0
- package/assets/pugi-mascot.ansi +15 -25
- package/assets/pugi-prozr2-mascot.ansi +9 -0
- package/bin/run.js +33 -1
- package/dist/commands/jobs-watch.js +201 -0
- package/dist/commands/jobs.js +15 -0
- package/dist/commands/smoke.js +133 -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/artifact-chain/dispatcher.js +148 -0
- package/dist/core/artifact-chain/exporter.js +164 -0
- package/dist/core/artifact-chain/state.js +243 -0
- package/dist/core/artifact-chain/steps.js +169 -0
- package/dist/core/auth/ensure-authenticated.js +129 -0
- package/dist/core/auth/env-provider.js +238 -0
- package/dist/core/auto-update/channels.js +122 -0
- package/dist/core/auto-update/checker.js +241 -0
- package/dist/core/auto-update/state.js +235 -0
- package/dist/core/bare-mode/index.js +107 -0
- package/dist/core/bash-classifier.js +400 -4
- package/dist/core/checkpoint/resumer.js +149 -0
- package/dist/core/checkpoint/rewinder.js +291 -0
- package/dist/core/codegraph/decision-store.js +248 -0
- package/dist/core/codegraph/detect-repo.js +459 -0
- package/dist/core/codegraph/install.js +134 -0
- package/dist/core/codegraph/offer-hook.js +220 -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 +208 -0
- package/dist/core/compact/token-counter.js +108 -0
- package/dist/core/consensus/diff-capture.js +112 -3
- 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/bare-mode.js +42 -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/hooks.js +118 -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/pugi-md.js +89 -0
- package/dist/core/diagnostics/probes/sandbox.js +40 -0
- package/dist/core/diagnostics/probes/session.js +74 -0
- package/dist/core/diagnostics/probes/status-snapshot.js +488 -0
- package/dist/core/diagnostics/probes/workspace.js +63 -0
- package/dist/core/diagnostics/types.js +70 -0
- package/dist/core/dispatch/cache-cleanup.js +197 -0
- package/dist/core/dispatch/cache-handoff.js +295 -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 +322 -0
- package/dist/core/engine/anvil-client.js +115 -5
- package/dist/core/engine/auto-compact.js +179 -0
- package/dist/core/engine/budgets.js +155 -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 +897 -211
- package/dist/core/engine/prompts.js +88 -2
- package/dist/core/engine/strip-internal-fields.js +124 -0
- package/dist/core/engine/tool-bridge.js +1045 -36
- package/dist/core/feedback/queue.js +177 -0
- package/dist/core/feedback/submitter.js +145 -0
- package/dist/core/file-cache.js +113 -1
- package/dist/core/hooks/events.js +44 -0
- package/dist/core/hooks/index.js +15 -0
- package/dist/core/hooks/registry.js +213 -0
- package/dist/core/hooks/runner.js +236 -0
- package/dist/core/hooks/v2/event-emitter.js +115 -0
- package/dist/core/hooks/v2/executor.js +282 -0
- package/dist/core/hooks/v2/index.js +25 -0
- package/dist/core/hooks/v2/lifecycle.js +104 -0
- package/dist/core/hooks/v2/loader.js +216 -0
- package/dist/core/hooks/v2/matcher.js +125 -0
- package/dist/core/hooks/v2/trust.js +143 -0
- package/dist/core/hooks/v2/types.js +86 -0
- package/dist/core/lsp/cache.js +105 -0
- package/dist/core/lsp/client.js +776 -0
- package/dist/core/lsp/language-detect.js +66 -0
- package/dist/core/lsp/post-edit-diagnostics.js +171 -0
- package/dist/core/mcp/client.js +75 -6
- package/dist/core/mcp/http-server.js +553 -0
- package/dist/core/mcp/orchestrator-tools.js +662 -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/memory/dual-write.js +416 -0
- package/dist/core/memory/phase1-kinds.js +20 -0
- package/dist/core/memory-sync/queue.js +158 -0
- package/dist/core/onboarding/ensure-initialized.js +133 -0
- package/dist/core/onboarding/marker.js +111 -0
- package/dist/core/onboarding/telemetry-state.js +108 -0
- package/dist/core/output-style/presets.js +176 -0
- package/dist/core/output-style/state.js +185 -0
- package/dist/core/path-security.js +284 -2
- package/dist/core/permissions/auto-classifier.js +124 -0
- package/dist/core/permissions/circuit-breaker.js +83 -0
- package/dist/core/permissions/gate.js +278 -0
- package/dist/core/permissions/index.js +20 -0
- package/dist/core/permissions/mode.js +174 -0
- package/dist/core/permissions/state.js +241 -0
- package/dist/core/permissions/tool-class.js +93 -0
- package/dist/core/prd-check/parser.js +215 -0
- package/dist/core/prd-check/reporter.js +127 -0
- package/dist/core/prd-check/session-review.js +557 -0
- package/dist/core/prd-check/verifiers.js +223 -0
- package/dist/core/pugi-md/context-injector.js +76 -0
- package/dist/core/pugi-md/walk-up.js +207 -0
- package/dist/core/release-notes/parser.js +241 -0
- package/dist/core/release-notes/state.js +116 -0
- package/dist/core/repl/history.js +11 -1
- package/dist/core/repl/model-pricing.js +135 -0
- package/dist/core/repl/session.js +1897 -37
- package/dist/core/repl/slash-commands.js +430 -15
- package/dist/core/repl/store/session-store.js +31 -2
- package/dist/core/repl/workspace-context.js +22 -0
- package/dist/core/repo-map/build.js +125 -0
- package/dist/core/repo-map/cache.js +185 -0
- package/dist/core/repo-map/extractor.js +254 -0
- package/dist/core/repo-map/formatter.js +145 -0
- package/dist/core/repo-map/scanner.js +211 -0
- package/dist/core/retry-budget/budget.js +284 -0
- package/dist/core/retry-budget/index.js +5 -0
- package/dist/core/session.js +92 -0
- package/dist/core/settings.js +80 -0
- package/dist/core/share/formatter.js +271 -0
- package/dist/core/share/redactor.js +221 -0
- package/dist/core/share/uploader.js +267 -0
- package/dist/core/skills/defaults.js +457 -0
- package/dist/core/smoke/headless-driver.js +174 -0
- package/dist/core/smoke/orchestrator.js +194 -0
- package/dist/core/smoke/runner.js +238 -0
- package/dist/core/smoke/scenario-parser.js +316 -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/telemetry/emitter.js +229 -0
- package/dist/core/telemetry/queue.js +251 -0
- package/dist/core/theme/context.js +91 -0
- package/dist/core/theme/presets.js +228 -0
- package/dist/core/theme/state.js +181 -0
- package/dist/core/todos/invariant.js +10 -0
- package/dist/core/todos/state.js +177 -0
- package/dist/core/transport/version-interceptor.js +166 -0
- package/dist/core/vim/keymap.js +288 -0
- package/dist/core/vim/state.js +92 -0
- package/dist/core/worktree-manager/cleanup.js +123 -0
- package/dist/core/worktree-manager/manager.js +303 -0
- package/dist/index.js +28 -0
- package/dist/runtime/bootstrap.js +190 -0
- package/dist/runtime/cli.js +3241 -343
- package/dist/runtime/commands/cancel.js +231 -0
- package/dist/runtime/commands/chain.js +489 -0
- package/dist/runtime/commands/codegraph-status.js +227 -0
- package/dist/runtime/commands/compact.js +297 -0
- package/dist/runtime/commands/cost.js +199 -0
- package/dist/runtime/commands/delegate.js +242 -11
- package/dist/runtime/commands/dispatch.js +126 -0
- package/dist/runtime/commands/doctor.js +412 -0
- package/dist/runtime/commands/feedback.js +184 -0
- package/dist/runtime/commands/hooks.js +184 -0
- package/dist/runtime/commands/lsp.js +368 -0
- package/dist/runtime/commands/mcp.js +879 -0
- package/dist/runtime/commands/memory.js +508 -0
- package/dist/runtime/commands/model.js +237 -0
- package/dist/runtime/commands/onboarding.js +275 -0
- package/dist/runtime/commands/patch.js +128 -0
- package/dist/runtime/commands/permissions.js +112 -0
- package/dist/runtime/commands/plan.js +143 -0
- package/dist/runtime/commands/prd-check.js +285 -0
- package/dist/runtime/commands/redo-blob-store.js +92 -0
- package/dist/runtime/commands/redo.js +361 -0
- package/dist/runtime/commands/release-notes.js +229 -0
- package/dist/runtime/commands/repo-map.js +95 -0
- package/dist/runtime/commands/report.js +299 -0
- package/dist/runtime/commands/resume.js +118 -0
- package/dist/runtime/commands/review-consensus.js +17 -2
- package/dist/runtime/commands/rewind.js +333 -0
- package/dist/runtime/commands/sessions.js +163 -0
- package/dist/runtime/commands/share.js +316 -0
- package/dist/runtime/commands/status.js +186 -0
- package/dist/runtime/commands/stickers.js +82 -0
- package/dist/runtime/commands/style.js +194 -0
- package/dist/runtime/commands/theme.js +196 -0
- package/dist/runtime/commands/undo.js +32 -0
- package/dist/runtime/commands/update.js +289 -0
- package/dist/runtime/commands/vim.js +140 -0
- package/dist/runtime/commands/worktree.js +177 -0
- package/dist/runtime/commands/worktrees.js +155 -0
- package/dist/runtime/headless-repl.js +195 -0
- 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 +229 -0
- package/dist/tools/apply-patch.js +556 -0
- package/dist/tools/ask-user-question.js +213 -0
- package/dist/tools/ask-user.js +115 -0
- package/dist/tools/bash.js +203 -4
- package/dist/tools/file-tools.js +85 -14
- package/dist/tools/lsp-tools.js +189 -0
- package/dist/tools/mcp-tool.js +260 -0
- package/dist/tools/multi-edit.js +361 -0
- package/dist/tools/powershell.js +268 -0
- package/dist/tools/registry.js +51 -0
- package/dist/tools/skill-tool.js +96 -0
- package/dist/tools/tasks.js +208 -0
- package/dist/tools/todo-write.js +184 -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 +81 -0
- package/dist/tui/conversation-pane.js +82 -8
- package/dist/tui/cost-table.js +111 -0
- package/dist/tui/doctor-table.js +46 -0
- package/dist/tui/feedback-prompt.js +156 -0
- package/dist/tui/input-box.js +218 -3
- package/dist/tui/markdown-render.js +4 -4
- package/dist/tui/onboarding-wizard.js +240 -0
- package/dist/tui/permissions-picker.js +86 -0
- package/dist/tui/render.js +35 -0
- package/dist/tui/repl-render.js +313 -35
- package/dist/tui/repl-splash-art.js +1 -1
- package/dist/tui/repl-splash-mascot.js +32 -8
- package/dist/tui/repl-splash.js +2 -2
- package/dist/tui/repl.js +85 -5
- 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/stickers-art.js +136 -0
- package/dist/tui/style-table.js +28 -0
- package/dist/tui/theme-table.js +29 -0
- package/dist/tui/thinking-spinner.js +123 -0
- package/dist/tui/tool-stream-pane.js +52 -3
- package/dist/tui/update-banner.js +27 -2
- package/dist/tui/vim-input.js +267 -0
- package/dist/tui/welcome-banner.js +107 -0
- package/dist/tui/welcome-data.js +293 -0
- package/docs/examples/codegraph.mcp.json +10 -0
- package/package.json +13 -7
- package/test/scenarios/codegen-create-file.scenario.txt +13 -0
- package/test/scenarios/compact-force.scenario.txt +11 -0
- package/test/scenarios/identity.scenario.txt +11 -0
- package/test/scenarios/persona-handoff.scenario.txt +11 -0
- package/test/scenarios/walkback.scenario.txt +12 -0
- 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
|