@orkestrel/scaffold 0.0.77 → 0.0.78
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 +204 -0
- package/dist/agents/skills/orkestrel-dispatch/scripts/brief.js +102 -0
- package/dist/agents/skills/orkestrel-dispatch/scripts/cite.js +95 -0
- package/dist/agents/skills/orkestrel-dispatch/scripts/helpers.js +207 -0
- package/dist/agents/skills/orkestrel-dispatch/scripts/launch.js +108 -0
- package/dist/agents/skills/orkestrel-dispatch/scripts/login.js +114 -0
- package/dist/agents/skills/orkestrel-dispatch/scripts/result.js +108 -0
- package/dist/agents/skills/orkestrel-dispatch/scripts/sweep.js +156 -0
- package/dist/agents/skills/orkestrel-harden/scripts/discovery.js +196 -0
- package/dist/agents/skills/orkestrel-publish/scripts/compare.js +206 -0
- package/dist/agents/skills/orkestrel-publish/scripts/pins.js +93 -0
- package/dist/agents/skills/orkestrel-publish/scripts/wave.js +458 -0
- package/dist/agents/skills/orkestrel-publish/scripts/window.js +188 -0
- package/dist/agents/skills/orkestrel-scout/scripts/map.js +300 -0
- package/dist/agents/templates/brief.md +55 -0
- package/dist/bin/main.js +4 -2
- package/dist/bin/main.js.map +1 -1
- package/dist/host/AGENTS.md +77 -135
- package/dist/host/agents/orchestration.md +147 -998
- package/dist/host/agents/skills/enterprise-bootstrap/SKILL.md +2 -2
- package/dist/host/agents/skills/enterprise-bootstrap/references/inspection.md +1 -1
- package/dist/host/agents/skills/{orkestrel-align-packages → orkestrel-align}/SKILL.md +6 -13
- package/dist/host/agents/skills/{orkestrel-align-packages → orkestrel-align}/agents/openai.yaml +1 -1
- package/dist/host/agents/skills/{orkestrel-align-packages → orkestrel-align}/references/fleet.md +5 -7
- package/dist/host/agents/skills/{orkestrel-build-application → orkestrel-build}/SKILL.md +11 -22
- package/dist/host/agents/skills/{orkestrel-build-application → orkestrel-build}/agents/openai.yaml +1 -1
- package/dist/host/agents/skills/orkestrel-debrief/SKILL.md +8 -16
- package/dist/host/agents/skills/orkestrel-debrief/references/instruction-audit.md +3 -3
- package/dist/host/agents/skills/orkestrel-debrief/references/retention.md +13 -13
- package/dist/host/agents/skills/orkestrel-dispatch/SKILL.md +61 -0
- package/dist/host/agents/skills/orkestrel-dispatch/agents/openai.yaml +4 -0
- package/dist/host/agents/skills/orkestrel-dispatch/references/bench.md +25 -0
- package/dist/host/agents/skills/orkestrel-dispatch/references/launch.md +32 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/bench.ts +259 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/brief.ts +110 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/cite.ts +115 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/helpers.ts +239 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/launch.ts +124 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/login.ts +123 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/result.ts +129 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/sweep.ts +157 -0
- package/dist/host/agents/skills/orkestrel-falsify/SKILL.md +42 -193
- package/dist/host/agents/skills/orkestrel-falsify/references/brief.md +38 -108
- package/dist/host/agents/skills/orkestrel-falsify/references/reconcile.md +35 -134
- package/dist/host/agents/skills/{orkestrel-harden-package → orkestrel-harden}/SKILL.md +10 -14
- package/dist/host/agents/skills/{orkestrel-harden-package → orkestrel-harden}/agents/openai.yaml +1 -1
- package/dist/host/agents/skills/{orkestrel-harden-package → orkestrel-harden}/references/hardening.md +3 -4
- package/dist/host/agents/skills/orkestrel-harden/scripts/discovery.ts +228 -0
- package/dist/host/agents/skills/{orkestrel-prove-journey → orkestrel-journey}/SKILL.md +15 -23
- package/dist/host/agents/skills/orkestrel-journey/agents/openai.yaml +4 -0
- package/dist/host/agents/skills/{orkestrel-prove-journey → orkestrel-journey}/references/captures.md +1 -1
- package/dist/host/agents/skills/{orkestrel-polish-surface → orkestrel-polish}/SKILL.md +25 -33
- package/dist/host/agents/skills/{orkestrel-polish-surface → orkestrel-polish}/agents/openai.yaml +1 -1
- package/dist/host/agents/skills/{orkestrel-polish-surface → orkestrel-polish}/references/capture-harness.md +3 -3
- package/dist/host/agents/skills/orkestrel-publish/SKILL.md +33 -20
- package/dist/host/agents/skills/orkestrel-publish/references/release.md +39 -0
- package/dist/host/agents/skills/orkestrel-publish/references/wave.md +22 -21
- package/dist/host/agents/skills/orkestrel-publish/references/window.md +27 -14
- package/dist/host/agents/skills/orkestrel-publish/scripts/compare.ts +220 -0
- package/dist/host/agents/skills/orkestrel-publish/scripts/pins.ts +114 -0
- package/dist/host/agents/skills/orkestrel-publish/scripts/wave.ts +629 -0
- package/dist/host/agents/skills/orkestrel-publish/scripts/window.ts +242 -0
- package/dist/host/agents/skills/orkestrel-scout/SKILL.md +28 -0
- package/dist/host/agents/skills/orkestrel-scout/agents/openai.yaml +4 -0
- package/dist/host/agents/skills/orkestrel-scout/scripts/map.ts +352 -0
- package/dist/host/agents/templates/brief.md +21 -142
- package/dist/host/agents/transports/claude-cli.md +21 -0
- package/dist/host/agents/transports/codex.md +38 -159
- package/dist/host/agents/transports/cursor.md +16 -65
- package/dist/host/claude/AGENTS.md +38 -0
- package/dist/host/claude/agents/analyst.md +14 -53
- package/dist/host/claude/agents/astra.md +26 -0
- package/dist/host/claude/agents/builder.md +14 -30
- package/dist/host/claude/agents/checker.md +13 -57
- package/dist/host/claude/agents/distiller.md +11 -26
- package/dist/host/claude/agents/grok.md +12 -35
- package/dist/host/claude/agents/opus.md +14 -30
- package/dist/host/claude/agents/planner.md +10 -44
- package/dist/host/claude/agents/researcher.md +11 -30
- package/dist/host/claude/agents/reviewer.md +11 -95
- package/dist/host/claude/agents/scout.md +9 -23
- package/dist/host/claude/agents/verifier.md +15 -33
- package/dist/host/claude/rules/documentation.md +8 -2
- package/dist/host/claude/rules/portability.md +7 -1
- package/dist/host/claude/rules/quality.md +36 -96
- package/dist/host/claude/rules/styles.md +3 -0
- package/dist/host/claude/rules/tests.md +6 -3
- package/dist/host/claude/rules/workspace.md +19 -15
- package/dist/host/claude/rules/writing.md +57 -108
- package/dist/host/claude/settings.json +5 -3
- package/dist/host/claude/skills/enterprise-bootstrap/SKILL.md +1 -1
- package/dist/host/claude/skills/{orkestrel-align-packages → orkestrel-align}/SKILL.md +2 -2
- package/dist/host/claude/skills/{orkestrel-build-application → orkestrel-build}/SKILL.md +2 -2
- package/dist/host/claude/skills/orkestrel-dispatch/SKILL.md +11 -0
- package/dist/host/claude/skills/orkestrel-falsify/SKILL.md +2 -1
- package/dist/host/claude/skills/{orkestrel-harden-package → orkestrel-harden}/SKILL.md +2 -2
- package/dist/host/claude/skills/{orkestrel-prove-journey → orkestrel-journey}/SKILL.md +2 -2
- package/dist/host/claude/skills/orkestrel-polish/SKILL.md +12 -0
- package/dist/host/claude/skills/orkestrel-scout/SKILL.md +11 -0
- package/dist/host/codex/agents/analyst.toml +14 -31
- package/dist/host/codex/agents/astra.toml +25 -0
- package/dist/host/codex/agents/builder.toml +13 -20
- package/dist/host/codex/agents/checker.toml +13 -27
- package/dist/host/codex/agents/distiller.toml +9 -22
- package/dist/host/codex/agents/grok.toml +11 -30
- package/dist/host/codex/agents/opus.toml +14 -22
- package/dist/host/codex/agents/orkestrel.toml +1 -1
- package/dist/host/codex/agents/planner.toml +11 -28
- package/dist/host/codex/agents/researcher.toml +10 -22
- package/dist/host/codex/agents/reviewer.toml +11 -27
- package/dist/host/codex/agents/scout.toml +11 -17
- package/dist/host/codex/agents/verifier.toml +16 -12
- package/dist/host/codex/config.toml +18 -21
- package/dist/host/cursor/mcp.json +0 -4
- package/dist/host/cursor/rules/orchestration.mdc +12 -20
- package/dist/host/dotfiles/mcp.json +0 -4
- package/dist/host/dotfiles/oxlintrc.json +7 -0
- package/dist/host/guides/probe.md +9 -9
- package/dist/host/guides/scaffold.md +117 -71
- package/dist/host/guides/test.md +1 -1
- package/dist/host/manifest.json +321 -184
- package/dist/host/scripts/codex.sh +0 -0
- package/dist/host/scripts/cursor.sh +0 -0
- package/dist/host/scripts/deps.sh +0 -0
- package/dist/host/scripts/ollama.sh +0 -0
- package/dist/host/tests/config.test.ts +68 -46
- package/dist/host/tests/policy.test.ts +1 -5
- package/dist/host/tests/setupPolicy.ts +179 -4
- package/dist/src/core/index.cjs +255 -84
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +94 -29
- package/dist/src/core/index.d.ts +94 -29
- package/dist/src/core/index.js +253 -85
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +55 -9
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +29 -4
- package/dist/src/server/index.d.ts +29 -4
- package/dist/src/server/index.js +56 -11
- package/dist/src/server/index.js.map +1 -1
- package/package.json +15 -11
- package/dist/host/CLAUDE.md +0 -61
- package/dist/host/agents/skills/orkestrel-prove-journey/agents/openai.yaml +0 -4
- package/dist/host/agents/transports/claude.md +0 -49
- package/dist/host/claude/agents/application.md +0 -36
- package/dist/host/claude/agents/sol.md +0 -61
- package/dist/host/claude/skills/orkestrel-polish-surface/SKILL.md +0 -12
- package/dist/host/codex/agents/application.toml +0 -25
- package/dist/host/codex/agents/sol.toml +0 -19
- /package/dist/host/agents/skills/{orkestrel-align-packages → orkestrel-align}/references/integration.md +0 -0
- /package/dist/host/agents/skills/{orkestrel-harden-package → orkestrel-harden}/references/centralization.md +0 -0
- /package/dist/host/agents/skills/{orkestrel-harden-package → orkestrel-harden}/references/contract.md +0 -0
- /package/dist/host/agents/skills/{orkestrel-harden-package → orkestrel-harden}/references/research.md +0 -0
- /package/dist/host/agents/skills/{orkestrel-prove-journey → orkestrel-journey}/references/decide.md +0 -0
- /package/dist/host/agents/skills/{orkestrel-prove-journey → orkestrel-journey}/references/layer.md +0 -0
- /package/dist/host/agents/skills/{orkestrel-prove-journey → orkestrel-journey}/references/statechart.md +0 -0
- /package/dist/host/agents/skills/{orkestrel-prove-journey → orkestrel-journey}/references/styles.md +0 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
// Work the npm authentication window. Run from the checkout root:
|
|
2
|
+
// node .agents/skills/orkestrel-publish/scripts/window.ts --whoami [--wait SECONDS]
|
|
3
|
+
// node .agents/skills/orkestrel-publish/scripts/window.ts --login [--wait SECONDS]
|
|
4
|
+
// node .agents/skills/orkestrel-publish/scripts/window.ts --publish DIR... --otp CODE [--json]
|
|
5
|
+
// node .agents/skills/orkestrel-publish/scripts/window.ts --confirm NAME@VERSION... [--wait SECONDS]
|
|
6
|
+
// --whoami reads `npm whoami` once, or polls it every 5 seconds until it answers or the wait ends.
|
|
7
|
+
// --login prints the command the operator runs in a real terminal (npm offers its approval only to a
|
|
8
|
+
// TTY, so no child of this script can hold it), then polls `npm whoami` until it answers or the wait
|
|
9
|
+
// ends. --publish re-reads `npm whoami` immediately before the first upload and refuses when it
|
|
10
|
+
// answers nothing, then runs `npm publish --ignore-scripts --browser=false --otp=CODE` in each
|
|
11
|
+
// directory back to back under captured pipes (npm's non-TTY guard turns a refused code into
|
|
12
|
+
// EOTP with no prompt, so the path needs no TTY), journals each upload to tmp/units/publish-<name>.log, reads the `+ name@version`
|
|
13
|
+
// acceptance line, stops at the first refusal and names the package to resume from, then confirms
|
|
14
|
+
// each accepted version against the registry. --confirm re-reads the registry for each name@version every 5
|
|
15
|
+
// seconds until it serves the version or the wait (default 120 seconds) ends. Exit 0 when the mode
|
|
16
|
+
// succeeded, 3 when it did not, 64 on usage.
|
|
17
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
18
|
+
import { join } from 'node:path'
|
|
19
|
+
import {
|
|
20
|
+
readList,
|
|
21
|
+
readMissingFlags,
|
|
22
|
+
readOption,
|
|
23
|
+
readString,
|
|
24
|
+
runNpm,
|
|
25
|
+
} from '../../orkestrel-dispatch/scripts/helpers.ts'
|
|
26
|
+
|
|
27
|
+
const POLL_MS = 5_000
|
|
28
|
+
const ACCEPTED = /^\+ (\S+)@(\S+)\s*$/mu
|
|
29
|
+
|
|
30
|
+
interface Upload {
|
|
31
|
+
readonly directory: string
|
|
32
|
+
readonly name: string | undefined
|
|
33
|
+
readonly version: string | undefined
|
|
34
|
+
readonly exit: number | undefined
|
|
35
|
+
readonly accepted: boolean
|
|
36
|
+
readonly confirmed: boolean
|
|
37
|
+
readonly code: string | undefined
|
|
38
|
+
readonly journal: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function waitFor(ms: number): Promise<void> {
|
|
42
|
+
return new Promise((resolve) => {
|
|
43
|
+
setTimeout(resolve, ms)
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function readWhoami(): string | undefined {
|
|
48
|
+
const result = runNpm(['whoami'])
|
|
49
|
+
const user = result.stdout.trim()
|
|
50
|
+
return result.status === 0 && user !== '' ? user : undefined
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function pollWhoami(waitSeconds: number): Promise<string | undefined> {
|
|
54
|
+
const started = Date.now()
|
|
55
|
+
let user = readWhoami()
|
|
56
|
+
while (user === undefined && Date.now() - started < waitSeconds * 1000) {
|
|
57
|
+
await waitFor(POLL_MS)
|
|
58
|
+
user = readWhoami()
|
|
59
|
+
}
|
|
60
|
+
return user
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function readServed(name: string, version: string): boolean {
|
|
64
|
+
const result = runNpm(['view', `${name}@${version}`, 'version', '--json'])
|
|
65
|
+
return result.status === 0 && result.stdout.replace(/["\s]/gu, '') === version
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function confirmServed(name: string, version: string, waitSeconds: number): Promise<boolean> {
|
|
69
|
+
const started = Date.now()
|
|
70
|
+
let served = readServed(name, version)
|
|
71
|
+
while (!served && Date.now() - started < waitSeconds * 1000) {
|
|
72
|
+
await waitFor(POLL_MS)
|
|
73
|
+
served = readServed(name, version)
|
|
74
|
+
}
|
|
75
|
+
return served
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function readErrorCode(text: string): string | undefined {
|
|
79
|
+
return text.match(/\b(E[A-Z0-9]{3,}|EOTP|E401|E403|E404)\b/u)?.[1]
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function readManifestName(directory: string): {
|
|
83
|
+
readonly name: string | undefined
|
|
84
|
+
readonly version: string | undefined
|
|
85
|
+
} {
|
|
86
|
+
const path = join(directory, 'package.json')
|
|
87
|
+
if (!existsSync(path)) return { name: undefined, version: undefined }
|
|
88
|
+
try {
|
|
89
|
+
const parsed: unknown = JSON.parse(readFileSync(path, 'utf8'))
|
|
90
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
91
|
+
return { name: undefined, version: undefined }
|
|
92
|
+
const record = Object.fromEntries(Object.entries(parsed))
|
|
93
|
+
return { name: readString(record, 'name'), version: readString(record, 'version') }
|
|
94
|
+
} catch {
|
|
95
|
+
return { name: undefined, version: undefined }
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function publishAll(
|
|
100
|
+
directories: readonly string[],
|
|
101
|
+
otp: string,
|
|
102
|
+
waitSeconds: number,
|
|
103
|
+
): Promise<readonly Upload[]> {
|
|
104
|
+
mkdirSync('tmp/units', { recursive: true })
|
|
105
|
+
const uploads: Upload[] = []
|
|
106
|
+
for (const directory of directories) {
|
|
107
|
+
const manifest = readManifestName(directory)
|
|
108
|
+
const journal = `tmp/units/publish-${(manifest.name ?? directory).replace(/[^A-Za-z0-9.-]+/gu, '-')}.log`
|
|
109
|
+
const result = runNpm(
|
|
110
|
+
['publish', '--ignore-scripts', '--browser=false', `--otp=${otp}`],
|
|
111
|
+
directory,
|
|
112
|
+
)
|
|
113
|
+
writeFileSync(journal, `${result.stdout}\n${result.stderr}\nexit=${result.status ?? 'null'}\n`)
|
|
114
|
+
const match = `${result.stdout}\n${result.stderr}`.match(ACCEPTED)
|
|
115
|
+
const accepted = result.status === 0 && match !== null
|
|
116
|
+
const name = match?.[1] ?? manifest.name
|
|
117
|
+
const version = match?.[2] ?? manifest.version
|
|
118
|
+
const upload: Upload = {
|
|
119
|
+
directory,
|
|
120
|
+
name,
|
|
121
|
+
version,
|
|
122
|
+
exit: result.status ?? undefined,
|
|
123
|
+
accepted,
|
|
124
|
+
confirmed: false,
|
|
125
|
+
code: accepted ? undefined : readErrorCode(result.stderr),
|
|
126
|
+
journal,
|
|
127
|
+
}
|
|
128
|
+
if (!accepted) {
|
|
129
|
+
uploads.push(upload)
|
|
130
|
+
console.error(
|
|
131
|
+
`window: ${name ?? directory} refused (${upload.code ?? `exit ${String(result.status)}`}); resume from it on a fresh code`,
|
|
132
|
+
)
|
|
133
|
+
break
|
|
134
|
+
}
|
|
135
|
+
console.error(`window: accepted ${name ?? '?'}@${version ?? '?'}`)
|
|
136
|
+
uploads.push(upload)
|
|
137
|
+
}
|
|
138
|
+
const confirmed: Upload[] = []
|
|
139
|
+
for (const upload of uploads) {
|
|
140
|
+
if (!upload.accepted || upload.name === undefined || upload.version === undefined) {
|
|
141
|
+
confirmed.push(upload)
|
|
142
|
+
continue
|
|
143
|
+
}
|
|
144
|
+
confirmed.push({
|
|
145
|
+
...upload,
|
|
146
|
+
confirmed: await confirmServed(upload.name, upload.version, waitSeconds),
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
return confirmed
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function main(argv: readonly string[]): Promise<number> {
|
|
153
|
+
const missing = readMissingFlags(argv, ['--wait', '--otp'])
|
|
154
|
+
if (missing.length > 0) {
|
|
155
|
+
console.error(`window: ${missing.join(', ')} given with no value`)
|
|
156
|
+
return 64
|
|
157
|
+
}
|
|
158
|
+
const wait = Number(readOption(argv, '--wait') ?? 120)
|
|
159
|
+
const modes = ['--whoami', '--login', '--publish', '--confirm'].filter((flag) =>
|
|
160
|
+
argv.includes(flag),
|
|
161
|
+
)
|
|
162
|
+
if (modes.length > 1 || (argv.includes('--wait') && (!Number.isFinite(wait) || wait < 0))) {
|
|
163
|
+
console.error(
|
|
164
|
+
'usage: window.ts names one mode (--whoami | --login | --publish | --confirm), and --wait takes a number of seconds',
|
|
165
|
+
)
|
|
166
|
+
return 64
|
|
167
|
+
}
|
|
168
|
+
if (argv.includes('--whoami')) {
|
|
169
|
+
const user = await pollWhoami(argv.includes('--wait') ? wait : 0)
|
|
170
|
+
console.log(JSON.stringify({ mode: 'whoami', user, live: user !== undefined }))
|
|
171
|
+
return user === undefined ? 3 : 0
|
|
172
|
+
}
|
|
173
|
+
if (argv.includes('--login')) {
|
|
174
|
+
const command =
|
|
175
|
+
process.platform === 'win32'
|
|
176
|
+
? 'npm login --browser=false'
|
|
177
|
+
: "script -qfc 'npm login --browser=false' tmp/units/npm-login.log"
|
|
178
|
+
console.error(
|
|
179
|
+
`window: run this in a real terminal, then approve the URL it prints on its first line: ${command}`,
|
|
180
|
+
)
|
|
181
|
+
const user = await pollWhoami(argv.includes('--wait') ? wait : 300)
|
|
182
|
+
console.log(JSON.stringify({ mode: 'login', command, user, live: user !== undefined }))
|
|
183
|
+
return user === undefined ? 3 : 0
|
|
184
|
+
}
|
|
185
|
+
if (argv.includes('--publish')) {
|
|
186
|
+
const directories = readList(argv, '--publish')
|
|
187
|
+
const otp = readOption(argv, '--otp')
|
|
188
|
+
if (
|
|
189
|
+
directories.length === 0 ||
|
|
190
|
+
otp === undefined ||
|
|
191
|
+
!/^\d{6,8}$/u.test(otp) ||
|
|
192
|
+
directories.some((directory) => !existsSync(join(directory, 'package.json')))
|
|
193
|
+
) {
|
|
194
|
+
console.error(
|
|
195
|
+
"usage: window.ts --publish DIR... --otp CODE [--wait SECONDS] [--json]; every DIR carries a package.json and CODE is the account's one-time code",
|
|
196
|
+
)
|
|
197
|
+
return 64
|
|
198
|
+
}
|
|
199
|
+
// A stored credential expires mid-session, so the session-start answer does not hold here.
|
|
200
|
+
if (readWhoami() === undefined) {
|
|
201
|
+
console.error(
|
|
202
|
+
'window: npm whoami answers nothing immediately before the upload; run --login first',
|
|
203
|
+
)
|
|
204
|
+
return 3
|
|
205
|
+
}
|
|
206
|
+
const uploads = await publishAll(directories, otp, wait)
|
|
207
|
+
if (argv.includes('--json')) console.log(JSON.stringify({ mode: 'publish', uploads }))
|
|
208
|
+
else {
|
|
209
|
+
for (const upload of uploads) {
|
|
210
|
+
console.log(
|
|
211
|
+
`window: ${upload.directory}\t${upload.name ?? '?'}@${upload.version ?? '?'}\t${upload.accepted ? 'accepted' : `refused ${upload.code ?? String(upload.exit)}`}\t${upload.confirmed ? 'confirmed' : 'unconfirmed'}\t${upload.journal}`,
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return uploads.every((upload) => upload.accepted && upload.confirmed) ? 0 : 3
|
|
216
|
+
}
|
|
217
|
+
if (argv.includes('--confirm')) {
|
|
218
|
+
const targets = readList(argv, '--confirm')
|
|
219
|
+
if (targets.length === 0 || targets.some((target) => !/^@?[^@]+@\S+$/u.test(target))) {
|
|
220
|
+
console.error('usage: window.ts --confirm NAME@VERSION... [--wait SECONDS]')
|
|
221
|
+
return 64
|
|
222
|
+
}
|
|
223
|
+
const readings: Array<{ readonly target: string; readonly served: boolean }> = []
|
|
224
|
+
for (const target of targets) {
|
|
225
|
+
const at = target.lastIndexOf('@')
|
|
226
|
+
readings.push({
|
|
227
|
+
target,
|
|
228
|
+
served: await confirmServed(target.slice(0, at), target.slice(at + 1), wait),
|
|
229
|
+
})
|
|
230
|
+
}
|
|
231
|
+
console.log(JSON.stringify({ mode: 'confirm', readings }))
|
|
232
|
+
return readings.every((reading) => reading.served) ? 0 : 3
|
|
233
|
+
}
|
|
234
|
+
console.error(
|
|
235
|
+
'usage: window.ts --whoami [--wait SECONDS] | --login [--wait SECONDS] | --publish DIR... --otp CODE [--json] | --confirm NAME@VERSION... [--wait SECONDS]',
|
|
236
|
+
)
|
|
237
|
+
return 64
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
main(process.argv.slice(2)).then((code) => {
|
|
241
|
+
process.exitCode = code
|
|
242
|
+
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: orkestrel-scout
|
|
3
|
+
description: >-
|
|
4
|
+
Map terrain mechanically before anyone reads it: file trees with line counts, Markdown headings, export lines, a term census, the files that name a path, and unresolved `§` cross-references, written once and handed to the lane that reads. Use before writing a brief, before a Grok absorption lane, before a rename or a sweep, when a dispatch names the `scout` role, or whenever a question is "where is", "how big is", "what names this", or "which files carry this term".
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Scout
|
|
8
|
+
|
|
9
|
+
A map is mechanical; reading is not. Run the map first, hand it to the reader, and let the reader spend its context on judgment.
|
|
10
|
+
|
|
11
|
+
## Scripts
|
|
12
|
+
|
|
13
|
+
| Script | Does |
|
|
14
|
+
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
15
|
+
| `scripts/map.ts` | `--tree PATH...` files with line counts and sizes; `--headings PATH...` Markdown headings with lines; `--frontmatter PATH...` every key of a frontmatter block, a TOML file, or a YAML file with its whole value, nested keys dotted (`interface.default_prompt`); `--exports PATH...` `export` lines in TypeScript files (a text reading); `--census TERM...` every line carrying a term; `--refs PATH` every line naming a path or its basename; `--anchors PATH...` every `§ Heading` reference whose heading its target lacks. `--paths a,b` narrows a census or refs walk; `--ignore-case`; `--json`; `--out FILE` writes the map instead of printing it. Walks skip `node_modules`, `.git`, `dist`, `tmp`, `.orkestrel`, `.idea`, and `host.json`. |
|
|
16
|
+
|
|
17
|
+
## Map
|
|
18
|
+
|
|
19
|
+
1. Name the question and the roots it covers. Refuse an unbounded one.
|
|
20
|
+
2. Run `node .agents/skills/orkestrel-scout/scripts/map.ts` with the modes the question needs, in one call. Write the map to `tmp/units/<unit>-map.txt` (or `.json`) when a lane will read it; print it when you will.
|
|
21
|
+
3. For a rename or a deletion, run `--refs <path>` and `--census <name>` and own every file that comes back.
|
|
22
|
+
4. Before an absorption lane, put the map's path in the brief and strike from the brief every question the map already answers; the lane reads and judges, it does not enumerate.
|
|
23
|
+
5. Before a brief, read `--anchors` over the files the brief names; repair the references that block the brief and report the rest.
|
|
24
|
+
|
|
25
|
+
## Refuse
|
|
26
|
+
|
|
27
|
+
- A question that needs reading at depth or a judgment: route it to `grok`, `distiller`, or the reviewer the size gate names, with the map attached.
|
|
28
|
+
- A census over a term shorter than three characters without `--paths`.
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
// Map terrain before a brief, an absorption lane, or a rename. Run from the checkout root:
|
|
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
|
+
// --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
|
|
8
|
+
// every line naming a path or its basename. --anchors lists every `§ Heading` reference whose
|
|
9
|
+
// heading the named file, or the same file, does not carry. Walks skip node_modules, .git, dist,
|
|
10
|
+
// tmp, .orkestrel, .idea, and host.json; --paths narrows a census or refs walk. Modes combine, each
|
|
11
|
+
// printing its section; --json prints one object; --out writes the output to FILE instead of the
|
|
12
|
+
// terminal. Give each flag once; a later duplicate is not read. Exit 0, or 64 when no mode is
|
|
13
|
+
// given or --refs names no path.
|
|
14
|
+
import { existsSync, readFileSync, statSync, mkdirSync, writeFileSync } from 'node:fs'
|
|
15
|
+
import { basename, dirname } from 'node:path'
|
|
16
|
+
import {
|
|
17
|
+
listFiles,
|
|
18
|
+
normalizeSlashes,
|
|
19
|
+
readList,
|
|
20
|
+
readMissingFlags,
|
|
21
|
+
readOption,
|
|
22
|
+
} from '../../orkestrel-dispatch/scripts/helpers.ts'
|
|
23
|
+
|
|
24
|
+
const EXPORT =
|
|
25
|
+
/^export\s+(?:default\s+)?(?:async\s+)?(?:abstract\s+)?(function\*?|const|let|class|interface|type|enum|namespace)\s+([A-Za-z_$][\w$]*)/u
|
|
26
|
+
const HEADING = /^(#{1,6})\s+(.+?)\s*#*\s*$/u
|
|
27
|
+
const ANCHOR =
|
|
28
|
+
/(?:`([^`\n]+\.md)`\s*)?§\s*"?([^"`\n|;,.)]+?)"?(?=\s*(?:[,.;)|]|\s(?:and|or|names|fixes|owns|states|binds|governs|decides|before|after)\b|$))/gu
|
|
29
|
+
const BINARY = /\.(png|jpg|jpeg|gif|webp|ico|woff2?|ttf|otf|eot|zip|tgz|gz|pdf|wasm)$/u
|
|
30
|
+
|
|
31
|
+
interface TreeRow {
|
|
32
|
+
readonly path: string
|
|
33
|
+
readonly lines: number
|
|
34
|
+
readonly bytes: number
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface LineRow {
|
|
38
|
+
readonly path: string
|
|
39
|
+
readonly line: number
|
|
40
|
+
readonly text: string
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface TermRows {
|
|
44
|
+
readonly term: string
|
|
45
|
+
readonly rows: readonly LineRow[]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface AnchorRow {
|
|
49
|
+
readonly path: string
|
|
50
|
+
readonly line: number
|
|
51
|
+
readonly target: string
|
|
52
|
+
readonly heading: string
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface Report {
|
|
56
|
+
readonly tree?: readonly TreeRow[]
|
|
57
|
+
readonly headings?: readonly LineRow[]
|
|
58
|
+
readonly frontmatter?: readonly LineRow[]
|
|
59
|
+
readonly exports?: readonly LineRow[]
|
|
60
|
+
readonly census?: readonly TermRows[]
|
|
61
|
+
readonly refs?: readonly LineRow[]
|
|
62
|
+
readonly anchors?: readonly AnchorRow[]
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function isText(path: string): boolean {
|
|
66
|
+
return !BINARY.test(path) && basename(path) !== 'host.json'
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function expandPaths(paths: readonly string[]): readonly string[] {
|
|
70
|
+
return paths.flatMap((path) => {
|
|
71
|
+
if (!existsSync(path)) return []
|
|
72
|
+
if (statSync(path).isDirectory()) return listFiles(path).map((file) => file.path)
|
|
73
|
+
return [normalizeSlashes(path)]
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function readLines(path: string): readonly string[] {
|
|
78
|
+
return readFileSync(path, 'utf8').split(/\r\n|\n/)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function mapTree(paths: readonly string[]): readonly TreeRow[] {
|
|
82
|
+
return expandPaths(paths)
|
|
83
|
+
.filter(isText)
|
|
84
|
+
.map((path) => {
|
|
85
|
+
const text = readFileSync(path, 'utf8')
|
|
86
|
+
return {
|
|
87
|
+
path,
|
|
88
|
+
lines: text === '' ? 0 : text.split(/\r\n|\n/).length,
|
|
89
|
+
bytes: Buffer.byteLength(text),
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
.sort((left, right) => left.path.localeCompare(right.path))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function mapHeadings(paths: readonly string[]): readonly LineRow[] {
|
|
96
|
+
const rows: LineRow[] = []
|
|
97
|
+
for (const path of expandPaths(paths).filter((candidate) => candidate.endsWith('.md'))) {
|
|
98
|
+
let fenced = false
|
|
99
|
+
readLines(path).forEach((text, index) => {
|
|
100
|
+
if (text.startsWith('```')) fenced = !fenced
|
|
101
|
+
if (fenced) return
|
|
102
|
+
const match = text.match(HEADING)
|
|
103
|
+
if (match?.[1] !== undefined && match[2] !== undefined) {
|
|
104
|
+
rows.push({ path, line: index + 1, text: `${match[1]} ${match[2]}` })
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
return rows
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function mapExports(paths: readonly string[]): readonly LineRow[] {
|
|
112
|
+
const rows: LineRow[] = []
|
|
113
|
+
for (const path of expandPaths(paths).filter((candidate) => /\.[cm]?ts$/u.test(candidate))) {
|
|
114
|
+
readLines(path).forEach((text, index) => {
|
|
115
|
+
const match = text.match(EXPORT)
|
|
116
|
+
if (match?.[1] !== undefined && match[2] !== undefined) {
|
|
117
|
+
rows.push({ path, line: index + 1, text: `${match[1]} ${match[2]}` })
|
|
118
|
+
} else if (/^export\s+\*\s+from\s+/u.test(text)) {
|
|
119
|
+
rows.push({ path, line: index + 1, text: text.trim() })
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
return rows
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function buildMatcher(term: string, ignoreCase: boolean): (text: string) => boolean {
|
|
127
|
+
const needle = ignoreCase ? term.toLowerCase() : term
|
|
128
|
+
return (text) => (ignoreCase ? text.toLowerCase() : text).includes(needle)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function mapCensus(
|
|
132
|
+
terms: readonly string[],
|
|
133
|
+
roots: readonly string[],
|
|
134
|
+
ignoreCase: boolean,
|
|
135
|
+
): readonly TermRows[] {
|
|
136
|
+
const files = expandPaths(roots).filter(isText)
|
|
137
|
+
return terms.map((term) => {
|
|
138
|
+
const matches = buildMatcher(term, ignoreCase)
|
|
139
|
+
const rows: LineRow[] = []
|
|
140
|
+
for (const path of files) {
|
|
141
|
+
readLines(path).forEach((text, index) => {
|
|
142
|
+
if (matches(text)) rows.push({ path, line: index + 1, text: text.trim().slice(0, 160) })
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
return { term, rows }
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function mapRefs(target: string, roots: readonly string[]): readonly LineRow[] {
|
|
150
|
+
const needle = normalizeSlashes(target)
|
|
151
|
+
const name = basename(needle)
|
|
152
|
+
const rows: LineRow[] = []
|
|
153
|
+
for (const path of expandPaths(roots).filter(isText)) {
|
|
154
|
+
if (path === needle) continue
|
|
155
|
+
readLines(path).forEach((text, index) => {
|
|
156
|
+
if (text.includes(needle) || text.includes(name)) {
|
|
157
|
+
rows.push({ path, line: index + 1, text: text.trim().slice(0, 160) })
|
|
158
|
+
}
|
|
159
|
+
})
|
|
160
|
+
}
|
|
161
|
+
return rows
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function readYamlKeys(path: string, lines: readonly string[], offset: number): readonly LineRow[] {
|
|
165
|
+
const rows: LineRow[] = []
|
|
166
|
+
const stack: Array<{ readonly indent: number; readonly key: string }> = []
|
|
167
|
+
lines.forEach((text, index) => {
|
|
168
|
+
const content = text.trimStart()
|
|
169
|
+
if (content === '' || content.startsWith('#')) return
|
|
170
|
+
const indent = text.length - content.length
|
|
171
|
+
const match = content.match(/^([A-Za-z_][\w-]*)\s*:(?:\s+(.*))?$/u)
|
|
172
|
+
if (match?.[1] === undefined) {
|
|
173
|
+
// A list item, a block scalar line, or a wrapped value extends the key before it.
|
|
174
|
+
const last = rows.at(-1)
|
|
175
|
+
if (last !== undefined && indent >= (stack.at(-1)?.indent ?? 0)) {
|
|
176
|
+
rows[rows.length - 1] = { ...last, text: `${last.text} ${content.trim()}`.trimEnd() }
|
|
177
|
+
}
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
while (stack.length > 0 && (stack.at(-1)?.indent ?? 0) >= indent) stack.pop()
|
|
181
|
+
const key = [...stack.map((entry) => entry.key), match[1]].join('.')
|
|
182
|
+
stack.push({ indent, key: match[1] })
|
|
183
|
+
rows.push({ path, line: index + offset, text: `${key}: ${(match[2] ?? '').trim()}`.trimEnd() })
|
|
184
|
+
})
|
|
185
|
+
return rows
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function readTomlKeys(path: string, lines: readonly string[], offset: number): readonly LineRow[] {
|
|
189
|
+
const rows: LineRow[] = []
|
|
190
|
+
let table = ''
|
|
191
|
+
let fence: string | undefined
|
|
192
|
+
lines.forEach((text, index) => {
|
|
193
|
+
if (fence !== undefined) {
|
|
194
|
+
// The body of a multi-line string carries no key.
|
|
195
|
+
if (text.includes(fence)) fence = undefined
|
|
196
|
+
return
|
|
197
|
+
}
|
|
198
|
+
const header = text.match(/^\s*\[\[?([^\]]+)\]\]?\s*$/u)
|
|
199
|
+
if (header?.[1] !== undefined) {
|
|
200
|
+
table = header[1].trim()
|
|
201
|
+
return
|
|
202
|
+
}
|
|
203
|
+
const match = text.match(/^\s*([A-Za-z0-9_.-]+|"[^"]+")\s*=\s*(.*)$/u)
|
|
204
|
+
if (match?.[1] === undefined) return
|
|
205
|
+
const value = (match[2] ?? '').trim()
|
|
206
|
+
const opening = value.match(/^("""|''')/u)?.[1]
|
|
207
|
+
if (opening !== undefined && value.indexOf(opening, 3) === -1) fence = opening
|
|
208
|
+
rows.push({
|
|
209
|
+
path,
|
|
210
|
+
line: index + offset,
|
|
211
|
+
text: `${table === '' ? '' : `${table}.`}${match[1]}: ${value}`.trimEnd(),
|
|
212
|
+
})
|
|
213
|
+
})
|
|
214
|
+
return rows
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function mapFrontmatter(paths: readonly string[]): readonly LineRow[] {
|
|
218
|
+
const rows: LineRow[] = []
|
|
219
|
+
for (const path of expandPaths(paths).filter((candidate) =>
|
|
220
|
+
/\.(md|mdc|toml|ya?ml)$/u.test(candidate),
|
|
221
|
+
)) {
|
|
222
|
+
const lines = readLines(path)
|
|
223
|
+
const markdown = /\.mdc?$/u.test(path)
|
|
224
|
+
const end = markdown ? lines.indexOf('---', 1) : lines.length
|
|
225
|
+
if (markdown && (lines[0] !== '---' || end === -1)) continue
|
|
226
|
+
const body = lines.slice(markdown ? 1 : 0, end)
|
|
227
|
+
const offset = markdown ? 2 : 1
|
|
228
|
+
rows.push(
|
|
229
|
+
...(path.endsWith('.toml')
|
|
230
|
+
? readTomlKeys(path, body, offset)
|
|
231
|
+
: readYamlKeys(path, body, offset)),
|
|
232
|
+
)
|
|
233
|
+
}
|
|
234
|
+
return rows
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function readHeadingTexts(path: string): ReadonlySet<string> {
|
|
238
|
+
return new Set(mapHeadings([path]).map((row) => row.text.replace(/^#+\s+/u, '').toLowerCase()))
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function mapAnchors(paths: readonly string[]): readonly AnchorRow[] {
|
|
242
|
+
const headings = new Map<string, ReadonlySet<string>>()
|
|
243
|
+
const rows: AnchorRow[] = []
|
|
244
|
+
for (const path of expandPaths(paths).filter((candidate) => candidate.endsWith('.md'))) {
|
|
245
|
+
readLines(path).forEach((text, index) => {
|
|
246
|
+
for (const match of text.matchAll(ANCHOR)) {
|
|
247
|
+
const target = match[1] === undefined ? path : normalizeSlashes(match[1])
|
|
248
|
+
const heading = (match[2] ?? '').trim()
|
|
249
|
+
if (heading === '') continue
|
|
250
|
+
if (!existsSync(target)) {
|
|
251
|
+
rows.push({ path, line: index + 1, target, heading })
|
|
252
|
+
continue
|
|
253
|
+
}
|
|
254
|
+
const known = headings.get(target) ?? readHeadingTexts(target)
|
|
255
|
+
headings.set(target, known)
|
|
256
|
+
if (!known.has(heading.toLowerCase())) rows.push({ path, line: index + 1, target, heading })
|
|
257
|
+
}
|
|
258
|
+
})
|
|
259
|
+
}
|
|
260
|
+
return rows
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function renderReport(report: Report): string {
|
|
264
|
+
const lines: string[] = []
|
|
265
|
+
if (report.tree !== undefined) {
|
|
266
|
+
lines.push('## Tree')
|
|
267
|
+
for (const row of report.tree) lines.push(`${row.path}\t${row.lines} lines\t${row.bytes} bytes`)
|
|
268
|
+
lines.push(
|
|
269
|
+
`total\t${report.tree.length} files\t${report.tree.reduce((sum, row) => sum + row.lines, 0)} lines`,
|
|
270
|
+
)
|
|
271
|
+
}
|
|
272
|
+
if (report.headings !== undefined) {
|
|
273
|
+
lines.push('## Headings')
|
|
274
|
+
for (const row of report.headings) lines.push(`${row.path}:${row.line}\t${row.text}`)
|
|
275
|
+
}
|
|
276
|
+
if (report.frontmatter !== undefined) {
|
|
277
|
+
lines.push('## Frontmatter')
|
|
278
|
+
for (const row of report.frontmatter) lines.push(`${row.path}:${row.line} ${row.text}`)
|
|
279
|
+
}
|
|
280
|
+
if (report.exports !== undefined) {
|
|
281
|
+
lines.push('## Exports (text reading)')
|
|
282
|
+
for (const row of report.exports) lines.push(`${row.path}:${row.line}\t${row.text}`)
|
|
283
|
+
}
|
|
284
|
+
if (report.census !== undefined) {
|
|
285
|
+
for (const entry of report.census) {
|
|
286
|
+
lines.push(`## Census: ${entry.term} (${entry.rows.length})`)
|
|
287
|
+
for (const row of entry.rows) lines.push(`${row.path}:${row.line}\t${row.text}`)
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if (report.refs !== undefined) {
|
|
291
|
+
lines.push(`## Refs (${report.refs.length})`)
|
|
292
|
+
for (const row of report.refs) lines.push(`${row.path}:${row.line}\t${row.text}`)
|
|
293
|
+
}
|
|
294
|
+
if (report.anchors !== undefined) {
|
|
295
|
+
lines.push(`## Unresolved anchors (${report.anchors.length})`)
|
|
296
|
+
for (const row of report.anchors)
|
|
297
|
+
lines.push(`${row.path}:${row.line}\t${row.target} § ${row.heading}`)
|
|
298
|
+
}
|
|
299
|
+
return `${lines.join('\n')}\n`
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function main(argv: readonly string[]): number {
|
|
303
|
+
const missing = readMissingFlags(argv, ['--refs', '--paths', '--out'])
|
|
304
|
+
if (missing.length > 0) {
|
|
305
|
+
console.error(`map: ${missing.join(', ')} given with no value`)
|
|
306
|
+
return 64
|
|
307
|
+
}
|
|
308
|
+
const roots = (readOption(argv, '--paths') ?? '.').split(',')
|
|
309
|
+
const ignoreCase = argv.includes('--ignore-case')
|
|
310
|
+
const tree = readList(argv, '--tree')
|
|
311
|
+
const headings = readList(argv, '--headings')
|
|
312
|
+
const frontmatter = readList(argv, '--frontmatter')
|
|
313
|
+
const exports = readList(argv, '--exports')
|
|
314
|
+
const census = readList(argv, '--census')
|
|
315
|
+
const refs = readOption(argv, '--refs')
|
|
316
|
+
const anchors = readList(argv, '--anchors')
|
|
317
|
+
if (
|
|
318
|
+
tree.length +
|
|
319
|
+
headings.length +
|
|
320
|
+
frontmatter.length +
|
|
321
|
+
exports.length +
|
|
322
|
+
census.length +
|
|
323
|
+
anchors.length ===
|
|
324
|
+
0 &&
|
|
325
|
+
refs === undefined
|
|
326
|
+
) {
|
|
327
|
+
console.error(
|
|
328
|
+
'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]',
|
|
329
|
+
)
|
|
330
|
+
return 64
|
|
331
|
+
}
|
|
332
|
+
const report: Report = {
|
|
333
|
+
...(tree.length > 0 ? { tree: mapTree(tree) } : {}),
|
|
334
|
+
...(headings.length > 0 ? { headings: mapHeadings(headings) } : {}),
|
|
335
|
+
...(frontmatter.length > 0 ? { frontmatter: mapFrontmatter(frontmatter) } : {}),
|
|
336
|
+
...(exports.length > 0 ? { exports: mapExports(exports) } : {}),
|
|
337
|
+
...(census.length > 0 ? { census: mapCensus(census, roots, ignoreCase) } : {}),
|
|
338
|
+
...(refs === undefined ? {} : { refs: mapRefs(refs, roots) }),
|
|
339
|
+
...(anchors.length > 0 ? { anchors: mapAnchors(anchors) } : {}),
|
|
340
|
+
}
|
|
341
|
+
const output = argv.includes('--json') ? `${JSON.stringify(report)}\n` : renderReport(report)
|
|
342
|
+
const out = readOption(argv, '--out')
|
|
343
|
+
if (out === undefined) process.stdout.write(output)
|
|
344
|
+
else {
|
|
345
|
+
mkdirSync(dirname(out), { recursive: true })
|
|
346
|
+
writeFileSync(out, output)
|
|
347
|
+
console.log(`map: wrote ${out}`)
|
|
348
|
+
}
|
|
349
|
+
return 0
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
process.exitCode = main(process.argv.slice(2))
|