@grainulation/silo 1.0.0 → 1.0.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/lib/templates.js CHANGED
@@ -5,9 +5,9 @@
5
5
  * A template contains: question, audience, constraints, and starter claims.
6
6
  */
7
7
 
8
- const fs = require('node:fs');
9
- const path = require('node:path');
10
- const { Store } = require('./store.js');
8
+ const fs = require("node:fs");
9
+ const path = require("node:path");
10
+ const { Store } = require("./store.js");
11
11
 
12
12
  class Templates {
13
13
  constructor(store) {
@@ -35,14 +35,14 @@ class Templates {
35
35
  savedAt: new Date().toISOString(),
36
36
  };
37
37
  const filePath = path.join(this.store.templatesDir, `${id}.json`);
38
- const tmp1 = filePath + '.tmp.' + process.pid;
39
- fs.writeFileSync(tmp1, JSON.stringify(entry, null, 2) + '\n', 'utf-8');
38
+ const tmp1 = filePath + ".tmp." + process.pid;
39
+ fs.writeFileSync(tmp1, JSON.stringify(entry, null, 2) + "\n", "utf-8");
40
40
  fs.renameSync(tmp1, filePath);
41
41
 
42
42
  this.store._addToIndex({
43
43
  id,
44
44
  name,
45
- type: 'template',
45
+ type: "template",
46
46
  claimCount: (template.seedClaims || []).length,
47
47
  storedAt: entry.savedAt,
48
48
  });
@@ -55,7 +55,7 @@ class Templates {
55
55
  const id = this._slugify(nameOrId);
56
56
  const filePath = path.join(this.store.templatesDir, `${id}.json`);
57
57
  if (!fs.existsSync(filePath)) return null;
58
- return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
58
+ return JSON.parse(fs.readFileSync(filePath, "utf-8"));
59
59
  }
60
60
 
61
61
  /** List all saved templates. */
@@ -66,10 +66,10 @@ class Templates {
66
66
 
67
67
  return fs
68
68
  .readdirSync(dir)
69
- .filter((f) => f.endsWith('.json'))
69
+ .filter((f) => f.endsWith(".json"))
70
70
  .map((f) => {
71
71
  try {
72
- const data = JSON.parse(fs.readFileSync(path.join(dir, f), 'utf-8'));
72
+ const data = JSON.parse(fs.readFileSync(path.join(dir, f), "utf-8"));
73
73
  return {
74
74
  id: data.id,
75
75
  name: data.name,
@@ -102,17 +102,21 @@ class Templates {
102
102
  }
103
103
 
104
104
  // Write seed claims
105
- const claimsPath = path.join(targetDir, 'claims.json');
105
+ const claimsPath = path.join(targetDir, "claims.json");
106
106
  const claims = (template.seedClaims || []).map((c, i) => ({
107
107
  ...c,
108
- id: c.id || `d${String(i + 1).padStart(3, '0')}`,
108
+ id: c.id || `d${String(i + 1).padStart(3, "0")}`,
109
109
  }));
110
- const tmpClaims = claimsPath + '.tmp.' + process.pid;
111
- fs.writeFileSync(tmpClaims, JSON.stringify(claims, null, 2) + '\n', 'utf-8');
110
+ const tmpClaims = claimsPath + ".tmp." + process.pid;
111
+ fs.writeFileSync(
112
+ tmpClaims,
113
+ JSON.stringify(claims, null, 2) + "\n",
114
+ "utf-8",
115
+ );
112
116
  fs.renameSync(tmpClaims, claimsPath);
113
117
 
114
118
  // Write sprint config stub
115
- const configPath = path.join(targetDir, 'sprint.json');
119
+ const configPath = path.join(targetDir, "sprint.json");
116
120
  const config = {
117
121
  question: template.question,
118
122
  audience: template.audience,
@@ -120,8 +124,12 @@ class Templates {
120
124
  fromTemplate: template.id,
121
125
  createdAt: new Date().toISOString(),
122
126
  };
123
- const tmpConfig = configPath + '.tmp.' + process.pid;
124
- fs.writeFileSync(tmpConfig, JSON.stringify(config, null, 2) + '\n', 'utf-8');
127
+ const tmpConfig = configPath + ".tmp." + process.pid;
128
+ fs.writeFileSync(
129
+ tmpConfig,
130
+ JSON.stringify(config, null, 2) + "\n",
131
+ "utf-8",
132
+ );
125
133
  fs.renameSync(tmpConfig, configPath);
126
134
 
127
135
  return {
@@ -132,7 +140,10 @@ class Templates {
132
140
  }
133
141
 
134
142
  _slugify(str) {
135
- return str.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
143
+ return str
144
+ .toLowerCase()
145
+ .replace(/[^a-z0-9]+/g, "-")
146
+ .replace(/^-|-$/g, "");
136
147
  }
137
148
  }
138
149
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grainulation/silo",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Reusable knowledge for research sprints -- shared claim libraries, templates, and knowledge packs",
5
5
  "main": "lib/index.js",
6
6
  "exports": {
@@ -27,12 +27,13 @@
27
27
  ],
28
28
  "author": "grainulation contributors",
29
29
  "license": "MIT",
30
+ "type": "module",
30
31
  "repository": {
31
32
  "type": "git",
32
33
  "url": "git+https://github.com/grainulation/silo.git"
33
34
  },
34
35
  "engines": {
35
- "node": ">=18.0.0"
36
+ "node": ">=20"
36
37
  },
37
38
  "bugs": {
38
39
  "url": "https://github.com/grainulation/silo/issues"
@@ -43,6 +44,9 @@
43
44
  "lib/",
44
45
  "packs/",
45
46
  "public/",
46
- "CHANGELOG.md"
47
+ "CHANGELOG.md",
48
+ "LICENSE",
49
+ "CODE_OF_CONDUCT.md",
50
+ "CONTRIBUTING.md"
47
51
  ]
48
52
  }
package/packs/adr.json ADDED
@@ -0,0 +1,219 @@
1
+ {
2
+ "name": "Architecture Decision Records",
3
+ "description": "Enterprise ADR framework with evidence tiers. Structured constraints and risk claims for common architecture decisions — use as seed claims when evaluating build-vs-buy, migration, scaling, or technology choices.",
4
+ "version": "1.0.0",
5
+ "claims": [
6
+ {
7
+ "id": "adr-001",
8
+ "type": "constraint",
9
+ "topic": "ADR decision structure",
10
+ "content": "Every ADR must state: (1) Decision context — what problem are we solving? (2) Decision drivers — what constraints and goals matter most? (3) Options considered — at least 2 alternatives with evidence. (4) Decision outcome — which option and why. (5) Consequences — accepted trade-offs.",
11
+ "source": {
12
+ "origin": "best-practice",
13
+ "artifact": "https://adr.github.io",
14
+ "connector": null
15
+ },
16
+ "evidence": "documented",
17
+ "status": "active",
18
+ "phase_added": "define",
19
+ "timestamp": "2026-01-01T00:00:00.000Z",
20
+ "conflicts_with": [],
21
+ "resolved_by": null,
22
+ "tags": ["adr", "structure", "decision-record", "enterprise"]
23
+ },
24
+ {
25
+ "id": "adr-002",
26
+ "type": "constraint",
27
+ "topic": "evidence-backed decisions",
28
+ "content": "Architecture decisions must be backed by evidence, not opinions. Each option should have claims at 'documented' tier or higher. 'We chose X because the team prefers it' is not an ADR — 'We chose X because benchmarks show 3x throughput under our load profile' is.",
29
+ "source": {
30
+ "origin": "best-practice",
31
+ "artifact": null,
32
+ "connector": null
33
+ },
34
+ "evidence": "documented",
35
+ "status": "active",
36
+ "phase_added": "define",
37
+ "timestamp": "2026-01-01T00:00:00.000Z",
38
+ "conflicts_with": [],
39
+ "resolved_by": null,
40
+ "tags": ["adr", "evidence", "decision-quality", "enterprise"]
41
+ },
42
+ {
43
+ "id": "adr-003",
44
+ "type": "constraint",
45
+ "topic": "reversibility assessment",
46
+ "content": "Every architecture decision must classify its reversibility: Type 1 (irreversible — database engine, programming language, cloud provider) requires heavyweight evidence. Type 2 (reversible — framework, library, API design) can proceed with lighter evidence.",
47
+ "source": {
48
+ "origin": "best-practice",
49
+ "artifact": null,
50
+ "connector": null
51
+ },
52
+ "evidence": "documented",
53
+ "status": "active",
54
+ "phase_added": "define",
55
+ "timestamp": "2026-01-01T00:00:00.000Z",
56
+ "conflicts_with": [],
57
+ "resolved_by": null,
58
+ "tags": ["adr", "reversibility", "type-1-type-2", "risk"]
59
+ },
60
+ {
61
+ "id": "adr-004",
62
+ "type": "risk",
63
+ "topic": "ADR decay",
64
+ "content": "ADRs that are not linked to code become stale and misleading. Risk: team makes decisions contradicting existing ADRs because they can't find them. Mitigation: store ADRs alongside code (docs/adr/), reference in relevant source files, review annually.",
65
+ "source": {
66
+ "origin": "best-practice",
67
+ "artifact": null,
68
+ "connector": null
69
+ },
70
+ "evidence": "documented",
71
+ "status": "active",
72
+ "phase_added": "define",
73
+ "timestamp": "2026-01-01T00:00:00.000Z",
74
+ "conflicts_with": [],
75
+ "resolved_by": null,
76
+ "tags": ["adr", "decay", "maintenance", "risk"]
77
+ },
78
+ {
79
+ "id": "adr-005",
80
+ "type": "recommendation",
81
+ "topic": "wheat-powered ADR workflow",
82
+ "content": "Use wheat sprint as ADR research phase: (1) Define decision question as sprint question. (2) Add constraints from stakeholders. (3) Research options — each generates factual claims with evidence tiers. (4) Challenge phase creates conflict claims between options. (5) Compile — the brief IS the ADR.",
83
+ "source": {
84
+ "origin": "best-practice",
85
+ "artifact": null,
86
+ "connector": null
87
+ },
88
+ "evidence": "stated",
89
+ "status": "active",
90
+ "phase_added": "define",
91
+ "timestamp": "2026-01-01T00:00:00.000Z",
92
+ "conflicts_with": [],
93
+ "resolved_by": null,
94
+ "tags": ["adr", "wheat", "workflow", "recommendation"]
95
+ },
96
+ {
97
+ "id": "adr-006",
98
+ "type": "factual",
99
+ "topic": "build vs buy framework",
100
+ "content": "Build-vs-buy evaluation dimensions: (1) Total cost of ownership over 3 years (build: salaries + infra; buy: license + integration). (2) Time to value (build: months; buy: weeks). (3) Customization requirements. (4) Strategic differentiation — is this your core competency? (5) Maintenance burden.",
101
+ "source": {
102
+ "origin": "best-practice",
103
+ "artifact": null,
104
+ "connector": null
105
+ },
106
+ "evidence": "documented",
107
+ "status": "active",
108
+ "phase_added": "define",
109
+ "timestamp": "2026-01-01T00:00:00.000Z",
110
+ "conflicts_with": [],
111
+ "resolved_by": null,
112
+ "tags": ["adr", "build-vs-buy", "framework", "tco"]
113
+ },
114
+ {
115
+ "id": "adr-007",
116
+ "type": "factual",
117
+ "topic": "migration decision framework",
118
+ "content": "Migration decision framework: (1) Is the current system at a hard limit (scale, compliance, EOL)? (2) What's the migration risk (data loss, downtime, rollback complexity)? (3) Can you migrate incrementally (strangler fig) or must it be big-bang? (4) What's the team's experience with the target?",
119
+ "source": {
120
+ "origin": "best-practice",
121
+ "artifact": null,
122
+ "connector": null
123
+ },
124
+ "evidence": "documented",
125
+ "status": "active",
126
+ "phase_added": "define",
127
+ "timestamp": "2026-01-01T00:00:00.000Z",
128
+ "conflicts_with": [],
129
+ "resolved_by": null,
130
+ "tags": ["adr", "migration", "framework", "risk-assessment"]
131
+ },
132
+ {
133
+ "id": "adr-008",
134
+ "type": "risk",
135
+ "topic": "consensus-driven decisions",
136
+ "content": "Risk: architecture by committee produces mediocre compromises. ADRs should identify a single decision owner (usually the tech lead or architect) who makes the call after reviewing evidence. The ADR documents the rationale for future teams.",
137
+ "source": {
138
+ "origin": "best-practice",
139
+ "artifact": null,
140
+ "connector": null
141
+ },
142
+ "evidence": "documented",
143
+ "status": "active",
144
+ "phase_added": "define",
145
+ "timestamp": "2026-01-01T00:00:00.000Z",
146
+ "conflicts_with": [],
147
+ "resolved_by": null,
148
+ "tags": ["adr", "decision-owner", "governance", "risk"]
149
+ },
150
+ {
151
+ "id": "adr-009",
152
+ "type": "constraint",
153
+ "topic": "ADR status lifecycle",
154
+ "content": "ADR statuses: Proposed (under research), Accepted (decision made), Deprecated (superseded by newer ADR), Superseded (link to replacement). Never delete ADRs — they document why past decisions were made, even if reversed.",
155
+ "source": {
156
+ "origin": "best-practice",
157
+ "artifact": "https://adr.github.io",
158
+ "connector": null
159
+ },
160
+ "evidence": "documented",
161
+ "status": "active",
162
+ "phase_added": "define",
163
+ "timestamp": "2026-01-01T00:00:00.000Z",
164
+ "conflicts_with": [],
165
+ "resolved_by": null,
166
+ "tags": ["adr", "lifecycle", "status", "governance"]
167
+ },
168
+ {
169
+ "id": "adr-010",
170
+ "type": "recommendation",
171
+ "topic": "ADR audit trail",
172
+ "content": "For enterprise compliance: ADRs created via wheat produce an immutable audit trail. The claims.json is version-controlled, the compilation certificate references a content hash, and git log shows who contributed what. This satisfies SOC 2 change management evidence requirements.",
173
+ "source": {
174
+ "origin": "best-practice",
175
+ "artifact": null,
176
+ "connector": null
177
+ },
178
+ "evidence": "stated",
179
+ "status": "active",
180
+ "phase_added": "define",
181
+ "timestamp": "2026-01-01T00:00:00.000Z",
182
+ "conflicts_with": [],
183
+ "resolved_by": null,
184
+ "tags": ["adr", "audit-trail", "compliance", "soc2", "enterprise"]
185
+ },
186
+ {
187
+ "id": "adr-011",
188
+ "type": "estimate",
189
+ "topic": "ADR time savings",
190
+ "content": "Traditional ADR process: 1-2 weeks of stakeholder meetings, document drafting, and review cycles. Wheat-powered ADR: 10-15 minute research sprint produces a compiled brief with evidence tiers, conflict resolution, and structured recommendations — then a 30-minute review meeting to accept.",
191
+ "source": { "origin": "research", "artifact": null, "connector": null },
192
+ "evidence": "stated",
193
+ "status": "active",
194
+ "phase_added": "research",
195
+ "timestamp": "2026-01-01T00:00:00.000Z",
196
+ "conflicts_with": [],
197
+ "resolved_by": null,
198
+ "tags": ["adr", "time-savings", "roi", "enterprise"]
199
+ },
200
+ {
201
+ "id": "adr-012",
202
+ "type": "factual",
203
+ "topic": "scaling decision patterns",
204
+ "content": "Common scaling decision points: (1) Vertical before horizontal — scale up is simpler until hardware limits. (2) Read replicas before sharding — reduces complexity for read-heavy workloads. (3) Caching before rewriting — often 10x improvement for fraction of effort. (4) CDN before app optimization for static assets.",
205
+ "source": {
206
+ "origin": "best-practice",
207
+ "artifact": null,
208
+ "connector": null
209
+ },
210
+ "evidence": "documented",
211
+ "status": "active",
212
+ "phase_added": "define",
213
+ "timestamp": "2026-01-01T00:00:00.000Z",
214
+ "conflicts_with": [],
215
+ "resolved_by": null,
216
+ "tags": ["adr", "scaling", "patterns", "architecture"]
217
+ }
218
+ ]
219
+ }
@@ -8,7 +8,11 @@
8
8
  "type": "constraint",
9
9
  "topic": "HTTP method semantics",
10
10
  "content": "GET must be safe and idempotent (no side effects). PUT must be idempotent (same result on repeat). POST is neither safe nor idempotent. DELETE should be idempotent (deleting an already-deleted resource returns 204 or 404, not an error). Violating these semantics breaks caches, retries, and client expectations.",
11
- "source": { "origin": "best-practice", "artifact": null, "connector": null },
11
+ "source": {
12
+ "origin": "best-practice",
13
+ "artifact": null,
14
+ "connector": null
15
+ },
12
16
  "evidence": "documented",
13
17
  "status": "active",
14
18
  "phase_added": "define",
@@ -22,7 +26,11 @@
22
26
  "type": "recommendation",
23
27
  "topic": "error response format",
24
28
  "content": "Use RFC 7807 (Problem Details) for error responses: {type, title, status, detail, instance}. Include a machine-readable error code field for client-side branching. Never expose stack traces, internal paths, or database errors in production responses.",
25
- "source": { "origin": "best-practice", "artifact": null, "connector": null },
29
+ "source": {
30
+ "origin": "best-practice",
31
+ "artifact": null,
32
+ "connector": null
33
+ },
26
34
  "evidence": "documented",
27
35
  "status": "active",
28
36
  "phase_added": "define",
@@ -36,7 +44,11 @@
36
44
  "type": "recommendation",
37
45
  "topic": "cursor-based pagination",
38
46
  "content": "Prefer cursor-based pagination over offset-based for datasets that change frequently. Offset pagination becomes inconsistent when rows are inserted or deleted between pages. Cursor pagination uses an opaque token (base64-encoded last-seen ID) and supports consistent forward iteration. Return {data, next_cursor, has_more} in responses.",
39
- "source": { "origin": "best-practice", "artifact": null, "connector": null },
47
+ "source": {
48
+ "origin": "best-practice",
49
+ "artifact": null,
50
+ "connector": null
51
+ },
40
52
  "evidence": "production",
41
53
  "status": "active",
42
54
  "phase_added": "define",
@@ -50,7 +62,11 @@
50
62
  "type": "risk",
51
63
  "topic": "URL path versioning lock-in",
52
64
  "content": "URL path versioning (/v1/users, /v2/users) is simple but creates permanent routing complexity. Every version doubles the endpoint surface. Prefer header-based versioning (Accept: application/vnd.api+json;version=2) or additive-only changes (no versioning needed if you never remove or rename fields).",
53
- "source": { "origin": "best-practice", "artifact": null, "connector": null },
65
+ "source": {
66
+ "origin": "best-practice",
67
+ "artifact": null,
68
+ "connector": null
69
+ },
54
70
  "evidence": "web",
55
71
  "status": "active",
56
72
  "phase_added": "define",
@@ -64,21 +80,34 @@
64
80
  "type": "constraint",
65
81
  "topic": "backwards compatibility rules",
66
82
  "content": "Breaking changes for public APIs: removing a field, renaming a field, changing a field type, changing a required/optional status, removing an endpoint, changing authentication. Non-breaking: adding optional fields, adding new endpoints, adding new enum values (if clients handle unknown values). Never make breaking changes without a version bump and deprecation period of at least 6 months.",
67
- "source": { "origin": "best-practice", "artifact": null, "connector": null },
83
+ "source": {
84
+ "origin": "best-practice",
85
+ "artifact": null,
86
+ "connector": null
87
+ },
68
88
  "evidence": "documented",
69
89
  "status": "active",
70
90
  "phase_added": "define",
71
91
  "timestamp": "2025-01-01T00:00:00.000Z",
72
92
  "conflicts_with": [],
73
93
  "resolved_by": null,
74
- "tags": ["api", "backwards-compatibility", "deprecation", "breaking-changes"]
94
+ "tags": [
95
+ "api",
96
+ "backwards-compatibility",
97
+ "deprecation",
98
+ "breaking-changes"
99
+ ]
75
100
  },
76
101
  {
77
102
  "id": "api-006",
78
103
  "type": "factual",
79
104
  "topic": "GraphQL vs REST tradeoffs",
80
105
  "content": "GraphQL eliminates over-fetching and under-fetching but introduces: query complexity analysis (must limit depth to 7-10 and breadth), N+1 data loader patterns, cache invalidation complexity (no HTTP caching by default), and a steeper client learning curve. REST is simpler for CRUD-heavy APIs with predictable access patterns.",
81
- "source": { "origin": "best-practice", "artifact": null, "connector": null },
106
+ "source": {
107
+ "origin": "best-practice",
108
+ "artifact": null,
109
+ "connector": null
110
+ },
82
111
  "evidence": "documented",
83
112
  "status": "active",
84
113
  "phase_added": "define",
@@ -92,7 +121,11 @@
92
121
  "type": "recommendation",
93
122
  "topic": "rate limit headers",
94
123
  "content": "Include rate limit headers in every response: X-RateLimit-Limit (max requests per window), X-RateLimit-Remaining (remaining in current window), X-RateLimit-Reset (Unix timestamp when window resets). Return 429 Too Many Requests with a Retry-After header when limit is exceeded. This lets well-behaved clients self-throttle.",
95
- "source": { "origin": "best-practice", "artifact": null, "connector": null },
124
+ "source": {
125
+ "origin": "best-practice",
126
+ "artifact": null,
127
+ "connector": null
128
+ },
96
129
  "evidence": "documented",
97
130
  "status": "active",
98
131
  "phase_added": "define",
@@ -106,7 +139,11 @@
106
139
  "type": "constraint",
107
140
  "topic": "request payload size limits",
108
141
  "content": "Set explicit request body size limits: 1 MB for standard JSON APIs, 10 MB for file uploads via multipart, and configurable limits for specific endpoints. Unbounded request bodies enable denial-of-service attacks. Return 413 Payload Too Large with a clear message stating the limit.",
109
- "source": { "origin": "best-practice", "artifact": null, "connector": null },
142
+ "source": {
143
+ "origin": "best-practice",
144
+ "artifact": null,
145
+ "connector": null
146
+ },
110
147
  "evidence": "documented",
111
148
  "status": "active",
112
149
  "phase_added": "define",
@@ -120,7 +157,11 @@
120
157
  "type": "recommendation",
121
158
  "topic": "idempotency keys for mutations",
122
159
  "content": "POST endpoints that create resources should accept an Idempotency-Key header. Store the key with the response for 24 hours. If a duplicate key arrives, return the stored response without re-executing. This prevents duplicate charges, orders, or records from network retries. Stripe, PayPal, and AWS all use this pattern.",
123
- "source": { "origin": "best-practice", "artifact": null, "connector": null },
160
+ "source": {
161
+ "origin": "best-practice",
162
+ "artifact": null,
163
+ "connector": null
164
+ },
124
165
  "evidence": "production",
125
166
  "status": "active",
126
167
  "phase_added": "define",
@@ -134,7 +175,11 @@
134
175
  "type": "risk",
135
176
  "topic": "nested resource depth",
136
177
  "content": "Deeply nested REST routes (/orgs/:id/teams/:id/members/:id/roles) become rigid and hard to evolve. Limit nesting to 2 levels maximum. Use flat routes with query filters for deeper relationships (/roles?member_id=X&team_id=Y). Deep nesting also makes URL construction fragile for API consumers.",
137
- "source": { "origin": "best-practice", "artifact": null, "connector": null },
178
+ "source": {
179
+ "origin": "best-practice",
180
+ "artifact": null,
181
+ "connector": null
182
+ },
138
183
  "evidence": "web",
139
184
  "status": "active",
140
185
  "phase_added": "define",
@@ -148,7 +193,11 @@
148
193
  "type": "factual",
149
194
  "topic": "HTTP status code semantics",
150
195
  "content": "Use status codes precisely: 200 (success with body), 201 (created, include Location header), 204 (success, no body), 400 (malformed request), 401 (unauthenticated), 403 (authenticated but unauthorized), 404 (not found or hidden), 409 (conflict/duplicate), 422 (valid syntax but semantic error), 429 (rate limited), 500 (server error). Avoid 200-wrapping errors.",
151
- "source": { "origin": "best-practice", "artifact": null, "connector": null },
196
+ "source": {
197
+ "origin": "best-practice",
198
+ "artifact": null,
199
+ "connector": null
200
+ },
152
201
  "evidence": "documented",
153
202
  "status": "active",
154
203
  "phase_added": "define",
@@ -162,7 +211,11 @@
162
211
  "type": "recommendation",
163
212
  "topic": "API documentation contract testing",
164
213
  "content": "Generate API documentation from code (OpenAPI/Swagger) rather than maintaining it manually. Use contract testing (Pact, Dredd, or OpenAPI-diff in CI) to verify that the implementation matches the spec. Manual docs drift within weeks; contract tests catch drift on every PR.",
165
- "source": { "origin": "best-practice", "artifact": null, "connector": null },
214
+ "source": {
215
+ "origin": "best-practice",
216
+ "artifact": null,
217
+ "connector": null
218
+ },
166
219
  "evidence": "tested",
167
220
  "status": "active",
168
221
  "phase_added": "define",
@@ -186,4 +239,4 @@
186
239
  "tags": ["api", "performance", "latency", "sla"]
187
240
  }
188
241
  ]
189
- }
242
+ }