@loopover/engine 1.0.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/CHANGELOG.md +94 -0
- package/LICENSE +661 -0
- package/README.md +676 -0
- package/dist/advisory/gate-advisory.d.ts +151 -0
- package/dist/advisory/gate-advisory.js +512 -0
- package/dist/ai-policy-map.d.ts +97 -0
- package/dist/ai-policy-map.js +377 -0
- package/dist/ams-policy-spec.d.ts +56 -0
- package/dist/ams-policy-spec.js +165 -0
- package/dist/calibration-dashboard.d.ts +23 -0
- package/dist/calibration-dashboard.js +65 -0
- package/dist/calibration-trend.d.ts +40 -0
- package/dist/calibration-trend.js +60 -0
- package/dist/contributor-fit.d.ts +24 -0
- package/dist/contributor-fit.js +50 -0
- package/dist/discovery-index-contract.d.ts +74 -0
- package/dist/discovery-index-contract.js +184 -0
- package/dist/discovery-soft-claim.d.ts +35 -0
- package/dist/discovery-soft-claim.js +50 -0
- package/dist/duplicate-winner.d.ts +57 -0
- package/dist/duplicate-winner.js +98 -0
- package/dist/feasibility.d.ts +27 -0
- package/dist/feasibility.js +86 -0
- package/dist/finding-severity-calibration.d.ts +116 -0
- package/dist/finding-severity-calibration.js +457 -0
- package/dist/fleet-run-manifest.d.ts +41 -0
- package/dist/fleet-run-manifest.js +151 -0
- package/dist/focus-manifest/guidance.d.ts +21 -0
- package/dist/focus-manifest/guidance.js +222 -0
- package/dist/focus-manifest.d.ts +826 -0
- package/dist/focus-manifest.js +2730 -0
- package/dist/gate-verdict-calibration.d.ts +111 -0
- package/dist/gate-verdict-calibration.js +411 -0
- package/dist/github/constants.d.ts +1 -0
- package/dist/github/constants.js +1 -0
- package/dist/github/sanitize-public-comment.d.ts +1 -0
- package/dist/github/sanitize-public-comment.js +26 -0
- package/dist/goal-model.d.ts +7 -0
- package/dist/goal-model.js +87 -0
- package/dist/governor/action-mode.d.ts +55 -0
- package/dist/governor/action-mode.js +74 -0
- package/dist/governor/budget-cap.d.ts +49 -0
- package/dist/governor/budget-cap.js +34 -0
- package/dist/governor/chokepoint.d.ts +58 -0
- package/dist/governor/chokepoint.js +269 -0
- package/dist/governor/kill-switch.d.ts +38 -0
- package/dist/governor/kill-switch.js +62 -0
- package/dist/governor/rate-limit.d.ts +40 -0
- package/dist/governor/rate-limit.js +63 -0
- package/dist/governor/reputation-throttle.d.ts +50 -0
- package/dist/governor/reputation-throttle.js +94 -0
- package/dist/governor/run-halt.d.ts +39 -0
- package/dist/governor/run-halt.js +83 -0
- package/dist/governor/self-plagiarism.d.ts +47 -0
- package/dist/governor/self-plagiarism.js +200 -0
- package/dist/governor/write-rate-limit.d.ts +52 -0
- package/dist/governor/write-rate-limit.js +132 -0
- package/dist/governor-ledger.d.ts +25 -0
- package/dist/governor-ledger.js +91 -0
- package/dist/index.d.ts +99 -0
- package/dist/index.js +120 -0
- package/dist/issue-plan-decomposition.d.ts +18 -0
- package/dist/issue-plan-decomposition.js +56 -0
- package/dist/issue-rag-query.d.ts +12 -0
- package/dist/issue-rag-query.js +35 -0
- package/dist/local-scorer.d.ts +34 -0
- package/dist/local-scorer.js +36 -0
- package/dist/metadata-best-min-score.d.ts +9 -0
- package/dist/metadata-best-min-score.js +9 -0
- package/dist/metadata-best-pick.d.ts +9 -0
- package/dist/metadata-best-pick.js +9 -0
- package/dist/metadata-min-score.d.ts +9 -0
- package/dist/metadata-min-score.js +13 -0
- package/dist/metadata-top-min-score.d.ts +9 -0
- package/dist/metadata-top-min-score.js +13 -0
- package/dist/metadata-top-pick.d.ts +9 -0
- package/dist/metadata-top-pick.js +13 -0
- package/dist/miner/acceptance-criteria.d.ts +43 -0
- package/dist/miner/acceptance-criteria.js +77 -0
- package/dist/miner/agent-sdk-driver.d.ts +39 -0
- package/dist/miner/agent-sdk-driver.js +162 -0
- package/dist/miner/attempt-log.d.ts +29 -0
- package/dist/miner/attempt-log.js +100 -0
- package/dist/miner/attempt-metering.d.ts +39 -0
- package/dist/miner/attempt-metering.js +67 -0
- package/dist/miner/cli-subprocess-driver.d.ts +68 -0
- package/dist/miner/cli-subprocess-driver.js +272 -0
- package/dist/miner/coding-agent-driver.d.ts +33 -0
- package/dist/miner/coding-agent-driver.js +36 -0
- package/dist/miner/coding-agent-invoke.d.ts +11 -0
- package/dist/miner/coding-agent-invoke.js +80 -0
- package/dist/miner/coding-agent-mode.d.ts +25 -0
- package/dist/miner/coding-agent-mode.js +43 -0
- package/dist/miner/driver-factory.d.ts +82 -0
- package/dist/miner/driver-factory.js +169 -0
- package/dist/miner/harness-submission-trigger.d.ts +37 -0
- package/dist/miner/harness-submission-trigger.js +46 -0
- package/dist/miner/iterate-loop.d.ts +97 -0
- package/dist/miner/iterate-loop.js +278 -0
- package/dist/miner/iterate-policy.d.ts +98 -0
- package/dist/miner/iterate-policy.js +98 -0
- package/dist/miner/lint-guard.d.ts +50 -0
- package/dist/miner/lint-guard.js +68 -0
- package/dist/miner/local-write-tools.d.ts +81 -0
- package/dist/miner/local-write-tools.js +113 -0
- package/dist/miner/loop-reentry-policy.d.ts +36 -0
- package/dist/miner/loop-reentry-policy.js +53 -0
- package/dist/miner/repo-map.d.ts +51 -0
- package/dist/miner/repo-map.js +268 -0
- package/dist/miner/self-review-adapter.d.ts +106 -0
- package/dist/miner/self-review-adapter.js +83 -0
- package/dist/miner/submission-gate.d.ts +40 -0
- package/dist/miner/submission-gate.js +89 -0
- package/dist/miner/worktree-allocator.d.ts +59 -0
- package/dist/miner/worktree-allocator.js +60 -0
- package/dist/miner/worktree-pool.d.ts +55 -0
- package/dist/miner/worktree-pool.js +65 -0
- package/dist/miner-goal-lane-fit.d.ts +18 -0
- package/dist/miner-goal-lane-fit.js +76 -0
- package/dist/miner-goal-spec.d.ts +139 -0
- package/dist/miner-goal-spec.js +281 -0
- package/dist/miner-prediction-metrics.d.ts +16 -0
- package/dist/miner-prediction-metrics.js +61 -0
- package/dist/miner-telemetry.d.ts +32 -0
- package/dist/miner-telemetry.js +87 -0
- package/dist/objective-anchor.d.ts +125 -0
- package/dist/objective-anchor.js +401 -0
- package/dist/opportunity-competition.d.ts +6 -0
- package/dist/opportunity-competition.js +29 -0
- package/dist/opportunity-freshness.d.ts +18 -0
- package/dist/opportunity-freshness.js +55 -0
- package/dist/opportunity-metadata.d.ts +53 -0
- package/dist/opportunity-metadata.js +204 -0
- package/dist/opportunity-ranker.d.ts +46 -0
- package/dist/opportunity-ranker.js +74 -0
- package/dist/pairwise-calibration.d.ts +49 -0
- package/dist/pairwise-calibration.js +106 -0
- package/dist/phase7-calibration-loop.d.ts +136 -0
- package/dist/phase7-calibration-loop.js +437 -0
- package/dist/plan-blocked.d.ts +6 -0
- package/dist/plan-blocked.js +24 -0
- package/dist/plan-completed.d.ts +5 -0
- package/dist/plan-completed.js +6 -0
- package/dist/plan-completion.d.ts +6 -0
- package/dist/plan-completion.js +7 -0
- package/dist/plan-empty.d.ts +5 -0
- package/dist/plan-empty.js +6 -0
- package/dist/plan-export.d.ts +27 -0
- package/dist/plan-export.js +86 -0
- package/dist/plan-failure.d.ts +5 -0
- package/dist/plan-failure.js +6 -0
- package/dist/plan-overall-status.d.ts +6 -0
- package/dist/plan-overall-status.js +25 -0
- package/dist/plan-pending.d.ts +5 -0
- package/dist/plan-pending.js +6 -0
- package/dist/plan-progress-complete.d.ts +7 -0
- package/dist/plan-progress-complete.js +10 -0
- package/dist/plan-ready.d.ts +6 -0
- package/dist/plan-ready.js +12 -0
- package/dist/plan-running.d.ts +5 -0
- package/dist/plan-running.js +6 -0
- package/dist/plan-skipped.d.ts +5 -0
- package/dist/plan-skipped.js +6 -0
- package/dist/plan-step-count.d.ts +5 -0
- package/dist/plan-step-count.js +6 -0
- package/dist/plan-step-stats.d.ts +5 -0
- package/dist/plan-step-stats.js +6 -0
- package/dist/plan-templates.d.ts +25 -0
- package/dist/plan-templates.js +107 -0
- package/dist/plan-terminated.d.ts +6 -0
- package/dist/plan-terminated.js +9 -0
- package/dist/portfolio/non-convergence.d.ts +37 -0
- package/dist/portfolio/non-convergence.js +57 -0
- package/dist/portfolio/queue.d.ts +30 -0
- package/dist/portfolio/queue.js +150 -0
- package/dist/predicted-gate.d.ts +103 -0
- package/dist/predicted-gate.js +218 -0
- package/dist/prompt-packet.d.ts +11 -0
- package/dist/prompt-packet.js +34 -0
- package/dist/ranked-opportunity-best-min-score.d.ts +8 -0
- package/dist/ranked-opportunity-best-min-score.js +9 -0
- package/dist/ranked-opportunity-best-pick.d.ts +8 -0
- package/dist/ranked-opportunity-best-pick.js +9 -0
- package/dist/ranked-opportunity-min-score.d.ts +8 -0
- package/dist/ranked-opportunity-min-score.js +13 -0
- package/dist/ranked-opportunity-top-min-score.d.ts +8 -0
- package/dist/ranked-opportunity-top-min-score.js +13 -0
- package/dist/review/advisory-ai-routing-config.d.ts +8 -0
- package/dist/review/advisory-ai-routing-config.js +40 -0
- package/dist/review/check-names.d.ts +1 -0
- package/dist/review/check-names.js +1 -0
- package/dist/review/cla-check.d.ts +49 -0
- package/dist/review/cla-check.js +71 -0
- package/dist/review/diff-file-priority.d.ts +1 -0
- package/dist/review/diff-file-priority.js +12 -0
- package/dist/review/enrichment-analyzer-names.d.ts +3 -0
- package/dist/review/enrichment-analyzer-names.js +63 -0
- package/dist/review/guardrail-config.d.ts +10 -0
- package/dist/review/guardrail-config.js +62 -0
- package/dist/review/linked-issue-hard-rules-config.d.ts +4 -0
- package/dist/review/linked-issue-hard-rules-config.js +79 -0
- package/dist/review/linked-issue-label-propagation.d.ts +9 -0
- package/dist/review/linked-issue-label-propagation.js +114 -0
- package/dist/review/pre-merge-checks.d.ts +28 -0
- package/dist/review/pre-merge-checks.js +66 -0
- package/dist/review/review-thread-findings.d.ts +1 -0
- package/dist/review/review-thread-findings.js +1 -0
- package/dist/review/safe-url.d.ts +5 -0
- package/dist/review/safe-url.js +137 -0
- package/dist/review/screenshot-table-gate.d.ts +87 -0
- package/dist/review/screenshot-table-gate.js +340 -0
- package/dist/review/unlinked-issue-guardrail-config.d.ts +9 -0
- package/dist/review/unlinked-issue-guardrail-config.js +44 -0
- package/dist/reviewer-consensus-calibration.d.ts +113 -0
- package/dist/reviewer-consensus-calibration.js +493 -0
- package/dist/reward-risk.d.ts +223 -0
- package/dist/reward-risk.js +618 -0
- package/dist/scoring/label-match.d.ts +4 -0
- package/dist/scoring/label-match.js +145 -0
- package/dist/scoring/model.d.ts +31 -0
- package/dist/scoring/model.js +157 -0
- package/dist/scoring/pending-pr-scenarios.d.ts +44 -0
- package/dist/scoring/pending-pr-scenarios.js +145 -0
- package/dist/scoring/preview.d.ts +246 -0
- package/dist/scoring/preview.js +1108 -0
- package/dist/scoring/types.d.ts +131 -0
- package/dist/scoring/types.js +7 -0
- package/dist/settings/auto-close-exempt.d.ts +10 -0
- package/dist/settings/auto-close-exempt.js +58 -0
- package/dist/settings/autonomy.d.ts +33 -0
- package/dist/settings/autonomy.js +80 -0
- package/dist/settings/command-authorization.d.ts +49 -0
- package/dist/settings/command-authorization.js +222 -0
- package/dist/settings/contributor-blacklist.d.ts +17 -0
- package/dist/settings/contributor-blacklist.js +86 -0
- package/dist/settings/moderation-rules.d.ts +51 -0
- package/dist/settings/moderation-rules.js +104 -0
- package/dist/settings/pr-type-label.d.ts +70 -0
- package/dist/settings/pr-type-label.js +149 -0
- package/dist/signals/change-guardrail.d.ts +49 -0
- package/dist/signals/change-guardrail.js +164 -0
- package/dist/signals/check-summary.d.ts +9 -0
- package/dist/signals/check-summary.js +12 -0
- package/dist/signals/contributor-calibration.d.ts +23 -0
- package/dist/signals/contributor-calibration.js +47 -0
- package/dist/signals/duplicate-winner.d.ts +9 -0
- package/dist/signals/duplicate-winner.js +9 -0
- package/dist/signals/path-matchers.d.ts +36 -0
- package/dist/signals/path-matchers.js +371 -0
- package/dist/signals/predicted-gate-engine.d.ts +62 -0
- package/dist/signals/predicted-gate-engine.js +895 -0
- package/dist/signals/preflight-limits.d.ts +14 -0
- package/dist/signals/preflight-limits.js +14 -0
- package/dist/signals/slop.d.ts +56 -0
- package/dist/signals/slop.js +324 -0
- package/dist/signals/test-evidence.d.ts +25 -0
- package/dist/signals/test-evidence.js +108 -0
- package/dist/subprocess-env.d.ts +23 -0
- package/dist/subprocess-env.js +74 -0
- package/dist/track-record-summary.d.ts +92 -0
- package/dist/track-record-summary.js +301 -0
- package/dist/types/manifest-deps-types.d.ts +514 -0
- package/dist/types/manifest-deps-types.js +4 -0
- package/dist/types/predicted-gate-types.d.ts +338 -0
- package/dist/types/predicted-gate-types.js +3 -0
- package/dist/types/reward-risk-types.d.ts +319 -0
- package/dist/types/reward-risk-types.js +11 -0
- package/dist/utils/json.d.ts +1 -0
- package/dist/utils/json.js +3 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +3 -0
- package/package.json +80 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Plan DAG rendering (pure).
|
|
2
|
+
//
|
|
3
|
+
// Deterministic, side-effect-free renderers over an already-validated plan DAG (the `planDagSchema` shape used by
|
|
4
|
+
// the MCP `gittensory_plan_status` surface in src/mcp/server.ts). No IO and no new logic: given a plan, produce
|
|
5
|
+
// either a human-readable Markdown checklist ordered by dependency, or a stable, key-ordered JSON string that is
|
|
6
|
+
// byte-identical across runs of the same plan (useful for diffing). The types below mirror the `planDagSchema`
|
|
7
|
+
// shape so the engine package stays standalone and does not import the app's Zod schema.
|
|
8
|
+
// Stable topological order: emit steps whose in-plan dependencies are already emitted, ties broken by the plan's
|
|
9
|
+
// original order. A dependency id not present in the plan is treated as satisfied. Any steps left in a cycle are
|
|
10
|
+
// appended in original order so nothing is dropped and the function always terminates.
|
|
11
|
+
function orderByDependency(steps) {
|
|
12
|
+
const present = new Set(steps.map((step) => step.id));
|
|
13
|
+
const emitted = new Set();
|
|
14
|
+
const ordered = [];
|
|
15
|
+
const remaining = [...steps];
|
|
16
|
+
let progressed = true;
|
|
17
|
+
while (remaining.length > 0 && progressed) {
|
|
18
|
+
progressed = false;
|
|
19
|
+
for (let i = 0; i < remaining.length;) {
|
|
20
|
+
const step = remaining[i];
|
|
21
|
+
const ready = step.dependsOn.every((dep) => !present.has(dep) || emitted.has(dep));
|
|
22
|
+
if (ready) {
|
|
23
|
+
ordered.push(step);
|
|
24
|
+
emitted.add(step.id);
|
|
25
|
+
remaining.splice(i, 1);
|
|
26
|
+
progressed = true;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
i += 1;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
ordered.push(...remaining);
|
|
34
|
+
return ordered;
|
|
35
|
+
}
|
|
36
|
+
// Make an untrusted title/error safe to drop into a Markdown checklist line: collapse any CR/LF run to a single
|
|
37
|
+
// space (both fields allow newlines in the plan schema) so a step cannot spill onto extra rows, and backslash-escape
|
|
38
|
+
// the Markdown control characters that would otherwise re-style the line (emphasis, code, links, html, tables,
|
|
39
|
+
// strikethrough) when the artifact is pasted into a review surface. Backslash is escaped by the same class, so the
|
|
40
|
+
// single pass is idempotent per character.
|
|
41
|
+
function displaySafe(text) {
|
|
42
|
+
return text.replace(/[\r\n]+/g, " ").replace(/[\\`*_[\]<>|~]/g, "\\$&");
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Render a plan DAG as a Markdown checklist ordered by dependency: one `- [x]`/`- [ ]` line per step (checked when
|
|
46
|
+
* the step is completed), annotated with its status, its attempt count when it has run, and its last error when
|
|
47
|
+
* present. Display fields are collapsed to a single line and Markdown control characters are escaped so each step
|
|
48
|
+
* stays on one row and an untrusted title/error cannot re-style it. Pure — it reads the plan and returns a string.
|
|
49
|
+
*/
|
|
50
|
+
export function renderPlanAsMarkdown(plan) {
|
|
51
|
+
const ordered = orderByDependency(plan.steps);
|
|
52
|
+
if (ordered.length === 0)
|
|
53
|
+
return "_No steps in this plan._";
|
|
54
|
+
return ordered
|
|
55
|
+
.map((step) => {
|
|
56
|
+
const box = step.status === "completed" ? "[x]" : "[ ]";
|
|
57
|
+
let line = `- ${box} ${displaySafe(step.title)} — ${step.status}`;
|
|
58
|
+
if (step.attempts > 0)
|
|
59
|
+
line += ` (attempt ${step.attempts}/${step.maxAttempts})`;
|
|
60
|
+
if (step.lastError)
|
|
61
|
+
line += `: ${displaySafe(step.lastError)}`;
|
|
62
|
+
return line;
|
|
63
|
+
})
|
|
64
|
+
.join("\n");
|
|
65
|
+
}
|
|
66
|
+
// Sort object keys at every level so the output is deterministic; arrays (e.g. `steps`) keep their order.
|
|
67
|
+
function sortedKeysReplacer(_key, value) {
|
|
68
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
69
|
+
const source = value;
|
|
70
|
+
return Object.keys(source)
|
|
71
|
+
.sort()
|
|
72
|
+
.reduce((acc, key) => {
|
|
73
|
+
acc[key] = source[key];
|
|
74
|
+
return acc;
|
|
75
|
+
}, {});
|
|
76
|
+
}
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Render a plan DAG as a stable, deterministically key-ordered JSON string. Two renders of the identical plan are
|
|
81
|
+
* byte-identical (object keys are sorted at every level; array order is preserved), which makes plan snapshots
|
|
82
|
+
* diffable across runs. Pure.
|
|
83
|
+
*/
|
|
84
|
+
export function renderPlanAsJson(plan) {
|
|
85
|
+
return JSON.stringify(plan, sortedKeysReplacer, 2);
|
|
86
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { PlanDag } from "./plan-export.js";
|
|
2
|
+
export type PlanOverallStatus = "pending" | "running" | "completed" | "failed" | "blocked";
|
|
3
|
+
/**
|
|
4
|
+
* Resolve the coarse plan status matching hosted `planProgress`'s `status` field. Pure — reads the plan DAG only.
|
|
5
|
+
*/
|
|
6
|
+
export declare function resolvePlanOverallStatus(plan: PlanDag): PlanOverallStatus;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const isDone = (status) => status === "completed" || status === "skipped";
|
|
2
|
+
function nextReadySteps(plan) {
|
|
3
|
+
const statusById = new Map(plan.steps.map((step) => [step.id, step.status]));
|
|
4
|
+
return plan.steps.filter((step) => step.status === "pending" && step.dependsOn.every((dep) => isDone(statusById.get(dep) ?? "pending")));
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Resolve the coarse plan status matching hosted `planProgress`'s `status` field. Pure — reads the plan DAG only.
|
|
8
|
+
*/
|
|
9
|
+
export function resolvePlanOverallStatus(plan) {
|
|
10
|
+
const total = plan.steps.length;
|
|
11
|
+
const completed = plan.steps.filter((step) => step.status === "completed").length;
|
|
12
|
+
const skipped = plan.steps.filter((step) => step.status === "skipped").length;
|
|
13
|
+
const failed = plan.steps.filter((step) => step.status === "failed").length;
|
|
14
|
+
const running = plan.steps.filter((step) => step.status === "running").length;
|
|
15
|
+
const pending = plan.steps.filter((step) => step.status === "pending").length;
|
|
16
|
+
if (total > 0 && completed + skipped === total)
|
|
17
|
+
return "completed";
|
|
18
|
+
if (failed > 0)
|
|
19
|
+
return "failed";
|
|
20
|
+
if (running > 0)
|
|
21
|
+
return "running";
|
|
22
|
+
if (pending > 0 && nextReadySteps(plan).length === 0)
|
|
23
|
+
return "blocked";
|
|
24
|
+
return "pending";
|
|
25
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { PlanDag } from "./plan-export.js";
|
|
2
|
+
/**
|
|
3
|
+
* Return whether every step is terminal success (`completed` or `skipped`). Empty plans are not complete. Mirrors
|
|
4
|
+
* the `completed` branch of hosted `planProgress` (distinct from `isPlanFullyCompleted`, which requires every
|
|
5
|
+
* step to be `completed`). Pure — reads the plan DAG only.
|
|
6
|
+
*/
|
|
7
|
+
export declare function isPlanProgressComplete(plan: PlanDag): boolean;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Return whether every step is terminal success (`completed` or `skipped`). Empty plans are not complete. Mirrors
|
|
3
|
+
* the `completed` branch of hosted `planProgress` (distinct from `isPlanFullyCompleted`, which requires every
|
|
4
|
+
* step to be `completed`). Pure — reads the plan DAG only.
|
|
5
|
+
*/
|
|
6
|
+
export function isPlanProgressComplete(plan) {
|
|
7
|
+
if (plan.steps.length === 0)
|
|
8
|
+
return false;
|
|
9
|
+
return plan.steps.every((step) => step.status === "completed" || step.status === "skipped");
|
|
10
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { PlanDag } from "./plan-export.js";
|
|
2
|
+
/**
|
|
3
|
+
* Return whether any step is runnable now: `pending` with every dependency `completed` or `skipped`. Mirrors hosted
|
|
4
|
+
* `nextReadySteps(plan).length > 0`. Pure.
|
|
5
|
+
*/
|
|
6
|
+
export declare function hasPlanReadySteps(plan: PlanDag): boolean;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const isDone = (status) => status === "completed" || status === "skipped";
|
|
2
|
+
function nextReadySteps(plan) {
|
|
3
|
+
const statusById = new Map(plan.steps.map((step) => [step.id, step.status]));
|
|
4
|
+
return plan.steps.filter((step) => step.status === "pending" && step.dependsOn.every((dep) => isDone(statusById.get(dep) ?? "pending")));
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Return whether any step is runnable now: `pending` with every dependency `completed` or `skipped`. Mirrors hosted
|
|
8
|
+
* `nextReadySteps(plan).length > 0`. Pure.
|
|
9
|
+
*/
|
|
10
|
+
export function hasPlanReadySteps(plan) {
|
|
11
|
+
return nextReadySteps(plan).length > 0;
|
|
12
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type RawPlanStep = {
|
|
2
|
+
id: string;
|
|
3
|
+
title: string;
|
|
4
|
+
actionClass?: string | undefined;
|
|
5
|
+
dependsOn?: string[] | undefined;
|
|
6
|
+
maxAttempts?: number | undefined;
|
|
7
|
+
/** When set on the prepare-phase `coding-agent` step, records which execution mode the attempt runs under (#4313). */
|
|
8
|
+
codingAgentMode?: CodingAgentExecutionMode | undefined;
|
|
9
|
+
};
|
|
10
|
+
/** Re-exported here so plan templates stay standalone — defined in miner/coding-agent-mode.ts. */
|
|
11
|
+
export type CodingAgentExecutionMode = "paused" | "dry_run" | "live";
|
|
12
|
+
export type PlanTemplateStage = "discover" | "analyze" | "create" | "manage" | "plan" | "prepare";
|
|
13
|
+
export type PlanTemplateContext = {
|
|
14
|
+
subject?: string | undefined;
|
|
15
|
+
/** Execution mode for the prepare-phase coding-agent step (#4313) — visible end-to-end on the plan DAG. */
|
|
16
|
+
codingAgentMode?: CodingAgentExecutionMode | undefined;
|
|
17
|
+
};
|
|
18
|
+
export declare function discoverPlanTemplate(context?: PlanTemplateContext): RawPlanStep[];
|
|
19
|
+
export declare function analyzePlanTemplate(context?: PlanTemplateContext): RawPlanStep[];
|
|
20
|
+
export declare function planPlanTemplate(context?: PlanTemplateContext): RawPlanStep[];
|
|
21
|
+
export declare function preparePlanTemplate(context?: PlanTemplateContext): RawPlanStep[];
|
|
22
|
+
export declare function createPlanTemplate(context?: PlanTemplateContext): RawPlanStep[];
|
|
23
|
+
export declare function managePlanTemplate(context?: PlanTemplateContext): RawPlanStep[];
|
|
24
|
+
export declare const PLAN_TEMPLATE_BUILDERS: Readonly<Record<PlanTemplateStage, (context?: PlanTemplateContext) => RawPlanStep[]>>;
|
|
25
|
+
export declare function buildPlanTemplate(stage: PlanTemplateStage, context?: PlanTemplateContext): RawPlanStep[];
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// Plan-template library (pure).
|
|
2
|
+
//
|
|
3
|
+
// Reusable plan TEMPLATES for the fixed miner lifecycle (discover -> analyze -> plan -> prepare -> create ->
|
|
4
|
+
// manage -> repeat), emitted in the exact stateless raw-step shape the MCP `gittensory_build_plan` tool accepts
|
|
5
|
+
// (`rawPlanStepSchema` in src/mcp/server.ts), so `build_plan` can normalize them into a validated DAG. Each builder
|
|
6
|
+
// is deterministic and side-effect-free: it only DESCRIBES steps and their `dependsOn` ordering — it never actuates
|
|
7
|
+
// anything. The `RawPlanStep` type below mirrors the raw-step schema so the engine package stays standalone and
|
|
8
|
+
// does not import the app's Zod schema (the tests validate the output against the real schema to guard drift).
|
|
9
|
+
// Title length ceiling of `rawPlanStepSchema.title` (max 300). Titles are hard-capped to this so a long subject can
|
|
10
|
+
// never produce an out-of-range step.
|
|
11
|
+
const MAX_TITLE_CHARS = 300;
|
|
12
|
+
// Keep the woven subject well under the title ceiling so the fixed prefix always survives the cap.
|
|
13
|
+
const MAX_SUBJECT_CHARS = 200;
|
|
14
|
+
// Collapse any run of whitespace (including newlines) to a single space and trim, so a subject yields a clean,
|
|
15
|
+
// deterministic one-line title.
|
|
16
|
+
function normalizeSubject(subject) {
|
|
17
|
+
return (subject ?? "").replace(/\s+/g, " ").trim().slice(0, MAX_SUBJECT_CHARS);
|
|
18
|
+
}
|
|
19
|
+
// Compose a step title from a fixed prefix and the optional subject, hard-capped to the schema's title ceiling.
|
|
20
|
+
function titleFor(prefix, subject) {
|
|
21
|
+
const full = subject ? `${prefix}: ${subject}` : prefix;
|
|
22
|
+
return full.slice(0, MAX_TITLE_CHARS);
|
|
23
|
+
}
|
|
24
|
+
// discover: rank candidate opportunities, validate lane fit, then run a pre-start check before analyze.
|
|
25
|
+
export function discoverPlanTemplate(context = {}) {
|
|
26
|
+
const subject = normalizeSubject(context.subject);
|
|
27
|
+
return [
|
|
28
|
+
{ id: "opportunity-rank", title: titleFor("Rank candidate opportunities", subject), actionClass: "discover", dependsOn: [], maxAttempts: 2 },
|
|
29
|
+
{ id: "lane-fit-check", title: titleFor("Validate miner goal lane fit", subject), actionClass: "analyze", dependsOn: ["opportunity-rank"], maxAttempts: 1 },
|
|
30
|
+
{ id: "pre-start-check", title: titleFor("Run pre-start check", subject), actionClass: "analyze", dependsOn: ["lane-fit-check"], maxAttempts: 2 },
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
// analyze: feasibility check and repository RAG retrieval run independently, then the prompt-packet build consumes
|
|
34
|
+
// both. Mirrors the ANALYZE-phase ordering described in the plan-template issue.
|
|
35
|
+
export function analyzePlanTemplate(context = {}) {
|
|
36
|
+
const subject = normalizeSubject(context.subject);
|
|
37
|
+
return [
|
|
38
|
+
{ id: "feasibility-check", title: titleFor("Assess feasibility", subject), actionClass: "analyze", dependsOn: [], maxAttempts: 1 },
|
|
39
|
+
{ id: "rag-retrieval", title: titleFor("Retrieve repository context", subject), actionClass: "retrieve", dependsOn: [], maxAttempts: 3 },
|
|
40
|
+
{ id: "prompt-packet", title: titleFor("Build prompt packet", subject), actionClass: "compose", dependsOn: ["feasibility-check", "rag-retrieval"], maxAttempts: 2 },
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
// plan: validate the analyze prompt packet, build the execution DAG, then run a readiness check before prepare.
|
|
44
|
+
export function planPlanTemplate(context = {}) {
|
|
45
|
+
const subject = normalizeSubject(context.subject);
|
|
46
|
+
return [
|
|
47
|
+
{ id: "packet-validate", title: titleFor("Validate prompt packet", subject), actionClass: "analyze", dependsOn: [], maxAttempts: 1 },
|
|
48
|
+
{ id: "plan-dag-build", title: titleFor("Build execution plan DAG", subject), actionClass: "compose", dependsOn: ["packet-validate"], maxAttempts: 2 },
|
|
49
|
+
{ id: "readiness-check", title: titleFor("Run plan readiness check", subject), actionClass: "analyze", dependsOn: ["plan-dag-build"], maxAttempts: 1 },
|
|
50
|
+
];
|
|
51
|
+
}
|
|
52
|
+
// prepare: a strict chain — create the branch, invoke the coding agent (placeholder step; no actuation here), then
|
|
53
|
+
// run the local tests. Mirrors the PREPARE-phase ordering described in the plan-template issue.
|
|
54
|
+
export function preparePlanTemplate(context = {}) {
|
|
55
|
+
const subject = normalizeSubject(context.subject);
|
|
56
|
+
const codingAgentStep = {
|
|
57
|
+
id: "coding-agent",
|
|
58
|
+
title: titleFor("Invoke coding agent", subject),
|
|
59
|
+
actionClass: "codegen",
|
|
60
|
+
dependsOn: ["branch-create"],
|
|
61
|
+
maxAttempts: 1,
|
|
62
|
+
...(context.codingAgentMode === undefined ? {} : { codingAgentMode: context.codingAgentMode }),
|
|
63
|
+
};
|
|
64
|
+
return [
|
|
65
|
+
{ id: "branch-create", title: titleFor("Create working branch", subject), actionClass: "vcs", dependsOn: [], maxAttempts: 3 },
|
|
66
|
+
codingAgentStep,
|
|
67
|
+
{ id: "local-test", title: titleFor("Run local tests", subject), actionClass: "test", dependsOn: ["coding-agent"], maxAttempts: 2 },
|
|
68
|
+
];
|
|
69
|
+
}
|
|
70
|
+
// create: commit the working tree, push the branch, then open the pull request with the public-safe packet.
|
|
71
|
+
export function createPlanTemplate(context = {}) {
|
|
72
|
+
const subject = normalizeSubject(context.subject);
|
|
73
|
+
return [
|
|
74
|
+
{ id: "commit-changes", title: titleFor("Commit working tree changes", subject), actionClass: "vcs", dependsOn: [], maxAttempts: 2 },
|
|
75
|
+
{ id: "push-branch", title: titleFor("Push feature branch", subject), actionClass: "vcs", dependsOn: ["commit-changes"], maxAttempts: 3 },
|
|
76
|
+
{ id: "open-pull-request", title: titleFor("Open pull request", subject), actionClass: "github", dependsOn: ["push-branch"], maxAttempts: 2 },
|
|
77
|
+
];
|
|
78
|
+
}
|
|
79
|
+
// manage: wait for CI, read the gate verdict, then sync the fork default branch for the next cycle.
|
|
80
|
+
export function managePlanTemplate(context = {}) {
|
|
81
|
+
const subject = normalizeSubject(context.subject);
|
|
82
|
+
return [
|
|
83
|
+
{ id: "wait-ci", title: titleFor("Wait for CI completion", subject), actionClass: "test", dependsOn: [], maxAttempts: 5 },
|
|
84
|
+
{ id: "read-gate-result", title: titleFor("Read gate verdict", subject), actionClass: "analyze", dependsOn: ["wait-ci"], maxAttempts: 2 },
|
|
85
|
+
{ id: "sync-fork", title: titleFor("Sync fork default branch", subject), actionClass: "vcs", dependsOn: ["read-gate-result"], maxAttempts: 3 },
|
|
86
|
+
];
|
|
87
|
+
}
|
|
88
|
+
// Registry of every stage transition to its template builder, so callers can enumerate or dispatch by stage.
|
|
89
|
+
// Frozen so a consumer cannot mutate the shared registry and change dispatch behavior process-wide.
|
|
90
|
+
export const PLAN_TEMPLATE_BUILDERS = Object.freeze({
|
|
91
|
+
discover: discoverPlanTemplate,
|
|
92
|
+
analyze: analyzePlanTemplate,
|
|
93
|
+
create: createPlanTemplate,
|
|
94
|
+
manage: managePlanTemplate,
|
|
95
|
+
plan: planPlanTemplate,
|
|
96
|
+
prepare: preparePlanTemplate,
|
|
97
|
+
});
|
|
98
|
+
// Build the raw-step template for a stage. Pure — a thin dispatcher over `PLAN_TEMPLATE_BUILDERS` that rejects an
|
|
99
|
+
// unknown stage with a clear error rather than a generic "not a function" TypeError (guards non-TypeScript callers).
|
|
100
|
+
export function buildPlanTemplate(stage, context = {}) {
|
|
101
|
+
const builder = Object.prototype.hasOwnProperty.call(PLAN_TEMPLATE_BUILDERS, stage)
|
|
102
|
+
? PLAN_TEMPLATE_BUILDERS[stage]
|
|
103
|
+
: undefined;
|
|
104
|
+
if (typeof builder !== "function")
|
|
105
|
+
throw new Error(`Unknown plan-template stage: ${String(stage)}`);
|
|
106
|
+
return builder(context);
|
|
107
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { PlanDag } from "./plan-export.js";
|
|
2
|
+
/**
|
|
3
|
+
* Return whether the plan reached a terminal outcome: any step `failed`, or every step is `completed` or `skipped`.
|
|
4
|
+
* Empty plans are not terminated. Pure.
|
|
5
|
+
*/
|
|
6
|
+
export declare function isPlanTerminated(plan: PlanDag): boolean;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { hasPlanFailedSteps } from "./plan-failure.js";
|
|
2
|
+
import { isPlanProgressComplete } from "./plan-progress-complete.js";
|
|
3
|
+
/**
|
|
4
|
+
* Return whether the plan reached a terminal outcome: any step `failed`, or every step is `completed` or `skipped`.
|
|
5
|
+
* Empty plans are not terminated. Pure.
|
|
6
|
+
*/
|
|
7
|
+
export function isPlanTerminated(plan) {
|
|
8
|
+
return hasPlanFailedSteps(plan) || isPlanProgressComplete(plan);
|
|
9
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export type PortfolioConvergenceStatus = "converging" | "stalled" | "non_convergent";
|
|
2
|
+
/** One queue item's attempt/outcome history. Plain counts — the caller already tracks or supplies these;
|
|
3
|
+
* this module invents no persistence (the queue table carries no attempt-history columns today). */
|
|
4
|
+
export type PortfolioConvergenceInput = {
|
|
5
|
+
/** Total attempts made on this item so far. */
|
|
6
|
+
attempts: number;
|
|
7
|
+
/** Consecutive failed attempts since the last improvement (reset to 0 on any progress). */
|
|
8
|
+
consecutiveFailures: number;
|
|
9
|
+
/** Times the item was re-enqueued (queued → in_progress → queued) without ever reaching `done`. */
|
|
10
|
+
reenqueues: number;
|
|
11
|
+
/** Whether the item has ever reached a terminal `done` outcome. */
|
|
12
|
+
reachedDone: boolean;
|
|
13
|
+
};
|
|
14
|
+
/** Streak lengths at (or above) which a still-unfinished item reads non-convergent. */
|
|
15
|
+
export type PortfolioConvergenceThresholds = {
|
|
16
|
+
/** consecutiveFailures ≥ this ⇒ non_convergent. */
|
|
17
|
+
maxConsecutiveFailures: number;
|
|
18
|
+
/** reenqueues (without reaching done) ≥ this ⇒ non_convergent. */
|
|
19
|
+
maxReenqueues: number;
|
|
20
|
+
};
|
|
21
|
+
/** Conservative defaults — a single failure or re-enqueue never trips these; only a sustained streak does. */
|
|
22
|
+
export declare const DEFAULT_PORTFOLIO_CONVERGENCE_THRESHOLDS: PortfolioConvergenceThresholds;
|
|
23
|
+
export type PortfolioConvergenceVerdict = {
|
|
24
|
+
status: PortfolioConvergenceStatus;
|
|
25
|
+
reasons: string[];
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Classify one queue item's convergence from its attempt/outcome counts. Pure and deterministic.
|
|
29
|
+
*
|
|
30
|
+
* - Zero attempts (not yet tried) reads `converging` — a first attempt is not evidence of a stuck loop
|
|
31
|
+
* (the same non-judgment-on-absence rule ../contributor-fit.ts applies to a first attempt).
|
|
32
|
+
* - An item that has reached `done` is `converging` by definition.
|
|
33
|
+
* - A sustained streak — `consecutiveFailures` or `reenqueues` at/above its threshold — reads
|
|
34
|
+
* `non_convergent`. A single failure or re-enqueue below threshold reads `stalled`, not non-convergent.
|
|
35
|
+
* - Attempts in progress with no failure streak read `converging`.
|
|
36
|
+
*/
|
|
37
|
+
export declare function classifyPortfolioConvergence(input: PortfolioConvergenceInput, thresholds?: PortfolioConvergenceThresholds): PortfolioConvergenceVerdict;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Non-convergence DETECTOR (#4286): a pure classifier over one portfolio-queue item's attempt/outcome
|
|
2
|
+
// history. It answers whether that item is making progress, is merely stalled, or is stuck in a
|
|
3
|
+
// non-convergent loop (cycling queued → in_progress → queued without ever reaching `done`, per the
|
|
4
|
+
// re-enqueue-in-place behaviour at packages/gittensory-miner/lib/portfolio-queue.js:108-115).
|
|
5
|
+
//
|
|
6
|
+
// DETECTOR ONLY — no enforcement, no write-blocking, no IO, no Date.now(), no randomness. It takes typed
|
|
7
|
+
// counts and returns a typed verdict; it gates nothing on its own. The fail-closed Governor chokepoint that
|
|
8
|
+
// COMPOSES this signal with rate-limit + budget caps into one allow/deny decision is separate,
|
|
9
|
+
// maintainer-owned work tracked in #2340 (milestone 13) — explicitly not this module.
|
|
10
|
+
//
|
|
11
|
+
// Mirrors the pure-classifier-over-typed-input discipline of ../contributor-fit.ts (typed input in,
|
|
12
|
+
// { status, reasons } out, and "absence of history is not evidence of a problem").
|
|
13
|
+
/** Conservative defaults — a single failure or re-enqueue never trips these; only a sustained streak does. */
|
|
14
|
+
export const DEFAULT_PORTFOLIO_CONVERGENCE_THRESHOLDS = {
|
|
15
|
+
maxConsecutiveFailures: 3,
|
|
16
|
+
maxReenqueues: 3,
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Classify one queue item's convergence from its attempt/outcome counts. Pure and deterministic.
|
|
20
|
+
*
|
|
21
|
+
* - Zero attempts (not yet tried) reads `converging` — a first attempt is not evidence of a stuck loop
|
|
22
|
+
* (the same non-judgment-on-absence rule ../contributor-fit.ts applies to a first attempt).
|
|
23
|
+
* - An item that has reached `done` is `converging` by definition.
|
|
24
|
+
* - A sustained streak — `consecutiveFailures` or `reenqueues` at/above its threshold — reads
|
|
25
|
+
* `non_convergent`. A single failure or re-enqueue below threshold reads `stalled`, not non-convergent.
|
|
26
|
+
* - Attempts in progress with no failure streak read `converging`.
|
|
27
|
+
*/
|
|
28
|
+
export function classifyPortfolioConvergence(input, thresholds = DEFAULT_PORTFOLIO_CONVERGENCE_THRESHOLDS) {
|
|
29
|
+
if (input.attempts <= 0) {
|
|
30
|
+
return {
|
|
31
|
+
status: "converging",
|
|
32
|
+
reasons: ["No attempts yet; a first attempt is not evidence of a stuck loop."],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
if (input.reachedDone) {
|
|
36
|
+
return { status: "converging", reasons: ["Item reached done."] };
|
|
37
|
+
}
|
|
38
|
+
const reasons = [];
|
|
39
|
+
if (input.consecutiveFailures >= thresholds.maxConsecutiveFailures) {
|
|
40
|
+
reasons.push(`${input.consecutiveFailures} consecutive failures (≥ ${thresholds.maxConsecutiveFailures}).`);
|
|
41
|
+
}
|
|
42
|
+
if (input.reenqueues >= thresholds.maxReenqueues) {
|
|
43
|
+
reasons.push(`re-enqueued ${input.reenqueues} times without reaching done (≥ ${thresholds.maxReenqueues}).`);
|
|
44
|
+
}
|
|
45
|
+
if (reasons.length > 0) {
|
|
46
|
+
return { status: "non_convergent", reasons };
|
|
47
|
+
}
|
|
48
|
+
if (input.consecutiveFailures > 0 || input.reenqueues > 0) {
|
|
49
|
+
return {
|
|
50
|
+
status: "stalled",
|
|
51
|
+
reasons: [
|
|
52
|
+
`${input.consecutiveFailures} consecutive failure(s), ${input.reenqueues} re-enqueue(s) — below the non-convergence threshold.`,
|
|
53
|
+
],
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return { status: "converging", reasons: ["Attempts in progress with no failure streak."] };
|
|
57
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Portfolio queue primitives (#2326). Pure bookkeeping for the miner's local cross-repo work queue:
|
|
3
|
+
* bucket items by repo, respect global/per-repo WIP caps, and select the next eligible batch in a
|
|
4
|
+
* deterministic diversified order. No IO, no Date, no randomness, and no enforcement/action logic.
|
|
5
|
+
*/
|
|
6
|
+
export type PortfolioQueueItemState = "queued" | "in_progress";
|
|
7
|
+
export type PortfolioQueueItem = {
|
|
8
|
+
id: string;
|
|
9
|
+
repoFullName: string;
|
|
10
|
+
state: PortfolioQueueItemState;
|
|
11
|
+
};
|
|
12
|
+
export type PortfolioQueueBucket = {
|
|
13
|
+
repoFullName: string;
|
|
14
|
+
items: PortfolioQueueItem[];
|
|
15
|
+
};
|
|
16
|
+
export type PortfolioQueue = {
|
|
17
|
+
buckets: PortfolioQueueBucket[];
|
|
18
|
+
};
|
|
19
|
+
export type PortfolioCaps = {
|
|
20
|
+
globalWipCap: number;
|
|
21
|
+
perRepoWipCap: number;
|
|
22
|
+
};
|
|
23
|
+
/** Append one item to the queue, creating its repo bucket if needed. Duplicate/blank ids are ignored. Pure. */
|
|
24
|
+
export declare function enqueueItem(queue: PortfolioQueue, item: PortfolioQueueItem): PortfolioQueue;
|
|
25
|
+
/** Remove matching items by id; empty buckets disappear. Unknown/blank ids are a no-op. Pure. */
|
|
26
|
+
export declare function dequeueItem(queue: PortfolioQueue, itemId: string): PortfolioQueue;
|
|
27
|
+
/** Select the next batch of queued items that fit within global/per-repo WIP caps. The batch always alternates
|
|
28
|
+
* repos when another repo still has an eligible item waiting; among those eligible repos, lower current load wins
|
|
29
|
+
* and ties keep stable bucket order. Pure. */
|
|
30
|
+
export declare function nextEligibleItems(queue: PortfolioQueue, caps: PortfolioCaps): PortfolioQueueItem[];
|