sneakoscope 3.1.7 → 3.1.9

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.
Files changed (50) hide show
  1. package/README.md +9 -2
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/crates/sks-core/src/main.rs +1 -1
  5. package/dist/.sks-build-stamp.json +4 -4
  6. package/dist/bin/sks.js +1 -1
  7. package/dist/cli/args.js +17 -0
  8. package/dist/cli/command-registry.js +16 -13
  9. package/dist/cli/router.js +8 -5
  10. package/dist/commands/doctor.js +97 -2
  11. package/dist/core/codex-app/codex-skill-sync.js +80 -154
  12. package/dist/core/codex-native/core-skill-integrity.js +89 -0
  13. package/dist/core/codex-native/core-skill-manifest.js +156 -0
  14. package/dist/core/codex-native/native-capability-postcheck.js +35 -0
  15. package/dist/core/codex-native/native-capability-repair-matrix.js +210 -0
  16. package/dist/core/codex-native/native-capability-repair.js +47 -0
  17. package/dist/core/codex-native/native-media-computer-repair.js +5 -0
  18. package/dist/core/codex-native/project-skill-dedupe.js +109 -0
  19. package/dist/core/codex-native/skill-name-canonicalizer.js +21 -0
  20. package/dist/core/codex-native/skill-registry-ledger.js +85 -0
  21. package/dist/core/commands/basic-cli.js +19 -10
  22. package/dist/core/commands/mad-sks-command.js +36 -13
  23. package/dist/core/commands/naruto-command.js +4 -1
  24. package/dist/core/commands/pipeline-command.js +3 -4
  25. package/dist/core/commands/qa-loop-command.js +36 -1
  26. package/dist/core/commands/research-command.js +61 -1
  27. package/dist/core/commands/team-command.js +63 -3
  28. package/dist/core/config/config-migration-journal.js +27 -0
  29. package/dist/core/config/managed-config-merge.js +105 -0
  30. package/dist/core/config/secret-preservation.js +169 -0
  31. package/dist/core/config/supabase-secret-preservation.js +29 -0
  32. package/dist/core/decision-contract.js +28 -4
  33. package/dist/core/doctor/command-alias-cleanup.js +64 -0
  34. package/dist/core/doctor/doctor-native-capability-repair.js +48 -0
  35. package/dist/core/feature-fixtures.js +2 -0
  36. package/dist/core/feature-registry.js +2 -2
  37. package/dist/core/fsx.js +1 -1
  38. package/dist/core/init.js +5 -1
  39. package/dist/core/naruto/naruto-work-graph.js +4 -1
  40. package/dist/core/pipeline-internals/runtime-core.js +50 -4
  41. package/dist/core/pipeline-internals/runtime-gates.js +10 -1
  42. package/dist/core/proof/route-proof-gate.js +1 -1
  43. package/dist/core/qa-loop.js +227 -11
  44. package/dist/core/questions.js +239 -2
  45. package/dist/core/routes.js +3 -4
  46. package/dist/core/version.js +1 -1
  47. package/dist/scripts/agent-native-release-gate.js +13 -4
  48. package/dist/scripts/sizecheck.js +8 -2
  49. package/dist/scripts/sks-3-1-8-check-lib.js +30 -0
  50. package/package.json +27 -1
@@ -0,0 +1,105 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { ensureDir, nowIso, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
4
+ import { isProtectedSecretKey, PROTECTED_SECRET_KEYS } from './supabase-secret-preservation.js';
5
+ export async function writeManagedJsonConfig(file, current, managed) {
6
+ const next = safeMergeObject(current, managed);
7
+ const before = `${JSON.stringify(current, null, 2)}\n`;
8
+ const after = `${JSON.stringify(next, null, 2)}\n`;
9
+ return writeMergedText(file, before, after, 'json', protectedKeysPresent(current));
10
+ }
11
+ export async function writeManagedTomlConfig(file, currentText, managedBlocks) {
12
+ let next = String(currentText || '').trimEnd();
13
+ for (const block of managedBlocks)
14
+ next = upsertTomlBlockPreservingSecrets(next, block);
15
+ return writeMergedText(file, currentText, `${next.trim()}\n`, 'toml', protectedKeysInText(currentText));
16
+ }
17
+ export async function writeManagedEnvConfig(file, currentText, managedLines) {
18
+ const existingKeys = new Set(String(currentText || '').split(/\r?\n/).map((line) => line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/)?.[1]).filter((value) => Boolean(value)));
19
+ const additions = managedLines.filter((line) => {
20
+ const key = line.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/)?.[1] || '';
21
+ return key && !existingKeys.has(key) && !isProtectedSecretKey(key);
22
+ });
23
+ const next = additions.length ? `${String(currentText || '').replace(/\s*$/, '\n')}${additions.join('\n')}\n` : String(currentText || '');
24
+ return writeMergedText(file, currentText, next, 'env', protectedKeysInText(currentText));
25
+ }
26
+ export function safeMergeObject(current, managed) {
27
+ const out = { ...current };
28
+ for (const [key, value] of Object.entries(managed)) {
29
+ if (isProtectedSecretKey(key) && current[key] != null)
30
+ continue;
31
+ if (isPlainObject(value) && isPlainObject(current[key]))
32
+ out[key] = safeMergeObject(current[key], value);
33
+ else
34
+ out[key] = value;
35
+ }
36
+ return out;
37
+ }
38
+ function upsertTomlBlockPreservingSecrets(text, block) {
39
+ const header = block.match(/^\s*\[([^\]]+)\]/)?.[1];
40
+ if (!header)
41
+ return text;
42
+ const lines = String(text || '').trimEnd().split('\n');
43
+ const start = lines.findIndex((line) => line.trim() === `[${header}]`);
44
+ const blockLines = block.trim().split('\n');
45
+ if (start === -1)
46
+ return [...lines.filter((line) => line.length), '', ...blockLines].join('\n');
47
+ let end = lines.length;
48
+ for (let index = start + 1; index < lines.length; index += 1) {
49
+ if (/^\s*\[.+\]\s*$/.test(lines[index] || '')) {
50
+ end = index;
51
+ break;
52
+ }
53
+ }
54
+ const existingSecretLines = lines.slice(start + 1, end).filter((line) => {
55
+ const key = line.match(/^\s*([A-Za-z0-9_.-]+)\s*=/)?.[1] || '';
56
+ return isProtectedSecretKey(`${header}.${key}`) || isProtectedSecretKey(key);
57
+ });
58
+ lines.splice(start, end - start, ...blockLines, ...existingSecretLines.filter((line) => !blockLines.includes(line)));
59
+ return lines.join('\n').replace(/\n{3,}/g, '\n\n');
60
+ }
61
+ async function writeMergedText(file, before, after, format, preserved) {
62
+ await ensureDir(path.dirname(file));
63
+ let backupPath = null;
64
+ if (before !== after) {
65
+ if (before.trim()) {
66
+ backupPath = `${file}.sks-managed-merge-${Date.now()}.bak`;
67
+ await fs.writeFile(backupPath, before, 'utf8');
68
+ }
69
+ await writeTextAtomic(file, after);
70
+ }
71
+ return {
72
+ schema: 'sks.managed-config-merge.v1',
73
+ generated_at: nowIso(),
74
+ ok: true,
75
+ path: file,
76
+ format,
77
+ changed: before !== after,
78
+ backup_path: backupPath,
79
+ protected_keys_preserved: preserved,
80
+ blockers: []
81
+ };
82
+ }
83
+ function protectedKeysPresent(value) {
84
+ const found = [];
85
+ for (const key of PROTECTED_SECRET_KEYS)
86
+ if (lookupPath(value, key) != null)
87
+ found.push(String(key));
88
+ return found;
89
+ }
90
+ function protectedKeysInText(text) {
91
+ return PROTECTED_SECRET_KEYS.filter((key) => new RegExp(`(^|\\n)\\s*(?:export\\s+)?${String(key).replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*=`).test(text)).map(String);
92
+ }
93
+ function lookupPath(value, dotted) {
94
+ let current = value;
95
+ for (const part of dotted.split('.')) {
96
+ if (!current || typeof current !== 'object' || Array.isArray(current))
97
+ return undefined;
98
+ current = current[part];
99
+ }
100
+ return current;
101
+ }
102
+ function isPlainObject(value) {
103
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
104
+ }
105
+ //# sourceMappingURL=managed-config-merge.js.map
@@ -0,0 +1,169 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ import { ensureDir, nowIso, readJson, readText, sha256, writeJsonAtomic } from '../fsx.js';
5
+ import { PROTECTED_SECRET_KEYS, PROTECTED_SUPABASE_ENV_KEYS } from './supabase-secret-preservation.js';
6
+ export async function captureSecretPreservationSnapshot(input) {
7
+ const root = path.resolve(input.root);
8
+ const sources = secretSources(root);
9
+ const fingerprints = [];
10
+ for (const source of sources) {
11
+ const text = await readText(source, null);
12
+ if (typeof text !== 'string')
13
+ continue;
14
+ if (source.endsWith('.json')) {
15
+ const json = await readJson(source, {}).catch(() => ({}));
16
+ fingerprints.push(...fingerprintsFromObject(json, source));
17
+ }
18
+ else {
19
+ fingerprints.push(...fingerprintsFromText(text, source));
20
+ }
21
+ }
22
+ const snapshot = {
23
+ schema: 'sks.secret-preservation-snapshot.v1',
24
+ generated_at: nowIso(),
25
+ root,
26
+ fingerprints: dedupeFingerprints(fingerprints)
27
+ };
28
+ if (input.artifactPath)
29
+ await writeJsonAtomic(input.artifactPath, snapshot).catch(() => undefined);
30
+ return snapshot;
31
+ }
32
+ export async function withSecretPreservationGuard(root, operationName, fn) {
33
+ const resolvedRoot = path.resolve(root);
34
+ const reportDir = path.join(resolvedRoot, '.sneakoscope', 'reports');
35
+ await ensureDir(reportDir);
36
+ const beforePath = path.join(reportDir, 'secret-preservation-before.json');
37
+ const afterPath = path.join(reportDir, 'secret-preservation-after.json');
38
+ const guardPath = path.join(reportDir, 'secret-preservation-guard.json');
39
+ const before = await captureSecretPreservationSnapshot({ root: resolvedRoot, artifactPath: beforePath });
40
+ let result;
41
+ try {
42
+ result = await fn();
43
+ }
44
+ catch (err) {
45
+ await writeJsonAtomic(guardPath, {
46
+ schema: 'sks.secret-preservation-guard.v1',
47
+ generated_at: nowIso(),
48
+ ok: false,
49
+ operation: operationName,
50
+ before_path: beforePath,
51
+ after_path: null,
52
+ restored_keys_count: 0,
53
+ missing_after: [],
54
+ raw_values_recorded: false,
55
+ operation_error: err instanceof Error ? err.message : String(err)
56
+ }).catch(() => undefined);
57
+ throw err;
58
+ }
59
+ const after = await captureSecretPreservationSnapshot({ root: resolvedRoot, artifactPath: afterPath });
60
+ const missing = missingProtectedSecrets(before, after);
61
+ const report = {
62
+ schema: 'sks.secret-preservation-guard.v1',
63
+ generated_at: nowIso(),
64
+ ok: missing.length === 0,
65
+ operation: operationName,
66
+ before_path: beforePath,
67
+ after_path: afterPath,
68
+ restored_keys_count: 0,
69
+ missing_after: missing,
70
+ raw_values_recorded: false
71
+ };
72
+ await writeJsonAtomic(guardPath, report).catch(() => undefined);
73
+ if (missing.length) {
74
+ throw new Error(`secret_preservation_failed:${missing.map((item) => `${item.source}:${item.key}`).join(',')}`);
75
+ }
76
+ return result;
77
+ }
78
+ export function missingProtectedSecrets(before, after) {
79
+ const afterMap = new Map(after.fingerprints.filter((fp) => fp.present).map((fp) => [`${fp.source}\0${fp.key}`, fp]));
80
+ return before.fingerprints
81
+ .filter((fp) => fp.present && fp.value_sha256)
82
+ .filter((fp) => !afterMap.has(`${fp.source}\0${fp.key}`))
83
+ .map((fp) => ({ key: fp.key, source: fp.source }));
84
+ }
85
+ function secretSources(root) {
86
+ const home = process.env.HOME || os.homedir();
87
+ return [
88
+ '.env',
89
+ '.env.local',
90
+ '.env.development',
91
+ '.env.production',
92
+ '.sneakoscope/config.json',
93
+ '.codex/config.toml'
94
+ ].map((rel) => path.join(root, rel)).concat(path.join(home, '.codex', 'config.toml'));
95
+ }
96
+ function fingerprintsFromText(text, source) {
97
+ const rows = [];
98
+ for (const key of PROTECTED_SECRET_KEYS) {
99
+ const value = readAssignment(text, key);
100
+ if (!value)
101
+ continue;
102
+ rows.push(fingerprint(String(key), source, value));
103
+ }
104
+ for (const envKey of PROTECTED_SUPABASE_ENV_KEYS) {
105
+ const value = readAssignment(text, envKey);
106
+ if (value)
107
+ rows.push(fingerprint(envKey, source, value));
108
+ }
109
+ return rows;
110
+ }
111
+ function fingerprintsFromObject(value, source) {
112
+ const flat = flattenObject(value);
113
+ const rows = [];
114
+ for (const [key, raw] of Object.entries(flat)) {
115
+ if (!PROTECTED_SECRET_KEYS.includes(key))
116
+ continue;
117
+ rows.push(fingerprint(key, source, String(raw)));
118
+ }
119
+ return rows;
120
+ }
121
+ function fingerprint(key, source, value) {
122
+ return {
123
+ key,
124
+ source,
125
+ present: Boolean(value),
126
+ redacted_preview: redactPreview(value),
127
+ value_sha256: value ? sha256(value) : null
128
+ };
129
+ }
130
+ function readAssignment(text, key) {
131
+ const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
132
+ const re = new RegExp(`^\\s*(?:export\\s+)?${escaped.replace(/\\\./g, '\\s*\\.\\s*')}\\s*=\\s*(.+?)\\s*$`, 'm');
133
+ const raw = String(text || '').match(re)?.[1]?.trim() || '';
134
+ return unquote(raw);
135
+ }
136
+ function unquote(value) {
137
+ const trimmed = String(value || '').trim().replace(/\s+#.*$/, '');
138
+ if ((trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'")))
139
+ return trimmed.slice(1, -1);
140
+ return trimmed;
141
+ }
142
+ function redactPreview(value) {
143
+ const text = String(value || '');
144
+ if (!text)
145
+ return '';
146
+ const head = text.slice(0, Math.min(3, text.length));
147
+ const tail = text.length > 6 ? text.slice(-3) : '';
148
+ return `${head}...${tail || 'redacted'}(${text.length})`;
149
+ }
150
+ function flattenObject(value, prefix = '') {
151
+ if (!value || typeof value !== 'object' || Array.isArray(value))
152
+ return {};
153
+ const out = {};
154
+ for (const [key, child] of Object.entries(value)) {
155
+ const nextKey = prefix ? `${prefix}.${key}` : key;
156
+ if (child && typeof child === 'object' && !Array.isArray(child))
157
+ Object.assign(out, flattenObject(child, nextKey));
158
+ else if (child != null)
159
+ out[nextKey] = String(child);
160
+ }
161
+ return out;
162
+ }
163
+ function dedupeFingerprints(fingerprints) {
164
+ const byKey = new Map();
165
+ for (const fp of fingerprints)
166
+ byKey.set(`${fp.source}\0${fp.key}`, fp);
167
+ return [...byKey.values()].sort((a, b) => a.source.localeCompare(b.source) || a.key.localeCompare(b.key));
168
+ }
169
+ //# sourceMappingURL=secret-preservation.js.map
@@ -0,0 +1,29 @@
1
+ export const PROTECTED_SUPABASE_ENV_KEYS = [
2
+ 'SUPABASE_URL',
3
+ 'SUPABASE_ANON_KEY',
4
+ 'SUPABASE_SERVICE_ROLE_KEY',
5
+ 'NEXT_PUBLIC_SUPABASE_URL',
6
+ 'NEXT_PUBLIC_SUPABASE_ANON_KEY',
7
+ 'VITE_SUPABASE_URL',
8
+ 'VITE_SUPABASE_ANON_KEY',
9
+ 'PUBLIC_SUPABASE_URL',
10
+ 'PUBLIC_SUPABASE_ANON_KEY'
11
+ ];
12
+ export const PROTECTED_SUPABASE_CONFIG_PATHS = [
13
+ 'supabase.url',
14
+ 'supabase.anon_key',
15
+ 'supabase.service_role_key',
16
+ 'mcp.supabase.url',
17
+ 'mcp.supabase.token',
18
+ 'mcp.supabase.access_token',
19
+ 'mcp.supabase.service_role_key'
20
+ ];
21
+ export const PROTECTED_SECRET_KEYS = [
22
+ ...PROTECTED_SUPABASE_ENV_KEYS,
23
+ ...PROTECTED_SUPABASE_CONFIG_PATHS
24
+ ];
25
+ export function isProtectedSecretKey(key) {
26
+ const normalized = String(key || '').trim();
27
+ return PROTECTED_SECRET_KEYS.some((candidate) => candidate === normalized);
28
+ }
29
+ //# sourceMappingURL=supabase-secret-preservation.js.map
@@ -1,7 +1,7 @@
1
1
  import path from 'node:path';
2
2
  import { readJson, writeJsonAtomic, nowIso, sha256 } from './fsx.js';
3
3
  import { validateQaLoopAnswers } from './qa-loop.js';
4
- import { inferAnswersForPrompt } from './questions.js';
4
+ import { buildRequestIntake, inferAnswersForPrompt, REQUEST_INTAKE_ARTIFACT } from './questions.js';
5
5
  import { bindMistakeRecallToAnswers, buildMistakeRecallLedger, mistakeRecallContractSummary, writeMistakeRecallArtifacts } from './mistake-recall.js';
6
6
  function isEmptyAnswer(v, slot = {}) {
7
7
  if (v === undefined || v === null)
@@ -41,7 +41,7 @@ export function validateAnswers(schema, answers) {
41
41
  errors.push(...validateQaLoopAnswers(schema, answers));
42
42
  return { ok: errors.length === 0, errors, resolved, totalRequired: schema.slots.filter((s) => s.required).length };
43
43
  }
44
- export function buildDecisionContract({ mission, schema, answers, mistakeRecall = null }) {
44
+ export function buildDecisionContract({ mission, schema, answers, mistakeRecall = null, requestIntake = null }) {
45
45
  const madSks = answers.MAD_SKS_MODE === 'explicit_invocation_only';
46
46
  const defaults = {
47
47
  if_multiple_valid_implementations: 'choose_smallest_reversible_change',
@@ -115,6 +115,14 @@ export function buildDecisionContract({ mission, schema, answers, mistakeRecall
115
115
  acceptance_criteria: Array.isArray(answers.ACCEPTANCE_CRITERIA) ? answers.ACCEPTANCE_CRITERIA : String(answers.ACCEPTANCE_CRITERIA || '').split('\n').map((x) => x.trim()).filter(Boolean),
116
116
  non_goals: Array.isArray(answers.NON_GOALS) ? answers.NON_GOALS : String(answers.NON_GOALS || '').split('\n').map((x) => x.trim()).filter(Boolean),
117
117
  test_scope: answers.TEST_SCOPE,
118
+ request_intake: requestIntake ? {
119
+ artifact: REQUEST_INTAKE_ARTIFACT,
120
+ prompt_hash: requestIntake.prompt_hash || null,
121
+ interpreted_intent: requestIntake.interpreted_intent || null,
122
+ requirements: requestIntake.requirements || [],
123
+ transformed_prompt: requestIntake.transformed_prompt || null,
124
+ wiki_context_used: requestIntake.wiki_context_used || null
125
+ } : null,
118
126
  triwiki_mistake_recall: mistakeRecallContractSummary(mistakeRecall),
119
127
  approved_defaults: defaults,
120
128
  decision_ladder: [
@@ -135,6 +143,7 @@ export function buildDecisionContract({ mission, schema, answers, mistakeRecall
135
143
  return contract;
136
144
  }
137
145
  export async function sealContract(missionDir, mission) {
146
+ const root = rootFromMissionDir(missionDir);
138
147
  const schema = await readJson(path.join(missionDir, 'required-answers.schema.json'), {});
139
148
  const explicitAnswers = await readJson(path.join(missionDir, 'answers.json'), {});
140
149
  const inferred = inferAnswersForPrompt(mission?.prompt || schema?.prompt || '', explicitAnswers);
@@ -143,7 +152,7 @@ export async function sealContract(missionDir, mission) {
143
152
  ...inferred.answers,
144
153
  ...explicitAnswers
145
154
  };
146
- const root = rootFromMissionDir(missionDir);
155
+ const requestIntake = await readOrBuildRequestIntake(missionDir, root, mission?.prompt || schema?.prompt || '', baseAnswers);
147
156
  const mistakeRecall = await buildMistakeRecallLedger(root, {
148
157
  prompt: mission?.prompt || schema?.prompt || '',
149
158
  answers: baseAnswers
@@ -152,7 +161,7 @@ export async function sealContract(missionDir, mission) {
152
161
  const validation = validateAnswers(schema, answers);
153
162
  if (!validation.ok)
154
163
  return { ok: false, validation };
155
- const contract = buildDecisionContract({ mission, schema, answers, mistakeRecall });
164
+ const contract = buildDecisionContract({ mission, schema, answers, mistakeRecall, requestIntake });
156
165
  const mistakeRecallConsumption = await writeMistakeRecallArtifacts(missionDir, mistakeRecall, contract);
157
166
  await writeJsonAtomic(path.join(missionDir, 'resolved-answers.json'), {
158
167
  explicit_answers: explicitAnswers,
@@ -163,12 +172,27 @@ export async function sealContract(missionDir, mission) {
163
172
  artifact: 'mistake-recall-ledger.json',
164
173
  consumed: mistakeRecallConsumption.ok,
165
174
  required: mistakeRecall.required
175
+ },
176
+ request_intake: {
177
+ artifact: REQUEST_INTAKE_ARTIFACT,
178
+ prompt_hash: requestIntake?.prompt_hash || null,
179
+ transformed_prompt_available: Boolean(requestIntake?.transformed_prompt)
166
180
  }
167
181
  });
168
182
  await writeJsonAtomic(path.join(missionDir, 'decision-contract.json'), contract);
169
183
  await writeJsonAtomic(path.join(missionDir, 'answer-validation.json'), validation);
170
184
  return { ok: true, validation, contract };
171
185
  }
186
+ async function readOrBuildRequestIntake(missionDir, root, prompt, answers = {}) {
187
+ const file = path.join(missionDir, REQUEST_INTAKE_ARTIFACT);
188
+ const existing = await readJson(file, null);
189
+ if (existing)
190
+ return existing;
191
+ const wikiContext = await readJson(path.join(root, '.sneakoscope', 'wiki', 'context-pack.json'), null);
192
+ const intake = buildRequestIntake(prompt, answers, { wikiContext });
193
+ await writeJsonAtomic(file, intake);
194
+ return intake;
195
+ }
172
196
  function rootFromMissionDir(missionDir) {
173
197
  const resolved = path.resolve(missionDir);
174
198
  const parts = resolved.split(path.sep);
@@ -0,0 +1,64 @@
1
+ import path from 'node:path';
2
+ import { COMMAND_CATALOG } from '../routes.js';
3
+ import { nowIso, writeJsonAtomic } from '../fsx.js';
4
+ import { COMMANDS, LEGACY_COMMAND_ALIASES, commandNames } from '../../cli/command-registry.js';
5
+ export const COMMAND_ALIAS_CLEANUP_SCHEMA = 'sks.command-alias-cleanup.v1';
6
+ export async function runDoctorCommandAliasCleanup(opts) {
7
+ const report = commandAliasCleanupReport(opts);
8
+ if (opts.fix)
9
+ await writeJsonAtomic(report.report_path, report);
10
+ return report;
11
+ }
12
+ export function commandAliasCleanupReport(opts) {
13
+ const root = opts.root;
14
+ const legacyAliases = Object.entries(LEGACY_COMMAND_ALIASES).map(([alias, canonical]) => ({
15
+ alias,
16
+ canonical
17
+ }));
18
+ const registeredAliasCommands = legacyAliases
19
+ .filter((entry) => Object.prototype.hasOwnProperty.call(COMMANDS, entry.alias))
20
+ .map((entry) => entry.alias);
21
+ const catalogAliasRows = legacyAliases
22
+ .filter((entry) => COMMAND_CATALOG.some((row) => row.name === entry.alias))
23
+ .map((entry) => entry.alias);
24
+ const canonical = commandNames();
25
+ const missingCanonicalTargets = legacyAliases
26
+ .filter((entry) => !canonical.includes(entry.canonical))
27
+ .map((entry) => `${entry.alias}->${entry.canonical}`);
28
+ const blockers = [
29
+ ...registeredAliasCommands.map((alias) => `legacy_alias_registered_as_command:${alias}`),
30
+ ...catalogAliasRows.map((alias) => `legacy_alias_visible_in_command_catalog:${alias}`),
31
+ ...missingCanonicalTargets.map((entry) => `legacy_alias_missing_target:${entry}`)
32
+ ];
33
+ const ok = blockers.length === 0;
34
+ return {
35
+ schema: COMMAND_ALIAS_CLEANUP_SCHEMA,
36
+ ok,
37
+ status: ok ? 'clean' : 'blocked',
38
+ generated_at: nowIso(),
39
+ root,
40
+ fix: Boolean(opts.fix),
41
+ report_path: path.join(root, '.sneakoscope', 'reports', 'command-alias-cleanup.json'),
42
+ canonical_command_count: canonical.length,
43
+ legacy_alias_count: legacyAliases.length,
44
+ aliases: legacyAliases,
45
+ detected: {
46
+ registered_alias_commands: registeredAliasCommands,
47
+ catalog_alias_rows: catalogAliasRows,
48
+ missing_canonical_targets: missingCanonicalTargets
49
+ },
50
+ actions: ok
51
+ ? [{
52
+ action: opts.fix ? 'doctor_fix_verified_aliases_consolidated' : 'verify_aliases_consolidated',
53
+ ok: true,
54
+ detail: 'Legacy command names dispatch through COMMAND_ALIASES and are not registered as duplicate command rows.'
55
+ }]
56
+ : [{
57
+ action: 'source_registry_cleanup_required',
58
+ ok: false,
59
+ detail: 'Remove duplicate alias rows from COMMANDS and COMMAND_CATALOG, then map them through LEGACY_COMMAND_ALIASES.'
60
+ }],
61
+ blockers
62
+ };
63
+ }
64
+ //# sourceMappingURL=command-alias-cleanup.js.map
@@ -0,0 +1,48 @@
1
+ import path from 'node:path';
2
+ import { nowIso, writeJsonAtomic } from '../fsx.js';
3
+ import { syncCoreSkillsIntegrity } from '../codex-native/core-skill-integrity.js';
4
+ import { dedupeProjectSkills } from '../codex-native/project-skill-dedupe.js';
5
+ import { repairNativeCapabilities } from '../codex-native/native-capability-repair.js';
6
+ import { withSecretPreservationGuard } from '../config/config-migration-journal.js';
7
+ export async function runDoctorNativeCapabilityRepair(input) {
8
+ const root = path.resolve(input.root);
9
+ const operation = async () => {
10
+ const coreSkills = await syncCoreSkillsIntegrity({ root, apply: input.fix });
11
+ const skillDedupe = await dedupeProjectSkills({
12
+ root,
13
+ fix: input.fix,
14
+ yes: input.yes,
15
+ quarantineUserDuplicates: (input.flags || []).includes('--quarantine-user-duplicate-skills')
16
+ });
17
+ const nativeCapabilities = await repairNativeCapabilities({
18
+ root,
19
+ fix: input.fix,
20
+ yes: input.yes,
21
+ allowManualInstructions: true
22
+ });
23
+ const blockers = [
24
+ ...(coreSkills.blockers || []),
25
+ ...(skillDedupe.blockers || []),
26
+ ...(nativeCapabilities.blockers || [])
27
+ ];
28
+ const report = {
29
+ schema: 'sks.doctor-native-capability-repair.v1',
30
+ generated_at: nowIso(),
31
+ ok: blockers.length === 0,
32
+ root,
33
+ fix: input.fix,
34
+ yes: input.yes,
35
+ core_skills: coreSkills,
36
+ skill_dedupe: skillDedupe,
37
+ native_capabilities: nativeCapabilities,
38
+ secret_preservation_guard: '.sneakoscope/reports/secret-preservation-guard.json',
39
+ blockers
40
+ };
41
+ await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'doctor-native-capability-repair.json'), report).catch(() => undefined);
42
+ return report;
43
+ };
44
+ if (!input.fix)
45
+ return operation();
46
+ return withSecretPreservationGuard(root, 'doctor-native-capability-repair', operation);
47
+ }
48
+ //# sourceMappingURL=doctor-native-capability-repair.js.map
@@ -39,6 +39,7 @@ const FIXTURES = Object.freeze({
39
39
  'cli-qa-loop': fixture('mock', 'sks qa-loop status latest --json', ['qa-loop-proof.json', 'completion-proof.json'], 'pass'),
40
40
  'cli-ppt': fixture('mock', 'sks ppt fixture --mock --json', ['ppt-imagegen-review-gate.json', 'completion-proof.json'], 'pass'),
41
41
  'cli-image-ux-review': fixture('mock', 'sks image-ux-review status latest --json', ['image-ux-generated-review-ledger.json', 'image-voxel-ledger.json'], 'pass'),
42
+ 'cli-computer-use': fixture('real_optional', 'sks computer-use status --json', [], 'pass'),
42
43
  'cli-pipeline': fixture('mock', 'sks pipeline status latest --json', ['pipeline-plan.json'], 'pass'),
43
44
  'cli-validate-artifacts': fixture('mock', 'sks validate-artifacts latest --json', ['validation-report.json'], 'pass'),
44
45
  'cli-hproof': fixture('mock', 'sks hproof check latest', ['completion-proof.json'], 'pass'),
@@ -68,6 +69,7 @@ const FIXTURES = Object.freeze({
68
69
  'cli-zellij': fixture('mock', 'npm run zellij:capability --silent', [], 'pass'),
69
70
  'cli-tmux': fixture('mock', 'removed runtime migration notice: sks tmux --json', [], 'pass'),
70
71
  'cli-mad': fixture('mock', 'sks mad --help', [], 'pass'),
72
+ 'cli-mad-sks': fixture('static', 'sks mad-sks status --json', [], 'pass'),
71
73
  'cli-auto-review': fixture('mock', 'sks auto-review status --json', [], 'pass'),
72
74
  'cli-commit': fixture('mock', 'sks commit --dry-run', [], 'pass'),
73
75
  'cli-commit-and-push': fixture('mock', 'sks commit-and-push --dry-run', [], 'pass'),
@@ -813,7 +813,7 @@ function commandCategory(name) {
813
813
  return 'proof-route';
814
814
  if (['qa-loop', 'research', 'recallpulse', 'skill-dream', 'eval', 'perf'].includes(name))
815
815
  return 'loop';
816
- if (['codex', 'codex-app', 'codex-native', 'codex-lb', 'auth', 'hooks', 'context7'].includes(name))
816
+ if (['codex', 'codex-app', 'codex-native', 'codex-lb', 'hooks', 'context7', 'computer-use'].includes(name))
817
817
  return 'integration';
818
818
  if (['db', 'guard', 'conflicts', 'harness', 'versioning'].includes(name))
819
819
  return 'safety';
@@ -826,7 +826,7 @@ function commandCategory(name) {
826
826
  function commandMaturity(name) {
827
827
  if (['help', 'version', 'commands', 'usage', 'root', 'quickstart', 'setup', 'doctor', 'selftest', 'update-check', 'fast-mode'].includes(name))
828
828
  return 'stable';
829
- if (['codex', 'codex-app', 'codex-lb', 'hooks', 'features', 'all-features', 'wiki', 'wrongness', 'team', 'pipeline', 'goal', 'db', 'guard'].includes(name))
829
+ if (['codex', 'codex-app', 'codex-native', 'codex-lb', 'hooks', 'features', 'all-features', 'wiki', 'wrongness', 'team', 'pipeline', 'goal', 'db', 'guard', 'computer-use', 'mad-sks'].includes(name))
830
830
  return 'beta';
831
831
  return 'labs';
832
832
  }
package/dist/core/fsx.js 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
  import { fileURLToPath } from 'node:url';
8
- export const PACKAGE_VERSION = '3.1.7';
8
+ export const PACKAGE_VERSION = '3.1.9';
9
9
  export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
10
10
  export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
11
11
  export function nowIso() {
package/dist/core/init.js CHANGED
@@ -11,6 +11,7 @@ import { AWESOME_DESIGN_MD_REFERENCE, CODEX_APP_IMAGE_GENERATION_DOC_URL, CODEX_
11
11
  import { SKILL_DREAM_POLICY, skillDreamPolicyText } from './skill-forge.js';
12
12
  import { CODEX_HOOK_EVENT_STATE_KEYS } from './codex-compat/codex-hook-events.js';
13
13
  import { codexCommandHookCurrentHash } from './codex-hooks/codex-hook-hash.js';
14
+ import { buildSksCoreSkillManifest, isCoreSkillName, renderCoreSkillTemplate } from './codex-native/core-skill-manifest.js';
14
15
  const REFLECTION_MEMORY_PATH = '.sneakoscope/memory/q2_facts/post-route-reflection.md';
15
16
  const SKS_GENERATED_GIT_PATTERNS = [
16
17
  '.sneakoscope/missions/',
@@ -1083,9 +1084,12 @@ export async function installSkills(root) {
1083
1084
  'design-ui-editor': `---\nname: design-ui-editor\ndescription: Legacy fallback UI/UX editor for existing design.md systems when Product Design plugin is unavailable.\n---\n\nUse Product Design plugin first. When falling back, read \`design.md\`, inspect relevant UI/assets/tests, consult getdesign-reference when improving the design system, apply the smallest design-system-conformant change, use imagegen for image/logo/raster assets, and verify render quality. ${productDesignPluginPolicyText()} ${CODEX_IMAGEGEN_REQUIRED_POLICY} If design.md is missing and Product Design is unavailable, use design-system-builder as fallback.\n`,
1084
1085
  'design-artifact-expert': `---\nname: design-artifact-expert\ndescription: Legacy fallback for high-fidelity HTML/UI/prototype artifacts when Product Design plugin cannot be used.\n---\n\nUse Product Design plugin first for design/UI/prototype work. When falling back, read design.md when present, consult getdesign-reference for design-system grounding, build the usable artifact first, preserve state, verify overlap/readability/responsiveness, and use imagegen for required assets. ${productDesignPluginPolicyText()} ${CODEX_IMAGEGEN_REQUIRED_POLICY}\n`
1085
1086
  };
1087
+ for (const skill of buildSksCoreSkillManifest().skills) {
1088
+ skills[skill.canonical_name] = renderCoreSkillTemplate(skill.canonical_name);
1089
+ }
1086
1090
  for (const [name, content] of Object.entries(skills)) {
1087
1091
  const dir = path.join(root, '.agents', 'skills', name);
1088
- const skillContent = enrichSkillContent(name, content);
1092
+ const skillContent = isCoreSkillName(name) ? content : enrichSkillContent(name, content);
1089
1093
  await ensureDir(dir);
1090
1094
  await writeTextAtomic(path.join(dir, 'SKILL.md'), `${skillContent.trim()}\n`);
1091
1095
  await writeSkillMetadata(dir, name);
@@ -29,7 +29,10 @@ export function buildNarutoWorkGraph(input = {}) {
29
29
  const readonly = input.readonly === true;
30
30
  const writeCapable = input.writeCapable !== false && !readonly;
31
31
  const minimumFanout = writeCapable ? requestedClones * 2 : requestedClones;
32
- const totalWorkItems = Math.max(minimumFanout, normalizePositiveInt(input.totalWorkItems, minimumFanout));
32
+ const requestedWorkItems = normalizePositiveInt(input.totalWorkItems, minimumFanout);
33
+ const totalWorkItems = input.honorExplicitTotalWorkItems === true
34
+ ? Math.max(requestedClones, requestedWorkItems)
35
+ : Math.max(minimumFanout, requestedWorkItems);
33
36
  const kindCycle = writeCapable ? WRITE_CAPABLE_KIND_CYCLE : READONLY_KIND_CYCLE;
34
37
  const basePath = normalizeNarutoPath(input.leaseBasePath || '.sneakoscope/naruto/patch-envelopes');
35
38
  const targetPaths = normalizePaths(input.targetPaths || []);