@orkestrel/scaffold 0.0.107 → 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.
- package/dist/agents/skills/orkestrel-dispatch/scripts/bench.js +326 -91
- package/dist/agents/skills/orkestrel-dispatch/scripts/brief.js +110 -14
- package/dist/agents/skills/orkestrel-dispatch/scripts/chain.js +280 -0
- package/dist/agents/skills/orkestrel-dispatch/scripts/cite.js +188 -26
- package/dist/agents/skills/orkestrel-dispatch/scripts/launch.js +142 -43
- package/dist/agents/skills/orkestrel-dispatch/scripts/login.js +240 -62
- package/dist/agents/skills/orkestrel-dispatch/scripts/patch.js +323 -0
- package/dist/agents/skills/orkestrel-dispatch/scripts/result.js +169 -32
- package/dist/agents/skills/orkestrel-dispatch/scripts/sweep.js +368 -27
- package/dist/agents/skills/orkestrel-falsify/scripts/mutate.js +560 -0
- package/dist/agents/skills/orkestrel-harden/scripts/discovery.js +1052 -205
- package/dist/agents/skills/orkestrel-harden/scripts/measure.js +361 -0
- package/dist/agents/skills/orkestrel-harden/scripts/titles.js +455 -80
- package/dist/agents/skills/orkestrel-parallel/scripts/cases.js +327 -0
- package/dist/agents/skills/orkestrel-publish/scripts/compare.js +298 -51
- package/dist/agents/skills/orkestrel-publish/scripts/pins.js +170 -13
- package/dist/agents/skills/orkestrel-publish/scripts/swap.js +956 -191
- package/dist/agents/skills/orkestrel-publish/scripts/wave.js +707 -112
- package/dist/agents/skills/orkestrel-publish/scripts/window.js +235 -41
- package/dist/agents/skills/orkestrel-scout/scripts/map.js +341 -54
- package/dist/agents/templates/brief.md +1 -1
- package/dist/bin/main.js +51 -46
- package/dist/bin/main.js.map +1 -1
- package/dist/host/AGENTS.md +26 -20
- package/dist/host/agents/orchestration.md +98 -58
- package/dist/host/agents/platforms/posix.md +15 -0
- package/dist/host/agents/platforms/windows.md +37 -0
- package/dist/host/agents/skills/orkestrel-align/SKILL.md +10 -13
- package/dist/host/agents/skills/orkestrel-align/references/fleet.md +4 -10
- package/dist/host/agents/skills/orkestrel-align/references/integration.md +1 -1
- package/dist/host/agents/skills/orkestrel-build/SKILL.md +16 -48
- package/dist/host/agents/skills/orkestrel-build/agents/openai.yaml +1 -1
- package/dist/host/agents/skills/orkestrel-debrief/SKILL.md +19 -16
- package/dist/host/agents/skills/orkestrel-debrief/agents/openai.yaml +1 -1
- package/dist/host/agents/skills/orkestrel-debrief/references/field-testing.md +15 -28
- package/dist/host/agents/skills/orkestrel-debrief/references/instruction-audit.md +5 -6
- package/dist/host/agents/skills/orkestrel-debrief/references/retention.md +12 -14
- package/dist/host/agents/skills/orkestrel-dispatch/SKILL.md +36 -24
- package/dist/host/agents/skills/orkestrel-dispatch/references/launch.md +14 -11
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/bench.ts +366 -75
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/brief.ts +126 -16
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/chain.ts +323 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/cite.ts +220 -31
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/launch.ts +176 -33
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/login.ts +294 -59
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/patch.ts +401 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/result.ts +199 -34
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/sweep.ts +428 -27
- package/dist/host/agents/skills/orkestrel-falsify/SKILL.md +10 -8
- package/dist/host/agents/skills/orkestrel-falsify/references/brief.md +1 -5
- package/dist/host/agents/skills/orkestrel-falsify/references/reconcile.md +1 -1
- package/dist/host/agents/skills/orkestrel-falsify/scripts/mutate.ts +701 -0
- package/dist/host/agents/skills/orkestrel-harden/SKILL.md +16 -12
- package/dist/host/agents/skills/orkestrel-harden/references/bindings.md +23 -81
- package/dist/host/agents/skills/orkestrel-harden/references/centralization.md +4 -5
- package/dist/host/agents/skills/orkestrel-harden/references/contract.md +2 -0
- package/dist/host/agents/skills/orkestrel-harden/references/document.md +33 -0
- package/dist/host/agents/skills/orkestrel-harden/references/equivalence.md +6 -3
- package/dist/host/agents/skills/orkestrel-harden/references/hardening.md +8 -4
- package/dist/host/agents/skills/orkestrel-harden/references/research.md +2 -0
- package/dist/host/agents/skills/orkestrel-harden/scripts/discovery.ts +1272 -279
- package/dist/host/agents/skills/orkestrel-harden/scripts/measure.ts +425 -0
- package/dist/host/agents/skills/orkestrel-harden/scripts/titles.ts +603 -98
- package/dist/host/agents/skills/orkestrel-journey/SKILL.md +75 -98
- package/dist/host/agents/skills/orkestrel-journey/references/captures.md +54 -17
- package/dist/host/agents/skills/orkestrel-journey/references/decide.md +1 -20
- package/dist/host/agents/skills/orkestrel-journey/references/layer.md +21 -31
- package/dist/host/agents/skills/orkestrel-journey/references/styles.md +1 -1
- package/dist/host/agents/skills/orkestrel-parallel/SKILL.md +91 -30
- package/dist/host/agents/skills/orkestrel-parallel/agents/openai.yaml +2 -2
- package/dist/host/agents/skills/orkestrel-parallel/references/claude.md +34 -0
- package/dist/host/agents/skills/orkestrel-parallel/references/codex.md +29 -0
- package/dist/host/agents/skills/orkestrel-parallel/references/cursor.md +29 -0
- package/dist/host/agents/skills/orkestrel-parallel/references/t3.md +31 -0
- package/dist/host/agents/skills/orkestrel-parallel/scripts/cases.ts +389 -0
- package/dist/host/agents/skills/orkestrel-polish/SKILL.md +16 -20
- package/dist/host/agents/skills/orkestrel-polish/references/capture-harness.md +16 -26
- package/dist/host/agents/skills/orkestrel-publish/SKILL.md +12 -26
- package/dist/host/agents/skills/orkestrel-publish/agents/openai.yaml +2 -2
- package/dist/host/agents/skills/orkestrel-publish/references/adopt.md +23 -0
- package/dist/host/agents/skills/orkestrel-publish/references/release.md +4 -4
- package/dist/host/agents/skills/orkestrel-publish/references/wave.md +12 -10
- package/dist/host/agents/skills/orkestrel-publish/references/window.md +20 -25
- package/dist/host/agents/skills/orkestrel-publish/scripts/compare.ts +348 -59
- package/dist/host/agents/skills/orkestrel-publish/scripts/pins.ts +202 -22
- package/dist/host/agents/skills/orkestrel-publish/scripts/swap.ts +1061 -191
- package/dist/host/agents/skills/orkestrel-publish/scripts/wave.ts +812 -140
- package/dist/host/agents/skills/orkestrel-publish/scripts/window.ts +280 -38
- package/dist/host/agents/skills/orkestrel-scout/SKILL.md +3 -3
- package/dist/host/agents/skills/orkestrel-scout/scripts/map.ts +428 -70
- package/dist/host/agents/templates/brief.md +1 -1
- package/dist/host/agents/transports/claude-cli.md +3 -3
- package/dist/host/agents/transports/codex.md +3 -8
- package/dist/host/agents/transports/cursor.md +2 -3
- package/dist/host/claude/AGENTS.md +7 -10
- package/dist/host/claude/agents/analyst.md +3 -3
- package/dist/host/claude/agents/astra.md +3 -3
- package/dist/host/claude/agents/builder.md +1 -1
- package/dist/host/claude/agents/checker.md +1 -1
- package/dist/host/claude/agents/distiller.md +1 -1
- package/dist/host/claude/agents/grok.md +1 -1
- package/dist/host/claude/agents/opus.md +2 -2
- package/dist/host/claude/agents/orkestrel.md +57 -90
- package/dist/host/claude/agents/researcher.md +1 -1
- package/dist/host/claude/agents/reviewer.md +4 -0
- package/dist/host/claude/agents/scout.md +1 -1
- package/dist/host/claude/agents/verifier.md +1 -1
- package/dist/host/claude/rules/application.md +14 -21
- package/dist/host/claude/rules/architecture.md +34 -44
- package/dist/host/claude/rules/browser.md +3 -4
- package/dist/host/claude/rules/campaign.md +55 -0
- package/dist/host/claude/rules/documentation.md +26 -11
- package/dist/host/claude/rules/names.md +9 -4
- package/dist/host/claude/rules/patterns.md +6 -7
- package/dist/host/claude/rules/portability.md +2 -3
- package/dist/host/claude/rules/quality.md +4 -5
- package/dist/host/claude/rules/styles.md +2 -2
- package/dist/host/claude/rules/tests.md +57 -114
- package/dist/host/claude/rules/typescript.md +8 -10
- package/dist/host/claude/rules/workspace.md +21 -19
- package/dist/host/claude/rules/writing.md +3 -3
- package/dist/host/claude/skills/orkestrel-dispatch/SKILL.md +1 -1
- package/dist/host/claude/skills/orkestrel-harden/SKILL.md +1 -1
- package/dist/host/claude/skills/orkestrel-parallel/SKILL.md +1 -1
- package/dist/host/claude/skills/orkestrel-publish/SKILL.md +1 -1
- package/dist/host/claude/skills/orkestrel-scout/SKILL.md +1 -1
- package/dist/host/codex/agents/analyst.toml +9 -3
- package/dist/host/codex/agents/astra.toml +3 -4
- package/dist/host/codex/agents/builder.toml +1 -1
- package/dist/host/codex/agents/opus.toml +3 -4
- package/dist/host/codex/agents/orkestrel.toml +1 -1
- package/dist/host/codex/agents/researcher.toml +2 -2
- package/dist/host/codex/agents/verifier.toml +1 -1
- package/dist/host/codex/config.toml +3 -3
- package/dist/host/configs/helpers.ts +21 -1
- package/dist/host/configs/policy.ts +405 -16
- package/dist/host/cursor/rules/orchestration.mdc +1 -0
- package/dist/host/dotfiles/oxlintrc.json +141 -5
- package/dist/host/guides/agent.md +111 -134
- package/dist/host/guides/capture.md +801 -0
- package/dist/host/guides/probe.md +27 -8
- package/dist/host/guides/scaffold.md +152 -19
- package/dist/host/guides/study.md +534 -440
- package/dist/host/guides/test.md +111 -44
- package/dist/host/manifest.json +199 -242
- package/dist/host/tests/config.test.ts +1558 -219
- package/dist/host/tests/policy.test.ts +127 -1
- package/dist/host/tests/setupPolicy.ts +1389 -39
- package/dist/src/core/index.cjs +239 -87
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +123 -39
- package/dist/src/core/index.d.ts +123 -39
- package/dist/src/core/index.js +234 -86
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +20 -27
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.js +21 -28
- package/dist/src/server/index.js.map +1 -1
- package/package.json +11 -11
- package/dist/agents/skills/orkestrel-dispatch/scripts/helpers.js +0 -283
- package/dist/host/agents/skills/orkestrel-dispatch/references/bench.md +0 -25
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/helpers.ts +0 -333
- package/dist/host/agents/skills/orkestrel-parallel/references/harnesses.md +0 -19
- package/dist/host/guides/supervisor.md +0 -4890
|
@@ -2,17 +2,25 @@
|
|
|
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.
|
|
11
|
-
//
|
|
12
|
-
|
|
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.
|
|
17
|
+
// Name one mode per run; the script refuses a flag it does not read, the `--flag=value` form
|
|
18
|
+
// included. Exit 0 on success, 2 when a deletion is refused, 64 on usage.
|
|
19
|
+
import { spawnSync } from 'node:child_process';
|
|
20
|
+
import { existsSync, readdirSync, readFileSync, realpathSync, rmSync, statSync } from 'node:fs';
|
|
13
21
|
import { basename, join } from 'node:path';
|
|
14
|
-
|
|
15
|
-
const LAUNCH_DIRECTORIES = [
|
|
22
|
+
/** Lists the directories under `tmp/` that hold launch files. */
|
|
23
|
+
export const LAUNCH_DIRECTORIES = [
|
|
16
24
|
'tmp/units',
|
|
17
25
|
'tmp/cursor',
|
|
18
26
|
'tmp/codex',
|
|
@@ -21,13 +29,136 @@ const LAUNCH_DIRECTORIES = [
|
|
|
21
29
|
'tmp/type',
|
|
22
30
|
'tmp/captures',
|
|
23
31
|
];
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
32
|
+
/** Holds the milliseconds within which a changed launch file counts as still in use. */
|
|
33
|
+
export const RECENT_MS = 2 * 60_000;
|
|
34
|
+
/** Holds the minutes a launch file must age before `--tmp` deletes it, when the call gives no threshold. */
|
|
35
|
+
export const DEFAULT_THRESHOLD = 60;
|
|
36
|
+
/** Names the directories a tree walk skips. */
|
|
37
|
+
export const SKIPPED_DIRECTORIES = new Set([
|
|
38
|
+
'node_modules',
|
|
39
|
+
'.git',
|
|
40
|
+
'dist',
|
|
41
|
+
'tmp',
|
|
42
|
+
'.orkestrel',
|
|
43
|
+
'.idea',
|
|
44
|
+
]);
|
|
45
|
+
/** Names no directory: a launch directory holds nothing a walk skips, so every file beneath it counts. */
|
|
46
|
+
export const NOTHING_SKIPPED = new Set();
|
|
47
|
+
/** Holds the buffer size for git's output, which the repository's file count bounds. */
|
|
48
|
+
export const GIT_BUFFER = 64 * 1024 * 1024;
|
|
49
|
+
/**
|
|
50
|
+
* Matches an absolute machine path: a drive-letter path, or one that opens `/Users/`, `/home/`, or
|
|
51
|
+
* `~/`. The lookbehind keeps `https://`, `src/home/`, and `./home/` from matching.
|
|
52
|
+
*/
|
|
53
|
+
export const MACHINE_PATH = /(?<![\w.-])(?:[A-Za-z]:[\\/]|\/(?:Users|home)\/|~\/)/u;
|
|
54
|
+
/** Matches one backticked token in a line. */
|
|
55
|
+
export const BACKTICKED = /`([^`\n]+)`/gu;
|
|
56
|
+
/** Matches the first glob or placeholder character in a path. */
|
|
57
|
+
export const PLACEHOLDER = /[*?<{]/u;
|
|
58
|
+
/** Lists the flags that take a value. */
|
|
59
|
+
export const VALUE_FLAGS = ['--unit', '--older-than'];
|
|
60
|
+
/** Lists every flag the script reads, value flags and switches alike. */
|
|
61
|
+
export const FLAGS = [...VALUE_FLAGS, '--report', '--tmp', '--delete'];
|
|
62
|
+
/** Holds the usage line printed when a call names an unknown mode. */
|
|
63
|
+
export const USAGE = 'usage: sweep.ts --report | --tmp [--older-than MINUTES] | --unit UNIT [--delete]';
|
|
64
|
+
/**
|
|
65
|
+
* Reads the value that follows a flag.
|
|
66
|
+
*
|
|
67
|
+
* @remarks
|
|
68
|
+
* The first occurrence of a flag wins, a following value that opens with `--` is a missing value,
|
|
69
|
+
* and the `--flag=value` form is not read.
|
|
70
|
+
*
|
|
71
|
+
* @param argv - The arguments after the script path.
|
|
72
|
+
* @param name - The flag, such as `--unit`.
|
|
73
|
+
* @returns The value after the flag, or undefined when the flag is absent, last, or followed by
|
|
74
|
+
* another flag.
|
|
75
|
+
*/
|
|
76
|
+
export function readOption(argv, name) {
|
|
77
|
+
const index = argv.indexOf(name);
|
|
78
|
+
const value = index === -1 ? undefined : argv[index + 1];
|
|
79
|
+
return value === undefined || value.startsWith('--') ? undefined : value;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Names the value flags a caller gave with no value.
|
|
83
|
+
*
|
|
84
|
+
* @param argv - The arguments after the script path.
|
|
85
|
+
* @param names - The flags that take a value.
|
|
86
|
+
* @returns Each named flag that appears last or is followed by another flag, in argument order.
|
|
87
|
+
*/
|
|
88
|
+
export function readMissingFlags(argv, names) {
|
|
89
|
+
return names.filter((name) => argv.includes(name) && readOption(argv, name) === undefined);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Names the flags a caller gave that the script does not read.
|
|
93
|
+
*
|
|
94
|
+
* @remarks
|
|
95
|
+
* A `--flag=value` argument is unknown, because the script reads no such form. A bare `--` ends the
|
|
96
|
+
* scan.
|
|
97
|
+
*
|
|
98
|
+
* @param argv - The arguments after the script path.
|
|
99
|
+
* @param flags - Every flag the script reads, value flags and switches alike.
|
|
100
|
+
* @returns Each argument that opens with `--` and is not a read flag, in argument order.
|
|
101
|
+
*/
|
|
102
|
+
export function readUnknownFlags(argv, flags) {
|
|
103
|
+
const end = argv.indexOf('--');
|
|
104
|
+
return (end === -1 ? argv : argv.slice(0, end)).filter((argument) => argument.startsWith('--') && !flags.includes(argument));
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Normalizes a host path to forward slashes.
|
|
108
|
+
*
|
|
109
|
+
* @param path - A host path.
|
|
110
|
+
* @returns The same path with every backslash replaced by a slash.
|
|
111
|
+
*/
|
|
112
|
+
export function normalizeSlashes(path) {
|
|
113
|
+
return path.split('\\').join('/');
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Lists every regular file beneath a directory, skipping the directories the script never reads.
|
|
117
|
+
*
|
|
118
|
+
* @remarks
|
|
119
|
+
* A skipped name matches a directory at any depth, exactly and case-sensitively.
|
|
120
|
+
*
|
|
121
|
+
* @param directory - The directory to walk.
|
|
122
|
+
* @param skipped - The directory names to skip. Default: the skipped directories.
|
|
123
|
+
* @returns Each file's forward-slash path and modification time, in walk order.
|
|
124
|
+
* @throws Thrown when `directory` does not exist or cannot be read.
|
|
125
|
+
*/
|
|
126
|
+
export function listFiles(directory, skipped = SKIPPED_DIRECTORIES) {
|
|
127
|
+
const files = [];
|
|
128
|
+
const pending = [directory];
|
|
129
|
+
while (pending.length > 0) {
|
|
130
|
+
const current = pending.pop();
|
|
131
|
+
if (current === undefined)
|
|
132
|
+
break;
|
|
133
|
+
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
134
|
+
if (skipped.has(entry.name))
|
|
135
|
+
continue;
|
|
136
|
+
const path = join(current, entry.name);
|
|
137
|
+
if (entry.isDirectory())
|
|
138
|
+
pending.push(path);
|
|
139
|
+
else if (entry.isFile()) {
|
|
140
|
+
files.push({ path: normalizeSlashes(path), modified: statSync(path).mtimeMs });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return files;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Formats a modification time as its UTC day.
|
|
148
|
+
*
|
|
149
|
+
* @param modified - A time in milliseconds since the epoch.
|
|
150
|
+
* @returns The day as `YYYY-MM-DD`.
|
|
151
|
+
*/
|
|
152
|
+
export function formatDay(modified) {
|
|
28
153
|
return new Date(modified).toISOString().slice(0, 10);
|
|
29
154
|
}
|
|
30
|
-
|
|
155
|
+
/**
|
|
156
|
+
* Reports how many files a launch directory holds and the age of the oldest.
|
|
157
|
+
*
|
|
158
|
+
* @param directory - The directory to report on.
|
|
159
|
+
* @returns The report line, or undefined when the directory is absent or empty.
|
|
160
|
+
*/
|
|
161
|
+
export function reportDirectory(directory) {
|
|
31
162
|
if (!existsSync(directory))
|
|
32
163
|
return undefined;
|
|
33
164
|
const files = listFiles(directory, NOTHING_SKIPPED);
|
|
@@ -36,14 +167,24 @@ function reportDirectory(directory) {
|
|
|
36
167
|
const oldest = files.reduce((low, file) => Math.min(low, file.modified), Number.POSITIVE_INFINITY);
|
|
37
168
|
return `sweep: ${directory} holds ${files.length} file(s), oldest ${formatDay(oldest)}`;
|
|
38
169
|
}
|
|
39
|
-
|
|
170
|
+
/**
|
|
171
|
+
* Lists the campaign directories under `.orkestrel/`.
|
|
172
|
+
*
|
|
173
|
+
* @returns Each directory's path, or nothing when `.orkestrel/` is absent.
|
|
174
|
+
*/
|
|
175
|
+
export function listCampaignDirectories() {
|
|
40
176
|
if (!existsSync('.orkestrel'))
|
|
41
177
|
return [];
|
|
42
178
|
return readdirSync('.orkestrel', { withFileTypes: true })
|
|
43
179
|
.filter((entry) => entry.isDirectory())
|
|
44
180
|
.map((entry) => join('.orkestrel', entry.name));
|
|
45
181
|
}
|
|
46
|
-
|
|
182
|
+
/**
|
|
183
|
+
* Reports the ecosystem files that sit directly under `.orkestrel/`.
|
|
184
|
+
*
|
|
185
|
+
* @returns The report line, or undefined when there are none.
|
|
186
|
+
*/
|
|
187
|
+
export function reportCampaignFiles() {
|
|
47
188
|
if (!existsSync('.orkestrel'))
|
|
48
189
|
return undefined;
|
|
49
190
|
const files = readdirSync('.orkestrel', { withFileTypes: true }).filter((entry) => entry.isFile());
|
|
@@ -51,7 +192,12 @@ function reportCampaignFiles() {
|
|
|
51
192
|
return undefined;
|
|
52
193
|
return `sweep: .orkestrel holds ${files.length} ecosystem file(s): ${files.map((entry) => entry.name).join(', ')}`;
|
|
53
194
|
}
|
|
54
|
-
|
|
195
|
+
/**
|
|
196
|
+
* Removes every empty directory beneath a directory, deepest first.
|
|
197
|
+
*
|
|
198
|
+
* @param directory - The directory whose empty descendants are removed; the directory itself stays.
|
|
199
|
+
*/
|
|
200
|
+
export function removeEmptyDirectories(directory) {
|
|
55
201
|
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
56
202
|
if (!entry.isDirectory())
|
|
57
203
|
continue;
|
|
@@ -61,13 +207,163 @@ function removeEmptyDirectories(directory) {
|
|
|
61
207
|
rmSync(path, { recursive: true });
|
|
62
208
|
}
|
|
63
209
|
}
|
|
64
|
-
|
|
210
|
+
/**
|
|
211
|
+
* Reads the `--older-than` threshold in minutes.
|
|
212
|
+
*
|
|
213
|
+
* @param argv - The arguments after the script path.
|
|
214
|
+
* @returns The default threshold when the flag is absent, the number when it is finite and above 0,
|
|
215
|
+
* and undefined otherwise.
|
|
216
|
+
*/
|
|
217
|
+
export function readThreshold(argv) {
|
|
65
218
|
if (!argv.includes('--older-than'))
|
|
66
|
-
return
|
|
219
|
+
return DEFAULT_THRESHOLD;
|
|
67
220
|
const value = Number(readOption(argv, '--older-than'));
|
|
68
221
|
return Number.isFinite(value) && value > 0 ? value : undefined;
|
|
69
222
|
}
|
|
70
|
-
|
|
223
|
+
/**
|
|
224
|
+
* Lists the files git tracks beneath a scope, as forward-slash paths from the root.
|
|
225
|
+
*
|
|
226
|
+
* @remarks
|
|
227
|
+
* Any git failure reads as nothing tracked, so the run keeps its old behavior.
|
|
228
|
+
*
|
|
229
|
+
* @param scope - The paths to list beneath; the whole repository when empty.
|
|
230
|
+
* @returns The tracked paths.
|
|
231
|
+
*/
|
|
232
|
+
export function listTracked(scope) {
|
|
233
|
+
const args = ['ls-files', '-z', ...(scope.length === 0 ? [] : ['--', ...scope])];
|
|
234
|
+
const run = spawnSync('git', args, { encoding: 'utf8', windowsHide: true, maxBuffer: GIT_BUFFER });
|
|
235
|
+
if (run.status !== 0)
|
|
236
|
+
return [];
|
|
237
|
+
return run.stdout.split('\0').filter((path) => path !== '');
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Names the probes git ignores, from one batched `git check-ignore`.
|
|
241
|
+
*
|
|
242
|
+
* @remarks
|
|
243
|
+
* Status 0 and 1 both answer, and any other status reads as nothing ignored.
|
|
244
|
+
*
|
|
245
|
+
* @param probes - The paths to ask about.
|
|
246
|
+
* @returns The probes git ignores.
|
|
247
|
+
*/
|
|
248
|
+
export function readIgnored(probes) {
|
|
249
|
+
if (probes.length === 0)
|
|
250
|
+
return new Set();
|
|
251
|
+
const run = spawnSync('git', ['check-ignore', '--stdin', '-z'], {
|
|
252
|
+
encoding: 'utf8',
|
|
253
|
+
windowsHide: true,
|
|
254
|
+
maxBuffer: GIT_BUFFER,
|
|
255
|
+
input: probes.join('\0'),
|
|
256
|
+
});
|
|
257
|
+
if (run.status !== 0 && run.status !== 1)
|
|
258
|
+
return new Set();
|
|
259
|
+
return new Set(run.stdout.split('\0').filter((path) => path !== ''));
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Reads a backticked token as a repository-relative citation.
|
|
263
|
+
*
|
|
264
|
+
* @remarks
|
|
265
|
+
* A token counts when it has no whitespace, contains `/`, and is no URL; the path stops at its first
|
|
266
|
+
* glob or placeholder character and drops a trailing `/`. A machine path, a path outside the
|
|
267
|
+
* repository, and a backslash path are no citation.
|
|
268
|
+
*
|
|
269
|
+
* @param token - The text between two backticks.
|
|
270
|
+
* @returns The citation, or undefined when the token is none.
|
|
271
|
+
*/
|
|
272
|
+
export function readCitation(token) {
|
|
273
|
+
if (/\s/u.test(token) || !token.includes('/') || token.includes('://'))
|
|
274
|
+
return undefined;
|
|
275
|
+
const stop = token.search(PLACEHOLDER);
|
|
276
|
+
const cut = (stop === -1 ? token : token.slice(0, stop)).replace(/^\.\//u, '');
|
|
277
|
+
const path = cut.replace(/\/+$/u, '');
|
|
278
|
+
if (path === '' || /^(?:\/|~|\.\.(?:\/|$)|-|[A-Za-z]:)/u.test(path) || path.includes('\\')) {
|
|
279
|
+
return undefined;
|
|
280
|
+
}
|
|
281
|
+
return { path, directory: cut.endsWith('/'), prefix: stop === -1 ? undefined : cut };
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Lists the findings a campaign Markdown file carries: machine paths and backticked citations.
|
|
285
|
+
*
|
|
286
|
+
* @param file - The Markdown file to read.
|
|
287
|
+
* @returns Each finding with its file and line, a machine path before the citations of its line.
|
|
288
|
+
*/
|
|
289
|
+
export function listFindings(file) {
|
|
290
|
+
const lines = readFileSync(file, 'utf8').split(/\r\n|\n/u);
|
|
291
|
+
return lines.flatMap((text, index) => {
|
|
292
|
+
const line = index + 1;
|
|
293
|
+
const machine = MACHINE_PATH.test(text) ? [{ file, line, citation: undefined }] : [];
|
|
294
|
+
const cited = [...text.matchAll(BACKTICKED)].flatMap((match) => {
|
|
295
|
+
const citation = readCitation(match[1] ?? '');
|
|
296
|
+
return citation === undefined ? [] : [{ file, line, citation }];
|
|
297
|
+
});
|
|
298
|
+
return [...machine, ...cited];
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Builds the path git is asked about for a citation.
|
|
303
|
+
*
|
|
304
|
+
* @param citation - The citation.
|
|
305
|
+
* @returns The path, with a trailing `/` when the citation names a directory.
|
|
306
|
+
*/
|
|
307
|
+
export function probeOf(citation) {
|
|
308
|
+
return citation.directory ? `${citation.path}/` : citation.path;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Checks whether git tracks a citation's path, a file beneath it, or a file its glob prefix opens.
|
|
312
|
+
*
|
|
313
|
+
* @param citation - The citation.
|
|
314
|
+
* @param tracked - The tracked paths.
|
|
315
|
+
* @returns True if a tracked path covers the citation; false otherwise.
|
|
316
|
+
*/
|
|
317
|
+
export function isCovered(citation, tracked) {
|
|
318
|
+
return tracked.some((path) => path === citation.path ||
|
|
319
|
+
path.startsWith(`${citation.path}/`) ||
|
|
320
|
+
(citation.prefix !== undefined && path.startsWith(citation.prefix)));
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Describes a finding as the message the report prints for it.
|
|
324
|
+
*
|
|
325
|
+
* @param finding - The finding.
|
|
326
|
+
* @param ignored - The probes git ignores.
|
|
327
|
+
* @param tracked - The tracked paths.
|
|
328
|
+
* @returns `names a machine path`, `cites ignored path <path>`, or undefined when git tracks the
|
|
329
|
+
* cited path or does not ignore it.
|
|
330
|
+
*/
|
|
331
|
+
export function describeFinding(finding, ignored, tracked) {
|
|
332
|
+
const { citation } = finding;
|
|
333
|
+
if (citation === undefined)
|
|
334
|
+
return 'names a machine path';
|
|
335
|
+
return ignored.has(probeOf(citation)) && !isCovered(citation, tracked)
|
|
336
|
+
? `cites ignored path ${citation.path}`
|
|
337
|
+
: undefined;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Reports the campaign Markdown lines that cite an ignored path or name a machine path.
|
|
341
|
+
*
|
|
342
|
+
* @returns One report line per finding, each file and line and message once.
|
|
343
|
+
*/
|
|
344
|
+
export function reportCitations() {
|
|
345
|
+
if (!existsSync('.orkestrel'))
|
|
346
|
+
return [];
|
|
347
|
+
const findings = listFiles('.orkestrel', NOTHING_SKIPPED)
|
|
348
|
+
.map((file) => file.path)
|
|
349
|
+
.filter((path) => /\.md$/iu.test(path))
|
|
350
|
+
.sort()
|
|
351
|
+
.flatMap((path) => listFindings(path));
|
|
352
|
+
const probes = new Set(findings.flatMap(({ citation }) => (citation === undefined ? [] : [probeOf(citation)])));
|
|
353
|
+
const ignored = readIgnored([...probes]);
|
|
354
|
+
const tracked = ignored.size === 0 ? [] : listTracked([]);
|
|
355
|
+
const lines = new Set(findings.flatMap((finding) => {
|
|
356
|
+
const text = describeFinding(finding, ignored, tracked);
|
|
357
|
+
return text === undefined ? [] : [`${finding.file}:${finding.line} ${text}`];
|
|
358
|
+
}));
|
|
359
|
+
return [...lines].map((key) => `sweep: ${key}`);
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Prints the leftover report.
|
|
363
|
+
*
|
|
364
|
+
* @returns The exit code, always 0.
|
|
365
|
+
*/
|
|
366
|
+
export function runReport() {
|
|
71
367
|
for (const directory of [...LAUNCH_DIRECTORIES, ...listCampaignDirectories()]) {
|
|
72
368
|
const line = reportDirectory(directory);
|
|
73
369
|
if (line !== undefined)
|
|
@@ -76,13 +372,22 @@ function runReport() {
|
|
|
76
372
|
const files = reportCampaignFiles();
|
|
77
373
|
if (files !== undefined)
|
|
78
374
|
console.log(files);
|
|
375
|
+
for (const line of reportCitations())
|
|
376
|
+
console.log(line);
|
|
79
377
|
return 0;
|
|
80
378
|
}
|
|
81
|
-
|
|
379
|
+
/**
|
|
380
|
+
* Deletes the launch files older than a threshold and keeps every tracked file.
|
|
381
|
+
*
|
|
382
|
+
* @param thresholdMinutes - The minutes a launch file must age before it is deleted.
|
|
383
|
+
* @returns The exit code: 0 on success, 2 when a launch file changed within the recent window.
|
|
384
|
+
*/
|
|
385
|
+
export function runSweep(thresholdMinutes) {
|
|
82
386
|
const now = Date.now();
|
|
83
387
|
const present = LAUNCH_DIRECTORIES.filter((directory) => existsSync(directory));
|
|
388
|
+
const tracked = new Set(listTracked(LAUNCH_DIRECTORIES));
|
|
84
389
|
const files = present.flatMap((directory) => listFiles(directory, NOTHING_SKIPPED));
|
|
85
|
-
if (files.some((file) => now - file.modified < RECENT_MS)) {
|
|
390
|
+
if (files.some((file) => !tracked.has(file.path) && now - file.modified < RECENT_MS)) {
|
|
86
391
|
console.error('sweep: a launch file changed within the last 2 minutes; refusing to delete');
|
|
87
392
|
return 2;
|
|
88
393
|
}
|
|
@@ -90,6 +395,10 @@ function runSweep(thresholdMinutes) {
|
|
|
90
395
|
for (const file of files) {
|
|
91
396
|
if (file.modified > cutoff)
|
|
92
397
|
continue;
|
|
398
|
+
if (tracked.has(file.path)) {
|
|
399
|
+
console.log(`sweep: kept tracked ${file.path}`);
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
93
402
|
rmSync(file.path, { force: true });
|
|
94
403
|
console.log(`sweep: deleted ${file.path}`);
|
|
95
404
|
}
|
|
@@ -97,7 +406,13 @@ function runSweep(thresholdMinutes) {
|
|
|
97
406
|
removeEmptyDirectories(directory);
|
|
98
407
|
return 0;
|
|
99
408
|
}
|
|
100
|
-
|
|
409
|
+
/**
|
|
410
|
+
* Lists one unit's files under the launch directories.
|
|
411
|
+
*
|
|
412
|
+
* @param unit - The unit name; a file belongs to it when its name is the unit followed by `-` or `.`.
|
|
413
|
+
* @returns Each file's path and modification time.
|
|
414
|
+
*/
|
|
415
|
+
export function listUnitFiles(unit) {
|
|
101
416
|
return LAUNCH_DIRECTORIES.filter((directory) => existsSync(directory))
|
|
102
417
|
.flatMap((directory) => listFiles(directory, NOTHING_SKIPPED))
|
|
103
418
|
.filter((file) => {
|
|
@@ -105,17 +420,30 @@ function listUnitFiles(unit) {
|
|
|
105
420
|
return name.startsWith(unit) && /^[-.]/u.test(name.slice(unit.length));
|
|
106
421
|
});
|
|
107
422
|
}
|
|
108
|
-
|
|
423
|
+
/**
|
|
424
|
+
* Lists or deletes one unit's launch files.
|
|
425
|
+
*
|
|
426
|
+
* @param unit - The unit name, or undefined when the call gave none.
|
|
427
|
+
* @param remove - If true, deletes the untracked files; if false, lists them.
|
|
428
|
+
* @returns The exit code: 0 on success, 2 when a deletion is refused, 64 on usage.
|
|
429
|
+
*/
|
|
430
|
+
export function runUnit(unit, remove) {
|
|
109
431
|
if (unit === undefined || !/^[A-Za-z0-9._][A-Za-z0-9._-]*$/u.test(unit)) {
|
|
110
432
|
console.error('usage: sweep.ts --unit UNIT [--delete]');
|
|
111
433
|
return 64;
|
|
112
434
|
}
|
|
113
435
|
const files = listUnitFiles(unit);
|
|
114
|
-
|
|
436
|
+
const tracked = new Set(listTracked(LAUNCH_DIRECTORIES));
|
|
437
|
+
if (remove &&
|
|
438
|
+
files.some((file) => !tracked.has(file.path) && Date.now() - file.modified < RECENT_MS)) {
|
|
115
439
|
console.error(`sweep: a file of ${unit} changed within the last 2 minutes; refusing to delete`);
|
|
116
440
|
return 2;
|
|
117
441
|
}
|
|
118
442
|
for (const file of files) {
|
|
443
|
+
if (tracked.has(file.path)) {
|
|
444
|
+
console.log(remove ? `sweep: kept tracked ${file.path}` : `sweep: found ${file.path} (tracked)`);
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
119
447
|
if (remove)
|
|
120
448
|
rmSync(file.path, { force: true });
|
|
121
449
|
console.log(`sweep: ${remove ? 'deleted' : 'found'} ${file.path}`);
|
|
@@ -123,16 +451,27 @@ function runUnit(unit, remove) {
|
|
|
123
451
|
console.log(`sweep: ${files.length} file(s) for ${unit}`);
|
|
124
452
|
return 0;
|
|
125
453
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
454
|
+
/**
|
|
455
|
+
* Runs the script on its arguments.
|
|
456
|
+
*
|
|
457
|
+
* @param argv - The arguments after the script path.
|
|
458
|
+
* @returns The exit code: 0 on success, 2 when a deletion is refused, 64 on usage.
|
|
459
|
+
*/
|
|
460
|
+
export function main(argv) {
|
|
461
|
+
const unknown = readUnknownFlags(argv, FLAGS);
|
|
462
|
+
if (unknown.length > 0) {
|
|
463
|
+
console.error(`sweep: ${unknown.join(', ')} is not a flag this script reads; ${USAGE}`);
|
|
129
464
|
return 64;
|
|
130
465
|
}
|
|
131
|
-
const missing = readMissingFlags(argv,
|
|
466
|
+
const missing = readMissingFlags(argv, VALUE_FLAGS);
|
|
132
467
|
if (missing.length > 0) {
|
|
133
468
|
console.error(`sweep: ${missing.join(', ')} given with no value`);
|
|
134
469
|
return 64;
|
|
135
470
|
}
|
|
471
|
+
if (!existsSync('.git')) {
|
|
472
|
+
console.error('sweep: run from the repository root');
|
|
473
|
+
return 64;
|
|
474
|
+
}
|
|
136
475
|
const modes = ['--report', '--tmp', '--unit'].filter((flag) => argv.includes(flag));
|
|
137
476
|
if (modes.length > 1) {
|
|
138
477
|
console.error('usage: sweep.ts names one mode: --report | --tmp [--older-than MINUTES] | --unit UNIT [--delete]');
|
|
@@ -150,7 +489,9 @@ function main(argv) {
|
|
|
150
489
|
return runUnit(readOption(argv, '--unit'), argv.includes('--delete'));
|
|
151
490
|
if (argv.length === 0 || argv.includes('--report'))
|
|
152
491
|
return runReport();
|
|
153
|
-
console.error(
|
|
492
|
+
console.error(USAGE);
|
|
154
493
|
return 64;
|
|
155
494
|
}
|
|
156
|
-
process.
|
|
495
|
+
if (process.argv[1] !== undefined && realpathSync(process.argv[1]) === import.meta.filename) {
|
|
496
|
+
process.exitCode = main(process.argv.slice(2));
|
|
497
|
+
}
|