@dv.nghiem/flowdeck 0.1.0
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/LICENSE +21 -0
- package/README.md +136 -0
- package/bin/flowdeck.js +108 -0
- package/dist/agents/architect.d.ts +3 -0
- package/dist/agents/architect.d.ts.map +1 -0
- package/dist/agents/code-explorer.d.ts +3 -0
- package/dist/agents/code-explorer.d.ts.map +1 -0
- package/dist/agents/coder.d.ts +3 -0
- package/dist/agents/coder.d.ts.map +1 -0
- package/dist/agents/debug.d.ts +4 -0
- package/dist/agents/debug.d.ts.map +1 -0
- package/dist/agents/doc-updater.d.ts +3 -0
- package/dist/agents/doc-updater.d.ts.map +1 -0
- package/dist/agents/flowdeck.d.ts +5 -0
- package/dist/agents/flowdeck.d.ts.map +1 -0
- package/dist/agents/index.d.ts +38 -0
- package/dist/agents/index.d.ts.map +1 -0
- package/dist/agents/mapper.d.ts +3 -0
- package/dist/agents/mapper.d.ts.map +1 -0
- package/dist/agents/orchestrator.d.ts +10 -0
- package/dist/agents/orchestrator.d.ts.map +1 -0
- package/dist/agents/performance.d.ts +4 -0
- package/dist/agents/performance.d.ts.map +1 -0
- package/dist/agents/planner.d.ts +3 -0
- package/dist/agents/planner.d.ts.map +1 -0
- package/dist/agents/policy-enforcer.d.ts +3 -0
- package/dist/agents/policy-enforcer.d.ts.map +1 -0
- package/dist/agents/researcher.d.ts +3 -0
- package/dist/agents/researcher.d.ts.map +1 -0
- package/dist/agents/reviewer.d.ts +3 -0
- package/dist/agents/reviewer.d.ts.map +1 -0
- package/dist/agents/risk-analyst.d.ts +3 -0
- package/dist/agents/risk-analyst.d.ts.map +1 -0
- package/dist/agents/security-auditor.d.ts +3 -0
- package/dist/agents/security-auditor.d.ts.map +1 -0
- package/dist/agents/specialist.d.ts +5 -0
- package/dist/agents/specialist.d.ts.map +1 -0
- package/dist/agents/tester.d.ts +3 -0
- package/dist/agents/tester.d.ts.map +1 -0
- package/dist/agents/types.d.ts +20 -0
- package/dist/agents/types.d.ts.map +1 -0
- package/dist/agents/writer.d.ts +3 -0
- package/dist/agents/writer.d.ts.map +1 -0
- package/dist/commands/analysis/analysis.test.d.ts +2 -0
- package/dist/commands/analysis/analysis.test.d.ts.map +1 -0
- package/dist/commands/analysis/analyze-change.d.ts +148 -0
- package/dist/commands/analysis/analyze-change.d.ts.map +1 -0
- package/dist/commands/analysis/evaluate-risk.d.ts +77 -0
- package/dist/commands/analysis/evaluate-risk.d.ts.map +1 -0
- package/dist/commands/analysis/guarded-edit.d.ts +72 -0
- package/dist/commands/analysis/guarded-edit.d.ts.map +1 -0
- package/dist/commands/execution/deploy-check.d.ts +91 -0
- package/dist/commands/execution/deploy-check.d.ts.map +1 -0
- package/dist/commands/execution/fix-bug.d.ts +187 -0
- package/dist/commands/execution/fix-bug.d.ts.map +1 -0
- package/dist/commands/execution/new-feature.d.ts +171 -0
- package/dist/commands/execution/new-feature.d.ts.map +1 -0
- package/dist/commands/execution/review-code.d.ts +130 -0
- package/dist/commands/execution/review-code.d.ts.map +1 -0
- package/dist/commands/execution/write-docs.d.ts +94 -0
- package/dist/commands/execution/write-docs.d.ts.map +1 -0
- package/dist/commands/governance/approve.d.ts +80 -0
- package/dist/commands/governance/approve.d.ts.map +1 -0
- package/dist/commands/intelligence/blast-radius.d.ts +67 -0
- package/dist/commands/intelligence/blast-radius.d.ts.map +1 -0
- package/dist/commands/intelligence/impact-radar.d.ts +71 -0
- package/dist/commands/intelligence/impact-radar.d.ts.map +1 -0
- package/dist/commands/intelligence/intelligence.test.d.ts +2 -0
- package/dist/commands/intelligence/intelligence.test.d.ts.map +1 -0
- package/dist/commands/intelligence/regression-predict.d.ts +75 -0
- package/dist/commands/intelligence/regression-predict.d.ts.map +1 -0
- package/dist/commands/intelligence/review-route.d.ts +65 -0
- package/dist/commands/intelligence/review-route.d.ts.map +1 -0
- package/dist/commands/intelligence/test-gap.d.ts +73 -0
- package/dist/commands/intelligence/test-gap.d.ts.map +1 -0
- package/dist/commands/intelligence/translate-intent.d.ts +87 -0
- package/dist/commands/intelligence/translate-intent.d.ts.map +1 -0
- package/dist/commands/intelligence/volatility-map-cmd.d.ts +68 -0
- package/dist/commands/intelligence/volatility-map-cmd.d.ts.map +1 -0
- package/dist/commands/planning/ask.d.ts +62 -0
- package/dist/commands/planning/ask.d.ts.map +1 -0
- package/dist/commands/planning/ask.test.d.ts +2 -0
- package/dist/commands/planning/ask.test.d.ts.map +1 -0
- package/dist/commands/planning/dashboard.d.ts +30 -0
- package/dist/commands/planning/dashboard.d.ts.map +1 -0
- package/dist/commands/planning/discuss.d.ts +39 -0
- package/dist/commands/planning/discuss.d.ts.map +1 -0
- package/dist/commands/planning/plan.d.ts +67 -0
- package/dist/commands/planning/plan.d.ts.map +1 -0
- package/dist/commands/planning/roadmap.d.ts +105 -0
- package/dist/commands/planning/roadmap.d.ts.map +1 -0
- package/dist/commands/setup/doctor.d.ts +10 -0
- package/dist/commands/setup/doctor.d.ts.map +1 -0
- package/dist/commands/setup/map-codebase.d.ts +62 -0
- package/dist/commands/setup/map-codebase.d.ts.map +1 -0
- package/dist/commands/setup/new-project.d.ts +19 -0
- package/dist/commands/setup/new-project.d.ts.map +1 -0
- package/dist/commands/setup/settings.d.ts +57 -0
- package/dist/commands/setup/settings.d.ts.map +1 -0
- package/dist/commands/state/checkpoint.d.ts +27 -0
- package/dist/commands/state/checkpoint.d.ts.map +1 -0
- package/dist/commands/state/multi-repo.d.ts +63 -0
- package/dist/commands/state/multi-repo.d.ts.map +1 -0
- package/dist/commands/state/progress.d.ts +57 -0
- package/dist/commands/state/progress.d.ts.map +1 -0
- package/dist/commands/state/resume.d.ts +11 -0
- package/dist/commands/state/resume.d.ts.map +1 -0
- package/dist/commands/state/workspace-commands.d.ts +207 -0
- package/dist/commands/state/workspace-commands.d.ts.map +1 -0
- package/dist/dashboard/lib/port-finder.d.ts +10 -0
- package/dist/dashboard/lib/port-finder.d.ts.map +1 -0
- package/dist/dashboard/lib/port-finder.test.d.ts +2 -0
- package/dist/dashboard/lib/port-finder.test.d.ts.map +1 -0
- package/dist/dashboard/lib/state-reader.d.ts +3 -0
- package/dist/dashboard/lib/state-reader.d.ts.map +1 -0
- package/dist/dashboard/server.d.ts +2 -0
- package/dist/dashboard/server.d.ts.map +1 -0
- package/dist/dashboard/server.mjs +13649 -0
- package/dist/dashboard/types.d.ts +72 -0
- package/dist/dashboard/types.d.ts.map +1 -0
- package/dist/dashboard/views/index.ejs +391 -0
- package/dist/dashboard/views/partials/blockers.ejs +10 -0
- package/dist/dashboard/views/partials/header.ejs +20 -0
- package/dist/dashboard/views/partials/phase-timeline.ejs +40 -0
- package/dist/dashboard/views/partials/progress.ejs +12 -0
- package/dist/hooks/approval-hook.d.ts +13 -0
- package/dist/hooks/approval-hook.d.ts.map +1 -0
- package/dist/hooks/compaction-hook.d.ts +23 -0
- package/dist/hooks/compaction-hook.d.ts.map +1 -0
- package/dist/hooks/context-window-monitor.d.ts +21 -0
- package/dist/hooks/context-window-monitor.d.ts.map +1 -0
- package/dist/hooks/decision-trace-hook.d.ts +13 -0
- package/dist/hooks/decision-trace-hook.d.ts.map +1 -0
- package/dist/hooks/file-tracker.d.ts +29 -0
- package/dist/hooks/file-tracker.d.ts.map +1 -0
- package/dist/hooks/guard-rails.d.ts +33 -0
- package/dist/hooks/guard-rails.d.ts.map +1 -0
- package/dist/hooks/index.d.ts +5 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/notifications.d.ts +21 -0
- package/dist/hooks/notifications.d.ts.map +1 -0
- package/dist/hooks/patch-trust.d.ts +27 -0
- package/dist/hooks/patch-trust.d.ts.map +1 -0
- package/dist/hooks/patch-trust.test.d.ts +2 -0
- package/dist/hooks/patch-trust.test.d.ts.map +1 -0
- package/dist/hooks/session-events.d.ts +8 -0
- package/dist/hooks/session-events.d.ts.map +1 -0
- package/dist/hooks/session-idle-hook.d.ts +21 -0
- package/dist/hooks/session-idle-hook.d.ts.map +1 -0
- package/dist/hooks/session-start.d.ts +10 -0
- package/dist/hooks/session-start.d.ts.map +1 -0
- package/dist/hooks/shell-env-hook.d.ts +21 -0
- package/dist/hooks/shell-env-hook.d.ts.map +1 -0
- package/dist/hooks/telemetry-hook.d.ts +25 -0
- package/dist/hooks/telemetry-hook.d.ts.map +1 -0
- package/dist/hooks/todo-hook.d.ts +25 -0
- package/dist/hooks/todo-hook.d.ts.map +1 -0
- package/dist/hooks/tool-guard.d.ts +41 -0
- package/dist/hooks/tool-guard.d.ts.map +1 -0
- package/dist/hooks/tool-guard.test.d.ts +2 -0
- package/dist/hooks/tool-guard.test.d.ts.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6639 -0
- package/dist/lib/confirmation.d.ts +20 -0
- package/dist/lib/confirmation.d.ts.map +1 -0
- package/dist/lib/impact-radar.d.ts +35 -0
- package/dist/lib/impact-radar.d.ts.map +1 -0
- package/dist/lib/signatures.d.ts +12 -0
- package/dist/lib/signatures.d.ts.map +1 -0
- package/dist/lib/timestamps.d.ts +23 -0
- package/dist/lib/timestamps.d.ts.map +1 -0
- package/dist/mcp/index.d.ts +20 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/services/agent-performance.d.ts +29 -0
- package/dist/services/agent-performance.d.ts.map +1 -0
- package/dist/services/approval-manager.d.ts +30 -0
- package/dist/services/approval-manager.d.ts.map +1 -0
- package/dist/services/index.d.ts +7 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/model-router.d.ts +35 -0
- package/dist/services/model-router.d.ts.map +1 -0
- package/dist/services/policy-compiler.d.ts +27 -0
- package/dist/services/policy-compiler.d.ts.map +1 -0
- package/dist/services/run-trace.d.ts +34 -0
- package/dist/services/run-trace.d.ts.map +1 -0
- package/dist/services/services.test.d.ts +2 -0
- package/dist/services/services.test.d.ts.map +1 -0
- package/dist/services/telemetry.d.ts +34 -0
- package/dist/services/telemetry.d.ts.map +1 -0
- package/dist/tools/agent-dispatch.test.d.ts +2 -0
- package/dist/tools/agent-dispatch.test.d.ts.map +1 -0
- package/dist/tools/codebase-state.d.ts +4 -0
- package/dist/tools/codebase-state.d.ts.map +1 -0
- package/dist/tools/context-generator.d.ts +3 -0
- package/dist/tools/context-generator.d.ts.map +1 -0
- package/dist/tools/council.d.ts +4 -0
- package/dist/tools/council.d.ts.map +1 -0
- package/dist/tools/decision-trace.d.ts +16 -0
- package/dist/tools/decision-trace.d.ts.map +1 -0
- package/dist/tools/delegate.d.ts +4 -0
- package/dist/tools/delegate.d.ts.map +1 -0
- package/dist/tools/failure-replay.d.ts +19 -0
- package/dist/tools/failure-replay.d.ts.map +1 -0
- package/dist/tools/failure-replay.test.d.ts +2 -0
- package/dist/tools/failure-replay.test.d.ts.map +1 -0
- package/dist/tools/hash-edit.d.ts +3 -0
- package/dist/tools/hash-edit.d.ts.map +1 -0
- package/dist/tools/planning-state-lib.d.ts +65 -0
- package/dist/tools/planning-state-lib.d.ts.map +1 -0
- package/dist/tools/planning-state.d.ts +3 -0
- package/dist/tools/planning-state.d.ts.map +1 -0
- package/dist/tools/policy-engine.d.ts +19 -0
- package/dist/tools/policy-engine.d.ts.map +1 -0
- package/dist/tools/repo-memory.d.ts +20 -0
- package/dist/tools/repo-memory.d.ts.map +1 -0
- package/dist/tools/repo-memory.test.d.ts +2 -0
- package/dist/tools/repo-memory.test.d.ts.map +1 -0
- package/dist/tools/run-parallel.d.ts +4 -0
- package/dist/tools/run-parallel.d.ts.map +1 -0
- package/dist/tools/run-pipeline.d.ts +4 -0
- package/dist/tools/run-pipeline.d.ts.map +1 -0
- package/dist/tools/volatility-map.d.ts +18 -0
- package/dist/tools/volatility-map.d.ts.map +1 -0
- package/dist/tools/volatility-map.test.d.ts +2 -0
- package/dist/tools/volatility-map.test.d.ts.map +1 -0
- package/dist/tools/workspace-state.d.ts +3 -0
- package/dist/tools/workspace-state.d.ts.map +1 -0
- package/docs/USER_GUIDE.md +20 -0
- package/docs/agents.md +562 -0
- package/docs/best-practices.md +47 -0
- package/docs/command-migration.md +175 -0
- package/docs/commands/fd-analyze-change.md +107 -0
- package/docs/commands/fd-ask.md +51 -0
- package/docs/commands/fd-checkpoint.md +10 -0
- package/docs/commands/fd-dashboard.md +11 -0
- package/docs/commands/fd-deploy-check.md +11 -0
- package/docs/commands/fd-discuss.md +28 -0
- package/docs/commands/fd-evaluate-risk.md +134 -0
- package/docs/commands/fd-fix-bug.md +24 -0
- package/docs/commands/fd-guarded-edit.md +105 -0
- package/docs/commands/fd-map-codebase.md +27 -0
- package/docs/commands/fd-multi-repo.md +63 -0
- package/docs/commands/fd-new-feature.md +25 -0
- package/docs/commands/fd-new-project.md +24 -0
- package/docs/commands/fd-plan.md +33 -0
- package/docs/commands/fd-progress.md +11 -0
- package/docs/commands/fd-resume.md +10 -0
- package/docs/commands/fd-review-code.md +29 -0
- package/docs/commands/fd-roadmap.md +10 -0
- package/docs/commands/fd-settings.md +10 -0
- package/docs/commands/fd-write-docs.md +10 -0
- package/docs/commands.md +476 -0
- package/docs/configuration.md +211 -0
- package/docs/feature-integration-architecture.md +255 -0
- package/docs/index.md +75 -0
- package/docs/installation.md +134 -0
- package/docs/intelligence.md +294 -0
- package/docs/multi-repo.md +201 -0
- package/docs/notifications.md +170 -0
- package/docs/parallel-execution.md +227 -0
- package/docs/quick-start.md +174 -0
- package/docs/rules.md +459 -0
- package/docs/skills.md +408 -0
- package/docs/workflows.md +376 -0
- package/package.json +58 -0
- package/postinstall.mjs +102 -0
- package/src/rules/README.md +37 -0
- package/src/rules/common/agent-orchestration.md +86 -0
- package/src/rules/common/coding-style.md +120 -0
- package/src/rules/common/git-workflow.md +77 -0
- package/src/rules/common/security.md +94 -0
- package/src/rules/common/testing.md +105 -0
- package/src/rules/golang/patterns.md +187 -0
- package/src/rules/java/patterns.md +204 -0
- package/src/rules/python/patterns.md +141 -0
- package/src/rules/rust/patterns.md +210 -0
- package/src/rules/typescript/patterns.md +168 -0
- package/src/skills/api-design/SKILL.md +143 -0
- package/src/skills/arch-constraint-guard/SKILL.md +61 -0
- package/src/skills/blast-radius-preview/SKILL.md +65 -0
- package/src/skills/change-impact-radar/SKILL.md +63 -0
- package/src/skills/code-review/SKILL.md +108 -0
- package/src/skills/code-tour/SKILL.md +101 -0
- package/src/skills/codebase-mapping/SKILL.md +87 -0
- package/src/skills/codebase-onboarding/SKILL.md +133 -0
- package/src/skills/confidence-aware-planning/SKILL.md +67 -0
- package/src/skills/context-load/SKILL.md +63 -0
- package/src/skills/debug-flow/SKILL.md +75 -0
- package/src/skills/decision-trace/SKILL.md +72 -0
- package/src/skills/dependency-audit/SKILL.md +126 -0
- package/src/skills/deploy-check/SKILL.md +87 -0
- package/src/skills/documentation-writer/SKILL.md +154 -0
- package/src/skills/failure-replay-engine/SKILL.md +59 -0
- package/src/skills/git-release/SKILL.md +94 -0
- package/src/skills/git-workflow/SKILL.md +177 -0
- package/src/skills/golang-patterns/SKILL.md +511 -0
- package/src/skills/human-review-routing/SKILL.md +65 -0
- package/src/skills/intent-translator/SKILL.md +57 -0
- package/src/skills/java-patterns/SKILL.md +479 -0
- package/src/skills/multi-repo/SKILL.md +187 -0
- package/src/skills/parallel-execute/SKILL.md +92 -0
- package/src/skills/patch-trust-score/SKILL.md +44 -0
- package/src/skills/performance-profiling/SKILL.md +153 -0
- package/src/skills/plan-task/SKILL.md +101 -0
- package/src/skills/python-patterns/SKILL.md +529 -0
- package/src/skills/refactor-guide/SKILL.md +117 -0
- package/src/skills/regression-prediction/SKILL.md +57 -0
- package/src/skills/repo-memory-graph/SKILL.md +49 -0
- package/src/skills/rust-patterns/SKILL.md +492 -0
- package/src/skills/security-scan/SKILL.md +91 -0
- package/src/skills/self-healing-policies/SKILL.md +76 -0
- package/src/skills/tdd-workflow/SKILL.md +126 -0
- package/src/skills/test-coverage/SKILL.md +94 -0
- package/src/skills/test-gap-detector/SKILL.md +58 -0
- package/src/skills/volatility-map/SKILL.md +52 -0
- package/src/workflows/debug-flow.md +119 -0
- package/src/workflows/deploy-check-flow.md +98 -0
- package/src/workflows/discuss-flow.md +97 -0
- package/src/workflows/execute-flow.md +233 -0
- package/src/workflows/execute-phase.md +142 -0
- package/src/workflows/fix-bug-flow.md +210 -0
- package/src/workflows/map-codebase-flow.md +92 -0
- package/src/workflows/multi-repo-flow.md +226 -0
- package/src/workflows/parallel-execution-flow.md +236 -0
- package/src/workflows/plan-flow.md +126 -0
- package/src/workflows/plan-phase.md +101 -0
- package/src/workflows/refactor-flow.md +122 -0
- package/src/workflows/review-code-flow.md +105 -0
- package/src/workflows/spec-driven-flow.md +43 -0
- package/src/workflows/write-docs-flow.md +95 -0
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: java-patterns
|
|
3
|
+
description: Modern Java (17+) patterns covering records, sealed classes, Stream API, CompletableFuture, Spring Boot, JPA, and testing. Activate when writing or reviewing Java code.
|
|
4
|
+
origin: FlowDeck
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Java Patterns Skill
|
|
8
|
+
|
|
9
|
+
Modern Java for production applications. Focuses on Java 17+ features and Spring Boot conventions.
|
|
10
|
+
|
|
11
|
+
## When to Activate
|
|
12
|
+
|
|
13
|
+
Activate when:
|
|
14
|
+
- Writing new Java services or libraries
|
|
15
|
+
- Reviewing Java code for correctness and modern idiom
|
|
16
|
+
- Designing API layers, service classes, or data access
|
|
17
|
+
- Troubleshooting Spring Boot configuration or JPA queries
|
|
18
|
+
- Setting up testing infrastructure
|
|
19
|
+
|
|
20
|
+
## Modern Java Features (17+)
|
|
21
|
+
|
|
22
|
+
### Records — Immutable Data Carriers
|
|
23
|
+
|
|
24
|
+
Records eliminate boilerplate for data classes. Use them for DTOs, value objects, and command/query objects.
|
|
25
|
+
|
|
26
|
+
```java
|
|
27
|
+
// Compiler generates constructor, getters, equals, hashCode, toString
|
|
28
|
+
public record Point(double x, double y) {}
|
|
29
|
+
|
|
30
|
+
// Custom constructor for validation
|
|
31
|
+
public record EmailAddress(String value) {
|
|
32
|
+
public EmailAddress {
|
|
33
|
+
Objects.requireNonNull(value);
|
|
34
|
+
if (!value.contains("@")) {
|
|
35
|
+
throw new IllegalArgumentException("invalid email: " + value);
|
|
36
|
+
}
|
|
37
|
+
value = value.toLowerCase();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Records can implement interfaces
|
|
42
|
+
public record PageRequest(int page, int size) implements Serializable {
|
|
43
|
+
public PageRequest {
|
|
44
|
+
if (page < 0) throw new IllegalArgumentException("page must be >= 0");
|
|
45
|
+
if (size < 1 || size > 100) throw new IllegalArgumentException("size must be 1-100");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public int offset() { return page * size; }
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Sealed Classes — Closed Hierarchies
|
|
53
|
+
|
|
54
|
+
Use sealed classes to model a fixed set of variants. The compiler enforces exhaustive pattern matching.
|
|
55
|
+
|
|
56
|
+
```java
|
|
57
|
+
public sealed interface PaymentResult
|
|
58
|
+
permits PaymentResult.Success, PaymentResult.Declined, PaymentResult.Error {
|
|
59
|
+
|
|
60
|
+
record Success(String transactionId, BigDecimal amount) implements PaymentResult {}
|
|
61
|
+
record Declined(String reason) implements PaymentResult {}
|
|
62
|
+
record Error(Throwable cause) implements PaymentResult {}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Pattern matching ensures all cases are handled
|
|
66
|
+
String message = switch (result) {
|
|
67
|
+
case PaymentResult.Success s -> "Charged " + s.amount();
|
|
68
|
+
case PaymentResult.Declined d -> "Declined: " + d.reason();
|
|
69
|
+
case PaymentResult.Error e -> "Error: " + e.cause().getMessage();
|
|
70
|
+
};
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Pattern Matching instanceof
|
|
74
|
+
|
|
75
|
+
```java
|
|
76
|
+
// ❌ Old style — verbose cast
|
|
77
|
+
if (obj instanceof String) {
|
|
78
|
+
String s = (String) obj;
|
|
79
|
+
System.out.println(s.length());
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ✅ Pattern variable — binding in same expression
|
|
83
|
+
if (obj instanceof String s) {
|
|
84
|
+
System.out.println(s.length());
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Combining with guards
|
|
88
|
+
if (obj instanceof String s && s.length() > 5) {
|
|
89
|
+
System.out.println("long string: " + s);
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Text Blocks
|
|
94
|
+
|
|
95
|
+
```java
|
|
96
|
+
// ✅ Multiline strings without escaping
|
|
97
|
+
String json = """
|
|
98
|
+
{
|
|
99
|
+
"name": "Alice",
|
|
100
|
+
"age": 30
|
|
101
|
+
}
|
|
102
|
+
""";
|
|
103
|
+
|
|
104
|
+
String query = """
|
|
105
|
+
SELECT u.id, u.email
|
|
106
|
+
FROM users u
|
|
107
|
+
WHERE u.active = true
|
|
108
|
+
AND u.created_at > :since
|
|
109
|
+
""";
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Optional — Use at API Boundaries
|
|
113
|
+
|
|
114
|
+
Optional communicates "might be absent" in a return type. Never use it as a field type or parameter.
|
|
115
|
+
|
|
116
|
+
```java
|
|
117
|
+
// ✅ Return type — caller must handle absence
|
|
118
|
+
public Optional<User> findByEmail(String email) {
|
|
119
|
+
return userRepository.findByEmail(email);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ✅ Chained operations
|
|
123
|
+
String displayName = findByEmail(email)
|
|
124
|
+
.map(User::displayName)
|
|
125
|
+
.orElse("Anonymous");
|
|
126
|
+
|
|
127
|
+
// ✅ Throw if absent with meaningful message
|
|
128
|
+
User user = findByEmail(email)
|
|
129
|
+
.orElseThrow(() -> new UserNotFoundException(email));
|
|
130
|
+
|
|
131
|
+
// ✅ Execute only if present
|
|
132
|
+
findByEmail(email).ifPresent(user -> auditLog.record(user.id()));
|
|
133
|
+
|
|
134
|
+
// ❌ Never null-check Optional itself
|
|
135
|
+
Optional<User> opt = findByEmail(email);
|
|
136
|
+
if (opt != null && opt.isPresent()) { ... } // wrong
|
|
137
|
+
|
|
138
|
+
// ❌ Never use as a field type
|
|
139
|
+
class Service {
|
|
140
|
+
private Optional<Cache> cache; // wrong — use @Nullable or just null
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Stream API
|
|
145
|
+
|
|
146
|
+
### Core Operations
|
|
147
|
+
|
|
148
|
+
```java
|
|
149
|
+
List<String> activeEmails = users.stream()
|
|
150
|
+
.filter(User::isActive)
|
|
151
|
+
.sorted(Comparator.comparing(User::lastName))
|
|
152
|
+
.map(User::email)
|
|
153
|
+
.toList(); // Java 16+ unmodifiable list
|
|
154
|
+
|
|
155
|
+
// Collectors
|
|
156
|
+
Map<Department, List<Employee>> byDept = employees.stream()
|
|
157
|
+
.collect(Collectors.groupingBy(Employee::department));
|
|
158
|
+
|
|
159
|
+
Map<Boolean, List<User>> partitioned = users.stream()
|
|
160
|
+
.collect(Collectors.partitioningBy(User::isAdmin));
|
|
161
|
+
|
|
162
|
+
// Reduce
|
|
163
|
+
BigDecimal total = cart.items().stream()
|
|
164
|
+
.map(Item::price)
|
|
165
|
+
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### flatMap — Flattening Nested Collections
|
|
169
|
+
|
|
170
|
+
```java
|
|
171
|
+
// One user has many orders, each order has many items
|
|
172
|
+
List<Item> allItems = users.stream()
|
|
173
|
+
.flatMap(u -> u.orders().stream())
|
|
174
|
+
.flatMap(o -> o.items().stream())
|
|
175
|
+
.toList();
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Parallel Streams — When NOT to Use
|
|
179
|
+
|
|
180
|
+
```java
|
|
181
|
+
// ✅ Parallel for CPU-bound work on large, independent datasets
|
|
182
|
+
long count = IntStream.range(0, 10_000_000)
|
|
183
|
+
.parallel()
|
|
184
|
+
.filter(n -> isPrime(n))
|
|
185
|
+
.count();
|
|
186
|
+
|
|
187
|
+
// ❌ Parallel with shared mutable state — data race
|
|
188
|
+
List<Integer> result = new ArrayList<>();
|
|
189
|
+
IntStream.range(0, 1000).parallel()
|
|
190
|
+
.forEach(result::add); // non-thread-safe!
|
|
191
|
+
|
|
192
|
+
// ❌ Parallel for I/O-bound work — blocks common ForkJoinPool
|
|
193
|
+
Stream.of(urls).parallel()
|
|
194
|
+
.map(this::fetch) // ties up ForkJoin threads on I/O
|
|
195
|
+
.toList();
|
|
196
|
+
|
|
197
|
+
// Rule: parallel only when dataset is large, work is CPU-bound,
|
|
198
|
+
// and no shared mutable state exists. Benchmark first.
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## CompletableFuture — Async Composition
|
|
202
|
+
|
|
203
|
+
```java
|
|
204
|
+
// Chain transformations
|
|
205
|
+
CompletableFuture<String> result = CompletableFuture
|
|
206
|
+
.supplyAsync(() -> fetchUser(userId)) // background thread
|
|
207
|
+
.thenApply(User::email) // same thread
|
|
208
|
+
.thenApplyAsync(this::sendWelcome, executor); // different thread
|
|
209
|
+
|
|
210
|
+
// Compose dependent futures (flatMap equivalent)
|
|
211
|
+
CompletableFuture<Order> order = CompletableFuture
|
|
212
|
+
.supplyAsync(() -> findUser(id))
|
|
213
|
+
.thenCompose(user -> placeOrder(user, items)); // avoids nested futures
|
|
214
|
+
|
|
215
|
+
// Recover from failure
|
|
216
|
+
CompletableFuture<User> withFallback = fetchUser(id)
|
|
217
|
+
.exceptionally(ex -> {
|
|
218
|
+
log.warn("fetch failed, using guest", ex);
|
|
219
|
+
return User.guest();
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// Wait for multiple independent futures
|
|
223
|
+
CompletableFuture<Void> all = CompletableFuture.allOf(
|
|
224
|
+
sendEmail(user), updateCache(user), auditLog(user)
|
|
225
|
+
);
|
|
226
|
+
all.join(); // blocks — use only at the top of the call stack
|
|
227
|
+
|
|
228
|
+
// Timeout
|
|
229
|
+
CompletableFuture<User> withTimeout = fetchUser(id)
|
|
230
|
+
.orTimeout(2, TimeUnit.SECONDS)
|
|
231
|
+
.exceptionally(ex -> User.guest());
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Spring Boot Patterns
|
|
235
|
+
|
|
236
|
+
### Dependency Injection — Constructor Injection Only
|
|
237
|
+
|
|
238
|
+
```java
|
|
239
|
+
// ✅ Constructor injection — dependencies are explicit and final
|
|
240
|
+
@Service
|
|
241
|
+
public class OrderService {
|
|
242
|
+
private final OrderRepository orders;
|
|
243
|
+
private final PaymentGateway payments;
|
|
244
|
+
private final EventPublisher events;
|
|
245
|
+
|
|
246
|
+
public OrderService(
|
|
247
|
+
OrderRepository orders,
|
|
248
|
+
PaymentGateway payments,
|
|
249
|
+
EventPublisher events
|
|
250
|
+
) {
|
|
251
|
+
this.orders = orders;
|
|
252
|
+
this.payments = payments;
|
|
253
|
+
this.events = events;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ❌ Field injection — hides dependencies, breaks tests
|
|
258
|
+
@Service
|
|
259
|
+
public class OrderService {
|
|
260
|
+
@Autowired private OrderRepository orders; // bad
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### @ConfigurationProperties — Typed Config
|
|
265
|
+
|
|
266
|
+
```java
|
|
267
|
+
@ConfigurationProperties(prefix = "payment")
|
|
268
|
+
public record PaymentConfig(
|
|
269
|
+
String apiKey,
|
|
270
|
+
URI baseUrl,
|
|
271
|
+
Duration timeout,
|
|
272
|
+
int maxRetries
|
|
273
|
+
) {}
|
|
274
|
+
|
|
275
|
+
// application.yml
|
|
276
|
+
// payment:
|
|
277
|
+
// api-key: sk_live_...
|
|
278
|
+
// base-url: https://api.stripe.com
|
|
279
|
+
// timeout: 5s
|
|
280
|
+
// max-retries: 3
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Layered Stereotypes
|
|
284
|
+
|
|
285
|
+
```java
|
|
286
|
+
@Repository // data access — wraps DataAccessException
|
|
287
|
+
@Service // business logic
|
|
288
|
+
@Component // general-purpose bean
|
|
289
|
+
@RestController // HTTP endpoints (combines @Controller + @ResponseBody)
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
## JPA and Hibernate
|
|
293
|
+
|
|
294
|
+
### N+1 Problem
|
|
295
|
+
|
|
296
|
+
```java
|
|
297
|
+
// ❌ N+1: one query for orders, then one query per order for items
|
|
298
|
+
List<Order> orders = orderRepo.findAll();
|
|
299
|
+
orders.forEach(o -> System.out.println(o.getItems().size()));
|
|
300
|
+
|
|
301
|
+
// ✅ Fetch join: single query
|
|
302
|
+
@Query("SELECT o FROM Order o JOIN FETCH o.items WHERE o.userId = :userId")
|
|
303
|
+
List<Order> findWithItems(@Param("userId") Long userId);
|
|
304
|
+
|
|
305
|
+
// ✅ Or use @EntityGraph for reuse
|
|
306
|
+
@EntityGraph(attributePaths = {"items", "items.product"})
|
|
307
|
+
List<Order> findByUserId(Long userId);
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### Fetch Strategy
|
|
311
|
+
|
|
312
|
+
```java
|
|
313
|
+
// Default for @OneToMany is LAZY — keep it that way
|
|
314
|
+
@OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
|
|
315
|
+
private List<OrderItem> items;
|
|
316
|
+
|
|
317
|
+
// EAGER causes joins on every query, even when items aren't needed
|
|
318
|
+
// Only use EAGER for @ManyToOne / @OneToOne where the association is always needed
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Transaction Boundaries
|
|
322
|
+
|
|
323
|
+
```java
|
|
324
|
+
// @Transactional on service methods, not repository methods
|
|
325
|
+
@Service
|
|
326
|
+
public class TransferService {
|
|
327
|
+
@Transactional
|
|
328
|
+
public void transfer(Long fromId, Long toId, BigDecimal amount) {
|
|
329
|
+
Account from = accounts.findById(fromId).orElseThrow();
|
|
330
|
+
Account to = accounts.findById(toId).orElseThrow();
|
|
331
|
+
from.debit(amount);
|
|
332
|
+
to.credit(amount);
|
|
333
|
+
// both saves happen in the same transaction
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
## Testing
|
|
339
|
+
|
|
340
|
+
### JUnit 5 with Mockito
|
|
341
|
+
|
|
342
|
+
```java
|
|
343
|
+
@ExtendWith(MockitoExtension.class)
|
|
344
|
+
class OrderServiceTest {
|
|
345
|
+
|
|
346
|
+
@Mock OrderRepository orders;
|
|
347
|
+
@Mock PaymentGateway payments;
|
|
348
|
+
@InjectMocks OrderService service;
|
|
349
|
+
|
|
350
|
+
@Test
|
|
351
|
+
void placeOrder_chargesPaymentAndSavesOrder() {
|
|
352
|
+
var user = aUser().build();
|
|
353
|
+
var items = List.of(anItem().withPrice(BigDecimal.TEN).build());
|
|
354
|
+
when(payments.charge(any(), any())).thenReturn(chargeSuccess());
|
|
355
|
+
|
|
356
|
+
var result = service.placeOrder(user, items);
|
|
357
|
+
|
|
358
|
+
assertThat(result.status()).isEqualTo(OrderStatus.CONFIRMED);
|
|
359
|
+
verify(orders).save(argThat(o -> o.total().equals(BigDecimal.TEN)));
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### Spring Boot Test Slices
|
|
365
|
+
|
|
366
|
+
```java
|
|
367
|
+
// Unit test — no Spring context
|
|
368
|
+
@ExtendWith(MockitoExtension.class)
|
|
369
|
+
class UserServiceTest { ... }
|
|
370
|
+
|
|
371
|
+
// Web layer only — no full context
|
|
372
|
+
@WebMvcTest(UserController.class)
|
|
373
|
+
class UserControllerTest {
|
|
374
|
+
@Autowired MockMvc mvc;
|
|
375
|
+
@MockBean UserService service;
|
|
376
|
+
|
|
377
|
+
@Test
|
|
378
|
+
void getUser_returns200() throws Exception {
|
|
379
|
+
when(service.findById(1L)).thenReturn(Optional.of(aUser().build()));
|
|
380
|
+
mvc.perform(get("/users/1"))
|
|
381
|
+
.andExpect(status().isOk())
|
|
382
|
+
.andExpect(jsonPath("$.email").value("alice@example.com"));
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Full integration test
|
|
387
|
+
@SpringBootTest(webEnvironment = RANDOM_PORT)
|
|
388
|
+
@Transactional // rolls back each test
|
|
389
|
+
class OrderIntegrationTest { ... }
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
## Build Configuration
|
|
393
|
+
|
|
394
|
+
### Maven
|
|
395
|
+
|
|
396
|
+
```xml
|
|
397
|
+
<properties>
|
|
398
|
+
<java.version>21</java.version>
|
|
399
|
+
<spring-boot.version>3.3.0</spring-boot.version>
|
|
400
|
+
</properties>
|
|
401
|
+
|
|
402
|
+
<plugin>
|
|
403
|
+
<groupId>org.apache.maven.plugins</groupId>
|
|
404
|
+
<artifactId>maven-compiler-plugin</artifactId>
|
|
405
|
+
<configuration>
|
|
406
|
+
<release>${java.version}</release>
|
|
407
|
+
<compilerArgs><arg>-parameters</arg></compilerArgs>
|
|
408
|
+
</configuration>
|
|
409
|
+
</plugin>
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### Gradle (Kotlin DSL)
|
|
413
|
+
|
|
414
|
+
```kotlin
|
|
415
|
+
java {
|
|
416
|
+
toolchain { languageVersion = JavaLanguageVersion.of(21) }
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
tasks.withType<JavaCompile> {
|
|
420
|
+
options.compilerArgs.add("-parameters")
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
## Common Pitfalls
|
|
425
|
+
|
|
426
|
+
### Checked Exceptions in New Code
|
|
427
|
+
|
|
428
|
+
```java
|
|
429
|
+
// ❌ Checked exceptions force callers to handle or declare them
|
|
430
|
+
public List<User> loadUsers() throws IOException, ParseException { ... }
|
|
431
|
+
|
|
432
|
+
// ✅ Wrap in unchecked and carry the cause
|
|
433
|
+
public List<User> loadUsers() {
|
|
434
|
+
try {
|
|
435
|
+
return parser.parse(Files.readString(path));
|
|
436
|
+
} catch (IOException | ParseException e) {
|
|
437
|
+
throw new UserLoadException("failed to load users from " + path, e);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
### String Concatenation in Loops
|
|
443
|
+
|
|
444
|
+
```java
|
|
445
|
+
// ❌ Creates a new String object every iteration — O(n²) allocations
|
|
446
|
+
String result = "";
|
|
447
|
+
for (String s : list) {
|
|
448
|
+
result += s + ", ";
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// ✅ StringBuilder is O(n)
|
|
452
|
+
var sb = new StringBuilder();
|
|
453
|
+
for (String s : list) {
|
|
454
|
+
sb.append(s).append(", ");
|
|
455
|
+
}
|
|
456
|
+
String result = sb.toString();
|
|
457
|
+
|
|
458
|
+
// ✅ Or use String.join / Collectors.joining
|
|
459
|
+
String result = String.join(", ", list);
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### Autoboxing Overhead
|
|
463
|
+
|
|
464
|
+
```java
|
|
465
|
+
// ❌ Unnecessary boxing in tight loops
|
|
466
|
+
Long sum = 0L;
|
|
467
|
+
for (long i = 0; i < 1_000_000; i++) {
|
|
468
|
+
sum += i; // unboxes sum, adds, boxes result each iteration
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// ✅ Use primitives
|
|
472
|
+
long sum = 0L;
|
|
473
|
+
for (long i = 0; i < 1_000_000; i++) {
|
|
474
|
+
sum += i;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// ✅ LongStream avoids boxing entirely
|
|
478
|
+
long sum = LongStream.range(0, 1_000_000).sum();
|
|
479
|
+
```
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: multi-repo
|
|
3
|
+
description: Coordinate changes across multiple repositories in a microservice architecture. Manages cross-repo dependencies, API contract evolution, and ordered rollouts.
|
|
4
|
+
origin: FlowDeck
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Multi-Repo Skill
|
|
8
|
+
|
|
9
|
+
Provides the patterns and vocabulary for planning, sequencing, and executing changes that span more than one repository in a microservice system.
|
|
10
|
+
|
|
11
|
+
## When to Activate
|
|
12
|
+
|
|
13
|
+
Activate when:
|
|
14
|
+
- A feature or fix requires changes in two or more repositories
|
|
15
|
+
- An API contract in one service must change and consumers exist in other repos
|
|
16
|
+
- A shared library version bump ripples downstream
|
|
17
|
+
- You need to verify no two services are making incompatible concurrent changes
|
|
18
|
+
|
|
19
|
+
Do not activate for single-repo changes, even if the repo is part of a larger microservice system.
|
|
20
|
+
|
|
21
|
+
## Architecture Patterns
|
|
22
|
+
|
|
23
|
+
### Service Mesh
|
|
24
|
+
|
|
25
|
+
Services communicate through well-defined contracts (REST, gRPC, event schemas). No direct database sharing between services. Each service owns its data store.
|
|
26
|
+
|
|
27
|
+
Key implication: contract changes are the primary coordination surface. A service can change its internals freely; it cannot change its contract without coordinating consumers.
|
|
28
|
+
|
|
29
|
+
### API Contracts
|
|
30
|
+
|
|
31
|
+
Every `upstream-api` service maintains a contract — the stable interface it promises to consumers. Contracts define:
|
|
32
|
+
- Endpoint paths and HTTP methods (or gRPC service definitions)
|
|
33
|
+
- Request and response schemas (fields, types, nullability)
|
|
34
|
+
- Error response shapes
|
|
35
|
+
- Authentication requirements
|
|
36
|
+
- Rate limits and SLAs (where relevant)
|
|
37
|
+
|
|
38
|
+
Contracts are the input to `@architect` when designing cross-repo changes.
|
|
39
|
+
|
|
40
|
+
### Event-Driven Changes
|
|
41
|
+
|
|
42
|
+
For services that communicate via message queues or event streams:
|
|
43
|
+
- The event schema plays the same role as an API contract
|
|
44
|
+
- Producers must publish the new schema before consumers update their handlers
|
|
45
|
+
- Both old and new schema versions must be supported during the transition window
|
|
46
|
+
- Treat schema changes with the same breaking/non-breaking classification used for REST APIs
|
|
47
|
+
|
|
48
|
+
## Microservice Role Vocabulary
|
|
49
|
+
|
|
50
|
+
| Role | Meaning |
|
|
51
|
+
|------|---------|
|
|
52
|
+
| `upstream-api` | Provides an API consumed by other services |
|
|
53
|
+
| `downstream-consumer` | Calls one or more upstream APIs |
|
|
54
|
+
| `shared-lib` | Imported as a package dependency by other services |
|
|
55
|
+
| `gateway` | Routes external traffic to internal services |
|
|
56
|
+
| `worker` | Processes background jobs; no inbound HTTP API |
|
|
57
|
+
|
|
58
|
+
## Config Schema
|
|
59
|
+
|
|
60
|
+
The multi-repo registry lives at `.planning/config.json` in the root repository.
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{
|
|
64
|
+
"sub_repos": [
|
|
65
|
+
{
|
|
66
|
+
"name": "user-service",
|
|
67
|
+
"path": "../user-service",
|
|
68
|
+
"role": "upstream-api",
|
|
69
|
+
"tech_stack": "node+typescript",
|
|
70
|
+
"owner_team": "platform"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"name": "order-service",
|
|
74
|
+
"path": "../order-service",
|
|
75
|
+
"role": "downstream-consumer",
|
|
76
|
+
"tech_stack": "node+typescript",
|
|
77
|
+
"owner_team": "commerce"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"name": "shared-types",
|
|
81
|
+
"path": "../shared-types",
|
|
82
|
+
"role": "shared-lib",
|
|
83
|
+
"tech_stack": "node+typescript",
|
|
84
|
+
"owner_team": "platform"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"name": "api-gateway",
|
|
88
|
+
"path": "../api-gateway",
|
|
89
|
+
"role": "gateway",
|
|
90
|
+
"tech_stack": "nginx+lua",
|
|
91
|
+
"owner_team": "infra"
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Field definitions:**
|
|
98
|
+
- `name` — unique identifier used in dependency graph and change plans
|
|
99
|
+
- `path` — relative path from the root repo's parent directory (the directory containing `.planning/`)
|
|
100
|
+
- `role` — service role from the vocabulary above; determines dependency ordering
|
|
101
|
+
- `tech_stack` — technology used; informs which tools and commands to run
|
|
102
|
+
- `owner_team` — team responsible; flagged in conflict reports and escalations
|
|
103
|
+
|
|
104
|
+
## Cross-Repo Workflow
|
|
105
|
+
|
|
106
|
+
### When to Use Feature Branches vs Trunk Per Repo
|
|
107
|
+
|
|
108
|
+
**Feature branches per repo:**
|
|
109
|
+
- Use when the change spans multiple repos and involves breaking API changes
|
|
110
|
+
- Each repo gets its own branch (e.g., `feature/refresh-token-support`)
|
|
111
|
+
- Branch per repo enables coordinated review before any deploy
|
|
112
|
+
- Merge in dependency order: upstream merged and released before downstream merges
|
|
113
|
+
|
|
114
|
+
**Trunk-based per repo:**
|
|
115
|
+
- Use when the change is non-breaking (additive field, new optional endpoint)
|
|
116
|
+
- Changes can be merged independently to each repo's main branch
|
|
117
|
+
- Downstream consumers update on their own schedule
|
|
118
|
+
- Safe because the old contract remains valid during transition
|
|
119
|
+
|
|
120
|
+
Decision rule: if any change in the set is breaking for any consumer, use feature branches. If all changes are non-breaking, trunk-based is fine.
|
|
121
|
+
|
|
122
|
+
## Contract-First Development Pattern
|
|
123
|
+
|
|
124
|
+
For any cross-repo change involving an API:
|
|
125
|
+
|
|
126
|
+
1. **Write the new contract first** — `@architect` produces the updated interface definition before any code is written
|
|
127
|
+
2. **Review the contract in isolation** — confirm all affected teams agree before implementation starts
|
|
128
|
+
3. **Implement upstream against the new contract** — `@coder` in the upstream repo
|
|
129
|
+
4. **Implement downstream against the new contract** — `@coder` in each consumer repo, independently
|
|
130
|
+
5. **Integration test** — verify upstream and downstream work together against the contract
|
|
131
|
+
|
|
132
|
+
This pattern allows Wave 3 parallelism (upstream and downstream `@coder` agents can work simultaneously from the same contract) even across repos.
|
|
133
|
+
|
|
134
|
+
## Breaking vs Non-Breaking API Changes
|
|
135
|
+
|
|
136
|
+
### Non-Breaking (safe to deploy independently):
|
|
137
|
+
- Adding a new optional field to a response object
|
|
138
|
+
- Adding a new endpoint (existing endpoints unchanged)
|
|
139
|
+
- Adding an optional request parameter
|
|
140
|
+
- Relaxing a validation rule (e.g., allowing a longer string)
|
|
141
|
+
- Adding a new event type to a message stream
|
|
142
|
+
|
|
143
|
+
### Breaking (requires coordinated rollout):
|
|
144
|
+
- Removing a field from a response object
|
|
145
|
+
- Renaming an existing field
|
|
146
|
+
- Changing a field's type (e.g., string → number)
|
|
147
|
+
- Making an optional field required
|
|
148
|
+
- Removing or renaming an endpoint
|
|
149
|
+
- Changing the authentication scheme
|
|
150
|
+
- Modifying an existing event schema in an incompatible way
|
|
151
|
+
|
|
152
|
+
When a breaking change is unavoidable, use versioned paths (`/v2/endpoint`) to maintain the old contract alongside the new one during the transition window. Retire the old version only after all consumers have migrated.
|
|
153
|
+
|
|
154
|
+
## Rollout Sequencing
|
|
155
|
+
|
|
156
|
+
Changes deploy in dependency order. The general sequence:
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
1. shared-lib → publish new version (semver bump)
|
|
160
|
+
2. upstream-api → canary → stage → prod
|
|
161
|
+
3. downstream-consumers → canary → stage → prod (after upstream is in prod)
|
|
162
|
+
4. gateway → route config update after all services are live in prod
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Canary phase:** 5% of traffic, monitor error rate and latency for 15 minutes before proceeding.
|
|
166
|
+
**Stage phase:** full deploy to staging environment, run integration tests.
|
|
167
|
+
**Prod phase:** full production deploy after stage passes.
|
|
168
|
+
|
|
169
|
+
For breaking changes, complete the full canary → stage → prod cycle for the upstream before starting the canary phase for any downstream consumer. Mixed versions in prod during a breaking change window will cause errors.
|
|
170
|
+
|
|
171
|
+
## Conflict Detection Rules
|
|
172
|
+
|
|
173
|
+
Two concurrent changes conflict when:
|
|
174
|
+
- Both change the same endpoint's response shape in incompatible ways
|
|
175
|
+
- Both require incompatible versions of the same shared-lib
|
|
176
|
+
- Both claim the same gateway route prefix
|
|
177
|
+
- Both migrate the same database table in incompatible directions
|
|
178
|
+
|
|
179
|
+
Conflicts must be resolved before any CHANGE PLAN is executed. The `@multi-repo-coordinator` surfaces conflicts with resolution options; the relevant `owner_team` decides.
|
|
180
|
+
|
|
181
|
+
## Independence Check Before Executing
|
|
182
|
+
|
|
183
|
+
Before running `/multi-repo`:
|
|
184
|
+
- [ ] `.planning/config.json` has a `sub_repos` array with at least two entries
|
|
185
|
+
- [ ] All `path` values resolve to actual directories on disk
|
|
186
|
+
- [ ] Each repo has its own `.git` directory (they are separate repos, not subtrees)
|
|
187
|
+
- [ ] You have read access to all repos in the registry
|