feat(feedback): add feedback send/dismiss, the survey constants, and the cadence store - #343
Conversation
…the cadence store The PostHog survey's id and question map live in src/survey/constants.ts and nowhere else; the payload the agent writes uses human keys and feedback send translates. survey sent carries $survey_id and one $survey_response_<id> per answered question, survey dismissed carries $survey_id, and neither adds a survey-specific property of its own. next_ask lives under surveys/<survey id>/ in the XDG config directory as a bare epoch; both verbs push it out 20 days. Under the telemetry opt-out both say nothing was sent and exit 0. feedback is unlisted in the agent index: the invite is the only door.
93c46ea to
10b54ee
Compare
|
@claude /review |
|
Claude finished @thecodedrift's task in 5m 14s —— View job Review complete
Review mode: incremental — read 0 prior review thread(s) before reviewing. SummaryThis is a clean, well-tested slice. I read every changed file ( Things I specifically checked and found correct:
One finding, posted inline on
Everything else — cadence file corruption handling, the smuggled- Branch: |
Read. The one finding ( — AI Coding Agent |
resolveCommandName only kept the subcommand for rule, so feedback send and feedback dismiss both reported as "feedback" even though they are distinct actions: send can fail with INVALID_INPUT, dismiss cannot, and a cli_error for "feedback" could not say which one it came from. The special case is now a small declared set of verb-carrying commands (rule, feedback) rather than a second if.
Stack (root → tip):
Slice 2 of 4 of
qualitative-survey-feedback(stacked on #342). Reachable only by someone who types it: nothing serves an invite until slice 4.What
src/survey/constants.ts: the survey id, the five{ key, id, question }entries (Q3's id is the post-single-choice one,6ebdfabb-…),COMPLETED_CHOICESin PostHog's casing, the four surveyed topics (used only by the gate), and the 10-day / 20-day intervals. The only place the identifiers live.src/survey/cadence.ts:next_askunder<XDG config>/taskless/surveys/<survey id>/, a bare epoch-ms string. Absent or unparseable reads as "ask now"; writes are best-effort.src/schemas/feedback.ts: human keys only (verbatim,goal,completed, optionalworkedWell/needsImprovement). A blank optional is rejected; an omitted one is unanswered.src/commands/feedback.ts:dismisscapturessurvey dismissed { $survey_id };send --from <file>validates, capturessurvey sent { $survey_id, $survey_response_<id>… }with unanswered optionals absent, and leaves the file in place (the recipe's clean-up step deletes it;/.tmp-*is ignored regardless). Both pushnext_askout 20 days. Under the opt-out both print that nothing was sent and exit 0;sendvalidates first so a bad payload is reported either way.isTelemetryEnabled()exported fromtelemetry.tsrather than restating the two variables.feedbackjoinsSUBCOMMAND_NAMESandUNLISTED_COMMANDS: the invite is the only door, pending a general feedback channel.Tests
survey-cadence.test.ts: constants pinned to the live survey; store round-trip, absent, corrupt-then-repaired, per-survey isolation (withXDG_CONFIG_HOMEin a temp dir).feedback-schema.test.ts: enum, missing required, blank optional, and that a smuggled$survey_*key is stripped.feedback-command.test.ts: both verbs with telemetry mocked (exact event shapes,next_askadvance, opt-out,INVALID_INPUTnaming the field, no.taskless/bootstrap), plus the built CLI: absent fromagent,--helplists both verbs,--jsonerror envelope.Refs #341