sneakoscope 3.1.4 → 3.1.5

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 (44) hide show
  1. package/README.md +1 -1
  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/install-helpers.js +56 -4
  8. package/dist/commands/codex-lb.js +12 -9
  9. package/dist/core/codex-app/codex-agent-role-sync.js +63 -11
  10. package/dist/core/codex-app/codex-agent-type-probe.js +202 -0
  11. package/dist/core/codex-app/codex-app-execution-profile.js +15 -8
  12. package/dist/core/codex-app/codex-app-fast-ui-repair.js +7 -4
  13. package/dist/core/codex-app/codex-app-harness-matrix.js +78 -26
  14. package/dist/core/codex-app/codex-app-types.js +21 -0
  15. package/dist/core/codex-app/codex-hook-approval-probe.js +188 -0
  16. package/dist/core/codex-app/codex-hook-lifecycle.js +31 -10
  17. package/dist/core/codex-app/codex-init-deep.js +97 -4
  18. package/dist/core/codex-app/codex-skill-sync.js +75 -3
  19. package/dist/core/codex-app/lazycodex-analysis.js +31 -10
  20. package/dist/core/codex-app/lazycodex-interop-policy.js +13 -2
  21. package/dist/core/codex-app/lazycodex-live-analyzer.js +98 -0
  22. package/dist/core/commands/mad-sks-command.js +37 -2
  23. package/dist/core/commands/qa-loop-command.js +3 -2
  24. package/dist/core/commands/research-command.js +2 -2
  25. package/dist/core/doctor/doctor-zellij-repair.js +5 -1
  26. package/dist/core/feature-fixtures.js +1 -0
  27. package/dist/core/feature-registry.js +4 -1
  28. package/dist/core/fsx.js +1 -1
  29. package/dist/core/init.js +4 -1
  30. package/dist/core/loops/loop-continuation-enforcer.js +11 -3
  31. package/dist/core/loops/loop-planner.js +21 -4
  32. package/dist/core/loops/loop-worker-runtime.js +23 -7
  33. package/dist/core/naruto/naruto-loop-worker-router.js +11 -2
  34. package/dist/core/qa-loop.js +39 -4
  35. package/dist/core/research/research-cycle-runner.js +1 -0
  36. package/dist/core/research/research-stage-runner.js +9 -2
  37. package/dist/core/research.js +35 -1
  38. package/dist/core/version.js +1 -1
  39. package/dist/core/zellij/homebrew-policy.js +0 -1
  40. package/dist/core/zellij/zellij-self-heal-types.js +45 -0
  41. package/dist/core/zellij/zellij-self-heal.js +69 -8
  42. package/dist/core/zellij/zellij-update.js +9 -1
  43. package/dist/scripts/sks-3-1-5-directive-check-lib.js +347 -0
  44. package/package.json +26 -2
@@ -1,61 +1,99 @@
1
- // @ts-nocheck
2
1
  import path from 'node:path';
3
2
  import { findCodexBinary } from '../codex-adapter.js';
4
3
  import { codexAppIntegrationStatus } from '../codex-app.js';
5
4
  import { detectCodex0138Capability } from '../codex-control/codex-0138-capability.js';
6
5
  import { detectCodex0139Capability } from '../codex-control/codex-0139-capability.js';
7
6
  import { buildCodexPluginInventory } from '../codex-plugins/codex-plugin-json.js';
8
- import { readCodexHookActualState } from '../codex-hooks/codex-hook-actual-discovery.js';
9
7
  import { nowIso, runProcess, writeJsonAtomic } from '../fsx.js';
10
8
  import { repairAgentRoleConfigs } from '../agents/agent-role-config.js';
11
9
  import { buildLazyCodexInteropPolicy } from './lazycodex-interop-policy.js';
10
+ import { probeCodexAgentTypeSupport } from './codex-agent-type-probe.js';
11
+ import { probeCodexHookApprovalState } from './codex-hook-approval-probe.js';
12
+ import { isRecord } from './codex-app-types.js';
12
13
  export async function buildCodexAppHarnessMatrix(input = { root: process.cwd() }) {
13
14
  const root = path.resolve(input.root || process.cwd());
14
15
  const codexBin = await findCodexBinary().catch(() => null);
15
16
  const version = codexBin ? await codexVersion(codexBin) : null;
16
- const cap0138 = await detectCodex0138Capability({ codexBin }).catch((err) => ({ blockers: [err?.message || String(err)] }));
17
- const cap0139 = await detectCodex0139Capability({ codexBin }).catch((err) => ({ blockers: [err?.message || String(err)] }));
18
- const app = await codexAppIntegrationStatus({ codex: { bin: codexBin, version, available: Boolean(codexBin) } }).catch((err) => ({ ok: false, blockers: [err?.message || String(err)] }));
19
- const plugins = await buildCodexPluginInventory().catch((err) => ({ plugins: [], marketplace_available: false, blockers: [err?.message || String(err)] }));
20
- const hooks = await readCodexHookActualState(root).catch((err) => ({ ok: false, entries: [], blockers: [err?.message || String(err)] }));
21
- const agents = await repairAgentRoleConfigs({ root, apply: input.applyRepairs === true, reportPath: path.join(root, '.sneakoscope', 'reports', 'codex-agent-role-sync.json') }).catch((err) => ({ ok: false, blockers: [err?.message || String(err)] }));
17
+ const cap0138 = await detectCodex0138Capability({ codexBin }).catch((err) => ({ blockers: [messageOf(err)] }));
18
+ const cap0139 = await detectCodex0139Capability({ codexBin }).catch((err) => ({ blockers: [messageOf(err)] }));
19
+ const app = await codexAppIntegrationStatus({ codex: { bin: codexBin, version, available: Boolean(codexBin) } }).catch((err) => ({ ok: false, blockers: [messageOf(err)] }));
20
+ const plugins = await buildCodexPluginInventory().catch((err) => ({ plugins: [], marketplace_available: false, blockers: [messageOf(err)] }));
21
+ const hookApproval = await probeCodexHookApprovalState(root, { codexBin }).catch((err) => ({
22
+ schema: 'sks.codex-hook-approval-probe.v1',
23
+ generated_at: nowIso(),
24
+ ok: false,
25
+ detectable: false,
26
+ approval_state: 'unknown',
27
+ sources_checked: [],
28
+ blockers: [messageOf(err)],
29
+ warnings: ['hook_approval_probe_failed']
30
+ }));
31
+ const agentType = await probeCodexAgentTypeSupport(root, { codexBin }).catch((err) => ({
32
+ schema: 'sks.codex-agent-type-probe.v1',
33
+ generated_at: nowIso(),
34
+ ok: false,
35
+ supported: false,
36
+ source: 'unknown',
37
+ spawn_tool_name: 'unknown',
38
+ schema_path: null,
39
+ evidence: [],
40
+ blockers: [messageOf(err)],
41
+ warnings: ['agent_type_probe_failed_message_role_fallback']
42
+ }));
43
+ const agents = await repairAgentRoleConfigs({ root, apply: input.applyRepairs === true, reportPath: path.join(root, '.sneakoscope', 'reports', 'codex-agent-role-sync.json') }).catch((err) => ({ ok: false, blockers: [messageOf(err)] }));
22
44
  const interop = await buildLazyCodexInteropPolicy({ root, inventory: plugins }).catch(() => null);
23
- const hasSkills = app?.required_skills?.ok === true || app?.skills?.ok === true || app?.skill_shadows?.ok !== false;
24
- const hookApprovalKnown = false;
45
+ const appRecord = isRecord(app) ? app : {};
46
+ const requiredSkills = isRecord(appRecord.required_skills) ? appRecord.required_skills : {};
47
+ const skills = isRecord(appRecord.skills) ? appRecord.skills : {};
48
+ const skillShadows = isRecord(appRecord.skill_shadows) ? appRecord.skill_shadows : {};
49
+ const hasSkills = requiredSkills.ok === true || skills.ok === true || skillShadows.ok !== false;
50
+ const hookApprovalKnown = hookApproval.detectable === true;
51
+ const hookBlockers = hookApproval.approval_state === 'modified_requires_reapproval'
52
+ ? ['hook_modified_requires_reapproval']
53
+ : [];
25
54
  const matrix = {
26
55
  schema: 'sks.codex-app-harness-matrix.v1',
27
56
  generated_at: nowIso(),
28
- ok: Boolean(codexBin) && cap0138.supports_plugin_json !== false && agents.ok !== false,
57
+ ok: Boolean(codexBin) && feature(cap0138, 'supports_plugin_json') !== false && recordOk(agents) !== false,
29
58
  codex_cli: { available: Boolean(codexBin), version },
30
59
  app_features: {
31
- plugin_json: cap0138.supports_plugin_json === true,
32
- marketplace_add: cap0139.supports_marketplace_source_field === true || plugins.marketplace_available === true,
33
- marketplace_upgrade: cap0139.supports_marketplace_source_field === true,
60
+ plugin_json: feature(cap0138, 'supports_plugin_json') === true,
61
+ marketplace_add: feature(cap0139, 'supports_marketplace_source_field') === true || feature(plugins, 'marketplace_available') === true,
62
+ marketplace_upgrade: feature(cap0139, 'supports_marketplace_source_field') === true,
34
63
  startup_review_detectable: hookApprovalKnown,
35
64
  hook_approval_state_detectable: hookApprovalKnown,
65
+ hook_approval_state: hookApproval.approval_state,
36
66
  skill_picker_ready: Boolean(hasSkills),
37
- agent_type_supported: process.env.SKS_CODEX_AGENT_TYPE_SUPPORTED === '1',
38
- mcp_inventory_ready: Array.isArray(plugins.plugins),
39
- app_handoff_ready: cap0138.supports_app_handoff === true,
40
- image_path_exposure_ready: cap0138.supports_image_path_exposure === true
67
+ agent_type_supported: agentType.supported,
68
+ mcp_inventory_ready: Array.isArray(isRecord(plugins) ? plugins.plugins : null),
69
+ app_handoff_ready: feature(cap0138, 'supports_app_handoff') === true,
70
+ image_path_exposure_ready: feature(cap0138, 'supports_image_path_exposure') === true
41
71
  },
42
72
  sks_integrations: {
43
73
  dollar_skills_synced: Boolean(hasSkills),
44
- agent_roles_synced: agents.ok !== false,
45
- hooks_synced: hooks.ok !== false && (hooks.entries || []).length > 0,
74
+ agent_roles_synced: recordOk(agents) !== false,
75
+ hooks_synced: hookApproval.approval_state === 'approved',
46
76
  init_deep_available: true,
47
77
  loop_mesh_app_profile_available: true
48
78
  },
79
+ probes: {
80
+ hook_approval: hookApproval,
81
+ agent_type: agentType
82
+ },
49
83
  blockers: [
50
84
  ...(!codexBin ? ['codex_cli_missing'] : []),
51
- ...(cap0138.blockers || []),
52
- ...(plugins.blockers || []),
53
- ...(agents.blockers || [])
85
+ ...blockersOf(cap0138),
86
+ ...blockersOf(plugins),
87
+ ...blockersOf(agents),
88
+ ...hookBlockers
54
89
  ],
55
90
  warnings: [
56
- ...(cap0139.blockers || []).map((b) => `codex_0139:${b}`),
57
- ...(hooks.blockers || []).map((b) => `hooks:${b}`),
58
- ...(!hookApprovalKnown ? ['hook_approval_state_unknown'] : []),
91
+ ...blockersOf(cap0139).map((b) => `codex_0139:${b}`),
92
+ ...hookApproval.blockers.map((b) => `hooks:${b}`),
93
+ ...hookApproval.warnings,
94
+ ...agentType.warnings,
95
+ ...(hookApproval.approval_state === 'pending_review' ? ['hook_approval_pending_review'] : []),
96
+ ...(hookApproval.approval_state === 'not_installed' ? ['hooks_not_installed'] : []),
59
97
  ...(interop?.lazycodex_detected ? ['lazycodex_detected_coexist_mode'] : [])
60
98
  ]
61
99
  };
@@ -72,4 +110,18 @@ async function codexVersion(bin) {
72
110
  const run = await runProcess(bin, ['--version'], { timeoutMs: 5000, maxOutputBytes: 16 * 1024 }).catch(() => null);
73
111
  return run?.code === 0 ? `${run.stdout || run.stderr || ''}`.trim() || null : null;
74
112
  }
113
+ function blockersOf(value) {
114
+ if (!isRecord(value) || !Array.isArray(value.blockers))
115
+ return [];
116
+ return value.blockers.map((item) => String(item)).filter(Boolean);
117
+ }
118
+ function feature(value, key) {
119
+ return isRecord(value) && typeof value[key] === 'boolean' ? value[key] : undefined;
120
+ }
121
+ function recordOk(value) {
122
+ return isRecord(value) && typeof value.ok === 'boolean' ? value.ok : undefined;
123
+ }
124
+ function messageOf(err) {
125
+ return err instanceof Error ? err.message : String(err);
126
+ }
75
127
  //# sourceMappingURL=codex-app-harness-matrix.js.map
@@ -0,0 +1,21 @@
1
+ export function isRecord(value) {
2
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
3
+ }
4
+ export function stringList(value) {
5
+ return Array.isArray(value) ? value.map((item) => String(item)).filter(Boolean) : [];
6
+ }
7
+ export function isCodexAppHarnessMatrix(value) {
8
+ if (!isRecord(value))
9
+ return false;
10
+ if (value.schema !== 'sks.codex-app-harness-matrix.v1')
11
+ return false;
12
+ const app = value.app_features;
13
+ const sks = value.sks_integrations;
14
+ return isRecord(app)
15
+ && isRecord(sks)
16
+ && typeof value.ok === 'boolean'
17
+ && isRecord(value.codex_cli)
18
+ && Array.isArray(value.blockers)
19
+ && Array.isArray(value.warnings);
20
+ }
21
+ //# sourceMappingURL=codex-app-types.js.map
@@ -0,0 +1,188 @@
1
+ import path from 'node:path';
2
+ import { findCodexBinary } from '../codex-adapter.js';
3
+ import { readCodexHookActualState } from '../codex-hooks/codex-hook-actual-discovery.js';
4
+ import { nowIso, runProcess, writeJsonAtomic } from '../fsx.js';
5
+ import { isRecord } from './codex-app-types.js';
6
+ export async function probeCodexHookApprovalState(root, input = {}) {
7
+ const env = input.env || process.env;
8
+ const sources = [];
9
+ const fixture = normalizeApprovalState(env.SKS_CODEX_HOOK_APPROVAL_FIXTURE);
10
+ if (fixture) {
11
+ const report = buildProbe(fixture !== 'unknown', fixture, [{
12
+ source: 'config',
13
+ ok: fixture !== 'unknown',
14
+ evidence: [`fixture:${fixture}`],
15
+ blockers: fixture === 'unknown' ? ['fixture_unknown'] : []
16
+ }]);
17
+ return persist(root, report, input.writeReport !== false);
18
+ }
19
+ const actual = await readCodexHookActualState(root).catch((err) => ({
20
+ schema: 'sks.codex-hook-actual-state.v1',
21
+ ok: false,
22
+ root,
23
+ sources: [],
24
+ managed_dirs: [],
25
+ entries: [],
26
+ unsupported_handlers: [],
27
+ invalid_matchers: [],
28
+ dual_representation: [],
29
+ warnings: [],
30
+ blockers: [messageOf(err)]
31
+ }));
32
+ const actualState = approvalFromActualState(actual);
33
+ sources.push({
34
+ source: 'hook-actual-state',
35
+ ok: actual.ok !== false,
36
+ evidence: [
37
+ `entries:${actual.entries.length}`,
38
+ `trusted:${actual.entries.filter((entry) => entry.trust_status === 'Trusted' || entry.trust_status === 'Managed').length}`,
39
+ `modified:${actual.entries.filter((entry) => entry.trust_status === 'Modified').length}`,
40
+ `untrusted:${actual.entries.filter((entry) => entry.trust_status === 'Untrusted').length}`
41
+ ],
42
+ blockers: actual.blockers
43
+ });
44
+ const doctor = await probeCodexDoctorApproval(input.codexBin, env);
45
+ sources.push(doctor.source);
46
+ const doctorState = doctor.approval_state;
47
+ const state = strongestApprovalState([actualState, doctorState]);
48
+ const detectable = state !== 'unknown';
49
+ const blockers = [
50
+ ...sources.flatMap((source) => source.blockers),
51
+ ...(state === 'modified_requires_reapproval' ? ['hook_modified_requires_reapproval'] : [])
52
+ ];
53
+ const warnings = [
54
+ ...(state === 'unknown' ? ['hook_approval_state_unknown'] : []),
55
+ ...(actual.entries.length > 0 && state === 'unknown' ? ['hooks_installed_but_approval_unknown'] : [])
56
+ ];
57
+ const report = {
58
+ schema: 'sks.codex-hook-approval-probe.v1',
59
+ generated_at: nowIso(),
60
+ ok: blockers.length === 0 && state !== 'pending_review' && state !== 'modified_requires_reapproval',
61
+ detectable,
62
+ approval_state: state,
63
+ sources_checked: sources,
64
+ blockers,
65
+ warnings
66
+ };
67
+ return persist(root, report, input.writeReport !== false);
68
+ }
69
+ function approvalFromActualState(actual) {
70
+ if (actual.entries.length === 0)
71
+ return 'not_installed';
72
+ if (actual.entries.some((entry) => entry.trust_status === 'Modified'))
73
+ return 'modified_requires_reapproval';
74
+ if (actual.entries.some((entry) => entry.trust_status === 'Untrusted'))
75
+ return 'pending_review';
76
+ if (actual.entries.every((entry) => entry.trust_status === 'Trusted' || entry.trust_status === 'Managed'))
77
+ return 'approved';
78
+ return 'unknown';
79
+ }
80
+ async function probeCodexDoctorApproval(codexBin, env) {
81
+ const bin = codexBin || env.CODEX_BIN || await findCodexBinary().catch(() => null);
82
+ if (!bin) {
83
+ return { approval_state: 'unknown', source: { source: 'codex-doctor-json', ok: false, evidence: [], blockers: ['codex_cli_missing'] } };
84
+ }
85
+ const run = await runProcess(bin, ['doctor', '--json'], { env, timeoutMs: 8000, maxOutputBytes: 256 * 1024 }).catch((err) => ({
86
+ code: 1,
87
+ stdout: '',
88
+ stderr: messageOf(err)
89
+ }));
90
+ const text = `${run.stdout || ''}${run.stderr || ''}`.trim();
91
+ if (run.code !== 0 || !text) {
92
+ return {
93
+ approval_state: 'unknown',
94
+ source: { source: 'codex-doctor-json', ok: false, evidence: text ? [text.slice(0, 240)] : [], blockers: ['codex_doctor_json_unavailable'] }
95
+ };
96
+ }
97
+ try {
98
+ const parsed = JSON.parse(text);
99
+ const state = findApprovalState(parsed);
100
+ return {
101
+ approval_state: state || 'unknown',
102
+ source: {
103
+ source: 'codex-doctor-json',
104
+ ok: Boolean(state),
105
+ evidence: state ? [`approval_state:${state}`] : ['doctor_json_no_hook_approval_field'],
106
+ blockers: state ? [] : ['hook_approval_not_exposed_by_codex_doctor']
107
+ }
108
+ };
109
+ }
110
+ catch {
111
+ return {
112
+ approval_state: 'unknown',
113
+ source: { source: 'codex-doctor-json', ok: false, evidence: [text.slice(0, 240)], blockers: ['codex_doctor_json_parse_failed'] }
114
+ };
115
+ }
116
+ }
117
+ function findApprovalState(value) {
118
+ if (!isRecord(value))
119
+ return null;
120
+ for (const key of ['hook_approval_state', 'approval_state', 'hookApprovalState']) {
121
+ const state = normalizeApprovalState(value[key]);
122
+ if (state)
123
+ return state;
124
+ }
125
+ for (const nested of Object.values(value)) {
126
+ if (Array.isArray(nested)) {
127
+ for (const item of nested) {
128
+ const found = findApprovalState(item);
129
+ if (found)
130
+ return found;
131
+ }
132
+ }
133
+ else if (isRecord(nested)) {
134
+ const found = findApprovalState(nested);
135
+ if (found)
136
+ return found;
137
+ }
138
+ }
139
+ return null;
140
+ }
141
+ function normalizeApprovalState(value) {
142
+ const raw = String(value || '').trim().toLowerCase().replace(/[-\s]+/g, '_');
143
+ if (raw === 'approved' || raw === 'trusted' || raw === 'managed')
144
+ return 'approved';
145
+ if (raw === 'pending' || raw === 'pending_review' || raw === 'untrusted')
146
+ return 'pending_review';
147
+ if (raw === 'modified' || raw === 'modified_requires_reapproval')
148
+ return 'modified_requires_reapproval';
149
+ if (raw === 'not_installed' || raw === 'missing')
150
+ return 'not_installed';
151
+ if (raw === 'unknown')
152
+ return 'unknown';
153
+ return null;
154
+ }
155
+ function strongestApprovalState(states) {
156
+ if (states.includes('modified_requires_reapproval'))
157
+ return 'modified_requires_reapproval';
158
+ if (states.includes('pending_review'))
159
+ return 'pending_review';
160
+ if (states.includes('approved'))
161
+ return 'approved';
162
+ if (states.every((state) => state === 'not_installed'))
163
+ return 'not_installed';
164
+ if (states.includes('not_installed') && states.length === 1)
165
+ return 'not_installed';
166
+ return 'unknown';
167
+ }
168
+ function buildProbe(detectable, approvalState, sources) {
169
+ return {
170
+ schema: 'sks.codex-hook-approval-probe.v1',
171
+ generated_at: nowIso(),
172
+ ok: approvalState === 'approved' || approvalState === 'not_installed',
173
+ detectable,
174
+ approval_state: approvalState,
175
+ sources_checked: sources,
176
+ blockers: sources.flatMap((source) => source.blockers),
177
+ warnings: approvalState === 'unknown' ? ['hook_approval_state_unknown'] : []
178
+ };
179
+ }
180
+ async function persist(root, report, writeReport) {
181
+ if (writeReport)
182
+ await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'codex-hook-approval-probe.json'), report).catch(() => undefined);
183
+ return report;
184
+ }
185
+ function messageOf(err) {
186
+ return err instanceof Error ? err.message : String(err);
187
+ }
188
+ //# sourceMappingURL=codex-hook-approval-probe.js.map
@@ -1,14 +1,24 @@
1
- // @ts-nocheck
2
1
  import path from 'node:path';
3
2
  import { nowIso, writeJsonAtomic } from '../fsx.js';
4
3
  import { readCodexHookActualState } from '../codex-hooks/codex-hook-actual-discovery.js';
5
4
  import { installManagedCodexHooks } from '../codex-hooks/codex-hook-managed-install.js';
5
+ import { probeCodexHookApprovalState } from './codex-hook-approval-probe.js';
6
6
  export async function buildCodexHookLifecycle(input = {}) {
7
7
  const root = path.resolve(input.root || process.cwd());
8
8
  const install = input.apply === true
9
- ? await installManagedCodexHooks(root).catch((err) => ({ ok: false, blockers: [err?.message || String(err)] }))
9
+ ? await installManagedCodexHooks(root).catch((err) => ({ ok: false, blockers: [messageOf(err)] }))
10
10
  : null;
11
- const actual = await readCodexHookActualState(root).catch((err) => ({ ok: false, entries: [], blockers: [err?.message || String(err)] }));
11
+ const actual = await readCodexHookActualState(root).catch((err) => ({ ok: false, entries: [], blockers: [messageOf(err)] }));
12
+ const probe = await probeCodexHookApprovalState(root).catch((err) => ({
13
+ schema: 'sks.codex-hook-approval-probe.v1',
14
+ generated_at: nowIso(),
15
+ ok: false,
16
+ detectable: false,
17
+ approval_state: 'unknown',
18
+ sources_checked: [],
19
+ blockers: [messageOf(err)],
20
+ warnings: ['hook_approval_probe_failed']
21
+ }));
12
22
  const events = {
13
23
  UserPromptSubmit: ['route_classifier', 'goal_to_loop_compiler', 'permission_profile_badge'],
14
24
  PreToolUse: ['requested_scope_guard', 'mad_db_priority_resolver', 'side_effect_zero_gate'],
@@ -16,25 +26,36 @@ export async function buildCodexHookLifecycle(input = {}) {
16
26
  Stop: ['continuation_enforcer', 'final_proof_check', 'loop_resume_hint'],
17
27
  Notification: ['operator_status', 'zellij_anchor', 'codex_app_status']
18
28
  };
19
- const installedEvents = new Set((actual.entries || []).map((entry) => entry.event));
29
+ const installedEvents = new Set(Array.isArray(actual.entries) ? actual.entries.map((entry) => String(entry.event || '')) : []);
20
30
  const report = {
21
31
  schema: 'sks.codex-hook-lifecycle.v1',
22
32
  generated_at: nowIso(),
23
- ok: actual.ok !== false,
33
+ ok: actual.ok !== false && probe.approval_state !== 'modified_requires_reapproval',
24
34
  apply: input.apply === true,
25
- approval_state: 'unknown',
26
- approval_state_detectable: false,
35
+ approval_state: probe.approval_state,
36
+ approval_state_detectable: probe.detectable,
27
37
  lifecycle: Object.fromEntries(Object.entries(events).map(([event, actions]) => [event, {
28
38
  actions,
29
39
  installed: installedEvents.has(event),
30
- approval_state: 'unknown'
40
+ approval_state: installedEvents.has(event) ? probe.approval_state : 'not_installed'
31
41
  }])),
32
42
  actual_state: actual,
33
43
  install,
34
- blockers: actual.blockers || [],
35
- warnings: ['hook_approval_state_unknown']
44
+ probe,
45
+ blockers: [
46
+ ...(Array.isArray(actual.blockers) ? actual.blockers.map(String) : []),
47
+ ...(probe.approval_state === 'modified_requires_reapproval' ? ['hook_modified_requires_reapproval'] : [])
48
+ ],
49
+ warnings: [
50
+ ...probe.warnings,
51
+ ...(probe.approval_state === 'pending_review' ? ['hook_approval_pending_review'] : []),
52
+ ...(probe.approval_state === 'unknown' ? ['hook_approval_state_unknown'] : [])
53
+ ]
36
54
  };
37
55
  await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'codex-hook-lifecycle.json'), report).catch(() => undefined);
38
56
  return report;
39
57
  }
58
+ function messageOf(err) {
59
+ return err instanceof Error ? err.message : String(err);
60
+ }
40
61
  //# sourceMappingURL=codex-hook-lifecycle.js.map
@@ -1,7 +1,6 @@
1
- // @ts-nocheck
2
1
  import fs from 'node:fs/promises';
3
2
  import path from 'node:path';
4
- import { ensureDir, nowIso, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
3
+ import { ensureDir, mergeManagedBlock, nowIso, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
5
4
  export async function runCodexInitDeep(input = {}) {
6
5
  const root = path.resolve(input.root || process.cwd());
7
6
  const dirs = await scoreDirectories(root);
@@ -9,20 +8,45 @@ export async function runCodexInitDeep(input = {}) {
9
8
  const contextDir = path.join(root, '.sneakoscope', 'context');
10
9
  const generatedPath = path.join(contextDir, 'AGENTS.generated.md');
11
10
  const markdown = renderGeneratedAgents(selected);
11
+ const directoryLocalAgents = { created: [], updated: [], skipped: [], backup_paths: [], blockers: [] };
12
12
  if (input.apply === true) {
13
13
  await ensureDir(contextDir);
14
14
  await writeTextAtomic(generatedPath, markdown);
15
+ if (input.directoryLocal === true) {
16
+ for (const row of selected) {
17
+ const agentsPath = path.join(root, row.dir, 'AGENTS.md');
18
+ try {
19
+ await ensureDir(path.dirname(agentsPath));
20
+ const existing = await fs.readFile(agentsPath, 'utf8').catch(() => '');
21
+ if (existing.trim()) {
22
+ const backup = `${agentsPath}.sks-backup-${Date.now()}`;
23
+ await fs.copyFile(agentsPath, backup);
24
+ directoryLocalAgents.backup_paths.push(path.relative(root, backup));
25
+ }
26
+ const status = await mergeManagedBlock(agentsPath, 'SKS INIT-DEEP MANAGED SECTION', renderDirectoryAgentsBlock(row));
27
+ if (status === 'created')
28
+ directoryLocalAgents.created.push(path.relative(root, agentsPath));
29
+ else
30
+ directoryLocalAgents.updated.push(path.relative(root, agentsPath));
31
+ }
32
+ catch (err) {
33
+ directoryLocalAgents.skipped.push(path.relative(root, agentsPath));
34
+ directoryLocalAgents.blockers.push(`${path.relative(root, agentsPath)}:${messageOf(err)}`);
35
+ }
36
+ }
37
+ }
15
38
  }
16
39
  const report = {
17
40
  schema: 'sks.codex-init-deep.v1',
18
41
  generated_at: nowIso(),
19
- ok: true,
42
+ ok: directoryLocalAgents.blockers.length === 0,
20
43
  apply: input.apply === true,
21
44
  root,
22
45
  generated_path: path.relative(root, generatedPath),
23
46
  root_agents_preserved: true,
24
47
  directory_guidance: selected,
25
- blockers: []
48
+ directory_local_agents: directoryLocalAgents,
49
+ blockers: directoryLocalAgents.blockers
26
50
  };
27
51
  await writeJsonAtomic(path.join(root, '.sneakoscope', 'reports', 'codex-init-deep.json'), report).catch(() => undefined);
28
52
  return report;
@@ -32,6 +56,52 @@ export async function readInitDeepMemory(root) {
32
56
  const text = await fs.readFile(file, 'utf8').catch(() => '');
33
57
  return text.trim() ? { path: file, text } : null;
34
58
  }
59
+ export async function readInitDeepMemoryHints(root, scopePaths = []) {
60
+ const resolvedRoot = path.resolve(root);
61
+ const hints = [];
62
+ const generated = await readInitDeepMemory(resolvedRoot).catch(() => null);
63
+ if (generated) {
64
+ hints.push({
65
+ path: path.relative(resolvedRoot, generated.path),
66
+ scope: '.',
67
+ summary: generated.text.split(/\r?\n/).filter((line) => /^##\s+/.test(line)).slice(0, 8).join(' | ')
68
+ });
69
+ }
70
+ const candidateDirs = new Set();
71
+ for (const scopePath of scopePaths) {
72
+ const absolute = path.resolve(resolvedRoot, scopePath);
73
+ if (!absolute.startsWith(resolvedRoot))
74
+ continue;
75
+ const stat = await fs.stat(absolute).catch(() => null);
76
+ let dir = stat?.isFile() ? path.dirname(absolute) : absolute;
77
+ while (dir.startsWith(resolvedRoot)) {
78
+ candidateDirs.add(dir);
79
+ if (dir === resolvedRoot)
80
+ break;
81
+ dir = path.dirname(dir);
82
+ }
83
+ }
84
+ for (const dir of [...candidateDirs].sort((a, b) => b.length - a.length)) {
85
+ const file = path.join(dir, 'AGENTS.md');
86
+ const text = await fs.readFile(file, 'utf8').catch(() => '');
87
+ if (!text.trim())
88
+ continue;
89
+ const managed = extractManagedSection(text, 'SKS INIT-DEEP MANAGED SECTION');
90
+ const userSummary = text.replace(/<!-- BEGIN [\s\S]*?<!-- END [^>]+-->/g, '').split(/\r?\n/).map((line) => line.trim()).filter(Boolean).slice(0, 4).join(' | ');
91
+ const summary = [managed ? managed.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).slice(0, 4).join(' | ') : '', userSummary ? `user:${userSummary}` : ''].filter(Boolean).join(' || ');
92
+ if (summary) {
93
+ hints.push({
94
+ path: path.relative(resolvedRoot, file),
95
+ scope: path.relative(resolvedRoot, dir) || '.',
96
+ summary
97
+ });
98
+ }
99
+ }
100
+ const unique = new Map();
101
+ for (const hint of hints)
102
+ unique.set(`${hint.path}:${hint.scope}`, hint);
103
+ return [...unique.values()].slice(0, 12);
104
+ }
35
105
  async function scoreDirectories(root) {
36
106
  const counts = new Map();
37
107
  await walk(path.join(root, 'src'), root, counts);
@@ -84,4 +154,27 @@ function renderGeneratedAgents(rows) {
84
154
  ])
85
155
  ].join('\n');
86
156
  }
157
+ function renderDirectoryAgentsBlock(row) {
158
+ return [
159
+ `# SKS Init-Deep Local Guidance: ${row.dir}`,
160
+ '',
161
+ `- Files observed: ${row.file_count}`,
162
+ `- Languages: ${row.languages.join(', ') || 'unknown'}`,
163
+ `- Guidance: ${row.guidance}`,
164
+ '- Preserve user-authored content outside this managed block.',
165
+ '- Hydrate TriWiki/current source before risky edits in this directory.'
166
+ ].join('\n');
167
+ }
168
+ function extractManagedSection(text, markerName) {
169
+ const begin = `<!-- BEGIN ${markerName} -->`;
170
+ const end = `<!-- END ${markerName} -->`;
171
+ const beginIdx = text.indexOf(begin);
172
+ const endIdx = text.indexOf(end);
173
+ if (beginIdx < 0 || endIdx < beginIdx)
174
+ return '';
175
+ return text.slice(beginIdx + begin.length, endIdx).trim();
176
+ }
177
+ function messageOf(err) {
178
+ return err instanceof Error ? err.message : String(err);
179
+ }
87
180
  //# sourceMappingURL=codex-init-deep.js.map