@indigoai-us/hq-cli 5.345.32 → 5.345.33
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 +8 -0
- package/dist/command-catalog.generated.d.ts +92 -3
- package/dist/command-catalog.generated.js +111 -3
- package/dist/commands/db-dump.d.ts +8 -0
- package/dist/commands/db-dump.js +50 -0
- package/dist/commands/db-provision.d.ts +3 -10
- package/dist/commands/db-provision.js +26 -47
- package/dist/commands/db-remote-deps.d.ts +18 -0
- package/dist/commands/db-remote-deps.js +59 -0
- package/dist/commands/db-sql.d.ts +2 -1
- package/dist/commands/db-sql.js +30 -5
- package/dist/commands/db-usage.d.ts +7 -0
- package/dist/commands/db-usage.js +28 -0
- package/dist/commands/db.js +4 -0
- package/dist/commands/files.js +33 -5
- package/dist/commands/lanes-message.js +40 -8
- package/dist/commands/lanes-questions.d.ts +6 -1
- package/dist/commands/lanes-questions.js +19 -7
- package/dist/commands/lanes-resume.js +17 -6
- package/dist/commands/lanes-shared.d.ts +6 -1
- package/dist/commands/lanes.js +74 -1
- package/dist/commands/mesh.js +150 -26
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +68 -604
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/lib/db/control-plane.d.ts +32 -0
- package/dist/lib/db/control-plane.js +50 -0
- package/dist/lib/db/redact.d.ts +4 -0
- package/dist/lib/db/redact.js +17 -0
- package/dist/lib/db/remote-engine.d.ts +67 -0
- package/dist/lib/db/remote-engine.js +165 -0
- package/dist/lib/db/usage-format.d.ts +4 -0
- package/dist/lib/db/usage-format.js +41 -0
- package/dist/lib/lanes/keep-alive.d.ts +12 -0
- package/dist/lib/lanes/keep-alive.js +25 -6
- package/dist/lib/mesh/api.d.ts +2 -0
- package/dist/lib/mesh/project-board.d.ts +17 -0
- package/dist/lib/mesh/project-board.js +28 -0
- package/dist/lib/mesh/story-status.d.ts +16 -0
- package/dist/lib/mesh/story-status.js +32 -7
- package/dist/utils/self-update.d.ts +2 -0
- package/dist/utils/self-update.js +61 -23
- package/dist/utils/version-check.d.ts +1 -1
- package/dist/utils/version-check.js +2 -2
- package/dist/utils/version-gate.d.ts +21 -14
- package/dist/utils/version-gate.js +119 -28
- package/package.json +4 -1
package/dist/commands/mesh.js
CHANGED
|
@@ -14,8 +14,8 @@ import chalk from "chalk";
|
|
|
14
14
|
import { ensureCognitoToken, isExpiring, isMachineIdentity, loadCachedTokens, } from "../utils/cognito-session.js";
|
|
15
15
|
import * as readline from "node:readline/promises";
|
|
16
16
|
import { STORY_STATUSES, appendThreadEvent, addProjectMember, ensureProjectThread, eventPayload, getProjectView, listActiveMembershipCompanies, listActiveThreads, patchStory, putProjectView, registerMeshProject, resolveActiveMembershipCompany, resolveMeshPrincipalUid, warmMeshConversationCache, } from "../lib/mesh/api.js";
|
|
17
|
-
import { closeSupersededProjectThread, ensureMissingBoardStories, ensureProjectBoard, boardStoryStatus, formatStoryStatusChanges, mergeProjectStories, prdSyncEventsThatChangeBoard, } from "../lib/mesh/project-board.js";
|
|
18
|
-
import { mapSourceStoryStatus } from "../lib/mesh/story-status.js";
|
|
17
|
+
import { closeSupersededProjectThread, ensureMissingBoardStories, ensureProjectBoard, boardStoryStatus, formatStoryStatusChanges, mergeProjectStories, prdSyncEventsThatChangeBoard, reconcileProjectStories, } from "../lib/mesh/project-board.js";
|
|
18
|
+
import { ACCEPTED_LEDGER_STATES, mapSourceStoryStatus, UnknownProjectStoryStateError } from "../lib/mesh/story-status.js";
|
|
19
19
|
import { buildPreflightFixtures } from "../lib/work-context/preflight-contract.js";
|
|
20
20
|
import { createCandidatesFetcher, createMigratePoster, createOrganizePoster, createWorkSessionDeliverer, fetchCompanyLive, formatCompanyLiveTable, openMeshTransport, probeMigrationCapabilityForMemberships, requireToken, } from "../lib/mesh/client.js";
|
|
21
21
|
import { clearDefaultCompany, effectiveUsageAttribution, isDefaultCompanyDisabled, readDeviceConfig, recordMigrationCapabilitySnapshot, setRepairHeldWithDefault, setDefaultCompany, setUsageAttribution, } from "../lib/work-context/config.js";
|
|
@@ -257,32 +257,43 @@ function projectStoriesFromJson(value, source) {
|
|
|
257
257
|
if (!rawStories) {
|
|
258
258
|
fail(`${source} must be a JSON story array or an object with userStories.`);
|
|
259
259
|
}
|
|
260
|
-
const
|
|
260
|
+
const rejectedStories = [];
|
|
261
|
+
const stories = rawStories.flatMap((raw, index) => {
|
|
261
262
|
const story = record(raw);
|
|
262
263
|
const id = typeof story?.id === "string" ? story.id.trim() : "";
|
|
263
264
|
const title = typeof story?.title === "string" ? story.title.trim() : "";
|
|
264
265
|
if (!story || !id || !title) {
|
|
265
266
|
fail(`${source} userStories[${index}] requires string id and title.`);
|
|
266
267
|
}
|
|
267
|
-
|
|
268
|
+
let status;
|
|
269
|
+
try {
|
|
270
|
+
status = mapSourceStoryStatus({ ...story, id }, `${source} userStories[${index}]`);
|
|
271
|
+
}
|
|
272
|
+
catch (err) {
|
|
273
|
+
if (!(err instanceof UnknownProjectStoryStateError))
|
|
274
|
+
throw err;
|
|
275
|
+
rejectedStories.push(err);
|
|
276
|
+
return [];
|
|
277
|
+
}
|
|
268
278
|
const resolvedStatus = status || (story.passes === true ? "done" : undefined);
|
|
269
|
-
return {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
279
|
+
return [{
|
|
280
|
+
id,
|
|
281
|
+
title,
|
|
282
|
+
...(typeof story.description === "string" ? { description: story.description } : {}),
|
|
283
|
+
...(Array.isArray(story.acceptanceCriteria)
|
|
284
|
+
? {
|
|
285
|
+
acceptanceCriteria: story.acceptanceCriteria.filter((item) => typeof item === "string"),
|
|
286
|
+
}
|
|
287
|
+
: {}),
|
|
288
|
+
...(resolvedStatus ? { status: resolvedStatus } : {}),
|
|
289
|
+
...(typeof story.state === "string" ? { ledgerState: story.state.trim() } : {}),
|
|
290
|
+
// hq-pro makes passes mirror the Board status. A completed PRD story
|
|
291
|
+
// without an explicit status must therefore become done.
|
|
292
|
+
passes: resolvedStatus === "done",
|
|
293
|
+
...(typeof story.priority === "number" && Number.isFinite(story.priority)
|
|
294
|
+
? { priority: story.priority }
|
|
295
|
+
: {}),
|
|
296
|
+
}];
|
|
286
297
|
});
|
|
287
298
|
const metadata = record(plan?.metadata);
|
|
288
299
|
const repos = metadata && Object.hasOwn(metadata, "repos")
|
|
@@ -292,12 +303,44 @@ function projectStoriesFromJson(value, source) {
|
|
|
292
303
|
...(typeof plan?.name === "string" ? { name: plan.name } : {}),
|
|
293
304
|
...(typeof plan?.description === "string" ? { description: plan.description } : {}),
|
|
294
305
|
stories,
|
|
306
|
+
rejectedStories,
|
|
295
307
|
...(repos ? { repos } : {}),
|
|
296
308
|
};
|
|
297
309
|
}
|
|
298
|
-
function
|
|
310
|
+
function projectStateErrors(rejected) {
|
|
311
|
+
return rejected.map((error) => ({
|
|
312
|
+
code: error.code,
|
|
313
|
+
file: error.source.split(" userStories[")[0],
|
|
314
|
+
storyId: error.storyId,
|
|
315
|
+
field: "state",
|
|
316
|
+
value: error.value,
|
|
317
|
+
accepted: ACCEPTED_LEDGER_STATES,
|
|
318
|
+
}));
|
|
319
|
+
}
|
|
320
|
+
class RejectedProjectStoryStatesError extends Error {
|
|
321
|
+
rejected;
|
|
322
|
+
code = "PROJECT_STORY_STATE_INVALID";
|
|
323
|
+
constructor(rejected) {
|
|
324
|
+
super(rejected.map((error) => `${error.code}: ${error.message}`).join("\n"));
|
|
325
|
+
this.rejected = rejected;
|
|
326
|
+
this.name = "RejectedProjectStoryStatesError";
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
function failProjectStoryStates(rejected, json) {
|
|
330
|
+
if (json)
|
|
331
|
+
console.log(JSON.stringify({ ok: false, errors: projectStateErrors(rejected) }));
|
|
332
|
+
else
|
|
333
|
+
for (const error of rejected)
|
|
334
|
+
console.error(`${error.code}: ${error.message}`);
|
|
335
|
+
process.exitCode = 1;
|
|
336
|
+
}
|
|
337
|
+
function parseProjectPlan(raw, source, options = {}) {
|
|
299
338
|
try {
|
|
300
|
-
|
|
339
|
+
const plan = projectStoriesFromJson(JSON.parse(raw), source);
|
|
340
|
+
if (plan.rejectedStories.length && !options.allowRejectedStories) {
|
|
341
|
+
throw new RejectedProjectStoryStatesError(plan.rejectedStories);
|
|
342
|
+
}
|
|
343
|
+
return plan;
|
|
301
344
|
}
|
|
302
345
|
catch (err) {
|
|
303
346
|
if (err instanceof SyntaxError)
|
|
@@ -330,6 +373,10 @@ async function runProjectSet(projectId, opts) {
|
|
|
330
373
|
: opts.storiesJson
|
|
331
374
|
? parseProjectPlan(opts.storiesJson, "--stories-json")
|
|
332
375
|
: undefined;
|
|
376
|
+
if (source?.rejectedStories.length) {
|
|
377
|
+
failProjectStoryStates(source.rejectedStories, Boolean(opts.json));
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
333
380
|
const explicitRepos = opts.reposJson
|
|
334
381
|
? parseProjectReposJson(opts.reposJson, "--repos-json")
|
|
335
382
|
: opts.repo?.length
|
|
@@ -479,8 +526,12 @@ async function runProjectMemberAdd(projectId, personUid, opts) {
|
|
|
479
526
|
async function runProjectEnsure(projectId, opts) {
|
|
480
527
|
if (!opts.storiesFile?.trim())
|
|
481
528
|
fail("`--stories-file <prd.json>` is required.");
|
|
482
|
-
const source = parseProjectPlan(readFileSync(opts.storiesFile, "utf8"), opts.storiesFile);
|
|
529
|
+
const source = parseProjectPlan(readFileSync(opts.storiesFile, "utf8"), opts.storiesFile, { allowRejectedStories: true });
|
|
483
530
|
const { token, company } = await withCompany(opts);
|
|
531
|
+
if (source.rejectedStories.length && source.stories.length === 0) {
|
|
532
|
+
failProjectStoryStates(source.rejectedStories, Boolean(opts.json));
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
484
535
|
let current;
|
|
485
536
|
try {
|
|
486
537
|
current = await getProjectView(token, company.companyUid, projectId);
|
|
@@ -506,12 +557,13 @@ async function runProjectEnsure(projectId, opts) {
|
|
|
506
557
|
}));
|
|
507
558
|
if (opts.json) {
|
|
508
559
|
console.log(JSON.stringify({
|
|
509
|
-
ok:
|
|
560
|
+
ok: source.rejectedStories.length === 0,
|
|
510
561
|
action: "project.ensure.dry-run",
|
|
511
562
|
projectId,
|
|
512
563
|
companyUid: company.companyUid,
|
|
513
564
|
sourceStatuses,
|
|
514
565
|
stories: boardStatuses,
|
|
566
|
+
...(source.rejectedStories.length ? { errors: projectStateErrors(source.rejectedStories) } : {}),
|
|
515
567
|
}, null, 2));
|
|
516
568
|
}
|
|
517
569
|
else {
|
|
@@ -523,6 +575,8 @@ async function runProjectEnsure(projectId, opts) {
|
|
|
523
575
|
for (const story of boardStatuses)
|
|
524
576
|
console.log(`${story.id}\t${story.status}`);
|
|
525
577
|
}
|
|
578
|
+
if (source.rejectedStories.length)
|
|
579
|
+
process.exitCode = 1;
|
|
526
580
|
return;
|
|
527
581
|
}
|
|
528
582
|
// Genesis thread, then the view, then register. Register returns 403
|
|
@@ -606,13 +660,74 @@ async function runProjectEnsure(projectId, opts) {
|
|
|
606
660
|
channelId: registration.channelId,
|
|
607
661
|
version: verified.version,
|
|
608
662
|
stories: verified.stories,
|
|
663
|
+
...(source.rejectedStories.length ? { ok: false, errors: projectStateErrors(source.rejectedStories) } : {}),
|
|
609
664
|
};
|
|
610
665
|
noteCallingSessionBound(payload.projectId, company);
|
|
611
666
|
if (opts.json) {
|
|
612
667
|
console.log(JSON.stringify(payload));
|
|
668
|
+
if (source.rejectedStories.length)
|
|
669
|
+
process.exitCode = 1;
|
|
613
670
|
return;
|
|
614
671
|
}
|
|
615
672
|
console.log(`Work mesh: project ${payload.projectId} ensured (thread ${payload.threadId}, channel ${payload.channelId}, ${payload.stories.length} stories)`);
|
|
673
|
+
if (source.rejectedStories.length)
|
|
674
|
+
failProjectStoryStates(source.rejectedStories, false);
|
|
675
|
+
}
|
|
676
|
+
async function runProjectReconcile(projectId, opts) {
|
|
677
|
+
if (!opts.storiesFile?.trim())
|
|
678
|
+
fail("`--stories-file <prd.json>` is required.");
|
|
679
|
+
const source = parseProjectPlan(readFileSync(opts.storiesFile, "utf8"), opts.storiesFile, { allowRejectedStories: true });
|
|
680
|
+
const { token, company } = await withCompany(opts);
|
|
681
|
+
let current;
|
|
682
|
+
try {
|
|
683
|
+
current = await getProjectView(token, company.companyUid, projectId);
|
|
684
|
+
}
|
|
685
|
+
catch (err) {
|
|
686
|
+
if (httpStatus(err) !== 404)
|
|
687
|
+
throw err;
|
|
688
|
+
}
|
|
689
|
+
if (current && (current.companyUid !== company.companyUid || current.projectId !== projectId)) {
|
|
690
|
+
fail("Work mesh project reconcile: Board company or project id did not match the request.");
|
|
691
|
+
}
|
|
692
|
+
const reconciliation = reconcileProjectStories(source.stories, current?.stories ?? []);
|
|
693
|
+
const errors = [
|
|
694
|
+
...projectStateErrors(source.rejectedStories),
|
|
695
|
+
...(!current ? [{ code: "PROJECT_BOARD_NOT_FOUND", projectId }] : []),
|
|
696
|
+
];
|
|
697
|
+
const mismatches = reconciliation.mismatches;
|
|
698
|
+
const clean = mismatches.length === 0 && errors.length === 0;
|
|
699
|
+
if (opts.json) {
|
|
700
|
+
console.log(JSON.stringify({
|
|
701
|
+
ok: clean,
|
|
702
|
+
action: "project.reconcile",
|
|
703
|
+
projectId,
|
|
704
|
+
companyUid: company.companyUid,
|
|
705
|
+
mismatches,
|
|
706
|
+
stateDriftCount: reconciliation.stateDriftCount,
|
|
707
|
+
ledgerStateUnsupported: reconciliation.ledgerStateUnsupported,
|
|
708
|
+
errors,
|
|
709
|
+
}, null, 2));
|
|
710
|
+
}
|
|
711
|
+
else if (clean) {
|
|
712
|
+
const note = reconciliation.ledgerStateUnsupported.length
|
|
713
|
+
? `; Board does not return ledgerState for ${reconciliation.ledgerStateUnsupported.length} stories yet`
|
|
714
|
+
: "";
|
|
715
|
+
console.log(`Work mesh: project ${projectId} matches ${source.stories.length} ledger stories${note}`);
|
|
716
|
+
}
|
|
717
|
+
else {
|
|
718
|
+
for (const mismatch of mismatches) {
|
|
719
|
+
console.log(`${mismatch.storyId}\tledger=${mismatch.ledgerState ?? "<none>"}\texpected=${mismatch.expectedBoardStatus ?? "<none>"}\tboard=${mismatch.boardStatus ?? "<missing>"}\tboardLedger=${mismatch.boardLedgerState ?? "<unsupported>"}\tstateDrift=${mismatch.stateDrift}\tmissing=${mismatch.missingOnBoard}\textra=${mismatch.extraOnBoard}`);
|
|
720
|
+
}
|
|
721
|
+
if (reconciliation.ledgerStateUnsupported.length) {
|
|
722
|
+
console.log(`Board does not return ledgerState for ${reconciliation.ledgerStateUnsupported.length} stories: ${reconciliation.ledgerStateUnsupported.join(", ")}`);
|
|
723
|
+
}
|
|
724
|
+
if (!current)
|
|
725
|
+
console.error(`PROJECT_BOARD_NOT_FOUND: Board project ${projectId} was not found.`);
|
|
726
|
+
for (const error of source.rejectedStories)
|
|
727
|
+
console.error(`${error.code}: ${error.message}`);
|
|
728
|
+
}
|
|
729
|
+
if (!clean)
|
|
730
|
+
process.exitCode = 1;
|
|
616
731
|
}
|
|
617
732
|
async function runPrdSync(opts) {
|
|
618
733
|
const sessionId = opts.session.trim();
|
|
@@ -689,6 +804,8 @@ async function runPrdSync(opts) {
|
|
|
689
804
|
});
|
|
690
805
|
}
|
|
691
806
|
catch (err) {
|
|
807
|
+
if (err instanceof RejectedProjectStoryStatesError)
|
|
808
|
+
throw err;
|
|
692
809
|
const reason = err instanceof Error ? err.message : String(err);
|
|
693
810
|
console.error(`work-mesh: prd-sync ensure failed; emitting anyway (${reason.slice(0, 160)})`);
|
|
694
811
|
}
|
|
@@ -828,8 +945,8 @@ async function runBindProject(target, opts) {
|
|
|
828
945
|
}
|
|
829
946
|
if (!current.companyUid)
|
|
830
947
|
fail(`Session ${sessionId} has no company. Bind a company first.`);
|
|
831
|
-
const { token, company } = await withCompany({ company: opts.company ?? current.companySlug });
|
|
832
948
|
const plan = parseProjectPlan(readFileSync(prdPath, "utf8"), prdPath);
|
|
949
|
+
const { token, company } = await withCompany({ company: opts.company ?? current.companySlug });
|
|
833
950
|
await ensureProjectBoard({
|
|
834
951
|
token,
|
|
835
952
|
company: {
|
|
@@ -2026,6 +2143,13 @@ export function registerMeshCommand(program, deps = {}) {
|
|
|
2026
2143
|
.option("--dry-run", "Read the current Board and print computed story statuses without writing")
|
|
2027
2144
|
.option("--json", "Print {projectId, threadId, channelId, version, stories}")
|
|
2028
2145
|
.action((projectId, opts) => wrap(() => runProjectEnsure(projectId, opts))());
|
|
2146
|
+
project
|
|
2147
|
+
.command("reconcile <projectId>")
|
|
2148
|
+
.description("Read the project Board and report drift from a PRD ledger")
|
|
2149
|
+
.requiredOption("--company <slug|uid>", "Company slug or cloud uid")
|
|
2150
|
+
.requiredOption("--stories-file <path>", "PRD JSON file with userStories")
|
|
2151
|
+
.option("--json", "Print machine-readable mismatch records")
|
|
2152
|
+
.action((projectId, opts) => wrap(() => runProjectReconcile(projectId, opts))());
|
|
2029
2153
|
project
|
|
2030
2154
|
.command("register <projectId>")
|
|
2031
2155
|
.description("Register an existing Board and bind its project channel; use project ensure to create or repair a Board")
|