@aryacode/dsh-engineering 0.0.0-stage → 0.3.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/CHANGELOG.md +115 -0
- package/LICENSE +21 -0
- package/README.md +228 -2
- package/cordis.patch.yml +122 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +77 -3
- package/vendor/ftview/README.md +55 -0
- package/vendor/ftview/SKILL.md +74 -0
- package/vendor/ftview/lib/checks.d.ts +27 -0
- package/vendor/ftview/lib/checks.js +39 -0
- package/vendor/ftview/lib/core.d.ts +208 -0
- package/vendor/ftview/lib/core.js +160 -0
- package/vendor/ftview/lib/edit/drafts.d.ts +49 -0
- package/vendor/ftview/lib/edit/drafts.js +84 -0
- package/vendor/ftview/lib/edit/operations.d.ts +62 -0
- package/vendor/ftview/lib/edit/operations.js +126 -0
- package/vendor/ftview/lib/edit/prepare.d.ts +38 -0
- package/vendor/ftview/lib/edit/prepare.js +133 -0
- package/vendor/ftview/lib/edit/tools.d.ts +25 -0
- package/vendor/ftview/lib/edit/tools.js +198 -0
- package/vendor/ftview/lib/edit.d.ts +30 -0
- package/vendor/ftview/lib/edit.js +38 -0
- package/vendor/ftview/lib/index.d.ts +31 -0
- package/vendor/ftview/lib/index.js +36 -0
- package/vendor/ftview/lib/model.d.ts +80 -0
- package/vendor/ftview/lib/model.js +267 -0
- package/vendor/ftview/lib/session.d.ts +48 -0
- package/vendor/ftview/lib/session.js +98 -0
- package/vendor/ftview/lib/skill.d.ts +29 -0
- package/vendor/ftview/lib/skill.js +43 -0
- package/vendor/ftview/lib/studio/discover.d.ts +36 -0
- package/vendor/ftview/lib/studio/discover.js +87 -0
- package/vendor/ftview/lib/studio/preflight.d.ts +51 -0
- package/vendor/ftview/lib/studio/preflight.js +64 -0
- package/vendor/ftview/lib/studio/tools.d.ts +15 -0
- package/vendor/ftview/lib/studio/tools.js +83 -0
- package/vendor/ftview/lib/studio.d.ts +30 -0
- package/vendor/ftview/lib/studio.js +37 -0
- package/vendor/ftview/lib/tools.d.ts +14 -0
- package/vendor/ftview/lib/tools.js +202 -0
- package/vendor/l5x/README.md +62 -0
- package/vendor/l5x/SKILL.md +81 -0
- package/vendor/l5x/lib/approvals.d.ts +50 -0
- package/vendor/l5x/lib/approvals.js +93 -0
- package/vendor/l5x/lib/client.js +412 -0
- package/vendor/l5x/lib/client.js.map +1 -0
- package/vendor/l5x/lib/core/errors.d.ts +38 -0
- package/vendor/l5x/lib/core/errors.js +48 -0
- package/vendor/l5x/lib/core/params.d.ts +49 -0
- package/vendor/l5x/lib/core/params.js +39 -0
- package/vendor/l5x/lib/core/result.d.ts +46 -0
- package/vendor/l5x/lib/core/result.js +21 -0
- package/vendor/l5x/lib/core/tool.d.ts +102 -0
- package/vendor/l5x/lib/core/tool.js +19 -0
- package/vendor/l5x/lib/drafts.d.ts +66 -0
- package/vendor/l5x/lib/drafts.js +98 -0
- package/vendor/l5x/lib/editing.d.ts +30 -0
- package/vendor/l5x/lib/editing.js +42 -0
- package/vendor/l5x/lib/engine/acd-edit.d.ts +13 -0
- package/vendor/l5x/lib/engine/acd-edit.js +115 -0
- package/vendor/l5x/lib/engine/analysis.d.ts +22 -0
- package/vendor/l5x/lib/engine/analysis.js +258 -0
- package/vendor/l5x/lib/engine/capabilities.d.ts +9 -0
- package/vendor/l5x/lib/engine/capabilities.js +40 -0
- package/vendor/l5x/lib/engine/change-set.d.ts +122 -0
- package/vendor/l5x/lib/engine/change-set.js +446 -0
- package/vendor/l5x/lib/engine/configuration.d.ts +21 -0
- package/vendor/l5x/lib/engine/configuration.js +42 -0
- package/vendor/l5x/lib/engine/details.d.ts +47 -0
- package/vendor/l5x/lib/engine/details.js +173 -0
- package/vendor/l5x/lib/engine/direct-polling.d.ts +91 -0
- package/vendor/l5x/lib/engine/direct-polling.js +193 -0
- package/vendor/l5x/lib/engine/download-review.d.ts +15 -0
- package/vendor/l5x/lib/engine/download-review.js +63 -0
- package/vendor/l5x/lib/engine/logic-syntax.d.ts +60 -0
- package/vendor/l5x/lib/engine/logic-syntax.js +237 -0
- package/vendor/l5x/lib/engine/observations.d.ts +41 -0
- package/vendor/l5x/lib/engine/observations.js +67 -0
- package/vendor/l5x/lib/engine/project-analysis.d.ts +131 -0
- package/vendor/l5x/lib/engine/project-analysis.js +314 -0
- package/vendor/l5x/lib/engine/review-evidence.d.ts +8 -0
- package/vendor/l5x/lib/engine/review-evidence.js +49 -0
- package/vendor/l5x/lib/engine/review.d.ts +18 -0
- package/vendor/l5x/lib/engine/review.js +123 -0
- package/vendor/l5x/lib/engine/studio-sdk.d.ts +101 -0
- package/vendor/l5x/lib/engine/studio-sdk.js +571 -0
- package/vendor/l5x/lib/engine/tag-resolution.d.ts +20 -0
- package/vendor/l5x/lib/engine/tag-resolution.js +169 -0
- package/vendor/l5x/lib/engine/usage.d.ts +16 -0
- package/vendor/l5x/lib/engine/usage.js +92 -0
- package/vendor/l5x/lib/engine/xml-regions.d.ts +23 -0
- package/vendor/l5x/lib/engine/xml-regions.js +77 -0
- package/vendor/l5x/lib/fs.d.ts +17 -0
- package/vendor/l5x/lib/fs.js +36 -0
- package/vendor/l5x/lib/index.d.ts +36 -0
- package/vendor/l5x/lib/index.js +42 -0
- package/vendor/l5x/lib/live.d.ts +48 -0
- package/vendor/l5x/lib/live.js +48 -0
- package/vendor/l5x/lib/model/aois.d.ts +39 -0
- package/vendor/l5x/lib/model/aois.js +112 -0
- package/vendor/l5x/lib/model/errors.d.ts +15 -0
- package/vendor/l5x/lib/model/errors.js +18 -0
- package/vendor/l5x/lib/model/index.d.ts +20 -0
- package/vendor/l5x/lib/model/index.js +19 -0
- package/vendor/l5x/lib/model/module.d.ts +37 -0
- package/vendor/l5x/lib/model/module.js +54 -0
- package/vendor/l5x/lib/model/modules.d.ts +24 -0
- package/vendor/l5x/lib/model/modules.js +55 -0
- package/vendor/l5x/lib/model/parse.d.ts +32 -0
- package/vendor/l5x/lib/model/parse.js +116 -0
- package/vendor/l5x/lib/model/project.d.ts +15 -0
- package/vendor/l5x/lib/model/project.js +31 -0
- package/vendor/l5x/lib/model/routines.d.ts +71 -0
- package/vendor/l5x/lib/model/routines.js +183 -0
- package/vendor/l5x/lib/model/tags.d.ts +51 -0
- package/vendor/l5x/lib/model/tags.js +119 -0
- package/vendor/l5x/lib/model/types.d.ts +304 -0
- package/vendor/l5x/lib/model/types.js +12 -0
- package/vendor/l5x/lib/model/udts.d.ts +34 -0
- package/vendor/l5x/lib/model/udts.js +81 -0
- package/vendor/l5x/lib/model/xml.d.ts +139 -0
- package/vendor/l5x/lib/model/xml.js +291 -0
- package/vendor/l5x/lib/model/xref.d.ts +28 -0
- package/vendor/l5x/lib/model/xref.js +154 -0
- package/vendor/l5x/lib/session.d.ts +119 -0
- package/vendor/l5x/lib/session.js +236 -0
- package/vendor/l5x/lib/skill.d.ts +30 -0
- package/vendor/l5x/lib/skill.js +44 -0
- package/vendor/l5x/lib/studio-state.d.ts +77 -0
- package/vendor/l5x/lib/studio-state.js +79 -0
- package/vendor/l5x/lib/studio.d.ts +29 -0
- package/vendor/l5x/lib/studio.js +40 -0
- package/vendor/l5x/lib/tools/checks.d.ts +19 -0
- package/vendor/l5x/lib/tools/checks.js +140 -0
- package/vendor/l5x/lib/tools/common.d.ts +41 -0
- package/vendor/l5x/lib/tools/common.js +84 -0
- package/vendor/l5x/lib/tools/controller.d.ts +21 -0
- package/vendor/l5x/lib/tools/controller.js +170 -0
- package/vendor/l5x/lib/tools/drafts.d.ts +23 -0
- package/vendor/l5x/lib/tools/drafts.js +270 -0
- package/vendor/l5x/lib/tools/live.d.ts +21 -0
- package/vendor/l5x/lib/tools/live.js +175 -0
- package/vendor/l5x/lib/tools/read.d.ts +19 -0
- package/vendor/l5x/lib/tools/read.js +245 -0
- package/vendor/l5x/lib/tools/references.d.ts +20 -0
- package/vendor/l5x/lib/tools/references.js +269 -0
- package/vendor/l5x/lib/tools/studio.d.ts +21 -0
- package/vendor/l5x/lib/tools/studio.js +193 -0
- package/vendor/l5x/poll-worker.mjs +77 -0
- package/vendor/l5x/studio-bridge/NuGet.Config +1 -0
- package/vendor/l5x/studio-bridge/Program.cs +304 -0
- package/vendor/l5x/studio-bridge/StudioBridge.csproj +11 -0
- package/vendor/mwi/README.md +60 -0
- package/vendor/mwi/SKILL.md +87 -0
- package/vendor/mwi/bridge/README.md +8 -0
- package/vendor/mwi/bridge/mw_bridge.ps1 +2763 -0
- package/vendor/mwi/bridge/sdk-interop-importer.cs +30 -0
- package/vendor/mwi/bridge/sdk-variable-reader.cs +22 -0
- package/vendor/mwi/bridge/start_bridge.cmd +21 -0
- package/vendor/mwi/bridge/watch_dialogs.cmd +13 -0
- package/vendor/mwi/bridge/watch_dialogs.ps1 +153 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__init__.py +14 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/__init__.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/block_interfaces.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/cfb.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/datatypes.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/declarations.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/errors.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/graphical_listing.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/grid.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/knowledge.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/mwt_bind.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/navigation.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/program_checks.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/project.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/staging.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/structure.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/task_bindings.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/tasks.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/tree.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/validation.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/variable_edit.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/variables.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/workflow.cpython-312.pyc +0 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/block_interfaces.py +275 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/build.py +339 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/capture.py +448 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/cfb.py +765 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/com.py +268 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/datatypes.py +204 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/declarations.py +399 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/descriptions.py +30 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/eip.py +292 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/errors.py +36 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/graphical_listing.py +208 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/grid.py +227 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/ide.py +317 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/iec.py +317 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/installed_help.py +151 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/knowledge.py +218 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/libraries.py +250 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/manuals.py +520 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/mwt_bind.py +133 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/navigation.py +44 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/pou_writer.py +1547 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/program_checks.py +360 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/project.py +704 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/reference_data/catalog.json +672 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/reference_data/patterns.json +86 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/restore.py +208 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/server.py +1913 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/snapshot.py +138 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/st_comments.py +25 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/staging.py +179 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/stlint.py +676 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/structure.py +98 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/task_bindings.py +68 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/tasks.py +162 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/transaction.py +157 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/tree.py +563 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/tree_writer.py +790 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/uia.py +465 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/validation.py +41 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/variable_edit.py +183 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/variables.py +472 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/workflow.py +130 -0
- package/vendor/mwi/code/engine/motionworks_iec_mcp/writer.py +666 -0
- package/vendor/mwi/code/mw_code.py +1258 -0
- package/vendor/mwi/legacy/edit-session.js +50 -0
- package/vendor/mwi/legacy/fb-insertion.js +74 -0
- package/vendor/mwi/legacy/native-code.js +83 -0
- package/vendor/mwi/legacy/native-groups.js +75 -0
- package/vendor/mwi/legacy/native-structure.js +143 -0
- package/vendor/mwi/legacy/native-variables.js +77 -0
- package/vendor/mwi/legacy/pou-conversion.js +47 -0
- package/vendor/mwi/legacy/pou-package.js +66 -0
- package/vendor/mwi/lib/core/bridge.d.ts +71 -0
- package/vendor/mwi/lib/core/bridge.js +53 -0
- package/vendor/mwi/lib/core/code-engine.d.ts +47 -0
- package/vendor/mwi/lib/core/code-engine.js +109 -0
- package/vendor/mwi/lib/core/errors.d.ts +37 -0
- package/vendor/mwi/lib/core/errors.js +47 -0
- package/vendor/mwi/lib/core/params.d.ts +52 -0
- package/vendor/mwi/lib/core/params.js +46 -0
- package/vendor/mwi/lib/core/ps-bridge.d.ts +62 -0
- package/vendor/mwi/lib/core/ps-bridge.js +127 -0
- package/vendor/mwi/lib/core/result.d.ts +45 -0
- package/vendor/mwi/lib/core/result.js +20 -0
- package/vendor/mwi/lib/core/tool.d.ts +80 -0
- package/vendor/mwi/lib/core/tool.js +55 -0
- package/vendor/mwi/lib/edit/compare.d.ts +33 -0
- package/vendor/mwi/lib/edit/compare.js +70 -0
- package/vendor/mwi/lib/edit.d.ts +20 -0
- package/vendor/mwi/lib/edit.js +32 -0
- package/vendor/mwi/lib/index.d.ts +21 -0
- package/vendor/mwi/lib/index.js +33 -0
- package/vendor/mwi/lib/read/service.d.ts +117 -0
- package/vendor/mwi/lib/read/service.js +229 -0
- package/vendor/mwi/lib/read.d.ts +21 -0
- package/vendor/mwi/lib/read.js +33 -0
- package/vendor/mwi/lib/session/project.d.ts +34 -0
- package/vendor/mwi/lib/session/project.js +54 -0
- package/vendor/mwi/lib/session/service.d.ts +167 -0
- package/vendor/mwi/lib/session/service.js +566 -0
- package/vendor/mwi/lib/session/stage.d.ts +84 -0
- package/vendor/mwi/lib/session/stage.js +187 -0
- package/vendor/mwi/lib/session/workspace.d.ts +52 -0
- package/vendor/mwi/lib/session/workspace.js +90 -0
- package/vendor/mwi/lib/skill.d.ts +36 -0
- package/vendor/mwi/lib/skill.js +56 -0
- package/vendor/mwi/lib/tools/edit.d.ts +20 -0
- package/vendor/mwi/lib/tools/edit.js +286 -0
- package/vendor/mwi/lib/tools/read.d.ts +19 -0
- package/vendor/mwi/lib/tools/read.js +161 -0
- package/vendor/mwi/lib/tools/session.d.ts +20 -0
- package/vendor/mwi/lib/tools/session.js +166 -0
- package/vendor/mwi/lib/tools/verify.d.ts +19 -0
- package/vendor/mwi/lib/tools/verify.js +85 -0
- package/vendor/mwi/lib/verify.d.ts +17 -0
- package/vendor/mwi/lib/verify.js +29 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The offline read surface: open, look, and read one component.
|
|
3
|
+
*
|
|
4
|
+
* Every tool answers with the same document — `summary`, `data`, optional
|
|
5
|
+
* `coverage`, and concrete `next` calls — and every expected failure answers
|
|
6
|
+
* with a code, a message and a fix. The tool descriptions name the one intent
|
|
7
|
+
* each tool serves; parameters carry their own meaning and an example.
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-engineering-l5x/tools/read
|
|
10
|
+
*/
|
|
11
|
+
import { getAoi, getRoutine, getUdt, listAois, listModules, listRoutines, listTags } from "../model/index.js";
|
|
12
|
+
import { L5xError } from "../core/errors.js";
|
|
13
|
+
import { success } from "../core/result.js";
|
|
14
|
+
import { choice, integer, object, string } from "../core/params.js";
|
|
15
|
+
import { COVERAGE, PROJECT_ID, integerArg, optionalString, requiredString, tool } from "./common.js";
|
|
16
|
+
/** Component kinds the reader can list and open. */
|
|
17
|
+
const KINDS = ['program', 'task', 'tag', 'udt', 'routine', 'aoi', 'module', 'alarm'];
|
|
18
|
+
/**
|
|
19
|
+
* The offline tools, bound to one chat's project store.
|
|
20
|
+
*
|
|
21
|
+
* @param session - Project handles for the calling chat.
|
|
22
|
+
* @returns Tool definitions in registration order.
|
|
23
|
+
*/
|
|
24
|
+
export function readTools(session) {
|
|
25
|
+
return [
|
|
26
|
+
tool({
|
|
27
|
+
name: 'open_project',
|
|
28
|
+
description: 'Open one Studio 5000 L5X export for this chat. Returns a project_id and a structure summary; every other tool reads this project.',
|
|
29
|
+
parameters: object({
|
|
30
|
+
path: string('Path to the .L5X export, absolute or relative to the workspace root. Example: "C:/Projects/Line/Line.L5X"'),
|
|
31
|
+
}, ['path']),
|
|
32
|
+
async run(args, exec) {
|
|
33
|
+
const project = await session.open(requiredString(args, 'path'), exec);
|
|
34
|
+
const document = project.document;
|
|
35
|
+
return success(`${document.controller.name}: ${document.programs.length} programs, ${document.tags.length} tags, ${document.routines.length} routines.`, {
|
|
36
|
+
project_id: project.id,
|
|
37
|
+
path: project.requestedPath,
|
|
38
|
+
sha256: project.sha256,
|
|
39
|
+
revision: revisionOf(document),
|
|
40
|
+
export_revision: project.softwareRevision,
|
|
41
|
+
verification: project.verification,
|
|
42
|
+
controller: document.controller,
|
|
43
|
+
counts: countsOf(document),
|
|
44
|
+
programs: document.programs.map(program => ({ name: program.name, mainRoutine: program.mainRoutine })),
|
|
45
|
+
tasks: document.tasks.map(task => ({ name: task.name, type: task.type, rate: task.rate, programs: task.programs })),
|
|
46
|
+
}, [{ tool: 'project_overview', why: 'Confirm tasks, programs and coverage before reading components.' }], COVERAGE);
|
|
47
|
+
},
|
|
48
|
+
}),
|
|
49
|
+
tool({
|
|
50
|
+
name: 'project_overview',
|
|
51
|
+
description: 'Structure and coverage of the open project: controller, revision, tasks, programs and component counts.',
|
|
52
|
+
parameters: object({ project_id: PROJECT_ID }, []),
|
|
53
|
+
async run(args, exec) {
|
|
54
|
+
const project = await session.require(optionalString(args, 'project_id'), exec);
|
|
55
|
+
const document = project.document;
|
|
56
|
+
return success(`${document.controller.name} (${document.controller.processorType}, revision ${revisionOf(document)}).`, {
|
|
57
|
+
project_id: project.id,
|
|
58
|
+
controller: document.controller,
|
|
59
|
+
revision: revisionOf(document),
|
|
60
|
+
export_revision: project.softwareRevision,
|
|
61
|
+
verification: project.verification,
|
|
62
|
+
counts: countsOf(document),
|
|
63
|
+
programs: document.programs,
|
|
64
|
+
tasks: document.tasks,
|
|
65
|
+
}, [
|
|
66
|
+
{ tool: 'list_components', args: { kind: 'routine' }, why: 'List the routines you can read next.' },
|
|
67
|
+
{ tool: 'list_components', args: { kind: 'tag' }, why: 'List tags before tracing or reading one.' },
|
|
68
|
+
], COVERAGE);
|
|
69
|
+
},
|
|
70
|
+
}),
|
|
71
|
+
tool({
|
|
72
|
+
name: 'list_components',
|
|
73
|
+
description: 'List one component kind in the open project, paginated. Use it to find the exact name get_component expects.',
|
|
74
|
+
parameters: object({
|
|
75
|
+
kind: choice('Component kind to list.', KINDS),
|
|
76
|
+
scope: string('Program name for a program-scoped tag or routine. Omit for controller scope.'),
|
|
77
|
+
filter: string('Case-insensitive substring matched against the component name.'),
|
|
78
|
+
offset: integer('Zero-based index of the first row to return.', { minimum: 0, default: 0 }),
|
|
79
|
+
limit: integer('Maximum rows to return.', { minimum: 1, maximum: 200, default: 50 }),
|
|
80
|
+
project_id: PROJECT_ID,
|
|
81
|
+
}, ['kind']),
|
|
82
|
+
async run(args, exec) {
|
|
83
|
+
const project = await session.require(optionalString(args, 'project_id'), exec);
|
|
84
|
+
const kind = requiredKind(args);
|
|
85
|
+
const filter = optionalString(args, 'filter') ?? '';
|
|
86
|
+
const offset = integerArg(args, 'offset', 0);
|
|
87
|
+
const limit = Math.min(Math.max(integerArg(args, 'limit', 50), 1), 200);
|
|
88
|
+
const rows = selectRows(project.document, kind, optionalString(args, 'scope'), filter);
|
|
89
|
+
const page = rows.slice(offset, offset + limit);
|
|
90
|
+
return success(`${rows.length} ${rows.length === 1 ? kind : `${kind}s`}${filter === '' ? '' : ` matching ${JSON.stringify(filter)}`}.`, { kind, total: rows.length, offset, limit, rows: page }, pageNext(kind, page, offset, limit, rows.length, filter));
|
|
91
|
+
},
|
|
92
|
+
}),
|
|
93
|
+
tool({
|
|
94
|
+
name: 'get_component',
|
|
95
|
+
description: 'Read one component in full by exact name: a tag, UDT, routine, AOI, module, program, task or alarm.',
|
|
96
|
+
parameters: object({
|
|
97
|
+
kind: choice('Component kind to read.', KINDS),
|
|
98
|
+
name: string('Exact component name as the export declares it.'),
|
|
99
|
+
scope: string('Program name when the component is program-scoped.'),
|
|
100
|
+
project_id: PROJECT_ID,
|
|
101
|
+
}, ['kind', 'name']),
|
|
102
|
+
async run(args, exec) {
|
|
103
|
+
const project = await session.require(optionalString(args, 'project_id'), exec);
|
|
104
|
+
const kind = requiredKind(args);
|
|
105
|
+
const name = requiredString(args, 'name');
|
|
106
|
+
const scope = optionalString(args, 'scope');
|
|
107
|
+
const component = readComponent(project.document, kind, name, scope);
|
|
108
|
+
return success(`${kind} ${name}${scope === undefined ? '' : ` in ${scope}`}.`, { kind, component }, [{ tool: 'list_components', args: { kind }, why: 'List the siblings of this component.' }], COVERAGE);
|
|
109
|
+
},
|
|
110
|
+
}),
|
|
111
|
+
];
|
|
112
|
+
}
|
|
113
|
+
/** A required component kind. */
|
|
114
|
+
function requiredKind(args) {
|
|
115
|
+
const value = requiredString(args, 'kind');
|
|
116
|
+
if (!KINDS.includes(value)) {
|
|
117
|
+
throw new L5xError('INVALID_ARGUMENT', `Unknown component kind ${JSON.stringify(value)}.`, `Pass one of: ${KINDS.join(', ')}.`, KINDS);
|
|
118
|
+
}
|
|
119
|
+
return value;
|
|
120
|
+
}
|
|
121
|
+
/** Controller revision as a readable string. */
|
|
122
|
+
function revisionOf(document) {
|
|
123
|
+
const { majorRev, minorRev } = document.controller;
|
|
124
|
+
return majorRev === '' ? '' : `${majorRev}.${minorRev}`;
|
|
125
|
+
}
|
|
126
|
+
/** Component counts for a summary. */
|
|
127
|
+
function countsOf(document) {
|
|
128
|
+
return {
|
|
129
|
+
programs: document.programs.length,
|
|
130
|
+
tasks: document.tasks.length,
|
|
131
|
+
tags: document.tags.length,
|
|
132
|
+
udts: document.udts.length,
|
|
133
|
+
routines: document.routines.length,
|
|
134
|
+
aois: document.aois.length,
|
|
135
|
+
modules: document.modules.length,
|
|
136
|
+
alarms: document.alarms.length,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/** One kind's rows, filtered and in export order. */
|
|
140
|
+
function selectRows(document, kind, scope, filter) {
|
|
141
|
+
const rows = kind === 'program'
|
|
142
|
+
? document.programs.map(program => ({ name: program.name, mainRoutine: program.mainRoutine, description: program.description }))
|
|
143
|
+
: kind === 'task'
|
|
144
|
+
? document.tasks.map(task => ({ name: task.name, type: task.type, rate: task.rate, programs: task.programs }))
|
|
145
|
+
: kind === 'tag'
|
|
146
|
+
? listTags(document, scope ?? '').map(tag => ({ name: tag.name, scope: tag.scope, dataType: tag.dataType, description: tag.description ?? '', externalAccess: tag.externalAccess ?? '' }))
|
|
147
|
+
: kind === 'udt'
|
|
148
|
+
? document.udts.map(udt => ({ name: udt.name, members: udt.members.length, description: udt.description }))
|
|
149
|
+
: kind === 'routine'
|
|
150
|
+
? listRoutines(document, scope ?? '').map(routine => ({ name: routine.name, program: routine.program, type: routine.type, rungCount: routine.rungCount ?? null, lineCount: routine.lineCount ?? null, description: routine.description }))
|
|
151
|
+
: kind === 'aoi'
|
|
152
|
+
? listAois(document).map(aoi => ({ name: aoi.name, revision: aoi.revision, description: aoi.description }))
|
|
153
|
+
: kind === 'module'
|
|
154
|
+
? listModules(document).map(module => ({ name: module.name, catalogNumber: module.catalogNumber, slot: module.slot ?? '', parentModule: module.parentModule, inhibited: module.inhibited === true }))
|
|
155
|
+
: document.alarms.map(alarm => ({ name: alarm.name, kind: alarm.kind, description: alarm.description }));
|
|
156
|
+
const needle = filter.toLowerCase();
|
|
157
|
+
return needle === '' ? rows : rows.filter(row => row.name.toLowerCase().includes(needle));
|
|
158
|
+
}
|
|
159
|
+
/** Next calls for one listing page. */
|
|
160
|
+
function pageNext(kind, page, offset, limit, total, filter) {
|
|
161
|
+
const next = [];
|
|
162
|
+
const first = page[0];
|
|
163
|
+
if (first !== undefined) {
|
|
164
|
+
const scope = first.scope ?? first.program;
|
|
165
|
+
next.push({
|
|
166
|
+
tool: 'get_component',
|
|
167
|
+
args: scope === undefined ? { kind, name: first.name } : { kind, name: first.name, scope },
|
|
168
|
+
why: 'Read the first row in full.',
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
if (offset + limit < total) {
|
|
172
|
+
next.push({
|
|
173
|
+
tool: 'list_components',
|
|
174
|
+
args: { kind, filter, offset: offset + limit, limit },
|
|
175
|
+
why: `Read the next ${Math.min(limit, total - offset - limit)} rows.`,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return next;
|
|
179
|
+
}
|
|
180
|
+
/** One component by exact name, with candidates when the name does not resolve. */
|
|
181
|
+
function readComponent(document, kind, name, scope) {
|
|
182
|
+
if (kind === 'tag') {
|
|
183
|
+
const matches = listTags(document, scope ?? '').filter(tag => tag.name === name);
|
|
184
|
+
const first = matches[0];
|
|
185
|
+
if (first === undefined)
|
|
186
|
+
throw notFound(kind, name, listTags(document, scope ?? '').map(tag => tag.name));
|
|
187
|
+
if (matches.length > 1) {
|
|
188
|
+
throw new L5xError('AMBIGUOUS_COMPONENT', `${name} is declared in ${matches.length} scopes.`, 'Pass scope to choose one.', matches.map(tag => tag.scope));
|
|
189
|
+
}
|
|
190
|
+
return first;
|
|
191
|
+
}
|
|
192
|
+
if (kind === 'udt') {
|
|
193
|
+
const matches = getUdt(document, name);
|
|
194
|
+
const first = matches[0];
|
|
195
|
+
if (first === undefined)
|
|
196
|
+
throw notFound(kind, name, document.udts.map(udt => udt.name));
|
|
197
|
+
return first;
|
|
198
|
+
}
|
|
199
|
+
if (kind === 'routine') {
|
|
200
|
+
const matches = listRoutines(document, scope ?? '').filter(routine => routine.name === name);
|
|
201
|
+
const first = matches[0];
|
|
202
|
+
if (first === undefined)
|
|
203
|
+
throw notFound(kind, name, listRoutines(document, scope ?? '').map(routine => routine.name));
|
|
204
|
+
if (matches.length > 1) {
|
|
205
|
+
throw new L5xError('AMBIGUOUS_COMPONENT', `${name} exists in ${matches.length} programs.`, 'Pass scope with the program name.', matches.map(routine => routine.program));
|
|
206
|
+
}
|
|
207
|
+
return getRoutine(document, first.program, name);
|
|
208
|
+
}
|
|
209
|
+
if (kind === 'aoi') {
|
|
210
|
+
const aoi = getAoi(document, name);
|
|
211
|
+
if (aoi === undefined)
|
|
212
|
+
throw notFound(kind, name, document.aois.map(entry => entry.name));
|
|
213
|
+
return aoi;
|
|
214
|
+
}
|
|
215
|
+
if (kind === 'module') {
|
|
216
|
+
const module = document.modules.find(entry => entry.name === name);
|
|
217
|
+
if (module === undefined)
|
|
218
|
+
throw notFound(kind, name, document.modules.map(entry => entry.name));
|
|
219
|
+
return module;
|
|
220
|
+
}
|
|
221
|
+
if (kind === 'program') {
|
|
222
|
+
const program = document.programs.find(entry => entry.name === name);
|
|
223
|
+
if (program === undefined)
|
|
224
|
+
throw notFound(kind, name, document.programs.map(entry => entry.name));
|
|
225
|
+
return program;
|
|
226
|
+
}
|
|
227
|
+
if (kind === 'task') {
|
|
228
|
+
const task = document.tasks.find(entry => entry.name === name);
|
|
229
|
+
if (task === undefined)
|
|
230
|
+
throw notFound(kind, name, document.tasks.map(entry => entry.name));
|
|
231
|
+
return task;
|
|
232
|
+
}
|
|
233
|
+
const alarm = document.alarms.find(entry => entry.name === name);
|
|
234
|
+
if (alarm === undefined)
|
|
235
|
+
throw notFound(kind, name, document.alarms.map(entry => entry.name));
|
|
236
|
+
return alarm;
|
|
237
|
+
}
|
|
238
|
+
/** The failure for a name that does not resolve, with near candidates. */
|
|
239
|
+
function notFound(kind, name, names) {
|
|
240
|
+
const near = names.filter(candidate => candidate.toLowerCase().includes(name.toLowerCase())).slice(0, 5);
|
|
241
|
+
const candidates = near.length > 0 ? near : names.slice(0, 5);
|
|
242
|
+
return new L5xError('COMPONENT_NOT_FOUND', `No ${kind} named ${JSON.stringify(name)} in this export.`, candidates.length === 0
|
|
243
|
+
? `Call list_components with kind "${kind}" to see what exists.`
|
|
244
|
+
: `Call list_components with kind "${kind}", or retry with one of the candidates.`, candidates);
|
|
245
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The reference cluster: find logic, follow a symbol, resolve an address, map
|
|
3
|
+
* one tag's element usage.
|
|
4
|
+
*
|
|
5
|
+
* The old surface answered "where is this used" with five tools. This module
|
|
6
|
+
* answers it with three names that state their intent — `find_references`,
|
|
7
|
+
* `trace_dependencies`, `resolve_tag` — plus `usage_map` for element/bit
|
|
8
|
+
* allocation, and `search_logic` for text that is not a symbol.
|
|
9
|
+
*
|
|
10
|
+
* @module dsh-engineering-l5x/tools/references
|
|
11
|
+
*/
|
|
12
|
+
import type { ToolDefinition } from '../core/tool.ts';
|
|
13
|
+
import type { ProjectSession } from '../session.ts';
|
|
14
|
+
/**
|
|
15
|
+
* The reference tools, bound to one chat's project store.
|
|
16
|
+
*
|
|
17
|
+
* @param session - Project handles for the calling chat.
|
|
18
|
+
* @returns Tool definitions in registration order.
|
|
19
|
+
*/
|
|
20
|
+
export declare function referenceTools(session: ProjectSession): readonly ToolDefinition[];
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The reference cluster: find logic, follow a symbol, resolve an address, map
|
|
3
|
+
* one tag's element usage.
|
|
4
|
+
*
|
|
5
|
+
* The old surface answered "where is this used" with five tools. This module
|
|
6
|
+
* answers it with three names that state their intent — `find_references`,
|
|
7
|
+
* `trace_dependencies`, `resolve_tag` — plus `usage_map` for element/bit
|
|
8
|
+
* allocation, and `search_logic` for text that is not a symbol.
|
|
9
|
+
*
|
|
10
|
+
* @module dsh-engineering-l5x/tools/references
|
|
11
|
+
*/
|
|
12
|
+
import { findReferences } from "../engine/analysis.js";
|
|
13
|
+
import { traceDependencies } from "../engine/project-analysis.js";
|
|
14
|
+
import { resolveTag } from "../engine/tag-resolution.js";
|
|
15
|
+
import { usageMap } from "../engine/usage.js";
|
|
16
|
+
import { L5xError } from "../core/errors.js";
|
|
17
|
+
import { success } from "../core/result.js";
|
|
18
|
+
import { choice, integer, object, string } from "../core/params.js";
|
|
19
|
+
import { COVERAGE, PROJECT_ID, integerArg, optionalString, requiredString, tool } from "./common.js";
|
|
20
|
+
/**
|
|
21
|
+
* The reference tools, bound to one chat's project store.
|
|
22
|
+
*
|
|
23
|
+
* @param session - Project handles for the calling chat.
|
|
24
|
+
* @returns Tool definitions in registration order.
|
|
25
|
+
*/
|
|
26
|
+
export function referenceTools(session) {
|
|
27
|
+
return [
|
|
28
|
+
tool({
|
|
29
|
+
name: 'search_logic',
|
|
30
|
+
description: 'Find literal text in routine logic. Use it to locate a rung, ST line or operand when you do not know the exact symbol name; use find_references when you do.',
|
|
31
|
+
parameters: object({
|
|
32
|
+
query: string('Case-insensitive text to find, matched literally inside rung text, ST lines and diagram node labels. Example: "OTE(Motor_Run)".'),
|
|
33
|
+
limit: integer('Maximum matches to return.', { minimum: 1, maximum: 100, default: 20 }),
|
|
34
|
+
project_id: PROJECT_ID,
|
|
35
|
+
}, ['query']),
|
|
36
|
+
async run(args, exec) {
|
|
37
|
+
const project = await projectOf(session, args, exec);
|
|
38
|
+
const query = requiredString(args, 'query');
|
|
39
|
+
const limit = Math.min(Math.max(integerArg(args, 'limit', 20), 1), 100);
|
|
40
|
+
const matches = logicMatches(project.document, query);
|
|
41
|
+
const page = matches.slice(0, limit);
|
|
42
|
+
const first = page[0];
|
|
43
|
+
return success(`${matches.length} ${matches.length === 1 ? 'match' : 'matches'} for ${JSON.stringify(query)} in routine logic.`, {
|
|
44
|
+
query,
|
|
45
|
+
total: matches.length,
|
|
46
|
+
returned: page.length,
|
|
47
|
+
truncated: matches.length > page.length,
|
|
48
|
+
matches: page,
|
|
49
|
+
}, [
|
|
50
|
+
...(first === undefined || first.program.startsWith('AOI::')
|
|
51
|
+
? []
|
|
52
|
+
: [{ tool: 'get_component', args: { kind: 'routine', name: first.routine, scope: first.program }, why: 'Read the routine that contains the first match.' }]),
|
|
53
|
+
{ tool: 'find_references', args: { symbol: query }, why: 'See every read and write of this name.' },
|
|
54
|
+
], COVERAGE);
|
|
55
|
+
},
|
|
56
|
+
}),
|
|
57
|
+
tool({
|
|
58
|
+
name: 'find_references',
|
|
59
|
+
description: 'Find where one symbol is read or written in exported logic, with scope, access and confidence. Use trace_dependencies for what feeds a symbol.',
|
|
60
|
+
parameters: object({
|
|
61
|
+
symbol: string('Symbol to follow, as declared or as written in logic. Example: "Motor_1" or "Program:MainProgram.Motor_1".'),
|
|
62
|
+
scope: string('Program scope to search. Omit to search every scope.'),
|
|
63
|
+
detail: choice('How much evidence to return.', ['summary', 'evidence'], { default: 'summary' }),
|
|
64
|
+
limit: integer('Maximum evidence rows when detail is "evidence".', { minimum: 1, maximum: 100, default: 20 }),
|
|
65
|
+
project_id: PROJECT_ID,
|
|
66
|
+
}, ['symbol']),
|
|
67
|
+
async run(args, exec) {
|
|
68
|
+
const project = await projectOf(session, args, exec);
|
|
69
|
+
const symbol = requiredString(args, 'symbol');
|
|
70
|
+
const scope = optionalString(args, 'scope');
|
|
71
|
+
const detail = optionalString(args, 'detail') ?? 'summary';
|
|
72
|
+
const limit = Math.min(Math.max(integerArg(args, 'limit', 20), 1), 100);
|
|
73
|
+
const evidence = findReferences(project, symbol, scope);
|
|
74
|
+
const byAccess = countAccess(evidence);
|
|
75
|
+
const rows = evidence.slice(0, limit).map(reference => ({
|
|
76
|
+
scope: reference.scope,
|
|
77
|
+
routine: reference.routine,
|
|
78
|
+
...(reference.rung === undefined ? {} : { rung: reference.rung }),
|
|
79
|
+
...(reference.line === undefined ? {} : { line: reference.line }),
|
|
80
|
+
symbol: reference.symbol,
|
|
81
|
+
...(reference.resolved === undefined ? {} : { resolved: reference.resolved }),
|
|
82
|
+
access: reference.access,
|
|
83
|
+
confidence: reference.confidence,
|
|
84
|
+
context: reference.context,
|
|
85
|
+
}));
|
|
86
|
+
const first = evidence[0];
|
|
87
|
+
return success(`${evidence.length} ${evidence.length === 1 ? 'reference' : 'references'} to ${symbol}: ${byAccess.read} read, ${byAccess.write + byAccess.read_write} write, ${byAccess.unknown} unknown.`, {
|
|
88
|
+
symbol,
|
|
89
|
+
total: evidence.length,
|
|
90
|
+
by_access: byAccess,
|
|
91
|
+
routines: [...new Set(evidence.map(reference => `${reference.scope}/${reference.routine}`))],
|
|
92
|
+
...(detail === 'evidence' ? { returned: rows.length, truncated: evidence.length > rows.length, evidence: rows } : {}),
|
|
93
|
+
}, [
|
|
94
|
+
{ tool: 'trace_dependencies', args: { symbol }, why: 'See what feeds this symbol, not just where it appears.' },
|
|
95
|
+
...(first === undefined
|
|
96
|
+
? []
|
|
97
|
+
: [{ tool: 'get_component', args: { kind: 'routine', name: first.routine, scope: first.scope }, why: 'Read the routine holding the first reference.' }]),
|
|
98
|
+
], COVERAGE);
|
|
99
|
+
},
|
|
100
|
+
}),
|
|
101
|
+
tool({
|
|
102
|
+
name: 'trace_dependencies',
|
|
103
|
+
description: 'Follow what feeds a symbol (upstream) or what it feeds (downstream) through parsed operands. Static evidence only; it does not establish runtime causality.',
|
|
104
|
+
parameters: object({
|
|
105
|
+
symbol: string('Symbol to trace. Example: "Conveyor_Run" or "AOI_EncoderScale::Out_FPM".'),
|
|
106
|
+
scope: string('Program or AOI name that declares the symbol. Omit for controller scope; an AOI::Member symbol resolves against that AOI automatically.'),
|
|
107
|
+
direction: choice('Which way to follow the graph.', ['upstream', 'downstream'], { default: 'upstream' }),
|
|
108
|
+
depth: integer('Maximum graph depth to follow. Default 3.', { minimum: 1, maximum: 6, default: 3 }),
|
|
109
|
+
project_id: PROJECT_ID,
|
|
110
|
+
}, ['symbol']),
|
|
111
|
+
async run(args, exec) {
|
|
112
|
+
const project = await projectOf(session, args, exec);
|
|
113
|
+
const requested = requiredString(args, 'symbol');
|
|
114
|
+
const explicitScope = optionalString(args, 'scope');
|
|
115
|
+
const parts = requested.split('::');
|
|
116
|
+
const qualified = parts.length > 1 && parts[0] !== '';
|
|
117
|
+
const scope = explicitScope === undefined
|
|
118
|
+
? (qualified ? normalizeScope(project.document, parts[0]) : 'controller')
|
|
119
|
+
: normalizeScope(project.document, explicitScope);
|
|
120
|
+
const symbol = qualified ? parts.slice(1).join('::') : requested;
|
|
121
|
+
const direction = optionalString(args, 'direction') === 'downstream' ? 'downstream' : 'upstream';
|
|
122
|
+
const depth = Math.min(Math.max(integerArg(args, 'depth', 3), 1), 6);
|
|
123
|
+
const graph = traceDependencies(project, symbol, scope, direction, {
|
|
124
|
+
depth,
|
|
125
|
+
nodes: 50,
|
|
126
|
+
edges: 100,
|
|
127
|
+
analysis: { references: 5000, edges: 5000 },
|
|
128
|
+
});
|
|
129
|
+
const resolution = graph.resolution;
|
|
130
|
+
if (resolution !== undefined && resolution.resolved === false) {
|
|
131
|
+
const declarations = [...project.document.aois.map(aoi => aoi.name), ...project.document.programs.map(program => program.name)];
|
|
132
|
+
throw new L5xError('UNRESOLVED_SYMBOL', `${requested} does not resolve in scope ${scope}: ${resolution.reason ?? 'not declared in this export'}.`, 'Pass scope with the declaring program or AOI. For an AOI member, write "AOI_Name::Member" or pass scope "AOI_Name" with the member name.', declarations.slice(0, 5));
|
|
133
|
+
}
|
|
134
|
+
const nodes = graph.nodes;
|
|
135
|
+
const edges = graph.edges;
|
|
136
|
+
const coverage = {
|
|
137
|
+
complete: graph.static_complete === true,
|
|
138
|
+
unverified: [
|
|
139
|
+
'runtime causality',
|
|
140
|
+
'external HMI, produced/consumed and driver consumers',
|
|
141
|
+
'dynamic indices',
|
|
142
|
+
...(graph.truncated === true ? ['the graph was truncated at the configured depth or edge bound'] : []),
|
|
143
|
+
],
|
|
144
|
+
};
|
|
145
|
+
return success(`${nodes.length} ${nodes.length === 1 ? 'symbol' : 'symbols'} and ${edges.length} ${edges.length === 1 ? 'edge' : 'edges'} ${direction} of ${requested}${graph.truncated === true ? ' (truncated)' : ''}.`, graph, [
|
|
146
|
+
{ tool: 'find_references', args: { symbol: requested }, why: 'See every occurrence rather than the dependency graph.' },
|
|
147
|
+
...(requested.includes('::') ? [] : [{ tool: 'usage_map', args: { tag: requested, scope }, why: 'Map element and bit usage for this tag.' }]),
|
|
148
|
+
], coverage);
|
|
149
|
+
},
|
|
150
|
+
}),
|
|
151
|
+
tool({
|
|
152
|
+
name: 'resolve_tag',
|
|
153
|
+
description: 'Resolve one Logix address against exported declarations: alias chain, member, integer bit and array subscript, with the evidence that remains unverified.',
|
|
154
|
+
parameters: object({
|
|
155
|
+
address: string('Address as written in logic. Example: "Motor_1.Speed" or "Alarms[2].3".'),
|
|
156
|
+
scope: string('Program scope for a local tag. Omit for controller scope.'),
|
|
157
|
+
project_id: PROJECT_ID,
|
|
158
|
+
}, ['address']),
|
|
159
|
+
async run(args, exec) {
|
|
160
|
+
const project = await projectOf(session, args, exec);
|
|
161
|
+
const address = requiredString(args, 'address');
|
|
162
|
+
const resolution = resolveTag(project.document, optionalString(args, 'scope') ?? 'controller', address);
|
|
163
|
+
const canonical = resolution.canonical;
|
|
164
|
+
return success(resolution.resolved
|
|
165
|
+
? `${address} resolves to ${canonical ?? 'its declared type'}${resolution.data_type === undefined ? '' : ` (${resolution.data_type})`}.`
|
|
166
|
+
: `${address} does not resolve: ${resolution.reason ?? 'not declared in this export'}.`, resolution, [
|
|
167
|
+
{ tool: 'find_references', args: { symbol: address }, why: 'See where this address is used.' },
|
|
168
|
+
...(canonical === undefined
|
|
169
|
+
? []
|
|
170
|
+
: [{ tool: 'find_references', args: { symbol: canonical.split(/[.[]/u)[0] ?? canonical }, why: 'Follow the base declaration this address resolves to.' }]),
|
|
171
|
+
], resolution.indices_verified
|
|
172
|
+
? COVERAGE
|
|
173
|
+
: { complete: false, unverified: ['a dynamic subscript was not expanded', ...COVERAGE.unverified] });
|
|
174
|
+
},
|
|
175
|
+
}),
|
|
176
|
+
tool({
|
|
177
|
+
name: 'usage_map',
|
|
178
|
+
description: 'Map how one declared tag is used: which array elements and bits are read or written, which remain free, and where each appears.',
|
|
179
|
+
parameters: object({
|
|
180
|
+
tag: string('Declared tag name. Example: "Alarm_Bits".'),
|
|
181
|
+
scope: string('Declaring scope: "controller" or a program name. Defaults to controller.'),
|
|
182
|
+
project_id: PROJECT_ID,
|
|
183
|
+
}, ['tag']),
|
|
184
|
+
async run(args, exec) {
|
|
185
|
+
const project = await projectOf(session, args, exec);
|
|
186
|
+
const tag = requiredString(args, 'tag');
|
|
187
|
+
const scope = optionalString(args, 'scope') ?? 'controller';
|
|
188
|
+
const declared = project.document.tags.find(entry => entry.name.toLowerCase() === tag.toLowerCase() && entry.scope.toLowerCase() === scope.toLowerCase());
|
|
189
|
+
if (declared === undefined) {
|
|
190
|
+
const declaredHere = project.document.tags
|
|
191
|
+
.filter(entry => entry.scope.toLowerCase() === scope.toLowerCase())
|
|
192
|
+
.map(entry => entry.name);
|
|
193
|
+
throw new L5xError('COMPONENT_NOT_FOUND', `Tag ${JSON.stringify(tag)} is not declared in scope ${JSON.stringify(scope)}.`, 'Call list_components with kind "tag" to see the declared names in this scope.', declaredHere.slice(0, 5));
|
|
194
|
+
}
|
|
195
|
+
const map = usageMap(project, scope, tag);
|
|
196
|
+
const indexes = map.indexes ?? [];
|
|
197
|
+
return success(`${declared.name}: ${indexes.length} indexed ${indexes.length === 1 ? 'element' : 'elements'} used${map.whole_element ? ', plus whole-tag use' : ''}.`, map, [{ tool: 'find_references', args: { symbol: declared.name, scope }, why: 'See every occurrence behind these counts.' }], {
|
|
198
|
+
complete: false,
|
|
199
|
+
unverified: ['external HMI, produced/consumed and driver consumers', 'dynamic indices are not expanded'],
|
|
200
|
+
});
|
|
201
|
+
},
|
|
202
|
+
}),
|
|
203
|
+
];
|
|
204
|
+
}
|
|
205
|
+
/** Resolve the calling project. */
|
|
206
|
+
async function projectOf(session, args, exec) {
|
|
207
|
+
return await session.require(optionalString(args, 'project_id'), exec);
|
|
208
|
+
}
|
|
209
|
+
/** The declaration scope a symbol resolves against: an AOI name becomes `AOI::Name`. */
|
|
210
|
+
function normalizeScope(document, scope) {
|
|
211
|
+
if (scope.startsWith('AOI::'))
|
|
212
|
+
return scope;
|
|
213
|
+
return document.aois.some(aoi => aoi.name === scope) ? `AOI::${scope}` : scope;
|
|
214
|
+
}
|
|
215
|
+
/** Count evidence by access kind. */
|
|
216
|
+
function countAccess(evidence) {
|
|
217
|
+
const counts = { read: 0, write: 0, read_write: 0, call: 0, unknown: 0 };
|
|
218
|
+
for (const entry of evidence) {
|
|
219
|
+
if (entry.access === 'read')
|
|
220
|
+
counts.read += 1;
|
|
221
|
+
else if (entry.access === 'write')
|
|
222
|
+
counts.write += 1;
|
|
223
|
+
else if (entry.access === 'read_write')
|
|
224
|
+
counts.read_write += 1;
|
|
225
|
+
else if (entry.access === 'call')
|
|
226
|
+
counts.call += 1;
|
|
227
|
+
else
|
|
228
|
+
counts.unknown += 1;
|
|
229
|
+
}
|
|
230
|
+
return counts;
|
|
231
|
+
}
|
|
232
|
+
/** Literal, case-insensitive matches across routine logic. */
|
|
233
|
+
function logicMatches(document, query) {
|
|
234
|
+
const needle = query.toLowerCase();
|
|
235
|
+
const matches = [];
|
|
236
|
+
const routines = [...document.routines, ...document.aois.flatMap(aoi => aoi.routines.map(routine => ({ ...routine, program: `AOI::${aoi.name}` })))];
|
|
237
|
+
for (const routine of routines) {
|
|
238
|
+
if (routine.body.type === 'RLL') {
|
|
239
|
+
for (const rung of routine.body.rungs) {
|
|
240
|
+
if (!rung.text.toLowerCase().includes(needle))
|
|
241
|
+
continue;
|
|
242
|
+
matches.push({
|
|
243
|
+
program: routine.program,
|
|
244
|
+
routine: routine.name,
|
|
245
|
+
language: 'RLL',
|
|
246
|
+
rung: rung.number,
|
|
247
|
+
text: rung.text,
|
|
248
|
+
...(rung.comment === undefined ? {} : { comment: rung.comment }),
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
else if (routine.body.type === 'ST') {
|
|
253
|
+
routine.body.code.split(/\r?\n/u).forEach((line, index) => {
|
|
254
|
+
if (line.toLowerCase().includes(needle)) {
|
|
255
|
+
matches.push({ program: routine.program, routine: routine.name, language: 'ST', line: index + 1, text: line.trim() });
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
else if (routine.body.type === 'FBD' || routine.body.type === 'SFC') {
|
|
260
|
+
for (const node of routine.body.nodes) {
|
|
261
|
+
const text = node.text ?? node.operand ?? '';
|
|
262
|
+
if (text.toLowerCase().includes(needle)) {
|
|
263
|
+
matches.push({ program: routine.program, routine: routine.name, language: routine.body.type, sheet: node.sheet, text });
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return matches;
|
|
269
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The online-supervised entry's offline half: SDK status, ACD export, L5X
|
|
3
|
+
* verification, routine build into a new ACD, and review-evidence checking.
|
|
4
|
+
*
|
|
5
|
+
* No tool here contacts a controller. Upload, mode change and download belong
|
|
6
|
+
* to the controller tools, which require per-call human approval and are not
|
|
7
|
+
* part of this entry yet.
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-engineering-l5x/tools/studio
|
|
10
|
+
*/
|
|
11
|
+
import type { ToolDefinition } from '../core/tool.ts';
|
|
12
|
+
import type { ProjectSession } from '../session.ts';
|
|
13
|
+
import type { StudioState } from '../studio-state.ts';
|
|
14
|
+
/**
|
|
15
|
+
* The studio tools, bound to one chat's projects and export provenance.
|
|
16
|
+
*
|
|
17
|
+
* @param session - Project handles for the calling chat.
|
|
18
|
+
* @param state - ACD export records for the calling chat.
|
|
19
|
+
* @returns Tool definitions in registration order.
|
|
20
|
+
*/
|
|
21
|
+
export declare function studioTools(session: ProjectSession, state: StudioState): readonly ToolDefinition[];
|