@ryuenn3123/agentic-senior-core 2.0.19 → 2.0.20
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/.agent-context/state/benchmark-comparison-schema.json +181 -0
- package/.cursorrules +1 -1
- package/.gemini/instructions.md +1 -1
- package/.github/copilot-instructions.md +1 -1
- package/.windsurfrules +1 -1
- package/AGENTS.md +1 -1
- package/lib/cli/commands/init.mjs +88 -0
- package/lib/cli/compiler.mjs +31 -0
- package/lib/cli/project-scaffolder.mjs +378 -0
- package/lib/cli/templates/api-contract.md.tmpl +143 -0
- package/lib/cli/templates/architecture-decision-record.md.tmpl +106 -0
- package/lib/cli/templates/database-schema.md.tmpl +74 -0
- package/lib/cli/templates/flow-overview.md.tmpl +119 -0
- package/lib/cli/templates/project-brief.md.tmpl +53 -0
- package/lib/cli/utils.mjs +5 -1
- package/package.json +2 -2
- package/scripts/bump-version.mjs +101 -0
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project Scaffolder — Dynamic project documentation generator.
|
|
3
|
+
* Generates project-specific docs during init when the target folder is empty.
|
|
4
|
+
* Depends on: constants.mjs, utils.mjs
|
|
5
|
+
*/
|
|
6
|
+
import fs from 'node:fs/promises';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
|
|
10
|
+
import { CLI_VERSION } from './constants.mjs';
|
|
11
|
+
import { ensureDirectory, askChoice, askYesNo, toTitleCase, pathExists } from './utils.mjs';
|
|
12
|
+
|
|
13
|
+
const CURRENT_FILE_PATH = fileURLToPath(import.meta.url);
|
|
14
|
+
const CURRENT_DIRECTORY_PATH = path.dirname(CURRENT_FILE_PATH);
|
|
15
|
+
const TEMPLATES_DIRECTORY_PATH = path.join(CURRENT_DIRECTORY_PATH, 'templates');
|
|
16
|
+
|
|
17
|
+
const DOMAIN_CHOICES = [
|
|
18
|
+
'API service',
|
|
19
|
+
'Web application',
|
|
20
|
+
'Mobile app',
|
|
21
|
+
'CLI tool',
|
|
22
|
+
'Library / SDK',
|
|
23
|
+
'Other',
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
const DATABASE_CHOICES = [
|
|
27
|
+
'None (stateless service)',
|
|
28
|
+
'SQL (PostgreSQL, MySQL, SQLite)',
|
|
29
|
+
'NoSQL (MongoDB, Redis, DynamoDB)',
|
|
30
|
+
'Both (SQL primary + cache layer)',
|
|
31
|
+
'Other',
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
const AUTH_CHOICES = [
|
|
35
|
+
'None (public service)',
|
|
36
|
+
'JWT (stateless token auth)',
|
|
37
|
+
'OAuth 2.0 (third-party login)',
|
|
38
|
+
'Session-based (server-side sessions)',
|
|
39
|
+
'API Key (simple key auth)',
|
|
40
|
+
'Other',
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Run the project discovery interview.
|
|
45
|
+
* Returns a structured object with all user responses.
|
|
46
|
+
*/
|
|
47
|
+
export async function runProjectDiscovery(userInterface) {
|
|
48
|
+
console.log('\n--- Project Discovery ---');
|
|
49
|
+
console.log('I will ask a few questions to generate project-specific documentation.');
|
|
50
|
+
console.log('This helps AI agents understand your project before writing code.\n');
|
|
51
|
+
|
|
52
|
+
const projectName = (await userInterface.question('Project name: ')).trim();
|
|
53
|
+
if (!projectName) {
|
|
54
|
+
throw new Error('Project name is required for documentation scaffolding.');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const projectDescription = (await userInterface.question('One-line description: ')).trim() || `A ${projectName} project.`;
|
|
58
|
+
|
|
59
|
+
const domainSelection = await askChoice(
|
|
60
|
+
'Primary domain:',
|
|
61
|
+
DOMAIN_CHOICES,
|
|
62
|
+
userInterface
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
let primaryDomain = domainSelection;
|
|
66
|
+
if (domainSelection === 'Other') {
|
|
67
|
+
primaryDomain = (await userInterface.question('Describe your domain: ')).trim() || 'Custom domain';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const databaseSelection = await askChoice(
|
|
71
|
+
'Database needs:',
|
|
72
|
+
DATABASE_CHOICES,
|
|
73
|
+
userInterface
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
let databaseChoice = databaseSelection;
|
|
77
|
+
if (databaseSelection === 'Other') {
|
|
78
|
+
databaseChoice = (await userInterface.question('Describe your database setup: ')).trim() || 'Custom database';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const authSelection = await askChoice(
|
|
82
|
+
'Auth strategy:',
|
|
83
|
+
AUTH_CHOICES,
|
|
84
|
+
userInterface
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
let authStrategy = authSelection;
|
|
88
|
+
if (authSelection === 'Other') {
|
|
89
|
+
authStrategy = (await userInterface.question('Describe your auth setup: ')).trim() || 'Custom auth';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
console.log('\nList your key features (one per line, press Enter twice to finish):');
|
|
93
|
+
const features = [];
|
|
94
|
+
let consecutiveEmptyLineCount = 0;
|
|
95
|
+
|
|
96
|
+
while (features.length < 10) {
|
|
97
|
+
const featureLine = (await userInterface.question(` Feature ${features.length + 1}: `)).trim();
|
|
98
|
+
|
|
99
|
+
if (!featureLine) {
|
|
100
|
+
consecutiveEmptyLineCount += 1;
|
|
101
|
+
if (consecutiveEmptyLineCount >= 1 && features.length >= 1) {
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
consecutiveEmptyLineCount = 0;
|
|
108
|
+
features.push(featureLine);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (features.length === 0) {
|
|
112
|
+
features.push('Core functionality (define during development)');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const additionalContext = (await userInterface.question('\nAdditional context (optional, press Enter to skip): ')).trim()
|
|
116
|
+
|| 'No additional context provided.';
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
projectName,
|
|
120
|
+
projectDescription,
|
|
121
|
+
primaryDomain,
|
|
122
|
+
databaseChoice,
|
|
123
|
+
authStrategy,
|
|
124
|
+
features,
|
|
125
|
+
additionalContext,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Determine which documents to generate based on project discovery answers.
|
|
131
|
+
*/
|
|
132
|
+
export function resolveDocumentManifest(discoveryAnswers) {
|
|
133
|
+
const hasDatabase = !discoveryAnswers.databaseChoice.toLowerCase().startsWith('none');
|
|
134
|
+
const hasAuth = !discoveryAnswers.authStrategy.toLowerCase().startsWith('none');
|
|
135
|
+
const isApiOrWebDomain = ['API service', 'Web application'].includes(discoveryAnswers.primaryDomain)
|
|
136
|
+
|| discoveryAnswers.primaryDomain.toLowerCase().includes('api')
|
|
137
|
+
|| discoveryAnswers.primaryDomain.toLowerCase().includes('web');
|
|
138
|
+
|
|
139
|
+
const documentManifest = [
|
|
140
|
+
{ templateFileName: 'project-brief.md.tmpl', outputFileName: 'project-brief.md', alwaysInclude: true },
|
|
141
|
+
{ templateFileName: 'architecture-decision-record.md.tmpl', outputFileName: 'architecture-decision-record.md', alwaysInclude: true },
|
|
142
|
+
{ templateFileName: 'flow-overview.md.tmpl', outputFileName: 'flow-overview.md', alwaysInclude: true },
|
|
143
|
+
];
|
|
144
|
+
|
|
145
|
+
if (hasDatabase) {
|
|
146
|
+
documentManifest.push({
|
|
147
|
+
templateFileName: 'database-schema.md.tmpl',
|
|
148
|
+
outputFileName: 'database-schema.md',
|
|
149
|
+
alwaysInclude: false,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (isApiOrWebDomain) {
|
|
154
|
+
documentManifest.push({
|
|
155
|
+
templateFileName: 'api-contract.md.tmpl',
|
|
156
|
+
outputFileName: 'api-contract.md',
|
|
157
|
+
alwaysInclude: false,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return { documentManifest, hasDatabase, hasAuth };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Build template context from discovery answers and init selections.
|
|
166
|
+
*/
|
|
167
|
+
export function buildTemplateContext(discoveryAnswers, initContext) {
|
|
168
|
+
const hasDatabase = !discoveryAnswers.databaseChoice.toLowerCase().startsWith('none');
|
|
169
|
+
const hasAuth = !discoveryAnswers.authStrategy.toLowerCase().startsWith('none');
|
|
170
|
+
|
|
171
|
+
const baseUrlMap = {
|
|
172
|
+
'API service': 'http://localhost:3000',
|
|
173
|
+
'Web application': 'http://localhost:3000/api',
|
|
174
|
+
'Mobile app': 'http://localhost:3000/api',
|
|
175
|
+
'CLI tool': 'N/A',
|
|
176
|
+
'Library / SDK': 'N/A',
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
projectName: discoveryAnswers.projectName,
|
|
181
|
+
projectDescription: discoveryAnswers.projectDescription,
|
|
182
|
+
primaryDomain: discoveryAnswers.primaryDomain,
|
|
183
|
+
databaseChoice: discoveryAnswers.databaseChoice,
|
|
184
|
+
authStrategy: discoveryAnswers.authStrategy,
|
|
185
|
+
features: discoveryAnswers.features,
|
|
186
|
+
additionalContext: discoveryAnswers.additionalContext,
|
|
187
|
+
stackFileName: initContext.stackFileName,
|
|
188
|
+
stackDisplayName: toTitleCase(initContext.stackFileName),
|
|
189
|
+
blueprintFileName: initContext.blueprintFileName,
|
|
190
|
+
blueprintDisplayName: toTitleCase(initContext.blueprintFileName),
|
|
191
|
+
cliVersion: CLI_VERSION,
|
|
192
|
+
generatedAt: new Date().toISOString(),
|
|
193
|
+
generatedDate: new Date().toISOString().split('T')[0],
|
|
194
|
+
hasDatabase,
|
|
195
|
+
hasAuth,
|
|
196
|
+
baseUrl: baseUrlMap[discoveryAnswers.primaryDomain] || 'http://localhost:3000',
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Render a template string by replacing {{placeholder}} tokens with context values.
|
|
202
|
+
* Supports:
|
|
203
|
+
* - {{key}} for simple values
|
|
204
|
+
* - {{#each key}}...{{this}}...{{/each}} for arrays
|
|
205
|
+
* - {{#if key}}...{{/if}} for conditionals
|
|
206
|
+
*/
|
|
207
|
+
export function renderTemplate(templateContent, templateContext) {
|
|
208
|
+
let renderedContent = templateContent;
|
|
209
|
+
|
|
210
|
+
// Process {{#each key}}...{{/each}} blocks
|
|
211
|
+
renderedContent = renderedContent.replace(
|
|
212
|
+
/\{\{#each\s+(\w+)\}\}([\s\S]*?)\{\{\/each\}\}/g,
|
|
213
|
+
(_fullMatch, iteratorKey, iteratorBody) => {
|
|
214
|
+
const iteratorValues = templateContext[iteratorKey];
|
|
215
|
+
|
|
216
|
+
if (!Array.isArray(iteratorValues) || iteratorValues.length === 0) {
|
|
217
|
+
return '';
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return iteratorValues
|
|
221
|
+
.map((iteratorValue) => iteratorBody.replace(/\{\{this\}\}/g, iteratorValue))
|
|
222
|
+
.join('');
|
|
223
|
+
}
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
// Process {{#if key}}...{{/if}} blocks
|
|
227
|
+
renderedContent = renderedContent.replace(
|
|
228
|
+
/\{\{#if\s+(\w+)\}\}([\s\S]*?)\{\{\/if\}\}/g,
|
|
229
|
+
(_fullMatch, conditionKey, conditionBody) => {
|
|
230
|
+
const conditionValue = templateContext[conditionKey];
|
|
231
|
+
return conditionValue ? conditionBody : '';
|
|
232
|
+
}
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
// Process simple {{key}} replacements
|
|
236
|
+
renderedContent = renderedContent.replace(
|
|
237
|
+
/\{\{(\w+)\}\}/g,
|
|
238
|
+
(_fullMatch, placeholderKey) => {
|
|
239
|
+
const placeholderValue = templateContext[placeholderKey];
|
|
240
|
+
|
|
241
|
+
if (placeholderValue === undefined || placeholderValue === null) {
|
|
242
|
+
return `{{${placeholderKey}}}`;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (Array.isArray(placeholderValue)) {
|
|
246
|
+
return placeholderValue.join(', ');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return String(placeholderValue);
|
|
250
|
+
}
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
return renderedContent;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Generate project documentation files from templates.
|
|
258
|
+
*/
|
|
259
|
+
export async function generateProjectDocumentation(
|
|
260
|
+
targetDirectoryPath,
|
|
261
|
+
discoveryAnswers,
|
|
262
|
+
initContext
|
|
263
|
+
) {
|
|
264
|
+
const docsDirectoryPath = path.join(targetDirectoryPath, 'docs');
|
|
265
|
+
await ensureDirectory(docsDirectoryPath);
|
|
266
|
+
|
|
267
|
+
const templateContext = buildTemplateContext(discoveryAnswers, initContext);
|
|
268
|
+
const { documentManifest } = resolveDocumentManifest(discoveryAnswers);
|
|
269
|
+
const generatedFileNames = [];
|
|
270
|
+
|
|
271
|
+
for (const documentEntry of documentManifest) {
|
|
272
|
+
const templateFilePath = path.join(TEMPLATES_DIRECTORY_PATH, documentEntry.templateFileName);
|
|
273
|
+
|
|
274
|
+
if (!(await pathExists(templateFilePath))) {
|
|
275
|
+
console.log(`[WARN] Template not found: ${documentEntry.templateFileName}, skipping.`);
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const templateContent = await fs.readFile(templateFilePath, 'utf8');
|
|
280
|
+
const renderedContent = renderTemplate(templateContent, templateContext);
|
|
281
|
+
const outputFilePath = path.join(docsDirectoryPath, documentEntry.outputFileName);
|
|
282
|
+
|
|
283
|
+
await fs.writeFile(outputFilePath, renderedContent, 'utf8');
|
|
284
|
+
generatedFileNames.push(documentEntry.outputFileName);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
docsDirectoryPath,
|
|
289
|
+
generatedFileNames,
|
|
290
|
+
templateVersion: '1.0.0',
|
|
291
|
+
discoveryAnswers,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Check if the target directory qualifies as "empty" for scaffolding purposes.
|
|
297
|
+
* A directory with only .git is still considered empty.
|
|
298
|
+
*/
|
|
299
|
+
export async function isDirectoryEffectivelyEmpty(targetDirectoryPath) {
|
|
300
|
+
try {
|
|
301
|
+
const directoryEntries = await fs.readdir(targetDirectoryPath);
|
|
302
|
+
const meaningfulEntries = directoryEntries.filter(
|
|
303
|
+
(entryName) => entryName !== '.git' && entryName !== '.gitignore'
|
|
304
|
+
);
|
|
305
|
+
return meaningfulEntries.length === 0;
|
|
306
|
+
} catch {
|
|
307
|
+
return true;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Check if project docs already exist in the target directory.
|
|
313
|
+
*/
|
|
314
|
+
export async function hasExistingProjectDocs(targetDirectoryPath) {
|
|
315
|
+
const projectBriefPath = path.join(targetDirectoryPath, 'docs', 'project-brief.md');
|
|
316
|
+
return pathExists(projectBriefPath);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Load project config from a YAML-like file for non-interactive mode.
|
|
321
|
+
* Uses a simple key: value format (one per line) for zero-dependency parsing.
|
|
322
|
+
*/
|
|
323
|
+
export async function loadProjectConfig(configFilePath) {
|
|
324
|
+
const configContent = await fs.readFile(configFilePath, 'utf8');
|
|
325
|
+
const configLines = configContent.split(/\r?\n/);
|
|
326
|
+
const configEntries = {};
|
|
327
|
+
let currentKey = null;
|
|
328
|
+
let currentArrayValues = null;
|
|
329
|
+
|
|
330
|
+
for (const configLine of configLines) {
|
|
331
|
+
const trimmedLine = configLine.trim();
|
|
332
|
+
|
|
333
|
+
if (!trimmedLine || trimmedLine.startsWith('#')) {
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (trimmedLine.startsWith('- ') && currentKey && currentArrayValues !== null) {
|
|
338
|
+
currentArrayValues.push(trimmedLine.slice(2).trim());
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (currentKey && currentArrayValues !== null) {
|
|
343
|
+
configEntries[currentKey] = currentArrayValues;
|
|
344
|
+
currentKey = null;
|
|
345
|
+
currentArrayValues = null;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const colonIndex = trimmedLine.indexOf(':');
|
|
349
|
+
if (colonIndex === -1) {
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const entryKey = trimmedLine.slice(0, colonIndex).trim();
|
|
354
|
+
const entryValue = trimmedLine.slice(colonIndex + 1).trim();
|
|
355
|
+
|
|
356
|
+
if (!entryValue) {
|
|
357
|
+
currentKey = entryKey;
|
|
358
|
+
currentArrayValues = [];
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
configEntries[entryKey] = entryValue;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (currentKey && currentArrayValues !== null) {
|
|
366
|
+
configEntries[currentKey] = currentArrayValues;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return {
|
|
370
|
+
projectName: configEntries.projectName || configEntries.name || '',
|
|
371
|
+
projectDescription: configEntries.projectDescription || configEntries.description || '',
|
|
372
|
+
primaryDomain: configEntries.primaryDomain || configEntries.domain || 'API service',
|
|
373
|
+
databaseChoice: configEntries.databaseChoice || configEntries.database || 'None (stateless service)',
|
|
374
|
+
authStrategy: configEntries.authStrategy || configEntries.auth || 'None (public service)',
|
|
375
|
+
features: Array.isArray(configEntries.features) ? configEntries.features : [],
|
|
376
|
+
additionalContext: configEntries.additionalContext || configEntries.context || 'No additional context provided.',
|
|
377
|
+
};
|
|
378
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# API Contract: {{projectName}}
|
|
2
|
+
|
|
3
|
+
Generated by Agentic-Senior-Core CLI v{{cliVersion}}
|
|
4
|
+
Generated at: {{generatedAt}}
|
|
5
|
+
Template version: 1.0.0
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## API Design Principles
|
|
10
|
+
|
|
11
|
+
Per `.agent-context/rules/api-docs.md`:
|
|
12
|
+
- OpenAPI 3.1 specification is mandatory for all HTTP APIs.
|
|
13
|
+
- Every endpoint must have documented request/response schemas.
|
|
14
|
+
- Error responses use typed error codes, never raw strings.
|
|
15
|
+
- Input validation is mandatory on every endpoint.
|
|
16
|
+
|
|
17
|
+
## Base URL
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
{{baseUrl}}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Endpoints
|
|
26
|
+
|
|
27
|
+
### Health Check
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
GET /health
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Response `200`:
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"status": "ok",
|
|
37
|
+
"version": "1.0.0",
|
|
38
|
+
"timestamp": "2026-01-01T00:00:00Z"
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
{{#if hasAuth}}
|
|
43
|
+
### Authentication
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
POST /auth/register
|
|
47
|
+
```
|
|
48
|
+
Request:
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"email": "user@example.com",
|
|
52
|
+
"password": "secure-password",
|
|
53
|
+
"displayName": "User Name"
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
POST /auth/login
|
|
59
|
+
```
|
|
60
|
+
Request:
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"email": "user@example.com",
|
|
64
|
+
"password": "secure-password"
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
Response `200`:
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"token": "jwt-token-here",
|
|
71
|
+
"expiresIn": 3600
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
{{/if}}
|
|
76
|
+
### Feature Endpoints
|
|
77
|
+
|
|
78
|
+
Based on the defined project features, plan these endpoint groups:
|
|
79
|
+
|
|
80
|
+
{{#each features}}
|
|
81
|
+
#### {{this}}
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
GET /api/[resource] — List
|
|
85
|
+
GET /api/[resource]/:id — Detail
|
|
86
|
+
POST /api/[resource] — Create
|
|
87
|
+
PUT /api/[resource]/:id — Update
|
|
88
|
+
DELETE /api/[resource]/:id — Delete
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
{{/each}}
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Error Response Format
|
|
96
|
+
|
|
97
|
+
All error responses follow this structure:
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"error": {
|
|
102
|
+
"code": "VALIDATION_ERROR",
|
|
103
|
+
"message": "Human-readable description",
|
|
104
|
+
"details": []
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Standard error codes:
|
|
110
|
+
- `VALIDATION_ERROR` (400)
|
|
111
|
+
- `UNAUTHORIZED` (401)
|
|
112
|
+
- `FORBIDDEN` (403)
|
|
113
|
+
- `NOT_FOUND` (404)
|
|
114
|
+
- `CONFLICT` (409)
|
|
115
|
+
- `INTERNAL_ERROR` (500)
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Pagination
|
|
120
|
+
|
|
121
|
+
List endpoints use cursor-based or offset-based pagination:
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
GET /api/[resource]?page=1&limit=20
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Response includes:
|
|
128
|
+
```json
|
|
129
|
+
{
|
|
130
|
+
"data": [],
|
|
131
|
+
"pagination": {
|
|
132
|
+
"page": 1,
|
|
133
|
+
"limit": 20,
|
|
134
|
+
"total": 100,
|
|
135
|
+
"totalPages": 5
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
This document is a living reference. Update it as API endpoints are implemented.
|
|
143
|
+
Maintain a corresponding OpenAPI 3.1 specification file alongside this document.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Architecture Decision Record: {{projectName}}
|
|
2
|
+
|
|
3
|
+
Generated by Agentic-Senior-Core CLI v{{cliVersion}}
|
|
4
|
+
Generated at: {{generatedAt}}
|
|
5
|
+
Template version: 1.0.0
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## ADR-001: Technology Stack Selection
|
|
10
|
+
|
|
11
|
+
**Status**: Accepted
|
|
12
|
+
**Date**: {{generatedDate}}
|
|
13
|
+
|
|
14
|
+
### Context
|
|
15
|
+
|
|
16
|
+
This project requires a {{primaryDomain}} solution. The team evaluated available stack profiles and blueprints from the Agentic-Senior-Core governance repository.
|
|
17
|
+
|
|
18
|
+
### Decision
|
|
19
|
+
|
|
20
|
+
- **Language/Runtime**: {{stackDisplayName}} (source: `.agent-context/stacks/{{stackFileName}}`)
|
|
21
|
+
- **Architecture blueprint**: {{blueprintDisplayName}} (source: `.agent-context/blueprints/{{blueprintFileName}}`)
|
|
22
|
+
- **Database**: {{databaseChoice}}
|
|
23
|
+
- **Auth**: {{authStrategy}}
|
|
24
|
+
|
|
25
|
+
### Rationale
|
|
26
|
+
|
|
27
|
+
The {{stackDisplayName}} stack was selected because:
|
|
28
|
+
- It matches the project domain ({{primaryDomain}}).
|
|
29
|
+
- The governance repository provides a verified blueprint for this stack.
|
|
30
|
+
- The team has access to skill packs covering this technology.
|
|
31
|
+
|
|
32
|
+
### Consequences
|
|
33
|
+
|
|
34
|
+
- All code must follow the conventions in `.agent-context/stacks/{{stackFileName}}`.
|
|
35
|
+
- Module boundaries must follow `.agent-context/blueprints/{{blueprintFileName}}`.
|
|
36
|
+
- Database access must follow `.agent-context/rules/database-design.md`.
|
|
37
|
+
- API contracts must follow `.agent-context/rules/api-docs.md`.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## ADR-002: Architecture Pattern
|
|
42
|
+
|
|
43
|
+
**Status**: Accepted
|
|
44
|
+
**Date**: {{generatedDate}}
|
|
45
|
+
|
|
46
|
+
### Context
|
|
47
|
+
|
|
48
|
+
The project needs a clear separation of concerns to remain maintainable as it grows.
|
|
49
|
+
|
|
50
|
+
### Decision
|
|
51
|
+
|
|
52
|
+
Follow the **Transport - Service - Repository** layered architecture as defined in `.agent-context/rules/architecture.md`.
|
|
53
|
+
|
|
54
|
+
{{#if hasDatabase}}
|
|
55
|
+
### Database Layer
|
|
56
|
+
|
|
57
|
+
- Primary database: {{databaseChoice}}
|
|
58
|
+
- ORM/query strategy: follow stack-specific conventions from the selected stack profile.
|
|
59
|
+
- Migration strategy: safe, reversible migrations per `.agent-context/rules/database-design.md`.
|
|
60
|
+
{{/if}}
|
|
61
|
+
|
|
62
|
+
{{#if hasAuth}}
|
|
63
|
+
### Authentication Layer
|
|
64
|
+
|
|
65
|
+
- Strategy: {{authStrategy}}
|
|
66
|
+
- Secrets management: no hardcoded secrets per `.agent-context/rules/security.md`.
|
|
67
|
+
- Input validation: mandatory on all auth endpoints per `.agent-context/rules/security.md`.
|
|
68
|
+
{{/if}}
|
|
69
|
+
|
|
70
|
+
### Consequences
|
|
71
|
+
|
|
72
|
+
- Every module must respect the three-layer separation.
|
|
73
|
+
- Cross-layer imports are forbidden (no repository calls from transport layer).
|
|
74
|
+
- Shared logic lives in a dedicated shared module, never in transport handlers.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## ADR-003: Project Structure
|
|
79
|
+
|
|
80
|
+
**Status**: Accepted
|
|
81
|
+
**Date**: {{generatedDate}}
|
|
82
|
+
|
|
83
|
+
### Decision
|
|
84
|
+
|
|
85
|
+
The project directory structure follows the selected blueprint (`{{blueprintDisplayName}}`). Key directories:
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
{{projectName}}/
|
|
89
|
+
src/
|
|
90
|
+
modules/ — feature modules (each with transport, service, repository)
|
|
91
|
+
shared/ — shared utilities, types, constants
|
|
92
|
+
config/ — environment and app configuration
|
|
93
|
+
docs/ — project documentation (this directory)
|
|
94
|
+
tests/ — test suites
|
|
95
|
+
.agent-context/ — governance rules and policies
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Consequences
|
|
99
|
+
|
|
100
|
+
- New features are added as self-contained modules.
|
|
101
|
+
- Shared code is extracted only when used by two or more modules.
|
|
102
|
+
- Configuration is centralized and validated at startup.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
Add new ADR entries below as architectural decisions are made during development.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Database Schema: {{projectName}}
|
|
2
|
+
|
|
3
|
+
Generated by Agentic-Senior-Core CLI v{{cliVersion}}
|
|
4
|
+
Generated at: {{generatedAt}}
|
|
5
|
+
Template version: 1.0.0
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Database Technology
|
|
10
|
+
|
|
11
|
+
**Type**: {{databaseChoice}}
|
|
12
|
+
**Auth strategy**: {{authStrategy}}
|
|
13
|
+
|
|
14
|
+
## Schema Design Principles
|
|
15
|
+
|
|
16
|
+
Per `.agent-context/rules/database-design.md`:
|
|
17
|
+
- Default to Third Normal Form (3NF) unless performance evidence justifies denormalization.
|
|
18
|
+
- Use safe, reversible migrations for every schema change.
|
|
19
|
+
- Never use raw SQL in application code without parameterized queries.
|
|
20
|
+
- Add indexes based on measured query patterns, not assumptions.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Core Tables
|
|
25
|
+
|
|
26
|
+
{{#if hasAuth}}
|
|
27
|
+
### users
|
|
28
|
+
|
|
29
|
+
| Column | Type | Constraints | Notes |
|
|
30
|
+
|--------|------|-------------|-------|
|
|
31
|
+
| id | UUID / BIGINT | PK, NOT NULL | Primary identifier |
|
|
32
|
+
| email | VARCHAR(255) | UNIQUE, NOT NULL | Login credential |
|
|
33
|
+
| password_hash | VARCHAR(255) | NOT NULL | Hashed with bcrypt/argon2 |
|
|
34
|
+
| display_name | VARCHAR(100) | NOT NULL | Public display name |
|
|
35
|
+
| role | VARCHAR(50) | NOT NULL, DEFAULT 'user' | Role-based access |
|
|
36
|
+
| created_at | TIMESTAMP | NOT NULL, DEFAULT NOW() | Record creation |
|
|
37
|
+
| updated_at | TIMESTAMP | NOT NULL, DEFAULT NOW() | Last modification |
|
|
38
|
+
|
|
39
|
+
{{/if}}
|
|
40
|
+
### [Define your domain tables here]
|
|
41
|
+
|
|
42
|
+
Based on the project features:
|
|
43
|
+
{{#each features}}
|
|
44
|
+
- **{{this}}** — consider what data this feature needs to store, read, and query.
|
|
45
|
+
{{/each}}
|
|
46
|
+
|
|
47
|
+
| Column | Type | Constraints | Notes |
|
|
48
|
+
|--------|------|-------------|-------|
|
|
49
|
+
| id | UUID / BIGINT | PK, NOT NULL | Primary identifier |
|
|
50
|
+
| ... | ... | ... | Define based on feature requirements |
|
|
51
|
+
| created_at | TIMESTAMP | NOT NULL, DEFAULT NOW() | Record creation |
|
|
52
|
+
| updated_at | TIMESTAMP | NOT NULL, DEFAULT NOW() | Last modification |
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Migration Strategy
|
|
57
|
+
|
|
58
|
+
1. **Create**: Write a new migration file for every schema change.
|
|
59
|
+
2. **Test**: Run migration on a local/staging database before production.
|
|
60
|
+
3. **Rollback**: Every migration must have a corresponding down/rollback script.
|
|
61
|
+
4. **Review**: Schema changes require review per `.agent-context/review-checklists/pr-checklist.md`.
|
|
62
|
+
|
|
63
|
+
## Indexes
|
|
64
|
+
|
|
65
|
+
Add indexes based on actual query patterns observed during development:
|
|
66
|
+
|
|
67
|
+
| Table | Column(s) | Type | Rationale |
|
|
68
|
+
|-------|-----------|------|-----------|
|
|
69
|
+
| users | email | UNIQUE | Login lookup |
|
|
70
|
+
| ... | ... | ... | Add as query patterns emerge |
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
This document is a living reference. Update it as the data model evolves.
|