@deftai/directive-core 0.90.0 → 0.92.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 (101) hide show
  1. package/dist/cache/fetch.js +3 -2
  2. package/dist/cache/io.d.ts +13 -2
  3. package/dist/cache/io.js +36 -7
  4. package/dist/cache/operations.js +5 -3
  5. package/dist/doctor/main.js +37 -0
  6. package/dist/doctor/openclaw-skills.d.ts +42 -8
  7. package/dist/doctor/openclaw-skills.js +281 -38
  8. package/dist/eval/health.d.ts +10 -1
  9. package/dist/eval/health.js +16 -16
  10. package/dist/hooks/dispatcher.d.ts +6 -1
  11. package/dist/hooks/dispatcher.js +30 -0
  12. package/dist/init-deposit/gitignore.js +3 -0
  13. package/dist/init-deposit/hygiene.d.ts +43 -1
  14. package/dist/init-deposit/hygiene.js +112 -7
  15. package/dist/init-deposit/init-deposit.d.ts +5 -0
  16. package/dist/init-deposit/init-deposit.js +37 -2
  17. package/dist/init-deposit/scaffold.js +3 -1
  18. package/dist/intake/github-auth-modes.d.ts +1 -1
  19. package/dist/intake/github-auth-modes.js +25 -3
  20. package/dist/intake/issue-ingest-cli.js +13 -0
  21. package/dist/intake/reconcile-issues-cli.js +13 -0
  22. package/dist/orchestration/probe-session.d.ts +5 -1
  23. package/dist/orchestration/probe-session.js +24 -13
  24. package/dist/platform/agents-md.js +1 -1
  25. package/dist/policy/deft-directive-disable.d.ts +86 -0
  26. package/dist/policy/deft-directive-disable.js +167 -0
  27. package/dist/policy/delivery-branch.d.ts +33 -0
  28. package/dist/policy/delivery-branch.js +124 -0
  29. package/dist/policy/index.d.ts +2 -0
  30. package/dist/policy/index.js +17 -1
  31. package/dist/policy/plan-extensions.d.ts +10 -0
  32. package/dist/policy/plan-extensions.js +16 -0
  33. package/dist/pr-closing-keywords/detect.d.ts +13 -0
  34. package/dist/pr-closing-keywords/detect.js +73 -8
  35. package/dist/pr-closing-keywords/index.d.ts +2 -2
  36. package/dist/pr-closing-keywords/index.js +1 -1
  37. package/dist/pr-closing-keywords/main.js +119 -90
  38. package/dist/pr-closing-keywords/types.d.ts +10 -0
  39. package/dist/render/constants.d.ts +16 -0
  40. package/dist/render/constants.js +16 -1
  41. package/dist/render/export-spec.d.ts +7 -1
  42. package/dist/render/export-spec.js +51 -5
  43. package/dist/render/index.d.ts +3 -2
  44. package/dist/render/index.js +2 -2
  45. package/dist/render/roadmap-render.js +164 -60
  46. package/dist/render/scope-outlook.d.ts +5 -0
  47. package/dist/render/scope-outlook.js +3 -0
  48. package/dist/render/spec-render.d.ts +28 -2
  49. package/dist/render/spec-render.js +111 -14
  50. package/dist/render/text-utils.d.ts +6 -0
  51. package/dist/render/text-utils.js +23 -0
  52. package/dist/scm/binary.d.ts +3 -0
  53. package/dist/scm/binary.js +10 -2
  54. package/dist/scm/call.d.ts +5 -0
  55. package/dist/scm/call.js +5 -0
  56. package/dist/scm/index.d.ts +1 -0
  57. package/dist/scm/index.js +1 -0
  58. package/dist/scm/main.d.ts +5 -0
  59. package/dist/scm/main.js +27 -0
  60. package/dist/scm/readiness-cli.d.ts +24 -0
  61. package/dist/scm/readiness-cli.js +85 -0
  62. package/dist/scm/readiness.d.ts +104 -0
  63. package/dist/scm/readiness.js +421 -0
  64. package/dist/scope/batch-promote.d.ts +26 -0
  65. package/dist/scope/batch-promote.js +167 -0
  66. package/dist/scope/brief-io.d.ts +3 -1
  67. package/dist/scope/brief-io.js +5 -3
  68. package/dist/scope/delivery-evidence.d.ts +112 -0
  69. package/dist/scope/delivery-evidence.js +419 -0
  70. package/dist/scope/index.d.ts +2 -0
  71. package/dist/scope/index.js +2 -0
  72. package/dist/scope/main.d.ts +7 -0
  73. package/dist/scope/main.js +155 -9
  74. package/dist/scope/registry-artifact-sync.js +4 -1
  75. package/dist/scope/transition.d.ts +11 -1
  76. package/dist/scope/transition.js +30 -4
  77. package/dist/scope/wip-cap-check.d.ts +6 -0
  78. package/dist/scope/wip-cap-check.js +14 -2
  79. package/dist/session/ritual-sentinel.js +21 -12
  80. package/dist/session/session-start-hook.d.ts +3 -0
  81. package/dist/session/session-start-hook.js +21 -0
  82. package/dist/session/session-start.d.ts +7 -0
  83. package/dist/session/session-start.js +85 -0
  84. package/dist/swarm/complete-cohort.d.ts +20 -1
  85. package/dist/swarm/complete-cohort.js +56 -9
  86. package/dist/swarm/finalize-cohort-cli.js +9 -0
  87. package/dist/swarm/finalize-cohort.d.ts +8 -0
  88. package/dist/swarm/finalize-cohort.js +217 -21
  89. package/dist/triage/welcome/index.d.ts +1 -1
  90. package/dist/triage/welcome/index.js +1 -1
  91. package/dist/triage/welcome/onboard.js +7 -1
  92. package/dist/triage/welcome/prior-state.d.ts +7 -0
  93. package/dist/triage/welcome/prior-state.js +16 -2
  94. package/dist/triage/welcome/writers.d.ts +10 -0
  95. package/dist/triage/welcome/writers.js +72 -2
  96. package/dist/user-config/experimental-rules.d.ts +43 -0
  97. package/dist/user-config/experimental-rules.js +162 -0
  98. package/dist/user-config/index.d.ts +1 -0
  99. package/dist/user-config/index.js +1 -0
  100. package/dist/verify-source/contained-writes.js +1 -1
  101. package/package.json +3 -3
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Temporary test/local kill-switch for Directive enforcement (#3039).
3
+ *
4
+ * Presence of root `.deft-directive-disable` means Directive **enforcement** is
5
+ * OFF (hooks, session ritual, automation) while the deposit may remain — **only
6
+ * when the file is not tracked by git**. A committed flag is misconfig: doctor
7
+ * warns and enforcement stays ON (repository-controlled content must not disable
8
+ * hooks for downstream clones).
9
+ *
10
+ * Distinct from `.no-deft-directive` (#2926 permanent opt-out): flag+deposit is
11
+ * **not** inconsistent here.
12
+ *
13
+ * Product choices (v1):
14
+ * - Flag is **root-only** (workspace root the tool opened).
15
+ * - Flag **must be gitignored** (canonical baseline); committed flag → doctor
16
+ * warns and does **not** short-circuit enforcement.
17
+ * - Deposit presence is **OK**.
18
+ * - Full re-enable requires: delete the file **and** start a **new** agent session.
19
+ * - Precedence: active kill-switch first, then `.no-deft-directive`, else normal.
20
+ */
21
+ import { execFileSync } from "node:child_process";
22
+ import { statSync } from "node:fs";
23
+ import { join, resolve } from "node:path";
24
+ import { CANONICAL_INSTALL_ROOT } from "../init-deposit/constants.js";
25
+ /** Canonical root-only filename (lowercase). Presence = candidate flag. */
26
+ export const DEFT_DIRECTIVE_DISABLE_FLAG_NAME = ".deft-directive-disable";
27
+ /** Gitignore entry the deposit must ensure (same as flag name). */
28
+ export const DEFT_DIRECTIVE_DISABLE_GITIGNORE_LINE = DEFT_DIRECTIVE_DISABLE_FLAG_NAME;
29
+ /**
30
+ * Canonical recovery message when Directive is disabled by the test kill-switch.
31
+ * Surfaces (doctor, agent, CLI, hooks) share this wording.
32
+ */
33
+ export const DEFT_DIRECTIVE_DISABLE_RECOVERY_MESSAGE = "Directive is DISABLED for this project via root `.deft-directive-disable` (test/local kill-switch).\n" +
34
+ "Deposit may still be present; enforcement (hooks, session ritual, automation) will not run.\n" +
35
+ "\n" +
36
+ "To fully re-enable Directive:\n" +
37
+ " 1. Delete the file: rm .deft-directive-disable (or equivalent)\n" +
38
+ " 2. Start a NEW agent session (reload AGENTS / host skills / hooks)\n" +
39
+ "Until both are done, Directive is not fully operational.";
40
+ /** One-line summary for log lines and short CLI output. */
41
+ export const DEFT_DIRECTIVE_DISABLE_ONE_LINE = "Directive disabled via `.deft-directive-disable` (test/local kill-switch; deposit OK)";
42
+ /** Doctor/status label when the kill-switch is active (local / untracked). */
43
+ export const DEFT_DIRECTIVE_DISABLE_STATUS = "disabled-test-kill-switch";
44
+ /**
45
+ * Warning when the kill-switch file is tracked by git (should be gitignored).
46
+ * Tracked flags do **not** disable enforcement.
47
+ */
48
+ export const DEFT_DIRECTIVE_DISABLE_TRACKED_WARNING = "Misconfig: `.deft-directive-disable` is tracked by git. The flag must be gitignored (local kill-switch only). Untrack it and ensure `.gitignore` covers the path. Enforcement is NOT disabled while the flag is tracked.";
49
+ /** Cap git probe so hot-path hooks never hang on a slow VCS call. */
50
+ const GIT_TRACKED_PROBE_TIMEOUT_MS = 1500;
51
+ /** Process-local cache: avoid re-spawning git on every PreToolUse while the flag is present. */
52
+ const trackedProbeCache = new Map();
53
+ const TRACKED_PROBE_CACHE_TTL_MS = 30_000;
54
+ function defaultIsFile(path) {
55
+ try {
56
+ return statSync(path).isFile();
57
+ }
58
+ catch {
59
+ return false;
60
+ }
61
+ }
62
+ function defaultIsDir(path) {
63
+ try {
64
+ return statSync(path).isDirectory();
65
+ }
66
+ catch {
67
+ return false;
68
+ }
69
+ }
70
+ function defaultIsGitTracked(projectRoot, relPath) {
71
+ const cacheKey = `${projectRoot}\0${relPath}`;
72
+ const now = Date.now();
73
+ const cached = trackedProbeCache.get(cacheKey);
74
+ if (cached !== undefined && now - cached.atMs < TRACKED_PROBE_CACHE_TTL_MS) {
75
+ return cached.tracked;
76
+ }
77
+ let tracked = false;
78
+ try {
79
+ const out = execFileSync("git", ["ls-files", "--", relPath], {
80
+ cwd: projectRoot,
81
+ encoding: "utf8",
82
+ stdio: ["ignore", "pipe", "ignore"],
83
+ timeout: GIT_TRACKED_PROBE_TIMEOUT_MS,
84
+ });
85
+ tracked = out.trim().length > 0;
86
+ }
87
+ catch {
88
+ tracked = false;
89
+ }
90
+ trackedProbeCache.set(cacheKey, { tracked, atMs: now });
91
+ return tracked;
92
+ }
93
+ /** Absolute path to the root kill-switch flag file. */
94
+ export function deftDirectiveDisableFlagPath(projectRoot) {
95
+ return resolve(projectRoot, DEFT_DIRECTIVE_DISABLE_FLAG_NAME);
96
+ }
97
+ /**
98
+ * Detect root `.deft-directive-disable`. Presence is file-existence only
99
+ * (empty or short comment OK). Deposit presence is reported but never treated
100
+ * as inconsistent (#3039 vs #2926).
101
+ *
102
+ * Enforcement short-circuit uses `state.active` (present && !trackedByGit).
103
+ */
104
+ export function detectDeftDirectiveDisable(projectRoot, seams = {}) {
105
+ const flagPath = deftDirectiveDisableFlagPath(projectRoot);
106
+ const isFile = seams.isFile ?? defaultIsFile;
107
+ const isDir = seams.isDir ?? defaultIsDir;
108
+ const isGitTracked = seams.isGitTracked ?? defaultIsGitTracked;
109
+ const present = isFile(flagPath);
110
+ const depositPresent = isDir(join(projectRoot, CANONICAL_INSTALL_ROOT));
111
+ let trackedByGit = false;
112
+ if (present) {
113
+ if (seams.skipTrackedCache && seams.isGitTracked === undefined) {
114
+ // Fresh probe without reading process cache (doctor / one-shot CLI).
115
+ try {
116
+ const out = execFileSync("git", ["ls-files", "--", DEFT_DIRECTIVE_DISABLE_FLAG_NAME], {
117
+ cwd: projectRoot,
118
+ encoding: "utf8",
119
+ stdio: ["ignore", "pipe", "ignore"],
120
+ timeout: GIT_TRACKED_PROBE_TIMEOUT_MS,
121
+ });
122
+ trackedByGit = out.trim().length > 0;
123
+ }
124
+ catch {
125
+ trackedByGit = false;
126
+ }
127
+ }
128
+ else {
129
+ trackedByGit = isGitTracked(projectRoot, DEFT_DIRECTIVE_DISABLE_FLAG_NAME);
130
+ }
131
+ }
132
+ return {
133
+ present,
134
+ flagPath,
135
+ depositPresent,
136
+ trackedByGit,
137
+ active: present && !trackedByGit,
138
+ };
139
+ }
140
+ /**
141
+ * True when the local (untracked) kill-switch is active for enforcement
142
+ * short-circuit. Tracked flags return false so hooks stay enforced.
143
+ */
144
+ export function isDeftDirectiveDisableActive(projectRoot, seams = {}) {
145
+ return detectDeftDirectiveDisable(projectRoot, seams).active;
146
+ }
147
+ /**
148
+ * Full operator-facing message for the kill-switch, optionally combined with
149
+ * permanent opt-out when both flags are present.
150
+ */
151
+ export function formatDeftDirectiveDisableMessage(options = {}) {
152
+ const parts = [];
153
+ if (options.oneLine) {
154
+ parts.push(DEFT_DIRECTIVE_DISABLE_ONE_LINE);
155
+ }
156
+ else {
157
+ parts.push(DEFT_DIRECTIVE_DISABLE_RECOVERY_MESSAGE);
158
+ }
159
+ if (options.permanentOptOutAlsoPresent) {
160
+ parts.push("Also present: root `.no-deft-directive` (permanent opt-out). Install/update fail-closed semantics still apply for that flag.");
161
+ }
162
+ if (options.trackedByGit) {
163
+ parts.push(DEFT_DIRECTIVE_DISABLE_TRACKED_WARNING);
164
+ }
165
+ return parts.join("\n\n");
166
+ }
167
+ //# sourceMappingURL=deft-directive-disable.js.map
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Typed plan.policy.deliveryBranch (#3041).
3
+ *
4
+ * Distinct from swarm/PR `baseBranch` (integration target for PRs / finalize
5
+ * sweep PRs). deliveryBranch is where shipped work must land for a delivered
6
+ * completion disposition.
7
+ */
8
+ import { type GitRunner } from "../session/git.js";
9
+ export declare const FIELD_DELIVERY_BRANCH = "plan.policy.deliveryBranch";
10
+ export declare const FIELD_DELIVERY_BRANCH_CLI_ALIAS = "deliveryBranch";
11
+ /** Framework fallback when neither policy nor git default can be resolved. */
12
+ export declare const DEFAULT_DELIVERY_BRANCH_FALLBACK = "master";
13
+ export type DeliveryBranchSource = "typed" | "git-default" | "default-fallback" | "default-on-error";
14
+ export interface DeliveryBranchResult {
15
+ readonly branch: string;
16
+ readonly source: DeliveryBranchSource;
17
+ readonly error: string | null;
18
+ }
19
+ /**
20
+ * Resolve the project's delivery branch (#3041).
21
+ *
22
+ * Order: typed plan.policy.deliveryBranch → git remote default → local main/master → "master".
23
+ */
24
+ export declare function resolveDeliveryBranch(projectRoot: string, runGit?: GitRunner): DeliveryBranchResult;
25
+ export interface DeliveryBranchPolicyField {
26
+ readonly name: string;
27
+ readonly current: string;
28
+ readonly default: string;
29
+ readonly source: string;
30
+ }
31
+ /** Inspector row for `task policy:show --field=deliveryBranch` (#3041). */
32
+ export declare function inspectDeliveryBranch(_data: Record<string, unknown> | null, projectRoot?: string): DeliveryBranchPolicyField;
33
+ //# sourceMappingURL=delivery-branch.d.ts.map
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Typed plan.policy.deliveryBranch (#3041).
3
+ *
4
+ * Distinct from swarm/PR `baseBranch` (integration target for PRs / finalize
5
+ * sweep PRs). deliveryBranch is where shipped work must land for a delivered
6
+ * completion disposition.
7
+ */
8
+ import { defaultGitRunner } from "../session/git.js";
9
+ import { readPlanPolicy } from "./plan-extensions.js";
10
+ import { loadProjectDefinition } from "./resolve.js";
11
+ export const FIELD_DELIVERY_BRANCH = "plan.policy.deliveryBranch";
12
+ export const FIELD_DELIVERY_BRANCH_CLI_ALIAS = "deliveryBranch";
13
+ /** Framework fallback when neither policy nor git default can be resolved. */
14
+ export const DEFAULT_DELIVERY_BRANCH_FALLBACK = "master";
15
+ function defaultBranchCandidates(projectRoot, runGit) {
16
+ const sym = runGit(projectRoot, ["symbolic-ref", "refs/remotes/origin/HEAD", "--short"]);
17
+ if (sym.code === 0 && sym.stdout) {
18
+ const trimmed = sym.stdout.trim();
19
+ // origin/main → main
20
+ const parts = trimmed.split("/");
21
+ const name = (parts.slice(1).join("/") || parts[0] || "").trim();
22
+ if (name.length > 0) {
23
+ return [name];
24
+ }
25
+ }
26
+ const candidates = [];
27
+ for (const branch of ["main", "master"]) {
28
+ const check = runGit(projectRoot, [
29
+ "show-ref",
30
+ "--verify",
31
+ "--quiet",
32
+ `refs/remotes/origin/${branch}`,
33
+ ]);
34
+ if (check.code === 0) {
35
+ candidates.push(branch);
36
+ }
37
+ }
38
+ if (candidates.length === 0) {
39
+ for (const branch of ["main", "master"]) {
40
+ const local = runGit(projectRoot, [
41
+ "show-ref",
42
+ "--verify",
43
+ "--quiet",
44
+ `refs/heads/${branch}`,
45
+ ]);
46
+ if (local.code === 0) {
47
+ candidates.push(branch);
48
+ }
49
+ }
50
+ }
51
+ return candidates;
52
+ }
53
+ /**
54
+ * Resolve the project's delivery branch (#3041).
55
+ *
56
+ * Order: typed plan.policy.deliveryBranch → git remote default → local main/master → "master".
57
+ */
58
+ export function resolveDeliveryBranch(projectRoot, runGit = defaultGitRunner) {
59
+ const [data, err] = loadProjectDefinition(projectRoot);
60
+ if (data === null) {
61
+ const gitDefault = defaultBranchCandidates(projectRoot, runGit)[0];
62
+ if (gitDefault !== undefined) {
63
+ return { branch: gitDefault, source: "git-default", error: err };
64
+ }
65
+ return {
66
+ branch: DEFAULT_DELIVERY_BRANCH_FALLBACK,
67
+ source: "default-fallback",
68
+ error: err,
69
+ };
70
+ }
71
+ const plan = data.plan;
72
+ if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
73
+ const gitDefault = defaultBranchCandidates(projectRoot, runGit)[0];
74
+ return {
75
+ branch: gitDefault ?? DEFAULT_DELIVERY_BRANCH_FALLBACK,
76
+ source: gitDefault !== undefined ? "git-default" : "default-fallback",
77
+ error: "PROJECT-DEFINITION 'plan' is not an object",
78
+ };
79
+ }
80
+ const policyBlock = readPlanPolicy(plan);
81
+ if (typeof policyBlock === "object" &&
82
+ policyBlock !== null &&
83
+ !Array.isArray(policyBlock) &&
84
+ "deliveryBranch" in policyBlock) {
85
+ const raw = policyBlock.deliveryBranch;
86
+ if (typeof raw !== "string" || raw.trim().length === 0) {
87
+ const gitDefault = defaultBranchCandidates(projectRoot, runGit)[0];
88
+ return {
89
+ branch: gitDefault ?? DEFAULT_DELIVERY_BRANCH_FALLBACK,
90
+ source: "default-on-error",
91
+ error: `plan.policy.deliveryBranch must be a non-empty string; got ${typeof raw}`,
92
+ };
93
+ }
94
+ return { branch: raw.trim(), source: "typed", error: null };
95
+ }
96
+ const gitDefault = defaultBranchCandidates(projectRoot, runGit)[0];
97
+ if (gitDefault !== undefined) {
98
+ return { branch: gitDefault, source: "git-default", error: null };
99
+ }
100
+ return {
101
+ branch: DEFAULT_DELIVERY_BRANCH_FALLBACK,
102
+ source: "default-fallback",
103
+ error: null,
104
+ };
105
+ }
106
+ /** Inspector row for `task policy:show --field=deliveryBranch` (#3041). */
107
+ export function inspectDeliveryBranch(_data, projectRoot) {
108
+ if (projectRoot === undefined || projectRoot.length === 0) {
109
+ return {
110
+ name: FIELD_DELIVERY_BRANCH,
111
+ current: DEFAULT_DELIVERY_BRANCH_FALLBACK,
112
+ default: DEFAULT_DELIVERY_BRANCH_FALLBACK,
113
+ source: "default",
114
+ };
115
+ }
116
+ const resolved = resolveDeliveryBranch(projectRoot);
117
+ return {
118
+ name: FIELD_DELIVERY_BRANCH,
119
+ current: resolved.branch,
120
+ default: DEFAULT_DELIVERY_BRANCH_FALLBACK,
121
+ source: resolved.source,
122
+ };
123
+ }
124
+ //# sourceMappingURL=delivery-branch.js.map
@@ -2,6 +2,8 @@ export * from "./agents-md-advisory.js";
2
2
  export * from "./autonomy.js";
3
3
  export * from "./capacity.js";
4
4
  export * from "./decisions.js";
5
+ export * from "./deft-directive-disable.js";
6
+ export * from "./delivery-branch.js";
5
7
  export * from "./disclosure.js";
6
8
  export * from "./host-hooks.js";
7
9
  export * from "./hotfix-criteria.js";
@@ -1,3 +1,4 @@
1
+ import { FIELD_DELIVERY_BRANCH, FIELD_DELIVERY_BRANCH_CLI_ALIAS, inspectDeliveryBranch, } from "./delivery-branch.js";
1
2
  import { FIELD_HOST_HOOKS, FIELD_HOST_HOOKS_CLI_ALIAS, inspectHostHooks } from "./host-hooks.js";
2
3
  import { FIELD_HOTFIX_CRITERIA, FIELD_HOTFIX_CRITERIA_CLI_ALIAS, inspectHotfixCriteria, } from "./hotfix-criteria.js";
3
4
  import { readPlanPolicy } from "./plan-extensions.js";
@@ -12,6 +13,8 @@ export * from "./agents-md-advisory.js";
12
13
  export * from "./autonomy.js";
13
14
  export * from "./capacity.js";
14
15
  export * from "./decisions.js";
16
+ export * from "./deft-directive-disable.js";
17
+ export * from "./delivery-branch.js";
15
18
  export * from "./disclosure.js";
16
19
  export * from "./host-hooks.js";
17
20
  export * from "./hotfix-criteria.js";
@@ -38,6 +41,7 @@ export const FIELD_TRIAGE_RANKING_LABELS = "plan.policy.triageRankingLabels";
38
41
  export const FIELD_TRIAGE_AUTO_CLASSIFY = "plan.policy.triageAutoClassify";
39
42
  export const FIELD_TRIAGE_HOLD_MARKERS = "plan.policy.triageHoldMarkers";
40
43
  export const FIELD_SWARM_SUBAGENT_BACKEND = "plan.policy.swarmSubagentBackend";
44
+ // deliveryBranch also exported from delivery-branch.js via export *
41
45
  export const DEFAULT_SESSION_RITUAL_STALENESS_HOURS = 4;
42
46
  export const DEFAULT_TRIAGE_SCOPE_VALUE = [
43
47
  { rule: "all-open" },
@@ -296,6 +300,15 @@ function inspectHotfixCriteriaField(data) {
296
300
  source: field.source,
297
301
  };
298
302
  }
303
+ function inspectDeliveryBranchField(data, projectRoot) {
304
+ const field = inspectDeliveryBranch(data, projectRoot);
305
+ return {
306
+ name: field.name,
307
+ current: field.current,
308
+ default: field.default,
309
+ source: field.source,
310
+ };
311
+ }
299
312
  const REGISTERED_POLICIES = [
300
313
  inspectAllowDirectCommits,
301
314
  inspectWipCap,
@@ -308,6 +321,7 @@ const REGISTERED_POLICIES = [
308
321
  emptyIsTyped: true,
309
322
  }),
310
323
  inspectSwarmSubagentBackend,
324
+ inspectDeliveryBranchField,
311
325
  inspectHostHooksField,
312
326
  inspectStalenessTicklerField,
313
327
  inspectRuntimeAuthorityField,
@@ -337,7 +351,9 @@ export function inspectOnePolicy(name, projectRoot) {
337
351
  ? FIELD_REQUIRE_HUMAN_MERGE
338
352
  : name === FIELD_HOTFIX_CRITERIA_CLI_ALIAS
339
353
  ? FIELD_HOTFIX_CRITERIA
340
- : name;
354
+ : name === FIELD_DELIVERY_BRANCH_CLI_ALIAS
355
+ ? FIELD_DELIVERY_BRANCH
356
+ : name;
341
357
  for (const field of inspectAllPolicies(projectRoot)) {
342
358
  if (field.name === normalized)
343
359
  return field;
@@ -17,6 +17,14 @@
17
17
  export declare const PLAN_POLICY_KEY = "x-directive/policy";
18
18
  /** Namespaced directive scope-completion note key. */
19
19
  export declare const PLAN_COMPLETED_NOTE_KEY = "x-directive/completedNote";
20
+ /**
21
+ * Namespaced onboarding decision-provenance block (#1694 / #1695).
22
+ *
23
+ * Holds facts that must NOT be smuggled through policy value fields (e.g.
24
+ * `plan.policy.wipCap` presence must not mean "operator decided a WIP cap").
25
+ * Out-of-band from `x-directive/policy` so value and decision stay orthogonal.
26
+ */
27
+ export declare const PLAN_ONBOARDING_KEY = "x-directive/onboarding";
20
28
  /** Legacy bare policy key (pre-#1650); read-side fallback / write-side migration source. */
21
29
  export declare const LEGACY_PLAN_POLICY_KEY = "policy";
22
30
  /** Legacy bare completedNote key (pre-#1650); read-side fallback / write-side migration source. */
@@ -25,6 +33,8 @@ export declare const LEGACY_PLAN_COMPLETED_NOTE_KEY = "completedNote";
25
33
  export declare function readPlanPolicy(plan: unknown): unknown;
26
34
  /** Read the directive completedNote from a plan (namespaced first, bare fallback). */
27
35
  export declare function readPlanCompletedNote(plan: unknown): unknown;
36
+ /** Read the namespaced onboarding decision-provenance block (#1694). */
37
+ export declare function readPlanOnboarding(plan: unknown): unknown;
28
38
  /**
29
39
  * Rename a legacy bare `policy` key to the namespaced key in place when only
30
40
  * the bare key is present. No-op when the namespaced key already exists or no
@@ -17,6 +17,14 @@
17
17
  export const PLAN_POLICY_KEY = "x-directive/policy";
18
18
  /** Namespaced directive scope-completion note key. */
19
19
  export const PLAN_COMPLETED_NOTE_KEY = "x-directive/completedNote";
20
+ /**
21
+ * Namespaced onboarding decision-provenance block (#1694 / #1695).
22
+ *
23
+ * Holds facts that must NOT be smuggled through policy value fields (e.g.
24
+ * `plan.policy.wipCap` presence must not mean "operator decided a WIP cap").
25
+ * Out-of-band from `x-directive/policy` so value and decision stay orthogonal.
26
+ */
27
+ export const PLAN_ONBOARDING_KEY = "x-directive/onboarding";
20
28
  /** Legacy bare policy key (pre-#1650); read-side fallback / write-side migration source. */
21
29
  export const LEGACY_PLAN_POLICY_KEY = "policy";
22
30
  /** Legacy bare completedNote key (pre-#1650); read-side fallback / write-side migration source. */
@@ -42,6 +50,14 @@ export function readPlanPolicy(plan) {
42
50
  export function readPlanCompletedNote(plan) {
43
51
  return readPlanExtension(plan, PLAN_COMPLETED_NOTE_KEY, LEGACY_PLAN_COMPLETED_NOTE_KEY);
44
52
  }
53
+ /** Read the namespaced onboarding decision-provenance block (#1694). */
54
+ export function readPlanOnboarding(plan) {
55
+ const planObj = asPlanObject(plan);
56
+ if (planObj === null) {
57
+ return undefined;
58
+ }
59
+ return planObj[PLAN_ONBOARDING_KEY];
60
+ }
45
61
  /**
46
62
  * Rename a legacy bare `policy` key to the namespaced key in place when only
47
63
  * the bare key is present. No-op when the namespaced key already exists or no
@@ -1,5 +1,18 @@
1
1
  import type { Hit } from "./types.js";
2
2
  export declare const CLOSING_KEYWORD_RE: RegExp;
3
3
  export declare function renderHit(hit: Hit): string;
4
+ /** Layer 0 FP hits only (#737): keyword in negation / quotation / example / code-block / blockquote. */
4
5
  export declare function findHits(text: string, source: string): Hit[];
6
+ /**
7
+ * Intent-mode hits (#3015 class D): every closing-keyword + `#N` match, regardless of
8
+ * surrounding prose. GitHub closes on token presence; conditional English is ignored.
9
+ * reason is the FP category when present, otherwise `intent`.
10
+ */
11
+ export declare function findAllClosingKeywordHits(text: string, source: string): Hit[];
12
+ /**
13
+ * Body trailer `deft-close-intent: full` is intentionally NOT an authorization path.
14
+ * Intent-mode allowlist is CLI `--allow-close N,M` only (#3015 / Greptile class-D gate).
15
+ * Markdown examples must never suppress real closing-keyword findings.
16
+ */
17
+ export declare function hasFullCloseIntent(_text: string): boolean;
5
18
  //# sourceMappingURL=detect.d.ts.map
@@ -21,7 +21,8 @@ const EXAMPLE_MARKERS = [
21
21
  /\blike\b/i,
22
22
  ];
23
23
  const BLOCKQUOTE_RE = /^\s*>\s/m;
24
- const CODE_FENCE_RE = /^```/m;
24
+ /** CommonMark fenced code opener/closer at line start: 3+ backticks or 3+ tildes. */
25
+ const CODE_FENCE_LINE_RE = /^(?:`{3,}|~{3,})/;
25
26
  function findAllMatches(text, re) {
26
27
  const flags = re.flags.includes("g") ? re.flags : `${re.flags}g`;
27
28
  const pattern = new RegExp(re.source, flags);
@@ -41,10 +42,41 @@ function lineStartingAt(text, offset) {
41
42
  }
42
43
  return text.slice(lineStart, lineEnd);
43
44
  }
45
+ /**
46
+ * CommonMark fence stack: open and close must use the same character, and the
47
+ * closer length must be >= opener length. Mixed ``` / ~~~ or shorter closers
48
+ * do not pop the open fence.
49
+ */
44
50
  function isInsideCodeFence(text, offset) {
45
- const prefix = text.slice(0, offset);
46
- const matches = prefix.match(CODE_FENCE_RE);
47
- return matches !== null && matches.length % 2 === 1;
51
+ let openChar = null;
52
+ let openLen = 0;
53
+ let lineStart = 0;
54
+ while (lineStart < offset) {
55
+ let lineEnd = text.indexOf("\n", lineStart);
56
+ if (lineEnd === -1 || lineEnd > offset) {
57
+ lineEnd = Math.min(text.length, offset);
58
+ }
59
+ const line = text.slice(lineStart, lineEnd);
60
+ const m = CODE_FENCE_LINE_RE.exec(line);
61
+ if (m !== null) {
62
+ const fence = m[0] ?? "";
63
+ const ch = fence[0];
64
+ const len = fence.length;
65
+ if (openChar === null) {
66
+ openChar = ch;
67
+ openLen = len;
68
+ }
69
+ else if (ch === openChar && len >= openLen) {
70
+ openChar = null;
71
+ openLen = 0;
72
+ }
73
+ }
74
+ if (lineEnd >= offset) {
75
+ break;
76
+ }
77
+ lineStart = lineEnd + 1;
78
+ }
79
+ return openChar !== null;
48
80
  }
49
81
  function classifyHit(text, match) {
50
82
  const start = match.index ?? 0;
@@ -88,6 +120,12 @@ export function renderHit(hit) {
88
120
  return (` [${hit.source}] ${hit.reason}: ` +
89
121
  `"...${hit.context}..." -> ${hit.keyword} #${hit.issueNumber}`);
90
122
  }
123
+ function snippetAround(text, match) {
124
+ const snippetStart = Math.max(0, (match.index ?? 0) - 30);
125
+ const snippetEnd = Math.min(text.length, (match.index ?? 0) + match[0].length + 30);
126
+ return text.slice(snippetStart, snippetEnd).replace(/\n/g, " ");
127
+ }
128
+ /** Layer 0 FP hits only (#737): keyword in negation / quotation / example / code-block / blockquote. */
91
129
  export function findHits(text, source) {
92
130
  const hits = [];
93
131
  const re = new RegExp(CLOSING_KEYWORD_RE.source, CLOSING_KEYWORD_RE.flags);
@@ -95,14 +133,11 @@ export function findHits(text, source) {
95
133
  while (match !== null) {
96
134
  const category = classifyHit(text, match);
97
135
  if (category !== null) {
98
- const snippetStart = Math.max(0, (match.index ?? 0) - 30);
99
- const snippetEnd = Math.min(text.length, (match.index ?? 0) + match[0].length + 30);
100
- const context = text.slice(snippetStart, snippetEnd).replace(/\n/g, " ");
101
136
  hits.push({
102
137
  source,
103
138
  keyword: match[1] ?? "",
104
139
  issueNumber: Number(match[2]),
105
- context,
140
+ context: snippetAround(text, match),
106
141
  reason: category,
107
142
  });
108
143
  }
@@ -110,4 +145,34 @@ export function findHits(text, source) {
110
145
  }
111
146
  return hits;
112
147
  }
148
+ /**
149
+ * Intent-mode hits (#3015 class D): every closing-keyword + `#N` match, regardless of
150
+ * surrounding prose. GitHub closes on token presence; conditional English is ignored.
151
+ * reason is the FP category when present, otherwise `intent`.
152
+ */
153
+ export function findAllClosingKeywordHits(text, source) {
154
+ const hits = [];
155
+ const re = new RegExp(CLOSING_KEYWORD_RE.source, CLOSING_KEYWORD_RE.flags);
156
+ let match = re.exec(text);
157
+ while (match !== null) {
158
+ const category = classifyHit(text, match);
159
+ hits.push({
160
+ source,
161
+ keyword: match[1] ?? "",
162
+ issueNumber: Number(match[2]),
163
+ context: snippetAround(text, match),
164
+ reason: category ?? "intent",
165
+ });
166
+ match = re.exec(text);
167
+ }
168
+ return hits;
169
+ }
170
+ /**
171
+ * Body trailer `deft-close-intent: full` is intentionally NOT an authorization path.
172
+ * Intent-mode allowlist is CLI `--allow-close N,M` only (#3015 / Greptile class-D gate).
173
+ * Markdown examples must never suppress real closing-keyword findings.
174
+ */
175
+ export function hasFullCloseIntent(_text) {
176
+ return false;
177
+ }
113
178
  //# sourceMappingURL=detect.js.map
@@ -1,7 +1,7 @@
1
1
  export { EXIT_CONFIG_ERROR, EXIT_HITS_FOUND, EXIT_OK, GH_TIMEOUT_S, WINDOW_RADIUS, } from "./constants.js";
2
- export { findHits, renderHit } from "./detect.js";
2
+ export { findAllClosingKeywordHits, findHits, hasFullCloseIntent, renderHit, } from "./detect.js";
3
3
  export { defaultRunGh, fetchPrBody, fetchPrCommitMessages } from "./gh.js";
4
4
  export { readCommitsFile, readTextFile } from "./io.js";
5
5
  export { cmdPrCheckClosingKeywords, parseAllowList, parseArgs, run } from "./main.js";
6
- export type { Hit, ParsedArgs, RunGhFn, RunGhResult } from "./types.js";
6
+ export type { ClosingKeywordMode, Hit, ParsedArgs, RunGhFn, RunGhResult } from "./types.js";
7
7
  //# sourceMappingURL=index.d.ts.map
@@ -1,5 +1,5 @@
1
1
  export { EXIT_CONFIG_ERROR, EXIT_HITS_FOUND, EXIT_OK, GH_TIMEOUT_S, WINDOW_RADIUS, } from "./constants.js";
2
- export { findHits, renderHit } from "./detect.js";
2
+ export { findAllClosingKeywordHits, findHits, hasFullCloseIntent, renderHit, } from "./detect.js";
3
3
  export { defaultRunGh, fetchPrBody, fetchPrCommitMessages } from "./gh.js";
4
4
  export { readCommitsFile, readTextFile } from "./io.js";
5
5
  export { cmdPrCheckClosingKeywords, parseAllowList, parseArgs, run } from "./main.js";