devmind 1.0.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 +191 -0
- package/README.md +148 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +232 -0
- package/dist/cli.js.map +1 -0
- package/dist/codebase/generators/architecture.d.ts +4 -0
- package/dist/codebase/generators/architecture.js +33 -0
- package/dist/codebase/generators/architecture.js.map +1 -0
- package/dist/codebase/generators/modules.d.ts +9 -0
- package/dist/codebase/generators/modules.js +46 -0
- package/dist/codebase/generators/modules.js.map +1 -0
- package/dist/codebase/generators/overview.d.ts +5 -0
- package/dist/codebase/generators/overview.js +34 -0
- package/dist/codebase/generators/overview.js.map +1 -0
- package/dist/codebase/generators/skeleton.d.ts +8 -0
- package/dist/codebase/generators/skeleton.js +57 -0
- package/dist/codebase/generators/skeleton.js.map +1 -0
- package/dist/codebase/index.d.ts +24 -0
- package/dist/codebase/index.js +50 -0
- package/dist/codebase/index.js.map +1 -0
- package/dist/codebase/parsers/typescript.d.ts +14 -0
- package/dist/codebase/parsers/typescript.js +145 -0
- package/dist/codebase/parsers/typescript.js.map +1 -0
- package/dist/codebase/scanners/filesystem.d.ts +17 -0
- package/dist/codebase/scanners/filesystem.js +152 -0
- package/dist/codebase/scanners/filesystem.js.map +1 -0
- package/dist/codebase/utils/hashing.d.ts +15 -0
- package/dist/codebase/utils/hashing.js +50 -0
- package/dist/codebase/utils/hashing.js.map +1 -0
- package/dist/codebase/utils/stats.d.ts +12 -0
- package/dist/codebase/utils/stats.js +55 -0
- package/dist/codebase/utils/stats.js.map +1 -0
- package/dist/codebase/utils/tree.d.ts +10 -0
- package/dist/codebase/utils/tree.js +22 -0
- package/dist/codebase/utils/tree.js.map +1 -0
- package/dist/commands/analyze.d.ts +6 -0
- package/dist/commands/analyze.js +97 -0
- package/dist/commands/analyze.js.map +1 -0
- package/dist/commands/context.d.ts +4 -0
- package/dist/commands/context.js +54 -0
- package/dist/commands/context.js.map +1 -0
- package/dist/core/config.d.ts +20 -0
- package/dist/core/config.js +40 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/errors.d.ts +18 -0
- package/dist/core/errors.js +53 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/fileio.d.ts +10 -0
- package/dist/core/fileio.js +57 -0
- package/dist/core/fileio.js.map +1 -0
- package/dist/core/index.d.ts +8 -0
- package/dist/core/index.js +9 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/logger.d.ts +15 -0
- package/dist/core/logger.js +40 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/types.d.ts +106 -0
- package/dist/core/types.js +5 -0
- package/dist/core/types.js.map +1 -0
- package/dist/database/cli.d.ts +7 -0
- package/dist/database/cli.js +132 -0
- package/dist/database/cli.js.map +1 -0
- package/dist/database/commands/checkpoint.d.ts +13 -0
- package/dist/database/commands/checkpoint.js +136 -0
- package/dist/database/commands/checkpoint.js.map +1 -0
- package/dist/database/commands/generate.d.ts +21 -0
- package/dist/database/commands/generate.js +269 -0
- package/dist/database/commands/generate.js.map +1 -0
- package/dist/database/commands/handoff.d.ts +15 -0
- package/dist/database/commands/handoff.js +332 -0
- package/dist/database/commands/handoff.js.map +1 -0
- package/dist/database/commands/history.d.ts +13 -0
- package/dist/database/commands/history.js +148 -0
- package/dist/database/commands/history.js.map +1 -0
- package/dist/database/commands/init.d.ts +10 -0
- package/dist/database/commands/init.js +28 -0
- package/dist/database/commands/init.js.map +1 -0
- package/dist/database/commands/learn.d.ts +12 -0
- package/dist/database/commands/learn.js +93 -0
- package/dist/database/commands/learn.js.map +1 -0
- package/dist/database/commands/memory.d.ts +90 -0
- package/dist/database/commands/memory.js +353 -0
- package/dist/database/commands/memory.js.map +1 -0
- package/dist/database/commands/show.d.ts +9 -0
- package/dist/database/commands/show.js +136 -0
- package/dist/database/commands/show.js.map +1 -0
- package/dist/database/commands/validate.d.ts +9 -0
- package/dist/database/commands/validate.js +200 -0
- package/dist/database/commands/validate.js.map +1 -0
- package/dist/database/commands/watch.d.ts +9 -0
- package/dist/database/commands/watch.js +77 -0
- package/dist/database/commands/watch.js.map +1 -0
- package/dist/database/extractors/drizzle.d.ts +62 -0
- package/dist/database/extractors/drizzle.js +251 -0
- package/dist/database/extractors/drizzle.js.map +1 -0
- package/dist/database/extractors/firebase.d.ts +39 -0
- package/dist/database/extractors/firebase.js +192 -0
- package/dist/database/extractors/firebase.js.map +1 -0
- package/dist/database/extractors/index.d.ts +69 -0
- package/dist/database/extractors/index.js +345 -0
- package/dist/database/extractors/index.js.map +1 -0
- package/dist/database/extractors/mongodb.d.ts +44 -0
- package/dist/database/extractors/mongodb.js +198 -0
- package/dist/database/extractors/mongodb.js.map +1 -0
- package/dist/database/extractors/mysql.d.ts +61 -0
- package/dist/database/extractors/mysql.js +173 -0
- package/dist/database/extractors/mysql.js.map +1 -0
- package/dist/database/extractors/postgres.d.ts +47 -0
- package/dist/database/extractors/postgres.js +141 -0
- package/dist/database/extractors/postgres.js.map +1 -0
- package/dist/database/extractors/prisma.d.ts +71 -0
- package/dist/database/extractors/prisma.js +270 -0
- package/dist/database/extractors/prisma.js.map +1 -0
- package/dist/database/extractors/sqlite.d.ts +50 -0
- package/dist/database/extractors/sqlite.js +148 -0
- package/dist/database/extractors/sqlite.js.map +1 -0
- package/dist/database/generators/learning-generator.d.ts +48 -0
- package/dist/database/generators/learning-generator.js +221 -0
- package/dist/database/generators/learning-generator.js.map +1 -0
- package/dist/database/generators/templates.d.ts +103 -0
- package/dist/database/generators/templates.js +1557 -0
- package/dist/database/generators/templates.js.map +1 -0
- package/dist/database/index.d.ts +15 -0
- package/dist/database/index.js +16 -0
- package/dist/database/index.js.map +1 -0
- package/dist/database/utils/json-output.d.ts +26 -0
- package/dist/database/utils/json-output.js +37 -0
- package/dist/database/utils/json-output.js.map +1 -0
- package/dist/generators/unified.d.ts +1 -0
- package/dist/generators/unified.js +173 -0
- package/dist/generators/unified.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/utils/config-detector.d.ts +1 -0
- package/dist/utils/config-detector.js +40 -0
- package/dist/utils/config-detector.js.map +1 -0
- package/dist/utils/config-loader.d.ts +16 -0
- package/dist/utils/config-loader.js +20 -0
- package/dist/utils/config-loader.js.map +1 -0
- package/package.json +78 -0
|
@@ -0,0 +1,1557 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template Generator - Enhanced Version
|
|
3
|
+
* Generates CLAUDE.md and AGENTS.md from schema information with full relationship support
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Type Mappings
|
|
9
|
+
// ============================================================================
|
|
10
|
+
const TYPE_MAPPINGS = [
|
|
11
|
+
// PostgreSQL
|
|
12
|
+
{ dbType: 'uuid', tsType: 'string', notes: 'UUID v4' },
|
|
13
|
+
{ dbType: 'text', tsType: 'string' },
|
|
14
|
+
{ dbType: 'varchar', tsType: 'string' },
|
|
15
|
+
{ dbType: 'integer', tsType: 'number' },
|
|
16
|
+
{ dbType: 'bigint', tsType: 'number' },
|
|
17
|
+
{ dbType: 'boolean', tsType: 'boolean' },
|
|
18
|
+
{ dbType: 'timestamp', tsType: 'Date' },
|
|
19
|
+
{ dbType: 'jsonb', tsType: 'Record<string, unknown>' },
|
|
20
|
+
// MySQL
|
|
21
|
+
{ dbType: 'int', tsType: 'number' },
|
|
22
|
+
{ dbType: 'datetime', tsType: 'Date' },
|
|
23
|
+
{ dbType: 'enum', tsType: 'string', notes: 'Enum values' },
|
|
24
|
+
// SQLite
|
|
25
|
+
{ dbType: 'INTEGER', tsType: 'number' },
|
|
26
|
+
{ dbType: 'TEXT', tsType: 'string' },
|
|
27
|
+
{ dbType: 'REAL', tsType: 'number' },
|
|
28
|
+
{ dbType: 'BLOB', tsType: 'Buffer' },
|
|
29
|
+
// Prisma
|
|
30
|
+
{ dbType: 'String', tsType: 'string' },
|
|
31
|
+
{ dbType: 'Int', tsType: 'number' },
|
|
32
|
+
{ dbType: 'Boolean', tsType: 'boolean' },
|
|
33
|
+
{ dbType: 'DateTime', tsType: 'Date' },
|
|
34
|
+
];
|
|
35
|
+
// ============================================================================
|
|
36
|
+
// Unified Schema Converter
|
|
37
|
+
// ============================================================================
|
|
38
|
+
export class UnifiedSchemaConverter {
|
|
39
|
+
static fromPostgres(schema) {
|
|
40
|
+
return {
|
|
41
|
+
tables: schema.tables.map((table) => ({
|
|
42
|
+
name: table.name,
|
|
43
|
+
description: table.description || undefined,
|
|
44
|
+
columns: table.columns.map((col) => ({
|
|
45
|
+
name: col.name,
|
|
46
|
+
type: col.type,
|
|
47
|
+
nullable: col.nullable,
|
|
48
|
+
default: col.default,
|
|
49
|
+
isPrimaryKey: table.primaryKey.includes(col.name),
|
|
50
|
+
isUnique: table.indexes.some((i) => i.unique && i.columns.includes(col.name)),
|
|
51
|
+
isForeignKey: table.foreignKeys.some((fk) => fk.column === col.name),
|
|
52
|
+
referencesTable: table.foreignKeys.find((fk) => fk.column === col.name)?.referencesTable,
|
|
53
|
+
referencesColumn: table.foreignKeys.find((fk) => fk.column === col.name)
|
|
54
|
+
?.referencesColumn,
|
|
55
|
+
onDelete: table.foreignKeys.find((fk) => fk.column === col.name)?.onDelete || undefined,
|
|
56
|
+
description: col.description || undefined,
|
|
57
|
+
})),
|
|
58
|
+
indexes: table.indexes.map((idx) => ({
|
|
59
|
+
name: idx.name,
|
|
60
|
+
columns: idx.columns,
|
|
61
|
+
unique: idx.unique,
|
|
62
|
+
isPrimaryKey: idx.isPrimaryKey,
|
|
63
|
+
})),
|
|
64
|
+
relations: table.foreignKeys.map((fk) => ({
|
|
65
|
+
fromTable: table.name,
|
|
66
|
+
fromColumn: fk.column,
|
|
67
|
+
toTable: fk.referencesTable,
|
|
68
|
+
toColumn: fk.referencesColumn,
|
|
69
|
+
cardinality: 'N:1',
|
|
70
|
+
onDelete: fk.onDelete || undefined,
|
|
71
|
+
})),
|
|
72
|
+
primaryKey: table.primaryKey,
|
|
73
|
+
})),
|
|
74
|
+
databaseType: 'postgresql',
|
|
75
|
+
schemaName: schema.schemaName,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
static fromMySQL(schema) {
|
|
79
|
+
return {
|
|
80
|
+
tables: schema.tables.map((table) => ({
|
|
81
|
+
name: table.name,
|
|
82
|
+
description: table.comment || undefined,
|
|
83
|
+
columns: table.columns.map((col) => ({
|
|
84
|
+
name: col.name,
|
|
85
|
+
type: col.type,
|
|
86
|
+
nullable: col.nullable,
|
|
87
|
+
default: col.default,
|
|
88
|
+
isPrimaryKey: table.primaryKey.includes(col.name),
|
|
89
|
+
isUnique: table.indexes.some((i) => i.unique && i.columns.includes(col.name)),
|
|
90
|
+
isForeignKey: table.foreignKeys.some((fk) => fk.column === col.name),
|
|
91
|
+
referencesTable: table.foreignKeys.find((fk) => fk.column === col.name)?.referencesTable,
|
|
92
|
+
referencesColumn: table.foreignKeys.find((fk) => fk.column === col.name)
|
|
93
|
+
?.referencesColumn,
|
|
94
|
+
onDelete: table.foreignKeys.find((fk) => fk.column === col.name)?.onDelete || undefined,
|
|
95
|
+
description: col.comment || undefined,
|
|
96
|
+
})),
|
|
97
|
+
indexes: table.indexes.map((idx) => ({
|
|
98
|
+
name: idx.name,
|
|
99
|
+
columns: idx.columns,
|
|
100
|
+
unique: idx.unique,
|
|
101
|
+
isPrimaryKey: idx.isPrimaryKey,
|
|
102
|
+
})),
|
|
103
|
+
relations: table.foreignKeys.map((fk) => ({
|
|
104
|
+
fromTable: table.name,
|
|
105
|
+
fromColumn: fk.column,
|
|
106
|
+
toTable: fk.referencesTable,
|
|
107
|
+
toColumn: fk.referencesColumn,
|
|
108
|
+
cardinality: 'N:1',
|
|
109
|
+
onDelete: fk.onDelete || undefined,
|
|
110
|
+
})),
|
|
111
|
+
primaryKey: table.primaryKey,
|
|
112
|
+
})),
|
|
113
|
+
databaseType: 'mysql',
|
|
114
|
+
schemaName: schema.databaseName,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
static fromSQLite(schema) {
|
|
118
|
+
return {
|
|
119
|
+
tables: schema.tables.map((table) => ({
|
|
120
|
+
name: table.name,
|
|
121
|
+
columns: table.columns.map((col) => ({
|
|
122
|
+
name: col.name,
|
|
123
|
+
type: col.type,
|
|
124
|
+
nullable: col.nullable,
|
|
125
|
+
default: col.default,
|
|
126
|
+
isPrimaryKey: table.primaryKey.includes(col.name),
|
|
127
|
+
isUnique: table.indexes.some((i) => i.unique && i.columns.includes(col.name)),
|
|
128
|
+
isForeignKey: table.foreignKeys.some((fk) => fk.from === col.name),
|
|
129
|
+
referencesTable: table.foreignKeys.find((fk) => fk.from === col.name)?.table,
|
|
130
|
+
referencesColumn: table.foreignKeys.find((fk) => fk.from === col.name)?.to,
|
|
131
|
+
onDelete: table.foreignKeys.find((fk) => fk.from === col.name)?.onDelete,
|
|
132
|
+
})),
|
|
133
|
+
indexes: table.indexes.map((idx) => ({
|
|
134
|
+
name: idx.name,
|
|
135
|
+
columns: idx.columns,
|
|
136
|
+
unique: idx.unique,
|
|
137
|
+
isPrimaryKey: idx.isPrimaryKey,
|
|
138
|
+
})),
|
|
139
|
+
relations: table.foreignKeys.map((fk) => ({
|
|
140
|
+
fromTable: table.name,
|
|
141
|
+
fromColumn: fk.from,
|
|
142
|
+
toTable: fk.table,
|
|
143
|
+
toColumn: fk.to,
|
|
144
|
+
cardinality: 'N:1',
|
|
145
|
+
onDelete: fk.onDelete || undefined,
|
|
146
|
+
})),
|
|
147
|
+
primaryKey: table.primaryKey,
|
|
148
|
+
})),
|
|
149
|
+
databaseType: 'sqlite',
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
static fromPrisma(schema) {
|
|
153
|
+
return {
|
|
154
|
+
tables: schema.tables.map((table) => ({
|
|
155
|
+
name: table.name,
|
|
156
|
+
description: table.description || undefined,
|
|
157
|
+
columns: table.columns.map((col) => ({
|
|
158
|
+
name: col.name,
|
|
159
|
+
type: col.type,
|
|
160
|
+
nullable: col.isOptional,
|
|
161
|
+
default: col.defaultValue,
|
|
162
|
+
isPrimaryKey: table.primaryKey.includes(col.name),
|
|
163
|
+
isUnique: col.isUnique,
|
|
164
|
+
isForeignKey: col.isRelation,
|
|
165
|
+
referencesTable: col.isRelation ? col.type : undefined,
|
|
166
|
+
onDelete: col.onDelete || undefined,
|
|
167
|
+
})),
|
|
168
|
+
indexes: table.indexes.map((idx) => ({
|
|
169
|
+
name: idx.name,
|
|
170
|
+
columns: idx.columns,
|
|
171
|
+
unique: idx.unique,
|
|
172
|
+
isPrimaryKey: false,
|
|
173
|
+
})),
|
|
174
|
+
relations: table.relations.map((rel) => ({
|
|
175
|
+
fromTable: rel.fromTable,
|
|
176
|
+
fromColumn: rel.fromFields[0] || '',
|
|
177
|
+
toTable: rel.toTable,
|
|
178
|
+
toColumn: rel.toFields[0] || '',
|
|
179
|
+
cardinality: rel.type === 'one-to-one'
|
|
180
|
+
? '1:1'
|
|
181
|
+
: rel.type === 'one-to-many'
|
|
182
|
+
? '1:N'
|
|
183
|
+
: rel.type === 'many-to-one'
|
|
184
|
+
? 'N:1'
|
|
185
|
+
: 'N:M',
|
|
186
|
+
onDelete: rel.onDelete || undefined,
|
|
187
|
+
})),
|
|
188
|
+
primaryKey: table.primaryKey,
|
|
189
|
+
})),
|
|
190
|
+
databaseType: 'prisma',
|
|
191
|
+
schemaName: schema.generator?.output || 'prisma',
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
static fromDrizzle(schema) {
|
|
195
|
+
return {
|
|
196
|
+
tables: schema.tables.map((table) => ({
|
|
197
|
+
name: table.name,
|
|
198
|
+
description: table.description || undefined,
|
|
199
|
+
columns: table.columns.map((col) => ({
|
|
200
|
+
name: col.name,
|
|
201
|
+
type: col.type,
|
|
202
|
+
nullable: col.isOptional,
|
|
203
|
+
default: col.defaultValue,
|
|
204
|
+
isPrimaryKey: col.isPrimaryKey,
|
|
205
|
+
isUnique: col.isUnique,
|
|
206
|
+
isForeignKey: col.isRelation,
|
|
207
|
+
referencesTable: col.relationName || undefined,
|
|
208
|
+
onDelete: col.onDelete || undefined,
|
|
209
|
+
})),
|
|
210
|
+
indexes: table.indexes.map((idx) => ({
|
|
211
|
+
name: idx.name,
|
|
212
|
+
columns: idx.columns,
|
|
213
|
+
unique: idx.unique,
|
|
214
|
+
isPrimaryKey: false,
|
|
215
|
+
})),
|
|
216
|
+
relations: table.relations.map((rel) => ({
|
|
217
|
+
fromTable: rel.fromTable,
|
|
218
|
+
fromColumn: rel.fromColumns[0] || '',
|
|
219
|
+
toTable: rel.toTable,
|
|
220
|
+
toColumn: rel.toColumns[0] || '',
|
|
221
|
+
cardinality: rel.type === 'one-to-one'
|
|
222
|
+
? '1:1'
|
|
223
|
+
: rel.type === 'one-to-many'
|
|
224
|
+
? '1:N'
|
|
225
|
+
: rel.type === 'many-to-one'
|
|
226
|
+
? 'N:1'
|
|
227
|
+
: 'N:M',
|
|
228
|
+
onDelete: rel.onDelete || undefined,
|
|
229
|
+
})),
|
|
230
|
+
primaryKey: table.primaryKey,
|
|
231
|
+
})),
|
|
232
|
+
databaseType: 'drizzle',
|
|
233
|
+
schemaName: schema.dialect,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// ============================================================================
|
|
238
|
+
// Template Generator
|
|
239
|
+
// ============================================================================
|
|
240
|
+
export class TemplateGenerator {
|
|
241
|
+
templateDir;
|
|
242
|
+
outputDir;
|
|
243
|
+
constructor(templateDir = 'templates', outputDir = '.ai') {
|
|
244
|
+
this.templateDir = templateDir;
|
|
245
|
+
this.outputDir = outputDir;
|
|
246
|
+
}
|
|
247
|
+
generate(schema) {
|
|
248
|
+
const data = this.buildTemplateData(schema);
|
|
249
|
+
const claudeMd = this.renderClaudeMd(data);
|
|
250
|
+
const agentsMd = this.renderAgentsMd(data);
|
|
251
|
+
const queries = this.generateAllQueryTemplates(schema);
|
|
252
|
+
const edgeCasesMd = this.renderEdgeCasesMd(data);
|
|
253
|
+
const constraintsMd = this.renderConstraintsMd(data);
|
|
254
|
+
const testTemplates = this.generateTestTemplates(schema);
|
|
255
|
+
const memoryPatterns = this.generateMemoryPatterns(schema);
|
|
256
|
+
const handoffTemplates = this.generateHandoffTemplates(schema);
|
|
257
|
+
const decisionTemplates = this.generateDecisionTemplates(schema);
|
|
258
|
+
return {
|
|
259
|
+
claudeMd,
|
|
260
|
+
agentsMd,
|
|
261
|
+
queries,
|
|
262
|
+
edgeCasesMd,
|
|
263
|
+
constraintsMd,
|
|
264
|
+
testTemplates,
|
|
265
|
+
memoryPatterns,
|
|
266
|
+
handoffTemplates,
|
|
267
|
+
decisionTemplates,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
async save(outputPath, schema) {
|
|
271
|
+
const { claudeMd, agentsMd, queries, edgeCasesMd, constraintsMd, testTemplates, memoryPatterns, handoffTemplates, decisionTemplates, } = this.generate(schema);
|
|
272
|
+
const fullOutputPath = path.resolve(outputPath);
|
|
273
|
+
await fs.promises.mkdir(fullOutputPath, { recursive: true });
|
|
274
|
+
await fs.promises.writeFile(path.join(fullOutputPath, 'CLAUDE.md'), claudeMd);
|
|
275
|
+
await fs.promises.writeFile(path.join(fullOutputPath, 'AGENTS.md'), agentsMd);
|
|
276
|
+
await fs.promises.writeFile(path.join(fullOutputPath, 'edge-cases.md'), edgeCasesMd);
|
|
277
|
+
await fs.promises.writeFile(path.join(fullOutputPath, 'constraints.md'), constraintsMd);
|
|
278
|
+
const queriesDir = path.join(fullOutputPath, 'queries');
|
|
279
|
+
await fs.promises.mkdir(queriesDir, { recursive: true });
|
|
280
|
+
for (const [filename, content] of Object.entries(queries)) {
|
|
281
|
+
await fs.promises.writeFile(path.join(queriesDir, filename), content);
|
|
282
|
+
}
|
|
283
|
+
// Save test templates
|
|
284
|
+
const testDir = path.join(fullOutputPath, 'test-templates');
|
|
285
|
+
await fs.promises.mkdir(testDir, { recursive: true });
|
|
286
|
+
for (const [filename, content] of Object.entries(testTemplates)) {
|
|
287
|
+
await fs.promises.writeFile(path.join(testDir, filename), content);
|
|
288
|
+
}
|
|
289
|
+
// Save memory patterns
|
|
290
|
+
const memoryDir = path.join(fullOutputPath, 'memory');
|
|
291
|
+
await fs.promises.mkdir(memoryDir, { recursive: true });
|
|
292
|
+
for (const [filename, content] of Object.entries(memoryPatterns)) {
|
|
293
|
+
await fs.promises.writeFile(path.join(memoryDir, filename), content);
|
|
294
|
+
}
|
|
295
|
+
// Save handoff templates
|
|
296
|
+
const handoffsDir = path.join(fullOutputPath, 'handoffs');
|
|
297
|
+
await fs.promises.mkdir(handoffsDir, { recursive: true });
|
|
298
|
+
for (const [filename, content] of Object.entries(handoffTemplates)) {
|
|
299
|
+
await fs.promises.writeFile(path.join(handoffsDir, filename), content);
|
|
300
|
+
}
|
|
301
|
+
// Save decision templates
|
|
302
|
+
const decisionsDir = path.join(fullOutputPath, 'decisions');
|
|
303
|
+
await fs.promises.mkdir(decisionsDir, { recursive: true });
|
|
304
|
+
for (const [filename, content] of Object.entries(decisionTemplates)) {
|
|
305
|
+
await fs.promises.writeFile(path.join(decisionsDir, filename), content);
|
|
306
|
+
}
|
|
307
|
+
// Save context templates
|
|
308
|
+
const contextDir = path.join(fullOutputPath, 'context');
|
|
309
|
+
await fs.promises.mkdir(contextDir, { recursive: true });
|
|
310
|
+
await fs.promises.writeFile(path.join(contextDir, 'SESSION_CONTEXT.json'), JSON.stringify({
|
|
311
|
+
version: '1.0.2',
|
|
312
|
+
session: {
|
|
313
|
+
id: 'sess_TEMPLATE',
|
|
314
|
+
agentId: 'agent_TEMPLATE',
|
|
315
|
+
timestamp: '2026-02-08T10:00:00Z',
|
|
316
|
+
status: 'in_progress',
|
|
317
|
+
},
|
|
318
|
+
state: {
|
|
319
|
+
phase: 'initializing',
|
|
320
|
+
progress: 0,
|
|
321
|
+
lastAction: 'Template generated',
|
|
322
|
+
nextAction: 'Define goals',
|
|
323
|
+
},
|
|
324
|
+
variables: {},
|
|
325
|
+
schema: {
|
|
326
|
+
databaseType: schema.databaseType,
|
|
327
|
+
tablesModified: schema.tables.map((t) => t.name),
|
|
328
|
+
schemaHash: 'TEMPLATE',
|
|
329
|
+
},
|
|
330
|
+
decisions: [],
|
|
331
|
+
handoffs: [],
|
|
332
|
+
errors: [],
|
|
333
|
+
}, null, 2));
|
|
334
|
+
}
|
|
335
|
+
buildTemplateData(schema) {
|
|
336
|
+
const tables = schema.tables.map((table) => ({
|
|
337
|
+
name: table.name,
|
|
338
|
+
description: table.description || '',
|
|
339
|
+
columns: table.columns.map((col) => ({
|
|
340
|
+
name: col.name,
|
|
341
|
+
type: col.type,
|
|
342
|
+
nullable: col.nullable,
|
|
343
|
+
default: col.default,
|
|
344
|
+
isKey: col.isPrimaryKey || col.isForeignKey,
|
|
345
|
+
description: col.description || '',
|
|
346
|
+
})),
|
|
347
|
+
indexes: table.indexes.map((idx) => ({
|
|
348
|
+
name: idx.name,
|
|
349
|
+
columns: idx.columns,
|
|
350
|
+
unique: idx.unique,
|
|
351
|
+
purpose: this.getIndexPurpose(idx, table),
|
|
352
|
+
})),
|
|
353
|
+
relations: table.relations.map((rel) => `- \`${rel.fromColumn}\` → \`${rel.toTable}\` (${rel.cardinality})`),
|
|
354
|
+
primaryKey: table.primaryKey.join(', ') || 'N/A',
|
|
355
|
+
foreignKeyCount: table.relations.length,
|
|
356
|
+
columnList: table.columns.map((c) => c.name + ': ' + c.type).join('\n'),
|
|
357
|
+
usagePattern: this.getUsagePattern(table),
|
|
358
|
+
}));
|
|
359
|
+
const relationships = this.buildRelationships(schema);
|
|
360
|
+
return {
|
|
361
|
+
timestamp: new Date().toISOString(),
|
|
362
|
+
databaseType: schema.databaseType,
|
|
363
|
+
schemaName: schema.schemaName || 'default',
|
|
364
|
+
source: schema.source || '',
|
|
365
|
+
tables,
|
|
366
|
+
relationships,
|
|
367
|
+
cardinalitySummary: this.getCardinalitySummary(schema),
|
|
368
|
+
businessRules: this.getBusinessRules(schema),
|
|
369
|
+
conventions: this.getConventions(schema),
|
|
370
|
+
ownershipRules: this.getOwnershipRules(schema),
|
|
371
|
+
typeMappings: TYPE_MAPPINGS,
|
|
372
|
+
performanceTips: this.getPerformanceTips(schema),
|
|
373
|
+
version: '1.0.2',
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
buildRelationships(schema) {
|
|
377
|
+
const relationships = [];
|
|
378
|
+
for (const table of schema.tables) {
|
|
379
|
+
for (const rel of table.relations) {
|
|
380
|
+
relationships.push({
|
|
381
|
+
fromTable: rel.fromTable,
|
|
382
|
+
toTable: rel.toTable,
|
|
383
|
+
cardinality: rel.cardinality,
|
|
384
|
+
onDelete: rel.onDelete,
|
|
385
|
+
description: this.getRelationshipDescription(rel),
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return relationships;
|
|
390
|
+
}
|
|
391
|
+
getCardinalitySummary(schema) {
|
|
392
|
+
const counts = { '1:1': 0, '1:N': 0, 'N:1': 0, 'N:M': 0 };
|
|
393
|
+
for (const table of schema.tables) {
|
|
394
|
+
for (const rel of table.relations) {
|
|
395
|
+
counts[rel.cardinality] = (counts[rel.cardinality] || 0) + 1;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
const parts = [];
|
|
399
|
+
if (counts['1:1'] > 0)
|
|
400
|
+
parts.push(`${counts['1:1']} one-to-one`);
|
|
401
|
+
if (counts['1:N'] > 0)
|
|
402
|
+
parts.push(`${counts['1:N']} one-to-many`);
|
|
403
|
+
if (counts['N:1'] > 0)
|
|
404
|
+
parts.push(`${counts['N:1']} many-to-one`);
|
|
405
|
+
if (counts['N:M'] > 0)
|
|
406
|
+
parts.push(`${counts['N:M']} many-to-many`);
|
|
407
|
+
return parts.length > 0 ? parts.join(', ') : 'No relationships defined';
|
|
408
|
+
}
|
|
409
|
+
getIndexPurpose(index, table) {
|
|
410
|
+
if (index.isPrimaryKey)
|
|
411
|
+
return 'Primary key constraint';
|
|
412
|
+
if (index.unique)
|
|
413
|
+
return 'Unique constraint';
|
|
414
|
+
if (index.columns.includes('organization_id'))
|
|
415
|
+
return 'Multi-tenant isolation';
|
|
416
|
+
if (index.columns.includes('deleted_at'))
|
|
417
|
+
return 'Soft delete filtering';
|
|
418
|
+
if (index.columns.includes('created_at'))
|
|
419
|
+
return 'Sorting and ordering';
|
|
420
|
+
if (index.columns.some((c) => table.relations.some((r) => r.fromColumn === c)))
|
|
421
|
+
return 'Foreign key index';
|
|
422
|
+
return 'Performance optimization';
|
|
423
|
+
}
|
|
424
|
+
getRelationshipDescription(rel) {
|
|
425
|
+
switch (rel.cardinality) {
|
|
426
|
+
case '1:1':
|
|
427
|
+
return `Each \`${rel.fromTable}\` has exactly one \`${rel.toTable}\``;
|
|
428
|
+
case '1:N':
|
|
429
|
+
return `Each \`${rel.fromTable}\` has many \`${rel.toTable}\``;
|
|
430
|
+
case 'N:1':
|
|
431
|
+
return `Many \`${rel.fromTable}\` belong to one \`${rel.toTable}\``;
|
|
432
|
+
case 'N:M':
|
|
433
|
+
return `\`${rel.fromTable}\` and \`${rel.toTable}\` have a many-to-many relationship`;
|
|
434
|
+
default:
|
|
435
|
+
return '';
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
getUsagePattern(table) {
|
|
439
|
+
if (table.name.endsWith('users') || table.name.endsWith('user')) {
|
|
440
|
+
return 'Core user table. All operations require organization_id check.';
|
|
441
|
+
}
|
|
442
|
+
if (table.name.endsWith('organizations') || table.name.endsWith('company')) {
|
|
443
|
+
return 'Root entity. No organization_id (is root).';
|
|
444
|
+
}
|
|
445
|
+
if (table.name.endsWith('sessions') || table.name.endsWith('tokens')) {
|
|
446
|
+
return 'Auth-related. Include user_id check. Soft delete recommended.';
|
|
447
|
+
}
|
|
448
|
+
if (table.relations.length === 0) {
|
|
449
|
+
return 'Standalone table. No foreign key dependencies.';
|
|
450
|
+
}
|
|
451
|
+
return 'Standard CRUD with potential organization_id isolation.';
|
|
452
|
+
}
|
|
453
|
+
getBusinessRules(schema) {
|
|
454
|
+
const rules = [
|
|
455
|
+
{
|
|
456
|
+
name: 'Organization Isolation',
|
|
457
|
+
description: 'All tables with organization_id must be filtered by it for multi-tenant security',
|
|
458
|
+
rule: 'SELECT * FROM table WHERE id = $1 AND organization_id = $2',
|
|
459
|
+
examples: [
|
|
460
|
+
'-- Correct: Include organization check',
|
|
461
|
+
'SELECT * FROM users WHERE id = $1 AND organization_id = $2;',
|
|
462
|
+
'-- Wrong: Missing organization check',
|
|
463
|
+
'SELECT * FROM users WHERE id = $1;',
|
|
464
|
+
],
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
name: 'Soft Deletes',
|
|
468
|
+
description: 'Use deleted_at for soft deletes instead of hard deletes to preserve data integrity',
|
|
469
|
+
rule: 'UPDATE table SET deleted_at = NOW() WHERE id = $1',
|
|
470
|
+
examples: [
|
|
471
|
+
'-- Soft delete (recommended)',
|
|
472
|
+
'UPDATE users SET deleted_at = NOW() WHERE id = $1;',
|
|
473
|
+
'-- Query without deleted records',
|
|
474
|
+
'SELECT * FROM users WHERE deleted_at IS NULL;',
|
|
475
|
+
],
|
|
476
|
+
},
|
|
477
|
+
];
|
|
478
|
+
const hasTimestamps = schema.tables.some((t) => t.columns.some((c) => c.name === 'created_at' || c.name === 'updated_at'));
|
|
479
|
+
if (hasTimestamps) {
|
|
480
|
+
rules.push({
|
|
481
|
+
name: 'Timestamp Conventions',
|
|
482
|
+
description: 'Use created_at for ordering, updated_at for change detection',
|
|
483
|
+
rule: 'ORDER BY created_at DESC, updated_at ASC',
|
|
484
|
+
examples: [
|
|
485
|
+
'-- Recent items first',
|
|
486
|
+
'SELECT * FROM table ORDER BY created_at DESC LIMIT 10;',
|
|
487
|
+
'-- Detect changes since last sync',
|
|
488
|
+
'SELECT * FROM table WHERE updated_at > $1;',
|
|
489
|
+
],
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
return rules;
|
|
493
|
+
}
|
|
494
|
+
getConventions(schema) {
|
|
495
|
+
const conventions = [
|
|
496
|
+
'Use parameterized queries to prevent SQL injection',
|
|
497
|
+
'Always include organization_id in WHERE clauses for multi-tenant tables',
|
|
498
|
+
'Use COALESCE for nullable column handling',
|
|
499
|
+
'Prefer ORDER BY created_at DESC for lists',
|
|
500
|
+
'Use EXPLAIN ANALYZE to optimize slow queries',
|
|
501
|
+
];
|
|
502
|
+
return conventions;
|
|
503
|
+
}
|
|
504
|
+
getOwnershipRules(schema) {
|
|
505
|
+
let rules = '- **Multi-Tenant Isolation**: All tables include `organization_id` for tenant separation\n';
|
|
506
|
+
rules += '- **Ownership Verification**: Check ownership before any write operation\n';
|
|
507
|
+
return rules;
|
|
508
|
+
}
|
|
509
|
+
getPerformanceTips(schema) {
|
|
510
|
+
const tips = [
|
|
511
|
+
'Use indexes on frequently filtered columns (organization_id, deleted_at)',
|
|
512
|
+
'Avoid SELECT *, specify needed columns for better performance',
|
|
513
|
+
'Batch inserts with bulk operations when inserting multiple rows',
|
|
514
|
+
];
|
|
515
|
+
const hasJoins = schema.tables.some((t) => t.relations.length > 0);
|
|
516
|
+
if (hasJoins) {
|
|
517
|
+
tips.push('Ensure foreign key columns are indexed for join performance');
|
|
518
|
+
tips.push('Use eager loading to avoid N+1 query problems');
|
|
519
|
+
}
|
|
520
|
+
return tips;
|
|
521
|
+
}
|
|
522
|
+
renderClaudeMd(data) {
|
|
523
|
+
let tablesSection = '';
|
|
524
|
+
for (const table of data.tables) {
|
|
525
|
+
let section = `### ${table.name}\n\n`;
|
|
526
|
+
section += '| Column | Type | Nullable | Key | Notes |\n';
|
|
527
|
+
section += '|--------|------|----------|-----|-------|\n';
|
|
528
|
+
for (const col of table.columns) {
|
|
529
|
+
const nullable = col.nullable ? 'yes' : 'no';
|
|
530
|
+
const key = col.isKey ? 'PK/FK' : '-';
|
|
531
|
+
const desc = col.description || '-';
|
|
532
|
+
section += `| \`${col.name}\` | \`${col.type}\` | ${nullable} | ${key} | ${desc} |\n`;
|
|
533
|
+
}
|
|
534
|
+
if (table.indexes.length > 0) {
|
|
535
|
+
section += '\n**Indexes:**\n';
|
|
536
|
+
for (const idx of table.indexes) {
|
|
537
|
+
const unique = idx.unique ? ' (unique)' : '';
|
|
538
|
+
section += `- \`${idx.name}\` on \`(${idx.columns.join(', ')})\`${unique} - ${idx.purpose}\n`;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
if (table.relations.length > 0) {
|
|
542
|
+
section += '\n**Foreign Keys:**\n';
|
|
543
|
+
for (const rel of table.relations) {
|
|
544
|
+
section += `${rel}\n`;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
tablesSection += section + '\n';
|
|
548
|
+
}
|
|
549
|
+
let relationshipsSection = '';
|
|
550
|
+
for (const rel of data.relationships) {
|
|
551
|
+
const onDelete = rel.onDelete ? ` (ON DELETE ${rel.onDelete})` : '';
|
|
552
|
+
relationshipsSection += `| ${rel.fromTable} | ${rel.cardinality} | ${rel.toTable} | ${onDelete} |\n`;
|
|
553
|
+
}
|
|
554
|
+
let businessRulesSection = '';
|
|
555
|
+
for (const rule of data.businessRules) {
|
|
556
|
+
businessRulesSection += `\n### ${rule.name}\n\n`;
|
|
557
|
+
businessRulesSection += `${rule.description}\n\n`;
|
|
558
|
+
businessRulesSection += `**Rule:** \n\`\`\`sql\n${rule.rule}\n\`\`\`\n`;
|
|
559
|
+
if (rule.examples.length > 0) {
|
|
560
|
+
businessRulesSection += '\n**Examples:**\n```sql\n' + rule.examples.join('\n') + '\n```\n';
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
let conventionsSection = '';
|
|
564
|
+
for (const conv of data.conventions) {
|
|
565
|
+
conventionsSection += `- ${conv}\n`;
|
|
566
|
+
}
|
|
567
|
+
// Build output with agent-aware callouts
|
|
568
|
+
let output = '# Database Context\n\n';
|
|
569
|
+
output += '> AUTO-GENERATED by cohere-db. Works with Claude, Codex, Antigravity, Xcode.\n\n';
|
|
570
|
+
// Agent-specific callouts
|
|
571
|
+
output += '> [!NOTE|CLAUDE]\n';
|
|
572
|
+
output += '> **Claude Code**: This file auto-loads.\n';
|
|
573
|
+
output += '> ```bash\n';
|
|
574
|
+
output += '> claude db:validate # Verify queries\n';
|
|
575
|
+
output += '> claude test:run # Run tests\n';
|
|
576
|
+
output += '> ```\n\n';
|
|
577
|
+
output += '> [!NOTE|CODEX]\n';
|
|
578
|
+
output += '> **Codex**: You MUST explicitly read this file:\n';
|
|
579
|
+
output += '> ```\n';
|
|
580
|
+
output += '> Read the CLAUDE.md file in this directory.\n';
|
|
581
|
+
output += '> ```\n';
|
|
582
|
+
output += '> Codex does NOT auto-load context files.\n\n';
|
|
583
|
+
output += '> [!NOTE|ANTIGRAVITY]\n';
|
|
584
|
+
output += '> **Antigravity**: Use with MCP. Coordinate via `.ai/memory/`.\n\n';
|
|
585
|
+
output += '## Overview\n\n';
|
|
586
|
+
output += `**Database Type:** ${data.databaseType}\n`;
|
|
587
|
+
output += `**Schema:** ${data.schemaName}\n`;
|
|
588
|
+
output += `**Tables:** ${data.tables.length}\n\n`;
|
|
589
|
+
output += '## Tables\n\n';
|
|
590
|
+
output += tablesSection;
|
|
591
|
+
output += '## Relationships\n\n';
|
|
592
|
+
output += '| From | Cardinality | To | Actions |\n';
|
|
593
|
+
output += '|------|-------------|-----|--------|\n';
|
|
594
|
+
output += relationshipsSection;
|
|
595
|
+
output += '\n> [!NOTE|ALL]\n';
|
|
596
|
+
output += '> **ALL AGENTS**: Foreign key columns MUST be indexed.\n\n';
|
|
597
|
+
output += '## Business Rules\n\n';
|
|
598
|
+
output += businessRulesSection;
|
|
599
|
+
output += '## Conventions\n\n';
|
|
600
|
+
output += conventionsSection;
|
|
601
|
+
output += '\n> [!NOTE|ALL]\n';
|
|
602
|
+
output += '> **ALL AGENTS**: Parameterized queries required. Never interpolate strings.\n\n';
|
|
603
|
+
output += '## Multi-Tenant Isolation\n\n';
|
|
604
|
+
output += data.ownershipRules + '\n\n';
|
|
605
|
+
output += '## Additional Resources\n\n';
|
|
606
|
+
output += '| Resource | Purpose |\n';
|
|
607
|
+
output += '|----------|----------|\n';
|
|
608
|
+
output += '| `.ai/edge-cases.md` | Edge case patterns |\n';
|
|
609
|
+
output += '| `.ai/constraints.md` | Query limits |\n';
|
|
610
|
+
output += '| `.ai/test-templates/` | Edge case tests |\n';
|
|
611
|
+
output += '| `.ai/memory/` | Checkpoint patterns |\n';
|
|
612
|
+
return output;
|
|
613
|
+
}
|
|
614
|
+
renderAgentsMd(data) {
|
|
615
|
+
let tableQuickRef = '';
|
|
616
|
+
for (const table of data.tables) {
|
|
617
|
+
const pk = table.primaryKey || 'N/A';
|
|
618
|
+
const sampleCols = table.columns
|
|
619
|
+
.slice(0, 3)
|
|
620
|
+
.map((c) => c.name)
|
|
621
|
+
.join(', ');
|
|
622
|
+
tableQuickRef += `| [${table.name}](#${table.name}) | ${pk} | ${table.foreignKeyCount} | ${sampleCols}... |\n`;
|
|
623
|
+
}
|
|
624
|
+
let tableDetails = '';
|
|
625
|
+
for (const table of data.tables) {
|
|
626
|
+
tableDetails += `\n### ${table.name}\n\n`;
|
|
627
|
+
tableDetails += `**Description:** ${table.description || 'No description available'}\n\n`;
|
|
628
|
+
tableDetails += '**Columns:**\n```\n';
|
|
629
|
+
tableDetails += table.columnList + '\n';
|
|
630
|
+
tableDetails += '```\n\n';
|
|
631
|
+
if (table.relations.length > 0) {
|
|
632
|
+
tableDetails += '**Relationships:**\n';
|
|
633
|
+
tableDetails += table.relations.join('\n') + '\n\n';
|
|
634
|
+
}
|
|
635
|
+
tableDetails += `**Usage Pattern:** ${table.usagePattern}\n`;
|
|
636
|
+
}
|
|
637
|
+
let cardinalitySection = '';
|
|
638
|
+
cardinalitySection += `**Relationship Summary:** ${data.cardinalitySummary}\n\n`;
|
|
639
|
+
cardinalitySection += '| Type | Description |\n';
|
|
640
|
+
cardinalitySection += '|------|-------------|\n';
|
|
641
|
+
cardinalitySection += '| 1:1 | One-to-one relationship |\n';
|
|
642
|
+
cardinalitySection += '| 1:N | One-to-many relationship |\n';
|
|
643
|
+
cardinalitySection += '| N:1 | Many-to-one relationship |\n';
|
|
644
|
+
cardinalitySection += '| N:M | Many-to-many relationship |\n';
|
|
645
|
+
let typeMappingsSection = '';
|
|
646
|
+
for (const tm of data.typeMappings) {
|
|
647
|
+
typeMappingsSection += `| \`${tm.dbType}\` | \`${tm.tsType}\` ${tm.notes ? `(${tm.notes})` : ''} |\n`;
|
|
648
|
+
}
|
|
649
|
+
// Build output with agent-aware callouts
|
|
650
|
+
let output = '# Database Context for AI Assistants\n\n';
|
|
651
|
+
output += '> AUTO-GENERATED by cohere. Core content works for all agents.\n\n';
|
|
652
|
+
output += '## Quick Reference\n\n';
|
|
653
|
+
output += '| Table | Primary Key | Foreign Keys | Sample Columns |\n';
|
|
654
|
+
output += '|-------|-------------|--------------|----------------|\n';
|
|
655
|
+
output += tableQuickRef;
|
|
656
|
+
output += cardinalitySection;
|
|
657
|
+
output += '\n## Table Details\n\n';
|
|
658
|
+
output += tableDetails;
|
|
659
|
+
output += '\n## Type Mappings\n\n';
|
|
660
|
+
output += '| Database Type | TypeScript Type | Notes |\n';
|
|
661
|
+
output += '|---------------|-----------------|-------|\n';
|
|
662
|
+
output += typeMappingsSection;
|
|
663
|
+
output += '\n## Performance Tips\n\n';
|
|
664
|
+
for (const tip of data.performanceTips) {
|
|
665
|
+
output += `- ${tip}\n`;
|
|
666
|
+
}
|
|
667
|
+
// Add agent-specific warnings
|
|
668
|
+
output += '\n## Platform Warnings\n\n';
|
|
669
|
+
output += '> [!WARNING|CODEX]\n';
|
|
670
|
+
output += '> **Codex**: Add to AGENTS.md:\n';
|
|
671
|
+
output += '> ```\n';
|
|
672
|
+
output += '> All queries MUST include organization_id filter.\n';
|
|
673
|
+
output += '> Pattern: SELECT ... WHERE id = $1 AND organization_id = $2\n';
|
|
674
|
+
output += '> ```\n\n';
|
|
675
|
+
output += '> [!WARNING|ANTIGRAVITY]\n';
|
|
676
|
+
output +=
|
|
677
|
+
'> **Antigravity**: Parallel agents share context. Use `.ai/memory/checkpoints/`.\n\n';
|
|
678
|
+
output += '> [!WARNING|ALL]\n';
|
|
679
|
+
output += '> **ALL AGENTS**: Checkpoint every 5-10 minutes to prevent context loss.\n';
|
|
680
|
+
return output;
|
|
681
|
+
}
|
|
682
|
+
generateAllQueryTemplates(schema) {
|
|
683
|
+
const queries = {};
|
|
684
|
+
for (const table of schema.tables) {
|
|
685
|
+
queries[`${table.name}.sql`] = this.generateQueryTemplate(table);
|
|
686
|
+
}
|
|
687
|
+
queries['transactions.sql'] = this.generateTransactionPatterns();
|
|
688
|
+
return queries;
|
|
689
|
+
}
|
|
690
|
+
generateQueryTemplate(table) {
|
|
691
|
+
const pk = table.primaryKey[0] || 'id';
|
|
692
|
+
const columns = table.columns.map((c) => c.name);
|
|
693
|
+
const hasOrgId = table.columns.some((c) => c.name === 'organization_id');
|
|
694
|
+
const hasDeletedAt = table.columns.some((c) => c.name === 'deleted_at');
|
|
695
|
+
const nonDefaultCols = columns.filter((c) => c !== 'created_at' && c !== 'updated_at');
|
|
696
|
+
let output = `-- ${table.name} queries\n`;
|
|
697
|
+
output += `-- AUTO-GENERATED by cohere\n\n`;
|
|
698
|
+
// READ operations
|
|
699
|
+
output += `-- === READ ===\n\n`;
|
|
700
|
+
output += `-- Get by ${pk}\n`;
|
|
701
|
+
output += `SELECT * FROM ${table.name} WHERE ${pk} = $1;\n\n`;
|
|
702
|
+
if (hasOrgId) {
|
|
703
|
+
output += `-- Get with organization check\n`;
|
|
704
|
+
output += `SELECT * FROM ${table.name} WHERE ${pk} = $1 AND organization_id = $2;\n\n`;
|
|
705
|
+
}
|
|
706
|
+
output += `-- List with pagination\n`;
|
|
707
|
+
output += `SELECT * FROM ${table.name}\n`;
|
|
708
|
+
if (hasOrgId) {
|
|
709
|
+
output += `WHERE organization_id = $1\n`;
|
|
710
|
+
}
|
|
711
|
+
output += `ORDER BY created_at DESC\n`;
|
|
712
|
+
output += `LIMIT $2 OFFSET $3;\n\n`;
|
|
713
|
+
if (hasDeletedAt) {
|
|
714
|
+
output += `-- List without deleted records\n`;
|
|
715
|
+
output += `SELECT * FROM ${table.name}\n`;
|
|
716
|
+
if (hasOrgId) {
|
|
717
|
+
output += `WHERE organization_id = $1\n`;
|
|
718
|
+
}
|
|
719
|
+
output += `AND deleted_at IS NULL\n`;
|
|
720
|
+
output += `ORDER BY created_at DESC;\n\n`;
|
|
721
|
+
}
|
|
722
|
+
// CREATE operations
|
|
723
|
+
output += `-- === CREATE ===\n\n`;
|
|
724
|
+
output += `-- Insert single record\n`;
|
|
725
|
+
output += `INSERT INTO ${table.name} (${nonDefaultCols.join(', ')})\n`;
|
|
726
|
+
output += `VALUES (${nonDefaultCols.map((_, i) => '$' + (i + 1)).join(', ')});\n`;
|
|
727
|
+
return output;
|
|
728
|
+
}
|
|
729
|
+
generateTransactionPatterns() {
|
|
730
|
+
return `-- Transaction Examples
|
|
731
|
+
-- Use transactions for atomic operations
|
|
732
|
+
|
|
733
|
+
BEGIN TRANSACTION;
|
|
734
|
+
|
|
735
|
+
-- 1. Create parent record
|
|
736
|
+
INSERT INTO orders (user_id, total, status)
|
|
737
|
+
VALUES ($1, $2, 'pending')
|
|
738
|
+
RETURNING id;
|
|
739
|
+
|
|
740
|
+
-- 2. Create child records
|
|
741
|
+
INSERT INTO order_items (order_id, product_id, quantity, price)
|
|
742
|
+
VALUES
|
|
743
|
+
($3, $4, $5, $6),
|
|
744
|
+
($3, $7, $8, $9);
|
|
745
|
+
|
|
746
|
+
-- 3. Update inventory
|
|
747
|
+
UPDATE products SET stock = stock - $5 WHERE id = $4;
|
|
748
|
+
UPDATE products SET stock = stock - $8 WHERE id = $7;
|
|
749
|
+
|
|
750
|
+
COMMIT;
|
|
751
|
+
`;
|
|
752
|
+
}
|
|
753
|
+
// ============================================================================
|
|
754
|
+
// Edge Cases Generation
|
|
755
|
+
// ============================================================================
|
|
756
|
+
renderEdgeCasesMd(data) {
|
|
757
|
+
let output = '# Edge Cases Handling Guide\n\n';
|
|
758
|
+
output += `> AUTO-GENERATED by cohere. Last updated: ${data.timestamp}\n\n`;
|
|
759
|
+
// Session Restart Amnesia
|
|
760
|
+
output += '## 1. Session Restart Amnesia\n\n';
|
|
761
|
+
output += '### Checkpoint Pattern\n';
|
|
762
|
+
output += '```typescript\n';
|
|
763
|
+
output += `// Checkpoint file: .ai/memory/checkpoint-{timestamp}.json\n`;
|
|
764
|
+
output += 'interface AgentCheckpoint {\n';
|
|
765
|
+
output += ' timestamp: string;\n';
|
|
766
|
+
output += ' sessionId: string;\n';
|
|
767
|
+
output += ' schemaVersion: string;\n';
|
|
768
|
+
output += ' currentTask?: TaskContext;\n';
|
|
769
|
+
output += ' discoveredPatterns: DiscoveredPattern[];\n';
|
|
770
|
+
output += '}\n';
|
|
771
|
+
output += '```\n\n';
|
|
772
|
+
// Tool Misalignment
|
|
773
|
+
output += '## 2. Tool Misalignment\n\n';
|
|
774
|
+
output += '### Type Constraint Warnings\n';
|
|
775
|
+
output += '```sql\n';
|
|
776
|
+
output += '-- Verify column types before queries\n';
|
|
777
|
+
output += 'SELECT column_name, data_type\n';
|
|
778
|
+
output += 'FROM information_schema.columns\n';
|
|
779
|
+
output += `WHERE table_name = '${data.tables[0]?.name || 'table_name'}';\n`;
|
|
780
|
+
output += '```\n\n';
|
|
781
|
+
// Infinite Context Exploration
|
|
782
|
+
output += '## 3. Infinite Context Exploration\n\n';
|
|
783
|
+
output += '### Query Limits\n';
|
|
784
|
+
output += '| Operation | Limit | Reason |\n';
|
|
785
|
+
output += '|-----------|-------|--------|\n';
|
|
786
|
+
output += '| SELECT rows | 10,000 max | Prevent context exhaustion |\n';
|
|
787
|
+
output += '| JOIN depth | 5 max | Query complexity |\n';
|
|
788
|
+
output += '| Query timeout | 30s | Resource sharing |\n\n';
|
|
789
|
+
// Verification Gaps
|
|
790
|
+
output += '## 4. Verification Gaps\n\n';
|
|
791
|
+
output += '### Duplicate Detection\n';
|
|
792
|
+
output += '```sql\n';
|
|
793
|
+
output += '-- Find potential duplicate records\n';
|
|
794
|
+
output += 'SELECT email, COUNT(*) as cnt\n';
|
|
795
|
+
output += `FROM ${data.tables[0]?.name || 'users'}\n`;
|
|
796
|
+
output += 'GROUP BY email\n';
|
|
797
|
+
output += 'HAVING COUNT(*) > 1;\n';
|
|
798
|
+
output += '```\n\n';
|
|
799
|
+
// Database-specific tables
|
|
800
|
+
output += '## 5. Schema-Specific Edge Cases\n\n';
|
|
801
|
+
for (const table of data.tables) {
|
|
802
|
+
const hasOrgId = table.columns.some((c) => c.name === 'organization_id');
|
|
803
|
+
const hasDeletedAt = table.columns.some((c) => c.name === 'deleted_at');
|
|
804
|
+
if (hasOrgId || hasDeletedAt) {
|
|
805
|
+
output += `### ${table.name}\n\n`;
|
|
806
|
+
if (hasOrgId) {
|
|
807
|
+
output +=
|
|
808
|
+
'- **Organization isolation required**: All queries must include `organization_id` check\n';
|
|
809
|
+
}
|
|
810
|
+
if (hasDeletedAt) {
|
|
811
|
+
output +=
|
|
812
|
+
'- **Soft delete pattern**: Use `deleted_at IS NULL` to exclude deleted records\n';
|
|
813
|
+
}
|
|
814
|
+
output += '\n';
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
return output;
|
|
818
|
+
}
|
|
819
|
+
renderConstraintsMd(data) {
|
|
820
|
+
let output = '# Query Constraints & Limits\n\n';
|
|
821
|
+
output += `> AUTO-GENERATED by cohere. Last updated: ${data.timestamp}\n\n`;
|
|
822
|
+
output += '## Query Limits\n\n';
|
|
823
|
+
output += '| Operation | Limit | Reason |\n';
|
|
824
|
+
output += '|-----------|-------|--------|\n';
|
|
825
|
+
output += '| SELECT rows | 10,000 max | Prevent context exhaustion |\n';
|
|
826
|
+
output += '| SELECT with OFFSET | 1,000 max | Performance degradation |\n';
|
|
827
|
+
output += '| JOIN depth | 5 max | Query complexity |\n';
|
|
828
|
+
output += '| INSERT rows (batch) | 1,000 max | Transaction size |\n';
|
|
829
|
+
output += '| Query timeout | 30s | Resource sharing |\n\n';
|
|
830
|
+
output += '## Required Patterns\n\n';
|
|
831
|
+
output += '### LIMIT Required\n';
|
|
832
|
+
output += '```sql\n';
|
|
833
|
+
output += '-- ✓ CORRECT: Always use LIMIT\n';
|
|
834
|
+
output += `SELECT * FROM ${data.tables[0]?.name || 'table'} LIMIT 100;\n`;
|
|
835
|
+
output += '-- ✗ WRONG: No LIMIT on potential large tables\n';
|
|
836
|
+
output += `SELECT * FROM ${data.tables[0]?.name || 'table'};\n`;
|
|
837
|
+
output += '```\n\n';
|
|
838
|
+
// Check if tables have organization_id
|
|
839
|
+
const hasOrgIsolation = data.tables.some((t) => t.columns.some((c) => c.name === 'organization_id'));
|
|
840
|
+
if (hasOrgIsolation) {
|
|
841
|
+
output += '### Organization Isolation Required\n';
|
|
842
|
+
output += '```sql\n';
|
|
843
|
+
output += '-- ✓ CORRECT: Organization check included\n';
|
|
844
|
+
output += `SELECT * FROM ${data.tables[0]?.name || 'users'} WHERE organization_id = $1;\n`;
|
|
845
|
+
output += '-- ✗ WRONG: Missing organization_id (security risk!)\n';
|
|
846
|
+
output += `SELECT * FROM ${data.tables[0]?.name || 'users'};\n`;
|
|
847
|
+
output += '```\n\n';
|
|
848
|
+
}
|
|
849
|
+
output += '## Pagination Patterns\n\n';
|
|
850
|
+
output += '### Keyset Pagination (Recommended)\n';
|
|
851
|
+
output += '```typescript\n';
|
|
852
|
+
output += 'async function getNextPage(lastId: string, limit: number = 50) {\n';
|
|
853
|
+
output += ` return query(\`SELECT * FROM ${data.tables[0]?.name || 'items'} WHERE id > $1 ORDER BY id LIMIT $2\`, [lastId, limit]);\n`;
|
|
854
|
+
output += '}\n';
|
|
855
|
+
output += '```\n\n';
|
|
856
|
+
output += '## Context Guardrails\n\n';
|
|
857
|
+
output += '```typescript\n';
|
|
858
|
+
output += 'const QUERY_CONSTRAINTS = {\n';
|
|
859
|
+
output += ' maxRows: 1000,\n';
|
|
860
|
+
output += ' maxDepth: 5,\n';
|
|
861
|
+
output += ' timeoutMs: 30000,\n';
|
|
862
|
+
output += ' requireOrderBy: true,\n';
|
|
863
|
+
output += ' requireLimit: true,\n';
|
|
864
|
+
output += '};\n';
|
|
865
|
+
output += '```\n\n';
|
|
866
|
+
return output;
|
|
867
|
+
}
|
|
868
|
+
generateTestTemplates(schema) {
|
|
869
|
+
const templates = {};
|
|
870
|
+
// Main edge cases test file
|
|
871
|
+
templates['edge-cases.test.ts'] = this.generateEdgeCasesTest(schema);
|
|
872
|
+
// CRUD edge cases
|
|
873
|
+
const crudTemplates = this.generateCRUDEdgeCases(schema);
|
|
874
|
+
for (const [name, content] of Object.entries(crudTemplates)) {
|
|
875
|
+
templates[name] = content;
|
|
876
|
+
}
|
|
877
|
+
return templates;
|
|
878
|
+
}
|
|
879
|
+
generateEdgeCasesTest(schema) {
|
|
880
|
+
const tableName = schema.tables[0]?.name || 'table_name';
|
|
881
|
+
const pk = schema.tables[0]?.primaryKey || 'id';
|
|
882
|
+
return `/**
|
|
883
|
+
* Edge Case Tests for ${tableName}
|
|
884
|
+
* AUTO-GENERATED by cohere
|
|
885
|
+
*/
|
|
886
|
+
|
|
887
|
+
describe('${tableName} Edge Cases', () => {
|
|
888
|
+
const orgId = 'test-org-id';
|
|
889
|
+
|
|
890
|
+
describe('Empty Result Sets', () => {
|
|
891
|
+
test('SELECT returns empty array when no rows match', async () => {
|
|
892
|
+
const result = await query(
|
|
893
|
+
'SELECT * FROM ${tableName} WHERE id = $1 AND organization_id = $2',
|
|
894
|
+
['nonexistent-id', orgId]
|
|
895
|
+
);
|
|
896
|
+
expect(result.rows).toEqual([]);
|
|
897
|
+
});
|
|
898
|
+
});
|
|
899
|
+
|
|
900
|
+
describe('Primary Key Edge Cases', () => {
|
|
901
|
+
test('INSERT fails on duplicate primary key', async () => {
|
|
902
|
+
await expect(query(
|
|
903
|
+
'INSERT INTO ${tableName} (id) VALUES ($1)',
|
|
904
|
+
['existing-id']
|
|
905
|
+
)).rejects.toThrow('duplicate_key');
|
|
906
|
+
});
|
|
907
|
+
|
|
908
|
+
test('UPDATE modifies correct row', async () => {
|
|
909
|
+
const result = await query(
|
|
910
|
+
'UPDATE ${tableName} SET updated_at = NOW() WHERE id = $1 AND organization_id = $2 RETURNING id',
|
|
911
|
+
['target-id', orgId]
|
|
912
|
+
);
|
|
913
|
+
expect(result.rowCount).toBe(1);
|
|
914
|
+
});
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
describe('Soft Delete Edge Cases', () => {
|
|
918
|
+
test('SELECT excludes soft-deleted records by default', async () => {
|
|
919
|
+
const result = await query(
|
|
920
|
+
'SELECT * FROM ${tableName} WHERE organization_id = $1',
|
|
921
|
+
[orgId]
|
|
922
|
+
);
|
|
923
|
+
expect(result.rows.every(r => r.deleted_at === null)).toBe(true);
|
|
924
|
+
});
|
|
925
|
+
});
|
|
926
|
+
});
|
|
927
|
+
`;
|
|
928
|
+
}
|
|
929
|
+
generateCRUDEdgeCases(schema) {
|
|
930
|
+
const templates = {};
|
|
931
|
+
for (const table of schema.tables) {
|
|
932
|
+
const testContent = this.generateSingleTableTests(table);
|
|
933
|
+
templates[table.name + '.test.ts'] = testContent;
|
|
934
|
+
}
|
|
935
|
+
return templates;
|
|
936
|
+
}
|
|
937
|
+
generateSingleTableTests(table) {
|
|
938
|
+
const orgIdLine = table.columns.some((c) => c.name === 'organization_id')
|
|
939
|
+
? " const orgId = 'test-org-id';"
|
|
940
|
+
: ' // No organization_id column in this table';
|
|
941
|
+
const softDeleteTest = table.columns.some((c) => c.name === 'deleted_at')
|
|
942
|
+
? `
|
|
943
|
+
test('Soft-deleted records are excluded by default', async () => {
|
|
944
|
+
const result = await query('SELECT * FROM ' + table.name);
|
|
945
|
+
expect(result.rows.every(r => r.deleted_at === null)).toBe(true);
|
|
946
|
+
});`
|
|
947
|
+
: '';
|
|
948
|
+
const updateTest = table.primaryKey.length > 0
|
|
949
|
+
? `
|
|
950
|
+
test('UPDATE with valid ' + table.primaryKey[0] + ' succeeds', async () => {
|
|
951
|
+
const result = await query(
|
|
952
|
+
'UPDATE ' + table.name + ' SET updated_at = NOW() WHERE id = $1 RETURNING id',
|
|
953
|
+
['test-id']
|
|
954
|
+
);
|
|
955
|
+
expect(result.rowCount).toBeGreaterThanOrEqual(0);
|
|
956
|
+
});`
|
|
957
|
+
: '';
|
|
958
|
+
return ('/**\n' +
|
|
959
|
+
' * ' +
|
|
960
|
+
table.name +
|
|
961
|
+
' Edge Case Tests\n' +
|
|
962
|
+
' * AUTO-GENERATED by cohere\n' +
|
|
963
|
+
' */\n\n' +
|
|
964
|
+
"describe('" +
|
|
965
|
+
table.name +
|
|
966
|
+
" Edge Cases', () => {\n" +
|
|
967
|
+
orgIdLine +
|
|
968
|
+
softDeleteTest +
|
|
969
|
+
updateTest +
|
|
970
|
+
'\n});\n');
|
|
971
|
+
}
|
|
972
|
+
// ============================================================================
|
|
973
|
+
// Memory Patterns Generation
|
|
974
|
+
// ============================================================================
|
|
975
|
+
generateMemoryPatterns(schema) {
|
|
976
|
+
const patterns = {};
|
|
977
|
+
patterns['checkpoint-patterns.md'] = this.generateCheckpointPatterns(schema);
|
|
978
|
+
patterns['session-template.md'] = this.generateSessionTemplate(schema);
|
|
979
|
+
patterns['accumulated-learnings.md'] = this.generateEmptyLearnings();
|
|
980
|
+
return patterns;
|
|
981
|
+
}
|
|
982
|
+
generateCheckpointPatterns(schema) {
|
|
983
|
+
const tablesList = schema.tables
|
|
984
|
+
.map((t) => '- **' +
|
|
985
|
+
t.name +
|
|
986
|
+
'**: ' +
|
|
987
|
+
t.columns.length +
|
|
988
|
+
' columns, ' +
|
|
989
|
+
t.relations.length +
|
|
990
|
+
' relationships')
|
|
991
|
+
.join('\n');
|
|
992
|
+
return ('# Memory & Checkpoint Patterns\n\n' +
|
|
993
|
+
'> AUTO-GENERATED by cohere-db. Pattern version: 1.0.2\n\n' +
|
|
994
|
+
'## Directory Structure\n\n' +
|
|
995
|
+
'```\n' +
|
|
996
|
+
'.ai/\n' +
|
|
997
|
+
'├── memory/\n' +
|
|
998
|
+
'│ ├── checkpoints/ # Session checkpoints\n' +
|
|
999
|
+
'│ │ └── checkpoint-{timestamp}.json\n' +
|
|
1000
|
+
'│ ├── accumulated-learnings.md # Cross-session knowledge\n' +
|
|
1001
|
+
'│ ├── edge-cases.md # Documented edge cases\n' +
|
|
1002
|
+
'│ └── session-history.md # Recent session notes\n' +
|
|
1003
|
+
'```\n\n' +
|
|
1004
|
+
'## Checkpoint Interface\n\n' +
|
|
1005
|
+
'```typescript\n' +
|
|
1006
|
+
'interface AgentCheckpoint {\n' +
|
|
1007
|
+
' timestamp: string;\n' +
|
|
1008
|
+
' sessionId: string;\n' +
|
|
1009
|
+
' schemaVersion: string;\n' +
|
|
1010
|
+
' schemaHash: string; // ' +
|
|
1011
|
+
schema.tables.length +
|
|
1012
|
+
' tables in current schema\n' +
|
|
1013
|
+
' currentTask?: TaskContext;\n' +
|
|
1014
|
+
' discoveredPatterns: DiscoveredPattern[];\n' +
|
|
1015
|
+
' pendingQueries: PendingQuery[];\n' +
|
|
1016
|
+
' agentMemory: Record<string, unknown>;\n' +
|
|
1017
|
+
'}\n\n' +
|
|
1018
|
+
'interface TaskContext {\n' +
|
|
1019
|
+
' taskId: string;\n' +
|
|
1020
|
+
" status: 'in_progress' | 'paused' | 'completed';\n" +
|
|
1021
|
+
' lastAction?: string;\n' +
|
|
1022
|
+
' progress: number;\n' +
|
|
1023
|
+
'}\n' +
|
|
1024
|
+
'```\n\n' +
|
|
1025
|
+
'## Tables in Schema\n' +
|
|
1026
|
+
tablesList +
|
|
1027
|
+
'\n\n' +
|
|
1028
|
+
'## Usage\n\n' +
|
|
1029
|
+
'```typescript\n' +
|
|
1030
|
+
"import { saveCheckpoint, restoreLatestCheckpoint } from './memory/checkpoint';\n\n" +
|
|
1031
|
+
"const checkpoint = await restoreLatestCheckpoint('.ai');\n" +
|
|
1032
|
+
'if (checkpoint) {\n' +
|
|
1033
|
+
" console.log('Resuming from:', checkpoint.timestamp);\n" +
|
|
1034
|
+
'}\n' +
|
|
1035
|
+
'// ... perform work ...\n' +
|
|
1036
|
+
"await saveCheckpoint('.ai', newCheckpoint);\n" +
|
|
1037
|
+
'```\n');
|
|
1038
|
+
}
|
|
1039
|
+
generateSessionTemplate(schema) {
|
|
1040
|
+
const tablesList = schema.tables.map((t) => '- ' + t.name).join('\n');
|
|
1041
|
+
return ('# Session Template\n\n' +
|
|
1042
|
+
'> AUTO-GENERATED by cohere\n\n' +
|
|
1043
|
+
'## Current Session\n\n' +
|
|
1044
|
+
'**Session ID:** ${SESSION_ID}\n' +
|
|
1045
|
+
'**Start Time:** ${START_TIME}\n' +
|
|
1046
|
+
'**Schema:** ' +
|
|
1047
|
+
schema.databaseType +
|
|
1048
|
+
'\n\n' +
|
|
1049
|
+
'## Tables in Scope\n' +
|
|
1050
|
+
tablesList +
|
|
1051
|
+
'\n\n' +
|
|
1052
|
+
'## Goals\n' +
|
|
1053
|
+
'- \n\n' +
|
|
1054
|
+
'## Progress\n' +
|
|
1055
|
+
'- [ ] \n\n' +
|
|
1056
|
+
'## Notes\n' +
|
|
1057
|
+
'- \n\n' +
|
|
1058
|
+
'---\n' +
|
|
1059
|
+
'*Delete this header and fill in your session details*\n');
|
|
1060
|
+
}
|
|
1061
|
+
generateEmptyLearnings() {
|
|
1062
|
+
return ('# Accumulated Learnings\n\n' +
|
|
1063
|
+
'> AUTO-GENERATED by cohere\n\n' +
|
|
1064
|
+
'Cross-session knowledge and patterns discovered during agent work.\n\n' +
|
|
1065
|
+
'## Getting Started\n\n' +
|
|
1066
|
+
'Copy patterns discovered across sessions here:\n\n' +
|
|
1067
|
+
'```markdown\n' +
|
|
1068
|
+
'## YYYY-MM-DD\n\n' +
|
|
1069
|
+
'**Category:** query|schema|relationship|edge-case\n\n' +
|
|
1070
|
+
'**Description:** \n\n' +
|
|
1071
|
+
'**Example:**\n' +
|
|
1072
|
+
'```sql\n' +
|
|
1073
|
+
'-- Your SQL example here\n' +
|
|
1074
|
+
'```\n\n' +
|
|
1075
|
+
'**Usage Count:** N\n' +
|
|
1076
|
+
'```\n\n' +
|
|
1077
|
+
'---\n' +
|
|
1078
|
+
'*This file is updated automatically by checkpoint patterns*\n');
|
|
1079
|
+
}
|
|
1080
|
+
// ============================================================================
|
|
1081
|
+
// Handoff Templates Generation
|
|
1082
|
+
// ============================================================================
|
|
1083
|
+
generateHandoffTemplates(schema) {
|
|
1084
|
+
const templates = {};
|
|
1085
|
+
templates['handoff-template.md'] = this.generateHandoffTemplate(schema);
|
|
1086
|
+
templates['MULTI_AGENT_PROTOCOL.md'] = this.generateMultiAgentProtocol(schema);
|
|
1087
|
+
return templates;
|
|
1088
|
+
}
|
|
1089
|
+
generateHandoffTemplate(schema) {
|
|
1090
|
+
const tablesList = schema.tables.map((t) => `- \`${t.name}\``).join('\n');
|
|
1091
|
+
return `# Agent Handoff Record
|
|
1092
|
+
|
|
1093
|
+
> AUTO-GENERATED by cohere. Read this before continuing.
|
|
1094
|
+
|
|
1095
|
+
## Session Info
|
|
1096
|
+
|
|
1097
|
+
| Field | Value |
|
|
1098
|
+
|-------|-------|
|
|
1099
|
+
| **Agent ID** | \`{agentId}\` |
|
|
1100
|
+
| **Session ID** | \`{sessionId}\` |
|
|
1101
|
+
| **Timestamp** | \`{timestamp}\` |
|
|
1102
|
+
| **Duration** | \`{duration}\` |
|
|
1103
|
+
| **Status** | \`{status}\` |
|
|
1104
|
+
|
|
1105
|
+
---
|
|
1106
|
+
|
|
1107
|
+
## What Was Attempted
|
|
1108
|
+
|
|
1109
|
+
### Goals
|
|
1110
|
+
- Goal 1
|
|
1111
|
+
- Goal 2
|
|
1112
|
+
- Goal 3
|
|
1113
|
+
|
|
1114
|
+
### Actions Taken
|
|
1115
|
+
|
|
1116
|
+
| Step | Action | Result |
|
|
1117
|
+
|------|--------|--------|
|
|
1118
|
+
| 1 | Action description | ✅ Success / ❌ Failed |
|
|
1119
|
+
| 2 | Action description | ✅ Success / ❌ Failed |
|
|
1120
|
+
| 3 | Action description | ⏸️ Paused |
|
|
1121
|
+
|
|
1122
|
+
---
|
|
1123
|
+
|
|
1124
|
+
## What Succeeded
|
|
1125
|
+
|
|
1126
|
+
- ✅ Success item 1
|
|
1127
|
+
- ✅ Success item 2
|
|
1128
|
+
|
|
1129
|
+
---
|
|
1130
|
+
|
|
1131
|
+
## What Failed
|
|
1132
|
+
|
|
1133
|
+
- ❌ Failed item 1
|
|
1134
|
+
- ❌ Failed item 2 with error: \`{error}\`
|
|
1135
|
+
|
|
1136
|
+
### Error Details
|
|
1137
|
+
\`\`\`
|
|
1138
|
+
{errorStackTrace}
|
|
1139
|
+
\`\`\`
|
|
1140
|
+
|
|
1141
|
+
### Recovery Attempts
|
|
1142
|
+
1. Attempt 1: Result
|
|
1143
|
+
2. Attempt 2: Result
|
|
1144
|
+
|
|
1145
|
+
---
|
|
1146
|
+
|
|
1147
|
+
## Decisions Made
|
|
1148
|
+
|
|
1149
|
+
| Decision ID | Choice | Rationale |
|
|
1150
|
+
|-------------|--------|-----------|
|
|
1151
|
+
| DECISION_001 | Choice A | Rationale |
|
|
1152
|
+
| DECISION_002 | Choice B | Rationale |
|
|
1153
|
+
|
|
1154
|
+
> See \`.ai/decisions/\` for detailed decision logs.
|
|
1155
|
+
|
|
1156
|
+
---
|
|
1157
|
+
|
|
1158
|
+
## Current State
|
|
1159
|
+
|
|
1160
|
+
### Last Completed Step
|
|
1161
|
+
\`\`\`
|
|
1162
|
+
{lastCompletedStep}
|
|
1163
|
+
\`\`\`
|
|
1164
|
+
|
|
1165
|
+
### Pending Work
|
|
1166
|
+
- [ ] Pending item 1
|
|
1167
|
+
- [ ] Pending item 2
|
|
1168
|
+
|
|
1169
|
+
### Known Issues
|
|
1170
|
+
- Issue 1: Description
|
|
1171
|
+
- Issue 2: Description
|
|
1172
|
+
|
|
1173
|
+
---
|
|
1174
|
+
|
|
1175
|
+
## Context for Next Agent
|
|
1176
|
+
|
|
1177
|
+
### Variables
|
|
1178
|
+
\`\`\`json
|
|
1179
|
+
{variablesJson}
|
|
1180
|
+
\`\`\`
|
|
1181
|
+
|
|
1182
|
+
### Schema State
|
|
1183
|
+
- Tables modified: {modifiedTables}
|
|
1184
|
+
- New relationships: {newRelationships}
|
|
1185
|
+
|
|
1186
|
+
### Tables in Scope
|
|
1187
|
+
${tablesList}
|
|
1188
|
+
|
|
1189
|
+
---
|
|
1190
|
+
|
|
1191
|
+
## Next Steps
|
|
1192
|
+
|
|
1193
|
+
### Immediate Actions
|
|
1194
|
+
1. Next action 1
|
|
1195
|
+
2. Next action 2
|
|
1196
|
+
|
|
1197
|
+
### Recommended Approach
|
|
1198
|
+
\`\`\`
|
|
1199
|
+
{recommendedApproach}
|
|
1200
|
+
\`\`\`
|
|
1201
|
+
|
|
1202
|
+
### Expected Duration
|
|
1203
|
+
- \`{estimatedDuration}\`
|
|
1204
|
+
|
|
1205
|
+
---
|
|
1206
|
+
|
|
1207
|
+
## Files Generated
|
|
1208
|
+
|
|
1209
|
+
| File | Purpose |
|
|
1210
|
+
|------|---------|
|
|
1211
|
+
| \`.ai/state/CURRENT_STATE.md\` | Progress snapshot |
|
|
1212
|
+
| \`.ai/context/SESSION_CONTEXT.json\` | Machine-readable state |
|
|
1213
|
+
| \`.ai/decisions/\` | Decision logs |
|
|
1214
|
+
|
|
1215
|
+
---
|
|
1216
|
+
|
|
1217
|
+
> **PROTOCOL**: Before spawning subagents:
|
|
1218
|
+
> 1. Generate handoff file
|
|
1219
|
+
> 2. Pass handoff to next agent
|
|
1220
|
+
> 3. Next agent reads \`.ai/state/CURRENT_STATE.md\` first
|
|
1221
|
+
`;
|
|
1222
|
+
}
|
|
1223
|
+
generateMultiAgentProtocol(schema) {
|
|
1224
|
+
const tablesList = schema.tables.map((t) => t.name).join(', ');
|
|
1225
|
+
return `# Multi-Agent Protocol
|
|
1226
|
+
|
|
1227
|
+
> AUTO-GENERATED by cohere. Protocol for agent-to-agent handoff.
|
|
1228
|
+
|
|
1229
|
+
## Overview
|
|
1230
|
+
|
|
1231
|
+
This document defines the protocol for safe handoff between AI agents working on the same codebase.
|
|
1232
|
+
|
|
1233
|
+
## Protocol Steps
|
|
1234
|
+
|
|
1235
|
+
### 1. Pre-Handoff (Current Agent)
|
|
1236
|
+
|
|
1237
|
+
Before ending your session:
|
|
1238
|
+
|
|
1239
|
+
\`\`\`bash
|
|
1240
|
+
cohere-db handoff --record --status {status} --agentId {agentId}
|
|
1241
|
+
\`\`\`
|
|
1242
|
+
|
|
1243
|
+
This creates:
|
|
1244
|
+
- \`.ai/handoffs/HANDOFF_<timestamp>.md\` - Full handoff record
|
|
1245
|
+
- \`.ai/state/CURRENT_STATE.md\` - Current progress snapshot
|
|
1246
|
+
- \`.ai/context/SESSION_CONTEXT.json\` - Machine-readable state
|
|
1247
|
+
|
|
1248
|
+
### 2. Handoff Transfer
|
|
1249
|
+
|
|
1250
|
+
Share these files with the next agent:
|
|
1251
|
+
- Handoff file location
|
|
1252
|
+
- Session context JSON
|
|
1253
|
+
- Any modified files
|
|
1254
|
+
|
|
1255
|
+
### 3. Post-Handoff (Next Agent)
|
|
1256
|
+
|
|
1257
|
+
On receiving handoff:
|
|
1258
|
+
|
|
1259
|
+
\`\`\`bash
|
|
1260
|
+
# List available sessions
|
|
1261
|
+
cohere-db handoff --list
|
|
1262
|
+
|
|
1263
|
+
# Resume specific session
|
|
1264
|
+
cohere-db handoff --resume <session_id>
|
|
1265
|
+
|
|
1266
|
+
# Read state first
|
|
1267
|
+
cat .ai/state/CURRENT_STATE.md
|
|
1268
|
+
\`\`\`
|
|
1269
|
+
|
|
1270
|
+
---
|
|
1271
|
+
|
|
1272
|
+
## State Files
|
|
1273
|
+
|
|
1274
|
+
### CURRENT_STATE.md
|
|
1275
|
+
|
|
1276
|
+
| Section | Purpose |
|
|
1277
|
+
|---------|---------|
|
|
1278
|
+
| Session Status | Agent ID, timestamp, status |
|
|
1279
|
+
| Progress | Completed steps, current step, remaining |
|
|
1280
|
+
| Work Queue | Pending tasks, blocked tasks |
|
|
1281
|
+
| Variables | Key state variables |
|
|
1282
|
+
| Checkpoints | Saved checkpoints |
|
|
1283
|
+
|
|
1284
|
+
### SESSION_CONTEXT.json
|
|
1285
|
+
|
|
1286
|
+
Machine-readable state for programmatic access:
|
|
1287
|
+
|
|
1288
|
+
\`\`\`json
|
|
1289
|
+
{
|
|
1290
|
+
"session": { "id", "agentId", "status" },
|
|
1291
|
+
"state": { "phase", "progress" },
|
|
1292
|
+
"variables": { /* key-value pairs */ },
|
|
1293
|
+
"schema": { "databaseType", "tablesModified" },
|
|
1294
|
+
"decisions": [ { "id", "title", "choice" } ]
|
|
1295
|
+
}
|
|
1296
|
+
\`\`\`
|
|
1297
|
+
|
|
1298
|
+
---
|
|
1299
|
+
|
|
1300
|
+
## Tables in Scope
|
|
1301
|
+
|
|
1302
|
+
${tablesList}
|
|
1303
|
+
|
|
1304
|
+
---
|
|
1305
|
+
|
|
1306
|
+
## Constraints
|
|
1307
|
+
|
|
1308
|
+
| Constraint | Value |
|
|
1309
|
+
|------------|-------|
|
|
1310
|
+
| Max handoff size | 10KB |
|
|
1311
|
+
| Checkpoint interval | 5 minutes |
|
|
1312
|
+
| Decision logging | Required for major choices |
|
|
1313
|
+
|
|
1314
|
+
---
|
|
1315
|
+
|
|
1316
|
+
## CLI Commands
|
|
1317
|
+
|
|
1318
|
+
| Command | Description |
|
|
1319
|
+
|---------|-------------|
|
|
1320
|
+
| \`cohere-db handoff --record\` | Record current session |
|
|
1321
|
+
| \`cohere-db handoff --list\` | List available sessions |
|
|
1322
|
+
| \`cohere-db handoff --resume <id>\` | Resume from session |
|
|
1323
|
+
| \`cohere-db handoff --status completed\` | Mark session complete |
|
|
1324
|
+
|
|
1325
|
+
---
|
|
1326
|
+
|
|
1327
|
+
## Best Practices
|
|
1328
|
+
|
|
1329
|
+
1. **Checkpoint frequently** - Save state every 5-10 minutes
|
|
1330
|
+
2. **Log decisions** - Use decision IDs in handoffs
|
|
1331
|
+
3. **Document failures** - Include error context in handoff
|
|
1332
|
+
4. **Atomic handoffs** - Complete handoff before ending session
|
|
1333
|
+
|
|
1334
|
+
---
|
|
1335
|
+
|
|
1336
|
+
## Example Flow
|
|
1337
|
+
|
|
1338
|
+
\`\`\`
|
|
1339
|
+
Agent A (in progress)
|
|
1340
|
+
↓
|
|
1341
|
+
cohere-db handoff --record --status paused
|
|
1342
|
+
↓
|
|
1343
|
+
Agent B receives files
|
|
1344
|
+
↓
|
|
1345
|
+
cohere-db handoff --resume sess_abc123
|
|
1346
|
+
↓
|
|
1347
|
+
Agent B continues work
|
|
1348
|
+
↓
|
|
1349
|
+
cohere-db handoff --record --status completed
|
|
1350
|
+
\`\`\`
|
|
1351
|
+
`;
|
|
1352
|
+
}
|
|
1353
|
+
// ============================================================================
|
|
1354
|
+
// Decision Templates Generation
|
|
1355
|
+
// ============================================================================
|
|
1356
|
+
generateDecisionTemplates(schema) {
|
|
1357
|
+
const templates = {};
|
|
1358
|
+
templates['decision-template.md'] = this.generateDecisionTemplate(schema);
|
|
1359
|
+
templates['DECISION_LOG.md'] = this.generateDecisionLog(schema);
|
|
1360
|
+
return templates;
|
|
1361
|
+
}
|
|
1362
|
+
generateDecisionTemplate(schema) {
|
|
1363
|
+
return `# Decision: {decisionTitle}
|
|
1364
|
+
|
|
1365
|
+
> AUTO-GENERATED by cohere. Decision ID: \`{decisionId}\`
|
|
1366
|
+
|
|
1367
|
+
## Metadata
|
|
1368
|
+
|
|
1369
|
+
| Field | Value |
|
|
1370
|
+
|-------|-------|
|
|
1371
|
+
| **Decision ID** | \`{decisionId}\` |
|
|
1372
|
+
| **Timestamp** | \`{timestamp}\` |
|
|
1373
|
+
| **Agent ID** | \`{agentId}\` |
|
|
1374
|
+
| **Session ID** | \`{sessionId}\` |
|
|
1375
|
+
| **Status** | \`{status}\` |
|
|
1376
|
+
|
|
1377
|
+
---
|
|
1378
|
+
|
|
1379
|
+
## Context
|
|
1380
|
+
|
|
1381
|
+
### Problem Statement
|
|
1382
|
+
|
|
1383
|
+
{problemDescription}
|
|
1384
|
+
|
|
1385
|
+
### Constraints
|
|
1386
|
+
|
|
1387
|
+
- Constraint 1
|
|
1388
|
+
- Constraint 2
|
|
1389
|
+
- Constraint 3
|
|
1390
|
+
|
|
1391
|
+
### Requirements
|
|
1392
|
+
|
|
1393
|
+
1. Requirement 1
|
|
1394
|
+
2. Requirement 2
|
|
1395
|
+
|
|
1396
|
+
---
|
|
1397
|
+
|
|
1398
|
+
## Options Considered
|
|
1399
|
+
|
|
1400
|
+
### Option A: {optionATitle}
|
|
1401
|
+
|
|
1402
|
+
| Attribute | Value |
|
|
1403
|
+
|-----------|-------|
|
|
1404
|
+
| Pros | Pro 1, Pro 2 |
|
|
1405
|
+
| Cons | Con 1, Con 2 |
|
|
1406
|
+
| Risk | Low/Medium/High |
|
|
1407
|
+
| Effort | Small/Medium/Large |
|
|
1408
|
+
|
|
1409
|
+
### Option B: {optionBTitle}
|
|
1410
|
+
|
|
1411
|
+
| Attribute | Value |
|
|
1412
|
+
|-----------|-------|
|
|
1413
|
+
| Pros | Pro 1, Pro 2 |
|
|
1414
|
+
| Cons | Con 1, Con 2 |
|
|
1415
|
+
| Risk | Low/Medium/High |
|
|
1416
|
+
| Effort | Small/Medium/Large |
|
|
1417
|
+
|
|
1418
|
+
---
|
|
1419
|
+
|
|
1420
|
+
## Decision
|
|
1421
|
+
|
|
1422
|
+
> **{chosenOption}** was selected
|
|
1423
|
+
|
|
1424
|
+
### Rationale
|
|
1425
|
+
|
|
1426
|
+
{choiceRationale}
|
|
1427
|
+
|
|
1428
|
+
### Trade-offs
|
|
1429
|
+
|
|
1430
|
+
| Factor | Impact |
|
|
1431
|
+
|--------|--------|
|
|
1432
|
+
| Factor 1 | {impact1} |
|
|
1433
|
+
| Factor 2 | {impact2} |
|
|
1434
|
+
|
|
1435
|
+
---
|
|
1436
|
+
|
|
1437
|
+
## Implementation
|
|
1438
|
+
|
|
1439
|
+
### Chosen Approach
|
|
1440
|
+
|
|
1441
|
+
{implementationDetails}
|
|
1442
|
+
|
|
1443
|
+
### Files Modified
|
|
1444
|
+
|
|
1445
|
+
| File | Change |
|
|
1446
|
+
|------|--------|
|
|
1447
|
+
| file1.ts | Modification A |
|
|
1448
|
+
| file2.sql | Modification B |
|
|
1449
|
+
|
|
1450
|
+
---
|
|
1451
|
+
|
|
1452
|
+
## Outcome
|
|
1453
|
+
|
|
1454
|
+
### Expected Results
|
|
1455
|
+
|
|
1456
|
+
1. Expected outcome 1
|
|
1457
|
+
2. Expected outcome 2
|
|
1458
|
+
|
|
1459
|
+
### Potential Risks
|
|
1460
|
+
|
|
1461
|
+
1. Risk 1 - Mitigation
|
|
1462
|
+
2. Risk 2 - Mitigation
|
|
1463
|
+
|
|
1464
|
+
### Follow-up Required
|
|
1465
|
+
|
|
1466
|
+
- [ ] Follow-up item 1
|
|
1467
|
+
- [ ] Follow-up item 2
|
|
1468
|
+
|
|
1469
|
+
---
|
|
1470
|
+
|
|
1471
|
+
## Related Decisions
|
|
1472
|
+
|
|
1473
|
+
| ID | Decision | Relationship |
|
|
1474
|
+
|----|----------|--------------|
|
|
1475
|
+
| DECISION_001 | Related decision | Precedes |
|
|
1476
|
+
| DECISION_003 | Related decision | Extends |
|
|
1477
|
+
|
|
1478
|
+
---
|
|
1479
|
+
|
|
1480
|
+
> **USAGE**: Use this format for all significant decisions.
|
|
1481
|
+
> Decision IDs are referenced in handoffs for context.
|
|
1482
|
+
`;
|
|
1483
|
+
}
|
|
1484
|
+
generateDecisionLog(schema) {
|
|
1485
|
+
const tablesList = schema.tables.map((t) => `- \`${t.name}\``).join('\n');
|
|
1486
|
+
return `# Decision Log
|
|
1487
|
+
|
|
1488
|
+
> AUTO-GENERATED by cohere. All decisions made during this session.
|
|
1489
|
+
|
|
1490
|
+
## Overview
|
|
1491
|
+
|
|
1492
|
+
This log tracks all significant decisions made by agents working on this codebase.
|
|
1493
|
+
|
|
1494
|
+
## Decision Index
|
|
1495
|
+
|
|
1496
|
+
| ID | Date | Title | Status |
|
|
1497
|
+
|----|------|-------|--------|
|
|
1498
|
+
| DECISION_001 | YYYY-MM-DD | Decision title | Active |
|
|
1499
|
+
| DECISION_002 | YYYY-MM-DD | Decision title | Active |
|
|
1500
|
+
|
|
1501
|
+
---
|
|
1502
|
+
|
|
1503
|
+
## Active Decisions
|
|
1504
|
+
|
|
1505
|
+
### DECISION_001: {decisionTitle}
|
|
1506
|
+
|
|
1507
|
+
**Status:** Active
|
|
1508
|
+
**Date:** {date}
|
|
1509
|
+
**Agent:** {agentId}
|
|
1510
|
+
|
|
1511
|
+
**Summary:** Brief summary of decision
|
|
1512
|
+
|
|
1513
|
+
**Impact:** Tables affected: ${tablesList}
|
|
1514
|
+
|
|
1515
|
+
---
|
|
1516
|
+
|
|
1517
|
+
## Archived Decisions
|
|
1518
|
+
|
|
1519
|
+
### DECISION_XXX: {archivedTitle}
|
|
1520
|
+
|
|
1521
|
+
**Status:** Archived
|
|
1522
|
+
**Date:** {date}
|
|
1523
|
+
**Reason:** Context no longer relevant
|
|
1524
|
+
|
|
1525
|
+
---
|
|
1526
|
+
|
|
1527
|
+
## Adding New Decisions
|
|
1528
|
+
|
|
1529
|
+
Use the template in \`.ai/decisions/decision-template.md\`:
|
|
1530
|
+
|
|
1531
|
+
\`\`\`bash
|
|
1532
|
+
# Create new decision
|
|
1533
|
+
cp .ai/decisions/decision-template.md .ai/decisions/DECISION_003.md
|
|
1534
|
+
|
|
1535
|
+
# Edit the file with your decision
|
|
1536
|
+
# Update this log
|
|
1537
|
+
\`\`\`
|
|
1538
|
+
|
|
1539
|
+
---
|
|
1540
|
+
|
|
1541
|
+
## Decision Categories
|
|
1542
|
+
|
|
1543
|
+
| Category | Description |
|
|
1544
|
+
|----------|-------------|
|
|
1545
|
+
| schema | Database schema changes |
|
|
1546
|
+
| query | Query pattern choices |
|
|
1547
|
+
| architecture | System design decisions |
|
|
1548
|
+
| workflow | Process/flow decisions |
|
|
1549
|
+
|
|
1550
|
+
---
|
|
1551
|
+
|
|
1552
|
+
> **NOTE**: Decision IDs are referenced in handoffs.
|
|
1553
|
+
> Always update this log when creating new decisions.
|
|
1554
|
+
`;
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
//# sourceMappingURL=templates.js.map
|