refactor(#3309): consolidate adviseRemedy() into the shared types leaf
adviseRemedy() was defined identically in two of the eight rule-group
files (config-validation.cts, agent-install.cts) while the other six
repeated the same {action: ADVISE, risk: NONE, args: {command}} object
literal inline ~20+ times. Found by the Standards-axis orthogonal
review (Duplicated Code smell).
Moves the one-line helper into health-diagnostic-types.cts, the leaf
module every rule-group file already imports for its enums/types, and
uses it consistently across all 8 files. Pure mechanical refactor — no
ADVISE remedy's command text, code, or action changed.
This commit is contained in:
@@ -26,10 +26,9 @@
|
||||
|
||||
// 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;
|
||||
const { SEVERITY, adviseRemedy } = healthDiagnosticMod;
|
||||
type Rule = healthDiagnosticMod.Rule;
|
||||
type Diagnostic = healthDiagnosticMod.Diagnostic;
|
||||
type Remedy = healthDiagnosticMod.Remedy;
|
||||
|
||||
// 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');
|
||||
@@ -41,10 +40,6 @@ const { SCOPE } = planningScopeMod;
|
||||
|
||||
import { PACKAGE_NAME } from '../package-identity.cjs';
|
||||
|
||||
function adviseRemedy(command: string): Remedy {
|
||||
return { action: REMEDY_ACTION.ADVISE, risk: REMEDY_RISK.NONE, args: { command } };
|
||||
}
|
||||
|
||||
/**
|
||||
* `check(snapshot)` for W010 — see module header for the exact 4-way
|
||||
* branching this ports from `verify.cts:1992-2027`, and its message/fix
|
||||
|
||||
@@ -56,9 +56,8 @@ type PlanningSnapshot = ReturnType<typeof planningSnapshotMod.buildPlanningSnaps
|
||||
|
||||
// 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;
|
||||
const { SEVERITY, REMEDY_ACTION, REMEDY_RISK, adviseRemedy } = healthDiagnosticMod;
|
||||
type Diagnostic = healthDiagnosticMod.Diagnostic;
|
||||
type Remedy = healthDiagnosticMod.Remedy;
|
||||
type Rule = healthDiagnosticMod.Rule;
|
||||
|
||||
import { VALID_PROFILES, VALID_TIERS, VALID_PHASE_TYPES } from '../model-catalog.cjs';
|
||||
@@ -66,10 +65,6 @@ import { VALID_PROFILES, VALID_TIERS, VALID_PHASE_TYPES } from '../model-catalog
|
||||
// verify.cts:2141 — inlined literal, not exported from anywhere; same list.
|
||||
const VALID_BRANCHING_STRATEGIES = ['none', 'phase', 'milestone'];
|
||||
|
||||
function adviseRemedy(command: string): Remedy {
|
||||
return { action: REMEDY_ACTION.ADVISE, risk: REMEDY_RISK.NONE, args: { command } };
|
||||
}
|
||||
|
||||
// ─── W003 — config.json not found (verify.cts:1777-1785) ───────────────────
|
||||
|
||||
function checkW003(snapshot: PlanningSnapshot): Diagnostic[] {
|
||||
|
||||
@@ -23,7 +23,7 @@ type PlanningSnapshot = ReturnType<typeof planningSnapshotMod.buildPlanningSnaps
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
import healthDiagnosticMod = require('../health-diagnostic-types.cjs');
|
||||
const { SEVERITY, REMEDY_ACTION, REMEDY_RISK } = healthDiagnosticMod;
|
||||
const { SEVERITY, REMEDY_ACTION, REMEDY_RISK, adviseRemedy } = healthDiagnosticMod;
|
||||
type Diagnostic = healthDiagnosticMod.Diagnostic;
|
||||
type Rule = healthDiagnosticMod.Rule;
|
||||
|
||||
@@ -85,14 +85,9 @@ function checkW019(snapshot: PlanningSnapshot): Diagnostic[] {
|
||||
code: 'W019',
|
||||
severity: SEVERITY.WARNING,
|
||||
message: `Unrecognized .planning/ file: ${filename} — not a canonical GSD artifact`,
|
||||
remedy: {
|
||||
action: REMEDY_ACTION.ADVISE,
|
||||
risk: REMEDY_RISK.NONE,
|
||||
args: {
|
||||
command:
|
||||
'Move to .planning/milestones/ archive subdir or delete if stale. See templates/README.md for the canonical artifact list.',
|
||||
},
|
||||
},
|
||||
remedy: adviseRemedy(
|
||||
'Move to .planning/milestones/ archive subdir or delete if stale. See templates/README.md for the canonical artifact list.',
|
||||
),
|
||||
});
|
||||
}
|
||||
return diagnostics;
|
||||
|
||||
@@ -55,7 +55,7 @@ type PlanningSnapshot = ReturnType<typeof planningSnapshotMod.buildPlanningSnaps
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
import healthDiagnosticMod = require('../health-diagnostic-types.cjs');
|
||||
const { SEVERITY, REMEDY_ACTION, REMEDY_RISK } = healthDiagnosticMod;
|
||||
const { SEVERITY, adviseRemedy } = healthDiagnosticMod;
|
||||
type Diagnostic = healthDiagnosticMod.Diagnostic;
|
||||
type Rule = healthDiagnosticMod.Rule;
|
||||
|
||||
@@ -77,11 +77,7 @@ function checkW005(snapshot: PlanningSnapshot): Diagnostic[] {
|
||||
code: 'W005',
|
||||
severity: SEVERITY.WARNING,
|
||||
message: `Phase directory "${name}" doesn't follow NN-name format`,
|
||||
remedy: {
|
||||
action: REMEDY_ACTION.ADVISE,
|
||||
risk: REMEDY_RISK.NONE,
|
||||
args: { command: 'Rename to match pattern (e.g., 01-setup)' },
|
||||
},
|
||||
remedy: adviseRemedy('Rename to match pattern (e.g., 01-setup)'),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -127,14 +123,9 @@ function checkW023(snapshot: PlanningSnapshot): Diagnostic[] {
|
||||
code: 'W023',
|
||||
severity: SEVERITY.WARNING,
|
||||
message: `Phase directories collide on normalized key "${key}": ${described}`,
|
||||
remedy: {
|
||||
action: REMEDY_ACTION.ADVISE,
|
||||
risk: REMEDY_RISK.NONE,
|
||||
args: {
|
||||
command:
|
||||
'Inspect each directory; rename or remove the duplicate so only one directory maps to this phase key',
|
||||
},
|
||||
},
|
||||
remedy: adviseRemedy(
|
||||
'Inspect each directory; rename or remove the duplicate so only one directory maps to this phase key',
|
||||
),
|
||||
});
|
||||
}
|
||||
return diagnostics;
|
||||
@@ -161,11 +152,7 @@ function checkI001(snapshot: PlanningSnapshot): Diagnostic[] {
|
||||
code: 'I001',
|
||||
severity: SEVERITY.INFO,
|
||||
message: `Phase ${phase.dir} has ${deficit} plan(s) without a matching summary`,
|
||||
remedy: {
|
||||
action: REMEDY_ACTION.ADVISE,
|
||||
risk: REMEDY_RISK.NONE,
|
||||
args: { command: 'May be in progress' },
|
||||
},
|
||||
remedy: adviseRemedy('May be in progress'),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -183,11 +170,7 @@ function checkW009(snapshot: PlanningSnapshot): Diagnostic[] {
|
||||
code: 'W009',
|
||||
severity: SEVERITY.WARNING,
|
||||
message: `Phase ${entry.dir}: has Validation Architecture in RESEARCH.md but no VALIDATION.md`,
|
||||
remedy: {
|
||||
action: REMEDY_ACTION.ADVISE,
|
||||
risk: REMEDY_RISK.NONE,
|
||||
args: { command: 'Re-run /gsd-plan-phase with --research to regenerate' },
|
||||
},
|
||||
remedy: adviseRemedy('Re-run /gsd-plan-phase with --research to regenerate'),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ type PlanningSnapshot = ReturnType<typeof planningSnapshotMod.buildPlanningSnaps
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
import healthDiagnosticMod = require('../health-diagnostic-types.cjs');
|
||||
const { SEVERITY, REMEDY_ACTION, REMEDY_RISK } = healthDiagnosticMod;
|
||||
const { SEVERITY, adviseRemedy } = healthDiagnosticMod;
|
||||
type Diagnostic = healthDiagnosticMod.Diagnostic;
|
||||
type Rule = healthDiagnosticMod.Rule;
|
||||
|
||||
@@ -158,11 +158,7 @@ function checkW006(snapshot: PlanningSnapshot): Diagnostic[] {
|
||||
code: 'W006',
|
||||
severity: SEVERITY.WARNING,
|
||||
message: `Phase ${phaseId} in ROADMAP.md but no directory on disk`,
|
||||
remedy: {
|
||||
action: REMEDY_ACTION.ADVISE,
|
||||
risk: REMEDY_RISK.NONE,
|
||||
args: { command: 'Create phase directory or remove from roadmap' },
|
||||
},
|
||||
remedy: adviseRemedy('Create phase directory or remove from roadmap'),
|
||||
});
|
||||
}
|
||||
return diagnostics;
|
||||
@@ -205,11 +201,7 @@ function checkW007(snapshot: PlanningSnapshot): Diagnostic[] {
|
||||
code: 'W007',
|
||||
severity: SEVERITY.WARNING,
|
||||
message: `Phase ${token} exists on disk but not in ROADMAP.md`,
|
||||
remedy: {
|
||||
action: REMEDY_ACTION.ADVISE,
|
||||
risk: REMEDY_RISK.NONE,
|
||||
args: { command: 'Add to roadmap or remove directory' },
|
||||
},
|
||||
remedy: adviseRemedy('Add to roadmap or remove directory'),
|
||||
});
|
||||
}
|
||||
return diagnostics;
|
||||
|
||||
@@ -35,7 +35,7 @@ type PlanningSnapshot = ReturnType<typeof planningSnapshotMod.buildPlanningSnaps
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
import healthDiagnosticMod = require('../health-diagnostic-types.cjs');
|
||||
const { SEVERITY, REMEDY_ACTION, REMEDY_RISK } = healthDiagnosticMod;
|
||||
const { SEVERITY, REMEDY_ACTION, REMEDY_RISK, adviseRemedy } = healthDiagnosticMod;
|
||||
type Diagnostic = healthDiagnosticMod.Diagnostic;
|
||||
type Rule = healthDiagnosticMod.Rule;
|
||||
|
||||
@@ -52,11 +52,7 @@ function checkE002(snapshot: PlanningSnapshot): Diagnostic[] {
|
||||
code: 'E002',
|
||||
severity: SEVERITY.ERROR,
|
||||
message: 'PROJECT.md not found',
|
||||
remedy: {
|
||||
action: REMEDY_ACTION.ADVISE,
|
||||
risk: REMEDY_RISK.NONE,
|
||||
args: { command: '/gsd-new-project' },
|
||||
},
|
||||
remedy: adviseRemedy('/gsd-new-project'),
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -86,11 +82,7 @@ function checkE003(snapshot: PlanningSnapshot): Diagnostic[] {
|
||||
code: 'E003',
|
||||
severity: SEVERITY.ERROR,
|
||||
message: 'ROADMAP.md not found',
|
||||
remedy: {
|
||||
action: REMEDY_ACTION.ADVISE,
|
||||
risk: REMEDY_RISK.NONE,
|
||||
args: { command: '/gsd-new-milestone' },
|
||||
},
|
||||
remedy: adviseRemedy('/gsd-new-milestone'),
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -161,11 +153,7 @@ function checkW001(snapshot: PlanningSnapshot): Diagnostic[] {
|
||||
code: 'W001',
|
||||
severity: SEVERITY.WARNING,
|
||||
message: `PROJECT.md missing section: ${required}`,
|
||||
remedy: {
|
||||
action: REMEDY_ACTION.ADVISE,
|
||||
risk: REMEDY_RISK.NONE,
|
||||
args: { command: 'Add section manually' },
|
||||
},
|
||||
remedy: adviseRemedy('Add section manually'),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
// `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-types.cjs');
|
||||
const { SEVERITY, REMEDY_ACTION, REMEDY_RISK } = healthDiagnosticMod;
|
||||
const { SEVERITY, adviseRemedy } = healthDiagnosticMod;
|
||||
type Rule = healthDiagnosticMod.Rule;
|
||||
type Diagnostic = healthDiagnosticMod.Diagnostic;
|
||||
|
||||
@@ -161,14 +161,9 @@ const RULE_W002: Rule = {
|
||||
code: 'W002',
|
||||
severity: SEVERITY.WARNING,
|
||||
message: `STATE.md references phase ${ref}, but only phases ${sortedValid.join(', ')} are declared`,
|
||||
remedy: {
|
||||
action: REMEDY_ACTION.ADVISE,
|
||||
risk: REMEDY_RISK.NONE,
|
||||
args: {
|
||||
command:
|
||||
'Review STATE.md manually before changing it; /gsd-health --repair will not overwrite an existing STATE.md for phase mismatches',
|
||||
},
|
||||
},
|
||||
remedy: adviseRemedy(
|
||||
'Review STATE.md manually before changing it; /gsd-health --repair will not overwrite an existing STATE.md for phase mismatches',
|
||||
),
|
||||
});
|
||||
}
|
||||
return diagnostics;
|
||||
@@ -210,11 +205,7 @@ const RULE_W011: Rule = {
|
||||
code: 'W011',
|
||||
severity: SEVERITY.WARNING,
|
||||
message: `STATE.md says current phase is ${phaseId} (status: ${statusVal || 'unknown'}) but ROADMAP.md shows it as [x] complete — state files may be out of sync`,
|
||||
remedy: {
|
||||
action: REMEDY_ACTION.ADVISE,
|
||||
risk: REMEDY_RISK.NONE,
|
||||
args: { command: 'Run /gsd-progress to re-derive current position, or manually update STATE.md' },
|
||||
},
|
||||
remedy: adviseRemedy('Run /gsd-progress to re-derive current position, or manually update STATE.md'),
|
||||
},
|
||||
];
|
||||
},
|
||||
@@ -243,11 +234,7 @@ const RULE_W021: Rule = {
|
||||
code: 'W021',
|
||||
severity: SEVERITY.WARNING,
|
||||
message: `Phase ${entry.phaseId}: integer prefix implies ${expectedMilestone} but listed under ${entry.milestone}`,
|
||||
remedy: {
|
||||
action: REMEDY_ACTION.ADVISE,
|
||||
risk: REMEDY_RISK.NONE,
|
||||
args: { command: 'gsd-tools roadmap upgrade --convention milestone-prefixed' },
|
||||
},
|
||||
remedy: adviseRemedy('gsd-tools roadmap upgrade --convention milestone-prefixed'),
|
||||
});
|
||||
}
|
||||
return diagnostics;
|
||||
@@ -283,13 +270,9 @@ const RULE_W026: Rule = {
|
||||
code: 'W026',
|
||||
severity: SEVERITY.WARNING,
|
||||
message: `STATE says milestone complete but ROADMAP lists ${unstarted.length} unstarted phase(s) (e.g. Phase ${unstarted[0]})`,
|
||||
remedy: {
|
||||
action: REMEDY_ACTION.ADVISE,
|
||||
risk: REMEDY_RISK.NONE,
|
||||
args: {
|
||||
command: 'Run validate consistency or re-run complete-milestone after verifying all phases are done',
|
||||
},
|
||||
},
|
||||
remedy: adviseRemedy(
|
||||
'Run validate consistency or re-run complete-milestone after verifying all phases are done',
|
||||
),
|
||||
},
|
||||
];
|
||||
},
|
||||
|
||||
@@ -80,12 +80,27 @@ interface Rule {
|
||||
check: (snapshot: PlanningSnapshot) => Diagnostic[]; // §8.1 rule 1 signature, verbatim
|
||||
}
|
||||
|
||||
// ─── adviseRemedy — shared ADVISE-remedy builder ───────────────────────────
|
||||
|
||||
/**
|
||||
* Every rule-group file needs the same `{action: ADVISE, risk: NONE, args:
|
||||
* {command}}` shape for a non-repairable finding's `fix` string (§8.3 rule
|
||||
* 5). Was defined identically in `config-validation.cts` and
|
||||
* `agent-install.cts`, and repeated inline elsewhere — moved to this shared,
|
||||
* dependency-free leaf so every rule-group file imports one implementation
|
||||
* instead of duplicating it.
|
||||
*/
|
||||
function adviseRemedy(command: string): Remedy {
|
||||
return { action: REMEDY_ACTION.ADVISE, risk: REMEDY_RISK.NONE, args: { command } };
|
||||
}
|
||||
|
||||
// ─── Exports ────────────────────────────────────────────────────────────────
|
||||
|
||||
const healthDiagnosticTypes = {
|
||||
SEVERITY,
|
||||
REMEDY_ACTION,
|
||||
REMEDY_RISK,
|
||||
adviseRemedy,
|
||||
};
|
||||
|
||||
// Namespace merge (same binding name as the value above) is how a CommonJS
|
||||
|
||||
Reference in New Issue
Block a user