Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
include: package:dart_flutter_team_lints/analysis_options.yaml

analyzer:
language:
strict-raw-types: true

linter:
rules:
- avoid_bool_literals_in_conditional_expressions
- avoid_classes_with_only_static_members
- avoid_redundant_argument_values
- avoid_returning_this
- avoid_unused_constructor_parameters
- avoid_void_async
- cancel_subscriptions
- cascade_invocations
- join_return_with_assignment
- literal_only_boolean_expressions
- missing_whitespace_between_adjacent_strings
- no_adjacent_strings_in_list
- no_runtimeType_toString
- prefer_const_declarations
- prefer_expression_function_bodies
- prefer_final_locals
- require_trailing_commas
- simple_directive_paths
- unnecessary_ignore
- use_raw_strings
- use_string_buffers
62 changes: 33 additions & 29 deletions packages/dash_discover/lib/src/cli.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ ArgParser buildArgParser() => ArgParser()
'rule',
abbr: 'r',
help:
'Run only a specific rule by ID (e.g. checks-migration, pattern-matching).',
'Run only a specific rule by ID '
'(e.g. checks-migration, pattern-matching).',
)
..addOption(
'category',
Expand Down Expand Up @@ -81,19 +82,21 @@ Future<int> runCli(
try {
results = parser.parse(args);
} on FormatException catch (e) {
err.writeln('Error: ${e.message}\n');
err.writeln(
'Usage: dart run dash_discover [path-to-target-package] [options]\n',
);
err.writeln(parser.usage);
err
..writeln('Error: ${e.message}\n')
..writeln(
'Usage: dart run dash_discover [path-to-target-package] [options]\n',
)
..writeln(parser.usage);
return ExitCode.usage.code;
}

if (results.flag('help')) {
out.writeln(
'Usage: dart run dash_discover [path-to-target-package] [options]\n',
);
out.writeln(parser.usage);
out
..writeln(
'Usage: dart run dash_discover [path-to-target-package] [options]\n',
)
..writeln(parser.usage);
return ExitCode.success.code;
}

Expand All @@ -110,10 +113,11 @@ Future<int> runCli(
out.writeln('skills/dash-discover/SKILL.md is up-to-date!');
return ExitCode.success.code;
} else {
err.writeln('Error: skills/dash-discover/SKILL.md is out of date.');
err.writeln(
'Run `dart run dash_discover --update-skill` to update it.',
);
err
..writeln('Error: skills/dash-discover/SKILL.md is out of date.')
..writeln(
'Run `dart run dash_discover --update-skill` to update it.',
);
return ExitCode.data.code;
}
}
Expand Down Expand Up @@ -144,16 +148,15 @@ Future<int> runCli(
workingDirectory: io.Directory(absPath),
);
for (final rule in defaultDiscoveryRules) {
out.writeln(
'• ${rule.id} (${rule.category.label}) [${rule.lifecycle.label}]',
);
out.writeln(' Skill: ${rule.target.skillName}');
out.writeln(' Lifecycle: ${rule.lifecycle.label}');
out.writeln(' Category: ${rule.category.label}');
out.writeln(
' Confidence: ${rule.defaultConfidence.name.toUpperCase()}',
);
out.writeln(' Description: ${rule.description}');
out
..writeln(
'• ${rule.id} (${rule.category.label}) [${rule.lifecycle.label}]',
)
..writeln(' Skill: ${rule.target.skillName}')
..writeln(' Lifecycle: ${rule.lifecycle.label}')
..writeln(' Category: ${rule.category.label}')
..writeln(' Confidence: ${rule.defaultConfidence.name.toUpperCase()}')
..writeln(' Description: ${rule.description}');
final local = catalog.findByName(rule.target.skillName);
if (local != null) {
out.writeln(' Resolution: Local (${local.skillPath})');
Expand All @@ -163,7 +166,7 @@ Future<int> runCli(
if (rule.target.commitSha != null) {
out.writeln(' Pinned SHA: ${rule.target.commitSha}');
}
out.writeln('');
out.writeln();
}
return ExitCode.success.code;
}
Expand All @@ -184,10 +187,11 @@ Future<int> runCli(
}).toList();

if (activeRules.isEmpty) {
err.writeln('Error: No rules match the specified filters.');
err.writeln(
'Available rules: ${defaultDiscoveryRules.map((r) => r.id).join(', ')}',
);
err
..writeln('Error: No rules match the specified filters.')
..writeln(
'Available rules: ${defaultDiscoveryRules.map((r) => r.id).join(', ')}',
);
return ExitCode.usage.code;
}

Expand Down
5 changes: 2 additions & 3 deletions packages/dash_discover/lib/src/context.dart
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,8 @@ class PackageContext {
rawPubspec.contains('$name:');
}

String readContent(File file) {
return _contentCache.putIfAbsent(file.path, () => file.readAsStringSync());
}
String readContent(File file) =>
_contentCache.putIfAbsent(file.path, () => file.readAsStringSync());

List<File>? _allTestFiles;

Expand Down
31 changes: 17 additions & 14 deletions packages/dash_discover/lib/src/discovery_engine.dart
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ class DiscoveryReport {
};

String toMarkdown() {
final buffer = StringBuffer();
buffer.writeln('# Meta-Skill Discovery Report: $packageName\n');
buffer.writeln('Path: `$packagePath`\n');
final buffer = StringBuffer()
..writeln('# Meta-Skill Discovery Report: $packageName\n')
..writeln('Path: `$packagePath`\n');

if (staticOpportunities.isEmpty) {
buffer.writeln(
Expand All @@ -41,19 +41,21 @@ class DiscoveryReport {
'## 🎯 Detected Opportunities (${staticOpportunities.length})\n',
);
for (final opp in staticOpportunities) {
buffer.writeln(
'### [${opp.skill}](${opp.target.resolvedUri}) (${opp.category.label})\n',
);
final targetDesc = opp.target.isLocal
? 'Local file (`${opp.target.localPath}`)'
: 'Remote GitHub (${opp.target.githubUrl})';
buffer.writeln('- **Target Skill**: $targetDesc');
buffer.writeln('- **Lifecycle**: ${opp.lifecycle.label}');
buffer.writeln('- **Confidence**: ${opp.confidence.label}');
buffer.writeln('- **Impact**: ${opp.affectedCount} file(s)');
buffer.writeln('- **Diagnosis**: ${opp.diagnosis}');
buffer.writeln('- **Prescription**: ${opp.prescription}');
buffer.writeln('- **Evidence**:');
buffer
..writeln(
'### [${opp.skill}](${opp.target.resolvedUri}) '
'(${opp.category.label})\n',
)
..writeln('- **Target Skill**: $targetDesc')
..writeln('- **Lifecycle**: ${opp.lifecycle.label}')
..writeln('- **Confidence**: ${opp.confidence.label}')
..writeln('- **Impact**: ${opp.affectedCount} file(s)')
..writeln('- **Diagnosis**: ${opp.diagnosis}')
..writeln('- **Prescription**: ${opp.prescription}')
..writeln('- **Evidence**:');
for (final ev in opp.evidence) {
buffer.writeln(' - $ev');
}
Expand Down Expand Up @@ -85,7 +87,8 @@ class DiscoveryEngine {
);
final rawOpportunities = staticEngine.scan();

// Resolve skill targets against catalog (prefer local SKILL.md over remote GitHub)
// Resolve skill targets against catalog (prefer local SKILL.md over
// remote GitHub)
final staticOpportunities = rawOpportunities.map((opp) {
final localMeta = catalog.findByName(opp.target.skillName);
if (localMeta != null) {
Expand Down
6 changes: 4 additions & 2 deletions packages/dash_discover/lib/src/models.dart
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ class SkillTarget {
this.localPath,
});

/// The basename identifier of the skill (e.g. `dart-migrate-to-checks-package`).
/// The basename identifier of the skill
/// (e.g. `dart-migrate-to-checks-package`).
String get skillName => path.split('/').last;

/// Full permalink or branch URL to the skill definition on GitHub.
Expand Down Expand Up @@ -122,7 +123,8 @@ class Opportunity implements Comparable<Opportunity> {

String get skill => target.skillName;

/// Resolved URI pointing to the local SKILL.md if installed, or upstream GitHub.
/// Resolved URI pointing to the local SKILL.md if installed, or upstream
/// GitHub.
Uri get resolvedUri => target.resolvedUri;

/// Whether this opportunity's target skill is installed locally.
Expand Down
9 changes: 5 additions & 4 deletions packages/dash_discover/lib/src/outline_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ class OutlineGenerator {

/// Builds the markdown outline string.
String generate({int maxSignatureLines = 250}) {
final buffer = StringBuffer();
buffer.writeln('# REPOSITORY OUTLINE: $packageName');
buffer.writeln('Path: $packagePath\n');
final buffer = StringBuffer()
..writeln('# REPOSITORY OUTLINE: $packageName')
..writeln('Path: $packagePath\n');

_appendPubspec(buffer);
_appendDirectoryStructure(buffer);
Expand Down Expand Up @@ -113,7 +113,8 @@ class OutlineGenerator {
buffer.writeln(trimmed);
if (raw.length > maxSignatureLines) {
buffer.writeln(
'\n... [${raw.length - maxSignatureLines} signature lines truncated for token efficiency] ...',
'\n... [${raw.length - maxSignatureLines} signature lines '
'truncated for token efficiency] ...',
);
}
return;
Expand Down
22 changes: 9 additions & 13 deletions packages/dash_discover/lib/src/package_facts.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class ParsedSource {
/// not part of the package's public API.
bool get isImplementation =>
relativePath.startsWith('lib/src/') ||
relativePath.startsWith('lib\\src\\');
relativePath.startsWith(r'lib\src\');

/// 1-based line number for [offset], for human-readable evidence.
int lineOf(int offset) {
Expand Down Expand Up @@ -186,18 +186,14 @@ class PackageFacts {
final sources = <ParsedSource>[];
for (final file in files) {
final content = context.readContent(file);
final CompilationUnit unit;
try {
unit = parseString(
content: content,
path: file.path,
throwIfDiagnostics: false,
).unit;
} on ArgumentError {
// Unparseable source (e.g. a future language feature this analyzer
// does not know). Skipping is correct: no facts beats wrong facts.
continue;
}
// `throwIfDiagnostics: false` keeps syntactically broken sources (e.g.
// a future language feature this analyzer does not know) from throwing;
// the recovered partial AST is still useful for fact extraction.
final unit = parseString(
content: content,
path: file.path,
throwIfDiagnostics: false,
).unit;
sources.add(
ParsedSource(
file: file,
Expand Down
9 changes: 6 additions & 3 deletions packages/dash_discover/lib/src/rule.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import 'dart:io';
import 'context.dart';
import 'models.dart';

/// Base contract for a discovery rule aligning project signals with an upstream skill.
/// Base contract for a discovery rule aligning project signals with an
/// upstream skill.
abstract class DiscoveryRule {
const DiscoveryRule();

Expand All @@ -15,7 +16,8 @@ abstract class DiscoveryRule {
/// Architectural category organizing this rule.
RuleCategory get category;

/// Skill lifecycle type: one-time migration, periodic hygiene, or architecture.
/// Skill lifecycle type: one-time migration, periodic hygiene, or
/// architecture.
SkillLifecycle get lifecycle;

/// Concise summary of what this rule detects.
Expand All @@ -27,7 +29,8 @@ abstract class DiscoveryRule {
/// Fast precondition check before running full evaluation.
bool appliesTo(PackageContext context) => true;

/// Evaluates the package and yields any discovered modernization opportunities.
/// Evaluates the package and yields any discovered modernization
/// opportunities.
Iterable<Opportunity> evaluate(PackageContext context);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,11 @@ final class ChecksMigrationRule extends DiscoveryRule {
confidence: defaultConfidence,
affectedCount: context.testFiles.length,
diagnosis:
'Repository depends on `package:test` but does not use `package:checks`.',
'Repository depends on `package:test` but does not use '
'`package:checks`.',
prescription:
'Migrate test assertions from legacy `expect(actual, matcher)` to fluent, strongly-typed `check(actual)...` chains.',
'Migrate test assertions from legacy `expect(actual, matcher)` to '
'fluent, strongly-typed `check(actual)...` chains.',
evidence: [
'pubspec.yaml lacks checks dependency',
...context.testFiles.take(3).map(context.relativePath),
Expand Down
3 changes: 2 additions & 1 deletion packages/dash_discover/lib/src/rules/cli_app_rule.dart
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ final class CliAppRule extends DiscoveryRule {
return null;
}

// 3. Thin Trampoline Guardrail (<30 non-empty lines delegating to package library)
// 3. Thin Trampoline Guardrail (<30 non-empty lines delegating to
// package library)
final lines = content.split('\n').where((l) => l.trim().isNotEmpty).length;
if (lines <= 30 &&
(content.contains('package:${context.packageName}/') ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ final class MatcherBestPracticesRule extends FileDiscoveryRule {
r'expect\(\s*[^,]+?\.length\b\s*,\s*([^,)]+)',
);

// Checks if the expected argument is a floating-point number (e.g. 25.0 or equals(25.0)):
// Checks if the expected argument is a floating-point number
// (e.g. 25.0 or equals(25.0)):
static final _floatPattern = RegExp(r'^(?:equals\(\s*)?\d+\.\d+');

// Matches map string key lookups:
Expand Down Expand Up @@ -57,7 +58,8 @@ final class MatcherBestPracticesRule extends FileDiscoveryRule {

@override
String get description =>
'Detects unidiomatic expect() assertions (e.g. expect(x.length, ...) or expect(x.isEmpty, true)).';
'Detects unidiomatic expect() assertions (e.g. expect(x.length, ...) '
'or expect(x.isEmpty, true)).';

@override
Confidence get defaultConfidence => Confidence.high;
Expand All @@ -73,11 +75,13 @@ final class MatcherBestPracticesRule extends FileDiscoveryRule {

@override
String get diagnosisTemplate =>
'{count} test file(s) use suboptimal expect() assertions instead of dedicated matchers.';
'{count} test file(s) use suboptimal expect() assertions instead of '
'dedicated matchers.';

@override
String get prescription =>
'Migrate to first-class matchers (hasLength, isEmpty, isNotEmpty, contains) for clearer assertion failure messages.';
'Migrate to first-class matchers (hasLength, isEmpty, isNotEmpty, '
'contains) for clearer assertion failure messages.';

@override
String? checkFile(File file, String content, PackageContext context) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ final class MockGenerationRule extends FileDiscoveryRule {

@override
String get description =>
'Detects handwritten fake or mock class definitions without mockito or mocktail.';
'Detects handwritten fake or mock class definitions without mockito '
'or mocktail.';

@override
Confidence get defaultConfidence => Confidence.medium;
Expand All @@ -48,7 +49,8 @@ final class MockGenerationRule extends FileDiscoveryRule {

@override
String get prescription =>
'Automate test stubbing using `@GenerateMocks` or `mocktail` to avoid manual maintenance of interface stubs.';
'Automate test stubbing using `@GenerateMocks` or `mocktail` to avoid '
'manual maintenance of interface stubs.';

@override
String? checkFile(File file, String content, PackageContext context) {
Expand Down
Loading
Loading