lazycodex-ai 5.1.4 → 5.1.6

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.
Files changed (61) hide show
  1. package/dist/cli/index.js +465 -187
  2. package/dist/cli-node/index.js +465 -187
  3. package/package.json +1 -1
  4. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  5. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +309 -133
  6. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  7. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  8. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  9. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  10. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  11. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  12. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  13. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  14. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
  15. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  16. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  17. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  18. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  19. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  20. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  21. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  22. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  23. package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +25 -18
  24. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  25. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  26. package/packages/omo-codex/plugin/components/ultrawork/src/directive-content.ts +1 -1
  27. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
  28. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
  29. package/packages/omo-codex/plugin/components/ulw-loop/directive.md +25 -18
  30. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
  31. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  32. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  33. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  34. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  35. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  36. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  37. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  38. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  39. package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
  40. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  41. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  42. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  43. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  44. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  45. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  46. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  47. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  48. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  49. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  50. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  51. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  52. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  53. package/packages/omo-codex/plugin/package-lock.json +12 -12
  54. package/packages/omo-codex/plugin/package.json +1 -1
  55. package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +1 -1
  56. package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +25 -18
  57. package/packages/omo-codex/plugin/skills/ulw-execute/SKILL.md +1 -1
  58. package/packages/omo-codex/scripts/install-dist/install-local.mjs +311 -135
  59. package/packages/prompts-core/prompts/ultrawork/codex.md +25 -18
  60. package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +1 -1
  61. package/packages/shared-skills/skills/ulw-execute/SKILL.md +1 -1
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // omo-codex-install:3946ebe75677b0182b2bd330aef6e28d80b0a14b4f2bef7d2cf926cc141467f0:91bb802279b3e0a38644491e221a425a04ca6cf2aec0cb4dbf8c9be3ce4bf8f5
2
+ // omo-codex-install:926babe1233096485f8e4db8e616e5deaa07ce9e732fdb0477b03390f4f15efa:4d5d406ea49f40877a220f86f4699fafaf2529a5afcea4e30abccfe48760df2c
3
3
  var __esm = (fn, res, err) => () => {
4
4
  if (fn)
5
5
  try {
@@ -9984,7 +9984,7 @@ var package_default;
9984
9984
  var init_package = __esm(() => {
9985
9985
  package_default = {
9986
9986
  name: "@oh-my-opencode/omo-codex",
9987
- version: "5.1.4",
9987
+ version: "5.1.6",
9988
9988
  type: "module",
9989
9989
  private: true,
9990
9990
  description: "Codex harness adapter for oh-my-openagent. Vendored Codex plugin namespace (omo) + TypeScript installer + telemetry.",
@@ -19252,7 +19252,6 @@ function normalizeLegacyModelFields(entry) {
19252
19252
  delete normalized["reasoningEffort"];
19253
19253
  delete normalized["thinking"];
19254
19254
  delete normalized["textVerbosity"];
19255
- delete normalized["maxTokens"];
19256
19255
  delete normalized["providerOptions"];
19257
19256
  if (typeof entry["model"] === "string")
19258
19257
  normalized["model"] = canonicalModelString(entry["model"]);
@@ -19270,10 +19269,15 @@ function normalizeLegacyModelFields(entry) {
19270
19269
  providerOptions["textVerbosity"] = entry["textVerbosity"];
19271
19270
  if (Object.keys(providerOptions).length > 0)
19272
19271
  normalized["provider_options"] = providerOptions;
19273
- if (entry["max_tokens"] !== undefined)
19272
+ if (entry["max_tokens"] !== undefined) {
19274
19273
  normalized["max_tokens"] = entry["max_tokens"];
19275
- else if (entry["maxTokens"] !== undefined)
19274
+ if (entry["maxTokens"] === undefined || typeof entry["maxTokens"] === "number") {
19275
+ delete normalized["maxTokens"];
19276
+ }
19277
+ } else if (typeof entry["maxTokens"] === "number") {
19276
19278
  normalized["max_tokens"] = entry["maxTokens"];
19279
+ delete normalized["maxTokens"];
19280
+ }
19277
19281
  return normalized;
19278
19282
  }
19279
19283
  var OmoLegacyFallbackModelObjectInputSchema = object({
@@ -19303,6 +19307,19 @@ function isRecord3(value) {
19303
19307
  return typeof value === "object" && value !== null && !Array.isArray(value);
19304
19308
  }
19305
19309
  var OmoAgentModelEntrySchema = union([string2(), OmoFallbackModelObjectSchema]);
19310
+ var PermissionValueSchema = _enum(["ask", "allow", "deny"]);
19311
+ var BashPermissionSchema = union([
19312
+ PermissionValueSchema,
19313
+ record(string2(), PermissionValueSchema)
19314
+ ]);
19315
+ var OmoAgentPermissionSchema = object({
19316
+ edit: PermissionValueSchema.optional(),
19317
+ bash: BashPermissionSchema.optional(),
19318
+ webfetch: PermissionValueSchema.optional(),
19319
+ task: PermissionValueSchema.optional(),
19320
+ doom_loop: PermissionValueSchema.optional(),
19321
+ external_directory: PermissionValueSchema.optional()
19322
+ }).catchall(PermissionValueSchema.optional());
19306
19323
  var OmoAgentDefInputSchema = object({
19307
19324
  description: string2().optional(),
19308
19325
  prompt: string2().optional(),
@@ -19319,7 +19336,9 @@ var OmoAgentDefInputSchema = object({
19319
19336
  disallowed_tools: array(string2()).optional(),
19320
19337
  max_turns: number2().int().nonnegative().optional(),
19321
19338
  temperature: number2().min(0).max(2).optional(),
19322
- disable: boolean2().optional()
19339
+ disable: boolean2().optional(),
19340
+ permission: OmoAgentPermissionSchema.optional(),
19341
+ prompt_append: string2().optional()
19323
19342
  }).strict();
19324
19343
  var OmoAgentDefSchema = preprocess((value) => isRecord3(value) ? normalizeLegacyModelFields(value) : value, OmoAgentDefInputSchema);
19325
19344
  var OmoAgentsConfigSchema = record(string2(), OmoAgentDefSchema);
@@ -20008,6 +20027,16 @@ function canonicalizeLegacyHarnessBlocks(document) {
20008
20027
  return { document: canonicalized, renames };
20009
20028
  }
20010
20029
 
20030
+ // packages/omo-config-core/src/loader/types.ts
20031
+ import { existsSync as existsSync3, lstatSync, readFileSync as readFileSync2, realpathSync as realpathSync2 } from "node:fs";
20032
+ var MERGED_OMO_CONFIG_PATH = "(merged omo config)";
20033
+ var DEFAULT_READ_FILE_SYSTEM = {
20034
+ existsSync: existsSync3,
20035
+ lstatSync,
20036
+ readFileSync: readFileSync2,
20037
+ realpathSync: realpathSync2
20038
+ };
20039
+
20011
20040
  // node_modules/.bun/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/impl/scanner.js
20012
20041
  function createScanner(text, ignoreTrivia = false) {
20013
20042
  const len = text.length;
@@ -20882,6 +20911,255 @@ function mergeOmoConfigRecords(base, override) {
20882
20911
  return result;
20883
20912
  }
20884
20913
 
20914
+ // packages/omo-config-core/src/loader/prune-invalid-leaves.ts
20915
+ var MAX_PRUNE_PASSES = 32;
20916
+ function isContainer(value) {
20917
+ return typeof value === "object" && value !== null;
20918
+ }
20919
+ function childOf(container, segment) {
20920
+ return Array.isArray(container) ? container[Number(segment)] : container[String(segment)];
20921
+ }
20922
+ function hasChild(container, segment) {
20923
+ if (Array.isArray(container)) {
20924
+ const index = Number(segment);
20925
+ return Number.isInteger(index) && index >= 0 && index < container.length;
20926
+ }
20927
+ return Object.hasOwn(container, String(segment));
20928
+ }
20929
+ function segmentsOf(path) {
20930
+ return path.map((segment) => typeof segment === "number" ? segment : String(segment));
20931
+ }
20932
+ function targetPaths(root, issue) {
20933
+ const base = segmentsOf(issue.path);
20934
+ if (issue.code === "unrecognized_keys")
20935
+ return issue.keys.map((key) => [...base, key]);
20936
+ let node = root;
20937
+ let depth = 0;
20938
+ for (const segment of base) {
20939
+ if (!isContainer(node) || !hasChild(node, segment))
20940
+ break;
20941
+ node = childOf(node, segment);
20942
+ depth += 1;
20943
+ }
20944
+ return [base.slice(0, depth)];
20945
+ }
20946
+ function isAncestor(ancestor, path) {
20947
+ return ancestor.length < path.length && ancestor.every((segment, index) => segment === path[index]);
20948
+ }
20949
+ function compareDescending(left, right) {
20950
+ const length = Math.min(left.length, right.length);
20951
+ for (let index = 0;index < length; index += 1) {
20952
+ const a = left[index];
20953
+ const b = right[index];
20954
+ if (a === b)
20955
+ continue;
20956
+ if (typeof a === "number" && typeof b === "number")
20957
+ return b - a;
20958
+ return String(b).localeCompare(String(a));
20959
+ }
20960
+ return right.length - left.length;
20961
+ }
20962
+ function removeChild(container, segment) {
20963
+ if (Array.isArray(container))
20964
+ container.splice(Number(segment), 1);
20965
+ else
20966
+ delete container[String(segment)];
20967
+ }
20968
+ function isEmpty(container) {
20969
+ return Array.isArray(container) ? container.length === 0 : Object.keys(container).length === 0;
20970
+ }
20971
+ function removePathAndEmptiedAncestors(root, path) {
20972
+ const chain = [root];
20973
+ for (const segment of path.slice(0, -1)) {
20974
+ const current = chain[chain.length - 1];
20975
+ if (current === undefined)
20976
+ return;
20977
+ const next = childOf(current, segment);
20978
+ if (!isContainer(next))
20979
+ return;
20980
+ chain.push(next);
20981
+ }
20982
+ for (let depth = path.length - 1;depth >= 0; depth -= 1) {
20983
+ const container = chain[depth];
20984
+ const segment = path[depth];
20985
+ if (container === undefined || segment === undefined)
20986
+ return;
20987
+ removeChild(container, segment);
20988
+ if (depth === 0 || !isEmpty(container))
20989
+ return;
20990
+ }
20991
+ }
20992
+ function prunePass(root, issues) {
20993
+ const byKey = new Map;
20994
+ for (const issue of issues) {
20995
+ for (const path of targetPaths(root, issue)) {
20996
+ if (path.length === 0)
20997
+ return null;
20998
+ const key = path.map((segment) => String(segment)).join(".");
20999
+ if (!byKey.has(key))
21000
+ byKey.set(key, { key, message: issue.message, path });
21001
+ }
21002
+ }
21003
+ const targets = [...byKey.values()];
21004
+ const outermost = targets.filter((target) => !targets.some((other) => isAncestor(other.path, target.path))).sort((left, right) => compareDescending(left.path, right.path));
21005
+ const next = structuredClone(root);
21006
+ for (const target of outermost)
21007
+ removePathAndEmptiedAncestors(next, target.path);
21008
+ return { dropped: outermost, next };
21009
+ }
21010
+ function pruneInvalidConfigPaths(config, issues, validate, maxPasses = MAX_PRUNE_PASSES) {
21011
+ const dropped = [];
21012
+ let current = config;
21013
+ let pending = issues;
21014
+ for (let pass = 0;pass < maxPasses; pass += 1) {
21015
+ const step = prunePass(current, pending);
21016
+ if (step === null || step.dropped.length === 0)
21017
+ return { ok: false, dropped };
21018
+ dropped.push(...step.dropped);
21019
+ current = step.next;
21020
+ if (Object.keys(current).length === 0)
21021
+ return { ok: false, dropped };
21022
+ const validation = validate(current);
21023
+ if (validation.success)
21024
+ return { ok: true, config: current, dropped };
21025
+ pending = validation.issues;
21026
+ }
21027
+ return { ok: false, dropped };
21028
+ }
21029
+
21030
+ // packages/omo-config-core/src/loader/layer-validation.ts
21031
+ function validationDiagnostic(path, issues) {
21032
+ const issuePaths = issues.map((issue) => issue.path.map((segment) => String(segment)).join("."));
21033
+ return {
21034
+ kind: "validation",
21035
+ message: `Invalid omo config at ${path}: ${issuePaths.join(", ")}`,
21036
+ path,
21037
+ issuePaths
21038
+ };
21039
+ }
21040
+ function invalidValueDiagnostics(path, dropped) {
21041
+ return dropped.map((entry) => ({
21042
+ kind: "invalid-value",
21043
+ message: `Ignored invalid value in ${path}: ${entry.key}: ${entry.message}`,
21044
+ path,
21045
+ issuePaths: [entry.key]
21046
+ }));
21047
+ }
21048
+ function unrecognizedKeyIssues(issues) {
21049
+ return issues.flatMap((issue) => issue.code === "unrecognized_keys" ? [{ keys: issue.keys, path: issue.path.map((segment) => String(segment)) }] : []);
21050
+ }
21051
+ function isRecord9(value) {
21052
+ return typeof value === "object" && value !== null && !Array.isArray(value);
21053
+ }
21054
+ function sanitizeUnsafeKeys(value, path = []) {
21055
+ if (Array.isArray(value)) {
21056
+ const issues = [];
21057
+ const sanitized = value.map((entry, index) => {
21058
+ const nested = sanitizeUnsafeKeys(entry, [...path, String(index)]);
21059
+ issues.push(...nested.issues);
21060
+ return nested.value;
21061
+ });
21062
+ return { issues, value: sanitized };
21063
+ }
21064
+ if (!isRecord9(value))
21065
+ return { issues: [], value };
21066
+ const issues = [];
21067
+ if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
21068
+ issues.push({ keys: ["__proto__"], path });
21069
+ }
21070
+ const sanitized = {};
21071
+ for (const [key, entry] of Object.entries(value)) {
21072
+ if (isUnsafeObjectKey(key)) {
21073
+ issues.push({ keys: [key], path });
21074
+ continue;
21075
+ }
21076
+ const nested = sanitizeUnsafeKeys(entry, [...path, key]);
21077
+ issues.push(...nested.issues);
21078
+ sanitized[key] = nested.value;
21079
+ }
21080
+ return { issues, value: sanitized };
21081
+ }
21082
+ function containerAt(record, path) {
21083
+ let node = record;
21084
+ for (const segment of path) {
21085
+ if (Array.isArray(node)) {
21086
+ const index = Number(segment);
21087
+ if (!Number.isInteger(index) || index < 0 || index >= node.length)
21088
+ return null;
21089
+ node = node[index];
21090
+ } else if (isRecord9(node) && Object.hasOwn(node, segment)) {
21091
+ node = node[segment];
21092
+ } else {
21093
+ return null;
21094
+ }
21095
+ }
21096
+ return isRecord9(node) ? node : null;
21097
+ }
21098
+ function stripUnrecognizedKeys(record, issues) {
21099
+ const stripped = structuredClone(record);
21100
+ const issuePaths = [];
21101
+ for (const issue of issues) {
21102
+ const container = containerAt(stripped, issue.path);
21103
+ if (container === null)
21104
+ continue;
21105
+ for (const key of issue.keys) {
21106
+ delete container[key];
21107
+ issuePaths.push([...issue.path, key].join("."));
21108
+ }
21109
+ }
21110
+ return { issuePaths, stripped };
21111
+ }
21112
+ function toRecord(value) {
21113
+ if (value === null || typeof value !== "object" || Array.isArray(value))
21114
+ return null;
21115
+ const record = {};
21116
+ for (const [key, entry] of Object.entries(value)) {
21117
+ record[key] = entry;
21118
+ }
21119
+ return record;
21120
+ }
21121
+ var validateLayerRecord = (record) => {
21122
+ const parsed = OmoConfigLayerSchema.safeParse(record);
21123
+ return parsed.success ? { success: true } : { success: false, issues: parsed.error.issues };
21124
+ };
21125
+ function validateConfigLayer(path, data) {
21126
+ const sanitized = sanitizeUnsafeKeys(data);
21127
+ const record = toRecord(sanitized.value);
21128
+ const unsafeIssuePaths = sanitized.issues.flatMap((issue) => issue.keys.map((key) => [...issue.path, key].join(".")));
21129
+ const unsafeDiagnostics = unsafeIssuePaths.length === 0 ? [] : [{ kind: "unknown-keys", message: `Ignored unknown keys in ${path}: ${unsafeIssuePaths.join(", ")}`, path, issuePaths: unsafeIssuePaths }];
21130
+ const validation = OmoConfigLayerSchema.safeParse(sanitized.value);
21131
+ if (validation.success) {
21132
+ if (record !== null)
21133
+ return { loaded: true, diagnostics: unsafeDiagnostics, value: record };
21134
+ return {
21135
+ loaded: false,
21136
+ diagnostics: [{ kind: "validation", message: `Invalid omo config at ${path}: root must be an object`, path }]
21137
+ };
21138
+ }
21139
+ const rejected = { loaded: false, diagnostics: [validationDiagnostic(path, validation.error.issues)] };
21140
+ const unknownIssues = unrecognizedKeyIssues(validation.error.issues);
21141
+ if (record === null)
21142
+ return rejected;
21143
+ let candidate = record;
21144
+ let issues = validation.error.issues;
21145
+ const diagnostics = [...unsafeDiagnostics];
21146
+ if (unknownIssues.length > 0) {
21147
+ const { issuePaths, stripped } = stripUnrecognizedKeys(record, unknownIssues);
21148
+ if (issuePaths.length > 0) {
21149
+ diagnostics.push({ kind: "unknown-keys", message: `Ignored unknown keys in ${path}: ${issuePaths.join(", ")}`, path, issuePaths });
21150
+ }
21151
+ const strippedValidation = validateLayerRecord(stripped);
21152
+ if (strippedValidation.success)
21153
+ return { loaded: true, diagnostics, value: stripped };
21154
+ candidate = stripped;
21155
+ issues = strippedValidation.issues;
21156
+ }
21157
+ const pruned = pruneInvalidConfigPaths(candidate, issues, validateLayerRecord);
21158
+ if (!pruned.ok)
21159
+ return rejected;
21160
+ return { loaded: true, diagnostics: [...diagnostics, ...invalidValueDiagnostics(path, pruned.dropped)], value: pruned.config };
21161
+ }
21162
+
20885
21163
  // packages/omo-config-core/src/loader/paths.ts
20886
21164
  import { userInfo } from "node:os";
20887
21165
  import { dirname as dirname8, join as join18, posix, resolve as resolve7 } from "node:path";
@@ -20891,15 +21169,6 @@ function toPosixPath2(path) {
20891
21169
  return path.split("\\").join("/");
20892
21170
  }
20893
21171
 
20894
- // packages/omo-config-core/src/loader/types.ts
20895
- import { existsSync as existsSync3, lstatSync, readFileSync as readFileSync2, realpathSync as realpathSync2 } from "node:fs";
20896
- var DEFAULT_READ_FILE_SYSTEM = {
20897
- existsSync: existsSync3,
20898
- lstatSync,
20899
- readFileSync: readFileSync2,
20900
- realpathSync: realpathSync2
20901
- };
20902
-
20903
21172
  // packages/omo-config-core/src/loader/paths.ts
20904
21173
  var MAX_PROJECT_CONFIG_DIRECTORY_DEPTH = 256;
20905
21174
  var ACCOUNT_HOME_DIR = userInfo().homedir;
@@ -20995,7 +21264,7 @@ function resolveOmoProfileName(options = {}) {
20995
21264
  const env = options.env ?? process.env;
20996
21265
  return profileName(options.profile) ?? profileName(env["OMO_PROFILE"]) ?? profileName(env["OCX_PROFILE"]) ?? profileNameFromOpenCodeConfigDir(env["OPENCODE_CONFIG_DIR"]);
20997
21266
  }
20998
- function toRecord(value) {
21267
+ function toRecord2(value) {
20999
21268
  if (value === null || typeof value !== "object" || Array.isArray(value))
21000
21269
  return;
21001
21270
  return Object.fromEntries(Object.entries(value));
@@ -21016,13 +21285,13 @@ function harnessLayer(config, harness) {
21016
21285
  const legacyKeys = Object.entries(OMO_CONFIG_LEGACY_HARNESS_ALIASES).filter(([, target]) => target === canonical).map(([legacy]) => harnessBlockKey(legacy));
21017
21286
  let layer = {};
21018
21287
  for (const key of [...legacyKeys, harnessBlockKey(canonical)]) {
21019
- layer = mergeOmoConfigRecords(layer, toRecord(config[key]) ?? {});
21288
+ layer = mergeOmoConfigRecords(layer, toRecord2(config[key]) ?? {});
21020
21289
  }
21021
21290
  return layer;
21022
21291
  }
21023
21292
  function resolveOmoConfigView(options) {
21024
- const profiles = toRecord(options.config["profiles"]);
21025
- const profile = options.profile === undefined ? undefined : toRecord(profiles?.[options.profile]);
21293
+ const profiles = toRecord2(options.config["profiles"]);
21294
+ const profile = options.profile === undefined ? undefined : toRecord2(profiles?.[options.profile]);
21026
21295
  const diagnostics = profile === undefined && options.profile !== undefined ? [{
21027
21296
  kind: "profile",
21028
21297
  message: `Activated omo profile "${options.profile}" does not exist; using the base configuration`,
@@ -21077,70 +21346,9 @@ function stripResolutionControlKeys(config) {
21077
21346
  } = config;
21078
21347
  return resolved;
21079
21348
  }
21080
- function validationDiagnostic(path, issues) {
21081
- const issuePaths = issues.map((issue) => issue.path.map((segment) => String(segment)).join("."));
21082
- return {
21083
- kind: "validation",
21084
- message: `Invalid omo config at ${path}: ${issuePaths.join(", ")}`,
21085
- path,
21086
- issuePaths
21087
- };
21088
- }
21089
- function unrecognizedKeyIssues(issues) {
21090
- return issues.flatMap((issue) => issue.code === "unrecognized_keys" ? [{ keys: issue.keys, path: issue.path.map((segment) => String(segment)) }] : []);
21091
- }
21092
- function hasUnsafeUnrecognizedKey(issues) {
21093
- return issues.some((issue) => issue.keys.some((key) => isUnsafeObjectKey(key)));
21094
- }
21095
- function hasTamperedPrototype(value) {
21096
- if (Array.isArray(value))
21097
- return value.some((entry) => hasTamperedPrototype(entry));
21098
- if (!isRecord9(value))
21099
- return false;
21100
- const prototype = Object.getPrototypeOf(value);
21101
- if (prototype !== Object.prototype && prototype !== null)
21102
- return true;
21103
- return Object.values(value).some((entry) => hasTamperedPrototype(entry));
21104
- }
21105
- function isRecord9(value) {
21106
- return typeof value === "object" && value !== null && !Array.isArray(value);
21107
- }
21108
- function containerAt(record, path) {
21109
- let container = record;
21110
- for (const segment of path) {
21111
- const next = container[segment];
21112
- if (!isRecord9(next))
21113
- return null;
21114
- container = next;
21115
- }
21116
- return container;
21117
- }
21118
- function stripUnrecognizedKeys(record, issues) {
21119
- const stripped = structuredClone(record);
21120
- const issuePaths = [];
21121
- for (const issue of issues) {
21122
- const container = containerAt(stripped, issue.path);
21123
- if (container === null)
21124
- continue;
21125
- for (const key of issue.keys) {
21126
- delete container[key];
21127
- issuePaths.push([...issue.path, key].join("."));
21128
- }
21129
- }
21130
- return { issuePaths, stripped };
21131
- }
21132
- function toRecord2(value) {
21133
- if (value === null || typeof value !== "object" || Array.isArray(value))
21134
- return null;
21135
- const record = {};
21136
- for (const [key, entry] of Object.entries(value)) {
21137
- record[key] = entry;
21138
- }
21139
- return record;
21140
- }
21141
21349
  function readConfigSource(path, scope, fileSystem) {
21142
21350
  if (!fileSystem.existsSync(path)) {
21143
- return { source: { exists: false, loaded: false, path, scope } };
21351
+ return { diagnostics: [], source: { exists: false, loaded: false, path, scope } };
21144
21352
  }
21145
21353
  let content;
21146
21354
  try {
@@ -21148,68 +21356,23 @@ function readConfigSource(path, scope, fileSystem) {
21148
21356
  } catch (error) {
21149
21357
  const message = error instanceof Error ? error.message : String(error);
21150
21358
  return {
21151
- diagnostic: { kind: "read", message: `Failed to read ${path}: ${message}`, path },
21359
+ diagnostics: [{ kind: "read", message: `Failed to read ${path}: ${message}`, path }],
21152
21360
  source: { exists: true, loaded: false, path, scope }
21153
21361
  };
21154
21362
  }
21155
21363
  const parsed = parseJsoncSafe(content);
21156
21364
  if (parsed.errors.length > 0) {
21157
21365
  return {
21158
- diagnostic: {
21366
+ diagnostics: [{
21159
21367
  kind: "parse",
21160
21368
  message: `JSONC parse error in ${path}: ${parsed.errors.map((error) => error.message).join(", ")}`,
21161
21369
  path
21162
- },
21370
+ }],
21163
21371
  source: { exists: true, loaded: false, path, scope }
21164
21372
  };
21165
21373
  }
21166
- if (hasTamperedPrototype(parsed.data)) {
21167
- return {
21168
- diagnostic: { kind: "validation", message: `Invalid omo config at ${path}: "__proto__" member is not allowed`, path },
21169
- source: { exists: true, loaded: false, path, scope }
21170
- };
21171
- }
21172
- const parsedRecord = toRecord2(parsed.data);
21173
- const validation = OmoConfigLayerSchema.safeParse(parsed.data);
21174
- if (!validation.success) {
21175
- const unrecognized = unrecognizedKeyIssues(validation.error.issues);
21176
- if (hasUnsafeUnrecognizedKey(unrecognized)) {
21177
- return {
21178
- diagnostic: validationDiagnostic(path, validation.error.issues),
21179
- source: { exists: true, loaded: false, path, scope }
21180
- };
21181
- }
21182
- const rejected = {
21183
- diagnostic: validationDiagnostic(path, validation.error.issues),
21184
- source: { exists: true, loaded: false, path, scope }
21185
- };
21186
- const unknownIssues = unrecognizedKeyIssues(validation.error.issues);
21187
- if (parsedRecord === null || unknownIssues.length === 0)
21188
- return rejected;
21189
- const { issuePaths, stripped } = stripUnrecognizedKeys(parsedRecord, unknownIssues);
21190
- if (!OmoConfigLayerSchema.safeParse(stripped).success)
21191
- return rejected;
21192
- return {
21193
- diagnostic: {
21194
- kind: "unknown-keys",
21195
- message: `Ignored unknown keys in ${path}: ${issuePaths.join(", ")}`,
21196
- path,
21197
- issuePaths
21198
- },
21199
- source: { exists: true, loaded: true, path, scope },
21200
- value: stripped
21201
- };
21202
- }
21203
- if (parsedRecord === null) {
21204
- return {
21205
- diagnostic: { kind: "validation", message: `Invalid omo config at ${path}: root must be an object`, path },
21206
- source: { exists: true, loaded: false, path, scope }
21207
- };
21208
- }
21209
- return {
21210
- source: { exists: true, loaded: true, path, scope },
21211
- value: parsedRecord
21212
- };
21374
+ const layer = validateConfigLayer(path, parsed.data);
21375
+ return layer.loaded ? { diagnostics: layer.diagnostics, source: { exists: true, loaded: true, path, scope }, value: layer.value } : { diagnostics: layer.diagnostics, source: { exists: true, loaded: false, path, scope } };
21213
21376
  }
21214
21377
  function legacyCategoryDiagnostic(path, renames) {
21215
21378
  const detail = renames.map((rename) => rename.dropped ? `${rename.path} ignored because ${rename.canonical} is also configured` : `${rename.path} renamed to ${rename.canonical}`).join(", ");
@@ -21244,8 +21407,7 @@ function loadOmoConfig(options = {}) {
21244
21407
  })) {
21245
21408
  const loaded = readConfigSource(candidate.path, candidate.scope, fileSystem);
21246
21409
  sources.push(loaded.source);
21247
- if (loaded.diagnostic !== undefined)
21248
- diagnostics.push(loaded.diagnostic);
21410
+ diagnostics.push(...loaded.diagnostics);
21249
21411
  if (loaded.value !== undefined) {
21250
21412
  const canonicalized = canonicalizeLegacyCategoryNames(loaded.value);
21251
21413
  if (canonicalized.renames.length > 0) {
@@ -21268,7 +21430,8 @@ function loadOmoConfig(options = {}) {
21268
21430
  ...options.harness === undefined ? {} : { harness: options.harness },
21269
21431
  ...requestedProfile === undefined ? {} : { profile: requestedProfile }
21270
21432
  });
21271
- const finalConfig = OmoConfigSchema.safeParse(mergeOmoConfigRecords(DEFAULT_RAW_CONFIG, resolved.config));
21433
+ const finalInput = mergeOmoConfigRecords(DEFAULT_RAW_CONFIG, resolved.config);
21434
+ const finalConfig = OmoConfigSchema.safeParse(finalInput);
21272
21435
  if (finalConfig.success) {
21273
21436
  return {
21274
21437
  config: stripResolutionControlKeys(finalConfig.data),
@@ -21278,9 +21441,22 @@ function loadOmoConfig(options = {}) {
21278
21441
  sources
21279
21442
  };
21280
21443
  }
21444
+ const pruned = pruneInvalidConfigPaths(finalInput, finalConfig.error.issues, (record) => {
21445
+ const parsed = OmoConfigSchema.safeParse(record);
21446
+ return parsed.success ? { success: true } : { success: false, issues: parsed.error.issues };
21447
+ });
21448
+ if (pruned.ok) {
21449
+ return {
21450
+ config: stripResolutionControlKeys(OmoConfigSchema.parse(pruned.config)),
21451
+ diagnostics: [...diagnostics, ...resolved.diagnostics, ...invalidValueDiagnostics(MERGED_OMO_CONFIG_PATH, pruned.dropped)],
21452
+ layers,
21453
+ ...resolved.profile === undefined ? {} : { profile: resolved.profile },
21454
+ sources
21455
+ };
21456
+ }
21281
21457
  return {
21282
21458
  config: stripResolutionControlKeys(OmoConfigSchema.parse(DEFAULT_RAW_CONFIG)),
21283
- diagnostics: [...diagnostics, ...resolved.diagnostics, validationDiagnostic("(merged omo config)", finalConfig.error.issues)],
21459
+ diagnostics: [...diagnostics, ...resolved.diagnostics, validationDiagnostic(MERGED_OMO_CONFIG_PATH, finalConfig.error.issues)],
21284
21460
  layers,
21285
21461
  ...resolved.profile === undefined ? {} : { profile: resolved.profile },
21286
21462
  sources
@@ -286,17 +286,17 @@ Until every success criterion PASSES with its evidence captured:
286
286
  vars. Append a one-line cleanup receipt to the notepad next to the
287
287
  artifact, e.g. `cleanup: killed 12345; tmux kill-session ulw-qa-foo;
288
288
  rm -rf /tmp/ulw.aB12cD`. No receipt → criterion stays in_progress.
289
- 6. Verify: LSP diagnostics clean on changed files + the test scope
290
- this criterion touched green (no skipped, no xfail added this
291
- turn). Re-run a validation command (suite, typecheck, build) only
292
- when its inputs changed since its last green run; ONE full-suite
293
- pass belongs immediately before the final message, not after
294
- every increment.
289
+ 6. Verify: LSP diagnostics clean on changed files; no test skipped or
290
+ xfail-ed this turn.
295
291
  7. Mark completed. Append non-obvious findings / learnings.
296
- 8. After each increment, re-run the scenarios that increment could
297
- have affected; re-run the full set once, right before the final
298
- message. Record PASS/FAIL inline with the evidence paths AND the
299
- cleanup receipt. Loop until all PASS.
292
+ 8. Evidence stays valid per target until an input changes; record with
293
+ each artifact the commit and what it exercised. After each increment
294
+ rerun what moved — the tests of every touched file and of the files
295
+ that import it, the scenarios that exercise them, anything whose
296
+ dependencies or environment changed — and cite the capture for the
297
+ rest. The full set (scenarios, suite, typecheck, build) runs once
298
+ more right before the final message. Record PASS/FAIL beside each
299
+ artifact. Loop until all PASS.
300
300
 
301
301
  Within a step, follow Finding things; READ before CHANGE, never in
302
302
  parallel with it.
@@ -399,13 +399,12 @@ When triggered, follow this procedure (NON-NEGOTIABLE):
399
399
  it names a success criterion the evidence fails; record concerns
400
400
  that cite no criterion as notes with a one-line reason — fixed or
401
401
  declined at your judgment.
402
- 3. Fix every criterion-cited blocker. Re-run ONLY the scenario QA
403
- affected by the fix; capture fresh evidence for the delta. Update
404
- notepad.
405
- 4. Re-submit to the SAME reviewer at most twice, passing only the
406
- delta diff, the blockers it cited, and the already-approved criteria
407
- marked out-of-scope. An approval whose only remaining items are
408
- notes counts as approval.
402
+ 3. Fix every criterion-cited blocker; rerun per Execution loop step 8
403
+ and update the notepad.
404
+ 4. Spawn a NEW reviewer for each re-review, at most twice, passing only
405
+ the delta diff, the blockers the last one cited, and the
406
+ already-approved criteria marked out-of-scope. An approval whose only
407
+ remaining items are notes counts as approval.
409
408
  5. On approval, declare done. If criterion-cited blockers remain after
410
409
  two re-reviews, stop and surface them to the user (mirroring the
411
410
  2-attempt stop rule below) — do not loop further.
@@ -430,7 +429,15 @@ commits this session — then stage + draft the message instead.
430
429
  for the regression it names is NOT evidence: mock-call assertions,
431
430
  pinned constants, a fixture equal to the default it must override,
432
431
  an expected value re-derived from the output under test.
433
- - Smallest correct change. No drive-by refactors.
432
+ - Make the smallest correct change per unit, and fix in THIS run every
433
+ defect inside the change's blast radius — the request not delivered,
434
+ a regression this change introduces, an invalid proof, a failing test
435
+ or stale doc of code you touched — as registered work (todo plus
436
+ success criterion) to the ideal state. A defect outside it gets a
437
+ tracked issue with reproduction and evidence and a line in the final
438
+ message; a deferral never turns a criterion into PASS. Keep delegated
439
+ unit scope hard: the worker reports, the orchestrator registers or
440
+ files.
434
441
  - Never suppress lints / errors / test failures. Never delete, skip,
435
442
  `.only`, `.skip`, `xfail`, or comment out tests to green the suite.
436
443
  - Never claim done from inference — only from captured evidence.
@@ -5,5 +5,5 @@
5
5
  "index.js": "b6b064fab277031c6c51f5a58b84b31040f9960bf767bf646adc0540c1d25c1b",
6
6
  "page-bundle.js": "bfd51e8383cf47200d57387bbbed258bce5a3a3d00c38fc108034d2888e23f0c"
7
7
  },
8
- "stagedAtUtc": "2026-09-29T22:25:16.537Z"
8
+ "stagedAtUtc": "2026-09-30T18:48:33.732Z"
9
9
  }
@@ -53,7 +53,7 @@ Do ALL of this immediately after the plan is selected, BEFORE the first implemen
53
53
 
54
54
  1. **Set the goal, in detail.** When a goal tool is available (`create_goal`), call it with a DETAILED objective: the plan name and path, the concrete end state, the phase and task counts, the delivery mode (direct, `--make-pr`, or `--ship`), and how completion will be verified. One work session = one registered goal (the goal tool holds one active goal); each phase then carries its own concrete goal — the ledger entry Phase 2 records before the wave's first dispatch, defined from the previous phase's landed and verified evidence. No goal tool -> record the same objective as the first ledger entry.
55
55
  2. **Register every phase and task as todos.** Mirror the plan into the todo/plan tool of your harness: one phase per plan wave, one todo per column-zero checkbox (including the final verification wave). Register ALL of them up front - never keep tasks in memory only.
56
- 3. **Keep them current at every moment.** Mark a todo in_progress when its work dispatches and done immediately after its verification passes. Never batch-complete at the end, never execute work that is not a registered todo; discovered work — a pre-existing bug, failing test, stale doc, or wrong guidance — is appended to the plan as a checkbox, mirrored as a todo before it runs, and fixed to the ideal state, never deferred as a follow-up. A worker that meets a defect outside its assigned files reports it instead of fixing it; only the orchestrator appends the checkbox and dispatches it to a correctly scoped unit. The todo list, Boulder state, and plan checkboxes must always tell the same story.
56
+ 3. **Keep them current at every moment.** Mark a todo in_progress when its work dispatches and done immediately after its verification passes. Never batch-complete at the end, never execute work that is not a registered todo; discovered work inside the change's blast radius — the request not delivered, a regression this change introduces, an invalid proof, a failing test or stale doc of touched code — is appended to the plan as a checkbox, mirrored as a todo before it runs, and fixed to the ideal state; a defect outside it gets a tracked issue with reproduction and evidence and a line in the final report. A worker that meets a defect outside its assigned files reports it instead of fixing it; only the orchestrator appends the checkbox and dispatches it to a correctly scoped unit. The todo list, Boulder state, and plan checkboxes must always tell the same story.
57
57
 
58
58
  ## Phase 1: Select the plan
59
59