@indigoai-us/hq-cli 5.286.0 → 5.288.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +57 -0
- package/assets/scaffold/core/scripts/clean-worktree-reconcile-handoff.sh +171 -0
- package/assets/scaffold/core/scripts/resume-thread-lock.sh +285 -0
- package/assets/scaffold/core/scripts/verify-story-deliverables.sh +148 -0
- package/assets/scaffold/core/scripts/worktree-gc.sh +362 -0
- package/dist/command-catalog.generated.d.ts +55 -0
- package/dist/command-catalog.generated.js +68 -0
- package/dist/commands/agent-enroll.js +10 -2
- package/dist/commands/agents.d.ts +1 -0
- package/dist/commands/agents.js +12 -4
- package/dist/commands/cloud-provision.js +99 -78
- package/dist/commands/core-native-handoff-flag.d.ts +4 -0
- package/dist/commands/core-native-handoff-flag.js +7 -0
- package/dist/commands/core-ontology.d.ts +4 -1
- package/dist/commands/core-ontology.js +26 -0
- package/dist/commands/core.js +45 -0
- package/dist/commands/lanes-resume.js +41 -2
- package/dist/commands/lanes-shared.d.ts +2 -0
- package/dist/commands/lanes.js +48 -29
- package/dist/commands/team-sync.js +71 -10
- package/dist/fast-lanes-list-route.js +2 -0
- package/dist/fast-lanes-list.js +1 -29
- package/dist/lanes-drain-hook.d.ts +12 -0
- package/dist/lanes-drain-hook.js +59 -55
- package/dist/lib/checkpoint-ontology/merge-copies.d.ts +10 -1
- package/dist/lib/checkpoint-ontology/merge-copies.js +17 -2
- package/dist/lib/checkpoint-ontology/plan.d.ts +0 -88
- package/dist/lib/checkpoint-ontology/plan.js +80 -107
- package/dist/lib/core-utils/sentry-report.js +5 -32
- package/dist/lib/core-utils/worktree-resume.d.ts +18 -0
- package/dist/lib/core-utils/worktree-resume.js +1182 -0
- package/dist/lib/lanes/dropbox.d.ts +1 -2
- package/dist/lib/lanes/dropbox.js +10 -111
- package/dist/lib/lanes/message-replay.d.ts +8 -0
- package/dist/lib/lanes/message-replay.js +119 -0
- package/dist/lib/lanes/outpost-snapshot-refresh.d.ts +21 -0
- package/dist/lib/lanes/outpost-snapshot-refresh.js +329 -0
- package/dist/lib/plan-limit-nag.js +11 -0
- package/dist/main.js +17 -4
- package/dist/utils/cli-telemetry.d.ts +21 -0
- package/dist/utils/cli-telemetry.js +101 -1
- package/dist/utils/cognito-refresh-sentry.d.ts +3 -0
- package/dist/utils/cognito-refresh-sentry.js +49 -0
- package/dist/utils/cognito-session.js +55 -0
- package/package.json +1 -1
|
@@ -16,6 +16,7 @@ import chalk from 'chalk';
|
|
|
16
16
|
import simpleGit from 'simple-git';
|
|
17
17
|
import { resolveDefaultHqRoot } from '../utils/cognito-session.js';
|
|
18
18
|
import { ensureCognitoToken } from '../utils/cognito-session.js';
|
|
19
|
+
import { readCompaniesManifestMap } from '../lib/work-context/repo-remote.js';
|
|
19
20
|
// ─── API helpers ────────────────────────────────────────────────────────────
|
|
20
21
|
const API_BASE = 'https://example.com/api';
|
|
21
22
|
async function apiGet(urlPath, token) {
|
|
@@ -137,15 +138,38 @@ function getTrackedFiles(repoDir) {
|
|
|
137
138
|
function discoverTeamDirs(hqRoot) {
|
|
138
139
|
const companiesDir = path.join(hqRoot, 'companies');
|
|
139
140
|
if (!fs.existsSync(companiesDir)) {
|
|
140
|
-
return
|
|
141
|
+
return {
|
|
142
|
+
teams: [],
|
|
143
|
+
cloudSyncedCompanyNames: [],
|
|
144
|
+
nonCloudBackedCompanyNames: [],
|
|
145
|
+
diagnostics: [`companies directory ${companiesDir}: missing`],
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
let entries;
|
|
149
|
+
try {
|
|
150
|
+
entries = fs.readdirSync(companiesDir, { withFileTypes: true });
|
|
141
151
|
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
return {
|
|
154
|
+
teams: [],
|
|
155
|
+
cloudSyncedCompanyNames: [],
|
|
156
|
+
nonCloudBackedCompanyNames: [],
|
|
157
|
+
diagnostics: [
|
|
158
|
+
`companies directory ${companiesDir}: could not read (${error instanceof Error ? error.message : 'unknown error'})`,
|
|
159
|
+
],
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
const companyDirs = entries.filter((entry) => entry.isDirectory());
|
|
142
163
|
const results = [];
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
164
|
+
const missingTeamJson = [];
|
|
165
|
+
const invalidTeamJson = [];
|
|
166
|
+
const companiesManifest = readCompaniesManifestMap(hqRoot) ?? {};
|
|
167
|
+
for (const entry of companyDirs) {
|
|
146
168
|
const teamJsonPath = path.join(companiesDir, entry.name, 'team.json');
|
|
147
|
-
if (!fs.existsSync(teamJsonPath))
|
|
169
|
+
if (!fs.existsSync(teamJsonPath)) {
|
|
170
|
+
missingTeamJson.push(teamJsonPath);
|
|
148
171
|
continue;
|
|
172
|
+
}
|
|
149
173
|
try {
|
|
150
174
|
const raw = fs.readFileSync(teamJsonPath, 'utf-8');
|
|
151
175
|
const meta = JSON.parse(raw);
|
|
@@ -155,12 +179,31 @@ function discoverTeamDirs(hqRoot) {
|
|
|
155
179
|
meta,
|
|
156
180
|
});
|
|
157
181
|
}
|
|
182
|
+
else {
|
|
183
|
+
invalidTeamJson.push(teamJsonPath);
|
|
184
|
+
}
|
|
158
185
|
}
|
|
159
186
|
catch {
|
|
160
|
-
|
|
187
|
+
invalidTeamJson.push(teamJsonPath);
|
|
161
188
|
}
|
|
162
189
|
}
|
|
163
|
-
|
|
190
|
+
const missingTeamJsonCompanyNames = missingTeamJson.map((file) => path.basename(path.dirname(file)));
|
|
191
|
+
const isCloudBacked = (name) => {
|
|
192
|
+
const cloudUid = companiesManifest[name]?.cloud_uid;
|
|
193
|
+
return typeof cloudUid === 'string' && cloudUid.trim().length > 0;
|
|
194
|
+
};
|
|
195
|
+
return {
|
|
196
|
+
teams: results,
|
|
197
|
+
cloudSyncedCompanyNames: missingTeamJsonCompanyNames.filter(isCloudBacked),
|
|
198
|
+
nonCloudBackedCompanyNames: missingTeamJsonCompanyNames.filter((name) => !isCloudBacked(name)),
|
|
199
|
+
diagnostics: [
|
|
200
|
+
`HQ root resolver: resolveDefaultHqRoot() returned ${hqRoot}`,
|
|
201
|
+
`companies directory ${companiesDir}: present; ${companyDirs.length} direct company directories found`,
|
|
202
|
+
`team.json discovery: ${results.length} valid, ${missingTeamJson.length} missing, ${invalidTeamJson.length} unreadable or invalid`,
|
|
203
|
+
...missingTeamJson.map((file) => `${file}: missing`),
|
|
204
|
+
...invalidTeamJson.map((file) => `${file}: unreadable or invalid`),
|
|
205
|
+
],
|
|
206
|
+
};
|
|
164
207
|
}
|
|
165
208
|
/**
|
|
166
209
|
* Sync a single team directory:
|
|
@@ -308,9 +351,27 @@ export function registerTeamSyncCommand(program) {
|
|
|
308
351
|
try {
|
|
309
352
|
const hqRoot = resolveDefaultHqRoot({ onMissing: 'throw' });
|
|
310
353
|
// 1. Discover team directories
|
|
311
|
-
|
|
354
|
+
const discovery = discoverTeamDirs(hqRoot);
|
|
355
|
+
let teamDirs = discovery.teams;
|
|
312
356
|
if (teamDirs.length === 0) {
|
|
313
|
-
console.log(chalk.yellow('No team directories found
|
|
357
|
+
console.log(chalk.yellow('No joined team directories found; no team sync ran.'));
|
|
358
|
+
if (discovery.cloudSyncedCompanyNames.length > 0) {
|
|
359
|
+
const companyNames = discovery.cloudSyncedCompanyNames
|
|
360
|
+
.map((name) => `"${name}"`)
|
|
361
|
+
.join(', ');
|
|
362
|
+
console.log(chalk.yellow(`Companies ${companyNames} are cloud-synced and do not have team.json. ` +
|
|
363
|
+
'hq team-sync only handles git team repositories with team.json.'));
|
|
364
|
+
console.log(chalk.dim('Use `hq sync pull --all` to sync company vault files and `hq sync status` to check their local sync state.'));
|
|
365
|
+
}
|
|
366
|
+
if (discovery.nonCloudBackedCompanyNames.length > 0) {
|
|
367
|
+
const companyNames = discovery.nonCloudBackedCompanyNames
|
|
368
|
+
.map((name) => `"${name}"`)
|
|
369
|
+
.join(', ');
|
|
370
|
+
console.log(`Companies ${companyNames} have no team.json and are not cloud-backed.`);
|
|
371
|
+
}
|
|
372
|
+
for (const diagnostic of discovery.diagnostics) {
|
|
373
|
+
console.log(chalk.dim(` ${diagnostic}`));
|
|
374
|
+
}
|
|
314
375
|
return;
|
|
315
376
|
}
|
|
316
377
|
// Filter to specific team if requested
|
|
@@ -318,7 +379,7 @@ export function registerTeamSyncCommand(program) {
|
|
|
318
379
|
teamDirs = teamDirs.filter((t) => t.meta.team_slug === options.team);
|
|
319
380
|
if (teamDirs.length === 0) {
|
|
320
381
|
console.log(chalk.red(`Team "${options.team}" not found. Available teams:`));
|
|
321
|
-
for (const t of discoverTeamDirs(hqRoot)) {
|
|
382
|
+
for (const t of discoverTeamDirs(hqRoot).teams) {
|
|
322
383
|
console.log(` - ${t.meta.team_slug} (${t.meta.team_name})`);
|
|
323
384
|
}
|
|
324
385
|
process.exit(1);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isFastCachedLanesListRequest } from "./fast-scaffold.js";
|
|
2
2
|
import { fastFlagEnabled } from "./native-fast-route.js";
|
|
3
|
+
import { registerOutpostLaneSnapshotRefreshChild } from "./lib/lanes/outpost-snapshot-refresh.js";
|
|
3
4
|
import { LANE_OUTPOST_SNAPSHOT_CACHE_FLAG } from "./lib/lanes/outpost-hire-cache.js";
|
|
4
5
|
import { LANE_OUTPOST_LOAD_BALANCING_FLAG } from "./lib/lanes/flag-keys.js";
|
|
5
6
|
/** Returns false unless this exact list form is safely enabled in the local flag snapshot. */
|
|
@@ -14,6 +15,7 @@ export async function runFastLanesListRequest(argv, env = process.env, deps = {
|
|
|
14
15
|
await runFastCachedLanesListFromEntrypoint();
|
|
15
16
|
},
|
|
16
17
|
}) {
|
|
18
|
+
registerOutpostLaneSnapshotRefreshChild(env);
|
|
17
19
|
if (!isFastCachedLanesListRequest(argv))
|
|
18
20
|
return false;
|
|
19
21
|
if (env.HQ_INTERNAL_LANES_REFRESH_REMOTE_SNAPSHOT === "1")
|
package/dist/fast-lanes-list.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
1
|
import * as fs from "node:fs";
|
|
3
2
|
import * as os from "node:os";
|
|
4
3
|
import * as path from "node:path";
|
|
5
4
|
import { compareLaneQueueOrder, laneRunsOnHost } from "./lib/lanes/admission.js";
|
|
6
5
|
import { readOutpostIdentity, resolveAdmissionHost } from "./lib/lanes/admission-host.js";
|
|
7
6
|
import { LaneSchemaError, isLanesError } from "./lib/lanes/errors.js";
|
|
7
|
+
import { startOutpostLaneSnapshotRefresh } from "./lib/lanes/outpost-snapshot-refresh.js";
|
|
8
8
|
import { OUTPOST_HIRE_SUMMARY_CACHE_TTL_MS, outpostHireSummaryCacheKey, readOutpostHireSummaryCacheIncludingStale, } from "./lib/lanes/outpost-hire-cache.js";
|
|
9
9
|
import { resolveLanesHqRoot } from "./lib/lanes/hq-root.js";
|
|
10
10
|
import { laneStories } from "./lib/lanes/stories.js";
|
|
@@ -82,34 +82,6 @@ function isOverdue(hqRoot, laneId, now) {
|
|
|
82
82
|
return false;
|
|
83
83
|
return now.getTime() > at;
|
|
84
84
|
}
|
|
85
|
-
function startOutpostLaneSnapshotRefresh(root, env) {
|
|
86
|
-
let entry;
|
|
87
|
-
try {
|
|
88
|
-
const currentEntry = process.argv[1];
|
|
89
|
-
if (!currentEntry)
|
|
90
|
-
throw new Error("process.argv[1] is empty");
|
|
91
|
-
entry = fs.realpathSync(currentEntry);
|
|
92
|
-
}
|
|
93
|
-
catch (err) {
|
|
94
|
-
process.stderr.write(`lanes snapshot refresh could not resolve the running CLI entry: ${errorMessage(err)}\n`);
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
try {
|
|
98
|
-
const child = spawn(process.execPath, [...process.execArgv, entry, "lanes", "list"], {
|
|
99
|
-
cwd: root,
|
|
100
|
-
detached: true,
|
|
101
|
-
stdio: ["ignore", "ignore", "inherit"],
|
|
102
|
-
env: { ...env, HQ_ROOT: root, HQ_NO_UPDATE_CHECK: "1", HQ_INTERNAL_LANES_REFRESH_REMOTE_SNAPSHOT: "1" },
|
|
103
|
-
});
|
|
104
|
-
child.once("error", (err) => {
|
|
105
|
-
process.stderr.write(`lanes snapshot refresh could not start: ${errorMessage(err)}\n`);
|
|
106
|
-
});
|
|
107
|
-
child.unref();
|
|
108
|
-
}
|
|
109
|
-
catch (err) {
|
|
110
|
-
process.stderr.write(`lanes snapshot refresh could not start: ${errorMessage(err)}\n`);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
85
|
function isOwnerLane(lane, host) {
|
|
114
86
|
return lane.host.kind === "outpost" &&
|
|
115
87
|
!isTerminalLaneState(lane.state) &&
|
|
@@ -1,2 +1,14 @@
|
|
|
1
|
+
import type { LaneDrainHookInput } from "./lib/lanes/dropbox.js";
|
|
2
|
+
interface DrainHookResult {
|
|
3
|
+
exitCode: number;
|
|
4
|
+
stdout: string;
|
|
5
|
+
stderr: string;
|
|
6
|
+
}
|
|
7
|
+
type DropboxModule = typeof import("./lib/lanes/dropbox.js");
|
|
8
|
+
export interface DrainHookRunnerDeps {
|
|
9
|
+
/** Test seam for proving the steady-state path does not load the drainer. */
|
|
10
|
+
loadDropbox?: () => Promise<Pick<DropboxModule, "runDrainHook">>;
|
|
11
|
+
}
|
|
12
|
+
export declare function runLaneDrainHook(env: NodeJS.ProcessEnv, input?: LaneDrainHookInput, inputError?: string, deps?: DrainHookRunnerDeps): Promise<DrainHookResult>;
|
|
1
13
|
export {};
|
|
2
14
|
//# sourceMappingURL=lanes-drain-hook.d.ts.map
|
package/dist/lanes-drain-hook.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { MESSAGE_REPLAY_STATE_NAME, readMessageReplayState, updateMessageReplayTranscript, writeMessageReplayState, } from "./lib/lanes/message-replay.js";
|
|
4
6
|
function parseHookInput(text) {
|
|
5
7
|
if (text.trim() === "")
|
|
6
8
|
return {};
|
|
@@ -72,38 +74,34 @@ function dropboxMayNeedDrain(box) {
|
|
|
72
74
|
}
|
|
73
75
|
return false;
|
|
74
76
|
}
|
|
75
|
-
function replayMayNeedWork(box, turnId, input) {
|
|
77
|
+
function replayMayNeedWork(box, turnId, input, env) {
|
|
76
78
|
if (!/^[A-Za-z0-9-]{1,80}$/.test(turnId))
|
|
77
79
|
return false;
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
let
|
|
81
|
-
const statePath = path.join(box, "message-compaction-replay.json");
|
|
80
|
+
const statePath = path.join(box, MESSAGE_REPLAY_STATE_NAME);
|
|
81
|
+
let state;
|
|
82
|
+
let hasLegacyFlag = false;
|
|
82
83
|
try {
|
|
83
|
-
|
|
84
|
+
state = readMessageReplayState(statePath, turnId, () => {
|
|
85
|
+
hasLegacyFlag = true;
|
|
86
|
+
});
|
|
84
87
|
}
|
|
85
88
|
catch {
|
|
86
89
|
// Keep the existing named replay error behavior for missing or unreadable state.
|
|
87
90
|
return true;
|
|
88
91
|
}
|
|
92
|
+
if (hasLegacyFlag || !input.transcript_path)
|
|
93
|
+
return hasLegacyFlag;
|
|
94
|
+
if (!state.provider)
|
|
95
|
+
return true;
|
|
89
96
|
try {
|
|
90
|
-
const
|
|
91
|
-
if (
|
|
97
|
+
const update = updateMessageReplayTranscript(state, input.transcript_path, state.provider, env);
|
|
98
|
+
if (update === "compacted")
|
|
92
99
|
return true;
|
|
93
|
-
|
|
94
|
-
if (state.schema_version !== 1 ||
|
|
95
|
-
state.turn_id !== turnId ||
|
|
96
|
-
(state.last_compaction_marker !== null &&
|
|
97
|
-
(!Number.isSafeInteger(state.last_compaction_marker) || Number(state.last_compaction_marker) < 0)) ||
|
|
98
|
-
(state.transcript_offset !== undefined &&
|
|
99
|
-
(!Number.isSafeInteger(state.transcript_offset) || Number(state.transcript_offset) < 0)) ||
|
|
100
|
-
(state.transcript_path !== undefined && typeof state.transcript_path !== "string") ||
|
|
101
|
-
state.flag_enabled !== undefined) {
|
|
102
|
-
return true;
|
|
103
|
-
}
|
|
100
|
+
writeMessageReplayState(statePath, state);
|
|
104
101
|
return false;
|
|
105
102
|
}
|
|
106
103
|
catch {
|
|
104
|
+
// The full path preserves the existing named replay error reporting.
|
|
107
105
|
return true;
|
|
108
106
|
}
|
|
109
107
|
}
|
|
@@ -125,52 +123,42 @@ function recordToolActivity(hqRoot, laneId) {
|
|
|
125
123
|
return `hq-lanes-drain: failed to record tool activity: ${err instanceof Error ? err.message : String(err)}\n`;
|
|
126
124
|
}
|
|
127
125
|
}
|
|
128
|
-
function finish(result
|
|
126
|
+
function finish(result) {
|
|
129
127
|
if (result.stdout)
|
|
130
128
|
process.stdout.write(result.stdout);
|
|
131
|
-
if (
|
|
132
|
-
process.stderr.write(
|
|
129
|
+
if (result.stderr)
|
|
130
|
+
process.stderr.write(result.stderr);
|
|
133
131
|
process.exitCode = result.exitCode;
|
|
134
132
|
}
|
|
135
|
-
async function runFullPath(input,
|
|
136
|
-
const
|
|
137
|
-
|
|
133
|
+
async function runFullPath(env, input, deps) {
|
|
134
|
+
const loadDropbox = deps.loadDropbox ?? (() => import("./lib/lanes/dropbox.js"));
|
|
135
|
+
const { runDrainHook } = await loadDropbox();
|
|
136
|
+
return runDrainHook(env, input);
|
|
138
137
|
}
|
|
139
|
-
async function
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
let inputError = "";
|
|
146
|
-
try {
|
|
147
|
-
input = parseHookInput(text);
|
|
148
|
-
}
|
|
149
|
-
catch (err) {
|
|
150
|
-
inputError = `hq-lanes-drain: ${err instanceof Error ? err.message : String(err)}\n`;
|
|
151
|
-
}
|
|
152
|
-
const laneId = process.env.HQ_LANE_ID ?? "";
|
|
138
|
+
export async function runLaneDrainHook(env, input = {}, inputError = "", deps = {}) {
|
|
139
|
+
const withInputError = (result) => ({
|
|
140
|
+
...result,
|
|
141
|
+
stderr: `${inputError}${result.stderr}`,
|
|
142
|
+
});
|
|
143
|
+
const laneId = env.HQ_LANE_ID ?? "";
|
|
153
144
|
if (laneId === "") {
|
|
154
|
-
|
|
155
|
-
return;
|
|
145
|
+
return withInputError({ exitCode: 0, stdout: "", stderr: "" });
|
|
156
146
|
}
|
|
157
|
-
const hqRoot =
|
|
147
|
+
const hqRoot = env.HQ_ROOT ?? "";
|
|
158
148
|
if (hqRoot === "" || !laneIdIsSafe(laneId)) {
|
|
159
|
-
await runFullPath(input,
|
|
160
|
-
return;
|
|
149
|
+
return withInputError(await runFullPath(env, input, deps));
|
|
161
150
|
}
|
|
162
151
|
const box = path.join(hqRoot, "workspace", "lanes-runs", laneId, "dropbox");
|
|
163
152
|
let noWork = false;
|
|
164
153
|
try {
|
|
165
154
|
noWork = !dropboxMayNeedDrain(box) &&
|
|
166
|
-
!replayMayNeedWork(box,
|
|
155
|
+
!replayMayNeedWork(box, env.HQ_LANE_TURN_ID ?? "", input, env);
|
|
167
156
|
}
|
|
168
157
|
catch {
|
|
169
158
|
// Filesystem uncertainty must use the full path so it retains its named errors.
|
|
170
159
|
}
|
|
171
160
|
if (!noWork) {
|
|
172
|
-
await runFullPath(input,
|
|
173
|
-
return;
|
|
161
|
+
return withInputError(await runFullPath(env, input, deps));
|
|
174
162
|
}
|
|
175
163
|
let stderr = recordToolActivity(hqRoot, laneId);
|
|
176
164
|
const directories = [
|
|
@@ -186,14 +174,30 @@ async function main() {
|
|
|
186
174
|
catch (err) {
|
|
187
175
|
const message = `Failed to open drop box ${box}: ${err instanceof Error ? err.message : String(err)}`;
|
|
188
176
|
stderr += `hq-lanes-drain: ${message}\n`;
|
|
189
|
-
|
|
190
|
-
|
|
177
|
+
return withInputError({ exitCode: 1, stdout: "", stderr });
|
|
178
|
+
}
|
|
179
|
+
return withInputError({ exitCode: 0, stdout: "", stderr });
|
|
180
|
+
}
|
|
181
|
+
async function main() {
|
|
182
|
+
const chunks = [];
|
|
183
|
+
for await (const chunk of process.stdin)
|
|
184
|
+
chunks.push(Buffer.from(chunk));
|
|
185
|
+
const text = Buffer.concat(chunks).toString("utf8");
|
|
186
|
+
let input = {};
|
|
187
|
+
let inputError = "";
|
|
188
|
+
try {
|
|
189
|
+
input = parseHookInput(text);
|
|
191
190
|
}
|
|
192
|
-
|
|
191
|
+
catch (err) {
|
|
192
|
+
inputError = `hq-lanes-drain: ${err instanceof Error ? err.message : String(err)}\n`;
|
|
193
|
+
}
|
|
194
|
+
finish(await runLaneDrainHook(process.env, input, inputError));
|
|
195
|
+
}
|
|
196
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
197
|
+
main().catch((err) => {
|
|
198
|
+
process.stderr.write(`hq-lanes-drain: hook_runner_failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
199
|
+
// A hook runner failure must not stop the provider's requested tool call.
|
|
200
|
+
process.exitCode = 0;
|
|
201
|
+
});
|
|
193
202
|
}
|
|
194
|
-
main().catch((err) => {
|
|
195
|
-
process.stderr.write(`hq-lanes-drain: hook_runner_failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
196
|
-
// A hook runner failure must not stop the provider's requested tool call.
|
|
197
|
-
process.exitCode = 0;
|
|
198
|
-
});
|
|
199
203
|
//# sourceMappingURL=lanes-drain-hook.js.map
|
|
@@ -42,7 +42,7 @@ export declare const MERGE_COPIES_FLAG = "checkpoint.ontology.merge-copies";
|
|
|
42
42
|
* names differ only in spacing, case or punctuation (CO-65).
|
|
43
43
|
*/
|
|
44
44
|
export declare const MERGE_VARIANTS_FLAG = "checkpoint.ontology.merge-variants";
|
|
45
|
-
export type CopySkipReason = "no_original" | "original_invalid" | "identity_differs" | "not_in_duplicate_group" | "group_size" | "type_differs" | "names_differ" | "numbered_copy";
|
|
45
|
+
export type CopySkipReason = "no_original" | "original_invalid" | "identity_differs" | "not_in_duplicate_group" | "group_size" | "type_differs" | "names_differ" | "numbered_copy" | "not_listed";
|
|
46
46
|
export type PairKind = "numbered" | "variants";
|
|
47
47
|
export interface CopySkip {
|
|
48
48
|
copy: string;
|
|
@@ -55,6 +55,11 @@ export interface CopyPair {
|
|
|
55
55
|
/** Company-relative path of the original it merges into. */
|
|
56
56
|
original: string;
|
|
57
57
|
}
|
|
58
|
+
/** One entry of a `--only` list: an exact copy -> original pair. */
|
|
59
|
+
export interface ListedPair {
|
|
60
|
+
copy: string;
|
|
61
|
+
original: string;
|
|
62
|
+
}
|
|
58
63
|
export interface CopyPlan extends CopyPair {
|
|
59
64
|
add_aliases: string[];
|
|
60
65
|
/** Copy names the original lacks that another entity already holds, so they do not move. */
|
|
@@ -98,6 +103,8 @@ export interface MergeCopiesReport {
|
|
|
98
103
|
path: string;
|
|
99
104
|
error: string;
|
|
100
105
|
}[];
|
|
106
|
+
/** Listed entries the finder did not produce. Absent unless `--only` was set. */
|
|
107
|
+
only_unmatched?: ListedPair[];
|
|
101
108
|
/** Entity-file links whose target file does not exist. */
|
|
102
109
|
broken_links_before: number;
|
|
103
110
|
broken_links_after: number;
|
|
@@ -114,6 +121,8 @@ export interface MergeCopiesOptions {
|
|
|
114
121
|
pairKind?: PairKind;
|
|
115
122
|
/** Handle at most this many copies (sorted by path). */
|
|
116
123
|
limit?: number;
|
|
124
|
+
/** Handle only these exact pairs; every other found pair is skipped as `not_listed`. */
|
|
125
|
+
only?: ListedPair[];
|
|
117
126
|
now: Date;
|
|
118
127
|
deleteVaultKey?: DeleteVaultKey;
|
|
119
128
|
}
|
|
@@ -500,7 +500,10 @@ export async function mergeCopies(options) {
|
|
|
500
500
|
const index = buildEntityIndex(company.dir);
|
|
501
501
|
const pairKind = options.pairKind ?? "numbered";
|
|
502
502
|
const found = pairKind === "variants" ? findVariantPairs(index) : findCopyPairs(index);
|
|
503
|
-
const
|
|
503
|
+
const listed = options.only
|
|
504
|
+
? found.pairs.filter((pair) => options.only.some((entry) => entry.copy === pair.copy && entry.original === pair.original))
|
|
505
|
+
: found.pairs;
|
|
506
|
+
const pairs = options.limit !== undefined ? listed.slice(0, Math.max(0, options.limit)) : listed;
|
|
504
507
|
const before = scanLinks(company).broken;
|
|
505
508
|
const { plans, merges, finals, notReady } = plan(company, index, pairs, found.pairs, now);
|
|
506
509
|
const report = {
|
|
@@ -511,7 +514,19 @@ export async function mergeCopies(options) {
|
|
|
511
514
|
duplicate_groups_after: 0,
|
|
512
515
|
copies_found: found.pairs.length,
|
|
513
516
|
plans,
|
|
514
|
-
skipped:
|
|
517
|
+
skipped: options.only
|
|
518
|
+
? [
|
|
519
|
+
...found.skipped,
|
|
520
|
+
...found.pairs
|
|
521
|
+
.filter((pair) => !listed.includes(pair))
|
|
522
|
+
.map((pair) => ({ copy: pair.copy, reason: "not_listed" })),
|
|
523
|
+
]
|
|
524
|
+
: found.skipped,
|
|
525
|
+
...(options.only
|
|
526
|
+
? {
|
|
527
|
+
only_unmatched: options.only.filter((entry) => !found.pairs.some((pair) => pair.copy === entry.copy && pair.original === entry.original)),
|
|
528
|
+
}
|
|
529
|
+
: {}),
|
|
515
530
|
merged: [],
|
|
516
531
|
relinked: [],
|
|
517
532
|
deleted: [],
|
|
@@ -20,99 +20,12 @@ export declare const ONTOLOGY_FLAG = "checkpoint.ontology";
|
|
|
20
20
|
* 6.18.7 or later; see ./sync-client.ts.
|
|
21
21
|
*/
|
|
22
22
|
export declare const ONTOLOGY_SYNC_GATED_FLAG = "checkpoint.ontology.sync-gated";
|
|
23
|
-
export declare const COMPANY_BRIEF_FLAG = "checkpoint.ontology.company-brief";
|
|
24
|
-
/**
|
|
25
|
-
* Applies only when the payload's sibling backend is grok. On gold-v2, Claude
|
|
26
|
-
* with stripped inputs lost 0.139 recall; Grok with stripped inputs gained
|
|
27
|
-
* precision without losing recall. A machine that falls back from grok keeps
|
|
28
|
-
* the annotated inputs.
|
|
29
|
-
*/
|
|
30
|
-
export declare const IGNORE_ANNOTATIONS_FLAG = "checkpoint.ontology.ignore-annotations";
|
|
31
|
-
/**
|
|
32
|
-
* With `--backend auto`, `hq core checkpoint` tries grok first for this
|
|
33
|
-
* company. Read here, in the detached sibling, and recorded in the company's
|
|
34
|
-
* state directory, so the foreground checkpoint makes no network call.
|
|
35
|
-
*/
|
|
36
|
-
export declare const GROK_BACKEND_FLAG = "checkpoint.ontology.grok-backend";
|
|
37
23
|
/**
|
|
38
24
|
* Several writers in one company vault (design section 20): record written
|
|
39
25
|
* entity bytes in the outbox, merge back set-aside versions, and report
|
|
40
26
|
* duplicate entities.
|
|
41
27
|
*/
|
|
42
28
|
export declare const MULTI_WRITER_FLAG = "checkpoint.ontology.multi-writer";
|
|
43
|
-
/**
|
|
44
|
-
* A candidate that links to an existing entity adds no aliases to it; the plan
|
|
45
|
-
* carries `link_aliases: false` and apply honours it. CO-42: most aliases
|
|
46
|
-
* written through links on the CO-41 runs were other things the text named
|
|
47
|
-
* next to the entity (`Publisher` on indigo, `Stripe Connect` on Stripe), and
|
|
48
|
-
* entity-hints multiplied them (22 aliases on a flag-off run, 45 and 125 on two
|
|
49
|
-
* hinted runs).
|
|
50
|
-
*/
|
|
51
|
-
export declare const NO_LINK_ALIASES_FLAG = "checkpoint.ontology.no-link-aliases";
|
|
52
|
-
/**
|
|
53
|
-
* A person the input and the company's entities give only a first name for is
|
|
54
|
-
* named by that first name instead of being left out. CO-44: on the CO-41
|
|
55
|
-
* grok-4.6 runs the model read "not a first name alone" as "skip them", and
|
|
56
|
-
* dropped people such as Amir and Sebastian, whose own entities carry only a
|
|
57
|
-
* first name. People were 24 of the 138 missed gold entities over three runs.
|
|
58
|
-
* The plan carries `first_name_people: true`, and apply then defers a one-word
|
|
59
|
-
* person whose word starts an existing person's fuller name or alias, since
|
|
60
|
-
* the model cannot see that `John Smith` already exists.
|
|
61
|
-
*/
|
|
62
|
-
export declare const FIRST_NAME_PEOPLE_FLAG = "checkpoint.ontology.first-name-people";
|
|
63
|
-
/**
|
|
64
|
-
* A knowledge input does not link to a concept entity whose canonical name is
|
|
65
|
-
* also the canonical name of an entity of another type (`concept/hq-secrets`
|
|
66
|
-
* beside `project/hq-secrets`). The plan carries
|
|
67
|
-
* `skip_ambiguous_concept_links: true`, and apply rejects the link as
|
|
68
|
-
* `ambiguous_type`. CO-46: 34 of the 73 false positives on the knowledge
|
|
69
|
-
* fixtures were such concept links; skipping them raised precision from 0.733
|
|
70
|
-
* to 0.803 and cost recall 0.023 on a replay of six runs.
|
|
71
|
-
*/
|
|
72
|
-
export declare const SKIP_AMBIGUOUS_CONCEPT_LINKS_FLAG = "checkpoint.ontology.skip-ambiguous-concept-links";
|
|
73
|
-
/**
|
|
74
|
-
* A knowledge link to the weaker of two entities that share a canonical name
|
|
75
|
-
* across types is redirected to the sibling other entity files link to
|
|
76
|
-
* (`project/build-your-own-agi` beside `concept/build-your-own-agi`, or
|
|
77
|
-
* `project/hq-core 2` beside `project/hq-core` when a concept also holds the
|
|
78
|
-
* name). The plan carries `dominant_sibling_link: true`, and apply reports
|
|
79
|
-
* the move in `dominant_sibling_redirects` without writing the entity files.
|
|
80
|
-
* CO-60: a replay of six runs raised knowledge precision from 0.725 to 0.754
|
|
81
|
-
* and recall from 0.784 to 0.816, and never changed a create.
|
|
82
|
-
*/
|
|
83
|
-
export declare const DOMINANT_SIBLING_LINK_FLAG = "checkpoint.ontology.dominant-sibling-link";
|
|
84
|
-
/**
|
|
85
|
-
* A knowledge link to a non-person entity that 50 or more
|
|
86
|
-
* other entity files link to is rejected as `hub_under_mentioned` when the
|
|
87
|
-
* input names it fewer than 2 times (whole word, any case, canonical name
|
|
88
|
-
* only). The plan carries `hub_mention_floor: true`. CO-62: a replay of six
|
|
89
|
-
* runs raised knowledge precision from 0.753 to 0.781 at the same recall.
|
|
90
|
-
*/
|
|
91
|
-
export declare const HUB_MENTION_FLOOR_FLAG = "checkpoint.ontology.hub-mention-floor";
|
|
92
|
-
/**
|
|
93
|
-
* A knowledge concept candidate whose name is an alias of more than one
|
|
94
|
-
* entity, and which apply would defer as `ambiguous_alias`, links instead to
|
|
95
|
-
* the one project whose canonical name it is (`HQ` typed as a concept beside
|
|
96
|
-
* `project/HQ` and several concepts aliased `HQ`). The plan carries
|
|
97
|
-
* `concept_project_link: true`. CO-51: over six runs of the shipping config
|
|
98
|
-
* the model typed HQ as a concept in some runs and not others, and the
|
|
99
|
-
* deferral lost 61 gold links; a replay of this rule raised recall from 0.639
|
|
100
|
-
* to 0.709 at precision 0.747 to 0.731 (61 of the 63 added links are HQ).
|
|
101
|
-
*/
|
|
102
|
-
export declare const CONCEPT_PROJECT_LINK_FLAG = "checkpoint.ontology.concept-project-link";
|
|
103
|
-
/**
|
|
104
|
-
* A knowledge link to the company's own company entity, or a
|
|
105
|
-
* concept-project-link redirect, needs evidence that names the entity as a
|
|
106
|
-
* word: the candidate name or one of its aliases, not inside a path, domain,
|
|
107
|
-
* handle, tag or identifier (`indigoai-us/hq`, `getindigo.ai`,
|
|
108
|
-
* `scope: company:indigo`). The plan carries `named_evidence: true`, and apply
|
|
109
|
-
* rejects such a link as `unnamed_in_evidence`. CO-57: on the six CO-52 runs,
|
|
110
|
-
* 67 of 143 false positives were links to company/indigo and project/HQ, most
|
|
111
|
-
* on evidence that only held the name inside an identifier; a replay of this
|
|
112
|
-
* rule raised precision on the concept-project-link arm from 0.705 to 0.750
|
|
113
|
-
* and lost no gold link.
|
|
114
|
-
*/
|
|
115
|
-
export declare const NAMED_EVIDENCE_FLAG = "checkpoint.ontology.named-evidence";
|
|
116
29
|
/**
|
|
117
30
|
* CO-59: consume the files touched by company checkpoints that were queued
|
|
118
31
|
* behind a busy sibling (see queued-touches.ts). On, the plan adds the
|
|
@@ -250,7 +163,6 @@ export type PlanOutcome = {
|
|
|
250
163
|
companyBrief?: CompanyBriefResult;
|
|
251
164
|
};
|
|
252
165
|
export declare const READ_PATH_INSTRUCTION = "When an input has read_path, read that file (relative to the run directory) instead of path, with the same line range. It is the same text with an older annotator's entity lists and entity links removed.";
|
|
253
|
-
export declare const PERSON_NAME_INSTRUCTION = "Name a person by the full name the input or the company's entities give, not a first name alone.";
|
|
254
166
|
export declare const FIRST_NAME_PEOPLE_INSTRUCTION = "Name a person by the full name the input or the company's entities give. When neither gives more than a first name, name the person by that first name; do not leave them out.";
|
|
255
167
|
export declare function ledgerPath(hqRoot: string, company: OntologyCompany): string;
|
|
256
168
|
export declare function readLedger(hqRoot: string, company: OntologyCompany): Ledger | null;
|