From e4645ab7454dcea7c0fc5e803f2aa3b2e7944120 Mon Sep 17 00:00:00 2001 From: "Russ T. Fugal" Date: Wed, 19 Aug 2026 12:09:14 -0600 Subject: [PATCH] refactor: apply the downstream workspace's oxlint autofixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mechanical codemod, produced by `oxlint --type-aware --fix` under the rule set the downstream workspace applies to this library when it is vendored there (packages/config/oxlint.base.jsonc). Landed ahead of the config itself so every commit stays green: none of these edits are findings under the config currently in the tree. 191 of that workspace's 213 findings, by rule: - typescript/array-type (152): `Array` → `T[]`, `ReadonlyArray` → `readonly T[]`. Type-identical; touches the public surface only in how the same types are spelled. - typescript/no-unnecessary-type-assertion (34): chiefly the `as never` escape hatches on every block in src/level-blocks/standard.ts, plus `parseToml(text) as ConfDoc` and a `as unknown as Record<…>`. All were assertions the compiler did not need. - typescript/prefer-includes (4), typescript/consistent-type-imports (1). Verified after the codemod: bun run typecheck clean, bun test 584 pass / 12 skip / 1 fail (the pre-existing XlsxWriter failure), format:check clean. --- src/conf/parse.ts | 13 ++-- src/conf/serialize.ts | 6 +- src/json/JsonReader.ts | 2 +- src/level-blocks/compile.ts | 2 +- src/level-blocks/gate.ts | 8 +-- src/level-blocks/graph.ts | 6 +- src/level-blocks/hiringLagPenalty.ts | 2 +- src/level-blocks/meta.ts | 2 +- src/level-blocks/phaseScoring.ts | 6 +- src/level-blocks/runConfig.ts | 20 +++--- src/level-blocks/standard.ts | 40 +++++------ src/level-blocks/types.ts | 2 +- src/level-blocks/unimodalDeviation.ts | 2 +- src/level-core/analyzeRun.ts | 12 ++-- src/level-core/errors.ts | 4 +- src/level-core/profiles.ts | 2 +- src/level-core/resolveCalendar.ts | 6 +- src/level-core/scheduleStream.ts | 22 +++---- src/level-core/search/serialSGS.ts | 57 ++++++++-------- src/level-core/types.ts | 66 +++++++++---------- src/level-core/weeklyProfile.ts | 6 +- src/level-core/weightedScorer.ts | 4 +- src/mpp/FixedData.ts | 8 +-- src/mpp/MppReader.ts | 2 +- src/xlsx/XlsxWriter.ts | 8 +-- test/helpers.ts | 2 +- test/level-blocks/collectors.test.ts | 2 +- .../concurrentResourceCost.test.ts | 2 +- test/level-blocks/graph.test.ts | 8 +-- test/level-blocks/hiringLagPenalty.test.ts | 2 +- test/level-blocks/meta.test.ts | 2 +- test/level-blocks/openUnitPenalty.test.ts | 2 +- test/level-blocks/pipelineFixtures.ts | 2 +- test/level-blocks/provenance.test.ts | 2 +- test/level-blocks/runConfig.test.ts | 2 +- test/level-blocks/standard.test.ts | 2 +- test/level-blocks/sweep.test.ts | 14 ++-- test/level-blocks/unimodalDeviation.test.ts | 2 +- test/level-core/analyzeRun.test.ts | 2 +- test/level-core/fixtures.ts | 8 +-- test/level-core/resolveCalendar.test.ts | 2 +- test/level-core/serialSGS.test.ts | 12 ++-- test/level-core/weeklyProfile.test.ts | 4 +- test/library.test.ts | 2 +- 44 files changed, 187 insertions(+), 195 deletions(-) diff --git a/src/conf/parse.ts b/src/conf/parse.ts index 868c2d2..b1c2294 100644 --- a/src/conf/parse.ts +++ b/src/conf/parse.ts @@ -108,7 +108,7 @@ function describeValue(value: unknown): string { * to detect reserved-key collisions. */ export function blockParamNames(def: BlockDef): string[] { try { - const js = z.toJSONSchema(def.block.schema.input as z.ZodType, { io: "input" }) as { + const js = z.toJSONSchema(def.block.schema.input, { io: "input" }) as { properties?: Record; }; return Object.keys(js.properties ?? {}); @@ -165,10 +165,7 @@ function scanValues(value: unknown, path: string, issues: ConfIssue[]): void { /** Translate a zod issue path (over the assembled RunConfig) into a * conf-oriented dotted path, so a param error reads `blocks..max` * rather than `pipeline.blocks.0.params.max`. */ -function confPath( - path: ReadonlyArray, - blocks: ReadonlyArray<{ instanceId: string }>, -): string { +function confPath(path: readonly PropertyKey[], blocks: readonly { instanceId: string }[]): string { const seg = path.map(String); if (seg[0] === "pipeline" && seg[1] === "blocks") { const idx = Number(seg[2]); @@ -190,7 +187,7 @@ function confPath( export function parseConf(text: string, opts?: ParseConfOptions): ParseConfResult { let doc: ConfDoc; try { - doc = parseToml(text) as ConfDoc; + doc = parseToml(text); } catch (err) { if (err instanceof TomlError) { return { @@ -362,7 +359,7 @@ export function parseConf(text: string, opts?: ParseConfOptions): ParseConfResul const parsed = runConfigSchema.safeParse(assembled); if (!parsed.success) { for (const issue of parsed.error.issues) { - const path = confPath(issue.path, blocks as Array<{ instanceId: string }>); + const path = confPath(issue.path, blocks); issues.push({ code: "LEVELSET_E302", ...(path !== "" ? { path } : {}), @@ -396,7 +393,7 @@ export function parseConf(text: string, opts?: ParseConfOptions): ParseConfResul function graphIssueToConf(issue: { instanceId?: string; - path?: Array; + path?: (string | number)[]; message: string; }): ConfIssue { return { diff --git a/src/conf/serialize.ts b/src/conf/serialize.ts index d92c3f0..3518e13 100644 --- a/src/conf/serialize.ts +++ b/src/conf/serialize.ts @@ -184,7 +184,7 @@ export function serializeConf(config: RunConfig, opts?: SerializeConfOptions): s // [resolve] if (config.resolve !== undefined) { - emitTable(["resolve"], [], config.resolve as Record, false, sections); + emitTable(["resolve"], [], config.resolve, false, sections); } // [pipeline] — systemCode only; formatVersion is implied by [levelset]. @@ -217,7 +217,7 @@ export function serializeConf(config: RunConfig, opts?: SerializeConfOptions): s // [sweep] + [[sweep.axes]] — emitted through the generic table walker so // targets and zip-axis record values retain their exact JSON structure. if (config.sweep !== undefined) { - emitTable(["sweep"], [], config.sweep as Record, false, sections); + emitTable(["sweep"], [], config.sweep, false, sections); } return sections.join("\n\n") + "\n"; @@ -267,7 +267,7 @@ export function lintRegistryForConf(registry: BlockRegistry): ConfIssue[] { } let schema: SchemaNode; try { - schema = z.toJSONSchema(def.block.schema.input as z.ZodType, { io: "input" }) as SchemaNode; + schema = z.toJSONSchema(def.block.schema.input, { io: "input" }) as SchemaNode; } catch { issues.push({ code: "LEVELSET_E302", diff --git a/src/json/JsonReader.ts b/src/json/JsonReader.ts index f5da2fc..04e810e 100644 --- a/src/json/JsonReader.ts +++ b/src/json/JsonReader.ts @@ -142,7 +142,7 @@ interface RawCalendar { interface RawWeekDay { dayType?: number; working?: boolean; - workingTimes?: Array<{ from?: string; to?: string }>; + workingTimes?: { from?: string; to?: string }[]; } interface RawException { diff --git a/src/level-blocks/compile.ts b/src/level-blocks/compile.ts index f9157ec..756666f 100644 --- a/src/level-blocks/compile.ts +++ b/src/level-blocks/compile.ts @@ -27,7 +27,7 @@ export interface CompileTarget { } export class GraphValidationError extends Error { - readonly issues: ReadonlyArray<{ instanceId?: string; code?: string; message: string }>; + readonly issues: readonly { instanceId?: string; code?: string; message: string }[]; constructor(issues: GraphValidationError["issues"]) { super(`Pipeline graph failed validation:\n${issues.map((i) => `- ${i.message}`).join("\n")}`); diff --git a/src/level-blocks/gate.ts b/src/level-blocks/gate.ts index a0b32dc..ac30eb2 100644 --- a/src/level-blocks/gate.ts +++ b/src/level-blocks/gate.ts @@ -106,7 +106,7 @@ function limitViolation(value: number, limit: ResolvedGateLimit): number { // `a` is less bad than `b` iff its violation vector is lexicographically // smaller: at the first limit where they differ, `a` violates less. Equal on // every limit is not less bad (first-seen wins, keeping selection stable). -function lexLessBad(a: ReadonlyArray, b: ReadonlyArray): boolean { +function lexLessBad(a: readonly number[], b: readonly number[]): boolean { for (let i = 0; i < a.length; i++) { if (a[i]! < b[i]!) return true; if (a[i]! > b[i]!) return false; @@ -121,10 +121,10 @@ function lexLessBad(a: ReadonlyArray, b: ReadonlyArray): boolean * ("leastBad", lexicographic over per-limit violation in declared order). */ export function selectGate( - schedules: ReadonlyArray, - limits: ReadonlyArray, + schedules: readonly Schedule[], + limits: readonly ResolvedGateLimit[], fallback: "leastBad" | "none", -): ReadonlyArray { +): readonly Schedule[] { const passing = schedules.filter((s) => limits.every((l) => withinBounds(l.scorer.score(s), l))); if (passing.length > 0) return passing; if (fallback === "none" || schedules.length === 0) return []; diff --git a/src/level-blocks/graph.ts b/src/level-blocks/graph.ts index f18a46f..f9c9715 100644 --- a/src/level-blocks/graph.ts +++ b/src/level-blocks/graph.ts @@ -43,15 +43,15 @@ export interface GraphIssue { * relative to the graph root for shape issues and to the block's * `params` for param issues — so canvas/panel UIs can target the exact * field instead of re-parsing the message. */ - path?: Array; + path?: (string | number)[]; message: string; } -function formatPath(path: ReadonlyArray): string { +function formatPath(path: readonly PropertyKey[]): string { return path.map(String).join("."); } -function toPath(path: ReadonlyArray): Array { +function toPath(path: readonly PropertyKey[]): (string | number)[] { return path.map((seg) => (typeof seg === "number" ? seg : String(seg))); } diff --git a/src/level-blocks/hiringLagPenalty.ts b/src/level-blocks/hiringLagPenalty.ts index 8bcebfa..3eafb06 100644 --- a/src/level-blocks/hiringLagPenalty.ts +++ b/src/level-blocks/hiringLagPenalty.ts @@ -39,7 +39,7 @@ const ScorerOutputSchema: z.ZodType = z.custom( typeof (val as Scorer).score === "function", ); -export function totalHires(profile: ReadonlyArray): number { +export function totalHires(profile: readonly number[]): number { if (profile.length === 0) return 0; let hires = profile[0]!; for (let i = 1; i < profile.length; i++) { diff --git a/src/level-blocks/meta.ts b/src/level-blocks/meta.ts index 0899765..398aacd 100644 --- a/src/level-blocks/meta.ts +++ b/src/level-blocks/meta.ts @@ -370,7 +370,7 @@ export function deriveBlockMeta(def: BlockDef): DerivedBlockMeta { const complex: string[] = []; let inputSchema: unknown = { type: "object" }; try { - const schema = z.toJSONSchema(def.block.schema.input as z.ZodType, { + const schema = z.toJSONSchema(def.block.schema.input, { io: "input", }) as JsonSchemaNode; inputSchema = schema; diff --git a/src/level-blocks/phaseScoring.ts b/src/level-blocks/phaseScoring.ts index f87d993..ceda5fd 100644 --- a/src/level-blocks/phaseScoring.ts +++ b/src/level-blocks/phaseScoring.ts @@ -43,7 +43,7 @@ export type PhaseWindow = z.infer; /** The distinct task ids referenced across all phases — for `Scorer.reads` * so `analyzeRun` can flag a phase pointing at a missing task. */ -export function phaseTaskIds(phases: ReadonlyArray): number[] { +export function phaseTaskIds(phases: readonly PhaseWindow[]): number[] { return [...new Set(phases.flatMap((p) => p.taskUniqueIds))]; } @@ -57,7 +57,7 @@ export function phaseTaskIds(phases: ReadonlyArray): number[] { * finishes early (or starts late) simply contributes nothing. */ export function phaseDeviation( schedule: Schedule, - phases: ReadonlyArray, + phases: readonly PhaseWindow[], edge: "finish" | "start", ): number { if (phases.length === 0) return 0; @@ -104,7 +104,7 @@ export function phaseDeviation( * through from the caller is future work for when a MiniZinc compiler lands. */ export function phaseDeviationMiniZinc( varName: string, - phases: ReadonlyArray, + phases: readonly PhaseWindow[], edge: "finish" | "start", ): string { const dayVar = edge === "finish" ? "finish_day" : "start_day"; diff --git a/src/level-blocks/runConfig.ts b/src/level-blocks/runConfig.ts index 856dde6..279d465 100644 --- a/src/level-blocks/runConfig.ts +++ b/src/level-blocks/runConfig.ts @@ -130,7 +130,7 @@ export interface WeightedScorerEntry { export interface CollectorResult { readonly instanceId: string; readonly kind: Collector["kind"]; - readonly schedules: ReadonlyArray; + readonly schedules: readonly Schedule[]; } /** A compiled, enabled collector paired with its graph instanceId — the @@ -147,8 +147,8 @@ export interface LevelingRun { readonly resolved: ResolvedProject; /** Static analysis result (warnings only — errors throw in runFromConfig). */ readonly analysis: RunAnalysis; - readonly constraints: ReadonlyArray; - readonly scorers: ReadonlyArray; + readonly constraints: readonly Constraint[]; + readonly scorers: readonly WeightedScorerEntry[]; readonly search: Search; /** The combined weighted objective (falls back to makespan when the * config declares no weighted scorers). */ @@ -160,7 +160,7 @@ export interface LevelingRun { /** Run every enabled collector block against a fresh search stream, * returning each one's selected schedule set, each result labeled by its * graph instanceId. Empty when the pipeline declares no collectors. */ - collectors(): Promise>; + collectors(): Promise; /** Every scorer's raw value, keyed by instanceId — the reported view. */ scores(schedule: Schedule): Record; /** Write the schedule back onto the source project, embedding run @@ -657,13 +657,13 @@ export function runFromConfig( } return run.stream().bestBy(objective); }, - async collectors(): Promise> { + async collectors(): Promise { const byId = new Map(runScorers.map((s) => [s.instanceId, s.scorer])); // Each collector drains its own fresh stream (stream() rebuilds the // generator), so they run independently. return Promise.all( collectors.map(async (c): Promise => { - let schedules: ReadonlyArray; + let schedules: readonly Schedule[]; switch (c.collector.kind) { case "KBest": schedules = await run.stream().kBestByLex( @@ -706,7 +706,7 @@ export function runFromConfig( levelsetVersion: LEVELSET_VERSION, generatedAt: new Date().toISOString(), search: search.name, - runConfig: filled as unknown as Record, + runConfig: filled, ...(filled.seed !== undefined ? { seed: filled.seed } : {}), scores: run.scores(schedule), ...(analysis.issues.length > 0 @@ -733,7 +733,7 @@ export function runFromConfig( } /** The scorer instanceIds a collector references, whatever its kind. */ -function collectorScorerRefs(collector: Collector): ReadonlyArray { +function collectorScorerRefs(collector: Collector): readonly string[] { switch (collector.kind) { case "KBest": return collector.by; @@ -775,11 +775,11 @@ async function* stripReturn(gen: AsyncGenerator): AsyncGenera async function* runSweepCell( search: Search, resolved: ResolvedProject, - constraints: ReadonlyArray, + constraints: readonly Constraint[], point: SweepPoint, stats: SweepStats, statsBySchedule: WeakMap, - cellScorers: ReadonlyArray, + cellScorers: readonly WeightedScorerEntry[], scorersBySchedule: WeakMap>, ): AsyncGenerator { let emitted = false; diff --git a/src/level-blocks/standard.ts b/src/level-blocks/standard.ts index 3956025..86bfdc2 100644 --- a/src/level-blocks/standard.ts +++ b/src/level-blocks/standard.ts @@ -59,7 +59,7 @@ export function standardBlockDefs(): BlockDef[] { code: STANDARD_CODES.maxConcurrentResource, family: "constraint", tier: "estimator", - block: MaxConcurrentResourceBlock as never, + block: MaxConcurrentResourceBlock, defaultShadow: { resourceUniqueId: 0, max: 1 }, assumptionTemplate: "at most {max} concurrent tasks on resource {resourceUniqueId}", }, @@ -67,7 +67,7 @@ export function standardBlockDefs(): BlockDef[] { code: STANDARD_CODES.peakCap, family: "constraint", tier: "estimator", - block: PeakCapBlock as never, + block: PeakCapBlock, defaultShadow: { resourceUniqueId: 0, cap: 1 }, assumptionTemplate: "peak demand on resource {resourceUniqueId} capped at {cap} units", }, @@ -75,7 +75,7 @@ export function standardBlockDefs(): BlockDef[] { code: STANDARD_CODES.concurrentUnitsLimit, family: "constraint", tier: "estimator", - block: ConcurrentUnitsLimitBlock as never, + block: ConcurrentUnitsLimitBlock, defaultShadow: { unitIds: [], max: 1 }, assumptionTemplate: "at most {max} work units open at once", }, @@ -83,7 +83,7 @@ export function standardBlockDefs(): BlockDef[] { code: STANDARD_CODES.deadline, family: "constraint", tier: "estimator", - block: DeadlineBlock as never, + block: DeadlineBlock, defaultShadow: { taskUniqueId: 0, latestFinish: 0 }, assumptionTemplate: "task {taskUniqueId} finishes by day {latestFinish}", }, @@ -91,7 +91,7 @@ export function standardBlockDefs(): BlockDef[] { code: STANDARD_CODES.release, family: "constraint", tier: "estimator", - block: ReleaseBlock as never, + block: ReleaseBlock, defaultShadow: { taskUniqueId: 0, earliestStart: 0 }, assumptionTemplate: "task {taskUniqueId} starts no earlier than day {earliestStart}", }, @@ -99,7 +99,7 @@ export function standardBlockDefs(): BlockDef[] { code: STANDARD_CODES.phaseRelease, family: "constraint", tier: "estimator", - block: PhaseReleaseBlock as never, + block: PhaseReleaseBlock, defaultShadow: { taskUniqueIds: [0], earliestStart: 0 }, assumptionTemplate: "phase tasks start no earlier than day {earliestStart}", }, @@ -107,7 +107,7 @@ export function standardBlockDefs(): BlockDef[] { code: STANDARD_CODES.modeSelection, family: "constraint", tier: "engineer", - block: ModeSelectionBlock as never, + block: ModeSelectionBlock, defaultShadow: { taskUniqueId: 0, modes: [{ modeId: 1, durationDays: 1, resourceDemand: [] }], @@ -118,7 +118,7 @@ export function standardBlockDefs(): BlockDef[] { code: STANDARD_CODES.crewBorrow, family: "constraint", tier: "engineer", - block: CrewBorrowBlock as never, + block: CrewBorrowBlock, defaultShadow: { taskUniqueIds: [0], homeResourceUniqueId: 0, @@ -131,63 +131,63 @@ export function standardBlockDefs(): BlockDef[] { code: STANDARD_CODES.concurrentResourceCost, family: "scorer", tier: "estimator", - block: ConcurrentResourceCostBlock as never, + block: ConcurrentResourceCostBlock, defaultShadow: { resourceUniqueId: 0, basePrice: 100, growthBase: 2, threshold: 0 }, }, { code: STANDARD_CODES.openUnitPenalty, family: "scorer", tier: "estimator", - block: OpenUnitPenaltyBlock as never, + block: OpenUnitPenaltyBlock, defaultShadow: { unitIds: [], softMax: 0, weight: 1 }, }, { code: STANDARD_CODES.hiringLagPenalty, family: "scorer", tier: "estimator", - block: HiringLagPenaltyBlock as never, + block: HiringLagPenaltyBlock, defaultShadow: { resourceUniqueId: 0, trainingWeeks: 2, costPerCrewWeek: 1000 }, }, { code: STANDARD_CODES.phaseLateness, family: "scorer", tier: "estimator", - block: PhaseLatenessBlock as never, + block: PhaseLatenessBlock, defaultShadow: { phases: [] }, }, { code: STANDARD_CODES.phaseEarliness, family: "scorer", tier: "estimator", - block: PhaseEarlinessBlock as never, + block: PhaseEarlinessBlock, defaultShadow: { phases: [] }, }, { code: STANDARD_CODES.unimodalDeviation, family: "scorer", tier: "engineer", - block: UnimodalDeviationBlock as never, + block: UnimodalDeviationBlock, defaultShadow: { resourceUniqueId: 0, allowSecondPeak: false, jitterTolerance: 1 }, }, { code: STANDARD_CODES.makespan, family: "scorer", tier: "sales", - block: MakespanBlock as never, + block: MakespanBlock, defaultShadow: {}, }, { code: STANDARD_CODES.serialSGS, family: "search", tier: "engineer", - block: SerialSGSBlock as never, + block: SerialSGSBlock, defaultShadow: {}, }, { code: STANDARD_CODES.kBest, family: "collector", tier: "engineer", - block: KBestBlock as never, + block: KBestBlock, // Placeholder scorer reference — the app rewires `by` to real scorer // instanceIds before building a config (same spirit as the id-0 // placeholders on the constraint shadows). @@ -197,14 +197,14 @@ export function standardBlockDefs(): BlockDef[] { code: STANDARD_CODES.pareto, family: "collector", tier: "engineer", - block: ParetoBlock as never, + block: ParetoBlock, defaultShadow: { over: ["makespan", "cost"] }, }, { code: STANDARD_CODES.gate, family: "collector", tier: "engineer", - block: GateBlock as never, + block: GateBlock, // Placeholder scorer reference + bound — the app rewires `limits` to // real scorer instanceIds and caps before building a config. defaultShadow: { limits: [{ scorer: "makespan", max: 0 }], fallback: "none" }, @@ -214,6 +214,6 @@ export function standardBlockDefs(): BlockDef[] { /** A registry pre-loaded with the standard defs; pass extra defs to extend * (plugins, app-specific blocks, `.v2` revisions). */ -export function standardRegistry(extraDefs?: ReadonlyArray): BlockRegistry { +export function standardRegistry(extraDefs?: readonly BlockDef[]): BlockRegistry { return createRegistry([...standardBlockDefs(), ...(extraDefs ?? [])]); } diff --git a/src/level-blocks/types.ts b/src/level-blocks/types.ts index a119dd7..966e8b8 100644 --- a/src/level-blocks/types.ts +++ b/src/level-blocks/types.ts @@ -56,7 +56,7 @@ export interface Block { export type ConstraintBlock = Block; /** A policy/group block that expands one authored instance into several * ordinary engine constraints. */ -export type MultiConstraintBlock = Block>; +export type MultiConstraintBlock = Block; export type ScoringBlock = Block; export type SearchBlock = Block; export type CollectorBlock = Block; diff --git a/src/level-blocks/unimodalDeviation.ts b/src/level-blocks/unimodalDeviation.ts index fbd9a5e..f3520a8 100644 --- a/src/level-blocks/unimodalDeviation.ts +++ b/src/level-blocks/unimodalDeviation.ts @@ -45,7 +45,7 @@ const ScorerOutputSchema: z.ZodType = z.custom( /** Sum of off-pattern dip/bump magnitudes around the dominant peak. * Returns 0 for a strictly unimodal profile within `jitterTolerance`. */ export function unimodalDeviation( - values: ReadonlyArray, + values: readonly number[], allowSecondPeak: boolean, jitterTolerance: number, ): number { diff --git a/src/level-core/analyzeRun.ts b/src/level-core/analyzeRun.ts index ac5c245..c7b689f 100644 --- a/src/level-core/analyzeRun.ts +++ b/src/level-core/analyzeRun.ts @@ -16,7 +16,7 @@ import { } from "./types.ts"; export interface AnalyzeRunOptions { - readonly scorers?: ReadonlyArray | undefined; + readonly scorers?: readonly Scorer[] | undefined; /** When given (and it implements `supports`), constraint kinds the search * won't enforce are reported as LEVELSET_W201 warnings. */ readonly search?: Search | undefined; @@ -25,12 +25,12 @@ export interface AnalyzeRunOptions { export interface RunAnalysis { /** True when no error-severity issues were found (warnings allowed). */ readonly ok: boolean; - readonly issues: ReadonlyArray; + readonly issues: readonly RunIssue[]; } export function analyzeRun( resolved: ResolvedProject, - constraints: ReadonlyArray, + constraints: readonly Constraint[], opts?: AnalyzeRunOptions, ): RunAnalysis { const issues: RunIssue[] = []; @@ -70,7 +70,7 @@ export function analyzeRun( const deadlines = new Map(); const releases = new Map(); const constraintEdges: PrecedenceEdge[] = []; - const unitEdges: Array<{ from: number; to: number; source: string }> = []; + const unitEdges: { from: number; to: number; source: string }[] = []; const seenNames = new Map(); const modePolicyByTask = new Map(); @@ -312,8 +312,8 @@ function warnEmpty(issues: RunIssue[], source: string, field: string): void { /** Kahn's algorithm; returns the node ids stuck in a cycle (unsorted * remainder) or null when acyclic. */ function findCycle( - nodes: ReadonlyArray, - edges: ReadonlyArray<[from: number, to: number]>, + nodes: readonly number[], + edges: readonly [from: number, to: number][], ): number[] | null { const indegree = new Map(nodes.map((n) => [n, 0])); const successors = new Map(nodes.map((n) => [n, []])); diff --git a/src/level-core/errors.ts b/src/level-core/errors.ts index dd8169d..a9a9ca9 100644 --- a/src/level-core/errors.ts +++ b/src/level-core/errors.ts @@ -43,9 +43,9 @@ export interface RunIssue { } export class RunAnalysisError extends Error { - readonly issues: ReadonlyArray; + readonly issues: readonly RunIssue[]; - constructor(issues: ReadonlyArray) { + constructor(issues: readonly RunIssue[]) { const errors = issues.filter((i) => i.severity === "error"); super( `Run analysis found ${String(errors.length)} error(s):\n` + diff --git a/src/level-core/profiles.ts b/src/level-core/profiles.ts index 8da7cf4..2101fea 100644 --- a/src/level-core/profiles.ts +++ b/src/level-core/profiles.ts @@ -112,7 +112,7 @@ export function scheduleProfiles(schedule: Schedule): ScheduleProfiles { * shared span slot. Shared by OpenUnitPenalty and unit-WIP reporting. */ export function openUnitCount( schedule: Schedule, - unitIds: ReadonlyArray, + unitIds: readonly number[], discipline?: number, ): Int32Array { const cal = resolveWorkingCalendar(schedule.resolved, null); diff --git a/src/level-core/resolveCalendar.ts b/src/level-core/resolveCalendar.ts index 54ce0ba..a130278 100644 --- a/src/level-core/resolveCalendar.ts +++ b/src/level-core/resolveCalendar.ts @@ -196,7 +196,7 @@ function resolveTask( }; } -function resolveEdges(tasks: ReadonlyArray, properties: ProjectProperties): PrecedenceEdge[] { +function resolveEdges(tasks: readonly Task[], properties: ProjectProperties): PrecedenceEdge[] { const edges: PrecedenceEdge[] = []; for (const t of tasks) { if (t.uniqueId === null) continue; @@ -281,9 +281,7 @@ function buildCalendarMap( /** Canonicalize WorkUnits into a by-id registry, the single owner of unit * definitions. Rejects duplicate ids so a unit id maps to exactly one * definition across every consumer (JS scorers and the MiniZinc backend). */ -export function resolveWorkUnits( - units: ReadonlyArray = [], -): ReadonlyMap { +export function resolveWorkUnits(units: readonly WorkUnit[] = []): ReadonlyMap { const map = new Map(); for (const u of units) { if (map.has(u.id)) { diff --git a/src/level-core/scheduleStream.ts b/src/level-core/scheduleStream.ts index 583b4e5..794b024 100644 --- a/src/level-core/scheduleStream.ts +++ b/src/level-core/scheduleStream.ts @@ -77,7 +77,7 @@ export class ScheduleStreamImpl implements ScheduleStream { return bestSchedule; } - async bestByLex(scorers: ReadonlyArray): Promise { + async bestByLex(scorers: readonly Scorer[]): Promise { let bestSchedule: Schedule | null = null; let bestScores: number[] | null = null; for await (const s of this.source) { @@ -90,7 +90,7 @@ export class ScheduleStreamImpl implements ScheduleStream { return bestSchedule; } - async kBestByLex(scorers: ReadonlyArray, k: number): Promise> { + async kBestByLex(scorers: readonly Scorer[], k: number): Promise { if (k <= 0) return []; const scored: { schedule: Schedule; scores: number[] }[] = []; for await (const s of this.source) { @@ -104,7 +104,7 @@ export class ScheduleStreamImpl implements ScheduleStream { return scored.slice(0, k).map((x) => x.schedule); } - async paretoFrontier(scorers: ReadonlyArray): Promise> { + async paretoFrontier(scorers: readonly Scorer[]): Promise { const collected: { schedule: Schedule; scores: number[] }[] = []; for await (const s of this.source) { collected.push({ @@ -127,7 +127,7 @@ export class ScheduleStreamImpl implements ScheduleStream { return frontier; } - async collect(limit?: number): Promise> { + async collect(limit?: number): Promise { const out: Schedule[] = []; for await (const s of this.source) { out.push(s); @@ -140,9 +140,9 @@ export class ScheduleStreamImpl implements ScheduleStream { // `a` beats `b` on the first tier where they differ; equal on all tiers is // not better (first seen wins, keeping selection deterministic). function lexBetter( - a: ReadonlyArray, - b: ReadonlyArray, - scorers: ReadonlyArray, + a: readonly number[], + b: readonly number[], + scorers: readonly Scorer[], ): boolean { for (let i = 0; i < scorers.length; i++) { const ai = a[i]!; @@ -156,9 +156,9 @@ function lexBetter( // `a` dominates `b` iff a is at least as good on every scorer and strictly // better on at least one. function dominates( - a: ReadonlyArray, - b: ReadonlyArray, - scorers: ReadonlyArray, + a: readonly number[], + b: readonly number[], + scorers: readonly Scorer[], ): boolean { let strictlyBetter = false; for (let i = 0; i < scorers.length; i++) { @@ -181,7 +181,7 @@ export function streamFromFactory(factory: () => AsyncGenerator): Sche /** Lazily concatenate streams in declaration order. A later factory is not * invoked until every schedule from the preceding stream has been consumed. */ -export function concatStreams(factories: ReadonlyArray<() => ScheduleStream>): ScheduleStream { +export function concatStreams(factories: readonly (() => ScheduleStream)[]): ScheduleStream { return new ScheduleStreamImpl({ async *[Symbol.asyncIterator]() { for (const factory of factories) { diff --git a/src/level-core/search/serialSGS.ts b/src/level-core/search/serialSGS.ts index 78bc46c..9cae934 100644 --- a/src/level-core/search/serialSGS.ts +++ b/src/level-core/search/serialSGS.ts @@ -46,7 +46,7 @@ interface PerResourceCap { // the day-loop stays a set union). Active-day occupancy — gaps free the slot. interface UnitCap { readonly max: number; - readonly taskUnits: ReadonlyMap>; + readonly taskUnits: ReadonlyMap; } // One placement alternative for a task: a duration + the resource demand it @@ -56,26 +56,23 @@ interface UnitCap { interface PlacementMode { readonly modeId: number | null; readonly durationDays: number; - readonly demand: ReadonlyArray<{ readonly resourceUniqueId: number; readonly units: number }>; + readonly demand: readonly { readonly resourceUniqueId: number; readonly units: number }[]; } interface Preprocessed { - readonly edges: ReadonlyArray; - readonly caps: ReadonlyMap>; - readonly unitCaps: ReadonlyArray; + readonly edges: readonly PrecedenceEdge[]; + readonly caps: ReadonlyMap; + readonly unitCaps: readonly UnitCap[]; /** Tightest deadline per task — the *constraint instance* is kept, not just * the day, so violations attribute to the named instance that bound. */ readonly deadlines: ReadonlyMap; readonly releases: ReadonlyMap; /** taskUniqueId → alternatives for ModeSelection/CrewBorrow-governed tasks. */ - readonly modesByTask: ReadonlyMap>; - readonly explanations: ReadonlyArray; + readonly modesByTask: ReadonlyMap; + readonly explanations: readonly Explanation[]; } -function preprocess( - resolved: ResolvedProject, - constraints: ReadonlyArray, -): Preprocessed { +function preprocess(resolved: ResolvedProject, constraints: readonly Constraint[]): Preprocessed { const edges: PrecedenceEdge[] = [...resolved.precedences]; const caps = new Map(); const unitCaps: UnitCap[] = []; @@ -226,8 +223,8 @@ function preprocess( } function mergeDemand( - demand: ReadonlyArray<{ readonly resourceUniqueId: number; readonly units: number }>, -): Array<{ resourceUniqueId: number; units: number }> { + demand: readonly { readonly resourceUniqueId: number; readonly units: number }[], +): { resourceUniqueId: number; units: number }[] { const unitsByResource = new Map(); for (const item of demand) { unitsByResource.set( @@ -239,8 +236,8 @@ function mergeDemand( } function topoSort( - tasks: ReadonlyArray, - edges: ReadonlyArray, + tasks: readonly ResolvedTask[], + edges: readonly PrecedenceEdge[], ): ResolvedTask[] { const taskById = new Map(tasks.map((t) => [t.uniqueId, t])); const indegree = new Map(); @@ -302,7 +299,7 @@ function safeAdvance(cal: WorkingCalendar, from: number, lag: number): number { // approximation are returned so the caller can annotate the Schedule. function earliestStart( task: ResolvedTask, - edges: ReadonlyArray, + edges: readonly PrecedenceEdge[], scheduled: ReadonlyMap, cal: WorkingCalendar, approximated: PrecedenceEdge[], @@ -348,12 +345,12 @@ function isFeasible( taskUniqueId: number, startDay: number, finishDay: number, - demand: ReadonlyArray<{ readonly resourceUniqueId: number; readonly units: number }>, + demand: readonly { readonly resourceUniqueId: number; readonly units: number }[], cal: WorkingCalendar, - caps: ReadonlyMap>, + caps: ReadonlyMap, load: ResourceLoad, - unitCaps: ReadonlyArray, - unitLoad: ReadonlyArray, + unitCaps: readonly UnitCap[], + unitLoad: readonly UnitLoad[], ): boolean { for (let d = startDay; d < finishDay; d++) { if (cal.bits[d] !== 1) continue; @@ -390,11 +387,11 @@ function applyLoad( taskUniqueId: number, startDay: number, finishDay: number, - demand: ReadonlyArray<{ readonly resourceUniqueId: number; readonly units: number }>, + demand: readonly { readonly resourceUniqueId: number; readonly units: number }[], cal: WorkingCalendar, load: ResourceLoad, - unitCaps: ReadonlyArray, - unitLoad: ReadonlyArray, + unitCaps: readonly UnitCap[], + unitLoad: readonly UnitLoad[], ): void { for (let d = startDay; d < finishDay; d++) { if (cal.bits[d] !== 1) continue; @@ -431,16 +428,16 @@ interface PlaceResult { function placeTask( task: ResolvedTask, - edges: ReadonlyArray, + edges: readonly PrecedenceEdge[], scheduled: Map, resolved: ResolvedProject, - caps: ReadonlyMap>, - unitCaps: ReadonlyArray, + caps: ReadonlyMap, + unitCaps: readonly UnitCap[], deadlines: ReadonlyMap, releases: ReadonlyMap, - modesByTask: ReadonlyMap>, + modesByTask: ReadonlyMap, load: ResourceLoad, - unitLoad: ReadonlyArray, + unitLoad: readonly UnitLoad[], approximated: PrecedenceEdge[], realized: ResolvedAssignment[], ): PlaceResult { @@ -456,7 +453,7 @@ function placeTask( // its preferred mode), else a single synthetic mode from static assignments. const taskModes = modesByTask.get(task.uniqueId); const isModeTask = taskModes !== undefined && taskModes.length > 0; - const modes: ReadonlyArray = isModeTask + const modes: readonly PlacementMode[] = isModeTask ? taskModes : [ { @@ -591,7 +588,7 @@ export const serialSGS: Search = { }, async *run( resolved: ResolvedProject, - constraints: ReadonlyArray, + constraints: readonly Constraint[], ): AsyncGenerator { // The three-lever enable model: `enabled: false` constraints are carried // in the run (for what-if toggling) but not enforced; the skip is diff --git a/src/level-core/types.ts b/src/level-core/types.ts index 6f65c94..0d1dad7 100644 --- a/src/level-core/types.ts +++ b/src/level-core/types.ts @@ -74,10 +74,10 @@ export interface ResolvedProject { readonly source: Project; readonly defaultCalendarUniqueId: number | null; readonly calendars: ReadonlyMap; - readonly tasks: ReadonlyArray; - readonly resources: ReadonlyArray; - readonly assignments: ReadonlyArray; - readonly precedences: ReadonlyArray; + readonly tasks: readonly ResolvedTask[]; + readonly resources: readonly ResolvedResource[]; + readonly assignments: readonly ResolvedAssignment[]; + readonly precedences: readonly PrecedenceEdge[]; // Canonical WorkUnit registry — the single owner of unit definitions. // Keyed by id, so a unit id maps to exactly one definition; constraints and // scoring blocks reference units by id rather than carrying copies. @@ -111,7 +111,7 @@ export interface WorkUnit { /** Serial-numbered instantiation of `productType` (e.g. "001"). */ readonly serial?: string | undefined; /** Tasks comprising this unit; may span disciplines. */ - readonly taskUniqueIds: ReadonlyArray; + readonly taskUniqueIds: readonly number[]; } // ───────────────────────────────────────────────────────────────── @@ -159,7 +159,7 @@ export function constraintLabel(c: Constraint): string { export interface PrecedenceConstraint extends ConstraintMeta { readonly kind: "Precedence"; - readonly edges: ReadonlyArray; + readonly edges: readonly PrecedenceEdge[]; } export interface CalendarsConstraint extends ConstraintMeta { @@ -194,7 +194,7 @@ export interface ConcurrentUnitsLimitConstraint extends ConstraintMeta { readonly kind: "ConcurrentUnitsLimit"; // Ids into ResolvedProject.workUnits. The set of units this cap governs; // treated as a set (duplicate references collapse). - readonly unitIds: ReadonlyArray; + readonly unitIds: readonly number[]; /** resourceUniqueId scoping the count to one discipline; omit = any task. */ readonly discipline?: number | undefined; readonly max: number; @@ -205,7 +205,7 @@ export interface ConcurrentUnitsLimitConstraint extends ConstraintMeta { export interface UnitPrecedenceConstraint extends ConstraintMeta { readonly kind: "UnitPrecedence"; readonly unitId: number; - readonly afterUnitIds: ReadonlyArray; + readonly afterUnitIds: readonly number[]; } // Shape constraint, separate from the Smoothness *scorer*. A moment @@ -222,7 +222,7 @@ export interface UnimodalProfileConstraint extends ConstraintMeta { export interface ModeSelectionConstraint extends ConstraintMeta { readonly kind: "ModeSelection"; readonly taskUniqueId: number; - readonly modes: ReadonlyArray; + readonly modes: readonly TaskMode[]; } /** One-way crew substitution policy. Each governed task prefers its static @@ -230,7 +230,7 @@ export interface ModeSelectionConstraint extends ConstraintMeta { * same units may be supplied by `borrowResourceUniqueId` to start earlier. */ export interface CrewBorrowConstraint extends ConstraintMeta { readonly kind: "CrewBorrow"; - readonly taskUniqueIds: ReadonlyArray; + readonly taskUniqueIds: readonly number[]; readonly homeResourceUniqueId: number; readonly borrowResourceUniqueId: number; } @@ -238,10 +238,10 @@ export interface CrewBorrowConstraint extends ConstraintMeta { export interface TaskMode { readonly modeId: number; readonly durationDays: number; - readonly resourceDemand: ReadonlyArray<{ + readonly resourceDemand: readonly { readonly resourceUniqueId: number; readonly units: number; - }>; + }[]; } // LBMS-flavored — keeps a crew working a sequence of units without @@ -251,7 +251,7 @@ export interface TaskMode { export interface CrewFlowContinuityConstraint extends ConstraintMeta { readonly kind: "CrewFlowContinuity"; readonly resourceUniqueId: number; - readonly unitOrder: ReadonlyArray; + readonly unitOrder: readonly number[]; } export interface DeadlineConstraint extends ConstraintMeta { @@ -285,9 +285,9 @@ export function assertNeverConstraint(c: never): never { * references a missing resource/unit/task *before* a search silently * optimizes on a constant 0. */ export interface ScorerReads { - readonly resourceUniqueIds?: ReadonlyArray | undefined; - readonly unitIds?: ReadonlyArray | undefined; - readonly taskUniqueIds?: ReadonlyArray | undefined; + readonly resourceUniqueIds?: readonly number[] | undefined; + readonly unitIds?: readonly number[] | undefined; + readonly taskUniqueIds?: readonly number[] | undefined; } export interface Scorer { @@ -320,7 +320,7 @@ export interface KBestCollector { readonly kind: "KBest"; readonly k: number; /** Scorer instanceIds, in tie-break priority order. */ - readonly by: ReadonlyArray; + readonly by: readonly string[]; } /** Keep the non-dominated set across the `over` scorers (Pareto frontier). @@ -329,7 +329,7 @@ export interface KBestCollector { export interface ParetoCollector { readonly kind: "Pareto"; /** Scorer instanceIds spanning the objective space. */ - readonly over: ReadonlyArray; + readonly over: readonly string[]; } /** Feasibility gate: keep only schedules that satisfy *every* limit, where a @@ -345,7 +345,7 @@ export interface ParetoCollector { * summed across scorers. */ export interface GateCollector { readonly kind: "Gate"; - readonly limits: ReadonlyArray; + readonly limits: readonly GateLimit[]; readonly fallback: "leastBad" | "none"; } @@ -375,14 +375,14 @@ export function assertNeverCollector(c: never): never { export interface Explanation { readonly violated: Constraint; - readonly involvedTaskIds: ReadonlyArray; + readonly involvedTaskIds: readonly number[]; readonly atDays: { readonly fromDay: DayIndex; readonly toDay: DayIndex } | null; readonly message: string; } export interface Failure { readonly kind: "failure"; - readonly explanations: ReadonlyArray; + readonly explanations: readonly Explanation[]; } // ───────────────────────────────────────────────────────────────── @@ -400,7 +400,7 @@ export interface ScheduledTask { export interface Schedule { readonly resolved: ResolvedProject; - readonly tasks: ReadonlyArray; + readonly tasks: readonly ScheduledTask[]; readonly makespan: DayIndex; /** Per-block annotations (scores, telemetry). Not for violations. */ readonly annotations: ReadonlyMap; @@ -411,7 +411,7 @@ export interface Schedule { * of the task's static `resolved.assignments`. Populated for every * mode-governed task (including those that pick their home mode) so * consumers apply a single rule; absent entirely when no task has modes. */ - readonly realizedAssignments?: ReadonlyArray; + readonly realizedAssignments?: readonly ResolvedAssignment[]; } // ───────────────────────────────────────────────────────────────── @@ -432,8 +432,8 @@ export interface ResourceDayDelta { } export interface ScheduleDiff { - readonly tasks: ReadonlyArray; - readonly resourceDays: ReadonlyArray; + readonly tasks: readonly TaskDelta[]; + readonly resourceDays: readonly ResourceDayDelta[]; /** Computed lazily — non-trivial CPM rerun. */ readonly criticalPathChanged: boolean; } @@ -461,14 +461,14 @@ export interface ScheduleStream { bestBy(scorer: Scorer): Promise; /** Best schedule by ordered tiers: scorers compared lexicographically — * the second scorer only breaks ties on the first, and so on. */ - bestByLex(scorers: ReadonlyArray): Promise; + bestByLex(scorers: readonly Scorer[]): Promise; /** The best `k` schedules by those same ordered tiers, best first. Yields * fewer than `k` when the stream is shorter, and none when `k <= 0`. * Ties keep first-seen order (matching `bestByLex`'s determinism). */ - kBestByLex(scorers: ReadonlyArray, k: number): Promise>; - paretoFrontier(scorers: ReadonlyArray): Promise>; + kBestByLex(scorers: readonly Scorer[], k: number): Promise; + paretoFrontier(scorers: readonly Scorer[]): Promise; /** Bounded materialization — caller asserts the stream terminates. */ - collect(limit?: number): Promise>; + collect(limit?: number): Promise; } // ───────────────────────────────────────────────────────────────── @@ -484,7 +484,7 @@ export interface Search { readonly name: string; run( resolved: ResolvedProject, - constraints: ReadonlyArray, + constraints: readonly Constraint[], ): AsyncGenerator; /** Whether this backend *enforces* the given constraint kind. Kinds it * doesn't enforce become `unsupportedConstraints` annotations on emitted @@ -514,8 +514,8 @@ export type SearchTransformer = (inner: Search) => Search; export type Stage = (resolved: ResolvedProject) => ResolvedProject; export interface Pipeline { - readonly stages: ReadonlyArray; - readonly constraints: ReadonlyArray; + readonly stages: readonly Stage[]; + readonly constraints: readonly Constraint[]; readonly search: Search; } @@ -541,7 +541,7 @@ export interface ResolveOptions { readonly horizonDays?: number; /** WorkUnit definitions for this project; resolved into a by-id registry. * Duplicate ids are rejected. */ - readonly workUnits?: ReadonlyArray | undefined; + readonly workUnits?: readonly WorkUnit[] | undefined; } export declare function resolveCalendar(project: Project, opts?: ResolveOptions): ResolvedProject; diff --git a/src/level-core/weeklyProfile.ts b/src/level-core/weeklyProfile.ts index d7091c9..d82df40 100644 --- a/src/level-core/weeklyProfile.ts +++ b/src/level-core/weeklyProfile.ts @@ -24,7 +24,7 @@ export type ResourceLoad = Map; // in realizedAssignments is loaded from there and never from resolved). export function effectiveAssignments( schedule: Schedule, -): ReadonlyArray { +): readonly Schedule["resolved"]["assignments"][number][] { const realized = schedule.realizedAssignments; if (!realized || realized.length === 0) return schedule.resolved.assignments; const overridden = new Set(realized.map((a) => a.taskUniqueId)); @@ -66,7 +66,7 @@ export function peakLoad(load: ResourceLoad, resourceId: number): number { return m; } -export function combinedPeakLoad(load: ResourceLoad, resourceIds: ReadonlyArray): number { +export function combinedPeakLoad(load: ResourceLoad, resourceIds: readonly number[]): number { const arrs = resourceIds.map((id) => load.get(id)).filter((a): a is Int16Array => !!a); if (arrs.length === 0) return 0; let m = 0; @@ -113,7 +113,7 @@ export function weeklyProfile( /** Combined weekly peak across multiple resources — for "install pool" views. */ export function weeklyProfileCombined( resolved: ResolvedProject, - arrs: ReadonlyArray, + arrs: readonly Int16Array[], rangeStart: DayIndex, rangeEnd: DayIndex, ): WeeklyBucket[] { diff --git a/src/level-core/weightedScorer.ts b/src/level-core/weightedScorer.ts index af44f2e..7494763 100644 --- a/src/level-core/weightedScorer.ts +++ b/src/level-core/weightedScorer.ts @@ -16,7 +16,7 @@ export interface WeightedEntry { * contribute negated, so a higher raw value still improves the combined * score. Entries with null/zero weight are excluded from the objective * (use `scoreReport` to still see their values). */ -export function weightedScorer(entries: ReadonlyArray, name = "objective"): Scorer { +export function weightedScorer(entries: readonly WeightedEntry[], name = "objective"): Scorer { const active = entries.filter( (e): e is WeightedEntry & { weight: number } => e.weight !== null && e.weight !== 0, ); @@ -38,7 +38,7 @@ export function weightedScorer(entries: ReadonlyArray, name = "ob * reported-but-unweighted view that rides along with the objective. */ export function scoreReport( schedule: Schedule, - scorers: ReadonlyArray, + scorers: readonly Scorer[], ): Record { const report: Record = {}; for (const scorer of scorers) report[scorer.name] = scorer.score(schedule); diff --git a/src/mpp/FixedData.ts b/src/mpp/FixedData.ts index b967a17..ed06727 100644 --- a/src/mpp/FixedData.ts +++ b/src/mpp/FixedData.ts @@ -4,9 +4,9 @@ import { MppUtility } from "./MppUtility.ts"; export class FixedData { private readonly offsetIndex: Map; public readonly raw: Uint8Array; - private readonly records: Array; + private readonly records: (Uint8Array | null)[]; - constructor(raw: Uint8Array, records: Array, offsets: number[]) { + constructor(raw: Uint8Array, records: (Uint8Array | null)[], offsets: number[]) { this.raw = raw; this.records = records; this.offsetIndex = new Map(); @@ -31,7 +31,7 @@ export class FixedData { } static fromMeta(meta: FixedMeta, raw: Uint8Array, maxExpectedSize = 0, minSize = 0): FixedData { - const records: Array = []; + const records: (Uint8Array | null)[] = []; const offsets: number[] = []; for (let index = 0; index < meta.adjustedItemCount; index += 1) { @@ -81,7 +81,7 @@ export class FixedData { const count = Math.floor(raw.length / itemSize) + (readRemainderBlock && raw.length % itemSize !== 0 ? 1 : 0); - const records: Array = []; + const records: (Uint8Array | null)[] = []; const offsets: number[] = []; for (let index = 0; index < count; index += 1) { diff --git a/src/mpp/MppReader.ts b/src/mpp/MppReader.ts index da5aa4d..ab92241 100644 --- a/src/mpp/MppReader.ts +++ b/src/mpp/MppReader.ts @@ -33,7 +33,7 @@ export function parseMppBuffer(data: Uint8Array | ArrayBuffer): MppContainer { throw new Error("Failed to read MPP data: the buffer is empty and not a valid MPP document."); } - let cfb: { FullPaths: string[]; FileIndex: Array<{ type: number; content?: Uint8Array }> }; + let cfb: { FullPaths: string[]; FileIndex: { type: number; content?: Uint8Array }[] }; try { cfb = CFB.read(bytes, { type: "array" }) as typeof cfb; } catch (error: unknown) { diff --git a/src/xlsx/XlsxWriter.ts b/src/xlsx/XlsxWriter.ts index 6e067c6..5e9510a 100644 --- a/src/xlsx/XlsxWriter.ts +++ b/src/xlsx/XlsxWriter.ts @@ -61,9 +61,9 @@ export interface SheetSpec { /** Workbook-level defined names, e.g. { name: "Rate_Install_Blended", * range: "Pricing!$B$4" }. Emitters that write formulas against named * ranges only survive row insertion. */ - namedRanges?: Array<{ name: string; range: string }>; + namedRanges?: { name: string; range: string }[]; /** 1-based inclusive row spans to group under an outline level. */ - outlineRows?: Array<{ from: number; to: number; level: number }>; + outlineRows?: { from: number; to: number; level: number }[]; } /** @@ -148,7 +148,7 @@ export class XlsxWriter { options?: XlsxWriterOptions, specSheets?: SheetSpec[], ): Promise { - const flat: Array<{ phaseIdx: number; bar: GanttBar }> = []; + const flat: { phaseIdx: number; bar: GanttBar }[] = []; for (let pi = 0; pi < gantt.phases.length; pi += 1) { for (const bar of gantt.phases[pi]!.items) { flat.push({ phaseIdx: pi, bar }); @@ -343,7 +343,7 @@ function addSpecSheets(workbook: ExcelJS.Workbook, sheets: SheetSpec[]): void { const cell = sheet.getCell(r + 1, c + 1); if (cellSpec.formula !== undefined) { // No cached result — Excel/LibreOffice recalculate on open. - cell.value = { formula: cellSpec.formula } as ExcelJS.CellFormulaValue; + cell.value = { formula: cellSpec.formula }; } else if (cellSpec.value !== undefined) { cell.value = cellSpec.value as ExcelJS.CellValue; } diff --git a/test/helpers.ts b/test/helpers.ts index fcedd11..033205a 100644 --- a/test/helpers.ts +++ b/test/helpers.ts @@ -64,7 +64,7 @@ export function makeMinimalProject(): ProjectFile { }; } -export function parseCsv(text: string): Array> { +export function parseCsv(text: string): Record[] { const rows: string[][] = []; let field = ""; let row: string[] = []; diff --git a/test/level-blocks/collectors.test.ts b/test/level-blocks/collectors.test.ts index 6edd0b6..cc4f2cb 100644 --- a/test/level-blocks/collectors.test.ts +++ b/test/level-blocks/collectors.test.ts @@ -41,7 +41,7 @@ const byGain: Scorer = { score: (s) => s.annotations.get("gain") as number, }; -function streamOf(schedules: ReadonlyArray): ScheduleStreamImpl { +function streamOf(schedules: readonly Schedule[]): ScheduleStreamImpl { return new ScheduleStreamImpl({ async *[Symbol.asyncIterator]() { for (const s of schedules) yield s; diff --git a/test/level-blocks/concurrentResourceCost.test.ts b/test/level-blocks/concurrentResourceCost.test.ts index b570acc..c2d2225 100644 --- a/test/level-blocks/concurrentResourceCost.test.ts +++ b/test/level-blocks/concurrentResourceCost.test.ts @@ -30,7 +30,7 @@ function monFriCalendar(uniqueId = 1): Calendar { function makeSchedule(opts: { scheduledTasks: ScheduledTask[]; - assignments: Array<{ taskUniqueId: number; units: number }>; + assignments: { taskUniqueId: number; units: number }[]; resourceUniqueId: number; }): Schedule { const calendars = [monFriCalendar()]; diff --git a/test/level-blocks/graph.test.ts b/test/level-blocks/graph.test.ts index 99affa4..1260dd9 100644 --- a/test/level-blocks/graph.test.ts +++ b/test/level-blocks/graph.test.ts @@ -101,7 +101,7 @@ describe("validateGraph — rejections", () => { expect(result.ok).toBe(false); if (!result.ok) { expect( - result.issues.some((i) => i.instanceId === "b1" && /Invalid params/.test(i.message)), + result.issues.some((i) => i.instanceId === "b1" && i.message.includes("Invalid params")), ).toBe(true); } }); @@ -112,7 +112,7 @@ describe("validateGraph — rejections", () => { const result = validateGraph(registry, graph); expect(result.ok).toBe(false); if (!result.ok) { - expect(result.issues.some((i) => /exactly one search/.test(i.message))).toBe(true); + expect(result.issues.some((i) => i.message.includes("exactly one search"))).toBe(true); } }); @@ -128,7 +128,7 @@ describe("validateGraph — rejections", () => { const result = validateGraph(registry, graph); expect(result.ok).toBe(false); if (!result.ok) { - expect(result.issues.some((i) => /found 2/.test(i.message))).toBe(true); + expect(result.issues.some((i) => i.message.includes("found 2"))).toBe(true); } }); @@ -138,7 +138,7 @@ describe("validateGraph — rejections", () => { const result = validateGraph(registry, graph); expect(result.ok).toBe(false); if (!result.ok) { - expect(result.issues.some((i) => /Duplicate instanceId: b1/.test(i.message))).toBe(true); + expect(result.issues.some((i) => i.message.includes("Duplicate instanceId: b1"))).toBe(true); } }); diff --git a/test/level-blocks/hiringLagPenalty.test.ts b/test/level-blocks/hiringLagPenalty.test.ts index b9f99d8..2f6d348 100644 --- a/test/level-blocks/hiringLagPenalty.test.ts +++ b/test/level-blocks/hiringLagPenalty.test.ts @@ -32,7 +32,7 @@ function monFriCalendar(uniqueId = 1): Calendar { // the per-week resource profile under test. function makeSchedule(opts: { scheduledTasks: ScheduledTask[]; - assignments: Array<{ taskUniqueId: number; units: number }>; + assignments: { taskUniqueId: number; units: number }[]; resourceUniqueId: number; }): Schedule { const calendars = [monFriCalendar()]; diff --git a/test/level-blocks/meta.test.ts b/test/level-blocks/meta.test.ts index 6913200..9919270 100644 --- a/test/level-blocks/meta.test.ts +++ b/test/level-blocks/meta.test.ts @@ -159,7 +159,7 @@ describe("deriveBlockMeta — panelBinding-driven kinds (date, slider)", () => { apply: (i: unknown) => i, toMiniZinc: () => ({ text: "" }), doc: { nl: "synthetic", pseudocode: "" }, - } as never, + }, defaultShadow: { start: "2026-01-01", level: 5 }, panelBindings: [ { diff --git a/test/level-blocks/openUnitPenalty.test.ts b/test/level-blocks/openUnitPenalty.test.ts index 40e5fb2..a9f6454 100644 --- a/test/level-blocks/openUnitPenalty.test.ts +++ b/test/level-blocks/openUnitPenalty.test.ts @@ -30,7 +30,7 @@ function monFriCalendar(uniqueId = 1): Calendar { function makeSchedule(opts: { scheduledTasks: ScheduledTask[]; resourceUniqueIds: number[]; - assignments: Array<{ taskUniqueId: number; resourceUniqueId: number }>; + assignments: { taskUniqueId: number; resourceUniqueId: number }[]; workUnits?: WorkUnit[]; }): Schedule { const calendars = [monFriCalendar()]; diff --git a/test/level-blocks/pipelineFixtures.ts b/test/level-blocks/pipelineFixtures.ts index 655aed1..01fd300 100644 --- a/test/level-blocks/pipelineFixtures.ts +++ b/test/level-blocks/pipelineFixtures.ts @@ -62,7 +62,7 @@ export function makeDef( defaultShadow: unknown, tier: BlockTier = "estimator", ): BlockDef { - return { code, family, tier, block: block as never, defaultShadow }; + return { code, family, tier, block: block, defaultShadow }; } export function makeDefs(): BlockDef[] { diff --git a/test/level-blocks/provenance.test.ts b/test/level-blocks/provenance.test.ts index c700d8c..4699c72 100644 --- a/test/level-blocks/provenance.test.ts +++ b/test/level-blocks/provenance.test.ts @@ -71,7 +71,7 @@ describe("run provenance", () => { const json = new JsonWriter().write(leveled); const reread = new JsonReader().read(json); - expect(reread.levelset).toEqual(leveled.levelset!); + expect(reread.levelset).toEqual(leveled.levelset); }); test("unknown provenance keys survive the round trip (forward compatibility)", async () => { diff --git a/test/level-blocks/runConfig.test.ts b/test/level-blocks/runConfig.test.ts index e8168a0..7627586 100644 --- a/test/level-blocks/runConfig.test.ts +++ b/test/level-blocks/runConfig.test.ts @@ -336,7 +336,7 @@ describe("runFromConfig", () => { apply: ({ scorers }: { scorers: string[] }) => ({ kind: "KBest", k: 1, by: scorers }), toMiniZinc: () => ({ text: "% pick" }), doc: { nl: "pick", pseudocode: "pick" }, - } as never, + }, defaultShadow: { scorers: ["makespan"] }, }; diff --git a/test/level-blocks/standard.test.ts b/test/level-blocks/standard.test.ts index 32d2330..e2d7f6f 100644 --- a/test/level-blocks/standard.test.ts +++ b/test/level-blocks/standard.test.ts @@ -47,7 +47,7 @@ describe("standardRegistry", () => { apply: () => ({ tag: "probe" }), toMiniZinc: () => ({ text: "% probe" }), doc: { nl: "probe", pseudocode: "probe" }, - } as never, + }, defaultShadow: {}, }; const registry = standardRegistry([extra]); diff --git a/test/level-blocks/sweep.test.ts b/test/level-blocks/sweep.test.ts index f256818..b41a914 100644 --- a/test/level-blocks/sweep.test.ts +++ b/test/level-blocks/sweep.test.ts @@ -113,7 +113,7 @@ describe("GridSweep expansion and validation", () => { }); test("rejects incoherent zip axes, ragged records, bad paths, bad delivered values, and oversized grids", () => { - const cases: Array<[unknown, RegExp]> = [ + const cases: [unknown, RegExp][] = [ [ { ...configWithSweep(), @@ -223,11 +223,11 @@ describe("GridSweep execution", () => { test("uses each cell's compiled scorer parameters during cross-cell selection", async () => { const config = configWithSweep(); - const blocks: Array<{ + const blocks: { instanceId: string; code: string; params: Record; - }> = config.pipeline.blocks; + }[] = config.pipeline.blocks; blocks.splice(2, 0, { instanceId: "cost", code: STANDARD_CODES.concurrentResourceCost, @@ -253,11 +253,11 @@ describe("GridSweep execution", () => { test("throws SweepError after an all-infeasible grid", async () => { const base = configWithSweep(); - const blocks: Array<{ + const blocks: { instanceId: string; code: string; params: Record; - }> = base.pipeline.blocks; + }[] = base.pipeline.blocks; blocks.splice(2, 0, { instanceId: "deadline", code: STANDARD_CODES.deadline, @@ -284,11 +284,11 @@ describe("GridSweep execution", () => { test("skips and records an infeasible cell while retaining feasible candidates", async () => { const base = configWithSweep(); - const blocks: Array<{ + const blocks: { instanceId: string; code: string; params: Record; - }> = base.pipeline.blocks; + }[] = base.pipeline.blocks; blocks.splice(2, 0, { instanceId: "deadline", code: STANDARD_CODES.deadline, diff --git a/test/level-blocks/unimodalDeviation.test.ts b/test/level-blocks/unimodalDeviation.test.ts index 76b1db3..1ce8468 100644 --- a/test/level-blocks/unimodalDeviation.test.ts +++ b/test/level-blocks/unimodalDeviation.test.ts @@ -30,7 +30,7 @@ function monFriCalendar(uniqueId = 1): Calendar { function makeSchedule(opts: { scheduledTasks: ScheduledTask[]; - assignments: Array<{ taskUniqueId: number; units: number }>; + assignments: { taskUniqueId: number; units: number }[]; resourceUniqueId: number; }): Schedule { const calendars = [monFriCalendar()]; diff --git a/test/level-core/analyzeRun.test.ts b/test/level-core/analyzeRun.test.ts index 84628d8..7c9665c 100644 --- a/test/level-core/analyzeRun.test.ts +++ b/test/level-core/analyzeRun.test.ts @@ -33,7 +33,7 @@ function resolvedFixture(): ResolvedProject { }); } -function codes(issues: ReadonlyArray<{ code: string }>): string[] { +function codes(issues: readonly { code: string }[]): string[] { return issues.map((i) => i.code); } diff --git a/test/level-core/fixtures.ts b/test/level-core/fixtures.ts index dba2700..db91fd6 100644 --- a/test/level-core/fixtures.ts +++ b/test/level-core/fixtures.ts @@ -138,8 +138,8 @@ export const SAT_JAN_10 = new Date(2026, 0, 10); /** Run serialSGS expecting exactly one feasible schedule. */ export async function runOnce( project: ProjectFile, - constraints: ReadonlyArray, - workUnits?: ReadonlyArray, + constraints: readonly Constraint[], + workUnits?: readonly WorkUnit[], ): Promise { const resolved = resolveCalendar(project, { workUnits }); const stream = new ScheduleStreamImpl({ @@ -153,8 +153,8 @@ export async function runOnce( /** Run serialSGS expecting exhaustion — returns the Failure. */ export async function runExpectingFailure( project: ProjectFile, - constraints: ReadonlyArray, - workUnits?: ReadonlyArray, + constraints: readonly Constraint[], + workUnits?: readonly WorkUnit[], ): Promise { const resolved = resolveCalendar(project, { workUnits }); const gen = serialSGS.run(resolved, constraints); diff --git a/test/level-core/resolveCalendar.test.ts b/test/level-core/resolveCalendar.test.ts index 89d1d66..255e483 100644 --- a/test/level-core/resolveCalendar.test.ts +++ b/test/level-core/resolveCalendar.test.ts @@ -287,7 +287,7 @@ describe("resolveCalendar — assignments, resources, source preservation", () = id: 10, uniqueId: 10, name: "Crew", - type: "Work" as never, + type: "Work", email: null, group: null, maxUnits: 2, diff --git a/test/level-core/serialSGS.test.ts b/test/level-core/serialSGS.test.ts index bcbb5a4..6b703ac 100644 --- a/test/level-core/serialSGS.test.ts +++ b/test/level-core/serialSGS.test.ts @@ -113,8 +113,8 @@ const SAT_JAN_10 = new Date(2026, 0, 10); async function runOnce( project: ProjectFile, - constraints: ReadonlyArray, - workUnits?: ReadonlyArray, + constraints: readonly Constraint[], + workUnits?: readonly WorkUnit[], ): Promise { const resolved = resolveCalendar(project, { workUnits }); const stream = new ScheduleStreamImpl({ @@ -706,7 +706,7 @@ describe("serialSGS — unimplemented constraints", () => { ]); expect(schedule.tasks).toHaveLength(1); const unsupported = schedule.annotations.get("unsupportedConstraints") as - | ReadonlyArray + | readonly Explanation[] | undefined; expect(unsupported).toBeDefined(); expect(unsupported).toHaveLength(1); @@ -719,7 +719,7 @@ describe("serialSGS — unimplemented constraints", () => { { kind: "UnitPrecedence", unitId: 20, afterUnitIds: [10] }, ]); const unsupported = schedule.annotations.get("unsupportedConstraints") as - | ReadonlyArray + | readonly Explanation[] | undefined; expect(unsupported).toBeDefined(); expect(unsupported![0]!.message).toMatch(/UnitPrecedence.*not implemented/); @@ -776,7 +776,7 @@ describe("serialSGS — FF/SF approximation", () => { expect(t1sched.finishDay).toBe(5); expect(t2sched.startDay).toBe(0); const approximated = schedule.annotations.get("approximatedRelations") as - | ReadonlyArray + | readonly Explanation[] | undefined; expect(approximated).toBeDefined(); expect(approximated!.length).toBe(1); @@ -800,7 +800,7 @@ describe("serialSGS — FF/SF approximation", () => { }); const schedule = await runOnce(makeProject([t1, t2]), []); const approximated = schedule.annotations.get("approximatedRelations") as - | ReadonlyArray + | readonly Explanation[] | undefined; expect(approximated).toBeDefined(); expect(approximated!.length).toBe(1); diff --git a/test/level-core/weeklyProfile.test.ts b/test/level-core/weeklyProfile.test.ts index 4ae286a..d7c2c69 100644 --- a/test/level-core/weeklyProfile.test.ts +++ b/test/level-core/weeklyProfile.test.ts @@ -34,8 +34,8 @@ function monFriCalendar(uniqueId = 1): Calendar { function makeSchedule(opts: { scheduledTasks: ScheduledTask[]; - assignments: Array<{ taskUniqueId: number; resourceUniqueId: number; units: number }>; - resourceUniqueIds: ReadonlyArray; + assignments: { taskUniqueId: number; resourceUniqueId: number; units: number }[]; + resourceUniqueIds: readonly number[]; epoch?: Date; }): Schedule { const calendars = [monFriCalendar()]; diff --git a/test/library.test.ts b/test/library.test.ts index 57ba1b5..972b13a 100644 --- a/test/library.test.ts +++ b/test/library.test.ts @@ -4,7 +4,7 @@ import { fileURLToPath } from "node:url"; import { MspdiReader } from "../src/mspdi/MspdiReader.ts"; import { MspdiWriter } from "../src/mspdi/MspdiWriter.ts"; import { formatProjectDate } from "../src/dateTime.ts"; -import { TimeUnit } from "../src/model/types.ts"; +import { type TimeUnit } from "../src/model/types.ts"; import type { ProjectFile } from "../src/model/Project.ts"; import { parseCsv, fixtureExists } from "./helpers.ts"; -- 2.54.0