@unlaxer/dve-toolkit 4.1.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.
@@ -0,0 +1,169 @@
1
+ // DVE Data Model v2 — Node/Edge/Query types
2
+
3
+ // ─── Nodes ───
4
+
5
+ export interface Session {
6
+ id: string;
7
+ date: string;
8
+ theme: string;
9
+ flow: "quick" | "design-review" | "brainstorm" | string;
10
+ structure: "roundtable" | "tribunal" | "wargame" | "pitch" | "consult" | "investigation" | string;
11
+ characters: string[];
12
+ file_path: string;
13
+ content?: string; // full markdown content (included in graph.json for static rendering)
14
+ }
15
+
16
+ export interface Gap {
17
+ id: string; // "{session_id}#G-{n}"
18
+ session_id: string;
19
+ summary: string;
20
+ category: string;
21
+ severity: "Critical" | "High" | "Medium" | "Low" | "Unknown";
22
+ status: "Active" | "Void" | "Archived";
23
+ line_ref: number;
24
+ discovered_by: string[];
25
+ }
26
+
27
+ export interface Decision {
28
+ id: string; // "DD-001"
29
+ title: string;
30
+ date: string;
31
+ rationale: string;
32
+ status: "active" | "overturned";
33
+ supersedes: string[];
34
+ superseded_by: string[];
35
+ gap_refs: string[];
36
+ session_refs: string[];
37
+ file_path: string;
38
+ content?: string; // full markdown content
39
+ }
40
+
41
+ export interface Spec {
42
+ id: string;
43
+ title: string;
44
+ type: "UC" | "TECH" | "DD" | "DQ" | "ACT";
45
+ status: "draft" | "reviewed" | "migrated";
46
+ decision_refs: string[];
47
+ migrated_to?: string;
48
+ file_path: string;
49
+ content?: string;
50
+ }
51
+
52
+ export type AnnotationAction = "comment" | "fork" | "overturn" | "constrain" | "drift";
53
+
54
+ export interface Annotation {
55
+ id: string; // "A-001"
56
+ target: { type: "session" | "gap" | "decision"; id: string };
57
+ target_line?: number;
58
+ author: string;
59
+ date: string;
60
+ body: string;
61
+ action: AnnotationAction;
62
+ }
63
+
64
+ // ─── Edges ───
65
+
66
+ export type EdgeType = "contains" | "discovers" | "resolves" | "supersedes" | "annotates" | "produces" | "implements";
67
+ export type Confidence = "explicit" | "inferred";
68
+
69
+ export interface Edge {
70
+ source: string;
71
+ target: string;
72
+ type: EdgeType;
73
+ confidence: Confidence;
74
+ evidence?: string;
75
+ }
76
+
77
+ // ─── Graph Node wrapper ───
78
+
79
+ export type NodeType = "session" | "dialogue" | "gap" | "decision" | "spec" | "annotation";
80
+
81
+ export interface GraphNode {
82
+ type: NodeType;
83
+ id: string;
84
+ data: Session | Gap | Decision | Spec | Annotation;
85
+ confidence: number; // 0.0 - 1.0
86
+ warnings: string[];
87
+ }
88
+
89
+ // ─── Graph output ───
90
+
91
+ export interface DVEGraph {
92
+ version: string;
93
+ generated_at: string;
94
+ stats: {
95
+ sessions: number;
96
+ gaps: number;
97
+ decisions: number;
98
+ annotations: number;
99
+ specs?: number;
100
+ };
101
+ nodes: GraphNode[];
102
+ edges: Edge[];
103
+ warnings: { file: string; message: string }[];
104
+ glossary?: GlossaryEntry[];
105
+ }
106
+
107
+ // ─── Parse result ───
108
+
109
+ export interface ParseResult<T> {
110
+ node: Partial<T>;
111
+ confidence: number;
112
+ warnings: string[];
113
+ source: { file: string; line?: number };
114
+ }
115
+
116
+ // ─── Multi-project ───
117
+
118
+ export interface MultiProjectGraph {
119
+ version: string;
120
+ generated_at: string;
121
+ projects: {
122
+ name: string;
123
+ path: string;
124
+ graph: DVEGraph;
125
+ }[];
126
+ }
127
+
128
+ // ─── Glossary ───
129
+
130
+ export interface GlossaryEntry {
131
+ term: string;
132
+ definition: string;
133
+ source: string;
134
+ aliases?: string[];
135
+ }
136
+
137
+ // ─── Changelog ───
138
+
139
+ export interface Changelog {
140
+ since: string;
141
+ new_nodes: string[];
142
+ removed_nodes: string[];
143
+ changed_statuses: { id: string; from: string; to: string }[];
144
+ }
145
+
146
+ // ─── ContextBundle ───
147
+
148
+ export interface ContextBundle {
149
+ type: "dve-context-bundle";
150
+ version: "1.0.0";
151
+ origin: {
152
+ node_type: NodeType;
153
+ node_id: string;
154
+ file: string;
155
+ };
156
+ summary: {
157
+ theme: string;
158
+ date_range: string;
159
+ prior_decisions: string[];
160
+ prior_gaps: { id: string; summary: string; status: string }[];
161
+ characters_used: string[];
162
+ session_count: number;
163
+ key_dialogue_excerpt?: string;
164
+ };
165
+ new_constraints: string[];
166
+ annotations: { date: string; action: string; body: string }[];
167
+ suggested_action: "revisit" | "deep_dive" | "new_angle" | "override";
168
+ prompt_template: string;
169
+ }
package/install.sh ADDED
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # DVE toolkit installer
5
+ # Sets up dve/ directories, builds kit, installs app dependencies
6
+
7
+ REAL_PATH="$(readlink -f "$0" 2>/dev/null || realpath "$0" 2>/dev/null || echo "$0")"
8
+ SCRIPT_DIR="$(cd "$(dirname "${REAL_PATH}")" && pwd)"
9
+ TARGET_DIR="${1:-.}"
10
+
11
+ # Resolve source
12
+ SRC="${SCRIPT_DIR}"
13
+ if [ ! -f "${SRC}/version.txt" ]; then
14
+ echo "Error: Cannot find DVE toolkit files."
15
+ exit 1
16
+ fi
17
+
18
+ SRC_VERSION="$(cat "${SRC}/version.txt" 2>/dev/null || echo "unknown")"
19
+
20
+ echo "DVE toolkit — install v${SRC_VERSION}"
21
+ echo ""
22
+
23
+ # Create directories
24
+ mkdir -p "${TARGET_DIR}/dve/annotations"
25
+ mkdir -p "${TARGET_DIR}/dve/contexts"
26
+ mkdir -p "${TARGET_DIR}/dve/dist"
27
+
28
+ # Initialize .dre/ state machine if not exists
29
+ if [ ! -f "${TARGET_DIR}/.dre/state-machine.yaml" ]; then
30
+ if [ -f "${SCRIPT_DIR}/../../../dre/kit/engine/engine.js" ]; then
31
+ echo " Initializing DRE workflow engine..."
32
+ node "${SCRIPT_DIR}/../../../dre/kit/engine/engine.js" init 2>/dev/null || true
33
+ fi
34
+ fi
35
+
36
+ # Compile kit if dist doesn't exist
37
+ if [ ! -d "${SRC}/dist" ]; then
38
+ echo " Compiling DVE kit..."
39
+ (cd "${SRC}" && npx tsc 2>/dev/null || true)
40
+ fi
41
+
42
+ # Install app dependencies if needed
43
+ APP_DIR="${SCRIPT_DIR}/../app"
44
+ if [ -d "${APP_DIR}" ] && [ ! -d "${APP_DIR}/node_modules" ]; then
45
+ echo " Installing DVE app dependencies..."
46
+ (cd "${APP_DIR}" && npm install 2>/dev/null || true)
47
+ fi
48
+
49
+ # Install skills
50
+ SKILLS_DIR="${TARGET_DIR}/.claude/skills"
51
+ mkdir -p "${SKILLS_DIR}"
52
+
53
+ if [ -d "${SRC}/skills" ]; then
54
+ for SKILL in "${SRC}/skills/"*.md; do
55
+ [ -f "${SKILL}" ] || continue
56
+ FNAME="$(basename "${SKILL}")"
57
+ if [ ! -f "${SKILLS_DIR}/${FNAME}" ]; then
58
+ cp "${SKILL}" "${SKILLS_DIR}/${FNAME}"
59
+ echo " [new] .claude/skills/${FNAME}"
60
+ fi
61
+ done
62
+ fi
63
+
64
+ # Build graph.json
65
+ if [ -f "${SRC}/dist/cli/dve-tool.js" ]; then
66
+ echo ""
67
+ echo " Building graph.json..."
68
+ (cd "${TARGET_DIR}" && node "${SRC}/dist/cli/dve-tool.js" build 2>/dev/null || true)
69
+ fi
70
+
71
+ echo ""
72
+ echo "DVE toolkit v${SRC_VERSION} installed."
73
+ echo ""
74
+ echo " Usage:"
75
+ echo " node dve/kit/dist/cli/dve-tool.js build Build graph"
76
+ echo " node dve/kit/dist/cli/dve-tool.js serve Start Web UI"
77
+ echo " node dve/kit/dist/cli/dve-tool.js status Show project state"
78
+ echo ""
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@unlaxer/dve-toolkit",
3
+ "version": "4.1.0",
4
+ "description": "DVE (Decision Visualization Engine) — DGE の決定プロセスを可視化するツールキット",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/opaopa6969/DxE-suite"
10
+ },
11
+ "bin": {
12
+ "dve": "./cli/dve-tool.js"
13
+ },
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "test": "node --test test/"
17
+ },
18
+ "dependencies": {
19
+ "unified": "^11.0.0",
20
+ "remark-parse": "^11.0.0",
21
+ "remark-frontmatter": "^5.0.0",
22
+ "yaml": "^2.4.0",
23
+ "chokidar": "^4.0.0"
24
+ },
25
+ "devDependencies": {
26
+ "typescript": "^5.0.0",
27
+ "@types/node": "^22.0.0"
28
+ }
29
+ }
@@ -0,0 +1,77 @@
1
+ // Annotation parser — extract annotations from dve/annotations/*.md
2
+
3
+ import { readFileSync } from "node:fs";
4
+ import path from "node:path";
5
+ import type { Annotation, AnnotationAction, ParseResult } from "../graph/schema.js";
6
+
7
+ const VALID_ACTIONS: AnnotationAction[] = ["comment", "fork", "overturn", "constrain", "drift"];
8
+
9
+ export function parseAnnotation(filePath: string): ParseResult<Annotation> {
10
+ const content = readFileSync(filePath, "utf-8");
11
+ const lines = content.split("\n");
12
+ const warnings: string[] = [];
13
+ const stem = path.basename(filePath, ".md");
14
+
15
+ // Parse YAML frontmatter
16
+ const fm: Record<string, string> = {};
17
+ let inFrontmatter = false;
18
+ let bodyStart = 0;
19
+
20
+ for (let i = 0; i < lines.length; i++) {
21
+ const line = lines[i].trim();
22
+ if (line === "---") {
23
+ if (!inFrontmatter) {
24
+ inFrontmatter = true;
25
+ continue;
26
+ } else {
27
+ bodyStart = i + 1;
28
+ break;
29
+ }
30
+ }
31
+ if (inFrontmatter) {
32
+ const m = line.match(/^(\w[\w_]*)\s*:\s*(.+)/);
33
+ if (m) fm[m[1]] = m[2].trim();
34
+ }
35
+ }
36
+
37
+ const target = fm["target"] ?? "";
38
+ const action = fm["action"] ?? "comment";
39
+ const date = fm["date"] ?? "";
40
+ const author = fm["author"] ?? "";
41
+ const targetLine = fm["target_line"] ? parseInt(fm["target_line"], 10) : undefined;
42
+
43
+ // Body is everything after frontmatter
44
+ const body = lines.slice(bodyStart).join("\n").trim();
45
+
46
+ // Determine target type
47
+ let targetType: "session" | "gap" | "decision" = "decision";
48
+ if (target.includes("#G-")) targetType = "gap";
49
+ else if (target.startsWith("DD-")) targetType = "decision";
50
+ else targetType = "session";
51
+
52
+ // Validate action
53
+ const validAction = VALID_ACTIONS.includes(action as AnnotationAction)
54
+ ? (action as AnnotationAction)
55
+ : "comment";
56
+ if (action !== validAction) {
57
+ warnings.push(`Unknown action "${action}", defaulting to "comment"`);
58
+ }
59
+
60
+ if (!target) warnings.push("No target specified");
61
+ if (!body) warnings.push("Empty annotation body");
62
+
63
+ return {
64
+ node: {
65
+ id: `A-${stem}`,
66
+ target: { type: targetType, id: target },
67
+ target_line: targetLine,
68
+ author,
69
+ date,
70
+ body,
71
+ action: validAction,
72
+ },
73
+ confidence: target && body ? 1.0 : 0.5,
74
+ warnings,
75
+ source: { file: filePath },
76
+ };
77
+ }
@@ -0,0 +1,104 @@
1
+ // Decision parser — extract DD from dge/decisions/DD-*.md
2
+
3
+ import { readFileSync } from "node:fs";
4
+ import path from "node:path";
5
+ import type { Decision, ParseResult } from "../graph/schema.js";
6
+
7
+ const DD_ID_RE = /^DD-(\d+)/;
8
+ const FIELD_RE = /^-\s+\*{0,2}(\w[\w\s/]*?)\*{0,2}[::]\s*(.+)/;
9
+ const SESSION_LINK_RE = /\[([^\]]+)\]\(\.\.\/sessions\/([^)]+)\)/g;
10
+ const GAP_REF_RE = /#(\d+)/g;
11
+ const SUPERSEDES_RE = /Supersedes[::]\s*(DD-\d+(?:\s*,\s*DD-\d+)*)/i;
12
+ const SUPERSEDED_BY_RE = /Superseded\s+by[::]\s*(DD-\d+(?:\s*,\s*DD-\d+)*)/i;
13
+
14
+ export function parseDecision(filePath: string): ParseResult<Decision> {
15
+ const content = readFileSync(filePath, "utf-8");
16
+ const lines = content.split("\n");
17
+ const warnings: string[] = [];
18
+ const stem = path.basename(filePath, ".md");
19
+
20
+ // ID from filename
21
+ const idMatch = stem.match(DD_ID_RE);
22
+ const id = idMatch ? `DD-${idMatch[1]}` : stem;
23
+
24
+ // Title from H1
25
+ const h1 = lines.find((l) => l.startsWith("# "));
26
+ const title = h1?.replace(/^#\s+/, "").replace(/^DD-\d+[::]?\s*/, "") ?? "";
27
+
28
+ // Fields from frontmatter-style metadata
29
+ const fields: Record<string, string> = {};
30
+ for (const line of lines) {
31
+ const m = line.match(FIELD_RE);
32
+ if (m) fields[m[1].trim().toLowerCase()] = m[2].trim();
33
+ }
34
+
35
+ const date = fields["date"] ?? "";
36
+
37
+ // Session refs from links
38
+ const sessionRefs: string[] = [];
39
+ for (const match of content.matchAll(SESSION_LINK_RE)) {
40
+ const sessionFile = match[2].replace(".md", "");
41
+ sessionRefs.push(sessionFile);
42
+ }
43
+
44
+ // Gap refs from # numbers
45
+ const gapRefs: string[] = [];
46
+ const gapField = fields["gap"] ?? "";
47
+ for (const match of gapField.matchAll(GAP_REF_RE)) {
48
+ gapRefs.push(match[0]);
49
+ }
50
+
51
+ // Supersedes
52
+ const supersedes: string[] = [];
53
+ const supMatch = content.match(SUPERSEDES_RE);
54
+ if (supMatch) {
55
+ supersedes.push(...supMatch[1].split(/\s*,\s*/));
56
+ }
57
+
58
+ // Superseded by
59
+ const supersededBy: string[] = [];
60
+ const supByMatch = content.match(SUPERSEDED_BY_RE);
61
+ if (supByMatch) {
62
+ supersededBy.push(...supByMatch[1].split(/\s*,\s*/));
63
+ }
64
+
65
+ // Rationale — text under ## Rationale or ## Decision
66
+ let rationale = "";
67
+ let inRationale = false;
68
+ for (const line of lines) {
69
+ if (/^##\s+(Rationale|Decision)/i.test(line)) {
70
+ inRationale = true;
71
+ continue;
72
+ }
73
+ if (inRationale && line.startsWith("## ")) break;
74
+ if (inRationale && line.trim()) {
75
+ rationale += (rationale ? " " : "") + line.trim();
76
+ }
77
+ }
78
+
79
+ // Status — check frontmatter for explicit status, otherwise default to active
80
+ const statusField = fields["status"]?.toLowerCase();
81
+ const status = statusField === "overturned" ? "overturned" as const : "active" as const;
82
+
83
+ if (!date) warnings.push("date not found");
84
+ if (sessionRefs.length === 0) warnings.push("no session references found");
85
+
86
+ return {
87
+ node: {
88
+ id,
89
+ title,
90
+ date,
91
+ rationale,
92
+ status,
93
+ supersedes,
94
+ superseded_by: supersededBy,
95
+ gap_refs: gapRefs,
96
+ session_refs: sessionRefs,
97
+ file_path: filePath,
98
+ content,
99
+ },
100
+ confidence: date && title ? 1.0 : 0.7,
101
+ warnings,
102
+ source: { file: filePath },
103
+ };
104
+ }
@@ -0,0 +1,45 @@
1
+ // Drift detector — detect decisions that may have diverged from implementation
2
+
3
+ import { execSync } from "node:child_process";
4
+ import { existsSync } from "node:fs";
5
+ import path from "node:path";
6
+ import type { GraphNode, Decision } from "../graph/schema.js";
7
+
8
+ export interface DriftResult {
9
+ ddId: string;
10
+ commitsSince: number;
11
+ latestCommit: string;
12
+ ddDate: string;
13
+ }
14
+
15
+ export function detectDrift(ddNodes: GraphNode[], cwd: string): DriftResult[] {
16
+ const results: DriftResult[] = [];
17
+
18
+ for (const dd of ddNodes) {
19
+ const data = dd.data as Partial<Decision>;
20
+ if (!data.date || data.status === "overturned") continue;
21
+
22
+ try {
23
+ const log = execSync(
24
+ `git log --oneline --since="${data.date}" -- .`,
25
+ { cwd, encoding: "utf-8", timeout: 5000 }
26
+ ).trim();
27
+
28
+ const commits = log ? log.split("\n").length : 0;
29
+ // Heuristic: if more than 10 commits since DD date, it might have drifted
30
+ if (commits > 10) {
31
+ const latest = log.split("\n")[0] ?? "";
32
+ results.push({
33
+ ddId: dd.id,
34
+ commitsSince: commits,
35
+ latestCommit: latest,
36
+ ddDate: data.date,
37
+ });
38
+ }
39
+ } catch {
40
+ // git not available
41
+ }
42
+ }
43
+
44
+ return results;
45
+ }
@@ -0,0 +1,62 @@
1
+ // git-linker — scan git log for "Ref: DD-*" and create implements edges
2
+
3
+ import { execSync } from "node:child_process";
4
+ import type { Edge } from "../graph/schema.js";
5
+
6
+ const REF_RE = /Ref:\s*(DD-\d+)/gi;
7
+
8
+ export interface GitLink {
9
+ commit: string;
10
+ date: string;
11
+ message: string;
12
+ ddRef: string;
13
+ }
14
+
15
+ export function scanGitLog(cwd: string, maxCommits = 500): GitLink[] {
16
+ const links: GitLink[] = [];
17
+
18
+ try {
19
+ const log = execSync(
20
+ `git log --oneline --format="%H|%aI|%s" -${maxCommits}`,
21
+ { cwd, encoding: "utf-8", timeout: 10000 }
22
+ );
23
+
24
+ for (const line of log.split("\n")) {
25
+ if (!line.trim()) continue;
26
+ const [commit, date, ...msgParts] = line.split("|");
27
+ const message = msgParts.join("|");
28
+
29
+ for (const match of message.matchAll(REF_RE)) {
30
+ links.push({
31
+ commit: commit.trim(),
32
+ date: date?.trim() ?? "",
33
+ message: message.trim(),
34
+ ddRef: match[1],
35
+ });
36
+ }
37
+ }
38
+ } catch {
39
+ // git not available or not a repo — return empty
40
+ }
41
+
42
+ return links;
43
+ }
44
+
45
+ export function gitLinkerEdges(cwd: string, existingDDIds: Set<string>): Edge[] {
46
+ const links = scanGitLog(cwd);
47
+ const edges: Edge[] = [];
48
+
49
+ for (const link of links) {
50
+ if (existingDDIds.has(link.ddRef)) {
51
+ edges.push({
52
+ source: link.ddRef,
53
+ target: `commit:${link.commit.slice(0, 7)}`,
54
+ type: "implements",
55
+ confidence: "explicit",
56
+ evidence: `git commit ${link.commit.slice(0, 7)}: ${link.message}`,
57
+ });
58
+ }
59
+ }
60
+
61
+ return edges;
62
+ }
@@ -0,0 +1,116 @@
1
+ // Glossary builder — auto-extract terms from DDs, sessions, specs + custom glossary
2
+
3
+ import { readFileSync, existsSync } from "node:fs";
4
+ import path from "node:path";
5
+ import type { DVEGraph } from "../graph/schema.js";
6
+
7
+ export interface GlossaryEntry {
8
+ term: string;
9
+ definition: string;
10
+ source: string; // "DD-001" | "session:xxx" | "custom"
11
+ aliases?: string[]; // alternative forms
12
+ }
13
+
14
+ export interface Glossary {
15
+ entries: GlossaryEntry[];
16
+ }
17
+
18
+ // Auto-extract terms from graph data
19
+ export function buildGlossary(graph: DVEGraph, projectPath: string): Glossary {
20
+ const entries: GlossaryEntry[] = [];
21
+ const seen = new Set<string>();
22
+
23
+ function add(term: string, definition: string, source: string, aliases?: string[]) {
24
+ const key = term.toLowerCase();
25
+ if (seen.has(key) || term.length < 2) return;
26
+ seen.add(key);
27
+ entries.push({ term, definition, source, aliases });
28
+ }
29
+
30
+ // 1. DD titles → terms
31
+ for (const node of graph.nodes) {
32
+ if (node.type === "decision") {
33
+ const d = node.data as any;
34
+ add(node.id, d.title ?? "", node.id);
35
+ }
36
+ }
37
+
38
+ // 2. Gap categories as terms
39
+ const categories = new Set<string>();
40
+ for (const node of graph.nodes) {
41
+ if (node.type === "gap") {
42
+ const d = node.data as any;
43
+ if (d.category && d.category !== "Unknown") categories.add(d.category);
44
+ }
45
+ }
46
+ const categoryDefs: Record<string, string> = {
47
+ "missing-logic": "ロジックの欠落 — 実装すべき処理が spec に記述されていない",
48
+ "missing logic": "ロジックの欠落 — 実装すべき処理が spec に記述されていない",
49
+ "spec-impl-mismatch": "仕様と実装の不整合 — spec と実際のコードが矛盾",
50
+ "spec-impl mismatch": "仕様と実装の不整合 — spec と実際のコードが矛盾",
51
+ "error-quality": "エラー品質 — エラーメッセージやハンドリングの不足",
52
+ "error quality": "エラー品質 — エラーメッセージやハンドリングの不足",
53
+ "integration": "統合の問題 — コンポーネント間の接続や依存の欠陥",
54
+ "UX": "ユーザー体験 — 操作性、表示、フィードバックの問題",
55
+ "test-coverage": "テストカバレッジ — テストが不足している領域",
56
+ "test coverage": "テストカバレッジ — テストが不足している領域",
57
+ };
58
+ for (const cat of categories) {
59
+ add(cat, categoryDefs[cat] ?? `Gap カテゴリ: ${cat}`, "system");
60
+ }
61
+
62
+ // 3. DxE-specific terms
63
+ const dxeTerms: [string, string, string[]][] = [
64
+ ["DGE", "Design-Gap Extraction — 会話劇で設計の穴を発見するメソッド", ["Dialogue-driven Gap Extraction"]],
65
+ ["DRE", "Document Rule Engine — rules/skills/agents をパッケージ化して配布", []],
66
+ ["DVE", "Decision Visualization Engine — 決定プロセスの可視化ツール", []],
67
+ ["DDE", "Document-Deficit Extraction — ドキュメントの穴を LLM+CLI で補完", []],
68
+ ["DD", "Design Decision — 設計判断の記録。DGE session から生まれる", ["Design Decision"]],
69
+ ["Gap", "設計の穴 — DGE の会話劇で発見される未定義・矛盾・考慮漏れ", []],
70
+ ["Session", "DGE の会話劇セッション。キャラクターが設計について議論し Gap を発見する", []],
71
+ ["Annotation", "後から追加するコメント・異議・撤回。session を汚さず別レイヤーで保存", []],
72
+ ["Spec", "Gap から生成された仕様書。UC/TECH/DD/DQ/ACT の5タイプ", []],
73
+ ["ContextBundle", "DVE → DGE の橋渡しデータ。過去の文脈を復元して DGE に渡す", []],
74
+ ["orphan gap", "DD に紐づかない孤立 Gap。まだ決定されていない設計の穴", ["孤立Gap"]],
75
+ ["overturn", "決定の撤回。DD のステータスが overturned に変わる", ["撤回"]],
76
+ ["drift", "現実との乖離。DD の内容と実際のコードが食い違っている状態", ["ドリフト"]],
77
+ ["supersedes", "DD の置き換え。新しい DD が古い DD を supersede する", []],
78
+ ["enforcement", "Hook ベースのルール強制。PostToolUse + Stop hook で検証", []],
79
+ ];
80
+ for (const [term, def, aliases] of dxeTerms) {
81
+ add(term, def, "system", aliases);
82
+ }
83
+
84
+ // 4. Characters
85
+ const charDefs: Record<string, string> = {
86
+ "今泉": "The Innocent Questioner — 前提を問う。5つの問いパターン(そもそも/要するに/他にないの/誰が困る/前もそうだった)",
87
+ "ヤン": "The Lazy Strategist — 最もシンプルな解を見つける。「要らなくない?」",
88
+ "千石": "品質を守る。「お客様への侮辱です」",
89
+ "リヴァイ": "The Implementation Enforcer — 動くものを要求する。「汚い。作り直せ。」",
90
+ "深澤": "The UX Feeler — ユーザーの感情と体験を言語化する",
91
+ "ビーン": "The Data Evangelist — 「データは何て言ってる?」",
92
+ "僕": "The Small-Scale Survivor — scope を縮小する。「もっと小規模にできませんか?」",
93
+ "Red Team": "セキュリティ/攻撃視点。「競合がこうしたら?」",
94
+ "ハウス": "隠れた問題を見つける。「全員嘘をついている」",
95
+ "ソクラテス": "前提を問い続ける。「なぜそう思う? もし逆だったら?」",
96
+ };
97
+ for (const [name, def] of Object.entries(charDefs)) {
98
+ add(name, def, "character");
99
+ }
100
+
101
+ // 5. Custom glossary file
102
+ const customPath = path.join(projectPath, "dve", "glossary.json");
103
+ if (existsSync(customPath)) {
104
+ try {
105
+ const custom = JSON.parse(readFileSync(customPath, "utf-8"));
106
+ for (const entry of custom.entries ?? []) {
107
+ add(entry.term, entry.definition, "custom", entry.aliases);
108
+ }
109
+ } catch { /* ignore */ }
110
+ }
111
+
112
+ // Sort by term length descending (longer terms match first)
113
+ entries.sort((a, b) => b.term.length - a.term.length);
114
+
115
+ return { entries };
116
+ }