lazycodex-ai 5.0.0-beta.89 → 5.0.0-beta.90

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 (159) hide show
  1. package/README.ja.md +1 -1
  2. package/README.ko.md +1 -1
  3. package/README.md +1 -1
  4. package/README.ru.md +1 -1
  5. package/README.zh-cn.md +1 -1
  6. package/dist/cli/config-manager/refresh-opencode-plugin-sandbox.d.ts +31 -0
  7. package/dist/cli/doctor/checks/tui-plugin-config.d.ts +11 -0
  8. package/dist/cli/index.js +1661 -859
  9. package/dist/cli/install-native/index.d.ts +9 -1
  10. package/dist/cli/install-native/legacy-omo-bin.d.ts +27 -0
  11. package/dist/cli/install-native/offer-native-setup.d.ts +31 -0
  12. package/dist/cli/install-native/omo-bin-test-fixtures.d.ts +35 -0
  13. package/dist/cli/install-native/plan.d.ts +8 -0
  14. package/dist/cli/install-native/repair-legacy-omo-bin.d.ts +26 -0
  15. package/dist/cli/install-native/run-native-install.d.ts +11 -1
  16. package/dist/cli/install-native/verify-omo-command.d.ts +22 -0
  17. package/dist/cli/native-edition-hint.d.ts +2 -2
  18. package/dist/cli-node/index.js +1661 -859
  19. package/package.json +1 -1
  20. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  21. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  22. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  23. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  24. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  25. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  26. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  27. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  28. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  29. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
  30. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  31. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  32. package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-5.6.md +5 -1
  33. package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-6.md +5 -1
  34. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  35. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  36. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  37. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  38. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  39. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  40. package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +7 -3
  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/ultrawork/src/directive-content.ts +1 -1
  44. package/packages/omo-codex/plugin/components/ulw-execute-continuation/directive.md +1 -1
  45. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
  46. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
  47. package/packages/omo-codex/plugin/components/ulw-loop/directive.md +7 -3
  48. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
  49. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  50. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  51. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  52. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  53. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  54. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  55. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  56. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  57. package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
  58. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  59. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  60. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  61. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  62. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  63. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  64. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  65. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  66. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  67. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  68. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  69. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  70. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  71. package/packages/omo-codex/plugin/package-lock.json +12 -12
  72. package/packages/omo-codex/plugin/package.json +1 -1
  73. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +2 -2
  74. package/packages/omo-codex/plugin/skills/browser/SKILL.md +8 -1
  75. package/packages/omo-codex/plugin/skills/browser/references/install.md +34 -5
  76. package/packages/omo-codex/plugin/skills/browser/runtime/omowright/index.js +2 -2
  77. package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +3 -3
  78. package/packages/omo-codex/plugin/skills/browser/scripts/browser-doctor.mjs +14 -3
  79. package/packages/omo-codex/plugin/skills/browser/scripts/browser-install.mjs +13 -1
  80. package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +7 -3
  81. package/packages/omo-codex/plugin/skills/ulw-research/AGENTS.md +63 -0
  82. package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +37 -25
  83. package/packages/omo-codex/plugin/skills/ulw-research/references/deliverable-phase.md +187 -0
  84. package/packages/omo-codex/plugin/skills/ulw-research/references/report-gates.md +66 -0
  85. package/packages/omo-codex/plugin/skills/ulw-research/scripts/cli-support.mjs +102 -0
  86. package/packages/omo-codex/plugin/skills/ulw-research/scripts/contracts.mjs +162 -0
  87. package/packages/omo-codex/plugin/skills/ulw-research/scripts/css-lite.mjs +204 -0
  88. package/packages/omo-codex/plugin/skills/ulw-research/scripts/design-spec.mjs +161 -0
  89. package/packages/omo-codex/plugin/skills/ulw-research/scripts/entities.mjs +39 -0
  90. package/packages/omo-codex/plugin/skills/ulw-research/scripts/entry-guard.mjs +21 -0
  91. package/packages/omo-codex/plugin/skills/ulw-research/scripts/format-extract-css.mjs +122 -0
  92. package/packages/omo-codex/plugin/skills/ulw-research/scripts/format-extract.mjs +229 -0
  93. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-figures.mjs +173 -0
  94. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-layout.mjs +146 -0
  95. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-static.mjs +167 -0
  96. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-structure.mjs +118 -0
  97. package/packages/omo-codex/plugin/skills/ulw-research/scripts/gates-text.mjs +190 -0
  98. package/packages/omo-codex/plugin/skills/ulw-research/scripts/html-lite.mjs +239 -0
  99. package/packages/omo-codex/plugin/skills/ulw-research/scripts/layout-probe.mjs +83 -0
  100. package/packages/omo-codex/plugin/skills/ulw-research/scripts/outcome.mjs +250 -0
  101. package/packages/omo-codex/plugin/skills/ulw-research/scripts/repair-tracker.mjs +171 -0
  102. package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools-commands.mjs +214 -0
  103. package/packages/omo-codex/plugin/skills/ulw-research/scripts/report-tools.mjs +61 -0
  104. package/packages/omo-codex/plugin/skills/visual-qa/references/browser-setup.md +3 -1
  105. package/packages/omo-codex/plugin/test/sync-skills-codex-compatibility.test.mjs +11 -0
  106. package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +2 -2
  107. package/packages/omo-codex/plugin/test/sync-skills.test.mjs +20 -0
  108. package/packages/omo-codex/scripts/install-dist/install-local.mjs +2 -2
  109. package/packages/prompts-core/prompts/ultrawork/codex.md +7 -3
  110. package/packages/shared-skills/skills/browser/SKILL.md +8 -1
  111. package/packages/shared-skills/skills/browser/references/install.md +34 -5
  112. package/packages/shared-skills/skills/browser/runtime/omowright/index.js +2 -2
  113. package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +3 -3
  114. package/packages/shared-skills/skills/browser/scripts/browser-doctor.mjs +14 -3
  115. package/packages/shared-skills/skills/browser/scripts/browser-install.mjs +13 -1
  116. package/packages/shared-skills/skills/ulw-research/AGENTS.md +63 -0
  117. package/packages/shared-skills/skills/ulw-research/SKILL.md +38 -26
  118. package/packages/shared-skills/skills/ulw-research/references/deliverable-phase.md +187 -0
  119. package/packages/shared-skills/skills/ulw-research/references/report-gates.md +66 -0
  120. package/packages/shared-skills/skills/ulw-research/scripts/cli-support.mjs +102 -0
  121. package/packages/shared-skills/skills/ulw-research/scripts/cli-support.test.ts +88 -0
  122. package/packages/shared-skills/skills/ulw-research/scripts/contracts.mjs +162 -0
  123. package/packages/shared-skills/skills/ulw-research/scripts/contracts.test.ts +177 -0
  124. package/packages/shared-skills/skills/ulw-research/scripts/css-lite.mjs +204 -0
  125. package/packages/shared-skills/skills/ulw-research/scripts/css-lite.test.ts +159 -0
  126. package/packages/shared-skills/skills/ulw-research/scripts/design-spec.mjs +161 -0
  127. package/packages/shared-skills/skills/ulw-research/scripts/design-spec.test.ts +162 -0
  128. package/packages/shared-skills/skills/ulw-research/scripts/entities.mjs +39 -0
  129. package/packages/shared-skills/skills/ulw-research/scripts/entry-guard.mjs +21 -0
  130. package/packages/shared-skills/skills/ulw-research/scripts/format-extract-css.mjs +122 -0
  131. package/packages/shared-skills/skills/ulw-research/scripts/format-extract.mjs +229 -0
  132. package/packages/shared-skills/skills/ulw-research/scripts/format-extract.test.ts +243 -0
  133. package/packages/shared-skills/skills/ulw-research/scripts/gates-figures.mjs +173 -0
  134. package/packages/shared-skills/skills/ulw-research/scripts/gates-layout.mjs +146 -0
  135. package/packages/shared-skills/skills/ulw-research/scripts/gates-layout.test.ts +236 -0
  136. package/packages/shared-skills/skills/ulw-research/scripts/gates-static.mjs +167 -0
  137. package/packages/shared-skills/skills/ulw-research/scripts/gates-static.test.ts +265 -0
  138. package/packages/shared-skills/skills/ulw-research/scripts/gates-structure.mjs +118 -0
  139. package/packages/shared-skills/skills/ulw-research/scripts/gates-text.mjs +190 -0
  140. package/packages/shared-skills/skills/ulw-research/scripts/glossary-vocabulary.test.ts +43 -0
  141. package/packages/shared-skills/skills/ulw-research/scripts/html-lite.mjs +239 -0
  142. package/packages/shared-skills/skills/ulw-research/scripts/html-lite.test.ts +191 -0
  143. package/packages/shared-skills/skills/ulw-research/scripts/layout-probe.mjs +83 -0
  144. package/packages/shared-skills/skills/ulw-research/scripts/layout-probe.test.ts +89 -0
  145. package/packages/shared-skills/skills/ulw-research/scripts/outcome.mjs +250 -0
  146. package/packages/shared-skills/skills/ulw-research/scripts/outcome.test.ts +281 -0
  147. package/packages/shared-skills/skills/ulw-research/scripts/repair-tracker.mjs +171 -0
  148. package/packages/shared-skills/skills/ulw-research/scripts/repair-tracker.test.ts +246 -0
  149. package/packages/shared-skills/skills/ulw-research/scripts/report-tools-commands.mjs +214 -0
  150. package/packages/shared-skills/skills/ulw-research/scripts/report-tools.mjs +61 -0
  151. package/packages/shared-skills/skills/ulw-research/scripts/report-tools.test.ts +213 -0
  152. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/assets/figure-1.svg +1 -0
  153. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/bad-report.html +44 -0
  154. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/boxes.json +90 -0
  155. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/design-spec.md +29 -0
  156. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/good-report.html +57 -0
  157. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/reference-report.html +86 -0
  158. package/packages/shared-skills/skills/ulw-research/scripts/tests/fixtures/reference.md +23 -0
  159. package/packages/shared-skills/skills/visual-qa/references/browser-setup.md +3 -1
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env node
2
+ // report-tools.mjs - the ulw-research deliverable CLI. Invoked as
3
+ // node "$SKILL_DIR/scripts/report-tools.mjs" <command> ...
4
+ // Exit codes: 0 pass, 1 semantic failure (blockers, a failed verify, a blocked repair), 2 usage or IO.
5
+
6
+ import { CliError, emit, exitCodeFor } from "./cli-support.mjs"
7
+ import { isCliEntry } from "./entry-guard.mjs"
8
+ import { runCheck, runFormatExtract, runLayoutProbe, runOutcome, runRepairDecide } from "./report-tools-commands.mjs"
9
+
10
+ export const COMMANDS = Object.freeze([
11
+ "check",
12
+ "layout-probe",
13
+ "repair decide",
14
+ "outcome init",
15
+ "outcome set",
16
+ "outcome gate",
17
+ "outcome render",
18
+ "outcome state",
19
+ "outcome verify",
20
+ "outcome finish",
21
+ "outcome briefing",
22
+ "format-extract",
23
+ ])
24
+
25
+ export async function run(argv) {
26
+ const [command, ...rest] = argv
27
+ switch (command) {
28
+ case "check":
29
+ return runCheck(rest)
30
+ case "layout-probe":
31
+ return runLayoutProbe(rest)
32
+ case "repair":
33
+ if (rest[0] !== "decide") throw new CliError("usage: repair decide --state <file> --defects <json>")
34
+ return runRepairDecide(rest.slice(1))
35
+ case "outcome":
36
+ return runOutcome(rest)
37
+ case "format-extract":
38
+ return runFormatExtract(rest)
39
+ case "--help":
40
+ case "help":
41
+ if (rest.includes("--json")) return { json: { commands: COMMANDS }, exitCode: 0 }
42
+ return { text: `report-tools <command>\n${COMMANDS.map((name) => ` ${name}`).join("\n")}`, exitCode: 0 }
43
+ default:
44
+ throw new CliError(`usage: report-tools <${COMMANDS.join(" | ")}> (run --help)`)
45
+ }
46
+ }
47
+
48
+ export async function main(argv = process.argv.slice(2)) {
49
+ try {
50
+ const result = await run(argv)
51
+ emit(result)
52
+ return exitCodeFor(result)
53
+ } catch (error) {
54
+ process.stderr.write(`report-tools: ${error.message}\n`)
55
+ return exitCodeFor(error)
56
+ }
57
+ }
58
+
59
+ if (isCliEntry(import.meta.url)) {
60
+ process.exitCode = await main()
61
+ }
@@ -0,0 +1,213 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import { mkdtempSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
3
+ import { tmpdir } from "node:os"
4
+ import { delimiter, join } from "node:path"
5
+
6
+ import { COMMANDS, run } from "./report-tools.mjs"
7
+
8
+ const SCRIPT = join(import.meta.dir, "report-tools.mjs")
9
+ const FIXTURES = join(import.meta.dir, "tests", "fixtures")
10
+
11
+ function sessionDir() {
12
+ const root = mkdtempSync(join(tmpdir(), "report-tools-cli-"))
13
+ const dir = join(root, "20260101-120000")
14
+ mkdirSync(dir)
15
+ return dir
16
+ }
17
+
18
+ function nodeWithoutBun(args: string[]) {
19
+ const nodePath = Bun.which("node")
20
+ if (nodePath === null) throw new Error("node not found on PATH")
21
+ const path = (process.env.PATH ?? "").split(delimiter).filter((entry) => !entry.includes(".bun")).join(delimiter)
22
+ return Bun.spawnSync({ cmd: [nodePath, SCRIPT, ...args], env: { ...process.env, PATH: path } })
23
+ }
24
+
25
+ function defectsFile(dir: string, name: string, codes: string[]) {
26
+ const file = join(dir, name)
27
+ writeFileSync(file, JSON.stringify(codes.map((code) => ({ code, message: code }))))
28
+ return file
29
+ }
30
+
31
+ describe("help and usage", () => {
32
+ test("#given --help --json #when run #then it lists exactly the command matrix", async () => {
33
+ const result = await run(["--help", "--json"])
34
+ expect(result.json.commands).toEqual([...COMMANDS])
35
+ expect(COMMANDS).toEqual(["check", "layout-probe", "repair decide", "outcome init", "outcome set", "outcome gate", "outcome render", "outcome state", "outcome verify", "outcome finish", "outcome briefing", "format-extract"])
36
+ })
37
+
38
+ test("#given no arguments under node #when spawned #then it exits 2 with a usage line on stderr", () => {
39
+ const proc = nodeWithoutBun([])
40
+ expect(proc.exitCode).toBe(2)
41
+ expect(proc.stderr.toString()).toContain("usage")
42
+ })
43
+
44
+ test("#given a missing report file #when checked #then it exits 2 naming the path", () => {
45
+ const proc = nodeWithoutBun(["check", "missing-report.html", "--design-spec", "missing-spec.md"])
46
+ expect(proc.exitCode).toBe(2)
47
+ expect(proc.stderr.toString()).toContain("missing-spec.md")
48
+ })
49
+ })
50
+
51
+ describe("check", () => {
52
+ test("#given the bad fixture under node #when checked #then it exits 1 with blockers in stdout JSON", () => {
53
+ const proc = nodeWithoutBun(["check", join(FIXTURES, "bad-report.html"), "--design-spec", join(FIXTURES, "design-spec.md")])
54
+ expect(proc.exitCode).toBe(1)
55
+ const parsed = JSON.parse(proc.stdout.toString())
56
+ expect(parsed.command).toBe("check")
57
+ expect(parsed.counts.blocker).toBeGreaterThanOrEqual(1)
58
+ expect(parsed.layout).toBe("not_run")
59
+ })
60
+
61
+ test("#given the good fixture #when checked #then it passes with exit 0", () => {
62
+ const proc = nodeWithoutBun(["check", join(FIXTURES, "good-report.html"), "--design-spec", join(FIXTURES, "design-spec.md")])
63
+ expect(proc.exitCode).toBe(0)
64
+ expect(JSON.parse(proc.stdout.toString()).status).toBe("pass")
65
+ })
66
+
67
+ test("#given a layout probe file #when checked #then layout gates run and the layout field is set", async () => {
68
+ const result = await run(["check", join(FIXTURES, "good-report.html"), "--design-spec", join(FIXTURES, "design-spec.md"), "--layout", join(FIXTURES, "boxes.json")])
69
+ expect(result.json.gatesRun).toContain("layout")
70
+ expect(["pass", "fail"]).toContain(result.json.layout)
71
+ })
72
+ })
73
+
74
+ describe("outcome", () => {
75
+ test("#given a promised pdf and docx #when verified before and after setting them #then verify fails then passes", async () => {
76
+ // given
77
+ const dir = sessionDir()
78
+ const pdf = join(dir, "report.pdf")
79
+ writeFileSync(pdf, "x")
80
+ await run(["outcome", "init", "--session-dir", dir, "--promised", "pdf,docx", "--lane", "no-format"])
81
+ // when
82
+ const before = await run(["outcome", "verify", "--session-dir", dir])
83
+ await run(["outcome", "set", "pdf", "delivered", "--path", pdf, "--session-dir", dir])
84
+ await run(["outcome", "set", "docx", "skipped", "--reason", "no editing audience", "--session-dir", dir])
85
+ await run(["outcome", "gate", "static", "pass", "--session-dir", dir])
86
+ const after = await run(["outcome", "verify", "--session-dir", dir])
87
+ // then
88
+ expect(before.exitCode).toBe(1)
89
+ expect(after.exitCode).toBe(0)
90
+ const manifest = JSON.parse(readFileSync(join(dir, "outcome.json"), "utf8"))
91
+ expect(manifest.deliverables.find((row: { format: string }) => row.format === "pdf").path).toBe(pdf)
92
+ expect(manifest.gates.static).toBe("pass")
93
+ })
94
+
95
+ test("#given a ledger and a fixed now #when the briefing is asked as JSON #then counts come from the ledger and the dir stamp", async () => {
96
+ const dir = sessionDir()
97
+ await run(["outcome", "init", "--session-dir", dir, "--promised", "html"])
98
+ writeFileSync(join(dir, "sources-ledger.md"), ["[S1] https://a.example/x", "[S2] https://b.example/y", "[Source 3] https://a.example/z"].join("\n"))
99
+ const start = new Date(2026, 0, 1, 12, 0, 0)
100
+ const now = new Date(start.getTime() + 61 * 60_000).toISOString()
101
+ const result = await run(["outcome", "briefing", "--json", "--session-dir", dir, "--now", now])
102
+ expect(result.json.sources).toBe(3)
103
+ expect(result.json.domains).toBe(2)
104
+ expect(result.json.minutes).toBe(61)
105
+ })
106
+ })
107
+
108
+ describe("repair decide", () => {
109
+ test("#given three minor code sets with the same measure #when decided in turn #then the third call stops on plateau", async () => {
110
+ const dir = sessionDir()
111
+ const state = join(dir, "repair-state.json")
112
+ const reasons = []
113
+ for (const [index, code] of ["heading_generic", "heading_too_long", "en_dash_in_prose"].entries()) {
114
+ const result = await run(["repair", "decide", "--state", state, "--defects", defectsFile(dir, `d${index}.json`, [code]), "--artifact-bytes", "4096", "--renders", "1", "--max-attempts", "5"])
115
+ reasons.push(result.json.decision.reason)
116
+ }
117
+ expect(reasons.at(-1)).toBe("plateau")
118
+ })
119
+
120
+ test("#given the same defects twice #when decided #then the second call stops on oscillation", async () => {
121
+ const dir = sessionDir()
122
+ const state = join(dir, "repair-state.json")
123
+ const file = defectsFile(dir, "d.json", ["heading_generic"])
124
+ await run(["repair", "decide", "--state", state, "--defects", file, "--artifact-bytes", "4096", "--renders", "1"])
125
+ const second = await run(["repair", "decide", "--state", state, "--defects", file, "--artifact-bytes", "4096", "--renders", "1"])
126
+ expect(second.json.decision.reason).toBe("oscillation")
127
+ expect(second.json.decision.action).toBe("deliver")
128
+ })
129
+
130
+ test("#given no artifact proof #when a stop condition holds #then delivery is blocked (fail closed)", async () => {
131
+ const dir = sessionDir()
132
+ const state = join(dir, "repair-state.json")
133
+ const file = defectsFile(dir, "d.json", ["heading_generic"])
134
+ await run(["repair", "decide", "--state", state, "--defects", file])
135
+ const second = await run(["repair", "decide", "--state", state, "--defects", file])
136
+ expect(second.json.decision.action).toBe("block")
137
+ expect(second.exitCode).toBe(1)
138
+ })
139
+ })
140
+
141
+ describe("layout-probe", () => {
142
+ test("#given --json #when run #then the source is an async IIFE", async () => {
143
+ const result = await run(["layout-probe", "--json", "--cap", "20"])
144
+ expect(result.json.source.startsWith("(async () => {")).toBe(true)
145
+ expect(result.json.source).toContain("const CAP = 20;")
146
+ })
147
+ })
148
+
149
+ describe("manifest bookkeeping", () => {
150
+ test("#given a session manifest #when repair decides deliver-on-exhaustion #then outcome.json carries the repair summary and the residual defects", async () => {
151
+ // given
152
+ const dir = sessionDir()
153
+ await run(["outcome", "init", "--session-dir", dir, "--promised", "html"])
154
+ const state = join(dir, "repair-state.json")
155
+ const file = defectsFile(dir, "d.json", ["heading_generic"])
156
+ const proof = ["--artifact-bytes", "4096", "--renders", "1", "--session-dir", dir]
157
+ // when
158
+ await run(["repair", "decide", "--state", state, "--defects", file, ...proof])
159
+ await run(["repair", "decide", "--state", state, "--defects", file, ...proof])
160
+ // then
161
+ const manifest = JSON.parse(readFileSync(join(dir, "outcome.json"), "utf8"))
162
+ expect(manifest.repair).toEqual({ attempts: 2, action: "deliver", reason: "oscillation" })
163
+ expect(manifest.residualDefects.map((d: { code: string }) => d.code)).toEqual(["heading_generic"])
164
+ const briefing = await run(["outcome", "briefing", "--json", "--session-dir", dir])
165
+ expect(briefing.json.residualDefects).toBe(1)
166
+ expect(briefing.json.repair).toEqual({ attempts: 2, action: "deliver", reason: "oscillation" })
167
+ })
168
+
169
+ test("#given a clean repair decision #when recorded #then residual defects are emptied", async () => {
170
+ const dir = sessionDir()
171
+ await run(["outcome", "init", "--session-dir", dir, "--promised", "html"])
172
+ const state = join(dir, "repair-state.json")
173
+ await run(["repair", "decide", "--state", state, "--defects", defectsFile(dir, "a.json", ["heading_generic"]), "--session-dir", dir])
174
+ await run(["repair", "decide", "--state", state, "--defects", defectsFile(dir, "b.json", []), "--session-dir", dir])
175
+ const manifest = JSON.parse(readFileSync(join(dir, "outcome.json"), "utf8"))
176
+ expect(manifest.residualDefects).toEqual([])
177
+ expect(manifest.repair.reason).toBe("clean")
178
+ })
179
+
180
+ test("#given a ledger and a fixed now #when the run is finished #then finishedAt, elapsedMinutes and source counts are stamped and still validate", async () => {
181
+ const dir = sessionDir()
182
+ const pdf = join(dir, "report.pdf")
183
+ writeFileSync(pdf, "x")
184
+ await run(["outcome", "init", "--session-dir", dir, "--promised", "pdf"])
185
+ await run(["outcome", "set", "pdf", "delivered", "--path", pdf, "--session-dir", dir])
186
+ writeFileSync(join(dir, "sources-ledger.md"), ["[S1] https://a.example/x", "[S2] https://b.example/y"].join("\n"))
187
+ const now = new Date(new Date(2026, 0, 1, 12, 0, 0).getTime() + 30 * 60_000).toISOString()
188
+ const result = await run(["outcome", "finish", "--session-dir", dir, "--now", now])
189
+ const manifest = JSON.parse(readFileSync(join(dir, "outcome.json"), "utf8"))
190
+ expect(result.exitCode).toBe(0)
191
+ expect(manifest.finishedAt).toBe(now)
192
+ expect(manifest.elapsedMinutes).toBe(30)
193
+ expect(manifest.sources).toEqual({ total: 2, domains: 2 })
194
+ expect((await run(["outcome", "verify", "--session-dir", dir])).exitCode).toBe(0)
195
+ })
196
+ })
197
+
198
+ describe("format-extract exit codes", () => {
199
+ test("#given a pdf reference under node #when extracted #then it exits 2 with the unsupported message", () => {
200
+ const dir = sessionDir()
201
+ const pdf = join(dir, "reference.pdf")
202
+ writeFileSync(pdf, "%PDF-1.7")
203
+ const proc = nodeWithoutBun(["format-extract", pdf, "--out", join(dir, "design-spec.md")])
204
+ expect(proc.exitCode).toBe(2)
205
+ expect(proc.stderr.toString()).toContain("unsupported")
206
+ })
207
+
208
+ test("#given an unreachable url #when extracted #then it exits 2", () => {
209
+ const dir = sessionDir()
210
+ const proc = nodeWithoutBun(["format-extract", "http://127.0.0.1:9/report.html", "--from-url", "--out", join(dir, "design-spec.md")])
211
+ expect(proc.exitCode).toBe(2)
212
+ })
213
+ })
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="640" height="360" viewBox="0 0 640 360"><rect width="640" height="360" fill="#f7f5f0"/></svg>
@@ -0,0 +1,44 @@
1
+ <!doctype html>
2
+ <html lang="ko">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Static gate fixture: every static defect once</title>
6
+ <style>
7
+ :root { --bg: #f7f5f0; --fg: #1f1d1a; --accent: #2f6f4f; }
8
+ body { background: var(--bg); color: var(--fg); font-family: "Nanum Myeongjo", serif; }
9
+ .note { color: #123456; }
10
+ </style>
11
+ </head>
12
+ <body>
13
+ <main>
14
+ <h1>분기별 배포 지표 점검 보고서</h1>
15
+ <section>
16
+ <h2>배포 지표가 흔들린 분기</h2>
17
+ <p>이번 분기에는 배포 파이프라인을 두 번 바꿨고, 그 과정을 기록으로 남겼어요 [S1].</p>
18
+ <p>매출이 12.5% 증가했다.</p>
19
+ <p>The rollout finished early — nobody expected that [S1].</p>
20
+ <p>A quick win–loss review followed [S1].</p>
21
+ <p>Launch day went well 🚀 [S1].</p>
22
+ <h3>Summary</h3>
23
+ <h3>This subsection heading keeps going far past any sensible single line of text</h3>
24
+ <img src="data:image/png;base64,iVBORw0KGgo=" alt="unwrapped chart" width="480" height="240">
25
+ <figure>
26
+ <svg width="480" height="240" viewBox="0 0 480 240"><rect x="0" y="0" width="40" height="120" fill="#2f6f4f"/></svg>
27
+ <figcaption>Blank chart [S1]</figcaption>
28
+ </figure>
29
+ <figure>
30
+ <svg width="480" height="240" viewBox="0 0 480 240"><title>Weekly deploys</title><text x="4" y="230">0</text><text x="4" y="20">10</text><text x="200" y="236">Deploys</text></svg>
31
+ <figcaption>Two ticks only [S1]</figcaption>
32
+ </figure>
33
+ <figure>
34
+ <img src="assets/missing-figure.png" alt="lost asset" width="480" height="240">
35
+ <figcaption>Lost asset [S1]</figcaption>
36
+ </figure>
37
+ </section>
38
+ <section>
39
+ <h2>팀 구성은 그대로였다</h2>
40
+ <p>팀 구성은 분기 내내 바뀌지 않았고, 새로 합류한 사람도 떠난 사람도 없었어요. 역할 분담도 그대로여서 배포 담당자는 여전히 한 사람이었고, 리뷰는 돌아가면서 맡았습니다. 회의 방식과 문서 양식도 이전 분기와 같았어요. 그래서 이 절에서는 사람 쪽 변화가 원인이라고 보기 어렵다는 점만 적어 둡니다.</p>
41
+ </section>
42
+ </main>
43
+ </body>
44
+ </html>
@@ -0,0 +1,90 @@
1
+ {
2
+ "probe_version": 1,
3
+ "viewport": { "w": 1280, "h": 900 },
4
+ "document": { "w": 1280, "h": 9240 },
5
+ "count": 7,
6
+ "truncated": false,
7
+ "boxes": [
8
+ {
9
+ "selector": "body > main > section:nth-of-type(1) > h2",
10
+ "tag": "h2",
11
+ "rect": { "x": 353, "y": 420, "w": 574, "h": 38.4 },
12
+ "parentSelector": "body > main > section:nth-of-type(1)",
13
+ "parentRect": { "x": 353, "y": 400, "w": 574, "h": 1400 },
14
+ "scroll": { "sw": 574, "cw": 574, "sh": 38, "ch": 38 },
15
+ "overflow": { "x": "visible", "y": "visible" },
16
+ "position": "static",
17
+ "textLen": 31
18
+ },
19
+ {
20
+ "selector": "body > main > section:nth-of-type(1) > p:nth-of-type(1)",
21
+ "tag": "p",
22
+ "rect": { "x": 353, "y": 474.4, "w": 574, "h": 115.6 },
23
+ "parentSelector": "body > main > section:nth-of-type(1)",
24
+ "parentRect": { "x": 353, "y": 400, "w": 574, "h": 1400 },
25
+ "scroll": { "sw": 574, "cw": 574, "sh": 116, "ch": 116 },
26
+ "overflow": { "x": "visible", "y": "visible" },
27
+ "position": "static",
28
+ "textLen": 212
29
+ },
30
+ {
31
+ "selector": "body > main > section:nth-of-type(1) > figure:nth-of-type(1)",
32
+ "tag": "figure",
33
+ "rect": { "x": 353, "y": 614, "w": 574, "h": 761 },
34
+ "parentSelector": "body > main > section:nth-of-type(1)",
35
+ "parentRect": { "x": 353, "y": 400, "w": 574, "h": 1400 },
36
+ "scroll": { "sw": 574, "cw": 574, "sh": 761, "ch": 761 },
37
+ "overflow": { "x": "visible", "y": "visible" },
38
+ "position": "static",
39
+ "textLen": 48
40
+ },
41
+ {
42
+ "selector": "body > main > section:nth-of-type(1) > figure:nth-of-type(1) > img",
43
+ "tag": "img",
44
+ "rect": { "x": 353, "y": 614, "w": 574, "h": 717 },
45
+ "parentSelector": "body > main > section:nth-of-type(1) > figure:nth-of-type(1)",
46
+ "parentRect": { "x": 353, "y": 614, "w": 574, "h": 761 },
47
+ "scroll": { "sw": 574, "cw": 574, "sh": 717, "ch": 717 },
48
+ "overflow": { "x": "visible", "y": "visible" },
49
+ "position": "static",
50
+ "textLen": 0,
51
+ "objectFit": "fill",
52
+ "natural": { "w": 1280, "h": 1600 }
53
+ },
54
+ {
55
+ "selector": "body > main > section:nth-of-type(1) > figure:nth-of-type(2)",
56
+ "tag": "figure",
57
+ "rect": { "x": 353, "y": 1399, "w": 574, "h": 368.4 },
58
+ "parentSelector": "body > main > section:nth-of-type(1)",
59
+ "parentRect": { "x": 353, "y": 400, "w": 574, "h": 1400 },
60
+ "scroll": { "sw": 574, "cw": 574, "sh": 368, "ch": 368 },
61
+ "overflow": { "x": "visible", "y": "visible" },
62
+ "position": "static",
63
+ "textLen": 52
64
+ },
65
+ {
66
+ "selector": "body > main > section:nth-of-type(1) > figure:nth-of-type(2) > img",
67
+ "tag": "img",
68
+ "rect": { "x": 353, "y": 1399, "w": 574, "h": 324.4 },
69
+ "parentSelector": "body > main > section:nth-of-type(1) > figure:nth-of-type(2)",
70
+ "parentRect": { "x": 353, "y": 1399, "w": 574, "h": 368.4 },
71
+ "scroll": { "sw": 574, "cw": 574, "sh": 324, "ch": 324 },
72
+ "overflow": { "x": "visible", "y": "visible" },
73
+ "position": "static",
74
+ "textLen": 0,
75
+ "objectFit": "fill",
76
+ "natural": { "w": 1100, "h": 620 }
77
+ },
78
+ {
79
+ "selector": "#method > pre",
80
+ "tag": "pre",
81
+ "rect": { "x": 353, "y": 8120, "w": 574, "h": 180 },
82
+ "parentSelector": "#method",
83
+ "parentRect": { "x": 353, "y": 8040, "w": 574, "h": 420 },
84
+ "scroll": { "sw": 812, "cw": 550, "sh": 156, "ch": 156 },
85
+ "overflow": { "x": "auto", "y": "auto" },
86
+ "position": "static",
87
+ "textLen": 640
88
+ }
89
+ ]
90
+ }
@@ -0,0 +1,29 @@
1
+ # Design spec
2
+
3
+ ## Extracted from
4
+ - Source: fixture written by hand for the static gate tests
5
+
6
+ ## Tokens
7
+ ```css
8
+ :root {
9
+ --bg: #f7f5f0;
10
+ --fg: #1f1d1a;
11
+ --accent: #2f6f4f;
12
+ --rule: #d9d4c7;
13
+ --mute: #6b665c;
14
+ }
15
+ @media (prefers-color-scheme: dark) {
16
+ :root {
17
+ --bg: #121110;
18
+ --fg: #e8e4dc;
19
+ --accent: #7fbf9f;
20
+ }
21
+ }
22
+ ```
23
+
24
+ ## Typography
25
+ - Body font: Pretendard, "Apple SD Gothic Neo", sans-serif
26
+
27
+ ## Citations
28
+ - Style: bracket-S
29
+ - Lineage: inline
@@ -0,0 +1,57 @@
1
+ <!doctype html>
2
+ <html lang="ko">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Static gate fixture: passing report</title>
6
+ <style>
7
+ :root { --bg: #f7f5f0; --fg: #1f1d1a; --accent: #2f6f4f; --rule: #d9d4c7; --mute: #6b665c; --ff: Pretendard, "Apple SD Gothic Neo", sans-serif; }
8
+ @media (prefers-color-scheme: dark) { :root { --bg: #121110; --fg: #e8e4dc; --accent: #7fbf9f; } }
9
+ body { background: var(--bg); color: var(--fg); font-family: var(--ff); word-break: keep-all; overflow-wrap: anywhere; }
10
+ th, td { border-bottom: 1px solid var(--rule); }
11
+ figcaption { color: var(--mute); }
12
+ .card { background: #fff; }
13
+ </style>
14
+ </head>
15
+ <body>
16
+ <header><h1>소규모 팀의 배포 속도 비교</h1></header>
17
+ <main>
18
+ <section id="speed">
19
+ <h2>배포 주기가 짧아진 이유 <svg class="mark" width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><path d="M2 8h12" stroke="#1f1d1a"/></svg></h2>
20
+ <p>매출이 12.5% 증가했다 [S1]. 같은 기간 팀 규모는 그대로였어요.</p>
21
+ <p>2019–2024 구간을 기준으로 비교했습니다.</p>
22
+ <p>배포는 한 달에 48건이었어요 MEASURED.</p>
23
+ <p data-lineage="DERIVED">평균 리드 타임은 3.2일로 계산했어요.</p>
24
+ <p>리뷰 대기 시간은 40% 줄었습니다 <a href="#s3">[3]</a>.</p>
25
+ <figure>
26
+ <img src="assets/figure-1.svg" alt="월별 배포 건수" width="640" height="360">
27
+ <figcaption>그림 1. 월별 배포 건수 [S1]</figcaption>
28
+ </figure>
29
+ <figure class="chart">
30
+ <svg width="640" height="320" viewBox="0 0 640 320" role="img"><title>Monthly deploys</title><rect x="40" y="100" width="60" height="200" fill="#2f6f4f"/><text x="4" y="300" font-size="12">0</text><text x="4" y="200" font-size="12">25</text><text x="4" y="100" font-size="12">50</text><text x="300" y="316" font-size="12">Deploys per month</text></svg>
31
+ <figcaption>월별 배포 추이 [S2]</figcaption>
32
+ </figure>
33
+ <table>
34
+ <thead><tr><th>항목</th><th>상태</th></tr></thead>
35
+ <tbody><tr><td>자동 배포</td><td>✓</td></tr><tr><td>수동 승인</td><td>—</td></tr></tbody>
36
+ </table>
37
+ </section>
38
+ <section id="review">
39
+ <h2>Review load moved to the author</h2>
40
+ <p>Authors now merge their own changes after a green build, which the team describes as the main reason reviews stopped queueing behind one maintainer [S2].</p>
41
+ </section>
42
+ <section id="method">
43
+ <h2>How this report was made</h2>
44
+ <p>We read the public changelog and counted 147 entries by hand, then checked the totals against the team handbook.</p>
45
+ </section>
46
+ <section id="sources">
47
+ <h2>Sources</h2>
48
+ <ol>
49
+ <li id="s1">Quarterly engineering review — internal summary, 2024</li>
50
+ <li id="s2">Team handbook, deploy chapter</li>
51
+ <li id="s3">Review queue dashboard export</li>
52
+ </ol>
53
+ </section>
54
+ </main>
55
+ <footer><p>Fixture for the static gate tests.</p></footer>
56
+ </body>
57
+ </html>
@@ -0,0 +1,86 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Reference report</title>
7
+ <style>
8
+ :root {
9
+ --bg: #faf6ef;
10
+ --bg-raise: #ffffff;
11
+ --fg: #24211b;
12
+ --fg-mute: #6d6759;
13
+ --rule: #e4dccb;
14
+ --accent: #c2410c;
15
+ --accent-soft: #fbe7d4;
16
+ --good: #15803d;
17
+ --measure: 720px;
18
+ --ff-body: "Inter Display", "Noto Sans KR", sans-serif;
19
+ --ff-head: "Fraunces", Georgia, serif;
20
+ --ff-mono: "Iosevka Term", ui-monospace, monospace;
21
+ }
22
+ @media (prefers-color-scheme: dark) {
23
+ :root {
24
+ --bg: #0c0b0a;
25
+ --bg-raise: #171512;
26
+ --fg: #fbf0df;
27
+ --fg-mute: #b3a992;
28
+ --rule: #2b2721;
29
+ --accent: #f8b878;
30
+ --accent-soft: #2a2013;
31
+ --good: #7cd992;
32
+ }
33
+ }
34
+ </style>
35
+ <style>
36
+ body {
37
+ margin: 0;
38
+ font-family: var(--ff-body);
39
+ font-size: 17px;
40
+ line-height: 1.7;
41
+ color: var(--fg);
42
+ background: var(--bg);
43
+ }
44
+ h1, h2, h3 { font-family: var(--ff-head); color: #24211b; }
45
+ a { color: #c2410c; }
46
+ code, pre { font-family: var(--ff-mono); }
47
+ .wrap { max-width: var(--measure); margin: 0 auto; padding: 0 16px; }
48
+ .fig { border: 1px solid #e4dccb; background: #ffffff; padding: 16px; }
49
+ .fig img { max-width: 100%; height: auto; object-fit: contain; }
50
+ .fig-cap { color: #6d6759; font-size: 14px; }
51
+ @media (min-width: 560px) { .wrap { padding: 0 24px; } }
52
+ @media (min-width: 640px) { .grid { display: grid; grid-template-columns: 1fr 1fr; } }
53
+ </style>
54
+ </head>
55
+ <body>
56
+ <main class="wrap">
57
+ <h1>How a small team ships every week</h1>
58
+ <p>A reference report used as a format source. Weekly releases held for 19 months [S1].</p>
59
+ <section>
60
+ <h2>01 The release cadence</h2>
61
+ <p>The team shipped 147 announcements in 16 months [S1]. The monthly mean is 8.75 [S2].</p>
62
+ <figure class="fig chart">
63
+ <svg viewBox="0 0 200 100" role="img"><title>Announcements per month</title><text x="4" y="12">count</text></svg>
64
+ <figcaption class="fig-cap">Announcements per month, counted from the changelog [S1].</figcaption>
65
+ </figure>
66
+ </section>
67
+ <section>
68
+ <h2>02 How work is reviewed</h2>
69
+ <p>Work lands on the main branch without a review queue [S3].</p>
70
+ <div class="fig">
71
+ <img src="data:image/gif;base64,R0lGODlhAQABAAAAACw=" alt="Screenshot of the note">
72
+ <p class="fig-cap">The published note, captured on the access date [S3].</p>
73
+ </div>
74
+ <pre><code>git push origin main</code></pre>
75
+ </section>
76
+ <section>
77
+ <h2>03 Sources</h2>
78
+ <ol>
79
+ <li>[S1] Changelog, accessed 2026-09-16.</li>
80
+ <li>[S2] Derived monthly mean.</li>
81
+ <li>[S3] Engineering note, accessed 2026-09-16.</li>
82
+ </ol>
83
+ </section>
84
+ </main>
85
+ </body>
86
+ </html>
@@ -0,0 +1,23 @@
1
+ # 작은 팀은 어떻게 매주 배포하는가
2
+
3
+ 기준일은 2026년 9월 16일입니다 [S1].
4
+
5
+ ## 01 배포 주기
6
+
7
+ 제품 공지는 147건이었어요 [S1]. 월평균은 8.75건입니다 [S2].
8
+
9
+ ```md
10
+ ## 99 not a heading inside a fence
11
+ ```
12
+
13
+ ### Details below the section level
14
+
15
+ ## 02 리뷰 없이 일하는 방식
16
+
17
+ 작업은 리뷰 대기열 없이 main 에 들어갑니다 [S3].
18
+
19
+ ## 03 출처
20
+
21
+ - [S1] Changelog, accessed 2026-09-16.
22
+ - [S2] Derived monthly mean.
23
+ - [S3] Engineering note, accessed 2026-09-16.
@@ -58,7 +58,9 @@ try {
58
58
 
59
59
  NEVER launch anything against, or clear cookies/cache/site data from, the user's live profile;
60
60
  the attached engine is the only sanctioned way to a signed-in page. If no extension is connected,
61
- run the `browser` skill's `scripts/browser-install.mjs`, relay its one human step, and wait — do
61
+ run the `browser` skill's `scripts/browser-install.mjs` for the browser the user actually uses
62
+ (from memory, or its detection; on `needsChoice` ask them and pass `--browser=<id>`), relay its
63
+ one human step, and wait — do
62
64
  not fall back to the owned engine for an authenticated criterion.
63
65
 
64
66
  ## Capture a screenshot at a fixed viewport