cc-codeconductor 1.4.1 → 1.5.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/README.md +61 -13
- package/dist/core/verification/rdd-receipt.d.ts +39 -0
- package/dist/core/verification/verification-runner.d.ts +8 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1257 -330
- package/dist/library.js +267 -26
- package/dist/validation/schemas.d.ts +521 -26
- package/docs/generated/cli.md +20 -0
- package/package.json +1 -1
- package/presets/agy/AGENTS.md +6 -0
- package/presets/agy/README.md +1 -1
- package/presets/agy/hooks.json +1 -1
- package/presets/agy/scripts/invoke-hook.cjs +20 -5
- package/presets/agy/settings.json +1 -1
- package/presets/agy/skills/api-versioning/SKILL.md +394 -0
- package/presets/agy/skills/astro/SKILL.md +318 -0
- package/presets/agy/skills/auth-token-inspector/SKILL.md +30 -0
- package/presets/agy/skills/cc-pagespeed/SKILL.md +2 -3
- package/presets/agy/skills/code-review/SKILL.md +207 -0
- package/presets/agy/skills/django-orm/SKILL.md +460 -0
- package/presets/agy/skills/django-uv/SKILL.md +405 -0
- package/presets/agy/skills/drizzle-schema-architect/SKILL.md +50 -0
- package/presets/agy/skills/fastapi-pydantic-strict/SKILL.md +43 -0
- package/presets/agy/skills/jpa-nplusone-detector/SKILL.md +45 -0
- package/presets/agy/skills/jpa-postgres/SKILL.md +623 -0
- package/presets/agy/skills/livewire-alpine-bridge/SKILL.md +35 -0
- package/presets/agy/skills/nextjs-typescript/SKILL.md +390 -0
- package/presets/agy/skills/python/SKILL.md +611 -0
- package/presets/agy/skills/seo-analytics-injector/SKILL.md +43 -0
- package/presets/agy/skills/spring-auth-auditor/SKILL.md +29 -0
- package/presets/agy/skills/spring-boot-feature/SKILL.md +563 -0
- package/presets/agy/skills/spring-boot-testing-strategy/SKILL.md +475 -0
- package/presets/agy/skills/tailwind-responsive-auditor/SKILL.md +29 -0
- package/presets/agy/skills/tdd-mutation-tester/SKILL.md +27 -0
- package/presets/agy/workflows/cc-handoff.md +2 -1
- package/presets/agy/workflows/cc-odd.md +15 -0
- package/presets/agy/workflows/cc-pagespeed.md +2 -3
- package/presets/agy/workflows/cc-review.md +31 -0
- package/presets/agy/workflows/cc-security.md +1 -1
- package/presets/claude/CLAUDE.md +6 -0
- package/presets/claude/commands/cc/handoff.md +2 -1
- package/presets/claude/commands/cc/odd.md +15 -0
- package/presets/claude/commands/cc/review.md +33 -0
- package/presets/claude/settings.json +2 -2
- package/presets/claude/skills/android/SKILL.md +1 -1
- package/presets/claude/skills/api-versioning/SKILL.md +1 -1
- package/presets/claude/skills/astro/SKILL.md +318 -0
- package/presets/claude/skills/auth-token-inspector/SKILL.md +30 -0
- package/presets/claude/skills/code-review/SKILL.md +207 -0
- package/presets/claude/skills/django-orm/SKILL.md +1 -1
- package/presets/claude/skills/django-testing/SKILL.md +1 -1
- package/presets/claude/skills/django-uv/SKILL.md +405 -0
- package/presets/claude/skills/drizzle-schema-architect/SKILL.md +50 -0
- package/presets/claude/skills/fastapi-pydantic-strict/SKILL.md +43 -0
- package/presets/claude/skills/jpa-nplusone-detector/SKILL.md +45 -0
- package/presets/claude/skills/jpa-postgres/SKILL.md +1 -1
- package/presets/claude/skills/livewire-alpine-bridge/SKILL.md +35 -0
- package/presets/claude/skills/nextjs-typescript/SKILL.md +390 -0
- package/presets/claude/skills/pagespeed-perf/SKILL.md +1 -1
- package/presets/claude/skills/python/SKILL.md +1 -1
- package/presets/claude/skills/python-django-stack/SKILL.md +1 -1
- package/presets/claude/skills/python-fastapi-stack/SKILL.md +1 -1
- package/presets/claude/skills/security/SKILL.md +1 -1
- package/presets/claude/skills/seo-analytics-injector/SKILL.md +43 -0
- package/presets/claude/skills/spring-auth-auditor/SKILL.md +29 -0
- package/presets/claude/skills/spring-boot-feature/SKILL.md +1 -1
- package/presets/claude/skills/spring-boot-kotlin/SKILL.md +1 -1
- package/presets/claude/skills/spring-boot-testing-strategy/SKILL.md +475 -0
- package/presets/claude/skills/sqlalchemy/SKILL.md +1 -1
- package/presets/claude/skills/tailwind-responsive-auditor/SKILL.md +29 -0
- package/presets/claude/skills/tdd-mutation-tester/SKILL.md +27 -0
- package/presets/claude/skills/testing-strategy/SKILL.md +1 -1
- package/presets/codex/AGENTS.md +6 -0
- package/presets/codex/skills/android/SKILL.md +1 -1
- package/presets/codex/skills/api-versioning/SKILL.md +1 -1
- package/presets/codex/skills/astro/SKILL.md +318 -0
- package/presets/codex/skills/auth-token-inspector/SKILL.md +30 -0
- package/presets/codex/skills/cc-handoff/SKILL.md +3 -1
- package/presets/codex/skills/cc-odd/SKILL.md +25 -0
- package/presets/codex/skills/cc-openspec/SKILL.md +5 -1
- package/presets/codex/skills/cc-pagespeed/SKILL.md +2 -3
- package/presets/codex/skills/cc-review/SKILL.md +31 -0
- package/presets/codex/skills/cc-security/SKILL.md +1 -1
- package/presets/codex/skills/cc-spec-mutation/SKILL.md +5 -0
- package/presets/codex/skills/cc-tdd-cycle/SKILL.md +8 -0
- package/presets/codex/skills/code-review/SKILL.md +207 -0
- package/presets/codex/skills/django-orm/SKILL.md +1 -1
- package/presets/codex/skills/django-testing/SKILL.md +1 -1
- package/presets/codex/skills/django-uv/SKILL.md +405 -0
- package/presets/codex/skills/drizzle-schema-architect/SKILL.md +50 -0
- package/presets/codex/skills/fastapi-pydantic-strict/SKILL.md +43 -0
- package/presets/codex/skills/jpa-nplusone-detector/SKILL.md +45 -0
- package/presets/codex/skills/jpa-postgres/SKILL.md +1 -1
- package/presets/codex/skills/livewire-alpine-bridge/SKILL.md +35 -0
- package/presets/codex/skills/nextjs-typescript/SKILL.md +390 -0
- package/presets/codex/skills/pagespeed-perf/SKILL.md +1 -1
- package/presets/codex/skills/python/SKILL.md +1 -1
- package/presets/codex/skills/python-django-stack/SKILL.md +1 -1
- package/presets/codex/skills/python-fastapi-stack/SKILL.md +1 -1
- package/presets/codex/skills/security-ai-llm/SKILL.md +43 -0
- package/presets/codex/skills/security-blue-team/SKILL.md +43 -0
- package/presets/codex/skills/security-cloud/SKILL.md +43 -0
- package/presets/codex/skills/security-crypto/SKILL.md +43 -0
- package/presets/codex/skills/security-exploit-dev/SKILL.md +45 -0
- package/presets/codex/skills/security-grc/SKILL.md +43 -0
- package/presets/codex/skills/security-incident-response/SKILL.md +45 -0
- package/presets/codex/skills/security-log-analysis/SKILL.md +43 -0
- package/presets/codex/skills/security-malware-analysis/SKILL.md +44 -0
- package/presets/codex/skills/security-mobile/SKILL.md +43 -0
- package/presets/codex/skills/security-network/SKILL.md +43 -0
- package/presets/codex/skills/security-ot-ics/SKILL.md +43 -0
- package/presets/codex/skills/security-recon/SKILL.md +45 -0
- package/presets/codex/skills/security-red-team/SKILL.md +44 -0
- package/presets/codex/skills/security-reverse-engineering/SKILL.md +44 -0
- package/presets/codex/skills/security-soc-automation/SKILL.md +43 -0
- package/presets/codex/skills/security-threat-hunting/SKILL.md +43 -0
- package/presets/codex/skills/security-vuln-assessment/SKILL.md +45 -0
- package/presets/codex/skills/security-web/SKILL.md +44 -0
- package/presets/codex/skills/seo-analytics-injector/SKILL.md +43 -0
- package/presets/codex/skills/spring-auth-auditor/SKILL.md +29 -0
- package/presets/codex/skills/spring-boot-feature/SKILL.md +2 -2
- package/presets/codex/skills/spring-boot-kotlin/SKILL.md +1 -1
- package/presets/codex/skills/spring-boot-testing-strategy/SKILL.md +475 -0
- package/presets/codex/skills/sqlalchemy/SKILL.md +1 -1
- package/presets/codex/skills/tailwind-responsive-auditor/SKILL.md +29 -0
- package/presets/codex/skills/tdd-mutation-tester/SKILL.md +27 -0
- package/presets/codex/skills/testing-strategy/SKILL.md +1 -1
- package/presets/cursor/AGENTS.md +6 -0
- package/presets/cursor/commands/cc/handoff.md +3 -1
- package/presets/cursor/commands/cc/odd.md +20 -0
- package/presets/cursor/commands/cc/openspec.md +5 -1
- package/presets/cursor/commands/cc/pagespeed.md +2 -3
- package/presets/cursor/commands/cc/review.md +31 -0
- package/presets/cursor/commands/cc/security.md +1 -1
- package/presets/cursor/commands/cc/spec-mutation.md +5 -0
- package/presets/cursor/commands/cc/tdd-cycle.md +8 -0
- package/presets/cursor/skills/android/SKILL.md +1 -1
- package/presets/cursor/skills/api-versioning/SKILL.md +2 -1
- package/presets/cursor/skills/astro/SKILL.md +1 -1
- package/presets/cursor/skills/auth-token-inspector/SKILL.md +1 -1
- package/presets/cursor/skills/code-review/SKILL.md +1 -1
- package/presets/cursor/skills/django-orm/SKILL.md +3 -5
- package/presets/cursor/skills/django-testing/SKILL.md +1 -1
- package/presets/cursor/skills/django-uv/SKILL.md +1 -1
- package/presets/cursor/skills/drizzle-schema-architect/SKILL.md +1 -1
- package/presets/cursor/skills/fastapi-pydantic-strict/SKILL.md +1 -1
- package/presets/cursor/skills/jpa-nplusone-detector/SKILL.md +1 -1
- package/presets/cursor/skills/jpa-postgres/SKILL.md +2 -4
- package/presets/cursor/skills/livewire-alpine-bridge/SKILL.md +1 -1
- package/presets/cursor/skills/nextjs-typescript/SKILL.md +1 -1
- package/presets/cursor/skills/pagespeed-perf/SKILL.md +1 -1
- package/presets/cursor/skills/python/SKILL.md +6 -7
- package/presets/cursor/skills/python-django-stack/SKILL.md +1 -1
- package/presets/cursor/skills/python-fastapi-stack/SKILL.md +1 -1
- package/presets/cursor/skills/security/SKILL.md +1 -1
- package/presets/cursor/skills/seo-analytics-injector/SKILL.md +1 -1
- package/presets/cursor/skills/spring-auth-auditor/SKILL.md +1 -1
- package/presets/cursor/skills/spring-boot-feature/SKILL.md +2 -4
- package/presets/cursor/skills/spring-boot-kotlin/SKILL.md +1 -1
- package/presets/cursor/skills/spring-boot-testing-strategy/SKILL.md +1 -1
- package/presets/cursor/skills/sqlalchemy/SKILL.md +1 -1
- package/presets/cursor/skills/tailwind-responsive-auditor/SKILL.md +1 -1
- package/presets/cursor/skills/tdd-mutation-tester/SKILL.md +1 -1
- package/presets/gemini/GEMINI.md +6 -0
- package/presets/gemini/commands/cc/handoff.toml +3 -1
- package/presets/gemini/commands/cc/odd.toml +20 -0
- package/presets/gemini/commands/cc/openspec.toml +5 -1
- package/presets/gemini/commands/cc/pagespeed.toml +2 -3
- package/presets/gemini/commands/cc/review.toml +31 -0
- package/presets/gemini/commands/cc/security.toml +1 -1
- package/presets/gemini/commands/cc/spec-mutation.toml +5 -0
- package/presets/gemini/commands/cc/tdd-cycle.toml +8 -0
- package/presets/opencode/README.md +45 -52
- package/presets/opencode/agents/implementer.md +2 -0
- package/presets/opencode/agents/reviewer.md +2 -0
- package/presets/opencode/agents/tester.md +2 -0
- package/presets/opencode/commands/cc-handoff.md +2 -1
- package/presets/opencode/commands/cc-odd.md +15 -0
- package/presets/opencode/commands/cc-pagespeed.md +2 -3
- package/presets/opencode/commands/cc-review.md +31 -0
- package/presets/opencode/commands/cc-security.md +1 -1
- package/presets/opencode/opencode.jsonc +1 -1
- package/presets/opencode/skills/android/SKILL.md +1 -1
- package/presets/opencode/skills/api-versioning/SKILL.md +2 -1
- package/presets/opencode/skills/astro/SKILL.md +1 -1
- package/presets/opencode/skills/auth-token-inspector/SKILL.md +1 -1
- package/presets/opencode/skills/code-review/SKILL.md +1 -1
- package/presets/opencode/skills/django-orm/SKILL.md +3 -3
- package/presets/opencode/skills/django-testing/SKILL.md +1 -1
- package/presets/opencode/skills/django-uv/SKILL.md +1 -1
- package/presets/opencode/skills/drizzle-schema-architect/SKILL.md +1 -1
- package/presets/opencode/skills/fastapi-pydantic-strict/SKILL.md +1 -1
- package/presets/opencode/skills/jpa-nplusone-detector/SKILL.md +1 -1
- package/presets/opencode/skills/jpa-postgres/SKILL.md +2 -1
- package/presets/opencode/skills/livewire-alpine-bridge/SKILL.md +1 -1
- package/presets/opencode/skills/nextjs-typescript/SKILL.md +1 -1
- package/presets/opencode/skills/pagespeed-perf/SKILL.md +1 -1
- package/presets/opencode/skills/python/SKILL.md +6 -5
- package/presets/opencode/skills/python-django-stack/SKILL.md +1 -1
- package/presets/opencode/skills/python-fastapi-stack/SKILL.md +1 -1
- package/presets/opencode/skills/security/SKILL.md +1 -1
- package/presets/opencode/skills/seo-analytics-injector/SKILL.md +1 -1
- package/presets/opencode/skills/spring-auth-auditor/SKILL.md +1 -1
- package/presets/opencode/skills/spring-boot-feature/SKILL.md +2 -1
- package/presets/opencode/skills/spring-boot-kotlin/SKILL.md +1 -1
- package/presets/opencode/skills/spring-boot-testing-strategy/SKILL.md +1 -1
- package/presets/opencode/skills/sqlalchemy/SKILL.md +1 -1
- package/presets/opencode/skills/tailwind-responsive-auditor/SKILL.md +1 -1
- package/presets/opencode/skills/tdd-mutation-tester/SKILL.md +1 -1
- package/presets/pi/AGENTS.md +6 -0
- package/presets/seo-hotel/skills/astro-seo/SKILL.md +1 -1
- package/presets/seo-hotel/skills/geo-readiness/SKILL.md +1 -1
- package/presets/seo-hotel/skills/off-page/SKILL.md +1 -1
- package/presets/seo-hotel/skills/schema-validator/SKILL.md +1 -1
- package/presets/seo-hotel/skills/seo-audit/SKILL.md +1 -1
- package/presets/shared/invoke-hook.cjs +20 -5
- package/src/presets/models/roles.yml +28 -28
- package/src/presets/shared-skills.yml +58 -22
- package/src/presets/targets/pi.yml +1 -0
package/docs/generated/cli.md
CHANGED
|
@@ -24,6 +24,14 @@ Initialize low-level CodeConductor configuration.
|
|
|
24
24
|
cc-codeconductor init [--locale en|es]
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
## usage
|
|
28
|
+
|
|
29
|
+
Show installation examples for presets, council, and LSP.
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
cc-codeconductor usage
|
|
33
|
+
```
|
|
34
|
+
|
|
27
35
|
## install
|
|
28
36
|
|
|
29
37
|
Install harness components.
|
|
@@ -184,6 +192,18 @@ Verify task completion with evidence.
|
|
|
184
192
|
cc-codeconductor verify --task <id>
|
|
185
193
|
```
|
|
186
194
|
|
|
195
|
+
## rdd
|
|
196
|
+
|
|
197
|
+
Capture and validate Receipt-Driven Development evidence.
|
|
198
|
+
|
|
199
|
+
```text
|
|
200
|
+
cc-codeconductor rdd capture --task <id> [--phase red|green|review]
|
|
201
|
+
cc-codeconductor rdd verify --receipt <id>
|
|
202
|
+
cc-codeconductor rdd status [--task <id>]
|
|
203
|
+
cc-codeconductor rdd git-check
|
|
204
|
+
cc-codeconductor rdd install-hooks
|
|
205
|
+
```
|
|
206
|
+
|
|
187
207
|
## seo
|
|
188
208
|
|
|
189
209
|
Audit SEO and generate llms.txt.
|
package/package.json
CHANGED
package/presets/agy/AGENTS.md
CHANGED
|
@@ -472,4 +472,10 @@ When the orchestrator receives a GoalGraph, it delegates tasks in dependency ord
|
|
|
472
472
|
### Monorepo Workspaces
|
|
473
473
|
- Focus operations strictly within the specified sub-package or workspace directory in the Task Card scope. Do not modify files or run commands outside this package directory.
|
|
474
474
|
|
|
475
|
+
## Receipt integrity
|
|
476
|
+
|
|
477
|
+
- For any implementation, test, review, handoff, or delivery decision, capture or verify the current RDD receipt with `bun run dev rdd`.
|
|
478
|
+
- A receipt is valid only for its exact candidate. If code, tests, contracts, or runner configuration changed, repeat the affected verification.
|
|
479
|
+
- TDD and Mutation Testing retain their existing gates; RDD verifies that their observed evidence still belongs to the current candidate.
|
|
480
|
+
|
|
475
481
|
<!-- CODECONDUCTOR:END managed -->
|
package/presets/agy/README.md
CHANGED
|
@@ -39,7 +39,7 @@ To configure permissions, models, and execution modes for the Antigravity CLI, u
|
|
|
39
39
|
Recommended settings:
|
|
40
40
|
```json
|
|
41
41
|
{
|
|
42
|
-
"model": "gemini-3.
|
|
42
|
+
"model": "gemini-3.8-flash-medium",
|
|
43
43
|
"toolPermission": "request-review",
|
|
44
44
|
"enableTerminalSandbox": true,
|
|
45
45
|
"allowNonWorkspaceAccess": false
|
package/presets/agy/hooks.json
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"enabled": true,
|
|
18
18
|
"PreToolUse": [
|
|
19
19
|
{
|
|
20
|
-
"matcher": "run_command|write_to_file|replace_file_content|multi_replace_file_content",
|
|
20
|
+
"matcher": "run_command|view_file|write_to_file|replace_file_content|multi_replace_file_content",
|
|
21
21
|
"hooks": [
|
|
22
22
|
{
|
|
23
23
|
"type": "command",
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
const { spawnSync } = require('node:child_process');
|
|
5
|
-
const { existsSync } = require('node:fs');
|
|
6
|
-
const { join, resolve } = require('node:path');
|
|
5
|
+
const { existsSync, readFileSync, realpathSync } = require('node:fs');
|
|
6
|
+
const { delimiter, join, resolve } = require('node:path');
|
|
7
7
|
|
|
8
8
|
const VALID_EVENTS = ['pre-tool', 'post-tool', 'session-start'];
|
|
9
9
|
const SPAWN_TIMEOUT = 10000;
|
|
@@ -21,9 +21,10 @@ const isAgy = process.argv.some((a) => a === '--format=agy') || extra.includes('
|
|
|
21
21
|
function findProjectRoot() {
|
|
22
22
|
const candidates = [
|
|
23
23
|
process.env.PROJECT_ROOT,
|
|
24
|
+
process.env.CLAUDE_PROJECT_DIR,
|
|
24
25
|
process.env.WORKSPACE_DIR,
|
|
25
|
-
resolve(__dirname, '..', '..'),
|
|
26
26
|
process.cwd(),
|
|
27
|
+
resolve(__dirname, '..', '..'),
|
|
27
28
|
].filter(Boolean);
|
|
28
29
|
|
|
29
30
|
for (const dir of candidates) {
|
|
@@ -40,6 +41,8 @@ function findProjectRoot() {
|
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
const projectRoot = findProjectRoot();
|
|
44
|
+
// Buffer once: each fallback attempt must receive the same host payload.
|
|
45
|
+
const input = process.stdin.isTTY ? '' : readFileSync(0, 'utf8');
|
|
43
46
|
|
|
44
47
|
/**
|
|
45
48
|
* Run one candidate runner. Stdout/stderr are captured (not inherited) so a
|
|
@@ -54,7 +57,8 @@ function tryRun(bin, runArgs) {
|
|
|
54
57
|
try {
|
|
55
58
|
const result = spawnSync(bin, runArgs, {
|
|
56
59
|
cwd: projectRoot,
|
|
57
|
-
stdio: ['
|
|
60
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
61
|
+
input,
|
|
58
62
|
windowsHide: true,
|
|
59
63
|
env: process.env,
|
|
60
64
|
encoding: 'utf8',
|
|
@@ -111,7 +115,18 @@ try {
|
|
|
111
115
|
tryRun(process.execPath, [localDist, 'hook', event, ...extra]);
|
|
112
116
|
}
|
|
113
117
|
|
|
114
|
-
|
|
118
|
+
// Locate global npm installs without invoking npx or Windows .cmd shims.
|
|
119
|
+
for (const binDir of (process.env.PATH || '').split(delimiter).filter(Boolean)) {
|
|
120
|
+
const candidates = [
|
|
121
|
+
join(binDir, 'node_modules', 'cc-codeconductor', 'dist', 'index.js'),
|
|
122
|
+
join(binDir, '..', 'lib', 'node_modules', 'cc-codeconductor', 'dist', 'index.js'),
|
|
123
|
+
];
|
|
124
|
+
const executable = join(binDir, 'cc-codeconductor');
|
|
125
|
+
if (existsSync(executable)) candidates.push(realpathSync(executable));
|
|
126
|
+
for (const candidate of candidates) {
|
|
127
|
+
if (existsSync(candidate)) tryRun(process.execPath, [candidate, 'hook', event, ...extra]);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
115
130
|
} catch {
|
|
116
131
|
// Ignore errors in runner attempts
|
|
117
132
|
}
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: api-versioning
|
|
3
|
+
name: api-versioning
|
|
4
|
+
description: >
|
|
5
|
+
Provides expert knowledge for designing, implementing, and managing REST API
|
|
6
|
+
versioning strategies with deprecation workflows.
|
|
7
|
+
|
|
8
|
+
user-invokable: true
|
|
9
|
+
license: MIT
|
|
10
|
+
metadata:
|
|
11
|
+
author: lgzarturo
|
|
12
|
+
category: api
|
|
13
|
+
|
|
14
|
+
compatibility:
|
|
15
|
+
tools: [claude, codex, gemini, agy, opencode]
|
|
16
|
+
stacks:
|
|
17
|
+
languages: [kotlin, java, typescript, python, go]
|
|
18
|
+
frameworks: [spring-boot, spring-mvc, express, fastapi]
|
|
19
|
+
|
|
20
|
+
risk:
|
|
21
|
+
level: high
|
|
22
|
+
can_execute_shell: false
|
|
23
|
+
can_modify_files: true
|
|
24
|
+
requires_network: false
|
|
25
|
+
|
|
26
|
+
inputs:
|
|
27
|
+
- source_files
|
|
28
|
+
- openapi spec files
|
|
29
|
+
- existing controller classes
|
|
30
|
+
|
|
31
|
+
outputs:
|
|
32
|
+
- versioned controller classes
|
|
33
|
+
- OpenAPI spec updates
|
|
34
|
+
- deprecation headers
|
|
35
|
+
- changelog entries
|
|
36
|
+
- contract test scaffolding
|
|
37
|
+
|
|
38
|
+
quality:
|
|
39
|
+
reviewed_by: codeconductor-core
|
|
40
|
+
version: 0.1.0
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
# API Versioning
|
|
44
|
+
|
|
45
|
+
## Versioning Strategies
|
|
46
|
+
|
|
47
|
+
### URL Path Versioning (recommended for breaking changes)
|
|
48
|
+
|
|
49
|
+
```text
|
|
50
|
+
GET /api/v1/users
|
|
51
|
+
GET /api/v2/users
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Tradeoffs:
|
|
55
|
+
|
|
56
|
+
- Explicit and visible in logs, proxies, and browser history
|
|
57
|
+
- Easy to cache at the CDN level — the URL uniquely identifies the resource
|
|
58
|
+
version
|
|
59
|
+
- Easy to route at the load balancer
|
|
60
|
+
- Results in some duplication of controller code
|
|
61
|
+
- Changing the URL violates REST HATEOAS principles, though in practice this is
|
|
62
|
+
acceptable
|
|
63
|
+
|
|
64
|
+
Use this when: you have breaking changes and need maximum visibility and
|
|
65
|
+
cacheability.
|
|
66
|
+
|
|
67
|
+
### Header Versioning
|
|
68
|
+
|
|
69
|
+
```text
|
|
70
|
+
GET /api/users
|
|
71
|
+
Accept: application/vnd.myapp+json;version=1
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Tradeoffs:
|
|
75
|
+
|
|
76
|
+
- Cleaner URLs
|
|
77
|
+
- Harder to test manually — browsers and curl require extra flags
|
|
78
|
+
- Cannot be bookmarked or linked directly
|
|
79
|
+
- CDN caching requires `Vary: Accept` header, which reduces cache hit rates
|
|
80
|
+
|
|
81
|
+
Use this when: you need clean URLs and your clients are all programmatic (no
|
|
82
|
+
browsers).
|
|
83
|
+
|
|
84
|
+
### Query Parameter Versioning (avoid)
|
|
85
|
+
|
|
86
|
+
```text
|
|
87
|
+
GET /api/users?version=1
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
This approach contaminates resource URLs with transport concerns. The version is
|
|
91
|
+
not part of the resource identity. Do not use it. The only valid exception is
|
|
92
|
+
temporary backward-compat support during a migration window.
|
|
93
|
+
|
|
94
|
+
## When to Version
|
|
95
|
+
|
|
96
|
+
Version when the change is breaking. Not every change requires a version bump.
|
|
97
|
+
|
|
98
|
+
**Breaking — requires new version:**
|
|
99
|
+
|
|
100
|
+
- Removing a field from a response
|
|
101
|
+
- Renaming a field
|
|
102
|
+
- Changing a field's type (e.g., `string` to `object`)
|
|
103
|
+
- Changing the meaning of an existing field
|
|
104
|
+
- Removing an endpoint
|
|
105
|
+
- Changing required fields in a request
|
|
106
|
+
- Changing status codes in a non-additive way
|
|
107
|
+
|
|
108
|
+
**Not breaking — no version bump needed:**
|
|
109
|
+
|
|
110
|
+
- Adding an optional field to a response
|
|
111
|
+
- Adding a new endpoint
|
|
112
|
+
- Adding an optional request parameter
|
|
113
|
+
- Deprecating a field (marking it, but still returning it)
|
|
114
|
+
- Performance improvements
|
|
115
|
+
- Bug fixes that restore documented behavior
|
|
116
|
+
|
|
117
|
+
## Deprecation Process
|
|
118
|
+
|
|
119
|
+
When a version or endpoint is being phased out, follow this process:
|
|
120
|
+
|
|
121
|
+
**Step 1: Mark in OpenAPI.**
|
|
122
|
+
|
|
123
|
+
```yaml
|
|
124
|
+
paths:
|
|
125
|
+
/api/v1/users/{id}:
|
|
126
|
+
get:
|
|
127
|
+
deprecated: true
|
|
128
|
+
description: |
|
|
129
|
+
Deprecated since 2026-05-07. Use /api/v2/users/{id} instead.
|
|
130
|
+
Sunset date: 2026-11-07.
|
|
131
|
+
summary: Get user by ID (deprecated)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Step 2: Add deprecation headers to responses.**
|
|
135
|
+
|
|
136
|
+
```kotlin
|
|
137
|
+
@GetMapping("/{id}")
|
|
138
|
+
fun getUserV1(@PathVariable id: UUID, response: HttpServletResponse): ResponseEntity<UserV1Response> {
|
|
139
|
+
response.addHeader("Deprecation", "date=\"Wed, 07 May 2026 00:00:00 GMT\"")
|
|
140
|
+
response.addHeader("Sunset", "Mon, 07 Nov 2026 00:00:00 GMT")
|
|
141
|
+
response.addHeader("Link", "</api/v2/users/$id>; rel=\"successor-version\"")
|
|
142
|
+
return ResponseEntity.ok(userService.getById(id).toV1Response())
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Step 3: Document in CHANGELOG.**
|
|
147
|
+
|
|
148
|
+
```markdown
|
|
149
|
+
## Deprecated
|
|
150
|
+
|
|
151
|
+
- `GET /api/v1/users/{id}` — deprecated in favor of `GET /api/v2/users/{id}`.
|
|
152
|
+
Sunset: 2026-11-07.
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**Step 4: Maintain dual support.**
|
|
156
|
+
|
|
157
|
+
Keep at least two active major versions at all times. When v3 ships, v1 can be
|
|
158
|
+
removed (v2 and v3 remain active).
|
|
159
|
+
|
|
160
|
+
**Step 5: Communicate the sunset date.**
|
|
161
|
+
|
|
162
|
+
Notify consumers before the sunset date through:
|
|
163
|
+
|
|
164
|
+
- API changelog
|
|
165
|
+
- Developer portal announcements
|
|
166
|
+
- Deprecation headers (machine-readable)
|
|
167
|
+
- Direct contact if you have consumer registration data
|
|
168
|
+
|
|
169
|
+
Do not remove a version without a minimum 6-month notice period. 3 months is the
|
|
170
|
+
absolute minimum if forced.
|
|
171
|
+
|
|
172
|
+
## OpenAPI Conventions
|
|
173
|
+
|
|
174
|
+
### One file per version (simple cases)
|
|
175
|
+
|
|
176
|
+
```text
|
|
177
|
+
openapi-v1.yaml
|
|
178
|
+
openapi-v2.yaml
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Each file is self-contained and independently valid.
|
|
182
|
+
|
|
183
|
+
### Single file with version in info (evolving APIs)
|
|
184
|
+
|
|
185
|
+
```yaml
|
|
186
|
+
openapi: '3.1.0'
|
|
187
|
+
info:
|
|
188
|
+
title: Users API
|
|
189
|
+
version: '2.0.0'
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Use `$ref` to share schemas across versions without duplication.
|
|
193
|
+
|
|
194
|
+
### Cross-version schema reuse
|
|
195
|
+
|
|
196
|
+
```yaml
|
|
197
|
+
# schemas/user-base.yaml
|
|
198
|
+
UserBase:
|
|
199
|
+
type: object
|
|
200
|
+
properties:
|
|
201
|
+
id:
|
|
202
|
+
type: string
|
|
203
|
+
format: uuid
|
|
204
|
+
email:
|
|
205
|
+
type: string
|
|
206
|
+
|
|
207
|
+
# openapi-v1.yaml
|
|
208
|
+
components:
|
|
209
|
+
schemas:
|
|
210
|
+
UserResponse:
|
|
211
|
+
allOf:
|
|
212
|
+
- $ref: './schemas/user-base.yaml#/UserBase'
|
|
213
|
+
- properties:
|
|
214
|
+
full_name:
|
|
215
|
+
type: string
|
|
216
|
+
|
|
217
|
+
# openapi-v2.yaml — splits full_name into first_name + last_name
|
|
218
|
+
components:
|
|
219
|
+
schemas:
|
|
220
|
+
UserResponse:
|
|
221
|
+
allOf:
|
|
222
|
+
- $ref: './schemas/user-base.yaml#/UserBase'
|
|
223
|
+
- properties:
|
|
224
|
+
first_name:
|
|
225
|
+
type: string
|
|
226
|
+
last_name:
|
|
227
|
+
type: string
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Documenting breaking changes
|
|
231
|
+
|
|
232
|
+
Put the breaking change in the endpoint description, not just in a changelog:
|
|
233
|
+
|
|
234
|
+
```yaml
|
|
235
|
+
/api/v2/users/{id}:
|
|
236
|
+
get:
|
|
237
|
+
description: |
|
|
238
|
+
Returns user details.
|
|
239
|
+
|
|
240
|
+
Breaking changes from v1:
|
|
241
|
+
- `full_name` has been replaced by `first_name` and `last_name`
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## Spring Boot Implementation
|
|
245
|
+
|
|
246
|
+
### URL path versioning
|
|
247
|
+
|
|
248
|
+
```kotlin
|
|
249
|
+
// V1 controller — never modify once published
|
|
250
|
+
@RestController
|
|
251
|
+
@RequestMapping("/api/v1/users")
|
|
252
|
+
class UserV1Controller(private val userService: UserService) {
|
|
253
|
+
|
|
254
|
+
@GetMapping("/{id}")
|
|
255
|
+
@Deprecated("Use /api/v2/users/{id}", ReplaceWith("UserV2Controller.getUser()"))
|
|
256
|
+
fun getUser(
|
|
257
|
+
@PathVariable id: UUID,
|
|
258
|
+
response: HttpServletResponse
|
|
259
|
+
): ResponseEntity<UserV1Response> {
|
|
260
|
+
response.addHeader("Deprecation", "date=\"Wed, 07 May 2026 00:00:00 GMT\"")
|
|
261
|
+
response.addHeader("Sunset", "Mon, 07 Nov 2026 00:00:00 GMT")
|
|
262
|
+
return when (val result = userService.getById(id)) {
|
|
263
|
+
is UserResult.Found -> ResponseEntity.ok(result.user.toV1Response())
|
|
264
|
+
is UserResult.NotFound -> ResponseEntity.notFound().build()
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// V2 controller — new version, new controller, shared service
|
|
270
|
+
@RestController
|
|
271
|
+
@RequestMapping("/api/v2/users")
|
|
272
|
+
class UserV2Controller(private val userService: UserService) {
|
|
273
|
+
|
|
274
|
+
@GetMapping("/{id}")
|
|
275
|
+
fun getUser(@PathVariable id: UUID): ResponseEntity<UserV2Response> {
|
|
276
|
+
return when (val result = userService.getById(id)) {
|
|
277
|
+
is UserResult.Found -> ResponseEntity.ok(result.user.toV2Response())
|
|
278
|
+
is UserResult.NotFound -> ResponseEntity.notFound().build()
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Rules:
|
|
285
|
+
|
|
286
|
+
- Create a new controller for each new version — do not modify the existing one
|
|
287
|
+
- The service layer is shared across versions — only the controller and DTO
|
|
288
|
+
change
|
|
289
|
+
- DTO mapper functions are version-specific: `toV1Response()`, `toV2Response()`
|
|
290
|
+
- Never delete a versioned controller until after the sunset date
|
|
291
|
+
|
|
292
|
+
### DTO versioning
|
|
293
|
+
|
|
294
|
+
```kotlin
|
|
295
|
+
// V1 — original shape
|
|
296
|
+
data class UserV1Response(
|
|
297
|
+
val id: UUID,
|
|
298
|
+
val email: String,
|
|
299
|
+
val full_name: String
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
// V2 — breaking change: split full_name
|
|
303
|
+
data class UserV2Response(
|
|
304
|
+
val id: UUID,
|
|
305
|
+
val email: String,
|
|
306
|
+
val first_name: String,
|
|
307
|
+
val last_name: String
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
// Extension functions for mapping
|
|
311
|
+
fun User.toV1Response(): UserV1Response = UserV1Response(
|
|
312
|
+
id = id,
|
|
313
|
+
email = email,
|
|
314
|
+
full_name = "$firstName $lastName"
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
fun User.toV2Response(): UserV2Response = UserV2Response(
|
|
318
|
+
id = id,
|
|
319
|
+
email = email,
|
|
320
|
+
first_name = firstName,
|
|
321
|
+
last_name = lastName
|
|
322
|
+
)
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
## Contract Testing
|
|
326
|
+
|
|
327
|
+
Contract tests verify that your API does not break existing consumers before
|
|
328
|
+
changes reach production.
|
|
329
|
+
|
|
330
|
+
**When to run:** in CI, before merging any change that touches a controller,
|
|
331
|
+
DTO, or OpenAPI spec.
|
|
332
|
+
|
|
333
|
+
**Tool: Pact (consumer-driven contracts)**
|
|
334
|
+
|
|
335
|
+
Consumer writes a pact:
|
|
336
|
+
|
|
337
|
+
```kotlin
|
|
338
|
+
// In the consumer service test
|
|
339
|
+
@ExtendWith(PactConsumerTestExt::class)
|
|
340
|
+
class UserServiceConsumerTest {
|
|
341
|
+
|
|
342
|
+
@Pact(consumer = "order-service", provider = "user-service")
|
|
343
|
+
fun getUserPact(builder: PactDslWithProvider): RequestResponsePact {
|
|
344
|
+
return builder
|
|
345
|
+
.given("user with id exists")
|
|
346
|
+
.uponReceiving("a request for user by id")
|
|
347
|
+
.path("/api/v1/users/123e4567-e89b-12d3-a456-426614174000")
|
|
348
|
+
.method("GET")
|
|
349
|
+
.willRespondWith()
|
|
350
|
+
.status(200)
|
|
351
|
+
.body(LambdaDsl.newJsonBody { body ->
|
|
352
|
+
body.uuid("id")
|
|
353
|
+
body.stringType("email")
|
|
354
|
+
body.stringType("full_name")
|
|
355
|
+
}.build())
|
|
356
|
+
.toPact()
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
Provider verifies the pact:
|
|
362
|
+
|
|
363
|
+
```kotlin
|
|
364
|
+
@Provider("user-service")
|
|
365
|
+
@PactFolder("pacts")
|
|
366
|
+
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
|
367
|
+
class UserServiceProviderTest {
|
|
368
|
+
|
|
369
|
+
@TestTarget
|
|
370
|
+
lateinit var target: HttpTestTarget
|
|
371
|
+
|
|
372
|
+
@BeforeEach
|
|
373
|
+
fun setUp(@LocalServerPort port: Int) {
|
|
374
|
+
target = HttpTestTarget("localhost", port)
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
**Rule:** run contract tests in CI before any merge that touches an API surface.
|
|
380
|
+
A broken contract test means a consumer will break in production.
|
|
381
|
+
|
|
382
|
+
## Test Structure Per Version
|
|
383
|
+
|
|
384
|
+
Each API version must have its own test class:
|
|
385
|
+
|
|
386
|
+
```text
|
|
387
|
+
src/test/kotlin/{package}/user/
|
|
388
|
+
controller/
|
|
389
|
+
UserV1ControllerTest.kt # tests for v1 endpoints
|
|
390
|
+
UserV2ControllerTest.kt # tests for v2 endpoints
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
Do not share test cases across versions. V1 behavior must be tested
|
|
394
|
+
independently from V2 — they can diverge.
|