@hyperfixi/mcp-server 2.3.0 → 2.3.1
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/README.md +14 -0
- package/dist/{esm-LUPOYJFX.js → esm-UBAMNMQH.js} +1 -1
- package/dist/index.js +610 -2
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -45,6 +45,20 @@ Or if installed globally:
|
|
|
45
45
|
}
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
+
## GRAIL Workflow Tools (5)
|
|
49
|
+
|
|
50
|
+
5 stable tools for Claude-native workflow orchestration: evaluate conditions, plan dependency-ordered steps, and execute actions with precondition checks, cost-aware planning, and drift detection.
|
|
51
|
+
|
|
52
|
+
| Tool | Description |
|
|
53
|
+
| ------------- | -------------------------------------------------------------- |
|
|
54
|
+
| `grail_check` | Evaluate conditions, show available/blocked affordances |
|
|
55
|
+
| `grail_plan` | Compute dependency-ordered plan for a goal affordance |
|
|
56
|
+
| `grail_run` | Execute an affordance with precondition and confirmation gates |
|
|
57
|
+
| `grail_info` | Return full workflow graph (conditions, affordances, enables) |
|
|
58
|
+
| `grail_list` | List all condition and affordance names |
|
|
59
|
+
|
|
60
|
+
See **[GRAIL.md](GRAIL.md)** for full documentation, schema reference, and examples.
|
|
61
|
+
|
|
48
62
|
## Available Tools (80)
|
|
49
63
|
|
|
50
64
|
### Validation & Semantic Tools (8)
|
|
@@ -161,7 +161,7 @@ function expand_(str, max, isTop) {
|
|
|
161
161
|
const x2 = numeric(n[0]);
|
|
162
162
|
const y2 = numeric(n[1]);
|
|
163
163
|
const width = Math.max(n[0].length, n[1].length);
|
|
164
|
-
let incr = n.length === 3 && n[2] !== void 0 ? Math.abs(numeric(n[2])) : 1;
|
|
164
|
+
let incr = n.length === 3 && n[2] !== void 0 ? Math.max(Math.abs(numeric(n[2])), 1) : 1;
|
|
165
165
|
let test = lte;
|
|
166
166
|
const reverse = y2 < x2;
|
|
167
167
|
if (reverse) {
|
package/dist/index.js
CHANGED
|
@@ -8077,7 +8077,7 @@ function createInlineExtractor() {
|
|
|
8077
8077
|
const fs = await import("fs");
|
|
8078
8078
|
const path = await import("path");
|
|
8079
8079
|
const crypto = await import("crypto");
|
|
8080
|
-
const { glob } = await import("./esm-
|
|
8080
|
+
const { glob } = await import("./esm-UBAMNMQH.js");
|
|
8081
8081
|
const globs = [
|
|
8082
8082
|
"**/*.html",
|
|
8083
8083
|
"**/*.htm",
|
|
@@ -8903,6 +8903,610 @@ async function handleTranslateLse(args) {
|
|
|
8903
8903
|
}
|
|
8904
8904
|
}
|
|
8905
8905
|
|
|
8906
|
+
// src/tools/grail-yaml-loader.ts
|
|
8907
|
+
import { readFileSync } from "fs";
|
|
8908
|
+
import { parse as parseYaml } from "yaml";
|
|
8909
|
+
function resolveTemplate(template, context) {
|
|
8910
|
+
return template.replace(/\{(\w+)\}/g, (match, key) => {
|
|
8911
|
+
return key in context ? context[key] : match;
|
|
8912
|
+
});
|
|
8913
|
+
}
|
|
8914
|
+
function loadGrailConfig(yamlPath) {
|
|
8915
|
+
const raw = readFileSync(yamlPath, "utf-8");
|
|
8916
|
+
const doc = parseYaml(raw);
|
|
8917
|
+
if (!doc || typeof doc !== "object") {
|
|
8918
|
+
throw new Error(`grail: invalid YAML at ${yamlPath}`);
|
|
8919
|
+
}
|
|
8920
|
+
const version = doc.version ?? "1.0";
|
|
8921
|
+
const domain = doc.domain ?? { name: "unknown" };
|
|
8922
|
+
const conditions = (doc.conditions ?? []).map(
|
|
8923
|
+
(c) => ({
|
|
8924
|
+
name: c.name,
|
|
8925
|
+
description: c.description ?? "",
|
|
8926
|
+
eval: c.eval
|
|
8927
|
+
})
|
|
8928
|
+
);
|
|
8929
|
+
const affordances = (doc.affordances ?? []).map(
|
|
8930
|
+
(a) => ({
|
|
8931
|
+
name: a.name,
|
|
8932
|
+
title: a.title ?? a.name,
|
|
8933
|
+
preconditions: a.preconditions ?? [],
|
|
8934
|
+
effects: a.effects ?? [],
|
|
8935
|
+
cost: a.cost ?? 1,
|
|
8936
|
+
preferred: a.preferred ?? false,
|
|
8937
|
+
confirm: a.confirm ?? "",
|
|
8938
|
+
action: a.action
|
|
8939
|
+
})
|
|
8940
|
+
);
|
|
8941
|
+
return { version, domain, conditions, affordances };
|
|
8942
|
+
}
|
|
8943
|
+
function findGrailYaml(cwd) {
|
|
8944
|
+
return process.env.GRAIL_YAML ?? `${cwd}/grail.yaml`;
|
|
8945
|
+
}
|
|
8946
|
+
|
|
8947
|
+
// src/tools/grail-registry.ts
|
|
8948
|
+
import { exec } from "child_process";
|
|
8949
|
+
var MAX_OUTPUT = 500;
|
|
8950
|
+
function truncate(s) {
|
|
8951
|
+
return s.length > MAX_OUTPUT ? s.slice(0, MAX_OUTPUT) + "\u2026" : s;
|
|
8952
|
+
}
|
|
8953
|
+
function execShell(cmd, cwd, timeout) {
|
|
8954
|
+
return new Promise((resolve) => {
|
|
8955
|
+
const start = Date.now();
|
|
8956
|
+
exec(cmd, { cwd, timeout: timeout * 1e3 }, (error, stdout, stderr) => {
|
|
8957
|
+
const duration_ms = Date.now() - start;
|
|
8958
|
+
const output = truncate((stdout ?? "") + (stderr ?? "")).trim();
|
|
8959
|
+
const timedOut = !!(error && "killed" in error && error.killed);
|
|
8960
|
+
const exitCode = error ? error.code ?? 1 : 0;
|
|
8961
|
+
resolve({
|
|
8962
|
+
exitCode,
|
|
8963
|
+
output: timedOut ? `Timed out after ${timeout}s` : output,
|
|
8964
|
+
duration_ms,
|
|
8965
|
+
timedOut
|
|
8966
|
+
});
|
|
8967
|
+
});
|
|
8968
|
+
});
|
|
8969
|
+
}
|
|
8970
|
+
var GrailRegistry = class {
|
|
8971
|
+
conditions = /* @__PURE__ */ new Map();
|
|
8972
|
+
affordances = /* @__PURE__ */ new Map();
|
|
8973
|
+
_cwd;
|
|
8974
|
+
constructor(config, cwd) {
|
|
8975
|
+
this._cwd = cwd;
|
|
8976
|
+
for (const c of config.conditions) {
|
|
8977
|
+
this.conditions.set(c.name, c);
|
|
8978
|
+
}
|
|
8979
|
+
for (const a of config.affordances) {
|
|
8980
|
+
this.affordances.set(a.name, a);
|
|
8981
|
+
}
|
|
8982
|
+
}
|
|
8983
|
+
_context() {
|
|
8984
|
+
return { cwd: this._cwd };
|
|
8985
|
+
}
|
|
8986
|
+
// ---------- Condition evaluation ----------
|
|
8987
|
+
async evaluate(name) {
|
|
8988
|
+
const cond = this.conditions.get(name);
|
|
8989
|
+
if (!cond) {
|
|
8990
|
+
return {
|
|
8991
|
+
passing: false,
|
|
8992
|
+
output: `Unknown condition: ${name}`,
|
|
8993
|
+
duration_ms: 0,
|
|
8994
|
+
error: "unknown"
|
|
8995
|
+
};
|
|
8996
|
+
}
|
|
8997
|
+
const ctx = this._context();
|
|
8998
|
+
const cmd = resolveTemplate(cond.eval.cmd, ctx);
|
|
8999
|
+
const evalCwd = cond.eval.cwd ? resolveTemplate(cond.eval.cwd, ctx) : this._cwd;
|
|
9000
|
+
const timeout = cond.eval.timeout ?? 30;
|
|
9001
|
+
try {
|
|
9002
|
+
const { exitCode, output, duration_ms } = await execShell(cmd, evalCwd, timeout);
|
|
9003
|
+
return { passing: exitCode === 0, output, duration_ms };
|
|
9004
|
+
} catch (err) {
|
|
9005
|
+
return {
|
|
9006
|
+
passing: false,
|
|
9007
|
+
output: "",
|
|
9008
|
+
duration_ms: 0,
|
|
9009
|
+
error: err instanceof Error ? err.message : String(err)
|
|
9010
|
+
};
|
|
9011
|
+
}
|
|
9012
|
+
}
|
|
9013
|
+
async evaluateAll() {
|
|
9014
|
+
const names = [...this.conditions.keys()];
|
|
9015
|
+
const results = {};
|
|
9016
|
+
const chunks = [];
|
|
9017
|
+
for (let i = 0; i < names.length; i += 8) {
|
|
9018
|
+
chunks.push(names.slice(i, i + 8));
|
|
9019
|
+
}
|
|
9020
|
+
for (const chunk of chunks) {
|
|
9021
|
+
const chunkResults = await Promise.all(
|
|
9022
|
+
chunk.map(async (name) => ({ name, result: await this.evaluate(name) }))
|
|
9023
|
+
);
|
|
9024
|
+
for (const { name, result } of chunkResults) {
|
|
9025
|
+
results[name] = result;
|
|
9026
|
+
}
|
|
9027
|
+
}
|
|
9028
|
+
return results;
|
|
9029
|
+
}
|
|
9030
|
+
// ---------- Affordance checks ----------
|
|
9031
|
+
checkPreconditions(affordanceName, truth) {
|
|
9032
|
+
const aff = this.affordances.get(affordanceName);
|
|
9033
|
+
if (!aff) return { met: false, unmet: [`unknown affordance: ${affordanceName}`] };
|
|
9034
|
+
const unmet = (aff.preconditions ?? []).filter((p) => !truth[p]);
|
|
9035
|
+
return { met: unmet.length === 0, unmet };
|
|
9036
|
+
}
|
|
9037
|
+
// ---------- Affordance execution ----------
|
|
9038
|
+
async run(affordanceName) {
|
|
9039
|
+
const aff = this.affordances.get(affordanceName);
|
|
9040
|
+
if (!aff) {
|
|
9041
|
+
return {
|
|
9042
|
+
success: false,
|
|
9043
|
+
exitCode: -1,
|
|
9044
|
+
output: `Unknown affordance: ${affordanceName}`,
|
|
9045
|
+
duration_ms: 0,
|
|
9046
|
+
cmd: ""
|
|
9047
|
+
};
|
|
9048
|
+
}
|
|
9049
|
+
if (!aff.action) {
|
|
9050
|
+
return {
|
|
9051
|
+
success: false,
|
|
9052
|
+
exitCode: -1,
|
|
9053
|
+
output: `Affordance ${affordanceName} has no action defined`,
|
|
9054
|
+
duration_ms: 0,
|
|
9055
|
+
cmd: ""
|
|
9056
|
+
};
|
|
9057
|
+
}
|
|
9058
|
+
const ctx = this._context();
|
|
9059
|
+
const cmd = resolveTemplate(aff.action.cmd, ctx);
|
|
9060
|
+
const actionCwd = aff.action.cwd ? resolveTemplate(aff.action.cwd, ctx) : this._cwd;
|
|
9061
|
+
const timeout = aff.action.timeout ?? 120;
|
|
9062
|
+
const { exitCode, output, duration_ms } = await execShell(cmd, actionCwd, timeout);
|
|
9063
|
+
return { success: exitCode === 0, exitCode, output, duration_ms, cmd };
|
|
9064
|
+
}
|
|
9065
|
+
// ---------- Introspection ----------
|
|
9066
|
+
conditionMap() {
|
|
9067
|
+
const result = {};
|
|
9068
|
+
for (const [name, cond] of this.conditions) {
|
|
9069
|
+
const producedBy = [];
|
|
9070
|
+
const requiredBy = [];
|
|
9071
|
+
for (const [affName, aff] of this.affordances) {
|
|
9072
|
+
if ((aff.effects ?? []).some(
|
|
9073
|
+
(e) => e === name || e.includes("{") && this._templateMatches(e, name)
|
|
9074
|
+
)) {
|
|
9075
|
+
producedBy.push(affName);
|
|
9076
|
+
}
|
|
9077
|
+
if ((aff.preconditions ?? []).includes(name)) {
|
|
9078
|
+
requiredBy.push(affName);
|
|
9079
|
+
}
|
|
9080
|
+
}
|
|
9081
|
+
result[name] = { description: cond.description, producedBy, requiredBy };
|
|
9082
|
+
}
|
|
9083
|
+
return result;
|
|
9084
|
+
}
|
|
9085
|
+
enablesGraph() {
|
|
9086
|
+
const graph = {};
|
|
9087
|
+
for (const [affName, aff] of this.affordances) {
|
|
9088
|
+
const enables = [];
|
|
9089
|
+
for (const effect of aff.effects ?? []) {
|
|
9090
|
+
for (const [otherName, other] of this.affordances) {
|
|
9091
|
+
if (otherName === affName) continue;
|
|
9092
|
+
if ((other.preconditions ?? []).includes(effect) && !enables.includes(otherName)) {
|
|
9093
|
+
enables.push(otherName);
|
|
9094
|
+
}
|
|
9095
|
+
}
|
|
9096
|
+
}
|
|
9097
|
+
if (enables.length > 0) {
|
|
9098
|
+
graph[affName] = enables;
|
|
9099
|
+
}
|
|
9100
|
+
}
|
|
9101
|
+
return graph;
|
|
9102
|
+
}
|
|
9103
|
+
_templateMatches(template, condition) {
|
|
9104
|
+
const braceIdx = template.indexOf("{");
|
|
9105
|
+
if (braceIdx === -1) return template === condition;
|
|
9106
|
+
const prefix = template.slice(0, braceIdx);
|
|
9107
|
+
return condition.startsWith(prefix);
|
|
9108
|
+
}
|
|
9109
|
+
};
|
|
9110
|
+
|
|
9111
|
+
// src/tools/grail-tools.ts
|
|
9112
|
+
import {
|
|
9113
|
+
parseConditionMap,
|
|
9114
|
+
plan as bfsPlan,
|
|
9115
|
+
applyEffects
|
|
9116
|
+
} from "@lokascript/planner";
|
|
9117
|
+
var grailTools = [
|
|
9118
|
+
{
|
|
9119
|
+
name: "grail_check",
|
|
9120
|
+
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).",
|
|
9121
|
+
inputSchema: {
|
|
9122
|
+
type: "object",
|
|
9123
|
+
properties: {
|
|
9124
|
+
only: {
|
|
9125
|
+
type: "string",
|
|
9126
|
+
description: "Comma-separated condition names to evaluate (default: all)"
|
|
9127
|
+
}
|
|
9128
|
+
}
|
|
9129
|
+
}
|
|
9130
|
+
},
|
|
9131
|
+
{
|
|
9132
|
+
name: "grail_plan",
|
|
9133
|
+
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.",
|
|
9134
|
+
inputSchema: {
|
|
9135
|
+
type: "object",
|
|
9136
|
+
properties: {
|
|
9137
|
+
goal: {
|
|
9138
|
+
type: "string",
|
|
9139
|
+
description: 'The affordance name to plan for (e.g., "release-publish")'
|
|
9140
|
+
},
|
|
9141
|
+
truth: {
|
|
9142
|
+
type: "object",
|
|
9143
|
+
description: "Pre-evaluated truth vector from grail_check response (condition name \u2192 boolean). If omitted, evaluates all conditions (slow)."
|
|
9144
|
+
}
|
|
9145
|
+
},
|
|
9146
|
+
required: ["goal"]
|
|
9147
|
+
}
|
|
9148
|
+
},
|
|
9149
|
+
{
|
|
9150
|
+
name: "grail_run",
|
|
9151
|
+
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.",
|
|
9152
|
+
inputSchema: {
|
|
9153
|
+
type: "object",
|
|
9154
|
+
properties: {
|
|
9155
|
+
action: {
|
|
9156
|
+
type: "string",
|
|
9157
|
+
description: 'The affordance name to execute (e.g., "run-lint")'
|
|
9158
|
+
},
|
|
9159
|
+
dry_run: {
|
|
9160
|
+
type: "boolean",
|
|
9161
|
+
description: "Preview the action without executing (default: false)"
|
|
9162
|
+
},
|
|
9163
|
+
confirmed: {
|
|
9164
|
+
type: "boolean",
|
|
9165
|
+
description: "Confirm execution for actions with confirmation gates (default: false)"
|
|
9166
|
+
},
|
|
9167
|
+
truth: {
|
|
9168
|
+
type: "object",
|
|
9169
|
+
description: "Pre-evaluated truth vector from grail_check response (condition name \u2192 boolean). If omitted, evaluates all conditions to check preconditions (slow)."
|
|
9170
|
+
}
|
|
9171
|
+
},
|
|
9172
|
+
required: ["action"]
|
|
9173
|
+
}
|
|
9174
|
+
},
|
|
9175
|
+
{
|
|
9176
|
+
name: "grail_info",
|
|
9177
|
+
description: "Return the full GRAIL workflow graph: conditions with descriptions, affordances with preconditions/effects/cost, and the enables graph.",
|
|
9178
|
+
inputSchema: {
|
|
9179
|
+
type: "object",
|
|
9180
|
+
properties: {}
|
|
9181
|
+
}
|
|
9182
|
+
},
|
|
9183
|
+
{
|
|
9184
|
+
name: "grail_list",
|
|
9185
|
+
description: "List all condition names and affordance names in the GRAIL configuration.",
|
|
9186
|
+
inputSchema: {
|
|
9187
|
+
type: "object",
|
|
9188
|
+
properties: {}
|
|
9189
|
+
}
|
|
9190
|
+
}
|
|
9191
|
+
];
|
|
9192
|
+
var _registry = null;
|
|
9193
|
+
var _cwd = null;
|
|
9194
|
+
function getRegistry() {
|
|
9195
|
+
const cwd = process.env.GRAIL_CWD ?? process.cwd();
|
|
9196
|
+
if (_registry && _cwd === cwd) return _registry;
|
|
9197
|
+
const yamlPath = findGrailYaml(cwd);
|
|
9198
|
+
const config = loadGrailConfig(yamlPath);
|
|
9199
|
+
_registry = new GrailRegistry(config, cwd);
|
|
9200
|
+
_cwd = cwd;
|
|
9201
|
+
return _registry;
|
|
9202
|
+
}
|
|
9203
|
+
async function handleCheck(args) {
|
|
9204
|
+
const registry2 = getRegistry();
|
|
9205
|
+
const only = getString(args, "only");
|
|
9206
|
+
const results = await registry2.evaluateAll();
|
|
9207
|
+
const cwd = process.env.GRAIL_CWD ?? process.cwd();
|
|
9208
|
+
const conditionNames = only ? only.split(",").map((s) => s.trim()).filter(Boolean) : Object.keys(results);
|
|
9209
|
+
const passing = [];
|
|
9210
|
+
const failing = [];
|
|
9211
|
+
const errors = [];
|
|
9212
|
+
for (const name of conditionNames) {
|
|
9213
|
+
const result = results[name];
|
|
9214
|
+
if (!result) continue;
|
|
9215
|
+
const cond = registry2.conditions.get(name);
|
|
9216
|
+
const desc = cond?.description ?? "";
|
|
9217
|
+
if (result.error) {
|
|
9218
|
+
errors.push({ name, description: desc, error: result.error });
|
|
9219
|
+
} else if (result.passing) {
|
|
9220
|
+
passing.push({ name, description: desc });
|
|
9221
|
+
} else {
|
|
9222
|
+
failing.push({ name, description: desc, output: result.output });
|
|
9223
|
+
}
|
|
9224
|
+
}
|
|
9225
|
+
const truth = {};
|
|
9226
|
+
for (const [name, result] of Object.entries(results)) {
|
|
9227
|
+
truth[name] = result.passing;
|
|
9228
|
+
}
|
|
9229
|
+
const available = [];
|
|
9230
|
+
const blocked = [];
|
|
9231
|
+
for (const [name, aff] of registry2.affordances) {
|
|
9232
|
+
const check = registry2.checkPreconditions(name, truth);
|
|
9233
|
+
if (check.met) {
|
|
9234
|
+
available.push({ name, title: aff.title ?? name, cost: aff.cost ?? 1 });
|
|
9235
|
+
} else {
|
|
9236
|
+
blocked.push({ name, title: aff.title ?? name, unmet: check.unmet });
|
|
9237
|
+
}
|
|
9238
|
+
}
|
|
9239
|
+
return jsonResponse({
|
|
9240
|
+
cwd,
|
|
9241
|
+
passing,
|
|
9242
|
+
failing,
|
|
9243
|
+
errors: errors.length > 0 ? errors : void 0,
|
|
9244
|
+
summary: `${passing.length} passing, ${failing.length} failing`,
|
|
9245
|
+
available,
|
|
9246
|
+
blocked,
|
|
9247
|
+
truth
|
|
9248
|
+
});
|
|
9249
|
+
}
|
|
9250
|
+
async function handlePlan(args) {
|
|
9251
|
+
const goal = getString(args, "goal");
|
|
9252
|
+
if (!goal) return errorResponse("Missing required parameter: goal");
|
|
9253
|
+
const registry2 = getRegistry();
|
|
9254
|
+
const aff = registry2.affordances.get(goal);
|
|
9255
|
+
if (!aff) {
|
|
9256
|
+
return jsonResponse({
|
|
9257
|
+
goal,
|
|
9258
|
+
feasible: false,
|
|
9259
|
+
reason: "unknown affordance",
|
|
9260
|
+
unsatisfiable: [],
|
|
9261
|
+
steps: []
|
|
9262
|
+
});
|
|
9263
|
+
}
|
|
9264
|
+
let truth;
|
|
9265
|
+
const suppliedTruth = args.truth;
|
|
9266
|
+
if (suppliedTruth && typeof suppliedTruth === "object") {
|
|
9267
|
+
truth = suppliedTruth;
|
|
9268
|
+
} else {
|
|
9269
|
+
const results = await registry2.evaluateAll();
|
|
9270
|
+
truth = {};
|
|
9271
|
+
for (const [name, result] of Object.entries(results)) {
|
|
9272
|
+
truth[name] = result.passing;
|
|
9273
|
+
}
|
|
9274
|
+
}
|
|
9275
|
+
const check = registry2.checkPreconditions(goal, truth);
|
|
9276
|
+
if (check.met) {
|
|
9277
|
+
const cmd = aff.action?.cmd ?? "";
|
|
9278
|
+
return jsonResponse({
|
|
9279
|
+
goal,
|
|
9280
|
+
feasible: true,
|
|
9281
|
+
phases: 1,
|
|
9282
|
+
totalCost: aff.cost ?? 1,
|
|
9283
|
+
currentConditions: truth,
|
|
9284
|
+
steps: [
|
|
9285
|
+
{
|
|
9286
|
+
action: goal,
|
|
9287
|
+
cmd,
|
|
9288
|
+
resolves: aff.effects ?? [],
|
|
9289
|
+
phase: 0,
|
|
9290
|
+
cost: aff.cost ?? 1,
|
|
9291
|
+
blocked_by: []
|
|
9292
|
+
}
|
|
9293
|
+
]
|
|
9294
|
+
});
|
|
9295
|
+
}
|
|
9296
|
+
const conditionMap = registry2.conditionMap();
|
|
9297
|
+
const { defs, conditionNames, mutexPrefixes } = parseConditionMap(conditionMap);
|
|
9298
|
+
const goalDef = defs.find((d) => d.name === goal);
|
|
9299
|
+
if (!goalDef) {
|
|
9300
|
+
return jsonResponse({
|
|
9301
|
+
goal,
|
|
9302
|
+
feasible: false,
|
|
9303
|
+
reason: "unknown affordance in condition map",
|
|
9304
|
+
unsatisfiable: [],
|
|
9305
|
+
steps: []
|
|
9306
|
+
});
|
|
9307
|
+
}
|
|
9308
|
+
const startState = /* @__PURE__ */ new Set();
|
|
9309
|
+
for (const [name, passing] of Object.entries(truth)) {
|
|
9310
|
+
if (passing) startState.add(name);
|
|
9311
|
+
}
|
|
9312
|
+
const unmet = goalDef.preconditions.filter((p) => !startState.has(p));
|
|
9313
|
+
const planSteps = [];
|
|
9314
|
+
const usedKeys = /* @__PURE__ */ new Set();
|
|
9315
|
+
let currentState = startState;
|
|
9316
|
+
for (const precond of unmet) {
|
|
9317
|
+
if (currentState.has(precond)) continue;
|
|
9318
|
+
const paths = bfsPlan(currentState, precond, defs, mutexPrefixes);
|
|
9319
|
+
if (paths.length === 0) {
|
|
9320
|
+
return jsonResponse({
|
|
9321
|
+
goal,
|
|
9322
|
+
feasible: false,
|
|
9323
|
+
reason: "unsatisfiable conditions",
|
|
9324
|
+
unsatisfiable: [precond],
|
|
9325
|
+
currentConditions: truth,
|
|
9326
|
+
steps: []
|
|
9327
|
+
});
|
|
9328
|
+
}
|
|
9329
|
+
for (const step of paths[0]) {
|
|
9330
|
+
if (usedKeys.has(step.key())) continue;
|
|
9331
|
+
usedKeys.add(step.key());
|
|
9332
|
+
planSteps.push({ name: step.name, params: { ...step.params } });
|
|
9333
|
+
const affDef = defs.find((d) => d.name === step.name);
|
|
9334
|
+
if (affDef) currentState = applyEffects(currentState, affDef, step.params, mutexPrefixes);
|
|
9335
|
+
}
|
|
9336
|
+
}
|
|
9337
|
+
planSteps.push({ name: goal, params: {} });
|
|
9338
|
+
const steps = planSteps.map((ps) => {
|
|
9339
|
+
const stepAff = registry2.affordances.get(ps.name);
|
|
9340
|
+
return {
|
|
9341
|
+
action: ps.name,
|
|
9342
|
+
cmd: stepAff?.action?.cmd ?? "",
|
|
9343
|
+
resolves: stepAff?.effects ?? [],
|
|
9344
|
+
phase: 0,
|
|
9345
|
+
cost: stepAff?.cost ?? 1,
|
|
9346
|
+
blocked_by: []
|
|
9347
|
+
};
|
|
9348
|
+
});
|
|
9349
|
+
const condProducer = /* @__PURE__ */ new Map();
|
|
9350
|
+
for (let i = 0; i < steps.length; i++) {
|
|
9351
|
+
for (const effect of steps[i].resolves) {
|
|
9352
|
+
condProducer.set(effect, i);
|
|
9353
|
+
}
|
|
9354
|
+
}
|
|
9355
|
+
for (let i = 0; i < steps.length; i++) {
|
|
9356
|
+
let depPhase = -1;
|
|
9357
|
+
const stepAff = registry2.affordances.get(steps[i].action);
|
|
9358
|
+
for (const precond of stepAff?.preconditions ?? []) {
|
|
9359
|
+
const producerIdx = condProducer.get(precond);
|
|
9360
|
+
if (producerIdx !== void 0 && producerIdx !== i) {
|
|
9361
|
+
depPhase = Math.max(depPhase, steps[producerIdx].phase);
|
|
9362
|
+
}
|
|
9363
|
+
}
|
|
9364
|
+
steps[i].phase = depPhase + 1;
|
|
9365
|
+
}
|
|
9366
|
+
for (let i = 0; i < steps.length; i++) {
|
|
9367
|
+
for (const condition of steps[i].resolves) {
|
|
9368
|
+
for (let j = i + 1; j < steps.length; j++) {
|
|
9369
|
+
const laterAff = registry2.affordances.get(steps[j].action);
|
|
9370
|
+
if ((laterAff?.preconditions ?? []).includes(condition)) {
|
|
9371
|
+
steps[i].blocked_by.push({ condition, needed_by: steps[j].action });
|
|
9372
|
+
}
|
|
9373
|
+
}
|
|
9374
|
+
}
|
|
9375
|
+
}
|
|
9376
|
+
const totalCost = steps.reduce((sum, s) => sum + s.cost, 0);
|
|
9377
|
+
const phases = Math.max(...steps.map((s) => s.phase)) + 1;
|
|
9378
|
+
return jsonResponse({
|
|
9379
|
+
goal,
|
|
9380
|
+
feasible: true,
|
|
9381
|
+
phases,
|
|
9382
|
+
totalCost,
|
|
9383
|
+
currentConditions: truth,
|
|
9384
|
+
steps
|
|
9385
|
+
});
|
|
9386
|
+
}
|
|
9387
|
+
async function handleRun(args) {
|
|
9388
|
+
const action = getString(args, "action");
|
|
9389
|
+
if (!action) return errorResponse("Missing required parameter: action");
|
|
9390
|
+
const dryRun = getBoolean(args, "dry_run");
|
|
9391
|
+
const confirmed = getBoolean(args, "confirmed");
|
|
9392
|
+
const registry2 = getRegistry();
|
|
9393
|
+
const aff = registry2.affordances.get(action);
|
|
9394
|
+
if (!aff) return errorResponse(`Unknown affordance: ${action}`);
|
|
9395
|
+
let truth;
|
|
9396
|
+
const suppliedTruth = args.truth;
|
|
9397
|
+
if (suppliedTruth && typeof suppliedTruth === "object") {
|
|
9398
|
+
truth = suppliedTruth;
|
|
9399
|
+
} else if ((aff.preconditions ?? []).length === 0) {
|
|
9400
|
+
truth = {};
|
|
9401
|
+
} else {
|
|
9402
|
+
const results = await registry2.evaluateAll();
|
|
9403
|
+
truth = {};
|
|
9404
|
+
for (const [name, result2] of Object.entries(results)) {
|
|
9405
|
+
truth[name] = result2.passing;
|
|
9406
|
+
}
|
|
9407
|
+
}
|
|
9408
|
+
const check = registry2.checkPreconditions(action, truth);
|
|
9409
|
+
if (!check.met) {
|
|
9410
|
+
return jsonResponse({
|
|
9411
|
+
action,
|
|
9412
|
+
blocked: true,
|
|
9413
|
+
unmet: check.unmet,
|
|
9414
|
+
hint: `Run grail_plan('${action}') to see what's needed`,
|
|
9415
|
+
cmd: aff.action?.cmd ?? ""
|
|
9416
|
+
});
|
|
9417
|
+
}
|
|
9418
|
+
if (aff.confirm && !confirmed) {
|
|
9419
|
+
return jsonResponse({
|
|
9420
|
+
action,
|
|
9421
|
+
needsConfirmation: true,
|
|
9422
|
+
message: aff.confirm === "true" || aff.confirm === true ? `Are you sure you want to run ${action}?` : aff.confirm,
|
|
9423
|
+
hint: `Re-run with confirmed=true to proceed: grail_run('${action}', confirmed=true)`,
|
|
9424
|
+
cmd: aff.action?.cmd ?? "",
|
|
9425
|
+
effects: aff.effects ?? []
|
|
9426
|
+
});
|
|
9427
|
+
}
|
|
9428
|
+
if (dryRun) {
|
|
9429
|
+
return jsonResponse({
|
|
9430
|
+
action,
|
|
9431
|
+
dry_run: true,
|
|
9432
|
+
preconditions_met: true,
|
|
9433
|
+
cmd: aff.action?.cmd ?? "",
|
|
9434
|
+
effects: aff.effects ?? []
|
|
9435
|
+
});
|
|
9436
|
+
}
|
|
9437
|
+
const result = await registry2.run(action);
|
|
9438
|
+
const driftWarnings = [];
|
|
9439
|
+
if (result.success && (aff.effects?.length ?? 0) > 0) {
|
|
9440
|
+
const postResults = await registry2.evaluateAll();
|
|
9441
|
+
for (const effect of aff.effects ?? []) {
|
|
9442
|
+
if (!effect.includes("{") && postResults[effect] && !postResults[effect].passing) {
|
|
9443
|
+
driftWarnings.push(`Effect '${effect}' declared but not true after execution`);
|
|
9444
|
+
}
|
|
9445
|
+
}
|
|
9446
|
+
}
|
|
9447
|
+
return jsonResponse({
|
|
9448
|
+
action,
|
|
9449
|
+
success: result.success,
|
|
9450
|
+
cmd: result.cmd,
|
|
9451
|
+
exitCode: result.exitCode,
|
|
9452
|
+
duration_ms: result.duration_ms,
|
|
9453
|
+
output: result.output,
|
|
9454
|
+
effects: aff.effects ?? [],
|
|
9455
|
+
driftWarnings: driftWarnings.length > 0 ? driftWarnings : void 0
|
|
9456
|
+
});
|
|
9457
|
+
}
|
|
9458
|
+
function handleInfo() {
|
|
9459
|
+
const registry2 = getRegistry();
|
|
9460
|
+
const conditions = {};
|
|
9461
|
+
for (const [name, cond] of registry2.conditions) {
|
|
9462
|
+
conditions[name] = { description: cond.description };
|
|
9463
|
+
}
|
|
9464
|
+
const affordances = {};
|
|
9465
|
+
for (const [name, aff] of registry2.affordances) {
|
|
9466
|
+
affordances[name] = {
|
|
9467
|
+
title: aff.title ?? name,
|
|
9468
|
+
preconditions: aff.preconditions ?? [],
|
|
9469
|
+
effects: aff.effects ?? [],
|
|
9470
|
+
cost: aff.cost ?? 1,
|
|
9471
|
+
cmd: aff.action?.cmd ?? ""
|
|
9472
|
+
};
|
|
9473
|
+
}
|
|
9474
|
+
return jsonResponse({
|
|
9475
|
+
domain: { name: "hyperfixi" },
|
|
9476
|
+
conditions,
|
|
9477
|
+
affordances,
|
|
9478
|
+
enablesGraph: registry2.enablesGraph()
|
|
9479
|
+
});
|
|
9480
|
+
}
|
|
9481
|
+
function handleList() {
|
|
9482
|
+
const registry2 = getRegistry();
|
|
9483
|
+
return jsonResponse({
|
|
9484
|
+
conditions: [...registry2.conditions.keys()],
|
|
9485
|
+
affordances: [...registry2.affordances.keys()]
|
|
9486
|
+
});
|
|
9487
|
+
}
|
|
9488
|
+
async function handleGrailTool(name, args) {
|
|
9489
|
+
try {
|
|
9490
|
+
switch (name) {
|
|
9491
|
+
case "grail_check":
|
|
9492
|
+
return await handleCheck(args);
|
|
9493
|
+
case "grail_plan":
|
|
9494
|
+
return await handlePlan(args);
|
|
9495
|
+
case "grail_run":
|
|
9496
|
+
return await handleRun(args);
|
|
9497
|
+
case "grail_info":
|
|
9498
|
+
return handleInfo();
|
|
9499
|
+
case "grail_list":
|
|
9500
|
+
return handleList();
|
|
9501
|
+
default:
|
|
9502
|
+
return errorResponse(`Unknown GRAIL tool: ${name}`);
|
|
9503
|
+
}
|
|
9504
|
+
} catch (err) {
|
|
9505
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
9506
|
+
return errorResponse(`GRAIL tool error: ${message}`);
|
|
9507
|
+
}
|
|
9508
|
+
}
|
|
9509
|
+
|
|
8906
9510
|
// src/resources/content.ts
|
|
8907
9511
|
function getCommandsReference() {
|
|
8908
9512
|
return `# Hyperscript Commands Reference
|
|
@@ -9930,7 +10534,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
9930
10534
|
...inventoryTools,
|
|
9931
10535
|
...trainingDataTools,
|
|
9932
10536
|
...feedbackTools,
|
|
9933
|
-
...lsePipelineTools
|
|
10537
|
+
...lsePipelineTools,
|
|
10538
|
+
...grailTools
|
|
9934
10539
|
]
|
|
9935
10540
|
};
|
|
9936
10541
|
});
|
|
@@ -9995,6 +10600,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
9995
10600
|
if (name === "lse_from_hyperscript" || name === "lse_to_hyperscript") {
|
|
9996
10601
|
return handleLsePipelineTool(name, args);
|
|
9997
10602
|
}
|
|
10603
|
+
if (name.startsWith("grail_")) {
|
|
10604
|
+
return handleGrailTool(name, args);
|
|
10605
|
+
}
|
|
9998
10606
|
if (name.startsWith("get_")) {
|
|
9999
10607
|
return handlePatternTool(name, args);
|
|
10000
10608
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperfixi/mcp-server",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.1",
|
|
4
4
|
"description": "Model Context Protocol server for HyperFixi hyperscript development",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -40,8 +40,10 @@
|
|
|
40
40
|
"@hyperfixi/patterns-reference": "*",
|
|
41
41
|
"@lokascript/compilation-service": "*",
|
|
42
42
|
"@lokascript/framework": "*",
|
|
43
|
+
"@lokascript/planner": "*",
|
|
43
44
|
"@lokascript/semantic": "*",
|
|
44
|
-
"@modelcontextprotocol/sdk": "^1.25.0"
|
|
45
|
+
"@modelcontextprotocol/sdk": "^1.25.0",
|
|
46
|
+
"yaml": "^2.8.3"
|
|
45
47
|
},
|
|
46
48
|
"peerDependencies": {
|
|
47
49
|
"@hyperfixi/core": "*",
|