sneakoscope 2.0.5 → 2.0.7

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 (70) hide show
  1. package/README.md +12 -4
  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 +37 -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 +19 -4
  11. package/dist/commands/mad-sks.js +2 -2
  12. package/dist/core/agents/agent-orchestrator.js +160 -6
  13. package/dist/core/agents/agent-patch-schema.js +16 -2
  14. package/dist/core/agents/agent-proof-evidence.js +27 -2
  15. package/dist/core/agents/agent-worker-pipeline.js +9 -1
  16. package/dist/core/agents/native-cli-session-swarm.js +25 -4
  17. package/dist/core/agents/native-cli-worker.js +28 -1
  18. package/dist/core/agents/native-worker-backend-router.js +19 -1
  19. package/dist/core/codex-app.js +124 -2
  20. package/dist/core/codex-control/python-codex-sdk-adapter.js +28 -4
  21. package/dist/core/commands/naruto-command.js +48 -14
  22. package/dist/core/feature-registry.js +2 -0
  23. package/dist/core/fsx.js +1 -1
  24. package/dist/core/git/git-integration-worktree.js +15 -0
  25. package/dist/core/git/git-repo-detection.js +72 -0
  26. package/dist/core/git/git-worktree-cache-policy.js +36 -0
  27. package/dist/core/git/git-worktree-capability.js +54 -0
  28. package/dist/core/git/git-worktree-cleanup.js +51 -0
  29. package/dist/core/git/git-worktree-conflict-resolver.js +13 -0
  30. package/dist/core/git/git-worktree-diff.js +50 -0
  31. package/dist/core/git/git-worktree-manager.js +86 -0
  32. package/dist/core/git/git-worktree-merge-queue.js +55 -0
  33. package/dist/core/git/git-worktree-patch-envelope.js +35 -0
  34. package/dist/core/git/git-worktree-pool.js +23 -0
  35. package/dist/core/git/git-worktree-root.js +52 -0
  36. package/dist/core/git/git-worktree-runner.js +40 -0
  37. package/dist/core/hooks-runtime.js +2 -233
  38. package/dist/core/init.js +8 -8
  39. package/dist/core/naruto/naruto-active-pool.js +55 -4
  40. package/dist/core/naruto/naruto-gpt-final-pack.js +2 -0
  41. package/dist/core/naruto/naruto-work-graph.js +16 -1
  42. package/dist/core/pipeline-internals/runtime-core.js +1 -1
  43. package/dist/core/ppt.js +31 -8
  44. package/dist/core/product-design-app-server.js +410 -0
  45. package/dist/core/product-design-plugin.js +139 -0
  46. package/dist/core/routes.js +8 -8
  47. package/dist/core/version.js +1 -1
  48. package/dist/core/zellij/zellij-naruto-dashboard.js +10 -1
  49. package/dist/core/zellij/zellij-worker-pane-manager.js +6 -4
  50. package/dist/scripts/git-worktree-cache-performance-check.js +25 -0
  51. package/dist/scripts/git-worktree-capability-check.js +27 -0
  52. package/dist/scripts/git-worktree-cleanup-check.js +27 -0
  53. package/dist/scripts/git-worktree-diff-export-check.js +43 -0
  54. package/dist/scripts/git-worktree-manager-check.js +37 -0
  55. package/dist/scripts/git-worktree-merge-queue-check.js +30 -0
  56. package/dist/scripts/git-worktree-pool-performance-check.js +20 -0
  57. package/dist/scripts/lib/git-worktree-fixture.js +33 -0
  58. package/dist/scripts/naruto-active-pool-check.js +13 -1
  59. package/dist/scripts/naruto-readonly-routing-check.js +116 -0
  60. package/dist/scripts/naruto-shadow-clone-swarm-check.js +16 -5
  61. package/dist/scripts/naruto-worktree-coding-check.js +44 -0
  62. package/dist/scripts/naruto-worktree-gpt-final-check.js +45 -0
  63. package/dist/scripts/naruto-worktree-zellij-ui-check.js +28 -0
  64. package/dist/scripts/product-design-auto-install-check.js +119 -0
  65. package/dist/scripts/product-design-plugin-routing-check.js +101 -0
  66. package/dist/scripts/release-parallel-check.js +16 -2
  67. package/dist/scripts/release-provenance-check.js +21 -0
  68. package/package.json +17 -3
  69. package/schemas/git/git-worktree-capability.schema.json +19 -0
  70. package/schemas/git/git-worktree-manifest.schema.json +36 -0
@@ -1,4 +1,39 @@
1
1
  import { createNarutoGeneration, completeNarutoGeneration } from './naruto-generation-scheduler.js';
2
+ export async function runNarutoActivePool(input) {
3
+ const base = simulateNarutoActivePool(input);
4
+ const allocations = [];
5
+ for (const item of input.graph.work_items) {
6
+ if (!item.write_allowed)
7
+ continue;
8
+ const mode = item.worktree?.mode || input.graph.worktree_policy.mode;
9
+ if (mode !== 'git-worktree') {
10
+ allocations.push({ work_item_id: item.id, mode, allocation_status: 'skipped', blockers: [] });
11
+ continue;
12
+ }
13
+ if (!input.allocateWorktree) {
14
+ allocations.push({ work_item_id: item.id, mode, allocation_status: 'planned', blockers: [] });
15
+ continue;
16
+ }
17
+ const allocated = await input.allocateWorktree(item);
18
+ allocations.push({
19
+ work_item_id: item.id,
20
+ mode,
21
+ allocation_status: allocated.ok ? 'allocated' : 'planned',
22
+ ...(allocated.worktree_path ? { worktree_path: allocated.worktree_path } : {}),
23
+ ...(allocated.branch ? { branch: allocated.branch } : {}),
24
+ blockers: allocated.blockers || []
25
+ });
26
+ }
27
+ const allocationBlockers = allocations.flatMap((row) => row.blockers);
28
+ return {
29
+ ...base,
30
+ ok: base.ok && allocationBlockers.length === 0,
31
+ worktree_mode: input.graph.worktree_policy.mode,
32
+ worktree_allocation_required_count: allocations.filter((row) => row.mode === 'git-worktree').length,
33
+ worktree_allocations: allocations,
34
+ blockers: [...base.blockers, ...allocationBlockers]
35
+ };
36
+ }
2
37
  export function simulateNarutoActivePool(input) {
3
38
  const safeActiveWorkers = Math.max(1, input.governor.safe_active_workers);
4
39
  const retryLimit = Math.max(0, Math.floor(Number(input.retryLimit ?? 1)));
@@ -8,18 +43,20 @@ export function simulateNarutoActivePool(input) {
8
43
  const completed = new Set();
9
44
  const failed = new Set();
10
45
  const executed = new Map();
46
+ const byId = new Map(input.graph.work_items.map((item) => [item.id, item]));
11
47
  const timeline = [];
12
48
  let generationIndex = 1;
13
49
  let tick = 0;
14
50
  let refillEvents = 0;
15
51
  let maxObserved = 0;
52
+ let maxObservedWriteLeaseConflicts = 0;
16
53
  let conflictItemsEnqueued = 0;
17
54
  while (pending.length || active.size) {
18
55
  let launched = 0;
19
56
  for (;;) {
20
57
  if (active.size >= safeActiveWorkers)
21
58
  break;
22
- const next = popRunnable(pending, completed, active);
59
+ const next = popRunnable(pending, completed, active, byId);
23
60
  if (!next)
24
61
  break;
25
62
  const generation = createNarutoGeneration(next, generationIndex, tick);
@@ -31,6 +68,7 @@ export function simulateNarutoActivePool(input) {
31
68
  if (launched)
32
69
  refillEvents += launched;
33
70
  maxObserved = Math.max(maxObserved, active.size);
71
+ maxObservedWriteLeaseConflicts = Math.max(maxObservedWriteLeaseConflicts, countActiveWriteLeaseConflicts(active, byId));
34
72
  timeline.push({ tick, active: active.size, pending: pending.length, completed: completed.size, event: launched ? 'refill' : 'wait' });
35
73
  const done = [...active.values()].slice(0, Math.max(1, Math.ceil(active.size / 2)));
36
74
  if (!done.length && pending.length)
@@ -42,7 +80,9 @@ export function simulateNarutoActivePool(input) {
42
80
  if (shouldFail) {
43
81
  failed.add(generation.work_item_id);
44
82
  conflictItemsEnqueued += 1;
45
- pending.push(conflictResolutionFollowup(generation.work_item_id, input.graph.work_items.length + conflictItemsEnqueued));
83
+ const followup = conflictResolutionFollowup(generation.work_item_id, input.graph.work_items.length + conflictItemsEnqueued);
84
+ pending.push(followup);
85
+ byId.set(followup.id, followup);
46
86
  }
47
87
  else {
48
88
  completed.add(generation.work_item_id);
@@ -57,6 +97,7 @@ export function simulateNarutoActivePool(input) {
57
97
  ...(pending.length ? ['naruto_active_pool_pending_not_drained'] : []),
58
98
  ...(active.size ? ['naruto_active_pool_active_not_drained'] : []),
59
99
  ...(maxObserved > safeActiveWorkers ? ['naruto_active_pool_exceeded_safe_workers'] : []),
100
+ ...(maxObservedWriteLeaseConflicts > 0 ? ['naruto_active_pool_overlapping_write_leases'] : []),
60
101
  ...(duplicateExecutionCount > conflictItemsEnqueued ? ['naruto_active_pool_duplicate_execution_without_retry'] : [])
61
102
  ];
62
103
  return {
@@ -70,11 +111,12 @@ export function simulateNarutoActivePool(input) {
70
111
  max_observed_active_workers: maxObserved,
71
112
  duplicate_execution_count: duplicateExecutionCount,
72
113
  conflict_items_enqueued: conflictItemsEnqueued,
114
+ max_observed_write_lease_conflicts: maxObservedWriteLeaseConflicts,
73
115
  timeline,
74
116
  blockers
75
117
  };
76
118
  }
77
- function popRunnable(pending, completed, active) {
119
+ function popRunnable(pending, completed, active, byId) {
78
120
  const activeWorkIds = new Set([...active.values()].map((item) => item.work_item_id));
79
121
  for (let index = 0; index < pending.length; index += 1) {
80
122
  const item = pending[index];
@@ -85,7 +127,7 @@ function popRunnable(pending, completed, active) {
85
127
  if (!item.dependencies.every((dep) => completed.has(dep)))
86
128
  continue;
87
129
  const writeConflict = [...active.values()].some((generation) => {
88
- const activeItem = pending.find((candidate) => candidate.id === generation.work_item_id);
130
+ const activeItem = byId.get(generation.work_item_id);
89
131
  return activeItem?.write_paths.some((file) => item.write_paths.includes(file));
90
132
  });
91
133
  if (writeConflict)
@@ -95,6 +137,15 @@ function popRunnable(pending, completed, active) {
95
137
  }
96
138
  return null;
97
139
  }
140
+ function countActiveWriteLeaseConflicts(active, byId) {
141
+ const counts = new Map();
142
+ for (const generation of active.values()) {
143
+ const item = byId.get(generation.work_item_id);
144
+ for (const file of item?.write_paths || [])
145
+ counts.set(file, (counts.get(file) || 0) + 1);
146
+ }
147
+ return [...counts.values()].filter((count) => count > 1).reduce((sum, count) => sum + count - 1, 0);
148
+ }
98
149
  function conflictResolutionFollowup(failedId, index) {
99
150
  const id = `NW-CONFLICT-${String(index).padStart(4, '0')}`;
100
151
  return {
@@ -12,6 +12,8 @@ export function buildNarutoGptFinalPack(input) {
12
12
  },
13
13
  role_distribution: input.roleDistribution,
14
14
  changed_files: [...new Set((input.changedFiles || []).map(String))],
15
+ worktree_policy: input.worktreePolicy || input.graph.worktree_policy,
16
+ worktree_diffs: (input.worktreeDiffs || []).slice(0, maxPatchEnvelopes).map(redactSecrets),
15
17
  patch_envelopes: (input.patchEnvelopes || []).slice(0, maxPatchEnvelopes).map(redactSecrets),
16
18
  verification_results: (input.verificationResults || []).slice(0, 200).map(redactSecrets),
17
19
  failed_shards: (input.failedShards || []).slice(0, 100).map(redactSecrets),
@@ -33,6 +33,13 @@ export function buildNarutoWorkGraph(input = {}) {
33
33
  const basePath = normalizeNarutoPath(input.leaseBasePath || '.sneakoscope/naruto/patch-envelopes');
34
34
  const targetPaths = normalizePaths(input.targetPaths || []);
35
35
  const readonlyPaths = normalizePaths(input.readonlyPaths || []);
36
+ const worktreePolicy = input.worktreePolicy || {
37
+ mode: 'patch-envelope-only',
38
+ required: false,
39
+ main_repo_root: null,
40
+ worktree_root: null,
41
+ fallback_reason: writeCapable ? 'git_capability_not_evaluated' : 'readonly_or_write_disabled'
42
+ };
36
43
  const workItems = [];
37
44
  for (let index = 0; index < totalWorkItems; index += 1) {
38
45
  const id = `NW-${String(index + 1).padStart(6, '0')}`;
@@ -64,7 +71,14 @@ export function buildNarutoWorkGraph(input = {}) {
64
71
  requires_patch_envelope: writePaths.length > 0,
65
72
  requires_verification: kind !== 'research' && kind !== 'final_review_input_pack',
66
73
  requires_gpt_final: writePaths.length > 0 || kind === 'final_review_input_pack'
67
- }
74
+ },
75
+ ...(writePaths.length > 0 ? {
76
+ worktree: {
77
+ mode: worktreePolicy.mode,
78
+ required: worktreePolicy.required,
79
+ allocation_required: worktreePolicy.mode === 'git-worktree'
80
+ }
81
+ } : {})
68
82
  });
69
83
  }
70
84
  const activeWaves = planNarutoWorkWaves(workItems, Math.max(1, normalizePositiveInt(input.maxActiveWorkers, requestedClones)));
@@ -88,6 +102,7 @@ export function buildNarutoWorkGraph(input = {}) {
88
102
  active_waves: activeWaves,
89
103
  mixed_work_kinds: mixedWorkKinds,
90
104
  write_allowed_count: writeAllowedCount,
105
+ worktree_policy: worktreePolicy,
91
106
  ok: blockers.length === 0,
92
107
  blockers
93
108
  };
@@ -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') },