new-feature
Skill0 starsChanged 51 days agoMITView on GitHub
End-to-end generator for a whole new feature — domain entity, data-provider interface, repository, data implementation, composition-root wiring, UI Cubit and screen, plus a test at every layer — following AGENTS.md section 7's exact sequence; use when building a new feature from scratch across all three packages.
41 other projects use a version of this.
Adopted by
Most recently taken up by
- NemesLaszlo/claude-code-harnesstheir own version, first seen Nov 8, 2026
- ice-lfernandes/claude-spring-architecttheir own version, first seen Nov 7, 2026
- nerviz-ai/nerviztheir own version, first seen Nov 7, 2026
- michaelshimeles/rstacktheir own version, first seen Nov 1, 2026
- qbicus/skillstheir own version, first seen Oct 25, 2026
- ricci-labs/twisetheir own version, first seen Oct 20, 2026
Used with
Projects with it also use evidence-driven-testing, greploop-apps and 10 more.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
jaydubya818Skillevidence-driven-testing
Capture observable runtime proof with video, screenshots, or measured output. Use when a change needs evidence beyond test reports.
Also in 6 projects that use this
jaydubya818Skillgreploop-apps
Run greploop through @greptile-apps when the normal trigger rejects a large PR. Use only with the companion greploop skill.
Also in 4 projects that use this
VercelSkillbefore-and-after
Add existing screenshots or screen recordings to a GitHub pull request as a before/after or preview block. Use when a PR needs visual media attached to its description. Browser navigation and capture belong to agent-browser.
Also in 6 projects that use this
BboyfaroukSkillgreploop
Iteratively improves a PR until Greptile gives it a 5/5 confidence score with zero unresolved comments. Triggers Greptile review, fixes all actionable comments, pushes, re-triggers review, and repeats. Use when the user wants to fully optimize a PR against Greptile's code review standards.
Also in 6 projects that use this
u9401066Skillbug-fix
Structured bug fixing workflow with root cause analysis and regression prevention. Triggers: BF, 修 bug, fix bug, debug, 除錯, 錯誤, error, 問題, issue, 修復, fix, 故障, fault, 異常, exception, 失敗, failure, 不能用, broken, 壞掉.
Also in 3 projects that use this
SvampenSkillinit-project
Generates a tailored .claude/project-config.md for the current project. Asks about project type, quality check commands, documentation paths, insight tags, and review strictness.
Also in 4 projects that use this
- +1 more from jaydubya818
Other versions41 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 41
- jhd3197/ServerKit★ 1.8ksame name
- michaelshimeles/rstack★ 1.4ksame name
- michaelshimeles/skills★ 1.4ksame name
- fpindej/netrock★ 234same name
- X-isdoingreat/canvas-pilot★ 123same name
- AnjanJ/rails_error_dashboard★ 93same name
- dan-the-dev/xp-developer-skills★ 31same name
- Fadi88/one-man-startup★ 16same name
- ice-lfernandes/claude-spring-architect★ 11same name
- nerviz-ai/nerviz★ 11same name
31 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository35 other files it gives its agents.
- AGENTS.mdAGENTS.md
- bootstrap-projectSkill
- choose-data-stackSkill
- code-reviewSkill
- conventional-commitsSkill
- data-providerSkill
- domain-modelingSkill
- error-handlingSkill
- flavors-and-flagsSkill
- flutter-fvmSkill
- localizationSkill
- new-featureSkill
- quality-gateSkill
- routingSkill
- state-managementSkill
- testingSkill
- themingSkill
- ui-screenSkill
- bootstrap-projectSkill
- choose-data-stackSkill
- code-reviewSkill
- conventional-commitsSkill
- data-providerSkill
- domain-modelingSkill
- error-handlingSkill
- flavors-and-flagsSkill
- flutter-fvmSkill
- flutter_fvmSkill
- localizationSkill
- quality-gateSkill
- routingSkill
- state-managementSkill
- testingSkill
- themingSkill
- ui-screenSkill
The file
We split this skill into the rules it gives an agent
5 rules. 0 are common, 1 is distinctive, and 4 are very specific to this project.
0
Common
Most of the 39 edited versions we read keep these.
1
Distinctive
This author's own choices. Worth a look.
4
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 15
Treat every file below as a template to adapt to your real entity — do not copy the shape of an unrelated app's repository class.
- Very specific: line 23
Run .agents/skills/choose-data-stack/SKILL.md first if packages/data has no persistence dependency yet.
- Very specific: line 63
Update the barrel: packages/domain/lib/src/entities/entities.dart
- Very specific: line 163
Test: packages/domain/test/repositories/note_repository_test.dart, using a hand-written fake (packages/domain/AGENTS.md → "Testing": no …
- Very specific: line 360
Update test/app_test.dart/test/widget_test.dart call sites that construct App(...) directly to pass the new required noteRepository …
“Common” is measured: kept by at least 6 in 10 of the 39 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Building a new feature end to end
This is the generator AGENTS.md section 7 and section 19's skill index point to. It walks the full sequence — domain entity → data-provider interface → repository → data implementation → composition root → UI Cubit → UI screen — with a working example at every step, plus the test each step requires (AGENTS.md section 6: every new entity/provider/repository/Cubit/widget/ route gets a test in the same change).
The worked example builds a Note feature: create, list, and delete a note.
Treat every file below as a template to adapt to your real entity — do
not copy the shape of an unrelated app's repository class. Mirror
FeatureFlagRepository
(packages/domain/lib/src/repositories/feature_flag_repository.dart)
instead — a plain constructor-injected class, no interface, no use-case
class (AGENTS.md section 1 and section 20 both forbid a repository
interface in domain).
Run .agents/skills/choose-data-stack/SKILL.md first if packages/data has
no persistence dependency yet. The data-layer step below assumes Drift is
installed; swap in whichever stack you chose.
1. Domain entity
packages/domain/lib/src/entities/note.dart:
import 'package:equatable/equatable.dart';
const _unset = Object();
/// A single freeform note. See `.agents/skills/domain-modeling/SKILL.md`
/// for the `copyWith`/sentinel pattern used below.
final class Note extends Equatable {
const Note({
required this.id,
required this.title,
required this.body,
required this.updatedAt,
});
final String id;
final String title;
final String body;
final DateTime updatedAt;
Note copyWith({String? title, Object? body = _unset}) => Note(
id: id,
title: title ?? this.title,
body: identical(body, _unset) ? this.body : body as String,
updatedAt: updatedAt,
);
@override
List<Object?> get props => [id, title, body, updatedAt];
}Update the barrel: packages/domain/lib/src/entities/entities.dart
library;
export 'feature_flag.dart';
export 'note.dart';Test: packages/domain/test/entities/note_test.dart
import 'package:domain/domain.dart';
import 'package:test/test.dart';
void main() {
group('Note', () {
final note = Note(id: '1', title: 'Title', body: 'Body', updatedAt: DateTime(2026));
test('copyWith with no arguments returns an equal copy', () {
expect(note.copyWith(), note);
});
test('copyWith(title:) changes only title', () {
expect(note.copyWith(title: 'New title').title, 'New title');
});
});
}2. Domain data-provider interface
packages/domain/lib/src/data_providers/note_data_provider.dart:
import '../entities/note.dart';
abstract interface class NoteDataProvider {
Future<List<Note>> getAllNotes();
Stream<List<Note>> watchAllNotes();
Future<Note> createNote({required String title, required String body});
Future<void> deleteNote(String id);
}createNote returns the created Note rather than taking one, because id
assignment belongs to the data provider, not its caller — this template has
no dedicated id-generation abstraction; a provider that needs one picks
whatever fits its stack (uuid, the database's own autoincrement/
gen_random_uuid(), ...) internally, since that's an infrastructure detail
(AGENTS.md section 2's dependency rule puts it squarely in packages/data).
Update the barrel: packages/domain/lib/src/data_providers/data_providers.dart
library;
export 'note_data_provider.dart';See .agents/skills/data-provider/SKILL.md for the full rationale (why
abstract interface class, why entity-only signatures).
3. Domain repository
packages/domain/lib/src/repositories/note_repository.dart:
import '../data_providers/note_data_provider.dart';
import '../entities/note.dart';
/// Constructor-injected with the [NoteDataProvider] interface, mirroring
/// `FeatureFlagRepository`'s shape — a concrete class, never an interface
/// (AGENTS.md section 1). Holds the orchestration a use case would
/// otherwise hold; here, that's nothing more than delegation, but a real
/// repository combining two providers or applying a rule before persisting
/// belongs here, not in a Cubit.
final class NoteRepository {
const NoteRepository(this._dataProvider);
final NoteDataProvider _dataProvider;
Future<List<Note>> getAllNotes() => _dataProvider.getAllNotes();
Stream<List<Note>> watchAllNotes() => _dataProvider.watchAllNotes();
Future<Note> createNote({required String title, required String body}) =>
_dataProvider.createNote(title: title, body: body);
Future<void> deleteNote(String id) => _dataProvider.deleteNote(id);
}Update the barrel: packages/domain/lib/src/repositories/repositories.dart
library;
export 'feature_flag_repository.dart';
export 'note_repository.dart';packages/domain/lib/domain.dart itself needs no edit — it already exports
the four top-level barrels (data_providers.dart, entities.dart,
failures.dart, repositories.dart), which now each carry the new file.
Test: packages/domain/test/repositories/note_repository_test.dart, using a
hand-written fake (packages/domain/AGENTS.md → "Testing": no mocking
package here, domain/pubspec.yaml has no mocktail):
import 'package:domain/domain.dart';
import 'package:test/test.dart';
final class FakeNoteDataProvider implements NoteDataProvider {
final Map<String, Note> _notes = {};
final _controller = StreamController<List<Note>>.broadcast();
var _count = 0;
@override
Future<List<Note>> getAllNotes() async => _notes.values.toList();
@override
Stream<List<Note>> watchAllNotes() => _controller.stream;
@override
Future<Note> createNote({required String title, required String body}) async {
final note = Note(id: 'fake-${_count++}', title: title, body: body, updatedAt: DateTime(2026));
_notes[note.id] = note;
_controller.add(_notes.values.toList());
return note;
}
@override
Future<void> deleteNote(String id) async {
_notes.remove(id);
_controller.add(_notes.values.toList());
}
}
void main() {
group('NoteRepository', () {
test('createNote then getAllNotes returns the created note', () async {
final repository = NoteRepository(FakeNoteDataProvider());
final note = await repository.createNote(title: 'Title', body: 'Body');
expect(await repository.getAllNotes(), [note]);
});
});
}(add import 'dart:async'; for StreamController).
4. Data implementation
packages/data/lib/src/data_providers/drift_note_data_provider.dart — see
.agents/skills/data-provider/SKILL.md section 2 for the full Drift
skeleton (table definition, guard/guardStream wrapping, private
_mapToEntity/_mapFromEntity). In short:
final class DriftNoteDataProvider implements NoteDataProvider {
DriftNoteDataProvider(this._db, [Uuid? uuid]) : _uuid = uuid ?? const Uuid();
final AppDatabase _db;
final Uuid _uuid;
@override
Future<List<Note>> getAllNotes() =>
guard(() async => (await _db.select(_db.notes).get()).map(_mapToEntity).toList(),
mapper: mapDriftError);
@override
Stream<List<Note>> watchAllNotes() => guardStream(
() => _db.select(_db.notes).watch().map((rows) => rows.map(_mapToEntity).toList()),
mapper: mapDriftError,
);
@override
Future<Note> createNote({required String title, required String body}) => guard(() async {
final note = Note(id: _uuid.v4(), title: title, body: body, updatedAt: DateTime.now());
await _db.into(_db.notes).insert(_mapFromEntity(note));
return note;
}, mapper: mapDriftError);
@override
Future<void> deleteNote(String id) =>
guard(() => (_db.delete(_db.notes)..where((t) => t.id.equals(id))).go(), mapper: mapDriftError);
Note _mapToEntity(NotesData row) =>
Note(id: row.id, title: row.title, body: row.body, updatedAt: row.updatedAt);
NotesCompanion _mapFromEntity(Note note) => NotesCompanion.insert(
id: note.id,
title: note.title,
body: note.body,
updatedAt: note.updatedAt,
);
}Uuid comes straight from package:uuid (add uuid: ^4.5.1 to
packages/data/pubspec.yaml if your chosen stack didn't already pull it
in) — imported and called directly here, no wrapper abstraction, because
packages/data is exactly where an external id-generation package is
allowed to live (AGENTS.md section 2).
Update the barrel: packages/data/lib/src/data_providers/data_providers.dart
library;
export 'drift_note_data_provider.dart';packages/data/lib/data.dart needs no edit — it already exports
src/data_providers/data_providers.dart.
Test: packages/data/test/data_providers/drift_note_data_provider_test.dart
— see .agents/skills/choose-data-stack/SKILL.md's Drift test-harness
example (NativeDatabase.memory()).
5. Composition root wiring
lib/bootstrap.dart — instantiate the database, the data provider, then the
repository, inside the existing bootstrap() function:
void bootstrap() {
runZonedGuarded(
() {
WidgetsFlutterBinding.ensureInitialized();
FlutterError.onError = (details) {
FlutterError.presentError(details);
_reportUncaughtError(details.exception, details.stack ?? StackTrace.current);
};
final featureFlagRepository = FeatureFlagRepository(
FeatureFlags(featureFlagDefaultsFor(AppEnvironment.flavor)),
);
final database = AppDatabase();
final noteDataProvider = DriftNoteDataProvider(database);
final noteRepository = NoteRepository(noteDataProvider);
runApp(
App(
router: buildAppRouter(),
featureFlagRepository: featureFlagRepository,
noteRepository: noteRepository,
),
);
},
_reportUncaughtError,
);
}lib/app.dart — add a constructor parameter and a matching
RepositoryProvider.value:
class App extends StatelessWidget {
const App({
required this.router,
required this.featureFlagRepository,
required this.noteRepository,
super.key,
});
final GoRouter router;
final FeatureFlagRepository featureFlagRepository;
final NoteRepository noteRepository;
@override
Widget build(BuildContext context) => MultiRepositoryProvider(
providers: [
RepositoryProvider<FeatureFlagRepository>.value(value: featureFlagRepository),
RepositoryProvider<NoteRepository>.value(value: noteRepository),
],
child: MultiBlocProvider(
providers: [
BlocProvider(create: (context) => FeatureFlagCubit(context.read<FeatureFlagRepository>())),
],
child: MaterialApp.router(
onGenerateTitle: (context) => context.l10n.appTitle,
theme: AppTheme.light,
darkTheme: AppTheme.dark,
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
routerConfig: router,
debugShowCheckedModeBanner: false,
),
),
);
}NoteRepository is not added to the MultiBlocProvider list — it's a
repository, exposed via RepositoryProvider, not a Cubit. Only app-wide
Cubits (like FeatureFlagCubit) go in MultiBlocProvider here; the
NoteListCubit built in step 6 is screen-scoped and created inside its own
screen instead (AGENTS.md section 8).
Update test/app_test.dart/test/widget_test.dart call sites that
construct App(...) directly to pass the new required noteRepository
parameter, or they will fail to compile.
6. UI Cubit
packages/ui/lib/src/features/notes/bloc/note_list_state.dart:
import 'package:domain/domain.dart';
import 'package:equatable/equatable.dart';
sealed class NoteListState extends Equatable {
const NoteListState();
@override
List<Object?> get props => [];
}
final class NoteListLoading extends NoteListState {
const NoteListLoading();
}
final class NoteListLoaded extends NoteListState {
const NoteListLoaded(this.notes);
final List<Note> notes;
@override
List<Object?> get props => [notes];
}
final class NoteListError extends NoteListState {
const NoteListError(this.failure);
final DomainFailure failure;
@override
List<Object?> get props => [failure];
}packages/ui/lib/src/features/notes/bloc/note_list_cubit.dart:
import 'dart:async';
import 'package:domain/domain.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'note_list_state.dart';
/// Screen-scoped Cubit — created inside `NotesScreen.build` (step 7), not
/// provided app-wide, since only that screen needs it (AGENTS.md section 8).
class NoteListCubit extends Cubit<NoteListState> {
NoteListCubit(this._repository) : super(const NoteListLoading()) {
_subscription = _repository.watchAllNotes().listen(
(notes) => emit(NoteListLoaded(notes)),
onError: (Object error) {
if (isClosed) return;
emit(NoteListError(error as DomainFailure));
},
);
}
final NoteRepository _repository;
late final StreamSubscription<List<Note>> _subscription;
Future<void> addNote(String title, String body) =>
_repository.createNote(title: title, body: body);
Future<void> deleteNote(String id) => _repository.deleteNote(id);
@override
Future<void> close() {
unawaited(_subscription.cancel());
return super.close();
}
}Note the two AGENTS.md section 16 rules applied here: if (isClosed) return;
before the async emit in the error handler, and the stream subscription is
cancelled in close(). addNote delegates straight to the repository and
never picks an id itself — id assignment is DriftNoteDataProvider's job
(step 4), not something the UI layer should have an opinion on.
Barrel: packages/ui/lib/src/features/notes/bloc/bloc.dart
library;
export 'note_list_cubit.dart';
export 'note_list_state.dart';Test: packages/ui/test/features/notes/bloc/note_list_cubit_test.dart,
mocking the domain repository with mocktail (never the data provider
underneath it — packages/ui/AGENTS.md → "Testing"):
// packages/ui/test/features/notes/bloc/note_list_cubit_test.dart
import 'package:bloc_test/bloc_test.dart';
import 'package:domain/domain.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:ui/ui.dart';
class MockNoteRepository extends Mock implements NoteRepository {}
void main() {
late MockNoteRepository repository;
setUp(() {
repository = MockNoteRepository();
when(() => repository.watchAllNotes()).thenAnswer((_) => const Stream.empty());
});
blocTest<NoteListCubit, NoteListState>(
'emits NoteListLoaded when the repository stream emits notes',
setUp: () {
when(() => repository.watchAllNotes()).thenAnswer(
(_) => Stream.value([Note(id: '1', title: 'T', body: 'B', updatedAt: DateTime(2026))]),
);
},
build: () => NoteListCubit(repository),
expect: () => [isA<NoteListLoaded>()],
);
}7. UI screen
packages/ui/lib/src/features/notes/presentation/notes_screen.dart — public
NotesScreen creates the Cubit; private _NotesView consumes it, per
AGENTS.md section 8:
import 'package:domain/domain.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../theme/theme.dart';
import '../bloc/bloc.dart';
class NotesScreen extends StatelessWidget {
const NotesScreen({super.key});
@override
Widget build(BuildContext context) => BlocProvider(
create: (context) => NoteListCubit(context.read<NoteRepository>()),
child: const _NotesView(),
);
}
class _NotesView extends StatelessWidget {
const _NotesView();
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: Text(context.l10n.notesScreenTitle)),
body: Padding(
padding: const EdgeInsets.all(AppSpacing.pageMargin),
child: BlocBuilder<NoteListCubit, NoteListState>(builder: (context, state) => _buildBody(context, state)),
),
);
Widget _buildBody(BuildContext context, NoteListState state) => switch (state) {
NoteListLoading() => const Center(child: CircularProgressIndicator()),
NoteListLoaded(:final notes) when notes.isEmpty => Center(child: Text(context.l10n.notesEmptyMessage)),
NoteListLoaded(:final notes) => ListView.separated(
itemCount: notes.length,
separatorBuilder: (context, index) => const SizedBox(height: AppSpacing.gutter),
itemBuilder: (context, index) => ListTile(
title: Text(notes[index].title),
subtitle: Text(notes[index].body),
onTap: () => context.read<NoteListCubit>().deleteNote(notes[index].id),
),
),
NoteListError() => Center(child: Text(context.l10n.genericErrorMessage)),
};
}notesScreenTitle and notesEmptyMessage must be added to
packages/ui/lib/l10n/app_en.arb/app_es.arb and regenerated with
fvm flutter gen-l10n from packages/ui/ before this compiles — no
hardcoded user-facing string is allowed in packages/ui/ (AGENTS.md
section 11); genericErrorMessage already exists.
Barrels:
packages/ui/lib/src/features/notes/presentation/presentation.dart
library;
export 'notes_screen.dart';packages/ui/lib/src/features/notes/notes.dart
library;
export 'bloc/bloc.dart';
export 'presentation/presentation.dart';packages/ui/lib/src/features/features.dart
library;
export 'home/home.dart';
export 'notes/notes.dart';packages/ui/lib/ui.dart needs no edit — it already exports
src/features/features.dart, which now carries the new feature through.
Wire the screen into routing (see .agents/skills/routing/SKILL.md for the
full pattern) — e.g. add a second branch to the StatefulShellRoute in
packages/ui/lib/src/router/app_router.dart, or point the existing
AppRoute.home at NotesScreen if this is the app's first real feature.
Test: packages/ui/test/features/notes/presentation/notes_screen_test.dart,
using pumpApp (packages/ui/test/helpers/pump_app.dart) plus a mocked
NoteRepository and a RepositoryProvider<NoteRepository>.value wrapped
around it, following the same shape as
packages/ui/test/features/home/home_screen_test.dart.
8. Definition of done (AGENTS.md section 18)
fvm dart run tool/quality_gate.dartexits 0.Noteentity,NoteDataProvider,NoteRepository,DriftNoteDataProvider,NoteListCubit,NotesScreeneach have a test in this change.- No hardcoded user-facing string in
packages/ui/— every string goes throughcontext.l10n. - No
dataimport anywhere inpackages/ui/; noflutter/dataimport inpackages/domain/. - Every barrel updated:
entities.dart,data_providers.dart(domain),repositories.dart;data_providers.dart(data); the feature's ownnotes.dartandfeatures.dart. App's newnoteRepositoryparameter is threaded throughbootstrap.dartand every directApp(...)construction in tests.- Hand back once
fvm dart run tool/quality_gate.dartpasses. The orchestrating session — not you, if you're a non-delegating subagent likefeature-implementer— runs thecode-reviewskill's parallel-review protocol before the feature is declared finished.
Common mistakes
- Defining
NoteRepositoryas anabstract interface class— repositories are concrete in this architecture; only the data-provider layer gets an interface (AGENTS.md section 20). - Providing
NoteListCubitapp-wide inlib/app.dart'sMultiBlocProviderinstead of creating it insideNotesScreen— it's screen-scoped, not an app-wide Cubit likeFeatureFlagCubit. - Generating the note's id anywhere in
packages/ui(insideNoteListCubitor the screen) instead of lettingDriftNoteDataProvider.createNoteassign it — id creation is apackages/dataconcern (AGENTS.md section 2's dependency rule), not something the UI layer should pick a strategy for. - Forgetting to add the new required parameter to every direct
App(...)construction (test/app_test.dart,test/widget_test.dart), which is a compile error, not a runtime one. - Skipping the barrel-file update at any layer — an unexported file compiles
inside its own package but is invisible to
package:domain/package:data/package:uiimports from anywhere else, which surfaces as a confusing "not found" error one layer up. - Interpolating
e.toString()or a rawDomainFailure.causeinto the screen's error text instead of switching on the failure/state and using localized copy (AGENTS.md section 9).
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

