@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.
Files changed (164) hide show
  1. package/LICENSE +200 -0
  2. package/NOTICE +8 -0
  3. package/README.md +152 -0
  4. package/dist/bundle.js +2887 -0
  5. package/dist/templates/launchd/ai.semicolony.host-signals-sidecar.plist +40 -0
  6. package/migrations/001_initial.sql +280 -0
  7. package/migrations/002_add_indexes.sql +11 -0
  8. package/migrations/003_add_fk.sql +32 -0
  9. package/migrations/004_session_count_trigger.sql +35 -0
  10. package/migrations/005_bot_workspace_audits.sql +16 -0
  11. package/migrations/006_bot_kb_subscriptions.sql +24 -0
  12. package/migrations/007_flatten_skill_names.sql +16 -0
  13. package/migrations/008_bot_workspace_files.sql +14 -0
  14. package/migrations/009_enforce_flat_skill_names.sql +53 -0
  15. package/migrations/010_bot_delegation.sql +27 -0
  16. package/migrations/011_drop_legacy_tables.sql +35 -0
  17. package/migrations/012_drop_bot_knowledge.sql +16 -0
  18. package/migrations/013_kb_workspace_domains.sql +88 -0
  19. package/migrations/014_timestamp_to_timestamptz.sql +33 -0
  20. package/migrations/015_kb_domain_enforcement.sql +83 -0
  21. package/migrations/016_ontology_redesign.sql +108 -0
  22. package/migrations/017_ontology_instance_model.sql +151 -0
  23. package/migrations/018_type_schema.sql +11 -0
  24. package/migrations/019_test_management.sql +69 -0
  25. package/migrations/020_bot_workspace_standard.sql +174 -0
  26. package/migrations/021_drop_dead_tables.sql +8 -0
  27. package/migrations/021_test_declarative.sql +19 -0
  28. package/migrations/022_bot_ontology_type.sql +38 -0
  29. package/migrations/022_kb_schema_enforcement.sql +77 -0
  30. package/migrations/023_decision_key_cleanup.ts +154 -0
  31. package/migrations/024_embedding_backfill.ts +137 -0
  32. package/migrations/025_kb_flat_keys.sql +33 -0
  33. package/migrations/026_drop_session_log.sql +19 -0
  34. package/migrations/027_kb_manager_skill.sql +22 -0
  35. package/migrations/028_skill_definitions_to_semicolony.sql +19 -0
  36. package/migrations/029_agents_citation_max_lines.sql +6 -0
  37. package/migrations/030_team_ontology.sql +54 -0
  38. package/migrations/031_gfp_tables.sql +101 -0
  39. package/migrations/031_kb_manager_domain_register.sql +12 -0
  40. package/migrations/032_gfp_skill_and_callback.sql +40 -0
  41. package/migrations/032_kebab_case_keys.sql +33 -0
  42. package/migrations/033_skill_workspace_rules.sql +10 -0
  43. package/migrations/034_current_to_date_keys.sql +13 -0
  44. package/migrations/034_current_to_date_keys.ts +104 -0
  45. package/migrations/035_bot_budgets.sql +51 -0
  46. package/migrations/036_gfp_stitch_design.sql +35 -0
  47. package/migrations/037_gfp_qa_sections.sql +13 -0
  48. package/migrations/038_gfp_phase_split.sql +26 -0
  49. package/migrations/039_meeting_audio_data.sql +2 -0
  50. package/migrations/040_bot_commitments.sql +68 -0
  51. package/migrations/041_palette_previews.sql +13 -0
  52. package/migrations/042_gfp_parallel_tracks.sql +37 -0
  53. package/migrations/043_blocker_key.sql +13 -0
  54. package/migrations/044_gfp_stitch_screenshots.sql +9 -0
  55. package/migrations/045_service_table_rename_and_projection.sql +150 -0
  56. package/migrations/046_service_ops_tables.sql +75 -0
  57. package/migrations/047_update_gfp_manager_skill.sql +16 -0
  58. package/migrations/048_service_migration_skill.sql +23 -0
  59. package/migrations/049_service_features.sql +39 -0
  60. package/migrations/050_service_kpi_action_items.sql +87 -0
  61. package/migrations/051_update_service_migration_skill_prompt.sql +9 -0
  62. package/migrations/052_feature_iteration_link.sql +11 -0
  63. package/migrations/052_services_table_rename.sql +149 -0
  64. package/migrations/053_feature_discovery.sql +47 -0
  65. package/migrations/054_deploy_verification.sql +17 -0
  66. package/migrations/055_feature_lifecycle.sql +37 -0
  67. package/migrations/056_service_incident_schema.sql +16 -0
  68. package/migrations/057_decision_date_to_metadata.ts +173 -0
  69. package/migrations/058_incubator_kb_schema.sql +8 -0
  70. package/migrations/059_incubator_sessions.sql +29 -0
  71. package/migrations/060_skill_reference_files.sql +8 -0
  72. package/migrations/061_cron_trigger_bridge.sql +7 -0
  73. package/migrations/062_bot_slack_profiles.sql +21 -0
  74. package/migrations/063_rename_gfp_skills.sql +7 -0
  75. package/migrations/064_bot_cost_log.sql +11 -0
  76. package/migrations/065_hub_spoke_orchestration.sql +35 -0
  77. package/migrations/066_cost_log_commitment.sql +9 -0
  78. package/migrations/067_module_ontology_type.sql +44 -0
  79. package/migrations/068_migrate_incubator_data.sql +32 -0
  80. package/migrations/069_action_items_sot.sql +156 -0
  81. package/migrations/069_cost_log_tokens.sql +45 -0
  82. package/migrations/070_services_sot_columns.sql +78 -0
  83. package/migrations/071_cleanup_deprecated_service_kb.sql +38 -0
  84. package/migrations/072_incubator_heartbeat.sql +12 -0
  85. package/migrations/072_service_role_schema.sql +57 -0
  86. package/migrations/073_service_type_and_platform.sql +8 -0
  87. package/migrations/074_schema_ref_type.sql +16 -0
  88. package/migrations/075_routing_delegation.sql +34 -0
  89. package/migrations/076_meeting_service_link.sql +5 -0
  90. package/migrations/077_agents_ontology_type.sql +93 -0
  91. package/migrations/078_incubator_agent.sql +45 -0
  92. package/migrations/079_drop_audio_blob_url.sql +4 -0
  93. package/migrations/080_kb_metadata_indexing.sql +9 -0
  94. package/migrations/081_person_unification.sql +41 -0
  95. package/migrations/082_discord_channel.sql +3 -0
  96. package/migrations/084_deprecate_kpi_metrics_table.sql +5 -0
  97. package/migrations/085_meeting_target_domain.sql +37 -0
  98. package/migrations/086_skill_routing_new_skills.sql +23 -0
  99. package/migrations/087_skill_routing_second_opinion.sql +8 -0
  100. package/migrations/088_meeting_notion_sync.sql +10 -0
  101. package/migrations/089_bot_commitments_dedup.sql +17 -0
  102. package/migrations/090_incubator_discord_guild.sql +8 -0
  103. package/migrations/091_drop_deprecated_gfp_kpi_views.sql +65 -0
  104. package/migrations/092_ontology_channel_mapping.sql +28 -0
  105. package/migrations/093_update_bot_workspace_paths.sql +15 -0
  106. package/migrations/094_cron_run_tracking.sql +28 -0
  107. package/migrations/095_drop_bot_cron_runs.sql +7 -0
  108. package/migrations/096_kb_pipeline_keys.sql +66 -0
  109. package/migrations/097_drop_empty_service_tables.sql +83 -0
  110. package/migrations/098_migrate_features_materials_drop.sql +119 -0
  111. package/migrations/099_migrate_sections_drop.sql +52 -0
  112. package/migrations/099_seed_cron_poller_tick.sql +14 -0
  113. package/migrations/100_migrate_services_drop.sql +57 -0
  114. package/migrations/101_drop_services.sql +5 -0
  115. package/migrations/102_cost_log_session_dedup.sql +13 -0
  116. package/migrations/103_board.sql +57 -0
  117. package/migrations/104_agent_factory.sql +284 -0
  118. package/migrations/105_kb_notify.sql +44 -0
  119. package/migrations/106_office_id_agents_commands.sql +73 -0
  120. package/migrations/107_agent_service_credentials.sql +79 -0
  121. package/migrations/108_drop_l2_ontology_seeds.sql +16 -0
  122. package/migrations/109_generic_ontology_descriptions.sql +21 -0
  123. package/migrations/110_kb_manager_skill_l2_cleanup.sql +34 -0
  124. package/migrations/111_drop_l2_team_ontology_seeds.sql +20 -0
  125. package/migrations/112_fix_pipeline_schema_and_role_trigger.sql +83 -0
  126. package/migrations/113_kb_history.sql +53 -0
  127. package/migrations/114_runtime_target_columns.sql +26 -0
  128. package/migrations/115_runtime_source_columns.sql +52 -0
  129. package/migrations/116_commitment_pattern_health.sql +81 -0
  130. package/migrations/117_commitment_pattern_alert_claims.sql +33 -0
  131. package/migrations/118_retire_semiclaw_semicolonybot_alias.sql +30 -0
  132. package/migrations/119_commitment_events.sql +51 -0
  133. package/migrations/120_host_signals.sql +51 -0
  134. package/migrations/121_agent_spec_derived_runtime_metadata.sql +68 -0
  135. package/migrations/122_agent_spec_derived_fk_policy.sql +18 -0
  136. package/migrations/123_agent_spec_projection_skip_targets.sql +24 -0
  137. package/migrations/124_bot_commitments_notify.sql +26 -0
  138. package/migrations/125_agent_personas.sql +36 -0
  139. package/migrations/128_entity_relations.sql +125 -0
  140. package/migrations/129_kb_multitenant_gateway.sql +86 -0
  141. package/migrations/130_jewelry_relation_types.sql +36 -0
  142. package/migrations/131_concierge_requests.sql +38 -0
  143. package/migrations/132_kb_supersession.sql +27 -0
  144. package/migrations/133_kb_claims.sql +52 -0
  145. package/migrations/134_migration_registry.sql +18 -0
  146. package/migrations/135_sc_roles.sql +102 -0
  147. package/migrations/136_runtime_tenant_columns.sql +71 -0
  148. package/migrations/137_runtime_backfill_team_semicolon.sql +131 -0
  149. package/migrations/138_runtime_constraints_not_valid.sql +56 -0
  150. package/migrations/139_tenant_vms.sql +31 -0
  151. package/migrations/140_fleet_slots.sql +23 -0
  152. package/migrations/141_provisioning_jobs.sql +29 -0
  153. package/migrations/142_rls_enable_semicolony.sql +129 -0
  154. package/migrations/143_rls_enable_public.sql +54 -0
  155. package/migrations/144_worker_device_registry.sql +211 -0
  156. package/migrations/145_workspace_profile_workstation_policy.sql +39 -0
  157. package/migrations/146_worker_profile_repo_scope.sql +24 -0
  158. package/migrations/147_worker_profile_semo_ops_repo_rename.sql +18 -0
  159. package/migrations/148_momo_runtime_convergence.sql +92 -0
  160. package/migrations/README.md +88 -0
  161. package/migrations-sqlite/kb/001_initial.sql +56 -0
  162. package/migrations-sqlite/ops/001_initial.sql +53 -0
  163. package/migrations-sqlite/ops/002_action_items.sql +48 -0
  164. package/package.json +78 -0
@@ -0,0 +1,41 @@
1
+ -- 076_person_unification.sql
2
+ -- team 타입과 person 타입을 person으로 통합
3
+
4
+ BEGIN;
5
+
6
+ -- 1. person 스키마에 team 전용 키 추가
7
+ INSERT INTO semicolony.kb_type_schema (type_key, scheme_key, key_type, required, scheme_description, value_hint)
8
+ VALUES
9
+ ('person', 'nickname', 'singleton', false, '닉네임', '팀 내 닉네임'),
10
+ ('person', 'slack-id', 'singleton', false, 'Slack ID', 'Slack User ID (e.g. URSQYUNQJ)'),
11
+ ('person', 'discord-id', 'singleton', false, 'Discord ID', 'Discord User ID (snowflake)'),
12
+ ('person', 'tech-stack', 'singleton', false, '기술스택', '주요 기술/언어/프레임워크'),
13
+ ('person', 'career', 'singleton', false, '경력', '이전 회사, 경력 요약'),
14
+ ('person', 'compensation', 'singleton', false, 'compensation', '보상 구조'),
15
+ ('person', 'dm-channel', 'singleton', false, 'dm-channel', 'Slack DM channel ID'),
16
+ ('person', 'communication-profile', 'singleton', false,
17
+ '봇 커뮤니케이션 프로필 — 기술수준, 접근권한, 톤 지시',
18
+ 'YAML: tech_level, access_level, dri_scope, comm_style, language')
19
+ ON CONFLICT (type_key, scheme_key) DO NOTHING;
20
+
21
+ -- 2. person의 role을 collection으로 변경
22
+ UPDATE semicolony.kb_type_schema SET key_type = 'collection',
23
+ value_hint = 'role/overall, role/{project-domain} 등 프로젝트별 역할'
24
+ WHERE type_key = 'person' AND scheme_key = 'role';
25
+
26
+ -- 3. ontology: team → person 타입 변경
27
+ UPDATE semicolony.ontology SET entity_type = 'person' WHERE entity_type = 'team';
28
+
29
+ -- 4. 기존 person 도메인에 organization 키 placeholder 삽입 (인스턴스별 수동 채움)
30
+ -- 세미콜론 내부 backfill 은 이 migration 이 처음 적용된 시점에 한해 효력 발휘.
31
+ -- OSS fresh install: 이 시점에 person 도메인 자체가 0건이므로 no-op.
32
+ -- (이전 버전은 세미콜론 팀원 13명 닉네임을 IN 절에 포함했으나 누출 방지를 위해 제거됨.)
33
+ -- organization 키 값은 인스턴스가 직접 채워야 한다 — 자동 시드하지 않음.
34
+
35
+ -- 5. team 타입 스키마 제거
36
+ DELETE FROM semicolony.kb_type_schema WHERE type_key = 'team';
37
+
38
+ -- 6. ontology_types에서 team 제거
39
+ DELETE FROM semicolony.ontology_types WHERE type_key = 'team';
40
+
41
+ COMMIT;
@@ -0,0 +1,3 @@
1
+ -- 082_discord_channel.sql
2
+ -- services 테이블에 discord_channel 컬럼 추가 (멀티플랫폼 채널→서비스 매핑)
3
+ ALTER TABLE semicolony.services ADD COLUMN IF NOT EXISTS discord_channel TEXT;
@@ -0,0 +1,5 @@
1
+ -- Phase 3: service_kpi_metrics 테이블 deprecated 마킹
2
+ -- KPI 데이터는 KB metadata가 SoT. 이 테이블은 historical fallback 전용.
3
+ -- 전환 완료 후 (1개월 검증) 별도 마이그레이션으로 DROP 예정.
4
+
5
+ COMMENT ON TABLE semicolony.service_kpi_metrics IS 'DEPRECATED: KPI data now lives in KB metadata (key=kpi, sub_key=YYYY-MM-DD). This table is retained for historical fallback reads only. Do not insert new records.';
@@ -0,0 +1,37 @@
1
+ -- Phase 6: meetings.service_id → target_domain 전환
2
+ -- service_id FK를 ontology domain 기반 target_domain으로 통일 (action_items 패턴과 동일)
3
+
4
+ -- Step 1: target_domain 컬럼 추가
5
+ ALTER TABLE semicolony.meetings
6
+ ADD COLUMN IF NOT EXISTS target_domain VARCHAR(100)
7
+ REFERENCES semicolony.ontology(domain);
8
+
9
+ -- Step 2: 기존 service_id → target_domain 데이터 마이그레이션
10
+ -- ontology에 등록된 도메인만 FK 위반 없이 안전하게 마이그레이션
11
+ UPDATE semicolony.meetings m
12
+ SET target_domain = s.service_domain
13
+ FROM semicolony.services s
14
+ JOIN semicolony.ontology o ON o.domain = s.service_domain
15
+ WHERE m.service_id = s.service_id
16
+ AND m.service_id IS NOT NULL
17
+ AND s.service_domain IS NOT NULL
18
+ AND m.target_domain IS NULL;
19
+
20
+ -- Step 2.5: 마이그레이션 후 누락 행 검증
21
+ DO $$
22
+ DECLARE unmigrated INT;
23
+ BEGIN
24
+ SELECT COUNT(*) INTO unmigrated
25
+ FROM semicolony.meetings
26
+ WHERE service_id IS NOT NULL AND target_domain IS NULL;
27
+ IF unmigrated > 0 THEN
28
+ RAISE WARNING '% meetings have service_id but no target_domain (service_domain missing from ontology)', unmigrated;
29
+ END IF;
30
+ END $$;
31
+
32
+ -- Step 3: service_id는 당분간 유지 (하위호환)
33
+ COMMENT ON COLUMN semicolony.meetings.service_id IS 'DEPRECATED: Use target_domain instead. Will be removed after migration period.';
34
+
35
+ -- Step 4: 인덱스
36
+ CREATE INDEX IF NOT EXISTS idx_meetings_target_domain ON semicolony.meetings(target_domain)
37
+ WHERE target_domain IS NOT NULL;
@@ -0,0 +1,23 @@
1
+ -- 086: 신규 스킬 6종 라우팅 등록
2
+
3
+ INSERT INTO semicolony.bot_delegation (from_bot_id, to_bot_id, delegation_type, domains, method, metadata)
4
+ VALUES
5
+ ('orchestrator', 'reviewclaw', 'skill-routing',
6
+ ARRAY['/security-scan','보안 스캔','security scan','취약점 점검','OWASP','시크릿 스캔','secret scan','npm audit'],
7
+ 'keyword', '{"skill":"security-scan","label":"security-scan"}'),
8
+ ('orchestrator', 'reviewclaw', 'skill-routing',
9
+ ARRAY['테스트 생성','test generate','테스트 만들어','add tests','missing tests','테스트 커버리지'],
10
+ 'keyword', '{"skill":"test-generator","label":"test-generator"}'),
11
+ ('orchestrator', 'designclaw', 'skill-routing',
12
+ ARRAY['visual qa','visual-qa','/visual-qa','시각 QA','레이아웃 점검','UI 검증','스크린샷 비교','visual regression'],
13
+ 'keyword', '{"skill":"visual-qa","label":"visual-qa"}'),
14
+ ('orchestrator', 'growthclaw', 'skill-routing',
15
+ ARRAY['/seo-check','seo crawl','seo 크롤링','메타태그 점검','on-page seo','heading 점검','sitemap 확인'],
16
+ 'keyword', '{"skill":"seo-crawl","label":"seo-crawl"}'),
17
+ ('orchestrator', 'infraclaw', 'skill-routing',
18
+ ARRAY['/canary-check','canary check','배포 후 점검','post-deploy check','카나리 체크','서비스 헬스체크','deploy health'],
19
+ 'keyword', '{"skill":"canary-monitor","label":"canary-monitor"}'),
20
+ ('orchestrator', 'workclaw', 'skill-routing',
21
+ ARRAY['migration verify','마이그레이션 검증','migration 검증','migration check','마이그레이션 점검','db migration'],
22
+ 'keyword', '{"skill":"migration-verify","label":"migration-verify"}')
23
+ ON CONFLICT DO NOTHING;
@@ -0,0 +1,8 @@
1
+ -- 087: second-opinion 스킬 라우팅 등록
2
+
3
+ INSERT INTO semicolony.bot_delegation (from_bot_id, to_bot_id, delegation_type, domains, method, metadata)
4
+ VALUES
5
+ ('orchestrator', 'reviewclaw', 'skill-routing',
6
+ ARRAY['second-opinion','세컨드 오피니언','cross-model','교차 검증','/second-opinion'],
7
+ 'keyword', '{"skill":"second-opinion","label":"second-opinion"}')
8
+ ON CONFLICT DO NOTHING;
@@ -0,0 +1,10 @@
1
+ ALTER TABLE semicolony.meetings
2
+ ADD COLUMN IF NOT EXISTS notion_page_id VARCHAR(100),
3
+ ADD COLUMN IF NOT EXISTS notion_url TEXT,
4
+ ADD COLUMN IF NOT EXISTS notion_sync_status VARCHAR(20) DEFAULT 'pending'
5
+ CHECK (notion_sync_status IN ('pending', 'synced', 'failed', 'skipped')),
6
+ ADD COLUMN IF NOT EXISTS notion_sync_error TEXT;
7
+
8
+ CREATE INDEX IF NOT EXISTS idx_meetings_notion_sync
9
+ ON semicolony.meetings(notion_sync_status)
10
+ WHERE notion_sync_status = 'failed';
@@ -0,0 +1,17 @@
1
+ -- 089: bot_commitments dedup guard
2
+ -- 동일한 Slack 이벤트(msg.ts)가 여러 slack-router 인스턴스에 의해 중복 dispatch되는
3
+ -- 경우 commitment이 중복 생성되는 것을 DB 레벨에서 차단한다.
4
+ --
5
+ -- 배경: 4/12-13 period에 slack-router가 2개 동시 실행되어 거의 모든 commitment이
6
+ -- 15~20초 간격으로 짝을 이뤄 중복 생성됨. 루터 정리로 즉발은 해결됐으나 재발 방지를
7
+ -- 위해 DB 유니크 제약을 추가한다.
8
+ --
9
+ -- 키: (bot_id, pipeline_context->>'slack_event_id')
10
+ -- 조건: status IN ('pending','active') AND pipeline_context ? 'slack_event_id'
11
+ -- - done/failed 행은 포함하지 않아 재처리(재시도)는 허용
12
+ -- - slack_event_id가 없는 로컬/크론/github 경로는 영향 없음
13
+
14
+ CREATE UNIQUE INDEX IF NOT EXISTS bot_commitments_slack_event_uniq
15
+ ON semicolony.bot_commitments (bot_id, (pipeline_context->>'slack_event_id'))
16
+ WHERE status IN ('pending', 'active')
17
+ AND pipeline_context ? 'slack_event_id';
@@ -0,0 +1,8 @@
1
+ -- 090_incubator_discord_guild.sql
2
+ -- incubator_sessions에 discord_guild 컬럼 추가 (Discord guild 단위 incubator 라우팅)
3
+ ALTER TABLE semicolony.incubator_sessions ADD COLUMN IF NOT EXISTS discord_guild TEXT;
4
+
5
+ -- SEMICOLONY Incubator 1기 서버 매핑
6
+ UPDATE semicolony.incubator_sessions
7
+ SET discord_guild = '1493481703002476658'
8
+ WHERE status = 'active' AND discord_guild IS NULL;
@@ -0,0 +1,65 @@
1
+ -- 091: 레거시 호환 VIEW + deprecated 테이블/컬럼 최종 제거
2
+ --
3
+ -- 선행 조건:
4
+ -- 045/052: gfp_* → service_* 테이블 리네임 + 호환 VIEW 생성
5
+ -- 069: action_items 리네임 + service_action_items VIEW 생성
6
+ -- 084: service_kpi_metrics deprecated (KB metadata가 SoT)
7
+ -- 085: meetings.service_id deprecated (target_domain 대체)
8
+ --
9
+ -- 코드 참조 제거 확인:
10
+ -- - gfp_* VIEW: 코드 참조 0건 (service_* 직접 참조로 전환)
11
+ -- - service_kpi_metrics: DB fallback 코드 제거됨 (KB-only)
12
+ -- - meetings.service_id: 코드에서 완전 제거됨
13
+ --
14
+ -- 사전 백업 권장:
15
+ -- pg_dump --table=semicolony.service_kpi_metrics -f backup_kpi_metrics.sql
16
+
17
+ BEGIN;
18
+
19
+ -- ── Guard: meetings.service_id 마이그레이션 완료 확인 (멱등) ──
20
+ DO $$
21
+ DECLARE
22
+ col_exists BOOLEAN;
23
+ unmigrated INT;
24
+ BEGIN
25
+ SELECT EXISTS (
26
+ SELECT 1 FROM information_schema.columns
27
+ WHERE table_schema = 'semicolony' AND table_name = 'meetings' AND column_name = 'service_id'
28
+ ) INTO col_exists;
29
+
30
+ IF NOT col_exists THEN
31
+ RAISE NOTICE 'service_id column already dropped — skipping guard';
32
+ RETURN;
33
+ END IF;
34
+
35
+ SELECT COUNT(*) INTO unmigrated
36
+ FROM semicolony.meetings
37
+ WHERE service_id IS NOT NULL AND target_domain IS NULL;
38
+ IF unmigrated > 0 THEN
39
+ RAISE EXCEPTION '% meetings still have service_id without target_domain — run 085 data migration first', unmigrated;
40
+ END IF;
41
+ END $$;
42
+
43
+ -- ── 1. 레거시 호환 VIEW 제거 (의존 순서 준수) ──
44
+
45
+ -- gfp_projects가 service_projects를 참조하므로 먼저 제거
46
+ DROP VIEW IF EXISTS semicolony.gfp_projects CASCADE;
47
+ DROP VIEW IF EXISTS semicolony.gfp_phase_sections CASCADE;
48
+ DROP VIEW IF EXISTS semicolony.gfp_materials CASCADE;
49
+ DROP VIEW IF EXISTS semicolony.gfp_research_tasks CASCADE;
50
+ DROP VIEW IF EXISTS semicolony.gfp_infra_requests CASCADE;
51
+
52
+ -- gfp_projects 제거 후 안전하게 제거 가능
53
+ DROP VIEW IF EXISTS semicolony.service_projects CASCADE;
54
+ DROP VIEW IF EXISTS semicolony.service_action_items CASCADE;
55
+
56
+ -- ── 2. service_kpi_metrics 테이블 제거 ──
57
+ -- leaf 테이블 (incoming FK 없음). 인덱스 2개 + 트리거 자동 제거.
58
+ DROP TABLE IF EXISTS semicolony.service_kpi_metrics;
59
+
60
+ -- ── 3. meetings.service_id 컬럼 제거 ──
61
+ DROP INDEX IF EXISTS semicolony.idx_meetings_service;
62
+ ALTER TABLE semicolony.meetings DROP CONSTRAINT IF EXISTS meetings_service_id_fkey;
63
+ ALTER TABLE semicolony.meetings DROP COLUMN IF EXISTS service_id;
64
+
65
+ COMMIT;
@@ -0,0 +1,28 @@
1
+ -- 092: ontology 채널 매핑 컬럼 추가
2
+ --
3
+ -- Orchestrator 라우터가 services 테이블 대신 ontology에서 직접 채널→도메인 매핑을 조회.
4
+ -- 비-서비스 도메인(organization, team, module 등)도 채널 라우팅 가능.
5
+ --
6
+ -- 선행 조건:
7
+ -- 070: services.slack_channel 컬럼 존재
8
+ -- 082: services.discord_channel 컬럼 존재
9
+
10
+ -- Step 1: ontology에 채널 컬럼 추가
11
+ ALTER TABLE semicolony.ontology
12
+ ADD COLUMN IF NOT EXISTS slack_channel TEXT,
13
+ ADD COLUMN IF NOT EXISTS discord_channel TEXT;
14
+
15
+ -- Step 2: 라우터 성능용 부분 인덱스
16
+ CREATE INDEX IF NOT EXISTS idx_ontology_slack_channel
17
+ ON semicolony.ontology (slack_channel) WHERE slack_channel IS NOT NULL;
18
+ CREATE INDEX IF NOT EXISTS idx_ontology_discord_channel
19
+ ON semicolony.ontology (discord_channel) WHERE discord_channel IS NOT NULL;
20
+
21
+ -- Step 3: services → ontology 기존 매핑 역동기화
22
+ UPDATE semicolony.ontology o
23
+ SET slack_channel = s.slack_channel,
24
+ discord_channel = s.discord_channel
25
+ FROM semicolony.services s
26
+ WHERE o.domain = s.service_domain
27
+ AND o.entity_type IN ('service', 'platform', 'module')
28
+ AND (s.slack_channel IS NOT NULL OR s.discord_channel IS NOT NULL);
@@ -0,0 +1,15 @@
1
+ -- 093: bot_status.workspace_path 레거시 경로 정리
2
+ --
3
+ -- Migration 078 (incubator_agent) 및 초기 시드가 workspace_path를
4
+ -- '~/.semicolony-bot-sessions/{bot}' 또는 '~/.openclaw-{bot}/workspace'로 기록.
5
+ -- 2026-04-15 봇 워크스페이스 통합 이후 SoT는 '~/.semicolony/workspaces/{bot}'.
6
+ -- `semicolony bots sync`로 자동 반영되는 봇은 이미 갱신됐으나, sync 경로를 타지 않는
7
+ -- 봇(incubator 등)은 수동 정리가 필요하다.
8
+
9
+ UPDATE semicolony.bot_status
10
+ SET workspace_path = '~/.semicolony/workspaces/' || bot_id
11
+ WHERE workspace_path LIKE '~/.semicolony-bot-sessions/%'
12
+ OR workspace_path LIKE '~/.openclaw-%'
13
+ OR workspace_path LIKE '/Users/%/.openclaw-%'
14
+ OR workspace_path LIKE '/Users/%/.semicolony-bot-sessions/%'
15
+ OR workspace_path LIKE '/Users/%/.semicolony/workspaces/%';
@@ -0,0 +1,28 @@
1
+ -- 062: Cron Run Tracking
2
+ -- bot_cron_jobs에 실행 결과 rollup 3컬럼 추가 + 신규 append-only bot_cron_runs 이력 테이블
3
+
4
+ ALTER TABLE semicolony.bot_cron_jobs
5
+ ADD COLUMN IF NOT EXISTS last_status TEXT,
6
+ ADD COLUMN IF NOT EXISTS last_error TEXT,
7
+ ADD COLUMN IF NOT EXISTS consecutive_failures INTEGER NOT NULL DEFAULT 0;
8
+
9
+ CREATE TABLE IF NOT EXISTS semicolony.bot_cron_runs (
10
+ id BIGSERIAL PRIMARY KEY,
11
+ bot_id TEXT NOT NULL,
12
+ job_id TEXT NOT NULL,
13
+ started_at TIMESTAMPTZ NOT NULL,
14
+ finished_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
15
+ duration_ms INTEGER,
16
+ status TEXT NOT NULL CHECK (status IN ('success','failure','timeout','skipped')),
17
+ error TEXT,
18
+ output_digest TEXT,
19
+ session_owner TEXT,
20
+ metadata JSONB NOT NULL DEFAULT '{}'::jsonb
21
+ );
22
+
23
+ CREATE INDEX IF NOT EXISTS idx_bot_cron_runs_job
24
+ ON semicolony.bot_cron_runs(bot_id, job_id, started_at DESC);
25
+
26
+ CREATE INDEX IF NOT EXISTS idx_bot_cron_runs_failure
27
+ ON semicolony.bot_cron_runs(status, started_at DESC)
28
+ WHERE status IN ('failure','timeout');
@@ -0,0 +1,7 @@
1
+ -- 095: bot_cron_runs 폐기 (commitment 통합)
2
+ -- cron 실행은 bot_commitments(source_type='cron')으로 기록한다.
3
+ -- bot_cron_jobs.last_status / last_error / consecutive_failures rollup 컬럼은 유지.
4
+
5
+ DROP INDEX IF EXISTS semicolony.idx_bot_cron_runs_failure;
6
+ DROP INDEX IF EXISTS semicolony.idx_bot_cron_runs_job;
7
+ DROP TABLE IF EXISTS semicolony.bot_cron_runs;
@@ -0,0 +1,66 @@
1
+ -- 096: KB pipeline keys 등록
2
+ -- service_* 테이블 → KB 이식을 위한 type schema 키 등록
3
+ -- Phase 0: 선행 조건 — 새 KB 키들을 service 타입에 등록
4
+
5
+ -- 파이프라인 워크플로우 키 (PM 파이프라인이 관리)
6
+ INSERT INTO semicolony.kb_type_schema (type_key, scheme_key, scheme_description, required, value_hint, sort_order, key_type, source)
7
+ VALUES
8
+ ('service', 'pipeline', '프로젝트 파이프라인 워크플로우 상태 (phase, lifecycle, preset 등)', false,
9
+ 'metadata: {current_phase, infra_phase, lifecycle, service_type, parent_domain, preset, design_step}',
10
+ 30, 'singleton', 'pipeline'),
11
+ ('service', 'section', '파이프라인 섹션 (phase별 콘텐츠 블록)', false,
12
+ 'sub_key: {track}/{phase}/{section_key}. metadata: {title, ordinal, status, source, qa_items}',
13
+ 31, 'collection', 'pipeline'),
14
+ ('service', 'material', '프로젝트 산출물 (기획서, 디자인 export 등)', false,
15
+ 'sub_key: {material_id}. metadata: {material_type, phase_mapping}',
16
+ 32, 'collection', 'manual'),
17
+ ('service', 'feature', '서비스 기능 카탈로그', false,
18
+ 'sub_key: {feature_slug}. metadata: {name, category, status, spec}',
19
+ 33, 'collection', 'pipeline'),
20
+ ('service', 'iteration', '운영 이터레이션 (스프린트)', false,
21
+ 'sub_key: {title_slug}. metadata: {status, goal, started_at, completed_at}',
22
+ 34, 'collection', 'manual'),
23
+ ('service', 'incident', '운영 인시던트', false,
24
+ 'sub_key: {YYYY-MM-DD}/{title_slug}. metadata: {severity, status, root_cause, resolution}',
25
+ 35, 'collection', 'manual'),
26
+ ('service', 'infra-request', '인프라 요청', false,
27
+ 'sub_key: {request_id}. metadata: {category, priority, status}',
28
+ 36, 'collection', 'pipeline'),
29
+ ('service', 'research', '리서치 태스크', false,
30
+ 'sub_key: {task_id}. metadata: {task_type, status, reference_urls}',
31
+ 37, 'collection', 'pipeline'),
32
+ ('service', 'session', '파이프라인 대화 세션 (discovery, conversation)', false,
33
+ 'sub_key: {type}/{session_id}. metadata: 세션 상태',
34
+ 38, 'collection', 'pipeline'),
35
+ ('service', 'deploy-verify', '배포 검증 결과', false,
36
+ 'sub_key: {infra_phase}/{YYYY-MM-DD}. metadata: {checks, overall_status, verified_by}',
37
+ 39, 'collection', 'pipeline')
38
+ ON CONFLICT (type_key, scheme_key) DO UPDATE SET
39
+ scheme_description = EXCLUDED.scheme_description,
40
+ value_hint = EXCLUDED.value_hint,
41
+ sort_order = EXCLUDED.sort_order,
42
+ key_type = EXCLUDED.key_type,
43
+ source = EXCLUDED.source;
44
+
45
+ -- source 컬럼에 'pipeline' 값 허용 (기존: 'manual', 'projection')
46
+ -- CHECK 제약이 있을 경우 대비 — 없으면 무시됨
47
+ DO $$
48
+ DECLARE
49
+ _conname text;
50
+ BEGIN
51
+ -- kb_type_schema.source 컬럼에 CHECK 제약이 있는지 확인 후 'pipeline' 허용으로 확장
52
+ SELECT c.conname INTO _conname
53
+ FROM pg_constraint c
54
+ JOIN pg_class t ON c.conrelid = t.oid
55
+ JOIN pg_namespace n ON t.relnamespace = n.oid
56
+ WHERE n.nspname = 'semicolony' AND t.relname = 'kb_type_schema'
57
+ AND c.contype = 'c' AND c.conname LIKE '%source%'
58
+ LIMIT 1;
59
+
60
+ IF _conname IS NOT NULL THEN
61
+ EXECUTE format('ALTER TABLE semicolony.kb_type_schema DROP CONSTRAINT %I', _conname);
62
+ ALTER TABLE semicolony.kb_type_schema
63
+ ADD CONSTRAINT kb_type_schema_source_check
64
+ CHECK (source IN ('manual', 'projection', 'pipeline'));
65
+ END IF;
66
+ END $$;
@@ -0,0 +1,83 @@
1
+ -- 097: Phase 1 — 빈/최소 데이터 service 테이블 6개 DROP
2
+ -- service_iterations (0건), service_incidents (0건), service_infra_requests (0건),
3
+ -- service_research_tasks (0건), feature_discovery_sessions (1건), feature_conversation_sessions (1건)
4
+ -- 코드는 이미 KB 기반으로 전환됨
5
+
6
+ -- 1. FK 제약 제거: action_items.iteration_id → service_iterations
7
+ ALTER TABLE semicolony.action_items DROP COLUMN IF EXISTS iteration_id;
8
+
9
+ -- 2. FK 제약 제거: service_features.iteration_id → service_iterations
10
+ ALTER TABLE semicolony.service_features DROP COLUMN IF EXISTS iteration_id;
11
+
12
+ -- 3. FK 제약 제거: service_sections.iteration_id (있을 경우)
13
+ ALTER TABLE semicolony.service_sections DROP COLUMN IF EXISTS iteration_id;
14
+
15
+ -- 4. 기존 세션 데이터 KB로 이식 (1건씩)
16
+ -- feature_discovery_sessions → KB session/discovery/{session_id}
17
+ DO $$
18
+ DECLARE
19
+ rec RECORD;
20
+ _domain TEXT;
21
+ BEGIN
22
+ FOR rec IN SELECT * FROM semicolony.feature_discovery_sessions LOOP
23
+ SELECT service_domain INTO _domain FROM semicolony.services WHERE service_id = rec.service_id;
24
+ IF _domain IS NOT NULL THEN
25
+ INSERT INTO semicolony.knowledge_base (domain, key, sub_key, content, created_by, metadata)
26
+ VALUES (
27
+ _domain, 'session', 'discovery/' || rec.session_id,
28
+ COALESCE(rec.source_url, ''),
29
+ 'migration-097',
30
+ jsonb_build_object(
31
+ 'session_id', rec.session_id,
32
+ 'service_id', rec.service_id,
33
+ 'source_url', rec.source_url,
34
+ 'status', rec.status,
35
+ 'candidates', rec.candidates,
36
+ 'confirmed', rec.confirmed,
37
+ 'screenshots', rec.screenshots,
38
+ 'error', rec.error,
39
+ 'created_at', rec.created_at::text
40
+ )
41
+ )
42
+ ON CONFLICT (domain, key, sub_key) DO NOTHING;
43
+ END IF;
44
+ END LOOP;
45
+ END $$;
46
+
47
+ -- feature_conversation_sessions → KB session/conversation/{session_id}
48
+ DO $$
49
+ DECLARE
50
+ rec RECORD;
51
+ _domain TEXT;
52
+ BEGIN
53
+ FOR rec IN SELECT * FROM semicolony.feature_conversation_sessions LOOP
54
+ SELECT service_domain INTO _domain FROM semicolony.services WHERE service_id = rec.service_id;
55
+ IF _domain IS NOT NULL THEN
56
+ INSERT INTO semicolony.knowledge_base (domain, key, sub_key, content, created_by, metadata)
57
+ VALUES (
58
+ _domain, 'session', 'conversation/' || rec.session_id,
59
+ '',
60
+ 'migration-097',
61
+ jsonb_build_object(
62
+ 'session_id', rec.session_id,
63
+ 'service_id', rec.service_id,
64
+ 'mode', rec.mode,
65
+ 'status', rec.status,
66
+ 'features', rec.features,
67
+ 'slack_channel', rec.slack_channel,
68
+ 'slack_thread_ts', rec.slack_thread_ts,
69
+ 'created_at', rec.created_at::text
70
+ )
71
+ )
72
+ ON CONFLICT (domain, key, sub_key) DO NOTHING;
73
+ END IF;
74
+ END LOOP;
75
+ END $$;
76
+
77
+ -- 5. 테이블 DROP (자식 테이블 먼저)
78
+ DROP TABLE IF EXISTS semicolony.feature_discovery_sessions CASCADE;
79
+ DROP TABLE IF EXISTS semicolony.feature_conversation_sessions CASCADE;
80
+ DROP TABLE IF EXISTS semicolony.service_research_tasks CASCADE;
81
+ DROP TABLE IF EXISTS semicolony.service_infra_requests CASCADE;
82
+ DROP TABLE IF EXISTS semicolony.service_incidents CASCADE;
83
+ DROP TABLE IF EXISTS semicolony.service_iterations CASCADE;
@@ -0,0 +1,119 @@
1
+ -- 098: Phase 2 — service_features(6건), service_materials(33건), 부수 3개 테이블 KB 이식 + DROP
2
+ -- 코드는 이미 KB 기반으로 전환됨 (Phase 2a+2b)
3
+
4
+ -- 1. service_features (6건) → KB feature/{feature_id}
5
+ -- drift 복구: 이미 DROP 된 소스 테이블은 to_regclass 가드로 skip (2026-04-23)
6
+ DO $$
7
+ DECLARE
8
+ rec RECORD;
9
+ _domain TEXT;
10
+ _slug TEXT;
11
+ BEGIN
12
+ IF to_regclass('semicolony.service_features') IS NULL OR to_regclass('semicolony.services') IS NULL THEN
13
+ RAISE NOTICE '098-1 skipped: source tables already dropped';
14
+ RETURN;
15
+ END IF;
16
+ FOR rec IN SELECT * FROM semicolony.service_features LOOP
17
+ SELECT service_domain INTO _domain FROM semicolony.services WHERE service_id = rec.service_id;
18
+ IF _domain IS NOT NULL THEN
19
+ _slug := lower(regexp_replace(rec.name, '[^a-zA-Z0-9가-힣]+', '-', 'g'));
20
+ _slug := regexp_replace(_slug, '^-|-$', '', 'g');
21
+ IF _slug = '' THEN _slug := left(rec.feature_id::text, 8); END IF;
22
+
23
+ INSERT INTO semicolony.knowledge_base (domain, key, sub_key, content, created_by, metadata)
24
+ VALUES (
25
+ _domain, 'feature', rec.feature_id::text,
26
+ COALESCE(rec.description, ''),
27
+ 'migration-098',
28
+ jsonb_build_object(
29
+ 'feature_id', rec.feature_id,
30
+ 'slug', _slug,
31
+ 'service_id', rec.service_id,
32
+ 'name', rec.name,
33
+ 'category', COALESCE(rec.category, 'core'),
34
+ 'status', COALESCE(rec.status, 'active'),
35
+ 'parent_id', rec.parent_id,
36
+ 'sort_order', COALESCE(rec.sort_order, 0),
37
+ 'feature_metadata', COALESCE(rec.metadata, '{}'::jsonb),
38
+ 'created_at', rec.created_at::text
39
+ )
40
+ )
41
+ ON CONFLICT (domain, key, sub_key) DO NOTHING;
42
+ END IF;
43
+ END LOOP;
44
+ END $$;
45
+
46
+ -- 2. service_materials (33건) → KB material/{material_id}
47
+ DO $$
48
+ DECLARE
49
+ rec RECORD;
50
+ _domain TEXT;
51
+ BEGIN
52
+ IF to_regclass('semicolony.service_materials') IS NULL OR to_regclass('semicolony.services') IS NULL THEN
53
+ RAISE NOTICE '098-2 skipped: source tables already dropped';
54
+ RETURN;
55
+ END IF;
56
+ FOR rec IN SELECT * FROM semicolony.service_materials LOOP
57
+ SELECT service_domain INTO _domain FROM semicolony.services WHERE service_id = rec.service_id;
58
+ IF _domain IS NOT NULL THEN
59
+ INSERT INTO semicolony.knowledge_base (domain, key, sub_key, content, created_by, metadata)
60
+ VALUES (
61
+ _domain, 'material', rec.material_id::text,
62
+ COALESCE(rec.content, ''),
63
+ 'migration-098',
64
+ jsonb_build_object(
65
+ 'material_id', rec.material_id,
66
+ 'service_id', rec.service_id,
67
+ 'material_type', COALESCE(rec.material_type, 'planning-doc'),
68
+ 'phase_mapping', COALESCE(rec.phase_mapping, 'null'::jsonb),
69
+ 'screenshot_data', rec.screenshot_data,
70
+ 'stitch_share_url', rec.stitch_share_url,
71
+ 'created_at', rec.created_at::text
72
+ )
73
+ )
74
+ ON CONFLICT (domain, key, sub_key) DO NOTHING;
75
+ END IF;
76
+ END LOOP;
77
+ END $$;
78
+
79
+ -- 3. deploy_verifications → KB deploy-verify/{infra_phase}/{date}
80
+ DO $$
81
+ DECLARE
82
+ rec RECORD;
83
+ _domain TEXT;
84
+ _date TEXT;
85
+ BEGIN
86
+ IF to_regclass('semicolony.deploy_verifications') IS NULL OR to_regclass('semicolony.services') IS NULL THEN
87
+ RAISE NOTICE '098-3 skipped: source tables already dropped';
88
+ RETURN;
89
+ END IF;
90
+ FOR rec IN SELECT * FROM semicolony.deploy_verifications LOOP
91
+ SELECT service_domain INTO _domain FROM semicolony.services WHERE service_id = rec.service_id;
92
+ IF _domain IS NOT NULL THEN
93
+ _date := to_char(rec.created_at, 'YYYY-MM-DD');
94
+ INSERT INTO semicolony.knowledge_base (domain, key, sub_key, content, created_by, metadata)
95
+ VALUES (
96
+ _domain, 'deploy-verify', rec.infra_phase || '/' || _date,
97
+ '',
98
+ 'migration-098',
99
+ jsonb_build_object(
100
+ 'verification_id', rec.verification_id,
101
+ 'service_id', rec.service_id,
102
+ 'infra_phase', rec.infra_phase,
103
+ 'checks', rec.checks,
104
+ 'overall_status', rec.overall_status,
105
+ 'verified_by', rec.verified_by,
106
+ 'created_at', rec.created_at::text
107
+ )
108
+ )
109
+ ON CONFLICT (domain, key, sub_key) DO NOTHING;
110
+ END IF;
111
+ END LOOP;
112
+ END $$;
113
+
114
+ -- 4. 테이블 DROP
115
+ DROP TABLE IF EXISTS semicolony.feature_status_transitions CASCADE;
116
+ DROP TABLE IF EXISTS semicolony.deploy_verifications CASCADE;
117
+ DROP TABLE IF EXISTS semicolony.service_kpi_metrics CASCADE;
118
+ DROP TABLE IF EXISTS semicolony.service_features CASCADE;
119
+ DROP TABLE IF EXISTS semicolony.service_materials CASCADE;
@@ -0,0 +1,52 @@
1
+ -- 099: Phase 3 — service_sections (141건) KB 이식 + DROP
2
+ -- 코드는 이미 KB 기반으로 전환됨 (section/{track}/{phase}/{section_key})
3
+
4
+ -- 1. service_sections → KB section/{track}/{phase}/{section_key}
5
+ -- drift 복구: 이미 DROP 된 소스 테이블은 to_regclass 가드로 skip (2026-04-23)
6
+ DO $$
7
+ DECLARE
8
+ rec RECORD;
9
+ _domain TEXT;
10
+ _sub_key TEXT;
11
+ BEGIN
12
+ IF to_regclass('semicolony.service_sections') IS NULL OR to_regclass('semicolony.services') IS NULL THEN
13
+ RAISE NOTICE '099-1 skipped: source tables already dropped';
14
+ RETURN;
15
+ END IF;
16
+ FOR rec IN SELECT * FROM semicolony.service_sections LOOP
17
+ SELECT service_domain INTO _domain FROM semicolony.services WHERE service_id = rec.service_id;
18
+ IF _domain IS NOT NULL THEN
19
+ _sub_key := COALESCE(rec.track, 'plan') || '/' || rec.phase || '/' || rec.section_key;
20
+
21
+ INSERT INTO semicolony.knowledge_base (domain, key, sub_key, content, created_by, metadata)
22
+ VALUES (
23
+ _domain, 'section', _sub_key,
24
+ COALESCE(rec.content, ''),
25
+ 'migration-099',
26
+ jsonb_build_object(
27
+ 'section_id', _sub_key,
28
+ 'service_id', rec.service_id,
29
+ 'phase', rec.phase,
30
+ 'track', COALESCE(rec.track, 'plan'),
31
+ 'section_key', rec.section_key,
32
+ 'title', COALESCE(rec.title, ''),
33
+ 'ordinal', COALESCE(rec.ordinal, 0),
34
+ 'status', COALESCE(rec.status, 'draft'),
35
+ 'source', COALESCE(rec.source, 'manual'),
36
+ 'reviewer_note', rec.reviewer_note,
37
+ 'qa_items', COALESCE(rec.qa_items, 'null'::jsonb),
38
+ 'slack_thread_ts', rec.slack_thread_ts,
39
+ 'kb_written_at', rec.kb_written_at::text,
40
+ 'created_at', rec.created_at::text
41
+ )
42
+ )
43
+ ON CONFLICT (domain, key, sub_key) DO UPDATE SET
44
+ content = EXCLUDED.content,
45
+ metadata = EXCLUDED.metadata,
46
+ updated_at = NOW();
47
+ END IF;
48
+ END LOOP;
49
+ END $$;
50
+
51
+ -- 2. 테이블 DROP
52
+ DROP TABLE IF EXISTS semicolony.service_sections CASCADE;
@@ -0,0 +1,14 @@
1
+ -- 099: cron-poller-tick 메타 잡 seed
2
+ -- Phase 1 폴러가 매 분 자기 heartbeat 을 mark-run 으로 기록하기 위한 대상.
3
+ -- semiclaw 봇에 FK 매핑, schedule '* * * * *', skip_dispatch 플래그로 tick WHERE 제외.
4
+
5
+ INSERT INTO semicolony.bot_cron_jobs (bot_id, job_id, name, schedule, enabled, payload)
6
+ VALUES (
7
+ 'semiclaw',
8
+ 'cron-poller-tick',
9
+ 'SEMICOLONY cron poller heartbeat',
10
+ '{"kind":"cron","expr":"* * * * *","tz":"Asia/Seoul"}'::jsonb,
11
+ TRUE,
12
+ '{"report_channel":"#bot-ops","skip_dispatch":true,"meta":"cron-poller"}'::jsonb
13
+ )
14
+ ON CONFLICT (bot_id, job_id) DO NOTHING;