watashi-db 0.0.13 → 0.0.15
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/CLAUDE.md +36 -0
- package/LICENSE +1 -1
- package/README.md +64 -2
- package/cowork-plugin/skills/groom/SKILL.md +51 -15
- package/cowork-plugin/skills/recall/SKILL.md +5 -6
- package/cowork-plugin/skills/reflect/SKILL.md +4 -4
- package/cowork-plugin/skills/remember/SKILL.md +3 -3
- package/cowork-plugin/skills/session-start/SKILL.md +3 -3
- package/dist/auth/token.d.ts +7 -0
- package/dist/auth/token.js +14 -0
- package/dist/auth/token.js.map +1 -0
- package/dist/config/schema.js +1 -1
- package/dist/constants.d.ts +9 -9
- package/dist/constants.js +29 -43
- package/dist/constants.js.map +1 -1
- package/dist/database/archive.js +6 -6
- package/dist/database/groom.js +5 -5
- package/dist/database/groom.js.map +1 -1
- package/dist/database/queries-core.d.ts +109 -5
- package/dist/database/queries-core.js +546 -186
- package/dist/database/queries-core.js.map +1 -1
- package/dist/database/queries.d.ts +85 -5
- package/dist/database/queries.js +33 -0
- package/dist/database/queries.js.map +1 -1
- package/dist/database/schema.d.ts +1 -0
- package/dist/database/schema.js +2299 -406
- package/dist/database/schema.js.map +1 -1
- package/dist/embedding/embed-on-write.d.ts +7 -1
- package/dist/embedding/embed-on-write.js +8 -3
- package/dist/embedding/embed-on-write.js.map +1 -1
- package/dist/hook.js +17 -9
- package/dist/hook.js.map +1 -1
- package/dist/http-server.d.ts +6 -0
- package/dist/http-server.js +235 -0
- package/dist/http-server.js.map +1 -0
- package/dist/index.js +14 -3
- package/dist/index.js.map +1 -1
- package/dist/resources/config-guide-content.d.ts +1 -1
- package/dist/resources/config-guide-content.js +2 -2
- package/dist/server-core.d.ts +15 -0
- package/dist/server-core.js +113 -0
- package/dist/server-core.js.map +1 -0
- package/dist/server-instructions.js +27 -24
- package/dist/server-instructions.js.map +1 -1
- package/dist/server.d.ts +4 -1
- package/dist/server.js +9 -103
- package/dist/server.js.map +1 -1
- package/dist/setup.js +5 -6
- package/dist/setup.js.map +1 -1
- package/dist/store/federation.d.ts +12 -1
- package/dist/store/federation.js +38 -0
- package/dist/store/federation.js.map +1 -1
- package/dist/store/sync-manager.d.ts +1 -1
- package/dist/store/sync-manager.js +9 -9
- package/dist/tools/claim-tools.d.ts +1 -1
- package/dist/tools/claim-tools.js +30 -17
- package/dist/tools/claim-tools.js.map +1 -1
- package/dist/tools/decision-tools.d.ts +1 -1
- package/dist/tools/decision-tools.js +23 -7
- package/dist/tools/decision-tools.js.map +1 -1
- package/dist/tools/episode-tools.d.ts +1 -1
- package/dist/tools/episode-tools.js +28 -7
- package/dist/tools/episode-tools.js.map +1 -1
- package/dist/tools/file-tools.d.ts +3 -0
- package/dist/tools/file-tools.js +350 -0
- package/dist/tools/file-tools.js.map +1 -0
- package/dist/tools/get-tools.d.ts +1 -1
- package/dist/tools/get-tools.js +39 -5
- package/dist/tools/get-tools.js.map +1 -1
- package/dist/tools/helpers.d.ts +40 -0
- package/dist/tools/helpers.js +69 -0
- package/dist/tools/helpers.js.map +1 -1
- package/dist/tools/knowledge-tools.d.ts +1 -1
- package/dist/tools/knowledge-tools.js +38 -6
- package/dist/tools/knowledge-tools.js.map +1 -1
- package/dist/tools/maintenance-tools.d.ts +1 -1
- package/dist/tools/maintenance-tools.js +95 -52
- package/dist/tools/maintenance-tools.js.map +1 -1
- package/dist/tools/memo-tools.d.ts +7 -11
- package/dist/tools/memo-tools.js +509 -309
- package/dist/tools/memo-tools.js.map +1 -1
- package/dist/tools/query-tools.d.ts +1 -1
- package/dist/tools/query-tools.js +266 -242
- package/dist/tools/query-tools.js.map +1 -1
- package/dist/types.d.ts +513 -76
- package/dist/types.js +185 -33
- package/dist/types.js.map +1 -1
- package/misc/20260316_110841_groom-recipe.md +483 -0
- package/misc/20260316_xaml-testing-library-recipe.md +817 -0
- package/misc/20260331_remote-transport-recipe.md +316 -0
- package/package.json +4 -2
- package/scripts/update-license-version.sh +7 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ulid } from "ulid";
|
|
2
|
+
import { DEFAULT_SEARCH_ENTITY_TYPES } from "../types.js";
|
|
2
3
|
import { claimScoreExpression, computeClaimScore } from "../scoring.js";
|
|
3
4
|
// === exp() サポート検出(SQLite数学関数) ===
|
|
4
5
|
/** exp() が使えるかを DB ごとにキャッシュ */
|
|
@@ -57,8 +58,8 @@ export function insertClaim(db, params) {
|
|
|
57
58
|
const id = ulid();
|
|
58
59
|
const now = new Date().toISOString();
|
|
59
60
|
db.prepare(`
|
|
60
|
-
INSERT INTO claims (id, l2_subject, l2_predicate, l2_object, category, scope, confidence, l2_evidence, l2_falsifier, l1_content, search_summary, l1_embedding, source_tool,
|
|
61
|
-
VALUES (@id, @l2_subject, @l2_predicate, @l2_object, @category, @scope, @confidence, @l2_evidence, @l2_falsifier, @l1_content, @search_summary, @l1_embedding, @source_tool, @
|
|
61
|
+
INSERT INTO claims (id, l2_subject, l2_predicate, l2_object, category, scope, confidence, l2_evidence, l2_falsifier, l1_content, search_summary, l1_embedding, user_input, source_tool, session_id, client_name, client_version, validity_status, created_at, updated_at)
|
|
62
|
+
VALUES (@id, @l2_subject, @l2_predicate, @l2_object, @category, @scope, @confidence, @l2_evidence, @l2_falsifier, @l1_content, @search_summary, @l1_embedding, @user_input, @source_tool, @session_id, @client_name, @client_version, 'active', @created_at, @updated_at)
|
|
62
63
|
`).run({
|
|
63
64
|
id,
|
|
64
65
|
l2_subject: params.subject,
|
|
@@ -72,8 +73,9 @@ export function insertClaim(db, params) {
|
|
|
72
73
|
l1_content: params.l1_content ?? null,
|
|
73
74
|
search_summary: params.search_summary ?? null,
|
|
74
75
|
l1_embedding: params.l1_embedding ?? null,
|
|
76
|
+
user_input: params.user_input ?? null,
|
|
75
77
|
source_tool: params.source_tool ?? null,
|
|
76
|
-
|
|
78
|
+
session_id: params.session_id ?? null,
|
|
77
79
|
client_name: params.client_name ?? null,
|
|
78
80
|
client_version: params.client_version ?? null,
|
|
79
81
|
created_at: now,
|
|
@@ -140,20 +142,20 @@ export function retractClaim(db, id, reason) {
|
|
|
140
142
|
const existing = getClaimById(db, id);
|
|
141
143
|
if (!existing)
|
|
142
144
|
return undefined;
|
|
143
|
-
if (existing.
|
|
145
|
+
if (existing.validity_status !== "active")
|
|
144
146
|
return existing;
|
|
145
147
|
const now = new Date().toISOString();
|
|
146
148
|
db.transaction(() => {
|
|
147
149
|
db.prepare(`
|
|
148
150
|
INSERT INTO claim_history (id, claim_id, field_name, old_value, new_value, reason, changed_at)
|
|
149
|
-
VALUES (@id, @claim_id, '
|
|
151
|
+
VALUES (@id, @claim_id, 'validity_status', 'active', 'invalidated', @reason, @changed_at)
|
|
150
152
|
`).run({
|
|
151
153
|
id: ulid(),
|
|
152
154
|
claim_id: id,
|
|
153
155
|
reason,
|
|
154
156
|
changed_at: now,
|
|
155
157
|
});
|
|
156
|
-
db.prepare("UPDATE claims SET
|
|
158
|
+
db.prepare("UPDATE claims SET validity_status = 'invalidated', updated_at = @updated_at WHERE id = @id").run({
|
|
157
159
|
id,
|
|
158
160
|
updated_at: now,
|
|
159
161
|
});
|
|
@@ -165,20 +167,20 @@ export function supersedeClaim(db, oldId, newId, reason) {
|
|
|
165
167
|
const newClaim = getClaimById(db, newId);
|
|
166
168
|
if (!oldClaim || !newClaim)
|
|
167
169
|
return undefined;
|
|
168
|
-
if (oldClaim.
|
|
170
|
+
if (oldClaim.validity_status !== "active")
|
|
169
171
|
return undefined;
|
|
170
172
|
const now = new Date().toISOString();
|
|
171
173
|
db.transaction(() => {
|
|
172
174
|
db.prepare(`
|
|
173
175
|
INSERT INTO claim_history (id, claim_id, field_name, old_value, new_value, reason, changed_at)
|
|
174
|
-
VALUES (@id, @claim_id, '
|
|
176
|
+
VALUES (@id, @claim_id, 'validity_status', 'active', 'superseded', @reason, @changed_at)
|
|
175
177
|
`).run({
|
|
176
178
|
id: ulid(),
|
|
177
179
|
claim_id: oldId,
|
|
178
180
|
reason,
|
|
179
181
|
changed_at: now,
|
|
180
182
|
});
|
|
181
|
-
db.prepare("UPDATE claims SET
|
|
183
|
+
db.prepare("UPDATE claims SET validity_status = 'superseded', updated_at = @updated_at WHERE id = @id").run({
|
|
182
184
|
id: oldId,
|
|
183
185
|
updated_at: now,
|
|
184
186
|
});
|
|
@@ -202,12 +204,12 @@ export function updateDecisionStatus(db, id, newStatus, reason) {
|
|
|
202
204
|
const existing = getDecisionById(db, id);
|
|
203
205
|
if (!existing)
|
|
204
206
|
return undefined;
|
|
205
|
-
if (existing.
|
|
207
|
+
if (existing.validity_status !== "active")
|
|
206
208
|
return existing;
|
|
207
209
|
const now = new Date().toISOString();
|
|
208
|
-
db.prepare("UPDATE decisions SET
|
|
210
|
+
db.prepare("UPDATE decisions SET validity_status = @validity_status, updated_at = @updated_at WHERE id = @id").run({
|
|
209
211
|
id,
|
|
210
|
-
|
|
212
|
+
validity_status: newStatus,
|
|
211
213
|
updated_at: now,
|
|
212
214
|
});
|
|
213
215
|
return getDecisionById(db, id);
|
|
@@ -223,8 +225,10 @@ export function searchClaims(db, params) {
|
|
|
223
225
|
const conditions = [];
|
|
224
226
|
const values = { limit: params.limit };
|
|
225
227
|
if (params.status) {
|
|
226
|
-
conditions.push("c.
|
|
227
|
-
|
|
228
|
+
conditions.push("c.validity_status = @validity_status");
|
|
229
|
+
conditions.push("c.is_archived = 0");
|
|
230
|
+
conditions.push("c.promoted_to IS NULL");
|
|
231
|
+
values.validity_status = params.status;
|
|
228
232
|
}
|
|
229
233
|
if (params.category) {
|
|
230
234
|
conditions.push("c.category = @category");
|
|
@@ -273,8 +277,10 @@ export function searchClaims(db, params) {
|
|
|
273
277
|
const conditions = [];
|
|
274
278
|
const values = { limit: params.limit };
|
|
275
279
|
if (params.status) {
|
|
276
|
-
conditions.push("
|
|
277
|
-
|
|
280
|
+
conditions.push("validity_status = @validity_status");
|
|
281
|
+
conditions.push("is_archived = 0");
|
|
282
|
+
conditions.push("promoted_to IS NULL");
|
|
283
|
+
values.validity_status = params.status;
|
|
278
284
|
}
|
|
279
285
|
if (params.category) {
|
|
280
286
|
conditions.push("category = @category");
|
|
@@ -422,7 +428,7 @@ function queryClaimContext(db, topic, scope, limit) {
|
|
|
422
428
|
SELECT c.* FROM claims c
|
|
423
429
|
JOIN claims_fts f ON c.rowid = f.rowid
|
|
424
430
|
WHERE claims_fts MATCH @query
|
|
425
|
-
AND c.
|
|
431
|
+
AND c.validity_status = 'active' AND c.is_archived = 0 AND c.promoted_to IS NULL
|
|
426
432
|
${scopeFilter}
|
|
427
433
|
ORDER BY rank
|
|
428
434
|
LIMIT @limit
|
|
@@ -437,7 +443,7 @@ function queryClaimContext(db, topic, scope, limit) {
|
|
|
437
443
|
const likeQuery = `%${topic}%`;
|
|
438
444
|
return db.prepare(`
|
|
439
445
|
SELECT c.* FROM claims c
|
|
440
|
-
WHERE c.
|
|
446
|
+
WHERE c.validity_status = 'active' AND c.is_archived = 0 AND c.promoted_to IS NULL
|
|
441
447
|
AND (c.l2_subject LIKE @like_query OR c.l2_predicate LIKE @like_query OR c.l2_object LIKE @like_query OR c.l2_evidence LIKE @like_query OR c.search_summary LIKE @like_query)
|
|
442
448
|
${scopeFilter}
|
|
443
449
|
ORDER BY c.updated_at DESC
|
|
@@ -459,7 +465,7 @@ function queryEpisodeContext(db, topic, scope, limit) {
|
|
|
459
465
|
SELECT e.* FROM episodes e
|
|
460
466
|
JOIN episodes_fts f ON e.rowid = f.rowid
|
|
461
467
|
WHERE episodes_fts MATCH @query
|
|
462
|
-
AND e.
|
|
468
|
+
AND e.validity_status = 'active' AND e.is_archived = 0 AND e.promoted_to IS NULL
|
|
463
469
|
${scopeFilter}
|
|
464
470
|
ORDER BY rank
|
|
465
471
|
LIMIT @limit
|
|
@@ -474,7 +480,7 @@ function queryEpisodeContext(db, topic, scope, limit) {
|
|
|
474
480
|
const likeQuery = `%${topic}%`;
|
|
475
481
|
return db.prepare(`
|
|
476
482
|
SELECT e.* FROM episodes e
|
|
477
|
-
WHERE e.
|
|
483
|
+
WHERE e.validity_status = 'active' AND e.is_archived = 0 AND e.promoted_to IS NULL
|
|
478
484
|
AND (e.title LIKE @like_query OR e.l1_content LIKE @like_query OR e.l2_context LIKE @like_query OR e.l2_trigger LIKE @like_query OR e.l2_principles LIKE @like_query OR e.l2_problems LIKE @like_query OR e.l2_desires LIKE @like_query OR e.l2_outcomes LIKE @like_query OR e.l2_decisions LIKE @like_query OR e.search_summary LIKE @like_query)
|
|
479
485
|
${scopeFilter}
|
|
480
486
|
ORDER BY e.updated_at DESC
|
|
@@ -496,7 +502,7 @@ function queryDecisionContext(db, topic, scope, limit) {
|
|
|
496
502
|
SELECT d.* FROM decisions d
|
|
497
503
|
JOIN decisions_fts f ON d.rowid = f.rowid
|
|
498
504
|
WHERE decisions_fts MATCH @query
|
|
499
|
-
AND d.
|
|
505
|
+
AND d.validity_status = 'active' AND d.is_archived = 0 AND d.promoted_to IS NULL
|
|
500
506
|
${scopeFilter}
|
|
501
507
|
ORDER BY rank
|
|
502
508
|
LIMIT @limit
|
|
@@ -511,7 +517,7 @@ function queryDecisionContext(db, topic, scope, limit) {
|
|
|
511
517
|
const likeQuery = `%${topic}%`;
|
|
512
518
|
return db.prepare(`
|
|
513
519
|
SELECT d.* FROM decisions d
|
|
514
|
-
WHERE d.
|
|
520
|
+
WHERE d.validity_status = 'active' AND d.is_archived = 0 AND d.promoted_to IS NULL
|
|
515
521
|
AND (d.title LIKE @like_query OR d.description LIKE @like_query OR d.l2_reasoning LIKE @like_query OR d.search_summary LIKE @like_query)
|
|
516
522
|
${scopeFilter}
|
|
517
523
|
ORDER BY d.created_at DESC
|
|
@@ -528,8 +534,8 @@ export function insertDecision(db, params) {
|
|
|
528
534
|
const id = ulid();
|
|
529
535
|
const now = new Date().toISOString();
|
|
530
536
|
db.prepare(`
|
|
531
|
-
INSERT INTO decisions (id, title, description, l1_content, l2_reasoning, l2_alternatives, related_claim_ids, scope,
|
|
532
|
-
VALUES (@id, @title, @description, @l1_content, @l2_reasoning, @l2_alternatives, @related_claim_ids, @scope, 'active', @search_summary, @l1_embedding, @source_tool, @client_name, @client_version, @user_input, @created_at, @updated_at)
|
|
537
|
+
INSERT INTO decisions (id, title, description, l1_content, l2_reasoning, l2_alternatives, related_claim_ids, scope, validity_status, search_summary, l1_embedding, source_tool, session_id, client_name, client_version, user_input, created_at, updated_at)
|
|
538
|
+
VALUES (@id, @title, @description, @l1_content, @l2_reasoning, @l2_alternatives, @related_claim_ids, @scope, 'active', @search_summary, @l1_embedding, @source_tool, @session_id, @client_name, @client_version, @user_input, @created_at, @updated_at)
|
|
533
539
|
`).run({
|
|
534
540
|
id,
|
|
535
541
|
title: params.title,
|
|
@@ -542,6 +548,7 @@ export function insertDecision(db, params) {
|
|
|
542
548
|
search_summary: params.search_summary ?? null,
|
|
543
549
|
l1_embedding: params.l1_embedding ?? null,
|
|
544
550
|
source_tool: params.source_tool ?? null,
|
|
551
|
+
session_id: params.session_id ?? null,
|
|
545
552
|
client_name: params.client_name ?? null,
|
|
546
553
|
client_version: params.client_version ?? null,
|
|
547
554
|
user_input: params.user_input ?? null,
|
|
@@ -559,8 +566,10 @@ export function listDecisions(db, params) {
|
|
|
559
566
|
const conditions = [];
|
|
560
567
|
const values = { limit: params.limit };
|
|
561
568
|
if (params.status) {
|
|
562
|
-
conditions.push("d.
|
|
563
|
-
|
|
569
|
+
conditions.push("d.validity_status = @validity_status");
|
|
570
|
+
conditions.push("d.is_archived = 0");
|
|
571
|
+
conditions.push("d.promoted_to IS NULL");
|
|
572
|
+
values.validity_status = params.status;
|
|
564
573
|
}
|
|
565
574
|
if (params.scope) {
|
|
566
575
|
conditions.push("(d.scope = @scope OR d.scope = 'global')");
|
|
@@ -606,8 +615,10 @@ export function listDecisions(db, params) {
|
|
|
606
615
|
const conditions = [];
|
|
607
616
|
const values = { limit: params.limit };
|
|
608
617
|
if (params.status) {
|
|
609
|
-
conditions.push("
|
|
610
|
-
|
|
618
|
+
conditions.push("validity_status = @validity_status");
|
|
619
|
+
conditions.push("is_archived = 0");
|
|
620
|
+
conditions.push("promoted_to IS NULL");
|
|
621
|
+
values.validity_status = params.status;
|
|
611
622
|
}
|
|
612
623
|
if (params.scope) {
|
|
613
624
|
conditions.push("(scope = @scope OR scope = 'global')");
|
|
@@ -625,34 +636,34 @@ export function listDecisions(db, params) {
|
|
|
625
636
|
// === 統計 ===
|
|
626
637
|
export function getStats(db) {
|
|
627
638
|
const total_claims = db.prepare("SELECT COUNT(*) as count FROM claims").get().count;
|
|
628
|
-
const active_claims = db.prepare("SELECT COUNT(*) as count FROM claims WHERE
|
|
629
|
-
const retracted_claims = db.prepare("SELECT COUNT(*) as count FROM claims WHERE
|
|
630
|
-
const superseded_claims = db.prepare("SELECT COUNT(*) as count FROM claims WHERE
|
|
639
|
+
const active_claims = db.prepare("SELECT COUNT(*) as count FROM claims WHERE validity_status = 'active' AND is_archived = 0 AND promoted_to IS NULL").get().count;
|
|
640
|
+
const retracted_claims = db.prepare("SELECT COUNT(*) as count FROM claims WHERE validity_status = 'invalidated'").get().count;
|
|
641
|
+
const superseded_claims = db.prepare("SELECT COUNT(*) as count FROM claims WHERE validity_status = 'superseded'").get().count;
|
|
631
642
|
const total_decisions = db.prepare("SELECT COUNT(*) as count FROM decisions").get().count;
|
|
632
|
-
const active_decisions = db.prepare("SELECT COUNT(*) as count FROM decisions WHERE
|
|
643
|
+
const active_decisions = db.prepare("SELECT COUNT(*) as count FROM decisions WHERE validity_status = 'active' AND is_archived = 0 AND promoted_to IS NULL").get().count;
|
|
633
644
|
const total_history_entries = db.prepare("SELECT COUNT(*) as count FROM claim_history").get().count;
|
|
634
645
|
// 2026-02-23 追加 (Issue #39 Part D): Episode/Insight/Theory/Model の安全なカウント
|
|
635
646
|
let active_episodes = 0;
|
|
636
647
|
try {
|
|
637
|
-
active_episodes = db.prepare("SELECT COUNT(*) as count FROM episodes WHERE
|
|
648
|
+
active_episodes = db.prepare("SELECT COUNT(*) as count FROM episodes WHERE validity_status = 'active' AND is_archived = 0 AND promoted_to IS NULL").get().count;
|
|
638
649
|
}
|
|
639
650
|
catch { /* テーブル未存在等 */ }
|
|
640
651
|
let active_insights = 0;
|
|
641
652
|
try {
|
|
642
|
-
active_insights = db.prepare("SELECT COUNT(*) as count FROM insights WHERE
|
|
653
|
+
active_insights = db.prepare("SELECT COUNT(*) as count FROM insights WHERE validity_status = 'active' AND is_archived = 0 AND promoted_to IS NULL").get().count;
|
|
643
654
|
}
|
|
644
655
|
catch { /* テーブル未存在等 */ }
|
|
645
656
|
let active_theories = 0;
|
|
646
657
|
try {
|
|
647
|
-
active_theories = db.prepare("SELECT COUNT(*) as count FROM theories WHERE
|
|
658
|
+
active_theories = db.prepare("SELECT COUNT(*) as count FROM theories WHERE validity_status = 'active' AND is_archived = 0 AND promoted_to IS NULL").get().count;
|
|
648
659
|
}
|
|
649
660
|
catch { /* テーブル未存在等 */ }
|
|
650
661
|
let active_models = 0;
|
|
651
662
|
try {
|
|
652
|
-
active_models = db.prepare("SELECT COUNT(*) as count FROM models WHERE
|
|
663
|
+
active_models = db.prepare("SELECT COUNT(*) as count FROM models WHERE validity_status = 'active' AND is_archived = 0 AND promoted_to IS NULL").get().count;
|
|
653
664
|
}
|
|
654
665
|
catch { /* テーブル未存在等 */ }
|
|
655
|
-
const categoryRows = db.prepare("SELECT category, COUNT(*) as count FROM claims WHERE
|
|
666
|
+
const categoryRows = db.prepare("SELECT category, COUNT(*) as count FROM claims WHERE validity_status = 'active' AND is_archived = 0 AND promoted_to IS NULL GROUP BY category").all();
|
|
656
667
|
const categories = {};
|
|
657
668
|
for (const row of categoryRows) {
|
|
658
669
|
categories[row.category] = row.count;
|
|
@@ -673,10 +684,10 @@ export function getStats(db) {
|
|
|
673
684
|
};
|
|
674
685
|
}
|
|
675
686
|
export function getAllActiveClaims(db) {
|
|
676
|
-
return db.prepare("SELECT * FROM claims WHERE
|
|
687
|
+
return db.prepare("SELECT * FROM claims WHERE validity_status = 'active' AND is_archived = 0 ORDER BY category, updated_at DESC").all();
|
|
677
688
|
}
|
|
678
689
|
export function getRecentDecisions(db, limit = 10) {
|
|
679
|
-
return db.prepare("SELECT * FROM decisions WHERE
|
|
690
|
+
return db.prepare("SELECT * FROM decisions WHERE validity_status = 'active' AND is_archived = 0 ORDER BY created_at DESC LIMIT ?").all(limit);
|
|
680
691
|
}
|
|
681
692
|
// === L2 Core v0: Claim関係 ===
|
|
682
693
|
export function insertClaimRelation(db, params) {
|
|
@@ -748,11 +759,11 @@ export function getClaimChecks(db, claimId) {
|
|
|
748
759
|
export function insertAuditLog(db, params) {
|
|
749
760
|
const id = ulid();
|
|
750
761
|
const now = new Date().toISOString();
|
|
751
|
-
// 2026-03-03 追加 (V21):
|
|
752
|
-
const
|
|
762
|
+
// 2026-03-03 追加 (V21): device_id でどのデバイスで作られたか追跡
|
|
763
|
+
const deviceIdRow = db.prepare("SELECT value FROM store_meta WHERE key = 'device_id'").get();
|
|
753
764
|
db.prepare(`
|
|
754
|
-
INSERT INTO audit_log (id, operation, entity_type, entity_id, summary, details, client_name, client_version, session_id, source_tool,
|
|
755
|
-
VALUES (@id, @operation, @entity_type, @entity_id, @summary, @details, @client_name, @client_version, @session_id, @source_tool, @
|
|
765
|
+
INSERT INTO audit_log (id, operation, entity_type, entity_id, summary, details, client_name, client_version, session_id, source_tool, device_id, created_at)
|
|
766
|
+
VALUES (@id, @operation, @entity_type, @entity_id, @summary, @details, @client_name, @client_version, @session_id, @source_tool, @device_id, @created_at)
|
|
756
767
|
`).run({
|
|
757
768
|
id,
|
|
758
769
|
operation: params.operation,
|
|
@@ -764,7 +775,7 @@ export function insertAuditLog(db, params) {
|
|
|
764
775
|
client_version: params.provenance?.client_version ?? null,
|
|
765
776
|
session_id: params.provenance?.session_id ?? null,
|
|
766
777
|
source_tool: params.source_tool ?? null,
|
|
767
|
-
|
|
778
|
+
device_id: deviceIdRow?.value ?? null,
|
|
768
779
|
created_at: now,
|
|
769
780
|
});
|
|
770
781
|
}
|
|
@@ -808,7 +819,7 @@ export function insertEpisode(db, params) {
|
|
|
808
819
|
const id = ulid();
|
|
809
820
|
const now = new Date().toISOString();
|
|
810
821
|
db.prepare(`
|
|
811
|
-
INSERT INTO episodes (id, title, l1_content, l2_context, l2_trigger, l2_problems, l2_desires, l2_decisions, l2_outcomes, l2_principles, evidence_refs, tags, scope,
|
|
822
|
+
INSERT INTO episodes (id, title, l1_content, l2_context, l2_trigger, l2_problems, l2_desires, l2_decisions, l2_outcomes, l2_principles, evidence_refs, tags, scope, validity_status, search_summary, l1_embedding, source_tool, session_id, client_name, client_version, user_input, created_at, updated_at)
|
|
812
823
|
VALUES (@id, @title, @l1_content, @l2_context, @l2_trigger, @l2_problems, @l2_desires, @l2_decisions, @l2_outcomes, @l2_principles, @evidence_refs, @tags, @scope, 'active', @search_summary, @l1_embedding, @source_tool, @session_id, @client_name, @client_version, @user_input, @created_at, @updated_at)
|
|
813
824
|
`).run({
|
|
814
825
|
id,
|
|
@@ -845,8 +856,10 @@ export function listEpisodes(db, params) {
|
|
|
845
856
|
const conditions = [];
|
|
846
857
|
const values = { limit: params.limit };
|
|
847
858
|
if (params.status) {
|
|
848
|
-
conditions.push("e.
|
|
849
|
-
|
|
859
|
+
conditions.push("e.validity_status = @validity_status");
|
|
860
|
+
conditions.push("e.is_archived = 0");
|
|
861
|
+
conditions.push("e.promoted_to IS NULL");
|
|
862
|
+
values.validity_status = params.status;
|
|
850
863
|
}
|
|
851
864
|
if (params.tag) {
|
|
852
865
|
conditions.push("e.tags LIKE @tag_like");
|
|
@@ -896,8 +909,10 @@ export function listEpisodes(db, params) {
|
|
|
896
909
|
const conditions = [];
|
|
897
910
|
const values = { limit: params.limit };
|
|
898
911
|
if (params.status) {
|
|
899
|
-
conditions.push("
|
|
900
|
-
|
|
912
|
+
conditions.push("validity_status = @validity_status");
|
|
913
|
+
conditions.push("is_archived = 0");
|
|
914
|
+
conditions.push("promoted_to IS NULL");
|
|
915
|
+
values.validity_status = params.status;
|
|
901
916
|
}
|
|
902
917
|
if (params.tag) {
|
|
903
918
|
conditions.push("tags LIKE @tag_like");
|
|
@@ -918,14 +933,14 @@ export function listEpisodes(db, params) {
|
|
|
918
933
|
}
|
|
919
934
|
export function getEpisodeStats(db) {
|
|
920
935
|
const total_episodes = db.prepare("SELECT COUNT(*) as count FROM episodes").get().count;
|
|
921
|
-
const active_episodes = db.prepare("SELECT COUNT(*) as count FROM episodes WHERE
|
|
922
|
-
const archived_episodes = db.prepare("SELECT COUNT(*) as count FROM episodes WHERE
|
|
936
|
+
const active_episodes = db.prepare("SELECT COUNT(*) as count FROM episodes WHERE validity_status = 'active' AND is_archived = 0").get().count;
|
|
937
|
+
const archived_episodes = db.prepare("SELECT COUNT(*) as count FROM episodes WHERE is_archived = 1").get().count;
|
|
923
938
|
return { total_episodes, active_episodes, archived_episodes };
|
|
924
939
|
}
|
|
925
940
|
// === Issue #40 Phase 3: Episode棚卸 ===
|
|
926
941
|
// 2026-02-23 追加 (Issue #40 Phase 3): 未グルーミングのEpisodeを取得
|
|
927
942
|
export function listUngroomedEpisodes(db, params) {
|
|
928
|
-
const conditions = ["e.
|
|
943
|
+
const conditions = ["e.validity_status = 'active' AND e.is_archived = 0"];
|
|
929
944
|
const values = { limit: params.limit };
|
|
930
945
|
if (!params.include_groomed) {
|
|
931
946
|
conditions.push("e.groomed_at IS NULL");
|
|
@@ -952,19 +967,19 @@ export function listUngroomedEpisodes(db, params) {
|
|
|
952
967
|
LEFT JOIN (
|
|
953
968
|
SELECT j.value AS eid, COUNT(*) AS cnt
|
|
954
969
|
FROM insights, json_each(insights.supporting_episode_ids) AS j
|
|
955
|
-
WHERE insights.
|
|
970
|
+
WHERE insights.validity_status = 'active' AND insights.is_archived = 0
|
|
956
971
|
GROUP BY j.value
|
|
957
972
|
) li ON li.eid = e.id
|
|
958
973
|
LEFT JOIN (
|
|
959
974
|
SELECT j.value AS eid, COUNT(*) AS cnt
|
|
960
975
|
FROM models, json_each(models.supporting_episode_ids) AS j
|
|
961
|
-
WHERE models.
|
|
976
|
+
WHERE models.validity_status = 'active' AND models.is_archived = 0
|
|
962
977
|
GROUP BY j.value
|
|
963
978
|
) lm ON lm.eid = e.id
|
|
964
979
|
LEFT JOIN (
|
|
965
980
|
SELECT j.value AS eid, COUNT(*) AS cnt
|
|
966
981
|
FROM theories, json_each(theories.supporting_episode_ids) AS j
|
|
967
|
-
WHERE theories.
|
|
982
|
+
WHERE theories.validity_status = 'active' AND theories.is_archived = 0
|
|
968
983
|
GROUP BY j.value
|
|
969
984
|
) lt ON lt.eid = e.id
|
|
970
985
|
WHERE ${whereClause}
|
|
@@ -987,7 +1002,7 @@ export function markEpisodesGroomed(db, episodeIds) {
|
|
|
987
1002
|
const markedIds = [];
|
|
988
1003
|
const transaction = db.transaction(() => {
|
|
989
1004
|
for (const id of episodeIds) {
|
|
990
|
-
const result = db.prepare("UPDATE episodes SET groomed_at = @now WHERE id = @id AND
|
|
1005
|
+
const result = db.prepare("UPDATE episodes SET groomed_at = @now WHERE id = @id AND validity_status = 'active' AND is_archived = 0 AND groomed_at IS NULL").run({ id, now });
|
|
991
1006
|
if (result.changes > 0) {
|
|
992
1007
|
markedIds.push(id);
|
|
993
1008
|
}
|
|
@@ -1003,7 +1018,7 @@ export function archiveEpisodes(db, params) {
|
|
|
1003
1018
|
if (params.episodeIds && params.episodeIds.length > 0) {
|
|
1004
1019
|
// 個別指定モード: 指定IDのみアーカイブ
|
|
1005
1020
|
const updated = [];
|
|
1006
|
-
const stmt = db.prepare("UPDATE episodes SET
|
|
1021
|
+
const stmt = db.prepare("UPDATE episodes SET is_archived = 1, updated_at = @now WHERE id = @id AND validity_status = 'active' AND is_archived = 0");
|
|
1007
1022
|
const now = new Date().toISOString();
|
|
1008
1023
|
for (const id of params.episodeIds) {
|
|
1009
1024
|
const result = stmt.run({ id, now });
|
|
@@ -1018,7 +1033,7 @@ export function archiveEpisodes(db, params) {
|
|
|
1018
1033
|
// 最新 keepLatest 件の ID を取得(保護対象)
|
|
1019
1034
|
const protectedIds = db.prepare(`
|
|
1020
1035
|
SELECT id FROM episodes
|
|
1021
|
-
WHERE
|
|
1036
|
+
WHERE validity_status = 'active' AND is_archived = 0${scopeFilter}
|
|
1022
1037
|
ORDER BY created_at DESC
|
|
1023
1038
|
LIMIT @keepLatest
|
|
1024
1039
|
`).all({ ...scopeValues, keepLatest });
|
|
@@ -1026,11 +1041,11 @@ export function archiveEpisodes(db, params) {
|
|
|
1026
1041
|
// groomed_at IS NOT NULL かつ保護対象外をアーカイブ
|
|
1027
1042
|
const candidates = db.prepare(`
|
|
1028
1043
|
SELECT id FROM episodes
|
|
1029
|
-
WHERE
|
|
1044
|
+
WHERE validity_status = 'active' AND is_archived = 0 AND groomed_at IS NOT NULL${scopeFilter}
|
|
1030
1045
|
`).all(scopeValues);
|
|
1031
1046
|
const toArchive = candidates.filter(r => !protectedSet.has(r.id));
|
|
1032
1047
|
const now = new Date().toISOString();
|
|
1033
|
-
const stmt = db.prepare("UPDATE episodes SET
|
|
1048
|
+
const stmt = db.prepare("UPDATE episodes SET is_archived = 1, updated_at = @now WHERE id = @id");
|
|
1034
1049
|
for (const r of toArchive) {
|
|
1035
1050
|
stmt.run({ id: r.id, now });
|
|
1036
1051
|
}
|
|
@@ -1041,7 +1056,7 @@ export function archiveDecisions(db, params) {
|
|
|
1041
1056
|
const keepLatest = params.keepLatest ?? 20;
|
|
1042
1057
|
if (params.decisionIds && params.decisionIds.length > 0) {
|
|
1043
1058
|
const updated = [];
|
|
1044
|
-
const stmt = db.prepare("UPDATE decisions SET
|
|
1059
|
+
const stmt = db.prepare("UPDATE decisions SET is_archived = 1, updated_at = @now WHERE id = @id AND validity_status = 'active' AND is_archived = 0");
|
|
1045
1060
|
const now = new Date().toISOString();
|
|
1046
1061
|
for (const id of params.decisionIds) {
|
|
1047
1062
|
const result = stmt.run({ id, now });
|
|
@@ -1055,18 +1070,18 @@ export function archiveDecisions(db, params) {
|
|
|
1055
1070
|
const scopeValues = params.scope ? { scope: params.scope } : {};
|
|
1056
1071
|
const protectedIds = db.prepare(`
|
|
1057
1072
|
SELECT id FROM decisions
|
|
1058
|
-
WHERE
|
|
1073
|
+
WHERE validity_status = 'active' AND is_archived = 0${scopeFilter}
|
|
1059
1074
|
ORDER BY created_at DESC
|
|
1060
1075
|
LIMIT @keepLatest
|
|
1061
1076
|
`).all({ ...scopeValues, keepLatest });
|
|
1062
1077
|
const protectedSet = new Set(protectedIds.map(r => r.id));
|
|
1063
1078
|
const candidates = db.prepare(`
|
|
1064
1079
|
SELECT id FROM decisions
|
|
1065
|
-
WHERE
|
|
1080
|
+
WHERE validity_status = 'active' AND is_archived = 0${scopeFilter}
|
|
1066
1081
|
`).all(scopeValues);
|
|
1067
1082
|
const toArchive = candidates.filter(r => !protectedSet.has(r.id));
|
|
1068
1083
|
const now = new Date().toISOString();
|
|
1069
|
-
const stmt = db.prepare("UPDATE decisions SET
|
|
1084
|
+
const stmt = db.prepare("UPDATE decisions SET is_archived = 1, updated_at = @now WHERE id = @id");
|
|
1070
1085
|
for (const r of toArchive) {
|
|
1071
1086
|
stmt.run({ id: r.id, now });
|
|
1072
1087
|
}
|
|
@@ -1079,7 +1094,7 @@ export function getTopicSummary(db, tagLimit = 10, decisionLimit = 5, scope) {
|
|
|
1079
1094
|
const scopeFilter = scope ? " AND (scope = @scope OR scope = 'global')" : "";
|
|
1080
1095
|
const scopeValues = scope ? { scope } : {};
|
|
1081
1096
|
// カテゴリ分布
|
|
1082
|
-
const categoryRows = db.prepare(`SELECT category, COUNT(*) as count FROM claims WHERE
|
|
1097
|
+
const categoryRows = db.prepare(`SELECT category, COUNT(*) as count FROM claims WHERE validity_status = 'active' AND is_archived = 0${scopeFilter} GROUP BY category`).all(scopeValues);
|
|
1083
1098
|
const categories = {};
|
|
1084
1099
|
for (const row of categoryRows) {
|
|
1085
1100
|
categories[row.category] = row.count;
|
|
@@ -1089,13 +1104,13 @@ export function getTopicSummary(db, tagLimit = 10, decisionLimit = 5, scope) {
|
|
|
1089
1104
|
try {
|
|
1090
1105
|
topTags = db.prepare(`
|
|
1091
1106
|
SELECT tag, COUNT(*) as count FROM (
|
|
1092
|
-
SELECT j.value as tag FROM episodes, json_each(episodes.tags) AS j WHERE episodes.
|
|
1107
|
+
SELECT j.value as tag FROM episodes, json_each(episodes.tags) AS j WHERE episodes.validity_status = 'active' AND episodes.is_archived = 0${scopeFilter.replace("scope", "episodes.scope")}
|
|
1093
1108
|
UNION ALL
|
|
1094
|
-
SELECT j.value as tag FROM theories, json_each(theories.tags) AS j WHERE theories.
|
|
1109
|
+
SELECT j.value as tag FROM theories, json_each(theories.tags) AS j WHERE theories.validity_status = 'active' AND theories.is_archived = 0${scopeFilter.replace("scope", "theories.scope")}
|
|
1095
1110
|
UNION ALL
|
|
1096
|
-
SELECT j.value as tag FROM insights, json_each(insights.tags) AS j WHERE insights.
|
|
1111
|
+
SELECT j.value as tag FROM insights, json_each(insights.tags) AS j WHERE insights.validity_status = 'active' AND insights.is_archived = 0${scopeFilter.replace("scope", "insights.scope")}
|
|
1097
1112
|
UNION ALL
|
|
1098
|
-
SELECT j.value as tag FROM models, json_each(models.tags) AS j WHERE models.
|
|
1113
|
+
SELECT j.value as tag FROM models, json_each(models.tags) AS j WHERE models.validity_status = 'active' AND models.is_archived = 0${scopeFilter.replace("scope", "models.scope")}
|
|
1099
1114
|
) GROUP BY tag ORDER BY count DESC LIMIT @tagLimit
|
|
1100
1115
|
`).all({ ...scopeValues, tagLimit });
|
|
1101
1116
|
}
|
|
@@ -1103,38 +1118,43 @@ export function getTopicSummary(db, tagLimit = 10, decisionLimit = 5, scope) {
|
|
|
1103
1118
|
// json_each 非対応環境のフォールバック: 空配列
|
|
1104
1119
|
}
|
|
1105
1120
|
// 最近のDecision
|
|
1106
|
-
const recentDecisions = db.prepare(`SELECT title, created_at FROM decisions WHERE
|
|
1121
|
+
const recentDecisions = db.prepare(`SELECT title, created_at FROM decisions WHERE validity_status = 'active' AND is_archived = 0${scopeFilter} ORDER BY created_at DESC LIMIT @decisionLimit`).all({ ...scopeValues, decisionLimit });
|
|
1107
1122
|
// アクティブスコープ一覧
|
|
1108
|
-
const scopeRows = db.prepare(`SELECT DISTINCT scope FROM claims WHERE
|
|
1123
|
+
const scopeRows = db.prepare(`SELECT DISTINCT scope FROM claims WHERE validity_status = 'active' AND is_archived = 0${scopeFilter} ORDER BY scope`).all(scopeValues);
|
|
1109
1124
|
const activeScopes = scopeRows.map(r => r.scope);
|
|
1110
1125
|
// 4テーブルのアクティブ件数
|
|
1111
|
-
const activeClaims = db.prepare(`SELECT COUNT(*) as count FROM claims WHERE
|
|
1112
|
-
const activeEpisodes = db.prepare(`SELECT COUNT(*) as count FROM episodes WHERE
|
|
1113
|
-
const activeDecisions = db.prepare(`SELECT COUNT(*) as count FROM decisions WHERE
|
|
1114
|
-
const activeTheories = db.prepare(`SELECT COUNT(*) as count FROM theories WHERE
|
|
1126
|
+
const activeClaims = db.prepare(`SELECT COUNT(*) as count FROM claims WHERE validity_status = 'active' AND is_archived = 0${scopeFilter}`).get(scopeValues).count;
|
|
1127
|
+
const activeEpisodes = db.prepare(`SELECT COUNT(*) as count FROM episodes WHERE validity_status = 'active' AND is_archived = 0${scopeFilter}`).get(scopeValues).count;
|
|
1128
|
+
const activeDecisions = db.prepare(`SELECT COUNT(*) as count FROM decisions WHERE validity_status = 'active' AND is_archived = 0${scopeFilter}`).get(scopeValues).count;
|
|
1129
|
+
const activeTheories = db.prepare(`SELECT COUNT(*) as count FROM theories WHERE validity_status = 'active' AND is_archived = 0${scopeFilter}`).get(scopeValues).count;
|
|
1115
1130
|
// 2026-02-23 追加 (Issue #40): Insight/Model のアクティブ件数
|
|
1116
|
-
const activeInsights = db.prepare(`SELECT COUNT(*) as count FROM insights WHERE
|
|
1117
|
-
const activeModels = db.prepare(`SELECT COUNT(*) as count FROM models WHERE
|
|
1131
|
+
const activeInsights = db.prepare(`SELECT COUNT(*) as count FROM insights WHERE validity_status = 'active' AND is_archived = 0${scopeFilter}`).get(scopeValues).count;
|
|
1132
|
+
const activeModels = db.prepare(`SELECT COUNT(*) as count FROM models WHERE validity_status = 'active' AND is_archived = 0${scopeFilter}`).get(scopeValues).count;
|
|
1118
1133
|
// 2026-02-25 リネーム: memos → user_memos (Issue #45 Phase 1)
|
|
1119
1134
|
let activeMemos = 0;
|
|
1120
1135
|
try {
|
|
1121
|
-
activeMemos = db.prepare(`SELECT COUNT(*) as count FROM user_memos WHERE
|
|
1136
|
+
activeMemos = db.prepare(`SELECT COUNT(*) as count FROM user_memos WHERE memo_status = 'active' AND is_archived = 0${scopeFilter}`).get(scopeValues).count;
|
|
1122
1137
|
}
|
|
1123
1138
|
catch { /* V16未適用 */ }
|
|
1139
|
+
let activeFiles = 0;
|
|
1140
|
+
try {
|
|
1141
|
+
activeFiles = db.prepare(`SELECT COUNT(*) as count FROM user_files WHERE file_status = 'active' AND is_archived = 0${scopeFilter}`).get(scopeValues).count;
|
|
1142
|
+
}
|
|
1143
|
+
catch { /* V31未適用 */ }
|
|
1124
1144
|
// Issue #47: エンティティタイトルから具体的キーワード候補を抽出
|
|
1125
1145
|
let topicKeywords = [];
|
|
1126
1146
|
try {
|
|
1127
1147
|
const keywordRows = db.prepare(`
|
|
1128
1148
|
SELECT title FROM (
|
|
1129
|
-
SELECT title, updated_at FROM episodes WHERE
|
|
1149
|
+
SELECT title, updated_at FROM episodes WHERE validity_status = 'active' AND is_archived = 0${scopeFilter.replace("scope", "episodes.scope")}
|
|
1130
1150
|
UNION ALL
|
|
1131
|
-
SELECT title, updated_at FROM decisions WHERE
|
|
1151
|
+
SELECT title, updated_at FROM decisions WHERE validity_status = 'active' AND is_archived = 0${scopeFilter.replace("scope", "decisions.scope")}
|
|
1132
1152
|
UNION ALL
|
|
1133
|
-
SELECT title, updated_at FROM theories WHERE
|
|
1153
|
+
SELECT title, updated_at FROM theories WHERE validity_status = 'active' AND is_archived = 0${scopeFilter.replace("scope", "theories.scope")}
|
|
1134
1154
|
UNION ALL
|
|
1135
|
-
SELECT title, updated_at FROM insights WHERE
|
|
1155
|
+
SELECT title, updated_at FROM insights WHERE validity_status = 'active' AND is_archived = 0${scopeFilter.replace("scope", "insights.scope")}
|
|
1136
1156
|
UNION ALL
|
|
1137
|
-
SELECT title, updated_at FROM models WHERE
|
|
1157
|
+
SELECT title, updated_at FROM models WHERE validity_status = 'active' AND is_archived = 0${scopeFilter.replace("scope", "models.scope")}
|
|
1138
1158
|
) ORDER BY updated_at DESC LIMIT 20
|
|
1139
1159
|
`).all(scopeValues);
|
|
1140
1160
|
topicKeywords = keywordRows.map(r => r.title.length > 30 ? r.title.slice(0, 30) + "…" : r.title);
|
|
@@ -1148,7 +1168,7 @@ export function getTopicSummary(db, tagLimit = 10, decisionLimit = 5, scope) {
|
|
|
1148
1168
|
topicKeywords,
|
|
1149
1169
|
recentDecisions,
|
|
1150
1170
|
activeScopes,
|
|
1151
|
-
counts: { activeClaims, activeEpisodes, activeDecisions, activeTheories, activeInsights, activeModels, activeMemos },
|
|
1171
|
+
counts: { activeClaims, activeEpisodes, activeDecisions, activeTheories, activeInsights, activeModels, activeMemos, activeFiles },
|
|
1152
1172
|
};
|
|
1153
1173
|
}
|
|
1154
1174
|
// === V9: Theory CRUD ===
|
|
@@ -1161,7 +1181,7 @@ export function insertTheory(db, params) {
|
|
|
1161
1181
|
INSERT INTO theories (id, title, description, l1_content,
|
|
1162
1182
|
l2_core_thesis, l2_principles, l2_trigger_conditions, l2_resolution_steps, l2_applicable_context,
|
|
1163
1183
|
non_goals, open_questions,
|
|
1164
|
-
supporting_episode_ids, supporting_claim_ids, evidence_refs, tags, scope, confidence,
|
|
1184
|
+
supporting_episode_ids, supporting_claim_ids, evidence_refs, tags, scope, confidence, validity_status, search_summary, l1_embedding,
|
|
1165
1185
|
source_tool, session_id, client_name, client_version, created_at, updated_at)
|
|
1166
1186
|
VALUES (@id, @title, @description, @l1_content,
|
|
1167
1187
|
@l2_core_thesis, @l2_principles, @l2_trigger_conditions, @l2_resolution_steps, @l2_applicable_context,
|
|
@@ -1206,8 +1226,10 @@ export function listTheories(db, params) {
|
|
|
1206
1226
|
const conditions = [];
|
|
1207
1227
|
const values = { limit: params.limit };
|
|
1208
1228
|
if (params.status) {
|
|
1209
|
-
conditions.push("t.
|
|
1210
|
-
|
|
1229
|
+
conditions.push("t.validity_status = @validity_status");
|
|
1230
|
+
conditions.push("t.is_archived = 0");
|
|
1231
|
+
conditions.push("t.promoted_to IS NULL");
|
|
1232
|
+
values.validity_status = params.status;
|
|
1211
1233
|
}
|
|
1212
1234
|
if (params.tag) {
|
|
1213
1235
|
conditions.push("t.tags LIKE @tag_like");
|
|
@@ -1257,8 +1279,10 @@ export function listTheories(db, params) {
|
|
|
1257
1279
|
const conditions = [];
|
|
1258
1280
|
const values = { limit: params.limit };
|
|
1259
1281
|
if (params.status) {
|
|
1260
|
-
conditions.push("
|
|
1261
|
-
|
|
1282
|
+
conditions.push("validity_status = @validity_status");
|
|
1283
|
+
conditions.push("is_archived = 0");
|
|
1284
|
+
conditions.push("promoted_to IS NULL");
|
|
1285
|
+
values.validity_status = params.status;
|
|
1262
1286
|
}
|
|
1263
1287
|
if (params.tag) {
|
|
1264
1288
|
conditions.push("tags LIKE @tag_like");
|
|
@@ -1292,7 +1316,7 @@ function queryInsightContext(db, topic, scope, limit) {
|
|
|
1292
1316
|
SELECT t.* FROM insights t
|
|
1293
1317
|
JOIN insights_fts f ON t.rowid = f.rowid
|
|
1294
1318
|
WHERE insights_fts MATCH @query
|
|
1295
|
-
AND t.
|
|
1319
|
+
AND t.validity_status = 'active' AND t.is_archived = 0 AND t.promoted_to IS NULL
|
|
1296
1320
|
${scopeFilter}
|
|
1297
1321
|
ORDER BY rank
|
|
1298
1322
|
LIMIT @limit
|
|
@@ -1308,7 +1332,7 @@ function queryInsightContext(db, topic, scope, limit) {
|
|
|
1308
1332
|
// 2026-03-01 修正 (Issue #53): l1/l2 カラム名に対応
|
|
1309
1333
|
return db.prepare(`
|
|
1310
1334
|
SELECT t.* FROM insights t
|
|
1311
|
-
WHERE t.
|
|
1335
|
+
WHERE t.validity_status = 'active' AND t.is_archived = 0 AND t.promoted_to IS NULL
|
|
1312
1336
|
AND (t.title LIKE @like_query OR t.description LIKE @like_query OR t.l1_content LIKE @like_query OR t.l2_core_thesis LIKE @like_query OR t.l2_principles LIKE @like_query OR t.tags LIKE @like_query OR t.search_summary LIKE @like_query)
|
|
1313
1337
|
${scopeFilter}
|
|
1314
1338
|
ORDER BY t.updated_at DESC
|
|
@@ -1330,7 +1354,7 @@ function queryModelContext(db, topic, scope, limit) {
|
|
|
1330
1354
|
SELECT t.* FROM models t
|
|
1331
1355
|
JOIN models_fts f ON t.rowid = f.rowid
|
|
1332
1356
|
WHERE models_fts MATCH @query
|
|
1333
|
-
AND t.
|
|
1357
|
+
AND t.validity_status = 'active' AND t.is_archived = 0 AND t.promoted_to IS NULL
|
|
1334
1358
|
${scopeFilter}
|
|
1335
1359
|
ORDER BY rank
|
|
1336
1360
|
LIMIT @limit
|
|
@@ -1345,7 +1369,7 @@ function queryModelContext(db, topic, scope, limit) {
|
|
|
1345
1369
|
const likeQuery = `%${topic}%`;
|
|
1346
1370
|
return db.prepare(`
|
|
1347
1371
|
SELECT t.* FROM models t
|
|
1348
|
-
WHERE t.
|
|
1372
|
+
WHERE t.validity_status = 'active' AND t.is_archived = 0 AND t.promoted_to IS NULL
|
|
1349
1373
|
AND (t.title LIKE @like_query OR t.description LIKE @like_query OR t.l1_content LIKE @like_query OR t.l2_core_thesis LIKE @like_query OR t.l2_principles LIKE @like_query OR t.tags LIKE @like_query OR t.search_summary LIKE @like_query)
|
|
1350
1374
|
${scopeFilter}
|
|
1351
1375
|
ORDER BY t.updated_at DESC
|
|
@@ -1366,7 +1390,7 @@ export function searchPrinciplesForRecall(db, promptText, limit) {
|
|
|
1366
1390
|
SELECT e.* FROM episodes e
|
|
1367
1391
|
JOIN episodes_fts f ON e.rowid = f.rowid
|
|
1368
1392
|
WHERE episodes_fts MATCH @query
|
|
1369
|
-
AND e.
|
|
1393
|
+
AND e.validity_status = 'active' AND e.is_archived = 0 AND e.promoted_to IS NULL
|
|
1370
1394
|
AND e.l2_principles != '[]'
|
|
1371
1395
|
ORDER BY rank
|
|
1372
1396
|
LIMIT @limit
|
|
@@ -1380,7 +1404,7 @@ export function searchPrinciplesForRecall(db, promptText, limit) {
|
|
|
1380
1404
|
const likeQuery = `%${querySnippet}%`;
|
|
1381
1405
|
episodeRows = db.prepare(`
|
|
1382
1406
|
SELECT e.* FROM episodes e
|
|
1383
|
-
WHERE e.
|
|
1407
|
+
WHERE e.validity_status = 'active' AND e.is_archived = 0 AND e.promoted_to IS NULL
|
|
1384
1408
|
AND e.l2_principles != '[]'
|
|
1385
1409
|
AND (e.title LIKE @like_query OR e.l1_content LIKE @like_query OR e.l2_context LIKE @like_query OR e.l2_trigger LIKE @like_query OR e.l2_principles LIKE @like_query OR e.l2_problems LIKE @like_query OR e.l2_desires LIKE @like_query OR e.l2_outcomes LIKE @like_query OR e.l2_decisions LIKE @like_query OR e.search_summary LIKE @like_query)
|
|
1386
1410
|
ORDER BY e.updated_at DESC
|
|
@@ -1429,7 +1453,7 @@ export function searchPrinciplesForRecall(db, promptText, limit) {
|
|
|
1429
1453
|
try {
|
|
1430
1454
|
const insightRows = db.prepare(`
|
|
1431
1455
|
SELECT * FROM insights
|
|
1432
|
-
WHERE
|
|
1456
|
+
WHERE validity_status = 'active' AND is_archived = 0
|
|
1433
1457
|
AND l2_principles != '[]'
|
|
1434
1458
|
AND (${insightLike.clause})
|
|
1435
1459
|
ORDER BY updated_at DESC
|
|
@@ -1450,7 +1474,7 @@ export function searchPrinciplesForRecall(db, promptText, limit) {
|
|
|
1450
1474
|
try {
|
|
1451
1475
|
const theoryRows = db.prepare(`
|
|
1452
1476
|
SELECT * FROM theories
|
|
1453
|
-
WHERE
|
|
1477
|
+
WHERE validity_status = 'active' AND is_archived = 0
|
|
1454
1478
|
AND (l2_principles != '[]' OR open_questions != '[]')
|
|
1455
1479
|
AND (${theoryLike.clause})
|
|
1456
1480
|
ORDER BY updated_at DESC
|
|
@@ -1484,7 +1508,7 @@ export function listEpisodesWithPrinciples(db, limit, scope) {
|
|
|
1484
1508
|
}
|
|
1485
1509
|
return db.prepare(`
|
|
1486
1510
|
SELECT * FROM episodes
|
|
1487
|
-
WHERE
|
|
1511
|
+
WHERE validity_status = 'active' AND is_archived = 0 AND l2_principles != '[]'
|
|
1488
1512
|
${scopeFilter}
|
|
1489
1513
|
ORDER BY updated_at DESC
|
|
1490
1514
|
LIMIT @limit
|
|
@@ -1505,7 +1529,7 @@ export function listEscalationCandidates(db, params) {
|
|
|
1505
1529
|
json_array_length(supporting_episode_ids) as supporting_episode_count,
|
|
1506
1530
|
scope, created_at, description
|
|
1507
1531
|
FROM insights
|
|
1508
|
-
WHERE
|
|
1532
|
+
WHERE validity_status = 'active' AND is_archived = 0
|
|
1509
1533
|
AND confidence >= 0.8
|
|
1510
1534
|
AND l2_principles != '[]'
|
|
1511
1535
|
AND json_array_length(supporting_episode_ids) >= 2
|
|
@@ -1533,7 +1557,7 @@ export function listEscalationCandidates(db, params) {
|
|
|
1533
1557
|
json_array_length(supporting_episode_ids) as supporting_episode_count,
|
|
1534
1558
|
scope, created_at, description
|
|
1535
1559
|
FROM theories
|
|
1536
|
-
WHERE
|
|
1560
|
+
WHERE validity_status = 'active' AND is_archived = 0
|
|
1537
1561
|
AND confidence >= 0.8
|
|
1538
1562
|
AND l2_principles != '[]'
|
|
1539
1563
|
AND json_array_length(supporting_episode_ids) >= 2
|
|
@@ -1561,7 +1585,7 @@ export function listEscalationCandidates(db, params) {
|
|
|
1561
1585
|
json_array_length(supporting_episode_ids) as supporting_episode_count,
|
|
1562
1586
|
scope, created_at, l2_core_thesis
|
|
1563
1587
|
FROM models
|
|
1564
|
-
WHERE
|
|
1588
|
+
WHERE validity_status = 'active' AND is_archived = 0
|
|
1565
1589
|
AND confidence >= 0.8
|
|
1566
1590
|
AND l2_principles != '[]'
|
|
1567
1591
|
AND json_array_length(supporting_episode_ids) >= 2
|
|
@@ -1603,7 +1627,7 @@ function queryTheoryContext(db, topic, scope, limit) {
|
|
|
1603
1627
|
SELECT t.* FROM theories t
|
|
1604
1628
|
JOIN theories_fts f ON t.rowid = f.rowid
|
|
1605
1629
|
WHERE theories_fts MATCH @query
|
|
1606
|
-
AND t.
|
|
1630
|
+
AND t.validity_status = 'active' AND t.is_archived = 0 AND t.promoted_to IS NULL
|
|
1607
1631
|
${scopeFilter}
|
|
1608
1632
|
ORDER BY rank
|
|
1609
1633
|
LIMIT @limit
|
|
@@ -1618,7 +1642,7 @@ function queryTheoryContext(db, topic, scope, limit) {
|
|
|
1618
1642
|
const likeQuery = `%${topic}%`;
|
|
1619
1643
|
return db.prepare(`
|
|
1620
1644
|
SELECT t.* FROM theories t
|
|
1621
|
-
WHERE t.
|
|
1645
|
+
WHERE t.validity_status = 'active' AND t.is_archived = 0 AND t.promoted_to IS NULL
|
|
1622
1646
|
AND (t.title LIKE @like_query OR t.description LIKE @like_query OR t.l1_content LIKE @like_query OR t.l2_core_thesis LIKE @like_query OR t.l2_principles LIKE @like_query OR t.tags LIKE @like_query OR t.search_summary LIKE @like_query)
|
|
1623
1647
|
${scopeFilter}
|
|
1624
1648
|
ORDER BY t.updated_at DESC
|
|
@@ -1632,7 +1656,7 @@ function queryTheoryContext(db, topic, scope, limit) {
|
|
|
1632
1656
|
* TypeScript 側で陳腐化スコアを計算してソート・フィルタする。
|
|
1633
1657
|
*/
|
|
1634
1658
|
export function getStaleClaims(db, params) {
|
|
1635
|
-
const conditions = ["c.
|
|
1659
|
+
const conditions = ["c.validity_status = 'active'", "c.is_archived = 0"];
|
|
1636
1660
|
const values = {};
|
|
1637
1661
|
if (params.scope) {
|
|
1638
1662
|
conditions.push("(c.scope = @scope OR c.scope = 'global')");
|
|
@@ -1763,11 +1787,11 @@ export function countUnconsolidatedItems(db) {
|
|
|
1763
1787
|
try {
|
|
1764
1788
|
const row = db.prepare(`
|
|
1765
1789
|
SELECT MAX(created_at) AS last_at FROM (
|
|
1766
|
-
SELECT created_at FROM theories WHERE
|
|
1790
|
+
SELECT created_at FROM theories WHERE validity_status = 'active' AND is_archived = 0
|
|
1767
1791
|
UNION ALL
|
|
1768
|
-
SELECT created_at FROM insights WHERE
|
|
1792
|
+
SELECT created_at FROM insights WHERE validity_status = 'active' AND is_archived = 0
|
|
1769
1793
|
UNION ALL
|
|
1770
|
-
SELECT created_at FROM models WHERE
|
|
1794
|
+
SELECT created_at FROM models WHERE validity_status = 'active' AND is_archived = 0
|
|
1771
1795
|
)
|
|
1772
1796
|
`).get();
|
|
1773
1797
|
lastAt = row?.last_at ?? null;
|
|
@@ -1778,9 +1802,9 @@ export function countUnconsolidatedItems(db) {
|
|
|
1778
1802
|
if (lastAt) {
|
|
1779
1803
|
const result = db.prepare(`
|
|
1780
1804
|
SELECT (
|
|
1781
|
-
(SELECT COUNT(*) FROM claims WHERE
|
|
1782
|
-
(SELECT COUNT(*) FROM decisions WHERE
|
|
1783
|
-
(SELECT COUNT(*) FROM episodes WHERE
|
|
1805
|
+
(SELECT COUNT(*) FROM claims WHERE validity_status = 'active' AND is_archived = 0 AND created_at > @since) +
|
|
1806
|
+
(SELECT COUNT(*) FROM decisions WHERE validity_status = 'active' AND is_archived = 0 AND created_at > @since) +
|
|
1807
|
+
(SELECT COUNT(*) FROM episodes WHERE validity_status = 'active' AND is_archived = 0 AND created_at > @since)
|
|
1784
1808
|
) AS count
|
|
1785
1809
|
`).get({ since: lastAt });
|
|
1786
1810
|
return result.count;
|
|
@@ -1788,9 +1812,9 @@ export function countUnconsolidatedItems(db) {
|
|
|
1788
1812
|
// Theory/Insight/Model が一件もない場合: 全アクティブアイテム合計
|
|
1789
1813
|
const result = db.prepare(`
|
|
1790
1814
|
SELECT (
|
|
1791
|
-
(SELECT COUNT(*) FROM claims WHERE
|
|
1792
|
-
(SELECT COUNT(*) FROM decisions WHERE
|
|
1793
|
-
(SELECT COUNT(*) FROM episodes WHERE
|
|
1815
|
+
(SELECT COUNT(*) FROM claims WHERE validity_status = 'active' AND is_archived = 0) +
|
|
1816
|
+
(SELECT COUNT(*) FROM decisions WHERE validity_status = 'active' AND is_archived = 0) +
|
|
1817
|
+
(SELECT COUNT(*) FROM episodes WHERE validity_status = 'active' AND is_archived = 0)
|
|
1794
1818
|
) AS count
|
|
1795
1819
|
`).get();
|
|
1796
1820
|
return result.count;
|
|
@@ -1801,7 +1825,7 @@ export function countUnconsolidatedItems(db) {
|
|
|
1801
1825
|
export function countStaleClaims(db) {
|
|
1802
1826
|
const result = db.prepare(`
|
|
1803
1827
|
SELECT COUNT(*) AS count FROM claims
|
|
1804
|
-
WHERE
|
|
1828
|
+
WHERE validity_status = 'active' AND is_archived = 0
|
|
1805
1829
|
AND julianday('now') - julianday(updated_at) > 30
|
|
1806
1830
|
`).get();
|
|
1807
1831
|
return result.count;
|
|
@@ -1884,44 +1908,44 @@ export function updateTheoryL1Embedding(db, id, l1_embedding) {
|
|
|
1884
1908
|
}
|
|
1885
1909
|
/** l1_embedding が NULL の active Claim を取得(バックフィル用) */
|
|
1886
1910
|
export function getClaimsWithoutL1Embedding(db, limit) {
|
|
1887
|
-
return db.prepare("SELECT * FROM claims WHERE
|
|
1911
|
+
return db.prepare("SELECT * FROM claims WHERE validity_status = 'active' AND is_archived = 0 AND l1_embedding IS NULL ORDER BY updated_at DESC LIMIT ?").all(limit);
|
|
1888
1912
|
}
|
|
1889
1913
|
/** l1_embedding が NULL の active Decision を取得(バックフィル用) */
|
|
1890
1914
|
export function getDecisionsWithoutL1Embedding(db, limit) {
|
|
1891
|
-
return db.prepare("SELECT * FROM decisions WHERE
|
|
1915
|
+
return db.prepare("SELECT * FROM decisions WHERE validity_status = 'active' AND is_archived = 0 AND l1_embedding IS NULL ORDER BY updated_at DESC LIMIT ?").all(limit);
|
|
1892
1916
|
}
|
|
1893
1917
|
/** l1_embedding が NULL の active Episode を取得(バックフィル用) */
|
|
1894
1918
|
export function getEpisodesWithoutL1Embedding(db, limit) {
|
|
1895
|
-
return db.prepare("SELECT * FROM episodes WHERE
|
|
1919
|
+
return db.prepare("SELECT * FROM episodes WHERE validity_status = 'active' AND is_archived = 0 AND l1_embedding IS NULL ORDER BY updated_at DESC LIMIT ?").all(limit);
|
|
1896
1920
|
}
|
|
1897
1921
|
/** l1_embedding が NULL の active Theory を取得(バックフィル用) */
|
|
1898
1922
|
export function getTheoriesWithoutL1Embedding(db, limit) {
|
|
1899
|
-
return db.prepare("SELECT * FROM theories WHERE
|
|
1923
|
+
return db.prepare("SELECT * FROM theories WHERE validity_status = 'active' AND is_archived = 0 AND l1_embedding IS NULL ORDER BY updated_at DESC LIMIT ?").all(limit);
|
|
1900
1924
|
}
|
|
1901
1925
|
/** 指定テーブルで l1_embedding が存在するレコードを取得 */
|
|
1902
1926
|
export function getClaimsWithL1Embedding(db, scope, limit = 100) {
|
|
1903
1927
|
if (scope) {
|
|
1904
|
-
return db.prepare("SELECT * FROM claims WHERE
|
|
1928
|
+
return db.prepare("SELECT * FROM claims WHERE validity_status = 'active' AND is_archived = 0 AND l1_embedding IS NOT NULL AND scope = ? ORDER BY updated_at DESC LIMIT ?").all(scope, limit);
|
|
1905
1929
|
}
|
|
1906
|
-
return db.prepare("SELECT * FROM claims WHERE
|
|
1930
|
+
return db.prepare("SELECT * FROM claims WHERE validity_status = 'active' AND is_archived = 0 AND l1_embedding IS NOT NULL ORDER BY updated_at DESC LIMIT ?").all(limit);
|
|
1907
1931
|
}
|
|
1908
1932
|
export function getDecisionsWithL1Embedding(db, scope, limit = 100) {
|
|
1909
1933
|
if (scope) {
|
|
1910
|
-
return db.prepare("SELECT * FROM decisions WHERE
|
|
1934
|
+
return db.prepare("SELECT * FROM decisions WHERE validity_status = 'active' AND is_archived = 0 AND l1_embedding IS NOT NULL AND scope = ? ORDER BY updated_at DESC LIMIT ?").all(scope, limit);
|
|
1911
1935
|
}
|
|
1912
|
-
return db.prepare("SELECT * FROM decisions WHERE
|
|
1936
|
+
return db.prepare("SELECT * FROM decisions WHERE validity_status = 'active' AND is_archived = 0 AND l1_embedding IS NOT NULL ORDER BY updated_at DESC LIMIT ?").all(limit);
|
|
1913
1937
|
}
|
|
1914
1938
|
export function getEpisodesWithL1Embedding(db, scope, limit = 100) {
|
|
1915
1939
|
if (scope) {
|
|
1916
|
-
return db.prepare("SELECT * FROM episodes WHERE
|
|
1940
|
+
return db.prepare("SELECT * FROM episodes WHERE validity_status = 'active' AND is_archived = 0 AND l1_embedding IS NOT NULL AND scope = ? ORDER BY updated_at DESC LIMIT ?").all(scope, limit);
|
|
1917
1941
|
}
|
|
1918
|
-
return db.prepare("SELECT * FROM episodes WHERE
|
|
1942
|
+
return db.prepare("SELECT * FROM episodes WHERE validity_status = 'active' AND is_archived = 0 AND l1_embedding IS NOT NULL ORDER BY updated_at DESC LIMIT ?").all(limit);
|
|
1919
1943
|
}
|
|
1920
1944
|
export function getTheoriesWithL1Embedding(db, scope, limit = 100) {
|
|
1921
1945
|
if (scope) {
|
|
1922
|
-
return db.prepare("SELECT * FROM theories WHERE
|
|
1946
|
+
return db.prepare("SELECT * FROM theories WHERE validity_status = 'active' AND is_archived = 0 AND l1_embedding IS NOT NULL AND scope = ? ORDER BY updated_at DESC LIMIT ?").all(scope, limit);
|
|
1923
1947
|
}
|
|
1924
|
-
return db.prepare("SELECT * FROM theories WHERE
|
|
1948
|
+
return db.prepare("SELECT * FROM theories WHERE validity_status = 'active' AND is_archived = 0 AND l1_embedding IS NOT NULL ORDER BY updated_at DESC LIMIT ?").all(limit);
|
|
1925
1949
|
}
|
|
1926
1950
|
// === V12: Insight CRUD (Issue #40) ===
|
|
1927
1951
|
// 2026-03-01 修正 (Issue #53): l1/l2 カラム拡張 — core_thesis → l2_core_thesis, principles → l2_principles, 新カラム4つ追加
|
|
@@ -1934,7 +1958,7 @@ export function insertInsight(db, params) {
|
|
|
1934
1958
|
l2_core_thesis, l2_principles, l2_trigger_conditions, l2_resolution_steps, l2_applicable_context,
|
|
1935
1959
|
tags, scope, confidence, search_summary, l1_embedding,
|
|
1936
1960
|
supporting_episode_ids, supporting_claim_ids, evidence_refs,
|
|
1937
|
-
|
|
1961
|
+
validity_status,
|
|
1938
1962
|
source_tool, session_id, client_name, client_version, created_at, updated_at)
|
|
1939
1963
|
VALUES (@id, @title, @description,
|
|
1940
1964
|
@l1_content,
|
|
@@ -1979,8 +2003,10 @@ export function listInsights(db, params) {
|
|
|
1979
2003
|
const conditions = [];
|
|
1980
2004
|
const values = { limit: params.limit };
|
|
1981
2005
|
if (params.status) {
|
|
1982
|
-
conditions.push("t.
|
|
1983
|
-
|
|
2006
|
+
conditions.push("t.validity_status = @validity_status");
|
|
2007
|
+
conditions.push("t.is_archived = 0");
|
|
2008
|
+
conditions.push("t.promoted_to IS NULL");
|
|
2009
|
+
values.validity_status = params.status;
|
|
1984
2010
|
}
|
|
1985
2011
|
if (params.tag) {
|
|
1986
2012
|
conditions.push("t.tags LIKE @tag_like");
|
|
@@ -2029,8 +2055,10 @@ export function listInsights(db, params) {
|
|
|
2029
2055
|
const conditions = [];
|
|
2030
2056
|
const values = { limit: params.limit };
|
|
2031
2057
|
if (params.status) {
|
|
2032
|
-
conditions.push("
|
|
2033
|
-
|
|
2058
|
+
conditions.push("validity_status = @validity_status");
|
|
2059
|
+
conditions.push("is_archived = 0");
|
|
2060
|
+
conditions.push("promoted_to IS NULL");
|
|
2061
|
+
values.validity_status = params.status;
|
|
2034
2062
|
}
|
|
2035
2063
|
if (params.tag) {
|
|
2036
2064
|
conditions.push("tags LIKE @tag_like");
|
|
@@ -2055,13 +2083,13 @@ export function updateInsightL1Embedding(db, id, l1_embedding) {
|
|
|
2055
2083
|
}
|
|
2056
2084
|
/** l1_embedding が NULL の active Insight を取得(バックフィル用) */
|
|
2057
2085
|
export function getInsightsWithoutL1Embedding(db, limit) {
|
|
2058
|
-
return db.prepare("SELECT * FROM insights WHERE
|
|
2086
|
+
return db.prepare("SELECT * FROM insights WHERE validity_status = 'active' AND is_archived = 0 AND l1_embedding IS NULL ORDER BY updated_at DESC LIMIT ?").all(limit);
|
|
2059
2087
|
}
|
|
2060
2088
|
export function getInsightsWithL1Embedding(db, scope, limit = 100) {
|
|
2061
2089
|
if (scope) {
|
|
2062
|
-
return db.prepare("SELECT * FROM insights WHERE
|
|
2090
|
+
return db.prepare("SELECT * FROM insights WHERE validity_status = 'active' AND is_archived = 0 AND l1_embedding IS NOT NULL AND scope = ? ORDER BY updated_at DESC LIMIT ?").all(scope, limit);
|
|
2063
2091
|
}
|
|
2064
|
-
return db.prepare("SELECT * FROM insights WHERE
|
|
2092
|
+
return db.prepare("SELECT * FROM insights WHERE validity_status = 'active' AND is_archived = 0 AND l1_embedding IS NOT NULL ORDER BY updated_at DESC LIMIT ?").all(limit);
|
|
2065
2093
|
}
|
|
2066
2094
|
// === V12: Model CRUD (Issue #40) ===
|
|
2067
2095
|
// 2026-03-01 修正 (Issue #59): core_thesis → l2_core_thesis, principles → l2_principles + 新l2カラム + l1_content
|
|
@@ -2073,7 +2101,7 @@ export function insertModel(db, params) {
|
|
|
2073
2101
|
INSERT INTO models (id, title, description, l1_content,
|
|
2074
2102
|
l2_core_thesis, l2_principles, l2_trigger_conditions, l2_resolution_steps, l2_applicable_context,
|
|
2075
2103
|
non_goals, open_questions,
|
|
2076
|
-
supporting_episode_ids, supporting_claim_ids, evidence_refs, tags, scope, confidence,
|
|
2104
|
+
supporting_episode_ids, supporting_claim_ids, evidence_refs, tags, scope, confidence, validity_status, search_summary, l1_embedding,
|
|
2077
2105
|
source_tool, session_id, client_name, client_version, created_at, updated_at)
|
|
2078
2106
|
VALUES (@id, @title, @description, @l1_content,
|
|
2079
2107
|
@l2_core_thesis, @l2_principles, @l2_trigger_conditions, @l2_resolution_steps, @l2_applicable_context,
|
|
@@ -2118,8 +2146,10 @@ export function listModels(db, params) {
|
|
|
2118
2146
|
const conditions = [];
|
|
2119
2147
|
const values = { limit: params.limit };
|
|
2120
2148
|
if (params.status) {
|
|
2121
|
-
conditions.push("t.
|
|
2122
|
-
|
|
2149
|
+
conditions.push("t.validity_status = @validity_status");
|
|
2150
|
+
conditions.push("t.is_archived = 0");
|
|
2151
|
+
conditions.push("t.promoted_to IS NULL");
|
|
2152
|
+
values.validity_status = params.status;
|
|
2123
2153
|
}
|
|
2124
2154
|
if (params.tag) {
|
|
2125
2155
|
conditions.push("t.tags LIKE @tag_like");
|
|
@@ -2168,8 +2198,10 @@ export function listModels(db, params) {
|
|
|
2168
2198
|
const conditions = [];
|
|
2169
2199
|
const values = { limit: params.limit };
|
|
2170
2200
|
if (params.status) {
|
|
2171
|
-
conditions.push("
|
|
2172
|
-
|
|
2201
|
+
conditions.push("validity_status = @validity_status");
|
|
2202
|
+
conditions.push("is_archived = 0");
|
|
2203
|
+
conditions.push("promoted_to IS NULL");
|
|
2204
|
+
values.validity_status = params.status;
|
|
2173
2205
|
}
|
|
2174
2206
|
if (params.tag) {
|
|
2175
2207
|
conditions.push("tags LIKE @tag_like");
|
|
@@ -2192,22 +2224,55 @@ export function listModels(db, params) {
|
|
|
2192
2224
|
export function updateModelL1Embedding(db, id, l1_embedding) {
|
|
2193
2225
|
db.prepare("UPDATE models SET l1_embedding = @l1_embedding WHERE id = @id").run({ id, l1_embedding });
|
|
2194
2226
|
}
|
|
2227
|
+
/** Theory/Insight/Model の共通フィールドを更新 (Issue #3 user_issue) */
|
|
2228
|
+
export function updateKnowledge(db, kind, id, updates) {
|
|
2229
|
+
const table = kind === "theory" ? "theories" : kind === "insight" ? "insights" : "models";
|
|
2230
|
+
const getById = kind === "theory" ? getTheoryById : kind === "insight" ? getInsightById : getModelById;
|
|
2231
|
+
const existing = getById(db, id);
|
|
2232
|
+
if (!existing)
|
|
2233
|
+
return undefined;
|
|
2234
|
+
const now = new Date().toISOString();
|
|
2235
|
+
const setClauses = ["updated_at = @updated_at"];
|
|
2236
|
+
const values = { id, updated_at: now };
|
|
2237
|
+
const stringFields = ["title", "description", "l1_content", "l2_core_thesis", "l2_applicable_context", "scope", "search_summary", "validity_status"];
|
|
2238
|
+
for (const field of stringFields) {
|
|
2239
|
+
if (updates[field] !== undefined) {
|
|
2240
|
+
setClauses.push(`${field} = @${field}`);
|
|
2241
|
+
values[field] = updates[field];
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
if (updates.confidence !== undefined) {
|
|
2245
|
+
setClauses.push("confidence = @confidence");
|
|
2246
|
+
values.confidence = updates.confidence;
|
|
2247
|
+
}
|
|
2248
|
+
const arrayFields = ["l2_principles", "l2_trigger_conditions", "l2_resolution_steps", "supporting_episode_ids", "supporting_claim_ids", "evidence_refs", "tags", "non_goals", "open_questions"];
|
|
2249
|
+
for (const field of arrayFields) {
|
|
2250
|
+
if (updates[field] !== undefined) {
|
|
2251
|
+
setClauses.push(`${field} = @${field}`);
|
|
2252
|
+
values[field] = JSON.stringify(updates[field]);
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
if (setClauses.length <= 1)
|
|
2256
|
+
return existing; // updated_at のみ = 変更なし
|
|
2257
|
+
db.prepare(`UPDATE ${table} SET ${setClauses.join(", ")} WHERE id = @id`).run(values);
|
|
2258
|
+
return getById(db, id);
|
|
2259
|
+
}
|
|
2195
2260
|
/** l1_embedding が NULL の active Model を取得(バックフィル用) */
|
|
2196
2261
|
export function getModelsWithoutL1Embedding(db, limit) {
|
|
2197
|
-
return db.prepare("SELECT * FROM models WHERE
|
|
2262
|
+
return db.prepare("SELECT * FROM models WHERE validity_status = 'active' AND is_archived = 0 AND l1_embedding IS NULL ORDER BY updated_at DESC LIMIT ?").all(limit);
|
|
2198
2263
|
}
|
|
2199
2264
|
export function getModelsWithL1Embedding(db, scope, limit = 100) {
|
|
2200
2265
|
if (scope) {
|
|
2201
|
-
return db.prepare("SELECT * FROM models WHERE
|
|
2266
|
+
return db.prepare("SELECT * FROM models WHERE validity_status = 'active' AND is_archived = 0 AND l1_embedding IS NOT NULL AND scope = ? ORDER BY updated_at DESC LIMIT ?").all(scope, limit);
|
|
2202
2267
|
}
|
|
2203
|
-
return db.prepare("SELECT * FROM models WHERE
|
|
2268
|
+
return db.prepare("SELECT * FROM models WHERE validity_status = 'active' AND is_archived = 0 AND l1_embedding IS NOT NULL ORDER BY updated_at DESC LIMIT ?").all(limit);
|
|
2204
2269
|
}
|
|
2205
2270
|
// 2026-03-04 修正 (Issue #54): content → l1_content, user_input 追加
|
|
2206
2271
|
export function insertUserMemo(db, params) {
|
|
2207
2272
|
const id = ulid();
|
|
2208
2273
|
const now = new Date().toISOString();
|
|
2209
2274
|
db.prepare(`
|
|
2210
|
-
INSERT INTO user_memos (id, title, l1_content, usage_policy, tags, scope, search_summary, user_input, l1_embedding, source_tool, client_name, client_version,
|
|
2275
|
+
INSERT INTO user_memos (id, title, l1_content, usage_policy, tags, scope, search_summary, user_input, l1_embedding, source_tool, client_name, client_version, memo_status, created_at, updated_at)
|
|
2211
2276
|
VALUES (@id, @title, @l1_content, @usage_policy, @tags, @scope, @search_summary, @user_input, @l1_embedding, @source_tool, @client_name, @client_version, 'active', @created_at, @updated_at)
|
|
2212
2277
|
`).run({
|
|
2213
2278
|
id,
|
|
@@ -2261,9 +2326,13 @@ export function updateUserMemo(db, id, updates) {
|
|
|
2261
2326
|
setClauses.push("search_summary = @search_summary");
|
|
2262
2327
|
values.search_summary = updates.search_summary;
|
|
2263
2328
|
}
|
|
2264
|
-
if (updates.
|
|
2265
|
-
setClauses.push("
|
|
2266
|
-
values.
|
|
2329
|
+
if (updates.memo_status !== undefined) {
|
|
2330
|
+
setClauses.push("memo_status = @memo_status");
|
|
2331
|
+
values.memo_status = updates.memo_status;
|
|
2332
|
+
}
|
|
2333
|
+
if (updates.is_archived !== undefined) {
|
|
2334
|
+
setClauses.push("is_archived = @is_archived");
|
|
2335
|
+
values.is_archived = updates.is_archived;
|
|
2267
2336
|
}
|
|
2268
2337
|
if (setClauses.length === 0)
|
|
2269
2338
|
return existing;
|
|
@@ -2278,8 +2347,9 @@ export function listUserMemos(db, params) {
|
|
|
2278
2347
|
const conditions = [];
|
|
2279
2348
|
const values = { limit };
|
|
2280
2349
|
if (params.status) {
|
|
2281
|
-
conditions.push("m.
|
|
2282
|
-
|
|
2350
|
+
conditions.push("m.memo_status = @memo_status");
|
|
2351
|
+
conditions.push("m.is_archived = 0");
|
|
2352
|
+
values.memo_status = params.status;
|
|
2283
2353
|
}
|
|
2284
2354
|
if (params.usage_policy) {
|
|
2285
2355
|
conditions.push("m.usage_policy = @usage_policy");
|
|
@@ -2325,28 +2395,28 @@ export function listUserMemos(db, params) {
|
|
|
2325
2395
|
}
|
|
2326
2396
|
export function listAutoUserMemos(db, scope, limit = 5) {
|
|
2327
2397
|
if (scope) {
|
|
2328
|
-
return db.prepare("SELECT * FROM user_memos WHERE
|
|
2398
|
+
return db.prepare("SELECT * FROM user_memos WHERE memo_status = 'active' AND is_archived = 0 AND usage_policy = 'auto' AND (scope = ? OR scope = 'global') ORDER BY updated_at DESC LIMIT ?").all(scope, limit);
|
|
2329
2399
|
}
|
|
2330
|
-
return db.prepare("SELECT * FROM user_memos WHERE
|
|
2400
|
+
return db.prepare("SELECT * FROM user_memos WHERE memo_status = 'active' AND is_archived = 0 AND usage_policy = 'auto' ORDER BY updated_at DESC LIMIT ?").all(limit);
|
|
2331
2401
|
}
|
|
2332
2402
|
export function updateUserMemoL1Embedding(db, id, l1_embedding) {
|
|
2333
2403
|
db.prepare("UPDATE user_memos SET l1_embedding = @l1_embedding WHERE id = @id").run({ id, l1_embedding });
|
|
2334
2404
|
}
|
|
2335
2405
|
export function getUserMemosWithoutL1Embedding(db, limit) {
|
|
2336
|
-
return db.prepare("SELECT * FROM user_memos WHERE
|
|
2406
|
+
return db.prepare("SELECT * FROM user_memos WHERE memo_status = 'active' AND is_archived = 0 AND l1_embedding IS NULL ORDER BY updated_at DESC LIMIT ?").all(limit);
|
|
2337
2407
|
}
|
|
2338
2408
|
export function getUserMemosWithL1Embedding(db, scope, limit = 100) {
|
|
2339
2409
|
if (scope) {
|
|
2340
|
-
return db.prepare("SELECT * FROM user_memos WHERE
|
|
2410
|
+
return db.prepare("SELECT * FROM user_memos WHERE memo_status = 'active' AND is_archived = 0 AND l1_embedding IS NOT NULL AND (scope = ? OR scope = 'global') ORDER BY updated_at DESC LIMIT ?").all(scope, limit);
|
|
2341
2411
|
}
|
|
2342
|
-
return db.prepare("SELECT * FROM user_memos WHERE
|
|
2412
|
+
return db.prepare("SELECT * FROM user_memos WHERE memo_status = 'active' AND is_archived = 0 AND l1_embedding IS NOT NULL ORDER BY updated_at DESC LIMIT ?").all(limit);
|
|
2343
2413
|
}
|
|
2344
2414
|
// 2026-03-04 修正 (Issue #54): content → l1_content
|
|
2345
2415
|
export function insertUserPlan(db, params) {
|
|
2346
2416
|
const id = ulid();
|
|
2347
2417
|
const now = new Date().toISOString();
|
|
2348
2418
|
db.prepare(`
|
|
2349
|
-
INSERT INTO user_plans (id, title, l1_content, usage_policy, tags, scope, search_summary, user_input, l1_embedding, source_tool, client_name, client_version,
|
|
2419
|
+
INSERT INTO user_plans (id, title, l1_content, usage_policy, tags, scope, search_summary, user_input, l1_embedding, source_tool, client_name, client_version, plan_status, created_at, updated_at)
|
|
2350
2420
|
VALUES (@id, @title, @l1_content, @usage_policy, @tags, @scope, @search_summary, @user_input, @l1_embedding, @source_tool, @client_name, @client_version, 'active', @created_at, @updated_at)
|
|
2351
2421
|
`).run({
|
|
2352
2422
|
id,
|
|
@@ -2400,9 +2470,13 @@ export function updateUserPlan(db, id, updates) {
|
|
|
2400
2470
|
setClauses.push("search_summary = @search_summary");
|
|
2401
2471
|
values.search_summary = updates.search_summary;
|
|
2402
2472
|
}
|
|
2403
|
-
if (updates.
|
|
2404
|
-
setClauses.push("
|
|
2405
|
-
values.
|
|
2473
|
+
if (updates.plan_status !== undefined) {
|
|
2474
|
+
setClauses.push("plan_status = @plan_status");
|
|
2475
|
+
values.plan_status = updates.plan_status;
|
|
2476
|
+
}
|
|
2477
|
+
if (updates.is_archived !== undefined) {
|
|
2478
|
+
setClauses.push("is_archived = @is_archived");
|
|
2479
|
+
values.is_archived = updates.is_archived;
|
|
2406
2480
|
}
|
|
2407
2481
|
if (setClauses.length === 0)
|
|
2408
2482
|
return existing;
|
|
@@ -2414,11 +2488,11 @@ export function updateUserPlan(db, id, updates) {
|
|
|
2414
2488
|
export function listUserPlans(db, params) {
|
|
2415
2489
|
const sanitized = params.query ? sanitizeFtsQuery(params.query) : null;
|
|
2416
2490
|
const limit = params.limit ?? 20;
|
|
2417
|
-
const conditions = [];
|
|
2491
|
+
const conditions = ["m.is_archived = 0"];
|
|
2418
2492
|
const values = { limit };
|
|
2419
2493
|
if (params.status) {
|
|
2420
|
-
conditions.push("m.
|
|
2421
|
-
values.
|
|
2494
|
+
conditions.push("m.plan_status = @plan_status");
|
|
2495
|
+
values.plan_status = params.status;
|
|
2422
2496
|
}
|
|
2423
2497
|
if (params.usage_policy) {
|
|
2424
2498
|
conditions.push("m.usage_policy = @usage_policy");
|
|
@@ -2464,27 +2538,29 @@ export function listUserPlans(db, params) {
|
|
|
2464
2538
|
}
|
|
2465
2539
|
export function listAutoUserPlans(db, scope, limit = 10) {
|
|
2466
2540
|
if (scope) {
|
|
2467
|
-
return db.prepare("SELECT * FROM user_plans WHERE
|
|
2541
|
+
return db.prepare("SELECT * FROM user_plans WHERE plan_status = 'active' AND is_archived = 0 AND usage_policy = 'auto' AND (scope = ? OR scope = 'global') ORDER BY updated_at DESC LIMIT ?").all(scope, limit);
|
|
2468
2542
|
}
|
|
2469
|
-
return db.prepare("SELECT * FROM user_plans WHERE
|
|
2543
|
+
return db.prepare("SELECT * FROM user_plans WHERE plan_status = 'active' AND is_archived = 0 AND usage_policy = 'auto' ORDER BY updated_at DESC LIMIT ?").all(limit);
|
|
2470
2544
|
}
|
|
2471
2545
|
/**
|
|
2472
2546
|
* Hook 用: アクティブなプランの軽量情報を取得する。
|
|
2473
2547
|
* auto compact 後にLLMがプランを再認識できるよう、Hook の additionalContext に注入する。
|
|
2474
2548
|
* l1_content は先頭500文字に切り詰めてトークン消費を抑える。
|
|
2475
2549
|
*/
|
|
2476
|
-
|
|
2550
|
+
// 2026-03-25 修正: limit デフォルト 3→2、プレビュー 500→200 文字に短縮
|
|
2551
|
+
// 元の実装: limit=3, 500文字 → Hook の情報量が多いとのフィードバック
|
|
2552
|
+
export function getActivePlansForHook(db, scope, limit = 2) {
|
|
2477
2553
|
let rows;
|
|
2478
2554
|
if (scope) {
|
|
2479
|
-
rows = db.prepare("SELECT id, title, l1_content, updated_at FROM user_plans WHERE
|
|
2555
|
+
rows = db.prepare("SELECT id, title, l1_content, updated_at FROM user_plans WHERE plan_status = 'active' AND is_archived = 0 AND usage_policy = 'auto' AND (scope = ? OR scope = 'global') ORDER BY updated_at DESC LIMIT ?").all(scope, limit);
|
|
2480
2556
|
}
|
|
2481
2557
|
else {
|
|
2482
|
-
rows = db.prepare("SELECT id, title, l1_content, updated_at FROM user_plans WHERE
|
|
2558
|
+
rows = db.prepare("SELECT id, title, l1_content, updated_at FROM user_plans WHERE plan_status = 'active' AND is_archived = 0 AND usage_policy = 'auto' ORDER BY updated_at DESC LIMIT ?").all(limit);
|
|
2483
2559
|
}
|
|
2484
2560
|
return rows.map(r => ({
|
|
2485
2561
|
id: r.id,
|
|
2486
2562
|
title: r.title,
|
|
2487
|
-
l1_content_preview: r.l1_content.length >
|
|
2563
|
+
l1_content_preview: r.l1_content.length > 200 ? r.l1_content.slice(0, 200) + "…" : r.l1_content,
|
|
2488
2564
|
updated_at: r.updated_at,
|
|
2489
2565
|
}));
|
|
2490
2566
|
}
|
|
@@ -2492,13 +2568,13 @@ export function updateUserPlanL1Embedding(db, id, l1_embedding) {
|
|
|
2492
2568
|
db.prepare("UPDATE user_plans SET l1_embedding = @l1_embedding WHERE id = @id").run({ id, l1_embedding });
|
|
2493
2569
|
}
|
|
2494
2570
|
export function getUserPlansWithoutL1Embedding(db, limit) {
|
|
2495
|
-
return db.prepare("SELECT * FROM user_plans WHERE
|
|
2571
|
+
return db.prepare("SELECT * FROM user_plans WHERE plan_status = 'active' AND is_archived = 0 AND l1_embedding IS NULL ORDER BY updated_at DESC LIMIT ?").all(limit);
|
|
2496
2572
|
}
|
|
2497
2573
|
export function getUserPlansWithL1Embedding(db, scope, limit = 100) {
|
|
2498
2574
|
if (scope) {
|
|
2499
|
-
return db.prepare("SELECT * FROM user_plans WHERE
|
|
2575
|
+
return db.prepare("SELECT * FROM user_plans WHERE plan_status = 'active' AND is_archived = 0 AND l1_embedding IS NOT NULL AND (scope = ? OR scope = 'global') ORDER BY updated_at DESC LIMIT ?").all(scope, limit);
|
|
2500
2576
|
}
|
|
2501
|
-
return db.prepare("SELECT * FROM user_plans WHERE
|
|
2577
|
+
return db.prepare("SELECT * FROM user_plans WHERE plan_status = 'active' AND is_archived = 0 AND l1_embedding IS NOT NULL ORDER BY updated_at DESC LIMIT ?").all(limit);
|
|
2502
2578
|
}
|
|
2503
2579
|
// === Issue #57: UserIssue CRUD ===
|
|
2504
2580
|
// 2026-03-04 修正 (Issue #54): content → l1_content
|
|
@@ -2506,7 +2582,7 @@ export function insertUserIssue(db, params) {
|
|
|
2506
2582
|
const id = ulid();
|
|
2507
2583
|
const now = new Date().toISOString();
|
|
2508
2584
|
db.prepare(`
|
|
2509
|
-
INSERT INTO user_issues (id, title, l1_content, entries, kind, priority, usage_policy, tags, scope, search_summary, user_input, l1_embedding, source_tool, client_name, client_version,
|
|
2585
|
+
INSERT INTO user_issues (id, title, l1_content, entries, kind, priority, usage_policy, tags, scope, search_summary, user_input, l1_embedding, source_tool, client_name, client_version, issue_status, created_at, updated_at)
|
|
2510
2586
|
VALUES (@id, @title, @l1_content, @entries, @kind, @priority, @usage_policy, @tags, @scope, @search_summary, @user_input, @l1_embedding, @source_tool, @client_name, @client_version, 'open', @created_at, @updated_at)
|
|
2511
2587
|
`).run({
|
|
2512
2588
|
id,
|
|
@@ -2551,6 +2627,10 @@ export function updateUserIssue(db, id, updates) {
|
|
|
2551
2627
|
setClauses.push("entries = @entries");
|
|
2552
2628
|
values.entries = updates.entries;
|
|
2553
2629
|
}
|
|
2630
|
+
if (updates.l1_embedding !== undefined) {
|
|
2631
|
+
setClauses.push("l1_embedding = @l1_embedding");
|
|
2632
|
+
values.l1_embedding = updates.l1_embedding;
|
|
2633
|
+
}
|
|
2554
2634
|
if (updates.priority !== undefined) {
|
|
2555
2635
|
setClauses.push("priority = @priority");
|
|
2556
2636
|
values.priority = updates.priority;
|
|
@@ -2571,9 +2651,13 @@ export function updateUserIssue(db, id, updates) {
|
|
|
2571
2651
|
setClauses.push("search_summary = @search_summary");
|
|
2572
2652
|
values.search_summary = updates.search_summary;
|
|
2573
2653
|
}
|
|
2574
|
-
if (updates.
|
|
2575
|
-
setClauses.push("
|
|
2576
|
-
values.
|
|
2654
|
+
if (updates.issue_status !== undefined) {
|
|
2655
|
+
setClauses.push("issue_status = @issue_status");
|
|
2656
|
+
values.issue_status = updates.issue_status;
|
|
2657
|
+
}
|
|
2658
|
+
if (updates.is_archived !== undefined) {
|
|
2659
|
+
setClauses.push("is_archived = @is_archived");
|
|
2660
|
+
values.is_archived = updates.is_archived;
|
|
2577
2661
|
}
|
|
2578
2662
|
if (setClauses.length === 0)
|
|
2579
2663
|
return existing;
|
|
@@ -2585,11 +2669,11 @@ export function updateUserIssue(db, id, updates) {
|
|
|
2585
2669
|
export function listUserIssues(db, params) {
|
|
2586
2670
|
const sanitized = params.query ? sanitizeFtsQuery(params.query) : null;
|
|
2587
2671
|
const limit = params.limit ?? 20;
|
|
2588
|
-
const conditions = [];
|
|
2672
|
+
const conditions = ["m.is_archived = 0"];
|
|
2589
2673
|
const values = { limit };
|
|
2590
2674
|
if (params.status) {
|
|
2591
|
-
conditions.push("m.
|
|
2592
|
-
values.
|
|
2675
|
+
conditions.push("m.issue_status = @issue_status");
|
|
2676
|
+
values.issue_status = params.status;
|
|
2593
2677
|
}
|
|
2594
2678
|
if (params.priority) {
|
|
2595
2679
|
conditions.push("m.priority = @priority");
|
|
@@ -2641,13 +2725,168 @@ export function updateUserIssueL1Embedding(db, id, l1_embedding) {
|
|
|
2641
2725
|
db.prepare("UPDATE user_issues SET l1_embedding = @l1_embedding WHERE id = @id").run({ id, l1_embedding });
|
|
2642
2726
|
}
|
|
2643
2727
|
export function getUserIssuesWithoutL1Embedding(db, limit) {
|
|
2644
|
-
return db.prepare("SELECT * FROM user_issues WHERE
|
|
2728
|
+
return db.prepare("SELECT * FROM user_issues WHERE issue_status = 'open' AND is_archived = 0 AND l1_embedding IS NULL ORDER BY updated_at DESC LIMIT ?").all(limit);
|
|
2645
2729
|
}
|
|
2646
2730
|
export function getUserIssuesWithL1Embedding(db, scope, limit = 100) {
|
|
2647
2731
|
if (scope) {
|
|
2648
|
-
return db.prepare("SELECT * FROM user_issues WHERE
|
|
2732
|
+
return db.prepare("SELECT * FROM user_issues WHERE issue_status = 'open' AND is_archived = 0 AND l1_embedding IS NOT NULL AND (scope = ? OR scope = 'global') ORDER BY updated_at DESC LIMIT ?").all(scope, limit);
|
|
2649
2733
|
}
|
|
2650
|
-
return db.prepare("SELECT * FROM user_issues WHERE
|
|
2734
|
+
return db.prepare("SELECT * FROM user_issues WHERE issue_status = 'open' AND is_archived = 0 AND l1_embedding IS NOT NULL ORDER BY updated_at DESC LIMIT ?").all(limit);
|
|
2735
|
+
}
|
|
2736
|
+
export function insertUserTopic(db, params) {
|
|
2737
|
+
const id = ulid();
|
|
2738
|
+
const now = new Date().toISOString();
|
|
2739
|
+
// device_id を自動取得(store_meta から)
|
|
2740
|
+
let deviceId = params.device_id ?? null;
|
|
2741
|
+
if (!deviceId) {
|
|
2742
|
+
const row = db.prepare("SELECT value FROM store_meta WHERE key = 'device_id'").get();
|
|
2743
|
+
deviceId = row?.value ?? null;
|
|
2744
|
+
}
|
|
2745
|
+
db.prepare(`
|
|
2746
|
+
INSERT INTO user_topics (id, title, summary, device_id, cwd, conversation_id, priority, tags, scope, search_summary, l1_embedding, source_tool, client_name, client_version, topic_status, created_at, updated_at)
|
|
2747
|
+
VALUES (@id, @title, @summary, @device_id, @cwd, @conversation_id, @priority, @tags, @scope, @search_summary, @l1_embedding, @source_tool, @client_name, @client_version, 'active', @created_at, @updated_at)
|
|
2748
|
+
`).run({
|
|
2749
|
+
id,
|
|
2750
|
+
title: params.title,
|
|
2751
|
+
summary: params.summary,
|
|
2752
|
+
device_id: deviceId,
|
|
2753
|
+
cwd: params.cwd ?? null,
|
|
2754
|
+
conversation_id: params.conversation_id ?? null,
|
|
2755
|
+
priority: params.priority ?? "medium",
|
|
2756
|
+
tags: JSON.stringify(params.tags),
|
|
2757
|
+
scope: params.scope ?? "global",
|
|
2758
|
+
search_summary: params.search_summary ?? null,
|
|
2759
|
+
l1_embedding: params.l1_embedding ?? null,
|
|
2760
|
+
source_tool: params.source_tool ?? null,
|
|
2761
|
+
client_name: params.client_name ?? null,
|
|
2762
|
+
client_version: params.client_version ?? null,
|
|
2763
|
+
created_at: now,
|
|
2764
|
+
updated_at: now,
|
|
2765
|
+
});
|
|
2766
|
+
return db.prepare("SELECT * FROM user_topics WHERE id = ?").get(id);
|
|
2767
|
+
}
|
|
2768
|
+
export function getUserTopicById(db, id) {
|
|
2769
|
+
return db.prepare("SELECT * FROM user_topics WHERE id = ?").get(id);
|
|
2770
|
+
}
|
|
2771
|
+
export function updateUserTopic(db, id, updates) {
|
|
2772
|
+
const existing = getUserTopicById(db, id);
|
|
2773
|
+
if (!existing)
|
|
2774
|
+
return undefined;
|
|
2775
|
+
const setClauses = [];
|
|
2776
|
+
const values = { id };
|
|
2777
|
+
if (updates.title !== undefined) {
|
|
2778
|
+
setClauses.push("title = @title");
|
|
2779
|
+
values.title = updates.title;
|
|
2780
|
+
}
|
|
2781
|
+
if (updates.summary !== undefined) {
|
|
2782
|
+
setClauses.push("summary = @summary");
|
|
2783
|
+
values.summary = updates.summary;
|
|
2784
|
+
}
|
|
2785
|
+
if (updates.conversation_id !== undefined) {
|
|
2786
|
+
setClauses.push("conversation_id = @conversation_id");
|
|
2787
|
+
values.conversation_id = updates.conversation_id;
|
|
2788
|
+
}
|
|
2789
|
+
if (updates.cwd !== undefined) {
|
|
2790
|
+
setClauses.push("cwd = @cwd");
|
|
2791
|
+
values.cwd = updates.cwd;
|
|
2792
|
+
}
|
|
2793
|
+
if (updates.priority !== undefined) {
|
|
2794
|
+
setClauses.push("priority = @priority");
|
|
2795
|
+
values.priority = updates.priority;
|
|
2796
|
+
}
|
|
2797
|
+
if (updates.tags !== undefined) {
|
|
2798
|
+
setClauses.push("tags = @tags");
|
|
2799
|
+
values.tags = JSON.stringify(updates.tags);
|
|
2800
|
+
}
|
|
2801
|
+
if (updates.scope !== undefined) {
|
|
2802
|
+
setClauses.push("scope = @scope");
|
|
2803
|
+
values.scope = updates.scope;
|
|
2804
|
+
}
|
|
2805
|
+
if (updates.search_summary !== undefined) {
|
|
2806
|
+
setClauses.push("search_summary = @search_summary");
|
|
2807
|
+
values.search_summary = updates.search_summary;
|
|
2808
|
+
}
|
|
2809
|
+
if (updates.topic_status !== undefined) {
|
|
2810
|
+
setClauses.push("topic_status = @topic_status");
|
|
2811
|
+
values.topic_status = updates.topic_status;
|
|
2812
|
+
}
|
|
2813
|
+
if (updates.is_archived !== undefined) {
|
|
2814
|
+
setClauses.push("is_archived = @is_archived");
|
|
2815
|
+
values.is_archived = updates.is_archived;
|
|
2816
|
+
}
|
|
2817
|
+
if (setClauses.length === 0)
|
|
2818
|
+
return existing;
|
|
2819
|
+
// summary/title 変更時は l1_embedding を無効化(陳腐化防止、backfill_embeddings で再生成)
|
|
2820
|
+
if (updates.summary !== undefined || updates.title !== undefined) {
|
|
2821
|
+
setClauses.push("l1_embedding = NULL");
|
|
2822
|
+
}
|
|
2823
|
+
setClauses.push("updated_at = @updated_at");
|
|
2824
|
+
values.updated_at = new Date().toISOString();
|
|
2825
|
+
db.prepare(`UPDATE user_topics SET ${setClauses.join(", ")} WHERE id = @id`).run(values);
|
|
2826
|
+
return db.prepare("SELECT * FROM user_topics WHERE id = ?").get(id);
|
|
2827
|
+
}
|
|
2828
|
+
export function listUserTopics(db, params) {
|
|
2829
|
+
const sanitized = params.query ? sanitizeFtsQuery(params.query) : null;
|
|
2830
|
+
const limit = params.limit ?? 20;
|
|
2831
|
+
const conditions = ["m.is_archived = 0"];
|
|
2832
|
+
const values = { limit };
|
|
2833
|
+
if (params.status) {
|
|
2834
|
+
conditions.push("m.topic_status = @topic_status");
|
|
2835
|
+
values.topic_status = params.status;
|
|
2836
|
+
}
|
|
2837
|
+
if (params.priority) {
|
|
2838
|
+
conditions.push("m.priority = @priority");
|
|
2839
|
+
values.priority = params.priority;
|
|
2840
|
+
}
|
|
2841
|
+
if (params.tag) {
|
|
2842
|
+
conditions.push("m.tags LIKE @tag_like");
|
|
2843
|
+
values.tag_like = `%"${params.tag}"%`;
|
|
2844
|
+
}
|
|
2845
|
+
if (params.scope) {
|
|
2846
|
+
conditions.push("(m.scope = @scope OR m.scope = 'global')");
|
|
2847
|
+
values.scope = params.scope;
|
|
2848
|
+
}
|
|
2849
|
+
// FTS5検索
|
|
2850
|
+
if (sanitized) {
|
|
2851
|
+
try {
|
|
2852
|
+
const ftsConditions = [...conditions];
|
|
2853
|
+
const ftsWhere = ftsConditions.length > 0 ? "AND " + ftsConditions.join(" AND ") : "";
|
|
2854
|
+
const ftsResults = db.prepare(`
|
|
2855
|
+
SELECT m.* FROM user_topics m
|
|
2856
|
+
JOIN user_topics_fts f ON m.rowid = f.rowid
|
|
2857
|
+
WHERE user_topics_fts MATCH @query ${ftsWhere}
|
|
2858
|
+
ORDER BY rank
|
|
2859
|
+
LIMIT @limit
|
|
2860
|
+
`).all({ ...values, query: sanitized });
|
|
2861
|
+
if (ftsResults.length > 0)
|
|
2862
|
+
return ftsResults;
|
|
2863
|
+
}
|
|
2864
|
+
catch { /* FTSエラー時はLIKEフォールバック */ }
|
|
2865
|
+
}
|
|
2866
|
+
// LIKEフォールバック or 全件取得
|
|
2867
|
+
if (params.query) {
|
|
2868
|
+
const likeParam = `%${params.query.slice(0, 100)}%`;
|
|
2869
|
+
conditions.push("(m.title LIKE @like_query OR m.summary LIKE @like_query OR m.search_summary LIKE @like_query)");
|
|
2870
|
+
values.like_query = likeParam;
|
|
2871
|
+
}
|
|
2872
|
+
const whereClause = conditions.length > 0 ? "WHERE " + conditions.join(" AND ") : "";
|
|
2873
|
+
return db.prepare(`
|
|
2874
|
+
SELECT m.* FROM user_topics m ${whereClause}
|
|
2875
|
+
ORDER BY m.updated_at DESC
|
|
2876
|
+
LIMIT @limit
|
|
2877
|
+
`).all(values);
|
|
2878
|
+
}
|
|
2879
|
+
export function updateUserTopicL1Embedding(db, id, l1_embedding) {
|
|
2880
|
+
db.prepare("UPDATE user_topics SET l1_embedding = @l1_embedding WHERE id = @id").run({ id, l1_embedding });
|
|
2881
|
+
}
|
|
2882
|
+
export function getUserTopicsWithoutL1Embedding(db, limit) {
|
|
2883
|
+
return db.prepare("SELECT * FROM user_topics WHERE topic_status = 'active' AND is_archived = 0 AND l1_embedding IS NULL ORDER BY updated_at DESC LIMIT ?").all(limit);
|
|
2884
|
+
}
|
|
2885
|
+
export function getUserTopicsWithL1Embedding(db, scope, limit = 100) {
|
|
2886
|
+
if (scope) {
|
|
2887
|
+
return db.prepare("SELECT * FROM user_topics WHERE topic_status = 'active' AND is_archived = 0 AND l1_embedding IS NOT NULL AND (scope = ? OR scope = 'global') ORDER BY updated_at DESC LIMIT ?").all(scope, limit);
|
|
2888
|
+
}
|
|
2889
|
+
return db.prepare("SELECT * FROM user_topics WHERE topic_status = 'active' AND is_archived = 0 AND l1_embedding IS NOT NULL ORDER BY updated_at DESC LIMIT ?").all(limit);
|
|
2651
2890
|
}
|
|
2652
2891
|
/**
|
|
2653
2892
|
* 横断検索(FTS5 + LIKEフォールバック)
|
|
@@ -2662,11 +2901,13 @@ export function unifiedSearch(db, query, scope, entityTypes, limit) {
|
|
|
2662
2901
|
scopeFilter = "AND (u.scope = @scope OR u.scope = 'global')";
|
|
2663
2902
|
baseValues.scope = scope;
|
|
2664
2903
|
}
|
|
2904
|
+
// entity_types 未指定時はデフォルト検索対象を使用
|
|
2905
|
+
const effectiveTypes = (entityTypes && entityTypes.length > 0) ? entityTypes : [...DEFAULT_SEARCH_ENTITY_TYPES];
|
|
2665
2906
|
let typeFilter = "";
|
|
2666
|
-
|
|
2667
|
-
const placeholders =
|
|
2907
|
+
{
|
|
2908
|
+
const placeholders = effectiveTypes.map((_, i) => `@et${i}`).join(", ");
|
|
2668
2909
|
typeFilter = `AND u.entity_type IN (${placeholders})`;
|
|
2669
|
-
|
|
2910
|
+
effectiveTypes.forEach((t, i) => { baseValues[`et${i}`] = t; });
|
|
2670
2911
|
}
|
|
2671
2912
|
// 1. FTS5検索
|
|
2672
2913
|
if (sanitized) {
|
|
@@ -2727,10 +2968,12 @@ export function getUnifiedSearchWithL1Embedding(db, scope, entityTypes, limit) {
|
|
|
2727
2968
|
conditions.push("(scope = @scope OR scope = 'global')");
|
|
2728
2969
|
values.scope = scope;
|
|
2729
2970
|
}
|
|
2730
|
-
|
|
2731
|
-
|
|
2971
|
+
// entity_types 未指定時はデフォルト検索対象を使用
|
|
2972
|
+
const effectiveTypes = (entityTypes && entityTypes.length > 0) ? entityTypes : [...DEFAULT_SEARCH_ENTITY_TYPES];
|
|
2973
|
+
{
|
|
2974
|
+
const placeholders = effectiveTypes.map((_, i) => `@et${i}`).join(", ");
|
|
2732
2975
|
conditions.push(`entity_type IN (${placeholders})`);
|
|
2733
|
-
|
|
2976
|
+
effectiveTypes.forEach((t, i) => { values[`et${i}`] = t; });
|
|
2734
2977
|
}
|
|
2735
2978
|
const whereClause = conditions.length > 0
|
|
2736
2979
|
? "WHERE " + conditions.join(" AND ")
|
|
@@ -2813,7 +3056,7 @@ export function rebuildUnifiedSearch(db) {
|
|
|
2813
3056
|
INSERT OR REPLACE INTO unified_search_items(${columns})
|
|
2814
3057
|
SELECT '${e.type}', id, scope, ${e.categoryExpr}, ${e.titleExpr}, ${e.searchExpr}, ${e.searchSummary}, ${e.tagsExpr}, created_at, updated_at
|
|
2815
3058
|
FROM ${e.table}
|
|
2816
|
-
WHERE
|
|
3059
|
+
WHERE validity_status = 'active' AND is_archived = 0 AND promoted_to IS NULL
|
|
2817
3060
|
`);
|
|
2818
3061
|
}
|
|
2819
3062
|
db.exec("INSERT INTO unified_search_fts(unified_search_fts) VALUES('rebuild')");
|
|
@@ -2834,6 +3077,8 @@ const ENTITY_TYPE_TO_TABLE = {
|
|
|
2834
3077
|
user_memo: "user_memos",
|
|
2835
3078
|
user_plan: "user_plans",
|
|
2836
3079
|
user_issue: "user_issues",
|
|
3080
|
+
user_topic: "user_topics",
|
|
3081
|
+
user_file: "user_files",
|
|
2837
3082
|
};
|
|
2838
3083
|
/**
|
|
2839
3084
|
* entity_id から entity_type を解決する(unified_search_items を使用)。
|
|
@@ -2982,4 +3227,119 @@ export function applyTombstones(db) {
|
|
|
2982
3227
|
}
|
|
2983
3228
|
return deleted;
|
|
2984
3229
|
}
|
|
3230
|
+
// ============================================================
|
|
3231
|
+
// UserFile (File Vault)
|
|
3232
|
+
// ============================================================
|
|
3233
|
+
export function insertUserFile(db, params) {
|
|
3234
|
+
const id = ulid();
|
|
3235
|
+
const now = new Date().toISOString();
|
|
3236
|
+
// device_id を自動取得(store_meta から)
|
|
3237
|
+
let deviceId = params.device_id ?? null;
|
|
3238
|
+
if (!deviceId) {
|
|
3239
|
+
const row = db.prepare("SELECT value FROM store_meta WHERE key = 'device_id'").get();
|
|
3240
|
+
deviceId = row?.value ?? null;
|
|
3241
|
+
}
|
|
3242
|
+
db.prepare(`
|
|
3243
|
+
INSERT INTO user_files (id, title, description, device_id, original_filename, original_encoding, file_data, file_hash, file_size, tags, scope, search_summary, l1_embedding, source_tool, client_name, client_version, file_status, created_at, updated_at)
|
|
3244
|
+
VALUES (@id, @title, @description, @device_id, @original_filename, @original_encoding, @file_data, @file_hash, @file_size, @tags, @scope, @search_summary, @l1_embedding, @source_tool, @client_name, @client_version, 'active', @created_at, @updated_at)
|
|
3245
|
+
`).run({
|
|
3246
|
+
id,
|
|
3247
|
+
title: params.title,
|
|
3248
|
+
description: params.description ?? null,
|
|
3249
|
+
device_id: deviceId,
|
|
3250
|
+
original_filename: params.original_filename,
|
|
3251
|
+
original_encoding: params.original_encoding ?? "utf-8",
|
|
3252
|
+
file_data: params.file_data,
|
|
3253
|
+
file_hash: params.file_hash,
|
|
3254
|
+
file_size: params.file_size,
|
|
3255
|
+
tags: JSON.stringify(params.tags),
|
|
3256
|
+
scope: params.scope ?? "global",
|
|
3257
|
+
search_summary: params.search_summary ?? null,
|
|
3258
|
+
l1_embedding: params.l1_embedding ?? null,
|
|
3259
|
+
source_tool: params.source_tool ?? null,
|
|
3260
|
+
client_name: params.client_name ?? null,
|
|
3261
|
+
client_version: params.client_version ?? null,
|
|
3262
|
+
created_at: now,
|
|
3263
|
+
updated_at: now,
|
|
3264
|
+
});
|
|
3265
|
+
return db.prepare("SELECT * FROM user_files WHERE id = ?").get(id);
|
|
3266
|
+
}
|
|
3267
|
+
export function getUserFileById(db, id) {
|
|
3268
|
+
return db.prepare("SELECT * FROM user_files WHERE id = ?").get(id);
|
|
3269
|
+
}
|
|
3270
|
+
export function getUserFileByTitle(db, title) {
|
|
3271
|
+
// 完全一致を優先、なければ部分一致
|
|
3272
|
+
const exact = db.prepare("SELECT * FROM user_files WHERE title = ? AND file_status = 'active' AND is_archived = 0").get(title);
|
|
3273
|
+
if (exact)
|
|
3274
|
+
return exact;
|
|
3275
|
+
return db.prepare("SELECT * FROM user_files WHERE title LIKE ? AND file_status = 'active' AND is_archived = 0 ORDER BY updated_at DESC LIMIT 1").get(`%${title}%`);
|
|
3276
|
+
}
|
|
3277
|
+
export function updateUserFile(db, id, updates) {
|
|
3278
|
+
const setClauses = [];
|
|
3279
|
+
const params = { id };
|
|
3280
|
+
if (updates.title !== undefined) {
|
|
3281
|
+
setClauses.push("title = @title");
|
|
3282
|
+
params.title = updates.title;
|
|
3283
|
+
}
|
|
3284
|
+
if (updates.description !== undefined) {
|
|
3285
|
+
setClauses.push("description = @description");
|
|
3286
|
+
params.description = updates.description;
|
|
3287
|
+
}
|
|
3288
|
+
if (updates.file_data !== undefined) {
|
|
3289
|
+
setClauses.push("file_data = @file_data");
|
|
3290
|
+
params.file_data = updates.file_data;
|
|
3291
|
+
}
|
|
3292
|
+
if (updates.file_hash !== undefined) {
|
|
3293
|
+
setClauses.push("file_hash = @file_hash");
|
|
3294
|
+
params.file_hash = updates.file_hash;
|
|
3295
|
+
}
|
|
3296
|
+
if (updates.file_size !== undefined) {
|
|
3297
|
+
setClauses.push("file_size = @file_size");
|
|
3298
|
+
params.file_size = updates.file_size;
|
|
3299
|
+
}
|
|
3300
|
+
if (updates.tags !== undefined) {
|
|
3301
|
+
setClauses.push("tags = @tags");
|
|
3302
|
+
params.tags = JSON.stringify(updates.tags);
|
|
3303
|
+
}
|
|
3304
|
+
if (updates.scope !== undefined) {
|
|
3305
|
+
setClauses.push("scope = @scope");
|
|
3306
|
+
params.scope = updates.scope;
|
|
3307
|
+
}
|
|
3308
|
+
if (updates.search_summary !== undefined) {
|
|
3309
|
+
setClauses.push("search_summary = @search_summary");
|
|
3310
|
+
params.search_summary = updates.search_summary;
|
|
3311
|
+
}
|
|
3312
|
+
if (updates.file_status !== undefined) {
|
|
3313
|
+
setClauses.push("file_status = @file_status");
|
|
3314
|
+
params.file_status = updates.file_status;
|
|
3315
|
+
}
|
|
3316
|
+
if (updates.is_archived !== undefined) {
|
|
3317
|
+
setClauses.push("is_archived = @is_archived");
|
|
3318
|
+
params.is_archived = updates.is_archived;
|
|
3319
|
+
}
|
|
3320
|
+
if (setClauses.length === 0)
|
|
3321
|
+
return getUserFileById(db, id);
|
|
3322
|
+
setClauses.push("updated_at = @updated_at");
|
|
3323
|
+
params.updated_at = new Date().toISOString();
|
|
3324
|
+
db.prepare(`UPDATE user_files SET ${setClauses.join(", ")} WHERE id = @id`).run(params);
|
|
3325
|
+
return getUserFileById(db, id);
|
|
3326
|
+
}
|
|
3327
|
+
export function listUserFiles(db, params) {
|
|
3328
|
+
const conditions = ["file_status = @file_status", "is_archived = @is_archived"];
|
|
3329
|
+
const bindParams = { file_status: params.file_status ?? "active", is_archived: params.is_archived ?? 0 };
|
|
3330
|
+
if (params.scope) {
|
|
3331
|
+
conditions.push("(scope = @scope OR scope = 'global')");
|
|
3332
|
+
bindParams.scope = params.scope;
|
|
3333
|
+
}
|
|
3334
|
+
const sql = `SELECT id, title, description, original_filename, original_encoding, file_hash, file_size, tags, scope, search_summary, file_status, is_archived, client_name, client_version, source_tool, created_at, updated_at FROM user_files WHERE ${conditions.join(" AND ")} ORDER BY updated_at DESC`;
|
|
3335
|
+
let rows = db.prepare(sql).all(bindParams);
|
|
3336
|
+
// タグフィルタリング(アプリケーション層)
|
|
3337
|
+
if (params.tags && params.tags.length > 0) {
|
|
3338
|
+
rows = rows.filter(r => {
|
|
3339
|
+
const rowTags = JSON.parse(r.tags);
|
|
3340
|
+
return params.tags.some(t => rowTags.includes(t));
|
|
3341
|
+
});
|
|
3342
|
+
}
|
|
3343
|
+
return rows;
|
|
3344
|
+
}
|
|
2985
3345
|
//# sourceMappingURL=queries-core.js.map
|