@deftai/directive-core 0.76.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 +54 -19
- 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/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/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/release/version.d.ts +2 -0
- package/dist/release/version.js +42 -0
- package/dist/release-e2e/npm-ops.js +20 -6
- 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/index.js
CHANGED
|
@@ -25,6 +25,7 @@ export * as intake from "./intake/index.js";
|
|
|
25
25
|
export * as layout from "./layout/index.js";
|
|
26
26
|
export * as legacyBridge from "./legacy-bridge/index.js";
|
|
27
27
|
export * as lifecycle from "./lifecycle/index.js";
|
|
28
|
+
export * as metrics from "./metrics/index.js";
|
|
28
29
|
export * as orchestration from "./orchestration/index.js";
|
|
29
30
|
export * as packs from "./packs/index.js";
|
|
30
31
|
export * as platform from "./platform/index.js";
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared metrics home resolver (#2545).
|
|
3
|
+
*
|
|
4
|
+
* Helped (value / CRUD) and health (eval:health) append logs are machine-local
|
|
5
|
+
* telemetry — not project source. They resolve outside the git worktree via an
|
|
6
|
+
* explicit ladder shared with USER.md platform discovery (#2271 / #2544):
|
|
7
|
+
*
|
|
8
|
+
* 1. `DEFT_METRICS_HOME` or `DEFT_EVAL_HOME` — explicit override (CI / headless)
|
|
9
|
+
* 2. `<projectRoot>/.deft/metrics/` — workspace-local opt-in when
|
|
10
|
+
* `DEFT_METRICS_PROJECT_LOCAL=1`
|
|
11
|
+
* 3. Platform user-data (`%APPDATA%\deft\metrics` / `~/.config/deft/metrics`)
|
|
12
|
+
* 4. Soft-disable when the resolved root is not writable — never fall back to
|
|
13
|
+
* `xbrief/.eval/results/`.
|
|
14
|
+
*/
|
|
15
|
+
export declare const METRICS_DISABLED_DIAGNOSTIC = "metrics persistence disabled";
|
|
16
|
+
export declare const DEFT_METRICS_HOME_ENV = "DEFT_METRICS_HOME";
|
|
17
|
+
export declare const DEFT_EVAL_HOME_ENV = "DEFT_EVAL_HOME";
|
|
18
|
+
export declare const DEFT_METRICS_PROJECT_LOCAL_ENV = "DEFT_METRICS_PROJECT_LOCAL";
|
|
19
|
+
export declare const PROJECT_LOCAL_METRICS_DIR = ".deft/metrics";
|
|
20
|
+
export declare const HELPED_METRICS_FILENAME = "crud-metrics.jsonl";
|
|
21
|
+
export declare const HEALTH_METRICS_FILENAME = "health-history.jsonl";
|
|
22
|
+
export type MetricsHomeRung = "env-override" | "project-local" | "platform-user-data" | "disabled";
|
|
23
|
+
export interface ResolveMetricsHomeResult {
|
|
24
|
+
/** Absolute metrics root, or null when persistence is disabled. */
|
|
25
|
+
readonly root: string | null;
|
|
26
|
+
readonly rung: MetricsHomeRung;
|
|
27
|
+
/** False when no writable metrics root could be resolved. */
|
|
28
|
+
readonly enabled: boolean;
|
|
29
|
+
readonly diagnostic: string;
|
|
30
|
+
}
|
|
31
|
+
export interface ResolveMetricsHomeOptions {
|
|
32
|
+
readonly projectRoot?: string;
|
|
33
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
34
|
+
readonly platform?: NodeJS.Platform;
|
|
35
|
+
readonly homeDir?: string;
|
|
36
|
+
/** Writable probe. Defaults to mkdir + W_OK access check. */
|
|
37
|
+
readonly probeWritable?: (path: string) => boolean;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Resolve the platform user-data metrics directory (`%APPDATA%\deft\metrics` /
|
|
41
|
+
* `~/.config/deft/metrics`).
|
|
42
|
+
*/
|
|
43
|
+
export declare function platformMetricsDir(platform: NodeJS.Platform, env: NodeJS.ProcessEnv, homeDir: string): string;
|
|
44
|
+
/** Resolve the metrics home root across the first-hit-wins ladder (#2545). */
|
|
45
|
+
export declare function resolveMetricsHome(options?: ResolveMetricsHomeOptions): ResolveMetricsHomeResult;
|
|
46
|
+
/** Absolute path to the helped / value metrics ledger (`helped/crud-metrics.jsonl`). */
|
|
47
|
+
export declare function helpedMetricsHistoryPath(projectRoot: string, options?: ResolveMetricsHomeOptions): string | null;
|
|
48
|
+
/** Absolute path to the health metrics ledger (`health/health-history.jsonl`). */
|
|
49
|
+
export declare function healthMetricsHistoryPath(projectRoot: string, options?: ResolveMetricsHomeOptions): string | null;
|
|
50
|
+
//# sourceMappingURL=resolve-metrics-home.d.ts.map
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared metrics home resolver (#2545).
|
|
3
|
+
*
|
|
4
|
+
* Helped (value / CRUD) and health (eval:health) append logs are machine-local
|
|
5
|
+
* telemetry — not project source. They resolve outside the git worktree via an
|
|
6
|
+
* explicit ladder shared with USER.md platform discovery (#2271 / #2544):
|
|
7
|
+
*
|
|
8
|
+
* 1. `DEFT_METRICS_HOME` or `DEFT_EVAL_HOME` — explicit override (CI / headless)
|
|
9
|
+
* 2. `<projectRoot>/.deft/metrics/` — workspace-local opt-in when
|
|
10
|
+
* `DEFT_METRICS_PROJECT_LOCAL=1`
|
|
11
|
+
* 3. Platform user-data (`%APPDATA%\deft\metrics` / `~/.config/deft/metrics`)
|
|
12
|
+
* 4. Soft-disable when the resolved root is not writable — never fall back to
|
|
13
|
+
* `xbrief/.eval/results/`.
|
|
14
|
+
*/
|
|
15
|
+
import { accessSync, constants, mkdirSync } from "node:fs";
|
|
16
|
+
import { homedir } from "node:os";
|
|
17
|
+
import { join, resolve } from "node:path";
|
|
18
|
+
import { platformUserConfigDir } from "../user-config/resolve-user-md.js";
|
|
19
|
+
export const METRICS_DISABLED_DIAGNOSTIC = "metrics persistence disabled";
|
|
20
|
+
export const DEFT_METRICS_HOME_ENV = "DEFT_METRICS_HOME";
|
|
21
|
+
export const DEFT_EVAL_HOME_ENV = "DEFT_EVAL_HOME";
|
|
22
|
+
export const DEFT_METRICS_PROJECT_LOCAL_ENV = "DEFT_METRICS_PROJECT_LOCAL";
|
|
23
|
+
export const PROJECT_LOCAL_METRICS_DIR = ".deft/metrics";
|
|
24
|
+
export const HELPED_METRICS_FILENAME = "crud-metrics.jsonl";
|
|
25
|
+
export const HEALTH_METRICS_FILENAME = "health-history.jsonl";
|
|
26
|
+
function defaultProbeWritable(path) {
|
|
27
|
+
try {
|
|
28
|
+
mkdirSync(path, { recursive: true });
|
|
29
|
+
accessSync(path, constants.W_OK);
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function metricsEnvOverride(env) {
|
|
37
|
+
const primary = env[DEFT_METRICS_HOME_ENV]?.trim();
|
|
38
|
+
if (primary) {
|
|
39
|
+
return primary;
|
|
40
|
+
}
|
|
41
|
+
const legacy = env[DEFT_EVAL_HOME_ENV]?.trim();
|
|
42
|
+
return legacy || null;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Resolve the platform user-data metrics directory (`%APPDATA%\deft\metrics` /
|
|
46
|
+
* `~/.config/deft/metrics`).
|
|
47
|
+
*/
|
|
48
|
+
export function platformMetricsDir(platform, env, homeDir) {
|
|
49
|
+
return join(platformUserConfigDir(platform, env, homeDir), "metrics");
|
|
50
|
+
}
|
|
51
|
+
/** Resolve the metrics home root across the first-hit-wins ladder (#2545). */
|
|
52
|
+
export function resolveMetricsHome(options = {}) {
|
|
53
|
+
const env = options.env ?? process.env;
|
|
54
|
+
const platform = options.platform ?? process.platform;
|
|
55
|
+
const homeDir = options.homeDir ?? homedir();
|
|
56
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
57
|
+
const probeWritable = options.probeWritable ?? defaultProbeWritable;
|
|
58
|
+
const override = metricsEnvOverride(env);
|
|
59
|
+
if (override) {
|
|
60
|
+
const root = resolve(override);
|
|
61
|
+
if (probeWritable(root)) {
|
|
62
|
+
return {
|
|
63
|
+
root,
|
|
64
|
+
rung: "env-override",
|
|
65
|
+
enabled: true,
|
|
66
|
+
diagnostic: `metrics home resolved from ${DEFT_METRICS_HOME_ENV}/${DEFT_EVAL_HOME_ENV}: ${root}`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
root: null,
|
|
71
|
+
rung: "disabled",
|
|
72
|
+
enabled: false,
|
|
73
|
+
diagnostic: `${METRICS_DISABLED_DIAGNOSTIC} (${DEFT_METRICS_HOME_ENV} not writable: ${root})`,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
if (env[DEFT_METRICS_PROJECT_LOCAL_ENV] === "1") {
|
|
77
|
+
const root = resolve(join(projectRoot, PROJECT_LOCAL_METRICS_DIR));
|
|
78
|
+
if (probeWritable(root)) {
|
|
79
|
+
return {
|
|
80
|
+
root,
|
|
81
|
+
rung: "project-local",
|
|
82
|
+
enabled: true,
|
|
83
|
+
diagnostic: `metrics home resolved from workspace-local opt-in: ${root}`,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
root: null,
|
|
88
|
+
rung: "disabled",
|
|
89
|
+
enabled: false,
|
|
90
|
+
diagnostic: `${METRICS_DISABLED_DIAGNOSTIC} (workspace-local metrics dir not writable: ${root})`,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const root = platformMetricsDir(platform, env, homeDir);
|
|
94
|
+
if (probeWritable(root)) {
|
|
95
|
+
return {
|
|
96
|
+
root,
|
|
97
|
+
rung: "platform-user-data",
|
|
98
|
+
enabled: true,
|
|
99
|
+
diagnostic: `metrics home resolved from platform user-data: ${root}`,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
root: null,
|
|
104
|
+
rung: "disabled",
|
|
105
|
+
enabled: false,
|
|
106
|
+
diagnostic: `${METRICS_DISABLED_DIAGNOSTIC} (platform user-data not writable: ${root})`,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/** Absolute path to the helped / value metrics ledger (`helped/crud-metrics.jsonl`). */
|
|
110
|
+
export function helpedMetricsHistoryPath(projectRoot, options = {}) {
|
|
111
|
+
const home = resolveMetricsHome({ ...options, projectRoot });
|
|
112
|
+
if (!home.enabled || home.root === null) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
return join(home.root, "helped", HELPED_METRICS_FILENAME);
|
|
116
|
+
}
|
|
117
|
+
/** Absolute path to the health metrics ledger (`health/health-history.jsonl`). */
|
|
118
|
+
export function healthMetricsHistoryPath(projectRoot, options = {}) {
|
|
119
|
+
const home = resolveMetricsHome({ ...options, projectRoot });
|
|
120
|
+
if (!home.enabled || home.root === null) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
return join(home.root, "health", HEALTH_METRICS_FILENAME);
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=resolve-metrics-home.js.map
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { clearPlanSequence, planSequencePath, readPlanSequence, writePlanSequence, } from "./store.js";
|
|
2
|
+
export { advancePlanSequence, type ContinuationResolution, createPlanSequence, EXHAUSTED_FAIL_CLOSED_MESSAGE, EXPLICIT_QUEUE_PHRASES, isExplicitQueueAsk, isPlanFirstPhrase, PLAN_FIRST_PHRASES, PLAN_SEQUENCE_CONTRACT, PLAN_SEQUENCE_FILENAME, type PlanSequence, type PlanSequenceEntry, type PlanSequenceKind, type PlanSequenceVerifyInput, type PlanSequenceVerifyResult, type PlanTargetKind, parsePlanSequence, resolveContinuation, verifyPlanTarget, } from "./types.js";
|
|
3
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { clearPlanSequence, planSequencePath, readPlanSequence, writePlanSequence, } from "./store.js";
|
|
2
|
+
export { advancePlanSequence, createPlanSequence, EXHAUSTED_FAIL_CLOSED_MESSAGE, EXPLICIT_QUEUE_PHRASES, isExplicitQueueAsk, isPlanFirstPhrase, PLAN_FIRST_PHRASES, PLAN_SEQUENCE_CONTRACT, PLAN_SEQUENCE_FILENAME, parsePlanSequence, resolveContinuation, verifyPlanTarget, } from "./types.js";
|
|
3
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -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
|
|
@@ -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,
|