@@ -174,7 +174,7 @@ const path = require('path');
|
||||
const core = require('./lib/core.cjs');
|
||||
const { error, findProjectRoot, ERROR_REASON } = core;
|
||||
const { getActiveWorkstream } = require('./lib/planning-workspace.cjs');
|
||||
const { resolveActiveWorkstream, applyResolvedWorkstreamEnv } = require('./lib/active-workstream-store.cjs');
|
||||
const { resolveActiveWorkstreamContext, applyResolvedWorkstreamEnv } = require('./lib/active-workstream-store.cjs');
|
||||
const state = require('./lib/state.cjs');
|
||||
const phase = require('./lib/phase.cjs');
|
||||
const roadmap = require('./lib/roadmap.cjs');
|
||||
@@ -293,14 +293,15 @@ async function main() {
|
||||
// Optional workstream override for parallel milestone work.
|
||||
// Priority: --ws flag > GSD_WORKSTREAM env var > session/shared pointer > null.
|
||||
let ws = null;
|
||||
let workstreamContext = null;
|
||||
try {
|
||||
const wsResolution = resolveActiveWorkstream(cwd, args, process.env, {
|
||||
workstreamContext = resolveActiveWorkstreamContext(cwd, args, process.env, {
|
||||
getStored: getActiveWorkstream,
|
||||
});
|
||||
ws = wsResolution.ws;
|
||||
args = wsResolution.args;
|
||||
ws = workstreamContext.ws;
|
||||
args = workstreamContext.args;
|
||||
// Set env var so all modules (planningDir, planningPaths) auto-resolve workstream paths.
|
||||
applyResolvedWorkstreamEnv(wsResolution, process.env);
|
||||
applyResolvedWorkstreamEnv(workstreamContext, process.env);
|
||||
} catch (err) {
|
||||
error(err.message || String(err));
|
||||
}
|
||||
@@ -431,7 +432,7 @@ async function main() {
|
||||
// When --pick is active, capture stdout and extract the requested field.
|
||||
if (pickField) {
|
||||
const captured = await captureStdoutSyncWrites(async () => {
|
||||
await runCommand(command, args, cwd, raw, defaultValue, originalCommand);
|
||||
await runCommand(command, args, cwd, raw, defaultValue, originalCommand, workstreamContext);
|
||||
});
|
||||
const resolved = resolveAtFileOutput(captured);
|
||||
try {
|
||||
@@ -451,7 +452,7 @@ async function main() {
|
||||
// every workflow to have a bash-specific `if [[ "$INIT" == @file:* ]]` check
|
||||
// that breaks on PowerShell and other non-bash shells.
|
||||
const captured = await captureStdoutSyncWrites(async () => {
|
||||
await runCommand(command, args, cwd, raw, defaultValue, originalCommand);
|
||||
await runCommand(command, args, cwd, raw, defaultValue, originalCommand, workstreamContext);
|
||||
});
|
||||
fs.writeSync(1, resolveAtFileOutput(captured));
|
||||
}
|
||||
@@ -518,7 +519,7 @@ function extractField(obj, fieldPath) {
|
||||
return current;
|
||||
}
|
||||
|
||||
async function runCommand(command, args, cwd, raw, defaultValue, originalCommand) {
|
||||
async function runCommand(command, args, cwd, raw, defaultValue, originalCommand, workstreamContext = null) {
|
||||
switch (command) {
|
||||
case 'state': {
|
||||
routeStateCommand({
|
||||
@@ -801,7 +802,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
||||
// The SDK handler (configPath) also exists but requires a projectDir that
|
||||
// is already resolved. Both produce identical output; keeping CJS here is
|
||||
// simpler and avoids sync-bridge overhead for a trivial path lookup.
|
||||
config.cmdConfigPath(cwd, raw);
|
||||
config.cmdConfigPath(cwd, raw, workstreamContext);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -651,10 +651,12 @@ function getCmdConfigSetModelProfileResultMessage(
|
||||
* Print the resolved config.json path (workstream-aware). Used by settings.md
|
||||
* so the workflow writes/reads the correct file when a workstream is active (#2282).
|
||||
*/
|
||||
function cmdConfigPath(cwd) {
|
||||
function cmdConfigPath(cwd, _raw, workstreamContext = null) {
|
||||
// Always emit as plain text — a file path is used via shell substitution,
|
||||
// never consumed as JSON. Passing raw=true forces plain-text output.
|
||||
const configPath = path.join(planningDir(cwd), 'config.json');
|
||||
const configPath = workstreamContext && workstreamContext.configPath
|
||||
? workstreamContext.configPath
|
||||
: path.join(planningDir(cwd), 'config.json');
|
||||
output(configPath, true, configPath);
|
||||
}
|
||||
|
||||
|
||||
@@ -280,6 +280,8 @@ function _deepMergeConfig(base, overlay) {
|
||||
function loadConfig(cwd, options = {}) {
|
||||
const activeWorkstream = Object.prototype.hasOwnProperty.call(options, 'workstream')
|
||||
? options.workstream
|
||||
: (options.workstreamContext && Object.prototype.hasOwnProperty.call(options.workstreamContext, 'ws'))
|
||||
? options.workstreamContext.ws
|
||||
: (process.env.GSD_WORKSTREAM || null);
|
||||
// When GSD_WORKSTREAM is set, load root config first so workstream config
|
||||
// can inherit from it. This prevents users from duplicating model_overrides,
|
||||
|
||||
@@ -298,6 +298,19 @@ describe('loadConfig workstream config inheritance (#2714)', () => {
|
||||
assert.strictEqual(config.model_profile, 'quality');
|
||||
assert.strictEqual(process.env.GSD_WORKSTREAM, 'feature-f');
|
||||
});
|
||||
|
||||
test('loadConfig accepts workstreamContext.ws without requiring env mutation', () => {
|
||||
writeRootConfig({ model_profile: 'balanced' });
|
||||
writeWorkstreamConfig('feature-g', { model_profile: 'quality' });
|
||||
delete process.env.GSD_WORKSTREAM;
|
||||
|
||||
const config = loadConfig(tmpDir, {
|
||||
workstreamContext: { ws: 'feature-g' },
|
||||
});
|
||||
|
||||
assert.strictEqual(config.model_profile, 'quality');
|
||||
assert.strictEqual(process.env.GSD_WORKSTREAM, undefined);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── loadConfig commit_docs gitignore auto-detection (#1250) ──────────────────
|
||||
|
||||
Reference in New Issue
Block a user