sneakoscope 3.1.4 → 3.1.6
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 +8 -36
- 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/command-registry.js +1 -2
- package/dist/cli/install-helpers.js +56 -4
- package/dist/commands/codex-app.js +1 -11
- package/dist/commands/codex-lb.js +12 -9
- package/dist/commands/codex-native.js +68 -0
- package/dist/commands/doctor.js +64 -0
- package/dist/core/codex-app/codex-agent-role-sync.js +69 -11
- package/dist/core/codex-app/codex-agent-type-probe.js +202 -0
- package/dist/core/codex-app/codex-app-execution-profile.js +43 -14
- package/dist/core/codex-app/codex-app-fast-ui-repair.js +7 -4
- package/dist/core/codex-app/codex-app-harness-matrix.js +4 -65
- 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 +34 -10
- package/dist/core/codex-app/codex-init-deep.js +154 -3
- package/dist/core/codex-app/codex-skill-sync.js +84 -9
- package/dist/core/codex-native/codex-native-capability-cache.js +21 -0
- package/dist/core/codex-native/codex-native-feature-broker.js +182 -0
- package/dist/core/codex-native/codex-native-feature-matrix.js +31 -0
- package/dist/core/codex-native/codex-native-harness-compat.js +54 -0
- package/dist/core/codex-native/codex-native-interop-policy.js +58 -0
- package/dist/core/codex-native/codex-native-invocation-router.js +112 -0
- package/dist/core/codex-native/codex-native-pattern-analysis.js +56 -0
- package/dist/core/codex-native/codex-native-reference-evidence.js +2 -0
- package/dist/core/codex-native/codex-native-reference-source.js +110 -0
- package/dist/core/codex-native/codex-native-rename-map.js +25 -0
- package/dist/core/commands/mad-sks-command.js +37 -2
- package/dist/core/commands/qa-loop-command.js +3 -2
- package/dist/core/commands/research-command.js +2 -2
- package/dist/core/doctor/doctor-zellij-repair.js +5 -1
- package/dist/core/feature-fixtures.js +3 -4
- package/dist/core/feature-registry.js +5 -2
- package/dist/core/fsx.js +1 -1
- package/dist/core/image/image-artifact-path-contract.js +18 -1
- package/dist/core/init.js +4 -1
- package/dist/core/loops/loop-continuation-enforcer.js +11 -3
- package/dist/core/loops/loop-owner-inference.js +3 -0
- package/dist/core/loops/loop-planner.js +28 -5
- package/dist/core/loops/loop-worker-runtime.js +52 -8
- package/dist/core/naruto/naruto-loop-worker-router.js +11 -2
- package/dist/core/qa-loop.js +62 -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 +68 -1
- package/dist/core/routes.js +2 -3
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/homebrew-policy.js +0 -1
- package/dist/core/zellij/zellij-self-heal-types.js +45 -0
- package/dist/core/zellij/zellij-self-heal.js +69 -8
- package/dist/core/zellij/zellij-update.js +9 -1
- package/dist/scripts/sks-3-1-4-directive-check-lib.js +1 -30
- package/dist/scripts/sks-3-1-5-directive-check-lib.js +318 -0
- package/dist/scripts/sks-3-1-6-directive-check-lib.js +522 -0
- package/package.json +53 -9
- package/dist/cli/hermes-command.js +0 -99
- package/dist/cli/openclaw-command.js +0 -83
- package/dist/commands/hermes.js +0 -5
- package/dist/commands/openclaw.js +0 -3
- package/dist/core/codex-app/lazycodex-analysis.js +0 -51
- package/dist/core/codex-app/lazycodex-interop-policy.js +0 -49
- package/dist/core/hermes.js +0 -192
- package/dist/core/openclaw.js +0 -171
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { findCodexBinary } from '../codex-adapter.js';
|
|
3
|
+
import { readCodexHookActualState } from '../codex-hooks/codex-hook-actual-discovery.js';
|
|
4
|
+
import { nowIso, runProcess, writeJsonAtomic } from '../fsx.js';
|
|
5
|
+
import { isRecord } from './codex-app-types.js';
|
|
6
|
+
export async function probeCodexHookApprovalState(root, input = {}) {
|
|
7
|
+
const env = input.env || process.env;
|
|
8
|
+
const sources = [];
|
|
9
|
+
const fixture = normalizeApprovalState(env.SKS_CODEX_HOOK_APPROVAL_FIXTURE);
|
|
10
|
+
if (fixture) {
|
|
11
|
+
const report = buildProbe(fixture !== 'unknown', fixture, [{
|
|
12
|
+
source: 'config',
|
|
13
|
+
ok: fixture !== 'unknown',
|
|
14
|
+
evidence: [`fixture:${fixture}`],
|
|
15
|
+
blockers: fixture === 'unknown' ? ['fixture_unknown'] : []
|
|
16
|
+
}]);
|
|
17
|
+
return persist(root, report, input.writeReport !== false);
|
|
18
|
+
}
|
|
19
|
+
const actual = await readCodexHookActualState(root).catch((err) => ({
|
|
20
|
+
schema: 'sks.codex-hook-actual-state.v1',
|
|
21
|
+
ok: false,
|
|
22
|
+
root,
|
|
23
|
+
sources: [],
|
|
24
|
+
managed_dirs: [],
|
|
25
|
+
entries: [],
|
|
26
|
+
unsupported_handlers: [],
|
|
27
|
+
invalid_matchers: [],
|
|
28
|
+
dual_representation: [],
|
|
29
|
+
warnings: [],
|
|
30
|
+
blockers: [messageOf(err)]
|
|
31
|
+
}));
|
|
32
|
+
const actualState = approvalFromActualState(actual);
|
|
33
|
+
sources.push({
|
|
34
|
+
source: 'hook-actual-state',
|
|
35
|
+
ok: actual.ok !== false,
|
|
36
|
+
evidence: [
|
|
37
|
+
`entries:${actual.entries.length}`,
|
|
38
|
+
`trusted:${actual.entries.filter((entry) => entry.trust_status === 'Trusted' || entry.trust_status === 'Managed').length}`,
|
|
39
|
+
`modified:${actual.entries.filter((entry) => entry.trust_status === 'Modified').length}`,
|
|
40
|
+
`untrusted:${actual.entries.filter((entry) => entry.trust_status === 'Untrusted').length}`
|
|
41
|
+
],
|
|
42
|
+
blockers: actual.blockers
|
|
43
|
+
});
|
|
44
|
+
const doctor = await probeCodexDoctorApproval(input.codexBin, env);
|
|
45
|
+
sources.push(doctor.source);
|
|
46
|
+
const doctorState = doctor.approval_state;
|
|
47
|
+
const state = strongestApprovalState([actualState, doctorState]);
|
|
48
|
+
const detectable = state !== 'unknown';
|
|
49
|
+
const blockers = [
|
|
50
|
+
...sources.flatMap((source) => source.blockers),
|
|
51
|
+
...(state === 'modified_requires_reapproval' ? ['hook_modified_requires_reapproval'] : [])
|
|
52
|
+
];
|
|
53
|
+
const warnings = [
|
|
54
|
+
...(state === 'unknown' ? ['hook_approval_state_unknown'] : []),
|
|
55
|
+
...(actual.entries.length > 0 && state === 'unknown' ? ['hooks_installed_but_approval_unknown'] : [])
|
|
56
|
+
];
|
|
57
|
+
const report = {
|
|
58
|
+
schema: 'sks.codex-hook-approval-probe.v1',
|
|
59
|
+
generated_at: nowIso(),
|
|
60
|
+
ok: blockers.length === 0 && state !== 'pending_review' && state !== 'modified_requires_reapproval',
|
|
61
|
+
detectable,
|
|
62
|
+
approval_state: state,
|
|
63
|
+
sources_checked: sources,
|
|
64
|
+
blockers,
|
|
65
|
+
warnings
|
|
66
|
+
};
|
|
67
|
+
return persist(root, report, input.writeReport !== false);
|
|
68
|
+
}
|
|
69
|
+
function approvalFromActualState(actual) {
|
|
70
|
+
if (actual.entries.length === 0)
|
|
71
|
+
return 'not_installed';
|
|
72
|
+
if (actual.entries.some((entry) => entry.trust_status === 'Modified'))
|
|
73
|
+
return 'modified_requires_reapproval';
|
|
74
|
+
if (actual.entries.some((entry) => entry.trust_status === 'Untrusted'))
|
|
75
|
+
return 'pending_review';
|
|
76
|
+
if (actual.entries.every((entry) => entry.trust_status === 'Trusted' || entry.trust_status === 'Managed'))
|
|
77
|
+
return 'approved';
|
|
78
|
+
return 'unknown';
|
|
79
|
+
}
|
|
80
|
+
async function probeCodexDoctorApproval(codexBin, env) {
|
|
81
|
+
const bin = codexBin || env.CODEX_BIN || await findCodexBinary().catch(() => null);
|
|
82
|
+
if (!bin) {
|
|
83
|
+
return { approval_state: 'unknown', source: { source: 'codex-doctor-json', ok: false, evidence: [], blockers: ['codex_cli_missing'] } };
|
|
84
|
+
}
|
|
85
|
+
const run = await runProcess(bin, ['doctor', '--json'], { env, timeoutMs: 8000, maxOutputBytes: 256 * 1024 }).catch((err) => ({
|
|
86
|
+
code: 1,
|
|
87
|
+
stdout: '',
|
|
88
|
+
stderr: messageOf(err)
|
|
89
|
+
}));
|
|
90
|
+
const text = `${run.stdout || ''}${run.stderr || ''}`.trim();
|
|
91
|
+
if (run.code !== 0 || !text) {
|
|
92
|
+
return {
|
|
93
|
+
approval_state: 'unknown',
|
|
94
|
+
source: { source: 'codex-doctor-json', ok: false, evidence: text ? [text.slice(0, 240)] : [], blockers: ['codex_doctor_json_unavailable'] }
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const parsed = JSON.parse(text);
|
|
99
|
+
const state = findApprovalState(parsed);
|
|
100
|
+
return {
|
|
101
|
+
approval_state: state || 'unknown',
|
|
102
|
+
source: {
|
|
103
|
+
source: 'codex-doctor-json',
|
|
104
|
+
ok: Boolean(state),
|
|
105
|
+
evidence: state ? [`approval_state:${state}`] : ['doctor_json_no_hook_approval_field'],
|
|
106
|
+
blockers: state ? [] : ['hook_approval_not_exposed_by_codex_doctor']
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return {
|
|
112
|
+
approval_state: 'unknown',
|
|
113
|
+
source: { source: 'codex-doctor-json', ok: false, evidence: [text.slice(0, 240)], blockers: ['codex_doctor_json_parse_failed'] }
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function findApprovalState(value) {
|
|
118
|
+
if (!isRecord(value))
|
|
119
|
+
return null;
|
|
120
|
+
for (const key of ['hook_approval_state', 'approval_state', 'hookApprovalState']) {
|
|
121
|
+
const state = normalizeApprovalState(value[key]);
|
|
122
|
+
if (state)
|
|
123
|
+
return state;
|
|
124
|
+
}
|
|
125
|
+
for (const nested of Object.values(value)) {
|
|
126
|
+
if (Array.isArray(nested)) {
|
|
127
|
+
for (const item of nested) {
|
|
128
|
+
const found = findApprovalState(item);
|
|
129
|
+
if (found)
|
|
130
|
+
return found;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else if (isRecord(nested)) {
|
|
134
|
+
const found = findApprovalState(nested);
|
|
135
|
+
if (found)
|
|
136
|
+
return found;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
function normalizeApprovalState(value) {
|
|
142
|
+
const raw = String(value || '').trim().toLowerCase().replace(/[-\s]+/g, '_');
|
|
143
|
+
if (raw === 'approved' || raw === 'trusted' || raw === 'managed')
|
|
144
|
+
return 'approved';
|
|
145
|
+
if (raw === 'pending' || raw === 'pending_review' || raw === 'untrusted')
|
|
146
|
+
return 'pending_review';
|
|
147
|
+
if (raw === 'modified' || raw === 'modified_requires_reapproval')
|
|
148
|
+
return 'modified_requires_reapproval';
|
|
149
|
+
if (raw === 'not_installed' || raw === 'missing')
|
|
150
|
+
return 'not_installed';
|
|
151
|
+
if (raw === 'unknown')
|
|
152
|
+
return 'unknown';
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
function strongestApprovalState(states) {
|
|
156
|
+
if (states.includes('modified_requires_reapproval'))
|
|
157
|
+
return 'modified_requires_reapproval';
|
|
158
|
+
if (states.includes('pending_review'))
|
|
159
|
+
return 'pending_review';
|
|
160
|
+
if (states.includes('approved'))
|
|
161
|
+
return 'approved';
|
|
162
|
+
if (states.every((state) => state === 'not_installed'))
|
|
163
|
+
return 'not_installed';
|
|
164
|
+
if (states.includes('not_installed') && states.length === 1)
|
|
165
|
+
return 'not_installed';
|
|
166
|
+
return 'unknown';
|
|
167
|
+
}
|
|
168
|
+
function buildProbe(detectable, approvalState, sources) {
|
|
169
|
+
return {
|
|
170
|
+
schema: 'sks.codex-hook-approval-probe.v1',
|
|
171
|
+
generated_at: nowIso(),
|
|
172
|
+
ok: approvalState === 'approved' || approvalState === 'not_installed',
|
|
173
|
+
detectable,
|
|
174
|
+
approval_state: approvalState,
|
|
175
|
+
sources_checked: sources,
|
|
176
|
+
blockers: sources.flatMap((source) => source.blockers),
|
|
177
|
+
warnings: approvalState === 'unknown' ? ['hook_approval_state_unknown'] : []
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
async function persist(root, report, writeReport) {
|
|
181
|
+
if (writeReport)
|
|
182
|
+
await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'codex-hook-approval-probe.json'), report).catch(() => undefined);
|
|
183
|
+
return report;
|
|
184
|
+
}
|
|
185
|
+
function messageOf(err) {
|
|
186
|
+
return err instanceof Error ? err.message : String(err);
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=codex-hook-approval-probe.js.map
|
|
@@ -1,14 +1,24 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
import path from 'node:path';
|
|
3
2
|
import { nowIso, writeJsonAtomic } from '../fsx.js';
|
|
4
3
|
import { readCodexHookActualState } from '../codex-hooks/codex-hook-actual-discovery.js';
|
|
5
4
|
import { installManagedCodexHooks } from '../codex-hooks/codex-hook-managed-install.js';
|
|
5
|
+
import { probeCodexHookApprovalState } from './codex-hook-approval-probe.js';
|
|
6
6
|
export async function buildCodexHookLifecycle(input = {}) {
|
|
7
7
|
const root = path.resolve(input.root || process.cwd());
|
|
8
8
|
const install = input.apply === true
|
|
9
|
-
? await installManagedCodexHooks(root).catch((err) => ({ ok: false, blockers: [
|
|
9
|
+
? await installManagedCodexHooks(root).catch((err) => ({ ok: false, blockers: [messageOf(err)] }))
|
|
10
10
|
: null;
|
|
11
|
-
const actual = await readCodexHookActualState(root).catch((err) => ({ ok: false, entries: [], blockers: [
|
|
11
|
+
const actual = await readCodexHookActualState(root).catch((err) => ({ ok: false, entries: [], blockers: [messageOf(err)] }));
|
|
12
|
+
const probe = await probeCodexHookApprovalState(root).catch((err) => ({
|
|
13
|
+
schema: 'sks.codex-hook-approval-probe.v1',
|
|
14
|
+
generated_at: nowIso(),
|
|
15
|
+
ok: false,
|
|
16
|
+
detectable: false,
|
|
17
|
+
approval_state: 'unknown',
|
|
18
|
+
sources_checked: [],
|
|
19
|
+
blockers: [messageOf(err)],
|
|
20
|
+
warnings: ['hook_approval_probe_failed']
|
|
21
|
+
}));
|
|
12
22
|
const events = {
|
|
13
23
|
UserPromptSubmit: ['route_classifier', 'goal_to_loop_compiler', 'permission_profile_badge'],
|
|
14
24
|
PreToolUse: ['requested_scope_guard', 'mad_db_priority_resolver', 'side_effect_zero_gate'],
|
|
@@ -16,25 +26,39 @@ export async function buildCodexHookLifecycle(input = {}) {
|
|
|
16
26
|
Stop: ['continuation_enforcer', 'final_proof_check', 'loop_resume_hint'],
|
|
17
27
|
Notification: ['operator_status', 'zellij_anchor', 'codex_app_status']
|
|
18
28
|
};
|
|
19
|
-
const installedEvents = new Set((actual.entries
|
|
29
|
+
const installedEvents = new Set(Array.isArray(actual.entries) ? actual.entries.map((entry) => String(entry.event || '')) : []);
|
|
20
30
|
const report = {
|
|
21
31
|
schema: 'sks.codex-hook-lifecycle.v1',
|
|
22
32
|
generated_at: nowIso(),
|
|
23
|
-
ok: actual.ok !== false,
|
|
33
|
+
ok: actual.ok !== false && probe.approval_state !== 'modified_requires_reapproval',
|
|
24
34
|
apply: input.apply === true,
|
|
25
|
-
approval_state:
|
|
26
|
-
approval_state_detectable:
|
|
35
|
+
approval_state: probe.approval_state,
|
|
36
|
+
approval_state_detectable: probe.detectable,
|
|
27
37
|
lifecycle: Object.fromEntries(Object.entries(events).map(([event, actions]) => [event, {
|
|
38
|
+
event,
|
|
28
39
|
actions,
|
|
40
|
+
sks_action: actions.join(','),
|
|
29
41
|
installed: installedEvents.has(event),
|
|
30
|
-
approval_state: '
|
|
42
|
+
approval_state: installedEvents.has(event) ? probe.approval_state : 'not_installed',
|
|
43
|
+
counted_as_evidence: installedEvents.has(event) && probe.approval_state === 'approved'
|
|
31
44
|
}])),
|
|
32
45
|
actual_state: actual,
|
|
33
46
|
install,
|
|
34
|
-
|
|
35
|
-
|
|
47
|
+
probe,
|
|
48
|
+
blockers: [
|
|
49
|
+
...(Array.isArray(actual.blockers) ? actual.blockers.map(String) : []),
|
|
50
|
+
...(probe.approval_state === 'modified_requires_reapproval' ? ['hook_modified_requires_reapproval'] : [])
|
|
51
|
+
],
|
|
52
|
+
warnings: [
|
|
53
|
+
...probe.warnings,
|
|
54
|
+
...(probe.approval_state === 'pending_review' ? ['hook_approval_pending_review'] : []),
|
|
55
|
+
...(probe.approval_state === 'unknown' ? ['hook_approval_state_unknown'] : [])
|
|
56
|
+
]
|
|
36
57
|
};
|
|
37
58
|
await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'codex-hook-lifecycle.json'), report).catch(() => undefined);
|
|
38
59
|
return report;
|
|
39
60
|
}
|
|
61
|
+
function messageOf(err) {
|
|
62
|
+
return err instanceof Error ? err.message : String(err);
|
|
63
|
+
}
|
|
40
64
|
//# sourceMappingURL=codex-hook-lifecycle.js.map
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
import fs from 'node:fs/promises';
|
|
3
2
|
import path from 'node:path';
|
|
4
3
|
import { ensureDir, nowIso, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
|
|
4
|
+
import { guardContextForRoute, guardedRm } from '../safety/mutation-guard.js';
|
|
5
|
+
import { createRequestedScopeContract } from '../safety/requested-scope-contract.js';
|
|
5
6
|
export async function runCodexInitDeep(input = {}) {
|
|
6
7
|
const root = path.resolve(input.root || process.cwd());
|
|
7
8
|
const dirs = await scoreDirectories(root);
|
|
@@ -9,29 +10,154 @@ export async function runCodexInitDeep(input = {}) {
|
|
|
9
10
|
const contextDir = path.join(root, '.sneakoscope', 'context');
|
|
10
11
|
const generatedPath = path.join(contextDir, 'AGENTS.generated.md');
|
|
11
12
|
const markdown = renderGeneratedAgents(selected);
|
|
13
|
+
const directoryLocalAgents = { created: [], updated: [], skipped: [], backup_paths: [], backups_created: 0, backups_pruned: [], unchanged_files: [], changed_only_backup: true, blockers: [] };
|
|
14
|
+
const backupRetention = Math.max(0, Number(process.env.SKS_INIT_DEEP_BACKUP_RETENTION || 5) || 5);
|
|
12
15
|
if (input.apply === true) {
|
|
13
16
|
await ensureDir(contextDir);
|
|
14
17
|
await writeTextAtomic(generatedPath, markdown);
|
|
18
|
+
if (input.directoryLocal === true) {
|
|
19
|
+
for (const row of selected) {
|
|
20
|
+
const agentsPath = path.join(root, row.dir, 'AGENTS.md');
|
|
21
|
+
try {
|
|
22
|
+
await ensureDir(path.dirname(agentsPath));
|
|
23
|
+
const existing = await fs.readFile(agentsPath, 'utf8').catch(() => '');
|
|
24
|
+
const next = mergeManagedBlockPreview(existing, 'SKS INIT-DEEP MANAGED SECTION', renderDirectoryAgentsBlock(row));
|
|
25
|
+
if (existing === next) {
|
|
26
|
+
directoryLocalAgents.unchanged_files.push(path.relative(root, agentsPath));
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (existing.trim()) {
|
|
30
|
+
const beforeHash = hashText(existing);
|
|
31
|
+
const backup = `${agentsPath}.sks-backup-${beforeHash.slice(0, 12)}-${Date.now()}`;
|
|
32
|
+
await fs.copyFile(agentsPath, backup);
|
|
33
|
+
directoryLocalAgents.backup_paths.push(path.relative(root, backup));
|
|
34
|
+
directoryLocalAgents.backups_created += 1;
|
|
35
|
+
}
|
|
36
|
+
await writeTextAtomic(agentsPath, next);
|
|
37
|
+
const pruned = await pruneBackups(root, agentsPath, backupRetention);
|
|
38
|
+
directoryLocalAgents.backups_pruned.push(...pruned.map((file) => path.relative(root, file)));
|
|
39
|
+
const status = existing.trim() ? (existing.includes('BEGIN SKS INIT-DEEP MANAGED SECTION') ? 'updated' : 'appended') : 'created';
|
|
40
|
+
if (status === 'created')
|
|
41
|
+
directoryLocalAgents.created.push(path.relative(root, agentsPath));
|
|
42
|
+
else
|
|
43
|
+
directoryLocalAgents.updated.push(path.relative(root, agentsPath));
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
directoryLocalAgents.skipped.push(path.relative(root, agentsPath));
|
|
47
|
+
directoryLocalAgents.blockers.push(`${path.relative(root, agentsPath)}:${messageOf(err)}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
15
51
|
}
|
|
16
52
|
const report = {
|
|
17
53
|
schema: 'sks.codex-init-deep.v1',
|
|
18
54
|
generated_at: nowIso(),
|
|
19
|
-
ok:
|
|
55
|
+
ok: directoryLocalAgents.blockers.length === 0,
|
|
20
56
|
apply: input.apply === true,
|
|
21
57
|
root,
|
|
22
58
|
generated_path: path.relative(root, generatedPath),
|
|
23
59
|
root_agents_preserved: true,
|
|
24
60
|
directory_guidance: selected,
|
|
25
|
-
|
|
61
|
+
directory_local_agents: directoryLocalAgents,
|
|
62
|
+
blockers: directoryLocalAgents.blockers
|
|
26
63
|
};
|
|
27
64
|
await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'codex-init-deep.json'), report).catch(() => undefined);
|
|
28
65
|
return report;
|
|
29
66
|
}
|
|
67
|
+
function mergeManagedBlockPreview(current, markerName, content) {
|
|
68
|
+
const begin = `<!-- BEGIN ${markerName} -->`;
|
|
69
|
+
const end = `<!-- END ${markerName} -->`;
|
|
70
|
+
const block = `${begin}\n${content.trim()}\n${end}\n`;
|
|
71
|
+
if (!current.trim())
|
|
72
|
+
return `${block}\n`;
|
|
73
|
+
const beginIdx = current.indexOf(begin);
|
|
74
|
+
const endIdx = current.indexOf(end);
|
|
75
|
+
if (beginIdx >= 0 && endIdx >= beginIdx) {
|
|
76
|
+
const afterEnd = endIdx + end.length;
|
|
77
|
+
return `${current.slice(0, beginIdx)}${block}${current.slice(afterEnd).replace(/^\n/, '')}`;
|
|
78
|
+
}
|
|
79
|
+
return `${current.replace(/\s*$/, '\n\n')}${block}\n`;
|
|
80
|
+
}
|
|
81
|
+
async function pruneBackups(root, agentsPath, keep) {
|
|
82
|
+
if (keep < 1)
|
|
83
|
+
return [];
|
|
84
|
+
const dir = path.dirname(agentsPath);
|
|
85
|
+
const base = path.basename(agentsPath);
|
|
86
|
+
const rows = await fs.readdir(dir).catch(() => []);
|
|
87
|
+
const backups = rows
|
|
88
|
+
.filter((name) => name.startsWith(`${base}.sks-backup-`))
|
|
89
|
+
.map((name) => path.join(dir, name))
|
|
90
|
+
.sort();
|
|
91
|
+
const remove = backups.slice(0, Math.max(0, backups.length - keep));
|
|
92
|
+
const contract = createRequestedScopeContract({
|
|
93
|
+
route: 'codex-app:init-deep',
|
|
94
|
+
userRequest: 'Prune only SKS init-deep AGENTS.md backup files after creating a fresh backup.',
|
|
95
|
+
projectRoot: root
|
|
96
|
+
});
|
|
97
|
+
const guard = guardContextForRoute(root, contract, 'prune SKS init-deep backup retention');
|
|
98
|
+
for (const file of remove)
|
|
99
|
+
await guardedRm(guard, file, { force: true }).catch(() => undefined);
|
|
100
|
+
return remove;
|
|
101
|
+
}
|
|
102
|
+
function hashText(text) {
|
|
103
|
+
let hash = 2166136261;
|
|
104
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
105
|
+
hash ^= text.charCodeAt(index);
|
|
106
|
+
hash = Math.imul(hash, 16777619);
|
|
107
|
+
}
|
|
108
|
+
return (hash >>> 0).toString(16).padStart(8, '0');
|
|
109
|
+
}
|
|
30
110
|
export async function readInitDeepMemory(root) {
|
|
31
111
|
const file = path.join(root, '.sneakoscope', 'context', 'AGENTS.generated.md');
|
|
32
112
|
const text = await fs.readFile(file, 'utf8').catch(() => '');
|
|
33
113
|
return text.trim() ? { path: file, text } : null;
|
|
34
114
|
}
|
|
115
|
+
export async function readInitDeepMemoryHints(root, scopePaths = []) {
|
|
116
|
+
const resolvedRoot = path.resolve(root);
|
|
117
|
+
const hints = [];
|
|
118
|
+
const generated = await readInitDeepMemory(resolvedRoot).catch(() => null);
|
|
119
|
+
if (generated) {
|
|
120
|
+
hints.push({
|
|
121
|
+
path: path.relative(resolvedRoot, generated.path),
|
|
122
|
+
scope: '.',
|
|
123
|
+
summary: generated.text.split(/\r?\n/).filter((line) => /^##\s+/.test(line)).slice(0, 8).join(' | ')
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
const candidateDirs = new Set();
|
|
127
|
+
for (const scopePath of scopePaths) {
|
|
128
|
+
const absolute = path.resolve(resolvedRoot, scopePath);
|
|
129
|
+
if (!absolute.startsWith(resolvedRoot))
|
|
130
|
+
continue;
|
|
131
|
+
const stat = await fs.stat(absolute).catch(() => null);
|
|
132
|
+
let dir = stat?.isFile() ? path.dirname(absolute) : absolute;
|
|
133
|
+
while (dir.startsWith(resolvedRoot)) {
|
|
134
|
+
candidateDirs.add(dir);
|
|
135
|
+
if (dir === resolvedRoot)
|
|
136
|
+
break;
|
|
137
|
+
dir = path.dirname(dir);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
for (const dir of [...candidateDirs].sort((a, b) => b.length - a.length)) {
|
|
141
|
+
const file = path.join(dir, 'AGENTS.md');
|
|
142
|
+
const text = await fs.readFile(file, 'utf8').catch(() => '');
|
|
143
|
+
if (!text.trim())
|
|
144
|
+
continue;
|
|
145
|
+
const managed = extractManagedSection(text, 'SKS INIT-DEEP MANAGED SECTION');
|
|
146
|
+
const userSummary = text.replace(/<!-- BEGIN [\s\S]*?<!-- END [^>]+-->/g, '').split(/\r?\n/).map((line) => line.trim()).filter(Boolean).slice(0, 4).join(' | ');
|
|
147
|
+
const summary = [managed ? managed.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).slice(0, 4).join(' | ') : '', userSummary ? `user:${userSummary}` : ''].filter(Boolean).join(' || ');
|
|
148
|
+
if (summary) {
|
|
149
|
+
hints.push({
|
|
150
|
+
path: path.relative(resolvedRoot, file),
|
|
151
|
+
scope: path.relative(resolvedRoot, dir) || '.',
|
|
152
|
+
summary
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const unique = new Map();
|
|
157
|
+
for (const hint of hints)
|
|
158
|
+
unique.set(`${hint.path}:${hint.scope}`, hint);
|
|
159
|
+
return [...unique.values()].slice(0, 12);
|
|
160
|
+
}
|
|
35
161
|
async function scoreDirectories(root) {
|
|
36
162
|
const counts = new Map();
|
|
37
163
|
await walk(path.join(root, 'src'), root, counts);
|
|
@@ -60,6 +186,8 @@ async function walk(dir, root, counts, depth = 0) {
|
|
|
60
186
|
await walk(full, root, counts, depth + 1);
|
|
61
187
|
}
|
|
62
188
|
else if (row.isFile()) {
|
|
189
|
+
if (row.name.includes('.sks-backup-'))
|
|
190
|
+
continue;
|
|
63
191
|
const relDir = path.relative(root, path.dirname(full)).split(path.sep).join('/');
|
|
64
192
|
const entry = counts.get(relDir) || { file_count: 0, langs: new Set() };
|
|
65
193
|
entry.file_count += 1;
|
|
@@ -84,4 +212,27 @@ function renderGeneratedAgents(rows) {
|
|
|
84
212
|
])
|
|
85
213
|
].join('\n');
|
|
86
214
|
}
|
|
215
|
+
function renderDirectoryAgentsBlock(row) {
|
|
216
|
+
return [
|
|
217
|
+
`# SKS Init-Deep Local Guidance: ${row.dir}`,
|
|
218
|
+
'',
|
|
219
|
+
`- Files observed: ${row.file_count}`,
|
|
220
|
+
`- Languages: ${row.languages.join(', ') || 'unknown'}`,
|
|
221
|
+
`- Guidance: ${row.guidance}`,
|
|
222
|
+
'- Preserve user-authored content outside this managed block.',
|
|
223
|
+
'- Hydrate TriWiki/current source before risky edits in this directory.'
|
|
224
|
+
].join('\n');
|
|
225
|
+
}
|
|
226
|
+
function extractManagedSection(text, markerName) {
|
|
227
|
+
const begin = `<!-- BEGIN ${markerName} -->`;
|
|
228
|
+
const end = `<!-- END ${markerName} -->`;
|
|
229
|
+
const beginIdx = text.indexOf(begin);
|
|
230
|
+
const endIdx = text.indexOf(end);
|
|
231
|
+
if (beginIdx < 0 || endIdx < beginIdx)
|
|
232
|
+
return '';
|
|
233
|
+
return text.slice(beginIdx + begin.length, endIdx).trim();
|
|
234
|
+
}
|
|
235
|
+
function messageOf(err) {
|
|
236
|
+
return err instanceof Error ? err.message : String(err);
|
|
237
|
+
}
|
|
87
238
|
//# sourceMappingURL=codex-init-deep.js.map
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
import fs from 'node:fs/promises';
|
|
3
2
|
import path from 'node:path';
|
|
4
3
|
import os from 'node:os';
|
|
@@ -14,19 +13,19 @@ const SKS_SKILLS = [
|
|
|
14
13
|
'$Computer-Use',
|
|
15
14
|
'$Init-Deep'
|
|
16
15
|
];
|
|
17
|
-
const
|
|
16
|
+
const EXTERNAL_ROUTE_RESERVED = new Set(['ulw-loop', 'ulw-plan', 'start-work']);
|
|
18
17
|
export async function syncCodexSksSkills(input) {
|
|
19
18
|
const root = path.resolve(input.root);
|
|
20
19
|
const skillsRoot = input.skillsRoot || path.join(process.env.CODEX_HOME || path.join(os.homedir(), '.codex'), 'skills');
|
|
21
20
|
const existing = await listSkillNames(skillsRoot);
|
|
22
|
-
const collisions = existing.filter((name) =>
|
|
21
|
+
const collisions = existing.filter((name) => EXTERNAL_ROUTE_RESERVED.has(name));
|
|
23
22
|
const desired = SKS_SKILLS.map((skill) => skillName(skill));
|
|
24
23
|
const created = [];
|
|
25
24
|
const skipped = [];
|
|
26
25
|
if (input.apply === true) {
|
|
27
26
|
await ensureDir(skillsRoot);
|
|
28
27
|
for (const name of desired) {
|
|
29
|
-
if (
|
|
28
|
+
if (EXTERNAL_ROUTE_RESERVED.has(name)) {
|
|
30
29
|
skipped.push(name);
|
|
31
30
|
continue;
|
|
32
31
|
}
|
|
@@ -53,10 +52,10 @@ export async function syncCodexSksSkills(input) {
|
|
|
53
52
|
existing_skills: existing,
|
|
54
53
|
created,
|
|
55
54
|
skipped,
|
|
56
|
-
|
|
55
|
+
external_route_names_preserved: collisions,
|
|
57
56
|
interop: {
|
|
58
57
|
mode: 'coexist',
|
|
59
|
-
|
|
58
|
+
clobbered_external_routes: false,
|
|
60
59
|
clobbered_user_skills: false
|
|
61
60
|
},
|
|
62
61
|
blockers: []
|
|
@@ -72,20 +71,96 @@ function skillName(value) {
|
|
|
72
71
|
return value.replace(/^\$/, '').toLowerCase();
|
|
73
72
|
}
|
|
74
73
|
function skillContent(name) {
|
|
74
|
+
const profile = skillProfile(name);
|
|
75
75
|
const body = [
|
|
76
76
|
'---',
|
|
77
77
|
`name: ${name}`,
|
|
78
|
-
`description: SKS managed Codex App route bridge for
|
|
78
|
+
`description: SKS managed Codex App route bridge for ${profile.command}.`,
|
|
79
79
|
'---',
|
|
80
80
|
'',
|
|
81
81
|
'<!-- BEGIN SKS MANAGED SKILL -->',
|
|
82
|
-
`
|
|
83
|
-
`
|
|
82
|
+
`Command: ${profile.command}`,
|
|
83
|
+
`Purpose: ${profile.purpose}`,
|
|
84
|
+
`Use when: ${profile.when}`,
|
|
85
|
+
`Route: ${profile.command}`,
|
|
86
|
+
`Evidence: ${profile.evidence}`,
|
|
87
|
+
'Safety: keep route state bounded, preserve user and external route assets, and stop on hard blockers instead of fabricating fallback behavior.',
|
|
88
|
+
'Proof paths: write the route-local mission artifact named in Evidence before claiming completion.',
|
|
89
|
+
'Failure recovery: if a proof path cannot be produced, record the blocker and continue only when the selected SKS route has another allowed evidence path.',
|
|
90
|
+
`Fallback: ${profile.fallback}`,
|
|
91
|
+
`checksum: ${hash(`${name}:${profile.command}:${profile.evidence}:${profile.fallback}`)}`,
|
|
84
92
|
'<!-- END SKS MANAGED SKILL -->',
|
|
85
93
|
''
|
|
86
94
|
].join('\n');
|
|
87
95
|
return body;
|
|
88
96
|
}
|
|
97
|
+
function skillProfile(name) {
|
|
98
|
+
const table = {
|
|
99
|
+
loop: {
|
|
100
|
+
command: '$Loop',
|
|
101
|
+
purpose: 'compile persisted route work into bounded loop plans with continuation evidence.',
|
|
102
|
+
when: 'a mission needs stage-by-stage execution, memory hints, or resume-safe artifacts.',
|
|
103
|
+
evidence: '.sneakoscope/loops/** plus codex-app-execution-profile.json',
|
|
104
|
+
fallback: 'use message-role routing when native agent_type is not verified.'
|
|
105
|
+
},
|
|
106
|
+
naruto: {
|
|
107
|
+
command: '$Naruto',
|
|
108
|
+
purpose: 'fan out bounded native worker lanes for high-scale review or implementation.',
|
|
109
|
+
when: 'parallel lanes are explicitly selected by the route and parent integration remains owner.',
|
|
110
|
+
evidence: 'naruto work graph, worker ledgers, and execution profile payloads.',
|
|
111
|
+
fallback: 'degrade to message-role workers without dropping proof artifacts.'
|
|
112
|
+
},
|
|
113
|
+
'qa-loop': {
|
|
114
|
+
command: '$QA-LOOP',
|
|
115
|
+
purpose: 'dogfood UI/API behavior with gate artifacts and current execution profile.',
|
|
116
|
+
when: 'route completion needs human-proxy verification or app handoff checks.',
|
|
117
|
+
evidence: 'qa-loop gate/result ledgers and codex-app-execution-profile.json.',
|
|
118
|
+
fallback: 'record the unavailable surface as blocked rather than inventing visual proof.'
|
|
119
|
+
},
|
|
120
|
+
research: {
|
|
121
|
+
command: '$Research',
|
|
122
|
+
purpose: 'run evidence-bound research cycles with source routing and synthesis ledgers.',
|
|
123
|
+
when: 'the request depends on discovery, evaluation, or external-source claims.',
|
|
124
|
+
evidence: 'research plan, source ledger, cycle record, and execution profile routing.',
|
|
125
|
+
fallback: 'mark unavailable source tools explicitly and avoid unsupported live-accuracy claims.'
|
|
126
|
+
},
|
|
127
|
+
dfix: {
|
|
128
|
+
command: '$DFix',
|
|
129
|
+
purpose: 'perform tiny direct fixes without the full Team route.',
|
|
130
|
+
when: 'copy/config/docs/labels/spacing/translation/mechanical edits are truly narrow.',
|
|
131
|
+
evidence: 'focused diff plus DFix Honest check.',
|
|
132
|
+
fallback: 'route broad implementation through Team/Loop instead.'
|
|
133
|
+
},
|
|
134
|
+
'image-ux-review': {
|
|
135
|
+
command: '$Image-UX-Review',
|
|
136
|
+
purpose: 'produce generated annotated UI review images and extract issue ledgers.',
|
|
137
|
+
when: 'visual UX critique is requested from screenshots or app captures.',
|
|
138
|
+
evidence: 'source inventory, generated annotation images, extracted issue ledger.',
|
|
139
|
+
fallback: 'block if raster annotation cannot be produced.'
|
|
140
|
+
},
|
|
141
|
+
'computer-use': {
|
|
142
|
+
command: '$Computer-Use',
|
|
143
|
+
purpose: 'operate native macOS desktop apps through the fast Computer Use lane.',
|
|
144
|
+
when: 'the task depends on non-web desktop UI or OS settings.',
|
|
145
|
+
evidence: 'desktop interaction notes/screenshots where available.',
|
|
146
|
+
fallback: 'use Browser/Chrome only for web targets.'
|
|
147
|
+
},
|
|
148
|
+
'init-deep': {
|
|
149
|
+
command: '$Init-Deep',
|
|
150
|
+
purpose: 'refresh project-local memory, directory AGENTS sections, and loop memory hints.',
|
|
151
|
+
when: 'a route needs deeper local context or directory-specific instruction recall.',
|
|
152
|
+
evidence: '.sneakoscope/context/AGENTS.generated.md and managed directory AGENTS blocks.',
|
|
153
|
+
fallback: 'preserve user content and skip directories that cannot be safely updated.'
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
return table[name] || {
|
|
157
|
+
command: `$${name}`,
|
|
158
|
+
purpose: 'bridge an SKS managed Codex App route.',
|
|
159
|
+
when: 'the matching SKS route is explicitly requested.',
|
|
160
|
+
evidence: '.sneakoscope route artifacts.',
|
|
161
|
+
fallback: 'record blockers with evidence.'
|
|
162
|
+
};
|
|
163
|
+
}
|
|
89
164
|
function hash(value) {
|
|
90
165
|
return crypto.createHash('sha256').update(`sks-skill:${value}`).digest('hex').slice(0, 12);
|
|
91
166
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { writeJsonAtomic } from '../fsx.js';
|
|
4
|
+
const CACHE_RELATIVE_PATH = '.sneakoscope/reports/codex-native-feature-matrix.json';
|
|
5
|
+
export function codexNativeCapabilityCachePath(root) {
|
|
6
|
+
return path.join(root, CACHE_RELATIVE_PATH);
|
|
7
|
+
}
|
|
8
|
+
export async function readCodexNativeCapabilityCache(root) {
|
|
9
|
+
const file = codexNativeCapabilityCachePath(root);
|
|
10
|
+
const text = await fs.readFile(file, 'utf8').catch(() => null);
|
|
11
|
+
if (!text)
|
|
12
|
+
return null;
|
|
13
|
+
const parsed = JSON.parse(text);
|
|
14
|
+
return parsed?.schema === 'sks.codex-native-feature-matrix.v1' ? parsed : null;
|
|
15
|
+
}
|
|
16
|
+
export async function writeCodexNativeCapabilityCache(root, matrix) {
|
|
17
|
+
const file = codexNativeCapabilityCachePath(root);
|
|
18
|
+
await writeJsonAtomic(file, matrix);
|
|
19
|
+
return file;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=codex-native-capability-cache.js.map
|