@junghanacs/entwurf 0.45.0 → 0.51.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 (116) hide show
  1. package/AGENTS.md +5 -3
  2. package/BASELINE.md +110 -1
  3. package/CHANGELOG.md +249 -0
  4. package/CONTRIBUTING.md +1 -1
  5. package/ENTWURF-CLI.json +1229 -0
  6. package/ENTWURF.json +63 -15
  7. package/FAQ.md +4 -4
  8. package/README.md +132 -188
  9. package/VERIFY.md +18 -11
  10. package/demo/README.md +1 -1
  11. package/docs/acp-backend-rail.md +1 -1
  12. package/docs/external-mcp-host.md +4 -4
  13. package/docs/setup-clean-host.md +94 -22
  14. package/mcp/entwurf-bridge/dist/ENTWURF.json +63 -15
  15. package/mcp/entwurf-bridge/dist/mcp/entwurf-bridge/src/index.js +19 -8
  16. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-contract.js +4 -4
  17. package/mcp/entwurf-bridge/dist/pi-extensions/lib/entwurf-v2-visible-resume.js +483 -18
  18. package/mcp/entwurf-bridge/dist/pi-extensions/lib/fresh-call-dispatch.js +47 -10
  19. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-fresh-call.js +613 -33
  20. package/mcp/entwurf-bridge/dist/pi-extensions/lib/herdr-resume-call.js +497 -0
  21. package/mcp/entwurf-bridge/dist/pi-extensions/lib/meta-session.js +3 -1
  22. package/mcp/entwurf-bridge/dist/pi-extensions/lib/mux-resume-call.js +50 -5
  23. package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-fresh-preflight.js +7 -1
  24. package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-location.js +347 -0
  25. package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-owner-fence.js +1132 -0
  26. package/mcp/entwurf-bridge/dist/pi-extensions/lib/pi-durable-runtime.js +1 -0
  27. package/mcp/entwurf-bridge/dist/pi-extensions/lib/resume-launch-identity.js +18 -11
  28. package/mcp/entwurf-bridge/dist/pi-extensions/meta-bridge-pi-durable.js +30 -15
  29. package/mcp/entwurf-bridge/dist/scripts/herdr-rails.mjs +40 -7
  30. package/mcp/entwurf-bridge/dist/scripts/meta-bridge-fresh-cut.js +1 -27
  31. package/mcp/entwurf-bridge/dist/scripts/meta-bridge-prune.js +1 -1
  32. package/mcp/entwurf-bridge/dist/scripts/meta-facts.js +1 -1
  33. package/mcp/entwurf-bridge/dist/scripts/peer-facts.js +1 -1
  34. package/mcp/entwurf-bridge/src/index.ts +28 -8
  35. package/mcp/entwurf-bridge/tsconfig.build.json +7 -1
  36. package/native/win32-x64/manifest.json +1 -1
  37. package/package.json +16 -20
  38. package/pi/pi-durable/bootstrap.mjs +179 -6
  39. package/pi/pi-durable/carrier/runtime.js +327 -57
  40. package/pi/pi-durable/carrier/session-name.js +11 -0
  41. package/pi/pi-durable/carrier/sessions.js +96 -20
  42. package/pi/pi-durable/carrier/subagent.js +5 -2
  43. package/pi/pi-durable/carrier/tui.js +60 -22
  44. package/pi/pi-durable/overlay/upstream-pin.json +26 -19
  45. package/pi-extensions/lib/entwurf-v2-contract-schema.ts +73 -69
  46. package/pi-extensions/lib/entwurf-v2-contract.ts +4 -4
  47. package/pi-extensions/lib/entwurf-v2-visible-resume.ts +710 -38
  48. package/pi-extensions/lib/fresh-call-dispatch.ts +64 -12
  49. package/pi-extensions/lib/herdr-fresh-call.ts +919 -40
  50. package/pi-extensions/lib/herdr-resume-call.ts +810 -0
  51. package/pi-extensions/lib/meta-session.ts +13 -8
  52. package/pi-extensions/lib/mux-resume-call.ts +77 -4
  53. package/pi-extensions/lib/pi-durable-fresh-preflight.ts +9 -2
  54. package/pi-extensions/lib/pi-durable-location.ts +446 -0
  55. package/pi-extensions/lib/pi-durable-owner-fence.ts +1461 -0
  56. package/pi-extensions/lib/pi-durable-runtime.ts +4 -0
  57. package/pi-extensions/lib/resume-launch-identity.ts +31 -12
  58. package/pi-extensions/meta-bridge-pi-durable.ts +30 -16
  59. package/run.sh +552 -699
  60. package/scripts/check-claude-launch.ts +341 -0
  61. package/scripts/check-entwurf-v2-contract.ts +4 -4
  62. package/scripts/check-entwurf-v2-surface.ts +2 -2
  63. package/scripts/check-fresh-call-dispatch.ts +70 -9
  64. package/scripts/check-fresh-cut-gate.sh +23 -9
  65. package/scripts/check-gate-qualification.ts +19 -14
  66. package/scripts/check-herdr-activation.ts +301 -23
  67. package/scripts/check-herdr-fresh-call.ts +161 -25
  68. package/scripts/check-herdr-plugin-build.ts +173 -1
  69. package/scripts/check-herdr-plugin.ts +28 -2
  70. package/scripts/check-herdr-runtime-bootstrap.ts +136 -0
  71. package/scripts/check-install-surface.ts +1236 -6
  72. package/scripts/check-meta-doctor-oracle.sh +1 -1
  73. package/scripts/check-meta-facts.ts +20 -1
  74. package/scripts/check-mux-resume-call.ts +28 -0
  75. package/scripts/check-peer-facts.ts +39 -2
  76. package/scripts/cli-help.mjs +222 -0
  77. package/scripts/dev-bin.sh +39 -2
  78. package/scripts/herdr-activation.mjs +28 -6
  79. package/scripts/herdr-operator-link.mjs +389 -0
  80. package/scripts/herdr-plugin-activate.mjs +5 -0
  81. package/scripts/herdr-plugin-deactivate.mjs +18 -5
  82. package/scripts/herdr-rails.mjs +43 -7
  83. package/scripts/herdr-runtime.mjs +27 -0
  84. package/scripts/meta-bridge-fresh-cut.ts +1 -27
  85. package/scripts/meta-bridge-prune.ts +1 -1
  86. package/scripts/meta-facts.ts +1 -1
  87. package/scripts/mutants/agent-guidance.json +4 -4
  88. package/scripts/mutants/claude-launch.json +142 -0
  89. package/scripts/mutants/cli-help-source.json +420 -0
  90. package/scripts/mutants/control-send-receipt.json +1 -1
  91. package/scripts/mutants/dev-gate-dispatch.json +109 -0
  92. package/scripts/mutants/fresh-call-dispatch.json +38 -2
  93. package/scripts/mutants/fresh-cut.json +15 -0
  94. package/scripts/mutants/herdr-activation.json +128 -0
  95. package/scripts/mutants/herdr-fresh-call.json +278 -5
  96. package/scripts/mutants/herdr-operator-link.json +318 -0
  97. package/scripts/mutants/herdr-plugin-build.json +55 -0
  98. package/scripts/mutants/herdr-plugin.json +17 -0
  99. package/scripts/mutants/herdr-resume-call.json +288 -0
  100. package/scripts/mutants/herdr-runtime-bootstrap.json +48 -0
  101. package/scripts/mutants/meta-facts.json +15 -0
  102. package/scripts/mutants/mux-resume-call.json +63 -2
  103. package/scripts/mutants/pack-install.json +4 -4
  104. package/scripts/mutants/peer-facts.json +15 -0
  105. package/scripts/mutants/pi-durable.json +1167 -3
  106. package/scripts/mutants/pi-floor.json +22 -5
  107. package/scripts/mutants/source-install.json +35 -0
  108. package/scripts/mutants/v2-surface.json +2 -2
  109. package/scripts/mutants/v2-visible-resume.json +255 -6
  110. package/scripts/peer-facts.ts +1 -1
  111. package/scripts/smoke-agy-install-state.sh +66 -0
  112. package/scripts/smoke-meta-prune.sh +23 -0
  113. package/scripts/smoke-omp-fresh-live.ts +12 -3
  114. package/scripts/smoke-omp-receive-live.ts +7 -1
  115. package/scripts/smoke-pi-durable-fresh-live.ts +12 -2
  116. package/team.json.example +39 -0
@@ -7,10 +7,10 @@
7
7
  "title": "one row of the pnpm workspace closure is left on the previous floor while every other surface moved — the shape a version bump actually fails in. Nothing else reads that file, so the stranded row surfaces only at install time, in a different command, far from the change that stranded it",
8
8
  "subject": "pnpm-workspace.yaml",
9
9
  "find": [
10
- " - '@earendil-works/pi-telemetry@0.85.0 || 0.85.1 || 0.86.0 || 0.87.0 || 0.87.1 || 0.99.1 || 0.99.2 || 1.0.0 || 1.0.2 || 1.0.4 || 1.1.0 || 1.2.0 || 1.3.0 || 1.4.0'"
10
+ " - '@earendil-works/pi-telemetry@0.85.0 || 0.85.1 || 0.86.0 || 0.87.0 || 0.87.1 || 0.99.1 || 0.99.2 || 1.0.0 || 1.0.2 || 1.0.4 || 1.1.0 || 1.2.0 || 1.3.0 || 1.4.0 || 1.5.0'"
11
11
  ],
12
12
  "replace": [
13
- " - '@earendil-works/pi-telemetry@0.85.0 || 0.85.1 || 0.86.0 || 0.87.0 || 0.87.1 || 0.99.1 || 0.99.2 || 1.0.0 || 1.0.2 || 1.0.4 || 1.1.0 || 1.2.0 || 1.3.0'"
13
+ " - '@earendil-works/pi-telemetry@0.85.0 || 0.85.1 || 0.86.0 || 0.87.0 || 0.87.1 || 0.99.1 || 0.99.2 || 1.0.0 || 1.0.2 || 1.0.4 || 1.1.0 || 1.2.0 || 1.3.0 || 1.4.0'"
14
14
  ],
15
15
  "gate": ["bash", "run.sh", "check-dep-versions"],
16
16
  "timeoutSeconds": 300,
@@ -21,8 +21,8 @@
21
21
  "claim": "PI-SDK-PRODUCTION-SET",
22
22
  "title": "the durable library's production pin falls one release behind the SDK set the pin declares, so the carrier would bind a pi-durable nobody verified it against",
23
23
  "subject": "package.json",
24
- "find": ["\"@earendil-works/pi-durable\": \"1.4.0\","],
25
- "replace": ["\"@earendil-works/pi-durable\": \"1.3.0\","],
24
+ "find": ["\"@earendil-works/pi-durable\": \"1.5.0\","],
25
+ "replace": ["\"@earendil-works/pi-durable\": \"1.4.0\","],
26
26
  "gate": ["bash", "run.sh", "check-dep-versions"],
27
27
  "timeoutSeconds": 300,
28
28
  "signature": "[QK:PI-SDK-PRODUCTION-SET]",
@@ -33,11 +33,28 @@
33
33
  "title": "an @earendil-works package returns as a dev dependency beside its production pin, one name in two dependency classes",
34
34
  "subject": "package.json",
35
35
  "find": ["\"@biomejs/biome\": \"^2.4.13\","],
36
- "replace": ["\"@biomejs/biome\": \"^2.4.13\",\n\t\t\"@earendil-works/pi-ai\": \"1.4.0\","],
36
+ "replace": ["\"@biomejs/biome\": \"^2.4.13\",\n\t\t\"@earendil-works/pi-ai\": \"1.5.0\","],
37
37
  "gate": ["bash", "run.sh", "check-dep-versions"],
38
38
  "timeoutSeconds": 300,
39
39
  "signature": "[QK:PI-SDK-NO-DUAL-DECLARATION]",
40
40
  "signatureSource": "run.sh"
41
+ },
42
+ {
43
+ "claim": "PI-FLOOR-PUBLISHED-SCOPE",
44
+ "title": "a published-scope region in an operator doc names a package the frozen published manifest does not record, so the 1.4 instructions inside it would be held to a pairing no artifact ships — the doc can no longer prove which published package its install steps belong to",
45
+ "subject": "docs/setup-clean-host.md",
46
+ "find": [
47
+ "<!-- entwurf:published @junghanacs/entwurf@0.50.0 pi 1.4.0 -->",
48
+ "| Component | Requirement | Needed for |"
49
+ ],
50
+ "replace": [
51
+ "<!-- entwurf:published @junghanacs/entwurf@0.49.0 pi 1.4.0 -->",
52
+ "| Component | Requirement | Needed for |"
53
+ ],
54
+ "gate": ["bash", "run.sh", "check-dep-versions"],
55
+ "timeoutSeconds": 300,
56
+ "signature": "[QK:PI-FLOOR-PUBLISHED-SCOPE]",
57
+ "signatureSource": "run.sh"
41
58
  }
42
59
  ]
43
60
  }
@@ -93,6 +93,41 @@
93
93
  "timeoutSeconds": 300,
94
94
  "signature": "[QK:SOURCE-MANIFEST-UNREADABLE-REFUSED]",
95
95
  "signatureSource": "scripts/smoke-agy-install-state.sh"
96
+ },
97
+ {
98
+ "claim": "INSTALLED-DEVBIN-MUTATION-REFUSED",
99
+ "title": "the installed-package test never matches, so an installed root's dev-bin expose/remove again takes over a developer's links and state (#153, measured before the guard)",
100
+ "subject": "scripts/dev-bin.sh",
101
+ "find": [" */node_modules/*)"],
102
+ "replace": [" */node_modules/*/never-an-install/*)"],
103
+ "gate": ["bash", "run.sh", "smoke-agy-install-state"],
104
+ "timeoutSeconds": 300,
105
+ "signatureSource": "scripts/smoke-agy-install-state.sh",
106
+ "signature": "[QK:INSTALLED-DEVBIN-MUTATION-REFUSED]"
107
+ },
108
+ {
109
+ "claim": "INSTALLED-DEVBIN-PUBLIC-STOPS",
110
+ "title": "the public expose-dev-bin ignores the helper's installed-package refusal and goes on to verify, exclusive and the package-manager queries",
111
+ "subject": "run.sh",
112
+ "find": [" [ \"$rc\" -eq 4 ] && return 4"],
113
+ "replace": [" : # the installed-package refusal no longer stops the wrapper"],
114
+ "gate": ["bash", "run.sh", "smoke-agy-install-state"],
115
+ "timeoutSeconds": 300,
116
+ "signatureSource": "scripts/smoke-agy-install-state.sh",
117
+ "signature": "[QK:INSTALLED-DEVBIN-PUBLIC-STOPS]"
118
+ },
119
+ {
120
+ "claim": "SOURCE-DEVBIN-MOVED-CHECKOUT",
121
+ "title": "the moved-checkout rule is dropped while closing the installed path, so a developer's link no longer follows its own checkout to another one",
122
+ "subject": "scripts/dev-bin.sh",
123
+ "find": [
124
+ " [ \"$(state_link \"$sf\")\" = \"$link\" ] && [ \"$cur\" = \"$(state_target \"$sf\")\" ] && return 0"
125
+ ],
126
+ "replace": [" :"],
127
+ "gate": ["bash", "run.sh", "smoke-agy-install-state"],
128
+ "timeoutSeconds": 300,
129
+ "signatureSource": "scripts/smoke-agy-install-state.sh",
130
+ "signature": "[QK:SOURCE-DEVBIN-MOVED-CHECKOUT]"
96
131
  }
97
132
  ]
98
133
  }
@@ -81,7 +81,7 @@
81
81
  "title": "MCP long description advertises owned-outcome as a selectable dormant verb again and drops the reason a dormant target actually returns",
82
82
  "subject": "ENTWURF.json",
83
83
  "find": [
84
- "A dormant socket rejects this delivery verb as dormant-fire-forget-unsupported. owned-outcome was withdrawn. Only on an explicit operator request, entwurf_resume_call may visibly reopen a proven-dormant Pi or Claude Code citizen under the same id, submitting no prompt; that lifecycle is separate from delivery. "
84
+ "A dormant socket rejects this delivery verb as dormant-fire-forget-unsupported. owned-outcome was withdrawn. Only on an explicit operator request, entwurf_resume_call may visibly reopen a proven-dormant Pi, pi-durable or Claude Code citizen under the same id, submitting no prompt; that lifecycle is separate from delivery. "
85
85
  ],
86
86
  "replace": [
87
87
  "owned-outcome wakes a DORMANT socket-domain citizen by spawn-bg resume ONLY and is never auto-converted. "
@@ -120,7 +120,7 @@
120
120
  "title": "entwurf_peers drops the dead-row sentence, so a dormant citizen is listed with nothing saying it is unreachable",
121
121
  "subject": "ENTWURF.json",
122
122
  "find": [
123
- "row. A `dead` socket row is a REPORTED FACT: no entwurf_v2 delivery is available while it is dormant. Listing it grants no action and is not an invitation to dispatch. Only on an explicit operator request, the separate entwurf_resume_call may visibly reopen a proven-dormant Pi or Claude Code citizen under the same id, submitting no prompt. In particular"
123
+ "row. A `dead` socket row is a REPORTED FACT: no entwurf_v2 delivery is available while it is dormant. Listing it grants no action and is not an invitation to dispatch. Only on an explicit operator request, the separate entwurf_resume_call may visibly reopen a proven-dormant Pi, pi-durable or Claude Code citizen under the same id, submitting no prompt. In particular"
124
124
  ],
125
125
  "replace": ["row. In particular"],
126
126
  "gate": ["bash", "run.sh", "check-entwurf-v2-surface"],
@@ -114,8 +114,8 @@
114
114
  "claim": "V2RESUME-ARGV-FROM-RECORD",
115
115
  "title": "the launcher is handed the caller's own directory instead of the record's, so the citizen returns in the wrong project",
116
116
  "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
117
- "find": ["\t\tconst launched = deps.launch({ backend: \"pi\", cwd: identity.cwd, runtimeArgs });"],
118
- "replace": ["\t\tconst launched = deps.launch({ backend: \"pi\", cwd: process.cwd(), runtimeArgs });"],
117
+ "find": ["\t\tconst launched = await deps.launch({ backend: \"pi\", cwd: identity.cwd, runtimeArgs });"],
118
+ "replace": ["\t\tconst launched = await deps.launch({ backend: \"pi\", cwd: process.cwd(), runtimeArgs });"],
119
119
  "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
120
120
  "timeoutSeconds": 60,
121
121
  "signature": "[QK:V2RESUME-ARGV-FROM-RECORD]",
@@ -201,10 +201,10 @@
201
201
  "title": "the backend is re-derived instead of taken from the typed error, which is the second read this rail refuses",
202
202
  "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
203
203
  "find": [
204
- "\t\t\t\t\t\t`${targetGardenId} is a ${err.backend} citizen; only pi and Claude Code citizens can be reopened ` +"
204
+ "\t\t\t\t\t\t`${targetGardenId} is a ${err.backend} citizen; only pi, pi-durable and Claude Code citizens can be reopened ` +"
205
205
  ],
206
206
  "replace": [
207
- "\t\t\t\t\t\t`${targetGardenId} is a citizen of another backend; only pi and Claude Code citizens can be reopened ` +"
207
+ "\t\t\t\t\t\t`${targetGardenId} is a citizen of another backend; only pi, pi-durable and Claude Code citizens can be reopened ` +"
208
208
  ],
209
209
  "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
210
210
  "timeoutSeconds": 60,
@@ -338,10 +338,10 @@
338
338
  "claim": "V2RESUME-PI-NEVER-READS-RECEIVER",
339
339
  "title": "the pi path consults a Claude Code receiver marker, mixing the two backends' dormancy proofs",
340
340
  "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
341
- "find": ["\t\tconst launched = deps.launch({ backend: \"pi\", cwd: identity.cwd, runtimeArgs });"],
341
+ "find": ["\t\tconst launched = await deps.launch({ backend: \"pi\", cwd: identity.cwd, runtimeArgs });"],
342
342
  "replace": [
343
343
  "\t\tdeps.inspectReceiver(targetGardenId);",
344
- "\t\tconst launched = deps.launch({ backend: \"pi\", cwd: identity.cwd, runtimeArgs });"
344
+ "\t\tconst launched = await deps.launch({ backend: \"pi\", cwd: identity.cwd, runtimeArgs });"
345
345
  ],
346
346
  "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
347
347
  "timeoutSeconds": 60,
@@ -502,6 +502,255 @@
502
502
  "timeoutSeconds": 60,
503
503
  "signature": "[QK:V2RESUME-REJECT-HINTS-FROM-ROOT]",
504
504
  "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
505
+ },
506
+ {
507
+ "claim": "V2RESUME-LAUNCH-AWAITED",
508
+ "title": "the pi launch stops being awaited, so a herdr launch that answers through a promise reads as a refusal with no reason while the tab it opened is on screen",
509
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
510
+ "find": ["\t\tconst launched = await deps.launch({ backend: \"pi\", cwd: identity.cwd, runtimeArgs });"],
511
+ "replace": [
512
+ "\t\tconst launched = deps.launch({ backend: \"pi\", cwd: identity.cwd, runtimeArgs }) as VisibleResumeLaunchOutcome;"
513
+ ],
514
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
515
+ "timeoutSeconds": 60,
516
+ "signature": "[QK:V2RESUME-LAUNCH-AWAITED]",
517
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
518
+ },
519
+ {
520
+ "claim": "V2RESUME-HERDR-RENDER",
521
+ "title": "the herdr branch of the renderer is dropped, so a herdr launch is printed as a tmux window with undefined runtime, window and pane pid",
522
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
523
+ "find": ["\tif (h.rail === \"herdr\") return renderHerdrPiResume(result, h, actor);"],
524
+ "replace": [""],
525
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
526
+ "timeoutSeconds": 60,
527
+ "signature": "[QK:V2RESUME-HERDR-RENDER]",
528
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
529
+ },
530
+ {
531
+ "claim": "V2RESUME-POST-CREATE-FAILURE-NAMED",
532
+ "title": "a launch failure after the tab create is folded into launch-refused, so the operator is told no window was opened over a tab that may be holding the citizen",
533
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
534
+ "find": ["\treturn \"failure\" in launched"],
535
+ "replace": ["\treturn \"failureNever\" in launched"],
536
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
537
+ "timeoutSeconds": 60,
538
+ "signature": "[QK:V2RESUME-POST-CREATE-FAILURE-NAMED]",
539
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
540
+ },
541
+ {
542
+ "claim": "V2RESUME-LAUNCH-FAILED-HEADER-HONEST",
543
+ "title": "the header of a tab create herdr declined by name claims the tab was created, sending the operator to look for a tab that never existed",
544
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
545
+ "find": ["\t\t\t? \"entwurf_resume_call failed at the herdr tab create step\""],
546
+ "replace": ["\t\t\t? \"entwurf_resume_call failed after the herdr tab was created\""],
547
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
548
+ "timeoutSeconds": 60,
549
+ "signature": "[QK:V2RESUME-LAUNCH-FAILED-HEADER-HONEST]",
550
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
551
+ },
552
+ {
553
+ "claim": "V2RESUME-CC-JOIN-HANDLE-ONLY",
554
+ "title": "the guard that admits only a handle carrying the opened pane's direct child is disabled, so a herdr agent-start or pi-durable layout handle reaches the join with no pid and the wiring defect no longer fails by name",
555
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
556
+ "find": ["\tif (handle.rail !== \"tmux\" && handle.rail !== \"herdr-layout-claude\") {"],
557
+ "replace": ["\tif (handle.rail !== \"tmux\" && handle.rail !== \"herdr-layout-claude\" && false) {"],
558
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
559
+ "timeoutSeconds": 60,
560
+ "signature": "[QK:V2RESUME-CC-JOIN-HANDLE-ONLY]",
561
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
562
+ },
563
+ {
564
+ "claim": "V2RESUME-PD-ADOPT-LAUNCH",
565
+ "title": "the adopt launch normalizes the reservation's raw profile, so the child's SDK reads another profile string than the one its reservation recorded and adopt row 5 refuses every resume",
566
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
567
+ "find": ["\t\t\tPI_CODING_AGENT_DIR: reservation.profile,"],
568
+ "replace": ["\t\t\tPI_CODING_AGENT_DIR: path.resolve(reservation.profile),"],
569
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
570
+ "timeoutSeconds": 60,
571
+ "signature": "[QK:V2RESUME-PD-ADOPT-LAUNCH]",
572
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
573
+ },
574
+ {
575
+ "claim": "V2RESUME-PD-WITHDRAW-NONE-CREATED",
576
+ "title": "a launch failure whose recovery is none no longer withdraws, so a herdr decline that created nothing leaves the citizen reserved by a live bridge",
577
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
578
+ "find": ["\t\tconst noneCreated = !(\"failure\" in launched) || launched.failure.recovery.outcome === \"none\";"],
579
+ "replace": ["\t\tconst noneCreated = !(\"failure\" in launched);"],
580
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
581
+ "timeoutSeconds": 60,
582
+ "signature": "[QK:V2RESUME-PD-WITHDRAW-NONE-CREATED]",
583
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
584
+ },
585
+ {
586
+ "claim": "V2RESUME-PD-KEEP-POSSIBLY-CREATED",
587
+ "title": "an unknown or orphaned launch outcome is read as none-created and the reservation is withdrawn while a child may still adopt it",
588
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
589
+ "find": ["\t\tconst noneCreated = !(\"failure\" in launched) || launched.failure.recovery.outcome === \"none\";"],
590
+ "replace": [
591
+ "\t\tconst noneCreated = !(\"failure\" in launched) || launched.failure.recovery.outcome !== \"closed\";"
592
+ ],
593
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
594
+ "timeoutSeconds": 60,
595
+ "signature": "[QK:V2RESUME-PD-KEEP-POSSIBLY-CREATED]",
596
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
597
+ },
598
+ {
599
+ "claim": "V2RESUME-PD-THROW-KEEPS",
600
+ "title": "a launcher that threw withdraws the reservation, though a window may exist whose child can still adopt it",
601
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
602
+ "find": [
603
+ "\t\t\t`entwurf-v2-visible-resume: the pi-durable launch of ${targetGardenId} threw: ${messageOf(err)}${kept}`,"
604
+ ],
605
+ "replace": [
606
+ "\t\t\t`entwurf-v2-visible-resume: the pi-durable launch of ${targetGardenId} threw: ${messageOf(err)}${await withdraw()}`,"
607
+ ],
608
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
609
+ "timeoutSeconds": 60,
610
+ "signature": "[QK:V2RESUME-PD-THROW-KEEPS]",
611
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
612
+ },
613
+ {
614
+ "claim": "V2RESUME-PD-WITHDRAW-FAILURE-REPORTED",
615
+ "title": "a failed withdrawal is silently retried, so the answer stops being the outcome of the one withdrawal this call made",
616
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
617
+ "find": ["\t\t\tawait deps.withdrawPiDurable(reservation);"],
618
+ "replace": ["\t\t\tawait deps.withdrawPiDurable(reservation).catch(() => deps.withdrawPiDurable(reservation));"],
619
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
620
+ "timeoutSeconds": 60,
621
+ "signature": "[QK:V2RESUME-PD-WITHDRAW-FAILURE-REPORTED]",
622
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
623
+ },
624
+ {
625
+ "claim": "V2RESUME-PD-RESERVE-LIVE-REFUSED",
626
+ "title": "a live holder's reserve refusal is no longer named target-live and surfaces as a raw fence error",
627
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
628
+ "find": ["\tif (err.reason === \"owner-live\") {"],
629
+ "replace": ["\tif (err.reason === \"withdraw-not-own\") {"],
630
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
631
+ "timeoutSeconds": 60,
632
+ "signature": "[QK:V2RESUME-PD-RESERVE-LIVE-REFUSED]",
633
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
634
+ },
635
+ {
636
+ "claim": "V2RESUME-PD-OBSERVE-BOUNDED-WAIT",
637
+ "title": "the adoption observation reads once and gives up, so a child that adopts a second later is reported not-yet-adopted",
638
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
639
+ "find": [
640
+ "\t\tif (deps.now() - started >= timeoutMs) return { kind: \"not-yet-adopted\", waitedMs: deps.now() - started };"
641
+ ],
642
+ "replace": [
643
+ "\t\tif (deps.now() - started >= 0) return { kind: \"not-yet-adopted\", waitedMs: deps.now() - started };"
644
+ ],
645
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
646
+ "timeoutSeconds": 60,
647
+ "signature": "[QK:V2RESUME-PD-OBSERVE-BOUNDED-WAIT]",
648
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
649
+ },
650
+ {
651
+ "claim": "V2RESUME-PD-OBSERVE-CLASSES",
652
+ "title": "an unreadable D claim is reported as reservation-lost instead of unobserved with its cause",
653
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
654
+ "find": [
655
+ "\t\t\treturn { kind: \"unobserved\", reason: seen.reason, detail: seen.detail, waitedMs: deps.now() - started };"
656
+ ],
657
+ "replace": ["\t\t\treturn { kind: \"reservation-lost\", waitedMs: deps.now() - started };"],
658
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
659
+ "timeoutSeconds": 60,
660
+ "signature": "[QK:V2RESUME-PD-OBSERVE-CLASSES]",
661
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
662
+ },
663
+ {
664
+ "claim": "V2RESUME-PD-RENDER",
665
+ "title": "the adopted receipt stops saying an adoption is not readiness, so it reads as the citizen being back",
666
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
667
+ "find": ["adoption of this reservation. That is not ` +"],
668
+ "replace": ["adoption of this reservation. That is ` +"],
669
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
670
+ "timeoutSeconds": 60,
671
+ "signature": "[QK:V2RESUME-PD-RENDER]",
672
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
673
+ },
674
+ {
675
+ "claim": "V2RESUME-PD-LAUNCH-FAILED-STEP",
676
+ "title": "a pi-durable layout.apply failure is rendered as the pi rail's tab create failure, sending the operator to a tab create step that never ran and to a control socket pi-durable does not have",
677
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
678
+ "find": ["\tconst layout = f.rail === \"herdr-layout\";"],
679
+ "replace": ["\tconst layout = false && f.rail === \"herdr-layout\";"],
680
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
681
+ "timeoutSeconds": 60,
682
+ "signature": "[QK:V2RESUME-PD-LAUNCH-FAILED-STEP]",
683
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
684
+ },
685
+ {
686
+ "claim": "V2RESUME-PD-RESERVED-HOLDER-NOT-ADDRESS",
687
+ "title": "a reservation's live parent is answered as a running holder, so the caller is pointed at a process entwurf_v2 cannot reach",
688
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
689
+ "find": ["\t\tif (err.holderState === \"reserved\") {"],
690
+ "replace": ["\t\tif (err.holderState === \"closed\") {"],
691
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
692
+ "timeoutSeconds": 60,
693
+ "signature": "[QK:V2RESUME-PD-RESERVED-HOLDER-NOT-ADDRESS]",
694
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
695
+ },
696
+ {
697
+ "claim": "V2RESUME-PD-RESERVE-WRITTEN-THEN-FAILED",
698
+ "title": "a reserve that wrote its reservation and then failed a lock release surfaces as a bare release error that names no reservation",
699
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
700
+ "find": [
701
+ "\t\t\tif (err.written !== undefined) throw new Error(piDurableReservedThenFailed(targetGardenId, err, err.written));"
702
+ ],
703
+ "replace": ["\t\t\tvoid piDurableReservedThenFailed;"],
704
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
705
+ "timeoutSeconds": 60,
706
+ "signature": "[QK:V2RESUME-PD-RESERVE-WRITTEN-THEN-FAILED]",
707
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
708
+ },
709
+ {
710
+ "claim": "V2RESUME-PD-WITHDRAW-PARTIAL-STATE",
711
+ "title": "a withdrawal that wrote the donor's row and then left its transaction file is reported as if it had written nothing",
712
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
713
+ "find": ["\tif (err instanceof PiDurableOwnerFenceError && err.written !== undefined) {"],
714
+ "replace": ["\tif (err instanceof PiDurableOwnerFenceError && err.written === null) {"],
715
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
716
+ "timeoutSeconds": 60,
717
+ "signature": "[QK:V2RESUME-PD-WITHDRAW-PARTIAL-STATE]",
718
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
719
+ },
720
+ {
721
+ "claim": "V2RESUME-CC-HERDR-JOIN",
722
+ "title": "the herdr Claude Code observation joins the marker owner to herdr's pane VIEW id instead of the pane's direct-child pid, so a real return is never seen",
723
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
724
+ "find": [
725
+ "\tconst panePid = handle.rail === \"tmux\" ? handle.panePid : (handle.paneChild as { pid: string }).pid;"
726
+ ],
727
+ "replace": ["\tconst panePid = handle.rail === \"tmux\" ? handle.panePid : handle.herdrPaneId;"],
728
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
729
+ "timeoutSeconds": 60,
730
+ "signature": "[QK:V2RESUME-CC-HERDR-JOIN]",
731
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
732
+ },
733
+ {
734
+ "claim": "V2RESUME-CC-HERDR-PROCESS-UNREAD",
735
+ "title": "a herdr launch whose pane child herdr did not report goes on to wait for a marker joined to no pid, and ends as a quiet resume-unobserved instead of naming herdr's cause",
736
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
737
+ "find": ["\tif (handle.rail === \"herdr-layout-claude\" && \"unread\" in handle.paneChild) {"],
738
+ "replace": ["\tif (handle.rail === \"herdr-layout-claude\" && \"unread\" in handle.paneChild && false) {"],
739
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
740
+ "timeoutSeconds": 60,
741
+ "signature": "[QK:V2RESUME-CC-HERDR-PROCESS-UNREAD]",
742
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
743
+ },
744
+ {
745
+ "claim": "V2RESUME-CC-HERDR-RENDER",
746
+ "title": "the herdr Claude Code receipt drops the pane child pid it joined, so the operator cannot tell which process the observation was about",
747
+ "subject": "pi-extensions/lib/entwurf-v2-visible-resume.ts",
748
+ "find": ["\t\t\t` child: ${child}\\n` +"],
749
+ "replace": ["\t\t\t\"\" +"],
750
+ "gate": ["bash", "run.sh", "check-entwurf-v2-visible-resume"],
751
+ "timeoutSeconds": 60,
752
+ "signature": "[QK:V2RESUME-CC-HERDR-RENDER]",
753
+ "signatureSource": "pi-extensions/lib/entwurf-v2-visible-resume.test.ts"
505
754
  }
506
755
  ]
507
756
  }
@@ -84,7 +84,7 @@ const arg = process.argv[2];
84
84
  // A dash argv is a flag this command does not have, not a store directory — treating it as
85
85
  // a path would answer `--help` with "empty store, exit 0", a silent wrong fact.
86
86
  if (process.argv.length > 3 || (arg !== undefined && arg.startsWith("-"))) {
87
- console.error("usage: entwurf peer-facts [meta-sessions-dir]");
87
+ console.error("peer-facts: see `entwurf help peer-facts` (from that checkout's root: `./run.sh help peer-facts`)");
88
88
  process.exit(2);
89
89
  }
90
90
 
@@ -697,6 +697,72 @@ if bash "$DEVBIN" expose entwurf-bridge >/dev/null 2>&1; then die "dev-bin migra
697
697
  ok "dev-bin migrate: foreign legacy refused (linkPath basename != entwurf-bridge)"
698
698
  rm -f "$LEGACY" "$DLINK" "$DSTATE"
699
699
  bash "$DEVBIN" remove >/dev/null 2>&1
700
+ # J-6 (#153): dev-bin mutation is a SOURCE checkout's authority. The moved-checkout rule lets a
701
+ # developer's link follow its own checkout (D1 → D2); an INSTALLED package must not inherit it —
702
+ # measured before this guard: a node_modules-shaped root's public expose-dev-bin retargeted every
703
+ # managed link and state a dev checkout had made. The installed root is judged by where dev-bin.sh
704
+ # itself lives (symlinks and directory aliases resolved), never by cwd or a target override. The
705
+ # copies below carry THIS checkout's run.sh/dev-bin.sh/package.json bytes, so the cells test them.
706
+ OVERRIDES=(-u ENTWURF_OPERATOR_TARGET -u ENTWURF_BRIDGE_TARGET -u ENTWURF_AGY_STATUSLINE_TARGET -u ENTWURF_AGY_IMPRINT_TARGET -u ENTWURF_COPILOT_STATUSLINE_TARGET)
707
+ make_root() { # $1 = root dir: the three real files plus no-op helper targets
708
+ mkdir -p "$1/scripts" "$1/mcp/entwurf-bridge"
709
+ cp "$REPO_DIR/run.sh" "$1/run.sh"; cp "$DEVBIN" "$1/scripts/dev-bin.sh"; cp "$REPO_DIR/package.json" "$1/package.json"
710
+ for _t in mcp/entwurf-bridge/start.sh scripts/agy-statusline.sh scripts/agy-imprint.sh scripts/copilot-statusline.sh; do
711
+ printf '#!/bin/sh\nexit 0\n' > "$1/$_t"; chmod +x "$1/$_t"
712
+ done
713
+ chmod +x "$1/run.sh" "$1/scripts/dev-bin.sh"; unset _t
714
+ }
715
+ devbin_world() { # every link in the bin dir, every dev-bin state body, and the profile bytes
716
+ { for _f in "$DBIN_DIR"/* "$XDG_DATA_HOME"/entwurf/dev-bin/*; do
717
+ [ -e "$_f" ] || [ -L "$_f" ] || continue
718
+ if [ -L "$_f" ]; then printf 'L %s -> %s\n' "$_f" "$(readlink "$_f")"; else printf 'F %s %s\n' "$_f" "$(sha256sum < "$_f")"; fi
719
+ done
720
+ printf 'P %s\n' "$(sha256sum < "$HOME/.profile")"; } 2>/dev/null; unset _f
721
+ }
722
+ D1="$SB/src-d1"; D2="$SB/src-d2"; INST="$SB/inst/node_modules/@junghanacs/entwurf"
723
+ make_root "$D1"; make_root "$D2"; make_root "$INST"
724
+ printf '# smoke profile seed\n' > "$HOME/.profile"
725
+ env "${OVERRIDES[@]}" bash "$D1/scripts/dev-bin.sh" expose >/dev/null 2>&1 # a dev checkout's five links + states
726
+ printf '{"schemaVersion":1,"linkPath":"%s","target":"%s","detectMode":"created-new","stampedAt":"x"}\n' \
727
+ "$DBIN_DIR/entwurf-bridge" "$D1/mcp/entwurf-bridge/start.sh" > "$XDG_DATA_HOME/entwurf/dev-bin/install-state.json" # a legacy state migration would act on
728
+ ln -s "$INST" "$SB/inst-alias"; ln -s "$INST/scripts/dev-bin.sh" "$SB/dev-bin-link.sh"
729
+ WORLD_BEFORE="$(devbin_world)"
730
+ set +e
731
+ bash "$INST/scripts/dev-bin.sh" expose >"$SB/j6-direct.out" 2>&1; RC_DIRECT=$? # target overrides still set
732
+ env "${OVERRIDES[@]}" bash "$SB/inst-alias/scripts/dev-bin.sh" expose entwurf >/dev/null 2>&1; RC_ALIAS=$?
733
+ env "${OVERRIDES[@]}" bash "$SB/dev-bin-link.sh" expose entwurf >/dev/null 2>&1; RC_LINK=$?
734
+ set -e
735
+ WORLD_AFTER_EXPOSE="$(devbin_world)"
736
+ # an installed-root link and state that DO match the installed target: remove still refuses
737
+ rm -f "$OLINK"; ln -s "$INST/run.sh" "$OLINK"
738
+ printf '{"schemaVersion":1,"linkPath":"%s","target":"%s","detectMode":"created-new","stampedAt":"x"}\n' "$OLINK" "$INST/run.sh" > "$OSTATE"
739
+ WORLD_BEFORE_REMOVE="$(devbin_world)"
740
+ set +e; env "${OVERRIDES[@]}" bash "$INST/scripts/dev-bin.sh" remove entwurf >/dev/null 2>&1; RC_REMOVE=$?; set -e
741
+ want "installed dev-bin: expose (direct, alias, symlinked script) and remove refuse with 4 before any write — no link, state, legacy migration or profile change, even for a link the installed root's own target matches [QK:INSTALLED-DEVBIN-MUTATION-REFUSED]" \
742
+ "[ '$RC_DIRECT' -eq 4 ] && [ '$RC_ALIAS' -eq 4 ] && [ '$RC_LINK' -eq 4 ] && [ '$RC_REMOVE' -eq 4 ] && grep -qF 'REFUSE expose: this dev-bin.sh belongs to an installed package' '$SB/j6-direct.out' && [ \"\$WORLD_BEFORE\" = \"\$WORLD_AFTER_EXPOSE\" ] && [ \"\$WORLD_BEFORE_REMOVE\" = \"\$(devbin_world)\" ] && [ -f '$XDG_DATA_HOME/entwurf/dev-bin/install-state.json' ]"
743
+ rm -f "$OLINK"; ln -s "$D1/run.sh" "$OLINK"
744
+ printf '{"schemaVersion":1,"linkPath":"%s","target":"%s","detectMode":"created-new","stampedAt":"x"}\n' "$OLINK" "$D1/run.sh" > "$OSTATE"
745
+ rm -f "$XDG_DATA_HOME/entwurf/dev-bin/install-state.json"
746
+ # the public verbs: the refusal propagates, and nothing after it runs — no verify, no exclusive,
747
+ # no package-manager query (a recording pnpm/npm in front of the fixture ones).
748
+ PM_TRACE="$SB/j6-pm.trace"; : > "$PM_TRACE"; PM_REC="$SB/j6-pm"; mkdir -p "$PM_REC"
749
+ for _pm in pnpm npm; do printf '#!/usr/bin/env bash\necho "%s $*" >> "%s"\nexec "%s/%s" "$@"\n' "$_pm" "$PM_TRACE" "$FAKE_PM" "$_pm" > "$PM_REC/$_pm"; chmod +x "$PM_REC/$_pm"; done; unset _pm
750
+ WORLD_BEFORE="$(devbin_world)"
751
+ set +e
752
+ OUT="$(env "${OVERRIDES[@]}" PATH="$PM_REC:$DBIN_DIR:$PATH" bash "$INST/run.sh" expose-dev-bin 2>&1)"; RC=$?
753
+ env "${OVERRIDES[@]}" PATH="$PM_REC:$DBIN_DIR:$PATH" bash "$INST/run.sh" remove-dev-bin >/dev/null 2>&1; RC_PREMOVE=$?
754
+ set -e
755
+ want "installed public expose-dev-bin/remove-dev-bin exit 4 with the named refusal, and nothing follows it: no verify, no exclusive, no package-manager query, no change [QK:INSTALLED-DEVBIN-PUBLIC-STOPS]" \
756
+ "[ '$RC' -eq 4 ] && [ '$RC_PREMOVE' -eq 4 ] && printf '%s' \"\$OUT\" | grep -qF 'REFUSE expose: this dev-bin.sh belongs to an installed package' && ! printf '%s' \"\$OUT\" | grep -qE 'dev-bin verify|dev-bin exclusive|PATH winner' && [ ! -s '$PM_TRACE' ] && [ \"\$WORLD_BEFORE\" = \"\$(devbin_world)\" ]"
757
+ # the developer's own authority is kept: a link a source checkout made follows it to another one
758
+ set +e; env "${OVERRIDES[@]}" bash "$D2/scripts/dev-bin.sh" expose entwurf >"$SB/j6-d2.out" 2>&1; RC=$?; set -e
759
+ want "source dev-bin keeps the moved-checkout rule: the D1 link and state move to D2 as refresh-ours [QK:SOURCE-DEVBIN-MOVED-CHECKOUT]" \
760
+ "[ '$RC' -eq 0 ] && [ \"\$(readlink '$OLINK')\" = '$D2/run.sh' ] && grep -q 'refresh-ours' '$SB/j6-d2.out' && python3 -c 'import json,sys; sys.exit(0 if json.load(open(sys.argv[1]))[\"target\"]==sys.argv[2] else 1)' '$OSTATE' '$D2/run.sh'"
761
+ env "${OVERRIDES[@]}" bash "$D2/scripts/dev-bin.sh" remove entwurf >/dev/null 2>&1
762
+ env "${OVERRIDES[@]}" bash "$D1/scripts/dev-bin.sh" remove >/dev/null 2>&1
763
+ rm -rf "$D1" "$D2" "$SB/inst" "$SB/inst-alias" "$SB/dev-bin-link.sh" "$PM_REC" "$PM_TRACE" "$HOME/.profile"
764
+ want "installed dev-bin cells leave no managed link or state behind" "[ -z \"\$(ls -A '$DBIN_DIR' 2>/dev/null)\" ] && [ ! -d '$XDG_DATA_HOME/entwurf/dev-bin' ]"
765
+
700
766
  unset ENTWURF_DEV_BIN_DIR ENTWURF_OPERATOR_TARGET ENTWURF_BRIDGE_TARGET ENTWURF_AGY_STATUSLINE_TARGET
701
767
  export PATH="$DEVBIN_ORIG_PATH"
702
768
  unset ENTWURF_AGY_IMPRINT_TARGET ENTWURF_COPILOT_STATUSLINE_TARGET
@@ -166,6 +166,29 @@ OUT3="$(node --experimental-strip-types "$PRUNE" "$STORE" --ttl-days 100 2>&1)"
166
166
  set -e
167
167
  echo "$OUT3" | grep -q "STALE recordUpdatedAt>100d (0):" && echo "$OUT3" | grep -q "KEEP (live/recent): 2 record" && ok "--ttl-days widens keep window" || bad "--ttl-days did not move stale boundary"
168
168
 
169
+ # the leaf's usage is now only the address of the description (#153): --help keeps exit 0 and an
170
+ # unknown flag keeps exit 2 with its cause line, both with nothing on stdout. These two children get
171
+ # their own scratch HOME, XDG roots, PI dir and store under $TMP, never the operator's.
172
+ PRUNE_HOME="$TMP/help-home"
173
+ prune_env=(HOME="$PRUNE_HOME" XDG_DATA_HOME="$PRUNE_HOME/.local/share" XDG_STATE_HOME="$PRUNE_HOME/.local/state"
174
+ XDG_CACHE_HOME="$PRUNE_HOME/.cache" XDG_CONFIG_HOME="$PRUNE_HOME/.config" PI_CODING_AGENT_DIR="$PRUNE_HOME/.pi/agent"
175
+ ENTWURF_META_SESSIONS_DIR="$PRUNE_HOME/store")
176
+ PRUNE_POINTER='meta-bridge-prune: see `entwurf help meta-bridge-prune` (from that checkout'"'"'s root: `./run.sh help meta-bridge-prune`)'
177
+ set +e
178
+ env "${prune_env[@]}" node --experimental-strip-types "$PRUNE" --help >"$TMP/help.out" 2>"$TMP/help.err"
179
+ HELP_CODE=$?
180
+ env "${prune_env[@]}" node --experimental-strip-types "$PRUNE" --bogus >"$TMP/bogus.out" 2>"$TMP/bogus.err"
181
+ BOGUS_CODE=$?
182
+ set -e
183
+ if [ "$HELP_CODE" = "0" ] && [ ! -s "$TMP/help.out" ] && [ "$(cat "$TMP/help.err")" = "$PRUNE_POINTER" ] \
184
+ && [ "$(wc -l <"$TMP/help.err")" = 1 ] \
185
+ && [ "$BOGUS_CODE" = "2" ] && [ ! -s "$TMP/bogus.out" ] \
186
+ && [ "$(cat "$TMP/bogus.err")" = "unknown flag: --bogus"$'\n'"$PRUNE_POINTER" ] && [ "$(wc -l <"$TMP/bogus.err")" = 2 ]; then
187
+ ok "--help (exit 0) and --bogus (exit 2, after its cause line) print nothing on stdout and point at \`entwurf help meta-bridge-prune\`"
188
+ else
189
+ bad "[QK:PRUNE-HELP-POINTER] the leaf's usage is not exactly the pointer line: help rc=$HELP_CODE stderr=$(head -c 300 "$TMP/help.err") stdout=$(head -c 200 "$TMP/help.out") | bogus rc=$BOGUS_CODE stderr=$(head -c 300 "$TMP/bogus.err") stdout=$(head -c 200 "$TMP/bogus.out")"
190
+ fi
191
+
169
192
  if [ "$fail" = "0" ]; then
170
193
  echo "smoke-meta-prune PASS"
171
194
  else
@@ -221,8 +221,12 @@ ok("the caller is a record-backed citizen with an armed mailbox and a sender mar
221
221
 
222
222
  const citizensBefore = new Set(
223
223
  (
224
- JSON.parse(execFileSync("bash", [path.join(REPO, "run.sh"), "meta-facts"], { encoding: "utf8" }))
225
- .citizens as Array<{
224
+ JSON.parse(
225
+ execFileSync("bash", [path.join(REPO, "run.sh"), "meta-facts"], {
226
+ encoding: "utf8",
227
+ maxBuffer: 64 * 1024 * 1024,
228
+ }),
229
+ ).citizens as Array<{
226
230
  gardenId: string;
227
231
  }>
228
232
  ).map((c) => c.gardenId),
@@ -356,7 +360,12 @@ ok(
356
360
  );
357
361
 
358
362
  // ── 7. that id is a real omp citizen, and the ONLY one this step minted ──────
359
- const facts = JSON.parse(execFileSync("bash", [path.join(REPO, "run.sh"), "meta-facts"], { encoding: "utf8" }));
363
+ const facts = JSON.parse(
364
+ execFileSync("bash", [path.join(REPO, "run.sh"), "meta-facts"], {
365
+ encoding: "utf8",
366
+ maxBuffer: 64 * 1024 * 1024,
367
+ }),
368
+ );
360
369
  const citizen = (facts.citizens as Array<Record<string, unknown>>).find((c) => c.gardenId === siblingGid);
361
370
  ok(
362
371
  "the callback id resolves to a V3 record whose backend is omp",
@@ -279,7 +279,13 @@ ok(
279
279
  // This closes a real gap rather than a cosmetic one. `entwurf_inbox_read` takes a
280
280
  // CALLER-SUPPLIED garden id and does not verify it against the caller's identity, so
281
281
  // `lastReadAt` alone proves a read happened — not that THIS session performed it.
282
- const record = JSON.parse(sh("bash", [path.join(REPO, "run.sh"), "meta-facts"]));
282
+ const record = JSON.parse(
283
+ execFileSync("bash", [path.join(REPO, "run.sh"), "meta-facts"], {
284
+ encoding: "utf8",
285
+ stdio: ["ignore", "pipe", "pipe"],
286
+ maxBuffer: 64 * 1024 * 1024,
287
+ }),
288
+ );
283
289
  const citizen = (record.citizens as Array<Record<string, unknown>>).find((c) => c.gardenId === gardenId);
284
290
  ok("the citizen's V3 record names a transcript for this session", typeof citizen?.transcriptPath === "string");
285
291
  const transcriptPath = citizen?.transcriptPath as string;
@@ -258,7 +258,12 @@ receipts["1-fixture"] = `gardenId=${fixtureGid} backend=claude-code (collector o
258
258
 
259
259
  const citizensBefore = new Set(
260
260
  (
261
- JSON.parse(execFileSync("bash", [path.join(REPO, "run.sh"), "meta-facts"], { encoding: "utf8" })) as {
261
+ JSON.parse(
262
+ execFileSync("bash", [path.join(REPO, "run.sh"), "meta-facts"], {
263
+ encoding: "utf8",
264
+ maxBuffer: 64 * 1024 * 1024,
265
+ }),
266
+ ) as {
262
267
  citizens: Array<{ gardenId: string }>;
263
268
  }
264
269
  ).citizens.map((c) => c.gardenId),
@@ -453,7 +458,12 @@ ok(
453
458
  JSON.stringify(pdMarker),
454
459
  );
455
460
 
456
- const facts = JSON.parse(execFileSync("bash", [path.join(REPO, "run.sh"), "meta-facts"], { encoding: "utf8" })) as {
461
+ const facts = JSON.parse(
462
+ execFileSync("bash", [path.join(REPO, "run.sh"), "meta-facts"], {
463
+ encoding: "utf8",
464
+ maxBuffer: 64 * 1024 * 1024,
465
+ }),
466
+ ) as {
457
467
  citizens: Array<{ gardenId: string }>;
458
468
  };
459
469
  // Other siblings may mint citizens in the operator's garden meanwhile; this step's are the ones in its scratch cwd.