pi-pier 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -12
- package/package.json +3 -3
- package/schemas/role-manifest.schema.json +7 -1
- package/skills/pi-herdr-workflow/SKILL.md +4 -5
- package/src/ansi-text.ts +62 -11
- package/src/ask-dialog.ts +308 -0
- package/src/ask-user.ts +113 -112
- package/src/bootstrap.ts +10 -13
- package/src/compact-coordinator.ts +279 -261
- package/src/compact-economics-core.ts +77 -145
- package/src/config-catalog-core.ts +77 -197
- package/src/config-command.ts +271 -64
- package/src/cordis-services.ts +64 -0
- package/src/dashboard-command.ts +77 -33
- package/src/delegation-core.ts +115 -0
- package/src/efficiency-config-core.ts +286 -486
- package/src/efficiency-store.ts +16 -29
- package/src/gc-core.ts +10 -36
- package/src/guards.ts +6 -0
- package/src/herdr-client.ts +202 -233
- package/src/history-store.ts +38 -23
- package/src/index-gates.ts +118 -0
- package/src/index-locks.ts +2 -7
- package/src/index-master.ts +31 -29
- package/src/index-notices.ts +28 -15
- package/src/index-pipe.ts +31 -12
- package/src/index-roles.ts +306 -0
- package/src/index-runtime.ts +16 -6
- package/src/index-worker.ts +8 -18
- package/src/index.ts +561 -570
- package/src/jev-client.ts +12 -17
- package/src/jev-core.ts +138 -90
- package/src/ledger.ts +13 -21
- package/src/lock-core.ts +28 -56
- package/src/manifest-compose.ts +35 -20
- package/src/observation-core.ts +52 -100
- package/src/pane-title.ts +19 -38
- package/src/panel-open.ts +47 -0
- package/src/pi-surface.ts +34 -22
- package/src/pier-options.ts +53 -50
- package/src/pipe-channel.ts +44 -59
- package/src/platform-paths.ts +19 -52
- package/src/{core → plugins}/grid-shape.ts +8 -10
- package/src/{core → plugins}/heat-plan.ts +20 -22
- package/src/plugins/observation.ts +475 -0
- package/src/plugins/subagent.ts +766 -0
- package/src/{core → plugins}/terminal.ts +198 -139
- package/src/{core → plugins}/todo.ts +73 -134
- package/src/progress-core.ts +16 -53
- package/src/reconcile-core.ts +24 -33
- package/src/reducer-core.ts +26 -64
- package/src/reducer-invoker.ts +266 -239
- package/src/renderers.ts +108 -84
- package/src/role-loader.ts +33 -28
- package/src/role-manifest.ts +25 -31
- package/src/role-state.ts +119 -0
- package/src/roles/worker-default.json +3 -3
- package/src/routing-telemetry.ts +136 -0
- package/src/runtime-policy.ts +25 -40
- package/src/session-tail.ts +65 -41
- package/src/settle-wake-core.ts +61 -22
- package/src/slim-frame.ts +25 -122
- package/src/stale-core.ts +17 -24
- package/src/storage-layout.ts +14 -43
- package/src/subagent-core.ts +423 -120
- package/src/subagent-gc.ts +109 -67
- package/src/subagent-poller.ts +383 -24
- package/src/subagent-session.ts +516 -0
- package/src/subagent-spawn.ts +577 -52
- package/src/swallow.ts +7 -17
- package/src/terminal-core.ts +187 -134
- package/src/todo-core.ts +57 -109
- package/src/todo-read-hook.ts +46 -39
- package/src/todo-reminder-core.ts +26 -51
- package/src/todo-window.ts +11 -12
- package/src/todos-service.ts +15 -35
- package/src/tool-error.ts +6 -13
- package/src/tool-gate.ts +38 -21
- package/src/tree-view.ts +162 -0
- package/src/vocab.ts +52 -25
- package/src/worker-marker.ts +76 -0
- package/src/ask-multi.ts +0 -260
- package/src/config-guide.ts +0 -361
- package/src/core/observation.ts +0 -555
- package/src/core/subagent.ts +0 -633
- package/src/efficiency-config-io.ts +0 -53
- package/src/focus-poller.ts +0 -244
- package/src/git-adapter.ts +0 -113
- package/src/notice-buffer.ts +0 -23
- package/src/subagent-execute.ts +0 -328
- package/src/subagent-git-io.ts +0 -80
- package/src/subagent-launch.ts +0 -114
- package/src/subagent-list-action.ts +0 -69
- package/src/subagent-output-action.ts +0 -104
- package/src/subagent-output-adapter.ts +0 -27
- package/src/subagent-output-core.ts +0 -263
- package/src/subagent-poll-loop.ts +0 -305
- package/src/subagent-port.ts +0 -27
- package/src/subagent-ready.ts +0 -89
- package/src/subagent-registry.ts +0 -70
- package/src/subagent-resolution.ts +0 -41
- package/src/subagent-scope.ts +0 -41
- package/src/subagent-session-io.ts +0 -230
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* D104
|
|
2
|
+
* D104 config catalog core (pure).
|
|
3
3
|
*
|
|
4
|
-
* Single source of truth for the
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* No I/O here; file/env reading lives in config-guide.ts. The catalog is guarded
|
|
9
|
-
* against drift by test/config-catalog-core.test.ts, which diffs it against
|
|
10
|
-
* schemas/efficiency-config.schema.json, schemas/role-manifest.schema.json and the
|
|
11
|
-
* env keys actually read by runtime-policy.ts / efficiency-config-core.ts.
|
|
4
|
+
* Single source of truth for the four pier configuration planes: knob descriptors, effective-value
|
|
5
|
+
* provenance (env > workspace > user > default) and rendering for `/pier-config show|check|doc`. The
|
|
6
|
+
* `env` plane derives from the `PIER_OPTIONS` registry, so it cannot disagree with the runtime readers;
|
|
7
|
+
* no I/O here (config-command.ts does the reading) and test/config-catalog.test.ts guards the mirror.
|
|
12
8
|
*/
|
|
13
9
|
|
|
14
|
-
|
|
10
|
+
import { PIER_OPTIONS } from './pier-options.ts';
|
|
11
|
+
|
|
12
|
+
export type ConfigPlaneId = 'efficiency' | 'roles' | 'pi' | 'env';
|
|
15
13
|
|
|
16
14
|
export type ConfigKind = 'boolean' | 'number' | 'string' | 'enum';
|
|
17
15
|
|
|
@@ -27,9 +25,7 @@ export interface ConfigKnob {
|
|
|
27
25
|
readonly min?: number;
|
|
28
26
|
/** Historical spellings still accepted for an env knob (B10: canonical `PIER_*` first). */
|
|
29
27
|
readonly aliases?: readonly string[];
|
|
30
|
-
/** One-line effect/impact, shown by `show`. */
|
|
31
28
|
readonly impact: string;
|
|
32
|
-
/** Where to read more (docs/schema anchor). */
|
|
33
29
|
readonly docRef?: string;
|
|
34
30
|
/** True when pier only reads the value (owned by pi / herdr). */
|
|
35
31
|
readonly readOnly?: boolean;
|
|
@@ -38,10 +34,9 @@ export interface ConfigKnob {
|
|
|
38
34
|
export interface ConfigPlane {
|
|
39
35
|
readonly id: ConfigPlaneId;
|
|
40
36
|
readonly title: string;
|
|
41
|
-
readonly owner: 'pier' | 'pi'
|
|
37
|
+
readonly owner: 'pier' | 'pi';
|
|
42
38
|
/** Human-facing path templates, in precedence order. */
|
|
43
39
|
readonly files: readonly string[];
|
|
44
|
-
/** What to do to change this plane. */
|
|
45
40
|
readonly editHint: string;
|
|
46
41
|
}
|
|
47
42
|
|
|
@@ -67,13 +62,6 @@ export const CONFIG_PLANES: readonly ConfigPlane[] = Object.freeze([
|
|
|
67
62
|
files: ['~/.pi/agent/settings.json', '<trusted-project>/.pi/settings.json'],
|
|
68
63
|
editHint: 'Use pi\'s /settings for everything except the OCC-relevant compaction.* keys',
|
|
69
64
|
},
|
|
70
|
-
{
|
|
71
|
-
id: 'boot',
|
|
72
|
-
title: 'Workbench boot-config',
|
|
73
|
-
owner: 'workbench',
|
|
74
|
-
files: ['$HERDR_PLUGIN_CONFIG_DIR/boot-config.json', 'packages/pier-workbench/scripts/boot-config.json'],
|
|
75
|
-
editHint: 'Prefer `npx pier-setup@latest update --force` over hand-editing paths',
|
|
76
|
-
},
|
|
77
65
|
{
|
|
78
66
|
id: 'env',
|
|
79
67
|
title: 'Runtime policy env (PIER_* / PI_HERDR_*)',
|
|
@@ -83,7 +71,7 @@ export const CONFIG_PLANES: readonly ConfigPlane[] = Object.freeze([
|
|
|
83
71
|
},
|
|
84
72
|
] as const);
|
|
85
73
|
|
|
86
|
-
/** Efficiency knobs: mirrors schemas/efficiency-config.schema.json (guarded by the
|
|
74
|
+
/** Efficiency knobs: mirrors schemas/efficiency-config.schema.json (guarded by the catalog test). */
|
|
87
75
|
const EFFICIENCY_KNOBS: readonly ConfigKnob[] = [
|
|
88
76
|
{ plane: 'efficiency', key: 'onlineContextCompact.enabled', kind: 'boolean', defaultValue: false, envVar: 'PI_HERDR_COMPACT_ENABLE', impact: 'Todo-driven online compaction (OCC) master switch', docRef: 'docs/efficiency-trial.md' },
|
|
89
77
|
{ plane: 'efficiency', key: 'onlineContextCompact.logEnabled', kind: 'boolean', defaultValue: false, envVar: 'PI_HERDR_COMPACT_LOG', impact: 'Write compact.jsonl decisions', docRef: 'docs/efficiency-trial.md' },
|
|
@@ -114,80 +102,29 @@ const EFFICIENCY_KNOBS: readonly ConfigKnob[] = [
|
|
|
114
102
|
{ plane: 'efficiency', key: 'jev.apiKey', kind: 'string', envVar: 'PIER_JEV_API_KEY', impact: 'API key; PIER_JEV_API_KEY env > config value > TYPESAFE_API_KEY env', docRef: 'docs/rfc-jev-integration.md' },
|
|
115
103
|
];
|
|
116
104
|
|
|
117
|
-
/** Role-manifest keys (mirrors schemas/role-manifest.schema.json). Roles are per-file, so these are inventory keys. */
|
|
118
|
-
const ROLE_KNOBS: readonly ConfigKnob[] = [
|
|
119
|
-
{ plane: 'roles', key: 'role', kind: 'string', impact: 'Role name (must match the file name)', docRef: 'docs/sidebar-role-config.md' },
|
|
120
|
-
{ plane: 'roles', key: 'version', kind: 'string', impact: 'Role profile version string', docRef: 'docs/sidebar-role-config.md' },
|
|
121
|
-
{ plane: 'roles', key: 'model', kind: 'string', impact: 'Dispatch routing: provider/model for panes spawned with this role', docRef: 'docs/sidebar-role-config.md' },
|
|
122
|
-
{ plane: 'roles', key: 'description', kind: 'string', impact: 'Human description shown in role listings', docRef: 'docs/sidebar-role-config.md' },
|
|
123
|
-
{ plane: 'roles', key: 'manifest.tools', kind: 'string', impact: 'Visible tool names for the role', docRef: 'docs/sidebar-role-config.md' },
|
|
124
|
-
{ plane: 'roles', key: 'manifest.rules', kind: 'string', impact: 'Permission map: tool name → allow|ask|deny', docRef: 'docs/sidebar-role-config.md' },
|
|
125
|
-
{ plane: 'roles', key: 'manifest.unknownTools', kind: 'enum', impact: 'Stance for tools missing from manifest.tools: allow|deny', docRef: 'docs/sidebar-role-config.md' },
|
|
126
|
-
{ plane: 'roles', key: 'services.todos', kind: 'string', impact: 'Todo service mode for the role: serial|parallel', docRef: 'docs/sidebar-role-config.md' },
|
|
127
|
-
];
|
|
128
|
-
|
|
129
105
|
/** Pi-owned keys pier only reads. */
|
|
130
106
|
const PI_KNOBS: readonly ConfigKnob[] = [
|
|
131
107
|
{ plane: 'pi', key: 'compaction.enabled', kind: 'boolean', impact: 'When false, OCC is disabled too (unless PI_HERDR_COMPACT_ENABLE=1)', docRef: 'docs/efficiency-trial.md', readOnly: true },
|
|
132
108
|
{ plane: 'pi', key: 'compaction.keepRecentTokens', kind: 'number', impact: 'Inherited as the OCC retention window when not set in the efficiency config', docRef: 'docs/efficiency-trial.md', readOnly: true },
|
|
133
109
|
];
|
|
134
110
|
|
|
135
|
-
/**
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
]
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
*/
|
|
149
|
-
const
|
|
150
|
-
{ plane: 'env', key: 'PIER_SUBAGENT_TIMEOUT_MS', aliases: ['PI_HERDR_SUBAGENT_TIMEOUT_MS'], kind: 'number', defaultValue: 600000, min: 1000, impact: 'Subagent inactivity budget before forced termination' },
|
|
151
|
-
{ plane: 'env', key: 'PIER_GC_TICK_MS', aliases: ['PI_HERDR_GC_TICK_MS'], kind: 'number', defaultValue: 30000, min: 1000, impact: 'Subagent GC ticker interval' },
|
|
152
|
-
{ plane: 'env', key: 'PIER_POLL_INTERVAL_MS', aliases: ['PI_HERDR_POLL_INTERVAL_MS'], kind: 'number', defaultValue: 30000, min: 1000, impact: 'Subagent state observation poll interval' },
|
|
153
|
-
{ plane: 'env', key: 'PIER_SETTLEMENT_WINDOW_MS', aliases: ['PI_HERDR_SETTLEMENT_WINDOW_MS'], kind: 'number', defaultValue: 60000, min: 0, impact: 'Settlement notice window / machine-inject grace / takeover idle' },
|
|
154
|
-
{ plane: 'env', key: 'PIER_OBSERVATION_WINDOW_MS', aliases: ['PI_HERDR_OBSERVATION_WINDOW_MS'], kind: 'number', defaultValue: 30000, min: 0, impact: 'Post-settle observation window before auto-consume' },
|
|
155
|
-
{ plane: 'env', key: 'PIER_FOREGROUND_PATIENCE_MS', aliases: ['PI_HERDR_FOREGROUND_PATIENCE_MS'], kind: 'number', defaultValue: 300000, min: 0, impact: 'Foreground patience before demoting a subagent to background' },
|
|
156
|
-
{ plane: 'env', key: 'PIER_SESSION_TTL_SECONDS', aliases: ['PI_HERDR_SESSION_TTL_SECONDS'], kind: 'number', defaultValue: 600, min: 0, impact: 'Session retention after subagent exit before GC' },
|
|
157
|
-
{ plane: 'env', key: 'PIER_GIT_TIMEOUT_MS', aliases: ['PI_HERDR_GIT_TIMEOUT_MS'], kind: 'number', defaultValue: 10000, min: 1, impact: 'git worktree/diff/cleanup execution timeout' },
|
|
158
|
-
{ plane: 'env', key: 'PIER_READY_TIMEOUT_MS', aliases: ['PI_HERDR_READY_TIMEOUT_MS'], kind: 'number', defaultValue: 90000, min: 1000, impact: 'Subagent pane pipe readiness wait (backoff; a dead pane fails fast)' },
|
|
159
|
-
{ plane: 'env', key: 'PIER_ISOLATE_SWEEP_ORPHANS', aliases: ['PI_HERDR_ISOLATE_SWEEP_ORPHANS'], kind: 'string', impact: 'Opt-in sweeping of orphaned isolate worktrees' },
|
|
160
|
-
{ plane: 'env', key: 'PIER_FOCUS_POLL_MS', kind: 'number', defaultValue: 1500, min: 0, impact: 'Pane-focus sampling cadence for the workbench heat layout (0 disables). Default 1500ms on Herdr <0.9.1; 0 (event-first) on 0.9.1+' },
|
|
161
|
-
{ plane: 'env', key: 'PIER_TERMINAL_PROMPT', aliases: ['PI_HERDR_TERMINAL_PROMPT'], kind: 'string', impact: 'Terminal readiness prompt strategy: bash|zsh|powershell|pwsh (default: $SHELL)' },
|
|
162
|
-
// B10: canonical names are `PIER_*`; the `PI_HERDR_*` spelling stays accepted (aliases).
|
|
163
|
-
{ plane: 'env', key: 'PIER_TODO_GRACE_MS', aliases: ['PI_HERDR_TODO_GRACE_MS'], kind: 'number', defaultValue: 30000, min: 1, impact: 'Delay before the unfinished-todo reminder fires (0/NaN falls back to the default)' },
|
|
164
|
-
{ plane: 'env', key: 'PIER_TERM_IDLE_MS', aliases: ['PI_HERDR_TERM_IDLE_MS'], kind: 'number', defaultValue: 1800000, min: 1, impact: 'Terminal idle threshold before a nudge is due (0/NaN falls back to the default)' },
|
|
165
|
-
{ plane: 'env', key: 'PIER_TERM_GRACE_MS', aliases: ['PI_HERDR_TERM_GRACE_MS'], kind: 'number', defaultValue: 30000, min: 1, impact: 'Extra grace after terminal idle before reading (0/NaN falls back to the default)' },
|
|
166
|
-
{ plane: 'env', key: 'PIER_TERM_READ_MAX', aliases: ['PI_HERDR_TERM_READ_MAX'], kind: 'number', defaultValue: 8000, min: 1, impact: 'Maximum terminal characters read per operation (0/NaN falls back to the default)' },
|
|
167
|
-
{ plane: 'env', key: 'PIER_TRACE', aliases: ['PI_HERDR_TRACE'], kind: 'string', impact: 'Write pier diagnostics to stderr (or to this file when it is a path)' },
|
|
168
|
-
{ plane: 'env', key: 'PIER_SLIM_FRAME', aliases: ['PI_HERDR_SLIM_FRAME'], kind: 'string', impact: 'Force the slim transcript frame on/off' },
|
|
169
|
-
{ plane: 'env', key: 'PIER_HMR', aliases: ['PI_HERDR_HMR'], kind: 'string', impact: 'Force hot-reload wiring on/off' },
|
|
170
|
-
];
|
|
171
|
-
|
|
172
|
-
export const CONFIG_KNOBS: readonly ConfigKnob[] = Object.freeze([
|
|
173
|
-
...EFFICIENCY_KNOBS,
|
|
174
|
-
...ROLE_KNOBS,
|
|
175
|
-
...PI_KNOBS,
|
|
176
|
-
...BOOT_KNOBS,
|
|
177
|
-
...ENV_KNOBS,
|
|
178
|
-
]);
|
|
179
|
-
|
|
180
|
-
export function catalogKeysForPlane(plane: ConfigPlaneId): string[] {
|
|
181
|
-
return CONFIG_KNOBS.filter((k) => k.plane === plane).map((k) => k.key);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
export function planeById(id: ConfigPlaneId): ConfigPlane | undefined {
|
|
185
|
-
return CONFIG_PLANES.find((p) => p.id === id);
|
|
186
|
-
}
|
|
111
|
+
/** Runtime policy / behaviour env knobs, derived from the `PIER_OPTIONS` registry. */
|
|
112
|
+
const ENV_KNOBS: readonly ConfigKnob[] = PIER_OPTIONS.map((option) => ({
|
|
113
|
+
plane: 'env' as const,
|
|
114
|
+
key: option.name,
|
|
115
|
+
kind: option.min === undefined ? ('string' as const) : ('number' as const),
|
|
116
|
+
defaultValue: option.min === undefined
|
|
117
|
+
? (option.fallback === '' ? undefined : option.fallback)
|
|
118
|
+
: Number(option.fallback),
|
|
119
|
+
aliases: option.legacy ? [option.legacy] : undefined,
|
|
120
|
+
min: option.min,
|
|
121
|
+
impact: option.description,
|
|
122
|
+
}));
|
|
123
|
+
|
|
124
|
+
/** Every knob the catalog can resolve (roles is a per-file plane: its schema is the reference). */
|
|
125
|
+
export const CONFIG_KNOBS: readonly ConfigKnob[] = Object.freeze([...EFFICIENCY_KNOBS, ...PI_KNOBS, ...ENV_KNOBS]);
|
|
187
126
|
|
|
188
|
-
/** Dotted-path getter for parsed JSON layers. */
|
|
189
127
|
export function readDotted(obj: unknown, dotted: string): unknown {
|
|
190
|
-
if (!obj || typeof obj !== 'object') return undefined;
|
|
191
128
|
let cursor: unknown = obj;
|
|
192
129
|
for (const part of dotted.split('.')) {
|
|
193
130
|
if (!cursor || typeof cursor !== 'object' || Array.isArray(cursor)) return undefined;
|
|
@@ -196,17 +133,10 @@ export function readDotted(obj: unknown, dotted: string): unknown {
|
|
|
196
133
|
return cursor;
|
|
197
134
|
}
|
|
198
135
|
|
|
199
|
-
/**
|
|
200
|
-
*
|
|
201
|
-
* that legitimate keys like `compaction.keepRecentTokens` are still shown verbatim.
|
|
202
|
-
*/
|
|
136
|
+
/** Secret-shaped KEY names (not values). Deliberately narrower than a bare `token` match so that
|
|
137
|
+
* legitimate keys like `compaction.keepRecentTokens` are still shown verbatim. */
|
|
203
138
|
const SECRET_LIKE = /(api[_-]?key|apikey|authorization|bearer|access[_-]?token|client[_-]?secret|password|credential)/i;
|
|
204
139
|
|
|
205
|
-
/** True when a value should never be echoed verbatim in command output. */
|
|
206
|
-
export function isSecretLike(key: string): boolean {
|
|
207
|
-
return SECRET_LIKE.test(key);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
140
|
export function formatValue(value: unknown): string {
|
|
211
141
|
if (value === undefined) return '(unset)';
|
|
212
142
|
if (typeof value === 'string') return value === '' ? '""' : value;
|
|
@@ -215,9 +145,8 @@ export function formatValue(value: unknown): string {
|
|
|
215
145
|
return JSON.stringify(value);
|
|
216
146
|
}
|
|
217
147
|
|
|
218
|
-
/** Renders a knob value, masking secret-looking keys. */
|
|
219
148
|
export function redactValue(key: string, value: unknown): string {
|
|
220
|
-
if (
|
|
149
|
+
if (SECRET_LIKE.test(key) && value !== undefined && value !== '') return '***';
|
|
221
150
|
return formatValue(value);
|
|
222
151
|
}
|
|
223
152
|
|
|
@@ -227,7 +156,6 @@ export interface ResolvedKnob {
|
|
|
227
156
|
readonly knob: ConfigKnob;
|
|
228
157
|
readonly value: string;
|
|
229
158
|
readonly source: ConfigSource;
|
|
230
|
-
/** Env var that actually supplied the value when it is an alias of `knob.key`. */
|
|
231
159
|
readonly via?: string;
|
|
232
160
|
/** Set when a workspace layer exists but was ignored (untrusted project). */
|
|
233
161
|
readonly note?: string;
|
|
@@ -243,52 +171,38 @@ export interface RawConfigLayers {
|
|
|
243
171
|
readonly piSettings?: { enabled?: boolean; keepRecentTokens?: number };
|
|
244
172
|
}
|
|
245
173
|
|
|
246
|
-
function resolveFileKnob(
|
|
247
|
-
knob:
|
|
248
|
-
|
|
249
|
-
): ResolvedKnob {
|
|
250
|
-
const env = layers.env ?? {};
|
|
251
|
-
const envRaw = knob.envVar ? env[knob.envVar] : undefined;
|
|
252
|
-
if (envRaw !== undefined && envRaw !== '') {
|
|
253
|
-
return { knob, value: redactValue(knob.key, envRaw), source: 'env' };
|
|
254
|
-
}
|
|
174
|
+
function resolveFileKnob(knob: ConfigKnob, layers: RawConfigLayers): ResolvedKnob {
|
|
175
|
+
const envRaw = knob.envVar ? (layers.env ?? {})[knob.envVar] : undefined;
|
|
176
|
+
if (envRaw !== undefined && envRaw !== '') return { knob, value: redactValue(knob.key, envRaw), source: 'env' };
|
|
255
177
|
|
|
256
178
|
const workspaceValue = readDotted(layers.workspace, knob.key);
|
|
257
179
|
const userValue = readDotted(layers.user, knob.key);
|
|
258
|
-
if (
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
}
|
|
264
|
-
return { knob, value: redactValue(knob.key, knob.defaultValue), source: 'default', note: 'workspace value ignored (untrusted project)' };
|
|
265
|
-
}
|
|
266
|
-
} else if (workspaceValue !== undefined) {
|
|
267
|
-
return { knob, value: redactValue(knob.key, workspaceValue), source: 'workspace' };
|
|
180
|
+
if (workspaceValue !== undefined) {
|
|
181
|
+
if (layers.workspaceTrusted) return { knob, value: redactValue(knob.key, workspaceValue), source: 'workspace' };
|
|
182
|
+
return userValue === undefined
|
|
183
|
+
? { knob, value: redactValue(knob.key, knob.defaultValue), source: 'default', note: 'workspace value ignored (untrusted project)' }
|
|
184
|
+
: { knob, value: redactValue(knob.key, userValue), source: 'user', note: 'workspace layer ignored (untrusted project)' };
|
|
268
185
|
}
|
|
269
|
-
|
|
270
186
|
if (userValue !== undefined) return { knob, value: redactValue(knob.key, userValue), source: 'user' };
|
|
271
187
|
return { knob, value: redactValue(knob.key, knob.defaultValue), source: 'default' };
|
|
272
188
|
}
|
|
273
189
|
|
|
274
|
-
/** Resolves efficiency + pi knobs from parsed layers. */
|
|
275
190
|
export function resolveConfigKnobs(layers: RawConfigLayers): ResolvedKnob[] {
|
|
276
|
-
const out
|
|
277
|
-
for (const knob of EFFICIENCY_KNOBS) out.push(resolveFileKnob(knob, layers));
|
|
191
|
+
const out = EFFICIENCY_KNOBS.map((knob) => resolveFileKnob(knob, layers));
|
|
278
192
|
|
|
279
193
|
// Effective-value rule from loadEfficiencyConfigFromDisk: pi's compaction.enabled=false
|
|
280
194
|
// disables OCC unless PI_HERDR_COMPACT_ENABLE forces it on.
|
|
281
195
|
const occ = out.find((e) => e.knob.key === 'onlineContextCompact.enabled');
|
|
282
196
|
const envForced = (layers.env?.PI_HERDR_COMPACT_ENABLE ?? '') !== '';
|
|
283
197
|
if (occ && occ.value === 'true' && layers.piSettings?.enabled === false && !envForced) {
|
|
284
|
-
|
|
285
|
-
out[index] = { ...occ, value: 'false', note: 'pi compaction.enabled=false disables OCC (set PI_HERDR_COMPACT_ENABLE=1 to force)' };
|
|
198
|
+
out[out.indexOf(occ)] = { ...occ, value: 'false', note: 'pi compaction.enabled=false disables OCC (set PI_HERDR_COMPACT_ENABLE=1 to force)' };
|
|
286
199
|
}
|
|
287
200
|
|
|
288
201
|
for (const knob of PI_KNOBS) {
|
|
289
202
|
const raw = knob.key === 'compaction.enabled' ? layers.piSettings?.enabled : layers.piSettings?.keepRecentTokens;
|
|
290
|
-
|
|
291
|
-
|
|
203
|
+
out.push(raw === undefined
|
|
204
|
+
? { knob, value: formatValue(undefined), source: 'pi', note: 'not set in pi settings' }
|
|
205
|
+
: { knob, value: redactValue(knob.key, raw), source: 'pi' });
|
|
292
206
|
}
|
|
293
207
|
return out;
|
|
294
208
|
}
|
|
@@ -300,33 +214,25 @@ export function resolveEnvKnobs(env: Record<string, string | undefined> = {}): R
|
|
|
300
214
|
for (const name of [knob.key, ...(knob.aliases ?? [])]) {
|
|
301
215
|
const raw = env[name];
|
|
302
216
|
if (raw === undefined || raw === '') continue;
|
|
303
|
-
return {
|
|
304
|
-
knob,
|
|
305
|
-
value: redactValue(knob.key, raw),
|
|
306
|
-
source: 'env' as const,
|
|
307
|
-
...(name === knob.key ? {} : { via: name }),
|
|
308
|
-
};
|
|
217
|
+
return { knob, value: redactValue(knob.key, raw), source: 'env' as const, ...(name === knob.key ? {} : { via: name }) };
|
|
309
218
|
}
|
|
310
219
|
return { knob, value: redactValue(knob.key, knob.defaultValue), source: 'default' as const };
|
|
311
220
|
});
|
|
312
221
|
}
|
|
313
222
|
|
|
314
|
-
/* ── checks ─────────────────────────────────────────────────────────────── */
|
|
315
|
-
|
|
316
223
|
export interface CheckReport {
|
|
317
224
|
readonly plane: ConfigPlaneId;
|
|
318
225
|
readonly ok: boolean;
|
|
319
226
|
readonly issues: readonly string[];
|
|
320
227
|
}
|
|
321
228
|
|
|
322
|
-
/** Validates env knobs against catalog bounds (mirrors parseEnvInt's warn-and-default behaviour). */
|
|
323
229
|
export function checkEnvKnobs(env: Record<string, string | undefined> = {}): string[] {
|
|
324
230
|
const issues: string[] = [];
|
|
325
231
|
for (const knob of ENV_KNOBS) {
|
|
232
|
+
if (knob.kind !== 'number') continue;
|
|
326
233
|
for (const name of [knob.key, ...(knob.aliases ?? [])]) {
|
|
327
234
|
const raw = env[name];
|
|
328
235
|
if (raw === undefined || raw === '') continue;
|
|
329
|
-
if (knob.kind !== 'number') continue;
|
|
330
236
|
const parsed = Number.parseInt(raw, 10);
|
|
331
237
|
const min = knob.min ?? 0;
|
|
332
238
|
if (!Number.isFinite(parsed) || parsed < min) {
|
|
@@ -337,68 +243,49 @@ export function checkEnvKnobs(env: Record<string, string | undefined> = {}): str
|
|
|
337
243
|
return issues;
|
|
338
244
|
}
|
|
339
245
|
|
|
340
|
-
/* ── rendering ──────────────────────────────────────────────────────────── */
|
|
341
|
-
|
|
342
|
-
const SOURCE_TAG: Record<ConfigSource, string> = {
|
|
343
|
-
env: 'env',
|
|
344
|
-
workspace: 'workspace',
|
|
345
|
-
user: 'user',
|
|
346
|
-
default: 'default',
|
|
347
|
-
pi: 'pi',
|
|
348
|
-
};
|
|
349
|
-
|
|
350
246
|
function sourceLabel(entry: ResolvedKnob): string {
|
|
351
|
-
const base = entry.via ? `${
|
|
247
|
+
const base = entry.via ? `${entry.source} via ${entry.via}` : entry.source;
|
|
352
248
|
return entry.note ? `${base} (${entry.note})` : base;
|
|
353
249
|
}
|
|
354
250
|
|
|
355
|
-
/** Short non-default summary of one plane: "3 changed (env 1 / workspace 1 / user 1)". */
|
|
356
251
|
export function summarizePlane(entries: readonly ResolvedKnob[]): string {
|
|
357
252
|
const changed = entries.filter((e) => e.source !== 'default');
|
|
358
253
|
if (changed.length === 0) return 'all defaults';
|
|
359
254
|
const bySource = new Map<ConfigSource, number>();
|
|
360
255
|
for (const e of changed) bySource.set(e.source, (bySource.get(e.source) ?? 0) + 1);
|
|
361
|
-
|
|
362
|
-
return `${changed.length} set (${parts.join(', ')})`;
|
|
256
|
+
return `${changed.length} set (${[...bySource].map(([s, n]) => `${s} ${n}`).join(', ')})`;
|
|
363
257
|
}
|
|
364
258
|
|
|
365
|
-
/** Index lines for `/pier-config` (kept short: one line per plane). */
|
|
366
259
|
export function renderIndex(lines: {
|
|
367
260
|
efficiency: readonly ResolvedKnob[];
|
|
368
261
|
pi: readonly ResolvedKnob[];
|
|
369
262
|
env: readonly ResolvedKnob[];
|
|
370
263
|
roleSummary: string;
|
|
371
|
-
bootSummary: string;
|
|
372
264
|
}): string[] {
|
|
373
|
-
const
|
|
374
|
-
lines.efficiency.
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
return out;
|
|
265
|
+
const mechanism = (prefix: string): string =>
|
|
266
|
+
lines.efficiency.some((e) => e.knob.key === `${prefix}.enabled` && e.value === 'true') ? 'on' : 'off';
|
|
267
|
+
return [
|
|
268
|
+
'pier config — 4 planes, env > workspace > user > default',
|
|
269
|
+
` efficiency ${summarizePlane(lines.efficiency)} — OCC ${mechanism('onlineContextCompact')} / OBS ${mechanism('observationPack')} / EPR ${mechanism('evidencePreservingReducer')}`,
|
|
270
|
+
` roles ${lines.roleSummary}`,
|
|
271
|
+
` pi ${summarizePlane(lines.pi)} — OCC reads compaction.* only`,
|
|
272
|
+
` env ${summarizePlane(lines.env)}`,
|
|
273
|
+
' show: /pier-config show <efficiency|roles|pi|env|all> check: /pier-config check report: /pier-config doc',
|
|
274
|
+
];
|
|
384
275
|
}
|
|
385
276
|
|
|
386
|
-
|
|
387
|
-
export function renderPlane(plane: ConfigPlaneId, entries: readonly ResolvedKnob[]): string[] {
|
|
277
|
+
export function renderPlane(entries: readonly ResolvedKnob[]): string[] {
|
|
388
278
|
const out: string[] = [];
|
|
389
279
|
for (const entry of entries) {
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
out.push(`
|
|
395
|
-
if (entry.knob.impact) out.push(` ${entry.knob.impact}`);
|
|
280
|
+
const { knob } = entry;
|
|
281
|
+
const from = knob.envVar && entry.source === 'env' ? ` (from ${knob.envVar})` : '';
|
|
282
|
+
const flags = [knob.readOnly ? 'read-only' : '', knob.docRef ?? ''].filter(Boolean).join('; ');
|
|
283
|
+
out.push(` ${knob.key}${from} = ${entry.value} [${sourceLabel(entry)}]${flags ? ` (${flags})` : ''}`);
|
|
284
|
+
out.push(` ${knob.impact}`);
|
|
396
285
|
}
|
|
397
|
-
void plane;
|
|
398
286
|
return out;
|
|
399
287
|
}
|
|
400
288
|
|
|
401
|
-
/** Validated-issue lines for `check`. */
|
|
402
289
|
export function renderCheck(reports: readonly CheckReport[]): string[] {
|
|
403
290
|
const out: string[] = [];
|
|
404
291
|
for (const report of reports) {
|
|
@@ -415,36 +302,29 @@ export interface ReportMeta {
|
|
|
415
302
|
readonly piVersion?: string;
|
|
416
303
|
}
|
|
417
304
|
|
|
418
|
-
/** Machine-truth markdown report (`/pier-config doc`). */
|
|
419
305
|
export function renderReport(entries: readonly ResolvedKnob[], meta: ReportMeta): string {
|
|
420
|
-
const lines
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
306
|
+
const lines = [
|
|
307
|
+
'# pier config report',
|
|
308
|
+
'',
|
|
309
|
+
`- generated: ${meta.generatedAt}`,
|
|
310
|
+
`- cwd: ${meta.cwd}`,
|
|
311
|
+
`- workspace trusted: ${meta.workspaceTrusted}`,
|
|
312
|
+
...(meta.piVersion ? [`- pi: ${meta.piVersion}`] : []),
|
|
313
|
+
'',
|
|
314
|
+
'Precedence: env > workspace (trusted) > user > default.',
|
|
315
|
+
'',
|
|
316
|
+
];
|
|
317
|
+
const escape = (text: string): string => text.replace(/\|/g, '\\|');
|
|
430
318
|
for (const plane of CONFIG_PLANES) {
|
|
431
319
|
const planeEntries = entries.filter((e) => e.knob.plane === plane.id);
|
|
432
|
-
lines.push(`## ${plane.id} — ${plane.title}`);
|
|
433
|
-
lines.push('');
|
|
434
|
-
lines.push(`Files: ${plane.files.map((f) => `\`${f}\``).join(' , ')}`);
|
|
435
|
-
lines.push('');
|
|
436
|
-
lines.push(`> ${plane.editHint}`);
|
|
437
|
-
lines.push('');
|
|
320
|
+
lines.push(`## ${plane.id} — ${plane.title}`, '', `Files: ${plane.files.map((f) => `\`${f}\``).join(' , ')}`, '', `> ${plane.editHint}`, '');
|
|
438
321
|
if (planeEntries.length === 0) {
|
|
439
|
-
lines.push('(no value-carrying knobs; per-file plane)');
|
|
440
|
-
lines.push('');
|
|
322
|
+
lines.push('(no value-carrying knobs; per-file plane)', '');
|
|
441
323
|
continue;
|
|
442
324
|
}
|
|
443
|
-
lines.push('| key | value | source | impact |');
|
|
444
|
-
lines.push('|---|---|---|---|');
|
|
325
|
+
lines.push('| key | value | source | impact |', '|---|---|---|---|');
|
|
445
326
|
for (const entry of planeEntries) {
|
|
446
|
-
|
|
447
|
-
lines.push(`| \`${entry.knob.key}\` | ${value} | ${sourceLabel(entry)} | ${entry.knob.impact.replace(/\|/g, '\\|')} |`);
|
|
327
|
+
lines.push(`| \`${entry.knob.key}\` | ${escape(entry.value)} | ${sourceLabel(entry)} | ${escape(entry.knob.impact)} |`);
|
|
448
328
|
}
|
|
449
329
|
lines.push('');
|
|
450
330
|
}
|