chore(agents,tool): rebuild agent config, lint gates, CI, and release tooling

Rework .agents/, Claude/Codex config, CI workflows, lint gates and
lint-rule tests, and the release/changelog tooling. The 5% comment
density gate stays diff-scoped inside git hooks, where GIT_DIR outranks
the -C the gate uses to locate the repository. The tray icon generator
writes multi-size ICO files from the SVG sources into the per-platform
tray asset directories. The custom code-review subagents are gone: the
built-in /code-review never dispatched them, and the subagent model now
lives in a user-level setting outside the repository.

The Makefile, build_config.yaml and CI hand the Core and Helper builds to
the setup build hook, so the buildkit launchers go, and setup.dart
packages AppImage and rpm on arm64 with flutter_distributor pinned to the
fork tag that normalizes packaging permissions.

Changelog: skip
This commit is contained in:
chen08209
2026-09-05 20:09:26 +08:00
parent 62addf738a
commit 96fcb9ab5e
60 changed files with 5482 additions and 1010 deletions
+71 -42
View File
@@ -3,10 +3,11 @@ import 'dart:io';
import 'package:args/args.dart';
import 'package:path/path.dart' as p;
import 'package:yaml/yaml.dart';
const _allTargets = <String, String>{
'android': 'apk',
'linux': 'deb', // appimage + rpm added for amd64 only
'linux': 'deb,appimage,rpm',
'macos': 'dmg',
'windows': 'exe,zip',
};
@@ -53,8 +54,20 @@ Future<void> main(List<String> args) async {
final env = results['env'] as String;
final rootDir = Directory.current.path;
final skipped = packagesNotBuildingAssets(
File(p.join(rootDir, 'pubspec.yaml')).readAsStringSync(),
);
if (skipped.isNotEmpty) {
stderr.writeln(
'pubspec.yaml sets hooks.user_defines.<package>.build_assets: false '
'for ${skipped.join(', ')}; a package built this way would ship '
'whatever is left in libclash/ and no Rust library. '
'Restore "build_assets: true".',
);
exit(1);
}
final arch = _detectArch();
final targets = _getTargets(platform, arch, results['targets']);
final targets = createPackageTargets(platform, results['targets']);
final androidArch = results['arch'] as String?;
final verbose = results['verbose'] as bool;
@@ -115,10 +128,20 @@ Map<String, String> createBuildEnvironment(String env) {
return {'APP_ENV': env};
}
String _getTargets(String platform, String arch, String? customTargets) {
if (customTargets != null) return customTargets;
if (platform == 'linux' && arch == 'amd64') return 'deb,appimage,rpm';
return _allTargets[platform]!;
/// Packages whose build hook `pubspec.yaml` turns into a no-op.
List<String> packagesNotBuildingAssets(String pubspec) {
final document = loadYaml(pubspec);
if (document is! Map) return const [];
final defines = (document['hooks'] as Map?)?['user_defines'];
if (defines is! Map) return const [];
return [
for (final MapEntry(:key, :value) in defines.entries)
if (value is Map && value['build_assets'] == false) key.toString(),
]..sort();
}
String createPackageTargets(String platform, String? customTargets) {
return customTargets ?? _allTargets[platform]!;
}
void _showHelp(ArgParser parser) {
@@ -152,7 +175,7 @@ Future<int> _package(
descriptionArgs.addAll(['--description', arch]);
}
final depExit = await _ensureDependencies(platform, arch);
final depExit = await _ensureDependencies(platform);
if (depExit != 0) return depExit;
final activateResult = await Process.run('dart', [
@@ -163,7 +186,7 @@ Future<int> _package(
'git',
'https://github.com/chen08209/flutter_distributor.git',
'--git-ref',
'FlClash',
'v0.6.11-flclash.2',
'--git-path',
'packages/flutter_distributor',
]);
@@ -188,7 +211,6 @@ Future<int> _package(
...descriptionArgs,
],
includeParentEnvironment: true,
environment: {'ANDROID_ARCH': ?androidArch},
runInShell: Platform.isWindows,
);
@@ -221,12 +243,12 @@ Future<bool> _hasCommand(String cmd) async {
return result.exitCode == 0;
}
Future<int> _ensureDependencies(String platform, String arch) async {
Future<int> _ensureDependencies(String platform) async {
switch (platform) {
case 'macos':
return _ensureMacosDependencies();
case 'linux':
return _ensureLinuxDependencies(arch);
return _ensureLinuxDependencies();
default:
return 0;
}
@@ -245,20 +267,15 @@ Future<int> _ensureMacosDependencies() async {
return result.exitCode;
}
Future<int> _ensureLinuxDependencies(String arch) async {
final pkgGroups = <List<String>>[
Future<int> _ensureLinuxDependencies() async {
const pkgGroups = <List<String>>[
['ninja-build', 'libgtk-3-dev'],
['libayatana-appindicator3-dev'],
['libkeybinder-3.0-dev'],
['libsecret-1-dev'],
['locate'],
['rpm', 'patchelf'],
['libfuse2'],
];
if (arch == 'amd64') {
pkgGroups.addAll([
['rpm', 'patchelf'],
['libfuse2'],
]);
}
final missingGroups = <List<String>>[];
for (final group in pkgGroups) {
@@ -298,33 +315,45 @@ Future<int> _ensureLinuxDependencies(String arch) async {
}
}
if (arch == 'amd64') {
const appimagetool = '/usr/local/bin/appimagetool';
if (File(appimagetool).existsSync()) {
stdout.writeln('appimagetool already installed, skipping.');
return 0;
}
stdout.writeln('Downloading appimagetool...');
final downloadName = arch == 'amd64' ? 'x86_64' : 'aarch64';
final dlResult = await Process.run('wget', [
'-O',
appimagetool,
'https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-$downloadName.AppImage',
]);
if (dlResult.exitCode != 0) {
stderr.write(dlResult.stderr);
return dlResult.exitCode;
}
await Process.run('chmod', ['+x', appimagetool]);
const appimagetool = '/usr/local/bin/appimagetool';
if (File(appimagetool).existsSync()) {
stdout.writeln('appimagetool already installed, skipping.');
return 0;
}
stdout.writeln('Downloading appimagetool...');
final downloadName =
'appimagetool-${appImageToolArch(_detectArch())}.AppImage';
final dlResult = await Process.run('wget', [
'-O',
appimagetool,
'https://github.com/AppImage/AppImageKit/releases/download/continuous/$downloadName',
]);
if (dlResult.exitCode != 0) {
stderr.write(dlResult.stderr);
return dlResult.exitCode;
}
await Process.run('chmod', ['+x', appimagetool]);
return 0;
}
String appImageToolArch(String arch) {
return arch == 'arm64' ? 'aarch64' : 'x86_64';
}
/// Ubuntu 24.04 ships libfuse2 under its time64 name, which `dpkg -s libfuse2` cannot see.
const _debianPackageAliases = <String, List<String>>{
'libfuse2': ['libfuse2t64'],
};
Future<bool> _isDebianPackageInstalled(String pkg) async {
final result = await Process.run('dpkg', ['-s', pkg]);
return result.exitCode == 0 &&
(result.stdout as String).contains('Status: install ok installed');
for (final name in [pkg, ...?_debianPackageAliases[pkg]]) {
final result = await Process.run('dpkg', ['-s', name]);
if (result.exitCode == 0 &&
(result.stdout as String).contains('Status: install ok installed')) {
return true;
}
}
return false;
}
Future<bool> _areDebianPackagesInstalled(List<String> pkgs) async {