@deftai/directive-core 0.94.0 → 0.95.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 +485 -0
- package/dist/doctor/main.js +41 -9
- package/dist/doctor/types.d.ts +2 -2
- package/dist/freshness/bind.d.ts +67 -0
- package/dist/freshness/bind.js +201 -0
- package/dist/freshness/cli.d.ts +30 -0
- package/dist/freshness/cli.js +180 -0
- package/dist/freshness/compare.d.ts +14 -0
- package/dist/freshness/compare.js +134 -0
- package/dist/freshness/generation.d.ts +44 -0
- package/dist/freshness/generation.js +172 -0
- package/dist/freshness/index.d.ts +13 -0
- package/dist/freshness/index.js +13 -0
- package/dist/freshness/report.d.ts +43 -0
- package/dist/freshness/report.js +139 -0
- package/dist/freshness/types.d.ts +70 -0
- package/dist/freshness/types.js +30 -0
- package/dist/handoff-evidence/index.d.ts +8 -0
- package/dist/handoff-evidence/index.js +7 -0
- package/dist/handoff-evidence/validate.d.ts +105 -0
- package/dist/handoff-evidence/validate.js +423 -0
- package/dist/hooks/dispatcher.d.ts +3 -0
- package/dist/hooks/dispatcher.js +12 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/init-deposit/agent-hooks.d.ts +1 -1
- package/dist/init-deposit/agent-hooks.js +5 -2
- package/dist/init-deposit/init-deposit.d.ts +7 -1
- package/dist/init-deposit/init-deposit.js +24 -4
- package/dist/init-deposit/refresh.d.ts +9 -1
- package/dist/init-deposit/refresh.js +48 -5
- package/dist/policy/index.d.ts +3 -0
- package/dist/policy/index.js +35 -0
- package/dist/release-e2e/greenfield-python-free-smoke.js +7 -3
- package/dist/session/ritual-entrypoint.d.ts +6 -2
- package/dist/session/ritual-entrypoint.js +15 -2
- package/dist/session/session-ready.js +31 -14
- package/dist/session/session-start.d.ts +2 -1
- package/dist/session/session-start.js +66 -3
- package/dist/session/verify-session-ritual.d.ts +4 -2
- package/dist/session/verify-session-ritual.js +11 -11
- package/dist/slash/product-set.js +4 -1
- package/dist/swarm/verify-review-clean.d.ts +4 -3
- package/dist/swarm/verify-review-clean.js +28 -73
- package/dist/triage/classify/index.d.ts +9 -0
- package/dist/triage/classify/index.js +34 -2
- package/dist/triage/classify/label-mirror.d.ts +143 -0
- package/dist/triage/classify/label-mirror.js +684 -0
- package/dist/triage/help/registry-data.d.ts +7 -7
- package/dist/triage/help/registry-data.js +27 -7
- package/dist/triage/queue/show.d.ts +1 -1
- package/dist/triage/queue/show.js +4 -2
- package/dist/vbrief-validate/project-definition.js +1 -1
- package/dist/verify-env/agent-hook-readiness.d.ts +42 -0
- package/dist/verify-env/agent-hook-readiness.js +150 -0
- package/dist/verify-env/agent-hooks-live-probe.d.ts +16 -4
- package/dist/verify-env/agent-hooks-live-probe.js +120 -107
- package/dist/verify-env/agent-hooks.js +11 -3
- package/dist/verify-env/index.d.ts +1 -0
- package/dist/verify-env/index.js +1 -0
- package/package.json +7 -3
|
@@ -17,12 +17,14 @@ import { prunePythonArtifactsFromDeposit } from "../deposit/python-free.js";
|
|
|
17
17
|
import { resolveInstalledContentRoot } from "../deposit/resolve-content.js";
|
|
18
18
|
import { manifestTagToVersion, parseInstallManifest } from "../doctor/manifest.js";
|
|
19
19
|
import { readCorePackageVersion } from "../engine-version.js";
|
|
20
|
+
import { readLiveGeneration, stampLiveGeneration } from "../freshness/generation.js";
|
|
20
21
|
import { resolveLifecycleRoot } from "../layout/resolve.js";
|
|
21
22
|
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";
|
|
22
23
|
import { runOrgForceOnMigration } from "../policy/org-force-on-migration.js";
|
|
23
24
|
import { checkLocalEngineIntegrity, classify, ENGINE_PACKAGE, plan, renderGlobalInstall, resolveEngine, } from "../resolution/index.js";
|
|
24
25
|
import { depositOpenClawL2ProductCommands } from "../slash/openclaw-deposit.js";
|
|
25
26
|
import { gitPorcelain } from "../story-ready/git.js";
|
|
27
|
+
import { agentHookReadinessJson, evaluateAgentHookReadiness, evaluateAgentHookReadinessSafely, } from "../verify-env/agent-hook-readiness.js";
|
|
26
28
|
import { removeStaleMigratedFrameworkNarrative } from "../xbrief-migrate/migrate-project.js";
|
|
27
29
|
import { writeAgentHookDeposit } from "./agent-hooks.js";
|
|
28
30
|
import { ensureInitGitignoreLines, isDepositTrackedInGit } from "./gitignore.js";
|
|
@@ -332,9 +334,12 @@ export function buildVersionSkewNotice(engineVersion, contentVersion, previousDe
|
|
|
332
334
|
}
|
|
333
335
|
return null;
|
|
334
336
|
}
|
|
335
|
-
export function buildUpdateSummaryJson(
|
|
337
|
+
export function buildUpdateSummaryJson(input) {
|
|
338
|
+
const { result, options, updateState, readiness } = input;
|
|
336
339
|
return {
|
|
337
|
-
success: true,
|
|
340
|
+
success: readiness ? readiness.code === 0 : true,
|
|
341
|
+
deposit_completed: true,
|
|
342
|
+
...(readiness ? { agent_hook_readiness: agentHookReadinessJson(readiness) } : {}),
|
|
338
343
|
action: "upgrade",
|
|
339
344
|
...(updateState ? { update_state: updateState } : {}),
|
|
340
345
|
version: result.engineVersion,
|
|
@@ -416,6 +421,27 @@ export async function runRefreshDeposit(args, io, seams = {}) {
|
|
|
416
421
|
// VERSION already matches (e.g. pre-#2804 additive deposits). Does not re-stamp.
|
|
417
422
|
await reconcileDepositToContentPackage(deftDir, contentRoot, io);
|
|
418
423
|
migrateLegacyInstallManifest(projectDir, join(deftDir, "VERSION"));
|
|
424
|
+
// #3117: ensure a readable live generation token exists without advancing
|
|
425
|
+
// when the payload did not swap (already-current). stampLiveGeneration is a
|
|
426
|
+
// no-op write when the token already matches (keeps git clean under #2118).
|
|
427
|
+
// If generation is missing or behind VERSION (e.g. prior stamp failed after
|
|
428
|
+
// VERSION rewrite), fail closed — do not report success with a stale token.
|
|
429
|
+
const priorGen = readLiveGeneration(projectDir);
|
|
430
|
+
const generationMatches = priorGen !== null &&
|
|
431
|
+
normalizeVersion(priorGen.contentVersion) === normalizeVersion(contentVersion);
|
|
432
|
+
try {
|
|
433
|
+
stampLiveGeneration(projectDir, {
|
|
434
|
+
contentVersion,
|
|
435
|
+
stampedBy: "directive-update",
|
|
436
|
+
increment: false,
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
catch (err) {
|
|
440
|
+
if (!generationMatches) {
|
|
441
|
+
throw err;
|
|
442
|
+
}
|
|
443
|
+
// Token already matches content; ensure write failure is non-fatal noise.
|
|
444
|
+
}
|
|
419
445
|
}
|
|
420
446
|
else {
|
|
421
447
|
// Full-tree replace (or injected seam). Additive copy is no longer the default.
|
|
@@ -426,12 +452,13 @@ export async function runRefreshDeposit(args, io, seams = {}) {
|
|
|
426
452
|
// additive seams. Throws => no VERSION rewrite (refuse stamp until clean).
|
|
427
453
|
await reconcileDepositToContentPackage(deftDir, contentRoot, io);
|
|
428
454
|
const nowIso = seams.nowIso ?? (() => new Date().toISOString().replace(/\.\d{3}Z$/, "Z"));
|
|
455
|
+
const stampedAt = nowIso();
|
|
429
456
|
const manifestFields = {
|
|
430
457
|
ref: contentVersion.startsWith("v") ? contentVersion : `v${contentVersion}`,
|
|
431
458
|
sha: "content-package",
|
|
432
459
|
tag: contentVersion.startsWith("v") ? contentVersion : `v${contentVersion}`,
|
|
433
460
|
installRoot: CANONICAL_INSTALL_ROOT,
|
|
434
|
-
fetchedAt:
|
|
461
|
+
fetchedAt: stampedAt,
|
|
435
462
|
fetchedBy: "directive-update",
|
|
436
463
|
...(previousManagedBy ? { managedBy: previousManagedBy } : {}),
|
|
437
464
|
};
|
|
@@ -440,6 +467,15 @@ export async function runRefreshDeposit(args, io, seams = {}) {
|
|
|
440
467
|
// .deft/core/VERSION has been rewritten (folded in from install-upgrade so no
|
|
441
468
|
// manifest behavior is lost by the redirect). Best-effort; never fatal.
|
|
442
469
|
migrateLegacyInstallManifest(projectDir, writtenManifestPath);
|
|
470
|
+
// #3117: monotonic live generation MUST advance after a successful payload
|
|
471
|
+
// swap. Suppressing stamp failure would leave a prior bound/live match
|
|
472
|
+
// reporting `current` while the on-disk payload already changed (Greptile P1).
|
|
473
|
+
stampLiveGeneration(projectDir, {
|
|
474
|
+
contentVersion,
|
|
475
|
+
stampedBy: "directive-update",
|
|
476
|
+
increment: true,
|
|
477
|
+
nowIso: stampedAt,
|
|
478
|
+
});
|
|
443
479
|
}
|
|
444
480
|
// #2595: payload freshness and consumer derivative freshness are independent.
|
|
445
481
|
// Always repair these cheap projections, including on the #2118 no-op path.
|
|
@@ -653,17 +689,24 @@ export async function runRefreshDepositCli(options) {
|
|
|
653
689
|
}
|
|
654
690
|
try {
|
|
655
691
|
const result = await runRefreshDeposit(options, io, options.seams);
|
|
692
|
+
const readiness = evaluateAgentHookReadinessSafely(result.projectDir, options.seams?.evaluateAgentHookReadiness ?? evaluateAgentHookReadiness);
|
|
656
693
|
const state = result.alreadyCurrent
|
|
657
694
|
? "current"
|
|
658
695
|
: classification?.state;
|
|
659
696
|
if (options.jsonOut) {
|
|
660
|
-
options.writeOut(`${JSON.stringify(buildUpdateSummaryJson(result, options, state), null, 2)}\n`);
|
|
697
|
+
options.writeOut(`${JSON.stringify(buildUpdateSummaryJson({ result, options, updateState: state, readiness }), null, 2)}\n`);
|
|
661
698
|
printUpdateComplete(result, { printf: options.writeErr }, state);
|
|
662
699
|
}
|
|
663
700
|
else {
|
|
664
701
|
printUpdateComplete(result, io, state);
|
|
665
702
|
}
|
|
666
|
-
|
|
703
|
+
const readinessOut = options.jsonOut
|
|
704
|
+
? options.writeErr
|
|
705
|
+
: readiness.stream === "stderr"
|
|
706
|
+
? options.writeErr
|
|
707
|
+
: options.writeOut;
|
|
708
|
+
readinessOut(`\n${readiness.message}\n`);
|
|
709
|
+
return readiness.code;
|
|
667
710
|
}
|
|
668
711
|
catch (cause) {
|
|
669
712
|
if (cause instanceof LegacyLayoutRefusedError) {
|
package/dist/policy/index.d.ts
CHANGED
|
@@ -31,12 +31,15 @@ export declare const FIELD_TRIAGE_SCOPE_IGNORES = "plan.policy.triageScopeIgnore
|
|
|
31
31
|
export declare const FIELD_TRIAGE_RANKING_LABELS = "plan.policy.triageRankingLabels";
|
|
32
32
|
export declare const FIELD_TRIAGE_AUTO_CLASSIFY = "plan.policy.triageAutoClassify";
|
|
33
33
|
export declare const FIELD_TRIAGE_HOLD_MARKERS = "plan.policy.triageHoldMarkers";
|
|
34
|
+
export declare const FIELD_TRIAGE_LABEL_MIRROR = "plan.policy.triageLabelMirror";
|
|
34
35
|
export declare const FIELD_SWARM_SUBAGENT_BACKEND = "plan.policy.swarmSubagentBackend";
|
|
35
36
|
export declare const DEFAULT_SESSION_RITUAL_STALENESS_HOURS = 4;
|
|
36
37
|
export declare const DEFAULT_TRIAGE_SCOPE_VALUE: readonly Record<string, unknown>[];
|
|
37
38
|
export declare const DEFAULT_TRIAGE_SCOPE_IGNORES_VALUE: readonly unknown[];
|
|
38
39
|
export declare const DEFAULT_TRIAGE_RANKING_LABELS_VALUE: readonly string[];
|
|
39
40
|
export declare const DEFAULT_TRIAGE_AUTO_CLASSIFY_VALUE: readonly unknown[];
|
|
41
|
+
/** Default for #1423 Tier-1 label mirror: enabled with `triaged` idempotency marker. */
|
|
42
|
+
export declare const DEFAULT_TRIAGE_LABEL_MIRROR_VALUE: Readonly<Record<string, unknown>>;
|
|
40
43
|
export declare const KNOWN_SUBAGENT_BACKEND_IDS: Set<string>;
|
|
41
44
|
export interface PolicyField {
|
|
42
45
|
readonly name: string;
|
package/dist/policy/index.js
CHANGED
|
@@ -46,6 +46,7 @@ export const FIELD_TRIAGE_SCOPE_IGNORES = "plan.policy.triageScopeIgnores";
|
|
|
46
46
|
export const FIELD_TRIAGE_RANKING_LABELS = "plan.policy.triageRankingLabels";
|
|
47
47
|
export const FIELD_TRIAGE_AUTO_CLASSIFY = "plan.policy.triageAutoClassify";
|
|
48
48
|
export const FIELD_TRIAGE_HOLD_MARKERS = "plan.policy.triageHoldMarkers";
|
|
49
|
+
export const FIELD_TRIAGE_LABEL_MIRROR = "plan.policy.triageLabelMirror";
|
|
49
50
|
export const FIELD_SWARM_SUBAGENT_BACKEND = "plan.policy.swarmSubagentBackend";
|
|
50
51
|
// deliveryBranch also exported from delivery-branch.js via export *
|
|
51
52
|
export const DEFAULT_SESSION_RITUAL_STALENESS_HOURS = 4;
|
|
@@ -55,6 +56,13 @@ export const DEFAULT_TRIAGE_SCOPE_VALUE = [
|
|
|
55
56
|
export const DEFAULT_TRIAGE_SCOPE_IGNORES_VALUE = [];
|
|
56
57
|
export const DEFAULT_TRIAGE_RANKING_LABELS_VALUE = [];
|
|
57
58
|
export const DEFAULT_TRIAGE_AUTO_CLASSIFY_VALUE = [];
|
|
59
|
+
/** Default for #1423 Tier-1 label mirror: enabled with `triaged` idempotency marker. */
|
|
60
|
+
export const DEFAULT_TRIAGE_LABEL_MIRROR_VALUE = {
|
|
61
|
+
enabled: true,
|
|
62
|
+
idempotencyLabel: "triaged",
|
|
63
|
+
alwaysLabels: ["triaged"],
|
|
64
|
+
actionLabels: {},
|
|
65
|
+
};
|
|
58
66
|
export const KNOWN_SUBAGENT_BACKEND_IDS = new Set(["composer", "cursor-cloud", "grok-build"]);
|
|
59
67
|
const FALLBACK_HOLD_MARKERS = [
|
|
60
68
|
"do not implement",
|
|
@@ -139,6 +147,32 @@ function inspectWipCap(data) {
|
|
|
139
147
|
source: "default",
|
|
140
148
|
};
|
|
141
149
|
}
|
|
150
|
+
function inspectTriageLabelMirrorField(data) {
|
|
151
|
+
const policyBlock = getPolicyBlock(data);
|
|
152
|
+
if ("triageLabelMirror" in policyBlock) {
|
|
153
|
+
const raw = policyBlock.triageLabelMirror;
|
|
154
|
+
if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) {
|
|
155
|
+
return {
|
|
156
|
+
name: FIELD_TRIAGE_LABEL_MIRROR,
|
|
157
|
+
current: raw,
|
|
158
|
+
default: { ...DEFAULT_TRIAGE_LABEL_MIRROR_VALUE },
|
|
159
|
+
source: "typed",
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
name: FIELD_TRIAGE_LABEL_MIRROR,
|
|
164
|
+
current: { ...DEFAULT_TRIAGE_LABEL_MIRROR_VALUE },
|
|
165
|
+
default: { ...DEFAULT_TRIAGE_LABEL_MIRROR_VALUE },
|
|
166
|
+
source: "default-on-error",
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
name: FIELD_TRIAGE_LABEL_MIRROR,
|
|
171
|
+
current: { ...DEFAULT_TRIAGE_LABEL_MIRROR_VALUE },
|
|
172
|
+
default: { ...DEFAULT_TRIAGE_LABEL_MIRROR_VALUE },
|
|
173
|
+
source: "default",
|
|
174
|
+
};
|
|
175
|
+
}
|
|
142
176
|
function inspectSessionRitualStalenessHours(data) {
|
|
143
177
|
const policyBlock = getPolicyBlock(data);
|
|
144
178
|
if ("sessionRitualStalenessHours" in policyBlock) {
|
|
@@ -362,6 +396,7 @@ const REGISTERED_POLICIES = [
|
|
|
362
396
|
(data) => listFieldInspector(data, "triageHoldMarkers", FIELD_TRIAGE_HOLD_MARKERS, defaultHoldMarkers(), {
|
|
363
397
|
emptyIsTyped: true,
|
|
364
398
|
}),
|
|
399
|
+
inspectTriageLabelMirrorField,
|
|
365
400
|
inspectSwarmSubagentBackend,
|
|
366
401
|
inspectDeliveryBranchField,
|
|
367
402
|
inspectMinGreptileConfidenceField,
|
|
@@ -152,9 +152,14 @@ export function rehearseGreenfieldPythonFreeSmoke(repoRoot, seams = {}, options
|
|
|
152
152
|
return [false, `greenfield smoke: expected global deft at ${deft}`];
|
|
153
153
|
}
|
|
154
154
|
const pyFree = pythonFreePathEnv(envBase);
|
|
155
|
+
const installedBin = join(npmPrefix, "bin");
|
|
156
|
+
const installedEnv = {
|
|
157
|
+
...pyFree,
|
|
158
|
+
PATH: `${installedBin}:${pyFree.PATH ?? ""}`,
|
|
159
|
+
};
|
|
155
160
|
[ok, reason] = runStep(spawn, "directive init", deft, ["init", "--yes", "--repo-root", projectDir], {
|
|
156
161
|
cwd: work,
|
|
157
|
-
env:
|
|
162
|
+
env: installedEnv,
|
|
158
163
|
timeoutMs: 120_000,
|
|
159
164
|
}, onProgress);
|
|
160
165
|
if (!ok)
|
|
@@ -173,8 +178,7 @@ export function rehearseGreenfieldPythonFreeSmoke(repoRoot, seams = {}, options
|
|
|
173
178
|
return [false, "greenfield smoke: repo-root Python run shim present after init"];
|
|
174
179
|
}
|
|
175
180
|
const checkEnv = {
|
|
176
|
-
...
|
|
177
|
-
PATH: `${join(npmPrefix, "bin")}:${pyFree.PATH ?? ""}`,
|
|
181
|
+
...installedEnv,
|
|
178
182
|
DEFT_SESSION_RITUAL_SKIP: "1",
|
|
179
183
|
};
|
|
180
184
|
onProgress?.("greenfield smoke: running consumer task deft:check (engine-invoke path)");
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import { type AgentHookReadinessResult } from "../verify-env/agent-hook-readiness.js";
|
|
1
2
|
export declare const ENTRYPOINT_TIMEOUT_SECONDS = 300;
|
|
2
3
|
export declare const ENTRYPOINT_TIMEOUT_EXIT_CODE = 124;
|
|
3
4
|
export type RitualEntrypointFn = (argv: readonly string[]) => number;
|
|
5
|
+
export interface DefaultRitualRunnerSeams {
|
|
6
|
+
readonly evaluateAgentHookReadiness?: (projectRoot: string) => AgentHookReadinessResult;
|
|
7
|
+
}
|
|
4
8
|
/** Run an in-process ritual entrypoint with stdout/stderr capture. */
|
|
5
9
|
export declare function callMain(mainFn: RitualEntrypointFn, argv: readonly string[], _options?: {
|
|
6
10
|
label?: string;
|
|
@@ -11,8 +15,8 @@ export declare function callMain(mainFn: RitualEntrypointFn, argv: readonly stri
|
|
|
11
15
|
};
|
|
12
16
|
/** CLI-shaped cache-fresh entrypoint (mirrors scripts/preflight_cache.py main). */
|
|
13
17
|
export declare function runCacheFreshMain(argv: readonly string[]): number;
|
|
14
|
-
/** Default gated-tier runner: in-process doctor
|
|
15
|
-
export declare function defaultRitualRunner(command: readonly string[], projectRoot: string): {
|
|
18
|
+
/** Default gated-tier runner: in-process hook readiness, doctor, and cache freshness. */
|
|
19
|
+
export declare function defaultRitualRunner(command: readonly string[], projectRoot: string, seams?: DefaultRitualRunnerSeams): {
|
|
16
20
|
code: number;
|
|
17
21
|
stdout: string;
|
|
18
22
|
stderr: string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { cmdDoctor } from "../doctor/main.js";
|
|
2
2
|
import { evaluate } from "../preflight-cache/evaluate.js";
|
|
3
|
+
import { evaluateAgentHookReadinessSafely, } from "../verify-env/agent-hook-readiness.js";
|
|
3
4
|
export const ENTRYPOINT_TIMEOUT_SECONDS = 300;
|
|
4
5
|
export const ENTRYPOINT_TIMEOUT_EXIT_CODE = 124;
|
|
5
6
|
/** Run an in-process ritual entrypoint with stdout/stderr capture. */
|
|
@@ -66,9 +67,21 @@ export function runCacheFreshMain(argv) {
|
|
|
66
67
|
}
|
|
67
68
|
return result.code;
|
|
68
69
|
}
|
|
69
|
-
/** Default gated-tier runner: in-process doctor
|
|
70
|
-
export function defaultRitualRunner(command, projectRoot) {
|
|
70
|
+
/** Default gated-tier runner: in-process hook readiness, doctor, and cache freshness. */
|
|
71
|
+
export function defaultRitualRunner(command, projectRoot, seams = {}) {
|
|
71
72
|
const [verb, ...rest] = command;
|
|
73
|
+
if (verb === "verify:hooks-installed") {
|
|
74
|
+
const evaluateReadiness = seams.evaluateAgentHookReadiness
|
|
75
|
+
? (root) => evaluateAgentHookReadinessSafely(root, seams.evaluateAgentHookReadiness)
|
|
76
|
+
: evaluateAgentHookReadinessSafely;
|
|
77
|
+
const result = evaluateReadiness(projectRoot);
|
|
78
|
+
const output = `${result.message}\n`;
|
|
79
|
+
return {
|
|
80
|
+
code: result.code,
|
|
81
|
+
stdout: result.stream === "stdout" ? output : "",
|
|
82
|
+
stderr: result.stream === "stderr" ? output : "",
|
|
83
|
+
};
|
|
84
|
+
}
|
|
72
85
|
if (verb === "doctor") {
|
|
73
86
|
return callMain((argv) => cmdDoctor(argv), ["--project-root", projectRoot, ...rest]);
|
|
74
87
|
}
|
|
@@ -90,12 +90,20 @@ export function runSessionReady(projectRoot, options = {}) {
|
|
|
90
90
|
const env = options.env ?? process.env;
|
|
91
91
|
const lines = [];
|
|
92
92
|
const steps = [];
|
|
93
|
-
const inspect = options.inspectRitual ??
|
|
94
|
-
const verify = options.verifyRitual ??
|
|
95
|
-
const start = options.runStart ??
|
|
93
|
+
const inspect = options.inspectRitual ?? inspectSessionRitual;
|
|
94
|
+
const verify = options.verifyRitual ?? verifySessionRitual;
|
|
95
|
+
const start = options.runStart ?? runSessionStart;
|
|
96
96
|
const fetchAll = options.fetchAll ?? cacheFetchAll;
|
|
97
97
|
const inferRepo = options.inferRepo ?? ((root) => inferSessionReadyRepo(root, env));
|
|
98
|
-
|
|
98
|
+
const verifyOpts = {
|
|
99
|
+
tier: "gated",
|
|
100
|
+
posture: "mutation",
|
|
101
|
+
now,
|
|
102
|
+
runGit: options.runGit,
|
|
103
|
+
runner: options.runner,
|
|
104
|
+
forceGatedSteps: ["agent_hooks"],
|
|
105
|
+
};
|
|
106
|
+
// --- Fast path: inspect is green, but refresh functional hook readiness before returning. ---
|
|
99
107
|
const gatedInspect = inspect(projectRoot, {
|
|
100
108
|
tier: "gated",
|
|
101
109
|
posture: "mutation",
|
|
@@ -103,6 +111,22 @@ export function runSessionReady(projectRoot, options = {}) {
|
|
|
103
111
|
runGit: options.runGit,
|
|
104
112
|
});
|
|
105
113
|
if (gatedInspect.code === 0) {
|
|
114
|
+
steps.push("verify:session-ritual:gated");
|
|
115
|
+
const refreshed = verify(projectRoot, verifyOpts);
|
|
116
|
+
if (!isGatedVerifyActuallyReady(refreshed)) {
|
|
117
|
+
const message = refreshed.code === 0 && refreshed.bypassed
|
|
118
|
+
? bypassedReadyFailure(refreshed)
|
|
119
|
+
: `${refreshed.message}\n Remaining blocker after session:ready. Fix the step above, then re-run \`${readyCommand()}\`.`;
|
|
120
|
+
lines.push(message);
|
|
121
|
+
return {
|
|
122
|
+
code: refreshed.code === 0 ? 1 : refreshed.code,
|
|
123
|
+
message,
|
|
124
|
+
path: SESSION_READY_FAILED,
|
|
125
|
+
lines,
|
|
126
|
+
steps,
|
|
127
|
+
duration_ms: elapsedMs(started),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
106
130
|
const message = "OK session ready (gated ritual already fresh).";
|
|
107
131
|
lines.push(message);
|
|
108
132
|
return {
|
|
@@ -138,7 +162,7 @@ export function runSessionReady(projectRoot, options = {}) {
|
|
|
138
162
|
`session:start failed (exit ${startResult.code}). Recovery: run \`${readyCommand()}\` again after fixing the blocker.`;
|
|
139
163
|
lines.push(message);
|
|
140
164
|
return {
|
|
141
|
-
code: startResult.code
|
|
165
|
+
code: startResult.code,
|
|
142
166
|
message,
|
|
143
167
|
path: SESSION_READY_FAILED,
|
|
144
168
|
lines,
|
|
@@ -149,13 +173,6 @@ export function runSessionReady(projectRoot, options = {}) {
|
|
|
149
173
|
}
|
|
150
174
|
// --- Gated verify (lazy doctor + cache_fresh) ---
|
|
151
175
|
steps.push("verify:session-ritual:gated");
|
|
152
|
-
const verifyOpts = {
|
|
153
|
-
tier: "gated",
|
|
154
|
-
posture: "mutation",
|
|
155
|
-
now,
|
|
156
|
-
runGit: options.runGit,
|
|
157
|
-
runner: options.runner,
|
|
158
|
-
};
|
|
159
176
|
let verifyResult = verify(projectRoot, verifyOpts);
|
|
160
177
|
if (isGatedVerifyActuallyReady(verifyResult)) {
|
|
161
178
|
const message = "OK session ready (gated ritual verified).";
|
|
@@ -222,7 +239,7 @@ export function runSessionReady(projectRoot, options = {}) {
|
|
|
222
239
|
};
|
|
223
240
|
}
|
|
224
241
|
steps.push("verify:session-ritual:gated:retry");
|
|
225
|
-
verifyResult = verify(projectRoot, verifyOpts);
|
|
242
|
+
verifyResult = verify(projectRoot, { ...verifyOpts, forceGatedSteps: [] });
|
|
226
243
|
if (isGatedVerifyActuallyReady(verifyResult)) {
|
|
227
244
|
const message = "OK session ready (recovered via cache refresh).";
|
|
228
245
|
lines.push(message);
|
|
@@ -253,7 +270,7 @@ export function runSessionReady(projectRoot, options = {}) {
|
|
|
253
270
|
` Remaining blocker after session:ready. Fix the step above, then re-run \`${readyCommand()}\`.`;
|
|
254
271
|
lines.push(message);
|
|
255
272
|
return {
|
|
256
|
-
code: verifyResult.code
|
|
273
|
+
code: verifyResult.code,
|
|
257
274
|
message,
|
|
258
275
|
path: SESSION_READY_FAILED,
|
|
259
276
|
lines,
|
|
@@ -16,7 +16,8 @@ export type SessionCeremonyTier = (typeof SESSION_CEREMONY_TIERS)[number];
|
|
|
16
16
|
export declare const COLD_CEREMONY_TIER: SessionCeremonyTier;
|
|
17
17
|
export declare const REARM_CEREMONY_TIER: SessionCeremonyTier;
|
|
18
18
|
export declare const QUICK_STEPS: readonly ["alignment", "branch_policy", "triage_welcome"];
|
|
19
|
-
export declare const GATED_STEPS: readonly ["doctor", "cache_fresh"];
|
|
19
|
+
export declare const GATED_STEPS: readonly ["agent_hooks", "doctor", "cache_fresh"];
|
|
20
|
+
export type GatedStepName = (typeof GATED_STEPS)[number];
|
|
20
21
|
/** Env opt-in for optional session:start network (release probe + triage cache hydrate) (#2991). */
|
|
21
22
|
export declare const ENV_SESSION_START_NETWORK = "DEFT_SESSION_START_NETWORK";
|
|
22
23
|
/** Human-readable skip notice when optional network is off the hot path (#2991). */
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { runningInsideDeftRepo } from "../doctor/paths.js";
|
|
3
3
|
import { emitSessionEvalReadback } from "../eval/readback.js";
|
|
4
|
+
import { bindSessionGeneration } from "../freshness/bind.js";
|
|
5
|
+
import { readLiveGeneration } from "../freshness/generation.js";
|
|
4
6
|
import { MIGRATE_COMPLETION_NUDGE, shouldEmitMigrateNudge } from "../init-deposit/migrate.js";
|
|
5
7
|
import { detectEnvironmentContext, environmentContextToDict, formatEnvironmentContext, } from "../platform/shell-context.js";
|
|
6
8
|
import { DEFT_DIRECTIVE_DISABLE_FLAG_NAME, DEFT_DIRECTIVE_DISABLE_STATUS, detectDeftDirectiveDisable, formatDeftDirectiveDisableMessage, isDeftDirectiveDisableActive, } from "../policy/deft-directive-disable.js";
|
|
@@ -20,6 +22,7 @@ import { defaultGitRunner, gitHead, gitIsAncestor, worktreePath } from "./git.js
|
|
|
20
22
|
import { emitSessionStartProcessCost } from "./process-cost.js";
|
|
21
23
|
import { probeSessionReleaseAvailability, } from "./release-availability.js";
|
|
22
24
|
import { newRitualStatePayload, readRitualState, ritualStatePath, ritualStep, writeRitualState, } from "./ritual-sentinel.js";
|
|
25
|
+
import { timestampIso } from "./time.js";
|
|
23
26
|
export const SESSION_POSTURES = ["read-only", "mutation"];
|
|
24
27
|
export const READ_ONLY_POSTURE = "read-only";
|
|
25
28
|
export const MUTATION_POSTURE = "mutation";
|
|
@@ -30,7 +33,7 @@ export const SESSION_CEREMONY_TIERS = ["cold", "rearm"];
|
|
|
30
33
|
export const COLD_CEREMONY_TIER = "cold";
|
|
31
34
|
export const REARM_CEREMONY_TIER = "rearm";
|
|
32
35
|
export const QUICK_STEPS = ["alignment", "branch_policy", "triage_welcome"];
|
|
33
|
-
export const GATED_STEPS = ["doctor", "cache_fresh"];
|
|
36
|
+
export const GATED_STEPS = ["agent_hooks", "doctor", "cache_fresh"];
|
|
34
37
|
/** Env opt-in for optional session:start network (release probe + triage cache hydrate) (#2991). */
|
|
35
38
|
export const ENV_SESSION_START_NETWORK = "DEFT_SESSION_START_NETWORK";
|
|
36
39
|
/** Human-readable skip notice when optional network is off the hot path (#2991). */
|
|
@@ -148,6 +151,10 @@ export function parseDeferrals(rawValues) {
|
|
|
148
151
|
const name = raw.slice(0, eq);
|
|
149
152
|
const reason = raw.slice(eq + 1);
|
|
150
153
|
const stepName = normaliseStepName(name.trim());
|
|
154
|
+
if (stepName === "agent_hooks") {
|
|
155
|
+
errors.push('ritual step "agent_hooks" is not deferrable');
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
151
158
|
if (!allowed.has(stepName)) {
|
|
152
159
|
errors.push(`unknown ritual step ${JSON.stringify(name)}; expected one of ${JSON.stringify([...allowed].sort())}`);
|
|
153
160
|
continue;
|
|
@@ -454,10 +461,11 @@ function runSessionRearm(projectRoot, options, instant, environment) {
|
|
|
454
461
|
};
|
|
455
462
|
const gatedSteps = restampSteps(eligibility.state.gatedSteps, instant);
|
|
456
463
|
const writeStarted = performance.now();
|
|
464
|
+
const rearmSessionId = (options.newSessionId ?? randomUUID)();
|
|
457
465
|
// Fresh payload (no rearm_needed / compact_resume_at) clears compact markers (#2992).
|
|
458
466
|
const writePayload = {
|
|
459
467
|
...newRitualStatePayload({
|
|
460
|
-
sessionId:
|
|
468
|
+
sessionId: rearmSessionId,
|
|
461
469
|
gitHead: eligibility.currentHead,
|
|
462
470
|
worktreePath: eligibility.currentWorktree,
|
|
463
471
|
startedAt: instant,
|
|
@@ -502,6 +510,32 @@ function runSessionRearm(projectRoot, options, instant, environment) {
|
|
|
502
510
|
.filter(([, step]) => !step.ok && !step.deferred_reason)
|
|
503
511
|
.map(([name]) => name);
|
|
504
512
|
const code = failed.length > 0 ? 1 : 0;
|
|
513
|
+
// #3117: bind live deposit generation into session context on re-arm (host-agnostic).
|
|
514
|
+
let freshnessBind = null;
|
|
515
|
+
try {
|
|
516
|
+
const bound = bindSessionGeneration(projectRoot, {
|
|
517
|
+
sessionId: rearmSessionId,
|
|
518
|
+
nowIso: timestampIso(instant),
|
|
519
|
+
payloadLoaded: true,
|
|
520
|
+
});
|
|
521
|
+
freshnessBind = {
|
|
522
|
+
bound_generation: bound.bound.boundGeneration,
|
|
523
|
+
live_generation: bound.live.generation,
|
|
524
|
+
content_version: bound.live.contentVersion,
|
|
525
|
+
path: bound.path,
|
|
526
|
+
};
|
|
527
|
+
lines.push(`[deft freshness] bound generation ${bound.bound.boundGeneration} ` +
|
|
528
|
+
`(live deposit v${bound.live.contentVersion})`);
|
|
529
|
+
lines.push(`[deft freshness] set DEFT_SESSION_ID=${rearmSessionId} for bare freshness:report/bind ` +
|
|
530
|
+
`(or pass --session-id; required for multi-agent trusted readiness)`);
|
|
531
|
+
}
|
|
532
|
+
catch {
|
|
533
|
+
const live = readLiveGeneration(projectRoot);
|
|
534
|
+
if (live !== null) {
|
|
535
|
+
lines.push(`[deft freshness] live generation ${live.generation} present; session bind deferred ` +
|
|
536
|
+
`(run \`deft freshness:bind\` after loading payload surfaces)`);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
505
539
|
// #2994: local process-cost event (best-effort; never blocks ceremony).
|
|
506
540
|
emitSessionStartProcessCost({
|
|
507
541
|
ceremonyTier: REARM_CEREMONY_TIER,
|
|
@@ -519,6 +553,7 @@ function runSessionRearm(projectRoot, options, instant, environment) {
|
|
|
519
553
|
ceremony_tier: REARM_CEREMONY_TIER,
|
|
520
554
|
rearm_eligible: true,
|
|
521
555
|
state_path: statePath,
|
|
556
|
+
...(freshnessBind ? { freshness: freshnessBind } : {}),
|
|
522
557
|
quick_steps: quickSteps,
|
|
523
558
|
gated_steps: gatedSteps,
|
|
524
559
|
steps: stepTimings,
|
|
@@ -829,8 +864,9 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
829
864
|
lines.push(consentPrompt.trimEnd());
|
|
830
865
|
}
|
|
831
866
|
const writeStarted = performance.now();
|
|
867
|
+
const coldSessionId = (options.newSessionId ?? randomUUID)();
|
|
832
868
|
const payload = newRitualStatePayload({
|
|
833
|
-
sessionId:
|
|
869
|
+
sessionId: coldSessionId,
|
|
834
870
|
gitHead: gitHeadValue,
|
|
835
871
|
worktreePath: worktreePath(projectRoot, runGit),
|
|
836
872
|
startedAt: instant,
|
|
@@ -860,11 +896,38 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
860
896
|
.map(([name]) => name);
|
|
861
897
|
const code = failed.length > 0 ? 1 : 0;
|
|
862
898
|
const totalMs = elapsedMs(overallStarted);
|
|
899
|
+
// #3117: bind live deposit generation when payload surfaces load (cold path).
|
|
900
|
+
let freshnessBind = null;
|
|
901
|
+
try {
|
|
902
|
+
const bound = bindSessionGeneration(projectRoot, {
|
|
903
|
+
sessionId: coldSessionId,
|
|
904
|
+
nowIso: timestampIso(instant),
|
|
905
|
+
payloadLoaded: true,
|
|
906
|
+
});
|
|
907
|
+
freshnessBind = {
|
|
908
|
+
bound_generation: bound.bound.boundGeneration,
|
|
909
|
+
live_generation: bound.live.generation,
|
|
910
|
+
content_version: bound.live.contentVersion,
|
|
911
|
+
path: bound.path,
|
|
912
|
+
};
|
|
913
|
+
lines.push(`[deft freshness] bound generation ${bound.bound.boundGeneration} ` +
|
|
914
|
+
`(live deposit v${bound.live.contentVersion})`);
|
|
915
|
+
lines.push(`[deft freshness] set DEFT_SESSION_ID=${coldSessionId} for bare freshness:report/bind ` +
|
|
916
|
+
`(or pass --session-id; required for multi-agent trusted readiness)`);
|
|
917
|
+
}
|
|
918
|
+
catch {
|
|
919
|
+
const live = readLiveGeneration(projectRoot);
|
|
920
|
+
if (live !== null) {
|
|
921
|
+
lines.push(`[deft freshness] live generation ${live.generation} present; session bind deferred ` +
|
|
922
|
+
`(run \`deft freshness:bind\` after loading payload surfaces)`);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
863
925
|
const resultPayload = {
|
|
864
926
|
ready: code === 0,
|
|
865
927
|
exit_code: code,
|
|
866
928
|
ceremony_tier: COLD_CEREMONY_TIER,
|
|
867
929
|
state_path: statePath,
|
|
930
|
+
...(freshnessBind ? { freshness: freshnessBind } : {}),
|
|
868
931
|
quick_steps: quickSteps,
|
|
869
932
|
gated_steps: gatedSteps,
|
|
870
933
|
steps: stepTimings,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { type GitRunner } from "./git.js";
|
|
2
2
|
import { type DirectivePosture } from "./posture.js";
|
|
3
|
-
import { type SessionCeremonyTier } from "./session-start.js";
|
|
3
|
+
import { type GatedStepName, type SessionCeremonyTier } from "./session-start.js";
|
|
4
4
|
export { formatCacheFetchAllRecoveryCommand, recoveryHintForStaleFailure, } from "./cache-recovery.js";
|
|
5
5
|
export declare const ENV_SKIP = "DEFT_SESSION_RITUAL_SKIP";
|
|
6
6
|
export { ENTRYPOINT_TIMEOUT_EXIT_CODE, ENTRYPOINT_TIMEOUT_SECONDS, } from "./ritual-entrypoint.js";
|
|
7
|
-
export declare const GATED_ENTRYPOINT_COMMANDS: Readonly<Record<
|
|
7
|
+
export declare const GATED_ENTRYPOINT_COMMANDS: Readonly<Record<GatedStepName, readonly string[]>>;
|
|
8
8
|
export interface VerifyResult {
|
|
9
9
|
readonly code: number;
|
|
10
10
|
readonly message: string;
|
|
@@ -37,6 +37,8 @@ export interface VerifySessionRitualOptions {
|
|
|
37
37
|
readonly posture?: DirectivePosture;
|
|
38
38
|
readonly envPosture?: string | undefined;
|
|
39
39
|
readonly handoffText?: string | null;
|
|
40
|
+
/** Re-run selected gated prerequisites even when their recorded step is green. */
|
|
41
|
+
readonly forceGatedSteps?: readonly GatedStepName[];
|
|
40
42
|
}
|
|
41
43
|
export interface InspectSessionRitualOptions {
|
|
42
44
|
readonly tier?: "quick" | "gated";
|
|
@@ -11,6 +11,7 @@ export { formatCacheFetchAllRecoveryCommand, recoveryHintForStaleFailure, } from
|
|
|
11
11
|
export const ENV_SKIP = "DEFT_SESSION_RITUAL_SKIP";
|
|
12
12
|
export { ENTRYPOINT_TIMEOUT_EXIT_CODE, ENTRYPOINT_TIMEOUT_SECONDS, } from "./ritual-entrypoint.js";
|
|
13
13
|
export const GATED_ENTRYPOINT_COMMANDS = {
|
|
14
|
+
agent_hooks: ["verify:hooks-installed", "--scope=agent", "--live"],
|
|
14
15
|
doctor: ["doctor"],
|
|
15
16
|
cache_fresh: ["verify:cache-fresh"],
|
|
16
17
|
};
|
|
@@ -26,16 +27,11 @@ function stepPasses(step) {
|
|
|
26
27
|
}
|
|
27
28
|
function failedStepMessage(tierName, stepName, step) {
|
|
28
29
|
const coldCmd = formatSessionStartRecoveryCommand("cold");
|
|
29
|
-
if (step ===
|
|
30
|
+
if (step === undefined) {
|
|
30
31
|
return (`session ritual ${tierName} step '${stepName}' is missing. ` +
|
|
31
32
|
`Run \`${coldCmd}\` before implementation dispatch.`);
|
|
32
33
|
}
|
|
33
|
-
|
|
34
|
-
step !== null &&
|
|
35
|
-
step.deferred_reason) {
|
|
36
|
-
return "";
|
|
37
|
-
}
|
|
38
|
-
const message = typeof step === "object" && step !== null ? step.message : null;
|
|
34
|
+
const message = step.message;
|
|
39
35
|
const suffix = typeof message === "string" && message.length > 0 ? `: ${message}` : "";
|
|
40
36
|
return `session ritual ${tierName} step '${stepName}' failed${suffix}`;
|
|
41
37
|
}
|
|
@@ -53,9 +49,9 @@ export function formatRitualRecoveryInstruction(tier = "cold") {
|
|
|
53
49
|
`(one-shot: session:start + gated ritual + cache recovery as needed).`);
|
|
54
50
|
}
|
|
55
51
|
function runGatedStep(projectRoot, payload, stepName, runner, now) {
|
|
56
|
-
const command = [...
|
|
52
|
+
const command = [...GATED_ENTRYPOINT_COMMANDS[stepName]];
|
|
57
53
|
const { code, stdout, stderr } = runner(command, projectRoot);
|
|
58
|
-
const message = stdout.trim() || stderr.trim() || `${command[0]
|
|
54
|
+
const message = stdout.trim() || stderr.trim() || `${command[0]} exited ${code}`;
|
|
59
55
|
const gated = payload.gated_steps ?? {};
|
|
60
56
|
gated[stepName] = ritualStep({
|
|
61
57
|
ok: code === 0,
|
|
@@ -354,11 +350,15 @@ export function verifySessionRitual(projectRoot, options = {}) {
|
|
|
354
350
|
const gated = { ...payload.gated_steps };
|
|
355
351
|
payload.gated_steps = gated;
|
|
356
352
|
const runCmd = options.runner ?? defaultRitualRunner;
|
|
353
|
+
const forced = new Set(options.forceGatedSteps ?? []);
|
|
357
354
|
for (const stepName of GATED_STEPS) {
|
|
358
355
|
const step = gated[stepName];
|
|
359
|
-
if (step?.deferred_reason)
|
|
356
|
+
if (step?.deferred_reason && stepName !== "agent_hooks")
|
|
360
357
|
continue;
|
|
361
|
-
|
|
358
|
+
// Hook readiness is a live mutation prerequisite, not a cacheable doctor
|
|
359
|
+
// result. Re-run it at every gated boundary so registration/shim drift
|
|
360
|
+
// cannot remain hidden behind an earlier green ritual record.
|
|
361
|
+
if (stepName !== "agent_hooks" && stepPasses(step) && !forced.has(stepName))
|
|
362
362
|
continue;
|
|
363
363
|
const writeError = runGatedStep(projectRoot, payload, stepName, runCmd, instant);
|
|
364
364
|
if (writeError !== null) {
|
|
@@ -109,9 +109,12 @@ export const PRODUCT_COMMANDS = Object.freeze([
|
|
|
109
109
|
{
|
|
110
110
|
logicalId: "/deft:checkpoint",
|
|
111
111
|
filenameStem: "deft-checkpoint",
|
|
112
|
+
// description documents the save path; wrappers load the strategy doc (same as continue).
|
|
113
|
+
// ⊗ Point dispatchPath at xbrief/continue.xbrief.json — that file is the checkpoint *output*,
|
|
114
|
+
// not the instruction doc; first invoke fails when it does not exist yet (#3105).
|
|
112
115
|
description: "Save session state to xbrief/continue.xbrief.json",
|
|
113
116
|
dispatchKind: "session",
|
|
114
|
-
dispatchPath: "
|
|
117
|
+
dispatchPath: "resilience/continue-here.md",
|
|
115
118
|
},
|
|
116
119
|
]);
|
|
117
120
|
/**
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import type { SlizardGateOptions } from "../pr-merge-readiness/slizard-gate.js";
|
|
1
|
+
import { type ComputeGateOptions } from "../pr-merge-readiness/compute.js";
|
|
3
2
|
import type { RunGhFn } from "../pr-merge-readiness/types.js";
|
|
4
|
-
type ReviewGateOptions =
|
|
3
|
+
type ReviewGateOptions = ComputeGateOptions;
|
|
5
4
|
import { EXIT_EXTERNAL_ERROR, EXIT_OK, EXIT_UNCLEAN } from "./constants.js";
|
|
6
5
|
export interface CohortResolutionError {
|
|
7
6
|
vbrief_path: string;
|
|
@@ -15,6 +14,8 @@ export interface CohortPRResult {
|
|
|
15
14
|
clean: boolean;
|
|
16
15
|
ci_summary: Record<string, unknown> | null;
|
|
17
16
|
slizard_summary: Record<string, unknown> | null;
|
|
17
|
+
via?: string;
|
|
18
|
+
partial_data?: Record<string, unknown>;
|
|
18
19
|
}
|
|
19
20
|
export interface CohortResult {
|
|
20
21
|
repo: string | null;
|