@event4u/agent-config 9.27.0 → 9.29.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 (220) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/AGENTS.md +13 -12
  3. package/CHANGELOG.md +165 -0
  4. package/README.md +1 -1
  5. package/agents/templates/.ai-council.yml.example +55 -24
  6. package/dist/agent-src/commands/analyze/conformance.md +24 -0
  7. package/dist/agent-src/commands/cost/report.md +2 -2
  8. package/dist/agent-src/commands/team/adversarial.md +8 -7
  9. package/dist/agent-src/commands/team/delegate.md +13 -12
  10. package/dist/agent-src/commands/team/review.md +9 -8
  11. package/dist/agent-src/commands/team/status.md +9 -8
  12. package/dist/agent-src/commands/team.md +29 -19
  13. package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +88 -2
  14. package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +29 -21
  15. package/dist/agent-src/contexts/execution/host-capability-manifest.md +26 -15
  16. package/dist/agent-src/contexts/execution/orchestration-telemetry.md +28 -3
  17. package/dist/agent-src/contexts/execution/subagent-response-contract.md +54 -0
  18. package/dist/agent-src/contexts/execution/subagent-routing.md +13 -11
  19. package/dist/agent-src/contexts/execution/subagent-spawn-contract.md +25 -0
  20. package/dist/agent-src/contexts/execution/subagent-steering.md +6 -3
  21. package/dist/agent-src/rules/artifact-engagement-recording.md +4 -1
  22. package/dist/agent-src/rules/autonomous-execution.md +1 -1
  23. package/dist/agent-src/rules/brand-source-of-truth.md +33 -6
  24. package/dist/agent-src/rules/context-hygiene.md +3 -1
  25. package/dist/agent-src/rules/council-availability.md +93 -0
  26. package/dist/agent-src/rules/cross-source-consistency.md +3 -0
  27. package/dist/agent-src/rules/delegation-policy.md +38 -10
  28. package/dist/agent-src/rules/design-fidelity.md +3 -0
  29. package/dist/agent-src/rules/evaluator-independence.md +2 -1
  30. package/dist/agent-src/rules/external-code-graph-interop.md +2 -1
  31. package/dist/agent-src/rules/finance-safety-floor.md +1 -1
  32. package/dist/agent-src/rules/git-history-discipline.md +1 -1
  33. package/dist/agent-src/rules/history-discipline.md +3 -1
  34. package/dist/agent-src/rules/legal-safety-floor.md +1 -1
  35. package/dist/agent-src/rules/no-pr-progress-comments.md +3 -1
  36. package/dist/agent-src/rules/onboarding-gate.md +3 -1
  37. package/dist/agent-src/rules/roadmap-ci-steps-policy.md +4 -1
  38. package/dist/agent-src/rules/role-mode-adherence.md +1 -1
  39. package/dist/agent-src/rules/scale-discipline.md +3 -1
  40. package/dist/agent-src/rules/self-repair-loop.md +103 -0
  41. package/dist/agent-src/rules/session-canary.md +2 -1
  42. package/dist/agent-src/rules/settings-ask-protocol.md +8 -7
  43. package/dist/agent-src/rules/strategy-safety-floor.md +1 -1
  44. package/dist/agent-src/rules/token-budget-discipline.md +4 -2
  45. package/dist/agent-src/rules/tool-safety.md +5 -1
  46. package/dist/agent-src/skills/adversarial-review/SKILL.md +1 -1
  47. package/dist/agent-src/skills/analysis-autonomous-mode/SKILL.md +1 -1
  48. package/dist/agent-src/skills/forensics-report/SKILL.md +114 -0
  49. package/dist/agent-src/skills/forensics-report/evals/triggers.json +17 -0
  50. package/dist/agent-src/skills/performance-analysis/SKILL.md +1 -1
  51. package/dist/agent-src/skills/persona-improvement/SKILL.md +1 -1
  52. package/dist/agent-src/skills/premortem/SKILL.md +30 -1
  53. package/dist/agent-src/skills/project-analyzer/SKILL.md +1 -1
  54. package/dist/agent-src/skills/reasoning-orchestrator/SKILL.md +2 -1
  55. package/dist/agent-src/skills/security-audit/SKILL.md +1 -1
  56. package/dist/agent-src/skills/sequential-thinking/SKILL.md +1 -1
  57. package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +1 -1
  58. package/dist/agent-src/skills/subagent-orchestration/SKILL.md +7 -5
  59. package/dist/agent-src/skills/subagent-orchestration/schemas/subagent-status.json +61 -3
  60. package/dist/agent-src/skills/test-case-discovery/SKILL.md +1 -1
  61. package/dist/agent-src/skills/universal-project-analysis/SKILL.md +1 -1
  62. package/dist/agent-src/templates/AGENTS.md +8 -12
  63. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  64. package/dist/agent-src/templates/roadmaps.md +23 -0
  65. package/dist/cli/registry.js +7 -0
  66. package/dist/cli/registry.js.map +1 -1
  67. package/dist/cli-delegate/chunk-66OVWGB5.js +270 -0
  68. package/dist/cli-delegate/{chunk-HJ6DAJHR.js → chunk-LCFJIQ4J.js} +89 -7
  69. package/dist/cli-delegate/{chunk-VSYT7OHO.js → chunk-LVRYBKTI.js} +31 -2
  70. package/dist/cli-delegate/{chunk-DVRS2KVR.js → chunk-MMXA5GY7.js} +10 -17
  71. package/dist/cli-delegate/{chunk-WR6GTRHJ.js → chunk-MP3JT3IU.js} +3 -3
  72. package/dist/cli-delegate/{chunk-3KJIMFTB.js → chunk-NZ5N2XQL.js} +467 -305
  73. package/dist/cli-delegate/{chunk-AIIUU73N.js → chunk-OJM3MBJM.js} +1 -1
  74. package/dist/cli-delegate/{chunk-BCPIOAGV.js → chunk-RCIA67XN.js} +117 -96
  75. package/dist/cli-delegate/chunk-REKCMYOU.js +2991 -0
  76. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  77. package/dist/cli-delegate/cmd_brand_status.js +134 -0
  78. package/dist/cli-delegate/cmd_conformance.js +10 -47
  79. package/dist/cli-delegate/cmd_converge.js +3 -3
  80. package/dist/cli-delegate/cmd_doctor.js +10 -6
  81. package/dist/cli-delegate/cmd_explain.js +80 -330
  82. package/dist/cli-delegate/cmd_export.js +6 -6
  83. package/dist/cli-delegate/cmd_fleet.js +3 -3
  84. package/dist/cli-delegate/cmd_handoff.js +3 -3
  85. package/dist/cli-delegate/cmd_mcp_available.js +433 -0
  86. package/dist/cli-delegate/cmd_migrate.js +3 -3
  87. package/dist/cli-delegate/cmd_packs_active.js +153 -0
  88. package/dist/cli-delegate/cmd_preflight.js +1 -1
  89. package/dist/cli-delegate/cmd_prune.js +6 -6
  90. package/dist/cli-delegate/cmd_route_audit.js +8 -8
  91. package/dist/cli-delegate/cmd_route_explain.js +4 -4
  92. package/dist/cli-delegate/cmd_settings_get.js +266 -0
  93. package/dist/cli-delegate/cmd_settings_migrate.js +6 -6
  94. package/dist/cli-delegate/cmd_settings_set.js +8 -2968
  95. package/dist/cli-delegate/cmd_sync.js +6 -6
  96. package/dist/cli-delegate/cmd_uninstall.js +6 -6
  97. package/dist/cli-delegate/cmd_update.js +4 -4
  98. package/dist/cli-delegate/cmd_upgrade.js +4 -4
  99. package/dist/cli-delegate/cmd_validate.js +6 -6
  100. package/dist/cli-delegate/cmd_versions.js +4 -4
  101. package/dist/discovery/deprecation-report.md +1 -1
  102. package/dist/discovery/discovery-manifest.json +164 -55
  103. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  104. package/dist/discovery/discovery-manifest.summary.md +4 -3
  105. package/dist/discovery/orphan-report.md +1 -1
  106. package/dist/discovery/packs.json +44 -5
  107. package/dist/discovery/trust-report.md +11 -11
  108. package/dist/discovery/workspaces.json +16 -6
  109. package/dist/hooks/dispatch.js +9341 -6131
  110. package/dist/install/install.mjs +739 -524
  111. package/dist/mcp/registry-manifest.json +3 -3
  112. package/dist/mcp/server.json +2 -2
  113. package/dist/mcp/server.mjs +777 -281
  114. package/dist/router.json +1 -1
  115. package/dist/scripts/_lib/agent_settings.js +134 -15
  116. package/dist/scripts/_lib/agent_settings.js.map +1 -1
  117. package/dist/scripts/condense.js +147 -2
  118. package/dist/scripts/condense.js.map +1 -1
  119. package/dist/server/schemas/settings.js +6 -7
  120. package/dist/server/schemas/settings.js.map +1 -1
  121. package/dist/shared/settingsCarveOut.js +0 -9
  122. package/dist/shared/settingsCarveOut.js.map +1 -1
  123. package/dist/shared/settingsClasses.js +11 -9
  124. package/dist/shared/settingsClasses.js.map +1 -1
  125. package/dist/ui/assets/{index-DmR0mbk4.js → index-JDAF00bC.js} +2 -2
  126. package/dist/ui/assets/{index-DmR0mbk4.js.map → index-JDAF00bC.js.map} +1 -1
  127. package/dist/ui/index.html +1 -1
  128. package/docs/contracts/settings-classes.md +68 -17
  129. package/docs/guidelines/agent-infra/memory-access.md +14 -3
  130. package/package.json +1 -1
  131. package/src/agent-src/templates/AGENTS.md +8 -12
  132. package/src/config/agent-settings.template.yml +52 -66
  133. package/src/config/budgets.yml +29 -0
  134. package/src/config/discovery/packs.yml +11 -0
  135. package/src/config/discovery/workspaces.yml +1 -1
  136. package/src/config/evaluator-budgets.json +4 -4
  137. package/src/config/gate-coverage.yml +26 -0
  138. package/src/config/gate-violation-baselines.json +3 -3
  139. package/src/scripts/_cli/cmd_brand_status.ts +174 -0
  140. package/src/scripts/_cli/cmd_conformance.ts +14 -42
  141. package/src/scripts/_cli/cmd_doctor.ts +65 -48
  142. package/src/scripts/_cli/cmd_mcp_available.ts +266 -0
  143. package/src/scripts/_cli/cmd_packs_active.ts +225 -0
  144. package/src/scripts/_cli/cmd_settings_get.ts +324 -0
  145. package/src/scripts/_dispatch.bash +57 -0
  146. package/src/scripts/_lib/agent_settings.ts +140 -16
  147. package/src/scripts/_lib/auto_dispatch.corpus.test.ts +1 -1
  148. package/src/scripts/_lib/auto_dispatch.ts +23 -18
  149. package/src/scripts/_lib/capsule_trigger.ts +124 -0
  150. package/src/scripts/_lib/carrier_divergence.ts +135 -0
  151. package/src/scripts/_lib/host_capability.ts +144 -4
  152. package/src/scripts/_lib/judgment_ladder.ts +382 -0
  153. package/src/scripts/_lib/orchestration_gate.ts +8 -0
  154. package/src/scripts/_lib/orchestration_record.ts +37 -0
  155. package/src/scripts/_lib/orchestration_savings.ts +1 -1
  156. package/src/scripts/_lib/prompt_shape.ts +88 -0
  157. package/src/scripts/_lib/review_skipped_record.ts +102 -0
  158. package/src/scripts/_lib/rule_layer_overlap.ts +254 -0
  159. package/src/scripts/_lib/self_repair.ts +489 -0
  160. package/src/scripts/_lib/self_repair_store.ts +128 -0
  161. package/src/scripts/_lib/subagent_capsule.ts +182 -0
  162. package/src/scripts/_lib/subagent_response.ts +19 -0
  163. package/src/scripts/_lib/subagent_spawn.ts +13 -0
  164. package/src/scripts/_lib/subagent_steering.ts +9 -4
  165. package/src/scripts/_lib/tier_budget_routing.ts +6 -1
  166. package/src/scripts/_lib/transcript_entry.ts +80 -0
  167. package/src/scripts/_lib/worker_budget.ts +37 -2
  168. package/src/scripts/ai_council/clients.ts +76 -54
  169. package/src/scripts/ai_council/config.ts +94 -6
  170. package/src/scripts/ai_council/handoff.ts +113 -0
  171. package/src/scripts/ai_council/modes.ts +19 -4
  172. package/src/scripts/ai_council/orchestrator.ts +105 -0
  173. package/src/scripts/ai_council/quorum.ts +84 -0
  174. package/src/scripts/ai_council/session.ts +44 -0
  175. package/src/scripts/ai_council/transport_resolver.ts +127 -1
  176. package/src/scripts/ai_team/availability.ts +110 -0
  177. package/src/scripts/ai_team/config.ts +34 -14
  178. package/src/scripts/ai_team/team_dispatch.ts +71 -29
  179. package/src/scripts/bench_critic_protocol.ts +396 -0
  180. package/src/scripts/check_branch_freshness.ts +255 -0
  181. package/src/scripts/check_council_config_location.ts +125 -1
  182. package/src/scripts/check_detector_corpus.ts +202 -0
  183. package/src/scripts/check_hook_bundle_freshness.ts +4 -1
  184. package/src/scripts/check_standing_rule_delivery.ts +314 -0
  185. package/src/scripts/condense.ts +162 -2
  186. package/src/scripts/config/profiles.ts +13 -3
  187. package/src/scripts/conformance_scan.ts +272 -24
  188. package/src/scripts/council_availability_hook.ts +188 -0
  189. package/src/scripts/council_cli.ts +527 -32
  190. package/src/scripts/forensics_report.ts +333 -0
  191. package/src/scripts/hook_manifest.yaml +107 -26
  192. package/src/scripts/hooks/block_no_verify.ts +39 -2
  193. package/src/scripts/hooks/block_unauthorized_git.ts +156 -7
  194. package/src/scripts/hooks/concern_registry.ts +10 -0
  195. package/src/scripts/hooks/delegation_nudge_hook.ts +484 -0
  196. package/src/scripts/hooks/end_review_nudge_hook.ts +726 -0
  197. package/src/scripts/hooks/orchestration_record_hook.ts +342 -0
  198. package/src/scripts/install.ts +151 -0
  199. package/src/scripts/language_mirror_hook.ts +30 -37
  200. package/src/scripts/lint_discovery_vocabulary.ts +2 -0
  201. package/src/scripts/lint_never_silent.ts +276 -0
  202. package/src/scripts/lint_no_activation_gates.ts +271 -0
  203. package/src/scripts/measure_scope_dedup.ts +19 -13
  204. package/src/scripts/onboarding_gate_hook.ts +40 -1
  205. package/src/scripts/orchestration_record.ts +12 -1
  206. package/src/scripts/preamble_byte_census.ts +9 -20
  207. package/src/scripts/rebuild_hook_bundle.ts +177 -0
  208. package/src/scripts/release.ts +74 -2
  209. package/src/scripts/report_carrier_divergence.ts +364 -0
  210. package/src/scripts/report_skill_activation.ts +257 -0
  211. package/src/scripts/report_skill_obligation_violations.ts +579 -0
  212. package/src/scripts/routing_doctor.ts +95 -39
  213. package/src/scripts/score_skill_selection.ts +19 -5
  214. package/src/scripts/self_repair_cli.ts +470 -0
  215. package/src/scripts/self_repair_hook.ts +241 -0
  216. package/src/scripts/team_events_spike.ts +271 -0
  217. package/src/scripts/team_review_gate_hook.ts +31 -9
  218. package/src/server/schemas/settings.ts +10 -17
  219. package/src/shared/settingsCarveOut.ts +0 -10
  220. package/src/shared/settingsClasses.ts +11 -9
@@ -0,0 +1,470 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Self-repair CLI — the queue reader and the single gated outward step.
4
+ *
5
+ * self-repair:status list open records
6
+ * self-repair:release <fp> [--dry-run] [--repo <owner/name>]
7
+ *
8
+ * `release` is the ONE keystroke the loop is designed around: everything up to
9
+ * it — detection, dedup, analysis, the authored fix, the commit — happens
10
+ * without the user. Publishing does not, because a push and a PR/issue are
11
+ * both Hard-Floor actions under `non-destructive-by-default`, and no standing
12
+ * instruction lifts that floor. Running this command IS the user's word.
13
+ *
14
+ * Route selection follows the spec exactly: a pull request when the fix can be
15
+ * authored and pushed — directly when the pre-flight probe shows upstream push
16
+ * rights, via fork → cross-repo PR when it does not — an issue when it cannot,
17
+ * and a local record when nothing can leave the machine. Once publishing was
18
+ * authorized, failures degrade down the ladder WITHIN the same call (push/PR
19
+ * failure or 30 s timeout → issue; issue failure → the record stays open with
20
+ * the errors attached) — degrading never widens what leaves the machine, only
21
+ * the vehicle. `--dry-run` prints the resolved route and the rendered body and
22
+ * touches no network — that is the path the tests drive.
23
+ *
24
+ * Fail-closed on privacy: the record's own text goes through the audited
25
+ * privacy floor before anything leaves the machine, and a refusal downgrades
26
+ * the record to local-only rather than scrubbing it into publishable shape.
27
+ */
28
+ import { spawnSync } from 'node:child_process';
29
+ import * as fs from 'node:fs';
30
+ import * as path from 'node:path';
31
+ import { fileURLToPath, pathToFileURL } from 'node:url';
32
+
33
+ import {
34
+ chooseEgress,
35
+ type DefectRecord,
36
+ type EgressCapability,
37
+ type EgressRoute,
38
+ egressBlockedReason,
39
+ type PushVia,
40
+ renderReport,
41
+ sanitizeEvidence,
42
+ } from './_lib/self_repair.js';
43
+ import {
44
+ attachReleaseErrors,
45
+ listRecords,
46
+ markReleased,
47
+ openRecords,
48
+ readRecord,
49
+ } from './_lib/self_repair_store.js';
50
+
51
+ export const UPSTREAM_REPO = 'event4u-app/agent-config';
52
+
53
+ /**
54
+ * Per-step ceiling for every network-touching egress step. A hung `git push`
55
+ * or `gh` call degrades down the ladder instead of hanging the release.
56
+ */
57
+ export const EGRESS_STEP_TIMEOUT_MS = 30_000;
58
+
59
+ const EXIT_OK = 0;
60
+ const EXIT_FAIL = 1;
61
+
62
+ /** Probe result for one machine — injectable so tests never shell out. */
63
+ export interface Probe {
64
+ agentConfigCheckout: string | null;
65
+ ghAuthenticated: boolean;
66
+ canPushUpstream: boolean;
67
+ canFork: boolean;
68
+ }
69
+
70
+ export interface RunResult {
71
+ ok: boolean;
72
+ out: string;
73
+ timedOut: boolean;
74
+ }
75
+
76
+ /** Injectable command runner — tests script it, production shells out. */
77
+ export type Runner = (cmd: string, args: string[], cwd?: string) => RunResult;
78
+
79
+ function run(cmd: string, args: string[], cwd?: string): RunResult {
80
+ const r = spawnSync(cmd, args, { cwd, encoding: 'utf-8', timeout: EGRESS_STEP_TIMEOUT_MS });
81
+ // Only spawnSync's own ETIMEDOUT counts: a child killed externally by
82
+ // SIGTERM is a failure, not a timeout, and recording it as "timed out"
83
+ // points the next debugging session at the wrong cause (R2 finding #3).
84
+ const timedOut = (r.error as NodeJS.ErrnoException | undefined)?.code === 'ETIMEDOUT';
85
+ return {
86
+ ok: r.status === 0 && !timedOut,
87
+ out: `${r.stdout ?? ''}${r.stderr ?? ''}`,
88
+ timedOut,
89
+ };
90
+ }
91
+
92
+ /**
93
+ * Where the fix can be authored. `AGENT_CONFIG_SOURCE` wins; otherwise the CWD
94
+ * when it IS the package checkout. A consumer project has neither — that is the
95
+ * normal case, and the reason the issue fallback exists.
96
+ */
97
+ export function findAgentConfigCheckout(env: NodeJS.ProcessEnv, cwd: string): string | null {
98
+ const explicit = env['AGENT_CONFIG_SOURCE'];
99
+ if (explicit && fs.existsSync(path.join(explicit, 'src', 'rules'))) {
100
+ return explicit;
101
+ }
102
+ if (
103
+ fs.existsSync(path.join(cwd, 'src', 'rules')) &&
104
+ fs.existsSync(path.join(cwd, 'src', 'scripts', 'hook_manifest.yaml'))
105
+ ) {
106
+ return cwd;
107
+ }
108
+ return null;
109
+ }
110
+
111
+ /**
112
+ * Pre-flight probe of ACTUAL push rights, not remote existence. `git remote`
113
+ * returning a name proves only that the repo was cloned — every consumer of
114
+ * the public repo has that and no write access. `gh api repos/<repo>` returns
115
+ * the authenticated viewer's `permissions.push` and the repo's `allow_forking`,
116
+ * which are the two facts the route actually depends on. An unreachable or
117
+ * unauthenticated API answers `false` for both — the ladder then degrades to
118
+ * an issue rather than failing at push time.
119
+ */
120
+ export function probeUpstreamRights(
121
+ runner: Runner,
122
+ repo: string,
123
+ ): { canPushUpstream: boolean; canFork: boolean } {
124
+ const r = runner('gh', ['api', `repos/${repo}`]);
125
+ if (!r.ok) {
126
+ return { canPushUpstream: false, canFork: false };
127
+ }
128
+ try {
129
+ const parsed: unknown = JSON.parse(r.out);
130
+ const meta = parsed as {
131
+ permissions?: { push?: boolean };
132
+ allow_forking?: boolean;
133
+ };
134
+ return {
135
+ canPushUpstream: meta.permissions?.push === true,
136
+ canFork: meta.allow_forking !== false,
137
+ };
138
+ } catch {
139
+ return { canPushUpstream: false, canFork: false };
140
+ }
141
+ }
142
+
143
+ export function probeMachine(
144
+ env: NodeJS.ProcessEnv,
145
+ cwd: string,
146
+ runner: Runner = run,
147
+ repo: string = UPSTREAM_REPO,
148
+ ): Probe {
149
+ const checkout = findAgentConfigCheckout(env, cwd);
150
+ const gh = runner('gh', ['auth', 'status']);
151
+ const rights = gh.ok
152
+ ? probeUpstreamRights(runner, repo)
153
+ : { canPushUpstream: false, canFork: false };
154
+ return {
155
+ agentConfigCheckout: checkout,
156
+ ghAuthenticated: gh.ok,
157
+ canPushUpstream: rights.canPushUpstream,
158
+ canFork: gh.ok && rights.canFork,
159
+ };
160
+ }
161
+
162
+ export function capabilityOf(probe: Probe): EgressCapability {
163
+ return {
164
+ hasAgentConfigCheckout: probe.agentConfigCheckout !== null,
165
+ ghAuthenticated: probe.ghAuthenticated,
166
+ canPushUpstream: probe.canPushUpstream,
167
+ canFork: probe.canFork,
168
+ };
169
+ }
170
+
171
+ /** Title line for the PR / issue — one per defect class, stable across reruns. */
172
+ export function titleFor(record: DefectRecord): string {
173
+ return `fix(self-repair): ${record.defect_class} — ${record.occurrences}x observed`;
174
+ }
175
+
176
+ export interface ReleasePlan {
177
+ route: EgressRoute;
178
+ pushVia: PushVia | null;
179
+ blocked: string | null;
180
+ title: string;
181
+ body: string;
182
+ }
183
+
184
+ /** Pure: everything `release` decides before it touches the network. */
185
+ export function planRelease(record: DefectRecord, probe: Probe, repoRoot: string): ReleasePlan {
186
+ const blocked = egressBlockedReason(record, repoRoot);
187
+ const choice =
188
+ blocked !== null
189
+ ? { route: 'local-only' as const, pushVia: null }
190
+ : chooseEgress(capabilityOf(probe));
191
+ return {
192
+ route: choice.route,
193
+ pushVia: choice.pushVia,
194
+ blocked,
195
+ title: titleFor(record),
196
+ body: renderReport(record, choice.route),
197
+ };
198
+ }
199
+
200
+ // ── egress ladder execution ────────────────────────────────────────
201
+
202
+ /** One egress step's outcome — kept for the record when the ladder exhausts. */
203
+ export interface EgressAttempt {
204
+ step: string;
205
+ ok: boolean;
206
+ detail: string;
207
+ }
208
+
209
+ export interface ReleaseOutcome {
210
+ /** What actually got published, or null when the whole ladder failed. */
211
+ published: 'pull-request' | 'issue' | null;
212
+ attempts: EgressAttempt[];
213
+ }
214
+
215
+ export interface ReleaseDeps {
216
+ runner: Runner;
217
+ now: () => string;
218
+ }
219
+
220
+ const FORK_REMOTE = 'self-repair-fork';
221
+
222
+ function failDetail(step: string, r: RunResult): string {
223
+ return r.timedOut
224
+ ? `${step}: timed out after ${EGRESS_STEP_TIMEOUT_MS / 1000}s`
225
+ : `${step}: ${r.out.trim() || 'failed'}`;
226
+ }
227
+
228
+ /**
229
+ * Push the fix branch — directly, or via fork → cross-repo PR — then open the
230
+ * PR. Returns null on success; on any step failure or timeout returns the
231
+ * failed attempt so the caller degrades down the ladder.
232
+ */
233
+ function tryPullRequest(
234
+ plan: ReleasePlan,
235
+ probe: Probe,
236
+ repo: string,
237
+ branch: string,
238
+ via: PushVia,
239
+ deps: ReleaseDeps,
240
+ ): EgressAttempt | null {
241
+ const checkout = probe.agentConfigCheckout!;
242
+ let prHead: string | null = null;
243
+
244
+ if (via === 'upstream') {
245
+ const pushed = deps.runner('git', ['push', '-u', 'origin', branch], checkout);
246
+ if (!pushed.ok) {
247
+ return { step: 'push-upstream', ok: false, detail: failDetail('push-upstream', pushed) };
248
+ }
249
+ } else {
250
+ const forked = deps.runner('gh', ['repo', 'fork', repo, '--clone=false']);
251
+ if (!forked.ok) {
252
+ return { step: 'fork-ensure', ok: false, detail: failDetail('fork-ensure', forked) };
253
+ }
254
+ const who = deps.runner('gh', ['api', 'user', '--jq', '.login']);
255
+ if (!who.ok || who.out.trim().length === 0) {
256
+ return { step: 'whoami', ok: false, detail: failDetail('whoami', who) };
257
+ }
258
+ const login = who.out.trim();
259
+ const repoName = repo.split('/')[1] ?? repo;
260
+ const hasRemote = deps.runner('git', ['remote', 'get-url', FORK_REMOTE], checkout);
261
+ if (!hasRemote.ok) {
262
+ const added = deps.runner(
263
+ 'git',
264
+ ['remote', 'add', FORK_REMOTE, `https://github.com/${login}/${repoName}.git`],
265
+ checkout,
266
+ );
267
+ if (!added.ok) {
268
+ return { step: 'fork-remote', ok: false, detail: failDetail('fork-remote', added) };
269
+ }
270
+ }
271
+ const pushed = deps.runner('git', ['push', '-u', FORK_REMOTE, branch], checkout);
272
+ if (!pushed.ok) {
273
+ return { step: 'push-fork', ok: false, detail: failDetail('push-fork', pushed) };
274
+ }
275
+ prHead = `${login}:${branch}`;
276
+ }
277
+
278
+ const prArgs = ['pr', 'create', '--repo', repo, '--title', plan.title, '--body', plan.body];
279
+ if (prHead !== null) {
280
+ prArgs.push('--head', prHead);
281
+ }
282
+ const pr = deps.runner('gh', prArgs, checkout);
283
+ if (!pr.ok) {
284
+ return { step: 'pr-create', ok: false, detail: failDetail('pr-create', pr) };
285
+ }
286
+ process.stdout.write(`${pr.out}`);
287
+ return null;
288
+ }
289
+
290
+ /**
291
+ * The egress ladder, executed within ONE `release` call: direct push → fork
292
+ * push → issue → open-with-errors. Every rung is separately timed out; a rung
293
+ * that fails degrades to the next instead of aborting the release. When the
294
+ * whole ladder fails, the record stays `open` with every failed attempt
295
+ * attached (sanitized — command output can carry local paths).
296
+ */
297
+ export function executeRelease(
298
+ record: DefectRecord,
299
+ plan: ReleasePlan,
300
+ probe: Probe,
301
+ repo: string,
302
+ deps: ReleaseDeps,
303
+ ): ReleaseOutcome {
304
+ const attempts: EgressAttempt[] = [];
305
+
306
+ if (plan.route === 'pull-request') {
307
+ const checkout = probe.agentConfigCheckout!;
308
+ const branch = deps.runner('git', ['branch', '--show-current'], checkout).out.trim();
309
+ if (!branch || branch === 'main') {
310
+ // Precondition, not an egress failure: the authored fix exists and
311
+ // must ride its own branch — an issue instead would drop the fix.
312
+ attempts.push({
313
+ step: 'branch-check',
314
+ ok: false,
315
+ detail:
316
+ 'the fix must sit on its own branch in the agent-config checkout ' +
317
+ `(currently: ${branch || 'detached'})`,
318
+ });
319
+ return { published: null, attempts };
320
+ }
321
+ const ladder: PushVia[] =
322
+ plan.pushVia === 'upstream' && probe.canFork
323
+ ? ['upstream', 'fork']
324
+ : [plan.pushVia ?? 'upstream'];
325
+ for (const via of ladder) {
326
+ const failed = tryPullRequest(plan, probe, repo, branch, via, deps);
327
+ if (failed === null) {
328
+ return { published: 'pull-request', attempts };
329
+ }
330
+ attempts.push(failed);
331
+ }
332
+ }
333
+
334
+ if (plan.route === 'pull-request' || plan.route === 'issue') {
335
+ // Re-render for the rung actually taken: a plan that degraded from
336
+ // pull-request must not file an issue whose body claims
337
+ // `Route: pull-request` (R2 finding #4).
338
+ const issueBody =
339
+ plan.route === 'issue' ? plan.body : renderReport(record, 'issue');
340
+ const issue = deps.runner('gh', [
341
+ 'issue',
342
+ 'create',
343
+ '--repo',
344
+ repo,
345
+ '--title',
346
+ plan.title,
347
+ '--body',
348
+ issueBody,
349
+ ]);
350
+ if (issue.ok) {
351
+ process.stdout.write(`${issue.out}`);
352
+ return { published: 'issue', attempts };
353
+ }
354
+ attempts.push({ step: 'issue-create', ok: false, detail: failDetail('issue-create', issue) });
355
+ }
356
+
357
+ return { published: null, attempts };
358
+ }
359
+
360
+ function statusCmd(root: string): number {
361
+ const open = openRecords(root);
362
+ const all = listRecords(root);
363
+ if (all.length === 0) {
364
+ process.stdout.write('self-repair: no records.\n');
365
+ return EXIT_OK;
366
+ }
367
+ process.stdout.write(`self-repair: ${open.length} open of ${all.length} record(s)\n\n`);
368
+ for (const r of all) {
369
+ const mark = r.status === 'open' ? 'o' : '.';
370
+ process.stdout.write(
371
+ ` ${mark} ${r.fingerprint} ${r.defect_class} ${r.occurrences}x ${r.last_seen}\n` +
372
+ ` ${r.evidence}\n`,
373
+ );
374
+ }
375
+ process.stdout.write('\nRelease one: agent-config self-repair:release <fingerprint>\n');
376
+ return EXIT_OK;
377
+ }
378
+
379
+ function releaseCmd(root: string, argv: string[]): number {
380
+ const fp = argv.find((a) => !a.startsWith('-'));
381
+ if (!fp) {
382
+ process.stderr.write('self-repair:release needs a fingerprint (see self-repair:status).\n');
383
+ return EXIT_FAIL;
384
+ }
385
+ const record = readRecord(root, fp);
386
+ if (record === null) {
387
+ process.stderr.write(`self-repair:release: no record ${fp}.\n`);
388
+ return EXIT_FAIL;
389
+ }
390
+ const dryRun = argv.includes('--dry-run');
391
+ const repoArg = argv.indexOf('--repo');
392
+ const repo = repoArg >= 0 ? (argv[repoArg + 1] ?? UPSTREAM_REPO) : UPSTREAM_REPO;
393
+ const probe = probeMachine(process.env, process.cwd());
394
+ const plan = planRelease(record, probe, root);
395
+
396
+ const via = plan.pushVia !== null ? ` via=${plan.pushVia}` : '';
397
+ process.stdout.write(`self-repair:release ${fp} -> route=${plan.route}${via}\n`);
398
+ if (plan.blocked !== null) {
399
+ process.stdout.write(` WARN ${plan.blocked}\n`);
400
+ }
401
+ if (dryRun) {
402
+ process.stdout.write(`\n--- ${plan.title} ---\n${plan.body}\n`);
403
+ return EXIT_OK;
404
+ }
405
+ if (plan.route === 'local-only') {
406
+ process.stdout.write(
407
+ ' Nothing published. The record stays in agents/runtime/self-repair/.\n',
408
+ );
409
+ return EXIT_OK;
410
+ }
411
+
412
+ const deps: ReleaseDeps = { runner: run, now: () => new Date().toISOString() };
413
+ const outcome = executeRelease(record, plan, probe, repo, deps);
414
+
415
+ for (const a of outcome.attempts) {
416
+ process.stderr.write(` FAIL ${a.detail}\n`);
417
+ }
418
+ if (outcome.published === null) {
419
+ const errors = outcome.attempts.map((a) => sanitizeEvidence(a.detail));
420
+ attachReleaseErrors(root, fp, errors, deps.now());
421
+ process.stderr.write(
422
+ ' The egress ladder is exhausted; the record stays open with the errors attached.\n',
423
+ );
424
+ return EXIT_FAIL;
425
+ }
426
+ if (outcome.published !== plan.route) {
427
+ process.stdout.write(` Degraded to ${outcome.published} after failed attempt(s).\n`);
428
+ }
429
+ markReleased(root, fp, deps.now());
430
+ return EXIT_OK;
431
+ }
432
+
433
+ export function main(argv: readonly string[]): number {
434
+ const root = process.cwd();
435
+ const sub = argv[0] ?? 'status';
436
+ if (sub === 'status') {
437
+ return statusCmd(root);
438
+ }
439
+ if (sub === 'release') {
440
+ return releaseCmd(root, [...argv.slice(1)]);
441
+ }
442
+ process.stderr.write(`self-repair: unknown subcommand '${sub}' (status | release).\n`);
443
+ return EXIT_FAIL;
444
+ }
445
+
446
+ declare const __AGENT_CONFIG_BUNDLE__: boolean | undefined;
447
+ function isCliEntry(): boolean {
448
+ if (typeof __AGENT_CONFIG_BUNDLE__ !== 'undefined' && __AGENT_CONFIG_BUNDLE__) {
449
+ return false;
450
+ }
451
+ if (process.argv[1] === undefined) {
452
+ return false;
453
+ }
454
+ const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
455
+ if (import.meta.url === argvUrl) {
456
+ return true;
457
+ }
458
+ try {
459
+ return (
460
+ fs.realpathSync(fileURLToPath(import.meta.url)) ===
461
+ fs.realpathSync(path.resolve(process.argv[1]))
462
+ );
463
+ } catch {
464
+ return false;
465
+ }
466
+ }
467
+
468
+ if (isCliEntry()) {
469
+ process.exit(main(process.argv.slice(2)));
470
+ }