From 44dfb9d8d54cc3c08148ecc654d04bf4fce362f3 Mon Sep 17 00:00:00 2001 From: ispyisail Date: Mon, 5 Oct 2026 18:32:42 +1300 Subject: [PATCH 1/3] Assistant house style: a drawing-convention text every AI assistant reads QetSettings::houseStyle()/setHouseStyle() (assistant/house_style), qet.houseStyle()/qet.setHouseStyle() in the script API, a house_style field in qet-assistant.json, and qet_about in the qet MCP server showing it. Co-Authored-By: Claude Opus 5.5 --- misc/qet-mcp/qet_mcp.py | 18 +++++++++++++++--- misc/qet-mcp/test_qet_mcp.py | 9 +++++++++ sources/scripting/assistantinfo.cpp | 9 ++++++++- sources/scripting/qetscriptapi.cpp | 27 +++++++++++++++++++++++++++ sources/scripting/qetscriptapi.h | 5 +++++ sources/utils/qetsettings.cpp | 17 +++++++++++++++++ sources/utils/qetsettings.h | 10 ++++++++++ 7 files changed, 91 insertions(+), 4 deletions(-) diff --git a/misc/qet-mcp/qet_mcp.py b/misc/qet-mcp/qet_mcp.py index df5c2a157..d96e7b018 100755 --- a/misc/qet-mcp/qet_mcp.py +++ b/misc/qet-mcp/qet_mcp.py @@ -3650,7 +3650,9 @@ SERVER_INSTRUCTIONS = ( "diagrams. A project (.qet) holds folios (sheets) of symbols " "(elements) joined by wires (conductors). Folio indexes count from 0.\n" "Start with qet_about: where QElectroTech keeps things, what is " - "switched on, and the stored scripts.\n" + "switched on, the stored scripts, and house_style -- this " + "installation's own drawing conventions if the user set any. Follow " + "house_style when placing or wiring anything.\n" "Two ways of working. HEADLESS (qet_* and qet_script_*): read, check " "and edit .qet files and store script buttons; nothing the user has " "open is touched. To make a button: qet_script_api for the calls, " @@ -3701,6 +3703,12 @@ def tool_about() -> dict: "script_api": info.get("script_api"), # Never the token: it is for the live tools, not the conversation. "live": {"open": bool(live), "pid": (live or {}).get("pid")}, + # This installation's drawing conventions, set once via + # qet.setHouseStyle() (scripting consent required, same as any + # other write) and read here so every assistant that connects sees + # them without being told by the user each time. No GUI field yet + # -- QetSettings::setHouseStyle() is there for one if it is wanted. + "house_style": info.get("house_style"), }) return out @@ -5186,8 +5194,12 @@ TOOLS = [ "scripts, element and title block collections), which " "features are on (scripting, live mode), every call a script " "can make, the stored scripts and the ones refused with why, " - "and whether a live session is open; plus this server's own " - "setup. Reads one file; changes nothing.", + "whether a live session is open, and house_style -- this " + "installation's own drawing conventions, in the user's words, " + "if they set any (qet.setHouseStyle()); null if not. Read " + "house_style and follow it, the same as any explicit " + "instruction, before placing or wiring anything. " + "Reads one file; changes nothing.", "inputSchema": {"type": "object", "properties": {}}, "handler": lambda a: tool_about(), }, diff --git a/misc/qet-mcp/test_qet_mcp.py b/misc/qet-mcp/test_qet_mcp.py index b4524d7ed..25f979c29 100644 --- a/misc/qet-mcp/test_qet_mcp.py +++ b/misc/qet-mcp/test_qet_mcp.py @@ -3584,6 +3584,15 @@ class AssistantInfoFile(unittest.TestCase): self.assertNotIn("SECRET", json.dumps(about)) self.assertEqual(about["script_api"], ["int currentFolio()"]) + def test_house_style_passes_through(self): + self.write(house_style="Grid: never off. Inputs left, outputs right.") + about = m.tool_about() + self.assertEqual(about["house_style"], "Grid: never off. Inputs left, outputs right.") + + def test_house_style_null_when_unset(self): + self.write(house_style=None) + self.assertIsNone(m.tool_about()["house_style"]) + def test_first_contact_carries_the_instructions(self): reply = m.handle({"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {}}) self.assertIn("qet_about", reply["result"]["instructions"]) diff --git a/sources/scripting/assistantinfo.cpp b/sources/scripting/assistantinfo.cpp index 88193b5a0..a6a81eab8 100644 --- a/sources/scripting/assistantinfo.cpp +++ b/sources/scripting/assistantinfo.cpp @@ -111,7 +111,14 @@ void write() {QStringLiteral("stored_scripts"), scripts}, {QStringLiteral("refused_scripts"), refused}, {QStringLiteral("recordings"), MacroRecorder::recordings()}, - {QStringLiteral("live"), s_live.isEmpty() ? QJsonValue() : QJsonValue(s_live)}}; + {QStringLiteral("live"), s_live.isEmpty() ? QJsonValue() : QJsonValue(s_live)}, + // Free-text drawing conventions, set once by the user + // (QetSettings::setHouseStyle(), qet.setHouseStyle()) and read + // by any assistant that connects, live or headless, without it + // having to ask the user or guess. Null, not "", when unset -- + // the two mean different things to a reader. + {QStringLiteral("house_style"), QetSettings::houseStyle().isEmpty() + ? QJsonValue() : QJsonValue(QetSettings::houseStyle())}}; const QString file_path = path(); QDir().mkpath(QFileInfo(file_path).path()); diff --git a/sources/scripting/qetscriptapi.cpp b/sources/scripting/qetscriptapi.cpp index 0de2b7497..e8bdddf9d 100644 --- a/sources/scripting/qetscriptapi.cpp +++ b/sources/scripting/qetscriptapi.cpp @@ -44,6 +44,7 @@ #include "../conductorsegment.h" #include "../conductorrouter.h" #include "../qetgraphicsitem/diagramimageitem.h" +#include "../utils/qetsettings.h" // See diagrameventaddpdf.h: a missing QtPdf module (or Qt < 6.4) is not // fatal at build time, so addPdfPage() is always declared -- a script @@ -4200,6 +4201,32 @@ QStringList QetScriptApi::folioPresets() const return names; } +/** + @brief QetScriptApi::houseStyle + This installation's drawing conventions (grid, flow direction, routing, + tagging, grouping, ...), as free text the user wrote once. Empty when + nobody has set any. Not project-scoped: the same text for every + project this QElectroTech opens, same as liveAssistantEnabled(). + @return the text +*/ +QString QetScriptApi::houseStyle() const +{ + return QetSettings::houseStyle(); +} + +/** + @brief QetScriptApi::setHouseStyle + Write the installation's drawing conventions. Always succeeds (a plain + setting, not project data, so there is nothing here to refuse); @return + is for a consistent call shape with the rest of the API. + @param text +*/ +bool QetScriptApi::setHouseStyle(const QString &text) +{ + QetSettings::setHouseStyle(text); + return true; +} + /** @brief QetScriptApi::setFolioBorder Change one field of a folio's frame through ChangeBorderCommand, as the diff --git a/sources/scripting/qetscriptapi.h b/sources/scripting/qetscriptapi.h index 572168a0f..202b8f936 100644 --- a/sources/scripting/qetscriptapi.h +++ b/sources/scripting/qetscriptapi.h @@ -567,6 +567,11 @@ class QetScriptApi : public QObject Q_INVOKABLE bool setFolioBorder(int folioIndex, const QString &property, const QString &value); Q_INVOKABLE QStringList folioPresets() const; + // -- this installation's drawing conventions (grid, flow, routing, tagging, -- + // -- grouping, ...), free text, for every assistant that connects to read -- + Q_INVOKABLE QString houseStyle() const; + Q_INVOKABLE bool setHouseStyle(const QString &text); + // -- the conductor defaults of a folio (Folio properties > Conductors), // or with folioIndex -1, the project's defaults for new folios -- Q_INVOKABLE QString conductorDefault(int folioIndex, const QString &property) const; diff --git a/sources/utils/qetsettings.cpp b/sources/utils/qetsettings.cpp index 43adb2ce4..de5f6e6b4 100644 --- a/sources/utils/qetsettings.cpp +++ b/sources/utils/qetsettings.cpp @@ -173,6 +173,23 @@ namespace QetSettings settings.setValue("scripting/live_assistant", enabled); } + /** + * @brief houseStyle + * @return this installation's drawing conventions, in the user's own + * words -- empty when nobody has set any. + */ + QString houseStyle() + { + QSettings settings; + return settings.value("assistant/house_style", QString()).toString(); + } + + void setHouseStyle(const QString &text) + { + QSettings settings; + settings.setValue("assistant/house_style", text); + } + /** * @brief setSheetBackground * Store the sheet background last picked in the diagram editor, so the diff --git a/sources/utils/qetsettings.h b/sources/utils/qetsettings.h index 64bf4b615..abea8d460 100644 --- a/sources/utils/qetsettings.h +++ b/sources/utils/qetsettings.h @@ -41,6 +41,16 @@ namespace QetSettings bool liveAssistantEnabled(); void setLiveAssistantEnabled(bool enabled); + /** + @brief Free-text drawing conventions for this installation (grid, + flow direction, routing, tagging, grouping, ...), written once by + the user and read by every AI assistant that connects -- live or + headless -- through qet-assistant.json and qet.houseStyle(). + Empty when never set. + */ + QString houseStyle(); + void setHouseStyle(const QString &text); + /** @brief The sheet (folio) background the diagram editors draw. From 36d574179b514d9762c83dab478a626605631bd5 Mon Sep 17 00:00:00 2001 From: ispyisail Date: Mon, 5 Oct 2026 18:35:38 +1300 Subject: [PATCH 2/3] Assistant house style: rewrite qet-assistant.json when the style changes qet.setHouseStyle() only saved the setting, so an assistant reading qet-assistant.json saw the old text until QElectroTech restarted. AssistantInfo::refresh() writes the file again, but only in a QElectroTech that writes it at all: a headless --run would otherwise replace the open QElectroTech's live channel with its own empty one. Co-Authored-By: Claude Opus 5.5 --- sources/scripting/assistantinfo.cpp | 18 +++++++++++++++--- sources/scripting/assistantinfo.h | 1 + sources/scripting/qetscriptapi.cpp | 4 ++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/sources/scripting/assistantinfo.cpp b/sources/scripting/assistantinfo.cpp index a6a81eab8..f35e8c9f1 100644 --- a/sources/scripting/assistantinfo.cpp +++ b/sources/scripting/assistantinfo.cpp @@ -36,6 +36,7 @@ namespace { QJsonObject s_live; ///< empty while live mode is closed bool s_running = true; + bool s_watching = false; ///< this QElectroTech writes the file (an editor opened) } namespace AssistantInfo { @@ -54,14 +55,25 @@ QString path() */ void watch() { - static bool watching = false; - if (watching) return; - watching = true; + if (s_watching) return; + s_watching = true; QObject::connect(&ScriptLibrary::instance(), &ScriptLibrary::changed, &write); QObject::connect(qApp, &QCoreApplication::aboutToQuit, &markStopped); write(); } +/** + @brief refresh + Write the file again after a setting it carries changed, but only in a + QElectroTech that writes it at all: a headless --run never opened an + editor, and writing from it would replace the open QElectroTech's live + channel with its own empty one. +*/ +void refresh() +{ + if (s_watching) write(); +} + void write() { QJsonArray scripts, refused; diff --git a/sources/scripting/assistantinfo.h b/sources/scripting/assistantinfo.h index 2243b3deb..02248d9a6 100644 --- a/sources/scripting/assistantinfo.h +++ b/sources/scripting/assistantinfo.h @@ -43,6 +43,7 @@ namespace AssistantInfo QString path(); void watch(); void write(); + void refresh(); void setLive(const QJsonObject &live); void markStopped(); } diff --git a/sources/scripting/qetscriptapi.cpp b/sources/scripting/qetscriptapi.cpp index e8bdddf9d..87894037d 100644 --- a/sources/scripting/qetscriptapi.cpp +++ b/sources/scripting/qetscriptapi.cpp @@ -45,6 +45,7 @@ #include "../conductorrouter.h" #include "../qetgraphicsitem/diagramimageitem.h" #include "../utils/qetsettings.h" +#include "assistantinfo.h" // See diagrameventaddpdf.h: a missing QtPdf module (or Qt < 6.4) is not // fatal at build time, so addPdfPage() is always declared -- a script @@ -4224,6 +4225,9 @@ QString QetScriptApi::houseStyle() const bool QetScriptApi::setHouseStyle(const QString &text) { QetSettings::setHouseStyle(text); + //qet-assistant.json carries it: an assistant reading the file + //now must see the new text, not the one from the last start + AssistantInfo::refresh(); return true; } From a4f12917bcc9a4e63fbe6462b28507f80d2e0a07 Mon Sep 17 00:00:00 2001 From: ispyisail Date: Mon, 5 Oct 2026 20:34:57 +1300 Subject: [PATCH 3/3] qet-mcp README: the house style Co-Authored-By: Claude Opus 5.5 --- misc/qet-mcp/README.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/misc/qet-mcp/README.md b/misc/qet-mcp/README.md index 0852d13a4..de741cc75 100644 --- a/misc/qet-mcp/README.md +++ b/misc/qet-mcp/README.md @@ -42,7 +42,7 @@ here read the model. | `qet_check` | **design-rule checks** — duplicate labels, unlabelled masters, unnumbered conductors, empty folios, terminals with more than four wires, folio reports with several wires | | `qet_layout_check` | **does the drawing read well?** — a 0–100 score; wires that jog because two symbols are a few pixels out of line, symbols off the grid, wires through symbols, overlaps, crossings; and the moves that fix them, ready for `qet_edit` | | `qet_query` | **ask the project database** — read-only SQL over the views and tables | -| `qet_about` | **start here** — where QElectroTech keeps things, what is switched on, the stored scripts, the calls a script can make (from `qet-assistant.json`) | +| `qet_about` | **start here** — where QElectroTech keeps things, what is switched on, the stored scripts, the calls a script can make (from `qet-assistant.json`), and your house style | | `qet_script_api` | **what a script can call** — every `qet.*` call of this build, and the header that makes a script a button | | `qet_script_test` | **try a script** on a copy of a project: what it would change, what it logged, its errors | | `qet_script_install` | **make a button** — store a script (and an SVG icon) where QElectroTech shows it in Project > Scripts and the Scripts toolbar | @@ -321,6 +321,21 @@ The usual round: read the recording, write a script that does the same in general (on the selected elements, say, not on these exact ones), `qet_recording_check` it until it matches, then `qet_script_install` it. +## Your house style + +Drawing conventions you want every assistant to follow -- grid, flow +direction, wire routing, tags, grouping -- written once, in your own words, +and kept by QElectroTech itself rather than by any one assistant: + +```js +qet.setHouseStyle("Never place a symbol off the 10 px grid.\n..."); +``` + +It is stored in QElectroTech's settings (needs `QET_ENABLE_SCRIPTING=1`, as +for any change), written to `qet-assistant.json` as `house_style`, and shown +by `qet_about`, whose description tells the assistant to follow it. Every +assistant that connects, of any make, reads the same text. + ## Live mode: working in the QElectroTech you have open Every tool above works on files, with no QElectroTech window involved. The