self-evolve-framework 1.5.0 → 1.6.1

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 (154) hide show
  1. package/README.md +33 -23
  2. package/bin/cli.js +24 -67
  3. package/package.json +1 -1
  4. package/template/rules/knowledge/README.md +56 -0
  5. package/template/rules/knowledge/ant-design-vue.mdc +54 -0
  6. package/template/rules/knowledge/general.mdc +56 -0
  7. package/template/rules/knowledge/rust.mdc +55 -0
  8. package/template/rules/knowledge/shadcn-svelte.mdc +64 -0
  9. package/template/rules/knowledge/svelte.mdc +135 -0
  10. package/template/rules/knowledge/tailwind.mdc +66 -0
  11. package/template/rules/knowledge/tauri.mdc +65 -0
  12. package/template/rules/knowledge/typescript.mdc +41 -0
  13. package/template/rules/knowledge/vue.mdc +48 -0
  14. package/template/rules/ponytail.mdc +23 -98
  15. package/template/rules/self-evolve.mdc +24 -0
  16. package/template/skills/impeccable/SKILL.md +47 -174
  17. package/template/skills/skillopt-sleep/SKILL.md +34 -48
  18. package/template/skills/impeccable/agents/impeccable_asset_producer.toml +0 -92
  19. package/template/skills/impeccable/agents/impeccable_manual_edit_applier.toml +0 -95
  20. package/template/skills/impeccable/agents/openai.yaml +0 -4
  21. package/template/skills/impeccable/reference/adapt.md +0 -311
  22. package/template/skills/impeccable/reference/animate.md +0 -201
  23. package/template/skills/impeccable/reference/audit.md +0 -133
  24. package/template/skills/impeccable/reference/bolder.md +0 -113
  25. package/template/skills/impeccable/reference/brand.md +0 -108
  26. package/template/skills/impeccable/reference/clarify.md +0 -288
  27. package/template/skills/impeccable/reference/codex.md +0 -105
  28. package/template/skills/impeccable/reference/colorize.md +0 -257
  29. package/template/skills/impeccable/reference/craft.md +0 -123
  30. package/template/skills/impeccable/reference/critique.md +0 -790
  31. package/template/skills/impeccable/reference/delight.md +0 -302
  32. package/template/skills/impeccable/reference/distill.md +0 -111
  33. package/template/skills/impeccable/reference/document.md +0 -429
  34. package/template/skills/impeccable/reference/extract.md +0 -69
  35. package/template/skills/impeccable/reference/harden.md +0 -347
  36. package/template/skills/impeccable/reference/hooks.md +0 -90
  37. package/template/skills/impeccable/reference/init.md +0 -172
  38. package/template/skills/impeccable/reference/interaction-design.md +0 -189
  39. package/template/skills/impeccable/reference/layout.md +0 -161
  40. package/template/skills/impeccable/reference/live.md +0 -720
  41. package/template/skills/impeccable/reference/onboard.md +0 -234
  42. package/template/skills/impeccable/reference/optimize.md +0 -258
  43. package/template/skills/impeccable/reference/overdrive.md +0 -130
  44. package/template/skills/impeccable/reference/polish.md +0 -241
  45. package/template/skills/impeccable/reference/product.md +0 -60
  46. package/template/skills/impeccable/reference/quieter.md +0 -99
  47. package/template/skills/impeccable/reference/shape.md +0 -165
  48. package/template/skills/impeccable/reference/typeset.md +0 -279
  49. package/template/skills/impeccable/scripts/command-metadata.json +0 -94
  50. package/template/skills/impeccable/scripts/context-signals.mjs +0 -225
  51. package/template/skills/impeccable/scripts/context.mjs +0 -961
  52. package/template/skills/impeccable/scripts/critique-storage.mjs +0 -242
  53. package/template/skills/impeccable/scripts/detect-csp.mjs +0 -198
  54. package/template/skills/impeccable/scripts/detect.mjs +0 -21
  55. package/template/skills/impeccable/scripts/detector/browser/injected/index.mjs +0 -1937
  56. package/template/skills/impeccable/scripts/detector/cli/main.mjs +0 -290
  57. package/template/skills/impeccable/scripts/detector/design-system.mjs +0 -750
  58. package/template/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +0 -5138
  59. package/template/skills/impeccable/scripts/detector/detect-antipatterns.mjs +0 -50
  60. package/template/skills/impeccable/scripts/detector/engines/browser/detect-url.mjs +0 -277
  61. package/template/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +0 -568
  62. package/template/skills/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +0 -1015
  63. package/template/skills/impeccable/scripts/detector/engines/static-html/detect-html.mjs +0 -234
  64. package/template/skills/impeccable/scripts/detector/engines/visual/screenshot-contrast.mjs +0 -189
  65. package/template/skills/impeccable/scripts/detector/findings.mjs +0 -12
  66. package/template/skills/impeccable/scripts/detector/node/file-system.mjs +0 -198
  67. package/template/skills/impeccable/scripts/detector/profile/profiler.mjs +0 -166
  68. package/template/skills/impeccable/scripts/detector/registry/antipatterns.mjs +0 -448
  69. package/template/skills/impeccable/scripts/detector/rules/checks.mjs +0 -2671
  70. package/template/skills/impeccable/scripts/detector/shared/color.mjs +0 -124
  71. package/template/skills/impeccable/scripts/detector/shared/constants.mjs +0 -101
  72. package/template/skills/impeccable/scripts/detector/shared/inline-ignores.mjs +0 -148
  73. package/template/skills/impeccable/scripts/detector/shared/page.mjs +0 -7
  74. package/template/skills/impeccable/scripts/hook-admin.mjs +0 -661
  75. package/template/skills/impeccable/scripts/hook-before-edit.mjs +0 -476
  76. package/template/skills/impeccable/scripts/hook-lib.mjs +0 -1632
  77. package/template/skills/impeccable/scripts/hook.mjs +0 -61
  78. package/template/skills/impeccable/scripts/lib/design-parser.mjs +0 -842
  79. package/template/skills/impeccable/scripts/lib/impeccable-config.mjs +0 -638
  80. package/template/skills/impeccable/scripts/lib/impeccable-paths.mjs +0 -128
  81. package/template/skills/impeccable/scripts/lib/is-generated.mjs +0 -69
  82. package/template/skills/impeccable/scripts/lib/target-args.mjs +0 -42
  83. package/template/skills/impeccable/scripts/live/browser-script-parts.mjs +0 -49
  84. package/template/skills/impeccable/scripts/live/completion.mjs +0 -19
  85. package/template/skills/impeccable/scripts/live/event-validation.mjs +0 -137
  86. package/template/skills/impeccable/scripts/live/insert-ui.mjs +0 -458
  87. package/template/skills/impeccable/scripts/live/manual-apply.mjs +0 -939
  88. package/template/skills/impeccable/scripts/live/manual-edit-routes.mjs +0 -357
  89. package/template/skills/impeccable/scripts/live/manual-edits-buffer.mjs +0 -152
  90. package/template/skills/impeccable/scripts/live/session-store.mjs +0 -289
  91. package/template/skills/impeccable/scripts/live/svelte-component.mjs +0 -826
  92. package/template/skills/impeccable/scripts/live/sveltekit-adapter.mjs +0 -274
  93. package/template/skills/impeccable/scripts/live/ui-core.mjs +0 -180
  94. package/template/skills/impeccable/scripts/live/vocabulary.mjs +0 -36
  95. package/template/skills/impeccable/scripts/live-accept.mjs +0 -812
  96. package/template/skills/impeccable/scripts/live-browser-dom.js +0 -146
  97. package/template/skills/impeccable/scripts/live-browser-session.js +0 -123
  98. package/template/skills/impeccable/scripts/live-browser.js +0 -11173
  99. package/template/skills/impeccable/scripts/live-commit-manual-edits.mjs +0 -1241
  100. package/template/skills/impeccable/scripts/live-complete.mjs +0 -75
  101. package/template/skills/impeccable/scripts/live-copy-edit-agent.mjs +0 -683
  102. package/template/skills/impeccable/scripts/live-discard-manual-edits.mjs +0 -51
  103. package/template/skills/impeccable/scripts/live-inject.mjs +0 -583
  104. package/template/skills/impeccable/scripts/live-insert.mjs +0 -272
  105. package/template/skills/impeccable/scripts/live-manual-edit-evidence.mjs +0 -363
  106. package/template/skills/impeccable/scripts/live-poll.mjs +0 -384
  107. package/template/skills/impeccable/scripts/live-resume.mjs +0 -94
  108. package/template/skills/impeccable/scripts/live-server.mjs +0 -1135
  109. package/template/skills/impeccable/scripts/live-status.mjs +0 -61
  110. package/template/skills/impeccable/scripts/live-target.mjs +0 -30
  111. package/template/skills/impeccable/scripts/live-wrap.mjs +0 -894
  112. package/template/skills/impeccable/scripts/live.mjs +0 -297
  113. package/template/skills/impeccable/scripts/modern-screenshot.umd.js +0 -14
  114. package/template/skills/impeccable/scripts/palette.mjs +0 -633
  115. package/template/skills/impeccable/scripts/pin.mjs +0 -214
  116. package/template/skills/skillopt-sleep/scripts/python/__init__.py +0 -20
  117. package/template/skills/skillopt-sleep/scripts/python/__main__.py +0 -343
  118. package/template/skills/skillopt-sleep/scripts/python/backend.py +0 -1371
  119. package/template/skills/skillopt-sleep/scripts/python/budget.py +0 -75
  120. package/template/skills/skillopt-sleep/scripts/python/config.py +0 -162
  121. package/template/skills/skillopt-sleep/scripts/python/consolidate.py +0 -238
  122. package/template/skills/skillopt-sleep/scripts/python/cycle.py +0 -291
  123. package/template/skills/skillopt-sleep/scripts/python/dream.py +0 -138
  124. package/template/skills/skillopt-sleep/scripts/python/experiments/__init__.py +0 -1
  125. package/template/skills/skillopt-sleep/scripts/python/experiments/gbrain_bench.py +0 -119
  126. package/template/skills/skillopt-sleep/scripts/python/experiments/personas.py +0 -86
  127. package/template/skills/skillopt-sleep/scripts/python/experiments/report.py +0 -132
  128. package/template/skills/skillopt-sleep/scripts/python/experiments/run_experiment.py +0 -178
  129. package/template/skills/skillopt-sleep/scripts/python/experiments/run_gbrain.py +0 -209
  130. package/template/skills/skillopt-sleep/scripts/python/experiments/run_transfer.py +0 -155
  131. package/template/skills/skillopt-sleep/scripts/python/experiments/sweep.py +0 -164
  132. package/template/skills/skillopt-sleep/scripts/python/gate.py +0 -50
  133. package/template/skills/skillopt-sleep/scripts/python/harvest.py +0 -304
  134. package/template/skills/skillopt-sleep/scripts/python/harvest_codex.py +0 -253
  135. package/template/skills/skillopt-sleep/scripts/python/harvest_sources.py +0 -41
  136. package/template/skills/skillopt-sleep/scripts/python/judges.py +0 -84
  137. package/template/skills/skillopt-sleep/scripts/python/llm_miner.py +0 -134
  138. package/template/skills/skillopt-sleep/scripts/python/memory.py +0 -129
  139. package/template/skills/skillopt-sleep/scripts/python/mine.py +0 -312
  140. package/template/skills/skillopt-sleep/scripts/python/replay.py +0 -146
  141. package/template/skills/skillopt-sleep/scripts/python/rollout.py +0 -153
  142. package/template/skills/skillopt-sleep/scripts/python/scheduler.py +0 -138
  143. package/template/skills/skillopt-sleep/scripts/python/slow_update.py +0 -142
  144. package/template/skills/skillopt-sleep/scripts/python/staging.py +0 -103
  145. package/template/skills/skillopt-sleep/scripts/python/state.py +0 -96
  146. package/template/skills/skillopt-sleep/scripts/python/tasks_file.py +0 -81
  147. package/template/skills/skillopt-sleep/scripts/python/types.py +0 -146
  148. package/template/skills/skillopt-sleep/scripts/shell/__init__.py +0 -0
  149. package/template/skills/skillopt-sleep/scripts/shell/eval_only.py +0 -466
  150. package/template/skills/skillopt-sleep/scripts/shell/materialize_searchqa.py +0 -148
  151. package/template/skills/skillopt-sleep/scripts/shell/run_alfworld.sh +0 -60
  152. package/template/skills/skillopt-sleep/scripts/shell/run_searchqa.sh +0 -40
  153. package/template/skills/skillopt-sleep/scripts/shell/run_spreadsheetbench.sh +0 -39
  154. package/template/skills/skillopt-sleep/scripts/shell/train.py +0 -556
@@ -1,214 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Pin/unpin sub-commands as standalone skill shortcuts.
4
- *
5
- * Usage:
6
- * node <scripts_path>/pin.mjs pin <command>
7
- * node <scripts_path>/pin.mjs unpin <command>
8
- *
9
- * `pin audit` creates a lightweight /audit skill that redirects to /impeccable audit.
10
- * `unpin audit` removes that shortcut.
11
- *
12
- * The script discovers harness directories (.claude/skills, .cursor/skills, etc.)
13
- * in the project root and creates/removes the pin in all of them.
14
- */
15
-
16
- import { existsSync, readFileSync, writeFileSync, mkdirSync, rmSync, readdirSync } from 'node:fs';
17
- import { join, resolve, dirname } from 'node:path';
18
- import { fileURLToPath } from 'node:url';
19
-
20
- const __dirname = dirname(fileURLToPath(import.meta.url));
21
-
22
- // All known harness directories
23
- const HARNESS_DIRS = [
24
- '.claude', '.cursor', '.gemini', '.codex', '.agents',
25
- '.trae', '.trae-cn', '.pi', '.opencode', '.kiro', '.rovodev',
26
- ];
27
-
28
- // Valid sub-command names
29
- const VALID_COMMANDS = [
30
- 'craft', 'init', 'extract', 'document', 'shape',
31
- 'critique', 'audit',
32
- 'polish', 'bolder', 'quieter', 'distill', 'harden', 'onboard', 'live',
33
- 'animate', 'colorize', 'typeset', 'layout', 'delight', 'overdrive',
34
- 'clarify', 'adapt', 'optimize',
35
- ];
36
-
37
- // Marker to identify pinned skills (so unpin doesn't delete user skills)
38
- const PIN_MARKER = '<!-- impeccable-pinned-skill -->';
39
-
40
- /**
41
- * Walk up from startDir to find a project root.
42
- */
43
- function findProjectRoot(startDir = process.cwd()) {
44
- let dir = resolve(startDir);
45
- while (dir !== '/') {
46
- if (
47
- existsSync(join(dir, 'package.json')) ||
48
- existsSync(join(dir, '.git')) ||
49
- existsSync(join(dir, 'skills-lock.json'))
50
- ) {
51
- return dir;
52
- }
53
- const parent = resolve(dir, '..');
54
- if (parent === dir) break;
55
- dir = parent;
56
- }
57
- return resolve(startDir);
58
- }
59
-
60
- /**
61
- * Find harness skill directories that have an impeccable skill installed.
62
- */
63
- function findHarnessDirs(projectRoot) {
64
- const dirs = [];
65
- for (const harness of HARNESS_DIRS) {
66
- const skillsDir = join(projectRoot, harness, 'skills');
67
- // Only pin in harness dirs that already have impeccable installed
68
- const impeccableDir = join(skillsDir, 'impeccable');
69
- if (existsSync(impeccableDir) || existsSync(join(skillsDir, 'i-impeccable'))) {
70
- dirs.push(skillsDir);
71
- }
72
- }
73
- return dirs;
74
- }
75
-
76
- /**
77
- * Load command metadata (descriptions for pinned skills).
78
- */
79
- function loadCommandMetadata() {
80
- const metadataPath = join(__dirname, 'command-metadata.json');
81
- if (existsSync(metadataPath)) {
82
- return JSON.parse(readFileSync(metadataPath, 'utf-8'));
83
- }
84
- return {};
85
- }
86
-
87
- /**
88
- * Generate a pinned skill's SKILL.md content.
89
- */
90
- function generatePinnedSkill(command, metadata) {
91
- const desc = metadata[command]?.description || `Shortcut for /impeccable ${command}.`;
92
- const hint = metadata[command]?.argumentHint || '[target]';
93
-
94
- return `---
95
- name: ${command}
96
- description: "${desc}"
97
- argument-hint: "${hint}"
98
- user-invocable: true
99
- ---
100
-
101
- ${PIN_MARKER}
102
-
103
- This is a pinned shortcut for \`{{command_prefix}}impeccable ${command}\`.
104
-
105
- Invoke {{command_prefix}}impeccable ${command}, passing along any arguments provided here, and follow its instructions.
106
- `;
107
- }
108
-
109
- /**
110
- * Pin a command: create shortcut skill in all harness dirs.
111
- */
112
- function pin(command, projectRoot) {
113
- const metadata = loadCommandMetadata();
114
- const harnessDirs = findHarnessDirs(projectRoot);
115
-
116
- if (harnessDirs.length === 0) {
117
- console.log('No harness directories with impeccable installed found.');
118
- return false;
119
- }
120
-
121
- const content = generatePinnedSkill(command, metadata);
122
- let created = 0;
123
-
124
- for (const skillsDir of harnessDirs) {
125
- // Check if skill already exists (and isn't a pin)
126
- const skillDir = join(skillsDir, command);
127
- if (existsSync(skillDir)) {
128
- const existingMd = join(skillDir, 'SKILL.md');
129
- if (existsSync(existingMd)) {
130
- const existing = readFileSync(existingMd, 'utf-8');
131
- if (!existing.includes(PIN_MARKER)) {
132
- console.log(` SKIP: ${skillDir} (non-pinned skill already exists)`);
133
- continue;
134
- }
135
- }
136
- }
137
-
138
- mkdirSync(skillDir, { recursive: true });
139
- writeFileSync(join(skillDir, 'SKILL.md'), content, 'utf-8');
140
- console.log(` + ${skillDir}`);
141
- created++;
142
- }
143
-
144
- if (created > 0) {
145
- console.log(`\nPinned '${command}' as a standalone shortcut in ${created} location(s).`);
146
- console.log(`You can now use /${command} directly.`);
147
- }
148
-
149
- return created > 0;
150
- }
151
-
152
- /**
153
- * Unpin a command: remove shortcut skill from all harness dirs.
154
- */
155
- function unpin(command, projectRoot) {
156
- const harnessDirs = findHarnessDirs(projectRoot);
157
- let removed = 0;
158
-
159
- for (const skillsDir of harnessDirs) {
160
- const skillDir = join(skillsDir, command);
161
- if (!existsSync(skillDir)) continue;
162
-
163
- const skillMd = join(skillDir, 'SKILL.md');
164
- if (!existsSync(skillMd)) continue;
165
-
166
- // Safety: only remove if it's a pinned skill
167
- const content = readFileSync(skillMd, 'utf-8');
168
- if (!content.includes(PIN_MARKER)) {
169
- console.log(` SKIP: ${skillDir} (not a pinned skill)`);
170
- continue;
171
- }
172
-
173
- rmSync(skillDir, { recursive: true, force: true });
174
- console.log(` - ${skillDir}`);
175
- removed++;
176
- }
177
-
178
- if (removed > 0) {
179
- console.log(`\nUnpinned '${command}' from ${removed} location(s).`);
180
- console.log(`Use /impeccable ${command} to access it.`);
181
- } else {
182
- console.log(`No pinned '${command}' shortcut found.`);
183
- }
184
-
185
- return removed > 0;
186
- }
187
-
188
- // --- CLI ---
189
- const [,, action, command] = process.argv;
190
-
191
- if (!action || !command) {
192
- console.log('Usage: node pin.mjs <pin|unpin> <command>');
193
- console.log(`\nAvailable commands: ${VALID_COMMANDS.join(', ')}`);
194
- process.exit(1);
195
- }
196
-
197
- if (action !== 'pin' && action !== 'unpin') {
198
- console.error(`Unknown action: ${action}. Use 'pin' or 'unpin'.`);
199
- process.exit(1);
200
- }
201
-
202
- if (!VALID_COMMANDS.includes(command)) {
203
- console.error(`Unknown command: ${command}`);
204
- console.error(`Available commands: ${VALID_COMMANDS.join(', ')}`);
205
- process.exit(1);
206
- }
207
-
208
- const root = findProjectRoot();
209
-
210
- if (action === 'pin') {
211
- pin(command, root);
212
- } else {
213
- unpin(command, root);
214
- }
@@ -1,20 +0,0 @@
1
- """SkillOpt-Sleep — nightly offline self-evolution for a local Claude agent.
2
-
3
- A Claude Code plugin engine that gives a user's agent a "sleep cycle":
4
- harvest the day's real session transcripts, mine recurring tasks, replay
5
- them offline, and consolidate short-term experience into long-term memory
6
- (CLAUDE.md) and skills (SKILL.md) behind a SkillOpt validation gate.
7
-
8
- Synthesizes three ideas:
9
- * SkillOpt — validation-gated bounded text optimization (this repo)
10
- * Dreams — offline memory consolidation, input never mutated
11
- * Sleep — short-term experience -> long-term competence, offline
12
-
13
- Public entry points:
14
- * skillopt_sleep.cli — `python -m skillopt_sleep ...`
15
- * skillopt_sleep.cycle.run_sleep_cycle(...)
16
- """
17
- from __future__ import annotations
18
-
19
- __all__ = ["__version__"]
20
- __version__ = "0.1.0"
@@ -1,343 +0,0 @@
1
- """SkillOpt-Sleep — command-line interface.
2
-
3
- python -m skillopt_sleep run # full cycle: harvest->mine->replay->gate->stage
4
- python -m skillopt_sleep dry-run # same but report only, no staging/adopt
5
- python -m skillopt_sleep status # show state + latest staged proposal
6
- python -m skillopt_sleep adopt # apply the latest staged proposal (with backup)
7
- python -m skillopt_sleep harvest # just print what would be mined (debug)
8
-
9
- Common flags:
10
- --project PATH project to evolve (default: cwd)
11
- --scope all|invoked harvest scope (default: invoked)
12
- --max-sessions N cap transcript sessions per run
13
- --max-tasks N cap mined tasks per run
14
- --target-skill-path PATH explicit live SKILL.md to stage/adopt
15
- --tasks-file PATH reviewed TaskRecord JSON file to replay instead of harvesting
16
- --backend mock|claude|codex|copilot
17
- --source claude|codex|auto
18
- --model NAME
19
- --lookback-hours N
20
- --auto-adopt
21
- --json machine-readable output
22
- """
23
- from __future__ import annotations
24
-
25
- import argparse
26
- import json
27
- import os
28
- import sys
29
- from typing import Any, Dict
30
-
31
- from skillopt_sleep.config import load_config
32
- from skillopt_sleep.cycle import run_sleep_cycle
33
- from skillopt_sleep.harvest_sources import harvest_for_config
34
- from skillopt_sleep.mine import mine
35
- from skillopt_sleep.staging import adopt as adopt_staging
36
- from skillopt_sleep.staging import latest_staging
37
- from skillopt_sleep.state import SleepState
38
- from skillopt_sleep.tasks_file import load_tasks_file, make_tasks_payload, write_tasks_file
39
-
40
-
41
- def _read_text(path: str) -> str:
42
- try:
43
- with open(path, encoding="utf-8") as f:
44
- return f.read()
45
- except Exception:
46
- return ""
47
-
48
-
49
- def _report_payload(rep, outcome) -> Dict[str, Any]:
50
- return {
51
- "night": rep.night,
52
- "accepted": rep.accepted,
53
- "gate_action": rep.gate_action,
54
- "no_edits_reason": getattr(rep, "no_edits_reason", ""),
55
- "baseline": rep.baseline_score,
56
- "candidate": rep.candidate_score,
57
- "n_tasks": rep.n_tasks,
58
- "n_sessions": rep.n_sessions,
59
- "n_accepted_edits": len(rep.edits),
60
- "n_rejected_edits": len(rep.rejected_edits),
61
- "edits": [e.__dict__ for e in rep.edits],
62
- "rejected_edits": [e.__dict__ for e in rep.rejected_edits],
63
- "notes": rep.notes,
64
- "staging_dir": outcome.staging_dir,
65
- "adopted": outcome.adopted,
66
- }
67
-
68
-
69
- def _add_common(p: argparse.ArgumentParser) -> None:
70
- p.add_argument("--project", default="")
71
- p.add_argument("--scope", default="", choices=["", "all", "invoked"])
72
- p.add_argument("--backend", default="", choices=["", "mock", "claude", "codex", "copilot"])
73
- p.add_argument("--model", default="")
74
- p.add_argument("--codex-path", default="", help="path to the real @openai/codex binary")
75
- p.add_argument("--claude-home", default="", help="override ~/.claude (also isolates state)")
76
- p.add_argument("--codex-home", default="", help="override ~/.codex for archived session harvest")
77
- p.add_argument("--source", default="", choices=["", "claude", "codex", "auto"],
78
- help="session transcript source")
79
- p.add_argument("--lookback-hours", type=int, default=None,
80
- help="harvest window in hours; 0 = scan full history")
81
- p.add_argument("--edit-budget", type=int, default=0)
82
- p.add_argument("--max-sessions", type=int, default=0,
83
- help="cap harvested sessions before mining; default derives from max tasks")
84
- p.add_argument("--max-tasks", type=int, default=0,
85
- help="cap mined tasks for this run")
86
- p.add_argument("--target-skill-path", default="",
87
- help="explicit live SKILL.md path to evolve/stage/adopt")
88
- p.add_argument("--tasks-file", default="",
89
- help="reviewed TaskRecord JSON file to replay instead of harvesting")
90
- p.add_argument("--progress", action="store_true",
91
- help="print phase progress to stderr")
92
- p.add_argument("--auto-adopt", action="store_true")
93
- p.add_argument("--json", action="store_true")
94
-
95
-
96
- def _cfg_from_args(args, task_meta: Dict[str, Any] | None = None) -> Any:
97
- overrides: Dict[str, Any] = {}
98
- if args.project:
99
- overrides["invoked_project"] = os.path.abspath(args.project)
100
- overrides["projects"] = "invoked"
101
- if args.scope:
102
- overrides["projects"] = args.scope
103
- if args.backend:
104
- overrides["backend"] = args.backend
105
- if args.model:
106
- overrides["model"] = args.model
107
- if getattr(args, "codex_path", ""):
108
- overrides["codex_path"] = os.path.abspath(args.codex_path)
109
- if getattr(args, "claude_home", ""):
110
- overrides["claude_home"] = os.path.abspath(args.claude_home)
111
- if getattr(args, "codex_home", ""):
112
- overrides["codex_home"] = os.path.abspath(args.codex_home)
113
- if getattr(args, "source", ""):
114
- overrides["transcript_source"] = args.source
115
- lh = getattr(args, "lookback_hours", None)
116
- if lh is not None: # --lookback-hours was explicitly passed (0 = full history)
117
- overrides["lookback_hours"] = lh
118
- if getattr(args, "edit_budget", 0):
119
- overrides["edit_budget"] = args.edit_budget
120
- if getattr(args, "max_sessions", 0):
121
- overrides["max_sessions_per_night"] = args.max_sessions
122
- if getattr(args, "max_tasks", 0):
123
- overrides["max_tasks_per_night"] = args.max_tasks
124
- target_skill_path = getattr(args, "target_skill_path", "")
125
- if not target_skill_path and task_meta:
126
- target_skill_path = str(task_meta.get("target_skill_path") or "")
127
- if target_skill_path:
128
- path = os.path.expanduser(target_skill_path)
129
- if args.project and not os.path.isabs(path):
130
- path = os.path.join(os.path.abspath(args.project), path)
131
- overrides["target_skill_path"] = os.path.abspath(path)
132
- if getattr(args, "progress", False):
133
- overrides["progress"] = True
134
- if getattr(args, "auto_adopt", False):
135
- overrides["auto_adopt"] = True
136
- return load_config(**overrides)
137
-
138
-
139
- def cmd_run(args, dry: bool = False) -> int:
140
- task_meta: Dict[str, Any] = {}
141
- tasks = None
142
- if getattr(args, "tasks_file", ""):
143
- # Load once before config so target_skill_path can default from metadata.
144
- tasks, task_meta = load_tasks_file(args.tasks_file)
145
- cfg = _cfg_from_args(args, task_meta=task_meta)
146
- if getattr(args, "tasks_file", ""):
147
- tasks, task_meta = load_tasks_file(
148
- args.tasks_file,
149
- holdout_fraction=cfg.get("holdout_fraction", 0.34),
150
- seed=cfg.get("seed", 42),
151
- )
152
- if cfg.get("backend", "mock") != "mock" and task_meta.get("reviewed") is not True:
153
- print(
154
- "[sleep] refusing real-backend replay from an unreviewed tasks file; "
155
- "inspect/redact it and set \"reviewed\": true first",
156
- file=sys.stderr,
157
- )
158
- return 2
159
- outcome = run_sleep_cycle(cfg, seed_tasks=tasks, dry_run=dry)
160
- rep = outcome.report
161
- if args.json:
162
- payload = _report_payload(rep, outcome)
163
- if task_meta:
164
- payload["tasks_file"] = task_meta.get("tasks_file", "")
165
- payload["tasks_reviewed"] = task_meta.get("reviewed", False)
166
- print(json.dumps(payload, ensure_ascii=False, indent=2))
167
- else:
168
- print(f"[sleep] night {rep.night}: {rep.n_sessions} sessions -> {rep.n_tasks} tasks")
169
- print(f"[sleep] held-out {rep.baseline_score:.3f} -> {rep.candidate_score:.3f} "
170
- f"=> {rep.gate_action} (accepted={rep.accepted})")
171
- for e in rep.edits:
172
- print(f" + [{e.target}/{e.op}] {e.content}")
173
- if rep.rejected_edits:
174
- print("[sleep] rejected by gate:")
175
- for e in rep.rejected_edits:
176
- print(f" - [{e.target}/{e.op}] {e.content}")
177
- if outcome.staging_dir:
178
- print(f"[sleep] staged: {outcome.staging_dir}")
179
- if not outcome.adopted:
180
- print("[sleep] review it, then: python -m skillopt_sleep adopt")
181
- if outcome.adopted:
182
- print(f"[sleep] auto-adopted: {', '.join(outcome.adopted_paths)}")
183
- return 0
184
-
185
-
186
- def cmd_status(args) -> int:
187
- cfg = _cfg_from_args(args)
188
- state = SleepState.load(cfg.state_path)
189
- project = cfg.get("invoked_project") or os.getcwd()
190
- latest = latest_staging(project)
191
- info = {
192
- "night": state.night,
193
- "state_path": cfg.state_path,
194
- "project": project,
195
- "history_tail": state.data.get("history", [])[-5:],
196
- "latest_staging": latest,
197
- "slow_memory_chars": len(state.slow_memory),
198
- }
199
- if args.json:
200
- print(json.dumps(info, ensure_ascii=False, indent=2))
201
- else:
202
- print(f"[sleep] nights so far: {state.night}")
203
- print(f"[sleep] project: {project}")
204
- if latest:
205
- print(f"[sleep] latest staged proposal: {latest}")
206
- rp = os.path.join(latest, "report.md")
207
- if os.path.exists(rp):
208
- with open(rp) as f:
209
- print("\n" + f.read())
210
- else:
211
- print("[sleep] no staged proposals yet.")
212
- return 0
213
-
214
-
215
- def cmd_adopt(args) -> int:
216
- cfg = _cfg_from_args(args)
217
- project = cfg.get("invoked_project") or os.getcwd()
218
- target = args.staging or latest_staging(project)
219
- if not target or not os.path.isdir(target):
220
- print("[sleep] nothing to adopt (no staging dir).")
221
- return 1
222
- updated = adopt_staging(target)
223
- print(f"[sleep] adopted from {target}")
224
- for p in updated:
225
- print(f" -> {p}")
226
- if not updated:
227
- print("[sleep] (proposal contained no accepted changes)")
228
- return 0
229
-
230
-
231
- def cmd_harvest(args) -> int:
232
- cfg = _cfg_from_args(args)
233
- session_limit = cfg.get("max_sessions_per_night", 0) or cfg.get("max_tasks_per_night", 40) * 3
234
- target_skill_path = cfg.managed_skill_path() if cfg.get("target_skill_path", "") else ""
235
- target_skill_text = _read_text(target_skill_path) if target_skill_path else ""
236
- max_tasks = cfg.get("max_tasks_per_night", 40)
237
- candidate_limit = max_tasks
238
- if cfg.get("target_task_filter", True) and target_skill_text:
239
- candidate_limit = max(max_tasks, max_tasks * 3)
240
- digests = harvest_for_config(cfg, limit=session_limit)
241
- tasks = mine(
242
- digests,
243
- max_tasks=max_tasks,
244
- candidate_limit=candidate_limit,
245
- holdout_fraction=cfg.get("holdout_fraction", 0.34),
246
- seed=cfg.get("seed", 42),
247
- target_skill_text=target_skill_text,
248
- target_skill_path=target_skill_path,
249
- )
250
- payload = make_tasks_payload(
251
- tasks,
252
- project=cfg.get("invoked_project") or os.getcwd(),
253
- transcript_source=cfg.get("transcript_source", ""),
254
- n_sessions=len(digests),
255
- target_skill_path=target_skill_path,
256
- )
257
- output_path = ""
258
- if getattr(args, "output", ""):
259
- output_path = write_tasks_file(args.output, payload)
260
- if args.json:
261
- json_payload = dict(payload)
262
- if output_path:
263
- json_payload["output"] = output_path
264
- print(json.dumps(json_payload, ensure_ascii=False, indent=2))
265
- else:
266
- print(f"[sleep] {len(digests)} sessions -> {len(tasks)} tasks")
267
- if output_path:
268
- print(f"[sleep] wrote reviewed-task draft: {output_path}")
269
- for t in tasks:
270
- print(f" [{t.split}/{t.outcome}] {t.intent[:90]}")
271
- return 0
272
-
273
-
274
- def cmd_schedule(args) -> int:
275
- from skillopt_sleep.scheduler import schedule, list_scheduled
276
- cfg = _cfg_from_args(args)
277
- project = cfg.get("invoked_project") or os.getcwd()
278
- ok, msg = schedule(project, backend=cfg.get("backend", "mock"),
279
- hour=args.hour, minute=args.minute,
280
- extra=("--auto-adopt" if getattr(args, "auto_adopt", False) else ""))
281
- print("[sleep] " + msg)
282
- cur = list_scheduled()
283
- if cur:
284
- print("[sleep] currently scheduled:")
285
- for ln in cur:
286
- print(" " + ln[:140])
287
- return 0 if ok else 1
288
-
289
-
290
- def cmd_unschedule(args) -> int:
291
- from skillopt_sleep.scheduler import unschedule
292
- cfg = _cfg_from_args(args)
293
- project = cfg.get("invoked_project") or os.getcwd()
294
- ok, msg = unschedule(project, all_projects=getattr(args, "all", False))
295
- print("[sleep] " + msg)
296
- return 0 if ok else 1
297
-
298
-
299
- def main(argv=None) -> int:
300
- parser = argparse.ArgumentParser(prog="skillopt_sleep", description="SkillOpt-Sleep nightly self-evolution")
301
- sub = parser.add_subparsers(dest="cmd", required=True)
302
-
303
- p_run = sub.add_parser("run", help="run a full sleep cycle")
304
- _add_common(p_run)
305
- p_dry = sub.add_parser("dry-run", help="harvest+mine+replay, report only")
306
- _add_common(p_dry)
307
- p_status = sub.add_parser("status", help="show state + latest proposal")
308
- _add_common(p_status)
309
- p_adopt = sub.add_parser("adopt", help="apply latest staged proposal")
310
- _add_common(p_adopt)
311
- p_adopt.add_argument("--staging", default="", help="specific staging dir")
312
- p_harvest = sub.add_parser("harvest", help="debug: show mined tasks")
313
- _add_common(p_harvest)
314
- p_harvest.add_argument("--output", default="", help="write mined tasks JSON for review")
315
- p_sched = sub.add_parser("schedule", help="install a nightly cron entry for this project")
316
- _add_common(p_sched)
317
- p_sched.add_argument("--hour", type=int, default=3)
318
- p_sched.add_argument("--minute", type=int, default=17)
319
- p_unsched = sub.add_parser("unschedule", help="remove the nightly cron entry")
320
- _add_common(p_unsched)
321
- p_unsched.add_argument("--all", action="store_true", help="remove all managed entries")
322
-
323
- args = parser.parse_args(argv)
324
- if args.cmd == "run":
325
- return cmd_run(args, dry=False)
326
- if args.cmd == "dry-run":
327
- return cmd_run(args, dry=True)
328
- if args.cmd == "status":
329
- return cmd_status(args)
330
- if args.cmd == "adopt":
331
- return cmd_adopt(args)
332
- if args.cmd == "harvest":
333
- return cmd_harvest(args)
334
- if args.cmd == "schedule":
335
- return cmd_schedule(args)
336
- if args.cmd == "unschedule":
337
- return cmd_unschedule(args)
338
- parser.print_help()
339
- return 2
340
-
341
-
342
- if __name__ == "__main__":
343
- sys.exit(main())