@pebblehouse/odin-cli 0.6.0 → 0.7.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/dist/index.js +85 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -311,7 +311,49 @@ decisionsCmd.command("get <slug> <id>").description("Get a decision by ID").acti
|
|
|
311
311
|
process.stdout.write(JSON.stringify(res) + "\n");
|
|
312
312
|
if (res.error) process.exit(1);
|
|
313
313
|
});
|
|
314
|
-
|
|
314
|
+
async function detectSupersedes(newTitle, newRationale, recentDecisions) {
|
|
315
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
316
|
+
if (!apiKey || recentDecisions.length === 0) return [];
|
|
317
|
+
const decisionList = recentDecisions.map((d) => `- ID: ${d.id} | Title: "${d.title}" | Rationale: "${d.rationale ?? "none"}"`).join("\n");
|
|
318
|
+
const prompt = `You are an architectural decision tracker. A new decision is being logged:
|
|
319
|
+
|
|
320
|
+
Title: "${newTitle}"
|
|
321
|
+
Rationale: "${newRationale ?? "none"}"
|
|
322
|
+
|
|
323
|
+
Here are recent active decisions (last 3 days):
|
|
324
|
+
${decisionList}
|
|
325
|
+
|
|
326
|
+
Which of these recent decisions, if any, does the new decision SUPERSEDE (i.e., replace, override, or contradict)?
|
|
327
|
+
|
|
328
|
+
Respond with ONLY a JSON array of IDs that are superseded. If none are superseded, respond with [].
|
|
329
|
+
Example: ["uuid-1", "uuid-2"] or []`;
|
|
330
|
+
try {
|
|
331
|
+
const res = await fetch("https://api.anthropic.com/v1/messages", {
|
|
332
|
+
method: "POST",
|
|
333
|
+
headers: {
|
|
334
|
+
"x-api-key": apiKey,
|
|
335
|
+
"anthropic-version": "2023-06-01",
|
|
336
|
+
"content-type": "application/json"
|
|
337
|
+
},
|
|
338
|
+
body: JSON.stringify({
|
|
339
|
+
model: "claude-haiku-4-5-20251001",
|
|
340
|
+
max_tokens: 256,
|
|
341
|
+
messages: [{ role: "user", content: prompt }]
|
|
342
|
+
})
|
|
343
|
+
});
|
|
344
|
+
if (!res.ok) return [];
|
|
345
|
+
const data = await res.json();
|
|
346
|
+
const text = data?.content?.[0]?.text?.trim() ?? "[]";
|
|
347
|
+
const match = text.match(/\[.*\]/s);
|
|
348
|
+
if (!match) return [];
|
|
349
|
+
const ids = JSON.parse(match[0]);
|
|
350
|
+
const validIds = new Set(recentDecisions.map((d) => d.id));
|
|
351
|
+
return ids.filter((id) => validIds.has(id));
|
|
352
|
+
} catch {
|
|
353
|
+
return [];
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
decisionsCmd.command("log <slug> <title>").description("Log a decision").option("--rationale <rationale>", "Decision rationale").option("--alternatives <alternatives>", "Alternatives considered").option("--no-auto-supersede", "Skip automatic supersede detection").action(async (slug, title, opts) => {
|
|
315
357
|
const res = await apiRequest(`/pebbles/${slug}/decisions`, {
|
|
316
358
|
method: "POST",
|
|
317
359
|
body: {
|
|
@@ -320,8 +362,48 @@ decisionsCmd.command("log <slug> <title>").description("Log a decision").option(
|
|
|
320
362
|
alternatives_considered: opts.alternatives
|
|
321
363
|
}
|
|
322
364
|
});
|
|
323
|
-
|
|
324
|
-
|
|
365
|
+
if (res.error || !res.data) {
|
|
366
|
+
process.stdout.write(JSON.stringify(res) + "\n");
|
|
367
|
+
if (res.error) process.exit(1);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
const newDecision = res.data;
|
|
371
|
+
if (opts.autoSupersede) {
|
|
372
|
+
const threeDaysAgo = /* @__PURE__ */ new Date();
|
|
373
|
+
threeDaysAgo.setDate(threeDaysAgo.getDate() - 3);
|
|
374
|
+
const recentRes = await apiRequest(`/pebbles/${slug}/decisions`, {
|
|
375
|
+
params: { limit: "100" }
|
|
376
|
+
});
|
|
377
|
+
if (recentRes.data) {
|
|
378
|
+
const recentActive = recentRes.data.filter(
|
|
379
|
+
(d) => d.id !== newDecision.id && d.status === "active" && new Date(d.created_at) >= threeDaysAgo
|
|
380
|
+
);
|
|
381
|
+
const supersededIds = await detectSupersedes(title, opts.rationale, recentActive);
|
|
382
|
+
if (supersededIds.length > 0) {
|
|
383
|
+
const updateRes = await apiRequest(`/pebbles/${slug}/decisions/${newDecision.id}`, {
|
|
384
|
+
method: "PUT",
|
|
385
|
+
body: { supersedes: supersededIds }
|
|
386
|
+
});
|
|
387
|
+
for (const id of supersededIds) {
|
|
388
|
+
await apiRequest(`/pebbles/${slug}/decisions/${id}`, {
|
|
389
|
+
method: "PUT",
|
|
390
|
+
body: { status: "superseded" }
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
if (updateRes.data) {
|
|
394
|
+
const supersededTitles = recentActive.filter((d) => supersededIds.includes(d.id)).map((d) => d.title);
|
|
395
|
+
process.stderr.write(
|
|
396
|
+
`\u26A1 Auto-superseded ${supersededIds.length} decision(s):
|
|
397
|
+
${supersededTitles.map((t) => ` \u21B3 "${t}"`).join("\n")}
|
|
398
|
+
`
|
|
399
|
+
);
|
|
400
|
+
process.stdout.write(JSON.stringify({ data: updateRes.data, error: null }) + "\n");
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
process.stdout.write(JSON.stringify({ data: newDecision, error: null }) + "\n");
|
|
325
407
|
});
|
|
326
408
|
|
|
327
409
|
// src/commands/context.ts
|