@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
|
@@ -365,10 +365,10 @@ function isManagedDevVar(line2) {
|
|
|
365
365
|
const match = line2.match(/^\s*(?:export\s+)?([A-Z][A-Z0-9_]*)\s*=/);
|
|
366
366
|
return !!match?.[1] && MANAGED_DEV_VARS.has(match[1]);
|
|
367
367
|
}
|
|
368
|
-
function writePrivateText(path,
|
|
368
|
+
function writePrivateText(path, text4) {
|
|
369
369
|
mkdirSync2(dirname3(path), { recursive: true });
|
|
370
370
|
const temporary = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
371
|
-
writeFileSync(temporary,
|
|
371
|
+
writeFileSync(temporary, text4, { mode: 384 });
|
|
372
372
|
chmodSync2(temporary, 384);
|
|
373
373
|
renameSync(temporary, path);
|
|
374
374
|
}
|
|
@@ -808,12 +808,12 @@ async function readAdminAiAudit(request3) {
|
|
|
808
808
|
}
|
|
809
809
|
}
|
|
810
810
|
async function responseBody(response2) {
|
|
811
|
-
const
|
|
812
|
-
if (!
|
|
811
|
+
const text4 = await response2.text();
|
|
812
|
+
if (!text4) return {};
|
|
813
813
|
try {
|
|
814
|
-
return JSON.parse(
|
|
814
|
+
return JSON.parse(text4);
|
|
815
815
|
} catch {
|
|
816
|
-
return { message:
|
|
816
|
+
return { message: text4.slice(0, 300) };
|
|
817
817
|
}
|
|
818
818
|
}
|
|
819
819
|
function apiError(status, body) {
|
|
@@ -915,12 +915,12 @@ function timestamp2(value2) {
|
|
|
915
915
|
return Number.isFinite(date.valueOf()) ? date.toISOString() : "";
|
|
916
916
|
}
|
|
917
917
|
async function responseBody2(res) {
|
|
918
|
-
const
|
|
919
|
-
if (!
|
|
918
|
+
const text4 = await res.text();
|
|
919
|
+
if (!text4) return {};
|
|
920
920
|
try {
|
|
921
|
-
return JSON.parse(
|
|
921
|
+
return JSON.parse(text4);
|
|
922
922
|
} catch {
|
|
923
|
-
return { message:
|
|
923
|
+
return { message: text4.slice(0, 300) };
|
|
924
924
|
}
|
|
925
925
|
}
|
|
926
926
|
function apiError2(action2, status, body) {
|
|
@@ -1096,12 +1096,12 @@ function catalogModels(body) {
|
|
|
1096
1096
|
return body.catalog.models.filter((value2) => isRecord3(value2) && typeof value2.id === "string" && typeof value2.provider === "string");
|
|
1097
1097
|
}
|
|
1098
1098
|
async function responseBody3(res) {
|
|
1099
|
-
const
|
|
1100
|
-
if (!
|
|
1099
|
+
const text4 = await res.text();
|
|
1100
|
+
if (!text4) return {};
|
|
1101
1101
|
try {
|
|
1102
|
-
return JSON.parse(
|
|
1102
|
+
return JSON.parse(text4);
|
|
1103
1103
|
} catch {
|
|
1104
|
-
return { message:
|
|
1104
|
+
return { message: text4.slice(0, 300) };
|
|
1105
1105
|
}
|
|
1106
1106
|
}
|
|
1107
1107
|
function apiError3(action2, status, body) {
|
|
@@ -2474,8 +2474,8 @@ async function appImport(options) {
|
|
|
2474
2474
|
const out = options.stdout ?? console;
|
|
2475
2475
|
const say = options.json ? (line2) => out.error(line2) : (line2) => out.log(line2);
|
|
2476
2476
|
const { tenant } = resolveTenant(cfg, options.env);
|
|
2477
|
-
const
|
|
2478
|
-
const { format, sources } = parseImport(
|
|
2477
|
+
const text4 = options.file === "-" ? (options.readStdin ?? (() => readFileSync5(0, "utf8")))() : readFileSync5(options.file, "utf8");
|
|
2478
|
+
const { format, sources } = parseImport(text4, options.ns);
|
|
2479
2479
|
if (format === "namespace-map" && options.ns) {
|
|
2480
2480
|
throw new Error("--ns cannot be combined with a {namespace: rows} file \u2014 the file already names each namespace");
|
|
2481
2481
|
}
|
|
@@ -2691,7 +2691,7 @@ var EXTENSIONS = {
|
|
|
2691
2691
|
"text/html": "html",
|
|
2692
2692
|
"application/json": "json"
|
|
2693
2693
|
};
|
|
2694
|
-
var encode = (
|
|
2694
|
+
var encode = (text4) => new TextEncoder().encode(text4);
|
|
2695
2695
|
function assetFileName(uuid, mime) {
|
|
2696
2696
|
const ext = EXTENSIONS[mime.split(";")[0].trim().toLowerCase()] ?? "bin";
|
|
2697
2697
|
return `${uuid.replace(/[^a-zA-Z0-9._-]/g, "_")}.${ext}`;
|
|
@@ -3549,9 +3549,9 @@ async function assertTenantAdminAccess(doFetch, cfg, env, token) {
|
|
|
3549
3549
|
}
|
|
3550
3550
|
throw new Error(`${env}: tenant access preflight (${tenantId}) failed: ${res.status} ${await safeText5(res)}`);
|
|
3551
3551
|
}
|
|
3552
|
-
function errorCode(
|
|
3552
|
+
function errorCode(text4) {
|
|
3553
3553
|
try {
|
|
3554
|
-
const body = JSON.parse(
|
|
3554
|
+
const body = JSON.parse(text4);
|
|
3555
3555
|
return typeof body.error?.code === "string" ? body.error.code : null;
|
|
3556
3556
|
} catch {
|
|
3557
3557
|
return null;
|
|
@@ -4286,15 +4286,15 @@ function readWranglerConfig(path) {
|
|
|
4286
4286
|
return null;
|
|
4287
4287
|
}
|
|
4288
4288
|
}
|
|
4289
|
-
function stripJsonComments(
|
|
4289
|
+
function stripJsonComments(text4) {
|
|
4290
4290
|
let result = "";
|
|
4291
4291
|
let inString = false;
|
|
4292
|
-
for (let i = 0; i <
|
|
4293
|
-
const ch =
|
|
4292
|
+
for (let i = 0; i < text4.length; i++) {
|
|
4293
|
+
const ch = text4[i];
|
|
4294
4294
|
if (inString) {
|
|
4295
4295
|
result += ch;
|
|
4296
4296
|
if (ch === "\\") {
|
|
4297
|
-
result +=
|
|
4297
|
+
result += text4[i + 1] ?? "";
|
|
4298
4298
|
i++;
|
|
4299
4299
|
} else if (ch === '"') {
|
|
4300
4300
|
inString = false;
|
|
@@ -4306,14 +4306,14 @@ function stripJsonComments(text3) {
|
|
|
4306
4306
|
result += ch;
|
|
4307
4307
|
continue;
|
|
4308
4308
|
}
|
|
4309
|
-
if (ch === "/" &&
|
|
4310
|
-
while (i <
|
|
4309
|
+
if (ch === "/" && text4[i + 1] === "/") {
|
|
4310
|
+
while (i < text4.length && text4[i] !== "\n") i++;
|
|
4311
4311
|
result += "\n";
|
|
4312
4312
|
continue;
|
|
4313
4313
|
}
|
|
4314
|
-
if (ch === "/" &&
|
|
4314
|
+
if (ch === "/" && text4[i + 1] === "*") {
|
|
4315
4315
|
i += 2;
|
|
4316
|
-
while (i <
|
|
4316
|
+
while (i < text4.length && !(text4[i] === "*" && text4[i + 1] === "/")) i++;
|
|
4317
4317
|
i++;
|
|
4318
4318
|
continue;
|
|
4319
4319
|
}
|
|
@@ -4827,9 +4827,9 @@ function initProject(options) {
|
|
|
4827
4827
|
out.log("created src/odla/schema.mjs and src/odla/rules.mjs");
|
|
4828
4828
|
out.log("updated .gitignore for local odla credentials");
|
|
4829
4829
|
}
|
|
4830
|
-
function writeIfMissing(path,
|
|
4830
|
+
function writeIfMissing(path, text4) {
|
|
4831
4831
|
if (existsSync9(path)) return;
|
|
4832
|
-
writeFileSync2(path,
|
|
4832
|
+
writeFileSync2(path, text4);
|
|
4833
4833
|
}
|
|
4834
4834
|
function configTemplate(input) {
|
|
4835
4835
|
const calendar = input.services.includes("calendar") ? ` calendar: {
|
|
@@ -5041,13 +5041,13 @@ async function secretsSetClerkKey(options) {
|
|
|
5041
5041
|
body: JSON.stringify({ value: value2 })
|
|
5042
5042
|
});
|
|
5043
5043
|
if (!res.ok) {
|
|
5044
|
-
const
|
|
5045
|
-
throw new Error(`store Clerk secret key failed (${res.status}): ${
|
|
5044
|
+
const text4 = scrubValue((await res.text().catch(() => "")).slice(0, 300), value2);
|
|
5045
|
+
throw new Error(`store Clerk secret key failed (${res.status}): ${text4 || "request failed"}`);
|
|
5046
5046
|
}
|
|
5047
5047
|
out.log(`Clerk secret key stored for ${tenantId} ($clerk_secret, reserved + write-only; the value was never echoed)`);
|
|
5048
5048
|
}
|
|
5049
|
-
function scrubValue(
|
|
5050
|
-
return redactSecrets(
|
|
5049
|
+
function scrubValue(text4, value2) {
|
|
5050
|
+
return redactSecrets(text4).split(value2).join("[value redacted]");
|
|
5051
5051
|
}
|
|
5052
5052
|
async function resolveVaultWrite(options) {
|
|
5053
5053
|
const out = options.stdout ?? console;
|
|
@@ -5339,8 +5339,8 @@ ${conflicts.map((f) => ` - ${f}`).join("\n")}`
|
|
|
5339
5339
|
harnesses: installations
|
|
5340
5340
|
};
|
|
5341
5341
|
}
|
|
5342
|
-
function pathsUnder(root,
|
|
5343
|
-
return [...
|
|
5342
|
+
function pathsUnder(root, paths2) {
|
|
5343
|
+
return [...paths2].map((path) => relative2(root, path)).filter((path) => path !== ".." && !path.startsWith(`..${sep}`) && !isAbsolute3(path)).sort();
|
|
5344
5344
|
}
|
|
5345
5345
|
function normalizeHarnesses(values, global) {
|
|
5346
5346
|
const requested = values?.length ? values : ["claude"];
|
|
@@ -5769,10 +5769,10 @@ import { existsSync as existsSync11 } from "fs";
|
|
|
5769
5769
|
import { cpus, hostname, totalmem } from "os";
|
|
5770
5770
|
import { resolve as resolve11 } from "path";
|
|
5771
5771
|
|
|
5772
|
-
// ../harness/dist/chunk-
|
|
5772
|
+
// ../harness/dist/chunk-RXNHCGWE.js
|
|
5773
5773
|
var HARNESS_PROTOCOL_VERSION = 1;
|
|
5774
5774
|
|
|
5775
|
-
// ../harness/dist/chunk-
|
|
5775
|
+
// ../harness/dist/chunk-CR6RE3A2.js
|
|
5776
5776
|
import { execFile, spawn as spawn3 } from "child_process";
|
|
5777
5777
|
import { constants } from "fs";
|
|
5778
5778
|
import { access } from "fs/promises";
|
|
@@ -6024,12 +6024,12 @@ async function gitSourceFiles(sourceDir, maxFiles, maxBytes) {
|
|
|
6024
6024
|
});
|
|
6025
6025
|
if (outputBytes > 8 * 1024 * 1024) throw new Error("git file inventory exceeds 8 MiB");
|
|
6026
6026
|
if (code !== 0) throw new Error(`git file inventory failed: ${Buffer.concat(stderr2).toString("utf8").slice(0, 1e3)}`);
|
|
6027
|
-
const
|
|
6028
|
-
if (
|
|
6027
|
+
const paths2 = Buffer.concat(stdout).toString("utf8").split("\0").filter(Boolean).sort();
|
|
6028
|
+
if (paths2.length > maxFiles) throw new Error(`workspace exceeds ${maxFiles} files`);
|
|
6029
6029
|
const root = resolve22(sourceDir);
|
|
6030
6030
|
const files = [];
|
|
6031
6031
|
let bytes = 0;
|
|
6032
|
-
for (const relativePath of
|
|
6032
|
+
for (const relativePath of paths2) {
|
|
6033
6033
|
if (!allowedWorkspacePath(relativePath)) continue;
|
|
6034
6034
|
const source = resolve22(root, relativePath);
|
|
6035
6035
|
if (!source.startsWith(`${root}${sep22}`)) throw new TypeError("git file path escapes workspace");
|
|
@@ -6145,7 +6145,7 @@ async function stageWorkspacePair(baselineSource, workspaceSource, options = {})
|
|
|
6145
6145
|
}
|
|
6146
6146
|
}
|
|
6147
6147
|
|
|
6148
|
-
// ../harness/dist/chunk-
|
|
6148
|
+
// ../harness/dist/chunk-ISR434K7.js
|
|
6149
6149
|
import { createHash as createHash3 } from "crypto";
|
|
6150
6150
|
import { readFile as readFile2, readdir as readdir2 } from "fs/promises";
|
|
6151
6151
|
import { relative as relative4, resolve as resolve10 } from "path";
|
|
@@ -6468,13 +6468,13 @@ function validateSnapshot(snapshot, limits) {
|
|
|
6468
6468
|
if (!Number.isSafeInteger(limits.maximumFiles) || limits.maximumFiles < 1 || !Number.isSafeInteger(limits.maximumBytes) || limits.maximumBytes < 1 || snapshot.files.length > limits.maximumFiles) {
|
|
6469
6469
|
throw new CamelError("limit_exceeded", "Code snapshot exceeds its registered limits.");
|
|
6470
6470
|
}
|
|
6471
|
-
const
|
|
6471
|
+
const paths2 = /* @__PURE__ */ new Set();
|
|
6472
6472
|
let bytes = 0;
|
|
6473
6473
|
for (const file of snapshot.files) {
|
|
6474
|
-
if (!file.path || file.path.startsWith("/") || file.path.includes("\\") || file.path.split("/").some((part) => !part || part === "." || part === "..") ||
|
|
6474
|
+
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") {
|
|
6475
6475
|
throw new CamelError("state_conflict", "Code snapshot contains an invalid or duplicate path.");
|
|
6476
6476
|
}
|
|
6477
|
-
|
|
6477
|
+
paths2.add(file.path);
|
|
6478
6478
|
bytes += utf8Length(file.path) + utf8Length(file.content);
|
|
6479
6479
|
}
|
|
6480
6480
|
if (bytes > limits.maximumBytes) {
|
|
@@ -6482,7 +6482,7 @@ function validateSnapshot(snapshot, limits) {
|
|
|
6482
6482
|
}
|
|
6483
6483
|
}
|
|
6484
6484
|
|
|
6485
|
-
// ../harness/dist/chunk-
|
|
6485
|
+
// ../harness/dist/chunk-ISR434K7.js
|
|
6486
6486
|
import { spawn as spawn4 } from "child_process";
|
|
6487
6487
|
import { lstat as lstat2 } from "fs/promises";
|
|
6488
6488
|
import { resolve as resolve23, sep as sep3 } from "path";
|
|
@@ -6628,8 +6628,8 @@ function boundedInteger(value2, spec) {
|
|
|
6628
6628
|
}
|
|
6629
6629
|
function boundedNumber(value2, spec) {
|
|
6630
6630
|
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.");
|
|
6631
|
-
const
|
|
6632
|
-
if (/e/i.test(
|
|
6631
|
+
const text4 = String(value2);
|
|
6632
|
+
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.");
|
|
6633
6633
|
return value2;
|
|
6634
6634
|
}
|
|
6635
6635
|
function enumMember(value2, spec) {
|
|
@@ -6785,11 +6785,11 @@ function validateUnsafeSelector(path, value2, tool) {
|
|
|
6785
6785
|
return void 0;
|
|
6786
6786
|
}
|
|
6787
6787
|
function looksLikeDestination(value2) {
|
|
6788
|
-
const
|
|
6789
|
-
return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(
|
|
6788
|
+
const text4 = value2.trim();
|
|
6789
|
+
return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text4) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text4);
|
|
6790
6790
|
}
|
|
6791
6791
|
|
|
6792
|
-
// ../harness/dist/chunk-
|
|
6792
|
+
// ../harness/dist/chunk-ISR434K7.js
|
|
6793
6793
|
import { readFile as readFile4, stat as stat2 } from "fs/promises";
|
|
6794
6794
|
import { readFile as readFile3 } from "fs/promises";
|
|
6795
6795
|
import { join as join33 } from "path";
|
|
@@ -6954,9 +6954,9 @@ async function extractImports(builder, input) {
|
|
|
6954
6954
|
const sources = input.paths.filter(isSourcePath);
|
|
6955
6955
|
const known = new Set(sources);
|
|
6956
6956
|
for (const path of sources) {
|
|
6957
|
-
let
|
|
6957
|
+
let text4;
|
|
6958
6958
|
try {
|
|
6959
|
-
|
|
6959
|
+
text4 = await input.read(path);
|
|
6960
6960
|
} catch {
|
|
6961
6961
|
continue;
|
|
6962
6962
|
}
|
|
@@ -6964,13 +6964,13 @@ async function extractImports(builder, input) {
|
|
|
6964
6964
|
const file = builder.node(FILE, path, pkg ? { pkg } : void 0);
|
|
6965
6965
|
if (pkg) builder.edge(builder.node(PACKAGE, pkg), CONTAINS, file);
|
|
6966
6966
|
const specifiers = /* @__PURE__ */ new Set();
|
|
6967
|
-
for (const match of
|
|
6968
|
-
for (const match of
|
|
6967
|
+
for (const match of text4.matchAll(IMPORT_FROM)) specifiers.add(match[1]);
|
|
6968
|
+
for (const match of text4.matchAll(BARE_IMPORT)) specifiers.add(match[1]);
|
|
6969
6969
|
for (const specifier of specifiers) {
|
|
6970
6970
|
const resolved = resolveImport(path, specifier, known);
|
|
6971
6971
|
if (resolved) builder.edge(file, IMPORTS, nodeId(FILE, resolved));
|
|
6972
6972
|
}
|
|
6973
|
-
for (const name of exportedNames(
|
|
6973
|
+
for (const name of exportedNames(text4)) {
|
|
6974
6974
|
builder.edge(file, EXPORTS, builder.node(SYMBOL, name));
|
|
6975
6975
|
}
|
|
6976
6976
|
}
|
|
@@ -7011,16 +7011,16 @@ async function extractData(builder, input) {
|
|
|
7011
7011
|
};
|
|
7012
7012
|
for (const path of input.paths) {
|
|
7013
7013
|
if (!SOURCE_FILE.test(path) || input.ignore?.(path)) continue;
|
|
7014
|
-
let
|
|
7014
|
+
let text4;
|
|
7015
7015
|
try {
|
|
7016
|
-
|
|
7016
|
+
text4 = await input.read(path);
|
|
7017
7017
|
} catch {
|
|
7018
7018
|
continue;
|
|
7019
7019
|
}
|
|
7020
|
-
for (const statement of
|
|
7020
|
+
for (const statement of text4.matchAll(STATEMENT)) {
|
|
7021
7021
|
const verb = statement[1].toUpperCase().replace(/\s+/g, " ");
|
|
7022
7022
|
const start = statement.index ?? 0;
|
|
7023
|
-
const rest =
|
|
7023
|
+
const rest = text4.slice(start + statement[0].length, start + STATEMENT_WINDOW);
|
|
7024
7024
|
if (verb === "SELECT") {
|
|
7025
7025
|
for (const read3 of rest.matchAll(READ_TABLES)) touch(path, read3[1].toLowerCase(), TABLE, READS);
|
|
7026
7026
|
continue;
|
|
@@ -7036,16 +7036,16 @@ async function extractData(builder, input) {
|
|
|
7036
7036
|
for (const read3 of rest.matchAll(READ_TABLES)) touch(path, read3[1].toLowerCase(), TABLE, READS);
|
|
7037
7037
|
}
|
|
7038
7038
|
}
|
|
7039
|
-
for (const match of
|
|
7040
|
-
touch(path, `${match[1]}.${match[2]}`, NAMESPACE, accessFor(
|
|
7039
|
+
for (const match of text4.matchAll(NS_CONST)) {
|
|
7040
|
+
touch(path, `${match[1]}.${match[2]}`, NAMESPACE, accessFor(text4, match.index ?? 0));
|
|
7041
7041
|
}
|
|
7042
|
-
for (const match of
|
|
7043
|
-
touch(path, match[1], NAMESPACE, accessFor(
|
|
7042
|
+
for (const match of text4.matchAll(NS_LITERAL)) {
|
|
7043
|
+
touch(path, match[1], NAMESPACE, accessFor(text4, match.index ?? 0));
|
|
7044
7044
|
}
|
|
7045
7045
|
}
|
|
7046
7046
|
}
|
|
7047
|
-
function accessFor(
|
|
7048
|
-
const window =
|
|
7047
|
+
function accessFor(text4, index) {
|
|
7048
|
+
const window = text4.slice(Math.max(0, index - 160), index + 40);
|
|
7049
7049
|
return /\b(?:transact|update|delete|create|insert|Ops)\b/.test(window) ? WRITES : READS;
|
|
7050
7050
|
}
|
|
7051
7051
|
async function buildCodeGraph(input) {
|
|
@@ -7057,7 +7057,7 @@ async function buildCodeGraph(input) {
|
|
|
7057
7057
|
return builder.build();
|
|
7058
7058
|
}
|
|
7059
7059
|
|
|
7060
|
-
// ../harness/dist/chunk-
|
|
7060
|
+
// ../harness/dist/chunk-ISR434K7.js
|
|
7061
7061
|
import { createHash as createHash32 } from "crypto";
|
|
7062
7062
|
async function digestStagedWorkspace(root, limits) {
|
|
7063
7063
|
const files = [];
|
|
@@ -7385,7 +7385,7 @@ function validateCodePatch(rawPatch, maxBytes) {
|
|
|
7385
7385
|
if (FORBIDDEN.test(patch2) || /(?:old|new)(?: file)? mode 120000/.test(patch2)) {
|
|
7386
7386
|
throw new TypeError("patch uses a forbidden binary, link, mode, rename, or copy operation");
|
|
7387
7387
|
}
|
|
7388
|
-
const
|
|
7388
|
+
const paths2 = [];
|
|
7389
7389
|
const lines = patch2.split("\n");
|
|
7390
7390
|
for (let index = 0; index < lines.length; index += 1) {
|
|
7391
7391
|
const line2 = lines[index];
|
|
@@ -7401,10 +7401,10 @@ function validateCodePatch(rawPatch, maxBytes) {
|
|
|
7401
7401
|
if (!validHeaderPath(oldPath, path, "a") || !validHeaderPath(newPath, path, "b")) {
|
|
7402
7402
|
throw new TypeError("patch file headers do not match the declared path");
|
|
7403
7403
|
}
|
|
7404
|
-
|
|
7404
|
+
paths2.push(path);
|
|
7405
7405
|
}
|
|
7406
|
-
if (!
|
|
7407
|
-
return
|
|
7406
|
+
if (!paths2.length || new Set(paths2).size !== paths2.length) throw new TypeError("patch has no diffs or repeats a path");
|
|
7407
|
+
return paths2;
|
|
7408
7408
|
}
|
|
7409
7409
|
function validHeaderPath(value2, path, prefix) {
|
|
7410
7410
|
return value2 === "/dev/null" || value2 === `${prefix}/${path}`;
|
|
@@ -7429,11 +7429,11 @@ function describePatchFailure(patch2, detail) {
|
|
|
7429
7429
|
const hint = hunks.length > 0 && contextless ? " A hunk has no context lines; include at least one unchanged line above or below each change." : "";
|
|
7430
7430
|
return `patch did not apply: ${detail}${hint}`;
|
|
7431
7431
|
}
|
|
7432
|
-
async function applyCodePatch(workspaceDir, rawPatch,
|
|
7432
|
+
async function applyCodePatch(workspaceDir, rawPatch, paths2) {
|
|
7433
7433
|
const patch2 = stripPatchEnvelope(rawPatch);
|
|
7434
7434
|
await gitApply(workspaceDir, patch2, true);
|
|
7435
7435
|
await gitApply(workspaceDir, patch2, false);
|
|
7436
|
-
for (const path of
|
|
7436
|
+
for (const path of paths2) {
|
|
7437
7437
|
try {
|
|
7438
7438
|
const info = await lstat2(resolveCodePath(workspaceDir, path));
|
|
7439
7439
|
if (info.isSymbolicLink() || !info.isFile() && !info.isDirectory()) {
|
|
@@ -7455,8 +7455,8 @@ function gitApply(cwd, patch2, check) {
|
|
|
7455
7455
|
});
|
|
7456
7456
|
let stderr2 = "";
|
|
7457
7457
|
child.stderr.setEncoding("utf8");
|
|
7458
|
-
child.stderr.on("data", (
|
|
7459
|
-
if (stderr2.length < 4e3) stderr2 +=
|
|
7458
|
+
child.stderr.on("data", (text22) => {
|
|
7459
|
+
if (stderr2.length < 4e3) stderr2 += text22.slice(0, 4e3);
|
|
7460
7460
|
});
|
|
7461
7461
|
child.once("error", reject);
|
|
7462
7462
|
child.once("exit", (code) => code === 0 ? accept() : reject(new TypeError(describePatchFailure(patch2, stderr2.trim().slice(0, 500)))));
|
|
@@ -7480,8 +7480,8 @@ async function restoreCodeWorkspaceCheckpoint(input) {
|
|
|
7480
7480
|
const workspace = await stageWorkspace(input.trustedBaseDir, input.stage);
|
|
7481
7481
|
try {
|
|
7482
7482
|
if (checkpoint.patch) {
|
|
7483
|
-
const
|
|
7484
|
-
await applyCodePatch(workspace.workspaceDir, checkpoint.patch,
|
|
7483
|
+
const paths2 = validateCodePatch(checkpoint.patch, 256 * 1024);
|
|
7484
|
+
await applyCodePatch(workspace.workspaceDir, checkpoint.patch, paths2);
|
|
7485
7485
|
}
|
|
7486
7486
|
return { workspace, checkpoint };
|
|
7487
7487
|
} catch (error) {
|
|
@@ -7630,8 +7630,8 @@ async function verifyCodeCandidate(input) {
|
|
|
7630
7630
|
try {
|
|
7631
7631
|
const baseDigest = await digestStagedWorkspace(staged.workspaceDir, limits);
|
|
7632
7632
|
if (baseDigest !== input.trustedBaseDigest) throw new TypeError("trusted base does not match its registered digest");
|
|
7633
|
-
const
|
|
7634
|
-
await applyCodePatch(staged.workspaceDir, input.candidatePatch,
|
|
7633
|
+
const paths2 = validateCodePatch(input.candidatePatch, policy.maximumPatchBytes);
|
|
7634
|
+
await applyCodePatch(staged.workspaceDir, input.candidatePatch, paths2);
|
|
7635
7635
|
const sourceDigest = await digestStagedWorkspace(staged.workspaceDir, limits);
|
|
7636
7636
|
const policyDigest = digestPolicy(policy);
|
|
7637
7637
|
const patchDigest = digestBytes(input.candidatePatch);
|
|
@@ -7640,7 +7640,7 @@ async function verifyCodeCandidate(input) {
|
|
|
7640
7640
|
trustedBaseDigest: input.trustedBaseDigest,
|
|
7641
7641
|
patchDigest
|
|
7642
7642
|
});
|
|
7643
|
-
const changedTests = changedTestPaths(
|
|
7643
|
+
const changedTests = changedTestPaths(paths2, policy);
|
|
7644
7644
|
if (changedTests.length > policy.maximumChangedTests) throw new TypeError("candidate changes too many test files");
|
|
7645
7645
|
const recipes = [];
|
|
7646
7646
|
const logs = [];
|
|
@@ -7707,8 +7707,8 @@ function validate2(input) {
|
|
|
7707
7707
|
}
|
|
7708
7708
|
return result;
|
|
7709
7709
|
}
|
|
7710
|
-
function changedTestPaths(
|
|
7711
|
-
return
|
|
7710
|
+
function changedTestPaths(paths2, policy) {
|
|
7711
|
+
return paths2.filter((path) => policy.testPathSuffixes.some((suffix) => path.endsWith(suffix)) || policy.testPathPrefixes.some((prefix) => path.startsWith(prefix) || path.includes(`/${prefix}`))).sort();
|
|
7712
7712
|
}
|
|
7713
7713
|
function recipeReceipt(recipe2, result, artifacts) {
|
|
7714
7714
|
const status = result.timedOut ? "timed_out" : result.outputLimitExceeded ? "output_limited" : result.exitCode === 0 && artifacts.every((item) => item.status === "verified") ? "passed" : "failed";
|
|
@@ -8439,7 +8439,7 @@ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = regi
|
|
|
8439
8439
|
files(root) {
|
|
8440
8440
|
const existing = cache2.get(root);
|
|
8441
8441
|
if (existing) return existing;
|
|
8442
|
-
const pending = enumerate(root, limit).then((
|
|
8442
|
+
const pending = enumerate(root, limit).then((paths2) => Object.freeze(paths2));
|
|
8443
8443
|
cache2.set(root, pending);
|
|
8444
8444
|
void pending.catch(() => {
|
|
8445
8445
|
if (cache2.get(root) === pending) cache2.delete(root);
|
|
@@ -8452,7 +8452,7 @@ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = regi
|
|
|
8452
8452
|
};
|
|
8453
8453
|
}
|
|
8454
8454
|
async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
|
|
8455
|
-
const
|
|
8455
|
+
const paths2 = [];
|
|
8456
8456
|
const walk = async (directory) => {
|
|
8457
8457
|
for (const entry of await readdir22(directory, { withFileTypes: true })) {
|
|
8458
8458
|
if (SKIP_WORKSPACE_DIRS.has(entry.name)) continue;
|
|
@@ -8466,26 +8466,26 @@ async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
|
|
|
8466
8466
|
} catch {
|
|
8467
8467
|
continue;
|
|
8468
8468
|
}
|
|
8469
|
-
|
|
8470
|
-
if (
|
|
8469
|
+
paths2.push(path);
|
|
8470
|
+
if (paths2.length > limit) throw new TypeError("workspace file registry exceeds its bound");
|
|
8471
8471
|
}
|
|
8472
8472
|
}
|
|
8473
8473
|
};
|
|
8474
8474
|
await walk(resolve42(root));
|
|
8475
|
-
return
|
|
8475
|
+
return paths2.sort();
|
|
8476
8476
|
}
|
|
8477
|
-
function listWorkspace(
|
|
8477
|
+
function listWorkspace(paths2, options = {}) {
|
|
8478
8478
|
const max = options.maxEntries ?? 1e3;
|
|
8479
8479
|
const prefix = options.prefix?.replace(/\/+$/, "");
|
|
8480
|
-
const scoped = prefix ?
|
|
8480
|
+
const scoped = prefix ? paths2.filter((path) => path === prefix || path.startsWith(`${prefix}/`)) : [...paths2];
|
|
8481
8481
|
return scoped.slice(0, max);
|
|
8482
8482
|
}
|
|
8483
|
-
async function searchWorkspace(root,
|
|
8483
|
+
async function searchWorkspace(root, paths2, options) {
|
|
8484
8484
|
options.signal?.throwIfAborted();
|
|
8485
8485
|
if (!options.query) throw new TypeError("search query must be a non-empty string");
|
|
8486
8486
|
const maxResults = options.maxResults ?? DEFAULT_MAX_RESULTS;
|
|
8487
8487
|
const maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES;
|
|
8488
|
-
const scoped = listWorkspace(
|
|
8488
|
+
const scoped = listWorkspace(paths2, { ...options.prefix ? { prefix: options.prefix } : {}, maxEntries: paths2.length });
|
|
8489
8489
|
if (scoped.length === 0) return [];
|
|
8490
8490
|
try {
|
|
8491
8491
|
return await nativeSearch(root, scoped, { ...options, maxResults, maxFileBytes });
|
|
@@ -8495,11 +8495,11 @@ async function searchWorkspace(root, paths, options) {
|
|
|
8495
8495
|
}
|
|
8496
8496
|
}
|
|
8497
8497
|
var MAX_NATIVE_ARG_BYTES = 96 * 1024;
|
|
8498
|
-
async function nativeSearch(root,
|
|
8498
|
+
async function nativeSearch(root, paths2, options) {
|
|
8499
8499
|
const batches = [];
|
|
8500
8500
|
let batch = [];
|
|
8501
8501
|
let bytes = 0;
|
|
8502
|
-
for (const path of
|
|
8502
|
+
for (const path of paths2) {
|
|
8503
8503
|
const size = Buffer.byteLength(path) + 1;
|
|
8504
8504
|
if (batch.length > 0 && bytes + size > MAX_NATIVE_ARG_BYTES) {
|
|
8505
8505
|
batches.push(batch);
|
|
@@ -8518,7 +8518,7 @@ async function nativeSearch(root, paths, options) {
|
|
|
8518
8518
|
}
|
|
8519
8519
|
return matches;
|
|
8520
8520
|
}
|
|
8521
|
-
function nativeSearchBatch(root,
|
|
8521
|
+
function nativeSearchBatch(root, paths2, options, remaining) {
|
|
8522
8522
|
return new Promise((resolveMatches, reject) => {
|
|
8523
8523
|
const args = [
|
|
8524
8524
|
"--fixed-strings",
|
|
@@ -8531,7 +8531,7 @@ function nativeSearchBatch(root, paths, options, remaining) {
|
|
|
8531
8531
|
options.caseSensitive === false ? "--ignore-case" : "--case-sensitive",
|
|
8532
8532
|
"--",
|
|
8533
8533
|
options.query,
|
|
8534
|
-
...
|
|
8534
|
+
...paths2
|
|
8535
8535
|
];
|
|
8536
8536
|
const child = spawn33("rg", args, {
|
|
8537
8537
|
cwd: root,
|
|
@@ -8717,16 +8717,16 @@ function createCodePolicyGate(options) {
|
|
|
8717
8717
|
}
|
|
8718
8718
|
};
|
|
8719
8719
|
}
|
|
8720
|
-
function directoryPrefixes(
|
|
8720
|
+
function directoryPrefixes(paths2) {
|
|
8721
8721
|
const prefixes = /* @__PURE__ */ new Set(["."]);
|
|
8722
|
-
for (const path of
|
|
8722
|
+
for (const path of paths2) {
|
|
8723
8723
|
const parts = path.split("/");
|
|
8724
8724
|
for (let index = 1; index < parts.length; index += 1) prefixes.add(parts.slice(0, index).join("/"));
|
|
8725
8725
|
}
|
|
8726
8726
|
return [...prefixes].sort();
|
|
8727
8727
|
}
|
|
8728
|
-
async function safePrefix(base,
|
|
8729
|
-
const prefixes = directoryPrefixes(
|
|
8728
|
+
async function safePrefix(base, paths2, prefix) {
|
|
8729
|
+
const prefixes = directoryPrefixes(paths2);
|
|
8730
8730
|
const conversions = await conversionRegistry(
|
|
8731
8731
|
[await registeredPolicy("code.prefix.v1", "code.prefixes.v1", prefixes)],
|
|
8732
8732
|
{ "code.prefixes.v1": prefixes }
|
|
@@ -8838,7 +8838,7 @@ function response(request3, ok, content2, details) {
|
|
|
8838
8838
|
return { requestId: request3.requestId, ok, content: content2, ...details ? { details } : {} };
|
|
8839
8839
|
}
|
|
8840
8840
|
var cache = /* @__PURE__ */ new Map();
|
|
8841
|
-
function workspaceGraphs(workspaceDir,
|
|
8841
|
+
function workspaceGraphs(workspaceDir, paths2) {
|
|
8842
8842
|
const existing = cache.get(workspaceDir);
|
|
8843
8843
|
if (existing) return existing;
|
|
8844
8844
|
const read22 = (path) => readFile3(join33(workspaceDir, path), "utf8");
|
|
@@ -8846,7 +8846,7 @@ function workspaceGraphs(workspaceDir, paths) {
|
|
|
8846
8846
|
// No knownTables: a staged workspace may not carry migrations, and a filter
|
|
8847
8847
|
// that silently drops every table is worse than an unfiltered one. Callers
|
|
8848
8848
|
// with ground truth should build the graph themselves.
|
|
8849
|
-
graph: await buildCodeGraph({ paths, read: read22, data: { ignore: (path) => path.includes(".generated.") } })
|
|
8849
|
+
graph: await buildCodeGraph({ paths: paths2, read: read22, data: { ignore: (path) => path.includes(".generated.") } })
|
|
8850
8850
|
}))();
|
|
8851
8851
|
cache.set(workspaceDir, built);
|
|
8852
8852
|
return built;
|
|
@@ -8906,11 +8906,11 @@ async function read(context, request3, options, policy, registry) {
|
|
|
8906
8906
|
if (endLine < startLine || endLine - startLine + 1 > (options.maxReadLines ?? 2e3)) {
|
|
8907
8907
|
throw new TypeError("requested line range exceeds its bound");
|
|
8908
8908
|
}
|
|
8909
|
-
const
|
|
8910
|
-
if (!
|
|
8909
|
+
const paths2 = await registry.files(context.workspaceDir);
|
|
8910
|
+
if (!paths2.includes(path)) {
|
|
8911
8911
|
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.`);
|
|
8912
8912
|
}
|
|
8913
|
-
const allowed = await policy.read(policyContext(context, request3, options, { paths, path, startLine, endLine }));
|
|
8913
|
+
const allowed = await policy.read(policyContext(context, request3, options, { paths: paths2, path, startLine, endLine }));
|
|
8914
8914
|
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
8915
8915
|
const target = resolveCodePath(context.workspaceDir, path);
|
|
8916
8916
|
const info = await stat2(target);
|
|
@@ -8932,16 +8932,16 @@ async function list(context, request3, options, policy, registry) {
|
|
|
8932
8932
|
const prefix = typeof raw === "string" && raw.length > 0 ? raw : void 0;
|
|
8933
8933
|
const maxEntries = optionalInteger(request3.input.maxEntries) ?? 1e3;
|
|
8934
8934
|
if (maxEntries > 5e3) throw new TypeError("maxEntries exceeds its bound");
|
|
8935
|
-
const
|
|
8936
|
-
const allowed = await policy.list(policyContext(context, request3, options, { paths, ...prefix ? { prefix } : {} }));
|
|
8935
|
+
const paths2 = await registry.files(context.workspaceDir);
|
|
8936
|
+
const allowed = await policy.list(policyContext(context, request3, options, { paths: paths2, ...prefix ? { prefix } : {} }));
|
|
8937
8937
|
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
8938
|
-
const entries = listWorkspace(
|
|
8938
|
+
const entries = listWorkspace(paths2, { ...prefix ? { prefix } : {}, maxEntries });
|
|
8939
8939
|
if (!entries.length) {
|
|
8940
8940
|
return response(request3, true, prefix ? `No files under "${prefix}".` : "Workspace is empty.", { count: 0 });
|
|
8941
8941
|
}
|
|
8942
|
-
const truncated = entries.length <
|
|
8943
|
-
const hint = !prefix &&
|
|
8944
|
-
\u2026 ${
|
|
8942
|
+
const truncated = entries.length < paths2.length && entries.length === maxEntries;
|
|
8943
|
+
const hint = !prefix && paths2.length > 500 ? `
|
|
8944
|
+
\u2026 ${paths2.length} files total. sandbox.overview is far cheaper for orientation; use a prefix here once you know the area.` : "";
|
|
8945
8945
|
return response(
|
|
8946
8946
|
request3,
|
|
8947
8947
|
true,
|
|
@@ -8959,10 +8959,10 @@ async function search(context, request3, options, policy, registry) {
|
|
|
8959
8959
|
const maxResults = optionalInteger(request3.input.maxResults) ?? 100;
|
|
8960
8960
|
if (maxResults > 500) throw new TypeError("maxResults exceeds its bound");
|
|
8961
8961
|
const caseSensitive = request3.input.caseSensitive === void 0 ? true : request3.input.caseSensitive === true;
|
|
8962
|
-
const
|
|
8963
|
-
const allowed = await policy.search(policyContext(context, request3, options, { paths, query, ...prefix ? { prefix } : {} }));
|
|
8962
|
+
const paths2 = await registry.files(context.workspaceDir);
|
|
8963
|
+
const allowed = await policy.search(policyContext(context, request3, options, { paths: paths2, query, ...prefix ? { prefix } : {} }));
|
|
8964
8964
|
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
8965
|
-
const matches = await searchWorkspace(context.workspaceDir,
|
|
8965
|
+
const matches = await searchWorkspace(context.workspaceDir, paths2, {
|
|
8966
8966
|
query,
|
|
8967
8967
|
maxResults,
|
|
8968
8968
|
caseSensitive,
|
|
@@ -8984,8 +8984,8 @@ async function graphQuery(context, request3, options, policy, registry) {
|
|
|
8984
8984
|
selector: query
|
|
8985
8985
|
}));
|
|
8986
8986
|
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
8987
|
-
const
|
|
8988
|
-
const graphs = await workspaceGraphs(context.workspaceDir,
|
|
8987
|
+
const paths2 = await registry.files(context.workspaceDir);
|
|
8988
|
+
const graphs = await workspaceGraphs(context.workspaceDir, paths2);
|
|
8989
8989
|
if (request3.tool === "sandbox.overview") {
|
|
8990
8990
|
return response(request3, true, renderOverview(graphs, query || void 0));
|
|
8991
8991
|
}
|
|
@@ -9047,16 +9047,16 @@ function toolFailureMessage(reason) {
|
|
|
9047
9047
|
async function patch(context, request3, options, policy, registry) {
|
|
9048
9048
|
exactKeys(request3.input, ["patch"]);
|
|
9049
9049
|
const value2 = stringField(request3.input, "patch");
|
|
9050
|
-
const
|
|
9051
|
-
if (
|
|
9050
|
+
const paths2 = validateCodePatch(value2, options.maxPatchBytes ?? 256 * 1024);
|
|
9051
|
+
if (paths2.some((path) => options.readOnlyPrefixes?.some((prefix) => path === prefix || path.startsWith(`${prefix}/`)))) {
|
|
9052
9052
|
throw new TypeError("patch targets a read-only reference source");
|
|
9053
9053
|
}
|
|
9054
9054
|
const allowed = await policy.patch(policyContext(context, request3, options, { patch: value2 }));
|
|
9055
9055
|
if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
|
|
9056
|
-
await applyCodePatch(context.workspaceDir, value2,
|
|
9056
|
+
await applyCodePatch(context.workspaceDir, value2, paths2);
|
|
9057
9057
|
registry.invalidate(context.workspaceDir);
|
|
9058
9058
|
forgetWorkspaceGraphs(context.workspaceDir);
|
|
9059
|
-
return response(request3, true, `Applied patch to ${
|
|
9059
|
+
return response(request3, true, `Applied patch to ${paths2.length} file(s).`, { paths: paths2 });
|
|
9060
9060
|
}
|
|
9061
9061
|
async function recipe(context, request3, options, recipes, policy) {
|
|
9062
9062
|
exactKeys(request3.input, ["recipeId"]);
|
|
@@ -9405,11 +9405,126 @@ async function startGoalPursuit(input) {
|
|
|
9405
9405
|
async function appendCodeRuntimeEvent(control, command, event, refs) {
|
|
9406
9406
|
const eventId = `${command.commandId.slice(0, 45)}:${refs.length + 1}`;
|
|
9407
9407
|
refs.push(eventId);
|
|
9408
|
-
const
|
|
9408
|
+
const attributed = { ...event, interactionId: command.commandId };
|
|
9409
|
+
const bounded = attributed.type === "message" ? { ...attributed, body: attributed.body.trim().slice(0, 2e4) || `${attributed.actor} event` } : attributed;
|
|
9409
9410
|
await control.appendSessionEvent(command.sessionId, eventId, bounded);
|
|
9410
9411
|
}
|
|
9411
9412
|
var digestRuntimeValue = (value2) => `sha256:${createHash32("sha256").update(value2).digest("hex")}`;
|
|
9412
9413
|
var runtimeErrorMessage = (value2) => value2 instanceof Error ? value2.message : String(value2);
|
|
9414
|
+
var text3 = (value2, maximum) => {
|
|
9415
|
+
if (typeof value2 !== "string") return void 0;
|
|
9416
|
+
const bounded = value2.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ").trim();
|
|
9417
|
+
return bounded ? bounded.slice(0, maximum) : void 0;
|
|
9418
|
+
};
|
|
9419
|
+
var integer2 = (value2) => Number.isSafeInteger(value2) && Number(value2) >= 0 ? Number(value2) : void 0;
|
|
9420
|
+
var record32 = (value2) => value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : void 0;
|
|
9421
|
+
var excerpt = (value2, tail = false) => {
|
|
9422
|
+
if (typeof value2 !== "string") return void 0;
|
|
9423
|
+
const safe = value2.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ");
|
|
9424
|
+
const source = (tail ? safe.slice(-1e4) : safe.slice(0, 1e4)).trim();
|
|
9425
|
+
if (!source) return void 0;
|
|
9426
|
+
const lines = source.split("\n").filter((line2) => line2.trim()).map((line2) => line2.slice(0, 240));
|
|
9427
|
+
const selected = tail ? lines.slice(-10) : lines.slice(0, 10);
|
|
9428
|
+
return text3(selected.join("\n"), 2400);
|
|
9429
|
+
};
|
|
9430
|
+
var paths = (value2) => {
|
|
9431
|
+
if (!Array.isArray(value2)) return void 0;
|
|
9432
|
+
const items = value2.flatMap((item) => {
|
|
9433
|
+
const path = text3(item, 1024);
|
|
9434
|
+
return path ? [path] : [];
|
|
9435
|
+
}).slice(0, 12);
|
|
9436
|
+
return items.length ? items : void 0;
|
|
9437
|
+
};
|
|
9438
|
+
function patchStats(value2) {
|
|
9439
|
+
if (typeof value2 !== "string") return {};
|
|
9440
|
+
let additions = 0;
|
|
9441
|
+
let deletions = 0;
|
|
9442
|
+
for (const line2 of value2.slice(0, 262144).split("\n")) {
|
|
9443
|
+
if (line2.startsWith("+++") || line2.startsWith("---")) continue;
|
|
9444
|
+
if (line2.startsWith("+")) additions += 1;
|
|
9445
|
+
else if (line2.startsWith("-")) deletions += 1;
|
|
9446
|
+
}
|
|
9447
|
+
return { ...additions ? { additions } : {}, ...deletions ? { deletions } : {} };
|
|
9448
|
+
}
|
|
9449
|
+
function searchResults(value2) {
|
|
9450
|
+
if (typeof value2 !== "string") return void 0;
|
|
9451
|
+
const results = value2.split("\n").flatMap((line2) => {
|
|
9452
|
+
const match = /^([^:\n]{1,1024}):(\d+):\s?(.*)$/.exec(line2);
|
|
9453
|
+
if (!match) return [];
|
|
9454
|
+
const lineNumber = Number(match[2]);
|
|
9455
|
+
const itemText = text3(match[3], 240);
|
|
9456
|
+
if (!Number.isSafeInteger(lineNumber) || lineNumber < 1) return [];
|
|
9457
|
+
return [{ path: match[1], line: lineNumber, ...itemText ? { text: itemText } : {} }];
|
|
9458
|
+
}).slice(0, 5);
|
|
9459
|
+
return results.length ? results : void 0;
|
|
9460
|
+
}
|
|
9461
|
+
function codeToolRequestPresentation(request3) {
|
|
9462
|
+
const input = request3.input;
|
|
9463
|
+
if (request3.tool === "sandbox.read") {
|
|
9464
|
+
const path = text3(input.path, 1024);
|
|
9465
|
+
if (!path) return void 0;
|
|
9466
|
+
const startLine = integer2(input.startLine);
|
|
9467
|
+
const endLine = integer2(input.endLine);
|
|
9468
|
+
return { kind: "read", path, ...startLine ? { startLine } : {}, ...endLine ? { endLine } : {} };
|
|
9469
|
+
}
|
|
9470
|
+
if (request3.tool === "sandbox.list") {
|
|
9471
|
+
const scope = text3(input.prefix, 1024);
|
|
9472
|
+
return { kind: "list", ...scope ? { scope } : {} };
|
|
9473
|
+
}
|
|
9474
|
+
if (request3.tool === "sandbox.search" || request3.tool === "sandbox.overview" || request3.tool === "sandbox.where_is" || request3.tool === "sandbox.who_imports" || request3.tool === "sandbox.who_touches") {
|
|
9475
|
+
const query = text3(input.query, 512);
|
|
9476
|
+
const scope = request3.tool === "sandbox.search" ? text3(input.prefix, 1024) : void 0;
|
|
9477
|
+
if (request3.tool === "sandbox.search" && !query) return void 0;
|
|
9478
|
+
return { kind: "query", ...query ? { query } : {}, ...scope ? { scope } : {} };
|
|
9479
|
+
}
|
|
9480
|
+
if (request3.tool === "sandbox.apply_patch") {
|
|
9481
|
+
return { kind: "patch", ...patchStats(input.patch) };
|
|
9482
|
+
}
|
|
9483
|
+
const recipeId = text3(input.recipeId, 120);
|
|
9484
|
+
return recipeId ? { kind: "recipe", recipeId } : void 0;
|
|
9485
|
+
}
|
|
9486
|
+
function codeToolResultPresentation(request3, response2) {
|
|
9487
|
+
const started = codeToolRequestPresentation(request3);
|
|
9488
|
+
if (!started || !response2.ok) return started;
|
|
9489
|
+
const details = record32(response2.details);
|
|
9490
|
+
if (started.kind === "read") {
|
|
9491
|
+
return {
|
|
9492
|
+
...started,
|
|
9493
|
+
...integer2(details?.startLine) ? { startLine: integer2(details?.startLine) } : {},
|
|
9494
|
+
...integer2(details?.endLine) ? { endLine: integer2(details?.endLine) } : {},
|
|
9495
|
+
...excerpt(response2.content) ? { excerpt: excerpt(response2.content) } : {}
|
|
9496
|
+
};
|
|
9497
|
+
}
|
|
9498
|
+
if (started.kind === "list") {
|
|
9499
|
+
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);
|
|
9500
|
+
return {
|
|
9501
|
+
...started,
|
|
9502
|
+
...integer2(details?.count) !== void 0 ? { count: integer2(details?.count) } : {},
|
|
9503
|
+
...listed.length ? { paths: listed } : {}
|
|
9504
|
+
};
|
|
9505
|
+
}
|
|
9506
|
+
if (started.kind === "query") {
|
|
9507
|
+
const results = request3.tool === "sandbox.search" ? searchResults(response2.content) : void 0;
|
|
9508
|
+
const resultExcerpt = request3.tool === "sandbox.search" ? void 0 : excerpt(response2.content);
|
|
9509
|
+
return {
|
|
9510
|
+
...started,
|
|
9511
|
+
...integer2(details?.count) !== void 0 ? { count: integer2(details?.count) } : {},
|
|
9512
|
+
...results ? { results } : {},
|
|
9513
|
+
...resultExcerpt ? { excerpt: resultExcerpt } : {}
|
|
9514
|
+
};
|
|
9515
|
+
}
|
|
9516
|
+
if (started.kind === "patch") {
|
|
9517
|
+
return { ...started, ...paths(details?.paths) ? { paths: paths(details?.paths) } : {} };
|
|
9518
|
+
}
|
|
9519
|
+
const output = response2.content.replace(/^Recipe [^\n]*\.?\s*/u, "");
|
|
9520
|
+
return {
|
|
9521
|
+
...started,
|
|
9522
|
+
...integer2(details?.exitCode) !== void 0 ? { exitCode: integer2(details?.exitCode) } : {},
|
|
9523
|
+
...typeof details?.timedOut === "boolean" ? { timedOut: details.timedOut } : {},
|
|
9524
|
+
...typeof details?.outputLimitExceeded === "boolean" ? { outputLimitExceeded: details.outputLimitExceeded } : {},
|
|
9525
|
+
...excerpt(output, true) ? { excerpt: excerpt(output, true) } : {}
|
|
9526
|
+
};
|
|
9527
|
+
}
|
|
9413
9528
|
var TheseusRuntimeEngine = class {
|
|
9414
9529
|
constructor(options) {
|
|
9415
9530
|
this.options = options;
|
|
@@ -9643,18 +9758,29 @@ var TheseusRuntimeEngine = class {
|
|
|
9643
9758
|
return {
|
|
9644
9759
|
execute: async (context, request3) => {
|
|
9645
9760
|
const startedAt = Date.now();
|
|
9761
|
+
const operationId = digestRuntimeValue(`${command.commandId}:${request3.requestId}`);
|
|
9762
|
+
const startedPresentation = codeToolRequestPresentation(request3);
|
|
9646
9763
|
await this.#event(
|
|
9647
9764
|
command,
|
|
9648
|
-
{
|
|
9765
|
+
{
|
|
9766
|
+
type: "tool",
|
|
9767
|
+
phase: "started",
|
|
9768
|
+
tool: request3.tool,
|
|
9769
|
+
operationId,
|
|
9770
|
+
...startedPresentation ? { presentation: startedPresentation } : {}
|
|
9771
|
+
},
|
|
9649
9772
|
active.conversationRefs
|
|
9650
9773
|
).catch(() => void 0);
|
|
9651
9774
|
const response2 = await broker.execute(context, request3);
|
|
9775
|
+
const completedPresentation = codeToolResultPresentation(request3, response2);
|
|
9652
9776
|
await this.#event(command, {
|
|
9653
9777
|
type: "tool",
|
|
9654
9778
|
phase: "completed",
|
|
9655
9779
|
tool: request3.tool,
|
|
9656
9780
|
ok: response2.ok,
|
|
9657
|
-
durationMs: Date.now() - startedAt
|
|
9781
|
+
durationMs: Date.now() - startedAt,
|
|
9782
|
+
operationId,
|
|
9783
|
+
...completedPresentation ? { presentation: completedPresentation } : {}
|
|
9658
9784
|
}, active.conversationRefs).catch(() => void 0);
|
|
9659
9785
|
return response2;
|
|
9660
9786
|
}
|
|
@@ -10697,7 +10823,8 @@ Usage:
|
|
|
10697
10823
|
odla-ai pm bug list [--app <id>] [--status <s>] [--severity <s>] [--goal <id>] [--assignee <id>] [--decision <id>] [--q <text>] [--limit <n>] [--offset <n>] [--json]
|
|
10698
10824
|
odla-ai pm goal add --app <id> --title <t> [--status <s>] [--proof <text>] [--target <pct>] [--mutation-id <id>] [--json]
|
|
10699
10825
|
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]
|
|
10700
|
-
odla-ai pm next --app <id> [--project <id>] [--json]
|
|
10826
|
+
odla-ai pm next --app <id> [--project <id>] [--verbose] [--json]
|
|
10827
|
+
odla-ai pm start --app <id> [<task-id>] [--goal <id>] [--verbose] [--mutation-id <id>] [--json] [claims the top Ready task in one call]
|
|
10701
10828
|
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]
|
|
10702
10829
|
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]
|
|
10703
10830
|
odla-ai pm task claim <id> --expected-revision <n> [--mutation-id <id>] [--json]
|
|
@@ -10718,7 +10845,7 @@ Usage:
|
|
|
10718
10845
|
odla-ai pm <goal|task|decision|bug> comments <id> [--json]
|
|
10719
10846
|
odla-ai pm <goal|task|decision|bug> history <id> [--limit <n>] [--json]
|
|
10720
10847
|
odla-ai pm <goal|task|decision|bug> rm <id>
|
|
10721
|
-
odla-ai pm handoff --app <id> [--project <id>] [--json]
|
|
10848
|
+
odla-ai pm handoff --app <id> [--project <id>] [--verbose] [--json]
|
|
10722
10849
|
odla-ai discuss groups [--json]
|
|
10723
10850
|
odla-ai discuss list [--app <id>] [--q <text>] [--state open|resolved|all] [--mentions] [--json]
|
|
10724
10851
|
odla-ai discuss read <topic> [--limit <n> --offset <n>] [--json]
|
|
@@ -10883,6 +11010,16 @@ Commands:
|
|
|
10883
11010
|
execution contract and a reviewed revision. Any caller with PM
|
|
10884
11011
|
mutation access to that app/project may do it; claims coordinate
|
|
10885
11012
|
execution but never own or lock the PM record.
|
|
11013
|
+
To pick up work: "pm next" reads open goals, Ready candidates,
|
|
11014
|
+
and work in progress; "pm start" claims the top Ready task in
|
|
11015
|
+
one call, compare-and-swapped on the revision it just read, and
|
|
11016
|
+
returns the task, its goal, and the acceptance criteria to work
|
|
11017
|
+
against. Finish with "pm task done <id>", or hand it back with
|
|
11018
|
+
"pm task release <id> --expected-revision <n>". "pm start" never
|
|
11019
|
+
marks work Ready: that contract is reviewed separately so an
|
|
11020
|
+
agent cannot authorize its own work.
|
|
11021
|
+
Intake output is projected to the fields an executing caller
|
|
11022
|
+
acts on; --verbose returns whole records with their audit trail.
|
|
10886
11023
|
bug Intent-first alias for PM bugs. "bug report" writes to
|
|
10887
11024
|
odla PM; odla product defects do not belong in GitHub Issues.
|
|
10888
11025
|
discuss Group discussions (via @odla-ai/chat) for the apps you co-own:
|
|
@@ -11633,6 +11770,15 @@ var FIELD_MAP = {
|
|
|
11633
11770
|
execution: { key: "executionMode" },
|
|
11634
11771
|
"expected-revision": { key: "expectedRevision", num: true }
|
|
11635
11772
|
};
|
|
11773
|
+
var PmRequestError = class extends Error {
|
|
11774
|
+
constructor(status, message2) {
|
|
11775
|
+
super(message2);
|
|
11776
|
+
this.status = status;
|
|
11777
|
+
this.name = "PmRequestError";
|
|
11778
|
+
}
|
|
11779
|
+
status;
|
|
11780
|
+
};
|
|
11781
|
+
var isRevisionConflict = (error) => error instanceof PmRequestError && error.status === 409;
|
|
11636
11782
|
async function pmRequest(ctx, method, path, body) {
|
|
11637
11783
|
const response2 = await ctx.doFetch(`${ctx.platformUrl}/registry/pm${path}`, {
|
|
11638
11784
|
method,
|
|
@@ -11652,7 +11798,8 @@ async function pmRequest(ctx, method, path, body) {
|
|
|
11652
11798
|
const message2 = error.message;
|
|
11653
11799
|
if (typeof message2 === "string" && message2.length > 0) detail = message2;
|
|
11654
11800
|
}
|
|
11655
|
-
throw new
|
|
11801
|
+
throw new PmRequestError(
|
|
11802
|
+
response2.status,
|
|
11656
11803
|
`pm ${method} ${path} failed: ${detail ?? `registry returned ${response2.status}`} (${response2.status})`
|
|
11657
11804
|
);
|
|
11658
11805
|
}
|
|
@@ -11667,8 +11814,8 @@ function collectFields(parsed, allowClear) {
|
|
|
11667
11814
|
if (allowClear) out[spec.key] = null;
|
|
11668
11815
|
continue;
|
|
11669
11816
|
}
|
|
11670
|
-
const
|
|
11671
|
-
out[spec.key] = spec.num ? Number(
|
|
11817
|
+
const text4 = stringOpt(value2);
|
|
11818
|
+
out[spec.key] = spec.num ? Number(text4) : text4;
|
|
11672
11819
|
}
|
|
11673
11820
|
return out;
|
|
11674
11821
|
}
|
|
@@ -11841,103 +11988,196 @@ async function pmTaskLifecycle(ctx, id2, action2, parsed) {
|
|
|
11841
11988
|
ctx.out.log(`task: ${label} \u2192 ${state2}`);
|
|
11842
11989
|
});
|
|
11843
11990
|
}
|
|
11844
|
-
async function
|
|
11991
|
+
async function pmRemove(ctx, entity, id2) {
|
|
11992
|
+
await pmRequest(ctx, "DELETE", `/${entity}/${encodeURIComponent(id2)}`);
|
|
11993
|
+
ctx.out.log(`deleted ${entity} ${id2}`);
|
|
11994
|
+
}
|
|
11995
|
+
|
|
11996
|
+
// src/pm-lean.ts
|
|
11997
|
+
var COMMON = ["id", "appId", "projectId", "title", "revision"];
|
|
11998
|
+
var PER_ENTITY = {
|
|
11999
|
+
goal: ["status", "proof", "targetPct", "currentPct"],
|
|
12000
|
+
task: [
|
|
12001
|
+
"column",
|
|
12002
|
+
"goalId",
|
|
12003
|
+
"alignmentDecisionId",
|
|
12004
|
+
"description",
|
|
12005
|
+
"acceptanceCriteria",
|
|
12006
|
+
"executionMode",
|
|
12007
|
+
"assigneeId",
|
|
12008
|
+
"claimedByPrincipalId",
|
|
12009
|
+
"dueAt"
|
|
12010
|
+
],
|
|
12011
|
+
decision: ["status", "body", "supersedesId"],
|
|
12012
|
+
bug: ["status", "severity", "description", "goalId", "assigneeId", "decisionId"]
|
|
12013
|
+
};
|
|
12014
|
+
function leanRecord(entity, record11) {
|
|
12015
|
+
const out = {};
|
|
12016
|
+
for (const key of [...COMMON, ...PER_ENTITY[entity]]) {
|
|
12017
|
+
const value2 = record11[key];
|
|
12018
|
+
if (value2 !== void 0 && value2 !== null) out[key] = value2;
|
|
12019
|
+
}
|
|
12020
|
+
return out;
|
|
12021
|
+
}
|
|
12022
|
+
function leanRecords(entity, records, verbose) {
|
|
12023
|
+
return verbose ? records : records.map((record11) => leanRecord(entity, record11));
|
|
12024
|
+
}
|
|
12025
|
+
|
|
12026
|
+
// src/pm-intake.ts
|
|
12027
|
+
var READY_COLUMN = "todo";
|
|
12028
|
+
var UNMET_GOAL_STATUS = "open";
|
|
12029
|
+
var ACTIVE_TASK_COLUMNS = "backlog,todo,doing,review";
|
|
12030
|
+
var OPEN_BUG_STATUSES = "open,triaged";
|
|
12031
|
+
var wantsVerbose = (parsed) => parsed.options.verbose === true;
|
|
12032
|
+
async function listFiltered(ctx, entity, appId, projectId, filters) {
|
|
11845
12033
|
const records = [];
|
|
11846
12034
|
for (; ; ) {
|
|
11847
|
-
const q = new URLSearchParams({
|
|
11848
|
-
app: appId,
|
|
11849
|
-
limit: "100",
|
|
11850
|
-
offset: String(records.length)
|
|
11851
|
-
});
|
|
12035
|
+
const q = new URLSearchParams({ app: appId, limit: "100", offset: String(records.length) });
|
|
11852
12036
|
if (projectId) q.set("project", projectId);
|
|
12037
|
+
for (const [key, value2] of Object.entries(filters)) q.set(key, value2);
|
|
11853
12038
|
const page2 = await pmRequest(ctx, "GET", `/${entity}?${q}`);
|
|
11854
12039
|
records.push(...page2.records);
|
|
11855
12040
|
if (records.length >= page2.total || page2.records.length === 0) return records;
|
|
11856
12041
|
}
|
|
11857
12042
|
}
|
|
11858
|
-
|
|
12043
|
+
function requireApp(ctx, parsed, command) {
|
|
11859
12044
|
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
11860
|
-
|
|
11861
|
-
|
|
12045
|
+
if (!appId) throw new Error(`pm ${command} needs --app <appId>`);
|
|
12046
|
+
return { appId, projectId: stringOpt(parsed.options.project) ?? ctx.projectId };
|
|
12047
|
+
}
|
|
12048
|
+
async function pmNext(ctx, parsed) {
|
|
12049
|
+
const { appId, projectId } = requireApp(ctx, parsed, "next");
|
|
12050
|
+
const verbose = wantsVerbose(parsed);
|
|
11862
12051
|
const [goals, tasks] = await Promise.all([
|
|
11863
|
-
|
|
11864
|
-
|
|
12052
|
+
listFiltered(ctx, "goal", appId, projectId, { status: UNMET_GOAL_STATUS }),
|
|
12053
|
+
// One request for both live columns; the split below is over open work only.
|
|
12054
|
+
listFiltered(ctx, "task", appId, projectId, { column: `${READY_COLUMN},doing` })
|
|
11865
12055
|
]);
|
|
12056
|
+
const ready = tasks.filter((record11) => record11.column === READY_COLUMN);
|
|
12057
|
+
const doing = tasks.filter((record11) => record11.column === "doing");
|
|
12058
|
+
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}`;
|
|
11866
12059
|
const result = {
|
|
11867
12060
|
appId,
|
|
11868
12061
|
projectId,
|
|
11869
|
-
openGoals:
|
|
11870
|
-
doing:
|
|
11871
|
-
ready:
|
|
12062
|
+
openGoals: leanRecords("goal", goals, verbose),
|
|
12063
|
+
doing: leanRecords("task", doing, verbose),
|
|
12064
|
+
ready: leanRecords("task", ready, verbose),
|
|
12065
|
+
next: guidance
|
|
11872
12066
|
};
|
|
11873
12067
|
emit2(ctx, result, () => {
|
|
11874
12068
|
ctx.out.log(`${appId}: goal-aligned work intake (read only)`);
|
|
11875
|
-
for (const [label, records] of [
|
|
11876
|
-
["doing",
|
|
11877
|
-
["ready",
|
|
11878
|
-
["open goals",
|
|
12069
|
+
for (const [label, entity, records] of [
|
|
12070
|
+
["doing", "task", doing],
|
|
12071
|
+
["ready", "task", ready],
|
|
12072
|
+
["open goals", "goal", goals]
|
|
11879
12073
|
]) {
|
|
11880
12074
|
ctx.out.log(`${label}:`);
|
|
11881
12075
|
if (!records.length) ctx.out.log("- (none)");
|
|
11882
|
-
else for (const record11 of records) printRecord(
|
|
11883
|
-
ctx,
|
|
11884
|
-
label === "open goals" ? "goal" : "task",
|
|
11885
|
-
record11
|
|
11886
|
-
);
|
|
11887
|
-
}
|
|
11888
|
-
if (!result.openGoals.length) {
|
|
11889
|
-
ctx.out.log("next: discuss alignment with the user before creating or claiming project work");
|
|
11890
|
-
} else if (!result.ready.length) {
|
|
11891
|
-
ctx.out.log("next: refine a linked Backlog task and mark it Ready");
|
|
11892
|
-
} else {
|
|
11893
|
-
ctx.out.log("next: review a Ready task, then claim it with its revision");
|
|
12076
|
+
else for (const record11 of records) printRecord(ctx, entity, record11);
|
|
11894
12077
|
}
|
|
12078
|
+
ctx.out.log(`next: ${guidance}`);
|
|
11895
12079
|
});
|
|
11896
12080
|
}
|
|
11897
12081
|
async function pmHandoff(ctx, parsed) {
|
|
11898
|
-
const appId =
|
|
11899
|
-
const
|
|
11900
|
-
if (!appId) throw new Error("pm handoff needs --app <appId>");
|
|
12082
|
+
const { appId, projectId } = requireApp(ctx, parsed, "handoff");
|
|
12083
|
+
const verbose = wantsVerbose(parsed);
|
|
11901
12084
|
const [goals, tasks, bugs] = await Promise.all([
|
|
11902
|
-
|
|
11903
|
-
|
|
11904
|
-
|
|
12085
|
+
listFiltered(ctx, "goal", appId, projectId, { status: UNMET_GOAL_STATUS }),
|
|
12086
|
+
listFiltered(ctx, "task", appId, projectId, { column: ACTIVE_TASK_COLUMNS }),
|
|
12087
|
+
listFiltered(ctx, "bug", appId, projectId, { status: OPEN_BUG_STATUSES })
|
|
11905
12088
|
]);
|
|
11906
|
-
const
|
|
12089
|
+
const clean4 = !goals.length && !tasks.length && !bugs.length;
|
|
12090
|
+
const result = {
|
|
11907
12091
|
appId,
|
|
11908
12092
|
projectId,
|
|
11909
|
-
unmetGoals:
|
|
11910
|
-
activeTasks:
|
|
11911
|
-
openBugs:
|
|
11912
|
-
|
|
11913
|
-
const result = {
|
|
11914
|
-
...handoff,
|
|
11915
|
-
clean: !handoff.unmetGoals.length && !handoff.activeTasks.length && !handoff.openBugs.length
|
|
12093
|
+
unmetGoals: leanRecords("goal", goals, verbose),
|
|
12094
|
+
activeTasks: leanRecords("task", tasks, verbose),
|
|
12095
|
+
openBugs: leanRecords("bug", bugs, verbose),
|
|
12096
|
+
clean: clean4
|
|
11916
12097
|
};
|
|
11917
12098
|
emit2(ctx, result, () => {
|
|
11918
|
-
if (
|
|
12099
|
+
if (clean4) {
|
|
11919
12100
|
ctx.out.log(`${appId}: no unresolved PM work`);
|
|
11920
12101
|
return;
|
|
11921
12102
|
}
|
|
11922
12103
|
ctx.out.log(`${appId}: authoritative PM handoff`);
|
|
11923
|
-
for (const [label, records] of [
|
|
11924
|
-
["unmet goals",
|
|
11925
|
-
["active tasks",
|
|
11926
|
-
["open bugs",
|
|
12104
|
+
for (const [label, entity, records] of [
|
|
12105
|
+
["unmet goals", "goal", goals],
|
|
12106
|
+
["active tasks", "task", tasks],
|
|
12107
|
+
["open bugs", "bug", bugs]
|
|
11927
12108
|
]) {
|
|
11928
12109
|
ctx.out.log(`${label}:`);
|
|
11929
12110
|
if (!records.length) ctx.out.log("- (none)");
|
|
11930
|
-
else for (const record11 of records) printRecord(
|
|
11931
|
-
ctx,
|
|
11932
|
-
label === "unmet goals" ? "goal" : label === "active tasks" ? "task" : "bug",
|
|
11933
|
-
record11
|
|
11934
|
-
);
|
|
12111
|
+
else for (const record11 of records) printRecord(ctx, entity, record11);
|
|
11935
12112
|
}
|
|
11936
12113
|
});
|
|
11937
12114
|
}
|
|
11938
|
-
|
|
11939
|
-
|
|
11940
|
-
|
|
12115
|
+
|
|
12116
|
+
// src/pm-start.ts
|
|
12117
|
+
var getTask = async (ctx, id2) => (await pmRequest(ctx, "GET", `/task/${encodeURIComponent(id2)}`)).record;
|
|
12118
|
+
async function claimAt(ctx, task, parsed) {
|
|
12119
|
+
const res = await pmRequest(
|
|
12120
|
+
ctx,
|
|
12121
|
+
"POST",
|
|
12122
|
+
`/task/${encodeURIComponent(task.id)}/claim`,
|
|
12123
|
+
{ expectedRevision: task.revision, mutationId: writeMutationId2(parsed) }
|
|
12124
|
+
);
|
|
12125
|
+
return res.record;
|
|
12126
|
+
}
|
|
12127
|
+
async function selectTask(ctx, parsed, appId, projectId) {
|
|
12128
|
+
const named = parsed.positionals[2];
|
|
12129
|
+
if (named) return getTask(ctx, named);
|
|
12130
|
+
const goalId = stringOpt(parsed.options.goal);
|
|
12131
|
+
const ready = await listFiltered(ctx, "task", appId, projectId, {
|
|
12132
|
+
column: READY_COLUMN,
|
|
12133
|
+
...goalId ? { goalId } : {}
|
|
12134
|
+
});
|
|
12135
|
+
const candidate = ready[0];
|
|
12136
|
+
if (!candidate) {
|
|
12137
|
+
throw new Error(
|
|
12138
|
+
`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.`
|
|
12139
|
+
);
|
|
12140
|
+
}
|
|
12141
|
+
return candidate;
|
|
12142
|
+
}
|
|
12143
|
+
async function pmStart(ctx, parsed) {
|
|
12144
|
+
const appId = stringOpt(parsed.options.app) ?? ctx.appId;
|
|
12145
|
+
if (!appId) throw new Error("pm start needs --app <appId>");
|
|
12146
|
+
const projectId = stringOpt(parsed.options.project) ?? ctx.projectId;
|
|
12147
|
+
const verbose = wantsVerbose(parsed);
|
|
12148
|
+
const selected = await selectTask(ctx, parsed, appId, projectId);
|
|
12149
|
+
if (selected.column !== READY_COLUMN) {
|
|
12150
|
+
throw new Error(
|
|
12151
|
+
`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.`
|
|
12152
|
+
);
|
|
12153
|
+
}
|
|
12154
|
+
let claimed;
|
|
12155
|
+
try {
|
|
12156
|
+
claimed = await claimAt(ctx, selected, parsed);
|
|
12157
|
+
} catch (error) {
|
|
12158
|
+
if (!isRevisionConflict(error)) throw error;
|
|
12159
|
+
const reloaded = await getTask(ctx, selected.id);
|
|
12160
|
+
if (reloaded.column !== READY_COLUMN) {
|
|
12161
|
+
throw new Error(
|
|
12162
|
+
`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.`
|
|
12163
|
+
);
|
|
12164
|
+
}
|
|
12165
|
+
claimed = await claimAt(ctx, reloaded, parsed);
|
|
12166
|
+
}
|
|
12167
|
+
const task = claimed ?? await getTask(ctx, selected.id);
|
|
12168
|
+
const goalId = typeof task.goalId === "string" ? task.goalId : void 0;
|
|
12169
|
+
const goal = goalId ? await pmRequest(ctx, "GET", `/goal/${encodeURIComponent(goalId)}`).then((res) => res.record).catch(() => null) : null;
|
|
12170
|
+
const result = {
|
|
12171
|
+
claimed: verbose ? task : leanRecord("task", task),
|
|
12172
|
+
goal: goal ? verbose ? goal : leanRecord("goal", goal) : null,
|
|
12173
|
+
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)'
|
|
12174
|
+
};
|
|
12175
|
+
emit2(ctx, result, () => {
|
|
12176
|
+
ctx.out.log(`claimed: ${studioRecordLink(ctx, "task", task)}`);
|
|
12177
|
+
if (goal) ctx.out.log(`goal: ${studioRecordLink(ctx, "goal", goal)}`);
|
|
12178
|
+
if (typeof task.acceptanceCriteria === "string") ctx.out.log(`acceptance: ${task.acceptanceCriteria}`);
|
|
12179
|
+
ctx.out.log(`next: ${result.next}`);
|
|
12180
|
+
});
|
|
11941
12181
|
}
|
|
11942
12182
|
|
|
11943
12183
|
// src/pm-links.ts
|
|
@@ -12411,9 +12651,13 @@ async function pmCommand(parsed, deps = {}) {
|
|
|
12411
12651
|
throw new Error(`unknown pm project action "${action3}". Try list|add|use.`);
|
|
12412
12652
|
}
|
|
12413
12653
|
if (word === "next") {
|
|
12414
|
-
assertArgs(parsed, [...COMMON_OPTIONS, "app", "project"], 2);
|
|
12654
|
+
assertArgs(parsed, [...COMMON_OPTIONS, "app", "project", "verbose"], 2);
|
|
12415
12655
|
return pmNext(await buildContext2(parsed, deps), parsed);
|
|
12416
12656
|
}
|
|
12657
|
+
if (word === "start") {
|
|
12658
|
+
assertArgs(parsed, [...COMMON_OPTIONS, "app", "project", "goal", "verbose", "mutation-id"], 3);
|
|
12659
|
+
return pmStart(await buildContext2(parsed, deps), parsed);
|
|
12660
|
+
}
|
|
12417
12661
|
if (word === "watch") {
|
|
12418
12662
|
assertArgs(parsed, [
|
|
12419
12663
|
...COMMON_OPTIONS,
|
|
@@ -12431,11 +12675,11 @@ async function pmCommand(parsed, deps = {}) {
|
|
|
12431
12675
|
return pmWatch(await buildContext2(parsed, deps), parsed).then(() => void 0);
|
|
12432
12676
|
}
|
|
12433
12677
|
if (word === "handoff") {
|
|
12434
|
-
assertArgs(parsed, [...COMMON_OPTIONS, "app", "project"], 2);
|
|
12678
|
+
assertArgs(parsed, [...COMMON_OPTIONS, "app", "project", "verbose"], 2);
|
|
12435
12679
|
return pmHandoff(await buildContext2(parsed, deps), parsed);
|
|
12436
12680
|
}
|
|
12437
12681
|
const entity = ALIASES[word];
|
|
12438
|
-
if (!entity) throw new Error(`unknown pm entity "${word}". Try "odla-ai pm bug list" (goal|task|decision|bug).`);
|
|
12682
|
+
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).`);
|
|
12439
12683
|
const requestedAction = parsed.positionals[2] ?? "list";
|
|
12440
12684
|
const action2 = canonicalAction(requestedAction);
|
|
12441
12685
|
if (!action2) throw new Error(`unknown pm action "${requestedAction}". Try list|add|get|set|done|link|ref|comment|comments|history|rm.`);
|
|
@@ -12983,14 +13227,14 @@ function statusMinutes(value2) {
|
|
|
12983
13227
|
}
|
|
12984
13228
|
async function read2(url, headers, doFetch) {
|
|
12985
13229
|
const response2 = await doFetch(url, { headers });
|
|
12986
|
-
const
|
|
13230
|
+
const text4 = await response2.text();
|
|
12987
13231
|
let body = {};
|
|
12988
|
-
if (
|
|
13232
|
+
if (text4) {
|
|
12989
13233
|
try {
|
|
12990
|
-
const value2 = JSON.parse(
|
|
13234
|
+
const value2 = JSON.parse(text4);
|
|
12991
13235
|
body = value2 && typeof value2 === "object" && !Array.isArray(value2) ? value2 : { value: value2 };
|
|
12992
13236
|
} catch {
|
|
12993
|
-
body = { message:
|
|
13237
|
+
body = { message: text4.slice(0, 300) };
|
|
12994
13238
|
}
|
|
12995
13239
|
}
|
|
12996
13240
|
return { httpStatus: response2.status, body };
|
|
@@ -13220,13 +13464,13 @@ async function monitorCommand(parsed, deps = {}) {
|
|
|
13220
13464
|
}
|
|
13221
13465
|
async function request2(url, init, doFetch) {
|
|
13222
13466
|
const response2 = await doFetch(url, init);
|
|
13223
|
-
const
|
|
13467
|
+
const text4 = await response2.text();
|
|
13224
13468
|
let body = {};
|
|
13225
13469
|
try {
|
|
13226
|
-
const parsed =
|
|
13470
|
+
const parsed = text4 ? JSON.parse(text4) : {};
|
|
13227
13471
|
body = record10(parsed) ? parsed : { value: parsed };
|
|
13228
13472
|
} catch {
|
|
13229
|
-
body = { message:
|
|
13473
|
+
body = { message: text4.slice(0, 500) };
|
|
13230
13474
|
}
|
|
13231
13475
|
if (!response2.ok) {
|
|
13232
13476
|
const error = record10(body.error) ? body.error : body;
|
|
@@ -13429,8 +13673,8 @@ function runtimeUrl(cfg, suffix = "") {
|
|
|
13429
13673
|
return `${cfg.platformUrl}/registry/apps/${encodeURIComponent(cfg.app.id)}/runtime-credentials${suffix}`;
|
|
13430
13674
|
}
|
|
13431
13675
|
async function safeError(response2) {
|
|
13432
|
-
const
|
|
13433
|
-
return redactSecrets(
|
|
13676
|
+
const text4 = await response2.text();
|
|
13677
|
+
return redactSecrets(text4.slice(0, 1e3));
|
|
13434
13678
|
}
|
|
13435
13679
|
async function finish(doFetch, cfg, token, sessionId, method) {
|
|
13436
13680
|
return doFetch(runtimeUrl(cfg, `/${encodeURIComponent(sessionId)}`), {
|
|
@@ -13849,6 +14093,7 @@ var COMMAND_SURFACE = {
|
|
|
13849
14093
|
project: { list: {}, add: {}, create: {}, use: {} },
|
|
13850
14094
|
handoff: {},
|
|
13851
14095
|
next: {},
|
|
14096
|
+
start: {},
|
|
13852
14097
|
watch: {}
|
|
13853
14098
|
},
|
|
13854
14099
|
provision: {},
|
|
@@ -13923,13 +14168,13 @@ function invocationPath(words2) {
|
|
|
13923
14168
|
return path;
|
|
13924
14169
|
}
|
|
13925
14170
|
function surfacePaths(node = COMMAND_SURFACE, prefix = []) {
|
|
13926
|
-
const
|
|
14171
|
+
const paths2 = [];
|
|
13927
14172
|
for (const [word, child] of Object.entries(node)) {
|
|
13928
14173
|
const path = [...prefix, word];
|
|
13929
|
-
|
|
13930
|
-
|
|
14174
|
+
paths2.push(path);
|
|
14175
|
+
paths2.push(...surfacePaths(child, path));
|
|
13931
14176
|
}
|
|
13932
|
-
return
|
|
14177
|
+
return paths2;
|
|
13933
14178
|
}
|
|
13934
14179
|
|
|
13935
14180
|
// src/record.ts
|
|
@@ -13992,10 +14237,10 @@ var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
|
13992
14237
|
var UNITS = { d: DAY_MS, w: 7 * DAY_MS, y: 365 * DAY_MS };
|
|
13993
14238
|
function parseDeviceTtl(raw) {
|
|
13994
14239
|
if (raw === void 0 || raw === true) return void 0;
|
|
13995
|
-
const
|
|
13996
|
-
if (!
|
|
13997
|
-
if (
|
|
13998
|
-
const match = /^(\d+)\s*([dwy])$/.exec(
|
|
14240
|
+
const text4 = String(raw).trim().toLowerCase();
|
|
14241
|
+
if (!text4) return void 0;
|
|
14242
|
+
if (text4 === "forever" || text4 === "never") return 100 * 365 * DAY_MS;
|
|
14243
|
+
const match = /^(\d+)\s*([dwy])$/.exec(text4);
|
|
13999
14244
|
if (!match) {
|
|
14000
14245
|
throw new Error(
|
|
14001
14246
|
`--device-ttl expects a duration like 30d, 6w, 2y, or "forever" (got "${raw}")`
|
|
@@ -14328,12 +14573,12 @@ async function runbookRemove(ctx, slug) {
|
|
|
14328
14573
|
// src/runbook-import.ts
|
|
14329
14574
|
import { readFileSync as readFileSync11, readdirSync as readdirSync2, statSync } from "fs";
|
|
14330
14575
|
import { basename as basename2, join as join15 } from "path";
|
|
14331
|
-
function parseRunbook(
|
|
14332
|
-
let rest =
|
|
14576
|
+
function parseRunbook(text4, slug) {
|
|
14577
|
+
let rest = text4;
|
|
14333
14578
|
const meta = {};
|
|
14334
|
-
const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(
|
|
14579
|
+
const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text4);
|
|
14335
14580
|
if (fm) {
|
|
14336
|
-
rest =
|
|
14581
|
+
rest = text4.slice(fm[0].length);
|
|
14337
14582
|
for (const line2 of fm[1].split(/\r?\n/)) {
|
|
14338
14583
|
const pair = /^(\w+)\s*:\s*(.+)$/.exec(line2.trim());
|
|
14339
14584
|
if (!pair) continue;
|
|
@@ -15958,4 +16203,4 @@ export {
|
|
|
15958
16203
|
isTerminalHostedSecurityStatus,
|
|
15959
16204
|
runCli
|
|
15960
16205
|
};
|
|
15961
|
-
//# sourceMappingURL=chunk-
|
|
16206
|
+
//# sourceMappingURL=chunk-T4OPSRME.js.map
|