oris-skills 2.2.3 → 3.0.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.
Files changed (86) hide show
  1. package/.cursor-plugin/plugin.json +2 -3
  2. package/CHANGELOG.md +24 -6
  3. package/README.md +28 -60
  4. package/docs/architecture.md +15 -11
  5. package/docs/distribution.md +3 -3
  6. package/docs/maintainer-guide.md +4 -4
  7. package/docs/user-guide.md +12 -20
  8. package/package.json +3 -6
  9. package/references/clean-code-checklist.md +2 -2
  10. package/references/conventions.md +36 -16
  11. package/references/doc-policy.md +5 -5
  12. package/references/research.md +45 -0
  13. package/references/settings.md +11 -5
  14. package/references/settings.schema.json +10 -0
  15. package/scripts/flow/oris-flow-layout.mjs +0 -16
  16. package/scripts/flow/oris-flow-scan.mjs +32 -317
  17. package/scripts/flow/oris-gitignore.mjs +1 -5
  18. package/scripts/install/install-user-skills.mjs +3 -3
  19. package/scripts/install/uninstall-user-skills.mjs +2 -29
  20. package/scripts/oris-skills.mjs +0 -47
  21. package/scripts/tests/run-all-tests.mjs +1 -9
  22. package/scripts/tests/test-cleanliness.mjs +54 -0
  23. package/scripts/tests/test-oris-flow-scan.mjs +21 -91
  24. package/scripts/tests/test-routing-lifecycle.mjs +24 -54
  25. package/scripts/tests/test-schemas.mjs +17 -31
  26. package/scripts/tests/test-skill-style.mjs +5 -2
  27. package/skills/oris-flow/SKILL.md +21 -11
  28. package/skills/oris-flow/agents/openai.yaml +1 -1
  29. package/skills/oris-flow/references/architecture.md +6 -3
  30. package/skills/oris-flow/references/change.md +3 -0
  31. package/skills/oris-flow/references/commit.md +9 -8
  32. package/skills/oris-flow/references/criteria.md +9 -5
  33. package/skills/oris-flow/references/discover.md +2 -2
  34. package/skills/oris-flow/references/docs.md +5 -1
  35. package/skills/oris-flow/references/fix.md +10 -5
  36. package/skills/oris-flow/references/handoff.md +67 -0
  37. package/skills/oris-flow/references/help.md +3 -6
  38. package/skills/oris-flow/references/implement.md +7 -4
  39. package/skills/oris-flow/references/new.md +6 -6
  40. package/skills/oris-flow/references/plan.md +7 -2
  41. package/skills/oris-flow/references/pr.md +2 -2
  42. package/skills/oris-flow/references/setup.md +33 -39
  43. package/skills/oris-flow/references/triage.md +69 -0
  44. package/skills/oris-flow/references/verify.md +5 -5
  45. package/agents/oris-loop-debriefer.md +0 -30
  46. package/agents/oris-loop-doctor.md +0 -32
  47. package/agents/oris-loop-executor.md +0 -35
  48. package/agents/oris-loop-verifier.md +0 -35
  49. package/references/loop-adapter.schema.json +0 -93
  50. package/references/loop-contract.md +0 -126
  51. package/references/loop.schema.json +0 -156
  52. package/references/repo-map.md +0 -51
  53. package/references/repo-map.schema.json +0 -213
  54. package/scripts/flow/oris-flow-clean-runtime.mjs +0 -182
  55. package/scripts/install/generate-agent-adapters.mjs +0 -81
  56. package/scripts/loop/oris-loop-bootstrap.mjs +0 -383
  57. package/scripts/loop/oris-loop-bundle.mjs +0 -22
  58. package/scripts/loop/oris-loop-chat.mjs +0 -396
  59. package/scripts/loop/oris-loop-demo.mjs +0 -171
  60. package/scripts/loop/oris-loop-document.mjs +0 -196
  61. package/scripts/loop/oris-loop-dry-run.mjs +0 -114
  62. package/scripts/loop/oris-loop-fixtures.mjs +0 -149
  63. package/scripts/loop/oris-loop-list.mjs +0 -81
  64. package/scripts/loop/oris-loop-paths.mjs +0 -232
  65. package/scripts/loop/oris-loop-run.mjs +0 -301
  66. package/scripts/loop/oris-loop-stop.mjs +0 -358
  67. package/scripts/loop/oris-loop-templates.mjs +0 -80
  68. package/scripts/loop/oris-loop-verify.mjs +0 -205
  69. package/scripts/tests/test-agent-adapters.mjs +0 -70
  70. package/scripts/tests/test-oris-1-0-cleanliness.mjs +0 -58
  71. package/scripts/tests/test-oris-flow-clean-runtime.mjs +0 -75
  72. package/scripts/tests/test-oris-loop-bootstrap.mjs +0 -94
  73. package/scripts/tests/test-oris-loop-document.mjs +0 -148
  74. package/scripts/tests/test-oris-loop-list.mjs +0 -74
  75. package/scripts/tests/test-oris-loop-run.mjs +0 -71
  76. package/scripts/tests/test-oris-loop-smoke.mjs +0 -120
  77. package/scripts/tests/test-oris-loop-stop.mjs +0 -432
  78. package/skills/oris-flow/references/loop-craft.md +0 -59
  79. package/skills/oris-flow/references/loop-improve.md +0 -32
  80. package/skills/oris-flow/references/loop-run.md +0 -47
  81. package/skills/oris-flow/references/loop.md +0 -56
  82. package/skills/oris-flow/templates/debriefer.md +0 -19
  83. package/skills/oris-flow/templates/doctor.md +0 -22
  84. package/skills/oris-flow/templates/executor.md +0 -25
  85. package/skills/oris-flow/templates/orchestrator.md +0 -36
  86. package/skills/oris-flow/templates/verifier.md +0 -24
@@ -2,31 +2,15 @@ import os from "node:os";
2
2
  import path from "node:path";
3
3
 
4
4
  export const ORIS_FLOW_ROOT = ".oris-flow";
5
- export const ORIS_FLOW_MANIFEST = ".oris-flow/manifest.json";
6
- export const ORIS_FLOW_MAPS_ROOT = ".oris-flow/maps";
7
5
  export const ORIS_FLOW_TASKS_ROOT = ".oris-flow/tasks";
8
- export const ORIS_FLOW_ADAPTER = ".oris-flow/adapter.json";
9
- export const ORIS_FLOW_LOOPS_ROOT = ".oris-flow/loops";
10
- export const ORIS_FLOW_RUNTIME = ".oris-flow/runtime";
11
6
  export const ORIS_FLOW_SETTINGS = ".oris-flow/settings.json";
12
7
  export const ORIS_HOME = path.join(os.homedir(), ".oris");
13
8
  export const ORIS_BUNDLE_ROOT = path.join(ORIS_HOME, "oris-skills");
14
- export const ORIS_LOOP_SCHEMA = "https://oris.dev/schemas/oris-loop-adapter-v1.json";
15
- export const ORIS_LOOP_SCHEMA_VERSION = 1;
16
- export const ORIS_LOOP_DOCUMENT_VERSION = 2;
17
- export const ORIS_FLOW_MAP_SCHEMA = "https://oris.dev/schemas/oris-flow-map-v1.json";
18
- export const ORIS_FLOW_MAP_SCHEMA_VERSION = 1;
19
9
 
20
10
  export function toPosixPath(value) {
21
11
  return String(value).replace(/\\/g, "/");
22
12
  }
23
13
 
24
- export function safeSlug(value, fallback = "loop") {
25
- const raw = path.basename(toPosixPath(value || fallback));
26
- const slug = raw.replace(/[^A-Za-z0-9_.-]/g, "-").replace(/^-+|-+$/g, "");
27
- return slug || fallback;
28
- }
29
-
30
14
  export function assertRepositoryRelative(value, label) {
31
15
  const normalized = toPosixPath(value ?? "");
32
16
  if (!normalized || normalized.startsWith("/") || /^[A-Za-z]:\//.test(normalized)) {
@@ -1,20 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import childProcess from "node:child_process";
3
+ /**
4
+ * One-shot repository detector for the setup route. Reports evidence — project
5
+ * type, stacks, commands, notable areas — and writes NOTHING. Durable facts the
6
+ * user confirms belong in AGENTS.md; preferences belong in .oris-flow/settings.json.
7
+ */
8
+
4
9
  import fs from "node:fs";
5
10
  import path from "node:path";
6
11
  import process from "node:process";
7
12
  import { fileURLToPath } from "node:url";
8
- import {
9
- ORIS_FLOW_MANIFEST,
10
- ORIS_FLOW_MAP_SCHEMA,
11
- ORIS_FLOW_MAP_SCHEMA_VERSION,
12
- ORIS_FLOW_MAPS_ROOT,
13
- ORIS_FLOW_TASKS_ROOT,
14
- relativeToRepository,
15
- resolveInsideRepository,
16
- toPosixPath,
17
- } from "./oris-flow-layout.mjs";
13
+ import { relativeToRepository, toPosixPath } from "./oris-flow-layout.mjs";
18
14
 
19
15
  const ignoredDirectories = new Set([
20
16
  ".oris-flow",
@@ -64,74 +60,10 @@ function fail(message) {
64
60
  process.exit(1);
65
61
  }
66
62
 
67
- function git(root, args) {
68
- try {
69
- const output = childProcess.execFileSync("git", args, { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
70
- return output.trim();
71
- } catch {
72
- return null;
73
- }
74
- }
75
-
76
- /** Commit SHA the scan anchors to; null outside git (the anchor is then unavailable, never an error). */
77
- export function gitHeadSha(root) {
78
- return git(root, ["rev-parse", "HEAD"]);
79
- }
80
-
81
- /**
82
- * Files changed since the anchored SHA: committed delta + working-tree changes,
83
- * deduped. The map's own files (.oris-flow/**) never count — they describe the
84
- * code, they are not the code.
85
- */
86
- export function gitChangedFiles(root, sinceSha) {
87
- const diff = git(root, ["diff", "--name-only", `${sinceSha}..HEAD`]);
88
- const status = git(root, ["status", "--porcelain", "-uall"]);
89
- if (diff === null && status === null) return null;
90
- const changed = new Set();
91
- const add = (file) => {
92
- const clean = file.replace(/^"|"$/g, "");
93
- if (clean && !clean.startsWith(".oris-flow/")) changed.add(clean);
94
- };
95
- for (const line of (diff ?? "").split(/\r?\n/)) if (line.trim()) add(line.trim());
96
- for (const line of (status ?? "").split(/\r?\n/)) {
97
- const file = line.slice(3).trim();
98
- if (file) add(file.includes(" -> ") ? file.split(" -> ")[1] : file);
99
- }
100
- return [...changed].sort();
101
- }
102
-
103
- /**
104
- * Freshness in one cheap call (the delta-scan anchor): compare the manifest's
105
- * headSha with the current HEAD. Equal and clean → fresh, zero reads. Moved →
106
- * the changed file list tells the caller which map areas to refresh in place.
107
- */
108
- export function checkFreshness(repositoryRoot) {
109
- const root = path.resolve(repositoryRoot);
110
- const manifest = readJson(path.join(root, ...ORIS_FLOW_MANIFEST.split("/")));
111
- if (!manifest) return { manifest: false, fresh: false, reason: "no manifest — run a full scan" };
112
- const headSha = gitHeadSha(root);
113
- const scannedSha = manifest.headSha ?? null;
114
- if (!headSha || !scannedSha) {
115
- return { manifest: true, fresh: false, reason: "no SHA anchor — refresh writes one", headSha, scannedSha, changedFiles: null };
116
- }
117
- const changedFiles = gitChangedFiles(root, scannedSha) ?? [];
118
- const fresh = headSha === scannedSha && changedFiles.length === 0;
119
- return {
120
- manifest: true,
121
- fresh,
122
- headSha,
123
- scannedSha,
124
- changedFiles: fresh ? [] : changedFiles.slice(0, 200),
125
- sections: Object.keys(manifest.sections ?? {}),
126
- };
127
- }
128
-
129
63
  function parseArgs(argv) {
130
64
  const options = {
131
65
  repositoryRoot: process.cwd(),
132
- write: false,
133
66
  json: false,
134
- area: "",
135
67
  };
136
68
  const readValue = (index, raw) => {
137
69
  if (raw.includes("=")) return [index, raw.slice(raw.indexOf("=") + 1)];
@@ -142,10 +74,7 @@ function parseArgs(argv) {
142
74
  const raw = argv[i];
143
75
  const key = raw.toLowerCase();
144
76
  if (key === "--repository-root" || key.startsWith("--repository-root=")) [i, options.repositoryRoot] = readValue(i, raw);
145
- else if (key === "--write") options.write = true;
146
77
  else if (key === "--json") options.json = true;
147
- else if (key === "--area" || key.startsWith("--area=")) [i, options.area] = readValue(i, raw);
148
- else if (key === "--check") options.check = true;
149
78
  else fail(`Unknown argument: ${raw}`);
150
79
  }
151
80
  return options;
@@ -309,274 +238,60 @@ function deriveStackCommands(stacks, files) {
309
238
  return commands;
310
239
  }
311
240
 
312
- function hasAny(files, predicate) {
313
- return files.some(predicate);
314
- }
241
+ /** Notable areas the setup interview walks through — evidence pointers, never a stored map. */
242
+ function detectAreas(files) {
243
+ const paths = files.map((file) => file.relative);
244
+ const areas = {};
315
245
 
316
- function makeSection(pathValue, summary, details) {
317
- return {
318
- pointer: {
319
- path: pathValue,
320
- confidence: details.confidence ?? "detected",
321
- lastScannedAt: details.scannedAt,
322
- stale: false,
323
- summary,
324
- },
325
- markdown: details.markdown,
326
- };
327
- }
328
-
329
- function buildSections(root, files, stacks, commands, scannedAt) {
330
- const sections = {};
331
- const topFiles = files.map((file) => file.relative);
246
+ const docs = paths.filter((file) => /(^|\/)(README|CONTEXT|CHANGELOG|CONTRIBUTING|AGENTS|CLAUDE)\.md$/i.test(file) || file.startsWith("docs/")).slice(0, 20);
247
+ if (docs.length > 0) areas.docs = docs;
332
248
 
333
- if (hasAny(files, (file) => /(^|\/)(README|CONTEXT|CHANGELOG|CONTRIBUTING|AGENTS|CLAUDE)\.md$/i.test(file.relative) || file.relative.startsWith("docs/"))) {
334
- const docs = topFiles.filter((file) => /(^|\/)(README|CONTEXT|CHANGELOG|CONTRIBUTING|AGENTS|CLAUDE)\.md$/i.test(file) || file.startsWith("docs/")).slice(0, 20);
335
- sections.docs = makeSection(".oris-flow/maps/docs.md", "Documentation and task context detected.", {
336
- scannedAt,
337
- markdown: [
338
- "# Docs",
339
- "",
340
- "Detected documentation paths:",
341
- ...docs.map((file) => `- ${file}`),
342
- "",
343
- ].join("\n"),
344
- });
345
- }
346
-
347
- const guidancePaths = topFiles.filter((file) =>
249
+ const agentGuidance = paths.filter((file) =>
348
250
  file.startsWith(".cursor/skills/") || file.startsWith(".cursor/rules/")
349
251
  || file.startsWith(".claude/skills/") || /^AGENTS\.md$/i.test(file) || /^CLAUDE\.md$/i.test(file)
350
252
  || /^CONTEXT\.md$/i.test(file) || file.startsWith("docs/adr/")).slice(0, 20);
351
- if (guidancePaths.length > 0) {
352
- sections.agentGuidance = makeSection(".oris-flow/maps/agent-guidance.md", "Agent guidance detected (AGENTS/CLAUDE/CONTEXT, skills, rules, ADRs).", {
353
- scannedAt,
354
- markdown: [
355
- "# Agent Guidance",
356
- "",
357
- "Detected agent-facing guidance:",
358
- ...guidancePaths.map((file) => `- ${file}`),
359
- "",
360
- ].join("\n"),
361
- });
362
- }
253
+ if (agentGuidance.length > 0) areas.agentGuidance = agentGuidance;
363
254
 
364
- if (stacks.length > 0) {
365
- sections.stack = makeSection(".oris-flow/maps/stack.md", `Detected ${stacks.length} stack signal(s).`, {
366
- scannedAt,
367
- markdown: [
368
- "# Stack",
369
- "",
370
- ...stacks.flatMap((stack) => [
371
- `## ${stack.label}`,
372
- "",
373
- ...stack.evidence.map((file) => `- ${file}`),
374
- "",
375
- ]),
376
- ].join("\n"),
377
- });
378
- }
255
+ const code = [...new Set(paths
256
+ .filter((file) => /(^|\/)(src|app|apps|packages)\//.test(file))
257
+ .map((file) => file.split("/").slice(0, 2).join("/")))]
258
+ .slice(0, 20);
259
+ if (code.length > 0) areas.code = code;
379
260
 
380
- if (commands.length > 0) {
381
- sections.commands = makeSection(".oris-flow/maps/commands.md", `Detected ${commands.length} command(s).`, {
382
- scannedAt,
383
- markdown: [
384
- "# Commands",
385
- "",
386
- ...commands.map((command) => `- \`${command.command}\` (${command.evidence || command.confidence}${command.confidence === "inferred" ? ", inferred — confirm before relying on it" : ""})`),
387
- "",
388
- ].join("\n"),
389
- });
390
- }
391
-
392
- if (hasAny(files, (file) => /(^|\/)(src|app|apps|packages)\//.test(file.relative))) {
393
- const codeRoots = [...new Set(topFiles
394
- .filter((file) => /(^|\/)(src|app|apps|packages)\//.test(file))
395
- .map((file) => file.split("/").slice(0, 2).join("/")))]
396
- .slice(0, 20);
397
- sections.code = makeSection(".oris-flow/maps/code.md", "Source code areas detected.", {
398
- scannedAt,
399
- markdown: [
400
- "# Code",
401
- "",
402
- "Detected source areas:",
403
- ...codeRoots.map((file) => `- ${file}`),
404
- "",
405
- ].join("\n"),
406
- });
407
- }
408
-
409
- if (hasAny(files, (file) => /(^|\/)(test|tests|spec|specs|e2e)(\/|$)|\.(test|spec)\.[jt]sx?$/i.test(file.relative))) {
410
- const tests = topFiles.filter((file) => /(^|\/)(test|tests|spec|specs|e2e)(\/|$)|\.(test|spec)\.[jt]sx?$/i.test(file)).slice(0, 20);
411
- sections.tests = makeSection(".oris-flow/maps/tests.md", "Test assets detected.", {
412
- scannedAt,
413
- markdown: [
414
- "# Tests",
415
- "",
416
- "Detected test paths:",
417
- ...tests.map((file) => `- ${file}`),
418
- "",
419
- ].join("\n"),
420
- });
421
- }
261
+ const tests = paths.filter((file) => /(^|\/)(test|tests|spec|specs|e2e)(\/|$)|\.(test|spec)\.[jt]sx?$/i.test(file)).slice(0, 20);
262
+ if (tests.length > 0) areas.tests = tests;
422
263
 
423
- return sections;
424
- }
425
-
426
- function resolveTasksRoot(root, existingManifest) {
427
- const configured = existingManifest?.setup?.tasksRoot;
428
- if (typeof configured === "string" && configured.length > 0) return configured;
429
- const legacy = path.join(root, "docs", "tasks");
430
- if (fs.existsSync(legacy) && fs.readdirSync(legacy).length > 0) return "docs/tasks";
431
- return ORIS_FLOW_TASKS_ROOT;
432
- }
433
-
434
- /**
435
- * Refresh keeps user truth: confirmed decisions, tasksRoot, version control,
436
- * confirmed commands, and confirmed sections (whose map files are also left
437
- * untouched). A confirmed section the scan no longer sees is marked stale,
438
- * never deleted.
439
- */
440
- function mergeWithExisting(scan, existingManifest) {
441
- if (!existingManifest || typeof existingManifest !== "object") return scan;
442
- const merged = scan;
443
- merged.manifest.generatedAt = existingManifest.generatedAt ?? merged.manifest.generatedAt;
444
- if (Array.isArray(existingManifest.setup?.confirmedDecisions)) {
445
- merged.manifest.setup.confirmedDecisions = existingManifest.setup.confirmedDecisions;
446
- }
447
- if (existingManifest.setup?.versionControl?.orisFlowMap) {
448
- merged.manifest.setup.versionControl.orisFlowMap = existingManifest.setup.versionControl.orisFlowMap;
449
- }
450
- if (existingManifest.projectType?.confidence === "confirmed") {
451
- merged.manifest.projectType = existingManifest.projectType;
452
- }
453
-
454
- const confirmedCommands = (existingManifest.commands ?? []).filter((command) => command?.confidence === "confirmed");
455
- if (confirmedCommands.length > 0) {
456
- const scanned = merged.manifest.commands.filter((command) =>
457
- !confirmedCommands.some((confirmed) => confirmed.name === command.name && confirmed.cwd === command.cwd));
458
- merged.manifest.commands = [...confirmedCommands, ...scanned];
459
- merged.commands = merged.manifest.commands;
460
- }
461
-
462
- merged.preservedSections = new Set();
463
- for (const [key, pointer] of Object.entries(existingManifest.sections ?? {})) {
464
- if (pointer?.confidence !== "confirmed") continue;
465
- const rescanned = merged.manifest.sections[key];
466
- merged.manifest.sections[key] = {
467
- ...pointer,
468
- stale: rescanned ? pointer.stale === true : true,
469
- };
470
- merged.preservedSections.add(key);
471
- if (merged.sections[key]) delete merged.sections[key];
472
- }
473
- return merged;
264
+ return areas;
474
265
  }
475
266
 
476
267
  export function scanRepository(repositoryRoot, options = {}) {
477
268
  const root = path.resolve(repositoryRoot);
478
- const scannedAt = new Date().toISOString();
479
- const headSha = gitHeadSha(root);
480
- const existingManifest = readJson(path.join(root, ...ORIS_FLOW_MANIFEST.split("/")));
481
269
  const { files, truncated } = walk(root, options);
482
270
  const stacks = detectStacks(files);
483
- const projectType = detectProjectType(files, stacks);
484
- const commands = [...findPackageCommands(files), ...deriveStackCommands(stacks, files)];
485
- const sections = buildSections(root, files, stacks, commands, scannedAt);
486
- const sectionPointers = Object.fromEntries(
487
- Object.entries(sections).map(([key, value]) => [key, value.pointer]),
488
- );
489
- const manifest = {
490
- $schema: ORIS_FLOW_MAP_SCHEMA,
491
- schemaVersion: ORIS_FLOW_MAP_SCHEMA_VERSION,
492
- repository: path.basename(root),
493
- generatedAt: scannedAt,
494
- updatedAt: scannedAt,
495
- headSha,
496
- projectType,
497
- truncated,
498
- setup: {
499
- mode: options.mode ?? (existingManifest ? "refresh" : "create"),
500
- scanDepth: options.scanDepth ?? "balanced",
501
- tasksRoot: options.tasksRoot ?? resolveTasksRoot(root, existingManifest),
502
- confirmedDecisions: [],
503
- versionControl: {
504
- orisFlowMap: options.versionControl ?? "commit",
505
- },
506
- },
507
- stacks,
508
- commands,
509
- sections: sectionPointers,
510
- };
511
- const scan = {
271
+ return {
512
272
  repositoryRoot: root,
513
- scannedAt,
273
+ scannedAt: new Date().toISOString(),
514
274
  fileCount: files.length,
515
275
  truncated,
516
- projectType,
276
+ projectType: detectProjectType(files, stacks),
517
277
  stacks,
518
- commands,
519
- sections,
520
- manifest,
521
- preservedSections: new Set(),
522
- };
523
- return mergeWithExisting(scan, existingManifest);
524
- }
525
-
526
- export function writeScanResult(scan, options = {}) {
527
- const root = scan.repositoryRoot;
528
- const area = options.area ?? "";
529
- const manifestPath = resolveInsideRepository(root, ORIS_FLOW_MANIFEST, "Oris Flow manifest");
530
- fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
531
- fs.writeFileSync(manifestPath, `${JSON.stringify(scan.manifest, null, 2)}\n`, "utf8");
532
-
533
- const written = [];
534
- for (const [key, section] of Object.entries(scan.sections)) {
535
- if (area && key !== area) continue;
536
- const sectionPath = resolveInsideRepository(root, section.pointer.path, "Oris Flow section");
537
- fs.mkdirSync(path.dirname(sectionPath), { recursive: true });
538
- fs.writeFileSync(sectionPath, section.markdown, "utf8");
539
- written.push(section.pointer.path);
540
- }
541
-
542
- const readmePath = resolveInsideRepository(root, `${ORIS_FLOW_MAPS_ROOT}/README.md`, "Oris Flow map README");
543
- if (!fs.existsSync(readmePath)) {
544
- fs.mkdirSync(path.dirname(readmePath), { recursive: true });
545
- fs.writeFileSync(readmePath, "# Oris Flow Maps\n\nGenerated by `oris-flow-scan.mjs`. Sections are present only when repository evidence supports them.\n", "utf8");
546
- }
547
- return {
548
- manifestPath: relativeToRepository(root, manifestPath),
549
- sectionPaths: written,
550
- preservedSections: [...(scan.preservedSections ?? [])],
278
+ commands: [...findPackageCommands(files), ...deriveStackCommands(stacks, files)],
279
+ areas: detectAreas(files),
551
280
  };
552
281
  }
553
282
 
554
283
  if (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(fileURLToPath(import.meta.url))) {
555
284
  const options = parseArgs(process.argv.slice(2));
556
- if (options.check) {
557
- console.log(JSON.stringify(checkFreshness(options.repositoryRoot), null, 2));
558
- process.exit(0);
559
- }
560
285
  const scan = scanRepository(options.repositoryRoot, options);
561
- const written = options.write ? writeScanResult(scan, { area: options.area }) : null;
562
- const payload = {
563
- repositoryRoot: toPosixPath(scan.repositoryRoot),
564
- fileCount: scan.fileCount,
565
- truncated: scan.truncated,
566
- projectType: scan.projectType,
567
- tasksRoot: scan.manifest.setup.tasksRoot,
568
- stacks: scan.stacks,
569
- commands: scan.commands,
570
- sections: scan.manifest.sections,
571
- written,
572
- };
573
- if (options.json || options.write) {
286
+ const payload = { ...scan, repositoryRoot: toPosixPath(scan.repositoryRoot) };
287
+ if (options.json) {
574
288
  console.log(JSON.stringify(payload, null, 2));
575
289
  } else {
576
290
  console.log(`Oris Flow scan: ${payload.repositoryRoot}`);
577
291
  console.log(`Files scanned: ${payload.fileCount}${payload.truncated ? " (truncated)" : ""}`);
578
292
  console.log(`Project type: ${payload.projectType.label}`);
579
293
  console.log(`Stacks: ${payload.stacks.map((stack) => stack.label).join(", ") || "none"}`);
580
- console.log(`Sections: ${Object.keys(payload.sections).join(", ") || "none"}`);
294
+ console.log(`Commands: ${payload.commands.map((command) => command.command).join(", ") || "none"}`);
295
+ console.log(`Areas: ${Object.keys(payload.areas).join(", ") || "none"}`);
581
296
  }
582
297
  }
@@ -2,13 +2,9 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
 
4
4
  export const ORIS_FLOW_GITIGNORE_ENTRIES = [
5
- ".oris-flow/manifest.json",
6
- ".oris-flow/maps/",
5
+ ".oris-flow/",
7
6
  ];
8
7
 
9
- export const ORIS_FLOW_RUNTIME_GITIGNORE_ENTRY = ".oris-flow/runtime/";
10
- export const ORIS_LOOP_GITIGNORE_ENTRY = ORIS_FLOW_RUNTIME_GITIGNORE_ENTRY;
11
-
12
8
  function normalizeGitignoreLines(text) {
13
9
  return text.replace(/\r\n/g, "\n").split("\n");
14
10
  }
@@ -250,7 +250,7 @@ Tell any agent that can read files:
250
250
  > Read \`${bundledSkill}\` and follow it exactly.
251
251
 
252
252
  That file routes every Oris flow (setup, discover, criteria, plan, implement, fix,
253
- verify, change, loop, architecture, docs, help) in the same chat.
253
+ verify, change, architecture, docs, help) in the same chat.
254
254
  `, "utf8");
255
255
  }
256
256
 
@@ -291,7 +291,7 @@ function main() {
291
291
 
292
292
  fs.rmSync(options.bundleRoot, { recursive: true, force: true });
293
293
  fs.mkdirSync(options.bundleRoot, { recursive: true });
294
- for (const item of [".cursor-plugin", "skills", "references", "agents", "scripts", "docs", "package.json", "README.md", "LICENSE"]) {
294
+ for (const item of [".cursor-plugin", "skills", "references", "scripts", "docs", "package.json", "README.md", "LICENSE"]) {
295
295
  const source = path.join(repoRoot, item);
296
296
  if (fs.existsSync(source)) copy(source, path.join(options.bundleRoot, item));
297
297
  }
@@ -323,7 +323,7 @@ function main() {
323
323
  installedAt: new Date().toISOString(),
324
324
  });
325
325
  log(`Done for agents: ${agents.join(", ")}. Reload your agent environment.`);
326
- log("Next: type /oris-flow in your agent chat, or try the loop demo: npx oris-skills loop demo");
326
+ log("Next: type /oris-flow in your agent chat.");
327
327
  }
328
328
 
329
329
  try {
@@ -24,7 +24,6 @@ function parseArgs(argv) {
24
24
  const options = {
25
25
  dryRun: false,
26
26
  bundleRoot: path.join(home, ".oris", "oris-skills"),
27
- settingsPath: path.join(home, ".oris", "settings.json"),
28
27
  cursorRoot: path.join(home, ".cursor"),
29
28
  claudeRoot: path.join(home, ".claude"),
30
29
  codexRoot: path.join(home, ".codex"),
@@ -45,7 +44,6 @@ function parseArgs(argv) {
45
44
  };
46
45
  if (key === "--dry-run") options.dryRun = true;
47
46
  else if (key === "--bundle-root" || key.startsWith("--bundle-root=")) options.bundleRoot = value();
48
- else if (key === "--settings" || key.startsWith("--settings=")) options.settingsPath = value();
49
47
  else if (key === "--cursor-root" || key.startsWith("--cursor-root=")) options.cursorRoot = value();
50
48
  else if (key === "--claude-root" || key.startsWith("--claude-root=")) options.claudeRoot = value();
51
49
  else if (key === "--codex-root" || key.startsWith("--codex-root=")) options.codexRoot = value();
@@ -92,15 +90,6 @@ function managedCodexPrompts(codexRoot) {
92
90
  .map((entry) => path.join(promptsRoot, entry.name));
93
91
  }
94
92
 
95
- /** Loop agent adapters that pre-2.2.1 installers wrote into <agent home>/agents/. */
96
- function managedAgentAdapters(agentRoot, extension) {
97
- const agentsRoot = path.join(agentRoot, "agents");
98
- if (!fs.existsSync(agentsRoot)) return [];
99
- return fs.readdirSync(agentsRoot, { withFileTypes: true })
100
- .filter((entry) => entry.isFile() && entry.name.toLowerCase().startsWith("oris-") && entry.name.endsWith(extension))
101
- .map((entry) => path.join(agentsRoot, entry.name));
102
- }
103
-
104
93
  function managedCopilotPrompts(copilotRoot) {
105
94
  const promptsRoot = path.join(copilotRoot, "prompts");
106
95
  if (!fs.existsSync(promptsRoot)) return [];
@@ -109,18 +98,6 @@ function managedCopilotPrompts(copilotRoot) {
109
98
  .map((entry) => path.join(promptsRoot, entry.name));
110
99
  }
111
100
 
112
- function legacyOrisPaths(options) {
113
- return [
114
- path.join(options.cursorRoot, "oris-skills"),
115
- path.join(options.cursorRoot, "oris-flow"),
116
- path.join(options.cursorRoot, "oris-loop"),
117
- path.join(options.cursorRoot, "oris-skills.settings.json"),
118
- path.join(options.cursorRoot, ".cursor-plugin-install-backups"),
119
- path.join(options.cursorRoot, "plugins", "oris-skills"),
120
- options.settingsPath,
121
- ];
122
- }
123
-
124
101
  function plannedRemovals(options) {
125
102
  const paths = new Set([
126
103
  options.bundleRoot,
@@ -128,11 +105,7 @@ function plannedRemovals(options) {
128
105
  ...managedSkillDirs(path.join(options.claudeRoot, "skills")),
129
106
  ...managedCodexPrompts(options.codexRoot),
130
107
  ...managedCopilotPrompts(options.copilotRoot),
131
- ...managedAgentAdapters(options.cursorRoot, ".md"),
132
- ...managedAgentAdapters(options.claudeRoot, ".md"),
133
- ...managedAgentAdapters(options.codexRoot, ".toml"),
134
108
  path.join(path.dirname(options.bundleRoot), "oris-flow.md"),
135
- ...legacyOrisPaths(options),
136
109
  ]);
137
110
  return [...paths].map((target) => path.resolve(target)).filter(safeOrisPath).sort();
138
111
  }
@@ -153,7 +126,7 @@ function removeOrisMcpServers(userMcpPath, dryRun) {
153
126
 
154
127
  export function uninstallUserSkills(rawArgs = []) {
155
128
  const options = parseArgs(rawArgs);
156
- for (const target of [options.bundleRoot, options.settingsPath, options.cursorRoot, options.claudeRoot, options.codexRoot, options.copilotRoot]) {
129
+ for (const target of [options.bundleRoot, options.cursorRoot, options.claudeRoot, options.codexRoot, options.copilotRoot]) {
157
130
  assertInsideHome(target);
158
131
  }
159
132
 
@@ -161,7 +134,7 @@ export function uninstallUserSkills(rawArgs = []) {
161
134
  log(options.dryRun ? "Dry run uninstall plan:" : "Uninstalling Oris-owned assets:");
162
135
  for (const target of removals) log(`${options.dryRun ? "Would remove" : "Remove"} ${target}`);
163
136
 
164
- // Legacy cleanup: older releases merged an Oris MCP server into Cursor's user config.
137
+ // The installer seeds the Oris MCP server into Cursor's user config; remove it with the rest.
165
138
  const mcpResult = removeOrisMcpServers(path.join(options.cursorRoot, "mcp.json"), options.dryRun);
166
139
  if (mcpResult.changed) log(`${options.dryRun ? "Would update" : "Updated"} ${mcpResult.path}`);
167
140
 
@@ -4,7 +4,6 @@ import childProcess from "node:child_process";
4
4
  import path from "node:path";
5
5
  import process from "node:process";
6
6
  import { fileURLToPath } from "node:url";
7
- import { bootstrapLoop } from "./loop/oris-loop-bootstrap.mjs";
8
7
 
9
8
  const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
10
9
 
@@ -19,15 +18,6 @@ function runNode(script, args, options = {}) {
19
18
  process.exit(status);
20
19
  }
21
20
 
22
- function runGitRoot() {
23
- const result = childProcess.spawnSync("git", ["rev-parse", "--show-toplevel"], {
24
- cwd: process.cwd(),
25
- shell: process.platform === "win32",
26
- encoding: "utf8",
27
- });
28
- return result.status === 0 && result.stdout.trim() ? path.resolve(result.stdout.trim()) : process.cwd();
29
- }
30
-
31
21
  function printHelp() {
32
22
  console.log(`Oris Skills
33
23
 
@@ -36,24 +26,10 @@ Install for your agents (Cursor, Claude Code, Codex):
36
26
  oris-skills uninstall [--dry-run]
37
27
  oris-skills reinstall [--dry-run]
38
28
 
39
- Learn and test loops:
40
- oris-skills loop demo create a safe tutorial loop
41
- oris-skills loop dry-run --loop <slug> preview the next pass, run nothing
42
- oris-skills loop verify --temp self-check the loop runtime
43
-
44
- Run and manage loops:
45
- oris-skills loop list [--json]
46
- oris-skills loop bootstrap [--platforms cursor,claude]
47
- oris-skills loop chat --action start --loop <slug>
48
- oris-skills loop chat --action status|set-state|repair|stop [...]
49
- oris-skills loop run --loop <slug> --agent codex|claude headless runner
50
-
51
29
  Repository tools:
52
- oris-skills doctor
53
30
  oris-skills validate
54
31
  oris-skills flow scan [...]
55
32
  oris-skills flow version-control [...]
56
- oris-skills flow clean-runtime [...]
57
33
  `);
58
34
  }
59
35
 
@@ -79,37 +55,14 @@ if (command === "reinstall") {
79
55
  runNode("install/install-user-skills.mjs", ["--force", ...commandArgs]);
80
56
  }
81
57
 
82
- if (command === "doctor") {
83
- const root = runGitRoot();
84
- const result = bootstrapLoop(root, { dryRun: true });
85
- console.log(JSON.stringify({
86
- repositoryRoot: root,
87
- ready: result.ready,
88
- missing: result.before.missing,
89
- plannedActions: result.actions,
90
- }, null, 2));
91
- process.exit(result.ready ? 0 : 1);
92
- }
93
-
94
58
  if (command === "validate") {
95
59
  // Kept as an alias of the full test suite for older docs and muscle memory.
96
60
  runNode("tests/run-all-tests.mjs", []);
97
61
  }
98
62
 
99
- if (command === "loop") {
100
- if (subcommand === "list") runNode("loop/oris-loop-list.mjs", rest);
101
- if (subcommand === "bootstrap") runNode("loop/oris-loop-bootstrap.mjs", rest);
102
- if (subcommand === "chat") runNode("loop/oris-loop-chat.mjs", rest);
103
- if (subcommand === "verify") runNode("loop/oris-loop-verify.mjs", rest);
104
- if (subcommand === "demo") runNode("loop/oris-loop-demo.mjs", rest);
105
- if (subcommand === "dry-run") runNode("loop/oris-loop-dry-run.mjs", rest);
106
- if (subcommand === "run") runNode("loop/oris-loop-run.mjs", rest);
107
- }
108
-
109
63
  if (command === "flow") {
110
64
  if (subcommand === "scan") runNode("flow/oris-flow-scan.mjs", rest);
111
65
  if (subcommand === "version-control") runNode("flow/oris-flow-version-control.mjs", rest);
112
- if (subcommand === "clean-runtime") runNode("flow/oris-flow-clean-runtime.mjs", rest);
113
66
  }
114
67
 
115
68
  console.error(`Unknown command: ${[command, subcommand].filter(Boolean).join(" ")}`);
@@ -9,21 +9,13 @@ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", ".
9
9
 
10
10
  const nodeTestFiles = [
11
11
  "scripts/tests/test-schemas.mjs",
12
- "scripts/tests/test-oris-loop-document.mjs",
13
- "scripts/tests/test-agent-adapters.mjs",
14
12
  "scripts/tests/test-skill-self-contained.mjs",
15
- "scripts/tests/test-oris-1-0-cleanliness.mjs",
13
+ "scripts/tests/test-cleanliness.mjs",
16
14
  "scripts/tests/test-oris-flow-scan.mjs",
17
15
  "scripts/tests/test-oris-flow-devops.mjs",
18
16
  "scripts/tests/test-install-mcp.mjs",
19
17
  "scripts/tests/test-routing-lifecycle.mjs",
20
18
  "scripts/tests/test-skill-style.mjs",
21
- "scripts/tests/test-oris-loop-stop.mjs",
22
- "scripts/tests/test-oris-loop-smoke.mjs",
23
- "scripts/tests/test-oris-loop-bootstrap.mjs",
24
- "scripts/tests/test-oris-flow-clean-runtime.mjs",
25
- "scripts/tests/test-oris-loop-list.mjs",
26
- "scripts/tests/test-oris-loop-run.mjs",
27
19
  "scripts/tests/test-oris-gitignore.mjs",
28
20
  ];
29
21