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,12 +1,12 @@
|
|
|
1
1
|
import { readFile, realpath, stat } from 'node:fs/promises';
|
|
2
|
-
import {
|
|
2
|
+
import { relative, resolve } from 'node:path';
|
|
3
3
|
import { resolveChangeTarget } from '../common/change-target.js';
|
|
4
4
|
import { NodeProcessRunner } from '../../process/node-process-runner.js';
|
|
5
5
|
import { normalizeSeparators } from '../../storage/path-boundary.js';
|
|
6
|
-
import { getUserHome } from '../common/path-helpers.js';
|
|
7
6
|
import { STATUSLINE_PIPE_FLAG } from './statusline-bridge.js';
|
|
8
7
|
import { lastRecordedShell, localStatuslineCommand } from './statusline-context-window.js';
|
|
9
8
|
import { CLAUDE_LOCAL_SETTINGS_FILE, CLAUDE_SETTINGS_FILE, firstPreviousStatusline, readSettings, STATUSLINE_BRIDGE_FILE, statuslineOf } from './statusline-settings.js';
|
|
9
|
+
import { claudeUserSettingsPath } from './user-settings.js';
|
|
10
10
|
import { parseStatuslineState, STATUSLINE_STATE_FILE } from './statusline-state.js';
|
|
11
11
|
const GIT_TIMEOUT_MILLISECONDS = 2000;
|
|
12
12
|
const GIT_NOT_IGNORED_EXIT_CODE = 1;
|
|
@@ -47,7 +47,7 @@ async function isScriptPresent(state) {
|
|
|
47
47
|
}
|
|
48
48
|
async function currentPreviousCommand(context, state) {
|
|
49
49
|
const project = await readSettings(resolve(context.projectRoot, CLAUDE_SETTINGS_FILE));
|
|
50
|
-
const user = await readSettings(
|
|
50
|
+
const user = await readSettings(claudeUserSettingsPath(context.userHome));
|
|
51
51
|
const previous = firstPreviousStatusline([{ source: 'local', value: state.previousLocal ?? undefined }, { source: 'project', value: statuslineOf(project) }, { source: 'user', value: statuslineOf(user) }]);
|
|
52
52
|
return previous?.command ?? null;
|
|
53
53
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { resolveChangeTarget } from '../common/change-target.js';
|
|
3
|
+
import { pathExists } from '../common/path-helpers.js';
|
|
4
|
+
import { planStatuslineOptOut, softenDefaultConflict } from './statusline-default.js';
|
|
5
|
+
import { planStatuslineRestore } from './statusline-restore.js';
|
|
6
|
+
import { STATUSLINE_BRIDGE_FILE } from './statusline-settings.js';
|
|
7
|
+
async function bridgeScriptDeletion(projectRoot) {
|
|
8
|
+
if (!(await pathExists(resolve(projectRoot, STATUSLINE_BRIDGE_FILE))))
|
|
9
|
+
return [];
|
|
10
|
+
const realPath = await resolveChangeTarget(projectRoot, STATUSLINE_BRIDGE_FILE);
|
|
11
|
+
return [{ path: STATUSLINE_BRIDGE_FILE, realPath, kind: 'delete', owner: 'runtime_asset', content: null, preview: { summary: 'Delete the status line bridge script, the mod now reads the context' } }];
|
|
12
|
+
}
|
|
13
|
+
async function restoration(context) {
|
|
14
|
+
if (context.statuslineBridge === 'remove')
|
|
15
|
+
return planStatuslineOptOut(context.projectRoot);
|
|
16
|
+
return softenDefaultConflict(await planStatuslineRestore(context.projectRoot));
|
|
17
|
+
}
|
|
18
|
+
export async function planModFirstStatusline(context) {
|
|
19
|
+
const restore = await restoration(context);
|
|
20
|
+
if (restore.conflicts.length > 0)
|
|
21
|
+
return restore;
|
|
22
|
+
return { ...restore, changes: [...restore.changes, ...await bridgeScriptDeletion(context.projectRoot)] };
|
|
23
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { HarnessContext } from '../../../core/contracts/adapter.js';
|
|
2
|
+
import type { ContextSource } from './context-source.js';
|
|
2
3
|
import { type StatuslinePlan } from './statusline-restore.js';
|
|
3
|
-
export declare function planStatuslineInstall(context: HarnessContext): Promise<StatuslinePlan>;
|
|
4
|
+
export declare function planStatuslineInstall(context: HarnessContext, source: ContextSource): Promise<StatuslinePlan>;
|
|
4
5
|
export declare function planStatuslineRemove(projectRoot: string): Promise<StatuslinePlan>;
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import { realpath } from 'node:fs/promises';
|
|
2
|
-
import {
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
3
|
import { setJsonProperty } from '../../storage/json-document-editor.js';
|
|
4
4
|
import { resolveChangeTarget } from '../common/change-target.js';
|
|
5
5
|
import { loadRuntimeAsset } from '../common/runtime-assets.js';
|
|
6
|
-
import { getUserHome } from '../common/path-helpers.js';
|
|
7
6
|
import { asRecord } from '../common/runtime-support.js';
|
|
8
7
|
import { clearStatuslineOptOut, hasStatuslineOptOut, planStatuslineOptOut, softenDefaultConflict } from './statusline-default.js';
|
|
8
|
+
import { planModFirstStatusline } from './statusline-migration.js';
|
|
9
9
|
import { planStatuslineRestore, statuslinePlanConflict } from './statusline-restore.js';
|
|
10
10
|
import { bridgeCommand, CLAUDE_LOCAL_SETTINGS_FILE, CLAUDE_SETTINGS_FILE, firstPreviousStatusline, isBridgeStatusline, readSettings, STATUSLINE_BRIDGE_FILE, STATUSLINE_KEY, statuslineOf, statuslineOptions, toCommandRoot } from './statusline-settings.js';
|
|
11
|
+
import { claudeUserSettingsPath } from './user-settings.js';
|
|
11
12
|
import { readStatuslineState, serializeStatuslineState, STATUSLINE_STATE_FILE } from './statusline-state.js';
|
|
12
13
|
const EMPTY_SETTINGS_TEXT = '{\n}\n';
|
|
13
14
|
const EMPTY_PLAN = { changes: [], conflicts: [], findings: [] };
|
|
14
|
-
export async function planStatuslineInstall(context) {
|
|
15
|
+
export async function planStatuslineInstall(context, source) {
|
|
16
|
+
if (source === 'mod')
|
|
17
|
+
return planModFirstStatusline(context);
|
|
15
18
|
const plan = context.statuslineBridge === 'remove' ? await planStatuslineOptOut(context.projectRoot) : await requestedEntry(context);
|
|
16
19
|
return { ...plan, changes: [await bridgeAssetChange(context.projectRoot), ...plan.changes] };
|
|
17
20
|
}
|
|
@@ -51,7 +54,7 @@ async function resolveOrigin(context, local, state) {
|
|
|
51
54
|
const project = await readSettings(resolve(context.projectRoot, CLAUDE_SETTINGS_FILE));
|
|
52
55
|
if (project.kind === 'invalid')
|
|
53
56
|
return statuslinePlanConflict(CLAUDE_SETTINGS_FILE, project.detail);
|
|
54
|
-
const user = await readSettings(
|
|
57
|
+
const user = await readSettings(claudeUserSettingsPath(context.userHome));
|
|
55
58
|
const localValue = local.kind === 'valid' && !isBridgeStatusline(local.statusLine) ? local.statusLine : undefined;
|
|
56
59
|
const previous = firstPreviousStatusline([{ source: 'local', value: localValue }, { source: 'project', value: statuslineOf(project) }, { source: 'user', value: statuslineOf(user) }]);
|
|
57
60
|
const previousLocal = asRecord(localValue);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { getUserHome } from '../common/path-helpers.js';
|
|
3
|
+
import { CLAUDE_SETTINGS_FILE } from './statusline-settings.js';
|
|
4
|
+
export const CLAUDE_CONFIG_DIR_VARIABLE = 'CLAUDE_CONFIG_DIR';
|
|
5
|
+
const USER_SETTINGS_FILE_NAME = 'settings.json';
|
|
6
|
+
export function claudeUserSettingsPath(injectedUserHome) {
|
|
7
|
+
const configDirectory = process.env[CLAUDE_CONFIG_DIR_VARIABLE]?.trim();
|
|
8
|
+
if (injectedUserHome === undefined && configDirectory !== undefined && configDirectory !== '')
|
|
9
|
+
return join(configDirectory, USER_SETTINGS_FILE_NAME);
|
|
10
|
+
return join(getUserHome(injectedUserHome), CLAUDE_SETTINGS_FILE);
|
|
11
|
+
}
|
|
@@ -3,6 +3,7 @@ import { resolve } from 'node:path';
|
|
|
3
3
|
import { deriveSupportProfile } from '../../../core/services/support-service.js';
|
|
4
4
|
import { validateJsonDocument } from '../../storage/json-validator.js';
|
|
5
5
|
import { createAssetMissingFinding, createCodexRootNotGitWarning, createIntegrationMissingFinding, createInvalidConfigFinding, createLimitationFinding, } from '../common/diagnostic-helpers.js';
|
|
6
|
+
import { applyConfigOrigin, attachConfigOrigin } from '../common/config-origin.js';
|
|
6
7
|
import { pathExists } from '../common/path-helpers.js';
|
|
7
8
|
import { probeExecutableVersion } from '../common/version-probes.js';
|
|
8
9
|
import { CODEX_CAPABILITIES } from './capabilities.js';
|
|
@@ -47,11 +48,11 @@ export class CodexAdapter {
|
|
|
47
48
|
probeVersion(context) {
|
|
48
49
|
return probeExecutableVersion(context.runner, CODEX_EXECUTABLES);
|
|
49
50
|
}
|
|
50
|
-
planInstall(context) {
|
|
51
|
-
return planCodexInstall(context.projectRoot);
|
|
51
|
+
async planInstall(context) {
|
|
52
|
+
return attachConfigOrigin(await planCodexInstall(context.projectRoot), { path: CODEX_CONFIG_FILE, manifest: context.manifest });
|
|
52
53
|
}
|
|
53
|
-
planRemove(context) {
|
|
54
|
-
return planCodexRemove(context.projectRoot);
|
|
54
|
+
async planRemove(context) {
|
|
55
|
+
return applyConfigOrigin(await planCodexRemove(context.projectRoot), { path: CODEX_CONFIG_FILE, manifest: context.manifest });
|
|
55
56
|
}
|
|
56
57
|
async diagnose(context) {
|
|
57
58
|
const findings = [];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CODEX_SKILL_INVOCATION } from './skill-invocation.js';
|
|
1
2
|
import { failureDetail, recordRuntimeFailure } from '../../../core/services/failure-policy.js';
|
|
2
3
|
import { asRecord, assetProjectRoot, characterLength, parsePayload, requireIdentifier, textValue } from '../common/runtime-support.js';
|
|
3
4
|
import { runProcessHook } from '../../runtime/process-hook-host.js';
|
|
@@ -7,7 +8,7 @@ import { codexPayloadSchema } from './schemas.js';
|
|
|
7
8
|
const HARNESS = 'codex-cli';
|
|
8
9
|
const ESTIMATION = { baselineTokens: 15000, tokensPerTurn: 150 };
|
|
9
10
|
const PATCH_PATH_PREFIXES = ['*** Add File: ', '*** Update File: ', '*** Delete File: ', '*** Move to: '];
|
|
10
|
-
export const codexDescriptor = { harness: HARNESS, capabilities: CODEX_CAPABILITIES, estimation: ESTIMATION, newSessionCommand: '/new' };
|
|
11
|
+
export const codexDescriptor = { harness: HARNESS, capabilities: CODEX_CAPABILITIES, estimation: ESTIMATION, newSessionCommand: '/new', skillInvocation: CODEX_SKILL_INVOCATION };
|
|
11
12
|
function sessionOf(payload) {
|
|
12
13
|
return { harness: HARNESS, sessionId: requireIdentifier(payload.session_id), agentId: payload.agent_id ?? null };
|
|
13
14
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const CODEX_SKILL_FOLDERS: readonly string[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const CODEX_SKILL_FOLDERS = ['.agents/skills'];
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AdapterPlan } from '../../../core/contracts/adapter.js';
|
|
2
|
+
import type { InstallationManifest } from '../../../core/contracts/manifest.js';
|
|
3
|
+
export type ScalarDefaults = Readonly<Record<string, string | number | boolean>>;
|
|
4
|
+
export type OriginTarget = {
|
|
5
|
+
readonly path: string;
|
|
6
|
+
readonly manifest?: InstallationManifest | null | undefined;
|
|
7
|
+
readonly defaults?: ScalarDefaults;
|
|
8
|
+
};
|
|
9
|
+
export declare function attachConfigOrigin(plan: AdapterPlan, target: OriginTarget): Promise<AdapterPlan>;
|
|
10
|
+
export declare function applyConfigOrigin(plan: AdapterPlan, target: OriginTarget): AdapterPlan;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { findNodeAtLocation, getNodeValue, parseTree } from 'jsonc-parser';
|
|
3
|
+
import { parseAndValidateJson, removeJsonProperty } from '../../storage/json-document-editor.js';
|
|
4
|
+
function topLevelKeys(text) {
|
|
5
|
+
const root = parseTree(text, [], { disallowComments: false });
|
|
6
|
+
if (root?.type !== 'object')
|
|
7
|
+
return [];
|
|
8
|
+
return (root.children ?? []).map((property) => String(property.children?.[0]?.value));
|
|
9
|
+
}
|
|
10
|
+
function isEmptyContainer(value) {
|
|
11
|
+
if (Array.isArray(value))
|
|
12
|
+
return value.length === 0;
|
|
13
|
+
return typeof value === 'object' && value !== null && Object.keys(value).length === 0;
|
|
14
|
+
}
|
|
15
|
+
function removeAddedKeys(text, origin, defaults) {
|
|
16
|
+
return origin.addedKeys.reduce((current, key) => {
|
|
17
|
+
const node = findNodeAtLocation(parseAndValidateJson(current), [key]);
|
|
18
|
+
if (!node)
|
|
19
|
+
return current;
|
|
20
|
+
const value = getNodeValue(node);
|
|
21
|
+
return isEmptyContainer(value) || value === defaults[key] ? removeJsonProperty(current, [key]) : current;
|
|
22
|
+
}, text);
|
|
23
|
+
}
|
|
24
|
+
function restoreChange(change, origin, defaults) {
|
|
25
|
+
if (change.path !== origin.path || change.kind !== 'update' || change.content === null)
|
|
26
|
+
return change;
|
|
27
|
+
const content = removeAddedKeys(change.content, origin, defaults);
|
|
28
|
+
if (origin.created && /^\s*\{\s*\}\s*$/.test(content))
|
|
29
|
+
return { ...change, kind: 'delete', content: null, preview: { summary: `Delete ${origin.path}, created by init` } };
|
|
30
|
+
return { ...change, content };
|
|
31
|
+
}
|
|
32
|
+
function recordConfigOrigin(target, original, installed) {
|
|
33
|
+
const path = target.path;
|
|
34
|
+
const recorded = target.manifest?.configOrigins?.find((origin) => origin.path === path);
|
|
35
|
+
if (recorded)
|
|
36
|
+
return recorded;
|
|
37
|
+
const before = new Set(original === null ? [] : topLevelKeys(original));
|
|
38
|
+
return { path, created: original === null, addedKeys: topLevelKeys(installed).filter((key) => !before.has(key)) };
|
|
39
|
+
}
|
|
40
|
+
export async function attachConfigOrigin(plan, target) {
|
|
41
|
+
const change = plan.changes.find((item) => item.path === target.path);
|
|
42
|
+
if (!change || change.content === null)
|
|
43
|
+
return plan;
|
|
44
|
+
const original = await readFile(change.realPath, 'utf8').catch(() => null);
|
|
45
|
+
return { ...plan, configOrigins: [recordConfigOrigin(target, original, change.content)] };
|
|
46
|
+
}
|
|
47
|
+
export function applyConfigOrigin(plan, target) {
|
|
48
|
+
const origin = target.manifest?.configOrigins?.find((item) => item.path === target.path);
|
|
49
|
+
if (!origin)
|
|
50
|
+
return plan;
|
|
51
|
+
return { ...plan, changes: plan.changes.map((change) => restoreChange(change, origin, target.defaults ?? {})) };
|
|
52
|
+
}
|
|
@@ -4,6 +4,7 @@ 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 { CURSOR_CAPABILITIES } from './capabilities.js';
|
|
@@ -21,11 +22,11 @@ export class CursorAdapter {
|
|
|
21
22
|
probeVersion(context) {
|
|
22
23
|
return probeExecutableVersion(context.runner, CURSOR_EXECUTABLES);
|
|
23
24
|
}
|
|
24
|
-
planInstall(context) {
|
|
25
|
-
return planCursorInstall(context.projectRoot);
|
|
25
|
+
async planInstall(context) {
|
|
26
|
+
return attachConfigOrigin(await planCursorInstall(context.projectRoot), { path: CURSOR_CONFIG_FILE, manifest: context.manifest });
|
|
26
27
|
}
|
|
27
|
-
planRemove(context) {
|
|
28
|
-
return planCursorRemove(context.projectRoot);
|
|
28
|
+
async planRemove(context) {
|
|
29
|
+
return applyConfigOrigin(await planCursorRemove(context.projectRoot), { path: CURSOR_CONFIG_FILE, manifest: context.manifest, defaults: { version: 1 } });
|
|
29
30
|
}
|
|
30
31
|
async diagnose(context) {
|
|
31
32
|
const findings = [];
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { CURSOR_SKILL_INVOCATION } from './skill-invocation.js';
|
|
1
2
|
import { asRecord, assetProjectRoot, characterLength, parsePayload, projectRootFromEnvironment, requireIdentifier, textValue } from '../common/runtime-support.js';
|
|
2
3
|
import { runProcessHook } from '../../runtime/process-hook-host.js';
|
|
3
4
|
import { CURSOR_CAPABILITIES } from './capabilities.js';
|
|
4
5
|
import { cursorPayloadSchema } from './schemas.js';
|
|
5
6
|
const HARNESS = 'cursor';
|
|
6
7
|
const ESTIMATION = { baselineTokens: 15000, tokensPerTurn: 150 };
|
|
7
|
-
export const cursorDescriptor = { harness: HARNESS, capabilities: CURSOR_CAPABILITIES, estimation: ESTIMATION, newSessionCommand: null };
|
|
8
|
+
export const cursorDescriptor = { harness: HARNESS, capabilities: CURSOR_CAPABILITIES, estimation: ESTIMATION, newSessionCommand: null, skillInvocation: CURSOR_SKILL_INVOCATION };
|
|
8
9
|
function sessionOf(payload) {
|
|
9
10
|
const sessionId = payload.session_id ?? payload.conversation_id;
|
|
10
11
|
return { harness: HARNESS, sessionId: requireIdentifier(sessionId), agentId: null };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const CURSOR_SKILL_FOLDERS: readonly string[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const CURSOR_SKILL_FOLDERS = ['.cursor/skills', '.agents/skills', '.claude/skills', '.codex/skills'];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { COPILOT_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 { COPILOT_CAPABILITIES } from './capabilities.js';
|
|
@@ -7,7 +8,7 @@ const ESTIMATION = { baselineTokens: 15000, tokensPerTurn: 150 };
|
|
|
7
8
|
const READ_TOOLS = ['view'];
|
|
8
9
|
const WRITE_TOOLS = ['edit', 'create'];
|
|
9
10
|
const SHELL_TOOLS = ['bash', 'powershell'];
|
|
10
|
-
export const copilotDescriptor = { harness: HARNESS, capabilities: COPILOT_CAPABILITIES, estimation: ESTIMATION, newSessionCommand: null };
|
|
11
|
+
export const copilotDescriptor = { harness: HARNESS, capabilities: COPILOT_CAPABILITIES, estimation: ESTIMATION, newSessionCommand: null, skillInvocation: COPILOT_SKILL_INVOCATION };
|
|
11
12
|
function sessionOf(payload) {
|
|
12
13
|
return { harness: HARNESS, sessionId: requireIdentifier(payload.sessionId), agentId: null };
|
|
13
14
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const COPILOT_SKILL_FOLDERS: readonly string[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const COPILOT_SKILL_FOLDERS = ['.github/skills', '.claude/skills', '.agents/skills'];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { OMP_SKILL_INVOCATION } from './skill-invocation.js';
|
|
1
2
|
import { createRuntimeResolver, measuredUsageFrom, runInProcessEvent } from '../common/in-process-support.js';
|
|
2
3
|
import { requireIdentifier, textValue } from '../common/runtime-support.js';
|
|
3
4
|
import { OMP_CAPABILITIES } from './capabilities.js';
|
|
@@ -5,7 +6,7 @@ import { mapOmpEvent, mapOmpInput, renderOmpToolResult } from './events.js';
|
|
|
5
6
|
export { mapOmpEvent, mapOmpInput, renderOmpToolResult } from './events.js';
|
|
6
7
|
const HARNESS = 'oh-my-pi';
|
|
7
8
|
const ESTIMATION = { baselineTokens: 15000, tokensPerTurn: 150 };
|
|
8
|
-
export const ompDescriptor = { harness: HARNESS, capabilities: OMP_CAPABILITIES, estimation: ESTIMATION, newSessionCommand: '/new' };
|
|
9
|
+
export const ompDescriptor = { harness: HARNESS, capabilities: OMP_CAPABILITIES, estimation: ESTIMATION, newSessionCommand: '/new', skillInvocation: OMP_SKILL_INVOCATION };
|
|
9
10
|
function rootOf(context) {
|
|
10
11
|
return textValue(context.cwd);
|
|
11
12
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const OMP_SKILL_FOLDERS: readonly string[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const OMP_SKILL_FOLDERS = ['.omp/skills', '.github/skills', '.agents/skills'];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { OPENCODE_SKILL_INVOCATION } from './skill-invocation.js';
|
|
1
2
|
import { createRuntimeResolver, runInProcessEvent } from '../common/in-process-support.js';
|
|
2
3
|
import { asRecord, parsePayload, textValue } from '../common/runtime-support.js';
|
|
3
4
|
import { OPENCODE_CAPABILITIES } from './capabilities.js';
|
|
@@ -7,7 +8,7 @@ export { mapOpenCodeSessionEvent, mapOpenCodeToolResult, openCodeObservedCharact
|
|
|
7
8
|
const HARNESS = 'opencode';
|
|
8
9
|
const ESTIMATION = { baselineTokens: 15000, tokensPerTurn: 150 };
|
|
9
10
|
const FALLBACK_SESSION_ID = 'project';
|
|
10
|
-
export const openCodeDescriptor = { harness: HARNESS, capabilities: OPENCODE_CAPABILITIES, estimation: ESTIMATION, newSessionCommand: null };
|
|
11
|
+
export const openCodeDescriptor = { harness: HARNESS, capabilities: OPENCODE_CAPABILITIES, estimation: ESTIMATION, newSessionCommand: null, skillInvocation: OPENCODE_SKILL_INVOCATION };
|
|
11
12
|
function resolveProjectRoot(context) {
|
|
12
13
|
const record = asRecord(context);
|
|
13
14
|
return textValue(record?.['directory']) ?? textValue(record?.['worktree']) ?? process.cwd();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const OPENCODE_SKILL_FOLDERS: readonly string[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const OPENCODE_SKILL_FOLDERS = ['.opencode/skills', '.claude/skills', '.agents/skills'];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PI_SKILL_INVOCATION } from './skill-invocation.js';
|
|
1
2
|
import { createRuntimeResolver, measuredUsageFrom, runInProcessEvent } from '../common/in-process-support.js';
|
|
2
3
|
import { requireIdentifier, textValue } from '../common/runtime-support.js';
|
|
3
4
|
import { PI_CAPABILITIES } from './capabilities.js';
|
|
@@ -5,7 +6,7 @@ import { mapPiEvent, mapPiInput, renderPiToolResult } from './events.js';
|
|
|
5
6
|
export { mapPiEvent, mapPiInput, renderPiToolResult } from './events.js';
|
|
6
7
|
const HARNESS = 'pi';
|
|
7
8
|
const ESTIMATION = { baselineTokens: 15000, tokensPerTurn: 150 };
|
|
8
|
-
export const piDescriptor = { harness: HARNESS, capabilities: PI_CAPABILITIES, estimation: ESTIMATION, newSessionCommand: '/new' };
|
|
9
|
+
export const piDescriptor = { harness: HARNESS, capabilities: PI_CAPABILITIES, estimation: ESTIMATION, newSessionCommand: '/new', skillInvocation: PI_SKILL_INVOCATION };
|
|
9
10
|
function rootOf(context) {
|
|
10
11
|
return textValue(context.cwd);
|
|
11
12
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const PI_SKILL_FOLDERS: readonly string[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const PI_SKILL_FOLDERS = ['.pi/skills', '.agents/skills'];
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { CLAUDE_SKILL_FOLDERS } from './claude-code/skill-folders.js';
|
|
2
|
+
import { CODEX_SKILL_FOLDERS } from './codex-cli/skill-folders.js';
|
|
3
|
+
import { CURSOR_SKILL_FOLDERS } from './cursor/skill-folders.js';
|
|
4
|
+
import { COPILOT_SKILL_FOLDERS } from './github-copilot-cli/skill-folders.js';
|
|
5
|
+
import { OPENCODE_SKILL_FOLDERS } from './opencode/skill-folders.js';
|
|
6
|
+
import { PI_SKILL_FOLDERS } from './pi/skill-folders.js';
|
|
7
|
+
import { OMP_SKILL_FOLDERS } from './oh-my-pi/skill-folders.js';
|
|
8
|
+
import { ANTIGRAVITY_SKILL_FOLDERS } from './antigravity-cli/skill-folders.js';
|
|
9
|
+
export const SKILL_FOLDERS = {
|
|
10
|
+
'claude-code': CLAUDE_SKILL_FOLDERS,
|
|
11
|
+
'codex-cli': CODEX_SKILL_FOLDERS,
|
|
12
|
+
cursor: CURSOR_SKILL_FOLDERS,
|
|
13
|
+
'github-copilot-cli': COPILOT_SKILL_FOLDERS,
|
|
14
|
+
opencode: OPENCODE_SKILL_FOLDERS,
|
|
15
|
+
pi: PI_SKILL_FOLDERS,
|
|
16
|
+
'oh-my-pi': OMP_SKILL_FOLDERS,
|
|
17
|
+
'antigravity-cli': ANTIGRAVITY_SKILL_FOLDERS,
|
|
18
|
+
};
|
|
19
|
+
export function resolveSkillFolders(harness) {
|
|
20
|
+
return SKILL_FOLDERS[harness];
|
|
21
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { HarnessId } from '../../core/contracts/harness.js';
|
|
2
|
+
import type { SkillInvocationFormat } from '../../core/contracts/skill-invocation.js';
|
|
3
|
+
export declare const SKILL_INVOCATIONS: Readonly<Record<HarnessId, SkillInvocationFormat>>;
|
|
4
|
+
export declare function resolveSkillInvocation(harness: HarnessId): SkillInvocationFormat;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ANTIGRAVITY_SKILL_INVOCATION } from './antigravity-cli/skill-invocation.js';
|
|
2
|
+
import { CLAUDE_SKILL_INVOCATION } from './claude-code/skill-invocation.js';
|
|
3
|
+
import { CODEX_SKILL_INVOCATION } from './codex-cli/skill-invocation.js';
|
|
4
|
+
import { CURSOR_SKILL_INVOCATION } from './cursor/skill-invocation.js';
|
|
5
|
+
import { COPILOT_SKILL_INVOCATION } from './github-copilot-cli/skill-invocation.js';
|
|
6
|
+
import { OMP_SKILL_INVOCATION } from './oh-my-pi/skill-invocation.js';
|
|
7
|
+
import { OPENCODE_SKILL_INVOCATION } from './opencode/skill-invocation.js';
|
|
8
|
+
import { PI_SKILL_INVOCATION } from './pi/skill-invocation.js';
|
|
9
|
+
export const SKILL_INVOCATIONS = {
|
|
10
|
+
'claude-code': CLAUDE_SKILL_INVOCATION,
|
|
11
|
+
'codex-cli': CODEX_SKILL_INVOCATION,
|
|
12
|
+
cursor: CURSOR_SKILL_INVOCATION,
|
|
13
|
+
'github-copilot-cli': COPILOT_SKILL_INVOCATION,
|
|
14
|
+
opencode: OPENCODE_SKILL_INVOCATION,
|
|
15
|
+
pi: PI_SKILL_INVOCATION,
|
|
16
|
+
'oh-my-pi': OMP_SKILL_INVOCATION,
|
|
17
|
+
'antigravity-cli': ANTIGRAVITY_SKILL_INVOCATION,
|
|
18
|
+
};
|
|
19
|
+
export function resolveSkillInvocation(harness) {
|
|
20
|
+
return SKILL_INVOCATIONS[harness];
|
|
21
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { join, resolve } from 'node:path';
|
|
4
|
+
import { RUNTIME_IGNORE_CONTENT } from '../../core/contracts/context-reading.js';
|
|
4
5
|
import { RUNTIME_STATE_RELATIVE_DIR } from '../storage/runtime-state-files.js';
|
|
5
|
-
export const RUNTIME_GITIGNORE_CONTENT =
|
|
6
|
+
export const RUNTIME_GITIGNORE_CONTENT = RUNTIME_IGNORE_CONTENT;
|
|
6
7
|
export const SESSIONS_DIR_NAME = 'sessions';
|
|
7
8
|
export const LEDGER_FILE_EXTENSION = '.jsonl';
|
|
8
9
|
export const SESSIONS_RELATIVE_PREFIX = `${RUNTIME_STATE_RELATIVE_DIR}/${SESSIONS_DIR_NAME}/`;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const DELIMITER = '---';
|
|
2
|
+
const BYTE_ORDER_MARK = '';
|
|
3
|
+
const KEY_LINE = /^(name|description):\s*(.*)$/;
|
|
4
|
+
const BLOCK_SCALAR = /^[>|]/;
|
|
5
|
+
const QUOTED = /^(["'])(.*)\1$/;
|
|
6
|
+
function scalar(raw) {
|
|
7
|
+
const value = raw.trim();
|
|
8
|
+
if (BLOCK_SCALAR.test(value))
|
|
9
|
+
return '';
|
|
10
|
+
return value.replace(QUOTED, '$2');
|
|
11
|
+
}
|
|
12
|
+
export function parseFrontmatter(text) {
|
|
13
|
+
const lines = text.replace(BYTE_ORDER_MARK, '').split(/\r?\n/);
|
|
14
|
+
const fields = { name: '', description: '' };
|
|
15
|
+
if (lines[0]?.trim() !== DELIMITER)
|
|
16
|
+
return { name: null, description: '' };
|
|
17
|
+
for (const line of lines.slice(1)) {
|
|
18
|
+
if (line.trim() === DELIMITER)
|
|
19
|
+
break;
|
|
20
|
+
const match = KEY_LINE.exec(line);
|
|
21
|
+
if (match?.[1] === 'name' || match?.[1] === 'description')
|
|
22
|
+
fields[match[1]] = scalar(match[2] ?? '');
|
|
23
|
+
}
|
|
24
|
+
return { name: fields.name === '' ? null : fields.name, description: fields.description };
|
|
25
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { SkillCatalog } from '../../core/contracts/skill-catalog.js';
|
|
2
|
+
export declare const MAX_FRONTMATTER_BYTES = 8192;
|
|
3
|
+
export declare const MAX_SKILL_DIRECTORIES = 500;
|
|
4
|
+
export declare function readSkillFolders(projectRoot: string, folders: readonly string[]): Promise<SkillCatalog>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { open, readdir, stat } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { isSkillName } from '../../core/contracts/skill-name.js';
|
|
4
|
+
import { parseFrontmatter } from './frontmatter.js';
|
|
5
|
+
export const MAX_FRONTMATTER_BYTES = 8192;
|
|
6
|
+
export const MAX_SKILL_DIRECTORIES = 500;
|
|
7
|
+
const SKILL_FILE = 'SKILL.md';
|
|
8
|
+
const MISSING_CODES = new Set(['ENOENT', 'ENOTDIR']);
|
|
9
|
+
const CHUNK_SIZE = 32;
|
|
10
|
+
function errorCode(error) {
|
|
11
|
+
return error instanceof Error && 'code' in error && typeof error.code === 'string' ? error.code : 'unreadable';
|
|
12
|
+
}
|
|
13
|
+
async function readHead(file) {
|
|
14
|
+
try {
|
|
15
|
+
const handle = await open(file, 'r');
|
|
16
|
+
try {
|
|
17
|
+
const buffer = Buffer.alloc(MAX_FRONTMATTER_BYTES);
|
|
18
|
+
const { bytesRead } = await handle.read(buffer, 0, MAX_FRONTMATTER_BYTES, 0);
|
|
19
|
+
return buffer.toString('utf8', 0, bytesRead);
|
|
20
|
+
}
|
|
21
|
+
finally {
|
|
22
|
+
await handle.close();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (MISSING_CODES.has(errorCode(error)))
|
|
27
|
+
return null;
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async function readSkill({ folder, directory, entry }) {
|
|
32
|
+
const path = join(directory, entry);
|
|
33
|
+
try {
|
|
34
|
+
if (!(await stat(path)).isDirectory())
|
|
35
|
+
return { skill: null, warning: null };
|
|
36
|
+
const head = await readHead(join(path, SKILL_FILE));
|
|
37
|
+
if (head === null)
|
|
38
|
+
return { skill: null, warning: null };
|
|
39
|
+
const { name, description } = parseFrontmatter(head);
|
|
40
|
+
const resolved = name ?? entry;
|
|
41
|
+
return { skill: isSkillName(resolved) ? { name: resolved, description, folder } : null, warning: null };
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
return MISSING_CODES.has(errorCode(error)) ? { skill: null, warning: null } : { skill: null, warning: `${folder}/${entry}: ${errorCode(error)}` };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async function readChunked(entries) {
|
|
48
|
+
const found = [];
|
|
49
|
+
for (let start = 0; start < entries.length; start += CHUNK_SIZE)
|
|
50
|
+
found.push(...await Promise.all(entries.slice(start, start + CHUNK_SIZE).map(readSkill)));
|
|
51
|
+
return found;
|
|
52
|
+
}
|
|
53
|
+
async function listFolder(projectRoot, folder) {
|
|
54
|
+
const directory = join(projectRoot, folder);
|
|
55
|
+
try {
|
|
56
|
+
const names = (await readdir(directory)).sort().slice(0, MAX_SKILL_DIRECTORIES);
|
|
57
|
+
return { entries: names.map((entry) => ({ folder, directory, entry })), warning: null };
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
return { entries: [], warning: MISSING_CODES.has(errorCode(error)) ? null : `${folder}: ${errorCode(error)}` };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export async function readSkillFolders(projectRoot, folders) {
|
|
64
|
+
const skills = new Map();
|
|
65
|
+
const warnings = [];
|
|
66
|
+
for (const folder of folders) {
|
|
67
|
+
const listed = await listFolder(projectRoot, folder);
|
|
68
|
+
if (listed.warning !== null)
|
|
69
|
+
warnings.push(listed.warning);
|
|
70
|
+
for (const { skill, warning } of await readChunked(listed.entries)) {
|
|
71
|
+
if (warning !== null)
|
|
72
|
+
warnings.push(warning);
|
|
73
|
+
if (skill !== null && !skills.has(skill.name))
|
|
74
|
+
skills.set(skill.name, skill);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return { skills: [...skills.values()], warnings };
|
|
78
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { resolveSkillFolders } from '../harnesses/skill-folders.js';
|
|
2
|
+
import { readSkillFolders } from './skill-catalog-reader.js';
|
|
3
|
+
export function createSkillCatalogReader() {
|
|
4
|
+
return (harness, projectRoot) => readSkillFolders(projectRoot, resolveSkillFolders(harness));
|
|
5
|
+
}
|