@orkestrel/scaffold 0.0.108 → 0.0.109

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 (157) hide show
  1. package/dist/agents/skills/orkestrel-dispatch/scripts/bench.js +326 -91
  2. package/dist/agents/skills/orkestrel-dispatch/scripts/brief.js +110 -14
  3. package/dist/agents/skills/orkestrel-dispatch/scripts/chain.js +280 -0
  4. package/dist/agents/skills/orkestrel-dispatch/scripts/cite.js +188 -26
  5. package/dist/agents/skills/orkestrel-dispatch/scripts/launch.js +142 -43
  6. package/dist/agents/skills/orkestrel-dispatch/scripts/login.js +240 -62
  7. package/dist/agents/skills/orkestrel-dispatch/scripts/patch.js +323 -0
  8. package/dist/agents/skills/orkestrel-dispatch/scripts/result.js +169 -32
  9. package/dist/agents/skills/orkestrel-dispatch/scripts/sweep.js +290 -69
  10. package/dist/agents/skills/orkestrel-falsify/scripts/mutate.js +560 -0
  11. package/dist/agents/skills/orkestrel-harden/scripts/discovery.js +1052 -205
  12. package/dist/agents/skills/orkestrel-harden/scripts/measure.js +361 -0
  13. package/dist/agents/skills/orkestrel-harden/scripts/titles.js +455 -80
  14. package/dist/agents/skills/orkestrel-parallel/scripts/cases.js +327 -0
  15. package/dist/agents/skills/orkestrel-publish/scripts/compare.js +298 -51
  16. package/dist/agents/skills/orkestrel-publish/scripts/pins.js +170 -13
  17. package/dist/agents/skills/orkestrel-publish/scripts/swap.js +956 -191
  18. package/dist/agents/skills/orkestrel-publish/scripts/wave.js +707 -112
  19. package/dist/agents/skills/orkestrel-publish/scripts/window.js +235 -41
  20. package/dist/agents/skills/orkestrel-scout/scripts/map.js +341 -54
  21. package/dist/agents/templates/brief.md +1 -1
  22. package/dist/bin/main.js +51 -46
  23. package/dist/bin/main.js.map +1 -1
  24. package/dist/host/AGENTS.md +23 -21
  25. package/dist/host/agents/orchestration.md +43 -18
  26. package/dist/host/agents/platforms/windows.md +3 -3
  27. package/dist/host/agents/skills/orkestrel-align/SKILL.md +10 -13
  28. package/dist/host/agents/skills/orkestrel-align/references/fleet.md +4 -10
  29. package/dist/host/agents/skills/orkestrel-align/references/integration.md +1 -1
  30. package/dist/host/agents/skills/orkestrel-build/SKILL.md +16 -48
  31. package/dist/host/agents/skills/orkestrel-build/agents/openai.yaml +1 -1
  32. package/dist/host/agents/skills/orkestrel-debrief/SKILL.md +19 -16
  33. package/dist/host/agents/skills/orkestrel-debrief/agents/openai.yaml +1 -1
  34. package/dist/host/agents/skills/orkestrel-debrief/references/field-testing.md +15 -28
  35. package/dist/host/agents/skills/orkestrel-debrief/references/instruction-audit.md +5 -6
  36. package/dist/host/agents/skills/orkestrel-debrief/references/retention.md +4 -6
  37. package/dist/host/agents/skills/orkestrel-dispatch/SKILL.md +31 -20
  38. package/dist/host/agents/skills/orkestrel-dispatch/references/launch.md +3 -4
  39. package/dist/host/agents/skills/orkestrel-dispatch/scripts/bench.ts +366 -75
  40. package/dist/host/agents/skills/orkestrel-dispatch/scripts/brief.ts +126 -16
  41. package/dist/host/agents/skills/orkestrel-dispatch/scripts/chain.ts +323 -0
  42. package/dist/host/agents/skills/orkestrel-dispatch/scripts/cite.ts +220 -31
  43. package/dist/host/agents/skills/orkestrel-dispatch/scripts/launch.ts +176 -33
  44. package/dist/host/agents/skills/orkestrel-dispatch/scripts/login.ts +294 -59
  45. package/dist/host/agents/skills/orkestrel-dispatch/scripts/patch.ts +401 -0
  46. package/dist/host/agents/skills/orkestrel-dispatch/scripts/result.ts +199 -34
  47. package/dist/host/agents/skills/orkestrel-dispatch/scripts/sweep.ts +330 -69
  48. package/dist/host/agents/skills/orkestrel-falsify/SKILL.md +10 -8
  49. package/dist/host/agents/skills/orkestrel-falsify/references/brief.md +1 -5
  50. package/dist/host/agents/skills/orkestrel-falsify/references/reconcile.md +1 -1
  51. package/dist/host/agents/skills/orkestrel-falsify/scripts/mutate.ts +701 -0
  52. package/dist/host/agents/skills/orkestrel-harden/SKILL.md +16 -12
  53. package/dist/host/agents/skills/orkestrel-harden/references/bindings.md +23 -81
  54. package/dist/host/agents/skills/orkestrel-harden/references/centralization.md +4 -5
  55. package/dist/host/agents/skills/orkestrel-harden/references/contract.md +2 -0
  56. package/dist/host/agents/skills/orkestrel-harden/references/document.md +33 -0
  57. package/dist/host/agents/skills/orkestrel-harden/references/equivalence.md +6 -3
  58. package/dist/host/agents/skills/orkestrel-harden/references/hardening.md +8 -4
  59. package/dist/host/agents/skills/orkestrel-harden/references/research.md +2 -0
  60. package/dist/host/agents/skills/orkestrel-harden/scripts/discovery.ts +1272 -279
  61. package/dist/host/agents/skills/orkestrel-harden/scripts/measure.ts +425 -0
  62. package/dist/host/agents/skills/orkestrel-harden/scripts/titles.ts +603 -98
  63. package/dist/host/agents/skills/orkestrel-journey/SKILL.md +75 -98
  64. package/dist/host/agents/skills/orkestrel-journey/references/captures.md +54 -17
  65. package/dist/host/agents/skills/orkestrel-journey/references/decide.md +1 -20
  66. package/dist/host/agents/skills/orkestrel-journey/references/layer.md +21 -31
  67. package/dist/host/agents/skills/orkestrel-journey/references/styles.md +1 -1
  68. package/dist/host/agents/skills/orkestrel-parallel/SKILL.md +28 -24
  69. package/dist/host/agents/skills/orkestrel-parallel/references/claude.md +15 -13
  70. package/dist/host/agents/skills/orkestrel-parallel/references/codex.md +1 -0
  71. package/dist/host/agents/skills/orkestrel-parallel/references/cursor.md +13 -13
  72. package/dist/host/agents/skills/orkestrel-parallel/scripts/cases.ts +389 -0
  73. package/dist/host/agents/skills/orkestrel-polish/SKILL.md +16 -20
  74. package/dist/host/agents/skills/orkestrel-polish/references/capture-harness.md +16 -26
  75. package/dist/host/agents/skills/orkestrel-publish/SKILL.md +12 -26
  76. package/dist/host/agents/skills/orkestrel-publish/agents/openai.yaml +2 -2
  77. package/dist/host/agents/skills/orkestrel-publish/references/adopt.md +23 -0
  78. package/dist/host/agents/skills/orkestrel-publish/references/release.md +4 -4
  79. package/dist/host/agents/skills/orkestrel-publish/references/wave.md +10 -9
  80. package/dist/host/agents/skills/orkestrel-publish/references/window.md +20 -25
  81. package/dist/host/agents/skills/orkestrel-publish/scripts/compare.ts +348 -59
  82. package/dist/host/agents/skills/orkestrel-publish/scripts/pins.ts +202 -22
  83. package/dist/host/agents/skills/orkestrel-publish/scripts/swap.ts +1061 -191
  84. package/dist/host/agents/skills/orkestrel-publish/scripts/wave.ts +812 -140
  85. package/dist/host/agents/skills/orkestrel-publish/scripts/window.ts +280 -38
  86. package/dist/host/agents/skills/orkestrel-scout/SKILL.md +3 -3
  87. package/dist/host/agents/skills/orkestrel-scout/scripts/map.ts +428 -70
  88. package/dist/host/agents/templates/brief.md +1 -1
  89. package/dist/host/agents/transports/claude-cli.md +3 -3
  90. package/dist/host/agents/transports/codex.md +2 -7
  91. package/dist/host/agents/transports/cursor.md +2 -3
  92. package/dist/host/claude/AGENTS.md +2 -2
  93. package/dist/host/claude/agents/analyst.md +3 -3
  94. package/dist/host/claude/agents/astra.md +3 -3
  95. package/dist/host/claude/agents/builder.md +1 -1
  96. package/dist/host/claude/agents/checker.md +1 -1
  97. package/dist/host/claude/agents/distiller.md +1 -1
  98. package/dist/host/claude/agents/grok.md +1 -1
  99. package/dist/host/claude/agents/opus.md +2 -2
  100. package/dist/host/claude/agents/orkestrel.md +57 -90
  101. package/dist/host/claude/agents/researcher.md +1 -1
  102. package/dist/host/claude/agents/reviewer.md +4 -0
  103. package/dist/host/claude/agents/scout.md +1 -1
  104. package/dist/host/claude/agents/verifier.md +1 -1
  105. package/dist/host/claude/rules/application.md +14 -21
  106. package/dist/host/claude/rules/architecture.md +34 -44
  107. package/dist/host/claude/rules/browser.md +3 -4
  108. package/dist/host/claude/rules/campaign.md +2 -2
  109. package/dist/host/claude/rules/documentation.md +25 -12
  110. package/dist/host/claude/rules/names.md +9 -4
  111. package/dist/host/claude/rules/patterns.md +6 -7
  112. package/dist/host/claude/rules/portability.md +1 -1
  113. package/dist/host/claude/rules/quality.md +4 -5
  114. package/dist/host/claude/rules/styles.md +2 -2
  115. package/dist/host/claude/rules/tests.md +57 -114
  116. package/dist/host/claude/rules/typescript.md +7 -9
  117. package/dist/host/claude/rules/workspace.md +21 -19
  118. package/dist/host/claude/rules/writing.md +2 -5
  119. package/dist/host/claude/skills/orkestrel-dispatch/SKILL.md +1 -1
  120. package/dist/host/claude/skills/orkestrel-harden/SKILL.md +1 -1
  121. package/dist/host/claude/skills/orkestrel-publish/SKILL.md +1 -1
  122. package/dist/host/claude/skills/orkestrel-scout/SKILL.md +1 -1
  123. package/dist/host/codex/agents/analyst.toml +9 -3
  124. package/dist/host/codex/agents/astra.toml +3 -4
  125. package/dist/host/codex/agents/builder.toml +1 -1
  126. package/dist/host/codex/agents/opus.toml +3 -4
  127. package/dist/host/codex/agents/orkestrel.toml +1 -1
  128. package/dist/host/codex/agents/researcher.toml +2 -2
  129. package/dist/host/codex/agents/verifier.toml +1 -1
  130. package/dist/host/configs/helpers.ts +21 -1
  131. package/dist/host/configs/policy.ts +299 -15
  132. package/dist/host/dotfiles/oxlintrc.json +137 -3
  133. package/dist/host/guides/agent.md +111 -134
  134. package/dist/host/guides/capture.md +801 -0
  135. package/dist/host/guides/probe.md +27 -8
  136. package/dist/host/guides/scaffold.md +96 -18
  137. package/dist/host/guides/study.md +534 -440
  138. package/dist/host/guides/test.md +111 -44
  139. package/dist/host/manifest.json +160 -240
  140. package/dist/host/tests/config.test.ts +971 -187
  141. package/dist/host/tests/policy.test.ts +127 -1
  142. package/dist/host/tests/setupPolicy.ts +893 -33
  143. package/dist/src/core/index.cjs +214 -86
  144. package/dist/src/core/index.cjs.map +1 -1
  145. package/dist/src/core/index.d.cts +122 -38
  146. package/dist/src/core/index.d.ts +122 -38
  147. package/dist/src/core/index.js +209 -85
  148. package/dist/src/core/index.js.map +1 -1
  149. package/dist/src/server/index.cjs +20 -27
  150. package/dist/src/server/index.cjs.map +1 -1
  151. package/dist/src/server/index.js +21 -28
  152. package/dist/src/server/index.js.map +1 -1
  153. package/package.json +11 -11
  154. package/dist/agents/skills/orkestrel-dispatch/scripts/helpers.js +0 -283
  155. package/dist/host/agents/skills/orkestrel-dispatch/references/bench.md +0 -25
  156. package/dist/host/agents/skills/orkestrel-dispatch/scripts/helpers.ts +0 -333
  157. package/dist/host/guides/supervisor.md +0 -4890
@@ -1,28 +1,172 @@
1
1
  // Map terrain before a brief, an absorption lane, or a rename. Run from the checkout root:
2
2
  // node .agents/skills/orkestrel-scout/scripts/map.ts --tree PATH... [--headings PATH...] [--frontmatter PATH...] [--exports PATH...] [--census TERM...] [--refs PATH] [--anchors PATH...] [--paths a,b] [--ignore-case] [--json] [--out FILE]
3
3
  // --tree lists every file under each path with its line count and size. --headings lists every
4
- // Markdown heading with its line. --frontmatter lists every key of a Markdown frontmatter block, a
5
- // TOML file, or a YAML file with its whole value, a nested key dotted under its parents
6
- // (`interface.default_prompt`) and a TOML key under its table. --exports lists every `export`
7
- // declaration line in a TypeScript file (a text reading, not the compiler's). --census lists every line carrying a term. --refs lists
4
+ // Markdown heading with its line; a heading inside a fenced block is no heading, where a fence
5
+ // opens with a run of three or more backticks or tildes indented up to three spaces and closes with
6
+ // a run of the same character at least as long. --frontmatter lists every key of a Markdown
7
+ // frontmatter block, a TOML file, or a YAML file with its whole value, a nested key dotted under its
8
+ // parents (`interface.default_prompt`) and a TOML key under its table. --exports lists every
9
+ // `export` declaration line in a TypeScript file (a text reading, not the compiler's). --census
10
+ // lists every line carrying a term. --refs lists
8
11
  // every line naming a path or its basename. --anchors lists every `§ Heading` reference whose
9
12
  // heading the named file, or the same file, does not carry. Walks skip node_modules, .git, dist,
10
13
  // tmp, .orkestrel, .idea, and host.json; --paths narrows a census or refs walk. Modes combine, each
11
14
  // printing its section; --json prints one object; --out writes the output to FILE instead of the
12
15
  // terminal. Give each flag once; a later duplicate is not read. A value that opens with `--` is a
13
- // missing value, so census a custom property without its leading dashes. Exit 0, or 64 when no
14
- // mode is given or a flag is given with no value.
15
- import { existsSync, readFileSync, statSync, mkdirSync, writeFileSync } from 'node:fs';
16
- import { basename, dirname } from 'node:path';
17
- import { listFiles, normalizeSlashes, readList, readMissingFlags, readOption, } from "../../orkestrel-dispatch/scripts/helpers.js";
18
- const EXPORT = /^export\s+(?:default\s+)?(?:async\s+)?(?:abstract\s+)?(function\*?|const|let|class|interface|type|enum|namespace)\s+([A-Za-z_$][\w$]*)/u;
19
- const HEADING = /^(#{1,6})\s+(.+?)\s*#*\s*$/u;
20
- const ANCHOR = /(?:`([^`\n]+\.md)`\s*)?§\s*"?([^"`\n|;,.)]+?)"?(?=\s*(?:[,.;)|]|\s(?:and|or|names|fixes|owns|states|binds|governs|decides|before|after)\b|$))/gu;
21
- const BINARY = /\.(png|jpg|jpeg|gif|webp|ico|woff2?|ttf|otf|eot|zip|tgz|gz|pdf|wasm)$/u;
22
- function isText(path) {
16
+ // missing value, so census a custom property without its leading dashes. The script refuses a flag
17
+ // it does not read, the `--flag=value` form included, and names every unknown flag and every flag
18
+ // given with no value in one refusal. Exit 0, or 64 when no mode is given, a flag is given with no
19
+ // value, or a flag is unknown.
20
+ import { existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, statSync, writeFileSync, } from 'node:fs';
21
+ import { basename, dirname, join } from 'node:path';
22
+ /** Matches an `export` declaration line and captures its kind and name. */
23
+ export const EXPORT = /^export\s+(?:default\s+)?(?:async\s+)?(?:abstract\s+)?(function\*?|const|let|class|interface|type|enum|namespace)\s+([A-Za-z_$][\w$]*)/u;
24
+ /** Matches a Markdown heading line and captures its marker and text. */
25
+ export const HEADING = /^(#{1,6})\s+(.+?)\s*#*\s*$/u;
26
+ /** Matches a `§ Heading` reference, with the backticked Markdown path that may precede it. */
27
+ export const ANCHOR = /(?:`([^`\n]+\.md)`\s*)?§\s*"?([^"`\n|;,.)]+?)"?(?=\s*(?:[,.;)|]|\s(?:and|or|names|fixes|owns|states|binds|governs|decides|before|after)\b|$))/gu;
28
+ /** Matches the extensions of the binary files a walk reads as no text. */
29
+ export const BINARY = /\.(png|jpg|jpeg|gif|webp|ico|woff2?|ttf|otf|eot|zip|tgz|gz|pdf|wasm)$/u;
30
+ /** Matches a line that can open or close a code fence and captures its run of backticks or tildes. */
31
+ export const FENCE = /^ {0,3}(`{3,}|~{3,})/u;
32
+ /** Names the directories a tree walk skips. */
33
+ export const SKIPPED_DIRECTORIES = new Set([
34
+ 'node_modules',
35
+ '.git',
36
+ 'dist',
37
+ 'tmp',
38
+ '.orkestrel',
39
+ '.idea',
40
+ ]);
41
+ /** Lists the flags that take a value. */
42
+ export const VALUE_FLAGS = [
43
+ '--tree',
44
+ '--headings',
45
+ '--frontmatter',
46
+ '--exports',
47
+ '--census',
48
+ '--refs',
49
+ '--anchors',
50
+ '--paths',
51
+ '--out',
52
+ ];
53
+ /** Lists every flag the script reads, value flags and switches alike. */
54
+ export const FLAGS = [...VALUE_FLAGS, '--ignore-case', '--json'];
55
+ /** Holds the usage line printed when a call names no mode. */
56
+ export const USAGE = 'usage: map.ts [--tree PATH...] [--headings PATH...] [--frontmatter PATH...] [--exports PATH...] [--census TERM...] [--refs PATH] [--anchors PATH...] [--paths a,b] [--ignore-case] [--json] [--out FILE]';
57
+ /**
58
+ * Reads the value that follows a flag.
59
+ *
60
+ * @remarks
61
+ * The first occurrence of a flag wins, a following value that opens with `--` is a missing value,
62
+ * and the `--flag=value` form is not read.
63
+ *
64
+ * @param argv - The arguments after the script path.
65
+ * @param name - The flag, such as `--paths`.
66
+ * @returns The value after the flag, or undefined when the flag is absent, last, or followed by
67
+ * another flag.
68
+ */
69
+ export function readOption(argv, name) {
70
+ const index = argv.indexOf(name);
71
+ const value = index === -1 ? undefined : argv[index + 1];
72
+ return value === undefined || value.startsWith('--') ? undefined : value;
73
+ }
74
+ /**
75
+ * Names the value flags a caller gave with no value.
76
+ *
77
+ * @param argv - The arguments after the script path.
78
+ * @param names - The flags that take a value.
79
+ * @returns Each named flag that appears last or is followed by another flag, in argument order.
80
+ */
81
+ export function readMissingFlags(argv, names) {
82
+ return names.filter((name) => argv.includes(name) && readOption(argv, name) === undefined);
83
+ }
84
+ /**
85
+ * Names the flags a caller gave that the script does not read.
86
+ *
87
+ * @remarks
88
+ * A `--flag=value` argument is unknown, because the script reads no such form. A bare `--` ends the
89
+ * scan.
90
+ *
91
+ * @param argv - The arguments after the script path.
92
+ * @param flags - Every flag the script reads, value flags and switches alike.
93
+ * @returns Each argument that opens with `--` and is not a read flag, in argument order.
94
+ */
95
+ export function readUnknownFlags(argv, flags) {
96
+ const end = argv.indexOf('--');
97
+ return (end === -1 ? argv : argv.slice(0, end)).filter((argument) => argument.startsWith('--') && !flags.includes(argument));
98
+ }
99
+ /**
100
+ * Reads the values that follow a flag up to the next flag.
101
+ *
102
+ * @param argv - The arguments after the script path.
103
+ * @param name - The flag, such as `--tree`.
104
+ * @returns The values between the flag and the next `--` argument, empty when the flag is absent.
105
+ */
106
+ export function readList(argv, name) {
107
+ const index = argv.indexOf(name);
108
+ if (index === -1)
109
+ return [];
110
+ const rest = argv.slice(index + 1);
111
+ const end = rest.findIndex((argument) => argument.startsWith('--'));
112
+ return end === -1 ? rest : rest.slice(0, end);
113
+ }
114
+ /**
115
+ * Normalizes a host path to forward slashes.
116
+ *
117
+ * @param path - A host path.
118
+ * @returns The same path with every backslash replaced by a slash.
119
+ */
120
+ export function normalizeSlashes(path) {
121
+ return path.split('\\').join('/');
122
+ }
123
+ /**
124
+ * Lists every regular file beneath a directory, skipping the directories the script never reads.
125
+ *
126
+ * @remarks
127
+ * A skipped name matches a directory at any depth, exactly and case-sensitively.
128
+ *
129
+ * @param directory - The directory to walk.
130
+ * @param skipped - The directory names to skip. Default: the skipped directories.
131
+ * @returns Each file's forward-slash path and modification time, in walk order.
132
+ * @throws Thrown when `directory` does not exist or cannot be read.
133
+ */
134
+ export function listFiles(directory, skipped = SKIPPED_DIRECTORIES) {
135
+ const files = [];
136
+ const pending = [directory];
137
+ while (pending.length > 0) {
138
+ const current = pending.pop();
139
+ if (current === undefined)
140
+ break;
141
+ for (const entry of readdirSync(current, { withFileTypes: true })) {
142
+ if (skipped.has(entry.name))
143
+ continue;
144
+ const path = join(current, entry.name);
145
+ if (entry.isDirectory())
146
+ pending.push(path);
147
+ else if (entry.isFile()) {
148
+ files.push({ path: normalizeSlashes(path), modified: statSync(path).mtimeMs });
149
+ }
150
+ }
151
+ }
152
+ return files;
153
+ }
154
+ /**
155
+ * Checks whether a walk reads a file as text.
156
+ *
157
+ * @param path - The file path.
158
+ * @returns True if the path carries no binary extension and is not `host.json`; false otherwise.
159
+ */
160
+ export function isText(path) {
23
161
  return !BINARY.test(path) && basename(path) !== 'host.json';
24
162
  }
25
- function expandPaths(paths) {
163
+ /**
164
+ * Expands paths into the files they name.
165
+ *
166
+ * @param paths - Files and directories; an absent path names nothing.
167
+ * @returns Each file, a directory expanded to the files beneath it.
168
+ */
169
+ export function expandPaths(paths) {
26
170
  return paths.flatMap((path) => {
27
171
  if (!existsSync(path))
28
172
  return [];
@@ -31,10 +175,22 @@ function expandPaths(paths) {
31
175
  return [normalizeSlashes(path)];
32
176
  });
33
177
  }
34
- function readLines(path) {
178
+ /**
179
+ * Reads a file as lines.
180
+ *
181
+ * @param path - The file to read.
182
+ * @returns The lines, split on either line ending.
183
+ */
184
+ export function readLines(path) {
35
185
  return readFileSync(path, 'utf8').split(/\r\n|\n/);
36
186
  }
37
- function mapTree(paths) {
187
+ /**
188
+ * Maps the files beneath paths with their line counts and sizes.
189
+ *
190
+ * @param paths - Files and directories.
191
+ * @returns A row per text file, sorted by path.
192
+ */
193
+ export function mapTree(paths) {
38
194
  return expandPaths(paths)
39
195
  .filter(isText)
40
196
  .map((path) => {
@@ -47,13 +203,48 @@ function mapTree(paths) {
47
203
  })
48
204
  .sort((left, right) => left.path.localeCompare(right.path));
49
205
  }
50
- function scanFences(lines) {
51
- const fenced = [];
52
- for (const text of lines)
53
- fenced.push((fenced.at(-1) ?? false) !== text.startsWith('```'));
54
- return fenced;
206
+ /**
207
+ * Finds the lines that can open or close a code fence.
208
+ *
209
+ * @param lines - The lines of a Markdown file.
210
+ * @returns Each line that opens with up to three spaces and a run of three or more backticks or tildes.
211
+ */
212
+ export function findFenceLines(lines) {
213
+ return lines.flatMap((text, index) => {
214
+ const run = text.match(FENCE)?.[1];
215
+ return run === undefined ? [] : [{ index, run }];
216
+ });
217
+ }
218
+ /**
219
+ * Marks the lines that sit inside a code fence.
220
+ *
221
+ * @remarks
222
+ * A fence opens on a run of three or more backticks or tildes, indented up to three spaces, and
223
+ * closes on a run of the same character at least as long; a fence-like line inside an open fence
224
+ * that does not close it is content, and a fence the file never closes runs to its end.
225
+ *
226
+ * @param lines - The lines of a Markdown file.
227
+ * @returns One flag per line: true for the fence lines and the lines between them.
228
+ */
229
+ export function scanFences(lines) {
230
+ const { open, spans } = findFenceLines(lines).reduce((state, fence) => {
231
+ if (state.open === undefined)
232
+ return { open: fence, spans: state.spans };
233
+ const closes = fence.run.startsWith(state.open.run.charAt(0)) && fence.run.length >= state.open.run.length;
234
+ return closes
235
+ ? { open: undefined, spans: [...state.spans, [state.open.index, fence.index]] }
236
+ : state;
237
+ }, { open: undefined, spans: [] });
238
+ const all = open === undefined ? spans : [...spans, [open.index, lines.length - 1]];
239
+ return [...lines.keys()].map((index) => all.some(([first, last]) => index >= first && index <= last));
55
240
  }
56
- function mapHeadings(paths) {
241
+ /**
242
+ * Maps the Markdown headings outside code fences.
243
+ *
244
+ * @param paths - Files and directories; only Markdown files are read.
245
+ * @returns A row per heading, its text the marker and the heading text.
246
+ */
247
+ export function mapHeadings(paths) {
57
248
  return expandPaths(paths)
58
249
  .filter((candidate) => candidate.endsWith('.md'))
59
250
  .flatMap((path) => {
@@ -69,7 +260,13 @@ function mapHeadings(paths) {
69
260
  });
70
261
  });
71
262
  }
72
- function mapExports(paths) {
263
+ /**
264
+ * Maps the `export` declaration lines of TypeScript files.
265
+ *
266
+ * @param paths - Files and directories; only TypeScript files are read.
267
+ * @returns A row per declaration, its text the kind and name, or the whole `export * from` line.
268
+ */
269
+ export function mapExports(paths) {
73
270
  return expandPaths(paths)
74
271
  .filter((candidate) => /\.[cm]?ts$/u.test(candidate))
75
272
  .flatMap((path) => readLines(path).flatMap((text, index) => {
@@ -82,11 +279,26 @@ function mapExports(paths) {
82
279
  : [];
83
280
  }));
84
281
  }
85
- function buildMatcher(term, ignoreCase) {
282
+ /**
283
+ * Builds the predicate a census applies to a line.
284
+ *
285
+ * @param term - The term to find.
286
+ * @param ignoreCase - If true, compares without regard to letter case; if false, compares exactly.
287
+ * @returns A predicate that is true for a line carrying the term.
288
+ */
289
+ export function buildMatcher(term, ignoreCase) {
86
290
  const needle = ignoreCase ? term.toLowerCase() : term;
87
291
  return (text) => (ignoreCase ? text.toLowerCase() : text).includes(needle);
88
292
  }
89
- function mapCensus(terms, roots, ignoreCase) {
293
+ /**
294
+ * Maps the lines that carry each census term.
295
+ *
296
+ * @param terms - The terms to find.
297
+ * @param roots - The files and directories to read.
298
+ * @param ignoreCase - If true, compares without regard to letter case; if false, compares exactly.
299
+ * @returns The matching lines per term, each line's text trimmed and cut to its first 160 characters.
300
+ */
301
+ export function mapCensus(terms, roots, ignoreCase) {
90
302
  const files = expandPaths(roots).filter(isText);
91
303
  return terms.map((term) => {
92
304
  const matches = buildMatcher(term, ignoreCase);
@@ -96,7 +308,14 @@ function mapCensus(terms, roots, ignoreCase) {
96
308
  };
97
309
  });
98
310
  }
99
- function mapRefs(target, roots) {
311
+ /**
312
+ * Maps the lines that name a path or its basename.
313
+ *
314
+ * @param target - The path to find.
315
+ * @param roots - The files and directories to read; the target file itself is left out.
316
+ * @returns The lines that carry the path or its basename.
317
+ */
318
+ export function mapRefs(target, roots) {
100
319
  const needle = normalizeSlashes(target);
101
320
  const name = basename(needle);
102
321
  return expandPaths(roots)
@@ -106,7 +325,14 @@ function mapRefs(target, roots) {
106
325
  ? [{ path, line: index + 1, text: text.trim().slice(0, 160) }]
107
326
  : []));
108
327
  }
109
- function classifyYamlLines(lines, offset) {
328
+ /**
329
+ * Classifies the lines of a YAML body.
330
+ *
331
+ * @param lines - The body lines.
332
+ * @param offset - The line number of the first body line.
333
+ * @returns Each non-blank, non-comment line with its indent, content, key, and inline value.
334
+ */
335
+ export function classifyYamlLines(lines, offset) {
110
336
  return lines.flatMap((text, index) => {
111
337
  const content = text.trimStart();
112
338
  if (content === '' || content.startsWith('#'))
@@ -123,7 +349,15 @@ function classifyYamlLines(lines, offset) {
123
349
  ];
124
350
  });
125
351
  }
126
- function stepYamlState(state, line, position) {
352
+ /**
353
+ * Steps the YAML reader over one classified line.
354
+ *
355
+ * @param state - The reader's state before the line.
356
+ * @param line - The classified line.
357
+ * @param position - The line's position among the classified lines.
358
+ * @returns The state after the line; a line with no key leaves it unchanged.
359
+ */
360
+ export function stepYamlState(state, line, position) {
127
361
  if (line.key === undefined)
128
362
  return state;
129
363
  // Indents strictly increase along the stack, so the entries at or past this indent are the tail.
@@ -134,7 +368,16 @@ function stepYamlState(state, line, position) {
134
368
  heads: [...state.heads, { position, line, key }],
135
369
  };
136
370
  }
137
- function readYamlKeys(path, lines, offset) {
371
+ /**
372
+ * Reads every key of a YAML body with its whole value.
373
+ *
374
+ * @param path - The file the body came from.
375
+ * @param lines - The body lines.
376
+ * @param offset - The line number of the first body line.
377
+ * @returns A row per key, a nested key dotted under its parents and its value extended by the
378
+ * list items and wrapped lines indented under it.
379
+ */
380
+ export function readYamlKeys(path, lines, offset) {
138
381
  const classified = classifyYamlLines(lines, offset);
139
382
  const { heads } = classified.reduce((state, line, position) => stepYamlState(state, line, position), { stack: [], heads: [] });
140
383
  // A list item, a block scalar line, or a wrapped value extends the key line before it when it is
@@ -153,7 +396,16 @@ function readYamlKeys(path, lines, offset) {
153
396
  .trimEnd(),
154
397
  }));
155
398
  }
156
- function stepTomlState(state, text, path, line) {
399
+ /**
400
+ * Steps the TOML reader over one line.
401
+ *
402
+ * @param state - The reader's state before the line.
403
+ * @param text - The line.
404
+ * @param path - The file the line came from.
405
+ * @param line - The line's number.
406
+ * @returns The state after the line, with a row added for a key and the table set for a header.
407
+ */
408
+ export function stepTomlState(state, text, path, line) {
157
409
  if (state.fence !== undefined) {
158
410
  // The body of a multi-line string carries no key.
159
411
  return text.includes(state.fence) ? { ...state, fence: undefined } : state;
@@ -165,10 +417,10 @@ function stepTomlState(state, text, path, line) {
165
417
  if (match?.[1] === undefined)
166
418
  return state;
167
419
  const value = (match[2] ?? '').trim();
168
- const opening = value.match(/^("""|''')/u)?.[1];
420
+ const marker = value.match(/^("""|''')/u)?.[1];
169
421
  return {
170
422
  table: state.table,
171
- fence: opening !== undefined && value.indexOf(opening, 3) === -1 ? opening : undefined,
423
+ fence: marker !== undefined && value.indexOf(marker, 3) === -1 ? marker : undefined,
172
424
  rows: [
173
425
  ...state.rows,
174
426
  {
@@ -179,10 +431,24 @@ function stepTomlState(state, text, path, line) {
179
431
  ],
180
432
  };
181
433
  }
182
- function readTomlKeys(path, lines, offset) {
434
+ /**
435
+ * Reads every key of a TOML body with its value.
436
+ *
437
+ * @param path - The file the body came from.
438
+ * @param lines - The body lines.
439
+ * @param offset - The line number of the first body line.
440
+ * @returns A row per key, a key under a table dotted beneath it; a multi-line string's body is no key.
441
+ */
442
+ export function readTomlKeys(path, lines, offset) {
183
443
  return lines.reduce((state, text, index) => stepTomlState(state, text, path, index + offset), { table: '', fence: undefined, rows: [] }).rows;
184
444
  }
185
- function mapFrontmatter(paths) {
445
+ /**
446
+ * Maps the keys of Markdown frontmatter, TOML, and YAML files.
447
+ *
448
+ * @param paths - Files and directories; only Markdown, MDC, TOML, and YAML files are read.
449
+ * @returns A row per key; a Markdown file without a closed frontmatter block gives none.
450
+ */
451
+ export function mapFrontmatter(paths) {
186
452
  return expandPaths(paths)
187
453
  .filter((candidate) => /\.(md|mdc|toml|ya?ml)$/u.test(candidate))
188
454
  .flatMap((path) => {
@@ -198,10 +464,23 @@ function mapFrontmatter(paths) {
198
464
  : readYamlKeys(path, body, offset);
199
465
  });
200
466
  }
201
- function readHeadingTexts(path) {
467
+ /**
468
+ * Reads the heading texts of a Markdown file.
469
+ *
470
+ * @param path - The Markdown file.
471
+ * @returns Each heading's text without its marker, lowercased.
472
+ */
473
+ export function readHeadingTexts(path) {
202
474
  return new Set(mapHeadings([path]).map((row) => row.text.replace(/^#+\s+/u, '').toLowerCase()));
203
475
  }
204
- function mapAnchors(paths) {
476
+ /**
477
+ * Maps the `§ Heading` references whose heading their target does not carry.
478
+ *
479
+ * @param paths - Files and directories; only Markdown files are read.
480
+ * @returns A row per unresolved reference. A reference with no backticked path before it targets
481
+ * the file that carries it, and a reference whose target file is absent is unresolved.
482
+ */
483
+ export function mapAnchors(paths) {
205
484
  const citations = expandPaths(paths)
206
485
  .filter((candidate) => candidate.endsWith('.md'))
207
486
  .flatMap((path) => readLines(path).flatMap((text, index) => [...text.matchAll(ANCHOR)].flatMap((match) => {
@@ -213,7 +492,13 @@ function mapAnchors(paths) {
213
492
  const headings = new Map([...new Set(citations.map(({ target }) => target).filter((target) => existsSync(target)))].map((target) => [target, readHeadingTexts(target)]));
214
493
  return citations.filter(({ target, heading }) => headings.get(target)?.has(heading.toLowerCase()) !== true);
215
494
  }
216
- function renderReport(report) {
495
+ /**
496
+ * Renders a map as text.
497
+ *
498
+ * @param report - The map.
499
+ * @returns One section per mode the map carries, each opened by a `##` heading, ending in a line feed.
500
+ */
501
+ export function renderReport(report) {
217
502
  const { tree, headings, frontmatter, exports, census, refs, anchors } = report;
218
503
  const lines = [
219
504
  ...(tree === undefined
@@ -251,20 +536,20 @@ function renderReport(report) {
251
536
  ];
252
537
  return `${lines.join('\n')}\n`;
253
538
  }
254
- function main(argv) {
255
- const missing = readMissingFlags(argv, [
256
- '--tree',
257
- '--headings',
258
- '--frontmatter',
259
- '--exports',
260
- '--census',
261
- '--refs',
262
- '--anchors',
263
- '--paths',
264
- '--out',
265
- ]);
266
- if (missing.length > 0) {
267
- console.error(`map: ${missing.join(', ')} given with no value`);
539
+ /**
540
+ * Runs the script on its arguments.
541
+ *
542
+ * @param argv - The arguments after the script path.
543
+ * @returns The exit code: 0 on success, 64 on usage.
544
+ */
545
+ export function main(argv) {
546
+ const unknown = readUnknownFlags(argv, FLAGS);
547
+ const missing = readMissingFlags(argv, VALUE_FLAGS);
548
+ if (unknown.length > 0 || missing.length > 0) {
549
+ console.error(`map: ${[
550
+ ...(unknown.length > 0 ? [`${unknown.join(', ')} is not a flag this script reads`] : []),
551
+ ...(missing.length > 0 ? [`${missing.join(', ')} given with no value`] : []),
552
+ ].join('; ')}; ${USAGE}`);
268
553
  return 64;
269
554
  }
270
555
  const roots = (readOption(argv, '--paths') ?? '.').split(',');
@@ -284,7 +569,7 @@ function main(argv) {
284
569
  anchors.length ===
285
570
  0 &&
286
571
  refs === undefined) {
287
- console.error('usage: map.ts [--tree PATH...] [--headings PATH...] [--frontmatter PATH...] [--exports PATH...] [--census TERM...] [--refs PATH] [--anchors PATH...] [--paths a,b] [--ignore-case] [--json] [--out FILE]');
572
+ console.error(USAGE);
288
573
  return 64;
289
574
  }
290
575
  const report = {
@@ -307,4 +592,6 @@ function main(argv) {
307
592
  }
308
593
  return 0;
309
594
  }
310
- process.exitCode = main(process.argv.slice(2));
595
+ if (process.argv[1] !== undefined && realpathSync(process.argv[1]) === import.meta.filename) {
596
+ process.exitCode = main(process.argv.slice(2));
597
+ }
@@ -27,7 +27,7 @@ UNKNOWN and how the unit reports it.
27
27
  ## Scope
28
28
 
29
29
  - **Owned.** FILES, including the tests that pin the behavior, the fixtures derived from a constant, and the materialized copy of a template.
30
- - **Shared (report-only).** FILES; return an exact patch.
30
+ - **Shared (report-only).** FILES; return an exact patch as an edit file: one `### <path>` block per edit, each with a `reason:` line and fenced `old:` and `new:` texts, that `.agents/skills/orkestrel-dispatch/scripts/patch.ts` applies.
31
31
  - **Off-limits.** FILES. Never list `tmp/probes/` here.
32
32
  - **Made false by this change.** FILES, derived by running the suite or by a named search bound.
33
33
  - **Tools and limits.** ALLOWED_TOOLS; scoped validation only; no tree-wide source-mutating command. For a verifier, declare each permitted output path per `.agents/orchestration.md` § Permission floor.