sneakoscope 0.7.24 → 0.7.26
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/README.md +53 -1
- package/package.json +3 -1
- package/src/cli/install-helpers.mjs +8 -1
- package/src/cli/main.mjs +44 -5
- package/src/cli/openclaw-command.mjs +83 -0
- package/src/core/fsx.mjs +1 -1
- package/src/core/init.mjs +158 -4
- package/src/core/openclaw.mjs +161 -0
- package/src/core/routes.mjs +2 -1
package/README.md
CHANGED
|
@@ -45,6 +45,7 @@ sks selftest --mock
|
|
|
45
45
|
| --- | --- |
|
|
46
46
|
| CLI runtime | `sks tmux open` and `sks --mad` explicitly launch Codex CLI with tmux; bare `sks` only prints help/readiness surfaces. |
|
|
47
47
|
| Codex App commands | Installs generated skills so `$Team`, `$From-Chat-IMG`, `$DFix`, `$QA-LOOP`, `$PPT`, `$Goal`, `$DB`, `$Wiki`, `$Help`, and related routes are visible in prompt workflows. |
|
|
48
|
+
| OpenClaw agents | Generates an OpenClaw skill package so OpenClaw agents can attach `sneakoscope-codex`, enable the `shell` tool, and discover/use SKS commands from the target repo root. |
|
|
48
49
|
| Pipeline plans | Writes `pipeline-plan.json` for stateful routes so the runtime lane, kept stages, skipped stages, verification commands, and no-unrequested-fallback invariant are visible with `sks pipeline plan`. |
|
|
49
50
|
| Team orchestration | Runs substantial work through score-based ambiguity handling, scouts, TriWiki refresh, debate, runtime task graphs, worker inboxes, implementation, review, cleanup, reflection, and Honest Mode; narrow work should use Proof Field evidence to skip unrelated pipeline work instead of expanding Team. |
|
|
50
51
|
| Skill dreaming | Records cheap generated-skill usage counters in JSON and only periodically scans `.agents/skills` for keep, merge, prune, and improvement candidates. Reports are recommendation-only and never delete skills automatically. |
|
|
@@ -95,7 +96,7 @@ sks bootstrap
|
|
|
95
96
|
|
|
96
97
|
`sks` commands work even when no project root is present. Project-aware commands use the nearest `.sneakoscope`, `.dcodex`, or `.git` root; if none exists, SKS uses a per-user global runtime root. `sks bootstrap` still initializes the current project when you want project-local hooks, skills, and TriWiki state.
|
|
97
98
|
|
|
98
|
-
Project setup writes shared `.gitignore` entries for generated SKS files: `.sneakoscope/`, `.codex/`, `.agents/`, and managed `AGENTS.md`. Use `sks setup --local-only` when you want those excludes kept only in `.git/info/exclude`.
|
|
99
|
+
Project setup writes shared `.gitignore` entries for generated SKS files: `.sneakoscope/`, `.codex/`, `.agents/`, and managed `AGENTS.md`. Setup, doctor repair, and npm postinstall refreshes also compare the previous SKS generated-file manifest with the current package templates and prune stale SKS-generated legacy skills or agent files while preserving user-owned custom skills. Use `sks setup --local-only` when you want those excludes kept only in `.git/info/exclude`.
|
|
99
100
|
|
|
100
101
|
During npm postinstall, SKS also installs generated Codex App skills and tries the official getdesign Codex skill command, `skills add MohtashamMurshid/getdesign`, when the `skills` CLI is available. If that CLI is missing, setup still installs the generated `getdesign-reference` skill. Design work still flows through one authority: `design.md`. When `design.md` is missing, `docs/Design-Sys-Prompt.md` is the builder prompt and getdesign plus curated DESIGN.md examples such as [VoltAgent/awesome-design-md](https://github.com/VoltAgent/awesome-design-md) are inputs to fuse into that SSOT or into route-local `$PPT` style tokens.
|
|
101
102
|
|
|
@@ -301,6 +302,57 @@ Use `sks dollar-commands` to confirm that terminal discovery and Codex App promp
|
|
|
301
302
|
|
|
302
303
|
TriWiki is intentionally sparse: `sks wiki sweep` records demote, soft-forget, archive, delete, promote-to-skill, and promote-to-rule candidates instead of injecting every old claim into future prompts. `sks harness fixture` validates the broader Harness Growth Factory contract: deliberate forgetting fixtures, skill card metadata, experiment schema, tool-error taxonomy, permission profiles, MultiAgentV2 defaults, and tmux cockpit view coverage. `sks code-structure scan` flags handwritten files above 1000/2000/3000-line thresholds so new logic can be extracted before command files become harder to maintain.
|
|
303
304
|
|
|
305
|
+
## OpenClaw Agent Usage
|
|
306
|
+
|
|
307
|
+
Sneakoscope can generate an OpenClaw skill package for agents that need to operate SKS-enabled repositories.
|
|
308
|
+
|
|
309
|
+
```sh
|
|
310
|
+
sks openclaw install
|
|
311
|
+
sks openclaw path
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
By default this writes:
|
|
315
|
+
|
|
316
|
+
```text
|
|
317
|
+
~/.openclaw/skills/sneakoscope-codex/
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
The generated skill contains `manifest.yaml`, `SKILL.md`, a skill README, and `openclaw-agent-config.example.yaml`. If you use a custom OpenClaw home, set `OPENCLAW_HOME` or pass `--dir`:
|
|
321
|
+
|
|
322
|
+
```sh
|
|
323
|
+
OPENCLAW_HOME=/opt/openclaw sks openclaw install
|
|
324
|
+
sks openclaw install --dir /opt/openclaw/skills/sneakoscope-codex
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Attach the skill to an OpenClaw agent with the built-in `shell` tool enabled:
|
|
328
|
+
|
|
329
|
+
```yaml
|
|
330
|
+
agents:
|
|
331
|
+
coding-agent:
|
|
332
|
+
tools:
|
|
333
|
+
- shell
|
|
334
|
+
skills:
|
|
335
|
+
- sneakoscope-codex
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Then prompt the OpenClaw agent from the target repo root:
|
|
339
|
+
|
|
340
|
+
```text
|
|
341
|
+
Run sks root, inspect AGENTS.md, then use the SKS Team route to implement this fix and verify it.
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Useful commands for OpenClaw agents:
|
|
345
|
+
|
|
346
|
+
```sh
|
|
347
|
+
sks root
|
|
348
|
+
sks commands
|
|
349
|
+
sks dollar-commands
|
|
350
|
+
sks deps check
|
|
351
|
+
sks proof-field scan --intent "small CLI change" --changed src/cli/main.mjs
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
If OpenClaw runs the skill inside a sandbox, grant shell execution only for the trusted local workspace. Database, Supabase, migration, and destructive filesystem work should still follow the repo's SKS safety route and require explicit write scope.
|
|
355
|
+
|
|
304
356
|
## Prompt `$` Commands
|
|
305
357
|
|
|
306
358
|
Use these inside Codex App or another agent prompt. They are prompt commands, not terminal commands.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sneakoscope",
|
|
3
3
|
"displayName": "ㅅㅋㅅ",
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.26",
|
|
5
5
|
"description": "Sneakoscope Codex: database-safe Codex CLI/App harness with Team, Goal, AutoResearch, TriWiki, and Honest Mode.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",
|
|
@@ -48,6 +48,8 @@
|
|
|
48
48
|
"sks",
|
|
49
49
|
"openai",
|
|
50
50
|
"openai-codex",
|
|
51
|
+
"openclaw",
|
|
52
|
+
"openclaw-skill",
|
|
51
53
|
"cli",
|
|
52
54
|
"developer-tools",
|
|
53
55
|
"ai-coding",
|
|
@@ -186,7 +186,14 @@ export async function ensureGlobalCodexSkillsDuringInstall(opts = {}) {
|
|
|
186
186
|
try {
|
|
187
187
|
const install = await installSkills(home);
|
|
188
188
|
const skills = await checkRequiredSkills(home, root);
|
|
189
|
-
return {
|
|
189
|
+
return {
|
|
190
|
+
status: skills.ok ? 'installed' : 'partial',
|
|
191
|
+
root,
|
|
192
|
+
installed_count: install.installed_skills.length,
|
|
193
|
+
removed_aliases: install.removed_agent_skill_aliases,
|
|
194
|
+
removed_stale_generated_skills: install.removed_stale_generated_skills,
|
|
195
|
+
missing_skills: skills.missing
|
|
196
|
+
};
|
|
190
197
|
} catch (err) {
|
|
191
198
|
return { status: 'failed', root, error: err.message };
|
|
192
199
|
}
|
package/src/cli/main.mjs
CHANGED
|
@@ -56,11 +56,13 @@ import { buildPromptContext } from '../core/prompt-context-builder.mjs';
|
|
|
56
56
|
import { renderTeamDashboardState, writeTeamDashboardState } from '../core/team-dashboard-renderer.mjs';
|
|
57
57
|
import { GOAL_WORKFLOW_ARTIFACT } from '../core/goal-workflow.mjs';
|
|
58
58
|
import { CODEX_APP_DOCS_URL, codexAppIntegrationStatus, formatCodexAppStatus } from '../core/codex-app.mjs';
|
|
59
|
+
import { OPENCLAW_SKILL_NAME, installOpenClawSkill } from '../core/openclaw.mjs';
|
|
59
60
|
import { buildTmuxLaunchPlan, buildTmuxOpenArgs, createTmuxSession, isTmuxShellSession, runTmuxLaunchPlanSyntaxCheck, tmuxReadiness, tmuxStatusKind, defaultTmuxSessionName, formatTmuxBanner, launchTmuxTeamView, launchTmuxUi, platformTmuxInstallHint, runTmuxStatus, sanitizeTmuxSessionName, teamLaneStyle } from '../core/tmux-ui.mjs';
|
|
60
61
|
import { autoReviewProfileName, autoReviewStatus, autoReviewSummary, enableAutoReview, disableAutoReview, enableMadHighProfile, madHighProfileName } from '../core/auto-review.mjs';
|
|
61
62
|
import { context7Command } from './context7-command.mjs';
|
|
62
63
|
import { askPostinstallQuestion, checkContext7, checkRequiredSkills, ensureCodexCliTool, ensureGlobalCodexSkillsDuringInstall, ensureProjectContext7Config, ensureRelatedCliTools, ensureSksCommandDuringInstall, globalCodexSkillsRoot, postinstall, postinstallBootstrapDecision } from './install-helpers.mjs';
|
|
63
64
|
import { buildTeamPlan, codeStructureCommand, dbCommand, defaultBeta, defaultVGraph, evalCommand, gcCommand, goalCommand, gxCommand, harnessCommand, hproofCommand, memoryCommand, migrateWikiContextPack, parseTeamCreateArgs, perfCommand, profileCommand, projectWikiClaims, proofFieldCommand, qaLoopCommand, quickstartCommand, researchCommand, skillDreamCommand, statsCommand, team, teamWorkflowMarkdown, validateArtifactsCommand, wikiCommand, wikiVoxelRowCount, writeWikiContextPack } from './maintenance-commands.mjs';
|
|
65
|
+
import { openClawCommand } from './openclaw-command.mjs';
|
|
64
66
|
|
|
65
67
|
const flag = (args, name) => args.includes(name);
|
|
66
68
|
const promptOf = (args) => args.filter((x) => !String(x).startsWith('--')).join(' ').trim();
|
|
@@ -88,7 +90,7 @@ export async function main(args) {
|
|
|
88
90
|
if (cmd === 'dollar-commands' || cmd === 'dollars' || cmd === '$') return dollarCommands(tail);
|
|
89
91
|
if (String(cmd).toLowerCase() === 'dfix') return dfixHelp();
|
|
90
92
|
const handlers = {
|
|
91
|
-
postinstall: () => postinstall({ bootstrap }), wizard: () => wizard(tail), ui: () => wizard(tail), 'update-check': () => updateCheck(tail), help: () => help(tail), commands: () => commands(tail), usage: () => usage(tail), root: () => rootCommand(tail), quickstart: () => quickstartCommand(), 'codex-app': () => codexAppHelp(tail), bootstrap: () => bootstrap(tail), deps: () => deps(sub, rest),
|
|
93
|
+
postinstall: () => postinstall({ bootstrap }), wizard: () => wizard(tail), ui: () => wizard(tail), 'update-check': () => updateCheck(tail), help: () => help(tail), commands: () => commands(tail), usage: () => usage(tail), root: () => rootCommand(tail), quickstart: () => quickstartCommand(), 'codex-app': () => codexAppHelp(tail), openclaw: () => openClawCommand(tail), bootstrap: () => bootstrap(tail), deps: () => deps(sub, rest),
|
|
92
94
|
'qa-loop': () => qaLoopCommand(sub, rest), ppt: () => pptCommand(sub, rest), context7: () => context7Command(sub, rest), pipeline: () => pipeline(sub, rest), guard: () => guard(sub, rest), conflicts: () => conflicts(sub, rest), versioning: () => versioning(sub, rest), reasoning: () => reasoningCommand(tail), aliases: () => aliases(), setup: () => setup(tail), 'fix-path': () => fixPath(tail), doctor: () => doctor(tail), init: () => init(tail), selftest: () => selftest(tail),
|
|
93
95
|
goal: () => goalCommand(sub, rest), research: () => researchCommand(sub, rest), hook: () => emitHook(sub), profile: () => profileCommand(sub, rest), hproof: () => hproofCommand(sub, rest), 'validate-artifacts': () => validateArtifactsCommand(tail), perf: () => perfCommand(sub, rest), 'proof-field': () => proofFieldCommand(sub, rest), 'skill-dream': () => skillDreamCommand(sub, rest), 'code-structure': () => codeStructureCommand(sub, rest), memory: () => memoryCommand(sub, rest), gx: () => gxCommand(sub, rest),
|
|
94
96
|
team: () => team(tail), db: () => dbCommand(sub, rest), eval: () => evalCommand(sub, rest), harness: () => harnessCommand(sub, rest), wiki: () => wikiCommand(sub, rest), gc: () => gcCommand(tail), stats: () => statsCommand(tail)
|
|
@@ -116,6 +118,7 @@ Usage:
|
|
|
116
118
|
sks bootstrap [--install-scope global|project] [--local-only] [--json]
|
|
117
119
|
sks deps check|install [tmux|codex|context7|all] [--yes] [--json]
|
|
118
120
|
sks codex-app
|
|
121
|
+
sks openclaw install|path|print [--dir path] [--force] [--json]
|
|
119
122
|
sks --mad [--high]
|
|
120
123
|
sks auto-review status|enable|start [--high]
|
|
121
124
|
sks --Auto-review [--high]
|
|
@@ -1192,6 +1195,7 @@ function usage(args = []) {
|
|
|
1192
1195
|
root: ['Root', '', ' sks root [--json]', '', 'Inside a project, SKS uses that project root. Outside any project marker, runtime commands use the per-user global SKS root instead of writing .sneakoscope into the current random folder.'],
|
|
1193
1196
|
deps: ['Dependencies', '', ' sks deps check [--json]', ' sks deps install [tmux|codex|context7|all] [--yes]', '', 'tmux on macOS uses Homebrew only after approval.'],
|
|
1194
1197
|
tmux: ['tmux', '', ' sks tmux open', ' sks tmux check', ' sks tmux status --once', ' sks deps install tmux', '', 'tmux launch is explicit. Running bare `sks` prints help and never opens tmux by itself.'],
|
|
1198
|
+
openclaw: ['OpenClaw', '', ' sks openclaw install', ' sks openclaw path', ' sks openclaw print SKILL.md', '', 'Installs an OpenClaw skill package under ~/.openclaw/skills/sneakoscope-codex so OpenClaw agents can attach skills: [sneakoscope-codex] with the shell tool and call local SKS commands from a project root.'],
|
|
1195
1199
|
team: ['Team', '', ' sks team "task" executor:5 reviewer:2 user:1', ' sks team watch latest', ' sks team lane latest --agent analysis_scout_1 --follow', ' sks team message latest --from analysis_scout_1 --to executor_1 --message "handoff note"', ' sks team cleanup-tmux latest', '', '$Team runs questions -> contract -> scouts -> TriWiki attention -> debate -> runtime graph/inbox -> fresh executors -> review -> cleanup -> reflection -> Honest.'],
|
|
1196
1200
|
'qa-loop': ['QA-LOOP', '', ' sks qa-loop prepare "QA this app"', ' sks qa-loop answer <MISSION_ID> answers.json', ' sks qa-loop run <MISSION_ID> --max-cycles 8', '', 'Report: YYYY-MM-DD-v<version>-qa-report.md'],
|
|
1197
1201
|
ppt: ['PPT', '', ' $PPT 투자자용 피치덱을 HTML 기반 PDF로 만들어줘', ' $PPT 우리 SaaS 소개자료 만들어줘', ' sks ppt build latest --json', ' sks ppt status latest --json', '', '$PPT asks delivery context, audience profile, STP strategy, decision context, and 3+ pain-point/solution/aha mappings before source research, design-system work, HTML/PDF export, and render QA. Independent strategy/render/file-write phases run in parallel where inputs allow and are recorded in ppt-parallel-report.json. The visual system must stay simple, restrained, and information-first; editable source HTML is kept under source-html/, PPT-only temporary build files are cleaned, and installed skills/MCPs outside the $PPT allowlist are ignored. Design uses getdesign-reference plus the built-in PPT design pipeline; imagegen and Context7 are conditional only when the sealed PPT contract needs raster assets or current external docs.'],
|
|
@@ -1396,11 +1400,12 @@ async function doctor(args) {
|
|
|
1396
1400
|
let conflictScan = await scanHarnessConflicts(root);
|
|
1397
1401
|
let repairApplied = false;
|
|
1398
1402
|
let globalSkillsRepair = null;
|
|
1403
|
+
let projectRepair = null;
|
|
1399
1404
|
const globalCommand = await globalSksCommand();
|
|
1400
1405
|
if (flag(args, '--fix') && !conflictScan.hard_block) {
|
|
1401
1406
|
const existingManifest = await readJson(path.join(root, '.sneakoscope', 'manifest.json'), null);
|
|
1402
1407
|
const fixScope = requestedScope || normalizeInstallScope(existingManifest?.installation?.scope || 'global');
|
|
1403
|
-
await initProject(root, { installScope: fixScope, globalCommand, localOnly: flag(args, '--local-only') || Boolean(existingManifest?.git?.local_only), force: true, repair: true });
|
|
1408
|
+
projectRepair = await initProject(root, { installScope: fixScope, globalCommand, localOnly: flag(args, '--local-only') || Boolean(existingManifest?.git?.local_only), force: true, repair: true });
|
|
1404
1409
|
if (!flag(args, '--local-only')) globalSkillsRepair = await ensureGlobalCodexSkillsDuringInstall({ force: true });
|
|
1405
1410
|
repairApplied = true;
|
|
1406
1411
|
conflictScan = await scanHarnessConflicts(root);
|
|
@@ -1427,7 +1432,7 @@ async function doctor(args) {
|
|
|
1427
1432
|
const result = {
|
|
1428
1433
|
node: { ok: nodeOk, version: process.version }, root, codex, rust,
|
|
1429
1434
|
install,
|
|
1430
|
-
repair: { applied: repairApplied, global_skills: globalSkillsRepair, blocked_by_other_harness: flag(args, '--fix') && conflictScan.hard_block },
|
|
1435
|
+
repair: { applied: repairApplied, project: projectRepair, global_skills: globalSkillsRepair, blocked_by_other_harness: flag(args, '--fix') && conflictScan.hard_block },
|
|
1431
1436
|
harness_conflicts: {
|
|
1432
1437
|
ok: conflictScan.ok,
|
|
1433
1438
|
hard_block: conflictScan.hard_block,
|
|
@@ -1652,7 +1657,7 @@ function readMaxCycles(args, fallback) {
|
|
|
1652
1657
|
|
|
1653
1658
|
function positionalArgs(args = []) {
|
|
1654
1659
|
const out = [];
|
|
1655
|
-
const valueFlags = new Set(['--format', '--iterations', '--out', '--baseline', '--candidate', '--install-scope', '--max-cycles', '--depth', '--scope', '--transport', '--query', '--topic', '--tokens', '--timeout-ms', '--sql', '--command', '--project-ref', '--agent', '--phase', '--message', '--role', '--max-anchors', '--lines']);
|
|
1660
|
+
const valueFlags = new Set(['--format', '--iterations', '--out', '--baseline', '--candidate', '--install-scope', '--max-cycles', '--depth', '--scope', '--transport', '--query', '--topic', '--tokens', '--timeout-ms', '--sql', '--command', '--project-ref', '--agent', '--phase', '--message', '--role', '--max-anchors', '--lines', '--dir']);
|
|
1656
1661
|
for (let i = 0; i < args.length; i++) {
|
|
1657
1662
|
const arg = String(args[i]);
|
|
1658
1663
|
if (valueFlags.has(arg)) {
|
|
@@ -1743,6 +1748,26 @@ async function selftest() {
|
|
|
1743
1748
|
await initProject(repairTmp, { installScope: 'project', localOnly: true });
|
|
1744
1749
|
await writeTextAtomic(path.join(repairTmp, '.agents', 'skills', 'team', 'SKILL.md'), 'tampered\n');
|
|
1745
1750
|
await writeTextAtomic(path.join(repairTmp, '.agents', 'skills', 'agent-team', 'SKILL.md'), '---\nname: agent-team\ndescription: Fallback Codex App picker alias for $Team.\n---\n');
|
|
1751
|
+
await ensureDir(path.join(repairTmp, '.agents', 'skills', 'stale-sks-generated'));
|
|
1752
|
+
await writeTextAtomic(path.join(repairTmp, '.agents', 'skills', 'stale-sks-generated', 'SKILL.md'), '---\nname: stale-sks-generated\ndescription: Old SKS generated skill that should disappear on update.\n---\n');
|
|
1753
|
+
await writeJsonAtomic(path.join(repairTmp, '.agents', 'skills', '.sks-generated.json'), {
|
|
1754
|
+
schema_version: 1,
|
|
1755
|
+
generated_by: 'sneakoscope',
|
|
1756
|
+
version: '0.0.1',
|
|
1757
|
+
skills: ['team', 'stale-sks-generated'],
|
|
1758
|
+
files: ['.agents/skills/team/SKILL.md', '.agents/skills/stale-sks-generated/SKILL.md']
|
|
1759
|
+
});
|
|
1760
|
+
const staleCodexAgentRel = '.codex/agents/stale-generated.toml';
|
|
1761
|
+
await writeTextAtomic(path.join(repairTmp, staleCodexAgentRel), 'name = "stale_generated"\n');
|
|
1762
|
+
const staleManifest = await readJson(path.join(repairTmp, '.sneakoscope', 'manifest.json'));
|
|
1763
|
+
staleManifest.version = '0.0.1';
|
|
1764
|
+
staleManifest.generated_files = {
|
|
1765
|
+
schema_version: 1,
|
|
1766
|
+
generated_by: 'sneakoscope',
|
|
1767
|
+
prune_policy: 'remove_previous_sks_generated_paths_absent_from_current_manifest',
|
|
1768
|
+
files: [...(staleManifest.generated_files?.files || []), '.agents/skills/stale-sks-generated/SKILL.md', staleCodexAgentRel]
|
|
1769
|
+
};
|
|
1770
|
+
await writeJsonAtomic(path.join(repairTmp, '.sneakoscope', 'manifest.json'), staleManifest);
|
|
1746
1771
|
await ensureDir(path.join(repairTmp, '.agents', 'skills', 'custom-keep'));
|
|
1747
1772
|
await writeTextAtomic(path.join(repairTmp, '.agents', 'skills', 'custom-keep', 'SKILL.md'), '---\nname: custom-keep\ndescription: User custom skill, not generated by SKS.\n---\n');
|
|
1748
1773
|
await writeTextAtomic(path.join(repairTmp, '.codex', 'skills', 'team', 'SKILL.md'), 'legacy mirror\n');
|
|
@@ -1765,6 +1790,11 @@ async function selftest() {
|
|
|
1765
1790
|
const repairedTeamSkill = await safeReadText(path.join(repairTmp, '.agents', 'skills', 'team', 'SKILL.md'));
|
|
1766
1791
|
if (!repairedTeamSkill.includes('SKS Team orchestration') || repairedTeamSkill.includes('tampered')) throw new Error('selftest failed: doctor repair did not regenerate team skill');
|
|
1767
1792
|
if (await exists(path.join(repairTmp, '.agents', 'skills', 'agent-team', 'SKILL.md'))) throw new Error('selftest failed: doctor repair did not remove deprecated agent-team alias skill');
|
|
1793
|
+
if (await exists(path.join(repairTmp, '.agents', 'skills', 'stale-sks-generated', 'SKILL.md'))) throw new Error('selftest failed: doctor repair did not prune stale generated skill from previous SKS manifest');
|
|
1794
|
+
if (await exists(path.join(repairTmp, staleCodexAgentRel))) throw new Error('selftest failed: doctor repair did not prune stale generated agent file from previous SKS manifest');
|
|
1795
|
+
if (!doctorRepairJson.repair?.project?.skill_install?.removed_stale_generated_skills?.includes('.agents/skills/stale-sks-generated')) throw new Error('selftest failed: doctor repair did not report stale generated skill pruning');
|
|
1796
|
+
const generatedCleanupReport = doctorRepairJson.repair?.project?.generated_cleanup || {};
|
|
1797
|
+
if (![...(generatedCleanupReport.pruned || []), ...(generatedCleanupReport.already_absent || [])].includes(staleCodexAgentRel)) throw new Error('selftest failed: doctor repair did not report stale generated file pruning');
|
|
1768
1798
|
if (!(await exists(path.join(repairTmp, '.agents', 'skills', 'custom-keep', 'SKILL.md')))) throw new Error('selftest failed: doctor repair removed a user-owned custom skill');
|
|
1769
1799
|
if (await exists(path.join(repairTmp, '.codex', 'skills', 'team', 'SKILL.md'))) throw new Error('selftest failed: doctor repair did not remove legacy .codex/skills');
|
|
1770
1800
|
const repairedQuickReference = await safeReadText(path.join(repairTmp, '.codex', 'SNEAKOSCOPE.md'));
|
|
@@ -2036,7 +2066,16 @@ async function selftest() {
|
|
|
2036
2066
|
if (!DOLLAR_DEFAULT_PIPELINE_TEXT.includes('$Team')) throw new Error('selftest failed: dollar-commands missing Team default routing guidance');
|
|
2037
2067
|
if (!DOLLAR_DEFAULT_PIPELINE_TEXT.includes('$From-Chat-IMG')) throw new Error('selftest failed: dollar-commands missing From-Chat-IMG guidance');
|
|
2038
2068
|
if (!DOLLAR_DEFAULT_PIPELINE_TEXT.includes('$MAD-SKS')) throw new Error('selftest failed: dollar-commands missing MAD-SKS scoped override guidance');
|
|
2039
|
-
if (!COMMAND_CATALOG.some((c) => c.name === 'context7') || !COMMAND_CATALOG.some((c) => c.name === 'pipeline') || !COMMAND_CATALOG.some((c) => c.name === 'qa-loop') || !COMMAND_CATALOG.some((c) => c.name === 'root')) throw new Error('selftest failed: context7/pipeline/qa-loop/root commands missing from catalog');
|
|
2069
|
+
if (!COMMAND_CATALOG.some((c) => c.name === 'context7') || !COMMAND_CATALOG.some((c) => c.name === 'pipeline') || !COMMAND_CATALOG.some((c) => c.name === 'qa-loop') || !COMMAND_CATALOG.some((c) => c.name === 'root') || !COMMAND_CATALOG.some((c) => c.name === 'openclaw')) throw new Error('selftest failed: context7/pipeline/qa-loop/root/openclaw commands missing from catalog');
|
|
2070
|
+
const openClawTmp = tmpdir();
|
|
2071
|
+
const openClawResult = await installOpenClawSkill({ targetDir: path.join(openClawTmp, 'skills', OPENCLAW_SKILL_NAME) });
|
|
2072
|
+
if (!openClawResult.ok) throw new Error(`selftest failed: OpenClaw skill install blocked: ${openClawResult.reason}`);
|
|
2073
|
+
const openClawSkillText = await safeReadText(path.join(openClawResult.target_dir, 'SKILL.md'));
|
|
2074
|
+
const openClawManifestText = await safeReadText(path.join(openClawResult.target_dir, 'manifest.yaml'));
|
|
2075
|
+
const openClawConfigText = await safeReadText(path.join(openClawResult.target_dir, 'openclaw-agent-config.example.yaml'));
|
|
2076
|
+
if (!openClawSkillText.includes('sks root') || !openClawSkillText.includes('$Team') || !openClawSkillText.includes('OpenClaw agent must have the built-in `shell` tool enabled')) throw new Error('selftest failed: OpenClaw skill missing SKS agent guidance');
|
|
2077
|
+
if (!openClawManifestText.includes('generated_by: sneakoscope') || !openClawManifestText.includes(`version: ${PACKAGE_VERSION}`)) throw new Error('selftest failed: OpenClaw manifest missing generated marker or version');
|
|
2078
|
+
if (!openClawConfigText.includes(`- ${OPENCLAW_SKILL_NAME}`) || !openClawConfigText.includes('- shell')) throw new Error('selftest failed: OpenClaw agent config example missing skill or shell tool');
|
|
2040
2079
|
const registryDollarCommands = DOLLAR_COMMANDS.map((c) => c.command);
|
|
2041
2080
|
const manifest = await readJson(path.join(tmp, '.sneakoscope', 'manifest.json'));
|
|
2042
2081
|
const policy = await readJson(path.join(tmp, '.sneakoscope', 'policy.json'));
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { OPENCLAW_SKILL_NAME, buildOpenClawSkillFiles, defaultOpenClawSkillDir, installOpenClawSkill } from '../core/openclaw.mjs';
|
|
3
|
+
|
|
4
|
+
const flag = (args, name) => args.includes(name);
|
|
5
|
+
|
|
6
|
+
function readFlagValue(args, name, fallback) {
|
|
7
|
+
const i = args.indexOf(name);
|
|
8
|
+
return i >= 0 && args[i + 1] ? args[i + 1] : fallback;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function positionalArgs(args = []) {
|
|
12
|
+
const out = [];
|
|
13
|
+
const valueFlags = new Set(['--dir']);
|
|
14
|
+
for (let i = 0; i < args.length; i++) {
|
|
15
|
+
const arg = String(args[i]);
|
|
16
|
+
if (valueFlags.has(arg)) {
|
|
17
|
+
i++;
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (!arg.startsWith('--')) out.push(arg);
|
|
21
|
+
}
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function openClawCommand(args = []) {
|
|
26
|
+
const action = args[0] || 'help';
|
|
27
|
+
const targetDir = readFlagValue(args, '--dir', defaultOpenClawSkillDir());
|
|
28
|
+
const resultOptions = {
|
|
29
|
+
targetDir,
|
|
30
|
+
force: flag(args, '--force'),
|
|
31
|
+
dryRun: flag(args, '--dry-run')
|
|
32
|
+
};
|
|
33
|
+
if (action === 'path') {
|
|
34
|
+
const result = { skill: OPENCLAW_SKILL_NAME, target_dir: path.resolve(targetDir) };
|
|
35
|
+
if (flag(args, '--json')) return console.log(JSON.stringify(result, null, 2));
|
|
36
|
+
console.log(result.target_dir);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (action === 'print') {
|
|
40
|
+
const file = positionalArgs(args.slice(1))[0] || 'SKILL.md';
|
|
41
|
+
const files = buildOpenClawSkillFiles();
|
|
42
|
+
if (!files[file]) {
|
|
43
|
+
console.error(`Unknown OpenClaw skill file: ${file}`);
|
|
44
|
+
console.error(`Files: ${Object.keys(files).join(', ')}`);
|
|
45
|
+
process.exitCode = 1;
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
console.log(files[file]);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (action === 'install') {
|
|
52
|
+
const result = await installOpenClawSkill(resultOptions);
|
|
53
|
+
if (flag(args, '--json')) return console.log(JSON.stringify(result, null, 2));
|
|
54
|
+
if (!result.ok) {
|
|
55
|
+
console.error(`OpenClaw skill install blocked: ${result.reason}`);
|
|
56
|
+
console.error(`Target: ${result.target_dir}`);
|
|
57
|
+
process.exitCode = 1;
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
console.log(`OpenClaw skill ${result.status}: ${result.target_dir}`);
|
|
61
|
+
console.log(`Attach it to an agent with skills: [${OPENCLAW_SKILL_NAME}] and tools: [shell].`);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
console.log(`OpenClaw
|
|
65
|
+
|
|
66
|
+
Usage:
|
|
67
|
+
sks openclaw install [--dir path] [--force] [--dry-run] [--json]
|
|
68
|
+
sks openclaw path [--dir path] [--json]
|
|
69
|
+
sks openclaw print [SKILL.md|manifest.yaml|README.md|openclaw-agent-config.example.yaml]
|
|
70
|
+
|
|
71
|
+
Default skill: ${OPENCLAW_SKILL_NAME}
|
|
72
|
+
Default path: ${defaultOpenClawSkillDir()}
|
|
73
|
+
|
|
74
|
+
After install, add this to an OpenClaw agent config:
|
|
75
|
+
|
|
76
|
+
agents:
|
|
77
|
+
coding-agent:
|
|
78
|
+
tools:
|
|
79
|
+
- shell
|
|
80
|
+
skills:
|
|
81
|
+
- ${OPENCLAW_SKILL_NAME}
|
|
82
|
+
`);
|
|
83
|
+
}
|
package/src/core/fsx.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import os from 'node:os';
|
|
|
5
5
|
import crypto from 'node:crypto';
|
|
6
6
|
import { spawn } from 'node:child_process';
|
|
7
7
|
|
|
8
|
-
export const PACKAGE_VERSION = '0.7.
|
|
8
|
+
export const PACKAGE_VERSION = '0.7.26';
|
|
9
9
|
export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
|
|
10
10
|
export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
|
|
11
11
|
|
package/src/core/init.mjs
CHANGED
|
@@ -11,6 +11,8 @@ import { SKILL_DREAM_POLICY, skillDreamPolicyText } from './skill-forge.mjs';
|
|
|
11
11
|
|
|
12
12
|
const REFLECTION_MEMORY_PATH = '.sneakoscope/memory/q2_facts/post-route-reflection.md';
|
|
13
13
|
const SKS_GENERATED_GIT_PATTERNS = ['.sneakoscope/', '.codex/', '.agents/', 'AGENTS.md'];
|
|
14
|
+
const SKS_SKILL_MANIFEST_FILE = '.sks-generated.json';
|
|
15
|
+
const GENERATED_PRUNE_POLICY = 'remove_previous_sks_generated_paths_absent_from_current_manifest';
|
|
14
16
|
|
|
15
17
|
function reflectionInstructionText(commandPrefix = 'sks') {
|
|
16
18
|
return `Post-route reflection: full routes load \`reflection\` after work/tests and before final; DFix/Answer/Help/Wiki/SKS discovery are exempt. Write reflection.md; record only real misses/gaps, or no_issue_acknowledged. For lessons, append TriWiki claim rows to ${REFLECTION_MEMORY_PATH}. Run "${commandPrefix} wiki refresh" or pack, validate, then pass reflection-gate.json.`;
|
|
@@ -101,6 +103,8 @@ export async function initProject(root, opts = {}) {
|
|
|
101
103
|
const requestedHookCommandPrefix = opts.hookCommandPrefix || sksCommandPrefix(installScope, { globalCommand: opts.globalCommand });
|
|
102
104
|
const hookCommandPrefix = sourceProject ? 'node ./bin/sks.mjs' : requestedHookCommandPrefix;
|
|
103
105
|
const sine = path.join(root, '.sneakoscope');
|
|
106
|
+
const manifestPath = path.join(sine, 'manifest.json');
|
|
107
|
+
const previousManifest = await readJson(manifestPath, null);
|
|
104
108
|
if (opts.repair) {
|
|
105
109
|
const repair = await repairSksGeneratedArtifacts(root, { resetState: Boolean(opts.resetState) });
|
|
106
110
|
if (repair.removed.length) created.push(`repaired generated SKS files (${repair.removed.length})`);
|
|
@@ -114,7 +118,7 @@ export async function initProject(root, opts = {}) {
|
|
|
114
118
|
if (localExclude?.path) created.push(`${path.relative(root, localExclude.path)} local-only excludes`);
|
|
115
119
|
if (sharedIgnore?.changed) created.push(`${path.relative(root, sharedIgnore.path)} SKS generated files ignore`);
|
|
116
120
|
|
|
117
|
-
|
|
121
|
+
const manifest = {
|
|
118
122
|
package: 'sneakoscope',
|
|
119
123
|
version: PACKAGE_VERSION,
|
|
120
124
|
initialized_at: nowIso(),
|
|
@@ -197,7 +201,8 @@ export async function initProject(root, opts = {}) {
|
|
|
197
201
|
},
|
|
198
202
|
database_safety: 'destructive_db_operations_denied_always',
|
|
199
203
|
gx_renderer: 'deterministic_svg_html'
|
|
200
|
-
}
|
|
204
|
+
};
|
|
205
|
+
await writeJsonAtomic(manifestPath, manifest);
|
|
201
206
|
created.push('.sneakoscope/manifest.json');
|
|
202
207
|
|
|
203
208
|
const dbSafetyPath = path.join(sine, 'db-safety.json');
|
|
@@ -467,16 +472,36 @@ policy = "Deny destructive database operations, credential exfiltration, persist
|
|
|
467
472
|
|
|
468
473
|
const skillInstall = await installSkills(root);
|
|
469
474
|
created.push('.agents/skills/*');
|
|
475
|
+
if (skillInstall.removed_stale_generated_skills.length) created.push(`stale generated skills removed (${skillInstall.removed_stale_generated_skills.length})`);
|
|
476
|
+
if (skillInstall.removed_agent_skill_aliases.length) created.push(`deprecated generated skill aliases removed (${skillInstall.removed_agent_skill_aliases.length})`);
|
|
470
477
|
if (skillInstall.removed_codex_skill_mirrors.length) created.push(`.codex/skills generated mirrors removed (${skillInstall.removed_codex_skill_mirrors.length})`);
|
|
471
|
-
await installCodexAgents(root);
|
|
478
|
+
const agentInstall = await installCodexAgents(root);
|
|
472
479
|
created.push('.codex/agents/*');
|
|
480
|
+
const generatedFiles = currentGeneratedFileInventory(skillInstall, agentInstall);
|
|
481
|
+
const generatedCleanup = await pruneStaleGeneratedFiles(root, previousManifest, generatedFiles);
|
|
482
|
+
if (generatedCleanup.pruned.length) created.push(`stale generated files pruned (${generatedCleanup.pruned.length})`);
|
|
483
|
+
manifest.generated_files = {
|
|
484
|
+
schema_version: 1,
|
|
485
|
+
generated_by: 'sneakoscope',
|
|
486
|
+
prune_policy: GENERATED_PRUNE_POLICY,
|
|
487
|
+
files: generatedFiles
|
|
488
|
+
};
|
|
489
|
+
manifest.generated_cleanup = {
|
|
490
|
+
schema_version: 1,
|
|
491
|
+
last_run_at: nowIso(),
|
|
492
|
+
previous_version: previousManifest?.version || null,
|
|
493
|
+
current_version: PACKAGE_VERSION,
|
|
494
|
+
pruned: generatedCleanup.pruned,
|
|
495
|
+
already_absent: generatedCleanup.already_absent || []
|
|
496
|
+
};
|
|
497
|
+
await writeJsonAtomic(manifestPath, manifest);
|
|
473
498
|
await writeHarnessGuardPolicy(root);
|
|
474
499
|
created.push('.sneakoscope/harness-guard.json');
|
|
475
500
|
const versionHookCommand = sourceProject ? 'node ./bin/sks.mjs' : hookCommandPrefix;
|
|
476
501
|
const versionHook = await installVersionGitHook(root, versionHookCommand);
|
|
477
502
|
if (versionHook.installed) created.push('.git/hooks/pre-commit SKS version guard');
|
|
478
503
|
else created.push(`version guard skipped (${versionHook.reason})`);
|
|
479
|
-
return { created };
|
|
504
|
+
return { created, generated_cleanup: generatedCleanup, skill_install: skillInstall };
|
|
480
505
|
}
|
|
481
506
|
|
|
482
507
|
async function ensureSharedGitIgnore(root) {
|
|
@@ -601,13 +626,57 @@ export async function installSkills(root) {
|
|
|
601
626
|
await writeSkillMetadata(dir, name);
|
|
602
627
|
}
|
|
603
628
|
const skillNames = Object.keys(skills);
|
|
629
|
+
const removedStaleGeneratedSkills = await removeStaleGeneratedSkillsFromManifest(root, skillNames);
|
|
630
|
+
await writeGeneratedSkillManifest(root, skillNames);
|
|
604
631
|
return {
|
|
605
632
|
installed_skills: skillNames,
|
|
633
|
+
generated_files: generatedSkillFiles(skillNames),
|
|
634
|
+
removed_stale_generated_skills: removedStaleGeneratedSkills,
|
|
606
635
|
removed_agent_skill_aliases: await removeGeneratedAgentSkillAliases(root, skillNames),
|
|
607
636
|
removed_codex_skill_mirrors: await removeGeneratedCodexSkillMirrors(root, skillNames)
|
|
608
637
|
};
|
|
609
638
|
}
|
|
610
639
|
|
|
640
|
+
function generatedSkillFiles(skillNames) {
|
|
641
|
+
return skillNames.flatMap((name) => [
|
|
642
|
+
`.agents/skills/${name}/SKILL.md`,
|
|
643
|
+
`.agents/skills/${name}/agents/openai.yaml`
|
|
644
|
+
]).sort();
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function generatedSkillManifestPath(root) {
|
|
648
|
+
return path.join(root, '.agents', 'skills', SKS_SKILL_MANIFEST_FILE);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
async function writeGeneratedSkillManifest(root, skillNames) {
|
|
652
|
+
const manifestPath = generatedSkillManifestPath(root);
|
|
653
|
+
await writeJsonAtomic(manifestPath, {
|
|
654
|
+
schema_version: 1,
|
|
655
|
+
generated_by: 'sneakoscope',
|
|
656
|
+
version: PACKAGE_VERSION,
|
|
657
|
+
prune_policy: GENERATED_PRUNE_POLICY,
|
|
658
|
+
skills: [...skillNames].sort(),
|
|
659
|
+
files: generatedSkillFiles(skillNames)
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
async function removeStaleGeneratedSkillsFromManifest(root, skillNames) {
|
|
664
|
+
const previous = await readJson(generatedSkillManifestPath(root), null);
|
|
665
|
+
const previousSkills = Array.isArray(previous?.skills) ? previous.skills : [];
|
|
666
|
+
if (!previousSkills.length) return [];
|
|
667
|
+
const current = new Set(skillNames);
|
|
668
|
+
const removed = [];
|
|
669
|
+
for (const name of previousSkills) {
|
|
670
|
+
const skillName = String(name || '').trim();
|
|
671
|
+
if (!skillName || current.has(skillName) || !/^[a-z0-9-]+$/.test(skillName)) continue;
|
|
672
|
+
const dir = path.join(root, '.agents', 'skills', skillName);
|
|
673
|
+
if (!(await exists(dir))) continue;
|
|
674
|
+
await fsp.rm(dir, { recursive: true, force: true });
|
|
675
|
+
removed.push(path.relative(root, dir));
|
|
676
|
+
}
|
|
677
|
+
return removed.sort();
|
|
678
|
+
}
|
|
679
|
+
|
|
611
680
|
function enrichSkillContent(name, content) {
|
|
612
681
|
if (!['sks', 'answer', 'wiki', 'team', 'qa-loop', 'ppt', 'computer-use', 'computer-use-fast', 'cu', 'goal', 'research', 'autoresearch', 'db', 'gx', 'reflection', 'prompt-pipeline', 'pipeline-runner', 'context7-docs', 'turbo-context-pack', 'hproof-evidence-bind'].includes(name)) return content;
|
|
613
682
|
const text = String(content || '').trimEnd();
|
|
@@ -704,4 +773,89 @@ async function installCodexAgents(root) {
|
|
|
704
773
|
for (const [file, content] of Object.entries(agents)) {
|
|
705
774
|
await writeTextAtomic(path.join(dir, file), content);
|
|
706
775
|
}
|
|
776
|
+
return {
|
|
777
|
+
installed_agents: Object.keys(agents),
|
|
778
|
+
generated_files: Object.keys(agents).map((file) => `.codex/agents/${file}`).sort()
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
function currentGeneratedFileInventory(skillInstall = {}, agentInstall = {}) {
|
|
783
|
+
return Array.from(new Set([
|
|
784
|
+
'.codex/config.toml',
|
|
785
|
+
'.codex/SNEAKOSCOPE.md',
|
|
786
|
+
'.codex/hooks.json',
|
|
787
|
+
'.sneakoscope/harness-guard.json',
|
|
788
|
+
'.sneakoscope/db-safety.json',
|
|
789
|
+
'.sneakoscope/policy.json',
|
|
790
|
+
'.agents/skills/.sks-generated.json',
|
|
791
|
+
...(Array.isArray(skillInstall.generated_files) ? skillInstall.generated_files : []),
|
|
792
|
+
...(Array.isArray(agentInstall.generated_files) ? agentInstall.generated_files : [])
|
|
793
|
+
])).sort();
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
async function pruneStaleGeneratedFiles(root, previousManifest, currentFiles) {
|
|
797
|
+
const previousFiles = Array.isArray(previousManifest?.generated_files?.files) ? previousManifest.generated_files.files : [];
|
|
798
|
+
if (!previousFiles.length) return { pruned: [] };
|
|
799
|
+
const current = new Set(currentFiles);
|
|
800
|
+
const pruned = [];
|
|
801
|
+
const already_absent = [];
|
|
802
|
+
for (const rel of previousFiles) {
|
|
803
|
+
const relPath = normalizeGeneratedRelPath(rel);
|
|
804
|
+
if (!relPath || current.has(relPath) || !isPrunableGeneratedPath(relPath)) continue;
|
|
805
|
+
const removed = await removeGeneratedRelPath(root, relPath);
|
|
806
|
+
if (removed) pruned.push(removed);
|
|
807
|
+
else already_absent.push(relPath);
|
|
808
|
+
}
|
|
809
|
+
return { pruned: pruned.sort(), already_absent: already_absent.sort() };
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
function normalizeGeneratedRelPath(value) {
|
|
813
|
+
const rel = String(value || '').trim().replaceAll('\\', '/');
|
|
814
|
+
if (!rel || rel.startsWith('/') || rel.includes('\0')) return null;
|
|
815
|
+
if (rel.split('/').some((part) => part === '..')) return null;
|
|
816
|
+
return rel;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
function isPrunableGeneratedPath(rel) {
|
|
820
|
+
if (rel.startsWith('.agents/skills/')) return true;
|
|
821
|
+
if (rel.startsWith('.codex/agents/')) return true;
|
|
822
|
+
if (rel.startsWith('.codex/skills/')) return true;
|
|
823
|
+
return new Set([
|
|
824
|
+
'.codex/config.toml',
|
|
825
|
+
'.codex/SNEAKOSCOPE.md',
|
|
826
|
+
'.codex/hooks.json',
|
|
827
|
+
'.sneakoscope/harness-guard.json',
|
|
828
|
+
'.sneakoscope/db-safety.json',
|
|
829
|
+
'.sneakoscope/policy.json'
|
|
830
|
+
]).has(rel);
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
async function removeGeneratedRelPath(root, rel) {
|
|
834
|
+
const absRoot = path.resolve(root);
|
|
835
|
+
const abs = path.resolve(absRoot, rel);
|
|
836
|
+
if (abs !== absRoot && !abs.startsWith(`${absRoot}${path.sep}`)) return null;
|
|
837
|
+
if (!(await exists(abs))) return null;
|
|
838
|
+
await fsp.rm(abs, { recursive: true, force: true });
|
|
839
|
+
await removeEmptyGeneratedParents(root, rel);
|
|
840
|
+
return rel;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
async function removeEmptyGeneratedParents(root, rel) {
|
|
844
|
+
const parts = rel.split('/');
|
|
845
|
+
if (parts.length <= 1) return;
|
|
846
|
+
const stopDirs = new Set([
|
|
847
|
+
path.resolve(root, '.agents', 'skills'),
|
|
848
|
+
path.resolve(root, '.codex', 'agents'),
|
|
849
|
+
path.resolve(root, '.codex', 'skills'),
|
|
850
|
+
path.resolve(root, '.codex'),
|
|
851
|
+
path.resolve(root, '.sneakoscope')
|
|
852
|
+
]);
|
|
853
|
+
let dir = path.resolve(root, ...parts.slice(0, -1));
|
|
854
|
+
while (!stopDirs.has(dir) && dir.startsWith(path.resolve(root))) {
|
|
855
|
+
await removeDirIfEmpty(dir);
|
|
856
|
+
const parent = path.dirname(dir);
|
|
857
|
+
if (parent === dir) break;
|
|
858
|
+
dir = parent;
|
|
859
|
+
}
|
|
860
|
+
if (rel.startsWith('.codex/skills/')) await removeDirIfEmpty(path.join(root, '.codex', 'skills'));
|
|
707
861
|
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import { exists, readText, writeTextAtomic, PACKAGE_VERSION } from './fsx.mjs';
|
|
4
|
+
|
|
5
|
+
export const OPENCLAW_SKILL_NAME = 'sneakoscope-codex';
|
|
6
|
+
|
|
7
|
+
export function defaultOpenClawHome(env = process.env) {
|
|
8
|
+
return path.resolve(env.OPENCLAW_HOME || path.join(env.HOME || os.homedir(), '.openclaw'));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function defaultOpenClawSkillDir(env = process.env) {
|
|
12
|
+
return path.join(defaultOpenClawHome(env), 'skills', OPENCLAW_SKILL_NAME);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function buildOpenClawSkillFiles(options = {}) {
|
|
16
|
+
const sksCommand = options.sksCommand || 'sks';
|
|
17
|
+
const version = options.version || PACKAGE_VERSION;
|
|
18
|
+
const skillName = options.skillName || OPENCLAW_SKILL_NAME;
|
|
19
|
+
return {
|
|
20
|
+
'manifest.yaml': openClawManifest({ skillName, version }),
|
|
21
|
+
'SKILL.md': openClawSkillMarkdown({ sksCommand, skillName, version }),
|
|
22
|
+
'README.md': openClawSkillReadme({ sksCommand, skillName, version }),
|
|
23
|
+
'openclaw-agent-config.example.yaml': openClawAgentConfigExample({ skillName })
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function installOpenClawSkill(options = {}) {
|
|
28
|
+
const targetDir = path.resolve(options.targetDir || defaultOpenClawSkillDir(options.env || process.env));
|
|
29
|
+
const files = buildOpenClawSkillFiles(options);
|
|
30
|
+
const existingManifest = path.join(targetDir, 'manifest.yaml');
|
|
31
|
+
const existing = await exists(existingManifest);
|
|
32
|
+
if (existing && !options.force) {
|
|
33
|
+
const text = await readText(existingManifest, '');
|
|
34
|
+
if (!text.includes('generated_by: sneakoscope')) {
|
|
35
|
+
return {
|
|
36
|
+
ok: false,
|
|
37
|
+
status: 'blocked_existing_skill',
|
|
38
|
+
target_dir: targetDir,
|
|
39
|
+
reason: 'Existing OpenClaw skill is not marked as generated by Sneakoscope. Re-run with --force to overwrite.'
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (options.dryRun) {
|
|
44
|
+
return { ok: true, status: existing ? 'would_update' : 'would_create', target_dir: targetDir, files: Object.keys(files) };
|
|
45
|
+
}
|
|
46
|
+
for (const [name, content] of Object.entries(files)) {
|
|
47
|
+
await writeTextAtomic(path.join(targetDir, name), content);
|
|
48
|
+
}
|
|
49
|
+
return { ok: true, status: existing ? 'updated' : 'created', target_dir: targetDir, files: Object.keys(files) };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function openClawManifest({ skillName, version }) {
|
|
53
|
+
return `name: ${skillName}
|
|
54
|
+
version: ${version}
|
|
55
|
+
description: Expose Sneakoscope Codex (sks) workflows to OpenClaw agents.
|
|
56
|
+
author: mandarange
|
|
57
|
+
license: MIT
|
|
58
|
+
generated_by: sneakoscope
|
|
59
|
+
homepage: https://github.com/mandarange/Sneakoscope-Codex
|
|
60
|
+
entrypoint: SKILL.md
|
|
61
|
+
permissions:
|
|
62
|
+
shell: execute
|
|
63
|
+
filesystem: read_write_project
|
|
64
|
+
tags:
|
|
65
|
+
- codex
|
|
66
|
+
- coding-agent
|
|
67
|
+
- orchestration
|
|
68
|
+
- qa
|
|
69
|
+
- safety
|
|
70
|
+
`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function openClawSkillMarkdown({ sksCommand, skillName, version }) {
|
|
74
|
+
return `---
|
|
75
|
+
name: ${skillName}
|
|
76
|
+
version: ${version}
|
|
77
|
+
description: Use Sneakoscope Codex routes, checks, and release gates from an OpenClaw agent.
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
# Sneakoscope Codex For OpenClaw
|
|
81
|
+
|
|
82
|
+
Use this skill when the user asks an OpenClaw agent to work in a codebase that uses Sneakoscope Codex, SKS, Team, QA-LOOP, TriWiki, Context7, DB safety, Honest Mode, release gates, or \`$...\` prompt routes.
|
|
83
|
+
|
|
84
|
+
## Required Tool Access
|
|
85
|
+
|
|
86
|
+
The OpenClaw agent must have the built-in \`shell\` tool enabled. Prefer running commands in the target repository root.
|
|
87
|
+
|
|
88
|
+
## Core Commands
|
|
89
|
+
|
|
90
|
+
- \`${sksCommand} root\` checks whether SKS is using a project root or the global runtime root.
|
|
91
|
+
- \`${sksCommand} bootstrap\` installs or repairs SKS project files, Codex App skills, hooks, and runtime state.
|
|
92
|
+
- \`${sksCommand} commands\` lists terminal commands the agent can call.
|
|
93
|
+
- \`${sksCommand} dollar-commands\` lists prompt routes such as \`$Team\`, \`$DFix\`, \`$QA-LOOP\`, \`$PPT\`, \`$Goal\`, \`$DB\`, \`$Wiki\`, and \`$Help\`.
|
|
94
|
+
- \`${sksCommand} deps check\` checks Codex, Context7, tmux, and related local readiness.
|
|
95
|
+
- \`${sksCommand} proof-field scan --intent "<task>" --changed file1,file2\` checks whether a narrow change can stay on a lightweight proof path.
|
|
96
|
+
- \`${sksCommand} release:check\` is not a terminal command; use \`npm run release:check\` inside the Sneakoscope package repository.
|
|
97
|
+
|
|
98
|
+
## Agent Operating Rules
|
|
99
|
+
|
|
100
|
+
1. Before substantive work, run \`${sksCommand} root\` and inspect the repository's \`AGENTS.md\` if present.
|
|
101
|
+
2. For implementation, prefer the repository's requested SKS route. General code work normally routes to \`$Team\`; tiny design or copy edits can use \`$DFix\`; UI/browser dogfood uses \`$QA-LOOP\`; database or Supabase work uses \`$DB\`.
|
|
102
|
+
3. Do not invent fallback implementation code when the requested SKS path is blocked. Report the blocker with command output and source paths.
|
|
103
|
+
4. For database, migration, and Supabase tasks, default to read-only inspection unless the user explicitly authorizes a write/migration scope.
|
|
104
|
+
5. Before claiming completion, run the most relevant verification command and summarize what passed, what was not verified, and any remaining blocker.
|
|
105
|
+
|
|
106
|
+
## Example Prompts For An OpenClaw Agent
|
|
107
|
+
|
|
108
|
+
- "In this repo, run \`${sksCommand} root\`, inspect \`AGENTS.md\`, then use the SKS Team route to implement the bug fix and verify it."
|
|
109
|
+
- "Use SKS QA-LOOP against localhost:3000. Do not run destructive tests."
|
|
110
|
+
- "Prepare this project for Codex App use: run \`${sksCommand} bootstrap\`, \`${sksCommand} codex-app check\`, and \`${sksCommand} dollar-commands\`."
|
|
111
|
+
`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function openClawSkillReadme({ sksCommand, skillName, version }) {
|
|
115
|
+
return `# ${skillName}
|
|
116
|
+
|
|
117
|
+
Version: ${version}
|
|
118
|
+
|
|
119
|
+
This OpenClaw skill lets an OpenClaw agent discover and use Sneakoscope Codex through the local \`${sksCommand}\` command.
|
|
120
|
+
|
|
121
|
+
## Install
|
|
122
|
+
|
|
123
|
+
\`\`\`sh
|
|
124
|
+
${sksCommand} openclaw install
|
|
125
|
+
\`\`\`
|
|
126
|
+
|
|
127
|
+
Then attach the skill to an OpenClaw agent:
|
|
128
|
+
|
|
129
|
+
\`\`\`yaml
|
|
130
|
+
agents:
|
|
131
|
+
coding-agent:
|
|
132
|
+
tools:
|
|
133
|
+
- shell
|
|
134
|
+
skills:
|
|
135
|
+
- ${skillName}
|
|
136
|
+
\`\`\`
|
|
137
|
+
|
|
138
|
+
## Verify
|
|
139
|
+
|
|
140
|
+
\`\`\`sh
|
|
141
|
+
openclaw skill run ${skillName} --help
|
|
142
|
+
${sksCommand} root
|
|
143
|
+
${sksCommand} commands
|
|
144
|
+
\`\`\`
|
|
145
|
+
|
|
146
|
+
If OpenClaw runs skills in a sandbox, allow shell execution for this skill and run it from the target repository root so SKS can find \`.git\`, \`.sneakoscope\`, or \`.dcodex\`.
|
|
147
|
+
`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function openClawAgentConfigExample({ skillName }) {
|
|
151
|
+
return `agents:
|
|
152
|
+
coding-agent:
|
|
153
|
+
model: openai/gpt-4o
|
|
154
|
+
channels:
|
|
155
|
+
- terminal
|
|
156
|
+
tools:
|
|
157
|
+
- shell
|
|
158
|
+
skills:
|
|
159
|
+
- ${skillName}
|
|
160
|
+
`;
|
|
161
|
+
}
|
package/src/core/routes.mjs
CHANGED
|
@@ -7,7 +7,7 @@ export const FROM_CHAT_IMG_CHECKLIST_ARTIFACT = 'from-chat-img-checklist.md';
|
|
|
7
7
|
export const FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT = 'from-chat-img-temp-triwiki.json';
|
|
8
8
|
export const FROM_CHAT_IMG_QA_LOOP_ARTIFACT = 'from-chat-img-qa-loop.json';
|
|
9
9
|
export const FROM_CHAT_IMG_TEMP_TRIWIKI_SESSIONS = 5;
|
|
10
|
-
export const USAGE_TOPICS = 'install|setup|bootstrap|root|deps|tmux|auto-review|team|qa-loop|ppt|goal|research|db|codex-app|dfix|design|imagegen|dollar|context7|pipeline|reasoning|guard|conflicts|versioning|eval|harness|hproof|gx|wiki|code-structure|proof-field|skill-dream';
|
|
10
|
+
export const USAGE_TOPICS = 'install|setup|bootstrap|root|deps|tmux|auto-review|team|qa-loop|ppt|goal|research|db|codex-app|openclaw|dfix|design|imagegen|dollar|context7|pipeline|reasoning|guard|conflicts|versioning|eval|harness|hproof|gx|wiki|code-structure|proof-field|skill-dream';
|
|
11
11
|
export const CODEX_COMPUTER_USE_EVIDENCE_SOURCE = 'codex_computer_use';
|
|
12
12
|
export const CODEX_APP_IMAGE_GENERATION_DOC_URL = 'https://developers.openai.com/codex/app/features#image-generation';
|
|
13
13
|
export const CODEX_COMPUTER_USE_ONLY_POLICY = 'Pipeline UI/browser verification and visual inspection must use Codex Computer Use only. Do not use Playwright, Chrome MCP, Browser Use, Selenium, Puppeteer, or any other browser automation substitute; if Codex Computer Use is unavailable, mark the UI/browser evidence unverified instead of substituting another tool.';
|
|
@@ -456,6 +456,7 @@ export const COMMAND_CATALOG = [
|
|
|
456
456
|
{ name: 'root', usage: 'sks root [--json]', description: 'Show whether SKS is using a project root or the per-user global SKS runtime root.' },
|
|
457
457
|
{ name: 'deps', usage: 'sks deps check|install [tmux|codex|context7|all] [--yes]', description: 'Check or guided-install Node/npm PATH, Codex CLI/App, Context7, Browser Use, Computer Use, tmux, and Homebrew on macOS.' },
|
|
458
458
|
{ name: 'codex-app', usage: 'sks codex-app [check|open]', description: 'Check Codex App install and first-party MCP/plugin readiness, then show app setup files and examples.' },
|
|
459
|
+
{ name: 'openclaw', usage: 'sks openclaw install|path|print [--dir path] [--force] [--json]', description: 'Generate an OpenClaw skill package so OpenClaw agents can discover and use local SKS workflows.' },
|
|
459
460
|
{ name: 'tmux', usage: 'sks tmux open|check|status [--workspace name]', description: 'Explicitly open the SKS tmux runtime, or check/status without launching tmux.' },
|
|
460
461
|
{ name: 'mad', usage: 'sks --mad [--high]', description: 'Open a one-shot tmux Codex CLI workspace with the SKS MAD full-access auto-review profile.' },
|
|
461
462
|
{ name: 'auto-review', usage: 'sks auto-review status|enable|start [--high] | sks --Auto-review --high', description: 'Enable Codex automatic approval review and launch SKS tmux with the auto-review profile.' },
|