@deftai/directive-core 0.76.0 → 0.78.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.
Files changed (70) hide show
  1. package/dist/agents-md-budget/evaluate.d.ts +8 -1
  2. package/dist/agents-md-budget/evaluate.js +54 -19
  3. package/dist/content-contracts/skills/skill-frontmatter.js +4 -0
  4. package/dist/doctor/constants.js +3 -3
  5. package/dist/doctor/index.d.ts +1 -0
  6. package/dist/doctor/index.js +1 -0
  7. package/dist/doctor/main.d.ts +1 -0
  8. package/dist/doctor/main.js +39 -1
  9. package/dist/doctor/payload-staleness.js +62 -63
  10. package/dist/doctor/release-availability.d.ts +28 -0
  11. package/dist/doctor/release-availability.js +35 -0
  12. package/dist/doctor/types.d.ts +3 -0
  13. package/dist/eval/crud-telemetry.d.ts +5 -4
  14. package/dist/eval/crud-telemetry.js +9 -5
  15. package/dist/eval/health.d.ts +5 -4
  16. package/dist/eval/health.js +24 -16
  17. package/dist/eval/readback.js +2 -8
  18. package/dist/eval/triggers.d.ts +76 -0
  19. package/dist/eval/triggers.js +259 -0
  20. package/dist/eval-triggers-relocation/evaluate.d.ts +36 -0
  21. package/dist/eval-triggers-relocation/evaluate.js +115 -0
  22. package/dist/eval-triggers-relocation/index.d.ts +2 -0
  23. package/dist/eval-triggers-relocation/index.js +2 -0
  24. package/dist/hooks/dispatcher.d.ts +43 -0
  25. package/dist/hooks/dispatcher.js +171 -0
  26. package/dist/hooks/index.d.ts +3 -0
  27. package/dist/hooks/index.js +3 -0
  28. package/dist/hooks/scope.d.ts +12 -0
  29. package/dist/hooks/scope.js +46 -0
  30. package/dist/hooks/tools.d.ts +4 -0
  31. package/dist/hooks/tools.js +23 -0
  32. package/dist/index.d.ts +1 -0
  33. package/dist/index.js +1 -0
  34. package/dist/init-deposit/agent-hooks.d.ts +22 -0
  35. package/dist/init-deposit/agent-hooks.js +228 -0
  36. package/dist/init-deposit/hygiene.js +3 -0
  37. package/dist/init-deposit/index.d.ts +1 -0
  38. package/dist/init-deposit/index.js +1 -0
  39. package/dist/init-deposit/init-deposit.js +2 -0
  40. package/dist/init-deposit/refresh.js +2 -0
  41. package/dist/intake/reconcile-issues.d.ts +1 -0
  42. package/dist/intake/reconcile-issues.js +51 -49
  43. package/dist/metrics/index.d.ts +2 -0
  44. package/dist/metrics/index.js +2 -0
  45. package/dist/metrics/resolve-metrics-home.d.ts +50 -0
  46. package/dist/metrics/resolve-metrics-home.js +125 -0
  47. package/dist/plan-sequence/index.d.ts +3 -0
  48. package/dist/plan-sequence/index.js +3 -0
  49. package/dist/plan-sequence/store.d.ts +6 -0
  50. package/dist/plan-sequence/store.js +29 -0
  51. package/dist/plan-sequence/types.d.ts +79 -0
  52. package/dist/plan-sequence/types.js +220 -0
  53. package/dist/release/build-dist.js +1 -0
  54. package/dist/release/version.d.ts +2 -0
  55. package/dist/release/version.js +42 -0
  56. package/dist/release-e2e/greenfield-python-free-smoke-cli.js +40 -1
  57. package/dist/release-e2e/greenfield-python-free-smoke.d.ts +2 -0
  58. package/dist/release-e2e/greenfield-python-free-smoke.js +28 -8
  59. package/dist/release-e2e/npm-ops.js +20 -6
  60. package/dist/session/verify-session-ritual.d.ts +16 -0
  61. package/dist/session/verify-session-ritual.js +63 -0
  62. package/dist/triage/help/registry-data.d.ts +12 -1
  63. package/dist/triage/help/registry-data.js +22 -1
  64. package/dist/verify-env/agent-hooks.d.ts +11 -0
  65. package/dist/verify-env/agent-hooks.js +45 -0
  66. package/dist/verify-env/command-spawn.d.ts +29 -0
  67. package/dist/verify-env/command-spawn.js +97 -0
  68. package/dist/verify-env/index.d.ts +2 -0
  69. package/dist/verify-env/index.js +2 -0
  70. package/package.json +19 -3
@@ -188,6 +188,52 @@ function splitRepoSlug(repo) {
188
188
  }
189
189
  return [parts[0], parts[1]];
190
190
  }
191
+ function normalizeRestIssueState(raw) {
192
+ if (typeof raw !== "string" || raw.length === 0) {
193
+ return "NOT_FOUND";
194
+ }
195
+ return raw.toUpperCase();
196
+ }
197
+ function normalizeRestStateReason(raw) {
198
+ if (typeof raw !== "string" || raw.length === 0) {
199
+ return null;
200
+ }
201
+ return raw.toUpperCase();
202
+ }
203
+ function isRestNotFoundResult(result) {
204
+ const stderr = result.stderr.trim();
205
+ return (stderr.includes("404") ||
206
+ stderr.includes("Not Found") ||
207
+ stderr.includes("Could not resolve to an Issue"));
208
+ }
209
+ function issueStateFromRestPayload(payload) {
210
+ return new IssueState(normalizeRestIssueState(payload.state), normalizeRestStateReason(payload.state_reason));
211
+ }
212
+ function fetchOneIssueStateRest(owner, name, issueNumber, scmCall, cwd) {
213
+ let result;
214
+ try {
215
+ result = scmCall("github-issue", "api", [`repos/${owner}/${name}/issues/${issueNumber}`], {
216
+ timeout: 30,
217
+ cwd,
218
+ });
219
+ }
220
+ catch {
221
+ return "CLI_MISSING";
222
+ }
223
+ if (result.returncode !== 0) {
224
+ if (isRestNotFoundResult(result)) {
225
+ return new IssueState("NOT_FOUND", null);
226
+ }
227
+ process.stderr.write(`Error: gh REST failed fetching issue #${issueNumber}: ${result.stderr.trim()}\n`);
228
+ return null;
229
+ }
230
+ try {
231
+ return issueStateFromRestPayload(JSON.parse(result.stdout));
232
+ }
233
+ catch {
234
+ return null;
235
+ }
236
+ }
191
237
  export function fetchIssueStates(repo, issueNumbers, options = {}) {
192
238
  if (issueNumbers.size === 0) {
193
239
  return new Map();
@@ -198,63 +244,19 @@ export function fetchIssueStates(repo, issueNumbers, options = {}) {
198
244
  return null;
199
245
  }
200
246
  const [owner, name] = parsed;
201
- const batchSize = options.batchSize ?? GRAPHQL_BATCH_SIZE;
202
247
  const sortedNumbers = [...issueNumbers].sort((a, b) => a - b);
203
248
  const states = new Map();
204
249
  const scmCall = options.scmCall ?? call;
205
- for (let start = 0; start < sortedNumbers.length; start += batchSize) {
206
- const batch = sortedNumbers.slice(start, start + batchSize);
207
- const aliases = batch
208
- .map((n) => `i${n}: issue(number: ${n}) { state stateReason }`)
209
- .join("\n ");
210
- const query = `query {\n repository(owner: "${owner}", name: "${name}") {\n ${aliases}\n }\n}\n`;
211
- let result;
212
- try {
213
- result = scmCall("github-issue", "api", ["graphql", "-f", `query=${query}`], {
214
- timeout: 60,
215
- cwd: options.cwd ?? undefined,
216
- });
217
- }
218
- catch {
250
+ for (const n of sortedNumbers) {
251
+ const fetched = fetchOneIssueStateRest(owner, name, n, scmCall, options.cwd ?? undefined);
252
+ if (fetched === "CLI_MISSING") {
219
253
  process.stderr.write("Error: gh CLI not found. Install GitHub CLI.\n");
220
254
  return null;
221
255
  }
222
- let payload = null;
223
- try {
224
- payload = result.stdout ? JSON.parse(result.stdout) : null;
225
- }
226
- catch {
227
- payload = null;
228
- }
229
- if (result.returncode !== 0) {
230
- if (payload === null || typeof payload.data !== "object" || payload.data === null) {
231
- process.stderr.write(`Error: gh CLI failed: ${result.stderr.trim()}\n`);
232
- return null;
233
- }
234
- const firstLine = result.stderr.trim().split("\n")[0] ?? "";
235
- process.stderr.write(`Warning: gh GraphQL returned partial errors (likely PR numbers referenced as issues): ${firstLine}\n`);
236
- }
237
- if (payload === null) {
238
- process.stderr.write("Error: failed to parse gh CLI graphql output.\n");
239
- return null;
240
- }
241
- const repoData = payload.data?.repository;
242
- if (repoData === null || typeof repoData !== "object" || Array.isArray(repoData)) {
243
- process.stderr.write("Error: gh CLI graphql response missing repository payload.\n");
256
+ if (fetched === null) {
244
257
  return null;
245
258
  }
246
- for (const n of batch) {
247
- const node = repoData[`i${n}`];
248
- if (node !== null && typeof node === "object" && !Array.isArray(node)) {
249
- const nodeObj = node;
250
- if (typeof nodeObj.state === "string") {
251
- const reason = typeof nodeObj.stateReason === "string" ? nodeObj.stateReason : null;
252
- states.set(n, new IssueState(nodeObj.state, reason));
253
- continue;
254
- }
255
- }
256
- states.set(n, new IssueState("NOT_FOUND", null));
257
- }
259
+ states.set(n, fetched);
258
260
  }
259
261
  return states;
260
262
  }
@@ -0,0 +1,2 @@
1
+ export * from "./resolve-metrics-home.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,2 @@
1
+ export * from "./resolve-metrics-home.js";
2
+ //# sourceMappingURL=index.js.map
@@ -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
@@ -15,6 +15,7 @@ export const DEFAULT_EXCLUDES = new Set([
15
15
  ".mypy_cache",
16
16
  ".ruff_cache",
17
17
  ".coverage",
18
+ "coverage",
18
19
  ]);
19
20
  export const DEFAULT_EXCLUDED_PATH_PREFIXES = [
20
21
  "history/archive",
@@ -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