@junghanacs/entwurf 0.15.0 → 0.16.0

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 (90) hide show
  1. package/AGENTS.md +1 -0
  2. package/CHANGELOG.md +299 -0
  3. package/DELIVERY.md +4 -3
  4. package/README.md +99 -67
  5. package/VERIFY.md +13 -7
  6. package/docs/acp-backend-rail.md +1 -1
  7. package/docs/external-mcp-host.md +147 -9
  8. package/docs/setup-clean-host.md +221 -16
  9. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +65 -20
  10. package/mcp/entwurf-bridge/dist/pi-extensions/entwurf-capabilities.json +1 -0
  11. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-self-address.js +17 -6
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-sender-identity.js +1 -1
  13. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +172 -5
  14. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-fresh-call.js +152 -17
  15. package/mcp/entwurf-bridge/dist/pi-extensions/lib/omp-fresh-preflight.js +271 -0
  16. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-omp.js +988 -0
  17. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-receive-omp.js +574 -0
  18. package/mcp/entwurf-bridge/dist/scripts/omp-receive-facts.js +84 -0
  19. package/mcp/entwurf-bridge/src/index.ts +65 -19
  20. package/mcp/entwurf-bridge/tsconfig.build.json +19 -3
  21. package/mcp/tsconfig.json +10 -0
  22. package/package.json +14 -4
  23. package/pi/entwurf-capabilities.json +1 -0
  24. package/pi/meta-bridge-omp/entwurf-meta-omp/package.json +7 -0
  25. package/pi/omp-receive/entwurf-receive-omp/package.json +7 -0
  26. package/pi-extensions/entwurf-control.ts +12 -9
  27. package/pi-extensions/lib/entwurf-self-address.ts +18 -7
  28. package/pi-extensions/lib/meta-sender-identity.ts +1 -1
  29. package/pi-extensions/lib/meta-session.ts +219 -5
  30. package/pi-extensions/lib/mux-fresh-call.ts +171 -17
  31. package/pi-extensions/lib/omp-fresh-preflight.ts +275 -0
  32. package/pi-extensions/meta-bridge-omp.ts +1244 -0
  33. package/pi-extensions/meta-bridge-receive-omp.ts +666 -0
  34. package/run.sh +1041 -174
  35. package/scripts/agy-bridge.sh +4 -2
  36. package/scripts/check-copilot-birth-hook.ts +700 -23
  37. package/scripts/check-entwurf-self-address.ts +31 -0
  38. package/scripts/check-fresh-cut-gate.sh +12 -4
  39. package/scripts/check-gate-qualification.ts +8 -2
  40. package/scripts/check-harness-admission-parity.ts +143 -0
  41. package/scripts/check-meta-doctor-oracle.sh +20 -0
  42. package/scripts/check-omp-birth-hook.ts +1049 -0
  43. package/scripts/check-omp-fresh-preflight.ts +181 -0
  44. package/scripts/check-omp-receive-arm.ts +516 -0
  45. package/scripts/check-setup-qualification.sh +126 -0
  46. package/scripts/copilot-bridge-doctor.sh +104 -71
  47. package/scripts/copilot-bridge-install.sh +180 -20
  48. package/scripts/copilot-bridge-oracle.sh +223 -0
  49. package/scripts/copilot-bridge-uninstall.sh +137 -0
  50. package/scripts/dev-bin.sh +50 -9
  51. package/scripts/fake-copilot-vendor.sh +67 -0
  52. package/scripts/inventory-verification-surface.ts +1 -1
  53. package/scripts/meta-bridge-hook-log.sh +9 -1
  54. package/scripts/mutants/copilot-birth.json +96 -5
  55. package/scripts/mutants/mux-fresh-call.json +22 -22
  56. package/scripts/mutants/omp-birth.json +173 -0
  57. package/scripts/mutants/omp-fresh.json +300 -0
  58. package/scripts/mutants/omp-receive.json +135 -0
  59. package/scripts/mutants/pi-package-ownership.json +92 -0
  60. package/scripts/mutants/self-address.json +34 -0
  61. package/scripts/mutants/setup-verdict.json +148 -0
  62. package/scripts/mutants/source-install.json +32 -0
  63. package/scripts/omp-bridge-doctor.sh +315 -0
  64. package/scripts/omp-bridge-install.sh +221 -0
  65. package/scripts/omp-bridge-oracle.sh +154 -0
  66. package/scripts/omp-bridge-uninstall.sh +57 -0
  67. package/scripts/omp-mcp-bridge.sh +320 -0
  68. package/scripts/omp-mcp-config.py +392 -0
  69. package/scripts/omp-receive-doctor.sh +246 -0
  70. package/scripts/omp-receive-facts.ts +106 -0
  71. package/scripts/omp-receive-install.sh +228 -0
  72. package/scripts/omp-receive-uninstall.sh +60 -0
  73. package/scripts/omp-tool-surface.py +349 -0
  74. package/scripts/pi_settings_io.py +22 -0
  75. package/scripts/raw-omp-measure/README.md +420 -0
  76. package/scripts/raw-omp-measure/probe-extension.ts +76 -0
  77. package/scripts/raw-omp-measure/probe-receive-surface.ts +250 -0
  78. package/scripts/raw-omp-measure/source-audit.md +414 -0
  79. package/scripts/register-pi-package.py +525 -7
  80. package/scripts/register-pi-provider.py +162 -12
  81. package/scripts/smoke-agy-install-state.sh +86 -30
  82. package/scripts/smoke-agy-statusline-state.sh +9 -6
  83. package/scripts/smoke-omp-bridge-state.sh +221 -0
  84. package/scripts/smoke-omp-fresh-live.ts +497 -0
  85. package/scripts/smoke-omp-mcp-state.sh +327 -0
  86. package/scripts/smoke-omp-receive-live.ts +340 -0
  87. package/scripts/smoke-omp-receive-state.sh +196 -0
  88. package/scripts/smoke-setup-verdict.sh +291 -0
  89. package/scripts/smoke-user-scope-citizen.sh +388 -1
  90. package/scripts/tsconfig.json +2 -0
@@ -0,0 +1,181 @@
1
+ /**
2
+ * check-omp-fresh-preflight — the agreement gate for the OMP fresh preflight's two reproduced
3
+ * resolvers (#87 Bundle C).
4
+ *
5
+ * ── Why this gate exists at all ──
6
+ *
7
+ * `pi-extensions/lib/omp-fresh-preflight.ts` contains TWO functions that already existed
8
+ * elsewhere in this repo, in another language:
9
+ *
10
+ * `ompAgentDir` reproduces `omp_agent_dir` from `scripts/omp-bridge-oracle.sh`
11
+ * `readOmpConfigFlag` reproduces the `tools.xdev` half of `scripts/omp-tool-surface.py`
12
+ *
13
+ * They are reproductions rather than calls for a stated reason — the preflight runs inside the pi
14
+ * extension AND inside the bundled MCP child, and resolving a sibling shell/python script by
15
+ * relative path from two different emit depths is exactly the arithmetic
16
+ * `check-capability-bundle-reach` exists to catch. But a reproduction is a second implementation,
17
+ * and a second implementation that nothing compares is a divergence with a date on it: the shell
18
+ * oracle changes for the installer's reasons, the preflight keeps answering the old way, and the
19
+ * two halves of the omp lane start addressing different directories while both look correct.
20
+ * `docs/adding-a-harness.md` step 3 names that failure by name.
21
+ *
22
+ * ── The oracle discipline this follows ──
23
+ *
24
+ * Step 1: "never the resolver under test". This gate never asks the TS half to confirm itself.
25
+ * It drives the SHIPPED shell and python leaves — the ones the installer and the doctor actually
26
+ * run — and requires the TS half to produce the same answer for the same input. When they
27
+ * disagree the gate names which input separated them, because "they differ" is not something an
28
+ * operator can act on.
29
+ *
30
+ * The environment matrix deliberately includes every REFUSAL the shell oracle has, not just the
31
+ * happy path: the refusals are the load-bearing half (#87 ledger M6 — a pi-shaped env knob must
32
+ * refuse rather than guess), and a TS half that resolved a directory where the shell refuses
33
+ * would preflight green against a store no live omp reads.
34
+ *
35
+ * Pure + subprocess, no network, no model, no vendor spawn. Fixtures only — this gate never
36
+ * reads the operator's real `~/.omp`.
37
+ */
38
+
39
+ import assert from "node:assert/strict";
40
+ import { execFileSync } from "node:child_process";
41
+ import fs from "node:fs";
42
+ import os from "node:os";
43
+ import path from "node:path";
44
+ import { fileURLToPath } from "node:url";
45
+ import { ompAgentDir, readOmpConfigFlag } from "../pi-extensions/lib/omp-fresh-preflight.ts";
46
+
47
+ const REPO_DIR = fileURLToPath(new URL("..", import.meta.url));
48
+
49
+ let passed = 0;
50
+ function ok(label: string, cond: boolean): void {
51
+ assert.ok(cond, label);
52
+ console.log(` ok ${label}`);
53
+ passed++;
54
+ }
55
+
56
+ /** The SHIPPED shell oracle, asked exactly the way the installer and the doctor ask it.
57
+ * Returns the resolved absolute path, or `null` for the oracle's refusal (rc 1). */
58
+ function shellAgentDir(env: Record<string, string>): string | null {
59
+ try {
60
+ const out = execFileSync(
61
+ "bash",
62
+ ["-c", `set -euo pipefail; . "${path.join(REPO_DIR, "scripts/omp-bridge-oracle.sh")}"; omp_agent_dir`],
63
+ { encoding: "utf8", env: { PATH: process.env.PATH ?? "", ...env }, stdio: ["ignore", "pipe", "pipe"] },
64
+ );
65
+ return out.trim();
66
+ } catch {
67
+ return null;
68
+ }
69
+ }
70
+
71
+ /** The SHIPPED python leaf's effective `tools.xdev` reading, reduced to the tri-state the
72
+ * preflight consumes: explicitly false / effectively true / could not be read. */
73
+ function pythonXdev(agentDir: string): "false" | "true" | "unreadable" {
74
+ const out = execFileSync("python3", [path.join(REPO_DIR, "scripts/omp-tool-surface.py"), agentDir], {
75
+ encoding: "utf8",
76
+ });
77
+ const fields = new Map(
78
+ out
79
+ .split("\n")
80
+ .filter((l) => l.trim() !== "")
81
+ .map((l) => {
82
+ const at = l.indexOf(" ");
83
+ return [l.slice(0, at), l.slice(at + 1)] as [string, string];
84
+ }),
85
+ );
86
+ if (fields.get("verdict") === "unreadable") return "unreadable";
87
+ return fields.get("xdev") === "false" ? "false" : "true";
88
+ }
89
+
90
+ // ===========================================================================
91
+ // 1. Agent-dir agreement, refusals included.
92
+ // ===========================================================================
93
+ {
94
+ const home = fs.mkdtempSync(path.join(os.tmpdir(), "entwurf-omp-oracle-"));
95
+ try {
96
+ const cases: Array<[label: string, env: Record<string, string>]> = [
97
+ ["plain host", { HOME: home }],
98
+ ["explicit ENTWURF_OMP_AGENT_DIR wins over everything", { HOME: home, ENTWURF_OMP_AGENT_DIR: `${home}/x` }],
99
+ [
100
+ "explicit override still wins beside a pi knob",
101
+ { HOME: home, ENTWURF_OMP_AGENT_DIR: `${home}/x`, PI_CODING_AGENT_DIR: "/elsewhere" },
102
+ ],
103
+ ["inherited PI_CODING_AGENT_DIR refuses", { HOME: home, PI_CODING_AGENT_DIR: "/elsewhere" }],
104
+ ["inherited PI_CONFIG_DIR refuses", { HOME: home, PI_CONFIG_DIR: ".pi" }],
105
+ ["PI_PROFILE with no OMP_PROFILE refuses", { HOME: home, PI_PROFILE: "work" }],
106
+ ["PI_PROFILE beside OMP_PROFILE resolves", { HOME: home, PI_PROFILE: "work", OMP_PROFILE: "work" }],
107
+ ["OMP_PROFILE moves the root", { HOME: home, OMP_PROFILE: "work" }],
108
+ ["OMP_PROFILE with a dot/dash is a valid name", { HOME: home, OMP_PROFILE: "a.b-c_1" }],
109
+ ["an uppercase OMP_PROFILE is refused", { HOME: home, OMP_PROFILE: "Work" }],
110
+ ["an OMP_PROFILE with a slash is refused", { HOME: home, OMP_PROFILE: "a/b" }],
111
+ ["an OMP_PROFILE starting with a dot is refused", { HOME: home, OMP_PROFILE: ".hidden" }],
112
+ ];
113
+ for (const [label, env] of cases) {
114
+ const shell = shellAgentDir(env);
115
+ const ts = ompAgentDir(env);
116
+ ok(
117
+ `[QK:OMP-PREFLIGHT-AGENT-DIR-ORACLE] agent dir agrees with the shipped shell oracle — ${label} (shell=${shell ?? "REFUSE"}, ts=${ts ?? "REFUSE"})`,
118
+ shell === ts,
119
+ );
120
+ }
121
+ // An empty override is not an override: both halves must fall through to the default
122
+ // rather than resolving the empty string into the process cwd.
123
+ ok(
124
+ "[QK:OMP-PREFLIGHT-AGENT-DIR-EMPTY-ENV] an empty ENTWURF_OMP_AGENT_DIR falls through in both halves instead of resolving to cwd",
125
+ shellAgentDir({ HOME: home, ENTWURF_OMP_AGENT_DIR: "" }) ===
126
+ ompAgentDir({ HOME: home, ENTWURF_OMP_AGENT_DIR: "" }),
127
+ );
128
+ } finally {
129
+ fs.rmSync(home, { recursive: true, force: true });
130
+ }
131
+ }
132
+
133
+ // ===========================================================================
134
+ // 2. `tools.xdev` agreement over hand-written config shapes.
135
+ //
136
+ // The corpus is what an operator actually types, including the shapes that
137
+ // are NOT valid YAML — the fail-closed direction only matters on those, and a
138
+ // corpus of well-formed files would never exercise it.
139
+ // ===========================================================================
140
+ {
141
+ const CONFIGS: Array<[label: string, yaml: string | null]> = [
142
+ ["absent config file", null],
143
+ ["empty file", ""],
144
+ ["the operator's real shape", "tools:\n xdev: false\n"],
145
+ ["xdev true", "tools:\n xdev: true\n"],
146
+ ["tools section without xdev", "tools:\n approvalMode: yolo\n"],
147
+ ["no tools section at all", "theme:\n dark: titanium\n"],
148
+ ["xdev false with a trailing comment", "tools:\n xdev: false # required by entwurf\n"],
149
+ ["xdev false among siblings", "tools:\n approvalMode: yolo\n xdev: false\n timeout: 30\n"],
150
+ ["a later top-level section after tools", "tools:\n xdev: false\nstatusLine:\n separator: ascii\n"],
151
+ ["xdev quoted", 'tools:\n xdev: "false"\n'],
152
+ ["xdev word form", "tools:\n xdev: off\n"],
153
+ ["xdev at a deeper indent", "tools:\n xdev: false\n"],
154
+ ["a nested xdev that is NOT tools.xdev", "tools:\n nested:\n xdev: false\n"],
155
+ ["a top-level xdev outside tools", "xdev: false\ntools:\n approvalMode: yolo\n"],
156
+ ["tab indentation (not YAML for the vendor)", "tools:\n\txdev: false\n"],
157
+ ["broken flow scalar", "tools: [oops\n"],
158
+ ];
159
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "entwurf-omp-xdev-"));
160
+ try {
161
+ for (const [label, yaml] of CONFIGS) {
162
+ const dir = fs.mkdtempSync(path.join(root, "case-"));
163
+ if (yaml !== null) fs.writeFileSync(path.join(dir, "config.yml"), yaml);
164
+ const python = pythonXdev(dir);
165
+ const ts = readOmpConfigFlag(dir, "tools", "xdev");
166
+ // The preflight's question is narrower than the doctor's: it needs "is this provably
167
+ // false?", so the python leaf's `unreadable` and `true` both map to "not false". That
168
+ // collapse is the CONTRACT, not a looseness — the two states send an operator to the
169
+ // same repair and the preflight refuses on both.
170
+ const pythonSaysFalse = python === "false";
171
+ ok(
172
+ `[QK:OMP-PREFLIGHT-XDEV-ORACLE] tools.xdev agrees with the shipped python leaf — ${label} (python=${python}, ts=${String(ts)})`,
173
+ pythonSaysFalse === (ts === false),
174
+ );
175
+ }
176
+ } finally {
177
+ fs.rmSync(root, { recursive: true, force: true });
178
+ }
179
+ }
180
+
181
+ console.log(`[check-omp-fresh-preflight] ${passed} assertions ok`);