harnery 0.5.0 → 0.7.0
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/README.md +16 -6
- package/dist/commander.d.ts +29 -0
- package/dist/commander.d.ts.map +1 -1
- package/dist/commander.js +4 -0
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +94 -33
- package/dist/commands/browse-ai.js +1 -1
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +41 -9
- package/dist/commands/cookies.js +1 -1
- package/dist/commands/decision.d.ts +4 -0
- package/dist/commands/decision.d.ts.map +1 -0
- package/dist/commands/decision.js +354 -0
- package/dist/commands/deinit.d.ts.map +1 -1
- package/dist/commands/deinit.js +4 -0
- package/dist/commands/devtools.d.ts +4 -0
- package/dist/commands/devtools.d.ts.map +1 -0
- package/dist/commands/devtools.js +239 -0
- package/dist/commands/docs.d.ts.map +1 -1
- package/dist/commands/docs.js +74 -2
- package/dist/commands/doctor.js +12 -4
- package/dist/commands/env.d.ts.map +1 -1
- package/dist/commands/env.js +3 -63
- package/dist/commands/fetch.js +1 -1
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +54 -14
- package/dist/commands/scratch.js +1 -1
- package/dist/commands/tunnel.d.ts.map +1 -1
- package/dist/commands/tunnel.js +273 -62
- package/dist/commands/web-fetch.js +1 -1
- package/dist/core/agents/coord-client.d.ts.map +1 -1
- package/dist/core/agents/coord-client.js +32 -8
- package/dist/core/agents/events/consume.d.ts +25 -2
- package/dist/core/agents/events/consume.d.ts.map +1 -1
- package/dist/core/agents/events/consume.js +55 -7
- package/dist/core/agents/events/emit.d.ts +2 -1
- package/dist/core/agents/events/emit.d.ts.map +1 -1
- package/dist/core/agents/events/emit.js +6 -1
- package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -1
- package/dist/core/agents/rules/claim-conflict.js +16 -5
- package/dist/core/agents/state/scratch.d.ts +1 -1
- package/dist/core/agents/state/scratch.js +2 -2
- package/dist/core/config.d.ts +10 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +13 -0
- package/dist/core/hooks/cli.js +3 -3
- package/dist/core/hooks/effects/index.d.ts +11 -7
- package/dist/core/hooks/effects/index.d.ts.map +1 -1
- package/dist/core/hooks/effects/index.js +15 -17
- package/dist/core/hooks/events/emit.d.ts.map +1 -1
- package/dist/core/hooks/events/emit.js +4 -0
- package/dist/core/hooks/events/rotate.d.ts +43 -0
- package/dist/core/hooks/events/rotate.d.ts.map +1 -0
- package/dist/core/hooks/events/rotate.js +142 -0
- package/dist/core/hooks/harness/events.d.ts +11 -1
- package/dist/core/hooks/harness/events.d.ts.map +1 -1
- package/dist/core/hooks/harness/events.js +22 -3
- package/dist/core/hooks/harness/wiring.d.ts +8 -0
- package/dist/core/hooks/harness/wiring.d.ts.map +1 -1
- package/dist/core/hooks/harness/wiring.js +34 -5
- package/dist/core/scratch/index.d.ts.map +1 -0
- package/dist/{lib → core}/scratch/index.js +2 -2
- package/dist/lib/agent-browser/client.js +1 -1
- package/dist/lib/browser/client.d.ts +14 -0
- package/dist/lib/browser/client.d.ts.map +1 -1
- package/dist/lib/browser/client.js +20 -0
- package/dist/lib/browser/index.d.ts +1 -0
- package/dist/lib/browser/index.d.ts.map +1 -1
- package/dist/lib/browser/runts.d.ts +44 -0
- package/dist/lib/browser/runts.d.ts.map +1 -0
- package/dist/lib/browser/runts.js +193 -0
- package/dist/lib/completion/walk.js +1 -1
- package/dist/lib/cookies/client.d.ts +1 -1
- package/dist/lib/cookies/client.d.ts.map +1 -1
- package/dist/lib/cookies/client.js +1 -1
- package/dist/lib/decision/index.d.ts +212 -0
- package/dist/lib/decision/index.d.ts.map +1 -0
- package/dist/lib/decision/index.js +523 -0
- package/dist/lib/devtools.d.ts +178 -0
- package/dist/lib/devtools.d.ts.map +1 -0
- package/dist/lib/devtools.js +1328 -0
- package/dist/lib/docs-frontmatter-migrate.d.ts +33 -0
- package/dist/lib/docs-frontmatter-migrate.d.ts.map +1 -0
- package/dist/lib/docs-frontmatter-migrate.js +364 -0
- package/dist/lib/docs-frontmatter.d.ts +33 -0
- package/dist/lib/docs-frontmatter.d.ts.map +1 -0
- package/dist/lib/docs-frontmatter.js +130 -0
- package/dist/lib/docs-index.d.ts +1 -0
- package/dist/lib/docs-index.d.ts.map +1 -1
- package/dist/lib/docs-index.js +4 -5
- package/dist/lib/docs-lint.d.ts +3 -0
- package/dist/lib/docs-lint.d.ts.map +1 -1
- package/dist/lib/docs-lint.js +67 -12
- package/dist/lib/docs-meta.d.ts +14 -0
- package/dist/lib/docs-meta.d.ts.map +1 -0
- package/dist/lib/docs-meta.js +34 -0
- package/dist/lib/docs-sweep.d.ts +12 -0
- package/dist/lib/docs-sweep.d.ts.map +1 -1
- package/dist/lib/docs-sweep.js +98 -103
- package/dist/lib/format.js +2 -2
- package/dist/lib/http/index.d.ts +1 -0
- package/dist/lib/http/index.d.ts.map +1 -1
- package/dist/lib/http/index.js +1 -0
- package/dist/lib/http/request.d.ts +77 -0
- package/dist/lib/http/request.d.ts.map +1 -0
- package/dist/lib/http/request.js +105 -0
- package/dist/lib/instructions/apply.d.ts +63 -0
- package/dist/lib/instructions/apply.d.ts.map +1 -0
- package/dist/lib/instructions/apply.js +255 -0
- package/dist/lib/instructions/splice.d.ts +73 -0
- package/dist/lib/instructions/splice.d.ts.map +1 -0
- package/dist/lib/instructions/splice.js +118 -0
- package/dist/lib/instructions/templates.d.ts +45 -0
- package/dist/lib/instructions/templates.d.ts.map +1 -0
- package/dist/lib/instructions/templates.js +258 -0
- package/dist/lib/tunnel/gate.d.ts +1 -0
- package/dist/lib/tunnel/gate.d.ts.map +1 -1
- package/dist/lib/tunnel/gate.js +15 -10
- package/dist/lib/tunnel/state.d.ts +11 -1
- package/dist/lib/tunnel/state.d.ts.map +1 -1
- package/dist/lib/tunnel/state.js +8 -3
- package/package.json +9 -6
- package/src/commander.ts +35 -0
- package/src/commands/agents.ts +97 -29
- package/src/commands/browse-ai.ts +1 -1
- package/src/commands/browse.ts +63 -8
- package/src/commands/cookies.ts +1 -1
- package/src/commands/decision.ts +438 -0
- package/src/commands/deinit.ts +5 -0
- package/src/commands/devtools.ts +284 -0
- package/src/commands/docs.ts +86 -2
- package/src/commands/doctor.ts +13 -4
- package/src/commands/env.ts +11 -77
- package/src/commands/fetch.ts +1 -1
- package/src/commands/init.ts +66 -15
- package/src/commands/scratch.ts +1 -1
- package/src/commands/tunnel.ts +316 -65
- package/src/commands/web-fetch.ts +1 -1
- package/src/core/agents/coord-client.ts +34 -7
- package/src/core/agents/events/consume.ts +65 -7
- package/src/core/agents/events/emit.ts +7 -1
- package/src/core/agents/rules/claim-conflict.ts +17 -6
- package/src/core/agents/state/scratch.ts +2 -2
- package/src/core/config.ts +15 -1
- package/src/core/hooks/cli.ts +3 -3
- package/src/core/hooks/effects/index.ts +23 -16
- package/src/core/hooks/events/emit.ts +5 -0
- package/src/core/hooks/events/rotate.ts +151 -0
- package/src/core/hooks/harness/events.ts +30 -3
- package/src/core/hooks/harness/wiring.ts +46 -5
- package/src/{lib → core}/scratch/index.ts +2 -2
- package/src/lib/agent-browser/client.ts +1 -1
- package/src/lib/browser/client.ts +28 -0
- package/src/lib/browser/index.ts +4 -0
- package/src/lib/browser/runts.ts +218 -0
- package/src/lib/completion/walk.ts +1 -1
- package/src/lib/cookies/client.ts +2 -2
- package/src/lib/decision/index.ts +685 -0
- package/src/lib/devtools.ts +1653 -0
- package/src/lib/docs-frontmatter-migrate.ts +427 -0
- package/src/lib/docs-frontmatter.ts +151 -0
- package/src/lib/docs-index.ts +4 -5
- package/src/lib/docs-lint.ts +61 -11
- package/src/lib/docs-meta.ts +44 -0
- package/src/lib/docs-sweep.ts +104 -102
- package/src/lib/format.ts +2 -2
- package/src/lib/http/index.ts +1 -0
- package/src/lib/http/request.ts +154 -0
- package/src/lib/instructions/apply.ts +318 -0
- package/src/lib/instructions/splice.ts +148 -0
- package/src/lib/instructions/templates.ts +295 -0
- package/src/lib/tunnel/gate.ts +15 -10
- package/src/lib/tunnel/state.ts +19 -4
- package/dist/lib/scratch/index.d.ts.map +0 -1
- /package/dist/{lib → core}/scratch/index.d.ts +0 -0
package/dist/lib/docs-lint.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync as __existsSyncForDocs } from "node:fs";
|
|
2
2
|
import { resolve as __resolveForDocs } from "node:path";
|
|
3
|
+
import { hasYamlStatus } from "./docs-frontmatter.js";
|
|
3
4
|
import { sh } from "./exec.js";
|
|
4
5
|
// Module-level docs context, initialized by initDocsContext() before any
|
|
5
6
|
// other function in this file is called. Consumers pass repo metadata
|
|
@@ -8,10 +9,12 @@ import { sh } from "./exec.js";
|
|
|
8
9
|
let REPO_ROOT = "";
|
|
9
10
|
let SUBMODULES = [];
|
|
10
11
|
let EXTRA_EXCLUDED_PREFIXES = [];
|
|
12
|
+
let DOCS_ROOT_ALLOWLIST = [];
|
|
11
13
|
export function initDocsContext(opts) {
|
|
12
14
|
REPO_ROOT = opts.repoRoot;
|
|
13
15
|
SUBMODULES = opts.submodules;
|
|
14
16
|
EXTRA_EXCLUDED_PREFIXES = opts.extraExcludedPrefixes ?? [];
|
|
17
|
+
DOCS_ROOT_ALLOWLIST = opts.docsRootAllowlist ?? [];
|
|
15
18
|
}
|
|
16
19
|
function submodulePath(name) {
|
|
17
20
|
return __resolveForDocs(REPO_ROOT, name);
|
|
@@ -128,10 +131,14 @@ function isDeclaredMonolith(path) {
|
|
|
128
131
|
const head = readHead(path, 10);
|
|
129
132
|
return /INTENTIONAL-MONOLITH/i.test(head);
|
|
130
133
|
}
|
|
131
|
-
/** Detect whether a file carries
|
|
132
|
-
function hasStatusHeader(path) {
|
|
133
|
-
|
|
134
|
-
|
|
134
|
+
/** Detect whether a file carries lifecycle status in leading YAML frontmatter. */
|
|
135
|
+
export function hasStatusHeader(path) {
|
|
136
|
+
try {
|
|
137
|
+
return hasYamlStatus(readFileSync(path, "utf8"));
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
135
142
|
}
|
|
136
143
|
// --- Individual checks ---
|
|
137
144
|
/** Entry tier files exist at the repo root */
|
|
@@ -196,6 +203,52 @@ function checkRootAllowlist(repoName, repoPath) {
|
|
|
196
203
|
}
|
|
197
204
|
return violations;
|
|
198
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* The host project's `docs/` root is an entry tier: only allowlisted files may
|
|
208
|
+
* sit loose there; topic docs belong in `docs/<topic>/` subdirs. Config-gated —
|
|
209
|
+
* a no-op unless the host supplies `docsRootAllowlist`. Parent-repo only:
|
|
210
|
+
* submodule `docs/` roots have their own entry tiers, not this one.
|
|
211
|
+
*/
|
|
212
|
+
function checkDocsRootAllowlist(repoName, repoPath) {
|
|
213
|
+
const violations = [];
|
|
214
|
+
if (DOCS_ROOT_ALLOWLIST.length === 0)
|
|
215
|
+
return violations; // opt-in
|
|
216
|
+
if (repoName !== "(root)")
|
|
217
|
+
return violations; // parent repo only
|
|
218
|
+
const allow = new Set(DOCS_ROOT_ALLOWLIST);
|
|
219
|
+
const docsDir = join(repoPath, "docs");
|
|
220
|
+
let entries;
|
|
221
|
+
try {
|
|
222
|
+
entries = readdirSync(docsDir);
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
return violations; // no docs/ dir — nothing to check
|
|
226
|
+
}
|
|
227
|
+
for (const entry of entries) {
|
|
228
|
+
// Subdirs are the intended home for topic docs; only loose files matter.
|
|
229
|
+
let isFile;
|
|
230
|
+
try {
|
|
231
|
+
isFile = statSync(join(docsDir, entry)).isFile();
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
if (!isFile)
|
|
237
|
+
continue;
|
|
238
|
+
if (!(entry.endsWith(".md") || entry.endsWith(".json")))
|
|
239
|
+
continue;
|
|
240
|
+
if (allow.has(entry))
|
|
241
|
+
continue;
|
|
242
|
+
violations.push({
|
|
243
|
+
severity: "error",
|
|
244
|
+
repo: repoName,
|
|
245
|
+
path: join("docs", entry),
|
|
246
|
+
rule: "docs-root-file",
|
|
247
|
+
message: `${entry} is not allowed loose at docs/ root — move it into a docs/<topic>/ subdir (or add it to context.docsRootAllowlist if it's a genuine entry-tier doc)`,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
return violations;
|
|
251
|
+
}
|
|
199
252
|
/** No SCREAMING_SNAKE_CASE filenames anywhere */
|
|
200
253
|
function checkNamingConvention(repoName, _repoPath, files) {
|
|
201
254
|
const violations = [];
|
|
@@ -295,10 +348,10 @@ function checkChangelogNames(repoName, _repoPath, files) {
|
|
|
295
348
|
}
|
|
296
349
|
return violations;
|
|
297
350
|
}
|
|
298
|
-
/** Plans and
|
|
351
|
+
/** Plans, issues, and handoffs must carry YAML lifecycle status (content check, slow). */
|
|
299
352
|
function checkStatusHeaders(repoName, repoPath, files) {
|
|
300
353
|
const violations = [];
|
|
301
|
-
const targetDirs = ["docs/plans/", "docs/issues/"];
|
|
354
|
+
const targetDirs = ["docs/plans/", "docs/issues/", "docs/handoffs/"];
|
|
302
355
|
for (const rel of files) {
|
|
303
356
|
const dirMatch = targetDirs.some((d) => rel.startsWith(d));
|
|
304
357
|
if (!dirMatch)
|
|
@@ -306,18 +359,19 @@ function checkStatusHeaders(repoName, repoPath, files) {
|
|
|
306
359
|
const name = basename(rel);
|
|
307
360
|
if (name === "README.md")
|
|
308
361
|
continue;
|
|
309
|
-
// Skip archive subdir
|
|
310
|
-
if (rel.includes("/archive/"))
|
|
311
|
-
continue;
|
|
312
362
|
const full = join(repoPath, rel);
|
|
313
363
|
if (!hasStatusHeader(full)) {
|
|
314
|
-
const kind = rel.startsWith("docs/plans/")
|
|
364
|
+
const kind = rel.startsWith("docs/plans/")
|
|
365
|
+
? "plan"
|
|
366
|
+
: rel.startsWith("docs/issues/")
|
|
367
|
+
? "issue"
|
|
368
|
+
: "handoff";
|
|
315
369
|
violations.push({
|
|
316
|
-
severity: "
|
|
370
|
+
severity: "error",
|
|
317
371
|
repo: repoName,
|
|
318
372
|
path: join(repoName === "(root)" ? "" : repoName, rel),
|
|
319
373
|
rule: "missing-status-header",
|
|
320
|
-
message: `${kind} missing
|
|
374
|
+
message: `${kind} missing status in leading YAML frontmatter`,
|
|
321
375
|
});
|
|
322
376
|
}
|
|
323
377
|
}
|
|
@@ -371,6 +425,7 @@ export async function runLint(opts) {
|
|
|
371
425
|
for (const { name, path, isSubmodule } of repos) {
|
|
372
426
|
violations.push(...checkEntryTier(name, path, isSubmodule));
|
|
373
427
|
violations.push(...checkRootAllowlist(name, path));
|
|
428
|
+
violations.push(...checkDocsRootAllowlist(name, path));
|
|
374
429
|
const files = await findMarkdownFiles(path);
|
|
375
430
|
violations.push(...checkNamingConvention(name, path, files));
|
|
376
431
|
violations.push(...checkDatedDirs(name, path, files));
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface DocsMetadata {
|
|
2
|
+
path: string;
|
|
3
|
+
data: Record<string, unknown>;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Read the leading YAML frontmatter from a markdown file.
|
|
7
|
+
*
|
|
8
|
+
* Relative paths resolve from the host repo root so the command behaves the
|
|
9
|
+
* same no matter which directory invoked it.
|
|
10
|
+
*/
|
|
11
|
+
export declare function readDocsMetadata(repoRoot: string, inputPath: string): DocsMetadata;
|
|
12
|
+
/** Read one top-level metadata key, failing when the key is absent. */
|
|
13
|
+
export declare function readDocsMetadataKey(metadata: Record<string, unknown>, key: string, inputPath: string): unknown;
|
|
14
|
+
//# sourceMappingURL=docs-meta.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docs-meta.d.ts","sourceRoot":"","sources":["../../src/lib/docs-meta.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,YAAY,CAgBlF;AAED,uEAAuE;AACvE,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,GAChB,OAAO,CAKT"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, resolve } from "node:path";
|
|
3
|
+
import { parseFrontmatter } from "./docs-frontmatter.js";
|
|
4
|
+
/**
|
|
5
|
+
* Read the leading YAML frontmatter from a markdown file.
|
|
6
|
+
*
|
|
7
|
+
* Relative paths resolve from the host repo root so the command behaves the
|
|
8
|
+
* same no matter which directory invoked it.
|
|
9
|
+
*/
|
|
10
|
+
export function readDocsMetadata(repoRoot, inputPath) {
|
|
11
|
+
const path = isAbsolute(inputPath) ? resolve(inputPath) : resolve(repoRoot, inputPath);
|
|
12
|
+
try {
|
|
13
|
+
if (!statSync(path).isFile())
|
|
14
|
+
throw new Error("not a file");
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
throw new Error(`Documentation file not found: ${inputPath}`);
|
|
18
|
+
}
|
|
19
|
+
const parsed = parseFrontmatter(readFileSync(path, "utf8"));
|
|
20
|
+
if (parsed.raw === null) {
|
|
21
|
+
throw new Error(`Documentation file has no leading YAML frontmatter: ${inputPath}`);
|
|
22
|
+
}
|
|
23
|
+
if (Object.keys(parsed.data).length === 0) {
|
|
24
|
+
throw new Error(`Documentation frontmatter is empty or malformed: ${inputPath}`);
|
|
25
|
+
}
|
|
26
|
+
return { path, data: parsed.data };
|
|
27
|
+
}
|
|
28
|
+
/** Read one top-level metadata key, failing when the key is absent. */
|
|
29
|
+
export function readDocsMetadataKey(metadata, key, inputPath) {
|
|
30
|
+
if (!Object.hasOwn(metadata, key)) {
|
|
31
|
+
throw new Error(`Documentation frontmatter key '${key}' not found: ${inputPath}`);
|
|
32
|
+
}
|
|
33
|
+
return metadata[key];
|
|
34
|
+
}
|
package/dist/lib/docs-sweep.d.ts
CHANGED
|
@@ -12,6 +12,10 @@ export declare function initDocsContext(opts: {
|
|
|
12
12
|
*
|
|
13
13
|
* Audit files and issue files under `docs/audits/` are explicitly **not**
|
|
14
14
|
* flagged for age; they're immutable records by design.
|
|
15
|
+
*
|
|
16
|
+
* Performance: ages come from one `git log --name-only` per repo (not one
|
|
17
|
+
* `git log` per file). A naive per-file spawn was ~1min+ on large hosts
|
|
18
|
+
* (thousands of topic docs) and looked hung when piped.
|
|
15
19
|
*/
|
|
16
20
|
export interface SweepOpts {
|
|
17
21
|
repo?: string;
|
|
@@ -24,6 +28,13 @@ export interface SweepItem {
|
|
|
24
28
|
message: string;
|
|
25
29
|
}
|
|
26
30
|
export type SweepKind = "stalled-plan" | "unarchived-shipped" | "open-issue-cold" | "cold-handoff" | "runbook-unverified" | "topic-doc-stale" | "decisions-dormant";
|
|
31
|
+
type AgeMap = Map<string, number>;
|
|
32
|
+
/**
|
|
33
|
+
* Parse a `git log --format="COMMIT %aI" --name-only` body into
|
|
34
|
+
* repo-relative path → age in days. Newest commit wins.
|
|
35
|
+
* Exported for unit tests.
|
|
36
|
+
*/
|
|
37
|
+
export declare function parseDocsAgeLog(stdout: string, nowMs?: number): AgeMap;
|
|
27
38
|
export declare function runSweep(opts: SweepOpts): Promise<SweepItem[]>;
|
|
28
39
|
/**
|
|
29
40
|
* Cheap parent-repo-only count of `cold-handoff` items, used by `docs lint`
|
|
@@ -31,4 +42,5 @@ export declare function runSweep(opts: SweepOpts): Promise<SweepItem[]>;
|
|
|
31
42
|
* submodule (handoffs live under the parent's docs/handoffs/ by convention).
|
|
32
43
|
*/
|
|
33
44
|
export declare function countColdHandoffs(): Promise<number>;
|
|
45
|
+
export {};
|
|
34
46
|
//# sourceMappingURL=docs-sweep.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"docs-sweep.d.ts","sourceRoot":"","sources":["../../src/lib/docs-sweep.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"docs-sweep.d.ts","sourceRoot":"","sources":["../../src/lib/docs-sweep.ts"],"names":[],"mappings":"AAWA,wBAAgB,eAAe,CAAC,IAAI,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAAG,IAAI,CAG/F;AAaD;;;;;;;;;;;;;;GAcG;AAEH,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,SAAS,GACjB,cAAc,GACd,oBAAoB,GACpB,iBAAiB,GACjB,cAAc,GACd,oBAAoB,GACpB,iBAAiB,GACjB,mBAAmB,CAAC;AAUxB,KAAK,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAElC;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,GAAE,MAAmB,GAAG,MAAM,CAgBlF;AAsND,wBAAsB,QAAQ,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CA4BpE;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC,CAKzD"}
|
package/dist/lib/docs-sweep.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync as __existsSyncForDocs } from "node:fs";
|
|
2
2
|
import { resolve as __resolveForDocs } from "node:path";
|
|
3
|
+
import { readDocStatus } from "./docs-frontmatter.js";
|
|
3
4
|
import { sh } from "./exec.js";
|
|
4
5
|
// Module-level docs context, initialized by initDocsContext() before any
|
|
5
6
|
// other function in this file is called. The repo root + submodule list
|
|
@@ -16,7 +17,7 @@ function submodulePath(name) {
|
|
|
16
17
|
function isSubmoduleInitialized(name) {
|
|
17
18
|
return __existsSyncForDocs(__resolveForDocs(REPO_ROOT, name, ".git"));
|
|
18
19
|
}
|
|
19
|
-
import { existsSync, readdirSync,
|
|
20
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
20
21
|
import { join, relative } from "node:path";
|
|
21
22
|
const STALLED_PLAN_DAYS = 90;
|
|
22
23
|
const UNARCHIVED_SHIPPED_DAYS = 180;
|
|
@@ -25,13 +26,48 @@ const COLD_HANDOFF_DAYS = 30;
|
|
|
25
26
|
const RUNBOOK_DAYS = 180;
|
|
26
27
|
const TOPIC_DOC_DAYS = 365;
|
|
27
28
|
const DECISIONS_DORMANT_DAYS = 180;
|
|
28
|
-
/**
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Parse a `git log --format="COMMIT %aI" --name-only` body into
|
|
31
|
+
* repo-relative path → age in days. Newest commit wins.
|
|
32
|
+
* Exported for unit tests.
|
|
33
|
+
*/
|
|
34
|
+
export function parseDocsAgeLog(stdout, nowMs = Date.now()) {
|
|
35
|
+
const ages = new Map();
|
|
36
|
+
let currentAge = null;
|
|
37
|
+
for (const line of stdout.split("\n")) {
|
|
38
|
+
if (line.startsWith("COMMIT ")) {
|
|
39
|
+
const iso = line.slice("COMMIT ".length).trim();
|
|
40
|
+
const ms = Date.parse(iso);
|
|
41
|
+
currentAge = Number.isNaN(ms) ? null : Math.floor((nowMs - ms) / (1000 * 60 * 60 * 24));
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (!line || currentAge == null)
|
|
45
|
+
continue;
|
|
46
|
+
if (!line.endsWith(".md"))
|
|
47
|
+
continue;
|
|
48
|
+
// First (newest) sighting wins
|
|
49
|
+
if (!ages.has(line))
|
|
50
|
+
ages.set(line, currentAge);
|
|
51
|
+
}
|
|
52
|
+
return ages;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* One `git log --name-only` for the whole docs/ tree. Docs histories are
|
|
56
|
+
* small even without --since (a large host's full docs log is ~5k lines / <100ms),
|
|
57
|
+
* so we take the full history for accurate ages on old files.
|
|
58
|
+
*/
|
|
59
|
+
async function loadDocsAges(cwd) {
|
|
60
|
+
const result = await sh(`git log --format="COMMIT %aI" --name-only -- docs/`, {
|
|
61
|
+
cwd,
|
|
62
|
+
timeout: 120_000,
|
|
63
|
+
});
|
|
31
64
|
if (result.exitCode !== 0 || !result.stdout.trim())
|
|
32
|
-
return
|
|
33
|
-
|
|
34
|
-
|
|
65
|
+
return new Map();
|
|
66
|
+
return parseDocsAgeLog(result.stdout);
|
|
67
|
+
}
|
|
68
|
+
/** Age for a tracked path, or null if untracked / never under docs/. */
|
|
69
|
+
function ageDays(ages, rel) {
|
|
70
|
+
return ages.has(rel) ? ages.get(rel) : null;
|
|
35
71
|
}
|
|
36
72
|
/** Days since ANY commit in the repo (measures repo activity) */
|
|
37
73
|
async function lastRepoCommitAgeDays(cwd) {
|
|
@@ -41,48 +77,36 @@ async function lastRepoCommitAgeDays(cwd) {
|
|
|
41
77
|
const ms = Date.now() - new Date(result.stdout.trim()).getTime();
|
|
42
78
|
return Math.floor(ms / (1000 * 60 * 60 * 24));
|
|
43
79
|
}
|
|
44
|
-
function
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
80
|
+
function walkMdFiles(dir, skipReadme = false) {
|
|
81
|
+
const out = [];
|
|
82
|
+
for (const entry of readdirSync(dir)) {
|
|
83
|
+
const full = join(dir, entry);
|
|
84
|
+
let st;
|
|
85
|
+
try {
|
|
86
|
+
st = statSync(full);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (st.isDirectory()) {
|
|
92
|
+
out.push(...walkMdFiles(full, skipReadme));
|
|
93
|
+
}
|
|
94
|
+
else if (entry.endsWith(".md") && !(skipReadme && entry === "README.md")) {
|
|
95
|
+
out.push(full);
|
|
96
|
+
}
|
|
53
97
|
}
|
|
98
|
+
return out;
|
|
54
99
|
}
|
|
55
|
-
|
|
100
|
+
function sweepPlans(repoName, repoPath, ages, items) {
|
|
56
101
|
const plansDir = join(repoPath, "docs", "plans");
|
|
57
102
|
if (!existsSync(plansDir))
|
|
58
103
|
return;
|
|
59
|
-
const
|
|
60
|
-
const out = [];
|
|
61
|
-
for (const entry of readdirSync(dir)) {
|
|
62
|
-
const full = join(dir, entry);
|
|
63
|
-
let st;
|
|
64
|
-
try {
|
|
65
|
-
st = statSync(full);
|
|
66
|
-
}
|
|
67
|
-
catch {
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
if (st.isDirectory()) {
|
|
71
|
-
out.push(...walk(full));
|
|
72
|
-
}
|
|
73
|
-
else if (entry.endsWith(".md") && entry !== "README.md") {
|
|
74
|
-
out.push(full);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return out;
|
|
78
|
-
};
|
|
79
|
-
const files = walk(plansDir);
|
|
80
|
-
for (const full of files) {
|
|
104
|
+
for (const full of walkMdFiles(plansDir, true)) {
|
|
81
105
|
const rel = relative(repoPath, full);
|
|
82
106
|
const displayPath = join(repoName === "(root)" ? "" : repoName, rel);
|
|
83
107
|
const isArchived = rel.includes("/archive/");
|
|
84
|
-
const status =
|
|
85
|
-
const age =
|
|
108
|
+
const status = readDocStatus(full, "plan");
|
|
109
|
+
const age = ageDays(ages, rel);
|
|
86
110
|
if (age == null)
|
|
87
111
|
continue;
|
|
88
112
|
if (!isArchived && status === "in-progress" && age > STALLED_PLAN_DAYS) {
|
|
@@ -105,7 +129,7 @@ async function sweepPlans(repoName, repoPath, items) {
|
|
|
105
129
|
}
|
|
106
130
|
}
|
|
107
131
|
}
|
|
108
|
-
|
|
132
|
+
function sweepIssues(repoName, repoPath, ages, items) {
|
|
109
133
|
const issuesDir = join(repoPath, "docs", "issues");
|
|
110
134
|
if (!existsSync(issuesDir))
|
|
111
135
|
return;
|
|
@@ -115,10 +139,10 @@ async function sweepIssues(repoName, repoPath, items) {
|
|
|
115
139
|
const full = join(issuesDir, entry);
|
|
116
140
|
const rel = join("docs", "issues", entry);
|
|
117
141
|
const displayPath = join(repoName === "(root)" ? "" : repoName, rel);
|
|
118
|
-
const status =
|
|
142
|
+
const status = readDocStatus(full, "issue");
|
|
119
143
|
if (status !== "open")
|
|
120
144
|
continue;
|
|
121
|
-
const age =
|
|
145
|
+
const age = ageDays(ages, rel);
|
|
122
146
|
if (age == null || age <= OPEN_ISSUE_DAYS)
|
|
123
147
|
continue;
|
|
124
148
|
items.push({
|
|
@@ -130,36 +154,16 @@ async function sweepIssues(repoName, repoPath, items) {
|
|
|
130
154
|
});
|
|
131
155
|
}
|
|
132
156
|
}
|
|
133
|
-
|
|
157
|
+
function sweepHandoffs(repoName, repoPath, ages, items) {
|
|
134
158
|
const handoffsDir = join(repoPath, "docs", "handoffs");
|
|
135
159
|
if (!existsSync(handoffsDir))
|
|
136
160
|
return;
|
|
137
|
-
const
|
|
138
|
-
const out = [];
|
|
139
|
-
for (const entry of readdirSync(dir)) {
|
|
140
|
-
const full = join(dir, entry);
|
|
141
|
-
let st;
|
|
142
|
-
try {
|
|
143
|
-
st = statSync(full);
|
|
144
|
-
}
|
|
145
|
-
catch {
|
|
146
|
-
continue;
|
|
147
|
-
}
|
|
148
|
-
if (st.isDirectory()) {
|
|
149
|
-
out.push(...walk(full));
|
|
150
|
-
}
|
|
151
|
-
else if (entry.endsWith(".md")) {
|
|
152
|
-
out.push(full);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
return out;
|
|
156
|
-
};
|
|
157
|
-
for (const full of walk(handoffsDir)) {
|
|
161
|
+
for (const full of walkMdFiles(handoffsDir)) {
|
|
158
162
|
const rel = relative(repoPath, full);
|
|
159
|
-
const status =
|
|
163
|
+
const status = readDocStatus(full, "handoff");
|
|
160
164
|
if (status !== "open")
|
|
161
165
|
continue;
|
|
162
|
-
const age =
|
|
166
|
+
const age = ageDays(ages, rel);
|
|
163
167
|
if (age == null || age <= COLD_HANDOFF_DAYS)
|
|
164
168
|
continue;
|
|
165
169
|
const displayPath = join(repoName === "(root)" ? "" : repoName, rel);
|
|
@@ -172,11 +176,11 @@ async function sweepHandoffs(repoName, repoPath, items) {
|
|
|
172
176
|
});
|
|
173
177
|
}
|
|
174
178
|
}
|
|
175
|
-
|
|
179
|
+
function sweepRunbook(repoName, repoPath, ages, items) {
|
|
176
180
|
const runbook = join(repoPath, "docs", "runbook.md");
|
|
177
181
|
if (!existsSync(runbook))
|
|
178
182
|
return;
|
|
179
|
-
const age =
|
|
183
|
+
const age = ageDays(ages, "docs/runbook.md");
|
|
180
184
|
if (age == null || age <= RUNBOOK_DAYS)
|
|
181
185
|
return;
|
|
182
186
|
const displayPath = join(repoName === "(root)" ? "" : repoName, "docs/runbook.md");
|
|
@@ -188,17 +192,22 @@ async function sweepRunbook(repoName, repoPath, items) {
|
|
|
188
192
|
message: `runbook hasn't been edited in ${age}d; re-verify procedures`,
|
|
189
193
|
});
|
|
190
194
|
}
|
|
191
|
-
|
|
195
|
+
function sweepTopicDocs(repoName, repoPath, ages, items) {
|
|
192
196
|
const docsDir = join(repoPath, "docs");
|
|
193
197
|
if (!existsSync(docsDir))
|
|
194
198
|
return;
|
|
195
|
-
// Skip known date-stamped or lifecycle-managed dirs
|
|
199
|
+
// Skip known date-stamped or lifecycle-managed dirs (and vendor dumps).
|
|
200
|
+
// handoffs/inquiries are lifecycle-managed elsewhere; vendors match the
|
|
201
|
+
// docs freshness scanner's IGNORE_DIRS.
|
|
196
202
|
const skipDirs = new Set([
|
|
197
203
|
"audits",
|
|
198
204
|
"issues",
|
|
199
205
|
"plans",
|
|
200
206
|
"changelogs",
|
|
201
207
|
"emails", // parent-specific
|
|
208
|
+
"handoffs",
|
|
209
|
+
"inquiries",
|
|
210
|
+
"vendors",
|
|
202
211
|
]);
|
|
203
212
|
for (const entry of readdirSync(docsDir)) {
|
|
204
213
|
if (skipDirs.has(entry))
|
|
@@ -213,28 +222,9 @@ async function sweepTopicDocs(repoName, repoPath, items) {
|
|
|
213
222
|
}
|
|
214
223
|
if (!st.isDirectory())
|
|
215
224
|
continue;
|
|
216
|
-
|
|
217
|
-
const walk = (dir) => {
|
|
218
|
-
const out = [];
|
|
219
|
-
for (const f of readdirSync(dir)) {
|
|
220
|
-
const fp = join(dir, f);
|
|
221
|
-
let s;
|
|
222
|
-
try {
|
|
223
|
-
s = statSync(fp);
|
|
224
|
-
}
|
|
225
|
-
catch {
|
|
226
|
-
continue;
|
|
227
|
-
}
|
|
228
|
-
if (s.isDirectory())
|
|
229
|
-
out.push(...walk(fp));
|
|
230
|
-
else if (f.endsWith(".md"))
|
|
231
|
-
out.push(fp);
|
|
232
|
-
}
|
|
233
|
-
return out;
|
|
234
|
-
};
|
|
235
|
-
for (const full2 of walk(full)) {
|
|
225
|
+
for (const full2 of walkMdFiles(full)) {
|
|
236
226
|
const rel = relative(repoPath, full2);
|
|
237
|
-
const age =
|
|
227
|
+
const age = ageDays(ages, rel);
|
|
238
228
|
if (age == null || age <= TOPIC_DOC_DAYS)
|
|
239
229
|
continue;
|
|
240
230
|
const displayPath = join(repoName === "(root)" ? "" : repoName, rel);
|
|
@@ -248,11 +238,11 @@ async function sweepTopicDocs(repoName, repoPath, items) {
|
|
|
248
238
|
}
|
|
249
239
|
}
|
|
250
240
|
}
|
|
251
|
-
async function sweepDecisions(repoName, repoPath, items) {
|
|
241
|
+
async function sweepDecisions(repoName, repoPath, ages, items) {
|
|
252
242
|
const decisions = join(repoPath, "docs", "decisions.md");
|
|
253
243
|
if (!existsSync(decisions))
|
|
254
244
|
return;
|
|
255
|
-
const age =
|
|
245
|
+
const age = ageDays(ages, "docs/decisions.md");
|
|
256
246
|
const repoAge = await lastRepoCommitAgeDays(repoPath);
|
|
257
247
|
if (age == null || repoAge == null)
|
|
258
248
|
return;
|
|
@@ -280,13 +270,17 @@ export async function runSweep(opts) {
|
|
|
280
270
|
const filter = opts.repo === "." ? "(root)" : opts.repo;
|
|
281
271
|
const filtered = filter ? targets.filter((t) => t.name === filter) : targets;
|
|
282
272
|
const items = [];
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
273
|
+
// Load ages per repo in parallel — one git log each, not one per file.
|
|
274
|
+
const ageMaps = await Promise.all(filtered.map((t) => loadDocsAges(t.path)));
|
|
275
|
+
for (let i = 0; i < filtered.length; i++) {
|
|
276
|
+
const { name, path } = filtered[i];
|
|
277
|
+
const ages = ageMaps[i];
|
|
278
|
+
sweepPlans(name, path, ages, items);
|
|
279
|
+
sweepIssues(name, path, ages, items);
|
|
280
|
+
sweepHandoffs(name, path, ages, items);
|
|
281
|
+
sweepRunbook(name, path, ages, items);
|
|
282
|
+
sweepTopicDocs(name, path, ages, items);
|
|
283
|
+
await sweepDecisions(name, path, ages, items);
|
|
290
284
|
}
|
|
291
285
|
// Sort by severity proxy: oldest first within kind
|
|
292
286
|
items.sort((a, b) => b.ageDays - a.ageDays);
|
|
@@ -299,6 +293,7 @@ export async function runSweep(opts) {
|
|
|
299
293
|
*/
|
|
300
294
|
export async function countColdHandoffs() {
|
|
301
295
|
const items = [];
|
|
302
|
-
await
|
|
296
|
+
const ages = await loadDocsAges(REPO_ROOT);
|
|
297
|
+
sweepHandoffs("(root)", REPO_ROOT, ages, items);
|
|
303
298
|
return items.length;
|
|
304
299
|
}
|
package/dist/lib/format.js
CHANGED
|
@@ -50,7 +50,7 @@ export function colorJson(value, indent = 0) {
|
|
|
50
50
|
// Class instances that define toJSON() (Big.js, Decimal.js, Date, etc.)
|
|
51
51
|
// would render as their raw internal shape if we walked Object.keys
|
|
52
52
|
// directly. Unwrap once so callers see the intended representation
|
|
53
|
-
// (Big.js → "1.97" instead of {s,e,c}
|
|
53
|
+
// (e.g. Big.js → "1.97" instead of its internal {s,e,c} fields).
|
|
54
54
|
const maybeJsonable = v;
|
|
55
55
|
if (typeof maybeJsonable.toJSON === "function") {
|
|
56
56
|
return fmt(maybeJsonable.toJSON(), depth);
|
|
@@ -109,7 +109,7 @@ function stringify(val) {
|
|
|
109
109
|
if (typeof val === "object") {
|
|
110
110
|
if (val instanceof Date)
|
|
111
111
|
return val.toISOString();
|
|
112
|
-
//
|
|
112
|
+
// Some data sources wrap a scalar as { value: "..." }; unwrap to the inner value.
|
|
113
113
|
if ("value" in val && Object.keys(val).length === 1) {
|
|
114
114
|
return String(val.value);
|
|
115
115
|
}
|
package/dist/lib/http/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/http/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/http/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChF,cAAc,cAAc,CAAC"}
|
package/dist/lib/http/index.js
CHANGED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retrying JSON-API request — the toolkit-tier HTTP primitive for host CLIs'
|
|
3
|
+
* vendor clients.
|
|
4
|
+
*
|
|
5
|
+
* Extracted from the first embedding host, where ten vendor clients carried
|
|
6
|
+
* byte-similar copies of the same loop: abort-controller timeout, retry on
|
|
7
|
+
* 429/5xx with exponential backoff + jitter, `Retry-After` honored when sane,
|
|
8
|
+
* vendor-specific error taxonomy applied by the caller. This module owns the
|
|
9
|
+
* loop; callers keep their auth headers and error classes:
|
|
10
|
+
*
|
|
11
|
+
* const r = await requestWithRetries(url, {
|
|
12
|
+
* method, body,
|
|
13
|
+
* headers: { Authorization: `ApiKey ${key}`, Accept: "application/json" },
|
|
14
|
+
* timeoutMs: this.timeoutMs,
|
|
15
|
+
* maxRetries: this.maxRetries,
|
|
16
|
+
* onResponse: ({ status }) => log(`${method} ${url} → ${status}`),
|
|
17
|
+
* networkError: (msg) => new VendorError("network_error", msg),
|
|
18
|
+
* });
|
|
19
|
+
* if (!r.ok) throw makeHttpError(r.status, url, r.text, r.headers);
|
|
20
|
+
*
|
|
21
|
+
* Design choices, so they survive review:
|
|
22
|
+
* - Terminal non-2xx responses RETURN (`ok: false`) rather than throw — the
|
|
23
|
+
* vendor error taxonomy belongs to the caller, not this module.
|
|
24
|
+
* - Only terminal NETWORK failures throw (after retries), because there is
|
|
25
|
+
* no response to hand back; `networkError` lets the caller keep its class.
|
|
26
|
+
* - The response body is always read (even on retried statuses) so keep-alive
|
|
27
|
+
* sockets are released.
|
|
28
|
+
*/
|
|
29
|
+
export interface RetryingResponse {
|
|
30
|
+
/** `status` in the 2xx range. */
|
|
31
|
+
ok: boolean;
|
|
32
|
+
status: number;
|
|
33
|
+
/** Response body as text; callers handle JSON parsing. */
|
|
34
|
+
text: string;
|
|
35
|
+
url: string;
|
|
36
|
+
headers: Headers;
|
|
37
|
+
}
|
|
38
|
+
export interface RequestWithRetriesOptions {
|
|
39
|
+
/** HTTP method. Default GET. */
|
|
40
|
+
method?: string;
|
|
41
|
+
/** Extra headers. Content-Type defaults to application/json when a non-string body is given. */
|
|
42
|
+
headers?: Record<string, string>;
|
|
43
|
+
/**
|
|
44
|
+
* Request body. Strings, Uint8Array, FormData, Blob, and ReadableStream pass
|
|
45
|
+
* through untouched; any other value is JSON.stringify'd (with a
|
|
46
|
+
* Content-Type: application/json default).
|
|
47
|
+
*/
|
|
48
|
+
body?: unknown;
|
|
49
|
+
/** Per-attempt timeout (AbortController). Default 30s. */
|
|
50
|
+
timeoutMs?: number;
|
|
51
|
+
/** Retries after the first attempt. Default 3. */
|
|
52
|
+
maxRetries?: number;
|
|
53
|
+
/** Which statuses to retry. Default: 429 and 5xx. */
|
|
54
|
+
shouldRetry?: (status: number) => boolean;
|
|
55
|
+
/** Delay before retry `attempt` (0-based). Default `backoffDelayMs`. */
|
|
56
|
+
delayMs?: (attempt: number, retryAfterSeconds?: number | null) => number;
|
|
57
|
+
/** Observability hook — fires once per received response (every attempt). */
|
|
58
|
+
onResponse?: (info: {
|
|
59
|
+
method: string;
|
|
60
|
+
url: string;
|
|
61
|
+
status: number;
|
|
62
|
+
attempt: number;
|
|
63
|
+
}) => void;
|
|
64
|
+
/**
|
|
65
|
+
* Wrap a terminal network failure (fetch threw on the last attempt) in the
|
|
66
|
+
* caller's error class. Default: a plain Error with the message.
|
|
67
|
+
*/
|
|
68
|
+
networkError?: (message: string, url: string, retries: number) => Error;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Exponential backoff with jitter: 500ms · 2^attempt, capped at 30s, plus up
|
|
72
|
+
* to 250ms of jitter. A sane `Retry-After` (0 < s < 60) short-circuits the
|
|
73
|
+
* curve — the server knows better than the guess.
|
|
74
|
+
*/
|
|
75
|
+
export declare function backoffDelayMs(attempt: number, retryAfterSeconds?: number | null): number;
|
|
76
|
+
export declare function requestWithRetries(url: string, opts?: RequestWithRetriesOptions): Promise<RetryingResponse>;
|
|
77
|
+
//# sourceMappingURL=request.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../../src/lib/http/request.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,iCAAiC;IACjC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,yBAAyB;IACxC,gCAAgC;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gGAAgG;IAChG,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC;;;;OAIG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qDAAqD;IACrD,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC;IAC1C,wEAAwE;IACxE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,KAAK,MAAM,CAAC;IACzE,6EAA6E;IAC7E,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC9F;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,KAAK,CAAC;CACzE;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAMzF;AAMD,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,yBAA8B,GACnC,OAAO,CAAC,gBAAgB,CAAC,CAkE3B"}
|