@aryacode/dsh-engineering 0.5.0 → 0.7.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 +52 -0
- package/README.md +9 -8
- package/cordis.patch.yml +1 -1
- package/entries/netlogic.js +266 -0
- package/entries/optix.js +13 -0
- package/package.json +10 -10
- package/vendor/l5x/SKILL.md +27 -7
- package/vendor/l5x/lib/engine/change-set.d.ts +9 -1
- package/vendor/l5x/lib/engine/change-set.js +32 -7
- package/vendor/l5x/lib/engine/documentation.d.ts +58 -0
- package/vendor/l5x/lib/engine/documentation.js +237 -0
- package/vendor/l5x/lib/engine/instructions.d.ts +50 -0
- package/vendor/l5x/lib/engine/instructions.js +107 -0
- package/vendor/l5x/lib/engine/ladder.d.ts +74 -0
- package/vendor/l5x/lib/engine/ladder.js +209 -0
- package/vendor/l5x/lib/index.d.ts +1 -0
- package/vendor/l5x/lib/index.js +3 -1
- package/vendor/l5x/lib/model/routines.js +6 -1
- package/vendor/l5x/lib/session.d.ts +20 -0
- package/vendor/l5x/lib/session.js +36 -0
- package/vendor/l5x/lib/tools/document.d.ts +18 -0
- package/vendor/l5x/lib/tools/document.js +119 -0
- package/vendor/l5x/lib/tools/drafts.js +109 -3
- package/vendor/optix/SKILL.md +68 -0
- package/vendor/optix/netlogic/SOURCE.json +12 -0
- package/vendor/optix/netlogic/index.json +1987 -0
- package/vendor/optix/netlogic/pages/accessing-project-nodes.md +113 -0
- package/vendor/optix/netlogic/pages/advanced-svg.md +124 -0
- package/vendor/optix/netlogic/pages/alarming.md +489 -0
- package/vendor/optix/netlogic/pages/async-tasks.md +236 -0
- package/vendor/optix/netlogic/pages/audit-signature.md +91 -0
- package/vendor/optix/netlogic/pages/colors.md +122 -0
- package/vendor/optix/netlogic/pages/companion-specs.md +77 -0
- package/vendor/optix/netlogic/pages/creating-objects.md +370 -0
- package/vendor/optix/netlogic/pages/database-interaction.md +437 -0
- package/vendor/optix/netlogic/pages/datagrids.md +123 -0
- package/vendor/optix/netlogic/pages/debugging-tips.md +605 -0
- package/vendor/optix/netlogic/pages/designtime-tags-import.md +531 -0
- package/vendor/optix/netlogic/pages/dialog-boxes.md +150 -0
- package/vendor/optix/netlogic/pages/dispatch-events.md +200 -0
- package/vendor/optix/netlogic/pages/dynamic-links.md +825 -0
- package/vendor/optix/netlogic/pages/element-access.md +1208 -0
- package/vendor/optix/netlogic/pages/engineering-units.md +298 -0
- package/vendor/optix/netlogic/pages/events.md +340 -0
- package/vendor/optix/netlogic/pages/execute-command.md +202 -0
- package/vendor/optix/netlogic/pages/fto-studio-cli.md +99 -0
- package/vendor/optix/netlogic/pages/good-practices.md +50 -0
- package/vendor/optix/netlogic/pages/information-model.md +89 -0
- package/vendor/optix/netlogic/pages/log-output.md +22 -0
- package/vendor/optix/netlogic/pages/managing-aliases.md +22 -0
- package/vendor/optix/netlogic/pages/manipulate-nodes.md +72 -0
- package/vendor/optix/netlogic/pages/mqtt.md +240 -0
- package/vendor/optix/netlogic/pages/netlogic-lifecycle.md +270 -0
- package/vendor/optix/netlogic/pages/netlogic-overview.md +155 -0
- package/vendor/optix/netlogic/pages/opcua.md +325 -0
- package/vendor/optix/netlogic/pages/plc-tags.md +219 -0
- package/vendor/optix/netlogic/pages/queries.md +1297 -0
- package/vendor/optix/netlogic/pages/random-generation.md +90 -0
- package/vendor/optix/netlogic/pages/recipes.md +375 -0
- package/vendor/optix/netlogic/pages/recipex.md +2383 -0
- package/vendor/optix/netlogic/pages/regex.md +15 -0
- package/vendor/optix/netlogic/pages/register-observers.md +346 -0
- package/vendor/optix/netlogic/pages/reports.md +141 -0
- package/vendor/optix/netlogic/pages/resource-uri.md +308 -0
- package/vendor/optix/netlogic/pages/runtime-tags-import.md +280 -0
- package/vendor/optix/netlogic/pages/serial-port.md +357 -0
- package/vendor/optix/netlogic/pages/sessions.md +58 -0
- package/vendor/optix/netlogic/pages/system-node.md +153 -0
- package/vendor/optix/netlogic/pages/template-library.md +127 -0
- package/vendor/optix/netlogic/pages/threading.md +236 -0
- package/vendor/optix/netlogic/pages/translations.md +417 -0
- package/vendor/optix/netlogic/pages/trends.md +128 -0
- package/vendor/optix/netlogic/pages/ui-animations.md +115 -0
- package/vendor/optix/netlogic/pages/users-groups.md +416 -0
- package/vendor/optix/netlogic/pages/variables-formatting.md +16 -0
- package/vendor/optix/netlogic/pages/variables-interaction.md +557 -0
- package/vendor/optix/netlogic/pages/variables-structure.md +155 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,57 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.7.0] - 2026-10-27
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Ladder generation that cannot emit invalid NeutralText.** Three pieces:
|
|
8
|
+
- `list_instructions` — the instruction catalog the composer validates: mnemonic, category, summary
|
|
9
|
+
and operand slots with their kinds (BOOL, NUMBER, TIMER, …), filterable. 41 instructions, covering
|
|
10
|
+
every mnemonic the Laminator project uses (45 distinct across 860 rungs).
|
|
11
|
+
- `compose_rung` — builds one rung from a structure tree (`spec_json`) or a named pattern
|
|
12
|
+
(`start_stop_latch`, `fault_latch`, `ton_delay`, `interlock`), resolves every operand against the
|
|
13
|
+
export (including unqualified program tags and aliases), type-checks each slot, emits the export's
|
|
14
|
+
own bracket style, and re-parses its output with the same parser `draft_rung` uses. Unknown
|
|
15
|
+
instructions name the nearest catalog entries; a wrong operand count, a non-BOOL contact, a
|
|
16
|
+
non-numeric value slot, an undeclared tag or an undeclared AOI is refused before anything is staged.
|
|
17
|
+
Passing `program`, `routine`, `mode` and `rung` stages the rung in the draft; `fault_latch` stages
|
|
18
|
+
its two rungs consecutively.
|
|
19
|
+
- **Rung comments are read and written.** The model read the `Comment` **attribute** only, so a real
|
|
20
|
+
Studio export's 572 child `<Comment>` elements were invisible. `decodeRung` now supports both shapes,
|
|
21
|
+
`draft_rung` accepts a `comment`, and `draft_rung_comment` sets or clears one on an existing rung.
|
|
22
|
+
CDATA terminators in a comment round-trip through the existing escaping.
|
|
23
|
+
- **Documentation dimension.** `document_project` writes a handover bundle into a NEW folder and never
|
|
24
|
+
touches the export: `PROJECT.md` (controller, revision, tasks, programs, counts), `io.md`/`io.csv`,
|
|
25
|
+
`tags.csv` and `tags-studio.csv` (the `Tag Name,Description` shape Studio's Tags and Comments import
|
|
26
|
+
expects), and `rungs/<program>.md` with each rung's number, comment, NeutralText, instructions and
|
|
27
|
+
resolved tags. Filters: `include`, `programs`, `routines`, `rungs_per_file`.
|
|
28
|
+
|
|
29
|
+
### Documentation
|
|
30
|
+
|
|
31
|
+
- `packages/l5x/SKILL.md`: a Generate section (catalog → compose → validate → apply) and a
|
|
32
|
+
Documentation section (bundle contents, the comment round trip, what the bundle cannot prove).
|
|
33
|
+
- README: 89 tools, L5X 44, and the 0.7.0 milestone.
|
|
34
|
+
|
|
35
|
+
## [0.6.0] - 2026-10-27
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
|
|
39
|
+
- **FactoryTalk Optix NetLogic reference.** The bundle vendors a pinned snapshot of Rockwell's
|
|
40
|
+
[NetLogic cheat sheet](https://github.com/FactoryTalk-Optix/NetLogic_CheatSheet) (51 pages, commit
|
|
41
|
+
`5e652541`) and exposes it as one tool instead of dumping C# into the prompt:
|
|
42
|
+
`optix_netlogic` with no arguments returns the contents, a `query` ranks pages by title, topic and
|
|
43
|
+
symbol before body text, and a `slug` returns that page verbatim. Every page result carries its
|
|
44
|
+
`risk` (`normal`, `advanced` or `unsupported`, with the repository's own Advanced section marked
|
|
45
|
+
unsupported), the upstream commit, a source URL and the upstream disclaimer that the snippets may use
|
|
46
|
+
non-public APIs and must not be used in production as-is.
|
|
47
|
+
- **A FactoryTalk Optix skill.** The row previously had tools and no guidance; `factorytalk-optix` now
|
|
48
|
+
names the surface, the NetLogic reader and its risk rule, and the report-before-claiming boundary.
|
|
49
|
+
- The `optix` row gains a `netlogic` switch (on by default). Off removes one tool and the guidance
|
|
50
|
+
without touching the prebuilt engine.
|
|
51
|
+
- `scripts/sync-netlogic.mjs` is the only writer of the snapshot: it pins a commit, copies
|
|
52
|
+
`pages/*.md`, and generates `index.json` plus `SOURCE.json` (commit, page count, risk counts).
|
|
53
|
+
`pnpm netlogic:sync` refreshes it; `test/netlogic.spec.mjs` fails when the index and the pages drift.
|
|
54
|
+
|
|
3
55
|
## [0.5.0] - 2026-10-27
|
|
4
56
|
|
|
5
57
|
Commissioned against the Laminator fixtures: a real Studio 5000 ACD exported to a 5.4 MB L5X, a
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
MotionWorks IEC and FactoryTalk Optix sit behind a single install, one row per surface. A row is the
|
|
5
5
|
switch: disable it and that plugin unloads, so its tool schemas leave the request.
|
|
6
6
|
|
|
7
|
-
`@aryacode/dsh-engineering` ·
|
|
7
|
+
`@aryacode/dsh-engineering` · 89 tools · 5 rows · 4 products · MIT
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -36,18 +36,18 @@ bundle.
|
|
|
36
36
|
|
|
37
37
|
| Product | Tools | Default | Needs |
|
|
38
38
|
|---|---|---|---|
|
|
39
|
-
| **Studio 5000 L5X** |
|
|
39
|
+
| **Studio 5000 L5X** | 44 | all on | Node for offline reads; Studio 5000 + Logix Designer SDK for SDK work; a controller for live |
|
|
40
40
|
| **FactoryTalk View ME** | 14 | all on | Node for an export folder; FactoryTalk View Studio ME for discovery and the handoff |
|
|
41
41
|
| **MotionWorks IEC** | 24 | all on | MotionWorks IEC 3 Pro installed; verified against 1.19 |
|
|
42
|
-
| **FactoryTalk Optix** |
|
|
42
|
+
| **FactoryTalk Optix** | 10 (3 need preview) | all on | Optix Studio installed; Tesseract for OCR |
|
|
43
43
|
|
|
44
44
|
Tool groups, by product:
|
|
45
45
|
|
|
46
|
-
- **L5X** —
|
|
46
|
+
- **L5X** — 13 read (`open_project` … `document_project`), 11 staged drafts (`draft_*`, `compose_rung`), 6 Logix
|
|
47
47
|
Designer SDK, 7 supervised controller, 7 live EtherNet/IP (2 reads, 2 identity/discovery, 1 batch read, 2 approval-gated writes)
|
|
48
48
|
- **FactoryTalk View** — 6 read, 5 staged edits, 3 Studio handoff
|
|
49
49
|
- **MotionWorks IEC** — 7 session, 7 saved-project reads, 3 verify, 7 direct edits
|
|
50
|
-
- **Optix** —
|
|
50
|
+
- **Optix** — 10 tools, linked to the `factorytalk-optix` guidance; `optix_netlogic` reads the pinned NetLogic cheat-sheet snapshot, and the 9 bridge tools need Studio
|
|
51
51
|
|
|
52
52
|
## Switches
|
|
53
53
|
|
|
@@ -62,7 +62,7 @@ down:
|
|
|
62
62
|
| `ftview` | FactoryTalk View ME read, guidance and edits; optionally the Studio handoff | `edit` (on), `studio` (on) |
|
|
63
63
|
| `mwi` | MotionWorks IEC session, reads, verify, guidance and the edits | `workspace` (empty), `edit` (on) |
|
|
64
64
|
| `viewers` | ladder + ME display previews | — |
|
|
65
|
-
| `optix` | FactoryTalk Optix Studio authoring | `bridgeUrls` (8768), `bridgeToken` (empty), `allowWrites` (on), `allowDestructive` (on), `allowInvoke` (on), `preview` (on), `allowPreviewInput` (on), `cdpUrl`, `previewUrl`, `ocrExecutable` (empty), `ocrLanguage` (`eng`), `ocrTessdataDirectory` (empty) |
|
|
65
|
+
| `optix` | FactoryTalk Optix Studio authoring and the NetLogic reader | `bridgeUrls` (8768), `bridgeToken` (empty), `allowWrites` (on), `allowDestructive` (on), `allowInvoke` (on), `preview` (on), `allowPreviewInput` (on), `cdpUrl`, `previewUrl`, `ocrExecutable` (empty), `ocrLanguage` (`eng`), `ocrTessdataDirectory` (empty), `netlogic` (on) |
|
|
66
66
|
|
|
67
67
|
**Why the row list collapsed.** The Plugins page lists a bundle's rows flat and skips `cordis:group`
|
|
68
68
|
containers (`plugin-manager/src/index.ts`: *"Group rows are containers … their children carry the
|
|
@@ -112,7 +112,7 @@ the lean profile is the one that turns them off.
|
|
|
112
112
|
- FBD/SFC semantics are not exercised
|
|
113
113
|
- FactoryTalk View Studio work is a **manual handoff**: the bundle plans the import and preflights
|
|
114
114
|
changed files, it does not import
|
|
115
|
-
- Optix
|
|
115
|
+
- Optix is linked to a skill now: the `factorytalk-optix` guidance plus the NetLogic reader; the bridge tools still follow the `optix` row config and the Studio bridge
|
|
116
116
|
|
|
117
117
|
**Not in this repository** — CAD mounts as an independent plugin.
|
|
118
118
|
|
|
@@ -120,6 +120,7 @@ the lean profile is the one that turns them off.
|
|
|
120
120
|
|
|
121
121
|
| Version | What landed |
|
|
122
122
|
|---|---|
|
|
123
|
+
| **0.7.0** | Ladder generation that cannot emit invalid NeutralText: an instruction catalog, a pattern/tree composer that resolves and type-checks every operand, rung comments read and written, and a documentation bundle (`document_project`) with the Studio tags-and-comments CSV |
|
|
123
124
|
| **0.5.0** | Commissioned on real files: FactoryTalk View reads genuine Studio exports (`<gfx>` displays, comma CSVs, UTF-16) instead of reporting zero; L5X alias resolution cuts 315 false "unresolved reference" findings to 16 on a 1279-tag project; `add_tag` no longer demands a value; a locked ACD exports from a copy |
|
|
124
125
|
| **0.4.17** | MotionWorks edits the real project again: the pre-edit backup is written where the bridge and engine verify it, an already-open project is attached instead of reloaded, and `mw_get` returns the body hash the edit takes |
|
|
125
126
|
| **0.4.15** | Every tool surface ships on by default; the row Configure pages switch them off |
|
|
@@ -175,7 +176,7 @@ even when the implementation behind a row is replaced.
|
|
|
175
176
|
| `ftview` | `dsh-engineering-ftview` | `edit` on · `studio` on |
|
|
176
177
|
| `mwi` | `dsh-engineering-mwi` | `edit` on |
|
|
177
178
|
| `viewers` | `@aryacode/dsh-engineering` (its own client face: ladder + ME display previews) | — |
|
|
178
|
-
| `optix` | `@aryacode/dsh-engineering/optix` (wraps the prebuilt `@aryacode/dsh-tool-ft-optix`) | `allowWrites` on · `allowDestructive` on · `allowInvoke` on · `preview` on |
|
|
179
|
+
| `optix` | `@aryacode/dsh-engineering/optix` (wraps the prebuilt `@aryacode/dsh-tool-ft-optix`) | `allowWrites` on · `allowDestructive` on · `allowInvoke` on · `preview` on · `netlogic` on |
|
|
179
180
|
|
|
180
181
|
## Safety
|
|
181
182
|
|
package/cordis.patch.yml
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
# l5x → editing, studio, live, allowWrites (all on by default)
|
|
10
10
|
# ftview → edit, studio (both on by default)
|
|
11
11
|
# mwi → edit (on by default)
|
|
12
|
-
# optix → authoring, destructive, invoke, preview (all on by default)
|
|
12
|
+
# optix → authoring, destructive, invoke, preview, netlogic (all on by default)
|
|
13
13
|
#
|
|
14
14
|
# Every surface ships on so a fresh install can use the full plugin. Turn one
|
|
15
15
|
# off from the row's Configure page when the profile is read-only or the request
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FactoryTalk Optix NetLogic retrieval.
|
|
3
|
+
*
|
|
4
|
+
* The vendor repository (FactoryTalk-Optix/NetLogic_CheatSheet) is vendored as a
|
|
5
|
+
* pinned snapshot by `scripts/sync-netlogic.mjs`. This entry exposes it as one tool
|
|
6
|
+
* so a model can pull the page it needs instead of every page at once: the table of
|
|
7
|
+
* contents costs a few hundred tokens, a page a few thousand.
|
|
8
|
+
*
|
|
9
|
+
* Every answer carries the page's risk flag and the upstream disclaimer. The
|
|
10
|
+
* repository itself says the snippets may use non-public APIs, may break a project
|
|
11
|
+
* and are not for production as-is; this module must not outrank that.
|
|
12
|
+
*
|
|
13
|
+
* @module dsh-engineering/entries/netlogic
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
17
|
+
import { dirname, join } from 'node:path'
|
|
18
|
+
import { fileURLToPath } from 'node:url'
|
|
19
|
+
|
|
20
|
+
/** Plugin name in a composition. */
|
|
21
|
+
export const name = 'engineering-optix-netlogic'
|
|
22
|
+
|
|
23
|
+
/** The tool needs the registry; the skill is registered when the host offers it. */
|
|
24
|
+
export const inject = ['tools']
|
|
25
|
+
|
|
26
|
+
/** Registration metadata, kept here so a test can assert it without the file. */
|
|
27
|
+
export const SKILL = {
|
|
28
|
+
name: 'factorytalk-optix',
|
|
29
|
+
description: 'FactoryTalk Optix Studio engineering: inspect and author the loaded project through the Studio bridge, validate expressions, take preview screenshots, read NetLogic C# snippets from the pinned cheat-sheet snapshot, and start from a template.',
|
|
30
|
+
whenToUse: 'The user asks about FactoryTalk Optix — project structure, nodes, variables, aliases, expressions, NetLogic C#, alarms, trends, recipes, OPC UA, or the emulator preview. Route FactoryTalk View ME elsewhere.',
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The upstream statement every page result repeats. */
|
|
34
|
+
export const DISCLAIMER = 'Reference only. The upstream repository states these snippets may use non-public APIs, may break a project, and must not be used in production as-is; adapt and test them in your own project.'
|
|
35
|
+
|
|
36
|
+
const HERE = dirname(fileURLToPath(import.meta.url))
|
|
37
|
+
const DATA_DIRS = [
|
|
38
|
+
join(HERE, '..', 'vendor', 'optix', 'netlogic'),
|
|
39
|
+
join(HERE, '..', 'packages', 'optix', 'netlogic'),
|
|
40
|
+
]
|
|
41
|
+
const SKILL_PATHS = [
|
|
42
|
+
join(HERE, '..', 'vendor', 'optix', 'SKILL.md'),
|
|
43
|
+
join(HERE, '..', 'packages', 'optix', 'SKILL.md'),
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
const RISKS = new Set(['normal', 'advanced', 'unsupported'])
|
|
47
|
+
|
|
48
|
+
/** The first existing path from a candidate list. */
|
|
49
|
+
function first(candidates) {
|
|
50
|
+
for (const candidate of candidates) if (existsSync(candidate)) return candidate
|
|
51
|
+
return undefined
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The vendored snapshot directory, or undefined when it was not shipped. */
|
|
55
|
+
export function dataDirectory() {
|
|
56
|
+
return first(DATA_DIRS)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Cached index and source pin; the snapshot is immutable for a process. */
|
|
60
|
+
let cachedSnapshot
|
|
61
|
+
function snapshot() {
|
|
62
|
+
if (cachedSnapshot === undefined) cachedSnapshot = loadSnapshot()
|
|
63
|
+
return cachedSnapshot
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Cached page text, keyed by slug. */
|
|
67
|
+
const pageCache = new Map()
|
|
68
|
+
|
|
69
|
+
/** Read the generated index and source pin. */
|
|
70
|
+
export function loadSnapshot() {
|
|
71
|
+
const directory = dataDirectory()
|
|
72
|
+
if (directory === undefined) throw new Error('The NetLogic snapshot is missing; run `pnpm netlogic:sync` before packing.')
|
|
73
|
+
const index = JSON.parse(readFileSync(join(directory, 'index.json'), 'utf8'))
|
|
74
|
+
const source = JSON.parse(readFileSync(join(directory, 'SOURCE.json'), 'utf8'))
|
|
75
|
+
if (!Array.isArray(index) || index.length === 0) throw new Error('The NetLogic index is empty; run `pnpm netlogic:sync`.')
|
|
76
|
+
for (const row of index) {
|
|
77
|
+
if (typeof row?.slug !== 'string' || !/^[a-z0-9][a-z0-9-]*$/u.test(row.slug)) throw new Error(`The NetLogic index has an invalid slug: ${String(row?.slug)}`)
|
|
78
|
+
if (!RISKS.has(row.risk)) throw new Error(`The NetLogic index has an invalid risk for ${row.slug}: ${String(row.risk)}`)
|
|
79
|
+
}
|
|
80
|
+
return { directory, index, source }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Read one page's markdown, refusing anything outside the snapshot. */
|
|
84
|
+
export function pageOf(slug) {
|
|
85
|
+
if (typeof slug !== 'string' || !/^[a-z0-9][a-z0-9-]*$/u.test(slug)) {
|
|
86
|
+
throw Object.assign(new Error(`"${String(slug)}" is not a page slug.`), { code: 'INVALID_ARGUMENT' })
|
|
87
|
+
}
|
|
88
|
+
const cached = pageCache.get(slug)
|
|
89
|
+
if (cached !== undefined) return cached
|
|
90
|
+
const { directory } = snapshot()
|
|
91
|
+
const path = join(directory, 'pages', `${slug}.md`)
|
|
92
|
+
if (!existsSync(path)) throw Object.assign(new Error(`No page named "${slug}".`), { code: 'COMPONENT_NOT_FOUND' })
|
|
93
|
+
const markdown = readFileSync(path, 'utf8')
|
|
94
|
+
pageCache.set(slug, markdown)
|
|
95
|
+
return markdown
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Score one page against a query; higher is better, zero means no hit. */
|
|
99
|
+
function scoreOf(row, query) {
|
|
100
|
+
const needle = query.toLowerCase()
|
|
101
|
+
if (row.slug === needle) return 100
|
|
102
|
+
if (row.title.toLowerCase() === needle) return 90
|
|
103
|
+
if (row.symbols.some(symbol => symbol.toLowerCase() === needle)) return 80
|
|
104
|
+
if (row.slug.includes(needle)) return 60
|
|
105
|
+
if (row.title.toLowerCase().includes(needle)) return 55
|
|
106
|
+
if (row.topics.some(topic => topic === needle)) return 45
|
|
107
|
+
if (row.symbols.some(symbol => symbol.toLowerCase().startsWith(needle))) return 35
|
|
108
|
+
return 0
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Rank pages by metadata, then by body text for the remainder. */
|
|
112
|
+
export function search(index, query, limit = 8) {
|
|
113
|
+
const scored = index.map(row => ({ row, score: scoreOf(row, query) })).filter(hit => hit.score > 0)
|
|
114
|
+
if (scored.length < limit) {
|
|
115
|
+
const seen = new Set(scored.map(hit => hit.row.slug))
|
|
116
|
+
for (const row of index) {
|
|
117
|
+
if (seen.has(row.slug)) continue
|
|
118
|
+
const body = pageOf(row.slug)
|
|
119
|
+
if (body.toLowerCase().includes(query.toLowerCase())) scored.push({ row, score: 20 })
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return scored
|
|
123
|
+
.sort((left, right) => right.score - left.score || left.row.title.localeCompare(right.row.title))
|
|
124
|
+
.slice(0, limit)
|
|
125
|
+
.map(hit => ({ slug: hit.row.slug, title: hit.row.title, section: hit.row.section, risk: hit.row.risk, score: hit.score }))
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** The coverage every answer carries. */
|
|
129
|
+
const COVERAGE = {
|
|
130
|
+
complete: false,
|
|
131
|
+
unverified: [
|
|
132
|
+
'C# compiles against the installed Optix version',
|
|
133
|
+
'the snippet is safe for this project',
|
|
134
|
+
'runtime behaviour in the emulator or on the panel',
|
|
135
|
+
],
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Answer one call.
|
|
140
|
+
*
|
|
141
|
+
* @param args - `slug` for one page, `query` to search, or neither for the contents.
|
|
142
|
+
* @returns The tool envelope.
|
|
143
|
+
*/
|
|
144
|
+
export function answer(args = {}) {
|
|
145
|
+
const { index, source } = snapshot()
|
|
146
|
+
const slug = typeof args.slug === 'string' && args.slug.trim() !== '' ? args.slug.trim() : undefined
|
|
147
|
+
if (slug !== undefined) {
|
|
148
|
+
const markdown = pageOf(slug)
|
|
149
|
+
const row = index.find(entry => entry.slug === slug)
|
|
150
|
+
return {
|
|
151
|
+
summary: `Read ${row.title} (${row.risk}) from ${source.repository}@${source.commit.slice(0, 8)}.`,
|
|
152
|
+
data: {
|
|
153
|
+
slug,
|
|
154
|
+
title: row.title,
|
|
155
|
+
section: row.section,
|
|
156
|
+
risk: row.risk,
|
|
157
|
+
commit: source.commit,
|
|
158
|
+
source_url: `https://github.com/${source.repository}/blob/${source.commit}/pages/${slug}.md`,
|
|
159
|
+
code_blocks: Math.floor((markdown.match(/^```/gmu) ?? []).length / 2),
|
|
160
|
+
disclaimer: DISCLAIMER,
|
|
161
|
+
markdown,
|
|
162
|
+
},
|
|
163
|
+
next: [{ tool: 'optix_edit', why: 'Apply the adapted snippet through the Studio bridge, then validate.' }],
|
|
164
|
+
coverage: COVERAGE,
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const query = typeof args.query === 'string' && args.query.trim() !== '' ? args.query.trim() : undefined
|
|
168
|
+
if (query === undefined) {
|
|
169
|
+
return {
|
|
170
|
+
summary: `${index.length} NetLogic pages from ${source.repository}@${source.commit.slice(0, 8)}.`,
|
|
171
|
+
data: {
|
|
172
|
+
commit: source.commit,
|
|
173
|
+
disclaimer: DISCLAIMER,
|
|
174
|
+
pages: index.map(row => ({ slug: row.slug, title: row.title, section: row.section, risk: row.risk })),
|
|
175
|
+
},
|
|
176
|
+
next: [{ tool: 'optix_netlogic', args: { query: 'sessions' }, why: 'Read the page for a topic before writing NetLogic.' }],
|
|
177
|
+
coverage: COVERAGE,
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const requested = typeof args.limit === 'number' && Number.isInteger(args.limit) ? args.limit : 8
|
|
181
|
+
const limit = Math.min(Math.max(requested, 1), 20)
|
|
182
|
+
const hits = search(index, query, limit)
|
|
183
|
+
return {
|
|
184
|
+
summary: hits.length === 0
|
|
185
|
+
? `No NetLogic page matches "${query}".`
|
|
186
|
+
: `${hits.length} NetLogic page(s) match "${query}".`,
|
|
187
|
+
data: { commit: source.commit, query, hits, ...(hits.length === 0 ? { suggestion: 'Call optix_netlogic with no arguments for the contents, then pass one slug.' } : {}) },
|
|
188
|
+
next: hits.length === 0
|
|
189
|
+
? [{ tool: 'optix_netlogic', why: 'List the contents and pick a slug.' }]
|
|
190
|
+
: [{ tool: 'optix_netlogic', args: { slug: hits[0].slug }, why: 'Read the best-matching page.' }],
|
|
191
|
+
coverage: COVERAGE,
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** The failure envelope every engineering tool returns. */
|
|
196
|
+
function failure(error) {
|
|
197
|
+
const code = typeof error?.code === 'string' ? error.code : 'INTERNAL'
|
|
198
|
+
return {
|
|
199
|
+
error: {
|
|
200
|
+
code,
|
|
201
|
+
message: error instanceof Error ? error.message : String(error),
|
|
202
|
+
fix: code === 'COMPONENT_NOT_FOUND'
|
|
203
|
+
? 'Call optix_netlogic with no arguments for the available slugs.'
|
|
204
|
+
: 'Correct the argument and retry; the snapshot is local and needs no network.',
|
|
205
|
+
candidates: [],
|
|
206
|
+
},
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** The tool definition this entry registers. */
|
|
211
|
+
export const definition = {
|
|
212
|
+
name: 'optix_netlogic',
|
|
213
|
+
description: 'Read the pinned FactoryTalk Optix NetLogic cheat-sheet snapshot: return the contents with no arguments, ranked pages for a query, or one page in full for a slug. Every page result carries its risk flag, the upstream commit and the upstream disclaimer.',
|
|
214
|
+
parameters: {
|
|
215
|
+
type: 'object',
|
|
216
|
+
properties: {
|
|
217
|
+
query: { type: 'string', description: 'Topic, method or symbol to search for, for example "sessions", "PeriodicTask" or "alarm".' },
|
|
218
|
+
slug: { type: 'string', description: 'Exact page slug from the contents, for example "netlogic-lifecycle".' },
|
|
219
|
+
limit: { type: 'integer', description: 'Most search hits to return; defaults to 8, maximum 20.', minimum: 1, maximum: 20 },
|
|
220
|
+
},
|
|
221
|
+
additionalProperties: false,
|
|
222
|
+
required: [],
|
|
223
|
+
},
|
|
224
|
+
output: {
|
|
225
|
+
schema: { type: 'object', additionalProperties: true },
|
|
226
|
+
render(_args, value) {
|
|
227
|
+
return [{ type: 'text', text: JSON.stringify(value, undefined, 2) }]
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
async execute(args) {
|
|
231
|
+
try {
|
|
232
|
+
return answer(args ?? {})
|
|
233
|
+
} catch (error) {
|
|
234
|
+
return failure(error)
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Register the retrieval tool and, when the host offers it, the Optix skill.
|
|
241
|
+
*
|
|
242
|
+
* @param ctx - Plugin context with the tool registry and an optional skills service.
|
|
243
|
+
*/
|
|
244
|
+
export function apply(ctx) {
|
|
245
|
+
ctx.tools.register(definition)
|
|
246
|
+
const skillPath = first(SKILL_PATHS)
|
|
247
|
+
const skills = typeof ctx.get === 'function' ? ctx.get('skills') : undefined
|
|
248
|
+
if (skillPath === undefined || skills === undefined || typeof skills.register !== 'function') {
|
|
249
|
+
try {
|
|
250
|
+
ctx.logger?.warn?.('optix-netlogic: the skill was not registered; the tool is available.')
|
|
251
|
+
} catch {
|
|
252
|
+
// A logger that refuses must not turn a missing skill into a crash.
|
|
253
|
+
}
|
|
254
|
+
return
|
|
255
|
+
}
|
|
256
|
+
const raw = readFileSync(skillPath, 'utf8')
|
|
257
|
+
const content = raw.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/u, '')
|
|
258
|
+
skills.register({
|
|
259
|
+
...SKILL,
|
|
260
|
+
content,
|
|
261
|
+
path: skillPath,
|
|
262
|
+
source: 'custom',
|
|
263
|
+
provider: '@aryacode/dsh-engineering',
|
|
264
|
+
invocation: { modelInvocable: true, userInvocable: true },
|
|
265
|
+
})
|
|
266
|
+
}
|
package/entries/optix.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import z from '@deepseek-ai/schemastery'
|
|
12
12
|
import { dirname, join } from 'node:path'
|
|
13
13
|
import * as optix from '../vendor/optix/lib/index.js'
|
|
14
|
+
import * as netlogic from './netlogic.js'
|
|
14
15
|
import { flag, list, text } from './config.js'
|
|
15
16
|
|
|
16
17
|
export const name = 'engineering-optix'
|
|
@@ -53,6 +54,9 @@ export const Config = z.object({
|
|
|
53
54
|
ocrTessdataDirectory: z.string().default('').volatile().description(
|
|
54
55
|
'Folder holding the .traineddata files. Empty derives tessdata beside tesseract.exe.',
|
|
55
56
|
),
|
|
57
|
+
netlogic: z.boolean().default(true).volatile().description(
|
|
58
|
+
'Register the pinned NetLogic cheat-sheet reader (optix_netlogic) and the FactoryTalk Optix skill. Off removes one tool and its guidance.',
|
|
59
|
+
),
|
|
56
60
|
requestTimeoutMs: z.natural().min(1).max(2147483647).default(20000),
|
|
57
61
|
maxResponseBytes: z.natural().min(1).default(2000000),
|
|
58
62
|
maxBatchOps: z.natural().min(1).default(40),
|
|
@@ -93,12 +97,21 @@ export function apply(ctx, config = {}) {
|
|
|
93
97
|
}
|
|
94
98
|
}
|
|
95
99
|
let mounted
|
|
100
|
+
let netlogicMounted
|
|
96
101
|
// Volatile-only config changes are committed into the live references and
|
|
97
102
|
// emit `loader/volatile-update` instead of re-running apply(). The prebuilt
|
|
98
103
|
// plugin reads its options once, so remount it to pick up a switch change.
|
|
99
104
|
const sync = () => {
|
|
100
105
|
if (mounted !== undefined) void mounted.dispose()
|
|
101
106
|
mounted = ctx.plugin(optix, options())
|
|
107
|
+
// The NetLogic reader is this repository's own entry: it is a separate plugin
|
|
108
|
+
// so the row's `netlogic` switch can drop one tool and its guidance without
|
|
109
|
+
// touching the prebuilt engine.
|
|
110
|
+
if (netlogicMounted !== undefined) {
|
|
111
|
+
void netlogicMounted.dispose()
|
|
112
|
+
netlogicMounted = undefined
|
|
113
|
+
}
|
|
114
|
+
if (flag(config.netlogic)) netlogicMounted = ctx.plugin(netlogic)
|
|
102
115
|
}
|
|
103
116
|
sync()
|
|
104
117
|
ctx.on('loader/volatile-update', sync)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aryacode/dsh-engineering",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "AryaCode engineering bundle: one install surface for the Studio 5000 L5X, FactoryTalk View ME, MotionWorks IEC and FactoryTalk Optix surfaces, one row per product, with the ladder and display previews.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|
|
@@ -55,14 +55,6 @@
|
|
|
55
55
|
]
|
|
56
56
|
}
|
|
57
57
|
},
|
|
58
|
-
"scripts": {
|
|
59
|
-
"test": "node --import tsx --test test/patch.spec.mjs test/docs.spec.mjs test/tool-reference.spec.mjs test/manifests.spec.mjs test/client-face.spec.mjs test/config.spec.mjs",
|
|
60
|
-
"docs:tools": "tsx scripts/gen-tool-reference.ts",
|
|
61
|
-
"docs:check": "tsx scripts/gen-tool-reference.ts --check",
|
|
62
|
-
"build:client": "node scripts/build-client.mjs",
|
|
63
|
-
"assemble": "node scripts/assemble.mjs",
|
|
64
|
-
"prepack": "node scripts/build-client.mjs && node scripts/assemble.mjs"
|
|
65
|
-
},
|
|
66
58
|
"dependencies": {
|
|
67
59
|
"fast-xml-parser": "5.11.1",
|
|
68
60
|
"fast-xml-validator": "1.4.2",
|
|
@@ -102,5 +94,13 @@
|
|
|
102
94
|
"./mwi/skill": "./vendor/mwi/lib/skill.js",
|
|
103
95
|
"./mwi/verify": "./vendor/mwi/lib/verify.js",
|
|
104
96
|
"./mwi/edit": "./vendor/mwi/lib/edit.js"
|
|
97
|
+
},
|
|
98
|
+
"scripts": {
|
|
99
|
+
"test": "node --import tsx --test test/patch.spec.mjs test/docs.spec.mjs test/tool-reference.spec.mjs test/manifests.spec.mjs test/client-face.spec.mjs test/config.spec.mjs test/netlogic.spec.mjs",
|
|
100
|
+
"docs:tools": "tsx scripts/gen-tool-reference.ts",
|
|
101
|
+
"docs:check": "tsx scripts/gen-tool-reference.ts --check",
|
|
102
|
+
"netlogic:sync": "node scripts/sync-netlogic.mjs",
|
|
103
|
+
"build:client": "node scripts/build-client.mjs",
|
|
104
|
+
"assemble": "node scripts/assemble.mjs"
|
|
105
105
|
}
|
|
106
|
-
}
|
|
106
|
+
}
|
package/vendor/l5x/SKILL.md
CHANGED
|
@@ -56,12 +56,12 @@ this is a backup on request ("back up this project"). Build it as a copy plus
|
|
|
56
56
|
|
|
57
57
|
## Live reads
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
`l5x-live` reads declared scalar tags over EtherNet/IP with no Studio, FactoryTalk Linx or SDK. If `poll_live_tags` is unavailable, say live reads are not enabled here.
|
|
60
60
|
|
|
61
|
-
- `poll_live_tags` takes an explicit IPv4 address, a CPU slot and 1 to 16 declared
|
|
61
|
+
- `poll_live_tags` takes an explicit IPv4 address, a CPU slot and 1 to 16 declared scalars, and returns timestamped sweeps, quality and controller identity.
|
|
62
62
|
- `start_plc_trend` samples the same way as a session-owned job the chart can follow; stop it through the job controls.
|
|
63
63
|
- These are read-only. Never write controller data, force tags, toggle bits or edit online.
|
|
64
|
-
- Report `live_logic_matches_export: "unverified"`:
|
|
64
|
+
- Report `live_logic_matches_export: "unverified"`: a read proves a value at a moment, not that the controller runs the export.
|
|
65
65
|
|
|
66
66
|
## Reporting
|
|
67
67
|
|
|
@@ -81,18 +81,38 @@ The `l5x-live` entry reads declared scalar tags straight from a controller over
|
|
|
81
81
|
The `l5x-studio` entry adds offline SDK work; it may be disabled. Call `studio_sdk_status` before using it.
|
|
82
82
|
|
|
83
83
|
- `export_acd_to_l5x` writes one new full L5X from an ACD. No controller contact.
|
|
84
|
-
- `verify_offline_l5x` imports a full L5X into a new offline ACD and builds it. Build needs project revision 37
|
|
85
|
-
- `build_acd_from_draft` imports one changed RLL or ST routine into a new ACD copy, builds
|
|
84
|
+
- `verify_offline_l5x` imports a full L5X into a new offline ACD and builds it. Build needs project revision 37+; `probe: true` tries a lower one and reports the SDK error. An L5X from `export_acd_to_l5x` may be refused as "no changes".
|
|
85
|
+
- `build_acd_from_draft` imports one changed RLL or ST routine into a new ACD copy, builds and exports it. The baseline must be an L5X exported from that ACD in this chat; neither file may change.
|
|
86
86
|
- `studio_review_evidence` checks program, routine and rung or line names and verbatim quotes before you report findings.
|
|
87
|
-
- `controller_approval_status`
|
|
87
|
+
- `controller_approval_status` says whether this session can ask a human. `capture_controller_snapshot`, `change_controller_mode` and `download_project` need per-call approval of the exact target and proceed only on `allowed-once`; `download_project` takes a `verification_id` from a passed build in this chat.
|
|
88
88
|
- `go_online`, `read_controller_mode` and `read_live_tag` are read-only and need no approval. Never write controller data, force tags, toggle bits or edit online.
|
|
89
89
|
|
|
90
90
|
## Editing
|
|
91
91
|
|
|
92
92
|
Editing uses the `draft_*` family. It may be disabled in this profile; if a draft tool is unavailable, say editing is not enabled here rather than trying another tool.
|
|
93
93
|
|
|
94
|
-
- One operation per call: `draft_rung`, `draft_st`, `draft_routine`, `draft_tag`, `draft_tag_description`, `draft_udt_member`, `draft_udt_description`.
|
|
94
|
+
- One operation per call: `draft_rung`, `draft_rung_comment`, `draft_st`, `draft_routine`, `draft_tag`, `draft_tag_description`, `draft_udt_member`, `draft_udt_description`.
|
|
95
95
|
- `draft_validate` prepares the accumulated draft against the project and returns its hash, diff summary and check findings. Read the findings before applying.
|
|
96
96
|
- `draft_apply` writes the draft to ONE new `.L5X` path. It never overwrites an existing file.
|
|
97
97
|
- After applying, reopen the written file with `open_project` to review it.
|
|
98
98
|
- Drafts belong to this chat. Another chat starts with none, and an applied draft is cleared.
|
|
99
|
+
|
|
100
|
+
## Generate
|
|
101
|
+
|
|
102
|
+
Write ladder through the composer, not by inventing NeutralText.
|
|
103
|
+
|
|
104
|
+
- `list_instructions` gives the mnemonic, category and operand slots with their kinds; read it before composing.
|
|
105
|
+
- `compose_rung` builds one rung from a tree (`spec_json`) or a pattern (`start_stop_latch`, `fault_latch`, `ton_delay`, `interlock`), resolves every operand, type-checks each slot and parses its own output; anything unknown or mistyped is refused before staging.
|
|
106
|
+
- Pass `program`, `routine`, `mode` and `rung` to stage it (plus `comment`); without them it returns text only.
|
|
107
|
+
- `fault_latch` stages two rungs at `rung` and `rung + 1`; rung numbers are not renumbered, so pick free ones.
|
|
108
|
+
|
|
109
|
+
## Documentation
|
|
110
|
+
|
|
111
|
+
`document_project` writes a handover bundle into a NEW folder and never touches the export:
|
|
112
|
+
|
|
113
|
+
- `PROJECT.md` — controller, revision, tasks, programs, counts;
|
|
114
|
+
- `io.md` / `io.csv` — modules and module-data tags;
|
|
115
|
+
- `tags.csv` and `tags-studio.csv` — every declared tag, and the `Tag Name,Description` shape Studio imports;
|
|
116
|
+
- `rungs/<program>.md` — each rung's number, comment, NeutralText, instructions and resolved tags.
|
|
117
|
+
|
|
118
|
+
The bundle is structure and citations, not prose: write the narrative yourself and use `draft_rung_comment` / `draft_tag_description` when it belongs in the project. No C&E, FAT/SAT or runtime proof.
|
|
@@ -5,6 +5,7 @@ type Operation = {
|
|
|
5
5
|
readonly routine: string;
|
|
6
6
|
readonly rung: number;
|
|
7
7
|
readonly text: string;
|
|
8
|
+
readonly comment?: string;
|
|
8
9
|
} | {
|
|
9
10
|
readonly operation: 'delete_rung';
|
|
10
11
|
readonly program: string;
|
|
@@ -17,6 +18,13 @@ type Operation = {
|
|
|
17
18
|
readonly rung: number;
|
|
18
19
|
readonly text: string;
|
|
19
20
|
readonly before_rung?: number;
|
|
21
|
+
readonly comment?: string;
|
|
22
|
+
} | {
|
|
23
|
+
readonly operation: 'set_rung_comment';
|
|
24
|
+
readonly program: string;
|
|
25
|
+
readonly routine: string;
|
|
26
|
+
readonly rung: number;
|
|
27
|
+
readonly comment: string;
|
|
20
28
|
} | {
|
|
21
29
|
readonly operation: 'replace_st';
|
|
22
30
|
readonly program: string;
|
|
@@ -59,7 +67,7 @@ type Operation = {
|
|
|
59
67
|
readonly description: string;
|
|
60
68
|
};
|
|
61
69
|
/** Compact contract returned with validation errors and embedded in tool descriptions. */
|
|
62
|
-
export declare const CHANGE_SET_HELP = "Operations (each also needs \"operation\"):\nreplace_rung {program,routine,rung,text}\ninsert_rung {program,routine,rung,text,before_rung?}\ndelete_rung {program,routine,rung}\nreplace_st {program,routine,code}\nadd_routine {program,name,language:\"RLL\"|\"ST\",code}\nadd_tag {scope,name,data_type,description,value? scalar, dimensions? \"4 2\", external_access? \"Read/Write\"|\"Read Only\"|\"None\"}\nset_tag_description {scope,name,description}\nadd_udt_member {udt,name,data_type,description}\nset_udt_description {udt,description}\nset_udt_member_description {udt,member,description}\nIdentifiers are at most 40 characters. Example: {\"version\":1,\"operations\":[{\"operation\":\"add_tag\",\"scope\":\"controller\",\"name\":\"SP_Start\",\"data_type\":\"REAL\",\"value\":80,\"description\":\"Start threshold\"}]}";
|
|
70
|
+
export declare const CHANGE_SET_HELP = "Operations (each also needs \"operation\"):\nreplace_rung {program,routine,rung,text,comment?}\ninsert_rung {program,routine,rung,text,before_rung?,comment?}\ndelete_rung {program,routine,rung}\nset_rung_comment {program,routine,rung,comment}\nreplace_st {program,routine,code}\nadd_routine {program,name,language:\"RLL\"|\"ST\",code}\nadd_tag {scope,name,data_type,description,value? scalar, dimensions? \"4 2\", external_access? \"Read/Write\"|\"Read Only\"|\"None\"}\nset_tag_description {scope,name,description}\nadd_udt_member {udt,name,data_type,description}\nset_udt_description {udt,description}\nset_udt_member_description {udt,member,description}\nIdentifiers are at most 40 characters. Example: {\"version\":1,\"operations\":[{\"operation\":\"add_tag\",\"scope\":\"controller\",\"name\":\"SP_Start\",\"data_type\":\"REAL\",\"value\":80,\"description\":\"Start threshold\"}]}";
|
|
63
71
|
/** Limits are resolved by the plugin's configuration before tools call this module. */
|
|
64
72
|
export interface ChangeSetLimits {
|
|
65
73
|
readonly operations: number;
|