55 Commits
Author SHA1 Message Date
Qiu Yuzhou 923f004527 feat: add third-party components and licenses window 2026-10-26 19:53:38 +08:00
Qiu Yuzhou 35c74d40c5 feat: bundle offline dependency provenance and license metadata 2026-10-26 19:06:38 +08:00
Qiu Yuzhou 5d6b8f433f test: move process lifecycle checks to smoke 2026-10-24 03:27:28 +08:00
Qiu Yuzhou 366bb43711 test(agent): replace timed reload integration test 2026-10-24 02:51:15 +08:00
Qiu Yuzhou f9e95474e5 refactor: adopt age configuration storage
Replace the custom encrypted container with AgeSwift 0.1.0 so standard
age tools can inspect sensitive configuration independently of the app.

Use independent Keychain X25519 identities for catalog.age and
proxy-listening.age. Preserve atomic commits and confirmed recovery;
reject historical NG2 formats without migration or cleanup.
2026-10-21 14:31:32 +08:00
Qiu Yuzhou 94ef128db8 feat(system-proxy): emit privileged helper events to unified log
The root system-proxy helper logged nothing and launchd discards its
stdout/stderr, leaving helper launches, connection refusals, GUI-side
timeouts and root-side failures invisible. Share the minimal unified-log
emission unit (SystemLog) across GUI, wrapper and helper, and add a
dedicated closed helper event enum (lifecycle minimum: listener ready,
connection accepted/rejected with peer pid, typed apply/clear results)
with ADR-0045 privacy splitting. The emission seam is injected at the
helper engine and listener for test doubles. Helper events are
Console-only: no stderr, no agent.log, no diagnostic view or report
surface (ADR-0047).
2026-10-20 15:59:38 +08:00
Qiu Yuzhou 1d85e86b8e feat(diagnostics): unify log lines into one filtered view
Collapse the four definitions of "one log line" into LogLine (timestamp,
level, source, text) with a single renderer and parser. agent.log lines
now carry the uniform prefix, absorbing the ssxng: event marker into the
source column; legacy lines degrade to raw display without rewriting.
Level detection moves into the row model with a classic three-level
taxonomy whose unified-log projection stays unchanged.

The diagnostics log view merges GUI events and the agent.log tail through
DiagnosticLogIntake into one time-ordered list with level and source
filters; the tail window logic moves into the intake and blank lines are
kept for raw fidelity. The report whitelist projection and unified-log
routing are unchanged.
2026-10-20 14:42:51 +08:00
Qiu Yuzhou 1541e97fb3 test(runtime): silence unified-log writes during test runs
Add the SSXNG_SYSTEM_LOG=0 test seam: RuntimeLog.emit and the sslocal
unified-log destination skip their os.Logger writes when it is set,
while stderr lines and the event sink are unaffected. Test execution
no longer floods the unified log store (a full validate run previously
recorded 795 entries under the shared subsystem, 743 from xctest and
52 from test-spawned agents).

The switch is injected by the schemes' test actions only (Run/Profile
keep system logging so debugging from Xcode still shows it), and set
explicitly in the fixtures that spawn the agent/sslocal processes,
including the fixture that strips inherited SSXNG_* path seams. The
main scheme is now declared explicitly to keep the variable out of the
Launch/Profile actions. Document the seam in ADR-0045.
2026-10-20 12:38:21 +08:00
Qiu Yuzhou d099239e3e feat(agent): route sslocal output through a dual-destination intake
Collect line framing, password redaction and oversized-line discard in
one SslocalOutputIntake and dispatch redacted lines through a
destination seam: stderr (freopen'd into agent.log, raw capture kept
for ADR-0006 local viewing) and the unified log under category "sslocal",
reusing the shared subsystem.

Upstream free text is never routed through RuntimeLogEvent and is
marked private in the unified log as a whole (ADR-0014 warns default
upstream logs may contain server addresses). Levels derive from
whitespace-separated upstream level tokens: ERROR maps to .error,
WARN/WARNING maps to .default (unified logging has no warning level),
and anything else maps to .info so the unified-log surface persists
only detected problems while agent.log stays the persistent surface.

The intake lives in Domain/Runtime so the hostless test mirror compiles
it; unit tests drive raw chunks against fake destinations instead of
parsing agent.log text. The 1 MiB agent.log retention policy deliberately
stays on the file side shared with wrapper event lines.
2026-10-20 12:18:41 +08:00
Qiu Yuzhou e0e2814b49 refactor: separate configuration, runtime, and log directories 2026-10-19 20:17:10 +08:00
Qiu Yuzhou e0db8b1e13 refactor(runtime): publish isolated supervision generations
Separate Agent supervision from sslocal input and preserve the latest intent after deployment failure. Persist failure barriers, serialize publication and retirement, and verify reload and single-instance behavior with real processes.

Refs #83
2026-10-19 03:00:33 +08:00
Qiu Yuzhou 4c6f0f60f3 fix(project): exclude .xcstrings-mcp.lock files from resources 2026-10-16 15:55:17 +08:00
Qiu Yuzhou 297fc30c41 feat(connectivity): measure warmed HTTP latency phases 2026-10-15 17:39:13 +08:00
Qiu Yuzhou 1353dc6292 fix(localization): disable automatic string catalog extraction 2026-10-14 11:52:21 +08:00
Qiu Yuzhou ebcd7ab2e2 feat(connectivity): add manual server connectivity tests 2026-10-14 00:53:52 +08:00
Qiu Yuzhou 2f0c0d7676 feat: separate local and LAN proxy listeners 2026-10-13 03:58:03 +08:00
Qiu Yuzhou c49d605a5b chore(ng2): remove fork issue references from comments 2026-10-13 00:38:06 +08:00
Qiu Yuzhou dc64349702 feat(plugins): bundle Cloak v2 client 2026-10-12 12:38:32 +08:00
Qiu Yuzhou f72c50dceb feat(plugins): configure plugin transport mode 2026-10-11 23:58:47 +08:00
Qiu Yuzhou 9b6d73e91c build(signing): 签名身份按 config 注入,Debug 免证书构建
证书与 Team 不入库(开源仓库):Debug 基线改为 ad-hoc,新 clone 与 CI 无
需任何证书即可 task build / task test;Release 基线保持 Developer ID,发
布产物仍须真签名。本机身份由 gitignore 的 Configs/Signing.local.xcconfig
注入,打包门槛的 Team 期望值同源,缺失时明确失败而非静默跳过。

project.yml 不能再设置 CODE_SIGN_IDENTITY / DEVELOPMENT_TEAM:pbxproj 构
建设置优先于 project xcconfig,写死会静默盖掉基线(实测)。整条注入链条
由 Tests/Architecture/SigningConfigurationTests.swift 守卫。

ad-hoc 产物不带 Hardened Runtime(Xcode 对 ad-hoc 签名不传 -o runtime),
嵌套 helper 重签与之一致;Hardened Runtime 仍由 DevID 路径与打包门槛断言。
2026-10-05 21:25:12 +08:00
Qiu Yuzhou 20a62117f2 refactor(tests): 按功能领域重组 Tests/ 目录
Tests/ 根层原有 138 个平铺测试文件,无法从目录读出测试覆盖的功能领域。

按功能领域重组为 17 个目录(Catalog/Workflow 与 Rules/Custom 两处嵌套,分别
对应 App/Application/CatalogWorkflow 与 Domain/Rules/Custom):Architecture /
Support / Agent / Activation / Catalog / Subscription / Rules / ProxyRuntime /
ProxyControl / Runtime / Settings / SystemProxy / Plugin / Diagnostics /
Credentials / Workspace / Servers;Smoke/ 保持独立 target 不动。138 个移动中
132 个是 100% 相似度的纯重命名,其余 6 个在重命名之上带路径反推改写(见下)。

本次不是纯搬移:测试代码比生产代码多一层位置耦合,有 7 处用固定层数的
deletingLastPathComponent() 从 #filePath 反推仓库路径(都假定文件就在 Tests/
根层)。这 7 处改走新增的 Tests/Support/TestSourceTree.swift——从调用点沿父
目录上溯找到含 project.yml 的目录,因此不再依赖父目录层数,以后移动测试文件
也不会静默指错。

配套改动:

- 新增 Tests 布局守卫 TestsLayoutTests,断言 Tests/ 根层无平铺 .swift。比
  Domain 那条弱一档——根层的 Info.plist(info.path 生成物)与
  Fixtures/RuleSnapshots 下的 JSON(folder resource)都必须保留,不能用
  「树下只有 .swift」覆盖。
- project.yml:Smoke target 按文件引用的 Tests/AppArtifact.swift 改指
  Tests/Support/;两处注释里写死的 Tests/SkeletonTests.swift 随移动更新。
  excludes 的裸名 "Smoke" / "Fixtures" 仍匹配 Tests/ 直属子目录,无需改动。
- README 的 Tests 条目展开为目录说明,并修掉 Domain 条目里写死的
  Tests/SkeletonTests.swift。
- 顺带删去 DomainLayoutTests 文档注释里「整理前 62 个文件」这类易漂移的数字。

验证:xcodegen generate 后单测 target 为 139 个源文件(全部非 Smoke 文件,0 个
Smoke 文件混入)且仍以 Fixtures/RuleSnapshots 为资源,Smoke target 为 8 个
Smoke 文件加 AppArtifact;依赖路径反推的 6 个测试类全绿(路径指错即失败);
task format 未改写任何被移动文件;task lint 与整理前同为 21 条内容型 warning;
build 0 error;task test 1025 用例全通过(整理前 1024,+1 为新守卫);
TestsLayoutTests 经变异验证(根层投放探针即失败);packaging-gate ALL CHECKS
PASSED。
2026-10-05 20:37:35 +08:00
Qiu Yuzhou a31e2fa4bf docs(adr): 记录源码目录即分层与主题及守卫约定
App/ 按分层、Domain/ 按主题组织,两个根层只放子目录、不放平铺文件;边界由
Tests/SkeletonTests.swift 的守卫正面圈定断言,而不是禁令加豁免登记。

ADR-0029 只记录组织原则本身:分层与主题划分的依据(Domain 主题名取自
GLOSSARY.md 的章节划分)、四条被否决的备选(平铺加豁免登记、feature-first、
Domain 照搬分层、守卫锁定目录集合),以及两条在代码里不可见的 XcodeGen
约束和跨进程契约按文件引用的代价。

ADR 不维护目录与文件清单——清单由 ShadowsocksX-NG2/README.md 的工程结构
一节维护,避免记录随事实漂移。

project.yml 的两处分层注释与 README 的工程结构一节各加一处引用:前者按该
文件既有写法引用编号,后者用完整链接。
2026-10-05 20:27:11 +08:00
Qiu Yuzhou 856d6b4272 refactor(domain): 按主题重组 Domain/ 源码目录
Domain/ 根目录原有 62 个平铺源文件,无法从目录读出主题划分。

按「目录即主题」重组为 11 个目录(Rules/Custom 为嵌套子目录):Catalog /
Subscription / Rules / Runtime / Activation / Settings / SystemProxy /
Plugin / Credentials / LegacyImport / Diagnostics。纯移动:类型名、模块名
与 import 均未改动,Domain/ 根下不再有散落文件。目录口径取自
GLOSSARY.md 的章节划分(配置与订阅、代理规则、代理控制与网络、设置、
凭据与诊断),而非新造分类。

配套改动:

- project.yml:wrapper 与 helper 两个 target 按文件引用的 15 条显式路径
  随目录改名,并补注释固定这一约定——这 15 条指向不存在的文件时,
  xcodegen generate 以 "missing source directory" 直接失败(实测,不静默)。
- SkeletonTests 新增 DomainLayoutTests:断言 Domain/ 根下无平铺 .swift、
  树下只有 .swift。后者防的是与 App/ 同源的坑——Domain/ 整目录是 app 与
  Core 的 source path,任何非源码文件都会被 XcodeGen 收进 Resources 阶段
  并封进签名后的 bundle。
- README 工程结构说明同步;docs/design 与 docs/research 属历史文档不改。

验证:xcodegen generate 后 app 164 / Core 163 源文件数与整理前一致;Agent
恰好 9 个、Helper 恰好 6 个 Domain 文件(无缺失无多出);app 与 Core 各覆盖
全部 62 个。clean build 0 error;task test 1024 用例全通过(整理前 1022,
+2 为新增守卫);task format 未改写任何被移动文件(证明纯移动);task lint
与整理前同为 21 条内容型 warning;packaging-gate.sh ALL CHECKS PASSED。

布局守卫已做变异验证:临时投放松散 .swift 与非 .swift 文件时两个用例均
失败并点名违规文件,清理后复验为绿。
2026-10-05 20:13:35 +08:00
Qiu Yuzhou fb428f12bc refactor(app): 按分层重组 App/ 源码目录
App/ 根目录原有 73 个散落源文件,已无法从目录读出任何分组信息。

按「目录即分层」重组为 Composition / Application / Presentation / UI /
PlatformEffects / Resources 六个目录,UI 层再按子视图分目录(Workspace、
Home、Servers(含 Plugin)、Subscriptions、Rules、Settings、Diagnostics、
StatusMenu)。纯移动:类型名、模块名与 import 均未改动,App/ 根层不再有
散落文件。

配套改动:

- project.yml:Core 测试镜像的 excludes 由裸文件名改为 `**/` 递归形式。
  XcodeGen 的 exclude 不跨 `/`,裸名只匹配 App 根层;MainApp.swift 下移后
  旧写法会静默失效,把 @main 组合根编进测试镜像。info.path 与
  entitlements.path 指向 App/Resources/;并排除 `**/*.md`——App/ 下任何
  非源码文件都会被 XcodeGen 收进 Resources 阶段并封进签名后的 bundle。
- SkeletonTests 的架构守卫由「默认扫描全部 App/ + 7 个豁免文件 + 1 个豁免
  前缀 + 4 个豁免目录」改为正面圈定 App/UI/,豁免登记全部删除;新增运行时
  适配器放进 Application/ 即自动落在扫描范围外,无需再登记。
- README 工程结构说明同步;根 .gitignore 的生成物路径随 Resources/ 归位。

验证:clean build 0 error;task test 1022 用例全通过(含两个架构守卫);
task format 无改动(证明纯移动);task lint 与移动前同为 21 条内容型
warning;packaging-gate.sh ALL CHECKS PASSED,确认 plist/entitlements 归位
后签名与 entitlements 语义不变。

注意:迁移时须先删旧的生成物再 xcodegen generate,否则遗留的 App/Info.plist
会被当作普通资源扫进 Resources 阶段。
2026-10-05 19:56:18 +08:00
Qiu Yuzhou 2e24cdc752 chore: 关闭构建期字符串字面量自动抽取
LOCALIZED_STRING_SWIFTUI_SUPPORT(Use Compiler to Extract Swift
Strings)置 NO:IDE 构建不再把 SwiftUI 字面量写进
Localizable.xcstrings。该目录按仓库惯例手工维护少量 key(中英对照),
自动抽取会在 Xcode 打开构建时膨胀文件(#81 人工验收时实测 88+ key)。
2026-10-04 11:36:12 +08:00
Qiu Yuzhou f80018a260 test: 并发执行冒烟测试并隔离本地网络夹具 2026-10-04 01:46:21 +08:00
Qiu Yuzhou 45b20d329b test: 单测开启多 runner 并行执行 2026-10-03 23:26:08 +08:00
Qiu Yuzhou 5c24b65227 refactor(rules): 删 RuleSet 死类型与 updateCustomRules 前文档时代缝 2026-10-03 19:11:30 +08:00
Qiu Yuzhou a5d96dc990 refactor(rules): keep one conversion path 2026-10-03 02:55:48 +08:00
Qiu Yuzhou 7d0ebc79b4 refactor(tests): drop TEST_HOST by linking tests against app-source static lib
单测不再以 app 可执行为宿主:新增 ShadowsocksX-NG2Core 静态库 target 同源
编译 App/(除 @main)与 Domain/,模块名沿用 ShadowsocksX_NG2 使全部测试的
@testable import 零改动;app target 模块改名 ShadowsocksX_NG2App 避免共享
Products 目录 swiftmodule 撞名。两个测试 target 改链 Core 后 xcodegen 不再
生成 TEST_HOST,xcodebuild test 只构建不启动 app——与安装版运行实例零接触
(pgrep 全程验证无 app 进程)。同源双编译沿用 Agent/Helper 共享 Domain 文件
的既定模式,Core 只被测试 target 依赖,发布链不编译它。

随之删除 MainApp.swift 的整套测试宿主脚手架(isUnitTesting/makeTesting/
Noop 服务/Ephemeral 存储,-142 行),app 在测试下不再启动,守卫失去存在
理由。宿主移除暴露三处 Bundle.main 依赖,改为显式缝:控制器新增 appBundle
注入(内置规则快照 BuiltinRuleCatalog 与 LaunchDaemon 清单指纹
launchDaemonPlistData(in:) 均按 bundle 读取,生产默认 .main 不变);XPC
listener delegate 期望客户端身份改为可注入(测试按本进程 SecCode 自查)。
测试经 AppArtifact.bundle(沿测试包落盘位置向上查找 app 产物,兼容
TEST_HOST 内嵌与平级两种布局)统一取产物。

665 单测 + 11 冒烟全绿,task build 生产构建不受影响。
2026-09-30 20:44:13 +08:00
Qiu Yuzhou ac60d14099 refactor(tests): locate app product via bundle ancestor walk, not Bundle.main
测试需要 app 产物(嵌入二进制、打包面断言)时改经 AppArtifact 沿测试包
落盘位置向上查找 ShadowsocksX-NG2.app,覆盖两种布局:TEST_HOST 过渡期
.xctest 嵌于 Contents/PlugIns/,去宿主化后与 .app 平级。接触点:
AgentLifecycleTests wrapper、BundleManagedPluginProviderTests 组合根事实
断言、smoke 三处 sslocal/wrapper/plugin 定位、SkeletonTests 改读产物
Info.plist。行为等价(665 单测 + 11 冒烟全绿),为去 TEST_HOST 铺路。
2026-09-30 20:05:37 +08:00
Qiu Yuzhou 43ea00cb44 fix(system-proxy): use BundleProgram in daemon plist and refresh stale registration
launchd 对 ProgramArguments[0] 的相对路径按 _PATH_STDPATH(PATH)解析而非
bundle:系统域 daemon 的 job 因此永远 spawn 失败(EX_CONFIG 节流循环,
launchctl print 显示 job state = spawn failed、runs 持续累加),客户端 XPC
调用只能等到超时。agent 之所以正常,是 GUI 域导入时被解析成绝对路径,system
域 submitted job 没有这一步。SMAppService 清单的正确键是 BundleProgram
(bundle 相对,launchd.plist(5)),改用之。

launchd 同时沿用注册提交时的定义快照:app 更新改动 LaunchDaemon 清单后旧
定义不会自动刷新(register() 对已注册服务返回 alreadyRegistered),必须注销
重注。门禁在已批准分支按清单内容 SHA-256 指纹(UserDefaults)检测漂移,漂移
才注销重注——审批与 bundle 签名绑定,重注不重新弹授权;首次注册成功即记指
纹,失败不记(下次启动重试),指纹一致为一次哈希比对的 no-op。
2026-09-30 18:54:19 +08:00
Qiu Yuzhou c1aba5a882 fix(system-proxy): make helper XPC connection reachable and crash-free
GUI 侧 XPC 回调闭包(invalidationHandler 与 remoteObjectProxy 错误回调)在
@MainActor 方法里形成、传给非 @Sendable 的 Foundation 参数被推断为主 actor
隔离,Foundation 在 XPC 内部队列上调起时触发动态隔离断言直接崩溃;显式标注
@Sendable(nonisolated),闭包只捕获 Sendable 应答闸门。

连接系统域 LaunchDaemon 的 MachService 必须带 options: .privileged(否则只
在 per-user launchd 域查找,永远失败走错误回调);并把 resume() 提到首次远
程调用之前,超时兜底任务在应答到达后取消。

收敛/清除失败补 RuntimeLog systemProxyWriteFailed(detail:)(诊断报告白名单
按既有承诺脱敏收录),XPC/ helper 层失败原因不再只能靠崩溃报告逆向。

listener delegate 与客户端校验移入 Domain 共享编译单元(与引擎同模式),
main.swift 改为显式注入引擎;新增真实 XPC 装配层 round-trip 测试:匿名监听
器挂真实 delegate(注入假特权写入器)+ listenerEndpoint 连接,覆盖接口签
名、客户端签名校验、Data 传输与 XPC 队列应答路径,无需 launchd。
2026-09-30 13:40:16 +08:00
Qiu Yuzhou d2a8d6105c feat(system-proxy): route writes through privileged LaunchDaemon helper
Implements issue #71 with ADR-0020 (supersedes ADR-0019's endpoint-signature
cleanup, startup cleanup, and health-gate cleanup).

- New ShadowsocksX-NG2SystemProxyHelper target: app-bundled root LaunchDaemon
  (SMAppService.daemon, on-demand via MachServices) that mechanically applies
  a closed typed configuration or executes an unconditional clear. Clients are
  accepted by code-signing bundle identifier only; requests run serially on a
  shared queue (last-writer-wins).
- SystemProxyConfiguration is now the closed typed apply payload (Codable over
  JSON XPC): explicit SOCKS/HTTP/HTTPS enable + endpoints, PAC and
  auto-discovery disabled with URL/JS removed, explicit simple-hostname
  exclusion, complete ExceptionsList. Unmodeled Proxies keys are preserved and
  equivalent values skip the commit.
- Cleanup is a side effect of a persisted system-proxy intent on-to-off
  transition only: the switch itself, or the agent-off cascade (persist both
  intents off, clear, then stop listeners; failed cascade persistence records
  no transition and clears nothing). Launch with the switch off, health-gate
  closure, invalid targets, and passive events never clear. Failed clears are
  surfaced without deferred retry.
- Endpoint-signature store, ownership migration, and direct Authorization
  Services writes are removed; old signature files stay inert. Helper
  unavailability or pending approval keeps an enabled intent pending and
  surfaces the Login Items approval path (HomeView button + menu label).
2026-09-30 12:26:40 +08:00
Qiu Yuzhou 5abb6c6033 build: migrate NG2 to Swift 6 and clear warnings 2026-09-28 08:44:53 +08:00
Qiu Yuzhou 851660f1c6 test: split real-process smoke into opt-in target
Real sslocal smoke (11 tests, ~27s) sat on every task test as the
slow tail. Tests/Smoke now builds as a separate target behind the
ShadowsocksX-NG2Smoke scheme; task test:smoke runs it on demand and
the README release gate requires it green next to packaging-gate.

SignalRecorder also gains kill(_, 0) semantics (stale pids report
ESRCH): the GUI-restart test burned the 5s waitForWrapperExit
ceiling on pid 99999, now 0.18s. Default lane: 617 tests ~40s.
2026-09-27 01:11:48 +08:00
Qiu Yuzhou 2acaef60c8 fix(agent): register LaunchAgent via launchctl bootstrap with absolute path
SMAppService does not resolve relative ProgramArguments paths reliably
when the app runs from DerivedData or other non-/Applications locations
(launchd reports inferred program + partial import and rejects spawn
with EX_CONFIG). The same binary and plist format work correctly when
registered via launchctl bootstrap with an absolute path.

Write the plist to ~/Library/LaunchAgents/ with the wrapper's absolute
path and register via launchctl bootstrap. Keep SMAppService only for
reading BTM approval status.
2026-09-26 23:32:42 +08:00
Qiu Yuzhou 354b0fb59e fix(agent): correct LaunchAgent ProgramArguments path for SMAppService
SMAppService resolves ProgramArguments relative to the app bundle's
Contents/ directory, not the bundle root. The plist used
Contents/MacOS/ShadowsocksX-NG2Agent which resolved to a nonexistent
Contents/Contents/MacOS/... path, causing launchd to report
EX_CONFIG / spawn failed and the wrapper never started.

Change to MacOS/ShadowsocksX-NG2Agent so launchd finds the binary.
2026-09-26 23:04:26 +08:00
Qiu Yuzhou a8cd6262c2 feat(proxy): remove PAC and accept ACL system proxy path
Remove the PAC product feature entirely: PAC HTTP service, port, health
probe, runtime extension, PAC URL system proxy projection, PAC rule
editing, and GFWList URL settings. The product now exposes rule / global /
direct modes only, all projected as a local SOCKS system-proxy target.

First-run defaults are rule + proxyWhenUnmatched, agent on, system proxy
off. Homepage and status menu share one ProxyControlSnapshot showing the
three modes and rule sub-options. Manual servers, subscriptions, Legacy
import, and independent SOCKS/HTTP inbounds are retained.

Fix: include verbose in SslocalListenFingerprint so a verbose-only
contract change restarts sslocal instead of leaving RUST_LOG stale.

Revise CONTEXT.md vocabulary, ADR-0007, and ADR-0009. Record GFWList
exception shadowing, IPv6 system-exception, DNS, and traffic-coverage
boundaries. Add host acceptance evidence document.

Closes #67
2026-09-26 21:58:12 +08:00
Qiu Yuzhou 7a59ca7827 feat(proxy): add China IPv4 CIDR direct candidates to rule mode
Pin gaoyifan/china-operator-ip china.txt and compile its IPv4 CIDRs into
the proxy_all ACL alongside China domain candidates. Explicit maintainer
update normalizes and deduplicates CIDRs and blocks replacement on
abnormal format, corruption, scale change, or all-invalid input.

Closes #64
2026-09-26 18:16:44 +08:00
Qiu Yuzhou 827fb98be3 refactor(ng2): fix all swiftlint warnings
77 → 0 warnings. Split oversized functions and files: controller and
agent extension files, standalone RuleSet/ProxySettingsFileStore/
SslocalListenSettings/LegacyImportService, test classes into topic
extensions. Regroup activation options into RuntimeDocumentOptions and
converter metadata into Provenance.

Runtime controller DI seams widen private → internal so the extension
files stay cohesive; new controller files are registered in the
architecture guard exemption list; Agent target adds RuleSet and
SslocalListenSettings to project.yml. Test JSON literals wrap inside
the string literal only.
2026-09-26 17:13:31 +08:00
Qiu Yuzhou 120116627a feat(proxy): add built-in China domain rules and rule mode
Introduce a source-independent rule model (action, domain/CIDR match,
source identity, conflict metadata) and ship a pinned geolocation-cn
snapshot produced by an explicit maintainer update. Rule mode with
proxy-when-unmatched compiles China domains as direct candidates into a
proxy_all ACL; the sub-option is selectable and persisted from home and
the status menu. Ordinary builds only read the local snapshot and fail
when it is missing or corrupt.
2026-09-26 15:25:45 +08:00
Qiu Yuzhou 39e25477bb feat(proxy): add ACL-backed direct mode 2026-09-26 11:46:45 +08:00
Qiu Yuzhou 3dc6043dc6 refactor(ng2): remove system-level shortcuts
Keep proxy controls explicit and leave existing shortcut preferences untouched.
2026-09-22 16:16:58 +08:00
Qiu Yuzhou 00fcd61a99 refactor(ng2): change bundle id to ShadowsocksX-NG2
Distinct id from Legacy (com.qiuyuzhou.ShadowsocksX-NG) lets NG2 own
~/Library/Application Support/ShadowsocksX-NG2/ directly; the v2/
subdir prefix is removed from all runtime paths.

- app/agent/tests targets + LaunchAgent plist renamed accordingly
- keychain service, queue labels, and Vendor signIdentifiers follow
  the new bundle id; embedded helpers re-sign at build time
- Legacy handoff/import constants keep pointing at the old id

Migration: dev data under ShadowsocksX-NG/v2/ is not carried over
(fresh catalog/activation); old LaunchAgent registration and old
keychain entries must be cleaned up manually.
2026-09-21 20:33:37 +08:00
Qiu Yuzhou 64eb7ff97d Configure app icon asset catalog 2026-09-21 11:47:54 +08:00
Qiu Yuzhou 5e13c1716f feat(ng2): managed plugins end to end (#38)
- 受管插件静态事实表(v2ray-plugin v1.3.2,来源/许可证/版本/重签)与
  BundleManagedPluginProvider:受管集内且 bundle 内可执行文件在位才返回
  绝对路径;与 Vendor 清单双向锚定(D6/D10)
- 服务器编辑器插件区:受管选择器(无 + 受管列表),选中受管项才显示
  供应链事实与参数输入;集外引用显式「本版本未提供」并原样保留
- updateServer 插件落盘语义:「无」整体清除、参数写删钥匙串、集外不漂移
- wrapper 监听建立判定:3 秒未建立本地监听记 error 日志,监管继续(D10)
- 诊断导出新增受管插件清单(名称/版本/提供状态,无参数)
- 功能级兼容实测记录(docs/research/plugin-compat-record.md)与端到端
  冒烟:契约插件路径 → sslocal 拉起 → v2ray-plugin 进程建立 → 停止链
2026-09-21 10:38:30 +08:00
Qiu Yuzhou 2531eb35fb feat(ng2): add settings page and preferences (#33) 2026-09-21 08:54:12 +08:00
Qiu Yuzhou c3a9f62216 feat(ng2): menu bar quick actions and global shortcuts (#31)
Status menu rebuilt around the D11 eight-item whitelist: header summary
(running status, mode, active target path), proxy toggle, mode picker with
checkmark, read-only active-target cascade over the catalog tree, update
all subscriptions, copy HTTP export line, open main window, and quit that
states the proxy keeps running in the background. Out-of-whitelist items
(copy PAC URL) removed. Two global shortcuts (KeyboardShortcuts 3.1.0)
toggle the proxy and cycle modes through the same controller entry points
as the menu.

Presentation logic extracted to StatusMenuModel (state summary mapping,
HTTP export line, mode cycle, read-only target tree) with unit tests;
activeTargetID is now published so the menu stays live when activation
happens while the proxy is off.
2026-09-21 07:39:22 +08:00
Qiu Yuzhou 8578de0ba4 feat(ng2): add PAC runtime and listen scope (#28) 2026-09-20 22:26:45 +08:00