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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ZONES } from '../contracts/zones.js';
|
|
2
2
|
import { SESSION_RESET_SIGNAL } from './reset-notice.js';
|
|
3
|
+
import { sentenceInvocation } from './skill-invocation.js';
|
|
3
4
|
const RESUME_PREFIX = '[ContextBrake resume v1]';
|
|
4
5
|
const HANDOFF_PATH = '.context-brake/handoff.md';
|
|
5
6
|
const GENERIC_ACTIONS = {
|
|
@@ -8,24 +9,29 @@ const GENERIC_ACTIONS = {
|
|
|
8
9
|
RED: 'finish or pause the current unit and tell the user what remains',
|
|
9
10
|
CRITICAL: 'stop starting new work; tell the user what remains',
|
|
10
11
|
};
|
|
11
|
-
export function
|
|
12
|
-
if (
|
|
12
|
+
export function isResetDue(reading, snapshot) {
|
|
13
|
+
if (snapshot.triggerPercentage !== undefined)
|
|
14
|
+
return reading.usagePercentage >= snapshot.triggerPercentage;
|
|
15
|
+
return reading.zone !== 'GREEN' && isAtOrAbove(reading.zone, snapshot.triggerZone);
|
|
16
|
+
}
|
|
17
|
+
export function zoneAction(zone, snapshot, options = {}) {
|
|
18
|
+
if (!isResetDue({ zone, usagePercentage: options.usagePercentage ?? 0 }, snapshot))
|
|
13
19
|
return GENERIC_ACTIONS[zone];
|
|
14
20
|
if (snapshot.command !== undefined)
|
|
15
|
-
return snapshotAction(zone, snapshot.command);
|
|
16
|
-
if (mode !== 'handoff')
|
|
21
|
+
return snapshotAction(zone, (options.invoke ?? sentenceInvocation)(snapshot.command));
|
|
22
|
+
if ((options.mode ?? 'off') !== 'handoff')
|
|
17
23
|
return GENERIC_ACTIONS[zone];
|
|
18
24
|
return `save handoff to ${HANDOFF_PATH}, end reply with ${SESSION_RESET_SIGNAL}`;
|
|
19
25
|
}
|
|
20
|
-
function snapshotAction(zone,
|
|
26
|
+
function snapshotAction(zone, invocation) {
|
|
21
27
|
const now = zone === 'CRITICAL' ? ' now' : '';
|
|
22
|
-
return `run "${
|
|
28
|
+
return `run "${invocation}"${now}, then end reply with ${SESSION_RESET_SIGNAL}`;
|
|
23
29
|
}
|
|
24
30
|
export function handoffResumeText(archivedPath) {
|
|
25
31
|
return `${RESUME_PREFIX} Read "${archivedPath}" and continue the previous work from it.`;
|
|
26
32
|
}
|
|
27
|
-
export function resumeText(snapshot) {
|
|
28
|
-
return snapshot.resumeCommand === undefined ? null : `${RESUME_PREFIX} Run "${snapshot.resumeCommand}" before continuing.`;
|
|
33
|
+
export function resumeText(snapshot, invoke = sentenceInvocation) {
|
|
34
|
+
return snapshot.resumeCommand === undefined ? null : `${RESUME_PREFIX} Run "${invoke(snapshot.resumeCommand)}" before continuing.`;
|
|
29
35
|
}
|
|
30
36
|
function isAtOrAbove(zone, trigger) {
|
|
31
37
|
return ZONES.indexOf(zone) >= ZONES.indexOf(trigger);
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { ANTIGRAVITY_SKILL_INVOCATION } from './skill-invocation.js';
|
|
1
2
|
import { asRecord, assetProjectRoot, characterLength, parsePayload, requireIdentifier, textValue } from '../common/runtime-support.js';
|
|
2
3
|
import { runProcessHook } from '../../runtime/process-hook-host.js';
|
|
3
4
|
import { ANTIGRAVITY_CAPABILITIES } from './capabilities.js';
|
|
4
5
|
import { antigravityPayloadSchema } from './schemas.js';
|
|
5
6
|
const HARNESS = 'antigravity-cli';
|
|
6
7
|
const ESTIMATION = { baselineTokens: 15000, tokensPerTurn: 150 };
|
|
7
|
-
export const antigravityDescriptor = { harness: HARNESS, capabilities: ANTIGRAVITY_CAPABILITIES, estimation: ESTIMATION, newSessionCommand: null };
|
|
8
|
+
export const antigravityDescriptor = { harness: HARNESS, capabilities: ANTIGRAVITY_CAPABILITIES, estimation: ESTIMATION, newSessionCommand: null, skillInvocation: ANTIGRAVITY_SKILL_INVOCATION };
|
|
8
9
|
function sessionOf(payload) {
|
|
9
10
|
return { harness: HARNESS, sessionId: requireIdentifier(payload.conversationId), agentId: null };
|
|
10
11
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const ANTIGRAVITY_SKILL_FOLDERS: readonly string[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const ANTIGRAVITY_SKILL_FOLDERS = ['.agents/skills'];
|
|
@@ -4,11 +4,13 @@ import {} from '../../../core/contracts/harness.js';
|
|
|
4
4
|
import { deriveSupportProfile } from '../../../core/services/support-service.js';
|
|
5
5
|
import { validateJsonDocument } from '../../storage/json-validator.js';
|
|
6
6
|
import { createAssetMissingFinding, createIntegrationMissingFinding, createInvalidConfigFinding } from '../common/diagnostic-helpers.js';
|
|
7
|
+
import { applyConfigOrigin, attachConfigOrigin } from '../common/config-origin.js';
|
|
7
8
|
import { pathExists } from '../common/path-helpers.js';
|
|
8
9
|
import { probeExecutableVersion } from '../common/version-probes.js';
|
|
9
10
|
import { diagnoseAutoRestart } from './auto-restart-diagnostics.js';
|
|
10
11
|
import { CLAUDE_CAPABILITIES } from './capabilities.js';
|
|
11
12
|
import { isTargetGroup } from './claude-merger.js';
|
|
13
|
+
import { detectContextSource } from './context-source.js';
|
|
12
14
|
import { CLAUDE_EXECUTABLES, detectClaude } from './detector.js';
|
|
13
15
|
import { CLAUDE_CONFIG_FILE, CLAUDE_HOOK_FILE, planClaudeInstall, planClaudeRemove } from './planner.js';
|
|
14
16
|
import { diagnoseStatusline } from './statusline-diagnostics.js';
|
|
@@ -24,11 +26,11 @@ export class ClaudeAdapter {
|
|
|
24
26
|
probeVersion(context) {
|
|
25
27
|
return probeExecutableVersion(context.runner, CLAUDE_EXECUTABLES);
|
|
26
28
|
}
|
|
27
|
-
planInstall(context) {
|
|
28
|
-
return planClaudeInstall(context);
|
|
29
|
+
async planInstall(context) {
|
|
30
|
+
return attachConfigOrigin(await planClaudeInstall(context, await detectContextSource(context)), { path: CLAUDE_CONFIG_FILE, manifest: context.manifest });
|
|
29
31
|
}
|
|
30
|
-
planRemove(context) {
|
|
31
|
-
return planClaudeRemove(context.projectRoot);
|
|
32
|
+
async planRemove(context) {
|
|
33
|
+
return applyConfigOrigin(await planClaudeRemove(context.projectRoot), { path: CLAUDE_CONFIG_FILE, manifest: context.manifest });
|
|
32
34
|
}
|
|
33
35
|
async diagnose(context) {
|
|
34
36
|
const findings = [];
|
|
@@ -30,7 +30,7 @@ export async function diagnoseAutoRestart(input) {
|
|
|
30
30
|
const normalized = input.version?.normalized ?? null;
|
|
31
31
|
if (normalized !== null && semver.lt(normalized, MODS_MINIMUM_CLAUDE_VERSION))
|
|
32
32
|
return [finding(tooOld(normalized))];
|
|
33
|
-
if (!(await
|
|
33
|
+
if (!(await isModInstalled(input.projectRoot)))
|
|
34
34
|
return [finding(FILES_DRIFTED)];
|
|
35
35
|
const log = await latestRestartLog(input.projectRoot, 'claude-code');
|
|
36
36
|
if (log === null)
|
|
@@ -41,7 +41,7 @@ export async function diagnoseAutoRestart(input) {
|
|
|
41
41
|
const skip = last !== undefined && last.code !== 'RESTARTED' ? [finding(lastSkip(last.code, last.at))] : [];
|
|
42
42
|
return [finding(READY), ...skip];
|
|
43
43
|
}
|
|
44
|
-
async function
|
|
44
|
+
export async function isModInstalled(projectRoot) {
|
|
45
45
|
const present = await Promise.all(MOD_FILES.map((path) => pathExists(resolve(projectRoot, path))));
|
|
46
46
|
if (present.includes(false))
|
|
47
47
|
return false;
|
|
@@ -4,4 +4,8 @@ export type AutoRestartPlan = {
|
|
|
4
4
|
readonly changes: readonly PlannedChange[];
|
|
5
5
|
readonly conflicts: readonly PlanConflict[];
|
|
6
6
|
};
|
|
7
|
-
export
|
|
7
|
+
export type AutoRestartOptions = {
|
|
8
|
+
readonly deleteFiles?: boolean;
|
|
9
|
+
readonly forModFirst?: boolean;
|
|
10
|
+
};
|
|
11
|
+
export declare function planAutoRestart(context: HarnessContext, base: readonly PlannedChange[], options?: AutoRestartOptions): Promise<AutoRestartPlan>;
|
|
@@ -16,7 +16,7 @@ async function installChanges(root) {
|
|
|
16
16
|
};
|
|
17
17
|
return Promise.all(MOD_FILES.map(async (path) => ({
|
|
18
18
|
path, realPath: await resolveChangeTarget(root, path), kind: 'create', owner: 'runtime_asset',
|
|
19
|
-
content: contents[path] ?? '', preview: { summary: 'Install the
|
|
19
|
+
content: contents[path] ?? '', preview: { summary: 'Install the ContextBrake mod' },
|
|
20
20
|
})));
|
|
21
21
|
}
|
|
22
22
|
async function isModified(context, path) {
|
|
@@ -27,7 +27,7 @@ async function isModified(context, path) {
|
|
|
27
27
|
return current !== recorded.sha256;
|
|
28
28
|
}
|
|
29
29
|
async function deletion(root, path) {
|
|
30
|
-
return { path, realPath: await resolveChangeTarget(root, path), kind: 'delete', owner: 'runtime_asset', content: null, preview: { summary: 'Delete the
|
|
30
|
+
return { path, realPath: await resolveChangeTarget(root, path), kind: 'delete', owner: 'runtime_asset', content: null, preview: { summary: 'Delete the ContextBrake mod' } };
|
|
31
31
|
}
|
|
32
32
|
async function removalChanges(context) {
|
|
33
33
|
const present = await Promise.all(MOD_FILES.map(async (path) => ((await pathExists(resolve(context.projectRoot, path))) ? path : undefined)));
|
|
@@ -53,7 +53,7 @@ async function settingsChange(root, base, wanted) {
|
|
|
53
53
|
if (text === base.text && base.existing === undefined)
|
|
54
54
|
return undefined;
|
|
55
55
|
const drop = !wanted && base.owned && isEmptySettings(text);
|
|
56
|
-
const summary = wanted ? 'Register the
|
|
56
|
+
const summary = wanted ? 'Register the ContextBrake mod' : 'Unregister the ContextBrake mod';
|
|
57
57
|
const kind = drop ? 'delete' : base.existing?.kind ?? (base.isNew ? 'create' : 'update');
|
|
58
58
|
return { path: CLAUDE_LOCAL_SETTINGS_FILE, realPath: await resolveChangeTarget(root, CLAUDE_LOCAL_SETTINGS_FILE), kind, owner: 'harness_entry', content: drop ? null : text, preview: drop ? { summary: 'Delete the local settings left empty' } : base.existing?.preview ?? { summary } };
|
|
59
59
|
}
|
|
@@ -66,9 +66,9 @@ async function fileChanges(context, wanted, deleteFiles) {
|
|
|
66
66
|
function isLocalSettingsDeletion(change) {
|
|
67
67
|
return change.path === CLAUDE_LOCAL_SETTINGS_FILE && change.kind === 'delete';
|
|
68
68
|
}
|
|
69
|
-
export async function planAutoRestart(context, base,
|
|
70
|
-
const wanted = context.autoRestart === true;
|
|
71
|
-
const files = await fileChanges(context, wanted, deleteFiles);
|
|
69
|
+
export async function planAutoRestart(context, base, options = {}) {
|
|
70
|
+
const wanted = context.autoRestart === true || options.forModFirst === true;
|
|
71
|
+
const files = await fileChanges(context, wanted, options.deleteFiles ?? true);
|
|
72
72
|
const deletesLocal = base.some(isLocalSettingsDeletion);
|
|
73
73
|
if (deletesLocal && !wanted)
|
|
74
74
|
return { changes: [...base, ...files.changes], conflicts: files.conflicts };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export const CLAUDE_CAPABILITIES = [
|
|
2
2
|
{ id: 'post_tool_telemetry', state: 'supported' },
|
|
3
3
|
{ id: 'session_boot', state: 'supported' },
|
|
4
|
-
{ id: 'context_usage', state: 'unknown', impact: 'Read from the session transcript, whose format is undocumented; falls back to an estimate. The context window comes from the status line bridge
|
|
4
|
+
{ id: 'context_usage', state: 'unknown', impact: 'Read from the session transcript, whose format is undocumented; falls back to an estimate. The context window comes from the mod that init installs on Claude Code 2.1.287 or later, or from the status line bridge on older versions; without either, it falls back to contextWindowCeiling.' },
|
|
5
5
|
{ id: 'auto_restart', state: 'unknown', impact: 'Opt-in through init --auto-restart; needs Claude Code 2.1.287 or later with mods enabled. Run doctor to check that the mod loads.' },
|
|
6
6
|
];
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { readdir, readFile, stat } from 'node:fs/promises';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
3
|
+
import { CONTEXT_READINGS_RELATIVE_DIR } from '../../../core/contracts/context-reading.js';
|
|
4
|
+
import { parseContextReading } from './mod-reading.js';
|
|
5
|
+
const READING_EXTENSION = '.json';
|
|
6
|
+
async function modifiedMilliseconds(path) {
|
|
7
|
+
return (await stat(path).catch(() => null))?.mtimeMs ?? 0;
|
|
8
|
+
}
|
|
9
|
+
async function newestFirst(directory) {
|
|
10
|
+
const names = (await readdir(directory).catch(() => [])).filter((name) => name.endsWith(READING_EXTENSION));
|
|
11
|
+
const dated = await Promise.all(names.map(async (name) => ({ path: join(directory, name), time: await modifiedMilliseconds(join(directory, name)) })));
|
|
12
|
+
return dated.sort((left, right) => right.time - left.time).map((entry) => entry.path);
|
|
13
|
+
}
|
|
14
|
+
export async function latestContextReading(projectRoot) {
|
|
15
|
+
for (const path of await newestFirst(resolve(projectRoot, CONTEXT_READINGS_RELATIVE_DIR, 'claude-code'))) {
|
|
16
|
+
const reading = parseContextReading(await readFile(path, 'utf8').catch(() => ''));
|
|
17
|
+
if (reading !== null)
|
|
18
|
+
return reading;
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { HarnessContext, StatuslineBridgeRequest } from '../../../core/contracts/adapter.js';
|
|
2
|
+
import type { VersionProbe } from '../../../core/contracts/harness.js';
|
|
3
|
+
export declare const CONTEXT_MOD_MINIMUM_CLAUDE_VERSION = "2.1.287";
|
|
4
|
+
export type ContextSource = 'mod' | 'bridge';
|
|
5
|
+
export type ContextSourceInput = {
|
|
6
|
+
readonly request: StatuslineBridgeRequest;
|
|
7
|
+
readonly version: VersionProbe;
|
|
8
|
+
};
|
|
9
|
+
export declare function decideContextSource(input: ContextSourceInput): ContextSource;
|
|
10
|
+
export declare function detectContextSource(context: HarnessContext): Promise<ContextSource>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import semver from 'semver';
|
|
2
|
+
import { NodeProcessRunner } from '../../process/node-process-runner.js';
|
|
3
|
+
import { probeExecutableVersion } from '../common/version-probes.js';
|
|
4
|
+
import { MODS_MINIMUM_CLAUDE_VERSION } from './auto-restart-diagnostics.js';
|
|
5
|
+
import { CLAUDE_EXECUTABLES } from './detector.js';
|
|
6
|
+
export const CONTEXT_MOD_MINIMUM_CLAUDE_VERSION = MODS_MINIMUM_CLAUDE_VERSION;
|
|
7
|
+
export function decideContextSource(input) {
|
|
8
|
+
if (input.request === 'install')
|
|
9
|
+
return 'bridge';
|
|
10
|
+
const version = input.version.normalized;
|
|
11
|
+
return version !== null && semver.gte(version, CONTEXT_MOD_MINIMUM_CLAUDE_VERSION) ? 'mod' : 'bridge';
|
|
12
|
+
}
|
|
13
|
+
export async function detectContextSource(context) {
|
|
14
|
+
const version = await probeExecutableVersion(context.runner ?? new NodeProcessRunner(), CLAUDE_EXECUTABLES);
|
|
15
|
+
return decideContextSource({ request: context.statuslineBridge ?? 'default', version });
|
|
16
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { contextReadingRelativePath, contextReadingSchema, isReadingSessionId, RUNTIME_IGNORE_CONTENT, RUNTIME_IGNORE_RELATIVE_PATH } from '../../../../core/contracts/context-reading.js';
|
|
2
|
+
import { lastRecordedCode } from './mod-log.js';
|
|
3
|
+
import { reportSafely } from './restart-flow.js';
|
|
4
|
+
const READING_HARNESS = 'claude-code';
|
|
5
|
+
const READING_VERSION = 1;
|
|
6
|
+
const PERCENTAGE_MAXIMUM = 100;
|
|
7
|
+
const UNREADABLE_CODE = 'ERROR_INTERNAL';
|
|
8
|
+
function positiveIntegerOrNull(value) {
|
|
9
|
+
return typeof value === 'number' && Number.isInteger(value) && value > 0 ? value : null;
|
|
10
|
+
}
|
|
11
|
+
function countOrNull(value) {
|
|
12
|
+
return typeof value === 'number' && Number.isInteger(value) && value >= 0 ? value : null;
|
|
13
|
+
}
|
|
14
|
+
function percentageOrNull(value) {
|
|
15
|
+
return typeof value === 'number' && value >= 0 && value <= PERCENTAGE_MAXIMUM ? value : null;
|
|
16
|
+
}
|
|
17
|
+
function readingOf(context, at) {
|
|
18
|
+
return { v: READING_VERSION, at, windowTokens: positiveIntegerOrNull(context.window), inputTokens: countOrNull(context.tokens), usedPercentage: percentageOrNull(context.percent) };
|
|
19
|
+
}
|
|
20
|
+
async function isAlreadyPublished($, path, reading) {
|
|
21
|
+
if (!(await $.fs.exists(path)))
|
|
22
|
+
return false;
|
|
23
|
+
try {
|
|
24
|
+
const previous = contextReadingSchema.safeParse(JSON.parse(String(await $.fs.read(path))));
|
|
25
|
+
return previous.success && previous.data.windowTokens === reading.windowTokens && previous.data.inputTokens === reading.inputTokens;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async function ensureRuntimeIgnore($, root) {
|
|
32
|
+
const path = `${root}/${RUNTIME_IGNORE_RELATIVE_PATH}`;
|
|
33
|
+
if (!(await $.fs.exists(path)))
|
|
34
|
+
await $.fs.write(path, RUNTIME_IGNORE_CONTENT);
|
|
35
|
+
}
|
|
36
|
+
async function reportOnce($) {
|
|
37
|
+
try {
|
|
38
|
+
if ((await lastRecordedCode($)) === UNREADABLE_CODE)
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
await reportSafely($, UNREADABLE_CODE);
|
|
45
|
+
}
|
|
46
|
+
async function publishChanged($) {
|
|
47
|
+
const sessionId = await $.session.id();
|
|
48
|
+
if (!isReadingSessionId(sessionId))
|
|
49
|
+
return;
|
|
50
|
+
const reading = readingOf((await $.session.usage()).context, new Date(await $.clock.now()).toISOString());
|
|
51
|
+
if (reading.windowTokens === null)
|
|
52
|
+
return reportOnce($);
|
|
53
|
+
const root = await $.session.root();
|
|
54
|
+
const path = `${root}/${contextReadingRelativePath(READING_HARNESS, sessionId)}`;
|
|
55
|
+
if (await isAlreadyPublished($, path, reading))
|
|
56
|
+
return;
|
|
57
|
+
await ensureRuntimeIgnore($, root);
|
|
58
|
+
await $.fs.write(path, `${JSON.stringify(reading)}\n`);
|
|
59
|
+
}
|
|
60
|
+
export async function publishContextReading($) {
|
|
61
|
+
try {
|
|
62
|
+
await publishChanged($);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
await reportOnce($);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
export type ModUsageContext = {
|
|
2
|
+
readonly tokens?: number | undefined;
|
|
3
|
+
readonly window?: number | undefined;
|
|
4
|
+
readonly percent?: number | undefined;
|
|
5
|
+
};
|
|
6
|
+
export type ModUsage = {
|
|
7
|
+
readonly context: ModUsageContext;
|
|
8
|
+
};
|
|
1
9
|
export type ModHost = {
|
|
2
10
|
readonly fs: {
|
|
3
11
|
read(path: string): Promise<unknown>;
|
|
@@ -22,6 +30,7 @@ export type ModHost = {
|
|
|
22
30
|
version(): Promise<{
|
|
23
31
|
readonly version: string;
|
|
24
32
|
}>;
|
|
33
|
+
usage(): Promise<ModUsage>;
|
|
25
34
|
};
|
|
26
35
|
readonly command: {
|
|
27
36
|
run(input: {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const MOD_VERSION = "1.
|
|
1
|
+
export declare const MOD_VERSION = "1.1.0";
|
|
2
2
|
export declare const MOD_LOG_DIR = ".context-brake/runtime/restart/claude-code";
|
|
3
3
|
export declare const CONFIG_FILE = "context-brake.config.json";
|
|
4
4
|
export declare const PERSON_PROMPT_ORIGINS: readonly string[];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RESTART_LOG_RELATIVE_DIR } from '../../../../core/contracts/restart-log.js';
|
|
2
|
-
export const MOD_VERSION = '1.
|
|
2
|
+
export const MOD_VERSION = '1.1.0';
|
|
3
3
|
export const MOD_LOG_DIR = `${RESTART_LOG_RELATIVE_DIR}/claude-code`;
|
|
4
4
|
export const CONFIG_FILE = 'context-brake.config.json';
|
|
5
5
|
export const PERSON_PROMPT_ORIGINS = ['composer', 'bridge'];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { RestartReasonCode } from '../../../../core/contracts/auto-restart.js';
|
|
2
2
|
import type { ModHost } from './host.js';
|
|
3
|
+
export declare function lastRecordedCode($: ModHost): Promise<RestartReasonCode | undefined>;
|
|
3
4
|
export declare function recordLoaded($: ModHost): Promise<void>;
|
|
4
5
|
export declare function recordDecision($: ModHost, code: RestartReasonCode): Promise<void>;
|
|
@@ -21,6 +21,9 @@ async function readLog($, path) {
|
|
|
21
21
|
async function writeLog($, path, log) {
|
|
22
22
|
await $.fs.write(path, `${JSON.stringify(log, null, 2)}\n`);
|
|
23
23
|
}
|
|
24
|
+
export async function lastRecordedCode($) {
|
|
25
|
+
return (await readLog($, await logPath($))).records.at(-1)?.code;
|
|
26
|
+
}
|
|
24
27
|
export async function recordLoaded($) {
|
|
25
28
|
const path = await logPath($);
|
|
26
29
|
const log = await readLog($, path);
|
|
@@ -1,18 +1,23 @@
|
|
|
1
|
+
import { publishContextReading } from './context-reading.js';
|
|
1
2
|
import { onPromptSubmit, onSessionStart, onToolCall, onTurnComplete, onTurnStart } from './hooks.js';
|
|
2
3
|
export function register(on) {
|
|
3
4
|
on('session.start', async ($, e, next) => {
|
|
4
5
|
await onSessionStart($);
|
|
6
|
+
await publishContextReading($);
|
|
5
7
|
return next(e);
|
|
6
8
|
});
|
|
7
9
|
on('turn.start', async ($, e, next) => {
|
|
8
10
|
await onTurnStart($);
|
|
11
|
+
await publishContextReading($);
|
|
9
12
|
return next(e);
|
|
10
13
|
});
|
|
11
|
-
on('tool.call', async (
|
|
14
|
+
on('tool.call', async ($, e, next) => {
|
|
12
15
|
onToolCall();
|
|
16
|
+
await publishContextReading($);
|
|
13
17
|
return next(e);
|
|
14
18
|
});
|
|
15
19
|
on('turn.complete', async ($, e, next) => {
|
|
20
|
+
await publishContextReading($);
|
|
16
21
|
await onTurnComplete($, e);
|
|
17
22
|
return next(e);
|
|
18
23
|
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type ContextReading } from '../../../core/contracts/context-reading.js';
|
|
2
|
+
import type { MeasuredUsage } from '../../../core/services/session-zone.js';
|
|
3
|
+
import type { TranscriptUsage } from './transcript-usage.js';
|
|
4
|
+
export declare function parseContextReading(text: string): ContextReading | null;
|
|
5
|
+
export declare function readContextReading(sessionId: string | undefined): Promise<ContextReading | null>;
|
|
6
|
+
export declare function freshestMeasured(transcript: TranscriptUsage | null, reading: ContextReading | null): MeasuredUsage | undefined;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { CONTEXT_READING_MAXIMUM_CHARACTERS, contextReadingRelativePath, contextReadingSchema, isReadingSessionId } from '../../../core/contracts/context-reading.js';
|
|
4
|
+
import { resolveClaudeProjectRoot } from './project-root.js';
|
|
5
|
+
export function parseContextReading(text) {
|
|
6
|
+
if (text.length > CONTEXT_READING_MAXIMUM_CHARACTERS)
|
|
7
|
+
return null;
|
|
8
|
+
try {
|
|
9
|
+
const parsed = contextReadingSchema.safeParse(JSON.parse(text));
|
|
10
|
+
return parsed.success ? parsed.data : null;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export async function readContextReading(sessionId) {
|
|
17
|
+
if (!isReadingSessionId(sessionId))
|
|
18
|
+
return null;
|
|
19
|
+
const path = resolve(await resolveClaudeProjectRoot(), contextReadingRelativePath('claude-code', sessionId));
|
|
20
|
+
return parseContextReading(await readFile(path, 'utf8').catch(() => ''));
|
|
21
|
+
}
|
|
22
|
+
function isNotOlder(reading, transcript) {
|
|
23
|
+
return transcript === null || Date.parse(reading.at) >= Date.parse(transcript.at);
|
|
24
|
+
}
|
|
25
|
+
function chooseTokens(transcript, reading) {
|
|
26
|
+
if (reading !== null && reading.inputTokens !== null && isNotOlder(reading, transcript))
|
|
27
|
+
return { tokens: reading.inputTokens, at: reading.at };
|
|
28
|
+
if (transcript === null)
|
|
29
|
+
return { tokens: null, at: reading?.at };
|
|
30
|
+
return { tokens: transcript.tokens, at: transcript.at };
|
|
31
|
+
}
|
|
32
|
+
export function freshestMeasured(transcript, reading) {
|
|
33
|
+
if (transcript === null && reading === null)
|
|
34
|
+
return undefined;
|
|
35
|
+
const choice = chooseTokens(transcript, reading);
|
|
36
|
+
return { tokens: choice.tokens, contextWindow: reading?.windowTokens ?? null, at: choice.at };
|
|
37
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { AdapterPlan, HarnessContext } from '../../../core/contracts/adapter.js';
|
|
2
2
|
import type { ManagedEntry } from '../../../core/contracts/manifest.js';
|
|
3
|
+
import type { ContextSource } from './context-source.js';
|
|
3
4
|
export declare const CLAUDE_CONFIG_FILE = ".claude/settings.json";
|
|
4
5
|
export declare const CLAUDE_HOOK_FILE = ".claude/hooks/context-brake.mjs";
|
|
5
6
|
export declare function buildClaudeEntries(): ManagedEntry[];
|
|
6
|
-
export declare function planClaudeInstall(context: HarnessContext): Promise<AdapterPlan>;
|
|
7
|
+
export declare function planClaudeInstall(context: HarnessContext, source?: ContextSource): Promise<AdapterPlan>;
|
|
7
8
|
export declare function planClaudeRemove(projectRoot: string): Promise<AdapterPlan>;
|
|
@@ -20,7 +20,7 @@ export function buildClaudeEntries() {
|
|
|
20
20
|
function invalidConfigPlan(detail) {
|
|
21
21
|
return { harness: 'claude-code', changes: [], conflicts: [{ path: CLAUDE_CONFIG_FILE, code: 'INVALID_HARNESS_CONFIG', detail }], entries: [] };
|
|
22
22
|
}
|
|
23
|
-
export async function planClaudeInstall(context) {
|
|
23
|
+
export async function planClaudeInstall(context, source = 'bridge') {
|
|
24
24
|
const projectRoot = context.projectRoot;
|
|
25
25
|
const realConfig = await resolveChangeTarget(projectRoot, CLAUDE_CONFIG_FILE);
|
|
26
26
|
let content = '{\n}\n';
|
|
@@ -42,13 +42,13 @@ export async function planClaudeInstall(context) {
|
|
|
42
42
|
return invalidConfigPlan(error instanceof Error ? error.message : String(error));
|
|
43
43
|
}
|
|
44
44
|
const assetContent = await loadRuntimeAsset('claude-code-hook.mjs');
|
|
45
|
-
const statusline = await planStatuslineInstall(context);
|
|
45
|
+
const statusline = await planStatuslineInstall(context, source);
|
|
46
46
|
const changes = [
|
|
47
47
|
{ path: CLAUDE_CONFIG_FILE, realPath: realConfig, kind: 'update', owner: 'harness_entry', content: updated, preview: { summary: 'Register Claude Code hooks' } },
|
|
48
48
|
{ path: CLAUDE_HOOK_FILE, realPath: await resolveChangeTarget(projectRoot, CLAUDE_HOOK_FILE), kind: 'create', owner: 'runtime_asset', content: assetContent, preview: { summary: 'Install ContextBrake hook script' } },
|
|
49
49
|
...statusline.changes,
|
|
50
50
|
];
|
|
51
|
-
const autoRestart = await planAutoRestart(context, changes);
|
|
51
|
+
const autoRestart = await planAutoRestart(context, changes, { forModFirst: source === 'mod' });
|
|
52
52
|
return { harness: 'claude-code', changes: [...autoRestart.changes], conflicts: [...statusline.conflicts, ...autoRestart.conflicts], entries: buildClaudeEntries(), findings: statusline.findings };
|
|
53
53
|
}
|
|
54
54
|
export async function planClaudeRemove(projectRoot) {
|
|
@@ -67,6 +67,6 @@ export async function planClaudeRemove(projectRoot) {
|
|
|
67
67
|
changes.push({ path: CLAUDE_HOOK_FILE, realPath: realHook, kind: 'delete', owner: 'runtime_asset', content: null, preview: { summary: 'Delete ContextBrake hook script' } });
|
|
68
68
|
const statusline = await planStatuslineRemove(projectRoot);
|
|
69
69
|
changes.push(...statusline.changes);
|
|
70
|
-
const autoRestart = await planAutoRestart({ projectRoot, autoRestart: false }, changes, false);
|
|
70
|
+
const autoRestart = await planAutoRestart({ projectRoot, autoRestart: false }, changes, { deleteFiles: false });
|
|
71
71
|
return { harness: 'claude-code', changes: [...autoRestart.changes], conflicts: [...statusline.conflicts, ...autoRestart.conflicts], entries: buildClaudeEntries(), assetPaths: [CLAUDE_HOOK_FILE, STATUSLINE_BRIDGE_FILE, ...MOD_FILES] };
|
|
72
72
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { assetProjectRoot, projectRootFromEnvironment } from '../common/runtime-support.js';
|
|
2
|
+
export const PROJECT_DIRECTORY_VARIABLE = 'CLAUDE_PROJECT_DIR';
|
|
3
|
+
export async function resolveClaudeProjectRoot() {
|
|
4
|
+
return projectRootFromEnvironment([PROJECT_DIRECTORY_VARIABLE]) ?? await assetProjectRoot();
|
|
5
|
+
}
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
+
import { CLAUDE_SKILL_INVOCATION } from './skill-invocation.js';
|
|
1
2
|
import { failureDetail, recordRuntimeFailure } from '../../../core/services/failure-policy.js';
|
|
2
|
-
import { asRecord,
|
|
3
|
+
import { asRecord, characterLength, parsePayload, requireIdentifier, textValue } from '../common/runtime-support.js';
|
|
3
4
|
import { runProcessHook } from '../../runtime/process-hook-host.js';
|
|
4
5
|
import { CLAUDE_CAPABILITIES } from './capabilities.js';
|
|
6
|
+
import { freshestMeasured, readContextReading } from './mod-reading.js';
|
|
7
|
+
import { resolveClaudeProjectRoot } from './project-root.js';
|
|
5
8
|
import { claudePayloadSchema } from './schemas.js';
|
|
6
9
|
import { readTranscriptUsage } from './transcript-usage.js';
|
|
7
10
|
const HARNESS = 'claude-code';
|
|
8
11
|
const ESTIMATION = { baselineTokens: 15000, tokensPerTurn: 150 };
|
|
9
12
|
const WRITE_TOOLS = ['Write', 'Edit', 'MultiEdit', 'NotebookEdit'];
|
|
10
13
|
const SKILL_TOOL = 'Skill';
|
|
11
|
-
export const claudeDescriptor = { harness: HARNESS, capabilities: CLAUDE_CAPABILITIES, estimation: ESTIMATION, newSessionCommand: '/clear' };
|
|
14
|
+
export const claudeDescriptor = { harness: HARNESS, capabilities: CLAUDE_CAPABILITIES, estimation: ESTIMATION, newSessionCommand: '/clear', skillInvocation: CLAUDE_SKILL_INVOCATION };
|
|
12
15
|
function sessionOf(payload) {
|
|
13
16
|
return { harness: HARNESS, sessionId: requireIdentifier(payload.session_id), agentId: payload.agent_id ?? null };
|
|
14
17
|
}
|
|
@@ -49,18 +52,20 @@ export async function mapClaudeInput(eventName, payload, errors) {
|
|
|
49
52
|
if (eventName !== 'PostToolUse')
|
|
50
53
|
return {};
|
|
51
54
|
const data = parsePayload(claudePayloadSchema, payload);
|
|
52
|
-
const measured = data.agent_id === undefined ? await measuredUsage({ path: data.transcript_path, eventName, errors }) : undefined;
|
|
55
|
+
const measured = data.agent_id === undefined ? await measuredUsage({ path: data.transcript_path, sessionId: data.session_id, eventName, errors }) : undefined;
|
|
53
56
|
const usage = measured === undefined ? {} : { measured };
|
|
54
57
|
return { ...usage, observedCharacters: characterLength(data.tool_input) + characterLength(data.tool_response) };
|
|
55
58
|
}
|
|
56
59
|
async function measuredUsage(input) {
|
|
60
|
+
return freshestMeasured(await transcriptUsage(input), await readContextReading(input.sessionId));
|
|
61
|
+
}
|
|
62
|
+
async function transcriptUsage(input) {
|
|
57
63
|
try {
|
|
58
|
-
|
|
59
|
-
return usage === null ? undefined : { tokens: usage.tokens, contextWindow: null, at: usage.at };
|
|
64
|
+
return await readTranscriptUsage(input.path);
|
|
60
65
|
}
|
|
61
66
|
catch (error) {
|
|
62
67
|
await recordRuntimeFailure(input.errors, { harness: HARNESS, event: input.eventName, code: 'UNEXPECTED', detail: failureDetail(error) });
|
|
63
|
-
return
|
|
68
|
+
return null;
|
|
64
69
|
}
|
|
65
70
|
}
|
|
66
71
|
export function renderClaudeDecision(decision, eventName) {
|
|
@@ -75,7 +80,7 @@ export const claudeAdapter = {
|
|
|
75
80
|
mapEvent: mapClaudeEvent,
|
|
76
81
|
mapInput: mapClaudeInput,
|
|
77
82
|
renderDecision: renderClaudeDecision,
|
|
78
|
-
resolveProjectRoot:
|
|
83
|
+
resolveProjectRoot: resolveClaudeProjectRoot,
|
|
79
84
|
};
|
|
80
85
|
export function runClaudeCodeHook() {
|
|
81
86
|
return runProcessHook(claudeAdapter);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const CLAUDE_SKILL_FOLDERS: readonly string[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const CLAUDE_SKILL_FOLDERS = ['.claude/skills'];
|
|
@@ -3,12 +3,25 @@ import { join, resolve } from 'node:path';
|
|
|
3
3
|
import { parseLedgerLines } from '../../../core/contracts/session-ledger.js';
|
|
4
4
|
import { summarizeStatusline } from '../../../core/services/statusline-summary.js';
|
|
5
5
|
import { LEDGER_FILE_EXTENSION, sessionsDirectory } from '../../runtime/runtime-paths.js';
|
|
6
|
+
import { isModInstalled } from './auto-restart-diagnostics.js';
|
|
7
|
+
import { latestContextReading } from './context-readings-scan.js';
|
|
6
8
|
import { CLAUDE_LOCAL_SETTINGS_FILE, commandOf, readSettings } from './statusline-settings.js';
|
|
7
9
|
import { readStatuslineState } from './statusline-state.js';
|
|
8
10
|
const ALL_LINES = -1;
|
|
9
11
|
export async function readClaudeContextWindow(projectRoot) {
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
+
const reading = await latestContextReading(projectRoot);
|
|
13
|
+
const lastWindowTokens = reading?.windowTokens ?? await lastRecordedWindow(projectRoot);
|
|
14
|
+
return { bridge: await bridgeState(projectRoot), mod: await modState(projectRoot, reading), source: sourceOf(reading, lastWindowTokens), lastWindowTokens };
|
|
15
|
+
}
|
|
16
|
+
function sourceOf(reading, lastWindowTokens) {
|
|
17
|
+
if (reading !== null)
|
|
18
|
+
return 'mod';
|
|
19
|
+
return lastWindowTokens === null ? 'contextWindowCeiling' : 'statusline';
|
|
20
|
+
}
|
|
21
|
+
async function modState(projectRoot, reading) {
|
|
22
|
+
if (reading !== null)
|
|
23
|
+
return 'reporting';
|
|
24
|
+
return (await isModInstalled(projectRoot)) ? 'installed' : 'absent';
|
|
12
25
|
}
|
|
13
26
|
export async function localStatuslineCommand(projectRoot) {
|
|
14
27
|
const local = await readSettings(resolve(projectRoot, CLAUDE_LOCAL_SETTINGS_FILE));
|