@indigoai-us/hq-cli 5.345.122 → 5.345.124

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.
Files changed (53) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +15 -0
  3. package/assets/scaffold/core/scripts/handoff-sync-prefetch.sh +121 -0
  4. package/assets/scaffold/core/scripts/handoff-sync-publish.sh +124 -0
  5. package/assets/scaffold/core/scripts/knowledge-pulse-lock.sh +27 -0
  6. package/assets/scaffold/core/scripts/lib/session-skill-catalog.sh +226 -0
  7. package/assets/scaffold/core/scripts/skill-installed.sh +43 -0
  8. package/assets/scaffold/core/scripts/work-mesh-project-registration-offer.sh +77 -0
  9. package/assets/scaffold/core/scripts/workflow-gate.sh +247 -0
  10. package/dist/command-catalog.generated.d.ts +332 -0
  11. package/dist/command-catalog.generated.js +402 -0
  12. package/dist/command-registration-plan.d.ts +6 -0
  13. package/dist/command-registration-plan.js +1 -0
  14. package/dist/commands/core-bucket-c-handoff-sync-fast.d.ts +3 -0
  15. package/dist/commands/core-bucket-c-handoff-sync-fast.js +16 -0
  16. package/dist/commands/core-bucket-c-handoff-sync-native.d.ts +45 -0
  17. package/dist/commands/core-bucket-c-handoff-sync-native.js +300 -0
  18. package/dist/commands/core-bucket-c-small-fast.d.ts +5 -0
  19. package/dist/commands/core-bucket-c-small-fast.js +19 -0
  20. package/dist/commands/core-bucket-c-small-native.d.ts +11 -0
  21. package/dist/commands/core-bucket-c-small-native.js +197 -0
  22. package/dist/commands/core-bucket-c-work-mesh-offer-native.d.ts +7 -0
  23. package/dist/commands/core-bucket-c-work-mesh-offer-native.js +147 -0
  24. package/dist/commands/core-bucket-c-workflow-gate-fast.d.ts +3 -0
  25. package/dist/commands/core-bucket-c-workflow-gate-fast.js +17 -0
  26. package/dist/commands/core-bucket-c-workflow-gate-native.d.ts +7 -0
  27. package/dist/commands/core-bucket-c-workflow-gate-native.js +250 -0
  28. package/dist/commands/core-derive-trigger-facts-native.js +4 -1
  29. package/dist/commands/core.js +37 -0
  30. package/dist/commands/deploy-comments.d.ts +2 -0
  31. package/dist/commands/deploy-comments.js +2 -2
  32. package/dist/commands/deploy-list.d.ts +22 -0
  33. package/dist/commands/deploy-list.js +153 -0
  34. package/dist/commands/lanes.js +41 -2
  35. package/dist/commands/mesh.js +457 -16
  36. package/dist/fast-scaffold.js +74 -0
  37. package/dist/lib/core-utils/session-company.d.ts +3 -0
  38. package/dist/lib/core-utils/session-company.js +78 -0
  39. package/dist/lib/lanes/quota-notice.d.ts +24 -0
  40. package/dist/lib/lanes/quota-notice.js +61 -0
  41. package/dist/lib/lanes/quota.d.ts +44 -8
  42. package/dist/lib/lanes/quota.js +187 -45
  43. package/dist/lib/lanes/reconcile.js +17 -1
  44. package/dist/lib/lanes/watcher.js +13 -1
  45. package/dist/lib/mesh/api.d.ts +2 -0
  46. package/dist/lib/mesh/api.js +3 -0
  47. package/dist/lib/mesh/client.d.ts +6 -1
  48. package/dist/lib/mesh/client.js +18 -5
  49. package/dist/lib/mesh/team-coordination.d.ts +106 -0
  50. package/dist/lib/mesh/team-coordination.js +297 -0
  51. package/dist/lib/work-context/auto-bind.d.ts +75 -0
  52. package/dist/lib/work-context/auto-bind.js +245 -0
  53. package/package.json +1 -1
@@ -137,6 +137,8 @@ export interface StoryPatch {
137
137
  status?: string;
138
138
  assigneeUid?: string;
139
139
  clearAssignee?: boolean;
140
+ /** Override another holder's assignment (server answers 409 otherwise). */
141
+ force?: boolean;
140
142
  }
141
143
  export interface StoryPatchResult {
142
144
  projectId: string;
@@ -56,6 +56,7 @@ export async function meshJson(token, path, init = {}) {
56
56
  ].filter(Boolean);
57
57
  const failure = new Error(`${res.status} ${code}${detail}${context.length ? ` (${context.join("; ")})` : ""}`);
58
58
  failure.status = res.status;
59
+ failure.body = data;
59
60
  if (typeof err.code === "string" && err.code)
60
61
  failure.code = err.code;
61
62
  throw failure;
@@ -398,6 +399,8 @@ export async function patchStory(token, companyUid, projectId, storyId, patch) {
398
399
  body.assigneeUid = patch.assigneeUid;
399
400
  if (patch.clearAssignee)
400
401
  body.clearAssignee = true;
402
+ if (patch.force)
403
+ body.force = true;
401
404
  const view = asRecord(await meshJson(token, `/v1/work-mesh/projects/${encodeURIComponent(projectId)}/stories/${encodeURIComponent(storyId)}`, {
402
405
  method: "PATCH",
403
406
  body,
@@ -114,6 +114,11 @@ export declare function fetchCompanyLive(token: string, companyUid: string, opts
114
114
  cursor?: string;
115
115
  get?: (path: string, query: Record<string, string>) => Promise<unknown>;
116
116
  }): Promise<CompanyLiveReadResponse>;
117
- export declare function formatCompanyLiveTable(live: CompanyLiveReadResponse, companyLabel: string): string[];
117
+ /**
118
+ * Fill participants[].displayName from the local directory cache when the
119
+ * server omitted it (older hq-pro builds do not send it).
120
+ */
121
+ export declare function withParticipantDisplayNames(live: CompanyLiveReadResponse, names: ReadonlyMap<string, string>): CompanyLiveReadResponse;
122
+ export declare function formatCompanyLiveTable(live: CompanyLiveReadResponse, companyLabel: string, names?: ReadonlyMap<string, string>): string[];
118
123
  export type { OrganizeSubmitDecision, OrganizeReceipt, CandidatesResponse, MigrateReceipt, PostMigrateFn, };
119
124
  //# sourceMappingURL=client.d.ts.map
@@ -290,22 +290,35 @@ export async function fetchCompanyLive(token, companyUid, opts) {
290
290
  : await meshJson(token, "/v1/work-mesh/live", { query });
291
291
  return data;
292
292
  }
293
- export function formatCompanyLiveTable(live, companyLabel) {
293
+ /**
294
+ * Fill participants[].displayName from the local directory cache when the
295
+ * server omitted it (older hq-pro builds do not send it).
296
+ */
297
+ export function withParticipantDisplayNames(live, names) {
298
+ return {
299
+ ...live,
300
+ participants: (live.participants ?? []).map((p) => {
301
+ const name = p.displayName?.trim() || names.get(p.actorUid);
302
+ return name ? { ...p, displayName: name } : p;
303
+ }),
304
+ };
305
+ }
306
+ export function formatCompanyLiveTable(live, companyLabel, names = new Map()) {
294
307
  const lines = [
295
308
  `Work mesh live: ${companyLabel} generatedAt=${live.generatedAt}`,
296
- "actorUid\tpresence\tdisplayName\tsessionId\tstatus\tcontext\tproject\ttask\tturns",
309
+ "name presence sessionId status context project task turns",
297
310
  ];
298
311
  for (const p of live.participants ?? []) {
312
+ const who = p.displayName?.trim() || names.get(p.actorUid) || p.actorUid;
299
313
  const sessions = p.sessions ?? [];
300
314
  if (sessions.length === 0) {
301
- lines.push(`${p.actorUid}\t${p.presence ?? "?"}\t${p.displayName ?? ""}\t-\t-\t-\t-\t-\t-`);
315
+ lines.push(`${who}\t${p.presence ?? "?"}\t-\t-\t-\t-\t-\t-`);
302
316
  continue;
303
317
  }
304
318
  for (const s of sessions) {
305
319
  lines.push([
306
- p.actorUid,
320
+ who,
307
321
  p.presence ?? "?",
308
- p.displayName ?? "",
309
322
  s.sessionId,
310
323
  s.status ?? "?",
311
324
  s.contextStatus ?? "?",
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Work Mesh team coordination (WMTC-7 / WMTC-8): story claims, overlap,
3
+ * project reassignment, assignee suggestions, and the presence roster.
4
+ *
5
+ * Every route here except the story PATCH is new on hq-pro. Callers treat a
6
+ * 404/405 without an error code as "this server does not have the route yet"
7
+ * and degrade to one plain line instead of failing.
8
+ */
9
+ export declare const STORY_ASSIGNEE_CONFLICT = "STORY_ASSIGNEE_CONFLICT";
10
+ /**
11
+ * True when the server does not know the route: 405, or a 404 that carries no
12
+ * error code. A coded 404 (for example PROJECT_NOT_FOUND) is a real answer.
13
+ */
14
+ export declare function isRouteUnavailable(err: unknown): boolean;
15
+ export declare function unavailableLine(feature: string): string;
16
+ export interface StoryAssigneeConflict {
17
+ code: typeof STORY_ASSIGNEE_CONFLICT;
18
+ error: string;
19
+ holderUid: string;
20
+ holderDisplayName?: string;
21
+ }
22
+ /** Extract a structured conflict from a 409 meshJson failure, if it is one. */
23
+ export declare function storyConflictFromError(err: unknown): StoryAssigneeConflict | null;
24
+ export declare function formatStoryConflictLine(storyId: string, conflict: StoryAssigneeConflict, names?: NameDirectory): string;
25
+ export type NameDirectory = Map<string, string>;
26
+ export interface DirectoryPerson {
27
+ uid: string;
28
+ displayName?: string;
29
+ email?: string;
30
+ }
31
+ export declare function peopleFromPayload(payload: unknown): DirectoryPerson[];
32
+ /** People known from the local contacts/directory cache. Never throws. */
33
+ export declare function loadCachedPeople(cacheRoot?: string): DirectoryPerson[];
34
+ export declare function loadNameDirectory(cacheRoot?: string): NameDirectory;
35
+ export declare function displayNameFor(uid: string, serverName: string | undefined, names: NameDirectory): string;
36
+ export type PersonResolution = {
37
+ ok: true;
38
+ uid: string;
39
+ displayName?: string;
40
+ } | {
41
+ ok: false;
42
+ reason: "none" | "ambiguous";
43
+ candidates: DirectoryPerson[];
44
+ };
45
+ /** Resolve a uid, email, or display name against a list of people. */
46
+ export declare function resolvePerson(query: string, people: DirectoryPerson[]): PersonResolution;
47
+ export interface MeshOverlap {
48
+ actorUid: string;
49
+ displayName?: string;
50
+ actorType?: string;
51
+ sessionId: string;
52
+ repo: string;
53
+ branch: string;
54
+ projectId?: string;
55
+ storyId?: string;
56
+ reasons: string[];
57
+ sharedFiles: string[];
58
+ lastSeenAt?: string;
59
+ }
60
+ export interface OverlapRequest {
61
+ companyUid: string;
62
+ sessionId: string;
63
+ repo: string;
64
+ branch: string;
65
+ projectId?: string;
66
+ storyId?: string;
67
+ files?: string[];
68
+ }
69
+ export declare const OVERLAP_MAX_FILES = 200;
70
+ export declare function postOverlap(token: string, req: OverlapRequest): Promise<MeshOverlap[]>;
71
+ export declare function formatOverlapLines(overlaps: MeshOverlap[], names: NameDirectory): string[];
72
+ export interface ReassignResult {
73
+ projectId: string;
74
+ ownerUid: string;
75
+ previousOwnerUid: string | null;
76
+ reassignedStories: string[];
77
+ }
78
+ export declare function postProjectReassign(token: string, projectId: string, body: {
79
+ companyUid: string;
80
+ toUid: string;
81
+ includeOpenStories?: boolean;
82
+ note?: string;
83
+ }): Promise<ReassignResult>;
84
+ export interface SuggestionCandidate {
85
+ actorUid: string;
86
+ displayName?: string;
87
+ actorType?: string;
88
+ score: number;
89
+ idle: boolean;
90
+ reasons: string[];
91
+ }
92
+ export declare function fetchStorySuggestions(token: string, companyUid: string, projectId: string, storyId: string): Promise<SuggestionCandidate[]>;
93
+ export declare function formatSuggestionLines(storyId: string, candidates: SuggestionCandidate[], names: NameDirectory): string[];
94
+ export interface RosterMember {
95
+ actorUid: string;
96
+ displayName?: string;
97
+ actorType?: string;
98
+ daemonLastSeenAt: string | null;
99
+ lastSessionAt: string | null;
100
+ boundSessions: number;
101
+ unboundSessions: number;
102
+ }
103
+ export declare function fetchRoster(token: string, companyUid: string): Promise<RosterMember[]>;
104
+ export declare function rosterFlags(member: RosterMember): string[];
105
+ export declare function formatRosterLines(members: RosterMember[], companyLabel: string, names: NameDirectory): string[];
106
+ //# sourceMappingURL=team-coordination.d.ts.map
@@ -0,0 +1,297 @@
1
+ /**
2
+ * Work Mesh team coordination (WMTC-7 / WMTC-8): story claims, overlap,
3
+ * project reassignment, assignee suggestions, and the presence roster.
4
+ *
5
+ * Every route here except the story PATCH is new on hq-pro. Callers treat a
6
+ * 404/405 without an error code as "this server does not have the route yet"
7
+ * and degrade to one plain line instead of failing.
8
+ */
9
+ import * as fs from "node:fs";
10
+ import * as path from "node:path";
11
+ import { meshJson } from "./api.js";
12
+ import { meshCacheRoot } from "./cache.js";
13
+ export const STORY_ASSIGNEE_CONFLICT = "STORY_ASSIGNEE_CONFLICT";
14
+ function asRecord(value) {
15
+ return value && typeof value === "object" && !Array.isArray(value)
16
+ ? value
17
+ : null;
18
+ }
19
+ function str(value) {
20
+ return typeof value === "string" && value.trim() ? value : undefined;
21
+ }
22
+ function strArray(value) {
23
+ return Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
24
+ }
25
+ /**
26
+ * True when the server does not know the route: 405, or a 404 that carries no
27
+ * error code. A coded 404 (for example PROJECT_NOT_FOUND) is a real answer.
28
+ */
29
+ export function isRouteUnavailable(err) {
30
+ const e = err;
31
+ if (!e || typeof e !== "object")
32
+ return false;
33
+ if (e.status === 405)
34
+ return true;
35
+ return e.status === 404 && !e.code;
36
+ }
37
+ export function unavailableLine(feature) {
38
+ return `${feature} is not available on this server yet`;
39
+ }
40
+ /** Extract a structured conflict from a 409 meshJson failure, if it is one. */
41
+ export function storyConflictFromError(err) {
42
+ const e = err;
43
+ if (!e || typeof e !== "object" || e.status !== 409 || e.code !== STORY_ASSIGNEE_CONFLICT)
44
+ return null;
45
+ const body = asRecord(e.body) ?? {};
46
+ const holderDisplayName = str(body.holderDisplayName);
47
+ return {
48
+ code: STORY_ASSIGNEE_CONFLICT,
49
+ error: str(body.error) ?? e.message,
50
+ holderUid: str(body.holderUid) ?? "",
51
+ ...(holderDisplayName ? { holderDisplayName } : {}),
52
+ };
53
+ }
54
+ export function formatStoryConflictLine(storyId, conflict, names) {
55
+ const name = conflict.holderDisplayName || names?.get(conflict.holderUid) || "another teammate";
56
+ const uid = conflict.holderUid ? ` (${conflict.holderUid})` : "";
57
+ return `Story ${storyId} is assigned to ${name}${uid}. Ask them to hand it off, or re-run with --force.`;
58
+ }
59
+ function readJson(file) {
60
+ try {
61
+ return JSON.parse(fs.readFileSync(file, "utf8"));
62
+ }
63
+ catch {
64
+ return null;
65
+ }
66
+ }
67
+ export function peopleFromPayload(payload) {
68
+ const rec = asRecord(payload);
69
+ const rows = Array.isArray(payload)
70
+ ? payload
71
+ : [
72
+ ...(Array.isArray(rec?.contacts) ? rec.contacts : []),
73
+ ...(Array.isArray(rec?.rows) ? rec.rows : []),
74
+ ...(Array.isArray(rec?.members) ? rec.members : []),
75
+ ];
76
+ const out = [];
77
+ for (const row of rows) {
78
+ const r = asRecord(row);
79
+ if (!r)
80
+ continue;
81
+ const uid = str(r.personUid) ?? str(r.actorUid) ?? str(r.uid);
82
+ if (!uid || !/^(prs|agt)_/.test(uid))
83
+ continue;
84
+ const displayName = str(r.displayName);
85
+ const email = str(r.email);
86
+ out.push({ uid, ...(displayName ? { displayName } : {}), ...(email ? { email } : {}) });
87
+ }
88
+ return out;
89
+ }
90
+ /** People known from the local contacts/directory cache. Never throws. */
91
+ export function loadCachedPeople(cacheRoot = meshCacheRoot()) {
92
+ const people = new Map();
93
+ for (const dir of ["contacts", "directory"]) {
94
+ let files;
95
+ try {
96
+ files = fs.readdirSync(path.join(cacheRoot, dir)).filter((f) => f.endsWith(".json"));
97
+ }
98
+ catch {
99
+ continue;
100
+ }
101
+ for (const file of files) {
102
+ for (const person of peopleFromPayload(readJson(path.join(cacheRoot, dir, file)))) {
103
+ const prior = people.get(person.uid);
104
+ const displayName = prior?.displayName ?? person.displayName;
105
+ const email = prior?.email ?? person.email;
106
+ people.set(person.uid, {
107
+ uid: person.uid,
108
+ ...(displayName ? { displayName } : {}),
109
+ ...(email ? { email } : {}),
110
+ });
111
+ }
112
+ }
113
+ }
114
+ return [...people.values()];
115
+ }
116
+ export function loadNameDirectory(cacheRoot = meshCacheRoot()) {
117
+ const names = new Map();
118
+ for (const person of loadCachedPeople(cacheRoot)) {
119
+ if (person.displayName)
120
+ names.set(person.uid, person.displayName);
121
+ }
122
+ return names;
123
+ }
124
+ export function displayNameFor(uid, serverName, names) {
125
+ return serverName?.trim() || names.get(uid) || uid;
126
+ }
127
+ /** Resolve a uid, email, or display name against a list of people. */
128
+ export function resolvePerson(query, people) {
129
+ const q = query.trim();
130
+ if (/^(prs|agt)_/.test(q)) {
131
+ const known = people.find((p) => p.uid === q);
132
+ return { ok: true, uid: q, ...(known?.displayName ? { displayName: known.displayName } : {}) };
133
+ }
134
+ const lower = q.toLowerCase();
135
+ const exact = people.filter((p) => p.email?.toLowerCase() === lower || p.displayName?.toLowerCase() === lower);
136
+ const matches = exact.length
137
+ ? exact
138
+ : people.filter((p) => p.displayName?.toLowerCase().split(/\s+/)[0] === lower);
139
+ if (matches.length === 1) {
140
+ const m = matches[0];
141
+ return { ok: true, uid: m.uid, ...(m.displayName ? { displayName: m.displayName } : {}) };
142
+ }
143
+ return { ok: false, reason: matches.length ? "ambiguous" : "none", candidates: matches };
144
+ }
145
+ export const OVERLAP_MAX_FILES = 200;
146
+ export async function postOverlap(token, req) {
147
+ const body = {
148
+ companyUid: req.companyUid,
149
+ sessionId: req.sessionId,
150
+ repo: req.repo,
151
+ branch: req.branch,
152
+ };
153
+ if (req.projectId)
154
+ body.projectId = req.projectId;
155
+ if (req.storyId)
156
+ body.storyId = req.storyId;
157
+ if (req.files?.length)
158
+ body.files = req.files.slice(0, OVERLAP_MAX_FILES);
159
+ const data = asRecord(await meshJson(token, "/v1/work-mesh/overlap", { method: "POST", body }));
160
+ const rows = Array.isArray(data?.overlaps) ? data.overlaps : [];
161
+ return rows.flatMap((row) => {
162
+ const r = asRecord(row);
163
+ const actorUid = str(r?.actorUid);
164
+ if (!r || !actorUid)
165
+ return [];
166
+ const displayName = str(r.displayName);
167
+ const actorType = str(r.actorType);
168
+ const projectId = str(r.projectId);
169
+ const storyId = str(r.storyId);
170
+ const lastSeenAt = str(r.lastSeenAt);
171
+ return [{
172
+ actorUid,
173
+ ...(displayName ? { displayName } : {}),
174
+ ...(actorType ? { actorType } : {}),
175
+ sessionId: str(r.sessionId) ?? "",
176
+ repo: str(r.repo) ?? "",
177
+ branch: str(r.branch) ?? "",
178
+ ...(projectId ? { projectId } : {}),
179
+ ...(storyId ? { storyId } : {}),
180
+ reasons: strArray(r.reasons),
181
+ sharedFiles: strArray(r.sharedFiles),
182
+ ...(lastSeenAt ? { lastSeenAt } : {}),
183
+ }];
184
+ });
185
+ }
186
+ export function formatOverlapLines(overlaps, names) {
187
+ if (overlaps.length === 0)
188
+ return ["No overlapping work found."];
189
+ const lines = [`${overlaps.length} overlapping session${overlaps.length === 1 ? "" : "s"}:`];
190
+ for (const o of overlaps) {
191
+ const who = displayNameFor(o.actorUid, o.displayName, names);
192
+ const where = [
193
+ o.repo ? `${o.repo}@${o.branch}` : "",
194
+ o.projectId ? `project ${o.projectId}` : "",
195
+ o.storyId ? `story ${o.storyId}` : "",
196
+ ].filter(Boolean).join(", ");
197
+ lines.push(`- ${who} in session ${o.sessionId}: same ${o.reasons.join(" + ") || "work"}${where ? ` (${where})` : ""}`);
198
+ if (o.sharedFiles.length) {
199
+ const shown = o.sharedFiles.slice(0, 10);
200
+ const more = o.sharedFiles.length - shown.length;
201
+ lines.push(` shared files: ${shown.join(", ")}${more > 0 ? ` (+${more} more)` : ""}`);
202
+ }
203
+ }
204
+ return lines;
205
+ }
206
+ export async function postProjectReassign(token, projectId, body) {
207
+ const payload = { companyUid: body.companyUid, toUid: body.toUid };
208
+ if (body.includeOpenStories)
209
+ payload.includeOpenStories = true;
210
+ if (body.note)
211
+ payload.note = body.note;
212
+ const data = asRecord(await meshJson(token, `/v1/work-mesh/projects/${encodeURIComponent(projectId)}/reassign`, { method: "POST", body: payload }));
213
+ return {
214
+ projectId: str(data?.projectId) ?? projectId,
215
+ ownerUid: str(data?.ownerUid) ?? body.toUid,
216
+ previousOwnerUid: str(data?.previousOwnerUid) ?? null,
217
+ reassignedStories: strArray(data?.reassignedStories),
218
+ };
219
+ }
220
+ export async function fetchStorySuggestions(token, companyUid, projectId, storyId) {
221
+ const data = asRecord(await meshJson(token, `/v1/work-mesh/projects/${encodeURIComponent(projectId)}/stories/${encodeURIComponent(storyId)}/suggestions`, { query: { companyUid } }));
222
+ const rows = Array.isArray(data?.candidates) ? data.candidates : [];
223
+ return rows.flatMap((row) => {
224
+ const r = asRecord(row);
225
+ const actorUid = str(r?.actorUid);
226
+ if (!r || !actorUid)
227
+ return [];
228
+ const displayName = str(r.displayName);
229
+ const actorType = str(r.actorType);
230
+ return [{
231
+ actorUid,
232
+ ...(displayName ? { displayName } : {}),
233
+ ...(actorType ? { actorType } : {}),
234
+ score: typeof r.score === "number" ? r.score : 0,
235
+ idle: r.idle === true,
236
+ reasons: strArray(r.reasons),
237
+ }];
238
+ });
239
+ }
240
+ export function formatSuggestionLines(storyId, candidates, names) {
241
+ if (candidates.length === 0)
242
+ return [`No suggested assignees for ${storyId}.`];
243
+ const lines = [`Suggested assignees for ${storyId}:`];
244
+ candidates.forEach((c, i) => {
245
+ const who = displayNameFor(c.actorUid, c.displayName, names);
246
+ const why = c.reasons.length ? `: ${c.reasons.join("; ")}` : "";
247
+ lines.push(`${i + 1}. ${who} (${c.actorUid}) score ${c.score}${c.idle ? ", idle" : ""}${why}`);
248
+ });
249
+ return lines;
250
+ }
251
+ export async function fetchRoster(token, companyUid) {
252
+ const data = asRecord(await meshJson(token, "/v1/work-mesh/live/roster", { query: { companyUid } }));
253
+ const rows = Array.isArray(data?.members) ? data.members : [];
254
+ return rows.flatMap((row) => {
255
+ const r = asRecord(row);
256
+ const actorUid = str(r?.actorUid);
257
+ if (!r || !actorUid)
258
+ return [];
259
+ const displayName = str(r.displayName);
260
+ const actorType = str(r.actorType);
261
+ return [{
262
+ actorUid,
263
+ ...(displayName ? { displayName } : {}),
264
+ ...(actorType ? { actorType } : {}),
265
+ daemonLastSeenAt: str(r.daemonLastSeenAt) ?? null,
266
+ lastSessionAt: str(r.lastSessionAt) ?? null,
267
+ boundSessions: typeof r.boundSessions === "number" ? r.boundSessions : 0,
268
+ unboundSessions: typeof r.unboundSessions === "number" ? r.unboundSessions : 0,
269
+ }];
270
+ });
271
+ }
272
+ export function rosterFlags(member) {
273
+ const flags = [];
274
+ if (!member.daemonLastSeenAt)
275
+ flags.push("no daemon report");
276
+ if (member.unboundSessions > 0 && member.boundSessions === 0)
277
+ flags.push("only unbound sessions");
278
+ return flags;
279
+ }
280
+ export function formatRosterLines(members, companyLabel, names) {
281
+ const lines = [
282
+ `Work mesh roster: ${companyLabel}`,
283
+ "name\tdaemon last seen\tlast session\tbound\tunbound\tflags",
284
+ ];
285
+ for (const m of members) {
286
+ lines.push([
287
+ displayNameFor(m.actorUid, m.displayName, names),
288
+ m.daemonLastSeenAt ?? "never",
289
+ m.lastSessionAt ?? "never",
290
+ String(m.boundSessions),
291
+ String(m.unboundSessions),
292
+ rosterFlags(m).join(", ") || "-",
293
+ ].join("\t"));
294
+ }
295
+ return lines;
296
+ }
297
+ //# sourceMappingURL=team-coordination.js.map
@@ -0,0 +1,75 @@
1
+ /**
2
+ * WMTC-8 gap 7: resolve the project for an unbound (needs_project) session
3
+ * from local evidence only. No network, no subprocess, so a background hook
4
+ * refresh can call it cheaply. Every failure resolves to "no binding".
5
+ *
6
+ * Order:
7
+ * (a) a prior binding for the same repo+branch recorded locally
8
+ * (b) PRDs in the session's company whose repo AND branch match
9
+ * (c) a PRD matching the repo only, when it is the single candidate
10
+ */
11
+ import type { SessionStateFile } from "./state.js";
12
+ export interface GitLocation {
13
+ /** Absolute top-level of the checkout containing cwd. */
14
+ topLevel: string;
15
+ /** Main checkout top-level when cwd is a linked worktree. */
16
+ mainTopLevel?: string;
17
+ branch: string | null;
18
+ }
19
+ export interface AutoBindCandidate {
20
+ projectId: string;
21
+ prdPath: string;
22
+ matchedBranch: boolean;
23
+ }
24
+ export type AutoBindDecision = {
25
+ action: "noop";
26
+ reason: "already_bound" | "not_needs_project" | "no_session";
27
+ projectId?: string;
28
+ } | {
29
+ action: "bind";
30
+ projectId: string;
31
+ source: "history" | "repo_branch" | "repo_only";
32
+ candidates: AutoBindCandidate[];
33
+ } | {
34
+ action: "skip";
35
+ reason: "no_git" | "no_company" | "no_match" | "ambiguous";
36
+ candidates: AutoBindCandidate[];
37
+ };
38
+ /** Find the git checkout for cwd and its current branch without spawning git. */
39
+ export declare function readGitLocation(cwd: string): GitLocation | null;
40
+ /** Repo identities for the checkout: HQ-relative paths and absolute paths. */
41
+ export declare function repoIdentities(loc: GitLocation, hqRoot: string): string[];
42
+ /** PRDs under companies/<company>/projects/* that name this checkout. */
43
+ export declare function findRepoPrdCandidates(input: {
44
+ hqRoot: string;
45
+ companySlug: string;
46
+ identities: string[];
47
+ branch: string | null;
48
+ }): AutoBindCandidate[];
49
+ export declare function autoBindHistoryPath(workContextRoot: string): string;
50
+ export declare function lookupPriorBinding(input: {
51
+ workContextRoot: string;
52
+ companySlug: string;
53
+ identities: string[];
54
+ branch: string | null;
55
+ }): string | null;
56
+ /** Remember a binding for this repo+branch. Best-effort; never throws. */
57
+ export declare function recordPriorBinding(input: {
58
+ workContextRoot: string;
59
+ companySlug: string;
60
+ identities: string[];
61
+ branch: string | null;
62
+ projectId: string;
63
+ now?: Date;
64
+ }): void;
65
+ export declare function decideAutoBind(input: {
66
+ state: SessionStateFile | null;
67
+ cwd: string;
68
+ hqRoot: string;
69
+ workContextRoot: string;
70
+ companySlug?: string;
71
+ }): AutoBindDecision & {
72
+ git?: GitLocation;
73
+ identities?: string[];
74
+ };
75
+ //# sourceMappingURL=auto-bind.d.ts.map