@c0x12c/spartan-ai-toolkit 1.8.4 → 1.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +69 -0
- package/VERSION +1 -1
- package/commands/spartan/build.md +123 -66
- package/commands/spartan.md +12 -0
- package/package.json +1 -1
- package/packs/core.yaml +1 -0
- package/packs/packs.compiled.json +1 -0
- package/rules/core/TIMEZONE.md +261 -0
- package/templates/build-config.yaml +63 -0
- package/templates/commands-config.yaml +55 -0
package/README.md
CHANGED
|
@@ -316,6 +316,7 @@ Rules are enforced automatically every session. No action needed — they're act
|
|
|
316
316
|
| Rule | Pack |
|
|
317
317
|
|------|------|
|
|
318
318
|
| `NAMING_CONVENTIONS` | core |
|
|
319
|
+
| `TIMEZONE` | core |
|
|
319
320
|
| `ARCHITECTURE` | shared-backend |
|
|
320
321
|
| `SCHEMA` | database |
|
|
321
322
|
| `ORM_AND_REPO` | database |
|
|
@@ -358,6 +359,74 @@ For other tools, copy the rule files from `toolkit/rules/` into your tool's conf
|
|
|
358
359
|
|
|
359
360
|
---
|
|
360
361
|
|
|
362
|
+
## Parallel Builds
|
|
363
|
+
|
|
364
|
+
By default, `/spartan:build` creates a **git worktree** per feature — a separate directory with its own branch. This means you can build 2+ features in parallel from different terminals:
|
|
365
|
+
|
|
366
|
+
```bash
|
|
367
|
+
# Terminal 1 # Terminal 2
|
|
368
|
+
claude claude
|
|
369
|
+
> /spartan:build auth > /spartan:build payments
|
|
370
|
+
# → .claude/worktrees/feature-auth/ # → .claude/worktrees/feature-payments/
|
|
371
|
+
# → PR #1 # → PR #2
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
No conflicts. Each session gets its own worktree, branch, and PR.
|
|
375
|
+
|
|
376
|
+
To disable worktrees (single-terminal mode), set `worktree: false` in `.spartan/build.yaml`.
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
## Project Config
|
|
381
|
+
|
|
382
|
+
Customize any Spartan command per project. Two config files in `.spartan/`:
|
|
383
|
+
|
|
384
|
+
### `.spartan/build.yaml` — Build workflow config
|
|
385
|
+
|
|
386
|
+
Controls `/spartan:build` behavior:
|
|
387
|
+
|
|
388
|
+
```yaml
|
|
389
|
+
worktree: true # git worktree per feature (default: true)
|
|
390
|
+
branch-prefix: "feature" # branch name: [prefix]/[slug]
|
|
391
|
+
max-review-rounds: 3 # review-fix cycles before asking user
|
|
392
|
+
skip-stages: [] # skip: spec, design, plan, ship (never review)
|
|
393
|
+
worktree-symlinks: [] # extra dirs to share across worktrees
|
|
394
|
+
|
|
395
|
+
prompts:
|
|
396
|
+
spec: |
|
|
397
|
+
Always include performance requirements.
|
|
398
|
+
plan: |
|
|
399
|
+
Every task must reference a Jira ticket.
|
|
400
|
+
implement: |
|
|
401
|
+
Add structured logging to new service methods.
|
|
402
|
+
review: |
|
|
403
|
+
Check all API responses include request_id.
|
|
404
|
+
ship: |
|
|
405
|
+
PR title: [PROJ-123] Short description.
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### `.spartan/commands.yaml` — Per-command prompt injection
|
|
409
|
+
|
|
410
|
+
Inject custom instructions into ANY Spartan command:
|
|
411
|
+
|
|
412
|
+
```yaml
|
|
413
|
+
prompts:
|
|
414
|
+
review: |
|
|
415
|
+
Flag any function longer than 50 lines.
|
|
416
|
+
pr-ready: |
|
|
417
|
+
Always add "Reviewers: @backend-team" for backend changes.
|
|
418
|
+
daily: |
|
|
419
|
+
Include blockers section. Tag by project area.
|
|
420
|
+
debug: |
|
|
421
|
+
Always check CloudWatch logs first.
|
|
422
|
+
migration: |
|
|
423
|
+
Migration files must start with ticket number.
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
Templates for both files are in `toolkit/templates/`.
|
|
427
|
+
|
|
428
|
+
---
|
|
429
|
+
|
|
361
430
|
## Target Stack
|
|
362
431
|
|
|
363
432
|
Rules and skills are tuned for this stack, but the command framework works with anything:
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.
|
|
1
|
+
1.9.1
|
|
@@ -14,23 +14,22 @@ You decide which steps to run, which skills to call, and when to move forward. T
|
|
|
14
14
|
```
|
|
15
15
|
SINGLE FEATURE:
|
|
16
16
|
|
|
17
|
-
Context → Spec → Design? → Plan
|
|
18
|
-
│ │ │
|
|
19
|
-
.memory/ Gate 1 Design
|
|
20
|
-
Gate
|
|
17
|
+
Context → Spec → Design? → Plan → Implement → Review Agent → Fix → Ship
|
|
18
|
+
│ │ │ │ │ │ │ │
|
|
19
|
+
.memory/ Gate 1 Design Gate 2 Gate 3 Spawn agent Loop Gate 4
|
|
20
|
+
Gate fix until OK
|
|
21
21
|
|
|
22
22
|
EPIC (multi-feature — auto-detected):
|
|
23
23
|
|
|
24
|
-
Context → Epic detected → Per feature: Spec/Design/Plan →
|
|
25
|
-
│ │ │
|
|
26
|
-
.planning/ read epic fill gaps if needed
|
|
27
|
-
epics/
|
|
24
|
+
Context → Epic detected → Per feature: Spec/Design/Plan → Implement → Review Agent → Fix → Ship
|
|
25
|
+
│ │ │ │ │ │ │
|
|
26
|
+
.planning/ read epic fill gaps if needed parallel by Spawn agent Loop one PR
|
|
27
|
+
epics/ dependency fix until OK
|
|
28
28
|
|
|
29
|
-
PARALLEL BUILDS (
|
|
29
|
+
PARALLEL BUILDS (automatic — each build in its own worktree):
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
(each terminal gets its own worktree, branch, and PR — no conflicts)
|
|
31
|
+
/spartan:build auth → worktree .claude/worktrees/feature-auth/ → PR #1
|
|
32
|
+
/spartan:build payments → worktree .claude/worktrees/feature-payments/ → PR #2
|
|
34
33
|
```
|
|
35
34
|
|
|
36
35
|
**Fast path:** For small work (< 1 day, ≤ 4 tasks), you do spec + plan inline. No separate commands needed.
|
|
@@ -52,6 +51,86 @@ PARALLEL BUILDS (multiple terminals):
|
|
|
52
51
|
|
|
53
52
|
---
|
|
54
53
|
|
|
54
|
+
## FIRST: Load Build Config & Enter Worktree
|
|
55
|
+
|
|
56
|
+
### Load project build config
|
|
57
|
+
|
|
58
|
+
Check for a project-level build config that overrides default behavior:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
cat .spartan/build.yaml 2>/dev/null || cat .spartan/build.yml 2>/dev/null
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
If `.spartan/build.yaml` exists, read it and apply overrides. All fields are optional — omit to use defaults.
|
|
65
|
+
|
|
66
|
+
| Field | Default | What it does |
|
|
67
|
+
|-------|---------|-------------|
|
|
68
|
+
| `worktree` | `true` | Create a git worktree per build. Set `false` for single-terminal workflow. |
|
|
69
|
+
| `branch-prefix` | `"feature"` | Branch name: `[prefix]/[slug]` (e.g., `feature/user-auth`) |
|
|
70
|
+
| `max-review-rounds` | `3` | Max review-fix cycles before asking the user |
|
|
71
|
+
| `skip-stages` | `[]` | Stages to skip. Valid: `spec`, `design`, `plan`, `review`, `ship` |
|
|
72
|
+
| `worktree-symlinks` | `[]` | Extra gitignored dirs to symlink into worktrees |
|
|
73
|
+
| `prompts.spec` | — | Custom instructions injected after spec questions |
|
|
74
|
+
| `prompts.plan` | — | Custom instructions injected into the plan stage |
|
|
75
|
+
| `prompts.implement` | — | Custom instructions injected during implementation |
|
|
76
|
+
| `prompts.review` | — | Custom instructions injected into the review agent's prompt |
|
|
77
|
+
| `prompts.ship` | — | Custom instructions injected before PR creation |
|
|
78
|
+
|
|
79
|
+
**If config has `prompts.*` sections**, inject those instructions at the relevant stage. They run AFTER the built-in instructions.
|
|
80
|
+
|
|
81
|
+
**If config has `skip-stages`**, skip those stages. But NEVER skip `review` even if listed — review is always mandatory.
|
|
82
|
+
|
|
83
|
+
### Enter Worktree
|
|
84
|
+
|
|
85
|
+
**Default: every build runs in a git worktree.** A worktree is a separate directory with its own branch — lets you run `/spartan:build` in multiple terminals without conflicts.
|
|
86
|
+
|
|
87
|
+
**Check if already in a worktree:**
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
git rev-parse --show-toplevel 2>/dev/null
|
|
91
|
+
git worktree list 2>/dev/null | head -3
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
If already in a worktree (not the main repo), skip — you're already isolated.
|
|
95
|
+
|
|
96
|
+
**If `worktree: false` in config**, skip worktree creation. Use `git checkout -b` later in Stage 3 instead.
|
|
97
|
+
|
|
98
|
+
**Otherwise, create a worktree now:**
|
|
99
|
+
|
|
100
|
+
Generate a slug from the feature description (e.g., "user auth" → `user-auth`). Use `branch-prefix` from config (default: `feature`).
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
EnterWorktree(name: "[prefix]-[slug]")
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
This creates a worktree at `.claude/worktrees/[prefix]-[slug]/` with a new branch and switches your working directory there.
|
|
107
|
+
|
|
108
|
+
**Symlink shared directories** — gitignored dirs don't appear in worktrees. Symlink them from the main repo:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
MAIN_REPO="$(git worktree list | head -1 | awk '{print $1}')"
|
|
112
|
+
SYMLINKS=".planning .memory .handoff .spartan"
|
|
113
|
+
|
|
114
|
+
# Add extra symlinks from config (worktree-symlinks field)
|
|
115
|
+
for dir in $SYMLINKS; do
|
|
116
|
+
[ -d "$MAIN_REPO/$dir" ] && [ ! -e "$dir" ] && ln -s "$MAIN_REPO/$dir" "$dir"
|
|
117
|
+
done
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
> "Working in worktree: `[path]` on branch `[prefix]-[slug]`"
|
|
121
|
+
|
|
122
|
+
**If `EnterWorktree` fails** (e.g., name conflict), fall back to manual worktree:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
SLUG="[slug]"
|
|
126
|
+
MAIN_REPO="$(pwd)"
|
|
127
|
+
git worktree add "$MAIN_REPO/.worktrees/$SLUG" -b "feature/$SLUG" 2>/dev/null
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Then tell the user: "Created worktree at `.worktrees/[slug]/`. Open a new terminal there and run `claude` + `/spartan:build`."
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
55
134
|
## Step 0: Detect Mode & Stack (silent — no questions)
|
|
56
135
|
|
|
57
136
|
Parse the user's input to find the mode:
|
|
@@ -183,6 +262,8 @@ Use the approach from `/spartan:spec` — ask questions one at a time, fill the
|
|
|
183
262
|
|
|
184
263
|
Then continue to the next stage automatically (don't tell the user to run a separate command).
|
|
185
264
|
|
|
265
|
+
**Custom spec prompts:** If `.spartan/build.yaml` has `prompts.spec`, apply those instructions now — ask any extra questions, add any extra requirements to the scope.
|
|
266
|
+
|
|
186
267
|
**GATE 1 — STOP and ask:**
|
|
187
268
|
> "Here's the scope. Anything to change before I plan?"
|
|
188
269
|
>
|
|
@@ -278,28 +359,24 @@ Uses skills: `ui-ux-pro-max`, frontend rules
|
|
|
278
359
|
|
|
279
360
|
**CRITICAL: Full-stack means BOTH layers must complete.** Don't move to Gate 3 after finishing backend only. The plan must include frontend tasks and ALL tasks must be done before review. If the spec mentions UI changes, API responses shown to users, or any user-facing behavior — frontend tasks are mandatory.
|
|
280
361
|
|
|
281
|
-
###
|
|
362
|
+
### Verify workspace
|
|
282
363
|
|
|
283
|
-
|
|
364
|
+
Confirm you're in the right place:
|
|
284
365
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
EnterWorktree(name: "feature-[slug]")
|
|
366
|
+
```bash
|
|
367
|
+
pwd
|
|
368
|
+
git branch --show-current
|
|
289
369
|
```
|
|
290
370
|
|
|
291
|
-
|
|
371
|
+
**If worktree is enabled (default):** You should already be in a worktree from the "FIRST" step. If not, go back and create one.
|
|
292
372
|
|
|
293
|
-
**
|
|
373
|
+
**If `worktree: false` in config:** Create a branch now:
|
|
294
374
|
|
|
295
375
|
```bash
|
|
296
|
-
|
|
297
|
-
for dir in .planning .memory .handoff .spartan; do
|
|
298
|
-
[ -d "$MAIN_REPO/$dir" ] && [ ! -e "$dir" ] && ln -s "$MAIN_REPO/$dir" "$dir"
|
|
299
|
-
done
|
|
376
|
+
git checkout -b feature/[slug]
|
|
300
377
|
```
|
|
301
378
|
|
|
302
|
-
|
|
379
|
+
**Custom plan prompts:** If `.spartan/build.yaml` has `prompts.plan`, apply those instructions now.
|
|
303
380
|
|
|
304
381
|
Write the first failing test for Task 1. Show it fails.
|
|
305
382
|
|
|
@@ -485,6 +562,8 @@ ls .planning/specs/*.md .planning/plans/*.md .planning/designs/*.md 2>/dev/null
|
|
|
485
562
|
|
|
486
563
|
Classify each changed file into backend/frontend/migration using the `file-types` from config (or defaults: `.kt/.java/.go/.py` = backend, `.tsx/.ts/.vue` = frontend, `.sql` = migration).
|
|
487
564
|
|
|
565
|
+
**Custom review prompts:** If `.spartan/build.yaml` has `prompts.review`, include those instructions in the review agent's prompt below — they're injected after the standard review stages.
|
|
566
|
+
|
|
488
567
|
### Step 3: Spawn the review agent
|
|
489
568
|
|
|
490
569
|
Use the `Agent` tool to spawn a reviewer. **The prompt is built from the config.**
|
|
@@ -605,7 +684,7 @@ When the reviewer reports back:
|
|
|
605
684
|
5. Spawn the review agent AGAIN with the updated diff — only the remaining/new changes need review
|
|
606
685
|
6. Repeat until the reviewer says PASS
|
|
607
686
|
|
|
608
|
-
**Max 3 review
|
|
687
|
+
**Max review rounds** (default: 3, configurable via `max-review-rounds` in `.spartan/build.yaml`). If still failing after max rounds, stop and ask the user:
|
|
609
688
|
> "Review found issues I can't fully fix. Here's what's left: [list]. Want to continue anyway or address these manually?"
|
|
610
689
|
|
|
611
690
|
### Step 4: Capture review learnings
|
|
@@ -670,6 +749,8 @@ After review completes: `TeamDelete()` to clean up.
|
|
|
670
749
|
|
|
671
750
|
## Stage 6: Ship
|
|
672
751
|
|
|
752
|
+
**Custom ship prompts:** If `.spartan/build.yaml` has `prompts.ship`, apply those instructions — PR naming rules, extra checks, deploy notes, etc.
|
|
753
|
+
|
|
673
754
|
### Create PR
|
|
674
755
|
Run the approach from `/spartan:pr-ready`:
|
|
675
756
|
- Rebase onto main
|
|
@@ -691,22 +772,6 @@ mkdir -p .memory/decisions .memory/patterns .memory/knowledge
|
|
|
691
772
|
|
|
692
773
|
Update `.memory/index.md` if you saved anything.
|
|
693
774
|
|
|
694
|
-
### Clean up worktree
|
|
695
|
-
|
|
696
|
-
After the PR is created, exit the worktree. Keep it around in case the user needs to make PR review fixes:
|
|
697
|
-
|
|
698
|
-
```
|
|
699
|
-
ExitWorktree(action: "keep")
|
|
700
|
-
```
|
|
701
|
-
|
|
702
|
-
> "PR created. Worktree kept at `.claude/worktrees/feature-[slug]/` in case you need to push fixes. To clean up later: `git worktree remove .claude/worktrees/feature-[slug]`"
|
|
703
|
-
|
|
704
|
-
If the user says the PR is merged and they're done, clean up:
|
|
705
|
-
|
|
706
|
-
```
|
|
707
|
-
ExitWorktree(action: "remove")
|
|
708
|
-
```
|
|
709
|
-
|
|
710
775
|
**GATE 4 — Done.**
|
|
711
776
|
> "PR created: [link]. Here's what's in it: [summary]."
|
|
712
777
|
|
|
@@ -763,25 +828,18 @@ Agent(
|
|
|
763
828
|
```
|
|
764
829
|
Collect results after all finish, then `TeamDelete()`.
|
|
765
830
|
|
|
766
|
-
### Step 2: Sort by dependency
|
|
831
|
+
### Step 2: Sort by dependency
|
|
767
832
|
|
|
768
833
|
Read the epic's Features table. Sort features by dependency order:
|
|
769
834
|
- Features with no dependencies → can build first
|
|
770
835
|
- Features that depend on others → build after their dependencies are done
|
|
771
836
|
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
```
|
|
775
|
-
EnterWorktree(name: "feature-[epic-slug]")
|
|
776
|
-
```
|
|
777
|
-
|
|
778
|
-
Symlink shared directories:
|
|
837
|
+
**If worktree enabled (default):** You should already be in a worktree from the "FIRST" step.
|
|
838
|
+
**If `worktree: false`:** Create a branch: `git checkout -b feature/[epic-slug]`
|
|
779
839
|
|
|
780
840
|
```bash
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
[ -d "$MAIN_REPO/$dir" ] && [ ! -e "$dir" ] && ln -s "$MAIN_REPO/$dir" "$dir"
|
|
784
|
-
done
|
|
841
|
+
pwd
|
|
842
|
+
git branch --show-current
|
|
785
843
|
```
|
|
786
844
|
|
|
787
845
|
### Step 3: Implement in dependency order
|
|
@@ -851,19 +909,13 @@ Run the full test suite. Then continue to **Stage 5: Review** — the review age
|
|
|
851
909
|
If a previous session was interrupted (context overflow, user stopped, etc.), this workflow can resume.
|
|
852
910
|
|
|
853
911
|
**How resume works:**
|
|
854
|
-
1.
|
|
855
|
-
|
|
856
|
-
git worktree list
|
|
857
|
-
ls .claude/worktrees/ 2>/dev/null
|
|
858
|
-
```
|
|
859
|
-
If a worktree exists for this feature, re-enter it with `EnterWorktree(name: "feature-[slug]")`. Don't create a new one.
|
|
860
|
-
2. Step 0.5 checks for `.handoff/` files and existing `.planning/` artifacts
|
|
861
|
-
3. Determine which stage was completed last:
|
|
912
|
+
1. Step 0.5 checks for `.handoff/` files and existing `.planning/` artifacts
|
|
913
|
+
2. Determine which stage was completed last:
|
|
862
914
|
- Has spec but no plan → resume at Stage 3 (Plan)
|
|
863
915
|
- Has plan but no commits on feature branch → resume at Stage 4 (Implement)
|
|
864
916
|
- Has commits but no PR → resume at Stage 5 (Review) or Stage 6 (Ship)
|
|
865
|
-
|
|
866
|
-
|
|
917
|
+
3. Show the user: "Resuming from [stage]. Here's what was done: [summary]."
|
|
918
|
+
4. Continue from that point.
|
|
867
919
|
|
|
868
920
|
**Don't re-do completed stages.** Read the saved artifacts and move forward.
|
|
869
921
|
|
|
@@ -886,8 +938,8 @@ If a previous session was interrupted (context overflow, user stopped, etc.), th
|
|
|
886
938
|
- **Full-stack = both layers done.** If the feature touches both backend and frontend, you MUST implement both before creating the PR. Backend-only completion is NOT "done" for a full-stack feature.
|
|
887
939
|
- **Epic = one branch, one PR.** When building from an epic, all features go on one branch and ship as one PR. Don't create separate PRs per feature. Parallelize independent features with Agent Teams when available.
|
|
888
940
|
- **Epic auto-detection.** If the user's feature name matches an epic in `.planning/epics/`, switch to epic mode automatically. Don't ask.
|
|
889
|
-
- **
|
|
890
|
-
- **
|
|
941
|
+
- **Worktree by default.** The first step is `EnterWorktree` (unless `worktree: false` in `.spartan/build.yaml`). This lets multiple terminals run `/spartan:build` simultaneously. If already in a worktree, skip. If `EnterWorktree` fails, fall back to manual worktree.
|
|
942
|
+
- **Build config is `.spartan/build.yaml`.** Controls worktrees, branch prefix, max review rounds, skip stages, custom prompts per stage, and extra worktree symlinks. All fields optional.
|
|
891
943
|
|
|
892
944
|
---
|
|
893
945
|
|
|
@@ -921,3 +973,8 @@ A feature is NOT done until every applicable item is checked:
|
|
|
921
973
|
- [ ] Atomic commits (one per task)
|
|
922
974
|
- [ ] Review agent passed (all HIGH/MEDIUM issues fixed)
|
|
923
975
|
- [ ] PR created with summary and test plan
|
|
976
|
+
|
|
977
|
+
### Worktree
|
|
978
|
+
- [ ] Worktree created and working directory switched (automatic unless `worktree: false`)
|
|
979
|
+
- [ ] All commits pushed to feature branch
|
|
980
|
+
- [ ] Worktree cleaned up after PR merged (`git worktree remove .claude/worktrees/[slug]`)
|
package/commands/spartan.md
CHANGED
|
@@ -48,6 +48,18 @@ This prevents confusion when the user is running multiple windows. Keep it to on
|
|
|
48
48
|
|
|
49
49
|
---
|
|
50
50
|
|
|
51
|
+
## Step 0.9: Load Command Config (silent, always runs)
|
|
52
|
+
|
|
53
|
+
Check for per-command custom prompts. Commands config lets users inject custom instructions into any Spartan command.
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
cat .spartan/commands.yaml 2>/dev/null || cat .spartan/commands.yml 2>/dev/null
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
If the file exists and has a `prompts.[command-name]` entry matching the command being routed to, pass those custom instructions to the command. They're appended after the built-in prompt.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
51
63
|
## Step 1: Detect Project Context (silent, no questions)
|
|
52
64
|
|
|
53
65
|
Before asking anything, scan the environment:
|
package/package.json
CHANGED
package/packs/core.yaml
CHANGED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# Timezone Rules
|
|
2
|
+
|
|
3
|
+
## One Rule: Everything is UTC
|
|
4
|
+
|
|
5
|
+
**Server stores UTC. API sends UTC. API receives UTC. No exceptions.**
|
|
6
|
+
|
|
7
|
+
The frontend is the only place that converts to/from local time — and only for display.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Database (UTC) → Backend (UTC) → API JSON (UTC) → Frontend receives (UTC) → Display (local)
|
|
11
|
+
← Frontend sends (UTC) ← Input (local → UTC)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Backend
|
|
17
|
+
|
|
18
|
+
### Always Use `Instant` — Never `LocalDateTime`
|
|
19
|
+
|
|
20
|
+
`Instant` is UTC by definition. `LocalDateTime` has no timezone info and leads to bugs.
|
|
21
|
+
|
|
22
|
+
```kotlin
|
|
23
|
+
// CORRECT — Instant is always UTC
|
|
24
|
+
val now: Instant = Instant.now()
|
|
25
|
+
val expiresAt: Instant = Instant.now().plusSeconds(3600)
|
|
26
|
+
|
|
27
|
+
// WRONG — LocalDateTime has no timezone, ambiguous
|
|
28
|
+
val now: LocalDateTime = LocalDateTime.now() // What timezone? Nobody knows.
|
|
29
|
+
val expiresAt: LocalDateTime = LocalDateTime.now().plusHours(1)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Never Use `ZonedDateTime` in Business Logic
|
|
33
|
+
|
|
34
|
+
`ZonedDateTime` is only for converting when absolutely needed (e.g., generating a report for a specific timezone). Don't pass it between layers.
|
|
35
|
+
|
|
36
|
+
```kotlin
|
|
37
|
+
// CORRECT — use Instant everywhere
|
|
38
|
+
data class UserEntity(
|
|
39
|
+
val createdAt: Instant,
|
|
40
|
+
val lastLoginAt: Instant?,
|
|
41
|
+
val subscriptionExpiresAt: Instant?
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
// WRONG — ZonedDateTime in entities/DTOs
|
|
45
|
+
data class UserEntity(
|
|
46
|
+
val createdAt: ZonedDateTime, // NO — carries timezone baggage
|
|
47
|
+
val lastLoginAt: LocalDateTime? // NO — ambiguous
|
|
48
|
+
)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Never Store Timezone in the Database
|
|
52
|
+
|
|
53
|
+
Don't add `timezone` columns. Don't save user timezone preferences alongside timestamps. If the frontend needs to display local time, it does the conversion itself.
|
|
54
|
+
|
|
55
|
+
```sql
|
|
56
|
+
-- CORRECT — just UTC timestamps
|
|
57
|
+
CREATE TABLE events (
|
|
58
|
+
id UUID PRIMARY KEY,
|
|
59
|
+
starts_at TIMESTAMP NOT NULL, -- UTC
|
|
60
|
+
ends_at TIMESTAMP NOT NULL, -- UTC
|
|
61
|
+
created_at TIMESTAMP DEFAULT NOW() -- UTC
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
-- WRONG — storing timezone info
|
|
65
|
+
CREATE TABLE events (
|
|
66
|
+
id UUID PRIMARY KEY,
|
|
67
|
+
starts_at TIMESTAMP NOT NULL,
|
|
68
|
+
ends_at TIMESTAMP NOT NULL,
|
|
69
|
+
timezone TEXT DEFAULT 'America/New_York' -- DON'T DO THIS
|
|
70
|
+
);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Jackson Serialization
|
|
74
|
+
|
|
75
|
+
Jackson must serialize all `Instant` fields as ISO 8601 with the `Z` (UTC) suffix. This should be configured globally:
|
|
76
|
+
|
|
77
|
+
```kotlin
|
|
78
|
+
// ObjectMapper config (usually already set)
|
|
79
|
+
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
|
|
80
|
+
// Output: "2024-01-15T10:30:00Z"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Every datetime field in API JSON looks like: `"2024-01-15T10:30:00Z"`
|
|
84
|
+
|
|
85
|
+
Never output offsets like `+07:00` or timezone names like `Asia/Ho_Chi_Minh`.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## API Contract
|
|
90
|
+
|
|
91
|
+
### All Datetime Fields Are ISO 8601 UTC
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"created_at": "2024-01-15T10:30:00Z",
|
|
96
|
+
"updated_at": "2024-01-15T14:22:33Z",
|
|
97
|
+
"expires_at": "2024-02-15T00:00:00Z"
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Request Bodies — Frontend Sends UTC
|
|
102
|
+
|
|
103
|
+
When the frontend sends a datetime, it MUST be UTC:
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"starts_at": "2024-01-20T09:00:00Z",
|
|
108
|
+
"ends_at": "2024-01-20T17:00:00Z"
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Query Parameters — Also UTC
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
GET /events?from=2024-01-01T00:00:00Z&to=2024-01-31T23:59:59Z
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### No Timezone Fields in Request or Response
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
// WRONG — timezone info in API
|
|
122
|
+
{
|
|
123
|
+
"starts_at": "2024-01-20T09:00:00Z",
|
|
124
|
+
"timezone": "America/New_York"
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// CORRECT — just UTC, frontend handles display
|
|
128
|
+
{
|
|
129
|
+
"starts_at": "2024-01-20T09:00:00Z"
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Frontend
|
|
136
|
+
|
|
137
|
+
### Receive UTC, Convert for Display
|
|
138
|
+
|
|
139
|
+
All API responses return UTC. Convert to local only when showing to the user:
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
// CORRECT — convert at display time
|
|
143
|
+
function formatDate(utcString: string): string {
|
|
144
|
+
return new Date(utcString).toLocaleString()
|
|
145
|
+
// Or use Intl.DateTimeFormat for more control
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// CORRECT — with specific format
|
|
149
|
+
function formatDate(utcString: string, locale = 'en-US'): string {
|
|
150
|
+
return new Intl.DateTimeFormat(locale, {
|
|
151
|
+
dateStyle: 'medium',
|
|
152
|
+
timeStyle: 'short',
|
|
153
|
+
}).format(new Date(utcString))
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Send UTC to Server
|
|
158
|
+
|
|
159
|
+
Convert local input to UTC before sending:
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
// CORRECT — convert to UTC before API call
|
|
163
|
+
const localDate = new Date(userInput) // user picks "Jan 20, 2024 9:00 AM"
|
|
164
|
+
const utcString = localDate.toISOString() // "2024-01-20T02:00:00.000Z" (if user is UTC+7)
|
|
165
|
+
|
|
166
|
+
await api.post('/events', {
|
|
167
|
+
startsAt: utcString, // Always UTC
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
// WRONG — sending local time string
|
|
171
|
+
await api.post('/events', {
|
|
172
|
+
startsAt: '2024-01-20T09:00:00', // No Z suffix — ambiguous!
|
|
173
|
+
})
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Date Libraries
|
|
177
|
+
|
|
178
|
+
If using a date library (date-fns, dayjs, luxon), still follow the same pattern:
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
// date-fns example
|
|
182
|
+
import { formatInTimeZone } from 'date-fns-tz'
|
|
183
|
+
|
|
184
|
+
// Display: UTC → user's local timezone
|
|
185
|
+
const display = formatInTimeZone(
|
|
186
|
+
new Date(apiResponse.createdAt), // UTC from API
|
|
187
|
+
Intl.DateTimeFormat().resolvedOptions().timeZone, // user's timezone
|
|
188
|
+
'MMM d, yyyy h:mm a'
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
// Send: local → UTC
|
|
192
|
+
const utc = new Date(localInput).toISOString()
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Never Store Timezone in Frontend State
|
|
196
|
+
|
|
197
|
+
Don't track the user's timezone in state or send it to the backend. The browser already knows the timezone — use it at render time.
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
// WRONG — tracking timezone in state
|
|
201
|
+
const [timezone, setTimezone] = useState('America/New_York')
|
|
202
|
+
|
|
203
|
+
// CORRECT — use browser timezone at render time
|
|
204
|
+
const userTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Database
|
|
210
|
+
|
|
211
|
+
### TIMESTAMP Without Timezone
|
|
212
|
+
|
|
213
|
+
Use `TIMESTAMP` (not `TIMESTAMPTZ`). The value is always UTC. No timezone info needed.
|
|
214
|
+
|
|
215
|
+
```sql
|
|
216
|
+
-- CORRECT
|
|
217
|
+
created_at TIMESTAMP DEFAULT NOW() -- NOW() returns UTC in a UTC-configured server
|
|
218
|
+
|
|
219
|
+
-- ALSO ACCEPTABLE (PostgreSQL stores both as UTC internally)
|
|
220
|
+
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Server Must Be Configured for UTC
|
|
224
|
+
|
|
225
|
+
The database server and application server must run in UTC:
|
|
226
|
+
|
|
227
|
+
```yaml
|
|
228
|
+
# application.yml
|
|
229
|
+
datasources:
|
|
230
|
+
default:
|
|
231
|
+
connection-properties:
|
|
232
|
+
timezone: UTC
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
```sql
|
|
236
|
+
-- PostgreSQL: verify server timezone
|
|
237
|
+
SHOW timezone; -- Should return 'UTC'
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Quick Reference
|
|
243
|
+
|
|
244
|
+
| Layer | Type | Format | Example |
|
|
245
|
+
|-------|------|--------|---------|
|
|
246
|
+
| Database | Column type | `TIMESTAMP` | `2024-01-15 10:30:00` |
|
|
247
|
+
| Backend (Kotlin) | Property type | `Instant` | `Instant.now()` |
|
|
248
|
+
| API JSON | String | ISO 8601 + Z | `"2024-01-15T10:30:00Z"` |
|
|
249
|
+
| Frontend (receive) | Parse | `new Date(utcString)` | `new Date("2024-01-15T10:30:00Z")` |
|
|
250
|
+
| Frontend (display) | Format | `toLocaleString()` | `"Jan 15, 2024, 5:30 PM"` (UTC+7) |
|
|
251
|
+
| Frontend (send) | Serialize | `toISOString()` | `"2024-01-15T10:30:00.000Z"` |
|
|
252
|
+
|
|
253
|
+
## What NOT to Do
|
|
254
|
+
|
|
255
|
+
- Don't use `LocalDateTime` in Kotlin — use `Instant`
|
|
256
|
+
- Don't store timezone names or offsets in the database
|
|
257
|
+
- Don't send timezone info in API requests or responses
|
|
258
|
+
- Don't use `TIMESTAMPTZ` thinking it "stores the timezone" — PostgreSQL converts everything to UTC anyway
|
|
259
|
+
- Don't convert to local time on the backend — that's the frontend's job
|
|
260
|
+
- Don't assume a timezone — let the browser handle it
|
|
261
|
+
- Don't format dates on the server for display — return UTC, let the client format
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# .spartan/build.yaml — Project-level build config
|
|
2
|
+
# Copy this file to .spartan/build.yaml in your project and customize.
|
|
3
|
+
# All fields are optional. Omit a field to use the default.
|
|
4
|
+
|
|
5
|
+
# --- Worktree ---
|
|
6
|
+
|
|
7
|
+
# Worktree isolation (default: true)
|
|
8
|
+
# When true, /spartan:build creates a git worktree per feature.
|
|
9
|
+
# Each terminal gets its own directory, branch, and PR — no conflicts.
|
|
10
|
+
# Set to false to use simple git checkout (single terminal only).
|
|
11
|
+
worktree: true
|
|
12
|
+
|
|
13
|
+
# Branch prefix (default: "feature")
|
|
14
|
+
# Controls the branch name: [prefix]/[slug] (e.g., "feature/user-auth")
|
|
15
|
+
branch-prefix: "feature"
|
|
16
|
+
|
|
17
|
+
# Extra directories to symlink into worktrees
|
|
18
|
+
# Gitignored dirs (.planning, .memory, .handoff, .spartan) are always symlinked.
|
|
19
|
+
# Add more here if your project has other shared gitignored dirs.
|
|
20
|
+
# worktree-symlinks:
|
|
21
|
+
# - .env
|
|
22
|
+
# - config/local
|
|
23
|
+
|
|
24
|
+
# --- Build stages ---
|
|
25
|
+
|
|
26
|
+
# Max review-fix cycles before asking the user (default: 3)
|
|
27
|
+
# max-review-rounds: 3
|
|
28
|
+
|
|
29
|
+
# Stages to skip (use carefully — most stages exist for a reason)
|
|
30
|
+
# Valid: spec, design, plan, review, ship
|
|
31
|
+
# Note: review can never actually be skipped — it's always enforced
|
|
32
|
+
# skip-stages: []
|
|
33
|
+
|
|
34
|
+
# --- Custom prompts ---
|
|
35
|
+
|
|
36
|
+
# Custom instructions injected into build stages.
|
|
37
|
+
# Claude reads these alongside the built-in workflow instructions.
|
|
38
|
+
# Use this to add project-specific rules, naming conventions, or checklists.
|
|
39
|
+
prompts:
|
|
40
|
+
# Injected after spec questions, before scope is finalized
|
|
41
|
+
# spec: |
|
|
42
|
+
# Always include performance requirements.
|
|
43
|
+
# Ask about expected load/traffic.
|
|
44
|
+
|
|
45
|
+
# Injected into the plan stage, after the task list is generated
|
|
46
|
+
# plan: |
|
|
47
|
+
# Every backend task must include a Flyway migration version check.
|
|
48
|
+
# Frontend tasks must reference the Figma frame URL.
|
|
49
|
+
|
|
50
|
+
# Injected during implementation
|
|
51
|
+
# implement: |
|
|
52
|
+
# Always add structured logging to new service methods.
|
|
53
|
+
# Use the project's error code registry for new error types.
|
|
54
|
+
|
|
55
|
+
# Injected into the review agent's prompt
|
|
56
|
+
# review: |
|
|
57
|
+
# Check that all API responses include request_id for tracing.
|
|
58
|
+
# Verify that new endpoints have rate limiting annotations.
|
|
59
|
+
|
|
60
|
+
# Injected before PR creation
|
|
61
|
+
# ship: |
|
|
62
|
+
# PR title must start with the Jira ticket number.
|
|
63
|
+
# Add "Tested on staging" to the test plan if it touches payments.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# .spartan/commands.yaml — Per-command overrides
|
|
2
|
+
# Inject custom prompts into ANY Spartan command. Claude reads these
|
|
3
|
+
# alongside the built-in instructions when the command runs.
|
|
4
|
+
# All fields are optional.
|
|
5
|
+
|
|
6
|
+
# --- Per-command prompt injection ---
|
|
7
|
+
# Key = command name (without "spartan:" prefix)
|
|
8
|
+
# Value = custom instructions appended to the command's prompt
|
|
9
|
+
|
|
10
|
+
# prompts:
|
|
11
|
+
# spec: |
|
|
12
|
+
# Always ask about performance requirements.
|
|
13
|
+
# Include acceptance criteria in BDD format.
|
|
14
|
+
#
|
|
15
|
+
# plan: |
|
|
16
|
+
# Tasks must reference Jira ticket numbers.
|
|
17
|
+
# Estimate hours per task.
|
|
18
|
+
#
|
|
19
|
+
# review: |
|
|
20
|
+
# Check that all new endpoints have OpenAPI annotations.
|
|
21
|
+
# Flag any function longer than 50 lines.
|
|
22
|
+
#
|
|
23
|
+
# pr-ready: |
|
|
24
|
+
# PR title format: [PROJ-123] Short description
|
|
25
|
+
# Always add "Reviewers: @backend-team" for backend changes.
|
|
26
|
+
#
|
|
27
|
+
# daily: |
|
|
28
|
+
# Include blockers section.
|
|
29
|
+
# Tag items by project area (auth, payments, infra).
|
|
30
|
+
#
|
|
31
|
+
# debug: |
|
|
32
|
+
# Always check CloudWatch logs first.
|
|
33
|
+
# Include the request_id in every investigation.
|
|
34
|
+
#
|
|
35
|
+
# migration: |
|
|
36
|
+
# Migration files must start with ticket number.
|
|
37
|
+
# Always add a rollback section.
|
|
38
|
+
#
|
|
39
|
+
# onboard: |
|
|
40
|
+
# Focus on the payments module first — it's the most complex.
|
|
41
|
+
# Skip the legacy admin/ directory.
|
|
42
|
+
|
|
43
|
+
# --- Command defaults ---
|
|
44
|
+
# Override default behavior for specific commands
|
|
45
|
+
|
|
46
|
+
# defaults:
|
|
47
|
+
# build:
|
|
48
|
+
# worktree: true
|
|
49
|
+
# branch-prefix: "feature"
|
|
50
|
+
# max-review-rounds: 3
|
|
51
|
+
# skip-stages: []
|
|
52
|
+
# spec:
|
|
53
|
+
# save-path: ".planning/specs" # where specs are saved
|
|
54
|
+
# review:
|
|
55
|
+
# parallel: true # use Agent Teams for parallel review if available
|