@loopover/miner 0.1.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 (218) hide show
  1. package/DEPLOYMENT.md +156 -0
  2. package/Dockerfile +38 -0
  3. package/README.md +269 -0
  4. package/bin/gittensory-miner-mcp.d.ts +64 -0
  5. package/bin/gittensory-miner-mcp.js +279 -0
  6. package/bin/gittensory-miner.js +196 -0
  7. package/docs/coding-agent-driver.md +140 -0
  8. package/docs/config-precedence.md +96 -0
  9. package/docs/cross-repo-discovery-phase1.md +55 -0
  10. package/docs/discovery-index-contract.md +60 -0
  11. package/docs/discovery-plane-operator-guide.md +101 -0
  12. package/docs/env-reference.md +30 -0
  13. package/docs/fleet-run-manifest.md +50 -0
  14. package/docs/miner-goal-spec.md +76 -0
  15. package/docs/miner-selfimprove-calibration.md +105 -0
  16. package/docs/observability.md +60 -0
  17. package/docs/operations-runbook.md +209 -0
  18. package/docs/repo-agnostic-capability-audit.md +129 -0
  19. package/docs/unattended-scheduling.md +107 -0
  20. package/expected-engine.version +1 -0
  21. package/lib/ams-policy.d.ts +20 -0
  22. package/lib/ams-policy.js +68 -0
  23. package/lib/attempt-cli.d.ts +99 -0
  24. package/lib/attempt-cli.js +523 -0
  25. package/lib/attempt-input-builder.d.ts +24 -0
  26. package/lib/attempt-input-builder.js +91 -0
  27. package/lib/attempt-log.d.ts +37 -0
  28. package/lib/attempt-log.js +177 -0
  29. package/lib/attempt-runner.d.ts +59 -0
  30. package/lib/attempt-runner.js +164 -0
  31. package/lib/attempt-worktree.d.ts +31 -0
  32. package/lib/attempt-worktree.js +94 -0
  33. package/lib/calibration-cli.d.ts +1 -0
  34. package/lib/calibration-cli.js +92 -0
  35. package/lib/calibration-run.d.ts +161 -0
  36. package/lib/calibration-run.js +232 -0
  37. package/lib/calibration-types.d.ts +46 -0
  38. package/lib/calibration-types.js +74 -0
  39. package/lib/calibration.d.ts +24 -0
  40. package/lib/calibration.js +99 -0
  41. package/lib/ci-poller.d.ts +33 -0
  42. package/lib/ci-poller.js +234 -0
  43. package/lib/claim-adjudication.d.ts +21 -0
  44. package/lib/claim-adjudication.js +36 -0
  45. package/lib/claim-conflict-resolver.d.ts +29 -0
  46. package/lib/claim-conflict-resolver.js +101 -0
  47. package/lib/claim-ledger-cli.d.ts +59 -0
  48. package/lib/claim-ledger-cli.js +320 -0
  49. package/lib/claim-ledger-expiry.d.ts +20 -0
  50. package/lib/claim-ledger-expiry.js +41 -0
  51. package/lib/claim-ledger.d.ts +63 -0
  52. package/lib/claim-ledger.js +303 -0
  53. package/lib/cli-error.d.ts +3 -0
  54. package/lib/cli-error.js +27 -0
  55. package/lib/cli.d.ts +3 -0
  56. package/lib/cli.js +72 -0
  57. package/lib/coding-agent-construction.d.ts +17 -0
  58. package/lib/coding-agent-construction.js +103 -0
  59. package/lib/coding-agent-house-rules.d.ts +24 -0
  60. package/lib/coding-agent-house-rules.js +64 -0
  61. package/lib/coding-task-spec.d.ts +44 -0
  62. package/lib/coding-task-spec.js +183 -0
  63. package/lib/config-precedence.d.ts +25 -0
  64. package/lib/config-precedence.js +38 -0
  65. package/lib/deny-check.d.ts +11 -0
  66. package/lib/deny-check.js +76 -0
  67. package/lib/deny-hook-synthesis.d.ts +93 -0
  68. package/lib/deny-hook-synthesis.js +399 -0
  69. package/lib/deny-hooks.d.ts +28 -0
  70. package/lib/deny-hooks.js +155 -0
  71. package/lib/deployment-docs-audit.d.ts +41 -0
  72. package/lib/deployment-docs-audit.js +115 -0
  73. package/lib/discover-cli.d.ts +94 -0
  74. package/lib/discover-cli.js +284 -0
  75. package/lib/discovery-throttle.d.ts +8 -0
  76. package/lib/discovery-throttle.js +33 -0
  77. package/lib/env-file-indirection.d.ts +4 -0
  78. package/lib/env-file-indirection.js +45 -0
  79. package/lib/event-ledger-cli.d.ts +82 -0
  80. package/lib/event-ledger-cli.js +266 -0
  81. package/lib/event-ledger.d.ts +37 -0
  82. package/lib/event-ledger.js +210 -0
  83. package/lib/execute-local-write.d.ts +14 -0
  84. package/lib/execute-local-write.js +50 -0
  85. package/lib/feasibility-cli.d.ts +25 -0
  86. package/lib/feasibility-cli.js +80 -0
  87. package/lib/forge-config.d.ts +17 -0
  88. package/lib/forge-config.js +37 -0
  89. package/lib/governor-action-mode.d.ts +26 -0
  90. package/lib/governor-action-mode.js +47 -0
  91. package/lib/governor-chokepoint-persisted.d.ts +18 -0
  92. package/lib/governor-chokepoint-persisted.js +46 -0
  93. package/lib/governor-chokepoint.d.ts +14 -0
  94. package/lib/governor-chokepoint.js +52 -0
  95. package/lib/governor-kill-switch.d.ts +26 -0
  96. package/lib/governor-kill-switch.js +47 -0
  97. package/lib/governor-ledger-cli.d.ts +32 -0
  98. package/lib/governor-ledger-cli.js +155 -0
  99. package/lib/governor-ledger.d.ts +46 -0
  100. package/lib/governor-ledger.js +203 -0
  101. package/lib/governor-open-pr.d.ts +21 -0
  102. package/lib/governor-open-pr.js +27 -0
  103. package/lib/governor-pause-cli.d.ts +23 -0
  104. package/lib/governor-pause-cli.js +171 -0
  105. package/lib/governor-run-halt.d.ts +37 -0
  106. package/lib/governor-run-halt.js +58 -0
  107. package/lib/governor-state.d.ts +63 -0
  108. package/lib/governor-state.js +363 -0
  109. package/lib/governor-write-rate-limit.d.ts +30 -0
  110. package/lib/governor-write-rate-limit.js +64 -0
  111. package/lib/harness-submission-trigger.d.ts +69 -0
  112. package/lib/harness-submission-trigger.js +138 -0
  113. package/lib/http-retry.d.ts +12 -0
  114. package/lib/http-retry.js +52 -0
  115. package/lib/laptop-init.d.ts +53 -0
  116. package/lib/laptop-init.js +332 -0
  117. package/lib/live-issue-snapshot.d.ts +16 -0
  118. package/lib/live-issue-snapshot.js +117 -0
  119. package/lib/local-store.d.ts +18 -0
  120. package/lib/local-store.js +62 -0
  121. package/lib/logger.d.ts +58 -0
  122. package/lib/logger.js +167 -0
  123. package/lib/loop-cli.d.ts +66 -0
  124. package/lib/loop-cli.js +540 -0
  125. package/lib/loop-closure.d.ts +35 -0
  126. package/lib/loop-closure.js +66 -0
  127. package/lib/loop-reentry.d.ts +51 -0
  128. package/lib/loop-reentry.js +124 -0
  129. package/lib/manage-poll.d.ts +79 -0
  130. package/lib/manage-poll.js +254 -0
  131. package/lib/manage-status.d.ts +70 -0
  132. package/lib/manage-status.js +241 -0
  133. package/lib/metrics-cli.d.ts +9 -0
  134. package/lib/metrics-cli.js +50 -0
  135. package/lib/migrate-cli.d.ts +24 -0
  136. package/lib/migrate-cli.js +111 -0
  137. package/lib/miner-goal-spec.d.ts +12 -0
  138. package/lib/miner-goal-spec.js +67 -0
  139. package/lib/opportunity-fanout.d.ts +86 -0
  140. package/lib/opportunity-fanout.js +597 -0
  141. package/lib/opportunity-ranker.d.ts +36 -0
  142. package/lib/opportunity-ranker.js +118 -0
  143. package/lib/orb-export.d.ts +54 -0
  144. package/lib/orb-export.js +206 -0
  145. package/lib/plan-store-cli.d.ts +37 -0
  146. package/lib/plan-store-cli.js +151 -0
  147. package/lib/plan-store.d.ts +51 -0
  148. package/lib/plan-store.js +231 -0
  149. package/lib/policy-doc-cache.d.ts +25 -0
  150. package/lib/policy-doc-cache.js +79 -0
  151. package/lib/policy-verdict-cache.d.ts +35 -0
  152. package/lib/policy-verdict-cache.js +106 -0
  153. package/lib/portfolio-dashboard.d.ts +30 -0
  154. package/lib/portfolio-dashboard.js +105 -0
  155. package/lib/portfolio-discovery.d.ts +29 -0
  156. package/lib/portfolio-discovery.js +100 -0
  157. package/lib/portfolio-queue-cli.d.ts +87 -0
  158. package/lib/portfolio-queue-cli.js +509 -0
  159. package/lib/portfolio-queue-expiry.d.ts +20 -0
  160. package/lib/portfolio-queue-expiry.js +51 -0
  161. package/lib/portfolio-queue-manager.d.ts +50 -0
  162. package/lib/portfolio-queue-manager.js +134 -0
  163. package/lib/portfolio-queue.d.ts +62 -0
  164. package/lib/portfolio-queue.js +351 -0
  165. package/lib/pr-disposition-poller.d.ts +26 -0
  166. package/lib/pr-disposition-poller.js +163 -0
  167. package/lib/pr-number-parse.d.ts +4 -0
  168. package/lib/pr-number-parse.js +23 -0
  169. package/lib/pr-outcome.d.ts +41 -0
  170. package/lib/pr-outcome.js +90 -0
  171. package/lib/prediction-ledger.d.ts +47 -0
  172. package/lib/prediction-ledger.js +222 -0
  173. package/lib/pretooluse-hook.d.ts +31 -0
  174. package/lib/pretooluse-hook.js +93 -0
  175. package/lib/process-lifecycle.d.ts +31 -0
  176. package/lib/process-lifecycle.js +106 -0
  177. package/lib/purge-cli.d.ts +44 -0
  178. package/lib/purge-cli.js +198 -0
  179. package/lib/rejection-signal.d.ts +6 -0
  180. package/lib/rejection-signal.js +101 -0
  181. package/lib/rejection-state-machine.d.ts +37 -0
  182. package/lib/rejection-state-machine.js +81 -0
  183. package/lib/rejection-templates.d.ts +12 -0
  184. package/lib/rejection-templates.js +71 -0
  185. package/lib/replay-objective-anchor.d.ts +79 -0
  186. package/lib/replay-objective-anchor.js +179 -0
  187. package/lib/replay-snapshot.d.ts +41 -0
  188. package/lib/replay-snapshot.js +288 -0
  189. package/lib/replay-task-generation.d.ts +126 -0
  190. package/lib/replay-task-generation.js +223 -0
  191. package/lib/repo-clone.d.ts +19 -0
  192. package/lib/repo-clone.js +102 -0
  193. package/lib/run-state-cli.d.ts +27 -0
  194. package/lib/run-state-cli.js +154 -0
  195. package/lib/run-state.d.ts +37 -0
  196. package/lib/run-state.js +150 -0
  197. package/lib/schema-version.d.ts +17 -0
  198. package/lib/schema-version.js +71 -0
  199. package/lib/self-review-context.d.ts +28 -0
  200. package/lib/self-review-context.js +339 -0
  201. package/lib/slop-assessment.d.ts +3 -0
  202. package/lib/slop-assessment.js +20 -0
  203. package/lib/stack-detection.d.ts +41 -0
  204. package/lib/stack-detection.js +248 -0
  205. package/lib/status.d.ts +64 -0
  206. package/lib/status.js +441 -0
  207. package/lib/store-maintenance.d.ts +31 -0
  208. package/lib/store-maintenance.js +183 -0
  209. package/lib/submission-freshness-check.d.ts +32 -0
  210. package/lib/submission-freshness-check.js +93 -0
  211. package/lib/update-check.d.ts +36 -0
  212. package/lib/update-check.js +161 -0
  213. package/lib/version.d.ts +3 -0
  214. package/lib/version.js +10 -0
  215. package/lib/worktree-allocator.d.ts +39 -0
  216. package/lib/worktree-allocator.js +262 -0
  217. package/package.json +50 -0
  218. package/schema/miner-goal-spec.schema.json +111 -0
@@ -0,0 +1,44 @@
1
+ import type { AcceptanceCriteria, FeasibilityGateResult, FeasibilityVerdict, IssueRecord, PullRequestRecord } from "@loopover/engine";
2
+
3
+ export type CodingTaskIssue = { number: number; title: string; body?: string | null | undefined; labels?: string[] | undefined };
4
+
5
+ export type CodingTaskClaimLedger = {
6
+ listClaims(filter: { repoFullName: string; status: string }): Array<{ issueNumber: number }>;
7
+ };
8
+
9
+ export type CodingTaskContext = { issues: IssueRecord[]; pullRequests: PullRequestRecord[] };
10
+
11
+ export function buildCodingTaskFeasibility(
12
+ repoFullName: string,
13
+ issue: CodingTaskIssue,
14
+ context: CodingTaskContext,
15
+ claimLedger: CodingTaskClaimLedger,
16
+ ): FeasibilityGateResult;
17
+
18
+ export function buildCodingTaskAcceptanceCriteria(issue: CodingTaskIssue, feasibility: FeasibilityGateResult): AcceptanceCriteria;
19
+
20
+ export function writeAcceptanceCriteriaFile(workingDirectory: string, acceptanceCriteria: AcceptanceCriteria): { written: boolean; path: string | null };
21
+
22
+ export type CodingTaskSpecInput = {
23
+ repoFullName: string;
24
+ issue: CodingTaskIssue;
25
+ context: CodingTaskContext;
26
+ claimLedger: CodingTaskClaimLedger;
27
+ workingDirectory: string;
28
+ };
29
+
30
+ export type CodingTaskSpecResult =
31
+ | { ready: false; verdict: FeasibilityVerdict; feasibility: FeasibilityGateResult }
32
+ | {
33
+ ready: true;
34
+ verdict: FeasibilityVerdict;
35
+ feasibility: FeasibilityGateResult;
36
+ acceptanceCriteriaPath: string;
37
+ instructions: string;
38
+ title: string;
39
+ body: string | undefined;
40
+ labels: string[] | undefined;
41
+ linkedIssues: number[];
42
+ };
43
+
44
+ export function buildCodingTaskSpec(input: CodingTaskSpecInput): CodingTaskSpecResult;
@@ -0,0 +1,183 @@
1
+ import { closeSync, constants as fsConstants, openSync, realpathSync, writeFileSync } from "node:fs";
2
+ import { isAbsolute, join, relative } from "node:path";
3
+ import {
4
+ ACCEPTANCE_CRITERIA_FILENAME,
5
+ buildAcceptanceCriteria,
6
+ buildCollisionReport,
7
+ buildFeasibilityVerdict,
8
+ buildPromptPacket,
9
+ feasibilityInputFromPreStartCheck,
10
+ serializeAcceptanceCriteria,
11
+ shouldWriteAcceptanceCriteria,
12
+ } from "@loopover/engine";
13
+
14
+ // Coding-task-spec builder (#5132, Wave 3.5 follow-up). The second gap discovered alongside #5132's CLI
15
+ // wiring: `IterateLoopInput.title`/`instructions`/`acceptanceCriteriaPath` had no builder anywhere in this
16
+ // package. `packages/gittensory-engine/src/miner/acceptance-criteria.ts` already composes a PromptPacket +
17
+ // FeasibilityGateResult into an immutable AcceptanceCriteria document (and deliberately does NOT write it --
18
+ // "actually writing it into the attempt's worktree is the worktree primitive's job", per its own header) --
19
+ // this module is that caller: derives the four inputs from a real target issue + the already-fetched
20
+ // SelfReviewContext (#5145), then writes the file for real.
21
+ //
22
+ // issueStatus is intentionally left undefined when computing feasibility: buildIssueQualityReport (the only
23
+ // thing that could supply it) lives only in root src/signals/engine.ts and has never been extracted into
24
+ // @loopover/engine (same gap #5145's own header documents for `issueQuality`). This is not a
25
+ // fabrication -- feasibilityInputFromPreStartCheck's OWN documented default for a missing
26
+ // issueQualityStatus/lifecycle is "ready", the same honest-default precedent already established.
27
+
28
+ function buildTaskBrief(issue) {
29
+ const body = (issue.body ?? "").trim();
30
+ return body ? `${issue.title}\n\n${body}` : issue.title;
31
+ }
32
+
33
+ function buildConstraints(issue) {
34
+ if (!Array.isArray(issue.labels) || issue.labels.length === 0) return "";
35
+ return `Labels on this issue: ${issue.labels.join(", ")}.`;
36
+ }
37
+
38
+ function buildFeasibilityNotes(feasibility) {
39
+ return [feasibility.summary, ...feasibility.avoidReasons, ...feasibility.raiseReasons].join("\n");
40
+ }
41
+
42
+ // Only ever resolves to "claimed"/"unclaimed": the claim ledger's own ClaimStatus vocabulary
43
+ // ("active"|"released"|"expired") has no "solved" concept for FeasibilityClaimStatus's "solved" value to
44
+ // map from -- that would need real evidence a PR already resolved the issue (e.g. a merged, linked PR),
45
+ // which this function doesn't have access to. Not fabricated; genuinely undetectable from claim data alone.
46
+ function resolveClaimStatus(claimLedger, repoFullName, issueNumber) {
47
+ const claims = claimLedger.listClaims({ repoFullName, status: "active" });
48
+ return claims.some((claim) => claim.issueNumber === issueNumber) ? "claimed" : "unclaimed";
49
+ }
50
+
51
+ // The target issue's own raw cluster risk from buildCollisionReport (newly exported from
52
+ // @loopover/engine's public barrel) -- "none" when the issue isn't part of any cluster at all.
53
+ // DELIBERATELY does NOT apply #5145's ">= 2 pull_request items" threshold: that gate exists specifically to
54
+ // stop inDuplicateCluster (self-review, "does MY OWN just-created submission look redundant") from firing on
55
+ // the ordinary case of one existing PR already legitimately closing the issue. Feasibility asks a different
56
+ // question -- "should I even START working on this issue" -- where an issue already having ANY open PR
57
+ // against it (buildCollisionReport's pairwise "shared linked issue" rule, which fires at "high" for exactly
58
+ // one PR) is a meaningful, real caution signal, not a false positive to filter out.
59
+ function resolveDuplicateClusterRisk(repoFullName, issues, pullRequests, issueNumber) {
60
+ const report = buildCollisionReport(repoFullName, issues, pullRequests);
61
+ const cluster = report.clusters.find((entry) => entry.items.some((item) => item.type === "issue" && item.number === issueNumber));
62
+ return cluster ? cluster.risk : "none";
63
+ }
64
+
65
+ /**
66
+ * Compute the feasibility verdict for one target issue, from real signals: whether the issue is present in
67
+ * the fetched context, its real claim status (the claim ledger), and its real duplicate-cluster risk
68
+ * (buildCollisionReport over the fetched issues/pullRequests). issueStatus is left to its documented
69
+ * "ready" default -- see this file's header for why that's honest, not fabricated.
70
+ *
71
+ * @param {string} repoFullName
72
+ * @param {{ number: number }} issue
73
+ * @param {{ issues: Array<{ number: number }>, pullRequests: unknown[] }} context
74
+ * @param {{ listClaims: (filter: { repoFullName: string, status: string }) => Array<{ issueNumber: number }> }} claimLedger
75
+ * @returns {import("@loopover/engine").FeasibilityGateResult}
76
+ */
77
+ export function buildCodingTaskFeasibility(repoFullName, issue, context, claimLedger) {
78
+ const found = context.issues.some((candidate) => candidate.number === issue.number);
79
+ const claimStatus = resolveClaimStatus(claimLedger, repoFullName, issue.number);
80
+ const duplicateClusterRisk = resolveDuplicateClusterRisk(repoFullName, context.issues, context.pullRequests, issue.number);
81
+ const feasibilityInput = feasibilityInputFromPreStartCheck({ found, claimStatus, duplicateClusterRisk });
82
+ return buildFeasibilityVerdict(feasibilityInput);
83
+ }
84
+
85
+ /**
86
+ * Compose the immutable AcceptanceCriteria document for one target issue + its feasibility verdict.
87
+ *
88
+ * @param {{ title: string, body?: string | null, labels?: string[] }} issue
89
+ * @param {import("@loopover/engine").FeasibilityGateResult} feasibility
90
+ * @returns {import("@loopover/engine").AcceptanceCriteria}
91
+ */
92
+ export function buildCodingTaskAcceptanceCriteria(issue, feasibility) {
93
+ const promptPacket = buildPromptPacket({
94
+ taskBrief: buildTaskBrief(issue),
95
+ constraints: buildConstraints(issue),
96
+ feasibilityNotes: buildFeasibilityNotes(feasibility),
97
+ retrievalContext: "",
98
+ });
99
+ return buildAcceptanceCriteria({ promptPacket, feasibility });
100
+ }
101
+
102
+ /**
103
+ * Write the acceptance-criteria document into the prepared worktree -- only when its own verdict authorizes
104
+ * it (shouldWriteAcceptanceCriteria: verdict === "go"). A raise/avoid verdict writes nothing; the caller is
105
+ * expected to abandon the attempt rather than start it, per acceptance-criteria.ts's own documented design.
106
+ *
107
+ * @param {string} workingDirectory
108
+ * @param {import("@loopover/engine").AcceptanceCriteria} acceptanceCriteria
109
+ * @returns {{ written: boolean, path: string | null }}
110
+ */
111
+ function assertContainedPath(root, path) {
112
+ const relativePath = relative(root, path);
113
+ if (relativePath === "" || (!relativePath.startsWith("..") && !isAbsolute(relativePath))) return;
114
+ throw new Error(`Refusing to write acceptance criteria outside the worktree: ${path}`);
115
+ }
116
+
117
+ export function writeAcceptanceCriteriaFile(workingDirectory, acceptanceCriteria) {
118
+ if (!shouldWriteAcceptanceCriteria(acceptanceCriteria.verdict)) return { written: false, path: null };
119
+ const root = realpathSync(workingDirectory);
120
+ const path = join(root, ACCEPTANCE_CRITERIA_FILENAME);
121
+ assertContainedPath(root, path);
122
+
123
+ let fd;
124
+ try {
125
+ fd = openSync(path, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL | fsConstants.O_NOFOLLOW, 0o600);
126
+ writeFileSync(fd, serializeAcceptanceCriteria(acceptanceCriteria), "utf8");
127
+ } finally {
128
+ if (fd !== undefined) closeSync(fd);
129
+ }
130
+
131
+ return { written: true, path };
132
+ }
133
+
134
+ /**
135
+ * The coding-agent driver's own prompt text (agent-sdk-driver.ts's header: "forwarded verbatim as the
136
+ * prompt -- the acceptance-criteria document already lives inside the worktree", so this points to it
137
+ * rather than repeating its content).
138
+ */
139
+ function buildInstructions(issue, acceptanceCriteriaPath) {
140
+ return [
141
+ `Resolve the following GitHub issue in this repository: #${issue.number} -- ${issue.title}`,
142
+ "",
143
+ (issue.body ?? "").trim(),
144
+ "",
145
+ `A structured acceptance-criteria document describing what "done" means for this attempt is at ${acceptanceCriteriaPath} -- read it and ensure your change satisfies every criterion before finishing.`,
146
+ ].join("\n");
147
+ }
148
+
149
+ /**
150
+ * Full composition: feasibility -> acceptance criteria -> (if authorized) write the file -> instructions.
151
+ * Returns `ready: false` (with the computed feasibility verdict, for the caller to report) when the
152
+ * verdict is `raise`/`avoid` -- the caller should abandon the attempt rather than proceed with no real
153
+ * acceptance-criteria file on disk.
154
+ *
155
+ * @param {{
156
+ * repoFullName: string, issue: { number: number, title: string, body?: string | null, labels?: string[] },
157
+ * context: { issues: Array<{ number: number }>, pullRequests: unknown[] },
158
+ * claimLedger: { listClaims: (filter: { repoFullName: string, status: string }) => Array<{ issueNumber: number }> },
159
+ * workingDirectory: string,
160
+ * }} input
161
+ * @returns {import("./coding-task-spec.js").CodingTaskSpecResult}
162
+ */
163
+ export function buildCodingTaskSpec(input) {
164
+ const feasibility = buildCodingTaskFeasibility(input.repoFullName, input.issue, input.context, input.claimLedger);
165
+ const acceptanceCriteria = buildCodingTaskAcceptanceCriteria(input.issue, feasibility);
166
+ const writeResult = writeAcceptanceCriteriaFile(input.workingDirectory, acceptanceCriteria);
167
+
168
+ if (!writeResult.written) {
169
+ return { ready: false, verdict: feasibility.verdict, feasibility };
170
+ }
171
+
172
+ return {
173
+ ready: true,
174
+ verdict: feasibility.verdict,
175
+ feasibility,
176
+ acceptanceCriteriaPath: writeResult.path,
177
+ instructions: buildInstructions(input.issue, writeResult.path),
178
+ title: input.issue.title,
179
+ body: input.issue.body ?? undefined,
180
+ labels: input.issue.labels,
181
+ linkedIssues: [input.issue.number],
182
+ };
183
+ }
@@ -0,0 +1,25 @@
1
+ import type { MinerActionMode, MinerKillSwitchScope } from "@loopover/engine";
2
+ import type { CodingAgentExecutionMode } from "@loopover/engine";
3
+ import type { ForgeConfig } from "./forge-config.js";
4
+
5
+ export function resolveDocumentedKillSwitchScope(
6
+ env: Record<string, string | undefined>,
7
+ repoPaused: boolean,
8
+ ): MinerKillSwitchScope;
9
+
10
+ export function resolveDocumentedGovernorActionMode(input: {
11
+ killSwitchScope: MinerKillSwitchScope;
12
+ repoLiveModeOptIn?: unknown;
13
+ globalLiveModeOptIn: boolean;
14
+ }): MinerActionMode;
15
+
16
+ export function resolveDocumentedCodingAgentMode(
17
+ env: Record<string, string | undefined>,
18
+ cliLiveFlag: boolean,
19
+ ): CodingAgentExecutionMode;
20
+
21
+ export function resolveDocumentedDiscoverTokenEnvVar(input?: {
22
+ cliTokenEnv?: string | null;
23
+ optionsTokenEnv?: string | null;
24
+ forge?: Partial<ForgeConfig>;
25
+ }): string;
@@ -0,0 +1,38 @@
1
+ /** Documented config-resolution helpers for precedence tests (#5198). Thin wrappers over existing resolvers —
2
+ * no new precedence rules. See `docs/config-precedence.md`. */
3
+
4
+ import {
5
+ isGlobalMinerCodingAgentPause,
6
+ resolveCodingAgentExecutionMode,
7
+ resolveMinerActionMode,
8
+ resolveMinerKillSwitch,
9
+ } from "@loopover/engine";
10
+ import { resolveForgeConfig } from "./forge-config.js";
11
+
12
+ /** Kill-switch scope from operator env + parsed goal-spec `killSwitch.paused`. */
13
+ export function resolveDocumentedKillSwitchScope(env, repoPaused) {
14
+ const global = /^(1|true|yes|on)$/i.test(env.GITTENSORY_MINER_KILL_SWITCH ?? "");
15
+ return resolveMinerKillSwitch({ global, repoPaused });
16
+ }
17
+
18
+ /** Governor action mode from kill-switch scope + env/yml live opt-ins. */
19
+ export function resolveDocumentedGovernorActionMode(input) {
20
+ return resolveMinerActionMode(input);
21
+ }
22
+
23
+ /** Coding-agent mode from operator env pause + CLI `--live` (via agentDryRun). */
24
+ export function resolveDocumentedCodingAgentMode(env, cliLiveFlag) {
25
+ return resolveCodingAgentExecutionMode({
26
+ globalPaused: isGlobalMinerCodingAgentPause(env),
27
+ agentDryRun: cliLiveFlag ? false : true,
28
+ });
29
+ }
30
+
31
+ /** Discover credential env var name: CLI > programmatic > forge default. Mirrors discover-cli.js. */
32
+ export function resolveDocumentedDiscoverTokenEnvVar(input = {}) {
33
+ const parsedTokenEnv = input.cliTokenEnv ?? null;
34
+ const optionsTokenEnv = input.optionsTokenEnv ?? null;
35
+ if (parsedTokenEnv !== null) return parsedTokenEnv;
36
+ if (optionsTokenEnv !== null) return optionsTokenEnv;
37
+ return resolveForgeConfig(input.forge).tokenEnvVar;
38
+ }
@@ -0,0 +1,11 @@
1
+ export type ParsedDenyCheckArgs =
2
+ | {
3
+ tool: string;
4
+ input: Record<string, unknown>;
5
+ json: boolean;
6
+ }
7
+ | { error: string };
8
+
9
+ export function parseDenyCheckArgs(args: string[]): ParsedDenyCheckArgs;
10
+
11
+ export function runDenyCheck(args: string[]): number;
@@ -0,0 +1,76 @@
1
+ import { evaluateDenyHooks } from "./deny-hooks.js";
2
+ import { argsWantJson, reportCliFailure } from "./cli-error.js";
3
+
4
+ const DENY_CHECK_USAGE =
5
+ "Usage: gittensory-miner hooks check --tool <name> --input <json> [--json]";
6
+
7
+ function parseToolInput(raw) {
8
+ if (raw === undefined) {
9
+ return { error: "Missing value for --input." };
10
+ }
11
+ try {
12
+ const parsed = JSON.parse(raw);
13
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
14
+ return { error: "Tool input must be a JSON object." };
15
+ }
16
+ return { value: parsed };
17
+ } catch {
18
+ return { error: "Tool input must be valid JSON." };
19
+ }
20
+ }
21
+
22
+ export function parseDenyCheckArgs(args) {
23
+ const options = {
24
+ json: false,
25
+ tool: undefined,
26
+ input: undefined,
27
+ };
28
+
29
+ for (let index = 0; index < args.length; index += 1) {
30
+ const token = args[index];
31
+ if (token === "--json") {
32
+ options.json = true;
33
+ continue;
34
+ }
35
+ if (token === "--tool" || token === "--name") {
36
+ const tool = args[++index];
37
+ if (!tool) return { error: "Missing value for --tool." };
38
+ options.tool = tool;
39
+ continue;
40
+ }
41
+ if (token === "--input") {
42
+ const parsed = parseToolInput(args[++index]);
43
+ if ("error" in parsed) return { error: parsed.error };
44
+ options.input = parsed.value;
45
+ continue;
46
+ }
47
+ if (token.startsWith("-")) {
48
+ return { error: `Unknown option: ${token}` };
49
+ }
50
+ return { error: DENY_CHECK_USAGE };
51
+ }
52
+
53
+ if (!options.tool || !options.input) {
54
+ return { error: DENY_CHECK_USAGE };
55
+ }
56
+
57
+ return options;
58
+ }
59
+
60
+ export function runDenyCheck(args) {
61
+ const parsed = parseDenyCheckArgs(args);
62
+ if ("error" in parsed) {
63
+ return reportCliFailure(argsWantJson(args), parsed.error);
64
+ }
65
+
66
+ const verdict = evaluateDenyHooks({ name: parsed.tool, input: parsed.input });
67
+ if (parsed.json) {
68
+ console.log(JSON.stringify(verdict));
69
+ } else if (!verdict.allowed) {
70
+ console.error(verdict.blockedBy?.reason ?? "Blocked by deny hook.");
71
+ } else {
72
+ console.log("allowed");
73
+ }
74
+
75
+ return verdict.allowed ? 0 : 1;
76
+ }
@@ -0,0 +1,93 @@
1
+ import type { DenyRule } from "./deny-hooks.js";
2
+
3
+ export type BlockerHistoryRecord = {
4
+ repoFullName?: string | null;
5
+ blockerCodes: string[];
6
+ changedPaths?: string[];
7
+ guardrailMatches?: string[];
8
+ pullNumber?: number | null;
9
+ recordedAt?: string | null;
10
+ };
11
+
12
+ export type DenyRuleProposalStatus = "proposed" | "approved" | "rejected";
13
+
14
+ export type DenyRuleProposalAudit = {
15
+ kind: string;
16
+ path?: string;
17
+ pathPattern?: string;
18
+ occurrenceCount?: number;
19
+ blockerCodes?: string[];
20
+ synthesizedAt: string;
21
+ };
22
+
23
+ export type DenyRuleProposal = {
24
+ id: string;
25
+ status: DenyRuleProposalStatus;
26
+ rule: DenyRule;
27
+ audit: DenyRuleProposalAudit;
28
+ };
29
+
30
+ export type SynthesisConfig = {
31
+ minPathOccurrences?: number;
32
+ maxProposals?: number;
33
+ };
34
+
35
+ export const DEFAULT_SYNTHESIS_CONFIG: Readonly<Required<SynthesisConfig>>;
36
+
37
+ export function normalizeBlockerHistoryRecord(record: unknown): BlockerHistoryRecord | null;
38
+
39
+ export function normalizeBlockerHistory(records: unknown): BlockerHistoryRecord[];
40
+
41
+ export function canonicalizeChangedPath(path: unknown): string | null;
42
+
43
+ export function changedPathToDenyGlob(path: string): string | null;
44
+
45
+ export function isCoveredByDefaultDenyRules(pathPattern: string): boolean;
46
+
47
+ export function aggregateBlockerHistory(records: unknown): {
48
+ pathCounts: Map<string, number>;
49
+ pathBlockers: Map<string, Set<string>>;
50
+ blockerCounts: Map<string, number>;
51
+ recordCount: number;
52
+ };
53
+
54
+ export function synthesizeDenyRuleProposals(
55
+ records: unknown,
56
+ config?: SynthesisConfig,
57
+ ): DenyRuleProposal[];
58
+
59
+ export function resolveEffectiveDenyRules(options?: {
60
+ includeDefaults?: boolean;
61
+ approvedProposals?: DenyRuleProposal[];
62
+ }): DenyRule[];
63
+
64
+ export function setProposalStatuses(
65
+ proposals: DenyRuleProposal[],
66
+ updates: Record<string, DenyRuleProposalStatus> | Map<string, DenyRuleProposalStatus>,
67
+ ): DenyRuleProposal[];
68
+
69
+ export function resolveDenyHookSynthesisDbPath(env?: Record<string, string | undefined>): string;
70
+
71
+ export type DenyHookSynthesisStore = {
72
+ dbPath: string;
73
+ refreshProposals(
74
+ repoFullName: string,
75
+ history: unknown,
76
+ config?: SynthesisConfig,
77
+ apiBaseUrl?: string,
78
+ ): DenyRuleProposal[];
79
+ listProposals(repoFullName: string, apiBaseUrl?: string): DenyRuleProposal[];
80
+ setProposalStatus(
81
+ repoFullName: string,
82
+ proposalId: string,
83
+ status: DenyRuleProposalStatus,
84
+ apiBaseUrl?: string,
85
+ ): void;
86
+ resolveEffectiveRules(
87
+ repoFullName: string,
88
+ options?: { includeDefaults?: boolean; apiBaseUrl?: string },
89
+ ): DenyRule[];
90
+ close(): void;
91
+ };
92
+
93
+ export function initDenyHookSynthesisStore(dbPath?: string): DenyHookSynthesisStore;