@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.
- package/AGENTS.md +62 -0
- package/CHANGELOG.md +100 -0
- package/LICENSE +21 -0
- package/README.md +144 -0
- package/align-project-documents/SKILL.md +94 -0
- package/align-project-documents/agents/openai.yaml +4 -0
- package/analyse-app-logs/LICENSE +21 -0
- package/analyse-app-logs/README.md +126 -0
- package/analyse-app-logs/SKILL.md +121 -0
- package/analyse-app-logs/agents/openai.yaml +4 -0
- package/analyse-app-logs/references/investigation-checklist.md +58 -0
- package/analyse-app-logs/references/log-signal-patterns.md +52 -0
- package/answering-questions-with-research/SKILL.md +46 -0
- package/answering-questions-with-research/agents/openai.yaml +4 -0
- package/bin/apollo-toolkit.js +7 -0
- package/commit-and-push/LICENSE +21 -0
- package/commit-and-push/README.md +26 -0
- package/commit-and-push/SKILL.md +70 -0
- package/commit-and-push/agents/openai.yaml +4 -0
- package/commit-and-push/references/branch-naming.md +15 -0
- package/commit-and-push/references/commit-messages.md +19 -0
- package/deep-research-topics/LICENSE +21 -0
- package/deep-research-topics/README.md +43 -0
- package/deep-research-topics/SKILL.md +84 -0
- package/deep-research-topics/agents/openai.yaml +4 -0
- package/develop-new-features/LICENSE +21 -0
- package/develop-new-features/README.md +52 -0
- package/develop-new-features/SKILL.md +105 -0
- package/develop-new-features/agents/openai.yaml +4 -0
- package/develop-new-features/references/testing-e2e.md +35 -0
- package/develop-new-features/references/testing-integration.md +42 -0
- package/develop-new-features/references/testing-property-based.md +44 -0
- package/develop-new-features/references/testing-unit.md +37 -0
- package/discover-edge-cases/CHANGELOG.md +19 -0
- package/discover-edge-cases/LICENSE +21 -0
- package/discover-edge-cases/README.md +87 -0
- package/discover-edge-cases/SKILL.md +124 -0
- package/discover-edge-cases/agents/openai.yaml +4 -0
- package/discover-edge-cases/references/architecture-edge-cases.md +41 -0
- package/discover-edge-cases/references/code-edge-cases.md +46 -0
- package/docs-to-voice/.env.example +106 -0
- package/docs-to-voice/CHANGELOG.md +71 -0
- package/docs-to-voice/LICENSE +21 -0
- package/docs-to-voice/README.md +118 -0
- package/docs-to-voice/SKILL.md +107 -0
- package/docs-to-voice/agents/openai.yaml +4 -0
- package/docs-to-voice/scripts/docs_to_voice.py +1385 -0
- package/docs-to-voice/scripts/docs_to_voice.sh +11 -0
- package/docs-to-voice/tests/test_docs_to_voice_api_max_chars.py +210 -0
- package/docs-to-voice/tests/test_docs_to_voice_sentence_timeline.py +115 -0
- package/docs-to-voice/tests/test_docs_to_voice_settings.py +43 -0
- package/docs-to-voice/tests/test_docs_to_voice_speech_rate.py +57 -0
- package/enhance-existing-features/CHANGELOG.md +35 -0
- package/enhance-existing-features/LICENSE +21 -0
- package/enhance-existing-features/README.md +54 -0
- package/enhance-existing-features/SKILL.md +120 -0
- package/enhance-existing-features/agents/openai.yaml +4 -0
- package/enhance-existing-features/references/e2e-tests.md +25 -0
- package/enhance-existing-features/references/integration-tests.md +30 -0
- package/enhance-existing-features/references/property-based-tests.md +33 -0
- package/enhance-existing-features/references/unit-tests.md +29 -0
- package/feature-propose/LICENSE +21 -0
- package/feature-propose/README.md +23 -0
- package/feature-propose/SKILL.md +107 -0
- package/feature-propose/agents/openai.yaml +4 -0
- package/feature-propose/references/enhancement-features.md +25 -0
- package/feature-propose/references/important-features.md +25 -0
- package/feature-propose/references/mvp-features.md +25 -0
- package/feature-propose/references/performance-features.md +25 -0
- package/financial-research/SKILL.md +208 -0
- package/financial-research/agents/openai.yaml +4 -0
- package/financial-research/assets/weekly_market_report_template.md +45 -0
- package/fix-github-issues/SKILL.md +98 -0
- package/fix-github-issues/agents/openai.yaml +4 -0
- package/fix-github-issues/scripts/list_issues.py +148 -0
- package/fix-github-issues/tests/test_list_issues.py +127 -0
- package/generate-spec/LICENSE +21 -0
- package/generate-spec/README.md +61 -0
- package/generate-spec/SKILL.md +96 -0
- package/generate-spec/agents/openai.yaml +4 -0
- package/generate-spec/references/templates/checklist.md +78 -0
- package/generate-spec/references/templates/spec.md +55 -0
- package/generate-spec/references/templates/tasks.md +35 -0
- package/generate-spec/scripts/create-specs +123 -0
- package/harden-app-security/CHANGELOG.md +27 -0
- package/harden-app-security/LICENSE +21 -0
- package/harden-app-security/README.md +46 -0
- package/harden-app-security/SKILL.md +127 -0
- package/harden-app-security/agents/openai.yaml +4 -0
- package/harden-app-security/references/agent-attack-catalog.md +117 -0
- package/harden-app-security/references/common-software-attack-catalog.md +168 -0
- package/harden-app-security/references/red-team-extreme-scenarios.md +81 -0
- package/harden-app-security/references/risk-checklist.md +78 -0
- package/harden-app-security/references/security-test-patterns-agent.md +101 -0
- package/harden-app-security/references/security-test-patterns-finance.md +88 -0
- package/harden-app-security/references/test-snippets.md +73 -0
- package/improve-observability/SKILL.md +114 -0
- package/improve-observability/agents/openai.yaml +4 -0
- package/learn-skill-from-conversations/CHANGELOG.md +15 -0
- package/learn-skill-from-conversations/LICENSE +22 -0
- package/learn-skill-from-conversations/README.md +47 -0
- package/learn-skill-from-conversations/SKILL.md +85 -0
- package/learn-skill-from-conversations/agents/openai.yaml +4 -0
- package/learn-skill-from-conversations/scripts/extract_recent_conversations.py +369 -0
- package/learn-skill-from-conversations/tests/test_extract_recent_conversations.py +176 -0
- package/learning-error-book/SKILL.md +112 -0
- package/learning-error-book/agents/openai.yaml +4 -0
- package/learning-error-book/assets/error_book_template.md +66 -0
- package/learning-error-book/scripts/render_markdown_to_pdf.py +367 -0
- package/lib/cli.js +338 -0
- package/lib/installer.js +225 -0
- package/maintain-project-constraints/SKILL.md +109 -0
- package/maintain-project-constraints/agents/openai.yaml +4 -0
- package/maintain-skill-catalog/README.md +18 -0
- package/maintain-skill-catalog/SKILL.md +66 -0
- package/maintain-skill-catalog/agents/openai.yaml +4 -0
- package/novel-to-short-video/CHANGELOG.md +53 -0
- package/novel-to-short-video/LICENSE +21 -0
- package/novel-to-short-video/README.md +63 -0
- package/novel-to-short-video/SKILL.md +233 -0
- package/novel-to-short-video/agents/openai.yaml +4 -0
- package/novel-to-short-video/references/plan-template.md +71 -0
- package/novel-to-short-video/references/roles-json.md +41 -0
- package/open-github-issue/LICENSE +21 -0
- package/open-github-issue/README.md +97 -0
- package/open-github-issue/SKILL.md +119 -0
- package/open-github-issue/agents/openai.yaml +4 -0
- package/open-github-issue/scripts/open_github_issue.py +380 -0
- package/open-github-issue/tests/test_open_github_issue.py +159 -0
- package/open-source-pr-workflow/CHANGELOG.md +32 -0
- package/open-source-pr-workflow/LICENSE +21 -0
- package/open-source-pr-workflow/README.md +23 -0
- package/open-source-pr-workflow/SKILL.md +123 -0
- package/open-source-pr-workflow/agents/openai.yaml +4 -0
- package/openai-text-to-image-storyboard/.env.example +10 -0
- package/openai-text-to-image-storyboard/CHANGELOG.md +49 -0
- package/openai-text-to-image-storyboard/LICENSE +21 -0
- package/openai-text-to-image-storyboard/README.md +99 -0
- package/openai-text-to-image-storyboard/SKILL.md +107 -0
- package/openai-text-to-image-storyboard/agents/openai.yaml +4 -0
- package/openai-text-to-image-storyboard/scripts/generate_storyboard_images.py +763 -0
- package/package.json +36 -0
- package/record-spending/SKILL.md +113 -0
- package/record-spending/agents/openai.yaml +4 -0
- package/record-spending/references/account-format.md +33 -0
- package/record-spending/references/workbook-layout.md +84 -0
- package/resolve-review-comments/SKILL.md +122 -0
- package/resolve-review-comments/agents/openai.yaml +4 -0
- package/resolve-review-comments/references/adoption-criteria.md +23 -0
- package/resolve-review-comments/scripts/review_threads.py +425 -0
- package/resolve-review-comments/tests/test_review_threads.py +74 -0
- package/review-change-set/LICENSE +21 -0
- package/review-change-set/README.md +55 -0
- package/review-change-set/SKILL.md +103 -0
- package/review-change-set/agents/openai.yaml +4 -0
- package/review-codebases/LICENSE +21 -0
- package/review-codebases/README.md +67 -0
- package/review-codebases/SKILL.md +109 -0
- package/review-codebases/agents/openai.yaml +4 -0
- package/scripts/install_skills.ps1 +283 -0
- package/scripts/install_skills.sh +262 -0
- package/scripts/validate_openai_agent_config.py +194 -0
- package/scripts/validate_skill_frontmatter.py +110 -0
- package/specs-to-project-docs/LICENSE +21 -0
- package/specs-to-project-docs/README.md +57 -0
- package/specs-to-project-docs/SKILL.md +111 -0
- package/specs-to-project-docs/agents/openai.yaml +4 -0
- package/specs-to-project-docs/references/templates/architecture.md +29 -0
- package/specs-to-project-docs/references/templates/configuration.md +29 -0
- package/specs-to-project-docs/references/templates/developer-guide.md +33 -0
- package/specs-to-project-docs/references/templates/docs-index.md +39 -0
- package/specs-to-project-docs/references/templates/features.md +25 -0
- package/specs-to-project-docs/references/templates/getting-started.md +38 -0
- package/specs-to-project-docs/references/templates/readme.md +49 -0
- package/systematic-debug/LICENSE +21 -0
- package/systematic-debug/README.md +81 -0
- package/systematic-debug/SKILL.md +59 -0
- package/systematic-debug/agents/openai.yaml +4 -0
- package/text-to-short-video/.env.example +36 -0
- package/text-to-short-video/LICENSE +21 -0
- package/text-to-short-video/README.md +82 -0
- package/text-to-short-video/SKILL.md +221 -0
- package/text-to-short-video/agents/openai.yaml +4 -0
- package/text-to-short-video/scripts/enforce_video_aspect_ratio.py +350 -0
- package/version-release/CHANGELOG.md +53 -0
- package/version-release/LICENSE +21 -0
- package/version-release/README.md +28 -0
- package/version-release/SKILL.md +94 -0
- package/version-release/agents/openai.yaml +4 -0
- package/version-release/references/branch-naming.md +15 -0
- package/version-release/references/changelog-writing.md +8 -0
- package/version-release/references/commit-messages.md +19 -0
- package/version-release/references/readme-writing.md +12 -0
- package/version-release/references/semantic-versioning.md +12 -0
- package/video-production/CHANGELOG.md +104 -0
- package/video-production/LICENSE +18 -0
- package/video-production/README.md +68 -0
- package/video-production/SKILL.md +213 -0
- package/video-production/agents/openai.yaml +4 -0
- package/video-production/references/plan-template.md +54 -0
- package/video-production/references/roles-json.md +41 -0
- package/weekly-financial-event-report/SKILL.md +195 -0
- package/weekly-financial-event-report/agents/openai.yaml +4 -0
- 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,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())
|