@deftai/directive-core 0.75.0 → 0.77.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/agents-md-budget/evaluate.d.ts +8 -1
- package/dist/agents-md-budget/evaluate.js +77 -22
- package/dist/agents-md-budget/skill-frontmatter.d.ts +2 -0
- package/dist/agents-md-budget/skill-frontmatter.js +4 -3
- package/dist/cache/operations.d.ts +2 -0
- package/dist/cache/operations.js +16 -1
- package/dist/content-contracts/skills/skill-frontmatter.js +4 -0
- package/dist/doctor/constants.js +3 -3
- package/dist/doctor/index.d.ts +1 -0
- package/dist/doctor/index.js +1 -0
- package/dist/doctor/main.js +2 -1
- package/dist/doctor/payload-staleness.js +62 -63
- package/dist/doctor/release-availability.d.ts +28 -0
- package/dist/doctor/release-availability.js +35 -0
- package/dist/eval/crud-telemetry.d.ts +5 -4
- package/dist/eval/crud-telemetry.js +9 -5
- package/dist/eval/health.d.ts +5 -4
- package/dist/eval/health.js +24 -16
- package/dist/eval/readback.js +2 -8
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/init-deposit/scaffold.js +28 -17
- package/dist/intake/issue-ingest.js +23 -4
- package/dist/metrics/index.d.ts +2 -0
- package/dist/metrics/index.js +2 -0
- package/dist/metrics/resolve-metrics-home.d.ts +50 -0
- package/dist/metrics/resolve-metrics-home.js +125 -0
- package/dist/packaging/openpackage-tiers.d.ts +12 -0
- package/dist/packaging/openpackage-tiers.js +42 -0
- package/dist/plan-sequence/index.d.ts +3 -0
- package/dist/plan-sequence/index.js +3 -0
- package/dist/plan-sequence/store.d.ts +6 -0
- package/dist/plan-sequence/store.js +29 -0
- package/dist/plan-sequence/types.d.ts +79 -0
- package/dist/plan-sequence/types.js +220 -0
- package/dist/preflight/evaluate.d.ts +5 -2
- package/dist/preflight/evaluate.js +9 -4
- package/dist/preflight/index.d.ts +1 -1
- package/dist/preflight/index.js +1 -1
- package/dist/release/pipeline-fixture.d.ts +3 -0
- package/dist/release/pipeline-fixture.js +11 -0
- package/dist/release/pipeline.js +4 -0
- package/dist/release/version.d.ts +2 -0
- package/dist/release/version.js +42 -0
- package/dist/release-e2e/npm-ops.js +20 -6
- package/dist/scope/transition.js +17 -2
- package/dist/scope/vbrief-ref.d.ts +6 -0
- package/dist/scope/vbrief-ref.js +21 -2
- package/dist/session/index.d.ts +1 -0
- package/dist/session/index.js +1 -0
- package/dist/session/posture.d.ts +50 -0
- package/dist/session/posture.js +152 -0
- package/dist/session/ritual-sentinel.d.ts +2 -0
- package/dist/session/ritual-sentinel.js +3 -0
- package/dist/session/session-start.d.ts +8 -0
- package/dist/session/session-start.js +43 -0
- package/dist/session/verify-session-ritual.d.ts +6 -0
- package/dist/session/verify-session-ritual.js +67 -4
- package/dist/triage/bootstrap/gitignore.js +8 -4
- package/dist/triage/cache-path.js +16 -3
- package/dist/triage/welcome/writers.js +2 -0
- package/dist/ts-check-lane/index.d.ts +1 -1
- package/dist/ts-check-lane/index.js +1 -1
- package/dist/ts-check-lane/run-lane.d.ts +5 -0
- package/dist/ts-check-lane/run-lane.js +15 -0
- package/dist/vbrief-build/constants.d.ts +3 -3
- package/dist/vbrief-build/constants.js +4 -3
- package/dist/vbrief-build/index.d.ts +1 -1
- package/dist/vbrief-build/index.js +1 -1
- package/dist/verify-env/command-spawn.d.ts +24 -0
- package/dist/verify-env/command-spawn.js +78 -0
- package/dist/verify-env/index.d.ts +1 -0
- package/dist/verify-env/index.js +1 -0
- package/package.json +7 -3
package/dist/session/index.js
CHANGED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ephemeral session posture (#2180).
|
|
3
|
+
*
|
|
4
|
+
* Conversational posture is agent-context state — not authority derived from
|
|
5
|
+
* `.deft/ritual-state.json`. Fresh or cleared contexts default to read-only;
|
|
6
|
+
* gates run fresh only at mutation boundaries.
|
|
7
|
+
*
|
|
8
|
+
* Vocabulary aligned with #2176 (`SessionPosture`: `read-only` | `mutation`).
|
|
9
|
+
*/
|
|
10
|
+
/** Live agent posture — never persisted as repo authority. */
|
|
11
|
+
export type DirectivePosture = "read-only" | "mutation";
|
|
12
|
+
/** Default for fresh or manually cleared contexts (#2180). */
|
|
13
|
+
export declare const DEFAULT_POSTURE: DirectivePosture;
|
|
14
|
+
/** Env override for deterministic gates / CLI (`read-only` | `mutation`). */
|
|
15
|
+
export declare const ENV_SESSION_POSTURE = "DEFT_SESSION_POSTURE";
|
|
16
|
+
/**
|
|
17
|
+
* Ritual-state contract (#2180 / #1348 narrowed):
|
|
18
|
+
* diagnostic evidence only — not proof of user intent or mutation posture.
|
|
19
|
+
*/
|
|
20
|
+
export declare const RITUAL_STATE_CONTRACT: "diagnostic-only";
|
|
21
|
+
/** Action-verb directives that establish mutation intent (#810 / #2180). */
|
|
22
|
+
export declare const MUTATION_INTENT_VERBS: readonly ["build", "implement", "ship", "swarm", "run agents", "start agent", "start_agent", "edit", "commit", "push", "open pr", "release", "scope:promote", "scope:activate", "scope:complete", "drive-to"];
|
|
23
|
+
export type MutationIntentVerb = (typeof MUTATION_INTENT_VERBS)[number];
|
|
24
|
+
export interface StructuredHandoff {
|
|
25
|
+
readonly posture: DirectivePosture;
|
|
26
|
+
readonly source: "plan" | "compaction" | "dispatch" | "allocation-context";
|
|
27
|
+
readonly mutationIntent: boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface ResolvePostureInput {
|
|
30
|
+
readonly envPosture?: string | undefined;
|
|
31
|
+
readonly handoffText?: string | null;
|
|
32
|
+
readonly explicitPosture?: DirectivePosture | null;
|
|
33
|
+
/** Gated tier implies a mutation boundary when posture is otherwise unset. */
|
|
34
|
+
readonly tier?: "quick" | "gated";
|
|
35
|
+
}
|
|
36
|
+
/** Ritual-state must never be treated as posture authority (#2180). */
|
|
37
|
+
export declare function ritualStateIsPostureAuthority(): boolean;
|
|
38
|
+
/** Detect explicit mutation intent from free-form operator or dispatch text. */
|
|
39
|
+
export declare function detectMutationIntent(text: string): boolean;
|
|
40
|
+
/** Parse structured handoff markers from plan / compaction / dispatch envelopes. */
|
|
41
|
+
export declare function parseStructuredHandoff(text: string | null | undefined): StructuredHandoff | null;
|
|
42
|
+
/**
|
|
43
|
+
* Resolve ephemeral posture for the current agent context.
|
|
44
|
+
* Precedence: explicit > env > structured handoff > tier default > read-only.
|
|
45
|
+
* Never reads `.deft/ritual-state.json`.
|
|
46
|
+
*/
|
|
47
|
+
export declare function resolveSessionPosture(input?: ResolvePostureInput): DirectivePosture;
|
|
48
|
+
/** Message when read-only posture skips ritual-state authority checks. */
|
|
49
|
+
export declare function readOnlyPostureMessage(tier: string): string;
|
|
50
|
+
//# sourceMappingURL=posture.d.ts.map
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ephemeral session posture (#2180).
|
|
3
|
+
*
|
|
4
|
+
* Conversational posture is agent-context state — not authority derived from
|
|
5
|
+
* `.deft/ritual-state.json`. Fresh or cleared contexts default to read-only;
|
|
6
|
+
* gates run fresh only at mutation boundaries.
|
|
7
|
+
*
|
|
8
|
+
* Vocabulary aligned with #2176 (`SessionPosture`: `read-only` | `mutation`).
|
|
9
|
+
*/
|
|
10
|
+
/** Default for fresh or manually cleared contexts (#2180). */
|
|
11
|
+
export const DEFAULT_POSTURE = "read-only";
|
|
12
|
+
/** Env override for deterministic gates / CLI (`read-only` | `mutation`). */
|
|
13
|
+
export const ENV_SESSION_POSTURE = "DEFT_SESSION_POSTURE";
|
|
14
|
+
/**
|
|
15
|
+
* Ritual-state contract (#2180 / #1348 narrowed):
|
|
16
|
+
* diagnostic evidence only — not proof of user intent or mutation posture.
|
|
17
|
+
*/
|
|
18
|
+
export const RITUAL_STATE_CONTRACT = "diagnostic-only";
|
|
19
|
+
/** Action-verb directives that establish mutation intent (#810 / #2180). */
|
|
20
|
+
export const MUTATION_INTENT_VERBS = [
|
|
21
|
+
"build",
|
|
22
|
+
"implement",
|
|
23
|
+
"ship",
|
|
24
|
+
"swarm",
|
|
25
|
+
"run agents",
|
|
26
|
+
"start agent",
|
|
27
|
+
"start_agent",
|
|
28
|
+
"edit",
|
|
29
|
+
"commit",
|
|
30
|
+
"push",
|
|
31
|
+
"open pr",
|
|
32
|
+
"release",
|
|
33
|
+
"scope:promote",
|
|
34
|
+
"scope:activate",
|
|
35
|
+
"scope:complete",
|
|
36
|
+
"drive-to",
|
|
37
|
+
];
|
|
38
|
+
function normalisePosture(raw) {
|
|
39
|
+
const value = (raw ?? "").trim().toLowerCase();
|
|
40
|
+
if (value === "read-only" || value === "readonly") {
|
|
41
|
+
return "read-only";
|
|
42
|
+
}
|
|
43
|
+
// Accept legacy "mutating" alias from early #2180 drafts.
|
|
44
|
+
if (value === "mutation" || value === "mutating") {
|
|
45
|
+
return "mutation";
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
/** Ritual-state must never be treated as posture authority (#2180). */
|
|
50
|
+
export function ritualStateIsPostureAuthority() {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
/** Detect explicit mutation intent from free-form operator or dispatch text. */
|
|
54
|
+
export function detectMutationIntent(text) {
|
|
55
|
+
const lower = text.toLowerCase();
|
|
56
|
+
for (const verb of MUTATION_INTENT_VERBS) {
|
|
57
|
+
// Require a non-hyphen / non-word char before the verb so hyphenated
|
|
58
|
+
// compounds (e.g. "rebuild") do not false-positive on short verbs.
|
|
59
|
+
const escaped = verb.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
60
|
+
const pattern = new RegExp(`(?:^|[^\\w-])${escaped}(?:$|[^\\w-])`, "i");
|
|
61
|
+
if (pattern.test(lower)) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (/(?:^|[^\w-])drive-to:\s*merge-ready(?:$|[^\w-])/i.test(text)) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
if (/\b(implement|ship)\s+\/\s*(implement|ship)\b/i.test(text)) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
/** Parse structured handoff markers from plan / compaction / dispatch envelopes. */
|
|
74
|
+
export function parseStructuredHandoff(text) {
|
|
75
|
+
if (!text || text.trim().length === 0) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
const dispatchKind = text.match(/dispatch_kind:\s*(swarm-cohort|solo)/i)?.[1]?.toLowerCase();
|
|
79
|
+
if (dispatchKind === "swarm-cohort" || dispatchKind === "solo") {
|
|
80
|
+
const mutationIntent = detectMutationIntent(text);
|
|
81
|
+
return {
|
|
82
|
+
posture: mutationIntent ? "mutation" : "read-only",
|
|
83
|
+
source: "allocation-context",
|
|
84
|
+
mutationIntent,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (/##\s*structured handoff/i.test(text) || /handoff_kind:/i.test(text)) {
|
|
88
|
+
const postureMatch = text
|
|
89
|
+
.match(/posture:\s*(read-only|mutation|mutating)/i)?.[1]
|
|
90
|
+
?.toLowerCase();
|
|
91
|
+
const intentTrue = /mutation_intent:\s*true\b/i.test(text);
|
|
92
|
+
const intentFalse = /mutation_intent:\s*false\b/i.test(text);
|
|
93
|
+
const source = /compaction/i.test(text) ? "compaction" : "plan";
|
|
94
|
+
// Explicit structural flags win over free-form verb heuristics (#2180 SLizard).
|
|
95
|
+
if (postureMatch === "read-only" || intentFalse) {
|
|
96
|
+
return {
|
|
97
|
+
posture: "read-only",
|
|
98
|
+
source,
|
|
99
|
+
mutationIntent: false,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
if (postureMatch === "mutation" || postureMatch === "mutating" || intentTrue) {
|
|
103
|
+
return {
|
|
104
|
+
posture: "mutation",
|
|
105
|
+
source,
|
|
106
|
+
mutationIntent: true,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const mutationIntent = detectMutationIntent(text);
|
|
110
|
+
return {
|
|
111
|
+
posture: mutationIntent ? "mutation" : "read-only",
|
|
112
|
+
source,
|
|
113
|
+
mutationIntent,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
if (detectMutationIntent(text)) {
|
|
117
|
+
return {
|
|
118
|
+
posture: "mutation",
|
|
119
|
+
source: "dispatch",
|
|
120
|
+
mutationIntent: true,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Resolve ephemeral posture for the current agent context.
|
|
127
|
+
* Precedence: explicit > env > structured handoff > tier default > read-only.
|
|
128
|
+
* Never reads `.deft/ritual-state.json`.
|
|
129
|
+
*/
|
|
130
|
+
export function resolveSessionPosture(input = {}) {
|
|
131
|
+
if (input.explicitPosture) {
|
|
132
|
+
return input.explicitPosture;
|
|
133
|
+
}
|
|
134
|
+
const fromEnv = normalisePosture(input.envPosture);
|
|
135
|
+
if (fromEnv) {
|
|
136
|
+
return fromEnv;
|
|
137
|
+
}
|
|
138
|
+
const handoff = parseStructuredHandoff(input.handoffText);
|
|
139
|
+
if (handoff) {
|
|
140
|
+
return handoff.posture;
|
|
141
|
+
}
|
|
142
|
+
if (input.tier === "gated") {
|
|
143
|
+
return "mutation";
|
|
144
|
+
}
|
|
145
|
+
return DEFAULT_POSTURE;
|
|
146
|
+
}
|
|
147
|
+
/** Message when read-only posture skips ritual-state authority checks. */
|
|
148
|
+
export function readOnlyPostureMessage(tier) {
|
|
149
|
+
return (`OK read-only posture — session ritual ${tier} tier not required ` +
|
|
150
|
+
`(ritual-state is diagnostic-only; run \`deft session:start\` at mutation boundaries).`);
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=posture.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export declare const SCHEMA_VERSION = 1;
|
|
2
2
|
export declare const RITUAL_STATE_SCHEMA_VERSION = 1;
|
|
3
|
+
export { RITUAL_STATE_CONTRACT } from "./posture.js";
|
|
3
4
|
export declare const SENTINEL_RELPATH: readonly [".deft", "last-session.json"];
|
|
4
5
|
export declare const RITUAL_STATE_RELPATH: readonly [".deft", "ritual-state.json"];
|
|
5
6
|
export declare const MIN_RESUME_AGE_MS: number;
|
|
@@ -11,6 +12,7 @@ export interface Sentinel {
|
|
|
11
12
|
readonly lastActiveVbrief: string;
|
|
12
13
|
readonly lastBranch: string;
|
|
13
14
|
}
|
|
15
|
+
/** Parsed `.deft/ritual-state.json` — diagnostic gate outcomes only (#2180). */
|
|
14
16
|
export interface RitualState {
|
|
15
17
|
readonly schemaVersion: number;
|
|
16
18
|
readonly sessionId: string;
|
|
@@ -2,9 +2,11 @@ import { closeSync, existsSync, fdatasyncSync, mkdirSync, openSync, readdirSync,
|
|
|
2
2
|
import { join, relative, resolve } from "node:path";
|
|
3
3
|
import { hasArtifactSuffix, LEGACY_ARTIFACT_DIR, MIGRATED_ARTIFACT_DIR, } from "../layout/resolve.js";
|
|
4
4
|
import { stableJson } from "./json.js";
|
|
5
|
+
import { RITUAL_STATE_CONTRACT } from "./posture.js";
|
|
5
6
|
import { parseTimestamp, timestampIso } from "./time.js";
|
|
6
7
|
export const SCHEMA_VERSION = 1;
|
|
7
8
|
export const RITUAL_STATE_SCHEMA_VERSION = 1;
|
|
9
|
+
export { RITUAL_STATE_CONTRACT } from "./posture.js";
|
|
8
10
|
export const SENTINEL_RELPATH = [".deft", "last-session.json"];
|
|
9
11
|
export const RITUAL_STATE_RELPATH = [".deft", "ritual-state.json"];
|
|
10
12
|
export const MIN_RESUME_AGE_MS = 2 * 60 * 60 * 1000;
|
|
@@ -37,6 +39,7 @@ export function ritualStep(input) {
|
|
|
37
39
|
export function newRitualStatePayload(input) {
|
|
38
40
|
return {
|
|
39
41
|
schemaVersion: RITUAL_STATE_SCHEMA_VERSION,
|
|
42
|
+
contract: RITUAL_STATE_CONTRACT,
|
|
40
43
|
session_id: input.sessionId,
|
|
41
44
|
git_head: input.gitHead,
|
|
42
45
|
worktree_path: input.worktreePath,
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { type ResolveUserMdResult } from "../user-config/resolve-user-md.js";
|
|
2
2
|
import type { GitRunner } from "./git.js";
|
|
3
3
|
import { ritualStatePath } from "./ritual-sentinel.js";
|
|
4
|
+
export declare const SESSION_POSTURES: readonly ["read-only", "mutation"];
|
|
5
|
+
export type SessionPosture = (typeof SESSION_POSTURES)[number];
|
|
6
|
+
export declare const READ_ONLY_POSTURE: SessionPosture;
|
|
7
|
+
export declare const MUTATION_POSTURE: SessionPosture;
|
|
8
|
+
export declare const READ_ONLY_ALIGNMENT_MESSAGE = "Deft Directive active -- AGENTS.md loaded.";
|
|
9
|
+
export declare const READ_ONLY_RESULT_MESSAGE = "read-only session posture (alignment only; no ritual-state write)";
|
|
4
10
|
export declare const QUICK_STEPS: readonly ["alignment", "branch_policy", "triage_welcome"];
|
|
5
11
|
export declare const GATED_STEPS: readonly ["doctor", "cache_fresh"];
|
|
6
12
|
export interface DefaultBranchSync {
|
|
@@ -16,6 +22,8 @@ export interface SessionStartResult {
|
|
|
16
22
|
readonly lines: readonly string[];
|
|
17
23
|
}
|
|
18
24
|
export interface SessionStartOptions {
|
|
25
|
+
/** #2176: read-only records alignment only; mutation runs the full quick tier. */
|
|
26
|
+
readonly posture?: SessionPosture;
|
|
19
27
|
readonly deferrals?: Readonly<Record<string, string>>;
|
|
20
28
|
readonly now?: Date;
|
|
21
29
|
readonly writeHistory?: boolean;
|
|
@@ -10,6 +10,11 @@ import { emitSessionValueReadback } from "../value/readback.js";
|
|
|
10
10
|
import { verifyRequiredTools } from "../verify-env/verify-tools.js";
|
|
11
11
|
import { defaultGitRunner, gitHead, worktreePath } from "./git.js";
|
|
12
12
|
import { newRitualStatePayload, ritualStatePath, ritualStep, writeRitualState, } from "./ritual-sentinel.js";
|
|
13
|
+
export const SESSION_POSTURES = ["read-only", "mutation"];
|
|
14
|
+
export const READ_ONLY_POSTURE = "read-only";
|
|
15
|
+
export const MUTATION_POSTURE = "mutation";
|
|
16
|
+
export const READ_ONLY_ALIGNMENT_MESSAGE = "Deft Directive active -- AGENTS.md loaded.";
|
|
17
|
+
export const READ_ONLY_RESULT_MESSAGE = "read-only session posture (alignment only; no ritual-state write)";
|
|
13
18
|
export const QUICK_STEPS = ["alignment", "branch_policy", "triage_welcome"];
|
|
14
19
|
export const GATED_STEPS = ["doctor", "cache_fresh"];
|
|
15
20
|
const STEP_ALIASES = {
|
|
@@ -182,10 +187,48 @@ export function defaultBranchSync(projectRoot, runGit = defaultGitRunner) {
|
|
|
182
187
|
}
|
|
183
188
|
return { branch, upstream, ahead, behind, warning };
|
|
184
189
|
}
|
|
190
|
+
function runReadOnlySessionStart(projectRoot, options, instant) {
|
|
191
|
+
const lines = [];
|
|
192
|
+
const resolveUserMd = options.resolveUserMd ?? ((root) => resolveUserMdPath({ projectRoot: root }));
|
|
193
|
+
const userMd = resolveUserMd(projectRoot);
|
|
194
|
+
const safePath = userMd.path.replace(/\r?\n/g, " ");
|
|
195
|
+
const safeDiagnostic = userMd.diagnostic.replace(/\r?\n/g, " ");
|
|
196
|
+
const userMdLine = userMd.found
|
|
197
|
+
? `USER.md resolved (${userMd.rung}): ${safePath}`
|
|
198
|
+
: safeDiagnostic;
|
|
199
|
+
lines.push(READ_ONLY_ALIGNMENT_MESSAGE);
|
|
200
|
+
lines.push(userMdLine);
|
|
201
|
+
const resultPayload = {
|
|
202
|
+
ready: true,
|
|
203
|
+
exit_code: 0,
|
|
204
|
+
posture: READ_ONLY_POSTURE,
|
|
205
|
+
state_path: null,
|
|
206
|
+
quick_steps: {
|
|
207
|
+
alignment: ritualStep({
|
|
208
|
+
ok: true,
|
|
209
|
+
ts: instant,
|
|
210
|
+
message: `${READ_ONLY_ALIGNMENT_MESSAGE} ${userMdLine}`,
|
|
211
|
+
}),
|
|
212
|
+
},
|
|
213
|
+
gated_steps: {},
|
|
214
|
+
user_md: {
|
|
215
|
+
path: userMd.path,
|
|
216
|
+
rung: userMd.rung,
|
|
217
|
+
found: userMd.found,
|
|
218
|
+
diagnostic: userMd.diagnostic,
|
|
219
|
+
},
|
|
220
|
+
message: READ_ONLY_RESULT_MESSAGE,
|
|
221
|
+
};
|
|
222
|
+
return { code: 0, payload: resultPayload, lines };
|
|
223
|
+
}
|
|
185
224
|
export function runSessionStart(projectRoot, options = {}) {
|
|
225
|
+
const posture = options.posture ?? MUTATION_POSTURE;
|
|
186
226
|
const instant = options.now ?? new Date();
|
|
187
227
|
const deferrals = options.deferrals ?? {};
|
|
188
228
|
const runGit = options.runGit ?? defaultGitRunner;
|
|
229
|
+
if (posture === READ_ONLY_POSTURE) {
|
|
230
|
+
return runReadOnlySessionStart(projectRoot, options, instant);
|
|
231
|
+
}
|
|
189
232
|
const { head: gitHeadValue, error: gitError } = gitHead(projectRoot, runGit);
|
|
190
233
|
if (gitHeadValue === null) {
|
|
191
234
|
const payload = {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type GitRunner } from "./git.js";
|
|
2
|
+
import { type DirectivePosture } from "./posture.js";
|
|
2
3
|
export declare const ENV_SKIP = "DEFT_SESSION_RITUAL_SKIP";
|
|
3
4
|
export { ENTRYPOINT_TIMEOUT_EXIT_CODE, ENTRYPOINT_TIMEOUT_SECONDS, } from "./ritual-entrypoint.js";
|
|
4
5
|
export declare const GATED_ENTRYPOINT_COMMANDS: Readonly<Record<string, readonly string[]>>;
|
|
@@ -9,6 +10,8 @@ export interface VerifyResult {
|
|
|
9
10
|
readonly statePath: string;
|
|
10
11
|
readonly bypassed: boolean;
|
|
11
12
|
readonly wouldFailCode: number | null;
|
|
13
|
+
readonly posture: DirectivePosture;
|
|
14
|
+
readonly ritualStateRequired: boolean;
|
|
12
15
|
}
|
|
13
16
|
export type RitualRunner = (command: readonly string[], projectRoot: string) => {
|
|
14
17
|
code: number;
|
|
@@ -22,6 +25,9 @@ export interface VerifySessionRitualOptions {
|
|
|
22
25
|
readonly bypass?: boolean;
|
|
23
26
|
readonly envSkip?: string | undefined;
|
|
24
27
|
readonly runGit?: GitRunner;
|
|
28
|
+
readonly posture?: DirectivePosture;
|
|
29
|
+
readonly envPosture?: string | undefined;
|
|
30
|
+
readonly handoffText?: string | null;
|
|
25
31
|
}
|
|
26
32
|
export declare function verifySessionRitual(projectRoot: string, options?: VerifySessionRitualOptions): VerifyResult;
|
|
27
33
|
export declare function emitVerifyJson(result: VerifyResult): string;
|
|
@@ -2,6 +2,7 @@ import { existsSync } from "node:fs";
|
|
|
2
2
|
import { formatFrameworkCommand } from "../render/framework-commands.js";
|
|
3
3
|
import { defaultGitRunner, gitHead, worktreePath } from "./git.js";
|
|
4
4
|
import { pythonJsonDump } from "./json.js";
|
|
5
|
+
import { ENV_SESSION_POSTURE, readOnlyPostureMessage, resolveSessionPosture, ritualStateIsPostureAuthority, } from "./posture.js";
|
|
5
6
|
import { defaultRitualRunner } from "./ritual-entrypoint.js";
|
|
6
7
|
import { readRitualState, ritualStatePath, ritualStep, writeRitualState, } from "./ritual-sentinel.js";
|
|
7
8
|
import { GATED_STEPS, QUICK_STEPS } from "./session-start.js";
|
|
@@ -106,6 +107,15 @@ function evaluateLoadedState(projectRoot, state, input) {
|
|
|
106
107
|
}
|
|
107
108
|
export function verifySessionRitual(projectRoot, options = {}) {
|
|
108
109
|
const tier = options.tier ?? "quick";
|
|
110
|
+
const posture = resolveSessionPosture({
|
|
111
|
+
explicitPosture: options.posture ?? null,
|
|
112
|
+
envPosture: options.envPosture ?? process.env.DEFT_SESSION_POSTURE,
|
|
113
|
+
handoffText: options.handoffText,
|
|
114
|
+
tier,
|
|
115
|
+
});
|
|
116
|
+
// Satisfy static analysis / keep ENV_SESSION_POSTURE as the public constant name.
|
|
117
|
+
void ENV_SESSION_POSTURE;
|
|
118
|
+
const ritualStateRequired = posture === "mutation" && !ritualStateIsPostureAuthority();
|
|
109
119
|
if (tier !== "quick" && tier !== "gated") {
|
|
110
120
|
return {
|
|
111
121
|
code: 2,
|
|
@@ -114,13 +124,29 @@ export function verifySessionRitual(projectRoot, options = {}) {
|
|
|
114
124
|
statePath: ritualStatePath(projectRoot),
|
|
115
125
|
bypassed: false,
|
|
116
126
|
wouldFailCode: null,
|
|
127
|
+
posture,
|
|
128
|
+
ritualStateRequired,
|
|
117
129
|
};
|
|
118
130
|
}
|
|
119
131
|
const instant = options.now ?? new Date();
|
|
120
|
-
const envSkip = options.envSkip ?? process.env
|
|
132
|
+
const envSkip = options.envSkip ?? process.env.DEFT_SESSION_RITUAL_SKIP;
|
|
121
133
|
const isBypassed = options.bypass ?? truthy(envSkip);
|
|
122
134
|
const statePath = ritualStatePath(projectRoot);
|
|
123
135
|
const missingStateFile = !existsSync(statePath);
|
|
136
|
+
// Read-only posture never treats ritual-state as authority — including when
|
|
137
|
+
// DEFT_SESSION_RITUAL_SKIP is set. Skip is only for mutation-boundary gates.
|
|
138
|
+
if (posture === "read-only") {
|
|
139
|
+
return {
|
|
140
|
+
code: 0,
|
|
141
|
+
message: readOnlyPostureMessage(tier),
|
|
142
|
+
tier,
|
|
143
|
+
statePath,
|
|
144
|
+
bypassed: false,
|
|
145
|
+
wouldFailCode: null,
|
|
146
|
+
posture,
|
|
147
|
+
ritualStateRequired: false,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
124
150
|
let [state, err] = readRitualState(projectRoot);
|
|
125
151
|
if (state === null) {
|
|
126
152
|
const code = missingStateFile ? 1 : 2;
|
|
@@ -129,9 +155,27 @@ export function verifySessionRitual(projectRoot, options = {}) {
|
|
|
129
155
|
? `${err}. Run \`${startCommand}\` before implementation dispatch.`
|
|
130
156
|
: (err ?? "ritual state invalid");
|
|
131
157
|
if (isBypassed) {
|
|
132
|
-
return {
|
|
158
|
+
return {
|
|
159
|
+
code: 0,
|
|
160
|
+
message,
|
|
161
|
+
tier,
|
|
162
|
+
statePath,
|
|
163
|
+
bypassed: true,
|
|
164
|
+
wouldFailCode: code,
|
|
165
|
+
posture,
|
|
166
|
+
ritualStateRequired,
|
|
167
|
+
};
|
|
133
168
|
}
|
|
134
|
-
return {
|
|
169
|
+
return {
|
|
170
|
+
code,
|
|
171
|
+
message,
|
|
172
|
+
tier,
|
|
173
|
+
statePath,
|
|
174
|
+
bypassed: false,
|
|
175
|
+
wouldFailCode: null,
|
|
176
|
+
posture,
|
|
177
|
+
ritualStateRequired,
|
|
178
|
+
};
|
|
135
179
|
}
|
|
136
180
|
if (tier === "gated" && !isBypassed) {
|
|
137
181
|
const [precheckCode, precheckMessage] = evaluateLoadedState(projectRoot, state, {
|
|
@@ -147,6 +191,8 @@ export function verifySessionRitual(projectRoot, options = {}) {
|
|
|
147
191
|
statePath,
|
|
148
192
|
bypassed: false,
|
|
149
193
|
wouldFailCode: null,
|
|
194
|
+
posture,
|
|
195
|
+
ritualStateRequired,
|
|
150
196
|
};
|
|
151
197
|
}
|
|
152
198
|
const payload = { ...state.raw };
|
|
@@ -168,6 +214,8 @@ export function verifySessionRitual(projectRoot, options = {}) {
|
|
|
168
214
|
statePath,
|
|
169
215
|
bypassed: false,
|
|
170
216
|
wouldFailCode: null,
|
|
217
|
+
posture,
|
|
218
|
+
ritualStateRequired,
|
|
171
219
|
};
|
|
172
220
|
}
|
|
173
221
|
}
|
|
@@ -182,6 +230,8 @@ export function verifySessionRitual(projectRoot, options = {}) {
|
|
|
182
230
|
statePath,
|
|
183
231
|
bypassed: false,
|
|
184
232
|
wouldFailCode: null,
|
|
233
|
+
posture,
|
|
234
|
+
ritualStateRequired,
|
|
185
235
|
};
|
|
186
236
|
}
|
|
187
237
|
}
|
|
@@ -198,9 +248,20 @@ export function verifySessionRitual(projectRoot, options = {}) {
|
|
|
198
248
|
statePath,
|
|
199
249
|
bypassed: true,
|
|
200
250
|
wouldFailCode: code === 0 ? null : code,
|
|
251
|
+
posture,
|
|
252
|
+
ritualStateRequired,
|
|
201
253
|
};
|
|
202
254
|
}
|
|
203
|
-
return {
|
|
255
|
+
return {
|
|
256
|
+
code,
|
|
257
|
+
message,
|
|
258
|
+
tier,
|
|
259
|
+
statePath,
|
|
260
|
+
bypassed: false,
|
|
261
|
+
wouldFailCode: null,
|
|
262
|
+
posture,
|
|
263
|
+
ritualStateRequired,
|
|
264
|
+
};
|
|
204
265
|
}
|
|
205
266
|
export function emitVerifyJson(result) {
|
|
206
267
|
return pythonJsonDump({
|
|
@@ -211,6 +272,8 @@ export function emitVerifyJson(result) {
|
|
|
211
272
|
state_path: result.statePath,
|
|
212
273
|
bypassed: result.bypassed,
|
|
213
274
|
would_fail_code: result.wouldFailCode,
|
|
275
|
+
posture: result.posture,
|
|
276
|
+
ritual_state_required: result.ritualStateRequired,
|
|
214
277
|
});
|
|
215
278
|
}
|
|
216
279
|
export function emitBypassWarning(result) {
|
|
@@ -394,14 +394,18 @@ function ensureEvalReadme(options) {
|
|
|
394
394
|
}
|
|
395
395
|
/** Ensure the #1144 hybrid policy is encoded in the repo (idempotent). */
|
|
396
396
|
export function stepEnsureGitignoreEvalEntries(projectRoot) {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
397
|
+
const stepName = "ensure_gitignore_eval_entries";
|
|
398
|
+
let readmePath;
|
|
399
|
+
try {
|
|
400
|
+
readmePath = resolveTriageCachePath(projectRoot, "README.md");
|
|
401
|
+
}
|
|
402
|
+
catch (err) {
|
|
403
|
+
return containmentFailure(stepName, err);
|
|
404
|
+
}
|
|
400
405
|
const readmeRel = evalRelDisplay(projectRoot, readmePath);
|
|
401
406
|
const entries = gitignoreTriageCacheEntries(projectRoot);
|
|
402
407
|
const glob = gitattributesTriageCacheGlob(projectRoot);
|
|
403
408
|
const ruleLine = `${glob} merge=union`;
|
|
404
|
-
const stepName = "ensure_gitignore_eval_entries";
|
|
405
409
|
const details = {};
|
|
406
410
|
const giResult = ensureGitignoreSelectiveEntries(projectRoot, stepName, entries);
|
|
407
411
|
if (!giResult.ok) {
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { existsSync, mkdirSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
8
8
|
import { join, relative } from "node:path";
|
|
9
|
+
import { assertProjectionContained } from "../fs/projection-containment.js";
|
|
9
10
|
import { MIGRATED_ARTIFACT_DIR, resolveEvalDir, resolveLifecycleLayout, resolveLifecycleRoot, } from "../layout/resolve.js";
|
|
10
11
|
import { generateTriageCacheReadmeBody } from "./bootstrap/gitignore.js";
|
|
11
12
|
/** Directory name for the triage working-set cache (not version-eval results). */
|
|
@@ -24,8 +25,8 @@ export const TRIAGE_CACHE_FILE_NAMES = [
|
|
|
24
25
|
];
|
|
25
26
|
/** Known triage-cache directory names migrated off the legacy `.eval/` tree. */
|
|
26
27
|
export const TRIAGE_CACHE_DIR_NAMES = ["decompositions"];
|
|
27
|
-
/**
|
|
28
|
-
|
|
28
|
+
/** Compute the layout-aware `.triage-cache/` directory without containment checks. */
|
|
29
|
+
function triageCacheDirPath(projectRoot) {
|
|
29
30
|
let layoutRoot;
|
|
30
31
|
try {
|
|
31
32
|
layoutRoot = resolveLifecycleRoot(projectRoot);
|
|
@@ -35,6 +36,17 @@ export function resolveTriageCacheDir(projectRoot) {
|
|
|
35
36
|
}
|
|
36
37
|
return join(layoutRoot, TRIAGE_CACHE_DIR_NAME);
|
|
37
38
|
}
|
|
39
|
+
/** Refuse symlink-escaping xbrief/.triage-cache before mkdir/read/write (#2446). */
|
|
40
|
+
function assertWritableTriageCachePath(projectRoot, ...segments) {
|
|
41
|
+
const base = triageCacheDirPath(projectRoot);
|
|
42
|
+
const target = segments.length > 0 ? join(base, ...segments) : base;
|
|
43
|
+
assertProjectionContained(projectRoot, target);
|
|
44
|
+
}
|
|
45
|
+
/** Absolute path to the layout-aware `.triage-cache/` directory. */
|
|
46
|
+
export function resolveTriageCacheDir(projectRoot) {
|
|
47
|
+
assertWritableTriageCachePath(projectRoot);
|
|
48
|
+
return triageCacheDirPath(projectRoot);
|
|
49
|
+
}
|
|
38
50
|
/** POSIX-style path relative to project root (e.g. `xbrief/.triage-cache/foo`). */
|
|
39
51
|
export function triageCacheRelPath(projectRoot, ...segments) {
|
|
40
52
|
let artifactDir;
|
|
@@ -118,8 +130,9 @@ export function migrateLegacyTriageCacheFromEval(projectRoot) {
|
|
|
118
130
|
}
|
|
119
131
|
/** Resolve a path under `.triage-cache/`, migrating legacy `.eval/` files first. */
|
|
120
132
|
export function resolveTriageCachePath(projectRoot, ...segments) {
|
|
133
|
+
assertWritableTriageCachePath(projectRoot, ...segments);
|
|
121
134
|
migrateLegacyTriageCacheFromEval(projectRoot);
|
|
122
|
-
return join(
|
|
135
|
+
return join(triageCacheDirPath(projectRoot), ...segments);
|
|
123
136
|
}
|
|
124
137
|
/** Display helper: project-root-relative POSIX path for logs and gitignore copy. */
|
|
125
138
|
export function triageCacheDisplayPath(projectRoot, absPath) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, writeFileSync, } from "node:fs";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { assertProjectionContained } from "../../fs/projection-containment.js";
|
|
3
4
|
import { hasArtifactSuffix, resolveLifecycleFolder, resolveProjectDefinitionPath, } from "../../layout/resolve.js";
|
|
4
5
|
import { migrateLegacyPolicyKey, PLAN_POLICY_KEY } from "../../policy/plan-extensions.js";
|
|
5
6
|
import { projectDefinitionMutationLock } from "../../vbrief-build/project-definition-io.js";
|
|
@@ -12,6 +13,7 @@ function utcIso() {
|
|
|
12
13
|
}
|
|
13
14
|
export function appendAuditEntry(projectRoot, entry) {
|
|
14
15
|
const logPath = join(resolve(projectRoot), AUDIT_LOG_REL_PATH);
|
|
16
|
+
assertProjectionContained(projectRoot, logPath);
|
|
15
17
|
mkdirSync(dirname(logPath), { recursive: true });
|
|
16
18
|
const line = `${utcIso()} ${entry}\n`;
|
|
17
19
|
if (!existsSync(logPath)) {
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export type { LaneRunner, ResolvePnpmOptions, RunnerResult, RunTsLaneOptions } from "./run-lane.js";
|
|
2
|
-
export { LANE_COMMANDS, resolvePnpm, runTsLane, SKIP_NOTICE } from "./run-lane.js";
|
|
2
|
+
export { LANE_COMMANDS, resolvePnpm, runTsLane, SKIP_NOTICE, sanitizeTsLaneEnv, } from "./run-lane.js";
|
|
3
3
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { LANE_COMMANDS, resolvePnpm, runTsLane, SKIP_NOTICE } from "./run-lane.js";
|
|
1
|
+
export { LANE_COMMANDS, resolvePnpm, runTsLane, SKIP_NOTICE, sanitizeTsLaneEnv, } from "./run-lane.js";
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
@@ -29,6 +29,11 @@ export interface RunnerResult {
|
|
|
29
29
|
readonly error?: Error;
|
|
30
30
|
}
|
|
31
31
|
export type LaneRunner = (argv: readonly string[], cwd: string) => RunnerResult;
|
|
32
|
+
/**
|
|
33
|
+
* Strip release preflight bypass vars before spawning pnpm/vitest so nested unit
|
|
34
|
+
* tests observe fail-closed branch and cache gates (#2434 / #1553 recurrence).
|
|
35
|
+
*/
|
|
36
|
+
export declare function sanitizeTsLaneEnv(base?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
32
37
|
export interface RunTsLaneOptions {
|
|
33
38
|
/** Resolved pnpm executable path, or null when not installed. */
|
|
34
39
|
readonly pnpm: string | null;
|
|
@@ -19,6 +19,9 @@
|
|
|
19
19
|
import { spawnSync } from "node:child_process";
|
|
20
20
|
import { existsSync } from "node:fs";
|
|
21
21
|
import { posix, win32 } from "node:path";
|
|
22
|
+
import { BRANCH_GATE_BYPASS_ENV, RELEASE_PREFLIGHT_ENV } from "../release/constants.js";
|
|
23
|
+
/** Release Step-5 vars that must not leak into vitest via inherited pnpm env (#2434). */
|
|
24
|
+
const TS_LANE_POISON_ENV_KEYS = [BRANCH_GATE_BYPASS_ENV, RELEASE_PREFLIGHT_ENV];
|
|
22
25
|
/**
|
|
23
26
|
* Run order is deliberate: lint (cheapest, catches the biome class first),
|
|
24
27
|
* then build, then the test suite.
|
|
@@ -31,6 +34,17 @@ export const LANE_COMMANDS = [
|
|
|
31
34
|
export const SKIP_NOTICE = "[ts:check-lane] pnpm not found on PATH -- skipping the TypeScript lane " +
|
|
32
35
|
"(build/lint/test). The TS engine stays validated by the dedicated CI job. " +
|
|
33
36
|
"Install the Node toolchain (pnpm) to run the TS lane locally.";
|
|
37
|
+
/**
|
|
38
|
+
* Strip release preflight bypass vars before spawning pnpm/vitest so nested unit
|
|
39
|
+
* tests observe fail-closed branch and cache gates (#2434 / #1553 recurrence).
|
|
40
|
+
*/
|
|
41
|
+
export function sanitizeTsLaneEnv(base = process.env) {
|
|
42
|
+
const env = { ...base };
|
|
43
|
+
for (const key of TS_LANE_POISON_ENV_KEYS) {
|
|
44
|
+
delete env[key];
|
|
45
|
+
}
|
|
46
|
+
return env;
|
|
47
|
+
}
|
|
34
48
|
/** Windows command shims (.cmd/.bat) need a shell; native executables do not. */
|
|
35
49
|
export function shouldUseShellForCommand(command, platform = process.platform) {
|
|
36
50
|
return platform === "win32" && /\.(?:cmd|bat)$/i.test(command);
|
|
@@ -41,6 +55,7 @@ function defaultRunner(argv, cwd) {
|
|
|
41
55
|
const commandPath = command ?? "";
|
|
42
56
|
const result = spawnSync(commandPath, rest, {
|
|
43
57
|
cwd,
|
|
58
|
+
env: sanitizeTsLaneEnv(process.env),
|
|
44
59
|
stdio: "inherit",
|
|
45
60
|
shell: shouldUseShellForCommand(commandPath),
|
|
46
61
|
});
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
/** Canonical
|
|
2
|
-
export declare const EMITTED_VBRIEF_VERSION
|
|
1
|
+
/** Canonical envelope version emitted on new scope xBRIEFs (#533, #2318). */
|
|
2
|
+
export declare const EMITTED_VBRIEF_VERSION: "0.8";
|
|
3
3
|
/** Migrator provenance namespace under ``plan.metadata`` (#616). */
|
|
4
4
|
export declare const MIGRATOR_METADATA_KEY = "x-migrator";
|
|
5
|
-
|
|
5
|
+
/** Layout-aware PROJECT-DEFINITION paths live in ``project-definition-io.ts`` / ``policy/resolve.ts`` (#2302). */
|
|
6
6
|
export declare const DEPRECATION_SENTINEL = "<!-- deft:deprecated-redirect -->";
|
|
7
7
|
export declare const INTERNAL_REFERENCE_TYPES: Set<string>;
|
|
8
8
|
export declare const EXTERNAL_REFERENCE_TYPES: Set<string>;
|