@orkestrel/scaffold 0.0.101 → 0.0.103

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 (59) hide show
  1. package/dist/agents/skills/orkestrel-dispatch/scripts/brief.js +6 -14
  2. package/dist/agents/skills/orkestrel-dispatch/scripts/cite.js +41 -28
  3. package/dist/agents/skills/orkestrel-dispatch/scripts/helpers.js +5 -12
  4. package/dist/agents/skills/orkestrel-dispatch/scripts/launch.js +25 -13
  5. package/dist/agents/skills/orkestrel-dispatch/scripts/login.js +18 -25
  6. package/dist/agents/skills/orkestrel-dispatch/scripts/result.js +11 -9
  7. package/dist/agents/skills/orkestrel-harden/scripts/discovery.js +233 -232
  8. package/dist/agents/skills/orkestrel-publish/scripts/compare.js +95 -89
  9. package/dist/agents/skills/orkestrel-publish/scripts/pins.js +5 -7
  10. package/dist/agents/skills/orkestrel-publish/scripts/swap.js +1273 -0
  11. package/dist/agents/skills/orkestrel-publish/scripts/wave.js +99 -89
  12. package/dist/agents/skills/orkestrel-publish/scripts/window.js +54 -52
  13. package/dist/agents/skills/orkestrel-scout/scripts/map.js +160 -161
  14. package/dist/bin/main.js +483 -444
  15. package/dist/bin/main.js.map +1 -1
  16. package/dist/host/AGENTS.md +1 -0
  17. package/dist/host/agents/skills/orkestrel-dispatch/SKILL.md +10 -10
  18. package/dist/host/agents/skills/orkestrel-dispatch/scripts/brief.ts +8 -11
  19. package/dist/host/agents/skills/orkestrel-dispatch/scripts/cite.ts +58 -28
  20. package/dist/host/agents/skills/orkestrel-dispatch/scripts/helpers.ts +5 -10
  21. package/dist/host/agents/skills/orkestrel-dispatch/scripts/launch.ts +31 -12
  22. package/dist/host/agents/skills/orkestrel-dispatch/scripts/login.ts +28 -23
  23. package/dist/host/agents/skills/orkestrel-dispatch/scripts/result.ts +12 -10
  24. package/dist/host/agents/skills/orkestrel-harden/scripts/discovery.ts +318 -237
  25. package/dist/host/agents/skills/orkestrel-journey/references/statechart.md +2 -5
  26. package/dist/host/agents/skills/orkestrel-publish/SKILL.md +7 -6
  27. package/dist/host/agents/skills/orkestrel-publish/references/release.md +12 -10
  28. package/dist/host/agents/skills/orkestrel-publish/references/wave.md +12 -5
  29. package/dist/host/agents/skills/orkestrel-publish/scripts/compare.ts +124 -82
  30. package/dist/host/agents/skills/orkestrel-publish/scripts/pins.ts +5 -7
  31. package/dist/host/agents/skills/orkestrel-publish/scripts/swap.ts +1655 -0
  32. package/dist/host/agents/skills/orkestrel-publish/scripts/wave.ts +128 -97
  33. package/dist/host/agents/skills/orkestrel-publish/scripts/window.ts +70 -55
  34. package/dist/host/agents/skills/orkestrel-scout/scripts/map.ts +215 -155
  35. package/dist/host/claude/rules/architecture.md +2 -2
  36. package/dist/host/claude/rules/tests.md +8 -2
  37. package/dist/host/claude/rules/typescript.md +130 -0
  38. package/dist/host/claude/rules/workspace.md +33 -18
  39. package/dist/host/configs/helpers.ts +209 -148
  40. package/dist/host/configs/policy.ts +110 -105
  41. package/dist/host/dotfiles/oxlintrc.json +1 -11
  42. package/dist/host/guides/scaffold.md +244 -170
  43. package/dist/host/manifest.json +36 -30
  44. package/dist/host/tests/config.test.ts +1263 -687
  45. package/dist/host/tests/policy.test.ts +19 -0
  46. package/dist/host/tests/setupPolicy.ts +1699 -1026
  47. package/dist/src/core/index.cjs +1595 -1686
  48. package/dist/src/core/index.cjs.map +1 -1
  49. package/dist/src/core/index.d.cts +202 -18
  50. package/dist/src/core/index.d.ts +202 -18
  51. package/dist/src/core/index.js +1590 -1687
  52. package/dist/src/core/index.js.map +1 -1
  53. package/dist/src/server/index.cjs +448 -392
  54. package/dist/src/server/index.cjs.map +1 -1
  55. package/dist/src/server/index.d.cts +1182 -1052
  56. package/dist/src/server/index.d.ts +1182 -1052
  57. package/dist/src/server/index.js +447 -395
  58. package/dist/src/server/index.js.map +1 -1
  59. package/package.json +2 -2
@@ -47,37 +47,40 @@ function mapTree(paths) {
47
47
  })
48
48
  .sort((left, right) => left.path.localeCompare(right.path));
49
49
  }
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;
55
+ }
50
56
  function mapHeadings(paths) {
51
- const rows = [];
52
- for (const path of expandPaths(paths).filter((candidate) => candidate.endsWith('.md'))) {
53
- let fenced = false;
54
- readLines(path).forEach((text, index) => {
55
- if (text.startsWith('```'))
56
- fenced = !fenced;
57
- if (fenced)
58
- return;
57
+ return expandPaths(paths)
58
+ .filter((candidate) => candidate.endsWith('.md'))
59
+ .flatMap((path) => {
60
+ const lines = readLines(path);
61
+ const fenced = scanFences(lines);
62
+ return lines.flatMap((text, index) => {
63
+ if (fenced[index] === true)
64
+ return [];
59
65
  const match = text.match(HEADING);
60
- if (match?.[1] !== undefined && match[2] !== undefined) {
61
- rows.push({ path, line: index + 1, text: `${match[1]} ${match[2]}` });
62
- }
66
+ return match?.[1] !== undefined && match[2] !== undefined
67
+ ? [{ path, line: index + 1, text: `${match[1]} ${match[2]}` }]
68
+ : [];
63
69
  });
64
- }
65
- return rows;
70
+ });
66
71
  }
67
72
  function mapExports(paths) {
68
- const rows = [];
69
- for (const path of expandPaths(paths).filter((candidate) => /\.[cm]?ts$/u.test(candidate))) {
70
- readLines(path).forEach((text, index) => {
71
- const match = text.match(EXPORT);
72
- if (match?.[1] !== undefined && match[2] !== undefined) {
73
- rows.push({ path, line: index + 1, text: `${match[1]} ${match[2]}` });
74
- }
75
- else if (/^export\s+\*\s+from\s+/u.test(text)) {
76
- rows.push({ path, line: index + 1, text: text.trim() });
77
- }
78
- });
79
- }
80
- return rows;
73
+ return expandPaths(paths)
74
+ .filter((candidate) => /\.[cm]?ts$/u.test(candidate))
75
+ .flatMap((path) => readLines(path).flatMap((text, index) => {
76
+ const match = text.match(EXPORT);
77
+ if (match?.[1] !== undefined && match[2] !== undefined) {
78
+ return [{ path, line: index + 1, text: `${match[1]} ${match[2]}` }];
79
+ }
80
+ return /^export\s+\*\s+from\s+/u.test(text)
81
+ ? [{ path, line: index + 1, text: text.trim() }]
82
+ : [];
83
+ }));
81
84
  }
82
85
  function buildMatcher(term, ignoreCase) {
83
86
  const needle = ignoreCase ? term.toLowerCase() : term;
@@ -87,169 +90,165 @@ function mapCensus(terms, roots, ignoreCase) {
87
90
  const files = expandPaths(roots).filter(isText);
88
91
  return terms.map((term) => {
89
92
  const matches = buildMatcher(term, ignoreCase);
90
- const rows = [];
91
- for (const path of files) {
92
- readLines(path).forEach((text, index) => {
93
- if (matches(text))
94
- rows.push({ path, line: index + 1, text: text.trim().slice(0, 160) });
95
- });
96
- }
97
- return { term, rows };
93
+ return {
94
+ term,
95
+ rows: files.flatMap((path) => readLines(path).flatMap((text, index) => matches(text) ? [{ path, line: index + 1, text: text.trim().slice(0, 160) }] : [])),
96
+ };
98
97
  });
99
98
  }
100
99
  function mapRefs(target, roots) {
101
100
  const needle = normalizeSlashes(target);
102
101
  const name = basename(needle);
103
- const rows = [];
104
- for (const path of expandPaths(roots).filter(isText)) {
105
- if (path === needle)
106
- continue;
107
- readLines(path).forEach((text, index) => {
108
- if (text.includes(needle) || text.includes(name)) {
109
- rows.push({ path, line: index + 1, text: text.trim().slice(0, 160) });
110
- }
111
- });
112
- }
113
- return rows;
102
+ return expandPaths(roots)
103
+ .filter(isText)
104
+ .filter((path) => path !== needle)
105
+ .flatMap((path) => readLines(path).flatMap((text, index) => text.includes(needle) || text.includes(name)
106
+ ? [{ path, line: index + 1, text: text.trim().slice(0, 160) }]
107
+ : []));
114
108
  }
115
- function readYamlKeys(path, lines, offset) {
116
- const rows = [];
117
- const stack = [];
118
- lines.forEach((text, index) => {
109
+ function classifyYamlLines(lines, offset) {
110
+ return lines.flatMap((text, index) => {
119
111
  const content = text.trimStart();
120
112
  if (content === '' || content.startsWith('#'))
121
- return;
122
- const indent = text.length - content.length;
113
+ return [];
123
114
  const match = content.match(/^([A-Za-z_][\w-]*)\s*:(?:\s+(.*))?$/u);
124
- if (match?.[1] === undefined) {
125
- // A list item, a block scalar line, or a wrapped value extends the key before it.
126
- const last = rows.at(-1);
127
- if (last !== undefined && indent >= (stack.at(-1)?.indent ?? 0)) {
128
- rows[rows.length - 1] = { ...last, text: `${last.text} ${content.trim()}`.trimEnd() };
129
- }
130
- return;
131
- }
132
- while (stack.length > 0 && (stack.at(-1)?.indent ?? 0) >= indent)
133
- stack.pop();
134
- const key = [...stack.map((entry) => entry.key), match[1]].join('.');
135
- stack.push({ indent, key: match[1] });
136
- rows.push({ path, line: index + offset, text: `${key}: ${(match[2] ?? '').trim()}`.trimEnd() });
115
+ return [
116
+ {
117
+ line: index + offset,
118
+ indent: text.length - content.length,
119
+ content,
120
+ key: match?.[1],
121
+ value: (match?.[2] ?? '').trim(),
122
+ },
123
+ ];
137
124
  });
138
- return rows;
125
+ }
126
+ function stepYamlState(state, line, position) {
127
+ if (line.key === undefined)
128
+ return state;
129
+ // Indents strictly increase along the stack, so the entries at or past this indent are the tail.
130
+ const stack = state.stack.filter((entry) => entry.indent < line.indent);
131
+ const key = [...stack.map((entry) => entry.key), line.key].join('.');
132
+ return {
133
+ stack: [...stack, { indent: line.indent, key: line.key }],
134
+ heads: [...state.heads, { position, line, key }],
135
+ };
136
+ }
137
+ function readYamlKeys(path, lines, offset) {
138
+ const classified = classifyYamlLines(lines, offset);
139
+ const { heads } = classified.reduce((state, line, position) => stepYamlState(state, line, position), { stack: [], heads: [] });
140
+ // A list item, a block scalar line, or a wrapped value extends the key line before it when it is
141
+ // indented at least as far; between two key lines every classified line is a non-key line.
142
+ return heads.map((head, rank) => ({
143
+ path,
144
+ line: head.line.line,
145
+ text: [
146
+ `${head.key}: ${head.line.value}`.trimEnd(),
147
+ ...classified
148
+ .slice(head.position + 1, heads[rank + 1]?.position ?? classified.length)
149
+ .filter((line) => line.indent >= head.line.indent)
150
+ .map((line) => line.content.trim()),
151
+ ]
152
+ .join(' ')
153
+ .trimEnd(),
154
+ }));
155
+ }
156
+ function stepTomlState(state, text, path, line) {
157
+ if (state.fence !== undefined) {
158
+ // The body of a multi-line string carries no key.
159
+ return text.includes(state.fence) ? { ...state, fence: undefined } : state;
160
+ }
161
+ const header = text.match(/^\s*\[\[?([^\]]+)\]\]?\s*$/u);
162
+ if (header?.[1] !== undefined)
163
+ return { ...state, table: header[1].trim() };
164
+ const match = text.match(/^\s*([A-Za-z0-9_.-]+|"[^"]+")\s*=\s*(.*)$/u);
165
+ if (match?.[1] === undefined)
166
+ return state;
167
+ const value = (match[2] ?? '').trim();
168
+ const opening = value.match(/^("""|''')/u)?.[1];
169
+ return {
170
+ table: state.table,
171
+ fence: opening !== undefined && value.indexOf(opening, 3) === -1 ? opening : undefined,
172
+ rows: [
173
+ ...state.rows,
174
+ {
175
+ path,
176
+ line,
177
+ text: `${state.table === '' ? '' : `${state.table}.`}${match[1]}: ${value}`.trimEnd(),
178
+ },
179
+ ],
180
+ };
139
181
  }
140
182
  function readTomlKeys(path, lines, offset) {
141
- const rows = [];
142
- let table = '';
143
- let fence;
144
- lines.forEach((text, index) => {
145
- if (fence !== undefined) {
146
- // The body of a multi-line string carries no key.
147
- if (text.includes(fence))
148
- fence = undefined;
149
- return;
150
- }
151
- const header = text.match(/^\s*\[\[?([^\]]+)\]\]?\s*$/u);
152
- if (header?.[1] !== undefined) {
153
- table = header[1].trim();
154
- return;
155
- }
156
- const match = text.match(/^\s*([A-Za-z0-9_.-]+|"[^"]+")\s*=\s*(.*)$/u);
157
- if (match?.[1] === undefined)
158
- return;
159
- const value = (match[2] ?? '').trim();
160
- const opening = value.match(/^("""|''')/u)?.[1];
161
- if (opening !== undefined && value.indexOf(opening, 3) === -1)
162
- fence = opening;
163
- rows.push({
164
- path,
165
- line: index + offset,
166
- text: `${table === '' ? '' : `${table}.`}${match[1]}: ${value}`.trimEnd(),
167
- });
168
- });
169
- return rows;
183
+ return lines.reduce((state, text, index) => stepTomlState(state, text, path, index + offset), { table: '', fence: undefined, rows: [] }).rows;
170
184
  }
171
185
  function mapFrontmatter(paths) {
172
- const rows = [];
173
- for (const path of expandPaths(paths).filter((candidate) => /\.(md|mdc|toml|ya?ml)$/u.test(candidate))) {
186
+ return expandPaths(paths)
187
+ .filter((candidate) => /\.(md|mdc|toml|ya?ml)$/u.test(candidate))
188
+ .flatMap((path) => {
174
189
  const lines = readLines(path);
175
190
  const markdown = /\.mdc?$/u.test(path);
176
191
  const end = markdown ? lines.indexOf('---', 1) : lines.length;
177
192
  if (markdown && (lines[0] !== '---' || end === -1))
178
- continue;
193
+ return [];
179
194
  const body = lines.slice(markdown ? 1 : 0, end);
180
195
  const offset = markdown ? 2 : 1;
181
- rows.push(...(path.endsWith('.toml')
196
+ return path.endsWith('.toml')
182
197
  ? readTomlKeys(path, body, offset)
183
- : readYamlKeys(path, body, offset)));
184
- }
185
- return rows;
198
+ : readYamlKeys(path, body, offset);
199
+ });
186
200
  }
187
201
  function readHeadingTexts(path) {
188
202
  return new Set(mapHeadings([path]).map((row) => row.text.replace(/^#+\s+/u, '').toLowerCase()));
189
203
  }
190
204
  function mapAnchors(paths) {
191
- const headings = new Map();
192
- const rows = [];
193
- for (const path of expandPaths(paths).filter((candidate) => candidate.endsWith('.md'))) {
194
- readLines(path).forEach((text, index) => {
195
- for (const match of text.matchAll(ANCHOR)) {
196
- const target = match[1] === undefined ? path : normalizeSlashes(match[1]);
197
- const heading = (match[2] ?? '').trim();
198
- if (heading === '')
199
- continue;
200
- if (!existsSync(target)) {
201
- rows.push({ path, line: index + 1, target, heading });
202
- continue;
203
- }
204
- const known = headings.get(target) ?? readHeadingTexts(target);
205
- headings.set(target, known);
206
- if (!known.has(heading.toLowerCase()))
207
- rows.push({ path, line: index + 1, target, heading });
208
- }
209
- });
210
- }
211
- return rows;
205
+ const citations = expandPaths(paths)
206
+ .filter((candidate) => candidate.endsWith('.md'))
207
+ .flatMap((path) => readLines(path).flatMap((text, index) => [...text.matchAll(ANCHOR)].flatMap((match) => {
208
+ const target = match[1] === undefined ? path : normalizeSlashes(match[1]);
209
+ const heading = (match[2] ?? '').trim();
210
+ return heading === '' ? [] : [{ path, line: index + 1, target, heading }];
211
+ })));
212
+ // Each existing target is read once, in the order its first citation names it.
213
+ const headings = new Map([...new Set(citations.map(({ target }) => target).filter((target) => existsSync(target)))].map((target) => [target, readHeadingTexts(target)]));
214
+ return citations.filter(({ target, heading }) => headings.get(target)?.has(heading.toLowerCase()) !== true);
212
215
  }
213
216
  function renderReport(report) {
214
- const lines = [];
215
- if (report.tree !== undefined) {
216
- lines.push('## Tree');
217
- for (const row of report.tree)
218
- lines.push(`${row.path}\t${row.lines} lines\t${row.bytes} bytes`);
219
- lines.push(`total\t${report.tree.length} files\t${report.tree.reduce((sum, row) => sum + row.lines, 0)} lines`);
220
- }
221
- if (report.headings !== undefined) {
222
- lines.push('## Headings');
223
- for (const row of report.headings)
224
- lines.push(`${row.path}:${row.line}\t${row.text}`);
225
- }
226
- if (report.frontmatter !== undefined) {
227
- lines.push('## Frontmatter');
228
- for (const row of report.frontmatter)
229
- lines.push(`${row.path}:${row.line} ${row.text}`);
230
- }
231
- if (report.exports !== undefined) {
232
- lines.push('## Exports (text reading)');
233
- for (const row of report.exports)
234
- lines.push(`${row.path}:${row.line}\t${row.text}`);
235
- }
236
- if (report.census !== undefined) {
237
- for (const entry of report.census) {
238
- lines.push(`## Census: ${entry.term} (${entry.rows.length})`);
239
- for (const row of entry.rows)
240
- lines.push(`${row.path}:${row.line}\t${row.text}`);
241
- }
242
- }
243
- if (report.refs !== undefined) {
244
- lines.push(`## Refs (${report.refs.length})`);
245
- for (const row of report.refs)
246
- lines.push(`${row.path}:${row.line}\t${row.text}`);
247
- }
248
- if (report.anchors !== undefined) {
249
- lines.push(`## Unresolved anchors (${report.anchors.length})`);
250
- for (const row of report.anchors)
251
- lines.push(`${row.path}:${row.line}\t${row.target} § ${row.heading}`);
252
- }
217
+ const { tree, headings, frontmatter, exports, census, refs, anchors } = report;
218
+ const lines = [
219
+ ...(tree === undefined
220
+ ? []
221
+ : [
222
+ '## Tree',
223
+ ...tree.map((row) => `${row.path}\t${row.lines} lines\t${row.bytes} bytes`),
224
+ `total\t${tree.length} files\t${tree.reduce((sum, row) => sum + row.lines, 0)} lines`,
225
+ ]),
226
+ ...(headings === undefined
227
+ ? []
228
+ : ['## Headings', ...headings.map((row) => `${row.path}:${row.line}\t${row.text}`)]),
229
+ ...(frontmatter === undefined
230
+ ? []
231
+ : ['## Frontmatter', ...frontmatter.map((row) => `${row.path}:${row.line}\t${row.text}`)]),
232
+ ...(exports === undefined
233
+ ? []
234
+ : [
235
+ '## Exports (text reading)',
236
+ ...exports.map((row) => `${row.path}:${row.line}\t${row.text}`),
237
+ ]),
238
+ ...(census ?? []).flatMap((entry) => [
239
+ `## Census: ${entry.term} (${entry.rows.length})`,
240
+ ...entry.rows.map((row) => `${row.path}:${row.line}\t${row.text}`),
241
+ ]),
242
+ ...(refs === undefined
243
+ ? []
244
+ : [`## Refs (${refs.length})`, ...refs.map((row) => `${row.path}:${row.line}\t${row.text}`)]),
245
+ ...(anchors === undefined
246
+ ? []
247
+ : [
248
+ `## Unresolved anchors (${anchors.length})`,
249
+ ...anchors.map((row) => `${row.path}:${row.line}\t${row.target} § ${row.heading}`),
250
+ ]),
251
+ ];
253
252
  return `${lines.join('\n')}\n`;
254
253
  }
255
254
  function main(argv) {