@odla-ai/cli 0.42.2 → 0.43.1
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/README.md +10 -4
- package/dist/bin.cjs +489 -214
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-K6ATAKGR.js → chunk-T4OPSRME.js} +453 -208
- package/dist/chunk-T4OPSRME.js.map +1 -0
- package/dist/{cli-4FCRBWED.js → cli-3JOB4OUV.js} +2 -2
- package/dist/index.cjs +452 -207
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/skills/odla/SKILL.md +10 -3
- package/skills/odla/references/pm-work-intake.md +33 -11
- package/skills/odla/references/pm.md +23 -12
- package/dist/chunk-K6ATAKGR.js.map +0 -1
- /package/dist/{cli-4FCRBWED.js.map → cli-3JOB4OUV.js.map} +0 -0
package/dist/bin.cjs
CHANGED
|
@@ -556,10 +556,10 @@ function isManagedDevVar(line2) {
|
|
|
556
556
|
const match = line2.match(/^\s*(?:export\s+)?([A-Z][A-Z0-9_]*)\s*=/);
|
|
557
557
|
return !!match?.[1] && MANAGED_DEV_VARS.has(match[1]);
|
|
558
558
|
}
|
|
559
|
-
function writePrivateText(path,
|
|
559
|
+
function writePrivateText(path, text4) {
|
|
560
560
|
(0, import_node_fs7.mkdirSync)((0, import_node_path5.dirname)(path), { recursive: true });
|
|
561
561
|
const temporary = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
562
|
-
(0, import_node_fs7.writeFileSync)(temporary,
|
|
562
|
+
(0, import_node_fs7.writeFileSync)(temporary, text4, { mode: 384 });
|
|
563
563
|
(0, import_node_fs7.chmodSync)(temporary, 384);
|
|
564
564
|
(0, import_node_fs7.renameSync)(temporary, path);
|
|
565
565
|
}
|
|
@@ -1105,12 +1105,12 @@ async function readAdminAiAudit(request3) {
|
|
|
1105
1105
|
}
|
|
1106
1106
|
}
|
|
1107
1107
|
async function responseBody(response2) {
|
|
1108
|
-
const
|
|
1109
|
-
if (!
|
|
1108
|
+
const text4 = await response2.text();
|
|
1109
|
+
if (!text4) return {};
|
|
1110
1110
|
try {
|
|
1111
|
-
return JSON.parse(
|
|
1111
|
+
return JSON.parse(text4);
|
|
1112
1112
|
} catch {
|
|
1113
|
-
return { message:
|
|
1113
|
+
return { message: text4.slice(0, 300) };
|
|
1114
1114
|
}
|
|
1115
1115
|
}
|
|
1116
1116
|
function apiError(status, body) {
|
|
@@ -1226,12 +1226,12 @@ function timestamp2(value2) {
|
|
|
1226
1226
|
return Number.isFinite(date.valueOf()) ? date.toISOString() : "";
|
|
1227
1227
|
}
|
|
1228
1228
|
async function responseBody2(res) {
|
|
1229
|
-
const
|
|
1230
|
-
if (!
|
|
1229
|
+
const text4 = await res.text();
|
|
1230
|
+
if (!text4) return {};
|
|
1231
1231
|
try {
|
|
1232
|
-
return JSON.parse(
|
|
1232
|
+
return JSON.parse(text4);
|
|
1233
1233
|
} catch {
|
|
1234
|
-
return { message:
|
|
1234
|
+
return { message: text4.slice(0, 300) };
|
|
1235
1235
|
}
|
|
1236
1236
|
}
|
|
1237
1237
|
function apiError2(action2, status, body) {
|
|
@@ -1414,12 +1414,12 @@ function catalogModels(body) {
|
|
|
1414
1414
|
return body.catalog.models.filter((value2) => isRecord3(value2) && typeof value2.id === "string" && typeof value2.provider === "string");
|
|
1415
1415
|
}
|
|
1416
1416
|
async function responseBody3(res) {
|
|
1417
|
-
const
|
|
1418
|
-
if (!
|
|
1417
|
+
const text4 = await res.text();
|
|
1418
|
+
if (!text4) return {};
|
|
1419
1419
|
try {
|
|
1420
|
-
return JSON.parse(
|
|
1420
|
+
return JSON.parse(text4);
|
|
1421
1421
|
} catch {
|
|
1422
|
-
return { message:
|
|
1422
|
+
return { message: text4.slice(0, 300) };
|
|
1423
1423
|
}
|
|
1424
1424
|
}
|
|
1425
1425
|
function apiError3(action2, status, body) {
|
|
@@ -2936,8 +2936,8 @@ async function appImport(options) {
|
|
|
2936
2936
|
const out = options.stdout ?? console;
|
|
2937
2937
|
const say = options.json ? (line2) => out.error(line2) : (line2) => out.log(line2);
|
|
2938
2938
|
const { tenant } = resolveTenant(cfg, options.env);
|
|
2939
|
-
const
|
|
2940
|
-
const { format, sources } = (0, import_import.parseImport)(
|
|
2939
|
+
const text4 = options.file === "-" ? (options.readStdin ?? (() => (0, import_node_fs12.readFileSync)(0, "utf8")))() : (0, import_node_fs12.readFileSync)(options.file, "utf8");
|
|
2940
|
+
const { format, sources } = (0, import_import.parseImport)(text4, options.ns);
|
|
2941
2941
|
if (format === "namespace-map" && options.ns) {
|
|
2942
2942
|
throw new Error("--ns cannot be combined with a {namespace: rows} file \u2014 the file already names each namespace");
|
|
2943
2943
|
}
|
|
@@ -3274,7 +3274,7 @@ var init_brand_design_unpack = __esm({
|
|
|
3274
3274
|
"text/html": "html",
|
|
3275
3275
|
"application/json": "json"
|
|
3276
3276
|
};
|
|
3277
|
-
encode = (
|
|
3277
|
+
encode = (text4) => new TextEncoder().encode(text4);
|
|
3278
3278
|
}
|
|
3279
3279
|
});
|
|
3280
3280
|
|
|
@@ -4130,9 +4130,9 @@ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
|
|
|
4130
4130
|
}
|
|
4131
4131
|
throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText5(res)}`);
|
|
4132
4132
|
}
|
|
4133
|
-
function errorCode(
|
|
4133
|
+
function errorCode(text4) {
|
|
4134
4134
|
try {
|
|
4135
|
-
const body = JSON.parse(
|
|
4135
|
+
const body = JSON.parse(text4);
|
|
4136
4136
|
return typeof body.error?.code === "string" ? body.error.code : null;
|
|
4137
4137
|
} catch {
|
|
4138
4138
|
return null;
|
|
@@ -4918,15 +4918,15 @@ function readWranglerConfig(path) {
|
|
|
4918
4918
|
return null;
|
|
4919
4919
|
}
|
|
4920
4920
|
}
|
|
4921
|
-
function stripJsonComments(
|
|
4921
|
+
function stripJsonComments(text4) {
|
|
4922
4922
|
let result = "";
|
|
4923
4923
|
let inString = false;
|
|
4924
|
-
for (let i = 0; i <
|
|
4925
|
-
const ch =
|
|
4924
|
+
for (let i = 0; i < text4.length; i++) {
|
|
4925
|
+
const ch = text4[i];
|
|
4926
4926
|
if (inString) {
|
|
4927
4927
|
result += ch;
|
|
4928
4928
|
if (ch === "\\") {
|
|
4929
|
-
result +=
|
|
4929
|
+
result += text4[i + 1] ?? "";
|
|
4930
4930
|
i++;
|
|
4931
4931
|
} else if (ch === '"') {
|
|
4932
4932
|
inString = false;
|
|
@@ -4938,14 +4938,14 @@ function stripJsonComments(text3) {
|
|
|
4938
4938
|
result += ch;
|
|
4939
4939
|
continue;
|
|
4940
4940
|
}
|
|
4941
|
-
if (ch === "/" &&
|
|
4942
|
-
while (i <
|
|
4941
|
+
if (ch === "/" && text4[i + 1] === "/") {
|
|
4942
|
+
while (i < text4.length && text4[i] !== "\n") i++;
|
|
4943
4943
|
result += "\n";
|
|
4944
4944
|
continue;
|
|
4945
4945
|
}
|
|
4946
|
-
if (ch === "/" &&
|
|
4946
|
+
if (ch === "/" && text4[i + 1] === "*") {
|
|
4947
4947
|
i += 2;
|
|
4948
|
-
while (i <
|
|
4948
|
+
while (i < text4.length && !(text4[i] === "*" && text4[i + 1] === "/")) i++;
|
|
4949
4949
|
i++;
|
|
4950
4950
|
continue;
|
|
4951
4951
|
}
|
|
@@ -5529,9 +5529,9 @@ function initProject(options) {
|
|
|
5529
5529
|
out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
|
|
5530
5530
|
out.log("updated .gitignore for local odla credentials");
|
|
5531
5531
|
}
|
|
5532
|
-
function writeIfMissing(path,
|
|
5532
|
+
function writeIfMissing(path, text4) {
|
|
5533
5533
|
if ((0, import_node_fs16.existsSync)(path)) return;
|
|
5534
|
-
(0, import_node_fs16.writeFileSync)(path,
|
|
5534
|
+
(0, import_node_fs16.writeFileSync)(path, text4);
|
|
5535
5535
|
}
|
|
5536
5536
|
function configTemplate(input) {
|
|
5537
5537
|
const calendar = input.services.includes("calendar") ? ` calendar: {
|
|
@@ -5763,13 +5763,13 @@ async function secretsSetClerkKey(options) {
|
|
|
5763
5763
|
body: JSON.stringify({ value: value2 })
|
|
5764
5764
|
});
|
|
5765
5765
|
if (!res.ok) {
|
|
5766
|
-
const
|
|
5767
|
-
throw new Error(`store Clerk secret key failed (${res.status}): ${
|
|
5766
|
+
const text4 = scrubValue((await res.text().catch(() => "")).slice(0, 300), value2);
|
|
5767
|
+
throw new Error(`store Clerk secret key failed (${res.status}): ${text4 || "request failed"}`);
|
|
5768
5768
|
}
|
|
5769
5769
|
out.log(`Clerk secret key stored for ${tenantId} ($clerk_secret, reserved + write-only; the value was never echoed)`);
|
|
5770
5770
|
}
|
|
5771
|
-
function scrubValue(
|
|
5772
|
-
return redactSecrets(
|
|
5771
|
+
function scrubValue(text4, value2) {
|
|
5772
|
+
return redactSecrets(text4).split(value2).join("[value redacted]");
|
|
5773
5773
|
}
|
|
5774
5774
|
async function resolveVaultWrite(options) {
|
|
5775
5775
|
const out = options.stdout ?? console;
|
|
@@ -6086,8 +6086,8 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
|
|
|
6086
6086
|
harnesses: installations
|
|
6087
6087
|
};
|
|
6088
6088
|
}
|
|
6089
|
-
function pathsUnder(root,
|
|
6090
|
-
return [...
|
|
6089
|
+
function pathsUnder(root, paths2) {
|
|
6090
|
+
return [...paths2].map((path) => (0, import_node_path16.relative)(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${import_node_path16.sep}`) && !(0, import_node_path16.isAbsolute)(path)).sort();
|
|
6091
6091
|
}
|
|
6092
6092
|
function normalizeHarnesses(values, global) {
|
|
6093
6093
|
const requested = values?.length ? values : ["claude"];
|
|
@@ -6557,17 +6557,17 @@ var init_cli_project = __esm({
|
|
|
6557
6557
|
}
|
|
6558
6558
|
});
|
|
6559
6559
|
|
|
6560
|
-
// ../harness/dist/chunk-
|
|
6560
|
+
// ../harness/dist/chunk-RXNHCGWE.js
|
|
6561
6561
|
var HARNESS_PROTOCOL_VERSION;
|
|
6562
|
-
var
|
|
6563
|
-
"../harness/dist/chunk-
|
|
6562
|
+
var init_chunk_RXNHCGWE = __esm({
|
|
6563
|
+
"../harness/dist/chunk-RXNHCGWE.js"() {
|
|
6564
6564
|
"use strict";
|
|
6565
6565
|
init_cjs_shims();
|
|
6566
6566
|
HARNESS_PROTOCOL_VERSION = 1;
|
|
6567
6567
|
}
|
|
6568
6568
|
});
|
|
6569
6569
|
|
|
6570
|
-
// ../harness/dist/chunk-
|
|
6570
|
+
// ../harness/dist/chunk-CR6RE3A2.js
|
|
6571
6571
|
function assertPinnedImage(image) {
|
|
6572
6572
|
if (!DIGEST_IMAGE.test(image)) throw new TypeError("container image must be pinned by sha256 digest");
|
|
6573
6573
|
}
|
|
@@ -6795,12 +6795,12 @@ async function gitSourceFiles(sourceDir, maxFiles, maxBytes) {
|
|
|
6795
6795
|
});
|
|
6796
6796
|
if (outputBytes > 8 * 1024 * 1024) throw new Error("git file inventory exceeds 8 MiB");
|
|
6797
6797
|
if (code !== 0) throw new Error(`git file inventory failed: ${Buffer.concat(stderr2).toString("utf8").slice(0, 1e3)}`);
|
|
6798
|
-
const
|
|
6799
|
-
if (
|
|
6798
|
+
const paths2 = Buffer.concat(stdout).toString("utf8").split("\0").filter(Boolean).sort();
|
|
6799
|
+
if (paths2.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
|
|
6800
6800
|
const root = (0, import_path4.resolve)(sourceDir);
|
|
6801
6801
|
const files = [];
|
|
6802
6802
|
let bytes = 0;
|
|
6803
|
-
for (const relativePath of
|
|
6803
|
+
for (const relativePath of paths2) {
|
|
6804
6804
|
if (!allowedWorkspacePath(relativePath)) continue;
|
|
6805
6805
|
const source = (0, import_path4.resolve)(root, relativePath);
|
|
6806
6806
|
if (!source.startsWith(`${root}${import_path4.sep}`)) throw new TypeError("git file path escapes workspace");
|
|
@@ -6916,8 +6916,8 @@ async function stageWorkspacePair(baselineSource, workspaceSource, options = {})
|
|
|
6916
6916
|
}
|
|
6917
6917
|
}
|
|
6918
6918
|
var import_child_process, import_fs, import_promises2, import_path, import_process, import_promises3, import_os, import_path2, import_child_process2, import_path3, import_promises4, import_os2, import_path4, import_child_process3, DIGEST_IMAGE, SKIP_WORKSPACE_DIRS, SECRET_WORKSPACE_FILE;
|
|
6919
|
-
var
|
|
6920
|
-
"../harness/dist/chunk-
|
|
6919
|
+
var init_chunk_CR6RE3A2 = __esm({
|
|
6920
|
+
"../harness/dist/chunk-CR6RE3A2.js"() {
|
|
6921
6921
|
"use strict";
|
|
6922
6922
|
init_cjs_shims();
|
|
6923
6923
|
import_child_process = require("child_process");
|
|
@@ -7276,13 +7276,13 @@ function validateSnapshot(snapshot, limits) {
|
|
|
7276
7276
|
if (!Number.isSafeInteger(limits.maximumFiles) || limits.maximumFiles < 1 || !Number.isSafeInteger(limits.maximumBytes) || limits.maximumBytes < 1 || snapshot.files.length > limits.maximumFiles) {
|
|
7277
7277
|
throw new CamelError("limit_exceeded", "Code snapshot exceeds its registered limits.");
|
|
7278
7278
|
}
|
|
7279
|
-
const
|
|
7279
|
+
const paths2 = /* @__PURE__ */ new Set();
|
|
7280
7280
|
let bytes = 0;
|
|
7281
7281
|
for (const file of snapshot.files) {
|
|
7282
|
-
if (!file.path || file.path.startsWith("/") || file.path.includes("\\") || file.path.split("/").some((part) => !part || part === "." || part === "..") ||
|
|
7282
|
+
if (!file.path || file.path.startsWith("/") || file.path.includes("\\") || file.path.split("/").some((part) => !part || part === "." || part === "..") || paths2.has(file.path) || typeof file.content !== "string") {
|
|
7283
7283
|
throw new CamelError("state_conflict", "Code snapshot contains an invalid or duplicate path.");
|
|
7284
7284
|
}
|
|
7285
|
-
|
|
7285
|
+
paths2.add(file.path);
|
|
7286
7286
|
bytes += utf8Length(file.path) + utf8Length(file.content);
|
|
7287
7287
|
}
|
|
7288
7288
|
if (bytes > limits.maximumBytes) {
|
|
@@ -7433,8 +7433,8 @@ function boundedInteger(value2, spec) {
|
|
|
7433
7433
|
}
|
|
7434
7434
|
function boundedNumber(value2, spec) {
|
|
7435
7435
|
if (spec.kind !== "finite_number" || typeof value2 !== "number" || !Number.isFinite(value2) || value2 < spec.minimum || value2 > spec.maximum) throw new CamelError("conversion_rejected", "Finite-number conversion rejected the structured value.");
|
|
7436
|
-
const
|
|
7437
|
-
if (/e/i.test(
|
|
7436
|
+
const text4 = String(value2);
|
|
7437
|
+
if (/e/i.test(text4) || (text4.split(".")[1]?.length ?? 0) > spec.maximumDecimalPlaces) throw new CamelError("conversion_rejected", "Finite-number conversion rejected a non-canonical decimal.");
|
|
7438
7438
|
return value2;
|
|
7439
7439
|
}
|
|
7440
7440
|
function enumMember(value2, spec) {
|
|
@@ -7617,8 +7617,8 @@ function validateUnsafeSelector(path, value2, tool) {
|
|
|
7617
7617
|
return void 0;
|
|
7618
7618
|
}
|
|
7619
7619
|
function looksLikeDestination(value2) {
|
|
7620
|
-
const
|
|
7621
|
-
return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(
|
|
7620
|
+
const text4 = value2.trim();
|
|
7621
|
+
return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text4) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text4);
|
|
7622
7622
|
}
|
|
7623
7623
|
var init_policy = __esm({
|
|
7624
7624
|
"../camel/dist/policy.js"() {
|
|
@@ -7792,9 +7792,9 @@ async function extractImports(builder, input) {
|
|
|
7792
7792
|
const sources = input.paths.filter(isSourcePath);
|
|
7793
7793
|
const known = new Set(sources);
|
|
7794
7794
|
for (const path of sources) {
|
|
7795
|
-
let
|
|
7795
|
+
let text4;
|
|
7796
7796
|
try {
|
|
7797
|
-
|
|
7797
|
+
text4 = await input.read(path);
|
|
7798
7798
|
} catch {
|
|
7799
7799
|
continue;
|
|
7800
7800
|
}
|
|
@@ -7802,13 +7802,13 @@ async function extractImports(builder, input) {
|
|
|
7802
7802
|
const file = builder.node(FILE, path, pkg ? { pkg } : void 0);
|
|
7803
7803
|
if (pkg) builder.edge(builder.node(PACKAGE, pkg), CONTAINS, file);
|
|
7804
7804
|
const specifiers = /* @__PURE__ */ new Set();
|
|
7805
|
-
for (const match of
|
|
7806
|
-
for (const match of
|
|
7805
|
+
for (const match of text4.matchAll(IMPORT_FROM)) specifiers.add(match[1]);
|
|
7806
|
+
for (const match of text4.matchAll(BARE_IMPORT)) specifiers.add(match[1]);
|
|
7807
7807
|
for (const specifier of specifiers) {
|
|
7808
7808
|
const resolved = resolveImport(path, specifier, known);
|
|
7809
7809
|
if (resolved) builder.edge(file, IMPORTS, nodeId(FILE, resolved));
|
|
7810
7810
|
}
|
|
7811
|
-
for (const name of exportedNames(
|
|
7811
|
+
for (const name of exportedNames(text4)) {
|
|
7812
7812
|
builder.edge(file, EXPORTS, builder.node(SYMBOL, name));
|
|
7813
7813
|
}
|
|
7814
7814
|
}
|
|
@@ -7821,16 +7821,16 @@ async function extractData(builder, input) {
|
|
|
7821
7821
|
};
|
|
7822
7822
|
for (const path of input.paths) {
|
|
7823
7823
|
if (!SOURCE_FILE.test(path) || input.ignore?.(path)) continue;
|
|
7824
|
-
let
|
|
7824
|
+
let text4;
|
|
7825
7825
|
try {
|
|
7826
|
-
|
|
7826
|
+
text4 = await input.read(path);
|
|
7827
7827
|
} catch {
|
|
7828
7828
|
continue;
|
|
7829
7829
|
}
|
|
7830
|
-
for (const statement of
|
|
7830
|
+
for (const statement of text4.matchAll(STATEMENT)) {
|
|
7831
7831
|
const verb = statement[1].toUpperCase().replace(/\s+/g, " ");
|
|
7832
7832
|
const start = statement.index ?? 0;
|
|
7833
|
-
const rest =
|
|
7833
|
+
const rest = text4.slice(start + statement[0].length, start + STATEMENT_WINDOW);
|
|
7834
7834
|
if (verb === "SELECT") {
|
|
7835
7835
|
for (const read3 of rest.matchAll(READ_TABLES)) touch(path, read3[1].toLowerCase(), TABLE, READS);
|
|
7836
7836
|
continue;
|
|
@@ -7846,16 +7846,16 @@ async function extractData(builder, input) {
|
|
|
7846
7846
|
for (const read3 of rest.matchAll(READ_TABLES)) touch(path, read3[1].toLowerCase(), TABLE, READS);
|
|
7847
7847
|
}
|
|
7848
7848
|
}
|
|
7849
|
-
for (const match of
|
|
7850
|
-
touch(path, `${match[1]}.${match[2]}`, NAMESPACE, accessFor(
|
|
7849
|
+
for (const match of text4.matchAll(NS_CONST)) {
|
|
7850
|
+
touch(path, `${match[1]}.${match[2]}`, NAMESPACE, accessFor(text4, match.index ?? 0));
|
|
7851
7851
|
}
|
|
7852
|
-
for (const match of
|
|
7853
|
-
touch(path, match[1], NAMESPACE, accessFor(
|
|
7852
|
+
for (const match of text4.matchAll(NS_LITERAL)) {
|
|
7853
|
+
touch(path, match[1], NAMESPACE, accessFor(text4, match.index ?? 0));
|
|
7854
7854
|
}
|
|
7855
7855
|
}
|
|
7856
7856
|
}
|
|
7857
|
-
function accessFor(
|
|
7858
|
-
const window =
|
|
7857
|
+
function accessFor(text4, index) {
|
|
7858
|
+
const window = text4.slice(Math.max(0, index - 160), index + 40);
|
|
7859
7859
|
return /\b(?:transact|update|delete|create|insert|Ops)\b/.test(window) ? WRITES : READS;
|
|
7860
7860
|
}
|
|
7861
7861
|
async function buildCodeGraph(input) {
|
|
@@ -7915,7 +7915,7 @@ var init_code2 = __esm({
|
|
|
7915
7915
|
}
|
|
7916
7916
|
});
|
|
7917
7917
|
|
|
7918
|
-
// ../harness/dist/chunk-
|
|
7918
|
+
// ../harness/dist/chunk-ISR434K7.js
|
|
7919
7919
|
async function digestStagedWorkspace(root, limits) {
|
|
7920
7920
|
const files = [];
|
|
7921
7921
|
const walk = async (directory) => {
|
|
@@ -8195,7 +8195,7 @@ function validateCodePatch(rawPatch, maxBytes) {
|
|
|
8195
8195
|
if (FORBIDDEN.test(patch2) || /(?:old|new)(?: file)? mode 120000/.test(patch2)) {
|
|
8196
8196
|
throw new TypeError("patch uses a forbidden binary, link, mode, rename, or copy operation");
|
|
8197
8197
|
}
|
|
8198
|
-
const
|
|
8198
|
+
const paths2 = [];
|
|
8199
8199
|
const lines = patch2.split("\n");
|
|
8200
8200
|
for (let index = 0; index < lines.length; index += 1) {
|
|
8201
8201
|
const line2 = lines[index];
|
|
@@ -8211,10 +8211,10 @@ function validateCodePatch(rawPatch, maxBytes) {
|
|
|
8211
8211
|
if (!validHeaderPath(oldPath, path, "a") || !validHeaderPath(newPath, path, "b")) {
|
|
8212
8212
|
throw new TypeError("patch file headers do not match the declared path");
|
|
8213
8213
|
}
|
|
8214
|
-
|
|
8214
|
+
paths2.push(path);
|
|
8215
8215
|
}
|
|
8216
|
-
if (!
|
|
8217
|
-
return
|
|
8216
|
+
if (!paths2.length || new Set(paths2).size !== paths2.length) throw new TypeError("patch has no diffs or repeats a path");
|
|
8217
|
+
return paths2;
|
|
8218
8218
|
}
|
|
8219
8219
|
function validHeaderPath(value2, path, prefix) {
|
|
8220
8220
|
return value2 === "/dev/null" || value2 === `${prefix}/${path}`;
|
|
@@ -8239,11 +8239,11 @@ function describePatchFailure(patch2, detail) {
|
|
|
8239
8239
|
const hint = hunks.length > 0 && contextless ? " A hunk has no context lines; include at least one unchanged line above or below each change." : "";
|
|
8240
8240
|
return `patch did not apply: ${detail}${hint}`;
|
|
8241
8241
|
}
|
|
8242
|
-
async function applyCodePatch(workspaceDir, rawPatch,
|
|
8242
|
+
async function applyCodePatch(workspaceDir, rawPatch, paths2) {
|
|
8243
8243
|
const patch2 = stripPatchEnvelope(rawPatch);
|
|
8244
8244
|
await gitApply(workspaceDir, patch2, true);
|
|
8245
8245
|
await gitApply(workspaceDir, patch2, false);
|
|
8246
|
-
for (const path of
|
|
8246
|
+
for (const path of paths2) {
|
|
8247
8247
|
try {
|
|
8248
8248
|
const info = await (0, import_promises6.lstat)(resolveCodePath(workspaceDir, path));
|
|
8249
8249
|
if (info.isSymbolicLink() || !info.isFile() && !info.isDirectory()) {
|
|
@@ -8265,8 +8265,8 @@ function gitApply(cwd, patch2, check) {
|
|
|
8265
8265
|
});
|
|
8266
8266
|
let stderr2 = "";
|
|
8267
8267
|
child.stderr.setEncoding("utf8");
|
|
8268
|
-
child.stderr.on("data", (
|
|
8269
|
-
if (stderr2.length < 4e3) stderr2 +=
|
|
8268
|
+
child.stderr.on("data", (text22) => {
|
|
8269
|
+
if (stderr2.length < 4e3) stderr2 += text22.slice(0, 4e3);
|
|
8270
8270
|
});
|
|
8271
8271
|
child.once("error", reject);
|
|
8272
8272
|
child.once("exit", (code) => code === 0 ? accept() : reject(new TypeError(describePatchFailure(patch2, stderr2.trim().slice(0, 500)))));
|
|
@@ -8290,8 +8290,8 @@ async function restoreCodeWorkspaceCheckpoint(input) {
|
|
|
8290
8290
|
const workspace = await stageWorkspace(input.trustedBaseDir, input.stage);
|
|
8291
8291
|
try {
|
|
8292
8292
|
if (checkpoint.patch) {
|
|
8293
|
-
const
|
|
8294
|
-
await applyCodePatch(workspace.workspaceDir, checkpoint.patch,
|
|
8293
|
+
const paths2 = validateCodePatch(checkpoint.patch, 256 * 1024);
|
|
8294
|
+
await applyCodePatch(workspace.workspaceDir, checkpoint.patch, paths2);
|
|
8295
8295
|
}
|
|
8296
8296
|
return { workspace, checkpoint };
|
|
8297
8297
|
} catch (error) {
|
|
@@ -8432,8 +8432,8 @@ async function verifyCodeCandidate(input) {
|
|
|
8432
8432
|
try {
|
|
8433
8433
|
const baseDigest = await digestStagedWorkspace(staged.workspaceDir, limits);
|
|
8434
8434
|
if (baseDigest !== input.trustedBaseDigest) throw new TypeError("trusted base does not match its registered digest");
|
|
8435
|
-
const
|
|
8436
|
-
await applyCodePatch(staged.workspaceDir, input.candidatePatch,
|
|
8435
|
+
const paths2 = validateCodePatch(input.candidatePatch, policy.maximumPatchBytes);
|
|
8436
|
+
await applyCodePatch(staged.workspaceDir, input.candidatePatch, paths2);
|
|
8437
8437
|
const sourceDigest = await digestStagedWorkspace(staged.workspaceDir, limits);
|
|
8438
8438
|
const policyDigest = digestPolicy(policy);
|
|
8439
8439
|
const patchDigest = digestBytes(input.candidatePatch);
|
|
@@ -8442,7 +8442,7 @@ async function verifyCodeCandidate(input) {
|
|
|
8442
8442
|
trustedBaseDigest: input.trustedBaseDigest,
|
|
8443
8443
|
patchDigest
|
|
8444
8444
|
});
|
|
8445
|
-
const changedTests = changedTestPaths(
|
|
8445
|
+
const changedTests = changedTestPaths(paths2, policy);
|
|
8446
8446
|
if (changedTests.length > policy.maximumChangedTests) throw new TypeError("candidate changes too many test files");
|
|
8447
8447
|
const recipes = [];
|
|
8448
8448
|
const logs = [];
|
|
@@ -8509,8 +8509,8 @@ function validate2(input) {
|
|
|
8509
8509
|
}
|
|
8510
8510
|
return result;
|
|
8511
8511
|
}
|
|
8512
|
-
function changedTestPaths(
|
|
8513
|
-
return
|
|
8512
|
+
function changedTestPaths(paths2, policy) {
|
|
8513
|
+
return paths2.filter((path) => policy.testPathSuffixes.some((suffix) => path.endsWith(suffix)) || policy.testPathPrefixes.some((prefix) => path.startsWith(prefix) || path.includes(`/${prefix}`))).sort();
|
|
8514
8514
|
}
|
|
8515
8515
|
function recipeReceipt(recipe2, result, artifacts) {
|
|
8516
8516
|
const status = result.timedOut ? "timed_out" : result.outputLimitExceeded ? "output_limited" : result.exitCode === 0 && artifacts.every((item) => item.status === "verified") ? "passed" : "failed";
|
|
@@ -9143,7 +9143,7 @@ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = regi
|
|
|
9143
9143
|
files(root) {
|
|
9144
9144
|
const existing = cache2.get(root);
|
|
9145
9145
|
if (existing) return existing;
|
|
9146
|
-
const pending = enumerate(root, limit).then((
|
|
9146
|
+
const pending = enumerate(root, limit).then((paths2) => Object.freeze(paths2));
|
|
9147
9147
|
cache2.set(root, pending);
|
|
9148
9148
|
void pending.catch(() => {
|
|
9149
9149
|
if (cache2.get(root) === pending) cache2.delete(root);
|
|
@@ -9156,7 +9156,7 @@ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = regi
|
|
|
9156
9156
|
};
|
|
9157
9157
|
}
|
|
9158
9158
|
async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
|
|
9159
|
-
const
|
|
9159
|
+
const paths2 = [];
|
|
9160
9160
|
const walk = async (directory) => {
|
|
9161
9161
|
for (const entry of await (0, import_promises9.readdir)(directory, { withFileTypes: true })) {
|
|
9162
9162
|
if (SKIP_WORKSPACE_DIRS.has(entry.name)) continue;
|
|
@@ -9170,26 +9170,26 @@ async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
|
|
|
9170
9170
|
} catch {
|
|
9171
9171
|
continue;
|
|
9172
9172
|
}
|
|
9173
|
-
|
|
9174
|
-
if (
|
|
9173
|
+
paths2.push(path);
|
|
9174
|
+
if (paths2.length > limit) throw new TypeError("workspace file registry exceeds its bound");
|
|
9175
9175
|
}
|
|
9176
9176
|
}
|
|
9177
9177
|
};
|
|
9178
9178
|
await walk((0, import_path9.resolve)(root));
|
|
9179
|
-
return
|
|
9179
|
+
return paths2.sort();
|
|
9180
9180
|
}
|
|
9181
|
-
function listWorkspace(
|
|
9181
|
+
function listWorkspace(paths2, options = {}) {
|
|
9182
9182
|
const max = options.maxEntries ?? 1e3;
|
|
9183
9183
|
const prefix = options.prefix?.replace(/\/+$/, "");
|
|
9184
|
-
const scoped = prefix ?
|
|
9184
|
+
const scoped = prefix ? paths2.filter((path) => path === prefix || path.startsWith(`${prefix}/`)) : [...paths2];
|
|
9185
9185
|
return scoped.slice(0, max);
|
|
9186
9186
|
}
|
|
9187
|
-
async function searchWorkspace(root,
|
|
9187
|
+
async function searchWorkspace(root, paths2, options) {
|
|
9188
9188
|
options.signal?.throwIfAborted();
|
|
9189
9189
|
if (!options.query) throw new TypeError("search query must be a non-empty string");
|
|
9190
9190
|
const maxResults = options.maxResults ?? DEFAULT_MAX_RESULTS;
|
|
9191
9191
|
const maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES;
|
|
9192
|
-
const scoped = listWorkspace(
|
|
9192
|
+
const scoped = listWorkspace(paths2, { ...options.prefix ? { prefix: options.prefix } : {}, maxEntries: paths2.length });
|
|
9193
9193
|
if (scoped.length === 0) return [];
|
|
9194
9194
|
try {
|
|
9195
9195
|
return await nativeSearch(root, scoped, { ...options, maxResults, maxFileBytes });
|
|
@@ -9198,11 +9198,11 @@ async function searchWorkspace(root, paths, options) {
|
|
|
9198
9198
|
return fallbackSearch(root, scoped, { ...options, maxResults, maxFileBytes });
|
|
9199
9199
|
}
|
|
9200
9200
|
}
|
|
9201
|
-
async function nativeSearch(root,
|
|
9201
|
+
async function nativeSearch(root, paths2, options) {
|
|
9202
9202
|
const batches = [];
|
|
9203
9203
|
let batch = [];
|
|
9204
9204
|
let bytes = 0;
|
|
9205
|
-
for (const path of
|
|
9205
|
+
for (const path of paths2) {
|
|
9206
9206
|
const size = Buffer.byteLength(path) + 1;
|
|
9207
9207
|
if (batch.length > 0 && bytes + size > MAX_NATIVE_ARG_BYTES) {
|
|
9208
9208
|
batches.push(batch);
|
|
@@ -9221,7 +9221,7 @@ async function nativeSearch(root, paths, options) {
|
|
|
9221
9221
|
}
|
|
9222
9222
|
return matches;
|
|
9223
9223
|
}
|
|
9224
|
-
function nativeSearchBatch(root,
|
|
9224
|
+
function nativeSearchBatch(root, paths2, options, remaining) {
|
|
9225
9225
|
return new Promise((resolveMatches, reject) => {
|
|
9226
9226
|
const args = [
|
|
9227
9227
|
"--fixed-strings",
|
|
@@ -9234,7 +9234,7 @@ function nativeSearchBatch(root, paths, options, remaining) {
|
|
|
9234
9234
|
options.caseSensitive === false ? "--ignore-case" : "--case-sensitive",
|
|
9235
9235
|
"--",
|
|
9236
9236
|
options.query,
|
|
9237
|
-
...
|
|
9237
|
+
...paths2
|
|
9238
9238
|
];
|
|
9239
9239
|
const child = (0, import_child_process6.spawn)("rg", args, {
|
|
9240
9240
|
cwd: root,
|
|
@@ -9380,16 +9380,16 @@ function createCodePolicyGate(options) {
|
|
|
9380
9380
|
}
|
|
9381
9381
|
};
|
|
9382
9382
|
}
|
|
9383
|
-
function directoryPrefixes(
|
|
9383
|
+
function directoryPrefixes(paths2) {
|
|
9384
9384
|
const prefixes = /* @__PURE__ */ new Set(["."]);
|
|
9385
|
-
for (const path of
|
|
9385
|
+
for (const path of paths2) {
|
|
9386
9386
|
const parts = path.split("/");
|
|
9387
9387
|
for (let index = 1; index < parts.length; index += 1) prefixes.add(parts.slice(0, index).join("/"));
|
|
9388
9388
|
}
|
|
9389
9389
|
return [...prefixes].sort();
|
|
9390
9390
|
}
|
|
9391
|
-
async function safePrefix(base,
|
|
9392
|
-
const prefixes = directoryPrefixes(
|
|
9391
|
+
async function safePrefix(base, paths2, prefix) {
|
|
9392
|
+
const prefixes = directoryPrefixes(paths2);
|
|
9393
9393
|
const conversions = await conversionRegistry(
|
|
9394
9394
|
[await registeredPolicy("code.prefix.v1", "code.prefixes.v1", prefixes)],
|
|
9395
9395
|
{ "code.prefixes.v1": prefixes }
|
|
@@ -9500,7 +9500,7 @@ function optionalInteger(value2) {
|
|
|
9500
9500
|
function response(request3, ok, content2, details) {
|
|
9501
9501
|
return { requestId: request3.requestId, ok, content: content2, ...details ? { details } : {} };
|
|
9502
9502
|
}
|
|
9503
|
-
function workspaceGraphs(workspaceDir,
|
|
9503
|
+
function workspaceGraphs(workspaceDir, paths2) {
|
|
9504
9504
|
const existing = cache.get(workspaceDir);
|
|
9505
9505
|
if (existing) return existing;
|
|
9506
9506
|
const read22 = (path) => (0, import_promises11.readFile)((0, import_path10.join)(workspaceDir, path), "utf8");
|
|
@@ -9508,7 +9508,7 @@ function workspaceGraphs(workspaceDir, paths) {
|
|
|
9508
9508
|
// No knownTables: a staged workspace may not carry migrations, and a filter
|
|
9509
9509
|
// that silently drops every table is worse than an unfiltered one. Callers
|
|
9510
9510
|
// with ground truth should build the graph themselves.
|
|
9511
|
-
graph: await buildCodeGraph({ paths, read: read22, data: { ignore: (path) => path.includes(".generated.") } })
|
|
9511
|
+
graph: await buildCodeGraph({ paths: paths2, read: read22, data: { ignore: (path) => path.includes(".generated.") } })
|
|
9512
9512
|
}))();
|
|
9513
9513
|
cache.set(workspaceDir, built);
|
|
9514
9514
|
return built;
|
|
@@ -9561,11 +9561,11 @@ async function read(context, request3, options, policy, registry) {
|
|
|
9561
9561
|
if (endLine < startLine || endLine - startLine + 1 > (options.maxReadLines ?? 2e3)) {
|
|
9562
9562
|
throw new TypeError("requested line range exceeds its bound");
|
|
9563
9563
|
}
|
|
9564
|
-
const
|
|
9565
|
-
if (!
|
|
9564
|
+
const paths2 = await registry.files(context.workspaceDir);
|
|
9565
|
+
if (!paths2.includes(path)) {
|
|
9566
9566
|
throw new TypeError(`no such file in the staged workspace: "${path}". Use sandbox.overview, sandbox.where_is or sandbox.search to find the correct path.`);
|
|
9567
9567
|
}
|
|
9568
|
-
const allowed = await policy.read(policyContext(context, request3, options, { paths, path, startLine, endLine }));
|
|
9568
|
+
const allowed = await policy.read(policyContext(context, request3, options, { paths: paths2, path, startLine, endLine }));
|
|
9569
9569
|
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
9570
9570
|
const target = resolveCodePath(context.workspaceDir, path);
|
|
9571
9571
|
const info = await (0, import_promises10.stat)(target);
|
|
@@ -9587,16 +9587,16 @@ async function list(context, request3, options, policy, registry) {
|
|
|
9587
9587
|
const prefix = typeof raw === "string" && raw.length > 0 ? raw : void 0;
|
|
9588
9588
|
const maxEntries = optionalInteger(request3.input.maxEntries) ?? 1e3;
|
|
9589
9589
|
if (maxEntries > 5e3) throw new TypeError("maxEntries exceeds its bound");
|
|
9590
|
-
const
|
|
9591
|
-
const allowed = await policy.list(policyContext(context, request3, options, { paths, ...prefix ? { prefix } : {} }));
|
|
9590
|
+
const paths2 = await registry.files(context.workspaceDir);
|
|
9591
|
+
const allowed = await policy.list(policyContext(context, request3, options, { paths: paths2, ...prefix ? { prefix } : {} }));
|
|
9592
9592
|
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
9593
|
-
const entries = listWorkspace(
|
|
9593
|
+
const entries = listWorkspace(paths2, { ...prefix ? { prefix } : {}, maxEntries });
|
|
9594
9594
|
if (!entries.length) {
|
|
9595
9595
|
return response(request3, true, prefix ? `No files under "${prefix}".` : "Workspace is empty.", { count: 0 });
|
|
9596
9596
|
}
|
|
9597
|
-
const truncated = entries.length <
|
|
9598
|
-
const hint = !prefix &&
|
|
9599
|
-
\u2026 ${
|
|
9597
|
+
const truncated = entries.length < paths2.length && entries.length === maxEntries;
|
|
9598
|
+
const hint = !prefix && paths2.length > 500 ? `
|
|
9599
|
+
\u2026 ${paths2.length} files total. sandbox.overview is far cheaper for orientation; use a prefix here once you know the area.` : "";
|
|
9600
9600
|
return response(
|
|
9601
9601
|
request3,
|
|
9602
9602
|
true,
|
|
@@ -9614,10 +9614,10 @@ async function search(context, request3, options, policy, registry) {
|
|
|
9614
9614
|
const maxResults = optionalInteger(request3.input.maxResults) ?? 100;
|
|
9615
9615
|
if (maxResults > 500) throw new TypeError("maxResults exceeds its bound");
|
|
9616
9616
|
const caseSensitive = request3.input.caseSensitive === void 0 ? true : request3.input.caseSensitive === true;
|
|
9617
|
-
const
|
|
9618
|
-
const allowed = await policy.search(policyContext(context, request3, options, { paths, query, ...prefix ? { prefix } : {} }));
|
|
9617
|
+
const paths2 = await registry.files(context.workspaceDir);
|
|
9618
|
+
const allowed = await policy.search(policyContext(context, request3, options, { paths: paths2, query, ...prefix ? { prefix } : {} }));
|
|
9619
9619
|
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
9620
|
-
const matches = await searchWorkspace(context.workspaceDir,
|
|
9620
|
+
const matches = await searchWorkspace(context.workspaceDir, paths2, {
|
|
9621
9621
|
query,
|
|
9622
9622
|
maxResults,
|
|
9623
9623
|
caseSensitive,
|
|
@@ -9639,8 +9639,8 @@ async function graphQuery(context, request3, options, policy, registry) {
|
|
|
9639
9639
|
selector: query
|
|
9640
9640
|
}));
|
|
9641
9641
|
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
9642
|
-
const
|
|
9643
|
-
const graphs = await workspaceGraphs(context.workspaceDir,
|
|
9642
|
+
const paths2 = await registry.files(context.workspaceDir);
|
|
9643
|
+
const graphs = await workspaceGraphs(context.workspaceDir, paths2);
|
|
9644
9644
|
if (request3.tool === "sandbox.overview") {
|
|
9645
9645
|
return response(request3, true, renderOverview(graphs, query || void 0));
|
|
9646
9646
|
}
|
|
@@ -9702,16 +9702,16 @@ function toolFailureMessage(reason) {
|
|
|
9702
9702
|
async function patch(context, request3, options, policy, registry) {
|
|
9703
9703
|
exactKeys(request3.input, ["patch"]);
|
|
9704
9704
|
const value2 = stringField(request3.input, "patch");
|
|
9705
|
-
const
|
|
9706
|
-
if (
|
|
9705
|
+
const paths2 = validateCodePatch(value2, options.maxPatchBytes ?? 256 * 1024);
|
|
9706
|
+
if (paths2.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
|
|
9707
9707
|
throw new TypeError("patch targets a read-only reference source");
|
|
9708
9708
|
}
|
|
9709
9709
|
const allowed = await policy.patch(policyContext(context, request3, options, { patch: value2 }));
|
|
9710
9710
|
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
9711
|
-
await applyCodePatch(context.workspaceDir, value2,
|
|
9711
|
+
await applyCodePatch(context.workspaceDir, value2, paths2);
|
|
9712
9712
|
registry.invalidate(context.workspaceDir);
|
|
9713
9713
|
forgetWorkspaceGraphs(context.workspaceDir);
|
|
9714
|
-
return response(request3, true, `Applied patch to ${
|
|
9714
|
+
return response(request3, true, `Applied patch to ${paths2.length} file(s).`, { paths: paths2 });
|
|
9715
9715
|
}
|
|
9716
9716
|
async function recipe(context, request3, options, recipes, policy) {
|
|
9717
9717
|
exactKeys(request3.input, ["recipeId"]);
|
|
@@ -10058,16 +10058,107 @@ async function startGoalPursuit(input) {
|
|
|
10058
10058
|
async function appendCodeRuntimeEvent(control, command, event, refs) {
|
|
10059
10059
|
const eventId = `${command.commandId.slice(0, 45)}:${refs.length + 1}`;
|
|
10060
10060
|
refs.push(eventId);
|
|
10061
|
-
const
|
|
10061
|
+
const attributed = { ...event, interactionId: command.commandId };
|
|
10062
|
+
const bounded = attributed.type === "message" ? { ...attributed, body: attributed.body.trim().slice(0, 2e4) || `${attributed.actor} event` } : attributed;
|
|
10062
10063
|
await control.appendSessionEvent(command.sessionId, eventId, bounded);
|
|
10063
10064
|
}
|
|
10064
|
-
|
|
10065
|
-
|
|
10066
|
-
|
|
10065
|
+
function patchStats(value2) {
|
|
10066
|
+
if (typeof value2 !== "string") return {};
|
|
10067
|
+
let additions = 0;
|
|
10068
|
+
let deletions = 0;
|
|
10069
|
+
for (const line2 of value2.slice(0, 262144).split("\n")) {
|
|
10070
|
+
if (line2.startsWith("+++") || line2.startsWith("---")) continue;
|
|
10071
|
+
if (line2.startsWith("+")) additions += 1;
|
|
10072
|
+
else if (line2.startsWith("-")) deletions += 1;
|
|
10073
|
+
}
|
|
10074
|
+
return { ...additions ? { additions } : {}, ...deletions ? { deletions } : {} };
|
|
10075
|
+
}
|
|
10076
|
+
function searchResults(value2) {
|
|
10077
|
+
if (typeof value2 !== "string") return void 0;
|
|
10078
|
+
const results = value2.split("\n").flatMap((line2) => {
|
|
10079
|
+
const match = /^([^:\n]{1,1024}):(\d+):\s?(.*)$/.exec(line2);
|
|
10080
|
+
if (!match) return [];
|
|
10081
|
+
const lineNumber = Number(match[2]);
|
|
10082
|
+
const itemText = text3(match[3], 240);
|
|
10083
|
+
if (!Number.isSafeInteger(lineNumber) || lineNumber < 1) return [];
|
|
10084
|
+
return [{ path: match[1], line: lineNumber, ...itemText ? { text: itemText } : {} }];
|
|
10085
|
+
}).slice(0, 5);
|
|
10086
|
+
return results.length ? results : void 0;
|
|
10087
|
+
}
|
|
10088
|
+
function codeToolRequestPresentation(request3) {
|
|
10089
|
+
const input = request3.input;
|
|
10090
|
+
if (request3.tool === "sandbox.read") {
|
|
10091
|
+
const path = text3(input.path, 1024);
|
|
10092
|
+
if (!path) return void 0;
|
|
10093
|
+
const startLine = integer2(input.startLine);
|
|
10094
|
+
const endLine = integer2(input.endLine);
|
|
10095
|
+
return { kind: "read", path, ...startLine ? { startLine } : {}, ...endLine ? { endLine } : {} };
|
|
10096
|
+
}
|
|
10097
|
+
if (request3.tool === "sandbox.list") {
|
|
10098
|
+
const scope = text3(input.prefix, 1024);
|
|
10099
|
+
return { kind: "list", ...scope ? { scope } : {} };
|
|
10100
|
+
}
|
|
10101
|
+
if (request3.tool === "sandbox.search" || request3.tool === "sandbox.overview" || request3.tool === "sandbox.where_is" || request3.tool === "sandbox.who_imports" || request3.tool === "sandbox.who_touches") {
|
|
10102
|
+
const query = text3(input.query, 512);
|
|
10103
|
+
const scope = request3.tool === "sandbox.search" ? text3(input.prefix, 1024) : void 0;
|
|
10104
|
+
if (request3.tool === "sandbox.search" && !query) return void 0;
|
|
10105
|
+
return { kind: "query", ...query ? { query } : {}, ...scope ? { scope } : {} };
|
|
10106
|
+
}
|
|
10107
|
+
if (request3.tool === "sandbox.apply_patch") {
|
|
10108
|
+
return { kind: "patch", ...patchStats(input.patch) };
|
|
10109
|
+
}
|
|
10110
|
+
const recipeId = text3(input.recipeId, 120);
|
|
10111
|
+
return recipeId ? { kind: "recipe", recipeId } : void 0;
|
|
10112
|
+
}
|
|
10113
|
+
function codeToolResultPresentation(request3, response2) {
|
|
10114
|
+
const started = codeToolRequestPresentation(request3);
|
|
10115
|
+
if (!started || !response2.ok) return started;
|
|
10116
|
+
const details = record32(response2.details);
|
|
10117
|
+
if (started.kind === "read") {
|
|
10118
|
+
return {
|
|
10119
|
+
...started,
|
|
10120
|
+
...integer2(details?.startLine) ? { startLine: integer2(details?.startLine) } : {},
|
|
10121
|
+
...integer2(details?.endLine) ? { endLine: integer2(details?.endLine) } : {},
|
|
10122
|
+
...excerpt(response2.content) ? { excerpt: excerpt(response2.content) } : {}
|
|
10123
|
+
};
|
|
10124
|
+
}
|
|
10125
|
+
if (started.kind === "list") {
|
|
10126
|
+
const listed = response2.content.split("\n").filter((line2) => line2 && !line2.startsWith("\u2026") && !line2.startsWith("Workspace ")).map((line2) => text3(line2, 1024)).filter((line2) => Boolean(line2)).slice(0, 8);
|
|
10127
|
+
return {
|
|
10128
|
+
...started,
|
|
10129
|
+
...integer2(details?.count) !== void 0 ? { count: integer2(details?.count) } : {},
|
|
10130
|
+
...listed.length ? { paths: listed } : {}
|
|
10131
|
+
};
|
|
10132
|
+
}
|
|
10133
|
+
if (started.kind === "query") {
|
|
10134
|
+
const results = request3.tool === "sandbox.search" ? searchResults(response2.content) : void 0;
|
|
10135
|
+
const resultExcerpt = request3.tool === "sandbox.search" ? void 0 : excerpt(response2.content);
|
|
10136
|
+
return {
|
|
10137
|
+
...started,
|
|
10138
|
+
...integer2(details?.count) !== void 0 ? { count: integer2(details?.count) } : {},
|
|
10139
|
+
...results ? { results } : {},
|
|
10140
|
+
...resultExcerpt ? { excerpt: resultExcerpt } : {}
|
|
10141
|
+
};
|
|
10142
|
+
}
|
|
10143
|
+
if (started.kind === "patch") {
|
|
10144
|
+
return { ...started, ...paths(details?.paths) ? { paths: paths(details?.paths) } : {} };
|
|
10145
|
+
}
|
|
10146
|
+
const output = response2.content.replace(/^Recipe [^\n]*\.?\s*/u, "");
|
|
10147
|
+
return {
|
|
10148
|
+
...started,
|
|
10149
|
+
...integer2(details?.exitCode) !== void 0 ? { exitCode: integer2(details?.exitCode) } : {},
|
|
10150
|
+
...typeof details?.timedOut === "boolean" ? { timedOut: details.timedOut } : {},
|
|
10151
|
+
...typeof details?.outputLimitExceeded === "boolean" ? { outputLimitExceeded: details.outputLimitExceeded } : {},
|
|
10152
|
+
...excerpt(output, true) ? { excerpt: excerpt(output, true) } : {}
|
|
10153
|
+
};
|
|
10154
|
+
}
|
|
10155
|
+
var import_crypto, import_promises5, import_path5, import_child_process4, import_promises6, import_path6, import_child_process5, import_process2, import_crypto2, import_crypto3, import_fs2, import_promises7, import_path7, import_promises8, import_os3, import_path8, import_ai4, import_ai5, import_child_process6, import_promises9, import_path9, import_promises10, import_promises11, import_path10, import_crypto4, CODE_RUNTIME_PROTOCOL_VERSION, CodeRuntimeReconciler, CodeRuntimeControlError, record5, invalid, RESERVED, SECRET, PATH, FORBIDDEN, ARTIFACT_PATH, PRIVATE_ARTIFACT_PART, SHA3, DIGEST3, ID3, RULE, DEFAULT_PREFIXES, DEFAULT_SUFFIXES, message, CodeRuntimeCheckpointManager, record22, SOURCE_LIMITS, RESERVED2, SECRET2, SOURCE_MAX_FILES, SOURCE_MAX_BYTES, SOURCE_SET_MAX_BYTES, V1_SYSTEM_PROMPT, V2_SYSTEM_PROMPT, V3_SYSTEM_PROMPT, SYSTEM_PROMPT_FOR, DEFAULT_MAX_FILES, DEFAULT_MAX_RESULTS, DEFAULT_MAX_FILE_BYTES, MAX_NATIVE_ARG_BYTES, DESTINATIONS, READ, LIST, SEARCH, GRAPH, PATCH, RECIPE, cache, shortId, GRAPH_TOOLS, MAX_MEMORY_BODY, POSITIVE, digestRuntimeValue, runtimeErrorMessage, text3, integer2, record32, excerpt, paths, TheseusRuntimeEngine;
|
|
10156
|
+
var init_chunk_ISR434K7 = __esm({
|
|
10157
|
+
"../harness/dist/chunk-ISR434K7.js"() {
|
|
10067
10158
|
"use strict";
|
|
10068
10159
|
init_cjs_shims();
|
|
10069
|
-
|
|
10070
|
-
|
|
10160
|
+
init_chunk_CR6RE3A2();
|
|
10161
|
+
init_chunk_RXNHCGWE();
|
|
10071
10162
|
import_crypto = require("crypto");
|
|
10072
10163
|
import_promises5 = require("fs/promises");
|
|
10073
10164
|
import_path5 = require("path");
|
|
@@ -10306,6 +10397,30 @@ Never claim a build or test passed unless odla_run_recipe returned that result.`
|
|
|
10306
10397
|
POSITIVE = (value2) => Number.isFinite(value2) && Number(value2) > 0 ? Number(value2) : void 0;
|
|
10307
10398
|
digestRuntimeValue = (value2) => `sha256:${(0, import_crypto4.createHash)("sha256").update(value2).digest("hex")}`;
|
|
10308
10399
|
runtimeErrorMessage = (value2) => value2 instanceof Error ? value2.message : String(value2);
|
|
10400
|
+
text3 = (value2, maximum) => {
|
|
10401
|
+
if (typeof value2 !== "string") return void 0;
|
|
10402
|
+
const bounded = value2.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ").trim();
|
|
10403
|
+
return bounded ? bounded.slice(0, maximum) : void 0;
|
|
10404
|
+
};
|
|
10405
|
+
integer2 = (value2) => Number.isSafeInteger(value2) && Number(value2) >= 0 ? Number(value2) : void 0;
|
|
10406
|
+
record32 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : void 0;
|
|
10407
|
+
excerpt = (value2, tail = false) => {
|
|
10408
|
+
if (typeof value2 !== "string") return void 0;
|
|
10409
|
+
const safe = value2.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ");
|
|
10410
|
+
const source = (tail ? safe.slice(-1e4) : safe.slice(0, 1e4)).trim();
|
|
10411
|
+
if (!source) return void 0;
|
|
10412
|
+
const lines = source.split("\n").filter((line2) => line2.trim()).map((line2) => line2.slice(0, 240));
|
|
10413
|
+
const selected = tail ? lines.slice(-10) : lines.slice(0, 10);
|
|
10414
|
+
return text3(selected.join("\n"), 2400);
|
|
10415
|
+
};
|
|
10416
|
+
paths = (value2) => {
|
|
10417
|
+
if (!Array.isArray(value2)) return void 0;
|
|
10418
|
+
const items = value2.flatMap((item) => {
|
|
10419
|
+
const path = text3(item, 1024);
|
|
10420
|
+
return path ? [path] : [];
|
|
10421
|
+
}).slice(0, 12);
|
|
10422
|
+
return items.length ? items : void 0;
|
|
10423
|
+
};
|
|
10309
10424
|
TheseusRuntimeEngine = class {
|
|
10310
10425
|
constructor(options) {
|
|
10311
10426
|
this.options = options;
|
|
@@ -10539,18 +10654,29 @@ Never claim a build or test passed unless odla_run_recipe returned that result.`
|
|
|
10539
10654
|
return {
|
|
10540
10655
|
execute: async (context, request3) => {
|
|
10541
10656
|
const startedAt = Date.now();
|
|
10657
|
+
const operationId = digestRuntimeValue(`${command.commandId}:${request3.requestId}`);
|
|
10658
|
+
const startedPresentation = codeToolRequestPresentation(request3);
|
|
10542
10659
|
await this.#event(
|
|
10543
10660
|
command,
|
|
10544
|
-
{
|
|
10661
|
+
{
|
|
10662
|
+
type: "tool",
|
|
10663
|
+
phase: "started",
|
|
10664
|
+
tool: request3.tool,
|
|
10665
|
+
operationId,
|
|
10666
|
+
...startedPresentation ? { presentation: startedPresentation } : {}
|
|
10667
|
+
},
|
|
10545
10668
|
active.conversationRefs
|
|
10546
10669
|
).catch(() => void 0);
|
|
10547
10670
|
const response2 = await broker.execute(context, request3);
|
|
10671
|
+
const completedPresentation = codeToolResultPresentation(request3, response2);
|
|
10548
10672
|
await this.#event(command, {
|
|
10549
10673
|
type: "tool",
|
|
10550
10674
|
phase: "completed",
|
|
10551
10675
|
tool: request3.tool,
|
|
10552
10676
|
ok: response2.ok,
|
|
10553
|
-
durationMs: Date.now() - startedAt
|
|
10677
|
+
durationMs: Date.now() - startedAt,
|
|
10678
|
+
operationId,
|
|
10679
|
+
...completedPresentation ? { presentation: completedPresentation } : {}
|
|
10554
10680
|
}, active.conversationRefs).catch(() => void 0);
|
|
10555
10681
|
return response2;
|
|
10556
10682
|
}
|
|
@@ -10591,8 +10717,8 @@ var init_node = __esm({
|
|
|
10591
10717
|
"../harness/dist/node.js"() {
|
|
10592
10718
|
"use strict";
|
|
10593
10719
|
init_cjs_shims();
|
|
10594
|
-
|
|
10595
|
-
|
|
10720
|
+
init_chunk_ISR434K7();
|
|
10721
|
+
init_chunk_CR6RE3A2();
|
|
10596
10722
|
MEASURED_PREMIUM = Object.freeze({
|
|
10597
10723
|
/** 3 racers vs pure depth at equal budget: 21,044 / 7,936. */
|
|
10598
10724
|
racePerRacer: 0.55,
|
|
@@ -11649,7 +11775,8 @@ Usage:
|
|
|
11649
11775
|
odla-ai pm bug list [--app <id>] [--status <s>] [--severity <s>] [--goal <id>] [--assignee <id>] [--decision <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
11650
11776
|
odla-ai pm goal add --app <id> --title <t> [--status <s>] [--proof <text>] [--target <pct>] [--mutation-id <id>] [--json]
|
|
11651
11777
|
odla-ai pm task add --app <id> --title <t> [--column <backlog|ready|doing|review|done>] [--goal <id>|--alignment-decision <id>] [--assignee <id>] [--description <text>|--body <text>] [--acceptance <text>] [--execution <human|agent|either>] [--due <epoch-ms>] [--mutation-id <id>] [--json]
|
|
11652
|
-
odla-ai pm next --app <id> [--project <id>] [--json]
|
|
11778
|
+
odla-ai pm next --app <id> [--project <id>] [--verbose] [--json]
|
|
11779
|
+
odla-ai pm start --app <id> [<task-id>] [--goal <id>] [--verbose] [--mutation-id <id>] [--json] [claims the top Ready task in one call]
|
|
11653
11780
|
odla-ai pm watch --app <id> [--cursor <cursor>] [--entity goal|task|decision|bug] [--action created|updated|deleted|comment.created|comment.updated] [--state <state>] [--by <principalId>] [--self <principalId>] [--interval <s>] [--timeout <s>] [--json|--jsonl]
|
|
11654
11781
|
odla-ai pm task ready <id> --expected-revision <n> [--goal <id>|--alignment-decision <id>] [--description <text>|--body <text>] [--acceptance <text>] [--execution <human|agent|either>] [--mutation-id <id>] [--json]
|
|
11655
11782
|
odla-ai pm task claim <id> --expected-revision <n> [--mutation-id <id>] [--json]
|
|
@@ -11670,7 +11797,7 @@ Usage:
|
|
|
11670
11797
|
odla-ai pm <goal|task|decision|bug> comments <id> [--json]
|
|
11671
11798
|
odla-ai pm <goal|task|decision|bug> history <id> [--limit <n>] [--json]
|
|
11672
11799
|
odla-ai pm <goal|task|decision|bug> rm <id>
|
|
11673
|
-
odla-ai pm handoff --app <id> [--project <id>] [--json]
|
|
11800
|
+
odla-ai pm handoff --app <id> [--project <id>] [--verbose] [--json]
|
|
11674
11801
|
odla-ai discuss groups [--json]
|
|
11675
11802
|
odla-ai discuss list [--app <id>] [--q <text>] [--state open|resolved|all] [--mentions] [--json]
|
|
11676
11803
|
odla-ai discuss read <topic> [--limit <n> --offset <n>] [--json]
|
|
@@ -11837,6 +11964,16 @@ Commands:
|
|
|
11837
11964
|
execution contract and a reviewed revision. Any caller with PM
|
|
11838
11965
|
mutation access to that app/project may do it; claims coordinate
|
|
11839
11966
|
execution but never own or lock the PM record.
|
|
11967
|
+
To pick up work: "pm next" reads open goals, Ready candidates,
|
|
11968
|
+
and work in progress; "pm start" claims the top Ready task in
|
|
11969
|
+
one call, compare-and-swapped on the revision it just read, and
|
|
11970
|
+
returns the task, its goal, and the acceptance criteria to work
|
|
11971
|
+
against. Finish with "pm task done <id>", or hand it back with
|
|
11972
|
+
"pm task release <id> --expected-revision <n>". "pm start" never
|
|
11973
|
+
marks work Ready: that contract is reviewed separately so an
|
|
11974
|
+
agent cannot authorize its own work.
|
|
11975
|
+
Intake output is projected to the fields an executing caller
|
|
11976
|
+
acts on; --verbose returns whole records with their audit trail.
|
|
11840
11977
|
bug Intent-first alias for PM bugs. "bug report" writes to
|
|
11841
11978
|
odla PM; odla product defects do not belong in GitHub Issues.
|
|
11842
11979
|
discuss Group discussions (via @odla-ai/chat) for the apps you co-own:
|
|
@@ -12648,7 +12785,8 @@ async function pmRequest(ctx, method, path, body) {
|
|
|
12648
12785
|
const message2 = error.message;
|
|
12649
12786
|
if (typeof message2 === "string" && message2.length > 0) detail = message2;
|
|
12650
12787
|
}
|
|
12651
|
-
throw new
|
|
12788
|
+
throw new PmRequestError(
|
|
12789
|
+
response2.status,
|
|
12652
12790
|
`pm ${method} ${path} failed: ${detail ?? `registry returned ${response2.status}`} (${response2.status})`
|
|
12653
12791
|
);
|
|
12654
12792
|
}
|
|
@@ -12663,8 +12801,8 @@ function collectFields(parsed, allowClear) {
|
|
|
12663
12801
|
if (allowClear) out[spec.key] = null;
|
|
12664
12802
|
continue;
|
|
12665
12803
|
}
|
|
12666
|
-
const
|
|
12667
|
-
out[spec.key] = spec.num ? Number(
|
|
12804
|
+
const text4 = stringOpt(value2);
|
|
12805
|
+
out[spec.key] = spec.num ? Number(text4) : text4;
|
|
12668
12806
|
}
|
|
12669
12807
|
return out;
|
|
12670
12808
|
}
|
|
@@ -12708,7 +12846,7 @@ function emit2(ctx, value2, human) {
|
|
|
12708
12846
|
if (ctx.json) ctx.out.log(JSON.stringify(value2, null, 2));
|
|
12709
12847
|
else human();
|
|
12710
12848
|
}
|
|
12711
|
-
var DONE, writeMutationId2, FIELD_MAP, STUDIO_SECTION;
|
|
12849
|
+
var DONE, writeMutationId2, FIELD_MAP, PmRequestError, isRevisionConflict, STUDIO_SECTION;
|
|
12712
12850
|
var init_pm_action_core = __esm({
|
|
12713
12851
|
"src/pm-action-core.ts"() {
|
|
12714
12852
|
"use strict";
|
|
@@ -12741,6 +12879,15 @@ var init_pm_action_core = __esm({
|
|
|
12741
12879
|
execution: { key: "executionMode" },
|
|
12742
12880
|
"expected-revision": { key: "expectedRevision", num: true }
|
|
12743
12881
|
};
|
|
12882
|
+
PmRequestError = class extends Error {
|
|
12883
|
+
constructor(status, message2) {
|
|
12884
|
+
super(message2);
|
|
12885
|
+
this.status = status;
|
|
12886
|
+
this.name = "PmRequestError";
|
|
12887
|
+
}
|
|
12888
|
+
status;
|
|
12889
|
+
};
|
|
12890
|
+
isRevisionConflict = (error) => error instanceof PmRequestError && error.status === 409;
|
|
12744
12891
|
STUDIO_SECTION = {
|
|
12745
12892
|
goal: "goals",
|
|
12746
12893
|
task: "board",
|
|
@@ -12872,110 +13019,231 @@ async function pmTaskLifecycle(ctx, id2, action2, parsed) {
|
|
|
12872
13019
|
ctx.out.log(`task: ${label} \u2192 ${state2}`);
|
|
12873
13020
|
});
|
|
12874
13021
|
}
|
|
12875
|
-
async function
|
|
13022
|
+
async function pmRemove(ctx, entity, id2) {
|
|
13023
|
+
await pmRequest(ctx, "DELETE", `/${entity}/${encodeURIComponent(id2)}`);
|
|
13024
|
+
ctx.out.log(`deleted ${entity} ${id2}`);
|
|
13025
|
+
}
|
|
13026
|
+
var init_pm_actions = __esm({
|
|
13027
|
+
"src/pm-actions.ts"() {
|
|
13028
|
+
"use strict";
|
|
13029
|
+
init_cjs_shims();
|
|
13030
|
+
init_argv();
|
|
13031
|
+
init_pm_action_core();
|
|
13032
|
+
}
|
|
13033
|
+
});
|
|
13034
|
+
|
|
13035
|
+
// src/pm-lean.ts
|
|
13036
|
+
function leanRecord(entity, record11) {
|
|
13037
|
+
const out = {};
|
|
13038
|
+
for (const key of [...COMMON, ...PER_ENTITY[entity]]) {
|
|
13039
|
+
const value2 = record11[key];
|
|
13040
|
+
if (value2 !== void 0 && value2 !== null) out[key] = value2;
|
|
13041
|
+
}
|
|
13042
|
+
return out;
|
|
13043
|
+
}
|
|
13044
|
+
function leanRecords(entity, records, verbose) {
|
|
13045
|
+
return verbose ? records : records.map((record11) => leanRecord(entity, record11));
|
|
13046
|
+
}
|
|
13047
|
+
var COMMON, PER_ENTITY;
|
|
13048
|
+
var init_pm_lean = __esm({
|
|
13049
|
+
"src/pm-lean.ts"() {
|
|
13050
|
+
"use strict";
|
|
13051
|
+
init_cjs_shims();
|
|
13052
|
+
COMMON = ["id", "appId", "projectId", "title", "revision"];
|
|
13053
|
+
PER_ENTITY = {
|
|
13054
|
+
goal: ["status", "proof", "targetPct", "currentPct"],
|
|
13055
|
+
task: [
|
|
13056
|
+
"column",
|
|
13057
|
+
"goalId",
|
|
13058
|
+
"alignmentDecisionId",
|
|
13059
|
+
"description",
|
|
13060
|
+
"acceptanceCriteria",
|
|
13061
|
+
"executionMode",
|
|
13062
|
+
"assigneeId",
|
|
13063
|
+
"claimedByPrincipalId",
|
|
13064
|
+
"dueAt"
|
|
13065
|
+
],
|
|
13066
|
+
decision: ["status", "body", "supersedesId"],
|
|
13067
|
+
bug: ["status", "severity", "description", "goalId", "assigneeId", "decisionId"]
|
|
13068
|
+
};
|
|
13069
|
+
}
|
|
13070
|
+
});
|
|
13071
|
+
|
|
13072
|
+
// src/pm-intake.ts
|
|
13073
|
+
async function listFiltered(ctx, entity, appId, projectId, filters) {
|
|
12876
13074
|
const records = [];
|
|
12877
13075
|
for (; ; ) {
|
|
12878
|
-
const q = new URLSearchParams({
|
|
12879
|
-
app: appId,
|
|
12880
|
-
limit: "100",
|
|
12881
|
-
offset: String(records.length)
|
|
12882
|
-
});
|
|
13076
|
+
const q = new URLSearchParams({ app: appId, limit: "100", offset: String(records.length) });
|
|
12883
13077
|
if (projectId) q.set("project", projectId);
|
|
13078
|
+
for (const [key, value2] of Object.entries(filters)) q.set(key, value2);
|
|
12884
13079
|
const page2 = await pmRequest(ctx, "GET", `/${entity}?${q}`);
|
|
12885
13080
|
records.push(...page2.records);
|
|
12886
13081
|
if (records.length >= page2.total || page2.records.length === 0) return records;
|
|
12887
13082
|
}
|
|
12888
13083
|
}
|
|
12889
|
-
|
|
13084
|
+
function requireApp(ctx, parsed, command) {
|
|
12890
13085
|
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
12891
|
-
|
|
12892
|
-
|
|
13086
|
+
if (!appId) throw new Error(`pm ${command} needs --app <appId>`);
|
|
13087
|
+
return { appId, projectId: stringOpt(parsed.options.project) ?? ctx.projectId };
|
|
13088
|
+
}
|
|
13089
|
+
async function pmNext(ctx, parsed) {
|
|
13090
|
+
const { appId, projectId } = requireApp(ctx, parsed, "next");
|
|
13091
|
+
const verbose = wantsVerbose(parsed);
|
|
12893
13092
|
const [goals, tasks] = await Promise.all([
|
|
12894
|
-
|
|
12895
|
-
|
|
13093
|
+
listFiltered(ctx, "goal", appId, projectId, { status: UNMET_GOAL_STATUS }),
|
|
13094
|
+
// One request for both live columns; the split below is over open work only.
|
|
13095
|
+
listFiltered(ctx, "task", appId, projectId, { column: `${READY_COLUMN},doing` })
|
|
12896
13096
|
]);
|
|
13097
|
+
const ready = tasks.filter((record11) => record11.column === READY_COLUMN);
|
|
13098
|
+
const doing = tasks.filter((record11) => record11.column === "doing");
|
|
13099
|
+
const guidance = !goals.length ? "discuss alignment with the user before creating or claiming project work" : !ready.length ? 'refine a linked Backlog task and mark it Ready with "pm task ready <id>"' : `claim the top Ready task in one call: odla-ai pm start --app ${appId}`;
|
|
12897
13100
|
const result = {
|
|
12898
13101
|
appId,
|
|
12899
13102
|
projectId,
|
|
12900
|
-
openGoals:
|
|
12901
|
-
doing:
|
|
12902
|
-
ready:
|
|
13103
|
+
openGoals: leanRecords("goal", goals, verbose),
|
|
13104
|
+
doing: leanRecords("task", doing, verbose),
|
|
13105
|
+
ready: leanRecords("task", ready, verbose),
|
|
13106
|
+
next: guidance
|
|
12903
13107
|
};
|
|
12904
13108
|
emit2(ctx, result, () => {
|
|
12905
13109
|
ctx.out.log(`${appId}: goal-aligned work intake (read only)`);
|
|
12906
|
-
for (const [label, records] of [
|
|
12907
|
-
["doing",
|
|
12908
|
-
["ready",
|
|
12909
|
-
["open goals",
|
|
13110
|
+
for (const [label, entity, records] of [
|
|
13111
|
+
["doing", "task", doing],
|
|
13112
|
+
["ready", "task", ready],
|
|
13113
|
+
["open goals", "goal", goals]
|
|
12910
13114
|
]) {
|
|
12911
13115
|
ctx.out.log(`${label}:`);
|
|
12912
13116
|
if (!records.length) ctx.out.log("- (none)");
|
|
12913
|
-
else for (const record11 of records) printRecord(
|
|
12914
|
-
ctx,
|
|
12915
|
-
label === "open goals" ? "goal" : "task",
|
|
12916
|
-
record11
|
|
12917
|
-
);
|
|
12918
|
-
}
|
|
12919
|
-
if (!result.openGoals.length) {
|
|
12920
|
-
ctx.out.log("next: discuss alignment with the user before creating or claiming project work");
|
|
12921
|
-
} else if (!result.ready.length) {
|
|
12922
|
-
ctx.out.log("next: refine a linked Backlog task and mark it Ready");
|
|
12923
|
-
} else {
|
|
12924
|
-
ctx.out.log("next: review a Ready task, then claim it with its revision");
|
|
13117
|
+
else for (const record11 of records) printRecord(ctx, entity, record11);
|
|
12925
13118
|
}
|
|
13119
|
+
ctx.out.log(`next: ${guidance}`);
|
|
12926
13120
|
});
|
|
12927
13121
|
}
|
|
12928
13122
|
async function pmHandoff(ctx, parsed) {
|
|
12929
|
-
const appId =
|
|
12930
|
-
const
|
|
12931
|
-
if (!appId) throw new Error("pm handoff needs --app <appId>");
|
|
13123
|
+
const { appId, projectId } = requireApp(ctx, parsed, "handoff");
|
|
13124
|
+
const verbose = wantsVerbose(parsed);
|
|
12932
13125
|
const [goals, tasks, bugs] = await Promise.all([
|
|
12933
|
-
|
|
12934
|
-
|
|
12935
|
-
|
|
13126
|
+
listFiltered(ctx, "goal", appId, projectId, { status: UNMET_GOAL_STATUS }),
|
|
13127
|
+
listFiltered(ctx, "task", appId, projectId, { column: ACTIVE_TASK_COLUMNS }),
|
|
13128
|
+
listFiltered(ctx, "bug", appId, projectId, { status: OPEN_BUG_STATUSES })
|
|
12936
13129
|
]);
|
|
12937
|
-
const
|
|
13130
|
+
const clean4 = !goals.length && !tasks.length && !bugs.length;
|
|
13131
|
+
const result = {
|
|
12938
13132
|
appId,
|
|
12939
13133
|
projectId,
|
|
12940
|
-
unmetGoals:
|
|
12941
|
-
activeTasks:
|
|
12942
|
-
openBugs:
|
|
12943
|
-
|
|
12944
|
-
const result = {
|
|
12945
|
-
...handoff,
|
|
12946
|
-
clean: !handoff.unmetGoals.length && !handoff.activeTasks.length && !handoff.openBugs.length
|
|
13134
|
+
unmetGoals: leanRecords("goal", goals, verbose),
|
|
13135
|
+
activeTasks: leanRecords("task", tasks, verbose),
|
|
13136
|
+
openBugs: leanRecords("bug", bugs, verbose),
|
|
13137
|
+
clean: clean4
|
|
12947
13138
|
};
|
|
12948
13139
|
emit2(ctx, result, () => {
|
|
12949
|
-
if (
|
|
13140
|
+
if (clean4) {
|
|
12950
13141
|
ctx.out.log(`${appId}: no unresolved PM work`);
|
|
12951
13142
|
return;
|
|
12952
13143
|
}
|
|
12953
13144
|
ctx.out.log(`${appId}: authoritative PM handoff`);
|
|
12954
|
-
for (const [label, records] of [
|
|
12955
|
-
["unmet goals",
|
|
12956
|
-
["active tasks",
|
|
12957
|
-
["open bugs",
|
|
13145
|
+
for (const [label, entity, records] of [
|
|
13146
|
+
["unmet goals", "goal", goals],
|
|
13147
|
+
["active tasks", "task", tasks],
|
|
13148
|
+
["open bugs", "bug", bugs]
|
|
12958
13149
|
]) {
|
|
12959
13150
|
ctx.out.log(`${label}:`);
|
|
12960
13151
|
if (!records.length) ctx.out.log("- (none)");
|
|
12961
|
-
else for (const record11 of records) printRecord(
|
|
12962
|
-
ctx,
|
|
12963
|
-
label === "unmet goals" ? "goal" : label === "active tasks" ? "task" : "bug",
|
|
12964
|
-
record11
|
|
12965
|
-
);
|
|
13152
|
+
else for (const record11 of records) printRecord(ctx, entity, record11);
|
|
12966
13153
|
}
|
|
12967
13154
|
});
|
|
12968
13155
|
}
|
|
12969
|
-
|
|
12970
|
-
|
|
12971
|
-
|
|
13156
|
+
var READY_COLUMN, UNMET_GOAL_STATUS, ACTIVE_TASK_COLUMNS, OPEN_BUG_STATUSES, wantsVerbose;
|
|
13157
|
+
var init_pm_intake = __esm({
|
|
13158
|
+
"src/pm-intake.ts"() {
|
|
13159
|
+
"use strict";
|
|
13160
|
+
init_cjs_shims();
|
|
13161
|
+
init_argv();
|
|
13162
|
+
init_pm_action_core();
|
|
13163
|
+
init_pm_lean();
|
|
13164
|
+
READY_COLUMN = "todo";
|
|
13165
|
+
UNMET_GOAL_STATUS = "open";
|
|
13166
|
+
ACTIVE_TASK_COLUMNS = "backlog,todo,doing,review";
|
|
13167
|
+
OPEN_BUG_STATUSES = "open,triaged";
|
|
13168
|
+
wantsVerbose = (parsed) => parsed.options.verbose === true;
|
|
13169
|
+
}
|
|
13170
|
+
});
|
|
13171
|
+
|
|
13172
|
+
// src/pm-start.ts
|
|
13173
|
+
async function claimAt(ctx, task, parsed) {
|
|
13174
|
+
const res = await pmRequest(
|
|
13175
|
+
ctx,
|
|
13176
|
+
"POST",
|
|
13177
|
+
`/task/${encodeURIComponent(task.id)}/claim`,
|
|
13178
|
+
{ expectedRevision: task.revision, mutationId: writeMutationId2(parsed) }
|
|
13179
|
+
);
|
|
13180
|
+
return res.record;
|
|
13181
|
+
}
|
|
13182
|
+
async function selectTask(ctx, parsed, appId, projectId) {
|
|
13183
|
+
const named = parsed.positionals[2];
|
|
13184
|
+
if (named) return getTask(ctx, named);
|
|
13185
|
+
const goalId = stringOpt(parsed.options.goal);
|
|
13186
|
+
const ready = await listFiltered(ctx, "task", appId, projectId, {
|
|
13187
|
+
column: READY_COLUMN,
|
|
13188
|
+
...goalId ? { goalId } : {}
|
|
13189
|
+
});
|
|
13190
|
+
const candidate = ready[0];
|
|
13191
|
+
if (!candidate) {
|
|
13192
|
+
throw new Error(
|
|
13193
|
+
`no Ready task to claim in ${appId}${goalId ? ` for goal ${goalId}` : ""}. Run "odla-ai pm next --app ${appId}" to see open goals and Backlog work, then "odla-ai pm task ready <id>" once a task has a complete contract.`
|
|
13194
|
+
);
|
|
13195
|
+
}
|
|
13196
|
+
return candidate;
|
|
12972
13197
|
}
|
|
12973
|
-
|
|
12974
|
-
|
|
13198
|
+
async function pmStart(ctx, parsed) {
|
|
13199
|
+
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
13200
|
+
if (!appId) throw new Error("pm start needs --app <appId>");
|
|
13201
|
+
const projectId = stringOpt(parsed.options.project) ?? ctx.projectId;
|
|
13202
|
+
const verbose = wantsVerbose(parsed);
|
|
13203
|
+
const selected = await selectTask(ctx, parsed, appId, projectId);
|
|
13204
|
+
if (selected.column !== READY_COLUMN) {
|
|
13205
|
+
throw new Error(
|
|
13206
|
+
`task ${selected.id} is ${selected.column === "doing" ? "already claimed" : `in ${selected.column}`}, not Ready. Only Ready work can be started; run "odla-ai pm task ready ${selected.id} --expected-revision ${selected.revision}" once its goal, description, and acceptance criteria are complete.`
|
|
13207
|
+
);
|
|
13208
|
+
}
|
|
13209
|
+
let claimed;
|
|
13210
|
+
try {
|
|
13211
|
+
claimed = await claimAt(ctx, selected, parsed);
|
|
13212
|
+
} catch (error) {
|
|
13213
|
+
if (!isRevisionConflict(error)) throw error;
|
|
13214
|
+
const reloaded = await getTask(ctx, selected.id);
|
|
13215
|
+
if (reloaded.column !== READY_COLUMN) {
|
|
13216
|
+
throw new Error(
|
|
13217
|
+
`task ${selected.id} was taken while starting it (now ${reloaded.column}). Run "odla-ai pm start --app ${appId}" again to take the next Ready task.`
|
|
13218
|
+
);
|
|
13219
|
+
}
|
|
13220
|
+
claimed = await claimAt(ctx, reloaded, parsed);
|
|
13221
|
+
}
|
|
13222
|
+
const task = claimed ?? await getTask(ctx, selected.id);
|
|
13223
|
+
const goalId = typeof task.goalId === "string" ? task.goalId : void 0;
|
|
13224
|
+
const goal = goalId ? await pmRequest(ctx, "GET", `/goal/${encodeURIComponent(goalId)}`).then((res) => res.record).catch(() => null) : null;
|
|
13225
|
+
const result = {
|
|
13226
|
+
claimed: verbose ? task : leanRecord("task", task),
|
|
13227
|
+
goal: goal ? verbose ? goal : leanRecord("goal", goal) : null,
|
|
13228
|
+
next: 'work the acceptance criteria, then "odla-ai pm task done <id>" (or "odla-ai pm task release <id> --expected-revision <n>" to hand it back)'
|
|
13229
|
+
};
|
|
13230
|
+
emit2(ctx, result, () => {
|
|
13231
|
+
ctx.out.log(`claimed: ${studioRecordLink(ctx, "task", task)}`);
|
|
13232
|
+
if (goal) ctx.out.log(`goal: ${studioRecordLink(ctx, "goal", goal)}`);
|
|
13233
|
+
if (typeof task.acceptanceCriteria === "string") ctx.out.log(`acceptance: ${task.acceptanceCriteria}`);
|
|
13234
|
+
ctx.out.log(`next: ${result.next}`);
|
|
13235
|
+
});
|
|
13236
|
+
}
|
|
13237
|
+
var getTask;
|
|
13238
|
+
var init_pm_start = __esm({
|
|
13239
|
+
"src/pm-start.ts"() {
|
|
12975
13240
|
"use strict";
|
|
12976
13241
|
init_cjs_shims();
|
|
12977
13242
|
init_argv();
|
|
12978
13243
|
init_pm_action_core();
|
|
13244
|
+
init_pm_lean();
|
|
13245
|
+
init_pm_intake();
|
|
13246
|
+
getTask = async (ctx, id2) => (await pmRequest(ctx, "GET", `/task/${encodeURIComponent(id2)}`)).record;
|
|
12979
13247
|
}
|
|
12980
13248
|
});
|
|
12981
13249
|
|
|
@@ -13457,9 +13725,13 @@ async function pmCommand(parsed, deps = {}) {
|
|
|
13457
13725
|
throw new Error(`unknown pm project action "${action3}". Try list|add|use.`);
|
|
13458
13726
|
}
|
|
13459
13727
|
if (word === "next") {
|
|
13460
|
-
assertArgs(parsed, [...COMMON_OPTIONS, "app", "project"], 2);
|
|
13728
|
+
assertArgs(parsed, [...COMMON_OPTIONS, "app", "project", "verbose"], 2);
|
|
13461
13729
|
return pmNext(await buildContext2(parsed, deps), parsed);
|
|
13462
13730
|
}
|
|
13731
|
+
if (word === "start") {
|
|
13732
|
+
assertArgs(parsed, [...COMMON_OPTIONS, "app", "project", "goal", "verbose", "mutation-id"], 3);
|
|
13733
|
+
return pmStart(await buildContext2(parsed, deps), parsed);
|
|
13734
|
+
}
|
|
13463
13735
|
if (word === "watch") {
|
|
13464
13736
|
assertArgs(parsed, [
|
|
13465
13737
|
...COMMON_OPTIONS,
|
|
@@ -13477,11 +13749,11 @@ async function pmCommand(parsed, deps = {}) {
|
|
|
13477
13749
|
return pmWatch(await buildContext2(parsed, deps), parsed).then(() => void 0);
|
|
13478
13750
|
}
|
|
13479
13751
|
if (word === "handoff") {
|
|
13480
|
-
assertArgs(parsed, [...COMMON_OPTIONS, "app", "project"], 2);
|
|
13752
|
+
assertArgs(parsed, [...COMMON_OPTIONS, "app", "project", "verbose"], 2);
|
|
13481
13753
|
return pmHandoff(await buildContext2(parsed, deps), parsed);
|
|
13482
13754
|
}
|
|
13483
13755
|
const entity = ALIASES[word];
|
|
13484
|
-
if (!entity) throw new Error(`unknown pm entity "${word}". Try "odla-ai pm bug list" (goal|task|decision|bug).`);
|
|
13756
|
+
if (!entity) throw new Error(`unknown pm entity "${word}". Try "odla-ai pm start" to claim Ready work, or "odla-ai pm bug list" (goal|task|decision|bug).`);
|
|
13485
13757
|
const requestedAction = parsed.positionals[2] ?? "list";
|
|
13486
13758
|
const action2 = canonicalAction(requestedAction);
|
|
13487
13759
|
if (!action2) throw new Error(`unknown pm action "${requestedAction}". Try list|add|get|set|done|link|ref|comment|comments|history|rm.`);
|
|
@@ -13528,6 +13800,8 @@ var init_pm_command = __esm({
|
|
|
13528
13800
|
init_argv();
|
|
13529
13801
|
init_operator_context();
|
|
13530
13802
|
init_pm_actions();
|
|
13803
|
+
init_pm_intake();
|
|
13804
|
+
init_pm_start();
|
|
13531
13805
|
init_pm_links();
|
|
13532
13806
|
init_pm_comments();
|
|
13533
13807
|
init_pm_history();
|
|
@@ -14125,14 +14399,14 @@ function statusMinutes(value2) {
|
|
|
14125
14399
|
}
|
|
14126
14400
|
async function read2(url, headers, doFetch) {
|
|
14127
14401
|
const response2 = await doFetch(url, { headers });
|
|
14128
|
-
const
|
|
14402
|
+
const text4 = await response2.text();
|
|
14129
14403
|
let body = {};
|
|
14130
|
-
if (
|
|
14404
|
+
if (text4) {
|
|
14131
14405
|
try {
|
|
14132
|
-
const value2 = JSON.parse(
|
|
14406
|
+
const value2 = JSON.parse(text4);
|
|
14133
14407
|
body = value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : { value: value2 };
|
|
14134
14408
|
} catch {
|
|
14135
|
-
body = { message:
|
|
14409
|
+
body = { message: text4.slice(0, 300) };
|
|
14136
14410
|
}
|
|
14137
14411
|
}
|
|
14138
14412
|
return { httpStatus: response2.status, body };
|
|
@@ -14365,13 +14639,13 @@ async function monitorCommand(parsed, deps = {}) {
|
|
|
14365
14639
|
}
|
|
14366
14640
|
async function request2(url, init, doFetch) {
|
|
14367
14641
|
const response2 = await doFetch(url, init);
|
|
14368
|
-
const
|
|
14642
|
+
const text4 = await response2.text();
|
|
14369
14643
|
let body = {};
|
|
14370
14644
|
try {
|
|
14371
|
-
const parsed =
|
|
14645
|
+
const parsed = text4 ? JSON.parse(text4) : {};
|
|
14372
14646
|
body = record10(parsed) ? parsed : { value: parsed };
|
|
14373
14647
|
} catch {
|
|
14374
|
-
body = { message:
|
|
14648
|
+
body = { message: text4.slice(0, 500) };
|
|
14375
14649
|
}
|
|
14376
14650
|
if (!response2.ok) {
|
|
14377
14651
|
const error = record10(body.error) ? body.error : body;
|
|
@@ -14611,8 +14885,8 @@ function runtimeUrl(cfg, suffix = "") {
|
|
|
14611
14885
|
return `${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/runtime-credentials${suffix}`;
|
|
14612
14886
|
}
|
|
14613
14887
|
async function safeError(response2) {
|
|
14614
|
-
const
|
|
14615
|
-
return redactSecrets(
|
|
14888
|
+
const text4 = await response2.text();
|
|
14889
|
+
return redactSecrets(text4.slice(0, 1e3));
|
|
14616
14890
|
}
|
|
14617
14891
|
async function finish(doFetch, cfg, token, sessionId, method) {
|
|
14618
14892
|
return doFetch(runtimeUrl(cfg, `/${encodeURIComponent(sessionId)}`), {
|
|
@@ -15107,6 +15381,7 @@ var init_surface = __esm({
|
|
|
15107
15381
|
project: { list: {}, add: {}, create: {}, use: {} },
|
|
15108
15382
|
handoff: {},
|
|
15109
15383
|
next: {},
|
|
15384
|
+
start: {},
|
|
15110
15385
|
watch: {}
|
|
15111
15386
|
},
|
|
15112
15387
|
provision: {},
|
|
@@ -15213,10 +15488,10 @@ var init_advisory_output = __esm({
|
|
|
15213
15488
|
// src/device-ttl.ts
|
|
15214
15489
|
function parseDeviceTtl(raw) {
|
|
15215
15490
|
if (raw === void 0 || raw === true) return void 0;
|
|
15216
|
-
const
|
|
15217
|
-
if (!
|
|
15218
|
-
if (
|
|
15219
|
-
const match = /^(\d+)\s*([dwy])$/.exec(
|
|
15491
|
+
const text4 = String(raw).trim().toLowerCase();
|
|
15492
|
+
if (!text4) return void 0;
|
|
15493
|
+
if (text4 === "forever" || text4 === "never") return 100 * 365 * DAY_MS;
|
|
15494
|
+
const match = /^(\d+)\s*([dwy])$/.exec(text4);
|
|
15220
15495
|
if (!match) {
|
|
15221
15496
|
throw new Error(
|
|
15222
15497
|
`--device-ttl expects a duration like 30d, 6w, 2y, or "forever" (got "${raw}")`
|
|
@@ -15583,12 +15858,12 @@ var init_runbook_actions = __esm({
|
|
|
15583
15858
|
});
|
|
15584
15859
|
|
|
15585
15860
|
// src/runbook-import.ts
|
|
15586
|
-
function parseRunbook(
|
|
15587
|
-
let rest =
|
|
15861
|
+
function parseRunbook(text4, slug) {
|
|
15862
|
+
let rest = text4;
|
|
15588
15863
|
const meta = {};
|
|
15589
|
-
const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(
|
|
15864
|
+
const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text4);
|
|
15590
15865
|
if (fm) {
|
|
15591
|
-
rest =
|
|
15866
|
+
rest = text4.slice(fm[0].length);
|
|
15592
15867
|
for (const line2 of fm[1].split(/\r?\n/)) {
|
|
15593
15868
|
const pair = /^(\w+)\s*:\s*(.+)$/.exec(line2.trim());
|
|
15594
15869
|
if (!pair) continue;
|