diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json index 0f8c39d..e0ae4c7 100644 --- a/.codex-plugin/plugin.json +++ b/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "computer-usage-summary-skill", - "version": "0.2.2", + "version": "0.2.3", "description": "Privacy-first, cross-platform local ActivityWatch summaries for Codex.", "author": { "name": "liuyewang", diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index c4df5fc..ed5dd39 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -4,6 +4,14 @@ This is a privacy-safe product and validation log. It records decisions and feedback themes, never ActivityWatch events, window titles, URLs, account names, local paths, or behavioral telemetry. +## 2026-07-27 - v0.2.3 Standardized Conversational Output + +- Standardized conversational summaries around `Today at a glance`, `What you + did`, `App table`, and `Caveats`. +- Kept the four-section structure for unavailable ActivityWatch data and made + reduced output opt-in when the user explicitly requests it. +- Added a regression test for the default conversational output contract. + ## 2026-07-27 - v0.2.2 Review Fixes - Convert invalid IANA time-zone input into a clean CLI validation error. diff --git a/skills/computer-usage-summary/SKILL.md b/skills/computer-usage-summary/SKILL.md index 7d22de8..b90434b 100644 --- a/skills/computer-usage-summary/SKILL.md +++ b/skills/computer-usage-summary/SKILL.md @@ -80,6 +80,41 @@ When the app, local API, buckets, or requested-range events are unavailable: 7. When ActivityWatch is unavailable, return its structured reason. Markdown, TSV, and CSV outputs provide `status`, `reason`, and `source` columns. +## Default Conversational Output + +For conversational summaries, always use the following four sections, in this +order, unless the user explicitly asks for a simpler or shorter output: + +1. `Today at a glance` + - State the local date and time zone. + - Report confirmed active time, away time, and evidence coverage. + - In fallback mode, state that unavailable values are `unavailable`; do not + estimate them. +2. `What you did` + - Give a chronological list of meaningful activity windows. + - Include the time window, activity, supporting app or sanitized title, + confidence, and evidence label (`confirmed ActivityWatch`, `Screen Time + reported`, `live snapshot`, or `metadata clue`). +3. `App table` + - Include app name, observed foreground sessions, active time, first seen, + last active, and source note when ActivityWatch data is available. + - If data is unavailable, keep this section and show the structured reason + with `unavailable` metrics rather than omitting the table. +4. `Caveats` + - State the relevant privacy and evidence limits, including that foreground + sessions are observed intervals rather than process-launch counts and that + tracking starts only when ActivityWatch starts. + +Keep these sections even when one contains little or no data. A request such as +"简单一点", "简要输出", "只要结论", or "只给表格" is an explicit request +for a reduced format; then omit or compress only the sections the user asks to +remove. Do not infer a reduced format merely because the request is short. + +For a requested export (`TSV`, `CSV`, or a saved report), follow the export +format the user requested instead of wrapping it in these conversational +headings. If the user asks for both a summary and an export, provide the four +conversational sections first and then the requested export. + ## Privacy Rules - Treat window titles as sensitive. Remove URLs, truncate long titles, and diff --git a/tests/test_activitywatch_summary.py b/tests/test_activitywatch_summary.py index 9b3b3ae..71b6770 100644 --- a/tests/test_activitywatch_summary.py +++ b/tests/test_activitywatch_summary.py @@ -175,6 +175,19 @@ def test_skill_frontmatter_is_present(self): self.assertTrue(skill.startswith("---\nname: computer-usage-summary\n")) self.assertIn("description:", skill.split("---", 2)[1]) + def test_skill_default_conversational_output_contract(self): + skill = (SKILL_ROOT / "SKILL.md").read_text(encoding="utf-8") + headings = [ + "`Today at a glance`", + "`What you did`", + "`App table`", + "`Caveats`", + ] + positions = [skill.index(heading) for heading in headings] + self.assertEqual(positions, sorted(positions)) + self.assertIn("unless the user explicitly asks for a simpler or shorter output", skill) + self.assertIn("Do not infer a reduced format merely because the request is short.", skill) + if __name__ == "__main__": unittest.main()