@team-semicolon/semicolony-cli 4.18.51
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/LICENSE +200 -0
- package/NOTICE +8 -0
- package/README.md +152 -0
- package/dist/bundle.js +2887 -0
- package/dist/templates/launchd/ai.semicolony.host-signals-sidecar.plist +40 -0
- package/migrations/001_initial.sql +280 -0
- package/migrations/002_add_indexes.sql +11 -0
- package/migrations/003_add_fk.sql +32 -0
- package/migrations/004_session_count_trigger.sql +35 -0
- package/migrations/005_bot_workspace_audits.sql +16 -0
- package/migrations/006_bot_kb_subscriptions.sql +24 -0
- package/migrations/007_flatten_skill_names.sql +16 -0
- package/migrations/008_bot_workspace_files.sql +14 -0
- package/migrations/009_enforce_flat_skill_names.sql +53 -0
- package/migrations/010_bot_delegation.sql +27 -0
- package/migrations/011_drop_legacy_tables.sql +35 -0
- package/migrations/012_drop_bot_knowledge.sql +16 -0
- package/migrations/013_kb_workspace_domains.sql +88 -0
- package/migrations/014_timestamp_to_timestamptz.sql +33 -0
- package/migrations/015_kb_domain_enforcement.sql +83 -0
- package/migrations/016_ontology_redesign.sql +108 -0
- package/migrations/017_ontology_instance_model.sql +151 -0
- package/migrations/018_type_schema.sql +11 -0
- package/migrations/019_test_management.sql +69 -0
- package/migrations/020_bot_workspace_standard.sql +174 -0
- package/migrations/021_drop_dead_tables.sql +8 -0
- package/migrations/021_test_declarative.sql +19 -0
- package/migrations/022_bot_ontology_type.sql +38 -0
- package/migrations/022_kb_schema_enforcement.sql +77 -0
- package/migrations/023_decision_key_cleanup.ts +154 -0
- package/migrations/024_embedding_backfill.ts +137 -0
- package/migrations/025_kb_flat_keys.sql +33 -0
- package/migrations/026_drop_session_log.sql +19 -0
- package/migrations/027_kb_manager_skill.sql +22 -0
- package/migrations/028_skill_definitions_to_semicolony.sql +19 -0
- package/migrations/029_agents_citation_max_lines.sql +6 -0
- package/migrations/030_team_ontology.sql +54 -0
- package/migrations/031_gfp_tables.sql +101 -0
- package/migrations/031_kb_manager_domain_register.sql +12 -0
- package/migrations/032_gfp_skill_and_callback.sql +40 -0
- package/migrations/032_kebab_case_keys.sql +33 -0
- package/migrations/033_skill_workspace_rules.sql +10 -0
- package/migrations/034_current_to_date_keys.sql +13 -0
- package/migrations/034_current_to_date_keys.ts +104 -0
- package/migrations/035_bot_budgets.sql +51 -0
- package/migrations/036_gfp_stitch_design.sql +35 -0
- package/migrations/037_gfp_qa_sections.sql +13 -0
- package/migrations/038_gfp_phase_split.sql +26 -0
- package/migrations/039_meeting_audio_data.sql +2 -0
- package/migrations/040_bot_commitments.sql +68 -0
- package/migrations/041_palette_previews.sql +13 -0
- package/migrations/042_gfp_parallel_tracks.sql +37 -0
- package/migrations/043_blocker_key.sql +13 -0
- package/migrations/044_gfp_stitch_screenshots.sql +9 -0
- package/migrations/045_service_table_rename_and_projection.sql +150 -0
- package/migrations/046_service_ops_tables.sql +75 -0
- package/migrations/047_update_gfp_manager_skill.sql +16 -0
- package/migrations/048_service_migration_skill.sql +23 -0
- package/migrations/049_service_features.sql +39 -0
- package/migrations/050_service_kpi_action_items.sql +87 -0
- package/migrations/051_update_service_migration_skill_prompt.sql +9 -0
- package/migrations/052_feature_iteration_link.sql +11 -0
- package/migrations/052_services_table_rename.sql +149 -0
- package/migrations/053_feature_discovery.sql +47 -0
- package/migrations/054_deploy_verification.sql +17 -0
- package/migrations/055_feature_lifecycle.sql +37 -0
- package/migrations/056_service_incident_schema.sql +16 -0
- package/migrations/057_decision_date_to_metadata.ts +173 -0
- package/migrations/058_incubator_kb_schema.sql +8 -0
- package/migrations/059_incubator_sessions.sql +29 -0
- package/migrations/060_skill_reference_files.sql +8 -0
- package/migrations/061_cron_trigger_bridge.sql +7 -0
- package/migrations/062_bot_slack_profiles.sql +21 -0
- package/migrations/063_rename_gfp_skills.sql +7 -0
- package/migrations/064_bot_cost_log.sql +11 -0
- package/migrations/065_hub_spoke_orchestration.sql +35 -0
- package/migrations/066_cost_log_commitment.sql +9 -0
- package/migrations/067_module_ontology_type.sql +44 -0
- package/migrations/068_migrate_incubator_data.sql +32 -0
- package/migrations/069_action_items_sot.sql +156 -0
- package/migrations/069_cost_log_tokens.sql +45 -0
- package/migrations/070_services_sot_columns.sql +78 -0
- package/migrations/071_cleanup_deprecated_service_kb.sql +38 -0
- package/migrations/072_incubator_heartbeat.sql +12 -0
- package/migrations/072_service_role_schema.sql +57 -0
- package/migrations/073_service_type_and_platform.sql +8 -0
- package/migrations/074_schema_ref_type.sql +16 -0
- package/migrations/075_routing_delegation.sql +34 -0
- package/migrations/076_meeting_service_link.sql +5 -0
- package/migrations/077_agents_ontology_type.sql +93 -0
- package/migrations/078_incubator_agent.sql +45 -0
- package/migrations/079_drop_audio_blob_url.sql +4 -0
- package/migrations/080_kb_metadata_indexing.sql +9 -0
- package/migrations/081_person_unification.sql +41 -0
- package/migrations/082_discord_channel.sql +3 -0
- package/migrations/084_deprecate_kpi_metrics_table.sql +5 -0
- package/migrations/085_meeting_target_domain.sql +37 -0
- package/migrations/086_skill_routing_new_skills.sql +23 -0
- package/migrations/087_skill_routing_second_opinion.sql +8 -0
- package/migrations/088_meeting_notion_sync.sql +10 -0
- package/migrations/089_bot_commitments_dedup.sql +17 -0
- package/migrations/090_incubator_discord_guild.sql +8 -0
- package/migrations/091_drop_deprecated_gfp_kpi_views.sql +65 -0
- package/migrations/092_ontology_channel_mapping.sql +28 -0
- package/migrations/093_update_bot_workspace_paths.sql +15 -0
- package/migrations/094_cron_run_tracking.sql +28 -0
- package/migrations/095_drop_bot_cron_runs.sql +7 -0
- package/migrations/096_kb_pipeline_keys.sql +66 -0
- package/migrations/097_drop_empty_service_tables.sql +83 -0
- package/migrations/098_migrate_features_materials_drop.sql +119 -0
- package/migrations/099_migrate_sections_drop.sql +52 -0
- package/migrations/099_seed_cron_poller_tick.sql +14 -0
- package/migrations/100_migrate_services_drop.sql +57 -0
- package/migrations/101_drop_services.sql +5 -0
- package/migrations/102_cost_log_session_dedup.sql +13 -0
- package/migrations/103_board.sql +57 -0
- package/migrations/104_agent_factory.sql +284 -0
- package/migrations/105_kb_notify.sql +44 -0
- package/migrations/106_office_id_agents_commands.sql +73 -0
- package/migrations/107_agent_service_credentials.sql +79 -0
- package/migrations/108_drop_l2_ontology_seeds.sql +16 -0
- package/migrations/109_generic_ontology_descriptions.sql +21 -0
- package/migrations/110_kb_manager_skill_l2_cleanup.sql +34 -0
- package/migrations/111_drop_l2_team_ontology_seeds.sql +20 -0
- package/migrations/112_fix_pipeline_schema_and_role_trigger.sql +83 -0
- package/migrations/113_kb_history.sql +53 -0
- package/migrations/114_runtime_target_columns.sql +26 -0
- package/migrations/115_runtime_source_columns.sql +52 -0
- package/migrations/116_commitment_pattern_health.sql +81 -0
- package/migrations/117_commitment_pattern_alert_claims.sql +33 -0
- package/migrations/118_retire_semiclaw_semicolonybot_alias.sql +30 -0
- package/migrations/119_commitment_events.sql +51 -0
- package/migrations/120_host_signals.sql +51 -0
- package/migrations/121_agent_spec_derived_runtime_metadata.sql +68 -0
- package/migrations/122_agent_spec_derived_fk_policy.sql +18 -0
- package/migrations/123_agent_spec_projection_skip_targets.sql +24 -0
- package/migrations/124_bot_commitments_notify.sql +26 -0
- package/migrations/125_agent_personas.sql +36 -0
- package/migrations/128_entity_relations.sql +125 -0
- package/migrations/129_kb_multitenant_gateway.sql +86 -0
- package/migrations/130_jewelry_relation_types.sql +36 -0
- package/migrations/131_concierge_requests.sql +38 -0
- package/migrations/132_kb_supersession.sql +27 -0
- package/migrations/133_kb_claims.sql +52 -0
- package/migrations/134_migration_registry.sql +18 -0
- package/migrations/135_sc_roles.sql +102 -0
- package/migrations/136_runtime_tenant_columns.sql +71 -0
- package/migrations/137_runtime_backfill_team_semicolon.sql +131 -0
- package/migrations/138_runtime_constraints_not_valid.sql +56 -0
- package/migrations/139_tenant_vms.sql +31 -0
- package/migrations/140_fleet_slots.sql +23 -0
- package/migrations/141_provisioning_jobs.sql +29 -0
- package/migrations/142_rls_enable_semicolony.sql +129 -0
- package/migrations/143_rls_enable_public.sql +54 -0
- package/migrations/144_worker_device_registry.sql +211 -0
- package/migrations/145_workspace_profile_workstation_policy.sql +39 -0
- package/migrations/146_worker_profile_repo_scope.sql +24 -0
- package/migrations/147_worker_profile_semo_ops_repo_rename.sql +18 -0
- package/migrations/148_momo_runtime_convergence.sql +92 -0
- package/migrations/README.md +88 -0
- package/migrations-sqlite/kb/001_initial.sql +56 -0
- package/migrations-sqlite/ops/001_initial.sql +53 -0
- package/migrations-sqlite/ops/002_action_items.sql +48 -0
- package/package.json +78 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
-- 020: bot_workspace_standard — 봇 워크스페이스 규격 SoT 테이블
|
|
2
|
+
-- v2.0 규격을 DB로 통합하여 MCP 도구/audit/셸 스크립트가 단일 소스에서 규칙 로드
|
|
3
|
+
|
|
4
|
+
CREATE TABLE IF NOT EXISTS semicolony.bot_workspace_standard (
|
|
5
|
+
id BIGSERIAL PRIMARY KEY,
|
|
6
|
+
path_pattern VARCHAR(255) NOT NULL, -- "SOUL.md", ".claude/", "*.ovpn"
|
|
7
|
+
entry_type VARCHAR(20) NOT NULL, -- 'file' | 'dir' | 'symlink' | 'glob'
|
|
8
|
+
level VARCHAR(20) NOT NULL, -- 'required' | 'optional' | 'forbidden'
|
|
9
|
+
severity VARCHAR(10) NOT NULL DEFAULT 'error', -- 'error' | 'warn'
|
|
10
|
+
category VARCHAR(50) NOT NULL DEFAULT 'structure', -- 'structure' | 'legacy' | 'hygiene'
|
|
11
|
+
bot_scope VARCHAR(20) NOT NULL DEFAULT 'all', -- 'all' | 'include' | 'exclude'
|
|
12
|
+
bot_ids TEXT[] DEFAULT '{}',
|
|
13
|
+
symlink_target TEXT, -- 심링크 타겟 (템플릿: $HOME)
|
|
14
|
+
content_rules JSONB, -- 콘텐츠 검증 규칙
|
|
15
|
+
description TEXT,
|
|
16
|
+
fix_action VARCHAR(50), -- 'create_file' | 'create_dir' | 'create_symlink' | null
|
|
17
|
+
fix_template TEXT, -- --fix 시 생성할 콘텐츠 ($BOT_ID 치환)
|
|
18
|
+
spec_version VARCHAR(20) NOT NULL DEFAULT '2.0',
|
|
19
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
20
|
+
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
|
21
|
+
UNIQUE (path_pattern, spec_version)
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
CREATE INDEX IF NOT EXISTS idx_bws_level ON semicolony.bot_workspace_standard(level);
|
|
25
|
+
CREATE INDEX IF NOT EXISTS idx_bws_version ON semicolony.bot_workspace_standard(spec_version);
|
|
26
|
+
|
|
27
|
+
-- ============================================================
|
|
28
|
+
-- Seed data (v2.0 규격 ~35행)
|
|
29
|
+
-- ============================================================
|
|
30
|
+
|
|
31
|
+
INSERT INTO semicolony.bot_workspace_standard (path_pattern, entry_type, level, severity, category, bot_scope, bot_ids, symlink_target, content_rules, description, fix_action, fix_template)
|
|
32
|
+
VALUES
|
|
33
|
+
-- 필수 파일
|
|
34
|
+
('SOUL.md', 'file', 'required', 'error', 'structure', 'all', '{}', NULL,
|
|
35
|
+
'{"max_lines": 120, "required_sections": ["## Identity", "## R&R", "## KB Lookup", "## Operating Procedures", "## NON-NEGOTIABLE"], "forbidden_patterns": ["U[0-9A-Z]{8,11}", "C[0-9A-Z]{8,11}"]}'::jsonb,
|
|
36
|
+
'봇 페르소나 + R&R + 행동강령', 'create_file',
|
|
37
|
+
'# $BOT_ID — SOUL\n\n## Identity\n\n> TODO\n\n## R&R\n\n> TODO\n\n## KB Lookup\n\n> kb_get/kb_search로 팀 정보 조회\n\n## Operating Procedures\n\n> TODO\n\n## NON-NEGOTIABLE\n\n1. TODO'),
|
|
38
|
+
|
|
39
|
+
('AGENTS.md', 'symlink', 'required', 'error', 'structure', 'all', '{}',
|
|
40
|
+
'$HOME/.openclaw-shared/AGENTS.md',
|
|
41
|
+
'{"max_lines": 150, "required_patterns": ["Every Session", "KB-First"]}'::jsonb,
|
|
42
|
+
'공통 세션 운영 규칙 (심링크)', 'create_symlink', NULL),
|
|
43
|
+
|
|
44
|
+
('USER.md', 'file', 'required', 'error', 'structure', 'all', '{}', NULL,
|
|
45
|
+
'{"max_lines": 15}'::jsonb,
|
|
46
|
+
'사용자 컨텍스트', 'create_file',
|
|
47
|
+
'# USER\n\n> TODO: $BOT_ID 사용자 정보'),
|
|
48
|
+
|
|
49
|
+
('MEMORY.md', 'file', 'required', 'error', 'structure', 'all', '{}', NULL,
|
|
50
|
+
'{"max_lines": 30, "required_patterns": ["KB SoT|KB.*SoT|Single Source"]}'::jsonb,
|
|
51
|
+
'KB 도메인 인덱스 (main 세션만)', 'create_file',
|
|
52
|
+
'# MEMORY\n\n> KB SoT — 팀 정보는 kb_search/kb_get으로 조회'),
|
|
53
|
+
|
|
54
|
+
('HEARTBEAT.md', 'file', 'required', 'error', 'structure', 'include', '{semiclaw}', NULL,
|
|
55
|
+
NULL,
|
|
56
|
+
'크론 작업 정의 (semiclaw 전용)', NULL, NULL),
|
|
57
|
+
|
|
58
|
+
('.claude/settings.json', 'file', 'required', 'error', 'structure', 'all', '{}', NULL,
|
|
59
|
+
'{"required_patterns": ["semicolony-kb"]}'::jsonb,
|
|
60
|
+
'MCP 서버 설정', NULL, NULL),
|
|
61
|
+
|
|
62
|
+
-- 필수 디렉토리
|
|
63
|
+
('.claude/', 'dir', 'required', 'error', 'structure', 'all', '{}', NULL, NULL,
|
|
64
|
+
'Claude 설정 디렉토리', 'create_dir', NULL),
|
|
65
|
+
|
|
66
|
+
('hooks/', 'dir', 'required', 'error', 'structure', 'all', '{}', NULL, NULL,
|
|
67
|
+
'OpenClaw 훅', 'create_dir', NULL),
|
|
68
|
+
|
|
69
|
+
('memory/', 'dir', 'required', 'error', 'structure', 'all', '{}', NULL, NULL,
|
|
70
|
+
'일일로그 디렉토리', 'create_dir', NULL),
|
|
71
|
+
|
|
72
|
+
('skills/', 'dir', 'required', 'error', 'structure', 'all', '{}', NULL, NULL,
|
|
73
|
+
'봇 전용 스킬', 'create_dir', NULL),
|
|
74
|
+
|
|
75
|
+
-- 선택 디렉토리/파일
|
|
76
|
+
('scripts/', 'dir', 'optional', 'warn', 'structure', 'all', '{}', NULL, NULL,
|
|
77
|
+
'유틸리티 스크립트', NULL, NULL),
|
|
78
|
+
|
|
79
|
+
('shared/', 'dir', 'optional', 'warn', 'structure', 'all', '{}', NULL, NULL,
|
|
80
|
+
'공유 리소스 (심링크)', NULL, NULL),
|
|
81
|
+
|
|
82
|
+
('.gh-pat', 'file', 'optional', 'warn', 'structure', 'all', '{}', NULL, NULL,
|
|
83
|
+
'GitHub PAT 파일', NULL, NULL),
|
|
84
|
+
|
|
85
|
+
('.gitignore', 'file', 'optional', 'warn', 'structure', 'all', '{}', NULL, NULL,
|
|
86
|
+
'Git ignore', NULL, NULL),
|
|
87
|
+
|
|
88
|
+
-- 레거시 파일 (금지)
|
|
89
|
+
('IDENTITY.md', 'file', 'forbidden', 'error', 'legacy', 'all', '{}', NULL, NULL,
|
|
90
|
+
'v1 레거시 — SOUL.md ## Identity로 대체됨', NULL, NULL),
|
|
91
|
+
|
|
92
|
+
('TOOLS.md', 'file', 'forbidden', 'error', 'legacy', 'all', '{}', NULL, NULL,
|
|
93
|
+
'v1 레거시 — KB lookup 지시로 대체', NULL, NULL),
|
|
94
|
+
|
|
95
|
+
('RULES.md', 'file', 'forbidden', 'error', 'legacy', 'all', '{}', NULL, NULL,
|
|
96
|
+
'v1 레거시 — SOUL.md ## NON-NEGOTIABLE로 통합', NULL, NULL),
|
|
97
|
+
|
|
98
|
+
('CLAUDE.md', 'file', 'forbidden', 'warn', 'legacy', 'all', '{}', NULL, NULL,
|
|
99
|
+
'v1 레거시 — 프로젝트 .claude/CLAUDE.md에 이미 존재', NULL, NULL),
|
|
100
|
+
|
|
101
|
+
('BOOTSTRAP.md', 'file', 'forbidden', 'warn', 'legacy', 'all', '{}', NULL, NULL,
|
|
102
|
+
'v1 레거시', NULL, NULL),
|
|
103
|
+
|
|
104
|
+
-- 위생 (금지)
|
|
105
|
+
('*/.git', 'glob', 'forbidden', 'error', 'hygiene', 'all', '{}', NULL, NULL,
|
|
106
|
+
'클론된 리포 — workspace에 git clone 금지', NULL, NULL),
|
|
107
|
+
|
|
108
|
+
('node_modules', 'glob', 'forbidden', 'error', 'hygiene', 'all', '{}', NULL, NULL,
|
|
109
|
+
'npm install 산출물', NULL, NULL),
|
|
110
|
+
|
|
111
|
+
('dist/', 'dir', 'forbidden', 'error', 'hygiene', 'all', '{}', NULL, NULL,
|
|
112
|
+
'빌드 산출물', NULL, NULL),
|
|
113
|
+
|
|
114
|
+
('build/', 'dir', 'forbidden', 'error', 'hygiene', 'all', '{}', NULL, NULL,
|
|
115
|
+
'빌드 산출물', NULL, NULL),
|
|
116
|
+
|
|
117
|
+
('.next/', 'dir', 'forbidden', 'error', 'hygiene', 'all', '{}', NULL, NULL,
|
|
118
|
+
'Next.js 빌드 산출물', NULL, NULL),
|
|
119
|
+
|
|
120
|
+
('out/', 'dir', 'forbidden', 'error', 'hygiene', 'all', '{}', NULL, NULL,
|
|
121
|
+
'빌드 산출물', NULL, NULL),
|
|
122
|
+
|
|
123
|
+
('*.ovpn', 'glob', 'forbidden', 'warn', 'hygiene', 'all', '{}', NULL, NULL,
|
|
124
|
+
'VPN 설정 파일 — 보안', NULL, NULL),
|
|
125
|
+
|
|
126
|
+
('*.pem', 'glob', 'forbidden', 'warn', 'hygiene', 'all', '{}', NULL, NULL,
|
|
127
|
+
'인증서 파일 — 보안', NULL, NULL),
|
|
128
|
+
|
|
129
|
+
('*.key', 'glob', 'forbidden', 'warn', 'hygiene', 'all', '{}', NULL, NULL,
|
|
130
|
+
'비밀키 파일 — 보안', NULL, NULL),
|
|
131
|
+
|
|
132
|
+
('credentials/', 'dir', 'forbidden', 'warn', 'hygiene', 'all', '{}', NULL, NULL,
|
|
133
|
+
'인증 정보 디렉토리', NULL, NULL),
|
|
134
|
+
|
|
135
|
+
('*.png', 'glob', 'forbidden', 'warn', 'hygiene', 'all', '{}', NULL, NULL,
|
|
136
|
+
'바이너리 이미지 파일', NULL, NULL),
|
|
137
|
+
|
|
138
|
+
('*.jpg', 'glob', 'forbidden', 'warn', 'hygiene', 'all', '{}', NULL, NULL,
|
|
139
|
+
'바이너리 이미지 파일', NULL, NULL),
|
|
140
|
+
|
|
141
|
+
('*.zip', 'glob', 'forbidden', 'warn', 'hygiene', 'all', '{}', NULL, NULL,
|
|
142
|
+
'압축 파일', NULL, NULL),
|
|
143
|
+
|
|
144
|
+
('*.tgz', 'glob', 'forbidden', 'warn', 'hygiene', 'all', '{}', NULL, NULL,
|
|
145
|
+
'압축 파일', NULL, NULL),
|
|
146
|
+
|
|
147
|
+
('temp/', 'glob', 'forbidden', 'warn', 'hygiene', 'all', '{}', NULL, NULL,
|
|
148
|
+
'임시 디렉토리', NULL, NULL),
|
|
149
|
+
|
|
150
|
+
('tmp/', 'glob', 'forbidden', 'warn', 'hygiene', 'all', '{}', NULL, NULL,
|
|
151
|
+
'임시 디렉토리', NULL, NULL),
|
|
152
|
+
|
|
153
|
+
('*.tmp', 'glob', 'forbidden', 'warn', 'hygiene', 'all', '{}', NULL, NULL,
|
|
154
|
+
'임시 파일', NULL, NULL),
|
|
155
|
+
|
|
156
|
+
('go.mod', 'file', 'forbidden', 'warn', 'hygiene', 'all', '{}', NULL, NULL,
|
|
157
|
+
'Go 모듈 파일 — workspace에 불필요', NULL, NULL),
|
|
158
|
+
|
|
159
|
+
('go.sum', 'file', 'forbidden', 'warn', 'hygiene', 'all', '{}', NULL, NULL,
|
|
160
|
+
'Go 체크섬 파일 — workspace에 불필요', NULL, NULL)
|
|
161
|
+
|
|
162
|
+
ON CONFLICT (path_pattern, spec_version) DO UPDATE SET
|
|
163
|
+
entry_type = EXCLUDED.entry_type,
|
|
164
|
+
level = EXCLUDED.level,
|
|
165
|
+
severity = EXCLUDED.severity,
|
|
166
|
+
category = EXCLUDED.category,
|
|
167
|
+
bot_scope = EXCLUDED.bot_scope,
|
|
168
|
+
bot_ids = EXCLUDED.bot_ids,
|
|
169
|
+
symlink_target = EXCLUDED.symlink_target,
|
|
170
|
+
content_rules = EXCLUDED.content_rules,
|
|
171
|
+
description = EXCLUDED.description,
|
|
172
|
+
fix_action = EXCLUDED.fix_action,
|
|
173
|
+
fix_template = EXCLUDED.fix_template,
|
|
174
|
+
updated_at = NOW();
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
-- 021: Drop dead tables
|
|
2
|
+
-- bot_kb_subscriptions: 코드 참조 0건, kb_digest since 파라미터로 대체
|
|
3
|
+
-- bot_knowledge: 012에서 DROP 선언되었으나 잔존 가능
|
|
4
|
+
-- kb_usage_log: 코드 참조 0건, 계획만 있고 미구현
|
|
5
|
+
|
|
6
|
+
DROP TABLE IF EXISTS semicolony.bot_kb_subscriptions;
|
|
7
|
+
DROP TABLE IF EXISTS semicolony.bot_knowledge;
|
|
8
|
+
DROP TABLE IF EXISTS semicolony.kb_usage_log;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
-- 021_test_declarative.sql
|
|
2
|
+
-- test_suites에 rule_source 컬럼 추가 + workspace-audit을 declarative 타입으로 전환
|
|
3
|
+
|
|
4
|
+
BEGIN;
|
|
5
|
+
|
|
6
|
+
-- runner_path를 nullable로 변경 (declarative는 path 불필요)
|
|
7
|
+
ALTER TABLE semicolony.test_suites ALTER COLUMN runner_path DROP NOT NULL;
|
|
8
|
+
|
|
9
|
+
-- rule_source: declarative 러너가 규칙을 로드할 테이블명
|
|
10
|
+
ALTER TABLE semicolony.test_suites ADD COLUMN IF NOT EXISTS rule_source TEXT;
|
|
11
|
+
|
|
12
|
+
-- workspace-audit을 declarative로 전환
|
|
13
|
+
UPDATE semicolony.test_suites
|
|
14
|
+
SET runner_type = 'declarative',
|
|
15
|
+
runner_path = NULL,
|
|
16
|
+
rule_source = 'bot_workspace_standard'
|
|
17
|
+
WHERE suite_id = 'workspace-audit';
|
|
18
|
+
|
|
19
|
+
COMMIT;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
-- 022_bot_ontology_type.sql
|
|
2
|
+
-- bot 타입을 온톨로지 + kb_type_schema에 추가하고, 봇을 도메인으로 등록
|
|
3
|
+
|
|
4
|
+
BEGIN;
|
|
5
|
+
|
|
6
|
+
-- 1. ontology_types에 bot 타입 추가
|
|
7
|
+
INSERT INTO semicolony.ontology_types (type_key, description)
|
|
8
|
+
VALUES ('bot', 'OpenClaw 봇 에이전트')
|
|
9
|
+
ON CONFLICT (type_key) DO NOTHING;
|
|
10
|
+
|
|
11
|
+
-- 2. bot 타입 scheme_key 정의 (KB 메타데이터 전용, 워크스페이스 파일은 bot_workspace_standard에서 관리)
|
|
12
|
+
INSERT INTO semicolony.kb_type_schema (type_key, scheme_key, scheme_description, required, value_hint, sort_order) VALUES
|
|
13
|
+
('bot', 'identity', '봇 기본 정보 (이름, 이모지, 한 줄 설명)', true, 'YAML: name, emoji, tagline', 1),
|
|
14
|
+
('bot', 'role', 'R&R — 역할 범위, 담당 업무', true, '마크다운: 업무 범위 테이블', 2),
|
|
15
|
+
('bot', 'status', '현재 상태', true, 'online | offline | maintenance', 3),
|
|
16
|
+
('bot', 'delegation', '인계 매트릭스 — 역할 밖 요청 라우팅', false, '마크다운: 요청유형 → 인계봇 테이블', 4),
|
|
17
|
+
('bot', 'gateway_config', '게이트웨이 설정 (포트, 토큰 경로)', false, 'YAML: port, token_path, model', 5),
|
|
18
|
+
('bot', 'cron_schedule', '크론 작업 목록', false, '마크다운: 스케줄 + 설명 테이블', 6),
|
|
19
|
+
('bot', 'slack_config', 'Slack 연동 (Bot ID, 채널)', false, 'YAML: bot_id, channels[]', 7)
|
|
20
|
+
ON CONFLICT (type_key, scheme_key) DO UPDATE SET
|
|
21
|
+
scheme_description = EXCLUDED.scheme_description,
|
|
22
|
+
required = EXCLUDED.required,
|
|
23
|
+
value_hint = EXCLUDED.value_hint,
|
|
24
|
+
sort_order = EXCLUDED.sort_order;
|
|
25
|
+
|
|
26
|
+
-- 3. 각 봇을 ontology 도메인으로 등록 (bot_status에서 동적 조회)
|
|
27
|
+
INSERT INTO semicolony.ontology (domain, entity_type, description, service, schema)
|
|
28
|
+
SELECT bot_id, 'bot',
|
|
29
|
+
COALESCE(name, bot_id) || ' — ' || COALESCE(role, 'Bot'),
|
|
30
|
+
bot_id,
|
|
31
|
+
'{}'::jsonb
|
|
32
|
+
FROM semicolony.bot_status
|
|
33
|
+
WHERE bot_id != 'shared'
|
|
34
|
+
ON CONFLICT (domain) DO UPDATE SET
|
|
35
|
+
entity_type = 'bot',
|
|
36
|
+
description = EXCLUDED.description;
|
|
37
|
+
|
|
38
|
+
COMMIT;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
-- 022_kb_schema_enforcement.sql
|
|
2
|
+
-- KB 키 검증 강화: scheme_key 1뎁스 전환 + key_type 컬럼 추가
|
|
3
|
+
--
|
|
4
|
+
-- 변경 내용:
|
|
5
|
+
-- 1. key_type 컬럼 추가 (singleton | collection)
|
|
6
|
+
-- 2. 기존 변수 패턴({...}) scheme_key → collection으로 마킹
|
|
7
|
+
-- 3. scheme_key를 1뎁스(첫 세그먼트)로 축약
|
|
8
|
+
-- 4. 축약 후 중복 제거
|
|
9
|
+
-- 5. service 타입에 kpi/milestone collection 추가
|
|
10
|
+
--
|
|
11
|
+
-- 선행 조건: 018_type_schema.sql 적용 완료
|
|
12
|
+
|
|
13
|
+
BEGIN;
|
|
14
|
+
|
|
15
|
+
-- ============================================================
|
|
16
|
+
-- 1. key_type 컬럼 추가
|
|
17
|
+
-- ============================================================
|
|
18
|
+
|
|
19
|
+
ALTER TABLE semicolony.kb_type_schema
|
|
20
|
+
ADD COLUMN IF NOT EXISTS key_type VARCHAR(20) DEFAULT 'singleton';
|
|
21
|
+
|
|
22
|
+
-- ============================================================
|
|
23
|
+
-- 2. 기존 데이터 key_type 설정 (변수 포함 = collection)
|
|
24
|
+
-- ============================================================
|
|
25
|
+
|
|
26
|
+
UPDATE semicolony.kb_type_schema
|
|
27
|
+
SET key_type = 'collection'
|
|
28
|
+
WHERE scheme_key LIKE '%{%';
|
|
29
|
+
|
|
30
|
+
-- ============================================================
|
|
31
|
+
-- 3. scheme_key를 1뎁스로 축약 (첫 번째 세그먼트만)
|
|
32
|
+
-- ============================================================
|
|
33
|
+
|
|
34
|
+
UPDATE semicolony.kb_type_schema
|
|
35
|
+
SET scheme_key = SPLIT_PART(scheme_key, '/', 1)
|
|
36
|
+
WHERE scheme_key LIKE '%/%';
|
|
37
|
+
|
|
38
|
+
-- ============================================================
|
|
39
|
+
-- 4. 중복 제거 (축약 후 동일 type_key+scheme_key 병합)
|
|
40
|
+
-- ============================================================
|
|
41
|
+
|
|
42
|
+
DELETE FROM semicolony.kb_type_schema a
|
|
43
|
+
USING semicolony.kb_type_schema b
|
|
44
|
+
WHERE a.id > b.id
|
|
45
|
+
AND a.type_key = b.type_key
|
|
46
|
+
AND a.scheme_key = b.scheme_key;
|
|
47
|
+
|
|
48
|
+
-- 축약 후 collection으로 마킹되지 않은 행도 올바르게 설정
|
|
49
|
+
-- (예: 'team' 은 원래 'team/{name}'이었으므로 collection)
|
|
50
|
+
UPDATE semicolony.kb_type_schema
|
|
51
|
+
SET key_type = 'collection'
|
|
52
|
+
WHERE key_type = 'singleton'
|
|
53
|
+
AND scheme_key IN ('team', 'decision', 'process', 'infra', 'bot-config', 'skill', 'spec', 'memory', 'session-log');
|
|
54
|
+
|
|
55
|
+
-- kpi 타입의 '{YYYY-WNN}' → collection으로 이미 변환됨, 축약 후 '{YYYY-WNN}' 자체가 첫 세그먼트
|
|
56
|
+
-- 이 잔여 항목 정리 (변수 패턴이 첫 세그먼트인 경우 삭제 — collection prefix로 대체 불가)
|
|
57
|
+
DELETE FROM semicolony.kb_type_schema
|
|
58
|
+
WHERE scheme_key LIKE '{%';
|
|
59
|
+
|
|
60
|
+
-- milestone 타입의 '{slug}' 도 동일하게 삭제
|
|
61
|
+
-- (service.milestone collection으로 통합)
|
|
62
|
+
|
|
63
|
+
-- ============================================================
|
|
64
|
+
-- 5. service 타입에 kpi/milestone collection 추가
|
|
65
|
+
-- ============================================================
|
|
66
|
+
|
|
67
|
+
INSERT INTO semicolony.kb_type_schema (type_key, scheme_key, scheme_description, required, value_hint, sort_order, key_type)
|
|
68
|
+
VALUES
|
|
69
|
+
('service', 'kpi', 'KPI (current/target/주간 스냅샷)', false, 'kpi/current, kpi/target, kpi/2026-W12', 10, 'collection'),
|
|
70
|
+
('service', 'milestone', '마일스톤 항목', false, 'milestone/blog-auto-gen', 11, 'collection')
|
|
71
|
+
ON CONFLICT (type_key, scheme_key) DO UPDATE SET
|
|
72
|
+
key_type = 'collection',
|
|
73
|
+
scheme_description = EXCLUDED.scheme_description,
|
|
74
|
+
value_hint = EXCLUDED.value_hint,
|
|
75
|
+
sort_order = EXCLUDED.sort_order;
|
|
76
|
+
|
|
77
|
+
COMMIT;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* 023_decision_key_cleanup.ts
|
|
4
|
+
*
|
|
5
|
+
* ~100건의 비표준 decision 키를 표준 형식(decision/{date}/{slug})으로 변환.
|
|
6
|
+
*
|
|
7
|
+
* 변환 규칙:
|
|
8
|
+
* - 이미 decision/{YYYY-MM-DD}/{slug} 형식이면 스킵
|
|
9
|
+
* - content/metadata에서 날짜 추출 시도
|
|
10
|
+
* - 날짜 추출 불가 시 updated_at에서 추출
|
|
11
|
+
* - 키 충돌 시 suffix 추가 (-2, -3, ...)
|
|
12
|
+
*
|
|
13
|
+
* 사용법:
|
|
14
|
+
* npx tsx packages/cli/migrations/023_decision_key_cleanup.ts [--dry-run]
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import pg from "pg";
|
|
18
|
+
|
|
19
|
+
const DATABASE_URL = process.env.DATABASE_URL;
|
|
20
|
+
if (!DATABASE_URL) {
|
|
21
|
+
console.error("DATABASE_URL 환경변수가 필요합니다");
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const DRY_RUN = process.argv.includes("--dry-run");
|
|
26
|
+
|
|
27
|
+
const STANDARD_PATTERN = /^decision\/\d{4}-\d{2}-\d{2}\/.+$/;
|
|
28
|
+
const DATE_PATTERN = /(\d{4}-\d{2}-\d{2})/;
|
|
29
|
+
|
|
30
|
+
function slugify(text: string): string {
|
|
31
|
+
return text
|
|
32
|
+
.toLowerCase()
|
|
33
|
+
.replace(/[가-힣]+/g, (match) => match) // keep Korean as-is
|
|
34
|
+
.replace(/[^a-z0-9가-힣\-]/g, "-")
|
|
35
|
+
.replace(/-+/g, "-")
|
|
36
|
+
.replace(/^-|-$/g, "")
|
|
37
|
+
.substring(0, 80);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function extractDate(keySlug: string, content: string, metadata: Record<string, unknown> | null): string | null {
|
|
41
|
+
// Try metadata first
|
|
42
|
+
if (metadata) {
|
|
43
|
+
for (const k of ["date", "decided_at", "created_at", "timestamp"]) {
|
|
44
|
+
const val = metadata[k];
|
|
45
|
+
if (typeof val === "string") {
|
|
46
|
+
const match = val.match(DATE_PATTERN);
|
|
47
|
+
if (match) return match[1];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Try key slug (e.g., "메모리 구조 개편 (2026-02-17)")
|
|
53
|
+
const keyMatch = keySlug.match(DATE_PATTERN);
|
|
54
|
+
if (keyMatch) return keyMatch[1];
|
|
55
|
+
|
|
56
|
+
// Try content
|
|
57
|
+
const contentMatch = content.match(DATE_PATTERN);
|
|
58
|
+
if (contentMatch) return contentMatch[1];
|
|
59
|
+
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function main() {
|
|
64
|
+
const pool = new pg.Pool({ connectionString: DATABASE_URL });
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
// Find all decision keys in semicolon domain
|
|
68
|
+
const result = await pool.query(`
|
|
69
|
+
SELECT domain, key, content, metadata, updated_at::text
|
|
70
|
+
FROM semicolony.knowledge_base
|
|
71
|
+
WHERE domain = 'semicolon' AND key LIKE 'decision/%'
|
|
72
|
+
ORDER BY key
|
|
73
|
+
`);
|
|
74
|
+
|
|
75
|
+
const rows = result.rows;
|
|
76
|
+
console.log(`총 decision 엔트리: ${rows.length}건`);
|
|
77
|
+
|
|
78
|
+
const nonStandard = rows.filter((r) => !STANDARD_PATTERN.test(r.key));
|
|
79
|
+
console.log(`비표준 키: ${nonStandard.length}건`);
|
|
80
|
+
|
|
81
|
+
if (nonStandard.length === 0) {
|
|
82
|
+
console.log("정리할 항목 없음");
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Collect existing keys to avoid conflicts
|
|
87
|
+
const existingKeys = new Set(rows.map((r) => r.key));
|
|
88
|
+
|
|
89
|
+
let updated = 0;
|
|
90
|
+
let skipped = 0;
|
|
91
|
+
|
|
92
|
+
for (const row of nonStandard) {
|
|
93
|
+
const oldKey = row.key;
|
|
94
|
+
const parts = oldKey.split("/");
|
|
95
|
+
|
|
96
|
+
// Extract the "slug" portion (everything after "decision/")
|
|
97
|
+
const rawSlug = parts.slice(1).join("/");
|
|
98
|
+
|
|
99
|
+
// Extract date
|
|
100
|
+
let date = extractDate(rawSlug, row.content, row.metadata);
|
|
101
|
+
if (!date && row.updated_at) {
|
|
102
|
+
date = row.updated_at.substring(0, 10);
|
|
103
|
+
}
|
|
104
|
+
if (!date) {
|
|
105
|
+
date = "unknown";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// If it already has a date as second segment, use that
|
|
109
|
+
if (parts.length >= 3 && DATE_PATTERN.test(parts[1])) {
|
|
110
|
+
// Already has date but may have extra depth — keep as is
|
|
111
|
+
console.log(` 스킵 (이미 날짜 포함): ${oldKey}`);
|
|
112
|
+
skipped++;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const slug = slugify(rawSlug);
|
|
117
|
+
let newKey = `decision/${date}/${slug}`;
|
|
118
|
+
|
|
119
|
+
// Dedup: if conflict, add suffix
|
|
120
|
+
if (existingKeys.has(newKey) && newKey !== oldKey) {
|
|
121
|
+
let suffix = 2;
|
|
122
|
+
while (existingKeys.has(`${newKey}-${suffix}`)) suffix++;
|
|
123
|
+
newKey = `${newKey}-${suffix}`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (newKey === oldKey) {
|
|
127
|
+
skipped++;
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
console.log(` ${oldKey} → ${newKey}`);
|
|
132
|
+
|
|
133
|
+
if (!DRY_RUN) {
|
|
134
|
+
await pool.query(
|
|
135
|
+
`UPDATE semicolony.knowledge_base SET key = $1 WHERE domain = $2 AND key = $3`,
|
|
136
|
+
[newKey, row.domain, oldKey]
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
existingKeys.delete(oldKey);
|
|
141
|
+
existingKeys.add(newKey);
|
|
142
|
+
updated++;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
console.log(`\n결과: ${updated}건 변환, ${skipped}건 스킵${DRY_RUN ? " (dry-run)" : ""}`);
|
|
146
|
+
} finally {
|
|
147
|
+
await pool.end();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
main().catch((err) => {
|
|
152
|
+
console.error("에러:", err);
|
|
153
|
+
process.exit(1);
|
|
154
|
+
});
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* 024_embedding_backfill.ts
|
|
4
|
+
*
|
|
5
|
+
* 임베딩 없는 KB 엔트리에 벡터 일괄 생성.
|
|
6
|
+
* OpenAI batch API를 사용하여 효율적으로 처리.
|
|
7
|
+
*
|
|
8
|
+
* 사용법:
|
|
9
|
+
* npx tsx packages/cli/migrations/024_embedding_backfill.ts [--dry-run] [--batch-size=20]
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import pg from "pg";
|
|
13
|
+
|
|
14
|
+
const DATABASE_URL = process.env.DATABASE_URL;
|
|
15
|
+
const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
|
|
16
|
+
|
|
17
|
+
if (!DATABASE_URL) {
|
|
18
|
+
console.error("DATABASE_URL 환경변수가 필요합니다");
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
if (!OPENAI_API_KEY) {
|
|
22
|
+
console.error("OPENAI_API_KEY 환경변수가 필요합니다");
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const DRY_RUN = process.argv.includes("--dry-run");
|
|
27
|
+
const BATCH_SIZE = parseInt(
|
|
28
|
+
process.argv.find((a) => a.startsWith("--batch-size="))?.split("=")[1] || "20",
|
|
29
|
+
10
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const EMBEDDING_MODEL = "text-embedding-3-small";
|
|
33
|
+
const EMBEDDING_DIMENSIONS = 1024;
|
|
34
|
+
|
|
35
|
+
async function generateEmbeddings(texts: string[]): Promise<(number[] | null)[]> {
|
|
36
|
+
const response = await fetch("https://api.openai.com/v1/embeddings", {
|
|
37
|
+
method: "POST",
|
|
38
|
+
headers: {
|
|
39
|
+
Authorization: `Bearer ${OPENAI_API_KEY}`,
|
|
40
|
+
"Content-Type": "application/json",
|
|
41
|
+
},
|
|
42
|
+
body: JSON.stringify({
|
|
43
|
+
model: EMBEDDING_MODEL,
|
|
44
|
+
input: texts.map((t) => t.substring(0, 8000)),
|
|
45
|
+
dimensions: EMBEDDING_DIMENSIONS,
|
|
46
|
+
}),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (!response.ok) {
|
|
50
|
+
const err = await response.text();
|
|
51
|
+
console.error(`API 에러: ${response.status} ${err}`);
|
|
52
|
+
return texts.map(() => null);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const data = (await response.json()) as { data?: { embedding: number[] }[] };
|
|
56
|
+
return data.data?.map((d) => d.embedding) || texts.map(() => null);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function main() {
|
|
60
|
+
const pool = new pg.Pool({ connectionString: DATABASE_URL });
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const result = await pool.query(`
|
|
64
|
+
SELECT domain, key, content
|
|
65
|
+
FROM semicolony.knowledge_base
|
|
66
|
+
WHERE embedding IS NULL
|
|
67
|
+
ORDER BY domain, key
|
|
68
|
+
`);
|
|
69
|
+
|
|
70
|
+
const rows = result.rows;
|
|
71
|
+
console.log(`임베딩 없는 엔트리: ${rows.length}건`);
|
|
72
|
+
|
|
73
|
+
if (rows.length === 0) {
|
|
74
|
+
console.log("처리할 항목 없음");
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (DRY_RUN) {
|
|
79
|
+
console.log("(dry-run) 실제 처리하지 않음");
|
|
80
|
+
for (const row of rows) {
|
|
81
|
+
console.log(` ${row.domain}/${row.key}`);
|
|
82
|
+
}
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let processed = 0;
|
|
87
|
+
let failed = 0;
|
|
88
|
+
|
|
89
|
+
for (let i = 0; i < rows.length; i += BATCH_SIZE) {
|
|
90
|
+
const batch = rows.slice(i, i + BATCH_SIZE);
|
|
91
|
+
const texts = batch.map((r) => `${r.key}: ${r.content}`);
|
|
92
|
+
|
|
93
|
+
console.log(`배치 ${Math.floor(i / BATCH_SIZE) + 1}/${Math.ceil(rows.length / BATCH_SIZE)} (${batch.length}건)...`);
|
|
94
|
+
|
|
95
|
+
const embeddings = await generateEmbeddings(texts);
|
|
96
|
+
|
|
97
|
+
for (let j = 0; j < batch.length; j++) {
|
|
98
|
+
const row = batch[j];
|
|
99
|
+
const embedding = embeddings[j];
|
|
100
|
+
|
|
101
|
+
if (!embedding) {
|
|
102
|
+
console.error(` 실패: ${row.domain}/${row.key}`);
|
|
103
|
+
failed++;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const embeddingStr = `[${embedding.join(",")}]`;
|
|
108
|
+
await pool.query(
|
|
109
|
+
`UPDATE semicolony.knowledge_base SET embedding = $1::vector WHERE domain = $2 AND key = $3`,
|
|
110
|
+
[embeddingStr, row.domain, row.key]
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
processed++;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Rate limiting: 짧은 대기
|
|
117
|
+
if (i + BATCH_SIZE < rows.length) {
|
|
118
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
console.log(`\n결과: ${processed}건 생성, ${failed}건 실패`);
|
|
123
|
+
|
|
124
|
+
// Verify
|
|
125
|
+
const remaining = await pool.query(
|
|
126
|
+
"SELECT COUNT(*)::int as cnt FROM semicolony.knowledge_base WHERE embedding IS NULL"
|
|
127
|
+
);
|
|
128
|
+
console.log(`남은 NULL 임베딩: ${remaining.rows[0].cnt}건`);
|
|
129
|
+
} finally {
|
|
130
|
+
await pool.end();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
main().catch((err) => {
|
|
135
|
+
console.error("에러:", err);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
-- 025: KB Flat Key Migration
|
|
2
|
+
-- key + sub_key 분리: key는 scheme_key exact match, sub_key는 계층 식별자
|
|
3
|
+
BEGIN;
|
|
4
|
+
|
|
5
|
+
-- 1. sub_key 컬럼 추가
|
|
6
|
+
ALTER TABLE semicolony.knowledge_base
|
|
7
|
+
ADD COLUMN IF NOT EXISTS sub_key VARCHAR(255) NOT NULL DEFAULT '';
|
|
8
|
+
|
|
9
|
+
-- 2. 기존 Unique constraint 제거 (UPDATE 전에 제거해야 충돌 없음)
|
|
10
|
+
ALTER TABLE semicolony.knowledge_base
|
|
11
|
+
DROP CONSTRAINT IF EXISTS knowledge_base_domain_key_key;
|
|
12
|
+
|
|
13
|
+
-- 3. 기존 collection 키 분리 (첫 '/' 기준)
|
|
14
|
+
UPDATE semicolony.knowledge_base
|
|
15
|
+
SET sub_key = SUBSTRING(key FROM POSITION('/' IN key) + 1),
|
|
16
|
+
key = SPLIT_PART(key, '/', 1)
|
|
17
|
+
WHERE key LIKE '%/%';
|
|
18
|
+
|
|
19
|
+
-- 4. 새 Unique constraint 추가
|
|
20
|
+
ALTER TABLE semicolony.knowledge_base
|
|
21
|
+
ADD CONSTRAINT knowledge_base_domain_key_subkey_key
|
|
22
|
+
UNIQUE (domain, key, sub_key);
|
|
23
|
+
|
|
24
|
+
-- 5. 조회 성능 인덱스
|
|
25
|
+
CREATE INDEX IF NOT EXISTS idx_kb_domain_key ON semicolony.knowledge_base (domain, key);
|
|
26
|
+
|
|
27
|
+
-- 6. 테스트 파이프라인 등록
|
|
28
|
+
INSERT INTO semicolony.test_suites (suite_id, name, layer, runner_type, runner_path, enabled)
|
|
29
|
+
VALUES ('025-flat-keys', '025 KB Flat Key Migration', 'integration', 'tsx',
|
|
30
|
+
'packages/mcp-kb/test-025-flat-keys.ts', true)
|
|
31
|
+
ON CONFLICT (suite_id) DO NOTHING;
|
|
32
|
+
|
|
33
|
+
COMMIT;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
BEGIN;
|
|
2
|
+
|
|
3
|
+
-- 1. kb_type_schema에서 session-log 행 제거
|
|
4
|
+
DELETE FROM semicolony.kb_type_schema
|
|
5
|
+
WHERE type_key = 'organization' AND scheme_key = 'session-log';
|
|
6
|
+
|
|
7
|
+
-- 2. ontology_types에서 session-log 타입 제거 (있다면)
|
|
8
|
+
DELETE FROM semicolony.ontology_types WHERE type_key = 'session-log';
|
|
9
|
+
|
|
10
|
+
-- 3. 기존 session-log 데이터 정리
|
|
11
|
+
DELETE FROM semicolony.knowledge_base
|
|
12
|
+
WHERE domain = 'semicolon' AND key = 'session-log';
|
|
13
|
+
|
|
14
|
+
-- 4. semicolon 온톨로지 description에서 session-log 참조 제거
|
|
15
|
+
UPDATE semicolony.ontology
|
|
16
|
+
SET description = REPLACE(description, '/session-log', '')
|
|
17
|
+
WHERE domain = 'semicolon' AND description LIKE '%session-log%';
|
|
18
|
+
|
|
19
|
+
COMMIT;
|