Not gonna lie, I can be an ambitious little goober.
Lately, my LinkedIn feed has been looking like an endless highlight reel. Friends my age are out there building impressive projects, landing internships, making appearances at tech events, and generally kickstarting their careers in the IT industry.
Meanwhile, there's me. On a gap year. Watching everyone else seemingly speedrun their developer origin stories while I'm still figuring out which quest to accept.
Don't get me wrong, I'm genuinely happy for them. But I'd be lying if I said I hadn't occasionally felt that bitter little sting of jealousy.
Maybe I just needed more sleep. Who knows?
The thing is, deep down, I've always believed I could reach those stars too. Make something meaningful. Leave my mark on the world. You know, all that protagonist character-development stuff.
And naturally, being the perfectly reasonable person I am, I decided the best way to prove that was to build something HUGE.
Not just a little side project. Oh no. I wanted something sophisticated. Something ambitious. Something that could potentially stand alongside the big names in enterprise software and open source.
There was just one tiny problem.
I wasn't quite ready for that.
By then, I'd taught myself most of what I knew about programming. And while that gave me enough confidence to dream up increasingly elaborate projects, it didn't necessarily mean I had the engineering foundations to bring them to life.
Nine times out of ten, things didn't quite go according to plan. Some ideas were so ridiculously ambitious that I'd abandon them before getting anywhere meaningful. Others actually made it into development, only to hit a wall I couldn't quite figure out how to climb.
Turns out, having the ambition to build the next big thing and knowing how to build it are two very different skill sets.
Who would've thought?
Eventually, I had to admit something to myself.
Maybe the problem wasn't that my ambitions were too big. Maybe I was trying to build skyscrapers on foundations that could barely support a garden shed.
And as much as I'd love to tell you I had some profound, cinematic moment of enlightenment, it was really just me realizing that my programming knowledge was a bit of a mess.
I'd picked up bits and pieces from tutorials, documentation, experiments, and whatever rabbit hole happened to catch my attention that week. I could write Python, sure. But there were still gaps in my understanding of the language itself, and I figured those gaps weren't exactly going to fix themselves.
So I made a decision.
I was going back to basics.
Back to Python fundamentals. Revisiting concepts I thought I understood, filling in the gaps, and making sure I actually knew what I was doing rather than just celebrating whenever my code ran without throwing an exception.
As for software architecture, testing, and all those other fancy engineering practices? Well, those would come later, mostly through the rather educational experience of building things and discovering just how many ways they could go wrong.
I wasn't giving up on building something extraordinary. I was just figuring out how to become the kind of engineer who could actually pull it off, y'know?
And with my foundations getting a much-needed renovation, it was time to rethink how I approached projects.
I still wanted to build something meaningful. Something people might actually use. Something that would challenge me enough to become a better developer.
And, of course, something that might one day make someone go, "Oh my God, isn't that the guy who builtâ"
Ahem.
Anyway.
This time, I decided to try something different. Instead of immediately attempting to build the next revolutionary piece of software, I'd start with something relatively small. Something practical. Something I could realistically build while learning a thing or two along the way.
A thing or two.
Looking back, that was a spectacular understatement.
I eventually settled on a simple idea: a Python workspace manager.
The initial objective wasn't particularly glamorous. I wanted a command-line tool that could handle the repetitive boilerplate setup involved in starting a new Python project.
Nothing revolutionary. Just a little utility to make my own development workflow less tedious.
And that little idea would eventually become RepoPy.
Enter RepoPy
So, what exactly was this grand new idea of mine?
Well, brace yourselves.
A folder. With Git initialized. And a .gitignore file.
...yep. That was pretty much it.
I wanted a simple command-line tool that could create a new project directory, initialize a Git repository, and throw in a .gitignore so I wouldn't have to repeat those steps every time I started something new.
Oh, and while I was at it, why not let it clone an existing GitHub repository in one go too?
Nothing particularly groundbreaking. Git already had the commands for all of this. I just wanted to bundle a few repetitive steps into something a little more convenient, that's all.
After spending so much time dreaming up projects with enough features to rival enterprise software, I'd somehow landed on a tool whose original job description was essentially:
"Make folder. Do Git things. Thank you."
And you know what?
For once, that felt like a perfectly reasonable place to start.
Little did I know, this seemingly innocent idea was about to introduce me to considerably more software engineering than I'd bargained for.
A Little CLI, a Lot of Architecture
So, how difficult could building a little Python CLI possibly be?
I mean, I already knew Python. I knew how to create directories, work with files, and run Git commands. Surely it was just a matter of putting those pieces together, right?
Well, yes.
But also, oh boy, was I about to learn a thing or two.
The first real challenge wasn't even getting the commands to work. It was figuring out how to structure the project itself.
See, when you're teaching yourself programming, it's pretty easy to fall into the habit of stuffing everything into one file, or perhaps splitting things across a couple of files when the original starts looking a little too intimidating.
And for small scripts? That can work perfectly fine.
But I didn't want RepoPy to become another collection of functions awkwardly sharing the same living space, praying nobody touched the wrong variable.
That's when I started learning about separation of concerns, modular architecture, and decoupling.
Fancy words, I know. But the underlying idea was surprisingly straightforward: different parts of a program should have clearly defined responsibilities, and they shouldn't need to know everything about one another just to get their jobs done.
And here's the thing: I wanted to approach RepoPy this way from the very beginning.
No giant main.py housing the entire population of my codebase, as I'd tended to do with previous projects. No waiting until everything became an incomprehensible mess before deciding, "Oh damn, it might be time to organize things."
I wanted to get into the habit of building software with a little more thought behind its structure, even if the project itself was relatively small.
So I began separating the CLI interface from the logic coordinating operations, and that logic from the lower-level functions actually doing the work.
Instead of asking myself, "How do I make this command work?", I started asking something a little more interesting:
"Where should this responsibility live, and what should it actually know about the rest of the system?"
Of course, putting code into different files doesn't magically make it well-architected. If only software engineering were that easy.
But for the first time, I was consciously thinking about how the pieces of a program should fit together, rather than just whether those pieces worked individually.
And all this because I wanted a tool that could create a folder and run git init.
Wonderful.
From My Terminal to PyPI
Now, there's one little detail I haven't mentioned yet.
Even when RepoPy was nothing more than a tool for initializing and cloning repositories, I had already decided that I wanted to publish it as a proper Python package.
Not just leave it sitting in a GitHub repository with instructions telling people to clone the source code and hope for the best.
I wanted something people could actually install.
And so, after completing that first milestone, I published RepoPy v0.1.0 to PyPI.
My very first release.
Now, v0.1.0 wasn't exactly overflowing with features. At that point, RepoPy was still doing the two things I'd originally built it to do.
But that was kind of the point.
I didn't want to wait until the project had every feature I could possibly imagine before putting it out into the world. I wanted to start with something small, functional, and usable, then build on it.
Of course, publishing a Python package introduced me to yet another collection of things I hadn't previously needed to think about.
Package metadata. Build configuration. Version numbers. Distribution artifacts. The difference between code that runs perfectly fine on my machine and software that can actually be installed somewhere else.
Apparently, writing a Python program and distributing a Python program are two separate adventures.
But eventually, there it was.
RepoPy v0.1.0. Published. Installable. Real.
It wasn't the next revolutionary piece of enterprise software.
It wasn't going to change the world overnight.
But it was something I'd actually built, finished, and released.
And after all those overly ambitious projects that never quite made it to the finish line?
That meant a lot more to me than its modest feature list might suggest.
Of course, publishing that first version didn't mean I was finished learning how to build reliable software.
Far from it.
Wait, You're Supposed to Test These Things?
With the initial architecture in place and RepoPy's first version out in the world, I was feeling pretty good about myself.
My code had structure. Different modules had different responsibilities. Everything was beginning to look like an actual software project rather than a suspiciously large Python script.
Surely things were going smoothly now, right?
Enter software testing.
Now, up until this point, my approach to testing was, shall we say, rather informal.
Write some code. Run it. See if it works. Maybe try a few different inputs. If nothing exploded, congratulations! Ship it and let's get going!
A perfectly reasonable strategy, provided your software never encounters an unexpected input, an operating system you haven't tested, or a user who possesses the terrifying ability to do something you didn't anticipate.
Unfortunately, software has a habit of encountering all THREE.
So I found myself learning about automated testing, particularly with pytest. And the syntax wasn't even the hardest part.
The real challenge was figuring out what on Earth I was supposed to test.
A function works when given the correct input? Lovely. But what if the input is invalid? What if the directory already exists? What if Git isn't installed? What if a subprocess fails halfway through? What if the user does something so spectacularly unexpected that even the documentation starts questioning its existence?
Suddenly, I wasn't just writing code anymore. I was trying to anticipate all the creative ways it could go wrong.
And then came mocking.
Apparently, when testing code that interacts with the filesystem, Git, and external processes, it's generally a good idea not to let every test loose on your actual development environment.
Who knew? (Not me.)
So I had to learn how to replace certain dependencies with controlled substitutes, simulate successful and failing operations, and verify that my code responded correctly without necessarily performing those operations for real.
Which sounds perfectly reasonable when explained like that.
Actually figuring out how to mock the right function, in the right place, without accidentally testing the mock instead of the code?
That was a whole different boss fight.
But slowly, testing started changing the way I approached development. I wasn't just asking whether a feature worked under normal conditions. I was learning to ask what assumptions it relied on, what could break those assumptions, and how the program should respond.
Now, here's a little confession: I didn't actually start writing automated tests from day one.
RepoPy's first milestone was already complete by the time I properly introduced pytest. At that point, the tool could initialize new repositories and clone existing ones, which was pretty much the entire original plan.
But as I started adding more commands, I decided it was probably time to stop relying on the ancient and highly sophisticated testing methodology of "eh, seems to work."
So I began writing unit tests alongside development, and I've kept doing that ever since.
Over time, the test suite grew alongside the project, eventually reaching 100% reported test coverage.
Now, before anyone starts throwing tomatoes at me, I'm well aware that 100% coverage doesn't mean 100% bug-free software. You can exercise every measured part of your code and still completely misunderstand what that code was supposed to do.
But for me, reaching that milestone was less about chasing a pretty number and more about developing the discipline to think through my implementation, its assumptions, and its failure cases.
Turns out, writing code that works is one challenge.
Building confidence that it behaves correctly when things go wrong is another challenge entirely.
And once again, my supposedly tiny Python project had found a new way to humble me.
The Robots Are Testing My Code Now
So, there I was. Writing tests, running pytest, watching little green dots appear in my terminal, and feeling increasingly confident that I was finally doing this software engineering thing properly.
But then a thought occurred to me.
"What if I could make GitHub run these tests for me?"
Because apparently, once you've started automating things, the natural next step is to automate the automation.
And that's how I stumbled into Continuous Integration, or CI.
The idea was pretty straightforward: instead of relying entirely on myself to remember to run the tests, I could have an automated workflow execute them whenever the appropriate GitHub events occurred.
And so began my first adventure with GitHub Actions.
My initial workflow was nothing particularly fancy. It just ran pytest.
Definitely simple enough, right?
Well, after a couple of failed runs and some quality time spent figuring out why my shiny new automation wasn't automating quite as intended, I finally got to see that beautiful green checkmark.
I'm not going to pretend I'd just engineered NASA's mission control systems, but seeing my own code being tested automatically by GitHub?
That felt pretty cool.
Of course, being me, I couldn't just leave it there.
Somewhere along the way, I discovered that there were other ways to check code quality beyond seeing whether the tests passed.
Enter linting and static type checking.
Linting, as I learned, helps catch suspicious code patterns, style inconsistencies, and other things that might deserve a second look. Static type checking, meanwhile, can identify certain type-related mistakes without needing to execute the program.
In other words, pytest was checking whether my code behaved as expected, while these new tools could help catch a bunch of different categories of problems.
So I decided to give Ruff and mypy a go.
And naturally, once I'd started using them locally, I figured I might as well invite them to the automated party.
My little pytest-only workflow gradually evolved into a broader quality-checking pipeline, running tests, linting, and type checking.
Three different tools, each looking at the code from a different angle.
None of them could guarantee that RepoPy was bug-free, of course. But together, they gave me considerably more confidence in the changes I was making.
At this point, I was beginning to suspect the project was teaching me more than I was teaching it.
Okay, Maybe Just One More Command...
Remember when I said RepoPy was supposed to be a little tool for initializing and cloning repositories?
Yeah. About that.
As I continued working on my projects, I kept running into other small, repetitive tasks that made me think, "You know what? RepoPy could probably handle this too."
For instance, what if I already had a local Git repository and wanted to link it to a remote? Or what if my workspace was slowly accumulating the digital equivalent of dust bunniesâbuild artifacts, test outputs, and cache directories scattered everywhere?
Surely I could give RepoPy a command to clean those up.
And while I was at it, wouldn't it be convenient to have another command that displayed useful information about the current project? Things like its Git configuration, virtual environment, and other basic project metadata, all in one place.
And so the cycle continued.
Find a mildly inconvenient task. Think of a command to automate it. Implement the command. Discover three new things I didn't know about software engineering.
Rinse and repeat.
What started as a couple of Git-related conveniences was gradually becoming a more complete Python workspace-management tool.
Now, I wasn't trying to cram every imaginable developer utility into one package. The features still needed to make sense together.
But I was beginning to realize that a useful project doesn't necessarily need some revolutionary, never-before-seen idea behind it.
Sometimes, it just needs to solve a collection of ordinary problems reasonably well.
Although, judging by how quickly my list of ideas was growing, someone probably should've introduced me to the concept of scope creep a little sooner.
Oh, Other People Have Opinions?
At some point during all this, I decided to share RepoPy on LinkedIn.
Partly because I was proud of what I'd been building, and partly because, well, what's the point of making something useful if nobody knows it exists?
And to my pleasant surprise, a few people actually took the time to offer suggestions.
One particularly useful piece of feedback concerned RepoPy's cloning functionality.
See, cloning a public repository is one thing. But automatically installing its dependencies afterward?
That's where things can get a littleeeeee... spicy.
Dependencies aren't necessarily harmless little packages politely waiting to be imported. Installing software from an untrusted project can potentially execute malicious code, whether through installation scripts, build processes, or compromised packages.
In other words, blindly installing dependencies from some random repository on the internet is not exactly what I'd call a five-star security strategy.
The suggestion was straightforward: ask the user for confirmation before installing dependencies after cloning.
And for people who already knew what they were doing, provide command-line flags to explicitly skip installation or allow it without an interactive prompt.
That way, RepoPy could remain convenient without silently making a potentially risky decision on the user's behalf.
I liked that idea.
Not just because it improved the command, but because it made me think about something I'd barely considered when RepoPy was still just a teeny weeny personal utility:
When other people start using your software, your assumptions become their risks.
A feature that seems convenient to its developer might behave very differently in the hands of someone who doesn't know what it's doing behind the scenes.
And suddenly, I wasn't just thinking about whether RepoPy could perform an operation.
I was thinking about whether it should perform that operation automatically.
Apparently, AI Agents Need Guardrails Too
Now, while I was working on making RepoPy's cloning workflow a little more security-conscious, something else was happening.
I was invited to contribute a RepoPy command extension to HOL Guard, a project concerned with safeguarding commands executed by AI coding agents.
Which, admittedly, was not something I'd imagined happening when I first decided to automate git init.
The idea was to help Guard understand what RepoPy's commands actually do, and which operations might deserve additional scrutiny before an AI agent executes them.
Because there's a pretty significant difference between asking RepoPy to display project information and asking it to install dependencies, modify Git configuration, or clean up files.
One is primarily informational. The others can change the environment, execute potentially untrusted code, or remove data.
And while developers can inspect a command and decide whether they're comfortable running it, AI coding agents introduce another layer of complexity. An agent might invoke a command as part of a larger task without fully appreciating its side effects.
That's where command-specific safeguards and permission boundaries become useful.
What I found particularly interesting was that these two developments were happening around the same time.
On one side, I was improving RepoPy's own behaviour so that potentially risky operations required more deliberate user choices.
On the other, I was helping an external system understand the risks associated with those operations when performed by AI agents.
Different problems, but a surprisingly similar underlying question:
Just because software can perform an operation, does that mean it should be allowed to do so automatically?
Somewhere along the way, my tiny Python workspace manager had wandered into the world of AI agent safety.
At this point, I'm honestly just waiting for someone to tell me it needs a space program.
Automating the Release of My Automation
Remember how I eventually discovered GitHub Actions and thought it would be neat to have GitHub run my tests automatically?
Well, apparently, that wasn't the end of my automation addiction.
By this point, I'd already been publishing versions of RepoPy to PyPI. And somewhere along the way, another thought occurred to me.
"Could I automate the release process too?"
Because why stop at automating tests when you could also automate the process of publishing the very tool you created to automate other things?
Automation inception.
Until then, I'd been publishing RepoPy manually, using PyPI API tokens to authenticate the uploads.
It worked, but it also meant I had to handle the publishing process myself whenever I wanted to release a new version.
So I decided to give GitHub Actions another job.
I configured a release workflow that would automatically build and publish RepoPy to PyPI whenever I pushed a new version tag.
Instead of manually going through the publishing process, I could prepare a release, push its tag, and let GitHub take care of the rest.
But while setting this up, I also discovered something rather interesting: PyPI Trusted Publishing.
Rather than storing a long-lived PyPI API token for the workflow to use, I could configure GitHub Actions to authenticate through OpenID Connect (OIDC).
In simpler terms, PyPI could verify that the publishing request came from an authorized GitHub Actions workflow and issue a short-lived credential for that release.
No permanent PyPI publishing token sitting in my repository's secrets, waiting to become somebody else's problem.
Convenient and a better security model?
Hehe. Now we're talking.
So I made the switch.
And just like that, my release process went from manually publishing packages with API tokens to pushing a version tag and letting an authenticated workflow handle the publication.
What began as a tiny convenience tool had now introduced me to automated testing, static analysis, continuous integration, Python packaging, release automation, and even a little software supply-chain security.
I may have slightly underestimated the educational consequences of laziness.
Still an Ambitious Little Goober
So, what did I actually get out of building RepoPy?
Well, quite a lot more than a Python CLI, that's for sure.
I went into this project wanting to automate a few repetitive Git operations. Somewhere along the way, I found myself learning about software architecture, separation of concerns, automated testing, mocking, CI pipelines, static analysis, Python packaging, release automation, and security-conscious design.
Not exactly what I'd expected from a glorified git init button.
But perhaps the biggest thing RepoPy gave me was confidence.
For the first time in a while, I'd taken an idea, broken it down into something manageable, built it, published it, and continued improving it.
And that made me a little more willing to tackle something bigger.
It gave me the confidence to start working on Quilchoom, a considerably more ambitious project, and the curiosity to explore contributing to other open-source projects.
After all, if I'd managed to learn this much from building my own little tool, what could I learn from working with other people's codebases?
Of course, that doesn't mean I've suddenly become some enlightened developer who's completely stopped comparing himself to everyone else.
Oh, absolutely not!
I still catch myself looking at my peers' projects and wondering how my own work stacks up. How sophisticated is their architecture? What engineering practices are they using? What do they understand that I haven't learned yet?
Sometimes, I probably spend a little too much time thinking about those questions LOL.
But increasingly, those comparisons also give me things to investigate. New concepts to learn. Different approaches to consider. A clearer picture of the kind of engineer I want to become.
I still have plenty of gaps in my knowledge, and I'm under no illusion that building RepoPy has somehow turned me into a seasoned software engineer.
Heck, I haven't even started university yet.
Everything I've learned so far has come from teaching myself, experimenting, reading documentation, making mistakes, and occasionally wondering why something that worked perfectly yesterday has decided to ruin my afternoon.
And that's partly why I'm proud of RepoPy.
Not because it's the most sophisticated project out there. Not because it can compete with the work of developers who've spent years in the industry.
But because I built it. I finished it. I put it out into the world. And I learned how to build better software because of it.
I still want to build something extraordinary someday.
That ambition hasn't gone anywhere.
But I'm starting to understand that becoming capable of building extraordinary things doesn't necessarily begin with an extraordinary project.
Sometimes, it begins with something small.
Something useful.
Something you can actually finish.
And sometimes, that something is just a little Python CLI that creates a folder and does Git things.
So, yeah.
Still an ambitious little goober.
Just one with a slightly better understanding of software engineering.
And a few more green checkmarks on GitHub.




Top comments (1)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.