id3-cli 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja-JP.md +769 -0
- package/README.ko-KR.md +769 -0
- package/README.md +769 -0
- package/README.tr-TR.md +769 -0
- package/README.zh-CN.md +769 -0
- package/dist/bin/cli.d.ts +2 -0
- package/dist/bin/cli.js +40 -0
- package/dist/bin/cli.js.map +1 -0
- package/dist/scripts/build-hooks.d.ts +1 -0
- package/dist/scripts/build-hooks.js +58 -0
- package/dist/scripts/build-hooks.js.map +1 -0
- package/dist/src/hooks/auto-audit.d.ts +4 -0
- package/dist/src/hooks/auto-audit.js +47 -0
- package/dist/src/hooks/auto-audit.js.map +1 -0
- package/dist/src/hooks/claude-pretool-entry.d.ts +1 -0
- package/dist/src/hooks/claude-pretool-entry.js +36 -0
- package/dist/src/hooks/claude-pretool-entry.js.map +1 -0
- package/dist/src/hooks/claude-stop-entry.d.ts +1 -0
- package/dist/src/hooks/claude-stop-entry.js +7 -0
- package/dist/src/hooks/claude-stop-entry.js.map +1 -0
- package/dist/src/hooks/post-commit-entry.d.ts +1 -0
- package/dist/src/hooks/post-commit-entry.js +7 -0
- package/dist/src/hooks/post-commit-entry.js.map +1 -0
- package/dist/src/hooks/pre-commit-entry.d.ts +1 -0
- package/dist/src/hooks/pre-commit-entry.js +16 -0
- package/dist/src/hooks/pre-commit-entry.js.map +1 -0
- package/dist/src/hooks/rule-check.d.ts +8 -0
- package/dist/src/hooks/rule-check.js +101 -0
- package/dist/src/hooks/rule-check.js.map +1 -0
- package/dist/src/hooks/schema-drift.d.ts +17 -0
- package/dist/src/hooks/schema-drift.js +151 -0
- package/dist/src/hooks/schema-drift.js.map +1 -0
- package/dist/src/hooks/shared.d.ts +43 -0
- package/dist/src/hooks/shared.js +98 -0
- package/dist/src/hooks/shared.js.map +1 -0
- package/dist/src/init.d.ts +20 -0
- package/dist/src/init.js +193 -0
- package/dist/src/init.js.map +1 -0
- package/dist/src/preview/mockup-generator.d.ts +56 -0
- package/dist/src/preview/mockup-generator.js +402 -0
- package/dist/src/preview/mockup-generator.js.map +1 -0
- package/dist/src/preview/renderer.d.ts +30 -0
- package/dist/src/preview/renderer.js +145 -0
- package/dist/src/preview/renderer.js.map +1 -0
- package/dist/src/preview/server.d.ts +9 -0
- package/dist/src/preview/server.js +55 -0
- package/dist/src/preview/server.js.map +1 -0
- package/dist/src/preview/ui-auditor.d.ts +27 -0
- package/dist/src/preview/ui-auditor.js +141 -0
- package/dist/src/preview/ui-auditor.js.map +1 -0
- package/dist/src/preview/ui-gate.d.ts +66 -0
- package/dist/src/preview/ui-gate.js +210 -0
- package/dist/src/preview/ui-gate.js.map +1 -0
- package/dist/src/utils/ascii.d.ts +7 -0
- package/dist/src/utils/ascii.js +41 -0
- package/dist/src/utils/ascii.js.map +1 -0
- package/dist/src/utils/fs.d.ts +6 -0
- package/dist/src/utils/fs.js +39 -0
- package/dist/src/utils/fs.js.map +1 -0
- package/dist/templates/hooks/iddd-auto-audit.js +121 -0
- package/dist/templates/hooks/iddd-schema-drift.js +279 -0
- package/dist/templates/hooks/post-commit +121 -0
- package/dist/templates/hooks/pre-commit +348 -0
- package/package.json +37 -0
- package/templates/.agents/skills/.gitkeep +0 -0
- package/templates/.claude/hooks/.gitkeep +0 -0
- package/templates/.claude/hooks/hook-config.json +34 -0
- package/templates/.claude/skills/.gitkeep +0 -0
- package/templates/.codex/.gitkeep +0 -0
- package/templates/.codex/hooks.json +40 -0
- package/templates/.iddd/commit-count +1 -0
- package/templates/.iddd/preview/.gitkeep +0 -0
- package/templates/AGENTS.md +204 -0
- package/templates/CLAUDE.md +215 -0
- package/templates/README.md +476 -0
- package/templates/docs/.gitkeep +0 -0
- package/templates/docs/business-rules.md +14 -0
- package/templates/docs/domain-glossary.md +8 -0
- package/templates/docs/info-debt.md +17 -0
- package/templates/docs/model-changelog.md +12 -0
- package/templates/hooks/.gitkeep +0 -0
- package/templates/hooks/iddd-auto-audit.js +121 -0
- package/templates/hooks/iddd-schema-drift.js +279 -0
- package/templates/hooks/post-commit +121 -0
- package/templates/hooks/pre-commit +348 -0
- package/templates/skills/id3-design-information/SKILL.md +170 -0
- package/templates/skills/id3-design-information/references/phase2-procedure.md +241 -0
- package/templates/skills/id3-design-ui/.gitkeep +0 -0
- package/templates/skills/id3-design-ui/SKILL.md +200 -0
- package/templates/skills/id3-design-ui/references/.gitkeep +0 -0
- package/templates/skills/id3-design-ui/references/step1-structure-derivation.md +177 -0
- package/templates/skills/id3-design-ui/references/step2-visual-contract.md +257 -0
- package/templates/skills/id3-design-ui/references/step3-gate-and-mockup.md +177 -0
- package/templates/skills/id3-design-ui/references/step4-implementation.md +244 -0
- package/templates/skills/id3-identify-entities/SKILL.md +239 -0
- package/templates/skills/id3-identify-entities/references/.gitkeep +0 -0
- package/templates/skills/id3-identify-entities/references/phase0-brownfield.md +377 -0
- package/templates/skills/id3-identify-entities/references/phase1-greenfield.md +319 -0
- package/templates/skills/id3-info-audit/.gitkeep +0 -0
- package/templates/skills/id3-info-audit/SKILL.md +191 -0
- package/templates/skills/id3-preview/.gitkeep +0 -0
- package/templates/skills/id3-preview/SKILL.md +168 -0
- package/templates/skills/id3-spawn-team/.gitkeep +0 -0
- package/templates/skills/id3-spawn-team/SKILL.md +213 -0
- package/templates/specs/.gitkeep +0 -0
- package/templates/specs/data-model.md +26 -0
- package/templates/specs/entity-catalog.md +22 -0
- package/templates/specs/ui-design-contract.md +54 -0
- package/templates/specs/ui-inventory.md +24 -0
- package/templates/specs/ui-structure.md +32 -0
- package/templates/src/.gitkeep +0 -0
- package/templates/steering/.gitkeep +0 -0
- package/templates/steering/data-conventions.md +42 -0
- package/templates/steering/product.md +38 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/hooks/pre-commit-entry.ts
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
|
|
6
|
+
// src/hooks/shared.ts
|
|
7
|
+
import { readFile } from "node:fs/promises";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { execSync } from "node:child_process";
|
|
10
|
+
|
|
11
|
+
// src/utils/ascii.ts
|
|
12
|
+
function box(content, options = {}) {
|
|
13
|
+
const { title, width = 47, padding = 1 } = options;
|
|
14
|
+
const lines = content.split("\n");
|
|
15
|
+
const innerWidth = width - 2;
|
|
16
|
+
const padStr = " ".repeat(padding);
|
|
17
|
+
const result = [];
|
|
18
|
+
if (title) {
|
|
19
|
+
const titleStr = ` ${title} `;
|
|
20
|
+
const remaining = innerWidth - titleStr.length - 1;
|
|
21
|
+
result.push(` \u250C\u2500${titleStr}${"\u2500".repeat(Math.max(0, remaining))}\u2510`);
|
|
22
|
+
} else {
|
|
23
|
+
result.push(` \u250C${"\u2500".repeat(innerWidth)}\u2510`);
|
|
24
|
+
}
|
|
25
|
+
result.push(` \u2502${" ".repeat(innerWidth)}\u2502`);
|
|
26
|
+
for (const line of lines) {
|
|
27
|
+
const padded = `${padStr}${line}`;
|
|
28
|
+
const spaces = innerWidth - padded.length;
|
|
29
|
+
result.push(` \u2502${padded}${" ".repeat(Math.max(0, spaces))}\u2502`);
|
|
30
|
+
}
|
|
31
|
+
result.push(` \u2502${" ".repeat(innerWidth)}\u2502`);
|
|
32
|
+
result.push(` \u2514${"\u2500".repeat(innerWidth)}\u2518`);
|
|
33
|
+
return result.join("\n");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/hooks/shared.ts
|
|
37
|
+
function parseGitDiff(output) {
|
|
38
|
+
if (!output.trim()) return [];
|
|
39
|
+
return output.trim().split("\n").map((line) => {
|
|
40
|
+
const [status, path] = line.split(" ");
|
|
41
|
+
return { status, path };
|
|
42
|
+
}).filter((e) => e.path);
|
|
43
|
+
}
|
|
44
|
+
function getCachedDiff() {
|
|
45
|
+
try {
|
|
46
|
+
const output = execSync("git diff --cached --name-status", {
|
|
47
|
+
encoding: "utf-8"
|
|
48
|
+
});
|
|
49
|
+
return parseGitDiff(output);
|
|
50
|
+
} catch {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function getCachedFileContent(filePath) {
|
|
55
|
+
try {
|
|
56
|
+
return execSync(`git show :${filePath}`, { encoding: "utf-8" });
|
|
57
|
+
} catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function isSchemaFile(filePath, patterns) {
|
|
62
|
+
return patterns.some((pattern) => matchGlob(filePath, pattern));
|
|
63
|
+
}
|
|
64
|
+
function isValidationFile(filePath, patterns) {
|
|
65
|
+
return patterns.some((pattern) => matchGlob(filePath, pattern));
|
|
66
|
+
}
|
|
67
|
+
function matchGlob(filePath, pattern) {
|
|
68
|
+
const regex = pattern.replace(/\./g, "\\.").replace(/\*\*/g, "{{GLOBSTAR}}").replace(/\*/g, "[^/]*").replace(/\{\{GLOBSTAR\}\}/g, ".*");
|
|
69
|
+
return new RegExp(`^${regex}$`).test(filePath) || new RegExp(`(^|/)${regex}$`).test(filePath);
|
|
70
|
+
}
|
|
71
|
+
async function loadHookConfig(projectRoot2) {
|
|
72
|
+
try {
|
|
73
|
+
const configPath = join(
|
|
74
|
+
projectRoot2,
|
|
75
|
+
".claude",
|
|
76
|
+
"hooks",
|
|
77
|
+
"hook-config.json"
|
|
78
|
+
);
|
|
79
|
+
const content = await readFile(configPath, "utf-8");
|
|
80
|
+
return JSON.parse(content);
|
|
81
|
+
} catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function isSkipHooks() {
|
|
86
|
+
return process.env["IDDD_SKIP_HOOKS"] === "1";
|
|
87
|
+
}
|
|
88
|
+
async function logSkip(projectRoot2) {
|
|
89
|
+
const { appendFile, mkdir } = await import("node:fs/promises");
|
|
90
|
+
const logDir = join(projectRoot2, ".iddd");
|
|
91
|
+
await mkdir(logDir, { recursive: true });
|
|
92
|
+
const logPath = join(logDir, "skip-history.log");
|
|
93
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
94
|
+
let commitHash = "unknown";
|
|
95
|
+
try {
|
|
96
|
+
commitHash = execSync("git rev-parse HEAD", { encoding: "utf-8" }).trim();
|
|
97
|
+
} catch {
|
|
98
|
+
}
|
|
99
|
+
await appendFile(logPath, `${timestamp} ${commitHash} Hook skipped
|
|
100
|
+
`);
|
|
101
|
+
}
|
|
102
|
+
function printBlock(title, message) {
|
|
103
|
+
console.error(box(message, { title }));
|
|
104
|
+
}
|
|
105
|
+
function printWarn(title, message) {
|
|
106
|
+
console.error(box(message, { title }));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// src/hooks/schema-drift.ts
|
|
110
|
+
function analyzeSchemaChanges(diffContent, filePath) {
|
|
111
|
+
const changes = [];
|
|
112
|
+
const addedLines = diffContent.split("\n").filter((l) => l.startsWith("+") && !l.startsWith("+++")).map((l) => l.slice(1).trim());
|
|
113
|
+
for (const line of addedLines) {
|
|
114
|
+
const createMatch = line.match(/CREATE\s+TABLE\s+(\w+)/i);
|
|
115
|
+
if (createMatch) {
|
|
116
|
+
changes.push({
|
|
117
|
+
type: "add",
|
|
118
|
+
table: createMatch[1],
|
|
119
|
+
trivial: false,
|
|
120
|
+
description: `Table ${createMatch[1]} created`
|
|
121
|
+
});
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const alterAddMatch = line.match(
|
|
125
|
+
/ALTER\s+TABLE\s+(\w+)\s+ADD\s+(?:COLUMN\s+)?(\w+)/i
|
|
126
|
+
);
|
|
127
|
+
if (alterAddMatch) {
|
|
128
|
+
changes.push({
|
|
129
|
+
type: "add",
|
|
130
|
+
table: alterAddMatch[1],
|
|
131
|
+
column: alterAddMatch[2],
|
|
132
|
+
trivial: false,
|
|
133
|
+
description: `Column ${alterAddMatch[1]}.${alterAddMatch[2]} added`
|
|
134
|
+
});
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const dropMatch = line.match(/DROP\s+TABLE\s+(\w+)/i);
|
|
138
|
+
if (dropMatch) {
|
|
139
|
+
changes.push({
|
|
140
|
+
type: "drop",
|
|
141
|
+
table: dropMatch[1],
|
|
142
|
+
trivial: false,
|
|
143
|
+
description: `Table ${dropMatch[1]} dropped`
|
|
144
|
+
});
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const indexMatch = line.match(/CREATE\s+(?:UNIQUE\s+)?INDEX\s+/i);
|
|
148
|
+
if (indexMatch) {
|
|
149
|
+
const tableMatch = line.match(/ON\s+(\w+)/i);
|
|
150
|
+
changes.push({
|
|
151
|
+
type: "modify",
|
|
152
|
+
table: tableMatch?.[1] ?? "unknown",
|
|
153
|
+
trivial: true,
|
|
154
|
+
description: `Index added`
|
|
155
|
+
});
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
const prismaModel = line.match(/^model\s+(\w+)\s*\{/);
|
|
159
|
+
if (prismaModel) {
|
|
160
|
+
changes.push({
|
|
161
|
+
type: "add",
|
|
162
|
+
table: prismaModel[1],
|
|
163
|
+
trivial: false,
|
|
164
|
+
description: `Model ${prismaModel[1]} added`
|
|
165
|
+
});
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
const djangoModel = line.match(/^class\s+(\w+)\(.*Model.*\)/);
|
|
169
|
+
if (djangoModel) {
|
|
170
|
+
changes.push({
|
|
171
|
+
type: "add",
|
|
172
|
+
table: djangoModel[1],
|
|
173
|
+
trivial: false,
|
|
174
|
+
description: `Model ${djangoModel[1]} added`
|
|
175
|
+
});
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return changes;
|
|
180
|
+
}
|
|
181
|
+
async function runSchemaDrift(projectRoot2) {
|
|
182
|
+
if (isSkipHooks()) {
|
|
183
|
+
await logSkip(projectRoot2);
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
const config = await loadHookConfig(projectRoot2);
|
|
187
|
+
if (!config?.hooks["pre-commit"]["schema-drift"]?.enabled) {
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
const { monitored_patterns } = config.hooks["pre-commit"]["schema-drift"];
|
|
191
|
+
const diff = getCachedDiff();
|
|
192
|
+
const schemaFiles = diff.filter(
|
|
193
|
+
(e) => isSchemaFile(e.path, monitored_patterns)
|
|
194
|
+
);
|
|
195
|
+
if (schemaFiles.length === 0) return true;
|
|
196
|
+
const catalogModified = diff.some(
|
|
197
|
+
(e) => e.path === "specs/entity-catalog.md"
|
|
198
|
+
);
|
|
199
|
+
const allChanges = [];
|
|
200
|
+
for (const file of schemaFiles) {
|
|
201
|
+
const content = getCachedFileContent(file.path);
|
|
202
|
+
if (content) {
|
|
203
|
+
allChanges.push(...analyzeSchemaChanges(content, file.path));
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (allChanges.length === 0) return true;
|
|
207
|
+
const hasStructural = allChanges.some((c) => !c.trivial);
|
|
208
|
+
if (catalogModified) {
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
if (!hasStructural) {
|
|
212
|
+
printWarn(
|
|
213
|
+
"\u26A0\uFE0F Schema Change Detected",
|
|
214
|
+
allChanges.map((c) => c.description).join("\n") + "\n\nConsider updating specs/entity-catalog.md"
|
|
215
|
+
);
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
const changeList = allChanges.filter((c) => !c.trivial).map((c) => ` ${c.description}`).join("\n");
|
|
219
|
+
printBlock(
|
|
220
|
+
"\u274C Schema Drift Detected",
|
|
221
|
+
`entity-catalog.md must be updated first.
|
|
222
|
+
|
|
223
|
+
${changeList}
|
|
224
|
+
|
|
225
|
+
Run /id3-info-audit or manually update
|
|
226
|
+
specs/entity-catalog.md`
|
|
227
|
+
);
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// src/hooks/rule-check.ts
|
|
232
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
233
|
+
import { join as join2 } from "node:path";
|
|
234
|
+
var VALIDATION_PATTERNS = [
|
|
235
|
+
{
|
|
236
|
+
regex: /z\.\s*(?:object|string|number|boolean|array|enum)\s*\(/g,
|
|
237
|
+
label: "zod"
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
regex: /yup\.\s*(?:object|string|number|boolean|array)\s*\(/g,
|
|
241
|
+
label: "yup"
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
regex: /Joi\.\s*(?:object|string|number|boolean|array)\s*\(/g,
|
|
245
|
+
label: "joi"
|
|
246
|
+
},
|
|
247
|
+
{ regex: /CHECK\s*\(/gi, label: "SQL CHECK" },
|
|
248
|
+
{ regex: /NOT\s+NULL/gi, label: "SQL NOT NULL" },
|
|
249
|
+
{ regex: /ADD\s+.*UNIQUE/gi, label: "SQL UNIQUE" },
|
|
250
|
+
{ regex: /@validator\s*\(/g, label: "pydantic" },
|
|
251
|
+
{ regex: /@field_validator\s*\(/g, label: "pydantic-v2" },
|
|
252
|
+
{ regex: /@Valid/g, label: "java-valid" },
|
|
253
|
+
{ regex: /@NotNull/g, label: "java-notnull" },
|
|
254
|
+
{
|
|
255
|
+
regex: /@Column\s*\(\s*.*nullable\s*[:=]\s*false/g,
|
|
256
|
+
label: "orm-notnull"
|
|
257
|
+
},
|
|
258
|
+
{ regex: /@IsNotEmpty\s*\(/g, label: "class-validator" },
|
|
259
|
+
{
|
|
260
|
+
regex: /body\s*\(\s*['"].*['"]\s*\)\s*\.(?:not|is)/g,
|
|
261
|
+
label: "express-validator"
|
|
262
|
+
}
|
|
263
|
+
];
|
|
264
|
+
function detectValidationPatterns(content, filePath) {
|
|
265
|
+
const detections = [];
|
|
266
|
+
const lines = content.split("\n");
|
|
267
|
+
for (let i = 0; i < lines.length; i++) {
|
|
268
|
+
const line = lines[i];
|
|
269
|
+
if (!line.startsWith("+") && content.includes("\n+")) {
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
const cleanLine = line.replace(/^\+/, "");
|
|
273
|
+
for (const { regex, label } of VALIDATION_PATTERNS) {
|
|
274
|
+
regex.lastIndex = 0;
|
|
275
|
+
if (regex.test(cleanLine)) {
|
|
276
|
+
detections.push({
|
|
277
|
+
file: filePath,
|
|
278
|
+
line: i + 1,
|
|
279
|
+
content: cleanLine.trim(),
|
|
280
|
+
pattern: label
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return detections;
|
|
286
|
+
}
|
|
287
|
+
async function hasMatchingRule(projectRoot2, detection) {
|
|
288
|
+
try {
|
|
289
|
+
const rulesPath = join2(projectRoot2, "docs", "business-rules.md");
|
|
290
|
+
const rules = await readFile2(rulesPath, "utf-8");
|
|
291
|
+
return rules.includes("BR-") && rules.length > 200;
|
|
292
|
+
} catch {
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
async function runRuleCheck(projectRoot2) {
|
|
297
|
+
if (isSkipHooks()) {
|
|
298
|
+
await logSkip(projectRoot2);
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
const config = await loadHookConfig(projectRoot2);
|
|
302
|
+
if (!config?.hooks["pre-commit"]["rule-check"]?.enabled) {
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
const { validation_patterns } = config.hooks["pre-commit"]["rule-check"];
|
|
306
|
+
const diff = getCachedDiff();
|
|
307
|
+
const validationFiles = diff.filter(
|
|
308
|
+
(e) => isValidationFile(e.path, validation_patterns)
|
|
309
|
+
);
|
|
310
|
+
if (validationFiles.length === 0) return true;
|
|
311
|
+
const allDetections = [];
|
|
312
|
+
for (const file of validationFiles) {
|
|
313
|
+
const content = getCachedFileContent(file.path);
|
|
314
|
+
if (content) {
|
|
315
|
+
allDetections.push(...detectValidationPatterns(content, file.path));
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (allDetections.length === 0) return true;
|
|
319
|
+
for (const detection of allDetections) {
|
|
320
|
+
const matched = await hasMatchingRule(projectRoot2, detection);
|
|
321
|
+
if (!matched) {
|
|
322
|
+
printWarn(
|
|
323
|
+
"\u26A0\uFE0F New Validation Detected",
|
|
324
|
+
`File: ${detection.file}:${detection.line}
|
|
325
|
+
Pattern: ${detection.pattern}
|
|
326
|
+
Content: ${detection.content}
|
|
327
|
+
|
|
328
|
+
No matching BR-xxx in business-rules.md
|
|
329
|
+
Consider registering this rule.`
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// src/hooks/pre-commit-entry.ts
|
|
337
|
+
var projectRoot = resolve(".");
|
|
338
|
+
async function main() {
|
|
339
|
+
const schemaDriftOk = await runSchemaDrift(projectRoot);
|
|
340
|
+
if (!schemaDriftOk) {
|
|
341
|
+
process.exit(1);
|
|
342
|
+
}
|
|
343
|
+
await runRuleCheck(projectRoot);
|
|
344
|
+
}
|
|
345
|
+
main().catch((err) => {
|
|
346
|
+
console.error("IDDD pre-commit hook error:", err);
|
|
347
|
+
process.exit(1);
|
|
348
|
+
});
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: id3-design-information
|
|
3
|
+
description: >
|
|
4
|
+
Refine conceptual model into logical model. Auto-derive business rules,
|
|
5
|
+
validity constraints, and derivation rules from the information model.
|
|
6
|
+
Trigger: information structuring, model refinement, logical model, schema design,
|
|
7
|
+
design information, refine model
|
|
8
|
+
allowed-tools: Read, Glob, Grep, Bash, Write, Edit
|
|
9
|
+
user-invocable: true
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Phase 2: Information Design
|
|
13
|
+
|
|
14
|
+
You are the lead agent performing IDDD Phase 2. This phase refines the conceptual model produced by Phase 0/1 into a logical model. Business rules, validity constraints, and derivation rules are systematically derived from the information model.
|
|
15
|
+
|
|
16
|
+
## Prerequisites
|
|
17
|
+
|
|
18
|
+
Before starting, verify the following conditions:
|
|
19
|
+
|
|
20
|
+
1. `specs/entity-catalog.md` exists and contains at least 2 identified entities.
|
|
21
|
+
2. `specs/data-model.md` exists with a Mermaid ERD (even if preliminary).
|
|
22
|
+
3. `docs/business-rules.md` exists (may be empty or partially populated from Phase 0).
|
|
23
|
+
4. `steering/data-conventions.md` exists with project naming and typing conventions.
|
|
24
|
+
|
|
25
|
+
If prerequisites are **not** met, respond:
|
|
26
|
+
|
|
27
|
+
> "Please complete Phase 0/1 (entity identification) first. Use 'identify entities' to start."
|
|
28
|
+
|
|
29
|
+
Do **not** proceed until all prerequisites are confirmed.
|
|
30
|
+
|
|
31
|
+
## Procedure Overview
|
|
32
|
+
|
|
33
|
+
Phase 2 consists of five sequential steps. Steps 1-3 refine the model and derive rules. Step 4 resolves ambiguities with the user. Step 5 updates all artifacts.
|
|
34
|
+
|
|
35
|
+
### Step 1: Attribute Refinement
|
|
36
|
+
|
|
37
|
+
For each entity in `specs/entity-catalog.md`:
|
|
38
|
+
|
|
39
|
+
- Assign concrete data types (UUID, TEXT, INTEGER, TIMESTAMP, JSONB, BOOLEAN, ENUM, etc.)
|
|
40
|
+
- Add constraints: NOT NULL, DEFAULT, UNIQUE, CHECK
|
|
41
|
+
- Evaluate index needs (search targets, FKs, frequently filtered attributes)
|
|
42
|
+
- Follow conventions defined in `steering/data-conventions.md`
|
|
43
|
+
|
|
44
|
+
Key conventions to enforce:
|
|
45
|
+
- PK strategy: UUID v7 by default (time-sortable)
|
|
46
|
+
- All entities get `created_at` and `updated_at` timestamps
|
|
47
|
+
- FK columns get automatic indexes
|
|
48
|
+
- ENUM vs reference table: 3 or fewer fixed values use ENUM; more use a reference table
|
|
49
|
+
- JSON columns: allowed only for frequently changing metadata; normalize if searchable
|
|
50
|
+
|
|
51
|
+
### Step 2: Relationship Concretization
|
|
52
|
+
|
|
53
|
+
For each relationship in the entity catalog:
|
|
54
|
+
|
|
55
|
+
- Determine FK placement (which table holds the FK)
|
|
56
|
+
- 1:N relationships: FK goes on the "many" side
|
|
57
|
+
- 1:1 relationships: FK goes on the dependent/optional side
|
|
58
|
+
- N:M relationships: create a junction table with two FKs
|
|
59
|
+
- Specify delete/update rules for every FK:
|
|
60
|
+
- CASCADE: child has no meaning without parent
|
|
61
|
+
- SET NULL: child can exist independently
|
|
62
|
+
- RESTRICT: prevent deletion when children exist
|
|
63
|
+
- Identify junction tables for many-to-many relationships and name them using either alphabetical convention (`entity_a_entity_b`) or a domain-specific name if one exists
|
|
64
|
+
- Define junction table attributes (if any beyond the two FKs, e.g., `role`, `quantity`, `start_date`)
|
|
65
|
+
- Update the Mermaid ERD in `specs/data-model.md` to reflect all FK placements and junction tables
|
|
66
|
+
|
|
67
|
+
### Step 3: Business Rule Auto-Derivation
|
|
68
|
+
|
|
69
|
+
Systematically scan the refined model and derive rules. Number each rule as BR-001, BR-002, etc.
|
|
70
|
+
|
|
71
|
+
**Derivation patterns:**
|
|
72
|
+
|
|
73
|
+
| Model Element | Rule Type | Example |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| NOT NULL constraint | Required field | "BR-001: User.email is required" |
|
|
76
|
+
| UNIQUE constraint | Uniqueness | "BR-002: User.email must be unique" |
|
|
77
|
+
| FK + CASCADE | Cascade delete | "BR-003: Deleting a Project deletes all Tasks" |
|
|
78
|
+
| FK + RESTRICT | Referential guard | "BR-004: Cannot delete Category with existing Products" |
|
|
79
|
+
| ENUM values | Validity domain | "BR-005: Order.status must be one of: draft, confirmed, shipped, delivered" |
|
|
80
|
+
| State transitions | Transition paths | "BR-006: Order can only transition draft->confirmed->shipped->delivered" |
|
|
81
|
+
| Derived attributes | Computation | "BR-007: Order.total = SUM(LineItem.subtotal)" |
|
|
82
|
+
| CHECK constraints | Range/format | "BR-008: Product.price must be >= 0" |
|
|
83
|
+
|
|
84
|
+
For each rule, record: entity, type (constraint / derivation / transition / validity), description, enforcement location (DB constraint / application logic / both), and implementation approach.
|
|
85
|
+
|
|
86
|
+
**Rule numbering:** Continue from the last BR-xxx number if Phase 0/1 already defined rules. Do not renumber existing rules.
|
|
87
|
+
|
|
88
|
+
**Cross-entity rules:** Some rules span multiple entities (cascade constraints, aggregate rules, cross-reference rules). These require explicit documentation since they cannot be enforced by a single column constraint.
|
|
89
|
+
|
|
90
|
+
**Conflict resolution:** If an auto-derived rule conflicts with an existing rule from Phase 0/1, flag the conflict to the user, present both versions, and let the user decide. Record unresolved conflicts in `docs/info-debt.md`.
|
|
91
|
+
|
|
92
|
+
**For detailed step-by-step procedures, see:** `references/phase2-procedure.md`
|
|
93
|
+
|
|
94
|
+
### Step 4: Design Decision Questions
|
|
95
|
+
|
|
96
|
+
Present questions to the user for decisions that cannot be auto-derived:
|
|
97
|
+
|
|
98
|
+
1. **Large data storage** -- inline vs external storage (S3, etc.) for binary/large text fields
|
|
99
|
+
2. **Soft delete scope** -- which entities use `deleted_at` pattern vs hard delete
|
|
100
|
+
3. **Multi-tenancy** -- tenant isolation strategy (row-level, schema-level, or none)
|
|
101
|
+
4. **Audit trail** -- which entities need history tracking (who changed what, when)
|
|
102
|
+
5. **Project-specific decisions** -- any domain-specific architectural choices
|
|
103
|
+
|
|
104
|
+
Record each decision in `specs/data-model.md` under "Design Decisions" with an ID (D-01, D-02, etc.), topic, choice, rationale, and date. Apply decisions immediately by updating the entity catalog: add `deleted_at` columns where soft delete applies, add `tenant_id` where multi-tenancy applies, add audit columns where history tracking is needed.
|
|
105
|
+
|
|
106
|
+
Skip question categories that are clearly not applicable to the project. For example, skip multi-tenancy for single-tenant applications, skip audit trail for prototype projects.
|
|
107
|
+
|
|
108
|
+
### Step 5: Artifact Updates
|
|
109
|
+
|
|
110
|
+
Update artifacts in this specific order to maintain consistency:
|
|
111
|
+
|
|
112
|
+
1. **`specs/entity-catalog.md`** -- Add all refined attributes with concrete data types, constraints, indexes. Verify every entity has: all attributes typed, NOT NULL/DEFAULT/UNIQUE specified, `created_at`/`updated_at` present, soft delete and tenant columns added per design decisions, all relationships listing FK column, cardinality, and delete rule.
|
|
113
|
+
|
|
114
|
+
2. **`specs/data-model.md`** -- Update the Mermaid ERD to reflect all entities, attributes, relationships, and junction tables. Populate the "Index Strategy" section. Populate the "Design Decisions" table with all D-xx entries.
|
|
115
|
+
|
|
116
|
+
3. **`docs/business-rules.md`** -- Add all derived rules (BR-001+) with full metadata: entity, type, description, enforcement location, and implementation approach. Ensure no duplicate or conflicting rules exist.
|
|
117
|
+
|
|
118
|
+
## Artifacts Produced
|
|
119
|
+
|
|
120
|
+
Upon completion, the following files will be created or updated:
|
|
121
|
+
|
|
122
|
+
| File | Status | Content |
|
|
123
|
+
|---|---|---|
|
|
124
|
+
| `specs/entity-catalog.md` | Updated | Data types, constraints, indexes added |
|
|
125
|
+
| `specs/data-model.md` | Updated | ERD, FK details, junction tables, design decisions |
|
|
126
|
+
| `docs/business-rules.md` | Updated | All derived business rules (BR-xxx) |
|
|
127
|
+
| `.iddd/preview/erd-phase2.html` | Created | ERD preview |
|
|
128
|
+
|
|
129
|
+
## Preview Integration
|
|
130
|
+
|
|
131
|
+
After completing all five steps, generate visual previews for user review:
|
|
132
|
+
|
|
133
|
+
1. **Generate ERD preview:** Render the finalized Mermaid ERD from `specs/data-model.md` into `.iddd/preview/erd-phase2.html`. The HTML file should embed the Mermaid library and render the ERD diagram with full entity details, relationships, and cardinality notation.
|
|
134
|
+
|
|
135
|
+
2. **Start preview server** with the ERD file available so the user can review in the browser. The server serves files from the `.iddd/preview/` directory.
|
|
136
|
+
|
|
137
|
+
Use `/id3-preview` to manually start or restart the preview server at any time.
|
|
138
|
+
|
|
139
|
+
## Version Header and Changelog
|
|
140
|
+
|
|
141
|
+
Upon Phase 2 completion, update YAML frontmatter in `specs/entity-catalog.md` and `specs/data-model.md`:
|
|
142
|
+
|
|
143
|
+
```yaml
|
|
144
|
+
---
|
|
145
|
+
version: "1.0"
|
|
146
|
+
last_verified: "YYYY-MM-DD"
|
|
147
|
+
phase: "Phase 2 Complete"
|
|
148
|
+
entity_count: N
|
|
149
|
+
rule_count: N
|
|
150
|
+
audit_status: "clean"
|
|
151
|
+
---
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Add an entry to `docs/model-changelog.md` following Keep a Changelog format:
|
|
155
|
+
|
|
156
|
+
```markdown
|
|
157
|
+
## [1.0] -- YYYY-MM-DD
|
|
158
|
+
### Phase 2 Complete
|
|
159
|
+
- N entities: logical model finalized
|
|
160
|
+
- M business rules derived
|
|
161
|
+
- Design decisions: D-01 (...), D-02 (...), ...
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Completion Message
|
|
165
|
+
|
|
166
|
+
When all steps are finished, display:
|
|
167
|
+
|
|
168
|
+
> "Logical model finalized. Phase 2 derived N business rules. Proceed to Phase 2.5 (/id3-design-ui) for UI design and implementation."
|
|
169
|
+
|
|
170
|
+
Replace N with actual count from the session.
|