@autopilot-harness/cli 0.2.11 → 0.2.13
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/dist/assets/vendor/runtime.mjs +626 -510
- package/dist/init/types.d.ts +1 -1
- package/dist/init/types.js +1 -1
- package/package.json +5 -5
|
@@ -42,6 +42,7 @@ var en_default = {
|
|
|
42
42
|
recover_ambient: "Recover: the previous turn ended with an error. Continue your current work; Autopilot RUN is not active.",
|
|
43
43
|
review_complete: "Review complete. All {total} confirm rounds passed; the review chain has ended. If the working tree still has uncommitted changes from this session, local commit only per the safe checklist (never stage .env/secrets/.autopilot runtime; no push unless the user asks); if clean, briefly confirm only.",
|
|
44
44
|
stuck: "Stuck: no progress for several stops. Change strategy or send Autopilot RESUME after fixing.",
|
|
45
|
+
stuck_soft: "Stuck: no progress for several stops (missing completion evidence). Change strategy, write matching .autopilot/verify-last.json, then end the turn. Session stays armed \u2014 Autopilot RESUME is not required unless the session was paused.",
|
|
45
46
|
verify_fix: "Verify failed ({reason}). Fix verify commands and rewrite verify-last.json; do not advance.",
|
|
46
47
|
need_evidence: `Need evidence: no-code item {currentId}{currentTitleSuffix} cannot advance without matching soft completion evidence. Write .autopilot/verify-last.json with itemId "{currentId}" and ok: true (only after this item's work is done). Then end the turn so the stop hook can advance/done. Do not ask the user to continue; do not invent Advance/Done.`,
|
|
47
48
|
track_pick: "Select a plan by number or slug."
|
|
@@ -118,6 +119,7 @@ var zh_CN_default = {
|
|
|
118
119
|
recover_ambient: "\u6062\u590D\uFF1A\u4E0A\u4E00\u56DE\u5408\u51FA\u9519\u3002\u7EE7\u7EED\u5F53\u524D\u4EFB\u52A1\uFF08\u672A\u5728\u6267\u884C checklist\uFF09\u3002",
|
|
119
120
|
review_complete: "\u81EA\u5BA1\u5B8C\u6210\u3002\u8FDE\u7EED {total} \u8F6E\u786E\u8BA4\u5DF2\u901A\u8FC7\uFF0C\u81EA\u5BA1\u94FE\u5DF2\u7ED3\u675F\u3002\u82E5\u5DE5\u4F5C\u533A\u4ECD\u6709\u672C\u4F1A\u8BDD\u672A\u63D0\u4EA4\u6539\u52A8\uFF0C\u6309\u5B89\u5168\u6E05\u5355\u672C\u5730 commit\uFF08\u52FF stage .env/\u5BC6\u94A5/.autopilot \u8FD0\u884C\u65F6\uFF1B\u52FF push\uFF0C\u9664\u975E\u7528\u6237\u660E\u786E\u8981\u6C42\uFF09\uFF1B\u5DF2\u5E72\u51C0\u5219\u53EA\u7B80\u77ED\u786E\u8BA4\u5373\u53EF\u3002",
|
|
120
121
|
stuck: "\u5361\u4F4F\uFF1A\u8FDE\u7EED\u591A\u8F6E\u65E0\u8FDB\u5C55\u3002\u8BF7\u6362\u7B56\u7565\uFF0C\u6216\u4FEE\u597D\u540E\u53D1\u9001 Autopilot RESUME\u3002",
|
|
122
|
+
stuck_soft: "\u5361\u4F4F\uFF1A\u8FDE\u7EED\u591A\u8F6E\u65E0\u8FDB\u5C55\uFF08\u7F3A\u5B8C\u6210\u8BC1\u636E\uFF09\u3002\u8BF7\u6362\u7B56\u7565\u5E76\u5199\u5165\u5339\u914D\u7684 .autopilot/verify-last.json \u540E\u7ED3\u675F\u56DE\u5408\u3002\u4F1A\u8BDD\u4ECD\u5728\u8FD0\u884C\uFF0C\u65E0\u9700 RESUME\uFF1B\u4EC5\u5728\u5DF2 pause \u65F6\u624D\u53D1\u9001 Autopilot RESUME\u3002",
|
|
121
123
|
verify_fix: "\u6821\u9A8C\u5931\u8D25\uFF08{reason}\uFF09\u3002\u8BF7\u4FEE\u590D verify \u547D\u4EE4\u5E76\u91CD\u5199 verify-last.json\uFF1B\u4E0D\u8981\u63A8\u8FDB\u3002",
|
|
122
124
|
need_evidence: '\u9700\u8981\u5B8C\u6210\u8BC1\u636E\uFF1A\u65E0\u4EE3\u7801\u6539\u52A8\u9879 {currentId}{currentTitleSuffix} \u7F3A\u5C11\u5339\u914D\u7684 soft \u5B8C\u6210\u8BC1\u636E\uFF0C\u65E0\u6CD5\u63A8\u8FDB\u3002\u8BF7\u5199\u5165 .autopilot/verify-last.json\uFF08itemId \u4E3A "{currentId}"\uFF0Cok: true\uFF1B\u987B\u5728\u8BE5\u9879\u5DE5\u4F5C\u5B8C\u6210\u540E\uFF09\u3002\u7136\u540E\u7ED3\u675F\u672C\u56DE\u5408\uFF0C\u7531 stop hook \u63A8\u8FDB/\u5B8C\u6210\u3002\u4E0D\u8981\u8BA9\u7528\u6237\u8BF4\u300C\u7EE7\u7EED\u300D\uFF1B\u4E0D\u8981\u81EA\u884C\u53D1\u660E\u63A8\u8FDB/\u5B8C\u6210\u6307\u4EE4\u3002',
|
|
123
125
|
track_pick: "\u8BF7\u7528\u6570\u5B57\u6216 slug \u9009\u62E9\u8981\u6267\u884C\u7684 plan\u3002"
|
|
@@ -2066,6 +2068,11 @@ function isRecoverOrStuckFollowupMessage(text) {
|
|
|
2066
2068
|
if (!line) return false;
|
|
2067
2069
|
return isRecoverFollowupMessage(line) || line.startsWith("Stuck:") || line.startsWith("\u5361\u4F4F\uFF1A") || line.startsWith("\u5361\u4F4F:");
|
|
2068
2070
|
}
|
|
2071
|
+
function isTerminalFollowupMessage(text) {
|
|
2072
|
+
const line = firstSubstantiveLine(text);
|
|
2073
|
+
if (!line) return false;
|
|
2074
|
+
return line.startsWith("All checklist") || line.startsWith("\u5168\u90E8\u5B8C\u6210") || line.startsWith("Review complete") || line.startsWith("\u81EA\u5BA1\u5B8C\u6210");
|
|
2075
|
+
}
|
|
2069
2076
|
var USER_ABORT_MARKERS = [
|
|
2070
2077
|
"user aborted",
|
|
2071
2078
|
"interrupted manually",
|
|
@@ -2393,93 +2400,469 @@ function pendingRedeliverAllowed(lastRedeliverAt) {
|
|
|
2393
2400
|
return Date.now() - t >= PENDING_REDELIVER_COOLDOWN_MS;
|
|
2394
2401
|
}
|
|
2395
2402
|
|
|
2396
|
-
// ../core/src/
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2403
|
+
// ../core/src/code-edit-detector.ts
|
|
2404
|
+
import { spawnSync } from "node:child_process";
|
|
2405
|
+
import path7 from "node:path";
|
|
2406
|
+
|
|
2407
|
+
// ../core/src/autopilot-ignore.ts
|
|
2408
|
+
import fs7 from "node:fs";
|
|
2409
|
+
import path6 from "node:path";
|
|
2410
|
+
var DEFAULT_AUTOPILOT_IGNORE_TEXT = `# Autopilot \u2014 paths that do NOT trigger self-review (gitignore syntax).
|
|
2411
|
+
#
|
|
2412
|
+
# What this file is:
|
|
2413
|
+
# - Controls whether an afterFileEdit counts as "product code" (opens fix/confirm).
|
|
2414
|
+
# - Does NOT change \`git diff\` / \`git status\` output (that is \`.gitignore\`).
|
|
2415
|
+
# - Review followups ask the agent to skip these paths when reading diffs (soft).
|
|
2416
|
+
#
|
|
2417
|
+
# Semantics:
|
|
2418
|
+
# - Same glob rules as gitignore; last matching pattern wins.
|
|
2419
|
+
# - Use \`!\` to force-include an exception (e.g. \`!docs/feed/**/*.yml\`).
|
|
2420
|
+
# - Markdown (*.md / *.mdx) is NOT ignored by default \u2014 design docs can be reviewed.
|
|
2421
|
+
# - \`docs/**\` is NOT ignored by default.
|
|
2422
|
+
# - Also skip untracked paths ignored by \`.gitignore\` (tracked files still count).
|
|
2423
|
+
#
|
|
2424
|
+
# Later (not implemented): hard-filtered review-diff / path ledger \u2014 see
|
|
2425
|
+
# docs/autopilot/workflows/autopilot-executing.md (B2 strong).
|
|
2426
|
+
|
|
2427
|
+
# Runtime / editor (prefer also listing these in .gitignore)
|
|
2428
|
+
.autopilot/**
|
|
2429
|
+
.cursor/**
|
|
2430
|
+
.claude/**
|
|
2431
|
+
|
|
2432
|
+
# Planning artifacts
|
|
2433
|
+
plans/**
|
|
2434
|
+
|
|
2435
|
+
# Common build / vendor trees
|
|
2436
|
+
node_modules/**
|
|
2437
|
+
dist/**
|
|
2438
|
+
build/**
|
|
2439
|
+
out/**
|
|
2440
|
+
target/**
|
|
2441
|
+
.target/**
|
|
2442
|
+
coverage/**
|
|
2443
|
+
.venv/**
|
|
2444
|
+
venv/**
|
|
2445
|
+
__pycache__/**
|
|
2446
|
+
|
|
2447
|
+
# Lockfiles / package manager noise
|
|
2448
|
+
package-lock.json
|
|
2449
|
+
pnpm-lock.yaml
|
|
2450
|
+
yarn.lock
|
|
2451
|
+
bun.lock
|
|
2452
|
+
bun.lockb
|
|
2453
|
+
Cargo.lock
|
|
2454
|
+
poetry.lock
|
|
2455
|
+
composer.lock
|
|
2456
|
+
|
|
2457
|
+
# Media / binary (do not trigger self-review)
|
|
2458
|
+
*.png
|
|
2459
|
+
*.jpg
|
|
2460
|
+
*.jpeg
|
|
2461
|
+
*.gif
|
|
2462
|
+
*.webp
|
|
2463
|
+
*.ico
|
|
2464
|
+
*.svg
|
|
2465
|
+
*.bmp
|
|
2466
|
+
*.mp3
|
|
2467
|
+
*.mp4
|
|
2468
|
+
*.wav
|
|
2469
|
+
*.webm
|
|
2470
|
+
*.mov
|
|
2471
|
+
*.woff
|
|
2472
|
+
*.woff2
|
|
2473
|
+
*.ttf
|
|
2474
|
+
*.otf
|
|
2475
|
+
*.eot
|
|
2476
|
+
*.pdf
|
|
2477
|
+
*.zip
|
|
2478
|
+
*.gz
|
|
2479
|
+
*.tgz
|
|
2480
|
+
*.7z
|
|
2481
|
+
*.rar
|
|
2482
|
+
*.jar
|
|
2483
|
+
*.class
|
|
2484
|
+
*.o
|
|
2485
|
+
*.a
|
|
2486
|
+
*.so
|
|
2487
|
+
*.dylib
|
|
2488
|
+
*.dll
|
|
2489
|
+
*.exe
|
|
2490
|
+
*.wasm
|
|
2491
|
+
|
|
2492
|
+
# Prose / data noise
|
|
2493
|
+
*.txt
|
|
2494
|
+
*.html
|
|
2495
|
+
*.htm
|
|
2496
|
+
*.csv
|
|
2497
|
+
*.tsv
|
|
2498
|
+
*.log
|
|
2499
|
+
*.map
|
|
2500
|
+
*.min.js
|
|
2501
|
+
*.min.css
|
|
2502
|
+
`;
|
|
2503
|
+
var MAX_AUTOPILOT_IGNORE_BYTES = 1e6;
|
|
2504
|
+
var MAX_AUTOPILOT_IGNORE_LINE_CHARS = 4096;
|
|
2505
|
+
var MAX_AUTOPILOT_IGNORE_PATTERNS = 1e4;
|
|
2506
|
+
var ignoreCache = /* @__PURE__ */ new Map();
|
|
2507
|
+
function escapeRegexChar(ch) {
|
|
2508
|
+
return ch.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
|
|
2426
2509
|
}
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2510
|
+
function globBodyToRegex(glob) {
|
|
2511
|
+
let out = "";
|
|
2512
|
+
for (let i = 0; i < glob.length; ) {
|
|
2513
|
+
const ch = glob[i];
|
|
2514
|
+
if (ch === "*" && glob[i + 1] === "*") {
|
|
2515
|
+
if (glob[i + 2] === "/") {
|
|
2516
|
+
out += "(?:.*/)?";
|
|
2517
|
+
i += 3;
|
|
2518
|
+
continue;
|
|
2519
|
+
}
|
|
2520
|
+
out += ".*";
|
|
2521
|
+
i += 2;
|
|
2522
|
+
continue;
|
|
2523
|
+
}
|
|
2524
|
+
if (ch === "*") {
|
|
2525
|
+
out += "[^/]*";
|
|
2526
|
+
i += 1;
|
|
2527
|
+
continue;
|
|
2528
|
+
}
|
|
2529
|
+
if (ch === "?") {
|
|
2530
|
+
out += "[^/]";
|
|
2531
|
+
i += 1;
|
|
2532
|
+
continue;
|
|
2533
|
+
}
|
|
2534
|
+
out += escapeRegexChar(ch);
|
|
2535
|
+
i += 1;
|
|
2434
2536
|
}
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2537
|
+
return out;
|
|
2538
|
+
}
|
|
2539
|
+
function compilePattern(raw) {
|
|
2540
|
+
let line = raw.trim();
|
|
2541
|
+
if (!line || line.startsWith("#")) return null;
|
|
2542
|
+
if (line.length > MAX_AUTOPILOT_IGNORE_LINE_CHARS) return null;
|
|
2543
|
+
let negated = false;
|
|
2544
|
+
if (line.startsWith("!")) {
|
|
2545
|
+
negated = true;
|
|
2546
|
+
line = line.slice(1).trim();
|
|
2547
|
+
if (!line || line.startsWith("#")) return null;
|
|
2438
2548
|
}
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2549
|
+
const dirOnly = line.endsWith("/");
|
|
2550
|
+
if (dirOnly) line = line.slice(0, -1);
|
|
2551
|
+
if (!line) return null;
|
|
2552
|
+
if (line.length > MAX_AUTOPILOT_IGNORE_LINE_CHARS) return null;
|
|
2553
|
+
let anchored = false;
|
|
2554
|
+
if (line.startsWith("/")) {
|
|
2555
|
+
anchored = true;
|
|
2556
|
+
line = line.slice(1);
|
|
2445
2557
|
}
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
const checklistPath = session.checklist_path;
|
|
2453
|
-
if (!checklistPath) return null;
|
|
2454
|
-
const root = this.trustedProjectRoot();
|
|
2455
|
-
if (!root || !isRealpathInsideProject(root, checklistPath)) {
|
|
2456
|
-
return null;
|
|
2558
|
+
const body = globBodyToRegex(line);
|
|
2559
|
+
let regexSource;
|
|
2560
|
+
if (anchored) {
|
|
2561
|
+
regexSource = `^${body}`;
|
|
2562
|
+
if (dirOnly || line.endsWith("/**") || line.endsWith("/*")) {
|
|
2563
|
+
regexSource += "(?:/.*)?";
|
|
2457
2564
|
}
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
followingItem: secondUnchecked(cl),
|
|
2464
|
-
checklist: cl
|
|
2465
|
-
};
|
|
2466
|
-
} catch {
|
|
2467
|
-
return null;
|
|
2565
|
+
regexSource += "$";
|
|
2566
|
+
} else if (line.includes("/")) {
|
|
2567
|
+
regexSource = `(?:^|.*/)${body}`;
|
|
2568
|
+
if (dirOnly || line.endsWith("/**") || line.endsWith("/*")) {
|
|
2569
|
+
regexSource += "(?:/.*)?";
|
|
2468
2570
|
}
|
|
2571
|
+
regexSource += "$";
|
|
2572
|
+
} else {
|
|
2573
|
+
regexSource = `(?:^|.*/)?${body}$`;
|
|
2469
2574
|
}
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2575
|
+
return { negated, regex: new RegExp(regexSource) };
|
|
2576
|
+
}
|
|
2577
|
+
function parseAutopilotIgnore(text) {
|
|
2578
|
+
const patterns = [];
|
|
2579
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
2580
|
+
const compiled = compilePattern(rawLine);
|
|
2581
|
+
if (!compiled) continue;
|
|
2582
|
+
patterns.push(compiled);
|
|
2583
|
+
if (patterns.length >= MAX_AUTOPILOT_IGNORE_PATTERNS) break;
|
|
2478
2584
|
}
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2585
|
+
return patterns;
|
|
2586
|
+
}
|
|
2587
|
+
var DEFAULT_AUTOPILOT_IGNORE_PATTERNS = parseAutopilotIgnore(
|
|
2588
|
+
DEFAULT_AUTOPILOT_IGNORE_TEXT
|
|
2589
|
+
);
|
|
2590
|
+
function normalizeRelativePath(filePath) {
|
|
2591
|
+
return filePath.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
2592
|
+
}
|
|
2593
|
+
function isAutopilotIgnoredPath(relativePath, patterns) {
|
|
2594
|
+
const norm = normalizeRelativePath(relativePath);
|
|
2595
|
+
let ignored = false;
|
|
2596
|
+
for (const pat of patterns) {
|
|
2597
|
+
if (pat.regex.test(norm)) {
|
|
2598
|
+
ignored = !pat.negated;
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2601
|
+
return ignored;
|
|
2602
|
+
}
|
|
2603
|
+
function realpathForCompare(absPath) {
|
|
2604
|
+
try {
|
|
2605
|
+
return fs7.realpathSync(absPath);
|
|
2606
|
+
} catch {
|
|
2607
|
+
}
|
|
2608
|
+
let dir = path6.dirname(absPath);
|
|
2609
|
+
const base = path6.basename(absPath);
|
|
2610
|
+
const missing = [];
|
|
2611
|
+
for (; ; ) {
|
|
2612
|
+
try {
|
|
2613
|
+
const realDir = fs7.realpathSync(dir);
|
|
2614
|
+
return path6.join(realDir, ...missing, base);
|
|
2615
|
+
} catch {
|
|
2616
|
+
const parent = path6.dirname(dir);
|
|
2617
|
+
if (parent === dir) return absPath;
|
|
2618
|
+
missing.unshift(path6.basename(dir));
|
|
2619
|
+
dir = parent;
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
function toProjectRelativePath(filePath, projectRoot) {
|
|
2624
|
+
const posix = filePath.replace(/\\/g, "/");
|
|
2625
|
+
if (!projectRoot?.trim()) {
|
|
2626
|
+
return normalizeRelativePath(posix);
|
|
2627
|
+
}
|
|
2628
|
+
const root = realpathForCompare(path6.resolve(projectRoot));
|
|
2629
|
+
const abs = realpathForCompare(
|
|
2630
|
+
path6.isAbsolute(posix) ? path6.resolve(posix) : path6.resolve(root, posix)
|
|
2631
|
+
);
|
|
2632
|
+
const rel = path6.relative(root, abs);
|
|
2633
|
+
if (rel.startsWith("..") || path6.isAbsolute(rel)) {
|
|
2634
|
+
return "";
|
|
2635
|
+
}
|
|
2636
|
+
return normalizeRelativePath(rel.replace(/\\/g, "/"));
|
|
2637
|
+
}
|
|
2638
|
+
function loadAutopilotIgnorePatterns(projectRoot) {
|
|
2639
|
+
const root = path6.resolve(projectRoot);
|
|
2640
|
+
const filePath = path6.join(root, ".autopilotignore");
|
|
2641
|
+
let st;
|
|
2642
|
+
try {
|
|
2643
|
+
st = fs7.lstatSync(filePath);
|
|
2644
|
+
} catch {
|
|
2645
|
+
return DEFAULT_AUTOPILOT_IGNORE_PATTERNS;
|
|
2646
|
+
}
|
|
2647
|
+
if (st.isSymbolicLink() || !st.isFile()) {
|
|
2648
|
+
return DEFAULT_AUTOPILOT_IGNORE_PATTERNS;
|
|
2649
|
+
}
|
|
2650
|
+
if (st.size > MAX_AUTOPILOT_IGNORE_BYTES) {
|
|
2651
|
+
return DEFAULT_AUTOPILOT_IGNORE_PATTERNS;
|
|
2652
|
+
}
|
|
2653
|
+
const cached = ignoreCache.get(root);
|
|
2654
|
+
if (cached && cached.mtimeMs === st.mtimeMs) {
|
|
2655
|
+
return cached.patterns;
|
|
2656
|
+
}
|
|
2657
|
+
let text;
|
|
2658
|
+
try {
|
|
2659
|
+
text = fs7.readFileSync(filePath, "utf8");
|
|
2660
|
+
} catch {
|
|
2661
|
+
return DEFAULT_AUTOPILOT_IGNORE_PATTERNS;
|
|
2662
|
+
}
|
|
2663
|
+
if (text.length > MAX_AUTOPILOT_IGNORE_BYTES) {
|
|
2664
|
+
return DEFAULT_AUTOPILOT_IGNORE_PATTERNS;
|
|
2665
|
+
}
|
|
2666
|
+
const patterns = parseAutopilotIgnore(text);
|
|
2667
|
+
ignoreCache.set(root, { mtimeMs: st.mtimeMs, patterns });
|
|
2668
|
+
return patterns;
|
|
2669
|
+
}
|
|
2670
|
+
|
|
2671
|
+
// ../core/src/code-edit-detector.ts
|
|
2672
|
+
function isUntrackedGitIgnored(projectRoot, relativePath) {
|
|
2673
|
+
if (!relativePath || relativePath.includes("\0")) return false;
|
|
2674
|
+
try {
|
|
2675
|
+
const r = spawnSync(
|
|
2676
|
+
"git",
|
|
2677
|
+
["check-ignore", "-q", "--", relativePath],
|
|
2678
|
+
{
|
|
2679
|
+
cwd: projectRoot,
|
|
2680
|
+
encoding: "utf8",
|
|
2681
|
+
timeout: 5e3,
|
|
2682
|
+
windowsHide: true,
|
|
2683
|
+
shell: false
|
|
2684
|
+
}
|
|
2685
|
+
);
|
|
2686
|
+
return r.status === 0;
|
|
2687
|
+
} catch {
|
|
2688
|
+
return false;
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2691
|
+
function isProductCodeEdit(filePath, opts) {
|
|
2692
|
+
const relative = toProjectRelativePath(filePath, opts?.projectRoot);
|
|
2693
|
+
if (!relative) return false;
|
|
2694
|
+
const patterns = opts?.projectRoot?.trim() ? loadAutopilotIgnorePatterns(opts.projectRoot) : DEFAULT_AUTOPILOT_IGNORE_PATTERNS;
|
|
2695
|
+
if (isAutopilotIgnoredPath(relative, patterns)) return false;
|
|
2696
|
+
const root = opts?.projectRoot?.trim();
|
|
2697
|
+
if (root && isUntrackedGitIgnored(root, relative)) return false;
|
|
2698
|
+
return true;
|
|
2699
|
+
}
|
|
2700
|
+
var MAX_DIRTY_PATHS_COLLECT = 2e3;
|
|
2701
|
+
var MAX_DIRTY_PATHS_CLASSIFY = 200;
|
|
2702
|
+
function gitDirtyRelativePaths(projectRoot) {
|
|
2703
|
+
if (!projectRoot || projectRoot.includes("\0")) return [];
|
|
2704
|
+
const out = [];
|
|
2705
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2706
|
+
const pushZ = (stdout) => {
|
|
2707
|
+
for (const raw of stdout.split("\0")) {
|
|
2708
|
+
const p = raw.replace(/\r$/, "");
|
|
2709
|
+
if (!p) continue;
|
|
2710
|
+
if (seen.has(p)) continue;
|
|
2711
|
+
seen.add(p);
|
|
2712
|
+
out.push(p);
|
|
2713
|
+
if (out.length >= MAX_DIRTY_PATHS_COLLECT) return;
|
|
2714
|
+
}
|
|
2715
|
+
};
|
|
2716
|
+
try {
|
|
2717
|
+
const tracked = spawnSync(
|
|
2718
|
+
"git",
|
|
2719
|
+
["diff", "--name-only", "-z", "HEAD", "--"],
|
|
2720
|
+
{
|
|
2721
|
+
cwd: projectRoot,
|
|
2722
|
+
encoding: "utf8",
|
|
2723
|
+
timeout: 5e3,
|
|
2724
|
+
windowsHide: true,
|
|
2725
|
+
shell: false
|
|
2726
|
+
}
|
|
2727
|
+
);
|
|
2728
|
+
if (tracked.error || tracked.status !== 0) {
|
|
2729
|
+
return [];
|
|
2730
|
+
}
|
|
2731
|
+
if (tracked.stdout) pushZ(tracked.stdout);
|
|
2732
|
+
if (out.length >= MAX_DIRTY_PATHS_COLLECT) return out;
|
|
2733
|
+
const untracked = spawnSync(
|
|
2734
|
+
"git",
|
|
2735
|
+
["ls-files", "-o", "--exclude-standard", "-z", "--"],
|
|
2736
|
+
{
|
|
2737
|
+
cwd: projectRoot,
|
|
2738
|
+
encoding: "utf8",
|
|
2739
|
+
timeout: 5e3,
|
|
2740
|
+
windowsHide: true,
|
|
2741
|
+
shell: false
|
|
2742
|
+
}
|
|
2743
|
+
);
|
|
2744
|
+
if (!untracked.error && untracked.status === 0 && untracked.stdout) {
|
|
2745
|
+
pushZ(untracked.stdout);
|
|
2746
|
+
}
|
|
2747
|
+
} catch {
|
|
2748
|
+
return [];
|
|
2749
|
+
}
|
|
2750
|
+
return out;
|
|
2751
|
+
}
|
|
2752
|
+
function hasDirtyProductCode(projectRoot) {
|
|
2753
|
+
const root = normalizeProjectRoot(projectRoot);
|
|
2754
|
+
if (!root) return false;
|
|
2755
|
+
const relatives = gitDirtyRelativePaths(root);
|
|
2756
|
+
const patterns = loadAutopilotIgnorePatterns(root);
|
|
2757
|
+
let classified = 0;
|
|
2758
|
+
for (const rel of relatives) {
|
|
2759
|
+
if (!rel || rel.includes("\0") || path7.isAbsolute(rel) || rel.split(/[/\\]/).includes("..")) {
|
|
2760
|
+
continue;
|
|
2761
|
+
}
|
|
2762
|
+
const abs = path7.join(root, rel);
|
|
2763
|
+
const relative = toProjectRelativePath(abs, root);
|
|
2764
|
+
if (!relative) continue;
|
|
2765
|
+
if (isAutopilotIgnoredPath(relative, patterns)) continue;
|
|
2766
|
+
classified += 1;
|
|
2767
|
+
if (classified > MAX_DIRTY_PATHS_CLASSIFY) {
|
|
2768
|
+
return false;
|
|
2769
|
+
}
|
|
2770
|
+
if (isProductCodeEdit(abs, { projectRoot: root })) {
|
|
2771
|
+
return true;
|
|
2772
|
+
}
|
|
2773
|
+
}
|
|
2774
|
+
return false;
|
|
2775
|
+
}
|
|
2776
|
+
|
|
2777
|
+
// ../core/src/review-engine.ts
|
|
2778
|
+
function defaultRender(kind, vars) {
|
|
2779
|
+
switch (kind) {
|
|
2780
|
+
case "review.fix":
|
|
2781
|
+
return `Review fix round ${vars.round} (no hard cap; confirm needs ${vars.total} consecutive no-edit rounds). Code changed this turn. Defect-first self-review and fix now: 1) when using git diff / git status, skip paths matching .autopilotignore, and skip untracked paths ignored by .gitignore; review only the remaining paths; 2) check correctness, null/boundaries, concurrency, security, regression, missing tests; 3) CRITICAL/HIGH must fix, MEDIUM preferably; 4) run relevant tests; 5) briefly state what you reviewed and changed (or "self-review clean"). Do not commit/push. If no further code changes, next stop enters multi-lens confirm.`;
|
|
2782
|
+
case "review.confirm":
|
|
2783
|
+
return `Review confirm ${vars.n}/${vars.total} (session round ${vars.sessionRound}; consecutive no-edit confirms, counted on the fix-round counter). Lens \u3010${vars.lensTitle}\u3011 (multi-lens confirm, not the same checklist again). ${vars.lensFocus} Previous turn had no further code edits. Recheck under this lens only: 1) git diff / git status \u2014 no new edits vs prior turn (or only already-reviewed edits); likewise skip paths matching .autopilotignore and untracked paths ignored by .gitignore; judge only from remaining paths; 2) dig into this lens only; ban vague "fully rechecked, all good"; 3) CRITICAL/HIGH under this lens must fix; MEDIUM preferably; 4) if you edit, fix and run related tests; 5) close with: "Lens (${vars.lensTitle}): self-review clean" or a short list of fixes; if clean, do not edit further. Do not commit/push.`;
|
|
2784
|
+
case "review.confirm_final":
|
|
2785
|
+
return `Review confirm ${vars.n}/${vars.total} (session round ${vars.sessionRound}; consecutive no-edit confirms, counted on the fix-round counter). Lens \u3010${vars.lensTitle}\u3011 (multi-lens confirm, not the same checklist again). ${vars.lensFocus} Previous turn had no further code edits. Recheck under this lens only: 1) git diff / git status \u2014 no new edits vs prior turn (or only already-reviewed edits); likewise skip paths matching .autopilotignore and untracked paths ignored by .gitignore; judge only from remaining paths; 2) dig into this lens only; ban vague "fully rechecked, all good"; 3) read-only: record CRITICAL/HIGH/missing tests \u2014 do not change code, add tests, or commit; if you already edited, accept returning to a fix round; never commit this turn; 4) do not run commands that mutate the repo; 5) close with: "Lens (${vars.lensTitle}): self-review clean" or list issues (no fixes); if clean, do not edit further. Do not commit/push. Handoff (commit) and next checklist item are handled by Advance/Done after the chain \u2014 not this turn.`;
|
|
2786
|
+
case "advance":
|
|
2787
|
+
return `Advance checklist: confirm chain passed cleanly (confirm rounds do not commit). First mark the completed current item ${vars.currentId ?? ""} [x] in checklist.md (do not mark the next item yet). Never mark an item [x] while you are still implementing it \u2014 only this Advance/Done followup checks off the completed item. Then, if the working tree still has uncommitted changes for this item (including checklist.md when plans/ is committed), local conventional commit only: git status/diff \u2192 stage only this checklist item's paths; never git add -A, never stage .env/secrets/.autopilot runtime; one conventional commit; no push/--no-verify/amend/force unless the user explicitly asks. If already clean after marking, skip commit. Then implement next: ${vars.nextId ?? ""} \u2014 ${vars.nextTitle ?? ""}.`;
|
|
2788
|
+
case "done":
|
|
2789
|
+
return `All checklist items done. Confirm chain passed (confirm rounds do not commit). Mark the last item [x]. If the working tree still has uncommitted changes for this item (including checklist.md when plans/ is committed), local conventional commit only (never stage .env/secrets/.autopilot runtime; no push unless the user asks); if clean, just confirm briefly. Phase is done.`;
|
|
2790
|
+
case "recover":
|
|
2791
|
+
return `Recover: the previous turn ended with an error. Continue the current task.`;
|
|
2792
|
+
case "recover_planning":
|
|
2793
|
+
return `Recover: the previous turn ended with an error. Continue planning; do not RUN or write product code.`;
|
|
2794
|
+
case "recover_ambient":
|
|
2795
|
+
return `Recover: the previous turn ended with an error. Continue your current work; Autopilot RUN is not active.`;
|
|
2796
|
+
case "review_complete":
|
|
2797
|
+
return `Review complete. All ${vars.total ?? 5} confirm rounds passed; the review chain has ended. If the working tree still has uncommitted changes from this session, local commit only per the safe checklist (never stage .env/secrets/.autopilot runtime; no push unless the user asks); if clean, briefly confirm only.`;
|
|
2798
|
+
case "stuck":
|
|
2799
|
+
return `Stuck: no progress for several stops. Change strategy or send Autopilot RESUME after fixing.`;
|
|
2800
|
+
case "stuck_soft":
|
|
2801
|
+
return `Stuck: no progress for several stops (missing completion evidence). Change strategy, write matching .autopilot/verify-last.json, then end the turn. Session stays armed \u2014 Autopilot RESUME is not required unless the session was paused.`;
|
|
2802
|
+
case "verify_fix":
|
|
2803
|
+
return `Verify failed (${vars.reason ?? "unknown"}). Fix verify commands and rewrite verify-last.json; do not advance.`;
|
|
2804
|
+
case "need_evidence":
|
|
2805
|
+
return `Need evidence: no-code item ${vars.currentId ?? ""}${vars.currentTitle ? ` \u2014 ${vars.currentTitle}` : ""} cannot advance without matching soft completion evidence. Write .autopilot/verify-last.json with itemId "${vars.currentId ?? ""}" and ok: true (only after this item's work is done). Then end the turn so the stop hook can advance/done. Do not ask the user to continue; do not invent Advance/Done.`;
|
|
2806
|
+
default:
|
|
2807
|
+
return "";
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
var ReviewEngine = class {
|
|
2811
|
+
constructor(store, config) {
|
|
2812
|
+
this.store = store;
|
|
2813
|
+
this.config = config;
|
|
2814
|
+
}
|
|
2815
|
+
render(kind, vars) {
|
|
2816
|
+
return (this.config.renderFollowup ?? defaultRender)(kind, vars);
|
|
2817
|
+
}
|
|
2818
|
+
lens(roundIndex) {
|
|
2819
|
+
const rounds = this.config.confirmRounds;
|
|
2820
|
+
return (this.config.resolveLens ?? getLens)(roundIndex, rounds);
|
|
2821
|
+
}
|
|
2822
|
+
/**
|
|
2823
|
+
* FS trust root: StateStore is authoritative.
|
|
2824
|
+
* config.projectRoot must not widen the boundary (mismatched/evil config).
|
|
2825
|
+
*/
|
|
2826
|
+
trustedProjectRoot() {
|
|
2827
|
+
return normalizeProjectRoot(this.store.projectRoot) ?? normalizeProjectRoot(this.config.projectRoot);
|
|
2828
|
+
}
|
|
2829
|
+
/**
|
|
2830
|
+
* Parse session checklist only when realpath stays under the project root.
|
|
2831
|
+
* O_NOFOLLOW alone cannot stop intermediate directory symlink escapes or a
|
|
2832
|
+
* poisoned absolute checklist_path in the session row.
|
|
2833
|
+
*/
|
|
2834
|
+
parseSessionChecklist(session) {
|
|
2835
|
+
const checklistPath = session.checklist_path;
|
|
2836
|
+
if (!checklistPath) return null;
|
|
2837
|
+
const root = this.trustedProjectRoot();
|
|
2838
|
+
if (!root || !isRealpathInsideProject(root, checklistPath)) {
|
|
2839
|
+
return null;
|
|
2840
|
+
}
|
|
2841
|
+
try {
|
|
2842
|
+
const cl = parseChecklist(checklistPath, { projectRoot: root });
|
|
2843
|
+
return {
|
|
2844
|
+
unchecked: countUnchecked(cl),
|
|
2845
|
+
currentItem: firstUnchecked(cl),
|
|
2846
|
+
followingItem: secondUnchecked(cl),
|
|
2847
|
+
checklist: cl
|
|
2848
|
+
};
|
|
2849
|
+
} catch {
|
|
2850
|
+
return null;
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
reviewChainResetFields() {
|
|
2854
|
+
return {
|
|
2855
|
+
confirm_left: null,
|
|
2856
|
+
fix_round: 0,
|
|
2857
|
+
code_edited: 0,
|
|
2858
|
+
item_confirm_complete: 0,
|
|
2859
|
+
reviewing_item_id: null
|
|
2860
|
+
};
|
|
2861
|
+
}
|
|
2862
|
+
renderAdvanceOrDone(targets) {
|
|
2863
|
+
const isAdvance = targets.next != null;
|
|
2864
|
+
const message = isAdvance ? this.render("advance", {
|
|
2865
|
+
currentId: targets.current?.id ?? "",
|
|
2483
2866
|
currentTitle: targets.current?.title ?? "",
|
|
2484
2867
|
nextId: targets.next?.id ?? "",
|
|
2485
2868
|
nextTitle: targets.next?.title ?? ""
|
|
@@ -2522,6 +2905,31 @@ var ReviewEngine = class {
|
|
|
2522
2905
|
return fromPending;
|
|
2523
2906
|
});
|
|
2524
2907
|
}
|
|
2908
|
+
/**
|
|
2909
|
+
* Stop-time arm when Shell (or other non-afterFileEdit) dirtied product paths.
|
|
2910
|
+
* - `clean`: no product dirty → caller may soft E0
|
|
2911
|
+
* - `armed`: code_edited set → caller must prefer E2 (never soft)
|
|
2912
|
+
* - `dirty_unarmed`: dirty seen but arm failed → caller must not soft
|
|
2913
|
+
* (otherwise unreviewed product could soft-advance)
|
|
2914
|
+
*/
|
|
2915
|
+
maybeArmCodeEditedFromDirtyTree(session) {
|
|
2916
|
+
const root = this.trustedProjectRoot();
|
|
2917
|
+
if (!root) return "clean";
|
|
2918
|
+
let dirty = false;
|
|
2919
|
+
try {
|
|
2920
|
+
dirty = hasDirtyProductCode(root);
|
|
2921
|
+
} catch {
|
|
2922
|
+
return "dirty_unarmed";
|
|
2923
|
+
}
|
|
2924
|
+
if (!dirty) return "clean";
|
|
2925
|
+
try {
|
|
2926
|
+
this.onCodeEdited(session.conversation_id);
|
|
2927
|
+
} catch {
|
|
2928
|
+
return "dirty_unarmed";
|
|
2929
|
+
}
|
|
2930
|
+
const live = this.store.getReviewChain(session.conversation_id);
|
|
2931
|
+
return live?.code_edited === 1 ? "armed" : "dirty_unarmed";
|
|
2932
|
+
}
|
|
2525
2933
|
handleStop(input) {
|
|
2526
2934
|
if (!this.store.isConversationIdOk(input.conversationId)) {
|
|
2527
2935
|
return null;
|
|
@@ -2687,24 +3095,62 @@ var ReviewEngine = class {
|
|
|
2687
3095
|
if (chainNow.confirm_left === null && chainNow.item_confirm_complete === 0 && inChain) {
|
|
2688
3096
|
return this.e3ArmConfirm(session, chainNow);
|
|
2689
3097
|
}
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
3098
|
+
if (chainNow.code_edited === 0 && isChecklistExecuting(session)) {
|
|
3099
|
+
const dirtyArm = this.maybeArmCodeEditedFromDirtyTree(session);
|
|
3100
|
+
if (dirtyArm === "dirty_unarmed") {
|
|
3101
|
+
return null;
|
|
3102
|
+
}
|
|
3103
|
+
if (dirtyArm === "armed") {
|
|
3104
|
+
const armed = this.store.getReviewChain(session.conversation_id) ?? chainNow;
|
|
3105
|
+
if (armed.code_edited === 1) {
|
|
3106
|
+
const tip = armed.pending_followup?.trim() ?? "";
|
|
3107
|
+
if (isRecoverOrStuckFollowupMessage(tip)) {
|
|
3108
|
+
const again = this.tryRedeliverPending(
|
|
3109
|
+
session.conversation_id,
|
|
3110
|
+
armed,
|
|
3111
|
+
events,
|
|
3112
|
+
transcriptPath
|
|
3113
|
+
);
|
|
3114
|
+
if (again) return again;
|
|
3115
|
+
return null;
|
|
3116
|
+
}
|
|
3117
|
+
const fix = this.e2Fix(session, armed);
|
|
3118
|
+
if (fix) return fix;
|
|
3119
|
+
const afterE2 = this.store.getReviewChain(session.conversation_id) ?? armed;
|
|
3120
|
+
const afterTip = afterE2.pending_followup?.trim() ?? "";
|
|
3121
|
+
if (isRecoverOrStuckFollowupMessage(afterTip)) {
|
|
3122
|
+
const again = this.tryRedeliverPending(
|
|
3123
|
+
session.conversation_id,
|
|
3124
|
+
afterE2,
|
|
3125
|
+
events,
|
|
3126
|
+
transcriptPath
|
|
3127
|
+
);
|
|
3128
|
+
if (again) return again;
|
|
3129
|
+
return null;
|
|
3130
|
+
}
|
|
3131
|
+
return null;
|
|
3132
|
+
}
|
|
3133
|
+
return null;
|
|
3134
|
+
}
|
|
3135
|
+
}
|
|
3136
|
+
return this.e0NoCodeContinue(session, chainNow);
|
|
3137
|
+
} catch (err) {
|
|
3138
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3139
|
+
if (msg.includes("No session for conversation") || msg.includes("Invalid conversation id")) {
|
|
3140
|
+
return null;
|
|
3141
|
+
}
|
|
3142
|
+
throw err;
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
pendingBlocksAdvance(conversationId, chain, events, transcriptPath) {
|
|
3146
|
+
const pending = chain.pending_followup?.trim();
|
|
3147
|
+
if (!pending) return false;
|
|
3148
|
+
if (events.length > 0 && automationFollowupPresent(events, pending)) {
|
|
3149
|
+
const cleared = this.clearMatchingPendingAndArmAmbient(
|
|
3150
|
+
conversationId,
|
|
3151
|
+
pending
|
|
3152
|
+
);
|
|
3153
|
+
if (!cleared) {
|
|
2708
3154
|
try {
|
|
2709
3155
|
const live = this.store.getReviewChain(conversationId)?.pending_followup?.trim() ?? "";
|
|
2710
3156
|
if (live) return true;
|
|
@@ -3202,10 +3648,10 @@ var ReviewEngine = class {
|
|
|
3202
3648
|
* salvage again — the prior tip does not cover the new failure.
|
|
3203
3649
|
*/
|
|
3204
3650
|
classifyCompletedOrphan(transcriptPath) {
|
|
3205
|
-
const
|
|
3206
|
-
if (!
|
|
3651
|
+
const path11 = transcriptPath?.trim();
|
|
3652
|
+
if (!path11) return "none";
|
|
3207
3653
|
try {
|
|
3208
|
-
const events = readTranscriptTail(
|
|
3654
|
+
const events = readTranscriptTail(path11);
|
|
3209
3655
|
const errIdx = latestUnresolvedTurnEndedErrorIndex(events);
|
|
3210
3656
|
if (errIdx < 0) return "none";
|
|
3211
3657
|
for (let i = events.length - 1; i > errIdx; i--) {
|
|
@@ -3919,28 +4365,16 @@ var ReviewEngine = class {
|
|
|
3919
4365
|
}
|
|
3920
4366
|
const nextIdle = sess.idle_stop_count + 1;
|
|
3921
4367
|
const nowStuck = nextIdle >= this.config.maxIdleStops;
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
paused: 1,
|
|
3929
|
-
paused_reason: "stuck",
|
|
3930
|
-
armed: 0
|
|
3931
|
-
});
|
|
3932
|
-
} else {
|
|
3933
|
-
this.store.upsertSession({
|
|
3934
|
-
conversation_id: cid2,
|
|
3935
|
-
project_root: sess.project_root,
|
|
3936
|
-
code_root: sess.code_root,
|
|
3937
|
-
idle_stop_count: nextIdle
|
|
3938
|
-
});
|
|
3939
|
-
}
|
|
4368
|
+
this.store.upsertSession({
|
|
4369
|
+
conversation_id: cid2,
|
|
4370
|
+
project_root: sess.project_root,
|
|
4371
|
+
code_root: sess.code_root,
|
|
4372
|
+
idle_stop_count: nextIdle
|
|
4373
|
+
});
|
|
3940
4374
|
const kind = nowStuck ? "stuck" : "need_evidence";
|
|
3941
4375
|
const title = (lockedItem.title ?? "").trim();
|
|
3942
4376
|
const message = this.render(
|
|
3943
|
-
|
|
4377
|
+
nowStuck ? "stuck_soft" : "need_evidence",
|
|
3944
4378
|
nowStuck ? {} : {
|
|
3945
4379
|
currentId: lockedItem.id,
|
|
3946
4380
|
currentTitle: title,
|
|
@@ -4299,8 +4733,8 @@ var ReviewEngine = class {
|
|
|
4299
4733
|
let unchecked = checklist.unchecked;
|
|
4300
4734
|
let next = checklist.next;
|
|
4301
4735
|
let targets = null;
|
|
4302
|
-
const
|
|
4303
|
-
const onChecklistPath = isChecklistExecuting(lockedSession) &&
|
|
4736
|
+
const path11 = lockedSession.checklist_path?.trim() ?? "";
|
|
4737
|
+
const onChecklistPath = isChecklistExecuting(lockedSession) && path11.length > 0;
|
|
4304
4738
|
if (onChecklistPath) {
|
|
4305
4739
|
const refreshed = this.parseSessionChecklist(lockedSession);
|
|
4306
4740
|
if (!refreshed?.checklist) {
|
|
@@ -4432,28 +4866,7 @@ var ReviewEngine = class {
|
|
|
4432
4866
|
} catch {
|
|
4433
4867
|
}
|
|
4434
4868
|
}
|
|
4435
|
-
|
|
4436
|
-
const next = session.idle_stop_count + 1;
|
|
4437
|
-
if (next >= this.config.maxIdleStops) {
|
|
4438
|
-
this.store.upsertSession({
|
|
4439
|
-
conversation_id: session.conversation_id,
|
|
4440
|
-
project_root: session.project_root,
|
|
4441
|
-
code_root: session.code_root,
|
|
4442
|
-
idle_stop_count: next,
|
|
4443
|
-
paused: 1,
|
|
4444
|
-
paused_reason: "stuck",
|
|
4445
|
-
armed: 0
|
|
4446
|
-
});
|
|
4447
|
-
} else {
|
|
4448
|
-
this.store.upsertSession({
|
|
4449
|
-
conversation_id: session.conversation_id,
|
|
4450
|
-
project_root: session.project_root,
|
|
4451
|
-
code_root: session.code_root,
|
|
4452
|
-
idle_stop_count: next
|
|
4453
|
-
});
|
|
4454
|
-
}
|
|
4455
|
-
}
|
|
4456
|
-
/** After a no-progress stop that didn't inject, check stuck threshold. */
|
|
4869
|
+
/** After a hard-stuck pause that didn't inject, surface stuck tip. */
|
|
4457
4870
|
checkStuck(session) {
|
|
4458
4871
|
const fresh = this.store.getSession(session.conversation_id);
|
|
4459
4872
|
if (!fresh) return null;
|
|
@@ -4507,10 +4920,11 @@ function applyOn(store, conversationId, projectRoot, opts) {
|
|
|
4507
4920
|
}
|
|
4508
4921
|
projectRoot = root;
|
|
4509
4922
|
const session = store.getSession(conversationId);
|
|
4923
|
+
const onBlockedMsg = "Autopilot is executing. Send Autopilot OFF, REPLAN, or RESUME before ON.";
|
|
4510
4924
|
if (session?.phase === "executing") {
|
|
4511
4925
|
return {
|
|
4512
4926
|
ok: false,
|
|
4513
|
-
userMessage:
|
|
4927
|
+
userMessage: onBlockedMsg
|
|
4514
4928
|
};
|
|
4515
4929
|
}
|
|
4516
4930
|
if (opts?.slug !== void 0 && !isSafeTrackSlug(opts.slug)) {
|
|
@@ -4520,46 +4934,39 @@ function applyOn(store, conversationId, projectRoot, opts) {
|
|
|
4520
4934
|
userMessage: `Invalid track slug "${sanitizeSessionDisplayText(raw).slice(0, 64)}".`
|
|
4521
4935
|
};
|
|
4522
4936
|
}
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
const
|
|
4937
|
+
return store.exclusiveWrite(() => {
|
|
4938
|
+
const live = store.getSession(conversationId);
|
|
4939
|
+
if (live?.phase === "executing") {
|
|
4940
|
+
return {
|
|
4941
|
+
commit: false,
|
|
4942
|
+
value: { ok: false, userMessage: onBlockedMsg }
|
|
4943
|
+
};
|
|
4944
|
+
}
|
|
4945
|
+
const prevTid = live?.track_id ?? "_pending";
|
|
4946
|
+
const trackId = opts?.slug ?? (live?.track_id && isBoundRunTrackId(live.track_id) ? live.track_id : "_pending");
|
|
4947
|
+
const checklistPath = trackId === prevTid ? live?.checklist_path ?? "" : "";
|
|
4948
|
+
const platform = resolveSessionPlatform(
|
|
4949
|
+
opts?.platform,
|
|
4950
|
+
live?.platform ?? "cursor"
|
|
4951
|
+
);
|
|
4952
|
+
const row = store.upsertSession({
|
|
4532
4953
|
conversation_id: conversationId,
|
|
4533
4954
|
project_root: projectRoot,
|
|
4534
4955
|
code_root: projectRoot,
|
|
4956
|
+
platform,
|
|
4535
4957
|
phase: "planning",
|
|
4536
4958
|
armed: 0,
|
|
4537
4959
|
paused: 0,
|
|
4538
4960
|
paused_reason: null,
|
|
4539
4961
|
track_id: trackId,
|
|
4540
4962
|
checklist_path: checklistPath,
|
|
4963
|
+
// ON returns to planning — drop mid-flow run/replan pick state.
|
|
4541
4964
|
pending_action: null,
|
|
4542
|
-
track_candidates_json: null
|
|
4543
|
-
platform
|
|
4965
|
+
track_candidates_json: null
|
|
4544
4966
|
});
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
const s = store.upsertSession({
|
|
4548
|
-
conversation_id: conversationId,
|
|
4549
|
-
project_root: projectRoot,
|
|
4550
|
-
code_root: projectRoot,
|
|
4551
|
-
platform,
|
|
4552
|
-
phase: "planning",
|
|
4553
|
-
armed: 0,
|
|
4554
|
-
paused: 0,
|
|
4555
|
-
paused_reason: null,
|
|
4556
|
-
track_id: trackId,
|
|
4557
|
-
checklist_path: checklistPath,
|
|
4558
|
-
// ON returns to planning — drop mid-flow run/replan pick state.
|
|
4559
|
-
pending_action: null,
|
|
4560
|
-
track_candidates_json: null
|
|
4967
|
+
store.clearPendingFollowupIf(conversationId, isTerminalFollowupMessage);
|
|
4968
|
+
return { commit: true, value: { ok: true, session: row } };
|
|
4561
4969
|
});
|
|
4562
|
-
return { ok: true, session: s };
|
|
4563
4970
|
}
|
|
4564
4971
|
function finishLocalResume(store, conversationId, session) {
|
|
4565
4972
|
const snap = store.getSession(conversationId);
|
|
@@ -4664,8 +5071,8 @@ function applyResumeReview(store, conversationId) {
|
|
|
4664
5071
|
}
|
|
4665
5072
|
|
|
4666
5073
|
// ../core/src/project-config.ts
|
|
4667
|
-
import
|
|
4668
|
-
import
|
|
5074
|
+
import fs8 from "node:fs";
|
|
5075
|
+
import path8 from "node:path";
|
|
4669
5076
|
var MAX_CONFIG_BYTES = 1e6;
|
|
4670
5077
|
var DEFAULT_PROJECT_REVIEW_CONFIG = {
|
|
4671
5078
|
confirmRounds: 5,
|
|
@@ -4825,28 +5232,28 @@ function loadProjectReviewConfig(projectRoot) {
|
|
|
4825
5232
|
if (!root) {
|
|
4826
5233
|
return cloneDefaultProjectReviewConfig();
|
|
4827
5234
|
}
|
|
4828
|
-
const configPath =
|
|
5235
|
+
const configPath = path8.join(root, ".autopilot", "config.yml");
|
|
4829
5236
|
try {
|
|
4830
|
-
const nofollow = typeof
|
|
5237
|
+
const nofollow = typeof fs8.constants.O_NOFOLLOW === "number" ? fs8.constants.O_NOFOLLOW : 0;
|
|
4831
5238
|
if (nofollow === 0) {
|
|
4832
|
-
if (!
|
|
4833
|
-
if (
|
|
5239
|
+
if (!fs8.existsSync(configPath)) return cloneDefaultProjectReviewConfig();
|
|
5240
|
+
if (fs8.lstatSync(configPath).isSymbolicLink()) {
|
|
4834
5241
|
return cloneDefaultProjectReviewConfig();
|
|
4835
5242
|
}
|
|
4836
5243
|
}
|
|
4837
5244
|
let fd;
|
|
4838
5245
|
try {
|
|
4839
|
-
fd =
|
|
5246
|
+
fd = fs8.openSync(configPath, fs8.constants.O_RDONLY | nofollow);
|
|
4840
5247
|
} catch {
|
|
4841
5248
|
return cloneDefaultProjectReviewConfig();
|
|
4842
5249
|
}
|
|
4843
5250
|
let raw;
|
|
4844
5251
|
try {
|
|
4845
|
-
const st =
|
|
5252
|
+
const st = fs8.fstatSync(fd);
|
|
4846
5253
|
if (!st.isFile() || st.size > MAX_CONFIG_BYTES) {
|
|
4847
5254
|
return cloneDefaultProjectReviewConfig();
|
|
4848
5255
|
}
|
|
4849
|
-
const lst =
|
|
5256
|
+
const lst = fs8.lstatSync(configPath);
|
|
4850
5257
|
if (lst.isSymbolicLink() || !lst.isFile()) {
|
|
4851
5258
|
return cloneDefaultProjectReviewConfig();
|
|
4852
5259
|
}
|
|
@@ -4857,10 +5264,10 @@ function loadProjectReviewConfig(projectRoot) {
|
|
|
4857
5264
|
return cloneDefaultProjectReviewConfig();
|
|
4858
5265
|
}
|
|
4859
5266
|
const buf = Buffer.alloc(st.size);
|
|
4860
|
-
const n =
|
|
5267
|
+
const n = fs8.readSync(fd, buf, 0, st.size, 0);
|
|
4861
5268
|
raw = buf.subarray(0, n).toString("utf8");
|
|
4862
5269
|
} finally {
|
|
4863
|
-
|
|
5270
|
+
fs8.closeSync(fd);
|
|
4864
5271
|
}
|
|
4865
5272
|
if (Buffer.byteLength(raw, "utf8") > MAX_CONFIG_BYTES) {
|
|
4866
5273
|
return cloneDefaultProjectReviewConfig();
|
|
@@ -4960,6 +5367,12 @@ function createRenderFollowup(bundle) {
|
|
|
4960
5367
|
);
|
|
4961
5368
|
case "stuck":
|
|
4962
5369
|
return renderTemplate(f.stuck ?? "", vars);
|
|
5370
|
+
case "stuck_soft":
|
|
5371
|
+
return renderTemplate(
|
|
5372
|
+
// Never fall back to hard `stuck` (RESUME-required) — that defeats C2.
|
|
5373
|
+
f.stuck_soft ?? "Stuck: no progress for several stops (missing completion evidence). Change strategy, write matching .autopilot/verify-last.json, then end the turn. Session stays armed \u2014 Autopilot RESUME is not required unless the session was paused.",
|
|
5374
|
+
vars
|
|
5375
|
+
);
|
|
4963
5376
|
case "verify_fix":
|
|
4964
5377
|
return renderTemplate(
|
|
4965
5378
|
f.verify_fix ?? "Verify failed ({reason}). Fix verify commands and rewrite verify-last.json; do not advance.",
|
|
@@ -5012,12 +5425,12 @@ function createConfiguredReviewEngine(store, projectRoot, localeBundle, preloade
|
|
|
5012
5425
|
}
|
|
5013
5426
|
|
|
5014
5427
|
// ../core/src/phase-actions.ts
|
|
5015
|
-
import
|
|
5016
|
-
import
|
|
5428
|
+
import fs10 from "node:fs";
|
|
5429
|
+
import path10 from "node:path";
|
|
5017
5430
|
|
|
5018
5431
|
// ../core/src/list-tracks.ts
|
|
5019
|
-
import
|
|
5020
|
-
import
|
|
5432
|
+
import fs9 from "node:fs";
|
|
5433
|
+
import path9 from "node:path";
|
|
5021
5434
|
function isRunnableTrack(t) {
|
|
5022
5435
|
if (t.paused) return false;
|
|
5023
5436
|
const unchecked = t.checklistTotal - t.checklistDone;
|
|
@@ -5028,19 +5441,19 @@ function readPlansDir(root, plansDir = "plans") {
|
|
|
5028
5441
|
if (typeof plansDir !== "string" || !root || root.includes("\0") || plansDir.includes("\0")) {
|
|
5029
5442
|
return [];
|
|
5030
5443
|
}
|
|
5031
|
-
const dir =
|
|
5444
|
+
const dir = path9.join(root, plansDir);
|
|
5032
5445
|
try {
|
|
5033
|
-
const lst =
|
|
5446
|
+
const lst = fs9.lstatSync(dir);
|
|
5034
5447
|
if (lst.isSymbolicLink() || !lst.isDirectory()) return [];
|
|
5035
5448
|
if (!isRealpathInsideProject(root, dir)) return [];
|
|
5036
5449
|
} catch {
|
|
5037
5450
|
return [];
|
|
5038
5451
|
}
|
|
5039
|
-
const names =
|
|
5452
|
+
const names = fs9.readdirSync(dir, { withFileTypes: true }).filter(
|
|
5040
5453
|
(d) => d.isDirectory() && !d.isSymbolicLink() && isSafeTrackSlug(d.name)
|
|
5041
5454
|
).map((d) => d.name);
|
|
5042
5455
|
try {
|
|
5043
|
-
const lst =
|
|
5456
|
+
const lst = fs9.lstatSync(dir);
|
|
5044
5457
|
if (lst.isSymbolicLink() || !lst.isDirectory()) return [];
|
|
5045
5458
|
if (!isRealpathInsideProject(root, dir)) return [];
|
|
5046
5459
|
} catch {
|
|
@@ -5054,27 +5467,27 @@ function titleFromPlan(planPath, slug, projectRoot) {
|
|
|
5054
5467
|
if (!root) return slug;
|
|
5055
5468
|
const maxBytes = 65536;
|
|
5056
5469
|
try {
|
|
5057
|
-
const nofollow = typeof
|
|
5470
|
+
const nofollow = typeof fs9.constants.O_NOFOLLOW === "number" ? fs9.constants.O_NOFOLLOW : 0;
|
|
5058
5471
|
if (nofollow === 0) {
|
|
5059
|
-
const lst =
|
|
5472
|
+
const lst = fs9.lstatSync(planPath);
|
|
5060
5473
|
if (lst.isSymbolicLink() || !lst.isFile()) return slug;
|
|
5061
5474
|
}
|
|
5062
|
-
const fd =
|
|
5475
|
+
const fd = fs9.openSync(planPath, fs9.constants.O_RDONLY | nofollow);
|
|
5063
5476
|
try {
|
|
5064
|
-
const st =
|
|
5477
|
+
const st = fs9.fstatSync(fd);
|
|
5065
5478
|
if (!st.isFile() || st.size <= 0) return slug;
|
|
5066
|
-
const lst =
|
|
5479
|
+
const lst = fs9.lstatSync(planPath);
|
|
5067
5480
|
if (lst.isSymbolicLink() || !lst.isFile()) return slug;
|
|
5068
5481
|
if (lst.ino !== st.ino || lst.dev !== st.dev) return slug;
|
|
5069
5482
|
if (!isRealpathInsideProject(root, planPath)) return slug;
|
|
5070
5483
|
const len = Math.min(st.size, maxBytes);
|
|
5071
5484
|
const buf = Buffer.alloc(len);
|
|
5072
|
-
const n =
|
|
5485
|
+
const n = fs9.readSync(fd, buf, 0, len, 0);
|
|
5073
5486
|
const first = buf.subarray(0, n).toString("utf8").split(/\r?\n/)[0] ?? "";
|
|
5074
5487
|
const m = first.match(/^#\s+(.+)/);
|
|
5075
5488
|
return m?.[1]?.trim() ?? slug;
|
|
5076
5489
|
} finally {
|
|
5077
|
-
|
|
5490
|
+
fs9.closeSync(fd);
|
|
5078
5491
|
}
|
|
5079
5492
|
} catch {
|
|
5080
5493
|
return slug;
|
|
@@ -5090,16 +5503,16 @@ function listTracks(root, store, filter = "all", plansDir = "plans") {
|
|
|
5090
5503
|
const slugs = readPlansDir(root, plansDir);
|
|
5091
5504
|
const tracks = [];
|
|
5092
5505
|
for (const slug of slugs) {
|
|
5093
|
-
const trackDir =
|
|
5506
|
+
const trackDir = path9.join(root, plansDir, slug);
|
|
5094
5507
|
try {
|
|
5095
|
-
const lst =
|
|
5508
|
+
const lst = fs9.lstatSync(trackDir);
|
|
5096
5509
|
if (lst.isSymbolicLink() || !lst.isDirectory()) continue;
|
|
5097
5510
|
if (!isRealpathInsideProject(root, trackDir)) continue;
|
|
5098
5511
|
} catch {
|
|
5099
5512
|
continue;
|
|
5100
5513
|
}
|
|
5101
|
-
const planPath =
|
|
5102
|
-
const checklistPath =
|
|
5514
|
+
const planPath = path9.join(trackDir, "plan.md");
|
|
5515
|
+
const checklistPath = path9.join(trackDir, "checklist.md");
|
|
5103
5516
|
let checklistTotal = 0;
|
|
5104
5517
|
let checklistDone = 0;
|
|
5105
5518
|
const checklistInProject = isRealpathInsideProject(root, checklistPath);
|
|
@@ -5171,7 +5584,7 @@ function canEnterExecuting(options) {
|
|
|
5171
5584
|
if (!root) {
|
|
5172
5585
|
return { ok: false, reason: "invalid project root" };
|
|
5173
5586
|
}
|
|
5174
|
-
if (!checklistPath || !
|
|
5587
|
+
if (!checklistPath || !fs9.existsSync(checklistPath)) {
|
|
5175
5588
|
return { ok: false, reason: "checklist missing" };
|
|
5176
5589
|
}
|
|
5177
5590
|
if (!isRealpathInsideProject(root, checklistPath)) {
|
|
@@ -5220,8 +5633,8 @@ function nowIso2() {
|
|
|
5220
5633
|
}
|
|
5221
5634
|
function checklistPathFor(projectRoot, slug, plansDir) {
|
|
5222
5635
|
const root = normalizeProjectRoot(projectRoot);
|
|
5223
|
-
if (!root) return
|
|
5224
|
-
return
|
|
5636
|
+
if (!root) return path10.join(plansDir, slug, "checklist.md");
|
|
5637
|
+
return path10.join(root, plansDir, slug, "checklist.md");
|
|
5225
5638
|
}
|
|
5226
5639
|
function sameChecklistBinding(stored, rebuilt, projectRoot) {
|
|
5227
5640
|
if (stored === rebuilt) return true;
|
|
@@ -5231,8 +5644,8 @@ function sameChecklistBinding(stored, rebuilt, projectRoot) {
|
|
|
5231
5644
|
const root = normalizeProjectRoot(projectRoot);
|
|
5232
5645
|
if (!root) return false;
|
|
5233
5646
|
try {
|
|
5234
|
-
const absStored =
|
|
5235
|
-
const absRebuilt =
|
|
5647
|
+
const absStored = path10.isAbsolute(stored) ? path10.resolve(stored) : path10.resolve(root, stored);
|
|
5648
|
+
const absRebuilt = path10.isAbsolute(rebuilt) ? path10.resolve(rebuilt) : path10.resolve(root, rebuilt);
|
|
5236
5649
|
return absStored === absRebuilt;
|
|
5237
5650
|
} catch {
|
|
5238
5651
|
return false;
|
|
@@ -5252,8 +5665,8 @@ function isChecklistPathAllowed(projectRoot, checklistPath) {
|
|
|
5252
5665
|
const root = normalizeProjectRoot(projectRoot);
|
|
5253
5666
|
if (!root) return false;
|
|
5254
5667
|
try {
|
|
5255
|
-
|
|
5256
|
-
|
|
5668
|
+
fs10.lstatSync(
|
|
5669
|
+
path10.isAbsolute(checklistPath) ? checklistPath : path10.resolve(root, checklistPath)
|
|
5257
5670
|
);
|
|
5258
5671
|
return isRealpathInsideProject(root, checklistPath);
|
|
5259
5672
|
} catch {
|
|
@@ -5290,8 +5703,8 @@ function upsertTrack(store, slug, checklistPath, plansDir, projectRoot) {
|
|
|
5290
5703
|
const ts = nowIso2();
|
|
5291
5704
|
const root = normalizeProjectRoot(projectRoot);
|
|
5292
5705
|
if (!root) return;
|
|
5293
|
-
const planPath =
|
|
5294
|
-
const briefPath =
|
|
5706
|
+
const planPath = path10.join(root, plansDir, slug, "plan.md");
|
|
5707
|
+
const briefPath = path10.join(root, plansDir, slug, "brief.md");
|
|
5295
5708
|
store.db.prepare(
|
|
5296
5709
|
`INSERT INTO tracks (track_id, slug, checklist_path, plan_path, brief_path, updated_at)
|
|
5297
5710
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
@@ -5305,8 +5718,8 @@ function upsertTrack(store, slug, checklistPath, plansDir, projectRoot) {
|
|
|
5305
5718
|
slug,
|
|
5306
5719
|
slug,
|
|
5307
5720
|
checklistPath,
|
|
5308
|
-
|
|
5309
|
-
|
|
5721
|
+
fs10.existsSync(planPath) ? planPath : null,
|
|
5722
|
+
fs10.existsSync(briefPath) ? briefPath : null,
|
|
5310
5723
|
ts
|
|
5311
5724
|
);
|
|
5312
5725
|
}
|
|
@@ -5711,303 +6124,6 @@ function applyTrackPick(store, conversationId, projectRoot, pick, opts) {
|
|
|
5711
6124
|
});
|
|
5712
6125
|
}
|
|
5713
6126
|
|
|
5714
|
-
// ../core/src/code-edit-detector.ts
|
|
5715
|
-
import { spawnSync } from "node:child_process";
|
|
5716
|
-
|
|
5717
|
-
// ../core/src/autopilot-ignore.ts
|
|
5718
|
-
import fs10 from "node:fs";
|
|
5719
|
-
import path9 from "node:path";
|
|
5720
|
-
var DEFAULT_AUTOPILOT_IGNORE_TEXT = `# Autopilot \u2014 paths that do NOT trigger self-review (gitignore syntax).
|
|
5721
|
-
#
|
|
5722
|
-
# What this file is:
|
|
5723
|
-
# - Controls whether an afterFileEdit counts as "product code" (opens fix/confirm).
|
|
5724
|
-
# - Does NOT change \`git diff\` / \`git status\` output (that is \`.gitignore\`).
|
|
5725
|
-
# - Review followups ask the agent to skip these paths when reading diffs (soft).
|
|
5726
|
-
#
|
|
5727
|
-
# Semantics:
|
|
5728
|
-
# - Same glob rules as gitignore; last matching pattern wins.
|
|
5729
|
-
# - Use \`!\` to force-include an exception (e.g. \`!docs/feed/**/*.yml\`).
|
|
5730
|
-
# - Markdown (*.md / *.mdx) is NOT ignored by default \u2014 design docs can be reviewed.
|
|
5731
|
-
# - \`docs/**\` is NOT ignored by default.
|
|
5732
|
-
# - Also skip untracked paths ignored by \`.gitignore\` (tracked files still count).
|
|
5733
|
-
#
|
|
5734
|
-
# Later (not implemented): hard-filtered review-diff / path ledger \u2014 see
|
|
5735
|
-
# docs/autopilot/workflows/autopilot-executing.md (B2 strong).
|
|
5736
|
-
|
|
5737
|
-
# Runtime / editor (prefer also listing these in .gitignore)
|
|
5738
|
-
.autopilot/**
|
|
5739
|
-
.cursor/**
|
|
5740
|
-
.claude/**
|
|
5741
|
-
|
|
5742
|
-
# Planning artifacts
|
|
5743
|
-
plans/**
|
|
5744
|
-
|
|
5745
|
-
# Common build / vendor trees
|
|
5746
|
-
node_modules/**
|
|
5747
|
-
dist/**
|
|
5748
|
-
build/**
|
|
5749
|
-
out/**
|
|
5750
|
-
target/**
|
|
5751
|
-
.target/**
|
|
5752
|
-
coverage/**
|
|
5753
|
-
.venv/**
|
|
5754
|
-
venv/**
|
|
5755
|
-
__pycache__/**
|
|
5756
|
-
|
|
5757
|
-
# Lockfiles / package manager noise
|
|
5758
|
-
package-lock.json
|
|
5759
|
-
pnpm-lock.yaml
|
|
5760
|
-
yarn.lock
|
|
5761
|
-
bun.lock
|
|
5762
|
-
bun.lockb
|
|
5763
|
-
Cargo.lock
|
|
5764
|
-
poetry.lock
|
|
5765
|
-
composer.lock
|
|
5766
|
-
|
|
5767
|
-
# Media / binary (do not trigger self-review)
|
|
5768
|
-
*.png
|
|
5769
|
-
*.jpg
|
|
5770
|
-
*.jpeg
|
|
5771
|
-
*.gif
|
|
5772
|
-
*.webp
|
|
5773
|
-
*.ico
|
|
5774
|
-
*.svg
|
|
5775
|
-
*.bmp
|
|
5776
|
-
*.mp3
|
|
5777
|
-
*.mp4
|
|
5778
|
-
*.wav
|
|
5779
|
-
*.webm
|
|
5780
|
-
*.mov
|
|
5781
|
-
*.woff
|
|
5782
|
-
*.woff2
|
|
5783
|
-
*.ttf
|
|
5784
|
-
*.otf
|
|
5785
|
-
*.eot
|
|
5786
|
-
*.pdf
|
|
5787
|
-
*.zip
|
|
5788
|
-
*.gz
|
|
5789
|
-
*.tgz
|
|
5790
|
-
*.7z
|
|
5791
|
-
*.rar
|
|
5792
|
-
*.jar
|
|
5793
|
-
*.class
|
|
5794
|
-
*.o
|
|
5795
|
-
*.a
|
|
5796
|
-
*.so
|
|
5797
|
-
*.dylib
|
|
5798
|
-
*.dll
|
|
5799
|
-
*.exe
|
|
5800
|
-
*.wasm
|
|
5801
|
-
|
|
5802
|
-
# Prose / data noise
|
|
5803
|
-
*.txt
|
|
5804
|
-
*.html
|
|
5805
|
-
*.htm
|
|
5806
|
-
*.csv
|
|
5807
|
-
*.tsv
|
|
5808
|
-
*.log
|
|
5809
|
-
*.map
|
|
5810
|
-
*.min.js
|
|
5811
|
-
*.min.css
|
|
5812
|
-
`;
|
|
5813
|
-
var MAX_AUTOPILOT_IGNORE_BYTES = 1e6;
|
|
5814
|
-
var MAX_AUTOPILOT_IGNORE_LINE_CHARS = 4096;
|
|
5815
|
-
var MAX_AUTOPILOT_IGNORE_PATTERNS = 1e4;
|
|
5816
|
-
var ignoreCache = /* @__PURE__ */ new Map();
|
|
5817
|
-
function escapeRegexChar(ch) {
|
|
5818
|
-
return ch.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
|
|
5819
|
-
}
|
|
5820
|
-
function globBodyToRegex(glob) {
|
|
5821
|
-
let out = "";
|
|
5822
|
-
for (let i = 0; i < glob.length; ) {
|
|
5823
|
-
const ch = glob[i];
|
|
5824
|
-
if (ch === "*" && glob[i + 1] === "*") {
|
|
5825
|
-
if (glob[i + 2] === "/") {
|
|
5826
|
-
out += "(?:.*/)?";
|
|
5827
|
-
i += 3;
|
|
5828
|
-
continue;
|
|
5829
|
-
}
|
|
5830
|
-
out += ".*";
|
|
5831
|
-
i += 2;
|
|
5832
|
-
continue;
|
|
5833
|
-
}
|
|
5834
|
-
if (ch === "*") {
|
|
5835
|
-
out += "[^/]*";
|
|
5836
|
-
i += 1;
|
|
5837
|
-
continue;
|
|
5838
|
-
}
|
|
5839
|
-
if (ch === "?") {
|
|
5840
|
-
out += "[^/]";
|
|
5841
|
-
i += 1;
|
|
5842
|
-
continue;
|
|
5843
|
-
}
|
|
5844
|
-
out += escapeRegexChar(ch);
|
|
5845
|
-
i += 1;
|
|
5846
|
-
}
|
|
5847
|
-
return out;
|
|
5848
|
-
}
|
|
5849
|
-
function compilePattern(raw) {
|
|
5850
|
-
let line = raw.trim();
|
|
5851
|
-
if (!line || line.startsWith("#")) return null;
|
|
5852
|
-
if (line.length > MAX_AUTOPILOT_IGNORE_LINE_CHARS) return null;
|
|
5853
|
-
let negated = false;
|
|
5854
|
-
if (line.startsWith("!")) {
|
|
5855
|
-
negated = true;
|
|
5856
|
-
line = line.slice(1).trim();
|
|
5857
|
-
if (!line || line.startsWith("#")) return null;
|
|
5858
|
-
}
|
|
5859
|
-
const dirOnly = line.endsWith("/");
|
|
5860
|
-
if (dirOnly) line = line.slice(0, -1);
|
|
5861
|
-
if (!line) return null;
|
|
5862
|
-
if (line.length > MAX_AUTOPILOT_IGNORE_LINE_CHARS) return null;
|
|
5863
|
-
let anchored = false;
|
|
5864
|
-
if (line.startsWith("/")) {
|
|
5865
|
-
anchored = true;
|
|
5866
|
-
line = line.slice(1);
|
|
5867
|
-
}
|
|
5868
|
-
const body = globBodyToRegex(line);
|
|
5869
|
-
let regexSource;
|
|
5870
|
-
if (anchored) {
|
|
5871
|
-
regexSource = `^${body}`;
|
|
5872
|
-
if (dirOnly || line.endsWith("/**") || line.endsWith("/*")) {
|
|
5873
|
-
regexSource += "(?:/.*)?";
|
|
5874
|
-
}
|
|
5875
|
-
regexSource += "$";
|
|
5876
|
-
} else if (line.includes("/")) {
|
|
5877
|
-
regexSource = `(?:^|.*/)${body}`;
|
|
5878
|
-
if (dirOnly || line.endsWith("/**") || line.endsWith("/*")) {
|
|
5879
|
-
regexSource += "(?:/.*)?";
|
|
5880
|
-
}
|
|
5881
|
-
regexSource += "$";
|
|
5882
|
-
} else {
|
|
5883
|
-
regexSource = `(?:^|.*/)?${body}$`;
|
|
5884
|
-
}
|
|
5885
|
-
return { negated, regex: new RegExp(regexSource) };
|
|
5886
|
-
}
|
|
5887
|
-
function parseAutopilotIgnore(text) {
|
|
5888
|
-
const patterns = [];
|
|
5889
|
-
for (const rawLine of text.split(/\r?\n/)) {
|
|
5890
|
-
const compiled = compilePattern(rawLine);
|
|
5891
|
-
if (!compiled) continue;
|
|
5892
|
-
patterns.push(compiled);
|
|
5893
|
-
if (patterns.length >= MAX_AUTOPILOT_IGNORE_PATTERNS) break;
|
|
5894
|
-
}
|
|
5895
|
-
return patterns;
|
|
5896
|
-
}
|
|
5897
|
-
var DEFAULT_AUTOPILOT_IGNORE_PATTERNS = parseAutopilotIgnore(
|
|
5898
|
-
DEFAULT_AUTOPILOT_IGNORE_TEXT
|
|
5899
|
-
);
|
|
5900
|
-
function normalizeRelativePath(filePath) {
|
|
5901
|
-
return filePath.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
5902
|
-
}
|
|
5903
|
-
function isAutopilotIgnoredPath(relativePath, patterns) {
|
|
5904
|
-
const norm = normalizeRelativePath(relativePath);
|
|
5905
|
-
let ignored = false;
|
|
5906
|
-
for (const pat of patterns) {
|
|
5907
|
-
if (pat.regex.test(norm)) {
|
|
5908
|
-
ignored = !pat.negated;
|
|
5909
|
-
}
|
|
5910
|
-
}
|
|
5911
|
-
return ignored;
|
|
5912
|
-
}
|
|
5913
|
-
function realpathForCompare(absPath) {
|
|
5914
|
-
try {
|
|
5915
|
-
return fs10.realpathSync(absPath);
|
|
5916
|
-
} catch {
|
|
5917
|
-
}
|
|
5918
|
-
let dir = path9.dirname(absPath);
|
|
5919
|
-
const base = path9.basename(absPath);
|
|
5920
|
-
const missing = [];
|
|
5921
|
-
for (; ; ) {
|
|
5922
|
-
try {
|
|
5923
|
-
const realDir = fs10.realpathSync(dir);
|
|
5924
|
-
return path9.join(realDir, ...missing, base);
|
|
5925
|
-
} catch {
|
|
5926
|
-
const parent = path9.dirname(dir);
|
|
5927
|
-
if (parent === dir) return absPath;
|
|
5928
|
-
missing.unshift(path9.basename(dir));
|
|
5929
|
-
dir = parent;
|
|
5930
|
-
}
|
|
5931
|
-
}
|
|
5932
|
-
}
|
|
5933
|
-
function toProjectRelativePath(filePath, projectRoot) {
|
|
5934
|
-
const posix = filePath.replace(/\\/g, "/");
|
|
5935
|
-
if (!projectRoot?.trim()) {
|
|
5936
|
-
return normalizeRelativePath(posix);
|
|
5937
|
-
}
|
|
5938
|
-
const root = realpathForCompare(path9.resolve(projectRoot));
|
|
5939
|
-
const abs = realpathForCompare(
|
|
5940
|
-
path9.isAbsolute(posix) ? path9.resolve(posix) : path9.resolve(root, posix)
|
|
5941
|
-
);
|
|
5942
|
-
const rel = path9.relative(root, abs);
|
|
5943
|
-
if (rel.startsWith("..") || path9.isAbsolute(rel)) {
|
|
5944
|
-
return "";
|
|
5945
|
-
}
|
|
5946
|
-
return normalizeRelativePath(rel.replace(/\\/g, "/"));
|
|
5947
|
-
}
|
|
5948
|
-
function loadAutopilotIgnorePatterns(projectRoot) {
|
|
5949
|
-
const root = path9.resolve(projectRoot);
|
|
5950
|
-
const filePath = path9.join(root, ".autopilotignore");
|
|
5951
|
-
let st;
|
|
5952
|
-
try {
|
|
5953
|
-
st = fs10.lstatSync(filePath);
|
|
5954
|
-
} catch {
|
|
5955
|
-
return DEFAULT_AUTOPILOT_IGNORE_PATTERNS;
|
|
5956
|
-
}
|
|
5957
|
-
if (st.isSymbolicLink() || !st.isFile()) {
|
|
5958
|
-
return DEFAULT_AUTOPILOT_IGNORE_PATTERNS;
|
|
5959
|
-
}
|
|
5960
|
-
if (st.size > MAX_AUTOPILOT_IGNORE_BYTES) {
|
|
5961
|
-
return DEFAULT_AUTOPILOT_IGNORE_PATTERNS;
|
|
5962
|
-
}
|
|
5963
|
-
const cached = ignoreCache.get(root);
|
|
5964
|
-
if (cached && cached.mtimeMs === st.mtimeMs) {
|
|
5965
|
-
return cached.patterns;
|
|
5966
|
-
}
|
|
5967
|
-
let text;
|
|
5968
|
-
try {
|
|
5969
|
-
text = fs10.readFileSync(filePath, "utf8");
|
|
5970
|
-
} catch {
|
|
5971
|
-
return DEFAULT_AUTOPILOT_IGNORE_PATTERNS;
|
|
5972
|
-
}
|
|
5973
|
-
if (text.length > MAX_AUTOPILOT_IGNORE_BYTES) {
|
|
5974
|
-
return DEFAULT_AUTOPILOT_IGNORE_PATTERNS;
|
|
5975
|
-
}
|
|
5976
|
-
const patterns = parseAutopilotIgnore(text);
|
|
5977
|
-
ignoreCache.set(root, { mtimeMs: st.mtimeMs, patterns });
|
|
5978
|
-
return patterns;
|
|
5979
|
-
}
|
|
5980
|
-
|
|
5981
|
-
// ../core/src/code-edit-detector.ts
|
|
5982
|
-
function isUntrackedGitIgnored(projectRoot, relativePath) {
|
|
5983
|
-
if (!relativePath || relativePath.includes("\0")) return false;
|
|
5984
|
-
try {
|
|
5985
|
-
const r = spawnSync(
|
|
5986
|
-
"git",
|
|
5987
|
-
["check-ignore", "-q", "--", relativePath],
|
|
5988
|
-
{
|
|
5989
|
-
cwd: projectRoot,
|
|
5990
|
-
encoding: "utf8",
|
|
5991
|
-
timeout: 5e3,
|
|
5992
|
-
windowsHide: true,
|
|
5993
|
-
shell: false
|
|
5994
|
-
}
|
|
5995
|
-
);
|
|
5996
|
-
return r.status === 0;
|
|
5997
|
-
} catch {
|
|
5998
|
-
return false;
|
|
5999
|
-
}
|
|
6000
|
-
}
|
|
6001
|
-
function isProductCodeEdit(filePath, opts) {
|
|
6002
|
-
const relative = toProjectRelativePath(filePath, opts?.projectRoot);
|
|
6003
|
-
if (!relative) return false;
|
|
6004
|
-
const patterns = opts?.projectRoot?.trim() ? loadAutopilotIgnorePatterns(opts.projectRoot) : DEFAULT_AUTOPILOT_IGNORE_PATTERNS;
|
|
6005
|
-
if (isAutopilotIgnoredPath(relative, patterns)) return false;
|
|
6006
|
-
const root = opts?.projectRoot?.trim();
|
|
6007
|
-
if (root && isUntrackedGitIgnored(root, relative)) return false;
|
|
6008
|
-
return true;
|
|
6009
|
-
}
|
|
6010
|
-
|
|
6011
6127
|
// ../ports/cursor/src/index.ts
|
|
6012
6128
|
function normalizeBlockSubmitMessage(message) {
|
|
6013
6129
|
return typeof message === "string" && message.trim().length > 0 ? message : "Request blocked.";
|