@aryacode/dsh-engineering 0.9.1 → 0.9.2

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 CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.9.2] - 2026-10-27
4
+
5
+ ### Fixed
6
+
7
+ - **The `engineering-standards` skill was missing from the catalog.** 0.9.1 mounted it by calling the skills
8
+ service from inside the resources row's `apply()`, which only works if that service already exists at that
9
+ row's turn — it mounts before the Optix row, so the skill was silently skipped (the tools still loaded).
10
+ The skill is now its own plugin with `inject: ['skills']`, the same pattern the product skills use, so the
11
+ host activates it when the service is up regardless of row order. A test asserts the injection list and
12
+ registration.
3
13
  ## [0.9.1] - 2026-10-27
4
14
 
5
15
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aryacode/dsh-engineering",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
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",
@@ -5,24 +5,18 @@
5
5
  *
6
6
  * The corpus is generated data (`index.json`, `SOURCE.json`) over authored
7
7
  * Markdown; the profile is produced by whichever product package has a project
8
- * open. This entry only wires them to the tool registry and, when the host offers
9
- * it, to the skills service.
8
+ * open. This entry wires them to the tool registry and mounts the skill as a
9
+ * sub-plugin that waits for the skills service, so row order cannot drop it.
10
10
  *
11
11
  * @module dsh-engineering-resources
12
12
  */
13
13
  import type { StyleProfile } from './profile.ts';
14
14
  /** Plugin name in a composition. */
15
15
  export declare const name = "engineering-resources";
16
- /** The tools need the registry; the skill is registered when the host offers it. */
16
+ /** The tools need the registry; the skill is a sub-plugin with its own injection. */
17
17
  export declare const inject: string[];
18
18
  /** The service name a generation tool can inject to read a profile by handle. */
19
19
  export declare const PROFILES_SERVICE = "engineeringProfiles";
20
- /** The skill metadata, kept here so a test can assert it without the file. */
21
- export declare const SKILL: {
22
- name: string;
23
- description: string;
24
- whenToUse: string;
25
- };
26
20
  /** Configuration for the row. */
27
21
  export interface Config {
28
22
  /** Whether the reference and profile tools are registered. Defaults to true. */
@@ -33,23 +27,27 @@ export interface ProfilesService {
33
27
  /** The profile for one handle, when it is still in memory. */
34
28
  get(id: string): StyleProfile | undefined;
35
29
  }
36
- /**
37
- * Register the resource tools and the standards skill.
38
- *
39
- * @param ctx - Plugin context with the tool registry and optional services.
40
- * @param config - Row configuration.
41
- */
42
- export declare function apply(ctx: {
30
+ /** The plugin context shape this row needs. */
31
+ interface RowContext {
43
32
  tools: {
44
33
  register(definition: unknown): void;
45
34
  };
46
35
  get?: (name: string) => unknown;
47
36
  provide?: (name: string, value: unknown) => void;
37
+ plugin?: (plugin: unknown) => void;
48
38
  logger?: {
49
39
  warn?(message: string): void;
50
40
  };
51
- }, config?: Config): void;
41
+ }
42
+ /**
43
+ * Register the resource tools and mount the standards skill.
44
+ *
45
+ * @param ctx - Plugin context with the tool registry and optional services.
46
+ * @param config - Row configuration.
47
+ */
48
+ export declare function apply(ctx: RowContext, config?: Config): void;
52
49
  export { getProfile } from './store.ts';
53
50
  export { validateProfile, profileId } from './profile.ts';
54
51
  export type { StyleProfile } from './profile.ts';
55
52
  export { resourceTools, referenceTool, profileTool } from './tools.ts';
53
+ export { SKILL } from './skill.ts';
@@ -5,39 +5,22 @@
5
5
  *
6
6
  * The corpus is generated data (`index.json`, `SOURCE.json`) over authored
7
7
  * Markdown; the profile is produced by whichever product package has a project
8
- * open. This entry only wires them to the tool registry and, when the host offers
9
- * it, to the skills service.
8
+ * open. This entry wires them to the tool registry and mounts the skill as a
9
+ * sub-plugin that waits for the skills service, so row order cannot drop it.
10
10
  *
11
11
  * @module dsh-engineering-resources
12
12
  */
13
- import { existsSync, readFileSync } from 'node:fs';
14
- import { dirname, join } from 'node:path';
15
- import { fileURLToPath } from 'node:url';
16
13
  import { resourceTools } from "./tools.js";
17
14
  import { getProfile } from "./store.js";
15
+ import * as skill from "./skill.js";
18
16
  /** Plugin name in a composition. */
19
17
  export const name = 'engineering-resources';
20
- /** The tools need the registry; the skill is registered when the host offers it. */
18
+ /** The tools need the registry; the skill is a sub-plugin with its own injection. */
21
19
  export const inject = ['tools'];
22
20
  /** The service name a generation tool can inject to read a profile by handle. */
23
21
  export const PROFILES_SERVICE = 'engineeringProfiles';
24
- /** The skill metadata, kept here so a test can assert it without the file. */
25
- export const SKILL = {
26
- name: 'engineering-standards',
27
- description: 'Engineering standards and house style for control code and HMI: read the local corpus for IEC 61131-3, Rockwell, Yaskawa, FactoryTalk and craft rules, profile the open project to follow its own conventions, and cite rule IDs in findings.',
28
- whenToUse: 'Before writing or reviewing PLC code, ladder, Structured Text, FactoryTalk View or Optix screens — especially when matching an existing project’s style, or when starting new work that should meet a professional standard.',
29
- };
30
- const HERE = dirname(fileURLToPath(import.meta.url));
31
- /** The skill file, in the built package or the source tree. */
32
- function skillPath() {
33
- for (const candidate of [join(HERE, '..', 'SKILL.md'), join(HERE, '..', '..', 'SKILL.md')]) {
34
- if (existsSync(candidate))
35
- return candidate;
36
- }
37
- return undefined;
38
- }
39
22
  /**
40
- * Register the resource tools and the standards skill.
23
+ * Register the resource tools and mount the standards skill.
41
24
  *
42
25
  * @param ctx - Plugin context with the tool registry and optional services.
43
26
  * @param config - Row configuration.
@@ -53,26 +36,16 @@ export function apply(ctx, config = {}) {
53
36
  ctx.tools.register(definition);
54
37
  }
55
38
  ctx.provide?.(PROFILES_SERVICE, { get: getProfile });
56
- const path = skillPath();
57
- const skills = getService('skills');
58
- if (path === undefined || skills?.register === undefined) {
59
- try {
60
- ctx.logger?.warn?.('engineering-resources: the engineering-standards skill was not registered; the tools are available.');
61
- }
62
- catch {
63
- // A logger that refuses must not turn a missing skill into a crash.
64
- }
65
- return;
39
+ // Mount the skill as its own plugin: it declares `inject: ['skills']`, so the
40
+ // host activates it when that service exists rather than at this row's turn.
41
+ if (typeof ctx.plugin === 'function') {
42
+ ctx.plugin(skill);
43
+ }
44
+ else {
45
+ const skills = getService('skills');
46
+ if (skills !== undefined)
47
+ skill.registerSkill(skills);
66
48
  }
67
- const content = readFileSync(path, 'utf8').replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/u, '');
68
- skills.register({
69
- ...SKILL,
70
- content,
71
- path,
72
- source: 'custom',
73
- provider: '@aryacode/dsh-engineering',
74
- invocation: { modelInvocable: true, userInvocable: true },
75
- });
76
49
  }
77
50
  catch (error) {
78
51
  try {
@@ -86,3 +59,4 @@ export function apply(ctx, config = {}) {
86
59
  export { getProfile } from "./store.js";
87
60
  export { validateProfile, profileId } from "./profile.js";
88
61
  export { resourceTools, referenceTool, profileTool } from "./tools.js";
62
+ export { SKILL } from "./skill.js";
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The engineering-standards skill, mounted as its own plugin.
3
+ *
4
+ * The first version registered the skill inside the resources row's `apply`,
5
+ * which meant it ran only if the skills service already existed at that moment.
6
+ * The resources row mounts before the Optix row, and the skill silently
7
+ * disappeared from the catalog when the service was not up yet. Declaring
8
+ * `inject: ['skills']` lets the host activate this plugin when the service
9
+ * exists, exactly like the product skill plugins.
10
+ *
11
+ * @module dsh-engineering-resources/skill
12
+ */
13
+ /** Plugin name in a composition. */
14
+ export declare const name = "engineering-resources-skill";
15
+ /** The host must offer the skills service before this plugin activates. */
16
+ export declare const inject: string[];
17
+ /** The skill metadata. */
18
+ export declare const SKILL: {
19
+ name: string;
20
+ description: string;
21
+ whenToUse: string;
22
+ };
23
+ /** The skill file, in the built package or the source tree. */
24
+ export declare function skillPath(): string | undefined;
25
+ /** The skill body, with any front matter removed. */
26
+ export declare function skillContent(path: string): string;
27
+ /**
28
+ * Register the skill in one skills service.
29
+ *
30
+ * @param service - The host's skills service.
31
+ * @returns True when it was registered; false when the file is missing.
32
+ */
33
+ export declare function registerSkill(service: {
34
+ register?(definition: unknown): void;
35
+ }): boolean;
36
+ /**
37
+ * Activate the skill plugin.
38
+ *
39
+ * @param ctx - Plugin context, with the skills service present by injection.
40
+ */
41
+ export declare function apply(ctx: {
42
+ get?: (name: string) => unknown;
43
+ logger?: {
44
+ warn?(message: string): void;
45
+ };
46
+ }): void;
@@ -0,0 +1,74 @@
1
+ /**
2
+ * The engineering-standards skill, mounted as its own plugin.
3
+ *
4
+ * The first version registered the skill inside the resources row's `apply`,
5
+ * which meant it ran only if the skills service already existed at that moment.
6
+ * The resources row mounts before the Optix row, and the skill silently
7
+ * disappeared from the catalog when the service was not up yet. Declaring
8
+ * `inject: ['skills']` lets the host activate this plugin when the service
9
+ * exists, exactly like the product skill plugins.
10
+ *
11
+ * @module dsh-engineering-resources/skill
12
+ */
13
+ import { existsSync, readFileSync } from 'node:fs';
14
+ import { dirname, join } from 'node:path';
15
+ import { fileURLToPath } from 'node:url';
16
+ /** Plugin name in a composition. */
17
+ export const name = 'engineering-resources-skill';
18
+ /** The host must offer the skills service before this plugin activates. */
19
+ export const inject = ['skills'];
20
+ /** The skill metadata. */
21
+ export const SKILL = {
22
+ name: 'engineering-standards',
23
+ description: 'Engineering standards and house style for control code and HMI: read the local corpus for IEC 61131-3, Rockwell, Yaskawa, FactoryTalk and craft rules, profile the open project to follow its own conventions, and cite rule IDs in findings.',
24
+ whenToUse: 'Before writing or reviewing PLC code, ladder, Structured Text, FactoryTalk View or Optix screens — especially when matching an existing project’s style, or when starting new work that should meet a professional standard.',
25
+ };
26
+ const HERE = dirname(fileURLToPath(import.meta.url));
27
+ /** The skill file, in the built package or the source tree. */
28
+ export function skillPath() {
29
+ for (const candidate of [join(HERE, '..', 'SKILL.md'), join(HERE, '..', '..', 'SKILL.md')]) {
30
+ if (existsSync(candidate))
31
+ return candidate;
32
+ }
33
+ return undefined;
34
+ }
35
+ /** The skill body, with any front matter removed. */
36
+ export function skillContent(path) {
37
+ return readFileSync(path, 'utf8').replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/u, '');
38
+ }
39
+ /**
40
+ * Register the skill in one skills service.
41
+ *
42
+ * @param service - The host's skills service.
43
+ * @returns True when it was registered; false when the file is missing.
44
+ */
45
+ export function registerSkill(service) {
46
+ const path = skillPath();
47
+ if (path === undefined || service.register === undefined)
48
+ return false;
49
+ service.register({
50
+ ...SKILL,
51
+ content: skillContent(path),
52
+ path,
53
+ source: 'custom',
54
+ provider: '@aryacode/dsh-engineering',
55
+ invocation: { modelInvocable: true, userInvocable: true },
56
+ });
57
+ return true;
58
+ }
59
+ /**
60
+ * Activate the skill plugin.
61
+ *
62
+ * @param ctx - Plugin context, with the skills service present by injection.
63
+ */
64
+ export function apply(ctx) {
65
+ const skills = typeof ctx.get === 'function' ? ctx.get('skills') : undefined;
66
+ if (skills === undefined || !registerSkill(skills)) {
67
+ try {
68
+ ctx.logger?.warn?.('engineering-resources: the engineering-standards skill could not be registered; the tools are available.');
69
+ }
70
+ catch {
71
+ // Nothing left to report to.
72
+ }
73
+ }
74
+ }