raise-cli 2.2.1__py3-none-any.whl
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.
- raise_cli/__init__.py +38 -0
- raise_cli/__main__.py +30 -0
- raise_cli/adapters/__init__.py +91 -0
- raise_cli/adapters/declarative/__init__.py +26 -0
- raise_cli/adapters/declarative/adapter.py +267 -0
- raise_cli/adapters/declarative/discovery.py +94 -0
- raise_cli/adapters/declarative/expressions.py +150 -0
- raise_cli/adapters/declarative/reference/__init__.py +1 -0
- raise_cli/adapters/declarative/reference/github.yaml +143 -0
- raise_cli/adapters/declarative/schema.py +98 -0
- raise_cli/adapters/filesystem.py +299 -0
- raise_cli/adapters/mcp_bridge.py +10 -0
- raise_cli/adapters/mcp_confluence.py +246 -0
- raise_cli/adapters/mcp_jira.py +405 -0
- raise_cli/adapters/models.py +205 -0
- raise_cli/adapters/protocols.py +180 -0
- raise_cli/adapters/registry.py +90 -0
- raise_cli/adapters/sync.py +149 -0
- raise_cli/agents/__init__.py +14 -0
- raise_cli/agents/antigravity.yaml +8 -0
- raise_cli/agents/claude.yaml +8 -0
- raise_cli/agents/copilot.yaml +8 -0
- raise_cli/agents/copilot_plugin.py +124 -0
- raise_cli/agents/cursor.yaml +7 -0
- raise_cli/agents/roo.yaml +8 -0
- raise_cli/agents/windsurf.yaml +8 -0
- raise_cli/artifacts/__init__.py +30 -0
- raise_cli/artifacts/models.py +43 -0
- raise_cli/artifacts/reader.py +55 -0
- raise_cli/artifacts/renderer.py +104 -0
- raise_cli/artifacts/story_design.py +69 -0
- raise_cli/artifacts/writer.py +45 -0
- raise_cli/backlog/__init__.py +1 -0
- raise_cli/backlog/sync.py +115 -0
- raise_cli/cli/__init__.py +3 -0
- raise_cli/cli/commands/__init__.py +3 -0
- raise_cli/cli/commands/_resolve.py +153 -0
- raise_cli/cli/commands/adapters.py +362 -0
- raise_cli/cli/commands/artifact.py +137 -0
- raise_cli/cli/commands/backlog.py +333 -0
- raise_cli/cli/commands/base.py +31 -0
- raise_cli/cli/commands/discover.py +551 -0
- raise_cli/cli/commands/docs.py +130 -0
- raise_cli/cli/commands/doctor.py +177 -0
- raise_cli/cli/commands/gate.py +223 -0
- raise_cli/cli/commands/graph.py +1086 -0
- raise_cli/cli/commands/info.py +81 -0
- raise_cli/cli/commands/init.py +746 -0
- raise_cli/cli/commands/journal.py +167 -0
- raise_cli/cli/commands/mcp.py +524 -0
- raise_cli/cli/commands/memory.py +467 -0
- raise_cli/cli/commands/pattern.py +348 -0
- raise_cli/cli/commands/profile.py +59 -0
- raise_cli/cli/commands/publish.py +80 -0
- raise_cli/cli/commands/release.py +338 -0
- raise_cli/cli/commands/session.py +528 -0
- raise_cli/cli/commands/signal.py +410 -0
- raise_cli/cli/commands/skill.py +350 -0
- raise_cli/cli/commands/skill_set.py +145 -0
- raise_cli/cli/error_handler.py +158 -0
- raise_cli/cli/main.py +163 -0
- raise_cli/compat.py +66 -0
- raise_cli/config/__init__.py +41 -0
- raise_cli/config/agent_plugin.py +105 -0
- raise_cli/config/agent_registry.py +233 -0
- raise_cli/config/agents.py +120 -0
- raise_cli/config/ide.py +32 -0
- raise_cli/config/paths.py +379 -0
- raise_cli/config/settings.py +180 -0
- raise_cli/context/__init__.py +42 -0
- raise_cli/context/analyzers/__init__.py +16 -0
- raise_cli/context/analyzers/models.py +36 -0
- raise_cli/context/analyzers/protocol.py +43 -0
- raise_cli/context/analyzers/python.py +292 -0
- raise_cli/context/builder.py +1569 -0
- raise_cli/context/diff.py +213 -0
- raise_cli/context/extractors/__init__.py +13 -0
- raise_cli/context/extractors/skills.py +121 -0
- raise_cli/core/__init__.py +37 -0
- raise_cli/core/files.py +66 -0
- raise_cli/core/text.py +174 -0
- raise_cli/core/tools.py +441 -0
- raise_cli/discovery/__init__.py +50 -0
- raise_cli/discovery/analyzer.py +691 -0
- raise_cli/discovery/drift.py +355 -0
- raise_cli/discovery/scanner.py +1687 -0
- raise_cli/doctor/__init__.py +4 -0
- raise_cli/doctor/checks/__init__.py +1 -0
- raise_cli/doctor/checks/environment.py +110 -0
- raise_cli/doctor/checks/project.py +238 -0
- raise_cli/doctor/fix.py +80 -0
- raise_cli/doctor/models.py +56 -0
- raise_cli/doctor/protocol.py +43 -0
- raise_cli/doctor/registry.py +100 -0
- raise_cli/doctor/report.py +141 -0
- raise_cli/doctor/runner.py +95 -0
- raise_cli/engines/__init__.py +3 -0
- raise_cli/exceptions.py +215 -0
- raise_cli/gates/__init__.py +19 -0
- raise_cli/gates/builtin/__init__.py +1 -0
- raise_cli/gates/builtin/coverage.py +52 -0
- raise_cli/gates/builtin/lint.py +48 -0
- raise_cli/gates/builtin/tests.py +48 -0
- raise_cli/gates/builtin/types.py +48 -0
- raise_cli/gates/models.py +40 -0
- raise_cli/gates/protocol.py +41 -0
- raise_cli/gates/registry.py +141 -0
- raise_cli/governance/__init__.py +11 -0
- raise_cli/governance/extractor.py +412 -0
- raise_cli/governance/models.py +134 -0
- raise_cli/governance/parsers/__init__.py +35 -0
- raise_cli/governance/parsers/_convert.py +38 -0
- raise_cli/governance/parsers/adr.py +274 -0
- raise_cli/governance/parsers/backlog.py +356 -0
- raise_cli/governance/parsers/constitution.py +119 -0
- raise_cli/governance/parsers/epic.py +323 -0
- raise_cli/governance/parsers/glossary.py +316 -0
- raise_cli/governance/parsers/guardrails.py +345 -0
- raise_cli/governance/parsers/prd.py +112 -0
- raise_cli/governance/parsers/roadmap.py +118 -0
- raise_cli/governance/parsers/vision.py +116 -0
- raise_cli/graph/__init__.py +1 -0
- raise_cli/graph/backends/__init__.py +57 -0
- raise_cli/graph/backends/api.py +137 -0
- raise_cli/graph/backends/dual.py +139 -0
- raise_cli/graph/backends/pending.py +84 -0
- raise_cli/handlers/__init__.py +3 -0
- raise_cli/hooks/__init__.py +54 -0
- raise_cli/hooks/builtin/__init__.py +1 -0
- raise_cli/hooks/builtin/backlog.py +216 -0
- raise_cli/hooks/builtin/gate_bridge.py +83 -0
- raise_cli/hooks/builtin/jira_sync.py +127 -0
- raise_cli/hooks/builtin/memory.py +117 -0
- raise_cli/hooks/builtin/telemetry.py +72 -0
- raise_cli/hooks/emitter.py +184 -0
- raise_cli/hooks/events.py +262 -0
- raise_cli/hooks/protocol.py +38 -0
- raise_cli/hooks/registry.py +117 -0
- raise_cli/mcp/__init__.py +33 -0
- raise_cli/mcp/bridge.py +218 -0
- raise_cli/mcp/models.py +43 -0
- raise_cli/mcp/registry.py +77 -0
- raise_cli/mcp/schema.py +41 -0
- raise_cli/memory/__init__.py +58 -0
- raise_cli/memory/loader.py +247 -0
- raise_cli/memory/migration.py +241 -0
- raise_cli/memory/models.py +169 -0
- raise_cli/memory/writer.py +598 -0
- raise_cli/onboarding/__init__.py +103 -0
- raise_cli/onboarding/bootstrap.py +324 -0
- raise_cli/onboarding/claudemd.py +17 -0
- raise_cli/onboarding/conventions.py +742 -0
- raise_cli/onboarding/detection.py +374 -0
- raise_cli/onboarding/governance.py +443 -0
- raise_cli/onboarding/instructions.py +672 -0
- raise_cli/onboarding/manifest.py +201 -0
- raise_cli/onboarding/memory_md.py +399 -0
- raise_cli/onboarding/migration.py +207 -0
- raise_cli/onboarding/profile.py +624 -0
- raise_cli/onboarding/skill_conflict.py +100 -0
- raise_cli/onboarding/skill_manifest.py +176 -0
- raise_cli/onboarding/skills.py +437 -0
- raise_cli/onboarding/workflows.py +101 -0
- raise_cli/output/__init__.py +28 -0
- raise_cli/output/console.py +394 -0
- raise_cli/output/formatters/__init__.py +9 -0
- raise_cli/output/formatters/adapters.py +135 -0
- raise_cli/output/formatters/discover.py +439 -0
- raise_cli/output/formatters/skill.py +298 -0
- raise_cli/publish/__init__.py +3 -0
- raise_cli/publish/changelog.py +80 -0
- raise_cli/publish/check.py +179 -0
- raise_cli/publish/version.py +172 -0
- raise_cli/rai_base/__init__.py +22 -0
- raise_cli/rai_base/framework/__init__.py +7 -0
- raise_cli/rai_base/framework/methodology.yaml +233 -0
- raise_cli/rai_base/governance/__init__.py +1 -0
- raise_cli/rai_base/governance/architecture/__init__.py +1 -0
- raise_cli/rai_base/governance/architecture/domain-model.md +20 -0
- raise_cli/rai_base/governance/architecture/system-context.md +34 -0
- raise_cli/rai_base/governance/architecture/system-design.md +24 -0
- raise_cli/rai_base/governance/backlog.md +8 -0
- raise_cli/rai_base/governance/guardrails.md +17 -0
- raise_cli/rai_base/governance/prd.md +25 -0
- raise_cli/rai_base/governance/vision.md +16 -0
- raise_cli/rai_base/identity/__init__.py +8 -0
- raise_cli/rai_base/identity/core.md +119 -0
- raise_cli/rai_base/identity/perspective.md +119 -0
- raise_cli/rai_base/memory/__init__.py +7 -0
- raise_cli/rai_base/memory/patterns-base.jsonl +55 -0
- raise_cli/schemas/__init__.py +3 -0
- raise_cli/schemas/journal.py +49 -0
- raise_cli/schemas/session_state.py +117 -0
- raise_cli/session/__init__.py +5 -0
- raise_cli/session/bundle.py +820 -0
- raise_cli/session/close.py +268 -0
- raise_cli/session/journal.py +119 -0
- raise_cli/session/resolver.py +126 -0
- raise_cli/session/state.py +187 -0
- raise_cli/skills/__init__.py +44 -0
- raise_cli/skills/locator.py +141 -0
- raise_cli/skills/name_checker.py +199 -0
- raise_cli/skills/parser.py +145 -0
- raise_cli/skills/scaffold.py +212 -0
- raise_cli/skills/schema.py +132 -0
- raise_cli/skills/skillsets.py +195 -0
- raise_cli/skills/validator.py +197 -0
- raise_cli/skills_base/__init__.py +80 -0
- raise_cli/skills_base/contract-template.md +60 -0
- raise_cli/skills_base/preamble.md +37 -0
- raise_cli/skills_base/rai-architecture-review/SKILL.md +137 -0
- raise_cli/skills_base/rai-debug/SKILL.md +171 -0
- raise_cli/skills_base/rai-discover/SKILL.md +167 -0
- raise_cli/skills_base/rai-discover-document/SKILL.md +128 -0
- raise_cli/skills_base/rai-discover-scan/SKILL.md +147 -0
- raise_cli/skills_base/rai-discover-start/SKILL.md +145 -0
- raise_cli/skills_base/rai-discover-validate/SKILL.md +142 -0
- raise_cli/skills_base/rai-docs-update/SKILL.md +142 -0
- raise_cli/skills_base/rai-doctor/SKILL.md +120 -0
- raise_cli/skills_base/rai-epic-close/SKILL.md +165 -0
- raise_cli/skills_base/rai-epic-close/templates/retrospective.md +68 -0
- raise_cli/skills_base/rai-epic-design/SKILL.md +146 -0
- raise_cli/skills_base/rai-epic-design/templates/design.md +24 -0
- raise_cli/skills_base/rai-epic-design/templates/scope.md +76 -0
- raise_cli/skills_base/rai-epic-plan/SKILL.md +153 -0
- raise_cli/skills_base/rai-epic-plan/_references/sequencing-strategies.md +67 -0
- raise_cli/skills_base/rai-epic-plan/templates/plan-section.md +49 -0
- raise_cli/skills_base/rai-epic-run/SKILL.md +208 -0
- raise_cli/skills_base/rai-epic-start/SKILL.md +136 -0
- raise_cli/skills_base/rai-epic-start/templates/brief.md +34 -0
- raise_cli/skills_base/rai-mcp-add/SKILL.md +176 -0
- raise_cli/skills_base/rai-mcp-remove/SKILL.md +120 -0
- raise_cli/skills_base/rai-mcp-status/SKILL.md +147 -0
- raise_cli/skills_base/rai-problem-shape/SKILL.md +138 -0
- raise_cli/skills_base/rai-project-create/SKILL.md +144 -0
- raise_cli/skills_base/rai-project-onboard/SKILL.md +162 -0
- raise_cli/skills_base/rai-quality-review/SKILL.md +189 -0
- raise_cli/skills_base/rai-research/SKILL.md +143 -0
- raise_cli/skills_base/rai-research/references/research-prompt-template.md +317 -0
- raise_cli/skills_base/rai-session-close/SKILL.md +176 -0
- raise_cli/skills_base/rai-session-start/SKILL.md +110 -0
- raise_cli/skills_base/rai-story-close/SKILL.md +198 -0
- raise_cli/skills_base/rai-story-design/SKILL.md +203 -0
- raise_cli/skills_base/rai-story-design/references/tech-design-story-v2.md +293 -0
- raise_cli/skills_base/rai-story-implement/SKILL.md +115 -0
- raise_cli/skills_base/rai-story-plan/SKILL.md +135 -0
- raise_cli/skills_base/rai-story-review/SKILL.md +178 -0
- raise_cli/skills_base/rai-story-run/SKILL.md +282 -0
- raise_cli/skills_base/rai-story-start/SKILL.md +166 -0
- raise_cli/skills_base/rai-story-start/templates/story.md +38 -0
- raise_cli/skills_base/rai-welcome/SKILL.md +134 -0
- raise_cli/telemetry/__init__.py +42 -0
- raise_cli/telemetry/schemas.py +285 -0
- raise_cli/telemetry/writer.py +217 -0
- raise_cli/tier/__init__.py +0 -0
- raise_cli/tier/context.py +134 -0
- raise_cli/viz/__init__.py +7 -0
- raise_cli/viz/generator.py +406 -0
- raise_cli-2.2.1.dist-info/METADATA +433 -0
- raise_cli-2.2.1.dist-info/RECORD +264 -0
- raise_cli-2.2.1.dist-info/WHEEL +4 -0
- raise_cli-2.2.1.dist-info/entry_points.txt +40 -0
- raise_cli-2.2.1.dist-info/licenses/LICENSE +190 -0
- raise_cli-2.2.1.dist-info/licenses/NOTICE +4 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rai-welcome
|
|
3
|
+
description: >
|
|
4
|
+
Conversational developer onboarding for RaiSE. Detects scenario,
|
|
5
|
+
sets up profile and graph, offers optional personalization.
|
|
6
|
+
|
|
7
|
+
license: MIT
|
|
8
|
+
|
|
9
|
+
metadata:
|
|
10
|
+
raise.work_cycle: utility
|
|
11
|
+
raise.frequency: once-per-developer
|
|
12
|
+
raise.fase: "setup"
|
|
13
|
+
raise.prerequisites: ""
|
|
14
|
+
raise.next: "rai-session-start"
|
|
15
|
+
raise.gate: ""
|
|
16
|
+
raise.adaptable: "true"
|
|
17
|
+
raise.version: "2.0.0"
|
|
18
|
+
raise.visibility: public
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# Welcome
|
|
22
|
+
|
|
23
|
+
## Purpose
|
|
24
|
+
|
|
25
|
+
Get a developer fully set up in a RaiSE project through a guided flow that detects their situation and only asks what's needed.
|
|
26
|
+
|
|
27
|
+
## Mastery Levels (ShuHaRi)
|
|
28
|
+
|
|
29
|
+
- **Shu**: Follow all steps, explain what each governance doc is for
|
|
30
|
+
- **Ha**: Detect scenario and fast-path through known setups
|
|
31
|
+
- **Ri**: One-shot setup with minimal questions
|
|
32
|
+
|
|
33
|
+
## Context
|
|
34
|
+
|
|
35
|
+
**When to use:** First time a developer works in a RaiSE project. Subsequent runs verify setup.
|
|
36
|
+
|
|
37
|
+
**When to skip:** Developer is already set up (profile exists, graph exists, CLAUDE.local.md exists).
|
|
38
|
+
|
|
39
|
+
**Inputs:** A project with `.raise/` directory (from `rai init`).
|
|
40
|
+
|
|
41
|
+
## Steps
|
|
42
|
+
|
|
43
|
+
### Step 1: Detect Scenario
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
ls ~/.rai/developer.yaml 2>/dev/null && echo "PROFILE_EXISTS" || echo "NO_PROFILE"
|
|
47
|
+
ls .raise/ 2>/dev/null && echo "RAISE_EXISTS" || echo "NO_RAISE"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
| Profile? | `.raise/`? | Action |
|
|
51
|
+
|----------|------------|--------|
|
|
52
|
+
| No | Yes | Full setup (Steps 2-5) |
|
|
53
|
+
| Yes | Yes | Verify only (Step 4) |
|
|
54
|
+
| Any | No | Stop: "Run `rai init` first, then `/rai-welcome` again." |
|
|
55
|
+
|
|
56
|
+
<verification>
|
|
57
|
+
Scenario detected. `.raise/` exists.
|
|
58
|
+
</verification>
|
|
59
|
+
|
|
60
|
+
### Step 2: Create Profile (if needed)
|
|
61
|
+
|
|
62
|
+
Ask developer's name (only mandatory question). Derive pattern prefix (first letter, uppercased), confirm.
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
rai session start --name "{name}" --project .
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Edit `~/.rai/developer.yaml` to add confirmed `pattern_prefix`.
|
|
69
|
+
|
|
70
|
+
<verification>
|
|
71
|
+
`~/.rai/developer.yaml` exists with name and pattern_prefix.
|
|
72
|
+
</verification>
|
|
73
|
+
|
|
74
|
+
### Step 3: Optional Personalization
|
|
75
|
+
|
|
76
|
+
Frame as skippable: "Want to customize? Or skip — defaults work well."
|
|
77
|
+
|
|
78
|
+
If customize, ask up to 3 questions:
|
|
79
|
+
1. **Language:** English / Spanish / Other → `communication.language`
|
|
80
|
+
2. **Style:** Detailed / Balanced / Direct → `communication.style`
|
|
81
|
+
3. **Focus guidance:** Yes / No → `communication.redirect_when_dispersing`
|
|
82
|
+
|
|
83
|
+
Defaults: `shu`, `balanced`, `en`, `detailed_explanations: true`, `redirect_when_dispersing: false`.
|
|
84
|
+
|
|
85
|
+
<verification>
|
|
86
|
+
Preferences saved or defaults accepted.
|
|
87
|
+
</verification>
|
|
88
|
+
|
|
89
|
+
### Step 4: Verify Setup
|
|
90
|
+
|
|
91
|
+
Build graph if missing (`rai graph build`). Scaffold `CLAUDE.local.md` if missing. Run context bundle:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
rai session start --project . --context
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Check: developer name appears, session count shown, no errors.
|
|
98
|
+
|
|
99
|
+
<verification>
|
|
100
|
+
Profile, graph, and local config all present and functional.
|
|
101
|
+
</verification>
|
|
102
|
+
|
|
103
|
+
### Step 5: Welcome Message
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
Welcome to RaiSE, {name}!
|
|
107
|
+
Setup: Profile ({prefix}), Graph ({N} concepts), CLAUDE.local.md
|
|
108
|
+
Next: /rai-session-start
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Output
|
|
112
|
+
|
|
113
|
+
| Item | Destination |
|
|
114
|
+
|------|-------------|
|
|
115
|
+
| Developer profile | `~/.rai/developer.yaml` |
|
|
116
|
+
| Knowledge graph | `.raise/rai/memory/index.json` |
|
|
117
|
+
| Local config | `CLAUDE.local.md` |
|
|
118
|
+
| Next | `/rai-session-start` |
|
|
119
|
+
|
|
120
|
+
## Quality Checklist
|
|
121
|
+
|
|
122
|
+
- [ ] Scenario detected before asking any questions
|
|
123
|
+
- [ ] Name is the only mandatory question
|
|
124
|
+
- [ ] Personalization clearly framed as optional
|
|
125
|
+
- [ ] Graph built if missing (not assumed)
|
|
126
|
+
- [ ] Context bundle runs successfully after setup
|
|
127
|
+
- [ ] NEVER overwrite existing CLAUDE.local.md
|
|
128
|
+
- [ ] NEVER ask about experience level — learned implicitly through coaching
|
|
129
|
+
|
|
130
|
+
## References
|
|
131
|
+
|
|
132
|
+
- Profile model: `src/raise_cli/onboarding/profile.py`
|
|
133
|
+
- Next: `/rai-session-start`
|
|
134
|
+
- One-time skill: subsequent runs verify, not recreate
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Telemetry module for local signal collection.
|
|
2
|
+
|
|
3
|
+
This module provides infrastructure for collecting telemetry signals
|
|
4
|
+
as specified in ADR-018 (Local Telemetry Architecture).
|
|
5
|
+
|
|
6
|
+
Signals are stored locally in `.raise/rai/personal/telemetry/signals.jsonl` and follow
|
|
7
|
+
OpenTelemetry semantic conventions for future OTLP export.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from raise_cli.telemetry.schemas import (
|
|
13
|
+
CalibrationEvent,
|
|
14
|
+
CommandUsage,
|
|
15
|
+
ErrorEvent,
|
|
16
|
+
SessionEvent,
|
|
17
|
+
Signal,
|
|
18
|
+
SkillEvent,
|
|
19
|
+
WorkLifecycle,
|
|
20
|
+
)
|
|
21
|
+
from raise_cli.telemetry.writer import (
|
|
22
|
+
EmitResult,
|
|
23
|
+
emit,
|
|
24
|
+
emit_command_usage,
|
|
25
|
+
emit_error_event,
|
|
26
|
+
emit_skill_event,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"CalibrationEvent",
|
|
31
|
+
"CommandUsage",
|
|
32
|
+
"EmitResult",
|
|
33
|
+
"ErrorEvent",
|
|
34
|
+
"SessionEvent",
|
|
35
|
+
"Signal",
|
|
36
|
+
"SkillEvent",
|
|
37
|
+
"WorkLifecycle",
|
|
38
|
+
"emit",
|
|
39
|
+
"emit_command_usage",
|
|
40
|
+
"emit_error_event",
|
|
41
|
+
"emit_skill_event",
|
|
42
|
+
]
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"""Pydantic models for telemetry signals.
|
|
2
|
+
|
|
3
|
+
This module defines the signal schemas for local telemetry collection
|
|
4
|
+
as specified in ADR-018. Signals follow OpenTelemetry semantic conventions
|
|
5
|
+
for future OTLP export compatibility.
|
|
6
|
+
|
|
7
|
+
Signal types:
|
|
8
|
+
- SkillEvent: Tracks skill invocations (start/complete/abandon)
|
|
9
|
+
- SessionEvent: Tracks session outcomes
|
|
10
|
+
- CalibrationEvent: Tracks estimate vs actual for velocity calibration
|
|
11
|
+
- ErrorEvent: Tracks tool failures
|
|
12
|
+
- CommandUsage: Tracks CLI command usage
|
|
13
|
+
- WorkLifecycle: Tracks work items (epic/story) through phases (Lean flow analysis)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from datetime import datetime
|
|
19
|
+
from typing import Annotated, Literal
|
|
20
|
+
|
|
21
|
+
from pydantic import BaseModel, Field
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class SkillEvent(BaseModel):
|
|
25
|
+
"""A skill invocation event.
|
|
26
|
+
|
|
27
|
+
Emitted when a skill starts, completes, or is abandoned.
|
|
28
|
+
|
|
29
|
+
Attributes:
|
|
30
|
+
type: Discriminator field, always "skill_event".
|
|
31
|
+
timestamp: When the event occurred (UTC).
|
|
32
|
+
skill: Name of the skill (e.g., "story-design").
|
|
33
|
+
event: Event type (start, complete, abandon).
|
|
34
|
+
duration_sec: Duration in seconds (only for complete/abandon).
|
|
35
|
+
|
|
36
|
+
Examples:
|
|
37
|
+
>>> from datetime import datetime, timezone
|
|
38
|
+
>>> event = SkillEvent(
|
|
39
|
+
... timestamp=datetime.now(timezone.utc),
|
|
40
|
+
... skill="story-design",
|
|
41
|
+
... event="complete",
|
|
42
|
+
... duration_sec=1800
|
|
43
|
+
... )
|
|
44
|
+
>>> event.type
|
|
45
|
+
'skill_event'
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
type: Literal["skill_event"] = "skill_event"
|
|
49
|
+
timestamp: datetime = Field(..., description="When the event occurred (UTC)")
|
|
50
|
+
skill: str = Field(..., description="Name of the skill (e.g., 'story-design')")
|
|
51
|
+
event: Literal["start", "complete", "abandon"] = Field(
|
|
52
|
+
..., description="Event type"
|
|
53
|
+
)
|
|
54
|
+
duration_sec: int | None = Field(
|
|
55
|
+
default=None, description="Duration in seconds (for complete/abandon)"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class SessionEvent(BaseModel):
|
|
60
|
+
"""A session lifecycle event.
|
|
61
|
+
|
|
62
|
+
Emitted when a session closes, capturing its outcome.
|
|
63
|
+
|
|
64
|
+
Attributes:
|
|
65
|
+
type: Discriminator field, always "session_event".
|
|
66
|
+
timestamp: When the event occurred (UTC).
|
|
67
|
+
session_type: Type of session (e.g., "story", "research").
|
|
68
|
+
outcome: How the session ended.
|
|
69
|
+
duration_min: Duration in minutes.
|
|
70
|
+
stories: Story IDs worked on during the session.
|
|
71
|
+
|
|
72
|
+
Examples:
|
|
73
|
+
>>> from datetime import datetime, timezone
|
|
74
|
+
>>> event = SessionEvent(
|
|
75
|
+
... timestamp=datetime.now(timezone.utc),
|
|
76
|
+
... session_type="story",
|
|
77
|
+
... outcome="success",
|
|
78
|
+
... duration_min=90,
|
|
79
|
+
... stories=["F9.1", "F9.2"]
|
|
80
|
+
... )
|
|
81
|
+
>>> event.type
|
|
82
|
+
'session_event'
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
type: Literal["session_event"] = "session_event"
|
|
86
|
+
timestamp: datetime = Field(..., description="When the event occurred (UTC)")
|
|
87
|
+
session_type: str = Field(
|
|
88
|
+
..., description="Type of session (e.g., 'story', 'research')"
|
|
89
|
+
)
|
|
90
|
+
outcome: Literal["success", "partial", "abandoned"] = Field(
|
|
91
|
+
..., description="How the session ended"
|
|
92
|
+
)
|
|
93
|
+
duration_min: int = Field(..., description="Duration in minutes")
|
|
94
|
+
stories: list[str] = Field(default_factory=list, description="Story IDs worked on")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class CalibrationEvent(BaseModel):
|
|
98
|
+
"""A calibration data point for velocity tracking.
|
|
99
|
+
|
|
100
|
+
Emitted when a story is completed, comparing estimate to actual.
|
|
101
|
+
|
|
102
|
+
Attributes:
|
|
103
|
+
type: Discriminator field, always "calibration".
|
|
104
|
+
timestamp: When the event occurred (UTC).
|
|
105
|
+
story_id: Story identifier (e.g., "F9.1").
|
|
106
|
+
story_size: T-shirt size (XS, S, M, L).
|
|
107
|
+
estimated_min: Estimated duration in minutes.
|
|
108
|
+
actual_min: Actual duration in minutes.
|
|
109
|
+
velocity: Ratio of estimated to actual (>1 means faster than expected).
|
|
110
|
+
|
|
111
|
+
Examples:
|
|
112
|
+
>>> from datetime import datetime, timezone
|
|
113
|
+
>>> event = CalibrationEvent(
|
|
114
|
+
... timestamp=datetime.now(timezone.utc),
|
|
115
|
+
... story_id="F9.1",
|
|
116
|
+
... story_size="XS",
|
|
117
|
+
... estimated_min=25,
|
|
118
|
+
... actual_min=20,
|
|
119
|
+
... velocity=1.25
|
|
120
|
+
... )
|
|
121
|
+
>>> event.velocity
|
|
122
|
+
1.25
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
type: Literal["calibration"] = "calibration"
|
|
126
|
+
timestamp: datetime = Field(..., description="When the event occurred (UTC)")
|
|
127
|
+
story_id: str = Field(..., description="Story identifier (e.g., 'F9.1')")
|
|
128
|
+
story_size: str = Field(..., description="T-shirt size (XS, S, M, L)")
|
|
129
|
+
estimated_min: int = Field(..., description="Estimated duration in minutes")
|
|
130
|
+
actual_min: int = Field(..., description="Actual duration in minutes")
|
|
131
|
+
velocity: float = Field(
|
|
132
|
+
..., description="Ratio of estimated to actual (>1 = faster)"
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class ErrorEvent(BaseModel):
|
|
137
|
+
"""A tool error event.
|
|
138
|
+
|
|
139
|
+
Emitted when a tool fails, for pattern detection.
|
|
140
|
+
|
|
141
|
+
Attributes:
|
|
142
|
+
type: Discriminator field, always "error_event".
|
|
143
|
+
timestamp: When the event occurred (UTC).
|
|
144
|
+
tool: Name of the tool that failed (e.g., "Bash", "Read").
|
|
145
|
+
error_type: Type of error (e.g., "command_not_found").
|
|
146
|
+
context: Brief context (no sensitive data).
|
|
147
|
+
recoverable: Whether the error was recoverable.
|
|
148
|
+
|
|
149
|
+
Examples:
|
|
150
|
+
>>> from datetime import datetime, timezone
|
|
151
|
+
>>> event = ErrorEvent(
|
|
152
|
+
... timestamp=datetime.now(timezone.utc),
|
|
153
|
+
... tool="Bash",
|
|
154
|
+
... error_type="command_not_found",
|
|
155
|
+
... context="pytest",
|
|
156
|
+
... recoverable=True
|
|
157
|
+
... )
|
|
158
|
+
>>> event.recoverable
|
|
159
|
+
True
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
type: Literal["error_event"] = "error_event"
|
|
163
|
+
timestamp: datetime = Field(..., description="When the event occurred (UTC)")
|
|
164
|
+
tool: str = Field(..., description="Name of the tool that failed")
|
|
165
|
+
error_type: str = Field(..., description="Type of error")
|
|
166
|
+
context: str = Field(..., description="Brief context (no sensitive data)")
|
|
167
|
+
recoverable: bool = Field(..., description="Whether the error was recoverable")
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class CommandUsage(BaseModel):
|
|
171
|
+
"""A CLI command usage event.
|
|
172
|
+
|
|
173
|
+
Emitted when a raise CLI command is invoked.
|
|
174
|
+
|
|
175
|
+
Attributes:
|
|
176
|
+
type: Discriminator field, always "command_usage".
|
|
177
|
+
timestamp: When the event occurred (UTC).
|
|
178
|
+
command: Main command name (e.g., "memory").
|
|
179
|
+
subcommand: Subcommand name if any (e.g., "query").
|
|
180
|
+
|
|
181
|
+
Examples:
|
|
182
|
+
>>> from datetime import datetime, timezone
|
|
183
|
+
>>> event = CommandUsage(
|
|
184
|
+
... timestamp=datetime.now(timezone.utc),
|
|
185
|
+
... command="memory",
|
|
186
|
+
... subcommand="query"
|
|
187
|
+
... )
|
|
188
|
+
>>> event.command
|
|
189
|
+
'memory'
|
|
190
|
+
"""
|
|
191
|
+
|
|
192
|
+
type: Literal["command_usage"] = "command_usage"
|
|
193
|
+
timestamp: datetime = Field(..., description="When the event occurred (UTC)")
|
|
194
|
+
command: str = Field(..., description="Main command name (e.g., 'memory')")
|
|
195
|
+
subcommand: str | None = Field(default=None, description="Subcommand name if any")
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class WorkLifecycle(BaseModel):
|
|
199
|
+
"""A unified work lifecycle event for Lean flow analysis.
|
|
200
|
+
|
|
201
|
+
Tracks work items (epics, stories, etc.) through normalized phases to enable:
|
|
202
|
+
- Lead time calculation (start to complete)
|
|
203
|
+
- Wait time detection (gaps between phases)
|
|
204
|
+
- WIP tracking (started but not completed)
|
|
205
|
+
- Bottleneck identification (longest phase)
|
|
206
|
+
- Flow efficiency (active time / lead time)
|
|
207
|
+
- Cross-level analysis (compare epic vs story flow)
|
|
208
|
+
|
|
209
|
+
Phases (normalized across all work types):
|
|
210
|
+
- design: Scope definition and specification
|
|
211
|
+
- plan: Task/story decomposition and sequencing
|
|
212
|
+
- implement: Active development work
|
|
213
|
+
- review: Retrospective and learnings
|
|
214
|
+
|
|
215
|
+
Attributes:
|
|
216
|
+
type: Discriminator field, always "work_lifecycle".
|
|
217
|
+
timestamp: When the event occurred (UTC).
|
|
218
|
+
work_type: Type of work item (epic, story, etc.).
|
|
219
|
+
work_id: Work item identifier (e.g., "E9", "F9.4").
|
|
220
|
+
event: Lifecycle event type.
|
|
221
|
+
phase: Current phase in the workflow.
|
|
222
|
+
blocker: Description of blocker (only for blocked event).
|
|
223
|
+
|
|
224
|
+
Examples:
|
|
225
|
+
>>> from datetime import datetime, timezone
|
|
226
|
+
>>> event = WorkLifecycle(
|
|
227
|
+
... timestamp=datetime.now(timezone.utc),
|
|
228
|
+
... work_type="story",
|
|
229
|
+
... work_id="F9.4",
|
|
230
|
+
... event="start",
|
|
231
|
+
... phase="design"
|
|
232
|
+
... )
|
|
233
|
+
>>> event.type
|
|
234
|
+
'work_lifecycle'
|
|
235
|
+
|
|
236
|
+
>>> epic = WorkLifecycle(
|
|
237
|
+
... timestamp=datetime.now(timezone.utc),
|
|
238
|
+
... work_type="epic",
|
|
239
|
+
... work_id="E9",
|
|
240
|
+
... event="complete",
|
|
241
|
+
... phase="review"
|
|
242
|
+
... )
|
|
243
|
+
>>> epic.work_type
|
|
244
|
+
'epic'
|
|
245
|
+
|
|
246
|
+
>>> blocked = WorkLifecycle(
|
|
247
|
+
... timestamp=datetime.now(timezone.utc),
|
|
248
|
+
... work_type="story",
|
|
249
|
+
... work_id="F9.4",
|
|
250
|
+
... event="blocked",
|
|
251
|
+
... phase="plan",
|
|
252
|
+
... blocker="unclear requirements"
|
|
253
|
+
... )
|
|
254
|
+
>>> blocked.blocker
|
|
255
|
+
'unclear requirements'
|
|
256
|
+
"""
|
|
257
|
+
|
|
258
|
+
type: Literal["work_lifecycle"] = "work_lifecycle"
|
|
259
|
+
timestamp: datetime = Field(..., description="When the event occurred (UTC)")
|
|
260
|
+
work_type: Literal["epic", "story"] = Field(
|
|
261
|
+
..., description="Type of work item (epic, story)"
|
|
262
|
+
)
|
|
263
|
+
work_id: str = Field(..., description="Work item identifier (e.g., 'E9', 'F9.4')")
|
|
264
|
+
event: Literal["start", "complete", "blocked", "unblocked", "abandoned"] = Field(
|
|
265
|
+
..., description="Lifecycle event type"
|
|
266
|
+
)
|
|
267
|
+
phase: Literal["init", "design", "plan", "implement", "review", "close"] = Field(
|
|
268
|
+
..., description="Current phase in the workflow"
|
|
269
|
+
)
|
|
270
|
+
blocker: str | None = Field(
|
|
271
|
+
default=None, description="Description of blocker (for blocked event)"
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
# Union type for type-safe signal handling
|
|
276
|
+
Signal = Annotated[
|
|
277
|
+
SkillEvent
|
|
278
|
+
| SessionEvent
|
|
279
|
+
| CalibrationEvent
|
|
280
|
+
| ErrorEvent
|
|
281
|
+
| CommandUsage
|
|
282
|
+
| WorkLifecycle,
|
|
283
|
+
Field(discriminator="type"),
|
|
284
|
+
]
|
|
285
|
+
"""Union of all signal types with discriminator for type-safe parsing."""
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"""Writer module for appending telemetry signals to JSONL.
|
|
2
|
+
|
|
3
|
+
This module provides the `emit()` function to append signals to
|
|
4
|
+
`.raise/rai/personal/telemetry/signals.jsonl` (gitignored, per-developer).
|
|
5
|
+
|
|
6
|
+
Signals are written as JSON lines (one JSON object per line),
|
|
7
|
+
which is append-friendly and git-friendly.
|
|
8
|
+
|
|
9
|
+
Note: Telemetry is personal data (F14.15) and should not be committed.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from datetime import UTC, datetime
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import TYPE_CHECKING, Literal
|
|
18
|
+
|
|
19
|
+
from raise_cli.compat import file_lock, file_unlock
|
|
20
|
+
from raise_cli.config.paths import (
|
|
21
|
+
SIGNALS_FILE,
|
|
22
|
+
TELEMETRY_SUBDIR,
|
|
23
|
+
get_personal_dir,
|
|
24
|
+
get_session_dir,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from raise_cli.telemetry.schemas import Signal
|
|
29
|
+
|
|
30
|
+
# Type alias for skill event types (matches SkillEvent.event)
|
|
31
|
+
SkillEventType = Literal["start", "complete", "abandon"]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class EmitResult:
|
|
36
|
+
"""Result of emitting a signal.
|
|
37
|
+
|
|
38
|
+
Attributes:
|
|
39
|
+
success: Whether the signal was written successfully.
|
|
40
|
+
path: Path where the signal was written.
|
|
41
|
+
error: Error message if failed, None otherwise.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
success: bool
|
|
45
|
+
path: Path | None = None
|
|
46
|
+
error: str | None = None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _get_telemetry_path(base_path: Path | None = None) -> Path:
|
|
50
|
+
"""Get the path to the signals.jsonl file in personal directory.
|
|
51
|
+
|
|
52
|
+
Telemetry is personal data (per-developer, gitignored) per F14.15.
|
|
53
|
+
Path: .raise/rai/personal/telemetry/signals.jsonl
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
base_path: Project root directory. Defaults to current directory.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Path to signals.jsonl file in personal directory.
|
|
60
|
+
"""
|
|
61
|
+
return get_personal_dir(base_path) / TELEMETRY_SUBDIR / SIGNALS_FILE
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _ensure_directory(path: Path) -> None:
|
|
65
|
+
"""Ensure the parent directory exists.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
path: Path to file whose parent directory should exist.
|
|
69
|
+
"""
|
|
70
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def emit(
|
|
74
|
+
signal: Signal,
|
|
75
|
+
*,
|
|
76
|
+
base_path: Path | None = None,
|
|
77
|
+
session_id: str | None = None,
|
|
78
|
+
) -> EmitResult:
|
|
79
|
+
"""Emit a telemetry signal to the signals.jsonl file.
|
|
80
|
+
|
|
81
|
+
When session_id is provided, writes to per-session directory:
|
|
82
|
+
.raise/rai/personal/sessions/{session_id}/signals.jsonl
|
|
83
|
+
|
|
84
|
+
When session_id is None, writes to shared telemetry directory:
|
|
85
|
+
.raise/rai/personal/telemetry/signals.jsonl
|
|
86
|
+
|
|
87
|
+
Creates the directory if it doesn't exist. Uses file locking for
|
|
88
|
+
thread-safe writes. Telemetry is personal data (gitignored).
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
signal: The signal to emit (any of the 5 signal types).
|
|
92
|
+
base_path: Base directory for telemetry. Defaults to current directory.
|
|
93
|
+
session_id: Optional session ID for per-session isolation.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
EmitResult with success status and path or error message.
|
|
97
|
+
"""
|
|
98
|
+
if session_id is not None:
|
|
99
|
+
path = get_session_dir(session_id, base_path) / SIGNALS_FILE
|
|
100
|
+
else:
|
|
101
|
+
path = _get_telemetry_path(base_path)
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
_ensure_directory(path)
|
|
105
|
+
|
|
106
|
+
# Serialize signal to JSON line
|
|
107
|
+
json_line = signal.model_dump_json() + "\n"
|
|
108
|
+
|
|
109
|
+
# Append with file locking for thread safety
|
|
110
|
+
with open(path, "a", encoding="utf-8") as f:
|
|
111
|
+
file_lock(f)
|
|
112
|
+
try:
|
|
113
|
+
f.write(json_line)
|
|
114
|
+
finally:
|
|
115
|
+
file_unlock(f)
|
|
116
|
+
|
|
117
|
+
return EmitResult(success=True, path=path)
|
|
118
|
+
|
|
119
|
+
except PermissionError as e:
|
|
120
|
+
return EmitResult(
|
|
121
|
+
success=False, error=f"Permission denied writing to {path}: {e}"
|
|
122
|
+
)
|
|
123
|
+
except OSError as e:
|
|
124
|
+
return EmitResult(success=False, error=f"OS error writing to {path}: {e}")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def emit_skill_event(
|
|
128
|
+
skill: str,
|
|
129
|
+
event: SkillEventType,
|
|
130
|
+
duration_sec: int | None = None,
|
|
131
|
+
*,
|
|
132
|
+
base_path: Path | None = None,
|
|
133
|
+
session_id: str | None = None,
|
|
134
|
+
) -> EmitResult:
|
|
135
|
+
"""Convenience function to emit a skill event.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
skill: Name of the skill (e.g., "story-design").
|
|
139
|
+
event: Event type ("start", "complete", "abandon").
|
|
140
|
+
duration_sec: Duration in seconds (for complete/abandon).
|
|
141
|
+
base_path: Base directory for telemetry.
|
|
142
|
+
session_id: Optional session ID for per-session isolation.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
EmitResult with success status.
|
|
146
|
+
"""
|
|
147
|
+
from raise_cli.telemetry.schemas import SkillEvent
|
|
148
|
+
|
|
149
|
+
signal = SkillEvent(
|
|
150
|
+
timestamp=datetime.now(UTC),
|
|
151
|
+
skill=skill,
|
|
152
|
+
event=event,
|
|
153
|
+
duration_sec=duration_sec,
|
|
154
|
+
)
|
|
155
|
+
return emit(signal, base_path=base_path, session_id=session_id)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def emit_command_usage(
|
|
159
|
+
command: str,
|
|
160
|
+
subcommand: str | None = None,
|
|
161
|
+
*,
|
|
162
|
+
base_path: Path | None = None,
|
|
163
|
+
session_id: str | None = None,
|
|
164
|
+
) -> EmitResult:
|
|
165
|
+
"""Convenience function to emit a command usage event.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
command: Main command name (e.g., "memory").
|
|
169
|
+
subcommand: Subcommand name if any (e.g., "query").
|
|
170
|
+
base_path: Base directory for telemetry.
|
|
171
|
+
session_id: Optional session ID for per-session isolation.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
EmitResult with success status.
|
|
175
|
+
"""
|
|
176
|
+
from raise_cli.telemetry.schemas import CommandUsage
|
|
177
|
+
|
|
178
|
+
signal = CommandUsage(
|
|
179
|
+
timestamp=datetime.now(UTC),
|
|
180
|
+
command=command,
|
|
181
|
+
subcommand=subcommand,
|
|
182
|
+
)
|
|
183
|
+
return emit(signal, base_path=base_path, session_id=session_id)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def emit_error_event(
|
|
187
|
+
tool: str,
|
|
188
|
+
error_type: str,
|
|
189
|
+
context: str,
|
|
190
|
+
recoverable: bool,
|
|
191
|
+
*,
|
|
192
|
+
base_path: Path | None = None,
|
|
193
|
+
session_id: str | None = None,
|
|
194
|
+
) -> EmitResult:
|
|
195
|
+
"""Convenience function to emit an error event.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
tool: Name of the tool that failed (e.g., "Bash").
|
|
199
|
+
error_type: Type of error (e.g., "command_not_found").
|
|
200
|
+
context: Brief context (no sensitive data).
|
|
201
|
+
recoverable: Whether the error was recoverable.
|
|
202
|
+
base_path: Base directory for telemetry.
|
|
203
|
+
session_id: Optional session ID for per-session isolation.
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
EmitResult with success status.
|
|
207
|
+
"""
|
|
208
|
+
from raise_cli.telemetry.schemas import ErrorEvent
|
|
209
|
+
|
|
210
|
+
signal = ErrorEvent(
|
|
211
|
+
timestamp=datetime.now(UTC),
|
|
212
|
+
tool=tool,
|
|
213
|
+
error_type=error_type,
|
|
214
|
+
context=context,
|
|
215
|
+
recoverable=recoverable,
|
|
216
|
+
)
|
|
217
|
+
return emit(signal, base_path=base_path, session_id=session_id)
|
|
File without changes
|