@brainervirus/workit-claude-code 7.3.0 → 7.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/dist/workit-hook.js +29 -30
- package/dist/workit.js +1571 -244
- package/package.json +3 -3
- package/skills/fanout/SKILL.md +27 -27
- package/skills/fanout/references/brief.md +5 -1
- package/skills/retro/SKILL.md +72 -0
- package/skills/retro/agents/openai.yaml +3 -0
- package/skills/retro/references/sources.md +35 -0
- package/skills/retro/references/steering.md +39 -0
- package/skills/review/SKILL.md +3 -2
- package/skills/shape/references/knowledge.md +3 -0
- package/skills/ship/SKILL.md +2 -1
package/dist/workit.js
CHANGED
|
@@ -65,7 +65,7 @@ var package_default;
|
|
|
65
65
|
var init_package = __esm(() => {
|
|
66
66
|
package_default = {
|
|
67
67
|
name: "@brainervirus/workit-cli",
|
|
68
|
-
version: "7.
|
|
68
|
+
version: "7.5.0",
|
|
69
69
|
private: false,
|
|
70
70
|
description: "Workit CLI — setup wizard, doctor, and task control for agentic coding workflows",
|
|
71
71
|
keywords: [
|
|
@@ -103,9 +103,9 @@ var init_package = __esm(() => {
|
|
|
103
103
|
},
|
|
104
104
|
dependencies: {
|
|
105
105
|
"@babel/parser": "7.29.9",
|
|
106
|
-
"@brainervirus/workit-core": "^7.
|
|
107
|
-
"@brainervirus/workit-cursor": "^7.
|
|
108
|
-
"@brainervirus/workit-opencode": "^7.
|
|
106
|
+
"@brainervirus/workit-core": "^7.5.0",
|
|
107
|
+
"@brainervirus/workit-cursor": "^7.5.0",
|
|
108
|
+
"@brainervirus/workit-opencode": "^7.5.0",
|
|
109
109
|
"@inkjs/ui": "2.0.0",
|
|
110
110
|
ink: "7.1.1",
|
|
111
111
|
react: "19.2.8"
|
|
@@ -63144,7 +63144,8 @@ var init_skill_manifests = __esm(() => {
|
|
|
63144
63144
|
"workit-test-audit",
|
|
63145
63145
|
"workit-deslop",
|
|
63146
63146
|
"workit-fanout",
|
|
63147
|
-
"workit-verify-app"
|
|
63147
|
+
"workit-verify-app",
|
|
63148
|
+
"workit-retro"
|
|
63148
63149
|
];
|
|
63149
63150
|
if (false) {}
|
|
63150
63151
|
});
|
|
@@ -69916,6 +69917,270 @@ var init_launch = __esm(() => {
|
|
|
69916
69917
|
init_upgrade();
|
|
69917
69918
|
});
|
|
69918
69919
|
|
|
69920
|
+
// packages/workit-core/src/knowledge.ts
|
|
69921
|
+
import { spawnSync as spawnSync14 } from "node:child_process";
|
|
69922
|
+
import fs17 from "node:fs";
|
|
69923
|
+
import path36 from "node:path";
|
|
69924
|
+
function lintKnowledge(root) {
|
|
69925
|
+
const seen = new Set;
|
|
69926
|
+
const unreadable = [];
|
|
69927
|
+
const load = (file) => {
|
|
69928
|
+
const doc = read(root, file);
|
|
69929
|
+
if (doc === "unreadable")
|
|
69930
|
+
unreadable.push(file);
|
|
69931
|
+
if (doc === null || doc === "unreadable" || seen.has(doc.real))
|
|
69932
|
+
return null;
|
|
69933
|
+
seen.add(doc.real);
|
|
69934
|
+
return doc;
|
|
69935
|
+
};
|
|
69936
|
+
const steering = STEERING_FILES.map(load).filter((doc) => doc !== null);
|
|
69937
|
+
const needBased = NEED_BASED_FILES.map(load).filter((doc) => doc !== null);
|
|
69938
|
+
const findings = [];
|
|
69939
|
+
for (const doc of steering) {
|
|
69940
|
+
const bytes = Buffer.byteLength(doc.text);
|
|
69941
|
+
if (bytes > AGENTS_BYTE_BUDGET)
|
|
69942
|
+
findings.push({
|
|
69943
|
+
rule: "agents-budget",
|
|
69944
|
+
file: doc.file,
|
|
69945
|
+
line: null,
|
|
69946
|
+
message: `${bytes} bytes, over the ${AGENTS_BYTE_BUDGET}-byte budget; move detail behind a pointer or into a check, and name what each addition removes`
|
|
69947
|
+
});
|
|
69948
|
+
}
|
|
69949
|
+
for (const doc of needBased)
|
|
69950
|
+
if (scaffoldOnly(doc))
|
|
69951
|
+
findings.push({
|
|
69952
|
+
rule: "scaffold-file",
|
|
69953
|
+
file: doc.file,
|
|
69954
|
+
line: null,
|
|
69955
|
+
message: "holds no entry (headings, comments or placeholders only); delete it, or create it in the same edit as its first real entry"
|
|
69956
|
+
});
|
|
69957
|
+
const tracked = trackedTopLevel(root);
|
|
69958
|
+
for (const doc of [...steering, ...needBased])
|
|
69959
|
+
findings.push(...brokenLinks(root, doc, tracked));
|
|
69960
|
+
const standards = needBased.find((doc) => doc.file === STANDARDS_FILE);
|
|
69961
|
+
if (standards)
|
|
69962
|
+
findings.push(...duplicateRules(steering, standards));
|
|
69963
|
+
return {
|
|
69964
|
+
root,
|
|
69965
|
+
budget: AGENTS_BYTE_BUDGET,
|
|
69966
|
+
files: [...steering, ...needBased].map((doc) => ({
|
|
69967
|
+
file: doc.file,
|
|
69968
|
+
bytes: Buffer.byteLength(doc.text)
|
|
69969
|
+
})),
|
|
69970
|
+
findings,
|
|
69971
|
+
unreadable
|
|
69972
|
+
};
|
|
69973
|
+
}
|
|
69974
|
+
var AGENTS_BYTE_BUDGET, STEERING_FILES, NEED_BASED_FILES, STANDARDS_FILE = "CODING_STANDARDS.md", MIN_RULE_WORDS = 6, FENCE, HEADING, TABLE_SEPARATOR, LIST_ITEM, PLACEHOLDER2, INDENTED_CODE, blank = (line) => (line ?? "").trim() === "", proseLines = (lines) => {
|
|
69975
|
+
const out = [];
|
|
69976
|
+
let fence = null;
|
|
69977
|
+
let comment = false;
|
|
69978
|
+
let indentedCode = false;
|
|
69979
|
+
let inList = false;
|
|
69980
|
+
lines.forEach((raw, index) => {
|
|
69981
|
+
if (fence) {
|
|
69982
|
+
const close = FENCE.exec(raw);
|
|
69983
|
+
if (close && close[1][0] === fence[0] && close[1].length >= fence.length && !close[2].trim())
|
|
69984
|
+
fence = null;
|
|
69985
|
+
return;
|
|
69986
|
+
}
|
|
69987
|
+
if (!comment) {
|
|
69988
|
+
const open = FENCE.exec(raw);
|
|
69989
|
+
if (open && !(open[1][0] === "`" && open[2].includes("`"))) {
|
|
69990
|
+
fence = open[1];
|
|
69991
|
+
indentedCode = false;
|
|
69992
|
+
return;
|
|
69993
|
+
}
|
|
69994
|
+
if (INDENTED_CODE.test(raw) && !blank(raw)) {
|
|
69995
|
+
if (indentedCode || !inList && blank(lines[index - 1])) {
|
|
69996
|
+
indentedCode = true;
|
|
69997
|
+
return;
|
|
69998
|
+
}
|
|
69999
|
+
} else if (!blank(raw)) {
|
|
70000
|
+
indentedCode = false;
|
|
70001
|
+
inList = LIST_ITEM.test(raw) || inList && /^\s/.test(raw);
|
|
70002
|
+
}
|
|
70003
|
+
}
|
|
70004
|
+
let text = raw;
|
|
70005
|
+
if (comment) {
|
|
70006
|
+
const end = text.indexOf("-->");
|
|
70007
|
+
if (end === -1)
|
|
70008
|
+
return;
|
|
70009
|
+
comment = false;
|
|
70010
|
+
text = text.slice(end + 3);
|
|
70011
|
+
}
|
|
70012
|
+
text = text.replace(/<!--[\s\S]*?-->/g, "");
|
|
70013
|
+
const open = text.indexOf("<!--");
|
|
70014
|
+
if (open !== -1) {
|
|
70015
|
+
comment = true;
|
|
70016
|
+
text = text.slice(0, open);
|
|
70017
|
+
}
|
|
70018
|
+
out.push({ index, text });
|
|
70019
|
+
});
|
|
70020
|
+
return out;
|
|
70021
|
+
}, withoutFrontMatter = (lines) => {
|
|
70022
|
+
if (lines[0]?.trim() !== "---")
|
|
70023
|
+
return lines;
|
|
70024
|
+
const end = lines.indexOf("---", 1);
|
|
70025
|
+
return end === -1 ? lines : [...lines.slice(0, end + 1).map(() => ""), ...lines.slice(end + 1)];
|
|
70026
|
+
}, read = (root, file) => {
|
|
70027
|
+
const full = path36.join(root, file);
|
|
70028
|
+
try {
|
|
70029
|
+
fs17.lstatSync(full);
|
|
70030
|
+
} catch {
|
|
70031
|
+
return null;
|
|
70032
|
+
}
|
|
70033
|
+
try {
|
|
70034
|
+
if (!fs17.statSync(full).isFile())
|
|
70035
|
+
return null;
|
|
70036
|
+
const text = fs17.readFileSync(full, "utf8");
|
|
70037
|
+
return { file, text, lines: text.split(/\r?\n/), real: fs17.realpathSync(full) };
|
|
70038
|
+
} catch {
|
|
70039
|
+
return "unreadable";
|
|
70040
|
+
}
|
|
70041
|
+
}, trackedTopLevel = (root) => {
|
|
70042
|
+
const run = spawnSync14("git", ["ls-files", "-z", "--cached"], {
|
|
70043
|
+
cwd: root,
|
|
70044
|
+
encoding: "utf8",
|
|
70045
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
70046
|
+
timeout: 20000,
|
|
70047
|
+
windowsHide: true
|
|
70048
|
+
});
|
|
70049
|
+
if (run.status !== 0)
|
|
70050
|
+
return null;
|
|
70051
|
+
return new Set(run.stdout.split("\x00").filter(Boolean).map((file) => file.split("/")[0]));
|
|
70052
|
+
}, exists = (target) => {
|
|
70053
|
+
try {
|
|
70054
|
+
fs17.statSync(target);
|
|
70055
|
+
return true;
|
|
70056
|
+
} catch {
|
|
70057
|
+
return false;
|
|
70058
|
+
}
|
|
70059
|
+
}, LINK, REFERENCE_DEFINITION, CODE_SPAN, PATH_LIKE, isExternal = (target) => /^[a-z][a-z0-9+.-]*:/i.test(target) || target.startsWith("#") || target.startsWith("//"), localTarget = (target) => {
|
|
70060
|
+
const bare = target.replace(/[#?].*$/, "");
|
|
70061
|
+
try {
|
|
70062
|
+
return decodeURIComponent(bare);
|
|
70063
|
+
} catch {
|
|
70064
|
+
return bare;
|
|
70065
|
+
}
|
|
70066
|
+
}, brokenLinks = (root, doc, tracked) => {
|
|
70067
|
+
const findings = [];
|
|
70068
|
+
const base = path36.dirname(path36.join(root, doc.file));
|
|
70069
|
+
const missing = (target, line, how) => findings.push({
|
|
70070
|
+
rule: "broken-link",
|
|
70071
|
+
file: doc.file,
|
|
70072
|
+
line,
|
|
70073
|
+
message: `${how} \`${target}\` points at a missing file`
|
|
70074
|
+
});
|
|
70075
|
+
for (const { index, text } of proseLines(doc.lines)) {
|
|
70076
|
+
const prose = text.replace(CODE_SPAN, (span) => " ".repeat(span.length));
|
|
70077
|
+
const targets = [...prose.matchAll(LINK)].map((match) => match[1] ?? match[2]);
|
|
70078
|
+
const definition = REFERENCE_DEFINITION.exec(prose)?.[1];
|
|
70079
|
+
if (definition)
|
|
70080
|
+
targets.push(definition);
|
|
70081
|
+
for (const target of targets) {
|
|
70082
|
+
if (isExternal(target))
|
|
70083
|
+
continue;
|
|
70084
|
+
const local = localTarget(target);
|
|
70085
|
+
if (!local)
|
|
70086
|
+
continue;
|
|
70087
|
+
const resolved = local.startsWith("/") ? path36.join(root, local) : path36.resolve(base, local);
|
|
70088
|
+
if (!exists(resolved))
|
|
70089
|
+
missing(target, index + 1, "link");
|
|
70090
|
+
}
|
|
70091
|
+
for (const [, span] of text.matchAll(CODE_SPAN)) {
|
|
70092
|
+
const candidate = span.replace(/:\d+(?::\d+)?$/, "");
|
|
70093
|
+
if (!PATH_LIKE.test(candidate))
|
|
70094
|
+
continue;
|
|
70095
|
+
const relative = /^\.\.?\//.test(candidate);
|
|
70096
|
+
const first = candidate.split("/")[0];
|
|
70097
|
+
if (!relative && !(tracked ? tracked.has(first) : exists(path36.join(root, first))))
|
|
70098
|
+
continue;
|
|
70099
|
+
const resolved = relative ? path36.resolve(base, candidate) : path36.join(root, candidate);
|
|
70100
|
+
if (!exists(resolved))
|
|
70101
|
+
missing(span, index + 1, "pointer");
|
|
70102
|
+
}
|
|
70103
|
+
}
|
|
70104
|
+
return findings;
|
|
70105
|
+
}, isPlaceholder2 = (text) => PLACEHOLDER2.test(text.replace(LIST_ITEM, "").replace(/[*_`]/g, "").replace(/^\[\s?\]\s*/, "").trim()), scaffoldOnly = (doc) => {
|
|
70106
|
+
const lines = proseLines(withoutFrontMatter(doc.lines));
|
|
70107
|
+
return lines.every(({ text }, position) => {
|
|
70108
|
+
const trimmed = text.trim();
|
|
70109
|
+
if (trimmed === "" || HEADING.test(text) || /^([-*_])(\s*\1){2,}$/.test(trimmed))
|
|
70110
|
+
return true;
|
|
70111
|
+
if (TABLE_SEPARATOR.test(text))
|
|
70112
|
+
return true;
|
|
70113
|
+
const next = lines[position + 1]?.text ?? "";
|
|
70114
|
+
if (trimmed.startsWith("|") && TABLE_SEPARATOR.test(next))
|
|
70115
|
+
return true;
|
|
70116
|
+
if (trimmed.startsWith("|"))
|
|
70117
|
+
return trimmed.split("|").map((cell) => cell.trim()).every((cell) => cell === "" || isPlaceholder2(cell));
|
|
70118
|
+
return isPlaceholder2(trimmed);
|
|
70119
|
+
});
|
|
70120
|
+
}, ruleUnits = (doc) => {
|
|
70121
|
+
const units = [];
|
|
70122
|
+
let current = null;
|
|
70123
|
+
const flush = () => {
|
|
70124
|
+
if (current)
|
|
70125
|
+
units.push(current);
|
|
70126
|
+
current = null;
|
|
70127
|
+
};
|
|
70128
|
+
for (const { index, text } of proseLines(doc.lines)) {
|
|
70129
|
+
const trimmed = text.trim();
|
|
70130
|
+
if (trimmed === "" || HEADING.test(text) || trimmed.startsWith("|")) {
|
|
70131
|
+
flush();
|
|
70132
|
+
continue;
|
|
70133
|
+
}
|
|
70134
|
+
if (LIST_ITEM.test(text) || !current) {
|
|
70135
|
+
flush();
|
|
70136
|
+
current = { line: index + 1, text: trimmed.replace(LIST_ITEM, "") };
|
|
70137
|
+
} else
|
|
70138
|
+
current.text += ` ${trimmed}`;
|
|
70139
|
+
}
|
|
70140
|
+
flush();
|
|
70141
|
+
return units;
|
|
70142
|
+
}, normalize = (sentence) => sentence.replace(/\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/[*_`]/g, "").toLowerCase().replace(/\s+/g, " ").replace(/[\s.,;:!?]+$/, "").trim(), sentences = (doc) => {
|
|
70143
|
+
const out = new Map;
|
|
70144
|
+
for (const unit of ruleUnits(doc))
|
|
70145
|
+
for (const sentence of unit.text.split(/(?<=[.!?])\s+/)) {
|
|
70146
|
+
const key = normalize(sentence);
|
|
70147
|
+
if (key.split(" ").length >= MIN_RULE_WORDS && !out.has(key))
|
|
70148
|
+
out.set(key, unit.line);
|
|
70149
|
+
}
|
|
70150
|
+
return out;
|
|
70151
|
+
}, duplicateRules = (steering, standards) => {
|
|
70152
|
+
const findings = [];
|
|
70153
|
+
const rules = sentences(standards);
|
|
70154
|
+
for (const doc of steering)
|
|
70155
|
+
for (const [key, line] of sentences(doc)) {
|
|
70156
|
+
const standardsLine = rules.get(key);
|
|
70157
|
+
if (standardsLine === undefined)
|
|
70158
|
+
continue;
|
|
70159
|
+
findings.push({
|
|
70160
|
+
rule: "duplicate-rule",
|
|
70161
|
+
file: doc.file,
|
|
70162
|
+
line,
|
|
70163
|
+
message: `repeats ${standards.file}:${standardsLine} ("${key.slice(0, 60)}${key.length > 60 ? "…" : ""}"); keep the rule in one file`
|
|
70164
|
+
});
|
|
70165
|
+
}
|
|
70166
|
+
return findings;
|
|
70167
|
+
};
|
|
70168
|
+
var init_knowledge = __esm(() => {
|
|
70169
|
+
AGENTS_BYTE_BUDGET = 8 * 1024;
|
|
70170
|
+
STEERING_FILES = ["AGENTS.md", "CLAUDE.md"];
|
|
70171
|
+
NEED_BASED_FILES = ["CODING_STANDARDS.md", "GLOSSARY.md"];
|
|
70172
|
+
FENCE = /^ {0,3}(`{3,}|~{3,})(.*)$/;
|
|
70173
|
+
HEADING = /^\s{0,3}#{1,6}(\s|$)/;
|
|
70174
|
+
TABLE_SEPARATOR = /^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)*\|?\s*$/;
|
|
70175
|
+
LIST_ITEM = /^\s*(?:[-*+]|\d+[.)])\s+/;
|
|
70176
|
+
PLACEHOLDER2 = /^(?:(?:tbd|todo|tba|fixme)\b.*|n\/a|none|none yet|coming soon|placeholder|\.\.\.|…|-)?[.:]?$/i;
|
|
70177
|
+
INDENTED_CODE = /^(?: {4}|\t)/;
|
|
70178
|
+
LINK = /!?\[[^\]]*\]\(\s*(?:<([^>\n]+)>|((?:[^()\s<>]|\([^()\s]*\))+))(?:\s+["'(][^)]*)?\)/g;
|
|
70179
|
+
REFERENCE_DEFINITION = /^\s{0,3}\[[^\]]+\]:\s*<?(\S+?)>?(?:\s|$)/;
|
|
70180
|
+
CODE_SPAN = /`([^`\n]+)`/g;
|
|
70181
|
+
PATH_LIKE = /^(?:\.{1,2}\/)?[\w@.-]+(?:\/[\w@.-]+)+\/?$/;
|
|
70182
|
+
});
|
|
70183
|
+
|
|
69919
70184
|
// packages/workit-cli/src/prompt.ts
|
|
69920
70185
|
import { createInterface } from "node:readline/promises";
|
|
69921
70186
|
async function askLine(question, output = process.stdout) {
|
|
@@ -69999,8 +70264,23 @@ async function run5(argv, io) {
|
|
|
69999
70264
|
fixLock = clearStaleMetadataLock(root, { force });
|
|
70000
70265
|
}
|
|
70001
70266
|
const report = runDoctor({ host: "cli", cwd: io.cwd, workspaceRoot: root });
|
|
70267
|
+
let knowledge = null;
|
|
70268
|
+
try {
|
|
70269
|
+
knowledge = lintKnowledge(root);
|
|
70270
|
+
if (knowledge.unreadable.length > 0)
|
|
70271
|
+
knowledge = null;
|
|
70272
|
+
} catch {
|
|
70273
|
+
knowledge = null;
|
|
70274
|
+
}
|
|
70275
|
+
const agents = knowledge?.files.find((file) => file.file === "AGENTS.md");
|
|
70276
|
+
const knowledgeLine = knowledge ? `info knowledge — ${agents ? `AGENTS.md ${agents.bytes} of ${knowledge.budget} bytes` : "no AGENTS.md"}; ${knowledge.findings.length} finding(s) (workit knowledge lint)` : "info knowledge — unavailable";
|
|
70002
70277
|
if (argv.includes("--json")) {
|
|
70003
|
-
|
|
70278
|
+
const summary = knowledge ? {
|
|
70279
|
+
agentsBytes: agents?.bytes ?? null,
|
|
70280
|
+
budget: knowledge.budget,
|
|
70281
|
+
findings: knowledge.findings.length
|
|
70282
|
+
} : { unavailable: true };
|
|
70283
|
+
io.stdout(`${JSON.stringify({ ...report, ...fixLock ? { fixLock } : {}, knowledge: summary }, null, 2)}
|
|
70004
70284
|
`);
|
|
70005
70285
|
return report.exitCode;
|
|
70006
70286
|
}
|
|
@@ -70019,12 +70299,15 @@ async function run5(argv, io) {
|
|
|
70019
70299
|
io.stdout(` fix: ${check.fix}
|
|
70020
70300
|
`);
|
|
70021
70301
|
}
|
|
70302
|
+
io.stdout(`${knowledgeLine}
|
|
70303
|
+
`);
|
|
70022
70304
|
io.stdout(`passed ${report.summary.passed} / warned ${report.summary.warned} / failed ${report.summary.failed}
|
|
70023
70305
|
`);
|
|
70024
70306
|
return report.exitCode;
|
|
70025
70307
|
}
|
|
70026
70308
|
var init_doctor2 = __esm(() => {
|
|
70027
70309
|
init_doctor();
|
|
70310
|
+
init_knowledge();
|
|
70028
70311
|
init_store_lock();
|
|
70029
70312
|
init_output();
|
|
70030
70313
|
init_prompt();
|
|
@@ -70135,11 +70418,11 @@ var init_redact = __esm(() => {
|
|
|
70135
70418
|
});
|
|
70136
70419
|
|
|
70137
70420
|
// packages/workit-core/src/checks.ts
|
|
70138
|
-
import { spawn, spawnSync as
|
|
70421
|
+
import { spawn, spawnSync as spawnSync15 } from "node:child_process";
|
|
70139
70422
|
import { createHash as createHash14 } from "node:crypto";
|
|
70140
|
-
import
|
|
70423
|
+
import fs18 from "node:fs";
|
|
70141
70424
|
import os13 from "node:os";
|
|
70142
|
-
import
|
|
70425
|
+
import path37 from "node:path";
|
|
70143
70426
|
import { StringDecoder } from "node:string_decoder";
|
|
70144
70427
|
function tailLines(text, lines = TAIL_LINES) {
|
|
70145
70428
|
const all = text.replace(/\r\n?/gu, `
|
|
@@ -70259,10 +70542,10 @@ function planSpawn(argv, options) {
|
|
|
70259
70542
|
return { file: argv[0], args: [], verbatim: false, shell: true };
|
|
70260
70543
|
if (options.platform !== "win32")
|
|
70261
70544
|
return { file: argv[0], args: argv.slice(1), verbatim: false, shell: false };
|
|
70262
|
-
const win =
|
|
70545
|
+
const win = path37.win32;
|
|
70263
70546
|
const isFile = options.isFile ?? ((file) => {
|
|
70264
70547
|
try {
|
|
70265
|
-
return
|
|
70548
|
+
return fs18.statSync(file).isFile();
|
|
70266
70549
|
} catch {
|
|
70267
70550
|
return false;
|
|
70268
70551
|
}
|
|
@@ -70314,7 +70597,7 @@ function runCheckCommand(argv, options) {
|
|
|
70314
70597
|
if (!child?.pid)
|
|
70315
70598
|
return;
|
|
70316
70599
|
if (windows)
|
|
70317
|
-
|
|
70600
|
+
spawnSync15("taskkill", ["/pid", String(child.pid), "/T", "/F"], {
|
|
70318
70601
|
stdio: "ignore",
|
|
70319
70602
|
windowsHide: true
|
|
70320
70603
|
});
|
|
@@ -70416,13 +70699,13 @@ function runCheckCommand(argv, options) {
|
|
|
70416
70699
|
function storeLog(storeRoot, log) {
|
|
70417
70700
|
const digest = logDigest(log);
|
|
70418
70701
|
const ref = `blobs/logs/${digest.slice("sha256:".length)}.log`;
|
|
70419
|
-
const file =
|
|
70702
|
+
const file = path37.join(storeRoot, ref);
|
|
70420
70703
|
try {
|
|
70421
|
-
if (!
|
|
70422
|
-
|
|
70704
|
+
if (!fs18.existsSync(file)) {
|
|
70705
|
+
fs18.mkdirSync(path37.dirname(file), { recursive: true });
|
|
70423
70706
|
const temp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
70424
|
-
|
|
70425
|
-
|
|
70707
|
+
fs18.writeFileSync(temp, log, { mode: 384 });
|
|
70708
|
+
fs18.renameSync(temp, file);
|
|
70426
70709
|
}
|
|
70427
70710
|
return { digest, ref };
|
|
70428
70711
|
} catch {
|
|
@@ -70432,17 +70715,17 @@ function storeLog(storeRoot, log) {
|
|
|
70432
70715
|
function pruneCheckLogs(storeRoot, options = {}) {
|
|
70433
70716
|
const limits = { ...LOG_RETENTION, ...options.retention };
|
|
70434
70717
|
const now = options.now ?? Date.now();
|
|
70435
|
-
const dir =
|
|
70718
|
+
const dir = path37.join(storeRoot, "blobs", "logs");
|
|
70436
70719
|
const report = { removed: 0, removedBytes: 0, kept: 0 };
|
|
70437
70720
|
let names;
|
|
70438
70721
|
try {
|
|
70439
|
-
names =
|
|
70722
|
+
names = fs18.readdirSync(dir);
|
|
70440
70723
|
} catch {
|
|
70441
70724
|
return report;
|
|
70442
70725
|
}
|
|
70443
70726
|
const files = names.flatMap((name) => {
|
|
70444
70727
|
try {
|
|
70445
|
-
const stat =
|
|
70728
|
+
const stat = fs18.statSync(path37.join(dir, name));
|
|
70446
70729
|
return stat.isFile() ? [{ name, size: stat.size, mtimeMs: stat.mtimeMs }] : [];
|
|
70447
70730
|
} catch {
|
|
70448
70731
|
return [];
|
|
@@ -70461,7 +70744,7 @@ function pruneCheckLogs(storeRoot, options = {}) {
|
|
|
70461
70744
|
continue;
|
|
70462
70745
|
if (!options.dryRun)
|
|
70463
70746
|
try {
|
|
70464
|
-
|
|
70747
|
+
fs18.rmSync(path37.join(dir, file.name), { force: true });
|
|
70465
70748
|
} catch {
|
|
70466
70749
|
continue;
|
|
70467
70750
|
}
|
|
@@ -70712,7 +70995,7 @@ var exports_check = {};
|
|
|
70712
70995
|
__export(exports_check, {
|
|
70713
70996
|
run: () => run9
|
|
70714
70997
|
});
|
|
70715
|
-
import
|
|
70998
|
+
import path38 from "node:path";
|
|
70716
70999
|
function parse5(argv) {
|
|
70717
71000
|
const split = argv.indexOf("--");
|
|
70718
71001
|
const head = split >= 0 ? argv.slice(0, split) : [...argv];
|
|
@@ -70895,7 +71178,7 @@ async function run9(argv, io) {
|
|
|
70895
71178
|
if (options.shell && command.length !== 1)
|
|
70896
71179
|
return emit(io, fail("invalid_input", '--shell takes the command as one string: workit check --shell -- "<cmd>"'));
|
|
70897
71180
|
}
|
|
70898
|
-
const relCwd = posix(
|
|
71181
|
+
const relCwd = posix(path38.relative(top, runCwd));
|
|
70899
71182
|
const configured = !options.shell && matchesNamedCheck(config, name, command, relCwd);
|
|
70900
71183
|
const actor = actorFromEnv(io.env);
|
|
70901
71184
|
const branchKey = codeKey(io.cwd, { base: options.base });
|
|
@@ -71019,7 +71302,7 @@ async function run9(argv, io) {
|
|
|
71019
71302
|
return recordError.code === "busy" ? 4 : recordError.code === "invalid_input" ? 2 : 5;
|
|
71020
71303
|
return 0;
|
|
71021
71304
|
}
|
|
71022
|
-
var USAGE3 = "workit check <name> | workit check [--name <n>] [--shell] [--timeout <s>] [--task <id>] [--json] -- <cmd…>", RECORD_TAIL_LINES = 20, RECORD_TAIL_CHARS = 200, posix = (value) => value.split(
|
|
71305
|
+
var USAGE3 = "workit check <name> | workit check [--name <n>] [--shell] [--timeout <s>] [--task <id>] [--json] -- <cmd…>", RECORD_TAIL_LINES = 20, RECORD_TAIL_CHARS = 200, posix = (value) => value.split(path38.sep).join("/") || ".", ENGINE_CODES2, FINGERPRINT_VARS;
|
|
71023
71306
|
var init_check = __esm(() => {
|
|
71024
71307
|
init_check_config();
|
|
71025
71308
|
init_checks3();
|
|
@@ -71040,7 +71323,7 @@ var init_check = __esm(() => {
|
|
|
71040
71323
|
var success2 = (data) => ({ ok: true, data }), failure3 = (code, error, unblock) => ({ ok: false, code, error, ...unblock ? { unblock } : {} });
|
|
71041
71324
|
|
|
71042
71325
|
// packages/workit-core/src/git/ops.ts
|
|
71043
|
-
import { spawnSync as
|
|
71326
|
+
import { spawnSync as spawnSync16 } from "node:child_process";
|
|
71044
71327
|
function dirtEntries(cwd) {
|
|
71045
71328
|
const run = git5(cwd, ["status", "--porcelain=v1", "-z", "--untracked-files=all"]);
|
|
71046
71329
|
if (!run.ok)
|
|
@@ -71384,7 +71667,7 @@ ${run.stdout}`;
|
|
|
71384
71667
|
return /stale info|cannot lock ref|fetch first|non-fast-forward/iu.test(text) ? { ok: false, lease: true, error: "the remote branch tip changed before deletion" } : { ok: false, lease: false, error: gitError(run) };
|
|
71385
71668
|
}
|
|
71386
71669
|
var COMMIT_TIMEOUT_MS, PUSH_TIMEOUT_MS = 120000, git5 = (cwd, args, options = {}) => {
|
|
71387
|
-
const result =
|
|
71670
|
+
const result = spawnSync16("git", args, {
|
|
71388
71671
|
cwd,
|
|
71389
71672
|
encoding: "utf8",
|
|
71390
71673
|
env: { ...options.env ?? process.env, GIT_TERMINAL_PROMPT: "0" },
|
|
@@ -71420,11 +71703,11 @@ var init_ops = __esm(() => {
|
|
|
71420
71703
|
});
|
|
71421
71704
|
|
|
71422
71705
|
// packages/workit-core/src/forge/reruns.ts
|
|
71423
|
-
import
|
|
71424
|
-
import
|
|
71706
|
+
import fs19 from "node:fs";
|
|
71707
|
+
import path39 from "node:path";
|
|
71425
71708
|
function rerunLogPath(cwd) {
|
|
71426
71709
|
const common = gitCommonDir(cwd);
|
|
71427
|
-
return common ?
|
|
71710
|
+
return common ? path39.join(common, "workit", "ci-reruns.jsonl") : null;
|
|
71428
71711
|
}
|
|
71429
71712
|
function rerunCounts(cwd, key) {
|
|
71430
71713
|
const counts = new Map;
|
|
@@ -71433,7 +71716,7 @@ function rerunCounts(cwd, key) {
|
|
|
71433
71716
|
return counts;
|
|
71434
71717
|
let text;
|
|
71435
71718
|
try {
|
|
71436
|
-
text =
|
|
71719
|
+
text = fs19.readFileSync(file, "utf8");
|
|
71437
71720
|
} catch {
|
|
71438
71721
|
return counts;
|
|
71439
71722
|
}
|
|
@@ -71459,8 +71742,8 @@ function recordReruns(cwd, rows) {
|
|
|
71459
71742
|
if (!file)
|
|
71460
71743
|
return false;
|
|
71461
71744
|
try {
|
|
71462
|
-
|
|
71463
|
-
|
|
71745
|
+
fs19.mkdirSync(path39.dirname(file), { recursive: true });
|
|
71746
|
+
fs19.appendFileSync(file, rows.map((row) => `${JSON.stringify(row)}
|
|
71464
71747
|
`).join(""));
|
|
71465
71748
|
return true;
|
|
71466
71749
|
} catch {
|
|
@@ -71472,18 +71755,18 @@ function withRerunLock(cwd, fn) {
|
|
|
71472
71755
|
if (!file)
|
|
71473
71756
|
return fn();
|
|
71474
71757
|
const lock = `${file.slice(0, -".jsonl".length)}.lock`;
|
|
71475
|
-
|
|
71758
|
+
fs19.mkdirSync(path39.dirname(lock), { recursive: true });
|
|
71476
71759
|
const until = Date.now() + LOCK_WAIT_MS2;
|
|
71477
71760
|
for (;; ) {
|
|
71478
71761
|
try {
|
|
71479
|
-
|
|
71762
|
+
fs19.closeSync(fs19.openSync(lock, "wx"));
|
|
71480
71763
|
break;
|
|
71481
71764
|
} catch (error) {
|
|
71482
71765
|
if (error.code !== "EEXIST")
|
|
71483
71766
|
throw error;
|
|
71484
71767
|
try {
|
|
71485
|
-
if (Date.now() -
|
|
71486
|
-
|
|
71768
|
+
if (Date.now() - fs19.statSync(lock).mtimeMs > LOCK_STALE_MS2) {
|
|
71769
|
+
fs19.rmSync(lock, { force: true });
|
|
71487
71770
|
continue;
|
|
71488
71771
|
}
|
|
71489
71772
|
} catch {
|
|
@@ -71497,7 +71780,7 @@ function withRerunLock(cwd, fn) {
|
|
|
71497
71780
|
try {
|
|
71498
71781
|
return fn();
|
|
71499
71782
|
} finally {
|
|
71500
|
-
|
|
71783
|
+
fs19.rmSync(lock, { force: true });
|
|
71501
71784
|
}
|
|
71502
71785
|
}
|
|
71503
71786
|
var isRow = (value) => {
|
|
@@ -71865,7 +72148,7 @@ var init_report = __esm(() => {
|
|
|
71865
72148
|
});
|
|
71866
72149
|
|
|
71867
72150
|
// packages/workit-core/src/forge/pr-ops.ts
|
|
71868
|
-
import { spawnSync as
|
|
72151
|
+
import { spawnSync as spawnSync17 } from "node:child_process";
|
|
71869
72152
|
function fillFromCommits(cwd, resolved, base) {
|
|
71870
72153
|
const tracking = `refs/remotes/${resolved.baseRemote}/${base}`;
|
|
71871
72154
|
if (!base.startsWith("-"))
|
|
@@ -71873,7 +72156,7 @@ function fillFromCommits(cwd, resolved, base) {
|
|
|
71873
72156
|
const from = resolveRef(cwd, tracking) ?? resolveRef(cwd, `refs/heads/${base}`);
|
|
71874
72157
|
if (!from)
|
|
71875
72158
|
return failure3("not_found", `base ${base} is not available locally`, `git fetch ${resolved.baseRemote} ${base}`);
|
|
71876
|
-
const log =
|
|
72159
|
+
const log = spawnSync17("git", ["log", "--reverse", "--no-merges", "--format=%s%x1f%b%x1e", `${from}..HEAD`], {
|
|
71877
72160
|
cwd,
|
|
71878
72161
|
encoding: "utf8",
|
|
71879
72162
|
timeout: GIT_TIMEOUTS.local,
|
|
@@ -72148,17 +72431,17 @@ var init_pr_ops = __esm(() => {
|
|
|
72148
72431
|
});
|
|
72149
72432
|
|
|
72150
72433
|
// packages/workit-core/src/forge/exec.ts
|
|
72151
|
-
import { spawnSync as
|
|
72152
|
-
import
|
|
72153
|
-
import
|
|
72434
|
+
import { spawnSync as spawnSync18 } from "node:child_process";
|
|
72435
|
+
import fs20 from "node:fs";
|
|
72436
|
+
import path40 from "node:path";
|
|
72154
72437
|
function findExecutable(bin, env) {
|
|
72155
|
-
const dirs = (env.PATH ?? env.Path ?? "").split(
|
|
72438
|
+
const dirs = (env.PATH ?? env.Path ?? "").split(path40.delimiter).filter(Boolean);
|
|
72156
72439
|
const exts = process.platform === "win32" ? [".exe"] : [""];
|
|
72157
72440
|
for (const dir of dirs)
|
|
72158
72441
|
for (const ext of exts) {
|
|
72159
|
-
const candidate =
|
|
72442
|
+
const candidate = path40.join(dir, `${bin}${ext}`);
|
|
72160
72443
|
try {
|
|
72161
|
-
const stat =
|
|
72444
|
+
const stat = fs20.statSync(candidate);
|
|
72162
72445
|
if (stat.isFile())
|
|
72163
72446
|
return candidate;
|
|
72164
72447
|
} catch {}
|
|
@@ -72183,7 +72466,7 @@ function systemRunner(kind, apiHost, env = process.env) {
|
|
|
72183
72466
|
if (!exe)
|
|
72184
72467
|
return { status: null, stdout: "", stderr: "", timedOut: false, missing: true };
|
|
72185
72468
|
const token = options.token ? kind === "github" ? { GH_TOKEN: options.token, GH_ENTERPRISE_TOKEN: options.token } : { GITLAB_TOKEN: options.token } : {};
|
|
72186
|
-
const result =
|
|
72469
|
+
const result = spawnSync18(exe, [...args], {
|
|
72187
72470
|
encoding: "utf8",
|
|
72188
72471
|
env: { ...childEnv, ...token },
|
|
72189
72472
|
maxBuffer: MAX_BUFFER2,
|
|
@@ -72853,7 +73136,7 @@ var init_gitlab = __esm(() => {
|
|
|
72853
73136
|
});
|
|
72854
73137
|
|
|
72855
73138
|
// packages/workit-core/src/forge/resolve.ts
|
|
72856
|
-
import
|
|
73139
|
+
import fs21 from "node:fs";
|
|
72857
73140
|
function resolveForge(cwd, options = {}) {
|
|
72858
73141
|
let workspace;
|
|
72859
73142
|
try {
|
|
@@ -72882,7 +73165,7 @@ function resolveForge(cwd, options = {}) {
|
|
|
72882
73165
|
if (derived.via === "host_name" && !explicit)
|
|
72883
73166
|
return failure3("blocked", `token_host_unverified: push host ${derived.host} only looks like ${kind}; workspace ${workspace?.name ?? "?"} vcs.tokenFile is not sent to it`, `if ${apiHost} is your ${kind} server, set ${kind}.host in ~/.config/workit/vcs.json (or vcs.host for the workspace); otherwise fix the push remote`);
|
|
72884
73167
|
try {
|
|
72885
|
-
token =
|
|
73168
|
+
token = fs21.readFileSync(tokenFile.trim(), "utf8").trim();
|
|
72886
73169
|
} catch {
|
|
72887
73170
|
return failure3("blocked", `workspace ${workspace?.name ?? "?"} vcs.tokenFile cannot be read`, "fix vcs.tokenFile in ~/.config/workit/workspaces.json");
|
|
72888
73171
|
}
|
|
@@ -72972,6 +73255,33 @@ var init_resolve = __esm(() => {
|
|
|
72972
73255
|
init_gitlab();
|
|
72973
73256
|
});
|
|
72974
73257
|
|
|
73258
|
+
// packages/workit-core/src/duration.ts
|
|
73259
|
+
function parseDuration2(value) {
|
|
73260
|
+
const text = value.trim().toLowerCase();
|
|
73261
|
+
if (/^\d+(?:\.\d+)?$/u.test(text))
|
|
73262
|
+
return Math.round(Number(text) * 1000);
|
|
73263
|
+
const parts = [...text.matchAll(/(\d+(?:\.\d+)?)\s*([a-z]+)\s*/gu)];
|
|
73264
|
+
if (parts.length === 0 || parts.map((part) => part[0]).join("") !== text)
|
|
73265
|
+
return null;
|
|
73266
|
+
let total = 0;
|
|
73267
|
+
for (const [, amount, unit] of parts) {
|
|
73268
|
+
const factor = UNITS.find(([pattern]) => pattern.test(unit))?.[1];
|
|
73269
|
+
if (factor === undefined)
|
|
73270
|
+
return null;
|
|
73271
|
+
total += Number(amount) * factor;
|
|
73272
|
+
}
|
|
73273
|
+
return Math.round(total);
|
|
73274
|
+
}
|
|
73275
|
+
var UNITS;
|
|
73276
|
+
var init_duration = __esm(() => {
|
|
73277
|
+
UNITS = [
|
|
73278
|
+
[/^(?:ms|milliseconds?)$/u, 1],
|
|
73279
|
+
[/^(?:s|secs?|seconds?)$/u, 1000],
|
|
73280
|
+
[/^(?:m|mins?|minutes?)$/u, 60000],
|
|
73281
|
+
[/^(?:h|hrs?|hours?)$/u, 3600000]
|
|
73282
|
+
];
|
|
73283
|
+
});
|
|
73284
|
+
|
|
72975
73285
|
// packages/workit-cli/src/verbs/forge-common.ts
|
|
72976
73286
|
function parseFlags(argv, spec) {
|
|
72977
73287
|
const parsed = { values: {}, booleans: new Set, lists: {}, positionals: [] };
|
|
@@ -73009,14 +73319,6 @@ function positiveInt(value, flag) {
|
|
|
73009
73319
|
return null;
|
|
73010
73320
|
return /^[1-9]\d{0,8}$/u.test(value) ? Number(value) : `${flag} must be a positive integer`;
|
|
73011
73321
|
}
|
|
73012
|
-
function parseDuration2(value) {
|
|
73013
|
-
const match = /^(\d+(?:\.\d+)?)(ms|s|m|h)?$/u.exec(value.trim());
|
|
73014
|
-
if (!match)
|
|
73015
|
-
return null;
|
|
73016
|
-
const unit = match[2] ?? "s";
|
|
73017
|
-
const factor = unit === "ms" ? 1 : unit === "s" ? 1000 : unit === "m" ? 60000 : 3600000;
|
|
73018
|
-
return Math.round(Number(match[1]) * factor);
|
|
73019
|
-
}
|
|
73020
73322
|
function releaseTrunk(io, track, branch) {
|
|
73021
73323
|
const resolved = vcsConfig("resolve", io.cwd, {
|
|
73022
73324
|
track,
|
|
@@ -73099,6 +73401,7 @@ var forgeDeps, usage3 = (io, message, line) => emit(io, fail("invalid_input", me
|
|
|
73099
73401
|
var init_forge_common = __esm(() => {
|
|
73100
73402
|
init_resolve();
|
|
73101
73403
|
init_vcs_config();
|
|
73404
|
+
init_duration();
|
|
73102
73405
|
init_output();
|
|
73103
73406
|
forgeDeps = {
|
|
73104
73407
|
runner: undefined,
|
|
@@ -73114,7 +73417,7 @@ __export(exports_pr, {
|
|
|
73114
73417
|
run: () => run10
|
|
73115
73418
|
});
|
|
73116
73419
|
import { readFileSync as readFileSync18 } from "node:fs";
|
|
73117
|
-
import
|
|
73420
|
+
import path41 from "node:path";
|
|
73118
73421
|
async function status(argv, io) {
|
|
73119
73422
|
const flags = parseFlags(argv, { pr: "value", branch: "value", "log-lines": "value" });
|
|
73120
73423
|
if (typeof flags === "string")
|
|
@@ -73185,7 +73488,7 @@ async function create4(argv, io) {
|
|
|
73185
73488
|
let body = flags.values.body ?? null;
|
|
73186
73489
|
if (flags.values["body-file"] !== undefined) {
|
|
73187
73490
|
try {
|
|
73188
|
-
body = readFileSync18(
|
|
73491
|
+
body = readFileSync18(path41.resolve(io.cwd, flags.values["body-file"]), "utf8");
|
|
73189
73492
|
} catch {
|
|
73190
73493
|
return usage3(io, `cannot read --body-file ${flags.values["body-file"]}`, CREATE_USAGE);
|
|
73191
73494
|
}
|
|
@@ -73677,7 +73980,7 @@ var init_git2 = __esm(() => {
|
|
|
73677
73980
|
});
|
|
73678
73981
|
|
|
73679
73982
|
// packages/workit-core/src/forge/verify.ts
|
|
73680
|
-
import { spawnSync as
|
|
73983
|
+
import { spawnSync as spawnSync19 } from "node:child_process";
|
|
73681
73984
|
function finish(cwd, report, actor) {
|
|
73682
73985
|
const delivered = report.observations.length > 0 && report.observations.every((entry) => entry.ok);
|
|
73683
73986
|
const out = { ...report, delivered };
|
|
@@ -73890,7 +74193,7 @@ var NPM_NAME, SEMVER, SAFE_ARG, systemNpm = (args) => {
|
|
|
73890
74193
|
};
|
|
73891
74194
|
const found = process.platform === "win32" ? findExecutable("npm.cmd", process.env) : null;
|
|
73892
74195
|
const npmCmd = found && !/["%!^]/u.test(found) ? `"${found}"` : "npm.cmd";
|
|
73893
|
-
const result = process.platform === "win32" ?
|
|
74196
|
+
const result = process.platform === "win32" ? spawnSync19(`${npmCmd} ${args.join(" ")}`, { ...options, shell: true }) : spawnSync19("npm", [...args], options);
|
|
73894
74197
|
return { status: result.status, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
73895
74198
|
}, short3 = (sha) => sha ? sha.slice(0, 12) : "(absent)", npmView = (stdout) => {
|
|
73896
74199
|
try {
|
|
@@ -73997,10 +74300,10 @@ var init_verify_delivery = __esm(() => {
|
|
|
73997
74300
|
});
|
|
73998
74301
|
|
|
73999
74302
|
// packages/workit-core/src/stack.ts
|
|
74000
|
-
import { spawnSync as
|
|
74303
|
+
import { spawnSync as spawnSync20 } from "node:child_process";
|
|
74001
74304
|
import { createHash as createHash15, randomBytes as randomBytes3 } from "node:crypto";
|
|
74002
|
-
import
|
|
74003
|
-
import
|
|
74305
|
+
import fs22 from "node:fs";
|
|
74306
|
+
import path42 from "node:path";
|
|
74004
74307
|
function worktreeOf(cwd, branch) {
|
|
74005
74308
|
const list = gitRun3(cwd, ["worktree", "list", "--porcelain"]);
|
|
74006
74309
|
if (!list.ok)
|
|
@@ -74023,7 +74326,7 @@ function rebasingBranch2(dir) {
|
|
|
74023
74326
|
if (!found.ok)
|
|
74024
74327
|
continue;
|
|
74025
74328
|
try {
|
|
74026
|
-
return
|
|
74329
|
+
return fs22.readFileSync(found.stdout.trim(), "utf8").trim();
|
|
74027
74330
|
} catch {}
|
|
74028
74331
|
}
|
|
74029
74332
|
return null;
|
|
@@ -74031,7 +74334,7 @@ function rebasingBranch2(dir) {
|
|
|
74031
74334
|
function rebaseInProgress(dir) {
|
|
74032
74335
|
for (const name of ["rebase-merge", "rebase-apply"]) {
|
|
74033
74336
|
const found = gitRun3(dir, ["rev-parse", "--path-format=absolute", "--git-path", name]);
|
|
74034
|
-
if (found.ok &&
|
|
74337
|
+
if (found.ok && fs22.existsSync(found.stdout.trim()))
|
|
74035
74338
|
return true;
|
|
74036
74339
|
}
|
|
74037
74340
|
return false;
|
|
@@ -74043,8 +74346,8 @@ function gitRestack(cwd, input) {
|
|
|
74043
74346
|
const scratch = scratchDir(cwd);
|
|
74044
74347
|
if (!scratch)
|
|
74045
74348
|
return { ok: false, conflict: false, worktree: cwd, error: "no store root" };
|
|
74046
|
-
|
|
74047
|
-
dir =
|
|
74349
|
+
fs22.mkdirSync(scratch, { recursive: true });
|
|
74350
|
+
dir = path42.join(scratch, `${input.branch.replace(/[^A-Za-z0-9._-]+/gu, "_").slice(0, 40)}-${randomBytes3(4).toString("hex")}`);
|
|
74048
74351
|
const added = gitRun3(cwd, ["worktree", "add", "-q", dir, input.branch], GIT_TIMEOUTS.worktree);
|
|
74049
74352
|
if (!added.ok)
|
|
74050
74353
|
return { ok: false, conflict: false, worktree: cwd, error: firstLine(added) };
|
|
@@ -74082,10 +74385,10 @@ function gitRestack(cwd, input) {
|
|
|
74082
74385
|
}
|
|
74083
74386
|
function pruneScratch(cwd) {
|
|
74084
74387
|
const scratch = scratchDir(cwd);
|
|
74085
|
-
if (!scratch || !
|
|
74388
|
+
if (!scratch || !fs22.existsSync(scratch))
|
|
74086
74389
|
return;
|
|
74087
|
-
for (const name of
|
|
74088
|
-
const dir =
|
|
74390
|
+
for (const name of fs22.readdirSync(scratch)) {
|
|
74391
|
+
const dir = path42.join(scratch, name);
|
|
74089
74392
|
if (!rebaseInProgress(dir) && !trackedDirt(dir))
|
|
74090
74393
|
gitRun3(cwd, ["worktree", "remove", "--force", dir]);
|
|
74091
74394
|
}
|
|
@@ -74095,7 +74398,7 @@ function stacksDir(cwd) {
|
|
|
74095
74398
|
const root = storeRoot(cwd);
|
|
74096
74399
|
if (!root.ok)
|
|
74097
74400
|
return stackFail(root.code, root.error, root.unblock);
|
|
74098
|
-
return { ok: true, data:
|
|
74401
|
+
return { ok: true, data: path42.join(root.value.root, "stacks") };
|
|
74099
74402
|
}
|
|
74100
74403
|
function parseStack(raw) {
|
|
74101
74404
|
if (!isRecord7(raw) || !Array.isArray(raw.branches))
|
|
@@ -74147,10 +74450,10 @@ function readStack(cwd, name) {
|
|
|
74147
74450
|
const dir = stacksDir(cwd);
|
|
74148
74451
|
if (!dir.ok)
|
|
74149
74452
|
return dir;
|
|
74150
|
-
const file =
|
|
74453
|
+
const file = path42.join(dir.data, fileFor(name));
|
|
74151
74454
|
let text;
|
|
74152
74455
|
try {
|
|
74153
|
-
text =
|
|
74456
|
+
text = fs22.readFileSync(file, "utf8");
|
|
74154
74457
|
} catch (error) {
|
|
74155
74458
|
if (error.code === "ENOENT")
|
|
74156
74459
|
return { ok: true, data: null };
|
|
@@ -74168,17 +74471,17 @@ function writeStack(cwd, stack, now = new Date) {
|
|
|
74168
74471
|
const dir = stacksDir(cwd);
|
|
74169
74472
|
if (!dir.ok)
|
|
74170
74473
|
return dir;
|
|
74171
|
-
const file =
|
|
74474
|
+
const file = path42.join(dir.data, fileFor(stack.name));
|
|
74172
74475
|
const temp = `${file}.${process.pid}.tmp`;
|
|
74173
74476
|
try {
|
|
74174
|
-
|
|
74477
|
+
fs22.mkdirSync(dir.data, { recursive: true });
|
|
74175
74478
|
stack.updatedAt = now.toISOString();
|
|
74176
|
-
|
|
74479
|
+
fs22.writeFileSync(temp, `${JSON.stringify(stack, null, 2)}
|
|
74177
74480
|
`);
|
|
74178
|
-
|
|
74481
|
+
fs22.renameSync(temp, file);
|
|
74179
74482
|
return { ok: true, data: undefined };
|
|
74180
74483
|
} catch (error) {
|
|
74181
|
-
|
|
74484
|
+
fs22.rmSync(temp, { force: true });
|
|
74182
74485
|
return stackFail("unavailable", `cannot write ${file}: ${error.message}`);
|
|
74183
74486
|
}
|
|
74184
74487
|
}
|
|
@@ -74188,7 +74491,7 @@ function listStacks(cwd) {
|
|
|
74188
74491
|
return dir;
|
|
74189
74492
|
let names = [];
|
|
74190
74493
|
try {
|
|
74191
|
-
names =
|
|
74494
|
+
names = fs22.readdirSync(dir.data).filter((name) => name.endsWith(".json"));
|
|
74192
74495
|
} catch {
|
|
74193
74496
|
return { ok: true, data: [] };
|
|
74194
74497
|
}
|
|
@@ -74196,7 +74499,7 @@ function listStacks(cwd) {
|
|
|
74196
74499
|
for (const name of names) {
|
|
74197
74500
|
let parsed = null;
|
|
74198
74501
|
try {
|
|
74199
|
-
parsed = parseStack(JSON.parse(
|
|
74502
|
+
parsed = parseStack(JSON.parse(fs22.readFileSync(path42.join(dir.data, name), "utf8")));
|
|
74200
74503
|
} catch {
|
|
74201
74504
|
parsed = null;
|
|
74202
74505
|
}
|
|
@@ -74243,7 +74546,7 @@ function stackLockStale(raw, age) {
|
|
|
74243
74546
|
}
|
|
74244
74547
|
function takeLock(lock) {
|
|
74245
74548
|
try {
|
|
74246
|
-
|
|
74549
|
+
fs22.mkdirSync(lock);
|
|
74247
74550
|
} catch (error) {
|
|
74248
74551
|
if (error.code === "EEXIST")
|
|
74249
74552
|
return null;
|
|
@@ -74255,24 +74558,24 @@ function takeLock(lock) {
|
|
|
74255
74558
|
host: localLockHost(),
|
|
74256
74559
|
nonce: randomBytes3(8).toString("hex")
|
|
74257
74560
|
};
|
|
74258
|
-
|
|
74561
|
+
fs22.writeFileSync(path42.join(lock, OWNER), JSON.stringify(owner));
|
|
74259
74562
|
return owner;
|
|
74260
74563
|
}
|
|
74261
74564
|
function reapLock(lock, observed) {
|
|
74262
74565
|
const tomb = `${lock}.reap-${randomBytes3(6).toString("hex")}`;
|
|
74263
74566
|
try {
|
|
74264
|
-
|
|
74567
|
+
fs22.renameSync(lock, tomb);
|
|
74265
74568
|
} catch {
|
|
74266
74569
|
return false;
|
|
74267
74570
|
}
|
|
74268
74571
|
if (readOwner(tomb).raw === observed) {
|
|
74269
|
-
|
|
74572
|
+
fs22.rmSync(tomb, { recursive: true, force: true });
|
|
74270
74573
|
return true;
|
|
74271
74574
|
}
|
|
74272
74575
|
try {
|
|
74273
|
-
|
|
74576
|
+
fs22.renameSync(tomb, lock);
|
|
74274
74577
|
} catch {
|
|
74275
|
-
|
|
74578
|
+
fs22.rmSync(tomb, { recursive: true, force: true });
|
|
74276
74579
|
}
|
|
74277
74580
|
return false;
|
|
74278
74581
|
}
|
|
@@ -74282,8 +74585,8 @@ function releaseLock(lock, owner) {
|
|
|
74282
74585
|
return;
|
|
74283
74586
|
const tomb = `${lock}.release-${randomBytes3(6).toString("hex")}`;
|
|
74284
74587
|
try {
|
|
74285
|
-
|
|
74286
|
-
|
|
74588
|
+
fs22.renameSync(lock, tomb);
|
|
74589
|
+
fs22.rmSync(tomb, { recursive: true, force: true });
|
|
74287
74590
|
} catch {}
|
|
74288
74591
|
}
|
|
74289
74592
|
async function withStackLock(cwd, name, fn, waitMs = LOCK_WAIT_MS3) {
|
|
@@ -74292,9 +74595,9 @@ async function withStackLock(cwd, name, fn, waitMs = LOCK_WAIT_MS3) {
|
|
|
74292
74595
|
return dir;
|
|
74293
74596
|
if (!validName(name))
|
|
74294
74597
|
return stackFail("invalid_input", "a stack name must be 1-200 printable characters");
|
|
74295
|
-
const lock =
|
|
74598
|
+
const lock = path42.join(dir.data, `${fileFor(name)}.lock`);
|
|
74296
74599
|
try {
|
|
74297
|
-
|
|
74600
|
+
fs22.mkdirSync(dir.data, { recursive: true });
|
|
74298
74601
|
} catch (error) {
|
|
74299
74602
|
return stackFail("unavailable", `cannot create ${dir.data}: ${error.message}`);
|
|
74300
74603
|
}
|
|
@@ -74322,7 +74625,7 @@ async function withStackLock(cwd, name, fn, waitMs = LOCK_WAIT_MS3) {
|
|
|
74322
74625
|
const touch = () => {
|
|
74323
74626
|
try {
|
|
74324
74627
|
const now = new Date;
|
|
74325
|
-
|
|
74628
|
+
fs22.utimesSync(path42.join(lock, OWNER), now, now);
|
|
74326
74629
|
} catch {}
|
|
74327
74630
|
};
|
|
74328
74631
|
const beat = setInterval(touch, HEARTBEAT_MS);
|
|
@@ -74510,7 +74813,7 @@ function commitKeys(cwd, range) {
|
|
|
74510
74813
|
const patches = new Map;
|
|
74511
74814
|
const diff = gitRun3(cwd, ["log", "--no-merges", "-p", "--format=commit %H", range]);
|
|
74512
74815
|
if (diff.ok && diff.stdout.trim()) {
|
|
74513
|
-
const ids =
|
|
74816
|
+
const ids = spawnSync20("git", ["patch-id", "--stable"], {
|
|
74514
74817
|
cwd,
|
|
74515
74818
|
input: diff.stdout,
|
|
74516
74819
|
encoding: "utf8",
|
|
@@ -75118,7 +75421,7 @@ var STACK_VERSION = 1, stackFail = (code, error, unblock, data) => ({
|
|
|
75118
75421
|
...unblock ? { unblock } : {},
|
|
75119
75422
|
...data ? { data } : {}
|
|
75120
75423
|
}), fromForge = (result) => stackFail(result.code, result.error, result.unblock), stackDeps, gitRun3 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
|
|
75121
|
-
const run =
|
|
75424
|
+
const run = spawnSync20("git", args, {
|
|
75122
75425
|
cwd,
|
|
75123
75426
|
encoding: "utf8",
|
|
75124
75427
|
timeout: timeoutMs,
|
|
@@ -75146,7 +75449,7 @@ var STACK_VERSION = 1, stackFail = (code, error, unblock, data) => ({
|
|
|
75146
75449
|
return !status.ok || status.stdout.trim() !== "";
|
|
75147
75450
|
}, scratchDir = (cwd) => {
|
|
75148
75451
|
const dir = stacksDir(cwd);
|
|
75149
|
-
return dir.ok ?
|
|
75452
|
+
return dir.ok ? path42.join(dir.data, "worktrees") : null;
|
|
75150
75453
|
}, firstLine = (run) => (`${run.stderr}
|
|
75151
75454
|
${run.stdout}`.split(`
|
|
75152
75455
|
`).find((line) => line.trim()) ?? "git failed").trim().slice(0, 300), stackFileName = (name) => `${name.replace(/[^A-Za-z0-9._-]+/gu, "_").replace(/^[._]+/u, "").slice(0, 48).toLowerCase()}-${createHash15("sha256").update(name).digest("hex").slice(0, 12)}.json`, fileFor, validName = (name) => {
|
|
@@ -75161,14 +75464,14 @@ ${run.stdout}`.split(`
|
|
|
75161
75464
|
}, isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), str3 = (value) => typeof value === "string" && value ? value : null, LOCK_WAIT_MS3 = 2000, HEARTBEAT_MS = 20000, HEARTBEAT_STALE_MS, OWNERLESS_STALE_MS = 30000, OWNER = "owner", readOwner = (lock) => {
|
|
75162
75465
|
let raw = null;
|
|
75163
75466
|
try {
|
|
75164
|
-
raw =
|
|
75467
|
+
raw = fs22.readFileSync(path42.join(lock, OWNER), "utf8");
|
|
75165
75468
|
} catch {
|
|
75166
75469
|
raw = null;
|
|
75167
75470
|
}
|
|
75168
75471
|
let age = null;
|
|
75169
|
-
for (const target of [
|
|
75472
|
+
for (const target of [path42.join(lock, OWNER), lock])
|
|
75170
75473
|
try {
|
|
75171
|
-
age = Date.now() -
|
|
75474
|
+
age = Date.now() - fs22.statSync(target).mtimeMs;
|
|
75172
75475
|
break;
|
|
75173
75476
|
} catch {}
|
|
75174
75477
|
return { raw, age };
|
|
@@ -75408,12 +75711,37 @@ var init_stack2 = __esm(() => {
|
|
|
75408
75711
|
});
|
|
75409
75712
|
|
|
75410
75713
|
// packages/workit-core/src/fanout.ts
|
|
75411
|
-
import { spawnSync as
|
|
75412
|
-
import
|
|
75413
|
-
import
|
|
75714
|
+
import { spawnSync as spawnSync21 } from "node:child_process";
|
|
75715
|
+
import fs23 from "node:fs";
|
|
75716
|
+
import path43 from "node:path";
|
|
75717
|
+
function expandBraces(pattern, limit = 64) {
|
|
75718
|
+
for (let index = 0;index < pattern.length; index += 1) {
|
|
75719
|
+
if (pattern[index] === "\\") {
|
|
75720
|
+
index += 1;
|
|
75721
|
+
continue;
|
|
75722
|
+
}
|
|
75723
|
+
const close = pattern.indexOf("}", index);
|
|
75724
|
+
if (pattern[index] !== "{" || close < index)
|
|
75725
|
+
continue;
|
|
75726
|
+
const head = pattern.slice(0, index);
|
|
75727
|
+
const tail = pattern.slice(close + 1);
|
|
75728
|
+
const out = [];
|
|
75729
|
+
for (const alternative of pattern.slice(index + 1, close).split(","))
|
|
75730
|
+
for (const rest of expandBraces(tail, limit)) {
|
|
75731
|
+
if (out.length >= limit)
|
|
75732
|
+
return out;
|
|
75733
|
+
out.push(`${head}${alternative}${rest}`);
|
|
75734
|
+
}
|
|
75735
|
+
return out;
|
|
75736
|
+
}
|
|
75737
|
+
return [pattern];
|
|
75738
|
+
}
|
|
75414
75739
|
function scopeSamples(pattern) {
|
|
75415
75740
|
if (!hasWildcard(pattern))
|
|
75416
75741
|
return [unescape2(pattern)];
|
|
75742
|
+
return [...new Set(expandBraces(pattern).map(sampleOne))];
|
|
75743
|
+
}
|
|
75744
|
+
function sampleOne(pattern) {
|
|
75417
75745
|
let out = "";
|
|
75418
75746
|
for (let index = 0;index < pattern.length; index += 1) {
|
|
75419
75747
|
const char = pattern[index];
|
|
@@ -75432,14 +75760,10 @@ function scopeSamples(pattern) {
|
|
|
75432
75760
|
out += "<any>";
|
|
75433
75761
|
else if (char === "?")
|
|
75434
75762
|
out += "x";
|
|
75435
|
-
else
|
|
75436
|
-
const close = pattern.indexOf("}", index);
|
|
75437
|
-
out += pattern.slice(index + 1, close).split(",")[0];
|
|
75438
|
-
index = close;
|
|
75439
|
-
} else
|
|
75763
|
+
else
|
|
75440
75764
|
out += char;
|
|
75441
75765
|
}
|
|
75442
|
-
return
|
|
75766
|
+
return out;
|
|
75443
75767
|
}
|
|
75444
75768
|
function globSource(pattern) {
|
|
75445
75769
|
let out = "";
|
|
@@ -75480,7 +75804,20 @@ function matchesScope(pattern, file) {
|
|
|
75480
75804
|
return regex.test(file) || !hasWildcard(pattern) && file.startsWith(`${unescape2(pattern)}/`);
|
|
75481
75805
|
}
|
|
75482
75806
|
function normalizePattern(raw) {
|
|
75483
|
-
let pattern = raw.trim()
|
|
75807
|
+
let pattern = raw.trim();
|
|
75808
|
+
if (pattern.includes("\\")) {
|
|
75809
|
+
if (/\\[*?{}]/u.test(pattern))
|
|
75810
|
+
return {
|
|
75811
|
+
error: "has \\*, \\?, \\{ or \\}: only \\[ and \\] are escapes; use / as the separator (src/**/*.ts)"
|
|
75812
|
+
};
|
|
75813
|
+
const brackets = /\\[[\]]/u.test(pattern);
|
|
75814
|
+
if (brackets && pattern.replace(/\\[[\]]/gu, "").includes("\\"))
|
|
75815
|
+
return {
|
|
75816
|
+
error: "mixes \\[ or \\] escapes with other backslashes, which is ambiguous: use / as the separator (app/\\[id\\]/page.tsx)"
|
|
75817
|
+
};
|
|
75818
|
+
if (!brackets)
|
|
75819
|
+
pattern = pattern.replaceAll("\\", "/");
|
|
75820
|
+
}
|
|
75484
75821
|
while (pattern.startsWith("./"))
|
|
75485
75822
|
pattern = pattern.slice(2);
|
|
75486
75823
|
if (pattern === "." || pattern === "")
|
|
@@ -75607,7 +75944,7 @@ function parsePlan(raw, defaults, name) {
|
|
|
75607
75944
|
id,
|
|
75608
75945
|
branch,
|
|
75609
75946
|
base: optional("base") ?? "",
|
|
75610
|
-
worktree: optional("worktree") ??
|
|
75947
|
+
worktree: path43.resolve(defaults.root, optional("worktree") ?? path43.join(defaults.worktreeRoot, id)),
|
|
75611
75948
|
tier,
|
|
75612
75949
|
dependsOn: [...new Set(dependsOn ?? [])],
|
|
75613
75950
|
scope,
|
|
@@ -75783,6 +76120,30 @@ function analyzeOverlap(plan, candidates) {
|
|
|
75783
76120
|
});
|
|
75784
76121
|
return { overlaps, conflicts };
|
|
75785
76122
|
}
|
|
76123
|
+
function caseIndex(files) {
|
|
76124
|
+
const index = new Map;
|
|
76125
|
+
for (const file of files) {
|
|
76126
|
+
const parts = file.split("/");
|
|
76127
|
+
for (let end = 1;end <= parts.length; end += 1) {
|
|
76128
|
+
const prefix = parts.slice(0, end).join("/");
|
|
76129
|
+
const key = prefix.toLowerCase();
|
|
76130
|
+
(index.get(key) ?? index.set(key, new Set).get(key))?.add(prefix);
|
|
76131
|
+
}
|
|
76132
|
+
}
|
|
76133
|
+
return index;
|
|
76134
|
+
}
|
|
76135
|
+
function caseClash(index, file) {
|
|
76136
|
+
const parts = file.split("/");
|
|
76137
|
+
for (let end = 1;end <= parts.length; end += 1) {
|
|
76138
|
+
const prefix = parts.slice(0, end).join("/");
|
|
76139
|
+
if (prefix.includes("<any>"))
|
|
76140
|
+
return null;
|
|
76141
|
+
const spellings = index.get(prefix.toLowerCase());
|
|
76142
|
+
if (spellings && !spellings.has(prefix))
|
|
76143
|
+
return { path: prefix, trunk: [...spellings].toSorted()[0] };
|
|
76144
|
+
}
|
|
76145
|
+
return null;
|
|
76146
|
+
}
|
|
75786
76147
|
function branchRef(cwd, branch) {
|
|
75787
76148
|
if (!branch || branch.startsWith("-"))
|
|
75788
76149
|
return null;
|
|
@@ -75805,16 +76166,19 @@ function defaultTrunk(cwd) {
|
|
|
75805
76166
|
const base = defaultBase(cwd);
|
|
75806
76167
|
return base ? base.replace(/^origin\//u, "") : "main";
|
|
75807
76168
|
}
|
|
75808
|
-
function
|
|
76169
|
+
function mainCheckout(cwd) {
|
|
75809
76170
|
const common = gitCommonDir(cwd);
|
|
75810
|
-
|
|
75811
|
-
|
|
76171
|
+
return common && path43.basename(common) === ".git" ? path43.dirname(common) : path43.resolve(cwd);
|
|
76172
|
+
}
|
|
76173
|
+
function worktreeRoot(cwd) {
|
|
76174
|
+
const main = mainCheckout(cwd);
|
|
76175
|
+
return path43.join(path43.dirname(main), `${path43.basename(main)}-wt`);
|
|
75812
76176
|
}
|
|
75813
76177
|
function fanoutsDir(cwd) {
|
|
75814
76178
|
const root = storeRoot(cwd);
|
|
75815
76179
|
if (!root.ok)
|
|
75816
76180
|
return fanoutFail(root.code, root.error, root.unblock);
|
|
75817
|
-
return { ok: true, data:
|
|
76181
|
+
return { ok: true, data: path43.join(root.value.root, "fanouts") };
|
|
75818
76182
|
}
|
|
75819
76183
|
function parseStored(raw) {
|
|
75820
76184
|
if (!isRecord8(raw) || typeof raw.name !== "string" || typeof raw.trunk !== "string")
|
|
@@ -75871,10 +76235,10 @@ function readFanout(cwd, name) {
|
|
|
75871
76235
|
const dir = fanoutsDir(cwd);
|
|
75872
76236
|
if (!dir.ok)
|
|
75873
76237
|
return dir;
|
|
75874
|
-
const file =
|
|
76238
|
+
const file = path43.join(dir.data, stackFileName(name));
|
|
75875
76239
|
let parsed = null;
|
|
75876
76240
|
try {
|
|
75877
|
-
parsed = parseStored(JSON.parse(
|
|
76241
|
+
parsed = parseStored(JSON.parse(fs23.readFileSync(file, "utf8")));
|
|
75878
76242
|
} catch (error) {
|
|
75879
76243
|
if (error.code === "ENOENT")
|
|
75880
76244
|
return { ok: true, data: null };
|
|
@@ -75888,7 +76252,7 @@ function listFanouts(cwd) {
|
|
|
75888
76252
|
return dir;
|
|
75889
76253
|
let names = [];
|
|
75890
76254
|
try {
|
|
75891
|
-
names =
|
|
76255
|
+
names = fs23.readdirSync(dir.data).filter((entry) => entry.endsWith(".json"));
|
|
75892
76256
|
} catch {
|
|
75893
76257
|
return { ok: true, data: [] };
|
|
75894
76258
|
}
|
|
@@ -75896,7 +76260,7 @@ function listFanouts(cwd) {
|
|
|
75896
76260
|
for (const entry of names) {
|
|
75897
76261
|
let parsed = null;
|
|
75898
76262
|
try {
|
|
75899
|
-
parsed = parseStored(JSON.parse(
|
|
76263
|
+
parsed = parseStored(JSON.parse(fs23.readFileSync(path43.join(dir.data, entry), "utf8")));
|
|
75900
76264
|
} catch {
|
|
75901
76265
|
parsed = null;
|
|
75902
76266
|
}
|
|
@@ -75926,16 +76290,16 @@ function writeFanout(cwd, plan) {
|
|
|
75926
76290
|
const dir = fanoutsDir(cwd);
|
|
75927
76291
|
if (!dir.ok)
|
|
75928
76292
|
return dir;
|
|
75929
|
-
const file =
|
|
76293
|
+
const file = path43.join(dir.data, stackFileName(plan.name));
|
|
75930
76294
|
const temp = `${file}.${process.pid}.tmp`;
|
|
75931
76295
|
try {
|
|
75932
|
-
|
|
75933
|
-
|
|
76296
|
+
fs23.mkdirSync(dir.data, { recursive: true });
|
|
76297
|
+
fs23.writeFileSync(temp, `${JSON.stringify(plan, null, 2)}
|
|
75934
76298
|
`);
|
|
75935
|
-
|
|
76299
|
+
fs23.renameSync(temp, file);
|
|
75936
76300
|
return { ok: true, data: file };
|
|
75937
76301
|
} catch (error) {
|
|
75938
|
-
|
|
76302
|
+
fs23.rmSync(temp, { force: true });
|
|
75939
76303
|
return fanoutFail("unavailable", `cannot write ${file}: ${error.message}`);
|
|
75940
76304
|
}
|
|
75941
76305
|
}
|
|
@@ -75943,7 +76307,11 @@ function planFanout(cwd, input, now = new Date) {
|
|
|
75943
76307
|
const dir = fanoutsDir(cwd);
|
|
75944
76308
|
if (!dir.ok)
|
|
75945
76309
|
return dir;
|
|
75946
|
-
const parsed = parsePlan(input.trunk && isRecord8(input.raw) ? { ...input.raw, trunk: input.trunk } : input.raw, {
|
|
76310
|
+
const parsed = parsePlan(input.trunk && isRecord8(input.raw) ? { ...input.raw, trunk: input.trunk } : input.raw, {
|
|
76311
|
+
trunk: input.trunk ?? defaultTrunk(cwd),
|
|
76312
|
+
root: mainCheckout(cwd),
|
|
76313
|
+
worktreeRoot: worktreeRoot(cwd)
|
|
76314
|
+
}, input.name);
|
|
75947
76315
|
if (!parsed.ok)
|
|
75948
76316
|
return fanoutFail("invalid_input", `the plan is incomplete: ${parsed.problems[0]}${parsed.problems.length > 1 ? ` (+${parsed.problems.length - 1} more)` : ""}`, "fill every listed field, then run workit fanout plan again", { problems: parsed.problems });
|
|
75949
76317
|
const plan = parsed.plan;
|
|
@@ -75958,6 +76326,20 @@ function planFanout(cwd, input, now = new Date) {
|
|
|
75958
76326
|
const { overlaps, conflicts } = analyzeOverlap(plan, [...tracked, ...samples]);
|
|
75959
76327
|
if (conflicts.length)
|
|
75960
76328
|
return fanoutFail("blocked", `${conflicts.length} file-scope overlap${conflicts.length === 1 ? "" : "s"} between slices: ${conflicts[0].slices.join(" and ")} on ${conflicts[0].paths[0]}${conflicts[0].paths.length > 1 ? ` (+${conflicts[0].paths.length - 1})` : ""}`, conflicts[0].suggestion.text, { name: plan.name, conflicts, overlaps });
|
|
76329
|
+
const trunkCase = caseIndex(tracked);
|
|
76330
|
+
const clashes = [];
|
|
76331
|
+
for (const slice of plan.slices) {
|
|
76332
|
+
const seen = new Set;
|
|
76333
|
+
for (const sample of [...slice.scope, ...slice.owns].flatMap(scopeSamples)) {
|
|
76334
|
+
const clash = caseClash(trunkCase, sample);
|
|
76335
|
+
if (clash && !seen.has(clash.path)) {
|
|
76336
|
+
seen.add(clash.path);
|
|
76337
|
+
clashes.push({ slice: slice.id, ...clash });
|
|
76338
|
+
}
|
|
76339
|
+
}
|
|
76340
|
+
}
|
|
76341
|
+
if (clashes.length)
|
|
76342
|
+
return fanoutFail("blocked", `slice ${clashes[0].slice} names ${clashes[0].path}, which differs only in case from ${plan.trunk}'s ${clashes[0].trunk} (one path on macOS and Windows)`, `use ${plan.trunk}'s spelling ${clashes[0].trunk} in slice ${clashes[0].slice}'s scope, then workit fanout plan again`, { name: plan.name, caseCollisions: clashes, overlaps });
|
|
75961
76343
|
const existing = readFanout(cwd, plan.name);
|
|
75962
76344
|
const previous = existing.ok ? existing.data : null;
|
|
75963
76345
|
plan.createdAt = previous?.createdAt || now.toISOString();
|
|
@@ -76014,7 +76396,7 @@ var FANOUT_VERSION = 1, TIERS, fanoutFail = (code, error, unblock, data) => ({
|
|
|
76014
76396
|
error,
|
|
76015
76397
|
...unblock ? { unblock } : {},
|
|
76016
76398
|
...data ? { data } : {}
|
|
76017
|
-
}), DEFAULT_SHARED, escapeRegExp4 = (text) => text.replace(/[.+^$()|[\]\\{}*?]/gu, "\\$&"), unescape2 = (pattern) => pattern.replace(/\\(.)/gu, "$1"), hasWildcard = (pattern) => /[*?{]/u.test(pattern.replace(/\\./gu, "")), compiled, matchesAny = (patterns, file) => patterns.some((pattern) => matchesScope(pattern, file)), isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value),
|
|
76399
|
+
}), DEFAULT_SHARED, escapeRegExp4 = (text) => text.replace(/[.+^$()|[\]\\{}*?]/gu, "\\$&"), unescape2 = (pattern) => pattern.replace(/\\(.)/gu, "$1"), hasWildcard = (pattern) => /[*?{]/u.test(pattern.replace(/\\./gu, "")), compiled, matchesAny = (patterns, file) => patterns.some((pattern) => matchesScope(pattern, file)), isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), PLACEHOLDER3, filled = (text) => text.trim() !== "" && !PLACEHOLDER3.test(text.trim()), textList = (value) => {
|
|
76018
76400
|
if (typeof value === "string")
|
|
76019
76401
|
return [value];
|
|
76020
76402
|
if (Array.isArray(value) && value.every((item) => typeof item === "string"))
|
|
@@ -76032,11 +76414,11 @@ var FANOUT_VERSION = 1, TIERS, fanoutFail = (code, error, unblock, data) => ({
|
|
|
76032
76414
|
edges,
|
|
76033
76415
|
text: edges.map((edge) => `add "${edge.dependsOn}" to "dependsOn" of slice ${edge.slice}`).join("; ")
|
|
76034
76416
|
};
|
|
76035
|
-
}, gitRun4 = (cwd, args) => {
|
|
76036
|
-
const run =
|
|
76417
|
+
}, gitRun4 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
|
|
76418
|
+
const run = spawnSync21("git", args, {
|
|
76037
76419
|
cwd,
|
|
76038
76420
|
encoding: "utf8",
|
|
76039
|
-
timeout:
|
|
76421
|
+
timeout: timeoutMs,
|
|
76040
76422
|
killSignal: "SIGKILL",
|
|
76041
76423
|
windowsHide: true,
|
|
76042
76424
|
maxBuffer: 64 * 1024 * 1024,
|
|
@@ -76065,10 +76447,204 @@ var init_fanout = __esm(() => {
|
|
|
76065
76447
|
"CHANGELOG.md"
|
|
76066
76448
|
];
|
|
76067
76449
|
compiled = new Map;
|
|
76068
|
-
|
|
76450
|
+
PLACEHOLDER3 = /^(?:<[^>]*>|tbd|todo|fixme|\?+|\.\.\.|…)$/iu;
|
|
76069
76451
|
ID = /^[a-z0-9][a-z0-9._-]{0,63}$/u;
|
|
76070
76452
|
});
|
|
76071
76453
|
|
|
76454
|
+
// packages/workit-core/src/fanout-landed.ts
|
|
76455
|
+
import { spawnSync as spawnSync22 } from "node:child_process";
|
|
76456
|
+
function forgeBreaker(resolved) {
|
|
76457
|
+
if (!resolved)
|
|
76458
|
+
return { forge: null, down: () => null };
|
|
76459
|
+
let tripped = null;
|
|
76460
|
+
const guard = (call) => (...args) => {
|
|
76461
|
+
if (tripped)
|
|
76462
|
+
return { ok: false, code: "unavailable", error: tripped };
|
|
76463
|
+
const result = call(...args);
|
|
76464
|
+
if (!result.ok && result.code === "unavailable")
|
|
76465
|
+
tripped = result.error;
|
|
76466
|
+
return result;
|
|
76467
|
+
};
|
|
76468
|
+
const inner = resolved.forge;
|
|
76469
|
+
const forge = Object.create(inner);
|
|
76470
|
+
forge.findPr = guard(inner.findPr.bind(inner));
|
|
76471
|
+
forge.prStatus = guard(inner.prStatus.bind(inner));
|
|
76472
|
+
return { forge: { ...resolved, forge }, down: () => tripped };
|
|
76473
|
+
}
|
|
76474
|
+
function branchCreatedAt(cwd, branch) {
|
|
76475
|
+
if (!branch || branch.startsWith("-"))
|
|
76476
|
+
return null;
|
|
76477
|
+
const log = gitRun4(cwd, ["log", "-g", "--format=%H", `refs/heads/${branch}`, "--"]);
|
|
76478
|
+
if (!log.ok)
|
|
76479
|
+
return null;
|
|
76480
|
+
return log.stdout.trim().split(`
|
|
76481
|
+
`).findLast(Boolean) ?? null;
|
|
76482
|
+
}
|
|
76483
|
+
function hasOwnCommits2(cwd, branch, start, head) {
|
|
76484
|
+
if (!branch || branch.startsWith("-"))
|
|
76485
|
+
return false;
|
|
76486
|
+
const log = gitRun4(cwd, ["log", "-g", "--format=%H%x00%gs", `refs/heads/${branch}`, "--"]);
|
|
76487
|
+
if (!log.ok)
|
|
76488
|
+
return false;
|
|
76489
|
+
for (const line of log.stdout.split(`
|
|
76490
|
+
`)) {
|
|
76491
|
+
const [sha, subject = ""] = line.split("\x00");
|
|
76492
|
+
if (!sha)
|
|
76493
|
+
continue;
|
|
76494
|
+
const made = /^(?:commit(?: \((?:initial|amend)\))?|cherry-pick|rebase(?: -i)? \(pick\)):/u.test(subject);
|
|
76495
|
+
const onto = /^rebase(?: -i)? \(finish\): \S+ onto ([0-9a-f]+)/u.exec(subject)?.[1];
|
|
76496
|
+
if (!made && !(onto && !sha.startsWith(onto) && !onto.startsWith(sha)))
|
|
76497
|
+
continue;
|
|
76498
|
+
if (sha !== head && !isAncestor2(cwd, sha, head))
|
|
76499
|
+
continue;
|
|
76500
|
+
if (start && (sha === start || isAncestor2(cwd, sha, start)))
|
|
76501
|
+
continue;
|
|
76502
|
+
return true;
|
|
76503
|
+
}
|
|
76504
|
+
return false;
|
|
76505
|
+
}
|
|
76506
|
+
function reverted(cwd, trunk, landedHead, start) {
|
|
76507
|
+
const fork = mergeBase(cwd, landedHead, trunk);
|
|
76508
|
+
if (fork && fork !== landedHead)
|
|
76509
|
+
return unchangedSince(cwd, fork, landedHead, trunk);
|
|
76510
|
+
const before = start && start !== landedHead && isAncestor2(cwd, start, landedHead) ? start : gitRun4(cwd, ["rev-parse", "--verify", "-q", `${landedHead}^1`]).stdout.trim();
|
|
76511
|
+
return before ? unchangedSince(cwd, before, landedHead, trunk) : false;
|
|
76512
|
+
}
|
|
76513
|
+
function unchangedSince(cwd, before, after, trunk) {
|
|
76514
|
+
const paths = gitRun4(cwd, ["diff", "--name-only", "--no-renames", "-z", before, after]);
|
|
76515
|
+
const files = paths.ok ? paths.stdout.split("\x00").filter(Boolean) : [];
|
|
76516
|
+
if (files.length === 0)
|
|
76517
|
+
return false;
|
|
76518
|
+
return gitRun4(cwd, ["diff", "--quiet", before, trunk, "--", ...files]).ok;
|
|
76519
|
+
}
|
|
76520
|
+
function trunkPatchIds(ctx, fork) {
|
|
76521
|
+
const cache = ctx.cache ??= new Map;
|
|
76522
|
+
const known = cache.get(fork);
|
|
76523
|
+
if (known)
|
|
76524
|
+
return known;
|
|
76525
|
+
const ids = new Set;
|
|
76526
|
+
const log = gitRun4(ctx.cwd, [
|
|
76527
|
+
"log",
|
|
76528
|
+
"--no-merges",
|
|
76529
|
+
`--max-count=${TRUNK_SCAN}`,
|
|
76530
|
+
"-p",
|
|
76531
|
+
"--format=commit %H",
|
|
76532
|
+
"-U3",
|
|
76533
|
+
"--diff-algorithm=myers",
|
|
76534
|
+
"--indent-heuristic",
|
|
76535
|
+
"--no-color",
|
|
76536
|
+
"--no-ext-diff",
|
|
76537
|
+
"--no-textconv",
|
|
76538
|
+
"--no-renames",
|
|
76539
|
+
"--full-index",
|
|
76540
|
+
"--src-prefix=a/",
|
|
76541
|
+
"--dst-prefix=b/",
|
|
76542
|
+
`${fork}..${ctx.trunk}`,
|
|
76543
|
+
"--"
|
|
76544
|
+
]);
|
|
76545
|
+
if (log.ok && log.stdout.trim()) {
|
|
76546
|
+
const run = spawnSync22("git", ["patch-id", "--stable"], {
|
|
76547
|
+
cwd: ctx.cwd,
|
|
76548
|
+
input: log.stdout,
|
|
76549
|
+
encoding: "utf8",
|
|
76550
|
+
timeout: GIT_TIMEOUTS.local,
|
|
76551
|
+
windowsHide: true,
|
|
76552
|
+
maxBuffer: 64 * 1024 * 1024
|
|
76553
|
+
});
|
|
76554
|
+
for (const line of (run.stdout ?? "").split(`
|
|
76555
|
+
`)) {
|
|
76556
|
+
const id = line.trim().split(/\s+/u)[0];
|
|
76557
|
+
if (id)
|
|
76558
|
+
ids.add(id);
|
|
76559
|
+
}
|
|
76560
|
+
}
|
|
76561
|
+
cache.set(fork, ids);
|
|
76562
|
+
return ids;
|
|
76563
|
+
}
|
|
76564
|
+
function linked(rows, input, slice, head) {
|
|
76565
|
+
const since = input.since ? Date.parse(input.since) : Number.NaN;
|
|
76566
|
+
return rows.some((row) => (Number.isNaN(since) || Date.parse(String(row.at)) >= since) && (row.type === "fanout.worktree.created" && row.fanout === input.fanout && row.slice === slice.id || head !== null && row.branch === slice.branch && row.head === head));
|
|
76567
|
+
}
|
|
76568
|
+
function detectLanding(ctx, slice, input) {
|
|
76569
|
+
const out = {
|
|
76570
|
+
landed: false,
|
|
76571
|
+
how: null,
|
|
76572
|
+
pr: null,
|
|
76573
|
+
landedHead: null,
|
|
76574
|
+
forgeError: null,
|
|
76575
|
+
note: null
|
|
76576
|
+
};
|
|
76577
|
+
const landed = (how, landedHead) => {
|
|
76578
|
+
if (!hasCommit(ctx.cwd, landedHead) || !reverted(ctx.cwd, ctx.trunk, landedHead, input.createdFrom))
|
|
76579
|
+
return { ...out, landed: true, how, landedHead };
|
|
76580
|
+
return {
|
|
76581
|
+
...out,
|
|
76582
|
+
note: `landed (${how}${out.pr ? ` #${out.pr.number}` : ""}), then reverted on the trunk: its paths read as before it`
|
|
76583
|
+
};
|
|
76584
|
+
};
|
|
76585
|
+
const { head } = input;
|
|
76586
|
+
if (ctx.forge) {
|
|
76587
|
+
const found = ctx.forge.forge.findPr(slice.branch, {
|
|
76588
|
+
owner: ctx.forge.headRepo.split("/")[0] ?? null,
|
|
76589
|
+
projectId: ctx.forge.headProjectId,
|
|
76590
|
+
sha: head
|
|
76591
|
+
});
|
|
76592
|
+
if (!found.ok)
|
|
76593
|
+
out.forgeError = found.error;
|
|
76594
|
+
else if (found.data) {
|
|
76595
|
+
out.pr = found.data;
|
|
76596
|
+
if (found.data.state === "merged") {
|
|
76597
|
+
const merged = found.data.headSha;
|
|
76598
|
+
const label = `#${found.data.number}`;
|
|
76599
|
+
if (!head) {
|
|
76600
|
+
if (!linked(input.rows, input, slice, merged)) {
|
|
76601
|
+
out.pr = null;
|
|
76602
|
+
out.note = `${slice.branch} is gone; merged ${label} is not linked to this slice in the ledger (an older branch of the same name?)`;
|
|
76603
|
+
return out;
|
|
76604
|
+
}
|
|
76605
|
+
return merged ? landed("pr_merged", merged) : { ...out, landed: true, how: "pr_merged" };
|
|
76606
|
+
}
|
|
76607
|
+
if (!merged || head === merged || isAncestor2(ctx.cwd, head, merged))
|
|
76608
|
+
return landed("pr_merged", merged ?? head);
|
|
76609
|
+
out.note = isAncestor2(ctx.cwd, merged, head) ? `${label} merged at ${merged.slice(0, 12)}; ${slice.branch} is ahead of it with newer commits` : `${label} merged at ${merged.slice(0, 12)}; ${slice.branch} has diverged from it (rewritten after the merge)`;
|
|
76610
|
+
return out;
|
|
76611
|
+
}
|
|
76612
|
+
}
|
|
76613
|
+
}
|
|
76614
|
+
if (!head)
|
|
76615
|
+
return out;
|
|
76616
|
+
const fork = mergeBase(ctx.cwd, head, ctx.trunk);
|
|
76617
|
+
if (fork === head) {
|
|
76618
|
+
return hasOwnCommits2(ctx.cwd, slice.branch, input.createdFrom, head) ? landed("on_trunk", head) : out;
|
|
76619
|
+
}
|
|
76620
|
+
if (!fork)
|
|
76621
|
+
return out;
|
|
76622
|
+
const trunkIds = trunkPatchIds(ctx, fork);
|
|
76623
|
+
if (trunkIds.size === 0)
|
|
76624
|
+
return out;
|
|
76625
|
+
const froms = [fork];
|
|
76626
|
+
if (input.parentHead && input.parentHead !== head && isAncestor2(ctx.cwd, input.parentHead, head))
|
|
76627
|
+
froms.push(input.parentHead);
|
|
76628
|
+
for (const from of froms) {
|
|
76629
|
+
const id = patchId(ctx.cwd, from, head);
|
|
76630
|
+
if (id && trunkIds.has(id))
|
|
76631
|
+
return landed("squash_patch", head);
|
|
76632
|
+
}
|
|
76633
|
+
return out;
|
|
76634
|
+
}
|
|
76635
|
+
function sliceStart(cwd, rows, fanout, slice) {
|
|
76636
|
+
const reflog = branchCreatedAt(cwd, slice.branch);
|
|
76637
|
+
if (reflog)
|
|
76638
|
+
return reflog;
|
|
76639
|
+
const row = rows.findLast((candidate) => candidate.type === "fanout.worktree.created" && candidate.fanout === fanout && candidate.slice === slice.id && typeof candidate.baseSha === "string");
|
|
76640
|
+
return row?.baseSha ?? null;
|
|
76641
|
+
}
|
|
76642
|
+
var isAncestor2 = (cwd, a, b) => gitRun4(cwd, ["merge-base", "--is-ancestor", a, b]).ok, TRUNK_SCAN = 500;
|
|
76643
|
+
var init_fanout_landed = __esm(() => {
|
|
76644
|
+
init_rev();
|
|
76645
|
+
init_fanout();
|
|
76646
|
+
});
|
|
76647
|
+
|
|
76072
76648
|
// packages/workit-core/src/fanout-check.ts
|
|
76073
76649
|
function mergeConflicts(cwd, a, b) {
|
|
76074
76650
|
const run = gitRun4(cwd, [
|
|
@@ -76102,10 +76678,15 @@ function checkFanout(cwd, plan, options = {}) {
|
|
|
76102
76678
|
const tips = new Map(plan.slices.map((slice) => [slice.id, branchRef(cwd, slice.branch)]));
|
|
76103
76679
|
const ledger = readLedger(cwd);
|
|
76104
76680
|
const rows = ledger.ok ? ledger.value.rows : [];
|
|
76681
|
+
const breaker = forgeBreaker(options.forge ?? null);
|
|
76682
|
+
const landings = sliceLandings(cwd, plan, trunk, tips, rows, breaker.forge);
|
|
76683
|
+
const trunkCase = caseIndex(nulList2(gitRun4(cwd, ["ls-tree", "-r", "-z", "--name-only", trunk]).stdout));
|
|
76105
76684
|
const checks = new Map;
|
|
76106
76685
|
const changedBy = new Map;
|
|
76107
|
-
for (const slice of plan.slices)
|
|
76108
|
-
|
|
76686
|
+
for (const slice of plan.slices) {
|
|
76687
|
+
const landing = landings.get(slice.id);
|
|
76688
|
+
checks.set(slice.id, landing.landed ? landedCheck(slice, tips.get(slice.id) ?? null, landing) : inspect(cwd, plan, slice, { trunk, trunkCase, tips, landings, rows }, changedBy));
|
|
76689
|
+
}
|
|
76109
76690
|
const conflicts = [];
|
|
76110
76691
|
for (let i = 0;i < order.length; i += 1)
|
|
76111
76692
|
for (let j = i + 1;j < order.length; j += 1) {
|
|
@@ -76113,7 +76694,9 @@ function checkFanout(cwd, plan, options = {}) {
|
|
|
76113
76694
|
const second = order[j];
|
|
76114
76695
|
const a = tips.get(first);
|
|
76115
76696
|
const b = tips.get(second);
|
|
76116
|
-
if (
|
|
76697
|
+
if (landings.get(first)?.landed || landings.get(second)?.landed)
|
|
76698
|
+
continue;
|
|
76699
|
+
if (!a || !b || isAncestor3(cwd, a, b) || isAncestor3(cwd, b, a))
|
|
76117
76700
|
continue;
|
|
76118
76701
|
const check = checks.get(second);
|
|
76119
76702
|
const theirs = new Map((changedBy.get(first) ?? []).map((file) => [file.toLowerCase(), file]));
|
|
@@ -76151,19 +76734,29 @@ function checkFanout(cwd, plan, options = {}) {
|
|
|
76151
76734
|
}
|
|
76152
76735
|
for (const id of order) {
|
|
76153
76736
|
const check = checks.get(id);
|
|
76154
|
-
|
|
76737
|
+
if (check.status === "landed")
|
|
76738
|
+
continue;
|
|
76739
|
+
check.waitsFor = (byId.get(id)?.dependsOn ?? []).filter((dep) => checks.get(dep)?.status !== "ready" && checks.get(dep)?.status !== "landed");
|
|
76155
76740
|
if (check.waitsFor.length)
|
|
76156
76741
|
check.reasons.push(`waits for ${check.waitsFor.join(", ")}`);
|
|
76157
76742
|
check.status = check.reasons.length ? "blocked" : "ready";
|
|
76158
76743
|
}
|
|
76159
76744
|
const selected = options.only?.length ? new Set(options.only) : null;
|
|
76160
76745
|
const slices = order.filter((id) => !selected || selected.has(id)).map((id) => checks.get(id));
|
|
76161
|
-
const ready = slices.every((check) => check.status
|
|
76746
|
+
const ready = slices.every((check) => check.status !== "blocked");
|
|
76162
76747
|
const blocked = slices.find((check) => check.status === "blocked");
|
|
76163
76748
|
const notes = plan.v === FANOUT_VERSION ? [] : [
|
|
76164
76749
|
`plan file version ${plan.v} is not ${FANOUT_VERSION}; read leniently (re-plan to rewrite it)`
|
|
76165
76750
|
];
|
|
76166
|
-
const
|
|
76751
|
+
const forgeErrors = [...landings.values()].flatMap((landing) => landing.forgeError ? [landing.forgeError] : []);
|
|
76752
|
+
if (!options.forge)
|
|
76753
|
+
notes.push("landed slices detected with git only (no forge): a squash merge may be missed");
|
|
76754
|
+
else if (breaker.down())
|
|
76755
|
+
notes.push(`forge went down (${breaker.down()}); git alone decided the remaining slices`);
|
|
76756
|
+
else if (forgeErrors.length)
|
|
76757
|
+
notes.push(`forge lookups failed (${forgeErrors[0]}); git alone decided those slices`);
|
|
76758
|
+
const toLand = slices.filter((check) => check.status === "ready").map((check) => check.id);
|
|
76759
|
+
const next = blocked ? unblockFor(blocked, plan) : toLand.length ? `land in this order: ${toLand.join(", ")} (workit pr merge each once verified; stacked slices with workit stack land)` : "every slice has landed";
|
|
76167
76760
|
return {
|
|
76168
76761
|
ok: true,
|
|
76169
76762
|
data: {
|
|
@@ -76179,28 +76772,61 @@ function checkFanout(cwd, plan, options = {}) {
|
|
|
76179
76772
|
}
|
|
76180
76773
|
};
|
|
76181
76774
|
}
|
|
76182
|
-
function
|
|
76183
|
-
const
|
|
76184
|
-
const
|
|
76185
|
-
|
|
76186
|
-
|
|
76187
|
-
|
|
76188
|
-
|
|
76189
|
-
|
|
76190
|
-
|
|
76191
|
-
|
|
76192
|
-
|
|
76193
|
-
|
|
76194
|
-
|
|
76195
|
-
|
|
76196
|
-
|
|
76775
|
+
function sliceLandings(cwd, plan, trunk, tips, rows, forge) {
|
|
76776
|
+
const ctx = { cwd, trunk, forge };
|
|
76777
|
+
const out = new Map;
|
|
76778
|
+
const byId = new Map(plan.slices.map((slice) => [slice.id, slice]));
|
|
76779
|
+
for (const id of landingOrder(plan.slices)) {
|
|
76780
|
+
const slice = byId.get(id);
|
|
76781
|
+
const parent = plan.slices.find((other) => other.branch === slice.base);
|
|
76782
|
+
const parentHead = parent ? tips.get(parent.id) ?? out.get(parent.id)?.landedHead ?? null : null;
|
|
76783
|
+
out.set(id, detectLanding(ctx, slice, {
|
|
76784
|
+
head: tips.get(id) ?? null,
|
|
76785
|
+
createdFrom: sliceStart(cwd, rows, plan.name, slice),
|
|
76786
|
+
parentHead,
|
|
76787
|
+
fanout: plan.name,
|
|
76788
|
+
since: plan.createdAt || null,
|
|
76789
|
+
rows
|
|
76790
|
+
}));
|
|
76791
|
+
}
|
|
76792
|
+
return out;
|
|
76793
|
+
}
|
|
76794
|
+
function landedCheck(slice, head, landing) {
|
|
76795
|
+
return {
|
|
76796
|
+
...blank2(slice, head),
|
|
76797
|
+
status: "landed",
|
|
76798
|
+
landed: { how: landing.how ?? "pr_merged", pr: landing.pr?.number ?? null }
|
|
76197
76799
|
};
|
|
76800
|
+
}
|
|
76801
|
+
function trunkCaseCollisions(cwd, ctx, head, changed) {
|
|
76802
|
+
const headFiles = treeFiles(cwd, head);
|
|
76803
|
+
const forkFiles = treeFiles(cwd, gitRun4(cwd, ["merge-base", head, ctx.trunk]).stdout.trim() || null);
|
|
76804
|
+
if (!headFiles)
|
|
76805
|
+
return [];
|
|
76806
|
+
const present = (files, prefix) => Boolean(files && (files.has(prefix) || [...files].some((file) => file.startsWith(`${prefix}/`))));
|
|
76807
|
+
const out = new Set;
|
|
76808
|
+
for (const file of changed) {
|
|
76809
|
+
if (!headFiles.has(file))
|
|
76810
|
+
continue;
|
|
76811
|
+
const clash = caseClash(ctx.trunkCase, file);
|
|
76812
|
+
if (!clash)
|
|
76813
|
+
continue;
|
|
76814
|
+
if (present(forkFiles, clash.trunk) && !present(headFiles, clash.trunk))
|
|
76815
|
+
continue;
|
|
76816
|
+
out.add(`${clash.path} ~ ${clash.trunk}`);
|
|
76817
|
+
}
|
|
76818
|
+
return [...out].toSorted();
|
|
76819
|
+
}
|
|
76820
|
+
function inspect(cwd, plan, slice, ctx, changedBy) {
|
|
76821
|
+
const { trunk, tips, rows } = ctx;
|
|
76822
|
+
const head = tips.get(slice.id) ?? null;
|
|
76823
|
+
const check = blank2(slice, head);
|
|
76198
76824
|
if (!head) {
|
|
76199
|
-
check.reasons.push(`branch ${slice.branch} not found (not started,
|
|
76825
|
+
check.reasons.push(`branch ${slice.branch} not found (not started, or not fetched)`);
|
|
76200
76826
|
return check;
|
|
76201
76827
|
}
|
|
76202
76828
|
const parent = plan.slices.find((other) => other.branch === slice.base);
|
|
76203
|
-
const baseRef = slice.base === plan.trunk ? trunk : parent ? tips.get(parent.id) ?? null : branchRef(cwd, slice.base);
|
|
76829
|
+
const baseRef = slice.base === plan.trunk ? trunk : parent ? tips.get(parent.id) ?? ctx.landings.get(parent.id)?.landedHead ?? null : branchRef(cwd, slice.base);
|
|
76204
76830
|
if (!SAFE(baseRef)) {
|
|
76205
76831
|
check.reasons.push(`base ${slice.base} not found`);
|
|
76206
76832
|
return check;
|
|
@@ -76218,6 +76844,9 @@ function inspect(cwd, plan, slice, trunk, tips, rows, changedBy) {
|
|
|
76218
76844
|
check.outOfScope = changed.filter((file) => !inScope(plan, slice, file)).toSorted();
|
|
76219
76845
|
if (check.outOfScope.length)
|
|
76220
76846
|
check.reasons.push(`edits outside its scope: ${check.outOfScope.join(", ")}`);
|
|
76847
|
+
check.caseCollisions = trunkCaseCollisions(cwd, ctx, head, changed);
|
|
76848
|
+
if (check.caseCollisions.length)
|
|
76849
|
+
check.reasons.push(`differs only in case from ${plan.trunk}: ${check.caseCollisions.join(", ")}`);
|
|
76221
76850
|
const trunkResult = mergeConflicts(cwd, trunk, head);
|
|
76222
76851
|
if (Array.isArray(trunkResult)) {
|
|
76223
76852
|
check.trunkConflicts = trunkResult;
|
|
@@ -76231,9 +76860,11 @@ function inspect(cwd, plan, slice, trunk, tips, rows, changedBy) {
|
|
|
76231
76860
|
}
|
|
76232
76861
|
function unblockFor(check, plan) {
|
|
76233
76862
|
if (!check.head)
|
|
76234
|
-
return `start or fetch ${check.branch}, then workit fanout check
|
|
76863
|
+
return `start or fetch ${check.branch}, then workit fanout check (a landed slice is detected through its merged PR when the forge is reachable)`;
|
|
76235
76864
|
if (check.outOfScope.length)
|
|
76236
76865
|
return `move ${check.outOfScope.join(", ")} off ${check.branch} (a follow-up slice), or widen slice ${check.id}'s scope with workit fanout plan, then workit fanout check`;
|
|
76866
|
+
if (check.caseCollisions.length)
|
|
76867
|
+
return `rename to ${plan.trunk}'s spelling on ${check.branch} (git mv), then workit fanout check`;
|
|
76237
76868
|
if (check.trunkConflicts.length)
|
|
76238
76869
|
return `rebase ${check.branch} onto ${plan.trunk} and resolve ${check.trunkConflicts.join(", ")}, then workit fanout check`;
|
|
76239
76870
|
if (check.siblingConflicts.length) {
|
|
@@ -76244,10 +76875,552 @@ function unblockFor(check, plan) {
|
|
|
76244
76875
|
return `unblock ${check.waitsFor.join(", ")} first`;
|
|
76245
76876
|
return check.reasons[0] ?? "workit fanout check";
|
|
76246
76877
|
}
|
|
76247
|
-
var SAFE = (value) => Boolean(value) && !value?.startsWith("-"),
|
|
76878
|
+
var SAFE = (value) => Boolean(value) && !value?.startsWith("-"), isAncestor3 = (cwd, a, b) => gitRun4(cwd, ["merge-base", "--is-ancestor", a, b]).ok, resolves = (cwd, ref) => gitRun4(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]).ok, blank2 = (slice, head) => ({
|
|
76879
|
+
id: slice.id,
|
|
76880
|
+
branch: slice.branch,
|
|
76881
|
+
head,
|
|
76882
|
+
base: slice.base,
|
|
76883
|
+
status: "blocked",
|
|
76884
|
+
changed: 0,
|
|
76885
|
+
outOfScope: [],
|
|
76886
|
+
caseCollisions: [],
|
|
76887
|
+
trunkConflicts: [],
|
|
76888
|
+
siblingConflicts: [],
|
|
76889
|
+
waitsFor: [],
|
|
76890
|
+
verdict: { accepted: false, basis: "missing" },
|
|
76891
|
+
landed: null,
|
|
76892
|
+
reasons: []
|
|
76893
|
+
}), treeFiles = (cwd, rev) => {
|
|
76894
|
+
if (!rev)
|
|
76895
|
+
return null;
|
|
76896
|
+
const run = gitRun4(cwd, ["ls-tree", "-r", "-z", "--name-only", rev]);
|
|
76897
|
+
return run.ok ? new Set(nulList2(run.stdout)) : null;
|
|
76898
|
+
};
|
|
76248
76899
|
var init_fanout_check = __esm(() => {
|
|
76249
76900
|
init_ledger();
|
|
76250
76901
|
init_fanout();
|
|
76902
|
+
init_fanout_landed();
|
|
76903
|
+
});
|
|
76904
|
+
|
|
76905
|
+
// packages/workit-core/src/fanout-status.ts
|
|
76906
|
+
function timeboxMs(text) {
|
|
76907
|
+
if (!text || /^\s*\d+(?:\.\d+)?\s*$/u.test(text))
|
|
76908
|
+
return null;
|
|
76909
|
+
const ms = parseDuration2(text);
|
|
76910
|
+
return ms && ms > 0 ? ms : null;
|
|
76911
|
+
}
|
|
76912
|
+
function activity(cwd, plan, slice, rows) {
|
|
76913
|
+
const head = branchRef(cwd, slice.branch);
|
|
76914
|
+
const commit = head ? isoOf(gitRun4(cwd, ["log", "-1", "--format=%ct", head, "--"]).stdout) : null;
|
|
76915
|
+
const moved = /@\{(\d+)\}/u.exec(gitRun4(cwd, [
|
|
76916
|
+
"log",
|
|
76917
|
+
"-g",
|
|
76918
|
+
"-1",
|
|
76919
|
+
"--date=unix",
|
|
76920
|
+
"--format=%gd",
|
|
76921
|
+
`refs/heads/${slice.branch}`,
|
|
76922
|
+
"--"
|
|
76923
|
+
]).stdout);
|
|
76924
|
+
const reflog = moved ? isoOf(moved[1]) : null;
|
|
76925
|
+
const ledger = rows.filter((row) => row.branch === slice.branch || row.fanout === plan.name && row.slice === slice.id).map((row) => row.at);
|
|
76926
|
+
return {
|
|
76927
|
+
head,
|
|
76928
|
+
commit,
|
|
76929
|
+
last: newest(commit, reflog, ...ledger),
|
|
76930
|
+
worktreeCreated: rows.some((row) => row.type === "fanout.worktree.created" && row.fanout === plan.name && row.slice === slice.id)
|
|
76931
|
+
};
|
|
76932
|
+
}
|
|
76933
|
+
function fanoutStatus(cwd, plan, options) {
|
|
76934
|
+
const now = options.now ?? new Date;
|
|
76935
|
+
const breaker = forgeBreaker(options.forge);
|
|
76936
|
+
const forge = breaker.forge;
|
|
76937
|
+
const trunk = trunkRef(cwd, plan.trunk);
|
|
76938
|
+
if (!trunk)
|
|
76939
|
+
return fanoutFail("unavailable", `trunk ${plan.trunk} does not resolve here`, `git fetch origin ${plan.trunk}`);
|
|
76940
|
+
const ledger = readLedger(cwd);
|
|
76941
|
+
const rows = ledger.ok ? ledger.value.rows : [];
|
|
76942
|
+
const notes = [];
|
|
76943
|
+
if (!ledger.ok)
|
|
76944
|
+
notes.push(`ledger unreadable (${ledger.error}); verdicts read as missing`);
|
|
76945
|
+
if (!forge)
|
|
76946
|
+
notes.push(`forge unavailable${options.forgeError ? ` (${options.forgeError})` : ""}: PR and CI not shown; landed detection uses git only, so a squash merge may be missed`);
|
|
76947
|
+
const tips = new Map(plan.slices.map((slice) => [slice.id, branchRef(cwd, slice.branch)]));
|
|
76948
|
+
const landings = sliceLandings(cwd, plan, trunk, tips, rows, forge);
|
|
76949
|
+
const byId = new Map(plan.slices.map((slice) => [slice.id, slice]));
|
|
76950
|
+
const order = landingOrder(plan.slices);
|
|
76951
|
+
const out = new Map;
|
|
76952
|
+
for (const id of order) {
|
|
76953
|
+
const slice = byId.get(id);
|
|
76954
|
+
const landing = landings.get(id);
|
|
76955
|
+
const seen = activity(cwd, plan, slice, rows);
|
|
76956
|
+
const threshold = options.stuckAfterMs ?? timeboxMs(slice.timebox) ?? DEFAULT_STUCK_MS;
|
|
76957
|
+
const idle = seen.last ? Math.max(0, now.getTime() - Date.parse(seen.last)) : null;
|
|
76958
|
+
const verdict = seen.head ? checkVerdicts(cwd, slice.branch, rows) : null;
|
|
76959
|
+
const row = {
|
|
76960
|
+
id,
|
|
76961
|
+
branch: slice.branch,
|
|
76962
|
+
base: slice.base,
|
|
76963
|
+
worktree: slice.worktree,
|
|
76964
|
+
dependsOn: slice.dependsOn,
|
|
76965
|
+
branchExists: seen.head !== null,
|
|
76966
|
+
head: seen.head,
|
|
76967
|
+
lastCommitAt: seen.commit,
|
|
76968
|
+
lastActivityAt: seen.last,
|
|
76969
|
+
idleMinutes: idle === null ? null : Math.floor(idle / 60000),
|
|
76970
|
+
stuckAfterMinutes: Math.round(threshold / 60000),
|
|
76971
|
+
pr: landing?.pr ? { number: landing.pr.number, url: landing.pr.url, state: landing.pr.state } : null,
|
|
76972
|
+
ci: "unknown",
|
|
76973
|
+
verdict: {
|
|
76974
|
+
accepted: verdict?.accepted.accepted ?? false,
|
|
76975
|
+
basis: verdict?.current.basis ?? "none",
|
|
76976
|
+
review: verdict?.review ?? "unreviewed"
|
|
76977
|
+
},
|
|
76978
|
+
landed: landing?.landed && landing.how ? { how: landing.how, pr: landing.pr?.number ?? null } : null,
|
|
76979
|
+
stuck: false,
|
|
76980
|
+
waitsFor: [],
|
|
76981
|
+
state: "not_started",
|
|
76982
|
+
reasons: [],
|
|
76983
|
+
notes: [
|
|
76984
|
+
...landing?.note ? [landing.note] : [],
|
|
76985
|
+
...landing?.forgeError ? [`forge: ${landing.forgeError}`] : []
|
|
76986
|
+
]
|
|
76987
|
+
};
|
|
76988
|
+
const answered = forge !== null && !landing?.forgeError;
|
|
76989
|
+
if (answered && landing?.pr && landing.pr.state === "open" && !row.landed) {
|
|
76990
|
+
const status = forge.forge.prStatus(landing.pr.number);
|
|
76991
|
+
if (status.ok)
|
|
76992
|
+
row.ci = checksState(gatingChecks(status.data).checks, missingRequired(status.data));
|
|
76993
|
+
else
|
|
76994
|
+
row.notes.push(`forge: ${status.error}`);
|
|
76995
|
+
}
|
|
76996
|
+
row.waitsFor = slice.dependsOn.filter((dep) => !out.get(dep)?.landed);
|
|
76997
|
+
out.set(id, row);
|
|
76998
|
+
if (row.landed) {
|
|
76999
|
+
row.state = "landed";
|
|
77000
|
+
continue;
|
|
77001
|
+
}
|
|
77002
|
+
const started = row.branchExists || seen.worktreeCreated;
|
|
77003
|
+
if (!row.branchExists)
|
|
77004
|
+
row.reasons.push("no branch yet");
|
|
77005
|
+
if (!row.verdict.accepted)
|
|
77006
|
+
row.reasons.push(`verdict ${row.verdict.basis === "none" ? "missing" : row.verdict.basis} on the current head`);
|
|
77007
|
+
if (answered && row.branchExists) {
|
|
77008
|
+
if (!row.pr)
|
|
77009
|
+
row.reasons.push("no PR");
|
|
77010
|
+
else if (row.pr.state !== "open")
|
|
77011
|
+
row.reasons.push(`PR ${row.pr.state}`);
|
|
77012
|
+
else if (row.ci === "failing" || row.ci === "pending")
|
|
77013
|
+
row.reasons.push(`CI ${row.ci}`);
|
|
77014
|
+
}
|
|
77015
|
+
row.stuck = started && !row.verdict.accepted && idle !== null && idle > threshold;
|
|
77016
|
+
if (row.stuck)
|
|
77017
|
+
row.reasons.push(`no activity for ${row.idleMinutes} min`);
|
|
77018
|
+
}
|
|
77019
|
+
const suggested = [];
|
|
77020
|
+
for (const id of order) {
|
|
77021
|
+
const row = out.get(id);
|
|
77022
|
+
if (row.landed || row.reasons.length)
|
|
77023
|
+
continue;
|
|
77024
|
+
if (row.waitsFor.every((dep) => suggested.includes(dep)))
|
|
77025
|
+
suggested.push(id);
|
|
77026
|
+
}
|
|
77027
|
+
const spawnable = [];
|
|
77028
|
+
for (const id of order) {
|
|
77029
|
+
const row = out.get(id);
|
|
77030
|
+
if (row.landed)
|
|
77031
|
+
continue;
|
|
77032
|
+
const parent = plan.slices.find((other) => other.branch === row.base);
|
|
77033
|
+
if (suggested.includes(id))
|
|
77034
|
+
row.state = "ready";
|
|
77035
|
+
else if (row.stuck)
|
|
77036
|
+
row.state = "stuck";
|
|
77037
|
+
else if (row.branchExists)
|
|
77038
|
+
row.state = "active";
|
|
77039
|
+
else if (row.waitsFor.length === 0 || parent && row.waitsFor.length === 1 && row.waitsFor[0] === parent.id && out.get(parent.id)?.verdict.accepted) {
|
|
77040
|
+
const created = rows.some((candidate) => candidate.type === "fanout.worktree.created" && candidate.fanout === plan.name && candidate.slice === id);
|
|
77041
|
+
row.state = created ? "active" : "not_started";
|
|
77042
|
+
if (!created)
|
|
77043
|
+
spawnable.push(id);
|
|
77044
|
+
} else
|
|
77045
|
+
row.state = "waiting";
|
|
77046
|
+
if (row.waitsFor.length && !suggested.includes(id))
|
|
77047
|
+
row.reasons.push(`waits for ${row.waitsFor.join(", ")}`);
|
|
77048
|
+
}
|
|
77049
|
+
const stuck = order.filter((id) => out.get(id)?.stuck);
|
|
77050
|
+
const down = breaker.down();
|
|
77051
|
+
if (down)
|
|
77052
|
+
notes.push(`forge went down during this run (${down}): later slices show no PR or CI, and git alone decided whether they landed`);
|
|
77053
|
+
const online = forge !== null && !down;
|
|
77054
|
+
const noPr = order.filter((id) => {
|
|
77055
|
+
const row = out.get(id);
|
|
77056
|
+
return !row.landed && row.reasons.length === 1 && row.reasons[0] === "no PR";
|
|
77057
|
+
});
|
|
77058
|
+
const next = stuck.length ? `replace ${stuck.join(", ")}: stop the worker, then workit fanout worktree release ${stuck[0]} (records git status first) and respawn it in MODE: resume` : suggested.length ? online ? `land in this order: ${suggested.join(", ")} (workit fanout check first; workit pr merge each)` : `land in this order: ${suggested.join(", ")} once each has a PR (offline: PRs unchecked; open a missing one with workit git push && workit pr create --fill, then workit fanout status online)` : noPr.length ? `open PRs for ${noPr.join(", ")}: workit git push && workit pr create --fill on each branch` : spawnable.length ? `spawn ${spawnable.join(", ")} (workit fanout worktree create <slice> on hosts without native worktrees)` : order.every((id) => out.get(id)?.landed) ? "every slice has landed" : "wait for workers, then workit fanout status again";
|
|
77059
|
+
return {
|
|
77060
|
+
ok: true,
|
|
77061
|
+
data: {
|
|
77062
|
+
name: plan.name,
|
|
77063
|
+
trunk: plan.trunk,
|
|
77064
|
+
trunkRef: trunk,
|
|
77065
|
+
forge: {
|
|
77066
|
+
available: online,
|
|
77067
|
+
error: online ? null : down ?? options.forgeError ?? null
|
|
77068
|
+
},
|
|
77069
|
+
slices: order.map((id) => out.get(id)),
|
|
77070
|
+
landingOrder: suggested,
|
|
77071
|
+
spawnable,
|
|
77072
|
+
stuck,
|
|
77073
|
+
next,
|
|
77074
|
+
notes
|
|
77075
|
+
}
|
|
77076
|
+
};
|
|
77077
|
+
}
|
|
77078
|
+
var DEFAULT_STUCK_MS, isoOf = (seconds) => {
|
|
77079
|
+
const value = Number(seconds.trim());
|
|
77080
|
+
return Number.isFinite(value) && value > 0 ? new Date(value * 1000).toISOString() : null;
|
|
77081
|
+
}, newest = (...values) => values.filter((value) => Boolean(value) && !Number.isNaN(Date.parse(value ?? ""))).reduce((best, value) => best === null || Date.parse(value) > Date.parse(best) ? value : best, null);
|
|
77082
|
+
var init_fanout_status = __esm(() => {
|
|
77083
|
+
init_duration();
|
|
77084
|
+
init_report();
|
|
77085
|
+
init_ledger();
|
|
77086
|
+
init_fanout_check();
|
|
77087
|
+
init_fanout_landed();
|
|
77088
|
+
init_fanout();
|
|
77089
|
+
DEFAULT_STUCK_MS = 30 * 60000;
|
|
77090
|
+
});
|
|
77091
|
+
|
|
77092
|
+
// packages/workit-core/src/fanout-worktree.ts
|
|
77093
|
+
import fs24 from "node:fs";
|
|
77094
|
+
import path44 from "node:path";
|
|
77095
|
+
function rowSample(lines) {
|
|
77096
|
+
const out = [];
|
|
77097
|
+
let bytes = 0;
|
|
77098
|
+
for (const line of lines) {
|
|
77099
|
+
const entry = line.slice(0, 200);
|
|
77100
|
+
bytes += Buffer.byteLength(entry) + 3;
|
|
77101
|
+
if (bytes > DIRTY_ROW_BYTES)
|
|
77102
|
+
break;
|
|
77103
|
+
out.push(entry);
|
|
77104
|
+
}
|
|
77105
|
+
return out;
|
|
77106
|
+
}
|
|
77107
|
+
function listWorktrees(cwd) {
|
|
77108
|
+
const out = [];
|
|
77109
|
+
let current = null;
|
|
77110
|
+
for (const field of gitRun4(cwd, ["worktree", "list", "--porcelain", "-z"]).stdout.split("\x00")) {
|
|
77111
|
+
if (field.startsWith("worktree ")) {
|
|
77112
|
+
current = { path: field.slice(9), head: null, branch: null, locked: false };
|
|
77113
|
+
out.push(current);
|
|
77114
|
+
} else if (!current)
|
|
77115
|
+
continue;
|
|
77116
|
+
else if (field.startsWith("HEAD "))
|
|
77117
|
+
current.head = field.slice(5);
|
|
77118
|
+
else if (field.startsWith("branch "))
|
|
77119
|
+
current.branch = field.slice(7);
|
|
77120
|
+
else if (field === "locked" || field.startsWith("locked "))
|
|
77121
|
+
current.locked = true;
|
|
77122
|
+
}
|
|
77123
|
+
return out;
|
|
77124
|
+
}
|
|
77125
|
+
function createdRow(cwd, plan, slice, target) {
|
|
77126
|
+
const ledger = readLedger(cwd);
|
|
77127
|
+
if (!ledger.ok)
|
|
77128
|
+
return null;
|
|
77129
|
+
const atPath = (row) => row.fanout === plan.name && row.slice === slice.id && typeof row.path === "string" && samePath(row.path, target);
|
|
77130
|
+
const rows = ledger.value.rows;
|
|
77131
|
+
const row = rows.findLast((candidate) => candidate.type === "fanout.worktree.created" && atPath(candidate));
|
|
77132
|
+
if (!row)
|
|
77133
|
+
return null;
|
|
77134
|
+
const removedSince = rows.some((candidate) => candidate.seq > row.seq && candidate.type === "fanout.worktree.released" && candidate.outcome === "removed" && atPath(candidate));
|
|
77135
|
+
if (removedSince)
|
|
77136
|
+
return null;
|
|
77137
|
+
const made = adminDirTime(target);
|
|
77138
|
+
if (made === null || made + CLOCK_SLACK_MS < Date.parse(row.at))
|
|
77139
|
+
return null;
|
|
77140
|
+
return row;
|
|
77141
|
+
}
|
|
77142
|
+
function adminDirTime(worktree) {
|
|
77143
|
+
const dir = gitRun4(worktree, ["rev-parse", "--absolute-git-dir"]);
|
|
77144
|
+
if (!dir.ok)
|
|
77145
|
+
return null;
|
|
77146
|
+
try {
|
|
77147
|
+
const stat = fs24.statSync(dir.stdout.trim());
|
|
77148
|
+
return stat.birthtimeMs > 0 ? stat.birthtimeMs : stat.mtimeMs;
|
|
77149
|
+
} catch {
|
|
77150
|
+
return null;
|
|
77151
|
+
}
|
|
77152
|
+
}
|
|
77153
|
+
function findSlice(plan, id) {
|
|
77154
|
+
const slice = plan.slices.find((candidate) => candidate.id === id);
|
|
77155
|
+
return slice ? { ok: true, data: slice } : fanoutFail("invalid_input", `${id}: not a slice of fanout ${plan.name}`, `slices: ${plan.slices.map((candidate) => candidate.id).join(", ")}`);
|
|
77156
|
+
}
|
|
77157
|
+
function excludeScratch(cwd) {
|
|
77158
|
+
const common = gitCommonDir(cwd);
|
|
77159
|
+
if (!common)
|
|
77160
|
+
return;
|
|
77161
|
+
const file = path44.join(common, "info", "exclude");
|
|
77162
|
+
let text = "";
|
|
77163
|
+
try {
|
|
77164
|
+
text = fs24.readFileSync(file, "utf8");
|
|
77165
|
+
} catch {
|
|
77166
|
+
text = "";
|
|
77167
|
+
}
|
|
77168
|
+
if (text.split(/\r?\n/u).includes(EXCLUDE_LINE))
|
|
77169
|
+
return;
|
|
77170
|
+
fs24.mkdirSync(path44.dirname(file), { recursive: true });
|
|
77171
|
+
fs24.appendFileSync(file, `${text && !text.endsWith(`
|
|
77172
|
+
`) ? `
|
|
77173
|
+
` : ""}# workit fanout: per-worker scratch dirs
|
|
77174
|
+
${EXCLUDE_LINE}
|
|
77175
|
+
`);
|
|
77176
|
+
}
|
|
77177
|
+
function ensureScratch(worktree) {
|
|
77178
|
+
const scratch = path44.join(worktree, SCRATCH_DIR);
|
|
77179
|
+
fs24.mkdirSync(scratch, { recursive: true });
|
|
77180
|
+
return scratch;
|
|
77181
|
+
}
|
|
77182
|
+
function createSliceWorktree(cwd, plan, input) {
|
|
77183
|
+
const found = findSlice(plan, input.slice);
|
|
77184
|
+
if (!found.ok)
|
|
77185
|
+
return found;
|
|
77186
|
+
const slice = found.data;
|
|
77187
|
+
if (!slice.worktree)
|
|
77188
|
+
return fanoutFail("invalid_input", `slice ${slice.id} has no worktree path`, "re-plan it with workit fanout plan");
|
|
77189
|
+
const target = slicePath(cwd, slice);
|
|
77190
|
+
const worktrees = listWorktrees(cwd);
|
|
77191
|
+
const localRef = `refs/heads/${slice.branch}`;
|
|
77192
|
+
const existing = worktrees.find((entry) => samePath(entry.path, target));
|
|
77193
|
+
const base = {
|
|
77194
|
+
fanout: plan.name,
|
|
77195
|
+
slice: slice.id,
|
|
77196
|
+
branch: slice.branch,
|
|
77197
|
+
base: slice.base,
|
|
77198
|
+
path: target
|
|
77199
|
+
};
|
|
77200
|
+
if (existing) {
|
|
77201
|
+
if (existing.branch !== null && existing.branch !== localRef)
|
|
77202
|
+
return fanoutFail("blocked", `${target} is a worktree on ${existing.branch.replace(/^refs\/heads\//u, "")}, not ${slice.branch}`, `release it first, or set slice ${slice.id}'s worktree to another path`);
|
|
77203
|
+
if (!createdRow(cwd, plan, slice, target))
|
|
77204
|
+
return fanoutFail("blocked", `${target} is a worktree that workit fanout worktree create did not make for slice ${slice.id}`, `remove it yourself (git worktree remove ${target}) once you know what it holds`);
|
|
77205
|
+
excludeScratch(cwd);
|
|
77206
|
+
return {
|
|
77207
|
+
ok: true,
|
|
77208
|
+
data: {
|
|
77209
|
+
...base,
|
|
77210
|
+
scratch: ensureScratch(target),
|
|
77211
|
+
head: existing.head ?? "",
|
|
77212
|
+
mode: resolveRef(cwd, localRef) ? "resume" : "new",
|
|
77213
|
+
created: false,
|
|
77214
|
+
notes: ["the worktree already exists; nothing was changed"]
|
|
77215
|
+
}
|
|
77216
|
+
};
|
|
77217
|
+
}
|
|
77218
|
+
const elsewhere = worktrees.find((entry) => entry.branch === localRef);
|
|
77219
|
+
if (elsewhere)
|
|
77220
|
+
return fanoutFail("blocked", `${slice.branch} is checked out at ${elsewhere.path}: never two live workers on one branch`, `stop that worker, then workit fanout worktree release ${slice.id} (or git worktree remove ${elsewhere.path})`);
|
|
77221
|
+
let dirExisted = false;
|
|
77222
|
+
try {
|
|
77223
|
+
const stat = fs24.statSync(target);
|
|
77224
|
+
if (!stat.isDirectory() || fs24.readdirSync(target).length > 0)
|
|
77225
|
+
return fanoutFail("blocked", `${target} exists and is ${stat.isDirectory() ? "not empty" : "not a directory"} (not a worktree of this repository)`, `move it away, or set slice ${slice.id}'s worktree to another path`);
|
|
77226
|
+
dirExisted = true;
|
|
77227
|
+
} catch (error) {
|
|
77228
|
+
if (error.code !== "ENOENT")
|
|
77229
|
+
return fanoutFail("failed", `cannot read ${target}: ${error.message}`);
|
|
77230
|
+
}
|
|
77231
|
+
const notes = [];
|
|
77232
|
+
let mode = "resume";
|
|
77233
|
+
if (resolveRef(cwd, localRef)) {
|
|
77234
|
+
const added = gitRun4(cwd, ["worktree", "add", target, slice.branch], SLOW);
|
|
77235
|
+
if (!added.ok)
|
|
77236
|
+
return worktreeFailed(added.stderr);
|
|
77237
|
+
} else {
|
|
77238
|
+
const remote = pushRemoteName(cwd) ?? "origin";
|
|
77239
|
+
const remoteRef = `refs/remotes/${remote}/${slice.branch}`;
|
|
77240
|
+
if (resolveRef(cwd, remoteRef)) {
|
|
77241
|
+
const added = gitRun4(cwd, ["worktree", "add", "-b", slice.branch, target, remoteRef], SLOW);
|
|
77242
|
+
if (!added.ok)
|
|
77243
|
+
return worktreeFailed(added.stderr);
|
|
77244
|
+
notes.push(`resumed ${slice.branch} from ${remote}/${slice.branch}`);
|
|
77245
|
+
} else {
|
|
77246
|
+
mode = "new";
|
|
77247
|
+
const parent = plan.slices.find((other) => other.branch === slice.base);
|
|
77248
|
+
const from = slice.base === plan.trunk ? trunkRef(cwd, plan.trunk) : parent ? branchRef(cwd, parent.branch) : branchRef(cwd, slice.base);
|
|
77249
|
+
if (!from)
|
|
77250
|
+
return fanoutFail("not_found", `base ${slice.base} of slice ${slice.id} does not resolve here`, parent ? `start slice ${parent.id} first (its branch ${parent.branch} is the base)` : `git fetch origin ${slice.base}`);
|
|
77251
|
+
const added = gitRun4(cwd, ["worktree", "add", "--detach", target, from], SLOW);
|
|
77252
|
+
if (!added.ok)
|
|
77253
|
+
return worktreeFailed(added.stderr);
|
|
77254
|
+
const branched = gitBranch(target, { name: slice.branch, base: slice.base });
|
|
77255
|
+
if (!branched.ok) {
|
|
77256
|
+
gitRun4(cwd, ["worktree", "remove", target], SLOW);
|
|
77257
|
+
return fanoutFail(branched.code, `workit git branch ${slice.branch} refused: ${branched.error}`, branched.unblock ?? `fix the branch name in the plan, then workit fanout worktree create ${slice.id}`);
|
|
77258
|
+
}
|
|
77259
|
+
notes.push(...branched.data.notes);
|
|
77260
|
+
}
|
|
77261
|
+
}
|
|
77262
|
+
excludeScratch(cwd);
|
|
77263
|
+
const scratch = ensureScratch(target);
|
|
77264
|
+
const head = gitRun4(target, ["rev-parse", "HEAD"]).stdout.trim();
|
|
77265
|
+
const baseSha = mode === "new" ? head : null;
|
|
77266
|
+
appendObserved(cwd, {
|
|
77267
|
+
type: "fanout.worktree.created",
|
|
77268
|
+
actor: input.actor,
|
|
77269
|
+
fanout: plan.name,
|
|
77270
|
+
slice: slice.id,
|
|
77271
|
+
branch: slice.branch,
|
|
77272
|
+
path: target,
|
|
77273
|
+
scratch,
|
|
77274
|
+
mode,
|
|
77275
|
+
head,
|
|
77276
|
+
dirExisted,
|
|
77277
|
+
planCreatedAt: plan.createdAt,
|
|
77278
|
+
...baseSha ? { baseSha } : {}
|
|
77279
|
+
});
|
|
77280
|
+
return { ok: true, data: { ...base, scratch, head, mode, created: true, notes } };
|
|
77281
|
+
}
|
|
77282
|
+
function releaseSliceWorktree(cwd, plan, input) {
|
|
77283
|
+
const found = findSlice(plan, input.slice);
|
|
77284
|
+
if (!found.ok)
|
|
77285
|
+
return found;
|
|
77286
|
+
const slice = found.data;
|
|
77287
|
+
const target = slicePath(cwd, slice);
|
|
77288
|
+
const worktrees = listWorktrees(cwd);
|
|
77289
|
+
const index = worktrees.findIndex((entry) => samePath(entry.path, target));
|
|
77290
|
+
const out = {
|
|
77291
|
+
fanout: plan.name,
|
|
77292
|
+
slice: slice.id,
|
|
77293
|
+
branch: slice.branch,
|
|
77294
|
+
path: target,
|
|
77295
|
+
head: null,
|
|
77296
|
+
dirty: [],
|
|
77297
|
+
forced: input.force,
|
|
77298
|
+
removed: false,
|
|
77299
|
+
unpushed: null,
|
|
77300
|
+
notes: []
|
|
77301
|
+
};
|
|
77302
|
+
if (index < 0) {
|
|
77303
|
+
if (fs24.existsSync(target))
|
|
77304
|
+
return fanoutFail("blocked", `${target} is not a worktree of this repository; nothing was removed`, "remove it yourself once you know what it holds");
|
|
77305
|
+
out.notes.push(`no worktree at ${target}; nothing to release`);
|
|
77306
|
+
return { ok: true, data: out };
|
|
77307
|
+
}
|
|
77308
|
+
const entry = worktrees[index];
|
|
77309
|
+
if (index === 0)
|
|
77310
|
+
return fanoutFail("blocked", `${target} is the main checkout; it is never released`);
|
|
77311
|
+
if (inside2(cwd, entry.path))
|
|
77312
|
+
return fanoutFail("blocked", `this command runs inside ${entry.path}`, `run it from the main checkout: workit --cwd <main checkout> fanout worktree release ${slice.id}`);
|
|
77313
|
+
if (entry.branch !== null && entry.branch !== `refs/heads/${slice.branch}`)
|
|
77314
|
+
return fanoutFail("blocked", `${entry.path} is on ${entry.branch.replace(/^refs\/heads\//u, "")}, not slice ${slice.id}'s ${slice.branch}; nothing was removed`);
|
|
77315
|
+
const made = createdRow(cwd, plan, slice, entry.path);
|
|
77316
|
+
if (!made)
|
|
77317
|
+
return fanoutFail("blocked", `${entry.path} was not made by workit fanout worktree create for slice ${slice.id} (no ledger row); nothing was removed`, `remove it yourself (git worktree remove ${entry.path}) once you know what it holds`);
|
|
77318
|
+
out.head = entry.head;
|
|
77319
|
+
const status = gitRun4(entry.path, [
|
|
77320
|
+
"status",
|
|
77321
|
+
"--porcelain=v1",
|
|
77322
|
+
"-z",
|
|
77323
|
+
"--untracked-files=all",
|
|
77324
|
+
"--ignore-submodules=none"
|
|
77325
|
+
]);
|
|
77326
|
+
if (!status.ok)
|
|
77327
|
+
return fanoutFail("failed", `git status in ${entry.path} failed: ${status.stderr.trim().split(`
|
|
77328
|
+
`)[0]}`, "nothing was removed; inspect the worktree by hand");
|
|
77329
|
+
const entries = porcelainEntries(status.stdout);
|
|
77330
|
+
const inScratch = (line) => line.slice(3).startsWith(`${SCRATCH_DIR}/`);
|
|
77331
|
+
out.dirty = entries.filter((line) => !inScratch(line));
|
|
77332
|
+
out.unpushed = unpushedCount(cwd, slice.branch);
|
|
77333
|
+
const refused = out.dirty.length > 0 && !input.force;
|
|
77334
|
+
const recorded = appendObserved(cwd, {
|
|
77335
|
+
type: "fanout.worktree.released",
|
|
77336
|
+
actor: input.actor,
|
|
77337
|
+
fanout: plan.name,
|
|
77338
|
+
slice: slice.id,
|
|
77339
|
+
branch: slice.branch,
|
|
77340
|
+
path: entry.path,
|
|
77341
|
+
head: entry.head,
|
|
77342
|
+
uncommittedCount: out.dirty.length,
|
|
77343
|
+
uncommitted: rowSample(out.dirty),
|
|
77344
|
+
forced: input.force,
|
|
77345
|
+
outcome: refused ? "refused" : "removed"
|
|
77346
|
+
});
|
|
77347
|
+
if (!recorded.ok)
|
|
77348
|
+
return fanoutFail("unavailable", `cannot record the worktree state in the ledger (${recorded.error}); nothing was removed`);
|
|
77349
|
+
if (refused)
|
|
77350
|
+
return fanoutFail("blocked", `${entry.path} has ${out.dirty.length} uncommitted change${out.dirty.length === 1 ? "" : "s"} (${out.dirty.slice(0, 3).join(", ")}${out.dirty.length > 3 ? ", …" : ""}); recorded in the ledger, nothing removed`, `commit them on ${slice.branch} (workit git commit), or pass --force to drop them`, { ...out });
|
|
77351
|
+
const args = ["worktree", "remove", ...input.force ? ["--force"] : [], entry.path];
|
|
77352
|
+
let removed = gitRun4(cwd, args, SLOW);
|
|
77353
|
+
if (!removed.ok && entries.some(inScratch)) {
|
|
77354
|
+
removeScratch(entry.path);
|
|
77355
|
+
removed = gitRun4(cwd, args, SLOW);
|
|
77356
|
+
}
|
|
77357
|
+
if (!removed.ok)
|
|
77358
|
+
return fanoutFail("failed", `git worktree remove failed: ${removed.stderr.trim().split(`
|
|
77359
|
+
`)[0]}`, entry.locked ? `git worktree unlock ${entry.path}, then release again` : undefined, { ...out });
|
|
77360
|
+
out.removed = true;
|
|
77361
|
+
if (made.dirExisted === true)
|
|
77362
|
+
fs24.mkdirSync(entry.path, { recursive: true });
|
|
77363
|
+
if (out.unpushed)
|
|
77364
|
+
out.notes.push(`${slice.branch} is kept with ${out.unpushed} commit(s) not on the remote`);
|
|
77365
|
+
return { ok: true, data: out };
|
|
77366
|
+
}
|
|
77367
|
+
function porcelainEntries(stdout) {
|
|
77368
|
+
const fields = nulList2(stdout);
|
|
77369
|
+
const out = [];
|
|
77370
|
+
for (let index = 0;index < fields.length; index += 1) {
|
|
77371
|
+
const entry = fields[index];
|
|
77372
|
+
out.push(entry);
|
|
77373
|
+
if (entry[0] === "R" || entry[0] === "C")
|
|
77374
|
+
index += 1;
|
|
77375
|
+
}
|
|
77376
|
+
return out;
|
|
77377
|
+
}
|
|
77378
|
+
function unpushedCount(cwd, branch) {
|
|
77379
|
+
const remote = pushRemoteName(cwd) ?? "origin";
|
|
77380
|
+
const local = resolveRef(cwd, `refs/heads/${branch}`);
|
|
77381
|
+
if (!local)
|
|
77382
|
+
return null;
|
|
77383
|
+
const tracking = resolveRef(cwd, `refs/remotes/${remote}/${branch}`);
|
|
77384
|
+
if (!tracking)
|
|
77385
|
+
return null;
|
|
77386
|
+
const count = gitRun4(cwd, ["rev-list", "--count", `${tracking}..${local}`]);
|
|
77387
|
+
return count.ok ? Number(count.stdout.trim()) : null;
|
|
77388
|
+
}
|
|
77389
|
+
function removeScratch(worktree) {
|
|
77390
|
+
const scratch = path44.join(worktree, SCRATCH_DIR);
|
|
77391
|
+
let stat;
|
|
77392
|
+
try {
|
|
77393
|
+
stat = fs24.lstatSync(scratch);
|
|
77394
|
+
} catch {
|
|
77395
|
+
return;
|
|
77396
|
+
}
|
|
77397
|
+
if (stat.isSymbolicLink() || !stat.isDirectory())
|
|
77398
|
+
fs24.unlinkSync(scratch);
|
|
77399
|
+
else
|
|
77400
|
+
fs24.rmSync(scratch, { recursive: true, force: true, maxRetries: 2 });
|
|
77401
|
+
}
|
|
77402
|
+
var SLOW, SCRATCH_DIR = ".workit-scratch", EXCLUDE_LINE, DIRTY_ROW_BYTES = 2000, canonical2 = (target) => {
|
|
77403
|
+
try {
|
|
77404
|
+
return fs24.realpathSync.native(target);
|
|
77405
|
+
} catch {
|
|
77406
|
+
return path44.resolve(target);
|
|
77407
|
+
}
|
|
77408
|
+
}, samePath = (a, b) => {
|
|
77409
|
+
const left = canonical2(a);
|
|
77410
|
+
const right = canonical2(b);
|
|
77411
|
+
return process.platform === "win32" || process.platform === "darwin" ? left.toLowerCase() === right.toLowerCase() : left === right;
|
|
77412
|
+
}, inside2 = (child, parent) => {
|
|
77413
|
+
const relative = path44.relative(canonical2(parent), canonical2(child));
|
|
77414
|
+
return relative === "" || !relative.startsWith("..") && !path44.isAbsolute(relative);
|
|
77415
|
+
}, slicePath = (cwd, slice) => path44.isAbsolute(slice.worktree) ? slice.worktree : path44.resolve(mainCheckout(cwd), slice.worktree), CLOCK_SLACK_MS = 2000, worktreeFailed = (stderr) => fanoutFail("failed", `git worktree add failed: ${stderr.trim().split(`
|
|
77416
|
+
`)[0] || "unknown error"}`);
|
|
77417
|
+
var init_fanout_worktree = __esm(() => {
|
|
77418
|
+
init_ops();
|
|
77419
|
+
init_rev();
|
|
77420
|
+
init_ledger();
|
|
77421
|
+
init_fanout();
|
|
77422
|
+
SLOW = GIT_TIMEOUTS.worktree;
|
|
77423
|
+
EXCLUDE_LINE = `/${SCRATCH_DIR}/`;
|
|
76251
77424
|
});
|
|
76252
77425
|
|
|
76253
77426
|
// packages/workit-cli/src/verbs/fanout.ts
|
|
@@ -76255,8 +77428,14 @@ var exports_fanout = {};
|
|
|
76255
77428
|
__export(exports_fanout, {
|
|
76256
77429
|
run: () => run15
|
|
76257
77430
|
});
|
|
76258
|
-
import
|
|
76259
|
-
import
|
|
77431
|
+
import fs25 from "node:fs";
|
|
77432
|
+
import path45 from "node:path";
|
|
77433
|
+
function forgeFor(io, offline) {
|
|
77434
|
+
if (offline)
|
|
77435
|
+
return { forge: null, error: "--offline" };
|
|
77436
|
+
const connected = connect(io, null);
|
|
77437
|
+
return connected.ok ? { forge: connected.data, error: null } : { forge: null, error: connected.error };
|
|
77438
|
+
}
|
|
76260
77439
|
function renderBlockedPlan(io, result) {
|
|
76261
77440
|
if (io.json || result.code !== "blocked" && result.code !== "invalid_input")
|
|
76262
77441
|
return;
|
|
@@ -76264,6 +77443,11 @@ function renderBlockedPlan(io, result) {
|
|
|
76264
77443
|
if (Array.isArray(problems))
|
|
76265
77444
|
for (const problem of problems)
|
|
76266
77445
|
io.stderr(` - ${problem}
|
|
77446
|
+
`);
|
|
77447
|
+
const clashes = result.data?.caseCollisions;
|
|
77448
|
+
if (Array.isArray(clashes))
|
|
77449
|
+
for (const clash of clashes)
|
|
77450
|
+
io.stderr(` - ${clash.slice}: ${clash.path} ~ trunk's ${clash.trunk}
|
|
76267
77451
|
`);
|
|
76268
77452
|
const conflicts = result.data?.conflicts;
|
|
76269
77453
|
if (Array.isArray(conflicts))
|
|
@@ -76301,10 +77485,10 @@ function plan2(argv, io) {
|
|
|
76301
77485
|
return usage3(io, "pass --trunk or --track, not both (--track picks the trunk)", PLAN_USAGE2);
|
|
76302
77486
|
if (flags.positionals.length !== 1)
|
|
76303
77487
|
return usage3(io, flags.positionals.length ? `unexpected argument ${flags.positionals[1]}` : "missing <plan.json>", PLAN_USAGE2);
|
|
76304
|
-
const file =
|
|
77488
|
+
const file = path45.resolve(io.cwd, flags.positionals[0]);
|
|
76305
77489
|
let raw;
|
|
76306
77490
|
try {
|
|
76307
|
-
raw = JSON.parse(
|
|
77491
|
+
raw = JSON.parse(fs25.readFileSync(file, "utf8"));
|
|
76308
77492
|
} catch (error) {
|
|
76309
77493
|
return usage3(io, `cannot read ${file} as JSON: ${error.message}`, PLAN_USAGE2);
|
|
76310
77494
|
}
|
|
@@ -76333,7 +77517,7 @@ function plan2(argv, io) {
|
|
|
76333
77517
|
]);
|
|
76334
77518
|
}
|
|
76335
77519
|
function check2(argv, io) {
|
|
76336
|
-
const flags = parseFlags(argv, { name: "value", base: "value" });
|
|
77520
|
+
const flags = parseFlags(argv, { name: "value", base: "value", offline: "boolean" });
|
|
76337
77521
|
if (typeof flags === "string")
|
|
76338
77522
|
return usage3(io, flags, CHECK_USAGE);
|
|
76339
77523
|
const selected = selectFanout(io.cwd, flags.values.name ?? null, currentBranch(io.cwd));
|
|
@@ -76341,7 +77525,8 @@ function check2(argv, io) {
|
|
|
76341
77525
|
return failed(io, selected);
|
|
76342
77526
|
const result = checkFanout(io.cwd, selected.data, {
|
|
76343
77527
|
only: flags.positionals,
|
|
76344
|
-
trunkRef: flags.values.base ?? null
|
|
77528
|
+
trunkRef: flags.values.base ?? null,
|
|
77529
|
+
forge: forgeFor(io, flags.booleans.has("offline")).forge
|
|
76345
77530
|
});
|
|
76346
77531
|
if (!result.ok)
|
|
76347
77532
|
return failed(io, result);
|
|
@@ -76357,17 +77542,92 @@ function check2(argv, io) {
|
|
|
76357
77542
|
unblock: result.data.next
|
|
76358
77543
|
}));
|
|
76359
77544
|
}
|
|
77545
|
+
function status3(argv, io) {
|
|
77546
|
+
const flags = parseFlags(argv, { name: "value", "stuck-after": "value", offline: "boolean" });
|
|
77547
|
+
if (typeof flags === "string")
|
|
77548
|
+
return usage3(io, flags, STATUS_USAGE3);
|
|
77549
|
+
if (flags.positionals.length)
|
|
77550
|
+
return usage3(io, `unexpected argument ${flags.positionals[0]}`, STATUS_USAGE3);
|
|
77551
|
+
const raw = flags.values["stuck-after"];
|
|
77552
|
+
const stuckAfterMs = raw === undefined ? null : parseDuration2(raw);
|
|
77553
|
+
if (stuckAfterMs === 0 || raw !== undefined && stuckAfterMs === null)
|
|
77554
|
+
return usage3(io, "--stuck-after takes a duration such as 30m, 45m or 2h", STATUS_USAGE3);
|
|
77555
|
+
const selected = selectFanout(io.cwd, flags.values.name ?? null, currentBranch(io.cwd));
|
|
77556
|
+
if (!selected.ok)
|
|
77557
|
+
return failed(io, selected);
|
|
77558
|
+
const forge = forgeFor(io, flags.booleans.has("offline"));
|
|
77559
|
+
const result = fanoutStatus(io.cwd, selected.data, {
|
|
77560
|
+
forge: forge.forge,
|
|
77561
|
+
forgeError: forge.error,
|
|
77562
|
+
stuckAfterMs
|
|
77563
|
+
});
|
|
77564
|
+
if (!result.ok)
|
|
77565
|
+
return failed(io, result);
|
|
77566
|
+
return emit(io, ok(result.data), renderStatus2);
|
|
77567
|
+
}
|
|
77568
|
+
function worktree(argv, io) {
|
|
77569
|
+
const [action, ...rest] = argv;
|
|
77570
|
+
const flags = parseFlags(rest, { name: "value", force: "boolean" });
|
|
77571
|
+
if (typeof flags === "string")
|
|
77572
|
+
return usage3(io, flags, WORKTREE_USAGE);
|
|
77573
|
+
if (action !== "create" && action !== "release")
|
|
77574
|
+
return usage3(io, action ? `unknown worktree action "${action}"` : "missing create or release", WORKTREE_USAGE);
|
|
77575
|
+
if (flags.positionals.length !== 1)
|
|
77576
|
+
return usage3(io, flags.positionals.length ? `unexpected argument ${flags.positionals[1]}` : "missing <slice>", WORKTREE_USAGE);
|
|
77577
|
+
if (action === "create" && flags.booleans.has("force"))
|
|
77578
|
+
return usage3(io, "--force applies to release only", WORKTREE_USAGE);
|
|
77579
|
+
const selected = selectFanout(io.cwd, flags.values.name ?? null, currentBranch(io.cwd));
|
|
77580
|
+
if (!selected.ok)
|
|
77581
|
+
return failed(io, selected);
|
|
77582
|
+
const actor = actorFromEnv(io.env);
|
|
77583
|
+
if (action === "create") {
|
|
77584
|
+
const result = createSliceWorktree(io.cwd, selected.data, {
|
|
77585
|
+
slice: flags.positionals[0],
|
|
77586
|
+
actor
|
|
77587
|
+
});
|
|
77588
|
+
if (!result.ok)
|
|
77589
|
+
return failed(io, result);
|
|
77590
|
+
return emit(io, ok(result.data), (data) => [
|
|
77591
|
+
`${data.created ? "created" : "kept"} worktree for slice ${data.slice} (${data.mode}): ${data.path}`,
|
|
77592
|
+
` branch ${data.branch} <- ${data.base} at ${short5(data.head)}`,
|
|
77593
|
+
` scratch ${data.scratch} (put it in the worker brief: temp files go here)`,
|
|
77594
|
+
...data.notes.map((note) => `note: ${note}`)
|
|
77595
|
+
]);
|
|
77596
|
+
}
|
|
77597
|
+
const result = releaseSliceWorktree(io.cwd, selected.data, {
|
|
77598
|
+
slice: flags.positionals[0],
|
|
77599
|
+
force: flags.booleans.has("force"),
|
|
77600
|
+
actor
|
|
77601
|
+
});
|
|
77602
|
+
if (!result.ok) {
|
|
77603
|
+
const code = failed(io, result);
|
|
77604
|
+
const dirty = result.data?.dirty;
|
|
77605
|
+
if (!io.json && Array.isArray(dirty))
|
|
77606
|
+
for (const line of dirty)
|
|
77607
|
+
io.stderr(` ${line}
|
|
77608
|
+
`);
|
|
77609
|
+
return code;
|
|
77610
|
+
}
|
|
77611
|
+
return emit(io, ok(result.data), (data) => [
|
|
77612
|
+
data.removed ? `released slice ${data.slice}: removed ${data.path}${data.dirty.length ? ` (dropped ${data.dirty.length} uncommitted change${data.dirty.length === 1 ? "" : "s"}, recorded in the ledger)` : ""}; branch ${data.branch} kept` : `slice ${data.slice}: nothing released`,
|
|
77613
|
+
...data.notes.map((note) => `note: ${note}`)
|
|
77614
|
+
]);
|
|
77615
|
+
}
|
|
76360
77616
|
async function run15(argv, io) {
|
|
76361
77617
|
const [sub, ...rest] = argv.filter((arg) => arg !== "--json");
|
|
76362
77618
|
if (sub === "plan")
|
|
76363
77619
|
return plan2(rest, io);
|
|
76364
77620
|
if (sub === "check")
|
|
76365
77621
|
return check2(rest, io);
|
|
77622
|
+
if (sub === "status")
|
|
77623
|
+
return status3(rest, io);
|
|
77624
|
+
if (sub === "worktree")
|
|
77625
|
+
return worktree(rest, io);
|
|
76366
77626
|
return usage3(io, sub && !sub.startsWith("-") ? `unknown fanout subcommand "${sub}"` : "missing fanout subcommand", USAGE8);
|
|
76367
77627
|
}
|
|
76368
|
-
var PLAN_USAGE2 = "workit fanout plan <plan.json> [--name <n>] [--trunk <b> | --track <t>] [--json]", CHECK_USAGE = "workit fanout check [<slice>…] [--name <n>] [--base <ref>] [--json]", USAGE8 = "workit fanout plan|check ... (workit help fanout)", failed = (io, result) => emit(io, fail(result.code, result.error, { unblock: result.unblock, data: result.data ?? {} })), renderCheck = (data) => [
|
|
77628
|
+
var PLAN_USAGE2 = "workit fanout plan <plan.json> [--name <n>] [--trunk <b> | --track <t>] [--json]", CHECK_USAGE = "workit fanout check [<slice>…] [--name <n>] [--base <ref>] [--offline] [--json]", STATUS_USAGE3 = "workit fanout status [--name <n>] [--stuck-after 30m] [--offline] [--json]", WORKTREE_USAGE = "workit fanout worktree create <slice> [--name <n>] | worktree release <slice> [--name <n>] [--force] [--json]", USAGE8 = "workit fanout plan|check|status|worktree ... (workit help fanout)", failed = (io, result) => emit(io, fail(result.code, result.error, { unblock: result.unblock, data: result.data ?? {} })), renderCheck = (data) => [
|
|
76369
77629
|
`fanout ${data.name} against ${data.trunkRef}: ${data.ready ? "ready to land" : "blocked"}`,
|
|
76370
|
-
...data.slices.map((slice) => ` ${slice.status === "ready" ? "ok " : "x "}${slice.id} (${slice.branch}) ${slice.changed} file${slice.changed === 1 ? "" : "s"} · verdict ${slice.verdict.accepted ? "accepted" : slice.verdict.basis}${slice.reasons.length ? `
|
|
77630
|
+
...data.slices.map((slice) => slice.status === "landed" ? ` -- ${slice.id} (${slice.branch}) landed (${slice.landed?.how}${slice.landed?.pr ? ` #${slice.landed.pr}` : ""})` : ` ${slice.status === "ready" ? "ok " : "x "}${slice.id} (${slice.branch}) ${slice.changed} file${slice.changed === 1 ? "" : "s"} · verdict ${slice.verdict.accepted ? "accepted" : slice.verdict.basis}${slice.reasons.length ? `
|
|
76371
77631
|
${slice.reasons.join(`
|
|
76372
77632
|
`)}` : ""}`),
|
|
76373
77633
|
...data.conflicts.map((conflict) => ` conflict ${conflict.first} x ${conflict.second}: ${conflict.paths.join(", ")}
|
|
@@ -76375,9 +77635,37 @@ var PLAN_USAGE2 = "workit fanout plan <plan.json> [--name <n>] [--trunk <b> | --
|
|
|
76375
77635
|
`landing order: ${data.landingOrder.join(", ")}`,
|
|
76376
77636
|
`next: ${data.next}`,
|
|
76377
77637
|
...data.notes.map((note) => `note: ${note}`)
|
|
76378
|
-
]
|
|
77638
|
+
], short5 = (sha) => sha ? sha.slice(0, 7) : "-", renderStatus2 = (data) => {
|
|
77639
|
+
const counts = new Map;
|
|
77640
|
+
for (const slice of data.slices)
|
|
77641
|
+
counts.set(slice.state, (counts.get(slice.state) ?? 0) + 1);
|
|
77642
|
+
return [
|
|
77643
|
+
`fanout ${data.name} on ${data.trunk}: ${[...counts].map(([state, count]) => `${count} ${state.replace("_", " ")}`).join(", ")} · forge ${data.forge.available ? "on" : "off"}`,
|
|
77644
|
+
...data.slices.map((slice) => {
|
|
77645
|
+
const parts = [
|
|
77646
|
+
slice.branchExists ? short5(slice.head) : "no branch",
|
|
77647
|
+
slice.lastCommitAt && slice.idleMinutes !== null ? `idle ${slice.idleMinutes} min` : null,
|
|
77648
|
+
slice.pr ? `PR #${slice.pr.number} ${slice.pr.state}` : null,
|
|
77649
|
+
slice.ci === "unknown" ? null : `CI ${slice.ci}`,
|
|
77650
|
+
`verdict ${slice.verdict.accepted ? "accepted" : slice.verdict.basis === "none" ? "missing" : slice.verdict.basis}`
|
|
77651
|
+
].filter(Boolean);
|
|
77652
|
+
const label = slice.state === "stuck" ? "STUCK" : slice.state.replace("_", " ");
|
|
77653
|
+
const detail = slice.landed ? `landed (${slice.landed.how}${slice.landed.pr ? ` #${slice.landed.pr}` : ""})` : parts.join(" · ");
|
|
77654
|
+
const why = slice.state === "landed" ? [] : [...slice.reasons, ...slice.notes];
|
|
77655
|
+
return ` ${label.padEnd(11)} ${slice.id} (${slice.branch}) ${detail}${why.length ? `
|
|
77656
|
+
${why.join(`
|
|
77657
|
+
`)}` : ""}`;
|
|
77658
|
+
}),
|
|
77659
|
+
`landing order: ${data.landingOrder.length ? data.landingOrder.join(", ") : "(none ready)"}`,
|
|
77660
|
+
...data.spawnable.length ? [`spawnable: ${data.spawnable.join(", ")}`] : [],
|
|
77661
|
+
`next: ${data.next}`,
|
|
77662
|
+
...data.notes.map((note) => `note: ${note}`)
|
|
77663
|
+
];
|
|
77664
|
+
};
|
|
76379
77665
|
var init_fanout2 = __esm(() => {
|
|
76380
77666
|
init_fanout_check();
|
|
77667
|
+
init_fanout_status();
|
|
77668
|
+
init_fanout_worktree();
|
|
76381
77669
|
init_fanout();
|
|
76382
77670
|
init_rev();
|
|
76383
77671
|
init_ledger();
|
|
@@ -91532,7 +92820,7 @@ var init_ast = __esm(() => {
|
|
|
91532
92820
|
|
|
91533
92821
|
// packages/workit-cli/src/test-audit/rules.ts
|
|
91534
92822
|
import { builtinModules } from "node:module";
|
|
91535
|
-
import
|
|
92823
|
+
import path46 from "node:path";
|
|
91536
92824
|
function testCallee(call) {
|
|
91537
92825
|
let callee = call.callee;
|
|
91538
92826
|
if (callee.type === "CallExpression")
|
|
@@ -92025,12 +93313,12 @@ function checkConstantsOnly(ctx, emit) {
|
|
|
92025
93313
|
}
|
|
92026
93314
|
function checkOverMocking(ctx, emit) {
|
|
92027
93315
|
const { program } = ctx.parsed;
|
|
92028
|
-
const unit =
|
|
93316
|
+
const unit = path46.basename(ctx.file).replace(/\.(?:[cm]?[jt]sx?)$/, "").replace(/\.(?:test|spec)$/, "");
|
|
92029
93317
|
const mocked = find2(program, (node) => node.type === "CallExpression" && MODULE_MOCK.test(calleeName(node.callee) ?? "") && node.arguments[0]?.type === "StringLiteral");
|
|
92030
93318
|
const internal = mocked.filter((call) => /^(?:\.|@\/|~\/)/.test(call.arguments[0].value));
|
|
92031
93319
|
for (const call of mocked) {
|
|
92032
93320
|
const spec = call.arguments[0].value;
|
|
92033
|
-
const base =
|
|
93321
|
+
const base = path46.basename(spec.replace(/\/index(?:\.[cm]?[jt]sx?)?$/, "")).replace(/\.(?:[cm]?[jt]sx?)$/, "");
|
|
92034
93322
|
if (base === unit)
|
|
92035
93323
|
emit({
|
|
92036
93324
|
rule: "over-mocking",
|
|
@@ -92418,11 +93706,11 @@ var init_rules = __esm(() => {
|
|
|
92418
93706
|
});
|
|
92419
93707
|
|
|
92420
93708
|
// packages/workit-cli/src/test-audit/audit.ts
|
|
92421
|
-
import { spawnSync as
|
|
93709
|
+
import { spawnSync as spawnSync23 } from "node:child_process";
|
|
92422
93710
|
import { existsSync as existsSync18, readdirSync as readdirSync8, readFileSync as readFileSync19, statSync as statSync7 } from "node:fs";
|
|
92423
|
-
import
|
|
93711
|
+
import path47 from "node:path";
|
|
92424
93712
|
function git6(cwd, args) {
|
|
92425
|
-
const run =
|
|
93713
|
+
const run = spawnSync23("git", ["-c", "core.quotepath=off", ...args], {
|
|
92426
93714
|
cwd,
|
|
92427
93715
|
encoding: "utf8",
|
|
92428
93716
|
maxBuffer: 64 * 1024 * 1024
|
|
@@ -92433,7 +93721,7 @@ function walkTests(dir, out) {
|
|
|
92433
93721
|
for (const entry of readdirSync8(dir, { withFileTypes: true })) {
|
|
92434
93722
|
if (entry.name.startsWith(".") && entry.name !== "." && entry.isDirectory())
|
|
92435
93723
|
continue;
|
|
92436
|
-
const full =
|
|
93724
|
+
const full = path47.join(dir, entry.name);
|
|
92437
93725
|
if (entry.isDirectory()) {
|
|
92438
93726
|
if (!SKIP_DIRS.has(entry.name))
|
|
92439
93727
|
walkTests(full, out);
|
|
@@ -92445,7 +93733,7 @@ function expandPaths(cwd, inputs) {
|
|
|
92445
93733
|
const files = [];
|
|
92446
93734
|
const missing = [];
|
|
92447
93735
|
for (const input of inputs.length > 0 ? inputs : ["."]) {
|
|
92448
|
-
const full =
|
|
93736
|
+
const full = path47.resolve(cwd, input);
|
|
92449
93737
|
if (!existsSync18(full))
|
|
92450
93738
|
missing.push(input);
|
|
92451
93739
|
else if (statSync7(full).isDirectory())
|
|
@@ -92482,11 +93770,11 @@ function changedLines(cwd, base) {
|
|
|
92482
93770
|
for (let offset = 0;offset < count; offset += 1)
|
|
92483
93771
|
lines.add(start + offset);
|
|
92484
93772
|
}
|
|
92485
|
-
changed.set(
|
|
93773
|
+
changed.set(path47.join(root, file), lines);
|
|
92486
93774
|
}
|
|
92487
93775
|
const untracked = git6(root, ["ls-files", "--others", "--exclude-standard", "-z"]);
|
|
92488
93776
|
for (const file of untracked.stdout.split("\x00").filter(Boolean))
|
|
92489
|
-
changed.set(
|
|
93777
|
+
changed.set(path47.join(root, file), "all");
|
|
92490
93778
|
return changed;
|
|
92491
93779
|
}
|
|
92492
93780
|
function auditFiles(files, display) {
|
|
@@ -92548,7 +93836,7 @@ function auditFiles(files, display) {
|
|
|
92548
93836
|
durationMs: Math.round(performance.now() - started)
|
|
92549
93837
|
};
|
|
92550
93838
|
}
|
|
92551
|
-
var TEST_FILE, CODE_FILE, SKIP_DIRS, toPosix = (file) => file.split(
|
|
93839
|
+
var TEST_FILE, CODE_FILE, SKIP_DIRS, toPosix = (file) => file.split(path47.sep).join("/"), SEVERITY_ORDER, ruleIds;
|
|
92552
93840
|
var init_audit = __esm(() => {
|
|
92553
93841
|
init_ast();
|
|
92554
93842
|
init_rules();
|
|
@@ -92578,7 +93866,7 @@ import {
|
|
|
92578
93866
|
copyFileSync as copyFileSync6
|
|
92579
93867
|
} from "node:fs";
|
|
92580
93868
|
import os14 from "node:os";
|
|
92581
|
-
import
|
|
93869
|
+
import path48 from "node:path";
|
|
92582
93870
|
function mutantsFor(source, file, lines) {
|
|
92583
93871
|
const parsed = parseSource(source, file);
|
|
92584
93872
|
const out = [];
|
|
@@ -92651,7 +93939,7 @@ function relatedTests(source, testFiles) {
|
|
|
92651
93939
|
for (const match of text.matchAll(SPECIFIER)) {
|
|
92652
93940
|
const spec = match[1];
|
|
92653
93941
|
if (spec.startsWith(".")) {
|
|
92654
|
-
if (stripExt(
|
|
93942
|
+
if (stripExt(path48.posix.join(path48.posix.dirname(file), spec)) === target)
|
|
92655
93943
|
return true;
|
|
92656
93944
|
} else if (spec.includes("/") && stripExt(spec).split("/").slice(-2).join("/") === tail)
|
|
92657
93945
|
return true;
|
|
@@ -92671,12 +93959,12 @@ function removeStaleCopies(tmp = os14.tmpdir()) {
|
|
|
92671
93959
|
for (const name of names) {
|
|
92672
93960
|
if (!name.startsWith(TEMP_PREFIX))
|
|
92673
93961
|
continue;
|
|
92674
|
-
const dir =
|
|
93962
|
+
const dir = path48.join(tmp, name);
|
|
92675
93963
|
try {
|
|
92676
93964
|
const stat = lstatSync7(dir);
|
|
92677
93965
|
if (!stat.isDirectory() || uid !== undefined && stat.uid !== uid)
|
|
92678
93966
|
continue;
|
|
92679
|
-
const pid = Number(readFileSync20(
|
|
93967
|
+
const pid = Number(readFileSync20(path48.join(dir, PID_FILE), "utf8"));
|
|
92680
93968
|
if (Number.isInteger(pid) && pid > 0 && alive(pid))
|
|
92681
93969
|
continue;
|
|
92682
93970
|
} catch (error) {
|
|
@@ -92693,8 +93981,8 @@ function removeStaleCopies(tmp = os14.tmpdir()) {
|
|
|
92693
93981
|
function linkNodeModules(origin, target, repo, copy) {
|
|
92694
93982
|
mkdirSync9(target, { recursive: true });
|
|
92695
93983
|
for (const entry of readdirSync9(origin, { withFileTypes: true })) {
|
|
92696
|
-
const from =
|
|
92697
|
-
const to =
|
|
93984
|
+
const from = path48.join(origin, entry.name);
|
|
93985
|
+
const to = path48.join(target, entry.name);
|
|
92698
93986
|
if (entry.name.startsWith("@") && entry.isDirectory()) {
|
|
92699
93987
|
linkNodeModules(from, to, repo, copy);
|
|
92700
93988
|
continue;
|
|
@@ -92710,9 +93998,9 @@ function linkNodeModules(origin, target, repo, copy) {
|
|
|
92710
93998
|
} catch {
|
|
92711
93999
|
continue;
|
|
92712
94000
|
}
|
|
92713
|
-
const relative =
|
|
92714
|
-
if (!relative.startsWith("..") && !
|
|
92715
|
-
symlinkSync(
|
|
94001
|
+
const relative = path48.relative(repo, real);
|
|
94002
|
+
if (!relative.startsWith("..") && !path48.isAbsolute(relative) && !relative.split(path48.sep).includes("node_modules")) {
|
|
94003
|
+
symlinkSync(path48.join(copy, relative), to, "junction");
|
|
92716
94004
|
continue;
|
|
92717
94005
|
}
|
|
92718
94006
|
}
|
|
@@ -92727,8 +94015,8 @@ function copySymlink(repo, tree, from, to) {
|
|
|
92727
94015
|
real = null;
|
|
92728
94016
|
}
|
|
92729
94017
|
const realRepo = realpathSync9(repo);
|
|
92730
|
-
if (real &&
|
|
92731
|
-
symlinkSync(
|
|
94018
|
+
if (real && inside3(realRepo, real)) {
|
|
94019
|
+
symlinkSync(path48.relative(path48.dirname(to), path48.join(tree, path48.relative(realRepo, real))), to);
|
|
92732
94020
|
return;
|
|
92733
94021
|
}
|
|
92734
94022
|
if (real && statSync8(real).isFile())
|
|
@@ -92737,10 +94025,10 @@ function copySymlink(repo, tree, from, to) {
|
|
|
92737
94025
|
symlinkSync(readlinkSync3(from), to);
|
|
92738
94026
|
}
|
|
92739
94027
|
function writeInside(copy, file, content) {
|
|
92740
|
-
const target =
|
|
94028
|
+
const target = path48.join(copy, file);
|
|
92741
94029
|
try {
|
|
92742
94030
|
const stat = lstatSync7(target);
|
|
92743
|
-
if (!stat.isFile() || !
|
|
94031
|
+
if (!stat.isFile() || !inside3(realpathSync9(copy), realpathSync9(path48.dirname(target))))
|
|
92744
94032
|
return false;
|
|
92745
94033
|
} catch {
|
|
92746
94034
|
return false;
|
|
@@ -92751,9 +94039,9 @@ function writeInside(copy, file, content) {
|
|
|
92751
94039
|
return true;
|
|
92752
94040
|
}
|
|
92753
94041
|
function copyWorkingTree(repo) {
|
|
92754
|
-
const dir = mkdtempSync4(
|
|
92755
|
-
writeFileSync9(
|
|
92756
|
-
const tree =
|
|
94042
|
+
const dir = mkdtempSync4(path48.join(os14.tmpdir(), TEMP_PREFIX));
|
|
94043
|
+
writeFileSync9(path48.join(dir, PID_FILE), String(process.pid));
|
|
94044
|
+
const tree = path48.join(dir, "tree");
|
|
92757
94045
|
const head = git6(repo, ["rev-parse", "--verify", "--quiet", "HEAD"]).stdout.trim();
|
|
92758
94046
|
if (head && git6(dir, ["clone", "-q", "--shared", "--no-checkout", repo, tree]).ok) {
|
|
92759
94047
|
const branch = git6(repo, ["symbolic-ref", "--quiet", "--short", "HEAD"]).stdout.trim();
|
|
@@ -92770,29 +94058,29 @@ function copyWorkingTree(repo) {
|
|
|
92770
94058
|
for (const relative of listed) {
|
|
92771
94059
|
if (!relative)
|
|
92772
94060
|
continue;
|
|
92773
|
-
const from =
|
|
94061
|
+
const from = path48.join(repo, relative);
|
|
92774
94062
|
let stat;
|
|
92775
94063
|
try {
|
|
92776
94064
|
stat = lstatSync7(from);
|
|
92777
94065
|
} catch {
|
|
92778
94066
|
continue;
|
|
92779
94067
|
}
|
|
92780
|
-
const to =
|
|
92781
|
-
mkdirSync9(
|
|
94068
|
+
const to = path48.join(tree, relative);
|
|
94069
|
+
mkdirSync9(path48.dirname(to), { recursive: true });
|
|
92782
94070
|
if (stat.isSymbolicLink())
|
|
92783
94071
|
copySymlink(repo, tree, from, to);
|
|
92784
94072
|
else if (stat.isFile())
|
|
92785
94073
|
copyFileSync6(from, to);
|
|
92786
|
-
let parent =
|
|
94074
|
+
let parent = path48.dirname(relative);
|
|
92787
94075
|
while (parent !== "." && !dirs.has(parent)) {
|
|
92788
94076
|
dirs.add(parent);
|
|
92789
|
-
parent =
|
|
94077
|
+
parent = path48.dirname(parent);
|
|
92790
94078
|
}
|
|
92791
94079
|
}
|
|
92792
94080
|
for (const parent of dirs) {
|
|
92793
|
-
const modules =
|
|
92794
|
-
if (existsSync19(modules) && !existsSync19(
|
|
92795
|
-
linkNodeModules(modules,
|
|
94081
|
+
const modules = path48.join(repo, parent, "node_modules");
|
|
94082
|
+
if (existsSync19(modules) && !existsSync19(path48.join(tree, parent, "node_modules")))
|
|
94083
|
+
linkNodeModules(modules, path48.join(tree, parent, "node_modules"), repo, tree);
|
|
92796
94084
|
}
|
|
92797
94085
|
return { dir, tree };
|
|
92798
94086
|
}
|
|
@@ -92831,14 +94119,14 @@ async function runMutation(options) {
|
|
|
92831
94119
|
const tracked = git6(repo, ["ls-files", "-co", "--exclude-standard", "-z"]).stdout.split("\x00").filter(Boolean);
|
|
92832
94120
|
const testFiles = tracked.filter((file) => TEST_FILE.test(file)).map((file) => {
|
|
92833
94121
|
try {
|
|
92834
|
-
return { file, text: readFileSync20(
|
|
94122
|
+
return { file, text: readFileSync20(path48.join(repo, file), "utf8") };
|
|
92835
94123
|
} catch {
|
|
92836
94124
|
return { file, text: "" };
|
|
92837
94125
|
}
|
|
92838
94126
|
});
|
|
92839
94127
|
const candidates = [];
|
|
92840
94128
|
for (const [absolute, lines] of [...options.changed].toSorted(([a], [b]) => a.localeCompare(b))) {
|
|
92841
|
-
const relative = toPosix(
|
|
94129
|
+
const relative = toPosix(path48.relative(repo, absolute));
|
|
92842
94130
|
if (!CODE_FILE.test(relative) || TEST_FILE.test(relative) || relative.endsWith(".d.ts"))
|
|
92843
94131
|
continue;
|
|
92844
94132
|
if (NOT_SOURCE.test(relative) || !existsSync19(absolute))
|
|
@@ -92852,9 +94140,9 @@ async function runMutation(options) {
|
|
|
92852
94140
|
for (const mutant of mutants)
|
|
92853
94141
|
if (!related.has(mutant.file))
|
|
92854
94142
|
related.set(mutant.file, relatedTests(mutant.file, testFiles));
|
|
92855
|
-
const cwdRelative =
|
|
94143
|
+
const cwdRelative = path48.relative(repo, options.cwd);
|
|
92856
94144
|
const asArg = (file) => {
|
|
92857
|
-
const relative = toPosix(
|
|
94145
|
+
const relative = toPosix(path48.relative(cwdRelative, file));
|
|
92858
94146
|
return shellQuote(relative.startsWith(".") ? relative : `./${relative}`);
|
|
92859
94147
|
};
|
|
92860
94148
|
const commandFor = (tests) => options.command.replaceAll("{files}", () => tests.map(asArg).join(" "));
|
|
@@ -92889,7 +94177,7 @@ async function runMutation(options) {
|
|
|
92889
94177
|
return finish2(options, 0, results, counts, started);
|
|
92890
94178
|
removeStaleCopies();
|
|
92891
94179
|
const { dir: copyDir, tree: copy } = copyWorkingTree(repo);
|
|
92892
|
-
const copyCwd =
|
|
94180
|
+
const copyCwd = path48.join(copy, cwdRelative);
|
|
92893
94181
|
let current = null;
|
|
92894
94182
|
const restore = () => {
|
|
92895
94183
|
if (current)
|
|
@@ -92924,7 +94212,7 @@ async function runMutation(options) {
|
|
|
92924
94212
|
record({ ...mutant, status: "skipped", durationMs: 0, tests, reason: "budget" });
|
|
92925
94213
|
continue;
|
|
92926
94214
|
}
|
|
92927
|
-
const original = readFileSync20(
|
|
94215
|
+
const original = readFileSync20(path48.join(copy, mutant.file), "utf8");
|
|
92928
94216
|
const mutated = original.slice(0, mutant.start) + mutant.replacement + original.slice(mutant.end);
|
|
92929
94217
|
current = { file: mutant.file, original };
|
|
92930
94218
|
if (!writeInside(copy, mutant.file, mutated)) {
|
|
@@ -92980,9 +94268,9 @@ var NOT_SOURCE, FLIPS, isStringy = (node) => node.type === "StringLiteral" || no
|
|
|
92980
94268
|
} catch (error) {
|
|
92981
94269
|
return error.code === "EPERM";
|
|
92982
94270
|
}
|
|
92983
|
-
},
|
|
92984
|
-
const relative =
|
|
92985
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
94271
|
+
}, inside3 = (root, file) => {
|
|
94272
|
+
const relative = path48.relative(root, file);
|
|
94273
|
+
return relative === "" || !relative.startsWith("..") && !path48.isAbsolute(relative);
|
|
92986
94274
|
}, active, killTree = (child) => {
|
|
92987
94275
|
try {
|
|
92988
94276
|
if (child.pid && process.platform !== "win32")
|
|
@@ -93031,7 +94319,7 @@ __export(exports_test_audit, {
|
|
|
93031
94319
|
run: () => run17
|
|
93032
94320
|
});
|
|
93033
94321
|
import { existsSync as existsSync20 } from "node:fs";
|
|
93034
|
-
import
|
|
94322
|
+
import path49 from "node:path";
|
|
93035
94323
|
function parse7(argv) {
|
|
93036
94324
|
const options = {
|
|
93037
94325
|
paths: [],
|
|
@@ -93131,8 +94419,8 @@ async function run17(argv, io) {
|
|
|
93131
94419
|
return emit(io, fail("invalid_input", `${options}
|
|
93132
94420
|
usage: ${USAGE10}`));
|
|
93133
94421
|
const top = git6(io.cwd, ["rev-parse", "--show-toplevel"]);
|
|
93134
|
-
const repo = top.ok ?
|
|
93135
|
-
const display = (file) => toPosix(
|
|
94422
|
+
const repo = top.ok ? path49.resolve(top.stdout.trim()) : null;
|
|
94423
|
+
const display = (file) => toPosix(path49.relative(io.cwd, file) || file);
|
|
93136
94424
|
let base = null;
|
|
93137
94425
|
let changed = null;
|
|
93138
94426
|
if (options.diff || options.mutate) {
|
|
@@ -93233,6 +94521,38 @@ var init_test_audit = __esm(() => {
|
|
|
93233
94521
|
]);
|
|
93234
94522
|
});
|
|
93235
94523
|
|
|
94524
|
+
// packages/workit-cli/src/verbs/knowledge.ts
|
|
94525
|
+
var exports_knowledge = {};
|
|
94526
|
+
__export(exports_knowledge, {
|
|
94527
|
+
run: () => run18
|
|
94528
|
+
});
|
|
94529
|
+
async function run18(argv, io) {
|
|
94530
|
+
const [sub, ...rest] = argv.filter((arg) => arg !== "--json");
|
|
94531
|
+
if (sub !== "lint" || rest.length > 0)
|
|
94532
|
+
return emit(io, fail("invalid_input", `usage: ${USAGE11}`));
|
|
94533
|
+
const report = lintKnowledge(checkRoot(io.cwd));
|
|
94534
|
+
if (report.unreadable.length > 0)
|
|
94535
|
+
return emit(io, fail("unavailable", `knowledge lint: cannot read ${report.unreadable.join(", ")}`, {
|
|
94536
|
+
data: report,
|
|
94537
|
+
unblock: `make ${report.unreadable.join(", ")} readable, then re-run workit knowledge lint`
|
|
94538
|
+
}));
|
|
94539
|
+
const summary = `knowledge lint: ${report.findings.length} finding${report.findings.length === 1 ? "" : "s"} in ${report.files.length} file${report.files.length === 1 ? "" : "s"} (${report.files.map((file) => `${file.file} ${file.bytes} B`).join(", ") || "no knowledge files"}; AGENTS.md budget ${report.budget} B)`;
|
|
94540
|
+
if (io.json)
|
|
94541
|
+
return emit(io, report.findings.length === 0 ? ok(report) : fail("failed", summary, { data: report }));
|
|
94542
|
+
for (const finding of report.findings)
|
|
94543
|
+
io.stdout(`${finding.file}${finding.line === null ? "" : `:${finding.line}`} [${finding.rule}] ${finding.message}
|
|
94544
|
+
`);
|
|
94545
|
+
io.stdout(`${summary}
|
|
94546
|
+
`);
|
|
94547
|
+
return report.findings.length === 0 ? 0 : 1;
|
|
94548
|
+
}
|
|
94549
|
+
var USAGE11 = "workit knowledge lint [--json]";
|
|
94550
|
+
var init_knowledge2 = __esm(() => {
|
|
94551
|
+
init_check_config();
|
|
94552
|
+
init_knowledge();
|
|
94553
|
+
init_output();
|
|
94554
|
+
});
|
|
94555
|
+
|
|
93236
94556
|
// packages/workit-core/src/core/youtrack-tools.ts
|
|
93237
94557
|
async function logTimeUpdate(input, operation = defaultOperations) {
|
|
93238
94558
|
try {
|
|
@@ -93340,14 +94660,14 @@ var init_youtrack_writes = __esm(() => {
|
|
|
93340
94660
|
// packages/workit-cli/src/verbs/youtrack.ts
|
|
93341
94661
|
var exports_youtrack = {};
|
|
93342
94662
|
__export(exports_youtrack, {
|
|
93343
|
-
run: () =>
|
|
94663
|
+
run: () => run19
|
|
93344
94664
|
});
|
|
93345
94665
|
import { readFileSync as readFileSync21 } from "node:fs";
|
|
93346
|
-
import
|
|
93347
|
-
async function
|
|
94666
|
+
import path50 from "node:path";
|
|
94667
|
+
async function run19(argv, io) {
|
|
93348
94668
|
const [sub, ...rest] = argv;
|
|
93349
94669
|
if (sub !== "note" && sub !== "time" && sub !== "meeting")
|
|
93350
|
-
return usage3(io, `unknown subcommand "${sub ?? ""}"`,
|
|
94670
|
+
return usage3(io, `unknown subcommand "${sub ?? ""}"`, USAGE12);
|
|
93351
94671
|
const flags = parseFlags(rest, {
|
|
93352
94672
|
markdown: "value",
|
|
93353
94673
|
file: "value",
|
|
@@ -93356,18 +94676,18 @@ async function run18(argv, io) {
|
|
|
93356
94676
|
date: "value"
|
|
93357
94677
|
});
|
|
93358
94678
|
if (typeof flags === "string")
|
|
93359
|
-
return usage3(io, flags,
|
|
94679
|
+
return usage3(io, flags, USAGE12);
|
|
93360
94680
|
const [issueId, extra] = flags.positionals;
|
|
93361
94681
|
if (!issueId || !ISSUE_RE2.test(issueId))
|
|
93362
|
-
return usage3(io, "an issue id like ABC-123 is required",
|
|
94682
|
+
return usage3(io, "an issue id like ABC-123 is required", USAGE12);
|
|
93363
94683
|
if (extra !== undefined)
|
|
93364
|
-
return usage3(io, `unexpected argument ${extra}`,
|
|
94684
|
+
return usage3(io, `unexpected argument ${extra}`, USAGE12);
|
|
93365
94685
|
const minutes = positiveInt(flags.values.minutes, "--minutes");
|
|
93366
94686
|
if (typeof minutes === "string")
|
|
93367
|
-
return usage3(io, minutes,
|
|
94687
|
+
return usage3(io, minutes, USAGE12);
|
|
93368
94688
|
const date = youTrackWorkDateMs(flags.values.date ?? "auto");
|
|
93369
94689
|
if ("error" in date)
|
|
93370
|
-
return usage3(io, date.error,
|
|
94690
|
+
return usage3(io, date.error, USAGE12);
|
|
93371
94691
|
const dateMs = date.data.dateMs;
|
|
93372
94692
|
const markedDay = flags.values.date !== undefined && flags.values.date !== "auto" ? { dateMs } : {};
|
|
93373
94693
|
const root = io.cwd;
|
|
@@ -93385,24 +94705,24 @@ async function run18(argv, io) {
|
|
|
93385
94705
|
};
|
|
93386
94706
|
if (sub === "note") {
|
|
93387
94707
|
if (Boolean(flags.values.markdown) === Boolean(flags.values.file))
|
|
93388
|
-
return usage3(io, "pass exactly one of --markdown or --file",
|
|
94708
|
+
return usage3(io, "pass exactly one of --markdown or --file", USAGE12);
|
|
93389
94709
|
let markdown = flags.values.markdown ?? "";
|
|
93390
94710
|
if (flags.values.file)
|
|
93391
94711
|
try {
|
|
93392
|
-
markdown = readFileSync21(
|
|
94712
|
+
markdown = readFileSync21(path50.resolve(io.cwd, flags.values.file), "utf8");
|
|
93393
94713
|
} catch (error) {
|
|
93394
|
-
return usage3(io, `cannot read ${flags.values.file}: ${String(error)}`,
|
|
94714
|
+
return usage3(io, `cannot read ${flags.values.file}: ${String(error)}`, USAGE12);
|
|
93395
94715
|
}
|
|
93396
94716
|
if (!markdown.trim())
|
|
93397
|
-
return usage3(io, "the note is empty",
|
|
94717
|
+
return usage3(io, "the note is empty", USAGE12);
|
|
93398
94718
|
const missing = step("comment", "comments", { operation: "note", issueId, markdown }, markdown, async (text) => commentResult(await postComment(issueId, text, root))) ?? (minutes ? step("time", "workItems", { operation: "note.time", issueId, markdown, minutes, ...markedDay }, "workit update", (text) => logTimeUpdate({ issueId, minutes, text, dateMs, workspace_root: root })) : null);
|
|
93399
94719
|
if (missing)
|
|
93400
94720
|
return emit(io, fail("unavailable", missing));
|
|
93401
94721
|
} else {
|
|
93402
94722
|
if (!minutes)
|
|
93403
|
-
return usage3(io, `${sub} needs --minutes <n>`,
|
|
94723
|
+
return usage3(io, `${sub} needs --minutes <n>`, USAGE12);
|
|
93404
94724
|
if (sub === "meeting" && !flags.values.text)
|
|
93405
|
-
return usage3(io, "meeting needs --text <what the meeting was>",
|
|
94725
|
+
return usage3(io, "meeting needs --text <what the meeting was>", USAGE12);
|
|
93406
94726
|
const text = flags.values.text ?? "";
|
|
93407
94727
|
const missing = step(sub, "workItems", { operation: sub, issueId, minutes, text, ...markedDay }, text, (marked) => logTimeUpdate({ issueId, minutes, text: marked, dateMs, workspace_root: root }));
|
|
93408
94728
|
if (missing)
|
|
@@ -93431,7 +94751,7 @@ async function run18(argv, io) {
|
|
|
93431
94751
|
...data.steps.flatMap((entry) => entry.note ? [` note (${entry.step}): ${entry.note}`] : [])
|
|
93432
94752
|
]);
|
|
93433
94753
|
}
|
|
93434
|
-
var
|
|
94754
|
+
var USAGE12 = "workit youtrack note <ISSUE> (--markdown <t> | --file <p>) [--minutes <n>] [--date auto|YYYY-MM-DD] | youtrack time <ISSUE> --minutes <n> [--text <t>] [--date …] | youtrack meeting <ISSUE> --minutes <n> --text <t> [--date …]", commentResult = (value) => {
|
|
93435
94755
|
try {
|
|
93436
94756
|
return { ok: true, data: unwrap2(value) };
|
|
93437
94757
|
} catch (error) {
|
|
@@ -93455,14 +94775,14 @@ var init_youtrack2 = __esm(() => {
|
|
|
93455
94775
|
// packages/workit-cli/src/verbs/changelog.ts
|
|
93456
94776
|
var exports_changelog = {};
|
|
93457
94777
|
__export(exports_changelog, {
|
|
93458
|
-
run: () =>
|
|
94778
|
+
run: () => run20
|
|
93459
94779
|
});
|
|
93460
94780
|
import { readFileSync as readFileSync22 } from "node:fs";
|
|
93461
|
-
import
|
|
93462
|
-
async function
|
|
94781
|
+
import path51 from "node:path";
|
|
94782
|
+
async function run20(argv, io) {
|
|
93463
94783
|
const [sub, ...rest] = argv;
|
|
93464
94784
|
if (sub !== "apply")
|
|
93465
|
-
return usage3(io, `unknown subcommand "${sub ?? ""}"`,
|
|
94785
|
+
return usage3(io, `unknown subcommand "${sub ?? ""}"`, USAGE13);
|
|
93466
94786
|
const flags = parseFlags(rest, {
|
|
93467
94787
|
entries: "value",
|
|
93468
94788
|
path: "value",
|
|
@@ -93470,18 +94790,18 @@ async function run19(argv, io) {
|
|
|
93470
94790
|
preview: "boolean"
|
|
93471
94791
|
});
|
|
93472
94792
|
if (typeof flags === "string")
|
|
93473
|
-
return usage3(io, flags,
|
|
94793
|
+
return usage3(io, flags, USAGE13);
|
|
93474
94794
|
if (flags.positionals.length)
|
|
93475
|
-
return usage3(io, `unexpected argument ${flags.positionals[0]}`,
|
|
94795
|
+
return usage3(io, `unexpected argument ${flags.positionals[0]}`, USAGE13);
|
|
93476
94796
|
const normalizeOnly = flags.booleans.has("normalize-only");
|
|
93477
94797
|
if (!normalizeOnly && !flags.values.entries)
|
|
93478
|
-
return usage3(io, "pass --entries <JSON> or --normalize-only",
|
|
94798
|
+
return usage3(io, "pass --entries <JSON> or --normalize-only", USAGE13);
|
|
93479
94799
|
let entries;
|
|
93480
94800
|
if (flags.values.entries)
|
|
93481
94801
|
try {
|
|
93482
94802
|
entries = readEntries(flags.values.entries, io.cwd);
|
|
93483
94803
|
} catch (error) {
|
|
93484
|
-
return usage3(io, `--entries is not valid JSON: ${String(error)}`,
|
|
94804
|
+
return usage3(io, `--entries is not valid JSON: ${String(error)}`, USAGE13);
|
|
93485
94805
|
}
|
|
93486
94806
|
const input = {
|
|
93487
94807
|
entries,
|
|
@@ -93503,8 +94823,8 @@ async function run19(argv, io) {
|
|
|
93503
94823
|
`changelog updated: ${String(applied.path ?? "CHANGELOG.md")}`
|
|
93504
94824
|
]);
|
|
93505
94825
|
}
|
|
93506
|
-
var
|
|
93507
|
-
const text = raw === "-" ? readFileSync22(0, "utf8") : raw.startsWith("@") ? readFileSync22(
|
|
94826
|
+
var USAGE13 = "workit changelog apply (--entries <JSON|@file|-> | --normalize-only) [--path CHANGELOG.md] [--preview]", readEntries = (raw, cwd) => {
|
|
94827
|
+
const text = raw === "-" ? readFileSync22(0, "utf8") : raw.startsWith("@") ? readFileSync22(path51.resolve(cwd, raw.slice(1)), "utf8") : raw;
|
|
93508
94828
|
return JSON.parse(text);
|
|
93509
94829
|
};
|
|
93510
94830
|
var init_changelog2 = __esm(() => {
|
|
@@ -93516,12 +94836,12 @@ var init_changelog2 = __esm(() => {
|
|
|
93516
94836
|
// packages/workit-cli/src/verbs/handoff.ts
|
|
93517
94837
|
var exports_handoff = {};
|
|
93518
94838
|
__export(exports_handoff, {
|
|
93519
|
-
run: () =>
|
|
94839
|
+
run: () => run21
|
|
93520
94840
|
});
|
|
93521
94841
|
import { parseArgs as parseArgs2 } from "node:util";
|
|
93522
94842
|
function human2(brief) {
|
|
93523
94843
|
const lines = [
|
|
93524
|
-
`branch: ${brief.branch ?? "(detached)"} @ ${
|
|
94844
|
+
`branch: ${brief.branch ?? "(detached)"} @ ${short6(brief.head)}${brief.subject ? ` ${brief.subject}` : ""}`
|
|
93525
94845
|
];
|
|
93526
94846
|
if (brief.base)
|
|
93527
94847
|
lines.push(`base: ${brief.base}${brief.ahead === null ? "" : ` (ahead ${brief.ahead}, behind ${brief.behind})`}`);
|
|
@@ -93564,11 +94884,11 @@ async function resumeBrief(argv, io) {
|
|
|
93564
94884
|
strict: true
|
|
93565
94885
|
}));
|
|
93566
94886
|
} catch (error) {
|
|
93567
|
-
return emit(io, fail("invalid_input", error.message, { unblock:
|
|
94887
|
+
return emit(io, fail("invalid_input", error.message, { unblock: USAGE14 }));
|
|
93568
94888
|
}
|
|
93569
94889
|
const last = values.last === undefined ? 10 : Number(values.last);
|
|
93570
94890
|
if (!Number.isInteger(last) || last < 1)
|
|
93571
|
-
return emit(io, fail("invalid_input", "--last must be a positive integer", { unblock:
|
|
94891
|
+
return emit(io, fail("invalid_input", "--last must be a positive integer", { unblock: USAGE14 }));
|
|
93572
94892
|
const built = buildHandoff(io.cwd, {
|
|
93573
94893
|
last,
|
|
93574
94894
|
note: values.note ?? null,
|
|
@@ -93586,13 +94906,13 @@ async function resumeBrief(argv, io) {
|
|
|
93586
94906
|
}
|
|
93587
94907
|
return emit(io, ok({ ...data, recorded }), human2);
|
|
93588
94908
|
}
|
|
93589
|
-
async function
|
|
94909
|
+
async function run21(argv, io) {
|
|
93590
94910
|
if (!argv.includes("--task"))
|
|
93591
94911
|
return resumeBrief(argv, io);
|
|
93592
94912
|
await Promise.resolve().then(() => init_task());
|
|
93593
94913
|
return runTaskCommand(["handoff", ...argv]);
|
|
93594
94914
|
}
|
|
93595
|
-
var
|
|
94915
|
+
var USAGE14 = "workit handoff [--note <text>] [--next <text>] [--record] [--last <n>] [--json]", short6 = (sha) => sha ? sha.slice(0, 12) : "no commit";
|
|
93596
94916
|
var init_handoff = __esm(() => {
|
|
93597
94917
|
init_ledger();
|
|
93598
94918
|
init_output();
|
|
@@ -93601,7 +94921,7 @@ var init_handoff = __esm(() => {
|
|
|
93601
94921
|
// packages/workit-cli/src/main.ts
|
|
93602
94922
|
init_package();
|
|
93603
94923
|
init_output();
|
|
93604
|
-
import
|
|
94924
|
+
import path52 from "node:path";
|
|
93605
94925
|
|
|
93606
94926
|
// packages/workit-cli/src/verbs/registry.ts
|
|
93607
94927
|
var TASK_FAMILY_NAMES = [
|
|
@@ -93716,8 +95036,8 @@ var VERBS = [
|
|
|
93716
95036
|
{
|
|
93717
95037
|
name: "fanout",
|
|
93718
95038
|
group: "delivery",
|
|
93719
|
-
usage: "workit fanout plan <plan.json> [--name <n>] [--trunk <b> | --track <t>] | fanout check [<slice>…] [--name <n>] [--base <ref>] [--json]",
|
|
93720
|
-
summary: "Register parallel slices (complete briefs, disjoint scopes, shared-file owners),
|
|
95039
|
+
usage: "workit fanout plan <plan.json> [--name <n>] [--trunk <b> | --track <t>] | fanout check [<slice>…] [--name <n>] [--base <ref>] [--offline] | fanout status [--name <n>] [--stuck-after 30m] [--offline] | fanout worktree create|release <slice> [--force] [--json]",
|
|
95040
|
+
summary: "Register parallel slices (complete briefs, disjoint scopes, shared-file owners), watch them (STUCK, PR, CI, verdict, landed, landing order), make and release slice worktrees, and gate fan-in: out-of-scope edits, trunk and sibling merge conflicts",
|
|
93721
95041
|
load: () => Promise.resolve().then(() => (init_fanout2(), exports_fanout))
|
|
93722
95042
|
},
|
|
93723
95043
|
{
|
|
@@ -93734,6 +95054,13 @@ var VERBS = [
|
|
|
93734
95054
|
summary: "Flag tautological and low-value tests with a suggested independent oracle; --mutate checks changed lines with diff-scoped mutation",
|
|
93735
95055
|
load: () => Promise.resolve().then(() => (init_test_audit(), exports_test_audit))
|
|
93736
95056
|
},
|
|
95057
|
+
{
|
|
95058
|
+
name: "knowledge",
|
|
95059
|
+
group: "delivery",
|
|
95060
|
+
usage: "workit knowledge lint [--json]",
|
|
95061
|
+
summary: "Lint agent knowledge files: AGENTS.md byte budget, broken local links, scaffold-only files, duplicated rules (exit 1 on findings)",
|
|
95062
|
+
load: () => Promise.resolve().then(() => (init_knowledge2(), exports_knowledge))
|
|
95063
|
+
},
|
|
93737
95064
|
{
|
|
93738
95065
|
name: "youtrack",
|
|
93739
95066
|
group: "delivery",
|
|
@@ -93838,7 +95165,7 @@ var defaultIo = (json) => ({
|
|
|
93838
95165
|
});
|
|
93839
95166
|
async function main(argv, overrides = {}, options = {}) {
|
|
93840
95167
|
const parsed = parseGlobals(argv);
|
|
93841
|
-
const cwd =
|
|
95168
|
+
const cwd = path52.resolve(overrides.cwd ?? process.cwd(), parsed.cwd ?? ".");
|
|
93842
95169
|
const io = { ...defaultIo(parsed.json), ...overrides, json: parsed.json, cwd };
|
|
93843
95170
|
if (parsed.error)
|
|
93844
95171
|
return emit(io, fail("invalid_input", parsed.error));
|