@dev-loops/core 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/bin/capture-deep-persona-signals.mjs +143 -0
- package/bin/ensure-phase-files.mjs +7 -0
- package/bin/log-bash-exit-1.mjs +7 -0
- package/bin/parse-review-threads.mjs +7 -0
- package/package.json +78 -0
- package/src/analysis/change-classifier.mjs +146 -0
- package/src/analysis/diff-analyzer.mjs +285 -0
- package/src/bash-exit-one.mjs +130 -0
- package/src/cli/helpers.mjs +22 -0
- package/src/cli/primitives.mjs +70 -0
- package/src/cli/retry-wrapper.mjs +169 -0
- package/src/cli/subcommand-runner.mjs +246 -0
- package/src/config/config.mjs +965 -0
- package/src/debt/cluster.mjs +240 -0
- package/src/debt/debt-finding.mjs +68 -0
- package/src/debt/debt-signal.mjs +46 -0
- package/src/debt/deep-persona-signals.mjs +266 -0
- package/src/debt/remediation-to-issue.mjs +121 -0
- package/src/debt/score.mjs +127 -0
- package/src/debt/shape.mjs +214 -0
- package/src/github/copilot-helpers.mjs +343 -0
- package/src/github/repo-slug.mjs +105 -0
- package/src/github/review-threads.mjs +343 -0
- package/src/harness/adapter.mjs +57 -0
- package/src/harness/index.mjs +3 -0
- package/src/harness/noop-adapter.mjs +22 -0
- package/src/harness/pi-adapter.mjs +47 -0
- package/src/loop/async-start-contract.mjs +170 -0
- package/src/loop/conductor-routing.mjs +817 -0
- package/src/loop/copilot-ci-status.mjs +255 -0
- package/src/loop/copilot-loop-iterations.mjs +161 -0
- package/src/loop/copilot-loop-state.mjs +510 -0
- package/src/loop/handoff-envelope.mjs +800 -0
- package/src/loop/issue-refinement-artifact.mjs +268 -0
- package/src/loop/lifecycle-state.mjs +342 -0
- package/src/loop/phase-files.mjs +187 -0
- package/src/loop/policy-constants.mjs +17 -0
- package/src/loop/pr-gate-coordination.mjs +1278 -0
- package/src/loop/public-dev-loop-routing-contract.mjs +277 -0
- package/src/loop/public-dev-loop-routing.mjs +1746 -0
- package/src/loop/queue-board-ordering.mjs +38 -0
- package/src/loop/queue-board-sync.mjs +223 -0
- package/src/loop/queue-driver.mjs +164 -0
- package/src/loop/queue-parallel.mjs +190 -0
- package/src/loop/queue-state.mjs +230 -0
- package/src/loop/retrospective-checkpoint.mjs +178 -0
- package/src/loop/reviewer-loop-state.mjs +456 -0
- package/src/loop/run-inspection.mjs +604 -0
- package/src/loop/steering.mjs +793 -0
- package/src/loop/timeout-policy.mjs +73 -0
- package/src/loop/tracker-first-loop-state.mjs +87 -0
- package/src/loop/tracker-pr-state.mjs +301 -0
- package/src/loop/worktree-guard.mjs +141 -0
- package/src/refinement/ac-dod-matrix.mjs +95 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
export function createDefaultPhaseManifest(phase) {
|
|
5
|
+
return {
|
|
6
|
+
phase,
|
|
7
|
+
status: "not-started",
|
|
8
|
+
startedAt: "",
|
|
9
|
+
completedAt: "",
|
|
10
|
+
nextPhase: "",
|
|
11
|
+
validation: {
|
|
12
|
+
check: "not-run",
|
|
13
|
+
test: "not-run",
|
|
14
|
+
coverage: "not-run",
|
|
15
|
+
},
|
|
16
|
+
artifacts: [],
|
|
17
|
+
subagents: [],
|
|
18
|
+
decisions: [],
|
|
19
|
+
notes: [],
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function createDefaultPhaseIndex() {
|
|
24
|
+
return {
|
|
25
|
+
phases: [],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function normalizePhaseName(phase) {
|
|
30
|
+
if (typeof phase !== "string" || !/^phase-\d+$/.test(phase)) {
|
|
31
|
+
throw new Error(`phase must match phase-<number>, received ${phase}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return phase;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function uniqueSortedStrings(values) {
|
|
38
|
+
return [...new Set(values.filter((value) => typeof value === "string" && value.length > 0))].sort();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function readJsonIfExists(filePath) {
|
|
42
|
+
try {
|
|
43
|
+
const content = await readFile(filePath, "utf8");
|
|
44
|
+
return JSON.parse(content);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function writeJson(filePath, value) {
|
|
55
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
56
|
+
await writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function buildPhasePaths(projectRoot, phase) {
|
|
60
|
+
const normalizedPhase = normalizePhaseName(phase);
|
|
61
|
+
const phasesRoot = path.join(projectRoot, "tmp", "phases");
|
|
62
|
+
const phaseDir = path.join(phasesRoot, normalizedPhase);
|
|
63
|
+
const docsPhasesRoot = path.join(projectRoot, "docs", "phases");
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
projectRoot,
|
|
67
|
+
phasesRoot,
|
|
68
|
+
phase: normalizedPhase,
|
|
69
|
+
phaseDir,
|
|
70
|
+
docsPhasesRoot,
|
|
71
|
+
phasePlanPath: path.join(docsPhasesRoot, `${normalizedPhase}.md`),
|
|
72
|
+
manifestPath: path.join(phaseDir, "manifest.json"),
|
|
73
|
+
indexPath: path.join(phasesRoot, "index.json"),
|
|
74
|
+
bashExitOnePath: path.join(phaseDir, "bash-exit-1.jsonl"),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function applyManifestPatch(manifest, patch = {}) {
|
|
79
|
+
const next = {
|
|
80
|
+
...manifest,
|
|
81
|
+
...patch,
|
|
82
|
+
validation: {
|
|
83
|
+
...manifest.validation,
|
|
84
|
+
...(patch.validation ?? {}),
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
next.artifacts = uniqueSortedStrings([...(manifest.artifacts ?? []), ...(patch.artifacts ?? [])]);
|
|
89
|
+
next.subagents = uniqueSortedStrings([...(manifest.subagents ?? []), ...(patch.subagents ?? [])]);
|
|
90
|
+
next.decisions = uniqueSortedStrings([...(manifest.decisions ?? []), ...(patch.decisions ?? [])]);
|
|
91
|
+
next.notes = uniqueSortedStrings([...(manifest.notes ?? []), ...(patch.notes ?? [])]);
|
|
92
|
+
|
|
93
|
+
return next;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function upsertPhaseIndex(index, phaseEntry) {
|
|
97
|
+
const phases = Array.isArray(index?.phases) ? [...index.phases] : [];
|
|
98
|
+
const existingIndex = phases.findIndex((entry) => entry.phase === phaseEntry.phase);
|
|
99
|
+
|
|
100
|
+
if (existingIndex >= 0) {
|
|
101
|
+
phases[existingIndex] = {
|
|
102
|
+
...phases[existingIndex],
|
|
103
|
+
...phaseEntry,
|
|
104
|
+
};
|
|
105
|
+
} else {
|
|
106
|
+
phases.push(phaseEntry);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
phases.sort((left, right) => left.phase.localeCompare(right.phase, undefined, { numeric: true }));
|
|
110
|
+
return { phases };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function ensurePhaseFiles(projectRoot, phase, patch = {}) {
|
|
114
|
+
const paths = buildPhasePaths(projectRoot, phase);
|
|
115
|
+
await mkdir(paths.phaseDir, { recursive: true });
|
|
116
|
+
|
|
117
|
+
const currentManifest = (await readJsonIfExists(paths.manifestPath)) ?? createDefaultPhaseManifest(paths.phase);
|
|
118
|
+
const nextManifest = applyManifestPatch(currentManifest, patch);
|
|
119
|
+
await writeJson(paths.manifestPath, nextManifest);
|
|
120
|
+
|
|
121
|
+
const currentIndex = (await readJsonIfExists(paths.indexPath)) ?? createDefaultPhaseIndex();
|
|
122
|
+
const nextIndex = upsertPhaseIndex(currentIndex, {
|
|
123
|
+
phase: paths.phase,
|
|
124
|
+
status: nextManifest.status,
|
|
125
|
+
manifestPath: path.relative(projectRoot, paths.manifestPath),
|
|
126
|
+
updatedAt: new Date().toISOString(),
|
|
127
|
+
});
|
|
128
|
+
await writeJson(paths.indexPath, nextIndex);
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
paths,
|
|
132
|
+
manifest: nextManifest,
|
|
133
|
+
index: nextIndex,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function requireOptionValue(args, flag) {
|
|
138
|
+
const value = args.shift();
|
|
139
|
+
|
|
140
|
+
if (typeof value !== "string" || value.length === 0 || value.startsWith("--")) {
|
|
141
|
+
throw new Error(`Missing value for ${flag}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return value;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function parseCliArgs(argv) {
|
|
148
|
+
const args = [...argv];
|
|
149
|
+
const options = {
|
|
150
|
+
projectRoot: process.cwd(),
|
|
151
|
+
phase: undefined,
|
|
152
|
+
patch: {},
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
while (args.length > 0) {
|
|
156
|
+
const token = args.shift();
|
|
157
|
+
|
|
158
|
+
if (token === "--project-root") {
|
|
159
|
+
options.projectRoot = requireOptionValue(args, "--project-root");
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (token === "--phase") {
|
|
164
|
+
options.phase = requireOptionValue(args, "--phase");
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (token === "--patch") {
|
|
169
|
+
options.patch = JSON.parse(requireOptionValue(args, "--patch"));
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
throw new Error(`Unknown argument: ${token}`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (!options.phase) {
|
|
177
|
+
throw new Error("Missing required --phase <phase-name> argument");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return options;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export async function runCli(argv = process.argv.slice(2), stdout = process.stdout) {
|
|
184
|
+
const options = parseCliArgs(argv);
|
|
185
|
+
const result = await ensurePhaseFiles(options.projectRoot, options.phase, options.patch);
|
|
186
|
+
stdout.write(`${JSON.stringify({ ok: true, ...result.paths })}\n`);
|
|
187
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized policy constants — single source of truth for
|
|
3
|
+
* timeout budgets, poll intervals, and related policy values.
|
|
4
|
+
*
|
|
5
|
+
* These replace the now-removed CLI policy flags (--timeout-ms,
|
|
6
|
+
* --poll-interval-ms, --probe-only, --force, --force-rerequest-review).
|
|
7
|
+
*/
|
|
8
|
+
export const DEFAULT_POLL_INTERVAL_MS = 60_000;
|
|
9
|
+
|
|
10
|
+
/** Copilot-first durable-wait seam: bootstrap-only PR watch budget */
|
|
11
|
+
export const COPILOT_FIRST_DURABLE_WAIT_TIMEOUT_MS = 3_600_000;
|
|
12
|
+
|
|
13
|
+
/** Copilot review wait: external healthy-wait budget */
|
|
14
|
+
export const COPILOT_REVIEW_WAIT_TIMEOUT_MS = 1_800_000;
|
|
15
|
+
|
|
16
|
+
/** Explicit single-check timeout value (used only for status probes) */
|
|
17
|
+
export const PROBE_ONLY_TIMEOUT_MS = 0;
|