opencodekit 0.20.2 → 0.20.4

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.
Files changed (57) hide show
  1. package/dist/index.js +1 -1
  2. package/dist/template/.opencode/agent/build.md +4 -0
  3. package/dist/template/.opencode/agent/explore.md +4 -0
  4. package/dist/template/.opencode/agent/general.md +4 -0
  5. package/dist/template/.opencode/agent/plan.md +4 -0
  6. package/dist/template/.opencode/agent/review.md +4 -0
  7. package/dist/template/.opencode/agent/scout.md +4 -0
  8. package/dist/template/.opencode/command/create.md +119 -25
  9. package/dist/template/.opencode/command/design.md +1 -2
  10. package/dist/template/.opencode/command/health.md +234 -0
  11. package/dist/template/.opencode/command/init-user.md +15 -0
  12. package/dist/template/.opencode/command/plan.md +3 -4
  13. package/dist/template/.opencode/command/pr.md +13 -0
  14. package/dist/template/.opencode/command/research.md +15 -3
  15. package/dist/template/.opencode/command/review-codebase.md +11 -1
  16. package/dist/template/.opencode/command/ship.md +72 -8
  17. package/dist/template/.opencode/command/status.md +1 -1
  18. package/dist/template/.opencode/command/ui-review.md +0 -1
  19. package/dist/template/.opencode/command/ui-slop-check.md +1 -1
  20. package/dist/template/.opencode/command/verify.md +11 -1
  21. package/dist/template/.opencode/memory.db +0 -0
  22. package/dist/template/.opencode/memory.db-shm +0 -0
  23. package/dist/template/.opencode/memory.db-wal +0 -0
  24. package/dist/template/.opencode/opencode.json +1678 -1677
  25. package/dist/template/.opencode/plugin/README.md +1 -1
  26. package/dist/template/.opencode/plugin/lib/compact.ts +194 -0
  27. package/dist/template/.opencode/plugin/lib/compile.ts +253 -0
  28. package/dist/template/.opencode/plugin/lib/db/graph.ts +253 -0
  29. package/dist/template/.opencode/plugin/lib/db/observations.ts +8 -3
  30. package/dist/template/.opencode/plugin/lib/db/schema.ts +96 -5
  31. package/dist/template/.opencode/plugin/lib/db/types.ts +73 -0
  32. package/dist/template/.opencode/plugin/lib/index-generator.ts +170 -0
  33. package/dist/template/.opencode/plugin/lib/lint.ts +359 -0
  34. package/dist/template/.opencode/plugin/lib/memory-admin-tools.ts +78 -4
  35. package/dist/template/.opencode/plugin/lib/memory-db.ts +19 -1
  36. package/dist/template/.opencode/plugin/lib/memory-helpers.ts +30 -0
  37. package/dist/template/.opencode/plugin/lib/memory-hooks.ts +10 -0
  38. package/dist/template/.opencode/plugin/lib/memory-tools.ts +167 -2
  39. package/dist/template/.opencode/plugin/lib/operation-log.ts +109 -0
  40. package/dist/template/.opencode/plugin/lib/validate.ts +243 -0
  41. package/dist/template/.opencode/plugin/memory.ts +2 -1
  42. package/dist/template/.opencode/skill/design-taste-frontend/SKILL.md +13 -1
  43. package/dist/template/.opencode/skill/figma-go/SKILL.md +1 -1
  44. package/dist/template/.opencode/skill/full-output-enforcement/SKILL.md +13 -0
  45. package/dist/template/.opencode/skill/high-end-visual-design/SKILL.md +13 -0
  46. package/dist/template/.opencode/skill/industrial-brutalist-ui/SKILL.md +13 -0
  47. package/dist/template/.opencode/skill/memory-system/SKILL.md +65 -1
  48. package/dist/template/.opencode/skill/minimalist-ui/SKILL.md +13 -0
  49. package/dist/template/.opencode/skill/redesign-existing-projects/SKILL.md +13 -0
  50. package/dist/template/.opencode/skill/requesting-code-review/SKILL.md +48 -2
  51. package/dist/template/.opencode/skill/requesting-code-review/references/specialist-profiles.md +108 -0
  52. package/dist/template/.opencode/skill/skill-creator/SKILL.md +25 -0
  53. package/dist/template/.opencode/skill/stitch-design-taste/SKILL.md +13 -0
  54. package/dist/template/.opencode/skill/verification-before-completion/SKILL.md +46 -0
  55. package/package.json +1 -1
  56. package/dist/template/.opencode/agent/runner.md +0 -79
  57. package/dist/template/.opencode/command/start.md +0 -156
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Memory Plugin — Core Tools
3
3
  *
4
- * observation, memory-search, memory-get, memory-read, memory-update, memory-timeline
4
+ * observation, memory-search, memory-get, memory-read, memory-update, memory-timeline,
5
+ * memory-graph-add, memory-graph-query, memory-graph-invalidate, memory-compact
5
6
  *
6
7
  * Uses factory pattern: createCoreTools(deps) returns tool definitions
7
8
  * that can be spread into plugin's tool:{} export.
@@ -11,17 +12,23 @@ import { readdir } from "node:fs/promises";
11
12
  import path from "node:path";
12
13
  import { tool } from "@opencode-ai/plugin/tool";
13
14
  import {
15
+ addEntityTriple,
14
16
  type ConfidenceLevel,
15
17
  checkFTS5Available,
18
+ getMemoryDB,
16
19
  getMemoryFile,
17
20
  getObservationsByIds,
18
21
  getTimelineAroundObservation,
22
+ invalidateTriple,
19
23
  type ObservationSource,
20
24
  type ObservationType,
25
+ queryEntity,
21
26
  searchDistillationsFTS,
22
27
  searchObservationsFTS,
23
28
  storeObservation,
24
29
  upsertMemoryFile,
30
+ type HallType,
31
+ VALID_HALLS,
25
32
  } from "./memory-db.js";
26
33
  import {
27
34
  autoDetectFiles,
@@ -31,6 +38,8 @@ import {
31
38
  TYPE_ICONS,
32
39
  VALID_TYPES,
33
40
  } from "./memory-helpers.js";
41
+ import { validateObservation } from "./validate.js";
42
+ import { compactObservations } from "./compact.js";
34
43
 
35
44
  /**
36
45
  * Wrap a memory tool execute function with DB error handling.
@@ -117,6 +126,22 @@ export function createCoreTools(deps: CoreToolDeps) {
117
126
  .string()
118
127
  .optional()
119
128
  .describe("manual, curator, imported"),
129
+ wing: tool.schema
130
+ .string()
131
+ .optional()
132
+ .describe("Navigation wing (project or person name)"),
133
+ hall: tool.schema
134
+ .string()
135
+ .optional()
136
+ .describe("Navigation hall: facts, events, discoveries, preferences, advice"),
137
+ room: tool.schema
138
+ .string()
139
+ .optional()
140
+ .describe("Navigation room (topic name, e.g. auth-migration)"),
141
+ raw_source: tool.schema
142
+ .string()
143
+ .optional()
144
+ .describe("Verbatim source text to preserve losslessly alongside narrative"),
120
145
  },
121
146
  execute: withDBErrorHandling(async (args) => {
122
147
  const obsType = args.type as ObservationType;
@@ -156,6 +181,37 @@ export function createCoreTools(deps: CoreToolDeps) {
156
181
  }
157
182
 
158
183
  const source = (args.source ?? "manual") as ObservationSource;
184
+ const hall = args.hall as HallType | undefined;
185
+ if (hall && !VALID_HALLS.includes(hall)) {
186
+ return `Error: Invalid hall "${args.hall}". Valid: ${VALID_HALLS.join(", ")}`;
187
+ }
188
+
189
+ // Validation gate: check for duplicates, contradictions, low quality
190
+ const validation = validateObservation({
191
+ type: obsType,
192
+ title: args.title,
193
+ subtitle: args.subtitle,
194
+ facts,
195
+ narrative,
196
+ concepts,
197
+ files_read: filesRead,
198
+ files_modified: filesModified,
199
+ confidence,
200
+ bead_id: args.bead_id,
201
+ supersedes,
202
+ source,
203
+ wing: args.wing,
204
+ hall,
205
+ room: args.room,
206
+ });
207
+
208
+ if (validation.verdict === "reject") {
209
+ const reasons = validation.issues.map(i => i.message).join("; ");
210
+ const dupHint = validation.duplicateOf
211
+ ? ` Use \`observation({ supersedes: "${validation.duplicateOf}", ... })\` to update it.`
212
+ : "";
213
+ return `Rejected: ${reasons}.${dupHint}`;
214
+ }
159
215
 
160
216
  const id = storeObservation({
161
217
  type: obsType,
@@ -163,6 +219,7 @@ export function createCoreTools(deps: CoreToolDeps) {
163
219
  subtitle: args.subtitle,
164
220
  facts,
165
221
  narrative,
222
+ raw_source: args.raw_source,
166
223
  concepts,
167
224
  files_read: filesRead,
168
225
  files_modified: filesModified,
@@ -170,9 +227,16 @@ export function createCoreTools(deps: CoreToolDeps) {
170
227
  bead_id: args.bead_id,
171
228
  supersedes,
172
229
  source,
230
+ wing: args.wing,
231
+ hall,
232
+ room: args.room,
173
233
  });
174
234
 
175
- return `${TYPE_ICONS[obsType] ?? "\uD83D\uDCCC"} Observation #${id} stored [${obsType}] "${args.title}" (confidence: ${confidence}, source: ${source})`;
235
+ const warnings = validation.issues.length > 0
236
+ ? `\n⚠️ Warnings: ${validation.issues.map(i => i.message).join("; ")}`
237
+ : "";
238
+
239
+ return `${TYPE_ICONS[obsType] ?? "\uD83D\uDCCC"} Observation #${id} stored [${obsType}] "${args.title}" (confidence: ${confidence}, source: ${source})${warnings}`;
176
240
  }),
177
241
  }),
178
242
 
@@ -366,5 +430,106 @@ export function createCoreTools(deps: CoreToolDeps) {
366
430
  return lines.join("\n");
367
431
  }),
368
432
  }),
433
+
434
+ "memory-graph-add": tool({
435
+ description: `Add a triple to the entity knowledge graph.\n\nStores a subject-predicate-object relationship with optional temporal bounds and confidence.\nUse for structured facts like "project uses typescript" or "user prefers dark-mode".\n\nExample:\nmemory-graph-add({ subject: "project", predicate: "uses", object: "typescript" })\nmemory-graph-add({ subject: "auth", predicate: "depends-on", object: "jwt", confidence: 0.9, source_observation_id: 42 })`,
436
+ args: {
437
+ subject: tool.schema.string().describe("Entity subject"),
438
+ predicate: tool.schema.string().describe("Relationship type (e.g. uses, depends-on, prefers)"),
439
+ object: tool.schema.string().describe("Entity object"),
440
+ valid_from: tool.schema.string().optional().describe("Start date (ISO, default: today)"),
441
+ valid_to: tool.schema.string().optional().describe("End date (ISO, null = still active)"),
442
+ confidence: tool.schema.number().optional().describe("Confidence 0.0-1.0 (default: 1.0)"),
443
+ source_observation_id: tool.schema.number().optional().describe("Link to source observation"),
444
+ },
445
+ execute: withDBErrorHandling(async (args) => {
446
+ if (!args.subject?.trim() || !args.predicate?.trim() || !args.object?.trim()) {
447
+ return "Error: subject, predicate, and object are required.";
448
+ }
449
+ const id = addEntityTriple({
450
+ subject: args.subject,
451
+ predicate: args.predicate,
452
+ object: args.object,
453
+ valid_from: args.valid_from,
454
+ valid_to: args.valid_to,
455
+ confidence: args.confidence,
456
+ source_observation_id: args.source_observation_id,
457
+ });
458
+ return `Triple #${id} added: ${args.subject} —[${args.predicate}]→ ${args.object}`;
459
+ }),
460
+ }),
461
+
462
+ "memory-graph-query": tool({
463
+ description: `Query the entity knowledge graph.\n\nFind relationships for an entity with optional time and direction filters.\nReturns triples where the entity appears as subject, object, or both.\n\nExample:\nmemory-graph-query({ entity: "typescript" })\nmemory-graph-query({ entity: "auth", direction: "out", active_only: true })\nmemory-graph-query({ entity: "project", as_of: "2025-06-01" })`,
464
+ args: {
465
+ entity: tool.schema.string().describe("Entity to query"),
466
+ direction: tool.schema.string().optional().describe("out (subject), in (object), both (default)"),
467
+ predicate: tool.schema.string().optional().describe("Filter by predicate"),
468
+ as_of: tool.schema.string().optional().describe("ISO date for point-in-time query"),
469
+ active_only: tool.schema.boolean().optional().describe("Only active triples (default: false)"),
470
+ limit: tool.schema.number().optional().describe("Max results (default: 50)"),
471
+ },
472
+ execute: withDBErrorHandling(async (args) => {
473
+ if (!args.entity?.trim()) return "Error: entity is required.";
474
+ const results = queryEntity(args.entity, {
475
+ direction: args.direction as "out" | "in" | "both" | undefined,
476
+ predicate: args.predicate,
477
+ as_of: args.as_of,
478
+ activeOnly: args.active_only,
479
+ limit: args.limit,
480
+ });
481
+ if (results.length === 0) return `No triples found for entity "${args.entity}".`;
482
+ const lines: string[] = [
483
+ `## Entity Graph: ${args.entity} (${results.length} triples)\n`,
484
+ "| ID | Subject | Predicate | Object | Active | Confidence |",
485
+ "|---|---|---|---|---|---|",
486
+ ];
487
+ for (const r of results) {
488
+ lines.push(`| ${r.id} | ${r.subject} | ${r.predicate} | ${r.object} | ${r.is_active ? "\u2705" : "\u274C"} | ${(r.confidence * 100).toFixed(0)}% |`);
489
+ }
490
+ return lines.join("\n");
491
+ }),
492
+ }),
493
+
494
+ "memory-graph-invalidate": tool({
495
+ description: `Invalidate (close) entity triples in the knowledge graph.\n\nMarks active triples matching subject+predicate+object as no longer valid by setting valid_to.\nUse when a fact is no longer true (e.g. project stopped using a library).\n\nExample:\nmemory-graph-invalidate({ subject: "project", predicate: "uses", object: "moment.js" })`,
496
+ args: {
497
+ subject: tool.schema.string().describe("Entity subject"),
498
+ predicate: tool.schema.string().describe("Relationship type"),
499
+ object: tool.schema.string().describe("Entity object"),
500
+ end_date: tool.schema.string().optional().describe("End date (ISO, default: today)"),
501
+ },
502
+ execute: withDBErrorHandling(async (args) => {
503
+ if (!args.subject?.trim() || !args.predicate?.trim() || !args.object?.trim()) {
504
+ return "Error: subject, predicate, and object are required.";
505
+ }
506
+ const count = invalidateTriple(args.subject, args.predicate, args.object, args.end_date);
507
+ return count > 0
508
+ ? `Invalidated ${count} triple(s): ${args.subject} —[${args.predicate}]→ ${args.object}`
509
+ : `No active triples found matching ${args.subject} —[${args.predicate}]→ ${args.object}.`;
510
+ }),
511
+ }),
512
+
513
+ "memory-compact": tool({
514
+ description: `Compact observations into a dense, token-efficient format.\n\nUses AAAK-inspired pipe-separated compression achieving ~3-5x token reduction.\nUseful for injecting memory into prompts with minimal token cost.\n\nExample:\nmemory-compact({})\nmemory-compact({ limit: 20 })`,
515
+ args: {
516
+ limit: tool.schema.number().optional().describe("Max observations to compact (default: all)"),
517
+ },
518
+ execute: withDBErrorHandling(async (args) => {
519
+ const db = getMemoryDB();
520
+ const limitClause = args.limit ? `LIMIT ${Number(args.limit)}` : "";
521
+ const observations = db.query(
522
+ `SELECT id, type, title, narrative, concepts, wing, hall, room, confidence, created_at
523
+ FROM observations
524
+ WHERE superseded_by IS NULL
525
+ ORDER BY created_at_epoch DESC ${limitClause}`
526
+ ).all() as { id: number; type: string; title: string; narrative?: string | null; concepts?: string | null; wing?: string | null; hall?: string | null; room?: string | null; confidence?: string | null; created_at?: string | null }[];
527
+ if (observations.length === 0) return "No observations to compact.";
528
+ const result = compactObservations(observations);
529
+ if (!result.compressed) return "No observations to compact.";
530
+ const ratio = result.compression_ratio > 0 ? ` (${(result.compression_ratio * 100).toFixed(0)}% of original)` : "";
531
+ return `## Compact Memory (${observations.length} observations, ~${result.token_estimate} tokens${ratio})\n\n${result.compressed}`;
532
+ }),
533
+ }),
369
534
  };
370
535
  }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Memory Operation Log — Append-Only Audit Trail
3
+ *
4
+ * Inspired by Karpathy's LLM Wiki log.md:
5
+ * chronological record of all memory operations for provenance tracking.
6
+ *
7
+ * Stored in memory_files as "log" — append-only, never overwritten.
8
+ */
9
+
10
+ import { getMemoryFile, upsertMemoryFile } from "./db/maintenance.js";
11
+
12
+ // ============================================================================
13
+ // Types
14
+ // ============================================================================
15
+
16
+ export type OperationType =
17
+ | "observation-created"
18
+ | "observation-superseded"
19
+ | "observation-validated"
20
+ | "observation-rejected"
21
+ | "index-generated"
22
+ | "lint-run"
23
+ | "compile-run"
24
+ | "maintenance-run"
25
+ | "distillation-created"
26
+ | "curation-run";
27
+
28
+ export interface LogEntry {
29
+ timestamp: string;
30
+ operation: OperationType;
31
+ targets: string[];
32
+ summary: string;
33
+ }
34
+
35
+ // ============================================================================
36
+ // Log Operations
37
+ // ============================================================================
38
+
39
+ /**
40
+ * Append an entry to the operation log.
41
+ * The log is append-only and stored in memory_files.
42
+ */
43
+ export function appendOperationLog(entry: Omit<LogEntry, "timestamp">): void {
44
+ const timestamp = new Date().toISOString().slice(0, 19);
45
+ const targets = entry.targets.length > 0 ? entry.targets.join(", ") : "-";
46
+ const line = `[${timestamp}] ${entry.operation} | ${targets} | ${entry.summary}`;
47
+
48
+ // Check if log exists
49
+ const existing = getMemoryFile("log");
50
+ if (existing) {
51
+ // Append with newline
52
+ upsertMemoryFile("log", line, "append");
53
+ } else {
54
+ // Create with header
55
+ const header = [
56
+ "# Memory Operation Log",
57
+ "",
58
+ "> Append-only chronological record of all memory operations.",
59
+ "> Format: [timestamp] operation | targets | summary",
60
+ "",
61
+ "---",
62
+ "",
63
+ line,
64
+ ].join("\n");
65
+ upsertMemoryFile("log", header, "replace");
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Get recent log entries (parsed from the log file).
71
+ */
72
+ export function getRecentLogEntries(limit = 20): LogEntry[] {
73
+ const existing = getMemoryFile("log");
74
+ if (!existing) return [];
75
+
76
+ const lines = existing.content
77
+ .split("\n")
78
+ .filter((line) => line.startsWith("["))
79
+ .slice(-limit);
80
+
81
+ return lines.map((line) => {
82
+ const match = line.match(/^\[(.+?)\] (.+?) \| (.+?) \| (.+)$/);
83
+ if (!match) {
84
+ return {
85
+ timestamp: "",
86
+ operation: "observation-created" as OperationType,
87
+ targets: [],
88
+ summary: line,
89
+ };
90
+ }
91
+ return {
92
+ timestamp: match[1],
93
+ operation: match[2] as OperationType,
94
+ targets: match[3].split(",").map((t) => t.trim()),
95
+ summary: match[4],
96
+ };
97
+ });
98
+ }
99
+
100
+ /**
101
+ * Get the full log content as a string.
102
+ */
103
+ export function getLogContent(): string {
104
+ const existing = getMemoryFile("log");
105
+ return (
106
+ existing?.content ??
107
+ "No operation log found. Run a memory operation to start logging."
108
+ );
109
+ }
@@ -0,0 +1,243 @@
1
+ /**
2
+ * Observation Validation Gate
3
+ *
4
+ * Inspired by jumperz's Secondmate Hermes validation:
5
+ * checks for duplicates and contradictions BEFORE storing an observation.
6
+ *
7
+ * Unlike Secondmate's approach (separate LLM model), this uses
8
+ * FTS5 similarity search + heuristic contradiction detection.
9
+ *
10
+ * Returns a validation result that can:
11
+ * - PASS: store normally
12
+ * - WARN: store but flag issues
13
+ * - REJECT: don't store, return reason
14
+ */
15
+
16
+ import type { ObservationInput, ObservationRow } from "./db/types.js";
17
+ import { getMemoryDB } from "./memory-db.js";
18
+ import { hasWord } from "./memory-helpers.js";
19
+ import { appendOperationLog } from "./operation-log.js";
20
+
21
+ // ============================================================================
22
+ // Types
23
+ // ============================================================================
24
+
25
+ export type ValidationVerdict = "pass" | "warn" | "reject";
26
+
27
+ export interface ValidationResult {
28
+ verdict: ValidationVerdict;
29
+ issues: ValidationIssue[];
30
+ /** If a near-duplicate was found, its ID */
31
+ duplicateOf?: number;
32
+ }
33
+
34
+ export interface ValidationIssue {
35
+ type: "duplicate" | "near-duplicate" | "contradiction" | "low-quality";
36
+ severity: "high" | "medium" | "low";
37
+ message: string;
38
+ relatedId?: number;
39
+ }
40
+
41
+ // ============================================================================
42
+ // Validation
43
+ // ============================================================================
44
+
45
+ /**
46
+ * Validate an observation before storing.
47
+ * Returns verdict (pass/warn/reject) with issues.
48
+ */
49
+ export function validateObservation(input: ObservationInput): ValidationResult {
50
+ const issues: ValidationIssue[] = [];
51
+
52
+ // Check 1: Exact title duplicate
53
+ const exactDup = findExactDuplicate(input);
54
+ if (exactDup) {
55
+ // If it's explicitly superseding, that's fine
56
+ if (input.supersedes === exactDup.id) {
57
+ // Intentional supersede — pass
58
+ } else {
59
+ issues.push({
60
+ type: "duplicate",
61
+ severity: "high",
62
+ message: `Exact duplicate of #${exactDup.id}: "${exactDup.title}"`,
63
+ relatedId: exactDup.id,
64
+ });
65
+
66
+ appendOperationLog({
67
+ operation: "observation-duplicate-warning",
68
+ targets: [`#${exactDup.id}`],
69
+ summary: `Duplicate warning: "${input.title}" (matches #${exactDup.id})`,
70
+ });
71
+
72
+ return {
73
+ verdict: "warn",
74
+ issues,
75
+ duplicateOf: exactDup.id,
76
+ };
77
+ }
78
+ }
79
+
80
+ // Check 2: Near-duplicate via FTS5
81
+ const nearDup = findNearDuplicate(input);
82
+ if (nearDup) {
83
+ issues.push({
84
+ type: "near-duplicate",
85
+ severity: "medium",
86
+ message: `Similar to #${nearDup.id}: "${nearDup.title}" (consider superseding)`,
87
+ relatedId: nearDup.id,
88
+ });
89
+ }
90
+
91
+ // Check 3: Contradiction with existing decisions
92
+ if (input.type === "decision") {
93
+ const contradiction = findContradiction(input);
94
+ if (contradiction) {
95
+ issues.push({
96
+ type: "contradiction",
97
+ severity: "medium",
98
+ message: `May contradict #${contradiction.id}: "${contradiction.title}"`,
99
+ relatedId: contradiction.id,
100
+ });
101
+ }
102
+ }
103
+
104
+ // Check 4: Low quality (no narrative, no concepts)
105
+ if (!input.narrative && !input.concepts) {
106
+ issues.push({
107
+ type: "low-quality",
108
+ severity: "low",
109
+ message:
110
+ "Observation has no narrative and no concepts — low knowledge value",
111
+ });
112
+ }
113
+
114
+ // Determine verdict
115
+ const hasHigh = issues.some((i) => i.severity === "high");
116
+ const verdict: ValidationVerdict = hasHigh
117
+ ? "reject"
118
+ : issues.length > 0
119
+ ? "warn"
120
+ : "pass";
121
+
122
+ if (verdict === "pass" || verdict === "warn") {
123
+ appendOperationLog({
124
+ operation: "observation-validated",
125
+ targets: [input.title],
126
+ summary: `Validated [${input.type}] "${input.title}" — ${verdict}${issues.length > 0 ? ` (${issues.length} issues)` : ""}`,
127
+ });
128
+ }
129
+
130
+ return { verdict, issues };
131
+ }
132
+
133
+ // ============================================================================
134
+ // Internal Checks
135
+ // ============================================================================
136
+
137
+ /**
138
+ * Check for exact title match (same type, active).
139
+ */
140
+ function findExactDuplicate(input: ObservationInput): ObservationRow | null {
141
+ const db = getMemoryDB();
142
+ return db
143
+ .query(
144
+ `SELECT * FROM observations
145
+ WHERE type = ? AND LOWER(title) = LOWER(?) AND superseded_by IS NULL
146
+ LIMIT 1`,
147
+ )
148
+ .get(input.type, input.title) as ObservationRow | null;
149
+ }
150
+
151
+ /**
152
+ * Check for near-duplicate via FTS5 title search.
153
+ */
154
+ function findNearDuplicate(input: ObservationInput): ObservationRow | null {
155
+ const db = getMemoryDB();
156
+
157
+ // Build FTS query from title words
158
+ // Strip FTS5 special operators and characters to prevent query syntax errors
159
+ const FTS5_OPERATORS = /\b(AND|OR|NOT|NEAR)\b/gi;
160
+ const words = input.title
161
+ .replace(FTS5_OPERATORS, "")
162
+ .replace(/['"*^+(){}]/g, "")
163
+ .split(/\s+/)
164
+ .filter((w) => w.length > 2)
165
+ .map((w) => `"${w}"*`)
166
+ .join(" AND ");
167
+
168
+ if (!words) return null;
169
+
170
+ try {
171
+ const result = db
172
+ .query(
173
+ `SELECT o.* FROM observations o
174
+ JOIN observations_fts fts ON fts.rowid = o.id
175
+ WHERE observations_fts MATCH ?
176
+ AND o.type = ?
177
+ AND o.superseded_by IS NULL
178
+ AND o.id != COALESCE(?, -1)
179
+ ORDER BY bm25(observations_fts) LIMIT 1`,
180
+ )
181
+ .get(
182
+ words,
183
+ input.type,
184
+ input.supersedes ?? null,
185
+ ) as ObservationRow | null;
186
+
187
+ return result;
188
+ } catch {
189
+ return null;
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Check for contradictions with existing decisions.
195
+ */
196
+ function findContradiction(input: ObservationInput): ObservationRow | null {
197
+ if (!input.concepts || input.concepts.length === 0) return null;
198
+
199
+ const db = getMemoryDB();
200
+
201
+ // Search for decisions with overlapping concepts
202
+ const conceptQuery = input.concepts.map((c) => `"${c}"*`).join(" OR ");
203
+
204
+ try {
205
+ const candidates = db
206
+ .query(
207
+ `SELECT o.* FROM observations o
208
+ JOIN observations_fts fts ON fts.rowid = o.id
209
+ WHERE observations_fts MATCH ?
210
+ AND o.type = 'decision'
211
+ AND o.superseded_by IS NULL
212
+ ORDER BY bm25(observations_fts) LIMIT 5`,
213
+ )
214
+ .all(conceptQuery) as ObservationRow[];
215
+
216
+ // Check for opposing signals
217
+ const inputText = `${input.title} ${input.narrative ?? ""}`.toLowerCase();
218
+ const contradictionPairs = [
219
+ ["use", "don't use"],
220
+ ["enable", "disable"],
221
+ ["add", "remove"],
222
+ ["prefer", "avoid"],
223
+ ["always", "never"],
224
+ ];
225
+
226
+ for (const candidate of candidates) {
227
+ const candidateText =
228
+ `${candidate.title} ${candidate.narrative ?? ""}`.toLowerCase();
229
+ for (const [wordA, wordB] of contradictionPairs) {
230
+ if (
231
+ (hasWord(inputText, wordA) && hasWord(candidateText, wordB)) ||
232
+ (hasWord(inputText, wordB) && hasWord(candidateText, wordA))
233
+ ) {
234
+ return candidate;
235
+ }
236
+ }
237
+ }
238
+ } catch {
239
+ // FTS5 query failed
240
+ }
241
+
242
+ return null;
243
+ }
@@ -11,7 +11,8 @@
11
11
  * 5. Context Management — messages.transform → token budget enforcement
12
12
  *
13
13
  * Tools: observation, memory-search, memory-get, memory-read,
14
- * memory-update, memory-timeline, memory-admin
14
+ * memory-update, memory-timeline, memory-graph-add, memory-graph-query,
15
+ * memory-graph-invalidate, memory-compact, memory-admin
15
16
  *
16
17
  * Module structure:
17
18
  * memory.ts — Plugin entry (this file)
@@ -1,8 +1,20 @@
1
1
  ---
2
2
  name: design-taste-frontend
3
- description: Use as the BASE aesthetic skill for any web UI to override default LLM design biases. Enforces strict typography, color, spacing, and component architecture rules. Load BEFORE frontend-design when premium visual quality is required.
3
+ description: Use when building any web UI as the BASE aesthetic layer to override default LLM design biases. Enforces strict typography, color, spacing, and component architecture rules. Load BEFORE frontend-design when premium visual quality is required.
4
4
  ---
5
5
 
6
+ ## When to Use
7
+
8
+ - When building any web UI that needs to override default LLM design biases
9
+ - Before loading `frontend-design` skill when premium visual quality is required
10
+ - As the base aesthetic layer for any UI implementation task
11
+
12
+ ## When NOT to Use
13
+
14
+ - When a more specific aesthetic overlay is loaded (high-end-visual-design, minimalist-ui, industrial-brutalist-ui)
15
+ - For non-UI tasks (backend, CLI, data processing)
16
+
17
+
6
18
  # High-Agency Frontend Skill
7
19
 
8
20
  ## 1. ACTIVE BASELINE CONFIGURATION
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: figma-go
3
- description: Use for Figma read/write access WITHOUT an API token, via figma-mcp-go plugin bridge. Prefer over figma skill when no API token is available. MUST load when user needs Figma data and has the desktop plugin installed.
3
+ description: Use when you need Figma read/write access WITHOUT an API token, via figma-mcp-go plugin bridge. Prefer over figma skill when no API token is available. MUST load when user needs Figma data and has the desktop plugin installed.
4
4
  mcp:
5
5
  figma-mcp-go:
6
6
  command: npx
@@ -3,6 +3,19 @@ name: full-output-enforcement
3
3
  description: MUST load when generating complete files, long code blocks, or any output where truncation or placeholder comments like '// ... rest of code' would be harmful. Bans all placeholder patterns and enforces exhaustive, unabridged output.
4
4
  ---
5
5
 
6
+ ## When to Use
7
+
8
+ - When generating complete files, long code blocks, or full configuration outputs
9
+ - When truncation or placeholder comments like `// ... rest of code` would be harmful
10
+ - When the user needs exhaustive, unabridged output
11
+
12
+ ## When NOT to Use
13
+
14
+ - For short code snippets or single-function responses
15
+ - When summarization is explicitly requested
16
+ - For conversational responses that don't involve code generation
17
+
18
+
6
19
  # Full-Output Enforcement
7
20
 
8
21
  ## Baseline
@@ -3,6 +3,19 @@ name: high-end-visual-design
3
3
  description: Use INSTEAD OF design-taste-frontend when user explicitly requests premium, agency-quality, or luxury visual design. Defines exact fonts, spacing, shadows, and animations that make websites feel expensive. Blocks cheap AI defaults.
4
4
  ---
5
5
 
6
+ ## When to Use
7
+
8
+ - When the user explicitly requests premium, agency-quality, or luxury visual design
9
+ - Instead of design-taste-frontend when high-end aesthetics are the priority
10
+ - For landing pages, marketing sites, or portfolio projects that need to feel expensive
11
+
12
+ ## When NOT to Use
13
+
14
+ - For internal tools, admin panels, or dashboards where function > form
15
+ - When minimalist-ui or industrial-brutalist-ui aesthetics are requested instead
16
+ - For rapid prototyping where visual polish is not yet needed
17
+
18
+
6
19
  # Agent Skill: Principal UI/UX Architect & Motion Choreographer (Awwwards-Tier)
7
20
 
8
21
  ## 1. Meta Information & Core Directive
@@ -3,6 +3,19 @@ name: industrial-brutalist-ui
3
3
  description: Use INSTEAD OF design-taste-frontend when user requests brutalist, military-terminal, or raw mechanical aesthetics. Swiss typographic print meets utilitarian color. For data-heavy dashboards or editorial sites needing declassified-blueprint energy.
4
4
  ---
5
5
 
6
+ ## When to Use
7
+
8
+ - When the user requests brutalist, military-terminal, or raw mechanical aesthetics
9
+ - For data-heavy dashboards or editorial sites needing declassified-blueprint energy
10
+ - Instead of design-taste-frontend when utilitarian aesthetics are requested
11
+
12
+ ## When NOT to Use
13
+
14
+ - For consumer-facing marketing or e-commerce sites
15
+ - When clean, minimalist, or luxury aesthetics are requested
16
+ - For accessibility-critical applications where brutalist patterns may reduce readability
17
+
18
+
6
19
  # SKILL: Industrial Brutalism & Tactical Telemetry UI
7
20
 
8
21
  ## 1. Skill Meta