fix(settings): wire fresh workspace defaults

This commit is contained in:
mvschwarz committed 2026-05-04 11:35:01 -07:00
1 parent cca3baada1
commit d2243da13b
8 files changed
+72 -8

No files matched your search

+6
View File
@@ -160,12 +160,16 @@ function setNestedValue(obj: Record<string, unknown>, parts: string[], value: un
// Per-subdir defaults derived from workspace.root. v0 uses canonical
// subdir names per founder dialog (slices/ steering/ progress/ field-notes/
// specs/). Steering is a FILE path (STEERING.md) under the steering subdir.
// Files and Progress default to the whole workspace so a fresh
// `rig config init-workspace` install is browsable without extra env wiring.
export function deriveWorkspaceDefault(key: ValidKey, workspaceRoot: string): string {
switch (key) {
case "workspace.slices_root": return join(workspaceRoot, "slices");
case "workspace.steering_path": return join(workspaceRoot, "steering", "STEERING.md");
case "workspace.field_notes_root": return join(workspaceRoot, "field-notes");
case "workspace.specs_root": return join(workspaceRoot, "specs");
case "files.allowlist": return `workspace:${workspaceRoot}`;
case "progress.scan_roots": return `workspace:${workspaceRoot}`;
default: return "";
}
}
@@ -175,6 +179,8 @@ const WORKSPACE_DERIVED_KEYS: ReadonlySet<ValidKey> = new Set([
"workspace.steering_path",
"workspace.field_notes_root",
"workspace.specs_root",
"files.allowlist",
"progress.scan_roots",
]);
function getDefaultValue(key: ValidKey, workspaceRoot: string): string | number | boolean {
@@ -92,6 +92,8 @@ describe("ConfigStore — extended namespaces (User Settings v0)", () => {
expect(cfg.workspace.steeringPath).toBe(join(cfg.workspace.root, "steering", "STEERING.md"));
expect(cfg.workspace.fieldNotesRoot).toBe(join(cfg.workspace.root, "field-notes"));
expect(cfg.workspace.specsRoot).toBe(join(cfg.workspace.root, "specs"));
expect(cfg.files.allowlist).toBe(`workspace:${cfg.workspace.root}`);
expect(cfg.progress.scanRoots).toBe(`workspace:${cfg.workspace.root}`);
});
it("setting workspace.root cascades into per-subdir defaults", () => {
@@ -101,6 +103,8 @@ describe("ConfigStore — extended namespaces (User Settings v0)", () => {
expect(cfg.workspace.root).toBe("/custom/ws");
expect(cfg.workspace.slicesRoot).toBe("/custom/ws/slices");
expect(cfg.workspace.steeringPath).toBe("/custom/ws/steering/STEERING.md");
expect(cfg.files.allowlist).toBe("workspace:/custom/ws");
expect(cfg.progress.scanRoots).toBe("workspace:/custom/ws");
});
it("per-subdir override beats workspace.root cascade", () => {
@@ -118,6 +118,24 @@ export function steeringOptsFromEnv(env: NodeJS.ProcessEnv = process.env): Steer
};
}
export interface SteeringSettingsDefaults {
workspaceRoot: string;
workspaceSteeringPath: string;
}
export function steeringOptsFromSettings(
settings: SteeringSettingsDefaults,
env: NodeJS.ProcessEnv = process.env,
): SteeringComposerOpts {
const envOpts = steeringOptsFromEnv(env);
return {
workspaceRoot: envOpts.workspaceRoot ?? settings.workspaceRoot,
steeringPath: envOpts.steeringPath ?? settings.workspaceSteeringPath,
roadmapPath: envOpts.roadmapPath ?? null,
deliveryReadyDir: envOpts.deliveryReadyDir ?? null,
};
}
export class SteeringComposer {
private readonly opts: Required<Omit<SteeringComposerOpts, "workspaceRoot">> & { workspaceRoot: string | null };
@@ -132,6 +132,8 @@ function deriveWorkspaceDefault(key: SettingsValidKey, workspaceRoot: string): s
case "workspace.steering_path": return path.join(workspaceRoot, "steering", "STEERING.md");
case "workspace.field_notes_root": return path.join(workspaceRoot, "field-notes");
case "workspace.specs_root": return path.join(workspaceRoot, "specs");
case "files.allowlist": return `workspace:${workspaceRoot}`;
case "progress.scan_roots": return `workspace:${workspaceRoot}`;
default: return "";
}
}
@@ -141,6 +143,8 @@ const WORKSPACE_DERIVED_KEYS: ReadonlySet<SettingsValidKey> = new Set([
"workspace.steering_path",
"workspace.field_notes_root",
"workspace.specs_root",
"files.allowlist",
"progress.scan_roots",
]);
function getDefaultValue(key: SettingsValidKey, workspaceRoot: string): string | number | boolean {
@@ -154,8 +158,6 @@ function getDefaultValue(key: SettingsValidKey, workspaceRoot: string): string |
case "transcripts.enabled": return true;
case "transcripts.path": return path.join(path.dirname(DEFAULT_CONFIG_PATH), "transcripts");
case "workspace.root": return DEFAULT_WORKSPACE_ROOT;
case "files.allowlist": return "";
case "progress.scan_roots": return "";
// Preview Terminal v0 (PL-018) defaults — match cli/src/config-store.ts.
case "ui.preview.refresh_interval_seconds": return 3;
case "ui.preview.max_pins": return 4;
+1 -1
View File
@@ -24,7 +24,7 @@ export function steeringRoutes(): Hono {
if (!composer.isReady()) {
return c.json({
error: "steering_workspace_not_configured",
hint: "Set OPENRIG_STEERING_WORKSPACE=/abs/path (and optionally OPENRIG_STEERING_PATH / OPENRIG_ROADMAP_PATH / OPENRIG_DELIVERY_READY_DIR for non-canonical layouts) and restart the daemon.",
hint: "Run rig config init-workspace, or set workspace.steering_path / OPENRIG_STEERING_PATH for non-canonical layouts, then restart the daemon.",
}, 503);
}
return c.json(composer.compose());
+7 -5
View File
@@ -795,11 +795,13 @@ export async function createDaemon(opts?: DaemonOptions): Promise<DaemonResult>
deps.progressIndexer = new ProgressIndexer({ roots: decodeProgressScanRoots(cfg.progressScanRootsRaw) });
// Operator Surface Reconciliation v0 — steering composer (item 1).
// Reads workspace root + per-section overrides from env. Empty
// workspace root → composer.isReady() = false → /api/steering
// returns 503 with structured setup hint.
const { SteeringComposer, steeringOptsFromEnv } = await import("./domain/steering/steering-composer.js");
deps.steeringComposer = new SteeringComposer(steeringOptsFromEnv());
// Reads typed workspace settings by default while preserving the
// OPENRIG_STEERING_* env override family for non-canonical layouts.
const { SteeringComposer, steeringOptsFromSettings } = await import("./domain/steering/steering-composer.js");
deps.steeringComposer = new SteeringComposer(steeringOptsFromSettings({
workspaceRoot: cfg.workspaceRoot,
workspaceSteeringPath: cfg.workspaceSteeringPath,
}));
// Workflows in Spec Library + Activation Lens v0 — active lens
// persistence under OPENRIG_HOME/active-workflow-lens.json. Same
@@ -21,6 +21,7 @@ import {
SteeringComposer,
matchRailItemCode,
steeringOptsFromEnv,
steeringOptsFromSettings,
} from "../src/domain/steering/steering-composer.js";
describe("Operator Surface Reconciliation v0 — matchRailItemCode", () => {
@@ -53,6 +54,35 @@ describe("Operator Surface Reconciliation v0 — steeringOptsFromEnv", () => {
expect(steeringOptsFromEnv({ OPENRIG_STEERING_WORKSPACE: "", RIGGED_STEERING_WORKSPACE: "/legacy" }))
.toMatchObject({ workspaceRoot: "/legacy" });
});
it("uses typed workspace settings as the fresh-install default, with env overrides still winning", () => {
const opts = steeringOptsFromSettings(
{
workspaceRoot: "/Users/me/.openrig/workspace",
workspaceSteeringPath: "/Users/me/.openrig/workspace/steering/STEERING.md",
},
{},
);
expect(opts).toMatchObject({
workspaceRoot: "/Users/me/.openrig/workspace",
steeringPath: "/Users/me/.openrig/workspace/steering/STEERING.md",
});
const overridden = steeringOptsFromSettings(
{
workspaceRoot: "/Users/me/.openrig/workspace",
workspaceSteeringPath: "/Users/me/.openrig/workspace/steering/STEERING.md",
},
{
OPENRIG_STEERING_WORKSPACE: "/env/workspace",
OPENRIG_STEERING_PATH: "/env/STEERING.md",
},
);
expect(overridden).toMatchObject({
workspaceRoot: "/env/workspace",
steeringPath: "/env/STEERING.md",
});
});
});
describe("Operator Surface Reconciliation v0 — SteeringComposer", () => {
@@ -104,6 +104,8 @@ describe("SettingsStore (User Settings v0)", () => {
expect(cfg.workspaceSteeringPath).toBe("/custom/ws/steering/STEERING.md");
expect(cfg.workspaceFieldNotesRoot).toBe("/custom/ws/field-notes");
expect(cfg.workspaceSpecsRoot).toBe("/custom/ws/specs");
expect(cfg.filesAllowlistRaw).toBe("workspace:/custom/ws");
expect(cfg.progressScanRootsRaw).toBe("workspace:/custom/ws");
});
it("per-subdir override beats the workspace.root cascade", () => {