oris-skills 2.0.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.
Files changed (81) hide show
  1. package/.cursor-plugin/plugin.json +31 -0
  2. package/LICENSE +21 -0
  3. package/README.md +67 -0
  4. package/agents/oris-loop-debriefer.md +29 -0
  5. package/agents/oris-loop-doctor.md +32 -0
  6. package/agents/oris-loop-executor.md +32 -0
  7. package/agents/oris-loop-verifier.md +31 -0
  8. package/docs/architecture.md +26 -0
  9. package/docs/distribution.md +38 -0
  10. package/docs/maintainer-guide.md +32 -0
  11. package/docs/user-guide.md +42 -0
  12. package/package.json +49 -0
  13. package/references/clean-code-checklist.md +107 -0
  14. package/references/conventions.md +39 -0
  15. package/references/doc-policy.md +24 -0
  16. package/references/loop-adapter.schema.json +126 -0
  17. package/references/loop-contract.md +119 -0
  18. package/references/loop.schema.json +143 -0
  19. package/references/questions.md +38 -0
  20. package/references/repo-map.md +53 -0
  21. package/references/repo-map.schema.json +198 -0
  22. package/references/settings.md +35 -0
  23. package/references/settings.schema.json +75 -0
  24. package/scripts/flow/oris-flow-clean-runtime.mjs +182 -0
  25. package/scripts/flow/oris-flow-layout.mjs +53 -0
  26. package/scripts/flow/oris-flow-scan.mjs +350 -0
  27. package/scripts/flow/oris-flow-version-control.mjs +42 -0
  28. package/scripts/flow/oris-gitignore.mjs +79 -0
  29. package/scripts/install/generate-agent-adapters.mjs +74 -0
  30. package/scripts/install/install-user-skills.mjs +271 -0
  31. package/scripts/install/uninstall-user-skills.mjs +163 -0
  32. package/scripts/loop/oris-loop-bootstrap.mjs +383 -0
  33. package/scripts/loop/oris-loop-bundle.mjs +22 -0
  34. package/scripts/loop/oris-loop-chat.mjs +408 -0
  35. package/scripts/loop/oris-loop-demo.mjs +180 -0
  36. package/scripts/loop/oris-loop-document.mjs +179 -0
  37. package/scripts/loop/oris-loop-dry-run.mjs +110 -0
  38. package/scripts/loop/oris-loop-fixtures.mjs +148 -0
  39. package/scripts/loop/oris-loop-list.mjs +81 -0
  40. package/scripts/loop/oris-loop-paths.mjs +232 -0
  41. package/scripts/loop/oris-loop-run.mjs +241 -0
  42. package/scripts/loop/oris-loop-stop.mjs +319 -0
  43. package/scripts/loop/oris-loop-templates.mjs +77 -0
  44. package/scripts/loop/oris-loop-verify.mjs +206 -0
  45. package/scripts/oris-skills.mjs +116 -0
  46. package/scripts/package-oris-skills.mjs +53 -0
  47. package/scripts/tests/run-all-tests.mjs +38 -0
  48. package/scripts/tests/test-agent-adapters.mjs +19 -0
  49. package/scripts/tests/test-oris-1-0-cleanliness.mjs +58 -0
  50. package/scripts/tests/test-oris-flow-clean-runtime.mjs +75 -0
  51. package/scripts/tests/test-oris-flow-scan.mjs +49 -0
  52. package/scripts/tests/test-oris-gitignore.mjs +35 -0
  53. package/scripts/tests/test-oris-loop-bootstrap.mjs +94 -0
  54. package/scripts/tests/test-oris-loop-document.mjs +112 -0
  55. package/scripts/tests/test-oris-loop-list.mjs +74 -0
  56. package/scripts/tests/test-oris-loop-run.mjs +45 -0
  57. package/scripts/tests/test-oris-loop-smoke.mjs +130 -0
  58. package/scripts/tests/test-oris-loop-stop.mjs +371 -0
  59. package/scripts/tests/test-routing-lifecycle.mjs +181 -0
  60. package/scripts/tests/test-schemas.mjs +55 -0
  61. package/skills/oris-flow/SKILL.md +55 -0
  62. package/skills/oris-flow/agents/openai.yaml +4 -0
  63. package/skills/oris-flow-criteria/SKILL.md +49 -0
  64. package/skills/oris-flow-discover/SKILL.md +58 -0
  65. package/skills/oris-flow-docs/SKILL.md +31 -0
  66. package/skills/oris-flow-fix/SKILL.md +42 -0
  67. package/skills/oris-flow-implement/SKILL.md +43 -0
  68. package/skills/oris-flow-plan/SKILL.md +51 -0
  69. package/skills/oris-flow-setup/SKILL.md +49 -0
  70. package/skills/oris-help/SKILL.md +30 -0
  71. package/skills/oris-help/agents/openai.yaml +4 -0
  72. package/skills/oris-loop/SKILL.md +66 -0
  73. package/skills/oris-loop/agents/openai.yaml +4 -0
  74. package/skills/oris-loop/references/craft.md +48 -0
  75. package/skills/oris-loop/references/improve.md +23 -0
  76. package/skills/oris-loop/references/run.md +30 -0
  77. package/skills/oris-loop/templates/debriefer.md +18 -0
  78. package/skills/oris-loop/templates/doctor.md +20 -0
  79. package/skills/oris-loop/templates/executor.md +19 -0
  80. package/skills/oris-loop/templates/orchestrator.md +33 -0
  81. package/skills/oris-loop/templates/verifier.md +21 -0
@@ -0,0 +1,383 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { ensureGitignoreEntries, ORIS_LOOP_GITIGNORE_ENTRY } from "../flow/oris-gitignore.mjs";
5
+ import {
6
+ ORIS_FLOW_ADAPTER,
7
+ ORIS_FLOW_LOOPS_ROOT,
8
+ ORIS_FLOW_MANIFEST,
9
+ ORIS_FLOW_RUNTIME,
10
+ ORIS_LOOP_SCHEMA,
11
+ ORIS_LOOP_SCHEMA_VERSION,
12
+ } from "../flow/oris-flow-layout.mjs";
13
+
14
+ export const HOOK_PLATFORMS = ["cursor", "claude"];
15
+
16
+ /** Repo-local wrapper: resolves the installed bundle and forwards stdin. */
17
+ export function stopHookWrapperSource(platform) {
18
+ return `#!/usr/bin/env node
19
+ import childProcess from "node:child_process";
20
+ import fs from "node:fs";
21
+ import os from "node:os";
22
+ import path from "node:path";
23
+ import process from "node:process";
24
+
25
+ function resolveStopHook() {
26
+ const candidates = [];
27
+ if (process.env.ORIS_SKILLS_SCRIPTS_ROOT) {
28
+ const scriptsRoot = path.resolve(process.env.ORIS_SKILLS_SCRIPTS_ROOT);
29
+ candidates.push(path.join(scriptsRoot, "loop", "oris-loop-stop.mjs"));
30
+ candidates.push(path.join(scriptsRoot, "oris-loop-stop.mjs"));
31
+ }
32
+ candidates.push(path.join(os.homedir(), ".oris", "oris-skills", "scripts", "loop", "oris-loop-stop.mjs"));
33
+ candidates.push(path.join(os.homedir(), ".cursor", "oris-skills", "scripts", "loop", "oris-loop-stop.mjs"));
34
+ for (const candidate of candidates) {
35
+ if (fs.existsSync(candidate)) return candidate;
36
+ }
37
+ process.stderr.write("Oris Loop stop hook not found. Run: npx oris-skills install\\n");
38
+ process.exit(1);
39
+ }
40
+
41
+ const result = childProcess.spawnSync(process.execPath, [resolveStopHook(), "--platform", "${platform}"], {
42
+ stdio: "inherit",
43
+ env: process.env,
44
+ });
45
+ process.exit(result.status ?? 1);
46
+ `;
47
+ }
48
+
49
+ const CURSOR_STOP_COMMAND = "node .cursor/hooks/oris-loop-stop.mjs";
50
+ const CLAUDE_STOP_COMMAND = "node .claude/hooks/oris-loop-stop.mjs";
51
+
52
+ function readJson(filePath) {
53
+ if (!fs.existsSync(filePath)) return null;
54
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
55
+ }
56
+
57
+ function listFiles(dir, depth = 0, maxDepth = 4, matches = []) {
58
+ if (depth > maxDepth || !fs.existsSync(dir)) return matches;
59
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
60
+ if (entry.name === "node_modules" || entry.name === ".git") continue;
61
+ const full = path.join(dir, entry.name);
62
+ if (entry.isFile() && /^playwright\.config\.(ts|js|mjs|cjs)$/.test(entry.name)) {
63
+ matches.push(full);
64
+ } else if (entry.isDirectory()) {
65
+ listFiles(full, depth + 1, maxDepth, matches);
66
+ }
67
+ }
68
+ return matches;
69
+ }
70
+
71
+ function toPosixRelative(root, absolutePath) {
72
+ return path.relative(root, absolutePath).split(path.sep).join("/");
73
+ }
74
+
75
+ export function detectPlaywrightRoot(repositoryRoot) {
76
+ const configs = listFiles(repositoryRoot);
77
+ const preferred = configs.sort((a, b) => a.length - b.length)[0];
78
+ if (!preferred) return null;
79
+ const configDir = path.dirname(preferred);
80
+ const text = fs.readFileSync(preferred, "utf8");
81
+ const match = text.match(/testDir:\s*['"]([^'"]+)['"]/);
82
+ if (match && match[1] !== ".") {
83
+ return toPosixRelative(repositoryRoot, path.resolve(configDir, match[1]));
84
+ }
85
+ return toPosixRelative(repositoryRoot, configDir);
86
+ }
87
+
88
+ function detectRepositoryName(repositoryRoot) {
89
+ const flowManifest = readJson(path.join(repositoryRoot, ORIS_FLOW_MANIFEST));
90
+ if (flowManifest?.repository) return String(flowManifest.repository);
91
+ return path.basename(repositoryRoot);
92
+ }
93
+
94
+ function detectPackageManagerCommand(repositoryRoot, testDir) {
95
+ const packageJsonPath = path.join(repositoryRoot, testDir, "package.json");
96
+ if (!fs.existsSync(packageJsonPath)) return null;
97
+ const pkg = readJson(packageJsonPath);
98
+ const scripts = pkg?.scripts ?? {};
99
+ const packageRoot = path.dirname(packageJsonPath);
100
+ const manager = fs.existsSync(path.join(packageRoot, "pnpm-lock.yaml"))
101
+ ? "pnpm"
102
+ : fs.existsSync(path.join(packageRoot, "yarn.lock"))
103
+ ? "yarn"
104
+ : "npm";
105
+ const executable = process.platform === "win32" ? `${manager}.cmd` : manager;
106
+ const runArgs = manager === "yarn" ? [] : ["run"];
107
+ if (scripts.check) {
108
+ return {
109
+ name: "e2e-check",
110
+ command: { executable, args: [...runArgs, "check"], cwd: testDir },
111
+ };
112
+ }
113
+ if (scripts.test) {
114
+ return {
115
+ name: "e2e-target",
116
+ command: { executable, args: [...runArgs, "test", "--", "--project=chromium"], cwd: testDir },
117
+ };
118
+ }
119
+ return null;
120
+ }
121
+
122
+ function detectProtectedPaths(repositoryRoot) {
123
+ const paths = [".git"];
124
+ for (const candidate of ["azure-pipelines.yml", ".github/workflows", ".gitlab-ci.yml", "NuGet.config"]) {
125
+ if (fs.existsSync(path.join(repositoryRoot, candidate))) paths.push(candidate);
126
+ }
127
+ return paths;
128
+ }
129
+
130
+ export function buildAdapterDraft(repositoryRoot, hints = {}) {
131
+ const repository = hints.repository ?? detectRepositoryName(repositoryRoot);
132
+ const testDir = hints.testDir ?? detectPlaywrightRoot(repositoryRoot);
133
+ const allowed = new Set([
134
+ ORIS_FLOW_LOOPS_ROOT,
135
+ ".oris-flow/maps",
136
+ ...(Array.isArray(hints.allowed) ? hints.allowed : []),
137
+ ]);
138
+ for (const optionalPath of ["docs", "docs/tasks", "specs", "src", "test", "tests"]) {
139
+ if (fs.existsSync(path.join(repositoryRoot, optionalPath))) allowed.add(optionalPath);
140
+ }
141
+ if (testDir) allowed.add(testDir);
142
+
143
+ const commands = {
144
+ verify: {
145
+ executable: process.execPath,
146
+ args: ["--version"],
147
+ cwd: ".",
148
+ },
149
+ };
150
+ const npmCommand = testDir ? detectPackageManagerCommand(repositoryRoot, testDir) : null;
151
+ if (npmCommand) commands[npmCommand.name] = npmCommand.command;
152
+
153
+ const adapter = {
154
+ $schema: ORIS_LOOP_SCHEMA,
155
+ schemaVersion: ORIS_LOOP_SCHEMA_VERSION,
156
+ repository,
157
+ paths: {
158
+ taskRoots: fs.existsSync(path.join(repositoryRoot, "docs", "tasks")) ? ["docs/tasks"] : [],
159
+ runtime: ORIS_FLOW_RUNTIME,
160
+ loops: ORIS_FLOW_LOOPS_ROOT,
161
+ allowed: [...allowed],
162
+ protected: detectProtectedPaths(repositoryRoot),
163
+ },
164
+ commands,
165
+ models: {},
166
+ profile: {
167
+ required: false,
168
+ defaultRole: "testing",
169
+ mappings: [],
170
+ },
171
+ preflight: {
172
+ minFreeDiskGiB: 5,
173
+ },
174
+ };
175
+ if (testDir) adapter.paths.testDir = testDir;
176
+ return adapter;
177
+ }
178
+
179
+ function hookFiles(platform, root) {
180
+ if (platform === "claude") {
181
+ return {
182
+ registrationPath: path.join(root, ".claude", "settings.json"),
183
+ wrapperPath: path.join(root, ".claude", "hooks", "oris-loop-stop.mjs"),
184
+ };
185
+ }
186
+ return {
187
+ registrationPath: path.join(root, ".cursor", "hooks.json"),
188
+ wrapperPath: path.join(root, ".cursor", "hooks", "oris-loop-stop.mjs"),
189
+ };
190
+ }
191
+
192
+ function hookRegistered(platform, registrationPath) {
193
+ const data = readJson(registrationPath);
194
+ if (!data) return false;
195
+ if (platform === "claude") {
196
+ return JSON.stringify(data?.hooks?.Stop ?? []).includes("oris-loop-stop");
197
+ }
198
+ const stops = data?.hooks?.stop ?? [];
199
+ return stops.some((entry) => String(entry?.command ?? "").includes("oris-loop-stop"));
200
+ }
201
+
202
+ export function assessLoopReadiness(repositoryRoot, platforms = HOOK_PLATFORMS) {
203
+ const root = path.resolve(repositoryRoot);
204
+ const adapterPath = path.join(root, ORIS_FLOW_ADAPTER);
205
+ const missing = [];
206
+ if (!fs.existsSync(adapterPath)) missing.push(ORIS_FLOW_ADAPTER);
207
+ for (const platform of platforms) {
208
+ const { registrationPath, wrapperPath } = hookFiles(platform, root);
209
+ if (!hookRegistered(platform, registrationPath)) {
210
+ missing.push(`${toPosixRelative(root, registrationPath)} stop registration for Oris Loop`);
211
+ }
212
+ if (!fs.existsSync(wrapperPath)) missing.push(toPosixRelative(root, wrapperPath));
213
+ }
214
+ return {
215
+ ready: missing.length === 0,
216
+ missing,
217
+ paths: { adapterPath },
218
+ };
219
+ }
220
+
221
+ function ensureGitignore(repositoryRoot, dryRun) {
222
+ return ensureGitignoreEntries(repositoryRoot, [ORIS_LOOP_GITIGNORE_ENTRY], dryRun);
223
+ }
224
+
225
+ export function writeStopHookWrapper(repositoryRoot, platform = "cursor") {
226
+ const { wrapperPath } = hookFiles(platform, path.resolve(repositoryRoot));
227
+ fs.mkdirSync(path.dirname(wrapperPath), { recursive: true });
228
+ fs.writeFileSync(wrapperPath, stopHookWrapperSource(platform), "utf8");
229
+ return wrapperPath;
230
+ }
231
+
232
+ export function writeHooksRegistration(repositoryRoot, platform = "cursor") {
233
+ const root = path.resolve(repositoryRoot);
234
+ const { registrationPath } = hookFiles(platform, root);
235
+ fs.mkdirSync(path.dirname(registrationPath), { recursive: true });
236
+ const existing = readJson(registrationPath) ?? (platform === "claude" ? {} : { version: 1, hooks: {} });
237
+ if (platform === "claude") {
238
+ existing.hooks ??= {};
239
+ const stops = Array.isArray(existing.hooks.Stop) ? existing.hooks.Stop : [];
240
+ if (!JSON.stringify(stops).includes("oris-loop-stop")) {
241
+ stops.push({ hooks: [{ type: "command", command: CLAUDE_STOP_COMMAND }] });
242
+ }
243
+ existing.hooks.Stop = stops;
244
+ } else {
245
+ existing.version ??= 1;
246
+ existing.hooks ??= {};
247
+ const stops = Array.isArray(existing.hooks.stop) ? existing.hooks.stop : [];
248
+ if (!stops.some((entry) => String(entry?.command ?? "").includes("oris-loop-stop"))) {
249
+ stops.push({ command: CURSOR_STOP_COMMAND, loop_limit: null });
250
+ }
251
+ existing.hooks.stop = stops;
252
+ }
253
+ fs.writeFileSync(registrationPath, `${JSON.stringify(existing, null, 2)}\n`, "utf8");
254
+ return registrationPath;
255
+ }
256
+
257
+ function ensureHookPlatform(repositoryRoot, platform, dryRun, actions) {
258
+ const root = path.resolve(repositoryRoot);
259
+ const { registrationPath, wrapperPath } = hookFiles(platform, root);
260
+ if (hookRegistered(platform, registrationPath)) {
261
+ actions.push({ action: "present", path: registrationPath });
262
+ } else if (dryRun) {
263
+ actions.push({ action: fs.existsSync(registrationPath) ? "merge" : "create", path: registrationPath });
264
+ } else {
265
+ actions.push({ action: "registered", path: writeHooksRegistration(root, platform) });
266
+ }
267
+ if (fs.existsSync(wrapperPath)) {
268
+ actions.push({ action: "present", path: wrapperPath });
269
+ } else if (dryRun) {
270
+ actions.push({ action: "create", path: wrapperPath });
271
+ } else {
272
+ actions.push({ action: "created", path: writeStopHookWrapper(root, platform) });
273
+ }
274
+ }
275
+
276
+ export function bootstrapLoop(repositoryRoot, options = {}) {
277
+ const root = path.resolve(repositoryRoot);
278
+ const dryRun = Boolean(options.dryRun);
279
+ const platforms = Array.isArray(options.platforms) && options.platforms.length > 0
280
+ ? options.platforms.filter((platform) => HOOK_PLATFORMS.includes(platform))
281
+ : HOOK_PLATFORMS;
282
+ const assessment = assessLoopReadiness(root, platforms);
283
+ if (assessment.ready) {
284
+ return {
285
+ repositoryRoot: root,
286
+ dryRun,
287
+ platforms,
288
+ before: assessment,
289
+ after: assessment,
290
+ actions: [],
291
+ ready: true,
292
+ };
293
+ }
294
+ const actions = [];
295
+
296
+ if (!fs.existsSync(path.join(root, ORIS_FLOW_ADAPTER))) {
297
+ const adapterPath = path.join(root, ORIS_FLOW_ADAPTER);
298
+ if (dryRun) {
299
+ actions.push({ action: "create", path: adapterPath, detail: "adapter draft" });
300
+ } else {
301
+ const adapter = buildAdapterDraft(root, options);
302
+ fs.mkdirSync(path.dirname(adapterPath), { recursive: true });
303
+ fs.writeFileSync(adapterPath, `${JSON.stringify(adapter, null, 2)}\n`, "utf8");
304
+ actions.push({ action: "created", path: adapterPath });
305
+ }
306
+ } else {
307
+ actions.push({ action: "present", path: path.join(root, ORIS_FLOW_ADAPTER) });
308
+ }
309
+
310
+ for (const platform of platforms) {
311
+ ensureHookPlatform(root, platform, dryRun, actions);
312
+ }
313
+
314
+ actions.push(ensureGitignore(root, dryRun));
315
+
316
+ const after = dryRun ? assessment : assessLoopReadiness(root, platforms);
317
+ return {
318
+ repositoryRoot: root,
319
+ dryRun,
320
+ platforms,
321
+ before: assessment,
322
+ after,
323
+ actions,
324
+ ready: after.ready,
325
+ };
326
+ }
327
+
328
+ function parseArgs(argv) {
329
+ const options = {
330
+ repositoryRoot: process.cwd(),
331
+ dryRun: false,
332
+ json: false,
333
+ platforms: [],
334
+ };
335
+ for (let i = 0; i < argv.length; i += 1) {
336
+ const raw = argv[i];
337
+ if (raw === "--dry-run") options.dryRun = true;
338
+ else if (raw === "--json") options.json = true;
339
+ else if (raw.startsWith("--platforms=")) options.platforms = raw.slice("--platforms=".length).split(",").map((value) => value.trim()).filter(Boolean);
340
+ else if (raw === "--platforms" && argv[i + 1]) {
341
+ options.platforms = argv[i + 1].split(",").map((value) => value.trim()).filter(Boolean);
342
+ i += 1;
343
+ } else if (raw.startsWith("--repository-root=")) options.repositoryRoot = raw.slice("--repository-root=".length);
344
+ else if (raw === "--repository-root" && argv[i + 1]) {
345
+ options.repositoryRoot = argv[i + 1];
346
+ i += 1;
347
+ } else if (!raw.startsWith("-")) {
348
+ options.repositoryRoot = raw;
349
+ }
350
+ }
351
+ return options;
352
+ }
353
+
354
+ export function printBootstrapReport(result) {
355
+ console.log(`Oris Loop bootstrap${result.dryRun ? " (dry run)" : ""}`);
356
+ console.log(`Repository: ${result.repositoryRoot}`);
357
+ console.log(`Hook platforms: ${result.platforms.join(", ")}`);
358
+ console.log("");
359
+ if (result.before.ready) {
360
+ console.log("Loop readiness: already configured.");
361
+ return;
362
+ }
363
+ console.log(`Loop readiness before: missing ${result.before.missing.length} item(s)`);
364
+ for (const item of result.before.missing) console.log(`- ${item}`);
365
+ console.log("");
366
+ console.log("Planned actions:");
367
+ for (const action of result.actions) {
368
+ console.log(`- ${action.action}: ${action.path}${action.detail ? ` (${action.detail})` : ""}`);
369
+ }
370
+ console.log("");
371
+ console.log(result.ready ? "Loop readiness: complete." : "Loop readiness: incomplete.");
372
+ }
373
+
374
+ if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url))) {
375
+ const options = parseArgs(process.argv.slice(2));
376
+ const result = bootstrapLoop(options.repositoryRoot, { dryRun: options.dryRun, platforms: options.platforms });
377
+ if (options.json) {
378
+ console.log(JSON.stringify(result, null, 2));
379
+ } else {
380
+ printBootstrapReport(result);
381
+ }
382
+ process.exit(result.ready || result.dryRun ? 0 : 1);
383
+ }
@@ -0,0 +1,22 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ /** Resolve the OrisSkills scripts directory for bundled loop helpers. */
7
+ export function resolveScriptsRoot(fromImportMetaUrl) {
8
+ if (process.env.ORIS_SKILLS_SCRIPTS_ROOT) {
9
+ return path.resolve(process.env.ORIS_SKILLS_SCRIPTS_ROOT);
10
+ }
11
+ const local = path.dirname(fileURLToPath(fromImportMetaUrl));
12
+ if (fs.existsSync(path.join(local, "oris-loop-stop.mjs"))) {
13
+ return local;
14
+ }
15
+ const installed = path.join(os.homedir(), ".oris", "oris-skills", "scripts", "loop");
16
+ if (fs.existsSync(installed)) return installed;
17
+ return path.join(os.homedir(), ".cursor", "oris-skills", "scripts", "loop");
18
+ }
19
+
20
+ export function scriptPath(fromImportMetaUrl, fileName) {
21
+ return path.join(resolveScriptsRoot(fromImportMetaUrl), fileName);
22
+ }