Compare commits
4 Commits
8318e60c33
...
hydrators-
| Author | SHA1 | Date | |
|---|---|---|---|
| 5947dcdc86 | |||
| f0aca17a0a | |||
| 5be7b84972 | |||
| ae077886ba |
44
AGENTS.md
Normal file
44
AGENTS.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Agent Instructions
|
||||
|
||||
Read and follow the instructions in `diachron/AGENTS.md`. That file
|
||||
contains framework conventions, commands, and structure that apply to
|
||||
all coding agents working on diachron-based projects.
|
||||
|
||||
This project uses **bd** (beads) for issue tracking. Run `bd onboard` to get started.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```bash
|
||||
bd ready # Find available work
|
||||
bd show <id> # View issue details
|
||||
bd update <id> --status in_progress # Claim work
|
||||
bd close <id> # Complete work
|
||||
bd sync # Sync with git
|
||||
```
|
||||
|
||||
## Landing the Plane (Session Completion)
|
||||
|
||||
**When ending a work session**, you MUST complete ALL steps below. Work is NOT complete until `git push` succeeds.
|
||||
|
||||
**MANDATORY WORKFLOW:**
|
||||
|
||||
1. **File issues for remaining work** - Create issues for anything that needs follow-up
|
||||
2. **Run quality gates** (if code changed) - Tests, linters, builds
|
||||
3. **Update issue status** - Close finished work, update in-progress items
|
||||
4. **PUSH TO REMOTE** - This is MANDATORY:
|
||||
```bash
|
||||
git pull --rebase
|
||||
bd sync
|
||||
git push
|
||||
git status # MUST show "up to date with origin"
|
||||
```
|
||||
5. **Clean up** - Clear stashes, prune remote branches
|
||||
6. **Verify** - All changes committed AND pushed
|
||||
7. **Hand off** - Provide context for next session
|
||||
|
||||
**CRITICAL RULES:**
|
||||
- Work is NOT complete until `git push` succeeds
|
||||
- NEVER stop before pushing - that leaves work stranded locally
|
||||
- NEVER say "ready to push when you are" - YOU must push
|
||||
- If push fails, resolve and retry until it succeeds
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with
|
||||
code in this repository.
|
||||
|
||||
Read and follow the instructions in `diachron/AGENTS.md`. That file
|
||||
contains framework conventions, commands, and structure that apply to
|
||||
all coding agents working on diachron-based projects.
|
||||
|
||||
## Project Overview
|
||||
|
||||
Diachron is an opinionated TypeScript/Node.js web framework with a Go-based
|
||||
|
||||
20
DIACHRON.md
20
DIACHRON.md
@@ -88,8 +88,24 @@ There are two owners of files in a diachron project:
|
||||
`backend/diachron/`, and the top-level scripts (`cmd`, `develop`, `mgmt`,
|
||||
`sync.sh`, `check.sh`).
|
||||
|
||||
Don't modify framework-owned files. This separation keeps framework upgrades
|
||||
clean.
|
||||
Don't modify framework-owned files unless you need to. This separation
|
||||
keeps framework upgrades clean. If you do need to change framework files
|
||||
(especially early on, there are rough edges), you can extract your changes
|
||||
as a patch:
|
||||
|
||||
```bash
|
||||
./diff-upstream.sh # full diff against upstream
|
||||
./diff-upstream.sh --stat # just list changed files
|
||||
```
|
||||
|
||||
This diffs every file in `file-list` against the upstream ref recorded in
|
||||
`.diachron-version`.
|
||||
|
||||
When you do change framework files, make each change in its own commit with
|
||||
a clear message explaining what the change is and why it's needed. Mixing
|
||||
framework fixes with application work in a single commit makes it much
|
||||
harder to upstream later. A clean history of discrete, well-explained
|
||||
framework commits is the easiest thing to turn into contributions.
|
||||
|
||||
## Getting started
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ const makeApp = ({routes, processTitle}: MakeAppArgs) => {
|
||||
console.log("DEBUG: trying pattern, match result =", match);
|
||||
if (match) {
|
||||
console.log("match", match);
|
||||
const resp = await pr.handler(req);
|
||||
const resp = await pr.handler(req, match.params);
|
||||
|
||||
return resp;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ const processRoutes=(routes:Route[]) :ProcessedRoutes => {
|
||||
|
||||
const handler: InternalHandler = async (
|
||||
expressRequest: ExpressRequest,
|
||||
params: Record<string, string>,
|
||||
): Promise<Result> => {
|
||||
const method = massageMethod(expressRequest.method);
|
||||
|
||||
@@ -47,7 +48,7 @@ const processRoutes=(routes:Route[]) :ProcessedRoutes => {
|
||||
pattern: route.path,
|
||||
path: expressRequest.originalUrl,
|
||||
method,
|
||||
parameters: { one: 1, two: 2 },
|
||||
parameters: params,
|
||||
request: expressRequest,
|
||||
user: auth.user,
|
||||
session: new Session(auth.session, auth.user),
|
||||
|
||||
@@ -29,13 +29,13 @@ export type Call = {
|
||||
pattern: string;
|
||||
path: string;
|
||||
method: Method;
|
||||
parameters: object;
|
||||
parameters: Record<string, string>;
|
||||
request: ExpressRequest;
|
||||
user: User;
|
||||
session: Session;
|
||||
};
|
||||
|
||||
export type InternalHandler = (req: ExpressRequest) => Promise<Result>;
|
||||
export type InternalHandler = (req: ExpressRequest, params: Record<string, string>) => Promise<Result>;
|
||||
|
||||
export type Handler = (call: Call) => Promise<Result>;
|
||||
export type ProcessedRoute = {
|
||||
|
||||
@@ -42,8 +42,8 @@ echo working dir: $PWD
|
||||
|
||||
# exit 0
|
||||
|
||||
tar cvf - $(cat "$PWD/file-list" | grep -v '^#') | (cd "$here" && tar xf -)
|
||||
tar cvf - $(cat "$PWD/file-list" | grep -v '^#' | sed 's/^?//') | (cd "$here" && tar xf -)
|
||||
|
||||
echo "$ref" > .diachron-version
|
||||
echo "$ref" > "$here/.diachron-version"
|
||||
|
||||
echo "Now, run the command ./sync.sh"
|
||||
|
||||
@@ -137,6 +137,14 @@ Do not edit: master/*, logger/*, diachron/*, backend/diachron/*
|
||||
```
|
||||
|
||||
If a task requires framework changes, confirm with the user first.
|
||||
When framework files are modified, the changes can be extracted as a
|
||||
diff against upstream with `./diff-upstream.sh` (or `--stat` to list
|
||||
changed files only).
|
||||
|
||||
When committing framework changes, keep them in separate commits from
|
||||
application code. Each framework commit should have a clear message
|
||||
explaining what was changed and why. This makes it much easier to
|
||||
upstream the changes later.
|
||||
|
||||
### Command safety tiers
|
||||
|
||||
@@ -179,6 +187,21 @@ framework. When you create or delete a file that is part of the project
|
||||
(not a scratch file or generated output), you must update `file-list` to
|
||||
match. Keep it sorted alphabetically.
|
||||
|
||||
Entries can have a `?` prefix (e.g. `?backend/app.ts`). These are
|
||||
**sample files** -- starter code that `bootstrap.sh` copies into a new
|
||||
project but that `upgrade.sh` will not overwrite. Once the user has the
|
||||
file, it belongs to them. On upgrade, new sample files that don't exist
|
||||
yet in the project are copied in; existing ones are left untouched.
|
||||
|
||||
Unprefixed entries are **framework-owned** and are always replaced on
|
||||
upgrade. When adding a new file to `file-list`, decide which category
|
||||
it belongs to:
|
||||
|
||||
- Framework-owned (no prefix): infrastructure scripts, framework
|
||||
library code, build tooling, config that must stay in sync.
|
||||
- Sample (`?` prefix): application starter code the user is expected
|
||||
to edit (routes, handlers, services, types, package.json, etc.).
|
||||
|
||||
## Things to avoid
|
||||
|
||||
- Do not introduce `.env` files or `dotenv` without checking with the
|
||||
|
||||
116
diff-upstream.sh
Executable file
116
diff-upstream.sh
Executable file
@@ -0,0 +1,116 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Generate a diff of framework files against the upstream version this
|
||||
# project is based on. Useful for contributing changes back to diachron.
|
||||
#
|
||||
# Usage:
|
||||
# ./diff-upstream.sh # diff against .diachron-version
|
||||
# ./diff-upstream.sh <ref> # diff against a specific ref
|
||||
# ./diff-upstream.sh --stat # show changed files only
|
||||
|
||||
set -eu
|
||||
set -o pipefail
|
||||
IFS=$'\n\t'
|
||||
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
stat_only=false
|
||||
ref=""
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--stat) stat_only=true ;;
|
||||
*) ref="$arg" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$ref" ]; then
|
||||
if [ ! -f "$DIR/.diachron-version" ]; then
|
||||
echo "Error: .diachron-version not found and no ref specified." >&2
|
||||
echo "Usage: $0 [--stat] [<ref>]" >&2
|
||||
exit 1
|
||||
fi
|
||||
ref=$(cat "$DIR/.diachron-version")
|
||||
fi
|
||||
|
||||
cached_repo="$HOME/.cache/diachron/v1/repositories/diachron.git"
|
||||
|
||||
if [ ! -d "$cached_repo" ]; then
|
||||
echo "Error: cached repository not found at $cached_repo" >&2
|
||||
echo "Run ./update-cached-repository.sh first." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Update cached repo
|
||||
"$DIR/update-cached-repository.sh"
|
||||
|
||||
# Verify ref exists
|
||||
if ! git -C "$cached_repo" rev-parse --verify "$ref^{commit}" >/dev/null 2>&1; then
|
||||
echo "Error: ref '$ref' not found in cached repository." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Read file-list (strip ? prefix from sample entries)
|
||||
files=()
|
||||
while IFS= read -r line; do
|
||||
[[ "$line" =~ ^[[:space:]]*# ]] && continue
|
||||
[[ -z "$line" ]] && continue
|
||||
files+=("${line#\?}")
|
||||
done < "$DIR/file-list"
|
||||
|
||||
# Check out upstream into a temp directory
|
||||
tmpdir=$(mktemp -d)
|
||||
cleanup() { rm -rf "$tmpdir"; }
|
||||
trap cleanup EXIT
|
||||
|
||||
git clone --quiet "$cached_repo" "$tmpdir/upstream"
|
||||
git -C "$tmpdir/upstream" checkout --quiet "$ref"
|
||||
|
||||
# Generate diff
|
||||
if $stat_only; then
|
||||
diff -rq "$tmpdir/upstream" "$DIR" \
|
||||
--no-dereference \
|
||||
2>/dev/null \
|
||||
| grep -v '^\.' \
|
||||
|| true
|
||||
|
||||
# Simpler: just list files that differ
|
||||
for f in "${files[@]}"; do
|
||||
# Skip directories
|
||||
[ -d "$DIR/$f" ] && continue
|
||||
|
||||
upstream="$tmpdir/upstream/$f"
|
||||
local="$DIR/$f"
|
||||
|
||||
if [ ! -f "$upstream" ] && [ -f "$local" ]; then
|
||||
echo "added: $f"
|
||||
elif [ -f "$upstream" ] && [ ! -f "$local" ]; then
|
||||
echo "removed: $f"
|
||||
elif [ -f "$upstream" ] && [ -f "$local" ]; then
|
||||
if ! diff -q "$upstream" "$local" >/dev/null 2>&1; then
|
||||
echo "modified: $f"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
else
|
||||
for f in "${files[@]}"; do
|
||||
[ -d "$DIR/$f" ] && continue
|
||||
|
||||
upstream="$tmpdir/upstream/$f"
|
||||
local="$DIR/$f"
|
||||
|
||||
if [ ! -f "$upstream" ] && [ -f "$local" ]; then
|
||||
diff -u /dev/null "$local" \
|
||||
--label "a/$f" --label "b/$f" \
|
||||
|| true
|
||||
elif [ -f "$upstream" ] && [ ! -f "$local" ]; then
|
||||
diff -u "$upstream" /dev/null \
|
||||
--label "a/$f" --label "b/$f" \
|
||||
|| true
|
||||
elif [ -f "$upstream" ] && [ -f "$local" ]; then
|
||||
diff -u "$upstream" "$local" \
|
||||
--label "a/$f" --label "b/$f" \
|
||||
|| true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
22
file-list
22
file-list
@@ -1,31 +1,37 @@
|
||||
# please keep this file sorted alphabetically
|
||||
#
|
||||
# Files prefixed with ? are sample/starter files. bootstrap.sh copies them
|
||||
# into a new project, but upgrade.sh will not overwrite them if the user has
|
||||
# already modified or replaced them. Unprefixed files are framework-owned
|
||||
# and are always replaced on upgrade.
|
||||
|
||||
.gitignore
|
||||
.go-version
|
||||
DIACHRON.md
|
||||
backend/.gitignore
|
||||
backend/.npmrc
|
||||
backend/app.ts
|
||||
?backend/app.ts
|
||||
backend/build.sh
|
||||
backend/check-deps.ts
|
||||
backend/check.sh
|
||||
backend/diachron
|
||||
backend/generated
|
||||
backend/group.ts
|
||||
backend/handlers.spec.ts
|
||||
backend/handlers.ts
|
||||
backend/package.json
|
||||
?backend/group.ts
|
||||
?backend/handlers.spec.ts
|
||||
?backend/handlers.ts
|
||||
?backend/package.json
|
||||
backend/pnpm-workspace.yaml
|
||||
backend/routes.ts
|
||||
?backend/routes.ts
|
||||
backend/run.sh
|
||||
backend/services.ts
|
||||
?backend/services.ts
|
||||
backend/show-config.sh
|
||||
backend/tsconfig.json
|
||||
backend/types.ts
|
||||
?backend/types.ts
|
||||
backend/watch.sh
|
||||
bootstrap.sh
|
||||
cmd
|
||||
develop
|
||||
diff-upstream.sh
|
||||
diachron
|
||||
diachron/AGENTS.md
|
||||
file-list
|
||||
|
||||
50
upgrade.sh
50
upgrade.sh
@@ -62,11 +62,17 @@ echo "Upgrading: $old_ref -> $new_ref"
|
||||
echo ""
|
||||
|
||||
# Read current file-list (files to remove)
|
||||
# Entries prefixed with ? are sample files -- we don't remove those on upgrade.
|
||||
old_files=()
|
||||
old_samples=()
|
||||
while IFS= read -r line; do
|
||||
[[ "$line" =~ ^[[:space:]]*# ]] && continue
|
||||
[[ -z "$line" ]] && continue
|
||||
old_files+=("$line")
|
||||
if [[ "$line" == \?* ]]; then
|
||||
old_samples+=("${line#\?}")
|
||||
else
|
||||
old_files+=("$line")
|
||||
fi
|
||||
done < "$DIR/file-list"
|
||||
|
||||
# Clone and checkout new version into a temp directory
|
||||
@@ -76,25 +82,44 @@ git -C "$tmpdir/diachron" checkout --quiet "$new_ref"
|
||||
|
||||
# Read new file-list (files to add)
|
||||
new_files=()
|
||||
new_samples=()
|
||||
while IFS= read -r line; do
|
||||
[[ "$line" =~ ^[[:space:]]*# ]] && continue
|
||||
[[ -z "$line" ]] && continue
|
||||
new_files+=("$line")
|
||||
if [[ "$line" == \?* ]]; then
|
||||
new_samples+=("${line#\?}")
|
||||
else
|
||||
new_files+=("$line")
|
||||
fi
|
||||
done < "$tmpdir/diachron/file-list"
|
||||
|
||||
# Remove old framework files
|
||||
# Remove old framework files (not samples -- those belong to the user)
|
||||
for f in "${old_files[@]}"; do
|
||||
git -C "$DIR" rm -rf --quiet --ignore-unmatch "$f"
|
||||
done
|
||||
|
||||
# Copy in new framework files
|
||||
(cd "$tmpdir/diachron" && tar cvf - "${new_files[@]}") | (cd "$DIR" && tar xf -)
|
||||
(cd "$tmpdir/diachron" && tar cf - "${new_files[@]}") | (cd "$DIR" && tar xf -)
|
||||
|
||||
# Stage them
|
||||
for f in "${new_files[@]}"; do
|
||||
git -C "$DIR" add "$f"
|
||||
done
|
||||
|
||||
# Handle sample files: copy only if the user doesn't already have them
|
||||
samples_added=()
|
||||
samples_skipped=()
|
||||
for f in "${new_samples[@]}"; do
|
||||
if [ -e "$DIR/$f" ]; then
|
||||
samples_skipped+=("$f")
|
||||
else
|
||||
# New sample that doesn't exist yet -- copy it in
|
||||
(cd "$tmpdir/diachron" && tar cf - "$f") | (cd "$DIR" && tar xf -)
|
||||
git -C "$DIR" add "$f"
|
||||
samples_added+=("$f")
|
||||
fi
|
||||
done
|
||||
|
||||
# Update version marker
|
||||
echo "$new_ref" > "$DIR/.diachron-version"
|
||||
git -C "$DIR" add "$DIR/.diachron-version"
|
||||
@@ -102,6 +127,23 @@ git -C "$DIR" add "$DIR/.diachron-version"
|
||||
echo "=== Upgrade staged: $old_ref -> $new_ref ==="
|
||||
echo ""
|
||||
echo "Framework files have been removed, replaced, and staged."
|
||||
|
||||
if [ ${#samples_added[@]} -gt 0 ]; then
|
||||
echo ""
|
||||
echo "New sample files added:"
|
||||
for f in "${samples_added[@]}"; do
|
||||
echo " + $f"
|
||||
done
|
||||
fi
|
||||
|
||||
if [ ${#samples_skipped[@]} -gt 0 ]; then
|
||||
echo ""
|
||||
echo "Sample files skipped (you already have these):"
|
||||
for f in "${samples_skipped[@]}"; do
|
||||
echo " ~ $f"
|
||||
done
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. Review: git diff --cached"
|
||||
|
||||
Reference in New Issue
Block a user