mirror of
https://github.com/bfollington/terma.git
synced 2026-09-19 09:04:14 +08:00
Initial library
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
# Report a bug
|
||||
|
||||
$ARGUMENTS
|
||||
|
||||
!include(./lib/bug-report.md)
|
||||
@@ -0,0 +1,4 @@
|
||||
mkdir -p out
|
||||
for file in bug-report.md feature.md orient.md implement.md prototype.md code-review.md decompose.md next-up.md harden.md research.md; do
|
||||
deno run --allow-read --allow-write build.ts "$file" "out/$file"
|
||||
done
|
||||
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env -S deno run --allow-read --allow-write
|
||||
|
||||
import { remark } from "npm:remark@15";
|
||||
import { visit } from "npm:unist-util-visit@5";
|
||||
import { readFileSync, writeFileSync } from "node:fs";
|
||||
import { resolve, dirname, join } from "node:path";
|
||||
|
||||
// Custom plugin to handle !include() syntax
|
||||
function remarkInclude() {
|
||||
const processingFiles = new Set<string>();
|
||||
|
||||
return (tree: any, file: any) => {
|
||||
const basePath = dirname(file.path || file.history?.[0] || ".");
|
||||
const currentFile = file.path || file.history?.[0] || "";
|
||||
|
||||
visit(tree, "paragraph", (node, index, parent) => {
|
||||
// Check if paragraph contains only an include directive
|
||||
if (node.children?.length === 1 && node.children[0].type === "text") {
|
||||
const text = node.children[0].value.trim();
|
||||
const includeMatch = text.match(/^!include\(([^)]+)\)$/);
|
||||
|
||||
if (includeMatch) {
|
||||
const includePath = includeMatch[1].trim().replace(/['"]/g, "");
|
||||
const fullPath = resolve(basePath, includePath);
|
||||
|
||||
// Check for circular includes
|
||||
if (processingFiles.has(fullPath)) {
|
||||
console.warn(`Warning: Circular include detected for ${includePath}`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const content = readFileSync(fullPath, "utf-8");
|
||||
|
||||
// Process included content with same processor (recursive)
|
||||
processingFiles.add(fullPath);
|
||||
const processor = remark().use(remarkInclude);
|
||||
const result = processor.processSync({ value: content, path: fullPath });
|
||||
processingFiles.delete(fullPath);
|
||||
|
||||
const parsed = processor.parse(String(result));
|
||||
|
||||
// Replace the paragraph with the parsed content
|
||||
if (parent && typeof index === "number") {
|
||||
parent.children.splice(index, 1, ...parsed.children);
|
||||
}
|
||||
} catch (err) {
|
||||
processingFiles.delete(fullPath);
|
||||
console.warn(`Warning: Could not include ${includePath}: ${err.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
async function processMarkdown(inputPath: string, outputPath: string) {
|
||||
const processor = remark().use(remarkInclude);
|
||||
|
||||
const input = readFileSync(inputPath, "utf-8");
|
||||
const result = await processor.process({ value: input, path: inputPath });
|
||||
|
||||
writeFileSync(outputPath, String(result));
|
||||
console.log(`Processed: ${inputPath} → ${outputPath}`);
|
||||
}
|
||||
|
||||
// Process a single file or directory
|
||||
if (import.meta.main) {
|
||||
const [inputPath, outputPath] = Deno.args;
|
||||
|
||||
if (!inputPath || !outputPath) {
|
||||
console.log("Usage: deno run --allow-read --allow-write templater.ts input.md output.md");
|
||||
Deno.exit(1);
|
||||
}
|
||||
|
||||
await processMarkdown(inputPath, outputPath);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Code Review
|
||||
|
||||
!include(./lib/review.md)
|
||||
@@ -0,0 +1,3 @@
|
||||
# Decompose
|
||||
|
||||
!include(./lib/decompose.md)
|
||||
@@ -0,0 +1,5 @@
|
||||
# Change/Add Feature
|
||||
|
||||
Let's work on a feature: $ARGUMENTS
|
||||
|
||||
!include(./lib/feature.md)
|
||||
@@ -0,0 +1,5 @@
|
||||
# Harden Feature
|
||||
|
||||
Now that this is working, let's harden it.
|
||||
|
||||
!include(./lib/harden.md)
|
||||
@@ -0,0 +1,3 @@
|
||||
# Implement Plan
|
||||
|
||||
!include(./lib/implement.md)
|
||||
@@ -0,0 +1,5 @@
|
||||
!include(./subagent.md)
|
||||
|
||||
# Report a bug
|
||||
|
||||
When we report a bug the subagent should take all of the context that we can pass to it from our conversation so far as relevant to the bug that I'm reporting. When we use this bug report command, the content of what I report is the observed behavior I'm seeing, but we also need to actually produce the steps that reliably could lead up to this or might reproduce this. Maybe some hypothesis of what could be done. Maybe a way to fix it. And we want to document the details of this bug in BUGS.md under a new heading. We want to make sure that this bug could be reproduced by somebody who was starting a blank session in this code base and trying to boot up and work on it. So we have to make sure we bring out all of our assumptions during the process of reporting it for later.
|
||||
@@ -0,0 +1,5 @@
|
||||
We always prefer functional programming over spaghetti-code mutable references. Functional programming here means thinking about types and transitions between them explicitly and a focus on values over places-in-memory. It means modeling functions as the primary unit. So avoiding classes except when they are the exact correct representation for something like a resource with a lifetime kind of concept or a service with pointers to other services where classes really are an elegant way of modeling those dynamics. Wherever we can prefer static pure functions, we should do so. We don't need to abuse ideas like carrying or partial application or any convoluted functional programming concepts. More so, the spirit of it which is that function orientation is all that you need. High order functions are of course encouraged as usual and we should prefer map filter and reduce typically to standard for loop type operations. But it depends on the context and we shouldn't be absolutist in any language that we're writing about. this kind of thing.
|
||||
|
||||
We're possible we would prefer to represent things as plain data that can be printed, manipulated and operated on by a small set of well-designed functions. This is very much inspired by the closure, language, and Ridge Hickey's discussion about software engineering best practices.
|
||||
|
||||
!include(./modules.md)
|
||||
@@ -0,0 +1,13 @@
|
||||
!include(./subagent.md)
|
||||
|
||||
# Decompose
|
||||
|
||||
Let's break this large module/monolith apart into smaller modular chunks. This might be a fractal process that takes several iterations to get right. So let's focus on first working out the right divisions of the code and breaking it apart without refactoring anything along the way. We want to just break it into logical units along whatever lines already exist and then whatever tension appears, we can then start to refactor and think more granularly from there. But any division of large files of a mixed function into somewhat useful groupings, just to preserve context windows of humans and albums alike is always useful.
|
||||
|
||||
## Modules
|
||||
|
||||
!include(./modules.md)
|
||||
|
||||
## Domain-Driven Design
|
||||
|
||||
!include(./domain-driven-design.md)
|
||||
@@ -0,0 +1 @@
|
||||
We want to follow the best practices of domain-driven design, thinking about bounded contexts and ubiquitous language within those bounded contexts and how the interchanges and boundaries between those contexts will be modeled in our code base and in the runtime dynamics of our system. It's important to consider the conceptual model presented by the application's types and try as best as we can to fit it to what's intuitive to humans and true to the problem domain finding a way to express only valid statements ideally making any invalid or confusing states unrepresentable within our domain when possible. That extends to error handling and types and API design, the names of functions and modules and parameters and even variables within the code are all part of a story that will be experienced by us traversing this code base in the future and so we need to be very mindful of how we construct it.
|
||||
@@ -0,0 +1,23 @@
|
||||
!include(./subagent.md)
|
||||
|
||||
# Feature
|
||||
|
||||
When adding or changing a feature of the application consult (or create) the SPEC.md file and see if there are any features similar to this one, or if this feature already exists, or if this feature would contradict or clash with any features mentioned in the SPEC.md file. We'll need to update the spec with the description of the new feature first. If there are lots of tests for existing kinds of features, we should consider creating new tests or changing the tests to include new feature and then implementing the feature against those tests.
|
||||
|
||||
SPEC.md should be sufficient for somebody to have an understanding of how they expect the application to operate, but does not have to be a comprehensive documentation of every minute feature. It is more of a user story level description of the application. It will mention the core ubiquitous language terms, but probably will not have very much code actually embedded within it.
|
||||
|
||||
## SWE Process
|
||||
|
||||
!include(./swe.md)
|
||||
|
||||
## Domain-Driven Design
|
||||
|
||||
!include(./domain-driven-design.md)
|
||||
|
||||
## Code Style
|
||||
|
||||
!include(./code-style.md)
|
||||
|
||||
## File Structure
|
||||
|
||||
!include(./files.md)
|
||||
@@ -0,0 +1,8 @@
|
||||
`SPEC.md` - Application specification
|
||||
`LOG.md` - Development log
|
||||
`BUGS.md` - Defect reports
|
||||
`progress/` - Reports on previous work sessions
|
||||
`research/` - Answers to research questions
|
||||
`MOD.md` - Scattered in the codebase, explains modules
|
||||
`README.md` - Explains tooling and usage
|
||||
`CLAUDE.md` or `llms.txt` - Instructions for Language Model agents
|
||||
@@ -0,0 +1,23 @@
|
||||
!include(./subagent.md)
|
||||
|
||||
# Harden
|
||||
|
||||
Take this feature/module that we're working on from prototype status to production grade.
|
||||
|
||||
We'll need to consider how to adjust the domain model, any types etc. and where the code should ultimately live. Perhaps we need to reabstract some layers?
|
||||
|
||||
# SWE
|
||||
|
||||
!include(swe.md)
|
||||
|
||||
# Domain-Driven Design
|
||||
|
||||
!include(domain-driven-design.md)
|
||||
|
||||
# Code Style
|
||||
|
||||
!include(code-style.md)
|
||||
|
||||
# File Structure
|
||||
|
||||
!include(files.md)
|
||||
@@ -0,0 +1,19 @@
|
||||
Create one or more subagents and delegate tasks to implement the plan.
|
||||
|
||||
!include(./subagent.md)
|
||||
|
||||
## SWE Process
|
||||
|
||||
!include(./swe.md)
|
||||
|
||||
## Domain-Driven Design
|
||||
|
||||
!include(./domain-driven-design.md)
|
||||
|
||||
## Code Style
|
||||
|
||||
!include(./code-style.md)
|
||||
|
||||
## File Structure
|
||||
|
||||
!include(./files.md)
|
||||
@@ -0,0 +1,3 @@
|
||||
Modules should be namespaced carefully, they should be decoupled as much as possible, and be of medium size. You don't want a constellation of tiny modules, everything overly decomposed, but when a module is straining to find a single focus, it should split into two modules, and this is something we should do all the time throughout our process of software engineering. To identify when a file or a module is growing beyond its limit, and consider the best way to split off what we're doing into at least two different pieces and continue on. So not boiling the ocean and re-igitating the whole module or code base, but splitting modules regularly to make sure we don't end up with giant files that are hard to traverse.
|
||||
|
||||
Modules may mean simply splitting a file into two, or a more formal decision about directory structure in the application. When we create or change the purpose of any _directory_ module, we should write an MOD.md file in the module root.
|
||||
@@ -0,0 +1,23 @@
|
||||
!include(./subagent.md)
|
||||
|
||||
# Change Phases
|
||||
|
||||
If we have a previous feature, let's update `LOG.md` to explain our changes and if they were substantial, let's ask the user if they want a progress report. We should clear the context window before beginning work on the new session, so we have to update `CLAUDE.md`, `SPEC.md,`, `LOG.md` and any progress reports to document everything.
|
||||
|
||||
Then, let's use planning mode to chart a course to the next checkpoint.
|
||||
|
||||
## SWE Process
|
||||
|
||||
!include(./swe.md)
|
||||
|
||||
## Domain-Driven Design
|
||||
|
||||
!include(./domain-driven-design.md)
|
||||
|
||||
## Code Style
|
||||
|
||||
!include(./code-style.md)
|
||||
|
||||
## File Structure
|
||||
|
||||
!include(./files.md)
|
||||
@@ -0,0 +1,7 @@
|
||||
# Progress Report
|
||||
|
||||
!(./subagent.md)
|
||||
|
||||
The subagent will capture our progress from this session in a report. Include the initial thrust of this conversation, the features that we've implemented and tackled, any challenges faced and what changes we ultimately landed on and any learnings in the process, maybe any intent for future, and refactorings or improvement to the code, basically so that we can put the session down and the next person who comes along to pick this up will understand what we were thinking and where I had was at, and hopefully be able to get back into the swing of things as quickly as possible.
|
||||
|
||||
Progress reports live in `progress/YYYMMDD_FEAT_report.md` (filename is dynamic) with a highlevel summary in `LOG.md`
|
||||
@@ -0,0 +1,21 @@
|
||||
!include(./subagent.md)
|
||||
|
||||
# Prototype
|
||||
|
||||
When we're prototyping, we're not "just hacking" but we don't want to have bogged down in the details. No need to try to make an immaculate model for a domain. We might not commit to it ultimately. So prototyping means to me that we will look to extend the existing domain (see Domain-Driven Design) in some way or remix or recompose pieces of it cleverly to get an idea of whether this is possible and is a good direction to proceed in. And then after prototyping phase, we will harden it in our normal software engineering loop. So prototyping means that we want to move quickly without what was stressing about the layout of code, the way we might if we were just adding a new feature that we knew we wanted to commit to, to the code base.
|
||||
|
||||
## SWE Process
|
||||
|
||||
!include(./swe.md)
|
||||
|
||||
## Domain-Driven Design
|
||||
|
||||
!include(./domain-driven-design.md)
|
||||
|
||||
## Code Style
|
||||
|
||||
!include(./code-style.md)
|
||||
|
||||
## File Structure
|
||||
|
||||
!include(./files.md)
|
||||
@@ -0,0 +1,11 @@
|
||||
!include(./subagent.md)
|
||||
|
||||
# Research
|
||||
|
||||
Research the codebase to find out the answer to this question. When I say research, this means first explore the SPEC.md, the LOG.md, any recent progress reports (`progress/`) to understand if this question has been changed recently, then explore as much of the codebase as is necessary to get a thorough understanding of the answer to the question. Consider reading the tests as a way of understanding what features and assertions we already make about the codebase, and we should understand if the tests are passing if the research question is implicitly covered by one of the test cases.
|
||||
|
||||
Write the results in `research/YYYYMMDD_QUESTION.md` where the filename is dynamic.
|
||||
|
||||
## File Structure
|
||||
|
||||
!include(./files.md)
|
||||
@@ -0,0 +1,39 @@
|
||||
!include(./subagent.md)
|
||||
|
||||
Review the code we have written with these priorities:
|
||||
|
||||
## Core Principles
|
||||
|
||||
**Channel the spirit of Rich Hickey**: Embrace simplicity, embrace immutability, embrace data.
|
||||
|
||||
Also consider the lessons of Erlang (Joe Armstrong), Elixir (José Valim), Elm (Evan Czaplicki), and Rust.
|
||||
|
||||
## Specific Focus Areas
|
||||
|
||||
### Code Structure
|
||||
- **Extract pure functions** for common logic and reusable operations
|
||||
- **Pay attention to the story that parameters and names tell** - use the code as a self-documenting structure
|
||||
- **Examine similar code** to ensure consistency and avoid duplication
|
||||
- **Use consistent naming conventions** that clearly express intent
|
||||
- **Decoupled modules** - consider inversion of control, decomposition, and breaking apart large files by extracting clear domains
|
||||
- **Reusable components** - identify robust abstractions that would clarify the code and make the functionality more robust
|
||||
|
||||
### Type Safety & Data
|
||||
- **Make invalid states unrepresentable**
|
||||
|
||||
### Error Handling
|
||||
- **Handle errors gracefully, or design APIs that make errors impossible**
|
||||
- Prefer throwing over silent failures or unclear undefined returns
|
||||
|
||||
### Functional Style
|
||||
- **Prefer a pure, functional programming style** over imperative approaches
|
||||
- Favor immutable data transformations in library code
|
||||
- Minimize side effects and make them explicit when necessary
|
||||
|
||||
### Code Style
|
||||
|
||||
!include(code-style.md)
|
||||
|
||||
### Domain-Driven design
|
||||
|
||||
!include(domain-driven-design.md)
|
||||
@@ -0,0 +1,5 @@
|
||||
Create a subagent and use ultra thinking within the agent to preserve our context window.
|
||||
|
||||
# Thought Process
|
||||
|
||||
!include(./thought-process.md)
|
||||
@@ -0,0 +1,3 @@
|
||||
We should follow our standard software engineering procedure that is starting with pure utility functions that move within the type domain and the type domain. And once those areas are established and we have tested utility functions work as we expect and the type domain makes sense and maps to the problem, then we start to build up into the integration domain of actually having effectful things, stateful things, render ing, blah blah blah, things that might actually require user testing or hands-on testing to confirm the validity of.
|
||||
|
||||
Make it work, make it right, make it fast in that order, where in this case work is something like sketch out the types and the utility functions and plug them together so that something happens and the light turns on, then make it right means tests hard on it, review the code and make sure that it all makes sense, plug any gaps and make assertions at runtime, make the code tight and add logging and things like that, and then make it fast. We can slightly confuse the code in the pursuit of better performance if we need to by documenting our decisions and changing the way we've abstracted things.
|
||||
@@ -0,0 +1,5 @@
|
||||
Let's follow a test driven approach for this feature where we'll ride the tests ahead of time and make them fail and then continually run them until they pass to verify that we've done what we wanted to do. This way we can step by step build up the complex layers of this kind of domain without getting too many risks of misunderstanding or a nightmare for debugging it on the other side.
|
||||
|
||||
Make sure to look at all the existing tests and check whether your property plays to insert new tests is, whether test should be updated or changed in the course of development is a difficult decision and may require insulting the spec or the change log of any recent things that we've done to understand the context of why the tests are behaving the way that they are.
|
||||
|
||||
Consult the SPEC.md to understand the requirements and constraints of the application.
|
||||
@@ -0,0 +1,3 @@
|
||||
Slow is smooth and smooth is fast. We only want to change one thing at a time and we want to make each cut deliberately without ever thrashing around aimlessly. We want to precise an orderly way of moving through problems that may be ambiguous and so we have to be careful about the sizing of each change we make and think about the staged delivery of every feature. So we always start with the types, the data, the way that we can start to make something tangible happen and exist and we build up layers of complexity slowly. We don't abstract up front into a complex system nor do we write everything in one file and wait till the very end to organize it. It's organic the way that the code based develops when modules split off as they make sense to us. We watch the linguistics of the code based develop and allow it to grow in ways that are and supportive of the design. It's not a constant rigidity or a paranoia but we can never let things get out of hand and what that means is always understanding how far through the process we are, how far from a check point we are and knowing what it would take to check the answers to our questions or if our current solution is valid against the existing domain and code base.
|
||||
|
||||
All problems can be solved easily if the task breakdown and scoping are performed correctly. We have to get these parts right and we have to understand if we're veering off track and what to do about it. Don't feel the need to try and make up for the fact that we've become confused or have ended up in strange territory. Be honest with me and say that our solution is not going to scale or that it isn't clear how to head in the direction we're heading in and let's regroup together. It's always easier to solve a problem with two heads than one.
|
||||
@@ -0,0 +1,5 @@
|
||||
# Next Checkpoint
|
||||
|
||||
Let's work towards the next checkpoint: $ARGUMENTS
|
||||
|
||||
!include(./lib/next-up.md)
|
||||
@@ -0,0 +1,7 @@
|
||||
!include(./lib/subagent.md)
|
||||
|
||||
# Orient
|
||||
|
||||
Read the `README.md` and the core files mentioned below, fan out from there to explore the project's structure and functionality.
|
||||
|
||||
Write a report on to brief the lead project agent so we can get to work!
|
||||
@@ -0,0 +1,13 @@
|
||||
# Report a bug
|
||||
|
||||
$ARGUMENTS
|
||||
|
||||
Create a subagent and use ultra thinking within the agent to preserve our context window.
|
||||
|
||||
# Thought Process
|
||||
|
||||
Slow is smooth and smooth is fast. We only want to change one thing at a time and we want to make each cut deliberately without ever thrashing around aimlessly. We want to precise an orderly way of moving through problems that may be ambiguous and so we have to be careful about the sizing of each change we make and think about the staged delivery of every feature. So we always start with the types, the data, the way that we can start to make something tangible happen and exist and we build up layers of complexity slowly. We don't abstract up front into a complex system nor do we write everything in one file and wait till the very end to organize it. It's organic the way that the code based develops when modules split off as they make sense to us. We watch the linguistics of the code based develop and allow it to grow in ways that are and supportive of the design. It's not a constant rigidity or a paranoia but we can never let things get out of hand and what that means is always understanding how far through the process we are, how far from a check point we are and knowing what it would take to check the answers to our questions or if our current solution is valid against the existing domain and code base.
|
||||
|
||||
All problems can be solved easily if the task breakdown and scoping are performed correctly. We have to get these parts right and we have to understand if we're veering off track and what to do about it. Don't feel the need to try and make up for the fact that we've become confused or have ended up in strange territory. Be honest with me and say that our solution is not going to scale or that it isn't clear how to head in the direction we're heading in and let's regroup together. It's always easier to solve a problem with two heads than one.
|
||||
|
||||
When we report a bug the sub agent should take all of the context that we can pass to it from our conversation so far as relevant to the bug that I'm reporting. When we use this bug report command, the content of what I report is the observed behavior I'm seeing, but we also need to actually produce the steps that reliably could lead up to this or might reproduce this. Maybe some hypothesis of what could be done. Maybe a way to fix it. And we want to document the details of this bug in BUGS.md under a new heading. We want to make sure that this bug could be reproduced by somebody who was starting a blank session in this code base and trying to boot up and work on it. So we have to make sure we bring out all of our assumptions during the process of reporting it for later.
|
||||
@@ -0,0 +1,57 @@
|
||||
# Code Review
|
||||
|
||||
Create a subagent and use ultra thinking within the agent to preserve our context window.
|
||||
|
||||
# Thought Process
|
||||
|
||||
Slow is smooth and smooth is fast. We only want to change one thing at a time and we want to make each cut deliberately without ever thrashing around aimlessly. We want to precise an orderly way of moving through problems that may be ambiguous and so we have to be careful about the sizing of each change we make and think about the staged delivery of every feature. So we always start with the types, the data, the way that we can start to make something tangible happen and exist and we build up layers of complexity slowly. We don't abstract up front into a complex system nor do we write everything in one file and wait till the very end to organize it. It's organic the way that the code based develops when modules split off as they make sense to us. We watch the linguistics of the code based develop and allow it to grow in ways that are and supportive of the design. It's not a constant rigidity or a paranoia but we can never let things get out of hand and what that means is always understanding how far through the process we are, how far from a check point we are and knowing what it would take to check the answers to our questions or if our current solution is valid against the existing domain and code base.
|
||||
|
||||
All problems can be solved easily if the task breakdown and scoping are performed correctly. We have to get these parts right and we have to understand if we're veering off track and what to do about it. Don't feel the need to try and make up for the fact that we've become confused or have ended up in strange territory. Be honest with me and say that our solution is not going to scale or that it isn't clear how to head in the direction we're heading in and let's regroup together. It's always easier to solve a problem with two heads than one.
|
||||
|
||||
Review the code we have written with these priorities:
|
||||
|
||||
## Core Principles
|
||||
|
||||
**Channel the spirit of Rich Hickey**: Embrace simplicity, embrace immutability, embrace data.
|
||||
|
||||
Also consider the lessons of Erlang (Joe Armstrong), Elixir (José Valim), Elm (Evan Czaplicki), and Rust.
|
||||
|
||||
## Specific Focus Areas
|
||||
|
||||
### Code Structure
|
||||
|
||||
* **Extract pure functions** for common logic and reusable operations
|
||||
* **Pay attention to the story that parameters and names tell** - use the code as a self-documenting structure
|
||||
* **Examine similar code** to ensure consistency and avoid duplication
|
||||
* **Use consistent naming conventions** that clearly express intent
|
||||
* **Decoupled modules** - consider inversion of control, decomposition, and breaking apart large files by extracting clear domains
|
||||
* **Reusable components** - identify robust abstractions that would clarify the code and make the functionality more robust
|
||||
|
||||
### Type Safety & Data
|
||||
|
||||
* **Make invalid states unrepresentable**
|
||||
|
||||
### Error Handling
|
||||
|
||||
* **Handle errors gracefully, or design APIs that make errors impossible**
|
||||
* Prefer throwing over silent failures or unclear undefined returns
|
||||
|
||||
### Functional Style
|
||||
|
||||
* **Prefer a pure, functional programming style** over imperative approaches
|
||||
* Favor immutable data transformations in library code
|
||||
* Minimize side effects and make them explicit when necessary
|
||||
|
||||
### Code Style
|
||||
|
||||
We always prefer functional programming over spaghetti-code mutable references. Functional programming here means thinking about types and transitions between them explicitly and a focus on values over places-in-memory. It means modeling functions as the primary unit. So avoiding classes except when they are the exact correct representation for something like a resource with a lifetime kind of concept or a service with pointers to other services where classes really are an elegant way of modeling those dynamics. Wherever we can prefer static pure functions, we should do so. We don't need to abuse ideas like carrying or partial application or any convoluted functional programming concepts. More so, the spirit of it which is that function orientation is all that you need. High order functions are of course encouraged as usual and we should prefer map filter and reduce typically to standard for loop type operations. But it depends on the context and we shouldn't be absolutist in any language that we're writing about. this kind of thing.
|
||||
|
||||
We're possible we would prefer to represent things as plain data that can be printed, manipulated and operated on by a small set of well-designed functions. This is very much inspired by the closure, language, and Ridge Hickey's discussion about software engineering best practices.
|
||||
|
||||
Modules should be namespaced carefully, they should be decoupled as much as possible, and be of medium size. You don't want a constellation of tiny modules, everything overly decomposed, but when a module is straining to find a single focus, it should split into two modules, and this is something we should do all the time throughout our process of software engineering. To identify when a file or a module is growing beyond its limit, and consider the best way to split off what we're doing into at least two different pieces and continue on. So not boiling the ocean and re-igitating the whole module or code base, but splitting modules regularly to make sure we don't end up with giant files that are hard to traverse.
|
||||
|
||||
Modules may mean simply splitting a file into two, or a more formal decision about directory structure in the application. When we create or change the purpose of any *directory* module, we should write an MOD.md file in the module root.
|
||||
|
||||
### Domain-Driven design
|
||||
|
||||
We want to follow the best practices of domain-driven design, thinking about bounded contexts and ubiquitous language within those bounded contexts and how the interchanges and boundaries between those contexts will be modeled in our code base and in the runtime dynamics of our system. It's important to consider the conceptual model presented by the application's types and try as best as we can to fit it to what's intuitive to humans and true to the problem domain finding a way to express only valid statements ideally making any invalid or confusing states unrepresentable within our domain when possible. That extends to error handling and types and API design, the names of functions and modules and parameters and even variables within the code are all part of a story that will be experienced by us traversing this code base in the future and so we need to be very mindful of how we construct it.
|
||||
@@ -0,0 +1,23 @@
|
||||
# Decompose
|
||||
|
||||
Create a subagent and use ultra thinking within the agent to preserve our context window.
|
||||
|
||||
# Thought Process
|
||||
|
||||
Slow is smooth and smooth is fast. We only want to change one thing at a time and we want to make each cut deliberately without ever thrashing around aimlessly. We want to precise an orderly way of moving through problems that may be ambiguous and so we have to be careful about the sizing of each change we make and think about the staged delivery of every feature. So we always start with the types, the data, the way that we can start to make something tangible happen and exist and we build up layers of complexity slowly. We don't abstract up front into a complex system nor do we write everything in one file and wait till the very end to organize it. It's organic the way that the code based develops when modules split off as they make sense to us. We watch the linguistics of the code based develop and allow it to grow in ways that are and supportive of the design. It's not a constant rigidity or a paranoia but we can never let things get out of hand and what that means is always understanding how far through the process we are, how far from a check point we are and knowing what it would take to check the answers to our questions or if our current solution is valid against the existing domain and code base.
|
||||
|
||||
All problems can be solved easily if the task breakdown and scoping are performed correctly. We have to get these parts right and we have to understand if we're veering off track and what to do about it. Don't feel the need to try and make up for the fact that we've become confused or have ended up in strange territory. Be honest with me and say that our solution is not going to scale or that it isn't clear how to head in the direction we're heading in and let's regroup together. It's always easier to solve a problem with two heads than one.
|
||||
|
||||
# Decompose
|
||||
|
||||
Let's break this large module/monolith apart into smaller modular chunks. This might be a fractal process that takes several iterations to get right. So let's focus on first working out the right divisions of the code and breaking it apart without refactoring anything along the way. We want to just break it into logical units along whatever lines already exist and then whatever tension appears, we can then start to refactor and think more granularly from there. But any division of large files of a mixed function into somewhat useful groupings, just to preserve context windows of humans and albums alike is always useful.
|
||||
|
||||
## Modules
|
||||
|
||||
Modules should be namespaced carefully, they should be decoupled as much as possible, and be of medium size. You don't want a constellation of tiny modules, everything overly decomposed, but when a module is straining to find a single focus, it should split into two modules, and this is something we should do all the time throughout our process of software engineering. To identify when a file or a module is growing beyond its limit, and consider the best way to split off what we're doing into at least two different pieces and continue on. So not boiling the ocean and re-igitating the whole module or code base, but splitting modules regularly to make sure we don't end up with giant files that are hard to traverse.
|
||||
|
||||
Modules may mean simply splitting a file into two, or a more formal decision about directory structure in the application. When we create or change the purpose of any *directory* module, we should write an MOD.md file in the module root.
|
||||
|
||||
## Domain-Driven Design
|
||||
|
||||
We want to follow the best practices of domain-driven design, thinking about bounded contexts and ubiquitous language within those bounded contexts and how the interchanges and boundaries between those contexts will be modeled in our code base and in the runtime dynamics of our system. It's important to consider the conceptual model presented by the application's types and try as best as we can to fit it to what's intuitive to humans and true to the problem domain finding a way to express only valid statements ideally making any invalid or confusing states unrepresentable within our domain when possible. That extends to error handling and types and API design, the names of functions and modules and parameters and even variables within the code are all part of a story that will be experienced by us traversing this code base in the future and so we need to be very mindful of how we construct it.
|
||||
@@ -0,0 +1,48 @@
|
||||
# Change/Add Feature
|
||||
|
||||
Let's work on a feature: $ARGUMENTS
|
||||
|
||||
Create a subagent and use ultra thinking within the agent to preserve our context window.
|
||||
|
||||
# Thought Process
|
||||
|
||||
Slow is smooth and smooth is fast. We only want to change one thing at a time and we want to make each cut deliberately without ever thrashing around aimlessly. We want to precise an orderly way of moving through problems that may be ambiguous and so we have to be careful about the sizing of each change we make and think about the staged delivery of every feature. So we always start with the types, the data, the way that we can start to make something tangible happen and exist and we build up layers of complexity slowly. We don't abstract up front into a complex system nor do we write everything in one file and wait till the very end to organize it. It's organic the way that the code based develops when modules split off as they make sense to us. We watch the linguistics of the code based develop and allow it to grow in ways that are and supportive of the design. It's not a constant rigidity or a paranoia but we can never let things get out of hand and what that means is always understanding how far through the process we are, how far from a check point we are and knowing what it would take to check the answers to our questions or if our current solution is valid against the existing domain and code base.
|
||||
|
||||
All problems can be solved easily if the task breakdown and scoping are performed correctly. We have to get these parts right and we have to understand if we're veering off track and what to do about it. Don't feel the need to try and make up for the fact that we've become confused or have ended up in strange territory. Be honest with me and say that our solution is not going to scale or that it isn't clear how to head in the direction we're heading in and let's regroup together. It's always easier to solve a problem with two heads than one.
|
||||
|
||||
# Feature
|
||||
|
||||
When adding or changing a feature of the application consult (or create) the SPEC.md file and see if there are any features similar to this one, or if this feature already exists, or if this feature would contradict or clash with any features mentioned in the SPEC.md file. We'll need to update the spec with the description of the new feature first. If there are lots of tests for existing kinds of features, we should consider creating new tests or changing the tests to include new feature and then implementing the feature against those tests.
|
||||
|
||||
SPEC.md should be sufficient for somebody to have an understanding of how they expect the application to operate, but does not have to be a comprehensive documentation of every minute feature. It is more of a user story level description of the application. It will mention the core ubiquitous language terms, but probably will not have very much code actually embedded within it.
|
||||
|
||||
## SWE Process
|
||||
|
||||
We should follow our standard software engineering procedure that is starting with pure utility functions that move within the type domain and the type domain. And once those areas are established and we have tested utility functions work as we expect and the type domain makes sense and maps to the problem, then we start to build up into the integration domain of actually having effectful things, stateful things, render ing, blah blah blah, things that might actually require user testing or hands-on testing to confirm the validity of.
|
||||
|
||||
Make it work, make it right, make it fast in that order, where in this case work is something like sketch out the types and the utility functions and plug them together so that something happens and the light turns on, then make it right means tests hard on it, review the code and make sure that it all makes sense, plug any gaps and make assertions at runtime, make the code tight and add logging and things like that, and then make it fast. We can slightly confuse the code in the pursuit of better performance if we need to by documenting our decisions and changing the way we've abstracted things.
|
||||
|
||||
## Domain-Driven Design
|
||||
|
||||
We want to follow the best practices of domain-driven design, thinking about bounded contexts and ubiquitous language within those bounded contexts and how the interchanges and boundaries between those contexts will be modeled in our code base and in the runtime dynamics of our system. It's important to consider the conceptual model presented by the application's types and try as best as we can to fit it to what's intuitive to humans and true to the problem domain finding a way to express only valid statements ideally making any invalid or confusing states unrepresentable within our domain when possible. That extends to error handling and types and API design, the names of functions and modules and parameters and even variables within the code are all part of a story that will be experienced by us traversing this code base in the future and so we need to be very mindful of how we construct it.
|
||||
|
||||
## Code Style
|
||||
|
||||
We always prefer functional programming over spaghetti-code mutable references. Functional programming here means thinking about types and transitions between them explicitly and a focus on values over places-in-memory. It means modeling functions as the primary unit. So avoiding classes except when they are the exact correct representation for something like a resource with a lifetime kind of concept or a service with pointers to other services where classes really are an elegant way of modeling those dynamics. Wherever we can prefer static pure functions, we should do so. We don't need to abuse ideas like carrying or partial application or any convoluted functional programming concepts. More so, the spirit of it which is that function orientation is all that you need. High order functions are of course encouraged as usual and we should prefer map filter and reduce typically to standard for loop type operations. But it depends on the context and we shouldn't be absolutist in any language that we're writing about. this kind of thing.
|
||||
|
||||
We're possible we would prefer to represent things as plain data that can be printed, manipulated and operated on by a small set of well-designed functions. This is very much inspired by the closure, language, and Ridge Hickey's discussion about software engineering best practices.
|
||||
|
||||
Modules should be namespaced carefully, they should be decoupled as much as possible, and be of medium size. You don't want a constellation of tiny modules, everything overly decomposed, but when a module is straining to find a single focus, it should split into two modules, and this is something we should do all the time throughout our process of software engineering. To identify when a file or a module is growing beyond its limit, and consider the best way to split off what we're doing into at least two different pieces and continue on. So not boiling the ocean and re-igitating the whole module or code base, but splitting modules regularly to make sure we don't end up with giant files that are hard to traverse.
|
||||
|
||||
Modules may mean simply splitting a file into two, or a more formal decision about directory structure in the application. When we create or change the purpose of any *directory* module, we should write an MOD.md file in the module root.
|
||||
|
||||
## File Structure
|
||||
|
||||
`SPEC.md` - Application specification
|
||||
`LOG.md` - Development log
|
||||
`BUGS.md` - Defect reports
|
||||
`progress/` - Reports on previous work sessions
|
||||
`research/` - Answers to research questions
|
||||
`MOD.md` - Scattered in the codebase, explains modules
|
||||
`README.md` - Explains tooling and usage
|
||||
`CLAUDE.md` or `llms.txt` - Instructions for Language Model agents
|
||||
@@ -0,0 +1,38 @@
|
||||
# Harden Feature
|
||||
|
||||
Now that this is working, let's harden it.
|
||||
|
||||
Take this feature/module that we're working on from prototype status to production grade.
|
||||
|
||||
We'll need to consider how to adjust the domain model, any types etc. and where the code should ultimately live. Perhaps we need to reabstract some layers?
|
||||
|
||||
# SWE
|
||||
|
||||
We should follow our standard software engineering procedure that is starting with pure utility functions that move within the type domain and the type domain. And once those areas are established and we have tested utility functions work as we expect and the type domain makes sense and maps to the problem, then we start to build up into the integration domain of actually having effectful things, stateful things, render ing, blah blah blah, things that might actually require user testing or hands-on testing to confirm the validity of.
|
||||
|
||||
Make it work, make it right, make it fast in that order, where in this case work is something like sketch out the types and the utility functions and plug them together so that something happens and the light turns on, then make it right means tests hard on it, review the code and make sure that it all makes sense, plug any gaps and make assertions at runtime, make the code tight and add logging and things like that, and then make it fast. We can slightly confuse the code in the pursuit of better performance if we need to by documenting our decisions and changing the way we've abstracted things.
|
||||
|
||||
# Domain-Driven Design
|
||||
|
||||
We want to follow the best practices of domain-driven design, thinking about bounded contexts and ubiquitous language within those bounded contexts and how the interchanges and boundaries between those contexts will be modeled in our code base and in the runtime dynamics of our system. It's important to consider the conceptual model presented by the application's types and try as best as we can to fit it to what's intuitive to humans and true to the problem domain finding a way to express only valid statements ideally making any invalid or confusing states unrepresentable within our domain when possible. That extends to error handling and types and API design, the names of functions and modules and parameters and even variables within the code are all part of a story that will be experienced by us traversing this code base in the future and so we need to be very mindful of how we construct it.
|
||||
|
||||
# Code Style
|
||||
|
||||
We always prefer functional programming over spaghetti-code mutable references. Functional programming here means thinking about types and transitions between them explicitly and a focus on values over places-in-memory. It means modeling functions as the primary unit. So avoiding classes except when they are the exact correct representation for something like a resource with a lifetime kind of concept or a service with pointers to other services where classes really are an elegant way of modeling those dynamics. Wherever we can prefer static pure functions, we should do so. We don't need to abuse ideas like carrying or partial application or any convoluted functional programming concepts. More so, the spirit of it which is that function orientation is all that you need. High order functions are of course encouraged as usual and we should prefer map filter and reduce typically to standard for loop type operations. But it depends on the context and we shouldn't be absolutist in any language that we're writing about. this kind of thing.
|
||||
|
||||
We're possible we would prefer to represent things as plain data that can be printed, manipulated and operated on by a small set of well-designed functions. This is very much inspired by the closure, language, and Ridge Hickey's discussion about software engineering best practices.
|
||||
|
||||
Modules should be namespaced carefully, they should be decoupled as much as possible, and be of medium size. You don't want a constellation of tiny modules, everything overly decomposed, but when a module is straining to find a single focus, it should split into two modules, and this is something we should do all the time throughout our process of software engineering. To identify when a file or a module is growing beyond its limit, and consider the best way to split off what we're doing into at least two different pieces and continue on. So not boiling the ocean and re-igitating the whole module or code base, but splitting modules regularly to make sure we don't end up with giant files that are hard to traverse.
|
||||
|
||||
Modules may mean simply splitting a file into two, or a more formal decision about directory structure in the application. When we create or change the purpose of any *directory* module, we should write an MOD.md file in the module root.
|
||||
|
||||
# File Structure
|
||||
|
||||
`SPEC.md` - Application specification
|
||||
`LOG.md` - Development log
|
||||
`BUGS.md` - Defect reports
|
||||
`progress/` - Reports on previous work sessions
|
||||
`research/` - Answers to research questions
|
||||
`MOD.md` - Scattered in the codebase, explains modules
|
||||
`README.md` - Explains tooling and usage
|
||||
`CLAUDE.md` or `llms.txt` - Instructions for Language Model agents
|
||||
@@ -0,0 +1,42 @@
|
||||
# Implement Plan
|
||||
|
||||
Create one or more subagents and delegate tasks to implement the plan.
|
||||
|
||||
Create a subagent and use ultra thinking within the agent to preserve our context window.
|
||||
|
||||
# Thought Process
|
||||
|
||||
Slow is smooth and smooth is fast. We only want to change one thing at a time and we want to make each cut deliberately without ever thrashing around aimlessly. We want to precise an orderly way of moving through problems that may be ambiguous and so we have to be careful about the sizing of each change we make and think about the staged delivery of every feature. So we always start with the types, the data, the way that we can start to make something tangible happen and exist and we build up layers of complexity slowly. We don't abstract up front into a complex system nor do we write everything in one file and wait till the very end to organize it. It's organic the way that the code based develops when modules split off as they make sense to us. We watch the linguistics of the code based develop and allow it to grow in ways that are and supportive of the design. It's not a constant rigidity or a paranoia but we can never let things get out of hand and what that means is always understanding how far through the process we are, how far from a check point we are and knowing what it would take to check the answers to our questions or if our current solution is valid against the existing domain and code base.
|
||||
|
||||
All problems can be solved easily if the task breakdown and scoping are performed correctly. We have to get these parts right and we have to understand if we're veering off track and what to do about it. Don't feel the need to try and make up for the fact that we've become confused or have ended up in strange territory. Be honest with me and say that our solution is not going to scale or that it isn't clear how to head in the direction we're heading in and let's regroup together. It's always easier to solve a problem with two heads than one.
|
||||
|
||||
## SWE Process
|
||||
|
||||
We should follow our standard software engineering procedure that is starting with pure utility functions that move within the type domain and the type domain. And once those areas are established and we have tested utility functions work as we expect and the type domain makes sense and maps to the problem, then we start to build up into the integration domain of actually having effectful things, stateful things, render ing, blah blah blah, things that might actually require user testing or hands-on testing to confirm the validity of.
|
||||
|
||||
Make it work, make it right, make it fast in that order, where in this case work is something like sketch out the types and the utility functions and plug them together so that something happens and the light turns on, then make it right means tests hard on it, review the code and make sure that it all makes sense, plug any gaps and make assertions at runtime, make the code tight and add logging and things like that, and then make it fast. We can slightly confuse the code in the pursuit of better performance if we need to by documenting our decisions and changing the way we've abstracted things.
|
||||
|
||||
## Domain-Driven Design
|
||||
|
||||
We want to follow the best practices of domain-driven design, thinking about bounded contexts and ubiquitous language within those bounded contexts and how the interchanges and boundaries between those contexts will be modeled in our code base and in the runtime dynamics of our system. It's important to consider the conceptual model presented by the application's types and try as best as we can to fit it to what's intuitive to humans and true to the problem domain finding a way to express only valid statements ideally making any invalid or confusing states unrepresentable within our domain when possible. That extends to error handling and types and API design, the names of functions and modules and parameters and even variables within the code are all part of a story that will be experienced by us traversing this code base in the future and so we need to be very mindful of how we construct it.
|
||||
|
||||
## Code Style
|
||||
|
||||
We always prefer functional programming over spaghetti-code mutable references. Functional programming here means thinking about types and transitions between them explicitly and a focus on values over places-in-memory. It means modeling functions as the primary unit. So avoiding classes except when they are the exact correct representation for something like a resource with a lifetime kind of concept or a service with pointers to other services where classes really are an elegant way of modeling those dynamics. Wherever we can prefer static pure functions, we should do so. We don't need to abuse ideas like carrying or partial application or any convoluted functional programming concepts. More so, the spirit of it which is that function orientation is all that you need. High order functions are of course encouraged as usual and we should prefer map filter and reduce typically to standard for loop type operations. But it depends on the context and we shouldn't be absolutist in any language that we're writing about. this kind of thing.
|
||||
|
||||
We're possible we would prefer to represent things as plain data that can be printed, manipulated and operated on by a small set of well-designed functions. This is very much inspired by the closure, language, and Ridge Hickey's discussion about software engineering best practices.
|
||||
|
||||
Modules should be namespaced carefully, they should be decoupled as much as possible, and be of medium size. You don't want a constellation of tiny modules, everything overly decomposed, but when a module is straining to find a single focus, it should split into two modules, and this is something we should do all the time throughout our process of software engineering. To identify when a file or a module is growing beyond its limit, and consider the best way to split off what we're doing into at least two different pieces and continue on. So not boiling the ocean and re-igitating the whole module or code base, but splitting modules regularly to make sure we don't end up with giant files that are hard to traverse.
|
||||
|
||||
Modules may mean simply splitting a file into two, or a more formal decision about directory structure in the application. When we create or change the purpose of any *directory* module, we should write an MOD.md file in the module root.
|
||||
|
||||
## File Structure
|
||||
|
||||
`SPEC.md` - Application specification
|
||||
`LOG.md` - Development log
|
||||
`BUGS.md` - Defect reports
|
||||
`progress/` - Reports on previous work sessions
|
||||
`research/` - Answers to research questions
|
||||
`MOD.md` - Scattered in the codebase, explains modules
|
||||
`README.md` - Explains tooling and usage
|
||||
`CLAUDE.md` or `llms.txt` - Instructions for Language Model agents
|
||||
@@ -0,0 +1,48 @@
|
||||
# Next Checkpoint
|
||||
|
||||
Let's work towards the next checkpoint: $ARGUMENTS
|
||||
|
||||
Create a subagent and use ultra thinking within the agent to preserve our context window.
|
||||
|
||||
# Thought Process
|
||||
|
||||
Slow is smooth and smooth is fast. We only want to change one thing at a time and we want to make each cut deliberately without ever thrashing around aimlessly. We want to precise an orderly way of moving through problems that may be ambiguous and so we have to be careful about the sizing of each change we make and think about the staged delivery of every feature. So we always start with the types, the data, the way that we can start to make something tangible happen and exist and we build up layers of complexity slowly. We don't abstract up front into a complex system nor do we write everything in one file and wait till the very end to organize it. It's organic the way that the code based develops when modules split off as they make sense to us. We watch the linguistics of the code based develop and allow it to grow in ways that are and supportive of the design. It's not a constant rigidity or a paranoia but we can never let things get out of hand and what that means is always understanding how far through the process we are, how far from a check point we are and knowing what it would take to check the answers to our questions or if our current solution is valid against the existing domain and code base.
|
||||
|
||||
All problems can be solved easily if the task breakdown and scoping are performed correctly. We have to get these parts right and we have to understand if we're veering off track and what to do about it. Don't feel the need to try and make up for the fact that we've become confused or have ended up in strange territory. Be honest with me and say that our solution is not going to scale or that it isn't clear how to head in the direction we're heading in and let's regroup together. It's always easier to solve a problem with two heads than one.
|
||||
|
||||
# Change Phases
|
||||
|
||||
If we have a previous feature, let's update `LOG.md` to explain our changes and if they were substantial, let's ask the user if they want a progress report. We should clear the context window before beginning work on the new session, so we have to update `CLAUDE.md`, `SPEC.md,`, `LOG.md` and any progress reports to document everything.
|
||||
|
||||
Then, let's use planning mode to chart a course to the next checkpoint.
|
||||
|
||||
## SWE Process
|
||||
|
||||
We should follow our standard software engineering procedure that is starting with pure utility functions that move within the type domain and the type domain. And once those areas are established and we have tested utility functions work as we expect and the type domain makes sense and maps to the problem, then we start to build up into the integration domain of actually having effectful things, stateful things, render ing, blah blah blah, things that might actually require user testing or hands-on testing to confirm the validity of.
|
||||
|
||||
Make it work, make it right, make it fast in that order, where in this case work is something like sketch out the types and the utility functions and plug them together so that something happens and the light turns on, then make it right means tests hard on it, review the code and make sure that it all makes sense, plug any gaps and make assertions at runtime, make the code tight and add logging and things like that, and then make it fast. We can slightly confuse the code in the pursuit of better performance if we need to by documenting our decisions and changing the way we've abstracted things.
|
||||
|
||||
## Domain-Driven Design
|
||||
|
||||
We want to follow the best practices of domain-driven design, thinking about bounded contexts and ubiquitous language within those bounded contexts and how the interchanges and boundaries between those contexts will be modeled in our code base and in the runtime dynamics of our system. It's important to consider the conceptual model presented by the application's types and try as best as we can to fit it to what's intuitive to humans and true to the problem domain finding a way to express only valid statements ideally making any invalid or confusing states unrepresentable within our domain when possible. That extends to error handling and types and API design, the names of functions and modules and parameters and even variables within the code are all part of a story that will be experienced by us traversing this code base in the future and so we need to be very mindful of how we construct it.
|
||||
|
||||
## Code Style
|
||||
|
||||
We always prefer functional programming over spaghetti-code mutable references. Functional programming here means thinking about types and transitions between them explicitly and a focus on values over places-in-memory. It means modeling functions as the primary unit. So avoiding classes except when they are the exact correct representation for something like a resource with a lifetime kind of concept or a service with pointers to other services where classes really are an elegant way of modeling those dynamics. Wherever we can prefer static pure functions, we should do so. We don't need to abuse ideas like carrying or partial application or any convoluted functional programming concepts. More so, the spirit of it which is that function orientation is all that you need. High order functions are of course encouraged as usual and we should prefer map filter and reduce typically to standard for loop type operations. But it depends on the context and we shouldn't be absolutist in any language that we're writing about. this kind of thing.
|
||||
|
||||
We're possible we would prefer to represent things as plain data that can be printed, manipulated and operated on by a small set of well-designed functions. This is very much inspired by the closure, language, and Ridge Hickey's discussion about software engineering best practices.
|
||||
|
||||
Modules should be namespaced carefully, they should be decoupled as much as possible, and be of medium size. You don't want a constellation of tiny modules, everything overly decomposed, but when a module is straining to find a single focus, it should split into two modules, and this is something we should do all the time throughout our process of software engineering. To identify when a file or a module is growing beyond its limit, and consider the best way to split off what we're doing into at least two different pieces and continue on. So not boiling the ocean and re-igitating the whole module or code base, but splitting modules regularly to make sure we don't end up with giant files that are hard to traverse.
|
||||
|
||||
Modules may mean simply splitting a file into two, or a more formal decision about directory structure in the application. When we create or change the purpose of any *directory* module, we should write an MOD.md file in the module root.
|
||||
|
||||
## File Structure
|
||||
|
||||
`SPEC.md` - Application specification
|
||||
`LOG.md` - Development log
|
||||
`BUGS.md` - Defect reports
|
||||
`progress/` - Reports on previous work sessions
|
||||
`research/` - Answers to research questions
|
||||
`MOD.md` - Scattered in the codebase, explains modules
|
||||
`README.md` - Explains tooling and usage
|
||||
`CLAUDE.md` or `llms.txt` - Instructions for Language Model agents
|
||||
@@ -0,0 +1,13 @@
|
||||
Create a subagent and use ultra thinking within the agent to preserve our context window.
|
||||
|
||||
# Thought Process
|
||||
|
||||
Slow is smooth and smooth is fast. We only want to change one thing at a time and we want to make each cut deliberately without ever thrashing around aimlessly. We want to precise an orderly way of moving through problems that may be ambiguous and so we have to be careful about the sizing of each change we make and think about the staged delivery of every feature. So we always start with the types, the data, the way that we can start to make something tangible happen and exist and we build up layers of complexity slowly. We don't abstract up front into a complex system nor do we write everything in one file and wait till the very end to organize it. It's organic the way that the code based develops when modules split off as they make sense to us. We watch the linguistics of the code based develop and allow it to grow in ways that are and supportive of the design. It's not a constant rigidity or a paranoia but we can never let things get out of hand and what that means is always understanding how far through the process we are, how far from a check point we are and knowing what it would take to check the answers to our questions or if our current solution is valid against the existing domain and code base.
|
||||
|
||||
All problems can be solved easily if the task breakdown and scoping are performed correctly. We have to get these parts right and we have to understand if we're veering off track and what to do about it. Don't feel the need to try and make up for the fact that we've become confused or have ended up in strange territory. Be honest with me and say that our solution is not going to scale or that it isn't clear how to head in the direction we're heading in and let's regroup together. It's always easier to solve a problem with two heads than one.
|
||||
|
||||
# Orient
|
||||
|
||||
Read the `README.md` and the core files mentioned below, fan out from there to explore the project's structure and functionality.
|
||||
|
||||
Write a report on to brief the lead project agent so we can get to work!
|
||||
@@ -0,0 +1,46 @@
|
||||
# Prototype
|
||||
|
||||
Let's prototype: $ARGUMENTS
|
||||
|
||||
Create a subagent and use ultra thinking within the agent to preserve our context window.
|
||||
|
||||
# Thought Process
|
||||
|
||||
Slow is smooth and smooth is fast. We only want to change one thing at a time and we want to make each cut deliberately without ever thrashing around aimlessly. We want to precise an orderly way of moving through problems that may be ambiguous and so we have to be careful about the sizing of each change we make and think about the staged delivery of every feature. So we always start with the types, the data, the way that we can start to make something tangible happen and exist and we build up layers of complexity slowly. We don't abstract up front into a complex system nor do we write everything in one file and wait till the very end to organize it. It's organic the way that the code based develops when modules split off as they make sense to us. We watch the linguistics of the code based develop and allow it to grow in ways that are and supportive of the design. It's not a constant rigidity or a paranoia but we can never let things get out of hand and what that means is always understanding how far through the process we are, how far from a check point we are and knowing what it would take to check the answers to our questions or if our current solution is valid against the existing domain and code base.
|
||||
|
||||
All problems can be solved easily if the task breakdown and scoping are performed correctly. We have to get these parts right and we have to understand if we're veering off track and what to do about it. Don't feel the need to try and make up for the fact that we've become confused or have ended up in strange territory. Be honest with me and say that our solution is not going to scale or that it isn't clear how to head in the direction we're heading in and let's regroup together. It's always easier to solve a problem with two heads than one.
|
||||
|
||||
# Prototype
|
||||
|
||||
When we're prototyping, we're not "just hacking" but we don't want to have bogged down in the details. No need to try to make an immaculate model for a domain. We might not commit to it ultimately. So prototyping means to me that we will look to extend the existing domain (see Domain-Driven Design) in some way or remix or recompose pieces of it cleverly to get an idea of whether this is possible and is a good direction to proceed in. And then after prototyping phase, we will harden it in our normal software engineering loop. So prototyping means that we want to move quickly without what was stressing about the layout of code, the way we might if we were just adding a new feature that we knew we wanted to commit to, to the code base.
|
||||
|
||||
## SWE Process
|
||||
|
||||
We should follow our standard software engineering procedure that is starting with pure utility functions that move within the type domain and the type domain. And once those areas are established and we have tested utility functions work as we expect and the type domain makes sense and maps to the problem, then we start to build up into the integration domain of actually having effectful things, stateful things, render ing, blah blah blah, things that might actually require user testing or hands-on testing to confirm the validity of.
|
||||
|
||||
Make it work, make it right, make it fast in that order, where in this case work is something like sketch out the types and the utility functions and plug them together so that something happens and the light turns on, then make it right means tests hard on it, review the code and make sure that it all makes sense, plug any gaps and make assertions at runtime, make the code tight and add logging and things like that, and then make it fast. We can slightly confuse the code in the pursuit of better performance if we need to by documenting our decisions and changing the way we've abstracted things.
|
||||
|
||||
## Domain-Driven Design
|
||||
|
||||
We want to follow the best practices of domain-driven design, thinking about bounded contexts and ubiquitous language within those bounded contexts and how the interchanges and boundaries between those contexts will be modeled in our code base and in the runtime dynamics of our system. It's important to consider the conceptual model presented by the application's types and try as best as we can to fit it to what's intuitive to humans and true to the problem domain finding a way to express only valid statements ideally making any invalid or confusing states unrepresentable within our domain when possible. That extends to error handling and types and API design, the names of functions and modules and parameters and even variables within the code are all part of a story that will be experienced by us traversing this code base in the future and so we need to be very mindful of how we construct it.
|
||||
|
||||
## Code Style
|
||||
|
||||
We always prefer functional programming over spaghetti-code mutable references. Functional programming here means thinking about types and transitions between them explicitly and a focus on values over places-in-memory. It means modeling functions as the primary unit. So avoiding classes except when they are the exact correct representation for something like a resource with a lifetime kind of concept or a service with pointers to other services where classes really are an elegant way of modeling those dynamics. Wherever we can prefer static pure functions, we should do so. We don't need to abuse ideas like carrying or partial application or any convoluted functional programming concepts. More so, the spirit of it which is that function orientation is all that you need. High order functions are of course encouraged as usual and we should prefer map filter and reduce typically to standard for loop type operations. But it depends on the context and we shouldn't be absolutist in any language that we're writing about. this kind of thing.
|
||||
|
||||
We're possible we would prefer to represent things as plain data that can be printed, manipulated and operated on by a small set of well-designed functions. This is very much inspired by the closure, language, and Ridge Hickey's discussion about software engineering best practices.
|
||||
|
||||
Modules should be namespaced carefully, they should be decoupled as much as possible, and be of medium size. You don't want a constellation of tiny modules, everything overly decomposed, but when a module is straining to find a single focus, it should split into two modules, and this is something we should do all the time throughout our process of software engineering. To identify when a file or a module is growing beyond its limit, and consider the best way to split off what we're doing into at least two different pieces and continue on. So not boiling the ocean and re-igitating the whole module or code base, but splitting modules regularly to make sure we don't end up with giant files that are hard to traverse.
|
||||
|
||||
Modules may mean simply splitting a file into two, or a more formal decision about directory structure in the application. When we create or change the purpose of any *directory* module, we should write an MOD.md file in the module root.
|
||||
|
||||
## File Structure
|
||||
|
||||
`SPEC.md` - Application specification
|
||||
`LOG.md` - Development log
|
||||
`BUGS.md` - Defect reports
|
||||
`progress/` - Reports on previous work sessions
|
||||
`research/` - Answers to research questions
|
||||
`MOD.md` - Scattered in the codebase, explains modules
|
||||
`README.md` - Explains tooling and usage
|
||||
`CLAUDE.md` or `llms.txt` - Instructions for Language Model agents
|
||||
@@ -0,0 +1,28 @@
|
||||
# Research Task
|
||||
|
||||
Research this: $ARGUMENTS
|
||||
|
||||
Create a subagent and use ultra thinking within the agent to preserve our context window.
|
||||
|
||||
# Thought Process
|
||||
|
||||
Slow is smooth and smooth is fast. We only want to change one thing at a time and we want to make each cut deliberately without ever thrashing around aimlessly. We want to precise an orderly way of moving through problems that may be ambiguous and so we have to be careful about the sizing of each change we make and think about the staged delivery of every feature. So we always start with the types, the data, the way that we can start to make something tangible happen and exist and we build up layers of complexity slowly. We don't abstract up front into a complex system nor do we write everything in one file and wait till the very end to organize it. It's organic the way that the code based develops when modules split off as they make sense to us. We watch the linguistics of the code based develop and allow it to grow in ways that are and supportive of the design. It's not a constant rigidity or a paranoia but we can never let things get out of hand and what that means is always understanding how far through the process we are, how far from a check point we are and knowing what it would take to check the answers to our questions or if our current solution is valid against the existing domain and code base.
|
||||
|
||||
All problems can be solved easily if the task breakdown and scoping are performed correctly. We have to get these parts right and we have to understand if we're veering off track and what to do about it. Don't feel the need to try and make up for the fact that we've become confused or have ended up in strange territory. Be honest with me and say that our solution is not going to scale or that it isn't clear how to head in the direction we're heading in and let's regroup together. It's always easier to solve a problem with two heads than one.
|
||||
|
||||
# Research
|
||||
|
||||
Research the codebase to find out the answer to this question. When I say research, this means first explore the SPEC.md, the LOG.md, any recent progress reports (`progress/`) to understand if this question has been changed recently, then explore as much of the codebase as is necessary to get a thorough understanding of the answer to the question. Consider reading the tests as a way of understanding what features and assertions we already make about the codebase, and we should understand if the tests are passing if the research question is implicitly covered by one of the test cases.
|
||||
|
||||
Write the results in `research/YYYYMMDD_QUESTION.md` where the filename is dynamic.
|
||||
|
||||
## File Structure
|
||||
|
||||
`SPEC.md` - Application specification
|
||||
`LOG.md` - Development log
|
||||
`BUGS.md` - Defect reports
|
||||
`progress/` - Reports on previous work sessions
|
||||
`research/` - Answers to research questions
|
||||
`MOD.md` - Scattered in the codebase, explains modules
|
||||
`README.md` - Explains tooling and usage
|
||||
`CLAUDE.md` or `llms.txt` - Instructions for Language Model agents
|
||||
@@ -0,0 +1,5 @@
|
||||
# Prototype
|
||||
|
||||
Let's prototype: $ARGUMENTS
|
||||
|
||||
!include(./lib/prototyping.md)
|
||||
@@ -0,0 +1,5 @@
|
||||
# Research Task
|
||||
|
||||
Research this: $ARGUMENTS
|
||||
|
||||
!include(./lib/research.md)
|
||||
Reference in New Issue
Block a user