@deftai/directive 0.88.0 → 0.90.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.
@@ -0,0 +1,301 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Escalation CLI (#518 slim / #2948 Wave 5): typed queue under .deft/escalations/.
4
+ *
5
+ * deft escalation:file -- --type cmd_approval --title "…"
6
+ * deft escalation:list [--open] [--type <type>] [--format json]
7
+ * deft escalation:resolve -- <id> --decision approved|denied|answered|dismissed
8
+ * deft escalation:batch-approve [--ids a,b] [--include-dangerous]
9
+ */
10
+ import { batchApproveEscalations, ESCALATION_TYPES, fileEscalation, isEscalationType, listEscalationsFiltered, resolveEscalation, } from "@deftai/directive-core/escalation";
11
+ function parseArgv(argv) {
12
+ const base = {
13
+ cmd: "list",
14
+ projectRoot: process.cwd(),
15
+ type: null,
16
+ title: null,
17
+ body: null,
18
+ agentId: "agent",
19
+ contextRefs: [],
20
+ slaHours: null,
21
+ dangerous: false,
22
+ id: null,
23
+ ids: [],
24
+ decision: null,
25
+ actor: "operator",
26
+ note: null,
27
+ answer: null,
28
+ openOnly: false,
29
+ includeDangerous: false,
30
+ format: "text",
31
+ };
32
+ const args = [...argv];
33
+ while (args[0] === "--")
34
+ args.shift();
35
+ if (args.length > 0 && !args[0]?.startsWith("-")) {
36
+ const cmd = args.shift();
37
+ if (cmd === "file" || cmd === "list" || cmd === "resolve" || cmd === "batch-approve") {
38
+ base.cmd = cmd;
39
+ }
40
+ else if (cmd.startsWith("esc-")) {
41
+ base.cmd = "resolve";
42
+ base.id = cmd;
43
+ }
44
+ else {
45
+ return { ...base, error: `unknown escalation subcommand: ${cmd}` };
46
+ }
47
+ }
48
+ while (args[0] === "--")
49
+ args.shift();
50
+ for (let i = 0; i < args.length; i++) {
51
+ const a = args[i];
52
+ if (a === undefined)
53
+ break;
54
+ if (a === "--project-root" || a === "--projectRoot") {
55
+ base.projectRoot = args[++i] ?? base.projectRoot;
56
+ continue;
57
+ }
58
+ if (a === "--type") {
59
+ base.type = args[++i] ?? null;
60
+ continue;
61
+ }
62
+ if (a === "--title") {
63
+ base.title = args[++i] ?? null;
64
+ continue;
65
+ }
66
+ if (a === "--body") {
67
+ base.body = args[++i] ?? null;
68
+ continue;
69
+ }
70
+ if (a === "--agent" || a === "--agent-id" || a === "--agentId") {
71
+ base.agentId = args[++i] ?? base.agentId;
72
+ continue;
73
+ }
74
+ if (a === "--context" || a === "--context-refs" || a === "--contextRefs") {
75
+ const raw = args[++i] ?? "";
76
+ base.contextRefs = raw
77
+ .split(/[,\s]+/)
78
+ .map((s) => s.trim())
79
+ .filter((s) => s.length > 0);
80
+ continue;
81
+ }
82
+ if (a === "--sla-hours" || a === "--slaHours") {
83
+ const n = Number(args[++i] ?? "");
84
+ base.slaHours = Number.isFinite(n) ? n : null;
85
+ continue;
86
+ }
87
+ if (a === "--dangerous") {
88
+ base.dangerous = true;
89
+ continue;
90
+ }
91
+ if (a === "--id") {
92
+ base.id = args[++i] ?? null;
93
+ continue;
94
+ }
95
+ if (a === "--ids") {
96
+ const raw = args[++i] ?? "";
97
+ base.ids = raw
98
+ .split(/[,\s]+/)
99
+ .map((s) => s.trim())
100
+ .filter((s) => s.length > 0);
101
+ continue;
102
+ }
103
+ if (a === "--decision") {
104
+ base.decision = args[++i] ?? null;
105
+ continue;
106
+ }
107
+ if (a === "--actor") {
108
+ base.actor = args[++i] ?? base.actor;
109
+ continue;
110
+ }
111
+ if (a === "--note") {
112
+ base.note = args[++i] ?? null;
113
+ continue;
114
+ }
115
+ if (a === "--answer") {
116
+ base.answer = args[++i] ?? null;
117
+ continue;
118
+ }
119
+ if (a === "--open") {
120
+ base.openOnly = true;
121
+ continue;
122
+ }
123
+ if (a === "--include-dangerous") {
124
+ base.includeDangerous = true;
125
+ continue;
126
+ }
127
+ if (a === "--format") {
128
+ const fmt = (args[++i] ?? "text").toLowerCase();
129
+ base.format = fmt === "json" ? "json" : "text";
130
+ continue;
131
+ }
132
+ if (!a.startsWith("-") && base.cmd === "resolve" && base.id === null) {
133
+ base.id = a;
134
+ continue;
135
+ }
136
+ if (a === "--help" || a === "-h") {
137
+ return { ...base, error: "help" };
138
+ }
139
+ }
140
+ return base;
141
+ }
142
+ function helpText() {
143
+ return [
144
+ "Usage:",
145
+ " deft escalation:file -- --type <type> --title <text> [--body <text>] [--agent <id>]",
146
+ " [--context refs…] [--sla-hours N] [--dangerous] [--format json]",
147
+ " deft escalation:list [--open] [--type <type>] [--format json]",
148
+ " deft escalation:resolve -- <id> --decision approved|denied|answered|dismissed",
149
+ " [--note <text>] [--answer <text>] [--actor <name>]",
150
+ " deft escalation:batch-approve [--ids a,b] [--include-dangerous] [--note <text>]",
151
+ "",
152
+ `Types: ${ESCALATION_TYPES.join(", ")}`,
153
+ "Bulk batch-approve is limited to cmd_approval + question (non-dangerous by default).",
154
+ "design_decision / approval / resource / external require individual resolve.",
155
+ "Store: .deft/escalations/<id>.json Contract: content/contracts/escalation.md",
156
+ "Compose gated actions with deft authz:grant after approval (Wave 1 grants).",
157
+ ].join("\n");
158
+ }
159
+ export function main(argv = process.argv.slice(2)) {
160
+ const args = parseArgv(argv);
161
+ if (args.error === "help") {
162
+ process.stdout.write(`${helpText()}\n`);
163
+ return 0;
164
+ }
165
+ if (args.error !== undefined) {
166
+ process.stderr.write(`escalation: ${args.error}\n`);
167
+ process.stderr.write(`${helpText()}\n`);
168
+ return 2;
169
+ }
170
+ try {
171
+ switch (args.cmd) {
172
+ case "file": {
173
+ if (args.type === null || args.type.trim().length === 0) {
174
+ process.stderr.write("escalation:file requires --type <type>\n");
175
+ return 2;
176
+ }
177
+ if (args.title === null || args.title.trim().length === 0) {
178
+ process.stderr.write("escalation:file requires --title <text>\n");
179
+ return 2;
180
+ }
181
+ const event = fileEscalation({
182
+ projectRoot: args.projectRoot,
183
+ type: args.type,
184
+ title: args.title,
185
+ body: args.body ?? undefined,
186
+ agentId: args.agentId,
187
+ contextRefs: args.contextRefs,
188
+ slaHours: args.slaHours ?? undefined,
189
+ dangerous: args.dangerous,
190
+ id: args.id ?? undefined,
191
+ });
192
+ if (args.format === "json") {
193
+ process.stdout.write(`${JSON.stringify(event, null, 2)}\n`);
194
+ }
195
+ else {
196
+ process.stdout.write(`✓ escalation filed id=${event.id} type=${event.type} status=${event.status}` +
197
+ `${event.dangerous ? " dangerous=true" : ""}\n`);
198
+ process.stdout.write(` title=${event.title}\n`);
199
+ process.stdout.write(` store=.deft/escalations/${event.id}.json\n`);
200
+ }
201
+ return 0;
202
+ }
203
+ case "list": {
204
+ let typeFilter;
205
+ if (args.type !== null && args.type.trim().length > 0) {
206
+ if (!isEscalationType(args.type.trim().toLowerCase())) {
207
+ process.stderr.write(`escalation:list unknown --type '${args.type}'; expected: ${ESCALATION_TYPES.join(", ")}\n`);
208
+ return 2;
209
+ }
210
+ typeFilter = args.type.trim().toLowerCase();
211
+ }
212
+ const items = listEscalationsFiltered(args.projectRoot, {
213
+ openOnly: args.openOnly,
214
+ type: typeFilter,
215
+ });
216
+ if (args.format === "json") {
217
+ process.stdout.write(`${JSON.stringify(items, null, 2)}\n`);
218
+ return 0;
219
+ }
220
+ if (items.length === 0) {
221
+ process.stdout.write(args.openOnly ? "No open escalations.\n" : "No escalations on disk.\n");
222
+ return 0;
223
+ }
224
+ process.stdout.write(`Escalations (${items.length}):\n`);
225
+ for (const e of items) {
226
+ const dang = e.dangerous ? " !dangerous" : "";
227
+ process.stdout.write(` - ${e.id} [${e.status}] type=${e.type}${dang} agent=${e.agentId} sla=${e.slaHours}h\n`);
228
+ process.stdout.write(` ${e.title}\n`);
229
+ if (e.resolution) {
230
+ process.stdout.write(` → ${e.resolution.decision} by ${e.resolution.resolvedBy} @ ${e.resolution.resolvedAt}\n`);
231
+ }
232
+ }
233
+ return 0;
234
+ }
235
+ case "resolve": {
236
+ if (args.id === null || args.id.trim().length === 0) {
237
+ process.stderr.write("escalation:resolve requires <id>\n");
238
+ return 2;
239
+ }
240
+ if (args.decision === null || args.decision.trim().length === 0) {
241
+ process.stderr.write("escalation:resolve requires --decision approved|denied|answered|dismissed\n");
242
+ return 2;
243
+ }
244
+ const result = resolveEscalation({
245
+ projectRoot: args.projectRoot,
246
+ id: args.id,
247
+ decision: args.decision,
248
+ actor: args.actor,
249
+ note: args.note,
250
+ answer: args.answer,
251
+ });
252
+ if (!result.ok) {
253
+ process.stderr.write(`escalation: ${result.message}\n`);
254
+ return result.code === "not-found" ? 1 : 2;
255
+ }
256
+ if (args.format === "json") {
257
+ process.stdout.write(`${JSON.stringify(result.event, null, 2)}\n`);
258
+ }
259
+ else {
260
+ process.stdout.write(`✓ resolved id=${result.event.id} decision=${result.event.resolution?.decision}\n`);
261
+ process.stdout.write(" For gated product actions, mint a Wave 1 grant: deft authz:grant …\n");
262
+ }
263
+ return 0;
264
+ }
265
+ case "batch-approve": {
266
+ const batch = batchApproveEscalations({
267
+ projectRoot: args.projectRoot,
268
+ ids: args.ids.length > 0 ? args.ids : undefined,
269
+ actor: args.actor,
270
+ note: args.note,
271
+ includeDangerous: args.includeDangerous,
272
+ });
273
+ if (args.format === "json") {
274
+ process.stdout.write(`${JSON.stringify(batch, null, 2)}\n`);
275
+ }
276
+ else {
277
+ process.stdout.write(`✓ batch-approve approved=${batch.approved.length} skipped=${batch.skipped.length}\n`);
278
+ for (const e of batch.approved) {
279
+ process.stdout.write(` + ${e.id} type=${e.type}\n`);
280
+ }
281
+ for (const s of batch.skipped) {
282
+ process.stdout.write(` - skip ${s.id}: ${s.reason}\n`);
283
+ }
284
+ }
285
+ return 0;
286
+ }
287
+ default:
288
+ process.stderr.write(`${helpText()}\n`);
289
+ return 2;
290
+ }
291
+ }
292
+ catch (err) {
293
+ process.stderr.write(`escalation: ${String(err)}\n`);
294
+ return 1;
295
+ }
296
+ }
297
+ export default main;
298
+ if (import.meta.url === `file://${process.argv[1]?.replace(/\\/g, "/")}`) {
299
+ process.exitCode = main();
300
+ }
301
+ //# sourceMappingURL=escalation-cli.js.map
@@ -48,10 +48,16 @@ export declare function readStdinHardened(readOnce: () => string, options?: {
48
48
  nowMs?: () => number;
49
49
  }): string;
50
50
  export declare function parseArgs(argv: readonly string[]): ParsedArgs;
51
+ /** @deprecated Prefer ParsedHookPayload from @deftai/directive-core/hooks (#2950). */
51
52
  export interface ParsedPayload {
52
53
  readonly payload: unknown;
53
54
  readonly context: HookPayloadContext;
54
55
  }
56
+ /**
57
+ * Parse host hook stdin into payload + context.
58
+ * Pure implementation lives in core classify (`parseHookStdin`); CLI re-exports
59
+ * for backward-compatible imports in tests (#2734 / #2738 / #2950).
60
+ */
55
61
  export declare function parsePayload(raw: string): ParsedPayload;
56
62
  /**
57
63
  * Provider-neutral hook dispatch CLI entry (`deft hook:dispatch` / `deft-hook`).
@@ -2,7 +2,7 @@
2
2
  import { readFileSync } from "node:fs";
3
3
  import { resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- import { decideHook, hookPayloadTopLevelKeys, isHookEvent, isHookHost, normalizeHookProjectRoot, projectRootFromHookPayload, renderHostDecision, } from "@deftai/directive-core/hooks";
5
+ import { decideHook, hookPayloadTopLevelKeys, isHookEvent, isHookHost, normalizeHookProjectRoot, parseHookStdin, projectRootFromHookPayload, renderHostDecision, } from "@deftai/directive-core/hooks";
6
6
  /**
7
7
  * How long to re-poll stdin after an empty first read before concluding the host
8
8
  * sent nothing (#2864). Keeps well under Cursor's deposited `timeout: 5` while
@@ -103,59 +103,13 @@ export function parseArgs(argv) {
103
103
  return { error: "--event is required" };
104
104
  return parsed;
105
105
  }
106
- const UTF8_BOM = "\uFEFF";
107
- const APPLY_PATCH_BEGIN_MARKER = "*** Begin Patch";
108
- /** Single-file Add/Update only other *** File: ops must fail closed (#2738 Greptile). */
109
- const APPLY_PATCH_MUTATION_LINE_RE = /^\*\*\* (Add File|Update File|Delete File|Move File|Rename File): (.+)$/gm;
110
- function stripUtf8Bom(raw) {
111
- return raw.startsWith(UTF8_BOM) ? raw.slice(UTF8_BOM.length) : raw;
112
- }
113
- function trySynthesizeFreeFormApplyPatch(normalized) {
114
- if (!normalized.includes(APPLY_PATCH_BEGIN_MARKER))
115
- return null;
116
- const mutations = [];
117
- for (const match of normalized.matchAll(APPLY_PATCH_MUTATION_LINE_RE)) {
118
- const op = match[1];
119
- const path = match[2]?.trim();
120
- if (op === undefined || !path)
121
- continue;
122
- mutations.push({ op, path });
123
- }
124
- if (mutations.length !== 1)
125
- return null;
126
- const sole = mutations[0];
127
- if (sole === undefined || (sole.op !== "Add File" && sole.op !== "Update File"))
128
- return null;
129
- return {
130
- payload: {
131
- tool_name: "ApplyPatch",
132
- tool_input: {
133
- path: sole.path,
134
- patch: normalized,
135
- },
136
- },
137
- context: {},
138
- };
139
- }
106
+ /**
107
+ * Parse host hook stdin into payload + context.
108
+ * Pure implementation lives in core classify (`parseHookStdin`); CLI re-exports
109
+ * for backward-compatible imports in tests (#2734 / #2738 / #2950).
110
+ */
140
111
  export function parsePayload(raw) {
141
- if (raw.trim().length === 0) {
142
- return { payload: {}, context: { stdinEmpty: true } };
143
- }
144
- const normalized = stripUtf8Bom(raw);
145
- if (normalized.trim().length === 0) {
146
- return { payload: {}, context: { stdinEmpty: true } };
147
- }
148
- try {
149
- return { payload: JSON.parse(normalized), context: {} };
150
- }
151
- catch {
152
- const synthesized = trySynthesizeFreeFormApplyPatch(normalized);
153
- if (synthesized !== null)
154
- return synthesized;
155
- // tool.before is installed only on direct-write matchers, so an unreadable
156
- // payload becomes a missing-tool denial rather than a fail-open crash.
157
- return { payload: {}, context: { parseFailed: true } };
158
- }
112
+ return parseHookStdin(raw);
159
113
  }
160
114
  /**
161
115
  * Provider-neutral hook dispatch CLI entry (`deft hook:dispatch` / `deft-hook`).
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ export interface ParsedLifecycleStatsArgs {
3
+ projectRoot: string;
4
+ since: string;
5
+ json: boolean;
6
+ error?: string;
7
+ }
8
+ /** Parse lifecycle:stats CLI args. */
9
+ export declare function parseArgs(argv: readonly string[]): ParsedLifecycleStatsArgs;
10
+ /** Native lifecycle:stats handler (#2995). */
11
+ export declare function run(argv?: readonly string[]): number;
12
+ //# sourceMappingURL=lifecycle-stats.d.ts.map
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI: deft lifecycle:stats — local xBRIEF folder counts for process rollups (#2995).
4
+ */
5
+ import { resolve } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import { collectLifecycleStats, formatLifecycleStatsText, parseDurationMs, } from "@deftai/directive-core/lifecycle";
8
+ /** Parse lifecycle:stats CLI args. */
9
+ export function parseArgs(argv) {
10
+ const parsed = {
11
+ projectRoot: ".",
12
+ since: "7d",
13
+ json: false,
14
+ };
15
+ for (let i = 0; i < argv.length; i += 1) {
16
+ const arg = argv[i];
17
+ if (arg === undefined)
18
+ continue;
19
+ if (arg === "--json") {
20
+ parsed.json = true;
21
+ continue;
22
+ }
23
+ if (arg === "--help" || arg === "-h") {
24
+ return {
25
+ ...parsed,
26
+ error: "usage: lifecycle:stats [--since=7d] [--json] [--project-root <path>]\n" +
27
+ " Counts xbrief lifecycle folders (filesystem only). See commands.md § lifecycle:stats.",
28
+ };
29
+ }
30
+ if (arg === "--project-root") {
31
+ const value = argv[i + 1];
32
+ if (value === undefined) {
33
+ return { ...parsed, error: "argument --project-root: expected one argument" };
34
+ }
35
+ parsed.projectRoot = value;
36
+ i += 1;
37
+ continue;
38
+ }
39
+ if (arg.startsWith("--project-root=")) {
40
+ parsed.projectRoot = arg.slice("--project-root=".length);
41
+ continue;
42
+ }
43
+ if (arg === "--since") {
44
+ const value = argv[i + 1];
45
+ if (value === undefined) {
46
+ return { ...parsed, error: "argument --since: expected one argument" };
47
+ }
48
+ parsed.since = value;
49
+ i += 1;
50
+ continue;
51
+ }
52
+ if (arg.startsWith("--since=")) {
53
+ parsed.since = arg.slice("--since=".length);
54
+ continue;
55
+ }
56
+ if (arg.startsWith("-")) {
57
+ return { ...parsed, error: `unknown flag: ${arg}` };
58
+ }
59
+ return { ...parsed, error: `unexpected argument: ${arg}` };
60
+ }
61
+ return parsed;
62
+ }
63
+ /** Native lifecycle:stats handler (#2995). */
64
+ export function run(argv = process.argv.slice(2)) {
65
+ const args = parseArgs(argv);
66
+ if (args.error !== undefined) {
67
+ process.stderr.write(`lifecycle_stats: ${args.error}\n`);
68
+ return 2;
69
+ }
70
+ try {
71
+ parseDurationMs(args.since);
72
+ }
73
+ catch (err) {
74
+ const msg = err instanceof Error ? err.message : String(err);
75
+ process.stderr.write(`lifecycle_stats: ${msg}\n`);
76
+ return 2;
77
+ }
78
+ try {
79
+ const stats = collectLifecycleStats({
80
+ projectRoot: resolve(args.projectRoot),
81
+ since: args.since,
82
+ });
83
+ if (args.json) {
84
+ process.stdout.write(`${JSON.stringify(stats, null, 2)}\n`);
85
+ }
86
+ else {
87
+ process.stdout.write(formatLifecycleStatsText(stats));
88
+ }
89
+ return 0;
90
+ }
91
+ catch (err) {
92
+ const msg = err instanceof Error ? err.message : String(err);
93
+ process.stderr.write(`lifecycle_stats: ${msg}\n`);
94
+ return 1;
95
+ }
96
+ }
97
+ if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
98
+ process.exit(run(process.argv.slice(2)));
99
+ }
100
+ //# sourceMappingURL=lifecycle-stats.js.map
package/dist/policy.d.ts CHANGED
@@ -7,7 +7,7 @@ interface ShowArgs {
7
7
  error?: string;
8
8
  }
9
9
  interface SetArgs {
10
- cmd: "show" | "enforce-branches" | "allow-direct-commits" | "enable-value-feedback" | "clear-value-feedback" | "disable-directive" | "enable-directive" | "resolve";
10
+ cmd: "show" | "enforce-branches" | "allow-direct-commits" | "allow-bot-merge" | "enable-value-feedback" | "clear-value-feedback" | "disable-directive" | "enable-directive" | "resolve";
11
11
  confirm: boolean;
12
12
  actor: string;
13
13
  note: string;
package/dist/policy.js CHANGED
@@ -6,7 +6,7 @@
6
6
  import { existsSync } from "node:fs";
7
7
  import { resolve as pathResolve, relative } from "node:path";
8
8
  import { fileURLToPath } from "node:url";
9
- import { clearValueFeedback, createNoDeftDirectiveFlag, describeShadowedPlanExtension, detectNoDeftDirective, detectShadowedPlanExtensions, disclosureLine, enableValueFeedback, FIELD_VALUE_FEEDBACK, FIELD_VALUE_FEEDBACK_CLI_ALIAS, formatValueFeedbackStatusLine, inspectAllPolicies, inspectOnePolicy, loadProjectDefinition, NO_DEFT_DIRECTIVE_DISABLED_MESSAGE, NO_DEFT_DIRECTIVE_FLAG_NAME, NO_DEFT_DIRECTIVE_INCONSISTENT_MESSAGE, policyColonInvocation, projectDefinitionPath, pythonListRepr, pythonStringRepr, registeredPolicyNames, removeNoDeftDirectiveFlag, renderJson, renderText, resolvePolicy, resolveValueFeedback, setPolicy, } from "@deftai/directive-core/policy";
9
+ import { ALLOW_BOT_MERGE_CAPABILITY_COST, clearValueFeedback, createNoDeftDirectiveFlag, describeShadowedPlanExtension, detectNoDeftDirective, detectShadowedPlanExtensions, disclosureLine, enableValueFeedback, FIELD_VALUE_FEEDBACK, FIELD_VALUE_FEEDBACK_CLI_ALIAS, formatValueFeedbackStatusLine, humanMergeDisclosureLine, inspectAllPolicies, inspectOnePolicy, loadProjectDefinition, NO_DEFT_DIRECTIVE_DISABLED_MESSAGE, NO_DEFT_DIRECTIVE_FLAG_NAME, NO_DEFT_DIRECTIVE_INCONSISTENT_MESSAGE, policyColonInvocation, projectDefinitionPath, pythonListRepr, pythonStringRepr, registeredPolicyNames, removeNoDeftDirectiveFlag, renderJson, renderText, resolveHumanMergePolicy, resolvePolicy, resolveValueFeedback, setPolicy, setRequireHumanMerge, } from "@deftai/directive-core/policy";
10
10
  const CAPABILITY_COST_DISCLOSURE = "\u26a0 Capability-cost disclosure -- enabling direct commits to the default " +
11
11
  "branch turns OFF the deft branch-protection policy.\n" +
12
12
  " \u2022 Pre-commit + pre-push hooks will no longer block default-branch " +
@@ -114,7 +114,7 @@ export function parseShowArgs(argv) {
114
114
  /** Parse argv for the policy CLI (show + set subcommands). */
115
115
  export function parseArgs(argv) {
116
116
  if (argv.length === 0) {
117
- const usage = "usage: policy [show|enforce-branches|allow-direct-commits|enable-value-feedback|clear-value-feedback|disable-directive|enable-directive|resolve] ...";
117
+ const usage = "usage: policy [show|enforce-branches|allow-direct-commits|allow-bot-merge|enable-value-feedback|clear-value-feedback|disable-directive|enable-directive|resolve] ...";
118
118
  return makeSetError(usage);
119
119
  }
120
120
  const cmd = argv[0];
@@ -148,6 +148,7 @@ export function parseArgs(argv) {
148
148
  }
149
149
  if (cmd === "enforce-branches" ||
150
150
  cmd === "allow-direct-commits" ||
151
+ cmd === "allow-bot-merge" ||
151
152
  cmd === "enable-value-feedback" ||
152
153
  cmd === "clear-value-feedback" ||
153
154
  cmd === "disable-directive" ||
@@ -157,13 +158,15 @@ export function parseArgs(argv) {
157
158
  ? policyColonInvocation("enforce-branches")
158
159
  : cmd === "allow-direct-commits"
159
160
  ? policyColonInvocation("allow-direct-commits")
160
- : cmd === "enable-value-feedback"
161
- ? policyColonInvocation("enable-value-feedback")
162
- : cmd === "clear-value-feedback"
163
- ? policyColonInvocation("clear-value-feedback")
164
- : cmd === "disable-directive"
165
- ? policyColonInvocation("disable-directive")
166
- : policyColonInvocation("enable-directive");
161
+ : cmd === "allow-bot-merge"
162
+ ? policyColonInvocation("allow-bot-merge")
163
+ : cmd === "enable-value-feedback"
164
+ ? policyColonInvocation("enable-value-feedback")
165
+ : cmd === "clear-value-feedback"
166
+ ? policyColonInvocation("clear-value-feedback")
167
+ : cmd === "disable-directive"
168
+ ? policyColonInvocation("disable-directive")
169
+ : policyColonInvocation("enable-directive");
167
170
  let note = "";
168
171
  let projectRoot = ".";
169
172
  for (let i = 1; i < argv.length; i += 1) {
@@ -381,6 +384,48 @@ function runEnableDirective(args) {
381
384
  process.stdout.write("Directive opt-out cleared. Run `directive init` or `directive update` to ensure install.\n");
382
385
  return 0;
383
386
  }
387
+ /** Allow agent/bot merge by writing requireHumanMerge=false (#1193). */
388
+ function runAllowBotMerge(args) {
389
+ const projectRoot = pathResolve(args.projectRoot);
390
+ if (!args.confirm) {
391
+ process.stdout.write(`${ALLOW_BOT_MERGE_CAPABILITY_COST}\n\n`);
392
+ process.stdout.write(`Re-run with --confirm to apply: ${policyColonInvocation("allow-bot-merge", " -- --confirm")}\n`);
393
+ return 1;
394
+ }
395
+ try {
396
+ const { changed, auditEntry } = setRequireHumanMerge(projectRoot, {
397
+ requireHumanMerge: false,
398
+ actor: args.actor,
399
+ note: args.note,
400
+ });
401
+ process.stdout.write(`\u2713 plan.policy.requireHumanMerge=false (human merge gate OFF; agent may merge).\n`);
402
+ if (changed) {
403
+ process.stdout.write(` audit: meta/policy-changes.log :: ${auditEntry}\n`);
404
+ }
405
+ else {
406
+ process.stdout.write(" no-op: value already matched (audit entry still appended for trail).\n");
407
+ }
408
+ const line = humanMergeDisclosureLine(resolveHumanMergePolicy(projectRoot));
409
+ if (line !== null) {
410
+ process.stdout.write(`${line}\n`);
411
+ }
412
+ else {
413
+ process.stdout.write("[deft policy] Human merge gate is OFF; agent may merge when other gates allow.\n");
414
+ }
415
+ return 0;
416
+ }
417
+ catch (err) {
418
+ const message = err instanceof Error ? err.message : String(err);
419
+ if (message.includes("PROJECT-DEFINITION not found")) {
420
+ process.stderr.write(`\u274c ${message}\n`);
421
+ const pdRel = relative(projectRoot, projectDefinitionPath(projectRoot));
422
+ process.stderr.write(` Recovery: run \`task setup\` to generate ${pdRel}.\n`);
423
+ return 2;
424
+ }
425
+ process.stderr.write(`\u274c Config error: ${message}\n`);
426
+ return 2;
427
+ }
428
+ }
384
429
  /** Run the policy CLI; returns process exit code. */
385
430
  export function run(argv) {
386
431
  const args = parseArgs(argv);
@@ -402,6 +447,9 @@ export function run(argv) {
402
447
  if (args.cmd === "enforce-branches" || args.cmd === "allow-direct-commits") {
403
448
  return runSet(args);
404
449
  }
450
+ if (args.cmd === "allow-bot-merge") {
451
+ return runAllowBotMerge(args);
452
+ }
405
453
  if (args.cmd === "enable-value-feedback") {
406
454
  return runEnableValueFeedback(args);
407
455
  }
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export declare function run(argv: string[]): number;
3
+ //# sourceMappingURL=pr-finish-loop.d.ts.map
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import { fileURLToPath } from "node:url";
3
+ import { cmdPrFinishLoop } from "@deftai/directive-core/dist/finish-loop/main.js";
4
+ export function run(argv) {
5
+ return cmdPrFinishLoop(argv);
6
+ }
7
+ if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
8
+ process.exit(run(process.argv.slice(2)));
9
+ }
10
+ //# sourceMappingURL=pr-finish-loop.js.map
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ export interface ParsedSessionReadyArgs {
3
+ projectRoot: string;
4
+ emitJson: boolean;
5
+ withNetwork: boolean;
6
+ repo: string | null;
7
+ error?: string;
8
+ }
9
+ /** Parse session:ready CLI args. */
10
+ export declare function parseArgs(argv: readonly string[]): ParsedSessionReadyArgs;
11
+ /** Native session:ready handler (#2993). */
12
+ export declare function run(argv?: readonly string[]): number;
13
+ //# sourceMappingURL=session-ready.d.ts.map