Files
msd-core/get-shit-done/bin/lib/config.cjs
Fana ef032bc8f1 feat: harden Nyquist defaults, add retroactive validation, compress prompts (#855)
* fix: change nyquist_validation default to true and harden absent-key skip conditions

new-project.md never wrote the key, so agents reading config directly
treated absent as falsy. Changed all agent skip conditions from "is false"
to "explicitly set to false; absent = enabled". Default changed from false
to true in core.cjs, config.cjs, and templates/config.json.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: enforce VALIDATION.md creation with verification gate and Check 8e

Step 5.5 was narrative markdown that Claude skipped under context pressure.
Now MANDATORY with Write tool requirement and file-existence verification.
Step 7.5 gates planner spawn on VALIDATION.md presence. Check 8e blocks
Dimension 8 if file missing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add W008/W009 health checks and addNyquistKey repair for Nyquist drift detection

W008 warns when workflow.nyquist_validation key is absent from config.json
(agents may skip validation). W009 warns when RESEARCH.md has Validation
Architecture section but no VALIDATION.md file exists. addNyquistKey repair
adds the missing key with default true value.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add /gsd:validate-phase command and gsd-nyquist-auditor agent

Retroactively applies Nyquist validation to already-executed phases.
Works mid-milestone and post-milestone. Detects existing test coverage,
maps gaps to phase requirements, writes missing tests, debugs failing
ones, and produces {phase}-VALIDATION.md from existing artifacts.

Handles three states: VALIDATION.md exists (audit + update), no
VALIDATION.md (reconstruct from PLAN.md + SUMMARY.md), phase not yet
executed (exit cleanly with guidance).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: audit-milestone reports Nyquist compliance gaps across phases

Adds Nyquist coverage table to audit-milestone output when
workflow.nyquist_validation is true. Identifies phases missing
VALIDATION.md or with nyquist_compliant: false/partial.
Routes to /gsd:validate-phase for resolution. Updates USER-GUIDE
with retroactive validation documentation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: compress Nyquist prompts to match GSD meta-prompt density conventions

Auditor agent: deleted philosophy section (35 lines), compressed execution
flow 60%, removed redundant constraints. Workflow: cut purpose bloat,
collapsed state narrative, compressed auditor spawn template. Command:
removed redundant process section. Plan-phase Steps 5.5/7.5: replaced
hedging language with directives. Audit-milestone Step 5.5: collapsed
sub-steps into inline instructions. Net: -376 lines.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 11:19:44 -06:00

170 lines
5.1 KiB
JavaScript

/**
* Config — Planning config CRUD operations
*/
const fs = require('fs');
const path = require('path');
const { output, error } = require('./core.cjs');
function cmdConfigEnsureSection(cwd, raw) {
const configPath = path.join(cwd, '.planning', 'config.json');
const planningDir = path.join(cwd, '.planning');
// Ensure .planning directory exists
try {
if (!fs.existsSync(planningDir)) {
fs.mkdirSync(planningDir, { recursive: true });
}
} catch (err) {
error('Failed to create .planning directory: ' + err.message);
}
// Check if config already exists
if (fs.existsSync(configPath)) {
const result = { created: false, reason: 'already_exists' };
output(result, raw, 'exists');
return;
}
// Detect Brave Search API key availability
const homedir = require('os').homedir();
const braveKeyFile = path.join(homedir, '.gsd', 'brave_api_key');
const hasBraveSearch = !!(process.env.BRAVE_API_KEY || fs.existsSync(braveKeyFile));
// Load user-level defaults from ~/.gsd/defaults.json if available
const globalDefaultsPath = path.join(homedir, '.gsd', 'defaults.json');
let userDefaults = {};
try {
if (fs.existsSync(globalDefaultsPath)) {
userDefaults = JSON.parse(fs.readFileSync(globalDefaultsPath, 'utf-8'));
// Migrate deprecated "depth" key to "granularity"
if ('depth' in userDefaults && !('granularity' in userDefaults)) {
const depthToGranularity = { quick: 'coarse', standard: 'standard', comprehensive: 'fine' };
userDefaults.granularity = depthToGranularity[userDefaults.depth] || userDefaults.depth;
delete userDefaults.depth;
try { fs.writeFileSync(globalDefaultsPath, JSON.stringify(userDefaults, null, 2), 'utf-8'); } catch {}
}
}
} catch (err) {
// Ignore malformed global defaults, fall back to hardcoded
}
// Create default config (user-level defaults override hardcoded defaults)
const hardcoded = {
model_profile: 'balanced',
commit_docs: true,
search_gitignored: false,
branching_strategy: 'none',
phase_branch_template: 'gsd/phase-{phase}-{slug}',
milestone_branch_template: 'gsd/{milestone}-{slug}',
workflow: {
research: true,
plan_check: true,
verifier: true,
nyquist_validation: true,
},
parallelization: true,
brave_search: hasBraveSearch,
};
const defaults = {
...hardcoded,
...userDefaults,
workflow: { ...hardcoded.workflow, ...(userDefaults.workflow || {}) },
};
try {
fs.writeFileSync(configPath, JSON.stringify(defaults, null, 2), 'utf-8');
const result = { created: true, path: '.planning/config.json' };
output(result, raw, 'created');
} catch (err) {
error('Failed to create config.json: ' + err.message);
}
}
function cmdConfigSet(cwd, keyPath, value, raw) {
const configPath = path.join(cwd, '.planning', 'config.json');
if (!keyPath) {
error('Usage: config-set <key.path> <value>');
}
// Parse value (handle booleans and numbers)
let parsedValue = value;
if (value === 'true') parsedValue = true;
else if (value === 'false') parsedValue = false;
else if (!isNaN(value) && value !== '') parsedValue = Number(value);
// Load existing config or start with empty object
let config = {};
try {
if (fs.existsSync(configPath)) {
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
}
} catch (err) {
error('Failed to read config.json: ' + err.message);
}
// Set nested value using dot notation (e.g., "workflow.research")
const keys = keyPath.split('.');
let current = config;
for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];
if (current[key] === undefined || typeof current[key] !== 'object') {
current[key] = {};
}
current = current[key];
}
current[keys[keys.length - 1]] = parsedValue;
// Write back
try {
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
const result = { updated: true, key: keyPath, value: parsedValue };
output(result, raw, `${keyPath}=${parsedValue}`);
} catch (err) {
error('Failed to write config.json: ' + err.message);
}
}
function cmdConfigGet(cwd, keyPath, raw) {
const configPath = path.join(cwd, '.planning', 'config.json');
if (!keyPath) {
error('Usage: config-get <key.path>');
}
let config = {};
try {
if (fs.existsSync(configPath)) {
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
} else {
error('No config.json found at ' + configPath);
}
} catch (err) {
if (err.message.startsWith('No config.json')) throw err;
error('Failed to read config.json: ' + err.message);
}
// Traverse dot-notation path (e.g., "workflow.auto_advance")
const keys = keyPath.split('.');
let current = config;
for (const key of keys) {
if (current === undefined || current === null || typeof current !== 'object') {
error(`Key not found: ${keyPath}`);
}
current = current[key];
}
if (current === undefined) {
error(`Key not found: ${keyPath}`);
}
output(current, raw, String(current));
}
module.exports = {
cmdConfigEnsureSection,
cmdConfigSet,
cmdConfigGet,
};