mirror of
https://github.com/Heretek-AI/IUMBTEMS.git
synced 2026-11-07 10:56:00 -10:00
- 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]>
111 lines
6.0 KiB
TypeScript
111 lines
6.0 KiB
TypeScript
// The plan's minimal-dependency rule, enforced on this repository: core's
|
|
// runtime dependencies must stay within the allowlist, and anything else needs
|
|
// a recorded justification in .factory/dependencies.json. Run by `bun run check`
|
|
// in CI (AGENTS.md: the allowlist is "enforced by our own gate"). It also holds
|
|
// the package boundaries from docs/adr/0001-monorepo.md.
|
|
import { readdir, readFile } from "node:fs/promises"
|
|
import path from "node:path"
|
|
import { CORE_DEPENDENCY_ALLOWLIST, checkDependencies } from "../packages/core/src/gates/minimal-deps.ts"
|
|
import { importsOf } from "./imports.ts"
|
|
import { workspacePackages } from "./packages.ts"
|
|
|
|
const root = path.resolve(import.meta.dir, "..")
|
|
const pkg = JSON.parse(await readFile(path.join(root, "packages/core/package.json"), "utf8")) as {
|
|
dependencies?: Record<string, string>
|
|
}
|
|
const justifications = await readFile(path.join(root, ".factory/dependencies.json"), "utf8")
|
|
.then((text) => JSON.parse(text) as Record<string, string>)
|
|
.catch(() => ({}) as Record<string, string>)
|
|
|
|
const dependencies = pkg.dependencies ?? {}
|
|
const check = checkDependencies(dependencies, justifications)
|
|
if (!check.ok) {
|
|
console.error("Core dependency gate failed:")
|
|
for (const violation of check.violations) console.error(` - ${violation.name}: ${violation.reason}`)
|
|
console.error(`Allowlist: ${CORE_DEPENDENCY_ALLOWLIST.join(", ")}`)
|
|
console.error("Add the dependency to .factory/dependencies.json with a reason, or remove it.")
|
|
process.exit(1)
|
|
}
|
|
|
|
// Package boundaries (docs/adr/0001-monorepo.md): core stays harness-neutral,
|
|
// so it may not import the CLI or the plugin, and the CLI may not import the
|
|
// plugin. Both bare specifiers and relative paths into another package count.
|
|
// Every workspace package needs an entry: a new surface fails the gate until
|
|
// it declares which packages it may not import, and core and cli may never
|
|
// import a new surface (their entries must ban it).
|
|
const BOUNDARIES = [
|
|
{ pkg: "core", banned: ["cli", "opencode", "testkit", "fleet", "web"] },
|
|
{ pkg: "cli", banned: ["opencode", "testkit", "fleet", "web"] },
|
|
// The plugin never imports the fleet daemon or the web UI (AGENTS.md: core,
|
|
// CLI and plugin never import fleet; nothing imports web).
|
|
{ pkg: "opencode", banned: ["fleet", "web"] },
|
|
// The test harness never imports the fleet daemon or the web UI either.
|
|
{ pkg: "testkit", banned: ["fleet", "web"] },
|
|
// Fleet depends on core only (#122): never on the CLI or the plugin.
|
|
// It serves the built web UI as static files (#129), never as an import.
|
|
{ pkg: "fleet", banned: ["cli", "opencode", "web"] },
|
|
// Web is a leaf surface (#129): nothing imports it, and it imports
|
|
// nothing at runtime except the fleet API's schema types (type-only).
|
|
// `fleet` stays banned with a type-only exemption below: a top-level
|
|
// `import type` (or `export type`) is erased, so only a value import of
|
|
// fleet from web fails the gate. Inline `{ type A }` members are not
|
|
// erased under verbatimModuleSyntax (scripts/imports.ts).
|
|
// The test-only exception lives in `test/`, outside this gate's `src/`
|
|
// scan: packages/web/test/bus.contract.test.ts runtime-imports the fleet
|
|
// server harness for the WS contract test.
|
|
{ pkg: "web", banned: ["core", "cli", "opencode", "testkit", "fleet"] },
|
|
] as const
|
|
|
|
const crossings: string[] = []
|
|
|
|
// The workspace is the universe: a package with no BOUNDARIES entry fails
|
|
// until it declares its bans, and core/cli entries must ban every other
|
|
// surface, so a new package cannot be silently imported by (or silently
|
|
// importable from) the harness-neutral layers. Bare-specifier names come
|
|
// from the same list (AGENTS.md one-list rule, #102): no second hand map
|
|
// that could miss a surface (a bare `@heretek-ai/es-core` import from web
|
|
// used to pass because the old map had no `core` key).
|
|
const workspace = await workspacePackages(root)
|
|
const NPM_NAME: Record<string, string> = Object.fromEntries(workspace.map((pkg) => [pkg.dir, pkg.name]))
|
|
const universe = workspace.map((pkg) => pkg.dir)
|
|
for (const pkg of universe)
|
|
if (!BOUNDARIES.some((entry) => entry.pkg === pkg))
|
|
crossings.push(`packages/${pkg} has no BOUNDARIES entry: declare which packages ${pkg} may not import`)
|
|
for (const outer of ["core", "cli"] as const) {
|
|
const bans = BOUNDARIES.find((entry) => entry.pkg === outer)?.banned ?? []
|
|
for (const pkg of universe)
|
|
if (pkg !== outer && pkg !== "core" && pkg !== "cli" && !(bans as readonly string[]).includes(pkg))
|
|
crossings.push(`packages/${outer} may import packages/${pkg}: new surfaces stay out of core and the CLI`)
|
|
}
|
|
for (const { pkg, banned } of BOUNDARIES) {
|
|
const src = path.join(root, "packages", pkg, "src")
|
|
const files = (await readdir(src, { recursive: true })).filter((file) => /\.tsx?$/.test(file))
|
|
for (const file of files) {
|
|
const absolute = path.join(src, file)
|
|
const text = await readFile(absolute, "utf8")
|
|
for (const { specifier, typeOnly } of importsOf(text)) {
|
|
const target = specifier.startsWith(".") ? path.resolve(path.dirname(absolute), specifier) : undefined
|
|
const hit = banned.find((other) =>
|
|
target
|
|
? target.startsWith(path.join(root, "packages", other) + path.sep)
|
|
: specifier === NPM_NAME[other] || specifier.startsWith(`${NPM_NAME[other]}/`),
|
|
)
|
|
if (!hit) continue
|
|
// Web's fleet exemption: a type-only import carries no runtime
|
|
// dependency, so only a value import of fleet from web crosses.
|
|
if (pkg === "web" && hit === "fleet" && typeOnly) continue
|
|
crossings.push(`packages/${pkg}/src/${file} imports ${specifier} (${hit})`)
|
|
}
|
|
}
|
|
}
|
|
if (crossings.length) {
|
|
console.error("Package boundary gate failed (docs/adr/0001-monorepo.md):")
|
|
for (const crossing of crossings) console.error(` - ${crossing}`)
|
|
console.error("Core may not import the CLI or the plugin; the CLI may not import the plugin.")
|
|
process.exit(1)
|
|
}
|
|
|
|
console.log(
|
|
`deps: ok (${Object.keys(dependencies).length} core runtime dependencies; all allowlisted or justified; package boundaries hold)`,
|
|
)
|