@deftai/directive-core 0.100.0 → 0.101.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 (85) hide show
  1. package/dist/authz/classify.js +100 -23
  2. package/dist/authz/decompose-apply.d.ts +14 -0
  3. package/dist/authz/decompose-apply.js +93 -18
  4. package/dist/check/cached-orchestrator.d.ts +26 -5
  5. package/dist/check/cached-orchestrator.js +228 -6
  6. package/dist/check/gate-lists.d.ts +10 -4
  7. package/dist/check/gate-lists.js +19 -5
  8. package/dist/check/index.d.ts +3 -2
  9. package/dist/check/index.js +2 -1
  10. package/dist/check/named-cause.d.ts +45 -0
  11. package/dist/check/named-cause.js +121 -0
  12. package/dist/check/orchestrator.js +6 -1
  13. package/dist/doctor/help.d.ts +5 -0
  14. package/dist/doctor/help.js +31 -0
  15. package/dist/doctor/index.d.ts +2 -0
  16. package/dist/doctor/index.js +2 -0
  17. package/dist/doctor/main.js +58 -0
  18. package/dist/doctor/session-coda.d.ts +82 -0
  19. package/dist/doctor/session-coda.js +151 -0
  20. package/dist/doctor/types.d.ts +16 -0
  21. package/dist/index.d.ts +2 -0
  22. package/dist/index.js +2 -0
  23. package/dist/init-deposit/gitignore.js +2 -0
  24. package/dist/intake/issue-ingest.js +7 -0
  25. package/dist/policy/ac-pass-banking.d.ts +58 -0
  26. package/dist/policy/ac-pass-banking.js +150 -0
  27. package/dist/policy/ceremony-dial.d.ts +29 -0
  28. package/dist/policy/ceremony-dial.js +76 -0
  29. package/dist/policy/index.d.ts +1 -0
  30. package/dist/policy/index.js +15 -1
  31. package/dist/preflight-cache/evaluate.d.ts +3 -0
  32. package/dist/preflight-cache/evaluate.js +3 -0
  33. package/dist/product-first-done-gate/acceptance.d.ts +25 -0
  34. package/dist/product-first-done-gate/acceptance.js +247 -0
  35. package/dist/product-first-done-gate/check-mode.d.ts +45 -0
  36. package/dist/product-first-done-gate/check-mode.js +148 -0
  37. package/dist/product-first-done-gate/evaluate.d.ts +50 -0
  38. package/dist/product-first-done-gate/evaluate.js +255 -0
  39. package/dist/product-first-done-gate/index.d.ts +11 -0
  40. package/dist/product-first-done-gate/index.js +11 -0
  41. package/dist/product-first-done-gate/types.d.ts +64 -0
  42. package/dist/product-first-done-gate/types.js +61 -0
  43. package/dist/run-summary/emit.d.ts +56 -0
  44. package/dist/run-summary/emit.js +171 -0
  45. package/dist/run-summary/index.d.ts +4 -0
  46. package/dist/run-summary/index.js +4 -0
  47. package/dist/run-summary/path.d.ts +25 -0
  48. package/dist/run-summary/path.js +78 -0
  49. package/dist/run-summary/types.d.ts +76 -0
  50. package/dist/run-summary/types.js +21 -0
  51. package/dist/scope/acceptance-evidence.d.ts +34 -0
  52. package/dist/scope/acceptance-evidence.js +84 -10
  53. package/dist/scope/decompose.d.ts +5 -0
  54. package/dist/scope/decompose.js +11 -2
  55. package/dist/session/ac-pass-banking.d.ts +221 -0
  56. package/dist/session/ac-pass-banking.js +761 -0
  57. package/dist/session/deposit-sha.d.ts +49 -0
  58. package/dist/session/deposit-sha.js +121 -0
  59. package/dist/session/effort-budget.d.ts +6 -0
  60. package/dist/session/effort-budget.js +21 -3
  61. package/dist/session/index.d.ts +5 -0
  62. package/dist/session/index.js +5 -0
  63. package/dist/session/orientation-compression.d.ts +127 -0
  64. package/dist/session/orientation-compression.js +425 -0
  65. package/dist/session/orientation-state.d.ts +29 -0
  66. package/dist/session/orientation-state.js +85 -0
  67. package/dist/session/session-start.d.ts +25 -0
  68. package/dist/session/session-start.js +175 -0
  69. package/dist/session/toolchain-preflight.d.ts +64 -0
  70. package/dist/session/toolchain-preflight.js +151 -0
  71. package/dist/triage/bootstrap/gitignore.d.ts +1 -1
  72. package/dist/triage/bootstrap/gitignore.js +8 -2
  73. package/dist/triage/bootstrap/index.js +15 -3
  74. package/dist/triage/classify/index.d.ts +1 -0
  75. package/dist/triage/classify/index.js +2 -0
  76. package/dist/triage/classify/label-mirror.js +16 -0
  77. package/dist/triage/classify/mirror-discovery-tip.d.ts +95 -0
  78. package/dist/triage/classify/mirror-discovery-tip.js +234 -0
  79. package/dist/triage/reconcile/reconcile.js +28 -6
  80. package/dist/triage/summary/index.js +12 -4
  81. package/dist/triage/welcome/default-mode.js +16 -0
  82. package/dist/ts-check-lane/run-lane.js +21 -4
  83. package/dist/vbrief-validate/conformance.js +7 -0
  84. package/dist/vitest-runner/coverage-debt-teardown.js +16 -4
  85. package/package.json +11 -3
@@ -3,6 +3,7 @@ export * from "./checks.js";
3
3
  export * from "./constants.js";
4
4
  export * from "./doctor-state.js";
5
5
  export * from "./flags.js";
6
+ export * from "./help.js";
6
7
  export * from "./json.js";
7
8
  export * from "./main.js";
8
9
  export * from "./manifest.js";
@@ -13,6 +14,7 @@ export * from "./openclaw-soft-rebind.js";
13
14
  export * from "./paths.js";
14
15
  export * from "./payload-staleness.js";
15
16
  export * from "./release-availability.js";
17
+ export * from "./session-coda.js";
16
18
  export * from "./taskfile.js";
17
19
  export * from "./types.js";
18
20
  export * from "./which.js";
@@ -16,6 +16,7 @@ import { checkXbriefEnvelopeMajorVersion, runChecks, XBRIEF_ENVELOPE_MAJOR_CHECK
16
16
  import { CONSUMER_FRAMEWORK_DIRS, EXPECTED_CONTENT_DIRS, EXPECTED_FRAMEWORK_DIRS, NETWORK_DISCLOSURE_LINE, PAYLOAD_STALENESS_OFFLINE_SKIP_MESSAGE, TASKFILE_INCLUDE_SNIPPET, UV_INSTALL_URL, } from "./constants.js";
17
17
  import { decideThrottle, formatIsoZ, readState, renderDoctorStatusLine, writeState, } from "./doctor-state.js";
18
18
  import { formatAllowedFlagsHint, formatUnknownFlagsError, parseDoctorFlags } from "./flags.js";
19
+ import { formatDoctorHelp } from "./help.js";
19
20
  import { pythonJsonDump } from "./json.js";
20
21
  import { parseInstallRootFromAgentsMd } from "./manifest.js";
21
22
  import { runNpmRegistryMirrorCheck } from "./npm-registry.js";
@@ -25,6 +26,7 @@ import { runOpenClawSoftRebindCheck } from "./openclaw-soft-rebind.js";
25
26
  import { createPlainSink } from "./output.js";
26
27
  import { readTextSafe, resolveFrameworkRootForProject, resolvePath, resolveVersion, runningInsideDeftRepo, } from "./paths.js";
27
28
  import { runPayloadStalenessCheck } from "./payload-staleness.js";
29
+ import { loadSessionCodas, projectRootRealpath, resolveSessionCodaLine, SESSION_CODA_ENV, sessionCodaMode, shouldEmitSessionCodaLine, utcDateYmd, } from "./session-coda.js";
28
30
  import { runLocalSignpostChecks } from "./signpost-checks.js";
29
31
  import { classifyTaskfileInclude, formatGatesSurfaceDualRemediation, formatMissingIncludeSnippet, GATES_SURFACE_DEFT_REMEDIATION, includesBlockHasDeftTaskfile, resolveConsumerTaskfile, } from "./taskfile.js";
30
32
  import { defaultWhich } from "./which.js";
@@ -58,6 +60,10 @@ export function cmdDoctor(args, seams = {}) {
58
60
  sink.info(formatAllowedFlagsHint());
59
61
  return 2;
60
62
  }
63
+ if (flags.help) {
64
+ process.stdout.write(formatDoctorHelp());
65
+ return 0;
66
+ }
61
67
  const sessionMode = flags.session;
62
68
  const fixMode = flags.fix && !sessionMode;
63
69
  const jsonMode = flags.json;
@@ -489,6 +495,15 @@ export function cmdDoctor(args, seams = {}) {
489
495
  sink.blank();
490
496
  if (errorCount === 0 && warningCount === 0) {
491
497
  sink.finalSuccess("System check passed!");
498
+ emitSessionCodaAfterFinalSuccess({
499
+ projectRoot,
500
+ frameworkRoot,
501
+ jsonMode,
502
+ exitOk: true,
503
+ now: nowFn(),
504
+ seams,
505
+ sink,
506
+ });
492
507
  return 0;
493
508
  }
494
509
  if (errorCount) {
@@ -498,8 +513,51 @@ export function cmdDoctor(args, seams = {}) {
498
513
  return 1;
499
514
  }
500
515
  sink.finalWarn(`System check completed with ${warningCount} warning(s).`);
516
+ emitSessionCodaAfterFinalSuccess({
517
+ projectRoot,
518
+ frameworkRoot,
519
+ jsonMode,
520
+ exitOk: true,
521
+ now: nowFn(),
522
+ seams,
523
+ sink,
524
+ });
501
525
  return 0;
502
526
  }
527
+ /**
528
+ * After the final human success footer only (D2): optional off-hint or ✦ coda.
529
+ * Never mid-run Next command: block; never JSON; never hard-fail path (D1).
530
+ */
531
+ function emitSessionCodaAfterFinalSuccess(input) {
532
+ const stdoutIsTty = input.seams.stdoutIsTty ?? (() => process.stdout.isTTY === true);
533
+ // `in` so tests can force unset via `{ sessionCodaEnv: undefined }` without
534
+ // leaking the host process env.
535
+ const envRaw = "sessionCodaEnv" in input.seams ? input.seams.sessionCodaEnv : process.env[SESSION_CODA_ENV];
536
+ const ciRaw = "ciEnv" in input.seams ? input.seams.ciEnv : process.env.CI;
537
+ const ci = Boolean(ciRaw && ciRaw !== "0" && String(ciRaw).toLowerCase() !== "false");
538
+ const shouldEmit = shouldEmitSessionCodaLine({
539
+ tty: stdoutIsTty(),
540
+ ci,
541
+ json: input.jsonMode,
542
+ exitOk: input.exitOk,
543
+ });
544
+ const mode = sessionCodaMode(envRaw);
545
+ const codas = mode === "on"
546
+ ? (input.seams.loadSessionCodas?.(input.frameworkRoot) ??
547
+ loadSessionCodas(input.frameworkRoot, input.seams.readText))
548
+ : [];
549
+ const line = resolveSessionCodaLine({
550
+ mode,
551
+ shouldEmit,
552
+ date: utcDateYmd(input.now),
553
+ projectRoot: projectRootRealpath(input.projectRoot),
554
+ codas,
555
+ });
556
+ if (line === null)
557
+ return;
558
+ input.sink.blank();
559
+ input.sink.raw(line);
560
+ }
503
561
  export function runAgentHooksHealthCheck(projectRoot, consumerContext, sink, addFinding, seams) {
504
562
  const checkName = "agent-hooks-registration";
505
563
  if (!consumerContext) {
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Optional deterministic session coda for interactive doctor success (#2712).
3
+ *
4
+ * Human TTY affordance only: never JSON, never AGENTS/skills, never exit-code
5
+ * changes. Env three-state DEFT_SESSION_CODA (unset / 1 / 0). Copy lives in
6
+ * content/doctor/session-coda.json — not embedded as an engine literal.
7
+ */
8
+ /** Env var name (env-only v1; no USER.md key). */
9
+ export declare const SESSION_CODA_ENV = "DEFT_SESSION_CODA";
10
+ /**
11
+ * Fixed discoverability line when env is unset and gates pass (D3).
12
+ * Snapshot-stable; not selected from the pack; no ✦ prefix.
13
+ */
14
+ export declare const SESSION_CODA_OFF_HINT = "Session coda: off (set DEFT_SESSION_CODA=1 to enable)";
15
+ /** U+2726 FOUR RAYED STAR + space — stable; do not localize. */
16
+ export declare const SESSION_CODA_STAR_PREFIX = "\u2726 ";
17
+ /** Max body length; longer pack entries are skipped (fail-open). */
18
+ export declare const SESSION_CODA_MAX_CHARS = 100;
19
+ /** Relative path under contentRoot (source: content/doctor/…; deposit: doctor/…). */
20
+ export declare const SESSION_CODA_REL_PATH: string;
21
+ export type SessionCodaMode = "unset" | "on" | "off";
22
+ /**
23
+ * Map DEFT_SESSION_CODA to the three-state mode (D3).
24
+ * - undefined / "" → unset (discoverability off-hint)
25
+ * - "1" → on (✦ coda)
26
+ * - "0" → off (silent)
27
+ * - any other non-empty value → unset (show correct enable form)
28
+ */
29
+ export declare function sessionCodaMode(envValue: string | undefined): SessionCodaMode;
30
+ /**
31
+ * Whether any extra line (off-hint or coda) is allowed (D1 + surface gates).
32
+ * TTY = stdout.isTTY; CI truthy blocks; json blocks; exitOk = errorCount === 0.
33
+ */
34
+ export declare function shouldEmitSessionCodaLine(input: {
35
+ readonly tty: boolean;
36
+ readonly ci: boolean;
37
+ readonly json: boolean;
38
+ readonly exitOk: boolean;
39
+ }): boolean;
40
+ /**
41
+ * FNV-1a 32-bit (#2712). Documented choice for hash_u32(date + "\\0" + root).
42
+ * No Math.random; same inputs ⇒ same u32 on every platform.
43
+ */
44
+ export declare function hashU32(input: string): number;
45
+ /** UTC calendar date yyyy-mm-dd from a Date (stable for 24h across a team day). */
46
+ export declare function utcDateYmd(date: Date): string;
47
+ /** Best-effort realpath; falls back to the given path when realpath fails. */
48
+ export declare function projectRootRealpath(projectRoot: string): string;
49
+ /**
50
+ * Select one coda body from the pack (enabled path only).
51
+ * index = hash_u32(utc_date + "\\0" + project_root_realpath) % len(codas)
52
+ * Empty list → null (omit ✦; doctor still 0).
53
+ */
54
+ export declare function selectSessionCoda(input: {
55
+ readonly date: string;
56
+ readonly projectRoot: string;
57
+ readonly codas: readonly string[];
58
+ }): string | null;
59
+ /** Skip non-strings and bodies over SESSION_CODA_MAX_CHARS. */
60
+ export declare function filterCodaEntries(raw: unknown): string[];
61
+ /**
62
+ * Load + filter coda lines from content. Fail-open: missing/unreadable/invalid
63
+ * / empty after filter → [] (omit ✦; no error noise).
64
+ */
65
+ export declare function loadSessionCodas(frameworkRoot: string, readText?: (path: string) => string | null): string[];
66
+ /** Format enabled coda line: ✦ + space + body. */
67
+ export declare function formatSessionCodaEnabledLine(body: string): string;
68
+ export interface ResolveSessionCodaLineInput {
69
+ readonly mode: SessionCodaMode;
70
+ readonly shouldEmit: boolean;
71
+ readonly date: string;
72
+ readonly projectRoot: string;
73
+ readonly codas: readonly string[];
74
+ }
75
+ /**
76
+ * Resolve the single extra line (or null) after final success footer.
77
+ * Does not print; caller places after blank line post footer (D2).
78
+ */
79
+ export declare function resolveSessionCodaLine(input: ResolveSessionCodaLineInput): string | null;
80
+ /** Help / env-docs fragment for DEFT_SESSION_CODA three-state (#2712). */
81
+ export declare function sessionCodaHelpText(): string;
82
+ //# sourceMappingURL=session-coda.d.ts.map
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Optional deterministic session coda for interactive doctor success (#2712).
3
+ *
4
+ * Human TTY affordance only: never JSON, never AGENTS/skills, never exit-code
5
+ * changes. Env three-state DEFT_SESSION_CODA (unset / 1 / 0). Copy lives in
6
+ * content/doctor/session-coda.json — not embedded as an engine literal.
7
+ */
8
+ import { readFileSync, realpathSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { contentRoot } from "../content-root.js";
11
+ /** Env var name (env-only v1; no USER.md key). */
12
+ export const SESSION_CODA_ENV = "DEFT_SESSION_CODA";
13
+ /**
14
+ * Fixed discoverability line when env is unset and gates pass (D3).
15
+ * Snapshot-stable; not selected from the pack; no ✦ prefix.
16
+ */
17
+ export const SESSION_CODA_OFF_HINT = "Session coda: off (set DEFT_SESSION_CODA=1 to enable)";
18
+ /** U+2726 FOUR RAYED STAR + space — stable; do not localize. */
19
+ export const SESSION_CODA_STAR_PREFIX = "\u2726 ";
20
+ /** Max body length; longer pack entries are skipped (fail-open). */
21
+ export const SESSION_CODA_MAX_CHARS = 100;
22
+ /** Relative path under contentRoot (source: content/doctor/…; deposit: doctor/…). */
23
+ export const SESSION_CODA_REL_PATH = join("doctor", "session-coda.json");
24
+ /**
25
+ * Map DEFT_SESSION_CODA to the three-state mode (D3).
26
+ * - undefined / "" → unset (discoverability off-hint)
27
+ * - "1" → on (✦ coda)
28
+ * - "0" → off (silent)
29
+ * - any other non-empty value → unset (show correct enable form)
30
+ */
31
+ export function sessionCodaMode(envValue) {
32
+ if (envValue === undefined || envValue === "")
33
+ return "unset";
34
+ if (envValue === "1")
35
+ return "on";
36
+ if (envValue === "0")
37
+ return "off";
38
+ return "unset";
39
+ }
40
+ /**
41
+ * Whether any extra line (off-hint or coda) is allowed (D1 + surface gates).
42
+ * TTY = stdout.isTTY; CI truthy blocks; json blocks; exitOk = errorCount === 0.
43
+ */
44
+ export function shouldEmitSessionCodaLine(input) {
45
+ return input.exitOk === true && input.tty === true && input.ci !== true && input.json !== true;
46
+ }
47
+ /**
48
+ * FNV-1a 32-bit (#2712). Documented choice for hash_u32(date + "\\0" + root).
49
+ * No Math.random; same inputs ⇒ same u32 on every platform.
50
+ */
51
+ export function hashU32(input) {
52
+ let hash = 0x811c9dc5;
53
+ for (let i = 0; i < input.length; i += 1) {
54
+ hash ^= input.charCodeAt(i);
55
+ hash = Math.imul(hash, 0x01000193);
56
+ }
57
+ return hash >>> 0;
58
+ }
59
+ /** UTC calendar date yyyy-mm-dd from a Date (stable for 24h across a team day). */
60
+ export function utcDateYmd(date) {
61
+ return date.toISOString().slice(0, 10);
62
+ }
63
+ /** Best-effort realpath; falls back to the given path when realpath fails. */
64
+ export function projectRootRealpath(projectRoot) {
65
+ try {
66
+ return realpathSync(projectRoot);
67
+ }
68
+ catch {
69
+ return projectRoot;
70
+ }
71
+ }
72
+ /**
73
+ * Select one coda body from the pack (enabled path only).
74
+ * index = hash_u32(utc_date + "\\0" + project_root_realpath) % len(codas)
75
+ * Empty list → null (omit ✦; doctor still 0).
76
+ */
77
+ export function selectSessionCoda(input) {
78
+ const { date, projectRoot, codas } = input;
79
+ if (codas.length === 0)
80
+ return null;
81
+ const key = `${date}\0${projectRoot}`;
82
+ const index = hashU32(key) % codas.length;
83
+ return codas[index] ?? null;
84
+ }
85
+ /** Skip non-strings and bodies over SESSION_CODA_MAX_CHARS. */
86
+ export function filterCodaEntries(raw) {
87
+ if (!Array.isArray(raw))
88
+ return [];
89
+ const out = [];
90
+ for (const entry of raw) {
91
+ if (typeof entry !== "string")
92
+ continue;
93
+ if (entry.length === 0 || entry.length > SESSION_CODA_MAX_CHARS)
94
+ continue;
95
+ out.push(entry);
96
+ }
97
+ return out;
98
+ }
99
+ /**
100
+ * Load + filter coda lines from content. Fail-open: missing/unreadable/invalid
101
+ * / empty after filter → [] (omit ✦; no error noise).
102
+ */
103
+ export function loadSessionCodas(frameworkRoot, readText) {
104
+ const packPath = join(contentRoot(frameworkRoot), SESSION_CODA_REL_PATH);
105
+ try {
106
+ const text = readText !== undefined ? readText(packPath) : readFileSync(packPath, { encoding: "utf8" });
107
+ if (text === null || text === "")
108
+ return [];
109
+ return filterCodaEntries(JSON.parse(text));
110
+ }
111
+ catch {
112
+ return [];
113
+ }
114
+ }
115
+ /** Format enabled coda line: ✦ + space + body. */
116
+ export function formatSessionCodaEnabledLine(body) {
117
+ return `${SESSION_CODA_STAR_PREFIX}${body}`;
118
+ }
119
+ /**
120
+ * Resolve the single extra line (or null) after final success footer.
121
+ * Does not print; caller places after blank line post footer (D2).
122
+ */
123
+ export function resolveSessionCodaLine(input) {
124
+ if (!input.shouldEmit)
125
+ return null;
126
+ if (input.mode === "off")
127
+ return null;
128
+ if (input.mode === "unset")
129
+ return SESSION_CODA_OFF_HINT;
130
+ const body = selectSessionCoda({
131
+ date: input.date,
132
+ projectRoot: input.projectRoot,
133
+ codas: input.codas,
134
+ });
135
+ if (body === null)
136
+ return null;
137
+ return formatSessionCodaEnabledLine(body);
138
+ }
139
+ /** Help / env-docs fragment for DEFT_SESSION_CODA three-state (#2712). */
140
+ export function sessionCodaHelpText() {
141
+ return [
142
+ "Environment:",
143
+ ` ${SESSION_CODA_ENV} Session coda after interactive success (exit 0, TTY, not CI, not --json):`,
144
+ " unset Print discoverability line:",
145
+ ` ${SESSION_CODA_OFF_HINT}`,
146
+ " =1 Print one curated line, e.g.:",
147
+ ` ${formatSessionCodaEnabledLine("Doctor first. Then continue.")}`,
148
+ " =0 Silent (no hint, no coda)",
149
+ ].join("\n");
150
+ }
151
+ //# sourceMappingURL=session-coda.js.map
@@ -123,6 +123,22 @@ export interface DoctorSeams {
123
123
  now?: Date;
124
124
  }) => string | null;
125
125
  readonly isTty?: () => boolean;
126
+ /**
127
+ * stdout TTY probe for session coda (#2712). Defaults to
128
+ * `process.stdout.isTTY === true` (not stdin). Separate from `isTty` (stdin
129
+ * used by --fix prompts).
130
+ */
131
+ readonly stdoutIsTty?: () => boolean;
132
+ /**
133
+ * Override DEFT_SESSION_CODA for tests (#2712). `undefined` means read
134
+ * process.env; pass `""` or omit via explicit nullish env simulation with
135
+ * `undefined` from a seam that returns undefined.
136
+ */
137
+ readonly sessionCodaEnv?: string | undefined;
138
+ /** Override CI env string for session coda gates (#2712). */
139
+ readonly ciEnv?: string | undefined;
140
+ /** Override session-coda pack loader for fail-open tests (#2712). */
141
+ readonly loadSessionCodas?: (frameworkRoot: string) => string[];
126
142
  readonly readYn?: (prompt: string, defaultYes: boolean) => boolean;
127
143
  readonly writeText?: (path: string, content: string) => void;
128
144
  readonly now?: () => Date;
package/dist/index.d.ts CHANGED
@@ -48,12 +48,14 @@ export * as prMonitor from "./pr-monitor/index.js";
48
48
  export * as prProtectedIssues from "./pr-protected-issues/index.js";
49
49
  export * as prWaitMergeable from "./pr-wait-mergeable/index.js";
50
50
  export * as preflight from "./preflight/index.js";
51
+ export * as productFirstDoneGate from "./product-first-done-gate/index.js";
51
52
  export * as release from "./release/index.js";
52
53
  export * as releaseE2e from "./release-e2e/index.js";
53
54
  export * as releasePublish from "./release-publish/index.js";
54
55
  export * as releaseRollback from "./release-rollback/index.js";
55
56
  export * as render from "./render/index.js";
56
57
  export * as resolution from "./resolution/index.js";
58
+ export * as runSummary from "./run-summary/index.js";
57
59
  export * as scm from "./scm/index.js";
58
60
  export * as scope from "./scope/index.js";
59
61
  export * as scopeProvenance from "./scope-provenance/index.js";
package/dist/index.js CHANGED
@@ -49,12 +49,14 @@ export * as prMonitor from "./pr-monitor/index.js";
49
49
  export * as prProtectedIssues from "./pr-protected-issues/index.js";
50
50
  export * as prWaitMergeable from "./pr-wait-mergeable/index.js";
51
51
  export * as preflight from "./preflight/index.js";
52
+ export * as productFirstDoneGate from "./product-first-done-gate/index.js";
52
53
  export * as release from "./release/index.js";
53
54
  export * as releaseE2e from "./release-e2e/index.js";
54
55
  export * as releasePublish from "./release-publish/index.js";
55
56
  export * as releaseRollback from "./release-rollback/index.js";
56
57
  export * as render from "./render/index.js";
57
58
  export * as resolution from "./resolution/index.js";
59
+ export * as runSummary from "./run-summary/index.js";
58
60
  export * as scm from "./scm/index.js";
59
61
  export * as scope from "./scope/index.js";
60
62
  export * as scopeProvenance from "./scope-provenance/index.js";
@@ -30,6 +30,8 @@ export const CANONICAL_GITIGNORE_BASELINE = [
30
30
  ".deft/ritual-state.json",
31
31
  ".deft/last-session.json",
32
32
  ".deft/routing.local.json",
33
+ // #3282: opt-in default run-summary JSONL at repo root (collectible; must not dirty trees).
34
+ ".deft-run-summary.json",
33
35
  // Temporary test/local kill-switch — must stay untracked (#3039).
34
36
  DEFT_DIRECTIVE_DISABLE_GITIGNORE_LINE,
35
37
  "vbrief/.triage-cache/candidates.jsonl",
@@ -5,6 +5,7 @@ import { scan } from "../cache/scanner.js";
5
5
  import { assertWriteTargetSafe, ProjectionContainmentError } from "../fs/projection-containment.js";
6
6
  import { hasArtifactSuffix, resolveLifecycleRoot } from "../layout/resolve.js";
7
7
  import { captureAndAttachLiteralAcceptance } from "../literal-acceptance/index.js";
8
+ import { stampAcceptanceFromLiteralCapture } from "../product-first-done-gate/index.js";
8
9
  import { call } from "../scm/call.js";
9
10
  import { resolveProjectRoot } from "../scope/project-context.js";
10
11
  import { resolveProjectRepo } from "../slice/project-context.js";
@@ -472,6 +473,12 @@ export function buildIssueVbrief(issue, status, repoUrl, options = {}) {
472
473
  }
473
474
  }
474
475
  Object.assign(plan, attached.plan);
476
+ // #3284: stamp plan.acceptance from captured literals (stated or none_stated floor).
477
+ Object.assign(plan, stampAcceptanceFromLiteralCapture(plan));
478
+ }
479
+ else {
480
+ // No body: still record none_stated acceptance so absence is a decision.
481
+ Object.assign(plan, stampAcceptanceFromLiteralCapture(plan));
475
482
  }
476
483
  const infoRootKey = options.infoRootKey ?? LEGACY_INFO_ROOT_KEY;
477
484
  const infoVersion = options.infoVersion ?? EMITTED_VBRIEF_VERSION;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Typed plan.policy.acPassBanking (#3285).
3
+ *
4
+ * Surplus-threshold policy for post-AC-pass deepening: after the first
5
+ * stated-AC bank, agents may deepen only when remaining budget is at least
6
+ * this fraction of the hard max (default 0.2 = 20%).
7
+ *
8
+ * Composes #3266 bank-the-pass and product-first done-gate (#3284).
9
+ */
10
+ /** Canonical dotted path for policy:show / PROJECT-DEFINITION. */
11
+ export declare const FIELD_AC_PASS_BANKING = "plan.policy.acPassBanking";
12
+ /** CLI alias for `task policy:show --field=acPassBanking`. */
13
+ export declare const FIELD_AC_PASS_BANKING_CLI_ALIAS = "acPassBanking";
14
+ /**
15
+ * Default surplus fraction of max turns/cost that must remain after bank
16
+ * before self-imposed deepening is allowed (#3285).
17
+ */
18
+ export declare const DEFAULT_SURPLUS_THRESHOLD = 0.2;
19
+ /** Floor / ceiling for surplusThreshold (inclusive). */
20
+ export declare const SURPLUS_THRESHOLD_MIN = 0;
21
+ export declare const SURPLUS_THRESHOLD_MAX = 1;
22
+ /** Optional env override (fraction 0–1 or percent 0–100). */
23
+ export declare const ENV_SURPLUS_THRESHOLD = "DEFT_AC_PASS_SURPLUS_THRESHOLD";
24
+ export interface AcPassBankingConfig {
25
+ /**
26
+ * When false, surplus gate is inactive (unbounded / optional discipline only).
27
+ * Default true — the gate still only binds under a hard effort budget.
28
+ */
29
+ readonly enabled: boolean;
30
+ /**
31
+ * Minimum remaining fraction of max turns/cost required to deepen after bank.
32
+ * Default {@link DEFAULT_SURPLUS_THRESHOLD} (0.2).
33
+ */
34
+ readonly surplusThreshold: number;
35
+ }
36
+ export type AcPassBankingSource = "typed" | "env" | "default" | "default-on-error";
37
+ export interface AcPassBankingResolved extends AcPassBankingConfig {
38
+ readonly source: AcPassBankingSource;
39
+ readonly error: string | null;
40
+ }
41
+ /** Parse a 0–1 fraction or a 0–100 percent string/number into a fraction. */
42
+ export declare function parseSurplusThreshold(raw: unknown): number | null;
43
+ /** Validate a `plan.policy.acPassBanking` payload. */
44
+ export declare function validateAcPassBanking(value: unknown): string[];
45
+ /**
46
+ * Resolve surplus policy from typed plan.policy.acPassBanking, then env,
47
+ * then defaults (#3285).
48
+ */
49
+ export declare function resolveAcPassBanking(projectRoot?: string | null, environ?: Readonly<Record<string, string | undefined>>): AcPassBankingResolved;
50
+ export interface AcPassBankingPolicyField {
51
+ readonly name: typeof FIELD_AC_PASS_BANKING;
52
+ readonly current: AcPassBankingConfig;
53
+ readonly default: AcPassBankingConfig;
54
+ readonly source: string;
55
+ }
56
+ /** Inspector row for `task policy:show --field=acPassBanking` (#3285). */
57
+ export declare function inspectAcPassBanking(_data: Record<string, unknown> | null, projectRoot?: string): AcPassBankingPolicyField;
58
+ //# sourceMappingURL=ac-pass-banking.d.ts.map
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Typed plan.policy.acPassBanking (#3285).
3
+ *
4
+ * Surplus-threshold policy for post-AC-pass deepening: after the first
5
+ * stated-AC bank, agents may deepen only when remaining budget is at least
6
+ * this fraction of the hard max (default 0.2 = 20%).
7
+ *
8
+ * Composes #3266 bank-the-pass and product-first done-gate (#3284).
9
+ */
10
+ import { readPlanPolicy } from "./plan-extensions.js";
11
+ import { loadProjectDefinition } from "./resolve.js";
12
+ /** Canonical dotted path for policy:show / PROJECT-DEFINITION. */
13
+ export const FIELD_AC_PASS_BANKING = "plan.policy.acPassBanking";
14
+ /** CLI alias for `task policy:show --field=acPassBanking`. */
15
+ export const FIELD_AC_PASS_BANKING_CLI_ALIAS = "acPassBanking";
16
+ /**
17
+ * Default surplus fraction of max turns/cost that must remain after bank
18
+ * before self-imposed deepening is allowed (#3285).
19
+ */
20
+ export const DEFAULT_SURPLUS_THRESHOLD = 0.2;
21
+ /** Floor / ceiling for surplusThreshold (inclusive). */
22
+ export const SURPLUS_THRESHOLD_MIN = 0;
23
+ export const SURPLUS_THRESHOLD_MAX = 1;
24
+ /** Optional env override (fraction 0–1 or percent 0–100). */
25
+ export const ENV_SURPLUS_THRESHOLD = "DEFT_AC_PASS_SURPLUS_THRESHOLD";
26
+ function defaultResolved(source, error = null, override) {
27
+ return {
28
+ enabled: override?.enabled ?? true,
29
+ surplusThreshold: override?.surplusThreshold ?? DEFAULT_SURPLUS_THRESHOLD,
30
+ source,
31
+ error,
32
+ };
33
+ }
34
+ /** Parse a 0–1 fraction or a 0–100 percent string/number into a fraction. */
35
+ export function parseSurplusThreshold(raw) {
36
+ if (typeof raw === "number" && Number.isFinite(raw)) {
37
+ if (raw >= 0 && raw <= 1)
38
+ return raw;
39
+ // Allow 20 meaning 20% when clearly a percent.
40
+ if (raw > 1 && raw <= 100)
41
+ return raw / 100;
42
+ return null;
43
+ }
44
+ if (typeof raw === "string") {
45
+ const text = raw.trim();
46
+ if (!text)
47
+ return null;
48
+ const percent = text.endsWith("%");
49
+ const n = Number(percent ? text.slice(0, -1).trim() : text);
50
+ if (!Number.isFinite(n))
51
+ return null;
52
+ if (percent) {
53
+ if (n < 0 || n > 100)
54
+ return null;
55
+ return n / 100;
56
+ }
57
+ if (n >= 0 && n <= 1)
58
+ return n;
59
+ if (n > 1 && n <= 100)
60
+ return n / 100;
61
+ return null;
62
+ }
63
+ return null;
64
+ }
65
+ /** Validate a `plan.policy.acPassBanking` payload. */
66
+ export function validateAcPassBanking(value) {
67
+ if (value === null || value === undefined) {
68
+ return [];
69
+ }
70
+ if (typeof value !== "object" || Array.isArray(value)) {
71
+ return [`${FIELD_AC_PASS_BANKING} must be an object; got ${typeof value}`];
72
+ }
73
+ const rec = value;
74
+ const errors = [];
75
+ if ("enabled" in rec && typeof rec.enabled !== "boolean") {
76
+ errors.push(`${FIELD_AC_PASS_BANKING}.enabled must be a boolean`);
77
+ }
78
+ if ("surplusThreshold" in rec) {
79
+ const parsed = parseSurplusThreshold(rec.surplusThreshold);
80
+ if (parsed === null) {
81
+ errors.push(`${FIELD_AC_PASS_BANKING}.surplusThreshold must be a fraction 0–1 ` +
82
+ `(or percent 0–100); got ${JSON.stringify(rec.surplusThreshold)}`);
83
+ }
84
+ }
85
+ return errors;
86
+ }
87
+ /**
88
+ * Resolve surplus policy from typed plan.policy.acPassBanking, then env,
89
+ * then defaults (#3285).
90
+ */
91
+ export function resolveAcPassBanking(projectRoot, environ = process.env) {
92
+ if (projectRoot !== undefined && projectRoot !== null && projectRoot.length > 0) {
93
+ const [data, err] = loadProjectDefinition(projectRoot);
94
+ if (data !== null) {
95
+ const policyBlock = readPlanPolicy(data.plan);
96
+ if (typeof policyBlock === "object" &&
97
+ policyBlock !== null &&
98
+ !Array.isArray(policyBlock) &&
99
+ "acPassBanking" in policyBlock) {
100
+ const raw = policyBlock.acPassBanking;
101
+ const errors = validateAcPassBanking(raw);
102
+ if (errors.length > 0) {
103
+ return defaultResolved("default-on-error", errors[0] ?? "invalid acPassBanking");
104
+ }
105
+ if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) {
106
+ const rec = raw;
107
+ const enabled = typeof rec.enabled === "boolean" ? rec.enabled : true;
108
+ const threshold = "surplusThreshold" in rec
109
+ ? (parseSurplusThreshold(rec.surplusThreshold) ?? DEFAULT_SURPLUS_THRESHOLD)
110
+ : DEFAULT_SURPLUS_THRESHOLD;
111
+ return {
112
+ enabled,
113
+ surplusThreshold: threshold,
114
+ source: "typed",
115
+ error: null,
116
+ };
117
+ }
118
+ }
119
+ }
120
+ else if (err !== null && err.length > 0) {
121
+ // Fall through to env/default; record nothing as fatal.
122
+ }
123
+ }
124
+ const envRaw = environ[ENV_SURPLUS_THRESHOLD];
125
+ if (envRaw !== undefined && envRaw.trim() !== "") {
126
+ const parsed = parseSurplusThreshold(envRaw);
127
+ if (parsed !== null) {
128
+ return defaultResolved("env", null, { surplusThreshold: parsed });
129
+ }
130
+ return defaultResolved("default-on-error", `${ENV_SURPLUS_THRESHOLD} is not a valid fraction/percent: ${envRaw}`);
131
+ }
132
+ return defaultResolved("default");
133
+ }
134
+ /** Inspector row for `task policy:show --field=acPassBanking` (#3285). */
135
+ export function inspectAcPassBanking(_data, projectRoot) {
136
+ const resolved = resolveAcPassBanking(projectRoot);
137
+ return {
138
+ name: FIELD_AC_PASS_BANKING,
139
+ current: {
140
+ enabled: resolved.enabled,
141
+ surplusThreshold: resolved.surplusThreshold,
142
+ },
143
+ default: {
144
+ enabled: true,
145
+ surplusThreshold: DEFAULT_SURPLUS_THRESHOLD,
146
+ },
147
+ source: resolved.source,
148
+ };
149
+ }
150
+ //# sourceMappingURL=ac-pass-banking.js.map
@@ -286,4 +286,33 @@ export interface CeremonyDialAuditSnapshot {
286
286
  export declare function readCeremonyDialAudit(projectRoot: string): CeremonyDialAuditSnapshot;
287
287
  /** One-line operator summary for audit tooling / failed-task forensics (#3263). */
288
288
  export declare function formatCeremonyDialAuditLine(audit: CeremonyDialAuditSnapshot): string;
289
+ /**
290
+ * Reified dial transition for run-summary telemetry (#3282).
291
+ *
292
+ * IMPLEMENTATION PREREQUISITE from the issue: dial transitions must be
293
+ * engine-managed events, not prose-only policy. This function is the CLI-facing
294
+ * transition hook (`policy:set-ceremony-dial` / dial escalate): it persists the
295
+ * override (when confirm) and always emits a `dial_transition` run-summary line
296
+ * when a depth change is applied.
297
+ */
298
+ export interface EscalateCeremonyDialOptions {
299
+ readonly to: CeremonyDepth;
300
+ readonly reason: string;
301
+ readonly evidence?: string;
302
+ readonly sessionId?: string;
303
+ readonly confirm?: boolean;
304
+ readonly actor?: string;
305
+ readonly note?: string;
306
+ /** When false, skip run-summary emission (tests). Default true. */
307
+ readonly emitRunSummary?: boolean;
308
+ readonly env?: NodeJS.ProcessEnv;
309
+ }
310
+ export interface EscalateCeremonyDialResult {
311
+ readonly exitCode: number;
312
+ readonly from: CeremonyDepth | null;
313
+ readonly to: CeremonyDepth;
314
+ readonly changed: boolean;
315
+ readonly lines: readonly string[];
316
+ }
317
+ export declare function escalateCeremonyDial(projectRoot: string, options: EscalateCeremonyDialOptions): EscalateCeremonyDialResult;
289
318
  //# sourceMappingURL=ceremony-dial.d.ts.map