Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
33a78ec
feat(cli): Emit cli_run once per invocation from the runner
thecodedrift Jun 12, 2026
56f8428
feat(cli): Emit concrete rule events; drop rule start/_completed pairs
thecodedrift Jun 13, 2026
eeed8a6
feat(cli): Concrete auth + lifecycle events; drop start/_completed pairs
thecodedrift Jun 13, 2026
931b32f
feat(cli): Collapse help_* into cli_help { topic }; drop cli_info
thecodedrift Jun 13, 2026
7b8603a
chore(openspec): Archive restructure-cli-telemetry and sync specs
thecodedrift Jun 13, 2026
5f31a5d
Merge branch 'feat/rethink-posthog-telemetry' into feat/telemetry-cli…
thecodedrift Jun 13, 2026
8b37cf9
Merge branch 'feat/telemetry-cli-run' into feat/telemetry-rule-events
thecodedrift Jun 13, 2026
f413c9a
Merge branch 'feat/telemetry-rule-events' into feat/telemetry-auth-li…
thecodedrift Jun 13, 2026
4f54029
Merge branch 'feat/telemetry-auth-lifecycle' into feat/telemetry-cli-…
thecodedrift Jun 13, 2026
70f3100
Merge branch 'feat/telemetry-cli-help' into feat/telemetry-finalize
thecodedrift Jun 13, 2026
58f75ee
fix(cli): Resolve cli_run identity fresh; tighten cli_error + drop cl…
thecodedrift Jun 13, 2026
f5b2863
fix(cli): Only emit cli_rule_created/improved when rules are written
thecodedrift Jun 13, 2026
00033ee
refactor(cli): Single-pass check counts; robust cli_installed assertion
thecodedrift Jun 13, 2026
ec1a1ac
test(cli): Assert no legacy help_* event on served-topic and index paths
thecodedrift Jun 13, 2026
1026261
test(cli): Prove help_* removal once via a source scan, not per-test
thecodedrift Jun 13, 2026
1e74b65
fix(cli): Resolve cli_run identity at invocation start, not end
thecodedrift Jun 13, 2026
0b1934b
Merge branch 'feat/rethink-posthog-telemetry' into feat/telemetry-cli…
thecodedrift Jun 13, 2026
0325d92
Merge branch 'feat/telemetry-cli-run' into feat/telemetry-rule-events
thecodedrift Jun 13, 2026
0f19217
Merge branch 'feat/telemetry-rule-events' into feat/telemetry-auth-li…
thecodedrift Jun 13, 2026
f94d46d
Merge branch 'feat/telemetry-auth-lifecycle' into feat/telemetry-cli-…
thecodedrift Jun 13, 2026
fa2d2ed
Merge branch 'feat/telemetry-cli-help' into feat/telemetry-finalize
thecodedrift Jun 13, 2026
20d46d5
docs(openspec): Sync corrected check/help spec into the archived capa…
thecodedrift Jun 13, 2026
cd2c3e7
refactor(cli): Uppercase the CLI acronym in CliError/CliErrorCode
thecodedrift Jun 13, 2026
ca51a57
Merge branch 'feat/telemetry-cli-run' into feat/telemetry-rule-events
thecodedrift Jun 13, 2026
cb202f5
Merge branch 'feat/telemetry-rule-events' into feat/telemetry-auth-li…
thecodedrift Jun 13, 2026
e26152b
Merge branch 'feat/telemetry-auth-lifecycle' into feat/telemetry-cli-…
thecodedrift Jun 13, 2026
e97b43f
Merge branch 'feat/telemetry-cli-help' into feat/telemetry-finalize
thecodedrift Jun 13, 2026
b204084
docs(openspec): Uppercase CLIError/CLIErrorCode in the analytics spec
thecodedrift Jun 13, 2026
0d9ad07
Merge branch 'feat/rethink-posthog-telemetry' into feat/telemetry-cli…
thecodedrift Jun 14, 2026
a8a1146
Merge branch 'feat/telemetry-cli-run' into feat/telemetry-rule-events
thecodedrift Jun 14, 2026
f7c222c
Merge branch 'feat/telemetry-rule-events' into feat/telemetry-auth-li…
thecodedrift Jun 14, 2026
d771118
Merge branch 'feat/telemetry-auth-lifecycle' into feat/telemetry-cli-…
thecodedrift Jun 14, 2026
6665364
Merge branch 'feat/telemetry-cli-help' into feat/telemetry-finalize
thecodedrift Jun 14, 2026
d47c3d8
fix(cli): Carry the CLIErrorCode rename into auth.ts
thecodedrift Jun 14, 2026
64f970a
Merge branch 'feat/telemetry-auth-lifecycle' into feat/telemetry-cli-…
thecodedrift Jun 14, 2026
3e19d2c
Merge branch 'feat/telemetry-cli-help' into feat/telemetry-finalize
thecodedrift Jun 14, 2026
47da415
refactor(cli): Uppercase the acronym in CLIErrorEnvelope too
thecodedrift Jun 14, 2026
ffb2095
Merge branch 'feat/telemetry-cli-run' into feat/telemetry-rule-events
thecodedrift Jun 14, 2026
cc8fecb
Merge branch 'feat/telemetry-rule-events' into feat/telemetry-auth-li…
thecodedrift Jun 14, 2026
82f59ab
Merge branch 'feat/telemetry-auth-lifecycle' into feat/telemetry-cli-…
thecodedrift Jun 14, 2026
51ee8c0
Merge branch 'feat/telemetry-cli-help' into feat/telemetry-finalize
thecodedrift Jun 14, 2026
5b5d22e
refactor(cli): Uppercase the acronym in CLIConfig
thecodedrift Jun 14, 2026
c16f7d6
Merge branch 'feat/telemetry-cli-run' into feat/telemetry-rule-events
thecodedrift Jun 14, 2026
572107f
Merge branch 'feat/telemetry-rule-events' into feat/telemetry-auth-li…
thecodedrift Jun 14, 2026
9bc1e33
Merge branch 'feat/telemetry-auth-lifecycle' into feat/telemetry-cli-…
thecodedrift Jun 14, 2026
2fbf0e5
Merge branch 'feat/telemetry-cli-help' into feat/telemetry-finalize
thecodedrift Jun 14, 2026
ce5ea85
Merge pull request #37 from taskless/feat/telemetry-finalize
thecodedrift Jun 14, 2026
7b575d9
Merge pull request #36 from taskless/feat/telemetry-cli-help
thecodedrift Jun 14, 2026
d579367
Merge pull request #35 from taskless/feat/telemetry-auth-lifecycle
thecodedrift Jun 14, 2026
52a213c
Merge pull request #34 from taskless/feat/telemetry-rule-events
thecodedrift Jun 14, 2026
7b33932
Merge remote-tracking branch 'origin/main' into feat/telemetry-cli-run
thecodedrift Jun 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ auth login success → cli_authenticated { }
auth logout success → cli_logged_out { }
init/install success → cli_installed { targets? }
onboard complete → cli_onboarded { }
check finishes → cli_check_completed{ errorCount, warningCount, filesScanned }
check finishes → cli_check_completed{ errorCount, warningCount, findings }
any command fails → cli_error { command, code }
help served → cli_help { topic } (topic = "(index)" for no-arg,
the attempted topic otherwise)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Tasks

## Phasing — stacked PRs

This change is cut into committable phases, each of which leaves the build and
tests green and maps to one stacked PR (Git Town). Tests travel with the phase
that introduces the behavior — there is no trailing "tests" phase. Phases are
ordered so the stack reads bottom → top:

```
main
└── docs openspec change contract (proposal/design/specs/tasks)
└── phase 1 cli_run denominator + cli_error (runner)
└── phase 2 rule events (created/improved/deleted)
└── phase 3 auth + lifecycle events (auth/install/onboard/check)
└── phase 4 cli_help { topic } + drop info/detect bespoke events
└── phase 5 finalize: sweep, gate, archive ← tip
```

Transitional note: while the stack is mid-flight, a command may briefly emit
both `cli_run` and a soon-to-be-removed legacy event (e.g. after phase 1 but
before phase 2). That dual signal exists only within the unmerged stack; the
hard cut (no dual-emit) holds for the released, fully-merged state. Each phase
keeps the suite green on its own.

## 1. Phase 1 — cli_run denominator + cli_error (PR 1)

- [x] 1.1 In `packages/cli/src/index.ts`, wrap command execution so exactly one `cli_run` is emitted per invocation from a `finally`-equivalent path, with `{ command, cli_version, success, durationMs, anonymous, loggedIn }`
- [x] 1.2 Resolve `command` from the matched citty subcommand (e.g. `"rule create"`, `"help"`); derive `success` from a thrown error / non-zero `process.exitCode`; measure `durationMs` from a start timestamp — extracted to a testable `telemetry-run.ts` (resolveCommandName/resolveCwd/emitRunEvents) so the entry module's side-effecting top level stays untested
- [x] 1.3 Emit `cli_error { command, code }` from the runner's catch path when the failure carries a stable `CLIErrorCode` — added an optional `code` to `CLIError`; falls back to `INTERNAL_ERROR`
- [x] 1.4 Tests: one `cli_run` per invocation (success and failure), and `cli_error` on a known-code failure — `test/cli-run.test.ts`
- [x] 1.5 typecheck + lint + suite green; commit; open PR 1

## 2. Phase 2 — rule concrete-state events (PR 2, on PR 1)

- [x] 2.1 `commands/rules.ts`: remove `cli_rule_create(_completed)`, `cli_rule_improve(_completed)`, `cli_rule_delete(_completed)`, `cli_rule_meta(_completed)`, `cli_rule_verify(_completed)`
- [x] 2.2 Emit `cli_rule_created`, `cli_rule_improved`, `cli_rule_deleted` at the point each state changes (counts/ids/booleans only); `verify`/`meta` are covered by `cli_run` alone (their command-level telemetry was removed entirely)
- [x] 2.3 Update rule command tests to the new events; assert no `cli_rule_*_completed` — only `telemetry.test.ts` referenced an old rule name (a sample), updated to `cli_rule_created`; rule-from/verify tests assert behavior, not events
- [x] 2.4 typecheck + lint + suite green; commit; open PR 2

## 3. Phase 3 — auth + lifecycle events (PR 3, on PR 2)

- [x] 3.1 `commands/auth.ts`: remove `cli_auth_login(_completed)`, `cli_auth_logout(_completed)`, `cli_auth_status(_completed)`; emit `cli_authenticated` (fresh login only) and `cli_logged_out` (token actually removed); status → `cli_run` only
- [x] 3.2 `commands/init.ts` + `wizard/index.ts`: remove `cli_init(_completed)`, `cli_init_cancelled`, `cli_update(_completed)`; emit `cli_installed` on a successful install (interactive + non-interactive + update)
- [x] 3.3 `commands/onboard.ts`: remove `cli_onboard_recipe` / `cli_onboard_already_done`; emit `cli_onboarded` when onboarding is marked complete
- [x] 3.4 `commands/check.ts`: remove `cli_check(_completed)`; emit `cli_check_completed { errorCount, warningCount, findings }` only when a scan actually runs (counts only — no matched code; `findings` replaces the unavailable `filesScanned`)
- [x] 3.5 Update auth/init/onboard/check tests to the new events — wizard-integration assertions updated to `cli_installed` / no-event-on-cancel; telemetry.test sample names → `cli_run`
- [x] 3.6 typecheck + lint + suite green; commit; open PR 3

## 4. Phase 4 — cli_help { topic } + drop bespoke info/detect events (PR 4, on PR 3)

- [x] 4.1 `commands/help.ts`: replace `help_index`, `help_<topic>`, `help_unknown` with one `cli_help { topic }` (served topic, `"(index)"` marker for no-arg, the attempted topic for unknown)
- [x] 4.2 `commands/info.ts`: remove bespoke `cli_info(_completed)` (covered by `cli_run`); also drop its now-unused `getTelemetry` import. NOTE: `detect.ts`/`cli_detect` is NOT on this branch's lineage (it lives in the unmerged local-rule-routing stack) — no change needed here; it will be reconciled when that stack and this one both land
- [x] 4.3 Assert `cli_help` carries `topic` and no `help_*` event — added `test/help-telemetry.test.ts` (served topic, index marker, unknown topic, and no legacy `help_*`)
- [x] 4.4 typecheck + lint + suite green; commit; open PR 4

## 5. Phase 5 — finalize (PR 5, tip)

- [x] 5.1 Grep the CLI for any remaining old event names (`_completed`, `help_index`, `help_<topic>`, `help_unknown`, legacy `cli_<action>` starts); remove any stragglers — clean; the only `_completed` is the intentional concrete event `cli_check_completed`
- [x] 5.2 Run `pnpm openspec validate restructure-cli-telemetry`; `pnpm typecheck`; `pnpm lint`; full suite green (259)
- [x] 5.3 Manual smoke: `info`, `help check`, `help` (index) run end-to-end after the refactor; concrete events + cli_run/cli_help/cli_error verified by the in-process tests
- [x] 5.4 Archive the change (`openspec archive restructure-cli-telemetry`) so the tip carries the spec sync + dated archive; commit; open PR 5
120 changes: 87 additions & 33 deletions openspec/specs/analytics/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,13 @@ Every `capture()` call SHALL include the `cli` property (anonymous UUID), the `c

#### Scenario: Anonymous capture includes standard properties

- **WHEN** `capture("cli_check")` is called without authentication
- **WHEN** `capture("cli_run")` is called without authentication
- **THEN** the event SHALL include `{ cli: anonymousUuid, cliVersion: <string>, scaffoldVersion: <number> }`
- **AND** the event SHALL NOT include a `groups` parameter

#### Scenario: Authenticated capture includes standard properties and group

- **WHEN** `capture("cli_rule_create")` is called with authentication
- **WHEN** `capture("cli_rule_created")` is called with authentication
- **THEN** the event SHALL include `{ cli: anonymousUuid, cliVersion: <string>, scaffoldVersion: <number> }`
- **AND** the `groups` parameter SHALL include `{ organization: String(orgId) }`

Expand All @@ -134,56 +134,87 @@ Every `capture()` call SHALL include the `cli` property (anonymous UUID), the `c

### Requirement: CLI events use cli\_ prefix

CLI action events SHALL continue to use the `cli_` prefix, but the event taxonomy SHALL be reorganized as follows:

- `cli_<action>` — fired when an action command begins execution (e.g. `cli_rule_create`, `cli_rule_improve`, `cli_rule_delete`, `cli_check`, `cli_info`, `cli_init`, `cli_auth_login`, `cli_auth_logout`)
- `cli_<action>_completed` — fired when an action command finishes execution; event properties SHALL include `success: boolean`, `durationMs: number`, and `errorCode?: string` (when failure)
- `help_<topic>` — fired when the help command serves a specific topic (e.g. `help_rule_create`, `help_check`, `help_auth`); replaces previous `cli_help_<topic>` events
- `help_index` — fired when the help command is invoked with no arguments (probable agent confusion / routing failure)
- `help_unknown` — fired when the help command receives an unknown topic; event properties SHALL include `topic: string` (the attempted topic)

The previous event names `cli_help`, `cli_help_auth`, `cli_help_check`, `cli_help_info`, `cli_help_init`, `cli_help_rule` SHALL be removed in this release. There is no dual-emit window — the rename is a hard cut.

#### Scenario: Action command emits start and completion events

- **WHEN** a user runs `taskless rule create --from req.json`
- **THEN** PostHog SHALL receive a `cli_rule_create` event when execution begins
- **AND** SHALL receive a `cli_rule_create_completed` event when execution finishes, with properties including `success`, `durationMs`, and (on failure) `errorCode`

#### Scenario: Help fetch emits topic intent
CLI events SHALL use the `cli_` prefix, with the taxonomy organized as a
`cli_run` denominator plus concrete state-transition events:

- `cli_run` — exactly one per invocation (see the dedicated requirement). This
replaces every previous `cli_<action>` start event and `cli_<action>_completed`
event; the `success`/`durationMs`/`command` signal lives here.
- Concrete state-transition events, each fired at the point the state actually
changes, carrying counts/ids/booleans only (never rule content, prompts, or
matched source):
- `cli_rule_created`, `cli_rule_improved`, `cli_rule_deleted`
- `cli_authenticated`, `cli_logged_out`
- `cli_installed`, `cli_onboarded`
- `cli_check_completed` — error/warning counts only (e.g. `errorCount`,
`warningCount`, `findings`)
- `cli_error` — a single failure event with `command` and `code` (a stable
`CLIErrorCode`)
- `cli_help` — fired when the help command serves a request, with a `topic`
property (the served topic; the exact literal `"(index)"` when invoked with no
topic; the attempted topic for an unknown request). This replaces the previous
`help_index`, `help_<topic>`, and `help_unknown` events.

Commands that carry no concrete state beyond the invocation (e.g. `info`,
`detect`, `update`, `auth status`, `rule verify`, `rule meta`) SHALL rely on
`cli_run` alone and SHALL NOT emit a bespoke event. The previous taxonomy
(`cli_<action>`, `cli_<action>_completed`, `help_index`, `help_<topic>`,
`help_unknown`) SHALL be removed in this release; there is no dual-emit window.

#### Scenario: Rule creation emits a concrete state event plus cli_run

- **WHEN** a user runs `taskless rule create --from req.json` and a rule is written
- **THEN** PostHog SHALL receive one `cli_run` event with `command: "rule create"`
- **AND** SHALL receive a `cli_rule_created` event
- **AND** SHALL NOT receive `cli_rule_create` or `cli_rule_create_completed`

#### Scenario: Help fetch emits cli_help with a topic

- **WHEN** an agent runs `taskless help rule create`
- **THEN** PostHog SHALL receive a `help_rule_create` event
- **THEN** PostHog SHALL receive a `cli_help` event with `topic: "rule create"`
- **AND** SHALL NOT receive a `help_rule_create` event

#### Scenario: Help no-args emits index event
#### Scenario: Help with no topic emits cli_help with the index marker

- **WHEN** an agent runs `taskless help`
- **THEN** PostHog SHALL receive a `help_index` event
- **THEN** PostHog SHALL receive a `cli_help` event with `topic: "(index)"`
- **AND** SHALL NOT receive a `help_index` event

#### Scenario: Help unknown topic emits help_unknown
#### Scenario: A command failure emits cli_error

- **WHEN** an agent runs `taskless help nonexistent`
- **THEN** PostHog SHALL receive a `help_unknown` event with property `topic: "nonexistent"`
- **WHEN** a command fails with a known `CLIErrorCode`
- **THEN** PostHog SHALL receive a `cli_error` event with `command` and `code`

#### Scenario: Old event names are not emitted

- **WHEN** any CLI command runs in v0.7.0
- **THEN** PostHog SHALL NOT receive any event named `cli_help`, `cli_help_<topic>`, or any other event under the previous taxonomy
- **WHEN** any CLI command runs in this release
- **THEN** PostHog SHALL NOT receive any event named `cli_<action>_completed`,
`help_index`, `help_<topic>`, or `help_unknown`

### Requirement: Wrong-topic re-routing is observable as a derivable funnel

The new event taxonomy is structured so that wrong-topic re-routing is a derivable funnel signal:
The taxonomy SHALL keep wrong-topic re-routing derivable as a funnel signal from
the new events:

- A `help_<topic_a>` event followed by no `cli_<action_a>` event AND a subsequent `help_<topic_b>` event indicates the agent fetched the recipe for topic A, did not act on it, and re-routed to topic B
- A `help_index` event followed by a `help_<topic>` event indicates the agent consulted the index before picking a topic (expected behavior; baseline)
- A `help_<topic>` event with no subsequent `cli_<action>` event AND no further `help_*` event indicates the agent abandoned the action
- A `cli_help { topic: A }` event not followed by the concrete event for topic A
(or by `cli_run` with the corresponding `command`), and then a subsequent
`cli_help { topic: B }`, indicates the agent fetched recipe A, did not act on
it, and re-routed to topic B.
- A `cli_help` index-marker event followed by a `cli_help { topic }` event
indicates the agent consulted the index before picking a topic (baseline).
- A `cli_help { topic }` event with no subsequent acting `cli_run` and no further
`cli_help` event indicates the agent abandoned the action.

No additional events SHALL be added to capture this signal directly — the funnel is derivable from the event sequence in PostHog. Dashboards SHOULD be created to surface re-routing rates per topic so wrong-topic confusion can be measured.
No additional events SHALL be added to capture this signal directly — it is
derivable from the `cli_help` / `cli_run` sequence. Dashboards SHOULD surface
re-routing rates per topic.

#### Scenario: Funnel data supports wrong-topic detection

- **WHEN** dashboards are constructed in PostHog
- **THEN** the events SHALL be sufficient to compute "rate of `help_<topic>` events not followed by a corresponding `cli_<action>` event within N minutes"
- **THEN** the `cli_help` (with `topic`) and `cli_run` (with `command`) events
SHALL be sufficient to compute "rate of `cli_help { topic }` not followed by a
corresponding acting `cli_run` within N minutes"

### Requirement: Telemetry failures are silent

Expand Down Expand Up @@ -218,3 +249,26 @@ Each command handler SHALL call `getTelemetry(cwd)` to lazily initialize the sin

- **WHEN** the CLI exits without running a command (e.g. showing top-level help)
- **THEN** `shutdownTelemetry()` SHALL be a no-op and no PostHog client SHALL be created

### Requirement: Every invocation emits exactly one cli_run event

The CLI SHALL emit exactly one `cli_run` event per invocation, from the top-level
runner rather than from individual commands. The event SHALL carry the properties
`command` (the resolved subcommand name, e.g. `"rule create"` or `"help"`),
`cli_version`, `success` (boolean), `durationMs` (number), `anonymous` (boolean),
and `loggedIn` (boolean). The event SHALL be emitted on both success and failure
(from a `finally`-equivalent path), and no command SHALL emit its own
"started" or "ran" event.

#### Scenario: A successful command emits one cli_run

- **WHEN** a user runs `taskless info`
- **THEN** PostHog SHALL receive exactly one `cli_run` event with
`command: "info"`, `success: true`, a numeric `durationMs`, and the
`cli_version`, `anonymous`, and `loggedIn` properties
- **AND** SHALL NOT receive a separate `cli_info` or `cli_info_completed` event

#### Scenario: A failing command still emits cli_run

- **WHEN** a command exits with an error
- **THEN** PostHog SHALL receive one `cli_run` event with `success: false`
6 changes: 3 additions & 3 deletions packages/cli/src/api/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ import { getConfigDirectory } from "../auth/token";
const DEFAULT_BASE_URL = "https://app.taskless.io/cli";
const CONFIG_FILE = "config.json";

interface CliConfig {
interface CLIConfig {
apiUrl?: string;
}

function readConfigFile(): CliConfig | undefined {
function readConfigFile(): CLIConfig | undefined {
try {
const filePath = join(getConfigDirectory(), CONFIG_FILE);
const raw = readFileSync(filePath, "utf8");
return JSON.parse(raw) as CliConfig;
return JSON.parse(raw) as CLIConfig;
} catch {
return undefined;
}
Expand Down
Loading