pagegraph 0.14.1 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +169 -10
  2. package/dist/audit.d.ts +1 -1
  3. package/dist/audit.js +1 -1
  4. package/dist/{checks-BS-aDXB_.js → checks-BEhJPNzN.js} +12 -1
  5. package/dist/checks-BEhJPNzN.js.map +1 -0
  6. package/dist/{claims-Bch7Lxlo.d.ts → claims-CCf2_Jox.d.ts} +2 -2
  7. package/dist/{claims-Bv7voeEV.js → claims-CQlzze1K.js} +2 -2
  8. package/dist/{claims-Bv7voeEV.js.map → claims-CQlzze1K.js.map} +1 -1
  9. package/dist/claims.d.ts +2 -2
  10. package/dist/claims.js +1 -1
  11. package/dist/cli.js +1355 -616
  12. package/dist/cli.js.map +1 -1
  13. package/dist/config.d.ts +7 -3
  14. package/dist/config.js +1 -1
  15. package/dist/config.js.map +1 -1
  16. package/dist/{content-CHd0X4s6.js → content-D5jZprMq.js} +2 -2
  17. package/dist/{content-CHd0X4s6.js.map → content-D5jZprMq.js.map} +1 -1
  18. package/dist/{content-DgNr7apS.d.ts → content-DerWgQy9.d.ts} +11 -3
  19. package/dist/{dist-cFoAzruL.js → dist-Br2oOB2Q.js} +2 -2
  20. package/dist/{dist-cFoAzruL.js.map → dist-Br2oOB2Q.js.map} +1 -1
  21. package/dist/{document-CztEu3Vd.d.ts → document-D9P8yY5g.d.ts} +4 -4
  22. package/dist/{graph-D1PIndsC.d.ts → graph-ChrvU-6X.d.ts} +27 -4
  23. package/dist/{graph-DMNOLXLu.js → graph-UONEaqIX.js} +43 -5
  24. package/dist/graph-UONEaqIX.js.map +1 -0
  25. package/dist/{index-C5w9ripz.d.ts → index-DzjUmbHY.d.ts} +5 -5
  26. package/dist/index.d.ts +6 -6
  27. package/dist/index.js +5 -6
  28. package/dist/index.js.map +1 -1
  29. package/dist/{page-heads-BfjWQvau.d.ts → page-heads-DDz_2_hq.d.ts} +3 -2
  30. package/dist/{page-heads-BgbaIjj9.js → page-heads-YL8nyYjF.js} +4 -3
  31. package/dist/page-heads-YL8nyYjF.js.map +1 -0
  32. package/dist/{pi-D3mMmYey.js → pi-Bz4BgZdC.js} +184 -154
  33. package/dist/pi-Bz4BgZdC.js.map +1 -0
  34. package/dist/{projections-Bk-qOOsR.d.ts → projections-Dqo_ih-J.d.ts} +2 -2
  35. package/dist/react.d.ts +5 -3
  36. package/dist/react.js +43 -19
  37. package/dist/react.js.map +1 -1
  38. package/dist/{state-BuJP_3Lr.js → state-2ikjo7uI.js} +638 -25
  39. package/dist/state-2ikjo7uI.js.map +1 -0
  40. package/dist/tanstack-start/index.d.ts +1 -1
  41. package/dist/tanstack-start/index.js +14 -5
  42. package/dist/tanstack-start/index.js.map +1 -1
  43. package/dist/tanstack-start/react.d.ts +1 -1
  44. package/dist/tanstack-start/server.d.ts +2 -2
  45. package/dist/tanstack-start/server.js +1 -1
  46. package/dist/tanstack-start/server.js.map +1 -1
  47. package/dist/{vite-graph-loader-DZCwejuJ.d.ts → vite-graph-loader-BtLgkN0V.d.ts} +7 -5
  48. package/package.json +1 -1
  49. package/dist/checks-BS-aDXB_.js.map +0 -1
  50. package/dist/declare-D5o7hwmv.js +0 -9
  51. package/dist/declare-D5o7hwmv.js.map +0 -1
  52. package/dist/graph-DMNOLXLu.js.map +0 -1
  53. package/dist/page-heads-BgbaIjj9.js.map +0 -1
  54. package/dist/pi-D3mMmYey.js.map +0 -1
  55. package/dist/state-BuJP_3Lr.js.map +0 -1
package/dist/cli.js CHANGED
@@ -1,20 +1,20 @@
1
1
  #!/usr/bin/env bun
2
- import { $ as decodeUnknownEffect, $i as isNotUndefined, $n as tapErrorTag, $r as Error$1, $t as catchDefect, A as Defect, Ai as fromNullishOr, An as orDie, Ar as void_$2, At as transduce, B as Natural, Bi as none, Bn as runPromise, Br as LogToStderr$1, Bt as offerUnsafe, Ci as empty$2, Cn as logWarning, Cr as isDone, Ct as fromPull, D as BooleanLiterals, Di as make$22, Dn as matchEffect, Dr as isFailure, Dt as run$5, E as Boolean$4, Ei as getUnsafe, En as match$2, Er as fail$1, Et as mkString, F as Int$4, Fi as isNone, Fn as provideService, Ft as unwrap$1, G as String$6, Gi as assignProperty, Gn as serviceOption, Gr as onExitPrimitive, Gt as interrupt$1, H as Number$3, Hi as Prototype, Hr as callback, Ht as take, I as Json, Ii as isOption, In as raceFirst, Ir as hasInterruptsOnly, It as endUnsafe, J as TaggedStruct, Ji as redact, Jn as suspend$1, Jr as suspend, Jt as acquireUseRelease, K as Struct, Ki as BaseProto, Kn as sleep, Kr as succeed$1, Kt as runIn, L as Literal, Li as isSome, Ln as result, Lt as failCauseUnsafe, M as Error$2, Mi as getOrElse, Mn as promise, Mr as UnknownError$1, Mt as fromTransform$1, N as FalseLiterals, Ni as getOrNull, Nn as provide$1, Nr as done, Nt as mapDone, O as Class, Oi as exists, On as onExit, Or as isSuccess, Ot as runCollect, P as Finite, Pi as getOrUndefined, Pn as provideContext, Pr as fail$2, Pt as toTransform, Q as Unknown, Qi as isNotNull, Qn as tapError, Qr as MinimumLogLevel$1, Qt as callback$1, R as Literals$2, Ri as map$5, Rn as runFork, Rr as squash, Rt as make$20, Si as add, Sn as logInfo, Sr as doneUnsafe, St as fromChannel$1, T as ArraySchema, Ti as getOrUndefined$1, Tn as mapError, Tr as makeUnsafe$3, Tt as merge, U as Record, Ui as PipeInspectableProto, Un as scope, Ur as flatMap$1, Ut as make$19, V as NullOr, Vi as some, Vr as asSome, Vt as poll, W as Redacted, Wi as withFiber, Wn as scoped, Wr as getCurrentFiber, Wt as makeUnsafe$2, X as Tuple, Xi as isBoolean$1, Xn as tap, Xr as uninterruptibleMask, Xt as andThen, Y as TrueLiterals, Yi as hasProperty, Yn as sync$1, Yr as sync, Yt as addFinalizer$1, Z as Union, Zi as isFunction, Zn as tapCause, Zr as void_$1, Zt as as, _ as prependUrl, _i as reduce, _n as ignore, _r as addFinalizer, _t as make$21, a as layer$13, aa as constVoid, ai as millis, an as context, ar as void_, at as is, bi as Reference, bn as logDebug, br as makeUnsafe$1, bt as decodeText, c as mapRequest, ca as identity, ci as toMillis, cn as ensuring, cr as catchDone, ct as suspend$2, d as acceptJson, di as findFirstIndex, dn as fn, dr as mergeAll, dt as toCodecStringTree, ea as isNullish, ei as TaggedError, en as catchFilter, et as decodeUnknownOption, f as bearerToken, fi as findLast, fn as fnUntraced, fr as provide, ft as toJsonSchemaDocument, g as post, gi as of, gn as gen, gr as Scope, gt as isRedacted, h as get$3, hi as match$1, hn as forkScoped, hr as succeedContext, ht as withDecodingDefault, ia as isUndefined, ii as isFinite, in as clockWith, ir as uninterruptibleMask$1, it as fromJsonString, j as Duration, ji as fromUndefinedOr, jn as orElseSucceed, jr as Done, jt as unwrap$2, k as DateTimeUtc, ki as flatMap$3, kn as option, kr as succeed$4, kt as splitLines, la as pipe, li as empty$1, ln as fail, lr as effect, lt as tag, mi as map$4, mn as forever, mr as succeed$5, mt as withConstructorDefault, na as isObject$1, ni as format$1, nn as catchTags, nr as try_, nt as encodeEffect, o as HttpClient, oa as dual, on as contextWith, or as withFiber$1, ot as optional$3, p as bodyJson, pi as isReadonlyArrayNonEmpty, pn as forEach$1, pr as provideMerge, pt as toTaggedUnion, q as TaggedError$1, qi as format, qn as succeed$2, qr as succeedNone, qt as acquireRelease, r as decodeResearchState, ra as isString, ri as fromInputUnsafe$1, rn as catch_, rr as uninterruptible, rt as encodeSync, s as filterStatusOk, sa as flow, si as seconds, sn as effectify, sr as withSpan, st as optionalKey, ta as isNumber, ti as taggedEnum, tn as catchTag, tr as tryPromise, tt as decodeUnknownSync, u as schemaBodyJson, ua as pipeArguments, ui as findFirst, un as flatMap$2, ur as merge$1, ut as toCodecJson, vi as fail$3, vn as interrupt, vr as addFinalizerExit, vt as value, wi as get$2, wn as map$3, wr as isDoneUnsafe, wt as isStream, xi as Service, xn as logError, xr as _await, xt as empty, y as Redacted$1, yi as succeed$3, yn as isEffect, yr as close, yt as callback$2, z as MutableJson, zi as match, zn as runForkWith, zr as ConsoleRef, zt as offer } from "./state-BuJP_3Lr.js";
2
+ import { $ as JsonObject, $i as getOrUndefined, $n as provideContext, $r as fail$2, $t as toTransform, A as writeResearchCheckpoint, Ai as findFirstIndex, An as fn, Ar as mergeAll, At as toCodecStringTree, Bi as Service, Bn as logError, Br as _await, Bt as empty, C as readWorkflowEdits, Ca as isUndefined, Ci as isFinite, Cn as clockWith, Cr as uninterruptibleMask$1, Ct as is, D as resolveWorkflowRepositoryRoot, Da as identity, Di as toMillis, Dn as ensuring, Dr as catchDone, Dt as suspend$2, E as resolveRepositoryReadPath, Ea as flow, Ei as seconds, En as effectify, Er as withSpan, Et as optionalKey, F as Redacted$1, Fi as of, Fn as gen, Fr as Scope, Ft as isRedacted, G as DateTimeUtc, Gi as getUnsafe, Gn as match$2, Gr as fail$1, Gt as mkString, H as Boolean$4, Hi as empty$2, Hn as logWarning, Hr as isDone, Ht as fromPull, Ii as reduce, In as ignore, Ir as addFinalizer, It as make$21, J as Error$2, Ji as flatMap$3, Jn as option, Jr as succeed$4, Jt as splitLines, K as Defect, Ki as make$22, Kn as matchEffect, Kr as isFailure, Kt as run$6, Li as fail$3, Ln as interrupt, Lr as addFinalizerExit, Lt as value, M as writeWorkflowFailure, Mi as isReadonlyArrayNonEmpty, Mn as forEach$1, Mr as provideMerge, Mt as toTaggedUnion, N as writeWorkflowProgress, Ni as map$4, Nn as forever, Nr as succeed$5, Nt as withConstructorDefault, O as createRunId, Oa as pipe, Oi as empty$1, On as fail, Or as effect, Ot as tag, P as TextTemplate, Pi as match$1, Pn as forkScoped, Pr as succeedContext, Pt as withDecodingDefault, Q as Json, Qi as getOrNull, Qn as provide$1, Qr as done, Qt as mapDone, Ri as succeed$3, Rn as isEffect, Rr as close, Rt as callback$2, Sa as isString, Si as fromInputUnsafe$1, Sn as catch_, Sr as uninterruptible, St as fromJsonString, T as writeWorkflowEdits, Ta as dual, Tn as contextWith, Tr as withFiber$1, Tt as optional$3, U as BooleanLiterals, Ui as get$2, Un as map$3, Ur as isDoneUnsafe, Ut as isStream, V as ArraySchema, Vi as add, Vn as logInfo, Vr as doneUnsafe, Vt as fromChannel$1, W as Class, Wi as getOrUndefined$1, Wn as mapError, Wr as makeUnsafe$3, Wt as merge, X as Finite, Xi as fromUndefinedOr, Xn as orElseSucceed, Xr as Done, Xt as unwrap$2, Y as FalseLiterals, Yi as fromNullishOr, Yn as orDie, Yr as void_$2, Yt as transduce, Z as Int$4, Zi as getOrElse, Zn as promise, Zr as UnknownError$1, Zt as fromTransform$1, _ as prependUrl, _a as isNotNull, _i as MinimumLogLevel$1, _n as callback$1, _r as tapError, _t as decodeUnknownEffect, a as layer$13, aa as none, ai as LogToStderr$1, an as offerUnsafe, ar as runPromise, at as NullOr, b as applyWorkflowEdits, ba as isNumber, bi as taggedEnum, bn as catchTag, br as tryPromise, bt as encodeEffect, c as mapRequest, ca as PipeInspectableProto, ci as flatMap$1, cn as make$19, cr as scope, ct as Redacted, d as acceptJson, da as BaseProto, di as succeed$1, dn as runIn, dr as sleep, dt as TaggedError$1, ea as isNone, en as unwrap$1, er as provideService, et as Literal, f as bearerToken, fa as format, fi as succeedNone, fn as acquireRelease, fr as succeed$2, ft as TaggedStruct, g as post, ga as isFunction, gi as void_$1, gn as as, gr as tapCause, gt as Unknown, h as get$3, ha as isBoolean$1, hi as uninterruptibleMask, hn as andThen, hr as tap, ht as Union, ia as match, ii as ConsoleRef, in as offer, ir as runForkWith, it as Null, j as writeRunBundle, ji as findLast, jn as fnUntraced, jr as provide, jt as toJsonSchemaDocument, k as serializeCause, ka as pipeArguments, ki as findFirst, kn as flatMap$2, kr as merge$1, kt as toCodecJson, la as withFiber, li as getCurrentFiber, ln as makeUnsafe$2, lr as scoped, lt as String$6, ma as hasProperty, mi as sync, mn as addFinalizer$1, mr as sync$1, mt as Tuple, na as isSome, nn as failCauseUnsafe, nr as result$1, nt as MutableJson, o as HttpClient, oa as some, oi as asSome, on as poll, ot as Number$3, p as bodyJson, pa as redact, pi as suspend, pn as acquireUseRelease, pr as suspend$1, pt as TrueLiterals, q as Duration, qi as exists, qn as onExit, qr as isSuccess, qt as runCollect, ra as map$5, ri as squash, rn as make$20, rr as runFork, rt as Natural, s as filterStatusOk, sa as Prototype, si as callback, sn as take, st as Record, t as decodeResearchState, ta as isOption, ti as hasInterruptsOnly, tn as endUnsafe, tr as raceFirst, tt as Literals$2, u as schemaBodyJson, ua as assignProperty, ui as onExitPrimitive, un as interrupt$1, ur as serviceOption, ut as Struct, va as isNotUndefined, vi as Error$1, vn as catchDefect, vr as tapErrorTag, vt as decodeUnknownOption, w as workflowEditsArtifact, wa as constVoid, wi as millis, wn as context, wr as void_, wt as mutable, x as decodeActionState, xa as isObject$1, xi as format$1, xn as catchTags, xr as try_, xt as encodeSync, ya as isNullish, yi as TaggedError, yn as catchFilter, yt as decodeUnknownSync, zi as Reference, zn as logDebug, zr as makeUnsafe$1, zt as decodeText } from "./state-2ikjo7uI.js";
3
3
  import * as NFS from "node:fs";
4
4
  import { existsSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
5
5
  import * as NodeChildProcess from "node:child_process";
6
6
  import { execFileSync, spawn } from "node:child_process";
7
7
  import { PassThrough } from "node:stream";
8
8
  import * as NodeCrypto from "node:crypto";
9
- import { createHash, randomUUID } from "node:crypto";
9
+ import { createHash } from "node:crypto";
10
10
  import * as OS from "node:os";
11
11
  import { tmpdir } from "node:os";
12
12
  import * as Path from "node:path";
13
- import { dirname, join, relative, resolve } from "node:path";
13
+ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
14
14
  import * as NodeUrl from "node:url";
15
15
  import { pathToFileURL } from "node:url";
16
16
  import * as readline from "node:readline";
17
- import { mkdir, mkdtemp, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
17
+ import { mkdir, mkdtemp, readFile, readdir, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
18
18
  import { createRunnableDevEnvironment, resolveConfig } from "vite";
19
19
  import { createServer, request } from "node:http";
20
20
  import { request as request$1 } from "node:https";
@@ -2362,7 +2362,7 @@ const layer$12 = /*#__PURE__*/ effect(ChildProcessSpawner, /* @__PURE__ */ gen(f
2362
2362
  evaluate: () => nodeStream,
2363
2363
  onError: (error) => toPlatformError(`fromWritable(fd${fd})`, toError(error), command)
2364
2364
  });
2365
- if (config.stream) yield* forkScoped(run$5(config.stream, sink));
2365
+ if (config.stream) yield* forkScoped(run$6(config.stream, sink));
2366
2366
  inputSinks.set(fd, sink);
2367
2367
  break;
2368
2368
  }
@@ -2396,7 +2396,7 @@ const layer$12 = /*#__PURE__*/ effect(ChildProcessSpawner, /* @__PURE__ */ gen(f
2396
2396
  endOnDone: config.endOnDone,
2397
2397
  encoding: config.encoding
2398
2398
  });
2399
- if (isStream(config.stream)) return as(forkScoped(run$5(config.stream, sink)), sink);
2399
+ if (isStream(config.stream)) return as(forkScoped(run$6(config.stream, sink)), sink);
2400
2400
  return succeed$2(sink);
2401
2401
  });
2402
2402
  const setupChildOutputStreams = (command, childProcess, stdoutConfig, stderrConfig) => {
@@ -3204,7 +3204,7 @@ const makeFileInfo = (stat) => try_({
3204
3204
  }),
3205
3205
  catch: handleBadArgument("stat")
3206
3206
  });
3207
- const stat = /*#__PURE__*/ (() => {
3207
+ const stat$1 = /*#__PURE__*/ (() => {
3208
3208
  const nodeStat = /*#__PURE__*/ effectify(NFS.stat, /*#__PURE__*/ handleErrnoException("FileSystem", "stat"), /*#__PURE__*/ handleBadArgument("stat"));
3209
3209
  return (path) => flatMap$2(nodeStat(path, { bigint: true }), makeFileInfo);
3210
3210
  })();
@@ -3226,7 +3226,7 @@ const watchNode = (path, info, options) => callback$2((queue) => acquireRelease(
3226
3226
  if (!path) return;
3227
3227
  switch (event) {
3228
3228
  case "rename":
3229
- runFork(matchEffect(stat(Path.resolve(directory, path)), {
3229
+ runFork(matchEffect(stat$1(Path.resolve(directory, path)), {
3230
3230
  onSuccess: (_) => offer(queue, {
3231
3231
  _tag: "Create",
3232
3232
  path
@@ -3259,7 +3259,7 @@ const watchNode = (path, info, options) => callback$2((queue) => acquireRelease(
3259
3259
  });
3260
3260
  return watcher;
3261
3261
  }), (watcher) => sync$1(() => watcher.close())));
3262
- const watch = (backend, path, options) => stat(path).pipe(map$3((stat) => backend.pipe(flatMap$3((_) => _.register(path, stat, options)), getOrElse(() => watchNode(path, stat, options)))), unwrap$2);
3262
+ const watch = (backend, path, options) => stat$1(path).pipe(map$3((stat) => backend.pipe(flatMap$3((_) => _.register(path, stat, options)), getOrElse(() => watchNode(path, stat, options)))), unwrap$2);
3263
3263
  const writeFile$1 = (path, data, options) => callback$1((resume, signal) => {
3264
3264
  try {
3265
3265
  NFS.writeFile(path, data, {
@@ -3294,7 +3294,7 @@ const makeFileSystem = /*#__PURE__*/ map$3(/*#__PURE__*/ serviceOption(WatchBack
3294
3294
  realPath,
3295
3295
  remove,
3296
3296
  rename: rename$1,
3297
- stat,
3297
+ stat: stat$1,
3298
3298
  symlink,
3299
3299
  truncate,
3300
3300
  utimes,
@@ -6009,7 +6009,7 @@ const Password = (options) => basePrompt(options, "password").pipe(map$2(make$21
6009
6009
  * @category running
6010
6010
  * @since 4.0.0
6011
6011
  */
6012
- const run$4 = /*#__PURE__*/ fnUntraced(function* (self) {
6012
+ const run$5 = /*#__PURE__*/ fnUntraced(function* (self) {
6013
6013
  const terminal = yield* Terminal;
6014
6014
  const input = yield* terminal.readInput;
6015
6015
  return yield* runWithInput(self, terminal, input);
@@ -6100,7 +6100,7 @@ const proto = {
6100
6100
  .../*#__PURE__*/ Prototype({
6101
6101
  label: "Prompt",
6102
6102
  evaluate() {
6103
- return run$4(this);
6103
+ return run$5(this);
6104
6104
  }
6105
6105
  }),
6106
6106
  [TypeId$6]: { _Output: (_) => _ }
@@ -9603,7 +9603,7 @@ const scanCommandLevel = (tokens, context) => {
9603
9603
  };
9604
9604
  //#endregion
9605
9605
  //#region node_modules/effect/dist/cli/internal/wizard.js
9606
- const run$3 = /*#__PURE__*/ fnUntraced(function* (command, options) {
9606
+ const run$4 = /*#__PURE__*/ fnUntraced(function* (command, options) {
9607
9607
  const commandPath = options?.commandPath ?? [command.name];
9608
9608
  const selected = getCommandAtPath(command, commandPath);
9609
9609
  const commandLine = (options?.prefix ?? commandPath).map((value) => commandLineArg(value));
@@ -9638,7 +9638,7 @@ const promptCommand = /*#__PURE__*/ fnUntraced(function* (command, commandLine,
9638
9638
  if (visibleSubcommands.length > 0) yield* logCurrentCommand(commandLine);
9639
9639
  }
9640
9640
  if (visibleSubcommands.length === 0) return;
9641
- const child = yield* run$4(Select({
9641
+ const child = yield* run$5(Select({
9642
9642
  message: "Command",
9643
9643
  choices: visibleSubcommands.map((command) => ({
9644
9644
  title: command.name,
@@ -9658,7 +9658,7 @@ const promptParam = /*#__PURE__*/ fnUntraced(function* (param) {
9658
9658
  const single = getUnderlyingSingleOrThrow(param);
9659
9659
  const metadata = getParamMetadata(param);
9660
9660
  if (metadata.isOptional) {
9661
- if (!(yield* run$4(Confirm({
9661
+ if (!(yield* run$5(Confirm({
9662
9662
  message: `Set ${renderParamLabel(single)}?`,
9663
9663
  initial: false
9664
9664
  })))) {
@@ -9666,7 +9666,7 @@ const promptParam = /*#__PURE__*/ fnUntraced(function* (param) {
9666
9666
  return [];
9667
9667
  }
9668
9668
  }
9669
- const count = !metadata.isVariadic ? 1 : yield* run$4(Int$2({
9669
+ const count = !metadata.isVariadic ? 1 : yield* run$5(Int$2({
9670
9670
  message: `${renderParamLabel(single)} count`,
9671
9671
  default: getOrElse(metadata.variadicMin, () => 0),
9672
9672
  min: getOrElse(metadata.variadicMin, () => 0),
@@ -9689,7 +9689,7 @@ const promptParam = /*#__PURE__*/ fnUntraced(function* (param) {
9689
9689
  const promptSingle = (single) => {
9690
9690
  const message = renderParamMessage(single);
9691
9691
  switch (single.primitiveType._tag) {
9692
- case "Boolean": return map$3(run$4(Confirm({
9692
+ case "Boolean": return map$3(run$5(Confirm({
9693
9693
  message,
9694
9694
  label: {
9695
9695
  confirm: "true",
@@ -9702,7 +9702,7 @@ const promptSingle = (single) => {
9702
9702
  })), (value) => commandLineArg(String(value)));
9703
9703
  case "Choice": {
9704
9704
  const choices = getChoiceKeys(single.primitiveType) ?? [];
9705
- return map$3(run$4(Select({
9705
+ return map$3(run$5(Select({
9706
9706
  message,
9707
9707
  choices: choices.map((choice) => ({
9708
9708
  title: choice,
@@ -9710,11 +9710,11 @@ const promptSingle = (single) => {
9710
9710
  }))
9711
9711
  })), commandLineArg);
9712
9712
  }
9713
- case "Date": return map$3(run$4(Date$1({ message })), (date) => commandLineArg(date.toISOString()));
9714
- case "Finite": return map$3(run$4(Number$1({ message })), (value) => commandLineArg(String(value)));
9715
- case "Int": return map$3(run$4(Int$2({ message })), (value) => commandLineArg(String(value)));
9716
- case "Redacted": return map$3(run$4(Password({ message })), (value$1) => commandLineArg(value(value$1), "<redacted>"));
9717
- default: return map$3(run$4(String$4({ message })), commandLineArg);
9713
+ case "Date": return map$3(run$5(Date$1({ message })), (date) => commandLineArg(date.toISOString()));
9714
+ case "Finite": return map$3(run$5(Number$1({ message })), (value) => commandLineArg(String(value)));
9715
+ case "Int": return map$3(run$5(Int$2({ message })), (value) => commandLineArg(String(value)));
9716
+ case "Redacted": return map$3(run$5(Password({ message })), (value$1) => commandLineArg(value(value$1), "<redacted>"));
9717
+ default: return map$3(run$5(String$4({ message })), commandLineArg);
9718
9718
  }
9719
9719
  };
9720
9720
  const commandLineArg = (value, displayValue = value) => ({
@@ -10276,7 +10276,7 @@ const showUserError = (error$4) => gen(function* () {
10276
10276
  * @category running
10277
10277
  * @since 4.0.0
10278
10278
  */
10279
- const run$2 = /*#__PURE__*/ dual(2, (command, config) => Stdio.use(({ args }) => flatMap$2(args, (args) => runWith(command, config)(args))));
10279
+ const run$3 = /*#__PURE__*/ dual(2, (command, config) => Stdio.use(({ args }) => flatMap$2(args, (args) => runWith(command, config)(args))));
10280
10280
  /**
10281
10281
  * Runs a command with explicitly provided arguments instead of using arguments from `Stdio`.
10282
10282
  *
@@ -10387,12 +10387,12 @@ const runWith = (command, config) => {
10387
10387
  if (flag === Wizard) return yield* gen(function* () {
10388
10388
  yield* log(renderIntroduction(command.name, config.version, command.description));
10389
10389
  const prefix = [command.name, ...args.filter((arg) => arg !== "--wizard" && !arg.startsWith("--wizard="))];
10390
- const wizardResult = yield* run$3(command, {
10390
+ const wizardResult = yield* run$4(command, {
10391
10391
  commandPath,
10392
10392
  prefix
10393
10393
  });
10394
10394
  yield* log(renderCompletion(wizardResult.displayArgs));
10395
- if (yield* run$4(Toggle({
10395
+ if (yield* run$5(Toggle({
10396
10396
  message: "Run this command?",
10397
10397
  initial: true,
10398
10398
  active: "yes",
@@ -10412,7 +10412,7 @@ const runWith = (command, config) => {
10412
10412
  commandPath,
10413
10413
  errors: parsedArgs.errors
10414
10414
  });
10415
- const parseResult = yield* result(commandImpl.parse(parsedArgs));
10415
+ const parseResult = yield* result$1(commandImpl.parse(parsedArgs));
10416
10416
  if (parseResult._tag === "Failure") return yield* new ShowHelp({
10417
10417
  commandPath,
10418
10418
  errors: [parseResult.failure]
@@ -10434,7 +10434,378 @@ const runWith = (command, config) => {
10434
10434
  };
10435
10435
  //#endregion
10436
10436
  //#region package.json
10437
- var version = "0.14.1";
10437
+ var version = "0.15.1";
10438
+ //#endregion
10439
+ //#region node_modules/effect/dist/cli/Argument.js
10440
+ /**
10441
+ * Creates a positional string argument.
10442
+ *
10443
+ * **Example** (Creating a string argument)
10444
+ *
10445
+ * ```ts import.meta.vitest
10446
+ * import { Argument } from "effect/cli"
10447
+ *
10448
+ * const filename = Argument.String("filename")
10449
+ * filename.kind // => "argument"
10450
+ * ```
10451
+ *
10452
+ * @stability unstable
10453
+ * @category constructors
10454
+ * @since 4.0.0
10455
+ */
10456
+ const String$1 = (name) => String$3(argumentKind, name);
10457
+ /**
10458
+ * Makes a positional argument optional.
10459
+ *
10460
+ * **Example** (Making an argument optional)
10461
+ *
10462
+ * ```ts import.meta.vitest
10463
+ * import { Argument } from "effect/cli"
10464
+ *
10465
+ * const optionalVersion = Argument.String("version").pipe(Argument.optional)
10466
+ * optionalVersion.kind // => "argument"
10467
+ * ```
10468
+ *
10469
+ * @stability unstable
10470
+ * @category combinators
10471
+ * @since 4.0.0
10472
+ */
10473
+ const optional = (arg) => optional$2(arg);
10474
+ /**
10475
+ * Adds a description to a positional argument.
10476
+ *
10477
+ * **Example** (Adding an argument description)
10478
+ *
10479
+ * ```ts import.meta.vitest
10480
+ * import { Argument } from "effect/cli"
10481
+ *
10482
+ * const filename = Argument.String("filename").pipe(
10483
+ * Argument.withDescription("The input file to process")
10484
+ * )
10485
+ * filename.kind // => "argument"
10486
+ * ```
10487
+ *
10488
+ * @stability unstable
10489
+ * @category combinators
10490
+ * @since 4.0.0
10491
+ */
10492
+ const withDescription = /*#__PURE__*/ dual(2, (self, description) => withDescription$3(self, description));
10493
+ /**
10494
+ * Creates a variadic positional argument that accepts multiple values.
10495
+ *
10496
+ * **Example** (Accepting multiple values)
10497
+ *
10498
+ * ```ts import.meta.vitest
10499
+ * import { Argument } from "effect/cli"
10500
+ *
10501
+ * // Accept any number of files
10502
+ * const anyFiles = Argument.String("files").pipe(Argument.variadic)
10503
+ *
10504
+ * // Accept at least 1 file
10505
+ * const atLeastOneFile = Argument.String("files").pipe(
10506
+ * Argument.variadic({ min: 1 })
10507
+ * )
10508
+ *
10509
+ * // Accept between 1 and 5 files
10510
+ * const limitedFiles = Argument.String("files").pipe(
10511
+ * Argument.variadic({ min: 1, max: 5 })
10512
+ * )
10513
+ *
10514
+ * const kinds = [anyFiles.kind, atLeastOneFile.kind, limitedFiles.kind] // => ["argument", "argument", "argument"]
10515
+ * ```
10516
+ *
10517
+ * @stability unstable
10518
+ * @category combinators
10519
+ * @since 4.0.0
10520
+ */
10521
+ const variadic = /*#__PURE__*/ dual((args) => isParam(args[0]), (self, options) => variadic$1(self, options));
10522
+ //#endregion
10523
+ //#region src/cli/output.ts
10524
+ /**
10525
+ * Shared surfaces for the `pagegraph` CLI. The three output planes:
10526
+ * - **data** → stdout via {@link printJson} / {@link printText}. Under `--json`
10527
+ * this is the *only* thing on stdout: no ANSI, no status, valid JSON.
10528
+ * - **status** → stderr via Effect leveled logging (`Effect.logInfo` /
10529
+ * `Effect.logDebug`), routed off stdout by `Logger.LogToStderr` in `main.ts`
10530
+ * and gated by the built-in `--log-level` flag.
10531
+ * - **diagnostics** → stderr; expected failures surface as {@link SeoCliError},
10532
+ * printed by the entrypoint with a non-zero exit.
10533
+ */
10534
+ /** Expected, user-facing CLI failure — message to stderr, process exits non-zero. */
10535
+ var SeoCliError = class extends TaggedError("SeoCliError") {};
10536
+ /** Machine-readable output. When set, stdout is exactly the JSON payload. */
10537
+ const jsonFlag = Boolean$1("json").pipe(withDescription$2("Emit the payload as JSON on stdout (no status, no color)"), withDefault(false));
10538
+ /**
10539
+ * Absolute origin the sitemap/robots projection is rendered under. It carries no
10540
+ * static default: the fallback comes from the loaded graph's site, which is not
10541
+ * known until the graph is acquired. Resolve it with {@link originOf}.
10542
+ */
10543
+ const originFlag = String$2("origin").pipe(withDescription$2("Absolute origin for URLs (default: origin from the loaded graph)"), optional$1);
10544
+ /** The `--origin` flag when given, else the origin in the loaded graph's site identity. */
10545
+ const originOf = (flag, configured) => getOrElse(flag, () => configured);
10546
+ /**
10547
+ * `--indexable` / `--no-indexable`. When omitted, the loaded graph's site identity
10548
+ * decides; a non-indexable host yields a disallow-all robots.txt with no Sitemap line.
10549
+ */
10550
+ const indexableFlag = Boolean$1("indexable").pipe(withDescription$2("Render as an indexable host; --no-indexable = disallow-all robots.txt"), optional$1);
10551
+ /** The `--indexable` flag when given, else the graph host's indexability. */
10552
+ const indexableOf = (flag, configured) => getOrElse(flag, () => configured);
10553
+ /** Data plane: pretty-printed JSON on stdout. */
10554
+ const printJson = (value) => log(JSON.stringify(value, null, 2));
10555
+ /** Data plane: a block of already-formatted text on stdout. */
10556
+ const printText = (text) => log(text);
10557
+ //#endregion
10558
+ //#region src/cli/load-config.ts
10559
+ /**
10560
+ * Config discovery and graph acquisition for the `pagegraph` CLI.
10561
+ *
10562
+ * The CLI knows how to *view* a graph; the host knows how to *produce* one. That
10563
+ * seam is a `pagegraph.config.ts` at the app root, found by walking up from the working
10564
+ * directory — so `bun run pagegraph check` works from anywhere inside the app.
10565
+ *
10566
+ * The config is a TypeScript module the CLI imports directly, which is one of the
10567
+ * reasons the `bin` runs under Bun (the other being the synchronous fd-1 flush in
10568
+ * `main.ts`).
10569
+ */
10570
+ const CONFIG_FILENAMES = [
10571
+ "pagegraph.config.ts",
10572
+ "pagegraph.config.js",
10573
+ "pagegraph.config.mjs"
10574
+ ];
10575
+ const LEGACY_CONFIG_FILENAMES = [
10576
+ "seo.config.ts",
10577
+ "seo.config.js",
10578
+ "seo.config.mjs"
10579
+ ];
10580
+ const messageOf$2 = (cause) => cause instanceof Error ? cause.message : String(cause);
10581
+ /** First `pagegraph.config.*` at or above `from`, or undefined at the filesystem root. */
10582
+ const findConfigFile = (from) => {
10583
+ let directory = resolve(from);
10584
+ for (;;) {
10585
+ for (const filename of CONFIG_FILENAMES) {
10586
+ const candidate = join(directory, filename);
10587
+ if (existsSync(candidate)) return candidate;
10588
+ }
10589
+ const parent = dirname(directory);
10590
+ if (parent === directory) return void 0;
10591
+ directory = parent;
10592
+ }
10593
+ };
10594
+ /** Find an old config only to provide the explicit breaking-rename error. */
10595
+ const findLegacyConfigFile = (from) => {
10596
+ let directory = resolve(from);
10597
+ for (;;) {
10598
+ for (const filename of LEGACY_CONFIG_FILENAMES) {
10599
+ const candidate = join(directory, filename);
10600
+ if (existsSync(candidate)) return candidate;
10601
+ }
10602
+ const parent = dirname(directory);
10603
+ if (parent === directory) return void 0;
10604
+ directory = parent;
10605
+ }
10606
+ };
10607
+ const configNotFoundMessage = (cwd) => {
10608
+ if (findLegacyConfigFile(cwd) !== void 0) return "seo.config.ts was renamed to pagegraph.config.ts; rename the file";
10609
+ return `No ${CONFIG_FILENAMES[0]} in ${cwd} or any parent directory. Create one that exports \`defineSeoConfig({ loadGraph })\` from "pagegraph/config".`;
10610
+ };
10611
+ const isStringArray = (value) => Array.isArray(value) && value.every(isString);
10612
+ const isCoverageRules = (value) => Array.isArray(value) && value.every((rule) => isObject$1(rule) && isString(rule["path"]) && Number.isSafeInteger(rule["minInbound"]) && rule["minInbound"] > 0);
10613
+ const isPositiveInteger = (value) => Number.isSafeInteger(value) && value > 0;
10614
+ const isContentPolicy = (value) => isObject$1(value) && (value["minWords"] === void 0 || Array.isArray(value["minWords"]) && value["minWords"].every((rule) => isObject$1(rule) && isString(rule["path"]) && isPositiveInteger(rule["minWords"])));
10615
+ const isOgImagePolicy = (value) => isObject$1(value) && Object.keys(value).every((key) => key === "severity" || key === "publicDirectory") && (value["severity"] === void 0 || value["severity"] === "structural" || value["severity"] === "editorial" || value["severity"] === "off") && (value["publicDirectory"] === void 0 || isString(value["publicDirectory"]) && value["publicDirectory"].trim().length > 0);
10616
+ const isFreshnessPolicy = (value) => isObject$1(value) && isPositiveInteger(value["maxAgeDays"]);
10617
+ const isStringRecord = (value) => isObject$1(value) && Object.values(value).every(isString);
10618
+ const isWorkflowTimeout = (value) => Number.isSafeInteger(value) && value > 0 && value <= 2147483647;
10619
+ const isStringArrayRecord = (value) => isObject$1(value) && Object.values(value).every(isStringArray);
10620
+ const isWorkflowConfig = (value) => {
10621
+ if (!isObject$1(value) || !isObject$1(value["agent"])) return false;
10622
+ const agent = value["agent"];
10623
+ const context = value["context"];
10624
+ return isString(agent["presetDirectory"]) && isString(agent["defaultModel"]) && (agent["models"] === void 0 || isStringRecord(agent["models"])) && (agent["timeoutMs"] === void 0 || isWorkflowTimeout(agent["timeoutMs"])) && (context === void 0 || isObject$1(context) && (context["files"] === void 0 || isStringArray(context["files"])) && (context["byWorkflow"] === void 0 || isStringArrayRecord(context["byWorkflow"]))) && (value["runsDirectory"] === void 0 || isString(value["runsDirectory"])) && (value["repositoryRoot"] === void 0 || isString(value["repositoryRoot"]) && value["repositoryRoot"].trim().length > 0);
10625
+ };
10626
+ const CLI_CONFIG_FIELDS = /* @__PURE__ */ new Set([
10627
+ "loadGraph",
10628
+ "transform",
10629
+ "coverage",
10630
+ "ogImage",
10631
+ "content",
10632
+ "freshness",
10633
+ "workflows"
10634
+ ]);
10635
+ /**
10636
+ * `pagegraph.config.ts` is the consumer's file and may be plain JS, so its types are
10637
+ * a suggestion, not a guarantee. Check every field the CLI consumes at this boundary.
10638
+ */
10639
+ const isSeoCliConfig = (value) => isObject$1(value) && Object.keys(value).every((key) => CLI_CONFIG_FIELDS.has(key)) && isFunction(value["loadGraph"]) && (value["transform"] === void 0 || isFunction(value["transform"])) && (value["coverage"] === void 0 || isCoverageRules(value["coverage"])) && (value["content"] === void 0 || isContentPolicy(value["content"])) && (value["ogImage"] === void 0 || isOgImagePolicy(value["ogImage"])) && (value["freshness"] === void 0 || isFreshnessPolicy(value["freshness"])) && (value["workflows"] === void 0 || isWorkflowConfig(value["workflows"]));
10640
+ /** Import and validate one config path. */
10641
+ const loadConfigFile = (configPath) => gen(function* () {
10642
+ yield* logDebug(`Loading SEO config from ${configPath}`);
10643
+ const module = yield* tryPromise({
10644
+ try: () => import(pathToFileURL(configPath).href),
10645
+ catch: (cause) => new SeoCliError({ message: `Could not load ${configPath}: ${messageOf$2(cause)}` })
10646
+ });
10647
+ if (!isSeoCliConfig(module.default)) {
10648
+ const duplicatedField = isObject$1(module.default) ? Object.keys(module.default).find((key) => !CLI_CONFIG_FIELDS.has(key)) : void 0;
10649
+ return yield* new SeoCliError({ message: duplicatedField === void 0 ? `${configPath} must default-export defineSeoConfig({ loadGraph }).` : `${configPath} contains unsupported field "${duplicatedField}"; pagegraph.config.ts accepts only CLI fields, while site identity and plugin options belong to the graph loader and pagegraph() in vite.config.ts.` });
10650
+ }
10651
+ return module.default;
10652
+ });
10653
+ /**
10654
+ * Load the app's `pagegraph.config.ts`. Cheap to run more than once per process: the
10655
+ * ESM cache evaluates the config module exactly once.
10656
+ */
10657
+ const loadSeoConfig = gen(function* () {
10658
+ const cwd = process.cwd();
10659
+ const configPath = findConfigFile(cwd);
10660
+ if (configPath === void 0) return yield* new SeoCliError({ message: configNotFoundMessage(cwd) });
10661
+ return yield* loadConfigFile(configPath);
10662
+ });
10663
+ /** Load the config together with the project root that owns it. */
10664
+ const loadSeoProjectConfig = gen(function* () {
10665
+ const cwd = process.cwd();
10666
+ const configPath = findConfigFile(cwd);
10667
+ if (configPath === void 0) return yield* new SeoCliError({ message: configNotFoundMessage(cwd) });
10668
+ return {
10669
+ config: yield* loadConfigFile(configPath),
10670
+ configPath,
10671
+ root: dirname(configPath)
10672
+ };
10673
+ });
10674
+ /**
10675
+ * Like {@link loadSeoConfig}, but `undefined` when no config exists. For a
10676
+ * framework-independent command that enriches its report when an app is present
10677
+ * without requiring one. A config that exists but fails to load still fails —
10678
+ * silence would hide a broken declaration.
10679
+ */
10680
+ const loadSeoConfigOptional = gen(function* () {
10681
+ const configPath = findConfigFile(process.cwd());
10682
+ if (configPath === void 0) {
10683
+ if (findLegacyConfigFile(process.cwd()) !== void 0) return yield* new SeoCliError({ message: configNotFoundMessage(process.cwd()) });
10684
+ return;
10685
+ }
10686
+ return yield* loadConfigFile(configPath);
10687
+ });
10688
+ /**
10689
+ * The live SEO graph as a scoped resource: whatever the loader acquired (for
10690
+ * {@link viteGraphLoader}, an in-process Vite server) is released when the
10691
+ * surrounding `Effect.scoped` exits, on success or failure.
10692
+ */
10693
+ const acquireLoadedGraph = (config, environment) => gen(function* () {
10694
+ yield* logDebug("Loading the SEO graph…");
10695
+ const load = async () => {
10696
+ const loaded = await config.loadGraph(void 0, environment);
10697
+ if (!isLoadedSeoGraph(loaded)) {
10698
+ let disposeFailure;
10699
+ if (isObject$1(loaded) && isFunction(loaded["dispose"])) try {
10700
+ await loaded["dispose"]();
10701
+ } catch (cause) {
10702
+ disposeFailure = cause;
10703
+ }
10704
+ const contractError = "The graph loader must return { graph, site: { origin, indexable, robots }, dispose }.";
10705
+ throw new Error(disposeFailure === void 0 ? contractError : `${contractError} Releasing the invalid result also failed: ${messageOf$2(disposeFailure)}`);
10706
+ }
10707
+ return loaded;
10708
+ };
10709
+ const loaded = yield* acquireRelease(tryPromise({
10710
+ try: load,
10711
+ catch: (cause) => new SeoCliError({ message: messageOf$2(cause) })
10712
+ }), (acquired) => promise(() => acquired.dispose()).pipe(catchDefect((defect) => logWarning(`Could not dispose the SEO graph loader: ${messageOf$2(defect)}`))));
10713
+ yield* logDebug(`Loaded SEO graph: ${loaded.graph.nodes.size} nodes, ${loaded.graph.edges.length} edges.`);
10714
+ return loaded;
10715
+ });
10716
+ const isLoadedSeoGraph = (value) => {
10717
+ if (!isObject$1(value) || !isObject$1(value["graph"]) || !(value["graph"]["nodes"] instanceof Map) || !Array.isArray(value["graph"]["edges"]) || !isFunction(value["dispose"]) || !isObject$1(value["site"])) return false;
10718
+ const site = value["site"];
10719
+ const robots = site["robots"];
10720
+ let canonicalOrigin = false;
10721
+ if (isString(site["origin"])) try {
10722
+ const url = new URL(site["origin"]);
10723
+ canonicalOrigin = (url.protocol === "http:" || url.protocol === "https:") && url.origin === site["origin"];
10724
+ } catch {
10725
+ canonicalOrigin = false;
10726
+ }
10727
+ return canonicalOrigin && isBoolean$1(site["indexable"]) && isObject$1(robots) && isStringArray(robots["disallow"]) && (robots["contentSignal"] === void 0 || isString(robots["contentSignal"])) && (robots["directives"] === void 0 || isStringArray(robots["directives"]));
10728
+ };
10729
+ /** {@link acquireLoadedGraph}, for commands that need only the graph. */
10730
+ const acquireGraph = (config) => map$3(acquireLoadedGraph(config), (loaded) => loaded.graph);
10731
+ //#endregion
10732
+ //#region src/cli/commands/apply.ts
10733
+ const runArgument = String$1("run").pipe(withDescription("Improve workflow run directory or run ID containing edits.json"));
10734
+ const onlyFlag = String$2("only").pipe(withDescription$2("Edit ID to apply (repeatable or comma-separated; default: every edit)"), between(0, 256));
10735
+ const checkFlag = Boolean$1("check").pipe(withDescription$2("Report which edits would apply without writing files"), withDefault(false));
10736
+ const symbols = {
10737
+ applied: "✓",
10738
+ applicable: "✓",
10739
+ stale: "✗",
10740
+ failed: "✗"
10741
+ };
10742
+ const renderApplyReport = (report) => [
10743
+ ...report.edits.map((edit) => `${symbols[edit.status]} ${edit.id} ${edit.status} ${edit.path}${edit.reason === void 0 ? "" : `: ${edit.reason}`}`),
10744
+ "",
10745
+ report.check ? `${report.counts.applicable} applicable, ${report.counts.stale} stale, ${report.counts.failed} failed (check only; no files changed)` : `${report.counts.applied} applied, ${report.counts.stale} stale, ${report.counts.failed} failed`
10746
+ ].join("\n");
10747
+ const applyCommand = make$6("apply", {
10748
+ run: runArgument,
10749
+ only: onlyFlag,
10750
+ check: checkFlag,
10751
+ json: jsonFlag
10752
+ }).pipe(withDescription$1("Apply the edits an improve workflow recorded, refusing edits whose target text changed"), withExamples([
10753
+ {
10754
+ command: "pagegraph apply <run-id> --check",
10755
+ description: "Report which recorded edits still apply"
10756
+ },
10757
+ {
10758
+ command: "pagegraph apply <run-id>",
10759
+ description: "Apply every recorded edit that still matches"
10760
+ },
10761
+ {
10762
+ command: "pagegraph apply .pagegraph/runs/<run-id> --only e1,e3",
10763
+ description: "Apply a subset of edits"
10764
+ }
10765
+ ]), withHandler(fnUntraced(function* ({ run, only, check, json }) {
10766
+ const project = yield* loadSeoProjectConfig;
10767
+ const workflows = project.config.workflows;
10768
+ if (workflows === void 0) return yield* new SeoCliError({ message: "pagegraph.config.ts has no workflows configuration; run `pagegraph init` and configure workflows." });
10769
+ const runsDirectory = workflows.runsDirectory ?? ".pagegraph/runs";
10770
+ const loaded = yield* try_({
10771
+ try: () => readWorkflowEdits(project.root, runsDirectory, run),
10772
+ catch: (cause) => new SeoCliError({ message: cause instanceof Error ? cause.message : String(cause) })
10773
+ });
10774
+ if ((yield* try_({
10775
+ try: () => realpathSync(loaded.edits.root),
10776
+ catch: () => new SeoCliError({ message: `The run's recorded app root no longer exists: ${loaded.edits.root}` })
10777
+ })) !== realpathSync(project.root)) return yield* new SeoCliError({ message: `Run ${loaded.edits.id} recorded edits for ${loaded.edits.root}; run apply from that app's pagegraph.config.ts directory.` });
10778
+ const ids = only.flatMap((value) => value.split(",")).map((value) => value.trim()).filter((value) => value.length > 0);
10779
+ const unknown = ids.filter((id) => !loaded.edits.edits.some((edit) => edit.id === id));
10780
+ if (unknown.length > 0) return yield* new SeoCliError({ message: `Unknown edit ID(s) for run ${loaded.edits.id}: ${unknown.join(", ")}. Recorded: ${loaded.edits.edits.map((edit) => edit.id).join(", ") || "none"}.` });
10781
+ const selected = ids.length === 0 ? loaded.edits.edits : loaded.edits.edits.filter((edit) => ids.includes(edit.id));
10782
+ const outcomes = yield* tryPromise({
10783
+ try: () => applyWorkflowEdits(project.root, selected, {
10784
+ presetDirectory: resolve(project.root, workflows.agent.presetDirectory),
10785
+ runsDirectory: resolve(project.root, runsDirectory),
10786
+ check
10787
+ }),
10788
+ catch: (cause) => new SeoCliError({ message: cause instanceof Error ? cause.message : String(cause) })
10789
+ });
10790
+ const count = (status) => outcomes.filter((outcome) => outcome.status === status).length;
10791
+ const report = {
10792
+ kind: "pagegraph-apply-report",
10793
+ schemaVersion: 1,
10794
+ run: loaded.edits.id,
10795
+ directory: loaded.directory,
10796
+ check,
10797
+ edits: outcomes,
10798
+ counts: {
10799
+ applied: count("applied"),
10800
+ applicable: count("applicable"),
10801
+ stale: count("stale"),
10802
+ failed: count("failed")
10803
+ }
10804
+ };
10805
+ if (json) yield* printJson(report);
10806
+ else yield* printText(selected.length === 0 ? `Run ${loaded.edits.id} recorded no edits.` : renderApplyReport(report));
10807
+ if (report.counts.stale + report.counts.failed > 0) return yield* new SeoCliError({ message: `${report.counts.stale} stale and ${report.counts.failed} failed edit(s) were not applied; see the report above.` });
10808
+ })));
10438
10809
  //#endregion
10439
10810
  //#region node_modules/effect/dist/ai/AiError.js
10440
10811
  /**
@@ -12125,7 +12496,7 @@ const TypeId$2 = "~effect/ai/DecisionModel";
12125
12496
  * @category models
12126
12497
  * @since 4.0.0
12127
12498
  */
12128
- var DecisionUsage = class extends Class("effect/ai/DecisionModel/DecisionUsage")({
12499
+ var DecisionUsage$1 = class extends Class("effect/ai/DecisionModel/DecisionUsage")({
12129
12500
  inputTokens: /*#__PURE__*/ optional$3(Finite),
12130
12501
  outputTokens: /*#__PURE__*/ optional$3(Finite)
12131
12502
  }) {};
@@ -12244,7 +12615,7 @@ const make$3 = (params) => sync$1(() => {
12244
12615
  decisions: definition.decisions
12245
12616
  })), flatMap$2((response) => map$3(validateAnswers(definition.decisions, response.answers, roundingError), (answers) => ({
12246
12617
  answers,
12247
- usage: new DecisionUsage({
12618
+ usage: new DecisionUsage$1({
12248
12619
  inputTokens: response.usage.inputTokens,
12249
12620
  outputTokens: response.usage.outputTokens
12250
12621
  })
@@ -12391,7 +12762,7 @@ const make$2 = (provider, modelName, layer) => Object.assign(Object.create(Proto
12391
12762
  * @category constructors
12392
12763
  * @since 4.0.0
12393
12764
  */
12394
- const model = (model) => make$2("typesafe", model, layer({ model }));
12765
+ const model$1 = (model) => make$2("typesafe", model, layer({ model }));
12395
12766
  /**
12396
12767
  * Builds a decision service. Probabilities arrive rounded to two decimals, so
12397
12768
  * DecisionModel rescales small sum drift when it validates distributions and
@@ -13000,6 +13371,17 @@ function checkGraph(graph, options = {}) {
13000
13371
  message: raw.message,
13001
13372
  fix: raw.fix
13002
13373
  })));
13374
+ const imageSeverity = options.ogImage?.severity ?? "structural";
13375
+ if (imageSeverity !== "off") for (const node of graph.nodes.values()) {
13376
+ if (!isSitemapEligible(node) || node.head?.image?.url.trim()) continue;
13377
+ violations.push({
13378
+ rule: "missing-og-image",
13379
+ severity: imageSeverity,
13380
+ path: node.path,
13381
+ message: `Sitemap page "${node.path}" has no resolved OG image.`,
13382
+ fix: "Declare staticData.seo.head.image (or a collection image), or supply an ogImage resolver to pagegraph()."
13383
+ });
13384
+ }
13003
13385
  const coverage = options.coverage;
13004
13386
  if (coverage !== void 0 && coverage.length > 0) violations.push(...checkCoverage(graph, coverage));
13005
13387
  if (options.freshness !== void 0) violations.push(...checkFreshness(graph, options.freshness, options.now));
@@ -14192,6 +14574,40 @@ const make = (options) => {
14192
14574
  };
14193
14575
  };
14194
14576
  //#endregion
14577
+ //#region src/decide/record.ts
14578
+ /**
14579
+ * The one record every decision family emits, and the batch report that groups
14580
+ * them. A record is self-describing: which family, model, threshold, and input
14581
+ * produced the verdict, plus the raw family-decoded answers the model returned.
14582
+ *
14583
+ * Answers stay `Schema.Unknown` because each family owns its own answer shape;
14584
+ * the record is the transport, not another schema to keep in sync. `review`
14585
+ * mirrors `verdict === "review"` and is what `--review-out` selects on.
14586
+ */
14587
+ /** Provider-reported token usage; unreported counts stay `undefined`. */
14588
+ const DecisionUsage = Struct({
14589
+ inputTokens: optional$3(Number$3),
14590
+ outputTokens: optional$3(Number$3)
14591
+ });
14592
+ /** One answered decision: what was asked, what came back, and how it landed. */
14593
+ const DecisionRecord = Struct({
14594
+ /** Family-qualified stable id: `<family>:<index>` within the batch. */
14595
+ decisionId: String$6,
14596
+ schemaVersion: Literal(1),
14597
+ family: String$6,
14598
+ model: String$6,
14599
+ threshold: Number$3,
14600
+ /** sha256 of the canonical input JSON — the cache key. */
14601
+ inputHash: String$6,
14602
+ /** Human reference for the input: a URL, `query:…`, or `from → to`. */
14603
+ inputRef: String$6,
14604
+ verdict: String$6,
14605
+ review: Boolean$4,
14606
+ /** Family-decoded model answers, exactly as the family reads them. */
14607
+ answers: Unknown,
14608
+ usage: optional$3(DecisionUsage)
14609
+ });
14610
+ //#endregion
14195
14611
  //#region src/decide/run.ts
14196
14612
  /**
14197
14613
  * The decision runner. A family supplies its input schema, a definition built
@@ -14474,184 +14890,65 @@ function verifyDocuments(documents, heads) {
14474
14890
  for (const document of documents) {
14475
14891
  verifyPath(document.path);
14476
14892
  if (paths.has(document.path)) throw new Error(`Duplicate captured document path "${document.path}"`);
14477
- paths.add(document.path);
14478
- const { hash, ...content } = document;
14479
- if (!/^[0-9a-f]{64}$/.test(hash) || hashDocument(content) !== hash) throw new Error(`Invalid captured document hash for "${document.path}"; build the app again.`);
14480
- }
14481
- const headPaths = /* @__PURE__ */ new Set();
14482
- for (const head of heads) {
14483
- verifyPath(head.path);
14484
- if (paths.has(head.path)) throw new Error(`Duplicate captured page path "${head.path}"`);
14485
- if (headPaths.has(head.path)) throw new Error(`Duplicate captured head path "${head.path}"`);
14486
- headPaths.add(head.path);
14487
- }
14488
- if (documents.length === 0 && heads.length === 0) throw new Error("No captured markdown pages; build the app or use --dev <origin>.");
14489
- }
14490
- /** Read the persisted document tree and graph heads; malformed captures always fail. */
14491
- async function readMarkdownCapture(root, origin) {
14492
- const directory = resolve(root, ".pagegraph/documents");
14493
- const documents = [];
14494
- const visit = async (folder) => {
14495
- for (const entry of await readdir(folder, { withFileTypes: true })) {
14496
- const file = resolve(folder, entry.name);
14497
- if (entry.isDirectory()) {
14498
- await visit(file);
14499
- continue;
14500
- }
14501
- if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
14502
- const document = decodeUnknownSync(fromJsonString(PageDocument))(await readFile(file, "utf8"), { onExcessProperty: "error" });
14503
- verifyPath(document.path);
14504
- const name = document.path === "/" ? "index" : document.path.slice(1);
14505
- if (file !== resolve(directory, `${name}.json`)) throw new Error(`Captured document "${document.path}" is stored at the wrong path: ${file}`);
14506
- documents.push(document);
14507
- }
14508
- };
14509
- try {
14510
- await visit(directory);
14511
- } catch (cause) {
14512
- if (cause instanceof Error && "code" in cause && cause.code === "ENOENT") throw new Error(`Missing markdown captures at ${directory}; build the app or use --dev <origin>.`);
14513
- throw cause;
14514
- }
14515
- const heads = decodeUnknownSync(fromJsonString(CaptureBundle.fields.heads))(await readFile(resolve(root, ".pagegraph/heads.json"), "utf8"), { onExcessProperty: "error" });
14516
- verifyDocuments(documents, heads);
14517
- return {
14518
- origin,
14519
- documents: documents.sort((a, b) => a.path.localeCompare(b.path)),
14520
- heads
14521
- };
14522
- }
14523
- /** The development endpoint supplies a complete capture from the running Start app. */
14524
- async function readDevMarkdownCapture(origin) {
14525
- const base = new URL(origin);
14526
- if (!/^https?:$/.test(base.protocol) || base.origin !== origin) throw new Error(`Invalid development server origin "${origin}"; use an http(s) origin.`);
14527
- const endpoint = new URL("/__pagegraph/markdown.json", base);
14528
- const response = await fetch(endpoint);
14529
- if (!response.ok) throw new Error(`GET ${endpoint} returned HTTP ${response.status}.`);
14530
- const bundle = decodeUnknownSync(fromJsonString(CaptureBundle))(await response.text(), { onExcessProperty: "error" });
14531
- const canonical = new URL(bundle.origin);
14532
- if (!/^https?:$/.test(canonical.protocol) || canonical.origin !== bundle.origin) throw new Error(`Invalid captured markdown origin "${bundle.origin}"`);
14533
- verifyDocuments(bundle.documents, bundle.heads);
14534
- return bundle;
14535
- }
14536
- //#endregion
14537
- //#region src/cli/output.ts
14538
- /**
14539
- * Shared surfaces for the `pagegraph` CLI. The three output planes:
14540
- * - **data** → stdout via {@link printJson} / {@link printText}. Under `--json`
14541
- * this is the *only* thing on stdout: no ANSI, no status, valid JSON.
14542
- * - **status** → stderr via Effect leveled logging (`Effect.logInfo` /
14543
- * `Effect.logDebug`), routed off stdout by `Logger.LogToStderr` in `main.ts`
14544
- * and gated by the built-in `--log-level` flag.
14545
- * - **diagnostics** → stderr; expected failures surface as {@link SeoCliError},
14546
- * printed by the entrypoint with a non-zero exit.
14547
- */
14548
- /** Expected, user-facing CLI failure — message to stderr, process exits non-zero. */
14549
- var SeoCliError = class extends TaggedError("SeoCliError") {};
14550
- /** Machine-readable output. When set, stdout is exactly the JSON payload. */
14551
- const jsonFlag = Boolean$1("json").pipe(withDescription$2("Emit the payload as JSON on stdout (no status, no color)"), withDefault(false));
14552
- /**
14553
- * Absolute origin the sitemap/robots projection is rendered under. It carries no
14554
- * static default: the fallback comes from the loaded graph's site, which is not
14555
- * known until the graph is acquired. Resolve it with {@link originOf}.
14556
- */
14557
- const originFlag = String$2("origin").pipe(withDescription$2("Absolute origin for URLs (default: origin from the loaded graph)"), optional$1);
14558
- /** The `--origin` flag when given, else the origin in the loaded graph's site identity. */
14559
- const originOf = (flag, configured) => getOrElse(flag, () => configured);
14560
- /**
14561
- * `--indexable` / `--no-indexable`. When omitted, the loaded graph's site identity
14562
- * decides; a non-indexable host yields a disallow-all robots.txt with no Sitemap line.
14563
- */
14564
- const indexableFlag = Boolean$1("indexable").pipe(withDescription$2("Render as an indexable host; --no-indexable = disallow-all robots.txt"), optional$1);
14565
- /** The `--indexable` flag when given, else the graph host's indexability. */
14566
- const indexableOf = (flag, configured) => getOrElse(flag, () => configured);
14567
- /** Data plane: pretty-printed JSON on stdout. */
14568
- const printJson = (value) => log(JSON.stringify(value, null, 2));
14569
- /** Data plane: a block of already-formatted text on stdout. */
14570
- const printText = (text) => log(text);
14571
- //#endregion
14572
- //#region node_modules/effect/dist/cli/Argument.js
14573
- /**
14574
- * Creates a positional string argument.
14575
- *
14576
- * **Example** (Creating a string argument)
14577
- *
14578
- * ```ts import.meta.vitest
14579
- * import { Argument } from "effect/cli"
14580
- *
14581
- * const filename = Argument.String("filename")
14582
- * filename.kind // => "argument"
14583
- * ```
14584
- *
14585
- * @stability unstable
14586
- * @category constructors
14587
- * @since 4.0.0
14588
- */
14589
- const String$1 = (name) => String$3(argumentKind, name);
14590
- /**
14591
- * Makes a positional argument optional.
14592
- *
14593
- * **Example** (Making an argument optional)
14594
- *
14595
- * ```ts import.meta.vitest
14596
- * import { Argument } from "effect/cli"
14597
- *
14598
- * const optionalVersion = Argument.String("version").pipe(Argument.optional)
14599
- * optionalVersion.kind // => "argument"
14600
- * ```
14601
- *
14602
- * @stability unstable
14603
- * @category combinators
14604
- * @since 4.0.0
14605
- */
14606
- const optional = (arg) => optional$2(arg);
14607
- /**
14608
- * Adds a description to a positional argument.
14609
- *
14610
- * **Example** (Adding an argument description)
14611
- *
14612
- * ```ts import.meta.vitest
14613
- * import { Argument } from "effect/cli"
14614
- *
14615
- * const filename = Argument.String("filename").pipe(
14616
- * Argument.withDescription("The input file to process")
14617
- * )
14618
- * filename.kind // => "argument"
14619
- * ```
14620
- *
14621
- * @stability unstable
14622
- * @category combinators
14623
- * @since 4.0.0
14624
- */
14625
- const withDescription = /*#__PURE__*/ dual(2, (self, description) => withDescription$3(self, description));
14626
- /**
14627
- * Creates a variadic positional argument that accepts multiple values.
14628
- *
14629
- * **Example** (Accepting multiple values)
14630
- *
14631
- * ```ts import.meta.vitest
14632
- * import { Argument } from "effect/cli"
14633
- *
14634
- * // Accept any number of files
14635
- * const anyFiles = Argument.String("files").pipe(Argument.variadic)
14636
- *
14637
- * // Accept at least 1 file
14638
- * const atLeastOneFile = Argument.String("files").pipe(
14639
- * Argument.variadic({ min: 1 })
14640
- * )
14641
- *
14642
- * // Accept between 1 and 5 files
14643
- * const limitedFiles = Argument.String("files").pipe(
14644
- * Argument.variadic({ min: 1, max: 5 })
14645
- * )
14646
- *
14647
- * const kinds = [anyFiles.kind, atLeastOneFile.kind, limitedFiles.kind] // => ["argument", "argument", "argument"]
14648
- * ```
14649
- *
14650
- * @stability unstable
14651
- * @category combinators
14652
- * @since 4.0.0
14653
- */
14654
- const variadic = /*#__PURE__*/ dual((args) => isParam(args[0]), (self, options) => variadic$1(self, options));
14893
+ paths.add(document.path);
14894
+ const { hash, ...content } = document;
14895
+ if (!/^[0-9a-f]{64}$/.test(hash) || hashDocument(content) !== hash) throw new Error(`Invalid captured document hash for "${document.path}"; build the app again.`);
14896
+ }
14897
+ const headPaths = /* @__PURE__ */ new Set();
14898
+ for (const head of heads) {
14899
+ verifyPath(head.path);
14900
+ if (paths.has(head.path)) throw new Error(`Duplicate captured page path "${head.path}"`);
14901
+ if (headPaths.has(head.path)) throw new Error(`Duplicate captured head path "${head.path}"`);
14902
+ headPaths.add(head.path);
14903
+ }
14904
+ if (documents.length === 0 && heads.length === 0) throw new Error("No captured markdown pages; build the app or use --dev <origin>.");
14905
+ }
14906
+ /** Read the persisted document tree and graph heads; malformed captures always fail. */
14907
+ async function readMarkdownCapture(root, origin) {
14908
+ const directory = resolve(root, ".pagegraph/documents");
14909
+ const documents = [];
14910
+ const visit = async (folder) => {
14911
+ for (const entry of await readdir(folder, { withFileTypes: true })) {
14912
+ const file = resolve(folder, entry.name);
14913
+ if (entry.isDirectory()) {
14914
+ await visit(file);
14915
+ continue;
14916
+ }
14917
+ if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
14918
+ const document = decodeUnknownSync(fromJsonString(PageDocument))(await readFile(file, "utf8"), { onExcessProperty: "error" });
14919
+ verifyPath(document.path);
14920
+ const name = document.path === "/" ? "index" : document.path.slice(1);
14921
+ if (file !== resolve(directory, `${name}.json`)) throw new Error(`Captured document "${document.path}" is stored at the wrong path: ${file}`);
14922
+ documents.push(document);
14923
+ }
14924
+ };
14925
+ try {
14926
+ await visit(directory);
14927
+ } catch (cause) {
14928
+ if (cause instanceof Error && "code" in cause && cause.code === "ENOENT") throw new Error(`Missing markdown captures at ${directory}; build the app or use --dev <origin>.`);
14929
+ throw cause;
14930
+ }
14931
+ const heads = decodeUnknownSync(fromJsonString(CaptureBundle.fields.heads))(await readFile(resolve(root, ".pagegraph/heads.json"), "utf8"), { onExcessProperty: "error" });
14932
+ verifyDocuments(documents, heads);
14933
+ return {
14934
+ origin,
14935
+ documents: documents.sort((a, b) => a.path.localeCompare(b.path)),
14936
+ heads
14937
+ };
14938
+ }
14939
+ /** The development endpoint supplies a complete capture from the running Start app. */
14940
+ async function readDevMarkdownCapture(origin) {
14941
+ const base = new URL(origin);
14942
+ if (!/^https?:$/.test(base.protocol) || base.origin !== origin) throw new Error(`Invalid development server origin "${origin}"; use an http(s) origin.`);
14943
+ const endpoint = new URL("/__pagegraph/markdown.json", base);
14944
+ const response = await fetch(endpoint);
14945
+ if (!response.ok) throw new Error(`GET ${endpoint} returned HTTP ${response.status}.`);
14946
+ const bundle = decodeUnknownSync(fromJsonString(CaptureBundle))(await response.text(), { onExcessProperty: "error" });
14947
+ const canonical = new URL(bundle.origin);
14948
+ if (!/^https?:$/.test(canonical.protocol) || canonical.origin !== bundle.origin) throw new Error(`Invalid captured markdown origin "${bundle.origin}"`);
14949
+ verifyDocuments(bundle.documents, bundle.heads);
14950
+ return bundle;
14951
+ }
14655
14952
  //#endregion
14656
14953
  //#region src/markdown/lock.ts
14657
14954
  const compare = (left, right) => left < right ? -1 : left > right ? 1 : 0;
@@ -14768,230 +15065,58 @@ const withCleanStdout = async (run) => {
14768
15065
  */
14769
15066
  const GRAPH_ENVIRONMENT = "pagegraph";
14770
15067
  const cliEnvironmentOverride = (command) => command === "build" ? { [GRAPH_ENVIRONMENT]: { isBundled: false } } : void 0;
14771
- /** Locate the config without overriding its authored root with an inline root. */
14772
- const appConfig = (options) => ({
14773
- configFile: options.configFile ?? [
14774
- "js",
14775
- "mjs",
14776
- "ts",
14777
- "cjs",
14778
- "mts",
14779
- "cts"
14780
- ].map((extension) => resolve(options.root, `vite.config.${extension}`)).find(existsSync) ?? false,
14781
- logLevel: "error",
14782
- plugins: [{
14783
- name: "pagegraph:loader-root",
14784
- enforce: "pre",
14785
- config(config) {
14786
- config.root ??= options.root;
14787
- }
14788
- }]
14789
- });
14790
- const pluginApi = (plugins, configFile) => {
14791
- const plugin = plugins.find((candidate) => candidate.name === "pagegraph");
14792
- if (plugin?.api === void 0) throw new Error(`${configFile ?? "The Vite config"} does not register pagegraph() from "pagegraph/tanstack-start".`);
14793
- return plugin.api;
14794
- };
14795
- /** Resolve settings, artifact root and facts together without route discovery. */
14796
- async function loadPagegraphSettings(options) {
14797
- const command = options.command ?? "build";
14798
- return withCleanStdout(async () => {
14799
- const config = await resolveConfig({
14800
- ...appConfig(options),
14801
- mode: options.mode ?? "production",
14802
- environments: cliEnvironmentOverride(command)
14803
- }, command);
14804
- const settings = pluginApi(config.plugins, config.configFile).options;
14805
- let facts = {};
14806
- if (settings.facts !== void 0) {
14807
- const environment = createRunnableDevEnvironment(GRAPH_ENVIRONMENT, config, { hot: false });
14808
- await environment.init();
14809
- try {
14810
- await environment.pluginContainer.buildStart({});
14811
- facts = decodeFacts((await environment.runner.import(resolve(config.root, settings.facts)))["facts"]);
14812
- } finally {
14813
- await environment.close();
14814
- }
14815
- }
14816
- return {
14817
- root: config.root,
14818
- options: settings,
14819
- facts
14820
- };
14821
- });
14822
- }
14823
- //#endregion
14824
- //#region src/cli/load-config.ts
14825
- /**
14826
- * Config discovery and graph acquisition for the `pagegraph` CLI.
14827
- *
14828
- * The CLI knows how to *view* a graph; the host knows how to *produce* one. That
14829
- * seam is a `pagegraph.config.ts` at the app root, found by walking up from the working
14830
- * directory — so `bun run pagegraph check` works from anywhere inside the app.
14831
- *
14832
- * The config is a TypeScript module the CLI imports directly, which is one of the
14833
- * reasons the `bin` runs under Bun (the other being the synchronous fd-1 flush in
14834
- * `main.ts`).
14835
- */
14836
- const CONFIG_FILENAMES = [
14837
- "pagegraph.config.ts",
14838
- "pagegraph.config.js",
14839
- "pagegraph.config.mjs"
14840
- ];
14841
- const LEGACY_CONFIG_FILENAMES = [
14842
- "seo.config.ts",
14843
- "seo.config.js",
14844
- "seo.config.mjs"
14845
- ];
14846
- const messageOf$2 = (cause) => cause instanceof Error ? cause.message : String(cause);
14847
- /** First `pagegraph.config.*` at or above `from`, or undefined at the filesystem root. */
14848
- const findConfigFile = (from) => {
14849
- let directory = resolve(from);
14850
- for (;;) {
14851
- for (const filename of CONFIG_FILENAMES) {
14852
- const candidate = join(directory, filename);
14853
- if (existsSync(candidate)) return candidate;
14854
- }
14855
- const parent = dirname(directory);
14856
- if (parent === directory) return void 0;
14857
- directory = parent;
14858
- }
14859
- };
14860
- /** Find an old config only to provide the explicit breaking-rename error. */
14861
- const findLegacyConfigFile = (from) => {
14862
- let directory = resolve(from);
14863
- for (;;) {
14864
- for (const filename of LEGACY_CONFIG_FILENAMES) {
14865
- const candidate = join(directory, filename);
14866
- if (existsSync(candidate)) return candidate;
14867
- }
14868
- const parent = dirname(directory);
14869
- if (parent === directory) return void 0;
14870
- directory = parent;
14871
- }
14872
- };
14873
- const configNotFoundMessage = (cwd) => {
14874
- if (findLegacyConfigFile(cwd) !== void 0) return "seo.config.ts was renamed to pagegraph.config.ts; rename the file";
14875
- return `No ${CONFIG_FILENAMES[0]} in ${cwd} or any parent directory. Create one that exports \`defineSeoConfig({ loadGraph })\` from "pagegraph/config".`;
14876
- };
14877
- const isStringArray = (value) => Array.isArray(value) && value.every(isString);
14878
- const isCoverageRules = (value) => Array.isArray(value) && value.every((rule) => isObject$1(rule) && isString(rule["path"]) && Number.isSafeInteger(rule["minInbound"]) && rule["minInbound"] > 0);
14879
- const isPositiveInteger = (value) => Number.isSafeInteger(value) && value > 0;
14880
- const isContentPolicy = (value) => isObject$1(value) && (value["minWords"] === void 0 || Array.isArray(value["minWords"]) && value["minWords"].every((rule) => isObject$1(rule) && isString(rule["path"]) && isPositiveInteger(rule["minWords"])));
14881
- const isFreshnessPolicy = (value) => isObject$1(value) && isPositiveInteger(value["maxAgeDays"]);
14882
- const isStringRecord = (value) => isObject$1(value) && Object.values(value).every(isString);
14883
- const isWorkflowTimeout = (value) => Number.isSafeInteger(value) && value > 0 && value <= 2147483647;
14884
- const isStringArrayRecord = (value) => isObject$1(value) && Object.values(value).every(isStringArray);
14885
- const isWorkflowConfig = (value) => {
14886
- if (!isObject$1(value) || !isObject$1(value["agent"])) return false;
14887
- const agent = value["agent"];
14888
- const context = value["context"];
14889
- return isString(agent["presetDirectory"]) && isString(agent["defaultModel"]) && (agent["models"] === void 0 || isStringRecord(agent["models"])) && (agent["timeoutMs"] === void 0 || isWorkflowTimeout(agent["timeoutMs"])) && (context === void 0 || isObject$1(context) && (context["files"] === void 0 || isStringArray(context["files"])) && (context["byWorkflow"] === void 0 || isStringArrayRecord(context["byWorkflow"]))) && (value["runsDirectory"] === void 0 || isString(value["runsDirectory"]));
14890
- };
14891
- const CLI_CONFIG_FIELDS = /* @__PURE__ */ new Set([
14892
- "loadGraph",
14893
- "transform",
14894
- "coverage",
14895
- "content",
14896
- "freshness",
14897
- "workflows"
14898
- ]);
14899
- /**
14900
- * `pagegraph.config.ts` is the consumer's file and may be plain JS, so its types are
14901
- * a suggestion, not a guarantee. Check every field the CLI consumes at this boundary.
14902
- */
14903
- const isSeoCliConfig = (value) => isObject$1(value) && Object.keys(value).every((key) => CLI_CONFIG_FIELDS.has(key)) && isFunction(value["loadGraph"]) && (value["transform"] === void 0 || isFunction(value["transform"])) && (value["coverage"] === void 0 || isCoverageRules(value["coverage"])) && (value["content"] === void 0 || isContentPolicy(value["content"])) && (value["freshness"] === void 0 || isFreshnessPolicy(value["freshness"])) && (value["workflows"] === void 0 || isWorkflowConfig(value["workflows"]));
14904
- /** Import and validate one config path. */
14905
- const loadConfigFile = (configPath) => gen(function* () {
14906
- yield* logDebug(`Loading SEO config from ${configPath}`);
14907
- const module = yield* tryPromise({
14908
- try: () => import(pathToFileURL(configPath).href),
14909
- catch: (cause) => new SeoCliError({ message: `Could not load ${configPath}: ${messageOf$2(cause)}` })
14910
- });
14911
- if (!isSeoCliConfig(module.default)) {
14912
- const duplicatedField = isObject$1(module.default) ? Object.keys(module.default).find((key) => !CLI_CONFIG_FIELDS.has(key)) : void 0;
14913
- return yield* new SeoCliError({ message: duplicatedField === void 0 ? `${configPath} must default-export defineSeoConfig({ loadGraph }).` : `${configPath} contains unsupported field "${duplicatedField}"; pagegraph.config.ts accepts only CLI fields, while site identity and plugin options belong to the graph loader and pagegraph() in vite.config.ts.` });
14914
- }
14915
- return module.default;
14916
- });
14917
- /**
14918
- * Load the app's `pagegraph.config.ts`. Cheap to run more than once per process: the
14919
- * ESM cache evaluates the config module exactly once.
14920
- */
14921
- const loadSeoConfig = gen(function* () {
14922
- const cwd = process.cwd();
14923
- const configPath = findConfigFile(cwd);
14924
- if (configPath === void 0) return yield* new SeoCliError({ message: configNotFoundMessage(cwd) });
14925
- return yield* loadConfigFile(configPath);
14926
- });
14927
- /** Load the config together with the project root that owns it. */
14928
- const loadSeoProjectConfig = gen(function* () {
14929
- const cwd = process.cwd();
14930
- const configPath = findConfigFile(cwd);
14931
- if (configPath === void 0) return yield* new SeoCliError({ message: configNotFoundMessage(cwd) });
14932
- return {
14933
- config: yield* loadConfigFile(configPath),
14934
- configPath,
14935
- root: dirname(configPath)
14936
- };
14937
- });
14938
- /**
14939
- * Like {@link loadSeoConfig}, but `undefined` when no config exists. For a
14940
- * framework-independent command that enriches its report when an app is present
14941
- * without requiring one. A config that exists but fails to load still fails —
14942
- * silence would hide a broken declaration.
14943
- */
14944
- const loadSeoConfigOptional = gen(function* () {
14945
- const configPath = findConfigFile(process.cwd());
14946
- if (configPath === void 0) {
14947
- if (findLegacyConfigFile(process.cwd()) !== void 0) return yield* new SeoCliError({ message: configNotFoundMessage(process.cwd()) });
14948
- return;
14949
- }
14950
- return yield* loadConfigFile(configPath);
14951
- });
14952
- /**
14953
- * The live SEO graph as a scoped resource: whatever the loader acquired (for
14954
- * {@link viteGraphLoader}, an in-process Vite server) is released when the
14955
- * surrounding `Effect.scoped` exits, on success or failure.
14956
- */
14957
- const acquireLoadedGraph = (config, environment) => gen(function* () {
14958
- yield* logDebug("Loading the SEO graph…");
14959
- const load = async () => {
14960
- const loaded = await config.loadGraph(void 0, environment);
14961
- if (!isLoadedSeoGraph(loaded)) {
14962
- let disposeFailure;
14963
- if (isObject$1(loaded) && isFunction(loaded["dispose"])) try {
14964
- await loaded["dispose"]();
14965
- } catch (cause) {
14966
- disposeFailure = cause;
14967
- }
14968
- const contractError = "The graph loader must return { graph, site: { origin, indexable, robots }, dispose }.";
14969
- throw new Error(disposeFailure === void 0 ? contractError : `${contractError} Releasing the invalid result also failed: ${messageOf$2(disposeFailure)}`);
15068
+ /** Locate the config without overriding its authored root with an inline root. */
15069
+ const appConfig = (options) => ({
15070
+ configFile: options.configFile ?? [
15071
+ "js",
15072
+ "mjs",
15073
+ "ts",
15074
+ "cjs",
15075
+ "mts",
15076
+ "cts"
15077
+ ].map((extension) => resolve(options.root, `vite.config.${extension}`)).find(existsSync) ?? false,
15078
+ logLevel: "error",
15079
+ plugins: [{
15080
+ name: "pagegraph:loader-root",
15081
+ enforce: "pre",
15082
+ config(config) {
15083
+ config.root ??= options.root;
14970
15084
  }
14971
- return loaded;
14972
- };
14973
- const loaded = yield* acquireRelease(tryPromise({
14974
- try: load,
14975
- catch: (cause) => new SeoCliError({ message: messageOf$2(cause) })
14976
- }), (acquired) => promise(() => acquired.dispose()).pipe(catchDefect((defect) => logWarning(`Could not dispose the SEO graph loader: ${messageOf$2(defect)}`))));
14977
- yield* logDebug(`Loaded SEO graph: ${loaded.graph.nodes.size} nodes, ${loaded.graph.edges.length} edges.`);
14978
- return loaded;
15085
+ }]
14979
15086
  });
14980
- const isLoadedSeoGraph = (value) => {
14981
- if (!isObject$1(value) || !isObject$1(value["graph"]) || !(value["graph"]["nodes"] instanceof Map) || !Array.isArray(value["graph"]["edges"]) || !isFunction(value["dispose"]) || !isObject$1(value["site"])) return false;
14982
- const site = value["site"];
14983
- const robots = site["robots"];
14984
- let canonicalOrigin = false;
14985
- if (isString(site["origin"])) try {
14986
- const url = new URL(site["origin"]);
14987
- canonicalOrigin = (url.protocol === "http:" || url.protocol === "https:") && url.origin === site["origin"];
14988
- } catch {
14989
- canonicalOrigin = false;
14990
- }
14991
- return canonicalOrigin && isBoolean$1(site["indexable"]) && isObject$1(robots) && isStringArray(robots["disallow"]) && (robots["contentSignal"] === void 0 || isString(robots["contentSignal"])) && (robots["directives"] === void 0 || isStringArray(robots["directives"]));
15087
+ const pluginApi = (plugins, configFile) => {
15088
+ const plugin = plugins.find((candidate) => candidate.name === "pagegraph");
15089
+ if (plugin?.api === void 0) throw new Error(`${configFile ?? "The Vite config"} does not register pagegraph() from "pagegraph/tanstack-start".`);
15090
+ return plugin.api;
14992
15091
  };
14993
- /** {@link acquireLoadedGraph}, for commands that need only the graph. */
14994
- const acquireGraph = (config) => map$3(acquireLoadedGraph(config), (loaded) => loaded.graph);
15092
+ /** Resolve settings, artifact root and facts together without route discovery. */
15093
+ async function loadPagegraphSettings(options) {
15094
+ const command = options.command ?? "build";
15095
+ return withCleanStdout(async () => {
15096
+ const config = await resolveConfig({
15097
+ ...appConfig(options),
15098
+ mode: options.mode ?? "production",
15099
+ environments: cliEnvironmentOverride(command)
15100
+ }, command);
15101
+ const settings = pluginApi(config.plugins, config.configFile).options;
15102
+ let facts = {};
15103
+ if (settings.facts !== void 0) {
15104
+ const environment = createRunnableDevEnvironment(GRAPH_ENVIRONMENT, config, { hot: false });
15105
+ await environment.init();
15106
+ try {
15107
+ await environment.pluginContainer.buildStart({});
15108
+ facts = decodeFacts((await environment.runner.import(resolve(config.root, settings.facts)))["facts"]);
15109
+ } finally {
15110
+ await environment.close();
15111
+ }
15112
+ }
15113
+ return {
15114
+ root: config.root,
15115
+ options: settings,
15116
+ facts
15117
+ };
15118
+ });
15119
+ }
14995
15120
  //#endregion
14996
15121
  //#region src/cli/commands/markdown.ts
14997
15122
  const devFlag = String$2("dev").pipe(optional$1, withDescription$2("Read captures from a running development-server origin"));
@@ -15179,7 +15304,7 @@ const checkCommand$1 = make$6("check", {
15179
15304
  const capture = yield* markdownCapture(root, markdown.origin, dev);
15180
15305
  const ask = suspend$1(() => {
15181
15306
  const id = claims.model.startsWith("typesafe/") ? claims.model.slice(9) : claims.model;
15182
- const layer = model(id === "jev" ? "jev-latest" : id).pipe(provide(layerConfig()), provide(layer$13));
15307
+ const layer = model$1(id === "jev" ? "jev-latest" : id).pipe(provide(layerConfig()), provide(layer$13));
15183
15308
  return runClaims(root, capture.documents, capture.heads, facts, claims, { refresh }).pipe(provide$1(layer));
15184
15309
  });
15185
15310
  const report = yield* (refresh ? ask : replayClaims(root, capture.documents, capture.heads, facts, claims).pipe(catchTag("ClaimsFailed", (error) => succeed$2(error.report)), catchTag("DecisionCacheMiss", () => ask))).pipe(mapError((error) => new SeoCliError({ message: String(error) })));
@@ -16923,6 +17048,57 @@ const crawlRenderedPages = async (seed, options) => {
16923
17048
  };
16924
17049
  };
16925
17050
  //#endregion
17051
+ //#region src/cli/og-images.ts
17052
+ const contained = (root, file) => {
17053
+ const path = relative(root, file);
17054
+ return path !== ".." && !path.startsWith(`..${sep}`) && !isAbsolute(path);
17055
+ };
17056
+ /** Verify static same-origin image assets without fetching external images. */
17057
+ async function checkLocalOgImages(graph, origin, projectRoot, policy) {
17058
+ const severity = policy?.severity ?? "structural";
17059
+ if (severity === "off") return [];
17060
+ const siteOrigin = new URL(origin).origin;
17061
+ const publicRoot = resolve(projectRoot, policy?.publicDirectory ?? "public");
17062
+ const findings = [];
17063
+ for (const node of graph.nodes.values()) {
17064
+ const image = node.head?.image;
17065
+ if (!isSitemapEligible(node) || !image?.url.trim()) continue;
17066
+ let url;
17067
+ try {
17068
+ url = new URL(image.url, origin);
17069
+ if (url.protocol !== "http:" && url.protocol !== "https:") throw new Error("Invalid image protocol");
17070
+ } catch {
17071
+ findings.push({
17072
+ severity,
17073
+ rule: "invalid-og-image",
17074
+ path: node.path,
17075
+ message: `Sitemap page "${node.path}" has an invalid OG image URL "${image.url}".`,
17076
+ fix: "Declare an HTTP(S) image URL or a local asset path."
17077
+ });
17078
+ continue;
17079
+ }
17080
+ if (url.origin !== siteOrigin) continue;
17081
+ let valid = false;
17082
+ try {
17083
+ const pathname = decodeURIComponent(url.pathname);
17084
+ if (pathname.includes("\0") || pathname.includes("\\")) throw new Error("Invalid asset path");
17085
+ const file = resolve(publicRoot, `.${pathname}`);
17086
+ if (contained(publicRoot, file)) {
17087
+ const [realRoot, realFile] = await Promise.all([realpath(publicRoot), realpath(file)]);
17088
+ valid = contained(realRoot, realFile) && (await stat(realFile)).isFile();
17089
+ }
17090
+ } catch {}
17091
+ if (!valid) findings.push({
17092
+ severity,
17093
+ rule: "missing-og-image-file",
17094
+ path: node.path,
17095
+ message: `Sitemap page "${node.path}" references OG image "${image.url}" without a file inside "${publicRoot}".`,
17096
+ fix: "Generate or add the image in the public asset directory, update its URL, or configure ogImage.publicDirectory."
17097
+ });
17098
+ }
17099
+ return findings;
17100
+ }
17101
+ //#endregion
16926
17102
  //#region src/cli/render.ts
16927
17103
  /** Count of edges pointing *at* each node — 0 means nothing links to it. */
16928
17104
  const incomingCounts = (graph) => {
@@ -17293,7 +17469,8 @@ const checkCommand = make$6("check", {
17293
17469
  description: "Also check headings, structured data, robots, and canonicals in rendered HTML"
17294
17470
  }
17295
17471
  ]), withHandler(fnUntraced(function* (flags) {
17296
- const config = yield* loadSeoConfig;
17472
+ const project = yield* loadSeoProjectConfig;
17473
+ const config = project.config;
17297
17474
  const flagRules = yield* forEach$1(flags.requireInbound, parseCoverageRule);
17298
17475
  const coverage = flagRules.length > 0 ? flagRules : config.coverage ?? [];
17299
17476
  const renderOptions = {
@@ -17304,11 +17481,16 @@ const checkCommand = make$6("check", {
17304
17481
  };
17305
17482
  const loaded = yield* scoped(acquireLoadedGraph(config));
17306
17483
  const graph = loaded.graph;
17307
- const violations = checkGraph(graph, config.freshness === void 0 ? { coverage } : {
17484
+ const violations = checkGraph(graph, config.freshness === void 0 ? {
17308
17485
  coverage,
17486
+ ogImage: config.ogImage
17487
+ } : {
17488
+ coverage,
17489
+ ogImage: config.ogImage,
17309
17490
  freshness: config.freshness,
17310
17491
  now: /* @__PURE__ */ new Date()
17311
17492
  });
17493
+ violations.push(...yield* promise(() => checkLocalOgImages(graph, loaded.site.origin, project.root, config.ogImage)));
17312
17494
  let rendered;
17313
17495
  if (isSome(flags.site)) {
17314
17496
  const documents = yield* renderSite(graph, flags.site.value, renderOptions);
@@ -19356,114 +19538,6 @@ const decodeLinksSuggestionReport = (value, origin) => {
19356
19538
  };
19357
19539
  };
19358
19540
  //#endregion
19359
- //#region src/workflows/prompts/summary.md
19360
- var summary_default = "# PageGraph {{workflow}}\n\n- Run: `{{runId}}`\n- Model: `{{model}}`\n- Pages: {{pages}}\n- Decisions: {{decisions}} ({{review}} review)\n- Executor searches: {{executorSearches}}\n- Executor calls: {{executorCalls}}\n- Changed files: {{changedFiles}}\n\n{{summary}}\n";
19361
- //#endregion
19362
- //#region src/workflows/template.ts
19363
- const placeholder = /\{\{([A-Za-z][A-Za-z0-9]*)\}\}/g;
19364
- /** A small immutable builder for checked text templates imported with `type: "text"`. */
19365
- var TextTemplate = class TextTemplate {
19366
- #source;
19367
- #values;
19368
- constructor(source, values) {
19369
- this.#source = source;
19370
- this.#values = values;
19371
- }
19372
- static from(source) {
19373
- return new TextTemplate(source, {});
19374
- }
19375
- value(name, value) {
19376
- return new TextTemplate(this.#source, {
19377
- ...this.#values,
19378
- [name]: String(value)
19379
- });
19380
- }
19381
- values(values) {
19382
- let template = this;
19383
- for (const [name, value] of Object.entries(values)) template = template.value(name, value);
19384
- return template;
19385
- }
19386
- render() {
19387
- const missing = /* @__PURE__ */ new Set();
19388
- const rendered = this.#source.replace(placeholder, (_match, name) => {
19389
- const value = this.#values[name];
19390
- if (value === void 0) {
19391
- missing.add(name);
19392
- return "";
19393
- }
19394
- return value;
19395
- });
19396
- if (missing.size > 0) throw new Error(`Missing template values: ${[...missing].sort().join(", ")}`);
19397
- return rendered;
19398
- }
19399
- };
19400
- //#endregion
19401
- //#region src/workflows/artifact.ts
19402
- const createRunId = (date = /* @__PURE__ */ new Date(), workflow = "workflow", nonce = randomUUID()) => `${date.toISOString().replaceAll(":", "-").replace(".", "-")}-${nonce.slice(0, 8)}-${workflow.replaceAll(".", "-")}`;
19403
- const resultSummary$1 = (result) => {
19404
- if (result !== null && typeof result === "object") {
19405
- const summary = result["summary"];
19406
- if (typeof summary === "string") return summary;
19407
- }
19408
- return "Workflow completed; inspect run.json for the structured result.";
19409
- };
19410
- const summary = (run) => {
19411
- const counts = run.decisions[0]?.report.counts;
19412
- return TextTemplate.from(summary_default).values({
19413
- workflow: run.workflow,
19414
- runId: run.id,
19415
- model: `${run.model.provider}/${run.model.id}`,
19416
- pages: run.targets.pages.length,
19417
- decisions: counts?.inputs ?? 0,
19418
- review: counts?.review ?? 0,
19419
- executorSearches: run.evidence.executor.searches.length,
19420
- executorCalls: run.evidence.executor.calls.length,
19421
- changedFiles: run.changes.files.length,
19422
- summary: resultSummary$1(run.result)
19423
- }).render();
19424
- };
19425
- const writeRunBundle = (root, runsDirectory, run) => {
19426
- const directory = resolve(root, runsDirectory, run.id);
19427
- mkdirSync(directory, { recursive: true });
19428
- writeFileSync(resolve(directory, "run.json"), `${JSON.stringify(run, null, 2)}\n`, "utf8");
19429
- writeFileSync(resolve(directory, "summary.md"), summary(run), "utf8");
19430
- return directory;
19431
- };
19432
- const writeResearchCheckpoint = (root, runsDirectory, checkpoint) => {
19433
- const directory = resolve(root, runsDirectory, checkpoint.id);
19434
- mkdirSync(directory, { recursive: true });
19435
- const path = resolve(directory, "research.json");
19436
- writeFileSync(path, `${JSON.stringify(checkpoint, null, 2)}\n`, "utf8");
19437
- return path;
19438
- };
19439
- const writeWorkflowFailure = (root, runsDirectory, id, details) => {
19440
- const directory = resolve(root, runsDirectory, id);
19441
- mkdirSync(directory, { recursive: true });
19442
- const path = resolve(directory, "failure.json");
19443
- writeFileSync(path, `${JSON.stringify(details, null, 2)}\n`, "utf8");
19444
- return path;
19445
- };
19446
- const serializeCause = (value, top = true, seen = /* @__PURE__ */ new Set()) => {
19447
- if (seen.has(value)) return {
19448
- name: "CircularCause",
19449
- message: "Circular error cause"
19450
- };
19451
- if (value !== null && typeof value === "object") {
19452
- seen.add(value);
19453
- return {
19454
- message: value instanceof Error ? value.message : "message" in value ? String(value.message) : JSON.stringify(value),
19455
- name: value instanceof Error ? value.name : "name" in value ? String(value.name) : "Error",
19456
- ...top && value instanceof Error ? { stack: value.stack } : {},
19457
- ..."cause" in value ? { cause: serializeCause(value.cause, false, seen) } : {},
19458
- ...value instanceof AggregateError ? { errors: value.errors.map((error) => serializeCause(error, false, new Set(seen))) } : {}
19459
- };
19460
- }
19461
- return {
19462
- name: "Error",
19463
- message: String(value)
19464
- };
19465
- };
19466
- //#endregion
19467
19541
  //#region src/workflows/specs/types.ts
19468
19542
  const defineWorkflow = (definition) => {
19469
19543
  const decodeState = decodeUnknownSync(definition.stateSchema, { errors: "all" });
@@ -19913,7 +19987,7 @@ const improveContentWorkflow = defineWorkflow({
19913
19987
  decisionInputs: (state) => state.items,
19914
19988
  family: contentFamily,
19915
19989
  researchInstructions: "Inspect the target page and its supporting research before proposing a bounded content improvement.",
19916
- actionInstructions: "Apply only evidence-backed copy edits that preserve the page's category, intent, and public product claims; leave the diff uncommitted."
19990
+ actionInstructions: "Submit edits only for evidence-backed copy changes that preserve the page's category, intent, and public product claims."
19917
19991
  });
19918
19992
  //#endregion
19919
19993
  //#region src/workflows/specs/keywords.ts
@@ -20029,7 +20103,7 @@ const linksWorkflow = defineWorkflow({
20029
20103
  evaluate: (_input, answers, threshold) => classify$1(answers, threshold)
20030
20104
  },
20031
20105
  researchInstructions: "Use the deterministic PageGraph edges and source content to propose useful contextual internal links and safe anchor text.",
20032
- actionInstructions: "Apply only useful, supported links at the owning source or route declaration; preserve generated graph contracts and leave the diff uncommitted."
20106
+ actionInstructions: "Submit edits only for useful, supported links at the owning source or route declaration, and preserve generated graph contracts."
20033
20107
  });
20034
20108
  //#endregion
20035
20109
  //#region src/decide/families/meta.ts
@@ -20134,7 +20208,7 @@ const metadataWorkflow = defineWorkflow({
20134
20208
  decisionInputs: (state) => state.items,
20135
20209
  family: metaFamily,
20136
20210
  researchInstructions: "Inspect route intent and current head metadata using repository and live provider evidence. For every proposed improvement, draft at least two distinct title/description candidates with unique ids so the decision step can rank them. Retain the current metadata as a candidate when it remains truthful. Return an empty items array when evidence supports no improvement.",
20137
- actionInstructions: "Apply the selected metadata candidate to the owning route or Fumadocs source, preserve truthful claims, and leave the diff uncommitted."
20211
+ actionInstructions: "Submit an edit applying the selected metadata candidate at the owning route or Fumadocs source, and preserve truthful claims."
20138
20212
  });
20139
20213
  //#endregion
20140
20214
  //#region src/decide/families/serp.ts
@@ -20295,7 +20369,7 @@ const schemaWorkflow = defineWorkflow({
20295
20369
  evaluate: (_input, answers, threshold) => classify(answers, threshold)
20296
20370
  },
20297
20371
  researchInstructions: "Inspect rendered head output and the route or Fumadocs source to derive only structured-data types justified by visible content.",
20298
- actionInstructions: "Apply justified JSON-LD changes at the owning source, preserve the existing composition contracts, and leave the diff uncommitted."
20372
+ actionInstructions: "Submit edits for justified JSON-LD changes at the owning source and preserve the existing composition contracts."
20299
20373
  });
20300
20374
  //#endregion
20301
20375
  //#region src/workflows/catalog.ts
@@ -20393,24 +20467,24 @@ const selectNeighborhood = (graph, targetPaths) => {
20393
20467
  outbound
20394
20468
  };
20395
20469
  };
20396
- const collectContextFiles = (root, workflow, context) => {
20470
+ const collectContextFiles = async (root, workflow, context, readOptions) => {
20397
20471
  const paths = [...context?.files ?? [], ...context?.byWorkflow?.[workflow] ?? []];
20398
- return [...new Set(paths)].map((path) => ({
20472
+ return Promise.all([...new Set(paths)].map(async (path) => ({
20399
20473
  path,
20400
- content: readFileSync(resolve(root, path), "utf8")
20401
- }));
20474
+ content: await readFile(await resolveRepositoryReadPath(root, path, readOptions), "utf8")
20475
+ })));
20402
20476
  };
20403
- const collectWorkflowEvidence = (graph, options, root, workflow, context) => {
20477
+ const collectWorkflowEvidence = async (graph, options, root, workflow, context, readOptions) => {
20404
20478
  const selected = selectGraph(graph, options);
20405
20479
  return {
20406
20480
  graph: serializeGraph(selected),
20407
20481
  neighborhood: selectNeighborhood(graph, new Set(selected.nodes.keys())),
20408
- sources: collectContextFiles(root, workflow, context)
20482
+ sources: await collectContextFiles(root, workflow, context, readOptions)
20409
20483
  };
20410
20484
  };
20411
20485
  //#endregion
20412
20486
  //#region src/workflows/git.ts
20413
- const run$1 = (cwd, args) => execFileSync("git", args, {
20487
+ const run$2 = (cwd, args) => execFileSync("git", args, {
20414
20488
  cwd,
20415
20489
  encoding: "utf8",
20416
20490
  stdio: [
@@ -20423,7 +20497,7 @@ const nul = (value) => value.split("\0").filter((item) => item.length > 0);
20423
20497
  const fingerprint = (root, gitRoot, path) => {
20424
20498
  const absolute = resolve(root, path);
20425
20499
  const gitPath = relative(gitRoot, absolute);
20426
- const status = run$1(gitRoot, [
20500
+ const status = run$2(gitRoot, [
20427
20501
  "status",
20428
20502
  "--short",
20429
20503
  "--",
@@ -20432,14 +20506,14 @@ const fingerprint = (root, gitRoot, path) => {
20432
20506
  let content;
20433
20507
  if (!existsSync(absolute)) content = Buffer.from("<deleted>");
20434
20508
  else if (statSync(absolute).isDirectory()) {
20435
- const stage = run$1(gitRoot, [
20509
+ const stage = run$2(gitRoot, [
20436
20510
  "ls-files",
20437
20511
  "--stage",
20438
20512
  "--",
20439
20513
  gitPath
20440
20514
  ]);
20441
20515
  if (stage.split(/\s+/, 1)[0] !== "160000") throw new Error(`Cannot fingerprint directory reported as a Git file: ${path}`);
20442
- const submoduleRoot = realpathSync(run$1(absolute, ["rev-parse", "--show-toplevel"]));
20516
+ const submoduleRoot = realpathSync(run$2(absolute, ["rev-parse", "--show-toplevel"]));
20443
20517
  if (submoduleRoot !== realpathSync(absolute)) throw new Error(`Git submodule is unavailable: ${path}`);
20444
20518
  content = Buffer.from(`gitlink:${stage}\0${JSON.stringify(inspectGit(submoduleRoot))}`);
20445
20519
  } else content = readFileSync(absolute);
@@ -20449,7 +20523,7 @@ const inspectGit = (root) => {
20449
20523
  const absoluteRoot = realpathSync(root);
20450
20524
  let gitTopLevel;
20451
20525
  try {
20452
- gitTopLevel = run$1(absoluteRoot, ["rev-parse", "--show-toplevel"]);
20526
+ gitTopLevel = run$2(absoluteRoot, ["rev-parse", "--show-toplevel"]);
20453
20527
  } catch {
20454
20528
  return {
20455
20529
  dirty: false,
@@ -20458,13 +20532,13 @@ const inspectGit = (root) => {
20458
20532
  };
20459
20533
  }
20460
20534
  const gitRoot = realpathSync(gitTopLevel);
20461
- const tracked = nul(run$1(gitRoot, [
20535
+ const tracked = nul(run$2(gitRoot, [
20462
20536
  "diff",
20463
20537
  "--name-only",
20464
20538
  "-z",
20465
20539
  "HEAD"
20466
20540
  ]));
20467
- const untracked = nul(run$1(gitRoot, [
20541
+ const untracked = nul(run$2(gitRoot, [
20468
20542
  "ls-files",
20469
20543
  "--others",
20470
20544
  "--exclude-standard",
@@ -20473,7 +20547,7 @@ const inspectGit = (root) => {
20473
20547
  ]));
20474
20548
  const files = [.../* @__PURE__ */ new Set([...tracked, ...untracked])].map((path) => relative(absoluteRoot, resolve(gitRoot, path))).sort();
20475
20549
  return {
20476
- head: run$1(gitRoot, ["rev-parse", "HEAD"]),
20550
+ head: run$2(gitRoot, ["rev-parse", "HEAD"]),
20477
20551
  dirty: files.length > 0,
20478
20552
  files,
20479
20553
  fingerprints: Object.fromEntries(files.map((path) => [path, fingerprint(absoluteRoot, gitRoot, path)]))
@@ -20496,11 +20570,514 @@ const createWorkflowMutationPolicy = (options) => {
20496
20570
  };
20497
20571
  //#endregion
20498
20572
  //#region src/workflows/prompts/action.md
20499
- var action_default = "Continue PageGraph's {{workflow}} workflow.\n\n{{instructions}}\n\nObserved state:\n{{state}}\n\nJev decisions:\n{{decisions}}\n\nApply only records in the resolved set whose verdict calls for a change. Never apply review records;\nleave them in the artifact for a human. If nothing is safely resolved, return no-change.\n\nRepository mutation mode: {{mutationMode}}. {{mutationInstruction}}\n\nCall submit_result with the final action result matching this shape:\n{\"summary\":\"...\",\"files\":[\"relative/path\"],\"outcome\":\"applied|dry-run|no-change\"}\n";
20573
+ var action_default = "Continue PageGraph's {{workflow}} workflow.\n\n{{instructions}}\n\nObserved state:\n{{state}}\n\nJev decisions:\n{{decisions}}\n\nAct only on records in the resolved set whose verdict calls for a change. Never act on review\nrecords; PageGraph lists them for a human in review.md.\n\nYou cannot write files. Express every source change as one entry in `edits`:\n- `path`: the file as `read_file` names it, inside the app root `{{appRoot}}`.\n- `oldText`: text copied exactly from the current file, with enough context to match exactly once.\n Omit it only to create a new file.\n- `newText`: the replacement text.\n- `reason` and `evidence`: why the change is justified, citing repository and provider evidence.\n\nUse `reviewItems` (`path`, `reason`, `evidence`) for recommendations you cannot or should not edit,\nsuch as generated output, unclear source ownership, or a claim the evidence does not settle. Submit\nempty `edits` when nothing is safely resolved.\n\nRepository mutation mode: {{mutationMode}}. {{mutationInstruction}}\n\nCall submit_result with the final action result matching this shape:\n{\"summary\":\"...\",\"edits\":[{\"path\":\"...\",\"oldText\":\"...\",\"newText\":\"...\",\"reason\":\"...\",\"evidence\":[\"...\"]}],\"reviewItems\":[{\"path\":\"...\",\"reason\":\"...\",\"evidence\":[\"...\"]}]}\n";
20500
20574
  //#endregion
20501
20575
  //#region src/workflows/prompts/research.md
20502
20576
  var research_default = "You are running PageGraph's {{workflow}} workflow.\n\n{{instructions}}\n\nUse these skills: {{skills}}. Treat the supplied project evidence as the deterministic baseline.\nThe graph contains the selected target pages. Its neighborhood preserves incoming and outgoing\nrelationships to pages outside that selection; those neighbors are context, not additional targets.\nWhen suggestion evidence is supplied, preserve each candidate's exact source sentence as `context`,\nverbatim anchor, source and destination paths. Do not invent additional link placements.\nReturn at most {{limit}} items or opportunities. Keep exploration scoped to these targets and stop\nwhen the collected evidence supports the requested decision; do not expand into a site-wide audit.\n\nUse executor_search with a short intent phrase to discover relevant tools and inspect their\nTypeScript input shapes. Never guess arguments from a description. If discovery returns no\nmatches, simplify the query. Use executor_execute with a TypeScript snippet that calls\n`tools.<namespace>.<tool>(input)` and includes a top-level `return` for the result. Compose\nwhichever current integrations best support this workflow; tools and providers are discovered\ndynamically. Repository research tools are read_file, list_files, and search_text.\nCall at least one relevant discovered tool and preserve source references and provider errors in the\nstructured state. Do not treat an errored call or empty dataset as proof of indexing or traffic.\nDistinguish project market settings from the country, language, device, and date filters actually\napplied to provider data; report only the segments supported by the call and its response.\n\n{{resultInstruction}} matching this JSON Schema:\n{{stateSchema}}\n\nMarket: {{market}}\nLanguage: {{language}}\nDevice: {{device}}\nRefresh requested: {{refresh}}\nDo not ask questions or create interactive forms. Fail clearly if required\ninput is missing.\nSeed queries: {{queries}}\nCompetitor seeds: {{competitors}}\nAuthority-domain seeds: {{domains}}\nDeterministic evidence:\n{{evidence}}\n";
20503
20577
  //#endregion
20578
+ //#region src/workflows/resume-codecs.ts
20579
+ const array = (schema) => mutable(ArraySchema(schema));
20580
+ const strings$1 = array(String$6);
20581
+ const optionalString$1 = optional$3(String$6);
20582
+ const model = Struct({
20583
+ provider: String$6,
20584
+ id: String$6
20585
+ });
20586
+ const usage = Struct({
20587
+ input: Number$3,
20588
+ output: Number$3,
20589
+ cacheRead: Number$3,
20590
+ cacheWrite: Number$3,
20591
+ cacheWrite1h: optional$3(Number$3),
20592
+ reasoning: optional$3(Number$3),
20593
+ totalTokens: Number$3,
20594
+ cost: Struct({
20595
+ input: Number$3,
20596
+ output: Number$3,
20597
+ cacheRead: Number$3,
20598
+ cacheWrite: Number$3,
20599
+ total: Number$3
20600
+ })
20601
+ });
20602
+ const text = Struct({
20603
+ type: Literal("text"),
20604
+ text: String$6,
20605
+ textSignature: optionalString$1
20606
+ });
20607
+ const image = Struct({
20608
+ type: Literal("image"),
20609
+ data: String$6,
20610
+ mimeType: String$6
20611
+ });
20612
+ const thinking = Struct({
20613
+ type: Literal("thinking"),
20614
+ thinking: String$6,
20615
+ thinkingSignature: optionalString$1,
20616
+ redacted: optional$3(Boolean$4)
20617
+ });
20618
+ const toolCall = Struct({
20619
+ type: Literal("toolCall"),
20620
+ id: String$6,
20621
+ name: String$6,
20622
+ arguments: JsonObject,
20623
+ thoughtSignature: optionalString$1,
20624
+ namespace: optionalString$1
20625
+ });
20626
+ const diagnostic = Struct({
20627
+ type: String$6,
20628
+ timestamp: Number$3,
20629
+ details: optional$3(JsonObject),
20630
+ error: optional$3(Struct({
20631
+ name: optionalString$1,
20632
+ message: String$6,
20633
+ stack: optionalString$1,
20634
+ code: optional$3(Union([String$6, Number$3]))
20635
+ }))
20636
+ });
20637
+ const deferred = Struct({
20638
+ provider: String$6,
20639
+ modelId: String$6,
20640
+ api: String$6,
20641
+ id: String$6,
20642
+ expiresAt: optional$3(Number$3),
20643
+ pollAfterMs: optional$3(Number$3),
20644
+ data: optional$3(Json)
20645
+ });
20646
+ const tool = Struct({
20647
+ name: String$6,
20648
+ description: String$6,
20649
+ parameters: JsonObject,
20650
+ constrainedSampling: optional$3(Union([
20651
+ Literal(false),
20652
+ Struct({
20653
+ type: Literal("json_schema"),
20654
+ strict: Literals$2(["prefer", "require"])
20655
+ }),
20656
+ Struct({
20657
+ type: Literal("grammar"),
20658
+ variants: Struct({
20659
+ openai_lark: optionalString$1,
20660
+ openai_regex: optionalString$1
20661
+ })
20662
+ })
20663
+ ]))
20664
+ });
20665
+ const message = Union([
20666
+ Struct({
20667
+ role: Literal("system"),
20668
+ content: Union([String$6, array(text)]),
20669
+ timestamp: Number$3,
20670
+ sections: optional$3(Record(String$6, Union([String$6, Null]))),
20671
+ toolsAdded: optional$3(array(tool)),
20672
+ toolsRemoved: optional$3(array(Struct({ name: String$6 })))
20673
+ }),
20674
+ Struct({
20675
+ role: Literal("user"),
20676
+ content: Union([String$6, array(Union([text, image]))]),
20677
+ timestamp: Number$3
20678
+ }),
20679
+ Struct({
20680
+ role: Literal("assistant"),
20681
+ content: array(Union([
20682
+ text,
20683
+ thinking,
20684
+ toolCall
20685
+ ])),
20686
+ api: String$6,
20687
+ provider: String$6,
20688
+ model: String$6,
20689
+ responseModel: optionalString$1,
20690
+ responseId: optionalString$1,
20691
+ providerThinkingLevel: optionalString$1,
20692
+ thinkingLevel: optional$3(Literals$2([
20693
+ "off",
20694
+ "minimal",
20695
+ "low",
20696
+ "medium",
20697
+ "high",
20698
+ "xhigh",
20699
+ "max"
20700
+ ])),
20701
+ diagnostics: optional$3(array(diagnostic)),
20702
+ usage,
20703
+ stopReason: Literals$2([
20704
+ "pending",
20705
+ "stop",
20706
+ "length",
20707
+ "toolUse",
20708
+ "error",
20709
+ "aborted",
20710
+ "deferred"
20711
+ ]),
20712
+ deferred: optional$3(deferred),
20713
+ errorMessage: optionalString$1,
20714
+ rawStopReason: optionalString$1,
20715
+ endTurn: optional$3(Boolean$4),
20716
+ timestamp: Number$3
20717
+ }),
20718
+ Struct({
20719
+ role: Literal("toolResult"),
20720
+ toolCallId: String$6,
20721
+ toolName: String$6,
20722
+ content: array(Union([text, image])),
20723
+ details: optional$3(Json),
20724
+ usage: optional$3(usage),
20725
+ isError: Boolean$4,
20726
+ timestamp: Number$3,
20727
+ nestedCalls: optional$3(Struct({
20728
+ complete: Boolean$4,
20729
+ calls: array(Struct({
20730
+ id: String$6,
20731
+ name: String$6,
20732
+ arguments: optional$3(JsonObject),
20733
+ argumentsBytes: optional$3(Number$3),
20734
+ status: Literals$2([
20735
+ "ok",
20736
+ "error",
20737
+ "unfinished"
20738
+ ]),
20739
+ durationMs: optional$3(Number$3),
20740
+ error: optionalString$1
20741
+ }))
20742
+ }))
20743
+ })
20744
+ ]);
20745
+ const agent = Struct({
20746
+ runtime: Literal("pi"),
20747
+ model,
20748
+ messages: array(message),
20749
+ usage
20750
+ });
20751
+ const rejectedItems = optional$3(array(Struct({
20752
+ index: Int$4,
20753
+ reason: String$6
20754
+ })));
20755
+ const executorRecord = Struct({
20756
+ tool: String$6,
20757
+ input: Unknown,
20758
+ output: Unknown
20759
+ });
20760
+ const executor = Struct({
20761
+ searches: array(executorRecord),
20762
+ calls: array(executorRecord)
20763
+ });
20764
+ const edge = Struct({
20765
+ from: String$6,
20766
+ to: String$6,
20767
+ type: Literals$2([
20768
+ "crumb-parent",
20769
+ "related",
20770
+ "redirect",
20771
+ "collection-member"
20772
+ ])
20773
+ });
20774
+ const node = Struct({
20775
+ path: String$6,
20776
+ kind: String$6,
20777
+ source: String$6,
20778
+ policy: Struct({
20779
+ kind: String$6,
20780
+ crumb: optionalString$1,
20781
+ robots: optionalString$1,
20782
+ related: optional$3(strings$1),
20783
+ redirectTo: optionalString$1,
20784
+ link: optional$3(Struct({
20785
+ title: String$6,
20786
+ description: String$6
20787
+ })),
20788
+ sitemap: optional$3(Union([Literal(false), Struct({
20789
+ priority: Number$3,
20790
+ changeFrequency: Literals$2(["weekly", "monthly"])
20791
+ })]))
20792
+ }),
20793
+ instance: optional$3(Struct({
20794
+ title: String$6,
20795
+ description: optionalString$1,
20796
+ publishedAt: optionalString$1,
20797
+ modifiedAt: optionalString$1
20798
+ }))
20799
+ });
20800
+ const neighbor = Struct({
20801
+ edge,
20802
+ node
20803
+ });
20804
+ const suggestions = Struct({
20805
+ kind: Literal("links-candidates"),
20806
+ schemaVersion: Literal(2),
20807
+ origin: String$6,
20808
+ limit: Number$3,
20809
+ pageLimit: Number$3,
20810
+ maxBodyBytes: Number$3,
20811
+ total: Number$3,
20812
+ truncated: Boolean$4,
20813
+ skipped: array(Struct({
20814
+ path: String$6,
20815
+ reason: String$6
20816
+ })),
20817
+ candidates: array(Struct({
20818
+ source: String$6,
20819
+ destination: String$6,
20820
+ cluster: String$6,
20821
+ reason: String$6,
20822
+ sentence: String$6,
20823
+ anchor: String$6,
20824
+ targetSentence: String$6,
20825
+ score: Number$3,
20826
+ scores: Struct({
20827
+ topical: Number$3,
20828
+ rarity: Number$3,
20829
+ inboundNeed: Number$3,
20830
+ graph: Number$3
20831
+ })
20832
+ }))
20833
+ });
20834
+ const evidence = Struct({
20835
+ graph: Struct({
20836
+ nodes: array(node),
20837
+ edges: array(edge)
20838
+ }),
20839
+ neighborhood: Struct({
20840
+ inbound: array(neighbor),
20841
+ outbound: array(neighbor)
20842
+ }),
20843
+ sources: array(Struct({
20844
+ path: String$6,
20845
+ content: String$6
20846
+ })),
20847
+ suggestions: optional$3(suggestions),
20848
+ executor
20849
+ });
20850
+ const options = Struct({
20851
+ pages: strings$1,
20852
+ queries: strings$1,
20853
+ kinds: strings$1,
20854
+ competitors: strings$1,
20855
+ domains: strings$1,
20856
+ limit: Int$4,
20857
+ market: optionalString$1,
20858
+ language: optionalString$1,
20859
+ device: optional$3(Literals$2(["desktop", "mobile"])),
20860
+ refresh: Boolean$4,
20861
+ dryRun: Boolean$4,
20862
+ allowDirty: Boolean$4,
20863
+ allowPrivate: optional$3(Boolean$4),
20864
+ suggestions: optionalString$1
20865
+ });
20866
+ const project = Struct({
20867
+ root: String$6,
20868
+ head: optionalString$1,
20869
+ dirtyAtStart: Boolean$4,
20870
+ filesAtStart: strings$1
20871
+ });
20872
+ const git = Struct({
20873
+ head: optionalString$1,
20874
+ dirty: Boolean$4,
20875
+ files: strings$1,
20876
+ fingerprints: Record(String$6, String$6)
20877
+ });
20878
+ const workflow = Literals$2([
20879
+ "research.keywords",
20880
+ "research.competitors",
20881
+ "research.authority",
20882
+ "analyze.serp",
20883
+ "analyze.content",
20884
+ "analyze.ai-search",
20885
+ "plan.architecture",
20886
+ "improve.content",
20887
+ "improve.metadata",
20888
+ "improve.schema",
20889
+ "improve.links"
20890
+ ]);
20891
+ const common = {
20892
+ schemaVersion: Literal(2),
20893
+ id: String$6,
20894
+ workflow
20895
+ };
20896
+ const decisions = Struct({
20897
+ kind: Literal("decide"),
20898
+ schemaVersion: Literal(1),
20899
+ family: String$6,
20900
+ model: String$6,
20901
+ threshold: Number$3,
20902
+ counts: Struct({
20903
+ inputs: Number$3,
20904
+ resolved: Number$3,
20905
+ review: Number$3
20906
+ }),
20907
+ verdicts: Record(String$6, Number$3),
20908
+ resolved: array(DecisionRecord),
20909
+ review: array(DecisionRecord)
20910
+ });
20911
+ const research = Struct({
20912
+ ...common,
20913
+ kind: Literal("pagegraph-workflow-research-checkpoint"),
20914
+ startedAt: String$6,
20915
+ checkpointedAt: String$6,
20916
+ project,
20917
+ options,
20918
+ evidence,
20919
+ state: Unknown,
20920
+ decisionInputs: array(Unknown),
20921
+ agent,
20922
+ rejectedItems
20923
+ });
20924
+ const result = Struct({
20925
+ state: Unknown,
20926
+ messages: array(message),
20927
+ usage,
20928
+ executor,
20929
+ rejectedItems
20930
+ });
20931
+ const progress = Struct({
20932
+ ...common,
20933
+ kind: Literal("pagegraph-workflow-progress"),
20934
+ startedAt: String$6,
20935
+ project,
20936
+ options,
20937
+ evidence,
20938
+ repositoryRoot: String$6,
20939
+ git,
20940
+ research: optional$3(research),
20941
+ decisions: optional$3(decisions),
20942
+ actionStarted: optional$3(Boolean$4),
20943
+ action: optional$3(result),
20944
+ actionGit: optional$3(git),
20945
+ actionSources: optional$3(array(Struct({
20946
+ path: String$6,
20947
+ content: String$6
20948
+ })))
20949
+ });
20950
+ const failure = Struct({
20951
+ ...common,
20952
+ kind: Literal("pagegraph-workflow-failure"),
20953
+ stage: String$6,
20954
+ agent: optional$3(agent),
20955
+ executor: optional$3(executor),
20956
+ cause: Unknown
20957
+ });
20958
+ const run$1 = Struct({
20959
+ ...common,
20960
+ kind: Literal("pagegraph-workflow-run"),
20961
+ startedAt: String$6,
20962
+ finishedAt: String$6,
20963
+ project: Struct({
20964
+ root: String$6,
20965
+ head: optionalString$1,
20966
+ dirtyAtStart: Boolean$4
20967
+ }),
20968
+ options,
20969
+ model,
20970
+ targets: Struct({
20971
+ pages: strings$1,
20972
+ queries: strings$1,
20973
+ kinds: strings$1
20974
+ }),
20975
+ evidence,
20976
+ decisions: array(Struct({
20977
+ family: String$6,
20978
+ questions: array(Struct({
20979
+ inputRef: String$6,
20980
+ decisions: Record(String$6, Unknown)
20981
+ })),
20982
+ report: decisions
20983
+ })),
20984
+ changes: Struct({
20985
+ files: strings$1,
20986
+ providerCalls: strings$1
20987
+ }),
20988
+ result: Unknown,
20989
+ agent,
20990
+ rejectedItems
20991
+ });
20992
+ const decodeWorkflowProgress = decodeUnknownSync(progress);
20993
+ const decodeWorkflowFailure = decodeUnknownSync(failure);
20994
+ const decodeWorkflowRun = decodeUnknownSync(run$1);
20995
+ //#endregion
20996
+ //#region src/workflows/resume.ts
20997
+ const readArtifact = (directory, filename, decode, workflow) => {
20998
+ const path = resolve(directory, filename);
20999
+ if (!existsSync(path)) return void 0;
21000
+ let value;
21001
+ try {
21002
+ value = decode(JSON.parse(readFileSync(path, "utf8")));
21003
+ } catch (cause) {
21004
+ throw new Error(`Cannot resume ${path}: invalid or unsupported schema-version-2 artifact. ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
21005
+ }
21006
+ if (value.workflow !== workflow || value.id !== basename(directory)) throw new Error(`Cannot resume ${path}: workflow or run ID differs from the requested ${workflow} run directory.`);
21007
+ return value;
21008
+ };
21009
+ const assertProjectRoot = (root, recordedRoot, directory) => {
21010
+ if (realpathSync(root) !== realpathSync(recordedRoot)) throw new Error(`Cannot resume ${directory}: the recorded project root differs from this config directory.`);
21011
+ };
21012
+ const inspectWorkflowGit = (root, runDirectory) => {
21013
+ const git = inspectGit(root);
21014
+ const canonicalRoot = realpathSync(root);
21015
+ let existing = runDirectory;
21016
+ while (!existsSync(existing)) existing = dirname(existing);
21017
+ const canonicalRun = resolve(realpathSync(existing), relative(existing, runDirectory));
21018
+ const files = git.files.filter((path) => {
21019
+ const within = relative(canonicalRun, resolve(canonicalRoot, path));
21020
+ return within === ".." || within.startsWith(`..${sep}`) || isAbsolute(within);
21021
+ });
21022
+ return {
21023
+ ...git,
21024
+ files,
21025
+ dirty: files.length > 0,
21026
+ fingerprints: Object.fromEntries(files.map((path) => [path, git.fingerprints[path]]))
21027
+ };
21028
+ };
21029
+ const resumeDirectory = (root, runsDirectory, from) => isAbsolute(from) || from.includes("/") || from.includes("\\") ? resolve(root, from) : resolve(root, runsDirectory, from);
21030
+ const loadCompletedWorkflowRun = (root, runsDirectory, from, workflow) => {
21031
+ const directory = resumeDirectory(root, runsDirectory, from);
21032
+ const run = readArtifact(directory, "run.json", decodeWorkflowRun, workflow);
21033
+ if (run === void 0) return void 0;
21034
+ assertProjectRoot(root, run.project.root, directory);
21035
+ return {
21036
+ directory,
21037
+ run
21038
+ };
21039
+ };
21040
+ const readRecordedWorkflowSources = async (repositoryRoot, sources, runsDirectory) => {
21041
+ if (await realpath(repositoryRoot) !== repositoryRoot) throw new Error("The recorded repository read root is no longer its original canonical directory.");
21042
+ return Promise.all(sources.map(async ({ path }) => ({
21043
+ path,
21044
+ content: await readFile(await resolveRepositoryReadPath(repositoryRoot, path, { runsDirectory }), "utf8")
21045
+ })));
21046
+ };
21047
+ const loadWorkflowResume = async (root, runsDirectory, from, workflow) => {
21048
+ const completed = loadCompletedWorkflowRun(root, runsDirectory, from, workflow);
21049
+ if (completed !== void 0) return completed;
21050
+ const directory = resumeDirectory(root, runsDirectory, from);
21051
+ const progress = readArtifact(directory, "progress.json", decodeWorkflowProgress, workflow);
21052
+ if (progress === void 0) throw new Error(`Cannot resume ${directory}: no resumable progress.json is recorded. Completed run.json artifacts can also be reused.`);
21053
+ assertProjectRoot(root, progress.project.root, directory);
21054
+ const failure = readArtifact(directory, "failure.json", decodeWorkflowFailure, workflow);
21055
+ if (progress.actionStarted === true && progress.action === void 0) throw new Error(`Cannot resume ${directory}: its action started but did not record completion. Inspect repository edits before starting a new run; an interrupted action cannot be replayed safely.`);
21056
+ if (progress.action !== void 0 && (progress.actionStarted !== true || progress.research === void 0 || progress.decisions === void 0)) throw new Error(`Cannot resume ${directory}: action completion is missing its stage boundaries.`);
21057
+ const checkpointGit = progress.action === void 0 ? progress.git : progress.actionGit;
21058
+ if (checkpointGit === void 0) throw new Error(`Cannot resume ${directory}: action completion lacks its repository snapshot.`);
21059
+ const currentGit = inspectWorkflowGit(root, directory);
21060
+ if (checkpointGit.head !== currentGit.head || JSON.stringify(checkpointGit.fingerprints) !== JSON.stringify(currentGit.fingerprints)) throw new Error(`Cannot resume ${directory}: repository HEAD or file contents changed since the recorded checkpoint. Restore the recorded source state or start a new run.`);
21061
+ const expectedSources = progress.action === void 0 ? progress.evidence.sources : progress.actionSources;
21062
+ if (expectedSources === void 0) throw new Error(`Cannot resume ${directory}: action completion lacks its context source snapshot.`);
21063
+ let currentSources;
21064
+ try {
21065
+ currentSources = await readRecordedWorkflowSources(progress.repositoryRoot, expectedSources, dirname(directory));
21066
+ } catch (cause) {
21067
+ throw new Error(`Cannot resume ${directory}: a recorded context source is unreadable or outside its original read bounds. ${cause instanceof Error ? cause.message : String(cause)}`, { cause });
21068
+ }
21069
+ for (const [index, source] of expectedSources.entries()) if (source.content !== currentSources[index].content) throw new Error(`Cannot resume ${directory}: recorded context source changed since the checkpoint: ${source.path}. Restore the recorded context or start a new run.`);
21070
+ if (progress.research !== void 0) {
21071
+ assertProjectRoot(root, progress.research.project.root, directory);
21072
+ if (progress.research.workflow !== workflow || progress.research.id !== progress.id) throw new Error(`Cannot resume ${directory}: research checkpoint differs from its progress artifact.`);
21073
+ } else if (failure?.agent === void 0 || failure.executor === void 0 || failure.stage !== "research") throw new Error(`Cannot resume ${directory}: its failed research did not record a Pi session and Executor evidence.`);
21074
+ return {
21075
+ directory,
21076
+ progress,
21077
+ ...failure === void 0 ? {} : { failure }
21078
+ };
21079
+ };
21080
+ //#endregion
20504
21081
  //#region src/workflows/run.ts
20505
21082
  const readCurrentSuggestionSentences = async (origin, path, allowPrivate, maxBodyBytes, signal) => {
20506
21083
  const options = {
@@ -20539,7 +21116,7 @@ const readCurrentSuggestionSentences = async (origin, path, allowPrivate, maxBod
20539
21116
  };
20540
21117
  /** Served-page reads use only the report origin and the same bounded robots checks as freshness validation. */
20541
21118
  const createFetchPageTool = async (report, allowPrivate, readSentences = readCurrentSuggestionSentences) => {
20542
- const { Type } = await import("./dist-cFoAzruL.js").then((n) => n.t);
21119
+ const { Type } = await import("./dist-Br2oOB2Q.js").then((n) => n.t);
20543
21120
  return {
20544
21121
  name: "fetch_page",
20545
21122
  label: "Read served page",
@@ -20563,7 +21140,7 @@ const createFetchPageTool = async (report, allowPrivate, readSentences = readCur
20563
21140
  };
20564
21141
  };
20565
21142
  const defaultDecide = async (inputs, spec) => {
20566
- const layer = model("jev-latest").pipe(provide(layerConfig()), provide(layer$13));
21143
+ const layer = model$1("jev-latest").pipe(provide(layerConfig()), provide(layer$13));
20567
21144
  return runPromise(gen(function* () {
20568
21145
  yield* Redacted$1("TYPESAFE_API_KEY");
20569
21146
  return yield* spec.runDecisions({
@@ -20589,41 +21166,67 @@ const researchPrompt = (spec, evidence, options) => TextTemplate.from(research_d
20589
21166
  domains: JSON.stringify(options.domains),
20590
21167
  evidence: JSON.stringify(evidence)
20591
21168
  }).render();
20592
- const actionPrompt = (spec, state, decisions, policy) => TextTemplate.from(action_default).values({
21169
+ const actionPrompt = (spec, state, decisions, policy, appRoot) => TextTemplate.from(action_default).values({
20593
21170
  workflow: spec.id,
20594
21171
  instructions: spec.actionInstructions ?? "Produce the final recommendation.",
20595
21172
  state: JSON.stringify(state),
20596
21173
  decisions: JSON.stringify(decisions),
21174
+ appRoot,
20597
21175
  mutationMode: policy.mode,
20598
- mutationInstruction: policy.mode === "dry-run" ? "Do not edit files. Describe the exact intended changes only." : "Edit the owning source files through edit_file or write_file and inspect the resulting files."
21176
+ mutationInstruction: policy.mode === "dry-run" ? "PageGraph validates your edits and records them in edits.json and review.md without changing source files; a human applies them later with `pagegraph apply`." : "PageGraph validates your edits against the current files and then applies them."
20599
21177
  }).render();
20600
21178
  const decisionArtifact = (spec, inputs, report) => ({
20601
21179
  family: spec.familyName,
20602
21180
  questions: spec.decisionQuestions(inputs),
20603
21181
  report
20604
21182
  });
21183
+ const failureRecovery = (stage, runDirectory) => {
21184
+ if (stage === "acquire") return "inspect that file; check the agent preset and Executor tools configuration";
21185
+ if (stage === "decide") return `inspect that file and research.json; check the decision provider configuration and resume with --from ${runDirectory}`;
21186
+ if (stage === "action") return `inspect the agent messages and repository; interrupted actions cannot be resumed safely. If ${runDirectory}/edits.json exists, \`pagegraph apply ${runDirectory} --check\` reports which edits remain`;
21187
+ return `inspect the agent messages in that file; resume with --from ${runDirectory}, optionally --model <id>, or raise workflows.agent.timeoutMs`;
21188
+ };
20605
21189
  const runWorkflow = async (input, dependencies = {}) => {
20606
21190
  const workflows = input.config.workflows;
20607
21191
  if (workflows === void 0) throw new Error("pagegraph.config.ts has no workflows configuration; run `pagegraph init` and configure workflows.");
20608
21192
  const spec = getWorkflowSpec(input.workflow);
20609
21193
  const now = dependencies.now ?? (() => /* @__PURE__ */ new Date());
20610
- const started = now();
20611
- const id = createRunId(started, spec.id);
20612
- const runsDirectory = input.out ?? workflows.runsDirectory ?? ".pagegraph/runs";
21194
+ let runsDirectory = input.out ?? workflows.runsDirectory ?? ".pagegraph/runs";
21195
+ const resumed = input.from === void 0 ? void 0 : await loadWorkflowResume(input.root, runsDirectory, input.from, spec.id);
21196
+ if (resumed?.run !== void 0) return {
21197
+ run: resumed.run,
21198
+ directory: resumed.directory
21199
+ };
21200
+ if (resumed !== void 0) {
21201
+ runsDirectory = dirname(resumed.directory);
21202
+ input = {
21203
+ ...input,
21204
+ options: {
21205
+ ...resumed.progress.options,
21206
+ dryRun: resumed.progress.options.dryRun || resumed.progress.action === void 0 && input.options.dryRun
21207
+ }
21208
+ };
21209
+ }
21210
+ const started = resumed?.progress === void 0 ? now() : new Date(resumed.progress.startedAt);
21211
+ const id = resumed?.progress?.id ?? createRunId(started, spec.id);
21212
+ const runDirectory = resolve(input.root, runsDirectory, id);
21213
+ let progress = resumed?.progress;
21214
+ let executorEvidence;
20613
21215
  let stage = "acquire";
20614
21216
  let agentArtifact;
20615
21217
  let pi;
20616
21218
  let selectedModel;
20617
21219
  try {
20618
- const gitAtStart = inspectGit(input.root);
21220
+ const gitAtStart = progress?.git ?? inspectWorkflowGit(input.root, runDirectory);
20619
21221
  const mutation = createWorkflowMutationPolicy({
20620
21222
  dirtyAtStart: gitAtStart.dirty,
20621
21223
  dryRun: spec.mutatesFiles ? input.options.dryRun : true,
20622
21224
  allowDirty: input.options.allowDirty
20623
21225
  });
20624
21226
  if (spec.mutatesFiles) mutation.assertStartAllowed();
20625
- const evidence = collectWorkflowEvidence(input.graph, input.options, input.root, spec.id, workflows.context);
20626
- const suggestionReport = input.options.suggestions === void 0 ? void 0 : (() => {
21227
+ const repositoryRoot = progress?.repositoryRoot ?? await resolveWorkflowRepositoryRoot(input.root, workflows.repositoryRoot);
21228
+ const evidence = progress?.evidence ?? await collectWorkflowEvidence(input.graph, input.options, repositoryRoot, spec.id, workflows.context, { runsDirectory: resolve(input.root, runsDirectory) });
21229
+ const suggestionReport = progress?.evidence.suggestions ?? (input.options.suggestions === void 0 ? void 0 : (() => {
20627
21230
  if (spec.id !== "improve.links") throw new Error("--suggestions is only valid for improve links");
20628
21231
  const report = decodeLinksSuggestionReport(JSON.parse(readFileSync(resolve(input.root, input.options.suggestions), "utf8")), new URL(input.site.origin).origin);
20629
21232
  const selectedSources = selectGraph(input.graph, input.options).nodes;
@@ -20632,19 +21235,61 @@ const runWorkflow = async (input, dependencies = {}) => {
20632
21235
  if (!selectedSources.has(candidate.source)) throw new Error(`Suggestion source is outside workflow page/kind/limit targets: ${candidate.source}`);
20633
21236
  }
20634
21237
  return report;
20635
- })();
21238
+ })());
20636
21239
  const suppliedEvidence = suggestionReport === void 0 ? evidence : {
20637
21240
  ...evidence,
20638
21241
  suggestions: suggestionReport
20639
21242
  };
20640
- pi = await import("./pi-D3mMmYey.js");
20641
- const runner = await (dependencies.acquireRunner ?? pi.acquireWorkflowRunner)({
21243
+ progress ??= {
21244
+ kind: "pagegraph-workflow-progress",
21245
+ schemaVersion: 2,
21246
+ id,
21247
+ workflow: spec.id,
21248
+ startedAt: started.toISOString(),
21249
+ project: {
21250
+ root: input.root,
21251
+ head: gitAtStart.head,
21252
+ dirtyAtStart: gitAtStart.dirty,
21253
+ filesAtStart: gitAtStart.files
21254
+ },
21255
+ options: input.options,
21256
+ evidence: {
21257
+ ...suppliedEvidence,
21258
+ executor: {
21259
+ searches: [],
21260
+ calls: []
21261
+ }
21262
+ },
21263
+ repositoryRoot,
21264
+ git: gitAtStart
21265
+ };
21266
+ const restoredAgent = progress.research?.agent ?? resumed?.failure?.agent;
21267
+ const restoredExecutor = progress.research?.evidence.executor ?? resumed?.failure?.executor;
21268
+ if (!dependencies.decide && progress?.decisions === void 0 && !process.env.TYPESAFE_API_KEY?.trim()) throw new Error("Missing decision provider credentials; set TYPESAFE_API_KEY before starting this workflow.");
21269
+ pi = await import("./pi-Bz4BgZdC.js");
21270
+ const runner = progress.research !== void 0 && (!spec.mutatesFiles || progress.action !== void 0) ? {
21271
+ model: progress.research.agent.model,
21272
+ research: async () => {
21273
+ throw new Error("Recorded research must not be repeated.");
21274
+ },
21275
+ act: async () => {
21276
+ throw new Error("Recorded action must not be repeated.");
21277
+ },
21278
+ close: async () => {}
21279
+ } : await (dependencies.acquireRunner ?? pi.acquireWorkflowRunner)({
20642
21280
  root: input.root,
21281
+ repositoryRoot,
21282
+ runDirectory,
20643
21283
  config: workflows.agent,
20644
- model: input.model ?? workflows.agent.models?.[spec.id],
21284
+ model: input.model ?? (restoredAgent === void 0 ? workflows.agent.models?.[spec.id] : `${restoredAgent.model.provider}/${restoredAgent.model.id}`),
20645
21285
  limit: input.options.limit,
20646
21286
  executor: dependencies.executor,
20647
21287
  runsDirectory: resolve(input.root, runsDirectory),
21288
+ ...restoredAgent === void 0 || restoredExecutor === void 0 ? {} : { resume: {
21289
+ agent: restoredAgent,
21290
+ executor: restoredExecutor,
21291
+ phase: progress.research === void 0 ? "research" : "action"
21292
+ } },
20648
21293
  additionalTools: spec.id === "improve.links" && suggestionReport !== void 0 ? [await createFetchPageTool(suggestionReport, input.options.allowPrivate === true, dependencies.readSuggestionSentences)] : []
20649
21294
  });
20650
21295
  const model = runner.model;
@@ -20656,6 +21301,7 @@ const runWorkflow = async (input, dependencies = {}) => {
20656
21301
  messages: result.messages,
20657
21302
  usage: result.usage
20658
21303
  };
21304
+ executorEvidence = result.executor;
20659
21305
  };
20660
21306
  retainRunnerResult({
20661
21307
  messages: [],
@@ -20668,13 +21314,19 @@ const runWorkflow = async (input, dependencies = {}) => {
20668
21314
  let turnFailed = false;
20669
21315
  try {
20670
21316
  stage = "research";
20671
- const researched = await runner.research(spec, researchPrompt(spec, suppliedEvidence, input.options), { signal: AbortSignal.timeout(workflows.agent.timeoutMs ?? 18e4) });
21317
+ const researched = progress.research === void 0 ? await runner.research(spec, `${resumed === void 0 ? "" : "Resume the recorded research conversation. Reuse completed Executor calls and their evidence; finish only the remaining research and submission.\n\n"}${researchPrompt(spec, suppliedEvidence, input.options)}`, { signal: AbortSignal.timeout(workflows.agent.timeoutMs ?? 18e4) }) : {
21318
+ state: progress.research.state,
21319
+ messages: progress.research.agent.messages,
21320
+ usage: progress.research.agent.usage,
21321
+ executor: progress.research.evidence.executor,
21322
+ rejectedItems: progress.research.rejectedItems
21323
+ };
20672
21324
  retainRunnerResult(researched);
20673
21325
  const assertResearchReadOnly = () => {
20674
- const researchFiles = changedFiles(gitAtStart, inspectGit(input.root));
21326
+ const researchFiles = changedFiles(gitAtStart, inspectWorkflowGit(input.root, runDirectory));
20675
21327
  if (researchFiles.length > 0) throw new Error(`${spec.id} changed repository files during its read-only research turn: ${researchFiles.join(", ")}`);
20676
21328
  };
20677
- assertResearchReadOnly();
21329
+ if (progress.action === void 0) assertResearchReadOnly();
20678
21330
  const state = decodeResearchState(spec, researched.state, input.options.limit);
20679
21331
  const decisionInputs = spec.decisionInputs(state);
20680
21332
  if (suggestionReport !== void 0) {
@@ -20704,13 +21356,25 @@ const runWorkflow = async (input, dependencies = {}) => {
20704
21356
  },
20705
21357
  state,
20706
21358
  decisionInputs,
20707
- agent: agentArtifact
21359
+ agent: agentArtifact,
21360
+ ...researched.rejectedItems === void 0 ? {} : { rejectedItems: researched.rejectedItems }
20708
21361
  };
20709
21362
  const checkpointPath = writeResearchCheckpoint(input.root, runsDirectory, checkpoint);
20710
- const gitAtCheckpoint = inspectGit(input.root);
21363
+ const checkpointContents = readFileSync(checkpointPath, "utf8");
21364
+ progress = {
21365
+ ...progress,
21366
+ research: checkpoint
21367
+ };
21368
+ writeWorkflowProgress(input.root, runsDirectory, progress);
21369
+ const gitAtCheckpoint = progress.git;
20711
21370
  stage = "decide";
20712
21371
  try {
20713
- const decisionReport = await (dependencies.decide ?? defaultDecide)(decisionInputs, spec);
21372
+ const decisionReport = progress.decisions ?? await (dependencies.decide ?? defaultDecide)(decisionInputs, spec);
21373
+ progress = {
21374
+ ...progress,
21375
+ decisions: decisionReport
21376
+ };
21377
+ writeWorkflowProgress(input.root, runsDirectory, progress);
20714
21378
  const accepted = spec.id === "improve.links" ? decisionReport.resolved.filter((record) => record.verdict === "add" || record.verdict === "update") : void 0;
20715
21379
  const acceptedIndexes = /* @__PURE__ */ new Set();
20716
21380
  if (accepted !== void 0) for (const record of accepted) {
@@ -20750,15 +21414,68 @@ const runWorkflow = async (input, dependencies = {}) => {
20750
21414
  }
20751
21415
  };
20752
21416
  const mayAct = spec.mutatesFiles && (actionItems === void 0 || actionItems.length > 0);
20753
- if (mayAct) stage = "action";
20754
- const acted = mayAct ? await runner.act(spec, actionPrompt(spec, actionState, actionDecisions, mutation), {
20755
- mode: mutation.mode,
20756
- signal: AbortSignal.timeout(workflows.agent.timeoutMs ?? 18e4)
20757
- }) : researched;
21417
+ if (mayAct) {
21418
+ stage = "action";
21419
+ if (progress.action === void 0) {
21420
+ progress = {
21421
+ ...progress,
21422
+ actionStarted: true
21423
+ };
21424
+ writeWorkflowProgress(input.root, runsDirectory, progress);
21425
+ }
21426
+ }
21427
+ const actionRecorded = progress.action !== void 0;
21428
+ const acted = progress.action ?? (mayAct ? await runner.act(spec, actionPrompt(spec, actionState, actionDecisions, mutation, relative(repositoryRoot, realpathSync(input.root)) || "."), { signal: AbortSignal.timeout(workflows.agent.timeoutMs ?? 18e4) }) : researched);
20758
21429
  retainRunnerResult(acted);
20759
- const gitAfter = inspectGit(input.root);
21430
+ if (readFileSync(checkpointPath, "utf8") !== checkpointContents) throw new Error(`${spec.id} changed repository files while running in read-only mode: ${checkpointPath}`);
21431
+ if (!actionRecorded) {
21432
+ const agentFiles = changedFiles(gitAtCheckpoint, inspectWorkflowGit(input.root, runDirectory));
21433
+ if (agentFiles.length > 0) throw new Error(`${spec.id} changed repository files while running in read-only mode: ${agentFiles.join(", ")}`);
21434
+ }
21435
+ const action = !spec.mutatesFiles ? void 0 : decodeActionState(mayAct ? acted.state : {
21436
+ summary: "No link suggestions accepted.",
21437
+ edits: [],
21438
+ reviewItems: []
21439
+ });
21440
+ const edits = action === void 0 ? void 0 : workflowEditsArtifact({
21441
+ id,
21442
+ workflow: spec.id,
21443
+ mode: mutation.mode,
21444
+ root: input.root,
21445
+ action,
21446
+ decisions: decisionReport,
21447
+ decisionInputs
21448
+ });
21449
+ if (edits !== void 0) {
21450
+ writeWorkflowEdits(runDirectory, edits);
21451
+ if (!actionRecorded && mutation.mode === "write" && edits.edits.length > 0) {
21452
+ const writeOptions = {
21453
+ presetDirectory: resolve(input.root, workflows.agent.presetDirectory),
21454
+ runsDirectory: resolve(input.root, runsDirectory)
21455
+ };
21456
+ const refused = (await applyWorkflowEdits(input.root, edits.edits, {
21457
+ ...writeOptions,
21458
+ check: true
21459
+ })).filter((outcome) => outcome.status !== "applicable");
21460
+ if (refused.length > 0) throw new Error(`Recorded edits no longer apply: ${refused.map((outcome) => `${outcome.id} (${outcome.path}): ${outcome.reason}`).join("; ")}`);
21461
+ const failed = (await applyWorkflowEdits(input.root, edits.edits, {
21462
+ ...writeOptions,
21463
+ check: false
21464
+ })).filter((outcome) => outcome.status !== "applied");
21465
+ if (failed.length > 0) throw new Error(`Could not apply recorded edits: ${failed.map((outcome) => `${outcome.id} (${outcome.path}): ${outcome.reason}`).join("; ")}`);
21466
+ }
21467
+ }
21468
+ const gitAfter = inspectWorkflowGit(input.root, runDirectory);
20760
21469
  const files = changedFiles(gitAtCheckpoint, gitAfter);
20761
- if ((!spec.mutatesFiles || input.options.dryRun) && files.length > 0) throw new Error(`${spec.id} changed repository files while running in read-only mode: ${files.join(", ")}`);
21470
+ if (mayAct) {
21471
+ progress = {
21472
+ ...progress,
21473
+ action: acted,
21474
+ actionGit: gitAfter,
21475
+ actionSources: await readRecordedWorkflowSources(repositoryRoot, progress.evidence.sources, resolve(input.root, runsDirectory))
21476
+ };
21477
+ writeWorkflowProgress(input.root, runsDirectory, progress);
21478
+ }
20762
21479
  const run = {
20763
21480
  kind: "pagegraph-workflow-run",
20764
21481
  schemaVersion: 2,
@@ -20787,12 +21504,14 @@ const runWorkflow = async (input, dependencies = {}) => {
20787
21504
  files,
20788
21505
  providerCalls: acted.executor.calls.map((call) => call.tool)
20789
21506
  },
20790
- result: mayAct ? acted.state : accepted !== void 0 ? {
20791
- summary: "No link suggestions accepted.",
20792
- outcome: "no-change",
20793
- files: []
20794
- } : state,
20795
- agent: agentArtifact
21507
+ result: edits === void 0 ? state : {
21508
+ summary: edits.summary,
21509
+ outcome: edits.edits.length === 0 ? "no-change" : edits.mode === "write" ? "applied" : "dry-run",
21510
+ edits: edits.edits,
21511
+ reviewItems: edits.reviewItems
21512
+ },
21513
+ agent: agentArtifact,
21514
+ ...researched.rejectedItems === void 0 ? {} : { rejectedItems: researched.rejectedItems }
20796
21515
  };
20797
21516
  return {
20798
21517
  run,
@@ -20824,6 +21543,7 @@ const runWorkflow = async (input, dependencies = {}) => {
20824
21543
  messages: current.result.messages,
20825
21544
  usage: current.result.usage
20826
21545
  };
21546
+ executorEvidence = current.result.executor;
20827
21547
  break;
20828
21548
  }
20829
21549
  current = current.cause;
@@ -20831,6 +21551,7 @@ const runWorkflow = async (input, dependencies = {}) => {
20831
21551
  const message = cause instanceof Error ? cause.message : String(cause);
20832
21552
  let path;
20833
21553
  try {
21554
+ if (progress !== void 0) writeWorkflowProgress(input.root, runsDirectory, progress);
20834
21555
  path = writeWorkflowFailure(input.root, runsDirectory, id, {
20835
21556
  kind: "pagegraph-workflow-failure",
20836
21557
  schemaVersion: 2,
@@ -20838,6 +21559,7 @@ const runWorkflow = async (input, dependencies = {}) => {
20838
21559
  workflow: spec.id,
20839
21560
  stage,
20840
21561
  ...agentArtifact === void 0 ? {} : { agent: agentArtifact },
21562
+ ...executorEvidence === void 0 ? {} : { executor: executorEvidence },
20841
21563
  cause: serializeCause(cause)
20842
21564
  });
20843
21565
  } catch (writeError) {
@@ -20847,7 +21569,8 @@ const runWorkflow = async (input, dependencies = {}) => {
20847
21569
  }
20848
21570
  throw new Error(`${message}\nCould not write failure artifact: ${String(writeError)}`, { cause });
20849
21571
  }
20850
- throw new Error(`${message}\nFailure artifact: ${path}\nNext: ${stage === "acquire" ? "inspect that file; check the agent preset and Executor tools configuration" : stage === "decide" ? "inspect that file and research.json; check the decision provider configuration and retry" : "inspect the agent messages in that file; rerun with --model <id> or raise workflows.agent.timeoutMs"}`, { cause });
21572
+ const next = failureRecovery(stage, runDirectory);
21573
+ throw new Error(`${message}\nFailure artifact: ${path}\nNext: ${next}`, { cause });
20851
21574
  }
20852
21575
  };
20853
21576
  //#endregion
@@ -20864,8 +21587,9 @@ const domainFlag = String$2("domain").pipe(withDescription$2("Authority target d
20864
21587
  const refreshFlag = Boolean$1("refresh").pipe(withDescription$2("Ask discovered tools to bypass reusable evidence when supported"), withDefault(false));
20865
21588
  const modelFlag = String$2("model").pipe(withDescription$2("Agent provider/model override"), optional$1);
20866
21589
  const presetDirectoryFlag = String$2("preset-directory").pipe(withDescription$2("Agent preset-directory override"), optional$1);
21590
+ const fromFlag = String$2("from").pipe(withDescription$2("Resume a recorded run directory or run ID, retaining its targets and completed stages"), optional$1);
20867
21591
  const outFlag = String$2("out").pipe(withDescription$2("Run-artifact directory override"), optional$1);
20868
- const dryRunFlag$1 = Boolean$1("dry-run").pipe(withDescription$2("Suppress file writes; discovered Executor tools remain available"), withDefault(false));
21592
+ const dryRunFlag$1 = Boolean$1("dry-run").pipe(withDescription$2("Record edits in edits.json and review.md without changing source files"), withDefault(false));
20869
21593
  const allowDirtyFlag$1 = Boolean$1("allow-dirty").pipe(withDescription$2("Permit file changes when the working tree is already dirty"), withDefault(false));
20870
21594
  const suggestionsFlag = String$2("suggestions").pipe(withDescription$2("Schema-version-2 links candidate JSON to evaluate"), optional$1);
20871
21595
  const baseFlags = {
@@ -20876,6 +21600,7 @@ const baseFlags = {
20876
21600
  model: modelFlag,
20877
21601
  presetDirectory: presetDirectoryFlag,
20878
21602
  out: outFlag,
21603
+ from: fromFlag,
20879
21604
  json: jsonFlag
20880
21605
  };
20881
21606
  const researchFlags = {
@@ -20924,10 +21649,16 @@ const resultSummary = (result) => {
20924
21649
  }
20925
21650
  return `${result.run.workflow} completed.`;
20926
21651
  };
20927
- /** Run one workflow command after loading the consumer's graph and config. */
21652
+ /** Reuse a completed run, or load the consumer's graph for remaining workflow stages. */
20928
21653
  const runWorkflowCommand = async (workflow, flags, extra = {}) => {
20929
21654
  const project = await runPromise(loadSeoProjectConfig);
20930
21655
  if (project.config.workflows === void 0) throw new Error("pagegraph.config.ts has no workflows configuration; run `pagegraph init` and add it.");
21656
+ const from = optionalString(flags.from);
21657
+ const out = optionalString(flags.out);
21658
+ if (from !== void 0) {
21659
+ const completed = loadCompletedWorkflowRun(project.root, out ?? project.config.workflows.runsDirectory ?? ".pagegraph/runs", from, workflow);
21660
+ if (completed !== void 0) return completed;
21661
+ }
20931
21662
  const configured = optionalString(flags.presetDirectory);
20932
21663
  const config = configured === void 0 ? project.config : {
20933
21664
  ...project.config,
@@ -20949,7 +21680,8 @@ const runWorkflowCommand = async (workflow, flags, extra = {}) => {
20949
21680
  workflow,
20950
21681
  options: optionsFrom(flags, extra),
20951
21682
  model: optionalString(flags.model),
20952
- out: optionalString(flags.out)
21683
+ out,
21684
+ from
20953
21685
  };
20954
21686
  return yield* promise(() => runWorkflow(input));
20955
21687
  })));
@@ -20976,7 +21708,13 @@ const workflowCommand = (input) => {
20976
21708
  catch: (cause) => new SeoCliError({ message: messageOf$1(cause) })
20977
21709
  });
20978
21710
  if (rawFlags.json === true) yield* printJson(result.run);
20979
- else yield* printText(`${resultSummary(result)}\n\nRun artifact: ${result.directory}`);
21711
+ else yield* printText([
21712
+ resultSummary(result),
21713
+ "",
21714
+ `Run artifact: ${result.directory}`,
21715
+ ...input.writesFiles ? [`Review: ${join(result.directory, "review.md")}`] : [],
21716
+ ...input.writesFiles && result.run.options.dryRun ? [`Apply: pagegraph apply ${result.directory} --check`] : []
21717
+ ].join("\n"));
20980
21718
  })));
20981
21719
  };
20982
21720
  const researchCommandGroup = make$6("research").pipe(withDescription$1("Research page-backed organic-search opportunities with Pi and Executor"), withSubcommands([
@@ -21164,76 +21902,76 @@ const inspectCommand = make$6("inspect", {
21164
21902
  var gitignore_default = "runs/\n";
21165
21903
  //#endregion
21166
21904
  //#region src/cli/preset/agent/AGENTS.md
21167
- var AGENTS_default = "# PageGraph SEO\n\nUse the seo agent for PageGraph workflows. Preserve the consumer repository's instructions and\nproduct truth. Use only the workflow skills attached by PageGraph, cite repository and provider\nevidence, and search Executor before choosing live SEO tools.\n\nPageGraph runs the workflow on Pi. Use `executor_search` to discover live tools and their TypeScript\ninput and result shapes, then `executor_execute` with a TypeScript snippet calling the discovered\n`tools.<namespace>.<tool>(input)` and a top-level `return`. Repository evidence is available through\n`read_file`, `list_files`, and `search_text`. Submit structured workflow state with `submit_result`.\n\nFor improve workflows, the action turn exposes `edit_file` and `write_file` only in write mode.\nDry-run actions describe intended changes using read tools. There is no shell tool.\n\nConfigure `workflows.agent.presetDirectory: \".pagegraph/agent\"` in `pagegraph.config.ts`.\n";
21905
+ var AGENTS_default = "# PageGraph SEO\n\nUse the seo agent for PageGraph workflows. Preserve the consumer repository's instructions and\nproduct truth. Use only the workflow skills attached by PageGraph, cite repository and provider\nevidence, and search Executor before choosing live SEO tools.\n\nPageGraph runs the workflow on Pi. Use `executor_search` to discover live tools and their TypeScript\ninput and result shapes, then `executor_execute` with a TypeScript snippet calling the discovered\n`tools.<namespace>.<tool>(input)` and a top-level `return`. Inside each `executor_execute` snippet, project the result to only the fields\nand rows needed; large results can exceed the preview limit. For example, use\n`rows.slice(0, 50).map(({ keyword, searchVolume, keywordDifficulty }) => ({ keyword, searchVolume, keywordDifficulty }))`.\nWhen a result says it was truncated, read its named JSON file with `read_file`; use `offset` and\n`maxBytes` to page through large artifacts. Repository evidence is available through `read_file`,\n`list_files`, and `search_text`. Submit structured workflow state with `submit_result`.\n\nImprove workflows have no file-writing tools. Their action turn submits `edits`, each with a path,\n`oldText` copied exactly from the current file, `newText`, a reason, and evidence, plus `reviewItems`\nfor changes a human must make. PageGraph applies edits only in write mode. There is no shell tool.\n\nConfigure `workflows.agent.presetDirectory: \".pagegraph/agent\"` in `pagegraph.config.ts`.\n";
21168
21906
  //#endregion
21169
21907
  //#region src/cli/preset/agent/agents/seo.md
21170
- var seo_default = "---\ndescription: Execute PageGraph SEO workflows from repository and live search evidence\n---\n\nExecute the PageGraph workflow supplied by the host. Start from PageGraph's deterministic route,\nsource, and rendered evidence, and load only the workflow skills attached to the request.\n\nExecutor is required for live evidence. Search its catalog before selecting integrations, then call\nthe discovered tools with their TypeScript arguments. Recipes are examples rather than an inventory.\nPrefer relevant cached evidence when it covers the requested market, language, device, and freshness window.\n\nKeep repository evidence, owned-site evidence, external provider estimates, observed search\nresults, and inference distinct. Record market, language, device, dates, freshness, limits, and cost\nwhen available. Product claims come from the consumer repository, not search-volume estimates.\n\nResearch is read-only. Do not edit files or mutate provider state during research. Apply repository\nchanges only when the host starts a post-decision action turn in write mode.\n\nDo not ask questions or create forms. Fail clearly when required evidence or configuration is\nunavailable.\n\nUse `executor_search` to discover tools and inspect their TypeScript shapes. Call them with\n`executor_execute`: the snippet calls the discovered `tools.<namespace>.<tool>(input)` and uses\na top-level `return` for the evidence. Use `read_file`, `list_files`, and `search_text` for repository evidence,\nand `submit_result` for the workflow's structured research or action result. Improve action turns\nexpose `edit_file` and `write_file` in write mode; dry-run turns keep only read tools.\n";
21908
+ var seo_default = "---\ndescription: Execute PageGraph SEO workflows from repository and live search evidence\n---\n\nExecute the PageGraph workflow supplied by the host. Start from PageGraph's deterministic route,\nsource, and rendered evidence, and load only the workflow skills attached to the request.\n\nExecutor is required for live evidence. Search its catalog before selecting integrations, then call\nthe discovered tools with their TypeScript arguments. Recipes are examples rather than an inventory.\nPrefer relevant cached evidence when it covers the requested market, language, device, and freshness window.\n\nKeep repository evidence, owned-site evidence, external provider estimates, observed search\nresults, and inference distinct. Record market, language, device, dates, freshness, limits, and cost\nwhen available. Product claims come from the consumer repository, not search-volume estimates.\n\nResearch is read-only. Do not mutate provider state. Propose repository changes only as structured\nedits in the post-decision action turn; PageGraph applies them in write mode.\n\nDo not ask questions or create forms. Fail clearly when required evidence or configuration is\nunavailable.\n\nUse `executor_search` to discover tools and inspect their TypeScript shapes. Call them with\n`executor_execute`: the snippet calls the discovered `tools.<namespace>.<tool>(input)` and uses\na top-level `return` for the evidence. Use `read_file`, `list_files`, and `search_text` for repository evidence,\nand `submit_result` for the workflow's structured research or action result. Improve action turns\nsubmit `edits` and `reviewItems`; no tool writes files.\n";
21171
21909
  //#endregion
21172
21910
  //#region src/cli/preset/agent/skills/ai-search/SKILL.md
21173
21911
  var SKILL_default$11 = "---\nname: ai-search\ndescription: Evaluate answer extractability, entity clarity, support, and citation readiness.\n---\n\n# AI Search Analysis\n\nStart from the page's visible content, structured data, route context, and cited sources. Read\n[Executor Starters](references/executor.md) before collecting current AI-search observations.\n\nEvaluate whether an answer engine can identify the subject, extract a concise answer, understand\nentity relationships, and trace material claims to first-party or trustworthy support. Distinguish\nobserved citations from inferred readiness. Do not claim a visibility lift from formatting alone.\n\nReturn the structured state requested by the workflow. This workflow produces evidence and a\ndiagnosis; it does not edit pages or submit them to an answer engine.\n";
21174
21912
  //#endregion
21175
21913
  //#region src/cli/preset/agent/skills/ai-search/references/executor.md
21176
- var executor_default$10 = "# Executor Starters for AI Search Analysis\n\nThese snippets are starting points, not an inventory. Search for current citation, brand-mention,\nanswer-engine, and owned-performance capabilities available through Executor.\n\nUse `executor_search` for each discovery query below; it returns the current tools and their\nTypeScript input and result shapes:\n\n- `AI search citations answer visibility brand mentions`\n- `Google Search Console generative AI performance page`\n\nPass a TypeScript snippet to `executor_execute`, replacing `discovered_namespace`, `discovered_tool`, and\narguments with the returned names and required fields:\n\n~~~ts\nreturn await tools.discovered_namespace.discovered_tool({\n domain,\n queries,\n market,\n language,\n});\n~~~\n\nRecord engine or surface, query, market, collection date, cited URL, response excerpt, sampling\nmethod, task status, errors, and cost. Missing citations in a small sample do not prove absence.\nDo not mutate provider projects or publish content.\n\nUse `read_file`, `list_files`, and `search_text` for repository evidence. Finish the turn with\n`submit_result` using the workflow result shape.\n";
21914
+ var executor_default$10 = "# Executor Starters for AI Search Analysis\n\nThese snippets are starting points, not an inventory. Search for current citation, brand-mention,\nanswer-engine, and owned-performance capabilities available through Executor.\n\nUse `executor_search` for each discovery query below; it returns the current tools and their\nTypeScript input and result shapes:\n\n- `AI search citations answer visibility brand mentions`\n- `Google Search Console generative AI performance page`\n\nPass a TypeScript snippet to `executor_execute`, replacing `discovered_namespace`, `discovered_tool`, and\narguments with the returned names and required fields:\n\n~~~ts\nreturn await tools.discovered_namespace.discovered_tool({\n domain,\n queries,\n market,\n language,\n});\n~~~\n\nRecord engine or surface, query, market, collection date, cited URL, response excerpt, sampling\nmethod, task status, errors, and cost. Missing citations in a small sample do not prove absence.\nDo not mutate provider projects or publish content.\n\nUse `read_file`, `list_files`, and `search_text` for repository evidence. Finish the turn with\n`submit_result` using the workflow result shape.\n\nKeep `executor_execute` results small: project to only the fields and rows needed in the TypeScript\nsnippet. For example, use `rows.slice(0, 50).map(({ keyword, searchVolume, keywordDifficulty }) => ({\n keyword, searchVolume, keywordDifficulty,\n}))`.\nIf PageGraph reports that a result was truncated, read the named JSON artifact with `read_file`.\nUse `offset` and `maxBytes` to read large files in chunks.\n";
21177
21915
  //#endregion
21178
21916
  //#region src/cli/preset/agent/skills/authority-research/SKILL.md
21179
21917
  var SKILL_default$10 = "---\nname: authority-research\ndescription: Qualify legitimate citation, directory, editorial, partner, and community opportunities.\n---\n\n# Authority Research\n\nUse PageGraph's pages to identify the asset that could reasonably earn each citation. Read\n[Executor Starters](references/executor.md) before collecting backlink or live-page evidence.\n\nUse backlink gaps only to nominate sources. Verify the current page and qualify topical relevance,\nlegitimacy, editorial fit, audience overlap, and eligibility. Reject paid-link schemes, bulk\ndirectories, expired pages, scraped lists, and opportunities with no suitable destination asset.\n\nReturn the structured state requested by the workflow. Contact discovery, outreach, submission,\nand provider project updates are separate operations and remain outside this research workflow.\n";
21180
21918
  //#endregion
21181
21919
  //#region src/cli/preset/agent/skills/authority-research/references/executor.md
21182
- var executor_default$9 = "# Executor Starters for Authority Research\n\nThese snippets are starting points, not an inventory. Discover backlink and live-page capabilities\nbefore composing calls.\n\nUse `executor_search` for each discovery query below; it returns the current tools and their\nTypeScript input and result shapes:\n\n- `OpenSEO DataForSEO backlink domain intersection`\n- `web search fetch directory editorial partner eligibility`\n\nPass a TypeScript snippet to `executor_execute`, replacing `discovered_namespace`, `discovered_tool`, and\narguments with the returned names and required fields:\n\n~~~ts\nreturn await tools.discovered_namespace.discovered_tool({\n targets: [ownedDomain, ...competitors],\n excludeInternal: true,\n limit: 100,\n});\n~~~\n\nRecord source URL, target domain, dates, limits, authority metrics, task status, errors, and cost.\nProvider metrics nominate prospects; current pages determine legitimacy. Do not discover personal\ncontact data, send outreach, submit listings, or mutate OpenSEO project state.\n\nUse `read_file`, `list_files`, and `search_text` for repository evidence. Finish the turn with\n`submit_result` using the workflow result shape.\n";
21920
+ var executor_default$9 = "# Executor Starters for Authority Research\n\nThese snippets are starting points, not an inventory. Discover backlink and live-page capabilities\nbefore composing calls.\n\nUse `executor_search` for each discovery query below; it returns the current tools and their\nTypeScript input and result shapes:\n\n- `OpenSEO DataForSEO backlink domain intersection`\n- `web search fetch directory editorial partner eligibility`\n\nPass a TypeScript snippet to `executor_execute`, replacing `discovered_namespace`, `discovered_tool`, and\narguments with the returned names and required fields:\n\n~~~ts\nreturn await tools.discovered_namespace.discovered_tool({\n targets: [ownedDomain, ...competitors],\n excludeInternal: true,\n limit: 100,\n});\n~~~\n\nRecord source URL, target domain, dates, limits, authority metrics, task status, errors, and cost.\nProvider metrics nominate prospects; current pages determine legitimacy. Do not discover personal\ncontact data, send outreach, submit listings, or mutate OpenSEO project state.\n\nUse `read_file`, `list_files`, and `search_text` for repository evidence. Finish the turn with\n`submit_result` using the workflow result shape.\n\nKeep `executor_execute` results small: project to only the fields and rows needed in the TypeScript\nsnippet. For example, use `rows.slice(0, 50).map(({ keyword, searchVolume, keywordDifficulty }) => ({\n keyword, searchVolume, keywordDifficulty,\n}))`.\nIf PageGraph reports that a result was truncated, read the named JSON artifact with `read_file`.\nUse `offset` and `maxBytes` to read large files in chunks.\n";
21183
21921
  //#endregion
21184
21922
  //#region src/cli/preset/agent/skills/competitive-landscape/SKILL.md
21185
21923
  var SKILL_default$9 = "---\nname: competitive-landscape\ndescription: Identify current search competitors and evidence-backed openings across representative queries.\n---\n\n# Competitive Landscape\n\nUse PageGraph's public-page corpus and product context to decide which domains compete for the same\nneed. Read [Executor Starters](references/executor.md) before collecting live market evidence.\n\nSample representative queries, find domains that recur, and compare their page types, intent,\ncoverage, differentiation, and authority signals. Separate direct product competitors, publishers,\ndirectories, communities, and incidental results. Study only the leaders whose evidence could\nchange a decision.\n\nReturn the structured state requested by the workflow. Do not copy competitor claims, treat every\nranking keyword as desirable, or update a provider's saved competitor list.\n";
21186
21924
  //#endregion
21187
21925
  //#region src/cli/preset/agent/skills/competitive-landscape/references/executor.md
21188
- var executor_default$8 = "# Executor Starters for a Competitive Landscape\n\nThese snippets are starting points, not an inventory. Discover current tools and call the discovered tools\nwith arguments matching their TypeScript shapes.\n\nUse `executor_search` for each discovery query below; it returns the current tools and their\nTypeScript input and result shapes:\n\n- `OpenSEO SERP competitors recurring domains`\n- `domain overview ranked keywords backlink authority`\n\nPass a TypeScript snippet to `executor_execute`, replacing `discovered_namespace`, `discovered_tool`, and\narguments with the returned names and required fields:\n\n~~~ts\nreturn await tools.discovered_namespace.discovered_tool({\n queries,\n market,\n language,\n device,\n});\n~~~\n\nPrefer relevant cached OpenSEO context or research when an existing project matches the domain\nunambiguously. Use external traffic and keyword values as estimates. Record query, market, device,\ndate, result depth, task status, errors, and cost. Do not update project context or competitors.\n\nUse `read_file`, `list_files`, and `search_text` for repository evidence. Finish the turn with\n`submit_result` using the workflow result shape.\n";
21926
+ var executor_default$8 = "# Executor Starters for a Competitive Landscape\n\nThese snippets are starting points, not an inventory. Discover current tools and call the discovered tools\nwith arguments matching their TypeScript shapes.\n\nUse `executor_search` for each discovery query below; it returns the current tools and their\nTypeScript input and result shapes:\n\n- `OpenSEO SERP competitors recurring domains`\n- `domain overview ranked keywords backlink authority`\n\nPass a TypeScript snippet to `executor_execute`, replacing `discovered_namespace`, `discovered_tool`, and\narguments with the returned names and required fields:\n\n~~~ts\nreturn await tools.discovered_namespace.discovered_tool({\n queries,\n market,\n language,\n device,\n});\n~~~\n\nPrefer relevant cached OpenSEO context or research when an existing project matches the domain\nunambiguously. Use external traffic and keyword values as estimates. Record query, market, device,\ndate, result depth, task status, errors, and cost. Do not update project context or competitors.\n\nUse `read_file`, `list_files`, and `search_text` for repository evidence. Finish the turn with\n`submit_result` using the workflow result shape.\n\nKeep `executor_execute` results small: project to only the fields and rows needed in the TypeScript\nsnippet. For example, use `rows.slice(0, 50).map(({ keyword, searchVolume, keywordDifficulty }) => ({\n keyword, searchVolume, keywordDifficulty,\n}))`.\nIf PageGraph reports that a result was truncated, read the named JSON artifact with `read_file`.\nUse `offset` and `maxBytes` to read large files in chunks.\n";
21189
21927
  //#endregion
21190
21928
  //#region src/cli/preset/agent/skills/content-analysis/SKILL.md
21191
21929
  var SKILL_default$8 = "---\nname: content-analysis\ndescription: Evaluate a page's substance, intent coverage, answer quality, and differentiation.\n---\n\n# Content Analysis\n\nStart from the supplied source, rendered page, route relationships, and project truth. Read\n[Executor Starters](references/executor.md) when live owned-site or competitive evidence is needed.\n\nJudge whether the page answers its intended need, supports material claims, avoids sibling-page\ncannibalization, and offers useful depth for its format. Use word counts and competitor excerpts as\ndescriptive signals, never as universal targets. Keep product accuracy ahead of search demand.\n\nReturn the structured state requested by the workflow. This workflow diagnoses content; it does not\nedit source files.\n";
21192
21930
  //#endregion
21193
21931
  //#region src/cli/preset/agent/skills/content-analysis/references/executor.md
21194
- var executor_default$7 = "# Executor Starters for Content Analysis\n\nThese snippets are starting points, not an inventory. Discover owned query/page performance and\ncurrent result-page evidence when they can change the diagnosis.\n\nUse `executor_search` for each discovery query below; it returns the current tools and their\nTypeScript input and result shapes:\n\n- `Google Search Console query page performance`\n- `OpenSEO current SERP competitor content`\n\nPass a TypeScript snippet to `executor_execute`, replacing `discovered_namespace`, `discovered_tool`, and\narguments with the returned names and required fields:\n\n~~~ts\nreturn await tools.discovered_namespace.discovered_tool({\n siteUrl,\n startDate,\n endDate,\n dimensions: [\"PAGE\", \"QUERY\"],\n});\n~~~\n\nKeep Search Console or Bing performance separate from provider estimates and live-page\nobservations. Record reporting dates, filters, market, limits, task status, errors, and cost. Do not\nchange content or provider state.\n\nUse `read_file`, `list_files`, and `search_text` for repository evidence. Finish the turn with\n`submit_result` using the workflow result shape.\n";
21932
+ var executor_default$7 = "# Executor Starters for Content Analysis\n\nThese snippets are starting points, not an inventory. Discover owned query/page performance and\ncurrent result-page evidence when they can change the diagnosis.\n\nUse `executor_search` for each discovery query below; it returns the current tools and their\nTypeScript input and result shapes:\n\n- `Google Search Console query page performance`\n- `OpenSEO current SERP competitor content`\n\nPass a TypeScript snippet to `executor_execute`, replacing `discovered_namespace`, `discovered_tool`, and\narguments with the returned names and required fields:\n\n~~~ts\nreturn await tools.discovered_namespace.discovered_tool({\n siteUrl,\n startDate,\n endDate,\n dimensions: [\"PAGE\", \"QUERY\"],\n});\n~~~\n\nKeep Search Console or Bing performance separate from provider estimates and live-page\nobservations. Record reporting dates, filters, market, limits, task status, errors, and cost. Do not\nchange content or provider state.\n\nUse `read_file`, `list_files`, and `search_text` for repository evidence. Finish the turn with\n`submit_result` using the workflow result shape.\n\nKeep `executor_execute` results small: project to only the fields and rows needed in the TypeScript\nsnippet. For example, use `rows.slice(0, 50).map(({ keyword, searchVolume, keywordDifficulty }) => ({\n keyword, searchVolume, keywordDifficulty,\n}))`.\nIf PageGraph reports that a result was truncated, read the named JSON artifact with `read_file`.\nUse `offset` and `maxBytes` to read large files in chunks.\n";
21195
21933
  //#endregion
21196
21934
  //#region src/cli/preset/agent/skills/content-improvement/SKILL.md
21197
- var SKILL_default$7 = "---\nname: content-improvement\ndescription: Research and apply bounded content changes supported by PageGraph and live evidence.\n---\n\n# Content Improvement\n\nResearch the target page exactly as described by [Executor Starters](references/executor.md). During\nthe research turn, diagnose intent, substance, differentiation, support, and sibling-page ownership\nwithout editing files.\n\nDuring the host-authorized action turn, apply only recommendations accepted by the decision report.\nPreserve the repository's public positioning and voice, edit the owning source rather than generated\noutput, and keep the change local to the selected page and intent. Do not invent metrics, customers,\ncapabilities, or citations.\n\nLeave the resulting repository diff uncommitted for review. Provider state remains read-only.\n";
21935
+ var SKILL_default$7 = "---\nname: content-improvement\ndescription: Research and apply bounded content changes supported by PageGraph and live evidence.\n---\n\n# Content Improvement\n\nResearch the target page exactly as described by [Executor Starters](references/executor.md). During\nthe research turn, diagnose intent, substance, differentiation, support, and sibling-page ownership\nwithout editing files.\n\nDuring the host-authorized action turn, submit edits only for recommendations accepted by the\ndecision report. Preserve the repository's public positioning and voice, target the owning source\nrather than generated output, and keep the change local to the selected page and intent. Record a\nreview item instead of an edit when the owning source or supporting evidence is unclear. Do not invent metrics, customers,\ncapabilities, or citations.\n\nPageGraph leaves applied edits uncommitted for review. Provider state remains read-only.\n";
21198
21936
  //#endregion
21199
21937
  //#region src/cli/preset/agent/skills/content-improvement/references/executor.md
21200
- var executor_default$6 = "# Executor Starters for Content Improvement\n\nThese snippets are starting points, not an inventory. Discover owned performance and current SERP\nevidence during the read-only research turn.\n\nUse `executor_search` for each discovery query below; it returns the current tools and their\nTypeScript input and result shapes:\n\n- `Google Search Console page query performance`\n- `OpenSEO current SERP intent competitor pages`\n\nPass a TypeScript snippet to `executor_execute`, replacing `discovered_namespace`, `discovered_tool`, and\narguments with the returned names and required fields:\n\n~~~ts\nreturn await tools.discovered_namespace.discovered_tool({\n query,\n market,\n language,\n device,\n});\n~~~\n\nRecord market, language, device, dates, limits, task status, errors, and cost. Search evidence may\nsupport a source edit only after the host evaluates it; never edit during research or write to an\nOpenSEO project.\n\nUse `read_file`, `list_files`, and `search_text` for repository evidence. Finish the turn with\n`submit_result` using the workflow result shape.\n\nAfter the host evaluates the research, a write-mode action turn exposes `edit_file` and\n`write_file` for guarded repository edits. In dry-run mode, describe intended edits using only\nread tools.\n";
21938
+ var executor_default$6 = "# Executor Starters for Content Improvement\n\nThese snippets are starting points, not an inventory. Discover owned performance and current SERP\nevidence during the read-only research turn.\n\nUse `executor_search` for each discovery query below; it returns the current tools and their\nTypeScript input and result shapes:\n\n- `Google Search Console page query performance`\n- `OpenSEO current SERP intent competitor pages`\n\nPass a TypeScript snippet to `executor_execute`, replacing `discovered_namespace`, `discovered_tool`, and\narguments with the returned names and required fields:\n\n~~~ts\nreturn await tools.discovered_namespace.discovered_tool({\n query,\n market,\n language,\n device,\n});\n~~~\n\nRecord market, language, device, dates, limits, task status, errors, and cost. Search evidence may\nsupport a source edit only after the host evaluates it; never edit during research or write to an\nOpenSEO project.\n\nUse `read_file`, `list_files`, and `search_text` for repository evidence. Finish the turn with\n`submit_result` using the workflow result shape.\n\nAfter the host evaluates the research, the action turn submits structured `edits` (path, exact\n`oldText`, `newText`, reason, evidence) and `reviewItems` through `submit_result`. There are no\nfile-writing tools: PageGraph validates each edit against the current file and records it in the\nrun's `edits.json` and `review.md`, then applies it only in write mode.\n\nKeep `executor_execute` results small: project to only the fields and rows needed in the TypeScript\nsnippet. For example, use `rows.slice(0, 50).map(({ keyword, searchVolume, keywordDifficulty }) => ({\n keyword, searchVolume, keywordDifficulty,\n}))`.\nIf PageGraph reports that a result was truncated, read the named JSON artifact with `read_file`.\nUse `offset` and `maxBytes` to read large files in chunks.\n";
21201
21939
  //#endregion
21202
21940
  //#region src/cli/preset/agent/skills/internal-linking/SKILL.md
21203
- var SKILL_default$6 = "---\nname: internal-linking\ndescription: Research and apply useful contextual links supported by route and page evidence.\n---\n\n# Internal Linking\n\nUse PageGraph's nodes and edges to find relevant source-target pairs, then inspect both pages before\nproposing a link. Read [Executor Starters](references/executor.md) only when owned search evidence\ncan clarify page ownership or discoverability.\n\nPrefer links that help a reader continue a task, strengthen a clear hierarchy, or connect an\norphaned but relevant page. Reject forced anchors, repeated links, unsupported wording, and links\nwhose target does not satisfy the promised intent.\n\nDuring the host-authorized action turn, apply only accepted links at the owning source or route\ndeclaration. Preserve generated graph contracts, use natural anchor text, leave the diff uncommitted,\nand keep provider state read-only.\n";
21941
+ var SKILL_default$6 = "---\nname: internal-linking\ndescription: Research and apply useful contextual links supported by route and page evidence.\n---\n\n# Internal Linking\n\nUse PageGraph's nodes and edges to find relevant source-target pairs, then inspect both pages before\nproposing a link. Read [Executor Starters](references/executor.md) only when owned search evidence\ncan clarify page ownership or discoverability.\n\nPrefer links that help a reader continue a task, strengthen a clear hierarchy, or connect an\norphaned but relevant page. Reject forced anchors, repeated links, unsupported wording, and links\nwhose target does not satisfy the promised intent.\n\nDuring the host-authorized action turn, submit edits only for accepted links at the owning source or\nroute declaration. Preserve generated graph contracts, use natural anchor text, and keep provider\nstate read-only.\n";
21204
21942
  //#endregion
21205
21943
  //#region src/cli/preset/agent/skills/internal-linking/references/executor.md
21206
- var executor_default$5 = "# Executor Starters for Internal Linking\n\nThese snippets are starting points, not an inventory. PageGraph's graph is primary; discover owned\nquery/page or crawl evidence only when it resolves ownership or discoverability.\n\nUse `executor_search` for each discovery query below; it returns the current tools and their\nTypeScript input and result shapes:\n\n- `Google Search Console query page internal ownership`\n- `Bing Webmaster crawl links URL information`\n\nPass a TypeScript snippet to `executor_execute`, replacing `discovered_namespace`, `discovered_tool`, and\narguments with the returned names and required fields:\n\n~~~ts\nreturn await tools.discovered_namespace.discovered_tool({\n siteUrl,\n startDate,\n endDate,\n dimensions: [\"PAGE\", \"QUERY\"],\n});\n~~~\n\nRecord dates, page and query filters, crawl state, task status, errors, and cost. Search performance\ncan support a graph recommendation but does not override the source content. Do not submit URLs,\nrequest indexing, or mutate provider projects.\n\nUse `read_file`, `list_files`, and `search_text` for repository evidence. Finish the turn with\n`submit_result` using the workflow result shape.\n\nAfter the host evaluates the research, a write-mode action turn exposes `edit_file` and\n`write_file` for guarded repository edits. In dry-run mode, describe intended edits using only\nread tools.\n\nUse `fetch_page` to read sentences from served pages on origins in the suggestion report.\n";
21944
+ var executor_default$5 = "# Executor Starters for Internal Linking\n\nThese snippets are starting points, not an inventory. PageGraph's graph is primary; discover owned\nquery/page or crawl evidence only when it resolves ownership or discoverability.\n\nUse `executor_search` for each discovery query below; it returns the current tools and their\nTypeScript input and result shapes:\n\n- `Google Search Console query page internal ownership`\n- `Bing Webmaster crawl links URL information`\n\nPass a TypeScript snippet to `executor_execute`, replacing `discovered_namespace`, `discovered_tool`, and\narguments with the returned names and required fields:\n\n~~~ts\nreturn await tools.discovered_namespace.discovered_tool({\n siteUrl,\n startDate,\n endDate,\n dimensions: [\"PAGE\", \"QUERY\"],\n});\n~~~\n\nRecord dates, page and query filters, crawl state, task status, errors, and cost. Search performance\ncan support a graph recommendation but does not override the source content. Do not submit URLs,\nrequest indexing, or mutate provider projects.\n\nUse `read_file`, `list_files`, and `search_text` for repository evidence. Finish the turn with\n`submit_result` using the workflow result shape.\n\nAfter the host evaluates the research, the action turn submits structured `edits` (path, exact\n`oldText`, `newText`, reason, evidence) and `reviewItems` through `submit_result`. There are no\nfile-writing tools: PageGraph validates each edit against the current file and records it in the\nrun's `edits.json` and `review.md`, then applies it only in write mode.\n\nUse `fetch_page` to read sentences from served pages on origins in the suggestion report.\n\nKeep `executor_execute` results small: project to only the fields and rows needed in the TypeScript\nsnippet. For example, use `rows.slice(0, 50).map(({ keyword, searchVolume, keywordDifficulty }) => ({\n keyword, searchVolume, keywordDifficulty,\n}))`.\nIf PageGraph reports that a result was truncated, read the named JSON artifact with `read_file`.\nUse `offset` and `maxBytes` to read large files in chunks.\n";
21207
21945
  //#endregion
21208
21946
  //#region src/cli/preset/agent/skills/keyword-research/SKILL.md
21209
21947
  var SKILL_default$5 = "---\nname: keyword-research\ndescription: Discover current search demand, validate intent, and map opportunities to public pages.\n---\n\n# Keyword Research\n\nUse PageGraph's route and page evidence as the ownership baseline. Read\n[Executor Starters](references/executor.md) before collecting live evidence.\n\nStart from the supplied seeds and consumer product context. Expand only into queries that match the\nactual product, audience, and market. Use owned-site performance to establish current visibility,\nprovider metrics to estimate demand and competition, and current SERPs to validate intent. A volume\nestimate alone does not establish product fit or justify a new page.\n\nMap each supported query to the strongest existing page. Propose a new page only when no current\npage can satisfy the distinct intent without becoming unfocused. Preserve evidence and caveats for\nevery opportunity.\n\nReturn the structured state requested by the workflow. Do not save keywords, update provider\nprojects, submit URLs, or edit repository files during research.\n";
21210
21948
  //#endregion
21211
21949
  //#region src/cli/preset/agent/skills/keyword-research/references/executor.md
21212
- var executor_default$4 = "# Executor Starters for Keyword Research\n\nThese snippets are starting points, not an inventory. Use `executor_search` before selecting calls, and inspect the returned TypeScript shapes.\n\nDiscover Google Search Console or Bing query/page performance, OpenSEO cached project context and\nkeyword research, provider keyword metrics, and live SERP results. Prefer a relevant cached OpenSEO\nresult when it already covers the requested market, language, device, and freshness. When a matching\nOpenSEO project can be resolved unambiguously, its project context and research log are supporting\nevidence; the consumer repository remains authoritative for product truth.\n\nUse `executor_search` for each discovery query below; it returns the current tools and their\nTypeScript input and result shapes:\n\n- `Google Search Console performance query page`\n- `OpenSEO project context cached keyword research`\n- `keyword volume difficulty current SERP results`\n\nPass a TypeScript snippet to `executor_execute`, replacing `discovered_namespace`, `discovered_tool`, and\narguments with the returned names and required fields:\n\n~~~ts\nreturn await tools.discovered_namespace.discovered_tool({ siteUrl, startDate, endDate, dimensions: [\"PAGE\", \"QUERY\"] });\n~~~\n\nTreat Search Console and Bing as owned-site evidence, OpenSEO and DataForSEO as provider evidence,\nand current result pages as observed evidence. Record market, language, device, reporting dates,\nlimits, task status, errors, and cost metadata when returned. Do not create or update an OpenSEO\nproject, save keywords, or perform another provider write.\n\nUse `read_file`, `list_files`, and `search_text` for repository evidence. Finish the turn with\n`submit_result` using the workflow result shape.\n";
21950
+ var executor_default$4 = "# Executor Starters for Keyword Research\n\nThese snippets are starting points, not an inventory. Use `executor_search` before selecting calls, and inspect the returned TypeScript shapes.\n\nDiscover Google Search Console or Bing query/page performance, OpenSEO cached project context and\nkeyword research, provider keyword metrics, and live SERP results. Prefer a relevant cached OpenSEO\nresult when it already covers the requested market, language, device, and freshness. When a matching\nOpenSEO project can be resolved unambiguously, its project context and research log are supporting\nevidence; the consumer repository remains authoritative for product truth.\n\nUse `executor_search` for each discovery query below; it returns the current tools and their\nTypeScript input and result shapes:\n\n- `Google Search Console performance query page`\n- `OpenSEO project context cached keyword research`\n- `keyword volume difficulty current SERP results`\n\nPass a TypeScript snippet to `executor_execute`, replacing `discovered_namespace`, `discovered_tool`, and\narguments with the returned names and required fields:\n\n~~~ts\nreturn await tools.discovered_namespace.discovered_tool({ siteUrl, startDate, endDate, dimensions: [\"PAGE\", \"QUERY\"] });\n~~~\n\nTreat Search Console and Bing as owned-site evidence, OpenSEO and DataForSEO as provider evidence,\nand current result pages as observed evidence. Record market, language, device, reporting dates,\nlimits, task status, errors, and cost metadata when returned. Do not create or update an OpenSEO\nproject, save keywords, or perform another provider write.\n\nUse `read_file`, `list_files`, and `search_text` for repository evidence. Finish the turn with\n`submit_result` using the workflow result shape.\n\nKeep `executor_execute` results small: project to only the fields and rows needed in the TypeScript\nsnippet. For example, use `rows.slice(0, 50).map(({ keyword, searchVolume, keywordDifficulty }) => ({\n keyword, searchVolume, keywordDifficulty,\n}))`.\nIf PageGraph reports that a result was truncated, read the named JSON artifact with `read_file`.\nUse `offset` and `maxBytes` to read large files in chunks.\n";
21213
21951
  //#endregion
21214
21952
  //#region src/cli/preset/agent/skills/metadata-improvement/SKILL.md
21215
- var SKILL_default$4 = "---\nname: metadata-improvement\ndescription: Research and apply truthful title and description improvements at the owning source.\n---\n\n# Metadata Improvement\n\nUse the route's intent, rendered head, sibling ownership, and live evidence described in\n[Executor Starters](references/executor.md). During research, compare candidate titles and\ndescriptions for accuracy, distinctness, intent alignment, and usefulness rather than fixed length\nor keyword-density targets.\n\nDuring the host-authorized action turn, apply only the accepted candidate at the owning route or\ncontent source. Preserve truthful product claims, existing metadata composition, canonical output,\nand repository conventions. Do not edit generated artifacts.\n\nLeave the resulting repository diff uncommitted. Provider state remains read-only.\n";
21953
+ var SKILL_default$4 = "---\nname: metadata-improvement\ndescription: Research and apply truthful title and description improvements at the owning source.\n---\n\n# Metadata Improvement\n\nUse the route's intent, rendered head, sibling ownership, and live evidence described in\n[Executor Starters](references/executor.md). During research, compare candidate titles and\ndescriptions for accuracy, distinctness, intent alignment, and usefulness rather than fixed length\nor keyword-density targets.\n\nDuring the host-authorized action turn, submit an edit only for the accepted candidate at the owning\nroute or content source. Preserve truthful product claims, existing metadata composition, canonical output,\nand repository conventions. Do not edit generated artifacts.\n\nPageGraph leaves applied edits uncommitted. Provider state remains read-only.\n";
21216
21954
  //#endregion
21217
21955
  //#region src/cli/preset/agent/skills/metadata-improvement/references/executor.md
21218
- var executor_default$3 = "# Executor Starters for Metadata Improvement\n\nThese snippets are starting points, not an inventory. Discover owned query/page evidence and a\ncurrent SERP before selecting a candidate.\n\nUse `executor_search` for each discovery query below; it returns the current tools and their\nTypeScript input and result shapes:\n\n- `Google Search Console query page CTR position`\n- `current SERP titles snippets intent`\n\nPass a TypeScript snippet to `executor_execute`, replacing `discovered_namespace`, `discovered_tool`, and\narguments with the returned names and required fields:\n\n~~~ts\nreturn await tools.discovered_namespace.discovered_tool({\n siteUrl,\n startDate,\n endDate,\n dimensions: [\"PAGE\", \"QUERY\"],\n});\n~~~\n\nRecord dates, query and page filters, market, device, task status, errors, and cost. CTR and position\ndescribe observed performance; they do not prove a particular title caused it. Do not mutate\nprovider metadata or request recrawls.\n\nUse `read_file`, `list_files`, and `search_text` for repository evidence. Finish the turn with\n`submit_result` using the workflow result shape.\n\nAfter the host evaluates the research, a write-mode action turn exposes `edit_file` and\n`write_file` for guarded repository edits. In dry-run mode, describe intended edits using only\nread tools.\n";
21956
+ var executor_default$3 = "# Executor Starters for Metadata Improvement\n\nThese snippets are starting points, not an inventory. Discover owned query/page evidence and a\ncurrent SERP before selecting a candidate.\n\nUse `executor_search` for each discovery query below; it returns the current tools and their\nTypeScript input and result shapes:\n\n- `Google Search Console query page CTR position`\n- `current SERP titles snippets intent`\n\nPass a TypeScript snippet to `executor_execute`, replacing `discovered_namespace`, `discovered_tool`, and\narguments with the returned names and required fields:\n\n~~~ts\nreturn await tools.discovered_namespace.discovered_tool({\n siteUrl,\n startDate,\n endDate,\n dimensions: [\"PAGE\", \"QUERY\"],\n});\n~~~\n\nRecord dates, query and page filters, market, device, task status, errors, and cost. CTR and position\ndescribe observed performance; they do not prove a particular title caused it. Do not mutate\nprovider metadata or request recrawls.\n\nUse `read_file`, `list_files`, and `search_text` for repository evidence. Finish the turn with\n`submit_result` using the workflow result shape.\n\nAfter the host evaluates the research, the action turn submits structured `edits` (path, exact\n`oldText`, `newText`, reason, evidence) and `reviewItems` through `submit_result`. There are no\nfile-writing tools: PageGraph validates each edit against the current file and records it in the\nrun's `edits.json` and `review.md`, then applies it only in write mode.\n\nKeep `executor_execute` results small: project to only the fields and rows needed in the TypeScript\nsnippet. For example, use `rows.slice(0, 50).map(({ keyword, searchVolume, keywordDifficulty }) => ({\n keyword, searchVolume, keywordDifficulty,\n}))`.\nIf PageGraph reports that a result was truncated, read the named JSON artifact with `read_file`.\nUse `offset` and `maxBytes` to read large files in chunks.\n";
21219
21957
  //#endregion
21220
21958
  //#region src/cli/preset/agent/skills/schema/SKILL.md
21221
- var SKILL_default$3 = "---\nname: schema\ndescription: Research and apply JSON-LD types justified by visible page content.\n---\n\n# Schema\n\nUse the rendered page, existing JSON-LD, route kind, and owning source as the primary evidence. Read\n[Executor Starters](references/executor.md) when owned search appearance or current eligibility\nevidence is material.\n\nPropose only types and properties that describe visible content. Schema does not repair missing\ncontent, guarantee a rich result, or justify unsupported ratings, prices, authorship, FAQs, or\norganization claims.\n\nDuring the host-authorized action turn, apply only accepted changes through the repository's\nexisting JSON-LD composition primitives. Do not edit rendered or generated output. Leave the diff\nuncommitted and provider state unchanged.\n";
21959
+ var SKILL_default$3 = "---\nname: schema\ndescription: Research and apply JSON-LD types justified by visible page content.\n---\n\n# Schema\n\nUse the rendered page, existing JSON-LD, route kind, and owning source as the primary evidence. Read\n[Executor Starters](references/executor.md) when owned search appearance or current eligibility\nevidence is material.\n\nPropose only types and properties that describe visible content. Schema does not repair missing\ncontent, guarantee a rich result, or justify unsupported ratings, prices, authorship, FAQs, or\norganization claims.\n\nDuring the host-authorized action turn, submit edits only for accepted changes, using the\nrepository's existing JSON-LD composition primitives. Do not target rendered or generated output;\nrecord a review item when the owning source is unclear. Provider state stays unchanged.\n";
21222
21960
  //#endregion
21223
21961
  //#region src/cli/preset/agent/skills/schema/references/executor.md
21224
- var executor_default$2 = "# Executor Starters for Schema\n\nThese snippets are starting points, not an inventory. Discover owned search-appearance, URL\ninspection, rich-result, or documentation tools when they can change the eligibility assessment.\n\nUse `executor_search` for each discovery query below; it returns the current tools and their\nTypeScript input and result shapes:\n\n- `Google Search Console search appearance page`\n- `URL inspection rich results structured data eligibility`\n\nPass a TypeScript snippet to `executor_execute`, replacing `discovered_namespace`, `discovered_tool`, and\narguments with the returned names and required fields:\n\n~~~ts\nreturn await tools.discovered_namespace.discovered_tool({\n siteUrl,\n startDate,\n endDate,\n dimensions: [\"SEARCH_APPEARANCE\", \"PAGE\"],\n});\n~~~\n\nTreat visible page content and current primary requirements as eligibility authority. Record dates,\npage filters, result types, task status, errors, and cost. Do not request indexing, submit schemas,\nor update provider projects.\n\nUse `read_file`, `list_files`, and `search_text` for repository evidence. Finish the turn with\n`submit_result` using the workflow result shape.\n\nAfter the host evaluates the research, a write-mode action turn exposes `edit_file` and\n`write_file` for guarded repository edits. In dry-run mode, describe intended edits using only\nread tools.\n";
21962
+ var executor_default$2 = "# Executor Starters for Schema\n\nThese snippets are starting points, not an inventory. Discover owned search-appearance, URL\ninspection, rich-result, or documentation tools when they can change the eligibility assessment.\n\nUse `executor_search` for each discovery query below; it returns the current tools and their\nTypeScript input and result shapes:\n\n- `Google Search Console search appearance page`\n- `URL inspection rich results structured data eligibility`\n\nPass a TypeScript snippet to `executor_execute`, replacing `discovered_namespace`, `discovered_tool`, and\narguments with the returned names and required fields:\n\n~~~ts\nreturn await tools.discovered_namespace.discovered_tool({\n siteUrl,\n startDate,\n endDate,\n dimensions: [\"SEARCH_APPEARANCE\", \"PAGE\"],\n});\n~~~\n\nTreat visible page content and current primary requirements as eligibility authority. Record dates,\npage filters, result types, task status, errors, and cost. Do not request indexing, submit schemas,\nor update provider projects.\n\nUse `read_file`, `list_files`, and `search_text` for repository evidence. Finish the turn with\n`submit_result` using the workflow result shape.\n\nAfter the host evaluates the research, the action turn submits structured `edits` (path, exact\n`oldText`, `newText`, reason, evidence) and `reviewItems` through `submit_result`. There are no\nfile-writing tools: PageGraph validates each edit against the current file and records it in the\nrun's `edits.json` and `review.md`, then applies it only in write mode.\n\nKeep `executor_execute` results small: project to only the fields and rows needed in the TypeScript\nsnippet. For example, use `rows.slice(0, 50).map(({ keyword, searchVolume, keywordDifficulty }) => ({\n keyword, searchVolume, keywordDifficulty,\n}))`.\nIf PageGraph reports that a result was truncated, read the named JSON artifact with `read_file`.\nUse `offset` and `maxBytes` to read large files in chunks.\n";
21225
21963
  //#endregion
21226
21964
  //#region src/cli/preset/agent/skills/serp-analysis/SKILL.md
21227
21965
  var SKILL_default$2 = "---\nname: serp-analysis\ndescription: Compare a target page with current results, intent, formats, features, and title patterns.\n---\n\n# SERP Analysis\n\nTreat each supplied query, market, language, device, and target page as one analysis unit. Read\n[Executor Starters](references/executor.md) before fetching current result evidence.\n\nIdentify dominant intent, page format, result features, recurring domains, title framing, and the\nevidence a useful page must satisfy. Compare the target against those patterns without turning\ncompetitor wording into product truth. Mark mixed or unstable intent rather than forcing a single\nconclusion.\n\nReturn the structured state requested by the workflow. A current SERP is observed evidence for its\nmarket and time, not a universal ranking rule.\n";
21228
21966
  //#endregion
21229
21967
  //#region src/cli/preset/agent/skills/serp-analysis/references/executor.md
21230
- var executor_default$1 = "# Executor Starters for SERP Analysis\n\nThese snippets are starting points, not an inventory. Discover a current organic SERP capability\nand resolve its exact signature before use.\n\nUse `executor_search` for each discovery query below; it returns the current tools and their\nTypeScript input and result shapes:\n\n- `OpenSEO DataForSEO current organic SERP features intent`\n\nPass a TypeScript snippet to `executor_execute`, replacing `discovered_namespace`, `discovered_tool`, and\narguments with the returned names and required fields:\n\n~~~ts\nreturn await tools.discovered_namespace.discovered_tool({\n query,\n market,\n language,\n device,\n depth: 20,\n});\n~~~\n\nReuse a matching cached OpenSEO SERP when it satisfies the requested freshness. Record query,\nmarket, language, device, collection time, depth, features, task status, errors, and cost. Fetch\nindividual result pages only when their content is material to the decision.\n\nUse `read_file`, `list_files`, and `search_text` for repository evidence. Finish the turn with\n`submit_result` using the workflow result shape.\n";
21968
+ var executor_default$1 = "# Executor Starters for SERP Analysis\n\nThese snippets are starting points, not an inventory. Discover a current organic SERP capability\nand resolve its exact signature before use.\n\nUse `executor_search` for each discovery query below; it returns the current tools and their\nTypeScript input and result shapes:\n\n- `OpenSEO DataForSEO current organic SERP features intent`\n\nPass a TypeScript snippet to `executor_execute`, replacing `discovered_namespace`, `discovered_tool`, and\narguments with the returned names and required fields:\n\n~~~ts\nreturn await tools.discovered_namespace.discovered_tool({\n query,\n market,\n language,\n device,\n depth: 20,\n});\n~~~\n\nReuse a matching cached OpenSEO SERP when it satisfies the requested freshness. Record query,\nmarket, language, device, collection time, depth, features, task status, errors, and cost. Fetch\nindividual result pages only when their content is material to the decision.\n\nUse `read_file`, `list_files`, and `search_text` for repository evidence. Finish the turn with\n`submit_result` using the workflow result shape.\n\nKeep `executor_execute` results small: project to only the fields and rows needed in the TypeScript\nsnippet. For example, use `rows.slice(0, 50).map(({ keyword, searchVolume, keywordDifficulty }) => ({\n keyword, searchVolume, keywordDifficulty,\n}))`.\nIf PageGraph reports that a result was truncated, read the named JSON artifact with `read_file`.\nUse `offset` and `maxBytes` to read large files in chunks.\n";
21231
21969
  //#endregion
21232
21970
  //#region src/cli/preset/agent/skills/site-architecture/SKILL.md
21233
21971
  var SKILL_default$1 = "---\nname: site-architecture\ndescription: Plan clear route ownership, hierarchy, collections, and contextual navigation.\n---\n\n# Site Architecture\n\nUse PageGraph's deterministic route nodes and edges as the primary model. Read\n[Executor Starters](references/executor.md) when owned search or crawl evidence can resolve an\nownership or discoverability question.\n\nAssign one clear intent owner, identify useful collections and parent-child relationships, and\nprefer consolidation when multiple pages compete for the same need. Propose new routes only when a\ndistinct supported intent lacks an adequate owner. Treat URL changes as consequential and preserve\nredirect and canonical requirements in the recommendation.\n\nReturn the structured state requested by the workflow. This planning workflow does not move routes,\nedit navigation, or submit sitemaps.\n";
21234
21972
  //#endregion
21235
21973
  //#region src/cli/preset/agent/skills/site-architecture/references/executor.md
21236
- var executor_default = "# Executor Starters for Site Architecture\n\nThese snippets are starting points, not an inventory. Discover owned page/query, crawl, sitemap,\nand URL evidence only when the deterministic graph does not answer the question.\n\nUse `executor_search` for each discovery query below; it returns the current tools and their\nTypeScript input and result shapes:\n\n- `Search Console page performance`\n- `Bing Webmaster crawl`\n\nInspect a selected tool's TypeScript shape with `executor_search` before executing it.\n\nPass a TypeScript snippet to `executor_execute`, replacing `discovered_namespace`, `discovered_tool`, and\narguments with the returned names and required fields:\n\n~~~ts\nreturn await tools.discovered_namespace.discovered_tool(input);\n~~~\n\nIn a later turn, call that discovered tool with the required fields from its returned signature. Property,\nproject, URL, date, and dimension arguments differ between integrations. Do not copy guessed fields\nfrom a recipe. Prefer page/query performance for demand and ownership questions; use URL inspection\nwhen the question actually concerns crawl, canonical, or indexing state.\n\nKeep submitted, crawled, indexed, ranking, and clicked evidence distinct. Record reporting dates,\nfilters, canonical state, task status, errors, and cost. Do not request indexing, submit sitemaps,\nor update provider projects.\n\nUse `read_file`, `list_files`, and `search_text` for repository evidence. Finish the turn with\n`submit_result` using the workflow result shape.\n";
21974
+ var executor_default = "# Executor Starters for Site Architecture\n\nThese snippets are starting points, not an inventory. Discover owned page/query, crawl, sitemap,\nand URL evidence only when the deterministic graph does not answer the question.\n\nUse `executor_search` for each discovery query below; it returns the current tools and their\nTypeScript input and result shapes:\n\n- `Search Console page performance`\n- `Bing Webmaster crawl`\n\nInspect a selected tool's TypeScript shape with `executor_search` before executing it.\n\nPass a TypeScript snippet to `executor_execute`, replacing `discovered_namespace`, `discovered_tool`, and\narguments with the returned names and required fields:\n\n~~~ts\nreturn await tools.discovered_namespace.discovered_tool(input);\n~~~\n\nIn a later turn, call that discovered tool with the required fields from its returned signature. Property,\nproject, URL, date, and dimension arguments differ between integrations. Do not copy guessed fields\nfrom a recipe. Prefer page/query performance for demand and ownership questions; use URL inspection\nwhen the question actually concerns crawl, canonical, or indexing state.\n\nKeep submitted, crawled, indexed, ranking, and clicked evidence distinct. Record reporting dates,\nfilters, canonical state, task status, errors, and cost. Do not request indexing, submit sitemaps,\nor update provider projects.\n\nUse `read_file`, `list_files`, and `search_text` for repository evidence. Finish the turn with\n`submit_result` using the workflow result shape.\n\nKeep `executor_execute` results small: project to only the fields and rows needed in the TypeScript\nsnippet. For example, use `rows.slice(0, 50).map(({ keyword, searchVolume, keywordDifficulty }) => ({\n keyword, searchVolume, keywordDifficulty,\n}))`.\nIf PageGraph reports that a result was truncated, read the named JSON artifact with `read_file`.\nUse `offset` and `maxBytes` to read large files in chunks.\n";
21237
21975
  //#endregion
21238
21976
  //#region src/cli/preset/agent/preset.ts
21239
21977
  const skill = (name, definition, executor) => ({
@@ -22007,6 +22745,7 @@ const cli = make$6("pagegraph").pipe(withDescription$1("Inspect, audit, research
22007
22745
  description: "Run bounded page-backed keyword research"
22008
22746
  }
22009
22747
  ]), withSubcommands([
22748
+ applyCommand,
22010
22749
  auditCommand,
22011
22750
  analyzeCommandGroup,
22012
22751
  initCommand,
@@ -22102,7 +22841,7 @@ const withStdoutBuffer = (program) => acquireUseRelease(sync$1(() => {
22102
22841
  if (!state.discard && state.buffer.length > 0) flushStdout(state);
22103
22842
  else state.buffer.length = 0;
22104
22843
  }));
22105
- const program = withStdoutBuffer(run$2(cli, { version }).pipe(provideService(LogToStderr, true), provide$1(layer$1), tapErrorTag("SeoCliError", (error$2) => error(`✗ ${error$2.message}`))));
22844
+ const program = withStdoutBuffer(run$3(cli, { version }).pipe(provideService(LogToStderr, true), provide$1(layer$1), tapErrorTag("SeoCliError", (error$2) => error(`✗ ${error$2.message}`))));
22106
22845
  /**
22107
22846
  * Run the CLI. An explicit call from `bin.ts` rather than an import-time side
22108
22847
  * effect: the package declares `sideEffects: false`, so a body that only ran on