@deftai/directive-core 0.107.0 → 0.108.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/dist/architecture/sor-preflight.js +2 -0
- package/dist/check/cached-orchestrator.js +28 -3
- package/dist/check/named-cause.js +18 -3
- package/dist/codebase/provider.js +7 -1
- package/dist/delivery-attempt/handoff.js +1 -1
- package/dist/design-critique/citation-grammar.d.ts +53 -0
- package/dist/design-critique/citation-grammar.js +303 -0
- package/dist/design-critique/completed-arc-record.d.ts +62 -0
- package/dist/design-critique/completed-arc-record.js +232 -0
- package/dist/freshness/bind.d.ts +32 -3
- package/dist/freshness/bind.js +58 -12
- package/dist/hooks/classify/host-session-identity.d.ts +76 -0
- package/dist/hooks/classify/host-session-identity.js +470 -0
- package/dist/hooks/classify/index.d.ts +3 -2
- package/dist/hooks/classify/index.js +3 -2
- package/dist/hooks/classify/paths.d.ts +7 -0
- package/dist/hooks/classify/paths.js +35 -0
- package/dist/hooks/classify/stdin.d.ts +5 -0
- package/dist/hooks/classify/stdin.js +27 -0
- package/dist/hooks/dispatcher.d.ts +52 -1
- package/dist/hooks/dispatcher.js +538 -45
- package/dist/hooks/fixtures/cases.d.ts +16 -2
- package/dist/hooks/fixtures/cases.js +175 -0
- package/dist/hooks/scope.js +3 -1
- package/dist/init-deposit/agent-hooks.d.ts +2 -1
- package/dist/init-deposit/agent-hooks.js +8 -1
- package/dist/init-deposit/hook-runtime-travel.d.ts +77 -0
- package/dist/init-deposit/hook-runtime-travel.js +269 -0
- package/dist/intake/clause-derivation.js +9 -3
- package/dist/intake/issue-ingest.js +62 -2
- package/dist/intake/platform-capabilities.d.ts +9 -2
- package/dist/intake/platform-capabilities.js +38 -9
- package/dist/orphan-active/evaluate.d.ts +25 -0
- package/dist/orphan-active/evaluate.js +193 -128
- package/dist/orphan-active/index.d.ts +1 -0
- package/dist/orphan-active/index.js +1 -0
- package/dist/orphan-active/issue-state.d.ts +121 -0
- package/dist/orphan-active/issue-state.js +278 -0
- package/dist/platform/cursor-managed-runtime.d.ts +95 -0
- package/dist/platform/cursor-managed-runtime.js +241 -0
- package/dist/platform/index.d.ts +1 -0
- package/dist/platform/index.js +1 -0
- package/dist/platform/platform-capabilities.d.ts +23 -1
- package/dist/platform/platform-capabilities.js +55 -15
- package/dist/policy/ceremony-dial.js +5 -9
- package/dist/policy/host-hooks.js +4 -9
- package/dist/policy/org-force-on-migration.js +5 -10
- package/dist/policy/plan-extensions.d.ts +14 -2
- package/dist/policy/plan-extensions.js +24 -3
- package/dist/policy/product-signal.js +5 -10
- package/dist/policy/require-human-merge.js +5 -9
- package/dist/policy/resolve.js +77 -16
- package/dist/policy/value-feedback.js +9 -18
- package/dist/pr-closeout-attestable/evaluate.d.ts +80 -0
- package/dist/pr-closeout-attestable/evaluate.js +306 -0
- package/dist/pr-closeout-attestable/index.d.ts +2 -0
- package/dist/pr-closeout-attestable/index.js +2 -0
- package/dist/pr-protected-issues/gh.d.ts +6 -2
- package/dist/pr-protected-issues/gh.js +7 -3
- package/dist/pr-wait-mergeable/cascade.d.ts +9 -1
- package/dist/pr-wait-mergeable/cascade.js +21 -1
- package/dist/pr-wait-mergeable/types.d.ts +2 -0
- package/dist/pr-wait-mergeable/wrappers.d.ts +6 -0
- package/dist/pr-wait-mergeable/wrappers.js +11 -0
- package/dist/preflight/evaluate.d.ts +15 -0
- package/dist/preflight/evaluate.js +29 -3
- package/dist/product-first-done-gate/acceptance-resolver.d.ts +14 -2
- package/dist/product-first-done-gate/acceptance-resolver.js +13 -2
- package/dist/product-first-done-gate/evaluate.js +17 -7
- package/dist/release/cli-drift-report.d.ts +68 -0
- package/dist/release/cli-drift-report.js +189 -0
- package/dist/release/index.d.ts +1 -0
- package/dist/release/index.js +1 -0
- package/dist/release/issue-state-fetch.d.ts +5 -3
- package/dist/release/issue-state-fetch.js +86 -21
- package/dist/release/native-steps.js +1 -0
- package/dist/release/pipeline.js +19 -0
- package/dist/release/types.d.ts +14 -0
- package/dist/render/constants.d.ts +4 -0
- package/dist/render/constants.js +11 -8
- package/dist/render/export-spec.js +31 -5
- package/dist/render/index.d.ts +1 -1
- package/dist/render/index.js +1 -1
- package/dist/render/prd-render.d.ts +4 -1
- package/dist/render/prd-render.js +82 -23
- package/dist/render/project-render.js +14 -7
- package/dist/render/scope-outlook.d.ts +2 -0
- package/dist/render/scope-outlook.js +3 -0
- package/dist/render/spec-render.js +2 -2
- package/dist/resolution/package-manager.d.ts +33 -4
- package/dist/resolution/package-manager.js +210 -9
- package/dist/review-monitor/constants.d.ts +7 -0
- package/dist/review-monitor/constants.js +7 -0
- package/dist/review-monitor/github-lease.d.ts +98 -1
- package/dist/review-monitor/github-lease.js +182 -1
- package/dist/review-monitor/lease-comment.d.ts +36 -0
- package/dist/review-monitor/lease-comment.js +103 -2
- package/dist/scm/call.js +12 -3
- package/dist/scm/design-critique-chip.d.ts +1 -0
- package/dist/scm/design-critique-chip.js +22 -6
- package/dist/scm/gh-rest.d.ts +9 -1
- package/dist/scm/gh-rest.js +103 -0
- package/dist/scm/readiness.d.ts +2 -0
- package/dist/scm/readiness.js +28 -4
- package/dist/scope/project-definition-sync.js +14 -7
- package/dist/session/git.d.ts +24 -0
- package/dist/session/git.js +138 -2
- package/dist/session/occupancy.d.ts +238 -12
- package/dist/session/occupancy.js +815 -29
- package/dist/session/ritual-sentinel.d.ts +5 -0
- package/dist/session/ritual-sentinel.js +5 -0
- package/dist/session/session-ready.d.ts +5 -1
- package/dist/session/session-ready.js +98 -8
- package/dist/session/session-start.d.ts +5 -1
- package/dist/session/session-start.js +32 -17
- package/dist/session/toolchain-preflight.d.ts +13 -3
- package/dist/session/toolchain-preflight.js +93 -18
- package/dist/session/verify-session-ritual.d.ts +11 -0
- package/dist/session/verify-session-ritual.js +60 -15
- package/dist/slice/constants.d.ts +1 -1
- package/dist/spec-authority/constants.d.ts +10 -2
- package/dist/spec-authority/constants.js +53 -8
- package/dist/spec-authority/resolver.d.ts +3 -0
- package/dist/spec-authority/resolver.js +55 -7
- package/dist/subprocess/max-buffer.d.ts +14 -0
- package/dist/subprocess/max-buffer.js +15 -0
- package/dist/swarm/complete-cohort.d.ts +2 -0
- package/dist/swarm/complete-cohort.js +21 -13
- package/dist/swarm/index.d.ts +1 -0
- package/dist/swarm/index.js +1 -0
- package/dist/swarm/launch-cli.js +53 -33
- package/dist/swarm/launch.d.ts +8 -0
- package/dist/swarm/launch.js +63 -43
- package/dist/swarm/pre-dispatch-cli.js +2 -1
- package/dist/swarm/pre-dispatch.js +10 -1
- package/dist/swarm/subagent-status-dir.d.ts +27 -0
- package/dist/swarm/subagent-status-dir.js +42 -0
- package/dist/swarm/worktrees.d.ts +17 -3
- package/dist/swarm/worktrees.js +48 -4
- package/dist/triage/scope/mutations-core.d.ts +2 -6
- package/dist/triage/scope/mutations-core.js +23 -55
- package/dist/triage/scope-drift/add-ignore.js +5 -39
- package/dist/triage/subscribe/index.d.ts +2 -6
- package/dist/triage/subscribe/index.js +15 -65
- package/dist/triage/welcome/writers.js +15 -74
- package/dist/vbrief-build/index.d.ts +2 -1
- package/dist/vbrief-build/index.js +2 -1
- package/dist/vbrief-build/parity-scenarios.js +5 -5
- package/dist/vbrief-build/project-definition-io.d.ts +63 -3
- package/dist/vbrief-build/project-definition-io.js +357 -31
- package/dist/vbrief-build/project-definition-mutation.d.ts +32 -0
- package/dist/vbrief-build/project-definition-mutation.js +43 -0
- package/dist/vbrief-validate/conformance.d.ts +1 -0
- package/dist/vbrief-validate/conformance.js +85 -8
- package/dist/vbrief-validate/main.js +2 -0
- package/dist/vbrief-validate/precutover.js +5 -10
- package/dist/verify-ac/clauses.d.ts +62 -2
- package/dist/verify-ac/clauses.js +183 -111
- package/dist/verify-ac/index.d.ts +1 -1
- package/dist/verify-ac/index.js +1 -1
- package/dist/verify-env/node-runtime.d.ts +8 -4
- package/dist/verify-env/node-runtime.js +9 -6
- package/dist/verify-env/toolchain-check.d.ts +35 -3
- package/dist/verify-env/toolchain-check.js +155 -36
- package/dist/verify-source/project-definition-mutation-boundary.d.ts +104 -0
- package/dist/verify-source/project-definition-mutation-boundary.js +326 -0
- package/package.json +7 -3
|
@@ -15,4 +15,19 @@
|
|
|
15
15
|
* so an overflow is never silent again.
|
|
16
16
|
*/
|
|
17
17
|
export const SUBPROCESS_MAX_BUFFER = 64 * 1024 * 1024;
|
|
18
|
+
/**
|
|
19
|
+
* Reason text for a failed capture, so an overflow is never blank (#1867 / #3903).
|
|
20
|
+
*
|
|
21
|
+
* A spawn-level failure (ENOBUFS past the ceiling, timeout kill, missing
|
|
22
|
+
* binary) reports no exit status and empty stderr, whether it arrives as a
|
|
23
|
+
* `spawnSync` result or as a thrown `execFileSync` error. Only then does the
|
|
24
|
+
* error message stand in for stderr: a process that exited with a status and
|
|
25
|
+
* chose to say nothing keeps its empty stderr.
|
|
26
|
+
*/
|
|
27
|
+
export function resolveCaptureFailureStderr(capture) {
|
|
28
|
+
if (capture.captured.trim().length > 0 || typeof capture.status === "number") {
|
|
29
|
+
return capture.captured;
|
|
30
|
+
}
|
|
31
|
+
return capture.message ?? capture.captured;
|
|
32
|
+
}
|
|
18
33
|
//# sourceMappingURL=max-buffer.js.map
|
|
@@ -47,6 +47,8 @@ export declare function completeCohort(args: {
|
|
|
47
47
|
delivery?: CohortDeliveryContext | null;
|
|
48
48
|
/** Optional launch allocation plan id so close-out reads this cohort's occupancy slot. */
|
|
49
49
|
allocationPlanId?: string | null;
|
|
50
|
+
/** Test seam and explicit ambient-owner source for close-out corroboration. */
|
|
51
|
+
env?: NodeJS.ProcessEnv;
|
|
50
52
|
}): {
|
|
51
53
|
exitCode: number;
|
|
52
54
|
stdout: string;
|
|
@@ -471,25 +471,33 @@ export function completeCohort(args) {
|
|
|
471
471
|
});
|
|
472
472
|
result.errors.push(...errors);
|
|
473
473
|
if (result.ok && args.dryRun !== true) {
|
|
474
|
-
const
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
else if (readOccupancy(projectRoot) !== null) {
|
|
474
|
+
const env = args.env ?? process.env;
|
|
475
|
+
const envSession = env.DEFT_SESSION_ID?.trim() ?? "";
|
|
476
|
+
const liveOccupancy = readOccupancy(projectRoot);
|
|
477
|
+
const resolved = resolveLaunchOccupancySessionId(projectRoot, {
|
|
478
|
+
allocationPlanId: args.allocationPlanId ?? null,
|
|
479
|
+
storyIds: paths.map(storyIdFromPath),
|
|
480
|
+
});
|
|
481
|
+
let sessionId = "";
|
|
482
|
+
if (liveOccupancy !== null) {
|
|
483
|
+
if (resolved.reason !== "ok" || resolved.sessionId.length === 0) {
|
|
485
484
|
result.ok = false;
|
|
486
485
|
result.errors.push("swarm close-out occupancy record is missing or belongs to a different cohort. " +
|
|
487
|
-
"
|
|
486
|
+
"Re-establish an aligned owner with session:start --steal --confirm " +
|
|
487
|
+
"--occupant <reported-session-id> --session-id=<your-session-id>.");
|
|
488
|
+
}
|
|
489
|
+
else if (envSession.length > 0 && envSession !== resolved.sessionId) {
|
|
490
|
+
result.ok = false;
|
|
491
|
+
result.errors.push(`swarm close-out ambient owner ${envSession} conflicts with cohort owner ` +
|
|
492
|
+
`${resolved.sessionId}; refusing to release a later session's live lease.`);
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
sessionId = resolved.sessionId;
|
|
488
496
|
}
|
|
489
497
|
}
|
|
490
498
|
if (result.ok && sessionId.length > 0) {
|
|
491
499
|
const released = releaseSwarmOccupancy(projectRoot, {
|
|
492
|
-
env
|
|
500
|
+
env,
|
|
493
501
|
sessionId,
|
|
494
502
|
});
|
|
495
503
|
if (released.code !== 0) {
|
package/dist/swarm/index.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export { routingSetMain } from "./routing-set-cli.js";
|
|
|
14
14
|
export * from "./routing-verify.js";
|
|
15
15
|
export { routingVerifyMain } from "./routing-verify-cli.js";
|
|
16
16
|
export * from "./subagent-backend.js";
|
|
17
|
+
export * from "./subagent-status-dir.js";
|
|
17
18
|
export * from "./subprocess.js";
|
|
18
19
|
export * from "./verify-review-clean.js";
|
|
19
20
|
export { verifyReviewCleanMain } from "./verify-review-clean-cli.js";
|
package/dist/swarm/index.js
CHANGED
|
@@ -14,6 +14,7 @@ export { routingSetMain } from "./routing-set-cli.js";
|
|
|
14
14
|
export * from "./routing-verify.js";
|
|
15
15
|
export { routingVerifyMain } from "./routing-verify-cli.js";
|
|
16
16
|
export * from "./subagent-backend.js";
|
|
17
|
+
export * from "./subagent-status-dir.js";
|
|
17
18
|
export * from "./subprocess.js";
|
|
18
19
|
export * from "./verify-review-clean.js";
|
|
19
20
|
export { verifyReviewCleanMain } from "./verify-review-clean-cli.js";
|
package/dist/swarm/launch-cli.js
CHANGED
|
@@ -17,54 +17,58 @@ export function parseLaunchArgv(argv) {
|
|
|
17
17
|
let enforceGatesFlag = false;
|
|
18
18
|
let noAudit = false;
|
|
19
19
|
let projectRoot = ".";
|
|
20
|
+
let sessionId = null;
|
|
21
|
+
let parseError = null;
|
|
20
22
|
for (let i = 0; i < argv.length; i += 1) {
|
|
21
23
|
const arg = argv[i];
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
const takeValue = () => {
|
|
25
|
+
const value = argv[i + 1];
|
|
26
|
+
if (value === undefined || value.startsWith("--")) {
|
|
27
|
+
parseError ??= `argument ${arg}: expected one argument`;
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
25
30
|
i += 1;
|
|
31
|
+
return value;
|
|
32
|
+
};
|
|
33
|
+
if (arg === "--stories") {
|
|
34
|
+
const value = takeValue();
|
|
35
|
+
if (value !== null)
|
|
36
|
+
stories.push(value);
|
|
26
37
|
}
|
|
27
|
-
else if (arg === "--paths"
|
|
28
|
-
|
|
29
|
-
|
|
38
|
+
else if (arg === "--paths") {
|
|
39
|
+
const value = takeValue();
|
|
40
|
+
if (value !== null)
|
|
41
|
+
paths.push(value);
|
|
30
42
|
}
|
|
31
|
-
else if (arg === "--group"
|
|
32
|
-
group =
|
|
33
|
-
i += 1;
|
|
43
|
+
else if (arg === "--group") {
|
|
44
|
+
group = takeValue();
|
|
34
45
|
}
|
|
35
|
-
else if (arg === "--worktree-map"
|
|
36
|
-
worktreeMap =
|
|
37
|
-
i += 1;
|
|
46
|
+
else if (arg === "--worktree-map") {
|
|
47
|
+
worktreeMap = takeValue();
|
|
38
48
|
}
|
|
39
|
-
else if (arg === "--base-branch"
|
|
40
|
-
baseBranch =
|
|
41
|
-
i += 1;
|
|
49
|
+
else if (arg === "--base-branch") {
|
|
50
|
+
baseBranch = takeValue() ?? baseBranch;
|
|
42
51
|
}
|
|
43
52
|
else if (arg === "--autonomous") {
|
|
44
53
|
autonomous = true;
|
|
45
54
|
}
|
|
46
|
-
else if (arg === "--allocation-plan-id"
|
|
47
|
-
allocationPlanId =
|
|
48
|
-
i += 1;
|
|
55
|
+
else if (arg === "--allocation-plan-id") {
|
|
56
|
+
allocationPlanId = takeValue();
|
|
49
57
|
}
|
|
50
|
-
else if (arg === "--batching-rationale"
|
|
51
|
-
batchingRationale =
|
|
52
|
-
i += 1;
|
|
58
|
+
else if (arg === "--batching-rationale") {
|
|
59
|
+
batchingRationale = takeValue();
|
|
53
60
|
}
|
|
54
|
-
else if (arg === "--operator-approval"
|
|
55
|
-
operatorApproval =
|
|
56
|
-
i += 1;
|
|
61
|
+
else if (arg === "--operator-approval") {
|
|
62
|
+
operatorApproval = takeValue();
|
|
57
63
|
}
|
|
58
64
|
else if (arg === "--no-create-worktrees") {
|
|
59
65
|
noCreateWorktrees = true;
|
|
60
66
|
}
|
|
61
|
-
else if (arg === "--output"
|
|
62
|
-
output =
|
|
63
|
-
i += 1;
|
|
67
|
+
else if (arg === "--output") {
|
|
68
|
+
output = takeValue();
|
|
64
69
|
}
|
|
65
|
-
else if (arg === "--gate-clearances"
|
|
66
|
-
gateClearancesPath =
|
|
67
|
-
i += 1;
|
|
70
|
+
else if (arg === "--gate-clearances") {
|
|
71
|
+
gateClearancesPath = takeValue();
|
|
68
72
|
}
|
|
69
73
|
else if (arg === "--enforce-gates") {
|
|
70
74
|
enforceGatesFlag = true;
|
|
@@ -72,12 +76,27 @@ export function parseLaunchArgv(argv) {
|
|
|
72
76
|
else if (arg === "--no-audit") {
|
|
73
77
|
noAudit = true;
|
|
74
78
|
}
|
|
75
|
-
else if (arg === "--project-root"
|
|
76
|
-
projectRoot =
|
|
77
|
-
|
|
79
|
+
else if (arg === "--project-root") {
|
|
80
|
+
projectRoot = takeValue() ?? projectRoot;
|
|
81
|
+
}
|
|
82
|
+
else if (arg === "--session-id") {
|
|
83
|
+
const value = takeValue();
|
|
84
|
+
if (value === null) {
|
|
85
|
+
// Keep an explicit invalid sentinel so swarmLaunch fails closed rather
|
|
86
|
+
// than falling back to ambient identity or minting a different owner.
|
|
87
|
+
sessionId = "";
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
sessionId = value;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else if (arg?.startsWith("--session-id=") === true) {
|
|
94
|
+
const value = arg.slice("--session-id=".length);
|
|
95
|
+
sessionId = value.startsWith("--") ? "" : value;
|
|
78
96
|
}
|
|
79
97
|
}
|
|
80
98
|
return {
|
|
99
|
+
parseError,
|
|
81
100
|
stories,
|
|
82
101
|
paths,
|
|
83
102
|
group,
|
|
@@ -93,6 +112,7 @@ export function parseLaunchArgv(argv) {
|
|
|
93
112
|
enforceGatesFlag,
|
|
94
113
|
noAudit,
|
|
95
114
|
projectRoot,
|
|
115
|
+
sessionId,
|
|
96
116
|
};
|
|
97
117
|
}
|
|
98
118
|
export function launchMain(argv = process.argv.slice(2)) {
|
package/dist/swarm/launch.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ExpectedGithubWorkerPrincipal, type GhRunner, GITHUB_AUTH_MODE_HOST_GH, GITHUB_AUTH_MODE_INJECTED_TOKEN } from "../intake/github-auth-modes.js";
|
|
2
|
+
import { releaseOccupancy } from "../session/occupancy.js";
|
|
2
3
|
import { EXIT_CONFIG_ERROR, EXIT_GATE_FAILED, EXIT_OK } from "./constants.js";
|
|
3
4
|
import { type WorktreeRecord } from "./worktrees.js";
|
|
4
5
|
export interface ResolvedStory {
|
|
@@ -19,6 +20,7 @@ export type WorktreeResolverFn = (mapping: readonly Record<string, unknown>[], b
|
|
|
19
20
|
repoRoot?: string;
|
|
20
21
|
}) => WorktreeRecord[];
|
|
21
22
|
export type RuntimeAuthProbeFn = () => [string, string];
|
|
23
|
+
export type OccupancyReleaseFn = typeof releaseOccupancy;
|
|
22
24
|
export declare const defaultPreflightGate: PreflightGateFn;
|
|
23
25
|
export declare const defaultReadinessGate: ReadinessGateFn;
|
|
24
26
|
export declare const defaultRuntimeAuthProbe: RuntimeAuthProbeFn;
|
|
@@ -144,6 +146,8 @@ export declare function buildManifest(resolved: readonly ResolvedStory[], option
|
|
|
144
146
|
}): Record<string, unknown>[];
|
|
145
147
|
export declare function orderCohort(resolved: readonly ResolvedStory[], _projectRoot: string): ResolvedStory[];
|
|
146
148
|
export interface LaunchArgs {
|
|
149
|
+
/** CLI parse failure carried into the core so later valid flags cannot erase it. */
|
|
150
|
+
parseError?: string | null;
|
|
147
151
|
stories?: readonly string[];
|
|
148
152
|
paths?: readonly string[];
|
|
149
153
|
group?: string | null;
|
|
@@ -159,10 +163,14 @@ export interface LaunchArgs {
|
|
|
159
163
|
enforceGatesFlag?: boolean;
|
|
160
164
|
noAudit?: boolean;
|
|
161
165
|
projectRoot?: string;
|
|
166
|
+
/** Explicit cooperative owner identity, including host-payload canonical IDs (#3611). */
|
|
167
|
+
sessionId?: string | null;
|
|
162
168
|
preflightGate?: PreflightGateFn;
|
|
163
169
|
readinessGate?: ReadinessGateFn;
|
|
164
170
|
worktreeResolver?: WorktreeResolverFn;
|
|
165
171
|
runtimeAuthProbe?: RuntimeAuthProbeFn;
|
|
172
|
+
/** Test seam: override occupancy release after a newly minted claim fails. */
|
|
173
|
+
releaseOccupancyFn?: OccupancyReleaseFn;
|
|
166
174
|
/**
|
|
167
175
|
* Injection seam for the routing-provider environment lookup, mirroring
|
|
168
176
|
* `resolveRoutingPath`'s `environ` parameter (#1877 Greptile follow-up).
|
package/dist/swarm/launch.js
CHANGED
|
@@ -5,7 +5,7 @@ import { ENV_EXPECTED_GITHUB_LOGIN, FAILURE_INSTALLATION_IDENTITY_UNVERIFIABLE,
|
|
|
5
5
|
import { getPlatformCapabilities } from "../intake/platform-capabilities.js";
|
|
6
6
|
import { hasArtifactSuffix, resolveLifecycleFolder, stripArtifactSuffix, } from "../layout/resolve.js";
|
|
7
7
|
import { evaluate as preflightEvaluate } from "../preflight/evaluate.js";
|
|
8
|
-
import { applyWorktreeOccupancy } from "../session/occupancy.js";
|
|
8
|
+
import { applyWorktreeOccupancy, releaseOccupancy } from "../session/occupancy.js";
|
|
9
9
|
import { issueNumbersFromPlan, scopeMetadataRank } from "../triage/queue/scope-walk.js";
|
|
10
10
|
import { selectionOrderingKey } from "../triage/queue/selection.js";
|
|
11
11
|
import { DEFAULT_BASE_BRANCH, EXIT_CONFIG_ERROR, EXIT_GATE_FAILED, EXIT_OK, GATE_ADVISE, GATE_ENFORCE, LEAF_CODING_WORKER_ROLE, } from "./constants.js";
|
|
@@ -695,7 +695,23 @@ function splitCsv(values) {
|
|
|
695
695
|
return out;
|
|
696
696
|
}
|
|
697
697
|
export function swarmLaunch(args) {
|
|
698
|
+
if (args.parseError !== undefined && args.parseError !== null) {
|
|
699
|
+
return {
|
|
700
|
+
exitCode: EXIT_CONFIG_ERROR,
|
|
701
|
+
stdout: "",
|
|
702
|
+
stderr: `Error: ${args.parseError}\n`,
|
|
703
|
+
};
|
|
704
|
+
}
|
|
698
705
|
const projectRoot = resolve(args.projectRoot ?? process.cwd());
|
|
706
|
+
if (args.sessionId !== undefined &&
|
|
707
|
+
args.sessionId !== null &&
|
|
708
|
+
(args.sessionId.trim().length === 0 || args.sessionId.trim().startsWith("--"))) {
|
|
709
|
+
return {
|
|
710
|
+
exitCode: EXIT_CONFIG_ERROR,
|
|
711
|
+
stdout: "",
|
|
712
|
+
stderr: "Error: --session-id requires a non-empty owner identity.\n",
|
|
713
|
+
};
|
|
714
|
+
}
|
|
699
715
|
const tokens = [...splitCsv(args.stories ?? []), ...splitCsv(args.paths ?? [])];
|
|
700
716
|
if (tokens.length === 0) {
|
|
701
717
|
return {
|
|
@@ -781,17 +797,52 @@ export function swarmLaunch(args) {
|
|
|
781
797
|
}
|
|
782
798
|
const operatorApproval = args.operatorApproval ??
|
|
783
799
|
`task swarm:launch (${args.autonomous ? "autonomous" : "interactive"})`;
|
|
800
|
+
const occupancy = applyWorktreeOccupancy(projectRoot, {
|
|
801
|
+
sessionId: args.sessionId ?? undefined,
|
|
802
|
+
env: args.environ ?? process.env,
|
|
803
|
+
intent: "swarm",
|
|
804
|
+
});
|
|
805
|
+
if (occupancy.code !== 0) {
|
|
806
|
+
return {
|
|
807
|
+
exitCode: EXIT_GATE_FAILED,
|
|
808
|
+
stdout: "",
|
|
809
|
+
stderr: `${occupancy.message}\n`,
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
// Heartbeat on an existing owner is not a new claim -- only release a lease
|
|
813
|
+
// this process just minted (#3649 paired failure clause).
|
|
814
|
+
const newlyClaimed = occupancy.action === "claimed";
|
|
815
|
+
const failAfterClaim = (exitCode, stderr) => {
|
|
816
|
+
if (newlyClaimed) {
|
|
817
|
+
const recovery = "Occupancy release failed after this launch error; the lease may still be live. " +
|
|
818
|
+
"The occupant may release (occupancy:release / session:end). A replacement owner must " +
|
|
819
|
+
"run session:start --steal --confirm --occupant <reported-session-id> " +
|
|
820
|
+
"--session-id=<your-session-id> to align lease and ritual state.";
|
|
821
|
+
try {
|
|
822
|
+
const release = args.releaseOccupancyFn ?? releaseOccupancy;
|
|
823
|
+
const decision = release(projectRoot, {
|
|
824
|
+
sessionId: occupancy.sessionId,
|
|
825
|
+
env: args.environ ?? process.env,
|
|
826
|
+
});
|
|
827
|
+
if (decision.code !== 0) {
|
|
828
|
+
stderr = `${stderr}\n${recovery} ${decision.message}\n`;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
catch (exc) {
|
|
832
|
+
// Do not replace the original structured launch error; tell the operator
|
|
833
|
+
// the lease may still deny later sessions (#3649 Greptile P1).
|
|
834
|
+
stderr = `${stderr}\n${recovery} ${String(exc)}\n`;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
return { exitCode, stdout: "", stderr };
|
|
838
|
+
};
|
|
784
839
|
let worktreeRecordMap = new Map();
|
|
785
840
|
if (args.worktreeMap !== undefined && args.worktreeMap !== null) {
|
|
786
841
|
const resolver = args.worktreeResolver ?? resolveWorktreeMap;
|
|
787
842
|
try {
|
|
788
843
|
const payload = JSON.parse(readFileSync(args.worktreeMap, "utf8"));
|
|
789
844
|
if (!Array.isArray(payload)) {
|
|
790
|
-
return {
|
|
791
|
-
exitCode: EXIT_CONFIG_ERROR,
|
|
792
|
-
stdout: "",
|
|
793
|
-
stderr: `Error: --worktree-map ${args.worktreeMap} must contain a JSON array of records.\n`,
|
|
794
|
-
};
|
|
845
|
+
return failAfterClaim(EXIT_CONFIG_ERROR, `Error: --worktree-map ${args.worktreeMap} must contain a JSON array of records.\n`);
|
|
795
846
|
}
|
|
796
847
|
const records = resolver(payload, args.baseBranch ?? DEFAULT_BASE_BRANCH, !(args.noCreateWorktrees ?? false), {
|
|
797
848
|
repoRoot: projectRoot,
|
|
@@ -799,11 +850,7 @@ export function swarmLaunch(args) {
|
|
|
799
850
|
worktreeRecordMap = new Map(records.map((r) => [r.story_id, r]));
|
|
800
851
|
}
|
|
801
852
|
catch (exc) {
|
|
802
|
-
return {
|
|
803
|
-
exitCode: EXIT_CONFIG_ERROR,
|
|
804
|
-
stdout: "",
|
|
805
|
-
stderr: `Error: worktree map resolution failed: ${String(exc)}\n`,
|
|
806
|
-
};
|
|
853
|
+
return failAfterClaim(EXIT_CONFIG_ERROR, `Error: worktree map resolution failed: ${String(exc)}\n`);
|
|
807
854
|
}
|
|
808
855
|
}
|
|
809
856
|
let runtimeMode;
|
|
@@ -813,7 +860,7 @@ export function swarmLaunch(args) {
|
|
|
813
860
|
[runtimeMode, githubAuthMode] = probe();
|
|
814
861
|
}
|
|
815
862
|
catch (exc) {
|
|
816
|
-
return
|
|
863
|
+
return failAfterClaim(EXIT_CONFIG_ERROR, `Error: ${String(exc)}\n`);
|
|
817
864
|
}
|
|
818
865
|
let resolvedModel = null;
|
|
819
866
|
let modelSource = null;
|
|
@@ -826,11 +873,7 @@ export function swarmLaunch(args) {
|
|
|
826
873
|
// would emit an exit-0 manifest with no model and no error to follow. Match
|
|
827
874
|
// verify:routing, which treats the same state as a config error (#1739).
|
|
828
875
|
if (route.source === "invalid") {
|
|
829
|
-
return {
|
|
830
|
-
exitCode: EXIT_CONFIG_ERROR,
|
|
831
|
-
stdout: "",
|
|
832
|
-
stderr: `Error: routing gate misconfigured: ${route.error ?? "invalid routing decision"}\n`,
|
|
833
|
-
};
|
|
876
|
+
return failAfterClaim(EXIT_CONFIG_ERROR, `Error: routing gate misconfigured: ${route.error ?? "invalid routing decision"}\n`);
|
|
834
877
|
}
|
|
835
878
|
if (route.decided) {
|
|
836
879
|
resolvedModel = route.model;
|
|
@@ -843,17 +886,6 @@ export function swarmLaunch(args) {
|
|
|
843
886
|
? dispatchProviderFor(backend.backend_id)
|
|
844
887
|
: null;
|
|
845
888
|
const workerRoleValue = routingFile !== null || backend !== null ? LEAF_CODING_WORKER_ROLE : null;
|
|
846
|
-
const occupancy = applyWorktreeOccupancy(projectRoot, {
|
|
847
|
-
env: args.environ ?? process.env,
|
|
848
|
-
intent: "swarm",
|
|
849
|
-
});
|
|
850
|
-
if (occupancy.code !== 0) {
|
|
851
|
-
return {
|
|
852
|
-
exitCode: EXIT_GATE_FAILED,
|
|
853
|
-
stdout: "",
|
|
854
|
-
stderr: `${occupancy.message}\n`,
|
|
855
|
-
};
|
|
856
|
-
}
|
|
857
889
|
const launchEnviron = args.environ ?? process.env;
|
|
858
890
|
let expectedGithubLogin = null;
|
|
859
891
|
// Only the injected-token launch path auto-validates (and fail-closes on a
|
|
@@ -871,11 +903,7 @@ export function swarmLaunch(args) {
|
|
|
871
903
|
cwd: projectRoot,
|
|
872
904
|
});
|
|
873
905
|
if (!injection.ok) {
|
|
874
|
-
return {
|
|
875
|
-
exitCode: EXIT_GATE_FAILED,
|
|
876
|
-
stdout: "",
|
|
877
|
-
stderr: `${injection.detail}\n${injection.remedy}\n`,
|
|
878
|
-
};
|
|
906
|
+
return failAfterClaim(EXIT_GATE_FAILED, `${injection.detail}\n${injection.remedy}\n`);
|
|
879
907
|
}
|
|
880
908
|
if (injection.injected) {
|
|
881
909
|
expectedGithubLogin = injection.expectedLogin;
|
|
@@ -919,11 +947,7 @@ export function swarmLaunch(args) {
|
|
|
919
947
|
});
|
|
920
948
|
}
|
|
921
949
|
catch (exc) {
|
|
922
|
-
return {
|
|
923
|
-
exitCode: EXIT_CONFIG_ERROR,
|
|
924
|
-
stdout: "",
|
|
925
|
-
stderr: `Error: could not persist launch occupancy_session_id: ${String(exc)}\n`,
|
|
926
|
-
};
|
|
950
|
+
return failAfterClaim(EXIT_CONFIG_ERROR, `Error: could not persist launch occupancy_session_id: ${String(exc)}\n`);
|
|
927
951
|
}
|
|
928
952
|
if (args.output !== undefined && args.output !== null) {
|
|
929
953
|
try {
|
|
@@ -939,11 +963,7 @@ export function swarmLaunch(args) {
|
|
|
939
963
|
});
|
|
940
964
|
}
|
|
941
965
|
catch (exc) {
|
|
942
|
-
return {
|
|
943
|
-
exitCode: EXIT_CONFIG_ERROR,
|
|
944
|
-
stdout: "",
|
|
945
|
-
stderr: `Error: could not write --output ${args.output}: ${String(exc)}\n`,
|
|
946
|
-
};
|
|
966
|
+
return failAfterClaim(EXIT_CONFIG_ERROR, `Error: could not write --output ${args.output}: ${String(exc)}\n`);
|
|
947
967
|
}
|
|
948
968
|
}
|
|
949
969
|
void args.noAudit;
|
|
@@ -85,7 +85,8 @@ Before spawning a peer implement leaf: run with default --action begin.
|
|
|
85
85
|
exit 1 active deny / gate block (do not spawn)
|
|
86
86
|
exit 2 config / usage error
|
|
87
87
|
|
|
88
|
-
Takeover: --action cancel, then pre-dispatch begin again
|
|
88
|
+
Takeover after REDISPATCH_OK: --action cancel, then pre-dispatch begin again
|
|
89
|
+
(not concurrent dual active; a killed worker stays running until cancelled).
|
|
89
90
|
Terminal: --action complete [--status succeeded|failed|cancelled|blocked]
|
|
90
91
|
|
|
91
92
|
Options:
|
|
@@ -12,6 +12,7 @@ import { existsSync, realpathSync } from "node:fs";
|
|
|
12
12
|
import { isAbsolute, normalize, resolve } from "node:path";
|
|
13
13
|
import { activeAttempts, beginAttempt, completeAttemptOnDisk, evaluatePreDispatch, hasActiveAttempt, listUnitLedgers, loadOrCreateUnitLedger, loadUnitLedger, markBlocked, saveUnitLedger, withUnitLock, } from "../delivery-attempt/index.js";
|
|
14
14
|
import { EXIT_CONFIG_ERROR, EXIT_GATE_FAILED, EXIT_OK } from "./constants.js";
|
|
15
|
+
import { ensureSubagentStatusDir, looksLikeWorktreeDir } from "./subagent-status-dir.js";
|
|
15
16
|
import { runText } from "./subprocess.js";
|
|
16
17
|
/** Default workflow id for drive-to:merge-ready implement leaves. */
|
|
17
18
|
export const IMPLEMENT_LEAF_WORKFLOW_ID = "drive-to:merge-ready";
|
|
@@ -170,6 +171,14 @@ function runBegin(input) {
|
|
|
170
171
|
const { scopeId, targetId, workflowId } = unitFields(input);
|
|
171
172
|
const sourceRevision = resolveSourceRevision(input.projectRoot, input.sourceRevision);
|
|
172
173
|
const trigger = input.trigger ?? "automatic";
|
|
174
|
+
// Arm heartbeat scratch so verify:subagent-alive can return REDISPATCH_OK
|
|
175
|
+
// instead of exit 2 ("scratch dir does not exist") after a host-kill (#3730).
|
|
176
|
+
// A bare relative worktree name (`b3730`) is opaque to the branch-vs-path
|
|
177
|
+
// heuristic, so accept an on-disk worktree as the second arming signal.
|
|
178
|
+
const armTarget = resolve(input.projectRoot, input.targetId.trim());
|
|
179
|
+
if (looksLikeFilesystemTarget(input.targetId) || looksLikeWorktreeDir(armTarget)) {
|
|
180
|
+
ensureSubagentStatusDir(armTarget);
|
|
181
|
+
}
|
|
173
182
|
// Exclusive lock → reload → evaluate → begin+save (same decision under lock;
|
|
174
183
|
// no stale preview decision on the success path).
|
|
175
184
|
try {
|
|
@@ -366,7 +375,7 @@ export function formatPreDispatchReport(result) {
|
|
|
366
375
|
lines.push(` attempt: ${result.attempt.attemptId} status=${result.attempt.status}`);
|
|
367
376
|
}
|
|
368
377
|
if (result.exitCode === EXIT_GATE_FAILED && result.decision === "DENY_DUPLICATE_ACTIVE") {
|
|
369
|
-
lines.push(" hint: do not spawn;
|
|
378
|
+
lines.push(" hint: do not spawn; a killed worker stays running until cancelled.", " Takeover: task swarm:pre-dispatch -- --scope-id <id> --target-id <target> --action cancel", " then the same command without --action (begin). REDISPATCH_OK does not lift DENY_DUPLICATE_ACTIVE.");
|
|
370
379
|
}
|
|
371
380
|
return lines.join("\n");
|
|
372
381
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Arm the shipped sub-agent heartbeat path (#3730 / #1365 / #2824).
|
|
3
|
+
*
|
|
4
|
+
* `verify:subagent-alive` exits 2 when the scratch directory is missing, which
|
|
5
|
+
* looks like "never started" instead of REDISPATCH_OK. Parents mkdir at
|
|
6
|
+
* worktree create and at pre-dispatch begin so a later missing record is
|
|
7
|
+
* exit 1. Does not write heartbeat records or liveness onto the C2 manifest.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* True when a resolved path is an on-disk git worktree, i.e. it carries a `.git`
|
|
11
|
+
* entry (a file for `git worktree add` trees, a directory for the main clone).
|
|
12
|
+
*
|
|
13
|
+
* Arming cannot key off `looksLikeFilesystemTarget` alone: that predicate exists
|
|
14
|
+
* to keep branch refs opaque and so answers false for a bare relative worktree
|
|
15
|
+
* name like `b3730`, leaving the liveness gate at its exit-2 config error for a
|
|
16
|
+
* supported dispatch form. Existence alone is too loose in the other direction —
|
|
17
|
+
* a branch named `docs` resolves onto the real `docs/` tree and would mkdir a
|
|
18
|
+
* stray scratch dir in the source checkout. The `.git` marker separates the two.
|
|
19
|
+
*/
|
|
20
|
+
export declare function looksLikeWorktreeDir(resolvedPath: string): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Create `<worktree>/.deft-scratch/subagent-status/` when the worktree exists.
|
|
23
|
+
* Returns the directory path, or null when the worktree is not on disk yet
|
|
24
|
+
* (do not mkdir a stray tree for a branch-name target).
|
|
25
|
+
*/
|
|
26
|
+
export declare function ensureSubagentStatusDir(worktreeRoot: string): string | null;
|
|
27
|
+
//# sourceMappingURL=subagent-status-dir.d.ts.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Arm the shipped sub-agent heartbeat path (#3730 / #1365 / #2824).
|
|
3
|
+
*
|
|
4
|
+
* `verify:subagent-alive` exits 2 when the scratch directory is missing, which
|
|
5
|
+
* looks like "never started" instead of REDISPATCH_OK. Parents mkdir at
|
|
6
|
+
* worktree create and at pre-dispatch begin so a later missing record is
|
|
7
|
+
* exit 1. Does not write heartbeat records or liveness onto the C2 manifest.
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { defaultScratchDir } from "../orchestration/subagent-monitor.js";
|
|
12
|
+
/**
|
|
13
|
+
* True when a resolved path is an on-disk git worktree, i.e. it carries a `.git`
|
|
14
|
+
* entry (a file for `git worktree add` trees, a directory for the main clone).
|
|
15
|
+
*
|
|
16
|
+
* Arming cannot key off `looksLikeFilesystemTarget` alone: that predicate exists
|
|
17
|
+
* to keep branch refs opaque and so answers false for a bare relative worktree
|
|
18
|
+
* name like `b3730`, leaving the liveness gate at its exit-2 config error for a
|
|
19
|
+
* supported dispatch form. Existence alone is too loose in the other direction —
|
|
20
|
+
* a branch named `docs` resolves onto the real `docs/` tree and would mkdir a
|
|
21
|
+
* stray scratch dir in the source checkout. The `.git` marker separates the two.
|
|
22
|
+
*/
|
|
23
|
+
export function looksLikeWorktreeDir(resolvedPath) {
|
|
24
|
+
const trimmed = resolvedPath.trim();
|
|
25
|
+
if (trimmed.length === 0)
|
|
26
|
+
return false;
|
|
27
|
+
return existsSync(join(trimmed, ".git"));
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Create `<worktree>/.deft-scratch/subagent-status/` when the worktree exists.
|
|
31
|
+
* Returns the directory path, or null when the worktree is not on disk yet
|
|
32
|
+
* (do not mkdir a stray tree for a branch-name target).
|
|
33
|
+
*/
|
|
34
|
+
export function ensureSubagentStatusDir(worktreeRoot) {
|
|
35
|
+
const trimmed = worktreeRoot.trim();
|
|
36
|
+
if (trimmed.length === 0 || !existsSync(trimmed))
|
|
37
|
+
return null;
|
|
38
|
+
const dir = defaultScratchDir(trimmed);
|
|
39
|
+
mkdirSync(dir, { recursive: true });
|
|
40
|
+
return dir;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=subagent-status-dir.js.map
|
|
@@ -21,6 +21,9 @@ export declare class WorktreeMapConfigError extends Error {
|
|
|
21
21
|
export declare class WorktreePathEscapeError extends WorktreeMapError {
|
|
22
22
|
name: string;
|
|
23
23
|
}
|
|
24
|
+
export declare class WorktreeRevisionMismatchError extends WorktreeMapError {
|
|
25
|
+
name: string;
|
|
26
|
+
}
|
|
24
27
|
export type GitRunner = (args: readonly string[], cwd: string) => TextCaptureResult;
|
|
25
28
|
export declare const defaultGitRunner: GitRunner;
|
|
26
29
|
/** Case-normalized comparison key for worktree-path equality. */
|
|
@@ -30,9 +33,20 @@ export interface WorktreeRecord {
|
|
|
30
33
|
readonly worktree_path: string;
|
|
31
34
|
readonly base_branch: string;
|
|
32
35
|
}
|
|
33
|
-
/**
|
|
34
|
-
export
|
|
35
|
-
|
|
36
|
+
/** One `git worktree list --porcelain` record. `head` is the commit OID or null. */
|
|
37
|
+
export interface WorktreePorcelainEntry {
|
|
38
|
+
readonly branch: string | null;
|
|
39
|
+
readonly head: string | null;
|
|
40
|
+
}
|
|
41
|
+
/** Parse `git worktree list --porcelain` into `{compareKey: {branch, head}}`. */
|
|
42
|
+
export declare function parseWorktreePorcelain(text: string): Map<string, WorktreePorcelainEntry>;
|
|
43
|
+
/**
|
|
44
|
+
* Resolve a story-to-worktree mapping into normalized C3 records (frozen contract).
|
|
45
|
+
*
|
|
46
|
+
* Registered paths are reused only when their porcelain HEAD OID matches the
|
|
47
|
+
* requested base OID. That comparison is a snapshot at resolution time — not a
|
|
48
|
+
* pin on the worker's start revision (`swarm:launch` emits a manifest and stops).
|
|
49
|
+
*/
|
|
36
50
|
export declare function resolveWorktreeMap(mapping: readonly Record<string, unknown>[], baseBranch: string, createMissing?: boolean, options?: {
|
|
37
51
|
repoRoot?: string;
|
|
38
52
|
git?: GitRunner;
|