decision-memory 0.1.3 → 0.1.5
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/dist/index.cjs +1 -1
- package/package.json +1 -1
- package/dist/chunk-43MFRJ7K.js +0 -389
- package/dist/chunk-5VFZ3YOK.js +0 -428
- package/dist/index.js +0 -3159
- package/dist/mcp.js +0 -21082
package/dist/index.cjs
CHANGED
|
@@ -3541,7 +3541,7 @@ function summaryCommand(options) {
|
|
|
3541
3541
|
|
|
3542
3542
|
// packages/cli/src/index.ts
|
|
3543
3543
|
var program2 = new Command();
|
|
3544
|
-
program2.name("decision-memory").description("Claude Code i\xE7in karar haf\u0131zas\u0131 arac\u0131").version("0.1.
|
|
3544
|
+
program2.name("decision-memory").description("Claude Code i\xE7in karar haf\u0131zas\u0131 arac\u0131").version("0.1.5");
|
|
3545
3545
|
program2.command("init").description("Proje k\xF6k\xFCnde DECISIONS.toon dosyas\u0131 olu\u015Fturur").option("-p, --project <name>", "Proje ad\u0131").option("-d, --dir <path>", "Dizin (varsay\u0131lan: \xE7al\u0131\u015Fma dizini)").action((options) => initCommand(options));
|
|
3546
3546
|
program2.command("log").description("Yeni bir karar ekler (interaktif veya flag'lerle)").option("-t, --topic <topic>", "Konu (\xF6rn: auth, database)").option("-d, --decision <text>", "Al\u0131nan karar").option("-r, --rationale <text>", "Gerek\xE7e").option("-i, --impact <level>", "Etki: low|medium|high|critical").option("--tags <tags>", "Etiketler (virg\xFClle ayr\u0131lm\u0131\u015F)").option("-f, --file <path>", "DECISIONS.toon dosya yolu").action(async (options) => {
|
|
3547
3547
|
await logCommand(options);
|
package/package.json
CHANGED
package/dist/chunk-43MFRJ7K.js
DELETED
|
@@ -1,389 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// packages/core/src/parser.ts
|
|
4
|
-
function splitCsvRow(line) {
|
|
5
|
-
const fields = [];
|
|
6
|
-
let current = "";
|
|
7
|
-
let inQuote = false;
|
|
8
|
-
let i = 0;
|
|
9
|
-
while (i < line.length) {
|
|
10
|
-
const ch = line[i];
|
|
11
|
-
if (inQuote) {
|
|
12
|
-
if (ch === "\\") {
|
|
13
|
-
const next = line[i + 1];
|
|
14
|
-
if (next === '"') {
|
|
15
|
-
current += '"';
|
|
16
|
-
i += 2;
|
|
17
|
-
continue;
|
|
18
|
-
} else if (next === "n") {
|
|
19
|
-
current += "\n";
|
|
20
|
-
i += 2;
|
|
21
|
-
continue;
|
|
22
|
-
} else if (next === "\\") {
|
|
23
|
-
current += "\\";
|
|
24
|
-
i += 2;
|
|
25
|
-
continue;
|
|
26
|
-
}
|
|
27
|
-
current += ch;
|
|
28
|
-
} else if (ch === '"') {
|
|
29
|
-
inQuote = false;
|
|
30
|
-
} else {
|
|
31
|
-
current += ch;
|
|
32
|
-
}
|
|
33
|
-
} else {
|
|
34
|
-
if (ch === '"') {
|
|
35
|
-
inQuote = true;
|
|
36
|
-
} else if (ch === ",") {
|
|
37
|
-
fields.push(current);
|
|
38
|
-
current = "";
|
|
39
|
-
} else {
|
|
40
|
-
current += ch;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
i++;
|
|
44
|
-
}
|
|
45
|
-
fields.push(current);
|
|
46
|
-
return fields;
|
|
47
|
-
}
|
|
48
|
-
function parseTableHeader(line) {
|
|
49
|
-
const match = line.match(/^decisions\[(\d+)\]\{([^}]+)\}:$/);
|
|
50
|
-
if (!match) return null;
|
|
51
|
-
return {
|
|
52
|
-
count: parseInt(match[1], 10),
|
|
53
|
-
fields: match[2].split(",")
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
function isSummaryLine(line) {
|
|
57
|
-
return line.startsWith("summary{");
|
|
58
|
-
}
|
|
59
|
-
function parseDecisionRow(fields, values) {
|
|
60
|
-
const get = (name) => {
|
|
61
|
-
const idx = fields.indexOf(name);
|
|
62
|
-
return idx >= 0 ? values[idx] ?? "" : "";
|
|
63
|
-
};
|
|
64
|
-
const impactRaw = get("impact");
|
|
65
|
-
const validImpacts = ["low", "medium", "high", "critical"];
|
|
66
|
-
const impact = validImpacts.includes(impactRaw) ? impactRaw : "medium";
|
|
67
|
-
const tagsRaw = get("tags");
|
|
68
|
-
const tags = tagsRaw ? tagsRaw.split("|").filter(Boolean) : [];
|
|
69
|
-
return {
|
|
70
|
-
id: get("id"),
|
|
71
|
-
ts: get("ts"),
|
|
72
|
-
topic: get("topic"),
|
|
73
|
-
decision: get("decision"),
|
|
74
|
-
rationale: get("rationale"),
|
|
75
|
-
impact,
|
|
76
|
-
tags
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
function parseDecisionFile(content) {
|
|
80
|
-
const lines = content.split("\n");
|
|
81
|
-
const meta = {
|
|
82
|
-
project: "unknown",
|
|
83
|
-
version: "1",
|
|
84
|
-
created: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
|
|
85
|
-
updated: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)
|
|
86
|
-
};
|
|
87
|
-
const decisions = [];
|
|
88
|
-
let tableFields = [];
|
|
89
|
-
let inTable = false;
|
|
90
|
-
let inSummary = false;
|
|
91
|
-
for (const rawLine of lines) {
|
|
92
|
-
const line = rawLine.trimEnd();
|
|
93
|
-
if (!line || line.startsWith("#")) continue;
|
|
94
|
-
if (isSummaryLine(line)) {
|
|
95
|
-
inTable = false;
|
|
96
|
-
inSummary = true;
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
if (inSummary) continue;
|
|
100
|
-
const tableHeader = parseTableHeader(line);
|
|
101
|
-
if (tableHeader) {
|
|
102
|
-
tableFields = tableHeader.fields;
|
|
103
|
-
inTable = true;
|
|
104
|
-
continue;
|
|
105
|
-
}
|
|
106
|
-
if (inTable && tableFields.length > 0) {
|
|
107
|
-
const values = splitCsvRow(line);
|
|
108
|
-
if (values.length >= tableFields.length) {
|
|
109
|
-
decisions.push(parseDecisionRow(tableFields, values));
|
|
110
|
-
}
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
const colonIdx = line.indexOf(":");
|
|
114
|
-
if (colonIdx > 0) {
|
|
115
|
-
const key = line.slice(0, colonIdx).trim();
|
|
116
|
-
const value = line.slice(colonIdx + 1).trim();
|
|
117
|
-
if (key === "project") meta.project = value;
|
|
118
|
-
else if (key === "version") meta.version = value;
|
|
119
|
-
else if (key === "created") meta.created = value;
|
|
120
|
-
else if (key === "updated") meta.updated = value;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
return { meta, decisions };
|
|
124
|
-
}
|
|
125
|
-
function serializeDecisionRow(d) {
|
|
126
|
-
const escapeField = (s) => {
|
|
127
|
-
if (s.includes(",") || s.includes('"') || s.includes("\n")) {
|
|
128
|
-
return '"' + s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n") + '"';
|
|
129
|
-
}
|
|
130
|
-
return s;
|
|
131
|
-
};
|
|
132
|
-
const tags = d.tags.join("|");
|
|
133
|
-
return [
|
|
134
|
-
d.id,
|
|
135
|
-
d.ts,
|
|
136
|
-
d.topic,
|
|
137
|
-
escapeField(d.decision),
|
|
138
|
-
escapeField(d.rationale),
|
|
139
|
-
d.impact,
|
|
140
|
-
tags
|
|
141
|
-
].join(",");
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// packages/core/src/searcher.ts
|
|
145
|
-
var IMPACT_WEIGHT = {
|
|
146
|
-
critical: 4,
|
|
147
|
-
high: 3,
|
|
148
|
-
medium: 2,
|
|
149
|
-
low: 1
|
|
150
|
-
};
|
|
151
|
-
function computeMatchedFields(d, keywords, tags) {
|
|
152
|
-
const matched = [];
|
|
153
|
-
const kws = keywords.map((k) => k.toLowerCase());
|
|
154
|
-
if (tags.length > 0 && tags.some((t) => d.tags.map((dt) => dt.toLowerCase()).includes(t))) {
|
|
155
|
-
matched.push("tags");
|
|
156
|
-
}
|
|
157
|
-
if (kws.some((k) => d.topic.toLowerCase().includes(k))) matched.push("topic");
|
|
158
|
-
if (kws.some((k) => d.decision.toLowerCase().includes(k))) matched.push("decision");
|
|
159
|
-
if (kws.some((k) => d.rationale.toLowerCase().includes(k))) matched.push("rationale");
|
|
160
|
-
return matched;
|
|
161
|
-
}
|
|
162
|
-
function searchDecisions(file, query) {
|
|
163
|
-
const keywords = (query.keywords ?? []).map((k) => k.toLowerCase());
|
|
164
|
-
const tags = (query.tags ?? []).map((t) => t.toLowerCase());
|
|
165
|
-
const impactFilter = query.impact;
|
|
166
|
-
const limit = query.limit ?? 5;
|
|
167
|
-
if (keywords.length === 0 && tags.length === 0 && !impactFilter) {
|
|
168
|
-
return file.decisions.slice(-limit).reverse().map((d) => ({ decision: d, matchedFields: [] }));
|
|
169
|
-
}
|
|
170
|
-
const results = [];
|
|
171
|
-
for (const d of file.decisions) {
|
|
172
|
-
if (impactFilter && d.impact !== impactFilter) continue;
|
|
173
|
-
const decisionLower = d.decision.toLowerCase();
|
|
174
|
-
const rationaleLower = d.rationale.toLowerCase();
|
|
175
|
-
const topicLower = d.topic.toLowerCase();
|
|
176
|
-
const tagsLower = d.tags.map((t) => t.toLowerCase());
|
|
177
|
-
const matchesTag = tags.length === 0 || tags.some((t) => tagsLower.includes(t));
|
|
178
|
-
const matchesKeyword = keywords.length === 0 || keywords.some(
|
|
179
|
-
(k) => topicLower.includes(k) || decisionLower.includes(k) || rationaleLower.includes(k) || tagsLower.some((t) => t.includes(k))
|
|
180
|
-
);
|
|
181
|
-
if (matchesTag && matchesKeyword) {
|
|
182
|
-
results.push({
|
|
183
|
-
decision: d,
|
|
184
|
-
matchedFields: computeMatchedFields(d, keywords, tags)
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
results.sort((a, b) => {
|
|
189
|
-
const weightDiff = IMPACT_WEIGHT[b.decision.impact] - IMPACT_WEIGHT[a.decision.impact];
|
|
190
|
-
if (weightDiff !== 0) return weightDiff;
|
|
191
|
-
return b.decision.ts.localeCompare(a.decision.ts);
|
|
192
|
-
});
|
|
193
|
-
return results.slice(0, limit);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// packages/core/src/summary.ts
|
|
197
|
-
var HIGH_IMPACT = ["high", "critical"];
|
|
198
|
-
function topTopics(decisions, n = 5) {
|
|
199
|
-
const counts = /* @__PURE__ */ new Map();
|
|
200
|
-
for (const d of decisions) {
|
|
201
|
-
counts.set(d.topic, (counts.get(d.topic) ?? 0) + 1);
|
|
202
|
-
}
|
|
203
|
-
return [...counts.entries()].sort((a, b) => b[1] - a[1]).slice(0, n).map(([topic]) => topic);
|
|
204
|
-
}
|
|
205
|
-
function buildContextSummary(file, options = {}) {
|
|
206
|
-
const { includeRecent = true } = options;
|
|
207
|
-
const { decisions } = file;
|
|
208
|
-
const highImpactCount = decisions.filter(
|
|
209
|
-
(d) => HIGH_IMPACT.includes(d.impact)
|
|
210
|
-
).length;
|
|
211
|
-
const lastUpdated = decisions.length > 0 ? decisions[decisions.length - 1].ts.slice(0, 10) : file.meta.updated;
|
|
212
|
-
const recentDecisions = includeRecent ? decisions.filter((d) => HIGH_IMPACT.includes(d.impact)).slice(-5).reverse() : [];
|
|
213
|
-
return {
|
|
214
|
-
total: decisions.length,
|
|
215
|
-
highImpactCount,
|
|
216
|
-
lastUpdated,
|
|
217
|
-
topTopics: topTopics(decisions),
|
|
218
|
-
recentDecisions
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
function buildSummaryBlock(file) {
|
|
222
|
-
const summary = buildContextSummary(file, { includeRecent: false });
|
|
223
|
-
const topics = summary.topTopics.join("|");
|
|
224
|
-
return `summary{total,high_impact,last_updated,top_topics}:
|
|
225
|
-
${summary.total},${summary.highImpactCount},${summary.lastUpdated},${topics}`;
|
|
226
|
-
}
|
|
227
|
-
function formatContextSummaryAsToon(file, includeRecent = true) {
|
|
228
|
-
const summary = buildContextSummary(file, { includeRecent });
|
|
229
|
-
const topics = summary.topTopics.join("|");
|
|
230
|
-
let output = `summary{total,high_impact,last_updated,top_topics}:
|
|
231
|
-
`;
|
|
232
|
-
output += `${summary.total},${summary.highImpactCount},${summary.lastUpdated},${topics}`;
|
|
233
|
-
if (includeRecent && summary.recentDecisions.length > 0) {
|
|
234
|
-
output += `
|
|
235
|
-
|
|
236
|
-
recent_high_impact[${summary.recentDecisions.length}]{id,ts,topic,decision,impact}:
|
|
237
|
-
`;
|
|
238
|
-
for (const d of summary.recentDecisions) {
|
|
239
|
-
const decisionField = d.decision.includes(",") ? `"${d.decision}"` : d.decision;
|
|
240
|
-
output += `${d.id},${d.ts},${d.topic},${decisionField},${d.impact}
|
|
241
|
-
`;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
return output.trimEnd();
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// packages/core/src/writer.ts
|
|
248
|
-
import * as fs from "fs";
|
|
249
|
-
import * as path from "path";
|
|
250
|
-
import * as os from "os";
|
|
251
|
-
|
|
252
|
-
// packages/core/src/idgen.ts
|
|
253
|
-
var ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
254
|
-
function encodeBase36(n) {
|
|
255
|
-
if (n === 0) return "0";
|
|
256
|
-
let result = "";
|
|
257
|
-
while (n > 0) {
|
|
258
|
-
result = ALPHABET[n % 36] + result;
|
|
259
|
-
n = Math.floor(n / 36);
|
|
260
|
-
}
|
|
261
|
-
return result;
|
|
262
|
-
}
|
|
263
|
-
function generateNextId(currentCount) {
|
|
264
|
-
const next = currentCount + 1;
|
|
265
|
-
if (next <= 999) {
|
|
266
|
-
return `D${String(next).padStart(3, "0")}`;
|
|
267
|
-
}
|
|
268
|
-
const encoded = encodeBase36(next).padStart(4, "0");
|
|
269
|
-
return `D${encoded}`;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// packages/core/src/writer.ts
|
|
273
|
-
function initDecisionFile(filePath, projectName) {
|
|
274
|
-
if (fs.existsSync(filePath)) {
|
|
275
|
-
throw new Error(`Dosya zaten var: ${filePath}`);
|
|
276
|
-
}
|
|
277
|
-
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
278
|
-
const content = `# decision-memory v1
|
|
279
|
-
project: ${projectName}
|
|
280
|
-
version: 1
|
|
281
|
-
created: ${today}
|
|
282
|
-
updated: ${today}
|
|
283
|
-
|
|
284
|
-
decisions[0]{id,ts,topic,decision,rationale,impact,tags}:
|
|
285
|
-
|
|
286
|
-
summary{total,high_impact,last_updated,top_topics}:
|
|
287
|
-
0,0,${today},
|
|
288
|
-
`;
|
|
289
|
-
fs.writeFileSync(filePath, content, "utf-8");
|
|
290
|
-
}
|
|
291
|
-
function updateDecisionCount(filePath, newCount) {
|
|
292
|
-
const content = fs.readFileSync(filePath, "utf-8");
|
|
293
|
-
const updated = content.replace(
|
|
294
|
-
/^decisions\[(\d+)\]\{/m,
|
|
295
|
-
`decisions[${newCount}]{`
|
|
296
|
-
);
|
|
297
|
-
const tmpPath = filePath + ".tmp";
|
|
298
|
-
fs.writeFileSync(tmpPath, updated, "utf-8");
|
|
299
|
-
fs.renameSync(tmpPath, filePath);
|
|
300
|
-
}
|
|
301
|
-
function updateSummaryBlock(filePath) {
|
|
302
|
-
const content = fs.readFileSync(filePath, "utf-8");
|
|
303
|
-
const file = parseDecisionFile(content);
|
|
304
|
-
const summaryBlock = buildSummaryBlock(file);
|
|
305
|
-
const summaryStart = content.indexOf("\nsummary{");
|
|
306
|
-
let base;
|
|
307
|
-
if (summaryStart >= 0) {
|
|
308
|
-
base = content.slice(0, summaryStart);
|
|
309
|
-
} else {
|
|
310
|
-
base = content.trimEnd();
|
|
311
|
-
}
|
|
312
|
-
const newContent = base + "\n" + summaryBlock + "\n";
|
|
313
|
-
const tmpPath = filePath + ".tmp";
|
|
314
|
-
fs.writeFileSync(tmpPath, newContent, "utf-8");
|
|
315
|
-
fs.renameSync(tmpPath, filePath);
|
|
316
|
-
}
|
|
317
|
-
function appendDecision(filePath, input) {
|
|
318
|
-
if (!fs.existsSync(filePath)) {
|
|
319
|
-
throw new Error(`DECISIONS.toon dosyas\u0131 bulunamad\u0131: ${filePath}
|
|
320
|
-
\xD6nce 'decision-memory init' komutunu \xE7al\u0131\u015Ft\u0131r\u0131n.`);
|
|
321
|
-
}
|
|
322
|
-
const content = fs.readFileSync(filePath, "utf-8");
|
|
323
|
-
const countMatch = content.match(/^decisions\[(\d+)\]/m);
|
|
324
|
-
const currentCount = countMatch ? parseInt(countMatch[1], 10) : 0;
|
|
325
|
-
const id = generateNextId(currentCount);
|
|
326
|
-
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "Z").replace(/:\d{2}Z$/, "Z");
|
|
327
|
-
const decision = {
|
|
328
|
-
id,
|
|
329
|
-
ts,
|
|
330
|
-
topic: input.topic,
|
|
331
|
-
decision: input.decision,
|
|
332
|
-
rationale: input.rationale,
|
|
333
|
-
impact: input.impact,
|
|
334
|
-
tags: input.tags
|
|
335
|
-
};
|
|
336
|
-
const row = serializeDecisionRow(decision);
|
|
337
|
-
const lines = content.split("\n");
|
|
338
|
-
let tableEnd = -1;
|
|
339
|
-
let inTable = false;
|
|
340
|
-
for (let i = 0; i < lines.length; i++) {
|
|
341
|
-
if (/^decisions\[\d+\]\{/.test(lines[i])) {
|
|
342
|
-
inTable = true;
|
|
343
|
-
tableEnd = i;
|
|
344
|
-
continue;
|
|
345
|
-
}
|
|
346
|
-
if (inTable) {
|
|
347
|
-
if (lines[i].startsWith("summary{") || lines[i] === "" && tableEnd >= 0) {
|
|
348
|
-
if (lines[i].startsWith("summary{")) {
|
|
349
|
-
tableEnd = i - 1;
|
|
350
|
-
} else {
|
|
351
|
-
tableEnd = i - 1;
|
|
352
|
-
}
|
|
353
|
-
break;
|
|
354
|
-
}
|
|
355
|
-
if (lines[i] !== "") {
|
|
356
|
-
tableEnd = i;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
lines.splice(tableEnd + 1, 0, row);
|
|
361
|
-
const newContent = lines.join("\n");
|
|
362
|
-
const tmpPath = filePath + ".tmp";
|
|
363
|
-
fs.writeFileSync(tmpPath, newContent, "utf-8");
|
|
364
|
-
fs.renameSync(tmpPath, filePath);
|
|
365
|
-
updateDecisionCount(filePath, currentCount + 1);
|
|
366
|
-
updateSummaryBlock(filePath);
|
|
367
|
-
return decision;
|
|
368
|
-
}
|
|
369
|
-
function resolveDecisionFilePath(cwd) {
|
|
370
|
-
if (process.env.DECISION_MEMORY_FILE) {
|
|
371
|
-
return process.env.DECISION_MEMORY_FILE;
|
|
372
|
-
}
|
|
373
|
-
const dir = cwd ?? process.cwd();
|
|
374
|
-
const local = path.join(dir, "DECISIONS.toon");
|
|
375
|
-
if (fs.existsSync(local)) return local;
|
|
376
|
-
const globalDir = path.join(os.homedir(), ".decision-memory");
|
|
377
|
-
fs.mkdirSync(globalDir, { recursive: true });
|
|
378
|
-
return path.join(globalDir, "global.toon");
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
export {
|
|
382
|
-
parseDecisionFile,
|
|
383
|
-
serializeDecisionRow,
|
|
384
|
-
searchDecisions,
|
|
385
|
-
formatContextSummaryAsToon,
|
|
386
|
-
initDecisionFile,
|
|
387
|
-
appendDecision,
|
|
388
|
-
resolveDecisionFilePath
|
|
389
|
-
};
|