harborloop 0.3.0 → 0.4.0-rc.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/bin/checksums.json +12 -0
- package/bin/harbor.mjs +188 -0
- package/package.json +14 -30
- package/dist/api.d.ts +0 -109
- package/dist/api.js +0 -409
- package/dist/auth/login.d.ts +0 -14
- package/dist/auth/login.js +0 -158
- package/dist/auth/loopback.d.ts +0 -11
- package/dist/auth/loopback.js +0 -87
- package/dist/auth/pkce.d.ts +0 -7
- package/dist/auth/pkce.js +0 -14
- package/dist/auth/store.d.ts +0 -26
- package/dist/auth/store.js +0 -107
- package/dist/cli-release.d.ts +0 -31
- package/dist/cli-release.js +0 -102
- package/dist/commands/bind.d.ts +0 -23
- package/dist/commands/bind.js +0 -86
- package/dist/commands/context.d.ts +0 -12
- package/dist/commands/context.js +0 -80
- package/dist/commands/daemon.d.ts +0 -39
- package/dist/commands/daemon.js +0 -140
- package/dist/commands/doctor.d.ts +0 -16
- package/dist/commands/doctor.js +0 -937
- package/dist/commands/init.d.ts +0 -36
- package/dist/commands/init.js +0 -192
- package/dist/commands/status.d.ts +0 -5
- package/dist/commands/status.js +0 -164
- package/dist/commands/sync.d.ts +0 -40
- package/dist/commands/sync.js +0 -242
- package/dist/commands/toggle.d.ts +0 -16
- package/dist/commands/toggle.js +0 -69
- package/dist/commands/unbind.d.ts +0 -14
- package/dist/commands/unbind.js +0 -60
- package/dist/commands/workspaces.d.ts +0 -18
- package/dist/commands/workspaces.js +0 -41
- package/dist/context-budget.d.ts +0 -40
- package/dist/context-budget.js +0 -259
- package/dist/daemon/install.d.ts +0 -50
- package/dist/daemon/install.js +0 -192
- package/dist/daemon/loop.d.ts +0 -15
- package/dist/daemon/loop.js +0 -84
- package/dist/daemon/state.d.ts +0 -79
- package/dist/daemon/state.js +0 -213
- package/dist/daemon/tick.d.ts +0 -37
- package/dist/daemon/tick.js +0 -231
- package/dist/documents/apply.d.ts +0 -21
- package/dist/documents/apply.js +0 -58
- package/dist/documents/bundle.d.ts +0 -41
- package/dist/documents/bundle.js +0 -43
- package/dist/documents/inspect.d.ts +0 -12
- package/dist/documents/inspect.js +0 -33
- package/dist/documents/manifest.d.ts +0 -14
- package/dist/documents/manifest.js +0 -52
- package/dist/documents/pointer.d.ts +0 -32
- package/dist/documents/pointer.js +0 -135
- package/dist/documents/write.d.ts +0 -57
- package/dist/documents/write.js +0 -211
- package/dist/endpoints.d.ts +0 -7
- package/dist/endpoints.js +0 -15
- package/dist/env-bundle.d.ts +0 -25
- package/dist/env-bundle.js +0 -33
- package/dist/env-scan.d.ts +0 -27
- package/dist/env-scan.js +0 -41
- package/dist/env-sync.d.ts +0 -31
- package/dist/env-sync.js +0 -37
- package/dist/index.d.ts +0 -11
- package/dist/index.js +0 -282
- package/dist/install-hooks.d.ts +0 -11
- package/dist/install-hooks.js +0 -64
- package/dist/lock.d.ts +0 -31
- package/dist/lock.js +0 -192
- package/dist/machine.d.ts +0 -13
- package/dist/machine.js +0 -53
- package/dist/memories.d.ts +0 -31
- package/dist/memories.js +0 -74
- package/dist/origin.d.ts +0 -4
- package/dist/origin.js +0 -59
- package/dist/paths.d.ts +0 -12
- package/dist/paths.js +0 -45
- package/dist/providers/claude.d.ts +0 -65
- package/dist/providers/claude.js +0 -412
- package/dist/providers/codex-trust.d.ts +0 -36
- package/dist/providers/codex-trust.js +0 -141
- package/dist/providers/codex.d.ts +0 -46
- package/dist/providers/codex.js +0 -520
- package/dist/providers/cursor.d.ts +0 -45
- package/dist/providers/cursor.js +0 -344
- package/dist/providers/detect.d.ts +0 -16
- package/dist/providers/detect.js +0 -70
- package/dist/providers/hook-coverage.d.ts +0 -5
- package/dist/providers/hook-coverage.js +0 -47
- package/dist/providers/hook-entry.d.ts +0 -1
- package/dist/providers/hook-entry.js +0 -8
- package/dist/providers/surfaces.d.ts +0 -19
- package/dist/providers/surfaces.js +0 -91
- package/dist/reclaim.d.ts +0 -17
- package/dist/reclaim.js +0 -33
- package/dist/render.d.ts +0 -17
- package/dist/render.js +0 -61
- package/dist/repo-sync.d.ts +0 -27
- package/dist/repo-sync.js +0 -34
- package/dist/repos.d.ts +0 -11
- package/dist/repos.js +0 -82
- package/dist/skills.d.ts +0 -84
- package/dist/skills.js +0 -383
- package/dist/slugify.d.ts +0 -1
- package/dist/slugify.js +0 -8
- package/dist/state.d.ts +0 -23
- package/dist/state.js +0 -161
- package/dist/sync-report.d.ts +0 -59
- package/dist/sync-report.js +0 -87
- package/hooks/_recall.mjs +0 -288
- package/hooks/_receipt.mjs +0 -47
- package/hooks/_shared.mjs +0 -1610
- package/hooks/_upgrade.mjs +0 -51
- package/hooks/session-capture.mjs +0 -60
- package/hooks/session-compact.mjs +0 -19
- package/hooks/session-context.mjs +0 -258
- package/hooks/session-report.mjs +0 -42
- package/hooks/session-toolcontext.mjs +0 -183
package/dist/slugify.js
DELETED
package/dist/state.d.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
export type PauseMode = 'off' | 'capture' | 'on';
|
|
2
|
-
export interface PauseState {
|
|
3
|
-
readonly global: PauseMode | null;
|
|
4
|
-
readonly repos: Readonly<Record<string, PauseMode>>;
|
|
5
|
-
}
|
|
6
|
-
export interface SyncedConfig {
|
|
7
|
-
readonly apiBase: string;
|
|
8
|
-
readonly origins: readonly string[];
|
|
9
|
-
readonly pause: PauseState;
|
|
10
|
-
readonly receipt: boolean;
|
|
11
|
-
}
|
|
12
|
-
export declare const EMPTY_PAUSE: PauseState;
|
|
13
|
-
export interface RepoArms {
|
|
14
|
-
readonly recall: boolean;
|
|
15
|
-
readonly capture: boolean;
|
|
16
|
-
}
|
|
17
|
-
export declare function resolveArms(pause: PauseState, originUrl: string): RepoArms;
|
|
18
|
-
export declare function readSyncedConfig(): Promise<SyncedConfig | null>;
|
|
19
|
-
export declare function updateSyncedConfig(mutate: (current: SyncedConfig) => SyncedConfig): Promise<SyncedConfig>;
|
|
20
|
-
export declare function withRepoPause(config: SyncedConfig, originUrl: string, mode: PauseMode | null): SyncedConfig;
|
|
21
|
-
export declare function withReceipt(config: SyncedConfig, receipt: boolean): SyncedConfig;
|
|
22
|
-
export declare function withGlobalPause(config: SyncedConfig, mode: PauseMode | null): SyncedConfig;
|
|
23
|
-
export declare function migrateLegacyState(): Promise<'migrated' | 'skipped'>;
|
package/dist/state.js
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
import { mkdir, readFile } from 'node:fs/promises';
|
|
2
|
-
import { updateFileLocked } from './lock.js';
|
|
3
|
-
import { normalizeOriginUrl } from './origin.js';
|
|
4
|
-
import { harborDir, legacyDirs, syncedOriginsFile } from './paths.js';
|
|
5
|
-
export const EMPTY_PAUSE = { global: null, repos: {} };
|
|
6
|
-
export function resolveArms(pause, originUrl) {
|
|
7
|
-
const mode = pause.repos[originUrl] ?? pause.global;
|
|
8
|
-
if (mode === 'off')
|
|
9
|
-
return { recall: false, capture: false };
|
|
10
|
-
if (mode === 'capture')
|
|
11
|
-
return { recall: true, capture: false };
|
|
12
|
-
return { recall: true, capture: true };
|
|
13
|
-
}
|
|
14
|
-
const isMode = (value) => value === 'off' || value === 'capture' || value === 'on';
|
|
15
|
-
function parsePause(raw) {
|
|
16
|
-
if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
17
|
-
return EMPTY_PAUSE;
|
|
18
|
-
}
|
|
19
|
-
const obj = raw;
|
|
20
|
-
const global = isMode(obj.global) ? obj.global : null;
|
|
21
|
-
const repos = {};
|
|
22
|
-
const rawRepos = obj.repos;
|
|
23
|
-
if (rawRepos !== null && typeof rawRepos === 'object' && !Array.isArray(rawRepos)) {
|
|
24
|
-
for (const [key, value] of Object.entries(rawRepos)) {
|
|
25
|
-
if (isMode(value))
|
|
26
|
-
repos[normalizeOriginUrl(key)] = value;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
return { global, repos };
|
|
30
|
-
}
|
|
31
|
-
export async function readSyncedConfig() {
|
|
32
|
-
let raw;
|
|
33
|
-
try {
|
|
34
|
-
raw = await readFile(syncedOriginsFile(), 'utf8');
|
|
35
|
-
}
|
|
36
|
-
catch {
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
let parsed;
|
|
40
|
-
try {
|
|
41
|
-
parsed = JSON.parse(raw);
|
|
42
|
-
}
|
|
43
|
-
catch {
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
const obj = parsed;
|
|
50
|
-
if (typeof obj.apiBase !== 'string' || !Array.isArray(obj.origins))
|
|
51
|
-
return null;
|
|
52
|
-
const legacyCaptureOff = obj.capture === false;
|
|
53
|
-
const pause = parsePause(obj.pause);
|
|
54
|
-
return {
|
|
55
|
-
apiBase: obj.apiBase.replace(/\/+$/, ''),
|
|
56
|
-
origins: obj.origins
|
|
57
|
-
.map((o) => normalizeOriginUrl(String(o)))
|
|
58
|
-
.filter((o) => o !== ''),
|
|
59
|
-
pause: legacyCaptureOff && pause.global === null
|
|
60
|
-
? { ...pause, global: 'capture' }
|
|
61
|
-
: pause,
|
|
62
|
-
receipt: readReceipt(obj),
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
function serialise(config) {
|
|
66
|
-
const body = {
|
|
67
|
-
apiBase: config.apiBase,
|
|
68
|
-
origins: [...config.origins],
|
|
69
|
-
};
|
|
70
|
-
if (config.pause.global !== null || Object.keys(config.pause.repos).length > 0) {
|
|
71
|
-
body.pause = { global: config.pause.global, repos: config.pause.repos };
|
|
72
|
-
}
|
|
73
|
-
if (!config.receipt)
|
|
74
|
-
body.receipt = false;
|
|
75
|
-
return `${JSON.stringify(body, null, 2)}\n`;
|
|
76
|
-
}
|
|
77
|
-
export async function updateSyncedConfig(mutate) {
|
|
78
|
-
await mkdir(harborDir(), { recursive: true, mode: 0o700 });
|
|
79
|
-
let result;
|
|
80
|
-
await updateFileLocked(syncedOriginsFile(), (existing) => {
|
|
81
|
-
const current = parseOrEmpty(existing);
|
|
82
|
-
result = mutate(current);
|
|
83
|
-
return serialise(result);
|
|
84
|
-
});
|
|
85
|
-
return result;
|
|
86
|
-
}
|
|
87
|
-
function parseOrEmpty(existing) {
|
|
88
|
-
if (existing.trim() === '') {
|
|
89
|
-
return { apiBase: '', origins: [], pause: EMPTY_PAUSE, receipt: true };
|
|
90
|
-
}
|
|
91
|
-
try {
|
|
92
|
-
const obj = JSON.parse(existing);
|
|
93
|
-
return {
|
|
94
|
-
apiBase: typeof obj.apiBase === 'string' ? obj.apiBase.replace(/\/+$/, '') : '',
|
|
95
|
-
origins: Array.isArray(obj.origins)
|
|
96
|
-
? obj.origins.map((o) => normalizeOriginUrl(String(o))).filter((o) => o !== '')
|
|
97
|
-
: [],
|
|
98
|
-
pause: obj.capture === false && parsePause(obj.pause).global === null
|
|
99
|
-
? { ...parsePause(obj.pause), global: 'capture' }
|
|
100
|
-
: parsePause(obj.pause),
|
|
101
|
-
receipt: readReceipt(obj),
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
catch {
|
|
105
|
-
return { apiBase: '', origins: [], pause: EMPTY_PAUSE, receipt: true };
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
function readReceipt(obj) {
|
|
109
|
-
return obj.receipt !== false;
|
|
110
|
-
}
|
|
111
|
-
export function withRepoPause(config, originUrl, mode) {
|
|
112
|
-
const repos = { ...config.pause.repos };
|
|
113
|
-
if (mode === null)
|
|
114
|
-
delete repos[originUrl];
|
|
115
|
-
else
|
|
116
|
-
repos[originUrl] = mode;
|
|
117
|
-
return { ...config, pause: { ...config.pause, repos } };
|
|
118
|
-
}
|
|
119
|
-
export function withReceipt(config, receipt) {
|
|
120
|
-
return { ...config, receipt };
|
|
121
|
-
}
|
|
122
|
-
export function withGlobalPause(config, mode) {
|
|
123
|
-
return { ...config, pause: { ...config.pause, global: mode } };
|
|
124
|
-
}
|
|
125
|
-
export async function migrateLegacyState() {
|
|
126
|
-
const existing = await readSyncedConfig();
|
|
127
|
-
if (existing)
|
|
128
|
-
return 'skipped';
|
|
129
|
-
let legacyRaw;
|
|
130
|
-
for (const dir of legacyDirs()) {
|
|
131
|
-
try {
|
|
132
|
-
legacyRaw = await readFile(`${dir}/synced-origins.json`, 'utf8');
|
|
133
|
-
break;
|
|
134
|
-
}
|
|
135
|
-
catch {
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
if (legacyRaw === undefined)
|
|
139
|
-
return 'skipped';
|
|
140
|
-
let parsed;
|
|
141
|
-
try {
|
|
142
|
-
parsed = JSON.parse(legacyRaw);
|
|
143
|
-
}
|
|
144
|
-
catch {
|
|
145
|
-
return 'skipped';
|
|
146
|
-
}
|
|
147
|
-
if (parsed === null || typeof parsed !== 'object')
|
|
148
|
-
return 'skipped';
|
|
149
|
-
const obj = parsed;
|
|
150
|
-
if (typeof obj.apiBase !== 'string' || !Array.isArray(obj.origins))
|
|
151
|
-
return 'skipped';
|
|
152
|
-
await updateSyncedConfig(() => ({
|
|
153
|
-
apiBase: obj.apiBase,
|
|
154
|
-
origins: obj.origins
|
|
155
|
-
.map((o) => normalizeOriginUrl(String(o)))
|
|
156
|
-
.filter((o) => o !== ''),
|
|
157
|
-
pause: obj.capture === false ? { global: 'capture', repos: {} } : EMPTY_PAUSE,
|
|
158
|
-
receipt: readReceipt(obj),
|
|
159
|
-
}));
|
|
160
|
-
return 'migrated';
|
|
161
|
-
}
|
package/dist/sync-report.d.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { type Owner } from './context-budget.js';
|
|
2
|
-
import { type EnvScanOptions } from './env-scan.js';
|
|
3
|
-
export interface SyncReportFile {
|
|
4
|
-
readonly kind: 'SKILL' | 'DOCUMENT';
|
|
5
|
-
readonly agent: string;
|
|
6
|
-
readonly dir: string;
|
|
7
|
-
readonly file: string;
|
|
8
|
-
readonly sha256: string | null;
|
|
9
|
-
readonly state: 'ours' | 'forked' | 'missing' | 'unverifiable';
|
|
10
|
-
}
|
|
11
|
-
export interface SyncReportSkill {
|
|
12
|
-
readonly name: string;
|
|
13
|
-
readonly owner: Owner;
|
|
14
|
-
readonly tokens: number;
|
|
15
|
-
readonly undiscoverable: boolean;
|
|
16
|
-
}
|
|
17
|
-
export interface SyncReportBudget {
|
|
18
|
-
readonly tokens: number;
|
|
19
|
-
readonly unmeasuredLines: number;
|
|
20
|
-
readonly skillsIndexed: number;
|
|
21
|
-
readonly skillsManaged: number;
|
|
22
|
-
readonly skillsOtherTool: number;
|
|
23
|
-
readonly skillsUnowned: number;
|
|
24
|
-
readonly skillsUndiscoverable: number;
|
|
25
|
-
readonly skills: readonly SyncReportSkill[];
|
|
26
|
-
}
|
|
27
|
-
export interface SyncReport {
|
|
28
|
-
readonly machineId: string;
|
|
29
|
-
readonly hostname: string;
|
|
30
|
-
readonly os: string;
|
|
31
|
-
readonly osVersion: string;
|
|
32
|
-
readonly cliVersion: string;
|
|
33
|
-
readonly bundleHash?: string;
|
|
34
|
-
readonly files: readonly SyncReportFile[];
|
|
35
|
-
readonly budget?: SyncReportBudget;
|
|
36
|
-
}
|
|
37
|
-
export type PostSyncReportResult = {
|
|
38
|
-
readonly kind: 'ok';
|
|
39
|
-
readonly machines: number;
|
|
40
|
-
} | {
|
|
41
|
-
readonly kind: 'unsupported';
|
|
42
|
-
} | {
|
|
43
|
-
readonly kind: 'error';
|
|
44
|
-
readonly status: number | 'network';
|
|
45
|
-
readonly message: string;
|
|
46
|
-
};
|
|
47
|
-
export interface PostSyncReportInput {
|
|
48
|
-
readonly apiBase: string;
|
|
49
|
-
readonly workspaceId: string;
|
|
50
|
-
readonly bearerToken: string;
|
|
51
|
-
readonly report: SyncReport;
|
|
52
|
-
readonly fetchImpl?: typeof fetch;
|
|
53
|
-
}
|
|
54
|
-
export declare function syncReportUrl(apiBase: string, workspaceId: string): string;
|
|
55
|
-
export declare function measureReportBudget(options: EnvScanOptions): Promise<SyncReportBudget | null>;
|
|
56
|
-
export declare function buildSyncReport(options?: EnvScanOptions & {
|
|
57
|
-
readonly bundleHash?: string;
|
|
58
|
-
}): Promise<SyncReport>;
|
|
59
|
-
export declare function postSyncReport(input: PostSyncReportInput): Promise<PostSyncReportResult>;
|
package/dist/sync-report.js
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { ApiError, apiRequest } from './api.js';
|
|
2
|
-
import { measureContext } from './context-budget.js';
|
|
3
|
-
import { scanEnv, scannedFiles } from './env-scan.js';
|
|
4
|
-
import { readMachineFacts } from './machine.js';
|
|
5
|
-
function stripTrailingSlash(s) {
|
|
6
|
-
return s.endsWith('/') ? s.slice(0, -1) : s;
|
|
7
|
-
}
|
|
8
|
-
export function syncReportUrl(apiBase, workspaceId) {
|
|
9
|
-
return (`${stripTrailingSlash(apiBase)}/workspaces/${encodeURIComponent(workspaceId)}` +
|
|
10
|
-
`/context/sync-report`);
|
|
11
|
-
}
|
|
12
|
-
export async function measureReportBudget(options) {
|
|
13
|
-
try {
|
|
14
|
-
const budget = await measureContext({
|
|
15
|
-
...(options.home === undefined ? {} : { home: options.home }),
|
|
16
|
-
...(options.agents === undefined ? {} : { agents: options.agents }),
|
|
17
|
-
repoPath: null,
|
|
18
|
-
});
|
|
19
|
-
return {
|
|
20
|
-
tokens: budget.measuredTokens,
|
|
21
|
-
unmeasuredLines: budget.unmeasuredLines,
|
|
22
|
-
skillsIndexed: budget.skills.length - budget.undiscoverable,
|
|
23
|
-
skillsManaged: budget.ownership.harbor,
|
|
24
|
-
skillsOtherTool: budget.ownership['other-tool'],
|
|
25
|
-
skillsUnowned: budget.ownership.nobody,
|
|
26
|
-
skillsUndiscoverable: budget.undiscoverable,
|
|
27
|
-
skills: budget.skills.map((skill) => ({
|
|
28
|
-
name: skill.name,
|
|
29
|
-
owner: skill.owner,
|
|
30
|
-
tokens: skill.tokens,
|
|
31
|
-
undiscoverable: skill.undiscoverable,
|
|
32
|
-
})),
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
catch {
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
export async function buildSyncReport(options = {}) {
|
|
40
|
-
const { bundleHash, ...scanOptions } = options;
|
|
41
|
-
const [facts, scan, budget] = await Promise.all([
|
|
42
|
-
readMachineFacts(),
|
|
43
|
-
scanEnv(scanOptions),
|
|
44
|
-
measureReportBudget(scanOptions),
|
|
45
|
-
]);
|
|
46
|
-
return {
|
|
47
|
-
machineId: facts.id,
|
|
48
|
-
hostname: facts.hostname,
|
|
49
|
-
os: facts.os,
|
|
50
|
-
osVersion: facts.osVersion,
|
|
51
|
-
cliVersion: facts.cliVersion,
|
|
52
|
-
...(bundleHash === undefined ? {} : { bundleHash }),
|
|
53
|
-
files: scannedFiles(scan).map((f) => ({
|
|
54
|
-
kind: f.kind,
|
|
55
|
-
agent: f.agent,
|
|
56
|
-
dir: f.dir,
|
|
57
|
-
file: f.file,
|
|
58
|
-
sha256: f.sha256,
|
|
59
|
-
state: f.state,
|
|
60
|
-
})),
|
|
61
|
-
...(budget === null ? {} : { budget }),
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
export async function postSyncReport(input) {
|
|
65
|
-
const url = syncReportUrl(input.apiBase, input.workspaceId);
|
|
66
|
-
try {
|
|
67
|
-
const body = await apiRequest(url, {
|
|
68
|
-
method: 'POST',
|
|
69
|
-
bearer: input.bearerToken,
|
|
70
|
-
body: input.report,
|
|
71
|
-
...(input.fetchImpl ? { fetchImpl: input.fetchImpl } : {}),
|
|
72
|
-
});
|
|
73
|
-
return { kind: 'ok', machines: body?.data?.machines ?? 1 };
|
|
74
|
-
}
|
|
75
|
-
catch (err) {
|
|
76
|
-
if (!(err instanceof ApiError)) {
|
|
77
|
-
return {
|
|
78
|
-
kind: 'error',
|
|
79
|
-
status: 'network',
|
|
80
|
-
message: err instanceof Error ? err.message : String(err),
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
if (err.status === 404)
|
|
84
|
-
return { kind: 'unsupported' };
|
|
85
|
-
return { kind: 'error', status: err.status ?? 'network', message: err.message };
|
|
86
|
-
}
|
|
87
|
-
}
|
package/hooks/_recall.mjs
DELETED
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
const TASK_KEY_RE = /\b[A-Z][A-Z0-9]+-\d+\b/g;
|
|
2
|
-
const STOPWORDS = new Set([
|
|
3
|
-
'the', 'a', 'an', 'and', 'or', 'but', 'if', 'then', 'else', 'so', 'as',
|
|
4
|
-
'of', 'to', 'in', 'on', 'at', 'by', 'for', 'with', 'from', 'into', 'onto',
|
|
5
|
-
'about', 'over', 'under', 'out', 'up', 'off', 'this', 'that', 'these',
|
|
6
|
-
'those', 'it', 'its', 'here', 'there', 'all', 'any', 'some', 'no', 'not',
|
|
7
|
-
'i', 'im', 'me', 'my', 'mine', 'we', 'us', 'our', 'ours', 'you', 'your',
|
|
8
|
-
'yours', 'he', 'him', 'his', 'she', 'her', 'they', 'them', 'their',
|
|
9
|
-
'hi', 'hey', 'hello', 'yo', 'thanks', 'thank', 'thankyou', 'please', 'pls',
|
|
10
|
-
'ok', 'okay', 'kk', 'yeah', 'yep', 'yes', 'nope', 'sure', 'cool', 'nice',
|
|
11
|
-
'great', 'lol', 'hmm', 'oh', 'ah', 'well', 'btw', 'fyi', 'actually',
|
|
12
|
-
'what', 'whats', 'why', 'how', 'when', 'where', 'who', 'which', 'whom',
|
|
13
|
-
'can', 'could', 'would', 'should', 'shall', 'will', 'wont', 'cant', 'do',
|
|
14
|
-
'does', 'did', 'is', 'are', 'am', 'was', 'were', 'be', 'been', 'being',
|
|
15
|
-
'have', 'has', 'had', 'having', 'may', 'might', 'must',
|
|
16
|
-
'get', 'got', 'make', 'made', 'let', 'lets', 'just', 'like', 'want',
|
|
17
|
-
'need', 'try', 'use', 'using', 'go', 'going', 'now', 'also', 'really',
|
|
18
|
-
'very', 'much', 'more', 'most', 'than', 'too', 'still', 'again', 'back',
|
|
19
|
-
'think', 'know', 'see', 'look', 'looks', 'tell', 'show', 'give', 'take',
|
|
20
|
-
'thing', 'things', 'stuff', 'something', 'anything', 'everything', 'one',
|
|
21
|
-
'ones', 'way', 'ways', 'bit', 'lot', 'good', 'bad', 'right', 'wrong',
|
|
22
|
-
]);
|
|
23
|
-
const ATTACHMENT_EXT_RE = /\.(?:png|jpe?g|gif|webp|svg|bmp|heic|heif|avif|pdf)$/i;
|
|
24
|
-
export function tokenize(text) {
|
|
25
|
-
return text
|
|
26
|
-
.toLowerCase()
|
|
27
|
-
.split(/[^a-z0-9]+/)
|
|
28
|
-
.filter((t) => t.length >= 2);
|
|
29
|
-
}
|
|
30
|
-
export function distinctiveTokens(text) {
|
|
31
|
-
const seen = new Set();
|
|
32
|
-
const out = [];
|
|
33
|
-
for (const tok of tokenize(text)) {
|
|
34
|
-
if (STOPWORDS.has(tok) || seen.has(tok))
|
|
35
|
-
continue;
|
|
36
|
-
seen.add(tok);
|
|
37
|
-
out.push(tok);
|
|
38
|
-
}
|
|
39
|
-
return out;
|
|
40
|
-
}
|
|
41
|
-
function isWhitespace(ch) {
|
|
42
|
-
return ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r';
|
|
43
|
-
}
|
|
44
|
-
export function stripAttachmentMentions(text) {
|
|
45
|
-
const pieces = [];
|
|
46
|
-
let cursor = 0;
|
|
47
|
-
let hadAttachment = false;
|
|
48
|
-
while (cursor < text.length) {
|
|
49
|
-
const at = text.indexOf('@', cursor);
|
|
50
|
-
if (at === -1) {
|
|
51
|
-
pieces.push(text.slice(cursor));
|
|
52
|
-
break;
|
|
53
|
-
}
|
|
54
|
-
if (at > 0 && !isWhitespace(text[at - 1] ?? '')) {
|
|
55
|
-
pieces.push(text.slice(cursor, at + 1));
|
|
56
|
-
cursor = at + 1;
|
|
57
|
-
continue;
|
|
58
|
-
}
|
|
59
|
-
let i = at + 1;
|
|
60
|
-
while (i < text.length) {
|
|
61
|
-
const ch = text[i];
|
|
62
|
-
if (ch === undefined)
|
|
63
|
-
break;
|
|
64
|
-
if (ch === '\\' && text[i + 1] === ' ') {
|
|
65
|
-
i += 2;
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
if (isWhitespace(ch))
|
|
69
|
-
break;
|
|
70
|
-
i += 1;
|
|
71
|
-
}
|
|
72
|
-
const rawPath = text.slice(at + 1, i);
|
|
73
|
-
const path = rawPath.replace(/\\ /g, ' ');
|
|
74
|
-
if (rawPath.length > 0 && ATTACHMENT_EXT_RE.test(path)) {
|
|
75
|
-
pieces.push(text.slice(cursor, at));
|
|
76
|
-
hadAttachment = true;
|
|
77
|
-
cursor = i;
|
|
78
|
-
}
|
|
79
|
-
else {
|
|
80
|
-
pieces.push(text.slice(cursor, i));
|
|
81
|
-
cursor = i;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
const stripped = pieces
|
|
85
|
-
.join('')
|
|
86
|
-
.replace(/[ \t]{2,}/g, ' ')
|
|
87
|
-
.replace(/[ \t]+\n/g, '\n')
|
|
88
|
-
.trim();
|
|
89
|
-
return { stripped, hadAttachment };
|
|
90
|
-
}
|
|
91
|
-
function extractTaskKeys(text, issueId) {
|
|
92
|
-
const seen = new Set();
|
|
93
|
-
const out = [];
|
|
94
|
-
const push = (raw) => {
|
|
95
|
-
const key = raw.toUpperCase();
|
|
96
|
-
if (seen.has(key))
|
|
97
|
-
return;
|
|
98
|
-
seen.add(key);
|
|
99
|
-
out.push(key);
|
|
100
|
-
};
|
|
101
|
-
for (const m of text.matchAll(TASK_KEY_RE))
|
|
102
|
-
push(m[0]);
|
|
103
|
-
if (issueId && issueId.length > 0)
|
|
104
|
-
push(issueId);
|
|
105
|
-
return out;
|
|
106
|
-
}
|
|
107
|
-
function fileEditTokens(fileEdits) {
|
|
108
|
-
if (!fileEdits || fileEdits.length === 0)
|
|
109
|
-
return [];
|
|
110
|
-
const seen = new Set();
|
|
111
|
-
const out = [];
|
|
112
|
-
const push = (raw) => {
|
|
113
|
-
const tok = raw.toLowerCase();
|
|
114
|
-
if (tok.length < 2 || seen.has(tok))
|
|
115
|
-
return;
|
|
116
|
-
seen.add(tok);
|
|
117
|
-
out.push(tok);
|
|
118
|
-
};
|
|
119
|
-
for (const edit of fileEdits) {
|
|
120
|
-
const base = edit.path.split(/[\\/]/).pop() ?? edit.path;
|
|
121
|
-
for (const part of base.split(/[^a-zA-Z0-9]+/))
|
|
122
|
-
push(part);
|
|
123
|
-
for (const sym of edit.symbols ?? [])
|
|
124
|
-
push(sym);
|
|
125
|
-
}
|
|
126
|
-
return out;
|
|
127
|
-
}
|
|
128
|
-
function normalizeDeducedTokens(deduced) {
|
|
129
|
-
if (!deduced || deduced.length === 0)
|
|
130
|
-
return [];
|
|
131
|
-
const seen = new Set();
|
|
132
|
-
const out = [];
|
|
133
|
-
for (const raw of deduced) {
|
|
134
|
-
const tok = raw.toLowerCase();
|
|
135
|
-
if (tok.length < 2 || seen.has(tok))
|
|
136
|
-
continue;
|
|
137
|
-
seen.add(tok);
|
|
138
|
-
out.push(tok);
|
|
139
|
-
}
|
|
140
|
-
return out;
|
|
141
|
-
}
|
|
142
|
-
export function buildRecallQuery(input) {
|
|
143
|
-
const taskKeys = extractTaskKeys(input.text, input.issueId);
|
|
144
|
-
const fileTokens = fileEditTokens(input.fileEdits);
|
|
145
|
-
const contentTokens = distinctiveTokens(input.text);
|
|
146
|
-
const deducedTokens = normalizeDeducedTokens(input.deducedTokens);
|
|
147
|
-
const tier = taskKeys.length
|
|
148
|
-
? 'A'
|
|
149
|
-
: fileTokens.length
|
|
150
|
-
? 'B'
|
|
151
|
-
: contentTokens.length
|
|
152
|
-
? 'C'
|
|
153
|
-
: deducedTokens.length
|
|
154
|
-
? 'D'
|
|
155
|
-
: null;
|
|
156
|
-
if (tier === null)
|
|
157
|
-
return null;
|
|
158
|
-
const queryParts = [...taskKeys, ...fileTokens, ...contentTokens, ...deducedTokens];
|
|
159
|
-
const tokenSeen = new Set();
|
|
160
|
-
const tokens = [];
|
|
161
|
-
for (const part of [
|
|
162
|
-
...taskKeys.map((k) => k.toLowerCase()),
|
|
163
|
-
...fileTokens,
|
|
164
|
-
...contentTokens,
|
|
165
|
-
...deducedTokens,
|
|
166
|
-
]) {
|
|
167
|
-
if (tokenSeen.has(part))
|
|
168
|
-
continue;
|
|
169
|
-
tokenSeen.add(part);
|
|
170
|
-
tokens.push(part);
|
|
171
|
-
}
|
|
172
|
-
return { query: queryParts.join(' '), tokens, tier };
|
|
173
|
-
}
|
|
174
|
-
export const SIGNATURE_TOKEN_CAP = 40;
|
|
175
|
-
function dedupe(tokens) {
|
|
176
|
-
const seen = new Set();
|
|
177
|
-
const out = [];
|
|
178
|
-
for (const tok of tokens) {
|
|
179
|
-
if (seen.has(tok))
|
|
180
|
-
continue;
|
|
181
|
-
seen.add(tok);
|
|
182
|
-
out.push(tok);
|
|
183
|
-
}
|
|
184
|
-
return out;
|
|
185
|
-
}
|
|
186
|
-
export const MIN_SHIFT_NEW_TOKENS = 2;
|
|
187
|
-
export function evaluateShift(signature, tokens) {
|
|
188
|
-
const covered = new Set(Array.isArray(signature) ? signature : []);
|
|
189
|
-
const newTokens = dedupe(tokens).filter((t) => !covered.has(t));
|
|
190
|
-
return { shift: newTokens.length >= MIN_SHIFT_NEW_TOKENS, newTokens };
|
|
191
|
-
}
|
|
192
|
-
export const EMPTY_ASK_TOKEN_CAP = 200;
|
|
193
|
-
export function askedAndEmpty(newTokens, emptyAsk) {
|
|
194
|
-
if (!Array.isArray(emptyAsk) || emptyAsk.length === 0)
|
|
195
|
-
return false;
|
|
196
|
-
if (!Array.isArray(newTokens) || newTokens.length === 0)
|
|
197
|
-
return false;
|
|
198
|
-
const known = new Set(emptyAsk);
|
|
199
|
-
return newTokens.every((t) => known.has(t));
|
|
200
|
-
}
|
|
201
|
-
export function foldEmptyAsk(emptyAsk, tokens) {
|
|
202
|
-
const existing = Array.isArray(emptyAsk) ? emptyAsk : [];
|
|
203
|
-
const merged = [...existing];
|
|
204
|
-
const present = new Set(existing);
|
|
205
|
-
for (const tok of tokens) {
|
|
206
|
-
if (present.has(tok))
|
|
207
|
-
continue;
|
|
208
|
-
present.add(tok);
|
|
209
|
-
merged.push(tok);
|
|
210
|
-
}
|
|
211
|
-
return merged.length > EMPTY_ASK_TOKEN_CAP
|
|
212
|
-
? merged.slice(merged.length - EMPTY_ASK_TOKEN_CAP)
|
|
213
|
-
: merged;
|
|
214
|
-
}
|
|
215
|
-
export function commitSignature(signature, tokens) {
|
|
216
|
-
const existing = Array.isArray(signature) ? signature : [];
|
|
217
|
-
const merged = [...existing];
|
|
218
|
-
const present = new Set(existing);
|
|
219
|
-
for (const tok of tokens) {
|
|
220
|
-
if (present.has(tok))
|
|
221
|
-
continue;
|
|
222
|
-
present.add(tok);
|
|
223
|
-
merged.push(tok);
|
|
224
|
-
}
|
|
225
|
-
return merged.length > SIGNATURE_TOKEN_CAP
|
|
226
|
-
? merged.slice(merged.length - SIGNATURE_TOKEN_CAP)
|
|
227
|
-
: merged;
|
|
228
|
-
}
|
|
229
|
-
export const PATH_STOPWORDS = new Set([
|
|
230
|
-
'packages', 'package', 'src', 'lib', 'libs', 'index', 'dist', 'build', 'out',
|
|
231
|
-
'node', 'modules', 'test', 'tests', 'spec', 'specs', 'mock', 'mocks',
|
|
232
|
-
'components', 'component', 'utils', 'util', 'helpers', 'helper', 'hooks',
|
|
233
|
-
'types', 'type', 'config', 'configs', 'scripts', 'script', 'app', 'apps',
|
|
234
|
-
'ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs', 'json', 'md', 'yml', 'yaml', 'css',
|
|
235
|
-
'new', 'old', 'tmp', 'temp', 'main', 'common', 'shared', 'core', 'base',
|
|
236
|
-
'file', 'files', 'data', 'docs', 'doc', 'public', 'assets', 'static',
|
|
237
|
-
]);
|
|
238
|
-
export function basenameTokens(filePath) {
|
|
239
|
-
const base = String(filePath).split(/[\\/]/).pop() ?? String(filePath);
|
|
240
|
-
const dot = base.lastIndexOf('.');
|
|
241
|
-
const stem = dot > 0 ? base.slice(0, dot) : base;
|
|
242
|
-
const out = [];
|
|
243
|
-
const seen = new Set();
|
|
244
|
-
for (const raw of stem.toLowerCase().split(/[^a-z0-9]+/)) {
|
|
245
|
-
if (raw.length < 3 || /^\d+$/.test(raw))
|
|
246
|
-
continue;
|
|
247
|
-
if (STOPWORDS.has(raw) || PATH_STOPWORDS.has(raw) || seen.has(raw))
|
|
248
|
-
continue;
|
|
249
|
-
seen.add(raw);
|
|
250
|
-
out.push(raw);
|
|
251
|
-
}
|
|
252
|
-
return out;
|
|
253
|
-
}
|
|
254
|
-
export function changeTokens(text) {
|
|
255
|
-
const out = new Set();
|
|
256
|
-
for (const raw of String(text).toLowerCase().split(/[^a-z0-9]+/)) {
|
|
257
|
-
if (raw.length < 4 || /^\d+$/.test(raw))
|
|
258
|
-
continue;
|
|
259
|
-
if (STOPWORDS.has(raw) || CODE_STOPWORDS.has(raw))
|
|
260
|
-
continue;
|
|
261
|
-
out.add(raw);
|
|
262
|
-
}
|
|
263
|
-
return out;
|
|
264
|
-
}
|
|
265
|
-
const CODE_STOPWORDS = new Set([
|
|
266
|
-
'const', 'let', 'var', 'function', 'return', 'import', 'export', 'from',
|
|
267
|
-
'type', 'types', 'interface', 'class', 'extends', 'implements', 'string',
|
|
268
|
-
'number', 'boolean', 'void', 'null', 'undefined', 'true', 'false', 'async',
|
|
269
|
-
'await', 'then', 'catch', 'throw', 'case', 'switch', 'break', 'continue',
|
|
270
|
-
'public', 'private', 'protected', 'readonly', 'static', 'this', 'that',
|
|
271
|
-
'with', 'when', 'where', 'which', 'while', 'each', 'into', 'over', 'they',
|
|
272
|
-
'them', 'their', 'there', 'these', 'those', 'than', 'been', 'being', 'have',
|
|
273
|
-
'has', 'had', 'does', 'done', 'will', 'would', 'could', 'should', 'must',
|
|
274
|
-
]);
|
|
275
|
-
export const MIN_CHANGE_OVERLAP = 3;
|
|
276
|
-
export function pathTokens(filePath) {
|
|
277
|
-
const out = [];
|
|
278
|
-
const seen = new Set();
|
|
279
|
-
for (const raw of String(filePath).toLowerCase().split(/[^a-z0-9]+/)) {
|
|
280
|
-
if (raw.length < 2)
|
|
281
|
-
continue;
|
|
282
|
-
if (STOPWORDS.has(raw) || PATH_STOPWORDS.has(raw) || seen.has(raw))
|
|
283
|
-
continue;
|
|
284
|
-
seen.add(raw);
|
|
285
|
-
out.push(raw);
|
|
286
|
-
}
|
|
287
|
-
return out;
|
|
288
|
-
}
|
package/hooks/_receipt.mjs
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
const MAX_LISTED = 5;
|
|
2
|
-
const TITLE_WIDTH = 72;
|
|
3
|
-
export function receiptCapable(agent) {
|
|
4
|
-
return agent !== 'cursor';
|
|
5
|
-
}
|
|
6
|
-
function fitTitle(raw) {
|
|
7
|
-
const flat = String(raw ?? '')
|
|
8
|
-
.replace(/\s+/g, ' ')
|
|
9
|
-
.trim();
|
|
10
|
-
if (flat === '')
|
|
11
|
-
return '';
|
|
12
|
-
return flat.length > TITLE_WIDTH ? `${flat.slice(0, TITLE_WIDTH - 1)}…` : flat;
|
|
13
|
-
}
|
|
14
|
-
function detailRow(entry) {
|
|
15
|
-
const kind = typeof entry.kind === 'string' ? entry.kind.trim() : '';
|
|
16
|
-
const source = typeof entry.source === 'string' ? entry.source.trim() : '';
|
|
17
|
-
return [kind, source].filter(Boolean).join(' · ');
|
|
18
|
-
}
|
|
19
|
-
export function renderReceipt(entries, opts = {}) {
|
|
20
|
-
if (!Array.isArray(entries))
|
|
21
|
-
return null;
|
|
22
|
-
const rows = entries
|
|
23
|
-
.filter((e) => e !== null && typeof e === 'object')
|
|
24
|
-
.map((e) => ({
|
|
25
|
-
n: Number.isInteger(e.n) && e.n > 0 ? e.n : null,
|
|
26
|
-
title: fitTitle(e.title),
|
|
27
|
-
detail: detailRow(e),
|
|
28
|
-
}))
|
|
29
|
-
.filter((e) => e.title !== '');
|
|
30
|
-
if (rows.length === 0)
|
|
31
|
-
return null;
|
|
32
|
-
const noun = rows.length === 1 ? 'rule' : 'rules';
|
|
33
|
-
const head = opts.delta
|
|
34
|
-
? `⚓ ${rows.length} new ${noun} for this turn`
|
|
35
|
-
: `⚓ ${rows.length} ${noun} from your team`;
|
|
36
|
-
const lines = [head];
|
|
37
|
-
for (const row of rows.slice(0, MAX_LISTED)) {
|
|
38
|
-
const handle = row.n === null ? ' ·' : `#${row.n}`.padStart(3);
|
|
39
|
-
lines.push(` ${handle} ${row.title}`);
|
|
40
|
-
if (row.detail !== '')
|
|
41
|
-
lines.push(` ${row.detail}`);
|
|
42
|
-
}
|
|
43
|
-
const hidden = rows.length - MAX_LISTED;
|
|
44
|
-
if (hidden > 0)
|
|
45
|
-
lines.push(` + ${hidden} more`);
|
|
46
|
-
return lines.join('\n');
|
|
47
|
-
}
|