sneakoscope 0.6.6 → 0.6.8

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 CHANGED
@@ -28,6 +28,14 @@ sks setup
28
28
  sks doctor --fix
29
29
  ```
30
30
 
31
+ Use local-only setup when the generated SKS files must never appear in git status:
32
+
33
+ ```bash
34
+ sks setup --local-only
35
+ ```
36
+
37
+ This writes repo-local excludes to `.git/info/exclude` for `.sneakoscope/`, `.codex/`, `.agents/`, and `AGENTS.md`. If `AGENTS.md` already exists, local-only setup does not modify it.
38
+
31
39
  The npm package name is `sneakoscope`; the command is branded as SKS and exposed as lowercase `sks` for shell portability. The package also exposes a `sneakoscope` command alias, so `sks setup` and `sneakoscope setup` are equivalent.
32
40
  Global installation is the default and recommended setup. During `sks setup` or `sks init`, SKS resolves the global binary when possible and writes that absolute path into `.codex/hooks.json`, which avoids PATH issues in GUI or hook execution environments. For a project-only install, use `npm i -D sneakoscope` and initialize hooks with `npx sks setup --install-scope project`; this writes hook commands that call the local `node_modules/sneakoscope` binary.
33
41
 
@@ -378,10 +386,10 @@ sks aliases
378
386
  sks --help
379
387
  sneakoscope --help
380
388
 
381
- sks setup [--install-scope global|project] [--force] [--json]
389
+ sks setup [--install-scope global|project] [--local-only] [--force] [--json]
382
390
  sks fix-path [--install-scope global|project] [--json]
383
- sks doctor [--fix] [--json] [--install-scope global|project]
384
- sks init [--force] [--install-scope global|project]
391
+ sks doctor [--fix] [--local-only] [--json] [--install-scope global|project]
392
+ sks init [--force] [--local-only] [--install-scope global|project]
385
393
  sks selftest [--mock]
386
394
 
387
395
  sks ralph prepare "task"
@@ -589,7 +597,6 @@ sks hproof check latest
589
597
  .codex/skills/ Codex App local project skills
590
598
  .codex/agents/ Codex App custom agents for Team mode
591
599
  .codex/SNEAKOSCOPE.md Codex App quick reference
592
- .agents/skills/ Sneakoscope Codex helper skills
593
600
  AGENTS.md managed repository rules block
594
601
  ```
595
602
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sneakoscope",
3
3
  "displayName": "Sneakoscope Codex",
4
- "version": "0.6.6",
4
+ "version": "0.6.8",
5
5
  "description": "Sneakoscope Codex: update-aware, database-safe Codex CLI harness with multi-agent Team orchestration, Ralph no-question execution, autoresearch-style loops, and H-Proof gates.",
6
6
  "type": "module",
7
7
  "homepage": "https://github.com/mandarange/Sneakoscope-Codex#readme",
package/src/cli/main.mjs CHANGED
@@ -50,10 +50,10 @@ const COMMAND_CATALOG = [
50
50
  { name: 'dollar-commands', usage: 'sks dollar-commands [--json]', description: 'List Codex App $ commands such as $DF.' },
51
51
  { name: 'df', usage: 'sks df', description: 'Explain $DF fast design/content fix mode.' },
52
52
  { name: 'aliases', usage: 'sks aliases', description: 'Show command aliases and npm binary names.' },
53
- { name: 'setup', usage: 'sks setup [--install-scope global|project] [--force] [--json]', description: 'Initialize SKS state, Codex App files, hooks, skills, and rules.' },
53
+ { name: 'setup', usage: 'sks setup [--install-scope global|project] [--local-only] [--force] [--json]', description: 'Initialize SKS state, Codex App files, hooks, skills, and rules.' },
54
54
  { name: 'fix-path', usage: 'sks fix-path [--install-scope global|project] [--json]', description: 'Refresh hook commands with the resolved SKS binary path.' },
55
- { name: 'doctor', usage: 'sks doctor [--fix] [--json] [--install-scope global|project]', description: 'Check Node, Codex CLI, install scope, hooks, skills, DB guard, and Codex App files.' },
56
- { name: 'init', usage: 'sks init [--force] [--install-scope global|project]', description: 'Initialize the local SKS control surface.' },
55
+ { name: 'doctor', usage: 'sks doctor [--fix] [--local-only] [--json] [--install-scope global|project]', description: 'Check Node, Codex CLI, install scope, hooks, skills, DB guard, and Codex App files.' },
56
+ { name: 'init', usage: 'sks init [--force] [--local-only] [--install-scope global|project]', description: 'Initialize the local SKS control surface.' },
57
57
  { name: 'selftest', usage: 'sks selftest [--mock]', description: 'Run local smoke tests without calling a model.' },
58
58
  { name: 'ralph', usage: 'sks ralph prepare|answer|run|status ...', description: 'Run mandatory-clarification Ralph missions with a no-question execution loop.' },
59
59
  { name: 'research', usage: 'sks research prepare|run|status ...', description: 'Run frontier-style research missions with novelty and falsification gates.' },
@@ -134,10 +134,10 @@ Usage:
134
134
  sks dollar-commands [--json]
135
135
  sks df
136
136
  sks aliases
137
- sks setup [--install-scope global|project] [--force] [--json]
137
+ sks setup [--install-scope global|project] [--local-only] [--force] [--json]
138
138
  sks fix-path [--install-scope global|project] [--json]
139
- sks doctor [--fix] [--json] [--install-scope global|project]
140
- sks init [--install-scope global|project]
139
+ sks doctor [--fix] [--local-only] [--json] [--install-scope global|project]
140
+ sks init [--install-scope global|project] [--local-only]
141
141
  sks selftest [--mock]
142
142
  sks ralph prepare "task"
143
143
  sks ralph answer <mission-id|latest> <answers.json>
@@ -326,6 +326,11 @@ Project-only install:
326
326
  npm i -D sneakoscope
327
327
  npx sks setup --install-scope project
328
328
 
329
+ Local-only install artifacts:
330
+ sks setup --local-only
331
+ # writes generated SKS files but excludes .sneakoscope/, .codex/, .agents/, AGENTS.md through .git/info/exclude
332
+ # existing AGENTS.md is not modified in local-only mode
333
+
329
334
  GitHub install for unreleased commits:
330
335
  npm i -g git+${REPOSITORY_URL}
331
336
  `);
@@ -563,6 +568,11 @@ Project-only install:
563
568
  npm i -D sneakoscope
564
569
  npx sks setup --install-scope project
565
570
 
571
+ Local-only install artifacts:
572
+ sks setup --local-only
573
+ # excludes .sneakoscope/, .codex/, .agents/, AGENTS.md through .git/info/exclude
574
+ # existing AGENTS.md is not modified in local-only mode
575
+
566
576
  GitHub install for unreleased commits:
567
577
  npm i -g git+${REPOSITORY_URL}
568
578
  sks setup
@@ -750,8 +760,9 @@ Render and verify:
750
760
  async function setup(args) {
751
761
  const root = await projectRoot();
752
762
  const installScope = installScopeFromArgs(args);
763
+ const localOnly = flag(args, '--local-only');
753
764
  const globalCommand = await globalSksCommand();
754
- const res = await initProject(root, { force: flag(args, '--force'), installScope, globalCommand });
765
+ const res = await initProject(root, { force: flag(args, '--force'), installScope, globalCommand, localOnly });
755
766
  const install = await installStatus(root, installScope, { globalCommand });
756
767
  const hooksPath = path.join(root, '.codex', 'hooks.json');
757
768
  const result = {
@@ -767,6 +778,7 @@ async function setup(args) {
767
778
  agents_rules: path.join(root, 'AGENTS.md')
768
779
  },
769
780
  created: res.created,
781
+ local_only: localOnly,
770
782
  next: ['sks selftest --mock', 'sks doctor', 'sks commands']
771
783
  };
772
784
  if (flag(args, '--json')) return console.log(JSON.stringify(result, null, 2));
@@ -774,9 +786,10 @@ async function setup(args) {
774
786
  console.log(`Project: ${root}`);
775
787
  console.log(`Install: ${install.ok ? 'ok' : 'missing'} ${install.scope} (${install.command_prefix})`);
776
788
  console.log(`Hooks: ${path.relative(root, hooksPath)}`);
789
+ if (localOnly) console.log('Git: local-only (.git/info/exclude; existing AGENTS.md not modified)');
777
790
  console.log(`Codex App: .codex/config.toml, .codex/hooks.json, .codex/skills, .codex/agents, .codex/SNEAKOSCOPE.md`);
778
791
  console.log(`Prompt: default optimization pipeline, $DF fast design/content route`);
779
- console.log(`Skills: .codex/skills, .agents/skills`);
792
+ console.log(`Skills: .codex/skills`);
780
793
  console.log(`Next: sks selftest --mock; sks commands; sks dollar-commands`);
781
794
  if (!install.ok && install.scope === 'global') console.log('\nGlobal command missing. Run: npm i -g sneakoscope');
782
795
  if (!install.ok && install.scope === 'project') console.log('\nProject package missing. Run: npm i -D sneakoscope');
@@ -789,7 +802,7 @@ async function fixPath(args) {
789
802
  ? installScopeFromArgs(args)
790
803
  : normalizeInstallScope(manifest?.installation?.scope || 'global');
791
804
  const globalCommand = await globalSksCommand();
792
- await initProject(root, { installScope, globalCommand });
805
+ await initProject(root, { installScope, globalCommand, localOnly: flag(args, '--local-only') || Boolean(manifest?.git?.local_only) });
793
806
  const install = await installStatus(root, installScope, { globalCommand });
794
807
  const result = {
795
808
  root,
@@ -814,7 +827,8 @@ async function doctor(args) {
814
827
  : null;
815
828
  if (flag(args, '--fix')) {
816
829
  const fixScope = requestedScope || 'global';
817
- await initProject(root, { installScope: fixScope, globalCommand: await globalSksCommand() });
830
+ const existingManifest = await readJson(path.join(root, '.sneakoscope', 'manifest.json'), null);
831
+ await initProject(root, { installScope: fixScope, globalCommand: await globalSksCommand(), localOnly: flag(args, '--local-only') || Boolean(existingManifest?.git?.local_only) });
818
832
  }
819
833
  const codex = await getCodexInfo();
820
834
  const rust = await rustInfo();
@@ -840,7 +854,7 @@ async function doctor(args) {
840
854
  sneakoscope: { ok: await exists(path.join(root, '.sneakoscope')) },
841
855
  db_guard: { ok: dbPolicyExists && dbScan.ok, policy: dbPolicyExists ? await loadDbSafetyPolicy(root) : null, scan: dbScan },
842
856
  hooks: { ok: await exists(path.join(root, '.codex', 'hooks.json')) },
843
- skills: { ok: (await exists(path.join(root, '.codex', 'skills'))) && (await exists(path.join(root, '.agents', 'skills'))) },
857
+ skills: { ok: await exists(path.join(root, '.codex', 'skills')) },
844
858
  codex_app: {
845
859
  ...codexApp,
846
860
  ok: codexApp.config.ok && codexApp.hooks.ok && codexApp.skills.ok && codexApp.agents.ok && codexApp.quick_reference.ok && codexApp.agents_rules.ok
@@ -859,7 +873,7 @@ async function doctor(args) {
859
873
  console.log(`DB Guard: ${result.db_guard.ok ? 'ok' : 'blocked'} ${dbScan.findings?.length || 0} finding(s)`);
860
874
  console.log(`Hooks: ${result.hooks.ok ? 'ok' : 'missing .codex/hooks.json'}`);
861
875
  console.log(`Codex App: ${result.codex_app.ok ? 'ok' : 'missing app files'} .codex/config.toml .codex/hooks.json .codex/skills .codex/agents .codex/SNEAKOSCOPE.md`);
862
- console.log(`Skills: ${result.skills.ok ? 'ok' : 'missing .codex/skills or .agents/skills'}`);
876
+ console.log(`Skills: ${result.skills.ok ? 'ok' : 'missing .codex/skills'}`);
863
877
  console.log(`Package: ${result.package.human}`);
864
878
  console.log(`Storage: ${storage.total_human || '0 B'}`);
865
879
  console.log(`Ready: ${result.ready ? 'yes' : 'no'}`);
@@ -872,10 +886,12 @@ async function doctor(args) {
872
886
  async function init(args) {
873
887
  const root = await projectRoot();
874
888
  const installScope = installScopeFromArgs(args);
889
+ const localOnly = flag(args, '--local-only');
875
890
  const globalCommand = await globalSksCommand();
876
- const res = await initProject(root, { force: flag(args, '--force'), installScope, globalCommand });
891
+ const res = await initProject(root, { force: flag(args, '--force'), installScope, globalCommand, localOnly });
877
892
  console.log(`Initialized Sneakoscope Codex in ${root}`);
878
893
  console.log(`Install scope: ${installScope} (${sksCommandPrefix(installScope, { globalCommand })})`);
894
+ if (localOnly) console.log('Git mode: local-only (.git/info/exclude)');
879
895
  for (const x of res.created) console.log(`- ${x}`);
880
896
  }
881
897
 
@@ -1207,6 +1223,16 @@ async function selftest() {
1207
1223
  await initProject(projectScopeTmp, { installScope: 'project' });
1208
1224
  const projectHooks = await readJson(path.join(projectScopeTmp, '.codex', 'hooks.json'));
1209
1225
  if (projectHooks.hooks.PreToolUse[0].hooks[0].command !== 'node ./node_modules/sneakoscope/bin/sks.mjs hook pre-tool') throw new Error('selftest failed: project install hook command missing');
1226
+ const localOnlyTmp = tmpdir();
1227
+ await ensureDir(path.join(localOnlyTmp, '.git'));
1228
+ await writeTextAtomic(path.join(localOnlyTmp, 'AGENTS.md'), 'existing local rules\n');
1229
+ await initProject(localOnlyTmp, { localOnly: true });
1230
+ const localExclude = await safeReadText(path.join(localOnlyTmp, '.git', 'info', 'exclude'));
1231
+ if (!localExclude.includes('.codex/') || !localExclude.includes('AGENTS.md')) throw new Error('selftest failed: local-only git excludes missing');
1232
+ const localAgents = await safeReadText(path.join(localOnlyTmp, 'AGENTS.md'));
1233
+ if (localAgents.trim() !== 'existing local rules') throw new Error('selftest failed: local-only modified existing AGENTS.md');
1234
+ const localManifest = await readJson(path.join(localOnlyTmp, '.sneakoscope', 'manifest.json'));
1235
+ if (!localManifest.git?.local_only) throw new Error('selftest failed: local-only manifest missing');
1210
1236
  if (!isTransientNpmBinPath('/tmp/.npm/_npx/abc/node_modules/.bin/sks')) throw new Error('selftest failed: npx bin path not recognized as transient');
1211
1237
  if (!isTransientNpmBinPath('/tmp/.npm-cache/_cacache/tmp/git-cloneabc/bin/sks.mjs')) throw new Error('selftest failed: npm cache git clone path not recognized as transient');
1212
1238
  if (isTransientNpmBinPath('/usr/local/bin/sks')) throw new Error('selftest failed: stable global bin marked transient');
@@ -1226,8 +1252,8 @@ async function selftest() {
1226
1252
  if (oldSksBin === undefined) delete process.env.SKS_BIN;
1227
1253
  else process.env.SKS_BIN = oldSksBin;
1228
1254
  }
1229
- const researchSkillExists = await exists(path.join(tmp, '.agents', 'skills', 'research-discovery', 'SKILL.md'));
1230
- if (!researchSkillExists) throw new Error('selftest failed: research skill not installed');
1255
+ const legacySkillMirrorExists = await exists(path.join(tmp, '.agents', 'skills', 'research-discovery', 'SKILL.md'));
1256
+ if (legacySkillMirrorExists) throw new Error('selftest failed: legacy .agents/skills mirror still installed');
1231
1257
  const codexAppSkillExists = await exists(path.join(tmp, '.codex', 'skills', 'research-discovery', 'SKILL.md'));
1232
1258
  if (!codexAppSkillExists) throw new Error('selftest failed: Codex App skill not installed');
1233
1259
  const dfSkillExists = await exists(path.join(tmp, '.codex', 'skills', 'DF', 'SKILL.md'));
@@ -1413,7 +1439,7 @@ async function projectWikiClaims(root) {
1413
1439
  const claims = [
1414
1440
  ['wiki-hooks', '.codex/hooks.json routes UserPromptSubmit, tool, permission, and Stop events through SKS guards.', '.codex/hooks.json', 'code', 'high'],
1415
1441
  ['wiki-config', '.codex/config.toml enables Codex App profiles, multi-agent support, and Team agent limits.', '.codex/config.toml', 'code', 'high'],
1416
- ['wiki-skills', '.codex/skills and .agents/skills provide local routes for DF, Team, Ralph, Research, AutoResearch, DB, GX, wiki, and evaluation workflows.', '.codex/skills', 'code', 'medium'],
1442
+ ['wiki-skills', '.codex/skills provides local routes for DF, Team, Ralph, Research, AutoResearch, DB, GX, wiki, and evaluation workflows.', '.codex/skills', 'code', 'medium'],
1417
1443
  ['wiki-agents', '.codex/agents defines Team planning, implementation, DB safety, and QA reviewer roles.', '.codex/agents', 'code', 'medium'],
1418
1444
  ['wiki-policy', '.sneakoscope/policy.json stores update-check, honest-mode, retention, database, performance, and prompt-pipeline policy.', '.sneakoscope/policy.json', 'contract', 'high'],
1419
1445
  ['wiki-memory', '.sneakoscope/memory stores Q0 raw, Q1 evidence, Q2 facts, Q3 tags, and Q4 control bits for hydratable context.', '.sneakoscope/memory', 'wiki', 'high'],
@@ -38,7 +38,7 @@ export function defaultEvaluationScenario() {
38
38
  ['req-wiki-rgba', 'TriWiki stores RGBA-derived trigonometric wiki anchors so compressed context remains hydratable by id, hash, source, and coordinate.', 'code', 'high', 1.2],
39
39
  ['req-retention', 'runtime logs and mission artifacts are bounded through retention policy and sks gc.', 'code', 'medium', 0.85],
40
40
  ['req-selftest', 'selftest covers contract sealing, DB guard blocking, done-gate evaluation, GX render/validate/drift, snapshot, and retention report.', 'test', 'high', 1.1],
41
- ['req-skill', 'sks init installs local skills under .agents/skills so project workflows can trigger domain-specific guidance.', 'code', 'medium', 0.9],
41
+ ['req-skill', 'sks init installs one canonical local skill set under .codex/skills so project workflows can trigger domain-specific guidance without duplicate commands.', 'code', 'medium', 0.9],
42
42
  ['req-design', 'design artifact work should gather design context, build an HTML artifact, expose variations when useful, and verify rendered output.', 'code', 'medium', 0.8]
43
43
  ];
44
44
  const noise = [
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.6';
8
+ export const PACKAGE_VERSION = '0.6.8';
9
9
  export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
10
10
  export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
11
11
 
package/src/core/init.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  import path from 'node:path';
2
- import { ensureDir, readJson, writeJsonAtomic, writeTextAtomic, mergeManagedBlock, nowIso, PACKAGE_VERSION, exists } from './fsx.mjs';
2
+ import fsp from 'node:fs/promises';
3
+ import { ensureDir, readJson, readText, writeJsonAtomic, writeTextAtomic, mergeManagedBlock, nowIso, PACKAGE_VERSION, exists } from './fsx.mjs';
3
4
  import { DEFAULT_RETENTION_POLICY } from './retention.mjs';
4
5
  import { DEFAULT_DB_SAFETY_POLICY } from './db-safety.mjs';
5
6
 
@@ -98,12 +99,15 @@ A task is not done until relevant tests are run or justified, unsupported critic
98
99
  export async function initProject(root, opts = {}) {
99
100
  const created = [];
100
101
  const installScope = normalizeInstallScope(opts.installScope || 'global');
102
+ const localOnly = Boolean(opts.localOnly);
101
103
  const hookCommandPrefix = opts.hookCommandPrefix || sksCommandPrefix(installScope, { globalCommand: opts.globalCommand });
102
104
  const sine = path.join(root, '.sneakoscope');
103
105
  const dirs = [
104
- '.sneakoscope/state', '.sneakoscope/missions', '.sneakoscope/db', '.sneakoscope/bus', '.sneakoscope/hproof', '.sneakoscope/db', '.sneakoscope/wiki', '.sneakoscope/memory/q0_raw', '.sneakoscope/memory/q1_evidence', '.sneakoscope/memory/q2_facts', '.sneakoscope/memory/q3_tags', '.sneakoscope/memory/q4_bits', '.sneakoscope/gx/cartridges', '.sneakoscope/model/fingerprints', '.sneakoscope/genome/candidates', '.sneakoscope/trajectories/raw', '.sneakoscope/locks', '.sneakoscope/tmp', '.sneakoscope/arenas', '.sneakoscope/reports', '.codex', '.codex/skills', '.codex/agents', '.agents/skills'
106
+ '.sneakoscope/state', '.sneakoscope/missions', '.sneakoscope/db', '.sneakoscope/bus', '.sneakoscope/hproof', '.sneakoscope/db', '.sneakoscope/wiki', '.sneakoscope/memory/q0_raw', '.sneakoscope/memory/q1_evidence', '.sneakoscope/memory/q2_facts', '.sneakoscope/memory/q3_tags', '.sneakoscope/memory/q4_bits', '.sneakoscope/gx/cartridges', '.sneakoscope/model/fingerprints', '.sneakoscope/genome/candidates', '.sneakoscope/trajectories/raw', '.sneakoscope/locks', '.sneakoscope/tmp', '.sneakoscope/arenas', '.sneakoscope/reports', '.codex', '.codex/skills', '.codex/agents'
105
107
  ];
106
108
  for (const d of dirs) await ensureDir(path.join(root, d));
109
+ const localExclude = localOnly ? await ensureLocalOnlyGitExclude(root) : null;
110
+ if (localExclude?.path) created.push(`${path.relative(root, localExclude.path)} local-only excludes`);
107
111
 
108
112
  await writeJsonAtomic(path.join(sine, 'manifest.json'), {
109
113
  package: 'sneakoscope',
@@ -138,6 +142,11 @@ export async function initProject(root, opts = {}) {
138
142
  channel_map: { r: 'domainAngle', g: 'layerRadius', b: 'phase', a: 'concentration' },
139
143
  continuity_model: 'selected_text_plus_hydratable_rgba_trig_anchors'
140
144
  },
145
+ git: {
146
+ local_only: localOnly,
147
+ exclude_path: localExclude?.path ? path.relative(root, localExclude.path) : null,
148
+ excluded_patterns: localExclude?.patterns || []
149
+ },
141
150
  database_safety: 'destructive_db_operations_denied_always',
142
151
  gx_renderer: 'deterministic_svg_html'
143
152
  });
@@ -157,7 +166,13 @@ export async function initProject(root, opts = {}) {
157
166
  const policy = await readJson(policyPath, {});
158
167
  await writeJsonAtomic(policyPath, {
159
168
  ...policy,
160
- installation: installPolicy(installScope, hookCommandPrefix)
169
+ installation: installPolicy(installScope, hookCommandPrefix),
170
+ git: {
171
+ ...(policy.git || {}),
172
+ local_only: localOnly || Boolean(policy.git?.local_only),
173
+ exclude_path: localExclude?.path ? path.relative(root, localExclude.path) : policy.git?.exclude_path || null,
174
+ excluded_patterns: localExclude?.patterns || policy.git?.excluded_patterns || []
175
+ }
161
176
  });
162
177
  }
163
178
 
@@ -165,6 +180,11 @@ export async function initProject(root, opts = {}) {
165
180
  return {
166
181
  schema_version: 1,
167
182
  installation: installPolicy(scope, commandPrefix),
183
+ git: {
184
+ local_only: localOnly,
185
+ exclude_path: localExclude?.path ? path.relative(root, localExclude.path) : null,
186
+ excluded_patterns: localExclude?.patterns || []
187
+ },
168
188
  retention: DEFAULT_RETENTION_POLICY,
169
189
  update_check: {
170
190
  enabled: true,
@@ -244,8 +264,13 @@ export async function initProject(root, opts = {}) {
244
264
  created.push('.sneakoscope/state/current.json');
245
265
  }
246
266
 
247
- await mergeManagedBlock(path.join(root, 'AGENTS.md'), 'Sneakoscope Codex GX MANAGED BLOCK', AGENTS_BLOCK);
248
- created.push('AGENTS.md managed block');
267
+ const agentsMdPath = path.join(root, 'AGENTS.md');
268
+ if (localOnly && await exists(agentsMdPath)) {
269
+ created.push('AGENTS.md skipped (local-only existing file)');
270
+ } else {
271
+ await mergeManagedBlock(agentsMdPath, 'Sneakoscope Codex GX MANAGED BLOCK', AGENTS_BLOCK);
272
+ created.push('AGENTS.md managed block');
273
+ }
249
274
 
250
275
  await writeTextAtomic(path.join(root, '.codex', 'config.toml'), `[features]\ncodex_hooks = true\nmulti_agent = true\n\n[agents]\nmax_threads = 6\nmax_depth = 1\n\n[agents.team_consensus]\ndescription = "Planning and debate agent for SKS Team mode. Maps options, constraints, risks, and proposes the agreed objective before implementation starts."\nconfig_file = "./agents/team-consensus.toml"\nnickname_candidates = ["Consensus", "Atlas"]\n\n[agents.implementation_worker]\ndescription = "Implementation worker for SKS Team mode. Owns a clearly bounded write set and coordinates with other workers without reverting their edits."\nconfig_file = "./agents/implementation-worker.toml"\nnickname_candidates = ["Builder", "Mason"]\n\n[agents.db_safety_reviewer]\ndescription = "Read-only database safety reviewer for SQL, migrations, RLS, destructive-operation risk, and rollback safety."\nconfig_file = "./agents/db-safety-reviewer.toml"\nnickname_candidates = ["Sentinel", "Ledger"]\n\n[agents.qa_reviewer]\ndescription = "Read-only verification reviewer for correctness, tests, regressions, and missing evidence."\nconfig_file = "./agents/qa-reviewer.toml"\nnickname_candidates = ["Verifier", "Scout"]\n\n[profiles.sks-ralph]\nmodel = "gpt-5.5"\napproval_policy = "never"\nsandbox_mode = "workspace-write"\nmodel_reasoning_effort = "high"\n\n[profiles.sks-research]\nmodel = "gpt-5.5"\napproval_policy = "never"\nsandbox_mode = "workspace-write"\nmodel_reasoning_effort = "xhigh"\n\n[profiles.sks-team]\nmodel = "gpt-5.5"\napproval_policy = "on-request"\nsandbox_mode = "workspace-write"\nmodel_reasoning_effort = "high"\n\n[profiles.sks-default]\nmodel = "gpt-5.5"\napproval_policy = "on-request"\nsandbox_mode = "workspace-write"\nmodel_reasoning_effort = "medium"\n`);
251
276
  created.push('.codex/config.toml');
@@ -264,14 +289,40 @@ export async function initProject(root, opts = {}) {
264
289
  });
265
290
  created.push(`.codex/hooks.json (${installScope})`);
266
291
 
267
- await installSkills(root);
292
+ const skillInstall = await installSkills(root);
268
293
  created.push('.codex/skills/*');
269
- created.push('.agents/skills/*');
294
+ if (skillInstall.removed_legacy_agent_skill_dirs.length) created.push(`.agents/skills legacy mirrors removed (${skillInstall.removed_legacy_agent_skill_dirs.length})`);
270
295
  await installCodexAgents(root);
271
296
  created.push('.codex/agents/*');
272
297
  return { created };
273
298
  }
274
299
 
300
+ async function ensureLocalOnlyGitExclude(root) {
301
+ const gitDir = await resolveGitDir(root);
302
+ if (!gitDir) return { path: null, patterns: [] };
303
+ const patterns = ['.sneakoscope/', '.codex/', '.agents/', 'AGENTS.md'];
304
+ const excludePath = path.join(gitDir, 'info', 'exclude');
305
+ await ensureDir(path.dirname(excludePath));
306
+ const markerStart = '# Sneakoscope Codex local-only generated files';
307
+ const current = await readText(excludePath, '');
308
+ if (!current.includes(markerStart)) {
309
+ const block = `${markerStart}\n${patterns.join('\n')}\n`;
310
+ await writeTextAtomic(excludePath, `${current.trimEnd()}${current.trim() ? '\n\n' : ''}${block}`);
311
+ }
312
+ return { path: excludePath, patterns };
313
+ }
314
+
315
+ async function resolveGitDir(root) {
316
+ const dotGit = path.join(root, '.git');
317
+ if (!(await exists(dotGit))) return null;
318
+ const text = await readText(dotGit, null);
319
+ if (typeof text === 'string') {
320
+ const match = text.match(/^gitdir:\s*(.+)\s*$/m);
321
+ if (match) return path.resolve(root, match[1]);
322
+ }
323
+ return dotGit;
324
+ }
325
+
275
326
  function codexAppQuickReference(scope, commandPrefix) {
276
327
  return `# Sneakoscope Codex for Codex App
277
328
 
@@ -385,12 +436,44 @@ async function installSkills(root) {
385
436
  'design-artifact-expert': `---\nname: design-artifact-expert\ndescription: Create or revise high-fidelity HTML, UI, prototype, deck-like, or visual design artifacts using project design context, variations, and rendered verification.\n---\n\nUse when the user asks for design, UI, prototype, HTML artifact, landing page, deck-like visual work, interaction design, or visual refinement.\n\nWorkflow:\n1. Understand the artifact, audience, constraints, fidelity, variants, and existing brand/design system.\n2. Inspect local code, assets, screenshots, or design-system docs before inventing visuals. If context exists, follow its visual vocabulary.\n3. Build the actual usable screen or artifact first; avoid empty landing-page framing unless the task is explicitly marketing.\n4. Use descriptive HTML filenames. Keep large artifacts split into small support files when needed.\n5. For screens/slides, add data-screen-label attributes for comment context. Slide labels are 1-indexed.\n6. Preserve state for decks, videos, or multi-step prototypes with localStorage when refresh continuity matters.\n7. Expose a small Tweaks surface for useful variants such as layout, density, color, copy, or interaction options.\n8. Verify the artifact renders cleanly in a browser or preview. For design tasks, set done-gate.json design_verification_required/present fields and cite evidence.\n\nQuality bar:\n- Root design decisions in available assets and components.\n- Use restrained, domain-appropriate layout and typography.\n- Avoid text overlap, unreadable controls, decorative clutter, one-note palettes, and placeholder-only deliverables.\n- Prefer icons and familiar controls for tool actions, and make repeated UI dimensions stable.\n`
386
437
  };
387
438
  for (const [name, content] of Object.entries(skills)) {
388
- for (const base of ['.codex/skills', '.agents/skills']) {
389
- const dir = path.join(root, base, name);
390
- await ensureDir(dir);
391
- await writeTextAtomic(path.join(dir, 'SKILL.md'), `${content.trim()}\n`);
392
- }
439
+ const dir = path.join(root, '.codex', 'skills', name);
440
+ await ensureDir(dir);
441
+ await writeTextAtomic(path.join(dir, 'SKILL.md'), `${content.trim()}\n`);
393
442
  }
443
+ return { removed_legacy_agent_skill_dirs: await removeLegacyAgentSkillMirrors(root, Object.keys(skills)) };
444
+ }
445
+
446
+ async function removeLegacyAgentSkillMirrors(root, skillNames) {
447
+ const legacyRoot = path.join(root, '.agents', 'skills');
448
+ if (!(await exists(legacyRoot))) return [];
449
+ const removed = [];
450
+ for (const name of skillNames) {
451
+ const dir = path.join(legacyRoot, name);
452
+ const skillPath = path.join(dir, 'SKILL.md');
453
+ const text = await readText(skillPath, null);
454
+ if (!isGeneratedSksLegacySkill(text, name)) continue;
455
+ await fsp.rm(dir, { recursive: true, force: true });
456
+ removed.push(path.relative(root, dir));
457
+ }
458
+ await removeDirIfEmpty(legacyRoot);
459
+ await removeDirIfEmpty(path.join(root, '.agents'));
460
+ return removed;
461
+ }
462
+
463
+ function isGeneratedSksLegacySkill(text, name) {
464
+ if (typeof text !== 'string') return false;
465
+ return text.startsWith('---') && new RegExp(`^name:\\s*${escapeRegExp(name)}\\s*$`, 'm').test(text);
466
+ }
467
+
468
+ function escapeRegExp(value) {
469
+ return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
470
+ }
471
+
472
+ async function removeDirIfEmpty(dir) {
473
+ try {
474
+ const entries = await fsp.readdir(dir);
475
+ if (!entries.length) await fsp.rmdir(dir);
476
+ } catch {}
394
477
  }
395
478
 
396
479
  async function installCodexAgents(root) {