sneakoscope 0.6.87 → 0.6.90
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 +4 -0
- package/package.json +1 -1
- package/src/cli/main.mjs +33 -1
- package/src/cli/maintenance-commands.mjs +2 -0
- package/src/core/fsx.mjs +1 -1
- package/src/core/hooks-runtime.mjs +49 -0
- package/src/core/init.mjs +35 -2
- package/src/core/pipeline.mjs +1 -1
package/README.md
CHANGED
|
@@ -90,6 +90,8 @@ sks bootstrap
|
|
|
90
90
|
|
|
91
91
|
`sks` commands work even when no project root is present. Project-aware commands use the nearest `.sneakoscope`, `.dcodex`, or `.git` root; if none exists, SKS uses a per-user global runtime root. `sks bootstrap` still initializes the current project when you want project-local hooks, skills, and TriWiki state.
|
|
92
92
|
|
|
93
|
+
Project setup writes shared `.gitignore` entries for generated SKS files: `.sneakoscope/`, `.codex/`, `.agents/`, and managed `AGENTS.md`. Use `sks setup --local-only` when you want those excludes kept only in `.git/info/exclude`.
|
|
94
|
+
|
|
93
95
|
### One-Shot Install
|
|
94
96
|
|
|
95
97
|
Use this when you do not want to keep a global install:
|
|
@@ -256,6 +258,8 @@ Generated app files include:
|
|
|
256
258
|
| `.codex/config.toml` | Codex profiles, agents, and MCP configuration. |
|
|
257
259
|
| `.sneakoscope/` | Runtime state, missions, wiki packs, policies, and artifacts. |
|
|
258
260
|
|
|
261
|
+
Default setup adds these generated SKS paths to the project `.gitignore`; `--local-only` uses `.git/info/exclude` instead.
|
|
262
|
+
|
|
259
263
|
Use `sks dollar-commands` to confirm that terminal discovery and Codex App prompt commands agree.
|
|
260
264
|
|
|
261
265
|
TriWiki is intentionally sparse: `sks wiki sweep` records demote, soft-forget, archive, delete, promote-to-skill, and promote-to-rule candidates instead of injecting every old claim into future prompts. `sks harness fixture` validates the broader Harness Growth Factory contract: deliberate forgetting fixtures, skill card metadata, experiment schema, tool-error taxonomy, permission profiles, MultiAgentV2 defaults, and Warp cockpit view coverage. `sks code-structure scan` flags handwritten files above 1000/2000/3000-line thresholds so new logic can be extracted before command files become harder to maintain.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sneakoscope",
|
|
3
3
|
"displayName": "ㅅㅋㅅ",
|
|
4
|
-
"version": "0.6.
|
|
4
|
+
"version": "0.6.90",
|
|
5
5
|
"description": "Sneakoscope Codex: database-safe Codex CLI/App harness with Team, Goal, AutoResearch, TriWiki, and Honest Mode.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",
|
package/src/cli/main.mjs
CHANGED
|
@@ -1385,6 +1385,7 @@ async function codexAppHelp(args = []) {
|
|
|
1385
1385
|
`Skills: project=${skills.project.ok ? 'ok' : `missing ${skills.project.missing.length}`} global=${skills.global.ok ? 'ok' : `missing ${skills.global.missing.length}`}`, '',
|
|
1386
1386
|
'Setup:', ' sks bootstrap', ' sks deps check', ' sks codex-app check', ' sks warp check', '',
|
|
1387
1387
|
'Generated files:', ' .codex/config.toml', ' .codex/hooks.json', ' .agents/skills/', ' .codex/agents/', ' .codex/SNEAKOSCOPE.md', ' AGENTS.md', '',
|
|
1388
|
+
'Git ignore:', ' default setup writes .gitignore entries for .sneakoscope/, .codex/, .agents/, AGENTS.md', ' --local-only writes those patterns to .git/info/exclude instead', '',
|
|
1388
1389
|
'Prompt routes:', formatDollarCommandsCompact(' ')
|
|
1389
1390
|
].join('\n'));
|
|
1390
1391
|
}
|
|
@@ -1564,6 +1565,7 @@ async function setup(args) {
|
|
|
1564
1565
|
console.log(`Hooks: ${path.relative(root, hooksPath)}`);
|
|
1565
1566
|
console.log(`Version: ${versioningInfo.enabled ? (versioningInfo.hook_installed ? 'auto-bump enabled' : 'auto-bump hook missing') : 'not enabled'}${versioningInfo.package_version ? ` (${versioningInfo.package_version})` : ''}`);
|
|
1566
1567
|
if (localOnly) console.log('Git: local-only (.git/info/exclude; user AGENTS preserved, SKS managed block refreshed)');
|
|
1568
|
+
else console.log('Git: .gitignore ignores SKS generated files');
|
|
1567
1569
|
console.log(`Codex App: .codex/config.toml, .codex/hooks.json, .agents/skills, .codex/agents, .codex/SNEAKOSCOPE.md`);
|
|
1568
1570
|
console.log(`Global $: ${globalSkills.status === 'installed' ? 'ok' : globalSkills.status} ${globalSkills.root || ''}`.trimEnd());
|
|
1569
1571
|
console.log(`App tools: ${appRuntime.ok ? 'ok' : 'needs setup'} Codex App=${appRuntime.app.installed ? 'ok' : 'missing'} Browser Use=${appRuntime.mcp.has_browser_use ? 'ok' : 'missing'} Computer Use=${appRuntime.mcp.has_computer_use ? 'ok' : 'missing'}`);
|
|
@@ -1759,6 +1761,7 @@ async function init(args) {
|
|
|
1759
1761
|
console.log(`Initialized ㅅㅋㅅ in ${root}`);
|
|
1760
1762
|
console.log(`Install scope: ${installScope} (${sksCommandPrefix(installScope, { globalCommand })})`);
|
|
1761
1763
|
if (localOnly) console.log('Git mode: local-only (.git/info/exclude)');
|
|
1764
|
+
else console.log('Git mode: shared .gitignore');
|
|
1762
1765
|
for (const x of res.created) console.log(`- ${x}`);
|
|
1763
1766
|
}
|
|
1764
1767
|
|
|
@@ -1921,6 +1924,24 @@ async function selftest() {
|
|
|
1921
1924
|
if (trippedStop) throw new Error('selftest failed: compliance loop guard did not terminally trip');
|
|
1922
1925
|
const loopBlocker = await readJson(path.join(loopMission.dir, 'hard-blocker.json'), null);
|
|
1923
1926
|
if (loopBlocker?.reason !== 'compliance_loop_guard_tripped') throw new Error('selftest failed: compliance loop guard did not write hard blocker');
|
|
1927
|
+
await setCurrent(tmp, loopState);
|
|
1928
|
+
const dfixPromptHook = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'hook', 'user-prompt-submit'], {
|
|
1929
|
+
cwd: tmp,
|
|
1930
|
+
input: JSON.stringify({ cwd: tmp, prompt: '$DFix Change the CTA label only' }),
|
|
1931
|
+
timeoutMs: 15000,
|
|
1932
|
+
maxOutputBytes: 64 * 1024
|
|
1933
|
+
});
|
|
1934
|
+
if (dfixPromptHook.code !== 0) throw new Error(`selftest failed: DFix prompt hook exited ${dfixPromptHook.code}: ${dfixPromptHook.stderr}`);
|
|
1935
|
+
const dfixStopHook = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'hook', 'stop'], {
|
|
1936
|
+
cwd: tmp,
|
|
1937
|
+
input: JSON.stringify({ cwd: tmp, last_assistant_message: 'DFix 완료 요약: CTA 라벨만 변경했습니다. 검증: 대상 파일 확인 통과. 남은 문제: 없음.' }),
|
|
1938
|
+
timeoutMs: 15000,
|
|
1939
|
+
maxOutputBytes: 64 * 1024
|
|
1940
|
+
});
|
|
1941
|
+
if (dfixStopHook.code !== 0) throw new Error(`selftest failed: DFix stop hook exited ${dfixStopHook.code}: ${dfixStopHook.stderr}`);
|
|
1942
|
+
const dfixStop = JSON.parse(dfixStopHook.stdout || '{}');
|
|
1943
|
+
if (dfixStop.decision === 'block' || dfixStop.continue === false) throw new Error(`selftest failed: DFix stop hook was blocked: ${dfixStopHook.stdout}`);
|
|
1944
|
+
if (!String(dfixStop.systemMessage || '').includes('DFix ultralight finalization accepted')) throw new Error('selftest failed: DFix stop hook did not use the ultralight finalization bypass');
|
|
1924
1945
|
await writeJsonAtomic(path.join(loopMission.dir, 'team-roster.json'), { schema_version: 1, mission_id: loopMission.id, confirmed: true });
|
|
1925
1946
|
await writeJsonAtomic(path.join(loopMission.dir, 'team-session-cleanup.json'), { schema_version: 1, passed: true, all_sessions_closed: true, outstanding_sessions: 0, live_transcript_finalized: true });
|
|
1926
1947
|
await writeJsonAtomic(path.join(loopMission.dir, 'team-gate.json'), { passed: true, team_roster_confirmed: true, analysis_artifact: true, triwiki_refreshed: true, triwiki_validated: true, consensus_artifact: true, implementation_team_fresh: true, review_artifact: true, integration_evidence: true, session_cleanup: true });
|
|
@@ -1967,9 +1988,11 @@ async function selftest() {
|
|
|
1967
1988
|
await writeJsonAtomic(path.join(postinstallBootstrapTmp, 'package.json'), { name: 'postinstall-bootstrap-smoke', version: '0.0.0' });
|
|
1968
1989
|
const postinstallBootstrap = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'postinstall'], { cwd: postinstallBootstrapTmp, input: 'y\n', env: { INIT_CWD: postinstallBootstrapTmp, HOME: path.join(postinstallBootstrapTmp, 'home'), SKS_SKIP_POSTINSTALL_SHIM: '1', SKS_SKIP_POSTINSTALL_CONTEXT7: '1', SKS_SKIP_POSTINSTALL_GLOBAL_SKILLS: '1', SKS_SKIP_CLI_TOOLS: '1', SKS_POSTINSTALL_PROMPT: '1' }, timeoutMs: 30000, maxOutputBytes: 256 * 1024 });
|
|
1969
1990
|
if (postinstallBootstrap.code !== 0 || !String(postinstallBootstrap.stdout || '').includes('SKS Ready')) throw new Error(`selftest failed: approved postinstall bootstrap did not run: ${postinstallBootstrap.stderr}`);
|
|
1970
|
-
for (const rel of ['.agents/skills/team/SKILL.md', '.codex/config.toml', '.codex/hooks.json', '.sneakoscope/harness-guard.json', '.codex/SNEAKOSCOPE.md', 'AGENTS.md']) {
|
|
1991
|
+
for (const rel of ['.agents/skills/team/SKILL.md', '.codex/config.toml', '.codex/hooks.json', '.sneakoscope/harness-guard.json', '.codex/SNEAKOSCOPE.md', 'AGENTS.md', '.gitignore']) {
|
|
1971
1992
|
if (!(await exists(path.join(postinstallBootstrapTmp, rel)))) throw new Error(`selftest failed: bootstrap did not create ${rel}`);
|
|
1972
1993
|
}
|
|
1994
|
+
const postinstallBootstrapGitignore = await safeReadText(path.join(postinstallBootstrapTmp, '.gitignore'));
|
|
1995
|
+
if (!postinstallBootstrapGitignore.includes('.sneakoscope/') || !postinstallBootstrapGitignore.includes('.codex/') || !postinstallBootstrapGitignore.includes('.agents/') || !postinstallBootstrapGitignore.includes('AGENTS.md')) throw new Error('selftest failed: bootstrap did not ignore SKS generated files');
|
|
1973
1996
|
const bootstrapJsonTmp = tmpdir();
|
|
1974
1997
|
await writeJsonAtomic(path.join(bootstrapJsonTmp, 'package.json'), { name: 'bootstrap-json-smoke', version: '0.0.0' });
|
|
1975
1998
|
const bootstrapJson = await runProcess(process.execPath, [path.join(packageRoot(), 'bin', 'sks.mjs'), 'bootstrap', '--json'], { cwd: bootstrapJsonTmp, env: { HOME: path.join(bootstrapJsonTmp, 'home'), SKS_SKIP_POSTINSTALL_GLOBAL_SKILLS: '1', SKS_SKIP_CLI_TOOLS: '1' }, timeoutMs: 30000, maxOutputBytes: 256 * 1024 });
|
|
@@ -2090,10 +2113,19 @@ async function selftest() {
|
|
|
2090
2113
|
await initProject(localOnlyTmp, { localOnly: true });
|
|
2091
2114
|
const localExclude = await safeReadText(path.join(localOnlyTmp, '.git', 'info', 'exclude'));
|
|
2092
2115
|
if (!localExclude.includes('.codex/') || !localExclude.includes('AGENTS.md')) throw new Error('selftest failed: local-only git excludes missing');
|
|
2116
|
+
if (await exists(path.join(localOnlyTmp, '.gitignore'))) throw new Error('selftest failed: local-only wrote shared .gitignore');
|
|
2093
2117
|
const localAgents = await safeReadText(path.join(localOnlyTmp, 'AGENTS.md'));
|
|
2094
2118
|
if (localAgents.trim() !== 'existing local rules') throw new Error('selftest failed: local-only modified existing AGENTS.md');
|
|
2095
2119
|
const localManifest = await readJson(path.join(localOnlyTmp, '.sneakoscope', 'manifest.json'));
|
|
2096
2120
|
if (!localManifest.git?.local_only) throw new Error('selftest failed: local-only manifest missing');
|
|
2121
|
+
const gitignoreTmp = tmpdir();
|
|
2122
|
+
await writeTextAtomic(path.join(gitignoreTmp, '.gitignore'), 'node_modules/\n.sneakoscope/\n');
|
|
2123
|
+
await initProject(gitignoreTmp, {});
|
|
2124
|
+
const gitignoreText = await safeReadText(path.join(gitignoreTmp, '.gitignore'));
|
|
2125
|
+
if (!gitignoreText.includes('node_modules/') || !gitignoreText.includes('# BEGIN Sneakoscope Codex generated files') || !gitignoreText.includes('.codex/') || !gitignoreText.includes('.agents/') || !gitignoreText.includes('AGENTS.md')) throw new Error('selftest failed: shared .gitignore did not preserve existing entries and add SKS patterns');
|
|
2126
|
+
await initProject(gitignoreTmp, {});
|
|
2127
|
+
const gitignoreTextSecond = await safeReadText(path.join(gitignoreTmp, '.gitignore'));
|
|
2128
|
+
if ((gitignoreTextSecond.match(/BEGIN Sneakoscope Codex generated files/g) || []).length !== 1) throw new Error('selftest failed: shared .gitignore managed block duplicated');
|
|
2097
2129
|
const managedAgentsTmp = tmpdir();
|
|
2098
2130
|
await ensureDir(path.join(managedAgentsTmp, '.git'));
|
|
2099
2131
|
await writeTextAtomic(path.join(managedAgentsTmp, 'AGENTS.md'), '<!-- BEGIN Sneakoscope Codex GX MANAGED BLOCK -->\nold managed rules\n<!-- END Sneakoscope Codex GX MANAGED BLOCK -->\n');
|
|
@@ -89,6 +89,8 @@ Local-only install artifacts:
|
|
|
89
89
|
# writes generated SKS files but excludes .sneakoscope/, .codex/, .agents/, AGENTS.md through .git/info/exclude
|
|
90
90
|
# user-owned AGENTS.md is preserved; an existing SKS managed block is refreshed
|
|
91
91
|
|
|
92
|
+
Default project setup writes the same SKS generated-file patterns into the project .gitignore.
|
|
93
|
+
|
|
92
94
|
GitHub install for unreleased commits:
|
|
93
95
|
npm i -g git+${REPOSITORY_URL}
|
|
94
96
|
sks bootstrap
|
package/src/core/fsx.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import os from 'node:os';
|
|
|
5
5
|
import crypto from 'node:crypto';
|
|
6
6
|
import { spawn } from 'node:child_process';
|
|
7
7
|
|
|
8
|
-
export const PACKAGE_VERSION = '0.6.
|
|
8
|
+
export const PACKAGE_VERSION = '0.6.90';
|
|
9
9
|
export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
|
|
10
10
|
export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
|
|
11
11
|
|
|
@@ -12,9 +12,11 @@ const TEAM_DIGEST_MESSAGE_CHARS = 180;
|
|
|
12
12
|
const TEAM_DIGEST_CONTEXT_CHARS = 1600;
|
|
13
13
|
const TEAM_DIGEST_SYSTEM_CHARS = 260;
|
|
14
14
|
const STOP_REPEAT_GUARD_ARTIFACT = 'stop-hook-repeat-guard.json';
|
|
15
|
+
const LIGHT_ROUTE_STOP_ARTIFACT = 'light-route-stop.json';
|
|
15
16
|
const STOP_REPEAT_GUARD_WINDOW_MS = 10 * 60 * 1000;
|
|
16
17
|
const STOP_REPEAT_GUARD_MAX_ENTRIES = 25;
|
|
17
18
|
const DEFAULT_STOP_REPEAT_GUARD_LIMIT = 2;
|
|
19
|
+
const LIGHT_ROUTE_STOP_WINDOW_MS = 10 * 60 * 1000;
|
|
18
20
|
|
|
19
21
|
async function loadHookPayload() {
|
|
20
22
|
const raw = await readStdin();
|
|
@@ -111,6 +113,7 @@ async function hookUserPrompt(root, state, payload, noQuestion) {
|
|
|
111
113
|
const command = dollarCommand(prompt);
|
|
112
114
|
const route = routePrompt(prompt);
|
|
113
115
|
const bypassActiveRoute = route?.id === 'DFix' || route?.id === 'Answer';
|
|
116
|
+
if (route?.id === 'DFix') await recordLightRouteStop(root, route, payload, prompt);
|
|
114
117
|
if (isClarificationAwaiting(state) && !looksLikeClarificationCancel(prompt)) {
|
|
115
118
|
const activeContext = await activeRouteContext(root, state);
|
|
116
119
|
const teamDigest = await teamLiveDigest(root, state);
|
|
@@ -222,6 +225,12 @@ async function hookPermission(root, state, payload, noQuestion) {
|
|
|
222
225
|
}
|
|
223
226
|
|
|
224
227
|
async function hookStop(root, state, payload, noQuestion) {
|
|
228
|
+
if (!noQuestion && await consumeLightRouteStop(root, payload)) {
|
|
229
|
+
return {
|
|
230
|
+
continue: true,
|
|
231
|
+
systemMessage: 'SKS: DFix ultralight finalization accepted; full-route Honest Mode loopback is not required.'
|
|
232
|
+
};
|
|
233
|
+
}
|
|
225
234
|
const routeDecision = await evaluateStop(root, state, payload, { noQuestion });
|
|
226
235
|
if (routeDecision) return routeDecision;
|
|
227
236
|
if (!noQuestion) {
|
|
@@ -258,6 +267,46 @@ async function hookStop(root, state, payload, noQuestion) {
|
|
|
258
267
|
};
|
|
259
268
|
}
|
|
260
269
|
|
|
270
|
+
async function recordLightRouteStop(root, route = {}, payload = {}, prompt = '') {
|
|
271
|
+
const now = nowIso();
|
|
272
|
+
const expires = new Date(Date.parse(now) + LIGHT_ROUTE_STOP_WINDOW_MS).toISOString();
|
|
273
|
+
const file = path.join(root, '.sneakoscope', 'state', LIGHT_ROUTE_STOP_ARTIFACT);
|
|
274
|
+
await writeJsonAtomic(file, {
|
|
275
|
+
schema_version: 1,
|
|
276
|
+
route: route.id || null,
|
|
277
|
+
route_command: route.command || null,
|
|
278
|
+
mode: route.mode || null,
|
|
279
|
+
conversation_id: conversationId(payload),
|
|
280
|
+
prompt_hash: sha256(String(prompt || '')).slice(0, 16),
|
|
281
|
+
created_at: now,
|
|
282
|
+
expires_at: expires,
|
|
283
|
+
pending_stop_bypass: true,
|
|
284
|
+
stop_policy: 'dfix_ultralight_bypasses_full_route_honest_mode'
|
|
285
|
+
}).catch(() => null);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async function consumeLightRouteStop(root, payload = {}) {
|
|
289
|
+
const file = path.join(root, '.sneakoscope', 'state', LIGHT_ROUTE_STOP_ARTIFACT);
|
|
290
|
+
const record = await readJson(file, null).catch(() => null);
|
|
291
|
+
if (!record?.pending_stop_bypass) return false;
|
|
292
|
+
if (record.route !== 'DFix') return false;
|
|
293
|
+
const nowMs = Date.now();
|
|
294
|
+
const expiresMs = Date.parse(record.expires_at || '');
|
|
295
|
+
if (!Number.isFinite(expiresMs) || expiresMs < nowMs) return false;
|
|
296
|
+
const currentConversation = conversationId(payload);
|
|
297
|
+
if (record.conversation_id && explicitConversationId(payload) && record.conversation_id !== currentConversation) return false;
|
|
298
|
+
await writeJsonAtomic(file, {
|
|
299
|
+
...record,
|
|
300
|
+
pending_stop_bypass: false,
|
|
301
|
+
consumed_at: nowIso()
|
|
302
|
+
}).catch(() => null);
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function explicitConversationId(payload = {}) {
|
|
307
|
+
return payload.conversation_id || payload.thread_id || payload.session_id || payload.chat_id || null;
|
|
308
|
+
}
|
|
309
|
+
|
|
261
310
|
async function finalizationRepeatDecision(root, state = {}, payload = {}, reason = '', kind = 'finalization') {
|
|
262
311
|
const now = nowIso();
|
|
263
312
|
const guardPath = path.join(root, '.sneakoscope', 'state', STOP_REPEAT_GUARD_ARTIFACT);
|
package/src/core/init.mjs
CHANGED
|
@@ -9,6 +9,8 @@ import { installVersionGitHook } from './version-manager.mjs';
|
|
|
9
9
|
import { CODEX_COMPUTER_USE_ONLY_POLICY, DOLLAR_COMMANDS, DOLLAR_COMMAND_ALIASES, DOLLAR_SKILL_NAMES, FROM_CHAT_IMG_CHECKLIST_ARTIFACT, FROM_CHAT_IMG_COVERAGE_ARTIFACT, FROM_CHAT_IMG_QA_LOOP_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_SESSIONS, RECOMMENDED_MCP_SERVERS, RECOMMENDED_SKILLS, chatCaptureIntakeText, context7ConfigToml, stackCurrentDocsPolicyText, triwikiContextTracking, triwikiContextTrackingText, triwikiStagePolicyText } from './routes.mjs';
|
|
10
10
|
|
|
11
11
|
const REFLECTION_MEMORY_PATH = '.sneakoscope/memory/q2_facts/post-route-reflection.md';
|
|
12
|
+
const SKS_GENERATED_GIT_PATTERNS = ['.sneakoscope/', '.codex/', '.agents/', 'AGENTS.md'];
|
|
13
|
+
|
|
12
14
|
function reflectionInstructionText(commandPrefix = 'sks') {
|
|
13
15
|
return `Post-route reflection: full routes load \`reflection\` after work/tests and before final; DFix/Answer/Help/Wiki/SKS discovery are exempt. Write reflection.md; record only real misses/gaps, or no_issue_acknowledged. For lessons, append TriWiki claim rows to ${REFLECTION_MEMORY_PATH}. Run "${commandPrefix} wiki refresh" or pack, validate, then pass reflection-gate.json.`;
|
|
14
16
|
}
|
|
@@ -107,7 +109,9 @@ export async function initProject(root, opts = {}) {
|
|
|
107
109
|
];
|
|
108
110
|
for (const d of dirs) await ensureDir(path.join(root, d));
|
|
109
111
|
const localExclude = localOnly ? await ensureLocalOnlyGitExclude(root) : null;
|
|
112
|
+
const sharedIgnore = localOnly ? null : await ensureSharedGitIgnore(root);
|
|
110
113
|
if (localExclude?.path) created.push(`${path.relative(root, localExclude.path)} local-only excludes`);
|
|
114
|
+
if (sharedIgnore?.changed) created.push(`${path.relative(root, sharedIgnore.path)} SKS generated files ignore`);
|
|
111
115
|
|
|
112
116
|
await writeJsonAtomic(path.join(sine, 'manifest.json'), {
|
|
113
117
|
package: 'sneakoscope',
|
|
@@ -165,6 +169,8 @@ export async function initProject(root, opts = {}) {
|
|
|
165
169
|
},
|
|
166
170
|
git: {
|
|
167
171
|
local_only: localOnly,
|
|
172
|
+
ignore_path: sharedIgnore?.path ? path.relative(root, sharedIgnore.path) : null,
|
|
173
|
+
ignored_patterns: sharedIgnore?.patterns || [],
|
|
168
174
|
exclude_path: localExclude?.path ? path.relative(root, localExclude.path) : null,
|
|
169
175
|
excluded_patterns: localExclude?.patterns || [],
|
|
170
176
|
versioning: {
|
|
@@ -198,6 +204,8 @@ export async function initProject(root, opts = {}) {
|
|
|
198
204
|
git: {
|
|
199
205
|
...(policy.git || {}),
|
|
200
206
|
local_only: localOnly || Boolean(policy.git?.local_only),
|
|
207
|
+
ignore_path: sharedIgnore?.path ? path.relative(root, sharedIgnore.path) : policy.git?.ignore_path || null,
|
|
208
|
+
ignored_patterns: sharedIgnore?.patterns || policy.git?.ignored_patterns || [],
|
|
201
209
|
exclude_path: localExclude?.path ? path.relative(root, localExclude.path) : policy.git?.exclude_path || null,
|
|
202
210
|
excluded_patterns: localExclude?.patterns || policy.git?.excluded_patterns || [],
|
|
203
211
|
versioning: {
|
|
@@ -267,6 +275,8 @@ export async function initProject(root, opts = {}) {
|
|
|
267
275
|
installation: installPolicy(scope, commandPrefix),
|
|
268
276
|
git: {
|
|
269
277
|
local_only: localOnly,
|
|
278
|
+
ignore_path: sharedIgnore?.path ? path.relative(root, sharedIgnore.path) : null,
|
|
279
|
+
ignored_patterns: sharedIgnore?.patterns || [],
|
|
270
280
|
exclude_path: localExclude?.path ? path.relative(root, localExclude.path) : null,
|
|
271
281
|
excluded_patterns: localExclude?.patterns || [],
|
|
272
282
|
versioning: {
|
|
@@ -445,10 +455,33 @@ policy = "Deny destructive database operations, credential exfiltration, persist
|
|
|
445
455
|
return { created };
|
|
446
456
|
}
|
|
447
457
|
|
|
458
|
+
async function ensureSharedGitIgnore(root) {
|
|
459
|
+
const patterns = SKS_GENERATED_GIT_PATTERNS;
|
|
460
|
+
const ignorePath = path.join(root, '.gitignore');
|
|
461
|
+
const markerStart = '# BEGIN Sneakoscope Codex generated files';
|
|
462
|
+
const markerEnd = '# END Sneakoscope Codex generated files';
|
|
463
|
+
const managedBlock = `${markerStart}\n${patterns.join('\n')}\n${markerEnd}\n`;
|
|
464
|
+
const current = await readText(ignorePath, '');
|
|
465
|
+
if (current.includes(markerStart)) {
|
|
466
|
+
const re = new RegExp(`${escapeRegExp(markerStart)}[\\s\\S]*?${escapeRegExp(markerEnd)}\\n?`);
|
|
467
|
+
const next = current.replace(re, managedBlock);
|
|
468
|
+
if (next !== current) await writeTextAtomic(ignorePath, next.endsWith('\n') ? next : `${next}\n`);
|
|
469
|
+
return { path: ignorePath, patterns, changed: next !== current };
|
|
470
|
+
}
|
|
471
|
+
const existing = new Set(current.split(/\r?\n/).map((line) => line.trim()).filter(Boolean));
|
|
472
|
+
const missing = patterns.filter((pattern) => !existing.has(pattern));
|
|
473
|
+
if (!missing.length) return { path: ignorePath, patterns, changed: false };
|
|
474
|
+
const block = missing.length === patterns.length
|
|
475
|
+
? managedBlock
|
|
476
|
+
: `${markerStart}\n${missing.join('\n')}\n${markerEnd}\n`;
|
|
477
|
+
await writeTextAtomic(ignorePath, `${current.trimEnd()}${current.trim() ? '\n\n' : ''}${block}`);
|
|
478
|
+
return { path: ignorePath, patterns, changed: true };
|
|
479
|
+
}
|
|
480
|
+
|
|
448
481
|
async function ensureLocalOnlyGitExclude(root) {
|
|
449
482
|
const gitDir = await resolveGitDir(root);
|
|
450
483
|
if (!gitDir) return { path: null, patterns: [] };
|
|
451
|
-
const patterns =
|
|
484
|
+
const patterns = SKS_GENERATED_GIT_PATTERNS;
|
|
452
485
|
const excludePath = path.join(gitDir, 'info', 'exclude');
|
|
453
486
|
await ensureDir(path.dirname(excludePath));
|
|
454
487
|
const markerStart = '# Sneakoscope Codex local-only generated files';
|
|
@@ -494,7 +527,7 @@ function codexAppQuickReference(scope, commandPrefix) {
|
|
|
494
527
|
|
|
495
528
|
export async function installSkills(root) {
|
|
496
529
|
const skills = {
|
|
497
|
-
'dfix': `---\nname: dfix\ndescription: Ultralight fast design/content fix mode for $DFix or $dfix requests and inferred simple edits such as text color, copy, labels, spacing, or translation.\n---\n\nUse for tiny copy/color/label/spacing/translation edits. List exact micro-edits, inspect only needed files, apply only those edits, and run cheap verification. Bypass broad SKS routing, Goal, Research, eval, and
|
|
530
|
+
'dfix': `---\nname: dfix\ndescription: Ultralight fast design/content fix mode for $DFix or $dfix requests and inferred simple edits such as text color, copy, labels, spacing, or translation.\n---\n\nUse for tiny copy/color/label/spacing/translation edits. List exact micro-edits, inspect only needed files, apply only those edits, and run cheap verification. Bypass broad SKS routing, Goal, Research, eval, redesign, and repeated full-route Honest Mode loops. Read \`design.md\` for UI work when present; use imagegen for image/logo/raster assets.\n`,
|
|
498
531
|
'answer': `---\nname: answer\ndescription: Answer-only research route for ordinary questions that should not start implementation.\n---\n\nUse for explanations, comparisons, status, facts, source-backed research, or docs guidance. Use repo/TriWiki first for project-local facts; hydrate low-trust claims from source. Browse or use Context7 for current external package/API/framework/MCP docs. End with a concise answer summary plus Honest Mode; do not create missions, subagents, or file edits.\n`,
|
|
499
532
|
'sks': `---\nname: sks\ndescription: General Sneakoscope Codex command route for $SKS or $sks usage, setup, status, and workflow help.\n---\n\nUse local SKS commands: bootstrap, deps, commands, quickstart, codex-app, context7, guard, conflicts, reasoning, wiki, pipeline. Promote code-changing work to Team unless Answer/DFix/Help/Wiki/safety route fits. Surface route/guard/scope, use TriWiki, do not edit installed harness files outside this engine repo, and require human-approved conflict cleanup.\n`,
|
|
500
533
|
'wiki': `---\nname: wiki\ndescription: Dollar-command route for $Wiki TriWiki refresh, pack, validate, and prune commands.\n---\n\nUse for $Wiki or Korean wiki-refresh requests. Refresh/update/갱신: run sks wiki refresh, then validate .sneakoscope/wiki/context-pack.json. Pack: run sks wiki pack, then validate. Prune/clean/정리: use sks wiki refresh --prune, or sks wiki prune --dry-run for inspection. Report claims, anchors, trust, attention.use_first/hydrate_first, validation, and blockers. Do not start ambiguity-gated implementation, subagents, or unrelated work.\n`,
|
package/src/core/pipeline.mjs
CHANGED
|
@@ -81,7 +81,7 @@ export function dfixQuickContext(prompt, route = routePrompt(prompt)) {
|
|
|
81
81
|
'2. Inspect only the files needed to locate that target.',
|
|
82
82
|
'3. Apply only the listed design/content edit; for UI/UX micro-edits read design.md when present, and use imagegen for any image/logo/raster asset.',
|
|
83
83
|
'4. Run only cheap verification when useful, such as syntax check, focused test, or local render smoke.',
|
|
84
|
-
'5. Final response: one short completion summary explaining what changed, plus verification or the exact blocker.'
|
|
84
|
+
'5. Final response: one short DFix completion summary explaining what changed, plus cheap verification or the exact blocker. Do not enter repeated full-route Honest Mode loops.'
|
|
85
85
|
].join('\n');
|
|
86
86
|
}
|
|
87
87
|
|