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
@@ -0,0 +1,54 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import assert from "node:assert/strict";
4
+ import test from "node:test";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
8
+ const ignoredDirs = new Set([".git", "node_modules", ".cursor", ".claude", ".codex", "dist", ".cursor-plugin-install-backups", ".oris-flow"]);
9
+
10
+ function walk(dir, matches = []) {
11
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
12
+ if (ignoredDirs.has(entry.name)) continue;
13
+ const full = path.join(dir, entry.name);
14
+ if (entry.isDirectory()) walk(full, matches);
15
+ else matches.push(full);
16
+ }
17
+ return matches;
18
+ }
19
+
20
+ function repoFiles() {
21
+ return walk(repoRoot).map((filePath) => ({
22
+ filePath,
23
+ relative: path.relative(repoRoot, filePath).replace(/\\/g, "/"),
24
+ }));
25
+ }
26
+
27
+ test("repository contains no PowerShell scripts", () => {
28
+ const psFiles = repoFiles().filter((entry) => entry.relative.endsWith(".ps1"));
29
+ assert.deepEqual(psFiles.map((entry) => entry.relative), []);
30
+ });
31
+
32
+ // Machinery removed in 3.0: the loop runtime and the manifest/maps knowledge store.
33
+ // The agent-facing surface (skills, references, docs, README) must never point at it;
34
+ // CHANGELOG keeps the history, and the uninstaller/gitignore keep legacy-cleanup paths.
35
+ const removedMachinery = /oris-skills loop |scripts\/loop\/|\.oris-flow\/(?:loops|runtime|manifest\.json|maps|adapter\.json)|loop-contract|references\/repo-map|flow scan[^\n]*--(?:check|write|area)|flow clean-runtime/;
36
+
37
+ test("agent-facing text never references machinery removed in 3.0", () => {
38
+ const offenders = repoFiles()
39
+ .filter((entry) => /^(skills|references|docs)\/.*\.md$|^(README|AGENTS|CONTEXT)\.md$/.test(entry.relative))
40
+ .filter((entry) => removedMachinery.test(fs.readFileSync(entry.filePath, "utf8")))
41
+ .map((entry) => entry.relative);
42
+ assert.deepEqual(offenders, []);
43
+ });
44
+
45
+ test("production scripts never invoke machinery removed in 3.0", () => {
46
+ const offenders = repoFiles()
47
+ // scripts/tests assert absences; the uninstaller/gitignore clean legacy paths — both must name them.
48
+ .filter((entry) => /^scripts\/(flow|install)\/.*\.mjs$|^scripts\/[^/]+\.mjs$/.test(entry.relative))
49
+ .filter((entry) => entry.relative !== "scripts/install/uninstall-user-skills.mjs")
50
+ .filter((entry) => entry.relative !== "scripts/flow/oris-gitignore.mjs")
51
+ .filter((entry) => removedMachinery.test(fs.readFileSync(entry.filePath, "utf8")))
52
+ .map((entry) => entry.relative);
53
+ assert.deepEqual(offenders, []);
54
+ });
@@ -1,18 +1,16 @@
1
- import assert from "node:assert/strict";
1
+ import assert from "node:assert/strict";
2
2
  import fs from "node:fs";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
5
5
  import test from "node:test";
6
- import childProcess from "node:child_process";
7
- import { checkFreshness, scanRepository, writeScanResult } from "../flow/oris-flow-scan.mjs";
8
- import { ORIS_FLOW_MANIFEST } from "../flow/oris-flow-layout.mjs";
6
+ import { scanRepository } from "../flow/oris-flow-scan.mjs";
9
7
 
10
8
  function write(filePath, content) {
11
9
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
12
10
  fs.writeFileSync(filePath, content, "utf8");
13
11
  }
14
12
 
15
- test("scanner creates only evidence-backed map sections", () => {
13
+ test("scanner reports only evidence-backed areas and writes nothing", () => {
16
14
  const root = fs.mkdtempSync(path.join(os.tmpdir(), "oris-flow-scan-"));
17
15
  try {
18
16
  write(path.join(root, "README.md"), "# Demo\n");
@@ -20,17 +18,13 @@ test("scanner creates only evidence-backed map sections", () => {
20
18
  write(path.join(root, "src", "index.js"), "export const value = 1;\n");
21
19
 
22
20
  const scan = scanRepository(root);
23
- assert.ok(scan.manifest.sections.docs);
24
- assert.ok(scan.manifest.sections.stack);
25
- assert.ok(scan.manifest.sections.commands);
26
- assert.ok(scan.manifest.sections.code);
27
- assert.equal(scan.manifest.sections.backendData, undefined);
28
- assert.equal(scan.manifest["known" + "Gaps"], undefined);
21
+ assert.ok(scan.areas.docs);
22
+ assert.ok(scan.areas.code);
23
+ assert.ok(scan.stacks.some((stack) => stack.id === "node"));
24
+ assert.ok(scan.commands.some((command) => command.command === "npm run test"));
25
+ assert.equal(scan.areas.tests, undefined, "no test evidence → no tests area");
29
26
 
30
- const written = writeScanResult(scan);
31
- assert.equal(written.manifestPath, ORIS_FLOW_MANIFEST);
32
- assert.ok(fs.existsSync(path.join(root, ORIS_FLOW_MANIFEST)));
33
- assert.ok(fs.existsSync(path.join(root, ".oris-flow", "maps", "stack.md")));
27
+ assert.equal(fs.existsSync(path.join(root, ".oris-flow")), false, "scan never writes");
34
28
  } finally {
35
29
  fs.rmSync(root, { recursive: true, force: true });
36
30
  }
@@ -41,8 +35,9 @@ test("scanner supports documentation-only repositories", () => {
41
35
  try {
42
36
  write(path.join(root, "docs", "guide.md"), "# Guide\n");
43
37
  const scan = scanRepository(root);
44
- assert.deepEqual(Object.keys(scan.manifest.sections), ["docs"]);
38
+ assert.deepEqual(Object.keys(scan.areas), ["docs"]);
45
39
  assert.deepEqual(scan.stacks, []);
40
+ assert.deepEqual(scan.commands, []);
46
41
  } finally {
47
42
  fs.rmSync(root, { recursive: true, force: true });
48
43
  }
@@ -61,96 +56,31 @@ test("scanner detects project type and derives inferred stack commands", () => {
61
56
  const dotnetTest = scan.commands.find((command) => command.command.startsWith("dotnet test"));
62
57
  assert.ok(dotnetTest, "expected an inferred dotnet test command");
63
58
  assert.equal(dotnetTest.confidence, "inferred");
64
- assert.equal(scan.manifest.setup.tasksRoot, ".oris-flow/tasks");
65
59
  } finally {
66
60
  fs.rmSync(root, { recursive: true, force: true });
67
61
  }
68
62
  });
69
63
 
70
- test("refresh preserves confirmed sections, commands, and tasksRoot", () => {
64
+ test("scanner flags truncation instead of silently under-reporting", () => {
71
65
  const root = fs.mkdtempSync(path.join(os.tmpdir(), "oris-flow-scan-"));
72
66
  try {
73
- write(path.join(root, "README.md"), "# Demo\n");
74
- write(path.join(root, "package.json"), `${JSON.stringify({ scripts: { test: "node --test" } })}\n`);
75
-
76
- const first = scanRepository(root);
77
- first.manifest.setup.tasksRoot = "docs/tasks";
78
- first.manifest.setup.confirmedDecisions = ["tests live under scripts/tests"];
79
- first.manifest.sections.docs.confidence = "confirmed";
80
- first.manifest.commands[0].confidence = "confirmed";
81
- writeScanResult(first);
82
- const enriched = "# Docs\n\nUser-confirmed enrichment that must survive refresh.\n";
83
- write(path.join(root, ".oris-flow", "maps", "docs.md"), enriched);
84
-
85
- const second = scanRepository(root);
86
- assert.equal(second.manifest.setup.tasksRoot, "docs/tasks");
87
- assert.deepEqual(second.manifest.setup.confirmedDecisions, ["tests live under scripts/tests"]);
88
- assert.equal(second.manifest.sections.docs.confidence, "confirmed");
89
- assert.equal(second.manifest.sections.docs.stale, false);
90
- assert.equal(second.manifest.commands[0].confidence, "confirmed");
91
- assert.ok(second.preservedSections.has("docs"));
92
-
93
- writeScanResult(second);
94
- assert.equal(fs.readFileSync(path.join(root, ".oris-flow", "maps", "docs.md"), "utf8"), enriched);
67
+ for (let index = 0; index < 12; index += 1) {
68
+ write(path.join(root, "src", `file-${index}.js`), "export {};\n");
69
+ }
70
+ const scan = scanRepository(root, { maxFiles: 5 });
71
+ assert.equal(scan.truncated, true);
72
+ assert.ok(scan.fileCount <= 5);
95
73
  } finally {
96
74
  fs.rmSync(root, { recursive: true, force: true });
97
75
  }
98
76
  });
99
77
 
100
- test("confirmed section without fresh evidence is marked stale, not deleted", () => {
101
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "oris-flow-scan-"));
102
- try {
103
- write(path.join(root, "README.md"), "# Demo\n");
104
- const first = scanRepository(root);
105
- first.manifest.sections.docs.confidence = "confirmed";
106
- writeScanResult(first);
107
-
108
- fs.rmSync(path.join(root, "README.md"));
109
- const second = scanRepository(root);
110
- assert.equal(second.manifest.sections.docs.confidence, "confirmed");
111
- assert.equal(second.manifest.sections.docs.stale, true);
112
- } finally {
113
- fs.rmSync(root, { recursive: true, force: true });
114
- }
115
- });
116
-
117
-
118
- test("scan anchors the map to HEAD; --check answers freshness from the SHA + changed files", () => {
78
+ test("empty repository classifies as empty", () => {
119
79
  const root = fs.mkdtempSync(path.join(os.tmpdir(), "oris-flow-scan-"));
120
- const git = (...args) => childProcess.execFileSync("git", args, { cwd: root, encoding: "utf8" });
121
80
  try {
122
- write(path.join(root, "README.md"), "# Demo\n");
123
- write(path.join(root, "package.json"), `${JSON.stringify({ scripts: { test: "node --test" } })}\n`);
124
- git("init", "-q");
125
- git("-c", "user.email=t@t", "-c", "user.name=t", "add", ".");
126
- git("-c", "user.email=t@t", "-c", "user.name=t", "commit", "-q", "-m", "init");
127
-
128
- const scan = scanRepository(root);
129
- assert.equal(typeof scan.manifest.headSha, "string", "scan records the HEAD anchor");
130
- writeScanResult(scan);
131
-
132
- const fresh = checkFreshness(root);
133
- assert.equal(fresh.fresh, true, "same SHA + clean tree → fresh, zero reads");
134
- assert.deepEqual(fresh.changedFiles, []);
135
-
136
- write(path.join(root, "src", "new.js"), "export const later = 1;\n");
137
- const stale = checkFreshness(root);
138
- assert.equal(stale.fresh, false, "working-tree change → stale");
139
- assert.ok(stale.changedFiles.some((file) => file.includes("new.js")), "changed files name the delta");
140
- } finally {
141
- fs.rmSync(root, { recursive: true, force: true });
142
- }
143
- });
144
-
145
- test("outside git the map still works — no anchor, never an error", () => {
146
- const root = fs.mkdtempSync(path.join(os.tmpdir(), "oris-flow-scan-"));
147
- try {
148
- write(path.join(root, "README.md"), "# Demo\n");
149
81
  const scan = scanRepository(root);
150
- assert.equal(scan.manifest.headSha, null);
151
- writeScanResult(scan);
152
- const check = checkFreshness(root);
153
- assert.equal(check.fresh, false, "no anchor → refresh advised, not an error");
82
+ assert.equal(scan.projectType.id, "empty");
83
+ assert.deepEqual(scan.areas, {});
154
84
  } finally {
155
85
  fs.rmSync(root, { recursive: true, force: true });
156
86
  }
@@ -59,7 +59,8 @@ test("shared conventions centralize question, language, and gate rules", () => {
59
59
  assert.match(conventions, /`Continua l'intervista`/);
60
60
  assert.match(conventions, /`Approva documento`/);
61
61
  assert.match(conventions, /`Annulla`/);
62
- assert.match(conventions, /oris-flow-criteria.*writes first/i, "the criteria write-first exception must live where skills read it");
62
+ assert.match(conventions, /skills\/oris-flow\/references\/criteria\.md`.*writes first/i, "the criteria write-first exception must live where skills read it");
63
+ assert.doesNotMatch(conventions, /oris-flow-(criteria|implement|fix)/, "destinations are named by file path, never by prose nickname");
63
64
  assert.equal(exists("references/questions.md"), false, "questions.md was merged into conventions.md");
64
65
  });
65
66
 
@@ -72,58 +73,27 @@ test("every skill points at conventions instead of repeating shared rules", () =
72
73
  }
73
74
  });
74
75
 
75
- test("loop skill covers craft, demo, run, audit, improve and editable prompt files", () => {
76
- const skill = read("skills/oris-flow/references/loop.md");
77
- assert.match(skill, /references\/loop-craft\.md/);
78
- assert.match(skill, /references\/loop-run\.md/);
79
- assert.match(skill, /references\/loop-improve\.md/);
80
- assert.match(skill, /oris-skills loop demo/);
81
- assert.match(skill, /oris-skills loop dry-run/);
82
- assert.match(skill, /oris-skills loop list --json/);
83
- assert.match(skill, /prompts\//);
84
- assert.match(skill, /improve\.mode: auto/);
85
- assert.match(skill, /Cursor|Claude Code|Codex/);
86
- for (const reference of ["craft", "run", "improve"]) {
87
- assert.equal(exists(`skills/oris-flow/references/loop-${reference}.md`), true, reference);
88
- }
89
- for (const template of ["orchestrator", "executor", "verifier", "doctor", "debriefer"]) {
90
- assert.equal(exists(`skills/oris-flow/templates/${template}.md`), true, template);
91
- }
92
- });
93
-
94
- test("loop contract stays subagent-first with per-role editable prompts", () => {
95
- const contract = read("references/loop-contract.md");
96
- assert.match(contract, /schemaVersion: 2/);
97
- assert.match(contract, /approved: true/);
98
- assert.match(contract, /prompts\/orchestrator\.md/);
99
- assert.match(contract, /improve/);
100
- assert.match(contract, /inherit/);
101
- assert.match(contract, /NEVER success/i);
102
- const run = read("skills/oris-flow/references/loop-run.md");
103
- assert.match(run, /oris-skills loop chat --action start --loop <slug>/);
104
- assert.match(run, /NEVER do executor\/verifier\/doctor work in the main chat/i);
105
- assert.match(run, /set-state blocked/);
106
- assert.match(run, /oris-skills loop run --loop <slug> --agent codex\|claude/);
107
- const craft = read("skills/oris-flow/references/loop-craft.md");
108
- assert.match(craft, /skills\/oris-flow\/templates\//);
109
- assert.match(craft, /write the loop \| edit verifier \| edit executor \| edit doctor/);
110
- assert.match(contract, /ORIS LOOP ACTIVE/, "the contract defines the executor gate literal");
111
- for (const text of [contract, run, craft, read("skills/oris-flow/references/loop.md")]) {
112
- assert.doesNotMatch(text, /node scripts\/(?:flow|loop)\//);
113
- assert.doesNotMatch(text, /composer-2\.5/);
114
- }
115
- for (const text of [run, craft, read("skills/oris-flow/references/loop.md")]) {
116
- assert.doesNotMatch(text, /ORIS LOOP ACTIVE/, "the gate literal lives in the contract and executor prompts only");
76
+ test("the loop runtime is fully removed", () => {
77
+ for (const legacy of [
78
+ "scripts/loop",
79
+ "agents",
80
+ "skills/oris-flow/templates",
81
+ "skills/oris-flow/references/loop.md",
82
+ "references/loop-contract.md",
83
+ "references/repo-map.md",
84
+ ]) {
85
+ assert.equal(exists(legacy), false, `${legacy} must not exist`);
117
86
  }
87
+ const skill = read("skills/oris-flow/SKILL.md");
88
+ assert.doesNotMatch(skill, /\bloop\b/i, "the router must not mention loops");
118
89
  });
119
90
 
120
- test("setup and repo-map keep the installed CLI as the command surface", () => {
91
+ test("setup keeps the installed CLI as the command surface and AGENTS.md as the store", () => {
121
92
  const setup = read("skills/oris-flow/references/setup.md");
122
93
  assert.match(setup, /oris-skills flow scan --repository-root <repo> --json/);
123
- assert.match(setup, /oris-skills loop bootstrap --repository-root <repo>/);
124
- assert.match(setup, /oris-skills flow clean-runtime --repository-root <repo> --dry-run/);
125
- const repoMap = read("references/repo-map.md");
126
- assert.match(repoMap, /never `node scripts\/\.\.\.` paths/);
94
+ assert.match(setup, /oris-skills flow version-control --repository-root <repo> --mode commit\|gitignore/);
95
+ assert.doesNotMatch(setup, /node scripts\//, "always the installed CLI, never source paths");
96
+ assert.match(setup, /AGENTS\.md/);
127
97
  });
128
98
 
129
99
  test("no markdown references the pre-2.0 layout", () => {
@@ -165,18 +135,18 @@ test("uninstall and reinstall dry-runs show destructive plan before action", ()
165
135
  }
166
136
  });
167
137
 
168
- test("CLI exposes install, loop learnability, and flow helper commands", () => {
138
+ test("CLI exposes install and flow helper commands, and no loop surface", () => {
169
139
  const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), "oris-cli-help-"));
170
140
  try {
171
141
  const help = runCli(["help"], homeDir);
172
142
  assert.equal(help.status, 0, help.stderr);
173
143
  assert.match(help.stdout, /--agents cursor,claude,codex/);
174
- assert.match(help.stdout, /oris-skills loop demo/);
175
- assert.match(help.stdout, /oris-skills loop dry-run --loop <slug>/);
176
- assert.match(help.stdout, /oris-skills loop verify --temp/);
177
- assert.match(help.stdout, /oris-skills loop chat --action start --loop <slug>/);
178
- assert.match(help.stdout, /oris-skills loop run --loop <slug> --agent codex\|claude/);
179
144
  assert.match(help.stdout, /oris-skills flow scan/);
145
+ assert.match(help.stdout, /oris-skills flow version-control/);
146
+ assert.doesNotMatch(help.stdout, /loop/i);
147
+
148
+ const loop = runCli(["loop", "list"], homeDir);
149
+ assert.equal(loop.status, 1, "loop is an unknown command in 3.0");
180
150
  } finally {
181
151
  fs.rmSync(homeDir, { recursive: true, force: true });
182
152
  }
@@ -12,45 +12,31 @@ function read(relativePath) {
12
12
  return JSON.parse(fs.readFileSync(path.join(root, relativePath), "utf8"));
13
13
  }
14
14
 
15
- test("settings schema is the minimal v1 contract: only responseStyle and artifactLanguage", () => {
15
+ test("settings schema is the v1 contract: preferences only, no secrets, no profiles", () => {
16
16
  const schema = read("references/settings.schema.json");
17
17
  assert.equal(schema.properties.version.const, 1);
18
18
  assert.deepEqual(schema.required, ["version", "orisFlow"]);
19
- assert.ok(schema.properties.orisFlow.properties.responseStyle);
20
- assert.ok(schema.properties.orisFlow.properties.artifactLanguage);
21
- assert.ok(!schema.properties.defaultProfiles, "test profiles were removed from settings");
22
- assert.ok(!schema.properties.profiles, "test profiles were removed from settings");
23
- });
24
-
25
- test("adapter schema v1 declares paths, commands, model aliases, preflight (no test profiles)", () => {
26
- const schema = read("references/loop-adapter.schema.json");
27
- assert.equal(schema.$id, "https://oris.dev/schemas/oris-loop-adapter-v1.json");
28
- assert.equal(schema.properties.schemaVersion.const, 1);
19
+ const properties = schema.properties.orisFlow.properties;
29
20
  assert.deepEqual(
30
- schema.required,
31
- ["$schema", "schemaVersion", "repository", "paths", "commands", "preflight"],
21
+ Object.keys(properties).sort(),
22
+ ["artifactLanguage", "responseStyle", "tasksRoot", "versionControl"],
32
23
  );
33
- assert.ok(schema.properties.paths.properties.loops);
34
- assert.ok(schema.properties.models, "adapter schema must allow a model alias map");
35
- assert.ok(!schema.properties.profile, "test profiles were removed from the adapter");
24
+ assert.deepEqual(properties.versionControl.enum, ["commit", "gitignore"]);
25
+ assert.ok(!schema.properties.defaultProfiles, "test profiles were removed from settings");
26
+ assert.ok(!schema.properties.profiles, "test profiles were removed from settings");
36
27
  });
37
28
 
38
- test("loop document schema is v2 with approved gate, per-role models, and improve mode", () => {
39
- const schema = read("references/loop.schema.json");
40
- assert.equal(schema.$id, "https://oris.dev/schemas/oris-loop-v2.json");
41
- assert.equal(schema.properties.schemaVersion.const, 2);
42
- assert.ok(schema.required.includes("approved"));
43
- assert.ok(!schema.required.includes("runtime"), "runtime field was removed in v2");
44
- for (const role of ["default", "executor", "verifier", "doctor", "debriefer"]) {
45
- assert.ok(schema.properties.models.properties[role], `models.${role}`);
29
+ test("no loop or repo-map schemas survive the 3.0 cleanup", () => {
30
+ for (const legacy of [
31
+ "references/loop.schema.json",
32
+ "references/loop-adapter.schema.json",
33
+ "references/repo-map.schema.json",
34
+ ]) {
35
+ assert.equal(fs.existsSync(path.join(root, legacy)), false, `${legacy} must not exist`);
46
36
  }
47
- assert.deepEqual(schema.properties.improve.properties.mode.enum, ["propose", "auto"]);
48
- assert.equal(schema.$defs.model.type, "string", "models are free strings (inherit | alias | model id), not a hardcoded enum");
49
37
  });
50
38
 
51
- test("repository map schema v1 allows stack-aware optional sections", () => {
52
- const schema = read("references/repo-map.schema.json");
53
- assert.equal(schema.properties.schemaVersion.const, 1);
54
- assert.deepEqual(schema.required, ["$schema", "schemaVersion", "repository", "generatedAt", "updatedAt", "setup", "sections"]);
55
- assert.ok(schema.$defs.sections.additionalProperties);
39
+ test("devops schema stays the only other JSON contract", () => {
40
+ const schemas = fs.readdirSync(path.join(root, "references")).filter((name) => name.endsWith(".schema.json"));
41
+ assert.deepEqual(schemas.sort(), ["devops.schema.json", "settings.schema.json"]);
56
42
  });
@@ -36,8 +36,11 @@ test("skill style: oris-flow", () => {
36
36
  assert.match(front[1], /^name: oris-flow$/m, "front matter name matches the folder");
37
37
  const description = front[1].match(/^description: (.+)$/m)?.[1];
38
38
  assert.ok(description, "front matter has description");
39
- assert.match(description, /Use for/, "description names its triggers");
40
- assert.doesNotMatch(front[1], /disable-model-invocation/, "the single entry point must stay model-invocable");
39
+ assert.match(
40
+ front[1],
41
+ /^disable-model-invocation: true$/m,
42
+ "the entry point is user-invoked only (/oris-flow) — decision 2026-07-10, never auto-triggered",
43
+ );
41
44
  assert.ok(text.includes("## Never"), "carries an explicit Never block");
42
45
  assert.ok(text.includes("## Done when"), "ends with a Done when checklist");
43
46
  const lines = text.split(/\r?\n/).length;
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  name: oris-flow
3
- description: The one Oris skill - read the intent, pick ONE route, apply it in the same chat. Use for /oris-flow and whenever the user wants to set up a repo for agents, start a project, understand or define behavior, write acceptance criteria, plan, implement, fix a bug, verify criteria, handle a spec change, run a work loop (fix until green, repeat until verified), review architecture, update task docs, commit changes, open a pull request, or asks how Oris works.
3
+ description: The one Oris skill - read the intent, pick ONE route, apply it in the same chat. Routes setup, new, triage, discover, criteria, plan, implement, fix, verify, change, architecture, docs, commit, pr, handoff, and help.
4
+ disable-model-invocation: true
4
5
  ---
5
6
 
6
7
  # Oris Flow — the router
@@ -22,6 +23,7 @@ Match the desired outcome to ONE route, then apply its reference:
22
23
  |-------|----------------------|-------|
23
24
  | setup | prepare / map / refresh an existing repo for Oris and agents | `skills/oris-flow/references/setup.md` |
24
25
  | new | bootstrap a project from scratch, greenfield, empty directory | `skills/oris-flow/references/new.md` |
26
+ | triage | capture an idea, bug report, or request as a work item with an agent-ready brief | `skills/oris-flow/references/triage.md` |
25
27
  | discover | understand or define product behavior, functional analysis | `skills/oris-flow/references/discover.md` |
26
28
  | criteria | acceptance criteria, QA checks, testable scenarios | `skills/oris-flow/references/criteria.md` |
27
29
  | plan | a technical implementation plan before coding | `skills/oris-flow/references/plan.md` |
@@ -29,11 +31,11 @@ Match the desired outcome to ONE route, then apply its reference:
29
31
  | fix | broken behavior, a regression, a failed QA check | `skills/oris-flow/references/fix.md` |
30
32
  | verify | check acceptance criteria against the real product, once | `skills/oris-flow/references/verify.md` |
31
33
  | change | the spec changed — update analysis, criteria, plan, and history together | `skills/oris-flow/references/change.md` |
32
- | loop | repeat work until verified — fix until green, step through a plan | `skills/oris-flow/references/loop.md` |
33
34
  | architecture | review architecture, find deepening/refactor opportunities | `skills/oris-flow/references/architecture.md` |
34
35
  | docs | update task docs after a change | `skills/oris-flow/references/docs.md` |
35
- | commit | commit changes with the Oris standard — map fresh, Conventional Commits, work item linked | `skills/oris-flow/references/commit.md` |
36
+ | commit | commit changes with the Oris standard — Conventional Commits, work item linked | `skills/oris-flow/references/commit.md` |
36
37
  | pr | open or update a pull request, English title/description linked to the work item | `skills/oris-flow/references/pr.md` |
38
+ | handoff | package session state — decisions, evidence, next move — to resume in a fresh session | `skills/oris-flow/references/handoff.md` |
37
39
  | help | how Oris works, install, update, troubleshooting | `skills/oris-flow/references/help.md` |
38
40
 
39
41
  ## Precedence (the calls that look ambiguous)
@@ -43,14 +45,18 @@ Match the desired outcome to ONE route, then apply its reference:
43
45
  2. RETHINK beats build: analyze, question, or reshape existing work →
44
46
  route to the interview owner (discover / plan / change), NEVER straight to implement.
45
47
  3. CHANGED spec on a feature that already has task docs or code → change, not discover —
46
- change owns the delta and the history.
48
+ change owns the delta and the history. Spec unchanged but code/reality moved on and the
49
+ task docs lag behind → docs, not change.
47
50
  4. BROKEN behavior → fix. NEW planned work → implement. The menu answer "Implement or
48
51
  fix" gets exactly one follow-up question to split them.
49
- 5. CHECK onceverify. Repeat-until-verified loop.
50
- 6. EMPTY directory or from-scratch intent new. Existing repo without
51
- `.oris-flow/manifest.json`recommend setup first; respect the user's choice.
52
- 7. COMMIT staged/working changes with the standard → commit. OPEN or update a pull request
52
+ 5. EMPTY directory or from-scratch intent new. Existing repo never touched by Oris
53
+ (no `AGENTS.md`, no `.oris-flow/`) recommend setup first; respect the user's choice.
54
+ 6. COMMIT staged/working changes with the standard commit. OPEN or update a pull request
53
55
  → pr. Neither writes product code — they package and publish what implement/fix produced.
56
+ 7. CAPTURE work for later (an idea, a report, backlog intake) → triage — it creates the
57
+ work item and stops. DOING the work now → discover / fix / implement.
58
+ 8. CONTINUE elsewhere — session ending, context full, "pass this to another agent" →
59
+ handoff.
54
60
 
55
61
  ## Menu (low confidence)
56
62
 
@@ -60,22 +66,26 @@ fallback in `references/conventions.md` `## Questions`), options:
60
66
 
61
67
  - Setup repository
62
68
  - New project from scratch
69
+ - Capture an idea or bug as a work item (triage)
63
70
  - Understand / define behavior (discovery)
64
71
  - Acceptance criteria
65
72
  - Technical plan
66
73
  - Implement or fix
67
74
  - Verify acceptance criteria
68
75
  - Spec changed: update feature docs
69
- - Loop: repeat until verified
70
76
  - Architecture review
71
77
  - Update docs
72
78
  - Commit changes
73
79
  - Open a pull request
80
+ - Hand off this session (continue elsewhere)
74
81
  - Help
75
82
  - Explain the options / Spiega le opzioni
76
83
 
77
- IF "Explain the options" → explain briefly, re-ask the same question.
78
- Every other label maps 1:1 to its route in the table above.
84
+ IF "Explain the options" → ONE message that contains BOTH: one explanation line per
85
+ option, THEN the full lettered list again below the explanations. Re-asking without the
86
+ explanations visible in that same message is the failure this rule exists to prevent.
87
+ Every other label maps to its route in the table above; "Implement or fix" is the one
88
+ label that splits, with exactly one follow-up question (Precedence 4).
79
89
 
80
90
  ## Never
81
91
 
@@ -1,4 +1,4 @@
1
1
  interface:
2
2
  display_name: "Oris Flow"
3
3
  short_description: "Start the Oris workflow menu"
4
- default_prompt: "Use $oris-flow to choose the right Oris workflow command."
4
+ default_prompt: "Use $oris-flow to enter the right Oris workflow."
@@ -22,7 +22,7 @@ routes to plan/implement.
22
22
 
23
23
  ## 1. Explore
24
24
 
25
- 1. READ first: setup map, `CONTEXT.md` (domain names for good seams), `docs/adr/`
25
+ 1. READ first: `AGENTS.md`, `CONTEXT.md` (domain names for good seams), `docs/adr/`
26
26
  (decisions NOT to re-litigate).
27
27
  2. WALK the codebase with parallel subagents. No rigid heuristics — note real friction:
28
28
  - understanding one concept requires bouncing between many small modules;
@@ -37,10 +37,13 @@ WRITE a self-contained HTML file to the OS temp dir (`architecture-review-<times
37
37
  OPEN it (`start` | `open` | `xdg-open`), TELL the absolute path.
38
38
 
39
39
  FORMAT: Tailwind CDN + Mermaid CDN. Per candidate, one card:
40
- - title (names the deepening) + strength badge: `Strong` | `Worth exploring` | `Speculative`;
40
+ - title (names the deepening) + strength badge `Strong` (friction observed in the code,
41
+ fix shape clear) | `Worth exploring` (friction observed, fix shape uncertain) |
42
+ `Speculative` (pattern suggests it, not yet observed);
41
43
  - files (monospaced), Problem (one sentence), Solution (one sentence);
42
44
  - Before/After diagram side by side — Mermaid for graph-shaped, hand-built divs/SVG for
43
- mass diagrams; if the diagram needs a paragraph, redraw the diagram;
45
+ mass diagrams; the diagram must explain itself without a caption if it needs one,
46
+ simplify the diagram;
44
47
  - wins as bullets ≤6 words, glossary terms only — never "cleaner code";
45
48
  - ADR conflict → amber callout ("contradicts ADR-0007 — worth reopening because…").
46
49
 
@@ -3,6 +3,9 @@
3
3
  Specs changed while the feature exists (documented, planned, or half-built). Understand
4
4
  the delta, update EVERYTHING together, keep the history traceable.
5
5
 
6
+ BOUNDARY: the business spec MOVED → this route. Spec unchanged but code/reality moved on
7
+ and the docs lag behind → `skills/oris-flow/references/docs.md`.
8
+
6
9
  RULES: `references/conventions.md`. Doc standards: `references/doc-policy.md`.
7
10
 
8
11
  ## Delta interview
@@ -1,15 +1,16 @@
1
1
  # Commit
2
2
 
3
- Turn staged or working changes into one clean commit — map fresh, message in English,
4
- work item linked. NEVER commit without showing the message and getting a yes.
3
+ Turn staged or working changes into one clean commit — message in English, work item
4
+ linked. NEVER commit without showing the message and getting a yes.
5
5
 
6
6
  RULES: `references/conventions.md`. DevOps config: `references/devops.md`.
7
7
 
8
8
  ## Before the commit
9
9
 
10
- 1. FRESHEN the map: `npx oris-skills flow scan --repository-root <repo> --check`. Stale
11
- refresh ONLY the changed areas (`--area <section>`) and stage `.oris-flow/` with the change.
12
- 2. READ the diff (`git status`, `git diff --staged`) to know what actually changed.
10
+ 1. READ the diff (`git status`, `git diff --staged`) to know what actually changed.
11
+ 2. IF the diff proved a durable fact `AGENTS.md` lacks or contradicts (a new command, a
12
+ moved area) correct `AGENTS.md` and stage it with the change (`references/conventions.md`
13
+ `## Project facts`).
13
14
  3. RESOLVE the work item id from context — current branch, the active task doc, or the
14
15
  DevOps provider's active items. Unsure → ASK once for the id; never invent one.
15
16
 
@@ -23,7 +24,7 @@ RULES: `references/conventions.md`. DevOps config: `references/devops.md`.
23
24
 
24
25
  ## Gate (never skipped)
25
26
 
26
- SHOW the full message + the files it will commit (code + refreshed map). ASK:
27
+ SHOW the full message + the files it will commit. ASK:
27
28
  commit | edit message | cancel. ON commit → `git commit`; NEVER `--no-verify`.
28
29
 
29
30
  ## After
@@ -36,10 +37,10 @@ commit | edit message | cancel. ON commit → `git commit`; NEVER `--no-verify`.
36
37
  - Commit before the message and file list are shown and confirmed.
37
38
  - Write the message in any language but English.
38
39
  - Invent a work item id, or bypass hooks with `--no-verify`.
39
- - Refresh confirmed map sections, or scan-rewrite areas the diff did not touch.
40
+ - Touch `AGENTS.md` sections the diff did not contradict.
40
41
 
41
42
  ## Done when
42
43
 
43
- - [ ] Map checked, and refreshed in place only where the diff touched.
44
+ - [ ] Diff read; `AGENTS.md` corrected only where the diff proved it wrong.
44
45
  - [ ] English Conventional message with the work item linked, confirmed at the gate.
45
46
  - [ ] Commit made without `--no-verify`; next step offered, not auto-run.
@@ -1,8 +1,8 @@
1
1
  # Acceptance Criteria
2
2
 
3
3
  Generate verifiable criteria grounded in observable product behavior — UI, API response,
4
- CLI output, or a visible system effect. They later feed verify-route checks and
5
- loop VERIFIER prompts — precision here pays twice.
4
+ CLI output, or a visible system effect. They are the exact checklist the verify route
5
+ executes — precision here pays twice.
6
6
 
7
7
  RULES: `references/conventions.md`. Doc standards: `references/doc-policy.md`.
8
8
 
@@ -12,9 +12,13 @@ RULES: `references/conventions.md`. Doc standards: `references/doc-policy.md`.
12
12
  preferred, never required — say once when missing) > current UI > code and tests.
13
13
  2. DEDUCE create vs update: existing `acceptance-criteria.md` → update, PRESERVE IDs.
14
14
  3. IDs: `AC-001`, `AC-002`, … stable across revisions.
15
- 4. Each scenario declares ALL of: role + starting state · deterministic data · numbered
16
- user steps · observable outcome under the step that causes it · negative coverage ·
17
- regressions to keep.
15
+ 4. Each scenario declares ALL of:
16
+ - role + starting state;
17
+ - deterministic data;
18
+ - numbered user steps;
19
+ - observable outcome under the step that causes it;
20
+ - negative coverage;
21
+ - regressions to keep.
18
22
  5. CLASSIFY each: `Verifiable` | `Needs setup/data` | `Blocked by Open QA`.
19
23
  6. DISCARD criteria not checkable through observable product behavior. Unbuilt features:
20
24
  only confirmed intended behavior.
@@ -1,7 +1,7 @@
1
1
  # Discovery
2
2
 
3
3
  Interview the stakeholder until the desired business behavior is clear enough to
4
- document. The cornerstone of the flow: everything downstream (criteria, plan, loops)
4
+ document. The cornerstone of the flow: everything downstream (criteria, plan, implement)
5
5
  builds on this.
6
6
 
7
7
  RULES: `references/conventions.md`. Doc standards: `references/doc-policy.md`.
@@ -9,7 +9,7 @@ RULES: `references/conventions.md`. Doc standards: `references/doc-policy.md`.
9
9
  ## Before asking
10
10
 
11
11
  1. EXPLORE first: task folder (`{tasksRoot}/{task}/`), existing task docs, project docs,
12
- setup map, code evidence, DevOps context when available.
12
+ `AGENTS.md`, code evidence, DevOps context when available.
13
13
  2. DEDUCE create vs update: existing `functional-analysis.md` → update; missing → create.
14
14
  Ask only on contradictory signals.
15
15
  3. Technical evidence = private input. TRANSLATE it to business language; never show file