@sassoftware/sas-score-mcp-serverjs 1.0.1-2 → 1.0.1-21

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.
@@ -4,7 +4,7 @@ import path from 'path';
4
4
  import os from 'os';
5
5
  import { fileURLToPath } from 'url';
6
6
 
7
- function setupSkills(clientName) {
7
+ function setupSkills(clientName,agentFolder) {
8
8
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
9
  // Paths
10
10
  let destination;
@@ -13,10 +13,13 @@ function setupSkills(clientName) {
13
13
  destination = path.join(process.cwd(), clientName);
14
14
  clientName = clientName.slice(1);
15
15
  } else {
16
- destination = path.join(os.homedir(), '.' + clientName);
16
+ destination = path.join(os.homedir(), '.' + clientName );
17
17
  }
18
18
 
19
- const source = path.join(__dirname, `../.skills` + '_' + clientName.toLowerCase());
19
+ if (agentFolder) {
20
+ destination = path.join(destination, agentFolder);
21
+ }
22
+ const source = path.join(__dirname, `../.skills`);
20
23
  console.error("==================================================================");
21
24
  console.error(` Copying ${source} to ${destination}...`);
22
25
 
@@ -25,22 +28,24 @@ function setupSkills(clientName) {
25
28
  if (!fs.existsSync(to)) fs.mkdirSync(to, { recursive: true });;
26
29
  fs.readdirSync(from).forEach(element => {
27
30
  const fromPath = path.join(from, element);
28
- const toPath = path.join(to, element);
31
+ let toPath = path.join(to, element);
32
+ if (clientName === 'claude' && element === 'copilot-instructions.md') {
33
+ toPath = path.join(to, 'CLAUDE.md');
34
+ }
29
35
  if (fs.lstatSync(fromPath).isFile()) {
30
36
  console.error(` 📄 Copying file: ${element}`);
31
37
  fs.copyFileSync(fromPath, toPath);
32
38
  } else if (fs.lstatSync(fromPath).isDirectory()) {
33
39
  console.error(`📂 Copying folder: ${element}`);
34
- copyFolderSync(fromPath, toPath) ;
40
+ copyFolderSync(fromPath, toPath);
35
41
  }
36
42
  });
37
43
  }
38
-
39
44
  function listExpandedFolder(dir, indent = "") {
40
45
  const entries = fs.readdirSync(dir, { withFileTypes: true });
41
46
 
42
47
  for (const entry of entries) {
43
- console.log(indent + entry.name);
48
+ console.error(indent + entry.name);
44
49
 
45
50
  if (entry.isDirectory()) {
46
51
  listExpandedFolder(path.join(dir, entry.name), indent + " ");
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import os from 'os';
5
+ import { fileURLToPath } from 'url';
6
+
7
+ function setupSkills(clientName,agentFolder) {
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
+ // Paths
10
+ let destination;
11
+ if (clientName.startsWith('.')) {
12
+ console.error('-----------------------',process.cwd());
13
+ destination = path.join(process.cwd(), clientName);
14
+ clientName = clientName.slice(1);
15
+ } else {
16
+ destination = path.join(os.homedir(), '.' + clientName );
17
+ }
18
+
19
+ if (agentFolder) {
20
+ destination = path.join(destination, agentFolder);
21
+ }
22
+ const source = path.join(__dirname, `../.skills` + '_' + clientName.toLowerCase());
23
+ console.error("==================================================================");
24
+ console.error(` Copying ${source} to ${destination}...`);
25
+
26
+
27
+
28
+ // Copy agents folder if it exists
29
+ let agentsFromPath = path.join(__dirname, `../.agents`);
30
+ let agentsToPath = path.join(destination, 'agents');
31
+ copyFolderSync(agentsFromPath, agentsToPath);
32
+
33
+ // now copy the skills folder to the destination
34
+ let toPath = path.join(destination, 'skills');
35
+ let fromPath = path.join(__dirname, `../.skills`);
36
+ copyFolderSync(fromPath, toPath);
37
+
38
+ // Now copy instructions
39
+ let instructionsFromPath = path.join(__dirname, `../.instructions`);
40
+ let instructionsToPath = destination;
41
+ copyFolderSync(instructionsFromPath, instructionsToPath);
42
+
43
+ function copyFolderSync(from, to) {
44
+ if (!fs.existsSync(from)) return [];
45
+ if (!fs.existsSync(to)) fs.mkdirSync(to, { recursive: true });;
46
+ fs.readdirSync(from).forEach(element => {
47
+ const fromPath = path.join(from, element);
48
+ let toPath = path.join(to, element);
49
+ if (clientName === 'claude' && element === 'copilot-instructions.md') {
50
+ toPath = path.join(to, 'CLAUDE.md');
51
+ }
52
+ if (fs.lstatSync(fromPath).isFile()) {
53
+ console.error(` 📄 Copying file: ${element}`);
54
+ fs.copyFileSync(fromPath, toPath);
55
+ } else if (fs.lstatSync(fromPath).isDirectory()) {
56
+ console.error(`📂 Copying folder: ${element}`);
57
+ copyFolderSync(fromPath, toPath);
58
+ }
59
+ });
60
+ }
61
+ function listExpandedFolder(dir, indent = "") {
62
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
63
+
64
+ for (const entry of entries) {
65
+ console.error(indent + entry.name);
66
+
67
+ if (entry.isDirectory()) {
68
+ listExpandedFolder(path.join(dir, entry.name), indent + " ");
69
+ }
70
+ }
71
+ }
72
+ try {
73
+ copyFolderSync(source, destination);
74
+ //listExpandedFolder(destination);
75
+ } catch (err) {
76
+ console.error('❌ Error copying files:', err.message);
77
+ }
78
+ }
79
+ export default setupSkills;
@@ -54,7 +54,7 @@ async function _listLibrary(params) {
54
54
  if (appControl != null) {
55
55
  await deleteSession(appControl);
56
56
  }
57
- return { isError: true, content: [{ type: 'text', text: JSON.stringify(err) }] };
57
+ return { isError: true, content: [{ type: 'text', text: (typeof err === 'string') ? err : JSON.stringify(err) }] };
58
58
  }
59
59
  }
60
60
 
@@ -44,7 +44,7 @@ async function _listTables(params) {
44
44
  structuredContent: response};
45
45
  } catch (err) {
46
46
  console.error(JSON.stringify(err));
47
- return {isError: true, content: [{ type: 'text', text: JSON.stringify(err) }] }
47
+ return {isError: true, content: [{ type: 'text', text: (typeof err === 'string') ? err : JSON.stringify(err) }] }
48
48
  }
49
49
 
50
50
  };
@@ -18,7 +18,7 @@ function findJobdef(_appContext) {
18
18
  find-jobdef — locate a specific SAS Viya job definition.
19
19
 
20
20
  USE when: find jobdef, does jobdef exist, is there a jobdef named, lookup jobdef, verify jobdef exists
21
- DO NOT USE for: list jobdefs (use list-jobdefs), run jobdef (use run-jobdef), find job/lib/table/model (use respective tools)
21
+ DO NOT USE for: list jobdefs (use sas-score-list-jobdefs), run jobdef (use sas-score-run-jobdef), find job/lib/table/model (use respective tools)
22
22
 
23
23
  PARAMETERS
24
24
  - name: string (required) — jobdef name to locate; if multiple supplied, use first
@@ -38,10 +38,10 @@ EXAMPLES
38
38
  - "is there a jobdef named metricsRefresh" → { name: "metricsRefresh" }
39
39
 
40
40
  NEGATIVE EXAMPLES (do not route here)
41
- - "list jobdefs" (use list-jobdefs)
42
- - "run jobdef cars_job_v4" (use run-jobdef)
43
- - "find job ETL" (use find-job)
44
- - "find table cars" (use find-table)
41
+ - "list jobdefs" (use sas-score-list-jobdefs)
42
+ - "run jobdef cars_job_v4" (use sas-score-run-jobdef)
43
+ - "find job ETL" (use sas-score-find-job)
44
+ - "find table cars" (use sas-score-find-table)
45
45
 
46
46
  ERRORS
47
47
  Returns { jobdefs: [] } if not found; { jobdefs: [name, ...] } if found. Never hallucinate jobdef names.
@@ -17,16 +17,16 @@ DO NOT USE for: find model, model metadata, list models, run programs/jobs, quer
17
17
 
18
18
  PARAMETERS
19
19
  - model: string — model name (required, exact match)
20
- - scenario: string | object | array — input data (required). Accepts: "x=1, y=2", {x:1, y:2}, or array of objects
21
- - uflag: boolean (default: false) — prefix model fields with underscore when true
20
+ - scenario: object — input data as JSON (optional, defaults to {}). Example: {age:45, income:60000}
21
+
22
22
 
23
23
  ROUTING RULES
24
- - "score with model X using a=1, b=2" → { model: "X", scenario: {a:1, b:2}, uflag: false }
25
- - "predict using model Y with age=45, income=60000" → { model: "Y", scenario: {age:45, income:60000}, uflag: false }
24
+ - "score with model X using a=1, b=2" → { model: "X", scenario: {a:1, b:2} }
25
+ - "predict using model Y with age=45, income=60000" → { model: "Y", scenario: {age:45, income:60000} }
26
26
 
27
27
  EXAMPLES
28
- - "score with model churn using age=45, income=60000" → { model: "churn", scenario: {age:45, income:60000}, uflag: false }
29
- - "predict creditScore for credit=700, debt=20000" → { model: "creditScore", scenario: {credit:700, debt:20000}, uflag: false }
28
+ - "score with model churn using age=45, income=60000" → { model: "churn", scenario: {age:45, income:60000} }
29
+ - "predict creditScore for credit=700, debt=20000" → { model: "creditScore", scenario: {credit:700, debt:20000} }
30
30
 
31
31
  NEGATIVE EXAMPLES (do not route here)
32
32
  - "find model X" (use find-model)
@@ -42,40 +42,38 @@ Returns predictions, probabilities, scores merged with input data. Returns error
42
42
  let spec = {
43
43
  name: 'mas-score',
44
44
  description: description,
45
- inputSchema:z.object({
45
+ inputSchema: z.object({
46
46
  model: z.string(),
47
- scenario: z.string(),
48
- uflag: z.boolean().optional()
47
+ scenario: z.any()
49
48
  }),
50
49
 
51
50
  handler: async (iparams) => {
52
- let params = {...iparams};
51
+ let params = {...iparams};
53
52
  let scenario = params.scenario;
54
-
55
- // Convert the scenario string to an object
56
- // Example: "x=1, y=2, z=3" to { x: 1, y: 2, z: 3 }
57
- let scenarioObj ={};
58
- let count = 0;
53
+
54
+ // Convert the scenario string to an object
55
+ // Example: "x=1, y=2, z=3" to { x: 1, y: 2, z: 3 }
56
+ let scenarioObj = {};
57
+ let count = 0;
59
58
  if (typeof scenario === 'object') {
60
59
  scenarioObj = scenario;
61
- } else if (Array.isArray(scenario)) {
60
+ } else if (Array.isArray(scenario)) {
62
61
  scenarioObj = scenario[0];
63
62
  } else {
64
63
  //console.error('Incoming scenario', scenario);
65
64
  scenarioObj = scenario.split(',').reduce((acc, pair) => {
66
- let [key, value] = pair.split('=');
67
- acc[key.trim()] = value;
68
- count++;
69
- return acc;
70
- }, {});
71
- }
72
- params.scenario= scenarioObj;
73
-
65
+ let [key, value] = pair.split('=');
66
+ acc[key.trim()] = value;
67
+ count++;
68
+ return acc;
69
+ }, {});
70
+ }
71
+ params.scenario = scenarioObj;
74
72
  // Drop model extension (e.g., .job, .model)
75
73
  if (params.model && params.model.includes('.')) {
76
74
  params.model = params.model.substring(0, params.model.lastIndexOf('.'));
77
75
  }
78
-
76
+
79
77
  log('modelScore params', params);
80
78
  // Check if the params.scenario is a string and parse it
81
79
  let r = await _masScoring(params)
@@ -45,10 +45,10 @@ Log output and CAS results. If output table specified, returned as markdown tabl
45
45
  description: description,
46
46
  inputSchema:z.object({
47
47
  src: z.string(),
48
- scenario: z.string(),
49
- output: z.string().optional,
50
- folder: z.string().optional,
51
- limit: z.number().optional
48
+ scenario: z.any(),
49
+ output: z.string().optional(),
50
+ folder: z.string().optional(),
51
+ limit: z.number().optional()
52
52
  }),
53
53
 
54
54
  // NOTE: Previously 'required' incorrectly listed 'program' which does not
@@ -70,15 +70,26 @@ Log output and CAS results. If output table specified, returned as markdown tabl
70
70
  `;
71
71
  }
72
72
  // figure out macros
73
-
74
- if (typeof scenario === 'string' && scenario.includes('=')) {
75
- scenario = scenario.split(',').reduce((acc, pair) => {
76
- const [k, ...rest] = pair.split('=');
77
- if (!k) return acc;
78
- acc[k.trim()] = rest.join('=').trim();
73
+
74
+ // Convert the scenario string to an object
75
+ // Example: "x=1, y=2, z=3" to { x: 1, y: 2, z: 3 }
76
+ let scenarioObj = {};
77
+ let count = 0;
78
+ if (typeof scenario === 'object') {
79
+ scenarioObj = scenario;
80
+ } else if (Array.isArray(scenario)) {
81
+ scenarioObj = scenario[0];
82
+ } else {
83
+ //console.error('Incoming scenario', scenario);
84
+ scenarioObj = scenario.split(',').reduce((acc, pair) => {
85
+ let [key, value] = pair.split('=');
86
+ acc[key.trim()] = value;
87
+ count++;
79
88
  return acc;
80
89
  }, {});
81
90
  }
91
+ params.scenario = scenarioObj;
92
+
82
93
  let iparms = {
83
94
  args: scenario,
84
95
  output: params.output,
@@ -16,7 +16,7 @@ DO NOT USE for: arbitrary SAS code (use run-sas-program), macros (use run-macro)
16
16
 
17
17
  PARAMETERS
18
18
  - name: string — job name (required)
19
- - scenario: string | object — input parameters. Accepts: "x=1, y=2" or {x:1, y:2}
19
+ - scenario: object — input parameters as JSON (optional, defaults to {}). Example: {month:10, year:2025}
20
20
 
21
21
  ROUTING RULES
22
22
  - "run job xyz" → { name: "xyz" }
@@ -41,34 +41,30 @@ Returns log output, listings, tables from job. Error if job not found.
41
41
  description: description,
42
42
  inputSchema: z.object({
43
43
  name: z.string(),
44
- scenario: z.string().optional()
44
+ scenario: z.any()
45
45
  }),
46
46
  handler: async (params) => {
47
47
  let scenario = params.scenario;
48
+
49
+ // Convert the scenario string to an object
50
+ // Example: "x=1, y=2, z=3" to { x: 1, y: 2, z: 3 }
48
51
  let scenarioObj = {};
49
52
  let count = 0;
50
- //
51
- if (scenario == null) {
52
- scenarioObj = {};
53
- } else if (typeof scenario === 'object') {
53
+ if (typeof scenario === 'object') {
54
54
  scenarioObj = scenario;
55
55
  } else if (Array.isArray(scenario)) {
56
56
  scenarioObj = scenario[0];
57
- } else if (typeof scenario === 'string') {
58
- if (scenario.trim() === '') {
59
- scenarioObj = {};
60
- } else {
61
- // console.error('Incoming scenario', scenario);
62
- scenarioObj = scenario.split(',').reduce((acc, pair) => {
63
- let [key, value] = pair.split('=');
64
- acc[key.trim()] = value;
65
- count++;
66
- return acc;
67
- }, {});
68
- }
57
+ } else {
58
+ //console.error('Incoming scenario', scenario);
59
+ scenarioObj = scenario.split(',').reduce((acc, pair) => {
60
+ let [key, value] = pair.split('=');
61
+ acc[key.trim()] = value;
62
+ count++;
63
+ return acc;
64
+ }, {});
69
65
  }
66
+ params.scenario = scenarioObj;
70
67
  params.type = 'job';
71
- params.scenario = scenarioObj;
72
68
  // Provide runtime context for auth and server settings
73
69
  let r = await _jobSubmit(params);
74
70
  return r;
@@ -17,7 +17,7 @@ DO NOT USE for: arbitrary SAS code (use run-sas-program), macros (use run-macro)
17
17
 
18
18
  PARAMETERS
19
19
  - name: string — jobdef name (required)
20
- - scenario: string | object — input parameters. Accepts: "x=1, y=2" or {x:1, y:2}
20
+ - scenario: object — input parameters as JSON (optional, defaults to {}). Example: {month:10, year:2025}
21
21
 
22
22
  ROUTING RULES
23
23
  - "run jobdef xyz" → { name: "xyz" }
@@ -42,34 +42,30 @@ Returns log output, listings, tables from jobdef. Error if jobdef not found.
42
42
  description: description,
43
43
  inputSchema: z.object({
44
44
  name: z.string(),
45
- scenario: z.string().optional()
45
+ scenario: z.any()
46
46
  }),
47
47
  handler: async (params) => {
48
48
  let scenario = params.scenario;
49
+
50
+ // Convert the scenario string to an object
51
+ // Example: "x=1, y=2, z=3" to { x: 1, y: 2, z: 3 }
49
52
  let scenarioObj = {};
50
53
  let count = 0;
51
- //
52
- if (scenario == null) {
53
- scenarioObj = {};
54
- } else if (typeof scenario === 'object') {
54
+ if (typeof scenario === 'object') {
55
55
  scenarioObj = scenario;
56
56
  } else if (Array.isArray(scenario)) {
57
57
  scenarioObj = scenario[0];
58
- } else if (typeof scenario === 'string') {
59
- if (scenario.trim() === '') {
60
- scenarioObj = {};
61
- } else {
62
- // console.error('Incoming scenario', scenario);
63
- scenarioObj = scenario.split(',').reduce((acc, pair) => {
64
- let [key, value] = pair.split('=');
65
- acc[key.trim()] = value;
66
- count++;
67
- return acc;
68
- }, {});
69
- }
58
+ } else {
59
+ //console.error('Incoming scenario', scenario);
60
+ scenarioObj = scenario.split(',').reduce((acc, pair) => {
61
+ let [key, value] = pair.split('=');
62
+ acc[key.trim()] = value;
63
+ count++;
64
+ return acc;
65
+ }, {});
70
66
  }
67
+ params.scenario = scenarioObj;
71
68
  params.type = 'def';
72
- params.scenario = scenarioObj;
73
69
  // Provide runtime context for auth and server settings
74
70
  let r = await _jobSubmit(params);
75
71
  return r;
@@ -1,70 +1,61 @@
1
- /*
2
- * Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
3
- * SPDX-License-Identifier: Apache-2.0
4
- */
5
-
6
- import { z } from 'zod';
7
- import _scrScore from '../toolHelpers/_scrScore.js';
8
-
9
- function scrScore(_appContext) {
10
- let description = `
11
- ## scr-score
12
-
13
- Purpose
14
- Score a scenario using a model deployed as a SCR container in Azure or another host).
15
-
16
- Inputs
17
- - url (string, required): SCR model identifier (URL)
18
- - scenario (string | object | array, optional): Input values to score. Accepts:
19
- - a comma-separated key=value string (e.g. "x=1, y=2"),
20
- - a JSON object with field names and values (recommended for typed inputs),
21
- - an array of objects for batch scoring. If omitted, the tool will return the model's input variable definitions.
22
-
23
- What it returns
24
- - When scoring: the SCR endpoint response (predictions, probabilities, scores) merged with or alongside the supplied inputs.
25
- - When \`scenario\` is omitted: metadata describing the model's input variables (names, types, required/optional).
26
-
27
- Usage notes
28
- - Run \`scr-info\` first to inspect the expected input variables and types.
29
- - Prefer structured objects for numeric/date values to avoid type ambiguity; the simple string parser keeps values as strings.
30
- - Ensure network connectivity and any required credentials for the target SCR service.
31
-
32
- Examples
33
- - scrScore with url="loan" and scenario="age=45, income=60000"
34
- - scrScore with url="https://scr-host/models/loan" and scenario={age:45, income:60000}
35
- `;
36
-
37
- let spec = {
38
- name: 'scr-score',
39
- description: description,
40
- inputSchema: z.object({
41
- url: z.string(),
42
- scenario: z.string().optional()
43
- }),
44
-
45
- handler: async (params) => {
46
- let {url, scenario,_appContext} = params;
47
-
48
- if (url === null) {
49
- return { status: { statusCode: 2, msg: `SCR model ${url} was not specified` }, results: {} };
50
- }
51
-
52
- // Normalize simple string scenarios like "x=1, y=2" into an object
53
- if (typeof scenario === 'string' && scenario.includes('=')) {
54
- scenario = scenario.split(',').reduce((acc, pair) => {
55
- const [k, ...rest] = pair.split('=');
56
- if (!k) return acc;
57
- acc[k.trim()] = rest.join('=').trim();
58
- return acc;
59
- }, {});
60
- }
61
-
62
- let r = await _scrScore({ url: url, scenario: scenario , _appContext: _appContext});
63
- return r;
64
- }
65
- }
66
-
67
- return spec;
68
- }
69
-
1
+ /*
2
+ * Copyright © 2025, SAS Institute Inc., Cary, NC, USA. All Rights Reserved.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ import { z } from 'zod';
7
+ import _scrScore from '../toolHelpers/_scrScore.js';
8
+
9
+ function scrScore(_appContext) {
10
+ let description = `
11
+ ## scr-score
12
+
13
+ Purpose
14
+ Score a scenario using a model deployed as a SCR container in Azure or another host).
15
+
16
+ Inputs
17
+ - url (string, required): SCR model identifier (URL)
18
+ - scenario (object, optional): Input values to score as a JSON object (e.g. {age:45, income:60000}). If omitted, defaults to {} and the tool will return the model's input variable definitions.
19
+
20
+ What it returns
21
+ - When scoring: the SCR endpoint response (predictions, probabilities, scores) merged with or alongside the supplied inputs.
22
+ - When \`scenario\` is omitted: metadata describing the model's input variables (names, types, required/optional).
23
+
24
+ Usage notes
25
+ - Run \`scr-info\` first to inspect the expected input variables and types.
26
+ - Prefer structured objects for numeric/date values to avoid type ambiguity; the simple string parser keeps values as strings.
27
+ - Ensure network connectivity and any required credentials for the target SCR service.
28
+
29
+ Examples
30
+ - scrScore with url="loan" and scenario={age:45, income:60000}
31
+ `;
32
+
33
+ let spec = {
34
+ name: 'scr-score',
35
+ description: description,
36
+ inputSchema: z.object({
37
+ url: z.string(),
38
+ scenario: z.union([z.record(z.any()), z.string()]).optional()
39
+ }),
40
+
41
+ handler: async (params) => {
42
+ let {url, _appContext} = params;
43
+ let scenario = params.scenario;
44
+ if (typeof scenario === 'string') {
45
+ try { scenario = JSON.parse(scenario); } catch { scenario = {}; }
46
+ }
47
+ if (url === null) {
48
+ return { status: { statusCode: 2, msg: `SCR model ${url} was not specified` }, results: {} };
49
+ }
50
+
51
+ const scenarioObj = (scenario != null && typeof scenario === 'object') ? scenario : {};
52
+
53
+ let r = await _scrScore({ url: url, scenario: scenarioObj, _appContext: _appContext});
54
+ return r;
55
+ }
56
+ }
57
+
58
+ return spec;
59
+ }
60
+
70
61
  export default scrScore;
@@ -1,13 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "mcp__sasmcp__sas-score-deva-score",
5
- "mcp__sasmcp__sas-score-find-model",
6
- "mcp__sasmcp__sas-score-model-info",
7
- "Bash(cp \"C:/dev/github/7-skill-integration/.claude/skills/sas-read-and-score/SKILL.md\" \"C:/dev/github/7-skill-integration/skills/sas-read-and-score/SKILL.md\")",
8
- "Bash(cp \"C:/dev/github/7-skill-integration/.claude/skills/sas-read-strategy/SKILL.md\" \"C:/dev/github/7-skill-integration/skills/sas-read-strategy/SKILL.md\")",
9
- "Bash(cp \"C:/dev/github/7-skill-integration/.claude/skills/sas-score-workflow/SKILL.md\" \"C:/dev/github/7-skill-integration/skills/sas-score-workflow/SKILL.md\")",
10
- "WebSearch"
11
- ]
12
- }
13
- }
File without changes
File without changes
File without changes