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:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
104
src/health-diagnostic-types.cts
Normal file
104
src/health-diagnostic-types.cts
Normal 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;
|
||||
@@ -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 ──────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
Reference in New Issue
Block a user