@azure-id/orc 1.6.0 → 1.7.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 (69) hide show
  1. package/CHANGELOG.md +110 -0
  2. package/README-id.md +206 -125
  3. package/README.md +274 -74
  4. package/bin/cli.js +1595 -2
  5. package/bin/mockrun-catalog.js +272 -267
  6. package/bin/verify-contracts.js +4527 -4509
  7. package/bin/verify-package.js +6 -0
  8. package/bin/webui/api.js +26 -0
  9. package/bin/webui/app.html +10 -0
  10. package/bin/webui/css/panels/knowledge.css +30 -0
  11. package/bin/webui/css/panels/rules.css +145 -0
  12. package/bin/webui/fixtures/index.js +27 -0
  13. package/bin/webui/fixtures/rules.js +1002 -0
  14. package/bin/webui/fixtures/wiki-one.js +198 -0
  15. package/bin/webui/i18n/en/knowledge.json +127 -117
  16. package/bin/webui/i18n/en/nav.json +1 -0
  17. package/bin/webui/i18n/en/rules.json +55 -0
  18. package/bin/webui/i18n/id/knowledge.json +127 -117
  19. package/bin/webui/i18n/id/nav.json +1 -0
  20. package/bin/webui/i18n/id/rules.json +55 -0
  21. package/bin/webui/js/01-i18n.js +1 -0
  22. package/bin/webui/js/panels/knowledge.js +117 -1
  23. package/bin/webui/js/panels/rules.js +351 -0
  24. package/mock-run/INDEX.md +107 -106
  25. package/mock-run/orc-rules.md +254 -0
  26. package/package.json +1 -1
  27. package/templates/commands/orc-rules.md +66 -0
  28. package/templates/commands/orc-wiki.md +23 -1
  29. package/templates/skills/_shared/README.md +61 -54
  30. package/templates/skills/_shared/phases/README.md +1 -0
  31. package/templates/skills/_shared/phases/preflight.md +20 -0
  32. package/templates/skills/_shared/phases/rules.md +154 -0
  33. package/templates/skills/_shared/return-validation.md +28 -0
  34. package/templates/skills/_shared/rules/CREDITS.md +141 -0
  35. package/templates/skills/_shared/rules/INDEX.md +107 -0
  36. package/templates/skills/_shared/rules/code.md +229 -0
  37. package/templates/skills/_shared/rules/delivery.md +114 -0
  38. package/templates/skills/_shared/rules/ui.md +139 -0
  39. package/templates/skills/_shared/rules/writing.md +223 -0
  40. package/templates/skills/context-combiner/SKILL.md +9 -1
  41. package/templates/skills/orc/SKILL.md +7 -0
  42. package/templates/skills/orc-aftermath/SKILL.md +7 -0
  43. package/templates/skills/orc-analyze/SKILL.md +7 -0
  44. package/templates/skills/orc-analyze-mini/SKILL.md +9 -1
  45. package/templates/skills/orc-boundary/SKILL.md +7 -0
  46. package/templates/skills/orc-brainstorm/SKILL.md +7 -0
  47. package/templates/skills/orc-budget/SKILL.md +7 -0
  48. package/templates/skills/orc-challenge/SKILL.md +7 -0
  49. package/templates/skills/orc-claude/SKILL.md +7 -0
  50. package/templates/skills/orc-diy/SKILL.md +7 -0
  51. package/templates/skills/orc-explain/SKILL.md +7 -0
  52. package/templates/skills/orc-export/SKILL.md +7 -0
  53. package/templates/skills/orc-fast/SKILL.md +7 -0
  54. package/templates/skills/orc-grill/SKILL.md +7 -0
  55. package/templates/skills/orc-handoff/SKILL.md +9 -1
  56. package/templates/skills/orc-learn/SKILL.md +7 -0
  57. package/templates/skills/orc-mini/SKILL.md +7 -0
  58. package/templates/skills/orc-pact/SKILL.md +7 -0
  59. package/templates/skills/orc-poly/SKILL.md +7 -0
  60. package/templates/skills/orc-pr-driver/SKILL.md +9 -1
  61. package/templates/skills/orc-pr-setup/SKILL.md +9 -1
  62. package/templates/skills/orc-quick/SKILL.md +7 -0
  63. package/templates/skills/orc-retro/SKILL.md +7 -0
  64. package/templates/skills/orc-route/SKILL.md +7 -0
  65. package/templates/skills/orc-test/SKILL.md +7 -0
  66. package/templates/skills/orc-verify/SKILL.md +7 -0
  67. package/templates/skills/orc-wiki/SKILL.md +15 -0
  68. package/templates/skills/orc-wiki/references/partial-refresh.md +68 -0
  69. package/templates/skills/orc-wiki/references/phases/phase-0.md +15 -1
@@ -1,271 +1,276 @@
1
- "use strict";
2
- /**
3
- * mockrun-catalog.js — the catalogue of MOCKED RUNS that ship with ORC.
4
- *
5
- * A mocked run is a written walkthrough of ONE lane: what you type, what ORC
6
- * prints back, what lands on disk. Nothing was executed to produce them — they
7
- * are documentation — and that is exactly why they exist: a new user needs to
8
- * know what a lane LOOKS like before paying tokens to find out.
9
- *
10
- * Two sources, one catalogue:
11
- * · `mock-run/*.md` the reader-facing walkthroughs
12
- * · `templates/skills/<skill>/examples/*` the dense annotated examples that
13
- * already shipped with each skill
14
- *
15
- * DERIVED, never hand-listed. A new file in either place appears by itself:
16
- * the title comes from the file's own `# ` heading, the lane from whether
17
- * `templates/commands/<name>.md` exists, and the one-line summary from the
18
- * `> ` blockquote the walkthroughs open with. The only hand-written table here
19
- * is the READING ORDER (`GROUPS` + `GROUP_OF`) — an order is editorial and
20
- * cannot be derived from a filename. A file nobody placed still shows up, in
21
- * the `other` group, because a mocked run that exists and is invisible is the
22
- * failure mode this module is meant to make impossible.
23
- *
24
- * This is package content, not project state: it is the same on every machine
25
- * and needs no `.claude/`. `orc mock-run` reads it, and `orc ui` serves it the
26
- * same way it serves the onboarding walkthrough — straight from the module,
27
- * because spawning a subprocess to read files that ship in this package would
28
- * be ceremony with a cost. It is NOT `orc mock`, which lists the runnable
29
- * `mock-examples/<slug>/` folders a green verify left in YOUR project.
30
- */
31
-
32
- const fs = require("fs");
33
- const path = require("path");
34
-
35
- const ROOT = path.join(__dirname, "..");
36
- const WALKTHROUGH_DIR = path.join(ROOT, "mock-run");
37
- const SKILLS_DIR = path.join(ROOT, "templates", "skills");
38
- const COMMANDS_DIR = path.join(ROOT, "templates", "commands");
39
-
40
- // The reading order. Group titles are DATA (the panel prints them as written
41
- // and never translates them, exactly like a config key or a doctor message).
42
- const GROUPS = [
43
- { id: "start", title: "Start here" },
44
- { id: "build", title: "Build a change" },
45
- { id: "decide", title: "Decide what to build" },
46
- { id: "knowledge", title: "Teach ORC your project" },
47
- { id: "check", title: "Check what happened" },
48
- { id: "ship", title: "Ship and hand over" },
49
- { id: "tools", title: "Panel and terminal" },
50
- { id: "other", title: "More" },
51
- ];
52
-
53
- // slug (or lane name) → group. Anything absent lands in `other`.
54
- const GROUP_OF = {
55
- "the-example-project": "start",
56
- "a-normal-day": "start",
57
-
58
- orc: "build",
59
- "orc-ultra": "build",
60
- "orc-mini": "build",
61
- "orc-fast": "build",
62
- "orc-quick": "build",
63
- "orc-diy": "build",
64
-
65
- // v0.48.0. It sits with the deciding lanes rather than the knowledge ones: a
66
- // /orc-doc document is derived from what you DECIDED, not from the code —
67
- // which is exactly what separates it from /orc-wiki and /orc-learn.
68
- "orc-doc": "decide",
69
-
70
- "orc-brainstorm": "decide",
71
- "orc-grill": "decide",
72
- "orc-plan": "decide",
73
- "orc-route": "decide",
74
- "orc-explain": "decide",
75
- "orc-analyze": "decide",
76
- "orc-analyze-mini": "decide",
77
- "context-combiner": "decide",
78
- "orc-poly": "decide",
79
-
80
- // v0.50.0. It sits with the TOOLS rather than the build lanes: `orc extra` is
81
- // not a lane you invoke — it is a setting that changes where the lanes you
82
- // already run send their work.
83
- "orc-extra": "tools",
84
- // v0.54.0. It sits beside `orc-extra` for the same reason: recovering a
85
- // half-finished foreign dispatch is a thing you do at the terminal and in the
86
- // panel, not a lane you invoke.
87
- "extra-recovery": "tools",
88
- // v0.55.0. Same shelf again: the four lanes that pin an agent instead of
89
- // scoring a task are a routing decision, and routing is where `orc-extra`
90
- // already lives.
91
- "extra-slots": "tools",
92
-
93
- "orc-wiki": "knowledge",
94
- "orc-pattern": "knowledge",
95
- "orc-learn": "knowledge",
96
- "orc-claude": "knowledge",
97
- "orc-export": "knowledge",
98
-
1
+ "use strict";
2
+ /**
3
+ * mockrun-catalog.js — the catalogue of MOCKED RUNS that ship with ORC.
4
+ *
5
+ * A mocked run is a written walkthrough of ONE lane: what you type, what ORC
6
+ * prints back, what lands on disk. Nothing was executed to produce them — they
7
+ * are documentation — and that is exactly why they exist: a new user needs to
8
+ * know what a lane LOOKS like before paying tokens to find out.
9
+ *
10
+ * Two sources, one catalogue:
11
+ * · `mock-run/*.md` the reader-facing walkthroughs
12
+ * · `templates/skills/<skill>/examples/*` the dense annotated examples that
13
+ * already shipped with each skill
14
+ *
15
+ * DERIVED, never hand-listed. A new file in either place appears by itself:
16
+ * the title comes from the file's own `# ` heading, the lane from whether
17
+ * `templates/commands/<name>.md` exists, and the one-line summary from the
18
+ * `> ` blockquote the walkthroughs open with. The only hand-written table here
19
+ * is the READING ORDER (`GROUPS` + `GROUP_OF`) — an order is editorial and
20
+ * cannot be derived from a filename. A file nobody placed still shows up, in
21
+ * the `other` group, because a mocked run that exists and is invisible is the
22
+ * failure mode this module is meant to make impossible.
23
+ *
24
+ * This is package content, not project state: it is the same on every machine
25
+ * and needs no `.claude/`. `orc mock-run` reads it, and `orc ui` serves it the
26
+ * same way it serves the onboarding walkthrough — straight from the module,
27
+ * because spawning a subprocess to read files that ship in this package would
28
+ * be ceremony with a cost. It is NOT `orc mock`, which lists the runnable
29
+ * `mock-examples/<slug>/` folders a green verify left in YOUR project.
30
+ */
31
+
32
+ const fs = require("fs");
33
+ const path = require("path");
34
+
35
+ const ROOT = path.join(__dirname, "..");
36
+ const WALKTHROUGH_DIR = path.join(ROOT, "mock-run");
37
+ const SKILLS_DIR = path.join(ROOT, "templates", "skills");
38
+ const COMMANDS_DIR = path.join(ROOT, "templates", "commands");
39
+
40
+ // The reading order. Group titles are DATA (the panel prints them as written
41
+ // and never translates them, exactly like a config key or a doctor message).
42
+ const GROUPS = [
43
+ { id: "start", title: "Start here" },
44
+ { id: "build", title: "Build a change" },
45
+ { id: "decide", title: "Decide what to build" },
46
+ { id: "knowledge", title: "Teach ORC your project" },
47
+ { id: "check", title: "Check what happened" },
48
+ { id: "ship", title: "Ship and hand over" },
49
+ { id: "tools", title: "Panel and terminal" },
50
+ { id: "other", title: "More" },
51
+ ];
52
+
53
+ // slug (or lane name) → group. Anything absent lands in `other`.
54
+ const GROUP_OF = {
55
+ "the-example-project": "start",
56
+ "a-normal-day": "start",
57
+
58
+ orc: "build",
59
+ "orc-ultra": "build",
60
+ "orc-mini": "build",
61
+ "orc-fast": "build",
62
+ "orc-quick": "build",
63
+ "orc-diy": "build",
64
+
65
+ // v0.48.0. It sits with the deciding lanes rather than the knowledge ones: a
66
+ // /orc-doc document is derived from what you DECIDED, not from the code —
67
+ // which is exactly what separates it from /orc-wiki and /orc-learn.
68
+ "orc-doc": "decide",
69
+
70
+ "orc-brainstorm": "decide",
71
+ "orc-grill": "decide",
72
+ "orc-plan": "decide",
73
+ "orc-route": "decide",
74
+ "orc-explain": "decide",
75
+ "orc-analyze": "decide",
76
+ "orc-analyze-mini": "decide",
77
+ "context-combiner": "decide",
78
+ "orc-poly": "decide",
79
+
80
+ // v0.50.0. It sits with the TOOLS rather than the build lanes: `orc extra` is
81
+ // not a lane you invoke — it is a setting that changes where the lanes you
82
+ // already run send their work.
83
+ "orc-extra": "tools",
84
+ // v0.54.0. It sits beside `orc-extra` for the same reason: recovering a
85
+ // half-finished foreign dispatch is a thing you do at the terminal and in the
86
+ // panel, not a lane you invoke.
87
+ "extra-recovery": "tools",
88
+ // v0.55.0. Same shelf again: the four lanes that pin an agent instead of
89
+ // scoring a task are a routing decision, and routing is where `orc-extra`
90
+ // already lives.
91
+ "extra-slots": "tools",
92
+
93
+ // v1.7.0. It sits with the KNOWLEDGE lanes rather than the tools: a rule set
94
+ // is something you teach ORC about this project, the same shelf as the wiki
95
+ // and the code patterns — and like both of those it is read on every run.
96
+ "orc-rules": "knowledge",
97
+
98
+ "orc-wiki": "knowledge",
99
+ "orc-pattern": "knowledge",
100
+ "orc-learn": "knowledge",
101
+ "orc-claude": "knowledge",
102
+ "orc-export": "knowledge",
103
+
99
104
  // v1.5.0. It sits with the CHECKING lanes rather than the build ones:
100
105
  // /orc-test makes nothing. It runs the requests, writes down what came back,
101
106
  // and hands the machine back — the same shape as /orc-verify beside it.
102
107
  "orc-test": "check",
103
- "orc-verify": "check",
104
- "orc-challenge": "check",
105
- // v0.49.1. It sits beside the lane it belongs to, and it is a SEPARATE
106
- // walkthrough because the council is a decision the reader takes before the
107
- // first review runs — not a detail of one.
108
- "orc-challenge-council": "check",
109
- "orc-pact": "check",
110
- "orc-boundary": "check",
111
- "orc-aftermath": "check",
112
- "orc-budget": "check",
113
- "orc-retro": "check",
114
-
115
- "orc-pr-setup": "ship",
116
- "orc-pr-driver": "ship",
117
- "orc-handoff": "ship",
118
-
119
- "orc-ui": "tools",
120
- "orc-cli": "tools",
121
- };
122
-
123
- // A few docs lead their group because they are prerequisites for reading the
124
- // rest, which alphabetical order cannot know. Everything else sorts by title.
125
- const PINNED_FIRST = ["the-example-project", "orc", "orc-brainstorm", "orc-wiki-example", "orc-verify-example"];
126
-
127
- // Files in mock-run/ that are not themselves a mocked run.
128
- const NOT_A_DOC = new Set(["INDEX.md", "README.md"]);
129
-
130
- function readSafe(p) {
131
- try {
132
- return fs.readFileSync(p, "utf8");
133
- } catch (_) {
134
- return null;
135
- }
136
- }
137
-
138
- function listSafe(dir) {
139
- try {
140
- return fs.readdirSync(dir, { withFileTypes: true });
141
- } catch (_) {
142
- return [];
143
- }
144
- }
145
-
146
- // `# Mock run — \`/orc-pact\`` → `Mock run — /orc-pact`. Backticks are markup
147
- // for a reader, not part of the title.
148
- function titleOf(md, fallback) {
149
- const m = String(md || "").match(/^#\s+(.+?)\s*$/m);
150
- return m ? m[1].replace(/`/g, "").trim() : fallback;
151
- }
152
-
153
- // The walkthroughs open with a `> one line` blockquote under the title. The
154
- // older annotated examples do not, so the first real line stands in.
155
- function summaryOf(md) {
156
- const lines = String(md || "").split(/\r?\n/);
157
- let seenTitle = false;
158
- for (const raw of lines) {
159
- const line = raw.trim();
160
- if (!line) continue;
161
- if (line.startsWith("# ")) {
162
- seenTitle = true;
163
- continue;
164
- }
165
- if (!seenTitle) continue;
166
- if (line.startsWith(">")) return clip(line.replace(/^>\s?/, ""));
167
- if (line.startsWith("#") || line.startsWith("---")) continue;
168
- return clip(line);
169
- }
170
- return "";
171
- }
172
-
173
- // A summary is read as one line of prose, so the markup that made it readable
174
- // in the file (`**bold**`, `` `code` ``) is noise here.
175
- function clip(s, max = 200) {
176
- const t = String(s).replace(/\*\*/g, "").replace(/`/g, "").trim();
177
- return t.length > max ? t.slice(0, max - 1).trimEnd() + "…" : t;
178
- }
179
-
180
- // A name is a lane only when the payload really ships that slash command. This
181
- // is what keeps `/orc-pact` a link and `a-normal-day` plain text, with no list
182
- // to maintain in two places.
183
- function laneOf(name) {
184
- return fs.existsSync(path.join(COMMANDS_DIR, name + ".md")) ? "/" + name : null;
185
- }
186
-
187
- function entry(slug, file, kind, name, md) {
188
- const stat = (() => {
189
- try {
190
- return fs.statSync(file);
191
- } catch (_) {
192
- return null;
193
- }
194
- })();
195
- const body = md === null ? "" : md;
196
- return {
197
- slug,
198
- title: titleOf(body, slug),
199
- lane: laneOf(name),
200
- kind, // "walkthrough" = written for a reader · "annotated" = the dense one
201
- group: GROUP_OF[name] || GROUP_OF[slug] || "other",
202
- summary: summaryOf(body),
203
- path: path.relative(ROOT, file).split(path.sep).join("/"),
204
- bytes: stat ? stat.size : body.length,
205
- lines: body ? body.split(/\r?\n/).length : 0,
206
- };
207
- }
208
-
209
- // Every mocked run that ships in this package, in reading order.
210
- function list() {
211
- const out = [];
212
-
213
- for (const e of listSafe(WALKTHROUGH_DIR)) {
214
- if (!e.isFile() || !e.name.endsWith(".md") || NOT_A_DOC.has(e.name)) continue;
215
- const name = e.name.replace(/\.md$/, "");
216
- const file = path.join(WALKTHROUGH_DIR, e.name);
217
- out.push(entry(name, file, "walkthrough", name, readSafe(file)));
218
- }
219
-
220
- for (const s of listSafe(SKILLS_DIR)) {
221
- if (!s.isDirectory()) continue;
222
- const exDir = path.join(SKILLS_DIR, s.name, "examples");
223
- for (const f of listSafe(exDir)) {
224
- if (!f.isFile() || !f.name.endsWith(".md")) continue;
225
- const file = path.join(exDir, f.name);
226
- // One example per skill is the shipped shape; a second one keeps its own
227
- // file name so neither can hide the other.
228
- const base = f.name.replace(/\.md$/, "");
229
- const slug = `${s.name}-example`;
230
- const taken = out.some((d) => d.slug === slug);
231
- out.push(entry(taken ? `${s.name}-${base}` : slug, file, "annotated", s.name, readSafe(file)));
232
- }
233
- }
234
-
235
- const rank = new Map(GROUPS.map((g, i) => [g.id, i]));
236
- out.sort((a, b) => {
237
- const g = (rank.get(a.group) ?? 99) - (rank.get(b.group) ?? 99);
238
- if (g) return g;
239
- // Inside a group: pinned docs first, then the reader-facing walkthrough
240
- // before the dense annotated example, then by title.
241
- const p = (d) => {
242
- const i = PINNED_FIRST.indexOf(d.slug);
243
- return i === -1 ? PINNED_FIRST.length : i;
244
- };
245
- const pin = p(a) - p(b);
246
- if (pin) return pin;
247
- if (a.kind !== b.kind) return a.kind === "walkthrough" ? -1 : 1;
248
- return a.title.localeCompare(b.title);
249
- });
250
- return out;
251
- }
252
-
253
- // The catalogue, grouped, with the counts a header can print without counting.
254
- function catalogue() {
255
- const docs = list();
256
- const groups = GROUPS.map((g) => ({
257
- ...g,
258
- docs: docs.filter((d) => d.group === g.id),
259
- })).filter((g) => g.docs.length);
260
- return { root: path.relative(ROOT, WALKTHROUGH_DIR) || "mock-run", total: docs.length, groups, docs };
261
- }
262
-
263
- // One doc, with its body. Returns null when the slug is unknown — the caller
264
- // decides what a miss means (exit 1 in the CLI, a 404 in the panel).
265
- function get(slug) {
266
- const hit = list().find((d) => d.slug === slug);
267
- if (!hit) return null;
268
- return { ...hit, body: readSafe(path.join(ROOT, hit.path)) || "" };
269
- }
270
-
271
- module.exports = { GROUPS, catalogue, list, get };
108
+ "orc-verify": "check",
109
+ "orc-challenge": "check",
110
+ // v0.49.1. It sits beside the lane it belongs to, and it is a SEPARATE
111
+ // walkthrough because the council is a decision the reader takes before the
112
+ // first review runs — not a detail of one.
113
+ "orc-challenge-council": "check",
114
+ "orc-pact": "check",
115
+ "orc-boundary": "check",
116
+ "orc-aftermath": "check",
117
+ "orc-budget": "check",
118
+ "orc-retro": "check",
119
+
120
+ "orc-pr-setup": "ship",
121
+ "orc-pr-driver": "ship",
122
+ "orc-handoff": "ship",
123
+
124
+ "orc-ui": "tools",
125
+ "orc-cli": "tools",
126
+ };
127
+
128
+ // A few docs lead their group because they are prerequisites for reading the
129
+ // rest, which alphabetical order cannot know. Everything else sorts by title.
130
+ const PINNED_FIRST = ["the-example-project", "orc", "orc-brainstorm", "orc-wiki-example", "orc-verify-example"];
131
+
132
+ // Files in mock-run/ that are not themselves a mocked run.
133
+ const NOT_A_DOC = new Set(["INDEX.md", "README.md"]);
134
+
135
+ function readSafe(p) {
136
+ try {
137
+ return fs.readFileSync(p, "utf8");
138
+ } catch (_) {
139
+ return null;
140
+ }
141
+ }
142
+
143
+ function listSafe(dir) {
144
+ try {
145
+ return fs.readdirSync(dir, { withFileTypes: true });
146
+ } catch (_) {
147
+ return [];
148
+ }
149
+ }
150
+
151
+ // `# Mock run — \`/orc-pact\`` → `Mock run — /orc-pact`. Backticks are markup
152
+ // for a reader, not part of the title.
153
+ function titleOf(md, fallback) {
154
+ const m = String(md || "").match(/^#\s+(.+?)\s*$/m);
155
+ return m ? m[1].replace(/`/g, "").trim() : fallback;
156
+ }
157
+
158
+ // The walkthroughs open with a `> one line` blockquote under the title. The
159
+ // older annotated examples do not, so the first real line stands in.
160
+ function summaryOf(md) {
161
+ const lines = String(md || "").split(/\r?\n/);
162
+ let seenTitle = false;
163
+ for (const raw of lines) {
164
+ const line = raw.trim();
165
+ if (!line) continue;
166
+ if (line.startsWith("# ")) {
167
+ seenTitle = true;
168
+ continue;
169
+ }
170
+ if (!seenTitle) continue;
171
+ if (line.startsWith(">")) return clip(line.replace(/^>\s?/, ""));
172
+ if (line.startsWith("#") || line.startsWith("---")) continue;
173
+ return clip(line);
174
+ }
175
+ return "";
176
+ }
177
+
178
+ // A summary is read as one line of prose, so the markup that made it readable
179
+ // in the file (`**bold**`, `` `code` ``) is noise here.
180
+ function clip(s, max = 200) {
181
+ const t = String(s).replace(/\*\*/g, "").replace(/`/g, "").trim();
182
+ return t.length > max ? t.slice(0, max - 1).trimEnd() + "…" : t;
183
+ }
184
+
185
+ // A name is a lane only when the payload really ships that slash command. This
186
+ // is what keeps `/orc-pact` a link and `a-normal-day` plain text, with no list
187
+ // to maintain in two places.
188
+ function laneOf(name) {
189
+ return fs.existsSync(path.join(COMMANDS_DIR, name + ".md")) ? "/" + name : null;
190
+ }
191
+
192
+ function entry(slug, file, kind, name, md) {
193
+ const stat = (() => {
194
+ try {
195
+ return fs.statSync(file);
196
+ } catch (_) {
197
+ return null;
198
+ }
199
+ })();
200
+ const body = md === null ? "" : md;
201
+ return {
202
+ slug,
203
+ title: titleOf(body, slug),
204
+ lane: laneOf(name),
205
+ kind, // "walkthrough" = written for a reader · "annotated" = the dense one
206
+ group: GROUP_OF[name] || GROUP_OF[slug] || "other",
207
+ summary: summaryOf(body),
208
+ path: path.relative(ROOT, file).split(path.sep).join("/"),
209
+ bytes: stat ? stat.size : body.length,
210
+ lines: body ? body.split(/\r?\n/).length : 0,
211
+ };
212
+ }
213
+
214
+ // Every mocked run that ships in this package, in reading order.
215
+ function list() {
216
+ const out = [];
217
+
218
+ for (const e of listSafe(WALKTHROUGH_DIR)) {
219
+ if (!e.isFile() || !e.name.endsWith(".md") || NOT_A_DOC.has(e.name)) continue;
220
+ const name = e.name.replace(/\.md$/, "");
221
+ const file = path.join(WALKTHROUGH_DIR, e.name);
222
+ out.push(entry(name, file, "walkthrough", name, readSafe(file)));
223
+ }
224
+
225
+ for (const s of listSafe(SKILLS_DIR)) {
226
+ if (!s.isDirectory()) continue;
227
+ const exDir = path.join(SKILLS_DIR, s.name, "examples");
228
+ for (const f of listSafe(exDir)) {
229
+ if (!f.isFile() || !f.name.endsWith(".md")) continue;
230
+ const file = path.join(exDir, f.name);
231
+ // One example per skill is the shipped shape; a second one keeps its own
232
+ // file name so neither can hide the other.
233
+ const base = f.name.replace(/\.md$/, "");
234
+ const slug = `${s.name}-example`;
235
+ const taken = out.some((d) => d.slug === slug);
236
+ out.push(entry(taken ? `${s.name}-${base}` : slug, file, "annotated", s.name, readSafe(file)));
237
+ }
238
+ }
239
+
240
+ const rank = new Map(GROUPS.map((g, i) => [g.id, i]));
241
+ out.sort((a, b) => {
242
+ const g = (rank.get(a.group) ?? 99) - (rank.get(b.group) ?? 99);
243
+ if (g) return g;
244
+ // Inside a group: pinned docs first, then the reader-facing walkthrough
245
+ // before the dense annotated example, then by title.
246
+ const p = (d) => {
247
+ const i = PINNED_FIRST.indexOf(d.slug);
248
+ return i === -1 ? PINNED_FIRST.length : i;
249
+ };
250
+ const pin = p(a) - p(b);
251
+ if (pin) return pin;
252
+ if (a.kind !== b.kind) return a.kind === "walkthrough" ? -1 : 1;
253
+ return a.title.localeCompare(b.title);
254
+ });
255
+ return out;
256
+ }
257
+
258
+ // The catalogue, grouped, with the counts a header can print without counting.
259
+ function catalogue() {
260
+ const docs = list();
261
+ const groups = GROUPS.map((g) => ({
262
+ ...g,
263
+ docs: docs.filter((d) => d.group === g.id),
264
+ })).filter((g) => g.docs.length);
265
+ return { root: path.relative(ROOT, WALKTHROUGH_DIR) || "mock-run", total: docs.length, groups, docs };
266
+ }
267
+
268
+ // One doc, with its body. Returns null when the slug is unknown — the caller
269
+ // decides what a miss means (exit 1 in the CLI, a 404 in the panel).
270
+ function get(slug) {
271
+ const hit = list().find((d) => d.slug === slug);
272
+ if (!hit) return null;
273
+ return { ...hit, body: readSafe(path.join(ROOT, hit.path)) || "" };
274
+ }
275
+
276
+ module.exports = { GROUPS, catalogue, list, get };