oh-my-opencode 5.1.46 → 5.1.47

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 (101) hide show
  1. package/.agents/command/publish.md +3 -4
  2. package/.agents/skills/publish/SKILL.md +3 -4
  3. package/.opencode/command/publish.md +3 -4
  4. package/dist/agents/atlas/agent.d.ts +3 -2
  5. package/dist/agents/sisyphus/index.d.ts +1 -0
  6. package/dist/agents/sisyphus/kimi-k3-1.d.ts +9 -0
  7. package/dist/agents/sisyphus-agent-factory.d.ts +1 -1
  8. package/dist/agents/sisyphus-junior/agent.d.ts +1 -1
  9. package/dist/agents/sisyphus-junior/index.d.ts +1 -0
  10. package/dist/agents/sisyphus-junior/kimi-k3-1.d.ts +6 -0
  11. package/dist/agents/types.d.ts +2 -2
  12. package/dist/cli/index.js +29 -21
  13. package/dist/cli/service-auth/keystore.d.ts +8 -3
  14. package/dist/cli-node/index.js +29 -21
  15. package/dist/index.js +634 -17
  16. package/dist/skills/browser/runtime/omowright/manifest.json +1 -1
  17. package/dist/skills/ulw-research/SKILL.md +1 -1
  18. package/dist/skills/ulw-research/references/deliverable-phase.md +9 -2
  19. package/dist/skills/ulw-research/scripts/copy-returns-polish.test.ts +209 -0
  20. package/dist/skills/ulw-research/scripts/fact-commands-meaning.mjs +7 -22
  21. package/dist/skills/ulw-research/scripts/fact-commands-polish.mjs +127 -0
  22. package/dist/skills/ulw-research/scripts/fact-commands-verify.mjs +19 -0
  23. package/dist/skills/ulw-research/scripts/fact-commands.mjs +4 -2
  24. package/dist/skills/ulw-research/scripts/fact-pairs.mjs +7 -9
  25. package/dist/skills/ulw-research/scripts/fact-pairs.test.ts +7 -23
  26. package/dist/skills/ulw-research/scripts/references-fences.test.ts +25 -0
  27. package/dist/skills/ulw-research/scripts/report-tools.test.ts +87 -0
  28. package/dist/skills/ulw-research/scripts/tests/fixtures/fidelity/polish-edits.json +12 -0
  29. package/dist/tui.js +13 -13
  30. package/package.json +13 -13
  31. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  32. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +1 -2
  33. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  34. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  35. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  36. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  37. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  38. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  39. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  40. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  41. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
  42. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  43. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  44. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  45. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  46. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  47. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  48. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  49. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  50. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  51. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  52. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
  53. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
  54. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
  55. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  56. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  57. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  58. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  59. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  60. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  61. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  62. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  63. package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
  64. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  65. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  66. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  67. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  68. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  69. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  70. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  71. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  72. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  73. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  74. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  75. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  76. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  77. package/packages/omo-codex/plugin/package-lock.json +12 -12
  78. package/packages/omo-codex/plugin/package.json +1 -1
  79. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +1 -1
  80. package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +1 -1
  81. package/packages/omo-codex/plugin/skills/ulw-research/references/deliverable-phase.md +9 -2
  82. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-meaning.mjs +7 -22
  83. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-polish.mjs +127 -0
  84. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands-verify.mjs +19 -0
  85. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-commands.mjs +4 -2
  86. package/packages/omo-codex/plugin/skills/ulw-research/scripts/fact-pairs.mjs +7 -9
  87. package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +1 -1
  88. package/packages/omo-codex/scripts/install-dist/install-local.mjs +4 -5
  89. package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +1 -1
  90. package/packages/shared-skills/skills/ulw-research/SKILL.md +1 -1
  91. package/packages/shared-skills/skills/ulw-research/references/deliverable-phase.md +9 -2
  92. package/packages/shared-skills/skills/ulw-research/scripts/copy-returns-polish.test.ts +209 -0
  93. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-meaning.mjs +7 -22
  94. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-polish.mjs +127 -0
  95. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands-verify.mjs +19 -0
  96. package/packages/shared-skills/skills/ulw-research/scripts/fact-commands.mjs +4 -2
  97. package/packages/shared-skills/skills/ulw-research/scripts/fact-pairs.mjs +7 -9
  98. package/packages/shared-skills/skills/ulw-research/scripts/fact-pairs.test.ts +7 -23
  99. package/packages/shared-skills/skills/ulw-research/scripts/references-fences.test.ts +25 -0
  100. package/packages/shared-skills/skills/ulw-research/scripts/report-tools.test.ts +87 -0
  101. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/fidelity/polish-edits.json +12 -0
@@ -8,7 +8,7 @@
8
8
  "type": "command",
9
9
  "command": "node \"${PLUGIN_ROOT}/components/ulw-loop/dist/cli.js\" hook post-tool-use-spawn",
10
10
  "timeout": 5,
11
- "statusMessage": "(OmO 5.1.46) Recording Spawn Admission",
11
+ "statusMessage": "(OmO 5.1.47) Recording Spawn Admission",
12
12
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\ulw-loop\\dist\\cli.js\" hook post-tool-use-spawn"
13
13
  }
14
14
  ]
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/ulw-loop/dist/cli.js\" hook pre-tool-use",
9
9
  "timeout": 5,
10
- "statusMessage": "(OmO 5.1.46) Enforcing Unlimited Goal Budget",
10
+ "statusMessage": "(OmO 5.1.47) Enforcing Unlimited Goal Budget",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\ulw-loop\\dist\\cli.js\" hook pre-tool-use"
12
12
  }
13
13
  ],
@@ -8,7 +8,7 @@
8
8
  "type": "command",
9
9
  "command": "node \"${PLUGIN_ROOT}/components/ulw-loop/dist/cli.js\" hook pre-tool-use-spawn",
10
10
  "timeout": 5,
11
- "statusMessage": "(OmO 5.1.46) Guarding Ulw-Loop Spawns",
11
+ "statusMessage": "(OmO 5.1.47) Guarding Ulw-Loop Spawns",
12
12
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\ulw-loop\\dist\\cli.js\" hook pre-tool-use-spawn"
13
13
  }
14
14
  ]
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/git-bash/dist/cli.js\" hook pre-tool-use",
9
9
  "timeout": 5,
10
- "statusMessage": "(OmO 5.1.46) Recommending Git Bash MCP",
10
+ "statusMessage": "(OmO 5.1.47) Recommending Git Bash MCP",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\git-bash\\dist\\cli.js\" hook pre-tool-use"
12
12
  }
13
13
  ],
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/scripts/auto-update.mjs\" hook session-start",
9
9
  "timeout": 15,
10
- "statusMessage": "(OmO 5.1.46) Checking Auto Update",
10
+ "statusMessage": "(OmO 5.1.47) Checking Auto Update",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\scripts\\auto-update.mjs\" hook session-start"
12
12
  }
13
13
  ],
@@ -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.46) Checking Bootstrap Provisioning"
11
+ "statusMessage": "(OmO 5.1.47) Checking Bootstrap Provisioning"
12
12
  }
13
13
  ]
14
14
  }
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/rules/dist/cli.js\" hook session-start",
9
9
  "timeout": 10,
10
- "statusMessage": "(OmO 5.1.46) Loading Project Rules",
10
+ "statusMessage": "(OmO 5.1.47) Loading Project Rules",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\rules\\dist\\cli.js\" hook session-start"
12
12
  }
13
13
  ]
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/telemetry/dist/cli.js\" hook session-start",
9
9
  "timeout": 15,
10
- "statusMessage": "(OmO 5.1.46) Recording Session Telemetry",
10
+ "statusMessage": "(OmO 5.1.47) Recording Session Telemetry",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\telemetry\\dist\\cli.js\" hook session-start"
12
12
  }
13
13
  ]
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/ulw-execute-continuation/dist/cli.js\" hook stop",
9
9
  "timeout": 10,
10
- "statusMessage": "(OmO 5.1.46) Checking Ulw-Execute Continuation",
10
+ "statusMessage": "(OmO 5.1.47) Checking Ulw-Execute Continuation",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\ulw-execute-continuation\\dist\\cli.js\" hook stop"
12
12
  }
13
13
  ]
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/ulw-loop/dist/cli.js\" hook stop",
9
9
  "timeout": 10,
10
- "statusMessage": "(OmO 5.1.46) Checking Ulw-Loop Resume",
10
+ "statusMessage": "(OmO 5.1.47) Checking Ulw-Loop Resume",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\ulw-loop\\dist\\cli.js\" hook stop"
12
12
  }
13
13
  ]
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/lazycodex-executor-verify/dist/cli.js\" hook subagent-stop",
9
9
  "timeout": 10,
10
- "statusMessage": "(OmO 5.1.46) Verifying LazyCodex Executor Evidence",
10
+ "statusMessage": "(OmO 5.1.47) Verifying LazyCodex Executor Evidence",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\lazycodex-executor-verify\\dist\\cli.js\" hook subagent-stop"
12
12
  }
13
13
  ],
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/ultrawork/dist/cli.js\" hook user-prompt-submit",
9
9
  "timeout": 5,
10
- "statusMessage": "(OmO 5.1.46) Checking Ultrawork Trigger",
10
+ "statusMessage": "(OmO 5.1.47) Checking Ultrawork Trigger",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\ultrawork\\dist\\cli.js\" hook user-prompt-submit"
12
12
  }
13
13
  ]
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/ulw-loop/dist/cli.js\" hook user-prompt-submit",
9
9
  "timeout": 10,
10
- "statusMessage": "(OmO 5.1.46) Checking Ulw-Loop Steering",
10
+ "statusMessage": "(OmO 5.1.47) Checking Ulw-Loop Steering",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\ulw-loop\\dist\\cli.js\" hook user-prompt-submit"
12
12
  }
13
13
  ]
@@ -7,7 +7,7 @@
7
7
  "type": "command",
8
8
  "command": "node \"${PLUGIN_ROOT}/components/rules/dist/cli.js\" hook user-prompt-submit",
9
9
  "timeout": 10,
10
- "statusMessage": "(OmO 5.1.46) Loading Project Rules",
10
+ "statusMessage": "(OmO 5.1.47) Loading Project Rules",
11
11
  "commandWindows": "powershell -NoProfile -ExecutionPolicy Bypass -File \"${PLUGIN_ROOT}\\components\\bootstrap\\scripts\\node-dispatch.ps1\" \"${PLUGIN_ROOT}\\components\\rules\\dist\\cli.js\" hook user-prompt-submit"
12
12
  }
13
13
  ]
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@sisyphuslabs/omo-codex-plugin",
3
- "version": "5.1.46",
3
+ "version": "5.1.47",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@sisyphuslabs/omo-codex-plugin",
9
- "version": "5.1.46",
9
+ "version": "5.1.47",
10
10
  "workspaces": [
11
11
  "components/comment-checker",
12
12
  "components/git-bash",
@@ -100,7 +100,7 @@
100
100
  },
101
101
  "components/comment-checker": {
102
102
  "name": "@code-yeongyu/codex-comment-checker",
103
- "version": "5.1.46",
103
+ "version": "5.1.47",
104
104
  "license": "MIT",
105
105
  "bin": {
106
106
  "omo-comment-checker": "dist/cli.js"
@@ -122,7 +122,7 @@
122
122
  },
123
123
  "components/git-bash": {
124
124
  "name": "@sisyphuslabs/codex-git-bash-hook",
125
- "version": "5.1.46",
125
+ "version": "5.1.47",
126
126
  "bin": {
127
127
  "omo-git-bash-hook": "dist/cli.js"
128
128
  },
@@ -136,7 +136,7 @@
136
136
  },
137
137
  "components/lazycodex-executor-verify": {
138
138
  "name": "@code-yeongyu/codex-lazycodex-executor-verify",
139
- "version": "5.1.46",
139
+ "version": "5.1.47",
140
140
  "license": "MIT",
141
141
  "bin": {
142
142
  "lazycodex-executor-verify": "dist/cli.js"
@@ -154,7 +154,7 @@
154
154
  },
155
155
  "components/lsp": {
156
156
  "name": "@code-yeongyu/codex-lsp",
157
- "version": "5.1.46",
157
+ "version": "5.1.47",
158
158
  "license": "MIT",
159
159
  "dependencies": {
160
160
  "@code-yeongyu/lsp-daemon": "file:../../../../lsp-daemon",
@@ -176,7 +176,7 @@
176
176
  },
177
177
  "components/rules": {
178
178
  "name": "@code-yeongyu/codex-rules",
179
- "version": "5.1.46",
179
+ "version": "5.1.47",
180
180
  "license": "MIT",
181
181
  "dependencies": {
182
182
  "picomatch": "^4.0.7"
@@ -199,7 +199,7 @@
199
199
  },
200
200
  "components/teammode": {
201
201
  "name": "@sisyphuslabs/codex-teammode",
202
- "version": "5.1.46",
202
+ "version": "5.1.47",
203
203
  "devDependencies": {
204
204
  "@types/node": "^26.6.2",
205
205
  "@vitest/coverage-v8": "5.0.1",
@@ -213,7 +213,7 @@
213
213
  },
214
214
  "components/telemetry": {
215
215
  "name": "@code-yeongyu/codex-telemetry",
216
- "version": "5.1.46",
216
+ "version": "5.1.47",
217
217
  "license": "MIT",
218
218
  "bin": {
219
219
  "omo-telemetry": "dist/cli.js"
@@ -232,7 +232,7 @@
232
232
  },
233
233
  "components/ultrawork": {
234
234
  "name": "@code-yeongyu/codex-ultrawork",
235
- "version": "5.1.46",
235
+ "version": "5.1.47",
236
236
  "license": "MIT",
237
237
  "bin": {
238
238
  "omo-ultrawork": "dist/cli.js"
@@ -251,7 +251,7 @@
251
251
  },
252
252
  "components/ulw-execute-continuation": {
253
253
  "name": "@code-yeongyu/codex-ulw-execute-continuation",
254
- "version": "5.1.46",
254
+ "version": "5.1.47",
255
255
  "license": "MIT",
256
256
  "bin": {
257
257
  "omo-ulw-execute-continuation": "dist/cli.js"
@@ -269,7 +269,7 @@
269
269
  },
270
270
  "components/ulw-loop": {
271
271
  "name": "@code-yeongyu/codex-ulw-loop",
272
- "version": "5.1.46",
272
+ "version": "5.1.47",
273
273
  "license": "MIT",
274
274
  "bin": {
275
275
  "omo-ulw-loop": "dist/cli.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sisyphuslabs/omo-codex-plugin",
3
- "version": "5.1.46",
3
+ "version": "5.1.47",
4
4
  "description": "Aggregate Codex plugin root for OMO components.",
5
5
  "type": "module",
6
6
  "packageManager": "npm@11.12.1",
@@ -175,7 +175,7 @@ prefixes when identity is needed.
175
175
  // and keeps every tool-based delivery gate (static, fidelity, layout, visual QA). The item is matched by
176
176
  // its text right after the Visual QA sentence, not by its list numeral, so adding or removing an earlier
177
177
  // delivery gate does not unhook the overlay.
178
- const ulwResearchProofreadGate = "**Proofread gate: `task(category=\"writing\", ...)`.** Hand the final text to a dedicated `writing` worker whose only job is language: grammar, spelling, punctuation, terminology consistency, and whether the prose reads NATIVELY in the report's own language. It proofreads only and never composes. It returns a defect list; fix every item and re-run the gate on the delta. Deliver only on a clean pass. This gate runs BEFORE the first delivery, not after the user finds the typo.";
178
+ const ulwResearchProofreadGate = "**Proofread gate: `task(category=\"writing\", ...)`.** Hand the final text to a dedicated `writing` worker whose only job is language: grammar, spelling, punctuation, terminology consistency, and whether the prose reads NATIVELY in the report's own language. It proofreads only and never composes, and it never edits the file: it returns `$SESSION_DIR/polish-edits.json`, an array of `{\"before\": ..., \"after\": ..., \"reason\": ...}` where `before` is the exact current text of one or more adjacent sentences. Edits change text only; an edit whose before/after would alter any tag, attribute, comment, or script/style content is rejected, and markup changes go through the full fidelity gates instead. Apply it with `node \"$SKILL_DIR/scripts/report-tools.mjs\" fact polish-apply \"$SESSION_DIR/report.html\" --edits \"$SESSION_DIR/polish-edits.json\" --out \"$SESSION_DIR/report.polished.html\" --session-dir \"$SESSION_DIR\"`: each edit is applied alone, and an edit that changes a fact span, a numeral, a link, or a claim id, or drops a hedge or qualifier the source carries, is `polish_edit_rejected` (a `before` not found exactly once is `polish_edit_unlocated`) and leaves its text unchanged. Send the rejected and unlocated edits back to the same worker with their reasons, plus `$SESSION_DIR/polish-defects.json` and the pre-polish `report.html` the backstop compared against, so the worker sees why each edit was refused; it returns either a rewritten sentence or a new edit list, and the repair round re-runs `fact polish-apply` with `\"$SESSION_DIR/report.polished.html\"` as both input and output, within the repair budget. Then run the backstop `fact polish-diff --before \"$SESSION_DIR/report.html\" --after \"$SESSION_DIR/report.polished.html\" --session-dir \"$SESSION_DIR\"` (exit `1` on `polish_diff_fail`), `fact verify` on `report.polished.html`, and `fact pairs \"$SESSION_DIR/report.polished.html\" --changed-since \"$SESSION_DIR/report.html.fill-map.json\" --session-dir \"$SESSION_DIR\"`, which keeps only the quote and claim pairs whose sentence text changed during polish; run the meaning readers above (cheap, then strong on the flagged pairs) on exactly those pairs and `fact meaning-check` before recording `outcome gate fidelity`. A rewording that keeps every number, hedge, qualifier, and link but swaps who did what to whom therefore still reaches a meaning reader on the delivered text. `report.polished.html` is the report that is delivered. Deliver only on a clean pass. This gate runs BEFORE the first delivery, not after the user finds the typo. Edits must not span entities (such as & or <) or markup; narrow the edit to the surrounding words instead, and a rejected edit comes back with the reason to act on.";
179
179
  const ulwResearchProofreadGateSource = ulwResearchProofreadGate.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
180
180
  const ulwResearchProofreadGatePattern = new RegExp(`(inspect the pixels\\.)\\n\\d+\\. ${ulwResearchProofreadGateSource}`);
181
181
 
@@ -5,5 +5,5 @@
5
5
  "index.js": "d67d3a847fe0bbc43e824feded48b280890d31879538309e671cd5f671133995",
6
6
  "page-bundle.js": "bfd51e8383cf47200d57387bbbed258bce5a3a3d00c38fc108034d2888e23f0c"
7
7
  },
8
- "stagedAtUtc": "2026-10-25T01:02:39.752Z"
8
+ "stagedAtUtc": "2026-10-25T10:57:58.945Z"
9
9
  }
@@ -133,7 +133,12 @@ Nothing reaches the requester until the gates for its QA tier pass, in this orde
133
133
  node "$SKILL_DIR/scripts/report-tools.mjs" layout-probe --json # .source is one async expression
134
134
  ```
135
135
  4. **Visual QA.** Render the artifact to images (PDF pages to PNG, the HTML in the browser skill's engine at desktop and phone widths) and look at the pixels for what a machine cannot see: composition, hierarchy, page breaks, figure legibility.
136
- 5. **Proofread.** A dedicated writing lane proofreads the final text only (grammar, spelling, punctuation, terminology, native register); it never composes or rewrites content.
136
+ 5. **Proofread.** A dedicated writing lane proofreads the final text only (grammar, spelling, punctuation, terminology, native register); it never composes or rewrites content and never edits the file. Edits change text only; an edit whose before/after would alter any tag, attribute, comment, or script/style content is rejected, and markup changes go through the full fidelity gates instead. It returns `polish-edits.json` (`[{before, after, reason}]`), which the orchestrator applies edit by edit; a rejected or unlocated edit goes back to the lane with its reason, within the repair budget. The backstop diff, `fact verify` on the polished file, and the meaning readers on the pairs whose sentences polish changed run before the fidelity gate is recorded again. Edits must not span entities (such as & or <) or markup; narrow the edit to the surrounding words instead, and a rejected edit comes back with the reason to act on.
137
+ ```bash
138
+ node "$SKILL_DIR/scripts/report-tools.mjs" fact polish-apply "$SESSION_DIR/report.html" --edits "$SESSION_DIR/polish-edits.json" --out "$SESSION_DIR/report.polished.html" --session-dir "$SESSION_DIR"
139
+ node "$SKILL_DIR/scripts/report-tools.mjs" fact polish-diff --before "$SESSION_DIR/report.html" --after "$SESSION_DIR/report.polished.html" --session-dir "$SESSION_DIR" # writes polish-defects.json
140
+ node "$SKILL_DIR/scripts/report-tools.mjs" fact pairs "$SESSION_DIR/report.polished.html" --changed-since "$SESSION_DIR/report.html.fill-map.json" --session-dir "$SESSION_DIR"
141
+ ```
137
142
 
138
143
  **Every reader-facing edition is verified.** The main report, the inline edition (section 9), LaTeX per-section sources, and slide sources are filled by `fact fill` from one placeholder source and each is verified by `fact verify` into its own record; PDF and DOCX are renders of a verified source. A delivered row is recorded with `outcome set <format> delivered --path <file> --verified-by <record>`, where the record's `artifact_sha256` equals the sha256 of the delivered source; a PDF or DOCX adds `--rendered-from <source>` and is matched against that source. In a session with a fact ledger, `outcome verify` refuses a delivered row without a matching record (`edition_unverified`, blocker, integrity). Markdown and LaTeX verification arrive with the Markdown stage; until then `fact verify --format md|tex` exits `2`, and such an edition is recorded `blocked_capability` with that reason rather than delivered unverified.
139
144
 
@@ -216,7 +221,9 @@ Invoke every command as `node "$SKILL_DIR/scripts/report-tools.mjs" <command>`.
216
221
  | `fact export` | `--csv`, `--out <file>`, `--session-dir <dir>` | never |
217
222
  | `fact fill <src>` | `--out <file>` or `--view`, `--format html\|md\|tex`, `--inline`, `--replace-failed`, `--session-dir <dir>` | a fill error (nothing is written) |
218
223
  | `fact verify <report>` | `--format html\|md` (from the extension by default), `--sources <dir>`, `--claims <claims.json>`, `--fill-map <file>` (a Markdown edition reads `<report>.fill-map.json` by default), `--brief <brief.md>`, `--out <record>`, `--session-dir <dir>` | any blocker or major defect |
219
- | `fact pairs <report>` | `--format html\|md`, `--ledger <fact-ledger.json>`, `--claims <claims.json>`, `--changed-since <fill-map.json>` (pair only the facts that map filled), `--brief <brief.md>`, `--out <pairs.json>`, `--session-dir <dir>` | the brief has no locked `## Fidelity` block (with `meaning check: off` it writes `{"skipped": true}` and exits `0`) |
224
+ | `fact pairs <report>` | `--format html\|md`, `--ledger <fact-ledger.json>`, `--claims <claims.json>`, `--changed-since <fill-map.json>` (keep only the pairs whose sentence text is not in the pre-polish artifact that map names, so the readers re-check exactly what polish rewrote; the map must carry `artifact_sha256` and the artifact must still hash to it, otherwise the command exits `2`), `--brief <brief.md>`, `--out <pairs.json>`, `--session-dir <dir>` | the brief has no locked `## Fidelity` block (with `meaning check: off` it writes `{"skipped": true}` and exits `0`) |
225
+ | `fact polish-apply <report.html>` | `--edits <polish-edits.json>` and `--out <report.polished.html>` (both required; a missing flag or a Markdown edition exits `2`), `--format html`, `--ledger <fact-ledger.json>`, `--session-dir <dir>` | never: it exits `0` whenever it wrote the output. Each rejected (`polish_edit_rejected`) or unlocated (`polish_edit_unlocated`) edit leaves its text unchanged and is printed verbatim, one line per rejection, in the summary and in `data.rejected_reasons`; `data` also carries `applied`, `rejected`, `unlocated`, `applied_indexes`, `input_sha256`, `edits_sha256`, and `out_sha256` |
226
+ | `fact polish-diff` | `--before <pre-polish.html>` and `--after <polished.html>` (both required; a missing flag exits `2`), `--ledger <fact-ledger.json>`, `--out <record>` (default `$SESSION_DIR/polish-defects.json`), `--session-dir <dir>` | a registry blocker in the diff (majors and minors are reported and exit `0`; a defect code the registry does not know refuses with `unknown defect code <code>`). The record is `{command, status, exitCode, before, artifact, artifact_sha256, defects[], counts}`, with `counts` in contract buckets |
220
227
  | `fact meaning-check` | `--pairs <pairs.json>` and `--verdicts <file>` (required; repeatable, one per reader tier), `--dispute P<n> --reason <text>`, `--run-id <id>`, `--calibration <file>` (default `fidelity-calibration.jsonl`), `--out <record>` (default `fidelity-meaning-defects.json`), `--session-dir <dir>` | `meaning_drift_confirmed` (stderr carries one JSON line with the defect and dropped-verdict counts) |
221
228
  | `fact derive` | `--op <op>` (required) with the operation's operands (section 11), `--unit`, `--to`, `--rationale`, `--disjoint`, `--label`, `--json`, `--session-dir <dir>` | `incompatible_operands` (no row written) |
222
229
  | `claims index <claim-graph.md>` | `--out <claims.json>` (required) | never (a malformed claim table exits `2`) |
@@ -1,6 +1,7 @@
1
1
  // fact-commands-meaning.mjs - `fact pairs` and `fact meaning-check`, the script half of the
2
2
  // separate-session meaning verifier. fact pairs writes the (source, report sentence) pairs a
3
- // reader lane gets and nothing else; it honours the brief's `meaning check` field and writes
3
+ // reader lane gets and nothing else (with --changed-since, only the pairs whose sentence polish
4
+ // rewrote); it honours the brief's `meaning check` field and writes
4
5
  // `{ skipped: true }` when it is off. fact meaning-check ingests the readers' verdict files
5
6
  // (meaning-check.mjs), writes fidelity-meaning-defects.json (a fidelity record bound to the pairs'
6
7
  // artifact_sha256 and report, through writeFidelityRecord), and records the run in
@@ -14,9 +15,8 @@ import { dirname, join, resolve } from "node:path"
14
15
 
15
16
  import { CliError, parseArgs, readJsonFile, sha256Of, writeJsonAtomic, writeTextAtomic } from "./cli-support.mjs"
16
17
  import { countDefects } from "./contracts.mjs"
17
- import { markdownAsHtml, readEdition } from "./fact-commands-verify.mjs"
18
- import { readLedger } from "./fact-ledger.mjs"
19
- import { validateLedger } from "./fact-ledger-schema.mjs"
18
+ import { ledgerFrom, markdownAsHtml, readEdition, sessionOf } from "./fact-commands-verify.mjs"
19
+ import { pairsChangedSince } from "./fact-commands-polish.mjs"
20
20
  import { extractPairs, PAIR_KINDS } from "./fact-pairs.mjs"
21
21
  import { FIDELITY_INCOMPLETE, meaningCheckEnabled, requireLockedBrief } from "./fidelity-brief.mjs"
22
22
  import { MEANING_RECORD, writeFidelityRecord } from "./fidelity-records.mjs"
@@ -31,26 +31,11 @@ export const PROMOTION = Object.freeze({ code: "meaning_drift_confirmed", to: "i
31
31
  /** The command status: fail when any defect blocks delivery under the registry policy. */
32
32
  export const meaningStatus = (defects) => (defects.some(blocksDelivery) ? "fail" : "pass")
33
33
 
34
- const sessionOf = (flags) => {
35
- const dir = flags["session-dir"] ?? process.env.SESSION_DIR
36
- return dir && dir !== true ? resolve(dir) : null
37
- }
38
34
  const inSession = (session, name, flag) => {
39
35
  if (session === null) throw new CliError(`${flag} is required without --session-dir (or SESSION_DIR)`)
40
36
  return join(session, name)
41
37
  }
42
38
 
43
- function readLedgerFrom(flags, session) {
44
- if (flags.ledger === undefined) {
45
- if (session === null) throw new CliError("--ledger is required without --session-dir (or SESSION_DIR)")
46
- return readLedger(session)
47
- }
48
- const ledger = readJsonFile(flags.ledger)
49
- const errors = validateLedger(ledger)
50
- if (errors.length > 0) throw new CliError(`${flags.ledger} is not a valid fact ledger: ${errors[0]}`)
51
- return ledger
52
- }
53
-
54
39
  function briefState(flags, session) {
55
40
  const path = flags.brief ?? inSession(session, "brief.md", "--brief")
56
41
  const lock = requireLockedBrief(path)
@@ -77,10 +62,10 @@ function runPairs(args) {
77
62
  const edition = readEdition(report, flags)
78
63
  const claimsPath = flags.claims ?? (session === null ? null : join(session, "claims.json"))
79
64
  const claims = claimsPath !== null && (flags.claims !== undefined || existsSync(claimsPath)) ? readJsonFile(claimsPath) : null
80
- const changed = flags["changed-since"] === undefined ? undefined : readJsonFile(flags["changed-since"]).entries
81
- const ledger = readLedgerFrom(flags, session)
65
+ const ledger = ledgerFrom(flags, session)
82
66
  const html = edition.format === "md" ? markdownAsHtml(edition.text, edition.entries, ledger).html : edition.text
83
- const pairs = extractPairs(html, ledger, claims, changed)
67
+ const all = extractPairs(html, ledger, claims)
68
+ const pairs = flags["changed-since"] === undefined ? all : pairsChangedSince(all, flags["changed-since"], ledger, claims)
84
69
  const counts = { total: pairs.length, force_strong: pairs.filter((pair) => pair.force_strong).length }
85
70
  for (const kind of PAIR_KINDS) counts[kind] = pairs.filter((pair) => pair.kind === kind).length
86
71
  const record = { schemaVersion: 1, skipped: false, report: resolve(report), format: edition.format, artifact_sha256: sha256Of(edition.text), pairs_sha256: sha256Of(JSON.stringify(pairs)), counts, verdict_schema: VERDICT_SCHEMA, pairs }
@@ -0,0 +1,127 @@
1
+ // fact-commands-polish.mjs - `fact polish-apply` and `fact polish-diff`, the script half of the
2
+ // proofread gate, plus the --changed-since filter of `fact pairs`.
3
+ //
4
+ // The writing lane returns polish-edits.json ([{before, after, reason}]) and never edits the
5
+ // report. polish-apply applies each edit through polish-apply.mjs and writes the result; a
6
+ // rejected (major) or unlocated (minor) edit leaves its region unchanged and is listed with its
7
+ // index and reason so the orchestrator can re-send it; every rejection reason is printed verbatim, one
8
+ // line per rejected edit (index and the first 60 characters of its before), in the summary and in
9
+ // data.rejected_reasons. It exits 0 whenever it wrote the output.
10
+ // polish-diff is the whole-document backstop (polish-diff.mjs) between the pre-polish artifact
11
+ // and any later copy; it exits 1 on a blocker. pairsChangedSince keeps only the pairs whose
12
+ // sentence text (normalized) is not in the pre-polish artifact that a fill map names, so the
13
+ // meaning readers re-check exactly what polish rewrote. No model, no network.
14
+
15
+ import { existsSync, readFileSync } from "node:fs"
16
+ import { join, resolve } from "node:path"
17
+
18
+ import { CliError, parseArgs, readJsonFile, sha256Of, writeJsonAtomic, writeTextAtomic } from "./cli-support.mjs"
19
+ import { countDefects, DEFECT_CODES } from "./contracts.mjs"
20
+ import { editionFormatOf, ledgerFrom, markdownAsHtml, sessionOf } from "./fact-commands-verify.mjs"
21
+ import { extractPairs } from "./fact-pairs.mjs"
22
+ import { applyEdits, POLISH_EDIT_FIELDS, validatePolishEdits } from "./polish-apply.mjs"
23
+ import { diffPolish } from "./polish-diff.mjs"
24
+ import { normalizeText } from "./text-normalize.mjs"
25
+
26
+ export { ledgerFrom, POLISH_EDIT_FIELDS, validatePolishEdits }
27
+
28
+ export const POLISH_RECORD = "polish-defects.json"
29
+
30
+ /**
31
+ * The polish-diff exit policy: only a registry blocker fails the command; majors are reported.
32
+ * Fail-closed like the delivery barrier: a code the registry does not know refuses outright,
33
+ * because a caller-claimed severity may not demote it.
34
+ */
35
+ export const polishDiffFails = (defects) =>
36
+ defects.some((defect) => {
37
+ if (!Object.hasOwn(DEFECT_CODES, defect?.code)) throw new CliError(`unknown defect code ${defect?.code}`)
38
+ return DEFECT_CODES[defect.code].severity === "blocker"
39
+ })
40
+
41
+ function readText(path) {
42
+ try {
43
+ return readFileSync(path, "utf8")
44
+ } catch (error) {
45
+ throw new CliError(`cannot read ${path}: ${error.code ?? error.message}`)
46
+ }
47
+ }
48
+
49
+ const withSeverity = (entry) => ({ ...entry, severity: DEFECT_CODES[entry.code].severity, hint: DEFECT_CODES[entry.code].hint })
50
+
51
+ /** The first 60 characters of an edit's before, on one line. */
52
+ const beforePreview = (before) => (typeof before === "string" ? before : "").replace(/\s+/gu, " ").slice(0, 60)
53
+
54
+ /** One line per rejected edit: its index, the start of its before, and its reason verbatim. */
55
+ const rejectionLine = (entry, edit) => ` rejected edit ${entry.index} ("${beforePreview(edit?.before)}"): ${entry.reason}`
56
+
57
+ function runPolishApply(args) {
58
+ const [report] = args.positionals
59
+ const { flags } = args
60
+ if (!report || flags.edits === undefined || flags.out === undefined) throw new CliError("usage: fact polish-apply <report.html> --edits polish-edits.json --out <report.polished.html> [--ledger F] [--session-dir D]")
61
+ if (editionFormatOf(report, flags.format) === "md") throw new CliError("fact polish-apply takes the filled HTML edition; polish a Markdown edition through its HTML source")
62
+ const html = readText(report)
63
+ const editsText = readText(flags.edits)
64
+ const edits = readJsonFile(flags.edits)
65
+ try {
66
+ validatePolishEdits(edits)
67
+ } catch (error) {
68
+ throw new CliError(`${flags.edits} must be an array of {${Object.keys(POLISH_EDIT_FIELDS).join(", ")}} objects: ${error.message}`)
69
+ }
70
+ const result = applyEdits(html, edits, ledgerFrom(flags))
71
+ writeTextAtomic(flags.out, result.out)
72
+ const defects = [...result.rejected, ...result.unlocated].sort((a, b) => a.index - b.index).map(withSeverity)
73
+ const counts = { applied: result.applied.length, rejected: result.rejected.length, unlocated: result.unlocated.length }
74
+ const data = { out: resolve(flags.out), ...counts, applied_indexes: result.applied, input_sha256: sha256Of(html), edits_sha256: sha256Of(editsText), out_sha256: sha256Of(result.out) }
75
+ const rejected_reasons = result.rejected.map((entry) => rejectionLine(entry, edits[entry.index]))
76
+ const summary = [`fact polish-apply: ${counts.applied} applied, ${counts.rejected} rejected, ${counts.unlocated} unlocated of ${edits.length} edit(s)`, ...rejected_reasons].join("\n")
77
+ return { json: { command: "fact polish-apply", status: "pass", exitCode: 0, defects, data: { ...data, rejected_reasons } }, summary, exitCode: 0 }
78
+ }
79
+
80
+ function runPolishDiff(args) {
81
+ const { flags } = args
82
+ if (flags.before === undefined || flags.after === undefined) throw new CliError("usage: fact polish-diff --before <pre-polish.html> --after <polished.html> [--ledger F] [--out polish-defects.json] [--session-dir D]")
83
+ const after = readText(flags.after)
84
+ const defects = diffPolish(readText(flags.before), after, ledgerFrom(flags)).map((defect) => ({ ...defect, hint: DEFECT_CODES[defect.code]?.hint ?? null }))
85
+ const counts = countDefects(defects)
86
+ const status = polishDiffFails(defects) ? "fail" : "pass"
87
+ const exitCode = status === "fail" ? 1 : 0
88
+ const record = { command: "fact polish-diff", status, exitCode, before: resolve(flags.before), artifact: resolve(flags.after), artifact_sha256: sha256Of(after), defects, counts }
89
+ const session = sessionOf(flags)
90
+ const out = flags.out ?? (session === null ? null : join(session, POLISH_RECORD))
91
+ if (out !== null) writeJsonAtomic(resolve(out), record)
92
+ const summary = `fact polish-diff: ${status.toUpperCase()} - ${counts.blocker} blocker, ${counts.major} major, ${counts.minor} minor`
93
+ return { json: { ...record, out: out === null ? null : resolve(out) }, summary, exitCode }
94
+ }
95
+
96
+ const pairKey = (pair) => [pair.kind, pair.sentence_key, pair.claim_id ?? "", normalizeText(pair.sentence)].join("\u0000")
97
+
98
+ /**
99
+ * The pairs whose sentence text changed since the pre-polish artifact a fill map names: a pair is
100
+ * kept unless the same (kind, sentence key, normalized sentence) occurs in that artifact. The map
101
+ * must carry the artifact's `artifact_sha256` and the bytes must still match it, so the filter is
102
+ * fail-closed on a hand-made or stale map.
103
+ * @param {object[]} pairs extractPairs output on the polished edition
104
+ * @param {string} mapPath the pre-polish fill map (fact fill's `<out>.fill-map.json`)
105
+ */
106
+ export function pairsChangedSince(pairs, mapPath, ledger, claims) {
107
+ const map = readJsonFile(mapPath)
108
+ if (typeof map.artifact !== "string" || !existsSync(map.artifact)) throw new CliError(`${mapPath} names no readable pre-polish artifact`)
109
+ const text = readText(map.artifact)
110
+ if (typeof map.artifact_sha256 !== "string") throw new CliError(`${mapPath} fill map has no artifact_sha256; --changed-since needs the pre-polish hash`)
111
+ if (sha256Of(text) !== map.artifact_sha256) throw new CliError(`${map.artifact} changed after fill; --changed-since needs the pre-polish bytes`)
112
+ const html = map.format === "md" ? markdownAsHtml(text, map.entries, ledger).html : text
113
+ const before = new Map()
114
+ for (const pair of extractPairs(html, ledger, claims)) before.set(pairKey(pair), (before.get(pairKey(pair)) ?? 0) + 1)
115
+ return pairs.filter((pair) => {
116
+ const left = before.get(pairKey(pair)) ?? 0
117
+ if (left === 0) return true
118
+ before.set(pairKey(pair), left - 1)
119
+ return false
120
+ })
121
+ }
122
+
123
+ /** @param {string[]} argv the arguments after `fact polish-apply` or `fact polish-diff` */
124
+ export function runFactPolish(sub, argv) {
125
+ const args = parseArgs(argv)
126
+ return sub === "polish-apply" ? runPolishApply(args) : runPolishDiff(args)
127
+ }
@@ -18,6 +18,7 @@ import { CliError, readJsonFile, sha256Of } from "./cli-support.mjs"
18
18
  import { countDefects } from "./contracts.mjs"
19
19
  import { recompute } from "./fact-derive.mjs"
20
20
  import { readLedger } from "./fact-ledger.mjs"
21
+ import { validateLedger } from "./fact-ledger-schema.mjs"
21
22
  import { FIDELITY_RECORD, writeFidelityRecord } from "./fidelity-records.mjs"
22
23
  import { blocksDelivery, checkFidelity } from "./gates-fidelity.mjs"
23
24
  import { parseMarkdown } from "./md-lite.mjs"
@@ -120,6 +121,24 @@ export function markdownAsHtml(text, entries, ledger) {
120
121
  return { html: `${html}${open ? MD_HTML_BLOCK_RULES[open].group[1] : ""}</body></html>\n`, stale: [...stale] }
121
122
  }
122
123
 
124
+ /** The session dir from --session-dir or SESSION_DIR, or null; a bare flag names no session. */
125
+ export const sessionOf = (flags) => {
126
+ const dir = flags["session-dir"] ?? process.env.SESSION_DIR
127
+ return dir && dir !== true ? resolve(dir) : null
128
+ }
129
+
130
+ /** The ledger from --ledger (validated) or from the session dir; every fact command loads it here. */
131
+ export function ledgerFrom(flags, session = sessionOf(flags)) {
132
+ if (flags.ledger === undefined) {
133
+ if (session === null) throw new CliError("--ledger is required without --session-dir (or SESSION_DIR)")
134
+ return readLedger(session)
135
+ }
136
+ const ledger = readJsonFile(flags.ledger)
137
+ const errors = validateLedger(ledger)
138
+ if (errors.length > 0) throw new CliError(`${flags.ledger} is not a valid fact ledger: ${errors[0]}`)
139
+ return ledger
140
+ }
141
+
123
142
  const MARKDOWN_EXTENSION = /\.(md|markdown)$/iu
124
143
  /** The edition format: the explicit flag, else md for a .md or .markdown path (any case), else html. */
125
144
  export const editionFormatOf = (path, flag) => flag ?? (MARKDOWN_EXTENSION.test(path) ? "md" : "html")
@@ -1,5 +1,6 @@
1
1
  // fact-commands.mjs - the `fact` subcommands of report-tools: add, quote, retract, export, fill,
2
- // derive, verify (fact-commands-verify.mjs), pairs and meaning-check (fact-commands-meaning.mjs). Every value reaches the ledger from a source
2
+ // derive, verify (fact-commands-verify.mjs), pairs and meaning-check (fact-commands-meaning.mjs), polish-apply and
3
+ // polish-diff (fact-commands-polish.mjs). Every value reaches the ledger from a source
3
4
  // snapshot through a deterministic locator or from a declared derivation; nothing here accepts a
4
5
  // typed value. add and quote print the row with 60 chars of source context on each side (JSON
5
6
  // with --json). Exit 0 ok, 1 semantic failure (fact_not_located, fill errors, a refused
@@ -217,6 +218,7 @@ function runDerive(args) {
217
218
  export async function runFact(argv) {
218
219
  const [sub, ...rest] = argv
219
220
  if (sub === "pairs" || sub === "meaning-check") return (await import("./fact-commands-meaning.mjs")).runFactMeaning(sub, rest)
221
+ if (sub === "polish-apply" || sub === "polish-diff") return (await import("./fact-commands-polish.mjs")).runFactPolish(sub, rest)
220
222
  const args = parseArgs(rest, { booleans: BOOLEANS })
221
223
  switch (sub) {
222
224
  case "add":
@@ -234,6 +236,6 @@ export async function runFact(argv) {
234
236
  case "verify":
235
237
  return (await import("./fact-commands-verify.mjs")).runFactVerify(args)
236
238
  default:
237
- throw new CliError("usage: fact <add|quote|retract|export|fill|verify|derive|pairs|meaning-check> ...")
239
+ throw new CliError("usage: fact <add|quote|retract|export|fill|verify|derive|pairs|meaning-check|polish-apply|polish-diff> ...")
238
240
  }
239
241
  }
@@ -96,11 +96,11 @@ function lexiconRouted(group, sentence) {
96
96
  return lexiconChecks(probes).forceStrong.length > 0
97
97
  }
98
98
 
99
- function quotePairs(sentence, rows, filled, slug, reportLang) {
99
+ function quotePairs(sentence, rows, slug, reportLang) {
100
100
  const groups = new Map()
101
101
  for (const fact of sentence.facts) {
102
102
  const row = rows.get(fact.id)
103
- if (!row?.quote || (filled && !filled.has(fact.id))) continue
103
+ if (!row?.quote) continue
104
104
  const group = groups.get(row.quote) ?? { quote: row.quote, row, rows, facts: [], ids: [] }
105
105
  group.facts.push(fact)
106
106
  if (!group.ids.includes(fact.id)) group.ids.push(fact.id)
@@ -136,11 +136,11 @@ function claimPairs(ctx, block, sentence, byClaim, slug, reportLang) {
136
136
  }))
137
137
  }
138
138
 
139
- function translationPair(node, rows, filled, slug, reportLang) {
139
+ function translationPair(node, rows, slug, reportLang) {
140
140
  const id = node.attrs["data-translates"]
141
141
  const row = rows.get(id)
142
142
  const text = proseText(node)
143
- if (!row?.quote || text === "" || (filled && !filled.has(id))) return null
143
+ if (!row?.quote || text === "") return null
144
144
  return {
145
145
  kind: "translation",
146
146
  sentence_key: sentenceKey(slug, [id]),
@@ -157,13 +157,11 @@ function translationPair(node, rows, filled, slug, reportLang) {
157
157
  * @param {string} html the filled report
158
158
  * @param {{ rows: object[] }} ledger
159
159
  * @param {{ claims: object[] } | object[] | null} claims indexClaims(...) output or its claims array
160
- * @param {{ fact_id: string }[]} [fillMap] fact fill's map; when given, only facts it filled are paired
161
160
  * @returns {object[]} pairs with pair_id P1, P2, ... in document order
162
161
  */
163
- export function extractPairs(html, ledger, claims, fillMap) {
162
+ export function extractPairs(html, ledger, claims) {
164
163
  const ctx = buildContext(parseHtml(html), {})
165
164
  const rows = new Map((ledger?.rows ?? []).map((row) => [row.fact_id, row]))
166
- const filled = Array.isArray(fillMap) ? new Set(fillMap.map((entry) => entry.fact_id)) : null
167
165
  const byClaim = claimMap(claims)
168
166
  const htmlLang = ctx.all.find((node) => node.tag === "html")?.attrs.lang
169
167
  const reportLang = htmlLang ? htmlLang.toLowerCase().split("-")[0] : null
@@ -174,12 +172,12 @@ export function extractPairs(html, ledger, claims, fillMap) {
174
172
  if (!findings) continue
175
173
  const slug = slugOf(block.scope)
176
174
  for (const sentence of block.sentences) {
177
- out.push(...quotePairs(sentence, rows, filled, slug, reportLang), ...claimPairs(ctx, block, sentence, byClaim, slug, reportLang))
175
+ out.push(...quotePairs(sentence, rows, slug, reportLang), ...claimPairs(ctx, block, sentence, byClaim, slug, reportLang))
178
176
  }
179
177
  for (const node of ctx.util.descendants(block.owner)) {
180
178
  if (node.attrs?.["data-translates"] === undefined || seenSpans.has(node)) continue
181
179
  seenSpans.add(node)
182
- const pair = translationPair(node, rows, filled, slug, reportLang)
180
+ const pair = translationPair(node, rows, slug, reportLang)
183
181
  if (pair) out.push(pair)
184
182
  }
185
183
  }