公开记录
软件健康报告模式 0.27.0 · 指标 2.5.0 · 2026-08-01 00:34 UTC

lethevimlet / sshift

A modern, responsive web-based SSH and SFTP terminal client for the AI stack

JavaScript · HTMLMIT★ 35 星标⑂ 3 复刻始于 2026年4月在 GitHub 上查看 ↗
类型网络服务如何判定

lethevimlet/sshift 的健康指数为 100 分中的 56 分,处于「中等」区间。 其得分最高的类别是Engineering Quality(91/100),最低的是Security(35/100)。 最近一次更新在 9 天前。 近期的大部分工作由 1 位贡献者完成。

56
总分 / 100
中等

软件健康指数

指标归入加权类别,统一采用 1–100 量表。总体分先取类别加权平均,再依据公开记录的分布进行校准,使各等级具有百分位含义;当公开证据触发高风险司法辖区政策时,评级会按政策调整,并设置 34(存在风险)的上限。

56
卓越93-100公开记录中的最高层级(约前 5%);基本满足所有检验标准
优秀80-92各方面均表现强劲;仅有少量不足
良好65-79健康;不足之处有限且可控
中等50-64可接受,但存在明显不足;建议进行审查
薄弱35-49多个领域存在实质性薄弱环节
存在风险20-34存在重大薄弱环节;采用时应保持审慎
危急1-19问题严重(项目被弃置、仅有单一维护者、缺乏基本工程规范)
活力社区与采用可持续性与治理工程质量安全AI 就绪度

评分画像

每条轴代表一个类别。形状比平均值更重要——健康的对象会填满整个图形,而“一峰一谷”式画像意味着某一维度的优势正掩盖另一维度的风险。

加权总体分 54 经校准后在公布的指数量表上为 56(记录校准 2026-08-02)。

所有权

Lethe个人账户
10 关注者12 个公开仓库始于 2012年12月@vimlet

该仓库由个人账户拥有。相较于组织支持的项目,单一所有者项目的延续性风险更高。

软件包生态系统

注册表软件包版本月下载量版本数最近发布标签
npm@lethevimlet/sshift1.6.12,001779 天前sshsftpterminalwebxtermweb-terminalssh-clientsftp-clientbrowser-terminal

按类别列示的指标

活力

项目是否仍有生命——是否仍在编写代码,是否仍在发布版本?

39薄弱 · 占总体的 21%
评分方式
28.8/36推送新近度 — 最近一次推送于 9 天前
8.3/36提交节奏 — 52 周中有 12 周有提交
18/18提交量 — 最近一年 244 次提交
10/10OpenSSF Scorecard:Maintained — 30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
所用输入
commits_last_year244
human_commit_share1
days_since_last_push9
active_weeks_last_year12
评分方式
0/27有发布版本 — 未发布任何发布版本
0/36发布时效 — 没有发布版本
0/27发布节奏 — 没有发布版本
0/10OpenSSF Scorecard:Signed-Releases — 无数据
所用输入
releases_count0
已排除计分(无数据或不适用):OpenSSF Scorecard:Signed-Releases。 其余权重已重新归一化。

社区与采用

项目是否拥有用户、下载量与关注度,并具备欢迎贡献者参与的配置?

49薄弱 · 占总体的 17%

流行度与采用

27存在风险
评分方式
24.8/60星标 — 35 个星标
2.5/25复刻 — 3 个复刻
0/15关注者 — 0 位关注者
所用输入
forks3
stars35
watchers0
growth_stateunverified
growth_factor_pct100
growth_unverified_reasonno_history

社区健康

70良好
评分方式
22.5/22.5README
22.5/22.5许可证 — 可识别的许可证(MIT)
18/18CONTRIBUTING 指南
0/13.5行为准则
0/7.2议题模板
0/6.3PR 模板
所用输入
has_readme
has_license
readme_badges
has_contributing
has_issue_template
has_code_of_conduct
readme_badge_services
has_pull_request_template
评分方式
44/80月度下载量 — npm 合计每月 2,001 次下载
0/20注册表被依赖数 — 该生态系统不报告此项
所用输入
packages@lethevimlet/sshift
dependents
ecosystemsnpm
total_downloads
monthly_downloads2,001
已排除计分(无数据或不适用):注册表被依赖数。 其余权重已重新归一化。

可持续性与治理

项目能否在其成员之外延续——巴士系数、响应能力、由谁支持,以及软件包的维护状况?

52中等 · 占总体的 23%
评分方式
9/54巴士系数 — 1 位贡献者贡献了半数提交
0/22.5提交分布 — 头号贡献者编写了 100% 的提交
1.4/13.5贡献者广度 — 1 位贡献者
3/10OpenSSF Scorecard:Contributors — project has 1 contributing companies or organizations -- score normalized to 3
所用输入
bus_factor1
contributors_sampled1
top_contributor_share1
评分方式
0/42议题解决 — 没有议题或无数据
30/30PR 接受 — 已裁定的 PR 中 1/1 已合并
0/13Newcomer PR acceptance — 30 天内没有首次贡献者的 PR 得到裁决
0/15OpenSSF Scorecard:Code-Review — Found 0/30 approved changesets -- score normalized to 0
所用输入
merged_prs1
open_issues0
closed_issues0
prs_merged_7d
prs_decided_7d
prs_merged_30d
prs_decided_30d
issue_closed_ratio
closed_unmerged_prs0
first_time_authors_30d
first_time_prs_merged_30d
first_time_prs_decided_30d
已排除计分(无数据或不适用):议题解决, newcomer_pr_acceptance。 其余权重已重新归一化。
评分方式
10/30所有权背书 — 个人(用户)账户
0/20已验证域名 — 不适用于个人账户
7.5/25所有者影响力 — lethevimlet 有 10 位关注者
20.1/25既往记录 — 12 个公开仓库,账户约 13 年
所用输入
followers10
owner_typeUser
is_verified
owner_loginlethevimlet
public_repos12
account_age_days4,987
已排除计分(无数据或不适用):已验证域名。 其余权重已重新归一化。
评分方式
25/25已发布且可解析 — npm 上有 1 个软件包
35/35发布时效 — 最近一次发布于 9 天前
20/20版本历史 — 77 个已发布版本
20/20未被弃用 — 活跃,未被弃用或撤回
所用输入
packages@lethevimlet/sshift
ecosystemsnpm
any_deprecated
min_days_since_publish9

工程质量

基础的工程与文档实践是否到位?

91优秀 · 占总体的 19%

工程实践

92优秀
评分方式
24/24CI 工作流 — 3 个工作流
24/24存在测试
16/16Linter 配置
9.6/9.6Pre-commit 钩子
0/6.4.editorconfig
0/20OpenSSF Scorecard:CI-Tests — 无数据
所用输入
has_ci
has_tests
has_editorconfig
has_linter_config
has_precommit_config
已排除计分(无数据或不适用):OpenSSF Scorecard:CI-Tests。 其余权重已重新归一化。

文档

90优秀
评分方式
30/30README
25/25文档目录
15/15文档 / 主页站点 — https://getsshift.dev
10/10仓库描述
0/10主题标签
10/10Wiki
所用输入
topics
has_wiki
homepagehttps://getsshift.dev
has_readme
has_docs_dir
has_description

安全

可见的安全与供应链实践是否稳固,且不存在未解决的高风险司法辖区暴露?

35薄弱 · 占总体的 16%

安全态势

35薄弱
评分方式
7.5/7.5Binary-Artifacts — no binaries found in the repo
0/7.5Branch-Protection — branch protection not enabled on development/release branches
0/2.5CI-Tests — 无数据
0/2.5CII-Best-Practices — no effort to earn an OpenSSF best practices badge detected
0/7.5Code-Review — Found 0/30 approved changesets -- score normalized to 0
0.8/2.5Contributors — project has 1 contributing companies or organizations -- score normalized to 3
10/10Dangerous-Workflow — no dangerous workflow patterns detected
0/7.5Dependency-Update-Tool — no update tool detected
0/5Fuzzing — project is not fuzzed
2.5/2.5许可证 — license file detected
7.5/7.5Maintained — 30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
5/5Packaging — packaging workflow detected
0/5Pinned-Dependencies — dependency not pinned by hash detected -- score normalized to 0
0/5SAST — no SAST tool detected
0/5Security-Policy — security policy file not detected
0/7.5Signed-Releases — 无数据
0/7.5Token-Permissions — detected GitHub workflow tokens with excessive permissions
0/7.5Vulnerabilities — 25 existing vulnerabilities detected
所用输入
sourceopenssf_scorecard
checks_evaluated16
scorecard_versionv5.5.0
checks_inconclusive2
scorecard_aggregate3.5
已排除计分(无数据或不适用):ci_tests, signed_releases。 其余权重已重新归一化。
评分方式
12.1/35直接依赖不含已知公告 — 2 个受影响:uuid 9.0.0 (high 7.5), express 4.18.2 (moderate 6.1)
5/25间接依赖不含已知公告 — 9 个受影响:body-parser 1.20.1 (high 7.5), engine.io 6.5.5 (high 7.5), path-to-regexp 0.1.7 (high 7.5),另有 6 个
18.6/40没有长期未处理的公告 — 9 个携带公告的软件包超过 90 天未处理;最早一条发布于 858 天前
所用输入
sourceosv
advisories17
affected_packages11
assessed_packages132
unassessed_packages0
affected_by_severityhigh 5, moderate 4, low 2
direct_affected_packages2
比对的是 npm:@lethevimlet/sshift@1.6.1 的运行时依赖闭包——安装已发布的软件包时真正被拉取进来的内容——共 132 个软件包。 未对可达性进行分析。

AI 就绪度

该仓库在多大程度上具备与 AI 编码代理协同开发与维护的条件?权重刻意设小(4%):代理工具链是一项真实的维护信号,但完全不具备的仓库仍可达到 100/100。

54中等 · 占总体的 4%
评分方式
45/45代理指令 — AGENTS.md
0/15机器可读文档(llms.txt)
27.2/40可读的提交历史 — 100 次人类提交中有 51 次说明了意图(结构化标题或解释性正文)
所用输入
has_llms_txt
legible_history_share0.51
agent_instruction_filesAGENTS.md
agent_instruction_max_bytes9,064
评分方式
0/18一条命令的引导启动
22/22自动化测试
11/11Lint / 格式化配置
0/11静态类型检查
10/10可复现环境 — Dockerfile, lockfile
0/10已体现的代理实践 — 最近 100 次提交中没有代理编写的提交
0/8自动化维护 — 未观察到自动依赖更新
0/10OpenSSF Scorecard:Pinned-Dependencies — dependency not pinned by hash detected -- score normalized to 0
所用输入
has_nix
has_tests
lockfilespackage-lock.json, pnpm-lock.yaml
has_dockerfile
typed_language
bootstrap_files
has_devcontainer
has_linter_config
typecheck_configs
agent_commit_share0
toolchain_manifests
dependency_bot_commit_share0
评分方式
0/45可类型检查的代码 — JavaScript,未配置类型检查
50/55可控的文件大小 — 采样的 77 个源文件中有 7 个超过 60KB
所用输入
primary_languageJavaScript
largest_source_bytes497,398
source_files_sampled77
oversized_source_files7

关键数据

35GitHub 星标
1贡献者
244最近 12 个月提交数
9距最近推送天数
0发布版本数
1巴士系数(bus factor)
0开放议题
npm软件包生态系统数

数据采集警告

  • Star history unavailable: GitHub GraphQL error: Resource not accessible by personal access token
  • GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository

更多细节

Star 与 Fork 历史 0 ★ / 3 ⇿
0Star
3Fork

每颗 star 和每个 fork 的添加时间,来自 GitHub 并按天汇总。累计增长位于其构成来源——每日新增——的正上方,二者可相互对照:稳定的自然增长与短暂的突增形态截然不同。当这一差别可被衡量时,它会作为增长真实性予以报告。

12233312026-042026-042026-05
OpenSSF Scorecard 3.5 / 10
3.5综合

来自开源项目 OpenSSF Scorecard 的独立、工具无关的安全评估。每项检查奖励的是安全实践本身,而非特定供应商的工具。Scorecard 无法判定的检查项标记为 不适用,并从安全评分中剔除(绝不按零分计)。Scorecard v5.5.0 · 2026-08-01 00:33 UTC

10Binary-Artifactsno binaries found in the repo
0Branch-Protectionbranch protection not enabled on development/release branches
不适用CI-Testsno pull request found
0CII-Best-Practicesno effort to earn an OpenSSF best practices badge detected
0Code-ReviewFound 0/30 approved changesets -- score normalized to 0
3Contributorsproject has 1 contributing companies or organizations -- score normalized to 3
10Dangerous-Workflowno dangerous workflow patterns detected
0Dependency-Update-Toolno update tool detected
0Fuzzingproject is not fuzzed
10Licenselicense file detected
10Maintained30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
10Packagingpackaging workflow detected
0Pinned-Dependenciesdependency not pinned by hash detected -- score normalized to 0
0SASTno SAST tool detected
0Security-Policysecurity policy file not detected
不适用Signed-Releasesno releases found
0Token-Permissionsdetected GitHub workflow tokens with excessive permissions
0Vulnerabilities25 existing vulnerabilities detected
直接依赖 17
注册表软件包版本约束清单文件
npm@xterm/addon-fit0.11.0package.json
npm@xterm/addon-image0.9.0package.json
npm@xterm/addon-search0.16.0package.json
npm@xterm/addon-serialize0.14.0package.json
npm@xterm/addon-unicode110.9.0package.json
npm@xterm/addon-web-links0.12.0package.json
npm@xterm/addon-webgl0.19.0package.json
npm@xterm/headless6.0.0package.json
npm@xterm/xterm6.0.0package.json
npmdotenv17.4.0package.json
npmexpress4.18.2package.json
npmhttpolyglot0.1.2package.json
npmlucide1.7.0package.json
npmselfsigned5.5.0package.json
npmsocket.io4.7.2package.json
npmssh21.15.0package.json
npmuuid9.0.0package.json
全部依赖 未采集

本报告未能采集到解析后的依赖集合:GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository

依赖安全公告 11

安装 npm:@lethevimlet/sshift@1.6.1 会引入 132 个包(直接与传递):其中 11 个存在已知公告,2 个为直接依赖。

软件包版本关系严重程度公告数修复版本
uuid9.0.0直接113.0.1
body-parser1.20.1间接22.3.0
engine.io6.5.5间接16.6.7
path-to-regexp0.1.7间接38.0.0
ws8.17.1间接28.21.0
express4.18.2直接25.0.0-beta.3
qs6.11.0间接26.14.2
send0.18.0间接10.19.0
serve-static1.15.0间接12.1.0
cookie0.4.2间接10.7.0
cookie0.5.0间接10.7.0

公告表示依赖图中记录的版本落入某条公告的受影响范围。可达性未经分析,且依赖图包含开发与测试的版本固定——某项发现可能只涉及工具链而非交付的软件。

原始 JSON 报告 机器可读
{
  "data": {
    "repo": {
      "topics": [],
      "is_fork": false,
      "size_kb": 7453,
      "has_wiki": true,
      "homepage": "https://getsshift.dev",
      "languages": {
        "CSS": 115037,
        "HTML": 614396,
        "Shell": 33274,
        "Dockerfile": 3633,
        "JavaScript": 1011287,
        "PowerShell": 39771
      },
      "pushed_at": "2026-07-22T16:15:20Z",
      "created_at": "2026-04-02T02:10:23Z",
      "owner_type": "User",
      "updated_at": "2026-07-30T02:19:45Z",
      "description": "A modern, responsive web-based SSH and SFTP terminal client for the AI stack",
      "is_archived": false,
      "is_disabled": false,
      "license_spdx": "MIT",
      "default_branch": "main",
      "license_spdx_raw": "MIT",
      "primary_language": "JavaScript",
      "significant_languages": [
        "JavaScript",
        "HTML"
      ]
    },
    "owner": {
      "blog": "https://vimlet.com",
      "name": "Lethe",
      "type": "User",
      "login": "lethevimlet",
      "company": "@vimlet",
      "location": "Madrid, Spain",
      "followers": 10,
      "avatar_url": "https://avatars.githubusercontent.com/u/2963923?v=4",
      "created_at": "2012-12-04T17:42:19Z",
      "is_verified": null,
      "public_repos": 12,
      "account_age_days": 4987
    },
    "license": {
      "state": "standard",
      "spdx_id": "MIT",
      "raw_spdx": "MIT",
      "file_present": true,
      "scorecard_found": true,
      "profile_has_license": true
    },
    "activity": {
      "releases": [],
      "recent_commits": [
        {
          "oid": "d551d43a298c091d10e0e373670271f3ba075fa8",
          "body": "forceResizeLikeRefit replicates a real window resize for one session\n(synchronous reflow -> measure -> fit -> clearTextureAtlas -> refresh ->\nssh-resize) and is wired into switchTab's safety net, ssh-control-acquired,\nssh-control-taken, and the force-resize button. _syncCharSizeThenClearAtlas\ncommit\n[…]\n in switchTab/setSessionFontSize/\nsetTerminalFontSize + SFTP setters. Adds deterministic regression tests\n(interlace-regression, force-resize-fix) and a manual repro harness.\n\nCo-authored-by: opencode",
          "is_bot": false,
          "headline": "v1.6.1: fix interlace black-band artefact on take-control/tab-grab",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-07-22T16:15:15Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "177b7c78a466cf53d7e80055fcc6ddc48583ebae",
          "body": "…settings\n\n- Add mic button next to keyboard button in tab actions (SSH tabs only;\n  disabled on SFTP). Merged Record/Pause toggle cycles idle -> recording ->\n  stop-and-transcribe, appending each segment to the transcript.\n- Speech-to-Text modal: editable transcript, Record/Pause toggle, Wand butto\n[…]\npe) so they behave identically on mobile/desktop.\n- _wireActionBtn fires touchend on all touch devices (not just isMobile)\n  fixing the 'mic dialog not working on mobile' bug.\n- Bump version to 1.6.0.",
          "is_bot": false,
          "headline": "v1.6.0: speech-to-text with mic button, AI wand cleanup, Speech & AI …",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-07-21T10:19:48Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "43a85892acc72e8e99bebe50ad59f4511452a794",
          "body": "…ew ruler white line\n\n- Fix interlaced black bands when dragging tabs between layout panels\n  (split, 2-columns, 2x2). After moveTabToPanel moves the terminal\n  wrapper DOM to a new container, the WebGL glyph atlas still holds\n  glyphs rasterised at the old panel's cell size. Fitting immediately\n  m\n[…]\nScrollbar: 4px on mobile, 8px on desktop. Slider bg overridden to\n  #3d444d via setProperty('background-color', ..., 'important').\n  MutationObserver enforces dimensions + bg on every xterm re-render.",
          "is_bot": false,
          "headline": "v1.5.2: fix interlace on tab drag between layout panels + hide overvi…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-07-20T21:59:43Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "b5d2277eeffd6e22b74ec84795c806b18774104d",
          "body": "…ew ruler white line\n\n- Mobile TUI scroll: touch scroll in mouse-tracking mode now sends SGR\n  mouse wheel escape sequences directly via ssh-data instead of\n  dispatching synthetic WheelEvents (which xterm's document-level touch\n  handler interfered with). Checks coreMouseService.activeProtocol\n  in\n[…]\nrent canvas created a visible white strip\n  when terminal had no scrollback or on blur.\n- MutationObserver runs on all platforms to enforce scrollbar\n  dimensions + slider bg on every xterm re-render.",
          "is_bot": false,
          "headline": "fix: mobile TUI scroll, rate-limit toast, thin scrollbar, hide overvi…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-07-20T21:55:19Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "22c84465965332accf238e50eb035ba20982013a",
          "body": "…use/scroll inactivity lockup\n\nBug 1 — Interlaced lines after Take Control on a refreshed browser tab\n- Root cause: the ssh-screen-sync completion callback wrote the\n  serialized terminal state + called terminal.resize(cols, rows) but\n  did NOT clear the WebGL glyph atlas. The atlas still held glyph\n[…]\n available, and that the\n  sftp-download code shape  does not throw.\n\nTest totals: 94 unit tests (was) -> 101 unit tests (+7 bugfix\nregressions). 0 failing. All touched source files pass node --check.",
          "is_bot": false,
          "headline": "v1.5.1: fix interlace-after-take-control, SFTP download crash, TUI mo…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-07-20T20:05:37Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "5dba19252ec4f055a0434557b55927bd0d97e742",
          "body": "…down fixes\n\nMarks the consolidated release of v1.4.9 + v1.4.10 changes:\n- Mobile tab-count duplicate-block bug fix + 8-phase hardening\n- xterm fidelity (surrogate split, OSC 52 cross-chunk, sync-window recovery, WebGL re-sync, payload validation, rate limits)\n- Sticky teardown respects remaining vi\n[…]\n tests + integration/browser suites (was 21 baseline)\n- Bug A: ssh-request-sync rate-limit no longer closes tabs (advisory flag)\n- Bug B: server restart = fresh slate, no localStorage auto-reconstruct",
          "is_bot": false,
          "headline": "v1.5.0: bump minor version — robustness pass + mobile tab/sticky tear…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-07-19T23:32:56Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "900ef22acb3c67d5d3302d997dd04a089c3db355",
          "body": "…art stale-tab leak\n\nBug A — ssh-request-sync rate-limit cascaded into closeTab\n- Root cause: the Phase-3 ssh-request-sync rate limit (added in v1.4.9)\n  emitted an 'ssh-error' with a sessionId. The client's ssh-error\n  handler at app.js:4521 had a catchall closeTab(sessionId) for ANY\n  error messag\n[…]\ns normally (server-restart case isolated)\n\nTest totals: 87 unit tests (was) → 94 unit tests (+7 bugfix regressions) + 11 skipped (SSH-credential-gated). Syntax checked: app.js, ssh.js, ssh-manager.js.",
          "is_bot": false,
          "headline": "v1.4.10: fix mobile dropdown switchTab closing all tabs + server-rest…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-07-19T23:31:33Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "196bbc20f06448c82b2c09665cf51f0fdc2d2c53",
          "body": "…lity + sticky teardown, add 145 tests + Docker SSH fixture\n\nPhase 1 — Mobile tab-count bug\n- handleTabOpened: remove duplicate createSSHTab/createSFTPTab block\n- server: socket.broadcast.emit('tab-opened') so originator doesn't echo to itself\n- createSSHTab/createSFTPTab: idempotency guards by sess\n[…]\nructure, env vars, fixture docs, troubleshooting\n\nTest totals: 21 passing / 5 skipped (baseline) → 146 passing / 18 skipped / 0 failing in Docker-less env; with Docker fixture: 164 passing / 0 skipped",
          "is_bot": false,
          "headline": "v1.4.9: robustness pass — fix mobile tab-count bug, harden xterm fide…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-07-19T23:07:00Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "ca041efeca981340b3ce74d377ae3ff2fad411d2",
          "body": "…ce re-measure after font swap\n\nThe v1.4.7 attempt (removing the redundant lineHeight/letterSpacing resets)\ndid not actually fix the interlace bug — those setters were no-ops since\nthe options were already at their base values, so removing them changed\nnothing.\n\nRoot cause: xterm.js v6's CharSizeSer\n[…]\nreshCharAtlas, so by the time fit() runs it sees the real\nfont's cell height and dims cols/rows correctly.  Uses the same private\n_api pattern already in use elsewhere (terminal._core._renderService).",
          "is_bot": false,
          "headline": "v1.4.8: fix interlace (alternating black bands) — force CharSizeServi…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-07-19T20:31:44Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "e2e330917d99ebb4313f588fb4b841444abbbe21",
          "body": "…nt bugs\n\nRoot cause for both symptoms was the sizing/fit path in _fitTerminal\nand initTerminal.\n\nBug 1 (alternating black horizontal bands between text rows):\n  _fitTerminal reset term.options.lineHeight and letterSpacing to base\n  values immediately before fitAddon.fit().  Those options are alread\n[…]\nbove transient collapsed\n  states), bumped retries to 5, and added two delayed safety-net refits\n  (250ms / 600ms) after the post-fonts-ready fit mirroring the existing\n  retry pattern in switchTab().",
          "is_bot": false,
          "headline": "v1.4.7: fix terminal interlace (black bands) and tiny-terminal-on-mou…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-07-15T11:26:41Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "2c40067b7c7b3bcda192836b857caa6a41d6bdb0",
          "body": "…tomGlyphs+WebGL as the correct approach",
          "is_bot": false,
          "headline": "v1.4.6: revert broken letterSpacing/lineHeight seam fix; document cus…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-06-11T15:34:47Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "fc8cd5b64e19abab901802686f60222b68912a5e",
          "body": "Only applies letterSpacing padding when it's imperceptible (≤0.15 CSS px);\nlarger padding that would create visible gaps between characters is skipped\nand we accept the occasional 1px seam instead. This fixes seams on most\nDPRs (e.g. 2x displays typically need ~0.1px padding) while avoiding the\nvisible spacing regression seen in v1.4.2-v1.4.3.",
          "is_bot": false,
          "headline": "v1.4.5: re-enable letterSpacing snapping with tight 0.15px threshold",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-06-11T13:40:08Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "40c353c7dbd0652a4f0f5fbb1bc0f8c7c640e619",
          "body": "The letterSpacing-based subpixel snapping (v1.4.2-v1.4.3) made seams\nmore visible rather than less — it added gaps between all characters\nbecause xterm.js customGlyphs does not fill the letterSpacing portion\nof block characters. Disabled _snapCellWidth and set letterSpacing: 0\nexplicitly. The primary seam mitigation is now customGlyphs: true,\nwhich draws block/box-drawing characters as vector fills that tile the\nfull cell width.",
          "is_bot": false,
          "headline": "v1.4.4: revert letterSpacing snapping, keep customGlyphs for seams",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-06-11T13:22:27Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "616efb13a77838555bca3cf9fa43396b27420391",
          "body": "…ment\n\nThe previous v1.4.2 _snapCellWidth() set letterSpacing AFTER fit(), which\nchanged cell width without recalculating column count — widening every\ncell while xterm still laid out the old narrower columns, making seams\nworse. Now the sequence is: fit → measure → snap letterSpacing →\nrefit if spacing changed, so columns are recalculated with the snapped\n(integer device-pixel) cell width. Two fits is intentional: first\nmeasures, second uses the corrected spacing.",
          "is_bot": false,
          "headline": "v1.4.3: fix subpixel seam snapping — refit after letterSpacing adjust…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-06-11T13:08:57Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "32cac3eb31af0e1e71d468848c80165e6a480bed",
          "body": "- Add 150ms delayed refit after every tab switch as safety net for\n  layout transitions that haven't settled by the 2nd animation frame\n- Lower _fitTerminal dimension guard from 10px to 2px threshold\n- Set customGlyphs: true and letterSpacing: 0 in terminal options\n- Add _snapCellWidth() to nudge le\n[…]\nional-DPR displays\n- Expand DPR change listener to also refit, re-snap, and rebuild\n  atlas (not just clear it) on zoom/monitor move\n- Call _snapCellWidth after every _fitTerminal and after font-ready",
          "is_bot": false,
          "headline": "v1.4.2: fix small terminal on tab switch, eliminate subpixel seams",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-06-11T12:55:47Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "e1ea1b6adb9124a167bf130be8bcdd5706f6e071",
          "body": "- Lower _fitTerminal dimension guard threshold from 10px to 2px to\n  avoid rejecting containers that are mid-layout-transition\n- Add retry loop in switchTab when _fitTerminal fails (up to 4\n  retries with 50-300ms delays) so tab switching always converges\n- Only emit ssh-resize after successful fit in take-control and\n  become-controller handlers; skip send when fit returns false to\n  avoid sending stale dimensions to the remote PTY\n- Same fix in ssh-connected initial resize",
          "is_bot": false,
          "headline": "v1.4.1: fix terminal small square on tab switch, fix take-control resize",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-06-11T12:38:29Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f65d9083075a806615e507de054c4dd9215ddbf4",
          "body": "- Resolve user's home directory on SFTP connect via realpath('.')\n- Auto-fallback from '/' to home dir when listing root fails with\n  permission denied (common with chrooted SFTP servers)\n- Send homeDir in sftp-error responses so the client can navigate home\n- Client auto-redirects to home directory\n[…]\n(Bug 3: dim/black panes)\n- Clear WebGL atlas on tab switch, visibility change, and font swap\n- Use requestAnimationFrame for post-layout refits\n- Font-aware initial PTY resize in ssh-connected handler",
          "is_bot": false,
          "headline": "v1.4.0: fix SFTP permission denied on root directories",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-06-11T11:59:37Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "540856225e6a3d3949dcb1bd2dbb40aa13c99be2",
          "body": "- Bug 1 (narrow column wrapping): await font loading before initial\n  fit() and PTY resize so glyph width is measured from the actual\n  monospace font, not the fallback; guard _fitTerminal() against\n  zero/near-zero container dimensions\n- Bug 2 (bottom-row garbage): call terminal.refresh() after eve\n[…]\nbGL on\n  tab activation; always clear atlas when a tab becomes visible\n- Also fix interspersed black lines in TUI apps (same root cause as\n  Bug 1: cell height computed from fallback font before swap)",
          "is_bot": false,
          "headline": "v1.3.9: fix terminal rendering glitches that heal on resize",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-06-11T11:42:27Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "e038e74b23c07bca7b965a251def6a4846ade45c",
          "body": "The mobile terminal's hidden textarea was cleared after every input event,\npreventing Gboard autocorrect from operating on the existing text. When\nGboard replaced a misspelled word, the deletion was silently dropped and\nthe corrected text was appended on top, causing duplication.\n\nReplace the e.data\n[…]\nearing textarea after every character to let Gboard operate on it\n- Clear textarea on Enter/Tab (line submission) and refocus instead\n- Let browser handle Backspace natively; diff detects the deletion",
          "is_bot": false,
          "headline": "v1.3.8: fix Gboard autocorrect text duplication in mobile terminal",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-06-08T23:39:59Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "6bb0d00507d304490de68ed867a339879429c839",
          "body": null,
          "is_bot": false,
          "headline": "v1.3.7: fix tab flash animation showing underline on inactive tabs",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-06-04T11:52:12Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "86acb49cc4ec4f6c2885021fdd0bd99624fcbb20",
          "body": null,
          "is_bot": false,
          "headline": "v1.3.6: fix WebGL context loss recovery on tab backgrounding",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-06-02T14:06:40Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "9934b2b3c4aa88477a799044974082dc9545b15c",
          "body": "- Await document.fonts.ready before first write, then clear atlas + refresh\n  so glyphs are rasterised with the correct font instead of fallback\n- Add _resetWebGLAtlas() helper; clear texture atlas on resize, font-size\n  change, theme change, tab-switch font restore, and DPR change\n- Add _setupDPRLi\n[…]\nars highlighting\n- Refresh after Ctrl+C / Ctrl+Shift+C copy operations\n- Improve onContextLoss: null out webglAddon ref and refresh terminal to\n  repaint with fallback renderer\n- Bump version to 1.3.5",
          "is_bot": false,
          "headline": "v1.3.5: fix WebGL renderer blank cell artefacts",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-06-01T10:39:36Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "00828ba32bda56809ba7c787b07376aa683e3bdb",
          "body": null,
          "is_bot": false,
          "headline": "bump version to 1.3.4",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-28T14:59:29Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "8d2f15cf81ce3a5c4e6655856072dce3147a9798",
          "body": null,
          "is_bot": false,
          "headline": "bump version to 1.3.3",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-28T02:04:47Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "114988f8943c20eefa84b53481b19db33c9ded6e",
          "body": "…r tab distribution\n\n- Add getClockwisePanels() for correct tab distribution across panels\n  (top-left → top-right → bottom-right → bottom-left for 2x2)\n- Fix open-tabs handler: set layout BEFORE syncing tabs so panels\n  exist before distributeTabsToPanels runs\n- Fix saveTabs emitting to server duri\n[…]\nropdown to show ALL tabs with panel badge\n  indicators for tabs in other panels\n- Add .tab-panel-badge CSS for panel indicators in dropdown\n- Server: don't broadcast tabs-sync when tabs array is empty",
          "is_bot": false,
          "headline": "Fix multi-panel tab sync, mobile panel preservation, and cross-browse…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-28T02:01:18Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "1e2530fe296672f50539c86f42f9f449ae0f0969",
          "body": "…terminal init on failure\n\n- During syncTabsFromServer, switchTab is suppressed to prevent the\n  last-created tab becoming active. Previously it returned immediately,\n  which meant terminal wrappers never got the 'active' CSS class,\n  causing initTerminal to fail with 'container not visible'. Now\n  \n[…]\ninstead of showing a toast.\n  The container may not be visible during sync because the correct\n  tab hasn't been activated yet — it becomes visible after the\n  final switchTab call at the end of sync.",
          "is_bot": false,
          "headline": "fix: suppress switchTab during sync but keep wrappers visible, retry …",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-28T01:06:50Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "b38072f1a4a344d341fec8e3adf83c455a17fa9d",
          "body": "…cked active state\n\nWhen the server restarts, activeTabsByPanel is empty (in-memory). The first\nclient connecting would get empty active tab info, causing the last-created\ntab to be selected instead of the first tab. Now if no active tab is tracked\nfor a panel, the first tab in that panel defaults to active.",
          "is_bot": false,
          "headline": "fix: default active tab to first tab per panel when server has no tra…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-28T00:28:51Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "b7b602076717bdde2f1ce155b66b030004d4b5eb",
          "body": "… truth\n\n1. Server now tracks active tab per panel (activeTabsByPanel) and\n   includes it in open-tabs event. New clients see which tab was last\n   active instead of defaulting to the last-created tab.\n\n2. syncTabsFromServer suppresses switchTab during tab creation, then\n   activates the correct tab\n[…]\nr the correct tab is activated.\n\n5. tab-manager: removeTab() cleans up activeTabsByPanel references.\n   setActiveTab/getActiveTab/getActiveTabsByPanel added to track\n   and query active tab per panel.",
          "is_bot": false,
          "headline": "fix: active tab sync, mobile dropdown, and server as single source of…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-28T00:21:28Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "12a5f4fa403ee0b273de75e260261268d58ec8d4",
          "body": "3 fixes for tabs disappearing or duplicating:\n\n1. When server has 0 active sessions (restart), restoreTabs() now always\n   creates NEW connections instead of trying to join dead sessions. This\n   eliminates the 'Session expired' message — credentials from localStorage\n   are used to reconnect automa\n[…]\nprocessed after init.\n\n3. Prevent duplicate tabs by fixing the timing — deferred open-tabs\n   ensures syncTabsFromServer runs only after the layout system and\n   event listeners are fully initialized.",
          "is_bot": false,
          "headline": "fix: session restoration after server restart and prevent duplicate tabs",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-27T23:55:47Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "d621a9f594b6443ac134950bbee933803b63a529",
          "body": "1. Server: SSH/SFTP session death no longer leaves zombie tab entries.\n   Previously, when an SSH connection died on the server (remote shell\n   exited, network drop, etc.) while no browser clients were connected,\n   the openTabs entry was never cleaned up. On reconnect, the client\n   would try ssh-\n[…]\nmissed.\n\n3. (Previous commit) Credentials from localStorage are merged into\n   server-synced tab data so auto-reconnection works even when the\n   server stripped passwords/private keys from open-tabs.",
          "is_bot": false,
          "headline": "fix: sessions disappearing on page refresh — 3 root causes",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-27T23:18:27Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "9185718f9a5a2bbc5713bca281fe2d6e19716e33",
          "body": "…sion recovery\n\nWhen the server sends open-tabs on reconnect/refresh, it strips\npassword/privateKey from connectionData for security. This meant that\nif ssh-join failed (e.g. server restarted), the auto-reconnect path\ncouldn't re-establish the SSH connection because it had no credentials.\n\nNow syncT\n[…]\nalStorage (which\nsaves them locally with the full connectionData) into the server's\nstripped version, so auto-reconnection works correctly after a page\nrefresh even if the server has lost the session.",
          "is_bot": false,
          "headline": "fix: merge credentials from localStorage into server tab sync for ses…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-27T17:51:08Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "c15543664c29dd2dafb6773d69a74d9d8790a1b1",
          "body": "…twork changes\n\n- Changed reconnectionAttempts from 5 to Infinity so the client never\n  stops trying to reconnect (backoff caps at 30s)\n- Added visibilitychange handler to trigger immediate reconnect when\n  the user returns to the tab\n- Improved disconnect/reconnect toast messages\n- Suppressed noisy\n[…]\ning the\nclient. With sticky sessions the SSH connection stays alive on the\nserver, but the client had no way back. Now it keeps trying and\nautomatically restores the session when connectivity returns.",
          "is_bot": false,
          "headline": "fix: make Socket.IO reconnect indefinitely so SSH sessions survive ne…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-27T17:24:33Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "7e91810c8a1fdac9c1b5da2495ded01896388fee",
          "body": "…ter bug",
          "is_bot": false,
          "headline": "fix: remove selectionForeground to fix WebGL glyph cache block charac…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-27T17:14:32Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "b3151909e2be3736b15dd9d407bbc1c1a19c608e",
          "body": "Remove redundant localhost-only check on /api/update endpoint.\nThe global auth middleware already protects all endpoints when a\npassword is set. Without a password, the admin has chosen open access,\nso blocking only updates was inconsistent with other endpoints.",
          "is_bot": false,
          "headline": "v1.3.2: fix remote update blocked without password",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-27T14:14:33Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "670855733e62a01c57cb4c09a843324ef05f9b97",
          "body": "- Bump default scrollback from 2000 to 10000 lines\n- Add scrollback as configurable setting in config.json (0-100000)\n- Add scrollback input to settings dialog (Terminal category)\n- Wire scrollback through server config, WS handler, and client terminal\n- Increase screen sync size limit from 256KB to\n[…]\nB for larger buffers\n- Redesign settings modal: desktop sidebar nav + 7 categories\n- Mobile: dropdown category selector, responsive layout\n- Fixed-height desktop settings modal with scrollable content",
          "is_bot": false,
          "headline": "v1.3.1 - configurable scrollback, categorized settings",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-27T11:33:51Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "f51851720e65396c8a5bc9e11f2d9042174fbfc6",
          "body": null,
          "is_bot": false,
          "headline": "v1.3.0 - fix tab duplication, spurious auth errors, update layout",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-27T10:53:25Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "0a17f473eea6c851bc30fc49b79def46b3ab584b",
          "body": "- Add sftp-join handler so SFTP sessions use join instead of new connection\n- createSFTPTab uses sftp-join when restoreSessionId is provided\n- Add sftp-joined client event handler\n- Suppress 'All configured authentication methods failed' toast when\n  reconnecting with stripped credentials (server omits password/privateKey)\n- Show user-friendly message when session expires without stored credentials\n- SSH error handler: only reconnect if credentials are available",
          "is_bot": false,
          "headline": "fix: prevent spurious auth error toast on second browser tab (v1.2.2)",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-27T10:43:32Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "432673bf90544645fdc6946e3814c82816c1e8b4",
          "body": "- Server is now the single source of truth for tabs on initial sync\n- Remove stale localStorage tabs not present on server\n- Skip localStorage restore when server tabs arrive first\n- Add 3s fallback timeout for unreachable servers\n- Break restore loop if server sync arrives mid-restore\n- Add removeTabLocally() for clean local-only tab cleanup",
          "is_bot": false,
          "headline": "fix: prevent cross-device tab duplication in sticky mode (v1.2.1)",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-27T10:32:48Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "2b15eb391ec30c8f026ba57ef0f4b02fa152a230",
          "body": "Root cause: multi-byte UTF-8 characters and ANSI escape sequences were\nbeing corrupted at chunk boundaries throughout the data pipeline.\n\nChanges:\n- Add @xterm/addon-unicode11 to client and server headless terminal.\n  Without Unicode 11 width rules, xterm.js used Unicode 6 widths,\n  causing cursor/e\n[…]\nunks to split at escape-safe\n  boundaries (before ESC bytes, not mid-sequence) instead of\n  arbitrary 32KB offsets.\n- Add Unicode11 addon script and loading in initTerminal().\n- Bump version to 1.2.0.",
          "is_bot": false,
          "headline": "fix: prevent xterm.js character dropping in TUI apps (OpenCode, etc.)",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-25T15:02:36Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "1481ef5acc0839537eb032331de65cc144d37941",
          "body": "- Always pass passphrase to puttygen via temp file (--old-passphrase <file>)\n  instead of inline argument, fixes 'cannot open mopycsa1988' error on all\n  puttygen versions that only accept file arguments\n- Prevent interactive puttygen prompts (which hang Node) by always providing\n  a passphrase file\n[…]\ntygen dependency for v2 encrypted keys\n- Fix double passphrase prompt - only re-prompt if user hasn't entered one\n- Better error messages: wrong passphrase, v3 format unsupported, decryption\n  failure",
          "is_bot": false,
          "headline": "v1.1.9: Fix PPK key conversion - passphrase file and v3 detection",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-25T10:50:08Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "af71930543a3ea7712704d2518e4e7a7489fdf95",
          "body": "…llback\n\n- Remove --old-passphrase-file fallback (only in puttygen 0.81+,\n  causes confusing errors on older versions that hide the real error)\n- Use --old-passphrase=VALUE as primary format (standard GNU long opt)\n- Fallback to space-separated --old-passphrase VALUE only if equals\n  format returns \n[…]\npt for passphrase on any passphrase-related error, not just\n  when passphrase field is empty\n- Propagate key conversion errors to client instead of silently\n  continuing with unconvertible key content",
          "is_bot": false,
          "headline": "v1.1.8: Fix PPK passphrase handling - remove --old-passphrase-file fa…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-25T10:06:23Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "969b944082402a72b7d7d158796e8c7251c836e6",
          "body": "- Fix puttygen --old-passphrase argument parsing (use equals sign format\n  to prevent passphrase being misinterpreted as filename)\n- Add --old-passphrase-file fallback for older puttygen versions\n- Prompt for passphrase when uploading encrypted key without one\n- Propagate key conversion errors to client instead of silently failing\n- Add onCancel callback to password modal for cancel handling",
          "is_bot": false,
          "headline": "v1.1.7: Fix encrypted PPK key passphrase handling",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-25T09:54:58Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "41884f36885d344fcf029c78dfbcbcd85a2f60d2",
          "body": "- Auto-convert PPK keys to OpenSSH at connection time (ssh-manager, sftp-manager)\n- Support encrypted PPK files with passphrase via puttygen --old-passphrase\n- Add convertKeyIfNeeded() utility for server-side PPK conversion\n- Pass passphrase through frontend key upload and REST convert-key endpoint\n- Auto-convert PPK keys when connecting from bookmarks (connectFromBookmark, openSFTPFromBookmark)\n- Fix case-insensitive PPK detection in offline fallback (PuTTY-User-Key-File regex)",
          "is_bot": false,
          "headline": "feat: add PPK key support for SSH connections, bump to 1.1.6",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-25T09:28:41Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "b23e1655a4e2783b6110c47d34000bd3fb52bb81",
          "body": "…obile input composition handling\n\n- Remove terminal.onBinary handler that was causing double-sends of\n  input on desktop (phantom characters in opencode input)\n- Add max toast limit (5) and deduplication to showToast()\n- Suppress socket connect/disconnect toasts during app update\n- Add max-height/overflow to toast container CSS\n- Fix mobile composition handling: check e.isComposing, e.inputType,\n  and deduplicate input vs compositionend (Gboard fix)\n- Bump version to 1.1.5",
          "is_bot": false,
          "headline": "fix: remove duplicate onBinary handler, fix toast stacking, improve m…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-25T08:22:30Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "76065aae2144688f7375517298006bb859c3344d",
          "body": null,
          "is_bot": false,
          "headline": "bump version to 1.1.4",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-24T11:23:53Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ff4cafeed3b4dfeb53351433f6cbf05bf3b79dcf",
          "body": "…, use readAsDataURL for fast base64 encoding",
          "is_bot": false,
          "headline": "fix: multi-file upload - append input to DOM for reliable file picker…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-24T10:59:52Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "d44432ba9cafb8f85119139377809a97b7e4aad8",
          "body": "…ploads, and cancel button\n\n- Fix large file SFTP crash by switching from single-message base64 to\n  chunked transfer protocol (1MB chunks with backpressure)\n- Fix Node.js 24+ crash (util.isDate removed) with polyfill in server entry\n- Add upload/download progress bar with percentage, size, and file\n[…]\nloop, destroys\n  server-side stream, clears download state)\n- Increase Socket.IO maxHttpBufferSize to 5MB for chunk payloads\n- Stream downloads in 1MB chunks instead of buffering entire file in memory",
          "is_bot": false,
          "headline": "feat: chunked SFTP transfers, upload progress, multi-file/drag-drop u…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-24T10:59:07Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "e4a1c8c5ace2ce5b9c67d0d7d33852fd19723379",
          "body": "… PowerShell (Admin)",
          "is_bot": false,
          "headline": "docs: add getsshift.dev badge, simplify Windows one-liner, relabel as…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-24T10:01:41Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "9c302b9cafdcf76a5bc35fa51d57b4cd39f1eb3f",
          "body": null,
          "is_bot": false,
          "headline": "bump version to 1.1.3",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-21T14:10:35Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c7a302e67c089c5d978f3750068880701bc19a82",
          "body": null,
          "is_bot": false,
          "headline": "bump version to 1.1.2",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-21T11:36:51Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "6625fbe5d9dc2ee9ec305cb8bf4c791629d615ed",
          "body": "…t (fixes arm64 crash)",
          "is_bot": false,
          "headline": "dockerfile: use npm install --package-lock-only instead of pnpm impor…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-21T11:31:45Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "d8f018e65f9b7cf52c5708217a04f6be65720db6",
          "body": "…pm ci",
          "is_bot": false,
          "headline": "dockerfile: regenerate package-lock.json from pnpm-lock.yaml before n…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-21T11:24:48Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "5957563b1514346618fb3ecac2fcf22ba35c84ca",
          "body": null,
          "is_bot": false,
          "headline": "regenerate package-lock.json to sync with package.json dependencies",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-21T11:19:43Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "027e2034b622c8ff6f132c9c19e264c6a2d17e8e",
          "body": "…n, add pnpm-lock.yaml to builder stage, bump label version",
          "is_bot": false,
          "headline": "fix dockerfile: use --omit=dev instead of deprecated --only=productio…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-21T11:14:21Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "9c19a1414daeefffa8a97b3a03e9478e2a46bb08",
          "body": null,
          "is_bot": false,
          "headline": "v1.1.1: fix version and npm package name in publish workflow",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-21T11:08:33Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "bd0806252bf3eabdd240cb55feb027953a294d2d",
          "body": null,
          "is_bot": false,
          "headline": "add pnpm-lock.yaml and trust policy exclusion for semver@6.3.1",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-21T11:01:10Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "cc2c1ff1c25972d78358a95da8ea7b3a2016f326",
          "body": null,
          "is_bot": false,
          "headline": "ci: remove test step from CI workflow",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-21T10:53:10Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "dd7943c8895acb3e292f04637f71e9de7aba47ac",
          "body": "- Remove pnpm version override in GHA (use packageManager from package.json)\n- Fix CI job name capitalization\n- Fix Socket.dev badge URL to use badge.socket.dev",
          "is_bot": false,
          "headline": "v1.1.2: fix CI workflow and Socket badge",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-21T10:51:09Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "8eb4b42f5b6610c880c2adab9d7cb212410b2bbc",
          "body": "…ate endpoint\n\n- Add pnpm as primary package manager with 7-day minimum release age\n  gate and no-downgrade trust policy (pnpm-workspace.yaml, .npmrc)\n- Add Socket Firewall (sfw) integration for install-time malware scanning\n  (ci.yml, npm-publish.yml, package.json install:safe script)\n- Add gitleak\n[…]\ncy Bypass,\n  add network auth gate for non-localhost requests (system.js)\n- Exclude @lethevimlet/sshift from release age and trust policy gates\n- Add Socket.dev badge to README\n- Bump version to 1.1.1",
          "is_bot": false,
          "headline": "v1.1.1: add supply chain security (pnpm + gitleaks + sfw), harden upd…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-21T10:43:54Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "0f460dda7bef05e7853a1c02a6df6266214c8e8a",
          "body": null,
          "is_bot": false,
          "headline": "Add CLI reference docs with --dev flag documentation",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T21:46:16Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "5ba3f585ea9da0cf8d6be6bda115d8a818b25cd4",
          "body": "…ity config",
          "is_bot": false,
          "headline": "Harden npm supply chain: pin dependency versions and add .npmrc secur…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T21:39:50Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "35a6a67d1d8932dfcfc27ced52adb5d0fcced88b",
          "body": null,
          "is_bot": false,
          "headline": "Bump version to 1.1.0; separate dev PID file for side-by-side instances",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T20:56:39Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "5b01f2439922bd0adcd9750080eac1d07775f74f",
          "body": "…heduler)\n\n- Rename isSystemdManaged/systemdUnit to isServiceManaged/serviceManager/serviceUnit\n- Add launchd detection (macOS) and Task Scheduler detection (Windows)\n- Update shell script to use SERVICE_MANAGED/SERVICE_MANAGER/SERVICE_UNIT vars\n- Add launchctl stop/start restart flow for macOS in shell script\n- Fix orphaned catch block and backtick/quote syntax errors",
          "is_bot": false,
          "headline": "Add cross-platform service manager detection (systemd/launchd/Task Sc…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T20:40:24Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "96ef9b4503d44b9f45baca061344e403e64b116d",
          "body": null,
          "is_bot": false,
          "headline": "Bumped version",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T20:17:07Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "098a0d08da7593f1535c0723e2e3202c10b0e238",
          "body": null,
          "is_bot": false,
          "headline": "Bump version to 1.0.20",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T20:02:35Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a32ca79f12ccdb2647e3c95ff462283c266b963e",
          "body": null,
          "is_bot": false,
          "headline": "Fix ReferenceError: escape ${NPM_DELAY} in shell template literal",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T19:59:34Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "9e49b894900d7672158dec374b560f2227500ea2",
          "body": null,
          "is_bot": false,
          "headline": "Bumped version",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T19:40:00Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ff363cd8ed6af54259d1a6421429584f6e0ecdea",
          "body": null,
          "is_bot": false,
          "headline": "Bump version to 1.0.17",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T19:34:41Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c0153317a04a2c359832c46138162594f86814a0",
          "body": "…ttempts with 30s delay)\n\n- Use systemctl is-active as fallback when cgroup detection fails\n- Retry npm install up to 3 times with 30s delay for registry propagation\n- Same retry logic for Windows PowerShell script",
          "is_bot": false,
          "headline": "Fix systemd detection (systemctl is-active fallback) + npm retry (3 a…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T19:18:48Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f49e6fef80e6c6915ef1ddb069b16685937723f0",
          "body": null,
          "is_bot": false,
          "headline": "Testing GUI update",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T19:07:37Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "755bf8eb26e1796f0bcdc8e84b3df2aa8c5b9537",
          "body": null,
          "is_bot": false,
          "headline": "Bump version to 1.0.15",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T18:27:35Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "853e7ede1724ce4fa2dcd9d258a52bedcc2c8a76",
          "body": "…erpolation",
          "is_bot": false,
          "headline": "Fix ReferenceError: move systemd detection before script template int…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T18:21:06Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "78b110273b6aa0b6922bb1d37cad26afee83c98f",
          "body": "Under systemd, the update script was being killed when systemctl\nstop/restart was called because it was in the same cgroup as the\nsshift service. Now uses systemd-run --scope to move the update\nscript to its own transient scope so it survives service restarts.\nAlso uses systemctl restart (not stop+start) for systemd path.",
          "is_bot": false,
          "headline": "Fix systemd update: spawn script in separate scope via systemd-run",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T18:13:56Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "60272cadeeb368e8c5e2d0503cbe803bd330fd07",
          "body": null,
          "is_bot": false,
          "headline": "Bump version to 1.0.12",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T18:07:59Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "59dd6711b7e055c2d3f1a506051fc5e7833cb5ee",
          "body": "…d of re-detecting\n\nThe shell script was trying to detect systemd independently but\nrunning in a different cgroup/namespace than the sshift process.\nNow passes isSystemdManaged and systemdUnit directly from the\nNode.js server (which correctly detects it via /proc/PID/cgroup)\nto the shell script as environment variables.",
          "is_bot": false,
          "headline": "Fix systemd detection in shell script - pass flag from Node.js instea…",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T18:05:18Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "654462b5754f1b3be4325d84d68cb647d8b4959e",
          "body": null,
          "is_bot": false,
          "headline": "Bump version to 1.0.10",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T17:59:36Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ca6cf4e481c9e249010281e1ecdedd6b0715173c",
          "body": "- Detect systemd service and use systemctl stop/start instead of\n  process.exit(0) + exec, which caused a race condition where systemd\n  would restart the old version before npm install completed\n- When systemd-managed, keep the server running until the update\n  script stops it via systemctl, preventing dual-instances\n- Add Task Scheduler detection for Windows (PowerShell script)\n- Delay process exit to 500ms under systemd (script handles stop)",
          "is_bot": false,
          "headline": "Fix GUI update for systemd-managed installations",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T17:52:11Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "2e18e0a70d215a9ddd37c12075ee973b22179ba7",
          "body": null,
          "is_bot": false,
          "headline": "Bumped version to 1.0.8",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T15:42:57Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ac315afaae00c902a83ccf86e641bf1091f8e2fb",
          "body": null,
          "is_bot": false,
          "headline": "Bumped version",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T15:23:55Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "be1546b34aa871ab491da2b052f59be57fb421c5",
          "body": null,
          "is_bot": false,
          "headline": "Bumped version",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T15:16:49Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "bed0a61508d40d8b8b193908484555aec49c740e",
          "body": null,
          "is_bot": false,
          "headline": "Bumped version",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T13:52:04Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "efce7f345656db8a6e31deec48b5ce48a2ed966b",
          "body": null,
          "is_bot": false,
          "headline": "Updated sshift bin pid logic",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T13:48:11Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c70b6179b168dceda093eef8e13f6a29793aa505",
          "body": null,
          "is_bot": false,
          "headline": "Fixed manage sessions",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T12:06:50Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "4f718818e67c170625c88787f0c23da1806aec3c",
          "body": null,
          "is_bot": false,
          "headline": "Added a better cache busting strategy",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-20T11:55:28Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "fe8a0679b24b4683d2202885d5360dcc4fd4ab3d",
          "body": null,
          "is_bot": false,
          "headline": "Bumped version",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-19T14:56:48Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "e563c5a2a473ff678e59554318defac087c09ff9",
          "body": null,
          "is_bot": false,
          "headline": "Fixed ctrl+v double text issue",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-19T14:38:51Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "10afea8e2bbae39081e6dbafbb58eeb31e07f6b9",
          "body": null,
          "is_bot": false,
          "headline": "Added debug button in settings",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-19T14:22:37Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ab31cd190d8733fb74b408974272fd915ddcb5c4",
          "body": null,
          "is_bot": false,
          "headline": "Made mobile term text size smaller and fixed copy context menu position",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-19T12:36:37Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "32c98f0528bb2b7f36f913e2ab2616e998b0839c",
          "body": null,
          "is_bot": false,
          "headline": "Added support for sc52 clipboard",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-19T12:24:58Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "79e0ce7ef838043defb4ac0cd89839d84aa8c2a9",
          "body": null,
          "is_bot": false,
          "headline": "Updated security dialogue and docs",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-19T11:37:53Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "e8276157205142ae38b09892fbc3ebe06d9d712f",
          "body": null,
          "is_bot": false,
          "headline": "Fixed an issue with mobile input",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-18T21:02:05Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "3e3dd8c09fd919aea3415a968eee22666cf25d36",
          "body": null,
          "is_bot": false,
          "headline": "Fixed mobile select scroll issue",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-18T16:51:15Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c353f598ddfb2a29f3d3f51ff7f111ce9d452821",
          "body": null,
          "is_bot": false,
          "headline": "Bumped version",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-18T11:45:21Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "0475b7fb26b6017df79c30fc595edc492d55f962",
          "body": null,
          "is_bot": false,
          "headline": "Made mobile clipboard menu bigger",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-18T11:41:54Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f1809f10e1217d0aacaac3c37e93ee0cb44d4672",
          "body": null,
          "is_bot": false,
          "headline": "Fixed clipboard refactor issues",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-18T11:36:11Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "361027745c085c1f27fa4e83de7850a3e7a7260a",
          "body": null,
          "is_bot": false,
          "headline": "Fixed paste issues",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-18T11:21:58Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c87f692ccd2906dd8ff376db80bf6a8cdc7973e9",
          "body": null,
          "is_bot": false,
          "headline": "Fixed issue with update button",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-14T15:18:41Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "18afe14a9de98d6647bffa895be77049a10bf06c",
          "body": null,
          "is_bot": false,
          "headline": "Bumped version",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-13T15:29:13Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "85356bae33aaa3d5c6727340afbc1c4d3b8b3f9f",
          "body": null,
          "is_bot": false,
          "headline": "Fixed gitaction cleanup",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-13T15:24:13Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c13140a9543956f02c5c1e852f8fd02c3d5addcd",
          "body": null,
          "is_bot": false,
          "headline": "Bumped version",
          "author_name": "lethevimlet",
          "author_login": "lethevimlet",
          "committed_at": "2026-05-13T15:03:51Z",
          "body_truncated": false,
          "is_coding_agent": false
        }
      ],
      "releases_count": 0,
      "commits_last_year": 244,
      "latest_release_at": null,
      "latest_release_tag": null,
      "releases_from_tags": false,
      "days_since_last_push": 9,
      "active_weeks_last_year": 12,
      "days_since_latest_release": null,
      "mean_days_between_releases": null
    },
    "community": {
      "has_readme": true,
      "has_license": true,
      "has_description": true,
      "has_contributing": true,
      "health_percentage": 57,
      "has_issue_template": false,
      "has_code_of_conduct": false,
      "has_pull_request_template": false
    },
    "ecosystem": {
      "packages": [
        {
          "name": "@lethevimlet/sshift",
          "exists": true,
          "license": "MIT",
          "keywords": [
            "ssh",
            "sftp",
            "terminal",
            "web",
            "xterm",
            "web-terminal",
            "ssh-client",
            "sftp-client",
            "browser-terminal"
          ],
          "ecosystem": "npm",
          "matches_repo": true,
          "registry_url": "https://www.npmjs.com/package/@lethevimlet/sshift",
          "is_deprecated": false,
          "latest_version": "1.6.1",
          "repository_url": "https://github.com/lethevimlet/sshift",
          "versions_count": 77,
          "total_downloads": null,
          "dependents_count": null,
          "deprecation_note": null,
          "maintainers_count": 1,
          "monthly_downloads": 2001,
          "first_published_at": "2026-04-08T21:01:07.835000Z",
          "latest_published_at": "2026-07-22T16:15:55.578000Z",
          "latest_version_yanked": null,
          "days_since_latest_publish": 9
        }
      ]
    },
    "popularity": {
      "forks": 3,
      "stars": 35,
      "watchers": 0,
      "fork_history": {
        "days": [
          {
            "date": "2026-04-05",
            "count": 1
          },
          {
            "date": "2026-04-27",
            "count": 1
          },
          {
            "date": "2026-05-05",
            "count": 1
          }
        ],
        "complete": true,
        "collected": 3,
        "total_forks": 3
      },
      "star_history": null,
      "open_issues_and_prs": 0
    },
    "ai_readiness": {
      "has_nix": false,
      "example_dirs": [],
      "has_llms_txt": false,
      "has_dockerfile": true,
      "has_mcp_signal": false,
      "bootstrap_files": [],
      "api_schema_files": [],
      "has_devcontainer": false,
      "typecheck_configs": [],
      "toolchain_manifests": [],
      "largest_source_bytes": 497398,
      "source_files_sampled": 77,
      "oversized_source_files": 7,
      "agent_instruction_files": [
        "AGENTS.md"
      ],
      "agent_instruction_max_bytes": 9064
    },
    "dependencies": {
      "manifests": [
        "package.json"
      ],
      "advisories": {
        "error": null,
        "scope": "published_package",
        "source": "osv",
        "findings": [
          {
            "name": "uuid",
            "direct": true,
            "version": "9.0.0",
            "severity": "high",
            "ecosystem": "npm",
            "cvss_score": 7.5,
            "advisory_ids": [
              "GHSA-w5hq-g745-h8pq"
            ],
            "fixed_version": "13.0.1",
            "advisory_count": 1,
            "oldest_advisory_days": 100
          },
          {
            "name": "body-parser",
            "direct": false,
            "version": "1.20.1",
            "severity": "high",
            "ecosystem": "npm",
            "cvss_score": 7.5,
            "advisory_ids": [
              "GHSA-qwcr-r2fm-qrc7",
              "GHSA-v422-hmwv-36x6"
            ],
            "fixed_version": "2.3.0",
            "advisory_count": 2,
            "oldest_advisory_days": 689
          },
          {
            "name": "engine.io",
            "direct": false,
            "version": "6.5.5",
            "severity": "high",
            "ecosystem": "npm",
            "cvss_score": 7.5,
            "advisory_ids": [
              "GHSA-r635-g3xr-vw7x"
            ],
            "fixed_version": "6.6.7",
            "advisory_count": 1,
            "oldest_advisory_days": 11
          },
          {
            "name": "path-to-regexp",
            "direct": false,
            "version": "0.1.7",
            "severity": "high",
            "ecosystem": "npm",
            "cvss_score": 7.5,
            "advisory_ids": [
              "GHSA-37ch-88jc-xwx2",
              "GHSA-9wv6-86v2-598j",
              "GHSA-rhx6-c78j-4q9w"
            ],
            "fixed_version": "8.0.0",
            "advisory_count": 3,
            "oldest_advisory_days": 690
          },
          {
            "name": "ws",
            "direct": false,
            "version": "8.17.1",
            "severity": "high",
            "ecosystem": "npm",
            "cvss_score": 7.5,
            "advisory_ids": [
              "GHSA-58qx-3vcg-4xpx",
              "GHSA-96hv-2xvq-fx4p"
            ],
            "fixed_version": "8.21.0",
            "advisory_count": 2,
            "oldest_advisory_days": 74
          },
          {
            "name": "express",
            "direct": true,
            "version": "4.18.2",
            "severity": "moderate",
            "ecosystem": "npm",
            "cvss_score": 6.1,
            "advisory_ids": [
              "GHSA-qw6h-vgh9-j6wx",
              "GHSA-rv95-896h-c2vc"
            ],
            "fixed_version": "5.0.0-beta.3",
            "advisory_count": 2,
            "oldest_advisory_days": 858
          },
          {
            "name": "qs",
            "direct": false,
            "version": "6.11.0",
            "severity": "moderate",
            "ecosystem": "npm",
            "cvss_score": 5.3,
            "advisory_ids": [
              "GHSA-6rw7-vpxm-498p",
              "GHSA-w7fw-mjwx-w883"
            ],
            "fixed_version": "6.14.2",
            "advisory_count": 2,
            "oldest_advisory_days": 213
          },
          {
            "name": "send",
            "direct": false,
            "version": "0.18.0",
            "severity": "moderate",
            "ecosystem": "npm",
            "cvss_score": 5,
            "advisory_ids": [
              "GHSA-m6fv-jmcg-4jfg"
            ],
            "fixed_version": "0.19.0",
            "advisory_count": 1,
            "oldest_advisory_days": 689
          },
          {
            "name": "serve-static",
            "direct": false,
            "version": "1.15.0",
            "severity": "moderate",
            "ecosystem": "npm",
            "cvss_score": 5,
            "advisory_ids": [
              "GHSA-cm22-4g7w-348p"
            ],
            "fixed_version": "2.1.0",
            "advisory_count": 1,
            "oldest_advisory_days": 689
          },
          {
            "name": "cookie",
            "direct": false,
            "version": "0.4.2",
            "severity": "low",
            "ecosystem": "npm",
            "cvss_score": null,
            "advisory_ids": [
              "GHSA-pxg6-pf52-xh8x"
            ],
            "fixed_version": "0.7.0",
            "advisory_count": 1,
            "oldest_advisory_days": 665
          },
          {
            "name": "cookie",
            "direct": false,
            "version": "0.5.0",
            "severity": "low",
            "ecosystem": "npm",
            "cvss_score": null,
            "advisory_ids": [
              "GHSA-pxg6-pf52-xh8x"
            ],
            "fixed_version": "0.7.0",
            "advisory_count": 1,
            "oldest_advisory_days": 665
          }
        ],
        "collected": true,
        "malicious": [],
        "truncated": false,
        "by_severity": {
          "low": 2,
          "high": 5,
          "moderate": 4
        },
        "advisory_count": 17,
        "affected_count": 11,
        "assessed_count": 132,
        "malicious_count": 0,
        "assessed_package": "npm:@lethevimlet/sshift@1.6.1",
        "unassessed_count": 0,
        "direct_affected_count": 2
      },
      "ecosystems": [
        "npm"
      ],
      "dependencies": [
        {
          "name": "@xterm/addon-fit",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "0.11.0"
        },
        {
          "name": "@xterm/addon-image",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "0.9.0"
        },
        {
          "name": "@xterm/addon-search",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "0.16.0"
        },
        {
          "name": "@xterm/addon-serialize",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "0.14.0"
        },
        {
          "name": "@xterm/addon-unicode11",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "0.9.0"
        },
        {
          "name": "@xterm/addon-web-links",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "0.12.0"
        },
        {
          "name": "@xterm/addon-webgl",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "0.19.0"
        },
        {
          "name": "@xterm/headless",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "6.0.0"
        },
        {
          "name": "@xterm/xterm",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "6.0.0"
        },
        {
          "name": "dotenv",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "17.4.0"
        },
        {
          "name": "express",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "4.18.2"
        },
        {
          "name": "httpolyglot",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "0.1.2"
        },
        {
          "name": "lucide",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "1.7.0"
        },
        {
          "name": "selfsigned",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "5.5.0"
        },
        {
          "name": "socket.io",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "4.7.2"
        },
        {
          "name": "ssh2",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "1.15.0"
        },
        {
          "name": "uuid",
          "manifest": "package.json",
          "ecosystem": "npm",
          "version_constraint": "9.0.0"
        }
      ],
      "all_dependencies": {
        "error": "GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository",
        "source": null,
        "packages": [],
        "collected": false,
        "truncated": false,
        "total_count": null,
        "direct_count": null,
        "indirect_count": null
      }
    },
    "maintainership": {
      "issues": {
        "open_prs": 0,
        "merged_prs": 1,
        "open_issues": 0,
        "closed_ratio": null,
        "closed_issues": 0,
        "closed_unmerged_prs": 0
      },
      "bus_factor": 1,
      "bot_contributors": 0,
      "top_contributors": [
        {
          "type": "User",
          "login": "lethevimlet",
          "commits": 244,
          "avatar_url": "https://avatars.githubusercontent.com/u/2963923?v=4"
        }
      ],
      "contributors_sampled": 1,
      "top_contributor_share": 1
    },
    "quality_signals": {
      "has_ci": true,
      "has_tests": true,
      "ci_workflows": [
        "ci.yml",
        "docker-publish.yml",
        "npm-publish.yml"
      ],
      "has_docs_dir": true,
      "linter_configs": [],
      "has_editorconfig": false,
      "has_linter_config": true,
      "has_precommit_config": true
    },
    "security_signals": {
      "lockfiles": [
        "package-lock.json",
        "pnpm-lock.yaml"
      ],
      "scorecard": {
        "checks": [
          {
            "name": "Binary-Artifacts",
            "score": 10,
            "reason": "no binaries found in the repo",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#binary-artifacts"
          },
          {
            "name": "Branch-Protection",
            "score": 0,
            "reason": "branch protection not enabled on development/release branches",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#branch-protection"
          },
          {
            "name": "CI-Tests",
            "score": null,
            "reason": "no pull request found",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#ci-tests"
          },
          {
            "name": "CII-Best-Practices",
            "score": 0,
            "reason": "no effort to earn an OpenSSF best practices badge detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#cii-best-practices"
          },
          {
            "name": "Code-Review",
            "score": 0,
            "reason": "Found 0/30 approved changesets -- score normalized to 0",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#code-review"
          },
          {
            "name": "Contributors",
            "score": 3,
            "reason": "project has 1 contributing companies or organizations -- score normalized to 3",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#contributors"
          },
          {
            "name": "Dangerous-Workflow",
            "score": 10,
            "reason": "no dangerous workflow patterns detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dangerous-workflow"
          },
          {
            "name": "Dependency-Update-Tool",
            "score": 0,
            "reason": "no update tool detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dependency-update-tool"
          },
          {
            "name": "Fuzzing",
            "score": 0,
            "reason": "project is not fuzzed",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#fuzzing"
          },
          {
            "name": "License",
            "score": 10,
            "reason": "license file detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#license"
          },
          {
            "name": "Maintained",
            "score": 10,
            "reason": "30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#maintained"
          },
          {
            "name": "Packaging",
            "score": 10,
            "reason": "packaging workflow detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#packaging"
          },
          {
            "name": "Pinned-Dependencies",
            "score": 0,
            "reason": "dependency not pinned by hash detected -- score normalized to 0",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#pinned-dependencies"
          },
          {
            "name": "SAST",
            "score": 0,
            "reason": "no SAST tool detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#sast"
          },
          {
            "name": "Security-Policy",
            "score": 0,
            "reason": "security policy file not detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#security-policy"
          },
          {
            "name": "Signed-Releases",
            "score": null,
            "reason": "no releases found",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#signed-releases"
          },
          {
            "name": "Token-Permissions",
            "score": 0,
            "reason": "detected GitHub workflow tokens with excessive permissions",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#token-permissions"
          },
          {
            "name": "Vulnerabilities",
            "score": 0,
            "reason": "25 existing vulnerabilities detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#vulnerabilities"
          }
        ],
        "commit": "d551d43a298c091d10e0e373670271f3ba075fa8",
        "ran_at": "2026-08-01T00:33:56Z",
        "aggregate_score": 3.5,
        "scorecard_version": "v5.5.0"
      },
      "has_codeql_workflow": false,
      "has_security_policy": false,
      "has_dependabot_config": false
    },
    "contribution_flow": {
      "collected": true,
      "ci_last_run_at": "2026-07-22T16:17:16Z",
      "oldest_open_prs": [],
      "last_merged_pr_at": "2026-04-07T12:53:21Z",
      "ci_last_conclusion": "FAILURE",
      "oldest_open_issues": []
    }
  },
  "config": {
    "disabled_metrics": [],
    "disabled_categories": [],
    "disabled_components": {}
  },
  "source": {
    "url": "https://github.com/lethevimlet/sshift",
    "host": "github.com",
    "name": "sshift",
    "owner": "lethevimlet"
  },
  "metrics": {
    "overall": {
      "key": "overall",
      "band": "moderate",
      "name": "Overall health",
      "note": "The weighted overall 54 is calibrated to 56 on the published index scale (record calibration 2026-08-02).",
      "notes": [
        {
          "code": "overall_calibration",
          "params": {
            "raw": 54,
            "calibrated": 56,
            "calibration": "2026-08-02"
          }
        }
      ],
      "value": 56,
      "inputs": {
        "security": 35,
        "vitality": 39,
        "community": 49,
        "governance": 52,
        "calibration": "2026-08-02",
        "engineering": 91,
        "ai_readiness": 54,
        "weighted_overall_raw": 54
      },
      "components": []
    },
    "categories": [
      {
        "key": "vitality",
        "band": "weak",
        "name": "Vitality",
        "value": 39,
        "weight": 0.21,
        "metrics": [
          {
            "key": "development_activity",
            "band": "good",
            "name": "Development activity",
            "note": null,
            "notes": [],
            "value": 65,
            "inputs": {
              "commits_last_year": 244,
              "human_commit_share": 1,
              "days_since_last_push": 9,
              "active_weeks_last_year": 12
            },
            "components": [
              {
                "key": "push_recency",
                "name": "Push recency",
                "detail": "last push 9 days ago",
                "points": 28.8,
                "status": "partial",
                "details": [
                  {
                    "code": "push_recency",
                    "params": {
                      "days": 9
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "commit_cadence",
                "name": "Commit cadence",
                "detail": "12/52 weeks with commits",
                "points": 8.3,
                "status": "partial",
                "details": [
                  {
                    "code": "commit_cadence_weeks",
                    "params": {
                      "weeks": 12
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "commit_volume",
                "name": "Commit volume",
                "detail": "244 commits in the last year",
                "points": 18,
                "status": "met",
                "details": [
                  {
                    "code": "commits_last_year",
                    "params": {
                      "count": 244
                    }
                  }
                ],
                "max_points": 18
              },
              {
                "key": "openssf_scorecard_maintained",
                "name": "OpenSSF Scorecard: Maintained",
                "detail": "30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10",
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "release_discipline",
            "band": "critical",
            "name": "Release discipline",
            "note": "Excluded from scoring (no data or not applicable): OpenSSF Scorecard: Signed-Releases. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "openssf_scorecard_signed_releases"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 1,
            "inputs": {
              "releases_count": 0
            },
            "components": [
              {
                "key": "ships_releases",
                "name": "Ships releases",
                "detail": "no releases published",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_releases_published",
                    "params": {}
                  }
                ],
                "max_points": 27
              },
              {
                "key": "release_recency",
                "name": "Release recency",
                "detail": "no releases",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_releases",
                    "params": {}
                  }
                ],
                "max_points": 36
              },
              {
                "key": "release_cadence",
                "name": "Release cadence",
                "detail": "no releases",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_releases",
                    "params": {}
                  }
                ],
                "max_points": 27
              },
              {
                "key": "openssf_scorecard_signed_releases",
                "name": "OpenSSF Scorecard: Signed-Releases",
                "detail": "no releases found",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_data",
                    "params": {}
                  }
                ],
                "max_points": 10
              }
            ]
          },
          {
            "key": "abandonment",
            "band": "exceptional",
            "name": "Abandonment",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "cap": null,
              "state": "unverified",
              "guards": [],
              "signals": [],
              "red_flag": false,
              "multiplier_pct": 100,
              "declared_reason": null,
              "unverified_reason": "repository_too_young",
              "unanswered_open_prs": null,
              "unanswered_open_issues": null,
              "days_since_last_merged_pr": null,
              "days_since_last_human_commit": null,
              "days_since_last_human_commit_is_floor": false
            },
            "components": [
              {
                "key": "project_is_still_maintained",
                "name": "Project is still maintained",
                "detail": "maintenance record not established from the collected data",
                "points": 100,
                "status": "met",
                "details": [
                  {
                    "code": "abandonment_unverified",
                    "params": {}
                  }
                ],
                "max_points": 100
              }
            ]
          }
        ],
        "description": "Is the project alive — is code being written and are releases shipping?"
      },
      {
        "key": "community",
        "band": "weak",
        "name": "Community & Adoption",
        "value": 49,
        "weight": 0.17,
        "metrics": [
          {
            "key": "popularity",
            "band": "at_risk",
            "name": "Popularity & adoption",
            "note": null,
            "notes": [],
            "value": 27,
            "inputs": {
              "forks": 3,
              "stars": 35,
              "watchers": 0,
              "growth_state": "unverified",
              "growth_factor_pct": 100,
              "growth_unverified_reason": "no_history"
            },
            "components": [
              {
                "key": "stars",
                "name": "Stars",
                "detail": "35 stars",
                "points": 24.8,
                "status": "partial",
                "details": [
                  {
                    "code": "stars",
                    "params": {
                      "count": 35
                    }
                  }
                ],
                "max_points": 60
              },
              {
                "key": "forks",
                "name": "Forks",
                "detail": "3 forks",
                "points": 2.5,
                "status": "partial",
                "details": [
                  {
                    "code": "forks",
                    "params": {
                      "count": 3
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "watchers",
                "name": "Watchers",
                "detail": "0 watchers",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "watchers",
                    "params": {
                      "count": 0
                    }
                  }
                ],
                "max_points": 15
              }
            ]
          },
          {
            "key": "community_health",
            "band": "good",
            "name": "Community health",
            "note": null,
            "notes": [],
            "value": 70,
            "inputs": {
              "has_readme": true,
              "has_license": true,
              "readme_badges": null,
              "has_contributing": true,
              "has_issue_template": false,
              "has_code_of_conduct": false,
              "readme_badge_services": [],
              "has_pull_request_template": false
            },
            "components": [
              {
                "key": "readme",
                "name": "README",
                "detail": null,
                "points": 22.5,
                "status": "met",
                "details": [],
                "max_points": 22.5
              },
              {
                "key": "license",
                "name": "License",
                "detail": "recognized license (MIT)",
                "points": 22.5,
                "status": "met",
                "details": [
                  {
                    "code": "license_standard",
                    "params": {}
                  },
                  {
                    "code": "license_spdx",
                    "params": {
                      "spdx": "MIT"
                    }
                  }
                ],
                "max_points": 22.5
              },
              {
                "key": "contributing_guide",
                "name": "CONTRIBUTING guide",
                "detail": null,
                "points": 18,
                "status": "met",
                "details": [],
                "max_points": 18
              },
              {
                "key": "code_of_conduct",
                "name": "Code of conduct",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 13.5
              },
              {
                "key": "issue_template",
                "name": "Issue template",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.2
              },
              {
                "key": "pr_template",
                "name": "PR template",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 6.3
              }
            ]
          },
          {
            "key": "ecosystem_adoption",
            "band": "moderate",
            "name": "Ecosystem adoption (downloads)",
            "note": "Excluded from scoring (no data or not applicable): Registry dependents. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "registry_dependents"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 55,
            "inputs": {
              "packages": [
                "@lethevimlet/sshift"
              ],
              "dependents": null,
              "ecosystems": "npm",
              "total_downloads": null,
              "monthly_downloads": 2001
            },
            "components": [
              {
                "key": "monthly_downloads",
                "name": "Monthly downloads",
                "detail": "2,001 downloads/month across npm",
                "points": 44,
                "status": "partial",
                "details": [
                  {
                    "code": "downloads_monthly",
                    "params": {
                      "count": 2001,
                      "ecosystems": "npm"
                    }
                  }
                ],
                "max_points": 80
              },
              {
                "key": "registry_dependents",
                "name": "Registry dependents",
                "detail": "not reported by this ecosystem",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "not_reported_by_this_ecosystem",
                    "params": {}
                  }
                ],
                "max_points": 20
              }
            ]
          }
        ],
        "description": "Does the project have users, downloads, attention, and a welcoming setup for contributors?"
      },
      {
        "key": "governance",
        "band": "moderate",
        "name": "Sustainability & Governance",
        "value": 52,
        "weight": 0.23,
        "metrics": [
          {
            "key": "maintainer_resilience",
            "band": "critical",
            "name": "Maintainer resilience (bus factor)",
            "note": null,
            "notes": [],
            "value": 13,
            "inputs": {
              "bus_factor": 1,
              "contributors_sampled": 1,
              "top_contributor_share": 1
            },
            "components": [
              {
                "key": "bus_factor",
                "name": "Bus factor",
                "detail": "1 contributor(s) cover half of all commits",
                "points": 9,
                "status": "partial",
                "details": [
                  {
                    "code": "bus_factor",
                    "params": {
                      "count": 1
                    }
                  }
                ],
                "max_points": 54
              },
              {
                "key": "commit_distribution",
                "name": "Commit distribution",
                "detail": "top contributor authored 100% of commits",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "top_contributor_share",
                    "params": {
                      "share": 100
                    }
                  }
                ],
                "max_points": 22.5
              },
              {
                "key": "contributor_breadth",
                "name": "Contributor breadth",
                "detail": "1 contributors",
                "points": 1.4,
                "status": "partial",
                "details": [
                  {
                    "code": "contributors_sampled",
                    "params": {
                      "count": 1
                    }
                  }
                ],
                "max_points": 13.5
              },
              {
                "key": "openssf_scorecard_contributors",
                "name": "OpenSSF Scorecard: Contributors",
                "detail": "project has 1 contributing companies or organizations -- score normalized to 3",
                "points": 3,
                "status": "partial",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "responsiveness",
            "band": "good",
            "name": "Issue & PR responsiveness",
            "note": "Excluded from scoring (no data or not applicable): Issue resolution, Newcomer PR acceptance. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "issue_resolution",
                    "newcomer_pr_acceptance"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 67,
            "inputs": {
              "merged_prs": 1,
              "open_issues": 0,
              "closed_issues": 0,
              "prs_merged_7d": null,
              "prs_decided_7d": null,
              "prs_merged_30d": null,
              "prs_decided_30d": null,
              "issue_closed_ratio": null,
              "closed_unmerged_prs": 0,
              "first_time_authors_30d": null,
              "first_time_prs_merged_30d": null,
              "first_time_prs_decided_30d": null
            },
            "components": [
              {
                "key": "issue_resolution",
                "name": "Issue resolution",
                "detail": "no issues or no data",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_issues_or_data",
                    "params": {}
                  }
                ],
                "max_points": 42
              },
              {
                "key": "pr_acceptance",
                "name": "PR acceptance",
                "detail": "1/1 decided PRs merged",
                "points": 30,
                "status": "met",
                "details": [
                  {
                    "code": "decided_prs_merged",
                    "params": {
                      "merged": 1,
                      "decided": 1
                    }
                  }
                ],
                "max_points": 30
              },
              {
                "key": "newcomer_pr_acceptance",
                "name": "Newcomer PR acceptance",
                "detail": "no first-time contributor's PR decided in 30d",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_newcomer_prs",
                    "params": {
                      "days": 30
                    }
                  }
                ],
                "max_points": 13
              },
              {
                "key": "openssf_scorecard_code_review",
                "name": "OpenSSF Scorecard: Code-Review",
                "detail": "Found 0/30 approved changesets -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 15
              }
            ]
          },
          {
            "key": "stewardship",
            "band": "weak",
            "name": "Ownership & stewardship",
            "note": "Excluded from scoring (no data or not applicable): Verified domain. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "verified_domain"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 47,
            "inputs": {
              "followers": 10,
              "owner_type": "User",
              "is_verified": null,
              "owner_login": "lethevimlet",
              "public_repos": 12,
              "account_age_days": 4987
            },
            "components": [
              {
                "key": "ownership_backing",
                "name": "Ownership backing",
                "detail": "personal (user) account",
                "points": 10,
                "status": "partial",
                "details": [
                  {
                    "code": "owner_personal",
                    "params": {}
                  }
                ],
                "max_points": 30
              },
              {
                "key": "verified_domain",
                "name": "Verified domain",
                "detail": "not applicable to user accounts",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "not_applicable_to_user_accounts",
                    "params": {}
                  }
                ],
                "max_points": 20
              },
              {
                "key": "owner_reach",
                "name": "Owner reach",
                "detail": "10 followers of lethevimlet",
                "points": 7.5,
                "status": "partial",
                "details": [
                  {
                    "code": "owner_followers",
                    "params": {
                      "count": 10,
                      "login": "lethevimlet"
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "track_record",
                "name": "Track record",
                "detail": "12 public repos, account ~13 yr old",
                "points": 20.1,
                "status": "partial",
                "details": [
                  {
                    "code": "public_repos",
                    "params": {
                      "count": 12
                    }
                  },
                  {
                    "code": "account_age_years",
                    "params": {
                      "years": 13
                    }
                  }
                ],
                "max_points": 25
              }
            ]
          },
          {
            "key": "package_maintenance",
            "band": "exceptional",
            "name": "Package maintenance",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "packages": [
                "@lethevimlet/sshift"
              ],
              "ecosystems": "npm",
              "any_deprecated": false,
              "min_days_since_publish": 9
            },
            "components": [
              {
                "key": "published_resolvable",
                "name": "Published & resolvable",
                "detail": "1 package(s) on npm",
                "points": 25,
                "status": "met",
                "details": [
                  {
                    "code": "packages_published",
                    "params": {
                      "count": 1,
                      "ecosystems": "npm"
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "publish_recency",
                "name": "Publish recency",
                "detail": "latest publish 9 days ago",
                "points": 35,
                "status": "met",
                "details": [
                  {
                    "code": "publish_recency",
                    "params": {
                      "days": 9
                    }
                  }
                ],
                "max_points": 35
              },
              {
                "key": "version_history",
                "name": "Version history",
                "detail": "77 published versions",
                "points": 20,
                "status": "met",
                "details": [
                  {
                    "code": "published_versions",
                    "params": {
                      "count": 77
                    }
                  }
                ],
                "max_points": 20
              },
              {
                "key": "not_deprecated",
                "name": "Not deprecated",
                "detail": "active, not deprecated or yanked",
                "points": 20,
                "status": "met",
                "details": [
                  {
                    "code": "package_not_deprecated",
                    "params": {}
                  }
                ],
                "max_points": 20
              }
            ]
          }
        ],
        "description": "Will the project survive its people — bus factor, responsiveness, who backs it, and package upkeep?"
      },
      {
        "key": "engineering",
        "band": "excellent",
        "name": "Engineering Quality",
        "value": 91,
        "weight": 0.19,
        "metrics": [
          {
            "key": "engineering_practices",
            "band": "excellent",
            "name": "Engineering practices",
            "note": "Excluded from scoring (no data or not applicable): OpenSSF Scorecard: CI-Tests. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "openssf_scorecard_ci_tests"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 92,
            "inputs": {
              "has_ci": true,
              "has_tests": true,
              "has_editorconfig": false,
              "has_linter_config": true,
              "has_precommit_config": true
            },
            "components": [
              {
                "key": "ci_workflows",
                "name": "CI workflows",
                "detail": "3 workflow(s)",
                "points": 24,
                "status": "met",
                "details": [
                  {
                    "code": "ci_workflows",
                    "params": {
                      "count": 3
                    }
                  }
                ],
                "max_points": 24
              },
              {
                "key": "tests_present",
                "name": "Tests present",
                "detail": null,
                "points": 24,
                "status": "met",
                "details": [],
                "max_points": 24
              },
              {
                "key": "linter_config",
                "name": "Linter config",
                "detail": null,
                "points": 16,
                "status": "met",
                "details": [],
                "max_points": 16
              },
              {
                "key": "pre_commit_hooks",
                "name": "Pre-commit hooks",
                "detail": null,
                "points": 9.6,
                "status": "met",
                "details": [],
                "max_points": 9.6
              },
              {
                "key": "editorconfig",
                "name": ".editorconfig",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 6.4
              },
              {
                "key": "openssf_scorecard_ci_tests",
                "name": "OpenSSF Scorecard: CI-Tests",
                "detail": "no pull request found",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_data",
                    "params": {}
                  }
                ],
                "max_points": 20
              }
            ]
          },
          {
            "key": "documentation",
            "band": "excellent",
            "name": "Documentation",
            "note": null,
            "notes": [],
            "value": 90,
            "inputs": {
              "topics": [],
              "has_wiki": true,
              "homepage": "https://getsshift.dev",
              "has_readme": true,
              "has_docs_dir": true,
              "has_description": true
            },
            "components": [
              {
                "key": "readme",
                "name": "README",
                "detail": null,
                "points": 30,
                "status": "met",
                "details": [],
                "max_points": 30
              },
              {
                "key": "documentation_directory",
                "name": "Documentation directory",
                "detail": null,
                "points": 25,
                "status": "met",
                "details": [],
                "max_points": 25
              },
              {
                "key": "documentation_homepage_site",
                "name": "Documentation / homepage site",
                "detail": "https://getsshift.dev",
                "points": 15,
                "status": "met",
                "details": [],
                "max_points": 15
              },
              {
                "key": "repository_description",
                "name": "Repository description",
                "detail": null,
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              },
              {
                "key": "topics",
                "name": "Topics",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              },
              {
                "key": "wiki",
                "name": "Wiki",
                "detail": null,
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              }
            ]
          }
        ],
        "description": "Are baseline engineering and documentation practices in place?"
      },
      {
        "key": "security",
        "band": "weak",
        "name": "Security",
        "value": 35,
        "weight": 0.16,
        "metrics": [
          {
            "key": "security_posture",
            "band": "weak",
            "name": "Security posture",
            "note": "Excluded from scoring (no data or not applicable): CI-Tests, Signed-Releases. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "ci_tests",
                    "signed_releases"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 35,
            "inputs": {
              "source": "openssf_scorecard",
              "checks_evaluated": 16,
              "scorecard_version": "v5.5.0",
              "checks_inconclusive": 2,
              "scorecard_aggregate": 3.5
            },
            "components": [
              {
                "key": "binary_artifacts",
                "name": "Binary-Artifacts",
                "detail": "no binaries found in the repo",
                "points": 7.5,
                "status": "met",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "branch_protection",
                "name": "Branch-Protection",
                "detail": "branch protection not enabled on development/release branches",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "ci_tests",
                "name": "CI-Tests",
                "detail": "no pull request found",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_data",
                    "params": {}
                  }
                ],
                "max_points": 2.5
              },
              {
                "key": "cii_best_practices",
                "name": "CII-Best-Practices",
                "detail": "no effort to earn an OpenSSF best practices badge detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "code_review",
                "name": "Code-Review",
                "detail": "Found 0/30 approved changesets -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "contributors",
                "name": "Contributors",
                "detail": "project has 1 contributing companies or organizations -- score normalized to 3",
                "points": 0.8,
                "status": "partial",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "dangerous_workflow",
                "name": "Dangerous-Workflow",
                "detail": "no dangerous workflow patterns detected",
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              },
              {
                "key": "dependency_update_tool",
                "name": "Dependency-Update-Tool",
                "detail": "no update tool detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "fuzzing",
                "name": "Fuzzing",
                "detail": "project is not fuzzed",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "license",
                "name": "License",
                "detail": "license file detected",
                "points": 2.5,
                "status": "met",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "maintained",
                "name": "Maintained",
                "detail": "30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10",
                "points": 7.5,
                "status": "met",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "packaging",
                "name": "Packaging",
                "detail": "packaging workflow detected",
                "points": 5,
                "status": "met",
                "details": [],
                "max_points": 5
              },
              {
                "key": "pinned_dependencies",
                "name": "Pinned-Dependencies",
                "detail": "dependency not pinned by hash detected -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "sast",
                "name": "SAST",
                "detail": "no SAST tool detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "security_policy",
                "name": "Security-Policy",
                "detail": "security policy file not detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "signed_releases",
                "name": "Signed-Releases",
                "detail": "no releases found",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_data",
                    "params": {}
                  }
                ],
                "max_points": 7.5
              },
              {
                "key": "token_permissions",
                "name": "Token-Permissions",
                "detail": "detected GitHub workflow tokens with excessive permissions",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "vulnerabilities",
                "name": "Vulnerabilities",
                "detail": "25 existing vulnerabilities detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              }
            ]
          },
          {
            "key": "dependency_advisories",
            "band": "weak",
            "name": "Dependency advisories",
            "note": "Matched the npm:@lethevimlet/sshift@1.6.1 runtime dependency closure — what installing the published package pulls in — 132 packages. Reachability is not analyzed.",
            "notes": [
              {
                "code": "advisories_scope_published",
                "params": {
                  "package": "npm:@lethevimlet/sshift@1.6.1",
                  "assessed": 132
                }
              },
              {
                "code": "advisories_reachability",
                "params": {}
              }
            ],
            "value": 36,
            "inputs": {
              "source": "osv",
              "advisories": 17,
              "affected_packages": 11,
              "assessed_packages": 132,
              "unassessed_packages": 0,
              "affected_by_severity": "high 5, moderate 4, low 2",
              "direct_affected_packages": 2
            },
            "components": [
              {
                "key": "direct_dependencies_free_of_known_advisories",
                "name": "Direct dependencies free of known advisories",
                "detail": "2 affected: uuid 9.0.0 (high 7.5), express 4.18.2 (moderate 6.1)",
                "points": 12.1,
                "status": "partial",
                "details": [
                  {
                    "code": "advisories_affected",
                    "params": {
                      "count": 2,
                      "packages": "uuid 9.0.0 (high 7.5), express 4.18.2 (moderate 6.1)"
                    }
                  }
                ],
                "max_points": 35
              },
              {
                "key": "indirect_dependencies_free_of_known_advisories",
                "name": "Indirect dependencies free of known advisories",
                "detail": "9 affected: body-parser 1.20.1 (high 7.5), engine.io 6.5.5 (high 7.5), path-to-regexp 0.1.7 (high 7.5), +6 more",
                "points": 5,
                "status": "partial",
                "details": [
                  {
                    "code": "advisories_affected",
                    "params": {
                      "count": 9,
                      "packages": "body-parser 1.20.1 (high 7.5), engine.io 6.5.5 (high 7.5), path-to-regexp 0.1.7 (high 7.5)"
                    }
                  },
                  {
                    "code": "advisories_affected_more",
                    "params": {
                      "count": 6
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "no_advisories_left_outstanding",
                "name": "No advisories left outstanding",
                "detail": "9 advisory-carrying package(s) unaddressed past 90 days; oldest published 858 days ago",
                "points": 18.6,
                "status": "partial",
                "details": [
                  {
                    "code": "advisories_stale",
                    "params": {
                      "days": 90,
                      "count": 9,
                      "oldest": 858
                    }
                  }
                ],
                "max_points": 40
              }
            ]
          },
          {
            "key": "malicious_dependencies",
            "band": "exceptional",
            "name": "Malicious dependencies",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "source": "osv",
              "meaning": "reported as a malicious package by the OpenSSF corpus; the remedy is removal or moving off the compromised name, never an upgrade of the same artifact. Versions the registry has since pulled are listed but not scored",
              "packages": [],
              "red_flag": false,
              "assessed_packages": 132,
              "malicious_packages": 0,
              "direct_malicious_packages": 0,
              "withdrawn_malicious_packages": 0,
              "installable_malicious_packages": 0
            },
            "components": [
              {
                "key": "no_dependency_reported_as_a_malicious_package",
                "name": "No dependency reported as a malicious package",
                "detail": "no dependency is reported as a malicious package",
                "points": 100,
                "status": "met",
                "details": [
                  {
                    "code": "no_malicious_dependencies",
                    "params": {}
                  }
                ],
                "max_points": 100
              }
            ]
          },
          {
            "key": "high_risk_jurisdiction_exposure",
            "band": "exceptional",
            "name": "High-Risk Jurisdiction Exposure",
            "note": "Only high-confidence self-published location evidence affects this multiplier. Ambiguous matches are review-only; country evidence is not proof of nationality, citizenship, legal registration, malicious intent, or sanctions status.",
            "notes": [
              {
                "code": "jurisdiction_evidence_limits",
                "params": {}
              }
            ],
            "value": 100,
            "inputs": {
              "meaning": "self-published location evidence; not nationality or citizenship",
              "red_flag": false,
              "exposures": [],
              "policy_countries": [
                "Russia",
                "Iran",
                "North Korea"
              ],
              "commit_weight_rule": {
                "min_commits": 50,
                "min_commit_share": 0.1
              },
              "review_only_matches": 0,
              "below_threshold_exposures": [],
              "assessed_self_published_locations": 2
            },
            "components": [
              {
                "key": "policy_exposure_multiplier",
                "name": "Policy exposure multiplier",
                "detail": "no confirmed policy-scope location match",
                "points": 100,
                "status": "met",
                "details": [
                  {
                    "code": "jurisdiction_no_match",
                    "params": {}
                  }
                ],
                "max_points": 100
              }
            ]
          }
        ],
        "description": "Are visible security and supply-chain practices strong, with no malicious dependency and no unresolved high-risk jurisdiction exposure?"
      },
      {
        "key": "ai_readiness",
        "band": "moderate",
        "name": "AI Readiness",
        "value": 54,
        "weight": 0.04,
        "metrics": [
          {
            "key": "ai_agent_context",
            "band": "good",
            "name": "Agent context & guidance",
            "note": null,
            "notes": [],
            "value": 72,
            "inputs": {
              "has_llms_txt": false,
              "legible_history_share": 0.51,
              "agent_instruction_files": [
                "AGENTS.md"
              ],
              "agent_instruction_max_bytes": 9064
            },
            "components": [
              {
                "key": "agent_instructions",
                "name": "Agent instructions",
                "detail": "AGENTS.md",
                "points": 45,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "AGENTS.md"
                    }
                  }
                ],
                "max_points": 45
              },
              {
                "key": "machine_readable_docs_llms_txt",
                "name": "Machine-readable docs (llms.txt)",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 15
              },
              {
                "key": "legible_commit_history",
                "name": "Legible commit history",
                "detail": "51 of 100 human commits state their intent (structured subject or explanatory body)",
                "points": 27.2,
                "status": "partial",
                "details": [
                  {
                    "code": "legible_history",
                    "params": {
                      "legible": 51,
                      "sampled": 100
                    }
                  }
                ],
                "max_points": 40
              }
            ]
          },
          {
            "key": "ai_verify_loop",
            "band": "weak",
            "name": "Verify loop (build / test / typecheck)",
            "note": null,
            "notes": [],
            "value": 43,
            "inputs": {
              "has_nix": false,
              "has_tests": true,
              "lockfiles": [
                "package-lock.json",
                "pnpm-lock.yaml"
              ],
              "has_dockerfile": true,
              "typed_language": false,
              "bootstrap_files": [],
              "has_devcontainer": false,
              "has_linter_config": true,
              "typecheck_configs": [],
              "agent_commit_share": 0,
              "toolchain_manifests": [],
              "dependency_bot_commit_share": 0
            },
            "components": [
              {
                "key": "one_command_bootstrap",
                "name": "One-command bootstrap",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 18
              },
              {
                "key": "automated_tests",
                "name": "Automated tests",
                "detail": null,
                "points": 22,
                "status": "met",
                "details": [],
                "max_points": 22
              },
              {
                "key": "lint_format_config",
                "name": "Lint / format config",
                "detail": null,
                "points": 11,
                "status": "met",
                "details": [],
                "max_points": 11
              },
              {
                "key": "static_type_checking",
                "name": "Static type checking",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 11
              },
              {
                "key": "reproducible_environment",
                "name": "Reproducible environment",
                "detail": "Dockerfile, lockfile",
                "points": 10,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "Dockerfile, lockfile"
                    }
                  }
                ],
                "max_points": 10
              },
              {
                "key": "demonstrated_agent_practice",
                "name": "Demonstrated agent practice",
                "detail": "no agent-authored commits among the last 100",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_agent_authored_commits",
                    "params": {
                      "sampled": 100
                    }
                  }
                ],
                "max_points": 10
              },
              {
                "key": "automated_maintenance",
                "name": "Automated maintenance",
                "detail": "no automated dependency updates observed",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_dependency_automation",
                    "params": {}
                  }
                ],
                "max_points": 8
              },
              {
                "key": "openssf_scorecard_pinned_dependencies",
                "name": "OpenSSF Scorecard: Pinned-Dependencies",
                "detail": "dependency not pinned by hash detected -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "ai_code_legibility",
            "band": "moderate",
            "name": "Code legibility for models",
            "note": null,
            "notes": [],
            "value": 50,
            "inputs": {
              "primary_language": "JavaScript",
              "largest_source_bytes": 497398,
              "source_files_sampled": 77,
              "oversized_source_files": 7
            },
            "components": [
              {
                "key": "type_checkable_code",
                "name": "Type-checkable code",
                "detail": "JavaScript without a type-check config",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_typecheck_config_language",
                    "params": {
                      "language": "JavaScript"
                    }
                  }
                ],
                "max_points": 45
              },
              {
                "key": "manageable_file_sizes",
                "name": "Manageable file sizes",
                "detail": "7/77 source files over 60KB",
                "points": 50,
                "status": "partial",
                "details": [
                  {
                    "code": "oversized_source_files",
                    "params": {
                      "kb": 60,
                      "sampled": 77,
                      "oversized": 7
                    }
                  }
                ],
                "max_points": 55
              }
            ]
          }
        ],
        "description": "How well is the repo equipped to be developed and maintained with AI coding agents? Carries a deliberately small weight: agent tooling is a real maintenance signal, but its absence must never gate the top of the scale (calibration saturates at raw 91, so 100/100 remains reachable with AI Readiness at zero)."
      }
    ],
    "classification": {
      "top": [
        "library",
        "application"
      ],
      "labels": [
        "library",
        "network-service"
      ],
      "scores": {
        "library": 6,
        "network-service": 4
      },
      "primary": "library",
      "evidence": [
        {
          "tier": "distribution",
          "label": "library",
          "source": "registry:npm",
          "weight": 6
        },
        {
          "tier": "dependencies",
          "label": "network-service",
          "source": "dep:express",
          "weight": 4
        }
      ],
      "artifacts": [],
      "confidence": "medium",
      "host_extension": false,
      "runs_as_process": true,
      "consumed_by_code": true
    },
    "metrics_version": "2.5.0"
  },
  "warnings": [
    "Star history unavailable: GitHub GraphQL error: Resource not accessible by personal access token",
    "GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository"
  ],
  "report_type": "repository",
  "generated_at": "2026-08-01T00:34:02.224350Z",
  "schema_version": "0.27.0",
  "badge_url": "https://raw.githubusercontent.com/inspect-software/badges/main/v1/l/lethevimlet/sshift.svg",
  "full_name": "lethevimlet/sshift",
  "license_state": "standard",
  "license_spdx": "MIT"
}

评分是信号,而非担保。 评分反映的是 GitHub 上公开可见的实践——不是代码审计,也不是安全保证。

缺失数据将被剔除并重新归一化权重,绝不按零分计。方法论已版本化并公开:指标 v2.5.0、模式 v0.27.0—— 完整方法论 · 指标知识库.

单项结果在整体记录中的位置: 汇总统计npm.