context-brake 1.0.0 → 1.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/README.md +31 -41
- package/dist/assets/runtime/antigravity-cli-hook.mjs +68 -23
- package/dist/assets/runtime/claude-code-hook.mjs +133 -35
- package/dist/assets/runtime/claude-code-mod.mjs +165 -59
- package/dist/assets/runtime/claude-code-statusline.mjs +41 -9
- package/dist/assets/runtime/codex-cli-hook.mjs +71 -23
- package/dist/assets/runtime/cursor-hook.mjs +71 -23
- package/dist/assets/runtime/github-copilot-cli-hook.mjs +71 -23
- package/dist/assets/runtime/omp-extension.js +70 -23
- package/dist/assets/runtime/omp-restart.js +41 -9
- package/dist/assets/runtime/opencode-plugin.js +70 -23
- package/dist/assets/runtime/pi-extension.js +73 -23
- package/dist/assets/runtime/pi-restart.js +41 -9
- package/dist/src/cli/assistant/assistant-context.js +7 -0
- package/dist/src/cli/assistant/assistant-questions.js +5 -4
- package/dist/src/cli/assistant/assistant-session.js +3 -1
- package/dist/src/cli/assistant/invocation-facts.d.ts +5 -0
- package/dist/src/cli/assistant/invocation-facts.js +6 -0
- package/dist/src/cli/assistant/questions-misc.d.ts +1 -0
- package/dist/src/cli/assistant/questions-misc.js +3 -2
- package/dist/src/cli/assistant/questions-skill.d.ts +21 -0
- package/dist/src/cli/assistant/questions-skill.js +45 -0
- package/dist/src/cli/assistant/questions-snapshot.d.ts +3 -1
- package/dist/src/cli/assistant/questions-snapshot.js +21 -26
- package/dist/src/cli/assistant/questions-trigger.d.ts +6 -0
- package/dist/src/cli/assistant/questions-trigger.js +56 -0
- package/dist/src/cli/assistant/skill-options.d.ts +20 -0
- package/dist/src/cli/assistant/skill-options.js +40 -0
- package/dist/src/cli/assistant/snapshot-specs.d.ts +7 -1
- package/dist/src/cli/assistant/snapshot-specs.js +17 -7
- package/dist/src/cli/assistant/summary-rows.d.ts +5 -0
- package/dist/src/cli/assistant/summary-rows.js +47 -0
- package/dist/src/cli/assistant/summary.d.ts +2 -1
- package/dist/src/cli/assistant/summary.js +21 -30
- package/dist/src/cli/assistant/types.d.ts +15 -0
- package/dist/src/cli/commands/doctor.js +2 -1
- package/dist/src/cli/commands/remove.js +1 -1
- package/dist/src/cli/init-arguments.js +5 -4
- package/dist/src/cli/init-flow.js +1 -1
- package/dist/src/cli/init-option-rules.d.ts +5 -0
- package/dist/src/cli/init-option-rules.js +16 -0
- package/dist/src/cli/output/card.d.ts +8 -0
- package/dist/src/cli/output/card.js +30 -0
- package/dist/src/cli/output/doctor-mode-text.js +8 -2
- package/dist/src/cli/output/install-rows.d.ts +6 -0
- package/dist/src/cli/output/install-rows.js +31 -0
- package/dist/src/cli/output/install-text.d.ts +4 -0
- package/dist/src/cli/output/install-text.js +26 -0
- package/dist/src/cli/output/style.d.ts +22 -0
- package/dist/src/cli/output/style.js +27 -0
- package/dist/src/cli/output/text.d.ts +1 -2
- package/dist/src/cli/output/text.js +1 -28
- package/dist/src/core/contracts/adapter.d.ts +2 -1
- package/dist/src/core/contracts/configuration.d.ts +2 -1
- package/dist/src/core/contracts/configuration.js +2 -10
- package/dist/src/core/contracts/context-reading.d.ts +16 -0
- package/dist/src/core/contracts/context-reading.js +20 -0
- package/dist/src/core/contracts/context-window-report.d.ts +8 -1
- package/dist/src/core/contracts/context-window-report.js +3 -1
- package/dist/src/core/contracts/diagnostics.d.ts +21 -0
- package/dist/src/core/contracts/diagnostics.js +2 -1
- package/dist/src/core/contracts/manifest.d.ts +16 -0
- package/dist/src/core/contracts/manifest.js +6 -0
- package/dist/src/core/contracts/runtime.d.ts +2 -0
- package/dist/src/core/contracts/skill-catalog.d.ts +11 -0
- package/dist/src/core/contracts/skill-catalog.js +1 -0
- package/dist/src/core/contracts/skill-invocation.d.ts +3 -0
- package/dist/src/core/contracts/skill-invocation.js +1 -0
- package/dist/src/core/contracts/skill-name.d.ts +5 -0
- package/dist/src/core/contracts/skill-name.js +31 -0
- package/dist/src/core/services/brake-engine.js +4 -3
- package/dist/src/core/services/doctor-report-extras.d.ts +2 -0
- package/dist/src/core/services/doctor-report-extras.js +22 -5
- package/dist/src/core/services/doctor-service.d.ts +2 -0
- package/dist/src/core/services/doctor-service.js +1 -1
- package/dist/src/core/services/failure-policy.js +1 -1
- package/dist/src/core/services/injection-policy.d.ts +1 -0
- package/dist/src/core/services/injection-policy.js +1 -1
- package/dist/src/core/services/installation-adapters.d.ts +2 -1
- package/dist/src/core/services/installation-adapters.js +3 -1
- package/dist/src/core/services/installation-builder.js +3 -2
- package/dist/src/core/services/installation-service.js +1 -1
- package/dist/src/core/services/manifest-change.d.ts +2 -1
- package/dist/src/core/services/manifest-change.js +2 -1
- package/dist/src/core/services/session-reset-handler.js +1 -1
- package/dist/src/core/services/skill-candidates.d.ts +16 -0
- package/dist/src/core/services/skill-candidates.js +14 -0
- package/dist/src/core/services/skill-invocation.d.ts +4 -0
- package/dist/src/core/services/skill-invocation.js +12 -0
- package/dist/src/core/services/skill-ranking.d.ts +7 -0
- package/dist/src/core/services/skill-ranking.js +25 -0
- package/dist/src/core/services/snapshot-merge.d.ts +1 -0
- package/dist/src/core/services/snapshot-merge.js +12 -5
- package/dist/src/core/services/trigger-text.d.ts +5 -0
- package/dist/src/core/services/trigger-text.js +4 -0
- package/dist/src/core/services/zone-guidance.d.ts +13 -2
- package/dist/src/core/services/zone-guidance.js +14 -8
- package/dist/src/infrastructure/harnesses/antigravity-cli/runtime.js +2 -1
- package/dist/src/infrastructure/harnesses/antigravity-cli/skill-folders.d.ts +1 -0
- package/dist/src/infrastructure/harnesses/antigravity-cli/skill-folders.js +1 -0
- package/dist/src/infrastructure/harnesses/antigravity-cli/skill-invocation.d.ts +2 -0
- package/dist/src/infrastructure/harnesses/antigravity-cli/skill-invocation.js +2 -0
- package/dist/src/infrastructure/harnesses/claude-code/adapter.js +6 -4
- package/dist/src/infrastructure/harnesses/claude-code/auto-restart-diagnostics.d.ts +1 -0
- package/dist/src/infrastructure/harnesses/claude-code/auto-restart-diagnostics.js +2 -2
- package/dist/src/infrastructure/harnesses/claude-code/auto-restart-planner.d.ts +5 -1
- package/dist/src/infrastructure/harnesses/claude-code/auto-restart-planner.js +6 -6
- package/dist/src/infrastructure/harnesses/claude-code/capabilities.js +1 -1
- package/dist/src/infrastructure/harnesses/claude-code/context-readings-scan.d.ts +2 -0
- package/dist/src/infrastructure/harnesses/claude-code/context-readings-scan.js +21 -0
- package/dist/src/infrastructure/harnesses/claude-code/context-source.d.ts +10 -0
- package/dist/src/infrastructure/harnesses/claude-code/context-source.js +16 -0
- package/dist/src/infrastructure/harnesses/claude-code/mod/context-reading.d.ts +2 -0
- package/dist/src/infrastructure/harnesses/claude-code/mod/context-reading.js +67 -0
- package/dist/src/infrastructure/harnesses/claude-code/mod/host.d.ts +9 -0
- package/dist/src/infrastructure/harnesses/claude-code/mod/mod-info.d.ts +1 -1
- package/dist/src/infrastructure/harnesses/claude-code/mod/mod-info.js +1 -1
- package/dist/src/infrastructure/harnesses/claude-code/mod/mod-log.d.ts +1 -0
- package/dist/src/infrastructure/harnesses/claude-code/mod/mod-log.js +3 -0
- package/dist/src/infrastructure/harnesses/claude-code/mod/register.js +6 -1
- package/dist/src/infrastructure/harnesses/claude-code/mod-reading.d.ts +6 -0
- package/dist/src/infrastructure/harnesses/claude-code/mod-reading.js +37 -0
- package/dist/src/infrastructure/harnesses/claude-code/planner.d.ts +2 -1
- package/dist/src/infrastructure/harnesses/claude-code/planner.js +4 -4
- package/dist/src/infrastructure/harnesses/claude-code/project-root.d.ts +2 -0
- package/dist/src/infrastructure/harnesses/claude-code/project-root.js +5 -0
- package/dist/src/infrastructure/harnesses/claude-code/runtime.js +12 -7
- package/dist/src/infrastructure/harnesses/claude-code/skill-folders.d.ts +1 -0
- package/dist/src/infrastructure/harnesses/claude-code/skill-folders.js +1 -0
- package/dist/src/infrastructure/harnesses/claude-code/skill-invocation.d.ts +2 -0
- package/dist/src/infrastructure/harnesses/claude-code/skill-invocation.js +2 -0
- package/dist/src/infrastructure/harnesses/claude-code/statusline-context-window.js +15 -2
- package/dist/src/infrastructure/harnesses/claude-code/statusline-diagnostics.js +3 -3
- package/dist/src/infrastructure/harnesses/claude-code/statusline-migration.d.ts +3 -0
- package/dist/src/infrastructure/harnesses/claude-code/statusline-migration.js +23 -0
- package/dist/src/infrastructure/harnesses/claude-code/statusline-planner.d.ts +2 -1
- package/dist/src/infrastructure/harnesses/claude-code/statusline-planner.js +7 -4
- package/dist/src/infrastructure/harnesses/claude-code/user-settings.d.ts +2 -0
- package/dist/src/infrastructure/harnesses/claude-code/user-settings.js +11 -0
- package/dist/src/infrastructure/harnesses/codex-cli/adapter.js +5 -4
- package/dist/src/infrastructure/harnesses/codex-cli/runtime.js +2 -1
- package/dist/src/infrastructure/harnesses/codex-cli/skill-folders.d.ts +1 -0
- package/dist/src/infrastructure/harnesses/codex-cli/skill-folders.js +1 -0
- package/dist/src/infrastructure/harnesses/codex-cli/skill-invocation.d.ts +2 -0
- package/dist/src/infrastructure/harnesses/codex-cli/skill-invocation.js +2 -0
- package/dist/src/infrastructure/harnesses/common/config-origin.d.ts +10 -0
- package/dist/src/infrastructure/harnesses/common/config-origin.js +52 -0
- package/dist/src/infrastructure/harnesses/cursor/adapter.js +5 -4
- package/dist/src/infrastructure/harnesses/cursor/runtime.js +2 -1
- package/dist/src/infrastructure/harnesses/cursor/skill-folders.d.ts +1 -0
- package/dist/src/infrastructure/harnesses/cursor/skill-folders.js +1 -0
- package/dist/src/infrastructure/harnesses/cursor/skill-invocation.d.ts +2 -0
- package/dist/src/infrastructure/harnesses/cursor/skill-invocation.js +2 -0
- package/dist/src/infrastructure/harnesses/github-copilot-cli/runtime.js +2 -1
- package/dist/src/infrastructure/harnesses/github-copilot-cli/skill-folders.d.ts +1 -0
- package/dist/src/infrastructure/harnesses/github-copilot-cli/skill-folders.js +1 -0
- package/dist/src/infrastructure/harnesses/github-copilot-cli/skill-invocation.d.ts +2 -0
- package/dist/src/infrastructure/harnesses/github-copilot-cli/skill-invocation.js +2 -0
- package/dist/src/infrastructure/harnesses/oh-my-pi/runtime.js +2 -1
- package/dist/src/infrastructure/harnesses/oh-my-pi/skill-folders.d.ts +1 -0
- package/dist/src/infrastructure/harnesses/oh-my-pi/skill-folders.js +1 -0
- package/dist/src/infrastructure/harnesses/oh-my-pi/skill-invocation.d.ts +2 -0
- package/dist/src/infrastructure/harnesses/oh-my-pi/skill-invocation.js +2 -0
- package/dist/src/infrastructure/harnesses/opencode/runtime.js +2 -1
- package/dist/src/infrastructure/harnesses/opencode/skill-folders.d.ts +1 -0
- package/dist/src/infrastructure/harnesses/opencode/skill-folders.js +1 -0
- package/dist/src/infrastructure/harnesses/opencode/skill-invocation.d.ts +2 -0
- package/dist/src/infrastructure/harnesses/opencode/skill-invocation.js +2 -0
- package/dist/src/infrastructure/harnesses/pi/runtime.js +2 -1
- package/dist/src/infrastructure/harnesses/pi/skill-folders.d.ts +1 -0
- package/dist/src/infrastructure/harnesses/pi/skill-folders.js +1 -0
- package/dist/src/infrastructure/harnesses/pi/skill-invocation.d.ts +2 -0
- package/dist/src/infrastructure/harnesses/pi/skill-invocation.js +2 -0
- package/dist/src/infrastructure/harnesses/skill-folders.d.ts +3 -0
- package/dist/src/infrastructure/harnesses/skill-folders.js +21 -0
- package/dist/src/infrastructure/harnesses/skill-invocations.d.ts +4 -0
- package/dist/src/infrastructure/harnesses/skill-invocations.js +21 -0
- package/dist/src/infrastructure/runtime/runtime-paths.js +2 -1
- package/dist/src/infrastructure/skills/frontmatter.d.ts +5 -0
- package/dist/src/infrastructure/skills/frontmatter.js +25 -0
- package/dist/src/infrastructure/skills/skill-catalog-reader.d.ts +4 -0
- package/dist/src/infrastructure/skills/skill-catalog-reader.js +78 -0
- package/dist/src/infrastructure/skills/skill-catalog.d.ts +2 -0
- package/dist/src/infrastructure/skills/skill-catalog.js +5 -0
- package/dist/src/infrastructure/storage/change-applier.d.ts +1 -0
- package/dist/src/infrastructure/storage/change-applier.js +2 -1
- package/dist/src/infrastructure/storage/json-document-editor.js +2 -1
- package/dist/src/infrastructure/storage/json-node-removal.d.ts +2 -0
- package/dist/src/infrastructure/storage/json-node-removal.js +70 -0
- package/dist/src/infrastructure/storage/json-span-utils.d.ts +1 -1
- package/dist/src/infrastructure/storage/json-span-utils.js +1 -48
- package/docs/telemetry-block.md +3 -1
- package/package.json +1 -1
- package/schemas/context-brake.config.schema.json +9 -4
- package/schemas/doctor-report.schema.json +51 -0
|
@@ -18308,6 +18308,34 @@ async function handleTurnEnd(host, turn) {
|
|
|
18308
18308
|
});
|
|
18309
18309
|
}
|
|
18310
18310
|
|
|
18311
|
+
// src/core/contracts/skill-name.ts
|
|
18312
|
+
var MAX_SKILL_NAME_LENGTH = 64;
|
|
18313
|
+
var NAME_SEGMENT = "[a-z0-9]+(?:-[a-z0-9]+)*";
|
|
18314
|
+
var NAMESPACE_SEPARATOR = ":";
|
|
18315
|
+
var INVOCATION_CHARACTERS = ["/", "$", "@"];
|
|
18316
|
+
var MAX_QUALIFIED_LENGTH = MAX_SKILL_NAME_LENGTH * 2 + NAMESPACE_SEPARATOR.length;
|
|
18317
|
+
function segmentFits(end) {
|
|
18318
|
+
return `(?=[^${NAMESPACE_SEPARATOR}]{1,${MAX_SKILL_NAME_LENGTH}}${end})`;
|
|
18319
|
+
}
|
|
18320
|
+
var PREFIX = `(?:${segmentFits(NAMESPACE_SEPARATOR)}${NAME_SEGMENT}${NAMESPACE_SEPARATOR})?`;
|
|
18321
|
+
var SKILL_NAME_PATTERN = new RegExp(`^${PREFIX}${segmentFits("$")}${NAME_SEGMENT}$`);
|
|
18322
|
+
var SHAPE_RULE = `lowercase letters, digits, and single hyphens, up to ${MAX_SKILL_NAME_LENGTH} characters, optionally prefixed by a plugin name and a colon`;
|
|
18323
|
+
function isSkillName(value) {
|
|
18324
|
+
return SKILL_NAME_PATTERN.test(value);
|
|
18325
|
+
}
|
|
18326
|
+
function skillNameProblem(value) {
|
|
18327
|
+
if (value === "") return "must not be empty";
|
|
18328
|
+
if (isSkillName(value)) return null;
|
|
18329
|
+
const bare = INVOCATION_CHARACTERS.includes(value.charAt(0)) ? value.slice(1) : null;
|
|
18330
|
+
if (bare !== null && isSkillName(bare)) return `"${value}" is not a skill name; use the bare name ${bare}`;
|
|
18331
|
+
return `"${value}" is not a skill name; use ${SHAPE_RULE}`;
|
|
18332
|
+
}
|
|
18333
|
+
var skillNameSchema = external_exports.string().check((ctx) => {
|
|
18334
|
+
const message = skillNameProblem(ctx.value);
|
|
18335
|
+
if (message !== null) ctx.issues.push({ code: "custom", input: ctx.value, message });
|
|
18336
|
+
});
|
|
18337
|
+
external_exports.globalRegistry.add(skillNameSchema, { pattern: SKILL_NAME_PATTERN.source, maxLength: MAX_QUALIFIED_LENGTH });
|
|
18338
|
+
|
|
18311
18339
|
// src/core/contracts/zones.ts
|
|
18312
18340
|
var ZONES = ["GREEN", "YELLOW", "RED", "CRITICAL"];
|
|
18313
18341
|
var USAGE_SOURCES = ["measured", "estimated"];
|
|
@@ -18331,15 +18359,8 @@ function uniqueCheck(message) {
|
|
|
18331
18359
|
if (!isUnique(ctx.value)) addIssue(ctx, { code: "custom", path: [], input: ctx.value, message });
|
|
18332
18360
|
};
|
|
18333
18361
|
}
|
|
18334
|
-
var MAX_AGENT_COMMAND_LENGTH = 200;
|
|
18335
|
-
var TRIMMED_RULE = "must not have leading or trailing whitespace";
|
|
18336
18362
|
var RESUME_REQUIRES_COMMAND_RULE = "requires snapshot.command";
|
|
18337
|
-
var
|
|
18338
|
-
var agentCommand = external_exports.string().check(external_exports.minLength(1), external_exports.maxLength(MAX_AGENT_COMMAND_LENGTH), (ctx) => {
|
|
18339
|
-
if (ctx.value.trim() !== ctx.value) addIssue(ctx, { code: "custom", path: [], input: ctx.value, message: TRIMMED_RULE });
|
|
18340
|
-
if (/[\r\n]/.test(ctx.value)) addIssue(ctx, { code: "custom", path: [], input: ctx.value, message: SINGLE_LINE_RULE });
|
|
18341
|
-
});
|
|
18342
|
-
var snapshotSchema = external_exports.strictObject({ triggerZone: external_exports._default(external_exports.enum(SNAPSHOT_TRIGGER_ZONES), "RED"), command: external_exports.optional(agentCommand), resumeCommand: external_exports.optional(agentCommand) }).check((ctx) => {
|
|
18363
|
+
var snapshotSchema = external_exports.strictObject({ triggerZone: external_exports._default(external_exports.enum(SNAPSHOT_TRIGGER_ZONES), "RED"), triggerPercentage: external_exports.optional(external_exports.int().check(external_exports.minimum(1), external_exports.maximum(100))), command: external_exports.optional(skillNameSchema), resumeCommand: external_exports.optional(skillNameSchema) }).check((ctx) => {
|
|
18343
18364
|
if (ctx.value.resumeCommand !== void 0 && ctx.value.command === void 0) addIssue(ctx, { code: "custom", path: ["resumeCommand"], input: ctx.value.resumeCommand, message: RESUME_REQUIRES_COMMAND_RULE });
|
|
18344
18365
|
});
|
|
18345
18366
|
var DEFAULT_SNAPSHOT = { triggerZone: "RED" };
|
|
@@ -18383,11 +18404,22 @@ var HANDOFF_ARCHIVE_LIMIT = 10;
|
|
|
18383
18404
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
18384
18405
|
import { join, resolve } from "node:path";
|
|
18385
18406
|
|
|
18407
|
+
// src/core/contracts/context-reading.ts
|
|
18408
|
+
var RUNTIME_IGNORE_CONTENT = "*\n";
|
|
18409
|
+
var PERCENTAGE_MAXIMUM = 100;
|
|
18410
|
+
var contextReadingSchema = external_exports.strictObject({
|
|
18411
|
+
v: external_exports.literal(1),
|
|
18412
|
+
at: external_exports.string().check(external_exports.minLength(1)),
|
|
18413
|
+
windowTokens: external_exports.nullable(external_exports.int().check(external_exports.positive())),
|
|
18414
|
+
inputTokens: external_exports.nullable(external_exports.int().check(external_exports.nonnegative())),
|
|
18415
|
+
usedPercentage: external_exports.nullable(external_exports.number().check(external_exports.gte(0), external_exports.lte(PERCENTAGE_MAXIMUM)))
|
|
18416
|
+
});
|
|
18417
|
+
|
|
18386
18418
|
// src/infrastructure/storage/runtime-state-files.ts
|
|
18387
18419
|
var RUNTIME_STATE_RELATIVE_DIR = ".context-brake/runtime";
|
|
18388
18420
|
|
|
18389
18421
|
// src/infrastructure/runtime/runtime-paths.ts
|
|
18390
|
-
var RUNTIME_GITIGNORE_CONTENT =
|
|
18422
|
+
var RUNTIME_GITIGNORE_CONTENT = RUNTIME_IGNORE_CONTENT;
|
|
18391
18423
|
var SESSIONS_DIR_NAME = "sessions";
|
|
18392
18424
|
var SESSIONS_RELATIVE_PREFIX = `${RUNTIME_STATE_RELATIVE_DIR}/${SESSIONS_DIR_NAME}/`;
|
|
18393
18425
|
function runtimeDirectory(projectRoot) {
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { detectHarnesses } from '../../core/services/detection-service.js';
|
|
2
2
|
import { getAllAdapters } from '../../infrastructure/harnesses/registry.js';
|
|
3
|
+
import { resolveSkillInvocation } from '../../infrastructure/harnesses/skill-invocations.js';
|
|
4
|
+
import { createSkillCatalogReader } from '../../infrastructure/skills/skill-catalog.js';
|
|
5
|
+
import { detectContextSource } from '../../infrastructure/harnesses/claude-code/context-source.js';
|
|
3
6
|
import { hasStatuslineOptOut } from '../../infrastructure/harnesses/claude-code/statusline-default.js';
|
|
4
7
|
import { isInsideGitWorkingTree } from '../../infrastructure/git/git-context.js';
|
|
5
8
|
import { NodeManifestStore } from '../../infrastructure/storage/manifest-store.js';
|
|
@@ -13,6 +16,10 @@ export async function buildAssistantContext(env, state) {
|
|
|
13
16
|
detections: detectHarnesses(sources, state.selection),
|
|
14
17
|
adapters,
|
|
15
18
|
hasStatuslineOptOut: await hasStatuslineOptOut(env.projectRoot),
|
|
19
|
+
contextSource: await detectContextSource(buildHarnessContext(env, manifest)),
|
|
16
20
|
insideGit: await isInsideGitWorkingTree(env.projectRoot),
|
|
21
|
+
invocations: resolveSkillInvocation,
|
|
22
|
+
projectRoot: env.projectRoot,
|
|
23
|
+
catalog: createSkillCatalogReader(),
|
|
17
24
|
};
|
|
18
25
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { invocationFacts } from './invocation-facts.js';
|
|
1
2
|
import { askHarnesses } from './questions-harness.js';
|
|
2
3
|
import { askMisc } from './questions-misc.js';
|
|
3
4
|
import { askRestart } from './questions-restart.js';
|
|
@@ -6,7 +7,7 @@ export async function runQuestions(context, prompts) {
|
|
|
6
7
|
const harness = await askHarnesses(context, prompts);
|
|
7
8
|
if (harness === null)
|
|
8
9
|
return null;
|
|
9
|
-
const snapshot = await askSnapshot(context, prompts);
|
|
10
|
+
const snapshot = await askSnapshot(context, harness.selected, prompts);
|
|
10
11
|
if (snapshot === null)
|
|
11
12
|
return null;
|
|
12
13
|
const restart = await askRestart(context, { selected: harness.selected, hasSnapshotCommand: snapshot.command !== null }, prompts);
|
|
@@ -18,9 +19,9 @@ export async function runQuestions(context, prompts) {
|
|
|
18
19
|
return {
|
|
19
20
|
flags: [...harness.flags, ...snapshot.flags, ...restart.flags, ...misc.flags],
|
|
20
21
|
facts: {
|
|
21
|
-
selected: harness.selected, excluded: harness.excluded, snapshotCommand: snapshot.command, triggerZone: snapshot.triggerZone,
|
|
22
|
-
resumeCommand: snapshot.resumeCommand, restartOn: restart.on, restartLimit: restart.limit, restartModes: restart.modes,
|
|
23
|
-
statuslineBridge: misc.statuslineBridge, debug: misc.debug, gitIgnore: misc.gitIgnore,
|
|
22
|
+
selected: harness.selected, excluded: harness.excluded, snapshotCommand: snapshot.command, triggerZone: snapshot.triggerZone, triggerPercentage: snapshot.triggerPercentage,
|
|
23
|
+
resumeCommand: snapshot.resumeCommand, invocations: invocationFacts(context.invocations, harness.selected, snapshot), restartOn: restart.on, restartLimit: restart.limit, restartModes: restart.modes,
|
|
24
|
+
statuslineBridge: misc.statuslineBridge, contextSource: misc.contextSource, debug: misc.debug, gitIgnore: misc.gitIgnore,
|
|
24
25
|
},
|
|
25
26
|
};
|
|
26
27
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { parseInit } from '../init-arguments.js';
|
|
2
2
|
import { loadInitConfigState } from '../init-config-state.js';
|
|
3
|
+
import { detectStyle } from '../output/style.js';
|
|
3
4
|
import { buildAssistantContext } from './assistant-context.js';
|
|
4
5
|
import { runQuestions } from './assistant-questions.js';
|
|
5
6
|
import { formatEquivalentCommand } from './equivalent-command.js';
|
|
@@ -14,7 +15,8 @@ async function askForFlags(env, prompts) {
|
|
|
14
15
|
const result = await runQuestions(await buildAssistantContext(env, state), prompts);
|
|
15
16
|
if (result === null)
|
|
16
17
|
return null;
|
|
17
|
-
|
|
18
|
+
const style = detectStyle({ env: process.env, platform: process.platform, isTty: process.stdout.isTTY === true });
|
|
19
|
+
process.stdout.write(`${renderSummary(result.facts, formatEquivalentCommand(result.flags), style).join('\n')}\n`);
|
|
18
20
|
return result.flags;
|
|
19
21
|
}
|
|
20
22
|
export async function resolveAssistedArgs(args, env, prompts) {
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { HarnessId } from '../../core/contracts/harness.js';
|
|
2
|
+
import type { SkillInvocationResolver } from '../../core/contracts/skill-invocation.js';
|
|
3
|
+
import type { SnapshotChoice } from './questions-snapshot.js';
|
|
4
|
+
import type { HarnessInvocationFact } from './types.js';
|
|
5
|
+
export declare function invocationFacts(resolve: SkillInvocationResolver, selected: readonly HarnessId[], snapshot: SnapshotChoice): HarnessInvocationFact[];
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export function invocationFacts(resolve, selected, snapshot) {
|
|
2
|
+
const { command, resumeCommand } = snapshot;
|
|
3
|
+
if (command === null)
|
|
4
|
+
return [];
|
|
5
|
+
return selected.map((harness) => ({ harness, command: resolve(harness)(command), resumeCommand: resumeCommand === null ? null : resolve(harness)(resumeCommand) }));
|
|
6
|
+
}
|
|
@@ -3,6 +3,7 @@ import type { PromptPort } from './prompt-port.js';
|
|
|
3
3
|
import type { AssistantContext } from './types.js';
|
|
4
4
|
export type MiscChoice = {
|
|
5
5
|
readonly statuslineBridge: boolean | null;
|
|
6
|
+
readonly contextSource: AssistantContext['contextSource'] | null;
|
|
6
7
|
readonly debug: boolean;
|
|
7
8
|
readonly gitIgnore: boolean | null;
|
|
8
9
|
readonly flags: readonly string[];
|
|
@@ -10,7 +10,8 @@ async function askBridge(context, prompts) {
|
|
|
10
10
|
return { answer: answer.value, flags: [answer.value ? '--statusline-bridge' : '--no-statusline-bridge'] };
|
|
11
11
|
}
|
|
12
12
|
export async function askMisc(context, selected, prompts) {
|
|
13
|
-
const
|
|
13
|
+
const isClaudeSelected = selected.includes('claude-code');
|
|
14
|
+
const bridge = isClaudeSelected && context.contextSource === 'bridge' ? await askBridge(context, prompts) : { answer: null, flags: [] };
|
|
14
15
|
if (bridge === null)
|
|
15
16
|
return null;
|
|
16
17
|
const wasDebug = context.config?.debug === true;
|
|
@@ -21,5 +22,5 @@ export async function askMisc(context, selected, prompts) {
|
|
|
21
22
|
const gitIgnore = await askGitIgnore(context, prompts);
|
|
22
23
|
if (gitIgnore === null)
|
|
23
24
|
return null;
|
|
24
|
-
return { statuslineBridge: bridge.answer, debug: debug.value, gitIgnore: gitIgnore.answer, flags: [...bridge.flags, ...debugFlags, ...gitIgnore.flags] };
|
|
25
|
+
return { statuslineBridge: bridge.answer, contextSource: isClaudeSelected ? context.contextSource : null, debug: debug.value, gitIgnore: gitIgnore.answer, flags: [...bridge.flags, ...debugFlags, ...gitIgnore.flags] };
|
|
25
26
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { HarnessId } from '../../core/contracts/harness.js';
|
|
2
|
+
import { type SkillCandidate } from '../../core/services/skill-candidates.js';
|
|
3
|
+
import { type SkillRole } from '../../core/services/skill-ranking.js';
|
|
4
|
+
import { type Answer } from './ask.js';
|
|
5
|
+
import type { PromptPort } from './prompt-port.js';
|
|
6
|
+
import type { AssistantContext, Validation } from './types.js';
|
|
7
|
+
import type { PromptSpec } from './ui-prompt.js';
|
|
8
|
+
export type TypedSkill = {
|
|
9
|
+
readonly spec: PromptSpec;
|
|
10
|
+
readonly validate: (answer: string) => Validation<string | null>;
|
|
11
|
+
};
|
|
12
|
+
export type SkillQuestion = {
|
|
13
|
+
readonly role: SkillRole;
|
|
14
|
+
readonly current: string | undefined;
|
|
15
|
+
readonly exclude?: string | undefined;
|
|
16
|
+
readonly candidates: readonly SkillCandidate[];
|
|
17
|
+
readonly selectedCount: number;
|
|
18
|
+
readonly typed: TypedSkill;
|
|
19
|
+
};
|
|
20
|
+
export declare function loadCandidates(context: AssistantContext, selected: readonly HarnessId[]): Promise<SkillCandidate[]>;
|
|
21
|
+
export declare function askSkill(question: SkillQuestion, prompts: PromptPort): Promise<Answer<string | null> | null>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { mergeCatalogs } from '../../core/services/skill-candidates.js';
|
|
2
|
+
import { rankSkills } from '../../core/services/skill-ranking.js';
|
|
3
|
+
import { askValidated } from './ask.js';
|
|
4
|
+
import { buildOptions, initialValue, NONE_VALUE, optionFor, OTHER_VALUE, pickSpec } from './skill-options.js';
|
|
5
|
+
function warn(message) {
|
|
6
|
+
process.stderr.write(`WARN: ${message}\n`);
|
|
7
|
+
}
|
|
8
|
+
export async function loadCandidates(context, selected) {
|
|
9
|
+
try {
|
|
10
|
+
const read = await Promise.all(selected.map(async (harness) => ({ harness, catalog: await context.catalog(harness, context.projectRoot) })));
|
|
11
|
+
const warnings = read.flatMap(({ catalog }) => catalog.warnings);
|
|
12
|
+
if (warnings.length > 0)
|
|
13
|
+
warn(`Some skill folders could not be read (${warnings.join('; ')}); the list may be incomplete.`);
|
|
14
|
+
return mergeCatalogs(read.map(({ harness, catalog }) => ({ harness, skills: catalog.skills })));
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
warn(`Could not read the project skills (${error instanceof Error ? error.message : 'unknown error'}); type the skill name instead.`);
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function pickValidator(options, typed, initial) {
|
|
22
|
+
return (answer) => {
|
|
23
|
+
if (answer === '')
|
|
24
|
+
return { value: initial };
|
|
25
|
+
const option = optionFor(options, answer);
|
|
26
|
+
if (option !== null)
|
|
27
|
+
return { value: option };
|
|
28
|
+
const result = typed.validate(answer);
|
|
29
|
+
return 'error' in result ? result : { value: result.value ?? NONE_VALUE };
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export async function askSkill(question, prompts) {
|
|
33
|
+
const { role, current, typed } = question;
|
|
34
|
+
const ranked = rankSkills(role, question.candidates, { exclude: question.exclude });
|
|
35
|
+
if (ranked.length === 0)
|
|
36
|
+
return askValidated(prompts, typed.spec, typed.validate);
|
|
37
|
+
const options = buildOptions({ role, ranked, current, selectedCount: question.selectedCount });
|
|
38
|
+
const initial = initialValue(options, current);
|
|
39
|
+
const picked = await askValidated(prompts, pickSpec(role, options, initial), pickValidator(options, typed, initial));
|
|
40
|
+
if (picked === null)
|
|
41
|
+
return null;
|
|
42
|
+
if (picked.value === OTHER_VALUE)
|
|
43
|
+
return askValidated(prompts, typed.spec, typed.validate);
|
|
44
|
+
return { value: picked.value === NONE_VALUE ? null : picked.value };
|
|
45
|
+
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import type { HarnessId } from '../../core/contracts/harness.js';
|
|
1
2
|
import type { PromptPort } from './prompt-port.js';
|
|
2
3
|
import type { AssistantContext } from './types.js';
|
|
3
4
|
export type SnapshotChoice = {
|
|
4
5
|
readonly command: string | null;
|
|
5
6
|
readonly triggerZone: string;
|
|
7
|
+
readonly triggerPercentage: number | null;
|
|
6
8
|
readonly resumeCommand: string | null;
|
|
7
9
|
readonly flags: readonly string[];
|
|
8
10
|
};
|
|
9
|
-
export declare function askSnapshot(context: AssistantContext, prompts: PromptPort): Promise<SnapshotChoice | null>;
|
|
11
|
+
export declare function askSnapshot(context: AssistantContext, selected: readonly HarnessId[], prompts: PromptPort): Promise<SnapshotChoice | null>;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { mergeSnapshot } from '../../core/services/snapshot-merge.js';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { askSkill, loadCandidates } from './questions-skill.js';
|
|
3
|
+
import { askTrigger } from './questions-trigger.js';
|
|
4
|
+
import { commandSpec, resumeSpec } from './snapshot-specs.js';
|
|
4
5
|
const NONE_WORD = 'none';
|
|
6
|
+
const RESUME_NONE_MESSAGE = 'Enter keeps the current resume skill; to remove it use --no-snapshot-command, which clears both skills.';
|
|
5
7
|
const DEFAULT_ZONE = 'RED';
|
|
6
8
|
function checked(candidate) {
|
|
7
9
|
const merge = mergeSnapshot(undefined, { ...candidate, clearCommand: false });
|
|
@@ -17,48 +19,41 @@ function validateCommand(current) {
|
|
|
17
19
|
return error === null ? { value: answer } : { error };
|
|
18
20
|
};
|
|
19
21
|
}
|
|
20
|
-
function validateTrigger(current) {
|
|
21
|
-
return (answer) => {
|
|
22
|
-
if (answer === '')
|
|
23
|
-
return { value: current };
|
|
24
|
-
const error = checked({ triggerZone: answer });
|
|
25
|
-
return error === null ? { value: answer } : { error };
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
22
|
function validateResume(current) {
|
|
29
23
|
return (answer) => {
|
|
30
24
|
if (answer === '')
|
|
31
25
|
return { value: current ?? null };
|
|
26
|
+
if (answer.toLowerCase() === NONE_WORD)
|
|
27
|
+
return { error: RESUME_NONE_MESSAGE };
|
|
32
28
|
const error = checked({ command: 'placeholder', resumeCommand: answer });
|
|
33
29
|
return error === null ? { value: answer } : { error };
|
|
34
30
|
};
|
|
35
31
|
}
|
|
36
|
-
function valueFlag(name, value) {
|
|
37
|
-
return value.startsWith('-') ? [`${name}=${value}`] : [name, value];
|
|
38
|
-
}
|
|
39
32
|
async function askCommandOptions(input, prompts) {
|
|
40
|
-
const { current, command } = input;
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
if (zone === null)
|
|
33
|
+
const { current, command, pool } = input;
|
|
34
|
+
const trigger = await askTrigger({ zone: current?.triggerZone ?? DEFAULT_ZONE, percentage: current?.triggerPercentage ?? null }, prompts);
|
|
35
|
+
if (trigger === null)
|
|
44
36
|
return null;
|
|
45
|
-
const
|
|
37
|
+
const typed = { spec: resumeSpec(current?.resumeCommand), validate: validateResume(current?.resumeCommand) };
|
|
38
|
+
const resume = await askSkill({ role: 'resume', current: current?.resumeCommand, exclude: command, ...pool, typed }, prompts);
|
|
46
39
|
if (resume === null)
|
|
47
40
|
return null;
|
|
48
41
|
const flags = [
|
|
49
|
-
...(command === current?.command ? [] :
|
|
50
|
-
...
|
|
51
|
-
...(resume.value === null || resume.value === current?.resumeCommand ? [] :
|
|
42
|
+
...(command === current?.command ? [] : ['--snapshot-command', command]),
|
|
43
|
+
...trigger.flags,
|
|
44
|
+
...(resume.value === null || resume.value === current?.resumeCommand ? [] : ['--resume-command', resume.value]),
|
|
52
45
|
];
|
|
53
|
-
return { command, triggerZone: zone.
|
|
46
|
+
return { command, triggerZone: trigger.zone, triggerPercentage: trigger.percentage, resumeCommand: resume.value, flags };
|
|
54
47
|
}
|
|
55
|
-
export async function askSnapshot(context, prompts) {
|
|
48
|
+
export async function askSnapshot(context, selected, prompts) {
|
|
56
49
|
const current = context.config?.snapshot;
|
|
57
|
-
const
|
|
50
|
+
const pool = { candidates: await loadCandidates(context, selected), selectedCount: selected.length };
|
|
51
|
+
const typed = { spec: commandSpec(current?.command), validate: validateCommand(current?.command) };
|
|
52
|
+
const command = await askSkill({ role: 'snapshot', current: current?.command, ...pool, typed }, prompts);
|
|
58
53
|
if (command === null)
|
|
59
54
|
return null;
|
|
60
55
|
if (command.value !== null)
|
|
61
|
-
return askCommandOptions({ current, command: command.value }, prompts);
|
|
56
|
+
return askCommandOptions({ current, command: command.value, pool }, prompts);
|
|
62
57
|
const flags = current?.command === undefined ? [] : ['--no-snapshot-command'];
|
|
63
|
-
return { command: null, triggerZone: current?.triggerZone ?? DEFAULT_ZONE, resumeCommand: null, flags };
|
|
58
|
+
return { command: null, triggerZone: current?.triggerZone ?? DEFAULT_ZONE, triggerPercentage: current?.triggerPercentage ?? null, resumeCommand: null, flags };
|
|
64
59
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { PromptPort } from './prompt-port.js';
|
|
2
|
+
import { type TriggerState } from './snapshot-specs.js';
|
|
3
|
+
export type TriggerChoice = TriggerState & {
|
|
4
|
+
readonly flags: readonly string[];
|
|
5
|
+
};
|
|
6
|
+
export declare function askTrigger(current: TriggerState, prompts: PromptPort): Promise<TriggerChoice | null>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { mergeSnapshot } from '../../core/services/snapshot-merge.js';
|
|
2
|
+
import { askValidated } from './ask.js';
|
|
3
|
+
import { PERCENTAGE_OPTION, percentageSpec, triggerSpec } from './snapshot-specs.js';
|
|
4
|
+
const PERCENTAGE_RULE = 'Enter a whole number from 1 to 100.';
|
|
5
|
+
const INTEGER_PATTERN = /^\d+$/;
|
|
6
|
+
function checked(candidate) {
|
|
7
|
+
const merge = mergeSnapshot(undefined, { ...candidate, clearCommand: false });
|
|
8
|
+
return 'error' in merge ? merge.error : null;
|
|
9
|
+
}
|
|
10
|
+
function validatePercentage(current) {
|
|
11
|
+
return (answer) => {
|
|
12
|
+
if (answer === '' && current !== null)
|
|
13
|
+
return { value: current };
|
|
14
|
+
if (!INTEGER_PATTERN.test(answer))
|
|
15
|
+
return { error: PERCENTAGE_RULE };
|
|
16
|
+
const error = checked({ triggerPercentage: Number(answer) });
|
|
17
|
+
return error === null ? { value: Number(answer) } : { error };
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function typedPercentage(answer) {
|
|
21
|
+
const result = validatePercentage(null)(answer);
|
|
22
|
+
return 'error' in result ? result : { value: { zone: null, percentage: result.value } };
|
|
23
|
+
}
|
|
24
|
+
function validateTrigger(current) {
|
|
25
|
+
return (answer) => {
|
|
26
|
+
if (answer === '')
|
|
27
|
+
return { value: current.percentage === null ? { zone: current.zone, percentage: null } : { zone: null, percentage: current.percentage } };
|
|
28
|
+
if (answer.toLowerCase() === PERCENTAGE_OPTION)
|
|
29
|
+
return { value: { zone: null, percentage: null } };
|
|
30
|
+
if (INTEGER_PATTERN.test(answer))
|
|
31
|
+
return typedPercentage(answer);
|
|
32
|
+
const error = checked({ triggerZone: answer });
|
|
33
|
+
return error === null ? { value: { zone: answer, percentage: null } } : { error };
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function triggerFlags(current, choice) {
|
|
37
|
+
if (choice.percentage !== null)
|
|
38
|
+
return choice.percentage === current.percentage ? [] : ['--snapshot-trigger-percentage', String(choice.percentage)];
|
|
39
|
+
return choice.zone === current.zone && current.percentage === null ? [] : ['--snapshot-trigger', choice.zone];
|
|
40
|
+
}
|
|
41
|
+
async function askPercentage(current, prompts) {
|
|
42
|
+
const answer = await askValidated(prompts, percentageSpec(current.percentage), validatePercentage(current.percentage));
|
|
43
|
+
return answer === null ? null : answer.value;
|
|
44
|
+
}
|
|
45
|
+
export async function askTrigger(current, prompts) {
|
|
46
|
+
const answer = await askValidated(prompts, triggerSpec(current), validateTrigger(current));
|
|
47
|
+
if (answer === null)
|
|
48
|
+
return null;
|
|
49
|
+
const { zone } = answer.value;
|
|
50
|
+
if (zone !== null)
|
|
51
|
+
return { zone, percentage: null, flags: triggerFlags(current, { zone, percentage: null }) };
|
|
52
|
+
const percentage = answer.value.percentage ?? (await askPercentage(current, prompts));
|
|
53
|
+
if (percentage === null)
|
|
54
|
+
return null;
|
|
55
|
+
return { zone: current.zone, percentage, flags: triggerFlags(current, { zone: current.zone, percentage }) };
|
|
56
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { SkillCandidate } from '../../core/services/skill-candidates.js';
|
|
2
|
+
import type { SkillRole } from '../../core/services/skill-ranking.js';
|
|
3
|
+
import type { PromptSpec } from './ui-prompt.js';
|
|
4
|
+
export declare const OTHER_VALUE = "?other";
|
|
5
|
+
export declare const NONE_VALUE = "?none";
|
|
6
|
+
export type PickOption = {
|
|
7
|
+
readonly value: string;
|
|
8
|
+
readonly label: string;
|
|
9
|
+
readonly hint: string;
|
|
10
|
+
};
|
|
11
|
+
export type OptionsInput = {
|
|
12
|
+
readonly role: SkillRole;
|
|
13
|
+
readonly ranked: readonly SkillCandidate[];
|
|
14
|
+
readonly current: string | undefined;
|
|
15
|
+
readonly selectedCount: number;
|
|
16
|
+
};
|
|
17
|
+
export declare function buildOptions(input: OptionsInput): PickOption[];
|
|
18
|
+
export declare function initialValue(options: readonly PickOption[], current: string | undefined): string;
|
|
19
|
+
export declare function optionFor(options: readonly PickOption[], answer: string): string | null;
|
|
20
|
+
export declare function pickSpec(role: SkillRole, options: readonly PickOption[], initial: string): PromptSpec;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export const OTHER_VALUE = '?other';
|
|
2
|
+
export const NONE_VALUE = '?none';
|
|
3
|
+
const PICK_COUNT = 4;
|
|
4
|
+
const TITLES = { snapshot: 'Snapshot skill', resume: 'Resume skill' };
|
|
5
|
+
function foundIn(candidate, selectedCount) {
|
|
6
|
+
if (candidate.origins.length >= selectedCount)
|
|
7
|
+
return `found in ${[...new Set(candidate.origins.map((origin) => origin.folder))].join(', ')}`;
|
|
8
|
+
return `found in ${candidate.origins.map((origin) => `${origin.harness} (${origin.folder})`).join(', ')}`;
|
|
9
|
+
}
|
|
10
|
+
function pickOption(candidate, index, input) {
|
|
11
|
+
const tags = [...(index === 0 ? ['recommended'] : []), ...(candidate.name === input.current ? ['current'] : [])];
|
|
12
|
+
return { value: candidate.name, label: tags.length === 0 ? candidate.name : `${candidate.name} (${tags.join(', ')})`, hint: foundIn(candidate, input.selectedCount) };
|
|
13
|
+
}
|
|
14
|
+
export function buildOptions(input) {
|
|
15
|
+
const picks = input.ranked.slice(0, PICK_COUNT);
|
|
16
|
+
const options = picks.map((candidate, index) => pickOption(candidate, index, input));
|
|
17
|
+
const { current } = input;
|
|
18
|
+
if (current !== undefined && !picks.some((candidate) => candidate.name === current)) {
|
|
19
|
+
options.unshift({ value: current, label: `${current} (current)`, hint: 'configured, not found in the project skill folders' });
|
|
20
|
+
}
|
|
21
|
+
const none = input.role === 'snapshot' ? [{ value: NONE_VALUE, label: 'none', hint: 'no snapshot skill' }] : [];
|
|
22
|
+
return [...options, { value: OTHER_VALUE, label: 'type another', hint: 'enter a skill name' }, ...none];
|
|
23
|
+
}
|
|
24
|
+
export function initialValue(options, current) {
|
|
25
|
+
return current ?? options[0]?.value ?? NONE_VALUE;
|
|
26
|
+
}
|
|
27
|
+
export function optionFor(options, answer) {
|
|
28
|
+
const named = options.find((option) => option.value === answer);
|
|
29
|
+
if (named !== undefined)
|
|
30
|
+
return named.value;
|
|
31
|
+
const number = Number(answer);
|
|
32
|
+
return Number.isInteger(number) && number >= 1 && number <= options.length ? options[number - 1]?.value ?? null : null;
|
|
33
|
+
}
|
|
34
|
+
export function pickSpec(role, options, initial) {
|
|
35
|
+
const lines = options.map((option, index) => ` ${String(index + 1)}) ${option.label} — ${option.hint}`);
|
|
36
|
+
return {
|
|
37
|
+
line: `${TITLES[role]}:\n${lines.join('\n')}\nNumber or skill name (Enter keeps [${initial}]): `,
|
|
38
|
+
ui: { kind: 'select', message: TITLES[role], options: options.map((option) => ({ value: option.value, label: option.label, hint: option.hint })), initial },
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import type { PromptSpec } from './ui-prompt.js';
|
|
2
2
|
export declare function commandSpec(current: string | undefined): PromptSpec;
|
|
3
|
-
export declare
|
|
3
|
+
export declare const PERCENTAGE_OPTION = "percentage";
|
|
4
|
+
export type TriggerState = {
|
|
5
|
+
readonly zone: string;
|
|
6
|
+
readonly percentage: number | null;
|
|
7
|
+
};
|
|
8
|
+
export declare function triggerSpec(current: TriggerState): PromptSpec;
|
|
9
|
+
export declare function percentageSpec(current: number | null): PromptSpec;
|
|
4
10
|
export declare function resumeSpec(current: string | undefined): PromptSpec;
|
|
@@ -3,20 +3,30 @@ const NONE_WORD = 'none';
|
|
|
3
3
|
export function commandSpec(current) {
|
|
4
4
|
const shown = current ?? NONE_WORD;
|
|
5
5
|
return {
|
|
6
|
-
line: `Snapshot
|
|
7
|
-
ui: { kind: 'text', message: '
|
|
6
|
+
line: `Snapshot skill name (Enter keeps [${shown}], type none for no skill): `,
|
|
7
|
+
ui: { kind: 'text', message: 'Name of the skill the agent runs before a reset, without a leading slash (type none for no skill)', placeholder: `Enter keeps ${shown}` },
|
|
8
8
|
};
|
|
9
9
|
}
|
|
10
|
-
export
|
|
10
|
+
export const PERCENTAGE_OPTION = 'percentage';
|
|
11
|
+
export function triggerSpec(current) {
|
|
12
|
+
const shown = current.percentage === null ? current.zone : `${current.percentage}%`;
|
|
13
|
+
const options = [...SNAPSHOT_TRIGGER_ZONES.map((zone) => ({ value: zone, label: zone })), { value: PERCENTAGE_OPTION, label: 'Percentage of the context window' }];
|
|
11
14
|
return {
|
|
12
|
-
line: `Snapshot trigger ${SNAPSHOT_TRIGGER_ZONES.join('
|
|
13
|
-
ui: { kind: 'select', message: '
|
|
15
|
+
line: `Snapshot trigger ${SNAPSHOT_TRIGGER_ZONES.join(', ')} or a percentage 1-100 [${shown}]: `,
|
|
16
|
+
ui: { kind: 'select', message: 'Where the snapshot is requested: a zone or a percentage of the context window', options, initial: current.percentage === null ? current.zone : PERCENTAGE_OPTION },
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function percentageSpec(current) {
|
|
20
|
+
const shown = current === null ? 'none' : `${current}%`;
|
|
21
|
+
return {
|
|
22
|
+
line: `Trigger percentage 1-100 [${shown}]: `,
|
|
23
|
+
ui: { kind: 'text', message: 'Percentage of the context window that requests the snapshot (1-100)', placeholder: `Enter keeps ${shown}` },
|
|
14
24
|
};
|
|
15
25
|
}
|
|
16
26
|
export function resumeSpec(current) {
|
|
17
27
|
const shown = current ?? NONE_WORD;
|
|
18
28
|
return {
|
|
19
|
-
line: `Resume
|
|
20
|
-
ui: { kind: 'text', message: '
|
|
29
|
+
line: `Resume skill name (Enter keeps [${shown}]): `,
|
|
30
|
+
ui: { kind: 'text', message: 'Name of the skill that resumes the work in the new session', placeholder: `Enter keeps ${shown}` },
|
|
21
31
|
};
|
|
22
32
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CardRow } from '../output/card.js';
|
|
2
|
+
import type { AssistantFacts } from './types.js';
|
|
3
|
+
export declare function harnessRows(facts: AssistantFacts): CardRow[];
|
|
4
|
+
export declare function snapshotRows(facts: AssistantFacts): CardRow[];
|
|
5
|
+
export declare function optionRows(facts: AssistantFacts): CardRow[];
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { describeTrigger } from '../../core/services/trigger-text.js';
|
|
2
|
+
function enabledRow(facts, harness) {
|
|
3
|
+
const mode = facts.restartModes.find((fact) => fact.harness === harness)?.mode;
|
|
4
|
+
return { label: harness, value: mode === undefined ? 'enabled' : `enabled, restart: ${mode}`, tone: 'good' };
|
|
5
|
+
}
|
|
6
|
+
export function harnessRows(facts) {
|
|
7
|
+
const selected = facts.selected.map((harness) => enabledRow(facts, harness));
|
|
8
|
+
const excluded = facts.excluded.map((harness) => ({ label: harness, value: 'turned off', tone: 'muted' }));
|
|
9
|
+
if (selected.length > 0)
|
|
10
|
+
return [...selected, ...excluded];
|
|
11
|
+
return [{ label: 'none', value: 'no harness selected', tone: 'warn' }, ...excluded];
|
|
12
|
+
}
|
|
13
|
+
function triggerRow(facts) {
|
|
14
|
+
if (facts.triggerPercentage === null)
|
|
15
|
+
return { label: 'Trigger zone', value: facts.triggerZone };
|
|
16
|
+
return { label: 'Trigger', value: describeTrigger(facts) };
|
|
17
|
+
}
|
|
18
|
+
export function snapshotRows(facts) {
|
|
19
|
+
if (facts.snapshotCommand === null)
|
|
20
|
+
return [{ label: 'Command', value: 'none (only zone headers are injected)', tone: 'muted' }];
|
|
21
|
+
const resume = facts.resumeCommand === null ? { label: 'Resume', value: 'none', tone: 'muted' } : { label: 'Resume', value: facts.resumeCommand, tone: 'accent' };
|
|
22
|
+
const received = facts.invocations.map((fact) => ({ label: `In ${fact.harness}`, value: `${fact.command}${fact.resumeCommand === null ? '' : `, resume: ${fact.resumeCommand}`}`, tone: 'accent' }));
|
|
23
|
+
return [{ label: 'Command', value: facts.snapshotCommand, tone: 'accent' }, triggerRow(facts), resume, ...received];
|
|
24
|
+
}
|
|
25
|
+
function switchRow(label, isOn, words) {
|
|
26
|
+
return isOn ? { label, value: words[0], tone: 'good' } : { label, value: words[1], tone: 'muted' };
|
|
27
|
+
}
|
|
28
|
+
function restartRow(facts) {
|
|
29
|
+
if (!facts.restartOn)
|
|
30
|
+
return switchRow('Restart', false, ['on', 'off']);
|
|
31
|
+
return { label: 'Restart', value: `on, at most ${facts.restartLimit ?? 'the default number of'} consecutive restarts`, tone: 'good' };
|
|
32
|
+
}
|
|
33
|
+
function contextRow(facts) {
|
|
34
|
+
if (facts.contextSource === 'mod')
|
|
35
|
+
return { label: 'Context source', value: 'mod (your status line is left alone)', tone: 'good' };
|
|
36
|
+
if (facts.statuslineBridge === null)
|
|
37
|
+
return { label: 'Status line bridge', value: 'not applicable (Claude Code not selected)', tone: 'muted' };
|
|
38
|
+
return switchRow('Status line bridge', facts.statuslineBridge, ['yes', 'no']);
|
|
39
|
+
}
|
|
40
|
+
function gitIgnoreRow(facts) {
|
|
41
|
+
if (facts.gitIgnore === null)
|
|
42
|
+
return { label: 'Git ignore', value: 'not applicable (not a Git repository)', tone: 'muted' };
|
|
43
|
+
return switchRow('Git ignore', facts.gitIgnore, ['yes', 'no']);
|
|
44
|
+
}
|
|
45
|
+
export function optionRows(facts) {
|
|
46
|
+
return [restartRow(facts), contextRow(facts), switchRow('Debug mode', facts.debug, ['on', 'off']), gitIgnoreRow(facts)];
|
|
47
|
+
}
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
+
import { type Style } from '../output/style.js';
|
|
1
2
|
import type { AssistantFacts } from './types.js';
|
|
2
|
-
export declare function renderSummary(facts: AssistantFacts, command: readonly string[]): string[];
|
|
3
|
+
export declare function renderSummary(facts: AssistantFacts, command: readonly string[], style?: Style): string[];
|