@akagilnc/pi-workflow-roles 0.1.4905 → 0.1.4911
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/dist/acp-host/production-host.js +70 -286
- package/dist/headless-host/production-host.js +65 -281
- package/dist/migrate-book-topology.js +0 -2
- package/dist/packaged-role-registry.js +0 -2
- package/dist/public-cli/instruction-seat-run.js +5 -58
- package/dist/public-cli/main.js +56 -272
- package/dist/public-cli/post-admission.js +3 -18
- package/dist/public-cli/turn-request.js +1 -1
- package/dist/public-role-summons.js +38 -190
- package/package.json +1 -1
- package/src/packaged-role-registry.ts +0 -2
- package/src/public-cli/instruction-seat-run.ts +5 -56
- package/src/public-cli/post-admission.ts +2 -32
- package/src/public-cli/turn-request.ts +1 -7
- package/src/public-role-summons.ts +58 -268
|
@@ -11,9 +11,8 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { execFile } from "node:child_process";
|
|
13
13
|
import { existsSync } from "node:fs";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import { join, relative, sep } from "node:path";
|
|
14
|
+
import { realpath } from "node:fs/promises";
|
|
15
|
+
import { join } from "node:path";
|
|
17
16
|
import { promisify } from "node:util";
|
|
18
17
|
|
|
19
18
|
import type { CliIo } from "./public-cli/cli-io.ts";
|
|
@@ -114,11 +113,6 @@ export type PublicSummonRequest = {
|
|
|
114
113
|
* the host session — not a package-built advice ledger.
|
|
115
114
|
*/
|
|
116
115
|
readonly resumeRunId?: string;
|
|
117
|
-
/**
|
|
118
|
-
* Ephemeral host-turn cwd override (#946 fresh-copy sandbox). Admission keeps
|
|
119
|
-
* the public --project / cwd identity; the host turn runs under this path.
|
|
120
|
-
*/
|
|
121
|
-
readonly executionCwd?: string;
|
|
122
116
|
/**
|
|
123
117
|
* Nested court station child (default true): omit Navigator auto-attendance
|
|
124
118
|
* and use station-child resume. Ordinary public-equivalent legs (dual-lens
|
|
@@ -448,7 +442,6 @@ export async function summonPublicRole(
|
|
|
448
442
|
? {}
|
|
449
443
|
: { correlationId: options.correlationId }),
|
|
450
444
|
...(options.createRunId === undefined ? {} : { createRunId: options.createRunId }),
|
|
451
|
-
...(options.executionCwd === undefined ? {} : { executionCwd: options.executionCwd }),
|
|
452
445
|
},
|
|
453
446
|
};
|
|
454
447
|
} catch (error) {
|
|
@@ -565,159 +558,22 @@ function withReviewerLens(
|
|
|
565
558
|
return [...argv.slice(0, separator), "--lens", lens, ...argv.slice(separator)];
|
|
566
559
|
}
|
|
567
560
|
|
|
568
|
-
/**
|
|
569
|
-
async function
|
|
570
|
-
readonly callerProjectRoot: string;
|
|
571
|
-
readonly sourceProjectRoot: string;
|
|
572
|
-
readonly projectRelative: string;
|
|
573
|
-
}> {
|
|
561
|
+
/** Git toplevel for Reviewer target status / seal checks (subdir-safe). */
|
|
562
|
+
async function resolveReviewerGitToplevel(projectRoot: string): Promise<string> {
|
|
574
563
|
const callerProjectRoot = await realpath(projectRoot);
|
|
575
|
-
|
|
564
|
+
return await realpath((await execFileAsync(
|
|
576
565
|
"git",
|
|
577
566
|
["rev-parse", "--show-toplevel"],
|
|
578
567
|
{ cwd: callerProjectRoot },
|
|
579
568
|
)).stdout.trim());
|
|
580
|
-
const callerRelative = relative(sourceProjectRoot, callerProjectRoot);
|
|
581
|
-
const projectRelative =
|
|
582
|
-
callerRelative === ""
|
|
583
|
-
|| callerRelative === "."
|
|
584
|
-
|| callerRelative.startsWith(`..${sep}`)
|
|
585
|
-
|| callerRelative === ".."
|
|
586
|
-
? ""
|
|
587
|
-
: callerRelative;
|
|
588
|
-
return { callerProjectRoot, sourceProjectRoot, projectRelative };
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
function reviewerSandboxPath(worktreeAxis: string, projectRelative: string): string {
|
|
592
|
-
return projectRelative === "" ? worktreeAxis : join(worktreeAxis, projectRelative);
|
|
593
569
|
}
|
|
594
570
|
|
|
595
|
-
export type EphemeralReviewerWorktree = {
|
|
596
|
-
readonly executionCwd: string;
|
|
597
|
-
/** Always safe to call once; delete failure is diagnostic only (#946 10a). */
|
|
598
|
-
readonly close: () => Promise<void>;
|
|
599
|
-
};
|
|
600
|
-
|
|
601
571
|
/**
|
|
602
|
-
*
|
|
603
|
-
*
|
|
604
|
-
*
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
readonly projectRoot: string;
|
|
608
|
-
readonly onCleanupDiagnostic?: (diagnostic: string) => void;
|
|
609
|
-
}): Promise<EphemeralReviewerWorktree> {
|
|
610
|
-
const { sourceProjectRoot, projectRelative } = await resolveReviewerWorktreeRoots(
|
|
611
|
-
options.projectRoot,
|
|
612
|
-
);
|
|
613
|
-
const { stdout: headStdout } = await execFileAsync(
|
|
614
|
-
"git",
|
|
615
|
-
["rev-parse", "--verify", "HEAD^{commit}"],
|
|
616
|
-
{ cwd: sourceProjectRoot },
|
|
617
|
-
);
|
|
618
|
-
const targetCommit = headStdout.trim();
|
|
619
|
-
const root = await mkdtemp(join(tmpdir(), "ak-reviewer-sandbox-"));
|
|
620
|
-
const worktreeRoot = join(root, "work");
|
|
621
|
-
let registered = false;
|
|
622
|
-
const rollback = async (cause: unknown): Promise<never> => {
|
|
623
|
-
// From registration onward, any pre-handle failure must remove the worktree
|
|
624
|
-
// and root. Keep the original prep cause; cleanup failures are diagnostic
|
|
625
|
-
// only (10a) — same face as close(), never silent (#946).
|
|
626
|
-
const cleanupErrors: unknown[] = [];
|
|
627
|
-
if (registered) {
|
|
628
|
-
try {
|
|
629
|
-
await execFileAsync("git", ["worktree", "remove", "--force", worktreeRoot], {
|
|
630
|
-
cwd: sourceProjectRoot,
|
|
631
|
-
});
|
|
632
|
-
} catch (error) {
|
|
633
|
-
cleanupErrors.push(error);
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
try {
|
|
637
|
-
await rm(root, { recursive: true, force: true });
|
|
638
|
-
} catch (error) {
|
|
639
|
-
cleanupErrors.push(error);
|
|
640
|
-
}
|
|
641
|
-
if (cleanupErrors.length > 0) {
|
|
642
|
-
options.onCleanupDiagnostic?.([
|
|
643
|
-
"reviewer worktree cleanup failed",
|
|
644
|
-
...cleanupErrors.map((error) =>
|
|
645
|
-
error instanceof Error ? error.message : String(error)),
|
|
646
|
-
].join("\n"));
|
|
647
|
-
}
|
|
648
|
-
throw cause;
|
|
649
|
-
};
|
|
650
|
-
try {
|
|
651
|
-
await execFileAsync("git", ["worktree", "add", "--detach", worktreeRoot, targetCommit], {
|
|
652
|
-
cwd: sourceProjectRoot,
|
|
653
|
-
});
|
|
654
|
-
registered = true;
|
|
655
|
-
// Preserve caller subdirectory even when absent from the pinned commit.
|
|
656
|
-
if (projectRelative !== "") {
|
|
657
|
-
await mkdir(reviewerSandboxPath(worktreeRoot, projectRelative), { recursive: true });
|
|
658
|
-
}
|
|
659
|
-
} catch (error) {
|
|
660
|
-
await rollback(error);
|
|
661
|
-
}
|
|
662
|
-
let closed = false;
|
|
663
|
-
return {
|
|
664
|
-
executionCwd: reviewerSandboxPath(worktreeRoot, projectRelative),
|
|
665
|
-
close: async () => {
|
|
666
|
-
if (closed) return;
|
|
667
|
-
closed = true;
|
|
668
|
-
const cleanupErrors: unknown[] = [];
|
|
669
|
-
try {
|
|
670
|
-
await execFileAsync("git", ["worktree", "remove", worktreeRoot], {
|
|
671
|
-
cwd: sourceProjectRoot,
|
|
672
|
-
});
|
|
673
|
-
} catch (error) {
|
|
674
|
-
cleanupErrors.push(error);
|
|
675
|
-
}
|
|
676
|
-
try {
|
|
677
|
-
await rm(root, { recursive: true, force: true });
|
|
678
|
-
} catch (error) {
|
|
679
|
-
cleanupErrors.push(error);
|
|
680
|
-
}
|
|
681
|
-
if (cleanupErrors.length > 0) {
|
|
682
|
-
options.onCleanupDiagnostic?.([
|
|
683
|
-
"reviewer worktree cleanup failed",
|
|
684
|
-
...cleanupErrors.map((error) =>
|
|
685
|
-
error instanceof Error ? error.message : String(error)),
|
|
686
|
-
].join("\n"));
|
|
687
|
-
}
|
|
688
|
-
},
|
|
689
|
-
};
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
/**
|
|
693
|
-
* One ephemeral detached worktree at the source tree's current HEAD.
|
|
694
|
-
* Always removed after `run` settles; delete failure is diagnostic only (#946 10a).
|
|
695
|
-
*/
|
|
696
|
-
export async function withEphemeralReviewerWorktree<T>(options: {
|
|
697
|
-
readonly projectRoot: string;
|
|
698
|
-
readonly run: (executionCwd: string) => Promise<T>;
|
|
699
|
-
readonly onCleanupDiagnostic?: (diagnostic: string) => void;
|
|
700
|
-
}): Promise<T> {
|
|
701
|
-
const sandbox = await openEphemeralReviewerWorktree({
|
|
702
|
-
projectRoot: options.projectRoot,
|
|
703
|
-
...(options.onCleanupDiagnostic === undefined
|
|
704
|
-
? {}
|
|
705
|
-
: { onCleanupDiagnostic: options.onCleanupDiagnostic }),
|
|
706
|
-
});
|
|
707
|
-
try {
|
|
708
|
-
return await options.run(sandbox.executionCwd);
|
|
709
|
-
} finally {
|
|
710
|
-
await sandbox.close();
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
/**
|
|
715
|
-
* Default Reviewer call: two explicit single-axis public legs in independent
|
|
716
|
-
* ephemeral detached worktrees, started as one parallel batch. Each leg reuses
|
|
717
|
-
* the caller's public argv and only adds `--lens` (10a). Worktrees are stateless
|
|
718
|
-
* execution sandboxes — created from the caller's current HEAD, always deleted
|
|
719
|
-
* when the call ends (delete failure is diagnostic only). Lifecycle stays in
|
|
720
|
-
* this shared summons seam, never in the role module (#946).
|
|
572
|
+
* Default Reviewer call: two explicit single-axis public legs on the caller's
|
|
573
|
+
* ticket worktree, started as one parallel batch (#997). Each leg reuses the
|
|
574
|
+
* caller's public argv and only adds `--lens` (10a). No seat-specific ephemeral
|
|
575
|
+
* copy — same shared tree as other seats. Lifecycle stays in this shared
|
|
576
|
+
* summons seam, never in the role module (#946 / ADR 0018).
|
|
721
577
|
*/
|
|
722
578
|
export async function summonParallelReviewerLenses(options: {
|
|
723
579
|
/** Public argv after the role token; must not already carry `--lens`. */
|
|
@@ -725,7 +581,7 @@ export async function summonParallelReviewerLenses(options: {
|
|
|
725
581
|
/** Same cwd the single-axis public entry would receive for this call. */
|
|
726
582
|
readonly cwd: string;
|
|
727
583
|
readonly projectRoot: string;
|
|
728
|
-
/** Typed --base from the public parse; used only for pre-
|
|
584
|
+
/** Typed --base from the public parse; used only for pre-dispatch fail-closed check. */
|
|
729
585
|
readonly baseRevision: string;
|
|
730
586
|
readonly home: string;
|
|
731
587
|
readonly agentDir?: string;
|
|
@@ -760,11 +616,9 @@ export async function summonParallelReviewerLenses(options: {
|
|
|
760
616
|
return { completeness: failure, correctness: failure } as const;
|
|
761
617
|
};
|
|
762
618
|
|
|
763
|
-
//
|
|
764
|
-
//
|
|
765
|
-
// Every pre-dispatch target check failure keeps the existing dual-child batch surface.
|
|
619
|
+
// Pre-dispatch target checks use git toplevel (subdir-safe). Every failure
|
|
620
|
+
// keeps the existing dual-child batch surface.
|
|
766
621
|
let sourceProjectRoot: string;
|
|
767
|
-
let projectRelative: string;
|
|
768
622
|
let targetCommit: string;
|
|
769
623
|
const statusArgs = [
|
|
770
624
|
"status",
|
|
@@ -775,9 +629,7 @@ export async function summonParallelReviewerLenses(options: {
|
|
|
775
629
|
":(top,exclude).claude/worktrees/**",
|
|
776
630
|
] as const;
|
|
777
631
|
try {
|
|
778
|
-
|
|
779
|
-
sourceProjectRoot = roots.sourceProjectRoot;
|
|
780
|
-
projectRelative = roots.projectRelative;
|
|
632
|
+
sourceProjectRoot = await resolveReviewerGitToplevel(options.projectRoot);
|
|
781
633
|
const { stdout: statusStdout } = await execFileAsync("git", statusArgs, {
|
|
782
634
|
cwd: sourceProjectRoot,
|
|
783
635
|
});
|
|
@@ -805,81 +657,50 @@ export async function summonParallelReviewerLenses(options: {
|
|
|
805
657
|
} catch (error) {
|
|
806
658
|
return dualFailure(error);
|
|
807
659
|
}
|
|
808
|
-
|
|
809
|
-
const
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
//
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
)
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
home: options.home,
|
|
853
|
-
...(options.agentDir === undefined ? {} : { agentDir: options.agentDir }),
|
|
854
|
-
...(options.credentials === undefined ? {} : { credentials: options.credentials }),
|
|
855
|
-
...(options.model === undefined ? {} : { model: options.model }),
|
|
856
|
-
...(options.host === undefined ? {} : { host: options.host }),
|
|
857
|
-
...(options.engine === undefined ? {} : { engine: options.engine }),
|
|
858
|
-
...(options.engineModel === undefined ? {} : { engineModel: options.engineModel }),
|
|
859
|
-
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
860
|
-
...(options.correlationId === undefined ? {} : { correlationId: options.correlationId }),
|
|
861
|
-
...(options.packageRoot === undefined ? {} : { packageRoot: options.packageRoot }),
|
|
862
|
-
...(options.roleTurnHost === undefined ? {} : { roleTurnHost: options.roleTurnHost }),
|
|
863
|
-
...(options.hostAdapters === undefined ? {} : { hostAdapters: options.hostAdapters }),
|
|
864
|
-
...(options.principalAuthority === undefined
|
|
865
|
-
? {}
|
|
866
|
-
: { principalAuthority: options.principalAuthority }),
|
|
867
|
-
...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
|
|
868
|
-
});
|
|
869
|
-
};
|
|
870
|
-
const settled = await Promise.allSettled([
|
|
871
|
-
summon("completeness", completenessRoot),
|
|
872
|
-
summon("correctness", correctnessRoot),
|
|
873
|
-
]);
|
|
874
|
-
results = {
|
|
875
|
-
completeness: settled[0].status === "fulfilled"
|
|
876
|
-
? settled[0].value
|
|
877
|
-
: failedResult(settled[0].reason),
|
|
878
|
-
correctness: settled[1].status === "fulfilled"
|
|
879
|
-
? settled[1].value
|
|
880
|
-
: failedResult(settled[1].reason),
|
|
881
|
-
};
|
|
882
|
-
}
|
|
660
|
+
|
|
661
|
+
const summon = (
|
|
662
|
+
lens: "completeness" | "correctness",
|
|
663
|
+
): Promise<PublicSummonResult> => {
|
|
664
|
+
// Single-axis public entry as-is: caller's argv + only --lens, under the
|
|
665
|
+
// same cwd the omitted-lens call used. Durable projectRoot admits from
|
|
666
|
+
// that argv/cwd pair; host turn runs on the ticket worktree (#997).
|
|
667
|
+
return summonPublicRole({
|
|
668
|
+
role: "reviewer",
|
|
669
|
+
argv: withReviewerLens(options.argv, lens),
|
|
670
|
+
cwd: options.cwd,
|
|
671
|
+
// Ordinary single-axis public semantics — not a nested court station.
|
|
672
|
+
stationChild: false,
|
|
673
|
+
home: options.home,
|
|
674
|
+
...(options.agentDir === undefined ? {} : { agentDir: options.agentDir }),
|
|
675
|
+
...(options.credentials === undefined ? {} : { credentials: options.credentials }),
|
|
676
|
+
...(options.model === undefined ? {} : { model: options.model }),
|
|
677
|
+
...(options.host === undefined ? {} : { host: options.host }),
|
|
678
|
+
...(options.engine === undefined ? {} : { engine: options.engine }),
|
|
679
|
+
...(options.engineModel === undefined ? {} : { engineModel: options.engineModel }),
|
|
680
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
681
|
+
...(options.correlationId === undefined ? {} : { correlationId: options.correlationId }),
|
|
682
|
+
...(options.packageRoot === undefined ? {} : { packageRoot: options.packageRoot }),
|
|
683
|
+
...(options.roleTurnHost === undefined ? {} : { roleTurnHost: options.roleTurnHost }),
|
|
684
|
+
...(options.hostAdapters === undefined ? {} : { hostAdapters: options.hostAdapters }),
|
|
685
|
+
...(options.principalAuthority === undefined
|
|
686
|
+
? {}
|
|
687
|
+
: { principalAuthority: options.principalAuthority }),
|
|
688
|
+
...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
|
|
689
|
+
});
|
|
690
|
+
};
|
|
691
|
+
const settled = await Promise.allSettled([
|
|
692
|
+
summon("completeness"),
|
|
693
|
+
summon("correctness"),
|
|
694
|
+
]);
|
|
695
|
+
let results = {
|
|
696
|
+
completeness: settled[0].status === "fulfilled"
|
|
697
|
+
? settled[0].value
|
|
698
|
+
: failedResult(settled[0].reason),
|
|
699
|
+
correctness: settled[1].status === "fulfilled"
|
|
700
|
+
? settled[1].value
|
|
701
|
+
: failedResult(settled[1].reason),
|
|
702
|
+
};
|
|
703
|
+
|
|
883
704
|
let sealDiagnostic: string | undefined;
|
|
884
705
|
try {
|
|
885
706
|
const { stdout: headBeforeStdout } = await execFileAsync(
|
|
@@ -927,37 +748,6 @@ export async function summonParallelReviewerLenses(options: {
|
|
|
927
748
|
correctness: withSealFailure(results.correctness),
|
|
928
749
|
};
|
|
929
750
|
}
|
|
930
|
-
// Stateless worktrees: always remove every axis created for this call.
|
|
931
|
-
// Delete failure is diagnostic only — do not flip child exit codes; leftover
|
|
932
|
-
// tmp dirs are left to the OS and git worktree prune (#946).
|
|
933
|
-
const cleanup = await Promise.allSettled(
|
|
934
|
-
[...created].map((path) =>
|
|
935
|
-
execFileAsync("git", ["worktree", "remove", path], { cwd: sourceProjectRoot })),
|
|
936
|
-
);
|
|
937
|
-
const cleanupFailures = cleanup.flatMap((result) =>
|
|
938
|
-
result.status === "rejected" ? [result.reason] : []);
|
|
939
|
-
if (cleanupFailures.length === 0) {
|
|
940
|
-
const rootCleanup = await Promise.allSettled([rm(root, { recursive: true, force: true })]);
|
|
941
|
-
cleanupFailures.push(...rootCleanup.flatMap((result) =>
|
|
942
|
-
result.status === "rejected" ? [result.reason] : []));
|
|
943
|
-
}
|
|
944
|
-
if (cleanupFailures.length > 0) {
|
|
945
|
-
const diagnostic = [
|
|
946
|
-
"parallel reviewer worktree cleanup failed",
|
|
947
|
-
...cleanupFailures.map((failure) =>
|
|
948
|
-
failure instanceof Error ? failure.message : String(failure)),
|
|
949
|
-
].join("\n");
|
|
950
|
-
const withCleanupDiagnostic = (result: PublicSummonResult): PublicSummonResult => ({
|
|
951
|
-
...result,
|
|
952
|
-
stderr: [result.stderr, diagnostic].filter(
|
|
953
|
-
(text): text is string => typeof text === "string" && text !== "",
|
|
954
|
-
).join("\n"),
|
|
955
|
-
});
|
|
956
|
-
results = {
|
|
957
|
-
completeness: withCleanupDiagnostic(results.completeness),
|
|
958
|
-
correctness: withCleanupDiagnostic(results.correctness),
|
|
959
|
-
};
|
|
960
|
-
}
|
|
961
751
|
return results;
|
|
962
752
|
}
|
|
963
753
|
|