@orkestrel/scaffold 0.0.107 → 0.0.108

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 (42) hide show
  1. package/dist/agents/skills/orkestrel-dispatch/scripts/sweep.js +124 -4
  2. package/dist/bin/main.js +1 -1
  3. package/dist/bin/main.js.map +1 -1
  4. package/dist/host/AGENTS.md +5 -1
  5. package/dist/host/agents/orchestration.md +60 -45
  6. package/dist/host/agents/platforms/posix.md +15 -0
  7. package/dist/host/agents/platforms/windows.md +37 -0
  8. package/dist/host/agents/skills/orkestrel-debrief/references/retention.md +10 -10
  9. package/dist/host/agents/skills/orkestrel-dispatch/SKILL.md +6 -5
  10. package/dist/host/agents/skills/orkestrel-dispatch/references/launch.md +12 -8
  11. package/dist/host/agents/skills/orkestrel-dispatch/scripts/sweep.ts +144 -4
  12. package/dist/host/agents/skills/orkestrel-parallel/SKILL.md +67 -10
  13. package/dist/host/agents/skills/orkestrel-parallel/agents/openai.yaml +2 -2
  14. package/dist/host/agents/skills/orkestrel-parallel/references/claude.md +32 -0
  15. package/dist/host/agents/skills/orkestrel-parallel/references/codex.md +28 -0
  16. package/dist/host/agents/skills/orkestrel-parallel/references/cursor.md +29 -0
  17. package/dist/host/agents/skills/orkestrel-parallel/references/t3.md +31 -0
  18. package/dist/host/agents/skills/orkestrel-publish/references/wave.md +2 -1
  19. package/dist/host/agents/transports/codex.md +1 -1
  20. package/dist/host/claude/AGENTS.md +6 -9
  21. package/dist/host/claude/rules/campaign.md +55 -0
  22. package/dist/host/claude/rules/documentation.md +2 -0
  23. package/dist/host/claude/rules/portability.md +1 -2
  24. package/dist/host/claude/rules/typescript.md +1 -1
  25. package/dist/host/claude/rules/writing.md +5 -2
  26. package/dist/host/claude/skills/orkestrel-parallel/SKILL.md +1 -1
  27. package/dist/host/codex/config.toml +3 -3
  28. package/dist/host/configs/policy.ts +106 -1
  29. package/dist/host/cursor/rules/orchestration.mdc +1 -0
  30. package/dist/host/dotfiles/oxlintrc.json +4 -2
  31. package/dist/host/guides/scaffold.md +56 -1
  32. package/dist/host/manifest.json +64 -27
  33. package/dist/host/tests/config.test.ts +560 -5
  34. package/dist/host/tests/setupPolicy.ts +496 -6
  35. package/dist/src/core/index.cjs +26 -2
  36. package/dist/src/core/index.cjs.map +1 -1
  37. package/dist/src/core/index.d.cts +1 -1
  38. package/dist/src/core/index.d.ts +1 -1
  39. package/dist/src/core/index.js +26 -2
  40. package/dist/src/core/index.js.map +1 -1
  41. package/package.json +1 -1
  42. package/dist/host/agents/skills/orkestrel-parallel/references/harnesses.md +0 -19
@@ -2,14 +2,21 @@
2
2
  // node .agents/skills/orkestrel-dispatch/scripts/sweep.ts --report
3
3
  // node .agents/skills/orkestrel-dispatch/scripts/sweep.ts --tmp [--older-than MINUTES]
4
4
  // node .agents/skills/orkestrel-dispatch/scripts/sweep.ts --unit UNIT [--delete]
5
- // --report lists leftovers under tmp/ and .orkestrel/ by age and deletes nothing.
5
+ // --report lists leftovers under tmp/ and .orkestrel/ by age and deletes nothing. It also checks
6
+ // every .md file under .orkestrel/: a backticked repository-relative path that git ignores prints
7
+ // `<file>:<line> cites ignored path <path>` (a path git tracks is never reported, nor is a glob or
8
+ // placeholder whose literal prefix git tracks), and an absolute machine path on a line prints
9
+ // `<file>:<line> names a machine path`. Both lines are report-only.
6
10
  // --tmp deletes launch files older than the threshold (default 60 minutes), removes emptied
7
11
  // directories, and refuses while any launch file changed in the last 2 minutes.
8
12
  // --unit lists one accepted unit's files under the launch directories (the brief, report, claims,
9
13
  // journal, errors, status, pid, and last-message files whose name is UNIT followed by `-` or `.`), and
10
14
  // --delete removes them, refusing while one changed in the last 2 minutes.
15
+ // --tmp and --unit --delete never delete a file git tracks: each prints `kept tracked <path>` and the
16
+ // refusal ignores a tracked file; --unit alone marks a tracked file in its listing.
11
17
  // Exit 0 on success, 2 when a deletion is refused, 64 on usage.
12
- import { existsSync, readdirSync, rmSync } from 'node:fs';
18
+ import { spawnSync } from 'node:child_process';
19
+ import { existsSync, readFileSync, readdirSync, rmSync } from 'node:fs';
13
20
  import { basename, join } from 'node:path';
14
21
  import { listFiles, readMissingFlags, readOption } from "./helpers.js";
15
22
  const LAUNCH_DIRECTORIES = [
@@ -24,6 +31,13 @@ const LAUNCH_DIRECTORIES = [
24
31
  const RECENT_MS = 2 * 60_000;
25
32
  // A launch directory holds nothing a walk skips, so every file beneath it counts.
26
33
  const NOTHING_SKIPPED = new Set();
34
+ // Git's output is bounded by the repository's file count, so a generous buffer is the only limit.
35
+ const GIT_BUFFER = 64 * 1024 * 1024;
36
+ // An absolute machine path: a drive-letter path, or one that opens `/Users/`, `/home/`, or `~/`. The
37
+ // lookbehind keeps `https://`, `src/home/`, and `./home/` from matching.
38
+ const MACHINE_PATH = /(?<![\w.-])(?:[A-Za-z]:[\\/]|\/(?:Users|home)\/|~\/)/u;
39
+ const BACKTICKED = /`([^`\n]+)`/gu;
40
+ const PLACEHOLDER = /[*?<{]/u;
27
41
  function formatDay(modified) {
28
42
  return new Date(modified).toISOString().slice(0, 10);
29
43
  }
@@ -67,6 +81,99 @@ function readThreshold(argv) {
67
81
  const value = Number(readOption(argv, '--older-than'));
68
82
  return Number.isFinite(value) && value > 0 ? value : undefined;
69
83
  }
84
+ // Lists the files git tracks beneath the scope (the whole repository when empty), as forward-slash
85
+ // paths from the root. Any git failure reads as nothing tracked, so the run keeps its old behavior.
86
+ function listTracked(scope) {
87
+ const args = ['ls-files', '-z', ...(scope.length === 0 ? [] : ['--', ...scope])];
88
+ const run = spawnSync('git', args, { encoding: 'utf8', windowsHide: true, maxBuffer: GIT_BUFFER });
89
+ if (run.status !== 0)
90
+ return [];
91
+ return run.stdout.split('\0').filter((path) => path !== '');
92
+ }
93
+ // Names the probes git ignores, from one batched `git check-ignore`: status 0 and 1 both answer,
94
+ // and any other status reads as nothing ignored.
95
+ function readIgnored(probes) {
96
+ if (probes.length === 0)
97
+ return new Set();
98
+ const run = spawnSync('git', ['check-ignore', '--stdin', '-z'], {
99
+ encoding: 'utf8',
100
+ windowsHide: true,
101
+ maxBuffer: GIT_BUFFER,
102
+ input: probes.join('\0'),
103
+ });
104
+ if (run.status !== 0 && run.status !== 1)
105
+ return new Set();
106
+ return new Set(run.stdout.split('\0').filter((path) => path !== ''));
107
+ }
108
+ // Reads a backticked token as a repository-relative citation. A token counts when it has no
109
+ // whitespace, contains `/`, and is no URL; the path stops at its first glob or placeholder character
110
+ // and drops a trailing `/`. A machine path, a path outside the repository, and a backslash path are
111
+ // no citation.
112
+ function readCitation(token) {
113
+ if (/\s/u.test(token) || !token.includes('/') || token.includes('://'))
114
+ return undefined;
115
+ const stop = token.search(PLACEHOLDER);
116
+ const cut = (stop === -1 ? token : token.slice(0, stop)).replace(/^\.\//u, '');
117
+ const path = cut.replace(/\/+$/u, '');
118
+ if (path === '' || /^(?:\/|~|\.\.(?:\/|$)|-|[A-Za-z]:)/u.test(path) || path.includes('\\')) {
119
+ return undefined;
120
+ }
121
+ return { path, directory: cut.endsWith('/'), prefix: stop === -1 ? undefined : cut };
122
+ }
123
+ function listFindings(file) {
124
+ const lines = readFileSync(file, 'utf8').split(/\r\n|\n/u);
125
+ return lines.flatMap((text, index) => {
126
+ const line = index + 1;
127
+ const machine = MACHINE_PATH.test(text) ? [{ file, line, citation: undefined }] : [];
128
+ const cited = [...text.matchAll(BACKTICKED)].flatMap((match) => {
129
+ const citation = readCitation(match[1] ?? '');
130
+ return citation === undefined ? [] : [{ file, line, citation }];
131
+ });
132
+ return [...machine, ...cited];
133
+ });
134
+ }
135
+ function probeOf(citation) {
136
+ return citation.directory ? `${citation.path}/` : citation.path;
137
+ }
138
+ // A citation is covered when git tracks the path, a file beneath it, or a file its glob prefix opens.
139
+ function isCovered(citation, tracked) {
140
+ return tracked.some((path) => path === citation.path ||
141
+ path.startsWith(`${citation.path}/`) ||
142
+ (citation.prefix !== undefined && path.startsWith(citation.prefix)));
143
+ }
144
+ function reportCitations() {
145
+ if (!existsSync('.orkestrel'))
146
+ return [];
147
+ const findings = listFiles('.orkestrel', NOTHING_SKIPPED)
148
+ .map((file) => file.path)
149
+ .filter((path) => /\.md$/iu.test(path))
150
+ .sort()
151
+ .flatMap((path) => listFindings(path));
152
+ const probes = new Set();
153
+ for (const finding of findings) {
154
+ if (finding.citation !== undefined)
155
+ probes.add(probeOf(finding.citation));
156
+ }
157
+ const ignored = readIgnored([...probes]);
158
+ const tracked = ignored.size === 0 ? [] : listTracked([]);
159
+ const seen = new Set();
160
+ const lines = [];
161
+ for (const { file, line, citation } of findings) {
162
+ const text = citation === undefined
163
+ ? 'names a machine path'
164
+ : ignored.has(probeOf(citation)) && !isCovered(citation, tracked)
165
+ ? `cites ignored path ${citation.path}`
166
+ : undefined;
167
+ if (text === undefined)
168
+ continue;
169
+ const key = `${file}:${line} ${text}`;
170
+ if (seen.has(key))
171
+ continue;
172
+ seen.add(key);
173
+ lines.push(`sweep: ${key}`);
174
+ }
175
+ return lines;
176
+ }
70
177
  function runReport() {
71
178
  for (const directory of [...LAUNCH_DIRECTORIES, ...listCampaignDirectories()]) {
72
179
  const line = reportDirectory(directory);
@@ -76,13 +183,16 @@ function runReport() {
76
183
  const files = reportCampaignFiles();
77
184
  if (files !== undefined)
78
185
  console.log(files);
186
+ for (const line of reportCitations())
187
+ console.log(line);
79
188
  return 0;
80
189
  }
81
190
  function runSweep(thresholdMinutes) {
82
191
  const now = Date.now();
83
192
  const present = LAUNCH_DIRECTORIES.filter((directory) => existsSync(directory));
193
+ const tracked = new Set(listTracked(LAUNCH_DIRECTORIES));
84
194
  const files = present.flatMap((directory) => listFiles(directory, NOTHING_SKIPPED));
85
- if (files.some((file) => now - file.modified < RECENT_MS)) {
195
+ if (files.some((file) => !tracked.has(file.path) && now - file.modified < RECENT_MS)) {
86
196
  console.error('sweep: a launch file changed within the last 2 minutes; refusing to delete');
87
197
  return 2;
88
198
  }
@@ -90,6 +200,10 @@ function runSweep(thresholdMinutes) {
90
200
  for (const file of files) {
91
201
  if (file.modified > cutoff)
92
202
  continue;
203
+ if (tracked.has(file.path)) {
204
+ console.log(`sweep: kept tracked ${file.path}`);
205
+ continue;
206
+ }
93
207
  rmSync(file.path, { force: true });
94
208
  console.log(`sweep: deleted ${file.path}`);
95
209
  }
@@ -111,11 +225,17 @@ function runUnit(unit, remove) {
111
225
  return 64;
112
226
  }
113
227
  const files = listUnitFiles(unit);
114
- if (remove && files.some((file) => Date.now() - file.modified < RECENT_MS)) {
228
+ const tracked = new Set(listTracked(LAUNCH_DIRECTORIES));
229
+ if (remove &&
230
+ files.some((file) => !tracked.has(file.path) && Date.now() - file.modified < RECENT_MS)) {
115
231
  console.error(`sweep: a file of ${unit} changed within the last 2 minutes; refusing to delete`);
116
232
  return 2;
117
233
  }
118
234
  for (const file of files) {
235
+ if (tracked.has(file.path)) {
236
+ console.log(remove ? `sweep: kept tracked ${file.path}` : `sweep: found ${file.path} (tracked)`);
237
+ continue;
238
+ }
119
239
  if (remove)
120
240
  rmSync(file.path, { force: true });
121
241
  console.log(`sweep: ${remove ? 'deleted' : 'found'} ${file.path}`);
package/dist/bin/main.js CHANGED
@@ -10,7 +10,7 @@ import { normalize } from "node:path";
10
10
  import { parseArgs } from "node:util";
11
11
  var package_default = {
12
12
  name: "@orkestrel/scaffold",
13
- version: "0.0.107",
13
+ version: "0.0.108",
14
14
  description: "Scaffold workspaces with commands: new, audit, repair, catalog, and overwrite.",
15
15
  keywords: [
16
16
  "audit",