fix(#3972): one worktreesOptedOut ladder in planning-workspace, shared by resolver and guard (#3979)
* test(#3972): the guard fallback must share the opt-out ladder * fix(#3972): one worktreesOptedOut ladder in planning-workspace, shared by resolver and guard * test(#3972): without a workstream the root config is the effective config * fix(#3972): guard the whole ladder body — malformed env values degrade, never throw * chore(#3972): changeset fragment (pr number backfilled after PR creation) * chore(#3972): backfill changeset PR number (3979) --------- Co-authored-by: sim <sim@local>
This commit is contained in:
@@ -1751,40 +1751,14 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
||||
* comment above). Never throws.
|
||||
*/
|
||||
function projectWorktreesOptedOut(cwd) {
|
||||
// #3963: the read must see the value `config-get` sees. Under
|
||||
// GSD_WORKSTREAM, config-get inherits the ROOT config for keys the
|
||||
// workstream config omits (config.cts resolveFromRootConfig, the #2714
|
||||
// inheritance; loadConfig's deep-merge implements the same ladder for
|
||||
// its consumers), so a root-level `use_worktrees: false` is part of the
|
||||
// effective config even when the workstream file omits the key. Mirror
|
||||
// that ladder for this one key with direct reads — never loadConfig,
|
||||
// which normalizes and rewrites config on a path that backs sentinel
|
||||
// writes (same discipline as the resolveRuntime comment in
|
||||
// resolveDispatchIsolationDecision).
|
||||
// Semantics: the workstream's OWN key wins; otherwise the root's (only
|
||||
// under the ws-env gate — loadConfigResolved does NOT inherit root under
|
||||
// GSD_PROJECT alone, and this read must not diverge from that); strict
|
||||
// `=== false`; any read failure degrades to "not opted out".
|
||||
// #3972: single owner — planning-workspace's worktreesOptedOut ladder
|
||||
// (scoped own-key, root inheritance under the ws gate, strict === false).
|
||||
// Kept as a local name so routeDispatchIsolation's call sites read the
|
||||
// same as they did in #3938/#3963; the logic itself now lives beside
|
||||
// planningDir/planningRoot where every isolation surface can share it.
|
||||
try {
|
||||
const { planningDir, planningRoot } = require('./lib/planning-workspace.cjs');
|
||||
const readCfg = (p) => {
|
||||
try {
|
||||
return JSON.parse(require('fs').readFileSync(p, 'utf8'));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
const ownKey = (cfg) =>
|
||||
cfg != null && typeof cfg === 'object'
|
||||
&& cfg.workflow != null && typeof cfg.workflow === 'object'
|
||||
&& Object.prototype.hasOwnProperty.call(cfg.workflow, 'use_worktrees');
|
||||
const wsCfg = readCfg(require('path').join(planningDir(cwd), 'config.json'));
|
||||
if (ownKey(wsCfg)) return wsCfg.workflow.use_worktrees === false;
|
||||
if (process.env['GSD_WORKSTREAM']) {
|
||||
const rootCfg = readCfg(require('path').join(planningRoot(cwd), 'config.json'));
|
||||
if (ownKey(rootCfg)) return rootCfg.workflow.use_worktrees === false;
|
||||
}
|
||||
return false;
|
||||
const { worktreesOptedOut } = require('./lib/planning-workspace.cjs');
|
||||
return worktreesOptedOut(cwd);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user