@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
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type PlanSequence } from "./types.js";
|
|
2
|
+
export declare function planSequencePath(projectRoot: string): string;
|
|
3
|
+
export declare function readPlanSequence(projectRoot: string): PlanSequence | null;
|
|
4
|
+
export declare function writePlanSequence(projectRoot: string, sequence: PlanSequence): string;
|
|
5
|
+
export declare function clearPlanSequence(projectRoot: string): boolean;
|
|
6
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { PLAN_SEQUENCE_FILENAME, parsePlanSequence } from "./types.js";
|
|
4
|
+
export function planSequencePath(projectRoot) {
|
|
5
|
+
return join(projectRoot, ".deft", PLAN_SEQUENCE_FILENAME);
|
|
6
|
+
}
|
|
7
|
+
export function readPlanSequence(projectRoot) {
|
|
8
|
+
const path = planSequencePath(projectRoot);
|
|
9
|
+
if (!existsSync(path)) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
const raw = JSON.parse(readFileSync(path, "utf8"));
|
|
13
|
+
return parsePlanSequence(raw);
|
|
14
|
+
}
|
|
15
|
+
export function writePlanSequence(projectRoot, sequence) {
|
|
16
|
+
const path = planSequencePath(projectRoot);
|
|
17
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
18
|
+
writeFileSync(path, `${JSON.stringify(sequence, null, 2)}\n`, "utf8");
|
|
19
|
+
return path;
|
|
20
|
+
}
|
|
21
|
+
export function clearPlanSequence(projectRoot) {
|
|
22
|
+
const path = planSequencePath(projectRoot);
|
|
23
|
+
if (!existsSync(path)) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
unlinkSync(path);
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ordered-plan continuation boundary (#2402).
|
|
3
|
+
*
|
|
4
|
+
* Continuation words ("next", "resume", "proceed") advance only within the
|
|
5
|
+
* narrowest operator-approved sequence. Exhaustion fails closed. Separate from
|
|
6
|
+
* triage queue continuationNumbers / continuationOrder.
|
|
7
|
+
*/
|
|
8
|
+
export declare const PLAN_SEQUENCE_FILENAME = "plan-sequence.json";
|
|
9
|
+
export declare const PLAN_SEQUENCE_CONTRACT: "ordered-plan-continuation";
|
|
10
|
+
export type PlanSequenceKind = "delivery" | "review" | "implementation" | "swarm" | "triage" | "cohort" | "checklist";
|
|
11
|
+
export type PlanTargetKind = "pr" | "issue" | "story" | "task" | "phase" | "checklist" | "review";
|
|
12
|
+
export interface PlanSequenceEntry {
|
|
13
|
+
readonly id: string;
|
|
14
|
+
readonly kind: PlanTargetKind;
|
|
15
|
+
readonly title?: string;
|
|
16
|
+
readonly issue?: number;
|
|
17
|
+
readonly status?: "pending" | "completed" | "skipped";
|
|
18
|
+
}
|
|
19
|
+
export interface PlanSequence {
|
|
20
|
+
readonly sequence_id: string;
|
|
21
|
+
readonly sequence_kind: PlanSequenceKind;
|
|
22
|
+
readonly entries: readonly PlanSequenceEntry[];
|
|
23
|
+
readonly current_index: number;
|
|
24
|
+
readonly batching_allowed: boolean;
|
|
25
|
+
/** Default false — exhausted sequences require fresh operator approval. */
|
|
26
|
+
readonly continuation_past_final: boolean;
|
|
27
|
+
readonly exhausted: boolean;
|
|
28
|
+
readonly authorized_by: string;
|
|
29
|
+
readonly created_at: string;
|
|
30
|
+
readonly updated_at: string;
|
|
31
|
+
}
|
|
32
|
+
export interface PlanSequenceVerifyInput {
|
|
33
|
+
readonly targetKind: PlanTargetKind;
|
|
34
|
+
readonly target: string;
|
|
35
|
+
}
|
|
36
|
+
export type PlanSequenceVerifyResult = {
|
|
37
|
+
readonly ok: true;
|
|
38
|
+
readonly entry: PlanSequenceEntry;
|
|
39
|
+
readonly index: number;
|
|
40
|
+
} | {
|
|
41
|
+
readonly ok: false;
|
|
42
|
+
readonly code: "missing" | "exhausted" | "mismatch" | "kind-mismatch";
|
|
43
|
+
readonly message: string;
|
|
44
|
+
};
|
|
45
|
+
export type ContinuationResolution = {
|
|
46
|
+
readonly action: "advance";
|
|
47
|
+
readonly entry: PlanSequenceEntry;
|
|
48
|
+
readonly index: number;
|
|
49
|
+
} | {
|
|
50
|
+
readonly action: "queue";
|
|
51
|
+
readonly reason: "no-active-sequence" | "explicit-queue-override" | "not-plan-first-phrase";
|
|
52
|
+
} | {
|
|
53
|
+
readonly action: "ask";
|
|
54
|
+
readonly reason: "exhausted" | "ambiguous-sequences";
|
|
55
|
+
readonly message: string;
|
|
56
|
+
};
|
|
57
|
+
/** Phrases that are explicit queue/backlog selection even mid-plan. */
|
|
58
|
+
export declare const EXPLICIT_QUEUE_PHRASES: readonly ["what's the queue", "whats the queue", "what is the queue", "build a cohort", "triage queue", "show the queue", "queue-driven", "from the backlog"];
|
|
59
|
+
/** Bare continuation / selection phrases that bind to ordered-plan when active. */
|
|
60
|
+
export declare const PLAN_FIRST_PHRASES: readonly ["what's next", "whats next", "what next", "what should i work on next", "next task", "next pr", "next issue", "next story", "move on", "proceed", "resume", "next"];
|
|
61
|
+
export declare const EXHAUSTED_FAIL_CLOSED_MESSAGE = "I have completed the approved sequence. Starting another item would exceed the current authorization. Please name the next target or approve queue-driven selection.";
|
|
62
|
+
export declare function isExplicitQueueAsk(text: string): boolean;
|
|
63
|
+
export declare function isPlanFirstPhrase(text: string): boolean;
|
|
64
|
+
/** Resolve how continuation/selection language should be handled. */
|
|
65
|
+
export declare function resolveContinuation(text: string, sequence: PlanSequence | null): ContinuationResolution;
|
|
66
|
+
export declare function verifyPlanTarget(sequence: PlanSequence | null, input: PlanSequenceVerifyInput): PlanSequenceVerifyResult;
|
|
67
|
+
export declare function createPlanSequence(input: {
|
|
68
|
+
sequence_id: string;
|
|
69
|
+
sequence_kind: PlanSequenceKind;
|
|
70
|
+
entries: readonly PlanSequenceEntry[];
|
|
71
|
+
authorized_by: string;
|
|
72
|
+
batching_allowed?: boolean;
|
|
73
|
+
continuation_past_final?: boolean;
|
|
74
|
+
now?: string;
|
|
75
|
+
}): PlanSequence;
|
|
76
|
+
/** Mark current entry completed and advance index; may set exhausted. */
|
|
77
|
+
export declare function advancePlanSequence(sequence: PlanSequence, now?: string): PlanSequence;
|
|
78
|
+
export declare function parsePlanSequence(raw: unknown): PlanSequence;
|
|
79
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ordered-plan continuation boundary (#2402).
|
|
3
|
+
*
|
|
4
|
+
* Continuation words ("next", "resume", "proceed") advance only within the
|
|
5
|
+
* narrowest operator-approved sequence. Exhaustion fails closed. Separate from
|
|
6
|
+
* triage queue continuationNumbers / continuationOrder.
|
|
7
|
+
*/
|
|
8
|
+
export const PLAN_SEQUENCE_FILENAME = "plan-sequence.json";
|
|
9
|
+
export const PLAN_SEQUENCE_CONTRACT = "ordered-plan-continuation";
|
|
10
|
+
/** Phrases that are explicit queue/backlog selection even mid-plan. */
|
|
11
|
+
export const EXPLICIT_QUEUE_PHRASES = [
|
|
12
|
+
"what's the queue",
|
|
13
|
+
"whats the queue",
|
|
14
|
+
"what is the queue",
|
|
15
|
+
"build a cohort",
|
|
16
|
+
"triage queue",
|
|
17
|
+
"show the queue",
|
|
18
|
+
"queue-driven",
|
|
19
|
+
"from the backlog",
|
|
20
|
+
];
|
|
21
|
+
/** Bare continuation / selection phrases that bind to ordered-plan when active. */
|
|
22
|
+
export const PLAN_FIRST_PHRASES = [
|
|
23
|
+
"what's next",
|
|
24
|
+
"whats next",
|
|
25
|
+
"what next",
|
|
26
|
+
"what should i work on next",
|
|
27
|
+
"next task",
|
|
28
|
+
"next pr",
|
|
29
|
+
"next issue",
|
|
30
|
+
"next story",
|
|
31
|
+
"move on",
|
|
32
|
+
"proceed",
|
|
33
|
+
"resume",
|
|
34
|
+
"next",
|
|
35
|
+
];
|
|
36
|
+
export const EXHAUSTED_FAIL_CLOSED_MESSAGE = "I have completed the approved sequence. Starting another item would exceed the current authorization. Please name the next target or approve queue-driven selection.";
|
|
37
|
+
function normalizeTarget(raw) {
|
|
38
|
+
return raw.trim().toLowerCase().replace(/^#/, "");
|
|
39
|
+
}
|
|
40
|
+
function entryMatches(entry, targetKind, target) {
|
|
41
|
+
if (entry.kind !== targetKind) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
const needle = normalizeTarget(target);
|
|
45
|
+
if (normalizeTarget(entry.id) === needle) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
if (entry.issue !== undefined && String(entry.issue) === needle) {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
if (entry.title !== undefined && normalizeTarget(entry.title) === needle) {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
export function isExplicitQueueAsk(text) {
|
|
57
|
+
const lower = text.toLowerCase();
|
|
58
|
+
return EXPLICIT_QUEUE_PHRASES.some((p) => lower.includes(p));
|
|
59
|
+
}
|
|
60
|
+
export function isPlanFirstPhrase(text) {
|
|
61
|
+
const lower = text.toLowerCase();
|
|
62
|
+
return PLAN_FIRST_PHRASES.some((p) => lower.includes(p));
|
|
63
|
+
}
|
|
64
|
+
/** Resolve how continuation/selection language should be handled. */
|
|
65
|
+
export function resolveContinuation(text, sequence) {
|
|
66
|
+
// Explicit backlog/cohort asks always select from the queue, even mid-plan.
|
|
67
|
+
if (isExplicitQueueAsk(text)) {
|
|
68
|
+
return { action: "queue", reason: "explicit-queue-override" };
|
|
69
|
+
}
|
|
70
|
+
// No active sequence → queue (same as today's #1149 path).
|
|
71
|
+
if (sequence === null) {
|
|
72
|
+
return { action: "queue", reason: "no-active-sequence" };
|
|
73
|
+
}
|
|
74
|
+
// Active sequence + plan-first / continuation phrase → bind to current entry.
|
|
75
|
+
if (!isPlanFirstPhrase(text)) {
|
|
76
|
+
return { action: "queue", reason: "not-plan-first-phrase" };
|
|
77
|
+
}
|
|
78
|
+
if (sequence.exhausted || sequence.current_index >= sequence.entries.length) {
|
|
79
|
+
if (sequence.continuation_past_final) {
|
|
80
|
+
return { action: "queue", reason: "no-active-sequence" };
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
action: "ask",
|
|
84
|
+
reason: "exhausted",
|
|
85
|
+
message: EXHAUSTED_FAIL_CLOSED_MESSAGE,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
const index = sequence.current_index;
|
|
89
|
+
const entry = sequence.entries[index];
|
|
90
|
+
if (entry === undefined) {
|
|
91
|
+
return {
|
|
92
|
+
action: "ask",
|
|
93
|
+
reason: "exhausted",
|
|
94
|
+
message: EXHAUSTED_FAIL_CLOSED_MESSAGE,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return { action: "advance", entry, index };
|
|
98
|
+
}
|
|
99
|
+
export function verifyPlanTarget(sequence, input) {
|
|
100
|
+
if (sequence === null) {
|
|
101
|
+
return {
|
|
102
|
+
ok: false,
|
|
103
|
+
code: "missing",
|
|
104
|
+
message: "No active ordered-plan sequence. Set one with plan-sequence:set, or obtain explicit operator approval for this target.",
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
if (sequence.exhausted || sequence.current_index >= sequence.entries.length) {
|
|
108
|
+
return {
|
|
109
|
+
ok: false,
|
|
110
|
+
code: "exhausted",
|
|
111
|
+
message: EXHAUSTED_FAIL_CLOSED_MESSAGE,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
const index = sequence.current_index;
|
|
115
|
+
const entry = sequence.entries[index];
|
|
116
|
+
if (entry === undefined) {
|
|
117
|
+
return {
|
|
118
|
+
ok: false,
|
|
119
|
+
code: "exhausted",
|
|
120
|
+
message: EXHAUSTED_FAIL_CLOSED_MESSAGE,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
if (entry.kind !== input.targetKind) {
|
|
124
|
+
return {
|
|
125
|
+
ok: false,
|
|
126
|
+
code: "kind-mismatch",
|
|
127
|
+
message: `Current ordered-plan entry is kind=${entry.kind} id=${entry.id}; requested kind=${input.targetKind} target=${input.target}.`,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
if (!entryMatches(entry, input.targetKind, input.target)) {
|
|
131
|
+
return {
|
|
132
|
+
ok: false,
|
|
133
|
+
code: "mismatch",
|
|
134
|
+
message: `Target ${input.targetKind}:${input.target} is not the current ordered-plan entry (${entry.kind}:${entry.id}).`,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
return { ok: true, entry, index };
|
|
138
|
+
}
|
|
139
|
+
export function createPlanSequence(input) {
|
|
140
|
+
if (input.entries.length === 0) {
|
|
141
|
+
throw new Error("plan-sequence: entries must be non-empty");
|
|
142
|
+
}
|
|
143
|
+
const now = input.now ?? new Date().toISOString();
|
|
144
|
+
return {
|
|
145
|
+
sequence_id: input.sequence_id,
|
|
146
|
+
sequence_kind: input.sequence_kind,
|
|
147
|
+
entries: input.entries.map((e) => ({ ...e, status: e.status ?? "pending" })),
|
|
148
|
+
current_index: 0,
|
|
149
|
+
batching_allowed: input.batching_allowed ?? false,
|
|
150
|
+
continuation_past_final: input.continuation_past_final ?? false,
|
|
151
|
+
exhausted: false,
|
|
152
|
+
authorized_by: input.authorized_by,
|
|
153
|
+
created_at: now,
|
|
154
|
+
updated_at: now,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/** Mark current entry completed and advance index; may set exhausted. */
|
|
158
|
+
export function advancePlanSequence(sequence, now = new Date().toISOString()) {
|
|
159
|
+
if (sequence.exhausted || sequence.current_index >= sequence.entries.length) {
|
|
160
|
+
return { ...sequence, exhausted: true, updated_at: now };
|
|
161
|
+
}
|
|
162
|
+
const entries = sequence.entries.map((e, i) => i === sequence.current_index ? { ...e, status: "completed" } : e);
|
|
163
|
+
const nextIndex = sequence.current_index + 1;
|
|
164
|
+
const exhausted = nextIndex >= entries.length;
|
|
165
|
+
return {
|
|
166
|
+
...sequence,
|
|
167
|
+
entries,
|
|
168
|
+
current_index: exhausted ? sequence.current_index : nextIndex,
|
|
169
|
+
exhausted,
|
|
170
|
+
updated_at: now,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
export function parsePlanSequence(raw) {
|
|
174
|
+
if (raw === null || typeof raw !== "object") {
|
|
175
|
+
throw new Error("plan-sequence: expected object");
|
|
176
|
+
}
|
|
177
|
+
const obj = raw;
|
|
178
|
+
if (typeof obj.sequence_id !== "string" || obj.sequence_id.length === 0) {
|
|
179
|
+
throw new Error("plan-sequence: sequence_id required");
|
|
180
|
+
}
|
|
181
|
+
if (typeof obj.sequence_kind !== "string") {
|
|
182
|
+
throw new Error("plan-sequence: sequence_kind required");
|
|
183
|
+
}
|
|
184
|
+
if (!Array.isArray(obj.entries) || obj.entries.length === 0) {
|
|
185
|
+
throw new Error("plan-sequence: entries must be a non-empty array");
|
|
186
|
+
}
|
|
187
|
+
const entries = obj.entries.map((item, i) => {
|
|
188
|
+
if (item === null || typeof item !== "object") {
|
|
189
|
+
throw new Error(`plan-sequence: entries[${i}] must be an object`);
|
|
190
|
+
}
|
|
191
|
+
const e = item;
|
|
192
|
+
if (typeof e.id !== "string" || typeof e.kind !== "string") {
|
|
193
|
+
throw new Error(`plan-sequence: entries[${i}] requires id and kind`);
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
id: e.id,
|
|
197
|
+
kind: e.kind,
|
|
198
|
+
title: typeof e.title === "string" ? e.title : undefined,
|
|
199
|
+
issue: typeof e.issue === "number" ? e.issue : undefined,
|
|
200
|
+
status: e.status === "completed" || e.status === "skipped" || e.status === "pending"
|
|
201
|
+
? e.status
|
|
202
|
+
: "pending",
|
|
203
|
+
};
|
|
204
|
+
});
|
|
205
|
+
const current_index = typeof obj.current_index === "number" ? obj.current_index : 0;
|
|
206
|
+
const exhausted = obj.exhausted === true || current_index >= entries.length;
|
|
207
|
+
return {
|
|
208
|
+
sequence_id: obj.sequence_id,
|
|
209
|
+
sequence_kind: obj.sequence_kind,
|
|
210
|
+
entries,
|
|
211
|
+
current_index,
|
|
212
|
+
batching_allowed: obj.batching_allowed === true,
|
|
213
|
+
continuation_past_final: obj.continuation_past_final === true,
|
|
214
|
+
exhausted,
|
|
215
|
+
authorized_by: typeof obj.authorized_by === "string" ? obj.authorized_by : "",
|
|
216
|
+
created_at: typeof obj.created_at === "string" ? obj.created_at : new Date().toISOString(),
|
|
217
|
+
updated_at: typeof obj.updated_at === "string" ? obj.updated_at : new Date().toISOString(),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
export declare const ACTIVE_FOLDER = "active";
|
|
3
3
|
/** Canonical eligibility status — only `running` signals an active handoff. */
|
|
4
4
|
export declare const ELIGIBLE_STATUS = "running";
|
|
5
|
-
/** Actionable redirect appended to every reject path (#810). */
|
|
6
|
-
export declare const ACTIVATE_HINT = "Run `task vbrief:activate {path}` before spawning an implementation agent.";
|
|
5
|
+
/** Actionable redirect appended to every reject path (#810 / #2449). */
|
|
6
|
+
export declare const ACTIVATE_HINT = "Run `task scope:activate -- {path}` (or legacy `task vbrief:activate -- {path}`) before spawning an implementation agent.";
|
|
7
|
+
/** Lifecycle folder names eligible for implementation (#810). */
|
|
8
|
+
export declare const ELIGIBLE_LIFECYCLE_DIRS: readonly ["xbrief/active", "vbrief/active"];
|
|
9
|
+
export declare const PREFLIGHT_USAGE_HINT = "Expected: `task xbrief:preflight -- xbrief/active/<story>.xbrief.json` (legacy: `task vbrief:preflight -- <path>`).";
|
|
7
10
|
/** Result of a vBRIEF preflight evaluation; mirrors the Python `evaluate` tuple. */
|
|
8
11
|
export interface EvaluateResult {
|
|
9
12
|
readonly exitCode: 0 | 1;
|
|
@@ -4,14 +4,17 @@ import { basename, dirname } from "node:path";
|
|
|
4
4
|
export const ACTIVE_FOLDER = "active";
|
|
5
5
|
/** Canonical eligibility status — only `running` signals an active handoff. */
|
|
6
6
|
export const ELIGIBLE_STATUS = "running";
|
|
7
|
-
/** Actionable redirect appended to every reject path (#810). */
|
|
8
|
-
export const ACTIVATE_HINT = "Run `task vbrief:activate {path}` before spawning an implementation agent.";
|
|
7
|
+
/** Actionable redirect appended to every reject path (#810 / #2449). */
|
|
8
|
+
export const ACTIVATE_HINT = "Run `task scope:activate -- {path}` (or legacy `task vbrief:activate -- {path}`) before spawning an implementation agent.";
|
|
9
|
+
/** Lifecycle folder names eligible for implementation (#810). */
|
|
10
|
+
export const ELIGIBLE_LIFECYCLE_DIRS = ["xbrief/active", "vbrief/active"];
|
|
11
|
+
export const PREFLIGHT_USAGE_HINT = "Expected: `task xbrief:preflight -- xbrief/active/<story>.xbrief.json` (legacy: `task vbrief:preflight -- <path>`).";
|
|
9
12
|
/** Substitute `{path}` without `$`-pattern expansion in user paths (#1721). */
|
|
10
13
|
export function formatActivateHint(path) {
|
|
11
14
|
return ACTIVATE_HINT.replace("{path}", () => path);
|
|
12
15
|
}
|
|
13
16
|
function buildReject(path, reason) {
|
|
14
|
-
return `${reason}\n ${formatActivateHint(path)}`;
|
|
17
|
+
return `${reason}\n ${PREFLIGHT_USAGE_HINT}\n ${formatActivateHint(path)}`;
|
|
15
18
|
}
|
|
16
19
|
/** Map Node `JSON.parse` errors to CPython `json.JSONDecodeError.msg` for parity (#1721). */
|
|
17
20
|
function nodeJsonErrorToPythonMsg(nodeMessage) {
|
|
@@ -91,10 +94,12 @@ export function evaluate(vbriefPath) {
|
|
|
91
94
|
};
|
|
92
95
|
}
|
|
93
96
|
const folder = basename(dirname(vbriefPath));
|
|
97
|
+
const parent = basename(dirname(dirname(vbriefPath)));
|
|
98
|
+
const lifecycleDir = `${parent}/${folder}`;
|
|
94
99
|
if (folder !== ACTIVE_FOLDER) {
|
|
95
100
|
return {
|
|
96
101
|
exitCode: 1,
|
|
97
|
-
message: buildReject(path, `
|
|
102
|
+
message: buildReject(path, `xBRIEF is in ${lifecycleDir}/ -- only xbrief/active/ (or legacy vbrief/active/) is eligible for implementation.`),
|
|
98
103
|
};
|
|
99
104
|
}
|
|
100
105
|
const record = payload;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export type { EvaluateResult } from "./evaluate.js";
|
|
2
|
-
export { ACTIVATE_HINT, ACTIVE_FOLDER, ELIGIBLE_STATUS, emitJson, evaluate, formatActivateHint, } from "./evaluate.js";
|
|
2
|
+
export { ACTIVATE_HINT, ACTIVE_FOLDER, ELIGIBLE_LIFECYCLE_DIRS, ELIGIBLE_STATUS, emitJson, evaluate, formatActivateHint, PREFLIGHT_USAGE_HINT, } from "./evaluate.js";
|
|
3
3
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/preflight/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { ACTIVATE_HINT, ACTIVE_FOLDER, ELIGIBLE_STATUS, emitJson, evaluate, formatActivateHint, } from "./evaluate.js";
|
|
1
|
+
export { ACTIVATE_HINT, ACTIVE_FOLDER, ELIGIBLE_LIFECYCLE_DIRS, ELIGIBLE_STATUS, emitJson, evaluate, formatActivateHint, PREFLIGHT_USAGE_HINT, } from "./evaluate.js";
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { mkdtempSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
/** Seed a real on-disk project root for pipeline write-path tests (#2470). */
|
|
5
|
+
export function seedReleaseProjectDir(changelog = `## [Unreleased]\n\n### Added\n- x\n`) {
|
|
6
|
+
const dir = mkdtempSync(join(tmpdir(), "release-proj-"));
|
|
7
|
+
writeFileSync(join(dir, "CHANGELOG.md"), changelog, "utf8");
|
|
8
|
+
writeFileSync(join(dir, "ROADMAP.md"), "# Roadmap\n", "utf8");
|
|
9
|
+
return dir;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=pipeline-fixture.js.map
|
package/dist/release/pipeline.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { assertProjectionContained } from "../fs/projection-containment.js";
|
|
3
4
|
import { prependUpgradeBanner, promoteChangelog, sectionForVersion } from "./changelog.js";
|
|
4
5
|
import { EXIT_CONFIG_ERROR, EXIT_OK, EXIT_VIOLATION, RELEASE_ARTIFACTS, TOTAL_STEPS, VERIFY_DRAFT_INTERVAL_SECONDS, VERIFY_DRAFT_MAX_ATTEMPTS, } from "./constants.js";
|
|
5
6
|
import { checkTagAvailable, createGithubRelease, readTextFile, verifyReleaseDraft } from "./gh.js";
|
|
@@ -16,6 +17,7 @@ export function runPipeline(config, seams = {}) {
|
|
|
16
17
|
const version = config.version;
|
|
17
18
|
const today = (seams.todayIso ?? todayIso)();
|
|
18
19
|
const changelogPath = join(projectRoot, "CHANGELOG.md");
|
|
20
|
+
const roadmapPath = join(projectRoot, "ROADMAP.md");
|
|
19
21
|
const readFile = seams.readFile ?? ((p) => readFileSync(p, "utf8"));
|
|
20
22
|
const writeFile = seams.writeFile ?? ((p, c) => writeFileSync(p, c, "utf8"));
|
|
21
23
|
const fileExists = seams.fileExists ?? ((p) => existsSync(p));
|
|
@@ -149,6 +151,7 @@ export function runPipeline(config, seams = {}) {
|
|
|
149
151
|
emit(6, label, `DRYRUN (would rewrite CHANGELOG.md: ## [Unreleased] -> ## [${version}] - ${today}; new compare link added;${summaryNote})`);
|
|
150
152
|
}
|
|
151
153
|
else {
|
|
154
|
+
assertProjectionContained(projectRoot, changelogPath);
|
|
152
155
|
writeFile(changelogPath, promotedChangelog);
|
|
153
156
|
emit(6, label, `OK (## [${version}] - ${today};${summaryNote})`);
|
|
154
157
|
}
|
|
@@ -158,6 +161,7 @@ export function runPipeline(config, seams = {}) {
|
|
|
158
161
|
emit(7, label, "DRYRUN (would run task roadmap:render)");
|
|
159
162
|
}
|
|
160
163
|
else {
|
|
164
|
+
assertProjectionContained(projectRoot, roadmapPath);
|
|
161
165
|
const [ok, reason] = refreshRoadmapFn(projectRoot);
|
|
162
166
|
if (ok) {
|
|
163
167
|
emit(7, label, `OK (${reason})`);
|
|
@@ -8,4 +8,6 @@ export declare function isPrereleaseTag(version: string): boolean;
|
|
|
8
8
|
/** Normalize a semver-shaped release tag to a PEP 440 version string (#771). */
|
|
9
9
|
export declare function toPep440(version: string): string;
|
|
10
10
|
export declare function isPublishable(version: string): boolean;
|
|
11
|
+
/** Compare two publishable Deft release versions using stable/prerelease ordering. */
|
|
12
|
+
export declare function comparePublishableVersions(left: string, right: string): -1 | 0 | 1;
|
|
11
13
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/release/version.js
CHANGED
|
@@ -5,6 +5,12 @@ const PRE_KIND_MAP = {
|
|
|
5
5
|
rc: "rc",
|
|
6
6
|
};
|
|
7
7
|
const NON_PUBLISHABLE_KINDS = new Set(["test"]);
|
|
8
|
+
const PRERELEASE_RANK = {
|
|
9
|
+
alpha: 0,
|
|
10
|
+
beta: 1,
|
|
11
|
+
rc: 2,
|
|
12
|
+
"": 3,
|
|
13
|
+
};
|
|
8
14
|
export class NonPublishableVersionError extends Error {
|
|
9
15
|
constructor(message) {
|
|
10
16
|
super(message);
|
|
@@ -71,4 +77,40 @@ export function isPublishable(version) {
|
|
|
71
77
|
return false;
|
|
72
78
|
}
|
|
73
79
|
}
|
|
80
|
+
function publishableVersionSortKey(version) {
|
|
81
|
+
const candidate = version.trim();
|
|
82
|
+
const match = PEP440_TAG_RE.exec(candidate);
|
|
83
|
+
if (match?.groups === undefined) {
|
|
84
|
+
throw new Error(`Cannot compare '${candidate}': expected ` + "[v]X.Y.Z or [v]X.Y.Z-(rc|alpha|beta).N");
|
|
85
|
+
}
|
|
86
|
+
const kind = match.groups.kind ?? "";
|
|
87
|
+
if (NON_PUBLISHABLE_KINDS.has(kind)) {
|
|
88
|
+
throw new NonPublishableVersionError(`Version '${candidate}' carries non-publishable pre-release tag '${kind}'.${match.groups.num}.`);
|
|
89
|
+
}
|
|
90
|
+
const rank = PRERELEASE_RANK[kind];
|
|
91
|
+
if (rank === undefined) {
|
|
92
|
+
throw new Error(`Cannot compare '${candidate}': unsupported pre-release kind '${kind}'.`);
|
|
93
|
+
}
|
|
94
|
+
return [
|
|
95
|
+
Number(match.groups.major),
|
|
96
|
+
Number(match.groups.minor),
|
|
97
|
+
Number(match.groups.patch),
|
|
98
|
+
rank,
|
|
99
|
+
Number(match.groups.num ?? 0),
|
|
100
|
+
];
|
|
101
|
+
}
|
|
102
|
+
/** Compare two publishable Deft release versions using stable/prerelease ordering. */
|
|
103
|
+
export function comparePublishableVersions(left, right) {
|
|
104
|
+
const leftKey = publishableVersionSortKey(left);
|
|
105
|
+
const rightKey = publishableVersionSortKey(right);
|
|
106
|
+
for (let index = 0; index < leftKey.length; index += 1) {
|
|
107
|
+
const leftPart = leftKey[index] ?? 0;
|
|
108
|
+
const rightPart = rightKey[index] ?? 0;
|
|
109
|
+
if (leftPart < rightPart)
|
|
110
|
+
return -1;
|
|
111
|
+
if (leftPart > rightPart)
|
|
112
|
+
return 1;
|
|
113
|
+
}
|
|
114
|
+
return 0;
|
|
115
|
+
}
|
|
74
116
|
//# sourceMappingURL=version.js.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { defaultWhich
|
|
3
|
+
import { defaultWhich } from "../release/spawn.js";
|
|
4
|
+
import { resolveCommandOnPath, spawnCommandText } from "../verify-env/command-spawn.js";
|
|
4
5
|
import { MODULE_NOT_FOUND_MARKERS, NPM_BUILD_TIMEOUT_SECONDS, NPM_E2E_REHEARSAL_TAG, NPM_INSTALL_RUN_TIMEOUT_SECONDS, NPM_INSTALL_TIMEOUT_SECONDS, NPM_PACK_TIMEOUT_SECONDS, NPM_PUBLISH_DRYRUN_TIMEOUT_SECONDS, NPM_PUBLISH_PACKAGES, } from "./constants.js";
|
|
5
6
|
/**
|
|
6
7
|
* npm publish dry-run rehearsal (#1910) -- the TS port of the
|
|
@@ -18,19 +19,32 @@ function resolveWhich(seams) {
|
|
|
18
19
|
* available.
|
|
19
20
|
*/
|
|
20
21
|
export function resolvePnpm(seams = {}) {
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
// Injected which seam wins for unit tests; production uses PATHEXT-aware PATH scan
|
|
23
|
+
// so `where pnpm` extensionless shims do not ENOENT under spawnSync (#2548 / #2467).
|
|
24
|
+
if (seams.which ?? seams.whichGh) {
|
|
25
|
+
const which = resolveWhich(seams);
|
|
26
|
+
const pnpm = which("pnpm");
|
|
27
|
+
if (pnpm) {
|
|
28
|
+
return [pnpm];
|
|
29
|
+
}
|
|
30
|
+
const corepack = which("corepack");
|
|
31
|
+
if (corepack) {
|
|
32
|
+
return [corepack, "pnpm"];
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
const pnpm = resolveCommandOnPath("pnpm");
|
|
23
37
|
if (pnpm) {
|
|
24
38
|
return [pnpm];
|
|
25
39
|
}
|
|
26
|
-
const corepack =
|
|
40
|
+
const corepack = resolveCommandOnPath("corepack");
|
|
27
41
|
if (corepack) {
|
|
28
42
|
return [corepack, "pnpm"];
|
|
29
43
|
}
|
|
30
44
|
return null;
|
|
31
45
|
}
|
|
32
46
|
function runNpmStep(cmd, cwd, env, label, timeoutSeconds, seams) {
|
|
33
|
-
const spawn = seams.spawnText ??
|
|
47
|
+
const spawn = seams.spawnText ?? spawnCommandText;
|
|
34
48
|
const head = cmd[0] ?? "";
|
|
35
49
|
const result = spawn(head, cmd.slice(1), {
|
|
36
50
|
cwd,
|
|
@@ -217,7 +231,7 @@ export function rehearseNpmInstallAndRun(cloneDir, version, seams = {}, options
|
|
|
217
231
|
[ok, reason] = runNpmStep(installArgs, consumerDir, env, "npm install packed tarballs", NPM_INSTALL_RUN_TIMEOUT_SECONDS, seams);
|
|
218
232
|
if (!ok)
|
|
219
233
|
return [false, reason];
|
|
220
|
-
const spawn = seams.spawnText ??
|
|
234
|
+
const spawn = seams.spawnText ?? spawnCommandText;
|
|
221
235
|
const cliBin = join(consumerDir, "node_modules", "@deftai", "directive", "dist", "bin.js");
|
|
222
236
|
// Liveness probe (#2010): `--version` loads the cli + core engine via
|
|
223
237
|
// engineInfo(), exercising the cross-package import path that #1993 broke,
|
package/dist/scope/transition.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, renameSync } from "node:fs";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
3
|
import { InstrumentedVbriefCrud, persistCrudMetrics } from "../eval/crud-telemetry.js";
|
|
4
|
+
import { assertProjectionContained, ProjectionContainmentError, } from "../fs/projection-containment.js";
|
|
4
5
|
import { hasArtifactSuffix } from "../layout/resolve.js";
|
|
5
6
|
import { append, canonicalLogPath, newDecisionId } from "./audit-log.js";
|
|
6
7
|
import { stampCompletionMetadata } from "./capacity-stamp.js";
|
|
@@ -76,11 +77,25 @@ export function runTransition(action, filePath, now = new Date()) {
|
|
|
76
77
|
message: `No-op: ${basename} is already in ${currentFolder}/ (status: ${currentStatus})`,
|
|
77
78
|
};
|
|
78
79
|
}
|
|
80
|
+
const vbriefRoot = dirname(dirname(resolvedPath));
|
|
81
|
+
const projectRoot = dirname(vbriefRoot);
|
|
82
|
+
if (targetFolder !== null) {
|
|
83
|
+
const destDir = join(vbriefRoot, targetFolder);
|
|
84
|
+
try {
|
|
85
|
+
// #2447: refuse lifecycle moves when the destination folder escapes the checkout.
|
|
86
|
+
// Run before mutating the source file so a refusal leaves lifecycle state intact.
|
|
87
|
+
assertProjectionContained(projectRoot, destDir);
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
if (err instanceof ProjectionContainmentError) {
|
|
91
|
+
return { ok: false, message: err.message };
|
|
92
|
+
}
|
|
93
|
+
throw err;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
79
96
|
const nowIso = utcNowIso(now);
|
|
80
97
|
planObj.status = targetStatus;
|
|
81
98
|
planObj.updated = nowIso;
|
|
82
|
-
const vbriefRoot = dirname(dirname(resolvedPath));
|
|
83
|
-
const projectRoot = dirname(vbriefRoot);
|
|
84
99
|
if (act === "complete") {
|
|
85
100
|
stampCompletionMetadata(planObj, projectRoot, nowIso);
|
|
86
101
|
}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
+
import { ProjectionContainmentError } from "../fs/projection-containment.js";
|
|
1
2
|
import { type ResolveLifecycleArtifactRefOptions } from "../layout/lifecycle-ref.js";
|
|
3
|
+
export { ProjectionContainmentError as LifecycleRefContainmentError };
|
|
4
|
+
/** Reject absolute or `..`-escaping lifecycle refs before path resolution (#2470). */
|
|
5
|
+
export declare function rejectEscapingLifecycleRel(rel: string): void;
|
|
6
|
+
/** Assert a resolved lifecycle artifact path stays under the lifecycle root (#2470). */
|
|
7
|
+
export declare function assertLifecycleArtifactContained(lifecycleRoot: string, resolvedPath: string): void;
|
|
2
8
|
/** Resolve a vBRIEF reference URI to an absolute path, or null. */
|
|
3
9
|
export declare function resolveVbriefRef(uri: unknown, vbriefDir: string, options?: ResolveLifecycleArtifactRefOptions): string | null;
|
|
4
10
|
/** Collect planRef values from the plan root and top-level items. */
|
package/dist/scope/vbrief-ref.js
CHANGED
|
@@ -1,7 +1,23 @@
|
|
|
1
|
-
import { resolve, sep } from "node:path";
|
|
1
|
+
import { isAbsolute, resolve, sep } from "node:path";
|
|
2
2
|
import { referenceTypeMatches } from "@deftai/directive-types";
|
|
3
|
+
import { assertProjectionContained, ProjectionContainmentError, } from "../fs/projection-containment.js";
|
|
3
4
|
import { resolveLifecycleArtifactRef, } from "../layout/lifecycle-ref.js";
|
|
4
5
|
import { hasArtifactSuffix, stripArtifactSuffix } from "../layout/resolve.js";
|
|
6
|
+
export { ProjectionContainmentError as LifecycleRefContainmentError };
|
|
7
|
+
/** Reject absolute or `..`-escaping lifecycle refs before path resolution (#2470). */
|
|
8
|
+
export function rejectEscapingLifecycleRel(rel) {
|
|
9
|
+
if (isAbsolute(rel)) {
|
|
10
|
+
throw new ProjectionContainmentError(`lifecycle ref refused: absolute path ${rel} is not allowed under the lifecycle root`, { projectDir: "", targetPath: rel, offendingPath: rel });
|
|
11
|
+
}
|
|
12
|
+
const segments = rel.split(/[/\\]+/).filter((segment) => segment.length > 0);
|
|
13
|
+
if (segments.includes("..")) {
|
|
14
|
+
throw new ProjectionContainmentError(`lifecycle ref refused: path ${rel} contains parent traversal (..)`, { projectDir: "", targetPath: rel, offendingPath: rel });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** Assert a resolved lifecycle artifact path stays under the lifecycle root (#2470). */
|
|
18
|
+
export function assertLifecycleArtifactContained(lifecycleRoot, resolvedPath) {
|
|
19
|
+
assertProjectionContained(lifecycleRoot, resolvedPath);
|
|
20
|
+
}
|
|
5
21
|
/** Resolve a vBRIEF reference URI to an absolute path, or null. */
|
|
6
22
|
export function resolveVbriefRef(uri, vbriefDir, options = {}) {
|
|
7
23
|
if (typeof uri !== "string" || uri.length === 0) {
|
|
@@ -17,7 +33,10 @@ export function resolveVbriefRef(uri, vbriefDir, options = {}) {
|
|
|
17
33
|
else {
|
|
18
34
|
rel = uri;
|
|
19
35
|
}
|
|
20
|
-
|
|
36
|
+
rejectEscapingLifecycleRel(rel);
|
|
37
|
+
const resolved = resolveLifecycleArtifactRef(rel, vbriefDir, options);
|
|
38
|
+
assertLifecycleArtifactContained(vbriefDir, resolved);
|
|
39
|
+
return resolved;
|
|
21
40
|
}
|
|
22
41
|
/** Collect planRef values from the plan root and top-level items. */
|
|
23
42
|
export function collectPlanRefs(plan) {
|