@deftai/directive-core 0.98.0 → 0.99.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/authz/classify.js +400 -56
- package/dist/consumer-check-contract/evaluate.d.ts +40 -0
- package/dist/consumer-check-contract/evaluate.js +188 -3
- package/dist/consumer-check-contract/index.d.ts +1 -1
- package/dist/consumer-check-contract/index.js +1 -1
- package/dist/content-contracts/skills/greptile-detector.d.ts +42 -0
- package/dist/content-contracts/skills/greptile-detector.js +202 -4
- package/dist/decision/index.d.ts +17 -0
- package/dist/decision/index.js +35 -0
- package/dist/decision/list.d.ts +47 -0
- package/dist/decision/list.js +250 -0
- package/dist/decision/schema.d.ts +88 -0
- package/dist/decision/schema.js +293 -0
- package/dist/decision/write.d.ts +82 -0
- package/dist/decision/write.js +427 -0
- package/dist/eval/report.d.ts +29 -0
- package/dist/eval/report.js +69 -0
- package/dist/eval/run.d.ts +9 -0
- package/dist/eval/run.js +40 -4
- package/dist/eval/version-pin.d.ts +99 -0
- package/dist/eval/version-pin.js +181 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/platform/host-content-surface.d.ts +74 -0
- package/dist/platform/host-content-surface.js +214 -0
- package/dist/platform/index.d.ts +1 -0
- package/dist/platform/index.js +1 -0
- package/dist/policy/ceremony-dial.d.ts +233 -0
- package/dist/policy/ceremony-dial.js +829 -0
- package/dist/policy/deft-directive-disable.js +12 -2
- package/dist/policy/index.d.ts +1 -0
- package/dist/policy/index.js +15 -1
- package/dist/pr-merge-readiness/evaluate.js +10 -0
- package/dist/pr-merge-readiness/mergeability.js +5 -0
- package/dist/pr-merge-readiness/output.js +2 -0
- package/dist/pr-merge-readiness/parse.js +4 -0
- package/dist/pr-merge-readiness/types.d.ts +6 -0
- package/dist/scope/effort-activate-gate.d.ts +28 -0
- package/dist/scope/effort-activate-gate.js +64 -0
- package/dist/scope/index.d.ts +1 -0
- package/dist/scope/index.js +1 -0
- package/dist/scope/transition.js +8 -0
- package/dist/scope-provenance/evaluate.d.ts +21 -0
- package/dist/scope-provenance/evaluate.js +143 -33
- package/dist/scope-provenance/index.d.ts +1 -1
- package/dist/scope-provenance/index.js +1 -1
- package/dist/session/session-start.d.ts +24 -1
- package/dist/session/session-start.js +183 -26
- package/dist/swarm/index.d.ts +2 -0
- package/dist/swarm/index.js +2 -0
- package/dist/swarm/pre-dispatch-cli.d.ts +19 -0
- package/dist/swarm/pre-dispatch-cli.js +143 -0
- package/dist/swarm/pre-dispatch.d.ts +87 -0
- package/dist/swarm/pre-dispatch.js +373 -0
- package/dist/vbrief-activate/activate.js +6 -0
- package/dist/vbrief-validate/constants.d.ts +2 -0
- package/dist/vbrief-validate/constants.js +2 -0
- package/dist/vbrief-validate/schema.js +4 -1
- package/package.json +15 -3
|
@@ -4,7 +4,9 @@ import { emitSessionEvalReadback } from "../eval/readback.js";
|
|
|
4
4
|
import { bindSessionGeneration } from "../freshness/bind.js";
|
|
5
5
|
import { readLiveGeneration } from "../freshness/generation.js";
|
|
6
6
|
import { MIGRATE_COMPLETION_NUDGE, shouldEmitMigrateNudge } from "../init-deposit/migrate.js";
|
|
7
|
+
import { hostContentSurfaceToDict, maybeFormatHostContentSurfaceLines, } from "../platform/host-content-surface.js";
|
|
7
8
|
import { detectEnvironmentContext, environmentContextToDict, formatEnvironmentContext, } from "../platform/shell-context.js";
|
|
9
|
+
import { ceremonyDialToDict, formatCeremonyDialStatusLine, mergeCeremonyDialDeferrals, resolveCeremonyDial, resolveSessionCeremonyDialInputs, } from "../policy/ceremony-dial.js";
|
|
8
10
|
import { DEFT_DIRECTIVE_DISABLE_FLAG_NAME, DEFT_DIRECTIVE_DISABLE_STATUS, detectDeftDirectiveDisable, formatDeftDirectiveDisableMessage, isDeftDirectiveDisableActive, } from "../policy/deft-directive-disable.js";
|
|
9
11
|
import { disclosureLine } from "../policy/disclosure.js";
|
|
10
12
|
import { detectNoDeftDirective, NO_DEFT_DIRECTIVE_DISABLED_MESSAGE, NO_DEFT_DIRECTIVE_FLAG_NAME, NO_DEFT_DIRECTIVE_INCONSISTENT_MESSAGE, NO_DEFT_DIRECTIVE_INCONSISTENT_POLICY, } from "../policy/no-deft-directive.js";
|
|
@@ -33,7 +35,14 @@ export const READ_ONLY_RESULT_MESSAGE = "read-only session posture (alignment on
|
|
|
33
35
|
export const SESSION_CEREMONY_TIERS = ["cold", "rearm"];
|
|
34
36
|
export const COLD_CEREMONY_TIER = "cold";
|
|
35
37
|
export const REARM_CEREMONY_TIER = "rearm";
|
|
36
|
-
|
|
38
|
+
// verify_tools is mutation readiness recorded on cold path (#3214 / #3156) —
|
|
39
|
+
// included so re-arm refuses after a tools-failed cold start.
|
|
40
|
+
export const QUICK_STEPS = [
|
|
41
|
+
"alignment",
|
|
42
|
+
"branch_policy",
|
|
43
|
+
"triage_welcome",
|
|
44
|
+
"verify_tools",
|
|
45
|
+
];
|
|
37
46
|
export const GATED_STEPS = ["agent_hooks", "doctor", "cache_fresh"];
|
|
38
47
|
/** Env opt-in for optional session:start network (release probe + triage cache hydrate) (#2991). */
|
|
39
48
|
export const ENV_SESSION_START_NETWORK = "DEFT_SESSION_START_NETWORK";
|
|
@@ -105,7 +114,13 @@ export function assessRearmEligibility(projectRoot, options = {}) {
|
|
|
105
114
|
}
|
|
106
115
|
}
|
|
107
116
|
for (const stepName of QUICK_STEPS) {
|
|
108
|
-
|
|
117
|
+
const step = state.quickSteps[stepName];
|
|
118
|
+
// Legacy ritual-state (pre-#3214 tools persistence) omits verify_tools —
|
|
119
|
+
// treat missing as pass; explicit failure still refuses re-arm.
|
|
120
|
+
if (stepName === "verify_tools" && (step === undefined || step === null)) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (!stepPassesForRearm(step)) {
|
|
109
124
|
return {
|
|
110
125
|
eligible: false,
|
|
111
126
|
reason: `quick step '${stepName}' is missing or failed (full cold session:start required)`,
|
|
@@ -340,6 +355,34 @@ function resolveSessionScmReadiness(options, allowOptionalNetwork) {
|
|
|
340
355
|
};
|
|
341
356
|
}
|
|
342
357
|
}
|
|
358
|
+
function resolveHostContentSurface(projectRoot, options, runtimeMode) {
|
|
359
|
+
try {
|
|
360
|
+
const seams = options.hostContentSurfaceSeams ?? {};
|
|
361
|
+
return maybeFormatHostContentSurfaceLines(projectRoot, {
|
|
362
|
+
...seams,
|
|
363
|
+
environ: seams.environ ?? options.env,
|
|
364
|
+
runtimeMode: seams.runtimeMode ?? runtimeMode ?? null,
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
catch {
|
|
368
|
+
// best-effort — session start must not abort on host-surface probe failures (#3162)
|
|
369
|
+
return {
|
|
370
|
+
report: {
|
|
371
|
+
contentClass: "unknown",
|
|
372
|
+
classSource: "probe-error",
|
|
373
|
+
signals: [],
|
|
374
|
+
managedSection: {
|
|
375
|
+
state: "unknown",
|
|
376
|
+
embeddedSha: null,
|
|
377
|
+
bodyHash: null,
|
|
378
|
+
path: `${projectRoot}/AGENTS.md`,
|
|
379
|
+
},
|
|
380
|
+
runtimeMode: null,
|
|
381
|
+
},
|
|
382
|
+
lines: [],
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
}
|
|
343
386
|
function runReadOnlySessionStart(projectRoot, options, instant, environment) {
|
|
344
387
|
const lines = [];
|
|
345
388
|
const resolveUserMd = options.resolveUserMd ?? ((root) => resolveUserMdPath({ projectRoot: root }));
|
|
@@ -351,10 +394,13 @@ function runReadOnlySessionStart(projectRoot, options, instant, environment) {
|
|
|
351
394
|
: safeDiagnostic;
|
|
352
395
|
// #2275: report SCM availability even on read-only alignment (shallow; no network).
|
|
353
396
|
const scm = resolveSessionScmReadiness(options, false);
|
|
397
|
+
// #3162: host content-surface class + managed drift (advisory).
|
|
398
|
+
const hostSurface = resolveHostContentSurface(projectRoot, options, scm.runtimeMode);
|
|
354
399
|
lines.push(READ_ONLY_ALIGNMENT_MESSAGE);
|
|
355
400
|
lines.push(userMdLine);
|
|
356
401
|
lines.push(formatEnvironmentContext(environment));
|
|
357
402
|
lines.push(...formatScmReadinessLines(scm));
|
|
403
|
+
lines.push(...hostSurface.lines);
|
|
358
404
|
const resultPayload = {
|
|
359
405
|
ready: true,
|
|
360
406
|
exit_code: 0,
|
|
@@ -376,6 +422,7 @@ function runReadOnlySessionStart(projectRoot, options, instant, environment) {
|
|
|
376
422
|
},
|
|
377
423
|
environment: environmentContextToDict(environment),
|
|
378
424
|
scm: scmReadinessToDict(scm),
|
|
425
|
+
host_content_surface: hostContentSurfaceToDict(hostSurface.report),
|
|
379
426
|
message: READ_ONLY_RESULT_MESSAGE,
|
|
380
427
|
};
|
|
381
428
|
return { code: 0, payload: resultPayload, lines };
|
|
@@ -418,11 +465,14 @@ function runSessionRearm(projectRoot, options, instant, environment) {
|
|
|
418
465
|
const alignmentMessage = `${READ_ONLY_ALIGNMENT_MESSAGE} ${userMdLine}`;
|
|
419
466
|
// #2275: re-arm still reports SCM state (shallow; no network).
|
|
420
467
|
const scm = resolveSessionScmReadiness(options, false);
|
|
468
|
+
// #3162: host content-surface class + managed drift (advisory).
|
|
469
|
+
const hostSurface = resolveHostContentSurface(projectRoot, options, scm.runtimeMode);
|
|
421
470
|
const lines = [
|
|
422
471
|
READ_ONLY_ALIGNMENT_MESSAGE,
|
|
423
472
|
userMdLine,
|
|
424
473
|
formatEnvironmentContext(environment),
|
|
425
474
|
...formatScmReadinessLines(scm),
|
|
475
|
+
...hostSurface.lines,
|
|
426
476
|
REARM_SKIPPED_FAT_PATH_MESSAGE,
|
|
427
477
|
];
|
|
428
478
|
// Light branch-policy disclosure (local only) so re-arm still surfaces policy state.
|
|
@@ -446,6 +496,41 @@ function runSessionRearm(projectRoot, options, instant, environment) {
|
|
|
446
496
|
}
|
|
447
497
|
const priorQuick = eligibility.state.quickSteps;
|
|
448
498
|
const priorTriage = priorQuick.triage_welcome ?? ritualStep({ ok: true, ts: instant });
|
|
499
|
+
// Greptile P1: legacy ritual-state without verify_tools must re-run tools —
|
|
500
|
+
// never invent ok:true. When prior exists, preserve without re-run (#2992).
|
|
501
|
+
let toolsStep;
|
|
502
|
+
if (priorQuick.verify_tools && typeof priorQuick.verify_tools === "object") {
|
|
503
|
+
const priorTools = priorQuick.verify_tools;
|
|
504
|
+
toolsStep = {
|
|
505
|
+
...priorTools,
|
|
506
|
+
ts: ritualStep({ ok: priorTools.ok === true, ts: instant }).ts,
|
|
507
|
+
message: typeof priorTools.message === "string"
|
|
508
|
+
? priorTools.message
|
|
509
|
+
: "verify:tools preserved on re-arm",
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
else {
|
|
513
|
+
const verifyToolsFn = options.verifyTools ??
|
|
514
|
+
((output) => {
|
|
515
|
+
const toolLines = [];
|
|
516
|
+
const result = verifyRequiredTools({ outputFn: (line) => toolLines.push(line) });
|
|
517
|
+
for (const line of toolLines) {
|
|
518
|
+
output(line);
|
|
519
|
+
}
|
|
520
|
+
return { exitCode: result.exitCode };
|
|
521
|
+
});
|
|
522
|
+
const toolsOutcome = verifyToolsFn((line) => lines.push(line));
|
|
523
|
+
const toolsOk = toolsOutcome.exitCode === 0;
|
|
524
|
+
toolsStep = ritualStep({
|
|
525
|
+
ok: toolsOk,
|
|
526
|
+
ts: instant,
|
|
527
|
+
message: toolsOk
|
|
528
|
+
? "verify:tools re-run on re-arm (legacy ritual lacked tools step)"
|
|
529
|
+
: `verify:tools failed on re-arm (exit ${toolsOutcome.exitCode})`,
|
|
530
|
+
exitCode: toolsOutcome.exitCode,
|
|
531
|
+
durationMs: 0,
|
|
532
|
+
});
|
|
533
|
+
}
|
|
449
534
|
const policyOk = policyResult.error === null || policyResult.source === "default-fail-closed";
|
|
450
535
|
const quickSteps = {
|
|
451
536
|
alignment: ritualStep({
|
|
@@ -461,6 +546,7 @@ function runSessionRearm(projectRoot, options, instant, environment) {
|
|
|
461
546
|
exitCode: policyOk ? 0 : 2,
|
|
462
547
|
durationMs: 0,
|
|
463
548
|
}),
|
|
549
|
+
verify_tools: toolsStep,
|
|
464
550
|
// Preserve prior triage outcome; do not re-run welcome / self-heal on re-arm.
|
|
465
551
|
triage_welcome: {
|
|
466
552
|
...priorTriage,
|
|
@@ -578,6 +664,7 @@ function runSessionRearm(projectRoot, options, instant, environment) {
|
|
|
578
664
|
},
|
|
579
665
|
environment: environmentContextToDict(environment),
|
|
580
666
|
scm: scmReadinessToDict(scm),
|
|
667
|
+
host_content_surface: hostContentSurfaceToDict(hostSurface.report),
|
|
581
668
|
message: code === 0 ? "session ritual re-armed" : "session ritual re-arm failed",
|
|
582
669
|
},
|
|
583
670
|
lines,
|
|
@@ -659,12 +746,26 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
659
746
|
const overallStarted = performance.now();
|
|
660
747
|
const stepTimings = [];
|
|
661
748
|
const allowOptionalNetwork = resolveSessionStartOptionalNetwork(options);
|
|
749
|
+
// #3214 / #3156: select ritual (ceremony) depth before building deferral maps.
|
|
750
|
+
// Rapid/minimal auto-defer informational cold steps only; mutation readiness
|
|
751
|
+
// (doctor, cache_fresh, agent_hooks, verify_tools) stays constant.
|
|
752
|
+
// Two-stage + provisional intake (#3214 design note / #1581 ordering): fill
|
|
753
|
+
// missing size/tier/shape from env/verb/files/deposit BEFORE resolve — never
|
|
754
|
+
// block on plan-item effort (post-planning only). Cold incomplete → rapid.
|
|
755
|
+
const { inputs: resolvedDialInputs, provisional: provisionalDial } = resolveSessionCeremonyDialInputs(projectRoot, options.ceremonyDialInputs, {
|
|
756
|
+
...options.ceremonyDialHints,
|
|
757
|
+
env: options.env,
|
|
758
|
+
});
|
|
759
|
+
const ceremonyDialSelection = options.ceremonyDial ?? resolveCeremonyDial(projectRoot, { inputs: resolvedDialInputs });
|
|
760
|
+
const effectiveDeferrals = mergeCeremonyDialDeferrals(deferrals, ceremonyDialSelection);
|
|
761
|
+
const skipFatPath = ceremonyDialSelection.profile.skipFatPath;
|
|
662
762
|
const { head: gitHeadValue, error: gitError } = gitHead(projectRoot, runGit);
|
|
663
763
|
if (gitHeadValue === null) {
|
|
664
764
|
const payload = {
|
|
665
765
|
ready: false,
|
|
666
766
|
exit_code: 2,
|
|
667
767
|
ceremony_tier: COLD_CEREMONY_TIER,
|
|
768
|
+
ceremony_dial: ceremonyDialToDict(ceremonyDialSelection),
|
|
668
769
|
environment: environmentContextToDict(environment),
|
|
669
770
|
message: gitError ?? "could not resolve git HEAD",
|
|
670
771
|
};
|
|
@@ -682,9 +783,13 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
682
783
|
lines: [formatEnvironmentContext(environment), payload.message],
|
|
683
784
|
};
|
|
684
785
|
}
|
|
685
|
-
const quickSteps = recordDeferredSteps(QUICK_STEPS,
|
|
686
|
-
const gatedSteps = recordDeferredSteps(GATED_STEPS,
|
|
786
|
+
const quickSteps = recordDeferredSteps(QUICK_STEPS, effectiveDeferrals, instant);
|
|
787
|
+
const gatedSteps = recordDeferredSteps(GATED_STEPS, effectiveDeferrals, instant);
|
|
687
788
|
const lines = [];
|
|
789
|
+
lines.push(formatCeremonyDialStatusLine(ceremonyDialSelection));
|
|
790
|
+
if (provisionalDial.reasons.length > 0 && options.ceremonyDial === undefined) {
|
|
791
|
+
lines.push(`[deft ceremony-dial] provisional: ${provisionalDial.reasons.join("; ")}`);
|
|
792
|
+
}
|
|
688
793
|
// Resolve USER.md via the shared first-hit-wins resolver so the alignment
|
|
689
794
|
// step finds preferences automatically in mismatched / headless sandboxes
|
|
690
795
|
// with zero manual DEFT_USER_PATH (#2271 / #2124). Never throws: an absent
|
|
@@ -724,6 +829,14 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
724
829
|
const scm = resolveSessionScmReadiness(options, allowOptionalNetwork);
|
|
725
830
|
lines.push(...formatScmReadinessLines(scm));
|
|
726
831
|
stepTimings.push({ name: "scm_readiness", duration_ms: elapsedMs(scmStepStarted) });
|
|
832
|
+
// #3162: host content-surface class + managed AGENTS drift (advisory; never blocks).
|
|
833
|
+
const hostSurfaceStepStarted = performance.now();
|
|
834
|
+
const hostSurface = resolveHostContentSurface(projectRoot, options, scm.runtimeMode);
|
|
835
|
+
lines.push(...hostSurface.lines);
|
|
836
|
+
stepTimings.push({
|
|
837
|
+
name: "host_content_surface",
|
|
838
|
+
duration_ms: elapsedMs(hostSurfaceStepStarted),
|
|
839
|
+
});
|
|
727
840
|
if (!quickSteps.branch_policy) {
|
|
728
841
|
const stepStarted = performance.now();
|
|
729
842
|
const result = resolvePolicy(projectRoot);
|
|
@@ -753,6 +866,11 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
753
866
|
else {
|
|
754
867
|
stepTimings.push({ name: "branch_policy", duration_ms: 0, skipped: true });
|
|
755
868
|
}
|
|
869
|
+
// #3214 / #3156: verify_tools is mutation readiness — always run, even under
|
|
870
|
+
// rapid/minimal. Dial skipFatPath only lightens *ceremony* (triage welcome,
|
|
871
|
+
// optional network, staleness tickler), never readiness gates.
|
|
872
|
+
// Persist outcome into quick_steps so ritual-state records failure (not only
|
|
873
|
+
// process exit) — re-arm / later readers must not see a green cold start.
|
|
756
874
|
{
|
|
757
875
|
const stepStarted = performance.now();
|
|
758
876
|
const verifyToolsFn = options.verifyTools ??
|
|
@@ -764,10 +882,28 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
764
882
|
}
|
|
765
883
|
return { exitCode: result.exitCode };
|
|
766
884
|
});
|
|
767
|
-
verifyToolsFn((line) => lines.push(line));
|
|
768
|
-
|
|
885
|
+
const toolsOutcome = verifyToolsFn((line) => lines.push(line));
|
|
886
|
+
const durationMs = elapsedMs(stepStarted);
|
|
887
|
+
const toolsOk = toolsOutcome.exitCode === 0;
|
|
888
|
+
const toolsMessage = toolsOk
|
|
889
|
+
? "verify:tools ok"
|
|
890
|
+
: `verify:tools failed (exit ${toolsOutcome.exitCode}); session not ready.`;
|
|
891
|
+
if (!toolsOk) {
|
|
892
|
+
lines.push(`[deft session] ${toolsMessage}`);
|
|
893
|
+
}
|
|
894
|
+
// Record on quick_steps (durable ritual-state) in addition to step timings.
|
|
895
|
+
quickSteps.verify_tools = ritualStep({
|
|
896
|
+
ok: toolsOk,
|
|
897
|
+
ts: instant,
|
|
898
|
+
message: toolsMessage,
|
|
899
|
+
exitCode: toolsOutcome.exitCode,
|
|
900
|
+
command: ["verify:tools"],
|
|
901
|
+
durationMs,
|
|
902
|
+
});
|
|
903
|
+
stepTimings.push({ name: "verify_tools", duration_ms: durationMs });
|
|
769
904
|
}
|
|
770
|
-
|
|
905
|
+
// #3214: rapid/minimal skip informational cold-path ceremony only.
|
|
906
|
+
if (!quickSteps.triage_welcome && !skipFatPath) {
|
|
771
907
|
const stepStarted = performance.now();
|
|
772
908
|
const captured = [];
|
|
773
909
|
const triageCommand = ["triage_welcome.run_default_mode", "--project-root", projectRoot];
|
|
@@ -825,7 +961,8 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
825
961
|
stepTimings.push({ name: "triage_welcome", duration_ms: 0, skipped: true });
|
|
826
962
|
}
|
|
827
963
|
// #2991: npm release probe is optional network — off by default so ritual write is not blocked.
|
|
828
|
-
|
|
964
|
+
// #3214: also skipped under rapid/minimal dial (lifecycleWrites light/minimal).
|
|
965
|
+
if (allowOptionalNetwork && !skipFatPath) {
|
|
829
966
|
const stepStarted = performance.now();
|
|
830
967
|
try {
|
|
831
968
|
const releaseAvailability = (options.probeReleaseAvailability ?? probeSessionReleaseAvailability)(projectRoot, { now: instant });
|
|
@@ -838,16 +975,20 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
838
975
|
}
|
|
839
976
|
else {
|
|
840
977
|
stepTimings.push({ name: "release_probe", duration_ms: 0, skipped: true });
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
const tickler = (options.runStalenessTickler ?? maybeRunStalenessTickler)(projectRoot, {
|
|
845
|
-
now: instant,
|
|
846
|
-
});
|
|
847
|
-
lines.push(...tickler.lines);
|
|
978
|
+
if (!skipFatPath) {
|
|
979
|
+
lines.push(OPTIONAL_NETWORK_SKIPPED_MESSAGE);
|
|
980
|
+
}
|
|
848
981
|
}
|
|
849
|
-
|
|
850
|
-
|
|
982
|
+
if (!skipFatPath) {
|
|
983
|
+
try {
|
|
984
|
+
const tickler = (options.runStalenessTickler ?? maybeRunStalenessTickler)(projectRoot, {
|
|
985
|
+
now: instant,
|
|
986
|
+
});
|
|
987
|
+
lines.push(...tickler.lines);
|
|
988
|
+
}
|
|
989
|
+
catch {
|
|
990
|
+
// Staleness tickler is best-effort and must never block session start.
|
|
991
|
+
}
|
|
851
992
|
}
|
|
852
993
|
if (!runningInsideDeftRepo(projectRoot) && shouldEmitMigrateNudge(projectRoot)) {
|
|
853
994
|
lines.push(MIGRATE_COMPLETION_NUDGE);
|
|
@@ -886,14 +1027,27 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
886
1027
|
}
|
|
887
1028
|
const writeStarted = performance.now();
|
|
888
1029
|
const coldSessionId = (options.newSessionId ?? randomUUID)();
|
|
889
|
-
const
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
1030
|
+
const dialDict = {
|
|
1031
|
+
...ceremonyDialToDict(ceremonyDialSelection),
|
|
1032
|
+
provisional: {
|
|
1033
|
+
taskSize: provisionalDial.taskSize,
|
|
1034
|
+
modelTier: provisionalDial.modelTier,
|
|
1035
|
+
projectShape: provisionalDial.projectShape,
|
|
1036
|
+
reasons: [...provisionalDial.reasons],
|
|
1037
|
+
},
|
|
1038
|
+
};
|
|
1039
|
+
const payload = {
|
|
1040
|
+
...newRitualStatePayload({
|
|
1041
|
+
sessionId: coldSessionId,
|
|
1042
|
+
gitHead: gitHeadValue,
|
|
1043
|
+
worktreePath: worktreePath(projectRoot, runGit),
|
|
1044
|
+
startedAt: instant,
|
|
1045
|
+
quickSteps,
|
|
1046
|
+
gatedSteps,
|
|
1047
|
+
}),
|
|
1048
|
+
// #3214: record dial choice on ritual-state for audit / later re-arm context.
|
|
1049
|
+
ceremony_dial: dialDict,
|
|
1050
|
+
};
|
|
897
1051
|
let statePath;
|
|
898
1052
|
try {
|
|
899
1053
|
statePath = writeRitualState(projectRoot, payload);
|
|
@@ -915,6 +1069,7 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
915
1069
|
const failed = Object.entries(quickSteps)
|
|
916
1070
|
.filter(([, step]) => !step.ok && !step.deferred_reason)
|
|
917
1071
|
.map(([name]) => name);
|
|
1072
|
+
// verify_tools is recorded on quick_steps; failure makes ready=false.
|
|
918
1073
|
const code = failed.length > 0 ? 1 : 0;
|
|
919
1074
|
const totalMs = elapsedMs(overallStarted);
|
|
920
1075
|
// #3117: bind live deposit generation when payload surfaces load (cold path).
|
|
@@ -947,13 +1102,14 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
947
1102
|
ready: code === 0,
|
|
948
1103
|
exit_code: code,
|
|
949
1104
|
ceremony_tier: COLD_CEREMONY_TIER,
|
|
1105
|
+
ceremony_dial: dialDict,
|
|
950
1106
|
state_path: statePath,
|
|
951
1107
|
...(freshnessBind ? { freshness: freshnessBind } : {}),
|
|
952
1108
|
quick_steps: quickSteps,
|
|
953
1109
|
gated_steps: gatedSteps,
|
|
954
1110
|
steps: stepTimings,
|
|
955
1111
|
duration_ms: totalMs,
|
|
956
|
-
optional_network: allowOptionalNetwork,
|
|
1112
|
+
optional_network: allowOptionalNetwork && !skipFatPath,
|
|
957
1113
|
user_md: {
|
|
958
1114
|
path: userMd.path,
|
|
959
1115
|
rung: userMd.rung,
|
|
@@ -962,6 +1118,7 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
962
1118
|
},
|
|
963
1119
|
environment: environmentContextToDict(environment),
|
|
964
1120
|
scm: scmReadinessToDict(scm),
|
|
1121
|
+
host_content_surface: hostContentSurfaceToDict(hostSurface.report),
|
|
965
1122
|
message: code === 0 ? "session ritual recorded" : "session ritual failed",
|
|
966
1123
|
};
|
|
967
1124
|
// #2994: local process-cost event (best-effort; never blocks ceremony).
|
package/dist/swarm/index.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ export * from "./finalize-cohort.js";
|
|
|
5
5
|
export { finalizeCohortMain, parseFinalizeCohortArgv } from "./finalize-cohort-cli.js";
|
|
6
6
|
export * from "./launch.js";
|
|
7
7
|
export { launchMain, parseLaunchArgv } from "./launch-cli.js";
|
|
8
|
+
export * from "./pre-dispatch.js";
|
|
9
|
+
export { parsePreDispatchArgv, preDispatchMain } from "./pre-dispatch-cli.js";
|
|
8
10
|
export * from "./readiness.js";
|
|
9
11
|
export { readinessMain } from "./readiness-cli.js";
|
|
10
12
|
export * from "./routing.js";
|
package/dist/swarm/index.js
CHANGED
|
@@ -5,6 +5,8 @@ export * from "./finalize-cohort.js";
|
|
|
5
5
|
export { finalizeCohortMain, parseFinalizeCohortArgv } from "./finalize-cohort-cli.js";
|
|
6
6
|
export * from "./launch.js";
|
|
7
7
|
export { launchMain, parseLaunchArgv } from "./launch-cli.js";
|
|
8
|
+
export * from "./pre-dispatch.js";
|
|
9
|
+
export { parsePreDispatchArgv, preDispatchMain } from "./pre-dispatch-cli.js";
|
|
8
10
|
export * from "./readiness.js";
|
|
9
11
|
export { readinessMain } from "./readiness-cli.js";
|
|
10
12
|
export * from "./routing.js";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { type CompleteStatus, type PreDispatchAction } from "./pre-dispatch.js";
|
|
3
|
+
export interface ParsedPreDispatchArgv {
|
|
4
|
+
projectRoot: string;
|
|
5
|
+
scopeId: string | null;
|
|
6
|
+
targetId: string | null;
|
|
7
|
+
workflowId: string | null;
|
|
8
|
+
action: PreDispatchAction | null;
|
|
9
|
+
sourceRevision: string | null;
|
|
10
|
+
attemptId: string | null;
|
|
11
|
+
status: CompleteStatus | null;
|
|
12
|
+
workerId: string | null;
|
|
13
|
+
externalRunId: string | null;
|
|
14
|
+
json: boolean;
|
|
15
|
+
help: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare function parsePreDispatchArgv(argv: string[]): ParsedPreDispatchArgv;
|
|
18
|
+
export declare function preDispatchMain(argv?: string[]): number;
|
|
19
|
+
//# sourceMappingURL=pre-dispatch-cli.d.ts.map
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI for task swarm:pre-dispatch (#3228).
|
|
4
|
+
*
|
|
5
|
+
* Exit codes:
|
|
6
|
+
* 0 — allow (begin succeeded / complete-cancel succeeded)
|
|
7
|
+
* 1 — gate deny (e.g. DENY_DUPLICATE_ACTIVE) or complete with no active attempt
|
|
8
|
+
* 2 — config / usage error
|
|
9
|
+
*/
|
|
10
|
+
import { resolve } from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
import { EXIT_CONFIG_ERROR } from "./constants.js";
|
|
13
|
+
import { COMPLETE_STATUSES, formatPreDispatchReport, PRE_DISPATCH_ACTIONS, swarmPreDispatch, } from "./pre-dispatch.js";
|
|
14
|
+
export function parsePreDispatchArgv(argv) {
|
|
15
|
+
const out = {
|
|
16
|
+
projectRoot: ".",
|
|
17
|
+
scopeId: null,
|
|
18
|
+
targetId: null,
|
|
19
|
+
workflowId: null,
|
|
20
|
+
action: null,
|
|
21
|
+
sourceRevision: null,
|
|
22
|
+
attemptId: null,
|
|
23
|
+
status: null,
|
|
24
|
+
workerId: null,
|
|
25
|
+
externalRunId: null,
|
|
26
|
+
json: false,
|
|
27
|
+
help: false,
|
|
28
|
+
};
|
|
29
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
30
|
+
const arg = argv[i];
|
|
31
|
+
if (arg === "--help" || arg === "-h") {
|
|
32
|
+
out.help = true;
|
|
33
|
+
}
|
|
34
|
+
else if (arg === "--json") {
|
|
35
|
+
out.json = true;
|
|
36
|
+
}
|
|
37
|
+
else if (arg === "--project-root" && argv[i + 1] !== undefined) {
|
|
38
|
+
out.projectRoot = argv[i + 1] ?? ".";
|
|
39
|
+
i += 1;
|
|
40
|
+
}
|
|
41
|
+
else if ((arg === "--scope-id" || arg === "--scope") && argv[i + 1] !== undefined) {
|
|
42
|
+
out.scopeId = argv[i + 1] ?? null;
|
|
43
|
+
i += 1;
|
|
44
|
+
}
|
|
45
|
+
else if ((arg === "--target-id" || arg === "--target") && argv[i + 1] !== undefined) {
|
|
46
|
+
out.targetId = argv[i + 1] ?? null;
|
|
47
|
+
i += 1;
|
|
48
|
+
}
|
|
49
|
+
else if ((arg === "--workflow-id" || arg === "--workflow") && argv[i + 1] !== undefined) {
|
|
50
|
+
out.workflowId = argv[i + 1] ?? null;
|
|
51
|
+
i += 1;
|
|
52
|
+
}
|
|
53
|
+
else if (arg === "--action" && argv[i + 1] !== undefined) {
|
|
54
|
+
out.action = (argv[i + 1] ?? null);
|
|
55
|
+
i += 1;
|
|
56
|
+
}
|
|
57
|
+
else if ((arg === "--source-revision" || arg === "--revision") && argv[i + 1] !== undefined) {
|
|
58
|
+
out.sourceRevision = argv[i + 1] ?? null;
|
|
59
|
+
i += 1;
|
|
60
|
+
}
|
|
61
|
+
else if (arg === "--attempt-id" && argv[i + 1] !== undefined) {
|
|
62
|
+
out.attemptId = argv[i + 1] ?? null;
|
|
63
|
+
i += 1;
|
|
64
|
+
}
|
|
65
|
+
else if (arg === "--status" && argv[i + 1] !== undefined) {
|
|
66
|
+
out.status = (argv[i + 1] ?? null);
|
|
67
|
+
i += 1;
|
|
68
|
+
}
|
|
69
|
+
else if (arg === "--worker-id" && argv[i + 1] !== undefined) {
|
|
70
|
+
out.workerId = argv[i + 1] ?? null;
|
|
71
|
+
i += 1;
|
|
72
|
+
}
|
|
73
|
+
else if (arg === "--external-run-id" && argv[i + 1] !== undefined) {
|
|
74
|
+
out.externalRunId = argv[i + 1] ?? null;
|
|
75
|
+
i += 1;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
const USAGE = `Usage: task swarm:pre-dispatch -- --scope-id <id> --target-id <worktree|branch> [options]
|
|
81
|
+
|
|
82
|
+
Pre-dispatch gate for implement leaves (#3228 / #3143 DENY_DUPLICATE_ACTIVE).
|
|
83
|
+
Before spawning a peer implement leaf: run with default --action begin.
|
|
84
|
+
exit 0 allow (attempt begun)
|
|
85
|
+
exit 1 active deny / gate block (do not spawn)
|
|
86
|
+
exit 2 config / usage error
|
|
87
|
+
|
|
88
|
+
Takeover: --action cancel, then pre-dispatch begin again (not concurrent dual active).
|
|
89
|
+
Terminal: --action complete [--status succeeded|failed|cancelled|blocked]
|
|
90
|
+
|
|
91
|
+
Options:
|
|
92
|
+
--scope-id, --scope <id> Unit scope (story/issue or xBRIEF plan id)
|
|
93
|
+
--target-id, --target <id> Unit target (worktree path or branch)
|
|
94
|
+
--workflow-id, --workflow <id> Default: drive-to:merge-ready
|
|
95
|
+
--action begin|complete|cancel Default: begin
|
|
96
|
+
--source-revision <sha> Default: git rev-parse HEAD
|
|
97
|
+
--attempt-id <id> For complete/cancel
|
|
98
|
+
--status <status> For complete: ${COMPLETE_STATUSES.join("|")}
|
|
99
|
+
--worker-id <id> Optional worker stamp on begin
|
|
100
|
+
--external-run-id <id> Optional external run id
|
|
101
|
+
--project-root <path> Project root (ledger under .deft/delivery-attempts/)
|
|
102
|
+
--json Machine-readable result on stdout
|
|
103
|
+
-h, --help Show this help
|
|
104
|
+
`;
|
|
105
|
+
export function preDispatchMain(argv = process.argv.slice(2)) {
|
|
106
|
+
const parsed = parsePreDispatchArgv(argv);
|
|
107
|
+
if (parsed.help) {
|
|
108
|
+
process.stdout.write(USAGE);
|
|
109
|
+
return EXIT_CONFIG_ERROR;
|
|
110
|
+
}
|
|
111
|
+
if (parsed.action !== null &&
|
|
112
|
+
!PRE_DISPATCH_ACTIONS.includes(parsed.action)) {
|
|
113
|
+
process.stderr.write(`Error: --action must be one of: ${PRE_DISPATCH_ACTIONS.join(", ")}.\n`);
|
|
114
|
+
return EXIT_CONFIG_ERROR;
|
|
115
|
+
}
|
|
116
|
+
if (parsed.status !== null && !COMPLETE_STATUSES.includes(parsed.status)) {
|
|
117
|
+
process.stderr.write(`Error: --status must be one of: ${COMPLETE_STATUSES.join(", ")}.\n`);
|
|
118
|
+
return EXIT_CONFIG_ERROR;
|
|
119
|
+
}
|
|
120
|
+
const result = swarmPreDispatch({
|
|
121
|
+
projectRoot: resolve(parsed.projectRoot),
|
|
122
|
+
scopeId: parsed.scopeId ?? "",
|
|
123
|
+
targetId: parsed.targetId ?? "",
|
|
124
|
+
workflowId: parsed.workflowId ?? undefined,
|
|
125
|
+
action: parsed.action ?? undefined,
|
|
126
|
+
sourceRevision: parsed.sourceRevision ?? undefined,
|
|
127
|
+
attemptId: parsed.attemptId ?? undefined,
|
|
128
|
+
status: parsed.status ?? undefined,
|
|
129
|
+
workerId: parsed.workerId,
|
|
130
|
+
externalRunId: parsed.externalRunId,
|
|
131
|
+
});
|
|
132
|
+
if (parsed.json) {
|
|
133
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
process.stdout.write(`${formatPreDispatchReport(result)}\n`);
|
|
137
|
+
}
|
|
138
|
+
return result.exitCode;
|
|
139
|
+
}
|
|
140
|
+
if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
141
|
+
process.exit(preDispatchMain());
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=pre-dispatch-cli.js.map
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Swarm implement-leaf pre-dispatch gate (#3228).
|
|
3
|
+
*
|
|
4
|
+
* Wires #3143 delivery-attempt DENY_DUPLICATE_ACTIVE onto the swarm re-dispatch
|
|
5
|
+
* path: before starting a peer implement leaf on a unit, if a non-terminal
|
|
6
|
+
* attempt already exists → exit non-zero and do not spawn.
|
|
7
|
+
*
|
|
8
|
+
* Takeover is two steps: cancel (complete status=cancelled) the prior attempt,
|
|
9
|
+
* then pre-dispatch begin again — never concurrent dual active.
|
|
10
|
+
*/
|
|
11
|
+
import { type AttemptTrigger, type DeliveryAttemptRecord, type PreDispatchDecision } from "../delivery-attempt/index.js";
|
|
12
|
+
import { EXIT_CONFIG_ERROR, EXIT_GATE_FAILED, EXIT_OK } from "./constants.js";
|
|
13
|
+
/** Default workflow id for drive-to:merge-ready implement leaves. */
|
|
14
|
+
export declare const IMPLEMENT_LEAF_WORKFLOW_ID = "drive-to:merge-ready";
|
|
15
|
+
export declare const PRE_DISPATCH_ACTIONS: readonly ["begin", "complete", "cancel"];
|
|
16
|
+
export type PreDispatchAction = (typeof PRE_DISPATCH_ACTIONS)[number];
|
|
17
|
+
export declare const COMPLETE_STATUSES: readonly ["succeeded", "failed", "cancelled", "blocked"];
|
|
18
|
+
export type CompleteStatus = (typeof COMPLETE_STATUSES)[number];
|
|
19
|
+
export interface SwarmPreDispatchInput {
|
|
20
|
+
readonly projectRoot: string;
|
|
21
|
+
readonly scopeId: string;
|
|
22
|
+
readonly targetId: string;
|
|
23
|
+
readonly workflowId?: string;
|
|
24
|
+
readonly action?: PreDispatchAction;
|
|
25
|
+
readonly sourceRevision?: string;
|
|
26
|
+
readonly attemptId?: string;
|
|
27
|
+
readonly status?: CompleteStatus;
|
|
28
|
+
readonly workerId?: string | null;
|
|
29
|
+
readonly externalRunId?: string | null;
|
|
30
|
+
readonly trigger?: AttemptTrigger;
|
|
31
|
+
readonly now?: string;
|
|
32
|
+
}
|
|
33
|
+
export interface SwarmPreDispatchResult {
|
|
34
|
+
readonly exitCode: typeof EXIT_OK | typeof EXIT_GATE_FAILED | typeof EXIT_CONFIG_ERROR;
|
|
35
|
+
readonly decision: PreDispatchDecision | null;
|
|
36
|
+
readonly reason: string;
|
|
37
|
+
readonly action: PreDispatchAction;
|
|
38
|
+
readonly scopeId: string;
|
|
39
|
+
readonly targetId: string;
|
|
40
|
+
readonly workflowId: string;
|
|
41
|
+
readonly attempt: DeliveryAttemptRecord | null;
|
|
42
|
+
readonly activeAttemptIds: readonly string[];
|
|
43
|
+
}
|
|
44
|
+
export declare function resolveSourceRevision(projectRoot: string, explicit?: string): string;
|
|
45
|
+
/**
|
|
46
|
+
* True when targetId should be treated as a filesystem path (worktree), not an
|
|
47
|
+
* opaque branch/ref id. Branch names with `/` (e.g. `feat/foo`) stay opaque.
|
|
48
|
+
*/
|
|
49
|
+
export declare function looksLikeFilesystemTarget(targetId: string): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Canonical unit target for ledger keys so relative/absolute/separator/case
|
|
52
|
+
* variants of the same worktree do not split gate state (#3228 Greptile P1).
|
|
53
|
+
*
|
|
54
|
+
* Always resolve under projectRoot to a stable absolute lexical key (even
|
|
55
|
+
* before the path exists). Do **not** realpath: following a symlink that is
|
|
56
|
+
* created between dispatches would change the key and split ledgers.
|
|
57
|
+
* Case-fold prevents case-insensitive FS splits. Existence never changes
|
|
58
|
+
* the ledger key.
|
|
59
|
+
*/
|
|
60
|
+
export declare function normalizeTargetId(projectRoot: string, targetId: string): string;
|
|
61
|
+
/**
|
|
62
|
+
* Best-effort physical identity for alias peer checks. Ledger keys stay lexical
|
|
63
|
+
* (stable); when a path exists, realpath groups symlink aliases that point at
|
|
64
|
+
* the same worktree so sequential begins still DENY.
|
|
65
|
+
*/
|
|
66
|
+
export declare function physicalTargetKey(projectRoot: string, targetId: string): string;
|
|
67
|
+
/** True when another unit (same scope+workflow, different target key) is active on same physical path. */
|
|
68
|
+
export declare function hasActiveAliasPeer(projectRoot: string, scopeId: string, targetId: string, workflowId: string): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Parse beginAttemptOnDisk-style error messages without a ReDoS-prone regex
|
|
71
|
+
* (CodeQL: polynomial regex on uncontrolled data).
|
|
72
|
+
*/
|
|
73
|
+
export declare function parseDecisionFromError(message: string): {
|
|
74
|
+
decision: PreDispatchDecision | null;
|
|
75
|
+
reason: string;
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Swarm pre-dispatch gate for implement leaves.
|
|
79
|
+
*
|
|
80
|
+
* - begin (default): evaluate #3143 gate; on allow, beginAttempt; exit 0 / 1 / 2
|
|
81
|
+
* - complete: terminal success/fail/blocked
|
|
82
|
+
* - cancel: terminal cancel (takeover step 1)
|
|
83
|
+
*/
|
|
84
|
+
export declare function swarmPreDispatch(input: SwarmPreDispatchInput): SwarmPreDispatchResult;
|
|
85
|
+
/** Human-readable one-line report for CLI stdout. */
|
|
86
|
+
export declare function formatPreDispatchReport(result: SwarmPreDispatchResult): string;
|
|
87
|
+
//# sourceMappingURL=pre-dispatch.d.ts.map
|