@laitszkin/apollo-toolkit 2.0.0

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.
Files changed (204) hide show
  1. package/AGENTS.md +62 -0
  2. package/CHANGELOG.md +100 -0
  3. package/LICENSE +21 -0
  4. package/README.md +144 -0
  5. package/align-project-documents/SKILL.md +94 -0
  6. package/align-project-documents/agents/openai.yaml +4 -0
  7. package/analyse-app-logs/LICENSE +21 -0
  8. package/analyse-app-logs/README.md +126 -0
  9. package/analyse-app-logs/SKILL.md +121 -0
  10. package/analyse-app-logs/agents/openai.yaml +4 -0
  11. package/analyse-app-logs/references/investigation-checklist.md +58 -0
  12. package/analyse-app-logs/references/log-signal-patterns.md +52 -0
  13. package/answering-questions-with-research/SKILL.md +46 -0
  14. package/answering-questions-with-research/agents/openai.yaml +4 -0
  15. package/bin/apollo-toolkit.js +7 -0
  16. package/commit-and-push/LICENSE +21 -0
  17. package/commit-and-push/README.md +26 -0
  18. package/commit-and-push/SKILL.md +70 -0
  19. package/commit-and-push/agents/openai.yaml +4 -0
  20. package/commit-and-push/references/branch-naming.md +15 -0
  21. package/commit-and-push/references/commit-messages.md +19 -0
  22. package/deep-research-topics/LICENSE +21 -0
  23. package/deep-research-topics/README.md +43 -0
  24. package/deep-research-topics/SKILL.md +84 -0
  25. package/deep-research-topics/agents/openai.yaml +4 -0
  26. package/develop-new-features/LICENSE +21 -0
  27. package/develop-new-features/README.md +52 -0
  28. package/develop-new-features/SKILL.md +105 -0
  29. package/develop-new-features/agents/openai.yaml +4 -0
  30. package/develop-new-features/references/testing-e2e.md +35 -0
  31. package/develop-new-features/references/testing-integration.md +42 -0
  32. package/develop-new-features/references/testing-property-based.md +44 -0
  33. package/develop-new-features/references/testing-unit.md +37 -0
  34. package/discover-edge-cases/CHANGELOG.md +19 -0
  35. package/discover-edge-cases/LICENSE +21 -0
  36. package/discover-edge-cases/README.md +87 -0
  37. package/discover-edge-cases/SKILL.md +124 -0
  38. package/discover-edge-cases/agents/openai.yaml +4 -0
  39. package/discover-edge-cases/references/architecture-edge-cases.md +41 -0
  40. package/discover-edge-cases/references/code-edge-cases.md +46 -0
  41. package/docs-to-voice/.env.example +106 -0
  42. package/docs-to-voice/CHANGELOG.md +71 -0
  43. package/docs-to-voice/LICENSE +21 -0
  44. package/docs-to-voice/README.md +118 -0
  45. package/docs-to-voice/SKILL.md +107 -0
  46. package/docs-to-voice/agents/openai.yaml +4 -0
  47. package/docs-to-voice/scripts/docs_to_voice.py +1385 -0
  48. package/docs-to-voice/scripts/docs_to_voice.sh +11 -0
  49. package/docs-to-voice/tests/test_docs_to_voice_api_max_chars.py +210 -0
  50. package/docs-to-voice/tests/test_docs_to_voice_sentence_timeline.py +115 -0
  51. package/docs-to-voice/tests/test_docs_to_voice_settings.py +43 -0
  52. package/docs-to-voice/tests/test_docs_to_voice_speech_rate.py +57 -0
  53. package/enhance-existing-features/CHANGELOG.md +35 -0
  54. package/enhance-existing-features/LICENSE +21 -0
  55. package/enhance-existing-features/README.md +54 -0
  56. package/enhance-existing-features/SKILL.md +120 -0
  57. package/enhance-existing-features/agents/openai.yaml +4 -0
  58. package/enhance-existing-features/references/e2e-tests.md +25 -0
  59. package/enhance-existing-features/references/integration-tests.md +30 -0
  60. package/enhance-existing-features/references/property-based-tests.md +33 -0
  61. package/enhance-existing-features/references/unit-tests.md +29 -0
  62. package/feature-propose/LICENSE +21 -0
  63. package/feature-propose/README.md +23 -0
  64. package/feature-propose/SKILL.md +107 -0
  65. package/feature-propose/agents/openai.yaml +4 -0
  66. package/feature-propose/references/enhancement-features.md +25 -0
  67. package/feature-propose/references/important-features.md +25 -0
  68. package/feature-propose/references/mvp-features.md +25 -0
  69. package/feature-propose/references/performance-features.md +25 -0
  70. package/financial-research/SKILL.md +208 -0
  71. package/financial-research/agents/openai.yaml +4 -0
  72. package/financial-research/assets/weekly_market_report_template.md +45 -0
  73. package/fix-github-issues/SKILL.md +98 -0
  74. package/fix-github-issues/agents/openai.yaml +4 -0
  75. package/fix-github-issues/scripts/list_issues.py +148 -0
  76. package/fix-github-issues/tests/test_list_issues.py +127 -0
  77. package/generate-spec/LICENSE +21 -0
  78. package/generate-spec/README.md +61 -0
  79. package/generate-spec/SKILL.md +96 -0
  80. package/generate-spec/agents/openai.yaml +4 -0
  81. package/generate-spec/references/templates/checklist.md +78 -0
  82. package/generate-spec/references/templates/spec.md +55 -0
  83. package/generate-spec/references/templates/tasks.md +35 -0
  84. package/generate-spec/scripts/create-specs +123 -0
  85. package/harden-app-security/CHANGELOG.md +27 -0
  86. package/harden-app-security/LICENSE +21 -0
  87. package/harden-app-security/README.md +46 -0
  88. package/harden-app-security/SKILL.md +127 -0
  89. package/harden-app-security/agents/openai.yaml +4 -0
  90. package/harden-app-security/references/agent-attack-catalog.md +117 -0
  91. package/harden-app-security/references/common-software-attack-catalog.md +168 -0
  92. package/harden-app-security/references/red-team-extreme-scenarios.md +81 -0
  93. package/harden-app-security/references/risk-checklist.md +78 -0
  94. package/harden-app-security/references/security-test-patterns-agent.md +101 -0
  95. package/harden-app-security/references/security-test-patterns-finance.md +88 -0
  96. package/harden-app-security/references/test-snippets.md +73 -0
  97. package/improve-observability/SKILL.md +114 -0
  98. package/improve-observability/agents/openai.yaml +4 -0
  99. package/learn-skill-from-conversations/CHANGELOG.md +15 -0
  100. package/learn-skill-from-conversations/LICENSE +22 -0
  101. package/learn-skill-from-conversations/README.md +47 -0
  102. package/learn-skill-from-conversations/SKILL.md +85 -0
  103. package/learn-skill-from-conversations/agents/openai.yaml +4 -0
  104. package/learn-skill-from-conversations/scripts/extract_recent_conversations.py +369 -0
  105. package/learn-skill-from-conversations/tests/test_extract_recent_conversations.py +176 -0
  106. package/learning-error-book/SKILL.md +112 -0
  107. package/learning-error-book/agents/openai.yaml +4 -0
  108. package/learning-error-book/assets/error_book_template.md +66 -0
  109. package/learning-error-book/scripts/render_markdown_to_pdf.py +367 -0
  110. package/lib/cli.js +338 -0
  111. package/lib/installer.js +225 -0
  112. package/maintain-project-constraints/SKILL.md +109 -0
  113. package/maintain-project-constraints/agents/openai.yaml +4 -0
  114. package/maintain-skill-catalog/README.md +18 -0
  115. package/maintain-skill-catalog/SKILL.md +66 -0
  116. package/maintain-skill-catalog/agents/openai.yaml +4 -0
  117. package/novel-to-short-video/CHANGELOG.md +53 -0
  118. package/novel-to-short-video/LICENSE +21 -0
  119. package/novel-to-short-video/README.md +63 -0
  120. package/novel-to-short-video/SKILL.md +233 -0
  121. package/novel-to-short-video/agents/openai.yaml +4 -0
  122. package/novel-to-short-video/references/plan-template.md +71 -0
  123. package/novel-to-short-video/references/roles-json.md +41 -0
  124. package/open-github-issue/LICENSE +21 -0
  125. package/open-github-issue/README.md +97 -0
  126. package/open-github-issue/SKILL.md +119 -0
  127. package/open-github-issue/agents/openai.yaml +4 -0
  128. package/open-github-issue/scripts/open_github_issue.py +380 -0
  129. package/open-github-issue/tests/test_open_github_issue.py +159 -0
  130. package/open-source-pr-workflow/CHANGELOG.md +32 -0
  131. package/open-source-pr-workflow/LICENSE +21 -0
  132. package/open-source-pr-workflow/README.md +23 -0
  133. package/open-source-pr-workflow/SKILL.md +123 -0
  134. package/open-source-pr-workflow/agents/openai.yaml +4 -0
  135. package/openai-text-to-image-storyboard/.env.example +10 -0
  136. package/openai-text-to-image-storyboard/CHANGELOG.md +49 -0
  137. package/openai-text-to-image-storyboard/LICENSE +21 -0
  138. package/openai-text-to-image-storyboard/README.md +99 -0
  139. package/openai-text-to-image-storyboard/SKILL.md +107 -0
  140. package/openai-text-to-image-storyboard/agents/openai.yaml +4 -0
  141. package/openai-text-to-image-storyboard/scripts/generate_storyboard_images.py +763 -0
  142. package/package.json +36 -0
  143. package/record-spending/SKILL.md +113 -0
  144. package/record-spending/agents/openai.yaml +4 -0
  145. package/record-spending/references/account-format.md +33 -0
  146. package/record-spending/references/workbook-layout.md +84 -0
  147. package/resolve-review-comments/SKILL.md +122 -0
  148. package/resolve-review-comments/agents/openai.yaml +4 -0
  149. package/resolve-review-comments/references/adoption-criteria.md +23 -0
  150. package/resolve-review-comments/scripts/review_threads.py +425 -0
  151. package/resolve-review-comments/tests/test_review_threads.py +74 -0
  152. package/review-change-set/LICENSE +21 -0
  153. package/review-change-set/README.md +55 -0
  154. package/review-change-set/SKILL.md +103 -0
  155. package/review-change-set/agents/openai.yaml +4 -0
  156. package/review-codebases/LICENSE +21 -0
  157. package/review-codebases/README.md +67 -0
  158. package/review-codebases/SKILL.md +109 -0
  159. package/review-codebases/agents/openai.yaml +4 -0
  160. package/scripts/install_skills.ps1 +283 -0
  161. package/scripts/install_skills.sh +262 -0
  162. package/scripts/validate_openai_agent_config.py +194 -0
  163. package/scripts/validate_skill_frontmatter.py +110 -0
  164. package/specs-to-project-docs/LICENSE +21 -0
  165. package/specs-to-project-docs/README.md +57 -0
  166. package/specs-to-project-docs/SKILL.md +111 -0
  167. package/specs-to-project-docs/agents/openai.yaml +4 -0
  168. package/specs-to-project-docs/references/templates/architecture.md +29 -0
  169. package/specs-to-project-docs/references/templates/configuration.md +29 -0
  170. package/specs-to-project-docs/references/templates/developer-guide.md +33 -0
  171. package/specs-to-project-docs/references/templates/docs-index.md +39 -0
  172. package/specs-to-project-docs/references/templates/features.md +25 -0
  173. package/specs-to-project-docs/references/templates/getting-started.md +38 -0
  174. package/specs-to-project-docs/references/templates/readme.md +49 -0
  175. package/systematic-debug/LICENSE +21 -0
  176. package/systematic-debug/README.md +81 -0
  177. package/systematic-debug/SKILL.md +59 -0
  178. package/systematic-debug/agents/openai.yaml +4 -0
  179. package/text-to-short-video/.env.example +36 -0
  180. package/text-to-short-video/LICENSE +21 -0
  181. package/text-to-short-video/README.md +82 -0
  182. package/text-to-short-video/SKILL.md +221 -0
  183. package/text-to-short-video/agents/openai.yaml +4 -0
  184. package/text-to-short-video/scripts/enforce_video_aspect_ratio.py +350 -0
  185. package/version-release/CHANGELOG.md +53 -0
  186. package/version-release/LICENSE +21 -0
  187. package/version-release/README.md +28 -0
  188. package/version-release/SKILL.md +94 -0
  189. package/version-release/agents/openai.yaml +4 -0
  190. package/version-release/references/branch-naming.md +15 -0
  191. package/version-release/references/changelog-writing.md +8 -0
  192. package/version-release/references/commit-messages.md +19 -0
  193. package/version-release/references/readme-writing.md +12 -0
  194. package/version-release/references/semantic-versioning.md +12 -0
  195. package/video-production/CHANGELOG.md +104 -0
  196. package/video-production/LICENSE +18 -0
  197. package/video-production/README.md +68 -0
  198. package/video-production/SKILL.md +213 -0
  199. package/video-production/agents/openai.yaml +4 -0
  200. package/video-production/references/plan-template.md +54 -0
  201. package/video-production/references/roles-json.md +41 -0
  202. package/weekly-financial-event-report/SKILL.md +195 -0
  203. package/weekly-financial-event-report/agents/openai.yaml +4 -0
  204. package/weekly-financial-event-report/assets/financial_event_report_template.md +53 -0
@@ -0,0 +1,114 @@
1
+ ---
2
+ name: improve-observability
3
+ description: Add focused observability to an existing system so opaque workflows become diagnosable. Use when users ask to improve observability, add instrumentation, expand logs/metrics/traces, expose failure reasons, or make a business flow easier to debug without changing the product behavior itself.
4
+ ---
5
+
6
+ # Improve Observability
7
+
8
+ ## Dependencies
9
+
10
+ - Required: none.
11
+ - Conditional: none.
12
+ - Optional: none.
13
+ - Fallback: not applicable.
14
+
15
+ ## Standards
16
+
17
+ - Evidence: Read the real execution path and current telemetry before deciding where visibility actually disappears.
18
+ - Execution: Add the smallest useful instrumentation around decision points, scope contracts, outcomes, and failure reasons.
19
+ - Quality: Keep changes behavior-neutral, use structured high-signal telemetry, avoid secrets, and lock the signals with tests.
20
+ - Output: Report which stages are now observable, which fields or metrics to inspect, and which tests validate the instrumentation.
21
+
22
+ ## Overview
23
+
24
+ Use this skill to make a hard-to-debug path observable with minimal, evidence-driven changes. Prefer small, high-signal instrumentation around decision points, inputs, outcomes, and failure reasons rather than broad logging spam.
25
+
26
+ ## When To Use
27
+
28
+ Use this skill when the user asks to:
29
+
30
+ - add observability to an existing feature or service
31
+ - expose why a workflow fails, stalls, retries, or exits early
32
+ - enrich logs around a specific bug, incident, queue job, API call, or state transition
33
+ - add metrics, traces, or structured fields so operators can isolate root cause faster
34
+ - improve debugging without redesigning the whole subsystem
35
+
36
+ Do not use this skill for generic bug fixing when the main request is behavior change rather than instrumentation.
37
+
38
+ ## Workflow
39
+
40
+ ### 1. Trace the real execution path
41
+
42
+ - Read the relevant entrypoints, orchestration layers, and current telemetry before editing.
43
+ - Identify the exact stages where information disappears: validation, branching, external calls, persistence, retries, settlement, cleanup, or error handling.
44
+ - Reuse the project's existing logger, tracing library, metric naming style, and error taxonomy.
45
+
46
+ ### 2. Choose the smallest useful signals
47
+
48
+ Add instrumentation only where it helps answer a concrete debugging question. Prefer:
49
+
50
+ - stable request or job identifiers for cross-log correlation
51
+ - structured fields for branch conditions, entity ids, counts, amounts, status, and reason codes
52
+ - start/end markers for long multi-step flows
53
+ - explicit logs for skipped paths and early returns
54
+ - metrics or counters for outcome classes when aggregates matter
55
+ - trace spans only when the project already uses tracing or timing data is necessary
56
+
57
+ Avoid logging secrets, full payload dumps, or highly volatile text that breaks searchability.
58
+
59
+ ### 3. Instrument decision points, not just failures
60
+
61
+ For each critical stage, make these states observable when relevant:
62
+
63
+ - entered the stage
64
+ - key preconditions or derived scope
65
+ - branch selected
66
+ - external dependency result
67
+ - persisted side effect or emitted command
68
+ - final outcome and failure reason
69
+
70
+ If a failure is already logged, improve its context instead of duplicating another generic error line.
71
+
72
+ ### 3.1 Preserve cross-stage scope contracts
73
+
74
+ When a workflow derives scope in one stage and consumes it later, make that contract observable end-to-end.
75
+
76
+ - log the derived scope close to where it is computed
77
+ - carry the same identifiers into downstream stages so operators can diff them directly
78
+ - add explicit `missing_*` and `extra_*` fields when one stage should be a superset or exact match of another
79
+ - prefer fail-fast diagnostics when a scope mismatch makes downstream errors ambiguous
80
+
81
+ This is especially useful for pipelines such as discover -> precheck -> execution, where the real bug is often "stage B saw a different dependency set than stage A prepared".
82
+
83
+ ### 4. Keep changes behavior-neutral
84
+
85
+ - Do not silently change business logic while adding observability.
86
+ - If a tiny safety fix is required to support the instrumentation, isolate it and explain why.
87
+ - Prefer additive fields over renamed fields unless the old format is actively harmful.
88
+
89
+ ### 5. Lock the signals with tests
90
+
91
+ Add or update tests that prove the new observability survives refactors. Focus on:
92
+
93
+ - emitted log fields or reason codes for the important branches
94
+ - metrics increments for success, skip, and failure paths
95
+ - regression coverage for the exact opaque scenario that motivated the work
96
+ - edge paths such as early-return, dependency failure, and partial completion
97
+
98
+ Use existing test helpers for log capture and avoid brittle assertions on timestamps or fully formatted log strings.
99
+
100
+ ## Output Expectations
101
+
102
+ When finishing the task:
103
+
104
+ - explain which stages are now observable
105
+ - point to the key log fields, metrics, or spans that operators should inspect
106
+ - mention any still-blind areas if they remain outside scope
107
+ - run the most relevant tests for the touched instrumentation
108
+
109
+ ## Guardrails
110
+
111
+ - Prefer structured, searchable telemetry over prose-heavy logs.
112
+ - Minimize volume; high-signal beats high-noise.
113
+ - Never add secrets, tokens, credentials, or raw personal data to telemetry.
114
+ - Match existing naming conventions so dashboards and log queries stay coherent.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Improve observability"
3
+ short_description: "Add targeted observability to debug hard-to-explain code paths."
4
+ default_prompt: "Use $improve-observability to add focused logs, traces, metrics, and tests around a failing or opaque workflow so the root cause becomes observable without changing product behavior."
@@ -0,0 +1,15 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
+
7
+ ## [v0.1.0] - 2026-02-17
8
+
9
+ ### Added
10
+ - Added edge-case tests for extractor limit handling and latest-session selection.
11
+
12
+ ### Changed
13
+ - Changed extractor default behavior to return all sessions in the last hour when `--limit` is omitted.
14
+ - Updated documentation and skill instructions to remove the fixed `--limit 10` usage.
15
+ - Kept `--limit` as an optional override for explicitly bounded extraction.
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yamiyorunoshura
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,47 @@
1
+ # learn-skill-from-conversations
2
+
3
+ Learn and evolve your local Codex skill library from recent conversation logs.
4
+
5
+ This skill extracts the latest conversations from `~/.codex/sessions` and `~/.codex/archived_sessions`, identifies reusable lessons, and applies skill updates through `skill-creator`.
6
+
7
+ ## Highlights
8
+
9
+ - Reads all sessions from the last 24 hours
10
+ - Stops immediately when there are no recent sessions
11
+ - Cleans up `sessions` files older than 7 days after reading
12
+ - Deletes `archived_sessions` files after reading them
13
+ - Defaults to creating a new skill unless strong overlap is confirmed
14
+ - Validates each changed skill with `quick_validate.py`
15
+
16
+ ## Project Structure
17
+
18
+ ```text
19
+ .
20
+ ├── SKILL.md
21
+ ├── agents/
22
+ │ └── openai.yaml
23
+ └── scripts/
24
+ └── extract_recent_conversations.py
25
+ ```
26
+
27
+ ## Requirements
28
+
29
+ - Python 3.9+
30
+ - Access to `~/.codex/sessions`
31
+ - Access to `~/.codex/archived_sessions`
32
+ - Local `skill-creator` skill at `~/.codex/skills/.system/skill-creator`
33
+
34
+ ## Quick Start
35
+
36
+ Run the extractor:
37
+
38
+ ```bash
39
+ python3 scripts/extract_recent_conversations.py --lookback-minutes 1440
40
+ ```
41
+
42
+ - If output is `NO_RECENT_CONVERSATIONS`, no action is required.
43
+ - Otherwise, review extracted `[USER]` / `[ASSISTANT]` messages and apply updates through `skill-creator`.
44
+
45
+ ## License
46
+
47
+ MIT. See `LICENSE` for details.
@@ -0,0 +1,85 @@
1
+ ---
2
+ name: learn-skill-from-conversations
3
+ description: Learn and evolve the local skill library from recent Codex conversation logs. Use when users ask to learn from past chats, mine ~/.codex/sessions or ~/.codex/archived_sessions, or automatically create/update skills from recurring lessons. The workflow reads all sessions from the last 24 hours, exits immediately if none exist, cleans up stale session files after reading, and applies skill changes through skill-creator with a default bias toward creating new skills.
4
+ ---
5
+
6
+ # Learn Skill from Conversations
7
+
8
+ ## Dependencies
9
+
10
+ - Required: `skill-creator` for all skill creation or update work.
11
+ - Conditional: none.
12
+ - Optional: none.
13
+ - Fallback: If skill changes cannot be delegated through `skill-creator`, stop and report the blocked dependency.
14
+
15
+ ## Standards
16
+
17
+ - Evidence: Extract recent Codex session history first and derive reusable lessons only from actual conversation patterns.
18
+ - Execution: Prefer creating a new skill unless the overlap with an existing skill is clearly strong, then apply the change through `skill-creator`.
19
+ - Quality: Take no action when there are no recent sessions, avoid unrelated broad refactors, and validate every changed skill.
20
+ - Output: Report the analyzed sessions, extracted lessons, created or updated skills, and the reasoning behind each decision.
21
+
22
+ ## Overview
23
+
24
+ Extract recent conversations, identify reusable lessons, and convert those lessons into concrete skill updates.
25
+
26
+ ## Required Resources
27
+
28
+ - `scripts/extract_recent_conversations.py` for deterministic session extraction.
29
+ - `$skill-creator` for all skill creation/update work.
30
+
31
+ ## Workflow
32
+
33
+ ### 1) Extract the latest conversation history
34
+
35
+ - Run:
36
+
37
+ ```bash
38
+ python3 ~/.codex/skills/learn-skill-from-conversations/scripts/extract_recent_conversations.py --lookback-minutes 1440
39
+ ```
40
+
41
+ - If output is exactly `NO_RECENT_CONVERSATIONS`, stop immediately and report that no action is required.
42
+ - Otherwise, review all `[USER]` and `[ASSISTANT]` blocks from each returned session.
43
+ - The extractor reads both `~/.codex/sessions` and `~/.codex/archived_sessions`.
44
+ - After extraction completes, it deletes `~/.codex/sessions` records older than 7 days and deletes all files under `~/.codex/archived_sessions`.
45
+
46
+ ### 2) Derive reusable lessons
47
+
48
+ - Identify repeated user needs, recurring friction, and repeated manual workflows.
49
+ - Ignore one-off issues that do not provide reusable value.
50
+
51
+ ### 3) Decide new skill vs existing skill (default: new skill)
52
+
53
+ - Prefer creating a new skill.
54
+ - Edit an existing skill only when the lesson is strongly related.
55
+ - Treat relation as strong only when all three conditions hold:
56
+ - Same primary trigger intent.
57
+ - At least 70% workflow overlap.
58
+ - The update does not dilute the existing skill's scope.
59
+ - If uncertain, create a new skill instead of expanding an old one.
60
+
61
+ ### 4) Apply changes through skill-creator
62
+
63
+ - Explicitly follow `$skill-creator` workflow before editing skills.
64
+ - For new skills, initialize with `~/.codex/skills/.system/skill-creator/scripts/init_skill.py`, then complete `SKILL.md` and required resources.
65
+ - For existing skills, make minimal focused edits and keep behavior consistent.
66
+
67
+ ### 5) Validate every changed skill
68
+
69
+ - Run:
70
+
71
+ ```bash
72
+ python3 ~/.codex/skills/.system/skill-creator/scripts/quick_validate.py <skill-path>
73
+ ```
74
+
75
+ - Resolve validation failures before finishing.
76
+
77
+ ### 6) Report result
78
+
79
+ - Summarize analyzed sessions, extracted lessons, created/updated skills, and why each decision was made.
80
+
81
+ ## Guardrails
82
+
83
+ - Take no action when there are no sessions in the last 24 hours.
84
+ - Avoid broad refactors across unrelated skills.
85
+ - Avoid duplicate skills when an existing skill is strongly related.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Learn Skill from Conversations"
3
+ short_description: "Learn and evolve skills from recent chats"
4
+ default_prompt: "Use $learn-skill-from-conversations to review the last 24 hours of sessions, including archived sessions, and evolve skills."
@@ -0,0 +1,369 @@
1
+ #!/usr/bin/env python3
2
+ """Extract recent Codex conversation history from Codex session stores."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ from dataclasses import dataclass
9
+ from datetime import datetime, timedelta, timezone
10
+ from pathlib import Path
11
+ from typing import Iterable, List, Optional, Sequence, Tuple
12
+
13
+ DEFAULT_LOOKBACK_MINUTES = 24 * 60
14
+ DEFAULT_RETENTION_DAYS = 7
15
+
16
+
17
+ @dataclass
18
+ class SessionRecord:
19
+ path: Path
20
+ timestamp_utc: datetime
21
+ messages: Optional[List[Tuple[str, str]]] = None
22
+
23
+
24
+ def parse_iso_timestamp(raw: Optional[str]) -> Optional[datetime]:
25
+ if not raw:
26
+ return None
27
+ value = raw.strip()
28
+ if not value:
29
+ return None
30
+ if value.endswith("Z"):
31
+ value = value[:-1] + "+00:00"
32
+ try:
33
+ parsed = datetime.fromisoformat(value)
34
+ except ValueError:
35
+ return None
36
+ if parsed.tzinfo is None:
37
+ parsed = parsed.replace(tzinfo=timezone.utc)
38
+ return parsed.astimezone(timezone.utc)
39
+
40
+
41
+ def read_session_timestamp(path: Path) -> Optional[datetime]:
42
+ try:
43
+ with path.open("r", encoding="utf-8") as handle:
44
+ first_line = handle.readline().strip()
45
+ except OSError:
46
+ return None
47
+
48
+ if not first_line:
49
+ return None
50
+
51
+ try:
52
+ first_entry = json.loads(first_line)
53
+ except json.JSONDecodeError:
54
+ return None
55
+
56
+ if first_entry.get("type") != "session_meta":
57
+ return None
58
+
59
+ payload = first_entry.get("payload", {})
60
+ if not isinstance(payload, dict):
61
+ return None
62
+
63
+ return parse_iso_timestamp(payload.get("timestamp")) or parse_iso_timestamp(first_entry.get("timestamp"))
64
+
65
+
66
+ def iter_session_paths(root: Path) -> Iterable[Path]:
67
+ if not root.exists() or not root.is_dir():
68
+ return
69
+ yield from root.rglob("*.jsonl")
70
+
71
+
72
+ def find_recent_sessions(
73
+ session_roots: Sequence[Path],
74
+ cutoff_utc: datetime,
75
+ limit: Optional[int],
76
+ ) -> List[SessionRecord]:
77
+ candidates: List[SessionRecord] = []
78
+ seen_paths = set()
79
+
80
+ for root in session_roots:
81
+ for path in iter_session_paths(root):
82
+ resolved_path = path.resolve()
83
+ if resolved_path in seen_paths:
84
+ continue
85
+ seen_paths.add(resolved_path)
86
+
87
+ timestamp_utc = read_session_timestamp(path)
88
+ if timestamp_utc is None:
89
+ continue
90
+ if timestamp_utc < cutoff_utc:
91
+ continue
92
+ candidates.append(SessionRecord(path=path, timestamp_utc=timestamp_utc))
93
+
94
+ candidates.sort(key=lambda record: record.timestamp_utc, reverse=True)
95
+ if limit is None:
96
+ return candidates
97
+ return candidates[:limit]
98
+
99
+
100
+ def sanitize_text(text: str, max_chars: int) -> str:
101
+ cleaned = text.replace("\r\n", "\n").replace("\r", "\n").strip()
102
+ if max_chars <= 0:
103
+ return cleaned
104
+ if len(cleaned) <= max_chars:
105
+ return cleaned
106
+ return cleaned[: max_chars - 1].rstrip() + "..."
107
+
108
+
109
+ def looks_like_wrapper_message(text: str) -> bool:
110
+ stripped = text.strip()
111
+ if not stripped:
112
+ return True
113
+ lower = stripped.lower()
114
+ return (
115
+ stripped.startswith("# AGENTS.md instructions for")
116
+ or stripped.startswith("<environment_context>")
117
+ or "<collaboration_mode>" in lower
118
+ or stripped.startswith("<permissions instructions>")
119
+ or stripped.startswith("<app-context>")
120
+ )
121
+
122
+
123
+ def extract_text_from_content(content: Sequence[object]) -> str:
124
+ texts: List[str] = []
125
+ for part in content:
126
+ if not isinstance(part, dict):
127
+ continue
128
+ part_type = part.get("type")
129
+ if part_type in {"input_text", "output_text", "text"}:
130
+ value = part.get("text", "")
131
+ if isinstance(value, str) and value.strip():
132
+ texts.append(value)
133
+ return "\n".join(texts).strip()
134
+
135
+
136
+ def extract_messages_from_event_entries(entries: Iterable[dict], max_chars: int) -> List[Tuple[str, str]]:
137
+ messages: List[Tuple[str, str]] = []
138
+ for entry in entries:
139
+ if entry.get("type") != "event_msg":
140
+ continue
141
+ payload = entry.get("payload", {})
142
+ if not isinstance(payload, dict):
143
+ continue
144
+
145
+ payload_type = payload.get("type")
146
+ if payload_type == "user_message":
147
+ text = payload.get("message", "")
148
+ if isinstance(text, str) and text.strip():
149
+ messages.append(("user", sanitize_text(text, max_chars)))
150
+ elif payload_type == "agent_message":
151
+ text = payload.get("message", "")
152
+ if isinstance(text, str) and text.strip():
153
+ messages.append(("assistant", sanitize_text(text, max_chars)))
154
+ return messages
155
+
156
+
157
+ def extract_messages_from_response_items(entries: Iterable[dict], max_chars: int) -> List[Tuple[str, str]]:
158
+ messages: List[Tuple[str, str]] = []
159
+ for entry in entries:
160
+ if entry.get("type") != "response_item":
161
+ continue
162
+ payload = entry.get("payload", {})
163
+ if not isinstance(payload, dict):
164
+ continue
165
+ if payload.get("type") != "message":
166
+ continue
167
+
168
+ role = payload.get("role")
169
+ if role not in {"user", "assistant"}:
170
+ continue
171
+
172
+ text = extract_text_from_content(payload.get("content", []))
173
+ if not text or looks_like_wrapper_message(text):
174
+ continue
175
+ messages.append((role, sanitize_text(text, max_chars)))
176
+ return messages
177
+
178
+
179
+ def extract_session_messages(path: Path, max_chars: int) -> List[Tuple[str, str]]:
180
+ entries: List[dict] = []
181
+ try:
182
+ with path.open("r", encoding="utf-8") as handle:
183
+ for line in handle:
184
+ line = line.strip()
185
+ if not line:
186
+ continue
187
+ try:
188
+ entries.append(json.loads(line))
189
+ except json.JSONDecodeError:
190
+ continue
191
+ except OSError:
192
+ return []
193
+
194
+ event_messages = extract_messages_from_event_entries(entries, max_chars)
195
+ if event_messages:
196
+ return event_messages
197
+ return extract_messages_from_response_items(entries, max_chars)
198
+
199
+
200
+ def delete_matching_files(root: Path, predicate) -> int:
201
+ if not root.exists() or not root.is_dir():
202
+ return 0
203
+
204
+ deleted_count = 0
205
+ for path in root.rglob("*.jsonl"):
206
+ if not predicate(path):
207
+ continue
208
+ try:
209
+ path.unlink()
210
+ except OSError:
211
+ continue
212
+ deleted_count += 1
213
+ return deleted_count
214
+
215
+
216
+ def path_is_same_or_nested(path: Path, root: Optional[Path]) -> bool:
217
+ if root is None:
218
+ return False
219
+ try:
220
+ path.resolve().relative_to(root.resolve())
221
+ return True
222
+ except ValueError:
223
+ return False
224
+
225
+
226
+ def cleanup_session_history(
227
+ sessions_dir: Path,
228
+ archived_sessions_dir: Path,
229
+ retention_cutoff_utc: datetime,
230
+ ) -> Tuple[int, int]:
231
+ sessions_root = sessions_dir.resolve() if sessions_dir.exists() else None
232
+ removed_old_sessions = delete_matching_files(
233
+ sessions_dir,
234
+ lambda path: (
235
+ (timestamp := read_session_timestamp(path)) is not None
236
+ and timestamp < retention_cutoff_utc
237
+ ),
238
+ )
239
+ removed_archived_sessions = delete_matching_files(
240
+ archived_sessions_dir,
241
+ lambda path: not path_is_same_or_nested(path, sessions_root),
242
+ )
243
+ return removed_old_sessions, removed_archived_sessions
244
+
245
+
246
+ def render_text_output(
247
+ records: Sequence[SessionRecord],
248
+ lookback_minutes: int,
249
+ max_message_chars: int,
250
+ removed_old_sessions: int,
251
+ removed_archived_sessions: int,
252
+ ) -> str:
253
+ if not records:
254
+ return "NO_RECENT_CONVERSATIONS"
255
+
256
+ lines: List[str] = [
257
+ f"RECENT_CONVERSATIONS_FOUND={len(records)}",
258
+ f"LOOKBACK_MINUTES={lookback_minutes}",
259
+ "ARCHIVED_SESSIONS_INCLUDED=true",
260
+ f"CLEANUP_REMOVED_OLD_SESSIONS={removed_old_sessions}",
261
+ f"CLEANUP_REMOVED_ARCHIVED_SESSIONS={removed_archived_sessions}",
262
+ ]
263
+
264
+ for index, record in enumerate(records, start=1):
265
+ lines.append(f"=== SESSION {index} ===")
266
+ lines.append(f"TIMESTAMP_UTC={record.timestamp_utc.isoformat()}")
267
+ lines.append(f"FILE={record.path}")
268
+
269
+ messages = record.messages
270
+ if messages is None:
271
+ messages = extract_session_messages(record.path, max_message_chars)
272
+ if not messages:
273
+ lines.append("MESSAGES=NONE")
274
+ continue
275
+
276
+ for role, message in messages:
277
+ tag = "USER" if role == "user" else "ASSISTANT"
278
+ lines.append(f"[{tag}]")
279
+ lines.append(message)
280
+ lines.append(f"[/{tag}]")
281
+
282
+ return "\n".join(lines)
283
+
284
+
285
+ def parse_args() -> argparse.Namespace:
286
+ parser = argparse.ArgumentParser(
287
+ description="Extract the latest conversation history from Codex session stores",
288
+ )
289
+ parser.add_argument(
290
+ "--sessions-dir",
291
+ default="~/.codex/sessions",
292
+ help="Path to the Codex sessions directory (default: ~/.codex/sessions)",
293
+ )
294
+ parser.add_argument(
295
+ "--archived-sessions-dir",
296
+ default="~/.codex/archived_sessions",
297
+ help="Path to archived Codex sessions (default: ~/.codex/archived_sessions)",
298
+ )
299
+ parser.add_argument(
300
+ "--lookback-minutes",
301
+ type=int,
302
+ default=DEFAULT_LOOKBACK_MINUTES,
303
+ help=f"How far back to look for sessions (default: {DEFAULT_LOOKBACK_MINUTES})",
304
+ )
305
+ parser.add_argument(
306
+ "--limit",
307
+ type=int,
308
+ default=None,
309
+ help="Maximum number of sessions to return (default: all within lookback window)",
310
+ )
311
+ parser.add_argument(
312
+ "--max-message-chars",
313
+ type=int,
314
+ default=1600,
315
+ help="Maximum characters per extracted message (default: 1600)",
316
+ )
317
+ parser.add_argument(
318
+ "--retention-days",
319
+ type=int,
320
+ default=DEFAULT_RETENTION_DAYS,
321
+ help=f"Delete sessions older than this many days after reading (default: {DEFAULT_RETENTION_DAYS})",
322
+ )
323
+ return parser.parse_args()
324
+
325
+
326
+ def main() -> int:
327
+ args = parse_args()
328
+
329
+ sessions_dir = Path(args.sessions_dir).expanduser().resolve()
330
+ archived_sessions_dir = Path(args.archived_sessions_dir).expanduser().resolve()
331
+ lookback_minutes = max(args.lookback_minutes, 1)
332
+ limit = args.limit if args.limit is not None and args.limit > 0 else None
333
+ max_message_chars = max(args.max_message_chars, 100)
334
+ retention_days = max(args.retention_days, 1)
335
+ now_utc = datetime.now(timezone.utc)
336
+
337
+ if (
338
+ (not sessions_dir.exists() or not sessions_dir.is_dir())
339
+ and (not archived_sessions_dir.exists() or not archived_sessions_dir.is_dir())
340
+ ):
341
+ print("NO_RECENT_CONVERSATIONS")
342
+ return 0
343
+
344
+ cutoff_utc = now_utc - timedelta(minutes=lookback_minutes)
345
+ recent_records = find_recent_sessions((sessions_dir, archived_sessions_dir), cutoff_utc, limit)
346
+ for record in recent_records:
347
+ record.messages = extract_session_messages(record.path, max_message_chars)
348
+
349
+ retention_cutoff_utc = now_utc - timedelta(days=retention_days)
350
+ removed_old_sessions, removed_archived_sessions = cleanup_session_history(
351
+ sessions_dir,
352
+ archived_sessions_dir,
353
+ retention_cutoff_utc,
354
+ )
355
+
356
+ print(
357
+ render_text_output(
358
+ recent_records,
359
+ lookback_minutes,
360
+ max_message_chars,
361
+ removed_old_sessions,
362
+ removed_archived_sessions,
363
+ )
364
+ )
365
+ return 0
366
+
367
+
368
+ if __name__ == "__main__":
369
+ raise SystemExit(main())