@deftai/directive-core 0.89.0 → 0.91.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/doctor/constants.d.ts +1 -1
- package/dist/doctor/constants.js +2 -0
- package/dist/doctor/flags.js +21 -1
- package/dist/doctor/index.d.ts +1 -0
- package/dist/doctor/index.js +1 -0
- package/dist/doctor/main.js +22 -0
- package/dist/doctor/openclaw-skills.d.ts +143 -0
- package/dist/doctor/openclaw-skills.js +706 -0
- package/dist/doctor/types.d.ts +11 -0
- package/dist/eval/health.d.ts +10 -1
- package/dist/eval/health.js +16 -16
- package/dist/hooks/dispatcher.js +20 -5
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/init-deposit/hygiene.js +3 -0
- package/dist/init-deposit/init-deposit.d.ts +5 -0
- package/dist/init-deposit/init-deposit.js +37 -2
- package/dist/intake/github-auth-modes.d.ts +1 -1
- package/dist/intake/github-auth-modes.js +25 -3
- package/dist/intake/issue-ingest-cli.js +13 -0
- package/dist/intake/reconcile-issues-cli.js +13 -0
- package/dist/lifecycle/events.js +2 -0
- package/dist/lifecycle/index.d.ts +1 -0
- package/dist/lifecycle/index.js +1 -0
- package/dist/lifecycle/stats.d.ts +53 -0
- package/dist/lifecycle/stats.js +286 -0
- package/dist/policy/plan-extensions.d.ts +10 -0
- package/dist/policy/plan-extensions.js +16 -0
- package/dist/pr-closing-keywords/detect.d.ts +13 -0
- package/dist/pr-closing-keywords/detect.js +73 -8
- package/dist/pr-closing-keywords/index.d.ts +2 -2
- package/dist/pr-closing-keywords/index.js +1 -1
- package/dist/pr-closing-keywords/main.js +119 -90
- package/dist/pr-closing-keywords/types.d.ts +10 -0
- package/dist/release/spawn.js +13 -0
- package/dist/release-e2e/git-ops.d.ts +7 -0
- package/dist/release-e2e/git-ops.js +35 -2
- package/dist/render/constants.d.ts +16 -0
- package/dist/render/constants.js +16 -1
- package/dist/render/export-spec.d.ts +7 -1
- package/dist/render/export-spec.js +51 -5
- package/dist/render/index.d.ts +3 -2
- package/dist/render/index.js +2 -2
- package/dist/render/roadmap-render.js +164 -60
- package/dist/render/scope-outlook.d.ts +5 -0
- package/dist/render/scope-outlook.js +3 -0
- package/dist/render/spec-render.d.ts +28 -2
- package/dist/render/spec-render.js +111 -14
- package/dist/render/text-utils.d.ts +6 -0
- package/dist/render/text-utils.js +23 -0
- package/dist/scm/binary.d.ts +3 -0
- package/dist/scm/binary.js +10 -2
- package/dist/scm/call.d.ts +5 -0
- package/dist/scm/call.js +5 -0
- package/dist/scm/index.d.ts +1 -0
- package/dist/scm/index.js +1 -0
- package/dist/scm/main.d.ts +5 -0
- package/dist/scm/main.js +27 -0
- package/dist/scm/readiness-cli.d.ts +24 -0
- package/dist/scm/readiness-cli.js +85 -0
- package/dist/scm/readiness.d.ts +104 -0
- package/dist/scm/readiness.js +421 -0
- package/dist/scope/batch-promote.d.ts +26 -0
- package/dist/scope/batch-promote.js +167 -0
- package/dist/scope/index.d.ts +1 -0
- package/dist/scope/index.js +1 -0
- package/dist/scope/main.d.ts +2 -0
- package/dist/scope/main.js +58 -7
- package/dist/scope/wip-cap-check.d.ts +6 -0
- package/dist/scope/wip-cap-check.js +14 -2
- package/dist/session/index.d.ts +2 -0
- package/dist/session/index.js +2 -0
- package/dist/session/process-cost-constants.d.ts +9 -0
- package/dist/session/process-cost-constants.js +11 -0
- package/dist/session/process-cost.d.ts +53 -0
- package/dist/session/process-cost.js +82 -0
- package/dist/session/ritual-sentinel.d.ts +5 -0
- package/dist/session/ritual-sentinel.js +12 -1
- package/dist/session/session-ready.d.ts +67 -0
- package/dist/session/session-ready.js +264 -0
- package/dist/session/session-start.d.ts +63 -1
- package/dist/session/session-start.js +432 -24
- package/dist/session/verify-session-ritual.d.ts +8 -0
- package/dist/session/verify-session-ritual.js +95 -35
- package/dist/tool-events/classify.d.ts +20 -0
- package/dist/tool-events/classify.js +634 -0
- package/dist/tool-events/index.d.ts +10 -0
- package/dist/tool-events/index.js +10 -0
- package/dist/tool-events/summarize.d.ts +34 -0
- package/dist/tool-events/summarize.js +93 -0
- package/dist/tool-events/types.d.ts +52 -0
- package/dist/tool-events/types.js +13 -0
- package/dist/triage/welcome/index.d.ts +1 -1
- package/dist/triage/welcome/index.js +1 -1
- package/dist/triage/welcome/onboard.js +7 -1
- package/dist/triage/welcome/prior-state.d.ts +7 -0
- package/dist/triage/welcome/prior-state.js +16 -2
- package/dist/triage/welcome/writers.d.ts +10 -0
- package/dist/triage/welcome/writers.js +72 -2
- package/package.json +7 -3
|
@@ -8,21 +8,38 @@ import { detectNoDeftDirective, NO_DEFT_DIRECTIVE_DISABLED_MESSAGE, NO_DEFT_DIRE
|
|
|
8
8
|
import { humanMergeDisclosureLine, resolveHumanMergePolicy, } from "../policy/require-human-merge.js";
|
|
9
9
|
import { resolvePolicy } from "../policy/resolve.js";
|
|
10
10
|
import { maybeFormatProductSignalConsentPrompt } from "../product-signal/consent-prompt.js";
|
|
11
|
+
import { formatFrameworkCommand } from "../render/framework-commands.js";
|
|
12
|
+
import { formatScmReadinessLines, probeScmReadiness, scmReadinessToDict, } from "../scm/readiness.js";
|
|
11
13
|
import { maybeRunStalenessTickler } from "../staleness-tickler/run.js";
|
|
12
14
|
import { runDefaultMode } from "../triage/welcome/default-mode.js";
|
|
13
15
|
import { resolveUserMdPath } from "../user-config/resolve-user-md.js";
|
|
14
16
|
import { emitSessionValueReadback } from "../value/readback.js";
|
|
15
17
|
import { verifyRequiredTools } from "../verify-env/verify-tools.js";
|
|
16
|
-
import { defaultGitRunner, gitHead, worktreePath } from "./git.js";
|
|
18
|
+
import { defaultGitRunner, gitHead, gitIsAncestor, worktreePath } from "./git.js";
|
|
19
|
+
import { emitSessionStartProcessCost } from "./process-cost.js";
|
|
17
20
|
import { probeSessionReleaseAvailability, } from "./release-availability.js";
|
|
18
|
-
import { newRitualStatePayload, ritualStatePath, ritualStep, writeRitualState, } from "./ritual-sentinel.js";
|
|
21
|
+
import { newRitualStatePayload, readRitualState, ritualStatePath, ritualStep, writeRitualState, } from "./ritual-sentinel.js";
|
|
19
22
|
export const SESSION_POSTURES = ["read-only", "mutation"];
|
|
20
23
|
export const READ_ONLY_POSTURE = "read-only";
|
|
21
24
|
export const MUTATION_POSTURE = "mutation";
|
|
22
25
|
export const READ_ONLY_ALIGNMENT_MESSAGE = "Deft Directive active -- AGENTS.md loaded.";
|
|
23
26
|
export const READ_ONLY_RESULT_MESSAGE = "read-only session posture (alignment only; no ritual-state write)";
|
|
27
|
+
/** Cold (full) vs re-arm (clock/HEAD refresh) ceremony tiers (#2992). */
|
|
28
|
+
export const SESSION_CEREMONY_TIERS = ["cold", "rearm"];
|
|
29
|
+
export const COLD_CEREMONY_TIER = "cold";
|
|
30
|
+
export const REARM_CEREMONY_TIER = "rearm";
|
|
24
31
|
export const QUICK_STEPS = ["alignment", "branch_policy", "triage_welcome"];
|
|
25
32
|
export const GATED_STEPS = ["doctor", "cache_fresh"];
|
|
33
|
+
/** Env opt-in for optional session:start network (release probe + triage cache hydrate) (#2991). */
|
|
34
|
+
export const ENV_SESSION_START_NETWORK = "DEFT_SESSION_START_NETWORK";
|
|
35
|
+
/** Human-readable skip notice when optional network is off the hot path (#2991). */
|
|
36
|
+
export const OPTIONAL_NETWORK_SKIPPED_MESSAGE = "[deft session] optional network skipped (release probe, triage cache hydrate/self-heal); " +
|
|
37
|
+
"pass --with-network or set DEFT_SESSION_START_NETWORK=1 to enable.";
|
|
38
|
+
/** Re-arm skips fat cold-path work when prior ritual is still structurally valid (#2992). */
|
|
39
|
+
export const REARM_SKIPPED_FAT_PATH_MESSAGE = "[deft session] re-arm tier: refreshed ritual clock + HEAD/worktree bind; " +
|
|
40
|
+
"skipped verify:tools, triage welcome, release probe, and staleness tickler.";
|
|
41
|
+
/** When --rearm is requested but state requires a full cold ceremony (#2992). */
|
|
42
|
+
export const REARM_INELIGIBLE_PREFIX = "session re-arm refused — full cold session:start required:";
|
|
26
43
|
const STEP_ALIASES = {
|
|
27
44
|
branch: "branch_policy",
|
|
28
45
|
"branch-policy": "branch_policy",
|
|
@@ -31,6 +48,89 @@ const STEP_ALIASES = {
|
|
|
31
48
|
triage: "triage_welcome",
|
|
32
49
|
"triage-welcome": "triage_welcome",
|
|
33
50
|
};
|
|
51
|
+
/** Format preferred `session:start` recovery command for cold vs re-arm (#2992). */
|
|
52
|
+
export function formatSessionStartRecoveryCommand(tier = "cold") {
|
|
53
|
+
if (tier === "rearm") {
|
|
54
|
+
return formatFrameworkCommand(["session:start", "--rearm"]);
|
|
55
|
+
}
|
|
56
|
+
return formatFrameworkCommand(["session:start"]);
|
|
57
|
+
}
|
|
58
|
+
function stepPassesForRearm(step) {
|
|
59
|
+
if (!step || typeof step !== "object")
|
|
60
|
+
return false;
|
|
61
|
+
if (step.deferred_reason)
|
|
62
|
+
return true;
|
|
63
|
+
return step.ok === true;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Whether a prior ritual can be re-armed without a full cold ceremony (#2992).
|
|
67
|
+
* Requires valid state, same worktree, continuous (or identical) HEAD, and
|
|
68
|
+
* previously-passing quick steps.
|
|
69
|
+
*/
|
|
70
|
+
export function assessRearmEligibility(projectRoot, options = {}) {
|
|
71
|
+
const runGit = options.runGit ?? defaultGitRunner;
|
|
72
|
+
const [state, err] = readRitualState(projectRoot);
|
|
73
|
+
if (state === null) {
|
|
74
|
+
return {
|
|
75
|
+
eligible: false,
|
|
76
|
+
reason: err ?? "ritual state missing (first install / cold path required)",
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const { head: currentHead, error: headError } = gitHead(projectRoot, runGit);
|
|
80
|
+
if (currentHead === null) {
|
|
81
|
+
return { eligible: false, reason: headError ?? "could not resolve git HEAD" };
|
|
82
|
+
}
|
|
83
|
+
const currentWorktree = worktreePath(projectRoot, runGit);
|
|
84
|
+
if (state.worktreePath !== currentWorktree) {
|
|
85
|
+
return {
|
|
86
|
+
eligible: false,
|
|
87
|
+
reason: `ritual state belongs to a different worktree (${state.worktreePath})`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
if (state.gitHead !== currentHead) {
|
|
91
|
+
const forward = gitIsAncestor(projectRoot, state.gitHead, currentHead, runGit);
|
|
92
|
+
if (forward === null) {
|
|
93
|
+
return { eligible: false, reason: "could not verify git history for session re-arm" };
|
|
94
|
+
}
|
|
95
|
+
if (!forward) {
|
|
96
|
+
return {
|
|
97
|
+
eligible: false,
|
|
98
|
+
reason: "git HEAD changed discontinuously (full cold session:start required)",
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
for (const stepName of QUICK_STEPS) {
|
|
103
|
+
if (!stepPassesForRearm(state.quickSteps[stepName])) {
|
|
104
|
+
return {
|
|
105
|
+
eligible: false,
|
|
106
|
+
reason: `quick step '${stepName}' is missing or failed (full cold session:start required)`,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return { eligible: true, state, currentHead, currentWorktree };
|
|
111
|
+
}
|
|
112
|
+
function restampSteps(steps, now) {
|
|
113
|
+
const out = {};
|
|
114
|
+
for (const [name, step] of Object.entries(steps)) {
|
|
115
|
+
out[name] = {
|
|
116
|
+
...step,
|
|
117
|
+
ts: ritualStep({ ok: step.ok === true, ts: now }).ts,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
/** Resolve whether optional session:start network work is enabled (#2991). */
|
|
123
|
+
export function resolveSessionStartOptionalNetwork(options = {}) {
|
|
124
|
+
if (options.allowOptionalNetwork === true)
|
|
125
|
+
return true;
|
|
126
|
+
if (options.allowOptionalNetwork === false)
|
|
127
|
+
return false;
|
|
128
|
+
const env = options.env ?? process.env;
|
|
129
|
+
return env[ENV_SESSION_START_NETWORK] === "1";
|
|
130
|
+
}
|
|
131
|
+
function elapsedMs(started) {
|
|
132
|
+
return Math.max(0, Math.round(performance.now() - started));
|
|
133
|
+
}
|
|
34
134
|
function normaliseStepName(name) {
|
|
35
135
|
return STEP_ALIASES[name] ?? name;
|
|
36
136
|
}
|
|
@@ -193,6 +293,44 @@ export function defaultBranchSync(projectRoot, runGit = defaultGitRunner) {
|
|
|
193
293
|
}
|
|
194
294
|
return { branch, upstream, ahead, behind, warning };
|
|
195
295
|
}
|
|
296
|
+
/**
|
|
297
|
+
* Resolve SCM readiness for session orientation (#2275).
|
|
298
|
+
* Shallow by default (PATH + auth status); deep when optional network is on.
|
|
299
|
+
* Never throws — session:start must not hard-block on SCM absence.
|
|
300
|
+
*/
|
|
301
|
+
function resolveSessionScmReadiness(options, allowOptionalNetwork) {
|
|
302
|
+
const probe = options.probeScm ?? probeScmReadiness;
|
|
303
|
+
try {
|
|
304
|
+
return probe({
|
|
305
|
+
depth: allowOptionalNetwork ? "deep" : "shallow",
|
|
306
|
+
env: options.env,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
catch {
|
|
310
|
+
return {
|
|
311
|
+
ready: false,
|
|
312
|
+
binary: null,
|
|
313
|
+
binaryPath: null,
|
|
314
|
+
authState: "unknown",
|
|
315
|
+
githubAuthMode: "host-gh",
|
|
316
|
+
runtimeMode: "local-unsandboxed",
|
|
317
|
+
injectedTokenPresent: false,
|
|
318
|
+
depth: allowOptionalNetwork ? "deep" : "shallow",
|
|
319
|
+
detail: "SCM readiness probe failed unexpectedly; treating SCM-dependent gates as skipped",
|
|
320
|
+
remediation: null,
|
|
321
|
+
skippedGates: [
|
|
322
|
+
"triage:queue",
|
|
323
|
+
"issue:ingest",
|
|
324
|
+
"pr:*",
|
|
325
|
+
"reconcile:issues",
|
|
326
|
+
"cache:fetch-all",
|
|
327
|
+
"scm:*",
|
|
328
|
+
],
|
|
329
|
+
login: null,
|
|
330
|
+
failureKind: "probe_error",
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
}
|
|
196
334
|
function runReadOnlySessionStart(projectRoot, options, instant, environment) {
|
|
197
335
|
const lines = [];
|
|
198
336
|
const resolveUserMd = options.resolveUserMd ?? ((root) => resolveUserMdPath({ projectRoot: root }));
|
|
@@ -202,9 +340,12 @@ function runReadOnlySessionStart(projectRoot, options, instant, environment) {
|
|
|
202
340
|
const userMdLine = userMd.found
|
|
203
341
|
? `USER.md resolved (${userMd.rung}): ${safePath}`
|
|
204
342
|
: safeDiagnostic;
|
|
343
|
+
// #2275: report SCM availability even on read-only alignment (shallow; no network).
|
|
344
|
+
const scm = resolveSessionScmReadiness(options, false);
|
|
205
345
|
lines.push(READ_ONLY_ALIGNMENT_MESSAGE);
|
|
206
346
|
lines.push(userMdLine);
|
|
207
347
|
lines.push(formatEnvironmentContext(environment));
|
|
348
|
+
lines.push(...formatScmReadinessLines(scm));
|
|
208
349
|
const resultPayload = {
|
|
209
350
|
ready: true,
|
|
210
351
|
exit_code: 0,
|
|
@@ -225,16 +366,183 @@ function runReadOnlySessionStart(projectRoot, options, instant, environment) {
|
|
|
225
366
|
diagnostic: userMd.diagnostic,
|
|
226
367
|
},
|
|
227
368
|
environment: environmentContextToDict(environment),
|
|
369
|
+
scm: scmReadinessToDict(scm),
|
|
228
370
|
message: READ_ONLY_RESULT_MESSAGE,
|
|
229
371
|
};
|
|
230
372
|
return { code: 0, payload: resultPayload, lines };
|
|
231
373
|
}
|
|
374
|
+
function runSessionRearm(projectRoot, options, instant, environment) {
|
|
375
|
+
const overallStarted = performance.now();
|
|
376
|
+
const runGit = options.runGit ?? defaultGitRunner;
|
|
377
|
+
const eligibility = assessRearmEligibility(projectRoot, { runGit });
|
|
378
|
+
if (!eligibility.eligible) {
|
|
379
|
+
const coldCmd = formatSessionStartRecoveryCommand("cold");
|
|
380
|
+
const message = `${REARM_INELIGIBLE_PREFIX} ${eligibility.reason}. Run \`${coldCmd}\`.`;
|
|
381
|
+
// #2994: still record failed attempt duration for process-cost rollups.
|
|
382
|
+
emitSessionStartProcessCost({
|
|
383
|
+
ceremonyTier: REARM_CEREMONY_TIER,
|
|
384
|
+
durationMs: elapsedMs(overallStarted),
|
|
385
|
+
exitCode: 1,
|
|
386
|
+
ready: false,
|
|
387
|
+
optionalNetwork: false,
|
|
388
|
+
}, { projectRoot });
|
|
389
|
+
return {
|
|
390
|
+
code: 1,
|
|
391
|
+
payload: {
|
|
392
|
+
ready: false,
|
|
393
|
+
exit_code: 1,
|
|
394
|
+
ceremony_tier: REARM_CEREMONY_TIER,
|
|
395
|
+
rearm_eligible: false,
|
|
396
|
+
environment: environmentContextToDict(environment),
|
|
397
|
+
message,
|
|
398
|
+
},
|
|
399
|
+
lines: [formatEnvironmentContext(environment), message],
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
const resolveUserMd = options.resolveUserMd ?? ((root) => resolveUserMdPath({ projectRoot: root }));
|
|
403
|
+
const userMd = resolveUserMd(projectRoot);
|
|
404
|
+
const safePath = userMd.path.replace(/\r?\n/g, " ");
|
|
405
|
+
const safeDiagnostic = userMd.diagnostic.replace(/\r?\n/g, " ");
|
|
406
|
+
const userMdLine = userMd.found
|
|
407
|
+
? `USER.md resolved (${userMd.rung}): ${safePath}`
|
|
408
|
+
: safeDiagnostic;
|
|
409
|
+
const alignmentMessage = `${READ_ONLY_ALIGNMENT_MESSAGE} ${userMdLine}`;
|
|
410
|
+
// #2275: re-arm still reports SCM state (shallow; no network).
|
|
411
|
+
const scm = resolveSessionScmReadiness(options, false);
|
|
412
|
+
const lines = [
|
|
413
|
+
READ_ONLY_ALIGNMENT_MESSAGE,
|
|
414
|
+
userMdLine,
|
|
415
|
+
formatEnvironmentContext(environment),
|
|
416
|
+
...formatScmReadinessLines(scm),
|
|
417
|
+
REARM_SKIPPED_FAT_PATH_MESSAGE,
|
|
418
|
+
];
|
|
419
|
+
// Light branch-policy disclosure (local only) so re-arm still surfaces policy state.
|
|
420
|
+
const policyResult = resolvePolicy(projectRoot);
|
|
421
|
+
const policyMessage = disclosureLine(policyResult);
|
|
422
|
+
lines.push(policyMessage);
|
|
423
|
+
const humanMerge = resolveHumanMergePolicy(projectRoot);
|
|
424
|
+
const humanMergeLine = humanMergeDisclosureLine(humanMerge);
|
|
425
|
+
if (humanMergeLine !== null) {
|
|
426
|
+
lines.push(humanMergeLine);
|
|
427
|
+
}
|
|
428
|
+
const priorQuick = eligibility.state.quickSteps;
|
|
429
|
+
const priorTriage = priorQuick.triage_welcome ?? ritualStep({ ok: true, ts: instant });
|
|
430
|
+
const policyOk = policyResult.error === null || policyResult.source === "default-fail-closed";
|
|
431
|
+
const quickSteps = {
|
|
432
|
+
alignment: ritualStep({
|
|
433
|
+
ok: true,
|
|
434
|
+
ts: instant,
|
|
435
|
+
message: alignmentMessage,
|
|
436
|
+
durationMs: 0,
|
|
437
|
+
}),
|
|
438
|
+
branch_policy: ritualStep({
|
|
439
|
+
ok: policyOk,
|
|
440
|
+
ts: instant,
|
|
441
|
+
message: policyMessage,
|
|
442
|
+
exitCode: policyOk ? 0 : 2,
|
|
443
|
+
durationMs: 0,
|
|
444
|
+
}),
|
|
445
|
+
// Preserve prior triage outcome; do not re-run welcome / self-heal on re-arm.
|
|
446
|
+
triage_welcome: {
|
|
447
|
+
...priorTriage,
|
|
448
|
+
ts: ritualStep({ ok: priorTriage.ok === true, ts: instant }).ts,
|
|
449
|
+
message: typeof priorTriage.message === "string"
|
|
450
|
+
? priorTriage.message
|
|
451
|
+
: "triage welcome preserved on re-arm",
|
|
452
|
+
},
|
|
453
|
+
};
|
|
454
|
+
const gatedSteps = restampSteps(eligibility.state.gatedSteps, instant);
|
|
455
|
+
const writeStarted = performance.now();
|
|
456
|
+
// Fresh payload (no rearm_needed / compact_resume_at) clears compact markers (#2992).
|
|
457
|
+
const writePayload = {
|
|
458
|
+
...newRitualStatePayload({
|
|
459
|
+
sessionId: (options.newSessionId ?? randomUUID)(),
|
|
460
|
+
gitHead: eligibility.currentHead,
|
|
461
|
+
worktreePath: eligibility.currentWorktree,
|
|
462
|
+
startedAt: instant,
|
|
463
|
+
quickSteps,
|
|
464
|
+
gatedSteps,
|
|
465
|
+
}),
|
|
466
|
+
ceremony_tier: REARM_CEREMONY_TIER,
|
|
467
|
+
};
|
|
468
|
+
let statePath;
|
|
469
|
+
try {
|
|
470
|
+
statePath = writeRitualState(projectRoot, writePayload);
|
|
471
|
+
}
|
|
472
|
+
catch (cause) {
|
|
473
|
+
// #2994: still record failed attempt when ritual-state write throws.
|
|
474
|
+
emitSessionStartProcessCost({
|
|
475
|
+
ceremonyTier: REARM_CEREMONY_TIER,
|
|
476
|
+
durationMs: elapsedMs(overallStarted),
|
|
477
|
+
exitCode: 2,
|
|
478
|
+
ready: false,
|
|
479
|
+
optionalNetwork: false,
|
|
480
|
+
steps: [
|
|
481
|
+
{ name: "alignment", duration_ms: 0 },
|
|
482
|
+
{ name: "branch_policy", duration_ms: 0 },
|
|
483
|
+
{ name: "verify_tools", duration_ms: 0, skipped: true },
|
|
484
|
+
{ name: "triage_welcome", duration_ms: 0, skipped: true },
|
|
485
|
+
{ name: "release_probe", duration_ms: 0, skipped: true },
|
|
486
|
+
{ name: "ritual_write", duration_ms: elapsedMs(writeStarted) },
|
|
487
|
+
],
|
|
488
|
+
}, { projectRoot });
|
|
489
|
+
throw cause;
|
|
490
|
+
}
|
|
491
|
+
const stepTimings = [
|
|
492
|
+
{ name: "alignment", duration_ms: 0 },
|
|
493
|
+
{ name: "branch_policy", duration_ms: 0 },
|
|
494
|
+
{ name: "verify_tools", duration_ms: 0, skipped: true },
|
|
495
|
+
{ name: "triage_welcome", duration_ms: 0, skipped: true },
|
|
496
|
+
{ name: "release_probe", duration_ms: 0, skipped: true },
|
|
497
|
+
{ name: "ritual_write", duration_ms: elapsedMs(writeStarted) },
|
|
498
|
+
];
|
|
499
|
+
const totalMs = elapsedMs(overallStarted);
|
|
500
|
+
const failed = Object.entries(quickSteps)
|
|
501
|
+
.filter(([, step]) => !step.ok && !step.deferred_reason)
|
|
502
|
+
.map(([name]) => name);
|
|
503
|
+
const code = failed.length > 0 ? 1 : 0;
|
|
504
|
+
// #2994: local process-cost event (best-effort; never blocks ceremony).
|
|
505
|
+
emitSessionStartProcessCost({
|
|
506
|
+
ceremonyTier: REARM_CEREMONY_TIER,
|
|
507
|
+
durationMs: totalMs,
|
|
508
|
+
exitCode: code,
|
|
509
|
+
ready: code === 0,
|
|
510
|
+
optionalNetwork: false,
|
|
511
|
+
steps: stepTimings,
|
|
512
|
+
}, { projectRoot });
|
|
513
|
+
return {
|
|
514
|
+
code,
|
|
515
|
+
payload: {
|
|
516
|
+
ready: code === 0,
|
|
517
|
+
exit_code: code,
|
|
518
|
+
ceremony_tier: REARM_CEREMONY_TIER,
|
|
519
|
+
rearm_eligible: true,
|
|
520
|
+
state_path: statePath,
|
|
521
|
+
quick_steps: quickSteps,
|
|
522
|
+
gated_steps: gatedSteps,
|
|
523
|
+
steps: stepTimings,
|
|
524
|
+
duration_ms: totalMs,
|
|
525
|
+
optional_network: false,
|
|
526
|
+
user_md: {
|
|
527
|
+
path: userMd.path,
|
|
528
|
+
rung: userMd.rung,
|
|
529
|
+
found: userMd.found,
|
|
530
|
+
diagnostic: userMd.diagnostic,
|
|
531
|
+
},
|
|
532
|
+
environment: environmentContextToDict(environment),
|
|
533
|
+
scm: scmReadinessToDict(scm),
|
|
534
|
+
message: code === 0 ? "session ritual re-armed" : "session ritual re-arm failed",
|
|
535
|
+
},
|
|
536
|
+
lines,
|
|
537
|
+
};
|
|
538
|
+
}
|
|
232
539
|
export function runSessionStart(projectRoot, options = {}) {
|
|
233
540
|
const posture = options.posture ?? MUTATION_POSTURE;
|
|
234
541
|
const instant = options.now ?? new Date();
|
|
235
542
|
const deferrals = options.deferrals ?? {};
|
|
236
543
|
const runGit = options.runGit ?? defaultGitRunner;
|
|
237
544
|
const environment = (options.probeEnvironment ?? detectEnvironmentContext)();
|
|
545
|
+
const ceremonyTier = options.ceremonyTier ?? COLD_CEREMONY_TIER;
|
|
238
546
|
// #2926: official root opt-out wins locally — skip Directive session ritual.
|
|
239
547
|
// disabled = skip ritual (exit 0 clean / 1 inconsistent). ready stays false so
|
|
240
548
|
// automation does not treat opt-out as "session fully initialized for work".
|
|
@@ -267,13 +575,30 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
267
575
|
if (posture === READ_ONLY_POSTURE) {
|
|
268
576
|
return runReadOnlySessionStart(projectRoot, options, instant, environment);
|
|
269
577
|
}
|
|
578
|
+
// #2992: re-arm path refreshes clock/bind without fat cold ceremony.
|
|
579
|
+
if (ceremonyTier === REARM_CEREMONY_TIER) {
|
|
580
|
+
return runSessionRearm(projectRoot, options, instant, environment);
|
|
581
|
+
}
|
|
582
|
+
const overallStarted = performance.now();
|
|
583
|
+
const stepTimings = [];
|
|
584
|
+
const allowOptionalNetwork = resolveSessionStartOptionalNetwork(options);
|
|
270
585
|
const { head: gitHeadValue, error: gitError } = gitHead(projectRoot, runGit);
|
|
271
586
|
if (gitHeadValue === null) {
|
|
272
587
|
const payload = {
|
|
273
588
|
ready: false,
|
|
589
|
+
exit_code: 2,
|
|
590
|
+
ceremony_tier: COLD_CEREMONY_TIER,
|
|
274
591
|
environment: environmentContextToDict(environment),
|
|
275
592
|
message: gitError ?? "could not resolve git HEAD",
|
|
276
593
|
};
|
|
594
|
+
// #2994: still record failed attempt duration for process-cost rollups.
|
|
595
|
+
emitSessionStartProcessCost({
|
|
596
|
+
ceremonyTier: COLD_CEREMONY_TIER,
|
|
597
|
+
durationMs: elapsedMs(overallStarted),
|
|
598
|
+
exitCode: 2,
|
|
599
|
+
ready: false,
|
|
600
|
+
optionalNetwork: allowOptionalNetwork,
|
|
601
|
+
}, { projectRoot });
|
|
277
602
|
return {
|
|
278
603
|
code: 2,
|
|
279
604
|
payload,
|
|
@@ -290,6 +615,7 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
290
615
|
const resolveUserMd = options.resolveUserMd ?? ((root) => resolveUserMdPath({ projectRoot: root }));
|
|
291
616
|
const userMd = resolveUserMd(projectRoot);
|
|
292
617
|
if (!quickSteps.alignment) {
|
|
618
|
+
const stepStarted = performance.now();
|
|
293
619
|
const message = "Deft Directive active -- AGENTS.md loaded.";
|
|
294
620
|
// Sanitize newlines on the data-derived path/diagnostic before they land in
|
|
295
621
|
// the ritual-step message / terminal output (matches doctor's CWE-116
|
|
@@ -300,25 +626,32 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
300
626
|
const userMdLine = userMd.found
|
|
301
627
|
? `USER.md resolved (${userMd.rung}): ${safePath}`
|
|
302
628
|
: safeDiagnostic;
|
|
629
|
+
const durationMs = elapsedMs(stepStarted);
|
|
303
630
|
quickSteps.alignment = ritualStep({
|
|
304
631
|
ok: true,
|
|
305
632
|
ts: instant,
|
|
306
633
|
message: `${message} ${userMdLine}`,
|
|
634
|
+
durationMs,
|
|
307
635
|
});
|
|
636
|
+
stepTimings.push({ name: "alignment", duration_ms: durationMs });
|
|
308
637
|
lines.push(message);
|
|
309
638
|
lines.push(userMdLine);
|
|
310
639
|
}
|
|
640
|
+
else {
|
|
641
|
+
stepTimings.push({ name: "alignment", duration_ms: 0, skipped: true });
|
|
642
|
+
}
|
|
311
643
|
lines.push(formatEnvironmentContext(environment));
|
|
644
|
+
// #2275: SCM tooling + auth readiness — shallow on hot path, deep with --with-network.
|
|
645
|
+
// Never fails session:start; reports which SCM-dependent gates are skipped.
|
|
646
|
+
const scmStepStarted = performance.now();
|
|
647
|
+
const scm = resolveSessionScmReadiness(options, allowOptionalNetwork);
|
|
648
|
+
lines.push(...formatScmReadinessLines(scm));
|
|
649
|
+
stepTimings.push({ name: "scm_readiness", duration_ms: elapsedMs(scmStepStarted) });
|
|
312
650
|
if (!quickSteps.branch_policy) {
|
|
651
|
+
const stepStarted = performance.now();
|
|
313
652
|
const result = resolvePolicy(projectRoot);
|
|
314
653
|
const message = disclosureLine(result);
|
|
315
654
|
const ok = result.error === null || result.source === "default-fail-closed";
|
|
316
|
-
quickSteps.branch_policy = ritualStep({
|
|
317
|
-
ok,
|
|
318
|
-
ts: instant,
|
|
319
|
-
message,
|
|
320
|
-
exitCode: ok ? 0 : 2,
|
|
321
|
-
});
|
|
322
655
|
lines.push(message);
|
|
323
656
|
// Human merge gate disclosure (#1193) — surface when ON (or env bypass active).
|
|
324
657
|
const humanMerge = resolveHumanMergePolicy(projectRoot);
|
|
@@ -330,18 +663,35 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
330
663
|
if (branchSync.warning) {
|
|
331
664
|
lines.push(branchSync.warning);
|
|
332
665
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
}
|
|
341
|
-
return { exitCode: result.exitCode };
|
|
666
|
+
const durationMs = elapsedMs(stepStarted);
|
|
667
|
+
quickSteps.branch_policy = ritualStep({
|
|
668
|
+
ok,
|
|
669
|
+
ts: instant,
|
|
670
|
+
message,
|
|
671
|
+
exitCode: ok ? 0 : 2,
|
|
672
|
+
durationMs,
|
|
342
673
|
});
|
|
343
|
-
|
|
674
|
+
stepTimings.push({ name: "branch_policy", duration_ms: durationMs });
|
|
675
|
+
}
|
|
676
|
+
else {
|
|
677
|
+
stepTimings.push({ name: "branch_policy", duration_ms: 0, skipped: true });
|
|
678
|
+
}
|
|
679
|
+
{
|
|
680
|
+
const stepStarted = performance.now();
|
|
681
|
+
const verifyToolsFn = options.verifyTools ??
|
|
682
|
+
((output) => {
|
|
683
|
+
const toolLines = [];
|
|
684
|
+
const result = verifyRequiredTools({ outputFn: (line) => toolLines.push(line) });
|
|
685
|
+
for (const line of toolLines) {
|
|
686
|
+
output(line);
|
|
687
|
+
}
|
|
688
|
+
return { exitCode: result.exitCode };
|
|
689
|
+
});
|
|
690
|
+
verifyToolsFn((line) => lines.push(line));
|
|
691
|
+
stepTimings.push({ name: "verify_tools", duration_ms: elapsedMs(stepStarted) });
|
|
692
|
+
}
|
|
344
693
|
if (!quickSteps.triage_welcome) {
|
|
694
|
+
const stepStarted = performance.now();
|
|
345
695
|
const captured = [];
|
|
346
696
|
const triageCommand = ["triage_welcome.run_default_mode", "--project-root", projectRoot];
|
|
347
697
|
try {
|
|
@@ -350,6 +700,13 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
350
700
|
const outcome = runDefaultMode(root, {
|
|
351
701
|
output: welcomeOpts.output,
|
|
352
702
|
writeHistory: welcomeOpts.writeHistory,
|
|
703
|
+
now: welcomeOpts.now,
|
|
704
|
+
// #2991: default hot path skips ensureTriageCacheHydrated / maybeSelfHealCache.
|
|
705
|
+
selfHealFn: allowOptionalNetwork
|
|
706
|
+
? undefined
|
|
707
|
+
: () => {
|
|
708
|
+
/* no network cache work on hot path */
|
|
709
|
+
},
|
|
353
710
|
});
|
|
354
711
|
return { exitCode: outcome.exitCode };
|
|
355
712
|
});
|
|
@@ -360,33 +717,51 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
360
717
|
});
|
|
361
718
|
const ok = outcome.exitCode === 0;
|
|
362
719
|
const message = captured.join("\n").trim() || "triage welcome completed";
|
|
720
|
+
const durationMs = elapsedMs(stepStarted);
|
|
363
721
|
quickSteps.triage_welcome = ritualStep({
|
|
364
722
|
ok,
|
|
365
723
|
ts: instant,
|
|
366
724
|
message,
|
|
367
725
|
exitCode: outcome.exitCode,
|
|
368
726
|
command: triageCommand,
|
|
727
|
+
durationMs,
|
|
369
728
|
});
|
|
729
|
+
stepTimings.push({ name: "triage_welcome", duration_ms: durationMs });
|
|
370
730
|
lines.push(...captured);
|
|
371
731
|
}
|
|
372
732
|
catch (exc) {
|
|
373
733
|
const message = `triage welcome failed: ${String(exc)}`;
|
|
734
|
+
const durationMs = elapsedMs(stepStarted);
|
|
374
735
|
quickSteps.triage_welcome = ritualStep({
|
|
375
736
|
ok: false,
|
|
376
737
|
ts: instant,
|
|
377
738
|
message,
|
|
378
739
|
exitCode: 2,
|
|
379
740
|
command: triageCommand,
|
|
741
|
+
durationMs,
|
|
380
742
|
});
|
|
743
|
+
stepTimings.push({ name: "triage_welcome", duration_ms: durationMs });
|
|
381
744
|
lines.push(message);
|
|
382
745
|
}
|
|
383
746
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
lines.push(...releaseAvailability.lines);
|
|
747
|
+
else {
|
|
748
|
+
stepTimings.push({ name: "triage_welcome", duration_ms: 0, skipped: true });
|
|
387
749
|
}
|
|
388
|
-
|
|
389
|
-
|
|
750
|
+
// #2991: npm release probe is optional network — off by default so ritual write is not blocked.
|
|
751
|
+
if (allowOptionalNetwork) {
|
|
752
|
+
const stepStarted = performance.now();
|
|
753
|
+
try {
|
|
754
|
+
const releaseAvailability = (options.probeReleaseAvailability ?? probeSessionReleaseAvailability)(projectRoot, { now: instant });
|
|
755
|
+
lines.push(...releaseAvailability.lines);
|
|
756
|
+
}
|
|
757
|
+
catch {
|
|
758
|
+
// Release availability is a best-effort operator advisory, never a session blocker.
|
|
759
|
+
}
|
|
760
|
+
stepTimings.push({ name: "release_probe", duration_ms: elapsedMs(stepStarted) });
|
|
761
|
+
}
|
|
762
|
+
else {
|
|
763
|
+
stepTimings.push({ name: "release_probe", duration_ms: 0, skipped: true });
|
|
764
|
+
lines.push(OPTIONAL_NETWORK_SKIPPED_MESSAGE);
|
|
390
765
|
}
|
|
391
766
|
try {
|
|
392
767
|
const tickler = (options.runStalenessTickler ?? maybeRunStalenessTickler)(projectRoot, {
|
|
@@ -422,6 +797,7 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
422
797
|
if (consentPrompt.length > 0) {
|
|
423
798
|
lines.push(consentPrompt.trimEnd());
|
|
424
799
|
}
|
|
800
|
+
const writeStarted = performance.now();
|
|
425
801
|
const payload = newRitualStatePayload({
|
|
426
802
|
sessionId: (options.newSessionId ?? randomUUID)(),
|
|
427
803
|
gitHead: gitHeadValue,
|
|
@@ -430,17 +806,39 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
430
806
|
quickSteps,
|
|
431
807
|
gatedSteps,
|
|
432
808
|
});
|
|
433
|
-
|
|
809
|
+
let statePath;
|
|
810
|
+
try {
|
|
811
|
+
statePath = writeRitualState(projectRoot, payload);
|
|
812
|
+
}
|
|
813
|
+
catch (cause) {
|
|
814
|
+
// #2994: still record failed attempt when ritual-state write throws.
|
|
815
|
+
stepTimings.push({ name: "ritual_write", duration_ms: elapsedMs(writeStarted) });
|
|
816
|
+
emitSessionStartProcessCost({
|
|
817
|
+
ceremonyTier: COLD_CEREMONY_TIER,
|
|
818
|
+
durationMs: elapsedMs(overallStarted),
|
|
819
|
+
exitCode: 2,
|
|
820
|
+
ready: false,
|
|
821
|
+
optionalNetwork: allowOptionalNetwork,
|
|
822
|
+
steps: stepTimings,
|
|
823
|
+
}, { projectRoot });
|
|
824
|
+
throw cause;
|
|
825
|
+
}
|
|
826
|
+
stepTimings.push({ name: "ritual_write", duration_ms: elapsedMs(writeStarted) });
|
|
434
827
|
const failed = Object.entries(quickSteps)
|
|
435
828
|
.filter(([, step]) => !step.ok && !step.deferred_reason)
|
|
436
829
|
.map(([name]) => name);
|
|
437
830
|
const code = failed.length > 0 ? 1 : 0;
|
|
831
|
+
const totalMs = elapsedMs(overallStarted);
|
|
438
832
|
const resultPayload = {
|
|
439
833
|
ready: code === 0,
|
|
440
834
|
exit_code: code,
|
|
835
|
+
ceremony_tier: COLD_CEREMONY_TIER,
|
|
441
836
|
state_path: statePath,
|
|
442
837
|
quick_steps: quickSteps,
|
|
443
838
|
gated_steps: gatedSteps,
|
|
839
|
+
steps: stepTimings,
|
|
840
|
+
duration_ms: totalMs,
|
|
841
|
+
optional_network: allowOptionalNetwork,
|
|
444
842
|
user_md: {
|
|
445
843
|
path: userMd.path,
|
|
446
844
|
rung: userMd.rung,
|
|
@@ -448,8 +846,18 @@ export function runSessionStart(projectRoot, options = {}) {
|
|
|
448
846
|
diagnostic: userMd.diagnostic,
|
|
449
847
|
},
|
|
450
848
|
environment: environmentContextToDict(environment),
|
|
849
|
+
scm: scmReadinessToDict(scm),
|
|
451
850
|
message: code === 0 ? "session ritual recorded" : "session ritual failed",
|
|
452
851
|
};
|
|
852
|
+
// #2994: local process-cost event (best-effort; never blocks ceremony).
|
|
853
|
+
emitSessionStartProcessCost({
|
|
854
|
+
ceremonyTier: COLD_CEREMONY_TIER,
|
|
855
|
+
durationMs: totalMs,
|
|
856
|
+
exitCode: code,
|
|
857
|
+
ready: code === 0,
|
|
858
|
+
optionalNetwork: allowOptionalNetwork,
|
|
859
|
+
steps: stepTimings,
|
|
860
|
+
}, { projectRoot });
|
|
453
861
|
return { code, payload: resultPayload, lines };
|
|
454
862
|
}
|
|
455
863
|
export { ritualStatePath };
|
|
@@ -1,5 +1,6 @@
|
|
|
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
4
|
export { formatCacheFetchAllRecoveryCommand, recoveryHintForStaleFailure, } from "./cache-recovery.js";
|
|
4
5
|
export declare const ENV_SKIP = "DEFT_SESSION_RITUAL_SKIP";
|
|
5
6
|
export { ENTRYPOINT_TIMEOUT_EXIT_CODE, ENTRYPOINT_TIMEOUT_SECONDS, } from "./ritual-entrypoint.js";
|
|
@@ -13,12 +14,19 @@ export interface VerifyResult {
|
|
|
13
14
|
readonly wouldFailCode: number | null;
|
|
14
15
|
readonly posture: DirectivePosture;
|
|
15
16
|
readonly ritualStateRequired: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Preferred session:start recovery when code !== 0 (#2992).
|
|
19
|
+
* `rearm` when age/compact stale on an otherwise valid bind; `cold` otherwise.
|
|
20
|
+
*/
|
|
21
|
+
readonly recoveryTier?: SessionCeremonyTier | null;
|
|
16
22
|
}
|
|
17
23
|
export type RitualRunner = (command: readonly string[], projectRoot: string) => {
|
|
18
24
|
code: number;
|
|
19
25
|
stdout: string;
|
|
20
26
|
stderr: string;
|
|
21
27
|
};
|
|
28
|
+
/** Human recovery line after a failed ritual probe (#2992 / #2993). */
|
|
29
|
+
export declare function formatRitualRecoveryInstruction(tier?: SessionCeremonyTier): string;
|
|
22
30
|
export interface VerifySessionRitualOptions {
|
|
23
31
|
readonly tier?: "quick" | "gated";
|
|
24
32
|
readonly now?: Date;
|