@bendyline/gezel-sdk 1.0.6 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/checks.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { G as GateScriptResult } from './types-cYfcp6_8.js';
1
+ import { G as GateScriptResult } from './types-DCBb1goX.js';
2
2
 
3
3
  /**
4
4
  * ─ Shared deliverable checks ─────────────────────────────────────────
@@ -42,6 +42,33 @@ interface CheckResult {
42
42
  detail: string;
43
43
  }
44
44
 
45
+ /**
46
+ * Existence oracle for cited workspace paths — the shared spine of the
47
+ * anti-fabrication checks (`citationsResolve`, `securityReport`,
48
+ * `codebaseReviewReport`).
49
+ *
50
+ * `list()` is only a FAST PATH here, never ground truth. The real surface
51
+ * behind it walks the workspace breadth-first under an entry cap (500 on
52
+ * the service side) and skips dotfiles, so in any repo bigger than the cap
53
+ * a truthful citation (`packages/core/src/paths.ts`, `.gitignore`) is
54
+ * simply absent from the listing. Treating that absence as fabrication
55
+ * made the gates unsatisfiable by honest citation and pressured models
56
+ * toward exactly the shallow or invented paths the gates exist to catch
57
+ * (task gezel/8, 2026-08-23: a 3,843-file repo where only the first 500
58
+ * entries counted as "real"). A listing miss therefore falls back to a
59
+ * per-path `read()` probe before any path may be called fabricated.
60
+ */
61
+ declare function createCitedPathChecker(ws: WorkspaceLike): (cited: string) => Promise<boolean>;
62
+ /**
63
+ * Strip citation decoration down to a workspace-relative path: backticks,
64
+ * a `:line` / `#anchor` suffix, leading `./` or `/`, and a `workspace/`
65
+ * prefix. Case is preserved — the probe read needs the real path on
66
+ * case-sensitive filesystems; only the listing key lowercases.
67
+ */
68
+ declare function cleanCitedPath(p: string): string;
69
+ /** Case-insensitive membership key for listing entries and citations alike. */
70
+ declare function citedPathKey(p: string): string;
71
+
45
72
  /**
46
73
  * HTML deliverable checks: truncation detection, inline-script
47
74
  * extraction, V8 syntax validation, and the two content sniffs the
@@ -327,19 +354,36 @@ interface CitationsResult extends CheckResult {
327
354
  unresolved: string[];
328
355
  /** Cited URLs (not checked offline unless a corpus allowlist is given). */
329
356
  urls: string[];
357
+ /** Unresolvable cited paths forgiven as task metadata (see `knownPaths`). */
358
+ forgiven?: string[];
330
359
  }
331
360
  /**
332
361
  * Every source `file` cites must exist. File-path citations are resolved
333
362
  * against the workspace listing (tolerant of leading `./`, `/`, and
334
- * `workspace/`, case-insensitive). URLs cannot be fetched offline, so
363
+ * `workspace/`, case-insensitive), with a per-path read probe for listing
364
+ * misses — the listing is capped and dotfile-blind, see
365
+ * `createCitedPathChecker`. URLs cannot be fetched offline, so
335
366
  * they pass unless `corpus` is supplied, in which case every cited path
336
367
  * AND URL must be a member of the allowlist. The anti-fabrication gate.
368
+ *
369
+ * `knownPaths` are paths the surrounding task itself supplied — invocation
370
+ * parameters, the step prompt's own path tokens, the artifact working
371
+ * folder. A cited path matching one of these that does NOT resolve is
372
+ * FORGIVEN (dropped from the citation set — it counts toward neither the
373
+ * minimum nor the fabrication verdict): transcribing the run's own
374
+ * metadata into a packet is bookkeeping, not sourcing. A knownPath that
375
+ * DOES resolve stays an ordinary resolved citation. Wild-caught: the
376
+ * powerpoint-deck research step requires sources.md to record the
377
+ * invocation inputs, and the backticked `tasks/8` / `powerpoint/task-8/
378
+ * deck.pptx` tokens — directory handles and a future output — read as six
379
+ * fabricated citations, failing the gate's honest no-research path.
337
380
  */
338
381
  declare function citationsResolve(ws: WorkspaceLike, file: string, opts?: {
339
382
  pattern?: string;
340
383
  flags?: string;
341
384
  minCitations?: number;
342
385
  corpus?: string[];
386
+ knownPaths?: readonly string[];
343
387
  }): Promise<CitationsResult>;
344
388
  /** Spec for {@link valuesSubsetOf}. */
345
389
  interface ValuesSubsetSpec {
@@ -394,6 +438,25 @@ interface SecurityReportResult extends CheckResult {
394
438
  }
395
439
  declare function securityReport(ws: WorkspaceLike, reportFile: string, opts?: SecurityReportOptions): Promise<SecurityReportResult>;
396
440
 
441
+ interface CodebaseReviewReportOptions {
442
+ /** Path (in `reports`) to the machine-readable findings JSON. */
443
+ findings?: string;
444
+ /** Section headings the report must contain. */
445
+ requiredSections?: string[];
446
+ /** Minimum systemic themes required once findings ≥ themeThreshold. */
447
+ minThemes?: number;
448
+ /** Findings count at/above which systemic-theme synthesis is required. */
449
+ themeThreshold?: number;
450
+ /** Minimum data rows the Scorecard table must carry. */
451
+ minScorecardRows?: number;
452
+ }
453
+ interface CodebaseReviewReportResult extends CheckResult {
454
+ findingCount: number;
455
+ /** Cited files that don't exist in the workspace, for logs/facts. */
456
+ fabricated: string[];
457
+ }
458
+ declare function codebaseReviewReport(reports: WorkspaceLike, workspace: WorkspaceLike, reportFile: string, opts?: CodebaseReviewReportOptions): Promise<CodebaseReviewReportResult>;
459
+
397
460
  /** ISO yyyy-mm-dd AND a real calendar date (ported from data-wrangle). */
398
461
  declare function isRealIsoDate(value: string): boolean;
399
462
  /**
@@ -692,6 +755,42 @@ interface PlanStructureResult {
692
755
  }
693
756
  declare function planStructure(text: string, spec?: PlanStructureSpec): PlanStructureResult;
694
757
 
758
+ interface CorpusCoverageShardInput {
759
+ path: string;
760
+ content: string;
761
+ }
762
+ interface CorpusCoverageLedger {
763
+ pullRequest?: number;
764
+ reviewedFiles: string[];
765
+ reviewedRecords: string[];
766
+ sources: Array<{
767
+ batchNumber: number;
768
+ shard: string;
769
+ }>;
770
+ complete: boolean;
771
+ }
772
+ interface CorpusCoverageMergeResult extends CheckResult {
773
+ ledger?: CorpusCoverageLedger;
774
+ content?: string;
775
+ expectedBatches: number;
776
+ mergedBatches: number;
777
+ missingBatches: number[];
778
+ }
779
+ /**
780
+ * Deterministically merge bounded corpus-coverage shards.
781
+ *
782
+ * The published batch manifest is the authority. Every accepted shard must
783
+ * name exactly one batch and reproduce that batch's path and record arrays in
784
+ * order; a parent cannot manufacture full-run coverage by copying paths that
785
+ * no child shard reported. Missing shards may be merged for an in-progress
786
+ * ledger, but malformed or out-of-scope shards always fail closed.
787
+ */
788
+ declare function mergeCorpusCoverageShards(batchesContent: string, shards: readonly CorpusCoverageShardInput[], opts?: {
789
+ pullRequest?: number;
790
+ requireComplete?: boolean;
791
+ batchesFile?: string;
792
+ }): CorpusCoverageMergeResult;
793
+
695
794
  /**
696
795
  * `@bendyline/gezel-sdk/checks` — the shared deliverable-check predicates,
697
796
  * re-exported for use INSIDE sandboxed scripts.
@@ -730,4 +829,4 @@ declare function workspaceFromGezel(g: {
730
829
  };
731
830
  }): WorkspaceLike;
732
831
 
733
- export { type CellType, type CheckResult, type CitationsResult, type CsvShapeResult, type CsvShapeSpec, type EntitiesResult, type EntitySpec, type ExplainableSniff, GateScriptResult, type GroundingFact, type GroundingResult, IMG_EXT, type ImageRefsReport, type InlineScript, type JsonPathEqualsResult, type JsonScalar, type JudgeVerdict, MIN_INLINE_JS_BYTES, MIN_JUDGE_EVIDENCE_SUBSTRING, type MarkdownHeadingsMatchResult, type ParsedTable, type PlanRow, type PlanStructureResult, type PlanStructureSpec, type ReadingLevelResult, type RecordFieldSpec, type RecordSchemaResult, type RecordSchemaSpec, type ScriptValidation, type SecurityReportOptions, type SecurityReportResult, type TableShapeResult, type TableShapeSpec, type UnsupportedClaimPattern, type UnsupportedClaimViolation, type UnsupportedClaimsResult, type ValuesSubsetResult, type ValuesSubsetSpec, type WordBandResult, type WorkspaceLike, buildJudgePrompt, citationsResolve, containsPattern, countDistinctMatches, cssMinBytes, csvShape, dataTableSniff, detectTypeScriptOnlySyntax, detectUnclosedScript, esmImports, explainSniff, extractInlineScripts, fileCountByExt, fileMinBytes, fileMinLines, findImageRefs, gateResult, grepMatches, htmlCompleteSniff, htmlGameSniff, imageRefsResolve, inlineJsBytes, isRealIsoDate, jsonPathEquals, jsonValid, markdownHeadingsMatch, namedEntitiesConsistent, normalizeDigitGroups, notContainsPattern, parseCsv, parseJudgeVerdict, parseMarkdownTable, planStructure, readingLevel, recordSchema, requireOrderedSections, resolveRelative, securityReport, standaloneJsParses, tableShape, totalMinBytes, unsupportedClaims, validateJudgeEvidence, validateScriptSyntax, valueGrounding, valuesSubsetOf, wordBand, workspaceFromGezel, wrapperReturnHint };
832
+ export { type CellType, type CheckResult, type CitationsResult, type CodebaseReviewReportOptions, type CodebaseReviewReportResult, type CorpusCoverageLedger, type CorpusCoverageMergeResult, type CorpusCoverageShardInput, type CsvShapeResult, type CsvShapeSpec, type EntitiesResult, type EntitySpec, type ExplainableSniff, GateScriptResult, type GroundingFact, type GroundingResult, IMG_EXT, type ImageRefsReport, type InlineScript, type JsonPathEqualsResult, type JsonScalar, type JudgeVerdict, MIN_INLINE_JS_BYTES, MIN_JUDGE_EVIDENCE_SUBSTRING, type MarkdownHeadingsMatchResult, type ParsedTable, type PlanRow, type PlanStructureResult, type PlanStructureSpec, type ReadingLevelResult, type RecordFieldSpec, type RecordSchemaResult, type RecordSchemaSpec, type ScriptValidation, type SecurityReportOptions, type SecurityReportResult, type TableShapeResult, type TableShapeSpec, type UnsupportedClaimPattern, type UnsupportedClaimViolation, type UnsupportedClaimsResult, type ValuesSubsetResult, type ValuesSubsetSpec, type WordBandResult, type WorkspaceLike, buildJudgePrompt, citationsResolve, citedPathKey, cleanCitedPath, codebaseReviewReport, containsPattern, countDistinctMatches, createCitedPathChecker, cssMinBytes, csvShape, dataTableSniff, detectTypeScriptOnlySyntax, detectUnclosedScript, esmImports, explainSniff, extractInlineScripts, fileCountByExt, fileMinBytes, fileMinLines, findImageRefs, gateResult, grepMatches, htmlCompleteSniff, htmlGameSniff, imageRefsResolve, inlineJsBytes, isRealIsoDate, jsonPathEquals, jsonValid, markdownHeadingsMatch, mergeCorpusCoverageShards, namedEntitiesConsistent, normalizeDigitGroups, notContainsPattern, parseCsv, parseJudgeVerdict, parseMarkdownTable, planStructure, readingLevel, recordSchema, requireOrderedSections, resolveRelative, securityReport, standaloneJsParses, tableShape, totalMinBytes, unsupportedClaims, validateJudgeEvidence, validateScriptSyntax, valueGrounding, valuesSubsetOf, wordBand, workspaceFromGezel, wrapperReturnHint };
package/dist/checks.js CHANGED
@@ -1,3 +1,33 @@
1
+ // ../core/src/checks/workspace-exists.ts
2
+ function createCitedPathChecker(ws) {
3
+ let listing = null;
4
+ const probes = /* @__PURE__ */ new Map();
5
+ const loadListing = () => {
6
+ listing ??= ws.list().then((files) => new Set(files.map((f) => citedPathKey(f))));
7
+ return listing;
8
+ };
9
+ return async (cited) => {
10
+ const probe = cleanCitedPath(cited);
11
+ if (!probe) return false;
12
+ if ((await loadListing()).has(probe.toLowerCase())) return true;
13
+ let hit = probes.get(probe);
14
+ if (!hit) {
15
+ hit = ws.read(probe).then(
16
+ (content) => content !== null,
17
+ () => false
18
+ );
19
+ probes.set(probe, hit);
20
+ }
21
+ return hit;
22
+ };
23
+ }
24
+ function cleanCitedPath(p) {
25
+ return p.trim().replace(/^`+|`+$/g, "").replace(/[:#].*$/, "").replace(/^\.\//, "").replace(/^\/+/, "").replace(/^workspace\//i, "");
26
+ }
27
+ function citedPathKey(p) {
28
+ return cleanCitedPath(p).toLowerCase();
29
+ }
30
+
1
31
  // ../core/src/checks/html.ts
2
32
  var MIN_INLINE_JS_BYTES = 2048;
3
33
  var SCRIPT_RE = /<script\b([^>]*)>([\s\S]*?)<\/script\s*>/gi;
@@ -536,12 +566,24 @@ function valueGrounding(text, facts, opts = {}) {
536
566
  decoysDetected
537
567
  };
538
568
  }
539
- var DEFAULT_CITATION_RE = /\(source:\s*([^)\s]+)(?:\s+\[[^\]]*\])*\s*\)|\]\(\s*(?!#)([^)\s]+?)\s*\)|`([^`]*\/[^`]+)`/gi;
569
+ var DEFAULT_CITATION_RE = (
570
+ // The inline-path form excludes newlines AND spaces on purpose: a path
571
+ // contains neither, and without the exclusions two failure families
572
+ // appear (both wild-caught). An UNBALANCED backtick lets the span swallow
573
+ // sentences until the next stray backtick; and even with balanced spans,
574
+ // the CLOSER of one legitimate span pairs with the OPENER of the next, so
575
+ // the prose BETWEEN two `code` spans — which mentions a path — became one
576
+ // giant unresolvable "citation" and honest work read as fabricated.
577
+ /\(source:\s*([^)\s]+)(?:\s+\[[^\]]*\])*\s*\)|\]\(\s*(?!#)([^)\s]+?)\s*\)|`([^`\s]*\/[^`\s]+)`/gi
578
+ );
579
+ function stripFencedBlocks(text) {
580
+ return text.replace(/^[ \t]*(`{3,}|~{3,})[^\n]*\n[\s\S]*?^[ \t]*\1[ \t]*$/gm, "").replace(/^[ \t]*(`{3,}|~{3,})[^\n]*\n[\s\S]*$/m, "");
581
+ }
540
582
  function extractCitations(text, re) {
541
583
  const flags = re.flags.includes("g") ? re.flags : `${re.flags}g`;
542
584
  const global = new RegExp(re.source, flags);
543
585
  const out = [];
544
- for (const m of text.matchAll(global)) {
586
+ for (const m of stripFencedBlocks(text).matchAll(global)) {
545
587
  const cap = m.slice(1).find((x) => x !== void 0) ?? m[0];
546
588
  if (cap) out.push(cap);
547
589
  }
@@ -550,8 +592,8 @@ function extractCitations(text, re) {
550
592
  function cleanCitation(raw) {
551
593
  return raw.trim().replace(/^[<'"`(]+/, "").replace(/[>'"`).,;:]+$/, "");
552
594
  }
553
- function normalizePath(p) {
554
- return p.trim().toLowerCase().replace(/^\.?\//, "").replace(/^workspace\//, "");
595
+ function normalizeForKnownMatch(p) {
596
+ return p.trim().replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\//, "").replace(/^workspace\//i, "").replace(/\/+$/, "").toLowerCase();
555
597
  }
556
598
  async function citationsResolve(ws, file, opts = {}) {
557
599
  const content = await ws.read(file);
@@ -574,38 +616,54 @@ async function citationsResolve(ws, file, opts = {}) {
574
616
  urls: []
575
617
  };
576
618
  }
577
- const cites = [...new Set(extractCitations(content, re).map(cleanCitation).filter(Boolean))];
619
+ const cites = [
620
+ ...new Set(
621
+ extractCitations(content, re).map(cleanCitation).filter(
622
+ (citation) => citation && (/^[a-z][\w+.-]*:\/\//i.test(citation) || !/[\\/]$/.test(citation))
623
+ )
624
+ )
625
+ ];
578
626
  const min = opts.minCitations ?? 1;
579
- const listing = new Set((await ws.list()).map(normalizePath));
627
+ const citedPathExists = createCitedPathChecker(ws);
580
628
  const corpus = opts.corpus ? new Set(opts.corpus.map((c) => c.toLowerCase())) : null;
629
+ const known = new Set((opts.knownPaths ?? []).map(normalizeForKnownMatch).filter(Boolean));
581
630
  const resolved = [];
582
631
  const unresolved = [];
583
632
  const urls = [];
633
+ const forgiven = [];
584
634
  for (const c of cites) {
585
635
  if (/^[a-z][\w+.-]*:\/\//i.test(c) || c.startsWith("mailto:")) {
586
636
  urls.push(c);
587
637
  if (corpus && !corpus.has(c.toLowerCase())) unresolved.push(c);
588
638
  continue;
589
639
  }
590
- if (listing.has(normalizePath(c)) || corpus?.has(c.toLowerCase())) resolved.push(c);
640
+ if (corpus?.has(c.toLowerCase()) || await citedPathExists(c)) resolved.push(c);
641
+ else if (known.has(normalizeForKnownMatch(c))) forgiven.push(c);
591
642
  else unresolved.push(c);
592
643
  }
593
- if (cites.length < min) {
644
+ if (cites.length - forgiven.length < min) {
594
645
  return {
595
646
  ok: false,
596
- detail: `${file} has ${cites.length} citation(s), need \u2265 ${min} \u2014 cite the source path/URL for each claim.`,
647
+ // Name the accepted FORMS, not just the rule: a model that wrote the
648
+ // right paths as plain prose ("File: src/pricing.js, line 8") reads
649
+ // "cite the source" as already satisfied and rewrites content instead
650
+ // of adding markup, looping to gate exhaustion (wild-caught:
651
+ // deepseek-v4 with a flawless diagnosis, three identical rejections).
652
+ detail: `${file} has ${cites.length - forgiven.length} recognizable citation(s), need \u2265 ${min}. Only these forms count as citations: a backticked path like \`src/file.js\`, a markdown link like [name](src/file.js), or (source: src/file.js). Plain prose paths are not counted \u2014 wrap each cited file path in backticks.`,
597
653
  resolved,
598
654
  unresolved,
599
- urls
655
+ urls,
656
+ ...forgiven.length > 0 ? { forgiven } : {}
600
657
  };
601
658
  }
602
659
  if (unresolved.length > 0) {
603
660
  return {
604
661
  ok: false,
605
- detail: `${file} cites ${unresolved.length} source(s) that do not exist: ${unresolved.slice(0, 5).join(", ")} \u2014 every cited path must resolve to a real file in the workspace${corpus ? "/corpus" : ""} (no fabricated citations).`,
662
+ detail: `${file} cites ${unresolved.length} source(s) that do not exist: ${unresolved.slice(0, 5).join(", ")}${unresolved.length > 5 ? ", \u2026" : ""} \u2014 every cited path must resolve to a real file in the workspace${corpus ? "/corpus" : ""} (no fabricated citations).`,
606
663
  resolved,
607
664
  unresolved,
608
- urls
665
+ urls,
666
+ ...forgiven.length > 0 ? { forgiven } : {}
609
667
  };
610
668
  }
611
669
  return {
@@ -613,7 +671,8 @@ async function citationsResolve(ws, file, opts = {}) {
613
671
  detail: `${file} cites ${resolved.length} resolvable source(s)${urls.length ? ` (+${urls.length} URL(s) not checked offline)` : ""}`,
614
672
  resolved,
615
673
  unresolved,
616
- urls
674
+ urls,
675
+ ...forgiven.length > 0 ? { forgiven } : {}
617
676
  };
618
677
  }
619
678
  function valuesSubsetOf(outputText, sourceTexts, spec) {
@@ -682,9 +741,6 @@ var VALID_SEVERITIES = /* @__PURE__ */ new Set(["critical", "high", "medium", "l
682
741
  function str(x) {
683
742
  return typeof x === "string" ? x.trim() : "";
684
743
  }
685
- function normalizePath2(p) {
686
- return p.trim().replace(/^`+|`+$/g, "").replace(/[:#].*$/, "").replace(/^\.\//, "").replace(/^\/+/, "").replace(/^workspace\//i, "").toLowerCase();
687
- }
688
744
  function escapeRe(s) {
689
745
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
690
746
  }
@@ -740,12 +796,12 @@ async function securityReport(ws, reportFile, opts = {}) {
740
796
  if (!arr) {
741
797
  return fail(`${findingsPath} must be a JSON array of findings (or { "findings": [...] }).`);
742
798
  }
743
- const listing = new Set((await ws.list()).map(normalizePath2));
799
+ const citedPathExists = createCitedPathChecker(ws);
744
800
  const problems = [];
745
801
  const fabricated = [];
746
802
  let critHigh = 0;
747
- arr.forEach((raw2, i) => {
748
- const f = raw2 ?? {};
803
+ for (const [i, rawFinding] of arr.entries()) {
804
+ const f = rawFinding ?? {};
749
805
  const file = str(f.file ?? f.path);
750
806
  const sev = str(f.severity).toLowerCase();
751
807
  const remediation = str(f.remediation ?? f.fix ?? f.recommendation);
@@ -753,13 +809,13 @@ async function securityReport(ws, reportFile, opts = {}) {
753
809
  const line = f.line ?? f.lineStart;
754
810
  const label = file || `#${i + 1}`;
755
811
  if (!file) problems.push(`finding #${i + 1} has no file`);
756
- else if (!listing.has(normalizePath2(file))) fabricated.push(file);
812
+ else if (!await citedPathExists(file)) fabricated.push(file);
757
813
  if (!VALID_SEVERITIES.has(sev)) problems.push(`${label} has an invalid severity "${sev}"`);
758
814
  if (!remediation) problems.push(`${label} has no remediation`);
759
815
  if (!title) problems.push(`${label} has no title/description`);
760
816
  if (typeof line !== "number") problems.push(`${label} is not pinned to a line`);
761
817
  if (sev === "critical" || sev === "high") critHigh++;
762
- });
818
+ }
763
819
  if (fabricated.length > 0) {
764
820
  return fail(
765
821
  `findings cite ${fabricated.length} file(s) that don't exist in the workspace: ${uniq(fabricated).slice(0, 5).join(", ")} \u2014 every finding must point at a real file:line (no fabricated citations).`,
@@ -818,6 +874,191 @@ async function securityReport(ws, reportFile, opts = {}) {
818
874
  };
819
875
  }
820
876
 
877
+ // ../core/src/checks/codebase-review-report.ts
878
+ var DEFAULT_SECTIONS2 = [
879
+ "Executive summary",
880
+ "Scorecard",
881
+ "Index coverage and method",
882
+ "Systemic themes",
883
+ "Findings",
884
+ "Quick wins",
885
+ "Strategic recommendations",
886
+ "Verified sound",
887
+ "Not assessed",
888
+ "Suggested deeper reviews"
889
+ ];
890
+ var VALID_SEVERITIES2 = /* @__PURE__ */ new Set(["critical", "high", "medium", "low", "info"]);
891
+ function str2(x) {
892
+ return typeof x === "string" ? x.trim() : "";
893
+ }
894
+ function escapeRe2(s) {
895
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
896
+ }
897
+ function hasSection2(content, name) {
898
+ return new RegExp(`^#{1,4}\\s+${escapeRe2(name)}\\b`, "im").test(content);
899
+ }
900
+ function sectionBody2(content, name) {
901
+ const m = new RegExp(`^(#{1,4})\\s+${escapeRe2(name)}\\b`, "im").exec(content);
902
+ if (!m) return "";
903
+ const level = m[1].length;
904
+ const start = m.index + m[0].length;
905
+ const rest = content.slice(start);
906
+ const next = new RegExp(`^#{1,${level}}\\s+\\S`, "m").exec(rest);
907
+ return next ? rest.slice(0, next.index) : rest;
908
+ }
909
+ function countThemeItems2(section) {
910
+ let n = 0;
911
+ for (const line of section.split(/\r?\n/)) {
912
+ if (/^\s*(#{3,4}\s+\S|[-*]\s+\S|\d+\.\s+\S|\*\*[^*]+\*\*)/.test(line)) n++;
913
+ }
914
+ return n;
915
+ }
916
+ function scorecardRows(section) {
917
+ const lines = section.split(/\r?\n/);
918
+ let headerAt = -1;
919
+ for (let i = 0; i < lines.length; i++) {
920
+ const line = lines[i];
921
+ if (/^\s*\|/.test(line) && /dimension/i.test(line) && /(grade|rating|score|health)/i.test(line)) {
922
+ headerAt = i;
923
+ break;
924
+ }
925
+ }
926
+ if (headerAt < 0) return -1;
927
+ let rows = 0;
928
+ for (let i = headerAt + 1; i < lines.length; i++) {
929
+ const line = lines[i];
930
+ if (!/^\s*\|/.test(line)) break;
931
+ if (/^\s*\|[\s:|-]+\|?\s*$/.test(line)) continue;
932
+ if (line.replace(/[|\s]/g, "").length === 0) continue;
933
+ rows++;
934
+ }
935
+ return rows;
936
+ }
937
+ function uniq2(xs) {
938
+ return [...new Set(xs)];
939
+ }
940
+ async function codebaseReviewReport(reports, workspace, reportFile, opts = {}) {
941
+ const fail = (detail, count = 0, fabricated2 = []) => ({
942
+ ok: false,
943
+ detail,
944
+ findingCount: count,
945
+ fabricated: fabricated2
946
+ });
947
+ const content = await reports.read(reportFile);
948
+ if (content === null) {
949
+ return fail(`${reportFile} not found \u2014 write the codebase review report before advancing.`);
950
+ }
951
+ const findingsPath = opts.findings ?? "codebase-review-findings.json";
952
+ const raw = await reports.read(findingsPath);
953
+ if (raw === null) {
954
+ return fail(
955
+ `${findingsPath} not found \u2014 emit a machine-readable findings JSON alongside the report.`
956
+ );
957
+ }
958
+ let parsed;
959
+ try {
960
+ parsed = JSON.parse(raw);
961
+ } catch (e) {
962
+ return fail(
963
+ `${findingsPath} is not valid JSON (${e instanceof Error ? e.message : "parse error"}). Emit a JSON array of findings.`
964
+ );
965
+ }
966
+ const arr = Array.isArray(parsed) ? parsed : parsed && typeof parsed === "object" && Array.isArray(parsed.findings) ? parsed.findings : null;
967
+ if (!arr) {
968
+ return fail(`${findingsPath} must be a JSON array of findings (or { "findings": [...] }).`);
969
+ }
970
+ const citedPathExists = createCitedPathChecker(workspace);
971
+ const problems = [];
972
+ const fabricated = [];
973
+ let critHigh = 0;
974
+ for (const [i, rawFinding] of arr.entries()) {
975
+ const f = rawFinding ?? {};
976
+ const file = str2(f.file ?? f.path);
977
+ const sev = str2(f.severity).toLowerCase();
978
+ const remediation = str2(f.remediation ?? f.fix ?? f.recommendation);
979
+ const title = str2(f.title ?? f.description ?? f.summary);
980
+ const line = f.line ?? f.lineStart;
981
+ const label = file || `#${i + 1}`;
982
+ if (!file) problems.push(`finding #${i + 1} has no file`);
983
+ else if (!await citedPathExists(file)) fabricated.push(file);
984
+ if (!VALID_SEVERITIES2.has(sev)) problems.push(`${label} has an invalid severity "${sev}"`);
985
+ if (!remediation) problems.push(`${label} has no remediation`);
986
+ if (!title) problems.push(`${label} has no title/description`);
987
+ if ((sev === "critical" || sev === "high") && typeof line !== "number") {
988
+ problems.push(`${label} is ${sev} but not pinned to a line`);
989
+ }
990
+ if (sev === "critical" || sev === "high") critHigh++;
991
+ }
992
+ if (fabricated.length > 0) {
993
+ return fail(
994
+ `findings cite ${fabricated.length} file(s) that don't exist in the workspace: ${uniq2(fabricated).slice(0, 5).join(", ")} \u2014 every finding must point at a real file (no fabricated citations).`,
995
+ arr.length,
996
+ uniq2(fabricated)
997
+ );
998
+ }
999
+ if (problems.length > 0) {
1000
+ return fail(
1001
+ `${problems.length} finding(s) are incomplete: ${problems.slice(0, 4).join("; ")} \u2014 every finding needs a real file, a severity, a title, and a concrete remediation (critical/high pinned to a line).`,
1002
+ arr.length
1003
+ );
1004
+ }
1005
+ const requiredSections = opts.requiredSections ?? DEFAULT_SECTIONS2;
1006
+ const missing = requiredSections.filter((s) => !hasSection2(content, s));
1007
+ if (missing.length > 0) {
1008
+ return fail(
1009
+ `the report is missing required section(s): ${missing.map((s) => `## ${s}`).join(", ")}.`,
1010
+ arr.length
1011
+ );
1012
+ }
1013
+ const minRows = opts.minScorecardRows ?? 4;
1014
+ const rows = scorecardRows(sectionBody2(content, "Scorecard"));
1015
+ if (rows < 0) {
1016
+ return fail(
1017
+ 'the "Scorecard" section has no per-dimension table \u2014 add a markdown table with Dimension and Grade columns.',
1018
+ arr.length
1019
+ );
1020
+ }
1021
+ if (rows < minRows) {
1022
+ return fail(
1023
+ `the Scorecard table has ${rows} dimension row(s); grade at least ${minRows} dimensions (e.g. architecture, code quality, hygiene, security, tests).`,
1024
+ arr.length
1025
+ );
1026
+ }
1027
+ const themeThreshold = opts.themeThreshold ?? 3;
1028
+ const minThemes = opts.minThemes ?? 2;
1029
+ if (arr.length >= themeThreshold) {
1030
+ const themes = sectionBody2(content, "Systemic themes");
1031
+ const items = countThemeItems2(themes);
1032
+ if (items < minThemes) {
1033
+ return fail(
1034
+ `the "Systemic themes" section lists ${items} theme(s); a review with ${arr.length} findings needs \u2265 ${minThemes}, each naming a root cause and its blast radius.`,
1035
+ arr.length
1036
+ );
1037
+ }
1038
+ if (!/root[\s-]?cause/i.test(themes) || !/(blast[\s-]?radius|impact|scope|reach)/i.test(themes)) {
1039
+ return fail(
1040
+ `the "Systemic themes" section must analyze each theme's root cause and blast radius \u2014 that analysis is absent.`,
1041
+ arr.length
1042
+ );
1043
+ }
1044
+ }
1045
+ const summary = sectionBody2(content, "Executive summary").toLowerCase();
1046
+ if (critHigh > 0 && /(excellent (health|shape|condition)|no (significant |material )?(issues|problems|findings)|nothing to fix|clean bill of health)/.test(
1047
+ summary
1048
+ ) && !/(critical|high|however|but |concern|risk)/.test(summary)) {
1049
+ return fail(
1050
+ `the executive summary reads clean but there ${critHigh === 1 ? "is" : "are"} ${critHigh} critical/high finding(s) \u2014 the summary must reflect the open severity.`,
1051
+ arr.length
1052
+ );
1053
+ }
1054
+ return {
1055
+ ok: true,
1056
+ detail: `codebase review OK: ${arr.length} finding(s), all cite real files, ${rows} scorecard dimension(s), required sections present${arr.length >= themeThreshold ? ", systemic themes analyzed" : ""}.`,
1057
+ findingCount: arr.length,
1058
+ fabricated: []
1059
+ };
1060
+ }
1061
+
821
1062
  // ../core/src/checks/records.ts
822
1063
  var ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
823
1064
  function isRealIsoDate(value) {
@@ -1804,6 +2045,172 @@ function findCycle(rows) {
1804
2045
  return cycle;
1805
2046
  }
1806
2047
 
2048
+ // ../core/src/checks/corpus-coverage.ts
2049
+ function failure(detail) {
2050
+ return {
2051
+ ok: false,
2052
+ detail,
2053
+ expectedBatches: 0,
2054
+ mergedBatches: 0,
2055
+ missingBatches: []
2056
+ };
2057
+ }
2058
+ function parseStringArray(value) {
2059
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string" || item === "")) {
2060
+ return null;
2061
+ }
2062
+ return value;
2063
+ }
2064
+ function firstMismatch(actual, expected) {
2065
+ if (actual.length !== expected.length) return Math.min(actual.length, expected.length);
2066
+ for (let index = 0; index < expected.length; index += 1) {
2067
+ if (actual[index] !== expected[index]) return index;
2068
+ }
2069
+ return null;
2070
+ }
2071
+ function mergeCorpusCoverageShards(batchesContent, shards, opts = {}) {
2072
+ const batchesFile = opts.batchesFile ?? "batches.json";
2073
+ let batchesRaw;
2074
+ try {
2075
+ batchesRaw = JSON.parse(batchesContent);
2076
+ } catch (err) {
2077
+ return failure(
2078
+ `${batchesFile} is not valid JSON (${err instanceof Error ? err.message : String(err)}).`
2079
+ );
2080
+ }
2081
+ if (!Array.isArray(batchesRaw) || batchesRaw.length === 0) {
2082
+ return failure(`${batchesFile} must contain a non-empty batch array.`);
2083
+ }
2084
+ const batches = [];
2085
+ const allPaths = /* @__PURE__ */ new Set();
2086
+ const allRecords = /* @__PURE__ */ new Set();
2087
+ for (let index = 0; index < batchesRaw.length; index += 1) {
2088
+ const raw = batchesRaw[index];
2089
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
2090
+ return failure(`${batchesFile}[${index}] must be an object.`);
2091
+ }
2092
+ const fields = raw;
2093
+ const batchNumber = fields.batchNumber;
2094
+ const paths = parseStringArray(fields.paths);
2095
+ const records = parseStringArray(fields.records);
2096
+ if (!Number.isInteger(batchNumber) || batchNumber !== index + 1) {
2097
+ return failure(
2098
+ `${batchesFile}[${index}] must have batchNumber ${index + 1}; found ${JSON.stringify(batchNumber)}.`
2099
+ );
2100
+ }
2101
+ if (!paths || paths.length === 0) {
2102
+ return failure(`${batchesFile}[${index}].paths must be a non-empty string array.`);
2103
+ }
2104
+ if (!records || records.length !== paths.length) {
2105
+ return failure(
2106
+ `${batchesFile}[${index}].records must contain one exact record path per changed path.`
2107
+ );
2108
+ }
2109
+ for (const path of paths) {
2110
+ if (allPaths.has(path))
2111
+ return failure(`${batchesFile} assigns '${path}' to multiple batches.`);
2112
+ allPaths.add(path);
2113
+ }
2114
+ for (const record of records) {
2115
+ if (allRecords.has(record)) {
2116
+ return failure(`${batchesFile} assigns record '${record}' to multiple batches.`);
2117
+ }
2118
+ allRecords.add(record);
2119
+ }
2120
+ batches.push({ batchNumber, paths, records });
2121
+ }
2122
+ const shardByBatch = /* @__PURE__ */ new Map();
2123
+ for (const shard of shards) {
2124
+ const normalized = shard.path.replace(/\\/g, "/");
2125
+ const match = /(?:^|\/)coverage-(\d+)\.json$/.exec(normalized);
2126
+ if (!match) continue;
2127
+ const batchNumber = Number(match[1]);
2128
+ if (batchNumber < 1 || batchNumber > batches.length) {
2129
+ return failure(
2130
+ `${shard.path} claims batch ${batchNumber}, but ${batchesFile} has batches 1-${batches.length}.`
2131
+ );
2132
+ }
2133
+ if (shardByBatch.has(batchNumber)) {
2134
+ return failure(`More than one coverage shard claims batch ${batchNumber}.`);
2135
+ }
2136
+ shardByBatch.set(batchNumber, shard);
2137
+ }
2138
+ const reviewedFiles = [];
2139
+ const reviewedRecords = [];
2140
+ const sources = [];
2141
+ for (const batch of batches) {
2142
+ const shard = shardByBatch.get(batch.batchNumber);
2143
+ if (!shard) continue;
2144
+ let parsed;
2145
+ try {
2146
+ parsed = JSON.parse(shard.content);
2147
+ } catch (err) {
2148
+ return failure(
2149
+ `${shard.path} is not valid JSON (${err instanceof Error ? err.message : String(err)}).`
2150
+ );
2151
+ }
2152
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
2153
+ return failure(`${shard.path} must contain a JSON object.`);
2154
+ }
2155
+ const fields = parsed;
2156
+ if (fields.batchNumber !== batch.batchNumber) {
2157
+ return failure(
2158
+ `${shard.path} must declare batchNumber ${batch.batchNumber}; found ${JSON.stringify(fields.batchNumber)}.`
2159
+ );
2160
+ }
2161
+ const files = parseStringArray(fields.reviewedFiles);
2162
+ const records = parseStringArray(fields.reviewedRecords);
2163
+ if (!files || !records) {
2164
+ return failure(`${shard.path} must contain reviewedFiles and reviewedRecords string arrays.`);
2165
+ }
2166
+ const fileMismatch = firstMismatch(files, batch.paths);
2167
+ if (fileMismatch !== null) {
2168
+ return failure(
2169
+ `${shard.path} does not exactly match batch ${batch.batchNumber}'s changed paths at position ${fileMismatch + 1}; coverage may only come from that batch's shard.`
2170
+ );
2171
+ }
2172
+ const recordMismatch = firstMismatch(records, batch.records);
2173
+ if (recordMismatch !== null) {
2174
+ return failure(
2175
+ `${shard.path} does not exactly match batch ${batch.batchNumber}'s artifact records at position ${recordMismatch + 1}.`
2176
+ );
2177
+ }
2178
+ reviewedFiles.push(...files);
2179
+ reviewedRecords.push(...records);
2180
+ sources.push({
2181
+ batchNumber: batch.batchNumber,
2182
+ shard: shard.path.replace(/\\/g, "/")
2183
+ });
2184
+ }
2185
+ const missingBatches = batches.map((batch) => batch.batchNumber).filter((batchNumber) => !shardByBatch.has(batchNumber));
2186
+ if (opts.requireComplete && missingBatches.length > 0) {
2187
+ return {
2188
+ ok: false,
2189
+ detail: `Coverage shards are still missing for batch${missingBatches.length === 1 ? "" : "es"} ${missingBatches.join(", ")}.`,
2190
+ expectedBatches: batches.length,
2191
+ mergedBatches: sources.length,
2192
+ missingBatches
2193
+ };
2194
+ }
2195
+ const ledger = {
2196
+ ...opts.pullRequest !== void 0 ? { pullRequest: opts.pullRequest } : {},
2197
+ reviewedFiles,
2198
+ reviewedRecords,
2199
+ sources,
2200
+ complete: missingBatches.length === 0
2201
+ };
2202
+ return {
2203
+ ok: true,
2204
+ detail: missingBatches.length === 0 ? `Coverage is provenance-complete across all ${batches.length} batches and ${reviewedFiles.length} changed paths.` : `Merged ${sources.length}/${batches.length} coverage shards; waiting for batches ${missingBatches.join(", ")}.`,
2205
+ ledger,
2206
+ content: `${JSON.stringify(ledger, null, 2)}
2207
+ `,
2208
+ expectedBatches: batches.length,
2209
+ mergedBatches: sources.length,
2210
+ missingBatches
2211
+ };
2212
+ }
2213
+
1807
2214
  // src/checks.ts
1808
2215
  function gateResult(ok, detail) {
1809
2216
  return ok ? { decision: "approve", message: detail } : { decision: "reject", message: detail };
@@ -1826,8 +2233,12 @@ export {
1826
2233
  MIN_JUDGE_EVIDENCE_SUBSTRING,
1827
2234
  buildJudgePrompt,
1828
2235
  citationsResolve,
2236
+ citedPathKey,
2237
+ cleanCitedPath,
2238
+ codebaseReviewReport,
1829
2239
  containsPattern,
1830
2240
  countDistinctMatches,
2241
+ createCitedPathChecker,
1831
2242
  cssMinBytes,
1832
2243
  csvShape,
1833
2244
  dataTableSniff,
@@ -1850,6 +2261,7 @@ export {
1850
2261
  jsonPathEquals,
1851
2262
  jsonValid,
1852
2263
  markdownHeadingsMatch,
2264
+ mergeCorpusCoverageShards,
1853
2265
  namedEntitiesConsistent,
1854
2266
  normalizeDigitGroups,
1855
2267
  notContainsPattern,
package/dist/index.d.ts CHANGED
@@ -1,5 +1,96 @@
1
- import { S as ScriptMeta, I as InferInput, a as InferOutput, b as ScriptInputs, c as ScriptOutputs } from './types-cYfcp6_8.js';
2
- export { G as GateScriptResult, d as ScriptArrayOutput, e as ScriptBooleanInput, f as ScriptBooleanOutput, g as ScriptCapability, h as ScriptChoiceInput, i as ScriptChoiceOption, j as ScriptInputField, k as ScriptJsonInput, l as ScriptJsonOutput, m as ScriptNumberInput, n as ScriptNumberOutput, o as ScriptObjectOutput, p as ScriptOutputField, q as ScriptRefInput, r as ScriptStringInput, s as ScriptStringOutput } from './types-cYfcp6_8.js';
1
+ import { S as ScriptMeta, I as InferInput, a as InferOutput, b as ScriptInputs, c as ScriptOutputs } from './types-DCBb1goX.js';
2
+ export { G as GateScriptResult, d as ScriptArrayOutput, e as ScriptBooleanInput, f as ScriptBooleanOutput, g as ScriptCapability, h as ScriptChoiceInput, i as ScriptChoiceOption, j as ScriptInputField, k as ScriptJsonInput, l as ScriptJsonOutput, m as ScriptNumberInput, n as ScriptNumberOutput, o as ScriptObjectOutput, p as ScriptOutputField, q as ScriptRefInput, r as ScriptStringInput, s as ScriptStringOutput } from './types-DCBb1goX.js';
3
+
4
+ /**
5
+ * Script-SDK wire types for workspace-index status and readiness.
6
+ *
7
+ * These deliberately live in the self-contained SDK instead of importing
8
+ * the Zod-inferred core types. Script sandboxes and Monaco receive only the
9
+ * SDK declaration files, so an external type import would make that surface
10
+ * depend on another package. The parity test beside this file keeps these
11
+ * structural copies aligned with the canonical core schemas.
12
+ */
13
+ interface WorkspaceIndexStatus {
14
+ state: 'fresh' | 'stale' | 'indexing' | 'never' | 'disabled';
15
+ embeddings?: {
16
+ status: 'cold' | 'warming' | 'ready' | 'disabled' | 'unavailable';
17
+ reason?: string;
18
+ };
19
+ meta?: {
20
+ version: number;
21
+ scannedAt: string;
22
+ root: string;
23
+ durationMs: number;
24
+ fileCount: number;
25
+ commandCount: number;
26
+ shapeCount?: number;
27
+ };
28
+ aiScanPending?: boolean;
29
+ aiDrive?: 'background' | 'full';
30
+ enrichment?: {
31
+ eligible: number;
32
+ summarized: number;
33
+ embedded: number;
34
+ searchReady?: number;
35
+ pending: number;
36
+ skipped?: number;
37
+ skippedFiles?: Array<{
38
+ path: string;
39
+ attempts: number;
40
+ reason?: string;
41
+ }>;
42
+ shadowsPending?: number;
43
+ embedOnlyPending?: number;
44
+ embedModel?: string;
45
+ vectorsAvailable?: boolean;
46
+ reviews?: {
47
+ eligible: number;
48
+ reviewed: number;
49
+ stale: number;
50
+ pending: number;
51
+ };
52
+ };
53
+ }
54
+ interface IndexReadinessReport {
55
+ version: 1;
56
+ projectId: string;
57
+ generatedAt: string;
58
+ indexingEnabled: boolean;
59
+ staticState: 'fresh' | 'stale' | 'indexing' | 'never' | 'disabled';
60
+ fileCount?: number;
61
+ scannedAt?: string;
62
+ search: {
63
+ ready: boolean;
64
+ eligible?: number;
65
+ embedded?: number;
66
+ pendingEmbedOnly?: number;
67
+ embedModel?: string;
68
+ vectorsAvailable?: boolean;
69
+ };
70
+ aiTier: {
71
+ staffed: boolean;
72
+ paused: boolean;
73
+ achievable: boolean;
74
+ summariesEligible?: number;
75
+ summarized?: number;
76
+ summariesPending?: number;
77
+ shadowsPending?: number;
78
+ skipped?: number;
79
+ reviews?: {
80
+ eligible: number;
81
+ reviewed: number;
82
+ stale: number;
83
+ pending: number;
84
+ };
85
+ };
86
+ wait: {
87
+ budgetMs: number;
88
+ waitedMs: number;
89
+ drained: boolean;
90
+ driveStillRunning: boolean;
91
+ };
92
+ notes: string[];
93
+ }
3
94
 
4
95
  /**
5
96
  * `@bendyline/gezel-sdk` — imported by TypeScript scripts running in the
@@ -556,6 +647,33 @@ interface GezelSDK<TInput = Record<string, unknown>> {
556
647
  */
557
648
  authed(url: string, opts: AuthedHttpOpts): Promise<AuthedHttpResponse>;
558
649
  };
650
+ /**
651
+ * The project's **workspace index** — status of the static scan and the
652
+ * AI tiers, plus a bounded "make it fresh" ensure. Requires `index.read`
653
+ * for {@link GezelSDK.index.status | status} and `index.refresh` for
654
+ * {@link GezelSDK.index.ensureFresh | ensureFresh}.
655
+ */
656
+ index: {
657
+ /** Current index status (static state + enrichment/review coverage). */
658
+ status(): Promise<WorkspaceIndexStatus>;
659
+ /**
660
+ * Make the index as fresh as it can get within an awake-time budget:
661
+ * awaited static re-scan, then an AI-tier catch-up drive raced against
662
+ * the budget. Always resolves with an honest readiness report — on a
663
+ * crew with no Boekwachter the AI tiers are reported unachievable
664
+ * rather than awaited, and an expired budget leaves the drive running
665
+ * in the background.
666
+ *
667
+ * @param opts.waitBudgetMs - Awake-time wait budget (default 180s,
668
+ * capped at 240s to stay inside the script run timeout).
669
+ * @param opts.reviews - Also wait for the per-file AI review tier
670
+ * (default true).
671
+ */
672
+ ensureFresh(opts?: {
673
+ waitBudgetMs?: number;
674
+ reviews?: boolean;
675
+ }): Promise<IndexReadinessReport>;
676
+ };
559
677
  /**
560
678
  * **Nested scripts** — run another script in the same project and get
561
679
  * its stamped output back. No capability is required to call, but the
@@ -592,4 +710,4 @@ declare const gezel: GezelSDK;
592
710
  type InferredInput<M> = M extends ScriptMeta<infer I, infer _O> ? InferInput<I> : never;
593
711
  type InferredOutput<M> = M extends ScriptMeta<infer _I, infer O> ? InferOutput<O> : never;
594
712
 
595
- export { type ArtifactEntry, type AuthedHttpOpts, type AuthedHttpResponse, type DocumentEntry, type FsEntry, type FsStat, type GezelSDK, type HttpRequestOpts, type HttpResponse, InferInput, InferOutput, type InferredInput, type InferredOutput, type OneShotOpts, ScriptInputs, ScriptMeta, ScriptOutputs, type ScriptResult, type TaskNote, type TaskNoteAuthor, type TaskStep, defineScript, gezel };
713
+ export { type ArtifactEntry, type AuthedHttpOpts, type AuthedHttpResponse, type DocumentEntry, type FsEntry, type FsStat, type GezelSDK, type HttpRequestOpts, type HttpResponse, type IndexReadinessReport, InferInput, InferOutput, type InferredInput, type InferredOutput, type OneShotOpts, ScriptInputs, ScriptMeta, ScriptOutputs, type ScriptResult, type TaskNote, type TaskNoteAuthor, type TaskStep, type WorkspaceIndexStatus, defineScript, gezel };
package/dist/index.js CHANGED
@@ -8,6 +8,11 @@ var DEFAULT_INIT = {
8
8
  engagementMode: "off",
9
9
  engagementFlags: { llmAllowed: false }
10
10
  };
11
+ var WRITE_BACKPRESSURE_RETRY_MS = 1;
12
+ var WRITE_BACKPRESSURE_TIMEOUT_MS = 3e4;
13
+ var WRITE_BACKPRESSURE_SIGNAL = new Int32Array(
14
+ new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT)
15
+ );
11
16
  function readInitSync() {
12
17
  if (process.env.GEZEL_SCRIPT_RUNTIME !== "1") {
13
18
  return DEFAULT_INIT;
@@ -132,8 +137,27 @@ var RpcClient = class {
132
137
  function writeFrame(payload) {
133
138
  const buf = Buffer.from(payload, "utf8");
134
139
  let offset = 0;
140
+ const startedAt = Date.now();
135
141
  while (offset < buf.length) {
136
- offset += writeSync(3, buf, offset, buf.length - offset);
142
+ try {
143
+ const written = writeSync(3, buf, offset, buf.length - offset);
144
+ if (written > 0) {
145
+ offset += written;
146
+ continue;
147
+ }
148
+ } catch (error) {
149
+ const code = error.code;
150
+ if (code === "EINTR") continue;
151
+ if (code !== "EAGAIN" && code !== "EWOULDBLOCK") throw error;
152
+ }
153
+ if (Date.now() - startedAt >= WRITE_BACKPRESSURE_TIMEOUT_MS) {
154
+ const error = new Error(
155
+ `script RPC channel remained backpressured after writing ${offset} of ${buf.length} bytes`
156
+ );
157
+ error.code = "ETIMEDOUT";
158
+ throw error;
159
+ }
160
+ Atomics.wait(WRITE_BACKPRESSURE_SIGNAL, 0, 0, WRITE_BACKPRESSURE_RETRY_MS);
137
161
  }
138
162
  }
139
163
 
@@ -208,6 +232,13 @@ var gezel = {
208
232
  mcp: {
209
233
  call: (tool, args) => rpc.call("mcp.call", { tool, args })
210
234
  },
235
+ index: {
236
+ status: () => rpc.call("index.status"),
237
+ ensureFresh: (opts) => rpc.call("index.ensureFresh", {
238
+ ...opts?.waitBudgetMs !== void 0 ? { waitBudgetMs: opts.waitBudgetMs } : {},
239
+ ...opts?.reviews !== void 0 ? { reviews: opts.reviews } : {}
240
+ })
241
+ },
211
242
  http: {
212
243
  request: (url, opts) => rpc.call("http.request", {
213
244
  url,
package/dist/page.d.ts CHANGED
@@ -19,6 +19,11 @@
19
19
  * - `browser` — an "Open in browser" tab with no embedding parent. Data
20
20
  * reads fall back to same-origin capability fetches; `tools.invoke`
21
21
  * rejects with code `'unavailable'`; `refresh()` reloads the page.
22
+ * - `serve` — the page is a shareable mini-site served by the daemon's
23
+ * app-serve head (`gezel app serve`). Nothing degrades: `tools.invoke`
24
+ * and `data.*` travel as same-origin fetches against the head API under
25
+ * the visitor's cookie; `data.watch` polls; `refresh()` reloads. The
26
+ * page still never sees a credential.
22
27
  * - `demo` — a raw file opened outside gezel entirely. The real shim is
23
28
  * never present there; gilde ships a paste-in stub that defines
24
29
  * `window.gezel` only when the real one is absent.
@@ -35,7 +40,7 @@ declare const GEZEL_PAGE_API_VERSION: 1;
35
40
  */
36
41
  type GezelPageReadSource = 'workspace' | 'artifacts';
37
42
  /** How the page is being viewed; see the module doc for what each mode degrades. */
38
- type GezelPageMode = 'embedded' | 'browser' | 'demo';
43
+ type GezelPageMode = 'embedded' | 'browser' | 'serve' | 'demo';
39
44
  /** Error codes a rejected page-API call carries (`GezelPageError.code`). */
40
45
  type GezelPageErrorCode = 'not-allowed' | 'invalid-input' | 'script-error' | 'timeout' | 'unavailable' | 'rate-limited';
41
46
  /**
@@ -21,7 +21,7 @@
21
21
  * `gezel.http.authed` additionally needs a `credential:<name>` entry
22
22
  * (declared as a raw string in `requires`) naming each credential it uses.
23
23
  */
24
- type ScriptCapability = 'llm' | 'network' | 'workspace.read' | 'workspace.write' | 'artifacts.read' | 'artifacts.write' | 'documents.read' | 'documents.write' | 'tasks.read' | 'tasks.write' | 'memory.read' | 'memory.write';
24
+ type ScriptCapability = 'llm' | 'network' | 'workspace.read' | 'workspace.write' | 'artifacts.read' | 'artifacts.write' | 'documents.read' | 'documents.write' | 'tasks.read' | 'tasks.write' | 'memory.read' | 'memory.write' | 'index.read' | 'index.refresh';
25
25
  /** A free-text string input field. */
26
26
  interface ScriptStringInput {
27
27
  type: 'string';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bendyline/gezel-sdk",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "description": "SDK imported by scripts running in the Gezel sandbox. Exposes the `gezel` object and `defineScript` helper.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -59,7 +59,7 @@
59
59
  "!dist/**/*.map"
60
60
  ],
61
61
  "devDependencies": {
62
- "@bendyline/gezel": "1.0.6",
62
+ "@bendyline/gezel": "1.0.7",
63
63
  "tsup": "^8.5.1",
64
64
  "typescript": "^6.0.3",
65
65
  "vitest": "^4.1.10"