In this video, I recap on building with source generators using the Copilot CLI! It's been a lot of fun, and a great learning experience using Copilot CLI.
📄 Auto-Generated Transcript ▾
Transcript is auto-generated and may contain errors.
Hey folks, just headed to the office here on Monday. We're almost through the first month of 2026, which is crazy. Time flies. Um, I'm going to talk about AI. I did a couple videos this morning already on a bit of Reddit stuff. I had a submitted question. And I figured jump into some AI um just usage from you know my experience over the weekend and kind of the week prior which has been a lot of fun. Um I I made it kind of a goal for myself this year to make sure I'm trying different things. uh started to hit a bit of a a groove I would say in the tool usage and kind of my flows and all like I'm not complaining about that of course but I want to make sure that I'm putting some effort into trying different things and so I know uh like at the time I'm recording this and over the past few weeks like there's been been this like resurgence of like interest into Claude code.
Uh, I I've used Claude. I made videos on it, you know, over the course of last year. I think it's great. Uh, I'm just not a terminal user. And um, so while I think Claude is awesome, the more that some of the things that make it awesome show up in other AI tools or like the less reason I have to kind of force myself into a terminal. Um, with that said, a week or two ago or whatever I was doing, um, I kind of just like finally made the decision that I have this uh, dependency injection sort of, uh, helper framework. It's called Needler, and it kind of brings together some different things that I like to use for dependency injection around plugins and things like that. Uh, it's all reflection based for C. And I really wanted to um to change it over to be source generated.
And I talked about this in another video. You might have seen it, you might not have. It was on code commute. And if you're not familiar with the words I'm saying because you're not a C developer, don't worry. Reflection is something where we can do type inspection at runtime. And that way we can look across assemblies. we can look for plugins, dynamically load them up. But a lot of the the plug-in frameworks that I like to build, uh, pardon me, like the plug-in architectures in my applications and services, a lot of the time it's just how I like to organize code. I don't actually need the the um the benefits or the the usage around like dynamically loading them anytime after startup. I have to clear my throat. Pardon me. I don't know what's going on there. Um, so the reality is I know a lot of that information at compile time.
Uh, most of the things I build, I know all of it at compile time. So if I can move to source generation, then instead of looking at type information at runtime, I can literally do all of that work at compilation time. and then uh that would be tedious and crappy for me to do as a developer by hand. But if I can have source generators do it, then it can write a whole bunch of code that I would really not want to write and it does it automatically. Um so I I figured this would be a really good exercise. I use clawed code to do some of it. I ran out of my limit, uh which was I guess to be expected. Um, I blew through it, you know, in for the the different intervals very quickly and then it was like I used it up for the month.
Uh, which reminds me, I think I'm reset again, so maybe I'll go back to trying it out. But it reminded me, hey, there is a co-pilot CLI and was funny because I use co-pilot in the terminal. Um, once I got in the sort of the the groove of using it, I was really enjoying it, which is saying a lot because I really like building things in an IDE. But I was really enjoying myself with the Copilot CLI. Um, I was like, "Okay, great. I'll do some YouTube videos on this." So, by the way, if you want to see actual videos of me going through this stuff, um, you can check out my main channel, which is Dev Leader. And so I wanted to put the YouTube videos together and stupidly I had restarted my computer to update and lost all of the terminal chat. So um anyway, I made the YouTube videos.
Uh one went up last week just a brief intro for model selection and co-pilot CLI. This week I'll have them where I'm actually talking about um how I was using it. But it was really enjoyable. And one of the the hilarious things just about like how fast this stuff moves in the video which I recorded whatever last week or something. In the video I was talking about how Claude code has a slash plan mode and I was like hey like I don't have that in the co-pilot CLI but like you can still plan with it. You just talk to it and plan right? And so, and it worked really, really well. Um, hilariously, like I was posting the video this morning before driving to work here and I was like, "Wait a second." Like, I I'm positive I've been using plan mode in in Copilot CLI.
So, like as I'm watching my video, I try I show it and I'm like there is no slash plan in the co-pilot CLI, but like over the last week they've added it and I've literally been using it which is like it feels ridiculous because it just recorded this video and like a day later like I was already using it. Um, which is cool. Like it moves really fast. One of the reminders I want to have for folks is like, you know, this stuff's going to continue to move really fast for the tooling. Um, there's so many quality of life things that we'll be getting across these different tools and stuff like that. So, um, it's one of the reasons I'm trying to make myself use them, kind of explore and see what's uh, what's interesting across them.
So yeah, I I used, you know, co-pilot CLI the prior weekend and then made the videos on it and then this past week, this is kind of what I'm going to talk about this past week and this past weekend I was doing more with it because it felt really good to be building out this library um with this CLI tool. And I was trying to put my finger on why because it's kind of weird, right? I'm self-proclaimed a person who is very very comfortable in Visual Studio. I like having an IDE. I don't like using a terminal. So like why am I using a terminal tool? Absolutely stupid. Sorry. Um, someone with a trailer is going so slow. Not okay. Um, why am I using a terminal to go build this stuff when I am very comfortable with an IDE?
And I I think one of the reasons in this particular case is that I'm building a library and when I'm building features in my other code bases like I am more often it's like either a feature I can delegate to co-pilot to go build for me like in the cloud and then if I need to then I can kind of pull the code down, run it, step through it, play with it. um or I I want to um use the feature itself like in in the IDE and like play with it. But I found like with this library development, it felt okay to to be in the terminal. Now, there's a bit of a catch and I'm going to try to, you know, get there, but um cuz there were some some things that came up this weekend where I was like, "Okay, there it Um, it I knew it couldn't have been this smooth this whole time.
So, spoiler alert. But over the course of the week, I was taking my my new functionality in needler, which now means that it supports reflection because reflection doesn't need to go away. There's actually many scenarios where if you do want to dynamically load plugins, you can absolutely use reflection, right? we there might be situations where we don't know everything at compile time. So you still want to use reflection. So I didn't get rid of it. I just have that as an option. And then source generation is the other option that we have. And um I think there's a lot of what's a good way to compare these things. I think there are situations where we cannot use source generation and so you need to rely on reflection.
So I maintain that and then I think with source generation there's a lot of really cool things we can build where you might say oh like I would have not liked to do that by hand uh or if I structure code this way and we can do some source generation then it's kind of like you would have had to copy and paste patterns by hand manually which would have sucked and now we can just do that for you. So I think both paths make sense to maintain but the source generation part for me is super cool because I can start to think about solving problems in different ways. Right? So um again I'll give you another completely different example of source generation. There is a a library I really like to use called strongly typed IDs.
And so just to give you an example of this, you might have identifiers for for various different things in your applications like could be a username, could be a user ID, and maybe your user ID is a string or maybe your user ID is a like 64-bit integer or it's a it's a gooid. um you you have these primitive types to use and sometimes that can make code less um less verbose because it's like you see string username or you see like long username um and or user ID sorry and like just because you have this primitive type it means like the in my opinion like the code becomes a little bit less verbose you can make accidents where you're passing certain things that you're not supposed to there's there's lots of like little quality of life things that I think that you get if you just had a dedicated strongly typed ID.
And so what would be a real big pain in the ass in my opinion is if every time you wanted one of these, you had to go define the entire primitive type because there's a lot of things like parsing that you'd have to go do or comparisons and or whatever else. And if you had to go do that by hand for every strongly typed ID, you'd be like, well, it's it's basically it's not worth doing cuz it would be such a pain in the ass to go maintain and copy and paste these things. But this library called strongly typed ID, I think it's by Andrew Lockach, you just put an attribute on a type, like you basically give it a name and then put this attribute on it and say it's backed by a string or a long or a GID, whatever. And it it source generates the whole thing for you in a very consistent way.
It's very reusable and it's it's awesome. So like that's just a whole thing that like basically with a source generator you can unlock functionality like that. So uh I I'm trying to personally kind of wrap my head around like what types of what are new ways that I can go build software this way. I'll give you another example. Um, I have this role playing game that I I kind of always go back to over the years to to keep building because it's a it's a really good playground for like trying things out. And uh, one of the more recent, this is still going back a couple years, one of the more recent attempts at working with this thing, I was like, "Hey, we need to start actually like pulling content into the game because I just keep building systems cuz I really enjoy doing that." but like let's actually, you know, it's a role playing game.
There's a huge focus on itemization, you know, having different types of enchantments on weapons and armor and stuff like that. And I wanted a lot of really cool complexity that way. And I said, let's actually put the content in the game so we could start, you know, generating items and see the stuff come to life. But I'm a I'm kind of like a systems person. So I'm like I'm not just going to hardcode like you know two attributes and two enchantments and go cool it works. Like I'm like no I want I want to have a system where I can like I can you know add a row to a table uh and and you know kind of just continue to add data very uh trivially. And so when I went to go look for building some tools around this, I'm like honestly like a spreadsheet is literally the most convenient thing here so I can write formulas and stuff and I'm like that works super well instead of building a custom tool.
So I did that. I used uh sheets and the the way that that integrates with the codebase is that like I I basically used my own like poor man's source generation to go like you know it runs a script and it goes and pulls this data from the sheets and it writes out all this code to go you know in code load up all your item data. Now, could this go in a database? Like, sure. But like, if I were doing that, it's basically from my Google sheet or some type of spreadsheet into a database to then go load up later. And instead, it was just like I went right from my spreadsheet into like the stuff's in memory. Is that going to scale to keep it all in memory? Like, I don't know. Probably not, but maybe. So anyway, point is that that was like a poor man's source generator.
I kind of rolled that myself. Um, and so it's just an example of like you have data and like is there a way that we can generate code? Because if you were to look at what was generated, you'd be like, dude, I would never type that out by hand. It's craziness. But when it's automatically done for you, like having it in code is actually really cool and handy because you have literally first class type information around all of these entities that you're pulling into the game. Like anyway, um, a lot of interesting benefits from that. So with source generation, which with being able to dynamically create code at at compile time, there's just a lot of stuff that my brain hasn't even like registered for what we can do. And so I'm going to wrap up that thought with one example from this weekend and then I'm going to go back to some of the other challenges I had.
So, um, one of the interesting features, uh, that a a library called Autofac has is that if you have, it's going to be kind of hard to explain this without a code example, but if you have a type and you're doing dependency injection with parameters passed in through constructors, if you had three parameters on the constructor and two of them can automatically be resolved and one of them can't. So let's say like the third parameter is a string and resolving just a random string from a dependency container is kind of nuts. Uh it doesn't like which string, right? Like if you can only register one string, which string is it going to be? Uh so it kind of makes it so that all of a sudden that type isn't really useful for being automatically resolved. you'd kind of have to manually construct it, which isn't the end of the world.
But if you had two parameters or more that can be resolved automatically, wouldn't it be really convenient to only have to specify the one that can't be automatically resolved? So, Autofac had a pattern for this, which is pretty handy. So, you could make a like a static factory method on your type. you only need to specify the stuff that cannot be handled by dependency injection. So, it's super cool. And then you could use this um this factory method wherever you wanted to go build this thing. So, like it's pretty neat. Um and that factory method you could resolve from the dependency container, right? So, that that's the the other part is that it automatically makes a factory method that you can resolve. you'd have to declare the signature, but now that means that you can resolve it for your other types. Kind of weird to explain.
Apologies. It's way easier with a a video. Maybe I'll make a video on this when this feature is done. So, um, in terms of source generation, I was having this conversation with co-pilot cuz it was like, hey, you know, here's maybe a feature gap that you want to think through. And I was like, well, what problem are we solving here? Right? like it sees it as a feature gap cuz it's trying to do some comparison to other dependency injection libraries. So, I'm I'm saying to it like, "Hey, yeah, we could go do this, but what's the benefit?" And I went back and forth with co-pilot a little bit and I could tell that it's like at this point in our our planning conversation, it doesn't really know or have a good suggestion. And it asked me a good question, right? It said like, "Do you even have a use case for this?
Like, could you give me an example of where you think any of this might be beneficial?" And I was like, "Okay, it's like it's a good question because I'm basically putting it back on it to be like, prove to me, prove to me that there's value." And it's like, it's kind of saying like, "Dude, I don't even know if there is." So, like, have you even thought about this or are we literally just talking about it because I, as co-pilot, brought it up? And I thought through and I said, you know what? What might be really cool is if we can use source generation and automatically make a factory type. So autofac um you define the signature of the factory. So you have to write it. You have to still manually register your type onto the service container. It's two manual steps. And then now you have access to the factory method when you want to use it.
So that part is automatic. And I was thinking it would be really cool to not even have to declare the signature yourself. Just kind of mark a type some way, put an attribute on it, whatever. And then it would be really really neat if it could literally source generate a dedicated factory type. Not even just like a funk or like a a call back, a delegate, not even that, but if it was a type and it just felt like you automatically had a full-on type and you could do that with source generation, I think that would be super neat. Um, is that better than just having a delegate? Like, I don't know. Like, maybe not. But I know in my own code I find myself manually making these factory classes for this exact situation. So, what if what if I didn't have to? So, just an example of me thinking through some source generation stuff.
And all of this is possible. Um, not because I spent, you know, years working with source generators. It's the opposite. Like I know very little about source generators and I've been relying on AI to build this for me. So anyway, um I'll probably make a follow-up video on Dev Leader once that functionality is all working. I think it's pretty neat. Um, but that's kind of where I got to with Needler is I'm kind of adding in these these new like quality of life things that I was like I just wouldn't have ever done before because I'm like it's it's just more work. It's a pain in the ass and I don't really need it. Now I can kind of play and it's it's really fun. So to bring it back to one of the other challenges that I encountered. So, as I was just describing, I kind of reached this point where I'm like, "Hey, Needler's doing a lot of the stuff that I really set out to go do with the source generation.
I started adding in things like analyzers. I'm trying to make it so that people are like, if you want to use it, you're on rails, right? Like, it's really hard to mess it up. I don't want you to have a foot gun to shoot yourself in the foot. I would really just like to make sure that it feels obvious to use for your use cases, right? It's going to be opinionated by default and if I can provide obvious ways for you for you to change that, great. But that's my goal, right? And so I've been building some features and quality of life things like this. And I reached a point, started noticing it like right at the end of last week. Um, but I reach this point where I'm like, hold on. Like, you know, I see that we're we're iterating on this new feature and I'm noticing like in my mind I'm like, I think we have to go back and touch this other feature to make this thing work.
And co-pilot's confidently telling me it has test passing now. It's implemented the new feature. And I'm like, this seems off. Like something's odd to me here because I don't think there's any way you could have done this properly. Come on, Tesla. What are you doing? I need to get in this lane. Like, I don't think there's any way you could have done this properly um without touching the other feature. And here you are telling me you have uh passing tests, you're ready to commit. Like something's just doesn't smell right. And then I noticed the age-old pattern that AI pulled over my eyes and I should have seen it coming, but a lot of um scenarios that it was building out and testing um it was creating test data, test scenarios that weren't based on reality. And so I am like, you know, full transparency, I've been Oh man, what are you doing, buddy?
You're going the wrong way. Um, I've been coding for a long time and like I'm not sitting here claiming like I'm the best programmer in the world, but like I am aware that when AI is building things, like I need to be pretty vigilant. I need to make sure that I'm actually looking at the tests. And I feel like I have been doing a a good job of that. But this is just evidence that at the the pace that I was moving, getting AI to build features for me, I got lost in the sauce. I missed it. And I think it's two parts. One is like the rate of development. It's probably more than two parts, but two parts that come to mind are the rate of development because it's able to do the feature iteration so fast, right? Um, it has the test. So, like it's that's one part and the second part is that it's building in a space that I am not familiar in.
Right? I' I've just been talking about how it's building source generation stuff out and I'm not familiar with all the intricacies of source generation. So when it's creating tests, I know for source generation, there's going to be scenarios where it's it's going to sound really meta, but like it's writing code like C code into a string because it's like I need to go compile this string or I need to prove that we're parsing C code to go generate the right thing. like it needs to run a generator. It's weird, right? Um kind of meta. And so I would see code like this and I see the tests, I see them passing, I see the scenarios and I'm like this makes sense to me, but I'm not realizing that some of the scenarios like I'm just not paying close enough attention is the reality, right? Like it's definitely on me.
And when you compound this because you're still moving fast and you're building more features on top to give you an example of what was happening um I wanted to build it's called an analyzer in inn net and it's basically like a powerful llinter. Okay. So you can build analyzer rules out. I've made more videos and stuff on this. Um, and so it looks at stuff at compile time and it can try to catch uh things that compile, but it's like, hey, this is probably a pattern you don't want to use. And so I was building out these analyzers in Needler to really guide people to do the right thing. Okay, so it's writing tests for the analyzers. I'm like, this is great. Um, but what I noticed is that it was doing things like, oh, you know, let's check when this attribute is present and when it's not, and then we can warn people to use it versus not, or like we can make more informed decisions.
We can really guide the users. And in my head, I'm like, this is this is awesome because it's it is truly going to help guide them. What I didn't realize is that the attributes it's talking about don't even exist. So, it built all of these safety nets and things on things that don't exist and then wrote tests by defining the attributes in the tests. So, you as a user, a consumer of Needler, you couldn't even use the attributes that it was talking about cuz they literally don't exist. And how the analyzers work is it's looking for the names of the attributes. And so like if you manage to declare your own attribute with the same name, then it would work. But it built all of these safety nets on things that literally do not exist outside of its test code. And so that's a very very misleading kind of thing.
Um, and so I spent a bunch of time this weekend trying to sort of rectify this, trying to find more spots where it's doing this. And um, you know, the problems compounded because because of things like analyzers and source generators that are kind of like a little bit meta, right? Like in some situations, it doesn't have a reference to the type definition. And it's like I I know it must exist. Let's use it by name here, which is fine for what it's doing, but it's brittle. Like I it literally created this situation that was very fabricated. Um so yeah, I I basically spent a lot of time sitting down with co-pilot on on Saturday and Sunday having backwards conversations. is instead of like what's the next feature to build, I'm like, hey, we built this feature, like let's actually go reflect on the test. Not no pun intended.
I'm not talking about reflection, but like let's go back and analyze them. Let's go look at what's done here and like let's figure out did we did we make a big oopsie, right? Is this all made up? Um, and one of the sort of takeaways from this is like at least for this type of library, for this type of work, for source generators, for analyzers, for this kind of stuff, um, I need to be a little bit more prescriptive with my tests. And I'll try to explain what I mean. So when it's using an analyzer, right, defines an analyzer, it writes a test. For the analyzer to be tested, what it will do is it will, like I kind of said earlier, it writes C code into a string, right? You can you can write anything you want into a string. So um it writes C code and then runs the analyzer against it to say whether or not um it would detect, you know, the violation of the rule or not.
And I think that's helpful. I think that's like a that's like a unit test of some of the analyzer code to prove out little scenarios like this. I think that is very helpful because it gives us granular control over very unique situations. So no problem with that. The the problem that I have is that like that's not testing the analyzer actually running in real code. Now, is there a difference? Like maybe not for the logic of the analyzer, right? That's what those tests are actually testing is the logic inside of the analyzer. But just to give you an example, maybe to configure the analyzer, maybe there's some type of configuration that has to happen. Maybe that's super complicated. Maybe that's brittle. Maybe that doesn't work in certain situations. And we would completely miss it. So that I might have all of these awesome analyzer rules and I'm feeling very good about it and then someone's like, "Great, turn them on.
Let's go use them for real and they're completely because of, you know, they're impossible to configure or um they're too strict. They're missing some like very obvious scenario that just happens to exist in all of the code. like when it's not running for real, there's obviously potential gaps. So that's one thing. Um the source generation stuff is very similar in that regard. Uh I I gave some examples even in you know the video I posted today on my dev leader channel which is um it wrote a lot of really you know functional working source generation code but when it came time to prove it with an application that did trimming like that's a real proof and it missed some things right so it had tons of tests it could prove that it was you doing what it's saying it's doing. It's all good, but there's still like these integration gaps.
And so, like I said, one of the reminders for myself for this kind of thing is like I need to do a better job making sure um the real thing is being exercised cuz like just kind of losing confidence. And it's happened enough times now where I'm like I kind of feel stupid for for not making myself think that way. It's like, you know, this is going to happen still. Like, why are you not paying close attention? So, anyway, having a lot of fun with the C-pilot CLI. Uh, is it better or worse than clawed code? I It's just different and I think that these things will continue to kind of converge in functionality, but I've been enjoying it and I find that very surprising to say as someone who doesn't like using a terminal. So, um, I'll be playing more with Copilot CLI, more with Claude.
Um, I don't know what next tool is kind of on my radar because I dabble with cursor. I dabble with VS Code. I really like my Visual Studio setup. Um, I use GitHub for building features and stuff out. So, yeah, I'll kind of see. But if yeah, if you have uh other things that you're exploring, other AI tools and stuff like that, if you want me to to do a video on it, let me know. Just leave it below in the comments. Uh, I can explore it. I can make a video on my main dev leader channel so you can see me using the tool and we can we can talk through it. But yeah, otherwise leave your questions, comments, and stuff below in the comments and then you can go to codeamed.com. You can submit stuff anonymously that way and I will make a video response just for you and well, I guess for everyone else on the internet that wants to watch it.
So, thanks so much. I'll see you next time.
Frequently Asked Questions
These Q&A summaries are AI-generated from the video transcript and may not reflect my exact wording. Watch the video for the full context.
- How did you use AI to build C# source generators over the weekend?
- I used the Copilot CLI tool to help build a source-generated dependency injection helper framework called Needler. I relied on AI to write much of the tedious source generation code automatically, which saved me from writing it by hand. This allowed me to experiment with new ways of building software using source generation, even though I have limited prior experience with it.
- What challenges did you face when using AI to develop source generators and analyzers?
- One challenge was that the AI sometimes created test scenarios based on fabricated attributes that don't exist outside the test code, which was misleading. Also, because source generation and analyzers are meta and complex, the AI-generated tests passed but missed integration issues. I had to be vigilant in reviewing tests and realized I need more prescriptive tests that exercise real code scenarios to avoid gaps.
- Why did you find using the Copilot CLI enjoyable despite preferring an IDE?
- Although I usually prefer building in an IDE like Visual Studio and dislike using terminals, I found the Copilot CLI enjoyable because it fit well with building a library like Needler. The CLI workflow felt good for this kind of development, and once I got into a groove using it, I appreciated how fast and effective it was for iterating on source generation features.