@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.
- package/DEPLOYMENT.md +156 -0
- package/Dockerfile +38 -0
- package/README.md +269 -0
- package/bin/gittensory-miner-mcp.d.ts +64 -0
- package/bin/gittensory-miner-mcp.js +279 -0
- package/bin/gittensory-miner.js +196 -0
- package/docs/coding-agent-driver.md +140 -0
- package/docs/config-precedence.md +96 -0
- package/docs/cross-repo-discovery-phase1.md +55 -0
- package/docs/discovery-index-contract.md +60 -0
- package/docs/discovery-plane-operator-guide.md +101 -0
- package/docs/env-reference.md +30 -0
- package/docs/fleet-run-manifest.md +50 -0
- package/docs/miner-goal-spec.md +76 -0
- package/docs/miner-selfimprove-calibration.md +105 -0
- package/docs/observability.md +60 -0
- package/docs/operations-runbook.md +209 -0
- package/docs/repo-agnostic-capability-audit.md +129 -0
- package/docs/unattended-scheduling.md +107 -0
- package/expected-engine.version +1 -0
- package/lib/ams-policy.d.ts +20 -0
- package/lib/ams-policy.js +68 -0
- package/lib/attempt-cli.d.ts +99 -0
- package/lib/attempt-cli.js +523 -0
- package/lib/attempt-input-builder.d.ts +24 -0
- package/lib/attempt-input-builder.js +91 -0
- package/lib/attempt-log.d.ts +37 -0
- package/lib/attempt-log.js +177 -0
- package/lib/attempt-runner.d.ts +59 -0
- package/lib/attempt-runner.js +164 -0
- package/lib/attempt-worktree.d.ts +31 -0
- package/lib/attempt-worktree.js +94 -0
- package/lib/calibration-cli.d.ts +1 -0
- package/lib/calibration-cli.js +92 -0
- package/lib/calibration-run.d.ts +161 -0
- package/lib/calibration-run.js +232 -0
- package/lib/calibration-types.d.ts +46 -0
- package/lib/calibration-types.js +74 -0
- package/lib/calibration.d.ts +24 -0
- package/lib/calibration.js +99 -0
- package/lib/ci-poller.d.ts +33 -0
- package/lib/ci-poller.js +234 -0
- package/lib/claim-adjudication.d.ts +21 -0
- package/lib/claim-adjudication.js +36 -0
- package/lib/claim-conflict-resolver.d.ts +29 -0
- package/lib/claim-conflict-resolver.js +101 -0
- package/lib/claim-ledger-cli.d.ts +59 -0
- package/lib/claim-ledger-cli.js +320 -0
- package/lib/claim-ledger-expiry.d.ts +20 -0
- package/lib/claim-ledger-expiry.js +41 -0
- package/lib/claim-ledger.d.ts +63 -0
- package/lib/claim-ledger.js +303 -0
- package/lib/cli-error.d.ts +3 -0
- package/lib/cli-error.js +27 -0
- package/lib/cli.d.ts +3 -0
- package/lib/cli.js +72 -0
- package/lib/coding-agent-construction.d.ts +17 -0
- package/lib/coding-agent-construction.js +103 -0
- package/lib/coding-agent-house-rules.d.ts +24 -0
- package/lib/coding-agent-house-rules.js +64 -0
- package/lib/coding-task-spec.d.ts +44 -0
- package/lib/coding-task-spec.js +183 -0
- package/lib/config-precedence.d.ts +25 -0
- package/lib/config-precedence.js +38 -0
- package/lib/deny-check.d.ts +11 -0
- package/lib/deny-check.js +76 -0
- package/lib/deny-hook-synthesis.d.ts +93 -0
- package/lib/deny-hook-synthesis.js +399 -0
- package/lib/deny-hooks.d.ts +28 -0
- package/lib/deny-hooks.js +155 -0
- package/lib/deployment-docs-audit.d.ts +41 -0
- package/lib/deployment-docs-audit.js +115 -0
- package/lib/discover-cli.d.ts +94 -0
- package/lib/discover-cli.js +284 -0
- package/lib/discovery-throttle.d.ts +8 -0
- package/lib/discovery-throttle.js +33 -0
- package/lib/env-file-indirection.d.ts +4 -0
- package/lib/env-file-indirection.js +45 -0
- package/lib/event-ledger-cli.d.ts +82 -0
- package/lib/event-ledger-cli.js +266 -0
- package/lib/event-ledger.d.ts +37 -0
- package/lib/event-ledger.js +210 -0
- package/lib/execute-local-write.d.ts +14 -0
- package/lib/execute-local-write.js +50 -0
- package/lib/feasibility-cli.d.ts +25 -0
- package/lib/feasibility-cli.js +80 -0
- package/lib/forge-config.d.ts +17 -0
- package/lib/forge-config.js +37 -0
- package/lib/governor-action-mode.d.ts +26 -0
- package/lib/governor-action-mode.js +47 -0
- package/lib/governor-chokepoint-persisted.d.ts +18 -0
- package/lib/governor-chokepoint-persisted.js +46 -0
- package/lib/governor-chokepoint.d.ts +14 -0
- package/lib/governor-chokepoint.js +52 -0
- package/lib/governor-kill-switch.d.ts +26 -0
- package/lib/governor-kill-switch.js +47 -0
- package/lib/governor-ledger-cli.d.ts +32 -0
- package/lib/governor-ledger-cli.js +155 -0
- package/lib/governor-ledger.d.ts +46 -0
- package/lib/governor-ledger.js +203 -0
- package/lib/governor-open-pr.d.ts +21 -0
- package/lib/governor-open-pr.js +27 -0
- package/lib/governor-pause-cli.d.ts +23 -0
- package/lib/governor-pause-cli.js +171 -0
- package/lib/governor-run-halt.d.ts +37 -0
- package/lib/governor-run-halt.js +58 -0
- package/lib/governor-state.d.ts +63 -0
- package/lib/governor-state.js +363 -0
- package/lib/governor-write-rate-limit.d.ts +30 -0
- package/lib/governor-write-rate-limit.js +64 -0
- package/lib/harness-submission-trigger.d.ts +69 -0
- package/lib/harness-submission-trigger.js +138 -0
- package/lib/http-retry.d.ts +12 -0
- package/lib/http-retry.js +52 -0
- package/lib/laptop-init.d.ts +53 -0
- package/lib/laptop-init.js +332 -0
- package/lib/live-issue-snapshot.d.ts +16 -0
- package/lib/live-issue-snapshot.js +117 -0
- package/lib/local-store.d.ts +18 -0
- package/lib/local-store.js +62 -0
- package/lib/logger.d.ts +58 -0
- package/lib/logger.js +167 -0
- package/lib/loop-cli.d.ts +66 -0
- package/lib/loop-cli.js +540 -0
- package/lib/loop-closure.d.ts +35 -0
- package/lib/loop-closure.js +66 -0
- package/lib/loop-reentry.d.ts +51 -0
- package/lib/loop-reentry.js +124 -0
- package/lib/manage-poll.d.ts +79 -0
- package/lib/manage-poll.js +254 -0
- package/lib/manage-status.d.ts +70 -0
- package/lib/manage-status.js +241 -0
- package/lib/metrics-cli.d.ts +9 -0
- package/lib/metrics-cli.js +50 -0
- package/lib/migrate-cli.d.ts +24 -0
- package/lib/migrate-cli.js +111 -0
- package/lib/miner-goal-spec.d.ts +12 -0
- package/lib/miner-goal-spec.js +67 -0
- package/lib/opportunity-fanout.d.ts +86 -0
- package/lib/opportunity-fanout.js +597 -0
- package/lib/opportunity-ranker.d.ts +36 -0
- package/lib/opportunity-ranker.js +118 -0
- package/lib/orb-export.d.ts +54 -0
- package/lib/orb-export.js +206 -0
- package/lib/plan-store-cli.d.ts +37 -0
- package/lib/plan-store-cli.js +151 -0
- package/lib/plan-store.d.ts +51 -0
- package/lib/plan-store.js +231 -0
- package/lib/policy-doc-cache.d.ts +25 -0
- package/lib/policy-doc-cache.js +79 -0
- package/lib/policy-verdict-cache.d.ts +35 -0
- package/lib/policy-verdict-cache.js +106 -0
- package/lib/portfolio-dashboard.d.ts +30 -0
- package/lib/portfolio-dashboard.js +105 -0
- package/lib/portfolio-discovery.d.ts +29 -0
- package/lib/portfolio-discovery.js +100 -0
- package/lib/portfolio-queue-cli.d.ts +87 -0
- package/lib/portfolio-queue-cli.js +509 -0
- package/lib/portfolio-queue-expiry.d.ts +20 -0
- package/lib/portfolio-queue-expiry.js +51 -0
- package/lib/portfolio-queue-manager.d.ts +50 -0
- package/lib/portfolio-queue-manager.js +134 -0
- package/lib/portfolio-queue.d.ts +62 -0
- package/lib/portfolio-queue.js +351 -0
- package/lib/pr-disposition-poller.d.ts +26 -0
- package/lib/pr-disposition-poller.js +163 -0
- package/lib/pr-number-parse.d.ts +4 -0
- package/lib/pr-number-parse.js +23 -0
- package/lib/pr-outcome.d.ts +41 -0
- package/lib/pr-outcome.js +90 -0
- package/lib/prediction-ledger.d.ts +47 -0
- package/lib/prediction-ledger.js +222 -0
- package/lib/pretooluse-hook.d.ts +31 -0
- package/lib/pretooluse-hook.js +93 -0
- package/lib/process-lifecycle.d.ts +31 -0
- package/lib/process-lifecycle.js +106 -0
- package/lib/purge-cli.d.ts +44 -0
- package/lib/purge-cli.js +198 -0
- package/lib/rejection-signal.d.ts +6 -0
- package/lib/rejection-signal.js +101 -0
- package/lib/rejection-state-machine.d.ts +37 -0
- package/lib/rejection-state-machine.js +81 -0
- package/lib/rejection-templates.d.ts +12 -0
- package/lib/rejection-templates.js +71 -0
- package/lib/replay-objective-anchor.d.ts +79 -0
- package/lib/replay-objective-anchor.js +179 -0
- package/lib/replay-snapshot.d.ts +41 -0
- package/lib/replay-snapshot.js +288 -0
- package/lib/replay-task-generation.d.ts +126 -0
- package/lib/replay-task-generation.js +223 -0
- package/lib/repo-clone.d.ts +19 -0
- package/lib/repo-clone.js +102 -0
- package/lib/run-state-cli.d.ts +27 -0
- package/lib/run-state-cli.js +154 -0
- package/lib/run-state.d.ts +37 -0
- package/lib/run-state.js +150 -0
- package/lib/schema-version.d.ts +17 -0
- package/lib/schema-version.js +71 -0
- package/lib/self-review-context.d.ts +28 -0
- package/lib/self-review-context.js +339 -0
- package/lib/slop-assessment.d.ts +3 -0
- package/lib/slop-assessment.js +20 -0
- package/lib/stack-detection.d.ts +41 -0
- package/lib/stack-detection.js +248 -0
- package/lib/status.d.ts +64 -0
- package/lib/status.js +441 -0
- package/lib/store-maintenance.d.ts +31 -0
- package/lib/store-maintenance.js +183 -0
- package/lib/submission-freshness-check.d.ts +32 -0
- package/lib/submission-freshness-check.js +93 -0
- package/lib/update-check.d.ts +36 -0
- package/lib/update-check.js +161 -0
- package/lib/version.d.ts +3 -0
- package/lib/version.js +10 -0
- package/lib/worktree-allocator.d.ts +39 -0
- package/lib/worktree-allocator.js +262 -0
- package/package.json +50 -0
- 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;
|