detectkit 0.13.1__tar.gz → 0.15.0__tar.gz
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.
- {detectkit-0.13.1/detectkit.egg-info → detectkit-0.15.0}/PKG-INFO +2 -2
- {detectkit-0.13.1 → detectkit-0.15.0}/README.md +1 -1
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/__init__.py +1 -1
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/alerting/channels/base.py +15 -11
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/alerting/channels/email.py +31 -5
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/alerting/channels/telegram.py +6 -1
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/alerting/channels/webhook.py +7 -2
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/alerting/orchestrator/_base.py +6 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/alerting/orchestrator/_decision.py +2 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/alerting/orchestrator/_recovery.py +1 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/cli/assets/claude/CLAUDE.section.md +6 -1
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/cli/assets/claude/rules/alerting.md +23 -1
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/cli/assets/claude/rules/project.md +7 -3
- detectkit-0.15.0/detectkit/cli/assets/claude/skills/dtk-feedback/SKILL.md +155 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/cli/commands/init_claude.py +2 -1
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/orchestration/task_manager/_alert_step.py +1 -0
- {detectkit-0.13.1 → detectkit-0.15.0/detectkit.egg-info}/PKG-INFO +2 -2
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit.egg-info/SOURCES.txt +1 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/LICENSE +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/MANIFEST.in +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/alerting/__init__.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/alerting/channels/__init__.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/alerting/channels/branding.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/alerting/channels/factory.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/alerting/channels/mattermost.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/alerting/channels/slack.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/alerting/orchestrator/__init__.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/alerting/orchestrator/_cooldown.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/alerting/orchestrator/_dispatch.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/alerting/orchestrator/_types.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/alerting/orchestrator/orchestrator.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/cli/__init__.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/cli/_output.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/cli/assets/claude/rules/cli.md +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/cli/assets/claude/rules/detectors.md +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/cli/assets/claude/rules/metrics.md +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/cli/assets/claude/rules/overview.md +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/cli/assets/claude/skills/dtk-new-metric/SKILL.md +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/cli/assets/claude/skills/dtk-setup-project/SKILL.md +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/cli/commands/__init__.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/cli/commands/clean.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/cli/commands/init.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/cli/commands/run.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/cli/commands/test_alert.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/cli/commands/unlock.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/cli/main.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/config/__init__.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/config/metric_config.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/config/profile.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/config/project_config.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/config/validator.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/core/__init__.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/core/interval.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/core/models.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/database/__init__.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/database/_sql_manager.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/database/clickhouse_manager.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/database/internal_tables/__init__.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/database/internal_tables/_alert_states.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/database/internal_tables/_base.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/database/internal_tables/_datapoints.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/database/internal_tables/_detections.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/database/internal_tables/_maintenance.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/database/internal_tables/_metrics.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/database/internal_tables/_schema.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/database/internal_tables/_tasks.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/database/internal_tables/manager.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/database/manager.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/database/mysql_manager.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/database/postgres_manager.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/database/tables.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/detectors/__init__.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/detectors/base.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/detectors/factory.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/detectors/seasonality.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/detectors/statistical/__init__.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/detectors/statistical/_windowed.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/detectors/statistical/iqr.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/detectors/statistical/mad.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/detectors/statistical/manual_bounds.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/detectors/statistical/zscore.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/loaders/__init__.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/loaders/metric_loader.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/loaders/query_template.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/orchestration/__init__.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/orchestration/error_dispatch.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/orchestration/task_manager/__init__.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/orchestration/task_manager/_base.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/orchestration/task_manager/_detect_step.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/orchestration/task_manager/_load_step.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/orchestration/task_manager/_types.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/orchestration/task_manager/manager.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/utils/__init__.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/utils/datetime_utils.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/utils/env_interpolation.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/utils/json_utils.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit/utils/stats.py +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit.egg-info/dependency_links.txt +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit.egg-info/entry_points.txt +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit.egg-info/requires.txt +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/detectkit.egg-info/top_level.txt +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/pyproject.toml +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/requirements.txt +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/setup.cfg +0 -0
- {detectkit-0.13.1 → detectkit-0.15.0}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: detectkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.15.0
|
|
4
4
|
Summary: Metric monitoring with automatic anomaly detection
|
|
5
5
|
Author: detectkit team
|
|
6
6
|
License: MIT
|
|
@@ -87,7 +87,7 @@ Dynamic: license-file
|
|
|
87
87
|
- **Database agnostic** — ClickHouse, PostgreSQL, MySQL
|
|
88
88
|
- **Idempotent** — resume from interruptions, no duplicate processing
|
|
89
89
|
- **CLI** — `dtk init`, `dtk run --select`, `dtk unlock`, `dtk clean`, tag-based selectors
|
|
90
|
-
- **AI-native onboarding** — `dtk init-claude` sets up Claude Code context (CLAUDE.md + rules +
|
|
90
|
+
- **AI-native onboarding** — `dtk init-claude` sets up Claude Code context (CLAUDE.md + rules + three skills) so an assistant can scaffold metrics, configure databases, and file feedback upstream
|
|
91
91
|
|
|
92
92
|
## Installation
|
|
93
93
|
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
- **Database agnostic** — ClickHouse, PostgreSQL, MySQL
|
|
20
20
|
- **Idempotent** — resume from interruptions, no duplicate processing
|
|
21
21
|
- **CLI** — `dtk init`, `dtk run --select`, `dtk unlock`, `dtk clean`, tag-based selectors
|
|
22
|
-
- **AI-native onboarding** — `dtk init-claude` sets up Claude Code context (CLAUDE.md + rules +
|
|
22
|
+
- **AI-native onboarding** — `dtk init-claude` sets up Claude Code context (CLAUDE.md + rules + three skills) so an assistant can scaffold metrics, configure databases, and file feedback upstream
|
|
23
23
|
|
|
24
24
|
## Installation
|
|
25
25
|
|
|
@@ -4,7 +4,7 @@ detectk - Anomaly Detection for Time-Series Metrics
|
|
|
4
4
|
A Python library for data analysts and engineers to monitor metrics with automatic anomaly detection.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
__version__ = "0.
|
|
7
|
+
__version__ = "0.15.0"
|
|
8
8
|
|
|
9
9
|
from detectkit.core.interval import Interval
|
|
10
10
|
from detectkit.core.models import ColumnDefinition, TableModel
|
|
@@ -32,10 +32,14 @@ class AlertData:
|
|
|
32
32
|
consecutive_count: Number of consecutive anomalies
|
|
33
33
|
is_recovery: True for recovery notifications
|
|
34
34
|
is_no_data: True for missing-data alerts (no_data_alert)
|
|
35
|
-
project_name: Optional ``detectkit_project.yml`` name. Surfaces
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
project_name: Optional ``detectkit_project.yml`` name. Surfaces as
|
|
36
|
+
``{project_name}`` / ``{project_name_prefix}`` in templates and, by
|
|
37
|
+
default, as a ``[name] `` prefix on every alert title/headline
|
|
38
|
+
(anomaly, recovery, no-data, error) plus a brand-paired footer
|
|
39
|
+
("detectkit · name" on webhook/email). The detectkit pipeline stamps
|
|
40
|
+
it from the project config; direct-API callers leave it ``None`` and
|
|
41
|
+
render unchanged. Lets multiple projects share one alert channel —
|
|
42
|
+
keeping the default brand bot name + avatar — without ambiguity.
|
|
39
43
|
|
|
40
44
|
Alert-rule fields (``min_detectors``, ``direction_policy``,
|
|
41
45
|
``consecutive_required``, ``detector_count``) describe *why the alert
|
|
@@ -450,7 +454,7 @@ class BaseAlertChannel(ABC):
|
|
|
450
454
|
Default template string
|
|
451
455
|
"""
|
|
452
456
|
return (
|
|
453
|
-
"🔴 Alert: {metric_name}\n"
|
|
457
|
+
"🔴 {project_name_prefix}Alert: {metric_name}\n"
|
|
454
458
|
"{description_line}"
|
|
455
459
|
"Quorum {detector_count}/{min_detectors} · "
|
|
456
460
|
"direction {direction} (policy {direction_policy}) · "
|
|
@@ -476,7 +480,7 @@ class BaseAlertChannel(ABC):
|
|
|
476
480
|
Default recovery template string
|
|
477
481
|
"""
|
|
478
482
|
return (
|
|
479
|
-
"🟢 Alert cleared: {metric_name}\n"
|
|
483
|
+
"🟢 {project_name_prefix}Alert cleared: {metric_name}\n"
|
|
480
484
|
"{description_line}"
|
|
481
485
|
"The alert condition no longer holds — "
|
|
482
486
|
"the metric is back within expected bounds.\n"
|
|
@@ -500,7 +504,7 @@ class BaseAlertChannel(ABC):
|
|
|
500
504
|
Returns:
|
|
501
505
|
Default title template string
|
|
502
506
|
"""
|
|
503
|
-
return "🔴 Alert: {metric_name}"
|
|
507
|
+
return "🔴 {project_name_prefix}Alert: {metric_name}"
|
|
504
508
|
|
|
505
509
|
def get_default_recovery_title_template(self) -> str:
|
|
506
510
|
"""
|
|
@@ -509,7 +513,7 @@ class BaseAlertChannel(ABC):
|
|
|
509
513
|
Returns:
|
|
510
514
|
Default recovery title template string
|
|
511
515
|
"""
|
|
512
|
-
return "🟢 Alert cleared: {metric_name}"
|
|
516
|
+
return "🟢 {project_name_prefix}Alert cleared: {metric_name}"
|
|
513
517
|
|
|
514
518
|
def get_default_no_data_template(self) -> str:
|
|
515
519
|
"""
|
|
@@ -519,7 +523,7 @@ class BaseAlertChannel(ABC):
|
|
|
519
523
|
has no datapoint (no row OR row with NULL/NaN value).
|
|
520
524
|
"""
|
|
521
525
|
return (
|
|
522
|
-
"🟡 No data for metric: {metric_name}\n"
|
|
526
|
+
"🟡 {project_name_prefix}No data for metric: {metric_name}\n"
|
|
523
527
|
"{description_line}"
|
|
524
528
|
"Time: {timestamp}\n"
|
|
525
529
|
"Status: query returned no datapoint for the latest interval\n"
|
|
@@ -529,12 +533,12 @@ class BaseAlertChannel(ABC):
|
|
|
529
533
|
|
|
530
534
|
def get_default_no_data_title_template(self) -> str:
|
|
531
535
|
"""Get default title template for no-data alerts."""
|
|
532
|
-
return "🟡 No data: {metric_name}"
|
|
536
|
+
return "🟡 {project_name_prefix}No data: {metric_name}"
|
|
533
537
|
|
|
534
538
|
def get_default_error_template(self) -> str:
|
|
535
539
|
"""Default body template for project-level error alerts."""
|
|
536
540
|
return (
|
|
537
|
-
"🔵 Pipeline failed for metric: {metric_name}\n"
|
|
541
|
+
"🔵 {project_name_prefix}Pipeline failed for metric: {metric_name}\n"
|
|
538
542
|
"{description_line}"
|
|
539
543
|
"Time: {timestamp}\n"
|
|
540
544
|
"Error: {error_type}: {error_message}\n"
|
|
@@ -74,7 +74,7 @@ class EmailChannel(BaseAlertChannel):
|
|
|
74
74
|
smtp_username: str | None = None,
|
|
75
75
|
smtp_password: str | None = None,
|
|
76
76
|
use_tls: bool = True,
|
|
77
|
-
subject_template: str = "🔴 Alert: {metric_name}",
|
|
77
|
+
subject_template: str = "🔴 {project_name_prefix}Alert: {metric_name}",
|
|
78
78
|
from_name: str = BRAND_USERNAME,
|
|
79
79
|
template: str | None = None,
|
|
80
80
|
**kwargs,
|
|
@@ -144,10 +144,18 @@ class EmailChannel(BaseAlertChannel):
|
|
|
144
144
|
# of a bot display name. formataddr quotes the name when required.
|
|
145
145
|
msg["From"] = formataddr((self.from_name, self.from_email))
|
|
146
146
|
msg["To"] = ", ".join(self.to_emails)
|
|
147
|
-
# Strip CR/LF from the metric name before
|
|
148
|
-
#
|
|
147
|
+
# Strip CR/LF from the metric *and* project name before they reach the
|
|
148
|
+
# Subject header so neither can inject extra email headers. The project
|
|
149
|
+
# prefix ("[my_project] ") makes the inbox row distinguishable when
|
|
150
|
+
# several projects email the same address.
|
|
149
151
|
subject_metric = alert_data.metric_name.replace("\r", " ").replace("\n", " ")
|
|
150
|
-
|
|
152
|
+
project_clean = (alert_data.project_name or "").replace("\r", " ").replace("\n", " ")
|
|
153
|
+
project_prefix = f"[{project_clean}] " if project_clean else ""
|
|
154
|
+
msg["Subject"] = self.subject_template.format(
|
|
155
|
+
metric_name=subject_metric,
|
|
156
|
+
project_name_prefix=project_prefix,
|
|
157
|
+
project_name=project_clean,
|
|
158
|
+
)
|
|
151
159
|
|
|
152
160
|
# Attach both parts. In multipart/alternative the LAST part is the
|
|
153
161
|
# preferred one, so HTML (the branded card) is shown when supported and
|
|
@@ -247,6 +255,8 @@ class EmailChannel(BaseAlertChannel):
|
|
|
247
255
|
f'font-size:4px;background-color:{accent};"> </td></tr>'
|
|
248
256
|
# header: logo + wordmark + status pill
|
|
249
257
|
f"{self._header_html(accent, pill)}"
|
|
258
|
+
# project eyebrow (small label above the metric, only when set)
|
|
259
|
+
f"{self._eyebrow_html(ctx['project_name'])}"
|
|
250
260
|
# title
|
|
251
261
|
f'<tr><td style="padding:6px 24px 2px 24px;font-family:{_SANS};font-size:22px;'
|
|
252
262
|
f'font-weight:bold;color:{_INK};mso-line-height-rule:exactly;line-height:28px;">'
|
|
@@ -258,6 +268,17 @@ class EmailChannel(BaseAlertChannel):
|
|
|
258
268
|
"</table></td></tr></table></body></html>"
|
|
259
269
|
)
|
|
260
270
|
|
|
271
|
+
def _eyebrow_html(self, project_name: str) -> str:
|
|
272
|
+
"""Small uppercase project label above the metric title (empty if unset)."""
|
|
273
|
+
if not project_name:
|
|
274
|
+
return ""
|
|
275
|
+
return (
|
|
276
|
+
f'<tr><td style="padding:10px 24px 0 24px;font-family:{_SANS};font-size:11px;'
|
|
277
|
+
f"font-weight:bold;letter-spacing:0.5px;color:{_FAINT};text-transform:uppercase;"
|
|
278
|
+
f'mso-line-height-rule:exactly;line-height:14px;">'
|
|
279
|
+
f"{html.escape(project_name)}</td></tr>"
|
|
280
|
+
)
|
|
281
|
+
|
|
261
282
|
def _header_html(self, accent: str, pill: str) -> str:
|
|
262
283
|
"""Logo + wordmark (real text) + colored status pill row."""
|
|
263
284
|
return (
|
|
@@ -432,11 +453,16 @@ class EmailChannel(BaseAlertChannel):
|
|
|
432
453
|
def _footer_html(self, alert_data: AlertData) -> str:
|
|
433
454
|
cc = self.format_mentions(alert_data.mentions)
|
|
434
455
|
cc_html = f" · {html.escape(cc)}" if cc else ""
|
|
456
|
+
# Pair the brand with the project name ("Sent by detectkit · my_project")
|
|
457
|
+
# so the source project is clear even past the subject/eyebrow.
|
|
458
|
+
project_html = (
|
|
459
|
+
f" · {html.escape(alert_data.project_name)}" if alert_data.project_name else ""
|
|
460
|
+
)
|
|
435
461
|
return (
|
|
436
462
|
f'<tr><td bgcolor="{_SURFACE}" style="background-color:{_SURFACE};'
|
|
437
463
|
f"border-top:1px solid {_BORDER};padding:14px 24px;font-family:{_SANS};"
|
|
438
464
|
f'font-size:12px;color:{_FAINT};mso-line-height-rule:exactly;line-height:16px;">'
|
|
439
|
-
f"Sent by detectkit{cc_html}</td></tr>"
|
|
465
|
+
f"Sent by detectkit{project_html}{cc_html}</td></tr>"
|
|
440
466
|
)
|
|
441
467
|
|
|
442
468
|
def format_mentions(self, mentions: list[str]) -> str:
|
|
@@ -148,7 +148,12 @@ class TelegramChannel(BaseAlertChannel):
|
|
|
148
148
|
return html.escape(str(value))
|
|
149
149
|
|
|
150
150
|
metric = esc(ctx["metric_name"])
|
|
151
|
-
|
|
151
|
+
# Lead the headline with the project name (when set) so multiple
|
|
152
|
+
# projects sharing one chat stay distinguishable — Telegram has no
|
|
153
|
+
# footer/avatar override, so this prefix is the only project cue.
|
|
154
|
+
proj = ctx["project_name"]
|
|
155
|
+
head_prefix = f"[{esc(proj)}] " if proj else ""
|
|
156
|
+
lines: list[str] = [f"{dot} <b>{head_prefix}{word} · {metric}</b>"]
|
|
152
157
|
|
|
153
158
|
if ctx["description"]:
|
|
154
159
|
lines.append(f"<i>{esc(self._cap(ctx['description'], _DESC_CAP))}</i>")
|
|
@@ -189,8 +189,13 @@ class WebhookChannel(BaseAlertChannel):
|
|
|
189
189
|
attachment["title_link"] = alert_data.dashboard_url
|
|
190
190
|
|
|
191
191
|
# Brand the attachment footer (reliable on Slack even when top-level
|
|
192
|
-
# username/icon are locked to the app install).
|
|
193
|
-
|
|
192
|
+
# username/icon are locked to the app install). Pair the brand name with
|
|
193
|
+
# the project name when set ("detectkit · my_project") so two projects
|
|
194
|
+
# posting to the same channel stay distinguishable even past the title.
|
|
195
|
+
footer = self.username or BRAND_USERNAME
|
|
196
|
+
if alert_data.project_name:
|
|
197
|
+
footer = f"{footer} · {alert_data.project_name}"
|
|
198
|
+
attachment["footer"] = footer
|
|
194
199
|
if self.icon_url:
|
|
195
200
|
attachment["footer_icon"] = self.icon_url
|
|
196
201
|
# Slack-only sugar: a real timestamp under the footer. Mattermost
|
|
@@ -25,6 +25,7 @@ class _OrchestratorBase:
|
|
|
25
25
|
mentions: list[str] | None = None,
|
|
26
26
|
dashboard_url: str | None = None,
|
|
27
27
|
links: dict[str, str] | None = None,
|
|
28
|
+
project_name: str | None = None,
|
|
28
29
|
):
|
|
29
30
|
self.metric_name = metric_name
|
|
30
31
|
self.interval = interval
|
|
@@ -37,6 +38,11 @@ class _OrchestratorBase:
|
|
|
37
38
|
self.mentions = mentions or []
|
|
38
39
|
self.dashboard_url = dashboard_url
|
|
39
40
|
self.links = links or {}
|
|
41
|
+
# Optional project name (``detectkit_project.yml`` ``name``). Stamped
|
|
42
|
+
# onto every AlertData so channels can label which project an alert
|
|
43
|
+
# came from — keeps multiple projects sharing one channel distinct
|
|
44
|
+
# while the bot keeps the default brand name + avatar.
|
|
45
|
+
self.project_name = project_name
|
|
40
46
|
|
|
41
47
|
@staticmethod
|
|
42
48
|
def _group_by_timestamp(
|
|
@@ -241,6 +241,7 @@ class _DecisionMixin(_OrchestratorBase):
|
|
|
241
241
|
mentions=self.mentions,
|
|
242
242
|
dashboard_url=self.dashboard_url,
|
|
243
243
|
links=self.links,
|
|
244
|
+
project_name=self.project_name,
|
|
244
245
|
# Alert rule the message foregrounds: configured thresholds plus
|
|
245
246
|
# the observed quorum size that satisfied them.
|
|
246
247
|
min_detectors=self.conditions.min_detectors,
|
|
@@ -300,6 +301,7 @@ class _DecisionMixin(_OrchestratorBase):
|
|
|
300
301
|
mentions=self.mentions,
|
|
301
302
|
dashboard_url=self.dashboard_url,
|
|
302
303
|
links=self.links,
|
|
304
|
+
project_name=self.project_name,
|
|
303
305
|
)
|
|
304
306
|
|
|
305
307
|
def get_last_complete_point(self, now: datetime | None = None) -> datetime:
|
|
@@ -178,6 +178,7 @@ class _RecoveryMixin(_OrchestratorBase):
|
|
|
178
178
|
mentions=self.mentions,
|
|
179
179
|
dashboard_url=self.dashboard_url,
|
|
180
180
|
links=self.links,
|
|
181
|
+
project_name=self.project_name,
|
|
181
182
|
# Echo the rule that had fired so the recovery message names the
|
|
182
183
|
# same alert condition that just cleared.
|
|
183
184
|
min_detectors=self.conditions.min_detectors,
|
|
@@ -26,7 +26,7 @@ version — **read the relevant one on demand** instead of guessing:
|
|
|
26
26
|
| Choosing/tuning detectors, preprocessing, trends, seasonality | `.claude/rules/detectkit/detectors.md` |
|
|
27
27
|
| Alert rules (quorum/direction/consecutive), cooldown, recovery, templates | `.claude/rules/detectkit/alerting.md` |
|
|
28
28
|
|
|
29
|
-
###
|
|
29
|
+
### Skills
|
|
30
30
|
|
|
31
31
|
- **First-time setup** — use the **`dtk-setup-project`** skill to configure the
|
|
32
32
|
database connection in `profiles.yml` (the `dtk init` placeholder ships example
|
|
@@ -34,6 +34,11 @@ version — **read the relevant one on demand** instead of guessing:
|
|
|
34
34
|
channel.
|
|
35
35
|
- **A new metric** — use the **`dtk-new-metric`** skill; it walks the config out
|
|
36
36
|
to a YAML file that validates and is ready to run.
|
|
37
|
+
- **Hit a detectkit bug, or have feedback** — once you've ruled out a local
|
|
38
|
+
config fix (see the gotchas below), use the **`dtk-feedback`** skill to file a
|
|
39
|
+
redacted bug report, feature request, or comment as a GitHub issue on the
|
|
40
|
+
upstream repo. It auto-collects the diagnostic context, strips every secret,
|
|
41
|
+
and never submits without showing you the exact text first.
|
|
37
42
|
|
|
38
43
|
### Gotchas that bite (keep these in mind)
|
|
39
44
|
|
|
@@ -158,7 +158,9 @@ With no custom `template`, each channel renders a structured, branded message
|
|
|
158
158
|
shared value computation lives in one place (`BaseAlertChannel.build_context`),
|
|
159
159
|
so templates and native rendering stay consistent. Every alert title/headline
|
|
160
160
|
leads with a colored **status circle** — 🔴 anomaly, 🟢 recovery, 🟡 no-data,
|
|
161
|
-
🔵 pipeline error — so the status reads from color alone.
|
|
161
|
+
🔵 pipeline error — so the status reads from color alone. It also leads with the
|
|
162
|
+
**project name** as a `[name] ` prefix (from `detectkit_project.yml`) — see
|
|
163
|
+
[Project label](#project-label-multi-project-channels) below.
|
|
162
164
|
|
|
163
165
|
- **Slack / Mattermost / generic webhook** — one message *attachment* with a
|
|
164
166
|
status-colored accent bar, a clickable title (the metric; links to
|
|
@@ -181,6 +183,25 @@ leads with a colored **status circle** — 🔴 anomaly, 🟢 recovery, 🟡 no-
|
|
|
181
183
|
table, a monospace params box, an optional "Open dashboard" button, and a
|
|
182
184
|
footer. The plain-text body remains the multipart fallback.
|
|
183
185
|
|
|
186
|
+
## Project label (multi-project channels)
|
|
187
|
+
|
|
188
|
+
The bot keeps the **detectkit brand** name + avatar by default (so users rarely
|
|
189
|
+
override them). To still tell apart two projects posting to the **same** channel,
|
|
190
|
+
detectkit stamps the project name (`detectkit_project.yml` → `name`) onto every
|
|
191
|
+
alert and shows it by default — no config needed:
|
|
192
|
+
|
|
193
|
+
- **Title / headline / subject** lead with a `[name] ` prefix on every kind
|
|
194
|
+
(anomaly, recovery, no-data, error): `🔴 [payments] Alert: api_error_rate`.
|
|
195
|
+
- **Webhook (Slack/Mattermost)** also pairs it in the footer: `detectkit · payments`.
|
|
196
|
+
- **Telegram** carries it in the bold headline (no footer/avatar to override).
|
|
197
|
+
- **Email** prefixes the subject, shows a small project eyebrow above the metric,
|
|
198
|
+
and pairs it in the footer (`Sent by detectkit · payments`).
|
|
199
|
+
|
|
200
|
+
It is exposed to custom templates as `{project_name}` and `{project_name_prefix}`
|
|
201
|
+
(`"[name] "` when set, else `""`). Direct library/API callers that don't set it
|
|
202
|
+
render unchanged. The `name` is informational only (it does not key any `_dtk_*`
|
|
203
|
+
table), so renaming it is safe — spaces are allowed for a prettier label.
|
|
204
|
+
|
|
184
205
|
## Multiple alert configs per metric
|
|
185
206
|
|
|
186
207
|
`alerting:` may be a **list** of independent blocks, each with its own channels,
|
|
@@ -210,6 +231,7 @@ referenced by path). Key variables:
|
|
|
210
231
|
| Variable | Meaning |
|
|
211
232
|
|---|---|
|
|
212
233
|
| `{metric_name}`, `{description}` / `{description_line}` | identity |
|
|
234
|
+
| `{project_name}` / `{project_name_prefix}` | project label (`"[name] "` prefix, or `""`) |
|
|
213
235
|
| `{timestamp}`, `{timezone}` | when (display tz via `alerting.timezone`, default UTC) |
|
|
214
236
|
| `{value}` / `{value_display}` | metric value (`value_display` is NaN-safe) |
|
|
215
237
|
| `{confidence_lower}` / `{confidence_upper}` / `{confidence_interval}` | bounds |
|
|
@@ -9,7 +9,9 @@ errors (not empty strings).
|
|
|
9
9
|
## `detectkit_project.yml`
|
|
10
10
|
|
|
11
11
|
```yaml
|
|
12
|
-
name: my_monitoring # required — project identifier
|
|
12
|
+
name: my_monitoring # required — project identifier; also labels every
|
|
13
|
+
# alert ("[my_monitoring] Alert: …") so multiple
|
|
14
|
+
# projects on one channel stay distinct (alerting.md)
|
|
13
15
|
version: "1.0" # optional (default "1.0")
|
|
14
16
|
default_profile: prod # profile name from profiles.yml
|
|
15
17
|
|
|
@@ -57,8 +59,10 @@ error_alerting:
|
|
|
57
59
|
via cron cadence). Channel send failures are swallowed so a flaky webhook
|
|
58
60
|
can't crash the run.
|
|
59
61
|
- Extra template variables: `{error_type}`, `{error_message}`, `{status}`
|
|
60
|
-
(always `"ERROR"`)
|
|
61
|
-
`"[<name>] "` when `name` set
|
|
62
|
+
(always `"ERROR"`). `{project_name}` / `{project_name_prefix}` (=
|
|
63
|
+
`"[<name>] "` when `name` set) are available here **and in every other alert
|
|
64
|
+
template** — and by default lead the title/headline on all channels, keeping
|
|
65
|
+
multi-project channels distinguishable (see `alerting.md` → Project label).
|
|
62
66
|
|
|
63
67
|
## `profiles.yml`
|
|
64
68
|
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dtk-feedback
|
|
3
|
+
description: >-
|
|
4
|
+
Report a detectkit bug, request a feature, or send feedback to the maintainers
|
|
5
|
+
as a GitHub issue on the upstream repo (alexeiveselov92/detectkit). Use when a
|
|
6
|
+
dtk command errors or behaves unexpectedly and it looks like a detectkit defect
|
|
7
|
+
(not the user's config), when the user wishes detectkit did something it
|
|
8
|
+
doesn't, or when they ask to report/file an issue, send feedback, or contact
|
|
9
|
+
the maintainers. Auto-collects diagnostic context, redacts all secrets, and
|
|
10
|
+
never submits without explicit confirmation.
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Send feedback to the detectkit maintainers
|
|
14
|
+
|
|
15
|
+
Turn a problem, a wish, or a comment into a clear GitHub issue on the upstream
|
|
16
|
+
repo **`alexeiveselov92/detectkit`** — carrying the diagnostic context
|
|
17
|
+
maintainers need and **leaking no secrets**. You are filing a *public* issue on
|
|
18
|
+
the user's behalf: never submit anything without showing the exact text and
|
|
19
|
+
getting an explicit "yes".
|
|
20
|
+
|
|
21
|
+
This skill is the procedure; the reference for ruling out config problems lives
|
|
22
|
+
under `.claude/rules/detectkit/`.
|
|
23
|
+
|
|
24
|
+
## Step 0 — Decide whether this belongs upstream
|
|
25
|
+
|
|
26
|
+
- **Bug** — a `dtk` command errors or produces clearly wrong output, **and**
|
|
27
|
+
you've ruled out a local config/usage mistake (Step 1).
|
|
28
|
+
- **Feature request** — the user needs detectkit to do something it doesn't.
|
|
29
|
+
- **Feedback / question** — a comment, confusion, or rough edge worth surfacing.
|
|
30
|
+
|
|
31
|
+
Do **not** open an issue for the user's own SQL, data, database, or
|
|
32
|
+
configuration mistakes — those are fixed locally with the rules files, not
|
|
33
|
+
reported as detectkit bugs. Only genuine detectkit behavior goes upstream.
|
|
34
|
+
|
|
35
|
+
## Step 1 — Rule out config first (for "it doesn't work")
|
|
36
|
+
|
|
37
|
+
Before calling something a bug, confirm it isn't a configuration or usage
|
|
38
|
+
problem. Read the matching `.claude/rules/detectkit/` file and check the usual
|
|
39
|
+
gotchas:
|
|
40
|
+
|
|
41
|
+
- the loading query filters on `{{ dtk_start_time }}` **and** `{{ dtk_end_time }}`;
|
|
42
|
+
- metric `name` is unique across the project;
|
|
43
|
+
- `profiles.yml` has the right location fields (`internal_database`/`data_database`,
|
|
44
|
+
or Postgres schemas) and a `default_profile` that exists;
|
|
45
|
+
- `alert_cooldown` is set; channels referenced by metrics exist.
|
|
46
|
+
|
|
47
|
+
If a local fix exists, **do that and stop** — don't file an issue. Escalate only
|
|
48
|
+
what is genuinely a detectkit defect.
|
|
49
|
+
|
|
50
|
+
## Step 2 — Gather context (do this for the user, don't make them dig)
|
|
51
|
+
|
|
52
|
+
Collect automatically:
|
|
53
|
+
|
|
54
|
+
- `dtk --version` — the detectkit version (**always** include it).
|
|
55
|
+
- Python version (`python3 --version`) and OS.
|
|
56
|
+
- The database backend **type only** (`clickhouse` / `postgres` / `mysql`) from
|
|
57
|
+
`profiles.yml`'s `type:` — never the host, port, or credentials.
|
|
58
|
+
|
|
59
|
+
For a **bug**, also gather:
|
|
60
|
+
|
|
61
|
+
- the exact command that was run;
|
|
62
|
+
- the full error message / traceback;
|
|
63
|
+
- what the user expected vs. what happened;
|
|
64
|
+
- a **minimal reproduction** — the failing metric YAML and/or SQL, with every
|
|
65
|
+
secret stripped (Step 3).
|
|
66
|
+
|
|
67
|
+
## Step 3 — Redact secrets (mandatory — before anything is shown or sent)
|
|
68
|
+
|
|
69
|
+
This is a public issue. Strip from every snippet you include:
|
|
70
|
+
|
|
71
|
+
- passwords, tokens, `bot_token`, `smtp_password`, API keys;
|
|
72
|
+
- `webhook_url`s, `chat_id`s, email addresses;
|
|
73
|
+
- hostnames, IPs, ports, and real database / schema / table names;
|
|
74
|
+
- the resolved value of anything inside `{{ env_var('…') }}` or `${…}` — keep the
|
|
75
|
+
`env_var(...)` shape, drop the value.
|
|
76
|
+
|
|
77
|
+
Replace with placeholders like `<redacted>`, `<your-host>`, `<your_table>` while
|
|
78
|
+
keeping the structure intact so the bug is still reproducible. **If you're not
|
|
79
|
+
sure whether something is sensitive, redact it.**
|
|
80
|
+
|
|
81
|
+
## Step 4 — Search for duplicates
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
gh issue list --repo alexeiveselov92/detectkit --search "<keywords>" --state all
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
If a matching issue already exists, offer to **add a comment** or a 👍 reaction
|
|
88
|
+
instead of opening a new one:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
gh issue comment <number> --repo alexeiveselov92/detectkit --body "<note>"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
This gives maintainers signal on frequency instead of a pile of duplicates.
|
|
95
|
+
|
|
96
|
+
## Step 5 — Draft the issue
|
|
97
|
+
|
|
98
|
+
Write a specific title (the actual symptom in a few words, not "bug"). Pick the
|
|
99
|
+
body template by type:
|
|
100
|
+
|
|
101
|
+
- **Bug** — `Summary` / `Environment` (detectkit version, Python, OS, backend) /
|
|
102
|
+
`Steps to reproduce` / `Expected` / `Actual` (with the traceback in a fenced
|
|
103
|
+
code block) / `Minimal config` (redacted).
|
|
104
|
+
- **Feature request** — `Problem` (the underlying need, not just the proposed
|
|
105
|
+
fix) / `Proposed behavior` / `Alternatives considered`.
|
|
106
|
+
- **Feedback** — free-form, concrete and respectful.
|
|
107
|
+
|
|
108
|
+
End the body with an attribution marker so maintainers can see it came through
|
|
109
|
+
the assistant funnel and on which version:
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
_Filed via the dtk init-claude assistant (detectkit <version>)._
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Step 6 — Preview and confirm (no silent submits)
|
|
116
|
+
|
|
117
|
+
Show the user the **full** title and body exactly as they will be posted, and
|
|
118
|
+
the target repo. Ask them to confirm: secrets redacted? right repo? Proceed to
|
|
119
|
+
Step 7 only on an explicit "yes". If they want edits, revise and re-preview.
|
|
120
|
+
|
|
121
|
+
## Step 7 — Submit
|
|
122
|
+
|
|
123
|
+
**Preferred — `gh` CLI** (check it's installed and authenticated first with
|
|
124
|
+
`gh auth status`). Write the body to a temp file to avoid shell-escaping the
|
|
125
|
+
markdown/traceback:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
gh issue create --repo alexeiveselov92/detectkit \
|
|
129
|
+
--title "<title>" --body-file <tmpfile>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Tag the issue so maintainers can triage assistant-filed reports: add the type
|
|
133
|
+
label (`--label bug` / `--label enhancement`) **and** `--label "via:assistant"`.
|
|
134
|
+
Labels must already exist on the repo, so if `gh` reports an unknown label,
|
|
135
|
+
**retry without the failing label(s)** — never fail the report over a missing
|
|
136
|
+
label. (Attribution also lives in the Step 5 body marker, so maintainers can
|
|
137
|
+
filter with `in:body "Filed via the dtk init-claude assistant"` even before the
|
|
138
|
+
`via:assistant` label exists.) Return the issue URL to the user.
|
|
139
|
+
|
|
140
|
+
**Fallback — no `gh`, or not authenticated.** Build a prefilled "new issue" URL
|
|
141
|
+
and hand it to the user to open and submit in their browser:
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
https://github.com/alexeiveselov92/detectkit/issues/new?title=<url-encoded>&body=<url-encoded>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
URL-encode both fields. If the body is too long for a URL, give the user the
|
|
148
|
+
title plus the body text to paste, and the plain link
|
|
149
|
+
`https://github.com/alexeiveselov92/detectkit/issues/new`.
|
|
150
|
+
|
|
151
|
+
## Step 8 — Close the loop
|
|
152
|
+
|
|
153
|
+
Give the user the issue URL (or the prefilled link), mention they can subscribe
|
|
154
|
+
for updates, and — if you also worked around the bug locally — restate that
|
|
155
|
+
local fix so they're unblocked right now, not just waiting on the issue.
|
|
@@ -11,7 +11,8 @@ It writes three things into the target directory:
|
|
|
11
11
|
injected/refreshed between HTML-comment markers (existing content is kept).
|
|
12
12
|
- ``.claude/rules/detectkit/`` — the reference docs the assistant reads on
|
|
13
13
|
demand (overview, cli, project, metrics, detectors, alerting).
|
|
14
|
-
- ``.claude/skills/`` — user-facing skills (``dtk-
|
|
14
|
+
- ``.claude/skills/`` — user-facing skills (``dtk-setup-project``,
|
|
15
|
+
``dtk-new-metric``, ``dtk-feedback``).
|
|
15
16
|
|
|
16
17
|
The source of truth for all of the above lives in ``detectkit/cli/assets/claude``
|
|
17
18
|
and ships with the package, so re-running this command after upgrading detectkit
|
|
@@ -64,6 +64,7 @@ class _AlertStepMixin(_TaskManagerBase):
|
|
|
64
64
|
mentions=alerting_config.mentions,
|
|
65
65
|
dashboard_url=alerting_config.dashboard_url,
|
|
66
66
|
links=alerting_config.links,
|
|
67
|
+
project_name=getattr(self.project_config, "name", None),
|
|
67
68
|
)
|
|
68
69
|
|
|
69
70
|
last_point = orchestrator.get_last_complete_point()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: detectkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.15.0
|
|
4
4
|
Summary: Metric monitoring with automatic anomaly detection
|
|
5
5
|
Author: detectkit team
|
|
6
6
|
License: MIT
|
|
@@ -87,7 +87,7 @@ Dynamic: license-file
|
|
|
87
87
|
- **Database agnostic** — ClickHouse, PostgreSQL, MySQL
|
|
88
88
|
- **Idempotent** — resume from interruptions, no duplicate processing
|
|
89
89
|
- **CLI** — `dtk init`, `dtk run --select`, `dtk unlock`, `dtk clean`, tag-based selectors
|
|
90
|
-
- **AI-native onboarding** — `dtk init-claude` sets up Claude Code context (CLAUDE.md + rules +
|
|
90
|
+
- **AI-native onboarding** — `dtk init-claude` sets up Claude Code context (CLAUDE.md + rules + three skills) so an assistant can scaffold metrics, configure databases, and file feedback upstream
|
|
91
91
|
|
|
92
92
|
## Installation
|
|
93
93
|
|
|
@@ -39,6 +39,7 @@ detectkit/cli/assets/claude/rules/detectors.md
|
|
|
39
39
|
detectkit/cli/assets/claude/rules/metrics.md
|
|
40
40
|
detectkit/cli/assets/claude/rules/overview.md
|
|
41
41
|
detectkit/cli/assets/claude/rules/project.md
|
|
42
|
+
detectkit/cli/assets/claude/skills/dtk-feedback/SKILL.md
|
|
42
43
|
detectkit/cli/assets/claude/skills/dtk-new-metric/SKILL.md
|
|
43
44
|
detectkit/cli/assets/claude/skills/dtk-setup-project/SKILL.md
|
|
44
45
|
detectkit/cli/commands/__init__.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{detectkit-0.13.1 → detectkit-0.15.0}/detectkit/cli/assets/claude/skills/dtk-new-metric/SKILL.md
RENAMED
|
File without changes
|
{detectkit-0.13.1 → detectkit-0.15.0}/detectkit/cli/assets/claude/skills/dtk-setup-project/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|