devflow-kit 3.0.1 → 3.1.0
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/CHANGELOG.md +13 -0
- package/dist/agents/git.md +2 -2
- package/dist/cli/agents-view/index.js +1 -1
- package/dist/cli/agents-view/render.js +2 -2
- package/dist/cli/agents-view/state.js +2 -2
- package/dist/cli/agents-view/terminal.js +5 -5
- package/dist/cli/commands/agents.js +7 -6
- package/dist/cli/commands/ambient.js +1 -1
- package/dist/cli/commands/attribution-prompts.js +8 -8
- package/dist/cli/commands/capture.js +1 -1
- package/dist/cli/commands/compliance-prompts.js +8 -8
- package/dist/cli/commands/compliance.js +8 -7
- package/dist/cli/commands/flags.js +33 -31
- package/dist/cli/commands/hud.js +1 -1
- package/dist/cli/commands/init-seed.js +9 -9
- package/dist/cli/commands/init.js +34 -32
- package/dist/cli/commands/install-report.js +10 -10
- package/dist/cli/commands/learning.js +267 -129
- package/dist/cli/commands/memory.js +1 -1
- package/dist/cli/commands/proxy.js +23 -23
- package/dist/cli/commands/rules.js +6 -5
- package/dist/cli/commands/tracker-prompts.js +6 -6
- package/dist/cli/commands/tracker.js +9 -9
- package/dist/cli/commands/uninstall.js +20 -20
- package/dist/cli/flags-view/render.js +5 -5
- package/dist/cli/flags-view/state.js +9 -9
- package/dist/cli/flags-view/terminal.js +4 -4
- package/dist/cli/tui/cells.js +1 -1
- package/dist/cli/tui/terminal.js +6 -6
- package/dist/commands/dynamic-build.md +18 -4
- package/dist/commands/dynamic-plan.md +19 -5
- package/dist/commands/dynamic-profile.md +17 -3
- package/dist/commands/dynamic-tickets.md +18 -4
- package/dist/commands/release.md +15 -1
- package/dist/commands/research.md +1 -1
- package/dist/commands/resolve.md +8 -9
- package/dist/core/agent-frontmatter.js +3 -3
- package/dist/core/agent-models.js +6 -6
- package/dist/core/agent-state.js +2 -2
- package/dist/core/ansi.js +2 -2
- package/dist/core/cache.js +7 -8
- package/dist/core/codex-auth-inspect.js +4 -4
- package/dist/core/compliance-compose.js +3 -3
- package/dist/core/compliance.js +3 -4
- package/dist/core/evidence-policy.js +14 -13
- package/dist/core/external-models.js +1 -1
- package/dist/core/feature-config.js +3 -3
- package/dist/core/feature-switch.js +3 -3
- package/dist/core/flags.js +25 -25
- package/dist/core/fs-atomic.js +6 -7
- package/dist/core/learning-queue-cleanup.js +16 -80
- package/dist/core/learning-store.js +61 -0
- package/dist/core/manifest.js +5 -5
- package/dist/core/mds-variants.js +13 -13
- package/dist/core/model-discovery.js +8 -8
- package/dist/core/observations.js +17 -101
- package/dist/core/orphan-sweep.js +4 -4
- package/dist/core/plugins.js +4 -5
- package/dist/core/project-paths.js +9 -13
- package/dist/core/proxy-log.js +8 -8
- package/dist/core/proxy-state.js +3 -3
- package/dist/core/reference-sweep.js +6 -6
- package/dist/core/teammate-mode-cleanup.js +1 -1
- package/dist/core/tracker.js +14 -14
- package/dist/hud/colors.js +2 -2
- package/dist/hud/components/learning-counts.js +2 -16
- package/dist/hud/components/version-badge.js +1 -1
- package/dist/skills/git/references/pr/resolve-review-threads.md +2 -2
- package/dist/targets/claude-code/compliance-install.js +17 -15
- package/dist/targets/claude-code/hooks.js +2 -2
- package/dist/targets/claude-code/installer.js +24 -24
- package/dist/targets/claude-code/legacy.js +1 -1
- package/dist/targets/claude-code/post-install.js +7 -7
- package/dist/targets/claude-code/tracker-install.js +2 -2
- package/package.json +1 -1
- package/src/assets/agents/code.md +1 -4
- package/src/assets/agents/design.md +2 -2
- package/src/assets/agents/diagnose.md +1 -1
- package/src/assets/agents/git.mds +2 -2
- package/src/assets/agents/knowledge.md +3 -3
- package/src/assets/agents/learning.md +281 -196
- package/src/assets/agents/research.md +1 -1
- package/src/assets/agents/review.md +3 -3
- package/src/assets/agents/scrutinize.md +1 -1
- package/src/assets/agents/skim.md +1 -1
- package/src/assets/agents/triage.md +9 -9
- package/src/assets/commands/_partials/_decisions.mds +8 -3
- package/src/assets/commands/_partials/_docs_root.mds +3 -3
- package/src/assets/commands/_partials/_engine.mds +1 -1
- package/src/assets/commands/_partials/_preamble.mds +6 -2
- package/src/assets/commands/_partials/_settings.mds +2 -2
- package/src/assets/commands/dynamic-build.mds +1 -1
- package/src/assets/commands/dynamic-plan.mds +3 -3
- package/src/assets/commands/dynamic-profile.mds +1 -1
- package/src/assets/commands/dynamic-tickets.mds +2 -2
- package/src/assets/commands/release.md +15 -1
- package/src/assets/commands/research.mds +1 -1
- package/src/assets/commands/resolve.mds +8 -9
- package/src/assets/mds/git/_pr.mds +3 -3
- package/src/assets/mds/tracker/_common.mds +1 -1
- package/src/assets/mds/tracker/_github.mds +1 -1
- package/src/assets/mds/tracker/_jira.mds +1 -1
- package/src/assets/mds/tracker/_linear.mds +1 -1
- package/src/assets/mds/tracker/_mcp.mds +6 -5
- package/src/assets/scripts/hooks/assets/orchestrator-charter.md +1 -0
- package/src/assets/scripts/hooks/background-memory-update +28 -22
- package/src/assets/scripts/hooks/capture-turn +1 -17
- package/src/assets/scripts/hooks/ensure-devflow-init +1 -1
- package/src/assets/scripts/hooks/ensure-proxy +5 -6
- package/src/assets/scripts/hooks/ensure-root-gitignore +1 -1
- package/src/assets/scripts/hooks/is-hex-sha +1 -1
- package/src/assets/scripts/hooks/json-helper.cjs +348 -814
- package/src/assets/scripts/hooks/json-parse +3 -2
- package/src/assets/scripts/hooks/lib/decisions-format.cjs +205 -156
- package/src/assets/scripts/hooks/lib/learning-store.cjs +3102 -0
- package/src/assets/scripts/hooks/lib/mkdir-lock.cjs +7 -5
- package/src/assets/scripts/hooks/lib/project-paths.cjs +13 -19
- package/src/assets/scripts/hooks/lib/render-decisions.cjs +253 -226
- package/src/assets/scripts/hooks/queue-append +2 -2
- package/src/assets/scripts/hooks/resolve-project-root +3 -4
- package/src/assets/scripts/hooks/session-start-context +40 -18
- package/src/assets/scripts/lib/project-config.cjs +2 -2
- package/src/assets/scripts/pr-evidence.cjs +3 -3
- package/src/assets/scripts/redact-secrets.cjs +20 -20
- package/src/assets/scripts/release-trace.cjs +1 -1
- package/src/assets/scripts/resolve-evidence-policy.cjs +3 -3
- package/src/assets/scripts/resolve-settings.cjs +3 -3
- package/src/assets/scripts/verify-evidence.cjs +2 -2
- package/src/assets/skills/apply-decisions/SKILL.md +37 -17
- package/src/assets/skills/docs-framework/SKILL.md +2 -2
- package/src/assets/skills/feature-knowledge/SKILL.md +6 -5
- package/dist/core/observation-io.js +0 -50
- package/src/assets/scripts/hooks/decisions-usage-scan.cjs +0 -131
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
* devflow proxy — Enable, disable, and check status of external model routing
|
|
3
3
|
* (GPT models via your OpenAI/Codex subscription).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* CLI-layer module; all core logic lives in src/core/proxy-state.ts
|
|
6
6
|
* and src/core/agent-models.ts.
|
|
7
|
-
*
|
|
8
|
-
* from the async action handler for all early-exit paths.
|
|
9
|
-
*
|
|
7
|
+
* Never process.exit() inside a finally-guarded scope (it would skip the finally);
|
|
8
|
+
* use return from the async action handler for all early-exit paths.
|
|
9
|
+
* Hook output strings use fixed templates; port number interpolation
|
|
10
10
|
* is acceptable (digit-validated integer, not user-controlled content).
|
|
11
11
|
*
|
|
12
12
|
* Branding note: "subswitch" must NEVER appear in user-visible strings. Internal
|
|
@@ -117,7 +117,7 @@ const UNKNOWN_MODEL_WINDOW_ENV = 'CLAUDE_CODE_DISABLE_UNKNOWN_MODEL_WINDOW_ENFOR
|
|
|
117
117
|
* Mutate a parsed Settings object in place: set ANTHROPIC_BASE_URL to our relay
|
|
118
118
|
* and set UNKNOWN_MODEL_WINDOW_ENV to '1'.
|
|
119
119
|
*
|
|
120
|
-
* D-P4-1: Each condition is evaluated independently (
|
|
120
|
+
* D-P4-1: Each condition is evaluated independently (no short-circuit that
|
|
121
121
|
* skips the second write when the first reports no change).
|
|
122
122
|
*
|
|
123
123
|
* Returns true when the object was changed by either assignment.
|
|
@@ -127,7 +127,7 @@ function _applyProxyEnvToObject(settings, port) {
|
|
|
127
127
|
s.env = s.env ?? {};
|
|
128
128
|
const env = s.env;
|
|
129
129
|
const newUrl = proxyBaseUrl(port);
|
|
130
|
-
// D-P4-1: evaluate each condition independently before combining (
|
|
130
|
+
// D-P4-1: evaluate each condition independently before combining (no short-circuit)
|
|
131
131
|
const urlChanged = env.ANTHROPIC_BASE_URL !== newUrl;
|
|
132
132
|
const windowVarChanged = env[UNKNOWN_MODEL_WINDOW_ENV] !== '1';
|
|
133
133
|
env.ANTHROPIC_BASE_URL = newUrl;
|
|
@@ -149,8 +149,8 @@ function _applyProxyEnvToObject(settings, port) {
|
|
|
149
149
|
* - uninstall → proxy.json.port (or DEFAULT_PROXY_PORT)
|
|
150
150
|
*
|
|
151
151
|
* D-P4-1: URL ownership gates the URL delete only; the window var is always ours to
|
|
152
|
-
* remove
|
|
153
|
-
* afterwards — never short-circuit composed inline
|
|
152
|
+
* remove. Each outcome is evaluated into a local and OR-ed
|
|
153
|
+
* afterwards — never short-circuit composed inline.
|
|
154
154
|
*
|
|
155
155
|
* Returns true when the object was changed by either deletion.
|
|
156
156
|
*/
|
|
@@ -163,7 +163,7 @@ function _stripProxyEnvFromObject(settings, managedPort) {
|
|
|
163
163
|
// managed the proxy — `proxyJsonExists()` — and the enable caller is taking ownership
|
|
164
164
|
// of the key anyway, so reaching this line means the value is Devflow's to remove.
|
|
165
165
|
// Removal is therefore unconditional: unlike ANTHROPIC_BASE_URL (port-scoped below),
|
|
166
|
-
// this key has no foreign value to protect.
|
|
166
|
+
// this key has no foreign value to protect.
|
|
167
167
|
const hadWindowVar = env[UNKNOWN_MODEL_WINDOW_ENV] !== undefined;
|
|
168
168
|
delete env[UNKNOWN_MODEL_WINDOW_ENV];
|
|
169
169
|
let removedUrl = false;
|
|
@@ -174,7 +174,7 @@ function _stripProxyEnvFromObject(settings, managedPort) {
|
|
|
174
174
|
}
|
|
175
175
|
if (Object.keys(env).length === 0)
|
|
176
176
|
delete s.env;
|
|
177
|
-
return removedUrl || hadWindowVar; // OR the locals — never compose with || inline
|
|
177
|
+
return removedUrl || hadWindowVar; // OR the locals — never compose with || inline
|
|
178
178
|
}
|
|
179
179
|
// ─── Pure env functions (exported for testing and cross-module reuse) ─────────
|
|
180
180
|
/**
|
|
@@ -234,7 +234,7 @@ export function readProxyEnvState(settingsJson, port) {
|
|
|
234
234
|
*/
|
|
235
235
|
export function addProxyHooks(settings, devflowDir) {
|
|
236
236
|
const command = runHookCommand(devflowDir, PROXY_HOOK_MARKER);
|
|
237
|
-
// Evaluate each event into its own local — never short-circuit
|
|
237
|
+
// Evaluate each event into its own local — never short-circuit.
|
|
238
238
|
const [addedSession, addedPrompt] = PROXY_HOOK_EVENTS.map((event) => ensureHook(settings, event, isProxyHook, { hooks: [{ type: 'command', command, timeout: 15 }] }));
|
|
239
239
|
return addedSession || addedPrompt;
|
|
240
240
|
}
|
|
@@ -246,7 +246,7 @@ export function addProxyHooks(settings, devflowDir) {
|
|
|
246
246
|
* Mutates settings in place. Returns true when any hook was removed.
|
|
247
247
|
*/
|
|
248
248
|
export function removeProxyHooks(settings) {
|
|
249
|
-
// Evaluate each event into its own local — never short-circuit
|
|
249
|
+
// Evaluate each event into its own local — never short-circuit.
|
|
250
250
|
const [removedSession, removedPrompt] = PROXY_HOOK_EVENTS.map((event) => removeHooks(settings, event, isProxyHook));
|
|
251
251
|
return removedSession || removedPrompt;
|
|
252
252
|
}
|
|
@@ -503,7 +503,7 @@ export async function realHttpGet(url, timeoutMs) {
|
|
|
503
503
|
/** Production doctor subprocess implementation. */
|
|
504
504
|
async function realSpawnDoctor(binPath, env, timeoutMs, logFile) {
|
|
505
505
|
// openProxyLog: 0700 parent dir + 0600 file creation + best-effort chmod for
|
|
506
|
-
// pre-existing wider modes (SEC-2). Non-fatal on chmod failure
|
|
506
|
+
// pre-existing wider modes (SEC-2). Non-fatal on chmod failure.
|
|
507
507
|
const logFd = await openProxyLog(logFile);
|
|
508
508
|
try {
|
|
509
509
|
return await new Promise((resolve) => {
|
|
@@ -558,7 +558,7 @@ async function realSpawnDoctor(binPath, env, timeoutMs, logFile) {
|
|
|
558
558
|
* Centralises the three private implementations so both runEnable and init.ts
|
|
559
559
|
* can consume them without byte-identical inline copies.
|
|
560
560
|
*
|
|
561
|
-
*
|
|
561
|
+
* Pure configuration factory; I/O implementations factored once.
|
|
562
562
|
*/
|
|
563
563
|
export function buildRealPreflightDeps(opts) {
|
|
564
564
|
const { settingsPath, onWarn, swallowSettingsReadError = false } = opts;
|
|
@@ -599,7 +599,7 @@ export function buildRealPreflightDeps(opts) {
|
|
|
599
599
|
* - OS-level spawn error (EMFILE, ENOMEM, EAGAIN) — always handled via the
|
|
600
600
|
* injected onError callback, never an uncaught exception
|
|
601
601
|
*
|
|
602
|
-
*
|
|
602
|
+
* No process.exit() — returns Result; caller decides error handling.
|
|
603
603
|
*/
|
|
604
604
|
export async function spawnRelayAndWaitForPort(port, binPath, configPath, logPath, pidPath, adopted, deps) {
|
|
605
605
|
if (adopted) {
|
|
@@ -784,7 +784,7 @@ async function applyEnableSettingsPass(settingsPath, devflowDir, port) {
|
|
|
784
784
|
* -n no host-name resolution -P no port-name resolution
|
|
785
785
|
* -sTCP:LISTEN only LISTEN-state sockets -t PIDs only
|
|
786
786
|
*
|
|
787
|
-
*
|
|
787
|
+
* Fixed argument array — port is validated as a safe integer
|
|
788
788
|
* by the caller before this function is invoked; it is never interpolated
|
|
789
789
|
* into a shell string.
|
|
790
790
|
*
|
|
@@ -855,8 +855,8 @@ async function readPidFile(pidPath) {
|
|
|
855
855
|
*
|
|
856
856
|
* applies D-EFR-5: signal 0 and health check are independent facts; lsof
|
|
857
857
|
* binds the pid-file PID to the port owner before any signal is sent.
|
|
858
|
-
*
|
|
859
|
-
*
|
|
858
|
+
* All sub-operations are non-fatal; a kill failure never aborts disable.
|
|
859
|
+
* Never process.exit() inside a finally-guarded scope (it would skip the finally).
|
|
860
860
|
*
|
|
861
861
|
* @param deps Optional injectable dependencies (default: real lsof implementation).
|
|
862
862
|
* Provide a stub in tests to exercise every branch without a real binary.
|
|
@@ -1211,7 +1211,7 @@ async function runStatus() {
|
|
|
1211
1211
|
}
|
|
1212
1212
|
}
|
|
1213
1213
|
// External models registry (cache-only, zero spawns — avoids multi-second silent pause in --status)
|
|
1214
|
-
const cacheDir = modelCacheDir(devflowDir); // authoritative path from cache.ts
|
|
1214
|
+
const cacheDir = modelCacheDir(devflowDir); // authoritative path from cache.ts
|
|
1215
1215
|
const catalog = getExternalModelsCached(cacheDir);
|
|
1216
1216
|
p.log.info(formatExternalModelsLine(catalog, logPath));
|
|
1217
1217
|
// Log path
|
|
@@ -1236,7 +1236,7 @@ async function runEnable(portOption) {
|
|
|
1236
1236
|
const configPath = path.join(devflowDir, 'proxy-routing.json');
|
|
1237
1237
|
const logPath = path.join(devflowDir, 'logs', 'proxy.log');
|
|
1238
1238
|
const pidPath = path.join(devflowDir, 'proxy.pid');
|
|
1239
|
-
const cacheDir = modelCacheDir(devflowDir); // authoritative path from cache.ts
|
|
1239
|
+
const cacheDir = modelCacheDir(devflowDir); // authoritative path from cache.ts
|
|
1240
1240
|
// Step 1: Read prior proxy.json (remembered port); --port flag overrides
|
|
1241
1241
|
const priorStateResult = await readProxyState(devflowDir);
|
|
1242
1242
|
const priorPort = priorStateResult.ok ? priorStateResult.value.port : DEFAULT_PROXY_PORT;
|
|
@@ -1260,7 +1260,7 @@ async function runEnable(portOption) {
|
|
|
1260
1260
|
await rotateProxyLogIfLarge(logPath);
|
|
1261
1261
|
// Read existing routing config to preserve user-added anthropic/limits/logLevel/providers
|
|
1262
1262
|
// blocks. A missing or malformed file falls back to clean defaults inside
|
|
1263
|
-
// buildRoutingConfigJson (non-fatal
|
|
1263
|
+
// buildRoutingConfigJson (non-fatal).
|
|
1264
1264
|
let existingRoutingContent;
|
|
1265
1265
|
try {
|
|
1266
1266
|
existingRoutingContent = await fs.readFile(configPath, 'utf-8');
|
|
@@ -1389,7 +1389,7 @@ async function runEnable(portOption) {
|
|
|
1389
1389
|
s.message('Warming model cache...');
|
|
1390
1390
|
await discoverExternalModels(cacheDir, logPath).catch(() => { });
|
|
1391
1391
|
s.stop(color.green('External model routing enabled'));
|
|
1392
|
-
// D-P4-1
|
|
1392
|
+
// D-P4-1: applies-on-restart — env var takes effect only for new sessions
|
|
1393
1393
|
p.log.info(color.dim('Context-window enforcement disabled for relay-routed models — applies to new Claude Code sessions'));
|
|
1394
1394
|
if (adopted) {
|
|
1395
1395
|
p.log.info(`Relay already running on port ${port} — adopted`);
|
|
@@ -1476,7 +1476,7 @@ async function runDisable() {
|
|
|
1476
1476
|
p.log.success('External model routing disabled');
|
|
1477
1477
|
// Step 5: Terminate the relay process.
|
|
1478
1478
|
// Identity is confirmed via TCP + health before signalling — a recycled PID
|
|
1479
|
-
// must never be killed.
|
|
1479
|
+
// must never be killed. All sub-operations are non-fatal.
|
|
1480
1480
|
const spawnLockPath = path.join(devflowDir, '.proxy-spawn.lock');
|
|
1481
1481
|
const terminateResult = await terminateRelay(pidPath, spawnLockPath, managedPort);
|
|
1482
1482
|
switch (terminateResult) {
|
|
@@ -114,7 +114,7 @@ async function printRulesList(claudeDir, devflowDir) {
|
|
|
114
114
|
* Returns which tier seeded: 'installed' | 'source' | 'none'.
|
|
115
115
|
* Creates the shadow directory before copying.
|
|
116
116
|
*
|
|
117
|
-
* D35 — seedRuleShadow tiers
|
|
117
|
+
* D35 — seedRuleShadow tiers:
|
|
118
118
|
* Tier 1 — installed rule at rulesTarget/{name}.md (fastest path when rules are enabled).
|
|
119
119
|
* SKIPPED for FEATURE_OWNED_RULES: the installed file is already stamped
|
|
120
120
|
* (${DEVFLOW_COMPLIANCE_FRAMEWORKS} replaced with label text), so seeding from
|
|
@@ -198,9 +198,10 @@ async function handleRuleUnshadow(name, allRules, devflowDir) {
|
|
|
198
198
|
* Compliance convergence runs unconditionally — convergeComplianceArtifacts handles the
|
|
199
199
|
* disabled case by removing stale artifacts, so no enabled-gate is needed here. Running
|
|
200
200
|
* converge regardless of the rules-install outcome ensures that a rules-install failure
|
|
201
|
-
* cannot leave compliance artifacts in a half-converged state
|
|
201
|
+
* cannot leave compliance artifacts in a half-converged state.
|
|
202
202
|
*
|
|
203
|
-
*
|
|
203
|
+
* Warn-not-throw on per-item compliance convergence failure, so a convergence
|
|
204
|
+
* failure never undoes the rules install.
|
|
204
205
|
*/
|
|
205
206
|
export async function runRulesEnable(claudeDir, devflowDir) {
|
|
206
207
|
const rulesTarget = path.join(claudeDir, 'rules', 'devflow');
|
|
@@ -241,7 +242,7 @@ export async function runRulesEnable(claudeDir, devflowDir) {
|
|
|
241
242
|
// disabled case itself (removes stale artifacts). Running this regardless of installFailed
|
|
242
243
|
// prevents a rules-install failure from leaving the compliance rule in a stale state.
|
|
243
244
|
// rulesEnabledOverride reflects the actual settled state: false when install failed so the
|
|
244
|
-
// compliance rule is not installed into a wiped, rules-disabled directory
|
|
245
|
+
// compliance rule is not installed into a wiped, rules-disabled directory.
|
|
245
246
|
try {
|
|
246
247
|
await convergeFromManifest({
|
|
247
248
|
claudeDir,
|
|
@@ -252,7 +253,7 @@ export async function runRulesEnable(claudeDir, devflowDir) {
|
|
|
252
253
|
});
|
|
253
254
|
}
|
|
254
255
|
catch (err) {
|
|
255
|
-
//
|
|
256
|
+
// Warn-not-abort — compliance convergence failure does not undo rules install
|
|
256
257
|
p.log.warn(`Compliance rule convergence failed — rules installed but compliance rule may be stale: ${err instanceof Error ? err.message : String(err)}`);
|
|
257
258
|
}
|
|
258
259
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tracker prompt helpers for devflow init.
|
|
3
3
|
*
|
|
4
|
-
* CLI-layer module
|
|
4
|
+
* CLI-layer module: prompt-rendering logic lives in src/cli/commands/,
|
|
5
5
|
* core business logic stays in src/core/tracker.ts.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* The wizard gate keys on `modePromptShown` (was the Setup-mode
|
|
8
8
|
* p.select prompt actually shown?), never on the mode name, so --recommended
|
|
9
9
|
* (flag, no prompt) and the non-TTY fallback preserve their promptless contracts.
|
|
10
|
-
*
|
|
10
|
+
* runTrackerStep never calls process.exit() or throws — callers
|
|
11
11
|
* own the cancel idiom (p.cancel + process.exit(0)), keeping try/finally safe.
|
|
12
12
|
* The shared DI seam (PromptOutcome, WizardPromptIO, clackNote, clackSelect) is
|
|
13
13
|
* defined once in prompt-io.ts and imported here, never re-declared.
|
|
@@ -55,7 +55,7 @@ export function formatTrackerSummary(provider) {
|
|
|
55
55
|
/**
|
|
56
56
|
* Determines whether the tracker wizard step should run for a given init invocation.
|
|
57
57
|
*
|
|
58
|
-
* Gate table (
|
|
58
|
+
* Gate table (key on modePromptShown, never on the mode name):
|
|
59
59
|
*
|
|
60
60
|
* --recommended flag / !isTTY fallback → no (promptless contract preserved)
|
|
61
61
|
* Interactive mode-prompt → Recommended → yes (modePromptShown=true)
|
|
@@ -102,14 +102,14 @@ export function buildClackTrackerPrompts() {
|
|
|
102
102
|
* 1. Note — "Current setting: …" header then the provider catalogue.
|
|
103
103
|
* 2. Provider select — labelled GitHub / Jira / Linear with hints, seeded from
|
|
104
104
|
* the prior state. `p.select`, never `p.confirm`: Enter-through must be an
|
|
105
|
-
* INFORMED keep of a named provider, not a y/N reflex
|
|
105
|
+
* INFORMED keep of a named provider, not a y/N reflex.
|
|
106
106
|
*
|
|
107
107
|
* Returns:
|
|
108
108
|
* {kind:'resolved', state, messages} — step completed; `state` is the chosen
|
|
109
109
|
* TrackerFeatureState; `messages` are emitted by the caller.
|
|
110
110
|
* {kind:'cancelled'} — user pressed Escape; caller runs p.cancel + process.exit(0).
|
|
111
111
|
*
|
|
112
|
-
* Invariants
|
|
112
|
+
* Invariants:
|
|
113
113
|
* - Never calls process.exit(), never throws.
|
|
114
114
|
* - The returned state is always a fresh object, never the seed.
|
|
115
115
|
* - All I/O is routed through the `prompts` parameter (injectable for tests).
|
|
@@ -3,19 +3,19 @@
|
|
|
3
3
|
*
|
|
4
4
|
* D-TRACKER-PAIR [DR-25]: `src/core/tracker.ts` (domain) +
|
|
5
5
|
* `src/cli/commands/tracker.ts` (CLI) mirrors the `compliance.ts` pair
|
|
6
|
-
* exactly;
|
|
7
|
-
* exist. A reviewer meeting several `tracker*` files in one
|
|
8
|
-
* has no signal that the duplication is deliberate.
|
|
6
|
+
* exactly; the split of pure core logic (src/core/) from the I/O layer is the
|
|
7
|
+
* reason both names exist. A reviewer meeting several `tracker*` files in one
|
|
8
|
+
* commit otherwise has no signal that the duplication is deliberate.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
10
|
+
* CLI-layer module; the provider domain, the strict parser and
|
|
11
11
|
* the ~/.devflow file lifecycle all live in src/core/tracker.ts.
|
|
12
12
|
* The manifest holds the MACHINE default. A repository may select its own
|
|
13
13
|
* provider in its committed `.devflow/project.json` (resolved by
|
|
14
14
|
* resolve-settings.cjs); `--status` names it on an `Effective:` line when one
|
|
15
15
|
* does, and `--set` never touches it.
|
|
16
|
-
*
|
|
16
|
+
* --set converges the sentinel in BOTH directions, so flipping
|
|
17
17
|
* back to github removes what flipping away wrote.
|
|
18
|
-
*
|
|
18
|
+
* A failed re-arm or sentinel step warns, it never aborts.
|
|
19
19
|
*/
|
|
20
20
|
import { Command } from 'commander';
|
|
21
21
|
import { promises as fs } from 'fs';
|
|
@@ -31,7 +31,7 @@ import { prefixSkillName } from '../../core/plugins.js';
|
|
|
31
31
|
/**
|
|
32
32
|
* Read the provenance header of `~/.devflow/tracker/{provider}.md`.
|
|
33
33
|
*
|
|
34
|
-
* Never throws
|
|
34
|
+
* Never throws: an absent, unreadable, or non-regular path is `absent`.
|
|
35
35
|
* Only the bounded head of the file is read and only the leading frontmatter
|
|
36
36
|
* block is scanned — through the one frontmatter parser the migration also uses —
|
|
37
37
|
* and nothing read here is trusted: every value is rendered through
|
|
@@ -52,7 +52,7 @@ export async function readTrackerProvenance(devflowDir, provider) {
|
|
|
52
52
|
* every install carries them all (D-INSTALL-ALL-PROVIDERS) — rather than listing
|
|
53
53
|
* the directory: the question a user asks `--status` is "can the agent load what
|
|
54
54
|
* it is told to load?", and a stray file in the tree is not an answer to it.
|
|
55
|
-
* Never throws
|
|
55
|
+
* Never throws.
|
|
56
56
|
*/
|
|
57
57
|
export async function readTrackerMechanics(claudeDir) {
|
|
58
58
|
const root = path.join(claudeDir, 'skills', prefixSkillName(SKILL_REFS_SKILL_NAME), 'references');
|
|
@@ -227,7 +227,7 @@ export const trackerCommand = new Command('tracker')
|
|
|
227
227
|
// undocumented dotfile. Every other --status in this CLI is a pure read,
|
|
228
228
|
// so this one reports the write it makes as a line of the note below — a
|
|
229
229
|
// machine-state change the output does not mention is a change the user
|
|
230
|
-
// cannot audit. Non-fatal exactly as on the --set path
|
|
230
|
+
// cannot audit. Non-fatal exactly as on the --set path: a
|
|
231
231
|
// failed re-arm warns, it never aborts the report the user asked for.
|
|
232
232
|
const statusRearm = await rearmTrackerInference(devflowDir);
|
|
233
233
|
const inference = statusRearm.ok
|
|
@@ -178,7 +178,7 @@ export function formatDryRunPlan(assets) {
|
|
|
178
178
|
* - "prompt" — interactive (isTTY=true); caller should ask the user
|
|
179
179
|
*
|
|
180
180
|
* @D1 Non-interactive-preserve invariant: when isTTY is false the result is
|
|
181
|
-
* always "preserve", never "prompt" — avoids
|
|
181
|
+
* always "preserve", never "prompt" — avoids a half-applied-state hazard.
|
|
182
182
|
*/
|
|
183
183
|
export function resolveSecurityRemovalDecision(opts) {
|
|
184
184
|
if (!opts.anySecurityPresent || opts.keepDocs)
|
|
@@ -193,7 +193,7 @@ export function resolveSecurityRemovalDecision(opts) {
|
|
|
193
193
|
*
|
|
194
194
|
* A cancel (user presses Ctrl-C on this prompt) is treated as decline: the
|
|
195
195
|
* .devflow/ directory is preserved and the uninstall continues with the remaining
|
|
196
|
-
* steps rather than aborting via process.exit()
|
|
196
|
+
* steps rather than aborting via process.exit().
|
|
197
197
|
*
|
|
198
198
|
* PURE — no I/O, fully testable.
|
|
199
199
|
*
|
|
@@ -233,8 +233,8 @@ export function partitionProjectData(entries) {
|
|
|
233
233
|
* realpaths, so macOS's `/var` → `/private/var` and a symlinked HOME still match.
|
|
234
234
|
*
|
|
235
235
|
* A `.devflow` that is a symbolic link to anywhere else is skipped too, and never
|
|
236
|
-
* followed: its target is a directory devflow cannot prove it wrote
|
|
237
|
-
*
|
|
236
|
+
* followed: its target is a directory devflow cannot prove it wrote, so a
|
|
237
|
+
* confirmed cleanup must not empty it — nor unlink a link the user made.
|
|
238
238
|
*/
|
|
239
239
|
export async function resolveProjectDataPlan(opts) {
|
|
240
240
|
if (opts.gitRoot === null)
|
|
@@ -320,9 +320,9 @@ export function formatProjectDataPlan(plan) {
|
|
|
320
320
|
* @D5 Precondition guard: any anomalous devflowDir falls back to 'artifacts-only' rather
|
|
321
321
|
* than throwing — business logic must not throw (engineering rule). The explicit guard
|
|
322
322
|
* makes the invariant present in production code, not only in tests (reliability rule).
|
|
323
|
-
* @D6
|
|
323
|
+
* @D6 The caller must NOT process.exit() after a cancel/decline response;
|
|
324
324
|
* removeAllDevFlow has already run by the time the prompt fires, so removeDevFlowInstallArtifacts
|
|
325
|
-
* must execute on every non-confirm path to leave a clean end-state
|
|
325
|
+
* must execute on every non-confirm path to leave a clean end-state.
|
|
326
326
|
* @D7 keepDocs gate: when the caller passes --keep-docs, the entire ~/.devflow dir cleanup
|
|
327
327
|
* prompt is suppressed — artifacts-only regardless of isTTY or userContent. This prevents
|
|
328
328
|
* --keep-docs from triggering prompts about skill shadows or preference-profile.md.
|
|
@@ -517,7 +517,7 @@ export function installArtifactPaths(devflowDir) {
|
|
|
517
517
|
{ relPath: 'agent-models.json' },
|
|
518
518
|
// cost history — auto-generated session telemetry, not user-authored.
|
|
519
519
|
// The whole costs/ tree is removed so sessions/ and archive.jsonl cannot
|
|
520
|
-
// drift from their write sites in src/hud/cost-history.ts
|
|
520
|
+
// drift from their write sites in src/hud/cost-history.ts.
|
|
521
521
|
{ relPath: 'costs', isDir: true },
|
|
522
522
|
// proxy artifacts
|
|
523
523
|
{ relPath: 'proxy.json' },
|
|
@@ -545,7 +545,7 @@ export function installArtifactPaths(devflowDir) {
|
|
|
545
545
|
{ relPath: 'logs', isDir: true },
|
|
546
546
|
// Model-discovery + HUD component caches. hudCacheDir() is the authoritative
|
|
547
547
|
// accessor for the cache/ parent (modelCacheDir() resolves beneath it), so the
|
|
548
|
-
// removal site stays byte-locked to the write sites
|
|
548
|
+
// removal site stays byte-locked to the write sites.
|
|
549
549
|
{ relPath: path.relative(devflowDir, hudCacheDir(devflowDir)), isDir: true },
|
|
550
550
|
];
|
|
551
551
|
}
|
|
@@ -579,7 +579,7 @@ export async function removeDevFlowInstallArtifacts(devflowDir, verbose) {
|
|
|
579
579
|
catch (error) {
|
|
580
580
|
p.log.warn(`Could not remove manifest.json: ${error}`);
|
|
581
581
|
}
|
|
582
|
-
// Proxy install artifacts — remove non-fatally (per-item failure isolation
|
|
582
|
+
// Proxy install artifacts — remove non-fatally (per-item failure isolation)
|
|
583
583
|
// Inform the user if a proxy relay process is still running (never kill — informational only).
|
|
584
584
|
try {
|
|
585
585
|
const pidContent = await fs.readFile(path.join(devflowDir, 'proxy.pid'), 'utf-8');
|
|
@@ -594,7 +594,7 @@ export async function removeDevFlowInstallArtifacts(devflowDir, verbose) {
|
|
|
594
594
|
}
|
|
595
595
|
}
|
|
596
596
|
catch { /* proxy.pid absent or unreadable — non-fatal */ }
|
|
597
|
-
// All install artifacts removed non-fatally
|
|
597
|
+
// All install artifacts removed non-fatally. Resolved against
|
|
598
598
|
// disk first, so a per-run staging basename is a real path by the time the
|
|
599
599
|
// containment guard below sees it.
|
|
600
600
|
for (const artifact of await resolveInstallArtifactPaths(devflowDir)) {
|
|
@@ -654,7 +654,7 @@ export async function isDevFlowInstalled(claudeDir) {
|
|
|
654
654
|
* scope directories, intersected with what actually exists on disk.
|
|
655
655
|
*
|
|
656
656
|
* Extracted from the dry-run loop body so the enumeration logic is
|
|
657
|
-
* independently testable (
|
|
657
|
+
* independently testable (tests must exercise the production
|
|
658
658
|
* path, not only the pure helper functions).
|
|
659
659
|
*
|
|
660
660
|
* Coverage matches removeAllDevFlow exactly:
|
|
@@ -664,7 +664,7 @@ export async function isDevFlowInstalled(claudeDir) {
|
|
|
664
664
|
* - Prefixed (devflow:name): live registry ∪ LEGACY_SKILL_NAMES.
|
|
665
665
|
* - Bare (name or devflow-name legacy): LEGACY_SKILL_NAMES ONLY.
|
|
666
666
|
* ~/.claude/skills/ is shared; bare dirs for live-registry skill names
|
|
667
|
-
* are by construction foreign to Devflow
|
|
667
|
+
* are by construction foreign to Devflow.
|
|
668
668
|
* 3. manifest.json (removed separately in removeDevFlowInstallArtifacts).
|
|
669
669
|
* 4. Every artifact path from installArtifactPaths(devflowDir) — the single
|
|
670
670
|
* source of truth shared with the real removal loop.
|
|
@@ -688,7 +688,7 @@ export async function enumerateDryRunExtras(claudeDir, devflowDir) {
|
|
|
688
688
|
catch { /* absent */ }
|
|
689
689
|
}
|
|
690
690
|
// 2. Skills: enumerate ALL removal candidates that exist on disk.
|
|
691
|
-
// Mirrors removeAllDevFlow's split-pass approach
|
|
691
|
+
// Mirrors removeAllDevFlow's split-pass approach:
|
|
692
692
|
// Prefixed: live registry ∪ LEGACY_SKILL_NAMES ∪ FEATURE_OWNED_SKILLS
|
|
693
693
|
// Bare: LEGACY_SKILL_NAMES only — shared skills/ dir; live-registry bare
|
|
694
694
|
// dirs are by construction foreign to Devflow.
|
|
@@ -923,7 +923,7 @@ export async function runFullPhaseForScope(opts) {
|
|
|
923
923
|
});
|
|
924
924
|
if (p.isCancel(confirmFullCleanup)) {
|
|
925
925
|
// removeAllDevFlow already ran — clean up the manifest to leave a consistent
|
|
926
|
-
// state.
|
|
926
|
+
// state. process.exit() here would skip removeDevFlowInstallArtifacts,
|
|
927
927
|
// leaving a stale manifest.json that points to assets no longer on disk.
|
|
928
928
|
await removeDevFlowInstallArtifacts(devflowDir, verbose);
|
|
929
929
|
p.log.info(`${devflowDir} preserved (full removal cancelled; removing scripts and install artifacts only)`);
|
|
@@ -945,7 +945,7 @@ export async function runFullPhaseForScope(opts) {
|
|
|
945
945
|
*
|
|
946
946
|
* D-UNINSTALL-CARVE-OUT: DEVFLOW_TRACKED_PATHS are never removed. `--keep-docs`,
|
|
947
947
|
* a non-interactive run, a decline and a cancel all leave the directory untouched,
|
|
948
|
-
* and a cancel continues the uninstall rather than exiting
|
|
948
|
+
* and a cancel continues the uninstall rather than exiting. The
|
|
949
949
|
* directory itself is removed only when nothing tracked was in it.
|
|
950
950
|
*/
|
|
951
951
|
async function runProjectDataStep(plan, gates) {
|
|
@@ -1412,7 +1412,7 @@ export async function removeAllDevFlow(claudeDir, devflowScriptsDir, verbose) {
|
|
|
1412
1412
|
}
|
|
1413
1413
|
// Remove Devflow skill directories.
|
|
1414
1414
|
// ~/.claude/skills/ is shared with other tools; the two passes use different
|
|
1415
|
-
// name sets to avoid deleting foreign dirs
|
|
1415
|
+
// name sets to avoid deleting foreign dirs:
|
|
1416
1416
|
//
|
|
1417
1417
|
// Prefixed pass (devflow:name): live registry ∪ LEGACY_SKILL_NAMES ∪ FEATURE_OWNED_SKILLS.
|
|
1418
1418
|
// prefixSkillName() is idempotent for already-prefixed legacy entries.
|
|
@@ -1421,7 +1421,7 @@ export async function removeAllDevFlow(claudeDir, devflowScriptsDir, verbose) {
|
|
|
1421
1421
|
// Bare pass (name or devflow-name legacy): LEGACY_SKILL_NAMES ONLY.
|
|
1422
1422
|
// A bare dir whose name matches a live-registry skill is by construction
|
|
1423
1423
|
// foreign to Devflow (the devflow: namespace shipped in dcecda3, 2026-03-30).
|
|
1424
|
-
// A bare 'compliance/' dir is also foreign (not in LEGACY_SKILL_NAMES)
|
|
1424
|
+
// A bare 'compliance/' dir is also foreign (not in LEGACY_SKILL_NAMES).
|
|
1425
1425
|
const prefixedSkillNames = new Set([...getAllSkillNames(), ...LEGACY_SKILL_NAMES, ...FEATURE_OWNED_SKILLS]);
|
|
1426
1426
|
const skillsDir = path.join(claudeDir, 'skills');
|
|
1427
1427
|
let skillsRemoved = 0;
|
|
@@ -1472,7 +1472,7 @@ export async function removeAllDevFlow(claudeDir, devflowScriptsDir, verbose) {
|
|
|
1472
1472
|
* cleaned up promptly rather than accumulating on disk.
|
|
1473
1473
|
*
|
|
1474
1474
|
* knownNames spans ALL plugins for each asset type so assets belonging to
|
|
1475
|
-
* plugins NOT being uninstalled are never swept
|
|
1475
|
+
* plugins NOT being uninstalled are never swept.
|
|
1476
1476
|
*
|
|
1477
1477
|
* Removals are announced only under `verbose`, but removal FAILURES always warn:
|
|
1478
1478
|
* a swept-but-not-actually-removed agent or command keeps loading in Claude Code,
|
|
@@ -1546,7 +1546,7 @@ export async function removeSelectedPlugins(claudeDir, plugins, verbose, install
|
|
|
1546
1546
|
// exclusively by the frozen LEGACY_SKILL_NAMES pass in removeAllDevFlow and
|
|
1547
1547
|
// in init.ts; live-registry skills never had bare installs (the devflow:
|
|
1548
1548
|
// namespace shipped in dcecda3, 2026-03-30), so a bare dir for a current
|
|
1549
|
-
// registry name is by construction foreign
|
|
1549
|
+
// registry name is by construction foreign.
|
|
1550
1550
|
try {
|
|
1551
1551
|
await fs.rm(path.join(skillsDir, prefixSkillName(skill)), { recursive: true, force: true });
|
|
1552
1552
|
}
|
|
@@ -1568,7 +1568,7 @@ export async function removeSelectedPlugins(claudeDir, plugins, verbose, install
|
|
|
1568
1568
|
// Registry-diff sweep: agents, commands, AND skills — named step (A6).
|
|
1569
1569
|
// Removes any orphaned files whose names left the registry (retired, renamed,
|
|
1570
1570
|
// or deleted from all plugins). Spans ALL plugins so assets belonging to
|
|
1571
|
-
// non-selected plugins are never swept
|
|
1571
|
+
// non-selected plugins are never swept.
|
|
1572
1572
|
await sweepDevflowNamespaces(claudeDir, verbose);
|
|
1573
1573
|
}
|
|
1574
1574
|
//# sourceMappingURL=uninstall.js.map
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pure TUI frame renderer for the devflow flags view.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* CLI-layer view module; zero fs/tty imports.
|
|
5
|
+
* Pure function, no process.exit(), no I/O.
|
|
6
6
|
*
|
|
7
7
|
* Layout (FIXED_ROWS = 10, viewport = state.viewportHeight — single owner):
|
|
8
8
|
* 1 Title " Devflow Flags"
|
|
@@ -59,14 +59,14 @@ export function computeViewportHeight(termRows) {
|
|
|
59
59
|
* non-boolean at devflow default → plain string
|
|
60
60
|
* non-boolean deviating from devflow default → bold string
|
|
61
61
|
*
|
|
62
|
-
* Colour vocabulary (one colour, one semantic
|
|
62
|
+
* Colour vocabulary (one colour, one semantic):
|
|
63
63
|
* cyan = focus indicator (chevron wrapper ‹ › on the cursor row only)
|
|
64
64
|
* yellow = dirty indicator (unconditional ●) and boolean 'off'
|
|
65
65
|
* green = boolean 'on'
|
|
66
66
|
* bold = non-boolean value deviating from devflow default
|
|
67
67
|
*
|
|
68
68
|
* disk-sourced values are routed through sanitizeCell to prevent TAB/LF
|
|
69
|
-
* layout breaks inside the fixed-width TUI cell
|
|
69
|
+
* layout breaks inside the fixed-width TUI cell.
|
|
70
70
|
*/
|
|
71
71
|
function formatValue(row) {
|
|
72
72
|
const v = row.configuredValue;
|
|
@@ -151,7 +151,7 @@ function renderRow(row, isCursor, isEditing, editBuffer, editCaret, labelW, valu
|
|
|
151
151
|
// so an inner RESET (e.g. from green('on')) does not kill the outer cyan.
|
|
152
152
|
// cyan('‹ ') + <styled-or-plain content> + cyan(' ›')
|
|
153
153
|
// rather than cyan(`‹ ${content} ›`), which terminates the outer cyan at the
|
|
154
|
-
// inner RESET, leaving the closing chevron unstyled
|
|
154
|
+
// inner RESET, leaving the closing chevron unstyled.
|
|
155
155
|
const chevronBudget = valueW - 4;
|
|
156
156
|
let valueCell;
|
|
157
157
|
if (isCursor && isEditing) {
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pure keypress reducer for the devflow flags TUI.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* CLI-layer view module; consumes src/core/ imports only.
|
|
5
|
+
* One syntax, one semantic — value vocabulary.
|
|
6
|
+
* Pure functions only — no process.exit(), no I/O.
|
|
7
|
+
* Generic shell in tui/terminal.ts; this module is pure logic.
|
|
8
8
|
*
|
|
9
9
|
* viewMode GLUE RULE: view-mode's neutralValue ('default') maps to null in the TUI.
|
|
10
10
|
* `buildFlagRows` maps record value 'default' → null via `recordToTui` (core/flags.ts);
|
|
11
11
|
* `collectFlagRecord` maps null → 'default' via `tuiToRecord` (core/flags.ts).
|
|
12
12
|
* Number 0 is ACTIVE — null ≠ 0. Both functions live next to neutralValueOf, their
|
|
13
|
-
* definition dependency
|
|
13
|
+
* definition dependency, so there is one shared definition rather than per-consumer copies.
|
|
14
14
|
*
|
|
15
15
|
* Strict number parsing: leading/trailing whitespace and leading zeros are
|
|
16
16
|
* invalid ('007' → error, ' 8' → error). This rejects pathological inputs
|
|
17
|
-
* before they reach coerceFlagValue
|
|
17
|
+
* before they reach coerceFlagValue.
|
|
18
18
|
*
|
|
19
19
|
* Buffer hard cap: BUFFER_MAX_LEN = 64 chars (paste-flood guard).
|
|
20
20
|
*
|
|
@@ -198,8 +198,8 @@ function enterEdit(state) {
|
|
|
198
198
|
* Contract:
|
|
199
199
|
* - Empty buffer + allowUnset → commit null (unset)
|
|
200
200
|
* - Empty buffer + !allowUnset → error "Value is required"
|
|
201
|
-
* - For number/string flags: delegate to parseFlagValueInput (
|
|
202
|
-
*
|
|
201
|
+
* - For number/string flags: delegate to parseFlagValueInput (strict grammar
|
|
202
|
+
* enforced at the core sink, not per-caller). Error messages
|
|
203
203
|
* distinguish format failures (padded/hex/leading-zeros) from bounds failures.
|
|
204
204
|
* - parseFlagValueInput returns null on invalid input → stay editing + error
|
|
205
205
|
*/
|
|
@@ -230,7 +230,7 @@ function commitEdit(state) {
|
|
|
230
230
|
};
|
|
231
231
|
}
|
|
232
232
|
}
|
|
233
|
-
// Number flag: parseFlagValueInput enforces strict decimal grammar
|
|
233
|
+
// Number flag: parseFlagValueInput enforces strict decimal grammar.
|
|
234
234
|
// Provide specific error messages to distinguish format from bounds failures.
|
|
235
235
|
if (flagDef.kind === 'number') {
|
|
236
236
|
if (buf !== buf.trim()) {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Thin adapter — devflow flags TUI shell over the generic runTui driver.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* a finally-guarded scope.
|
|
7
|
-
*
|
|
4
|
+
* Impure I/O shell in CLI layer; pure logic lives in state.ts/render.ts.
|
|
5
|
+
* Cleanup wired via Promise resolve — never process.exit() inside
|
|
6
|
+
* a finally-guarded scope (it would skip the finally).
|
|
7
|
+
* Thin adapter over the generic shell (src/cli/tui/terminal.ts).
|
|
8
8
|
*
|
|
9
9
|
* Public API:
|
|
10
10
|
* - runFlagsTui(initialRows, io?) → Promise<FlagsTuiResult>
|
package/dist/cli/tui/cells.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared TUI cell helpers — shared by agents-view and flags-view.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Generified into a shared module rather than copy-adapted per consumer.
|
|
5
5
|
* Pure functions, no I/O.
|
|
6
6
|
*/
|
|
7
7
|
import { stripAnsi, truncate } from '../../core/ansi.js';
|
package/dist/cli/tui/terminal.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Generic TUI shell — shared by agents-view and flags-view.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* a finally-guarded scope.
|
|
7
|
-
*
|
|
4
|
+
* Impure I/O shell in CLI layer; pure logic lives in state + render.
|
|
5
|
+
* Cleanup wired via Promise resolve — never process.exit() inside
|
|
6
|
+
* a finally-guarded scope (it would skip the finally).
|
|
7
|
+
* One generic shell, thin adapters per TUI — not copy-adapted per consumer.
|
|
8
8
|
*
|
|
9
9
|
* Bounded: MAX_KEYPRESSES = 50_000 hard limit (reliability rule — every loop bounded).
|
|
10
10
|
*
|
|
@@ -268,8 +268,8 @@ export async function runTui(spec) {
|
|
|
268
268
|
* A throw inside an EventEmitter listener does NOT reject the enclosing
|
|
269
269
|
* promise — it escapes as an uncaughtException and kills the process with
|
|
270
270
|
* cleanup() never having run, leaving raw mode and alt-screen set. Routing
|
|
271
|
-
* every handler failure through here keeps the
|
|
272
|
-
* always runs
|
|
271
|
+
* every handler failure through here keeps the invariant that cleanup
|
|
272
|
+
* always runs, while still surfacing the error rather than swallowing it.
|
|
273
273
|
*/
|
|
274
274
|
function fail(err) {
|
|
275
275
|
cleanup();
|