@fusengine/harness 0.1.43 → 0.1.45

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 (47) hide show
  1. package/dist/adapters/claude/index.d.mts +2 -2
  2. package/dist/adapters/claude/index.mjs +2 -2
  3. package/dist/adapters/cline/index.mjs +1 -1
  4. package/dist/adapters/codex/index.d.mts +1 -1
  5. package/dist/adapters/codex/index.mjs +1 -1
  6. package/dist/adapters/cursor/index.mjs +1 -1
  7. package/dist/adapters/gemini/index.mjs +1 -1
  8. package/dist/cache/index.d.mts +2 -2
  9. package/dist/cache/index.mjs +2 -2
  10. package/dist/{claude-CeRYMOaG.mjs → claude-DLh0fHWM.mjs} +6 -2
  11. package/dist/cli/bin.mjs +227 -7
  12. package/dist/cli/index.mjs +1 -1
  13. package/dist/config/index.d.mts +1 -1
  14. package/dist/config/index.mjs +1 -2
  15. package/dist/{doc-helpers-D14nkD5D.d.mts → doc-helpers-BNfYWvYv.d.mts} +9 -1
  16. package/dist/{doc-helpers-BhzDmJ18.mjs → doc-helpers-CWZegVdR.mjs} +14 -5
  17. package/dist/{dotenv-DGyLln7U.mjs → dotenv-B9nM4cuQ.mjs} +26 -1
  18. package/dist/evaluate-d7Pp8XJH.mjs +784 -0
  19. package/dist/freshness/index.d.mts +1 -1
  20. package/dist/freshness/index.mjs +1 -1
  21. package/dist/{handle-BTHcKWQ5.mjs → handle-DJaaD2BT.mjs} +849 -395
  22. package/dist/home-state-mKZxP4oZ.mjs +52 -0
  23. package/dist/{index-D7GpOmkl.d.mts → index-BA-SqNR7.d.mts} +1 -1
  24. package/dist/{index-BX-xcvhY.d.mts → index-BUwEmIK-.d.mts} +10 -1
  25. package/dist/{index-DXQfL1u8.d.mts → index-BXPySPxE.d.mts} +7 -1
  26. package/dist/{index-CwOdFBOr.d.mts → index-BxjzFraL.d.mts} +3 -1
  27. package/dist/{index-DN4cZDbU.d.mts → index-CVhw7eA0.d.mts} +111 -23
  28. package/dist/index.d.mts +6 -6
  29. package/dist/index.mjs +8 -9
  30. package/dist/{loader-Bn-DbZmt.mjs → loader-AGz4nK7d.mjs} +1 -1
  31. package/dist/{mcp-store-CnsW9oFj.mjs → mcp-store-BkBDmuxN.mjs} +22 -5
  32. package/dist/policy/index.d.mts +2 -2
  33. package/dist/policy/index.mjs +3 -3
  34. package/dist/refs/index.mjs +2 -2
  35. package/dist/{router-BfX0hJg8.mjs → router-PKVNBHge.mjs} +11 -1
  36. package/dist/{run-jgivVDv6.mjs → run-DLXtA5DH.mjs} +1 -1
  37. package/dist/runtime/index.d.mts +71 -14
  38. package/dist/runtime/index.mjs +3 -3
  39. package/dist/{session-state-Dzq6yrw7.d.mts → session-state-D4F_Dub6.d.mts} +1 -1
  40. package/dist/state/index.d.mts +1 -1
  41. package/dist/{store-CdWOQ9zD.mjs → store-CNjFenWe.mjs} +3 -50
  42. package/dist/tracking/index.d.mts +1 -1
  43. package/dist/tracking/index.mjs +1 -1
  44. package/dist/{validate-xi-zc-22.mjs → validate-DcQaKzau.mjs} +538 -84
  45. package/package.json +1 -1
  46. package/dist/evaluate-CeivW6G0.mjs +0 -477
  47. package/dist/ttl-BG55s6HZ.mjs +0 -20
@@ -1,9 +1,11 @@
1
1
  import { r as resolveMaxLines } from "./limits-CHn8AIL1.mjs";
2
- import { j as countLines } from "./evaluate-CeivW6G0.mjs";
3
- import { r as isDocConsulted, t as formatDocDeny } from "./doc-helpers-BhzDmJ18.mjs";
4
- import { t as routeReferences } from "./router-BfX0hJg8.mjs";
5
- import { join } from "node:path";
6
- import { existsSync, readFileSync, readdirSync } from "node:fs";
2
+ import { c as ttlLabel, t as HOME_DIR } from "./dotenv-B9nM4cuQ.mjs";
3
+ import { t as detectHarness } from "./harness-C8Nxxyn_.mjs";
4
+ import { F as countFrameworkCodeLines, N as PLUGINS_DIR, P as SOLID_REF } from "./evaluate-d7Pp8XJH.mjs";
5
+ import { r as isDocConsulted, t as formatDocDeny } from "./doc-helpers-CWZegVdR.mjs";
6
+ import { t as routeReferences } from "./router-PKVNBHge.mjs";
7
+ import { extname, join } from "node:path";
8
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
7
9
  import { homedir } from "node:os";
8
10
  //#region src/policy/detect-project.ts
9
11
  /** Keywords that signal a development task (APEX trigger). */
@@ -41,6 +43,27 @@ function requiredArchSkill(cwd) {
41
43
  default: return null;
42
44
  }
43
45
  }
46
+ /** Config file names that mark a project as using Tailwind (v3 JS config or legacy). */
47
+ const TAILWIND_CONFIG_FILES = [
48
+ "tailwind.config.js",
49
+ "tailwind.config.ts",
50
+ "tailwind.config.mjs",
51
+ "tailwind.config.cjs"
52
+ ];
53
+ /** True when `package.json` lists `tailwindcss` as a dep (Tailwind v4 CSS-first, no config file). */
54
+ function hasTailwindDependency(dir) {
55
+ const pkgPath = join(dir, "package.json");
56
+ if (!existsSync(pkgPath)) return false;
57
+ try {
58
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
59
+ return "tailwindcss" in {
60
+ ...pkg.dependencies,
61
+ ...pkg.devDependencies
62
+ };
63
+ } catch {
64
+ return false;
65
+ }
66
+ }
44
67
  /** Detect the project type by scanning config files in `dir`. */
45
68
  function detectProjectType(dir) {
46
69
  const has = (f) => existsSync(join(dir, f));
@@ -50,7 +73,7 @@ function detectProjectType(dir) {
50
73
  if (has("svelte.config.js") || has("svelte.config.ts")) return "svelte";
51
74
  if (has("vite.config.ts") && has("src/App.vue")) return "vue";
52
75
  if (has("vite.config.ts") || has("vite.config.js")) return "react";
53
- if (has("tailwind.config.js") || has("tailwind.config.ts")) return "tailwind";
76
+ if (TAILWIND_CONFIG_FILES.some(has) || hasTailwindDependency(dir)) return "tailwind";
54
77
  if (has("composer.json") && has("artisan")) return "laravel";
55
78
  if (has("Gemfile") && has("config/routes.rb")) return "rails";
56
79
  if (has("requirements.txt") || has("pyproject.toml") || has("setup.py")) return has("manage.py") ? "django" : "python";
@@ -64,36 +87,79 @@ function detectProjectType(dir) {
64
87
  return "generic";
65
88
  }
66
89
  //#endregion
67
- //#region src/policy/apex.ts
68
- /** Gate: any one documentation source (Context7, Exa, or web) must have been consulted this session. */
69
- const docConsultedGate = (ctx) => isDocConsulted(ctx.authorizations, ctx.sessionId) ? null : {
70
- kind: "block",
71
- title: "APEX: documentation not consulted",
72
- reason: formatDocDeny(ctx.framework),
73
- actions: ["Use ANY ONE of: mcp__context7__query-docs, mcp__exa__web_search_exa, WebSearch, or WebFetch"]
74
- };
90
+ //#region src/policy/detect-framework.ts
91
+ /**
92
+ * Detect the framework from a file path extension + content patterns.
93
+ * Aligned with the fusengine require-solid-read detection (distinct from
94
+ * {@link detectProjectType}, which scans config files on disk).
95
+ */
96
+ function detectFramework(filePath, content) {
97
+ if (/\.(tsx?|jsx?|vue|svelte)$/.test(filePath) || /from ['"]react|useState|className=/.test(content)) {
98
+ if (/(page|layout|loading|error|route)\.(ts|tsx)$/.test(filePath) || /use client|use server/.test(content)) return "nextjs";
99
+ return "react";
100
+ }
101
+ if (/\.swift$/.test(filePath)) return "swift";
102
+ if (/\.php$/.test(filePath)) return "laravel";
103
+ if (/\.java$/.test(filePath)) return "java";
104
+ if (/\.go$/.test(filePath)) return "go";
105
+ if (/\.rb$/.test(filePath)) return "ruby";
106
+ if (/\.rs$/.test(filePath)) return "rust";
107
+ if (/\.css$/.test(filePath) || /@tailwind|@apply/.test(content)) return "tailwind";
108
+ return "generic";
109
+ }
110
+ //#endregion
111
+ //#region src/policy/apex-gates.ts
75
112
  /** Gate: the routed SOLID references for this edit must have been read. */
76
113
  const solidReadGate = (ctx) => {
77
114
  if (!ctx.refs?.length) return null;
78
115
  const routed = routeReferences(ctx.refs, ctx.filePath, ctx.content);
79
- if (!routed) return null;
116
+ const ttl = ttlLabel(Math.round((ctx.windowMs ?? 120 * 1e3) / 1e3));
117
+ if (!routed) {
118
+ const ref = SOLID_REF[ctx.framework] ?? "generic/";
119
+ return {
120
+ kind: "block",
121
+ title: `APEX: no SOLID reference matched ${ctx.filePath}`,
122
+ reason: `${ctx.refs.length} SOLID reference(s) are loaded but none scored for this edit (expires every ${ttl}) — read the framework skill directly instead.`,
123
+ actions: [`Read ${PLUGINS_DIR}/${ref}SKILL.md`]
124
+ };
125
+ }
80
126
  const read = new Set(ctx.refsRead ?? []);
81
127
  const missing = routed.required.map((r) => r.meta.filePath).filter((p) => !read.has(p));
82
128
  if (missing.length === 0) return null;
129
+ const optional = routed.optional.map((r) => r.meta.filePath);
130
+ const reason = [
131
+ `Read these before editing ${ctx.filePath} (expires every ${ttl}):`,
132
+ ...optional.length ? [`Optional: ${optional.join(", ")}`] : [],
133
+ `Full skill: ${routed.skillPath}`
134
+ ].join("\n");
83
135
  return {
84
136
  kind: "block",
85
137
  title: `APEX: read SOLID references for ${ctx.framework}`,
86
- reason: `Read these before editing ${ctx.filePath}:`,
138
+ reason,
87
139
  actions: missing
88
140
  };
89
141
  };
90
142
  /** Gate: the required prior agents (explore + research) must have run within the window. */
91
- const freshnessGate = (ctx) => ctx.agentsFresh === false ? {
143
+ const freshnessGate = (ctx) => {
144
+ if (ctx.agentsFresh !== false) return null;
145
+ const missing = ctx.missingAgents?.length ? ctx.missingAgents : ["explore-codebase", "research-expert"];
146
+ const ttl = ttlLabel(Math.round((ctx.windowMs ?? 120 * 1e3) / 1e3));
147
+ return {
148
+ kind: "block",
149
+ title: "APEX: explore + research required",
150
+ reason: `Run ${missing.join(" and ")} within the freshness window (${ttl} TTL) before editing ${ctx.framework}.`,
151
+ actions: missing.map((name) => `Launch the ${name} agent`)
152
+ };
153
+ };
154
+ //#endregion
155
+ //#region src/policy/apex.ts
156
+ /** Gate: BOTH Context7 AND Exa (or a web fallback alone) must have been consulted this session. */
157
+ const docConsultedGate = (ctx) => isDocConsulted(ctx.authorizations, ctx.sessionId) ? null : {
92
158
  kind: "block",
93
- title: "APEX: explore + research required",
94
- reason: `Run explore-codebase and research-expert (within the freshness window) before editing ${ctx.framework}.`,
95
- actions: ["Launch the explore-codebase agent", "Launch the research-expert agent"]
96
- } : null;
159
+ title: "APEX: documentation not consulted",
160
+ reason: formatDocDeny(ctx.framework),
161
+ actions: ["Use BOTH mcp__context7__query-docs AND mcp__exa__web_search_exa, or a web fallback alone (WebSearch/WebFetch)"]
162
+ };
97
163
  /** Gate: brainstorming must precede creating new files when flagged. */
98
164
  const brainstormGate = (ctx) => ctx.brainstormRequired && ctx.brainstormFresh === false ? {
99
165
  kind: "block",
@@ -212,7 +278,7 @@ const SWIFT_TYPE_RE = /^(class|struct) [^\n{]* \{/m;
212
278
  function reactGate(filePath, content, fileLines) {
213
279
  const v = [];
214
280
  const max = resolveMaxLines();
215
- const lines = fileLines ?? countLines(content);
281
+ const lines = fileLines ?? countFrameworkCodeLines(content);
216
282
  if (lines > max) v.push(`File has ${lines} lines (limit: ${max}). Split to hooks/, components/, or utils/.`);
217
283
  if (filePath.includes("/components/") && TS_DECL_RE.test(content)) v.push("Interface/type in component. Move to src/interfaces/ or src/types/.");
218
284
  if (HOOK_RE.test(content) && !filePath.includes("/hooks/")) v.push("Custom hook defined outside hooks/ directory. Move to hooks/.");
@@ -222,7 +288,7 @@ function reactGate(filePath, content, fileLines) {
222
288
  function nextGate(filePath, content, fileLines) {
223
289
  const v = [];
224
290
  const max = /(page|layout|loading|error|not-found)\.(tsx|ts)$/.test(filePath) ? 150 : 100;
225
- const lines = fileLines ?? countLines(content);
291
+ const lines = fileLines ?? countFrameworkCodeLines(content);
226
292
  if (lines > max) v.push(`File has ${lines} lines (limit: ${max}). Split to lib/, hooks/, or components/.`);
227
293
  if (/\/(app|components|modules)\//.test(filePath) && !filePath.includes("/interfaces/") && TS_DECL_RE.test(content)) v.push("Interface/type in component. Move to modules/[feature]/src/interfaces/.");
228
294
  if (CLIENT_HOOK_RE.test(content)) {
@@ -234,7 +300,7 @@ function nextGate(filePath, content, fileLines) {
234
300
  /** Laravel/PHP: line limit, interface outside `/Contracts/`, fat controller (>80). */
235
301
  function laravelGate(filePath, content, fileLines) {
236
302
  const v = [];
237
- const lines = fileLines ?? countLines(content);
303
+ const lines = fileLines ?? countFrameworkCodeLines(content);
238
304
  const max = resolveMaxLines();
239
305
  if (lines > max) v.push(`File has ${lines} lines (limit: ${max}). Split using Services, Actions, or Traits.`);
240
306
  if (PHP_INTERFACE_RE.test(content) && !filePath.includes("/Contracts/")) v.push("Interface defined outside Contracts/. Move to app/Contracts/ or FuseCore/{Module}/App/Contracts/.");
@@ -245,7 +311,7 @@ function laravelGate(filePath, content, fileLines) {
245
311
  function swiftGate(filePath, content, fileLines) {
246
312
  const v = [];
247
313
  const max = /(View|Screen)\.swift$/.test(filePath) ? 150 : 100;
248
- const lines = fileLines ?? countLines(content);
314
+ const lines = fileLines ?? countFrameworkCodeLines(content);
249
315
  if (lines > max) v.push(`File has ${lines} lines (limit: ${max}). Extract to ViewModels, Services, or subviews.`);
250
316
  if (SWIFT_PROTOCOL_RE.test(content) && !filePath.includes("/Protocols/")) v.push("Protocol defined outside Protocols/ directory.");
251
317
  if (filePath.endsWith("ViewModel.swift") && !content.includes("@MainActor")) v.push("ViewModel missing @MainActor annotation.");
@@ -593,6 +659,106 @@ const SWIFT_TRIGGERS = {
593
659
  ]
594
660
  };
595
661
  //#endregion
662
+ //#region src/policy/skill-patterns/tailwind.ts
663
+ /**
664
+ * Tailwind CSS skill-trigger patterns, ported verbatim from
665
+ * `tailwind_skill_triggers.py`. 12 skills.
666
+ *
667
+ * NOTE: Tailwind uses case-SENSITIVE matching (source `re.search` WITHOUT
668
+ * `re.IGNORECASE`). The framework name is registered in
669
+ * `CASE_SENSITIVE_FRAMEWORKS` so the gate compiles these without the `i` flag.
670
+ */
671
+ /** Map of Tailwind sub-skill name → triggering utility-class patterns (case-sensitive). */
672
+ const TAILWIND_TRIGGERS = {
673
+ "tailwindcss-v4": [
674
+ "@theme\\b",
675
+ "@source\\b",
676
+ "@utility\\b",
677
+ "@variant\\b",
678
+ "@import\\s+['\"]tailwindcss",
679
+ "@config\\b"
680
+ ],
681
+ "tailwindcss-layout": [
682
+ "\\b(flex|grid|inline-flex|inline-grid)\\b",
683
+ "(justify|items|place)-(start|end|center|between)",
684
+ "(grid-cols|grid-rows|col-span|row-span)-",
685
+ "(absolute|relative|fixed|sticky)\\b"
686
+ ],
687
+ "tailwindcss-typography": [
688
+ "(font-sans|font-serif|font-mono|font-bold|font-semibold)\\b",
689
+ "(text-xs|text-sm|text-base|text-lg|text-xl|text-2xl)\\b",
690
+ "(tracking-|leading-|line-clamp-)\\b"
691
+ ],
692
+ "tailwindcss-backgrounds": [
693
+ "(bg-gradient|bg-linear|bg-radial|bg-conic)\\b",
694
+ "(from-|via-|to-)\\w+",
695
+ "bg-\\[url\\b"
696
+ ],
697
+ "tailwindcss-borders": ["(rounded-|border-|ring-|outline-|divide-)\\w+", "(border-dashed|border-dotted|border-double)\\b"],
698
+ "tailwindcss-effects": [
699
+ "(shadow-|opacity-|blur-|brightness-|contrast-)\\w+",
700
+ "(backdrop-blur|backdrop-brightness|backdrop-contrast)\\b",
701
+ "(inset-shadow-|mask-)\\w+"
702
+ ],
703
+ "tailwindcss-transforms": [
704
+ "(scale-|rotate-|translate-|skew-)\\w+",
705
+ "(transition-|duration-|ease-|delay-)\\w+",
706
+ "(animate-spin|animate-pulse|animate-bounce)\\b"
707
+ ],
708
+ "tailwindcss-responsive": [
709
+ "(sm:|md:|lg:|xl:|2xl:)\\w+",
710
+ "(@container|container-type)\\b",
711
+ "(min-\\[|max-\\[)\\d+"
712
+ ],
713
+ "tailwindcss-spacing": ["\\b[pm][xytblr]?-\\d+\\b", "(space-x-|space-y-|gap-)\\d+"],
714
+ "tailwindcss-sizing": [
715
+ "\\b[wh]-(full|screen|auto|min|max|fit)\\b",
716
+ "(min-w-|max-w-|min-h-|max-h-)\\w+",
717
+ "(aspect-video|aspect-square)\\b"
718
+ ],
719
+ "tailwindcss-interactivity": ["(cursor-|select-|pointer-events-|scroll-)\\w+", "(snap-|overscroll-|touch-)\\w+"],
720
+ "tailwindcss-custom-styles": [
721
+ "@apply\\b",
722
+ "@utility\\s+\\w+",
723
+ "@variant\\s+\\w+",
724
+ "theme\\(\\s*['\"]"
725
+ ]
726
+ };
727
+ //#endregion
728
+ //#region src/policy/skill-patterns/shadcn-subskills.ts
729
+ /**
730
+ * shadcn/ui domain skill-trigger patterns, ported verbatim from
731
+ * `shadcn_skill_triggers.py` (the standalone shadcn-expert plugin, distinct
732
+ * from the `SHADCN` HTML-detection patterns in `./shadcn.ts` consumed by
733
+ * react/nextjs). 5 sub-skills. Matched case-insensitively (source `re.IGNORECASE`).
734
+ */
735
+ /** Map of shadcn/ui sub-skill name → triggering code patterns. */
736
+ const SHADCN_TRIGGERS = {
737
+ "shadcn-detection": [
738
+ "components\\.json",
739
+ "@radix-ui/",
740
+ "@base-ui/",
741
+ "data-\\[state=",
742
+ "data-\\[disabled\\]"
743
+ ],
744
+ "shadcn-components": [
745
+ "from\\s+['\"].*components/ui/",
746
+ "<(Button|Input|Select|Dialog|Card|Table|Tabs|Badge)\\b",
747
+ "(Popover|Tooltip|Sheet|Drawer|Command|Accordion)\\b"
748
+ ],
749
+ "shadcn-theming": [
750
+ "--(primary|secondary|muted|accent|destructive|foreground):",
751
+ "(cssVariables|themeConfig|globals\\.css)\\b",
752
+ ":root\\s*\\{|\\.dark\\s*\\{"
753
+ ],
754
+ "shadcn-registries": [
755
+ "mcp__shadcn__(search|view|get_add_command|list_items)",
756
+ "bunx.*shadcn@latest\\s+add\\b",
757
+ "(registries|@shadcn|@acme)\\b"
758
+ ],
759
+ "shadcn-migration": ["(@radix-ui.*@base-ui|@base-ui.*@radix-ui)", "(migrat|convert|switch).*(radix|base.?ui)"]
760
+ };
761
+ //#endregion
596
762
  //#region src/policy/skill-trigger-patterns.ts
597
763
  /**
598
764
  * Per-framework code-pattern → required sub-skill data, ported verbatim from the
@@ -607,13 +773,15 @@ const SWIFT_TRIGGERS = {
607
773
  * Frameworks whose Python source omits `re.IGNORECASE`, so their regexes must
608
774
  * be compiled WITHOUT the `i` flag to stay faithful.
609
775
  */
610
- const CASE_SENSITIVE_FRAMEWORKS = /* @__PURE__ */ new Set(["swift"]);
776
+ const CASE_SENSITIVE_FRAMEWORKS = /* @__PURE__ */ new Set(["swift", "tailwind"]);
611
777
  /** Map of required sub-skill name → triggering code patterns, keyed by framework. */
612
778
  const SKILL_TRIGGERS = {
613
779
  react: REACT_TRIGGERS,
614
780
  nextjs: NEXTJS_TRIGGERS,
615
781
  laravel: LARAVEL_TRIGGERS,
616
- swift: SWIFT_TRIGGERS
782
+ swift: SWIFT_TRIGGERS,
783
+ tailwind: TAILWIND_TRIGGERS,
784
+ shadcn: SHADCN_TRIGGERS
617
785
  };
618
786
  //#endregion
619
787
  //#region src/policy/shadcn-project.ts
@@ -636,6 +804,94 @@ function isShadcnProject(cwd) {
636
804
  return UI_DIRS.some((dir) => existsSync(join(cwd, dir)));
637
805
  }
638
806
  //#endregion
807
+ //#region src/runtime/lifecycle/cartographer/detect.ts
808
+ /**
809
+ * Plugin discovery (fs). Ports `detect_plugins.py`: marketplace `plugins` dir
810
+ * resolution + `plugin.json` meta reading.
811
+ */
812
+ /** Sorted entry names of `dir` (alpha, byte-order), or `[]` on error. */
813
+ function sortedNames$1(dir) {
814
+ try {
815
+ return readdirSync(dir).sort((a, b) => a.localeCompare(b, "en"));
816
+ } catch {
817
+ return [];
818
+ }
819
+ }
820
+ /**
821
+ * Read `[version, name]` from `<pluginPath>/.claude-plugin/plugin.json`.
822
+ * @param pluginPath - Absolute plugin directory.
823
+ * @returns The `[version, name]` pair (both "" when absent/unreadable).
824
+ */
825
+ function readPluginMeta(pluginPath) {
826
+ const pj = join(pluginPath, ".claude-plugin", "plugin.json");
827
+ if (!existsSync(pj)) return ["", ""];
828
+ try {
829
+ const meta = JSON.parse(readFileSync(pj, "utf-8"));
830
+ return [meta.version ?? "", meta.name ?? ""];
831
+ } catch {
832
+ return ["", ""];
833
+ }
834
+ }
835
+ /**
836
+ * Auto-detect the marketplace `plugins` dir that contains `cartographer`,
837
+ * falling back to the first marketplace with a `plugins` dir, else `cwd`.
838
+ * Ports `find_marketplace_plugins`, but harness-agnostic: the config dir is
839
+ * derived from the detected harness (`.claude`, `.codex`, `.cursor`, …) via the
840
+ * shared `HOME_DIR` mapping instead of a hardcoded `.claude`.
841
+ * @param home - Home directory (defaults to `~`).
842
+ * @param id - Detected harness id (defaults to runtime detection).
843
+ * @returns The resolved plugins directory.
844
+ */
845
+ function findMarketplacePlugins(home = homedir(), id = detectHarness().id) {
846
+ const mp = join(home, HOME_DIR[id] ?? ".claude", "plugins", "marketplaces");
847
+ const markets = sortedNames$1(mp);
848
+ for (const m of markets) if (existsSync(join(mp, m, "plugins", "cartographer"))) return join(mp, m, "plugins");
849
+ for (const m of markets) if (existsSync(join(mp, m, "plugins"))) return join(mp, m, "plugins");
850
+ return process.cwd();
851
+ }
852
+ //#endregion
853
+ //#region src/policy/skill-path.ts
854
+ /**
855
+ * Dynamic skill-path resolution (fs). Replaces a hardcoded `PLUGIN_DIR`
856
+ * table (framework -> plugin dir name), which can drift from what's actually
857
+ * installed: scans installed marketplace plugins for a `skills/<name>/SKILL.md`
858
+ * that exists ON DISK, returning the first real absolute path found — never a
859
+ * guessed one.
860
+ *
861
+ * Deliberately lighter than the cartographer's own `scanSkills` (which
862
+ * frontmatter-parses every skill in a plugin): this runs once per missing
863
+ * sub-skill on every gated Write/Edit, so it does a single flat `readdirSync`
864
+ * of the plugins dir + one `existsSync` per candidate — no file reads, no
865
+ * recursion, no `statSync`.
866
+ */
867
+ /** Sorted entry names of `dir` (alpha, byte-order), or `[]` on error. */
868
+ function pluginDirNames(dir) {
869
+ try {
870
+ return readdirSync(dir).sort((a, b) => a.localeCompare(b, "en"));
871
+ } catch {
872
+ return [];
873
+ }
874
+ }
875
+ /**
876
+ * Resolve the real, installed `SKILL.md` path for `skillName`.
877
+ * @param skillName - the sub-skill directory name (e.g. "solid-react",
878
+ * "shadcn-detection", "react-shadcn").
879
+ * @param pluginsDirOverride - override for the marketplace plugins dir
880
+ * (tests); defaults to the auto-detected marketplace root.
881
+ * @returns the absolute `<pluginDir>/skills/<skillName>/SKILL.md` of the
882
+ * first installed plugin that actually has it, or the generic
883
+ * `${PLUGINS_DIR}/<skillName>` fallback when none is found (CI/test
884
+ * environments without a marketplace).
885
+ */
886
+ function resolveSkillPath(skillName, pluginsDirOverride) {
887
+ const pluginsDir = pluginsDirOverride ?? findMarketplacePlugins();
888
+ for (const dir of pluginDirNames(pluginsDir)) {
889
+ const candidate = join(pluginsDir, dir, "skills", skillName, "SKILL.md");
890
+ if (existsSync(candidate)) return candidate;
891
+ }
892
+ return `${PLUGINS_DIR}/${skillName}`;
893
+ }
894
+ //#endregion
639
895
  //#region src/policy/skill-triggers.ts
640
896
  /**
641
897
  * Detect which sub-skills the written `content` requires for a `framework`.
@@ -655,6 +911,24 @@ function detectRequiredSkills(framework, content) {
655
911
  return required;
656
912
  }
657
913
  /**
914
+ * `.tsx`/`.jsx` — the only extensions `check-tailwind-skill.py` actually gates
915
+ * (its regex also lists `.css`/`.html`, but both hit an early return right
916
+ * after, in the Python source).
917
+ */
918
+ const TAILWIND_FILE = /\.(tsx|jsx)$/;
919
+ /** Ported verbatim from `check-tailwind-skill.py`'s `TW_PATTERN`. */
920
+ const TAILWIND_CONTENT = /(className|class).*['"].*\b(flex|grid|p-|m-|w-|h-|text-|bg-|border-)/;
921
+ /**
922
+ * True when `filePath`/`content` match the Python Tailwind gate's trigger
923
+ * condition. React/Next.js components embed Tailwind utility classes in
924
+ * `className` — this check fires IN ADDITION TO the primary framework gate,
925
+ * never instead of it: {@link detectFramework} keeps returning "react"/
926
+ * "nextjs" for these files (framework SOLID rules stay correct).
927
+ */
928
+ function usesTailwindUtilities(filePath, content) {
929
+ return TAILWIND_FILE.test(filePath) && TAILWIND_CONTENT.test(content);
930
+ }
931
+ /**
658
932
  * Block when a required sub-skill's `skills/<name>/` path is absent from
659
933
  * `refsRead`. Mirrors `specific_skill_consulted`, which confirms a skill was
660
934
  * read by checking the tracking file contains `skills/<name>/`.
@@ -664,10 +938,15 @@ function detectRequiredSkills(framework, content) {
664
938
  * @param forcedSkill - a skill the detected modular architecture forces (optional).
665
939
  * @param cwd - project root; when set and not a shadcn project, `*-shadcn`
666
940
  * requirements are skipped (ports the Python `is_shadcn_project` filter).
941
+ * @param filePath - the file being written; when it's a `.tsx`/`.jsx` file
942
+ * with Tailwind utility classes in `className`, the "tailwind" domain
943
+ * skills are merged in alongside `framework`'s own (ports the separate
944
+ * `check-tailwind-skill.py` gate, independent of react/nextjs).
667
945
  * @returns a `block` Prompt naming the missing sub-skills, or `null` when satisfied.
668
946
  */
669
- function skillTriggerGate(framework, content, refsRead, forcedSkill, cwd) {
947
+ function skillTriggerGate(framework, content, refsRead, forcedSkill, cwd, filePath) {
670
948
  let required = detectRequiredSkills(framework, content);
949
+ if (framework !== "tailwind" && filePath && usesTailwindUtilities(filePath, content)) required = [...required, ...detectRequiredSkills("tailwind", content)];
671
950
  if (forcedSkill && !required.includes(forcedSkill)) required.push(forcedSkill);
672
951
  if (cwd && !isShadcnProject(cwd)) required = required.filter((s) => !s.endsWith("-shadcn"));
673
952
  const missing = required.filter((s) => !refsRead.some((r) => r.includes(`skills/${s}/`)));
@@ -676,10 +955,237 @@ function skillTriggerGate(framework, content, refsRead, forcedSkill, cwd) {
676
955
  kind: "block",
677
956
  title: "Required sub-skill not consulted",
678
957
  reason: `${framework}: code uses APIs covered by ${missing.join(", ")} but its skill reference was not read this session.`,
679
- actions: missing.map((s) => `Read skills/${s}/ before writing this code`)
958
+ actions: missing.map((s) => `Read ${resolveSkillPath(s)}`)
680
959
  };
681
960
  }
682
961
  //#endregion
962
+ //#region src/policy/cartographer/frontmatter.ts
963
+ /**
964
+ * Frontmatter parsing — pure text helpers (no fs). Ports `parse_frontmatter.py`.
965
+ */
966
+ const BLOCK_SCALARS = /* @__PURE__ */ new Set([
967
+ "|",
968
+ ">",
969
+ "|+",
970
+ "|-",
971
+ ">+",
972
+ ">-"
973
+ ]);
974
+ /** Escape regex metacharacters in an arbitrary field name. */
975
+ function escapeRe(s) {
976
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
977
+ }
978
+ /**
979
+ * Extract a single frontmatter field's value from `text`. Strips surrounding
980
+ * quotes; skips YAML block-scalar markers. Returns "" when absent.
981
+ * @param text - The full document text.
982
+ * @param field - The frontmatter key to read.
983
+ * @returns The field value, or "".
984
+ */
985
+ function parseField(text, field) {
986
+ const fm = /^---\s*\n([\s\S]*?)\n---/.exec(text);
987
+ if (!fm || fm[1] === void 0) return "";
988
+ const lineRe = new RegExp(`^${escapeRe(field)}\\s*:\\s*(.+)$`);
989
+ for (const line of fm[1].split("\n")) {
990
+ const m = lineRe.exec(line);
991
+ if (!m || m[1] === void 0) continue;
992
+ const val = m[1].trim().replace(/^["']|["']$/g, "");
993
+ if (BLOCK_SCALARS.has(val)) continue;
994
+ return val;
995
+ }
996
+ return "";
997
+ }
998
+ /**
999
+ * Derive a short description from the body following the frontmatter: the first
1000
+ * non-empty trimmed line, sliced to `maxLen`. Returns "" when none.
1001
+ * @param text - The full document text.
1002
+ * @param maxLen - Maximum length of the returned description.
1003
+ * @returns The body-derived description, or "".
1004
+ */
1005
+ function parseBodyDesc(text, maxLen = 60) {
1006
+ const m = /^---\s*\n[\s\S]*?\n---\s*\n([\s\S]*)/.exec(text);
1007
+ if (!m || m[1] === void 0) return "";
1008
+ for (const raw of m[1].split("\n")) {
1009
+ const line = raw.trim();
1010
+ if (line) return line.slice(0, maxLen);
1011
+ }
1012
+ return "";
1013
+ }
1014
+ //#endregion
1015
+ //#region src/runtime/lifecycle/cartographer/scan-hooks.ts
1016
+ /**
1017
+ * Hook scanning (fs). Ports `_scan_hooks` from `scan_plugins.py`: reads a
1018
+ * plugin's `hooks/hooks.json` and reduces it to a single `("hooks", "<events>", "")`
1019
+ * row. The `hooks` value (or the whole document) is either a MAP of event →
1020
+ * entries (keys are the events) or a LIST of entries each carrying an `event`.
1021
+ */
1022
+ /** True for a non-null plain object (excludes arrays), mirroring Python `isinstance(x, dict)`. */
1023
+ function isPlainObject(v) {
1024
+ return typeof v === "object" && v !== null && !Array.isArray(v);
1025
+ }
1026
+ /** Sort unique non-empty strings alpha (byte-order). */
1027
+ function sortedEvents(events) {
1028
+ return [...new Set([...events].filter((e) => e))].sort((a, b) => a.localeCompare(b, "en"));
1029
+ }
1030
+ /** Derive hook event names from the `hooks.json` data (map keys, or list `event` fields). */
1031
+ function hookEvents(hooksData) {
1032
+ if (isPlainObject(hooksData)) return sortedEvents(Object.keys(hooksData).filter((k) => !k.startsWith("_")));
1033
+ if (Array.isArray(hooksData)) return sortedEvents(hooksData.map((h) => isPlainObject(h) ? String(h.event ?? "") : ""));
1034
+ return [];
1035
+ }
1036
+ /**
1037
+ * Scan `hooks/hooks.json` into a single `("hooks", "<events>", "")` row.
1038
+ * @param root - Absolute plugin directory.
1039
+ * @returns The single hooks row, or `[]` when absent/empty/unreadable.
1040
+ */
1041
+ function scanHooks(root) {
1042
+ const file = join(root, "hooks", "hooks.json");
1043
+ if (!existsSync(file)) return [];
1044
+ try {
1045
+ const raw = JSON.parse(readFileSync(file, "utf-8"));
1046
+ const events = hookEvents(isPlainObject(raw) ? raw.hooks ?? raw : {});
1047
+ return events.length ? [[
1048
+ "hooks",
1049
+ events.join(", "),
1050
+ ""
1051
+ ]] : [];
1052
+ } catch {
1053
+ return [];
1054
+ }
1055
+ }
1056
+ //#endregion
1057
+ //#region src/runtime/lifecycle/cartographer/scan.ts
1058
+ /**
1059
+ * Plugin scanning (fs). Ports `scan_plugins.py`: turns a plugin's
1060
+ * agents/skills/commands/hooks into ordered `[type, name, desc]` rows.
1061
+ */
1062
+ /** Sorted entry names of `dir` (alpha, byte-order), or `[]` on error. */
1063
+ function sortedNames(dir) {
1064
+ try {
1065
+ return readdirSync(dir).sort((a, b) => a.localeCompare(b, "en"));
1066
+ } catch {
1067
+ return [];
1068
+ }
1069
+ }
1070
+ /** Read a `.md` frontmatter field from a file path, "" when missing/unreadable. */
1071
+ function fileField(path, field) {
1072
+ try {
1073
+ return parseField(readFileSync(path, "utf-8"), field);
1074
+ } catch {
1075
+ return "";
1076
+ }
1077
+ }
1078
+ /** Scan `agents/*.md` → `("agent", name, desc[:50])` rows. */
1079
+ function scanAgents(root) {
1080
+ const dir = join(root, "agents");
1081
+ return sortedNames(dir).filter((n) => extname(n) === ".md").map((n) => {
1082
+ const f = join(dir, n);
1083
+ return [
1084
+ "agent",
1085
+ fileField(f, "name") || n.replace(/\.md$/, ""),
1086
+ fileField(f, "description").slice(0, 50)
1087
+ ];
1088
+ });
1089
+ }
1090
+ /** Scan `skills/<dir>/SKILL.md` → `("skill", dir, desc)` rows. */
1091
+ function scanSkills(root) {
1092
+ const dir = join(root, "skills");
1093
+ const rows = [];
1094
+ for (const name of sortedNames(dir)) {
1095
+ try {
1096
+ if (!statSync(join(dir, name)).isDirectory()) continue;
1097
+ } catch {
1098
+ continue;
1099
+ }
1100
+ const skillMd = join(dir, name, "SKILL.md");
1101
+ let desc = "";
1102
+ if (existsSync(skillMd)) {
1103
+ desc = fileField(skillMd, "description");
1104
+ if (!desc) try {
1105
+ desc = parseBodyDesc(readFileSync(skillMd, "utf-8"));
1106
+ } catch {}
1107
+ }
1108
+ rows.push([
1109
+ "skill",
1110
+ name,
1111
+ desc || "(no description)"
1112
+ ]);
1113
+ }
1114
+ return rows;
1115
+ }
1116
+ /** Scan `commands/*.md` → `("command", "/name", desc[:50])` rows. */
1117
+ function scanCommands(root) {
1118
+ const dir = join(root, "commands");
1119
+ return sortedNames(dir).filter((n) => extname(n) === ".md").map((n) => [
1120
+ "command",
1121
+ `/${n.replace(/\.md$/, "")}`,
1122
+ fileField(join(dir, n), "description").slice(0, 50)
1123
+ ]);
1124
+ }
1125
+ /**
1126
+ * Scan a single plugin directory into ordered `[type, name, desc]` rows.
1127
+ * @param pluginDir - Absolute plugin directory.
1128
+ * @returns The agents + skills + commands + hooks rows.
1129
+ */
1130
+ function scanPlugin(pluginDir) {
1131
+ return [
1132
+ ...scanAgents(pluginDir),
1133
+ ...scanSkills(pluginDir),
1134
+ ...scanCommands(pluginDir),
1135
+ ...scanHooks(pluginDir)
1136
+ ];
1137
+ }
1138
+ //#endregion
1139
+ //#region src/policy/expert-agents.ts
1140
+ /**
1141
+ * Dynamic expert-agent resolution (fs). Replaces a hardcoded agent-id table:
1142
+ * scans installed marketplace plugins for an `agents/*.md` whose frontmatter
1143
+ * `name` matches the detected {@link ProjectType}, returning the real
1144
+ * `<plugin>:<agent>` id — never a fictional one absent from disk.
1145
+ *
1146
+ * Reuses the cartographer's own fs primitives ({@link findMarketplacePlugins},
1147
+ * {@link readPluginMeta}, {@link scanAgents}) — the same shallow,
1148
+ * non-recursive scan already run uncached on every SessionStart
1149
+ * (`generateEcosystemMap`), scoped here to just `agents/*.md` (skips
1150
+ * skills/commands/hooks) to keep the per-prompt cost minimal.
1151
+ */
1152
+ /** Sorted plugin-dir names directly under `pluginsDir` (dirs only, alpha), or `[]`. */
1153
+ function pluginDirs(pluginsDir) {
1154
+ let names;
1155
+ try {
1156
+ names = readdirSync(pluginsDir);
1157
+ } catch {
1158
+ return [];
1159
+ }
1160
+ return names.filter((n) => !n.startsWith(".") && !n.startsWith("_")).filter((n) => {
1161
+ try {
1162
+ return statSync(join(pluginsDir, n)).isDirectory();
1163
+ } catch {
1164
+ return false;
1165
+ }
1166
+ }).sort((a, b) => a.localeCompare(b, "en"));
1167
+ }
1168
+ /**
1169
+ * Resolve the real, installed expert-agent id for `type`.
1170
+ * @param type - Detected project type.
1171
+ * @param pluginsDirOverride - Override for the marketplace plugins dir
1172
+ * (tests); defaults to the auto-detected marketplace root.
1173
+ * @returns `<plugin>:<agent>` for the first installed plugin whose agent name
1174
+ * starts with `type`, or "general-purpose" when none is installed.
1175
+ */
1176
+ function getExpertAgent(type, pluginsDirOverride) {
1177
+ const pluginsDir = pluginsDirOverride ?? findMarketplacePlugins();
1178
+ for (const dir of pluginDirs(pluginsDir)) {
1179
+ const pluginPath = join(pluginsDir, dir);
1180
+ const agent = scanAgents(pluginPath).map(([, name]) => name).find((name) => name.toLowerCase().startsWith(type.toLowerCase()));
1181
+ if (agent) {
1182
+ const [, pkgName] = readPluginMeta(pluginPath);
1183
+ return `${pkgName || dir}:${agent}`;
1184
+ }
1185
+ }
1186
+ return "general-purpose";
1187
+ }
1188
+ //#endregion
683
1189
  //#region src/policy/claude-md-context.ts
684
1190
  /** Dev-verb regex (FR/EN) that triggers the APEX preamble (case-insensitive). */
685
1191
  const DEV_VERBS = /(cr[ée]er|impl[ée]menter|ajouter|d[ée]velopper|construire|build|refactor|migrer|implement|create|add|develop)/i;
@@ -712,12 +1218,13 @@ function detectClaudeMdProjectType(cwd) {
712
1218
  * @returns The APEX instruction text.
713
1219
  */
714
1220
  function buildApexInstruction(projectType, maxLines) {
1221
+ const expertAgent = getExpertAgent(projectType);
715
1222
  return `INSTRUCTION: This is a development task. Use APEX methodology:
716
1223
 
717
1224
  **TRACKING FILE**: [project]/.claude/apex/task.json (auto-created on first Write/Edit)
718
1225
 
719
1226
  1. **ANALYZE** (MANDATORY - 3 AGENTS IN PARALLEL):
720
- - explore-codebase + research-expert + ${projectType}-expert (framework expertise)\n - Project type detected: ${projectType}\n\n2. **PLAN**: Use TaskCreate to break down tasks (<${maxLines} lines per file)\n\n3. **EXECUTE**: ${projectType}-expert, follow SOLID principles, split at ${maxLines - 10} lines\n\n4. **EXAMINE**: Run sniper agent after ANY modification\n\n**IMPORTANT**: Read .claude/apex/task.json to check documentation status before writing code.`;
1227
+ - explore-codebase + research-expert + ${expertAgent} (framework expertise)\n - Project type detected: ${projectType}\n\n2. **PLAN**: Use TaskCreate to break down tasks (<${maxLines} lines per file)\n\n3. **EXECUTE**: ${expertAgent}, follow SOLID principles, split at ${maxLines - 10} lines\n\n4. **EXAMINE**: Run sniper agent after ANY modification\n\n**IMPORTANT**: Read .claude/apex/task.json to check documentation status before writing code.`;
721
1228
  }
722
1229
  /**
723
1230
  * Build the UserPromptSubmit injection text: read `~/.claude/CLAUDE.md` and,
@@ -905,59 +1412,6 @@ const EXCLUDE_DIRS = /* @__PURE__ */ new Set([
905
1412
  ".swiftpm"
906
1413
  ]);
907
1414
  //#endregion
908
- //#region src/policy/cartographer/frontmatter.ts
909
- /**
910
- * Frontmatter parsing — pure text helpers (no fs). Ports `parse_frontmatter.py`.
911
- */
912
- const BLOCK_SCALARS = /* @__PURE__ */ new Set([
913
- "|",
914
- ">",
915
- "|+",
916
- "|-",
917
- ">+",
918
- ">-"
919
- ]);
920
- /** Escape regex metacharacters in an arbitrary field name. */
921
- function escapeRe(s) {
922
- return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
923
- }
924
- /**
925
- * Extract a single frontmatter field's value from `text`. Strips surrounding
926
- * quotes; skips YAML block-scalar markers. Returns "" when absent.
927
- * @param text - The full document text.
928
- * @param field - The frontmatter key to read.
929
- * @returns The field value, or "".
930
- */
931
- function parseField(text, field) {
932
- const fm = /^---\s*\n([\s\S]*?)\n---/.exec(text);
933
- if (!fm || fm[1] === void 0) return "";
934
- const lineRe = new RegExp(`^${escapeRe(field)}\\s*:\\s*(.+)$`);
935
- for (const line of fm[1].split("\n")) {
936
- const m = lineRe.exec(line);
937
- if (!m || m[1] === void 0) continue;
938
- const val = m[1].trim().replace(/^["']|["']$/g, "");
939
- if (BLOCK_SCALARS.has(val)) continue;
940
- return val;
941
- }
942
- return "";
943
- }
944
- /**
945
- * Derive a short description from the body following the frontmatter: the first
946
- * non-empty trimmed line, sliced to `maxLen`. Returns "" when none.
947
- * @param text - The full document text.
948
- * @param maxLen - Maximum length of the returned description.
949
- * @returns The body-derived description, or "".
950
- */
951
- function parseBodyDesc(text, maxLen = 60) {
952
- const m = /^---\s*\n[\s\S]*?\n---\s*\n([\s\S]*)/.exec(text);
953
- if (!m || m[1] === void 0) return "";
954
- for (const raw of m[1].split("\n")) {
955
- const line = raw.trim();
956
- if (line) return line.slice(0, maxLen);
957
- }
958
- return "";
959
- }
960
- //#endregion
961
1415
  //#region src/policy/cartographer/entry.ts
962
1416
  const ENTRY_RE = /^(.*?)\[([^\]]+)\]\(([^)]+)\)\s*(?:—|-{1,2})\s*(.*)$/;
963
1417
  const ENRICH_RE = /^(?:.*?)\[([^\]]+)\]\(([^)]+)\)\s*(?:—|-{1,2})\s*(.+)$/;
@@ -1085,4 +1539,4 @@ function missingSeoElements(html) {
1085
1539
  return missing;
1086
1540
  }
1087
1541
  //#endregion
1088
- export { evaluateApex as A, MAX_EXA_RESULTS as C, APEX_GATES as D, detectCreationIntent as E, detectProjectType as F, isApexCommand as I, requiredArchSkill as L, solidReadGate as M, DEV_KEYWORDS as N, brainstormGate as O, detectModularArchitecture as P, frameworkSolidGate as S, capVerbosity as T, buildClaudeMdContext as _, firstHeading as a, skillTriggerGate as b, parseBodyDesc as c, PROJECT_INDICATORS as d, buildApexTaskContext as f, buildApexInstruction as g, DEV_VERBS as h, firstComment as i, freshnessGate as j, docConsultedGate as k, parseField as l, loadApexTaskState as m, missingSeoElements as n, parseEnrichment as o, buildApexTaskInjection as p, descFromText as r, parseEntry as s, isHtmlLike as t, EXCLUDE_DIRS as u, detectClaudeMdProjectType as v, MAX_TOKENS as w, SKILL_TRIGGERS as x, detectRequiredSkills as y };
1542
+ export { detectCreationIntent as A, detectProjectType as B, findMarketplacePlugins as C, MAX_EXA_RESULTS as D, frameworkSolidGate as E, freshnessGate as F, requiredArchSkill as H, solidReadGate as I, detectFramework as L, brainstormGate as M, docConsultedGate as N, MAX_TOKENS as O, evaluateApex as P, DEV_KEYWORDS as R, usesTailwindUtilities as S, SKILL_TRIGGERS as T, isApexCommand as V, scanPlugin as _, firstHeading as a, detectRequiredSkills as b, EXCLUDE_DIRS as c, buildApexTaskInjection as d, loadApexTaskState as f, detectClaudeMdProjectType as g, buildClaudeMdContext as h, firstComment as i, APEX_GATES as j, capVerbosity as k, PROJECT_INDICATORS as l, buildApexInstruction as m, missingSeoElements as n, parseEnrichment as o, DEV_VERBS as p, descFromText as r, parseEntry as s, isHtmlLike as t, buildApexTaskContext as u, parseBodyDesc as v, readPluginMeta as w, skillTriggerGate as x, parseField as y, detectModularArchitecture as z };