fix(#3894): quick path honors workflow.research_before_questions; key resolves from global defaults (#4047)
* test(#3894): research_before_questions must resolve globally and order quick.md (failing first) * fix(#3894): quick path honors workflow.research_before_questions; key resolves from global defaults Two layers, one key. The quick workflow ran its discussion phase before its research phase unconditionally — neither quick.md nor its steps ever read workflow.research_before_questions, though the key is documented, schema-registered, /gsd-settings-writable, and honored by /gsd-discuss-phase and /gsd-new-project. A gray-area answer given without research is then written to <quick_id>-CONTEXT.md as a locked decision downstream agents are told not to revisit — an evidence-free choice made unfalsifiable (the reporter's #3714 misresolution). - quick.md Step 4 now carries the same research-before-questions check the two honoring paths make: when enabled, research-phase executes before discussion-phase; false/unset keeps the written order. Both sections stay section-manifest gated. - src/config-loader.cts forwarded workflow.post_planning_gaps from ~/.gsd/defaults.json but silently dropped this key — same file, same nesting, one resolved and one didn't. Now forwarded with the same flat + nested-alias fallback shape, added to the resolution-keys lockstep canary and the #3532 shadowed-warning set (nested alias reporting generalized over both keys). Emitted-Drift-Ack-Growth: quick.md — #3894: +Step 4 ordering rule (the research-before-questions check the discuss-phase and new-project paths already make); a real behavioral gate, not incidental bloat. * fix(#3894): review fold-ins — gate the CONTEXT.md reference, colon slash-forms - quick/steps/research-phase.md directed the researcher subagent to read <quick_id>-CONTEXT.md under DISCUSS_MODE with no existence hedge — but under the new ordering (research BEFORE discussion) the file cannot exist yet when the researcher is dispatched. The reference now says read-only-if-present with the #3894 reason; the alignment purpose still applies on the default ordering. - quick.md's new rule used the hyphen slash forms (/gsd-discuss-phase, /gsd-new-project); source artifacts under gsd-core/workflows must author the colon form the install-time converters key on — the same file already uses /gsd:new-project and /gsd:quick elsewhere. * docs(#3894): planning-config row names the flat CONFIG_DEFAULTS alias config-field-docs requires every CONFIG_DEFAULTS key to appear in the doc; the row documented the canonical namespaced form only. Adds the same alias sentence post_planning_gaps's row carries, plus the #3894 quick-path note. * chore(#3894): changeset fragment (pr number backfilled after PR creation) * chore(#3894): backfill changeset PR number (4047) --------- Co-authored-by: sim <sim@local>
This commit is contained in:
@@ -133,6 +133,7 @@ const CONFIG_DEFAULTS = {
|
||||
security_asvs_level: _getNestedConfigDefault('workflow', 'security_asvs_level'),
|
||||
security_block_on: _getNestedConfigDefault('workflow', 'security_block_on'),
|
||||
post_planning_gaps: _getNestedConfigDefault('workflow', 'post_planning_gaps'),
|
||||
research_before_questions: _getNestedConfigDefault('workflow', 'research_before_questions'), // #3894
|
||||
smart_zone_tokens: _getNestedConfigDefault('workflow', 'smart_zone_tokens'),
|
||||
inline_plan_threshold: _getNestedConfigDefault('workflow', 'inline_plan_threshold'), // #3801
|
||||
max_prompt_tokens: _getNestedConfigDefault('review', 'max_prompt_tokens'),
|
||||
@@ -345,7 +346,7 @@ function _resetRuntimeWarningCacheForTests(): void {
|
||||
// drift in either direction.
|
||||
const GLOBAL_DEFAULTS_RESOLUTION_KEYS = [
|
||||
'model_profile', 'commit_docs', 'research', 'plan_checker', 'verifier',
|
||||
'nyquist_validation', 'post_planning_gaps', 'parallelization', 'text_mode',
|
||||
'nyquist_validation', 'post_planning_gaps', 'research_before_questions', 'parallelization', 'text_mode',
|
||||
'resolve_model_ids', 'context_window', 'subagent_timeout', 'model_overrides',
|
||||
'models', 'granularity', 'granularities', 'planning', 'dynamic_routing',
|
||||
'effort', 'fast_mode', 'agent_skills', 'response_language', 'runtime',
|
||||
@@ -365,11 +366,14 @@ function _warnShadowedGlobalDefaults(globalDefaults: Record<string, unknown>, gl
|
||||
// `?? globalDefaults['workflow']?.['post_planning_gaps']` fallback in
|
||||
// _globalBaseCfg) — a global file using only the nested form is equally
|
||||
// shadowed, so it reports under its dotted name.
|
||||
if (!shadowed.includes('post_planning_gaps')) {
|
||||
const wf = globalDefaults['workflow'];
|
||||
if (wf && typeof wf === 'object' && !Array.isArray(wf) &&
|
||||
Object.prototype.hasOwnProperty.call(wf, 'post_planning_gaps')) {
|
||||
shadowed.push('workflow.post_planning_gaps');
|
||||
// #3894: research_before_questions gets the same nested-alias reporting.
|
||||
const nestedAliasKeys = ['post_planning_gaps', 'research_before_questions'];
|
||||
const wf = globalDefaults['workflow'];
|
||||
if (wf && typeof wf === 'object' && !Array.isArray(wf)) {
|
||||
for (const k of nestedAliasKeys) {
|
||||
if (!shadowed.includes(k) && Object.prototype.hasOwnProperty.call(wf, k)) {
|
||||
shadowed.push(`workflow.${k}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (shadowed.length === 0) return;
|
||||
@@ -1020,6 +1024,12 @@ function loadConfigResolved(cwd: string, options: Record<string, unknown> = {}):
|
||||
post_planning_gaps: (globalDefaults['post_planning_gaps'])
|
||||
?? (globalDefaults['workflow'] as Record<string, unknown> | undefined)?.['post_planning_gaps']
|
||||
?? defaults.post_planning_gaps,
|
||||
// #3894: same nested-alias shape as post_planning_gaps above — the key
|
||||
// was silently dropped from global defaults, so it was unavailable at
|
||||
// user scope AND inert at project scope on the /gsd-quick path.
|
||||
research_before_questions: (globalDefaults['research_before_questions'])
|
||||
?? (globalDefaults['workflow'] as Record<string, unknown> | undefined)?.['research_before_questions']
|
||||
?? defaults.research_before_questions,
|
||||
parallelization: (globalDefaults['parallelization']) ?? defaults.parallelization,
|
||||
text_mode: (globalDefaults['text_mode']) ?? defaults.text_mode,
|
||||
resolve_model_ids: (globalDefaults['resolve_model_ids']) ?? defaults.resolve_model_ids,
|
||||
|
||||
Reference in New Issue
Block a user