@alpaca-software/40kdc-data 0.1.2 → 0.2.0
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/README.md +2 -0
- package/dist/abilities-resolver/resolver.d.ts +13 -4
- package/dist/abilities-resolver/resolver.d.ts.map +1 -1
- package/dist/abilities-resolver/resolver.js +22 -15
- package/dist/abilities-resolver/resolver.js.map +1 -1
- package/dist/audit-coverage.d.ts +78 -0
- package/dist/audit-coverage.d.ts.map +1 -0
- package/dist/audit-coverage.js +341 -0
- package/dist/audit-coverage.js.map +1 -0
- package/dist/author-batch.d.ts +147 -0
- package/dist/author-batch.d.ts.map +1 -0
- package/dist/author-batch.js +675 -0
- package/dist/author-batch.js.map +1 -0
- package/dist/author-input.d.ts +37 -0
- package/dist/author-input.d.ts.map +1 -0
- package/dist/author-input.js +162 -0
- package/dist/author-input.js.map +1 -0
- package/dist/cli.js +7 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/translate.d.ts.map +1 -1
- package/dist/commands/translate.js +9 -4
- package/dist/commands/translate.js.map +1 -1
- package/dist/cruncher/attribution.d.ts +66 -0
- package/dist/cruncher/attribution.d.ts.map +1 -0
- package/dist/cruncher/attribution.js +88 -0
- package/dist/cruncher/attribution.js.map +1 -0
- package/dist/cruncher/buffs.d.ts +80 -2
- package/dist/cruncher/buffs.d.ts.map +1 -1
- package/dist/cruncher/buffs.js +33 -4
- package/dist/cruncher/buffs.js.map +1 -1
- package/dist/cruncher/engine.d.ts.map +1 -1
- package/dist/cruncher/engine.js +50 -15
- package/dist/cruncher/engine.js.map +1 -1
- package/dist/cruncher/from-dsl.d.ts +32 -0
- package/dist/cruncher/from-dsl.d.ts.map +1 -1
- package/dist/cruncher/from-dsl.js +605 -45
- package/dist/cruncher/from-dsl.js.map +1 -1
- package/dist/cruncher/index.d.ts +1 -0
- package/dist/cruncher/index.d.ts.map +1 -1
- package/dist/cruncher/index.js +1 -0
- package/dist/cruncher/index.js.map +1 -1
- package/dist/data/bundle.generated.js +1 -1
- package/dist/data/bundle.generated.js.map +1 -1
- package/dist/data/collection.d.ts +9 -0
- package/dist/data/collection.d.ts.map +1 -1
- package/dist/data/collection.js +14 -0
- package/dist/data/collection.js.map +1 -1
- package/dist/data/dataset.d.ts +80 -2
- package/dist/data/dataset.d.ts.map +1 -1
- package/dist/data/dataset.js +143 -6
- package/dist/data/dataset.js.map +1 -1
- package/dist/data/entities.d.ts +2 -5
- package/dist/data/entities.d.ts.map +1 -1
- package/dist/data/entities.js.map +1 -1
- package/dist/data/index.d.ts +3 -2
- package/dist/data/index.d.ts.map +1 -1
- package/dist/data/index.js +1 -1
- package/dist/data/index.js.map +1 -1
- package/dist/data/normalize.d.ts.map +1 -1
- package/dist/data/normalize.js +8 -1
- package/dist/data/normalize.js.map +1 -1
- package/dist/data/roster-resolve.d.ts +26 -1
- package/dist/data/roster-resolve.d.ts.map +1 -1
- package/dist/data/roster-resolve.js +46 -0
- package/dist/data/roster-resolve.js.map +1 -1
- package/dist/export/index.d.ts +1 -0
- package/dist/export/index.d.ts.map +1 -1
- package/dist/export/index.js +3 -0
- package/dist/export/index.js.map +1 -1
- package/dist/export/rosterizer.d.ts +3 -0
- package/dist/export/rosterizer.d.ts.map +1 -0
- package/dist/export/rosterizer.js +144 -0
- package/dist/export/rosterizer.js.map +1 -0
- package/dist/export/serializer.d.ts +1 -1
- package/dist/export/serializer.d.ts.map +1 -1
- package/dist/export/serializer.js.map +1 -1
- package/dist/gen-conformance.js +212 -11
- package/dist/gen-conformance.js.map +1 -1
- package/dist/import/gw.d.ts +69 -0
- package/dist/import/gw.d.ts.map +1 -0
- package/dist/import/gw.js +245 -0
- package/dist/import/gw.js.map +1 -0
- package/dist/import/import-roster.d.ts +52 -3
- package/dist/import/import-roster.d.ts.map +1 -1
- package/dist/import/import-roster.js +114 -4
- package/dist/import/import-roster.js.map +1 -1
- package/dist/import/index.d.ts +2 -2
- package/dist/import/index.d.ts.map +1 -1
- package/dist/import/index.js +1 -1
- package/dist/import/index.js.map +1 -1
- package/dist/import/listforge.d.ts.map +1 -1
- package/dist/import/listforge.js +15 -1
- package/dist/import/listforge.js.map +1 -1
- package/dist/import/newrecruit-text.d.ts +3 -0
- package/dist/import/newrecruit-text.d.ts.map +1 -1
- package/dist/import/newrecruit-text.js +6 -0
- package/dist/import/newrecruit-text.js.map +1 -1
- package/dist/import/newrecruit-wtc.d.ts.map +1 -1
- package/dist/import/newrecruit-wtc.js +10 -7
- package/dist/import/newrecruit-wtc.js.map +1 -1
- package/dist/import/rosterizer.d.ts +70 -0
- package/dist/import/rosterizer.d.ts.map +1 -0
- package/dist/import/rosterizer.js +348 -0
- package/dist/import/rosterizer.js.map +1 -0
- package/dist/import/types.d.ts +1 -1
- package/dist/import/types.d.ts.map +1 -1
- package/dist/import/types.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/migrations/2026-weapon-keywords.js +4 -0
- package/dist/migrations/2026-weapon-keywords.js.map +1 -1
- package/dist/runner.d.ts +38 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +537 -0
- package/dist/runner.js.map +1 -0
- package/dist/scrub-defensive-flag.d.ts +23 -0
- package/dist/scrub-defensive-flag.d.ts.map +1 -0
- package/dist/scrub-defensive-flag.js +149 -0
- package/dist/scrub-defensive-flag.js.map +1 -0
- package/dist/scrub-ip.d.ts +14 -0
- package/dist/scrub-ip.d.ts.map +1 -0
- package/dist/scrub-ip.js +88 -0
- package/dist/scrub-ip.js.map +1 -0
- package/package.json +10 -2
- package/schemas/core/roster.schema.json +3 -1
- package/schemas/enrichment/ability-dsl/effect.schema.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n/**\n * NDJSON conformance runner — the TypeScript implementation of the wire\n * protocol in `conformance/RUNNER_PROTOCOL.md`. Each line on stdin is a JSON\n * request `{op, args?}`; each line on stdout is a JSON response\n * `{ok: true, value}` or `{ok: false, error_kind, error_payload?}`.\n *\n * This file exports {@link processRequest} so vitest can drive the runner\n * in-process without spawning a child; the bottom `if (isCliMain())` block\n * wires the same dispatcher to real stdin/stdout for the cross-impl differ.\n *\n * The runner is a *thin* wrapper over the existing public API — it is not the\n * canonical way to use the package. Library consumers should import the\n * functions directly; the runner exists to give the cross-implementation\n * differ a uniform interface across language ports.\n */\nimport { createInterface } from \"node:readline\";\nimport { readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport process from \"node:process\";\n\nimport { Dataset } from \"./data/dataset.js\";\nimport { normalizeName } from \"./data/normalize.js\";\nimport { exportRoster, type ExportFormat } from \"./export/index.js\";\nimport { importRoster, tryImportRoster, REGISTERED_ADAPTERS } from \"./import/import-roster.js\";\nimport { selectAdapter } from \"./import/adapter.js\";\nimport { createValidator } from \"./schema-loader.js\";\nimport { attributeStages, crunch, type Buff, type EngineContext, type EngineInput } from \"./cruncher/index.js\";\nimport type Ajv from \"ajv\";\n\n// -----------------------------------------------------------------------------\n// Constants — spec version and implementation identity.\n// -----------------------------------------------------------------------------\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nfunction loadSpecVersion(): number {\n // The corpus lives at repo-root/conformance/. Walk up from tools/src/ until\n // we find it so the runner works both from source (tsx) and from dist/.\n for (const candidate of [\n join(__dirname, \"../../conformance/SPEC_VERSION\"),\n join(__dirname, \"../../../conformance/SPEC_VERSION\"),\n ]) {\n try {\n return Number.parseInt(readFileSync(candidate, \"utf8\").trim(), 10);\n } catch {\n // try next\n }\n }\n throw new Error(\"could not locate conformance/SPEC_VERSION\");\n}\n\nfunction loadImplVersion(): string {\n for (const candidate of [\n join(__dirname, \"../package.json\"),\n join(__dirname, \"../../package.json\"),\n ]) {\n try {\n return (JSON.parse(readFileSync(candidate, \"utf8\")) as { version: string }).version;\n } catch {\n // try next\n }\n }\n return \"unknown\";\n}\n\nconst SPEC_VERSION = loadSpecVersion();\nconst IMPL_VERSION = loadImplVersion();\nconst IMPL_NAME = \"ts\";\n\n// -----------------------------------------------------------------------------\n// Error envelope helpers and the closed `error_kind` enum.\n// -----------------------------------------------------------------------------\n\nexport type RunnerResponse =\n | { ok: true; value: unknown }\n | { ok: false; error_kind: ErrorKind; error_payload?: unknown };\n\nconst ERROR_KINDS = [\n \"INVALID_INPUT\",\n \"UNKNOWN_OP\",\n \"UNKNOWN_ENTITY\",\n \"IMPORT_FAILED\",\n \"EXPORT_FAILED\",\n \"VALIDATION_ERROR\",\n \"CRUNCH_ERROR\",\n \"INTERNAL_ERROR\",\n] as const;\ntype ErrorKind = (typeof ERROR_KINDS)[number];\n\nfunction ok(value: unknown): RunnerResponse {\n return { ok: true, value };\n}\nfunction err(kind: ErrorKind, payload?: unknown): RunnerResponse {\n return payload === undefined\n ? { ok: false, error_kind: kind }\n : { ok: false, error_kind: kind, error_payload: payload };\n}\n\n// -----------------------------------------------------------------------------\n// Runner state — init must be the first request; subsequent ops fail with\n// INVALID_INPUT until init succeeds. Dataset and validator are lazy.\n// -----------------------------------------------------------------------------\n\nexport interface RunnerState {\n initialized: boolean;\n locale: string;\n tz: string;\n seed: number;\n dataset?: Dataset;\n validator?: Ajv;\n}\n\nexport function createRunnerState(): RunnerState {\n return { initialized: false, locale: \"C\", tz: \"UTC\", seed: 0 };\n}\n\nfunction getDataset(state: RunnerState): Dataset {\n if (!state.dataset) state.dataset = Dataset.embedded();\n return state.dataset;\n}\n\nfunction getValidator(state: RunnerState): Ajv {\n if (!state.validator) state.validator = createValidator();\n return state.validator;\n}\n\n// -----------------------------------------------------------------------------\n// Op handlers.\n// -----------------------------------------------------------------------------\n\nfunction handleInit(state: RunnerState, args: unknown): RunnerResponse {\n if (state.initialized) {\n return err(\"INVALID_INPUT\", { detail: \"init called twice\" });\n }\n if (typeof args !== \"object\" || args === null) {\n return err(\"INVALID_INPUT\", { detail: \"init args must be an object\" });\n }\n const a = args as { spec_version?: unknown; locale?: unknown; tz?: unknown; seed?: unknown };\n if (a.spec_version !== SPEC_VERSION) {\n return err(\"INVALID_INPUT\", {\n detail: `spec_version mismatch: runner=${SPEC_VERSION}, request=${String(a.spec_version)}`,\n });\n }\n if (a.locale !== \"C\") {\n return err(\"INVALID_INPUT\", { detail: `unsupported locale: ${String(a.locale)} (only \"C\")` });\n }\n if (a.tz !== \"UTC\") {\n return err(\"INVALID_INPUT\", { detail: `unsupported tz: ${String(a.tz)} (only \"UTC\")` });\n }\n if (typeof a.seed !== \"number\") {\n return err(\"INVALID_INPUT\", { detail: \"seed must be a number\" });\n }\n state.initialized = true;\n state.locale = a.locale;\n state.tz = a.tz;\n state.seed = a.seed;\n return ok({ impl: IMPL_NAME, spec_version: SPEC_VERSION, impl_version: IMPL_VERSION });\n}\n\nfunction handleNormalize(args: unknown): RunnerResponse {\n if (typeof args !== \"object\" || args === null) {\n return err(\"INVALID_INPUT\", { detail: \"normalize args must be an object\" });\n }\n const a = args as { input?: unknown };\n if (typeof a.input !== \"string\") {\n return err(\"INVALID_INPUT\", { detail: \"normalize.input must be a string\" });\n }\n return ok(normalizeName(a.input));\n}\n\nfunction handleImport(state: RunnerState, args: unknown): RunnerResponse {\n if (typeof args !== \"object\" || args === null) {\n return err(\"INVALID_INPUT\", { detail: \"import args must be an object\" });\n }\n const a = args as { format?: unknown; input?: unknown };\n if (typeof a.input !== \"string\") {\n return err(\"INVALID_INPUT\", { detail: \"import.input must be a string\" });\n }\n try {\n // The wire protocol carries every input as a string: JSON payloads come\n // through as the JSON text, text payloads come through as-is. The import\n // pipeline decides which by attempting to parse JSON first.\n const trimmed = a.input.trimStart();\n let decoded: unknown;\n if (trimmed.startsWith(\"{\") || trimmed.startsWith(\"[\")) {\n try {\n decoded = JSON.parse(a.input);\n } catch {\n decoded = a.input;\n }\n } else {\n decoded = a.input;\n }\n const roster = importRoster(decoded, { dataset: getDataset(state) });\n return ok(roster);\n } catch (e) {\n return err(\"IMPORT_FAILED\", { detail: (e as Error).message, format: a.format ?? null });\n }\n}\n\nfunction handleTryImport(state: RunnerState, args: unknown): RunnerResponse {\n if (typeof args !== \"object\" || args === null) {\n return err(\"INVALID_INPUT\", { detail: \"try_import args must be an object\" });\n }\n const a = args as { input?: unknown };\n if (typeof a.input !== \"string\") {\n return err(\"INVALID_INPUT\", { detail: \"try_import.input must be a string\" });\n }\n const result = tryImportRoster(a.input, { dataset: getDataset(state) });\n if (!result.ok) {\n return err(\"IMPORT_FAILED\", { reason: result.reason, message: result.message });\n }\n return ok({ format: result.format, roster: result.roster });\n}\n\nconst EXPORT_FORMATS: ExportFormat[] = [\n \"newrecruit-json\",\n \"newrecruit-wtc-compact\",\n \"newrecruit-wtc-full\",\n \"newrecruit-simple\",\n \"roster-json\",\n \"rosterizer\",\n];\n\nfunction handleExport(args: unknown): RunnerResponse {\n if (typeof args !== \"object\" || args === null) {\n return err(\"INVALID_INPUT\", { detail: \"export args must be an object\" });\n }\n const a = args as { format?: unknown; roster?: unknown };\n if (typeof a.format !== \"string\" || !EXPORT_FORMATS.includes(a.format as ExportFormat)) {\n return err(\"INVALID_INPUT\", { detail: `unknown export format: ${String(a.format)}` });\n }\n if (typeof a.roster !== \"object\" || a.roster === null) {\n return err(\"INVALID_INPUT\", { detail: \"export.roster must be an object\" });\n }\n try {\n // We assume the caller passes the canonical resolved Roster shape; if they\n // pass something the serializer can't handle, this throws.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n return ok(exportRoster(a.roster as any, a.format as ExportFormat));\n } catch (e) {\n return err(\"EXPORT_FAILED\", { detail: (e as Error).message });\n }\n}\n\nfunction handleLinkedQuery(state: RunnerState, args: unknown): RunnerResponse {\n if (typeof args !== \"object\" || args === null) {\n return err(\"INVALID_INPUT\", { detail: \"linked_query args must be an object\" });\n }\n const a = args as { query?: unknown; input?: unknown };\n if (typeof a.query !== \"string\") {\n return err(\"INVALID_INPUT\", { detail: \"linked_query.query must be a string\" });\n }\n const ds = getDataset(state);\n const input = (a.input ?? {}) as Record<string, string>;\n try {\n switch (a.query) {\n case \"find_unit\":\n return ok(ds.units.find(input.query ?? \"\")?.id ?? null);\n case \"find_weapon\":\n return ok(ds.weapons.find(input.query ?? \"\")?.id ?? null);\n case \"find_faction\":\n return ok(ds.factions.find(input.query ?? \"\")?.id ?? null);\n case \"find_ability\":\n return ok(ds.abilities.find(input.query ?? \"\")?.id ?? null);\n case \"abilities_of\": {\n const u = ds.units.get(input.unitId);\n if (!u) return err(\"UNKNOWN_ENTITY\", { kind: \"unit\", id: input.unitId });\n return ok(u.abilities.map((x) => x.id));\n }\n case \"weapons_of\": {\n const u = ds.units.get(input.unitId);\n if (!u) return err(\"UNKNOWN_ENTITY\", { kind: \"unit\", id: input.unitId });\n return ok(u.weapons.map((x) => x.id));\n }\n case \"phases_of\": {\n const ab = ds.abilities.get(input.abilityId);\n if (!ab) return err(\"UNKNOWN_ENTITY\", { kind: \"ability\", id: input.abilityId });\n return ok([...ab.phases]);\n }\n case \"faction_of\": {\n const u = ds.units.get(input.unitId);\n if (!u) return err(\"UNKNOWN_ENTITY\", { kind: \"unit\", id: input.unitId });\n return ok(u.faction?.id ?? null);\n }\n case \"abilities_of_faction\":\n return ok(ds.abilities.byFaction(input.factionId).map((x) => x.id));\n case \"weapons_of_faction\": {\n const f = ds.factions.get(input.factionId);\n if (!f) return err(\"UNKNOWN_ENTITY\", { kind: \"faction\", id: input.factionId });\n return ok(f.weapons.map((x) => x.id));\n }\n default:\n return err(\"INVALID_INPUT\", { detail: `unknown linked_query: ${a.query}` });\n }\n } catch (e) {\n return err(\"INTERNAL_ERROR\", { detail: (e as Error).message });\n }\n}\n\nconst VALIDATOR_TARGETS: Record<string, string> = {\n unit: \"https://40kdc.dev/schemas/core/unit.schema.json\",\n weapon: \"https://40kdc.dev/schemas/core/weapon.schema.json\",\n faction: \"https://40kdc.dev/schemas/core/faction.schema.json\",\n ability: \"https://40kdc.dev/schemas/enrichment/ability-dsl/ability.schema.json\",\n};\n\nfunction ajvKeywordToCode(keyword: string): string {\n switch (keyword) {\n case \"required\":\n return \"REQUIRED_MISSING\";\n case \"type\":\n return \"TYPE_MISMATCH\";\n case \"enum\":\n return \"ENUM_VIOLATION\";\n case \"pattern\":\n case \"format\":\n return \"PATTERN_MISMATCH\";\n case \"minimum\":\n case \"maximum\":\n case \"exclusiveMinimum\":\n case \"exclusiveMaximum\":\n case \"minLength\":\n case \"maxLength\":\n case \"minItems\":\n case \"maxItems\":\n return \"RANGE_VIOLATION\";\n case \"additionalProperties\":\n return \"ADDITIONAL_PROPERTY\";\n case \"uniqueItems\":\n return \"UNIQUE_VIOLATION\";\n default:\n return `UNMAPPED:${keyword}`;\n }\n}\n\nfunction handleValidate(state: RunnerState, args: unknown): RunnerResponse {\n if (typeof args !== \"object\" || args === null) {\n return err(\"INVALID_INPUT\", { detail: \"validate args must be an object\" });\n }\n const a = args as { target?: unknown; value?: unknown };\n if (typeof a.target !== \"string\" || !(a.target in VALIDATOR_TARGETS)) {\n return err(\"INVALID_INPUT\", { detail: `unknown validator target: ${String(a.target)}` });\n }\n let validate;\n try {\n validate = getValidator(state).getSchema(VALIDATOR_TARGETS[a.target]);\n } catch (e) {\n return err(\"VALIDATION_ERROR\", { detail: (e as Error).message });\n }\n if (!validate) {\n return err(\"VALIDATION_ERROR\", { detail: `schema not loaded: ${a.target}` });\n }\n validate(a.value);\n const raw = validate.errors ?? [];\n const seen = new Set<string>();\n const out: { path: string; code: string }[] = [];\n for (const e of raw) {\n const code = ajvKeywordToCode(e.keyword);\n if (code.startsWith(\"UNMAPPED:\")) continue;\n const path =\n e.keyword === \"required\"\n ? `${e.instancePath}/${(e.params as { missingProperty?: string }).missingProperty ?? \"\"}`\n : e.instancePath;\n const key = `${path}|${code}`;\n if (seen.has(key)) continue;\n seen.add(key);\n out.push({ path, code });\n }\n return ok(out);\n}\n\ninterface CrunchArgs {\n attacker?: { weaponId?: string; profileIndex?: number };\n modelsFiring?: number;\n target?: { unitId?: string; profileIndex?: number; modelCount?: number };\n context?: EngineContext;\n buffs?: Buff[];\n}\n\n/**\n * Validate the wire-shape `crunch`/`attribution` args and assemble the\n * {@link EngineInput} both ops share, or return a typed runner error. The two\n * ops have identical inputs — separating this lets each handler stay tiny and\n * keeps the validation contract in one place.\n */\nfunction buildEngineInput(\n state: RunnerState,\n a: CrunchArgs,\n opName: string,\n): { ok: true; input: EngineInput } | { ok: false; response: RunnerResponse } {\n if (!a.attacker?.weaponId || typeof a.attacker.profileIndex !== \"number\") {\n return { ok: false, response: err(\"INVALID_INPUT\", { detail: `${opName}.attacker.weaponId/profileIndex required` }) };\n }\n if (!a.target?.unitId || typeof a.target.profileIndex !== \"number\") {\n return { ok: false, response: err(\"INVALID_INPUT\", { detail: `${opName}.target.unitId/profileIndex required` }) };\n }\n if (typeof a.modelsFiring !== \"number\") {\n return { ok: false, response: err(\"INVALID_INPUT\", { detail: `${opName}.modelsFiring required` }) };\n }\n if (!a.context) {\n return { ok: false, response: err(\"INVALID_INPUT\", { detail: `${opName}.context required` }) };\n }\n const ds = getDataset(state);\n const weapon = ds.weapons.get(a.attacker.weaponId);\n if (!weapon) return { ok: false, response: err(\"UNKNOWN_ENTITY\", { kind: \"weapon\", id: a.attacker.weaponId }) };\n const unit = ds.units.get(a.target.unitId);\n if (!unit) return { ok: false, response: err(\"UNKNOWN_ENTITY\", { kind: \"unit\", id: a.target.unitId }) };\n const input: EngineInput = {\n attacker: { weapon: weapon.raw, profileIndex: a.attacker.profileIndex },\n target: {\n unit: unit.raw,\n profileIndex: a.target.profileIndex,\n ...(a.target.modelCount !== undefined ? { modelCount: a.target.modelCount } : {}),\n },\n modelsFiring: a.modelsFiring,\n buffs: a.buffs ?? [],\n context: a.context,\n };\n return { ok: true, input };\n}\n\nfunction handleCrunch(state: RunnerState, args: unknown): RunnerResponse {\n if (typeof args !== \"object\" || args === null) {\n return err(\"INVALID_INPUT\", { detail: \"crunch args must be an object\" });\n }\n const built = buildEngineInput(state, args as CrunchArgs, \"crunch\");\n if (!built.ok) return built.response;\n try {\n // Canonical wire shape: stages array only. `resolved` is impl-internal\n // (TS/Rust shape diverges); per-stage `detail` strings aren't byte-equal\n // across impls. The differ compares per-stage `expected` with 5e-4\n // tolerance — that's the cross-impl contract.\n const out = crunch(built.input, getDataset(state));\n return ok({ stages: out.stages.map((s) => ({ name: s.name, expected: s.expected })) });\n } catch (e) {\n return err(\"CRUNCH_ERROR\", { detail: (e as Error).message });\n }\n}\n\nfunction handleAttribution(state: RunnerState, args: unknown): RunnerResponse {\n if (typeof args !== \"object\" || args === null) {\n return err(\"INVALID_INPUT\", { detail: \"attribution args must be an object\" });\n }\n const a = args as CrunchArgs & { epsilon?: number };\n const built = buildEngineInput(state, a, \"attribution\");\n if (!built.ok) return built.response;\n try {\n const opts = typeof a.epsilon === \"number\" ? { epsilon: a.epsilon } : undefined;\n // Drop `detail` from the wire (impl-specific formatting). Keep all\n // numeric fields and the kind-tagged BuffSource shape that's already\n // serde-compatible.\n const stages = attributeStages(built.input, getDataset(state), opts);\n return ok(\n stages.map((s) => ({\n name: s.name,\n expected: s.expected,\n baseline: s.baseline,\n lifts: s.lifts,\n residual: s.residual,\n intrinsics: s.intrinsics,\n })),\n );\n } catch (e) {\n return err(\"CRUNCH_ERROR\", { detail: (e as Error).message });\n }\n}\n\n// -----------------------------------------------------------------------------\n// Dispatcher and per-line entry point.\n// -----------------------------------------------------------------------------\n\n/**\n * Apply one decoded request to the runner state and return the response. Used\n * directly by tests; the CLI loop wraps it with line parsing.\n */\nexport function dispatch(state: RunnerState, req: { op: string; args?: unknown }): RunnerResponse {\n if (!state.initialized && req.op !== \"init\") {\n return err(\"INVALID_INPUT\", { detail: \"must init before any other op\" });\n }\n switch (req.op) {\n case \"init\":\n return handleInit(state, req.args);\n case \"version\":\n return ok({ impl: IMPL_NAME, spec_version: SPEC_VERSION, impl_version: IMPL_VERSION });\n case \"normalize\":\n return handleNormalize(req.args);\n case \"import\":\n return handleImport(state, req.args);\n case \"try_import\":\n return handleTryImport(state, req.args);\n case \"export\":\n return handleExport(req.args);\n case \"linked_query\":\n return handleLinkedQuery(state, req.args);\n case \"validate\":\n return handleValidate(state, req.args);\n case \"crunch\":\n return handleCrunch(state, req.args);\n case \"attribution\":\n return handleAttribution(state, req.args);\n case \"shutdown\":\n return ok(null);\n default:\n return err(\"UNKNOWN_OP\", { op: req.op });\n }\n}\n\n/**\n * Process one line of stdin (one NDJSON request) and return the line that\n * should be written to stdout (one NDJSON response). Returns `null` only on\n * fully-empty input lines, which should be silently ignored.\n */\nexport function processRequest(state: RunnerState, line: string): string | null {\n const trimmed = line.trim();\n if (trimmed === \"\") return null;\n let req: { op?: unknown; args?: unknown };\n try {\n req = JSON.parse(trimmed);\n } catch (e) {\n return JSON.stringify(err(\"INVALID_INPUT\", { detail: `not valid JSON: ${(e as Error).message}` }));\n }\n if (typeof req.op !== \"string\") {\n return JSON.stringify(err(\"INVALID_INPUT\", { detail: \"request must have a string `op` field\" }));\n }\n const response = dispatch(state, { op: req.op, args: req.args });\n return JSON.stringify(response);\n}\n\n// -----------------------------------------------------------------------------\n// CLI: wire stdin/stdout. Only runs when this file is the process entry point.\n// -----------------------------------------------------------------------------\n\nfunction isCliMain(): boolean {\n // process.argv[1] is the path the runtime invoked. With tsx and node both,\n // it points at this file (or its compiled .js form). Comparing absolute\n // paths is the most robust portable check.\n if (!process.argv[1]) return false;\n const entry = process.argv[1];\n const here = fileURLToPath(import.meta.url);\n return entry === here || entry.endsWith(\"/runner.js\") || entry.endsWith(\"/runner.ts\");\n}\n\nasync function runCli(): Promise<void> {\n const state = createRunnerState();\n const rl = createInterface({ input: process.stdin });\n // We must respond on the same tick the request arrives — the differ\n // pipelines requests and expects responses in order. readline preserves\n // line ordering, so processing inside `line` handler is sufficient.\n for await (const line of rl) {\n const out = processRequest(state, line);\n if (out !== null) {\n process.stdout.write(out + \"\\n\");\n }\n // `shutdown` returns `ok(null)`; honor it by exiting after the response\n // is flushed.\n try {\n const req = JSON.parse(line);\n if (req && req.op === \"shutdown\") {\n process.exit(0);\n }\n } catch {\n // already handled above; not a shutdown\n }\n }\n}\n\nif (isCliMain()) {\n runCli().catch((e) => {\n // Last-ditch INTERNAL_ERROR so the differ sees a typed failure rather\n // than an opaque crash.\n process.stdout.write(\n JSON.stringify({\n ok: false,\n error_kind: \"INTERNAL_ERROR\",\n error_payload: { detail: (e as Error).message },\n }) + \"\\n\",\n );\n process.exit(1);\n });\n}\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** The exact sentinel the audit pass historically wrote — only this string scrubs. */
|
|
2
|
+
export declare const STALE_FLAG = "defensive ability (skipped for damage calc)";
|
|
3
|
+
interface AbilityEntry {
|
|
4
|
+
ability_id?: string;
|
|
5
|
+
ability_type?: string;
|
|
6
|
+
effect?: unknown;
|
|
7
|
+
community_notes?: string;
|
|
8
|
+
[k: string]: unknown;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Does this ability's effect produce any defender-side buff today? Mirrors the
|
|
12
|
+
* audit-coverage walk: every phase, target perspective, permissive context.
|
|
13
|
+
*/
|
|
14
|
+
export declare function producesDefensiveBuff(entry: AbilityEntry): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Strip the stale flag from one in-memory abilities array; return the number
|
|
17
|
+
* of entries whose flag was cleared. The note field is deleted outright when
|
|
18
|
+
* it held only the sentinel (the common case); other-note coexistence isn't
|
|
19
|
+
* a thing in the current corpus (verified — every match is exact-equal).
|
|
20
|
+
*/
|
|
21
|
+
export declare function scrubDefensiveFlags(abilities: AbilityEntry[]): number;
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=scrub-defensive-flag.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scrub-defensive-flag.d.ts","sourceRoot":"","sources":["../src/scrub-defensive-flag.ts"],"names":[],"mappings":"AAuCA,sFAAsF;AACtF,eAAO,MAAM,UAAU,gDAAgD,CAAC;AAIxE,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAQlE;AA8BD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,YAAY,EAAE,GAAG,MAAM,CASrE"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Defensive-flag scrub: remove the stale `"defensive ability (skipped for damage
|
|
3
|
+
* calc)"` `community_notes` marker from ability entries whose effect now
|
|
4
|
+
* translates into real defender-side buffs.
|
|
5
|
+
*
|
|
6
|
+
* The marker was set when the cruncher couldn't read `damage-reduction`,
|
|
7
|
+
* `invulnerable-save`, and `feel-no-pain {scope:"mortal"}`. With those landed
|
|
8
|
+
* (#22), entries carrying valid DSL for those effects are no longer "skipped"
|
|
9
|
+
* — leaving the flag in place would lie to downstream consumers (the SPA
|
|
10
|
+
* surfaces it as "this is opaque to the engine").
|
|
11
|
+
*
|
|
12
|
+
* Decision per entry:
|
|
13
|
+
* - Walk the effect through `effectToBuffs(perspective:"target")` across
|
|
14
|
+
* every phase, with a permissive context (mirrors `audit-coverage.ts`).
|
|
15
|
+
* - If ANY phase yields `applied.length > 0`, the engine reads the ability
|
|
16
|
+
* today — strip the flag (delete `community_notes` outright if that was
|
|
17
|
+
* the only note; otherwise leave the field untouched, since we don't own
|
|
18
|
+
* other authored notes).
|
|
19
|
+
* - Otherwise leave the flag in place. Those are the genuine residue —
|
|
20
|
+
* `ability-grant`, `mortal-wounds`, the translator gap on
|
|
21
|
+
* `roll-modifier {target:"attacker"}`, etc. — for the follow-on grind.
|
|
22
|
+
*
|
|
23
|
+
* Idempotent: re-running is a no-op. The scrub only acts on the exact-match
|
|
24
|
+
* sentinel string; refined notes from a future pass stay put.
|
|
25
|
+
*
|
|
26
|
+
* Usage: npx tsx tools/src/scrub-defensive-flag.ts (writes in place)
|
|
27
|
+
* npx tsx tools/src/scrub-defensive-flag.ts --check (report only)
|
|
28
|
+
*/
|
|
29
|
+
import { readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
30
|
+
import { resolve } from "node:path";
|
|
31
|
+
import { fileURLToPath } from "node:url";
|
|
32
|
+
import { effectToBuffs } from "./cruncher/from-dsl.js";
|
|
33
|
+
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
|
34
|
+
const ENRICHMENT_ROOT = resolve(__dirname, "../../data/enrichment");
|
|
35
|
+
/** The exact sentinel the audit pass historically wrote — only this string scrubs. */
|
|
36
|
+
export const STALE_FLAG = "defensive ability (skipped for damage calc)";
|
|
37
|
+
const PHASES = ["command", "movement", "shooting", "charge", "fight"];
|
|
38
|
+
/**
|
|
39
|
+
* Does this ability's effect produce any defender-side buff today? Mirrors the
|
|
40
|
+
* audit-coverage walk: every phase, target perspective, permissive context.
|
|
41
|
+
*/
|
|
42
|
+
export function producesDefensiveBuff(entry) {
|
|
43
|
+
if (entry.effect === undefined)
|
|
44
|
+
return false;
|
|
45
|
+
const source = sourceFor(entry);
|
|
46
|
+
for (const phase of PHASES) {
|
|
47
|
+
const result = effectToBuffs(entry.effect, source, permissiveContext(phase), "target");
|
|
48
|
+
if (result.applied.length > 0 || result.activatable.length > 0)
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
/** Build a permissive context — every situational flag on. Matches audit-coverage. */
|
|
54
|
+
function permissiveContext(phase) {
|
|
55
|
+
return {
|
|
56
|
+
phase,
|
|
57
|
+
attackerStationary: true,
|
|
58
|
+
attackerCharged: true,
|
|
59
|
+
withinHalfRange: true,
|
|
60
|
+
attackerInCover: true,
|
|
61
|
+
targetInCover: true,
|
|
62
|
+
attackerAttached: true,
|
|
63
|
+
attackerKeywords: [],
|
|
64
|
+
targetKeywords: [],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/** Buff-source label for the walker. Kind is not load-bearing for this scrub. */
|
|
68
|
+
function sourceFor(entry) {
|
|
69
|
+
const kind = entry.ability_type === "faction"
|
|
70
|
+
? "army"
|
|
71
|
+
: entry.ability_type === "detachment"
|
|
72
|
+
? "detachment"
|
|
73
|
+
: entry.ability_type === "stratagem"
|
|
74
|
+
? "detachment-stratagem"
|
|
75
|
+
: "unit";
|
|
76
|
+
return { kind: "ability", abilityId: entry.ability_id ?? "?", abilityKind: kind };
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Strip the stale flag from one in-memory abilities array; return the number
|
|
80
|
+
* of entries whose flag was cleared. The note field is deleted outright when
|
|
81
|
+
* it held only the sentinel (the common case); other-note coexistence isn't
|
|
82
|
+
* a thing in the current corpus (verified — every match is exact-equal).
|
|
83
|
+
*/
|
|
84
|
+
export function scrubDefensiveFlags(abilities) {
|
|
85
|
+
let changed = 0;
|
|
86
|
+
for (const a of abilities) {
|
|
87
|
+
if (a.community_notes !== STALE_FLAG)
|
|
88
|
+
continue;
|
|
89
|
+
if (!producesDefensiveBuff(a))
|
|
90
|
+
continue;
|
|
91
|
+
delete a.community_notes;
|
|
92
|
+
changed++;
|
|
93
|
+
}
|
|
94
|
+
return changed;
|
|
95
|
+
}
|
|
96
|
+
function run(check) {
|
|
97
|
+
let totalCleared = 0;
|
|
98
|
+
let totalCarriesFlag = 0;
|
|
99
|
+
let filesTouched = 0;
|
|
100
|
+
for (const entry of readdirSync(ENRICHMENT_ROOT, { withFileTypes: true })) {
|
|
101
|
+
if (!entry.isDirectory() || entry.name === "_example")
|
|
102
|
+
continue;
|
|
103
|
+
const file = resolve(ENRICHMENT_ROOT, entry.name, "abilities.json");
|
|
104
|
+
let abilities;
|
|
105
|
+
try {
|
|
106
|
+
abilities = JSON.parse(readFileSync(file, "utf-8"));
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (!Array.isArray(abilities))
|
|
112
|
+
continue;
|
|
113
|
+
const flagged = abilities.filter((a) => a.community_notes === STALE_FLAG);
|
|
114
|
+
if (flagged.length === 0)
|
|
115
|
+
continue;
|
|
116
|
+
totalCarriesFlag += flagged.length;
|
|
117
|
+
const clearable = flagged.filter((a) => producesDefensiveBuff(a)).length;
|
|
118
|
+
if (clearable === 0)
|
|
119
|
+
continue;
|
|
120
|
+
if (check) {
|
|
121
|
+
console.log(` ${String(clearable).padStart(3)}/${String(flagged.length).padEnd(3)} ${entry.name}`);
|
|
122
|
+
totalCleared += clearable;
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
const cleared = scrubDefensiveFlags(abilities);
|
|
126
|
+
writeFileSync(file, JSON.stringify(abilities, null, 2) + "\n");
|
|
127
|
+
console.log(` ✓ ${entry.name}: cleared ${cleared}/${flagged.length}`);
|
|
128
|
+
totalCleared += cleared;
|
|
129
|
+
filesTouched++;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (check) {
|
|
133
|
+
if (totalCleared === 0) {
|
|
134
|
+
console.log("No stale flags found that the engine now reads.");
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
console.log(`\n${totalCleared} of ${totalCarriesFlag} flagged entr${totalCarriesFlag === 1 ? "y" : "ies"} are now engine-readable; re-run without --check to clear.`);
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
console.log(`\nCleared ${totalCleared} of ${totalCarriesFlag} flagged entr${totalCarriesFlag === 1 ? "y" : "ies"} across ${filesTouched} file(s). Remaining flags are genuine engine-residue (translator gaps / non-buff effects).`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const isMain = process.argv[1] &&
|
|
146
|
+
resolve(process.argv[1]).replace(/\.\w+$/, "") === fileURLToPath(import.meta.url).replace(/\.\w+$/, "");
|
|
147
|
+
if (isMain)
|
|
148
|
+
run(process.argv.includes("--check"));
|
|
149
|
+
//# sourceMappingURL=scrub-defensive-flag.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scrub-defensive-flag.js","sourceRoot":"","sources":["../src/scrub-defensive-flag.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Defensive-flag scrub: remove the stale `\"defensive ability (skipped for damage\n * calc)\"` `community_notes` marker from ability entries whose effect now\n * translates into real defender-side buffs.\n *\n * The marker was set when the cruncher couldn't read `damage-reduction`,\n * `invulnerable-save`, and `feel-no-pain {scope:\"mortal\"}`. With those landed\n * (#22), entries carrying valid DSL for those effects are no longer \"skipped\"\n * — leaving the flag in place would lie to downstream consumers (the SPA\n * surfaces it as \"this is opaque to the engine\").\n *\n * Decision per entry:\n * - Walk the effect through `effectToBuffs(perspective:\"target\")` across\n * every phase, with a permissive context (mirrors `audit-coverage.ts`).\n * - If ANY phase yields `applied.length > 0`, the engine reads the ability\n * today — strip the flag (delete `community_notes` outright if that was\n * the only note; otherwise leave the field untouched, since we don't own\n * other authored notes).\n * - Otherwise leave the flag in place. Those are the genuine residue —\n * `ability-grant`, `mortal-wounds`, the translator gap on\n * `roll-modifier {target:\"attacker\"}`, etc. — for the follow-on grind.\n *\n * Idempotent: re-running is a no-op. The scrub only acts on the exact-match\n * sentinel string; refined notes from a future pass stay put.\n *\n * Usage: npx tsx tools/src/scrub-defensive-flag.ts (writes in place)\n * npx tsx tools/src/scrub-defensive-flag.ts --check (report only)\n */\nimport { readdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { effectToBuffs } from \"./cruncher/from-dsl.js\";\nimport type { BuffSource, EngineContext } from \"./cruncher/buffs.js\";\nimport type { Phase } from \"./generated.js\";\n\nconst __dirname = fileURLToPath(new URL(\".\", import.meta.url));\nconst ENRICHMENT_ROOT = resolve(__dirname, \"../../data/enrichment\");\n\n/** The exact sentinel the audit pass historically wrote — only this string scrubs. */\nexport const STALE_FLAG = \"defensive ability (skipped for damage calc)\";\n\nconst PHASES: Phase[] = [\"command\", \"movement\", \"shooting\", \"charge\", \"fight\"];\n\ninterface AbilityEntry {\n ability_id?: string;\n ability_type?: string;\n effect?: unknown;\n community_notes?: string;\n [k: string]: unknown;\n}\n\n/**\n * Does this ability's effect produce any defender-side buff today? Mirrors the\n * audit-coverage walk: every phase, target perspective, permissive context.\n */\nexport function producesDefensiveBuff(entry: AbilityEntry): boolean {\n if (entry.effect === undefined) return false;\n const source = sourceFor(entry);\n for (const phase of PHASES) {\n const result = effectToBuffs(entry.effect, source, permissiveContext(phase), \"target\");\n if (result.applied.length > 0 || result.activatable.length > 0) return true;\n }\n return false;\n}\n\n/** Build a permissive context — every situational flag on. Matches audit-coverage. */\nfunction permissiveContext(phase: Phase): EngineContext {\n return {\n phase,\n attackerStationary: true,\n attackerCharged: true,\n withinHalfRange: true,\n attackerInCover: true,\n targetInCover: true,\n attackerAttached: true,\n attackerKeywords: [],\n targetKeywords: [],\n };\n}\n\n/** Buff-source label for the walker. Kind is not load-bearing for this scrub. */\nfunction sourceFor(entry: AbilityEntry): BuffSource {\n const kind =\n entry.ability_type === \"faction\"\n ? \"army\"\n : entry.ability_type === \"detachment\"\n ? \"detachment\"\n : entry.ability_type === \"stratagem\"\n ? \"detachment-stratagem\"\n : \"unit\";\n return { kind: \"ability\", abilityId: entry.ability_id ?? \"?\", abilityKind: kind };\n}\n\n/**\n * Strip the stale flag from one in-memory abilities array; return the number\n * of entries whose flag was cleared. The note field is deleted outright when\n * it held only the sentinel (the common case); other-note coexistence isn't\n * a thing in the current corpus (verified — every match is exact-equal).\n */\nexport function scrubDefensiveFlags(abilities: AbilityEntry[]): number {\n let changed = 0;\n for (const a of abilities) {\n if (a.community_notes !== STALE_FLAG) continue;\n if (!producesDefensiveBuff(a)) continue;\n delete a.community_notes;\n changed++;\n }\n return changed;\n}\n\nfunction run(check: boolean): void {\n let totalCleared = 0;\n let totalCarriesFlag = 0;\n let filesTouched = 0;\n for (const entry of readdirSync(ENRICHMENT_ROOT, { withFileTypes: true })) {\n if (!entry.isDirectory() || entry.name === \"_example\") continue;\n const file = resolve(ENRICHMENT_ROOT, entry.name, \"abilities.json\");\n let abilities: AbilityEntry[];\n try {\n abilities = JSON.parse(readFileSync(file, \"utf-8\"));\n } catch {\n continue;\n }\n if (!Array.isArray(abilities)) continue;\n\n const flagged = abilities.filter((a) => a.community_notes === STALE_FLAG);\n if (flagged.length === 0) continue;\n totalCarriesFlag += flagged.length;\n const clearable = flagged.filter((a) => producesDefensiveBuff(a)).length;\n if (clearable === 0) continue;\n\n if (check) {\n console.log(` ${String(clearable).padStart(3)}/${String(flagged.length).padEnd(3)} ${entry.name}`);\n totalCleared += clearable;\n } else {\n const cleared = scrubDefensiveFlags(abilities);\n writeFileSync(file, JSON.stringify(abilities, null, 2) + \"\\n\");\n console.log(` ✓ ${entry.name}: cleared ${cleared}/${flagged.length}`);\n totalCleared += cleared;\n filesTouched++;\n }\n }\n if (check) {\n if (totalCleared === 0) {\n console.log(\"No stale flags found that the engine now reads.\");\n } else {\n console.log(`\\n${totalCleared} of ${totalCarriesFlag} flagged entr${totalCarriesFlag === 1 ? \"y\" : \"ies\"} are now engine-readable; re-run without --check to clear.`);\n process.exit(1);\n }\n } else {\n console.log(`\\nCleared ${totalCleared} of ${totalCarriesFlag} flagged entr${totalCarriesFlag === 1 ? \"y\" : \"ies\"} across ${filesTouched} file(s). Remaining flags are genuine engine-residue (translator gaps / non-buff effects).`);\n }\n}\n\nconst isMain =\n process.argv[1] &&\n resolve(process.argv[1]).replace(/\\.\\w+$/, \"\") === fileURLToPath(import.meta.url).replace(/\\.\\w+$/, \"\");\nif (isMain) run(process.argv.includes(\"--check\"));\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** The note any GW-text-bearing entry is rewritten to. */
|
|
2
|
+
export declare const SCRUB_CITATION = "auto-generated stub \u2014 needs manual authoring (source: 10e Datasheets_abilities; rules text omitted for IP)";
|
|
3
|
+
/** True when this note carries verbatim GW rules text and must be scrubbed. */
|
|
4
|
+
export declare function isLeak(note: string | undefined): boolean;
|
|
5
|
+
/** Replace a leaking note with the citation; pass authored notes through unchanged. */
|
|
6
|
+
export declare function scrubNote(note: string | undefined): string | undefined;
|
|
7
|
+
interface AbilityEntry {
|
|
8
|
+
community_notes?: string;
|
|
9
|
+
[k: string]: unknown;
|
|
10
|
+
}
|
|
11
|
+
/** Scrub one file's array in place; return how many entries changed. */
|
|
12
|
+
export declare function scrubAbilities(abilities: AbilityEntry[]): number;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=scrub-ip.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scrub-ip.d.ts","sourceRoot":"","sources":["../src/scrub-ip.ts"],"names":[],"mappings":"AAuBA,0DAA0D;AAC1D,eAAO,MAAM,cAAc,oHACmF,CAAC;AAK/G,+EAA+E;AAC/E,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAExD;AAED,uFAAuF;AACvF,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAEtE;AAED,UAAU,YAAY;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACtB;AAED,wEAAwE;AACxE,wBAAgB,cAAc,CAAC,SAAS,EAAE,YAAY,EAAE,GAAG,MAAM,CAShE"}
|
package/dist/scrub-ip.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IP scrub: remove verbatim Games Workshop rules text from enrichment data.
|
|
3
|
+
*
|
|
4
|
+
* The original Haiku authoring pass, when it failed to translate a rule into
|
|
5
|
+
* the ability DSL, left a stub and dumped the source GW `description` into
|
|
6
|
+
* `community_notes` as `"… Original: <verbatim rules text>"`. CLAUDE.md forbids
|
|
7
|
+
* committing GW ability text. This codemod replaces any such note with a
|
|
8
|
+
* non-infringing citation, leaving genuinely-authored notes (analysis,
|
|
9
|
+
* `"skipped for damage calc"`, etc.) untouched.
|
|
10
|
+
*
|
|
11
|
+
* Idempotent — re-running after a scrub is a no-op. Markers: `"Original:"` (the
|
|
12
|
+
* dump prefix) and `"■"` (a GW bullet glyph that rides along with copied text).
|
|
13
|
+
*
|
|
14
|
+
* Usage: npx tsx tools/src/scrub-ip.ts (writes in place)
|
|
15
|
+
* npx tsx tools/src/scrub-ip.ts --check (report only, non-zero if leaks remain)
|
|
16
|
+
*/
|
|
17
|
+
import { readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
18
|
+
import { resolve } from "node:path";
|
|
19
|
+
import { fileURLToPath } from "node:url";
|
|
20
|
+
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
|
21
|
+
const ENRICHMENT_ROOT = resolve(__dirname, "../../data/enrichment");
|
|
22
|
+
/** The note any GW-text-bearing entry is rewritten to. */
|
|
23
|
+
export const SCRUB_CITATION = "auto-generated stub — needs manual authoring (source: 10e Datasheets_abilities; rules text omitted for IP)";
|
|
24
|
+
/** Markers identifying a note that carries verbatim GW text. */
|
|
25
|
+
const LEAK_MARKER = /Original:|■/;
|
|
26
|
+
/** True when this note carries verbatim GW rules text and must be scrubbed. */
|
|
27
|
+
export function isLeak(note) {
|
|
28
|
+
return typeof note === "string" && LEAK_MARKER.test(note);
|
|
29
|
+
}
|
|
30
|
+
/** Replace a leaking note with the citation; pass authored notes through unchanged. */
|
|
31
|
+
export function scrubNote(note) {
|
|
32
|
+
return isLeak(note) ? SCRUB_CITATION : note;
|
|
33
|
+
}
|
|
34
|
+
/** Scrub one file's array in place; return how many entries changed. */
|
|
35
|
+
export function scrubAbilities(abilities) {
|
|
36
|
+
let changed = 0;
|
|
37
|
+
for (const a of abilities) {
|
|
38
|
+
if (isLeak(a.community_notes)) {
|
|
39
|
+
a.community_notes = SCRUB_CITATION;
|
|
40
|
+
changed++;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return changed;
|
|
44
|
+
}
|
|
45
|
+
function run(check) {
|
|
46
|
+
let totalLeaks = 0;
|
|
47
|
+
let filesTouched = 0;
|
|
48
|
+
for (const entry of readdirSync(ENRICHMENT_ROOT, { withFileTypes: true })) {
|
|
49
|
+
if (!entry.isDirectory() || entry.name === "_example")
|
|
50
|
+
continue;
|
|
51
|
+
const file = resolve(ENRICHMENT_ROOT, entry.name, "abilities.json");
|
|
52
|
+
let abilities;
|
|
53
|
+
try {
|
|
54
|
+
abilities = JSON.parse(readFileSync(file, "utf-8"));
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (!Array.isArray(abilities))
|
|
60
|
+
continue;
|
|
61
|
+
const leaks = abilities.filter((a) => isLeak(a.community_notes)).length;
|
|
62
|
+
if (leaks === 0)
|
|
63
|
+
continue;
|
|
64
|
+
totalLeaks += leaks;
|
|
65
|
+
filesTouched++;
|
|
66
|
+
if (check) {
|
|
67
|
+
console.log(` ${String(leaks).padStart(3)} ${entry.name}`);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
scrubAbilities(abilities);
|
|
71
|
+
writeFileSync(file, JSON.stringify(abilities, null, 2) + "\n");
|
|
72
|
+
console.log(` ✓ ${entry.name}: scrubbed ${leaks}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (check) {
|
|
76
|
+
console.log(totalLeaks === 0 ? "No GW-text leaks." : `\n${totalLeaks} leak(s) across ${filesTouched} file(s).`);
|
|
77
|
+
if (totalLeaks > 0)
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
console.log(`\nScrubbed ${totalLeaks} entr${totalLeaks === 1 ? "y" : "ies"} across ${filesTouched} file(s).`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const isMain = process.argv[1] &&
|
|
85
|
+
resolve(process.argv[1]).replace(/\.\w+$/, "") === fileURLToPath(import.meta.url).replace(/\.\w+$/, "");
|
|
86
|
+
if (isMain)
|
|
87
|
+
run(process.argv.includes("--check"));
|
|
88
|
+
//# sourceMappingURL=scrub-ip.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scrub-ip.js","sourceRoot":"","sources":["../src/scrub-ip.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * IP scrub: remove verbatim Games Workshop rules text from enrichment data.\n *\n * The original Haiku authoring pass, when it failed to translate a rule into\n * the ability DSL, left a stub and dumped the source GW `description` into\n * `community_notes` as `\"… Original: <verbatim rules text>\"`. CLAUDE.md forbids\n * committing GW ability text. This codemod replaces any such note with a\n * non-infringing citation, leaving genuinely-authored notes (analysis,\n * `\"skipped for damage calc\"`, etc.) untouched.\n *\n * Idempotent — re-running after a scrub is a no-op. Markers: `\"Original:\"` (the\n * dump prefix) and `\"■\"` (a GW bullet glyph that rides along with copied text).\n *\n * Usage: npx tsx tools/src/scrub-ip.ts (writes in place)\n * npx tsx tools/src/scrub-ip.ts --check (report only, non-zero if leaks remain)\n */\nimport { readdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nconst __dirname = fileURLToPath(new URL(\".\", import.meta.url));\nconst ENRICHMENT_ROOT = resolve(__dirname, \"../../data/enrichment\");\n\n/** The note any GW-text-bearing entry is rewritten to. */\nexport const SCRUB_CITATION =\n \"auto-generated stub — needs manual authoring (source: 10e Datasheets_abilities; rules text omitted for IP)\";\n\n/** Markers identifying a note that carries verbatim GW text. */\nconst LEAK_MARKER = /Original:|■/;\n\n/** True when this note carries verbatim GW rules text and must be scrubbed. */\nexport function isLeak(note: string | undefined): boolean {\n return typeof note === \"string\" && LEAK_MARKER.test(note);\n}\n\n/** Replace a leaking note with the citation; pass authored notes through unchanged. */\nexport function scrubNote(note: string | undefined): string | undefined {\n return isLeak(note) ? SCRUB_CITATION : note;\n}\n\ninterface AbilityEntry {\n community_notes?: string;\n [k: string]: unknown;\n}\n\n/** Scrub one file's array in place; return how many entries changed. */\nexport function scrubAbilities(abilities: AbilityEntry[]): number {\n let changed = 0;\n for (const a of abilities) {\n if (isLeak(a.community_notes)) {\n a.community_notes = SCRUB_CITATION;\n changed++;\n }\n }\n return changed;\n}\n\nfunction run(check: boolean): void {\n let totalLeaks = 0;\n let filesTouched = 0;\n for (const entry of readdirSync(ENRICHMENT_ROOT, { withFileTypes: true })) {\n if (!entry.isDirectory() || entry.name === \"_example\") continue;\n const file = resolve(ENRICHMENT_ROOT, entry.name, \"abilities.json\");\n let abilities: AbilityEntry[];\n try {\n abilities = JSON.parse(readFileSync(file, \"utf-8\"));\n } catch {\n continue;\n }\n if (!Array.isArray(abilities)) continue;\n const leaks = abilities.filter((a) => isLeak(a.community_notes)).length;\n if (leaks === 0) continue;\n totalLeaks += leaks;\n filesTouched++;\n if (check) {\n console.log(` ${String(leaks).padStart(3)} ${entry.name}`);\n } else {\n scrubAbilities(abilities);\n writeFileSync(file, JSON.stringify(abilities, null, 2) + \"\\n\");\n console.log(` ✓ ${entry.name}: scrubbed ${leaks}`);\n }\n }\n if (check) {\n console.log(totalLeaks === 0 ? \"No GW-text leaks.\" : `\\n${totalLeaks} leak(s) across ${filesTouched} file(s).`);\n if (totalLeaks > 0) process.exit(1);\n } else {\n console.log(`\\nScrubbed ${totalLeaks} entr${totalLeaks === 1 ? \"y\" : \"ies\"} across ${filesTouched} file(s).`);\n }\n}\n\nconst isMain =\n process.argv[1] &&\n resolve(process.argv[1]).replace(/\\.\\w+$/, \"\") === fileURLToPath(import.meta.url).replace(/\\.\\w+$/, \"\");\nif (isMain) run(process.argv.includes(\"--check\"));\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alpaca-software/40kdc-data",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The 40kdc Warhammer 40K dataset behind a linked, typed API — find units, follow them to their weapons, abilities, phases, and factions. Also validates data against the canonical JSON Schemas.",
|
|
6
6
|
"keywords": [
|
|
@@ -37,7 +37,8 @@
|
|
|
37
37
|
"README.md"
|
|
38
38
|
],
|
|
39
39
|
"bin": {
|
|
40
|
-
"40kdc-validate": "./dist/cli.js"
|
|
40
|
+
"40kdc-validate": "./dist/cli.js",
|
|
41
|
+
"40kdc-runner": "./dist/runner.js"
|
|
41
42
|
},
|
|
42
43
|
"publishConfig": {
|
|
43
44
|
"access": "public",
|
|
@@ -56,6 +57,13 @@
|
|
|
56
57
|
"validate:core": "tsx src/cli.ts validate-core",
|
|
57
58
|
"validate:enrichment": "tsx src/cli.ts validate-enrichment",
|
|
58
59
|
"translate": "tsx src/cli.ts translate",
|
|
60
|
+
"audit:coverage": "tsx src/cli.ts audit-coverage --write",
|
|
61
|
+
"scrub:defensive-flag": "tsx src/scrub-defensive-flag.ts",
|
|
62
|
+
"author:input": "tsx src/author-input.ts",
|
|
63
|
+
"author:propose": "tsx src/author-batch.ts propose",
|
|
64
|
+
"author:repair": "tsx src/author-batch.ts repair",
|
|
65
|
+
"author:apply": "tsx src/author-batch.ts apply",
|
|
66
|
+
"author:review": "tsx src/author-batch.ts review",
|
|
59
67
|
"bundle:schemas": "tsx src/bundle-schemas.ts",
|
|
60
68
|
"codegen:types": "json2ts -i ../crates/wh40kdc/schemas/bundled.schema.json -o src/generated.ts --unreachableDefinitions --bannerComment \"/* Generated from crates/wh40kdc/schemas/bundled.schema.json by 'npm run codegen:types'. DO NOT EDIT BY HAND. */\"",
|
|
61
69
|
"copy:schemas": "rm -rf schemas && cp -R ../schemas schemas",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"modifier": { "type": "object", "additionalProperties": true }
|
|
46
46
|
},
|
|
47
47
|
"required": ["type", "target"],
|
|
48
|
-
"$comment": "When `type` is `re-roll`, `modifier` must carry `roll` (string) and `subset` (`ones` | `all-failures`). Rerolls always target failures; the subset decides whether only 1s are rerolled or every failed die. The constraint is enforced by AJV at validation time and stripped from the codegen bundle (typify can't model if/then/else) — the generated TS/Rust types therefore see `modifier` as an open object, matching its other-`type` callers.",
|
|
48
|
+
"$comment": "When `type` is `re-roll`, `modifier` must carry `roll` (string) and `subset` (`ones` | `all-failures`). Rerolls always target failures; the subset decides whether only 1s are rerolled or every failed die. The constraint is enforced by AJV at validation time and stripped from the codegen bundle (typify can't model if/then/else) — the generated TS/Rust types therefore see `modifier` as an open object, matching its other-`type` callers. When `type` is `feel-no-pain`, `modifier` carries `threshold` (the FNP save target) and optionally `scope` ∈ {`all`, `mortal`}; an absent scope defaults to `all` (fires on every unsaved wound). The two scopes compose independently against the mortal-wound stream.",
|
|
49
49
|
"allOf": [
|
|
50
50
|
{
|
|
51
51
|
"if": {
|