sneakoscope 2.0.4 → 2.0.6

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 (120) hide show
  1. package/README.md +18 -11
  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/build-manifest.json +78 -8
  8. package/dist/cli/install-helpers.js +23 -0
  9. package/dist/commands/codex-app.js +25 -3
  10. package/dist/commands/doctor.js +33 -4
  11. package/dist/commands/mad-sks.js +2 -2
  12. package/dist/core/agents/agent-orchestrator.js +22 -3
  13. package/dist/core/agents/agent-proof-evidence.js +59 -2
  14. package/dist/core/agents/agent-roster.js +35 -6
  15. package/dist/core/agents/agent-schema.js +1 -1
  16. package/dist/core/agents/agent-worker-pipeline.js +9 -1
  17. package/dist/core/agents/native-worker-backend-router.js +50 -10
  18. package/dist/core/agents/ollama-worker-config.js +164 -15
  19. package/dist/core/codex/codex-0-137-compat.js +119 -0
  20. package/dist/core/codex-app.js +124 -2
  21. package/dist/core/codex-control/codex-control-proof.js +4 -1
  22. package/dist/core/codex-control/codex-sdk-capability.js +1 -1
  23. package/dist/core/codex-control/codex-task-runner.js +329 -5
  24. package/dist/core/codex-control/python-codex-sdk-adapter.js +197 -0
  25. package/dist/core/codex-control/python-codex-sdk-event-translator.js +14 -0
  26. package/dist/core/commands/local-model-command.js +65 -19
  27. package/dist/core/commands/naruto-command.js +124 -8
  28. package/dist/core/commands/run-command.js +1 -1
  29. package/dist/core/doctor/doctor-readiness-matrix.js +21 -2
  30. package/dist/core/fsx.js +1 -1
  31. package/dist/core/hooks-runtime.js +2 -233
  32. package/dist/core/init.js +8 -8
  33. package/dist/core/local-llm/local-llm-backpressure.js +20 -0
  34. package/dist/core/local-llm/local-llm-capability.js +29 -0
  35. package/dist/core/local-llm/local-llm-client.js +100 -0
  36. package/dist/core/local-llm/local-llm-config.js +6 -1
  37. package/dist/core/local-llm/local-llm-context-cache.js +21 -0
  38. package/dist/core/local-llm/local-llm-control-adapter.js +101 -0
  39. package/dist/core/local-llm/local-llm-json-repair.js +52 -0
  40. package/dist/core/local-llm/local-llm-metrics.js +42 -0
  41. package/dist/core/local-llm/local-llm-ollama-client.js +67 -0
  42. package/dist/core/local-llm/local-llm-openai-compatible-client.js +30 -0
  43. package/dist/core/local-llm/local-llm-prompt-cache.js +12 -0
  44. package/dist/core/local-llm/local-llm-scheduler.js +29 -0
  45. package/dist/core/local-llm/local-llm-schema-enforcer.js +15 -0
  46. package/dist/core/local-llm/local-llm-smoke.js +83 -0
  47. package/dist/core/local-llm/local-llm-warmup.js +20 -0
  48. package/dist/core/local-llm/local-worker-eligibility.js +27 -0
  49. package/dist/core/naruto/hardware-capacity-probe.js +36 -0
  50. package/dist/core/naruto/naruto-active-pool.js +134 -0
  51. package/dist/core/naruto/naruto-backpressure.js +13 -0
  52. package/dist/core/naruto/naruto-concurrency-governor.js +65 -0
  53. package/dist/core/naruto/naruto-finalizer.js +18 -0
  54. package/dist/core/naruto/naruto-generation-scheduler.js +18 -0
  55. package/dist/core/naruto/naruto-gpt-final-pack.js +49 -0
  56. package/dist/core/naruto/naruto-parallel-patch-apply.js +95 -0
  57. package/dist/core/naruto/naruto-patch-transaction-batch.js +42 -0
  58. package/dist/core/naruto/naruto-role-policy.js +107 -0
  59. package/dist/core/naruto/naruto-verification-dag.js +42 -0
  60. package/dist/core/naruto/naruto-verification-pool.js +18 -0
  61. package/dist/core/naruto/naruto-work-graph.js +198 -0
  62. package/dist/core/naruto/naruto-work-item.js +40 -0
  63. package/dist/core/naruto/naruto-work-stealing.js +11 -0
  64. package/dist/core/naruto/resource-pressure-monitor.js +32 -0
  65. package/dist/core/pipeline/finalize-pipeline-result.js +58 -0
  66. package/dist/core/pipeline/gpt-final-required.js +12 -0
  67. package/dist/core/pipeline-internals/runtime-core.js +1 -1
  68. package/dist/core/ppt.js +31 -8
  69. package/dist/core/product-design-app-server.js +410 -0
  70. package/dist/core/product-design-plugin.js +139 -0
  71. package/dist/core/prompt/prompt-placeholder-guard.js +30 -0
  72. package/dist/core/router/capability-card.js +13 -0
  73. package/dist/core/router/route-cache.js +3 -0
  74. package/dist/core/router/ultra-router.js +2 -1
  75. package/dist/core/routes.js +12 -12
  76. package/dist/core/version.js +1 -1
  77. package/dist/core/zellij/zellij-lane-runtime.js +2 -2
  78. package/dist/core/zellij/zellij-naruto-dashboard.js +36 -0
  79. package/dist/core/zellij/zellij-worker-pane-manager.js +4 -4
  80. package/dist/scripts/blackbox-command-import-smoke.js +10 -1
  81. package/dist/scripts/check-package-boundary.js +12 -3
  82. package/dist/scripts/codex-0-137-compat-check.js +27 -0
  83. package/dist/scripts/codex-environment-scoped-approvals-check.js +10 -0
  84. package/dist/scripts/codex-plugin-list-json-check.js +8 -0
  85. package/dist/scripts/codex-thread-runtime-choice-check.js +10 -0
  86. package/dist/scripts/local-collab-all-pipelines-final-gpt-check.js +21 -0
  87. package/dist/scripts/local-llm-all-pipelines-check.js +11 -0
  88. package/dist/scripts/local-llm-cache-performance-check.js +10 -0
  89. package/dist/scripts/local-llm-capability-check.js +14 -0
  90. package/dist/scripts/local-llm-smoke-check.js +23 -0
  91. package/dist/scripts/local-llm-structured-output-check.js +11 -0
  92. package/dist/scripts/local-llm-throughput-check.js +10 -0
  93. package/dist/scripts/local-llm-tool-call-repair-check.js +10 -0
  94. package/dist/scripts/local-llm-warmup-check.js +11 -0
  95. package/dist/scripts/naruto-active-pool-check.js +39 -0
  96. package/dist/scripts/naruto-concurrency-governor-check.js +52 -0
  97. package/dist/scripts/naruto-gpt-final-pack-check.js +34 -0
  98. package/dist/scripts/naruto-parallel-patch-apply-check.js +41 -0
  99. package/dist/scripts/naruto-readonly-routing-check.js +116 -0
  100. package/dist/scripts/naruto-real-local-gpt-final-smoke.js +16 -0
  101. package/dist/scripts/naruto-role-distribution-check.js +23 -0
  102. package/dist/scripts/naruto-shadow-clone-swarm-check.js +13 -0
  103. package/dist/scripts/naruto-verification-pool-check.js +36 -0
  104. package/dist/scripts/naruto-work-graph-check.js +24 -0
  105. package/dist/scripts/naruto-zellij-massive-ui-check.js +23 -0
  106. package/dist/scripts/product-design-auto-install-check.js +119 -0
  107. package/dist/scripts/product-design-plugin-routing-check.js +101 -0
  108. package/dist/scripts/prompt-placeholder-guard-check.js +33 -0
  109. package/dist/scripts/python-codex-sdk-all-pipelines-check.js +47 -0
  110. package/dist/scripts/python-codex-sdk-capability-check.js +75 -0
  111. package/dist/scripts/python-codex-sdk-sandbox-policy-check.js +10 -0
  112. package/dist/scripts/python-codex-sdk-stream-bridge-check.js +12 -0
  113. package/dist/scripts/release-parallel-check.js +16 -2
  114. package/dist/scripts/release-provenance-check.js +21 -0
  115. package/dist/scripts/release-real-check.js +5 -0
  116. package/dist/scripts/zellij-worker-pane-manager-check.js +1 -1
  117. package/package.json +36 -4
  118. package/schemas/local-llm/local-model-config.schema.json +74 -0
  119. package/schemas/naruto/naruto-concurrency-governor.schema.json +21 -0
  120. package/schemas/naruto/naruto-work-graph.schema.json +22 -0
@@ -0,0 +1,40 @@
1
+ export const NARUTO_WORK_KINDS = [
2
+ 'implementation',
3
+ 'code_modification',
4
+ 'refactor',
5
+ 'test_generation',
6
+ 'test_execution',
7
+ 'verification',
8
+ 'research',
9
+ 'documentation',
10
+ 'ux_review',
11
+ 'ppt_review',
12
+ 'image_review',
13
+ 'conflict_resolution',
14
+ 'patch_rebase',
15
+ 'rollback_preparation',
16
+ 'integration_support',
17
+ 'final_review_input_pack'
18
+ ];
19
+ export const NARUTO_WRITE_WORK_KINDS = new Set([
20
+ 'implementation',
21
+ 'code_modification',
22
+ 'refactor',
23
+ 'test_generation',
24
+ 'documentation',
25
+ 'conflict_resolution',
26
+ 'patch_rebase',
27
+ 'rollback_preparation',
28
+ 'integration_support'
29
+ ]);
30
+ export function isNarutoWriteKind(kind) {
31
+ return NARUTO_WRITE_WORK_KINDS.has(kind);
32
+ }
33
+ export function normalizeNarutoWorkKind(value, fallback = 'verification') {
34
+ const text = String(value || '');
35
+ return NARUTO_WORK_KINDS.includes(text) ? text : fallback;
36
+ }
37
+ export function normalizeNarutoPath(value) {
38
+ return String(value || '').replace(/\\/g, '/').replace(/^\.\/+/, '').split('/').filter((part) => part && part !== '.').join('/');
39
+ }
40
+ //# sourceMappingURL=naruto-work-item.js.map
@@ -0,0 +1,11 @@
1
+ export function stealNarutoWork(queue, input = {}) {
2
+ const item = queue.shift() || null;
3
+ return {
4
+ schema: 'sks.naruto-work-stealing.v1',
5
+ stolen: Boolean(item),
6
+ work_item_id: item?.id || null,
7
+ from_queue: input.fromQueue || 'pending',
8
+ to_slot: input.toSlot || 'idle-slot'
9
+ };
10
+ }
11
+ //# sourceMappingURL=naruto-work-stealing.js.map
@@ -0,0 +1,32 @@
1
+ export function monitorNarutoResourcePressure(probe, input = {}) {
2
+ const activeWorkers = Math.max(1, Math.floor(Number(input.activeWorkers || 1)));
3
+ const zellijCap = Math.max(1, Math.floor(Number(input.zellijVisiblePaneCap || 12)));
4
+ const memoryPressure = 1 - (probe.free_memory_bytes / Math.max(1, probe.total_memory_bytes));
5
+ const cpuPressure = Math.min(1, (probe.current_load_average[0] || 0) / Math.max(1, probe.cpu_core_count));
6
+ const fdPressure = Math.min(1, (activeWorkers * 6 + probe.process_count) / Math.max(1, probe.file_descriptor_limit));
7
+ const zellijPressure = Math.min(1, probe.zellij_pane_count / zellijCap);
8
+ const diskIoPressure = probe.disk_io_pressure;
9
+ const reasons = [
10
+ ...(memoryPressure > 0.8 ? ['memory_cap'] : []),
11
+ ...(cpuPressure > 0.9 ? ['cpu_load_cap'] : []),
12
+ ...(fdPressure > 0.75 ? ['file_descriptor_budget'] : []),
13
+ ...(zellijPressure > 0.9 ? ['zellij_ui_pane_budget'] : []),
14
+ ...(diskIoPressure > 0.75 ? ['disk_io_pressure'] : [])
15
+ ];
16
+ const maxPressure = Math.max(memoryPressure, cpuPressure, fdPressure, zellijPressure, diskIoPressure);
17
+ const state = maxPressure >= 0.92 ? 'saturated' : maxPressure >= 0.72 ? 'throttled' : 'normal';
18
+ return {
19
+ schema: 'sks.naruto-resource-pressure.v1',
20
+ state,
21
+ memory_pressure: round(memoryPressure),
22
+ cpu_pressure: round(cpuPressure),
23
+ fd_pressure: round(fdPressure),
24
+ zellij_pressure: round(zellijPressure),
25
+ disk_io_pressure: round(diskIoPressure),
26
+ reasons
27
+ };
28
+ }
29
+ function round(value) {
30
+ return Math.round(Math.max(0, Math.min(1, value)) * 1000) / 1000;
31
+ }
32
+ //# sourceMappingURL=resource-pressure-monitor.js.map
@@ -0,0 +1,58 @@
1
+ import path from 'node:path';
2
+ import { nowIso, writeJsonAtomic } from '../fsx.js';
3
+ import { runGptFinalArbiter } from '../codex-control/gpt-final-arbiter.js';
4
+ import { gptFinalRequiredForPipeline } from './gpt-final-required.js';
5
+ export async function finalizePipelineResult(input) {
6
+ const cwd = path.resolve(input.cwd || process.cwd());
7
+ const root = path.resolve(input.mutationLedgerRoot || path.join(cwd, '.sneakoscope', 'tmp', 'pipeline-finalize', safeName(input.missionId)));
8
+ const requirement = gptFinalRequiredForPipeline({
9
+ localParticipated: input.localParticipated,
10
+ candidateResults: input.candidateResults
11
+ });
12
+ let arbiter = null;
13
+ let blockers = [];
14
+ if (requirement.gpt_final_required) {
15
+ arbiter = await runGptFinalArbiter({
16
+ schema: 'sks.gpt-final-arbiter-input.v1',
17
+ route: input.route,
18
+ mission_id: input.missionId,
19
+ local_mode: 'local-parallel-gpt-final',
20
+ local_outputs: input.candidateResults,
21
+ candidate_patch_envelopes: input.candidatePatchEnvelopes
22
+ }, {
23
+ cwd,
24
+ mutationLedgerRoot: path.join(root, 'gpt-final-arbiter'),
25
+ ...(typeof input.forceGptFinalUnavailable === 'boolean' ? { forceUnavailable: input.forceGptFinalUnavailable } : {})
26
+ });
27
+ blockers = [
28
+ ...(arbiter.ok ? [] : ['gpt_final_arbiter_required_not_passed']),
29
+ ...(Array.isArray(arbiter.blockers) ? arbiter.blockers.map(String) : [])
30
+ ];
31
+ }
32
+ const result = {
33
+ schema: 'sks.pipeline-finalize-result.v1',
34
+ generated_at: nowIso(),
35
+ ok: blockers.length === 0,
36
+ route: input.route,
37
+ mission_id: input.missionId,
38
+ local_participated: requirement.local_participated,
39
+ gpt_final_required: requirement.gpt_final_required,
40
+ gpt_final_arbiter: arbiter,
41
+ final_status: blockers.length ? 'blocked' : 'accepted',
42
+ apply_allowed: blockers.length === 0 && input.applyPatches === true,
43
+ final_patch_source: requirement.gpt_final_required ? 'gpt_final_arbiter' : 'deterministic_finalize',
44
+ candidate_results_count: input.candidateResults.length,
45
+ candidate_patch_envelope_count: input.candidatePatchEnvelopes.length,
46
+ verification_results_count: input.verificationResults.length,
47
+ side_effect_report: input.sideEffectReport,
48
+ mutation_ledger: input.mutationLedger,
49
+ rollback_plan: input.rollbackPlan,
50
+ blockers
51
+ };
52
+ await writeJsonAtomic(path.join(root, 'pipeline-finalize-result.json'), result);
53
+ return result;
54
+ }
55
+ function safeName(value) {
56
+ return String(value || 'unknown').replace(/[^a-zA-Z0-9_.-]+/g, '-').slice(0, 80);
57
+ }
58
+ //# sourceMappingURL=finalize-pipeline-result.js.map
@@ -0,0 +1,12 @@
1
+ import { localCollaborationParticipated } from '../local-llm/local-collaboration-policy.js';
2
+ export function gptFinalRequiredForPipeline(input) {
3
+ const localParticipated = input.localParticipated === true
4
+ || localCollaborationParticipated(Array.isArray(input.candidateResults) ? input.candidateResults : []);
5
+ return {
6
+ schema: 'sks.gpt-final-required.v1',
7
+ local_participated: localParticipated,
8
+ gpt_final_required: localParticipated,
9
+ reason: localParticipated ? 'local_llm_outputs_are_drafts' : 'no_local_participation'
10
+ };
11
+ }
12
+ //# sourceMappingURL=gpt-final-required.js.map
@@ -386,7 +386,7 @@ export function promptPipelineContext(prompt, route = null) {
386
386
  skillDreamPolicyText(),
387
387
  route?.id === 'PPT'
388
388
  ? `${pptPipelineAllowlistPolicyText()} ${getdesignReferencePolicyText()}`
389
- : `Design routing: UI/UX reads design.md first; if missing, use design-system-builder from docs/Design-Sys-Prompt.md with plan-tool clarification and a default font recommendation. Existing designs use design-ui-editor plus design-artifact-expert. Image/logo/raster assets use imagegen, which must prefer Codex App built-in image generation documented at ${CODEX_APP_IMAGE_GENERATION_DOC_URL}. ${CODEX_IMAGEGEN_REQUIRED_POLICY} ${getdesignReferencePolicyText()}`,
389
+ : `Design routing: UI/UX uses the Codex App Product Design plugin first for get-context/user-context, research/ideate, prototype/image-to-code/url-to-code, audit/design-qa, and share when available. Treat design.md, design-system-builder, design-ui-editor, design-artifact-expert, and getdesign-reference as compatibility fallback only when the Product Design plugin is unavailable or an existing local design.md must be preserved. Image/logo/raster assets use imagegen, which must prefer Codex App built-in image generation documented at ${CODEX_APP_IMAGE_GENERATION_DOC_URL}. ${CODEX_IMAGEGEN_REQUIRED_POLICY} ${getdesignReferencePolicyText()}`,
390
390
  triwikiContextTrackingText(),
391
391
  triwikiStagePolicyText(),
392
392
  stackCurrentDocsPolicyText(),
package/dist/core/ppt.js CHANGED
@@ -2,6 +2,7 @@ import path from 'node:path';
2
2
  import fsp from 'node:fs/promises';
3
3
  import { nowIso, readJson, sha256, writeJsonAtomic, writeTextAtomic } from './fsx.js';
4
4
  import { AWESOME_DESIGN_MD_REFERENCE, CODEX_APP_IMAGE_GENERATION_DOC_URL, CODEX_IMAGEGEN_EVIDENCE_SOURCE, DESIGN_SYSTEM_SSOT, GETDESIGN_REFERENCE, PPT_CONDITIONAL_SKILL_ALLOWLIST, PPT_PIPELINE_MCP_ALLOWLIST, PPT_PIPELINE_SKILL_ALLOWLIST } from './routes.js';
5
+ import { PRODUCT_DESIGN_LEGACY_DESIGN_FALLBACK_SKILLS, PRODUCT_DESIGN_PIPELINE_STAGES, PRODUCT_DESIGN_PLUGIN, PRODUCT_DESIGN_REQUIRED_SKILLS } from './product-design-plugin.js';
5
6
  export const PPT_AUDIENCE_STRATEGY_ARTIFACT = 'ppt-audience-strategy.json';
6
7
  export const PPT_GATE_ARTIFACT = 'ppt-gate.json';
7
8
  export const PPT_SOURCE_LEDGER_ARTIFACT = 'ppt-source-ledger.json';
@@ -868,28 +869,48 @@ export function buildPptStyleTokens(contract = {}) {
868
869
  required_skills: [...PPT_PIPELINE_SKILL_ALLOWLIST],
869
870
  conditional_skills: [...PPT_CONDITIONAL_SKILL_ALLOWLIST],
870
871
  allowed_mcp_servers: [...PPT_PIPELINE_MCP_ALLOWLIST],
872
+ primary_design_plugin: PRODUCT_DESIGN_PLUGIN.id,
873
+ product_design_tools: [...PRODUCT_DESIGN_REQUIRED_SKILLS],
874
+ product_design_stage_map: [...PRODUCT_DESIGN_PIPELINE_STAGES],
871
875
  ignore_installed_out_of_pipeline_skills: true,
872
- ignored_design_skills_even_if_installed: ['design-artifact-expert', 'design-ui-editor', 'design-system-builder'],
873
- anti_ai_design_goal: 'prevent AI-like generic presentation design by forcing decisions through audience, sources, getdesign reference, and the design SSOT instead of freeform decorative design skills',
874
- rule: 'PPT design and render work must use only the route allowlist. Installed skills or MCP servers outside this allowlist are ignored unless the sealed PPT contract explicitly activates a conditional entry.'
876
+ ignored_design_skills_even_if_installed: [...PRODUCT_DESIGN_LEGACY_DESIGN_FALLBACK_SKILLS],
877
+ anti_ai_design_goal: 'prevent AI-like generic presentation design by forcing decisions through Product Design plugin evidence, audience, sources, and route-local style tokens instead of freeform decorative design skills',
878
+ rule: 'PPT design and render work must use Product Design plugin first plus only the route allowlist. Installed skills or MCP servers outside this allowlist are ignored unless the sealed PPT contract explicitly activates a conditional entry.'
875
879
  },
876
880
  design_ssot: {
881
+ primary_authority: PRODUCT_DESIGN_PLUGIN.id,
877
882
  authority: DESIGN_SYSTEM_SSOT.authority_file,
878
883
  builder_prompt: DESIGN_SYSTEM_SSOT.builder_prompt,
879
884
  route_local_artifact: PPT_STYLE_TOKENS_ARTIFACT,
880
- rule: 'PPT style tokens are a route-local projection of the design SSOT; source inputs are selected, fused, and applied here rather than kept as independent authorities.'
885
+ mode: 'product_design_primary_with_local_fallback_cache',
886
+ rule: 'PPT style tokens are a route-local projection of Product Design plugin evidence when available; design.md/getdesign fallback inputs are selected, fused, and applied here rather than kept as independent authorities.'
887
+ },
888
+ product_design_plugin: {
889
+ id: PRODUCT_DESIGN_PLUGIN.id,
890
+ display_name: PRODUCT_DESIGN_PLUGIN.display_name,
891
+ marketplace: PRODUCT_DESIGN_PLUGIN.marketplace,
892
+ marketplace_kind: PRODUCT_DESIGN_PLUGIN.marketplace_kind,
893
+ remote_plugin_id: PRODUCT_DESIGN_PLUGIN.remote_plugin_id,
894
+ app_server_read_params: PRODUCT_DESIGN_PLUGIN.app_server.read_params,
895
+ required_skills: [...PRODUCT_DESIGN_REQUIRED_SKILLS],
896
+ stage_map: [...PRODUCT_DESIGN_PIPELINE_STAGES]
881
897
  },
882
898
  design_reference_selection: reference,
883
899
  source_inputs: [
900
+ {
901
+ id: PRODUCT_DESIGN_PLUGIN.id,
902
+ url: PRODUCT_DESIGN_PLUGIN.marketplace,
903
+ role: 'primary_codex_app_design_plugin'
904
+ },
884
905
  {
885
906
  id: GETDESIGN_REFERENCE.id,
886
907
  url: GETDESIGN_REFERENCE.url,
887
- role: 'source_input_for_ssot'
908
+ role: 'fallback_source_input_for_ssot'
888
909
  },
889
910
  {
890
911
  id: AWESOME_DESIGN_MD_REFERENCE.id,
891
912
  url: AWESOME_DESIGN_MD_REFERENCE.url,
892
- role: 'source_input_for_ssot'
913
+ role: 'fallback_source_input_for_ssot'
893
914
  }
894
915
  ],
895
916
  avoid: ['over-designed decoration', 'ornamental gradients', 'nested cards', 'low-contrast gray body text', 'excessive motion or effects'],
@@ -1130,13 +1151,15 @@ export function buildPptRenderReport({ contract = {}, audience, sourceLedger, fa
1130
1151
  design_policy_checks: [
1131
1152
  { id: 'information_first', passed: styleTokens.design_policy?.priority === 'information_first' },
1132
1153
  { id: 'restrained_detail', passed: styleTokens.design_policy?.visual_style === 'simple_restrained_detailed' },
1133
- { id: 'design_ssot_declared', passed: styleTokens.design_policy?.design_ssot?.authority === DESIGN_SYSTEM_SSOT.authority_file },
1134
- { id: 'curated_design_md_input_fused', passed: (styleTokens.design_policy?.source_inputs || []).some((entry) => entry.url === AWESOME_DESIGN_MD_REFERENCE.url && entry.role === 'source_input_for_ssot') },
1154
+ { id: 'product_design_plugin_declared', passed: styleTokens.design_policy?.product_design_plugin?.id === PRODUCT_DESIGN_PLUGIN.id && (styleTokens.design_policy?.product_design_plugin?.required_skills || []).includes('design-qa') },
1155
+ { id: 'design_ssot_declared', passed: styleTokens.design_policy?.design_ssot?.authority === DESIGN_SYSTEM_SSOT.authority_file && styleTokens.design_policy?.design_ssot?.primary_authority === PRODUCT_DESIGN_PLUGIN.id },
1156
+ { id: 'curated_design_md_input_fused', passed: (styleTokens.design_policy?.source_inputs || []).some((entry) => entry.url === AWESOME_DESIGN_MD_REFERENCE.url && /fallback_source_input/.test(entry.role || '')) },
1135
1157
  { id: 'concrete_design_reference_selected', passed: Boolean(styleTokens.design_policy?.design_reference_selection?.primary?.id && styleTokens.design_policy?.design_reference_selection?.selected_sources?.length) },
1136
1158
  { id: 'reference_rules_applied_to_tokens', passed: Boolean(styleTokens.layout?.composition && styleTokens.layout?.treatment && styleTokens.design_policy?.design_reference_selection?.applied_token_profile) },
1137
1159
  { id: 'html_uses_reference_layout', passed: typeof html === 'string' && html.includes('decision evidence') && html.includes(styleTokens.layout?.composition || 'presentation-grid') },
1138
1160
  { id: 'ppt_skill_allowlist_enforced', passed: JSON.stringify(styleTokens.design_policy?.pipeline_allowlist?.required_skills || []) === JSON.stringify([...PPT_PIPELINE_SKILL_ALLOWLIST]) },
1139
1161
  { id: 'out_of_pipeline_design_skills_ignored', passed: styleTokens.design_policy?.pipeline_allowlist?.ignore_installed_out_of_pipeline_skills === true && (styleTokens.design_policy?.pipeline_allowlist?.ignored_design_skills_even_if_installed || []).includes('design-artifact-expert') },
1162
+ { id: 'legacy_design_skills_fallback_only', passed: styleTokens.design_policy?.pipeline_allowlist?.primary_design_plugin === PRODUCT_DESIGN_PLUGIN.id && (styleTokens.design_policy?.pipeline_allowlist?.ignored_design_skills_even_if_installed || []).includes('design-system-builder') },
1140
1163
  { id: 'ppt_mcp_allowlist_scoped', passed: (styleTokens.design_policy?.pipeline_allowlist?.allowed_mcp_servers || []).every((entry) => entry.mcp === 'context7' && /external_documentation/.test(entry.condition || '')) },
1141
1164
  { id: 'no_decorative_overdesign', passed: !String(html).includes('gradient') },
1142
1165
  { id: 'fact_ledger_embedded', passed: typeof html === 'string' && html.includes('ppt-fact-ledger') },
@@ -0,0 +1,410 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { EMPTY_CODEX_INFO, getCodexInfo } from './codex-adapter.js';
3
+ import { PRODUCT_DESIGN_PLUGIN, normalizeProductDesignPluginEvidence } from './product-design-plugin.js';
4
+ export const PRODUCT_DESIGN_AUTO_INSTALL_ENV = 'SKS_PRODUCT_DESIGN_AUTO_INSTALL';
5
+ export function productDesignAutoInstallRequested(opts = {}) {
6
+ const env = opts.env || process.env;
7
+ return opts.autoInstallProductDesign === true
8
+ || opts.installProductDesign === true
9
+ || opts.requireProductDesign === true
10
+ || opts.designRoute === true
11
+ || env?.[PRODUCT_DESIGN_AUTO_INSTALL_ENV] === '1'
12
+ || /^true$/i.test(String(env?.[PRODUCT_DESIGN_AUTO_INSTALL_ENV] || ''));
13
+ }
14
+ export async function ensureProductDesignPluginInstalled(opts = {}) {
15
+ const autoInstallProductDesign = productDesignAutoInstallRequested(opts);
16
+ const injectedRequest = opts.request || opts.appServerRequest;
17
+ if (injectedRequest) {
18
+ return ensureProductDesignPluginInstalledWithRequest(injectedRequest, {
19
+ ...opts,
20
+ autoInstallProductDesign
21
+ });
22
+ }
23
+ const codex = opts.codex || await getCodexInfo().catch(() => EMPTY_CODEX_INFO);
24
+ if (!codex.bin) {
25
+ return productDesignAppServerUnavailable('codex_cli_missing', 'Codex CLI missing.', {
26
+ autoInstallProductDesign
27
+ });
28
+ }
29
+ const client = new CodexAppServerJsonRpcClient({
30
+ command: codex.bin,
31
+ args: opts.appServerArgs || ['app-server', '--stdio'],
32
+ env: opts.env || process.env,
33
+ timeoutMs: opts.timeoutMs || 20000,
34
+ cwd: opts.cwd || process.cwd()
35
+ });
36
+ try {
37
+ await client.initialize();
38
+ const result = await ensureProductDesignPluginInstalledWithRequest((method, params) => client.request(method, params), {
39
+ ...opts,
40
+ autoInstallProductDesign
41
+ });
42
+ return {
43
+ ...result,
44
+ app_server_command: `${codex.bin} ${(opts.appServerArgs || ['app-server', '--stdio']).join(' ')}`
45
+ };
46
+ }
47
+ catch (err) {
48
+ return productDesignAppServerUnavailable('product_design_app_server_request_failed', err?.message || String(err), {
49
+ autoInstallProductDesign,
50
+ stderr: client.stderr.trim()
51
+ });
52
+ }
53
+ finally {
54
+ await client.close();
55
+ }
56
+ }
57
+ export async function ensureProductDesignPluginInstalledWithRequest(request, opts = {}) {
58
+ const autoInstallProductDesign = productDesignAutoInstallRequested(opts);
59
+ const calls = [];
60
+ const errors = [];
61
+ const call = async (method, params) => {
62
+ calls.push({ method, params });
63
+ try {
64
+ return { ok: true, result: await request(method, params) };
65
+ }
66
+ catch (err) {
67
+ const error = err?.message || String(err);
68
+ errors.push({ method, params, error });
69
+ return { ok: false, error };
70
+ }
71
+ };
72
+ const before = await readProductDesignFromAppServer(call);
73
+ if (before.evidence.ok) {
74
+ return productDesignEnsureReport({
75
+ ok: true,
76
+ status: 'ready',
77
+ autoInstallProductDesign,
78
+ installAttempted: false,
79
+ before,
80
+ after: before,
81
+ calls,
82
+ errors,
83
+ blockers: []
84
+ });
85
+ }
86
+ if (!autoInstallProductDesign) {
87
+ return productDesignEnsureReport({
88
+ ok: false,
89
+ status: 'install_not_requested',
90
+ autoInstallProductDesign,
91
+ installAttempted: false,
92
+ before,
93
+ after: before,
94
+ calls,
95
+ errors,
96
+ blockers: uniqueStrings([
97
+ ...before.evidence.blockers,
98
+ 'product_design_auto_install_not_requested'
99
+ ])
100
+ });
101
+ }
102
+ const install = await call('plugin/install', before.install_params || PRODUCT_DESIGN_PLUGIN.app_server.install_params);
103
+ const after = await readProductDesignFromAppServer(call, before.read_params || PRODUCT_DESIGN_PLUGIN.app_server.read_params);
104
+ let installedListEvidence = null;
105
+ if (!after.evidence.ok) {
106
+ const installedList = await call('plugin/installed', {});
107
+ const summary = installedList.ok ? findProductDesignPluginSummaryFromMarketplaces(installedList.result) : null;
108
+ if (summary) {
109
+ installedListEvidence = normalizeProductDesignPluginEvidence({
110
+ plugin: {
111
+ marketplaceName: summary.marketplaceName,
112
+ summary,
113
+ skills: []
114
+ }
115
+ });
116
+ }
117
+ }
118
+ const ok = after.evidence.ok;
119
+ return productDesignEnsureReport({
120
+ ok,
121
+ status: ok ? 'installed' : 'install_unverified',
122
+ autoInstallProductDesign,
123
+ installAttempted: true,
124
+ installResponse: summarizeProductDesignInstallResponse(install.result),
125
+ installError: install.ok ? null : install.error,
126
+ before,
127
+ after,
128
+ installedListEvidence,
129
+ calls,
130
+ errors,
131
+ blockers: ok ? [] : uniqueStrings([
132
+ ...after.evidence.blockers,
133
+ ...(installedListEvidence?.blockers || []),
134
+ ...(install.ok ? ['product_design_plugin_install_unverified'] : ['product_design_plugin_install_failed'])
135
+ ])
136
+ });
137
+ }
138
+ export async function readProductDesignFromAppServer(call, preferredReadParams = PRODUCT_DESIGN_PLUGIN.app_server.read_params) {
139
+ const direct = await call('plugin/read', preferredReadParams);
140
+ if (direct.ok) {
141
+ return {
142
+ read_source: 'plugin/read',
143
+ read_params: preferredReadParams,
144
+ install_params: preferredReadParams,
145
+ evidence: normalizeProductDesignPluginEvidence(direct.result)
146
+ };
147
+ }
148
+ const list = await call('plugin/list', PRODUCT_DESIGN_PLUGIN.app_server.list_params);
149
+ const summary = list.ok ? findProductDesignPluginSummaryFromMarketplaces(list.result) : null;
150
+ const discoveredReadParams = summary
151
+ ? productDesignAppServerReadParamsFromSummary(summary)
152
+ : PRODUCT_DESIGN_PLUGIN.app_server.read_params;
153
+ if (summary) {
154
+ const discoveredRead = await call('plugin/read', discoveredReadParams);
155
+ if (discoveredRead.ok) {
156
+ return {
157
+ read_source: 'plugin/list+plugin/read',
158
+ read_params: discoveredReadParams,
159
+ install_params: discoveredReadParams,
160
+ evidence: normalizeProductDesignPluginEvidence(discoveredRead.result)
161
+ };
162
+ }
163
+ }
164
+ const evidence = summary
165
+ ? normalizeProductDesignPluginEvidence({
166
+ plugin: {
167
+ marketplaceName: summary.marketplaceName,
168
+ summary,
169
+ skills: []
170
+ }
171
+ })
172
+ : normalizeProductDesignPluginEvidence({});
173
+ return {
174
+ read_source: summary ? 'plugin/list_summary' : 'plugin/read_failed',
175
+ read_params: discoveredReadParams,
176
+ install_params: discoveredReadParams,
177
+ evidence: {
178
+ ...evidence,
179
+ blockers: uniqueStrings([
180
+ ...evidence.blockers,
181
+ 'product_design_plugin_read_failed'
182
+ ])
183
+ }
184
+ };
185
+ }
186
+ export function findProductDesignPluginSummaryFromMarketplaces(input = {}) {
187
+ const marketplaces = Array.isArray(input?.marketplaces) ? input.marketplaces : [];
188
+ for (const marketplace of marketplaces) {
189
+ const plugins = Array.isArray(marketplace?.plugins) ? marketplace.plugins : [];
190
+ for (const plugin of plugins) {
191
+ const remotePluginId = String(plugin?.remotePluginId || plugin?.remote_plugin_id || '');
192
+ const id = String(plugin?.id || '');
193
+ const name = String(plugin?.name || '');
194
+ const displayName = String(plugin?.displayName || plugin?.display_name || plugin?.interface?.displayName || '');
195
+ const matches = id === PRODUCT_DESIGN_PLUGIN.id
196
+ || name === PRODUCT_DESIGN_PLUGIN.name
197
+ || displayName === PRODUCT_DESIGN_PLUGIN.display_name
198
+ || remotePluginId === PRODUCT_DESIGN_PLUGIN.remote_plugin_id;
199
+ if (matches) {
200
+ return {
201
+ ...plugin,
202
+ displayName,
203
+ marketplaceName: plugin?.marketplaceName || marketplace?.name || PRODUCT_DESIGN_PLUGIN.marketplace,
204
+ remotePluginId
205
+ };
206
+ }
207
+ }
208
+ }
209
+ return null;
210
+ }
211
+ export function productDesignAppServerReadParamsFromSummary(summary = {}) {
212
+ return {
213
+ remoteMarketplaceName: summary.marketplaceName || PRODUCT_DESIGN_PLUGIN.marketplace,
214
+ pluginName: summary.remotePluginId || summary.remote_plugin_id || PRODUCT_DESIGN_PLUGIN.remote_plugin_id
215
+ };
216
+ }
217
+ function productDesignEnsureReport(input) {
218
+ const remoteEvidence = input.after?.evidence || input.before?.evidence || normalizeProductDesignPluginEvidence({});
219
+ return {
220
+ schema: 'sks.product-design-app-server-ensure.v1',
221
+ ok: input.ok,
222
+ checked: true,
223
+ status: input.status,
224
+ auto_install_requested: input.autoInstallProductDesign === true,
225
+ install_attempted: input.installAttempted === true,
226
+ install_response: input.installResponse || null,
227
+ install_error: input.installError || null,
228
+ before_evidence: input.before?.evidence || null,
229
+ after_evidence: input.after?.evidence || null,
230
+ installed_list_evidence: input.installedListEvidence || null,
231
+ remote_evidence: remoteEvidence,
232
+ read_source: input.after?.read_source || input.before?.read_source || null,
233
+ read_params: input.after?.read_params || input.before?.read_params || PRODUCT_DESIGN_PLUGIN.app_server.read_params,
234
+ install_params: input.before?.install_params || PRODUCT_DESIGN_PLUGIN.app_server.install_params,
235
+ calls: input.calls || [],
236
+ errors: input.errors || [],
237
+ blockers: input.blockers || []
238
+ };
239
+ }
240
+ function productDesignAppServerUnavailable(reason, error, extra = {}) {
241
+ const evidence = normalizeProductDesignPluginEvidence({});
242
+ return {
243
+ schema: 'sks.product-design-app-server-ensure.v1',
244
+ ok: false,
245
+ checked: false,
246
+ status: 'app_server_unavailable',
247
+ auto_install_requested: extra.autoInstallProductDesign === true,
248
+ install_attempted: false,
249
+ install_response: null,
250
+ install_error: error,
251
+ before_evidence: evidence,
252
+ after_evidence: evidence,
253
+ installed_list_evidence: null,
254
+ remote_evidence: evidence,
255
+ read_source: null,
256
+ read_params: PRODUCT_DESIGN_PLUGIN.app_server.read_params,
257
+ install_params: PRODUCT_DESIGN_PLUGIN.app_server.install_params,
258
+ calls: [],
259
+ errors: [{ reason, error }],
260
+ stderr: extra.stderr || '',
261
+ blockers: uniqueStrings([
262
+ ...evidence.blockers,
263
+ reason
264
+ ])
265
+ };
266
+ }
267
+ function summarizeProductDesignInstallResponse(result = {}) {
268
+ if (!result)
269
+ return null;
270
+ return {
271
+ auth_policy: result.authPolicy || result.auth_policy || null,
272
+ apps_needing_auth: Array.isArray(result.appsNeedingAuth)
273
+ ? result.appsNeedingAuth.map((app) => ({
274
+ id: app?.id || null,
275
+ name: app?.name || app?.displayName || app?.display_name || null
276
+ }))
277
+ : []
278
+ };
279
+ }
280
+ function uniqueStrings(values = []) {
281
+ return Array.from(new Set(values.filter(Boolean).map((value) => String(value))));
282
+ }
283
+ class CodexAppServerJsonRpcClient {
284
+ command;
285
+ args;
286
+ env;
287
+ cwd;
288
+ timeoutMs;
289
+ child;
290
+ nextId;
291
+ pending;
292
+ stdoutBuffer;
293
+ stderr;
294
+ constructor(config = {}) {
295
+ this.command = config.command;
296
+ this.args = config.args || ['app-server', '--stdio'];
297
+ this.env = config.env || process.env;
298
+ this.cwd = config.cwd || process.cwd();
299
+ this.timeoutMs = Number(config.timeoutMs || 20000);
300
+ this.child = null;
301
+ this.nextId = 1;
302
+ this.pending = new Map();
303
+ this.stdoutBuffer = '';
304
+ this.stderr = '';
305
+ }
306
+ async initialize() {
307
+ this.start();
308
+ const result = await this.request('initialize', {
309
+ clientInfo: {
310
+ name: 'sneakoscope-product-design',
311
+ title: 'Sneakoscope Product Design Installer',
312
+ version: '1.0.0'
313
+ },
314
+ capabilities: {
315
+ experimentalApi: true,
316
+ requestAttestation: false,
317
+ optOutNotificationMethods: []
318
+ }
319
+ });
320
+ this.notify('notifications/initialized', {});
321
+ return result;
322
+ }
323
+ start() {
324
+ if (this.child)
325
+ return;
326
+ this.child = spawn(this.command, this.args, {
327
+ stdio: ['pipe', 'pipe', 'pipe'],
328
+ shell: false,
329
+ env: this.env,
330
+ cwd: this.cwd
331
+ });
332
+ this.child.stdout?.on('data', (chunk) => this.handleStdout(chunk));
333
+ this.child.stderr?.on('data', (chunk) => {
334
+ this.stderr += chunk.toString('utf8');
335
+ if (this.stderr.length > 64 * 1024)
336
+ this.stderr = this.stderr.slice(-64 * 1024);
337
+ });
338
+ this.child.on('error', (err) => this.rejectAll(err));
339
+ this.child.on('close', (code) => {
340
+ this.rejectAll(new Error(`Codex app-server exited before response (code ${code ?? 'signal'}). ${this.stderr.trim()}`.trim()));
341
+ });
342
+ }
343
+ request(method, params) {
344
+ this.start();
345
+ const id = this.nextId++;
346
+ const message = { jsonrpc: '2.0', id, method, params };
347
+ return new Promise((resolve, reject) => {
348
+ const timer = setTimeout(() => {
349
+ this.pending.delete(id);
350
+ reject(new Error(`Codex app-server request timed out: ${method}. ${this.stderr.trim()}`.trim()));
351
+ }, this.timeoutMs);
352
+ timer.unref?.();
353
+ this.pending.set(id, { resolve, reject, timer });
354
+ this.child?.stdin?.write(`${JSON.stringify(message)}\n`);
355
+ });
356
+ }
357
+ notify(method, params) {
358
+ this.start();
359
+ this.child?.stdin?.write(`${JSON.stringify({ jsonrpc: '2.0', method, params })}\n`);
360
+ }
361
+ handleStdout(chunk) {
362
+ this.stdoutBuffer += chunk.toString('utf8');
363
+ const lines = this.stdoutBuffer.split(/\r?\n/);
364
+ this.stdoutBuffer = lines.pop() || '';
365
+ for (const line of lines) {
366
+ if (!line.trim())
367
+ continue;
368
+ let message;
369
+ try {
370
+ message = JSON.parse(line);
371
+ }
372
+ catch {
373
+ continue;
374
+ }
375
+ if (message.id === undefined || !this.pending.has(message.id))
376
+ continue;
377
+ const pending = this.pending.get(message.id);
378
+ if (!pending)
379
+ continue;
380
+ this.pending.delete(message.id);
381
+ clearTimeout(pending.timer);
382
+ if (message.error)
383
+ pending.reject(new Error(message.error.message || JSON.stringify(message.error)));
384
+ else
385
+ pending.resolve(message.result);
386
+ }
387
+ }
388
+ rejectAll(err) {
389
+ for (const [id, pending] of this.pending.entries()) {
390
+ clearTimeout(pending.timer);
391
+ pending.reject(err);
392
+ this.pending.delete(id);
393
+ }
394
+ }
395
+ async close() {
396
+ if (!this.child)
397
+ return;
398
+ const child = this.child;
399
+ this.child = null;
400
+ try {
401
+ child.stdin?.end();
402
+ }
403
+ catch { }
404
+ try {
405
+ child.kill('SIGTERM');
406
+ }
407
+ catch { }
408
+ }
409
+ }
410
+ //# sourceMappingURL=product-design-app-server.js.map