@indigoai-us/hq-cli 5.138.0 → 5.140.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 +17 -0
- package/dist/command-catalog.generated.d.ts +51 -0
- package/dist/command-catalog.generated.js +67 -0
- package/dist/commands/mesh.js +280 -21
- package/dist/commands/report.d.ts +0 -1
- package/dist/commands/report.js +93 -13
- package/dist/lib/lanes/monitor.js +20 -1
- package/dist/lib/mesh/api.d.ts +1 -0
- package/dist/lib/mesh/live/flush.d.ts +1 -1
- package/dist/lib/mesh/live/flush.js +35 -1
- package/dist/lib/mesh/project-board.d.ts +19 -0
- package/dist/lib/mesh/project-board.js +27 -0
- package/dist/lib/work-context/bind-best.d.ts +64 -0
- package/dist/lib/work-context/bind-best.js +299 -0
- package/dist/lib/work-context/contract.d.ts +8 -0
- package/dist/lib/work-context/reconcile.d.ts +9 -0
- package/dist/lib/work-context/reconcile.js +88 -3
- package/dist/lib/work-context/state.d.ts +13 -0
- package/dist/lib/work-context/state.js +9 -0
- package/package.json +1 -1
|
@@ -61,6 +61,14 @@ export interface ContextResult {
|
|
|
61
61
|
bindingEpisodeId?: string;
|
|
62
62
|
clientOperationId: string;
|
|
63
63
|
errorCode?: string;
|
|
64
|
+
/** Why a project was chosen, or why the session is asking. Local consumers only. */
|
|
65
|
+
projectBindReason?: string;
|
|
66
|
+
/** Ranked alternatives when classification is needs_project. */
|
|
67
|
+
projectCandidates?: Array<{
|
|
68
|
+
projectId: string;
|
|
69
|
+
title?: string;
|
|
70
|
+
score?: number;
|
|
71
|
+
}>;
|
|
64
72
|
}
|
|
65
73
|
export type NormalizeTaskIdResult = {
|
|
66
74
|
ok: true;
|
|
@@ -69,6 +69,15 @@ export interface ReconcileDeps {
|
|
|
69
69
|
* companyUid when local resolution only yielded a slug. Injectable for tests.
|
|
70
70
|
*/
|
|
71
71
|
validateMembership?: (slug: string) => Promise<MembershipValidationResult> | MembershipValidationResult;
|
|
72
|
+
/**
|
|
73
|
+
* Register a Board and PUT the local PRD. Called only when online and the
|
|
74
|
+
* exact slug already has a local project folder.
|
|
75
|
+
*/
|
|
76
|
+
ensureProjectBoard?: (input: {
|
|
77
|
+
companyUid: string;
|
|
78
|
+
projectId: string;
|
|
79
|
+
prdPath: string;
|
|
80
|
+
}) => Promise<void>;
|
|
72
81
|
}
|
|
73
82
|
export interface ReconcileOutcome {
|
|
74
83
|
result: ContextResult;
|
|
@@ -9,6 +9,7 @@ import { invalidateDefaultCompanyMemberships, } from "./config.js";
|
|
|
9
9
|
import { WORK_CONTEXT_CONTRACT_VERSION, normalizeTaskId, } from "./contract.js";
|
|
10
10
|
import { EXIT_INVALID_IDENTITY, EXIT_NOT_TRACKING, EXIT_OK, InvalidDecisionOriginError, NotTrackingError, } from "./errors.js";
|
|
11
11
|
import { decisionFromCandidates, } from "./organize.js";
|
|
12
|
+
import { refineProjectBinding } from "./bind-best.js";
|
|
12
13
|
import { resolveProjectTask, shouldAskAfter } from "./project.js";
|
|
13
14
|
import { deriveRemoteOwnerSlug, deriveRepoIdentityKey } from "./repo-remote.js";
|
|
14
15
|
import { enqueueOutbox, listOutboxOperations, markOutboxAcked, markOutboxQuarantined, markOutboxQueued, replayOutbox, writeAckedRegisterState, } from "./outbox.js";
|
|
@@ -35,6 +36,10 @@ function resultOf(parts) {
|
|
|
35
36
|
out.bindingEpisodeId = parts.bindingEpisodeId;
|
|
36
37
|
if (parts.errorCode)
|
|
37
38
|
out.errorCode = parts.errorCode;
|
|
39
|
+
if (parts.projectBindReason)
|
|
40
|
+
out.projectBindReason = parts.projectBindReason;
|
|
41
|
+
if (parts.projectCandidates)
|
|
42
|
+
out.projectCandidates = parts.projectCandidates;
|
|
38
43
|
return out;
|
|
39
44
|
}
|
|
40
45
|
function parseUntrackedOrigin(obs) {
|
|
@@ -441,8 +446,77 @@ export async function reconcileObservation(obs, deps) {
|
|
|
441
446
|
projectResolved = null;
|
|
442
447
|
}
|
|
443
448
|
}
|
|
444
|
-
|
|
445
|
-
|
|
449
|
+
let projectId = projectResolved?.projectId;
|
|
450
|
+
let taskId = projectResolved?.taskId;
|
|
451
|
+
let projectBindReason;
|
|
452
|
+
let projectCandidates;
|
|
453
|
+
let projectGate;
|
|
454
|
+
if (projectResolved &&
|
|
455
|
+
projectId &&
|
|
456
|
+
projectResolved.source !== "existing_binding" &&
|
|
457
|
+
obs.source !== "transcript" &&
|
|
458
|
+
hqRoot &&
|
|
459
|
+
resolvedCompany.slug) {
|
|
460
|
+
const refined = refineProjectBinding({
|
|
461
|
+
hint: projectId,
|
|
462
|
+
hqRoot,
|
|
463
|
+
companySlug: resolvedCompany.slug,
|
|
464
|
+
companyUid: resolvedCompany.uid,
|
|
465
|
+
repoPath: cwd,
|
|
466
|
+
env,
|
|
467
|
+
nowMs: nowFn().getTime(),
|
|
468
|
+
});
|
|
469
|
+
if (refined.action === "bind") {
|
|
470
|
+
if (refined.projectId !== projectId)
|
|
471
|
+
taskId = undefined;
|
|
472
|
+
projectId = refined.projectId;
|
|
473
|
+
projectBindReason = refined.reason;
|
|
474
|
+
diagnostics.push(`project bind: ${refined.reason}`);
|
|
475
|
+
}
|
|
476
|
+
else if (refined.action === "create") {
|
|
477
|
+
projectBindReason = refined.reason;
|
|
478
|
+
if (!deps.offline && resolvedCompany.uid && deps.ensureProjectBoard) {
|
|
479
|
+
try {
|
|
480
|
+
await deps.ensureProjectBoard({
|
|
481
|
+
companyUid: resolvedCompany.uid,
|
|
482
|
+
projectId,
|
|
483
|
+
prdPath: refined.prdPath,
|
|
484
|
+
});
|
|
485
|
+
diagnostics.push(`project board created for ${projectId}`);
|
|
486
|
+
}
|
|
487
|
+
catch (err) {
|
|
488
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
489
|
+
diagnostics.push(`project board create failed: ${message}`);
|
|
490
|
+
projectBindReason = `Board create failed for ${projectId}: ${message}`;
|
|
491
|
+
projectCandidates = [{ projectId, title: projectId }];
|
|
492
|
+
projectGate = "choose";
|
|
493
|
+
projectId = undefined;
|
|
494
|
+
taskId = undefined;
|
|
495
|
+
projectResolved = null;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
else {
|
|
499
|
+
diagnostics.push(`project bind: local PRD for ${projectId}; Board create waits for online reconcile`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
else if (refined.action === "ask") {
|
|
503
|
+
projectBindReason = refined.reason;
|
|
504
|
+
projectCandidates = refined.candidates;
|
|
505
|
+
projectGate = "choose";
|
|
506
|
+
projectId = undefined;
|
|
507
|
+
taskId = undefined;
|
|
508
|
+
projectResolved = null;
|
|
509
|
+
diagnostics.push(`project ask: ${refined.reason}`);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
const bindResultFields = {
|
|
513
|
+
...(projectBindReason ? { projectBindReason } : {}),
|
|
514
|
+
...(projectCandidates ? { projectCandidates } : {}),
|
|
515
|
+
};
|
|
516
|
+
const bindStateFields = {
|
|
517
|
+
...bindResultFields,
|
|
518
|
+
...(projectGate ? { projectGate } : {}),
|
|
519
|
+
};
|
|
446
520
|
// Trusted / deterministic auto-bind never asks.
|
|
447
521
|
// Transcript-sourced sessions never ask (US-018).
|
|
448
522
|
const fromTranscript = obs.source === "transcript";
|
|
@@ -457,7 +531,10 @@ export async function reconcileObservation(obs, deps) {
|
|
|
457
531
|
(projectResolved.source === "deterministic_cwd" ||
|
|
458
532
|
projectResolved.source === "deterministic_registry"));
|
|
459
533
|
let classification;
|
|
460
|
-
if (
|
|
534
|
+
if (projectGate === "choose") {
|
|
535
|
+
classification = "needs_project";
|
|
536
|
+
}
|
|
537
|
+
else if (autoBound) {
|
|
461
538
|
classification = "bound";
|
|
462
539
|
}
|
|
463
540
|
else if (projectId && taskId) {
|
|
@@ -569,6 +646,7 @@ export async function reconcileObservation(obs, deps) {
|
|
|
569
646
|
...(typeof toolWrites === "number" ? { toolWrites } : {}),
|
|
570
647
|
...(hasTurn ? { hasTurn: true } : {}),
|
|
571
648
|
...(decision ? { decision } : {}),
|
|
649
|
+
...bindStateFields,
|
|
572
650
|
...transcriptFields,
|
|
573
651
|
}, prior), deps.root);
|
|
574
652
|
const kind = registerClassification === "bound"
|
|
@@ -591,6 +669,7 @@ export async function reconcileObservation(obs, deps) {
|
|
|
591
669
|
projectId,
|
|
592
670
|
taskId,
|
|
593
671
|
bindingEpisodeId: acknowledgedPrior.bindingEpisodeId,
|
|
672
|
+
...bindResultFields,
|
|
594
673
|
}),
|
|
595
674
|
exitCode: EXIT_OK,
|
|
596
675
|
diagnostics,
|
|
@@ -652,6 +731,7 @@ export async function reconcileObservation(obs, deps) {
|
|
|
652
731
|
...(hasTurn ? { hasTurn: true } : {}),
|
|
653
732
|
...(decision ? { decision } : {}),
|
|
654
733
|
...(companyConfidence ? { companyConfidence } : {}),
|
|
734
|
+
...bindStateFields,
|
|
655
735
|
...transcriptFields,
|
|
656
736
|
}, prior);
|
|
657
737
|
statePath = writeSessionState(stateWrite, deps.root);
|
|
@@ -690,6 +770,7 @@ export async function reconcileObservation(obs, deps) {
|
|
|
690
770
|
companySlug: resolvedCompany.slug,
|
|
691
771
|
projectId,
|
|
692
772
|
taskId,
|
|
773
|
+
...bindResultFields,
|
|
693
774
|
}),
|
|
694
775
|
exitCode: EXIT_OK,
|
|
695
776
|
diagnostics: [...diagnostics, "offline/local: operation remains queued"],
|
|
@@ -736,6 +817,7 @@ export async function reconcileObservation(obs, deps) {
|
|
|
736
817
|
startedAt,
|
|
737
818
|
...(typeof toolWrites === "number" ? { toolWrites } : {}),
|
|
738
819
|
...(hasTurn ? { hasTurn: true } : {}),
|
|
820
|
+
...bindStateFields,
|
|
739
821
|
...transcriptFields,
|
|
740
822
|
},
|
|
741
823
|
// Merge against the just-written projection rather than the stale
|
|
@@ -761,6 +843,7 @@ export async function reconcileObservation(obs, deps) {
|
|
|
761
843
|
projectId,
|
|
762
844
|
taskId,
|
|
763
845
|
bindingEpisodeId: delivered.receiptId,
|
|
846
|
+
...bindResultFields,
|
|
764
847
|
}),
|
|
765
848
|
exitCode: EXIT_OK,
|
|
766
849
|
diagnostics: [...diagnostics, `acked receipt=${delivered.receiptId}`],
|
|
@@ -781,6 +864,7 @@ export async function reconcileObservation(obs, deps) {
|
|
|
781
864
|
projectId,
|
|
782
865
|
taskId,
|
|
783
866
|
errorCode: delivered.code,
|
|
867
|
+
...bindResultFields,
|
|
784
868
|
}),
|
|
785
869
|
exitCode: EXIT_OK,
|
|
786
870
|
diagnostics: [...diagnostics, `transient ${delivered.code}; remains queued`],
|
|
@@ -801,6 +885,7 @@ export async function reconcileObservation(obs, deps) {
|
|
|
801
885
|
projectId,
|
|
802
886
|
taskId,
|
|
803
887
|
errorCode: delivered.code,
|
|
888
|
+
...bindResultFields,
|
|
804
889
|
}),
|
|
805
890
|
exitCode: EXIT_OK,
|
|
806
891
|
diagnostics: [...diagnostics, `quarantined: ${delivered.code}`],
|
|
@@ -91,6 +91,19 @@ export interface SessionStateFile {
|
|
|
91
91
|
transcriptMarker?: "from transcript";
|
|
92
92
|
/** Historical repair under device-default evidence is intentionally weak. */
|
|
93
93
|
companyConfidence?: "device_default";
|
|
94
|
+
/** Why the current project was chosen, or why the session is asking. */
|
|
95
|
+
projectBindReason?: string;
|
|
96
|
+
/** Ranked alternatives when the session is waiting for a project choice. */
|
|
97
|
+
projectCandidates?: Array<{
|
|
98
|
+
projectId: string;
|
|
99
|
+
title?: string;
|
|
100
|
+
score?: number;
|
|
101
|
+
}>;
|
|
102
|
+
/**
|
|
103
|
+
* When set, flush holds events instead of posting them.
|
|
104
|
+
* `choose` — the match was unclear. `missing_board` — the server 404'd the project.
|
|
105
|
+
*/
|
|
106
|
+
projectGate?: "choose" | "missing_board";
|
|
94
107
|
updatedAt: string;
|
|
95
108
|
}
|
|
96
109
|
/**
|
|
@@ -29,6 +29,9 @@ export const SESSION_STATE_ALLOWLIST = new Set([
|
|
|
29
29
|
"localHarness",
|
|
30
30
|
"transcriptMarker",
|
|
31
31
|
"companyConfidence",
|
|
32
|
+
"projectBindReason",
|
|
33
|
+
"projectCandidates",
|
|
34
|
+
"projectGate",
|
|
32
35
|
"updatedAt",
|
|
33
36
|
]);
|
|
34
37
|
const SESSION_STATE_LOCK_TIMEOUT_MS = 5_000;
|
|
@@ -228,6 +231,12 @@ export function projectSessionState(state) {
|
|
|
228
231
|
out.transcriptMarker = state.transcriptMarker;
|
|
229
232
|
if (state.companyConfidence)
|
|
230
233
|
out.companyConfidence = state.companyConfidence;
|
|
234
|
+
if (state.projectBindReason)
|
|
235
|
+
out.projectBindReason = state.projectBindReason;
|
|
236
|
+
if (state.projectCandidates?.length)
|
|
237
|
+
out.projectCandidates = state.projectCandidates;
|
|
238
|
+
if (state.projectGate)
|
|
239
|
+
out.projectGate = state.projectGate;
|
|
231
240
|
return out;
|
|
232
241
|
}
|
|
233
242
|
/**
|