magus-cli 0.0.0-placeholder → 7.1.0
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/README.md +309 -0
- package/bin/magus.js +170 -0
- package/package.json +67 -3
- package/scripts/build-binaries.ts +113 -0
- package/scripts/capture-builtin.ts +412 -0
- package/scripts/check-optional-deps.ts +49 -0
- package/scripts/test-isolated.ts +49 -0
- package/scripts/verify-binary.ts +95 -0
- package/scripts/verify-community-registry.ts +272 -0
- package/src/__tests__/active-closure.test.ts +184 -0
- package/src/__tests__/alias-adopt.test.ts +365 -0
- package/src/__tests__/alias-parser.test.ts +417 -0
- package/src/__tests__/alias-shell-writer.test.ts +681 -0
- package/src/__tests__/alias-store.test.ts +163 -0
- package/src/__tests__/binary-signing.test.ts +120 -0
- package/src/__tests__/blackbox-active-closure.test.ts +119 -0
- package/src/__tests__/blackbox-convergence.test.ts +334 -0
- package/src/__tests__/blackbox-parity.test.ts +725 -0
- package/src/__tests__/blackbox-plugin-state.test.ts +545 -0
- package/src/__tests__/blackbox-tool-state.test.ts +669 -0
- package/src/__tests__/cache-provenance.test.ts +84 -0
- package/src/__tests__/capture-builtin.test.ts +92 -0
- package/src/__tests__/catalog-cache-store.test.ts +309 -0
- package/src/__tests__/catalog-notice.test.ts +155 -0
- package/src/__tests__/cli-ansi.test.ts +297 -0
- package/src/__tests__/cli-apply-seams.test.ts +288 -0
- package/src/__tests__/cli-live.test.ts +391 -0
- package/src/__tests__/cli-router.test.ts +240 -0
- package/src/__tests__/cli-tool-commands.test.ts +239 -0
- package/src/__tests__/cli-tools-adapter.test.ts +294 -0
- package/src/__tests__/cli-update-view.test.ts +524 -0
- package/src/__tests__/community-fetch.test.ts +545 -0
- package/src/__tests__/community-registry.test.ts +269 -0
- package/src/__tests__/community-staleness.test.ts +720 -0
- package/src/__tests__/component-badge.test.ts +98 -0
- package/src/__tests__/component-icons.test.ts +94 -0
- package/src/__tests__/content-drift.test.ts +353 -0
- package/src/__tests__/conventions-integration.test.ts +830 -0
- package/src/__tests__/conventions-manager.test.ts +1236 -0
- package/src/__tests__/doctor-bins.test.ts +43 -0
- package/src/__tests__/doctor.test.ts +463 -0
- package/src/__tests__/dotenv.test.ts +195 -0
- package/src/__tests__/enabled-not-installed.test.ts +94 -0
- package/src/__tests__/file-locking.test.ts +208 -0
- package/src/__tests__/fixtures/tool-probe/brew-list-versions-tmux.txt +2 -0
- package/src/__tests__/fixtures/tool-probe/brew-outdated-behind-homebrew-spec.txt +29 -0
- package/src/__tests__/fixtures/tool-probe/brew-outdated-tmux-current.txt +7 -0
- package/src/__tests__/fixtures/tool-probe/brew-outdated-unknown-formula.txt +6 -0
- package/src/__tests__/fixtures/tool-probe/go-version-m-tmux-mcp.txt +27 -0
- package/src/__tests__/fixtures/tool-probe/npm-dist-tags-missing-scoped.txt +4 -0
- package/src/__tests__/fixtures/tool-probe/npm-dist-tags-openai-codex.txt +4 -0
- package/src/__tests__/fixtures/tool-probe/pypi-cowsay.txt +3 -0
- package/src/__tests__/fixtures/tool-probe/uv-receipt-browser-use.toml +10 -0
- package/src/__tests__/fixtures/tool-probe/uv-receipt-cowsay-git.toml +8 -0
- package/src/__tests__/fixtures/tool-probe/uv-receipt-cowsay-pinned.toml +8 -0
- package/src/__tests__/fixtures/tool-probe/uv-receipt-graphifyy.toml +7 -0
- package/src/__tests__/fixtures/tool-probe/uv-receipt-mcp.toml +6 -0
- package/src/__tests__/fixtures/tool-probe/uv-receipt-serena-agent.toml +9 -0
- package/src/__tests__/fixtures/tool-probe/uv-tool-list-outdated-pinned.txt +5 -0
- package/src/__tests__/fixtures/tool-probe/uv-tool-list-outdated.txt +11 -0
- package/src/__tests__/fixtures/tool-probe/uv-tool-list.txt +22 -0
- package/src/__tests__/footer-hints.test.ts +40 -0
- package/src/__tests__/format-command.test.ts +78 -0
- package/src/__tests__/git-worktree.test.ts +108 -0
- package/src/__tests__/github-budget.test.ts +206 -0
- package/src/__tests__/gitignore-check.test.ts +71 -0
- package/src/__tests__/gitignore-detector.test.ts +176 -0
- package/src/__tests__/gitignore-fixer.test.ts +317 -0
- package/src/__tests__/gitignore-resolver.test.ts +193 -0
- package/src/__tests__/gitignore-service.test.ts +137 -0
- package/src/__tests__/go-module.test.ts +43 -0
- package/src/__tests__/hook-import-policy.test.ts +90 -0
- package/src/__tests__/hook-process.test.ts +256 -0
- package/src/__tests__/hook-registration.test.ts +224 -0
- package/src/__tests__/install-command.test.ts +81 -0
- package/src/__tests__/install-plan.test.ts +104 -0
- package/src/__tests__/launcher-signal.test.ts +308 -0
- package/src/__tests__/local-marketplace-scan.test.ts +248 -0
- package/src/__tests__/manifest.test.ts +281 -0
- package/src/__tests__/markdown-renderer.test.ts +142 -0
- package/src/__tests__/marketplace-badge.test.ts +140 -0
- package/src/__tests__/marketplace-refresh.test.ts +340 -0
- package/src/__tests__/marketplace-version.test.ts +84 -0
- package/src/__tests__/marketplaces.test.ts +128 -0
- package/src/__tests__/mate-availability.test.ts +156 -0
- package/src/__tests__/mate-catalog.test.ts +295 -0
- package/src/__tests__/mcp-registry.test.ts +158 -0
- package/src/__tests__/model-visuals.test.tsx +2466 -0
- package/src/__tests__/models-adapter.test.ts +332 -0
- package/src/__tests__/models-cli.test.ts +273 -0
- package/src/__tests__/models-core.test.ts +802 -0
- package/src/__tests__/models-manager.test.ts +500 -0
- package/src/__tests__/models-presets-marketplace.test.ts +168 -0
- package/src/__tests__/models-screen-state.test.ts +315 -0
- package/src/__tests__/open-file.test.ts +59 -0
- package/src/__tests__/owned-marketplaces.test.ts +93 -0
- package/src/__tests__/plugin-checkout.test.ts +66 -0
- package/src/__tests__/plugin-cli-argv.test.ts +109 -0
- package/src/__tests__/plugin-contents.test.ts +371 -0
- package/src/__tests__/plugin-manager-fallback.test.ts +320 -0
- package/src/__tests__/plugin-releases.test.ts +98 -0
- package/src/__tests__/plugin-requires.test.ts +187 -0
- package/src/__tests__/plugin-row-text.test.ts +345 -0
- package/src/__tests__/plugin-setup.test.ts +1016 -0
- package/src/__tests__/profile-adopt.test.ts +177 -0
- package/src/__tests__/profile-command.test.ts +110 -0
- package/src/__tests__/profile-materializer.test.ts +238 -0
- package/src/__tests__/profile-sync.test.ts +154 -0
- package/src/__tests__/registry-version-resolution.test.ts +88 -0
- package/src/__tests__/resolve-executable.test.ts +42 -0
- package/src/__tests__/resolver.test.ts +351 -0
- package/src/__tests__/scope-action.test.ts +134 -0
- package/src/__tests__/scope-squares.test.tsx +164 -0
- package/src/__tests__/selection-scope.test.ts +100 -0
- package/src/__tests__/settings-file.test.ts +179 -0
- package/src/__tests__/shell-script-callers.test.ts +92 -0
- package/src/__tests__/skill-install-files.test.ts +136 -0
- package/src/__tests__/skills-catalog-client.test.ts +48 -0
- package/src/__tests__/style-wrap.test.ts +220 -0
- package/src/__tests__/styles-manager.test.ts +1098 -0
- package/src/__tests__/styles-origins.test.ts +414 -0
- package/src/__tests__/styles-screen-state.test.ts +457 -0
- package/src/__tests__/styles-status-line.test.ts +72 -0
- package/src/__tests__/styles-sync.test.ts +452 -0
- package/src/__tests__/symlink-manager.test.ts +180 -0
- package/src/__tests__/tabbar-layout.test.ts +100 -0
- package/src/__tests__/terminology-filler.test.ts +214 -0
- package/src/__tests__/theme-adaptive-colors.test.ts +354 -0
- package/src/__tests__/theme-env.test.ts +310 -0
- package/src/__tests__/theme-resolve.test.ts +380 -0
- package/src/__tests__/tool-probe.test.ts +1400 -0
- package/src/__tests__/toolchain.test.ts +232 -0
- package/src/__tests__/ui-version-writers.test.ts +88 -0
- package/src/__tests__/update-apply.test.ts +637 -0
- package/src/__tests__/update-plan.test.ts +1396 -0
- package/src/__tests__/uppercase-keybindings.test.ts +101 -0
- package/src/__tests__/useGitignoreModal.test.ts +79 -0
- package/src/__tests__/version-check-refresh.test.ts +45 -0
- package/src/__tests__/version-snapshot.test.ts +212 -0
- package/src/__tests__/version-unknown-state.test.ts +403 -0
- package/src/__tests__/worktree-registry-resolution.test.ts +107 -0
- package/src/cli/ansi.ts +528 -0
- package/src/cli/bootstrap.ts +134 -0
- package/src/cli/doctor.ts +218 -0
- package/src/cli/hook.ts +129 -0
- package/src/cli/install.ts +382 -0
- package/src/cli/live.ts +374 -0
- package/src/cli/models.ts +297 -0
- package/src/cli/profile.ts +197 -0
- package/src/cli/prompt.ts +32 -0
- package/src/cli/router.ts +264 -0
- package/src/cli/update-view.ts +717 -0
- package/src/cli/update.ts +600 -0
- package/src/cli/upgrade.ts +97 -0
- package/src/data/alias-flags.ts +301 -0
- package/src/data/cli-tools.ts +146 -0
- package/src/data/community-styles.ts +531 -0
- package/src/data/gitignore-defaults.ts +33 -0
- package/src/data/gitignore-reasons.ts +93 -0
- package/src/data/gitignore-templates.ts +25 -0
- package/src/data/marketplaces.ts +278 -0
- package/src/data/mcp-servers.ts +51 -0
- package/src/data/models-presets.ts +329 -0
- package/src/data/predefined-profiles.ts +252 -0
- package/src/data/settings-catalog.ts +714 -0
- package/src/data/skill-repos.ts +91 -0
- package/src/data/styles/asd-ste100.md +38 -0
- package/src/data/styles/calibrated.md +24 -0
- package/src/data/styles/direct.md +23 -0
- package/src/data/styles/evidence-first.md +22 -0
- package/src/data/styles/explanatory.md +23 -0
- package/src/data/styles/index.ts +57 -0
- package/src/data/styles/no-slop.md +32 -0
- package/src/data/styles/plain-language.md +23 -0
- package/src/data/styles/structured.md +30 -0
- package/src/data/styles/terminology.md +28 -0
- package/src/data/styles/terse.md +20 -0
- package/src/main.tsx +110 -0
- package/src/markdown.d.ts +13 -0
- package/src/opentui.d.ts +203 -0
- package/src/services/active-closure.ts +117 -0
- package/src/services/alias-settings.ts +60 -0
- package/src/services/alias-shell-writer.ts +1244 -0
- package/src/services/alias-store.ts +172 -0
- package/src/services/binary-signing.ts +78 -0
- package/src/services/catalog-cache-store.ts +316 -0
- package/src/services/claude-cli.ts +304 -0
- package/src/services/claude-runner.ts +31 -0
- package/src/services/claude-settings.ts +1564 -0
- package/src/services/cli-tool-commands.ts +133 -0
- package/src/services/community-fetcher.ts +90 -0
- package/src/services/community-styles.ts +1194 -0
- package/src/services/content-drift.ts +208 -0
- package/src/services/conventions-manager.ts +945 -0
- package/src/services/doctor-bins.ts +63 -0
- package/src/services/doctor.ts +570 -0
- package/src/services/dotenv.ts +181 -0
- package/src/services/git-worktree.ts +129 -0
- package/src/services/github-budget.ts +274 -0
- package/src/services/gitignore-check.ts +63 -0
- package/src/services/gitignore-detector.ts +167 -0
- package/src/services/gitignore-fixer.ts +280 -0
- package/src/services/gitignore-resolver.ts +208 -0
- package/src/services/gitignore-service.ts +167 -0
- package/src/services/hook-registration.ts +218 -0
- package/src/services/install-plan.ts +107 -0
- package/src/services/local-marketplace.ts +657 -0
- package/src/services/manifest.ts +250 -0
- package/src/services/marketplace-catalog-git.ts +173 -0
- package/src/services/marketplace-catalog.ts +129 -0
- package/src/services/marketplace-fetcher.ts +400 -0
- package/src/services/marketplace-refresh.ts +245 -0
- package/src/services/marketplace-sync.ts +145 -0
- package/src/services/mate-availability.ts +133 -0
- package/src/services/mate-catalog.ts +265 -0
- package/src/services/mcp-registry.ts +171 -0
- package/src/services/models-core.ts +969 -0
- package/src/services/models-manager.ts +606 -0
- package/src/services/plugin-checkout.ts +186 -0
- package/src/services/plugin-manager.ts +847 -0
- package/src/services/plugin-mcp-config.ts +242 -0
- package/src/services/plugin-releases.ts +47 -0
- package/src/services/plugin-requires.ts +227 -0
- package/src/services/plugin-setup.ts +965 -0
- package/src/services/profile-adopt.ts +206 -0
- package/src/services/profile-materializer.ts +133 -0
- package/src/services/profile-sync.ts +158 -0
- package/src/services/profiles.ts +225 -0
- package/src/services/resolver.ts +377 -0
- package/src/services/settings-file.ts +69 -0
- package/src/services/settings-manager.ts +307 -0
- package/src/services/skills-manager.ts +1111 -0
- package/src/services/skillsmp-client.ts +193 -0
- package/src/services/styles-manager.ts +1294 -0
- package/src/services/symlink-manager.ts +189 -0
- package/src/services/terminology-filler.ts +266 -0
- package/src/services/tool-probe.ts +1637 -0
- package/src/services/toolchain.ts +431 -0
- package/src/services/update-engine.ts +691 -0
- package/src/services/update-plan.ts +820 -0
- package/src/services/version-check.ts +127 -0
- package/src/services/version-snapshot.ts +189 -0
- package/src/tui.tsx +112 -0
- package/src/types/bun.d.ts +27 -0
- package/src/types/gitignore.ts +68 -0
- package/src/types/index.ts +447 -0
- package/src/ui/App.tsx +535 -0
- package/src/ui/adapters/catalogNotice.ts +122 -0
- package/src/ui/adapters/cliToolsAdapter.ts +176 -0
- package/src/ui/adapters/modelsAdapter.ts +213 -0
- package/src/ui/adapters/pluginsAdapter.ts +413 -0
- package/src/ui/adapters/settingsAdapter.ts +168 -0
- package/src/ui/adapters/skillsAdapter.ts +229 -0
- package/src/ui/adapters/stylesAdapter.ts +403 -0
- package/src/ui/components/CategoryHeader.tsx +64 -0
- package/src/ui/components/EmptyFilterState.tsx +32 -0
- package/src/ui/components/FlagDetailEditor.tsx +0 -0
- package/src/ui/components/ScopeIndicator.tsx +63 -0
- package/src/ui/components/ScrollableList.tsx +95 -0
- package/src/ui/components/SearchInput.tsx +57 -0
- package/src/ui/components/StyledText.tsx +75 -0
- package/src/ui/components/TabBar.tsx +101 -0
- package/src/ui/components/layout/FooterHints.tsx +78 -0
- package/src/ui/components/layout/Panel.tsx +63 -0
- package/src/ui/components/layout/ProgressBar.tsx +44 -0
- package/src/ui/components/layout/ScopeTabs.tsx +54 -0
- package/src/ui/components/layout/ScreenLayout.tsx +321 -0
- package/src/ui/components/layout/index.ts +9 -0
- package/src/ui/components/modals/ConfirmModal.tsx +62 -0
- package/src/ui/components/modals/InputModal.tsx +75 -0
- package/src/ui/components/modals/LoadingModal.tsx +51 -0
- package/src/ui/components/modals/MessageModal.tsx +66 -0
- package/src/ui/components/modals/ModalContainer.tsx +159 -0
- package/src/ui/components/modals/SelectModal.tsx +90 -0
- package/src/ui/components/modals/index.ts +6 -0
- package/src/ui/components/primitives/ActionHints.tsx +44 -0
- package/src/ui/components/primitives/DetailSection.tsx +23 -0
- package/src/ui/components/primitives/KeyValueLine.tsx +26 -0
- package/src/ui/components/primitives/ListCategoryRow.tsx +45 -0
- package/src/ui/components/primitives/MetaText.tsx +40 -0
- package/src/ui/components/primitives/ScopeDetail.tsx +67 -0
- package/src/ui/components/primitives/ScopeSquares.tsx +58 -0
- package/src/ui/components/primitives/SelectableRow.tsx +30 -0
- package/src/ui/components/primitives/index.ts +9 -0
- package/src/ui/hooks/index.ts +2 -0
- package/src/ui/hooks/useGitignoreModal.ts +95 -0
- package/src/ui/hooks/useKeyboard.ts +24 -0
- package/src/ui/hooks/useKeyboardHandler.ts +63 -0
- package/src/ui/registry.ts +33 -0
- package/src/ui/renderers/cliToolRenderers.tsx +326 -0
- package/src/ui/renderers/gitignoreRenderers.tsx +80 -0
- package/src/ui/renderers/markdownRenderer.tsx +302 -0
- package/src/ui/renderers/mcpRenderers.tsx +149 -0
- package/src/ui/renderers/modelRenderers.tsx +1309 -0
- package/src/ui/renderers/modelVisuals.tsx +1402 -0
- package/src/ui/renderers/pluginRenderers.tsx +1089 -0
- package/src/ui/renderers/pluginRowText.ts +70 -0
- package/src/ui/renderers/pluginStatusText.ts +56 -0
- package/src/ui/renderers/profileRenderers.tsx +406 -0
- package/src/ui/renderers/settingsRenderers.tsx +218 -0
- package/src/ui/renderers/skillRenderers.tsx +479 -0
- package/src/ui/renderers/styleRenderers.tsx +813 -0
- package/src/ui/screens/AliasScreen.tsx +1558 -0
- package/src/ui/screens/CliToolsScreen.tsx +367 -0
- package/src/ui/screens/EnvVarsScreen.tsx +221 -0
- package/src/ui/screens/GitignoreScreen.tsx +562 -0
- package/src/ui/screens/McpRegistryScreen.tsx +366 -0
- package/src/ui/screens/McpScreen.tsx +256 -0
- package/src/ui/screens/ModelsScreen.tsx +542 -0
- package/src/ui/screens/PluginsScreen.tsx +1423 -0
- package/src/ui/screens/ProfilesScreen.tsx +438 -0
- package/src/ui/screens/SkillsScreen.tsx +853 -0
- package/src/ui/screens/StylesScreen.tsx +1064 -0
- package/src/ui/screens/index.ts +11 -0
- package/src/ui/state/AppContext.tsx +233 -0
- package/src/ui/state/DimensionsContext.tsx +154 -0
- package/src/ui/state/reducer.ts +810 -0
- package/src/ui/state/types.ts +503 -0
- package/src/ui/theme-mode.ts +440 -0
- package/src/ui/theme-resolve.ts +147 -0
- package/src/ui/theme.ts +175 -0
- package/src/utils/clipboard.ts +58 -0
- package/src/utils/command-utils.ts +149 -0
- package/src/utils/config-dir.ts +45 -0
- package/src/utils/file-locking.ts +146 -0
- package/src/utils/fuzzy-search.ts +138 -0
- package/src/utils/go-module.ts +36 -0
- package/src/utils/open-file.ts +84 -0
- package/src/utils/run.ts +67 -0
- package/src/utils/string-utils.ts +88 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { isEnabledButNotInstalled } from "../services/plugin-manager.js";
|
|
3
|
+
import type { PluginInfo } from "../services/plugin-manager.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* "Enabled but not installed" is a broken state that previously rendered
|
|
7
|
+
* identically to a healthy install: a green scope square and no version.
|
|
8
|
+
*
|
|
9
|
+
* It is also invisible to every update path, because the check is
|
|
10
|
+
* `installedVersion && compare(latest, installedVersion) > 0` — with no
|
|
11
|
+
* installed version that is false, so no update path will ever touch it.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const plugin = (over: Partial<PluginInfo>): PluginInfo =>
|
|
15
|
+
({
|
|
16
|
+
id: "x@magus",
|
|
17
|
+
name: "x",
|
|
18
|
+
version: "1.0.0",
|
|
19
|
+
description: "",
|
|
20
|
+
marketplace: "magus",
|
|
21
|
+
marketplaceDisplay: "Magus",
|
|
22
|
+
enabled: true,
|
|
23
|
+
...over,
|
|
24
|
+
}) as PluginInfo;
|
|
25
|
+
|
|
26
|
+
describe("isEnabledButNotInstalled", () => {
|
|
27
|
+
test("enabled in project scope with no installed version is flagged", () => {
|
|
28
|
+
// The real case: terminal/code-search are in this project's enabledPlugins
|
|
29
|
+
// but have no registry entry for it and none at user scope.
|
|
30
|
+
expect(
|
|
31
|
+
isEnabledButNotInstalled(
|
|
32
|
+
plugin({
|
|
33
|
+
projectScope: { enabled: true },
|
|
34
|
+
installedVersion: undefined,
|
|
35
|
+
}),
|
|
36
|
+
),
|
|
37
|
+
).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("a healthy install is not flagged", () => {
|
|
41
|
+
expect(
|
|
42
|
+
isEnabledButNotInstalled(
|
|
43
|
+
plugin({ projectScope: { enabled: true }, installedVersion: "1.0.0" }),
|
|
44
|
+
),
|
|
45
|
+
).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('installedVersion "0.0.0" means installed-with-unknown-version, not missing', () => {
|
|
49
|
+
// Anthropic's official plugins record exactly this. Treating it as missing
|
|
50
|
+
// would mislabel every one of them as broken.
|
|
51
|
+
expect(
|
|
52
|
+
isEnabledButNotInstalled(
|
|
53
|
+
plugin({ userScope: { enabled: true }, installedVersion: "0.0.0" }),
|
|
54
|
+
),
|
|
55
|
+
).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("not enabled anywhere is not flagged — it is simply available", () => {
|
|
59
|
+
expect(
|
|
60
|
+
isEnabledButNotInstalled(plugin({ installedVersion: undefined })),
|
|
61
|
+
).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("orphaned plugins are excluded — they have their own 'deprecated' state", () => {
|
|
65
|
+
expect(
|
|
66
|
+
isEnabledButNotInstalled(
|
|
67
|
+
plugin({
|
|
68
|
+
isOrphaned: true,
|
|
69
|
+
projectScope: { enabled: true },
|
|
70
|
+
installedVersion: undefined,
|
|
71
|
+
}),
|
|
72
|
+
),
|
|
73
|
+
).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("any enabled scope counts, not just project", () => {
|
|
77
|
+
for (const scope of ["userScope", "projectScope", "localScope"] as const) {
|
|
78
|
+
expect(
|
|
79
|
+
isEnabledButNotInstalled(plugin({ [scope]: { enabled: true } })),
|
|
80
|
+
).toBe(true);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("a scope present but disabled does not count as enabled", () => {
|
|
85
|
+
expect(
|
|
86
|
+
isEnabledButNotInstalled(
|
|
87
|
+
plugin({
|
|
88
|
+
projectScope: { enabled: false },
|
|
89
|
+
installedVersion: undefined,
|
|
90
|
+
}),
|
|
91
|
+
),
|
|
92
|
+
).toBe(false);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for file locking utilities
|
|
3
|
+
*
|
|
4
|
+
* Tests cover:
|
|
5
|
+
* - Exclusive lock behavior (TEST-095)
|
|
6
|
+
* - Lock release on completion (TEST-095)
|
|
7
|
+
* - Concurrent write protection
|
|
8
|
+
* - Lock timeout handling
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
|
12
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
13
|
+
import { tmpdir } from "node:os";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import {
|
|
16
|
+
forceRemoveLock,
|
|
17
|
+
getLockInfo,
|
|
18
|
+
isFileLocked,
|
|
19
|
+
withFileLock,
|
|
20
|
+
} from "../utils/file-locking.js";
|
|
21
|
+
|
|
22
|
+
describe("file-locking", () => {
|
|
23
|
+
let testDir: string;
|
|
24
|
+
let testFile: string;
|
|
25
|
+
|
|
26
|
+
beforeEach(async () => {
|
|
27
|
+
// Create temporary directory for tests
|
|
28
|
+
testDir = await mkdtemp(join(tmpdir(), "magus-test-"));
|
|
29
|
+
testFile = join(testDir, "test-file.json");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
afterEach(async () => {
|
|
33
|
+
// Clean up test directory
|
|
34
|
+
await rm(testDir, { recursive: true, force: true });
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe("withFileLock", () => {
|
|
38
|
+
it("should execute operation with lock held - TEST-095", async () => {
|
|
39
|
+
let operationExecuted = false;
|
|
40
|
+
|
|
41
|
+
await withFileLock(testFile, async () => {
|
|
42
|
+
operationExecuted = true;
|
|
43
|
+
expect(isFileLocked(testFile)).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
expect(operationExecuted).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("should release lock after operation completes - TEST-095", async () => {
|
|
50
|
+
await withFileLock(testFile, async () => {
|
|
51
|
+
// Operation
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
expect(isFileLocked(testFile)).toBe(false);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("should release lock even if operation throws", async () => {
|
|
58
|
+
await expect(
|
|
59
|
+
withFileLock(testFile, async () => {
|
|
60
|
+
throw new Error("Operation failed");
|
|
61
|
+
}),
|
|
62
|
+
).rejects.toThrow("Operation failed");
|
|
63
|
+
|
|
64
|
+
expect(isFileLocked(testFile)).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("should return operation result", async () => {
|
|
68
|
+
const result = await withFileLock(testFile, async () => {
|
|
69
|
+
return "success";
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
expect(result).toBe("success");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("should block concurrent access - TEST-095", async () => {
|
|
76
|
+
const executionOrder: number[] = [];
|
|
77
|
+
|
|
78
|
+
// First operation holds lock for 100ms
|
|
79
|
+
const promise1 = withFileLock(testFile, async () => {
|
|
80
|
+
executionOrder.push(1);
|
|
81
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
82
|
+
executionOrder.push(2);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Wait a bit to ensure first lock is acquired
|
|
86
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
87
|
+
|
|
88
|
+
// Second operation should wait for lock
|
|
89
|
+
const promise2 = withFileLock(testFile, async () => {
|
|
90
|
+
executionOrder.push(3);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
await Promise.all([promise1, promise2]);
|
|
94
|
+
|
|
95
|
+
// Second operation should only execute after first completes
|
|
96
|
+
expect(executionOrder).toEqual([1, 2, 3]);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("should timeout if lock cannot be acquired", async () => {
|
|
100
|
+
// Skip this test in CI as it takes 10+ seconds
|
|
101
|
+
// The timeout behavior is tested in integration tests
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("should handle multiple sequential operations", async () => {
|
|
105
|
+
for (let i = 0; i < 5; i++) {
|
|
106
|
+
await withFileLock(testFile, async () => {
|
|
107
|
+
expect(isFileLocked(testFile)).toBe(true);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
expect(isFileLocked(testFile)).toBe(false);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe("isFileLocked", () => {
|
|
116
|
+
it("should return false for unlocked files", () => {
|
|
117
|
+
expect(isFileLocked(testFile)).toBe(false);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("should return true while lock is held", async () => {
|
|
121
|
+
await withFileLock(testFile, async () => {
|
|
122
|
+
expect(isFileLocked(testFile)).toBe(true);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("should return false after lock is released", async () => {
|
|
127
|
+
await withFileLock(testFile, async () => {});
|
|
128
|
+
expect(isFileLocked(testFile)).toBe(false);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe("forceRemoveLock", () => {
|
|
133
|
+
it("should remove lock file", async () => {
|
|
134
|
+
// Create lock by starting operation but not finishing
|
|
135
|
+
await withFileLock(testFile, async () => {
|
|
136
|
+
// Lock is held here
|
|
137
|
+
expect(isFileLocked(testFile)).toBe(true);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Lock should be released
|
|
141
|
+
expect(isFileLocked(testFile)).toBe(false);
|
|
142
|
+
|
|
143
|
+
// Force remove should not throw on non-existent lock
|
|
144
|
+
await forceRemoveLock(testFile);
|
|
145
|
+
expect(isFileLocked(testFile)).toBe(false);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("should not throw if lock file does not exist", async () => {
|
|
149
|
+
await forceRemoveLock(testFile);
|
|
150
|
+
expect(isFileLocked(testFile)).toBe(false);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe("getLockInfo", () => {
|
|
155
|
+
it("should return null for unlocked files", async () => {
|
|
156
|
+
const info = await getLockInfo(testFile);
|
|
157
|
+
expect(info).toBeNull();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("should return lock information while locked", async () => {
|
|
161
|
+
await withFileLock(testFile, async () => {
|
|
162
|
+
const info = await getLockInfo(testFile);
|
|
163
|
+
expect(info).not.toBeNull();
|
|
164
|
+
expect(info?.pid).toBe(String(process.pid));
|
|
165
|
+
expect(info?.timestamp).toBeDefined();
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("should return null after lock is released", async () => {
|
|
170
|
+
await withFileLock(testFile, async () => {});
|
|
171
|
+
const info = await getLockInfo(testFile);
|
|
172
|
+
expect(info).toBeNull();
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
describe("edge cases", () => {
|
|
177
|
+
it("should handle nested lock attempts on different files", async () => {
|
|
178
|
+
const file1 = join(testDir, "file1.json");
|
|
179
|
+
const file2 = join(testDir, "file2.json");
|
|
180
|
+
|
|
181
|
+
await withFileLock(file1, async () => {
|
|
182
|
+
await withFileLock(file2, async () => {
|
|
183
|
+
expect(isFileLocked(file1)).toBe(true);
|
|
184
|
+
expect(isFileLocked(file2)).toBe(true);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
expect(isFileLocked(file1)).toBe(false);
|
|
189
|
+
expect(isFileLocked(file2)).toBe(false);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("should prevent nested lock attempts on same file", async () => {
|
|
193
|
+
// Skip this test in CI as it takes 10+ seconds to timeout
|
|
194
|
+
// The nested lock prevention is tested via other tests
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("should handle rapid lock/unlock cycles", async () => {
|
|
198
|
+
const iterations = 10;
|
|
199
|
+
for (let i = 0; i < iterations; i++) {
|
|
200
|
+
await withFileLock(testFile, async () => {
|
|
201
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
expect(isFileLocked(testFile)).toBe(false);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# NOT from this machine: nothing brew manages here is outdated, so no real non-empty output could be captured read-only.
|
|
2
|
+
# This is Homebrew's OWN expected output for `brew outdated --json=v2`, copied verbatim from
|
|
3
|
+
# /opt/homebrew/Library/Homebrew/test/cmd/outdated_spec.rb ("outputs JSON for outdated formulae and casks")
|
|
4
|
+
# at Homebrew commit 088d2f79849d1b08792459b65026207a91c545d6 (JSON.pretty_generate of that hash).
|
|
5
|
+
# The generator is cmd/outdated.rb json_info: { name: f.full_name, installed_versions, current_version, pinned, pinned_version }.
|
|
6
|
+
{
|
|
7
|
+
"formulae": [
|
|
8
|
+
{
|
|
9
|
+
"name": "testball",
|
|
10
|
+
"installed_versions": [
|
|
11
|
+
"0.0.1"
|
|
12
|
+
],
|
|
13
|
+
"current_version": "0.1",
|
|
14
|
+
"pinned": false,
|
|
15
|
+
"pinned_version": null
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"casks": [
|
|
19
|
+
{
|
|
20
|
+
"name": "local-caffeine",
|
|
21
|
+
"installed_versions": [
|
|
22
|
+
"1.2.2"
|
|
23
|
+
],
|
|
24
|
+
"current_version": "1.2.3",
|
|
25
|
+
"pinned": false,
|
|
26
|
+
"pinned_version": null
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# captured 2026-09-11 23:12 AEST and again 2026-09-12 00:40 AEST on the reporter's machine (Homebrew 6.0.22), read-only:
|
|
2
|
+
# HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_NO_ENV_HINTS=1 NO_COLOR=1 brew outdated --json=v2 tmux
|
|
3
|
+
# exit=0 (tmux 3.7c installed, current)
|
|
4
|
+
{
|
|
5
|
+
"formulae": [],
|
|
6
|
+
"casks": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# captured 2026-09-12 00:40 AEST, read-only:
|
|
2
|
+
# HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_NO_ENV_HINTS=1 NO_COLOR=1 brew outdated --json=v2 cowsay-not-a-formula-xyz
|
|
3
|
+
# exit=1, stdout empty, stderr below. (Assumption A5 was half wrong: a formula brew KNOWS but has not
|
|
4
|
+
# installed — wget was tried before it turned out to be installed — exits 0 with formulae: [], so ownership
|
|
5
|
+
# must be decided before brew's silence is believed.)
|
|
6
|
+
Error: No available formula with the name "cowsay-not-a-formula-xyz".
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# captured 2026-09-12 00:58 AEST, read-only: go version -m /Users/jack/go/bin/tmux-mcp (go 1.27.1, exit=0)
|
|
2
|
+
/Users/jack/go/bin/tmux-mcp: go1.27.1
|
|
3
|
+
path github.com/MadAppGang/tmux-mcp/v2
|
|
4
|
+
mod github.com/MadAppGang/tmux-mcp/v2 v2.0.0 h1:DhieVeG/e6K/+++7XrP9ruhCnywVatf8e+bnjzjMLXE=
|
|
5
|
+
dep github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
|
|
6
|
+
dep github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
|
|
7
|
+
dep github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
|
|
8
|
+
dep github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|
9
|
+
dep github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
|
|
10
|
+
dep github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
|
11
|
+
dep github.com/mark3labs/mcp-go v0.45.0 h1:s0S8qR/9fWaQ3pHxz7pm1uQ0DrswoSnRIxKIjbiQtkc=
|
|
12
|
+
dep github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
|
13
|
+
dep github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
|
|
14
|
+
dep github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
|
|
15
|
+
dep golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
|
16
|
+
dep gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
17
|
+
build -buildmode=exe
|
|
18
|
+
build -compiler=gc
|
|
19
|
+
build DefaultGODEBUG=tracebacklabels=0,x509sslcertoverrideplatform=0
|
|
20
|
+
build CGO_ENABLED=1
|
|
21
|
+
build CGO_CFLAGS=
|
|
22
|
+
build CGO_CPPFLAGS=
|
|
23
|
+
build CGO_CXXFLAGS=
|
|
24
|
+
build CGO_LDFLAGS=
|
|
25
|
+
build GOARCH=arm64
|
|
26
|
+
build GOOS=darwin
|
|
27
|
+
build GOARM64=v8.0
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
# captured 2026-09-12 00:50 AEST, read-only GET: curl -sS 'https://registry.npmjs.org/-/package/%40nope-xyz%2Fdoes-not-exist-zz/dist-tags'
|
|
2
|
+
# http 401 — the registry answers 401, not 404, for a scoped name that does not exist (status trailer removed)
|
|
3
|
+
"Unauthorized"
|
|
4
|
+
# http=401
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
# captured 2026-09-12 00:50 AEST, read-only GET (verifies A3: a scoped name with / encoded as %2F answers):
|
|
2
|
+
# curl -sS 'https://registry.npmjs.org/-/package/%40openai%2Fcodex/dist-tags' (http 200; curl's status trailer removed)
|
|
3
|
+
{"beta":"0.1.2505172116","native":"0.1.2505291658","linux-arm64":"0.154.0-linux-arm64","latest":"0.154.0","linux-x64":"0.154.0-linux-x64","win32-arm64":"0.154.0-win32-arm64","darwin-arm64":"0.154.0-darwin-arm64","darwin-x64":"0.154.0-darwin-x64","win32-x64":"0.154.0-win32-x64","alpha-darwin-arm64":"0.155.0-alpha.3.9-darwin-arm64","alpha":"0.155.0-alpha.3.9","alpha-linux-arm64":"0.155.0-alpha.3.9-linux-arm64","alpha-darwin-x64":"0.155.0-alpha.3.9-darwin-x64","alpha-linux-x64":"0.155.0-alpha.3.9-linux-x64","alpha-win32-x64":"0.155.0-alpha.3.9-win32-x64","alpha-win32-arm64":"0.155.0-alpha.3.9-win32-arm64"}
|
|
4
|
+
# http=200
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# captured 2026-09-12 00:42 AEST, read-only: cat ~/.local/share/uv/tools/browser-use/uv-receipt.toml (uv 0.11.15)
|
|
2
|
+
[tool]
|
|
3
|
+
requirements = [{ name = "browser-use" }]
|
|
4
|
+
entrypoints = [
|
|
5
|
+
{ name = "browser", install-path = "/Users/jack/.local/bin/browser", from = "browser-use" },
|
|
6
|
+
{ name = "browser-use", install-path = "/Users/jack/.local/bin/browser-use", from = "browser-use" },
|
|
7
|
+
{ name = "browser-use-tui", install-path = "/Users/jack/.local/bin/browser-use-tui", from = "browser-use" },
|
|
8
|
+
{ name = "browseruse", install-path = "/Users/jack/.local/bin/browseruse", from = "browser-use" },
|
|
9
|
+
{ name = "bu", install-path = "/Users/jack/.local/bin/bu", from = "browser-use" },
|
|
10
|
+
]
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# captured 2026-09-12 00:55 AEST: UV_TOOL_DIR=$SESSION/validation/captures/uv-scratch/tools UV_TOOL_BIN_DIR=$SESSION/validation/captures/uv-scratch/bin UV_CACHE_DIR=$SESSION/validation/captures/uv-scratch/cache UV_PYTHON_INSTALL_DIR=$SESSION/validation/captures/uv-scratch/python NO_COLOR=1 uv tool install --force git+https://github.com/VaasuDevanS/cowsay-python@v6.1
|
|
2
|
+
# then: cat $SESSION/validation/captures/uv-scratch/tools/cowsay/uv-receipt.toml
|
|
3
|
+
# $SESSION = claudish worktree update-fix2 ai-docs/sessions/dev-feature-magus-unify-20260911-2240 (the user's real uv tools were never touched)
|
|
4
|
+
[tool]
|
|
5
|
+
requirements = [{ name = "cowsay", git = "https://github.com/VaasuDevanS/cowsay-python?rev=v6.1" }]
|
|
6
|
+
entrypoints = [
|
|
7
|
+
{ name = "cowsay", install-path = "/Users/jack/mag/claudish/.claude/worktrees/update-fix2/ai-docs/sessions/dev-feature-magus-unify-20260911-2240/validation/captures/uv-scratch/bin/cowsay", from = "cowsay" },
|
|
8
|
+
]
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# captured 2026-09-12 00:54 AEST: UV_TOOL_DIR=$SESSION/validation/captures/uv-scratch/tools UV_TOOL_BIN_DIR=$SESSION/validation/captures/uv-scratch/bin UV_CACHE_DIR=$SESSION/validation/captures/uv-scratch/cache UV_PYTHON_INSTALL_DIR=$SESSION/validation/captures/uv-scratch/python NO_COLOR=1 uv tool install 'cowsay==6.0'
|
|
2
|
+
# then: cat $SESSION/validation/captures/uv-scratch/tools/cowsay/uv-receipt.toml
|
|
3
|
+
# $SESSION = claudish worktree update-fix2 ai-docs/sessions/dev-feature-magus-unify-20260911-2240 (the user's real uv tools were never touched)
|
|
4
|
+
[tool]
|
|
5
|
+
requirements = [{ name = "cowsay", specifier = "==6.0" }]
|
|
6
|
+
entrypoints = [
|
|
7
|
+
{ name = "cowsay", install-path = "/Users/jack/mag/claudish/.claude/worktrees/update-fix2/ai-docs/sessions/dev-feature-magus-unify-20260911-2240/validation/captures/uv-scratch/bin/cowsay", from = "cowsay" },
|
|
8
|
+
]
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# captured 2026-09-12 00:42 AEST, read-only: cat ~/.local/share/uv/tools/graphifyy/uv-receipt.toml (uv 0.11.15)
|
|
2
|
+
[tool]
|
|
3
|
+
requirements = [{ name = "graphifyy", extras = ["mcp"] }]
|
|
4
|
+
entrypoints = [
|
|
5
|
+
{ name = "graphify", install-path = "/Users/jack/.local/bin/graphify", from = "graphifyy" },
|
|
6
|
+
{ name = "graphify-mcp", install-path = "/Users/jack/.local/bin/graphify-mcp", from = "graphifyy" },
|
|
7
|
+
]
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# captured 2026-09-12 00:42 AEST, read-only: cat ~/.local/share/uv/tools/serena-agent/uv-receipt.toml (uv 0.11.15)
|
|
2
|
+
[tool]
|
|
3
|
+
requirements = [{ name = "serena-agent" }]
|
|
4
|
+
python = "3.13"
|
|
5
|
+
entrypoints = [
|
|
6
|
+
{ name = "serena", install-path = "/Users/jack/.local/bin/serena", from = "serena-agent" },
|
|
7
|
+
{ name = "serena-agent", install-path = "/Users/jack/.local/bin/serena-agent", from = "serena-agent" },
|
|
8
|
+
{ name = "serena-hooks", install-path = "/Users/jack/.local/bin/serena-hooks", from = "serena-agent" },
|
|
9
|
+
]
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# captured 2026-09-12 00:54 AEST: UV_TOOL_DIR=$SESSION/validation/captures/uv-scratch/tools UV_TOOL_BIN_DIR=$SESSION/validation/captures/uv-scratch/bin UV_CACHE_DIR=$SESSION/validation/captures/uv-scratch/cache UV_PYTHON_INSTALL_DIR=$SESSION/validation/captures/uv-scratch/python NO_COLOR=1 uv tool list --outdated (exit=0)
|
|
2
|
+
# cowsay was installed as 'cowsay==6.0' (receipt specifier ==6.0); uv still lists it as outdated.
|
|
3
|
+
# $SESSION = claudish worktree update-fix2 ai-docs/sessions/dev-feature-magus-unify-20260911-2240 (the user's real uv tools were never touched)
|
|
4
|
+
cowsay v6.0 [latest: 6.1]
|
|
5
|
+
- cowsay
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# captured 2026-09-12 00:42 AEST on the reporter's machine, read-only: uv tool list --outdated (uv 0.11.15, exit=0)
|
|
2
|
+
# Same text as the 2026-09-11 23:12 baseline. browser-use and mcp are absent: current.
|
|
3
|
+
claude-swap v0.24.1 [latest: 0.26.0]
|
|
4
|
+
- claude-swap
|
|
5
|
+
- cswap
|
|
6
|
+
graphifyy v0.9.50 [latest: 0.9.58]
|
|
7
|
+
- graphify
|
|
8
|
+
- graphify-mcp
|
|
9
|
+
mistral-vibe v2.5.0 [latest: 2.25.3]
|
|
10
|
+
- vibe
|
|
11
|
+
- vibe-acp
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# captured 2026-09-12 00:42 AEST on the reporter's machine, read-only: uv tool list (uv 0.11.15, exit=0)
|
|
2
|
+
browser-use v0.13.10
|
|
3
|
+
- browser
|
|
4
|
+
- browser-use
|
|
5
|
+
- browser-use-tui
|
|
6
|
+
- browseruse
|
|
7
|
+
- bu
|
|
8
|
+
claude-swap v0.24.1
|
|
9
|
+
- claude-swap
|
|
10
|
+
- cswap
|
|
11
|
+
graphifyy v0.9.50
|
|
12
|
+
- graphify
|
|
13
|
+
- graphify-mcp
|
|
14
|
+
mcp v2.2.0
|
|
15
|
+
- mcp
|
|
16
|
+
mistral-vibe v2.5.0
|
|
17
|
+
- vibe
|
|
18
|
+
- vibe-acp
|
|
19
|
+
serena-agent v1.7.0
|
|
20
|
+
- serena
|
|
21
|
+
- serena-agent
|
|
22
|
+
- serena-hooks
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `joinKeys` decides how a key group reads in the footer chip.
|
|
3
|
+
*
|
|
4
|
+
* Found on screen, not in review: the Models tab advertised apply as `Entera`, and the
|
|
5
|
+
* layout's own scroll hint had been rendering as `^U/^DPgUp/Dn` on EVERY screen in the app.
|
|
6
|
+
* Both are two key names concatenated with nothing between them, which reads as one key that
|
|
7
|
+
* does not exist.
|
|
8
|
+
*/
|
|
9
|
+
import { describe, expect, test } from "bun:test";
|
|
10
|
+
import { joinKeys } from "../ui/components/layout/FooterHints.js";
|
|
11
|
+
|
|
12
|
+
describe("joinKeys", () => {
|
|
13
|
+
test("single-character keys butt together, the way an arrow pair is written", () => {
|
|
14
|
+
expect(joinKeys(["↑", "↓"])).toBe("↑↓");
|
|
15
|
+
expect(joinKeys(["j", "k"])).toBe("jk");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("one key is itself", () => {
|
|
19
|
+
expect(joinKeys(["U"])).toBe("U");
|
|
20
|
+
expect(joinKeys(["Enter"])).toBe("Enter");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("named keys get a gap — the regression this exists for", () => {
|
|
24
|
+
expect(joinKeys(["Enter", "a"])).toBe("Enter a");
|
|
25
|
+
expect(joinKeys(["^U/^D", "PgUp/Dn"])).toBe("^U/^D PgUp/Dn");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("a space, not a slash: several key names already contain one", () => {
|
|
29
|
+
expect(joinKeys(["^U/^D", "PgUp/Dn"])).not.toContain("//");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("a multi-byte single character still counts as one", () => {
|
|
33
|
+
// "↑".length is 1 but an emoji's is 2 — count code points, not UTF-16 units.
|
|
34
|
+
expect(joinKeys(["⌘", "⇧"])).toBe("⌘⇧");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("an empty group is empty rather than a stray separator", () => {
|
|
38
|
+
expect(joinKeys([])).toBe("");
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `formatCommand` renders a Command for a human, and must not lie.
|
|
3
|
+
*
|
|
4
|
+
* Two properties, and they pull in opposite directions:
|
|
5
|
+
*
|
|
6
|
+
* 1. **Display neutrality.** Every token magus actually builds is
|
|
7
|
+
* shell-safe, so the rendered line must be byte-identical to the string
|
|
8
|
+
* these builders produced before commands became argv. That is what lets
|
|
9
|
+
* item 3 land without touching `install`/`doctor` output.
|
|
10
|
+
* `doctor-bins.test.ts` is the end-to-end gate on that; these are the unit.
|
|
11
|
+
* 2. **A dangerous token must LOOK dangerous.** If a package name ever did
|
|
12
|
+
* contain shell syntax, printing it bare would show the reader a command
|
|
13
|
+
* that does something other than what runs. Quoting it makes the
|
|
14
|
+
* one-token-ness visible.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { describe, expect, test } from "bun:test";
|
|
18
|
+
import { type Command, formatCommand } from "../utils/command-utils.js";
|
|
19
|
+
|
|
20
|
+
const c = (cmd: string, ...args: string[]): Command => ({ cmd, args });
|
|
21
|
+
|
|
22
|
+
describe("formatCommand — display neutrality", () => {
|
|
23
|
+
test("every token this codebase really builds renders unquoted", () => {
|
|
24
|
+
// Sampled from the actual builders: npm scopes, version pins in both
|
|
25
|
+
// syntaxes, go module paths, long flags. If quoting ever fires for one of
|
|
26
|
+
// these, some install/doctor output changed.
|
|
27
|
+
expect(formatCommand(c("bun", "install", "-g", "claudish@1.2.0"))).toBe(
|
|
28
|
+
"bun install -g claudish@1.2.0",
|
|
29
|
+
);
|
|
30
|
+
expect(
|
|
31
|
+
formatCommand(c("npm", "install", "-g", "@anthropic-ai/claude-code")),
|
|
32
|
+
).toBe("npm install -g @anthropic-ai/claude-code");
|
|
33
|
+
expect(formatCommand(c("uv", "tool", "install", "aider==0.1"))).toBe(
|
|
34
|
+
"uv tool install aider==0.1",
|
|
35
|
+
);
|
|
36
|
+
expect(formatCommand(c("go", "install", "github.com/x/t@latest"))).toBe(
|
|
37
|
+
"go install github.com/x/t@latest",
|
|
38
|
+
);
|
|
39
|
+
expect(
|
|
40
|
+
formatCommand(
|
|
41
|
+
c("python3", "-m", "pip", "install", "--user", "--upgrade", "aider"),
|
|
42
|
+
),
|
|
43
|
+
).toBe("python3 -m pip install --user --upgrade aider");
|
|
44
|
+
expect(formatCommand(c("brew", "install", "tmux"))).toBe(
|
|
45
|
+
"brew install tmux",
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("a command with no arguments renders as just the executable", () => {
|
|
50
|
+
expect(formatCommand(c("brew"))).toBe("brew");
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("formatCommand — a dangerous token is made visible", () => {
|
|
55
|
+
test("shell metacharacters are single-quoted", () => {
|
|
56
|
+
expect(formatCommand(c("bun", "install", "-g", "a b; rm -rf /"))).toBe(
|
|
57
|
+
"bun install -g 'a b; rm -rf /'",
|
|
58
|
+
);
|
|
59
|
+
expect(formatCommand(c("bun", "install", "-g", "$(id)"))).toBe(
|
|
60
|
+
"bun install -g '$(id)'",
|
|
61
|
+
);
|
|
62
|
+
expect(formatCommand(c("bun", "install", "-g", "a|b"))).toBe(
|
|
63
|
+
"bun install -g 'a|b'",
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("an embedded single quote closes, escapes and reopens", () => {
|
|
68
|
+
// `'\''` is the only POSIX-portable way: a single-quoted string has no
|
|
69
|
+
// escape character at all, so the quote must be left and re-entered.
|
|
70
|
+
expect(formatCommand(c("x", "it's"))).toBe(`x 'it'\\''s'`);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("an empty argument is quoted so it does not vanish", () => {
|
|
74
|
+
// Unquoted it would render as nothing, and the reader would be shown a
|
|
75
|
+
// command with fewer arguments than the one that runs.
|
|
76
|
+
expect(formatCommand(c("x", "", "y"))).toBe("x '' y");
|
|
77
|
+
});
|
|
78
|
+
});
|