sneakoscope 3.1.3 → 3.1.5
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 +1 -1
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/crates/sks-core/src/main.rs +1 -1
- package/dist/.sks-build-stamp.json +4 -4
- package/dist/bin/sks.js +1 -1
- package/dist/cli/install-helpers.js +56 -4
- package/dist/commands/codex-app.js +45 -1
- package/dist/commands/codex-lb.js +12 -9
- package/dist/commands/doctor.js +44 -1
- package/dist/core/codex-app/codex-agent-role-sync.js +119 -0
- package/dist/core/codex-app/codex-agent-type-probe.js +202 -0
- package/dist/core/codex-app/codex-app-execution-profile.js +39 -0
- package/dist/core/codex-app/codex-app-fast-ui-repair.js +7 -4
- package/dist/core/codex-app/codex-app-harness-matrix.js +127 -0
- package/dist/core/codex-app/codex-app-types.js +21 -0
- package/dist/core/codex-app/codex-hook-approval-probe.js +188 -0
- package/dist/core/codex-app/codex-hook-lifecycle.js +61 -0
- package/dist/core/codex-app/codex-init-deep.js +180 -0
- package/dist/core/codex-app/codex-skill-sync.js +164 -0
- package/dist/core/codex-app/lazycodex-analysis.js +72 -0
- package/dist/core/codex-app/lazycodex-interop-policy.js +60 -0
- package/dist/core/codex-app/lazycodex-live-analyzer.js +98 -0
- package/dist/core/commands/loop-command.js +11 -0
- package/dist/core/commands/mad-sks-command.js +113 -17
- package/dist/core/commands/qa-loop-command.js +3 -2
- package/dist/core/commands/research-command.js +2 -2
- package/dist/core/doctor/doctor-readiness-matrix.js +7 -0
- package/dist/core/doctor/doctor-zellij-repair.js +40 -0
- package/dist/core/feature-fixtures.js +1 -0
- package/dist/core/feature-registry.js +4 -1
- package/dist/core/fsx.js +1 -1
- package/dist/core/hooks-runtime.js +13 -0
- package/dist/core/init.js +4 -1
- package/dist/core/loops/loop-continuation-enforcer.js +40 -0
- package/dist/core/loops/loop-planner.js +29 -3
- package/dist/core/loops/loop-worker-runtime.js +27 -7
- package/dist/core/naruto/naruto-loop-worker-router.js +11 -2
- package/dist/core/qa-loop.js +39 -4
- package/dist/core/research/research-cycle-runner.js +1 -0
- package/dist/core/research/research-stage-runner.js +9 -2
- package/dist/core/research.js +35 -1
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/homebrew-policy.js +44 -0
- package/dist/core/zellij/zellij-capability.js +32 -3
- package/dist/core/zellij/zellij-self-heal-types.js +45 -0
- package/dist/core/zellij/zellij-self-heal.js +414 -0
- package/dist/core/zellij/zellij-update.js +39 -6
- package/dist/scripts/sks-3-1-4-directive-check-lib.js +241 -0
- package/dist/scripts/sks-3-1-5-directive-check-lib.js +347 -0
- package/package.json +52 -2
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { ensureDir, mergeManagedBlock, nowIso, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
|
|
4
|
+
export async function runCodexInitDeep(input = {}) {
|
|
5
|
+
const root = path.resolve(input.root || process.cwd());
|
|
6
|
+
const dirs = await scoreDirectories(root);
|
|
7
|
+
const selected = dirs.filter((row) => row.score >= 4).slice(0, 12);
|
|
8
|
+
const contextDir = path.join(root, '.sneakoscope', 'context');
|
|
9
|
+
const generatedPath = path.join(contextDir, 'AGENTS.generated.md');
|
|
10
|
+
const markdown = renderGeneratedAgents(selected);
|
|
11
|
+
const directoryLocalAgents = { created: [], updated: [], skipped: [], backup_paths: [], blockers: [] };
|
|
12
|
+
if (input.apply === true) {
|
|
13
|
+
await ensureDir(contextDir);
|
|
14
|
+
await writeTextAtomic(generatedPath, markdown);
|
|
15
|
+
if (input.directoryLocal === true) {
|
|
16
|
+
for (const row of selected) {
|
|
17
|
+
const agentsPath = path.join(root, row.dir, 'AGENTS.md');
|
|
18
|
+
try {
|
|
19
|
+
await ensureDir(path.dirname(agentsPath));
|
|
20
|
+
const existing = await fs.readFile(agentsPath, 'utf8').catch(() => '');
|
|
21
|
+
if (existing.trim()) {
|
|
22
|
+
const backup = `${agentsPath}.sks-backup-${Date.now()}`;
|
|
23
|
+
await fs.copyFile(agentsPath, backup);
|
|
24
|
+
directoryLocalAgents.backup_paths.push(path.relative(root, backup));
|
|
25
|
+
}
|
|
26
|
+
const status = await mergeManagedBlock(agentsPath, 'SKS INIT-DEEP MANAGED SECTION', renderDirectoryAgentsBlock(row));
|
|
27
|
+
if (status === 'created')
|
|
28
|
+
directoryLocalAgents.created.push(path.relative(root, agentsPath));
|
|
29
|
+
else
|
|
30
|
+
directoryLocalAgents.updated.push(path.relative(root, agentsPath));
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
directoryLocalAgents.skipped.push(path.relative(root, agentsPath));
|
|
34
|
+
directoryLocalAgents.blockers.push(`${path.relative(root, agentsPath)}:${messageOf(err)}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const report = {
|
|
40
|
+
schema: 'sks.codex-init-deep.v1',
|
|
41
|
+
generated_at: nowIso(),
|
|
42
|
+
ok: directoryLocalAgents.blockers.length === 0,
|
|
43
|
+
apply: input.apply === true,
|
|
44
|
+
root,
|
|
45
|
+
generated_path: path.relative(root, generatedPath),
|
|
46
|
+
root_agents_preserved: true,
|
|
47
|
+
directory_guidance: selected,
|
|
48
|
+
directory_local_agents: directoryLocalAgents,
|
|
49
|
+
blockers: directoryLocalAgents.blockers
|
|
50
|
+
};
|
|
51
|
+
await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'codex-init-deep.json'), report).catch(() => undefined);
|
|
52
|
+
return report;
|
|
53
|
+
}
|
|
54
|
+
export async function readInitDeepMemory(root) {
|
|
55
|
+
const file = path.join(root, '.sneakoscope', 'context', 'AGENTS.generated.md');
|
|
56
|
+
const text = await fs.readFile(file, 'utf8').catch(() => '');
|
|
57
|
+
return text.trim() ? { path: file, text } : null;
|
|
58
|
+
}
|
|
59
|
+
export async function readInitDeepMemoryHints(root, scopePaths = []) {
|
|
60
|
+
const resolvedRoot = path.resolve(root);
|
|
61
|
+
const hints = [];
|
|
62
|
+
const generated = await readInitDeepMemory(resolvedRoot).catch(() => null);
|
|
63
|
+
if (generated) {
|
|
64
|
+
hints.push({
|
|
65
|
+
path: path.relative(resolvedRoot, generated.path),
|
|
66
|
+
scope: '.',
|
|
67
|
+
summary: generated.text.split(/\r?\n/).filter((line) => /^##\s+/.test(line)).slice(0, 8).join(' | ')
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
const candidateDirs = new Set();
|
|
71
|
+
for (const scopePath of scopePaths) {
|
|
72
|
+
const absolute = path.resolve(resolvedRoot, scopePath);
|
|
73
|
+
if (!absolute.startsWith(resolvedRoot))
|
|
74
|
+
continue;
|
|
75
|
+
const stat = await fs.stat(absolute).catch(() => null);
|
|
76
|
+
let dir = stat?.isFile() ? path.dirname(absolute) : absolute;
|
|
77
|
+
while (dir.startsWith(resolvedRoot)) {
|
|
78
|
+
candidateDirs.add(dir);
|
|
79
|
+
if (dir === resolvedRoot)
|
|
80
|
+
break;
|
|
81
|
+
dir = path.dirname(dir);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
for (const dir of [...candidateDirs].sort((a, b) => b.length - a.length)) {
|
|
85
|
+
const file = path.join(dir, 'AGENTS.md');
|
|
86
|
+
const text = await fs.readFile(file, 'utf8').catch(() => '');
|
|
87
|
+
if (!text.trim())
|
|
88
|
+
continue;
|
|
89
|
+
const managed = extractManagedSection(text, 'SKS INIT-DEEP MANAGED SECTION');
|
|
90
|
+
const userSummary = text.replace(/<!-- BEGIN [\s\S]*?<!-- END [^>]+-->/g, '').split(/\r?\n/).map((line) => line.trim()).filter(Boolean).slice(0, 4).join(' | ');
|
|
91
|
+
const summary = [managed ? managed.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).slice(0, 4).join(' | ') : '', userSummary ? `user:${userSummary}` : ''].filter(Boolean).join(' || ');
|
|
92
|
+
if (summary) {
|
|
93
|
+
hints.push({
|
|
94
|
+
path: path.relative(resolvedRoot, file),
|
|
95
|
+
scope: path.relative(resolvedRoot, dir) || '.',
|
|
96
|
+
summary
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const unique = new Map();
|
|
101
|
+
for (const hint of hints)
|
|
102
|
+
unique.set(`${hint.path}:${hint.scope}`, hint);
|
|
103
|
+
return [...unique.values()].slice(0, 12);
|
|
104
|
+
}
|
|
105
|
+
async function scoreDirectories(root) {
|
|
106
|
+
const counts = new Map();
|
|
107
|
+
await walk(path.join(root, 'src'), root, counts);
|
|
108
|
+
await walk(path.join(root, 'docs'), root, counts);
|
|
109
|
+
const highRisk = [/src\/core\/zellij/, /src\/core\/loops/, /src\/core\/codex-app/, /src\/commands/];
|
|
110
|
+
return [...counts.entries()].map(([dir, value]) => {
|
|
111
|
+
const risky = highRisk.some((re) => re.test(dir));
|
|
112
|
+
const score = Math.min(10, Math.ceil(value.file_count / 6) + value.langs.size + (risky ? 3 : 0));
|
|
113
|
+
return {
|
|
114
|
+
dir,
|
|
115
|
+
file_count: value.file_count,
|
|
116
|
+
languages: [...value.langs].sort(),
|
|
117
|
+
score,
|
|
118
|
+
guidance: risky ? 'High-risk SKS runtime area; hydrate TriWiki/current source before edits.' : 'Use local source conventions and keep changes owner-scoped.'
|
|
119
|
+
};
|
|
120
|
+
}).sort((a, b) => b.score - a.score || a.dir.localeCompare(b.dir));
|
|
121
|
+
}
|
|
122
|
+
async function walk(dir, root, counts, depth = 0) {
|
|
123
|
+
if (depth > 3)
|
|
124
|
+
return;
|
|
125
|
+
const rows = await fs.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
126
|
+
for (const row of rows) {
|
|
127
|
+
const full = path.join(dir, row.name);
|
|
128
|
+
if (row.isDirectory()) {
|
|
129
|
+
if (!['node_modules', 'dist', '.git'].includes(row.name))
|
|
130
|
+
await walk(full, root, counts, depth + 1);
|
|
131
|
+
}
|
|
132
|
+
else if (row.isFile()) {
|
|
133
|
+
const relDir = path.relative(root, path.dirname(full)).split(path.sep).join('/');
|
|
134
|
+
const entry = counts.get(relDir) || { file_count: 0, langs: new Set() };
|
|
135
|
+
entry.file_count += 1;
|
|
136
|
+
entry.langs.add(path.extname(row.name).replace(/^\./, '') || 'text');
|
|
137
|
+
counts.set(relDir, entry);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function renderGeneratedAgents(rows) {
|
|
142
|
+
return [
|
|
143
|
+
'# SKS Init-Deep Generated Context',
|
|
144
|
+
'',
|
|
145
|
+
'This file is generated under `.sneakoscope/context` so user-authored `AGENTS.md` files are preserved.',
|
|
146
|
+
'',
|
|
147
|
+
...rows.flatMap((row) => [
|
|
148
|
+
`## ${row.dir}`,
|
|
149
|
+
'',
|
|
150
|
+
`- Files: ${row.file_count}`,
|
|
151
|
+
`- Languages: ${row.languages.join(', ') || 'unknown'}`,
|
|
152
|
+
`- Guidance: ${row.guidance}`,
|
|
153
|
+
''
|
|
154
|
+
])
|
|
155
|
+
].join('\n');
|
|
156
|
+
}
|
|
157
|
+
function renderDirectoryAgentsBlock(row) {
|
|
158
|
+
return [
|
|
159
|
+
`# SKS Init-Deep Local Guidance: ${row.dir}`,
|
|
160
|
+
'',
|
|
161
|
+
`- Files observed: ${row.file_count}`,
|
|
162
|
+
`- Languages: ${row.languages.join(', ') || 'unknown'}`,
|
|
163
|
+
`- Guidance: ${row.guidance}`,
|
|
164
|
+
'- Preserve user-authored content outside this managed block.',
|
|
165
|
+
'- Hydrate TriWiki/current source before risky edits in this directory.'
|
|
166
|
+
].join('\n');
|
|
167
|
+
}
|
|
168
|
+
function extractManagedSection(text, markerName) {
|
|
169
|
+
const begin = `<!-- BEGIN ${markerName} -->`;
|
|
170
|
+
const end = `<!-- END ${markerName} -->`;
|
|
171
|
+
const beginIdx = text.indexOf(begin);
|
|
172
|
+
const endIdx = text.indexOf(end);
|
|
173
|
+
if (beginIdx < 0 || endIdx < beginIdx)
|
|
174
|
+
return '';
|
|
175
|
+
return text.slice(beginIdx + begin.length, endIdx).trim();
|
|
176
|
+
}
|
|
177
|
+
function messageOf(err) {
|
|
178
|
+
return err instanceof Error ? err.message : String(err);
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=codex-init-deep.js.map
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import crypto from 'node:crypto';
|
|
5
|
+
import { ensureDir, nowIso, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
|
|
6
|
+
const SKS_SKILLS = [
|
|
7
|
+
'$Loop',
|
|
8
|
+
'$Naruto',
|
|
9
|
+
'$QA-LOOP',
|
|
10
|
+
'$Research',
|
|
11
|
+
'$DFix',
|
|
12
|
+
'$Image-UX-Review',
|
|
13
|
+
'$Computer-Use',
|
|
14
|
+
'$Init-Deep'
|
|
15
|
+
];
|
|
16
|
+
const LAZYCODEX_RESERVED = new Set(['ulw-loop', 'ulw-plan', 'start-work']);
|
|
17
|
+
export async function syncCodexSksSkills(input) {
|
|
18
|
+
const root = path.resolve(input.root);
|
|
19
|
+
const skillsRoot = input.skillsRoot || path.join(process.env.CODEX_HOME || path.join(os.homedir(), '.codex'), 'skills');
|
|
20
|
+
const existing = await listSkillNames(skillsRoot);
|
|
21
|
+
const collisions = existing.filter((name) => LAZYCODEX_RESERVED.has(name));
|
|
22
|
+
const desired = SKS_SKILLS.map((skill) => skillName(skill));
|
|
23
|
+
const created = [];
|
|
24
|
+
const skipped = [];
|
|
25
|
+
if (input.apply === true) {
|
|
26
|
+
await ensureDir(skillsRoot);
|
|
27
|
+
for (const name of desired) {
|
|
28
|
+
if (LAZYCODEX_RESERVED.has(name)) {
|
|
29
|
+
skipped.push(name);
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const dir = path.join(skillsRoot, name);
|
|
33
|
+
const file = path.join(dir, 'SKILL.md');
|
|
34
|
+
const content = skillContent(name);
|
|
35
|
+
const current = await fs.readFile(file, 'utf8').catch(() => '');
|
|
36
|
+
if (current && !current.includes('BEGIN SKS MANAGED SKILL')) {
|
|
37
|
+
skipped.push(name);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
await ensureDir(dir);
|
|
41
|
+
await writeTextAtomic(file, content);
|
|
42
|
+
created.push(file);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const report = {
|
|
46
|
+
schema: 'sks.codex-skill-sync.v1',
|
|
47
|
+
generated_at: nowIso(),
|
|
48
|
+
ok: true,
|
|
49
|
+
apply: input.apply === true,
|
|
50
|
+
skills_root: skillsRoot,
|
|
51
|
+
desired_skills: desired,
|
|
52
|
+
existing_skills: existing,
|
|
53
|
+
created,
|
|
54
|
+
skipped,
|
|
55
|
+
lazycodex_reserved_present: collisions,
|
|
56
|
+
interop: {
|
|
57
|
+
mode: 'coexist',
|
|
58
|
+
clobbered_lazycodex: false,
|
|
59
|
+
clobbered_user_skills: false
|
|
60
|
+
},
|
|
61
|
+
blockers: []
|
|
62
|
+
};
|
|
63
|
+
await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'codex-skill-sync.json'), report).catch(() => undefined);
|
|
64
|
+
return report;
|
|
65
|
+
}
|
|
66
|
+
async function listSkillNames(root) {
|
|
67
|
+
const rows = await fs.readdir(root, { withFileTypes: true }).catch(() => []);
|
|
68
|
+
return rows.filter((row) => row.isDirectory()).map((row) => row.name).sort();
|
|
69
|
+
}
|
|
70
|
+
function skillName(value) {
|
|
71
|
+
return value.replace(/^\$/, '').toLowerCase();
|
|
72
|
+
}
|
|
73
|
+
function skillContent(name) {
|
|
74
|
+
const profile = skillProfile(name);
|
|
75
|
+
const body = [
|
|
76
|
+
'---',
|
|
77
|
+
`name: ${name}`,
|
|
78
|
+
`description: SKS managed Codex App route bridge for ${profile.command}.`,
|
|
79
|
+
'---',
|
|
80
|
+
'',
|
|
81
|
+
'<!-- BEGIN SKS MANAGED SKILL -->',
|
|
82
|
+
`Command: ${profile.command}`,
|
|
83
|
+
`Purpose: ${profile.purpose}`,
|
|
84
|
+
`Use when: ${profile.when}`,
|
|
85
|
+
`Evidence: ${profile.evidence}`,
|
|
86
|
+
'Safety: keep route state bounded, preserve user and LazyCodex/OmO skills, and stop on hard blockers instead of fabricating fallback behavior.',
|
|
87
|
+
`Fallback: ${profile.fallback}`,
|
|
88
|
+
`checksum: ${hash(name)}`,
|
|
89
|
+
'<!-- END SKS MANAGED SKILL -->',
|
|
90
|
+
''
|
|
91
|
+
].join('\n');
|
|
92
|
+
return body;
|
|
93
|
+
}
|
|
94
|
+
function skillProfile(name) {
|
|
95
|
+
const table = {
|
|
96
|
+
loop: {
|
|
97
|
+
command: '$Loop',
|
|
98
|
+
purpose: 'compile persisted route work into bounded loop plans with continuation evidence.',
|
|
99
|
+
when: 'a mission needs stage-by-stage execution, memory hints, or resume-safe artifacts.',
|
|
100
|
+
evidence: '.sneakoscope/loops/** plus codex-app-execution-profile.json',
|
|
101
|
+
fallback: 'use message-role routing when native agent_type is not verified.'
|
|
102
|
+
},
|
|
103
|
+
naruto: {
|
|
104
|
+
command: '$Naruto',
|
|
105
|
+
purpose: 'fan out bounded native worker lanes for high-scale review or implementation.',
|
|
106
|
+
when: 'parallel lanes are explicitly selected by the route and parent integration remains owner.',
|
|
107
|
+
evidence: 'naruto work graph, worker ledgers, and execution profile payloads.',
|
|
108
|
+
fallback: 'degrade to message-role workers without dropping proof artifacts.'
|
|
109
|
+
},
|
|
110
|
+
'qa-loop': {
|
|
111
|
+
command: '$QA-LOOP',
|
|
112
|
+
purpose: 'dogfood UI/API behavior with gate artifacts and current execution profile.',
|
|
113
|
+
when: 'route completion needs human-proxy verification or app handoff checks.',
|
|
114
|
+
evidence: 'qa-loop gate/result ledgers and codex-app-execution-profile.json.',
|
|
115
|
+
fallback: 'record the unavailable surface as blocked rather than inventing visual proof.'
|
|
116
|
+
},
|
|
117
|
+
research: {
|
|
118
|
+
command: '$Research',
|
|
119
|
+
purpose: 'run evidence-bound research cycles with source routing and synthesis ledgers.',
|
|
120
|
+
when: 'the request depends on discovery, evaluation, or external-source claims.',
|
|
121
|
+
evidence: 'research plan, source ledger, cycle record, and execution profile routing.',
|
|
122
|
+
fallback: 'mark unavailable source tools explicitly and avoid unsupported live-accuracy claims.'
|
|
123
|
+
},
|
|
124
|
+
dfix: {
|
|
125
|
+
command: '$DFix',
|
|
126
|
+
purpose: 'perform tiny direct fixes without the full Team route.',
|
|
127
|
+
when: 'copy/config/docs/labels/spacing/translation/mechanical edits are truly narrow.',
|
|
128
|
+
evidence: 'focused diff plus DFix Honest check.',
|
|
129
|
+
fallback: 'route broad implementation through Team/Loop instead.'
|
|
130
|
+
},
|
|
131
|
+
'image-ux-review': {
|
|
132
|
+
command: '$Image-UX-Review',
|
|
133
|
+
purpose: 'produce generated annotated UI review images and extract issue ledgers.',
|
|
134
|
+
when: 'visual UX critique is requested from screenshots or app captures.',
|
|
135
|
+
evidence: 'source inventory, generated annotation images, extracted issue ledger.',
|
|
136
|
+
fallback: 'block if raster annotation cannot be produced.'
|
|
137
|
+
},
|
|
138
|
+
'computer-use': {
|
|
139
|
+
command: '$Computer-Use',
|
|
140
|
+
purpose: 'operate native macOS desktop apps through the fast Computer Use lane.',
|
|
141
|
+
when: 'the task depends on non-web desktop UI or OS settings.',
|
|
142
|
+
evidence: 'desktop interaction notes/screenshots where available.',
|
|
143
|
+
fallback: 'use Browser/Chrome only for web targets.'
|
|
144
|
+
},
|
|
145
|
+
'init-deep': {
|
|
146
|
+
command: '$Init-Deep',
|
|
147
|
+
purpose: 'refresh project-local memory, directory AGENTS sections, and loop memory hints.',
|
|
148
|
+
when: 'a route needs deeper local context or directory-specific instruction recall.',
|
|
149
|
+
evidence: '.sneakoscope/context/AGENTS.generated.md and managed directory AGENTS blocks.',
|
|
150
|
+
fallback: 'preserve user content and skip directories that cannot be safely updated.'
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
return table[name] || {
|
|
154
|
+
command: `$${name}`,
|
|
155
|
+
purpose: 'bridge an SKS managed Codex App route.',
|
|
156
|
+
when: 'the matching SKS route is explicitly requested.',
|
|
157
|
+
evidence: '.sneakoscope route artifacts.',
|
|
158
|
+
fallback: 'record blockers with evidence.'
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function hash(value) {
|
|
162
|
+
return crypto.createHash('sha256').update(`sks-skill:${value}`).digest('hex').slice(0, 12);
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=codex-skill-sync.js.map
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { nowIso, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
|
|
3
|
+
import { analyzeLazyCodexLiveSource } from './lazycodex-live-analyzer.js';
|
|
4
|
+
export function buildLazyCodexPatternAnalysis(live = null) {
|
|
5
|
+
const liveByPattern = new Map(live?.patterns.map((row) => [row.id, row]) || []);
|
|
6
|
+
const liveEvidenceByPattern = new Map();
|
|
7
|
+
for (const row of live?.evidence || []) {
|
|
8
|
+
const list = liveEvidenceByPattern.get(row.pattern_id) || [];
|
|
9
|
+
list.push(`${row.file}:${row.lines?.join('-') || 'unknown'}:${row.snippet_hash}`);
|
|
10
|
+
liveEvidenceByPattern.set(row.pattern_id, list);
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
schema: 'sks.lazycodex-pattern-analysis.v1',
|
|
14
|
+
source_repo: 'code-yeongyu/lazycodex',
|
|
15
|
+
analyzed_at: nowIso(),
|
|
16
|
+
patterns: [
|
|
17
|
+
pattern('npx-no-global-install', 'npx no-global install alias', ['Directive: npx lazycodex-ai install aliases npx --yes --package oh-my-openagent omo install --platform=codex.'], 'adapt', 'Keep optional tooling no-global by default and record repair transactions.', ['src/cli/install-helpers.ts', 'src/core/zellij/zellij-self-heal.ts']),
|
|
18
|
+
pattern('codex-marketplace-plugin', 'Codex marketplace plugin add/upgrade', ['Directive: codex plugin marketplace add and codex plugin add omo@sisyphuslabs.'], 'adapt', 'Track marketplace/plugin inventory without assuming hooks are approved.', ['src/core/codex-app/codex-app-harness-matrix.ts']),
|
|
19
|
+
pattern('startup-review-hooks', 'Startup review hook approval', ['Directive: hooks require Codex startup review approval and re-approval after modifications.'], 'adopt', 'Separate installed hook files from approval state; unknown remains unknown.', ['src/core/codex-app/codex-hook-lifecycle.ts']),
|
|
20
|
+
pattern('background-bootstrap', 'Background bootstrap and restart notice', ['Directive: first approved session performs background bootstrap and upgrade may require restart.'], 'adapt', 'Report bootstrap proof as warning/blocker instead of silently assuming completion.', ['src/core/codex-app/codex-app-harness-matrix.ts']),
|
|
21
|
+
pattern('doctor-health-report', 'Doctor health report for plugin cache/hooks/MCP/agents/config', ['Directive: LazyCodex doctor reports plugin cache, hooks, MCP servers, agents, config state.'], 'adopt', 'Add Codex App Harness section to SKS doctor.', ['src/commands/doctor.ts']),
|
|
22
|
+
pattern('dollar-skill-picker', '$ skill picker and $command invocation', ['Directive: Codex composer $ browses installed skills.'], 'adapt', 'Keep SKS route skills synced without clobbering user or LazyCodex skills.', ['src/core/codex-app/codex-skill-sync.ts']),
|
|
23
|
+
pattern('init-deep-agents', '$init-deep hierarchical AGENTS.md', ['Directive: init-deep creates hierarchical AGENTS.md context.'], 'adapt', 'Generate SKS memory under .sneakoscope/context by default and preserve user AGENTS.md.', ['src/core/codex-app/codex-init-deep.ts']),
|
|
24
|
+
pattern('plan-start-loop', '$ulw-plan, $start-work, $ulw-loop command pillars', ['Directive: separate planning, durable work, and evidence loop.'], 'adapt', 'Map onto sks loop plan/run/proof without replacing existing Loop Mesh.', ['src/core/commands/loop-command.ts']),
|
|
25
|
+
pattern('specialist-skills', 'Specialist skills', ['Directive: specialist skills include review-work, LSP, AST-grep, programming, frontend UI/UX.'], 'watch', 'Keep checker profile selection explicit and evidence-backed.', ['src/core/loops/loop-gate-selector.ts']),
|
|
26
|
+
pattern('native-agent-type', 'Native spawn_agent agent_type with message fallback', ['Directive: LazyCodex probes agent_type and falls back to role in message.'], 'adopt', 'Expose native agent_type capability in execution profile.', ['src/core/codex-app/codex-agent-role-sync.ts', 'src/core/codex-app/codex-app-execution-profile.ts']),
|
|
27
|
+
pattern('multi-model-routing', 'Multi-model routing', ['Directive: LazyCodex/OmO route multiple models.'], 'watch', 'SKS keeps provider/profile policy separate from harness matrix.', ['src/core/provider/provider-context.ts']),
|
|
28
|
+
pattern('hook-continuation', 'Hook lifecycle and continuation enforcer', ['Directive: UserPromptSubmit, PreToolUse, PostToolUse, Stop, Notification map to pipeline actions.'], 'adopt', 'Map lifecycle and add Loop continuation proof adapter.', ['src/core/codex-app/codex-hook-lifecycle.ts', 'src/core/loops/loop-continuation-enforcer.ts']),
|
|
29
|
+
pattern('skill-mcp-slashcommand', 'Skill MCP and slashcommand tool', ['Directive: OmO exposes skill MCP and slashcommand tools.'], 'adapt', 'Report MCP candidates and SKS route skill availability without assuming external plugin behavior.', ['src/core/codex-app/codex-app-harness-matrix.ts']),
|
|
30
|
+
pattern('lsp-ast-grep', 'LSP/AST-grep optional tooling', ['Directive: LSP/AST-grep are optional-but-first-class loop gates/tools.'], 'watch', 'Use as future specialist gates; do not add unrequested fallback tooling now.', ['src/core/loops/loop-gate-selector.ts'])
|
|
31
|
+
].map((row) => {
|
|
32
|
+
const livePattern = liveByPattern.get(row.id);
|
|
33
|
+
const liveEvidence = liveEvidenceByPattern.get(row.id) || [];
|
|
34
|
+
return {
|
|
35
|
+
...row,
|
|
36
|
+
evidence: [...row.evidence, ...liveEvidence],
|
|
37
|
+
confidence: liveEvidence.length ? 'high' : livePattern ? 'medium' : 'low',
|
|
38
|
+
live_evidence: liveEvidence
|
|
39
|
+
};
|
|
40
|
+
}),
|
|
41
|
+
blockers: live?.blockers || [],
|
|
42
|
+
warnings: live?.warnings || (live ? [] : ['live_analysis_not_available_static_only']),
|
|
43
|
+
live_analysis_path: live ? '.sneakoscope/reports/lazycodex-live-analysis.json' : null
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export async function writeLazyCodexPatternAnalysis(root) {
|
|
47
|
+
const live = await analyzeLazyCodexLiveSource({ root, writeReport: true }).catch(() => null);
|
|
48
|
+
const report = buildLazyCodexPatternAnalysis(live);
|
|
49
|
+
await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'lazycodex-analysis.json'), report);
|
|
50
|
+
await writeTextAtomic(path.join(root, 'docs', 'lazycodex-analysis.md'), renderLazyCodexAnalysisMarkdown(report)).catch(() => undefined);
|
|
51
|
+
return report;
|
|
52
|
+
}
|
|
53
|
+
export function renderLazyCodexAnalysisMarkdown(report) {
|
|
54
|
+
const rows = report.patterns.map((p) => `| ${p.id} | ${p.sks_adoption} | ${p.confidence} | ${p.live_evidence.length} | ${p.rationale.replace(/\|/g, '\\|')} |`).join('\n');
|
|
55
|
+
return [
|
|
56
|
+
'# LazyCodex / OmO Pattern Analysis',
|
|
57
|
+
'',
|
|
58
|
+
`Source repo: \`${report.source_repo}\``,
|
|
59
|
+
`Analyzed at: \`${report.analyzed_at}\``,
|
|
60
|
+
`Live analysis: \`${report.live_analysis_path || 'not available'}\``,
|
|
61
|
+
'',
|
|
62
|
+
'| Pattern | Adoption | Confidence | Live Evidence | Rationale |',
|
|
63
|
+
'|---|---|---|---:|---|',
|
|
64
|
+
rows,
|
|
65
|
+
'',
|
|
66
|
+
'This artifact combines static directive mapping with hashed current-source evidence when available. Long source excerpts are intentionally omitted.'
|
|
67
|
+
].join('\n');
|
|
68
|
+
}
|
|
69
|
+
function pattern(id, title, evidence, sks_adoption, rationale, target_modules) {
|
|
70
|
+
return { id, title, evidence, sks_adoption, rationale, target_modules };
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=lazycodex-analysis.js.map
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import { nowIso, writeJsonAtomic } from '../fsx.js';
|
|
5
|
+
import { buildCodexPluginInventory } from '../codex-plugins/codex-plugin-json.js';
|
|
6
|
+
export async function buildLazyCodexInteropPolicy(input) {
|
|
7
|
+
const root = path.resolve(input.root);
|
|
8
|
+
const inventory = normalizeInventory(input.inventory || await buildCodexPluginInventory().catch((err) => ({ plugins: [], blockers: [messageOf(err)] })));
|
|
9
|
+
const codexHome = input.codexHome || process.env.CODEX_HOME || path.join(os.homedir(), '.codex');
|
|
10
|
+
const skillNames = await discoverSkillNames([path.join(root, '.agents', 'skills'), path.join(codexHome, 'skills')]);
|
|
11
|
+
const pluginIds = (inventory.plugins || []).map((plugin) => `${plugin.id || ''} ${plugin.name || ''}`.toLowerCase());
|
|
12
|
+
const lazycodexInstalled = pluginIds.some((id) => id.includes('omo') || id.includes('lazycodex'))
|
|
13
|
+
|| ['ulw-loop', 'ulw-plan', 'start-work'].some((name) => skillNames.includes(name));
|
|
14
|
+
const collisions = ['ulw-loop', 'ulw-plan', 'start-work'].filter((name) => skillNames.includes(name));
|
|
15
|
+
const report = {
|
|
16
|
+
schema: 'sks.lazycodex-interop-policy.v1',
|
|
17
|
+
generated_at: nowIso(),
|
|
18
|
+
ok: true,
|
|
19
|
+
mode: input.mode || 'coexist',
|
|
20
|
+
lazycodex_detected: lazycodexInstalled,
|
|
21
|
+
detection: {
|
|
22
|
+
plugin_inventory_ids: pluginIds,
|
|
23
|
+
skill_names: skillNames,
|
|
24
|
+
collisions
|
|
25
|
+
},
|
|
26
|
+
policy: {
|
|
27
|
+
clobber_lazycodex_skills: false,
|
|
28
|
+
clobber_user_skills: false,
|
|
29
|
+
default_mode: 'coexist',
|
|
30
|
+
explicit_handoff_required: true
|
|
31
|
+
},
|
|
32
|
+
actions: collisions.map((name) => `preserve_existing_skill:${name}`),
|
|
33
|
+
blockers: []
|
|
34
|
+
};
|
|
35
|
+
await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'lazycodex-interop-policy.json'), report).catch(() => undefined);
|
|
36
|
+
return report;
|
|
37
|
+
}
|
|
38
|
+
function normalizeInventory(value) {
|
|
39
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
40
|
+
return { plugins: [], blockers: [] };
|
|
41
|
+
const record = value;
|
|
42
|
+
return {
|
|
43
|
+
plugins: Array.isArray(record.plugins) ? record.plugins.map((plugin) => plugin && typeof plugin === 'object' ? plugin : {}) : [],
|
|
44
|
+
blockers: Array.isArray(record.blockers) ? record.blockers.map(String) : []
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function messageOf(err) {
|
|
48
|
+
return err instanceof Error ? err.message : String(err);
|
|
49
|
+
}
|
|
50
|
+
async function discoverSkillNames(roots) {
|
|
51
|
+
const names = new Set();
|
|
52
|
+
for (const root of roots) {
|
|
53
|
+
const entries = await fs.readdir(root, { withFileTypes: true }).catch(() => []);
|
|
54
|
+
for (const entry of entries)
|
|
55
|
+
if (entry.isDirectory())
|
|
56
|
+
names.add(entry.name);
|
|
57
|
+
}
|
|
58
|
+
return [...names].sort();
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=lazycodex-interop-policy.js.map
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { nowIso, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
|
|
5
|
+
const PATTERNS = [
|
|
6
|
+
{ id: 'npx-no-global-install', claim: 'npx install command evidence', re: /\bnpx\b.+(?:lazycodex|omo|oh-my-openagent)/i },
|
|
7
|
+
{ id: 'codex-marketplace-plugin', claim: 'Codex marketplace install command evidence', re: /\bcodex\b.+(?:plugin|marketplace).+(?:add|install)/i },
|
|
8
|
+
{ id: 'startup-review-hooks', claim: 'Hook approval statement evidence', re: /\bhook\b.+\b(approval|approve|review|trusted|trust)\b/i },
|
|
9
|
+
{ id: 'doctor-health-report', claim: 'Doctor health report evidence', re: /\bdoctor\b.+\b(health|report|check)\b/i },
|
|
10
|
+
{ id: 'plan-start-loop', claim: '$ulw-loop/$ulw-plan/$start-work command evidence', re: /\$(?:ulw-loop|ulw-plan|start-work)\b/ },
|
|
11
|
+
{ id: 'init-deep-agents', claim: '$init-deep evidence', re: /\$init-deep\b|init-deep/i },
|
|
12
|
+
{ id: 'native-agent-type', claim: 'agent_type fallback evidence', re: /\bagent_type\b|message[- ]role|fallback/i }
|
|
13
|
+
];
|
|
14
|
+
export async function analyzeLazyCodexLiveSource(input) {
|
|
15
|
+
const root = path.resolve(input.root);
|
|
16
|
+
const sourceDir = input.sourceDir ? path.resolve(input.sourceDir) : path.join(root, '.sneakoscope', 'cache', 'lazycodex');
|
|
17
|
+
const files = ['README.md', 'package.json', 'bin/lazycodex-ai.js', '.gitmodules'];
|
|
18
|
+
const evidence = [];
|
|
19
|
+
const blockers = [];
|
|
20
|
+
for (const rel of files) {
|
|
21
|
+
const file = path.join(sourceDir, rel);
|
|
22
|
+
const text = await fs.readFile(file, 'utf8').catch(() => '');
|
|
23
|
+
if (!text)
|
|
24
|
+
continue;
|
|
25
|
+
evidence.push(...extractEvidence(rel, text));
|
|
26
|
+
}
|
|
27
|
+
if (!evidence.length)
|
|
28
|
+
blockers.push(`lazycodex_source_evidence_missing:${sourceDir}`);
|
|
29
|
+
const sourceSha = await gitSha(sourceDir);
|
|
30
|
+
const patterns = PATTERNS.map((pattern) => ({
|
|
31
|
+
id: pattern.id,
|
|
32
|
+
title: pattern.claim,
|
|
33
|
+
evidence: evidence.filter((row) => row.pattern_id === pattern.id).map((row) => `${row.file}:${row.lines?.join('-') || 'unknown'}:${row.snippet_hash}`),
|
|
34
|
+
sks_adoption: pattern.id === 'native-agent-type' || pattern.id === 'startup-review-hooks' || pattern.id === 'doctor-health-report' ? 'adopt' : 'adapt',
|
|
35
|
+
rationale: evidence.some((row) => row.pattern_id === pattern.id) ? 'Live source evidence found and hashed.' : 'No live source evidence found; keep static analysis as lower confidence.',
|
|
36
|
+
target_modules: []
|
|
37
|
+
}));
|
|
38
|
+
const report = {
|
|
39
|
+
schema: 'sks.lazycodex-live-analysis.v1',
|
|
40
|
+
generated_at: nowIso(),
|
|
41
|
+
source_repo: 'code-yeongyu/lazycodex',
|
|
42
|
+
source_ref: input.sourceRef || sourceSha || 'local-snapshot',
|
|
43
|
+
source_sha: sourceSha,
|
|
44
|
+
evidence,
|
|
45
|
+
patterns,
|
|
46
|
+
blockers,
|
|
47
|
+
warnings: blockers.length ? ['live_evidence_incomplete_static_fallback_required'] : []
|
|
48
|
+
};
|
|
49
|
+
if (input.writeReport !== false) {
|
|
50
|
+
await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'lazycodex-live-analysis.json'), report).catch(() => undefined);
|
|
51
|
+
await writeTextAtomic(path.join(root, 'docs', 'lazycodex-analysis.md'), renderLazyCodexLiveMarkdown(report)).catch(() => undefined);
|
|
52
|
+
}
|
|
53
|
+
return report;
|
|
54
|
+
}
|
|
55
|
+
export function extractEvidence(file, text) {
|
|
56
|
+
const lines = text.split(/\r?\n/);
|
|
57
|
+
const rows = [];
|
|
58
|
+
for (const pattern of PATTERNS) {
|
|
59
|
+
const index = lines.findIndex((line) => pattern.re.test(line));
|
|
60
|
+
if (index < 0)
|
|
61
|
+
continue;
|
|
62
|
+
const snippet = lines.slice(Math.max(0, index - 1), Math.min(lines.length, index + 2)).join('\n').slice(0, 500);
|
|
63
|
+
rows.push({
|
|
64
|
+
pattern_id: pattern.id,
|
|
65
|
+
file,
|
|
66
|
+
lines: [index + 1, index + 1],
|
|
67
|
+
snippet_hash: createHash('sha256').update(snippet).digest('hex'),
|
|
68
|
+
claim: pattern.claim,
|
|
69
|
+
confidence: 'high'
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return rows;
|
|
73
|
+
}
|
|
74
|
+
export function renderLazyCodexLiveMarkdown(report) {
|
|
75
|
+
const rows = report.evidence.map((row) => `| ${row.pattern_id} | ${row.file} | ${row.lines?.join('-') || '-'} | ${row.snippet_hash.slice(0, 16)} | ${row.confidence} |`).join('\n');
|
|
76
|
+
return [
|
|
77
|
+
'# LazyCodex / OmO Pattern Analysis',
|
|
78
|
+
'',
|
|
79
|
+
`Source repo: \`${report.source_repo}\``,
|
|
80
|
+
`Source ref: \`${report.source_ref}\``,
|
|
81
|
+
`Source sha: \`${report.source_sha || 'unknown'}\``,
|
|
82
|
+
`Generated at: \`${report.generated_at}\``,
|
|
83
|
+
'',
|
|
84
|
+
'| Pattern | File | Lines | Snippet Hash | Confidence |',
|
|
85
|
+
'|---|---|---:|---|---|',
|
|
86
|
+
rows || '| none | - | - | - | low |',
|
|
87
|
+
'',
|
|
88
|
+
'Long source excerpts are intentionally omitted; release artifacts store line anchors and snippet hashes only.'
|
|
89
|
+
].join('\n');
|
|
90
|
+
}
|
|
91
|
+
async function gitSha(sourceDir) {
|
|
92
|
+
const head = await fs.readFile(path.join(sourceDir, '.git', 'HEAD'), 'utf8').catch(() => '');
|
|
93
|
+
const ref = head.match(/^ref:\s*(.+)$/m)?.[1];
|
|
94
|
+
if (ref)
|
|
95
|
+
return (await fs.readFile(path.join(sourceDir, '.git', ref), 'utf8').catch(() => '')).trim() || null;
|
|
96
|
+
return /^[0-9a-f]{40}$/i.test(head.trim()) ? head.trim() : null;
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=lazycodex-live-analyzer.js.map
|
|
@@ -11,6 +11,7 @@ import { runLoopNode, runLoopPlan } from '../loops/loop-runtime.js';
|
|
|
11
11
|
import { scheduleLoopGraph } from '../loops/loop-scheduler.js';
|
|
12
12
|
import { writeLoopKillRequest } from '../loops/loop-runtime-control.js';
|
|
13
13
|
import { flag, promptOf, readFlagValue } from './command-utils.js';
|
|
14
|
+
import { runCodexInitDeep } from '../codex-app/codex-init-deep.js';
|
|
14
15
|
export async function loopCommand(subcommand = 'help', args = []) {
|
|
15
16
|
const action = subcommand || 'help';
|
|
16
17
|
if (action === 'plan')
|
|
@@ -27,6 +28,8 @@ export async function loopCommand(subcommand = 'help', args = []) {
|
|
|
27
28
|
return loopResume(args);
|
|
28
29
|
if (action === 'graph')
|
|
29
30
|
return loopGraph(args);
|
|
31
|
+
if (action === 'init-deep')
|
|
32
|
+
return loopInitDeep(args);
|
|
30
33
|
console.log(`SKS Loop
|
|
31
34
|
|
|
32
35
|
Usage:
|
|
@@ -37,8 +40,16 @@ Usage:
|
|
|
37
40
|
sks loop kill <loop-id|all>
|
|
38
41
|
sks loop resume latest [--rerun-completed]
|
|
39
42
|
sks loop graph latest
|
|
43
|
+
sks loop init-deep [--json]
|
|
40
44
|
`);
|
|
41
45
|
}
|
|
46
|
+
async function loopInitDeep(args) {
|
|
47
|
+
const root = await sksRoot();
|
|
48
|
+
const result = await runCodexInitDeep({ root, apply: !flag(args, '--check-only') && !flag(args, '--dry-run') });
|
|
49
|
+
if (flag(args, '--json'))
|
|
50
|
+
return printJson(result);
|
|
51
|
+
console.log(`Loop init-deep: ${result.ok ? 'ok' : 'blocked'} ${result.generated_path || ''}`);
|
|
52
|
+
}
|
|
42
53
|
async function loopPlan(args) {
|
|
43
54
|
const root = await sksRoot();
|
|
44
55
|
const request = promptOf(args);
|