@seanyao/roll 4.702.2 → 4.702.3
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 +10 -0
- package/README.md +5 -1
- package/dist/roll.mjs +285 -93
- package/guide/en/loop.md +8 -0
- package/guide/en/project-setup.md +7 -0
- package/guide/zh/loop.md +6 -0
- package/guide/zh/project-setup.md +6 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## v4.702.3 — 2026-07-02
|
|
6
|
+
|
|
7
|
+
### 稳定性
|
|
8
|
+
- `roll init` 现在会负责提交并推送自己生成的项目约定文件,`roll loop go` 不再一开工就撞 dirty(FIX-1072)`[loop-engine]`
|
|
9
|
+
- Builder 提交会被钉在 cycle worktree,不再把提交漏到主 checkout(FIX-1073)`[loop-engine]`
|
|
10
|
+
- 状态面板给出的 AI client 修复命令现在可以直接执行,不再多带无效参数(FIX-1070)`[cli]`
|
|
11
|
+
|
|
12
|
+
### 可见性
|
|
13
|
+
- `roll loop watch` 不再把旧 cycle 的卡片和 agent 贴到新的空转或重试状态上(FIX-1074)`[loop-observability]`
|
|
14
|
+
|
|
5
15
|
## v4.702.2 — 2026-07-02
|
|
6
16
|
|
|
7
17
|
### Breaking changes
|
package/README.md
CHANGED
|
@@ -53,6 +53,10 @@ waits for owner confirmation before writing. Automation must use
|
|
|
53
53
|
After any init path, `roll next` is the continuation button: it reads the same
|
|
54
54
|
brief, onboard plan, backlog, and Roll markers, then prints one best next
|
|
55
55
|
command instead of a menu.
|
|
56
|
+
When `roll init` writes Roll-owned meta files inside a git worktree, it also
|
|
57
|
+
adds, commits, and pushes those files to `origin` when possible, then prints the
|
|
58
|
+
commit/push result. Product files you created yourself are not included in that
|
|
59
|
+
finalization commit.
|
|
56
60
|
First time through? Start with [Getting started](guide/en/getting-started.md).
|
|
57
61
|
|
|
58
62
|
## Language Surfaces
|
|
@@ -205,7 +209,7 @@ with `roll loop resume` when ready.
|
|
|
205
209
|
| `roll loop <on\|off\|go\|watch\|runs\|cycles\|cycle\|alert\|…>` | Run, observe, and maintain the autonomous executor |
|
|
206
210
|
| `roll next` | Continue init/onboard with one best next command |
|
|
207
211
|
| `roll release [--dry-run\|--showcase]` | Release planning/flow plus golden-path showcase support |
|
|
208
|
-
| `roll setup [skills\|offboard
|
|
212
|
+
| `roll setup [-f\|--force] [--reselect]` / `roll setup skills\|offboard` | Install/sync conventions or remove Roll-owned project artifacts |
|
|
209
213
|
| `roll status [ci\|pulse] [--json]` | Project health, CI state, and delivery pulse |
|
|
210
214
|
| `roll test [--where] [--reset]` | Run tests through the isolation adapter |
|
|
211
215
|
| `roll update` | Upgrade the global Roll install and re-sync conventions |
|
package/dist/roll.mjs
CHANGED
|
@@ -12475,6 +12475,12 @@ function signalLabel(ev) {
|
|
|
12475
12475
|
return `pair ${clean2(ev.peer)} ${ev.cause}`;
|
|
12476
12476
|
case "pair:excluded":
|
|
12477
12477
|
return `pair ${clean2(ev.agent)} excluded ${ev.cause} (${ev.failures})`;
|
|
12478
|
+
case "agent:retry":
|
|
12479
|
+
return `retry ${clean2(ev.fromAgent)} \u2192 ${clean2(ev.toAgent)} (${clean2(ev.reason)})`;
|
|
12480
|
+
case "goal:gate_tripped":
|
|
12481
|
+
return `paused ${clean2(ev.reason)} (${ev.gate})`;
|
|
12482
|
+
case "goal:state":
|
|
12483
|
+
return `goal ${clean2(ev.to)} ${clean2(ev.reason)}`;
|
|
12478
12484
|
default:
|
|
12479
12485
|
return void 0;
|
|
12480
12486
|
}
|
|
@@ -12486,6 +12492,20 @@ function summarizeWatchEvents(lines3, durableLookup, nowMs) {
|
|
|
12486
12492
|
const summary = { tcrCount: 0, hasEnd: false };
|
|
12487
12493
|
const events = [];
|
|
12488
12494
|
let seen = false;
|
|
12495
|
+
const resetCycleScope = (cycleId) => {
|
|
12496
|
+
if (summary.cycleId === cycleId)
|
|
12497
|
+
return;
|
|
12498
|
+
summary.cycleId = cycleId;
|
|
12499
|
+
summary.storyId = void 0;
|
|
12500
|
+
summary.agent = void 0;
|
|
12501
|
+
summary.phase = void 0;
|
|
12502
|
+
summary.tcrCount = 0;
|
|
12503
|
+
summary.outcome = void 0;
|
|
12504
|
+
summary.hasEnd = false;
|
|
12505
|
+
summary.acceptedScore = void 0;
|
|
12506
|
+
summary.attest = void 0;
|
|
12507
|
+
summary.lastPr = void 0;
|
|
12508
|
+
};
|
|
12489
12509
|
for (const line of lines3) {
|
|
12490
12510
|
const ev = parseEventLine(line);
|
|
12491
12511
|
if (ev === null)
|
|
@@ -12493,26 +12513,26 @@ function summarizeWatchEvents(lines3, durableLookup, nowMs) {
|
|
|
12493
12513
|
events.push(ev);
|
|
12494
12514
|
seen = true;
|
|
12495
12515
|
if (ev.type === "cycle:start") {
|
|
12496
|
-
|
|
12516
|
+
resetCycleScope(ev.cycleId);
|
|
12497
12517
|
summary.storyId = ev.storyId;
|
|
12498
12518
|
summary.agent = ev.agent;
|
|
12499
|
-
summary.phase = void 0;
|
|
12500
|
-
summary.tcrCount = 0;
|
|
12501
|
-
summary.outcome = void 0;
|
|
12502
|
-
summary.hasEnd = false;
|
|
12503
|
-
summary.acceptedScore = void 0;
|
|
12504
|
-
summary.attest = void 0;
|
|
12505
|
-
summary.lastPr = void 0;
|
|
12506
12519
|
} else if (ev.type === "cycle:phase") {
|
|
12507
|
-
|
|
12520
|
+
resetCycleScope(ev.cycleId);
|
|
12508
12521
|
summary.phase = ev.phase;
|
|
12509
12522
|
} else if (ev.type === "cycle:tcr") {
|
|
12510
|
-
|
|
12523
|
+
resetCycleScope(ev.cycleId);
|
|
12511
12524
|
summary.tcrCount += 1;
|
|
12512
|
-
} else if (ev.type === "cycle:end"
|
|
12513
|
-
|
|
12525
|
+
} else if (ev.type === "cycle:end") {
|
|
12526
|
+
resetCycleScope(ev.cycleId);
|
|
12527
|
+
summary.outcome = ev.outcome;
|
|
12528
|
+
summary.hasEnd = true;
|
|
12529
|
+
} else if (ev.type === "cycle:terminal") {
|
|
12530
|
+
resetCycleScope(ev.cycleId);
|
|
12531
|
+
summary.storyId = ev.storyId;
|
|
12532
|
+
summary.agent = ev.agent;
|
|
12514
12533
|
summary.outcome = ev.outcome;
|
|
12515
12534
|
summary.hasEnd = true;
|
|
12535
|
+
summary.tcrCount = ev.tcr.present ? ev.tcr.value : 0;
|
|
12516
12536
|
}
|
|
12517
12537
|
if (ev.type === "pair:score") {
|
|
12518
12538
|
summary.acceptedScore = { peer: ev.peer, score: ev.score, verdict: ev.verdict };
|
|
@@ -12584,7 +12604,7 @@ function renderWatchStatusSummary(summary, nowMs) {
|
|
|
12584
12604
|
const story = summary.storyId !== void 0 && summary.storyId !== "" ? summary.storyId : "story unknown";
|
|
12585
12605
|
const agent = summary.agent !== void 0 && summary.agent !== "" ? summary.agent : "agent unknown";
|
|
12586
12606
|
const phase = summary.phase !== void 0 ? `phase ${summary.phase}` : "phase unknown";
|
|
12587
|
-
const classification = summary.classification !== void 0 ? summary.classification : "active";
|
|
12607
|
+
const classification = summary.classification !== void 0 ? summary.classification : summary.hasEnd ? "ended" : "active";
|
|
12588
12608
|
const tcr = `${summary.tcrCount} TCR`;
|
|
12589
12609
|
const parts = [`status ${phase}`, classification, quietText(summary, nowMs), story, agent, cycle];
|
|
12590
12610
|
if (summary.microStep !== void 0) {
|
|
@@ -12656,11 +12676,18 @@ function previousTransitionLine(previous) {
|
|
|
12656
12676
|
return ` previous: ${parts.join(" \xB7 ")}`;
|
|
12657
12677
|
}
|
|
12658
12678
|
function nextTransitionLine(current, ctx) {
|
|
12679
|
+
if (current.storyId === void 0 || current.storyId === "") {
|
|
12680
|
+
const outcome = current.hasEnd ? current.outcome ?? "unknown" : "no active story";
|
|
12681
|
+
return ` next: no runnable work \xB7 ${outcome}`;
|
|
12682
|
+
}
|
|
12659
12683
|
const story = current.storyId !== void 0 && current.storyId !== "" ? current.storyId : "story unknown";
|
|
12660
12684
|
const brief = current.storyId !== void 0 && current.storyId !== "" ? ctx?.storyBrief?.(current.storyId) ?? "brief unavailable" : "brief unavailable";
|
|
12661
12685
|
return ` next: ${story} \xB7 ${brief}`;
|
|
12662
12686
|
}
|
|
12663
12687
|
function builderTransitionLine(current, ctx) {
|
|
12688
|
+
if (current.storyId === void 0 || current.storyId === "") {
|
|
12689
|
+
return " builder: none \xB7 no story/agent selected";
|
|
12690
|
+
}
|
|
12664
12691
|
const route = current.storyId !== void 0 && current.storyId !== "" ? ctx?.routeReason?.(current.storyId) : void 0;
|
|
12665
12692
|
if (route !== void 0) {
|
|
12666
12693
|
return ` builder: ${route.agent} \xB7 selected by ${route.reason}`;
|
|
@@ -18793,16 +18820,16 @@ async function crontabRead() {
|
|
|
18793
18820
|
}
|
|
18794
18821
|
}
|
|
18795
18822
|
async function crontabWrite(text2) {
|
|
18796
|
-
return new Promise((
|
|
18823
|
+
return new Promise((resolve12, reject) => {
|
|
18797
18824
|
const child = execFile4("crontab", ["-"], (err16) => {
|
|
18798
18825
|
if (err16 === null) {
|
|
18799
|
-
|
|
18826
|
+
resolve12(0);
|
|
18800
18827
|
} else if (typeof err16.code === "number") {
|
|
18801
|
-
|
|
18828
|
+
resolve12(err16.code);
|
|
18802
18829
|
} else if (err16.code === "ENOENT") {
|
|
18803
18830
|
reject(err16);
|
|
18804
18831
|
} else {
|
|
18805
|
-
|
|
18832
|
+
resolve12(1);
|
|
18806
18833
|
}
|
|
18807
18834
|
});
|
|
18808
18835
|
child.stdin?.end(text2);
|
|
@@ -20846,14 +20873,14 @@ var init_mcp = __esm({
|
|
|
20846
20873
|
child.on("exit", () => this.rejectAll(new Error("MCP server exited")));
|
|
20847
20874
|
}
|
|
20848
20875
|
static async open(child) {
|
|
20849
|
-
await new Promise((
|
|
20876
|
+
await new Promise((resolve12, reject) => {
|
|
20850
20877
|
const onError = (cause) => {
|
|
20851
20878
|
child.off("spawn", onSpawn);
|
|
20852
20879
|
reject(cause);
|
|
20853
20880
|
};
|
|
20854
20881
|
const onSpawn = () => {
|
|
20855
20882
|
child.off("error", onError);
|
|
20856
|
-
|
|
20883
|
+
resolve12();
|
|
20857
20884
|
};
|
|
20858
20885
|
child.once("error", onError);
|
|
20859
20886
|
child.once("spawn", onSpawn);
|
|
@@ -20882,8 +20909,8 @@ var init_mcp = __esm({
|
|
|
20882
20909
|
const id = this.nextId;
|
|
20883
20910
|
this.nextId += 1;
|
|
20884
20911
|
const message = { jsonrpc: "2.0", id, method, params };
|
|
20885
|
-
return new Promise((
|
|
20886
|
-
this.pending.set(id, { resolve:
|
|
20912
|
+
return new Promise((resolve12, reject) => {
|
|
20913
|
+
this.pending.set(id, { resolve: resolve12, reject });
|
|
20887
20914
|
this.write(message);
|
|
20888
20915
|
});
|
|
20889
20916
|
}
|
|
@@ -20978,7 +21005,7 @@ async function requestWithRedirects(url, input, deps, timeoutMs, redirects, star
|
|
|
20978
21005
|
return response;
|
|
20979
21006
|
}
|
|
20980
21007
|
function requestOnce(url, input, deps, timeoutMs, startedAt) {
|
|
20981
|
-
return new Promise((
|
|
21008
|
+
return new Promise((resolve12, reject) => {
|
|
20982
21009
|
const proxy = proxyFor(url);
|
|
20983
21010
|
const viaProxy = proxy !== void 0;
|
|
20984
21011
|
const requestUrl = viaProxy ? proxy : url;
|
|
@@ -21000,7 +21027,7 @@ function requestOnce(url, input, deps, timeoutMs, startedAt) {
|
|
|
21000
21027
|
});
|
|
21001
21028
|
res.on("end", () => {
|
|
21002
21029
|
clearTimeout(timer);
|
|
21003
|
-
|
|
21030
|
+
resolve12({
|
|
21004
21031
|
statusCode: res.statusCode ?? 0,
|
|
21005
21032
|
headers: normalizeHeaders(res.headers),
|
|
21006
21033
|
body: deps.redact(Buffer.concat(chunks).toString("utf8")),
|
|
@@ -21054,7 +21081,7 @@ function isTimeout(cause) {
|
|
|
21054
21081
|
function delay(ms) {
|
|
21055
21082
|
if (ms <= 0)
|
|
21056
21083
|
return Promise.resolve();
|
|
21057
|
-
return new Promise((
|
|
21084
|
+
return new Promise((resolve12) => setTimeout(resolve12, ms));
|
|
21058
21085
|
}
|
|
21059
21086
|
function failure5(invocation, startedAt, endedAt, code, message, retryable, detail, attempt) {
|
|
21060
21087
|
return {
|
|
@@ -145685,7 +145712,7 @@ ${lanes.join("\n")}
|
|
|
145685
145712
|
}
|
|
145686
145713
|
}
|
|
145687
145714
|
function createImportCallExpressionAMD(arg, containsLexicalThis) {
|
|
145688
|
-
const
|
|
145715
|
+
const resolve12 = factory2.createUniqueName("resolve");
|
|
145689
145716
|
const reject = factory2.createUniqueName("reject");
|
|
145690
145717
|
const parameters = [
|
|
145691
145718
|
factory2.createParameterDeclaration(
|
|
@@ -145694,7 +145721,7 @@ ${lanes.join("\n")}
|
|
|
145694
145721
|
/*dotDotDotToken*/
|
|
145695
145722
|
void 0,
|
|
145696
145723
|
/*name*/
|
|
145697
|
-
|
|
145724
|
+
resolve12
|
|
145698
145725
|
),
|
|
145699
145726
|
factory2.createParameterDeclaration(
|
|
145700
145727
|
/*modifiers*/
|
|
@@ -145711,7 +145738,7 @@ ${lanes.join("\n")}
|
|
|
145711
145738
|
factory2.createIdentifier("require"),
|
|
145712
145739
|
/*typeArguments*/
|
|
145713
145740
|
void 0,
|
|
145714
|
-
[factory2.createArrayLiteralExpression([arg || factory2.createOmittedExpression()]),
|
|
145741
|
+
[factory2.createArrayLiteralExpression([arg || factory2.createOmittedExpression()]), resolve12, reject]
|
|
145715
145742
|
)
|
|
145716
145743
|
)
|
|
145717
145744
|
]);
|
|
@@ -232642,8 +232669,8 @@ Additional information: BADCLIENT: Bad error code, ${badCode} not found in range
|
|
|
232642
232669
|
installPackage(options) {
|
|
232643
232670
|
this.packageInstallId++;
|
|
232644
232671
|
const request = { kind: "installPackage", ...options, id: this.packageInstallId };
|
|
232645
|
-
const promise = new Promise((
|
|
232646
|
-
(this.packageInstalledPromise ?? (this.packageInstalledPromise = /* @__PURE__ */ new Map())).set(this.packageInstallId, { resolve:
|
|
232672
|
+
const promise = new Promise((resolve12, reject) => {
|
|
232673
|
+
(this.packageInstalledPromise ?? (this.packageInstalledPromise = /* @__PURE__ */ new Map())).set(this.packageInstallId, { resolve: resolve12, reject });
|
|
232647
232674
|
});
|
|
232648
232675
|
this.installer.send(request);
|
|
232649
232676
|
return promise;
|
|
@@ -232936,10 +232963,10 @@ __export(structure_scan_exports, {
|
|
|
232936
232963
|
});
|
|
232937
232964
|
import { createHash as createHash5 } from "node:crypto";
|
|
232938
232965
|
import { existsSync as existsSync63, readFileSync as readFileSync59 } from "node:fs";
|
|
232939
|
-
import { basename as basename13, dirname as dirname32, extname, join as join65, relative as relative5, resolve as
|
|
232966
|
+
import { basename as basename13, dirname as dirname32, extname, join as join65, relative as relative5, resolve as resolve7, sep as sep4 } from "node:path";
|
|
232940
232967
|
function buildStaticProjectGraph(input) {
|
|
232941
|
-
const root =
|
|
232942
|
-
const tsconfigPaths = input.tsconfigPath ? [
|
|
232968
|
+
const root = resolve7(input.root);
|
|
232969
|
+
const tsconfigPaths = input.tsconfigPath ? [resolve7(root, input.tsconfigPath)] : findTsconfigs(root);
|
|
232943
232970
|
if (tsconfigPaths.length === 0) {
|
|
232944
232971
|
return emptyGraph(root, "No tsconfig.json found for Dream structure scan");
|
|
232945
232972
|
}
|
|
@@ -233086,7 +233113,7 @@ function createLanguageServiceHost(fileNames, options) {
|
|
|
233086
233113
|
};
|
|
233087
233114
|
}
|
|
233088
233115
|
function normalizePath(root, path) {
|
|
233089
|
-
return relative5(root,
|
|
233116
|
+
return relative5(root, resolve7(path)).split(sep4).join("/");
|
|
233090
233117
|
}
|
|
233091
233118
|
function isExcluded(root, path) {
|
|
233092
233119
|
const rel = normalizePath(root, path);
|
|
@@ -233200,7 +233227,7 @@ function importEdges(root, sourceFile) {
|
|
|
233200
233227
|
function resolveModulePath(root, containingFile, specifier) {
|
|
233201
233228
|
if (!specifier.startsWith("."))
|
|
233202
233229
|
return void 0;
|
|
233203
|
-
const base =
|
|
233230
|
+
const base = resolve7(dirname32(containingFile), specifier);
|
|
233204
233231
|
for (const suffix of [".ts", ".tsx", ".mts", ".cts", "/index.ts"]) {
|
|
233205
233232
|
const candidate = `${base}${suffix}`;
|
|
233206
233233
|
if (existsSync63(candidate))
|
|
@@ -234138,7 +234165,7 @@ function networkNeeds(command, args) {
|
|
|
234138
234165
|
}
|
|
234139
234166
|
}
|
|
234140
234167
|
function tcpConnect(host, port, timeoutMs) {
|
|
234141
|
-
return new Promise((
|
|
234168
|
+
return new Promise((resolve12, reject) => {
|
|
234142
234169
|
const socket = createConnection({ host, port });
|
|
234143
234170
|
let settled = false;
|
|
234144
234171
|
const done = (err16) => {
|
|
@@ -234150,7 +234177,7 @@ function tcpConnect(host, port, timeoutMs) {
|
|
|
234150
234177
|
if (err16)
|
|
234151
234178
|
reject(err16);
|
|
234152
234179
|
else
|
|
234153
|
-
|
|
234180
|
+
resolve12();
|
|
234154
234181
|
};
|
|
234155
234182
|
socket.setTimeout(timeoutMs, () => done(new Error("tcp timeout")));
|
|
234156
234183
|
socket.once("connect", () => done());
|
|
@@ -234162,11 +234189,11 @@ async function networkReachable(probes = {}) {
|
|
|
234162
234189
|
host: PROBE_HOST,
|
|
234163
234190
|
port: PROBE_PORT
|
|
234164
234191
|
};
|
|
234165
|
-
const
|
|
234192
|
+
const resolve12 = probes.resolve ?? ((h) => lookup(h));
|
|
234166
234193
|
const tcpProbe2 = probes.tcpProbe ?? (() => tcpConnect(target.host, target.port, TCP_TIMEOUT_MS));
|
|
234167
234194
|
try {
|
|
234168
234195
|
await Promise.race([
|
|
234169
|
-
|
|
234196
|
+
resolve12(target.host),
|
|
234170
234197
|
new Promise((_, rej) => {
|
|
234171
234198
|
const timer = setTimeout(() => rej(new Error("dns timeout")), DNS_TIMEOUT_MS);
|
|
234172
234199
|
if (typeof timer === "object")
|
|
@@ -234473,7 +234500,7 @@ init_dist2();
|
|
|
234473
234500
|
import { spawn as spawn2 } from "node:child_process";
|
|
234474
234501
|
import { existsSync as existsSync15, readFileSync as readFileSync12, realpathSync as realpathSync2, unlinkSync, writeFileSync as writeFileSync6 } from "node:fs";
|
|
234475
234502
|
import { homedir as homedir4 } from "node:os";
|
|
234476
|
-
import { join as join18 } from "node:path";
|
|
234503
|
+
import { join as join18, resolve as resolve5 } from "node:path";
|
|
234477
234504
|
var liveAgents = /* @__PURE__ */ new Set();
|
|
234478
234505
|
function killLiveAgents(signal = "SIGKILL") {
|
|
234479
234506
|
let n = 0;
|
|
@@ -234799,12 +234826,28 @@ function evidenceFrameEnv(runDir) {
|
|
|
234799
234826
|
};
|
|
234800
234827
|
}
|
|
234801
234828
|
var INHERITED_GIT_ENV_KEYS = ["GIT_DIR", "GIT_WORK_TREE", "GIT_COMMON_DIR", "GIT_INDEX_FILE"];
|
|
234829
|
+
function worktreeGitEnv(cwd) {
|
|
234830
|
+
const gitPath = join18(cwd, ".git");
|
|
234831
|
+
if (!existsSync15(gitPath))
|
|
234832
|
+
return {};
|
|
234833
|
+
try {
|
|
234834
|
+
const text2 = readFileSync12(gitPath, "utf8");
|
|
234835
|
+
const match = /^gitdir:\s*(.+)\s*$/m.exec(text2);
|
|
234836
|
+
if (match?.[1] !== void 0 && match[1].trim() !== "") {
|
|
234837
|
+
return { GIT_DIR: resolve5(cwd, match[1].trim()), GIT_WORK_TREE: cwd };
|
|
234838
|
+
}
|
|
234839
|
+
} catch {
|
|
234840
|
+
return { GIT_DIR: gitPath, GIT_WORK_TREE: cwd };
|
|
234841
|
+
}
|
|
234842
|
+
return {};
|
|
234843
|
+
}
|
|
234802
234844
|
function childEnv(opts) {
|
|
234803
234845
|
const env = { ...opts.env ?? process.env };
|
|
234804
234846
|
for (const key of INHERITED_GIT_ENV_KEYS)
|
|
234805
234847
|
delete env[key];
|
|
234806
234848
|
env.PWD = opts.cwd;
|
|
234807
234849
|
delete env.OLDPWD;
|
|
234850
|
+
Object.assign(env, worktreeGitEnv(opts.cwd));
|
|
234808
234851
|
return opts.runDir !== void 0 && opts.runDir !== "" ? { ...env, ...evidenceFrameEnv(opts.runDir) } : env;
|
|
234809
234852
|
}
|
|
234810
234853
|
function withAgentProfileEnv(agent, opts) {
|
|
@@ -234820,7 +234863,7 @@ function withAgentProfileEnv(agent, opts) {
|
|
|
234820
234863
|
function spawnAndWait(bin, args, opts, pty = false) {
|
|
234821
234864
|
process.stderr.write(`[runner] spawn ${bin} argv=${JSON.stringify(args.map((a) => a.length > 80 ? `${a.slice(0, 77)}...` : a))}
|
|
234822
234865
|
`);
|
|
234823
|
-
return new Promise((
|
|
234866
|
+
return new Promise((resolve12) => {
|
|
234824
234867
|
const child = spawn2(bin, args, {
|
|
234825
234868
|
cwd: opts.cwd,
|
|
234826
234869
|
env: childEnv(opts),
|
|
@@ -234863,7 +234906,7 @@ function spawnAndWait(bin, args, opts, pty = false) {
|
|
|
234863
234906
|
opts.cleanup?.();
|
|
234864
234907
|
} catch {
|
|
234865
234908
|
}
|
|
234866
|
-
|
|
234909
|
+
resolve12(result);
|
|
234867
234910
|
};
|
|
234868
234911
|
child.on("error", (e) => {
|
|
234869
234912
|
settle({ stdout, stderr: `${stderr}${String(e)}
|
|
@@ -234890,13 +234933,13 @@ function realDeps() {
|
|
|
234890
234933
|
sharedRoot: () => process.env["ROLL_SHARED_ROOT"] || join19(homedir5(), ".shared", "roll"),
|
|
234891
234934
|
launchdDir: () => join19(homedir5(), "Library", "LaunchAgents"),
|
|
234892
234935
|
scheduler: createScheduler(process.platform, { uid: process.getuid?.() ?? 501 }),
|
|
234893
|
-
execRunner: (runner) => new Promise((
|
|
234936
|
+
execRunner: (runner) => new Promise((resolve12) => {
|
|
234894
234937
|
const child = spawn3("bash", [runner], {
|
|
234895
234938
|
stdio: "inherit",
|
|
234896
234939
|
env: { ...process.env, ROLL_LOOP_FORCE: "1" }
|
|
234897
234940
|
});
|
|
234898
|
-
child.on("exit", (code) =>
|
|
234899
|
-
child.on("error", () =>
|
|
234941
|
+
child.on("exit", (code) => resolve12(code ?? 1));
|
|
234942
|
+
child.on("error", () => resolve12(1));
|
|
234900
234943
|
}),
|
|
234901
234944
|
hasTmux: () => {
|
|
234902
234945
|
try {
|
|
@@ -234907,7 +234950,7 @@ function realDeps() {
|
|
|
234907
234950
|
},
|
|
234908
234951
|
// The `loop now` inline observation: tail live.log while the cycle holds
|
|
234909
234952
|
// the inner lock; Ctrl-C stops the TAIL only (the cycle lives in tmux).
|
|
234910
|
-
observe: (rt) => new Promise((
|
|
234953
|
+
observe: (rt) => new Promise((resolve12) => {
|
|
234911
234954
|
const lock = join19(rt, "inner.lock");
|
|
234912
234955
|
const tail3 = spawn3("tail", ["-n", "+1", "-F", join19(rt, "live.log")], { stdio: "inherit" });
|
|
234913
234956
|
let sawLock = false;
|
|
@@ -234918,7 +234961,7 @@ function realDeps() {
|
|
|
234918
234961
|
} catch {
|
|
234919
234962
|
}
|
|
234920
234963
|
process.removeListener("SIGINT", finish);
|
|
234921
|
-
|
|
234964
|
+
resolve12();
|
|
234922
234965
|
};
|
|
234923
234966
|
const timer = setInterval(() => {
|
|
234924
234967
|
if (existsSync16(lock))
|
|
@@ -249779,13 +249822,13 @@ function followSupervisorCollabStream(projectPath3, noColor2) {
|
|
|
249779
249822
|
const tickLimit = envOptionalPositiveInt("ROLL_SUPERVISOR_COLLAB_WATCH_TICKS");
|
|
249780
249823
|
let renderedCycles = 0;
|
|
249781
249824
|
let ticks = 0;
|
|
249782
|
-
return new Promise((
|
|
249825
|
+
return new Promise((resolve12) => {
|
|
249783
249826
|
let timer;
|
|
249784
249827
|
const stop = (code) => {
|
|
249785
249828
|
if (timer !== void 0)
|
|
249786
249829
|
clearInterval(timer);
|
|
249787
249830
|
process.removeListener("SIGINT", onSigint);
|
|
249788
|
-
|
|
249831
|
+
resolve12(code);
|
|
249789
249832
|
};
|
|
249790
249833
|
const onSigint = () => stop(130);
|
|
249791
249834
|
const tick = () => {
|
|
@@ -249876,7 +249919,7 @@ function unknownSupervisorLiveFlag(args) {
|
|
|
249876
249919
|
function followSupervisorLiveBoard(projectPath3, intervalMs) {
|
|
249877
249920
|
const tickLimit = envOptionalPositiveInt("ROLL_SUPERVISOR_LIVE_WATCH_TICKS");
|
|
249878
249921
|
let ticks = 0;
|
|
249879
|
-
return new Promise((
|
|
249922
|
+
return new Promise((resolve12) => {
|
|
249880
249923
|
let timer;
|
|
249881
249924
|
let stopped = false;
|
|
249882
249925
|
const stop = (code) => {
|
|
@@ -249887,7 +249930,7 @@ function followSupervisorLiveBoard(projectPath3, intervalMs) {
|
|
|
249887
249930
|
clearInterval(timer);
|
|
249888
249931
|
process.removeListener("SIGINT", onSigint);
|
|
249889
249932
|
process.stdout.write("\x1B[?25h");
|
|
249890
|
-
|
|
249933
|
+
resolve12(code);
|
|
249891
249934
|
};
|
|
249892
249935
|
const onSigint = () => stop(130);
|
|
249893
249936
|
const tick = () => {
|
|
@@ -251053,7 +251096,7 @@ function spawnPeerReviewAgent(input) {
|
|
|
251053
251096
|
const cmd = textAgentArgv(input.agent, input.prompt);
|
|
251054
251097
|
if (cmd === null)
|
|
251055
251098
|
return Promise.resolve({ status: "error", reason: "unsupported_reviewer", stdout: "" });
|
|
251056
|
-
return new Promise((
|
|
251099
|
+
return new Promise((resolve12) => {
|
|
251057
251100
|
let stdout = "";
|
|
251058
251101
|
let stderr = "";
|
|
251059
251102
|
let timedOut = false;
|
|
@@ -251071,7 +251114,7 @@ function spawnPeerReviewAgent(input) {
|
|
|
251071
251114
|
settled = true;
|
|
251072
251115
|
if (timer !== void 0)
|
|
251073
251116
|
clearTimeout(timer);
|
|
251074
|
-
|
|
251117
|
+
resolve12(result);
|
|
251075
251118
|
};
|
|
251076
251119
|
timer = setTimeout(() => {
|
|
251077
251120
|
timedOut = true;
|
|
@@ -251681,7 +251724,7 @@ function realDeps5() {
|
|
|
251681
251724
|
startTmux: startGoTmux,
|
|
251682
251725
|
followFeed: followGoLiveFeed,
|
|
251683
251726
|
runOnce: realRunOnce,
|
|
251684
|
-
sleep: (ms) => new Promise((
|
|
251727
|
+
sleep: (ms) => new Promise((resolve12) => setTimeout(resolve12, ms)),
|
|
251685
251728
|
externalTools: guideExternalToolSetup,
|
|
251686
251729
|
preinstallChromium: () => {
|
|
251687
251730
|
silentPreinstallChromium();
|
|
@@ -251726,6 +251769,59 @@ function eventsPath(projectPath3) {
|
|
|
251726
251769
|
function runsPath2(projectPath3) {
|
|
251727
251770
|
return join55(runtimeDir5(projectPath3), "runs.jsonl");
|
|
251728
251771
|
}
|
|
251772
|
+
function parsePorcelainPath(line) {
|
|
251773
|
+
const raw = line.length > 3 ? line.slice(3).trim() : line.trim();
|
|
251774
|
+
const target = raw.includes(" -> ") ? raw.split(" -> ").at(-1) ?? raw : raw;
|
|
251775
|
+
return target.replace(/^"|"$/g, "").replace(/\/$/, "");
|
|
251776
|
+
}
|
|
251777
|
+
function gitDirtyPaths(projectPath3) {
|
|
251778
|
+
const result = spawnSync8("git", ["status", "--porcelain", "--untracked-files=all"], {
|
|
251779
|
+
cwd: projectPath3,
|
|
251780
|
+
encoding: "utf8",
|
|
251781
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
251782
|
+
});
|
|
251783
|
+
if (result.status !== 0)
|
|
251784
|
+
return [];
|
|
251785
|
+
return String(result.stdout ?? "").split("\n").map((line) => line.trimEnd()).filter((line) => line.trim() !== "").map(parsePorcelainPath).filter((path) => path !== ".roll/loop" && !path.startsWith(".roll/loop/")).slice(0, 50);
|
|
251786
|
+
}
|
|
251787
|
+
function isBootstrapArtifactPath(path) {
|
|
251788
|
+
if (path === "AGENTS.md" || path === "CLAUDE.md" || path === ".cursor-rules" || path === "project_rules.md")
|
|
251789
|
+
return true;
|
|
251790
|
+
if (path === ".roll" || path.startsWith(".roll/"))
|
|
251791
|
+
return true;
|
|
251792
|
+
if (path === ".claude" || path.startsWith(".claude/"))
|
|
251793
|
+
return true;
|
|
251794
|
+
return false;
|
|
251795
|
+
}
|
|
251796
|
+
function classifyBootstrapArtifacts(paths) {
|
|
251797
|
+
const files = paths.filter((path) => path.trim() !== "");
|
|
251798
|
+
if (files.length === 0)
|
|
251799
|
+
return { kind: "none", files: [] };
|
|
251800
|
+
return files.every(isBootstrapArtifactPath) ? { kind: "bootstrap_only", files } : { kind: "mixed", files };
|
|
251801
|
+
}
|
|
251802
|
+
function bootstrapArtifactsMessage(files) {
|
|
251803
|
+
const shown = files.slice(0, 12).join(", ");
|
|
251804
|
+
const more = files.length > 12 ? `, ... +${files.length - 12} more` : "";
|
|
251805
|
+
return [
|
|
251806
|
+
"roll loop go: bootstrap_artifacts_unconfirmed",
|
|
251807
|
+
` files: ${shown}${more}`,
|
|
251808
|
+
" These files define project conventions/backlog metadata. Confirm their ownership before running builders:",
|
|
251809
|
+
" - commit them to the product repo if they are product truth",
|
|
251810
|
+
" - commit private Roll metadata inside .roll/roll-meta when applicable",
|
|
251811
|
+
" - ignore/externalize them by project policy",
|
|
251812
|
+
" - or clean them up and re-run init/design",
|
|
251813
|
+
" Then rerun: roll loop go",
|
|
251814
|
+
"",
|
|
251815
|
+
"roll loop go: bootstrap_artifacts_unconfirmed",
|
|
251816
|
+
" \u8FD9\u4E9B\u6587\u4EF6\u5B9A\u4E49\u9879\u76EE\u7EA6\u5B9A/backlog \u5143\u6570\u636E\u3002\u5148\u786E\u8BA4\u5F52\u5C5E\uFF0C\u518D\u542F\u52A8 builder\uFF1A",
|
|
251817
|
+
" - \u5C5E\u4E8E\u4EA7\u54C1\u4E8B\u5B9E\u5C31\u63D0\u4EA4\u5230\u4EA7\u54C1\u4ED3",
|
|
251818
|
+
" - \u5C5E\u4E8E\u79C1\u6709 Roll \u5143\u6570\u636E\u5C31\u63D0\u4EA4\u5230 .roll/roll-meta",
|
|
251819
|
+
" - \u6309\u9879\u76EE\u7EA6\u5B9A ignore/\u5916\u7F6E",
|
|
251820
|
+
" - \u6216\u6E05\u7406\u540E\u91CD\u8DD1 init/design",
|
|
251821
|
+
" \u7136\u540E\u518D\u8FD0\u884C\uFF1Aroll loop go",
|
|
251822
|
+
""
|
|
251823
|
+
].join("\n");
|
|
251824
|
+
}
|
|
251729
251825
|
function parseOptions(args) {
|
|
251730
251826
|
let scope = { kind: "all" };
|
|
251731
251827
|
let maxCycles;
|
|
@@ -251958,7 +252054,7 @@ function startGoTmux(input) {
|
|
|
251958
252054
|
}
|
|
251959
252055
|
}
|
|
251960
252056
|
function followGoLiveFeed(projectPath3, rollBin4) {
|
|
251961
|
-
return new Promise((
|
|
252057
|
+
return new Promise((resolve12) => {
|
|
251962
252058
|
const cmd = rollBin4.endsWith(".js") || rollBin4.endsWith(".mjs") ? process.execPath : rollBin4;
|
|
251963
252059
|
const args = rollBin4.endsWith(".js") || rollBin4.endsWith(".mjs") ? [rollBin4, "loop", "watch", "--since", "all"] : ["loop", "watch", "--since", "all"];
|
|
251964
252060
|
const watch = spawn7(cmd, args, { cwd: projectPath3, stdio: ["ignore", "inherit", "inherit"] });
|
|
@@ -251968,7 +252064,7 @@ function followGoLiveFeed(projectPath3, rollBin4) {
|
|
|
251968
252064
|
} catch {
|
|
251969
252065
|
}
|
|
251970
252066
|
process.removeListener("SIGINT", finish);
|
|
251971
|
-
|
|
252067
|
+
resolve12();
|
|
251972
252068
|
};
|
|
251973
252069
|
process.on("SIGINT", finish);
|
|
251974
252070
|
watch.on("exit", finish);
|
|
@@ -251979,7 +252075,7 @@ function realRunOnce(input) {
|
|
|
251979
252075
|
const bin = rollBin();
|
|
251980
252076
|
const cmd = bin.endsWith(".js") || bin.endsWith(".mjs") ? process.execPath : bin;
|
|
251981
252077
|
const args = cmd === process.execPath ? [bin, "loop", "run-once"] : ["loop", "run-once"];
|
|
251982
|
-
return new Promise((
|
|
252078
|
+
return new Promise((resolve12) => {
|
|
251983
252079
|
const child = spawn7(cmd, args, {
|
|
251984
252080
|
cwd: input.projectPath,
|
|
251985
252081
|
detached: true,
|
|
@@ -251993,8 +252089,8 @@ function realRunOnce(input) {
|
|
|
251993
252089
|
},
|
|
251994
252090
|
stdio: "inherit"
|
|
251995
252091
|
});
|
|
251996
|
-
child.on("exit", (code) =>
|
|
251997
|
-
child.on("error", () =>
|
|
252092
|
+
child.on("exit", (code) => resolve12(code ?? 1));
|
|
252093
|
+
child.on("error", () => resolve12(1));
|
|
251998
252094
|
});
|
|
251999
252095
|
}
|
|
252000
252096
|
function writeGoal(path, goal) {
|
|
@@ -252822,6 +252918,14 @@ async function runGoWorker(id, opts, deps) {
|
|
|
252822
252918
|
stopReason2 = preProgressGate.reason;
|
|
252823
252919
|
break;
|
|
252824
252920
|
}
|
|
252921
|
+
const bootstrap = classifyBootstrapArtifacts(gitDirtyPaths(id.path));
|
|
252922
|
+
if (bootstrap.kind === "bootstrap_only") {
|
|
252923
|
+
stopReason2 = "bootstrap_artifacts_unconfirmed";
|
|
252924
|
+
appendGoalGate(bus, evPath, sid, "progress", "paused", stopReason2, { files: bootstrap.files.join(", "), count: bootstrap.files.length }, deps.nowSec());
|
|
252925
|
+
process.stdout.write(bootstrapArtifactsMessage(bootstrap.files));
|
|
252926
|
+
goal = pauseGoal(id.path, bus, stopReason2, deps.nowIso(), deps.nowSec()) ?? goal;
|
|
252927
|
+
break;
|
|
252928
|
+
}
|
|
252825
252929
|
const allowedCards = allowedCardsForScope(id.path, goal, progress);
|
|
252826
252930
|
if (allScopeCardsSkipped(id.path, goal, progress)) {
|
|
252827
252931
|
stopReason2 = "no_progress_on_all_cards";
|
|
@@ -253975,7 +254079,7 @@ function renderDesignNudge(lang10) {
|
|
|
253975
254079
|
init_dist2();
|
|
253976
254080
|
init_dist();
|
|
253977
254081
|
import { existsSync as existsSync59, readdirSync as readdirSync24, readFileSync as readFileSync56, statSync as statSync22 } from "node:fs";
|
|
253978
|
-
import { relative as relative4, resolve as
|
|
254082
|
+
import { relative as relative4, resolve as resolve6 } from "node:path";
|
|
253979
254083
|
var IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
253980
254084
|
".git",
|
|
253981
254085
|
"node_modules",
|
|
@@ -253997,23 +254101,23 @@ function walkMarkdownFiles(dir, out3, includeGenerated) {
|
|
|
253997
254101
|
continue;
|
|
253998
254102
|
if (!includeGenerated && (entry.name === ".roll" || entry.name === "archive"))
|
|
253999
254103
|
continue;
|
|
254000
|
-
walkMarkdownFiles(
|
|
254104
|
+
walkMarkdownFiles(resolve6(dir, entry.name), out3, includeGenerated);
|
|
254001
254105
|
} else if (entry.isFile() && isMarkdownFile(entry.name)) {
|
|
254002
|
-
out3.push(
|
|
254106
|
+
out3.push(resolve6(dir, entry.name));
|
|
254003
254107
|
}
|
|
254004
254108
|
}
|
|
254005
254109
|
}
|
|
254006
254110
|
function defaultScanRoots(root, includeGenerated) {
|
|
254007
254111
|
const roots = [];
|
|
254008
254112
|
for (const rel of ["AGENTS.md", "roll.md", "conventions", "skills", "guide"]) {
|
|
254009
|
-
const abs =
|
|
254113
|
+
const abs = resolve6(root, rel);
|
|
254010
254114
|
if (!existsSync59(abs))
|
|
254011
254115
|
continue;
|
|
254012
254116
|
roots.push(abs);
|
|
254013
254117
|
}
|
|
254014
254118
|
if (includeGenerated) {
|
|
254015
254119
|
for (const rel of [".roll", "archive"]) {
|
|
254016
|
-
const abs =
|
|
254120
|
+
const abs = resolve6(root, rel);
|
|
254017
254121
|
if (existsSync59(abs))
|
|
254018
254122
|
roots.push(abs);
|
|
254019
254123
|
}
|
|
@@ -256854,7 +256958,7 @@ function renderAiClients(out3, clients) {
|
|
|
256854
256958
|
}
|
|
256855
256959
|
out3.push(" " + pad(nameCol, 14) + pad(c("dim", cl.cfg_file), 14) + pad(syncCol, 14) + c("dim", String(cl.skills)));
|
|
256856
256960
|
if (cl.sync === "out-of-sync" || cl.sync === "missing") {
|
|
256857
|
-
out3.push(" " + c("dim", "fix: ") + c("blue",
|
|
256961
|
+
out3.push(" " + c("dim", "fix: ") + c("blue", "roll setup -f"));
|
|
256858
256962
|
}
|
|
256859
256963
|
}
|
|
256860
256964
|
out3.push("", hr(), "");
|
|
@@ -257180,7 +257284,7 @@ function collectAgentPanel(projectPath3, deps = defaultAgentPanelDeps()) {
|
|
|
257180
257284
|
costUsd72h: Number(s.cost.toFixed(4)),
|
|
257181
257285
|
files,
|
|
257182
257286
|
syncStale: isInstalled && stale,
|
|
257183
|
-
...isInstalled && stale ? { setupCmd:
|
|
257287
|
+
...isInstalled && stale ? { setupCmd: "roll setup -f" } : {}
|
|
257184
257288
|
});
|
|
257185
257289
|
}
|
|
257186
257290
|
rows.sort((a, b) => Number(b.installed) - Number(a.installed) || b.cycles72h - a.cycles72h || a.name.localeCompare(b.name));
|
|
@@ -258049,7 +258153,7 @@ function collectLoopHeartbeat(deps) {
|
|
|
258049
258153
|
// packages/cli/dist/lib/git-hooks.js
|
|
258050
258154
|
import { execFileSync as execFileSync20 } from "node:child_process";
|
|
258051
258155
|
import { readdirSync as readdirSync29 } from "node:fs";
|
|
258052
|
-
import { isAbsolute, join as join77, resolve as
|
|
258156
|
+
import { isAbsolute, join as join77, resolve as resolve8 } from "node:path";
|
|
258053
258157
|
var HOOK_DESCRIPTIONS = {
|
|
258054
258158
|
"pre-commit": {
|
|
258055
258159
|
en: "TCR proof gate before commit",
|
|
@@ -258111,11 +258215,11 @@ function resolveHooksPath(projectPath3) {
|
|
|
258111
258215
|
if (configured !== "" && configured !== ".git/hooks") {
|
|
258112
258216
|
return {
|
|
258113
258217
|
displayPath: configured,
|
|
258114
|
-
fsPath: isAbsolute(configured) ? configured :
|
|
258218
|
+
fsPath: isAbsolute(configured) ? configured : resolve8(projectPath3, configured)
|
|
258115
258219
|
};
|
|
258116
258220
|
}
|
|
258117
258221
|
const gitPath = gitOutput3(projectPath3, ["rev-parse", "--git-path", "hooks"]);
|
|
258118
|
-
const fsPath = gitPath === "" ? join77(projectPath3, ".git", "hooks") : isAbsolute(gitPath) ? gitPath :
|
|
258222
|
+
const fsPath = gitPath === "" ? join77(projectPath3, ".git", "hooks") : isAbsolute(gitPath) ? gitPath : resolve8(projectPath3, gitPath);
|
|
258119
258223
|
return {
|
|
258120
258224
|
displayPath: configured === "" ? ".git/hooks" : configured,
|
|
258121
258225
|
fsPath
|
|
@@ -260053,6 +260157,91 @@ function registerProject(projectDir) {
|
|
|
260053
260157
|
} catch {
|
|
260054
260158
|
}
|
|
260055
260159
|
}
|
|
260160
|
+
var ROLL_OWNED_GIT_PATHS = [
|
|
260161
|
+
"AGENTS.md",
|
|
260162
|
+
".claude/CLAUDE.md",
|
|
260163
|
+
".roll/.version",
|
|
260164
|
+
".roll/agents.yaml",
|
|
260165
|
+
".roll/backlog.md",
|
|
260166
|
+
".roll/brief.md",
|
|
260167
|
+
".roll/briefs",
|
|
260168
|
+
".roll/domain",
|
|
260169
|
+
".roll/domain/context-map.md",
|
|
260170
|
+
".roll/features",
|
|
260171
|
+
".roll/features.md",
|
|
260172
|
+
".roll/init-diagnosis.yaml",
|
|
260173
|
+
".roll/onboard-changeset.yaml",
|
|
260174
|
+
".roll/onboard-plan.yaml",
|
|
260175
|
+
".roll/tech-analysis.md",
|
|
260176
|
+
".roll/test-assessment.md",
|
|
260177
|
+
".gitignore"
|
|
260178
|
+
];
|
|
260179
|
+
function runGit(projectDir, args) {
|
|
260180
|
+
const result = spawnSync11("git", args, {
|
|
260181
|
+
cwd: projectDir,
|
|
260182
|
+
encoding: "utf8",
|
|
260183
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
260184
|
+
});
|
|
260185
|
+
return {
|
|
260186
|
+
ok: result.status === 0,
|
|
260187
|
+
stdout: String(result.stdout ?? ""),
|
|
260188
|
+
stderr: String(result.stderr ?? "")
|
|
260189
|
+
};
|
|
260190
|
+
}
|
|
260191
|
+
function existingRollOwnedPaths(projectDir) {
|
|
260192
|
+
return ROLL_OWNED_GIT_PATHS.filter((rel) => existsSync83(join86(projectDir, rel.replace(/\/$/, ""))));
|
|
260193
|
+
}
|
|
260194
|
+
function printGitFinalizeManual(reason, command) {
|
|
260195
|
+
warn4(`Roll meta git finalize needs manual follow-up: ${reason}`);
|
|
260196
|
+
process.stderr.write(` ${command}
|
|
260197
|
+
`);
|
|
260198
|
+
}
|
|
260199
|
+
function finalizeRollOwnedGit(projectDir) {
|
|
260200
|
+
if (!runGit(projectDir, ["rev-parse", "--is-inside-work-tree"]).ok)
|
|
260201
|
+
return;
|
|
260202
|
+
const topLevel = runGit(projectDir, ["rev-parse", "--show-toplevel"]);
|
|
260203
|
+
if (!topLevel.ok)
|
|
260204
|
+
return;
|
|
260205
|
+
if (realpathSync11(projectDir) !== realpathSync11(topLevel.stdout.trim()))
|
|
260206
|
+
return;
|
|
260207
|
+
const paths = existingRollOwnedPaths(projectDir);
|
|
260208
|
+
if (paths.length === 0)
|
|
260209
|
+
return;
|
|
260210
|
+
if (!runGit(projectDir, ["diff", "--cached", "--quiet"]).ok) {
|
|
260211
|
+
printGitFinalizeManual("existing staged changes; auto commit skipped", `git add -A -f -- ${paths.join(" ")} && git commit -m 'roll init: commit Roll-owned meta files'`);
|
|
260212
|
+
return;
|
|
260213
|
+
}
|
|
260214
|
+
const add = runGit(projectDir, ["add", "-A", "-f", "--", ...paths]);
|
|
260215
|
+
if (!add.ok) {
|
|
260216
|
+
printGitFinalizeManual("git add failed", `git add -A -f -- ${paths.join(" ")}`);
|
|
260217
|
+
return;
|
|
260218
|
+
}
|
|
260219
|
+
if (runGit(projectDir, ["diff", "--cached", "--quiet", "--", ...paths]).ok)
|
|
260220
|
+
return;
|
|
260221
|
+
const commit2 = runGit(projectDir, ["commit", "-m", "roll init: commit Roll-owned meta files"]);
|
|
260222
|
+
if (!commit2.ok) {
|
|
260223
|
+
printGitFinalizeManual("git commit failed", "git commit -m 'roll init: commit Roll-owned meta files' -- AGENTS.md .roll .claude .gitignore");
|
|
260224
|
+
return;
|
|
260225
|
+
}
|
|
260226
|
+
const sha = runGit(projectDir, ["rev-parse", "--short", "HEAD"]).stdout.trim();
|
|
260227
|
+
ok10(`Roll meta committed${sha !== "" ? `: ${sha}` : ""}`);
|
|
260228
|
+
const branch = runGit(projectDir, ["rev-parse", "--abbrev-ref", "HEAD"]).stdout.trim();
|
|
260229
|
+
if (branch === "" || branch === "HEAD") {
|
|
260230
|
+
printGitFinalizeManual("detached HEAD; push skipped", "git push origin HEAD");
|
|
260231
|
+
return;
|
|
260232
|
+
}
|
|
260233
|
+
const remotes = runGit(projectDir, ["remote"]).stdout.split("\n").filter((line) => line.trim() !== "");
|
|
260234
|
+
if (!remotes.includes("origin")) {
|
|
260235
|
+
printGitFinalizeManual("origin remote is not configured; push skipped", `git push -u origin ${branch}`);
|
|
260236
|
+
return;
|
|
260237
|
+
}
|
|
260238
|
+
const push2 = runGit(projectDir, ["push", "-u", "origin", branch]);
|
|
260239
|
+
if (!push2.ok) {
|
|
260240
|
+
printGitFinalizeManual("git push failed", `git push -u origin ${branch}`);
|
|
260241
|
+
return;
|
|
260242
|
+
}
|
|
260243
|
+
ok10(`Roll meta pushed: origin/${branch}`);
|
|
260244
|
+
}
|
|
260056
260245
|
function err9(line) {
|
|
260057
260246
|
const { RED, NC } = pal4();
|
|
260058
260247
|
process.stderr.write(`${RED}[roll]${NC} ${line}
|
|
@@ -260914,6 +261103,7 @@ function initRepair(projectDir, facts, diagnosis2, opts = {}) {
|
|
|
260914
261103
|
recordCreatedFileIfNeeded(projectDir, changeset, ".roll/.version", stampExisted);
|
|
260915
261104
|
printMergeSummary(summary);
|
|
260916
261105
|
ok10("Repair complete.");
|
|
261106
|
+
finalizeRollOwnedGit(projectDir);
|
|
260917
261107
|
return 0;
|
|
260918
261108
|
}
|
|
260919
261109
|
function initApply(projectDir, opts = {}) {
|
|
@@ -261029,6 +261219,7 @@ function initApply(projectDir, opts = {}) {
|
|
|
261029
261219
|
registerProject(projectDir);
|
|
261030
261220
|
process.stdout.write("\n");
|
|
261031
261221
|
ok10(m3("init.onboard_apply_complete_onboard"));
|
|
261222
|
+
finalizeRollOwnedGit(projectDir);
|
|
261032
261223
|
return 0;
|
|
261033
261224
|
} catch (error) {
|
|
261034
261225
|
err9(m32("init.onboard_apply_failed"));
|
|
@@ -261859,6 +262050,7 @@ function initCommand(args, deps = {}) {
|
|
|
261859
262050
|
registerProject(projectDir);
|
|
261860
262051
|
const shouldNudge = detectDesignHandoff(projectDir).shouldNudge;
|
|
261861
262052
|
emitInitUi(projectDir, hasAgents, syncStatus, summary, shouldNudge, repairMode ? "repair" : void 0, conciergeNextItems(brief, initDiagnosis));
|
|
262053
|
+
finalizeRollOwnedGit(projectDir);
|
|
261862
262054
|
void err9;
|
|
261863
262055
|
return 0;
|
|
261864
262056
|
}
|
|
@@ -262057,7 +262249,7 @@ init_dist();
|
|
|
262057
262249
|
init_dist2();
|
|
262058
262250
|
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
262059
262251
|
import { existsSync as existsSync85, mkdirSync as mkdirSync37, readFileSync as readFileSync78, renameSync as renameSync16, statSync as statSync31, writeFileSync as writeFileSync39 } from "node:fs";
|
|
262060
|
-
import { dirname as dirname39, join as join88, relative as relative6, resolve as
|
|
262252
|
+
import { dirname as dirname39, join as join88, relative as relative6, resolve as resolve9 } from "node:path";
|
|
262061
262253
|
|
|
262062
262254
|
// packages/cli/dist/lib/review-page.js
|
|
262063
262255
|
init_dist2();
|
|
@@ -262466,7 +262658,7 @@ function renderDesignReviewPageForTarget(ctx, cardsCreated) {
|
|
|
262466
262658
|
return;
|
|
262467
262659
|
const base = join88(".roll", "features", epic, ctx.target);
|
|
262468
262660
|
const md = join88(base, "spec.md");
|
|
262469
|
-
const absMd =
|
|
262661
|
+
const absMd = resolve9(ctx.cwd, md);
|
|
262470
262662
|
if (!existsSync85(absMd) || !hasDetailedDesign(absMd))
|
|
262471
262663
|
return;
|
|
262472
262664
|
const markdown = readFileSync78(absMd, "utf8");
|
|
@@ -262481,7 +262673,7 @@ function renderDesignReviewPageForTarget(ctx, cardsCreated) {
|
|
|
262481
262673
|
markdown,
|
|
262482
262674
|
lang: ctx.lang
|
|
262483
262675
|
});
|
|
262484
|
-
const out3 =
|
|
262676
|
+
const out3 = resolve9(ctx.cwd, base, "design-review.html");
|
|
262485
262677
|
mkdirSync37(dirname39(out3), { recursive: true });
|
|
262486
262678
|
const tmp = `${out3}.tmp`;
|
|
262487
262679
|
writeFileSync39(tmp, html, "utf8");
|
|
@@ -262594,12 +262786,12 @@ function printHandoff(ctx, statusCode, rawTranscript) {
|
|
|
262594
262786
|
const md = join88(base, "spec.md");
|
|
262595
262787
|
const reviewHtml = join88(base, "design-review.html");
|
|
262596
262788
|
const html = join88(base, "spec.html");
|
|
262597
|
-
if (existsSync85(
|
|
262598
|
-
designPath = hasDetailedDesign(
|
|
262789
|
+
if (existsSync85(resolve9(ctx.cwd, md))) {
|
|
262790
|
+
designPath = hasDetailedDesign(resolve9(ctx.cwd, md)) ? `${md}#detailed-design` : md;
|
|
262599
262791
|
}
|
|
262600
|
-
if (existsSync85(
|
|
262792
|
+
if (existsSync85(resolve9(ctx.cwd, reviewHtml)))
|
|
262601
262793
|
reviewPagePath = reviewHtml;
|
|
262602
|
-
else if (existsSync85(
|
|
262794
|
+
else if (existsSync85(resolve9(ctx.cwd, html)))
|
|
262603
262795
|
reviewPagePath = html;
|
|
262604
262796
|
}
|
|
262605
262797
|
let status2;
|
|
@@ -262664,7 +262856,7 @@ function designCommand(args, deps = {}) {
|
|
|
262664
262856
|
emit4(t(v3Catalog, l, "design.not_roll_project"));
|
|
262665
262857
|
return 1;
|
|
262666
262858
|
}
|
|
262667
|
-
if (fromFile !== void 0 && !isRegularFile(
|
|
262859
|
+
if (fromFile !== void 0 && !isRegularFile(resolve9(d.cwd, fromFile))) {
|
|
262668
262860
|
emit4(t(v3Catalog, l, "design.from_file_not_found", fromFile));
|
|
262669
262861
|
return 1;
|
|
262670
262862
|
}
|
|
@@ -263734,8 +263926,8 @@ async function probeAgentReachable(agent, spawn10, opts = {}) {
|
|
|
263734
263926
|
try {
|
|
263735
263927
|
res = await Promise.race([
|
|
263736
263928
|
spawn10(agent, { cwd, skillBody: `Reply with exactly: ${token}`, timeoutMs, bare: true }),
|
|
263737
|
-
new Promise((
|
|
263738
|
-
setTimeout(() =>
|
|
263929
|
+
new Promise((resolve12) => {
|
|
263930
|
+
setTimeout(() => resolve12(null), timeoutMs).unref();
|
|
263739
263931
|
})
|
|
263740
263932
|
]);
|
|
263741
263933
|
} catch (e) {
|
|
@@ -264621,7 +264813,7 @@ function reviewTimeoutMs(diffChars) {
|
|
|
264621
264813
|
async function firstValid(promises) {
|
|
264622
264814
|
if (promises.length === 0)
|
|
264623
264815
|
return null;
|
|
264624
|
-
return new Promise((
|
|
264816
|
+
return new Promise((resolve12, reject) => {
|
|
264625
264817
|
let pending = promises.length;
|
|
264626
264818
|
let settled = false;
|
|
264627
264819
|
let lastError;
|
|
@@ -264633,7 +264825,7 @@ async function firstValid(promises) {
|
|
|
264633
264825
|
if (threw)
|
|
264634
264826
|
reject(lastError);
|
|
264635
264827
|
else
|
|
264636
|
-
|
|
264828
|
+
resolve12(null);
|
|
264637
264829
|
}
|
|
264638
264830
|
};
|
|
264639
264831
|
for (const p of promises) {
|
|
@@ -264642,7 +264834,7 @@ async function firstValid(promises) {
|
|
|
264642
264834
|
return;
|
|
264643
264835
|
if (value !== null) {
|
|
264644
264836
|
settled = true;
|
|
264645
|
-
|
|
264837
|
+
resolve12(value);
|
|
264646
264838
|
return;
|
|
264647
264839
|
}
|
|
264648
264840
|
onNonResult();
|
|
@@ -265025,7 +265217,7 @@ DIFF:
|
|
|
265025
265217
|
|
|
265026
265218
|
// packages/cli/dist/runner/executor.js
|
|
265027
265219
|
var execFileAsync10 = promisify10(execFile10);
|
|
265028
|
-
function
|
|
265220
|
+
function parsePorcelainPath2(line) {
|
|
265029
265221
|
const raw = line.length > 3 ? line.slice(3).trim() : line.trim();
|
|
265030
265222
|
const target = raw.includes(" -> ") ? raw.split(" -> ").at(-1) ?? raw : raw;
|
|
265031
265223
|
return target.replace(/^"|"$/g, "");
|
|
@@ -265036,7 +265228,7 @@ async function checkMainDirty(repoCwd) {
|
|
|
265036
265228
|
cwd: repoCwd,
|
|
265037
265229
|
encoding: "utf8"
|
|
265038
265230
|
});
|
|
265039
|
-
return stdout.split("\n").map((line) => line.trimEnd()).filter((line) => line.trim() !== "").map(
|
|
265231
|
+
return stdout.split("\n").map((line) => line.trimEnd()).filter((line) => line.trim() !== "").map(parsePorcelainPath2).filter((path) => path !== ".roll" && !path.startsWith(".roll/")).slice(0, 50);
|
|
265040
265232
|
} catch {
|
|
265041
265233
|
return [];
|
|
265042
265234
|
}
|
|
@@ -265995,7 +266187,7 @@ ${stderr}
|
|
|
265995
266187
|
// FIX-319: review-only framing, no worker autorun directive
|
|
265996
266188
|
...ctx.evidenceRunDir !== void 0 ? { runDir: ctx.evidenceRunDir } : {}
|
|
265997
266189
|
}),
|
|
265998
|
-
new Promise((
|
|
266190
|
+
new Promise((resolve12) => setTimeout(() => resolve12(null), timeoutMs).unref())
|
|
265999
266191
|
]);
|
|
266000
266192
|
} catch (e) {
|
|
266001
266193
|
const detail = e instanceof Error ? e.message : String(e);
|
|
@@ -266135,7 +266327,7 @@ ${res.stderr}`;
|
|
|
266135
266327
|
timeoutMs,
|
|
266136
266328
|
...ctx.evidenceRunDir !== void 0 ? { runDir: ctx.evidenceRunDir } : {}
|
|
266137
266329
|
}),
|
|
266138
|
-
new Promise((
|
|
266330
|
+
new Promise((resolve12) => setTimeout(() => resolve12(null), timeoutMs).unref())
|
|
266139
266331
|
]);
|
|
266140
266332
|
} catch (e) {
|
|
266141
266333
|
const detail = e instanceof Error ? e.message : String(e);
|
|
@@ -269132,10 +269324,10 @@ function autoGc(projectPath3) {
|
|
|
269132
269324
|
}
|
|
269133
269325
|
}
|
|
269134
269326
|
}
|
|
269135
|
-
async function isOffline(
|
|
269327
|
+
async function isOffline(resolve12 = (h) => lookup2(h)) {
|
|
269136
269328
|
try {
|
|
269137
269329
|
await Promise.race([
|
|
269138
|
-
|
|
269330
|
+
resolve12("github.com"),
|
|
269139
269331
|
new Promise((_, rej) => {
|
|
269140
269332
|
const t2 = setTimeout(() => rej(new Error("dns timeout")), 1500);
|
|
269141
269333
|
if (typeof t2 === "object")
|
|
@@ -269204,7 +269396,7 @@ init_dist();
|
|
|
269204
269396
|
import { spawnSync as spawnSync15 } from "node:child_process";
|
|
269205
269397
|
import { existsSync as existsSync100, readFileSync as readFileSync94, realpathSync as realpathSync14, renameSync as renameSync18, rmSync as rmSync17, writeFileSync as writeFileSync50 } from "node:fs";
|
|
269206
269398
|
import { homedir as homedir28 } from "node:os";
|
|
269207
|
-
import { join as join104, resolve as
|
|
269399
|
+
import { join as join104, resolve as resolve10 } from "node:path";
|
|
269208
269400
|
function pal5() {
|
|
269209
269401
|
const noColor2 = (process.env["NO_COLOR"] ?? "") !== "";
|
|
269210
269402
|
return noColor2 ? { RED: "", GREEN: "", YELLOW: "", CYAN: "", BOLD: "", NC: "" } : {
|
|
@@ -269308,7 +269500,7 @@ function writeFileAtomic2(path, text2) {
|
|
|
269308
269500
|
}
|
|
269309
269501
|
}
|
|
269310
269502
|
function resolveChangesetItem(projectDir, item) {
|
|
269311
|
-
return item.startsWith("/") ?
|
|
269503
|
+
return item.startsWith("/") ? resolve10(item) : resolve10(projectDir, item);
|
|
269312
269504
|
}
|
|
269313
269505
|
var HELP3 = `Usage: roll offboard [--confirm]
|
|
269314
269506
|
Preview (default) or apply (--confirm) the removal of every
|
|
@@ -269329,7 +269521,7 @@ function offboardCommand(args) {
|
|
|
269329
269521
|
}
|
|
269330
269522
|
let projectDir;
|
|
269331
269523
|
try {
|
|
269332
|
-
projectDir = realpathSync14(
|
|
269524
|
+
projectDir = realpathSync14(resolve10(process.cwd()));
|
|
269333
269525
|
} catch {
|
|
269334
269526
|
projectDir = process.cwd();
|
|
269335
269527
|
}
|
|
@@ -272118,7 +272310,7 @@ init_showcase2();
|
|
|
272118
272310
|
// packages/cli/dist/commands/test.js
|
|
272119
272311
|
import { spawnSync as spawnSync18 } from "node:child_process";
|
|
272120
272312
|
import { appendFileSync as appendFileSync19, existsSync as existsSync108, mkdirSync as mkdirSync53, readFileSync as readFileSync102, rmSync as rmSync19, writeFileSync as writeFileSync56 } from "node:fs";
|
|
272121
|
-
import { dirname as dirname50, join as join112, resolve as
|
|
272313
|
+
import { dirname as dirname50, join as join112, resolve as resolve11 } from "node:path";
|
|
272122
272314
|
function pal7() {
|
|
272123
272315
|
const noColor2 = (process.env["NO_COLOR"] ?? "") !== "";
|
|
272124
272316
|
return noColor2 ? { CYAN: "", GREEN: "", YELLOW: "", RED: "", NC: "" } : { CYAN: "\x1B[0;36m", GREEN: "\x1B[0;32m", YELLOW: "\x1B[0;33m", RED: "\x1B[0;31m", NC: "\x1B[0m" };
|
|
@@ -272138,7 +272330,7 @@ function currentEvidenceFrame() {
|
|
|
272138
272330
|
const raw = (process.env["ROLL_RUN_DIR"] ?? "").trim();
|
|
272139
272331
|
if (raw === "")
|
|
272140
272332
|
return null;
|
|
272141
|
-
const runDir =
|
|
272333
|
+
const runDir = resolve11(raw);
|
|
272142
272334
|
if (!existsSync108(runDir))
|
|
272143
272335
|
return null;
|
|
272144
272336
|
const frame = {
|
|
@@ -273114,7 +273306,7 @@ function registerAll() {
|
|
|
273114
273306
|
if (args[0] === "offboard")
|
|
273115
273307
|
return offboardCommand(args.slice(1));
|
|
273116
273308
|
return setupCommand(args);
|
|
273117
|
-
}, { help: "Usage: roll setup [skills
|
|
273309
|
+
}, { help: "Usage: roll setup [-f|--force] [--reselect]\n roll setup skills [args...]\n roll setup offboard [args...]\n Install or re-sync Roll conventions/templates for this machine; use -f to force refresh.\n\u672C\u673A\u5B89\u88C5\u6216\u91CD\u65B0\u540C\u6B65 Roll \u6A21\u677F\u4E0E\u7EA6\u5B9A\uFF1B-f \u5F3A\u5236\u5237\u65B0\u3002" });
|
|
273118
273310
|
registerPorted("ci", (args) => {
|
|
273119
273311
|
if (args.includes("--wait"))
|
|
273120
273312
|
return ciWaitCommand(args);
|
package/guide/en/loop.md
CHANGED
|
@@ -213,6 +213,14 @@ bricked by a limit you set days ago. Scope (`--epic`/`--cards`) and `--review`
|
|
|
213
213
|
still persist when unspecified, because they are the goal's identity, not a
|
|
214
214
|
per-run safety knob.
|
|
215
215
|
|
|
216
|
+
Before the first builder cycle, `roll loop go` also has a fallback check for
|
|
217
|
+
leftover uncommitted Roll bootstrap artifacts such as `AGENTS.md`, `.claude/`,
|
|
218
|
+
or `.roll/` metadata. Current `roll init` should already commit and push those
|
|
219
|
+
Roll-owned files; if a historical or manual path still leaves only those files
|
|
220
|
+
dirty, the goal pauses with `bootstrap_artifacts_unconfirmed` and prints the
|
|
221
|
+
files to confirm. Commit or clean them, then run `roll loop go` again. This
|
|
222
|
+
preflight does not start a cycle and does not count as no-progress.
|
|
223
|
+
|
|
216
224
|
`roll loop go` enforces safety only at cycle boundaries. `--budget <usd>` uses
|
|
217
225
|
the effective run cost ledger and moves the goal to `budget_limited` when the
|
|
218
226
|
budget is reached. An idle or aborted cycle that ran no agent counts as a known
|
|
@@ -45,6 +45,13 @@ and apply an onboard plan, repair partial markers, run the old-layout migration,
|
|
|
45
45
|
start the loop on the next Todo card, or inspect status when nothing is
|
|
46
46
|
actionable.
|
|
47
47
|
|
|
48
|
+
When a path writes Roll-owned meta files inside a git worktree, `roll init`
|
|
49
|
+
finishes by adding, committing, and pushing those files to `origin` when
|
|
50
|
+
possible. The finalize step is scoped to Roll-owned paths such as `AGENTS.md`,
|
|
51
|
+
`.claude/CLAUDE.md`, `.roll/**`, and Roll-managed `.gitignore` entries; product
|
|
52
|
+
source, PRDs, and other user files are left alone. If commit or push cannot be
|
|
53
|
+
completed, init prints the manual command to run.
|
|
54
|
+
|
|
48
55
|
Upgrading from a pre-2.0 layout (`BACKLOG.md` at root or `docs/features/`)?
|
|
49
56
|
Run `npx @seanyao/roll@2 migrate` first — see
|
|
50
57
|
[migration-2.0.md](migration-2.0.md). `roll init` will refuse to scaffold on top
|
package/guide/zh/loop.md
CHANGED
|
@@ -198,6 +198,12 @@ unknown,这类行仍按保守侧停止,不当作 0。用量闸检查 5 小
|
|
|
198
198
|
不会让会话无限停摆;超时后 Roll 记录 `usage_wait_timeout` 审计事件并让 goal 保持暂停。
|
|
199
199
|
`--for <duration>` 是墙钟时间盒:当前 cycle 收尾后,goal 以 `timebox` 原因暂停。
|
|
200
200
|
|
|
201
|
+
启动第一个 builder cycle 前,`roll loop go` 还会兜底检查遗留未提交的 Roll bootstrap 产物,
|
|
202
|
+
例如 `AGENTS.md`、`.claude/` 或 `.roll/` 元数据。新版 `roll init` 应该已经把这些
|
|
203
|
+
Roll-owned 文件提交并推送;如果历史版本或手工路径仍然只留下这些脏文件,goal 会以
|
|
204
|
+
`bootstrap_artifacts_unconfirmed` 暂停,并打印待确认文件。提交或清理后,再重新运行
|
|
205
|
+
`roll loop go`。这个 preflight 不会启动 cycle,也不会累加 no-progress。
|
|
206
|
+
|
|
201
207
|
每次安全闸触发都会记录 `goal:gate_tripped`,`roll loop goal` 会显示最近一次安全闸读数。
|
|
202
208
|
|
|
203
209
|
### `roll loop goal` 字段含义
|
|
@@ -38,6 +38,12 @@ roll init
|
|
|
38
38
|
从 brief 进入设计、审阅并 apply onboard plan、修复 partial 标记、执行旧布局迁移、
|
|
39
39
|
对下一张 Todo 开 loop,或在没有可执行项时查看 status。
|
|
40
40
|
|
|
41
|
+
当某条路径在 git worktree 中写入 Roll-owned meta 文件时,`roll init` 会在收尾阶段尽力
|
|
42
|
+
把这些文件 add、commit 并 push 到 `origin`。这个 finalize 只覆盖 Roll 管理的路径,例如
|
|
43
|
+
`AGENTS.md`、`.claude/CLAUDE.md`、`.roll/**` 和 Roll 自己追加的 `.gitignore` 条目;
|
|
44
|
+
产品源码、PRD 和其他用户文件不会被顺手提交。若 commit 或 push 无法完成,init 会打印需要
|
|
45
|
+
手动执行的命令。
|
|
46
|
+
|
|
41
47
|
正在从 2.0 之前的布局升级(`BACKLOG.md` 在根目录或 `docs/features/`)?
|
|
42
48
|
先跑 `npx @seanyao/roll@2 migrate` —— 见
|
|
43
49
|
[migration-2.0.md](migration-2.0.md)。`roll init` 会拒绝在迁移到一半的
|