@deftai/directive-core 0.87.0 → 0.89.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 (193) hide show
  1. package/dist/authz/actions.d.ts +55 -0
  2. package/dist/authz/actions.js +125 -0
  3. package/dist/authz/classify.d.ts +23 -0
  4. package/dist/authz/classify.js +217 -0
  5. package/dist/authz/closed-verb.d.ts +42 -0
  6. package/dist/authz/closed-verb.js +279 -0
  7. package/dist/authz/evaluate.d.ts +41 -0
  8. package/dist/authz/evaluate.js +298 -0
  9. package/dist/authz/index.d.ts +15 -0
  10. package/dist/authz/index.js +15 -0
  11. package/dist/authz/origin.d.ts +28 -0
  12. package/dist/authz/origin.js +64 -0
  13. package/dist/authz/paths.d.ts +6 -0
  14. package/dist/authz/paths.js +19 -0
  15. package/dist/authz/store.d.ts +40 -0
  16. package/dist/authz/store.js +317 -0
  17. package/dist/authz/templates.d.ts +139 -0
  18. package/dist/authz/templates.js +241 -0
  19. package/dist/authz/types.d.ts +111 -0
  20. package/dist/authz/types.js +41 -0
  21. package/dist/authz/verb-classification.d.ts +44 -0
  22. package/dist/authz/verb-classification.js +237 -0
  23. package/dist/branch/evaluate.js +6 -1
  24. package/dist/cache/fetch.js +10 -5
  25. package/dist/cache/io.d.ts +9 -2
  26. package/dist/cache/io.js +30 -10
  27. package/dist/cache/scanner.d.ts +11 -1
  28. package/dist/cache/scanner.js +29 -4
  29. package/dist/cache/task-cache/store.js +11 -7
  30. package/dist/capacity/backfill.js +10 -4
  31. package/dist/category-b-namespace/index.js +10 -4
  32. package/dist/check/gate-lists.js +1 -0
  33. package/dist/codebase/default-extractor.js +3 -0
  34. package/dist/codebase/map.js +11 -4
  35. package/dist/content-contracts/skills/helpers.d.ts +10 -0
  36. package/dist/content-contracts/skills/helpers.js +35 -0
  37. package/dist/deposit/copy-tree.d.ts +19 -1
  38. package/dist/deposit/copy-tree.js +134 -5
  39. package/dist/doctor/doctor-state.js +28 -4
  40. package/dist/doctor/main.d.ts +10 -0
  41. package/dist/doctor/main.js +208 -0
  42. package/dist/doctor/types.d.ts +11 -0
  43. package/dist/escalation/actions.d.ts +63 -0
  44. package/dist/escalation/actions.js +137 -0
  45. package/dist/escalation/index.d.ts +8 -0
  46. package/dist/escalation/index.js +8 -0
  47. package/dist/escalation/paths.d.ts +3 -0
  48. package/dist/escalation/paths.js +10 -0
  49. package/dist/escalation/store.d.ts +17 -0
  50. package/dist/escalation/store.js +172 -0
  51. package/dist/escalation/types.d.ts +73 -0
  52. package/dist/escalation/types.js +43 -0
  53. package/dist/eval/crud-telemetry.js +30 -10
  54. package/dist/eval/health.js +22 -7
  55. package/dist/eval/readback.js +11 -10
  56. package/dist/eval/run.js +32 -16
  57. package/dist/events/attribution-enrichment.js +10 -4
  58. package/dist/finish-loop/directive-finish-loop.d.ts +37 -0
  59. package/dist/finish-loop/directive-finish-loop.js +239 -0
  60. package/dist/finish-loop/grant-gate.d.ts +31 -0
  61. package/dist/finish-loop/grant-gate.js +169 -0
  62. package/dist/finish-loop/index.d.ts +11 -0
  63. package/dist/finish-loop/index.js +11 -0
  64. package/dist/finish-loop/main.d.ts +35 -0
  65. package/dist/finish-loop/main.js +365 -0
  66. package/dist/finish-loop/pr-finish-loop.d.ts +34 -0
  67. package/dist/finish-loop/pr-finish-loop.js +217 -0
  68. package/dist/finish-loop/progress.d.ts +17 -0
  69. package/dist/finish-loop/progress.js +45 -0
  70. package/dist/finish-loop/queue.d.ts +16 -0
  71. package/dist/finish-loop/queue.js +65 -0
  72. package/dist/finish-loop/types.d.ts +52 -0
  73. package/dist/finish-loop/types.js +10 -0
  74. package/dist/fs/contained-write.d.ts +101 -0
  75. package/dist/fs/contained-write.js +281 -0
  76. package/dist/fs/projection-containment.d.ts +18 -0
  77. package/dist/fs/projection-containment.js +40 -0
  78. package/dist/hooks/classify/classify.d.ts +10 -0
  79. package/dist/hooks/classify/classify.js +37 -0
  80. package/dist/hooks/classify/index.d.ts +14 -0
  81. package/dist/hooks/classify/index.js +13 -0
  82. package/dist/hooks/classify/paths.d.ts +22 -0
  83. package/dist/hooks/classify/paths.js +72 -0
  84. package/dist/hooks/classify/payload.d.ts +16 -0
  85. package/dist/hooks/classify/payload.js +45 -0
  86. package/dist/hooks/classify/stdin.d.ts +12 -0
  87. package/dist/hooks/classify/stdin.js +63 -0
  88. package/dist/hooks/classify/tool-name.d.ts +18 -0
  89. package/dist/hooks/classify/tool-name.js +85 -0
  90. package/dist/hooks/classify/types.d.ts +41 -0
  91. package/dist/hooks/classify/types.js +7 -0
  92. package/dist/hooks/dispatcher.d.ts +30 -15
  93. package/dist/hooks/dispatcher.js +326 -134
  94. package/dist/hooks/fixtures/cases.d.ts +44 -0
  95. package/dist/hooks/fixtures/cases.js +595 -0
  96. package/dist/hooks/fixtures/index.d.ts +2 -0
  97. package/dist/hooks/fixtures/index.js +2 -0
  98. package/dist/hooks/index.d.ts +2 -0
  99. package/dist/hooks/index.js +2 -0
  100. package/dist/hooks/tools.d.ts +31 -0
  101. package/dist/hooks/tools.js +74 -0
  102. package/dist/index.d.ts +4 -0
  103. package/dist/index.js +5 -0
  104. package/dist/init-deposit/agent-hooks.d.ts +1 -1
  105. package/dist/init-deposit/agent-hooks.js +65 -10
  106. package/dist/init-deposit/gitignore.js +11 -3
  107. package/dist/init-deposit/headless-manifest.js +12 -4
  108. package/dist/init-deposit/hygiene.d.ts +16 -0
  109. package/dist/init-deposit/hygiene.js +26 -0
  110. package/dist/init-deposit/init-dispatch.js +28 -0
  111. package/dist/init-deposit/migrate.d.ts +1 -1
  112. package/dist/init-deposit/migrate.js +13 -3
  113. package/dist/init-deposit/prettierignore.js +12 -5
  114. package/dist/init-deposit/refresh.js +38 -7
  115. package/dist/init-deposit/scaffold.js +42 -29
  116. package/dist/init-deposit/xbrief-projections.js +17 -10
  117. package/dist/intake/candidates-log.js +46 -35
  118. package/dist/intake/github-body-cli.d.ts +6 -0
  119. package/dist/intake/github-body-cli.js +17 -0
  120. package/dist/intake/github-body.d.ts +46 -3
  121. package/dist/intake/github-body.js +191 -20
  122. package/dist/intake/issue-emit.d.ts +1 -0
  123. package/dist/intake/issue-emit.js +41 -23
  124. package/dist/intake/issue-ingest.js +11 -2
  125. package/dist/lifecycle/event-detect.js +12 -4
  126. package/dist/lifecycle/events.js +29 -20
  127. package/dist/lifecycle/lifecycle-hygiene.js +9 -3
  128. package/dist/packs/pack-render.d.ts +33 -0
  129. package/dist/packs/pack-render.js +172 -13
  130. package/dist/packs/quarantine-ext.d.ts +10 -0
  131. package/dist/packs/quarantine-ext.js +26 -2
  132. package/dist/plan-sequence/store.js +11 -4
  133. package/dist/policy/hotfix-criteria.d.ts +79 -0
  134. package/dist/policy/hotfix-criteria.js +197 -0
  135. package/dist/policy/index.d.ts +5 -0
  136. package/dist/policy/index.js +32 -1
  137. package/dist/policy/intent-ceiling.d.ts +79 -0
  138. package/dist/policy/intent-ceiling.js +181 -0
  139. package/dist/policy/no-deft-directive.d.ts +59 -0
  140. package/dist/policy/no-deft-directive.js +110 -0
  141. package/dist/policy/org-force-on-migration.d.ts +52 -0
  142. package/dist/policy/org-force-on-migration.js +269 -27
  143. package/dist/policy/require-human-merge.d.ts +75 -0
  144. package/dist/policy/require-human-merge.js +324 -0
  145. package/dist/policy/resolve.js +17 -6
  146. package/dist/policy/runtime-authority.d.ts +56 -2
  147. package/dist/policy/runtime-authority.js +297 -3
  148. package/dist/policy/write-fence.d.ts +78 -0
  149. package/dist/policy/write-fence.js +164 -0
  150. package/dist/pr-wait-mergeable/cascade.d.ts +10 -0
  151. package/dist/pr-wait-mergeable/cascade.js +28 -0
  152. package/dist/preflight/evaluate.js +10 -0
  153. package/dist/product-signal/consent.js +21 -5
  154. package/dist/product-signal/submit.js +22 -12
  155. package/dist/release/build-dist-runner.js +5 -2
  156. package/dist/release/build-dist.d.ts +19 -1
  157. package/dist/release/build-dist.js +50 -2
  158. package/dist/release/native-steps.js +7 -2
  159. package/dist/release-publish/pipeline.d.ts +13 -0
  160. package/dist/release-publish/pipeline.js +46 -1
  161. package/dist/scope/audit-log.js +19 -24
  162. package/dist/scope/decompose.js +10 -5
  163. package/dist/scope/decomposed-refs.js +18 -3
  164. package/dist/scope/demote.js +9 -2
  165. package/dist/scope/undo.js +9 -2
  166. package/dist/session/release-availability.js +26 -6
  167. package/dist/session/ritual-sentinel.js +19 -12
  168. package/dist/session/session-start-hook.d.ts +3 -0
  169. package/dist/session/session-start-hook.js +15 -0
  170. package/dist/session/session-start.js +37 -0
  171. package/dist/staleness-tickler/state.js +26 -6
  172. package/dist/swarm/launch.js +13 -3
  173. package/dist/swarm/routing.js +9 -3
  174. package/dist/task-surface/index.js +24 -9
  175. package/dist/triage/bootstrap/gitignore.js +53 -10
  176. package/dist/triage/cache-path.js +10 -3
  177. package/dist/triage/welcome/summary.js +11 -4
  178. package/dist/triage/welcome/writers.js +45 -16
  179. package/dist/validate-content/validate-links.js +5 -0
  180. package/dist/value/readback.js +11 -6
  181. package/dist/vbrief-activate/activate.js +12 -4
  182. package/dist/vbrief-build/project-definition-io.js +14 -6
  183. package/dist/verify-source/contained-writes.d.ts +59 -0
  184. package/dist/verify-source/contained-writes.js +272 -0
  185. package/dist/verify-source/cursor-tier1.js +7 -2
  186. package/dist/verify-source/index.d.ts +1 -0
  187. package/dist/verify-source/index.js +1 -0
  188. package/dist/verify-source/openclaw-tier1.js +19 -1
  189. package/dist/verify-source/verify-stubs.js +3 -0
  190. package/dist/xbrief-migrate/migrate-project.js +26 -12
  191. package/dist/xbrief-migrate/transforms.d.ts +4 -2
  192. package/dist/xbrief-migrate/transforms.js +37 -14
  193. package/package.json +16 -3
@@ -0,0 +1,239 @@
1
+ /**
2
+ * directive:finish-loop — outer walk-away cascade (#871 Wave 5).
3
+ *
4
+ * 1. Gate on finish-loop human-origin grant (or DEFT_ALLOW_FINISH_LOOP)
5
+ * 2. Scan xbrief queue (active/pending)
6
+ * 3. Append progress JSONL each iteration
7
+ * 4. When --pr supplied, run pr:finish-loop
8
+ * 5. Implementation steps are agent-owned: CLI emits AGENT_STEP and exits
9
+ * ACTION_REQUIRED so a live agent continues; full autonomous coding is
10
+ * not inlined in the Taskfile.
11
+ *
12
+ * Halt: empty queue | grant expiry/deny | max iterations | gate deny
13
+ */
14
+ import { evaluateFinishLoopGrant } from "./grant-gate.js";
15
+ import { runPrFinishLoop } from "./pr-finish-loop.js";
16
+ import { appendFinishLoopProgress, finishLoopProgressPath, makeProgressLine } from "./progress.js";
17
+ import { scanFinishLoopQueue } from "./queue.js";
18
+ import { EXIT_ACTION_REQUIRED, EXIT_BLOCKED, EXIT_OK, } from "./types.js";
19
+ export const DEFAULT_MAX_ITERATIONS = 20;
20
+ export function runDirectiveFinishLoop(options) {
21
+ const projectRoot = options.projectRoot;
22
+ const progressPath = finishLoopProgressPath(projectRoot);
23
+ const maxIterations = options.maxIterations ?? DEFAULT_MAX_ITERATIONS;
24
+ const writeProgress = options.writeProgress !== false;
25
+ const scan = options.scanQueueFn ?? scanFinishLoopQueue;
26
+ const prLoop = options.prFinishLoopFn ?? runPrFinishLoop;
27
+ const progress = (phase, iteration, haltReason, message, fields = {}) => {
28
+ if (!writeProgress)
29
+ return;
30
+ appendFinishLoopProgress(projectRoot, makeProgressLine({
31
+ phase,
32
+ iteration,
33
+ haltReason,
34
+ message,
35
+ prNumber: fields.prNumber ?? options.prNumber ?? null,
36
+ grantId: fields.grantId ?? null,
37
+ queueCount: fields.queueCount ?? null,
38
+ exitCode: fields.exitCode ?? null,
39
+ extra: fields.extra,
40
+ }));
41
+ };
42
+ // --- Gate ---
43
+ let grantId = null;
44
+ if (options.skipGrantGate !== true) {
45
+ const gate = evaluateFinishLoopGrant({
46
+ projectRoot,
47
+ env: options.env,
48
+ now: options.now,
49
+ });
50
+ if (!gate.allowed) {
51
+ const haltReason = gate.code === "authz-grant-expired" ? "grant-expired" : "grant-missing";
52
+ const message = `BLOCKED directive:finish-loop: ${gate.reason}`;
53
+ progress("gate", 0, haltReason, message, {
54
+ grantId: gate.grantId,
55
+ exitCode: EXIT_BLOCKED,
56
+ });
57
+ return {
58
+ exitCode: EXIT_BLOCKED,
59
+ haltReason,
60
+ message,
61
+ iterations: 0,
62
+ queueCount: 0,
63
+ grantId: gate.grantId,
64
+ progressPath,
65
+ };
66
+ }
67
+ grantId = gate.grantId;
68
+ progress("gate", 0, null, `grant ok: ${gate.reason}`, { grantId });
69
+ }
70
+ let lastQueueCount = 0;
71
+ for (let iteration = 1; iteration <= maxIterations; iteration += 1) {
72
+ // Re-check grant each iteration (expiry mid-loop)
73
+ if (options.skipGrantGate !== true) {
74
+ const gate = evaluateFinishLoopGrant({
75
+ projectRoot,
76
+ env: options.env,
77
+ now: options.now,
78
+ });
79
+ if (!gate.allowed) {
80
+ const haltReason = gate.code === "authz-grant-expired" ? "grant-expired" : "grant-deny";
81
+ const message = `BLOCKED directive:finish-loop mid-loop: ${gate.reason}`;
82
+ progress("halt", iteration, haltReason, message, {
83
+ grantId: gate.grantId,
84
+ exitCode: EXIT_BLOCKED,
85
+ queueCount: lastQueueCount,
86
+ });
87
+ return {
88
+ exitCode: EXIT_BLOCKED,
89
+ haltReason,
90
+ message,
91
+ iterations: iteration,
92
+ queueCount: lastQueueCount,
93
+ grantId: gate.grantId,
94
+ progressPath,
95
+ };
96
+ }
97
+ grantId = gate.grantId;
98
+ }
99
+ const queue = scan(projectRoot);
100
+ lastQueueCount = queue.length;
101
+ progress("queue-scan", iteration, null, `queue count=${queue.length}`, {
102
+ grantId,
103
+ queueCount: queue.length,
104
+ });
105
+ if (queue.length === 0 && (options.prNumber === null || options.prNumber === undefined)) {
106
+ const message = "directive:finish-loop complete: empty scope queue (no active/pending xBRIEF work).";
107
+ progress("halt", iteration, "empty-queue", message, {
108
+ grantId,
109
+ queueCount: 0,
110
+ exitCode: EXIT_OK,
111
+ });
112
+ return {
113
+ exitCode: EXIT_OK,
114
+ haltReason: "empty-queue",
115
+ message,
116
+ iterations: iteration,
117
+ queueCount: 0,
118
+ grantId,
119
+ progressPath,
120
+ };
121
+ }
122
+ // Optional PR shepherd
123
+ if (options.prNumber !== null && options.prNumber !== undefined) {
124
+ const prResult = prLoop({
125
+ projectRoot,
126
+ prNumber: options.prNumber,
127
+ repo: options.repo,
128
+ maxWaitMinutes: options.maxWaitMinutes,
129
+ pollSeconds: options.pollSeconds,
130
+ oneShot: options.oneShot,
131
+ merge: options.merge,
132
+ skipGrantGate: true, // already gated
133
+ env: options.env,
134
+ now: options.now,
135
+ writeProgress,
136
+ iteration,
137
+ });
138
+ progress("pr-watch", iteration, prResult.haltReason, prResult.message, {
139
+ grantId,
140
+ queueCount: queue.length,
141
+ exitCode: prResult.exitCode,
142
+ prNumber: options.prNumber,
143
+ });
144
+ if (prResult.haltReason === "address-findings") {
145
+ return {
146
+ exitCode: EXIT_ACTION_REQUIRED,
147
+ haltReason: "address-findings",
148
+ message: prResult.message,
149
+ iterations: iteration,
150
+ queueCount: queue.length,
151
+ grantId,
152
+ progressPath,
153
+ };
154
+ }
155
+ if (prResult.haltReason === "require-human-merge") {
156
+ return {
157
+ exitCode: EXIT_ACTION_REQUIRED,
158
+ haltReason: "require-human-merge",
159
+ message: prResult.message,
160
+ iterations: iteration,
161
+ queueCount: queue.length,
162
+ grantId,
163
+ progressPath,
164
+ };
165
+ }
166
+ if (prResult.exitCode === EXIT_BLOCKED) {
167
+ return {
168
+ exitCode: EXIT_BLOCKED,
169
+ haltReason: prResult.haltReason,
170
+ message: prResult.message,
171
+ iterations: iteration,
172
+ queueCount: queue.length,
173
+ grantId,
174
+ progressPath,
175
+ };
176
+ }
177
+ // CLEAN / MERGED with empty queue → done
178
+ if (queue.length === 0) {
179
+ const message = `directive:finish-loop done after PR #${options.prNumber}: ${prResult.haltReason}`;
180
+ progress("halt", iteration, prResult.haltReason, message, {
181
+ grantId,
182
+ queueCount: 0,
183
+ exitCode: EXIT_OK,
184
+ prNumber: options.prNumber,
185
+ });
186
+ return {
187
+ exitCode: EXIT_OK,
188
+ haltReason: prResult.haltReason,
189
+ message,
190
+ iterations: iteration,
191
+ queueCount: 0,
192
+ grantId,
193
+ progressPath,
194
+ };
195
+ }
196
+ }
197
+ // Agent-owned implement step when queue non-empty
198
+ if (queue.length > 0) {
199
+ const top = queue[0];
200
+ const message = `AGENT_STEP directive:finish-loop iteration=${iteration}: ` +
201
+ `${queue.length} scope item(s) remain (next: ${top?.name ?? "?"}). ` +
202
+ "Implementation is agent-orchestrated: promote/activate story, implement, " +
203
+ "open PR, then re-run `task directive:finish-loop` / `task pr:finish-loop -- <N>`. " +
204
+ "Gates already verified finish-loop grant for edit/push/pr/merge.";
205
+ progress("implement", iteration, "agent-implement", message, {
206
+ grantId,
207
+ queueCount: queue.length,
208
+ exitCode: EXIT_ACTION_REQUIRED,
209
+ extra: { nextPath: top?.path },
210
+ });
211
+ return {
212
+ exitCode: EXIT_ACTION_REQUIRED,
213
+ haltReason: "agent-implement",
214
+ message,
215
+ iterations: iteration,
216
+ queueCount: queue.length,
217
+ grantId,
218
+ progressPath,
219
+ };
220
+ }
221
+ }
222
+ const message = `directive:finish-loop halted: max iterations (${maxIterations}) reached ` +
223
+ `with queueCount=${lastQueueCount}.`;
224
+ progress("halt", maxIterations, "max-iterations", message, {
225
+ grantId,
226
+ queueCount: lastQueueCount,
227
+ exitCode: EXIT_BLOCKED,
228
+ });
229
+ return {
230
+ exitCode: EXIT_BLOCKED,
231
+ haltReason: "max-iterations",
232
+ message,
233
+ iterations: maxIterations,
234
+ queueCount: lastQueueCount,
235
+ grantId,
236
+ progressPath,
237
+ };
238
+ }
239
+ //# sourceMappingURL=directive-finish-loop.js.map
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Finish-loop grant gate (#871): require a live human-origin grant covering
3
+ * edit/push/pr/merge (finish-loop template ops), or DEFT_ALLOW_FINISH_LOOP=1.
4
+ *
5
+ * Agent-authored grants never satisfy. No second mint path.
6
+ */
7
+ import type { AuthzOperation, HumanOriginGrant } from "../authz/types.js";
8
+ import type { FinishLoopGrantGateResult } from "./types.js";
9
+ export type EnvMap = Readonly<Record<string, string | undefined>>;
10
+ /**
11
+ * True when grant.operations covers every finish-loop op (edit, push, pr, merge).
12
+ * A single grant may cover the full set; partial coverage reports missing ops.
13
+ */
14
+ export declare function grantCoversFinishLoopOps(grant: HumanOriginGrant): {
15
+ covered: boolean;
16
+ missing: AuthzOperation[];
17
+ };
18
+ export interface EvaluateFinishLoopGrantInput {
19
+ readonly projectRoot: string;
20
+ readonly grants?: readonly HumanOriginGrant[];
21
+ readonly env?: EnvMap;
22
+ readonly now?: Date;
23
+ /** When set, require grant to cover at least this op (default: full finish-loop set). */
24
+ readonly requireFullSet?: boolean;
25
+ readonly op?: AuthzOperation;
26
+ }
27
+ /**
28
+ * Fail-closed gate for finish-loop / pr:finish-loop entry.
29
+ */
30
+ export declare function evaluateFinishLoopGrant(input: EvaluateFinishLoopGrantInput): FinishLoopGrantGateResult;
31
+ //# sourceMappingURL=grant-gate.d.ts.map
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Finish-loop grant gate (#871): require a live human-origin grant covering
3
+ * edit/push/pr/merge (finish-loop template ops), or DEFT_ALLOW_FINISH_LOOP=1.
4
+ *
5
+ * Agent-authored grants never satisfy. No second mint path.
6
+ */
7
+ import { isHumanOriginGrant, isRejectedOriginKind } from "../authz/origin.js";
8
+ import { listActiveHumanGrants, listGrants, loadAuthzState } from "../authz/store.js";
9
+ import { FINISH_LOOP_OPERATIONS, FINISH_LOOP_TEMPLATE_NAME } from "../authz/templates.js";
10
+ const ENV_BYPASS = "DEFT_ALLOW_FINISH_LOOP";
11
+ function envTruthy(value) {
12
+ if (value === undefined)
13
+ return false;
14
+ const v = value.trim().toLowerCase();
15
+ return v === "1" || v === "true" || v === "yes";
16
+ }
17
+ function grantLive(grant, now) {
18
+ if (!isHumanOriginGrant(grant)) {
19
+ const kind = grant.origin.kind;
20
+ if (isRejectedOriginKind(kind)) {
21
+ return {
22
+ ok: false,
23
+ code: "authz-grant-origin-reject",
24
+ reason: `Directive denied finish-loop: grant ${grant.id} origin.kind=${kind} is ` +
25
+ "agent/self-authored and cannot authorize walk-away ops. " +
26
+ `Human action required: \`deft authz:grant -- --template ${FINISH_LOOP_TEMPLATE_NAME}\`.`,
27
+ };
28
+ }
29
+ return {
30
+ ok: false,
31
+ code: "authz-grant-origin-reject",
32
+ reason: `Directive denied finish-loop: grant ${grant.id} lacks human-origin provenance. ` +
33
+ `Human action required: \`deft authz:grant -- --template ${FINISH_LOOP_TEMPLATE_NAME}\`.`,
34
+ };
35
+ }
36
+ if (grant.semantics.revokedAt !== null) {
37
+ return {
38
+ ok: false,
39
+ code: "authz-grant-revoked",
40
+ reason: `Directive denied finish-loop: grant ${grant.id} was revoked at ${grant.semantics.revokedAt}.`,
41
+ };
42
+ }
43
+ if (grant.semantics.singleUse && grant.semantics.usedAt !== null) {
44
+ return {
45
+ ok: false,
46
+ code: "authz-grant-single-use-spent",
47
+ reason: `Directive denied finish-loop: single-use grant ${grant.id} already spent.`,
48
+ };
49
+ }
50
+ if (grant.semantics.expiresAt !== null) {
51
+ const exp = Date.parse(grant.semantics.expiresAt);
52
+ if (Number.isNaN(exp) || exp <= now.getTime()) {
53
+ return {
54
+ ok: false,
55
+ code: "authz-grant-expired",
56
+ reason: `Directive denied finish-loop: grant ${grant.id} expired at ${grant.semantics.expiresAt}. ` +
57
+ `Human action required: mint a fresh grant via \`deft authz:grant -- --template ${FINISH_LOOP_TEMPLATE_NAME}\`.`,
58
+ };
59
+ }
60
+ }
61
+ return { ok: true, code: "finish-loop-allow", reason: "ok" };
62
+ }
63
+ /**
64
+ * True when grant.operations covers every finish-loop op (edit, push, pr, merge).
65
+ * A single grant may cover the full set; partial coverage reports missing ops.
66
+ */
67
+ export function grantCoversFinishLoopOps(grant) {
68
+ const have = new Set(grant.scope.operations.map((o) => o.toLowerCase()));
69
+ const missing = FINISH_LOOP_OPERATIONS.filter((op) => !have.has(op.toLowerCase()));
70
+ return { covered: missing.length === 0, missing: [...missing] };
71
+ }
72
+ /**
73
+ * Fail-closed gate for finish-loop / pr:finish-loop entry.
74
+ */
75
+ export function evaluateFinishLoopGrant(input) {
76
+ const env = input.env ?? process.env;
77
+ if (envTruthy(env[ENV_BYPASS])) {
78
+ return {
79
+ allowed: true,
80
+ code: "finish-loop-env-bypass",
81
+ reason: `Directive allowed finish-loop via ephemeral env bypass ${ENV_BYPASS}=1.`,
82
+ grantId: null,
83
+ missingOps: [],
84
+ };
85
+ }
86
+ const now = input.now ?? new Date();
87
+ const state = loadAuthzState(input.projectRoot);
88
+ // Prefer active grants; if none, still inspect disk grants so expiry/origin
89
+ // denials surface as grant-expired / origin-reject rather than silent missing.
90
+ let grants;
91
+ if (input.grants !== undefined) {
92
+ grants = input.grants;
93
+ }
94
+ else {
95
+ const active = listActiveHumanGrants(input.projectRoot, state, now);
96
+ grants = active.length > 0 ? active : listGrants(input.projectRoot);
97
+ }
98
+ let lastDeny = null;
99
+ for (const grant of grants) {
100
+ const live = grantLive(grant, now);
101
+ if (!live.ok) {
102
+ lastDeny = {
103
+ allowed: false,
104
+ code: live.code,
105
+ reason: live.reason,
106
+ grantId: grant.id,
107
+ missingOps: [],
108
+ };
109
+ continue;
110
+ }
111
+ if (input.op !== undefined) {
112
+ const have = grant.scope.operations.map((o) => o.toLowerCase());
113
+ if (!have.includes(input.op.toLowerCase())) {
114
+ lastDeny = {
115
+ allowed: false,
116
+ code: "authz-grant-scope-deny",
117
+ reason: `Directive denied finish-loop: grant ${grant.id} ops=[${grant.scope.operations.join(",")}] ` +
118
+ `does not cover '${input.op}'. Human action required: ` +
119
+ `\`deft authz:grant -- --template ${FINISH_LOOP_TEMPLATE_NAME}\`.`,
120
+ grantId: grant.id,
121
+ missingOps: [input.op],
122
+ };
123
+ continue;
124
+ }
125
+ return {
126
+ allowed: true,
127
+ code: "finish-loop-allow",
128
+ reason: `Directive allowed finish-loop op '${input.op}' via human-origin grant ${grant.id}.`,
129
+ grantId: grant.id,
130
+ missingOps: [],
131
+ };
132
+ }
133
+ const cover = grantCoversFinishLoopOps(grant);
134
+ if (!cover.covered && input.requireFullSet !== false) {
135
+ lastDeny = {
136
+ allowed: false,
137
+ code: "authz-grant-scope-deny",
138
+ reason: `Directive denied finish-loop: grant ${grant.id} missing ops ` +
139
+ `[${cover.missing.join(",")}]. Finish-loop requires ` +
140
+ `[${FINISH_LOOP_OPERATIONS.join(",")}]. Human action required: ` +
141
+ `\`deft authz:grant -- --template ${FINISH_LOOP_TEMPLATE_NAME}\`.`,
142
+ grantId: grant.id,
143
+ missingOps: cover.missing,
144
+ };
145
+ continue;
146
+ }
147
+ return {
148
+ allowed: true,
149
+ code: "finish-loop-allow",
150
+ reason: `Directive allowed finish-loop via human-origin grant ${grant.id}.`,
151
+ grantId: grant.id,
152
+ missingOps: [],
153
+ };
154
+ }
155
+ if (lastDeny !== null)
156
+ return lastDeny;
157
+ return {
158
+ allowed: false,
159
+ code: "authz-grant-missing",
160
+ reason: "Directive denied finish-loop: no human-origin grant covers edit/push/pr/merge " +
161
+ `and ${ENV_BYPASS} is not set. Human action required: ` +
162
+ `\`deft authz:grant -- --template ${FINISH_LOOP_TEMPLATE_NAME}\` ` +
163
+ `(or set ${ENV_BYPASS}=1 for a single-shell ephemeral bypass). ` +
164
+ "Release-class verbs are NOT authorized by this template.",
165
+ grantId: null,
166
+ missingOps: [...FINISH_LOOP_OPERATIONS],
167
+ };
168
+ }
169
+ //# sourceMappingURL=grant-gate.js.map
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Walk-away finish-loop product (#871 Wave 5 / #2948).
3
+ */
4
+ export * from "./directive-finish-loop.js";
5
+ export * from "./grant-gate.js";
6
+ export * from "./main.js";
7
+ export * from "./pr-finish-loop.js";
8
+ export * from "./progress.js";
9
+ export * from "./queue.js";
10
+ export * from "./types.js";
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Walk-away finish-loop product (#871 Wave 5 / #2948).
3
+ */
4
+ export * from "./directive-finish-loop.js";
5
+ export * from "./grant-gate.js";
6
+ export * from "./main.js";
7
+ export * from "./pr-finish-loop.js";
8
+ export * from "./progress.js";
9
+ export * from "./queue.js";
10
+ export * from "./types.js";
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,35 @@
1
+ /**
2
+ * CLI entry for pr:finish-loop and directive:finish-loop (#871).
3
+ */
4
+ export interface ParsedPrFinishArgs {
5
+ readonly prNumber: number | null;
6
+ readonly repo: string | null;
7
+ readonly projectRoot: string;
8
+ readonly maxWaitMinutes: number | null;
9
+ readonly pollSeconds: number | null;
10
+ readonly oneShot: boolean;
11
+ readonly merge: boolean;
12
+ readonly emitJson: boolean;
13
+ readonly help: boolean;
14
+ readonly error?: string;
15
+ }
16
+ export interface ParsedDirectiveFinishArgs {
17
+ readonly prNumber: number | null;
18
+ readonly repo: string | null;
19
+ readonly projectRoot: string;
20
+ readonly maxIterations: number | null;
21
+ readonly maxWaitMinutes: number | null;
22
+ readonly pollSeconds: number | null;
23
+ readonly oneShot: boolean;
24
+ readonly merge: boolean;
25
+ readonly emitJson: boolean;
26
+ readonly help: boolean;
27
+ readonly error?: string;
28
+ }
29
+ export declare function parsePrFinishArgs(argv: readonly string[]): ParsedPrFinishArgs;
30
+ export declare function parseDirectiveFinishArgs(argv: readonly string[]): ParsedDirectiveFinishArgs;
31
+ export declare function formatPrFinishHelp(): string;
32
+ export declare function formatDirectiveFinishHelp(): string;
33
+ export declare function cmdPrFinishLoop(argv?: readonly string[]): number;
34
+ export declare function cmdDirectiveFinishLoop(argv?: readonly string[]): number;
35
+ //# sourceMappingURL=main.d.ts.map