Files
IUMBTEMS/packages/core/src/harvest/prior-art.ts
T
BillyOutlastandClaude Opus 5.5 020ba87f08 Release 1.6.0: 1.0 cutover, phases 0–5 and the review remediation (#151)
- 1.0 cutover: the rewrite monorepo lands on main. 1.1.5 through 1.5.0
  never published on their own and ship here as 1.6.0, in lockstep for
  es-core, es-cli and epistemic-swarm (ADR 0001).
- Phase 0 (#140): fail-closed seat models, one CLI grammar for the
  human-only rule, sealed source-cache readers, pinned MCP identity, a
  registry-generated permission probe suite, one package list.
- Phase 1 (#141): research backends with failover, the Scraper-Swarm
  gateway, callable brainstorm/harvest, research-only runs, the
  deep-research coordinator, Markdown/HTML dossiers, domain-pack tiers.
- Phase 2 (#142): ADR 0002 and the shared approveStage service, masked
  TUI approvals, the headless runner (JSONL events, metrics, cancel).
- Phase 3 (#144): the human-only es-fleet daemon, DAG scheduler, worktree
  coordinator with a gated integration branch, telemetry bus and watch.
- Phase 4 (#146): the loopback web control plane (SolidJS, ADR 0003):
  dashboard, browser approvals, config preview, evidence explorer.
- Phase 5 (#149): telemetry harvest, distillation into proposals, the
  self-dogfood preset.
- Cutover review remediation (#152): CLI approvals bound to the preview
  with attempt limiting (#143), human-only `factory run` and fleet token,
  DNS-resolving SSRF guard, value-level secret scrubbing, fleet gc and
  allocation races, CodeQL #84, boundary-gate gaps, one REPORT.md writer
  per run mode.

Closes #96.
Closes #97.
Closes #98.
Closes #99.
Closes #100.
Closes #101.
Closes #103.
Closes #106.
Closes #107.
Closes #108.
Closes #109.
Closes #110.
Closes #111.
Closes #112.
Closes #113.
Closes #114.
Closes #117.
Closes #118.
Closes #119.
Closes #120.
Closes #122.
Closes #123.
Closes #124.
Closes #125.
Closes #126.
Closes #127.
Closes #129.
Closes #130.
Closes #131.
Closes #132.
Closes #133.
Closes #135.
Closes #136.
Closes #147.
Closes #148.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-09 21:33:44 -04:00

46 lines
2.1 KiB
TypeScript

// The record of prior-art searches: what es_harvest_prior_art actually found.
// It lives under .factory/runtime (a control dir no agent may write), so a
// brainstorm can only cite prior art that a search returned. Records validate
// on write and on read (#109): a corrupt record is refused, never trusted.
import path from "node:path"
import { factoryLayout } from "../layout.ts"
import { type PriorArtSearch, PriorArtSearchSchema } from "../schema/harvest.ts"
import { readJson, writeJson } from "../util/fs.ts"
export type { PriorArtSearch }
const recordFile = (root: string) => path.join(factoryLayout(root).runtime, "harvest", "prior-art.json")
export async function readPriorArt(root: string): Promise<PriorArtSearch[]> {
const raw = (await readJson<unknown[]>(recordFile(root)).catch(() => undefined)) ?? []
// Corrupt entries are dropped, never trusted; writers are refused outright.
return raw.flatMap((entry) => {
const parsed = PriorArtSearchSchema.safeParse(entry)
return parsed.success ? [parsed.data] : []
})
}
/** Add searches to the record (latest search per run+idea wins). */
export async function recordPriorArt(root: string, searches: readonly PriorArtSearch[]): Promise<void> {
for (const search of searches) {
const parsed = PriorArtSearchSchema.safeParse(search)
if (!parsed.success) throw new Error(`invalid prior-art search: ${parsed.error.message}`)
}
const keys = new Set(searches.map((search) => `${search.run ?? ""}::${search.idea}`))
const kept = (await readPriorArt(root)).filter((search) => !keys.has(`${search.run ?? ""}::${search.idea}`))
await writeJson(recordFile(root), [...kept, ...searches])
}
/**
* URLs any recorded, successful search returned. A brainstorm run sees its
* own searches plus legacy global records (written before runs); other runs'
* searches do not satisfy it.
*/
export async function priorArtUrls(root: string, run?: string): Promise<Set<string>> {
return new Set(
(await readPriorArt(root))
.filter((search) => search.status === "ok" && (search.run === undefined || search.run === run))
.flatMap((search) => search.results.map((result) => result.url)),
)
}