@event4u/agent-config 9.28.0 → 9.30.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/AGENTS.md +6 -6
- package/CHANGELOG.md +172 -5
- package/README.md +2 -1
- package/agents/templates/.ai-council.yml.example +55 -24
- package/dist/agent-src/commands/analyze/inbox.md +22 -1
- package/dist/agent-src/commands/cost/report.md +2 -2
- package/dist/agent-src/commands/explain-run.md +31 -1
- package/dist/agent-src/commands/team/adversarial.md +8 -7
- package/dist/agent-src/commands/team/delegate.md +14 -12
- package/dist/agent-src/commands/team/review.md +9 -8
- package/dist/agent-src/commands/team/status.md +9 -8
- package/dist/agent-src/commands/team.md +29 -19
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +88 -2
- package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +29 -21
- package/dist/agent-src/contexts/execution/host-capability-manifest.md +26 -15
- package/dist/agent-src/contexts/execution/orchestration-telemetry.md +22 -1
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +17 -0
- package/dist/agent-src/contexts/execution/subagent-response-contract.md +65 -1
- package/dist/agent-src/contexts/execution/subagent-routing.md +13 -11
- package/dist/agent-src/contexts/execution/subagent-spawn-contract.md +25 -0
- package/dist/agent-src/contexts/execution/subagent-steering.md +6 -3
- package/dist/agent-src/rules/delegation-policy.md +11 -7
- package/dist/agent-src/rules/preservation-guard.md +9 -0
- package/dist/agent-src/rules/settings-ask-protocol.md +8 -7
- package/dist/agent-src/rules/token-budget-discipline.md +1 -1
- package/dist/agent-src/scripts/roadmap_gates.ts +312 -0
- package/dist/agent-src/scripts/update_roadmap_progress.ts +72 -4
- package/dist/agent-src/skills/forensics-report/SKILL.md +114 -0
- package/dist/agent-src/skills/forensics-report/evals/triggers.json +17 -0
- package/dist/agent-src/skills/premortem/SKILL.md +30 -1
- package/dist/agent-src/skills/reasoning-orchestrator/SKILL.md +2 -1
- package/dist/agent-src/skills/subagent-orchestration/SKILL.md +17 -5
- package/dist/agent-src/skills/subagent-orchestration/schemas/subagent-status.json +61 -3
- package/dist/agent-src/skills/test-case-discovery/SKILL.md +1 -1
- package/dist/agent-src/templates/AGENTS.md +4 -4
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +23 -0
- package/dist/cli/registry.js +2 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/{chunk-22GCIJ2O.js → chunk-66OVWGB5.js} +1 -1
- package/dist/cli-delegate/{chunk-7OMQCH4V.js → chunk-BTJNRUX3.js} +22 -35
- package/dist/cli-delegate/{chunk-DVRS2KVR.js → chunk-DDUNNFYX.js} +24 -17
- package/dist/cli-delegate/{chunk-PQSAQKHW.js → chunk-EQY4FLJB.js} +3 -3
- package/dist/cli-delegate/{chunk-2BE5I2MN.js → chunk-IYTGIG2Y.js} +155 -91
- package/dist/cli-delegate/{chunk-GB2FSUEE.js → chunk-LCFJIQ4J.js} +32 -0
- package/dist/cli-delegate/{chunk-VSYT7OHO.js → chunk-LVRYBKTI.js} +31 -2
- package/dist/cli-delegate/chunk-MMJL4TIX.js +18 -0
- package/dist/cli-delegate/{chunk-JXARCGVN.js → chunk-MP3JT3IU.js} +1 -1
- package/dist/cli-delegate/chunk-O3CO5BFB.js +161 -0
- package/dist/cli-delegate/{chunk-REKCMYOU.js → chunk-PFBV6XRT.js} +36 -20
- package/dist/cli-delegate/chunk-RVGBH4XK.js +142 -0
- package/dist/cli-delegate/cmd_analyze_session.js +1 -1
- package/dist/cli-delegate/cmd_brand_status.js +1 -1
- package/dist/cli-delegate/cmd_conformance.js +2803 -137
- package/dist/cli-delegate/cmd_doctor.js +10 -5
- package/dist/cli-delegate/cmd_explain.js +2 -2
- package/dist/cli-delegate/cmd_export.js +4 -4
- package/dist/cli-delegate/cmd_handoff.js +88 -8
- package/dist/cli-delegate/cmd_mcp_available.js +1 -1
- package/dist/cli-delegate/cmd_migrate.js +2 -2
- package/dist/cli-delegate/cmd_packs_active.js +2 -2
- package/dist/cli-delegate/cmd_prune.js +4 -4
- package/dist/cli-delegate/cmd_route_audit.js +3 -2
- package/dist/cli-delegate/cmd_route_explain.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +177 -0
- package/dist/cli-delegate/cmd_settings_get.js +2 -8
- package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
- package/dist/cli-delegate/cmd_settings_set.js +2 -2
- package/dist/cli-delegate/cmd_sync.js +4 -4
- package/dist/cli-delegate/cmd_uninstall.js +4 -4
- package/dist/cli-delegate/cmd_update.js +1 -1
- package/dist/cli-delegate/cmd_upgrade.js +1 -1
- package/dist/cli-delegate/cmd_validate.js +4 -4
- package/dist/cli-delegate/cmd_versions.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +72 -23
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -2
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +39 -2
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +12 -4
- package/dist/hooks/dispatch.js +2278 -611
- package/dist/install/install.mjs +56 -17
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +4122 -699
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/agent_settings.js +54 -0
- package/dist/scripts/_lib/agent_settings.js.map +1 -1
- package/dist/scripts/condense.js +147 -2
- package/dist/scripts/condense.js.map +1 -1
- package/dist/server/schemas/settings.js +12 -7
- package/dist/server/schemas/settings.js.map +1 -1
- package/dist/shared/settingsCarveOut.js +0 -9
- package/dist/shared/settingsCarveOut.js.map +1 -1
- package/dist/shared/settingsClasses.js +11 -9
- package/dist/shared/settingsClasses.js.map +1 -1
- package/dist/ui/assets/index-B5qGYOJo.js +26 -0
- package/dist/ui/assets/index-B5qGYOJo.js.map +1 -0
- package/dist/ui/index.html +1 -1
- package/docs/contracts/settings-classes.md +58 -12
- package/package.json +1 -1
- package/src/agent-src/scripts/roadmap_gates.ts +312 -0
- package/src/agent-src/scripts/update_roadmap_progress.ts +72 -4
- package/src/agent-src/templates/AGENTS.md +4 -4
- package/src/config/agent-settings.template.yml +88 -66
- package/src/config/discovery/packs.yml +11 -0
- package/src/config/discovery/workspaces.yml +1 -1
- package/src/config/dispatch-economy-metrics.json +61 -0
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-violation-baselines.json +3 -3
- package/src/config/hook-token-budget.json +91 -0
- package/src/config/recycle-threshold-budget.json +12 -0
- package/src/config/rule-enforcement-baseline.json +86 -0
- package/src/scripts/_cli/cmd_conformance.ts +31 -44
- package/src/scripts/_cli/cmd_doctor.ts +65 -48
- package/src/scripts/_cli/cmd_session_recycle.ts +226 -0
- package/src/scripts/_dispatch.bash +23 -0
- package/src/scripts/_lib/agent_settings.ts +58 -0
- package/src/scripts/_lib/auto_dispatch.corpus.test.ts +1 -1
- package/src/scripts/_lib/auto_dispatch.ts +23 -18
- package/src/scripts/_lib/capsule_trigger.ts +124 -0
- package/src/scripts/_lib/host_capability.ts +63 -6
- package/src/scripts/_lib/judgment_ladder.ts +634 -0
- package/src/scripts/_lib/orchestration_gate.ts +8 -0
- package/src/scripts/_lib/orchestration_record.ts +78 -10
- package/src/scripts/_lib/orchestration_savings.ts +1 -1
- package/src/scripts/_lib/recycle_envelope_paths.ts +35 -0
- package/src/scripts/_lib/self_repair.ts +354 -13
- package/src/scripts/_lib/self_repair_store.ts +28 -0
- package/src/scripts/_lib/session_eol.ts +230 -0
- package/src/scripts/_lib/session_role.ts +42 -0
- package/src/scripts/_lib/subagent_capsule.ts +341 -0
- package/src/scripts/_lib/subagent_response.ts +94 -0
- package/src/scripts/_lib/subagent_spawn.ts +13 -0
- package/src/scripts/_lib/subagent_steering.ts +9 -4
- package/src/scripts/_lib/tier_budget_routing.ts +6 -1
- package/src/scripts/_lib/worker_budget.ts +37 -2
- package/src/scripts/ai_council/clients.ts +126 -55
- package/src/scripts/ai_council/config.ts +94 -6
- package/src/scripts/ai_council/handoff.ts +113 -0
- package/src/scripts/ai_council/modes.ts +19 -4
- package/src/scripts/ai_council/orchestrator.ts +105 -0
- package/src/scripts/ai_council/quorum.ts +84 -0
- package/src/scripts/ai_council/session.ts +44 -0
- package/src/scripts/ai_council/transport_resolver.ts +127 -1
- package/src/scripts/ai_team/availability.ts +110 -0
- package/src/scripts/ai_team/config.ts +34 -14
- package/src/scripts/ai_team/team_dispatch.ts +71 -29
- package/src/scripts/ask_transport.ts +221 -0
- package/src/scripts/bench_critic_protocol.ts +396 -0
- package/src/scripts/bench_hook_injection.ts +234 -0
- package/src/scripts/check_adr_frontmatter.ts +84 -40
- package/src/scripts/check_branch_freshness.ts +1 -0
- package/src/scripts/check_corpus_staleness.ts +18 -4
- package/src/scripts/check_detector_corpus.ts +219 -0
- package/src/scripts/check_gitignore_freshness.ts +42 -8
- package/src/scripts/check_hook_bundle_freshness.ts +24 -1
- package/src/scripts/check_kernel_prefix_stability.ts +1 -0
- package/src/scripts/check_standing_rule_delivery.ts +73 -32
- package/src/scripts/check_static_layer_stability.ts +154 -0
- package/src/scripts/check_suppression_hygiene.ts +48 -0
- package/src/scripts/condense.ts +162 -2
- package/src/scripts/council_cli.ts +401 -25
- package/src/scripts/dispatch_economy_report.ts +448 -0
- package/src/scripts/dispatch_r2_reviewer.ts +15 -2
- package/src/scripts/explain_run.ts +206 -4
- package/src/scripts/forensics_report.ts +333 -0
- package/src/scripts/generate_settings_reference.ts +1 -0
- package/src/scripts/handoff_context_hook.ts +112 -4
- package/src/scripts/hook_manifest.yaml +117 -10
- package/src/scripts/hooks/concern_registry.ts +8 -0
- package/src/scripts/hooks/delegation_nudge_hook.ts +106 -35
- package/src/scripts/hooks/dispatch_hook.ts +39 -2
- package/src/scripts/hooks/edit_shape_hook.ts +303 -0
- package/src/scripts/hooks/orchestration_record_hook.ts +19 -1
- package/src/scripts/hooks/reread_guard_hook.ts +314 -0
- package/src/scripts/hooks/rtk_wrap_hook.ts +133 -14
- package/src/scripts/hooks/session_eol_hook.ts +268 -0
- package/src/scripts/hooks/turn_end_gate_hook.ts +507 -0
- package/src/scripts/install-hooks.sh +1 -1
- package/src/scripts/language_mirror_hook.ts +89 -0
- package/src/scripts/lint_decision_impact.ts +14 -1
- package/src/scripts/lint_discovery_vocabulary.ts +2 -0
- package/src/scripts/lint_hook_manifest.ts +82 -0
- package/src/scripts/lint_never_silent.ts +276 -0
- package/src/scripts/lint_no_activation_gates.ts +271 -0
- package/src/scripts/lint_readme_serial_comma.ts +1 -0
- package/src/scripts/lint_rule_enforcement_declaration.ts +347 -0
- package/src/scripts/measure_turn_end_gate.ts +194 -0
- package/src/scripts/orchestration_record.ts +12 -1
- package/src/scripts/rebuild_hook_bundle.ts +178 -0
- package/src/scripts/release.ts +74 -2
- package/src/scripts/report_conformance_funnel.ts +284 -0
- package/src/scripts/report_imperative_density.ts +1 -0
- package/src/scripts/routing_doctor.ts +118 -36
- package/src/scripts/score_skill_selection.ts +19 -5
- package/src/scripts/self_repair_cli.ts +337 -59
- package/src/scripts/session_eol_report.ts +261 -0
- package/src/scripts/team_events_spike.ts +271 -0
- package/src/scripts/team_review_gate_hook.ts +31 -9
- package/src/server/schemas/settings.ts +24 -17
- package/src/shared/settingsCarveOut.ts +0 -10
- package/src/shared/settingsClasses.ts +11 -9
- package/src/templates/compact-instructions.md +40 -0
- package/dist/ui/assets/index-DmR0mbk4.js +0 -26
- package/dist/ui/assets/index-DmR0mbk4.js.map +0 -1
|
@@ -12,9 +12,14 @@
|
|
|
12
12
|
* instruction lifts that floor. Running this command IS the user's word.
|
|
13
13
|
*
|
|
14
14
|
* Route selection follows the spec exactly: a pull request when the fix can be
|
|
15
|
-
* authored and pushed
|
|
16
|
-
*
|
|
17
|
-
*
|
|
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.
|
|
18
23
|
*
|
|
19
24
|
* Fail-closed on privacy: the record's own text goes through the audited
|
|
20
25
|
* privacy floor before anything leaves the machine, and a refusal downgrades
|
|
@@ -26,17 +31,34 @@ import * as path from 'node:path';
|
|
|
26
31
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
27
32
|
|
|
28
33
|
import {
|
|
29
|
-
|
|
34
|
+
chooseEgress,
|
|
30
35
|
type DefectRecord,
|
|
31
36
|
type EgressCapability,
|
|
37
|
+
type EgressChoice,
|
|
32
38
|
type EgressRoute,
|
|
33
39
|
egressBlockedReason,
|
|
40
|
+
patchDeniedReason,
|
|
41
|
+
type PushVia,
|
|
34
42
|
renderReport,
|
|
43
|
+
sanitizeEvidence,
|
|
35
44
|
} from './_lib/self_repair.js';
|
|
36
|
-
import {
|
|
45
|
+
import { buildSettingsClassIndex, parseSettingsClassRows } from '../shared/settingsClasses.js';
|
|
46
|
+
import {
|
|
47
|
+
attachReleaseErrors,
|
|
48
|
+
listRecords,
|
|
49
|
+
markReleased,
|
|
50
|
+
openRecords,
|
|
51
|
+
readRecord,
|
|
52
|
+
} from './_lib/self_repair_store.js';
|
|
37
53
|
|
|
38
54
|
export const UPSTREAM_REPO = 'event4u-app/agent-config';
|
|
39
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Per-step ceiling for every network-touching egress step. A hung `git push`
|
|
58
|
+
* or `gh` call degrades down the ladder instead of hanging the release.
|
|
59
|
+
*/
|
|
60
|
+
export const EGRESS_STEP_TIMEOUT_MS = 30_000;
|
|
61
|
+
|
|
40
62
|
const EXIT_OK = 0;
|
|
41
63
|
const EXIT_FAIL = 1;
|
|
42
64
|
|
|
@@ -44,12 +66,30 @@ const EXIT_FAIL = 1;
|
|
|
44
66
|
export interface Probe {
|
|
45
67
|
agentConfigCheckout: string | null;
|
|
46
68
|
ghAuthenticated: boolean;
|
|
47
|
-
|
|
69
|
+
canPushUpstream: boolean;
|
|
70
|
+
canFork: boolean;
|
|
48
71
|
}
|
|
49
72
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
73
|
+
export interface RunResult {
|
|
74
|
+
ok: boolean;
|
|
75
|
+
out: string;
|
|
76
|
+
timedOut: boolean;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Injectable command runner — tests script it, production shells out. */
|
|
80
|
+
export type Runner = (cmd: string, args: string[], cwd?: string) => RunResult;
|
|
81
|
+
|
|
82
|
+
function run(cmd: string, args: string[], cwd?: string): RunResult {
|
|
83
|
+
const r = spawnSync(cmd, args, { cwd, encoding: 'utf-8', timeout: EGRESS_STEP_TIMEOUT_MS });
|
|
84
|
+
// Only spawnSync's own ETIMEDOUT counts: a child killed externally by
|
|
85
|
+
// SIGTERM is a failure, not a timeout, and recording it as "timed out"
|
|
86
|
+
// points the next debugging session at the wrong cause (R2 finding #3).
|
|
87
|
+
const timedOut = (r.error as NodeJS.ErrnoException | undefined)?.code === 'ETIMEDOUT';
|
|
88
|
+
return {
|
|
89
|
+
ok: r.status === 0 && !timedOut,
|
|
90
|
+
out: `${r.stdout ?? ''}${r.stderr ?? ''}`,
|
|
91
|
+
timedOut,
|
|
92
|
+
};
|
|
53
93
|
}
|
|
54
94
|
|
|
55
95
|
/**
|
|
@@ -71,18 +111,63 @@ export function findAgentConfigCheckout(env: NodeJS.ProcessEnv, cwd: string): st
|
|
|
71
111
|
return null;
|
|
72
112
|
}
|
|
73
113
|
|
|
74
|
-
|
|
114
|
+
/**
|
|
115
|
+
* Pre-flight probe of ACTUAL push rights, not remote existence. `git remote`
|
|
116
|
+
* returning a name proves only that the repo was cloned — every consumer of
|
|
117
|
+
* the public repo has that and no write access. `gh api repos/<repo>` returns
|
|
118
|
+
* the authenticated viewer's `permissions.push` and the repo's `allow_forking`,
|
|
119
|
+
* which are the two facts the route actually depends on. An unreachable or
|
|
120
|
+
* unauthenticated API answers `false` for both — the ladder then degrades to
|
|
121
|
+
* an issue rather than failing at push time.
|
|
122
|
+
*/
|
|
123
|
+
export function probeUpstreamRights(
|
|
124
|
+
runner: Runner,
|
|
125
|
+
repo: string,
|
|
126
|
+
): { canPushUpstream: boolean; canFork: boolean } {
|
|
127
|
+
const r = runner('gh', ['api', `repos/${repo}`]);
|
|
128
|
+
if (!r.ok) {
|
|
129
|
+
return { canPushUpstream: false, canFork: false };
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
const parsed: unknown = JSON.parse(r.out);
|
|
133
|
+
const meta = parsed as {
|
|
134
|
+
permissions?: { push?: boolean };
|
|
135
|
+
allow_forking?: boolean;
|
|
136
|
+
};
|
|
137
|
+
return {
|
|
138
|
+
canPushUpstream: meta.permissions?.push === true,
|
|
139
|
+
canFork: meta.allow_forking !== false,
|
|
140
|
+
};
|
|
141
|
+
} catch {
|
|
142
|
+
return { canPushUpstream: false, canFork: false };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function probeMachine(
|
|
147
|
+
env: NodeJS.ProcessEnv,
|
|
148
|
+
cwd: string,
|
|
149
|
+
runner: Runner = run,
|
|
150
|
+
repo: string = UPSTREAM_REPO,
|
|
151
|
+
): Probe {
|
|
75
152
|
const checkout = findAgentConfigCheckout(env, cwd);
|
|
76
|
-
const gh =
|
|
77
|
-
const
|
|
78
|
-
|
|
153
|
+
const gh = runner('gh', ['auth', 'status']);
|
|
154
|
+
const rights = gh.ok
|
|
155
|
+
? probeUpstreamRights(runner, repo)
|
|
156
|
+
: { canPushUpstream: false, canFork: false };
|
|
157
|
+
return {
|
|
158
|
+
agentConfigCheckout: checkout,
|
|
159
|
+
ghAuthenticated: gh.ok,
|
|
160
|
+
canPushUpstream: rights.canPushUpstream,
|
|
161
|
+
canFork: gh.ok && rights.canFork,
|
|
162
|
+
};
|
|
79
163
|
}
|
|
80
164
|
|
|
81
165
|
export function capabilityOf(probe: Probe): EgressCapability {
|
|
82
166
|
return {
|
|
83
167
|
hasAgentConfigCheckout: probe.agentConfigCheckout !== null,
|
|
84
168
|
ghAuthenticated: probe.ghAuthenticated,
|
|
85
|
-
|
|
169
|
+
canPushUpstream: probe.canPushUpstream,
|
|
170
|
+
canFork: probe.canFork,
|
|
86
171
|
};
|
|
87
172
|
}
|
|
88
173
|
|
|
@@ -93,17 +178,226 @@ export function titleFor(record: DefectRecord): string {
|
|
|
93
178
|
|
|
94
179
|
export interface ReleasePlan {
|
|
95
180
|
route: EgressRoute;
|
|
181
|
+
pushVia: PushVia | null;
|
|
96
182
|
blocked: string | null;
|
|
183
|
+
/**
|
|
184
|
+
* Non-null when the record targets a may-not-modify surface (kernel rules,
|
|
185
|
+
* safety floors, settings class C, CI enforcement, self-repair's own
|
|
186
|
+
* policy/code): the patch/PR path is refused with this reason and the
|
|
187
|
+
* route degrades to report-only. Detection and the issue path stay open.
|
|
188
|
+
*/
|
|
189
|
+
patch_denied: string | null;
|
|
97
190
|
title: string;
|
|
98
191
|
body: string;
|
|
99
192
|
}
|
|
100
193
|
|
|
194
|
+
/**
|
|
195
|
+
* Class-C settings keys from the contract in the agent-config checkout, for
|
|
196
|
+
* the deny-list's dynamic key match. `null` when there is no checkout or the
|
|
197
|
+
* contract is unreadable — the deny-list's static path matches still apply.
|
|
198
|
+
*/
|
|
199
|
+
export function readClassCKeys(checkout: string | null): ReadonlySet<string> | null {
|
|
200
|
+
if (checkout === null) {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
const md = fs.readFileSync(
|
|
205
|
+
path.join(checkout, 'docs', 'contracts', 'settings-classes.md'),
|
|
206
|
+
'utf-8',
|
|
207
|
+
);
|
|
208
|
+
const index = buildSettingsClassIndex(parseSettingsClassRows(md));
|
|
209
|
+
return new Set([...index].filter(([, cls]) => cls === 'C').map(([key]) => key));
|
|
210
|
+
} catch {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
101
215
|
/** Pure: everything `release` decides before it touches the network. */
|
|
102
|
-
export function planRelease(
|
|
216
|
+
export function planRelease(
|
|
217
|
+
record: DefectRecord,
|
|
218
|
+
probe: Probe,
|
|
219
|
+
repoRoot: string,
|
|
220
|
+
opts?: { classCKeys?: ReadonlySet<string> | null },
|
|
221
|
+
): ReleasePlan {
|
|
103
222
|
const blocked = egressBlockedReason(record, repoRoot);
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
223
|
+
const patch_denied = blocked === null ? patchDeniedReason(record, opts) : null;
|
|
224
|
+
let choice: EgressChoice =
|
|
225
|
+
blocked !== null
|
|
226
|
+
? { route: 'local-only' as const, pushVia: null }
|
|
227
|
+
: chooseEgress(capabilityOf(probe));
|
|
228
|
+
if (patch_denied !== null && choice.route === 'pull-request') {
|
|
229
|
+
// chooseEgress only returns pull-request when gh is authenticated, so
|
|
230
|
+
// report-only degrades to an issue, never to a dropped record.
|
|
231
|
+
choice = { route: 'issue', pushVia: null };
|
|
232
|
+
}
|
|
233
|
+
return {
|
|
234
|
+
route: choice.route,
|
|
235
|
+
pushVia: choice.pushVia,
|
|
236
|
+
blocked,
|
|
237
|
+
patch_denied,
|
|
238
|
+
title: titleFor(record),
|
|
239
|
+
body: renderReport(record, choice.route),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ── egress ladder execution ────────────────────────────────────────
|
|
244
|
+
|
|
245
|
+
/** One egress step's outcome — kept for the record when the ladder exhausts. */
|
|
246
|
+
export interface EgressAttempt {
|
|
247
|
+
step: string;
|
|
248
|
+
ok: boolean;
|
|
249
|
+
detail: string;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export interface ReleaseOutcome {
|
|
253
|
+
/** What actually got published, or null when the whole ladder failed. */
|
|
254
|
+
published: 'pull-request' | 'issue' | null;
|
|
255
|
+
attempts: EgressAttempt[];
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export interface ReleaseDeps {
|
|
259
|
+
runner: Runner;
|
|
260
|
+
now: () => string;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const FORK_REMOTE = 'self-repair-fork';
|
|
264
|
+
|
|
265
|
+
function failDetail(step: string, r: RunResult): string {
|
|
266
|
+
return r.timedOut
|
|
267
|
+
? `${step}: timed out after ${EGRESS_STEP_TIMEOUT_MS / 1000}s`
|
|
268
|
+
: `${step}: ${r.out.trim() || 'failed'}`;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Push the fix branch — directly, or via fork → cross-repo PR — then open the
|
|
273
|
+
* PR. Returns null on success; on any step failure or timeout returns the
|
|
274
|
+
* failed attempt so the caller degrades down the ladder.
|
|
275
|
+
*/
|
|
276
|
+
function tryPullRequest(
|
|
277
|
+
plan: ReleasePlan,
|
|
278
|
+
probe: Probe,
|
|
279
|
+
repo: string,
|
|
280
|
+
branch: string,
|
|
281
|
+
via: PushVia,
|
|
282
|
+
deps: ReleaseDeps,
|
|
283
|
+
): EgressAttempt | null {
|
|
284
|
+
const checkout = probe.agentConfigCheckout!;
|
|
285
|
+
let prHead: string | null = null;
|
|
286
|
+
|
|
287
|
+
if (via === 'upstream') {
|
|
288
|
+
const pushed = deps.runner('git', ['push', '-u', 'origin', branch], checkout);
|
|
289
|
+
if (!pushed.ok) {
|
|
290
|
+
return { step: 'push-upstream', ok: false, detail: failDetail('push-upstream', pushed) };
|
|
291
|
+
}
|
|
292
|
+
} else {
|
|
293
|
+
const forked = deps.runner('gh', ['repo', 'fork', repo, '--clone=false']);
|
|
294
|
+
if (!forked.ok) {
|
|
295
|
+
return { step: 'fork-ensure', ok: false, detail: failDetail('fork-ensure', forked) };
|
|
296
|
+
}
|
|
297
|
+
const who = deps.runner('gh', ['api', 'user', '--jq', '.login']);
|
|
298
|
+
if (!who.ok || who.out.trim().length === 0) {
|
|
299
|
+
return { step: 'whoami', ok: false, detail: failDetail('whoami', who) };
|
|
300
|
+
}
|
|
301
|
+
const login = who.out.trim();
|
|
302
|
+
const repoName = repo.split('/')[1] ?? repo;
|
|
303
|
+
const hasRemote = deps.runner('git', ['remote', 'get-url', FORK_REMOTE], checkout);
|
|
304
|
+
if (!hasRemote.ok) {
|
|
305
|
+
const added = deps.runner(
|
|
306
|
+
'git',
|
|
307
|
+
['remote', 'add', FORK_REMOTE, `https://github.com/${login}/${repoName}.git`],
|
|
308
|
+
checkout,
|
|
309
|
+
);
|
|
310
|
+
if (!added.ok) {
|
|
311
|
+
return { step: 'fork-remote', ok: false, detail: failDetail('fork-remote', added) };
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
const pushed = deps.runner('git', ['push', '-u', FORK_REMOTE, branch], checkout);
|
|
315
|
+
if (!pushed.ok) {
|
|
316
|
+
return { step: 'push-fork', ok: false, detail: failDetail('push-fork', pushed) };
|
|
317
|
+
}
|
|
318
|
+
prHead = `${login}:${branch}`;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const prArgs = ['pr', 'create', '--repo', repo, '--title', plan.title, '--body', plan.body];
|
|
322
|
+
if (prHead !== null) {
|
|
323
|
+
prArgs.push('--head', prHead);
|
|
324
|
+
}
|
|
325
|
+
const pr = deps.runner('gh', prArgs, checkout);
|
|
326
|
+
if (!pr.ok) {
|
|
327
|
+
return { step: 'pr-create', ok: false, detail: failDetail('pr-create', pr) };
|
|
328
|
+
}
|
|
329
|
+
process.stdout.write(`${pr.out}`);
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* The egress ladder, executed within ONE `release` call: direct push → fork
|
|
335
|
+
* push → issue → open-with-errors. Every rung is separately timed out; a rung
|
|
336
|
+
* that fails degrades to the next instead of aborting the release. When the
|
|
337
|
+
* whole ladder fails, the record stays `open` with every failed attempt
|
|
338
|
+
* attached (sanitized — command output can carry local paths).
|
|
339
|
+
*/
|
|
340
|
+
export function executeRelease(
|
|
341
|
+
record: DefectRecord,
|
|
342
|
+
plan: ReleasePlan,
|
|
343
|
+
probe: Probe,
|
|
344
|
+
repo: string,
|
|
345
|
+
deps: ReleaseDeps,
|
|
346
|
+
): ReleaseOutcome {
|
|
347
|
+
const attempts: EgressAttempt[] = [];
|
|
348
|
+
|
|
349
|
+
if (plan.route === 'pull-request') {
|
|
350
|
+
const checkout = probe.agentConfigCheckout!;
|
|
351
|
+
const branch = deps.runner('git', ['branch', '--show-current'], checkout).out.trim();
|
|
352
|
+
if (!branch || branch === 'main') {
|
|
353
|
+
// Precondition, not an egress failure: the authored fix exists and
|
|
354
|
+
// must ride its own branch — an issue instead would drop the fix.
|
|
355
|
+
attempts.push({
|
|
356
|
+
step: 'branch-check',
|
|
357
|
+
ok: false,
|
|
358
|
+
detail:
|
|
359
|
+
'the fix must sit on its own branch in the agent-config checkout ' +
|
|
360
|
+
`(currently: ${branch || 'detached'})`,
|
|
361
|
+
});
|
|
362
|
+
return { published: null, attempts };
|
|
363
|
+
}
|
|
364
|
+
const ladder: PushVia[] =
|
|
365
|
+
plan.pushVia === 'upstream' && probe.canFork
|
|
366
|
+
? ['upstream', 'fork']
|
|
367
|
+
: [plan.pushVia ?? 'upstream'];
|
|
368
|
+
for (const via of ladder) {
|
|
369
|
+
const failed = tryPullRequest(plan, probe, repo, branch, via, deps);
|
|
370
|
+
if (failed === null) {
|
|
371
|
+
return { published: 'pull-request', attempts };
|
|
372
|
+
}
|
|
373
|
+
attempts.push(failed);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (plan.route === 'pull-request' || plan.route === 'issue') {
|
|
378
|
+
// Re-render for the rung actually taken: a plan that degraded from
|
|
379
|
+
// pull-request must not file an issue whose body claims
|
|
380
|
+
// `Route: pull-request` (R2 finding #4).
|
|
381
|
+
const issueBody =
|
|
382
|
+
plan.route === 'issue' ? plan.body : renderReport(record, 'issue');
|
|
383
|
+
const issue = deps.runner('gh', [
|
|
384
|
+
'issue',
|
|
385
|
+
'create',
|
|
386
|
+
'--repo',
|
|
387
|
+
repo,
|
|
388
|
+
'--title',
|
|
389
|
+
plan.title,
|
|
390
|
+
'--body',
|
|
391
|
+
issueBody,
|
|
392
|
+
]);
|
|
393
|
+
if (issue.ok) {
|
|
394
|
+
process.stdout.write(`${issue.out}`);
|
|
395
|
+
return { published: 'issue', attempts };
|
|
396
|
+
}
|
|
397
|
+
attempts.push({ step: 'issue-create', ok: false, detail: failDetail('issue-create', issue) });
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return { published: null, attempts };
|
|
107
401
|
}
|
|
108
402
|
|
|
109
403
|
function statusCmd(root: string): number {
|
|
@@ -140,12 +434,18 @@ function releaseCmd(root: string, argv: string[]): number {
|
|
|
140
434
|
const repoArg = argv.indexOf('--repo');
|
|
141
435
|
const repo = repoArg >= 0 ? (argv[repoArg + 1] ?? UPSTREAM_REPO) : UPSTREAM_REPO;
|
|
142
436
|
const probe = probeMachine(process.env, process.cwd());
|
|
143
|
-
const plan = planRelease(record, probe, root
|
|
437
|
+
const plan = planRelease(record, probe, root, {
|
|
438
|
+
classCKeys: readClassCKeys(probe.agentConfigCheckout),
|
|
439
|
+
});
|
|
144
440
|
|
|
145
|
-
|
|
441
|
+
const via = plan.pushVia !== null ? ` via=${plan.pushVia}` : '';
|
|
442
|
+
process.stdout.write(`self-repair:release ${fp} -> route=${plan.route}${via}\n`);
|
|
146
443
|
if (plan.blocked !== null) {
|
|
147
444
|
process.stdout.write(` WARN ${plan.blocked}\n`);
|
|
148
445
|
}
|
|
446
|
+
if (plan.patch_denied !== null) {
|
|
447
|
+
process.stdout.write(` DENY ${plan.patch_denied}\n`);
|
|
448
|
+
}
|
|
149
449
|
if (dryRun) {
|
|
150
450
|
process.stdout.write(`\n--- ${plan.title} ---\n${plan.body}\n`);
|
|
151
451
|
return EXIT_OK;
|
|
@@ -156,47 +456,25 @@ function releaseCmd(root: string, argv: string[]): number {
|
|
|
156
456
|
);
|
|
157
457
|
return EXIT_OK;
|
|
158
458
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (!r.ok) {
|
|
172
|
-
return EXIT_FAIL;
|
|
173
|
-
}
|
|
174
|
-
} else {
|
|
175
|
-
const checkout = probe.agentConfigCheckout!;
|
|
176
|
-
const branch = run('git', ['branch', '--show-current'], checkout).out.trim();
|
|
177
|
-
if (!branch || branch === 'main') {
|
|
178
|
-
process.stderr.write(
|
|
179
|
-
' The fix must sit on its own branch in the agent-config checkout ' +
|
|
180
|
-
`(currently: ${branch || 'detached'}).\n`,
|
|
181
|
-
);
|
|
182
|
-
return EXIT_FAIL;
|
|
183
|
-
}
|
|
184
|
-
const pushed = run('git', ['push', '-u', 'origin', branch], checkout);
|
|
185
|
-
if (!pushed.ok) {
|
|
186
|
-
process.stderr.write(`${pushed.out}\n`);
|
|
187
|
-
return EXIT_FAIL;
|
|
188
|
-
}
|
|
189
|
-
const pr = run(
|
|
190
|
-
'gh',
|
|
191
|
-
['pr', 'create', '--repo', repo, '--title', plan.title, '--body', plan.body],
|
|
192
|
-
checkout,
|
|
459
|
+
|
|
460
|
+
const deps: ReleaseDeps = { runner: run, now: () => new Date().toISOString() };
|
|
461
|
+
const outcome = executeRelease(record, plan, probe, repo, deps);
|
|
462
|
+
|
|
463
|
+
for (const a of outcome.attempts) {
|
|
464
|
+
process.stderr.write(` FAIL ${a.detail}\n`);
|
|
465
|
+
}
|
|
466
|
+
if (outcome.published === null) {
|
|
467
|
+
const errors = outcome.attempts.map((a) => sanitizeEvidence(a.detail));
|
|
468
|
+
attachReleaseErrors(root, fp, errors, deps.now());
|
|
469
|
+
process.stderr.write(
|
|
470
|
+
' The egress ladder is exhausted; the record stays open with the errors attached.\n',
|
|
193
471
|
);
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
472
|
+
return EXIT_FAIL;
|
|
473
|
+
}
|
|
474
|
+
if (outcome.published !== plan.route) {
|
|
475
|
+
process.stdout.write(` Degraded to ${outcome.published} after failed attempt(s).\n`);
|
|
198
476
|
}
|
|
199
|
-
markReleased(root, fp,
|
|
477
|
+
markReleased(root, fp, deps.now());
|
|
200
478
|
return EXIT_OK;
|
|
201
479
|
}
|
|
202
480
|
|