oh-my-opencode 5.1.47 → 5.1.48

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 (100) hide show
  1. package/dist/cli/index.js +15 -15
  2. package/dist/cli-node/index.js +15 -15
  3. package/dist/index.js +14 -14
  4. package/dist/skills/browser/runtime/omowright/manifest.json +1 -1
  5. package/dist/skills/ulw-research/SKILL.md +58 -1
  6. package/dist/skills/ulw-research/references/deliverable-phase.md +51 -9
  7. package/dist/skills/ulw-research/references/report-gates.md +2 -1
  8. package/dist/skills/ulw-research/scripts/contracts.mjs +3 -11
  9. package/dist/skills/ulw-research/scripts/contracts.test.ts +1 -0
  10. package/dist/skills/ulw-research/scripts/copy-returns-fidelity-policy.test.ts +156 -0
  11. package/dist/skills/ulw-research/scripts/copy-returns-polish.test.ts +6 -6
  12. package/dist/skills/ulw-research/scripts/fact-commands-fill.mjs +86 -0
  13. package/dist/skills/ulw-research/scripts/fact-commands-meaning.mjs +14 -22
  14. package/dist/skills/ulw-research/scripts/fact-commands-polish.mjs +7 -2
  15. package/dist/skills/ulw-research/scripts/fact-commands-verify.mjs +18 -4
  16. package/dist/skills/ulw-research/scripts/fact-commands.mjs +6 -47
  17. package/dist/skills/ulw-research/scripts/fact-fill.mjs +3 -2
  18. package/dist/skills/ulw-research/scripts/fidelity-policy.mjs +183 -0
  19. package/dist/skills/ulw-research/scripts/report-tools.test.ts +245 -4
  20. package/dist/skills/ulw-research/scripts/source-commands.mjs +8 -3
  21. package/dist/tui.js +14 -14
  22. package/package.json +14 -14
  23. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  24. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  25. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  26. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  27. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  28. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  29. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  30. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  31. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  32. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
  33. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  34. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  35. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  36. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  37. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  38. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  39. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  40. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  41. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  42. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  43. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
  44. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
  45. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
  46. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  47. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  48. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  49. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  50. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  51. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  52. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  53. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  54. package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
  55. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  56. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  57. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  58. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  59. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  60. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  61. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  62. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  63. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  64. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  65. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  66. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  67. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  68. package/packages/omo-codex/plugin/package-lock.json +12 -12
  69. package/packages/omo-codex/plugin/package.json +1 -1
  70. package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +1 -1
  71. package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +58 -1
  72. package/packages/omo-codex/plugin/skills/ulw-research/references/deliverable-phase.md +51 -9
  73. package/packages/omo-codex/plugin/skills/ulw-research/references/report-gates.md +2 -1
  74. package/packages/omo-codex/plugin/skills/ulw-research/scripts/contracts.mjs +3 -11
  75. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-fill.mjs +86 -0
  76. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-meaning.mjs +14 -22
  77. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-polish.mjs +7 -2
  78. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-verify.mjs +18 -4
  79. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands.mjs +6 -47
  80. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-fill.mjs +3 -2
  81. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fidelity-policy.mjs +183 -0
  82. package/packages/omo-codex/plugin/skills/ulw-research/scripts/source-commands.mjs +8 -3
  83. package/packages/omo-codex/scripts/install-dist/install-local.mjs +2 -2
  84. package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +1 -1
  85. package/packages/shared-skills/skills/ulw-research/SKILL.md +58 -1
  86. package/packages/shared-skills/skills/ulw-research/references/deliverable-phase.md +51 -9
  87. package/packages/shared-skills/skills/ulw-research/references/report-gates.md +2 -1
  88. package/packages/shared-skills/skills/ulw-research/scripts/contracts.mjs +3 -11
  89. package/packages/shared-skills/skills/ulw-research/scripts/contracts.test.ts +1 -0
  90. package/packages/shared-skills/skills/ulw-research/scripts/copy-returns-fidelity-policy.test.ts +156 -0
  91. package/packages/shared-skills/skills/ulw-research/scripts/copy-returns-polish.test.ts +6 -6
  92. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-fill.mjs +86 -0
  93. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-meaning.mjs +14 -22
  94. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-polish.mjs +7 -2
  95. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-verify.mjs +18 -4
  96. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands.mjs +6 -47
  97. package/packages/shared-skills/skills/ulw-research/scripts/fact-fill.mjs +3 -2
  98. package/packages/shared-skills/skills/ulw-research/scripts/fidelity-policy.mjs +183 -0
  99. package/packages/shared-skills/skills/ulw-research/scripts/report-tools.test.ts +245 -4
  100. package/packages/shared-skills/skills/ulw-research/scripts/source-commands.mjs +8 -3
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, onTestFinished, test } from "bun:test"
2
2
  import { createHash } from "node:crypto"
3
- import { copyFileSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"
3
+ import { copyFileSync, existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"
4
4
  import { tmpdir } from "node:os"
5
5
  import { delimiter, join } from "node:path"
6
6
 
@@ -517,7 +517,7 @@ function polishSession() {
517
517
  }
518
518
 
519
519
  function polishApply(dir: string, edits: string) {
520
- const args = ["fact", "polish-apply", join(dir, "report.html"), "--edits", edits, "--ledger", join(dir, "fact-ledger.json"), "--out", join(dir, "report.polished.html")]
520
+ const args = ["fact", "polish-apply", join(dir, "report.html"), "--edits", edits, "--ledger", join(dir, "fact-ledger.json"), "--out", join(dir, "report.polished.html"), "--session-dir", dir]
521
521
  const proc = nodeJson(args)
522
522
  return { ...proc, json: proc.json as unknown as PolishJson }
523
523
  }
@@ -547,7 +547,7 @@ describe("polish wiring under node", () => {
547
547
  expect(applied.json.defects[0]?.reason).toContain("markup changed")
548
548
  expect(readFileSync(join(dir, "report.polished.html"), "utf8")).toBe(html)
549
549
  writeFileSync(join(dir, "attribute.html"), html.replace("<h2>Findings</h2>", '<h2 id="findings">Findings</h2>'))
550
- const diffed = nodeJson(["fact", "polish-diff", "--before", join(dir, "report.html"), "--after", join(dir, "attribute.html"), "--ledger", join(dir, "fact-ledger.json")])
550
+ const diffed = nodeJson(["fact", "polish-diff", "--before", join(dir, "report.html"), "--after", join(dir, "attribute.html"), "--ledger", join(dir, "fact-ledger.json"), "--session-dir", dir])
551
551
  expect(diffed.code).toBe(1)
552
552
  expect(codesOf(diffed.json)).toContain("polish_diff_fail")
553
553
  expect(JSON.stringify(diffed.json)).toContain("markup changed")
@@ -570,7 +570,7 @@ describe("polish wiring under node", () => {
570
570
  const { dir } = polishSession()
571
571
  const html = readFileSync(join(dir, "report.html"), "utf8")
572
572
  writeFileSync(join(dir, "hand.html"), html.replace("320 m", "321 m"))
573
- const diffed = nodeJson(["fact", "polish-diff", "--before", join(dir, "report.html"), "--after", join(dir, "hand.html"), "--ledger", join(dir, "fact-ledger.json")])
573
+ const diffed = nodeJson(["fact", "polish-diff", "--before", join(dir, "report.html"), "--after", join(dir, "hand.html"), "--ledger", join(dir, "fact-ledger.json"), "--session-dir", dir])
574
574
  expect(diffed.code).toBe(1)
575
575
  expect(codesOf(diffed.json)).toContain("polish_diff_fail")
576
576
  })
@@ -755,3 +755,244 @@ describe("fidelity delivery barrier", () => {
755
755
  expect(matched).toEqual({ exitCode: 0, codes: [] })
756
756
  })
757
757
  })
758
+
759
+ /** Rewrite one `## Fidelity` field line of the session's brief.md. */
760
+ function setField(dir: string, field: string, value: string, answeredBy = "user") {
761
+ const path = join(dir, "brief.md")
762
+ const line = new RegExp(`^${field}: .*$`, "mu")
763
+ writeFileSync(path, readFileSync(path, "utf8").replace(line, `${field}: ${value} (answered_by: ${answeredBy})`))
764
+ }
765
+
766
+ function fillTemplate(dir: string, body: string, extra: string[] = []) {
767
+ const source = ['<!doctype html><html lang="en"><head><meta charset="utf-8"><title>T</title></head><body><h1>T</h1>', "<h2>Findings</h2>", body, "</body></html>"].join("\n")
768
+ writeFileSync(join(dir, "t.src.html"), source)
769
+ return nodeJson(["fact", "fill", join(dir, "t.src.html"), "--out", join(dir, "t.html"), "--session-dir", dir, ...extra])
770
+ }
771
+
772
+ /** Snapshot a one-line text source under `id` with the given provenance (omitted: unknown) and register its span row. */
773
+ function textSource(dir: string, id: string, provenance?: string) {
774
+ writeFileSync(join(dir, `${id}.txt`), "Commentary: the span length is 320 m according to this review.\n")
775
+ const prov = provenance === undefined ? [] : ["--provenance", provenance]
776
+ const snap = nodeJson(["source", "snapshot", "--id", id, "--text", join(dir, `${id}.txt`), "--kind", "document", ...prov, "--session-dir", dir])
777
+ const add = nodeJson(["fact", "add", "--source", id, "--find", "span length", "--label", `span_${id.toLowerCase()}`, "--json", "--session-dir", dir])
778
+ return { snap, row: add.json.data?.row }
779
+ }
780
+
781
+ const ledgerRow = (dir: string, id: string) => JSON.parse(readFileSync(join(dir, "fact-ledger.json"), "utf8")).rows.find((r: { fact_id: string }) => r.fact_id === id)
782
+
783
+ describe("fidelity brief lock and policy under node", () => {
784
+ test("#given a filled session whose brief turns pending #when each reader-facing command runs #then all six exit 1 with only fidelity_interview_incomplete and write nothing", () => {
785
+ // given
786
+ const { dir, s } = pairedSession()
787
+ copyFileSync(join(FIXTURES, "fidelity-brief-pending.md"), join(dir, "brief.md"))
788
+ for (const name of ["fidelity-defects.json", "pairs.json"]) rmSync(join(dir, name), { force: true })
789
+ const drift = verdicts(dir, "cheap.json", "cheap", [])
790
+ writeFileSync(join(dir, "edits.json"), "[]")
791
+ // when
792
+ const runs = {
793
+ fill: nodeJson(["fact", "fill", join(dir, "report.src.html"), "--out", join(dir, "again.html"), ...s]),
794
+ verify: verify(dir, "report.html"),
795
+ pairs: nodeJson(["fact", "pairs", join(dir, "report.html"), "--out", join(dir, "pairs.json"), ...s]),
796
+ meaning: nodeJson(["fact", "meaning-check", "--pairs", join(dir, "absent-pairs.json"), "--verdicts", drift, ...s]),
797
+ polishApply: nodeJson(["fact", "polish-apply", join(dir, "report.html"), "--edits", join(dir, "edits.json"), "--out", join(dir, "polished.html"), ...s]),
798
+ polishDiff: nodeJson(["fact", "polish-diff", "--before", join(dir, "report.html"), "--after", join(dir, "report.html"), "--out", join(dir, "polish.json"), ...s]),
799
+ }
800
+ // then
801
+ for (const [name, result] of Object.entries(runs)) {
802
+ expect({ name, code: result.code, codes: codesOf(result.json) }).toEqual({ name, code: 1, codes: ["fidelity_interview_incomplete"] })
803
+ }
804
+ for (const name of ["again.html", "fidelity-defects.json", "pairs.json", "polished.html", "polish.json", "fidelity-meaning-defects.json"]) expect({ name, exists: existsSync(join(dir, name)) }).toEqual({ name, exists: false })
805
+ })
806
+
807
+ test("#given derived numbers none #when a derived or a converted placeholder is filled #then derived_not_allowed, and allowed renders both", () => {
808
+ // given
809
+ const { dir, s, mm } = fidelitySession()
810
+ const expr = nodeJson(["fact", "derive", "--op", "expr", "F1 * 2", "--unit", "m", "--rationale", "two spans", "--label", "two_spans", "--json", ...s])
811
+ const twice = expr.json.data.row.fact_id
812
+ setField(dir, "derived numbers", "none")
813
+ // when
814
+ const derived = fillTemplate(dir, `<p>Two spans are {{${twice}:two_spans}} [S4].</p>`)
815
+ const converted = fillTemplate(dir, `<p>The span is {{${mm}:span_mm}} [S4].</p>`)
816
+ setField(dir, "derived numbers", "allowed")
817
+ const allowed = fillTemplate(dir, `<p>Two spans are {{${twice}:two_spans}} [S4]. The span is {{${mm}:span_mm}} [S4].</p>`)
818
+ // then
819
+ expect(expr.code).toBe(0)
820
+ expect([derived.code, codesOf(derived.json)]).toEqual([1, ["derived_not_allowed"]])
821
+ expect([converted.code, codesOf(converted.json)]).toEqual([1, ["derived_not_allowed"]])
822
+ expect(allowed.code).toBe(0)
823
+ expect(readFileSync(join(dir, "t.html"), "utf8")).toContain("320,000 mm")
824
+ })
825
+
826
+ test("#given source floor primary only #when rows from secondary, unknown, and primary sources are filled #then only the primary row renders", () => {
827
+ // given
828
+ const { dir } = fidelitySession()
829
+ const secondary = textSource(dir, "S7", "secondary")
830
+ const unknown = textSource(dir, "S8")
831
+ const primary = textSource(dir, "S6", "primary")
832
+ setField(dir, "source floor", "primary only")
833
+ // when
834
+ const fills = [secondary, unknown, primary].map(({ row }) => fillTemplate(dir, `<p>The span is {{${row.fact_id}:${row.label}}} [S4].</p>`))
835
+ // then
836
+ expect([secondary.snap.json, unknown.snap.json, primary.snap.json].map((json) => (json as unknown as { data: { provenance: string } }).data.provenance)).toEqual(["secondary", "unknown", "primary"])
837
+ expect(fills.map((f) => [f.code, codesOf(f.json)])).toEqual([
838
+ [1, ["source_outside_floor"]],
839
+ [1, ["source_outside_floor"]],
840
+ [0, []],
841
+ ])
842
+ })
843
+
844
+ test("#given a row registered while source floor is pending #when the floor locks to pointed only S4, then standard #then fill refuses, then renders, and outside_floor caches each result", () => {
845
+ // given
846
+ const { dir } = fidelitySession()
847
+ setField(dir, "source floor", "pending", "pending")
848
+ const { snap, row } = textSource(dir, "S9")
849
+ const body = `<p>The span is {{${row.fact_id}:${row.label}}} [S9].</p>`
850
+ // when
851
+ setField(dir, "source floor", "pointed only: S4")
852
+ const refused = fillTemplate(dir, body)
853
+ const cachedRefused = ledgerRow(dir, row.fact_id).outside_floor
854
+ setField(dir, "source floor", "standard")
855
+ const rendered = fillTemplate(dir, body)
856
+ // then
857
+ expect(snap.code).toBe(0)
858
+ expect(row.outside_floor).toBe(false)
859
+ expect([refused.code, codesOf(refused.json)]).toEqual([1, ["source_outside_floor"]])
860
+ expect(cachedRefused).toBe(true)
861
+ expect(rendered.code).toBe(0)
862
+ expect(ledgerRow(dir, row.fact_id).outside_floor).toBe(false)
863
+ })
864
+
865
+ test("#given a markdown translation comment after a quote #when languages is translation beside, then quotes original #then fact pairs emits a translation pair only for the first", () => {
866
+ // given
867
+ const { dir, s } = fidelitySession()
868
+ const md = ["# T", "", "## Findings", "", "The operator wrote {{Q1:completion}} <!-- translates: Q1 --> completion comes in 2027 at the earliest [S4].", ""].join("\n")
869
+ writeFileSync(join(dir, "t.src.md"), md)
870
+ const kinds = () => {
871
+ const filled = nodeJson(["fact", "fill", join(dir, "t.src.md"), "--format", "md", "--out", join(dir, "t.md"), ...s])
872
+ const pairs = nodeJson(["fact", "pairs", join(dir, "t.md"), "--out", join(dir, "pairs.json"), ...s])
873
+ return { fill: filled.code, pairs: pairs.code, kinds: JSON.parse(readFileSync(join(dir, "pairs.json"), "utf8")).pairs.map((p: Pair) => p.kind) }
874
+ }
875
+ // when
876
+ const beside = kinds()
877
+ setField(dir, "languages", "report en; sources en; quotes original")
878
+ const original = kinds()
879
+ // then
880
+ expect(beside).toEqual({ fill: 0, pairs: 0, kinds: ["quote", "translation"] })
881
+ expect(original).toEqual({ fill: 0, pairs: 0, kinds: ["quote"] })
882
+ })
883
+
884
+ test("#given a sentence that failed twice #when filled with --replace-failed #then block-quote replaces it and fallback omit removes it", () => {
885
+ // given
886
+ const { dir } = fidelitySession()
887
+ const state = { schemaVersion: 1, failures: { "findings#F1": 2 }, replaced: [] }
888
+ const body = "<p>The span length is {{F1:span_len}} [S4]. The operator wrote {{Q1:completion}} [S4].</p>"
889
+ // when
890
+ writeFileSync(join(dir, "fidelity-state.json"), JSON.stringify(state))
891
+ const quoted = fillTemplate(dir, body, ["--replace-failed"])
892
+ const quotedHtml = readFileSync(join(dir, "t.html"), "utf8")
893
+ setField(dir, "fallback", "omit")
894
+ writeFileSync(join(dir, "fidelity-state.json"), JSON.stringify(state))
895
+ const omitted = fillTemplate(dir, body, ["--replace-failed"])
896
+ const omittedHtml = readFileSync(join(dir, "t.html"), "utf8")
897
+ // then
898
+ expect(quoted.code).toBe(0)
899
+ expect(quotedHtml).toContain("<blockquote")
900
+ expect(omitted.code).toBe(0)
901
+ expect(omittedHtml).not.toContain("<blockquote")
902
+ expect(omittedHtml).not.toContain("The span length is")
903
+ expect(omittedHtml).toContain("The operator wrote")
904
+ })
905
+
906
+ test("#given --calibration #when meaning-check flags a pair #then the calibration line goes to that path, not the session default", () => {
907
+ const { dir, s } = pairedSession()
908
+ const drift = { pair_id: "P2", verdict: "drift", drift_kind: "hedge_dropped", fragment: "no earlier than 2027", source_fragment: "we estimate" }
909
+ const log = join(dir, "custom-calibration.jsonl")
910
+ const checked = nodeJson(["fact", "meaning-check", "--pairs", join(dir, "pairs.json"), "--verdicts", verdicts(dir, "cheap.json", "cheap", [drift, { pair_id: "P1", verdict: "ok" }]), "--calibration", log, ...s])
911
+ const rows = readFileSync(log, "utf8").trim().split("\n").map((line) => JSON.parse(line)).filter((row) => row.header !== true && row.run !== true)
912
+ expect(checked.code).toBe(0)
913
+ expect(rows).toHaveLength(1)
914
+ expect(existsSync(join(dir, "fidelity-calibration.jsonl"))).toBe(false)
915
+ })
916
+ })
917
+
918
+ describe("fidelity brief values, sourceless rows, and the brief binding under node", () => {
919
+ test("#given derived numbers or fallback off its table #when fact fill runs #then a usage error names the field and value and nothing is written", () => {
920
+ // given
921
+ const { dir, s } = fidelitySession()
922
+ const twice = nodeJson(["fact", "derive", "--op", "expr", "F1 * 2", "--unit", "m", "--rationale", "two spans", "--label", "two_spans", "--json", ...s]).json.data.row.fact_id
923
+ // when
924
+ setField(dir, "derived numbers", "nope")
925
+ const derived = fillTemplate(dir, `<p>Two spans are {{${twice}:two_spans}} [S4].</p>`)
926
+ setField(dir, "derived numbers", "allowed")
927
+ setField(dir, "fallback", "drop")
928
+ const fallback = fillTemplate(dir, "<p>The span length is {{F1:span_len}} [S4].</p>")
929
+ // then
930
+ expect({ code: derived.code, named: derived.stderr.includes('derived numbers "nope"') }).toEqual({ code: 2, named: true })
931
+ expect({ code: fallback.code, named: fallback.stderr.includes('fallback "drop"') }).toEqual({ code: 2, named: true })
932
+ expect(existsSync(join(dir, "t.html"))).toBe(false)
933
+ })
934
+
935
+ test("#given an assumed row #when filled under primary only, then standard #then the sourceless row is source_outside_floor, then renders", () => {
936
+ // given
937
+ const { dir, s } = fidelitySession()
938
+ const assumed = nodeJson(["fact", "add", "--assumed", "12", "--unit", "%", "--rationale", "planning share chosen by the requester", "--label", "share_assumed", "--json", ...s]).json.data.row.fact_id
939
+ const body = `<p>The planning share is {{${assumed}:share_assumed}}.</p>`
940
+ // when
941
+ setField(dir, "source floor", "primary only")
942
+ const refused = fillTemplate(dir, body)
943
+ setField(dir, "source floor", "standard")
944
+ const rendered = fillTemplate(dir, body)
945
+ // then
946
+ expect([refused.code, codesOf(refused.json)]).toEqual([1, ["source_outside_floor"]])
947
+ expect(JSON.stringify(refused.json)).toContain("no source")
948
+ expect(rendered.code).toBe(0)
949
+ })
950
+
951
+ test("#given a derived row whose operand rests on a secondary source #when filled under primary only #then source_outside_floor names that source", () => {
952
+ // given
953
+ const { dir, s } = fidelitySession()
954
+ const { row } = textSource(dir, "S7", "secondary")
955
+ const twice = nodeJson(["fact", "derive", "--op", "expr", `${row.fact_id} * 2`, "--unit", "m", "--rationale", "two spans", "--label", "two_spans", "--json", ...s]).json.data.row.fact_id
956
+ setField(dir, "source floor", "primary only")
957
+ // when
958
+ const refused = fillTemplate(dir, `<p>Two spans are {{${twice}:two_spans}} [S7].</p>`)
959
+ // then
960
+ expect([refused.code, codesOf(refused.json)]).toEqual([1, ["source_outside_floor"]])
961
+ expect(JSON.stringify(refused.json)).toContain("rests on S7")
962
+ })
963
+
964
+ test("#given an edition filled under standard from a secondary source #when the brief tightens to primary only #then fact verify refuses with and without its fill map", () => {
965
+ // given
966
+ const { dir, s } = fidelitySession()
967
+ const { row } = textSource(dir, "S7", "secondary")
968
+ const src = readFileSync(join(dir, "report.src.html"), "utf8")
969
+ .replace("<h2>How", `<p>A review gives the span as {{${row.fact_id}:${row.label}}} [S7].</p>\n<h2>How`)
970
+ .replace("</ol>", '<li id="s7">[S7] Bridge review, retrieved 2026-10-01.</li></ol>')
971
+ writeFileSync(join(dir, "report.src.html"), src)
972
+ const filled = nodeJson(["fact", "fill", join(dir, "report.src.html"), "--out", join(dir, "report.html"), ...s])
973
+ const map = join(dir, "report.html.fill-map.json")
974
+ const before = verify(dir, "report.html")
975
+ // when
976
+ setField(dir, "source floor", "primary only")
977
+ const bound = verify(dir, "report.html")
978
+ const record = JSON.parse(readFileSync(join(dir, "fidelity-defects.json"), "utf8"))
979
+ const kept = readFileSync(map, "utf8")
980
+ rmSync(map)
981
+ const unbound = verify(dir, "report.html")
982
+ setField(dir, "source floor", "standard")
983
+ const { brief_sha256: _dropped, ...legacy } = JSON.parse(kept)
984
+ writeFileSync(map, JSON.stringify(legacy))
985
+ const unstamped = verify(dir, "report.html")
986
+ // then
987
+ expect([filled.code, before.code, codesOf(before.json)]).toEqual([0, 0, []])
988
+ expect(typeof JSON.parse(kept).brief_sha256).toBe("string")
989
+ expect(bound.code).toBe(1)
990
+ expect(codesOf(bound.json)).toContain("brief_changed")
991
+ expect(codesOf(bound.json)).toContain("source_outside_floor")
992
+ expect(record.status).toBe("fail")
993
+ expect(unbound.code).toBe(1)
994
+ expect(codesOf(unbound.json)).toContain("source_outside_floor")
995
+ expect(codesOf(unbound.json)).not.toContain("brief_changed")
996
+ expect([unstamped.code, codesOf(unstamped.json)]).toEqual([1, ["brief_changed"]])
997
+ })
998
+ })
@@ -14,6 +14,7 @@ import { join, resolve } from "node:path"
14
14
  import { indexClaims } from "./claims-index.mjs"
15
15
  import { CliError, parseArgs, sha256Of, writeJsonAtomic } from "./cli-support.mjs"
16
16
  import { parseFidelityBlock } from "./fidelity-brief.mjs"
17
+ import { parseSourceFloor } from "./fidelity-policy.mjs"
17
18
  import { writeSnapshot } from "./source-snapshot.mjs"
18
19
 
19
20
  const FETCH_TIMEOUT_MS = 30_000
@@ -44,12 +45,16 @@ export function snapshotOf(sessionDir, id) {
44
45
  return { meta: JSON.parse(readInput(join(dir, "meta.json"))), text: readInput(join(dir, "text.txt")), html: existsSync(html) ? readInput(html) : null }
45
46
  }
46
47
 
47
- /** Whether the brief's source floor lists this source as pointed at (`pointed only: a, b`). */
48
+ /**
49
+ * Whether the brief's source floor lists this source as pointed at (`pointed only: a, b`). An absent
50
+ * or pending floor points at nothing; a malformed floor is a usage error (parseSourceFloor's CliError).
51
+ */
48
52
  export function pointedFlag(sessionDir, url) {
49
53
  const brief = join(sessionDir, "brief.md")
50
54
  const floor = existsSync(brief) ? (parseFidelityBlock(readInput(brief)).fields?.["source floor"] ?? "") : ""
51
- const match = /^pointed only:\s*(.+)$/i.exec(floor.trim())
52
- return match ? match[1].split(",").map((entry) => entry.trim()).includes(url) : false
55
+ if (floor === "" || floor === "pending") return false
56
+ const rule = parseSourceFloor(floor)
57
+ return rule?.kind === "pointed" && rule.list.includes(url)
53
58
  }
54
59
 
55
60
  async function fetchSource(url) {
package/dist/tui.js CHANGED
@@ -75521,7 +75521,7 @@ init_shared();
75521
75521
  // package.json
75522
75522
  var package_default = {
75523
75523
  name: "oh-my-opencode",
75524
- version: "5.1.47",
75524
+ version: "5.1.48",
75525
75525
  description: "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
75526
75526
  main: "./dist/index.js",
75527
75527
  types: "dist/index.d.ts",
@@ -75704,7 +75704,7 @@ var package_default = {
75704
75704
  },
75705
75705
  devDependencies: {
75706
75706
  "@clack/prompts": "^1.8.1",
75707
- "@code-yeongyu/senpi": "2026.10.23-1",
75707
+ "@code-yeongyu/senpi": "2026.10.25-2",
75708
75708
  "@modelcontextprotocol/sdk": "^1.30.0",
75709
75709
  "@oh-my-opencode/agents-md-core": "workspace:*",
75710
75710
  "@oh-my-opencode/ast-grep-mcp": "workspace:*",
@@ -75767,18 +75767,18 @@ var package_default = {
75767
75767
  typescript: "^7.0.2"
75768
75768
  },
75769
75769
  optionalDependencies: {
75770
- "oh-my-opencode-darwin-arm64": "5.1.47",
75771
- "oh-my-opencode-darwin-x64": "5.1.47",
75772
- "oh-my-opencode-darwin-x64-baseline": "5.1.47",
75773
- "oh-my-opencode-linux-arm64": "5.1.47",
75774
- "oh-my-opencode-linux-arm64-musl": "5.1.47",
75775
- "oh-my-opencode-linux-x64": "5.1.47",
75776
- "oh-my-opencode-linux-x64-baseline": "5.1.47",
75777
- "oh-my-opencode-linux-x64-musl": "5.1.47",
75778
- "oh-my-opencode-linux-x64-musl-baseline": "5.1.47",
75779
- "oh-my-opencode-windows-arm64": "5.1.47",
75780
- "oh-my-opencode-windows-x64": "5.1.47",
75781
- "oh-my-opencode-windows-x64-baseline": "5.1.47"
75770
+ "oh-my-opencode-darwin-arm64": "5.1.48",
75771
+ "oh-my-opencode-darwin-x64": "5.1.48",
75772
+ "oh-my-opencode-darwin-x64-baseline": "5.1.48",
75773
+ "oh-my-opencode-linux-arm64": "5.1.48",
75774
+ "oh-my-opencode-linux-arm64-musl": "5.1.48",
75775
+ "oh-my-opencode-linux-x64": "5.1.48",
75776
+ "oh-my-opencode-linux-x64-baseline": "5.1.48",
75777
+ "oh-my-opencode-linux-x64-musl": "5.1.48",
75778
+ "oh-my-opencode-linux-x64-musl-baseline": "5.1.48",
75779
+ "oh-my-opencode-windows-arm64": "5.1.48",
75780
+ "oh-my-opencode-windows-x64": "5.1.48",
75781
+ "oh-my-opencode-windows-x64-baseline": "5.1.48"
75782
75782
  },
75783
75783
  overrides: {
75784
75784
  hono: "^4.13.8",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-opencode",
3
- "version": "5.1.47",
3
+ "version": "5.1.48",
4
4
  "description": "The Best AI Agent Harness - Batteries-Included OpenCode Plugin with Multi-Model Orchestration, Parallel Background Agents, and Crafted LSP/AST Tools",
5
5
  "main": "./dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -183,7 +183,7 @@
183
183
  },
184
184
  "devDependencies": {
185
185
  "@clack/prompts": "^1.8.1",
186
- "@code-yeongyu/senpi": "2026.10.23-1",
186
+ "@code-yeongyu/senpi": "2026.10.25-2",
187
187
  "@modelcontextprotocol/sdk": "^1.30.0",
188
188
  "@oh-my-opencode/agents-md-core": "workspace:*",
189
189
  "@oh-my-opencode/ast-grep-mcp": "workspace:*",
@@ -246,18 +246,18 @@
246
246
  "typescript": "^7.0.2"
247
247
  },
248
248
  "optionalDependencies": {
249
- "oh-my-opencode-darwin-arm64": "5.1.47",
250
- "oh-my-opencode-darwin-x64": "5.1.47",
251
- "oh-my-opencode-darwin-x64-baseline": "5.1.47",
252
- "oh-my-opencode-linux-arm64": "5.1.47",
253
- "oh-my-opencode-linux-arm64-musl": "5.1.47",
254
- "oh-my-opencode-linux-x64": "5.1.47",
255
- "oh-my-opencode-linux-x64-baseline": "5.1.47",
256
- "oh-my-opencode-linux-x64-musl": "5.1.47",
257
- "oh-my-opencode-linux-x64-musl-baseline": "5.1.47",
258
- "oh-my-opencode-windows-arm64": "5.1.47",
259
- "oh-my-opencode-windows-x64": "5.1.47",
260
- "oh-my-opencode-windows-x64-baseline": "5.1.47"
249
+ "oh-my-opencode-darwin-arm64": "5.1.48",
250
+ "oh-my-opencode-darwin-x64": "5.1.48",
251
+ "oh-my-opencode-darwin-x64-baseline": "5.1.48",
252
+ "oh-my-opencode-linux-arm64": "5.1.48",
253
+ "oh-my-opencode-linux-arm64-musl": "5.1.48",
254
+ "oh-my-opencode-linux-x64": "5.1.48",
255
+ "oh-my-opencode-linux-x64-baseline": "5.1.48",
256
+ "oh-my-opencode-linux-x64-musl": "5.1.48",
257
+ "oh-my-opencode-linux-x64-musl-baseline": "5.1.48",
258
+ "oh-my-opencode-windows-arm64": "5.1.48",
259
+ "oh-my-opencode-windows-x64": "5.1.48",
260
+ "oh-my-opencode-windows-x64-baseline": "5.1.48"
261
261
  },
262
262
  "overrides": {
263
263
  "hono": "^4.13.8",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omo",
3
- "version": "5.1.47",
3
+ "version": "5.1.48",
4
4
  "description": "One Codex plugin namespace for Yeongyu's local Codex components.",
5
5
  "author": {
6
6
  "name": "Yeongyu Kim",
@@ -8,7 +8,7 @@
8
8
  "command": "node \"${PLUGIN_ROOT}/components/bootstrap/dist/cli.js\" hook session-start",
9
9
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\bootstrap.ps1\"",
10
10
  "timeout": 30,
11
- "statusMessage": "(OmO 5.1.47) Checking Bootstrap Provisioning"
11
+ "statusMessage": "(OmO 5.1.48) Checking Bootstrap Provisioning"
12
12
  }
13
13
  ]
14
14
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sisyphuslabs/codex-bootstrap",
3
- "version": "5.1.47",
3
+ "version": "5.1.48",
4
4
  "description": "Codex SessionStart bootstrap component that provisions LazyCodex runtime dependencies from a detached worker.",
5
5
  "type": "module",
6
6
  "private": true,
@@ -8,7 +8,7 @@
8
8
  "type": "command",
9
9
  "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook post-tool-use",
10
10
  "timeout": 30,
11
- "statusMessage": "(OmO 5.1.47) Checking Comments"
11
+ "statusMessage": "(OmO 5.1.48) Checking Comments"
12
12
  }
13
13
  ]
14
14
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/codex-comment-checker",
3
- "version": "5.1.47",
3
+ "version": "5.1.48",
4
4
  "description": "Codex plugin that runs comment-checker after edit-like PostToolUse hooks.",
5
5
  "type": "module",
6
6
  "packageManager": "npm@11.12.1",
@@ -8,7 +8,7 @@
8
8
  "type": "command",
9
9
  "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook pre-tool-use",
10
10
  "timeout": 5,
11
- "statusMessage": "(OmO 5.1.47) Recommending Git Bash MCP"
11
+ "statusMessage": "(OmO 5.1.48) Recommending Git Bash MCP"
12
12
  }
13
13
  ]
14
14
  }
@@ -20,7 +20,7 @@
20
20
  "type": "command",
21
21
  "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook post-compact",
22
22
  "timeout": 5,
23
- "statusMessage": "(OmO 5.1.47) Resetting Git Bash MCP Reminder"
23
+ "statusMessage": "(OmO 5.1.48) Resetting Git Bash MCP Reminder"
24
24
  }
25
25
  ]
26
26
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sisyphuslabs/codex-git-bash-hook",
3
- "version": "5.1.47",
3
+ "version": "5.1.48",
4
4
  "description": "Codex hook component that reminds Windows sessions to prefer the OMO git_bash MCP.",
5
5
  "type": "module",
6
6
  "private": true,
@@ -8,7 +8,7 @@
8
8
  "type": "command",
9
9
  "command": "node \"${PLUGIN_ROOT}/components/lazycodex-executor-verify/dist/cli.js\" hook subagent-stop",
10
10
  "timeout": 10,
11
- "statusMessage": "(OmO 5.1.47) Verifying LazyCodex Executor Evidence"
11
+ "statusMessage": "(OmO 5.1.48) Verifying LazyCodex Executor Evidence"
12
12
  }
13
13
  ]
14
14
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/codex-lazycodex-executor-verify",
3
- "version": "5.1.47",
3
+ "version": "5.1.48",
4
4
  "description": "Codex SubagentStop evidence verifier for LazyCodex executor completions.",
5
5
  "type": "module",
6
6
  "packageManager": "npm@11.12.1",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "version": "5.1.47",
4
- "inputDigest": "sha256:6cabf483e0820d8ebb42944ea3df97bedb1fcc9362a46f2659007ffa48225e10",
3
+ "version": "5.1.48",
4
+ "inputDigest": "sha256:be1d599745bd7b9394f34c8c277f3888b908c4b7fa1fd672aeae6c8025a4cd07",
5
5
  "outputs": [
6
6
  {
7
7
  "path": "cli.d.ts",
@@ -8,7 +8,7 @@
8
8
  "type": "command",
9
9
  "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook post-tool-use",
10
10
  "timeout": 60,
11
- "statusMessage": "(OmO 5.1.47) Checking LSP Diagnostics"
11
+ "statusMessage": "(OmO 5.1.48) Checking LSP Diagnostics"
12
12
  }
13
13
  ]
14
14
  }
@@ -21,7 +21,7 @@
21
21
  "type": "command",
22
22
  "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook post-compact",
23
23
  "timeout": 5,
24
- "statusMessage": "(OmO 5.1.47) Resetting LSP Diagnostics Cache"
24
+ "statusMessage": "(OmO 5.1.48) Resetting LSP Diagnostics Cache"
25
25
  }
26
26
  ]
27
27
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/codex-lsp",
3
- "version": "5.1.47",
3
+ "version": "5.1.48",
4
4
  "description": "Codex plugin that exposes Language Server Protocol tools and post-edit diagnostics.",
5
5
  "type": "module",
6
6
  "packageManager": "npm@11.12.1",
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook session-start",
9
9
  "timeout": 10,
10
- "statusMessage": "(OmO 5.1.47) Loading Project Rules"
10
+ "statusMessage": "(OmO 5.1.48) Loading Project Rules"
11
11
  }
12
12
  ]
13
13
  }
@@ -19,7 +19,7 @@
19
19
  "type": "command",
20
20
  "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook user-prompt-submit",
21
21
  "timeout": 10,
22
- "statusMessage": "(OmO 5.1.47) Loading Project Rules"
22
+ "statusMessage": "(OmO 5.1.48) Loading Project Rules"
23
23
  }
24
24
  ]
25
25
  }
@@ -32,7 +32,7 @@
32
32
  "type": "command",
33
33
  "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook post-tool-use",
34
34
  "timeout": 10,
35
- "statusMessage": "(OmO 5.1.47) Matching Project Rules"
35
+ "statusMessage": "(OmO 5.1.48) Matching Project Rules"
36
36
  }
37
37
  ]
38
38
  }
@@ -45,7 +45,7 @@
45
45
  "type": "command",
46
46
  "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook post-compact",
47
47
  "timeout": 10,
48
- "statusMessage": "(OmO 5.1.47) Resetting Project Rule Cache"
48
+ "statusMessage": "(OmO 5.1.48) Resetting Project Rule Cache"
49
49
  }
50
50
  ]
51
51
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/codex-rules",
3
- "version": "5.1.47",
3
+ "version": "5.1.48",
4
4
  "description": "Codex plugin that injects project rule files into model context through lifecycle hooks.",
5
5
  "type": "module",
6
6
  "packageManager": "npm@11.12.1",
@@ -8,7 +8,7 @@
8
8
  "type": "command",
9
9
  "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook post-tool-use",
10
10
  "timeout": 10,
11
- "statusMessage": "(OmO 5.1.47) Checking Thread Title Hygiene"
11
+ "statusMessage": "(OmO 5.1.48) Checking Thread Title Hygiene"
12
12
  }
13
13
  ]
14
14
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sisyphuslabs/codex-teammode",
3
- "version": "5.1.47",
3
+ "version": "5.1.48",
4
4
  "description": "Codex team-mode hook component that keeps background thread titles descriptive after create_thread.",
5
5
  "type": "module",
6
6
  "private": true,
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook session-start",
9
9
  "timeout": 5,
10
- "statusMessage": "(OmO 5.1.47) Recording Session Telemetry"
10
+ "statusMessage": "(OmO 5.1.48) Recording Session Telemetry"
11
11
  }
12
12
  ]
13
13
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/codex-telemetry",
3
- "version": "5.1.47",
3
+ "version": "5.1.48",
4
4
  "description": "Codex plugin component that emits omo-codex anonymous daily-active telemetry on SessionStart.",
5
5
  "type": "module",
6
6
  "packageManager": "npm@11.12.1",
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/dist/cli.js\" hook user-prompt-submit",
9
9
  "timeout": 5,
10
- "statusMessage": "(OmO 5.1.47) Checking Ultrawork Trigger"
10
+ "statusMessage": "(OmO 5.1.48) Checking Ultrawork Trigger"
11
11
  }
12
12
  ]
13
13
  }