sneakoscope 6.1.2 → 6.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -4
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/dist/cli/install-helpers.js +8 -3
- package/dist/config/skills-manifest.json +58 -58
- package/dist/core/agents/agent-effort-policy.js +28 -17
- package/dist/core/agents/agent-schema.js +1 -1
- package/dist/core/codex/codex-config-guard.js +178 -7
- package/dist/core/codex/codex-config-readability.js +21 -8
- package/dist/core/codex/codex-config-toml.js +14 -11
- package/dist/core/codex-app/mcp-manager.js +679 -0
- package/dist/core/codex-app/sks-menubar.js +405 -6
- package/dist/core/codex-control/codex-lb-launch-recovery.js +15 -0
- package/dist/core/codex-native/core-skill-manifest.js +1 -1
- package/dist/core/commands/mad-sks-command.js +44 -3
- package/dist/core/commands/menubar-command.js +94 -0
- package/dist/core/commands/naruto-command.js +3 -1
- package/dist/core/commands/wiki-command.js +11 -5
- package/dist/core/fsx.js +1 -0
- package/dist/core/hooks-runtime/code-pack-freshness-preflight.js +14 -8
- package/dist/core/hooks-runtime/naruto-decision-gate.js +184 -0
- package/dist/core/hooks-runtime/stop-repeat-guard.js +89 -0
- package/dist/core/hooks-runtime.js +36 -105
- package/dist/core/init/skills.js +4 -4
- package/dist/core/init.js +1 -1
- package/dist/core/managed-assets/managed-assets-manifest.js +178 -29
- package/dist/core/preflight/parallel-preflight-engine.js +16 -3
- package/dist/core/proof/route-adapter.js +1 -1
- package/dist/core/proof/selftest-proof-fixtures.js +3 -5
- package/dist/core/provider/model-router.js +15 -6
- package/dist/core/release/package-size-budget.js +4 -6
- package/dist/core/research/research-plan-markdown.js +123 -0
- package/dist/core/research/research-super-search.js +8 -4
- package/dist/core/research.js +4 -119
- package/dist/core/retention.js +70 -2
- package/dist/core/routes/dollar-manifest-lite.js +1 -1
- package/dist/core/routes.js +75 -22
- package/dist/core/subagents/agent-catalog.js +87 -15
- package/dist/core/subagents/model-policy.js +189 -48
- package/dist/core/subagents/naruto-help-contract.js +11 -4
- package/dist/core/subagents/official-subagent-preparation.js +55 -9
- package/dist/core/subagents/official-subagent-prompt.js +45 -23
- package/dist/core/subagents/thread-budget.js +1 -1
- package/dist/core/subagents/triwiki-attention.js +117 -14
- package/dist/core/triwiki/code-pack-head-freshness.js +291 -0
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-launcher.js +12 -2
- package/dist/core/zellij/zellij-pane-proof.js +9 -1
- package/dist/core/zellij/zellij-update.js +14 -1
- package/dist/scripts/canonical-test-runner.js +7 -1
- package/dist/scripts/codex-lb-fast-mode-truth-check.js +11 -1
- package/dist/scripts/codex-lb-fast-ui-preservation-check.js +9 -2
- package/dist/scripts/codex-lb-gpt56-fast-profile-check.js +13 -2
- package/dist/scripts/codex-native-agent-role-content-check.js +13 -1
- package/dist/scripts/codex-sdk-backend-router-check.js +2 -0
- package/dist/scripts/lib/codex-sdk-gate-lib.js +4 -0
- package/dist/scripts/official-subagent-workflow-check.js +1 -1
- package/dist/scripts/packlist-performance-check.js +1 -18
- package/dist/scripts/python-codex-sdk-all-pipelines-check.js +8 -0
- package/dist/scripts/sks-menubar-install-check.js +6 -1
- package/dist/scripts/super-search-provider-interface-check.js +31 -18
- package/package.json +3 -1
- package/dist/scripts/codex-0139-feature-probes-check.js +0 -30
- package/dist/scripts/codex-0139-marketplace-source-check.js +0 -13
|
@@ -2,41 +2,42 @@ import path from 'node:path';
|
|
|
2
2
|
import { readJson } from '../fsx.js';
|
|
3
3
|
export const BOUNDED_TRIWIKI_ATTENTION_SCHEMA = 'sks.subagent-triwiki-attention.v1';
|
|
4
4
|
export const DEFAULT_TRIWIKI_ATTENTION_ANCHOR_LIMIT = 8;
|
|
5
|
-
export async function readBoundedTriwikiAttention(root, limit = DEFAULT_TRIWIKI_ATTENTION_ANCHOR_LIMIT) {
|
|
5
|
+
export async function readBoundedTriwikiAttention(root, limit = DEFAULT_TRIWIKI_ATTENTION_ANCHOR_LIMIT, query = '') {
|
|
6
6
|
const pack = await readJson(path.join(root, '.sneakoscope', 'wiki', 'context-pack.json'), null);
|
|
7
|
-
return extractBoundedTriwikiAttention(pack, limit);
|
|
7
|
+
return extractBoundedTriwikiAttention(pack, limit, query);
|
|
8
8
|
}
|
|
9
|
-
export function extractBoundedTriwikiAttention(value, limit = DEFAULT_TRIWIKI_ATTENTION_ANCHOR_LIMIT) {
|
|
9
|
+
export function extractBoundedTriwikiAttention(value, limit = DEFAULT_TRIWIKI_ATTENTION_ANCHOR_LIMIT, query = '') {
|
|
10
10
|
const pack = asRecord(value);
|
|
11
11
|
const attention = asRecord(pack.attention);
|
|
12
12
|
const anchorLimit = normalizeLimit(limit);
|
|
13
13
|
const hydrateHints = new Map();
|
|
14
|
-
|
|
14
|
+
const hydrateOrder = new Map();
|
|
15
|
+
for (const [index, row] of (Array.isArray(attention.hydrate_first) ? attention.hydrate_first : []).entries()) {
|
|
15
16
|
if (!Array.isArray(row))
|
|
16
17
|
continue;
|
|
17
18
|
const id = text(row[0]);
|
|
18
19
|
const hint = text(row[1]);
|
|
19
|
-
if (id && hint)
|
|
20
|
+
if (id && hint) {
|
|
20
21
|
hydrateHints.set(id, hint.slice(0, 240));
|
|
22
|
+
hydrateOrder.set(id, index);
|
|
23
|
+
}
|
|
21
24
|
}
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
for (const row of Array.isArray(attention.use_first) ? attention.use_first : []) {
|
|
25
|
+
const useFirst = [];
|
|
26
|
+
for (const [index, row] of (Array.isArray(attention.use_first) ? attention.use_first : []).entries()) {
|
|
25
27
|
if (!Array.isArray(row))
|
|
26
28
|
continue;
|
|
27
29
|
const id = text(row[0]);
|
|
28
|
-
if (!id ||
|
|
30
|
+
if (!id || useFirst.some((anchor) => anchor.id === id))
|
|
29
31
|
continue;
|
|
30
|
-
|
|
31
|
-
anchors.push({
|
|
32
|
+
useFirst.push({
|
|
32
33
|
id,
|
|
33
34
|
claim_hash: text(row[1]) || null,
|
|
34
35
|
source_hash: text(row[2]) || null,
|
|
35
|
-
hydrate_hint: hydrateHints.get(id) || null
|
|
36
|
+
hydrate_hint: hydrateHints.get(id) || null,
|
|
37
|
+
order: index
|
|
36
38
|
});
|
|
37
|
-
if (anchors.length >= anchorLimit)
|
|
38
|
-
break;
|
|
39
39
|
}
|
|
40
|
+
const anchors = selectAnchors(useFirst, hydrateHints, hydrateOrder, anchorLimit, query);
|
|
40
41
|
return {
|
|
41
42
|
schema: BOUNDED_TRIWIKI_ATTENTION_SCHEMA,
|
|
42
43
|
source: '.sneakoscope/wiki/context-pack.json',
|
|
@@ -48,6 +49,108 @@ export function extractBoundedTriwikiAttention(value, limit = DEFAULT_TRIWIKI_AT
|
|
|
48
49
|
full_pack_injected: false
|
|
49
50
|
};
|
|
50
51
|
}
|
|
52
|
+
function selectAnchors(useFirst, hydrateHints, hydrateOrder, limit, query) {
|
|
53
|
+
const tokens = attentionQueryTokens(query);
|
|
54
|
+
if (!tokens.length)
|
|
55
|
+
return useFirst.slice(0, limit).map(stripOrder);
|
|
56
|
+
const selected = [];
|
|
57
|
+
const seen = new Set();
|
|
58
|
+
// Keep the leading high-trust policy anchors, then spend the remaining
|
|
59
|
+
// budget on query-relevant use_first or hydrate-first candidates. Hydrate-
|
|
60
|
+
// only rows stay hints: workers must open the cited source before relying on
|
|
61
|
+
// them, so relevance improves without treating lower-trust summaries as fact.
|
|
62
|
+
for (const anchor of useFirst.slice(0, Math.min(3, limit))) {
|
|
63
|
+
selected.push(stripOrder(anchor));
|
|
64
|
+
seen.add(anchor.id);
|
|
65
|
+
}
|
|
66
|
+
const candidates = [];
|
|
67
|
+
for (const anchor of useFirst) {
|
|
68
|
+
if (seen.has(anchor.id))
|
|
69
|
+
continue;
|
|
70
|
+
candidates.push({
|
|
71
|
+
...stripOrder(anchor),
|
|
72
|
+
score: attentionRelevance(anchor.id, anchor.hydrate_hint, tokens),
|
|
73
|
+
order: anchor.order,
|
|
74
|
+
priority: 0
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
for (const [id, hint] of hydrateHints.entries()) {
|
|
78
|
+
if (seen.has(id) || useFirst.some((anchor) => anchor.id === id))
|
|
79
|
+
continue;
|
|
80
|
+
const score = attentionRelevance(id, hint, tokens);
|
|
81
|
+
if (score <= 0)
|
|
82
|
+
continue;
|
|
83
|
+
candidates.push({
|
|
84
|
+
id,
|
|
85
|
+
claim_hash: null,
|
|
86
|
+
source_hash: null,
|
|
87
|
+
hydrate_hint: hint,
|
|
88
|
+
score,
|
|
89
|
+
order: hydrateOrder.get(id) ?? Number.MAX_SAFE_INTEGER,
|
|
90
|
+
priority: 1
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
candidates.sort((left, right) => right.score - left.score || left.priority - right.priority || left.order - right.order);
|
|
94
|
+
for (const candidate of candidates) {
|
|
95
|
+
if (selected.length >= limit)
|
|
96
|
+
break;
|
|
97
|
+
if (candidate.score <= 0 || seen.has(candidate.id))
|
|
98
|
+
continue;
|
|
99
|
+
selected.push(stripRank(candidate));
|
|
100
|
+
seen.add(candidate.id);
|
|
101
|
+
}
|
|
102
|
+
for (const anchor of useFirst) {
|
|
103
|
+
if (selected.length >= limit)
|
|
104
|
+
break;
|
|
105
|
+
if (seen.has(anchor.id))
|
|
106
|
+
continue;
|
|
107
|
+
selected.push(stripOrder(anchor));
|
|
108
|
+
seen.add(anchor.id);
|
|
109
|
+
}
|
|
110
|
+
return selected;
|
|
111
|
+
}
|
|
112
|
+
function attentionQueryTokens(value) {
|
|
113
|
+
const stop = new Set([
|
|
114
|
+
'and', 'the', 'for', 'with', 'from', 'into', 'this', 'that', 'sks', 'src', 'core',
|
|
115
|
+
'작업', '구현', '개선', '추가', '변경', '기능', '모든', '최대한'
|
|
116
|
+
]);
|
|
117
|
+
return [...new Set(String(value || '')
|
|
118
|
+
.normalize('NFKC')
|
|
119
|
+
.toLowerCase()
|
|
120
|
+
.replace(/[^\p{L}\p{N}]+/gu, ' ')
|
|
121
|
+
.split(/\s+/)
|
|
122
|
+
.map((token) => token.trim())
|
|
123
|
+
.filter((token) => token.length >= 2 && !stop.has(token)))]
|
|
124
|
+
.slice(0, 64);
|
|
125
|
+
}
|
|
126
|
+
function attentionRelevance(id, hint, tokens) {
|
|
127
|
+
const haystack = `${id} ${hint || ''}`
|
|
128
|
+
.normalize('NFKC')
|
|
129
|
+
.toLowerCase()
|
|
130
|
+
.replace(/[^\p{L}\p{N}]+/gu, ' ');
|
|
131
|
+
const compact = haystack.replace(/\s+/g, '');
|
|
132
|
+
return tokens.reduce((score, token) => {
|
|
133
|
+
if (haystack.includes(token))
|
|
134
|
+
return score + (token.length >= 5 ? 4 : 3);
|
|
135
|
+
return compact.includes(token) ? score + 2 : score;
|
|
136
|
+
}, 0);
|
|
137
|
+
}
|
|
138
|
+
function stripOrder(anchor) {
|
|
139
|
+
return {
|
|
140
|
+
id: anchor.id,
|
|
141
|
+
claim_hash: anchor.claim_hash,
|
|
142
|
+
source_hash: anchor.source_hash,
|
|
143
|
+
hydrate_hint: anchor.hydrate_hint
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function stripRank(anchor) {
|
|
147
|
+
return {
|
|
148
|
+
id: anchor.id,
|
|
149
|
+
claim_hash: anchor.claim_hash,
|
|
150
|
+
source_hash: anchor.source_hash,
|
|
151
|
+
hydrate_hint: anchor.hydrate_hint
|
|
152
|
+
};
|
|
153
|
+
}
|
|
51
154
|
function normalizeLimit(value) {
|
|
52
155
|
const parsed = Number(value);
|
|
53
156
|
if (!Number.isFinite(parsed))
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import fsp from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { readJson, runProcess, writeJsonAtomic } from '../fsx.js';
|
|
4
|
+
const CODE_PACK_METADATA_PATHS = new Set([
|
|
5
|
+
'.sneakoscope/wiki/code-pack.json',
|
|
6
|
+
'.sneakoscope/wiki/code-pack.prev.json'
|
|
7
|
+
]);
|
|
8
|
+
const COMMIT_MARKER = '@SKS-CODE-PACK';
|
|
9
|
+
const ADVISORY_CACHE_SCHEMA = 'sks.code-pack-head-freshness-cache.v1';
|
|
10
|
+
const ADVISORY_CACHE_PATH = path.join('.sneakoscope', 'cache', 'code-pack-head-freshness.json');
|
|
11
|
+
export async function inspectCodePackHeadFreshness(root, packShaInput, opts = {}) {
|
|
12
|
+
const timeoutMs = Math.max(1, Math.floor(opts.timeoutMs ?? 5_000));
|
|
13
|
+
const startedAt = Date.now();
|
|
14
|
+
const packSha = normalizeGitSha(packShaInput);
|
|
15
|
+
const fastHead = await readGitHeadFromAdminFiles(root);
|
|
16
|
+
if (!packSha)
|
|
17
|
+
return staleWithCurrentHead(root, null, timeoutMs, fastHead, false, 'invalid_pack_sha');
|
|
18
|
+
if (fastHead === packSha)
|
|
19
|
+
return freshness(true, true, 'exact_head', fastHead, packSha, false, []);
|
|
20
|
+
// Only the non-blocking hook consumes this cache. Authoritative wiki
|
|
21
|
+
// validation always replays Git history and never trusts advisory state.
|
|
22
|
+
if (opts.advisoryCache && fastHead) {
|
|
23
|
+
const cached = await readAdvisoryCache(root, packSha, fastHead);
|
|
24
|
+
if (cached)
|
|
25
|
+
return cached;
|
|
26
|
+
}
|
|
27
|
+
// One common-path Git process returns HEAD plus every committed path after
|
|
28
|
+
// the pack was generated. Excluding all parents of packSha keeps packSha in
|
|
29
|
+
// the walk (including a root commit), so its presence proves ancestry. We
|
|
30
|
+
// keep scanning after the pack marker because a later merge can introduce
|
|
31
|
+
// sibling-branch commits that Git orders after packSha. `-m` exposes merge
|
|
32
|
+
// resolution paths and `--no-renames` makes touched paths auditable.
|
|
33
|
+
const history = await runProcess('git', [
|
|
34
|
+
'-c',
|
|
35
|
+
'core.quotepath=true',
|
|
36
|
+
'log',
|
|
37
|
+
'--topo-order',
|
|
38
|
+
'-m',
|
|
39
|
+
'--no-ext-diff',
|
|
40
|
+
`--format=${COMMIT_MARKER}%x09%H`,
|
|
41
|
+
'--name-status',
|
|
42
|
+
'--no-renames',
|
|
43
|
+
'HEAD',
|
|
44
|
+
'--not',
|
|
45
|
+
`${packSha}^@`,
|
|
46
|
+
'--'
|
|
47
|
+
], {
|
|
48
|
+
cwd: root,
|
|
49
|
+
timeoutMs,
|
|
50
|
+
maxOutputBytes: 64 * 1024,
|
|
51
|
+
env: gitEnvironment({
|
|
52
|
+
GIT_NO_REPLACE_OBJECTS: '1',
|
|
53
|
+
GIT_OPTIONAL_LOCKS: '0',
|
|
54
|
+
LC_ALL: 'C'
|
|
55
|
+
})
|
|
56
|
+
}).catch(() => null);
|
|
57
|
+
if (!history || history.code !== 0 || history.timedOut) {
|
|
58
|
+
return staleWithCurrentHead(root, packSha, remainingMs(startedAt, timeoutMs), fastHead, false, history?.timedOut ? 'git_timeout' : 'git_failed');
|
|
59
|
+
}
|
|
60
|
+
const parsed = parseHistory(history.stdout, packSha);
|
|
61
|
+
if (history.truncated || !parsed.currentSha || parsed.invalid) {
|
|
62
|
+
const currentSha = parsed.currentSha
|
|
63
|
+
|| fastHead
|
|
64
|
+
|| await readCurrentHead(root, remainingMs(startedAt, timeoutMs));
|
|
65
|
+
return freshness(false, false, history.truncated ? 'history_truncated' : 'history_parse_invalid', currentSha, packSha, false, parsed.changedPaths);
|
|
66
|
+
}
|
|
67
|
+
if (!parsed.sawPack) {
|
|
68
|
+
return freshness(false, true, 'pack_not_ancestor', parsed.currentSha, packSha, false, parsed.changedPaths);
|
|
69
|
+
}
|
|
70
|
+
const metadataOnly = parsed.changedPaths.every((value) => CODE_PACK_METADATA_PATHS.has(value));
|
|
71
|
+
const exactHead = parsed.currentSha === packSha;
|
|
72
|
+
const result = freshness(metadataOnly, true, metadataOnly ? 'metadata_only_history' : 'source_change_history', parsed.currentSha, packSha, metadataOnly && !exactHead, parsed.changedPaths);
|
|
73
|
+
if (opts.advisoryCache && fastHead === parsed.currentSha) {
|
|
74
|
+
await writeAdvisoryCache(root, result).catch(() => undefined);
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
function parseHistory(stdout, packSha) {
|
|
79
|
+
let currentSha = null;
|
|
80
|
+
let activeCommit = null;
|
|
81
|
+
let sawPack = false;
|
|
82
|
+
let invalid = false;
|
|
83
|
+
const changedPaths = [];
|
|
84
|
+
const seenPaths = new Set();
|
|
85
|
+
for (const line of String(stdout || '').split(/\r?\n/)) {
|
|
86
|
+
if (line === '')
|
|
87
|
+
continue;
|
|
88
|
+
const marker = line.match(/^@SKS-CODE-PACK\t([0-9a-f]{40,64})$/i);
|
|
89
|
+
if (marker) {
|
|
90
|
+
activeCommit = String(marker[1]).toLowerCase();
|
|
91
|
+
currentSha ||= activeCommit;
|
|
92
|
+
if (activeCommit === packSha)
|
|
93
|
+
sawPack = true;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (!activeCommit) {
|
|
97
|
+
invalid = true;
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
const separator = line.indexOf('\t');
|
|
101
|
+
const status = separator > 0 ? line.slice(0, separator) : '';
|
|
102
|
+
const pathText = separator > 0 ? line.slice(separator + 1) : '';
|
|
103
|
+
if (!/^[A-Z][0-9]*$/.test(status) || !pathText) {
|
|
104
|
+
invalid = true;
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
if (activeCommit === packSha)
|
|
108
|
+
continue;
|
|
109
|
+
// Do not trim Git paths: leading/trailing whitespace is meaningful and
|
|
110
|
+
// must never be normalized into an allowlisted metadata path.
|
|
111
|
+
for (const changedPath of pathText.split('\t')) {
|
|
112
|
+
if (!changedPath || seenPaths.has(changedPath))
|
|
113
|
+
continue;
|
|
114
|
+
seenPaths.add(changedPath);
|
|
115
|
+
changedPaths.push(changedPath);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return { currentSha, sawPack, invalid, changedPaths };
|
|
119
|
+
}
|
|
120
|
+
async function readAdvisoryCache(root, packSha, currentSha) {
|
|
121
|
+
const cached = await readJson(path.join(root, ADVISORY_CACHE_PATH), null).catch(() => null);
|
|
122
|
+
if (cached?.schema !== ADVISORY_CACHE_SCHEMA)
|
|
123
|
+
return null;
|
|
124
|
+
if (normalizeGitSha(cached.pack_sha) !== packSha || normalizeGitSha(cached.current_sha) !== currentSha)
|
|
125
|
+
return null;
|
|
126
|
+
const changedPaths = Array.isArray(cached.changed_paths)
|
|
127
|
+
? cached.changed_paths.filter((value) => typeof value === 'string').slice(0, 64)
|
|
128
|
+
: [];
|
|
129
|
+
const fresh = cached.fresh === true;
|
|
130
|
+
if (fresh && (cached.metadata_only_drift !== true || changedPaths.some((value) => !CODE_PACK_METADATA_PATHS.has(value)))) {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
return freshness(fresh, true, 'advisory_cache', currentSha, packSha, fresh, changedPaths);
|
|
134
|
+
}
|
|
135
|
+
async function writeAdvisoryCache(root, result) {
|
|
136
|
+
if (!result.pack_sha || !result.current_sha)
|
|
137
|
+
return;
|
|
138
|
+
await writeJsonAtomic(path.join(root, ADVISORY_CACHE_PATH), {
|
|
139
|
+
schema: ADVISORY_CACHE_SCHEMA,
|
|
140
|
+
pack_sha: result.pack_sha,
|
|
141
|
+
current_sha: result.current_sha,
|
|
142
|
+
fresh: result.fresh,
|
|
143
|
+
reason: result.reason,
|
|
144
|
+
metadata_only_drift: result.metadata_only_drift,
|
|
145
|
+
changed_paths: result.changed_paths.slice(0, 64),
|
|
146
|
+
checked_at: new Date().toISOString()
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
async function staleWithCurrentHead(root, packSha, timeoutMs, knownCurrentSha = null, conclusive = true, reason = 'git_failed') {
|
|
150
|
+
const currentSha = knownCurrentSha || await readCurrentHead(root, timeoutMs);
|
|
151
|
+
return freshness(false, conclusive, reason, currentSha, packSha, false, []);
|
|
152
|
+
}
|
|
153
|
+
async function readCurrentHead(root, timeoutMs) {
|
|
154
|
+
if (timeoutMs <= 0)
|
|
155
|
+
return null;
|
|
156
|
+
const head = await runProcess('git', ['rev-parse', 'HEAD'], {
|
|
157
|
+
cwd: root,
|
|
158
|
+
timeoutMs,
|
|
159
|
+
maxOutputBytes: 4 * 1024,
|
|
160
|
+
env: gitEnvironment({
|
|
161
|
+
GIT_NO_REPLACE_OBJECTS: '1',
|
|
162
|
+
GIT_OPTIONAL_LOCKS: '0',
|
|
163
|
+
LC_ALL: 'C'
|
|
164
|
+
})
|
|
165
|
+
}).catch(() => null);
|
|
166
|
+
return head && head.code === 0 && !head.truncated ? normalizeGitSha(head.stdout) : null;
|
|
167
|
+
}
|
|
168
|
+
async function readGitHeadFromAdminFiles(root) {
|
|
169
|
+
const gitDir = await resolveGitDir(root);
|
|
170
|
+
if (!gitDir)
|
|
171
|
+
return null;
|
|
172
|
+
const headText = await readSmallText(path.join(gitDir, 'HEAD'), 4 * 1024);
|
|
173
|
+
if (!headText)
|
|
174
|
+
return null;
|
|
175
|
+
const detached = normalizeGitSha(headText);
|
|
176
|
+
if (detached)
|
|
177
|
+
return detached;
|
|
178
|
+
const symbolic = headText.match(/^ref:\s*(refs\/[^\r\n]+)\s*$/);
|
|
179
|
+
const ref = symbolic ? String(symbolic[1]) : '';
|
|
180
|
+
if (!safeGitRef(ref))
|
|
181
|
+
return null;
|
|
182
|
+
const commonDir = await resolveCommonDir(gitDir);
|
|
183
|
+
for (const base of [...new Set([gitDir, commonDir])]) {
|
|
184
|
+
const loose = await readLooseRef(base, ref);
|
|
185
|
+
if (loose)
|
|
186
|
+
return loose;
|
|
187
|
+
}
|
|
188
|
+
return readPackedRef(commonDir, ref);
|
|
189
|
+
}
|
|
190
|
+
async function resolveGitDir(root) {
|
|
191
|
+
const dotGit = path.join(root, '.git');
|
|
192
|
+
const stat = await fsp.stat(dotGit).catch(() => null);
|
|
193
|
+
if (!stat)
|
|
194
|
+
return null;
|
|
195
|
+
if (stat.isDirectory())
|
|
196
|
+
return dotGit;
|
|
197
|
+
if (!stat.isFile())
|
|
198
|
+
return null;
|
|
199
|
+
const text = await readSmallText(dotGit, 4 * 1024);
|
|
200
|
+
const match = text?.match(/^gitdir:\s*(.+?)\s*$/m);
|
|
201
|
+
return match ? path.resolve(path.dirname(dotGit), String(match[1])) : null;
|
|
202
|
+
}
|
|
203
|
+
async function resolveCommonDir(gitDir) {
|
|
204
|
+
const text = await readSmallText(path.join(gitDir, 'commondir'), 4 * 1024);
|
|
205
|
+
return text?.trim() ? path.resolve(gitDir, text.trim()) : gitDir;
|
|
206
|
+
}
|
|
207
|
+
async function readLooseRef(base, ref) {
|
|
208
|
+
const file = safeRefPath(base, ref);
|
|
209
|
+
if (!file)
|
|
210
|
+
return null;
|
|
211
|
+
return normalizeGitSha(await readSmallText(file, 4 * 1024));
|
|
212
|
+
}
|
|
213
|
+
async function readPackedRef(commonDir, ref) {
|
|
214
|
+
const text = await readSmallText(path.join(commonDir, 'packed-refs'), 4 * 1024 * 1024);
|
|
215
|
+
if (!text)
|
|
216
|
+
return null;
|
|
217
|
+
for (const line of text.split(/\r?\n/)) {
|
|
218
|
+
if (!line || line.startsWith('#') || line.startsWith('^'))
|
|
219
|
+
continue;
|
|
220
|
+
const separator = line.indexOf(' ');
|
|
221
|
+
if (separator <= 0 || line.slice(separator + 1) !== ref)
|
|
222
|
+
continue;
|
|
223
|
+
return normalizeGitSha(line.slice(0, separator));
|
|
224
|
+
}
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
async function readSmallText(file, maxBytes) {
|
|
228
|
+
const stat = await fsp.stat(file).catch(() => null);
|
|
229
|
+
if (!stat?.isFile() || stat.size > maxBytes)
|
|
230
|
+
return null;
|
|
231
|
+
return fsp.readFile(file, 'utf8').catch(() => null);
|
|
232
|
+
}
|
|
233
|
+
function safeGitRef(ref) {
|
|
234
|
+
return /^refs\/[A-Za-z0-9._/-]+$/.test(ref)
|
|
235
|
+
&& !ref.includes('..')
|
|
236
|
+
&& !ref.includes('//')
|
|
237
|
+
&& !ref.includes('@{')
|
|
238
|
+
&& !ref.endsWith('/')
|
|
239
|
+
&& !ref.endsWith('.')
|
|
240
|
+
&& !ref.endsWith('.lock');
|
|
241
|
+
}
|
|
242
|
+
function safeRefPath(base, ref) {
|
|
243
|
+
if (!safeGitRef(ref))
|
|
244
|
+
return null;
|
|
245
|
+
const candidate = path.resolve(base, ...ref.split('/'));
|
|
246
|
+
const relative = path.relative(path.resolve(base), candidate);
|
|
247
|
+
return relative && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative) ? candidate : null;
|
|
248
|
+
}
|
|
249
|
+
function remainingMs(startedAt, timeoutMs) {
|
|
250
|
+
return Math.max(0, timeoutMs - (Date.now() - startedAt));
|
|
251
|
+
}
|
|
252
|
+
function gitEnvironment(extra = {}) {
|
|
253
|
+
return {
|
|
254
|
+
GIT_ALTERNATE_OBJECT_DIRECTORIES: undefined,
|
|
255
|
+
GIT_COMMON_DIR: undefined,
|
|
256
|
+
GIT_CONFIG: undefined,
|
|
257
|
+
GIT_CONFIG_COUNT: '0',
|
|
258
|
+
GIT_CONFIG_GLOBAL: undefined,
|
|
259
|
+
GIT_CONFIG_NOSYSTEM: undefined,
|
|
260
|
+
GIT_CONFIG_PARAMETERS: undefined,
|
|
261
|
+
GIT_CONFIG_SYSTEM: undefined,
|
|
262
|
+
GIT_DIR: undefined,
|
|
263
|
+
GIT_DISCOVERY_ACROSS_FILESYSTEM: undefined,
|
|
264
|
+
GIT_EXTERNAL_DIFF: undefined,
|
|
265
|
+
GIT_GRAFT_FILE: undefined,
|
|
266
|
+
GIT_INDEX_FILE: undefined,
|
|
267
|
+
GIT_NAMESPACE: undefined,
|
|
268
|
+
GIT_OBJECT_DIRECTORY: undefined,
|
|
269
|
+
GIT_QUARANTINE_PATH: undefined,
|
|
270
|
+
GIT_REPLACE_REF_BASE: undefined,
|
|
271
|
+
GIT_SHALLOW_FILE: undefined,
|
|
272
|
+
GIT_WORK_TREE: undefined,
|
|
273
|
+
...extra
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
function freshness(fresh, conclusive, reason, currentSha, packSha, metadataOnlyDrift, changedPaths) {
|
|
277
|
+
return {
|
|
278
|
+
fresh,
|
|
279
|
+
conclusive,
|
|
280
|
+
reason,
|
|
281
|
+
current_sha: currentSha,
|
|
282
|
+
pack_sha: packSha,
|
|
283
|
+
metadata_only_drift: metadataOnlyDrift,
|
|
284
|
+
changed_paths: changedPaths
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
function normalizeGitSha(value) {
|
|
288
|
+
const text = String(value || '').trim();
|
|
289
|
+
return /^[0-9a-f]{40,64}$/i.test(text) && !/^0+$/.test(text) ? text.toLowerCase() : null;
|
|
290
|
+
}
|
|
291
|
+
//# sourceMappingURL=code-pack-head-freshness.js.map
|
package/dist/core/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = '6.
|
|
1
|
+
export const PACKAGE_VERSION = '6.2.0';
|
|
2
2
|
//# sourceMappingURL=version.js.map
|
|
@@ -29,7 +29,8 @@ export async function launchZellijLayout(opts = {}) {
|
|
|
29
29
|
if (opts.codexBin)
|
|
30
30
|
layoutInput.codexBin = opts.codexBin;
|
|
31
31
|
const layout = await writeZellijLayout(root, layoutInput);
|
|
32
|
-
const capability =
|
|
32
|
+
const capability = reusableZellijCapability(opts.zellijCapability, opts.requireZellij === true)
|
|
33
|
+
|| await checkZellijCapability({ root, require: opts.requireZellij === true });
|
|
33
34
|
// Configure the clipboard pipeline so selections inside the session reach the OS
|
|
34
35
|
// clipboard (Zellij's default OSC-52 copy is dropped by Terminal.app etc.). The
|
|
35
36
|
// copy option flags are appended AFTER `--default-layout <path>` so the launch
|
|
@@ -46,6 +47,7 @@ export async function launchZellijLayout(opts = {}) {
|
|
|
46
47
|
root: opts.cwd || root,
|
|
47
48
|
env: launchProcessEnv,
|
|
48
49
|
cliArgs: layout.codex_args,
|
|
50
|
+
...(opts.verifiedCodexLbToolOutputRecovery ? { verifiedProbe: opts.verifiedCodexLbToolOutputRecovery } : {}),
|
|
49
51
|
...(typeof opts.recoveryFetch === 'function' ? { fetchImpl: opts.recoveryFetch } : {}),
|
|
50
52
|
...(opts.recoveryTimeoutMs === undefined ? {} : { timeoutMs: opts.recoveryTimeoutMs }),
|
|
51
53
|
...(opts.recoveryAllowUnverified === true ? { allowUnverified: true } : {})
|
|
@@ -75,7 +77,8 @@ export async function launchZellijLayout(opts = {}) {
|
|
|
75
77
|
ledgerRoot: path.join(root, '.sneakoscope', 'missions', missionId),
|
|
76
78
|
sessionName,
|
|
77
79
|
expectedLaneCount: 0,
|
|
78
|
-
expectedCwd: opts.cwd || root
|
|
80
|
+
expectedCwd: opts.cwd || root,
|
|
81
|
+
zellijCapability: capability
|
|
79
82
|
};
|
|
80
83
|
if (layout.main_pane_kind === 'codex_interactive')
|
|
81
84
|
paneProofOpts.expectedMainCommandIncludes = 'codex';
|
|
@@ -169,6 +172,13 @@ export async function launchZellijLayout(opts = {}) {
|
|
|
169
172
|
]);
|
|
170
173
|
return report;
|
|
171
174
|
}
|
|
175
|
+
function reusableZellijCapability(value, requireZellij) {
|
|
176
|
+
if (!value || value.schema !== 'sks.zellij-capability.v1')
|
|
177
|
+
return null;
|
|
178
|
+
if (requireZellij && value.status !== 'ok')
|
|
179
|
+
return null;
|
|
180
|
+
return value;
|
|
181
|
+
}
|
|
172
182
|
function launchRecoveryEnv(launchEnv) {
|
|
173
183
|
const env = { ...process.env };
|
|
174
184
|
for (const [key, value] of Object.entries(launchEnv || {})) {
|
|
@@ -9,7 +9,8 @@ export async function writeZellijPaneProof(root, opts = {}) {
|
|
|
9
9
|
const session = await readJson(path.join(outRoot, 'zellij-session.json'), null);
|
|
10
10
|
const sessionName = opts.sessionName || session?.session_name || null;
|
|
11
11
|
const expectedMainCommand = opts.expectedMainCommandIncludes || (session?.codex_pane?.enabled === true ? String(session.codex_pane.bin || 'codex') : '');
|
|
12
|
-
const capability =
|
|
12
|
+
const capability = reusableZellijCapability(opts.zellijCapability, opts.require === true)
|
|
13
|
+
|| await checkZellijCapability({ root, require: opts.require === true, writeReport: true });
|
|
13
14
|
const command = sessionName
|
|
14
15
|
? ['--session', sessionName, 'action', 'list-panes', '--json', '--all']
|
|
15
16
|
: ['action', 'list-panes', '--json', '--all'];
|
|
@@ -93,6 +94,13 @@ export async function writeZellijPaneProof(root, opts = {}) {
|
|
|
93
94
|
await writeJsonAtomic(path.join(outRoot, 'zellij-pane-proof.json'), report);
|
|
94
95
|
return report;
|
|
95
96
|
}
|
|
97
|
+
function reusableZellijCapability(value, requireZellij) {
|
|
98
|
+
if (!value || value.schema !== 'sks.zellij-capability.v1')
|
|
99
|
+
return null;
|
|
100
|
+
if (requireZellij && value.status !== 'ok')
|
|
101
|
+
return null;
|
|
102
|
+
return value;
|
|
103
|
+
}
|
|
96
104
|
export async function readZellijPaneProof(root) {
|
|
97
105
|
return readJson(path.join(root, 'zellij-pane-proof.json'), null);
|
|
98
106
|
}
|
|
@@ -206,6 +206,7 @@ export async function upgradeZellijToLatest(input = {}) {
|
|
|
206
206
|
export async function maybePromptZellijUpdateForLaunch(args = [], opts = {}) {
|
|
207
207
|
const env = opts.env || process.env;
|
|
208
208
|
const list = (args || []).map((arg) => String(arg));
|
|
209
|
+
const autoYes = list.includes('--yes') || list.includes('-y');
|
|
209
210
|
const mode = resolveZellijUpdatePromptMode({
|
|
210
211
|
env,
|
|
211
212
|
skipFlag: list.includes('--json') || list.includes('--skip-cli-tools') || list.includes('--skip-zellij-update'),
|
|
@@ -215,6 +216,19 @@ export async function maybePromptZellijUpdateForLaunch(args = [], opts = {}) {
|
|
|
215
216
|
if (mode === 'skip') {
|
|
216
217
|
return { status: 'skipped', current: null, latest: null, command: null };
|
|
217
218
|
}
|
|
219
|
+
if (opts.deferUpdateCheck === true && !autoYes) {
|
|
220
|
+
const localCapability = await checkZellijCapability({ require: false, writeReport: false, env }).catch(() => null);
|
|
221
|
+
if (localCapability?.status === 'ok') {
|
|
222
|
+
return {
|
|
223
|
+
status: 'current',
|
|
224
|
+
current: localCapability.version,
|
|
225
|
+
latest: null,
|
|
226
|
+
command: null,
|
|
227
|
+
deferred: true,
|
|
228
|
+
capability: localCapability
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
}
|
|
218
232
|
const noticeInput = { env };
|
|
219
233
|
if (opts.missionDir !== undefined)
|
|
220
234
|
noticeInput.missionDir = opts.missionDir;
|
|
@@ -264,7 +278,6 @@ export async function maybePromptZellijUpdateForLaunch(args = [], opts = {}) {
|
|
|
264
278
|
return { status: 'current', current: notice.current_version, latest: notice.latest_version, command: null, error: notice.error || null };
|
|
265
279
|
}
|
|
266
280
|
const label = opts.label || 'Zellij launch';
|
|
267
|
-
const autoYes = list.includes('--yes') || list.includes('-y');
|
|
268
281
|
if (mode === 'nonblocking-notice') {
|
|
269
282
|
console.log(`Zellij update available: ${notice.current_version} -> ${notice.latest_version}. Run: ${notice.upgrade_command}`);
|
|
270
283
|
return { status: 'available', current: notice.current_version, latest: notice.latest_version, command: notice.upgrade_command };
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { spawn } from 'node:child_process';
|
|
5
|
-
import { tmpdir } from '../core/fsx.js';
|
|
5
|
+
import { SKS_TEMP_LEASE_FILE, tmpdir, writeJsonAtomic } from '../core/fsx.js';
|
|
6
6
|
const root = process.cwd();
|
|
7
7
|
const compiled = discover(path.join(root, 'dist'), (file) => file.endsWith('.test.js') && file.includes(`${path.sep}__tests__${path.sep}`));
|
|
8
8
|
const unit = discover(path.join(root, 'test', 'unit'), (file) => file.endsWith('.test.mjs'));
|
|
@@ -60,6 +60,12 @@ process.once('exit', () => {
|
|
|
60
60
|
if (error)
|
|
61
61
|
console.error(`canonical test cleanup failed during exit: ${error.message}`);
|
|
62
62
|
});
|
|
63
|
+
await writeJsonAtomic(path.join(scratch, SKS_TEMP_LEASE_FILE), {
|
|
64
|
+
schema: 'sks.temp-lease.v1',
|
|
65
|
+
kind: 'canonical-test-runner',
|
|
66
|
+
pid: process.pid,
|
|
67
|
+
created_at: new Date().toISOString()
|
|
68
|
+
});
|
|
63
69
|
const isolatedProcessGroup = process.platform !== 'win32';
|
|
64
70
|
const child = spawn(process.execPath, ['--test', '--test-concurrency=1', ...files, ...process.argv.slice(2)], {
|
|
65
71
|
cwd: root,
|
|
@@ -3,6 +3,7 @@ import fs from 'node:fs/promises';
|
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { run } from '../commands/codex-lb.js';
|
|
6
|
+
import { CODEX_LB_TOOL_OUTPUT_RECOVERY_MIN_VERSION } from '../core/codex-lb/codex-lb-tool-output-recovery.js';
|
|
6
7
|
const calls = [];
|
|
7
8
|
const home = await fs.mkdtemp(path.join(os.tmpdir(), 'sks-codex-lb-fast-truth-'));
|
|
8
9
|
await fs.mkdir(path.join(home, '.codex'), { recursive: true });
|
|
@@ -58,7 +59,16 @@ async function runFastCheck(env) {
|
|
|
58
59
|
try {
|
|
59
60
|
Object.assign(process.env, env);
|
|
60
61
|
process.exitCode = 0;
|
|
61
|
-
globalThis.fetch = async (
|
|
62
|
+
globalThis.fetch = async (url, init = {}) => {
|
|
63
|
+
if (new URL(String(url)).pathname === '/health') {
|
|
64
|
+
return new Response('{}', {
|
|
65
|
+
status: 200,
|
|
66
|
+
headers: {
|
|
67
|
+
'content-type': 'application/json',
|
|
68
|
+
'x-app-version': CODEX_LB_TOOL_OUTPUT_RECOVERY_MIN_VERSION
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
62
72
|
const body = JSON.parse(String(init.body || '{}'));
|
|
63
73
|
calls.push({ body });
|
|
64
74
|
const id = calls.length % 2 === 1 ? 'resp_fast_1' : 'resp_fast_2';
|
|
@@ -41,8 +41,15 @@ await fs.writeFile(configPath, [
|
|
|
41
41
|
].join('\n'));
|
|
42
42
|
await fs.writeFile(envPath, 'export CODEX_LB_BASE_URL="https://lb.example.test/backend-api/codex"\nexport CODEX_LB_API_KEY="sk-test-fast-ui"\n');
|
|
43
43
|
await fs.writeFile(authPath, `${oauthAuth}\n`);
|
|
44
|
+
const toolOutputRecoveryFetch = async () => new Response('{}', {
|
|
45
|
+
status: 200,
|
|
46
|
+
headers: {
|
|
47
|
+
'content-type': 'application/json',
|
|
48
|
+
'x-app-version': '1.21.0-beta.3'
|
|
49
|
+
}
|
|
50
|
+
});
|
|
44
51
|
const install = await ensureGlobalCodexFastModeDuringInstall({ home, configPath, forceFastMode: true });
|
|
45
|
-
const firstRepair = await repairCodexLbAuth({ home, configPath, envPath, forceCodexLbApiKeyAuth: true, forceFastMode: true, authMode: 'codex-lb' });
|
|
52
|
+
const firstRepair = await repairCodexLbAuth({ home, configPath, envPath, forceCodexLbApiKeyAuth: true, forceFastMode: true, authMode: 'codex-lb', toolOutputRecoveryFetch });
|
|
46
53
|
const firstConfig = await fs.readFile(configPath, 'utf8');
|
|
47
54
|
const firstAssert = assertConfig(firstConfig, 'first_use_codex_lb');
|
|
48
55
|
const release = await releaseCodexLbAuthHold({ home, configPath, authPath, backupPath: oauthBackupPath });
|
|
@@ -55,7 +62,7 @@ const releaseAssert = {
|
|
|
55
62
|
...(hasLegacyFastModeTables(releasedConfig) ? ['legacy_fast_mode_tables_survived_use_oauth'] : [])
|
|
56
63
|
]
|
|
57
64
|
};
|
|
58
|
-
const secondRepair = await repairCodexLbAuth({ home, configPath, envPath, forceCodexLbApiKeyAuth: true, forceFastMode: true, authMode: 'codex-lb' });
|
|
65
|
+
const secondRepair = await repairCodexLbAuth({ home, configPath, envPath, forceCodexLbApiKeyAuth: true, forceFastMode: true, authMode: 'codex-lb', toolOutputRecoveryFetch });
|
|
59
66
|
const secondConfig = await fs.readFile(configPath, 'utf8');
|
|
60
67
|
const secondAssert = assertConfig(secondConfig, 'second_use_codex_lb');
|
|
61
68
|
const report = {
|