@hyperfixi/mcp-server 2.3.0 → 2.4.0

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.
package/dist/index.js CHANGED
@@ -1,4 +1,8 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ feedbackTools,
4
+ handleFeedbackTool
5
+ } from "./chunk-P76KH42G.js";
2
6
 
3
7
  // src/index.ts
4
8
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -1164,7 +1168,32 @@ var cachedAnalyzer = null;
1164
1168
  function getSemanticAnalyzer() {
1165
1169
  if (!semanticPackage) return null;
1166
1170
  if (!cachedAnalyzer) {
1167
- cachedAnalyzer = semanticPackage.createSemanticAnalyzer();
1171
+ cachedAnalyzer = {
1172
+ analyze(code, language) {
1173
+ if (!semanticPackage.isLanguageRegistered(language)) {
1174
+ return {
1175
+ confidence: 0,
1176
+ errors: [`Language '${language}' is not supported for semantic parsing`]
1177
+ };
1178
+ }
1179
+ const result = semanticPackage.parseSemantic(code, language);
1180
+ const out = {
1181
+ confidence: result.confidence
1182
+ };
1183
+ if (result.tokensConsumed !== void 0) out.tokensConsumed = result.tokensConsumed;
1184
+ if (result.node) {
1185
+ out.command = { name: result.node.action, roles: result.node.roles };
1186
+ }
1187
+ if (result.error) out.errors = [result.error];
1188
+ return out;
1189
+ },
1190
+ supportsLanguage(language) {
1191
+ return semanticPackage.isLanguageRegistered(language);
1192
+ },
1193
+ supportedLanguages() {
1194
+ return semanticPackage.getRegisteredLanguages();
1195
+ }
1196
+ };
1168
1197
  }
1169
1198
  return cachedAnalyzer;
1170
1199
  }
@@ -1462,7 +1491,7 @@ function getValidCommandsForLanguage(language) {
1462
1491
  return englishCommands;
1463
1492
  }
1464
1493
  try {
1465
- const profile = semanticPackage.getProfile(language);
1494
+ const profile = semanticPackage.tryGetProfile(language);
1466
1495
  if (profile && profile.keywords) {
1467
1496
  const commands = [...englishCommands];
1468
1497
  for (const [, translation] of Object.entries(profile.keywords)) {
@@ -1486,7 +1515,7 @@ function getEventHandlerKeywords(language) {
1486
1515
  return variants;
1487
1516
  }
1488
1517
  try {
1489
- const profile = semanticPackage.getProfile(language);
1518
+ const profile = semanticPackage.tryGetProfile(language);
1490
1519
  if (profile && profile.keywords && profile.keywords["on"]) {
1491
1520
  const trans = profile.keywords["on"];
1492
1521
  if (trans.primary) {
@@ -1619,7 +1648,7 @@ function validateHyperscript(code, language) {
1619
1648
  const endVariants = ["end"];
1620
1649
  if (semanticPackage && language !== "en") {
1621
1650
  try {
1622
- const profile = semanticPackage.getProfile(language);
1651
+ const profile = semanticPackage.tryGetProfile(language);
1623
1652
  if (profile?.keywords?.["if"]?.primary) {
1624
1653
  ifVariants.push(profile.keywords["if"].primary);
1625
1654
  }
@@ -1709,7 +1738,7 @@ function validateHyperscript(code, language) {
1709
1738
  const toggleVariants = ["toggle"];
1710
1739
  if (semanticPackage && language !== "en") {
1711
1740
  try {
1712
- const profile = semanticPackage.getProfile(language);
1741
+ const profile = semanticPackage.tryGetProfile(language);
1713
1742
  if (profile?.keywords?.toggle?.primary) {
1714
1743
  toggleVariants.push(profile.keywords.toggle.primary);
1715
1744
  }
@@ -1902,7 +1931,7 @@ function translateSuggestion(suggestion, language) {
1902
1931
  return suggestion;
1903
1932
  }
1904
1933
  try {
1905
- const profile = semanticPackage.getProfile(language);
1934
+ const profile = semanticPackage.tryGetProfile(language);
1906
1935
  if (profile && profile.keywords && profile.keywords[suggestion.command]) {
1907
1936
  const trans = profile.keywords[suggestion.command];
1908
1937
  if (trans.primary) {
@@ -2457,7 +2486,7 @@ function explainInLanguage(code, sourceLanguage, targetLanguage, includeTranslat
2457
2486
  throw new Error("Semantic analyzer not available");
2458
2487
  }
2459
2488
  const parseResult = analyzer.analyze(code, sourceLanguage);
2460
- const profile = semanticPackage.getProfile(targetLanguage);
2489
+ const profile = semanticPackage.tryGetProfile(targetLanguage);
2461
2490
  const explanation = {
2462
2491
  code,
2463
2492
  sourceLanguage,
@@ -2698,7 +2727,28 @@ var cachedAnalyzer2 = null;
2698
2727
  function getSemanticAnalyzer2() {
2699
2728
  if (!semanticPackage2) return null;
2700
2729
  if (!cachedAnalyzer2) {
2701
- cachedAnalyzer2 = semanticPackage2.createSemanticAnalyzer();
2730
+ cachedAnalyzer2 = {
2731
+ analyze(code, language) {
2732
+ if (!semanticPackage2.isLanguageRegistered(language)) {
2733
+ return {
2734
+ confidence: 0,
2735
+ errors: [`Language '${language}' is not supported for semantic parsing`]
2736
+ };
2737
+ }
2738
+ const result = semanticPackage2.parseSemantic(code, language);
2739
+ const out = {
2740
+ confidence: result.confidence
2741
+ };
2742
+ if (result.node) {
2743
+ out.command = { name: result.node.action, roles: result.node.roles };
2744
+ }
2745
+ if (result.error) out.errors = [result.error];
2746
+ return out;
2747
+ },
2748
+ supportsLanguage(language) {
2749
+ return semanticPackage2.isLanguageRegistered(language);
2750
+ }
2751
+ };
2702
2752
  }
2703
2753
  return cachedAnalyzer2;
2704
2754
  }
@@ -2986,7 +3036,7 @@ function getValidCommandsForLanguage2(language) {
2986
3036
  return englishCommands;
2987
3037
  }
2988
3038
  try {
2989
- const profile = semanticPackage2.getProfile(language);
3039
+ const profile = semanticPackage2.tryGetProfile(language);
2990
3040
  if (profile && profile.keywords) {
2991
3041
  const commands = [...englishCommands];
2992
3042
  for (const [engCommand, translation] of Object.entries(profile.keywords)) {
@@ -3177,7 +3227,7 @@ function getKeywordTranslation(command, language) {
3177
3227
  return { label: command };
3178
3228
  }
3179
3229
  try {
3180
- const profile = semanticPackage2.getProfile(language);
3230
+ const profile = semanticPackage2.tryGetProfile(language);
3181
3231
  if (profile && profile.keywords && profile.keywords[command]) {
3182
3232
  const trans = profile.keywords[command];
3183
3233
  if (trans.primary) {
@@ -3193,7 +3243,7 @@ function getReferenceTranslation(ref, language) {
3193
3243
  return { label: ref };
3194
3244
  }
3195
3245
  try {
3196
- const profile = semanticPackage2.getProfile(language);
3246
+ const profile = semanticPackage2.tryGetProfile(language);
3197
3247
  if (profile && profile.references && profile.references[ref]) {
3198
3248
  return { label: profile.references[ref], englishLabel: ref };
3199
3249
  }
@@ -3378,7 +3428,7 @@ function normalizeToEnglish(word, language) {
3378
3428
  return wordLower;
3379
3429
  }
3380
3430
  try {
3381
- const profile = semanticPackage2.getProfile(language);
3431
+ const profile = semanticPackage2.tryGetProfile(language);
3382
3432
  if (profile && profile.keywords) {
3383
3433
  for (const [engKey, translation] of Object.entries(profile.keywords)) {
3384
3434
  const trans = translation;
@@ -3466,7 +3516,7 @@ function getKeywordVariants(engKeyword, language) {
3466
3516
  return variants;
3467
3517
  }
3468
3518
  try {
3469
- const profile = semanticPackage2.getProfile(language);
3519
+ const profile = semanticPackage2.tryGetProfile(language);
3470
3520
  if (profile && profile.keywords && profile.keywords[engKeyword]) {
3471
3521
  const trans = profile.keywords[engKeyword];
3472
3522
  if (trans.primary) {
@@ -4767,9 +4817,9 @@ function handleSearchLanguageElements(args) {
4767
4817
  }
4768
4818
  }
4769
4819
  }
4770
- if (searchTypes.includes("keyword") && semanticPackage3?.languageProfiles) {
4820
+ if (searchTypes.includes("keyword") && semanticPackage3?.KNOWN_PROFILES) {
4771
4821
  const seenKeywords = /* @__PURE__ */ new Set();
4772
- for (const [lang, profile] of Object.entries(semanticPackage3.languageProfiles)) {
4822
+ for (const [lang, profile] of Object.entries(semanticPackage3.KNOWN_PROFILES)) {
4773
4823
  if (!profile.keywords) continue;
4774
4824
  for (const [keyword, trans] of Object.entries(profile.keywords)) {
4775
4825
  const primary = (trans?.primary || "").toLowerCase();
@@ -5741,159 +5791,7 @@ async function generateServerRoutes(args) {
5741
5791
  }
5742
5792
 
5743
5793
  // src/tools/domain-registry-setup.ts
5744
- import { DomainRegistry } from "@lokascript/framework";
5745
- function createDomainRegistry() {
5746
- const registry2 = new DomainRegistry();
5747
- registry2.register({
5748
- name: "sql",
5749
- description: "natural language SQL",
5750
- languages: ["en", "es", "ja", "ar", "ko", "zh", "tr", "fr"],
5751
- inputLabel: "query",
5752
- inputDescription: 'SQL query in natural language (e.g., "select name from users")',
5753
- outputDescription: "a SQL query string",
5754
- getDSL: () => import("@lokascript/domain-sql").then((m) => m.createSQLDSL()),
5755
- getRenderer: () => import("@lokascript/domain-sql").then((m) => m.renderSQL),
5756
- scanConfig: {
5757
- attributes: ["data-sql", "_sql"],
5758
- scriptTypes: ["text/sql-dsl"],
5759
- defaultLanguage: "en"
5760
- }
5761
- });
5762
- registry2.register({
5763
- name: "bdd",
5764
- description: "BDD scenario",
5765
- languages: ["en", "es", "ja", "ar"],
5766
- inputLabel: "scenario",
5767
- inputDescription: 'BDD scenario text (e.g., "given #button is exists")',
5768
- outputDescription: "Playwright test code with Given/When/Then assertions",
5769
- getDSL: () => import("@lokascript/domain-bdd").then((m) => m.createBDDDSL()),
5770
- getRenderer: () => import("@lokascript/domain-bdd").then((m) => m.renderBDD),
5771
- scanConfig: {
5772
- attributes: ["data-bdd", "_bdd"],
5773
- scriptTypes: ["text/bdd"],
5774
- defaultLanguage: "en"
5775
- }
5776
- });
5777
- registry2.register({
5778
- name: "jsx",
5779
- description: "natural language JSX/React",
5780
- languages: ["en", "es", "ja", "ar", "ko", "zh", "tr", "fr"],
5781
- inputLabel: "code",
5782
- inputDescription: 'JSX description in natural language (e.g., "element div with className app")',
5783
- outputDescription: "JSX/React component markup",
5784
- getDSL: () => import("@lokascript/domain-jsx").then((m) => m.createJSXDSL()),
5785
- getRenderer: () => import("@lokascript/domain-jsx").then((m) => m.renderJSX),
5786
- scanConfig: {
5787
- attributes: ["data-jsx", "_jsx"],
5788
- scriptTypes: ["text/jsx-dsl"],
5789
- defaultLanguage: "en"
5790
- }
5791
- });
5792
- registry2.register({
5793
- name: "todo",
5794
- description: "natural language todo management",
5795
- languages: ["en", "es", "ja", "ar", "ko", "zh", "tr", "fr"],
5796
- inputLabel: "command",
5797
- inputDescription: 'Todo command in natural language (e.g., "add milk to groceries")',
5798
- outputDescription: "a structured todo operation object",
5799
- getDSL: () => import("@lokascript/domain-todo").then((m) => m.createTodoDSL()),
5800
- getRenderer: () => import("@lokascript/domain-todo").then((m) => m.renderTodo),
5801
- scanConfig: {
5802
- attributes: ["data-todo", "_todo"],
5803
- scriptTypes: ["text/todo"],
5804
- defaultLanguage: "en"
5805
- }
5806
- });
5807
- registry2.register({
5808
- name: "behaviorspec",
5809
- description: "interaction testing specification",
5810
- languages: ["en", "es", "ja", "ar", "ko", "zh", "tr", "fr"],
5811
- inputLabel: "scenario",
5812
- inputDescription: 'BehaviorSpec scenario text (e.g., "given page /home")',
5813
- outputDescription: "a Playwright interaction test with page assertions",
5814
- getDSL: () => import("@lokascript/domain-behaviorspec").then((m) => m.createBehaviorSpecDSL()),
5815
- getRenderer: () => import("@lokascript/domain-behaviorspec").then((m) => m.renderBehaviorSpec),
5816
- scanConfig: {
5817
- attributes: ["data-spec", "_spec"],
5818
- scriptTypes: ["text/behaviorspec"],
5819
- defaultLanguage: "en"
5820
- }
5821
- });
5822
- registry2.register({
5823
- name: "llm",
5824
- description: "natural language LLM prompts (ask, summarize, analyze, translate)",
5825
- languages: ["en", "es", "ja", "ar", "ko", "zh", "tr", "fr"],
5826
- inputLabel: "command",
5827
- inputDescription: 'LLM command in natural language (e.g., "ask claude to summarize #article as bullets")',
5828
- outputDescription: "an LLMPromptSpec JSON (action, messages, model preferences)",
5829
- getDSL: () => import("@lokascript/domain-llm").then((m) => m.createLLMDSL()),
5830
- getRenderer: () => import("@lokascript/domain-llm").then((m) => m.renderLLM),
5831
- scanConfig: {
5832
- attributes: ["data-llm", "_llm"],
5833
- scriptTypes: ["text/llm"],
5834
- defaultLanguage: "en"
5835
- }
5836
- });
5837
- registry2.register({
5838
- name: "flow",
5839
- description: "declarative reactive data flow pipelines (fetch, poll, stream, submit)",
5840
- languages: ["en", "es", "ja", "ar", "ko", "zh", "tr", "fr"],
5841
- inputLabel: "pipeline",
5842
- inputDescription: 'FlowScript pipeline (e.g., "fetch /api/users as json into #list")',
5843
- outputDescription: "reactive data flow JavaScript (fetch/poll/stream handlers)",
5844
- getDSL: () => import("@lokascript/domain-flow").then((m) => m.createFlowDSL()),
5845
- getRenderer: () => import("@lokascript/domain-flow").then((m) => m.renderFlow),
5846
- scanConfig: {
5847
- attributes: ["data-flow", "_flow"],
5848
- scriptTypes: ["text/flowscript"],
5849
- defaultLanguage: "en"
5850
- }
5851
- });
5852
- registry2.register({
5853
- name: "voice",
5854
- description: "voice/accessibility commands for speech-controlled web interaction",
5855
- languages: ["en", "es", "ja", "ar", "ko", "zh", "tr", "fr"],
5856
- inputLabel: "command",
5857
- inputDescription: 'Voice command in natural language (e.g., "click the submit button")',
5858
- outputDescription: "a DOM interaction command (click, scroll, type, navigate)",
5859
- getDSL: () => import("@lokascript/domain-voice").then((m) => m.createVoiceDSL()),
5860
- getRenderer: () => import("@lokascript/domain-voice").then((m) => m.renderVoice),
5861
- scanConfig: {
5862
- attributes: ["data-voice", "_voice"],
5863
- scriptTypes: ["text/voice"],
5864
- defaultLanguage: "en"
5865
- }
5866
- });
5867
- loadAllSchemas(registry2);
5868
- return registry2;
5869
- }
5870
- async function loadAllSchemas(registry2) {
5871
- const loaders = [
5872
- { domain: "sql", load: () => import("@lokascript/domain-sql").then((m) => [...m.allSchemas]) },
5873
- { domain: "bdd", load: () => import("@lokascript/domain-bdd").then((m) => [...m.allSchemas]) },
5874
- { domain: "jsx", load: () => import("@lokascript/domain-jsx").then((m) => [...m.allSchemas]) },
5875
- { domain: "todo", load: () => import("@lokascript/domain-todo").then((m) => [...m.allSchemas]) },
5876
- {
5877
- domain: "behaviorspec",
5878
- load: () => import("@lokascript/domain-behaviorspec").then((m) => [...m.allSchemas])
5879
- },
5880
- { domain: "llm", load: () => import("@lokascript/domain-llm").then((m) => [...m.allSchemas]) },
5881
- { domain: "flow", load: () => import("@lokascript/domain-flow").then((m) => [...m.allSchemas]) },
5882
- {
5883
- domain: "voice",
5884
- load: () => import("@lokascript/domain-voice").then((m) => [...m.allSchemas])
5885
- }
5886
- ];
5887
- await Promise.allSettled(
5888
- loaders.map(async ({ domain, load }) => {
5889
- try {
5890
- const schemas = await load();
5891
- registry2.setSchemas(domain, schemas);
5892
- } catch {
5893
- }
5894
- })
5895
- );
5896
- }
5794
+ import { createDomainRegistry } from "@lokascript/domain-config";
5897
5795
 
5898
5796
  // src/tools/bdd-extras.ts
5899
5797
  var bddScenarioParser = null;
@@ -6891,15 +6789,13 @@ async function handleExecuteLLM(args, server2, registry2) {
6891
6789
 
6892
6790
  // src/tools/dispatcher.ts
6893
6791
  import { CrossDomainDispatcher } from "@lokascript/framework";
6792
+ import { DOMAIN_PRIORITY } from "@lokascript/domain-config";
6894
6793
  var dispatcher = null;
6895
6794
  function getDispatcher(registry2) {
6896
6795
  if (!dispatcher) {
6897
6796
  dispatcher = new CrossDomainDispatcher(registry2, {
6898
6797
  minConfidence: 0.5,
6899
- // Domains with more specific schemas should win over generic catch-all domains
6900
- // when confidence scores are close. voice/flow have broad patterns that match
6901
- // inputs intended for sql/bdd/todo/jsx.
6902
- priority: ["sql", "bdd", "behaviorspec", "jsx", "todo", "llm", "flow", "voice"]
6798
+ priority: DOMAIN_PRIORITY
6903
6799
  });
6904
6800
  }
6905
6801
  return dispatcher;
@@ -8077,7 +7973,7 @@ function createInlineExtractor() {
8077
7973
  const fs = await import("fs");
8078
7974
  const path = await import("path");
8079
7975
  const crypto = await import("crypto");
8080
- const { glob } = await import("./esm-LUPOYJFX.js");
7976
+ const { glob } = await import("./esm-RQFF6QRX.js");
8081
7977
  const globs = [
8082
7978
  "**/*.html",
8083
7979
  "**/*.htm",
@@ -8298,245 +8194,6 @@ async function loadDomainSchemas(domain) {
8298
8194
  }
8299
8195
  }
8300
8196
 
8301
- // src/tools/feedback-tools.ts
8302
- var feedbackTools = [
8303
- {
8304
- name: "lse_validate_and_feedback",
8305
- description: "Validate LSE input and return structured feedback with machine-actionable hints, corrected examples, and schema context for LLM self-correction.",
8306
- inputSchema: {
8307
- type: "object",
8308
- properties: {
8309
- input: {
8310
- type: "string",
8311
- description: "The LSE input to validate (bracket syntax, JSON, or natural language)"
8312
- },
8313
- format: {
8314
- type: "string",
8315
- description: "Input format: explicit (default), json, protocol (full-fidelity v1.1/v1.2), envelope (v1.2 multi-node), or natural"
8316
- },
8317
- domain: {
8318
- type: "string",
8319
- description: "Domain to validate against (flow, sql, bdd, llm, jsx, todo, behaviorspec, voice)"
8320
- }
8321
- },
8322
- required: ["input"]
8323
- }
8324
- },
8325
- {
8326
- name: "lse_pattern_stats",
8327
- description: "Return pattern hit-rate statistics: success rates by command and language, top diagnostic failures, and outcome distribution.",
8328
- inputSchema: {
8329
- type: "object",
8330
- properties: {
8331
- domain: {
8332
- type: "string",
8333
- description: "Filter stats by domain (optional)"
8334
- }
8335
- }
8336
- }
8337
- }
8338
- ];
8339
- async function handleFeedbackTool(name, args) {
8340
- switch (name) {
8341
- case "lse_validate_and_feedback":
8342
- return handleValidateAndFeedback(args);
8343
- case "lse_pattern_stats":
8344
- return handlePatternStats();
8345
- default:
8346
- return {
8347
- content: [{ type: "text", text: `Unknown feedback tool: ${name}` }],
8348
- isError: true
8349
- };
8350
- }
8351
- }
8352
- async function handleValidateAndFeedback(args) {
8353
- try {
8354
- const { buildFeedback, parseExplicit: parseExplicit2 } = await import("@lokascript/framework");
8355
- const input = args.input;
8356
- if (!input) {
8357
- return {
8358
- content: [{ type: "text", text: "Missing required parameter: input" }],
8359
- isError: true
8360
- };
8361
- }
8362
- const format = args.format || "explicit";
8363
- const domain = args.domain;
8364
- let action;
8365
- let schemaLookup;
8366
- const diagnostics = [];
8367
- if (domain) {
8368
- const schemas = await loadDomainSchemas2(domain);
8369
- if (schemas) {
8370
- schemaLookup = createSchemaLookup(schemas);
8371
- }
8372
- }
8373
- if (format === "explicit") {
8374
- try {
8375
- const result = parseExplicit2(input, { schemaLookup });
8376
- if (result) {
8377
- action = result.action;
8378
- }
8379
- } catch (e) {
8380
- diagnostics.push({
8381
- severity: "error",
8382
- message: e instanceof Error ? e.message : "Parse error: invalid bracket syntax",
8383
- code: "parse-error"
8384
- });
8385
- }
8386
- } else if (format === "json") {
8387
- try {
8388
- const parsed = JSON.parse(input);
8389
- action = parsed.action;
8390
- if (!action) {
8391
- diagnostics.push({
8392
- severity: "error",
8393
- message: 'JSON missing required "action" field',
8394
- code: "missing-role"
8395
- });
8396
- }
8397
- } catch {
8398
- diagnostics.push({
8399
- severity: "error",
8400
- message: "Invalid JSON syntax",
8401
- code: "parse-error"
8402
- });
8403
- }
8404
- } else if (format === "protocol") {
8405
- try {
8406
- const { validateProtocolJSON: validateProtocolJSON2, fromProtocolJSON: fromProtocolJSON2 } = await import("@lokascript/framework");
8407
- const parsed = JSON.parse(input);
8408
- const protoDiags = validateProtocolJSON2(parsed);
8409
- for (const d of protoDiags) {
8410
- diagnostics.push({ severity: d.severity, message: d.message, code: d.code });
8411
- }
8412
- if (protoDiags.filter((d) => d.severity === "error").length === 0) {
8413
- const node = fromProtocolJSON2(parsed);
8414
- action = node.action;
8415
- }
8416
- } catch (e) {
8417
- diagnostics.push({
8418
- severity: "error",
8419
- message: e instanceof Error ? e.message : "Invalid protocol JSON",
8420
- code: "parse-error"
8421
- });
8422
- }
8423
- } else if (format === "envelope") {
8424
- try {
8425
- const { isEnvelope: isEnvelope2, fromEnvelopeJSON: fromEnvelopeJSON2, validateProtocolJSON: validateProtocolJSON2 } = await import("@lokascript/framework");
8426
- const parsed = JSON.parse(input);
8427
- if (!isEnvelope2(parsed)) {
8428
- diagnostics.push({
8429
- severity: "error",
8430
- message: "Not a valid LSE envelope: missing lseVersion or nodes",
8431
- code: "invalid-envelope"
8432
- });
8433
- } else {
8434
- for (let i = 0; i < parsed.nodes.length; i++) {
8435
- const nodeDiags = validateProtocolJSON2(parsed.nodes[i]);
8436
- for (const d of nodeDiags) {
8437
- diagnostics.push({
8438
- severity: d.severity,
8439
- message: `Node ${i}: ${d.message}`,
8440
- code: d.code
8441
- });
8442
- }
8443
- }
8444
- if (diagnostics.filter((d) => d.severity === "error").length === 0) {
8445
- const envelope = fromEnvelopeJSON2(parsed);
8446
- action = envelope.nodes[0]?.action;
8447
- }
8448
- }
8449
- } catch (e) {
8450
- diagnostics.push({
8451
- severity: "error",
8452
- message: e instanceof Error ? e.message : "Invalid envelope JSON",
8453
- code: "parse-error"
8454
- });
8455
- }
8456
- }
8457
- const feedbackFormat = format === "protocol" || format === "envelope" ? "json" : format;
8458
- const feedback = buildFeedback(input, feedbackFormat, diagnostics, schemaLookup, action);
8459
- return {
8460
- content: [{ type: "text", text: JSON.stringify(feedback, null, 2) }]
8461
- };
8462
- } catch (error) {
8463
- return {
8464
- content: [
8465
- {
8466
- type: "text",
8467
- text: `Feedback tool error: ${error instanceof Error ? error.message : String(error)}`
8468
- }
8469
- ],
8470
- isError: true
8471
- };
8472
- }
8473
- }
8474
- var _tracker = null;
8475
- async function getTracker() {
8476
- if (!_tracker) {
8477
- const { PatternTracker } = await import("@lokascript/framework");
8478
- _tracker = new PatternTracker(1e3);
8479
- }
8480
- return _tracker;
8481
- }
8482
- async function handlePatternStats() {
8483
- try {
8484
- const tracker = await getTracker();
8485
- const summary = tracker.getSummary();
8486
- const text = summary.totalEvents === 0 ? "No pattern events recorded yet. Events are recorded as LSE inputs are validated." : JSON.stringify(summary, null, 2);
8487
- return {
8488
- content: [{ type: "text", text }]
8489
- };
8490
- } catch (error) {
8491
- return {
8492
- content: [
8493
- {
8494
- type: "text",
8495
- text: `Pattern stats error: ${error instanceof Error ? error.message : String(error)}`
8496
- }
8497
- ],
8498
- isError: true
8499
- };
8500
- }
8501
- }
8502
- async function loadDomainSchemas2(domain) {
8503
- try {
8504
- switch (domain) {
8505
- case "flow":
8506
- return (await import("@lokascript/domain-flow")).allSchemas;
8507
- case "sql":
8508
- return (await import("@lokascript/domain-sql")).allSchemas;
8509
- case "bdd":
8510
- return (await import("@lokascript/domain-bdd")).allSchemas;
8511
- case "llm":
8512
- return (await import("@lokascript/domain-llm")).allSchemas;
8513
- case "jsx":
8514
- return (await import("@lokascript/domain-jsx")).allSchemas;
8515
- case "todo":
8516
- return (await import("@lokascript/domain-todo")).allSchemas;
8517
- case "behaviorspec":
8518
- return (await import("@lokascript/domain-behaviorspec")).allSchemas;
8519
- case "voice":
8520
- return (await import("@lokascript/domain-voice")).allSchemas;
8521
- default:
8522
- return null;
8523
- }
8524
- } catch {
8525
- return null;
8526
- }
8527
- }
8528
- function createSchemaLookup(schemas) {
8529
- const map = /* @__PURE__ */ new Map();
8530
- for (const s of schemas) {
8531
- map.set(s.action, s);
8532
- }
8533
- return {
8534
- getSchema(action) {
8535
- return map.get(action);
8536
- }
8537
- };
8538
- }
8539
-
8540
8197
  // src/tools/lse-pipeline.ts
8541
8198
  var lsePipelineTools = [
8542
8199
  // ── Phase 7.5: LLM-Native LSE tools ──────────────────────────────────────
@@ -8903,6 +8560,835 @@ async function handleTranslateLse(args) {
8903
8560
  }
8904
8561
  }
8905
8562
 
8563
+ // src/tools/grail-yaml-loader.ts
8564
+ import { readFileSync } from "fs";
8565
+ import { parse as parseYaml } from "yaml";
8566
+ function resolveTemplate(template, context) {
8567
+ return template.replace(/\{(\w+)\}/g, (match, key) => {
8568
+ return key in context ? context[key] : match;
8569
+ });
8570
+ }
8571
+ function loadGrailConfig(yamlPath) {
8572
+ const raw = readFileSync(yamlPath, "utf-8");
8573
+ const doc = parseYaml(raw);
8574
+ if (!doc || typeof doc !== "object") {
8575
+ throw new Error(`grail: invalid YAML at ${yamlPath}`);
8576
+ }
8577
+ const version = doc.version ?? "1.0";
8578
+ const domain = doc.domain ?? { name: "unknown" };
8579
+ const conditions = (doc.conditions ?? []).map(
8580
+ (c) => ({
8581
+ name: c.name,
8582
+ description: c.description ?? "",
8583
+ eval: c.eval
8584
+ })
8585
+ );
8586
+ const affordances = (doc.affordances ?? []).map(
8587
+ (a) => ({
8588
+ name: a.name,
8589
+ title: a.title ?? a.name,
8590
+ preconditions: a.preconditions ?? [],
8591
+ effects: a.effects ?? [],
8592
+ cost: a.cost ?? 1,
8593
+ preferred: a.preferred ?? false,
8594
+ confirm: a.confirm ?? "",
8595
+ action: a.action
8596
+ })
8597
+ );
8598
+ return { version, domain, conditions, affordances };
8599
+ }
8600
+ function findGrailYaml(cwd) {
8601
+ return process.env.GRAIL_YAML ?? `${cwd}/grail.yaml`;
8602
+ }
8603
+
8604
+ // src/tools/grail-registry.ts
8605
+ import { exec } from "child_process";
8606
+ var MAX_OUTPUT = 500;
8607
+ function truncate(s) {
8608
+ return s.length > MAX_OUTPUT ? s.slice(0, MAX_OUTPUT) + "\u2026" : s;
8609
+ }
8610
+ function execShell(cmd, cwd, timeout) {
8611
+ return new Promise((resolve) => {
8612
+ const start = Date.now();
8613
+ exec(cmd, { cwd, timeout: timeout * 1e3 }, (error, stdout, stderr) => {
8614
+ const duration_ms = Date.now() - start;
8615
+ const output = truncate((stdout ?? "") + (stderr ?? "")).trim();
8616
+ const timedOut = !!(error && "killed" in error && error.killed);
8617
+ const exitCode = error ? error.code ?? 1 : 0;
8618
+ resolve({
8619
+ exitCode,
8620
+ output: timedOut ? `Timed out after ${timeout}s` : output,
8621
+ duration_ms,
8622
+ timedOut
8623
+ });
8624
+ });
8625
+ });
8626
+ }
8627
+ var GrailRegistry = class {
8628
+ conditions = /* @__PURE__ */ new Map();
8629
+ affordances = /* @__PURE__ */ new Map();
8630
+ _cwd;
8631
+ constructor(config, cwd) {
8632
+ this._cwd = cwd;
8633
+ for (const c of config.conditions) {
8634
+ this.conditions.set(c.name, c);
8635
+ }
8636
+ for (const a of config.affordances) {
8637
+ this.affordances.set(a.name, a);
8638
+ }
8639
+ }
8640
+ _context() {
8641
+ return { cwd: this._cwd };
8642
+ }
8643
+ // ---------- Condition evaluation ----------
8644
+ async evaluate(name) {
8645
+ const cond = this.conditions.get(name);
8646
+ if (!cond) {
8647
+ return {
8648
+ passing: false,
8649
+ output: `Unknown condition: ${name}`,
8650
+ duration_ms: 0,
8651
+ error: "unknown"
8652
+ };
8653
+ }
8654
+ const ctx = this._context();
8655
+ const cmd = resolveTemplate(cond.eval.cmd, ctx);
8656
+ const evalCwd = cond.eval.cwd ? resolveTemplate(cond.eval.cwd, ctx) : this._cwd;
8657
+ const timeout = cond.eval.timeout ?? 30;
8658
+ try {
8659
+ const { exitCode, output, duration_ms } = await execShell(cmd, evalCwd, timeout);
8660
+ return { passing: exitCode === 0, output, duration_ms };
8661
+ } catch (err) {
8662
+ return {
8663
+ passing: false,
8664
+ output: "",
8665
+ duration_ms: 0,
8666
+ error: err instanceof Error ? err.message : String(err)
8667
+ };
8668
+ }
8669
+ }
8670
+ async evaluateAll() {
8671
+ const names = [...this.conditions.keys()];
8672
+ const results = {};
8673
+ const chunks = [];
8674
+ for (let i = 0; i < names.length; i += 8) {
8675
+ chunks.push(names.slice(i, i + 8));
8676
+ }
8677
+ for (const chunk of chunks) {
8678
+ const chunkResults = await Promise.all(
8679
+ chunk.map(async (name) => ({ name, result: await this.evaluate(name) }))
8680
+ );
8681
+ for (const { name, result } of chunkResults) {
8682
+ results[name] = result;
8683
+ }
8684
+ }
8685
+ return results;
8686
+ }
8687
+ // ---------- Affordance checks ----------
8688
+ checkPreconditions(affordanceName, truth) {
8689
+ const aff = this.affordances.get(affordanceName);
8690
+ if (!aff) return { met: false, unmet: [`unknown affordance: ${affordanceName}`] };
8691
+ const unmet = (aff.preconditions ?? []).filter((p) => !truth[p]);
8692
+ return { met: unmet.length === 0, unmet };
8693
+ }
8694
+ // ---------- Affordance execution ----------
8695
+ async run(affordanceName) {
8696
+ const aff = this.affordances.get(affordanceName);
8697
+ if (!aff) {
8698
+ return {
8699
+ success: false,
8700
+ exitCode: -1,
8701
+ output: `Unknown affordance: ${affordanceName}`,
8702
+ duration_ms: 0,
8703
+ cmd: ""
8704
+ };
8705
+ }
8706
+ if (!aff.action) {
8707
+ return {
8708
+ success: false,
8709
+ exitCode: -1,
8710
+ output: `Affordance ${affordanceName} has no action defined`,
8711
+ duration_ms: 0,
8712
+ cmd: ""
8713
+ };
8714
+ }
8715
+ const ctx = this._context();
8716
+ const cmd = resolveTemplate(aff.action.cmd, ctx);
8717
+ const actionCwd = aff.action.cwd ? resolveTemplate(aff.action.cwd, ctx) : this._cwd;
8718
+ const timeout = aff.action.timeout ?? 120;
8719
+ const { exitCode, output, duration_ms } = await execShell(cmd, actionCwd, timeout);
8720
+ return { success: exitCode === 0, exitCode, output, duration_ms, cmd };
8721
+ }
8722
+ // ---------- Introspection ----------
8723
+ conditionMap() {
8724
+ const result = {};
8725
+ for (const [name, cond] of this.conditions) {
8726
+ const producedBy = [];
8727
+ const requiredBy = [];
8728
+ for (const [affName, aff] of this.affordances) {
8729
+ if ((aff.effects ?? []).some(
8730
+ (e) => e === name || e.includes("{") && this._templateMatches(e, name)
8731
+ )) {
8732
+ producedBy.push(affName);
8733
+ }
8734
+ if ((aff.preconditions ?? []).includes(name)) {
8735
+ requiredBy.push(affName);
8736
+ }
8737
+ }
8738
+ result[name] = { description: cond.description, producedBy, requiredBy };
8739
+ }
8740
+ return result;
8741
+ }
8742
+ enablesGraph() {
8743
+ const graph = {};
8744
+ for (const [affName, aff] of this.affordances) {
8745
+ const enables = [];
8746
+ for (const effect of aff.effects ?? []) {
8747
+ for (const [otherName, other] of this.affordances) {
8748
+ if (otherName === affName) continue;
8749
+ if ((other.preconditions ?? []).includes(effect) && !enables.includes(otherName)) {
8750
+ enables.push(otherName);
8751
+ }
8752
+ }
8753
+ }
8754
+ if (enables.length > 0) {
8755
+ graph[affName] = enables;
8756
+ }
8757
+ }
8758
+ return graph;
8759
+ }
8760
+ _templateMatches(template, condition) {
8761
+ const braceIdx = template.indexOf("{");
8762
+ if (braceIdx === -1) return template === condition;
8763
+ const prefix = template.slice(0, braceIdx);
8764
+ return condition.startsWith(prefix);
8765
+ }
8766
+ };
8767
+
8768
+ // src/tools/grail-tools.ts
8769
+ import {
8770
+ parseConditionMap,
8771
+ plan as bfsPlan,
8772
+ applyEffects
8773
+ } from "@lokascript/planner";
8774
+ var grailTools = [
8775
+ {
8776
+ name: "grail_check",
8777
+ description: "Evaluate GRAIL conditions and show available/blocked affordances. Returns passing conditions, failing conditions (with diagnostic output), available actions (preconditions met), and blocked actions (with unmet conditions).",
8778
+ inputSchema: {
8779
+ type: "object",
8780
+ properties: {
8781
+ only: {
8782
+ type: "string",
8783
+ description: "Comma-separated condition names to evaluate (default: all)"
8784
+ }
8785
+ }
8786
+ }
8787
+ },
8788
+ {
8789
+ name: "grail_plan",
8790
+ description: "Compute a dependency-ordered plan to make a goal affordance feasible. Returns ordered steps with phases, costs, and blocked-by links. Pass truth from a prior grail_check to skip re-evaluation.",
8791
+ inputSchema: {
8792
+ type: "object",
8793
+ properties: {
8794
+ goal: {
8795
+ type: "string",
8796
+ description: 'The affordance name to plan for (e.g., "release-publish")'
8797
+ },
8798
+ truth: {
8799
+ type: "object",
8800
+ description: "Pre-evaluated truth vector from grail_check response (condition name \u2192 boolean). If omitted, evaluates all conditions (slow)."
8801
+ }
8802
+ },
8803
+ required: ["goal"]
8804
+ }
8805
+ },
8806
+ {
8807
+ name: "grail_run",
8808
+ description: "Execute a GRAIL affordance. Checks preconditions first \u2014 blocks if unmet. Respects confirmation gates. Use dry_run=true to preview without executing. Pass truth from a prior grail_check to skip re-evaluation of preconditions.",
8809
+ inputSchema: {
8810
+ type: "object",
8811
+ properties: {
8812
+ action: {
8813
+ type: "string",
8814
+ description: 'The affordance name to execute (e.g., "run-lint")'
8815
+ },
8816
+ dry_run: {
8817
+ type: "boolean",
8818
+ description: "Preview the action without executing (default: false)"
8819
+ },
8820
+ confirmed: {
8821
+ type: "boolean",
8822
+ description: "Confirm execution for actions with confirmation gates (default: false)"
8823
+ },
8824
+ truth: {
8825
+ type: "object",
8826
+ description: "Pre-evaluated truth vector from grail_check response (condition name \u2192 boolean). If omitted, evaluates all conditions to check preconditions (slow)."
8827
+ }
8828
+ },
8829
+ required: ["action"]
8830
+ }
8831
+ },
8832
+ {
8833
+ name: "grail_info",
8834
+ description: "Return the full GRAIL workflow graph: conditions with descriptions, affordances with preconditions/effects/cost, and the enables graph.",
8835
+ inputSchema: {
8836
+ type: "object",
8837
+ properties: {}
8838
+ }
8839
+ },
8840
+ {
8841
+ name: "grail_list",
8842
+ description: "List all condition names and affordance names in the GRAIL configuration.",
8843
+ inputSchema: {
8844
+ type: "object",
8845
+ properties: {}
8846
+ }
8847
+ }
8848
+ ];
8849
+ var _registry = null;
8850
+ var _cwd = null;
8851
+ function getRegistry() {
8852
+ const cwd = process.env.GRAIL_CWD ?? process.cwd();
8853
+ if (_registry && _cwd === cwd) return _registry;
8854
+ const yamlPath = findGrailYaml(cwd);
8855
+ const config = loadGrailConfig(yamlPath);
8856
+ _registry = new GrailRegistry(config, cwd);
8857
+ _cwd = cwd;
8858
+ return _registry;
8859
+ }
8860
+ async function handleCheck(args) {
8861
+ const registry2 = getRegistry();
8862
+ const only = getString(args, "only");
8863
+ const results = await registry2.evaluateAll();
8864
+ const cwd = process.env.GRAIL_CWD ?? process.cwd();
8865
+ const conditionNames = only ? only.split(",").map((s) => s.trim()).filter(Boolean) : Object.keys(results);
8866
+ const passing = [];
8867
+ const failing = [];
8868
+ const errors = [];
8869
+ for (const name of conditionNames) {
8870
+ const result = results[name];
8871
+ if (!result) continue;
8872
+ const cond = registry2.conditions.get(name);
8873
+ const desc = cond?.description ?? "";
8874
+ if (result.error) {
8875
+ errors.push({ name, description: desc, error: result.error });
8876
+ } else if (result.passing) {
8877
+ passing.push({ name, description: desc });
8878
+ } else {
8879
+ failing.push({ name, description: desc, output: result.output });
8880
+ }
8881
+ }
8882
+ const truth = {};
8883
+ for (const [name, result] of Object.entries(results)) {
8884
+ truth[name] = result.passing;
8885
+ }
8886
+ const available = [];
8887
+ const blocked = [];
8888
+ for (const [name, aff] of registry2.affordances) {
8889
+ const check = registry2.checkPreconditions(name, truth);
8890
+ if (check.met) {
8891
+ available.push({ name, title: aff.title ?? name, cost: aff.cost ?? 1 });
8892
+ } else {
8893
+ blocked.push({ name, title: aff.title ?? name, unmet: check.unmet });
8894
+ }
8895
+ }
8896
+ return jsonResponse({
8897
+ cwd,
8898
+ passing,
8899
+ failing,
8900
+ errors: errors.length > 0 ? errors : void 0,
8901
+ summary: `${passing.length} passing, ${failing.length} failing`,
8902
+ available,
8903
+ blocked,
8904
+ truth
8905
+ });
8906
+ }
8907
+ async function handlePlan(args) {
8908
+ const goal = getString(args, "goal");
8909
+ if (!goal) return errorResponse("Missing required parameter: goal");
8910
+ const registry2 = getRegistry();
8911
+ const aff = registry2.affordances.get(goal);
8912
+ if (!aff) {
8913
+ return jsonResponse({
8914
+ goal,
8915
+ feasible: false,
8916
+ reason: "unknown affordance",
8917
+ unsatisfiable: [],
8918
+ steps: []
8919
+ });
8920
+ }
8921
+ let truth;
8922
+ const suppliedTruth = args.truth;
8923
+ if (suppliedTruth && typeof suppliedTruth === "object") {
8924
+ truth = suppliedTruth;
8925
+ } else {
8926
+ const results = await registry2.evaluateAll();
8927
+ truth = {};
8928
+ for (const [name, result] of Object.entries(results)) {
8929
+ truth[name] = result.passing;
8930
+ }
8931
+ }
8932
+ const check = registry2.checkPreconditions(goal, truth);
8933
+ if (check.met) {
8934
+ const cmd = aff.action?.cmd ?? "";
8935
+ return jsonResponse({
8936
+ goal,
8937
+ feasible: true,
8938
+ phases: 1,
8939
+ totalCost: aff.cost ?? 1,
8940
+ currentConditions: truth,
8941
+ steps: [
8942
+ {
8943
+ action: goal,
8944
+ cmd,
8945
+ resolves: aff.effects ?? [],
8946
+ phase: 0,
8947
+ cost: aff.cost ?? 1,
8948
+ blocked_by: []
8949
+ }
8950
+ ]
8951
+ });
8952
+ }
8953
+ const conditionMap = registry2.conditionMap();
8954
+ const { defs, conditionNames, mutexPrefixes } = parseConditionMap(conditionMap);
8955
+ const goalDef = defs.find((d) => d.name === goal);
8956
+ if (!goalDef) {
8957
+ return jsonResponse({
8958
+ goal,
8959
+ feasible: false,
8960
+ reason: "unknown affordance in condition map",
8961
+ unsatisfiable: [],
8962
+ steps: []
8963
+ });
8964
+ }
8965
+ const startState = /* @__PURE__ */ new Set();
8966
+ for (const [name, passing] of Object.entries(truth)) {
8967
+ if (passing) startState.add(name);
8968
+ }
8969
+ const unmet = goalDef.preconditions.filter((p) => !startState.has(p));
8970
+ const planSteps = [];
8971
+ const usedKeys = /* @__PURE__ */ new Set();
8972
+ let currentState = startState;
8973
+ for (const precond of unmet) {
8974
+ if (currentState.has(precond)) continue;
8975
+ const paths = bfsPlan(currentState, precond, defs, mutexPrefixes);
8976
+ if (paths.length === 0) {
8977
+ return jsonResponse({
8978
+ goal,
8979
+ feasible: false,
8980
+ reason: "unsatisfiable conditions",
8981
+ unsatisfiable: [precond],
8982
+ currentConditions: truth,
8983
+ steps: []
8984
+ });
8985
+ }
8986
+ for (const step of paths[0]) {
8987
+ if (usedKeys.has(step.key())) continue;
8988
+ usedKeys.add(step.key());
8989
+ planSteps.push({ name: step.name, params: { ...step.params } });
8990
+ const affDef = defs.find((d) => d.name === step.name);
8991
+ if (affDef) currentState = applyEffects(currentState, affDef, step.params, mutexPrefixes);
8992
+ }
8993
+ }
8994
+ planSteps.push({ name: goal, params: {} });
8995
+ const steps = planSteps.map((ps) => {
8996
+ const stepAff = registry2.affordances.get(ps.name);
8997
+ return {
8998
+ action: ps.name,
8999
+ cmd: stepAff?.action?.cmd ?? "",
9000
+ resolves: stepAff?.effects ?? [],
9001
+ phase: 0,
9002
+ cost: stepAff?.cost ?? 1,
9003
+ blocked_by: []
9004
+ };
9005
+ });
9006
+ const condProducer = /* @__PURE__ */ new Map();
9007
+ for (let i = 0; i < steps.length; i++) {
9008
+ for (const effect of steps[i].resolves) {
9009
+ condProducer.set(effect, i);
9010
+ }
9011
+ }
9012
+ for (let i = 0; i < steps.length; i++) {
9013
+ let depPhase = -1;
9014
+ const stepAff = registry2.affordances.get(steps[i].action);
9015
+ for (const precond of stepAff?.preconditions ?? []) {
9016
+ const producerIdx = condProducer.get(precond);
9017
+ if (producerIdx !== void 0 && producerIdx !== i) {
9018
+ depPhase = Math.max(depPhase, steps[producerIdx].phase);
9019
+ }
9020
+ }
9021
+ steps[i].phase = depPhase + 1;
9022
+ }
9023
+ for (let i = 0; i < steps.length; i++) {
9024
+ for (const condition of steps[i].resolves) {
9025
+ for (let j = i + 1; j < steps.length; j++) {
9026
+ const laterAff = registry2.affordances.get(steps[j].action);
9027
+ if ((laterAff?.preconditions ?? []).includes(condition)) {
9028
+ steps[i].blocked_by.push({ condition, needed_by: steps[j].action });
9029
+ }
9030
+ }
9031
+ }
9032
+ }
9033
+ const totalCost = steps.reduce((sum, s) => sum + s.cost, 0);
9034
+ const phases = Math.max(...steps.map((s) => s.phase)) + 1;
9035
+ return jsonResponse({
9036
+ goal,
9037
+ feasible: true,
9038
+ phases,
9039
+ totalCost,
9040
+ currentConditions: truth,
9041
+ steps
9042
+ });
9043
+ }
9044
+ async function handleRun(args) {
9045
+ const action = getString(args, "action");
9046
+ if (!action) return errorResponse("Missing required parameter: action");
9047
+ const dryRun = getBoolean(args, "dry_run");
9048
+ const confirmed = getBoolean(args, "confirmed");
9049
+ const registry2 = getRegistry();
9050
+ const aff = registry2.affordances.get(action);
9051
+ if (!aff) return errorResponse(`Unknown affordance: ${action}`);
9052
+ let truth;
9053
+ const suppliedTruth = args.truth;
9054
+ if (suppliedTruth && typeof suppliedTruth === "object") {
9055
+ truth = suppliedTruth;
9056
+ } else if ((aff.preconditions ?? []).length === 0) {
9057
+ truth = {};
9058
+ } else {
9059
+ const results = await registry2.evaluateAll();
9060
+ truth = {};
9061
+ for (const [name, result2] of Object.entries(results)) {
9062
+ truth[name] = result2.passing;
9063
+ }
9064
+ }
9065
+ const check = registry2.checkPreconditions(action, truth);
9066
+ if (!check.met) {
9067
+ return jsonResponse({
9068
+ action,
9069
+ blocked: true,
9070
+ unmet: check.unmet,
9071
+ hint: `Run grail_plan('${action}') to see what's needed`,
9072
+ cmd: aff.action?.cmd ?? ""
9073
+ });
9074
+ }
9075
+ if (aff.confirm && !confirmed) {
9076
+ return jsonResponse({
9077
+ action,
9078
+ needsConfirmation: true,
9079
+ message: aff.confirm === "true" || aff.confirm === true ? `Are you sure you want to run ${action}?` : aff.confirm,
9080
+ hint: `Re-run with confirmed=true to proceed: grail_run('${action}', confirmed=true)`,
9081
+ cmd: aff.action?.cmd ?? "",
9082
+ effects: aff.effects ?? []
9083
+ });
9084
+ }
9085
+ if (dryRun) {
9086
+ return jsonResponse({
9087
+ action,
9088
+ dry_run: true,
9089
+ preconditions_met: true,
9090
+ cmd: aff.action?.cmd ?? "",
9091
+ effects: aff.effects ?? []
9092
+ });
9093
+ }
9094
+ const result = await registry2.run(action);
9095
+ const driftWarnings = [];
9096
+ if (result.success && (aff.effects?.length ?? 0) > 0) {
9097
+ const postResults = await registry2.evaluateAll();
9098
+ for (const effect of aff.effects ?? []) {
9099
+ if (!effect.includes("{") && postResults[effect] && !postResults[effect].passing) {
9100
+ driftWarnings.push(`Effect '${effect}' declared but not true after execution`);
9101
+ }
9102
+ }
9103
+ }
9104
+ return jsonResponse({
9105
+ action,
9106
+ success: result.success,
9107
+ cmd: result.cmd,
9108
+ exitCode: result.exitCode,
9109
+ duration_ms: result.duration_ms,
9110
+ output: result.output,
9111
+ effects: aff.effects ?? [],
9112
+ driftWarnings: driftWarnings.length > 0 ? driftWarnings : void 0
9113
+ });
9114
+ }
9115
+ function handleInfo() {
9116
+ const registry2 = getRegistry();
9117
+ const conditions = {};
9118
+ for (const [name, cond] of registry2.conditions) {
9119
+ conditions[name] = { description: cond.description };
9120
+ }
9121
+ const affordances = {};
9122
+ for (const [name, aff] of registry2.affordances) {
9123
+ affordances[name] = {
9124
+ title: aff.title ?? name,
9125
+ preconditions: aff.preconditions ?? [],
9126
+ effects: aff.effects ?? [],
9127
+ cost: aff.cost ?? 1,
9128
+ cmd: aff.action?.cmd ?? ""
9129
+ };
9130
+ }
9131
+ return jsonResponse({
9132
+ domain: { name: "hyperfixi" },
9133
+ conditions,
9134
+ affordances,
9135
+ enablesGraph: registry2.enablesGraph()
9136
+ });
9137
+ }
9138
+ function handleList() {
9139
+ const registry2 = getRegistry();
9140
+ return jsonResponse({
9141
+ conditions: [...registry2.conditions.keys()],
9142
+ affordances: [...registry2.affordances.keys()]
9143
+ });
9144
+ }
9145
+ async function handleGrailTool(name, args) {
9146
+ try {
9147
+ switch (name) {
9148
+ case "grail_check":
9149
+ return await handleCheck(args);
9150
+ case "grail_plan":
9151
+ return await handlePlan(args);
9152
+ case "grail_run":
9153
+ return await handleRun(args);
9154
+ case "grail_info":
9155
+ return handleInfo();
9156
+ case "grail_list":
9157
+ return handleList();
9158
+ default:
9159
+ return errorResponse(`Unknown GRAIL tool: ${name}`);
9160
+ }
9161
+ } catch (err) {
9162
+ const message = err instanceof Error ? err.message : String(err);
9163
+ return errorResponse(`GRAIL tool error: ${message}`);
9164
+ }
9165
+ }
9166
+
9167
+ // src/tools/lse-correction.ts
9168
+ var lseCorrectionTools = [
9169
+ {
9170
+ name: "lse_generate_with_correction",
9171
+ description: "Stateless LSE generation helper. Without an attempt, returns a generation prompt and schema context for producing LSE protocol JSON. With an attempt, validates it and returns either the valid node or structured feedback + corrected example + a refined prompt. Call repeatedly (up to maxRounds) to converge on valid LSE. Supports bracket syntax ([command role:value ...]) or raw protocol JSON.",
9172
+ inputSchema: {
9173
+ type: "object",
9174
+ properties: {
9175
+ task: {
9176
+ type: "string",
9177
+ description: 'Natural language description of the UI interaction to generate. E.g. "A button that toggles .active on #sidebar when clicked".'
9178
+ },
9179
+ attempt: {
9180
+ type: "string",
9181
+ description: "Your current LSE attempt \u2014 bracket syntax or JSON string. Omit on round 0 to get a generation prompt."
9182
+ },
9183
+ domain: {
9184
+ type: "string",
9185
+ description: "Optional command domain for schema validation: hyperscript, flow, sql, bdd, voice, etc."
9186
+ },
9187
+ round: {
9188
+ type: "number",
9189
+ description: "Current correction round (0-based, default 0)."
9190
+ },
9191
+ maxRounds: {
9192
+ type: "number",
9193
+ description: "Maximum rounds before giving up (default 3)."
9194
+ }
9195
+ },
9196
+ required: ["task"]
9197
+ }
9198
+ }
9199
+ ];
9200
+ async function handleLseCorrectionTool(name, args) {
9201
+ if (name !== "lse_generate_with_correction") {
9202
+ return errorResponse(`Unknown correction tool: ${name}`);
9203
+ }
9204
+ const task = args.task;
9205
+ if (!task || typeof task !== "string") {
9206
+ return errorResponse("Missing required parameter: task");
9207
+ }
9208
+ const attempt = typeof args.attempt === "string" ? args.attempt.trim() : void 0;
9209
+ const domain = typeof args.domain === "string" ? args.domain : void 0;
9210
+ const round = typeof args.round === "number" ? args.round : 0;
9211
+ const maxRounds = typeof args.maxRounds === "number" ? args.maxRounds : 3;
9212
+ if (!attempt) {
9213
+ return handleGenerationPrompt(task, domain);
9214
+ }
9215
+ if (round >= maxRounds) {
9216
+ return jsonResponse({
9217
+ ok: false,
9218
+ exhausted: true,
9219
+ round,
9220
+ maxRounds,
9221
+ message: `Maximum correction rounds (${maxRounds}) reached without valid LSE. Review the task description and try a simpler interaction pattern.`
9222
+ });
9223
+ }
9224
+ return handleValidateAttempt(task, attempt, domain, round, maxRounds);
9225
+ }
9226
+ async function handleGenerationPrompt(task, domain) {
9227
+ const schemaContext = await buildSchemaContext(domain);
9228
+ const systemContext = [
9229
+ "## LSE (LokaScript Explicit Syntax) Generation",
9230
+ "",
9231
+ "LSE uses bracket syntax: `[command role:value role:value ...]`",
9232
+ "",
9233
+ "### Value types",
9234
+ "- Selector: `.class`, `#id`, `element`, or complex selectors \u2192 `patient:.active`",
9235
+ '- Literal: quoted strings or numbers \u2192 `source:"/api/data"`, `count:3`',
9236
+ "- Reference: built-in refs (me, it, you, body, window, document) \u2192 `destination:me`",
9237
+ "- Flag: boolean flag \u2192 `+async` (enabled), `~async` (disabled)",
9238
+ "",
9239
+ "### Common patterns",
9240
+ "- Toggle class: `[toggle patient:.active]`",
9241
+ "- Add/remove class: `[add patient:.highlight destination:#target]`",
9242
+ '- Set value: `[set patient:#input value:"hello"]`',
9243
+ '- Fetch data: `[fetch source:"/api/data" target:#results]`',
9244
+ "- Show/hide: `[show patient:#modal]` / `[hide patient:#modal]`",
9245
+ "",
9246
+ "### Protocol JSON (alternative format)",
9247
+ "```json",
9248
+ '{"action":"toggle","roles":{"patient":{"type":"selector","value":".active"}}}',
9249
+ "```"
9250
+ ];
9251
+ if (schemaContext) {
9252
+ systemContext.push("", "### Available commands for this domain", schemaContext);
9253
+ }
9254
+ const nextPrompt = [
9255
+ `Task: ${task}`,
9256
+ "",
9257
+ "Generate LSE bracket syntax or protocol JSON for the above task.",
9258
+ "Return ONLY the LSE \u2014 no explanation, no markdown fences.",
9259
+ "If using bracket syntax: `[command role:value ...]`",
9260
+ 'If using JSON: `{"action":"...","roles":{...}}`'
9261
+ ].join("\n");
9262
+ return jsonResponse({
9263
+ ok: false,
9264
+ needsAttempt: true,
9265
+ round: 0,
9266
+ systemContext: systemContext.join("\n"),
9267
+ nextPrompt,
9268
+ instructions: "Use systemContext as your system prompt and nextPrompt as your user message. Pass the response back as `attempt` in the next call."
9269
+ });
9270
+ }
9271
+ async function handleValidateAttempt(task, attempt, domain, round, maxRounds) {
9272
+ const { parseExplicit: parseExplicit2, renderExplicit: renderExplicit2, toProtocolJSON: toProtocolJSON2, validateProtocolJSON: validateProtocolJSON2, fromProtocolJSON: fromProtocolJSON2 } = await import("@lokascript/framework");
9273
+ let node = null;
9274
+ const diagnostics = [];
9275
+ const isJSON = attempt.trimStart().startsWith("{");
9276
+ if (isJSON) {
9277
+ let parsed;
9278
+ try {
9279
+ parsed = JSON.parse(attempt);
9280
+ } catch {
9281
+ diagnostics.push({
9282
+ severity: "error",
9283
+ code: "INVALID_JSON",
9284
+ message: "Attempt is not valid JSON"
9285
+ });
9286
+ }
9287
+ if (parsed) {
9288
+ const protoDiags = validateProtocolJSON2(parsed);
9289
+ for (const d of protoDiags) diagnostics.push(d);
9290
+ if (!protoDiags.some((d) => d.severity === "error")) {
9291
+ try {
9292
+ node = fromProtocolJSON2(parsed);
9293
+ } catch (e) {
9294
+ diagnostics.push({
9295
+ severity: "error",
9296
+ code: "DESERIALIZE_ERROR",
9297
+ message: e instanceof Error ? e.message : String(e)
9298
+ });
9299
+ }
9300
+ }
9301
+ }
9302
+ } else {
9303
+ try {
9304
+ node = parseExplicit2(attempt);
9305
+ } catch (e) {
9306
+ diagnostics.push({
9307
+ severity: "error",
9308
+ code: "PARSE_ERROR",
9309
+ message: e instanceof Error ? e.message : String(e)
9310
+ });
9311
+ }
9312
+ }
9313
+ const hasErrors = diagnostics.some((d) => d.severity === "error");
9314
+ if (!hasErrors && node) {
9315
+ const lse = renderExplicit2(node);
9316
+ const protocol = toProtocolJSON2(node);
9317
+ return jsonResponse({
9318
+ ok: true,
9319
+ round,
9320
+ lse,
9321
+ protocol,
9322
+ diagnostics,
9323
+ message: round === 0 ? "Valid LSE on first attempt." : `Valid LSE after ${round} correction round${round === 1 ? "" : "s"}.`
9324
+ });
9325
+ }
9326
+ const correctedExample = buildCorrectedExample(attempt, diagnostics);
9327
+ const remaining = maxRounds - round - 1;
9328
+ let nextPrompt;
9329
+ if (remaining > 0) {
9330
+ nextPrompt = [
9331
+ `Task: ${task}`,
9332
+ "",
9333
+ "Your previous attempt had errors:",
9334
+ ...diagnostics.filter((d) => d.severity === "error").map((d) => ` \u2022 [${d.code}] ${d.message}`),
9335
+ "",
9336
+ correctedExample ? `Corrected example:
9337
+ ${correctedExample}` : "",
9338
+ "",
9339
+ "Generate a corrected LSE. Return ONLY the LSE \u2014 no explanation."
9340
+ ].filter(Boolean).join("\n");
9341
+ } else {
9342
+ nextPrompt = [
9343
+ `Task: ${task}`,
9344
+ "",
9345
+ "Final attempt. Your previous LSE had these errors:",
9346
+ ...diagnostics.filter((d) => d.severity === "error").map((d) => ` \u2022 [${d.code}] ${d.message}`),
9347
+ "",
9348
+ "Produce the simplest valid LSE you can for this task."
9349
+ ].join("\n");
9350
+ }
9351
+ return jsonResponse({
9352
+ ok: false,
9353
+ round,
9354
+ remainingRounds: remaining,
9355
+ diagnostics,
9356
+ correctedExample,
9357
+ nextPrompt,
9358
+ instructions: remaining > 0 ? `Use nextPrompt to generate a corrected attempt, then call again with round: ${round + 1}.` : "Last correction round \u2014 generate the simplest valid LSE you can."
9359
+ });
9360
+ }
9361
+ function buildCorrectedExample(attempt, diagnostics) {
9362
+ const bracketMatch = attempt.match(/^\[\s*(\w+)/);
9363
+ const jsonMatch = attempt.match(/"action"\s*:\s*"(\w+)"/);
9364
+ const action = bracketMatch?.[1] ?? jsonMatch?.[1];
9365
+ if (!action) return null;
9366
+ const missingRoles = diagnostics.filter((d) => d.code === "MISSING_REQUIRED_ROLE").map((d) => {
9367
+ const m = d.message.match(/role "(\w+)"/);
9368
+ return m?.[1];
9369
+ }).filter(Boolean);
9370
+ if (missingRoles.length > 0) {
9371
+ const roleParts = missingRoles.map((r) => `${r}:.<value>`).join(" ");
9372
+ return `[${action} ${roleParts}]`;
9373
+ }
9374
+ return `[${action} patient:.<selector>]`;
9375
+ }
9376
+ async function buildSchemaContext(domain) {
9377
+ if (!domain) return null;
9378
+ try {
9379
+ const { loadDomainSchemas: loadDomainSchemas2 } = await import("./feedback-tools-S6VDG6F5.js");
9380
+ if (typeof loadDomainSchemas2 !== "function") return null;
9381
+ const schemas = await loadDomainSchemas2(domain);
9382
+ if (!schemas || schemas.length === 0) return null;
9383
+ return schemas.map((s) => {
9384
+ const roles = (s.roles ?? []).map((r) => `${r.role}${r.required ? "" : "?"}`).join(", ");
9385
+ return `- \`${s.action}\`${roles ? ` (${roles})` : ""}${s.description ? ": " + s.description : ""}`;
9386
+ }).join("\n");
9387
+ } catch {
9388
+ return null;
9389
+ }
9390
+ }
9391
+
8906
9392
  // src/resources/content.ts
8907
9393
  function getCommandsReference() {
8908
9394
  return `# Hyperscript Commands Reference
@@ -9930,7 +10416,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
9930
10416
  ...inventoryTools,
9931
10417
  ...trainingDataTools,
9932
10418
  ...feedbackTools,
9933
- ...lsePipelineTools
10419
+ ...lsePipelineTools,
10420
+ ...grailTools,
10421
+ ...lseCorrectionTools
9934
10422
  ]
9935
10423
  };
9936
10424
  });
@@ -9995,6 +10483,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
9995
10483
  if (name === "lse_from_hyperscript" || name === "lse_to_hyperscript") {
9996
10484
  return handleLsePipelineTool(name, args);
9997
10485
  }
10486
+ if (name.startsWith("grail_")) {
10487
+ return handleGrailTool(name, args);
10488
+ }
10489
+ if (name === "lse_generate_with_correction") {
10490
+ return handleLseCorrectionTool(name, args);
10491
+ }
9998
10492
  if (name.startsWith("get_")) {
9999
10493
  return handlePatternTool(name, args);
10000
10494
  }