refactor(#3309): extract health-diagnostic-types leaf module, wire RULES

Wiring all 8 rule-group files into health-diagnostic.cts's RULES array
created a genuine CJS circular dependency: each group file required
health-diagnostic.cjs back for the shared enums, and health-diagnostic.cjs
now required the group files forward, so the enums were undefined
mid-load (destructuring health-diagnostic.cjs's still-unassigned
exports).

Fixes it by splitting the enums/types (SEVERITY, REMEDY_ACTION,
REMEDY_RISK, Remedy, Diagnostic, Rule) into a dependency-free leaf
module, health-diagnostic-types.cts, that both sides import instead of
each other. health-diagnostic.cts re-exports the enums for existing
consumers. RULES is now the real concatenation of all 8 groups (31
codes — E001 intentionally stays a pre-check outside the table).
This commit is contained in:
sim
2026-08-13 01:46:20 -04:00
parent c469aeeacd
commit e0021a2fed
15 changed files with 167 additions and 69 deletions

1
.gitignore vendored
View File

@@ -196,6 +196,7 @@ build/
/gsd-core/bin/lib/planning-workspace.cjs
/gsd-core/bin/lib/planning-scope.cjs
/gsd-core/bin/lib/planning-snapshot.cjs
/gsd-core/bin/lib/health-diagnostic-types.cjs
/gsd-core/bin/lib/health-diagnostic.cjs
/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs
/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs

View File

@@ -106,6 +106,9 @@ Leaf module owning the frozen `SCOPE` discriminator (`COMPLETE` / `TRUNCATED` /
### Planning Snapshot Module
Module owning the parsed projection of `.planning/` that a diagnostic rule may read, per ADR-3180 §8.1 (Decision 8, Phase 10, #3308). `buildPlanningSnapshot(cwd) → PlanningSnapshot` is composed EXCLUSIVELY from the already-consolidated §7 owners — `getMilestoneInfo` (Roadmap Parser Module), `listMilestonePhaseDirs` (Phase Locator Module), `isPhaseComplete` (Verification Module), `scanPhasePlans` (Plan Scan Module), `stateFieldValue`/`stateCurrentPositionSlice` (STATE.md Document Module), `planningPaths` (Planning Workspace Module) — and introduces no new semantic derivation of its own. `PlanningSnapshot` exposes `milestone`/`phaseDirs`/`phases`/`currentPhaseLabel`, each a `{value, scope}` pair per the Planning Scope Module's frozen `SCOPE` enum; `phases` additionally carries a `PhaseSnapshot[]` (`dir`, `complete`, `verificationStatus`, `planCount`, `summaryCount`, `scope`). The one new piece of logic this module adds is `worstScope(...scopes) → Scope`, a pure severity-ordered combinator (`UNREADABLE` > `UNSCOPED` > `TRUNCATED` > `COMPLETE`) that folds several independently-scoped owner answers about the same phase directory into one composite signal — NOT a re-derivation of any owner (each owner's own algorithm is untouched; only their already-computed `scope` verdicts are combined), but new coordination logic no single owner has the visibility to express. Every exposed field carries PARSED values only, never raw document text — this is structural, not advisory: a diagnostic rule given only the parsed value cannot re-derive a field's location the way `#3162`'s three inert `Current Phase` literal-search predicates did. Read failures on STATE.md (exists-but-unreadable, distinct from absent) are reported via the Unusable Input Diagnostic Module's `warnUnusableInput(UNUSABLE_REASON.STATE_UNREADABLE)`. Guarded by `scripts/lint-planning-snapshot-bypass-drift.cjs` (ratcheted per Decision 4(e), scoped to `DIAGNOSTIC_RULE_FUNCTIONS` — currently `cmdValidateHealth` in `src/verify.cts` only, acknowledging its existing raw `.planning/` reads as debt owned by Phase 11, #3309, which migrates it onto this snapshot). Source of truth: `gsd-core/bin/lib/planning-snapshot.cjs` (generated from `src/planning-snapshot.cts`). Design: `.gsd/phase/refactor-3308-planning-snapshot-parsed-projection/40-design.md`.
### Health Diagnostic Types Module
Leaf module owning the `SEVERITY`/`REMEDY_ACTION`/`REMEDY_RISK` enums and `Diagnostic`/`Remedy`/`Rule` types shared between the Health Diagnostic Module (the evaluator) and the Health Diagnostic Rule Groups (the eight rule-group files it concatenates). Split out of `src/health-diagnostic.cts` (Phase 11, #3309, ADR-3180 §8.2/§8.3/§8.5) to break a CJS circular dependency: the evaluator must `require()` every rule-group file to populate `RULES`, and every rule-group file needs these enums/types — if the rule-group files required the evaluator back, the require cycle would resolve `module.exports` before it is assigned. This leaf has no runtime dependency on either side of that cycle. Source of truth: `gsd-core/bin/lib/health-diagnostic-types.cjs` (generated from `src/health-diagnostic-types.cts`).
### Health Diagnostic Module
Module owning the frozen rule-table contract for `validate health`, per ADR-3180 §8.2/§8.3/§8.5 (Phase 11, #3309). Exposes three frozen enums — `SEVERITY` (`error`/`warning`/`info`), `REMEDY_ACTION` (the six real repair actions harvested from `cmdValidateHealth`'s existing `--repair` implementation — `createConfig`, `resetConfig`, `regenerateState`, `addNyquistKey`, `addAiIntegrationPhaseKey`, `backfillMilestones` — plus `advise`, the non-repairable payload every non-actionable finding's fix text becomes), and `REMEDY_RISK` (`none`/`destructive`) — plus the `Diagnostic`/`Remedy`/`Rule` shapes every rule's `check(snapshot: PlanningSnapshot) → Diagnostic[]` signature and every finding's `remedy` conform to. `RULES: Rule[]` is the rule table a later migration batch appends the 32 rules extracted from `cmdValidateHealth` (`src/verify.cts:1616-2577`) onto; this phase ships it EMPTY, establishing only the container and its type. `evaluateRules(snapshot) → Diagnostic[]` runs every rule in `RULES` against one `PlanningSnapshot` and flattens the results, throwing on any two rules sharing a `code` — defense in depth beside the future static 1:1 lint guard (§8.2 rule 1). `applyRepairs(cwd, diagnostics, repair, backfill) → {applied, refused}` is the `--repair`/`--backfill` dispatcher: a `DESTRUCTIVE` remedy (`resetConfig`/`regenerateState` — health.md's own published table: "loses custom settings" / "loses session history") is reported but never executed by `--repair`, a deliberate, disclosed breaking change (§8.3 rule 3) from `cmdValidateHealth`'s current unconditional application; `backfillMilestones` alone among the `NONE`-risk actions is requested by `--backfill` without `--repair`, mirroring `cmdValidateHealth`'s existing gate (`src/verify.cts:2504`). Per-action repair handlers are stubs in this phase — they land alongside the rules that need them. Source of truth: `gsd-core/bin/lib/health-diagnostic.cjs` (generated from `src/health-diagnostic.cts`). Design: `.gsd/phase/refactor-3309-health-diagnostic-rule-table/40-design.md`.

View File

@@ -378,6 +378,7 @@
"graphify.cjs",
"gsd2-import.cjs",
"handshake-serialized.cjs",
"health-diagnostic-types.cjs",
"health-diagnostic.cjs",
"hook-bus.cjs",
"host-integration-sdk.cjs",

View File

@@ -495,6 +495,7 @@ Full listing: `gsd-core/bin/lib/*.cjs`.
| `graphify.cjs` | Knowledge-graph build/query/status/diff for `/gsd-graphify` |
| `graphify-command-router.cjs` | ADR-959 capability command router for `gsd-tools graphify` — dispatches build/query/status/diff subcommands; first real capability command cutover (phase 4d-impl-2) |
| `gsd2-import.cjs` | External-plan ingest for `/gsd-import --from-gsd2` |
| `health-diagnostic-types.cjs` | Shared, dependency-free `SEVERITY`/`REMEDY_ACTION`/`REMEDY_RISK` enums and `Diagnostic`/`Remedy`/`Rule` types for `validate health` — split out of `health-diagnostic.cjs` so its rule-group files can depend on the enums/types without a CJS circular require back into the evaluator (ADR-3180 §8.2/§8.3/§8.5, Phase 11, #3309) |
| `health-diagnostic.cjs` | Frozen rule-table contract for `validate health` — `SEVERITY`/`REMEDY_ACTION`/`REMEDY_RISK` enums, `Diagnostic`/`Remedy`/`Rule` shapes, the `RULES` table (empty in this phase; a later migration batch appends the 32 rules extracted from `cmdValidateHealth`), `evaluateRules` (throws on duplicate rule codes), and `applyRepairs` (the `--repair`/`--backfill` dispatcher — refuses `DESTRUCTIVE`-risk remedies) (ADR-3180 §8.2/§8.3/§8.5, Phase 11, #3309) |
| `host-integration.cjs` | Host-Integration Interface (ADR-1239 Phase A) — negotiated capability contract over the six host-integration points; `negotiateHostCapabilities` fail-closes on undeclared/unknown/`undocumented` values, typed degradation ladder, host-capability profiles; the 8 `runtime.hostIntegration` axes are validated in `capability-validator.cjs` and sourced per-CLI in `docs/reference/host-integration-capability-matrix.md` |
| `host-runtime-detection.cjs` | Host Runtime Detection Module (ADR-2313 Phase 5, #3245) — the detection rung beneath `GSD_RUNTIME` and `.planning/config.json` `runtime` that lets `init` report `agent_runtime: codex` inside a Codex session instead of the hardcoded `claude` default; `detectHostRuntime` returns the typed `{runtime, source, signal}` from citation-backed Codex signals (`CODEX_SANDBOX`/`CODEX_SANDBOX_NETWORK_DISABLED`, else `CODEX_HOME` + `config.toml`), `resolveReportedRuntime` composes the full ladder. Pure, injectable, never writes, never shells out |

View File

@@ -135,6 +135,7 @@ export default tseslint.config(
'gsd-core/bin/lib/configuration.cjs',
'gsd-core/bin/lib/state-document.cjs',
'gsd-core/bin/lib/planning-snapshot.cjs',
'gsd-core/bin/lib/health-diagnostic-types.cjs',
'gsd-core/bin/lib/health-diagnostic.cjs',
'gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs',
'gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs',

View File

@@ -24,8 +24,8 @@
* ("Rule table organization" — Agent installation group)
*/
// eslint-disable-next-line @typescript-eslint/no-require-imports -- health-diagnostic.cjs is an export= CommonJS module
import healthDiagnosticMod = require('../health-diagnostic.cjs');
// eslint-disable-next-line @typescript-eslint/no-require-imports -- health-diagnostic-types.cjs is an export= CommonJS module
import healthDiagnosticMod = require('../health-diagnostic-types.cjs');
const { SEVERITY, REMEDY_ACTION, REMEDY_RISK } = healthDiagnosticMod;
type Rule = healthDiagnosticMod.Rule;
type Diagnostic = healthDiagnosticMod.Diagnostic;

View File

@@ -54,8 +54,8 @@ import type planningSnapshotMod = require('../planning-snapshot.cjs');
type PlanningSnapshot = ReturnType<typeof planningSnapshotMod.buildPlanningSnapshot>;
// eslint-disable-next-line @typescript-eslint/no-require-imports -- health-diagnostic.cjs is an export= CommonJS module
import healthDiagnosticMod = require('../health-diagnostic.cjs');
// eslint-disable-next-line @typescript-eslint/no-require-imports -- health-diagnostic-types.cjs is an export= CommonJS module
import healthDiagnosticMod = require('../health-diagnostic-types.cjs');
const { SEVERITY, REMEDY_ACTION, REMEDY_RISK } = healthDiagnosticMod;
type Diagnostic = healthDiagnosticMod.Diagnostic;
type Remedy = healthDiagnosticMod.Remedy;

View File

@@ -22,7 +22,7 @@ import type planningSnapshotMod = require('../planning-snapshot.cjs');
type PlanningSnapshot = ReturnType<typeof planningSnapshotMod.buildPlanningSnapshot>;
// eslint-disable-next-line @typescript-eslint/no-require-imports
import healthDiagnosticMod = require('../health-diagnostic.cjs');
import healthDiagnosticMod = require('../health-diagnostic-types.cjs');
const { SEVERITY, REMEDY_ACTION, REMEDY_RISK } = healthDiagnosticMod;
type Diagnostic = healthDiagnosticMod.Diagnostic;
type Rule = healthDiagnosticMod.Rule;

View File

@@ -44,7 +44,7 @@ import type planningSnapshotMod = require('../planning-snapshot.cjs');
type PlanningSnapshot = ReturnType<typeof planningSnapshotMod.buildPlanningSnapshot>;
// eslint-disable-next-line @typescript-eslint/no-require-imports
import healthDiagnosticMod = require('../health-diagnostic.cjs');
import healthDiagnosticMod = require('../health-diagnostic-types.cjs');
const { SEVERITY, REMEDY_ACTION, REMEDY_RISK } = healthDiagnosticMod;
type Diagnostic = healthDiagnosticMod.Diagnostic;
type Rule = healthDiagnosticMod.Rule;

View File

@@ -87,7 +87,7 @@ import type planningSnapshotMod = require('../planning-snapshot.cjs');
type PlanningSnapshot = ReturnType<typeof planningSnapshotMod.buildPlanningSnapshot>;
// eslint-disable-next-line @typescript-eslint/no-require-imports
import healthDiagnosticMod = require('../health-diagnostic.cjs');
import healthDiagnosticMod = require('../health-diagnostic-types.cjs');
const { SEVERITY, REMEDY_ACTION, REMEDY_RISK } = healthDiagnosticMod;
type Diagnostic = healthDiagnosticMod.Diagnostic;
type Rule = healthDiagnosticMod.Rule;

View File

@@ -22,7 +22,7 @@ import type planningSnapshotMod = require('../planning-snapshot.cjs');
type PlanningSnapshot = ReturnType<typeof planningSnapshotMod.buildPlanningSnapshot>;
// eslint-disable-next-line @typescript-eslint/no-require-imports
import healthDiagnosticMod = require('../health-diagnostic.cjs');
import healthDiagnosticMod = require('../health-diagnostic-types.cjs');
const { SEVERITY, REMEDY_ACTION, REMEDY_RISK } = healthDiagnosticMod;
type Diagnostic = healthDiagnosticMod.Diagnostic;
type Rule = healthDiagnosticMod.Rule;

View File

@@ -42,7 +42,7 @@
// — unlike `health-diagnostic.cts`'s own type-only import of
// `planning-snapshot.cjs`, which never touches that module's runtime values.
// eslint-disable-next-line @typescript-eslint/no-require-imports -- export= CommonJS module
import healthDiagnosticMod = require('../health-diagnostic.cjs');
import healthDiagnosticMod = require('../health-diagnostic-types.cjs');
const { SEVERITY, REMEDY_ACTION, REMEDY_RISK } = healthDiagnosticMod;
type Rule = healthDiagnosticMod.Rule;
type Diagnostic = healthDiagnosticMod.Diagnostic;

View File

@@ -64,7 +64,7 @@ import type planningSnapshotMod = require('../planning-snapshot.cjs');
type PlanningSnapshot = ReturnType<typeof planningSnapshotMod.buildPlanningSnapshot>;
// eslint-disable-next-line @typescript-eslint/no-require-imports
import healthDiagnosticMod = require('../health-diagnostic.cjs');
import healthDiagnosticMod = require('../health-diagnostic-types.cjs');
const { SEVERITY, REMEDY_ACTION, REMEDY_RISK } = healthDiagnosticMod;
type Diagnostic = healthDiagnosticMod.Diagnostic;
type Rule = healthDiagnosticMod.Rule;

View File

@@ -0,0 +1,104 @@
/**
* Health Diagnostic Types — shared, dependency-free rule-table types (Phase
* 11, #3309, ADR-3180 §8.2/§8.3/§8.5).
*
* Split out from `src/health-diagnostic.cts` to break a CJS circular
* dependency between the evaluator and its own rule-group files
* (`src/health-diagnostic-rules/*.cts`): those files need the frozen
* `SEVERITY`/`REMEDY_ACTION`/`REMEDY_RISK` enums and the `Diagnostic`/
* `Remedy`/`Rule` shapes, but the evaluator (`health-diagnostic.cts`) also
* needs to `require()` every rule-group file to populate its `RULES` array —
* a rule-group file requiring `health-diagnostic.cjs` back, mid-load, reads
* `module.exports` before it is assigned, so the destructured enums come
* back `undefined`. This leaf has NO runtime dependency on anything in that
* cycle, so both sides can depend on it directly.
*
* Design: .gsd/phase/refactor-3309-health-diagnostic-rule-table/40-design.md
* Test matrix: .gsd/phase/refactor-3309-health-diagnostic-rule-table/50-test-matrix.md
*
* ADR-457 build-at-publish: source in src/health-diagnostic-types.cts,
* compiled to gsd-core/bin/lib/health-diagnostic-types.cjs (gitignored).
*/
// eslint-disable-next-line @typescript-eslint/no-require-imports -- type-only; erased at compile time, no runtime require emitted
import type planningSnapshotMod = require('./planning-snapshot.cjs');
type PlanningSnapshot = ReturnType<typeof planningSnapshotMod.buildPlanningSnapshot>;
// ─── Severity ───────────────────────────────────────────────────────────────
const SEVERITY = Object.freeze({
ERROR: 'error',
WARNING: 'warning',
INFO: 'info',
});
type Severity = (typeof SEVERITY)[keyof typeof SEVERITY];
// ─── Remedy action / risk ───────────────────────────────────────────────────
// Harvested from health.md's published table + the corrected 6-action
// implementation (`src/verify.cts:2405-2553`) — not 5; `addAiIntegrationPhaseKey`
// (verify.cts:1860/2481-2502) was live in code, missing from docs (design
// doc, "Ground truth vs. issue #3309's claims" section).
const REMEDY_ACTION = Object.freeze({
CREATE_CONFIG: 'createConfig',
RESET_CONFIG: 'resetConfig',
REGENERATE_STATE: 'regenerateState',
ADD_NYQUIST_KEY: 'addNyquistKey',
ADD_AI_INTEGRATION_PHASE_KEY: 'addAiIntegrationPhaseKey',
BACKFILL_MILESTONES: 'backfillMilestones',
// §8.3 rule 5 — every non-repairable finding's `fix` string becomes an
// ADVISE payload; ADVISE never acts, only describes.
ADVISE: 'advise',
});
type RemedyAction = (typeof REMEDY_ACTION)[keyof typeof REMEDY_ACTION];
const REMEDY_RISK = Object.freeze({
NONE: 'none',
DESTRUCTIVE: 'destructive',
});
type RemedyRisk = (typeof REMEDY_RISK)[keyof typeof REMEDY_RISK];
// ─── Diagnostic / Rule shapes ───────────────────────────────────────────────
interface Remedy {
action: RemedyAction;
risk: RemedyRisk;
args: Record<string, unknown>;
}
interface Diagnostic {
code: string; // e.g. 'W010' — append-only, never renumbered (§8.2 rule 2)
severity: Severity; // property of the RULE, never the emit call (§8.2 rule 3)
message: string;
remedy: Remedy;
}
interface Rule {
code: string;
severity: Severity;
check: (snapshot: PlanningSnapshot) => Diagnostic[]; // §8.1 rule 1 signature, verbatim
}
// ─── Exports ────────────────────────────────────────────────────────────────
const healthDiagnosticTypes = {
SEVERITY,
REMEDY_ACTION,
REMEDY_RISK,
};
// Namespace merge (same binding name as the value above) is how a CommonJS
// `export =` module exposes a type alongside its runtime export — `export
// type` is rejected by TS2309 ("An export assignment cannot be used in a
// module with other exported elements") when combined with `export =`, so
// these types ride along on the exported object via declaration merging
// instead. Mirrors `src/planning-scope.cts`'s exact mechanism. Consumers
// doing `import x = require('./health-diagnostic-types.cjs')` can reference
// the types as `x.Severity`, `x.RemedyAction`, etc.
// eslint-disable-next-line @typescript-eslint/no-namespace
declare namespace healthDiagnosticTypes {
export { Severity, RemedyAction, RemedyRisk, Remedy, Diagnostic, Rule };
}
export = healthDiagnosticTypes;

View File

@@ -30,68 +30,55 @@ import type planningSnapshotMod = require('./planning-snapshot.cjs');
type PlanningSnapshot = ReturnType<typeof planningSnapshotMod.buildPlanningSnapshot>;
// ─── Severity ───────────────────────────────────────────────────────────────
const SEVERITY = Object.freeze({
ERROR: 'error',
WARNING: 'warning',
INFO: 'info',
});
type Severity = (typeof SEVERITY)[keyof typeof SEVERITY];
// ─── Remedy action / risk ───────────────────────────────────────────────────
// Harvested from health.md's published table + the corrected 6-action
// implementation (`src/verify.cts:2405-2553`) — not 5; `addAiIntegrationPhaseKey`
// (verify.cts:1860/2481-2502) was live in code, missing from docs (design
// doc, "Ground truth vs. issue #3309's claims" section).
const REMEDY_ACTION = Object.freeze({
CREATE_CONFIG: 'createConfig',
RESET_CONFIG: 'resetConfig',
REGENERATE_STATE: 'regenerateState',
ADD_NYQUIST_KEY: 'addNyquistKey',
ADD_AI_INTEGRATION_PHASE_KEY: 'addAiIntegrationPhaseKey',
BACKFILL_MILESTONES: 'backfillMilestones',
// §8.3 rule 5 — every non-repairable finding's `fix` string becomes an
// ADVISE payload; ADVISE never acts, only describes.
ADVISE: 'advise',
});
type RemedyAction = (typeof REMEDY_ACTION)[keyof typeof REMEDY_ACTION];
const REMEDY_RISK = Object.freeze({
NONE: 'none',
DESTRUCTIVE: 'destructive',
});
type RemedyRisk = (typeof REMEDY_RISK)[keyof typeof REMEDY_RISK];
// ─── Diagnostic / Rule shapes ───────────────────────────────────────────────
interface Remedy {
action: RemedyAction;
risk: RemedyRisk;
args: Record<string, unknown>;
}
interface Diagnostic {
code: string; // e.g. 'W010' — append-only, never renumbered (§8.2 rule 2)
severity: Severity; // property of the RULE, never the emit call (§8.2 rule 3)
message: string;
remedy: Remedy;
}
interface Rule {
code: string;
severity: Severity;
check: (snapshot: PlanningSnapshot) => Diagnostic[]; // §8.1 rule 1 signature, verbatim
}
// Runtime values (SEVERITY/REMEDY_ACTION/REMEDY_RISK) are needed here — not
// just types — for `applyRepairs`'s comparisons, so this is a normal
// (non type-only) `import ... = require(...)`. `health-diagnostic-types.cjs`
// is the leaf module these enums/types were extracted to, so that this file
// can `require()` every rule-group file below without a circular dependency
// (see that module's file-level comment for the full explanation).
// eslint-disable-next-line @typescript-eslint/no-require-imports
import healthDiagnosticTypesMod = require('./health-diagnostic-types.cjs');
const { SEVERITY, REMEDY_ACTION, REMEDY_RISK } = healthDiagnosticTypesMod;
type Severity = healthDiagnosticTypesMod.Severity;
type RemedyAction = healthDiagnosticTypesMod.RemedyAction;
type RemedyRisk = healthDiagnosticTypesMod.RemedyRisk;
type Remedy = healthDiagnosticTypesMod.Remedy;
type Diagnostic = healthDiagnosticTypesMod.Diagnostic;
type Rule = healthDiagnosticTypesMod.Rule;
// ─── Rule table ─────────────────────────────────────────────────────────────
// Starts EMPTY. A later migration batch appends each of the 32 rule
// functions extracted from `cmdValidateHealth` (design doc, "Rule table
// organization" section) — this phase establishes only the container and its
// type.
const RULES: Rule[] = [];
// Populated by concatenating each rule group's exported `RULES` array (design
// doc, "Rule table organization" section) — the 32 rule functions extracted
// from `cmdValidateHealth`, `src/verify.cts:1616-2577`.
// eslint-disable-next-line @typescript-eslint/no-require-imports
import rootExistenceMod = require('./health-diagnostic-rules/root-existence.cjs');
// eslint-disable-next-line @typescript-eslint/no-require-imports
import stateConsistencyMod = require('./health-diagnostic-rules/state-consistency.cjs');
// eslint-disable-next-line @typescript-eslint/no-require-imports
import configValidationMod = require('./health-diagnostic-rules/config-validation.cjs');
// eslint-disable-next-line @typescript-eslint/no-require-imports
import phaseStructureMod = require('./health-diagnostic-rules/phase-structure.cjs');
// eslint-disable-next-line @typescript-eslint/no-require-imports
import agentInstallMod = require('./health-diagnostic-rules/agent-install.cjs');
// eslint-disable-next-line @typescript-eslint/no-require-imports
import roadmapDiskConsistencyMod = require('./health-diagnostic-rules/roadmap-disk-consistency.cjs');
// eslint-disable-next-line @typescript-eslint/no-require-imports
import worktreeHealthMod = require('./health-diagnostic-rules/worktree-health.cjs');
// eslint-disable-next-line @typescript-eslint/no-require-imports
import milestoneArchiveHygieneMod = require('./health-diagnostic-rules/milestone-archive-hygiene.cjs');
const RULES: Rule[] = [
...rootExistenceMod.RULES,
...stateConsistencyMod.RULES,
...configValidationMod.RULES,
...phaseStructureMod.RULES,
...agentInstallMod.RULES,
...roadmapDiskConsistencyMod.RULES,
...worktreeHealthMod.RULES,
...milestoneArchiveHygieneMod.RULES,
];
// ─── Evaluator ──────────────────────────────────────────────────────────────