@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/AGENTS.md +13 -12
- package/CHANGELOG.md +165 -0
- package/README.md +1 -1
- package/agents/templates/.ai-council.yml.example +55 -24
- package/dist/agent-src/commands/analyze/conformance.md +24 -0
- package/dist/agent-src/commands/cost/report.md +2 -2
- package/dist/agent-src/commands/team/adversarial.md +8 -7
- package/dist/agent-src/commands/team/delegate.md +13 -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 +28 -3
- package/dist/agent-src/contexts/execution/subagent-response-contract.md +54 -0
- 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/artifact-engagement-recording.md +4 -1
- package/dist/agent-src/rules/autonomous-execution.md +1 -1
- package/dist/agent-src/rules/brand-source-of-truth.md +33 -6
- package/dist/agent-src/rules/context-hygiene.md +3 -1
- package/dist/agent-src/rules/council-availability.md +93 -0
- package/dist/agent-src/rules/cross-source-consistency.md +3 -0
- package/dist/agent-src/rules/delegation-policy.md +38 -10
- package/dist/agent-src/rules/design-fidelity.md +3 -0
- package/dist/agent-src/rules/evaluator-independence.md +2 -1
- package/dist/agent-src/rules/external-code-graph-interop.md +2 -1
- package/dist/agent-src/rules/finance-safety-floor.md +1 -1
- package/dist/agent-src/rules/git-history-discipline.md +1 -1
- package/dist/agent-src/rules/history-discipline.md +3 -1
- package/dist/agent-src/rules/legal-safety-floor.md +1 -1
- package/dist/agent-src/rules/no-pr-progress-comments.md +3 -1
- package/dist/agent-src/rules/onboarding-gate.md +3 -1
- package/dist/agent-src/rules/roadmap-ci-steps-policy.md +4 -1
- package/dist/agent-src/rules/role-mode-adherence.md +1 -1
- package/dist/agent-src/rules/scale-discipline.md +3 -1
- package/dist/agent-src/rules/self-repair-loop.md +103 -0
- package/dist/agent-src/rules/session-canary.md +2 -1
- package/dist/agent-src/rules/settings-ask-protocol.md +8 -7
- package/dist/agent-src/rules/strategy-safety-floor.md +1 -1
- package/dist/agent-src/rules/token-budget-discipline.md +4 -2
- package/dist/agent-src/rules/tool-safety.md +5 -1
- package/dist/agent-src/skills/adversarial-review/SKILL.md +1 -1
- package/dist/agent-src/skills/analysis-autonomous-mode/SKILL.md +1 -1
- 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/performance-analysis/SKILL.md +1 -1
- package/dist/agent-src/skills/persona-improvement/SKILL.md +1 -1
- package/dist/agent-src/skills/premortem/SKILL.md +30 -1
- package/dist/agent-src/skills/project-analyzer/SKILL.md +1 -1
- package/dist/agent-src/skills/reasoning-orchestrator/SKILL.md +2 -1
- package/dist/agent-src/skills/security-audit/SKILL.md +1 -1
- package/dist/agent-src/skills/sequential-thinking/SKILL.md +1 -1
- package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +1 -1
- package/dist/agent-src/skills/subagent-orchestration/SKILL.md +7 -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/skills/universal-project-analysis/SKILL.md +1 -1
- package/dist/agent-src/templates/AGENTS.md +8 -12
- 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 +7 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/chunk-66OVWGB5.js +270 -0
- package/dist/cli-delegate/{chunk-HJ6DAJHR.js → chunk-LCFJIQ4J.js} +89 -7
- package/dist/cli-delegate/{chunk-VSYT7OHO.js → chunk-LVRYBKTI.js} +31 -2
- package/dist/cli-delegate/{chunk-DVRS2KVR.js → chunk-MMXA5GY7.js} +10 -17
- package/dist/cli-delegate/{chunk-WR6GTRHJ.js → chunk-MP3JT3IU.js} +3 -3
- package/dist/cli-delegate/{chunk-3KJIMFTB.js → chunk-NZ5N2XQL.js} +467 -305
- package/dist/cli-delegate/{chunk-AIIUU73N.js → chunk-OJM3MBJM.js} +1 -1
- package/dist/cli-delegate/{chunk-BCPIOAGV.js → chunk-RCIA67XN.js} +117 -96
- package/dist/cli-delegate/chunk-REKCMYOU.js +2991 -0
- package/dist/cli-delegate/cmd_analyze_session.js +1 -1
- package/dist/cli-delegate/cmd_brand_status.js +134 -0
- package/dist/cli-delegate/cmd_conformance.js +10 -47
- package/dist/cli-delegate/cmd_converge.js +3 -3
- package/dist/cli-delegate/cmd_doctor.js +10 -6
- package/dist/cli-delegate/cmd_explain.js +80 -330
- package/dist/cli-delegate/cmd_export.js +6 -6
- package/dist/cli-delegate/cmd_fleet.js +3 -3
- package/dist/cli-delegate/cmd_handoff.js +3 -3
- package/dist/cli-delegate/cmd_mcp_available.js +433 -0
- package/dist/cli-delegate/cmd_migrate.js +3 -3
- package/dist/cli-delegate/cmd_packs_active.js +153 -0
- package/dist/cli-delegate/cmd_preflight.js +1 -1
- package/dist/cli-delegate/cmd_prune.js +6 -6
- package/dist/cli-delegate/cmd_route_audit.js +8 -8
- package/dist/cli-delegate/cmd_route_explain.js +4 -4
- package/dist/cli-delegate/cmd_settings_get.js +266 -0
- package/dist/cli-delegate/cmd_settings_migrate.js +6 -6
- package/dist/cli-delegate/cmd_settings_set.js +8 -2968
- package/dist/cli-delegate/cmd_sync.js +6 -6
- package/dist/cli-delegate/cmd_uninstall.js +6 -6
- package/dist/cli-delegate/cmd_update.js +4 -4
- package/dist/cli-delegate/cmd_upgrade.js +4 -4
- package/dist/cli-delegate/cmd_validate.js +6 -6
- package/dist/cli-delegate/cmd_versions.js +4 -4
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +164 -55
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +4 -3
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +44 -5
- package/dist/discovery/trust-report.md +11 -11
- package/dist/discovery/workspaces.json +16 -6
- package/dist/hooks/dispatch.js +9341 -6131
- package/dist/install/install.mjs +739 -524
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +777 -281
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/agent_settings.js +134 -15
- 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 +6 -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-DmR0mbk4.js → index-JDAF00bC.js} +2 -2
- package/dist/ui/assets/{index-DmR0mbk4.js.map → index-JDAF00bC.js.map} +1 -1
- package/dist/ui/index.html +1 -1
- package/docs/contracts/settings-classes.md +68 -17
- package/docs/guidelines/agent-infra/memory-access.md +14 -3
- package/package.json +1 -1
- package/src/agent-src/templates/AGENTS.md +8 -12
- package/src/config/agent-settings.template.yml +52 -66
- package/src/config/budgets.yml +29 -0
- package/src/config/discovery/packs.yml +11 -0
- package/src/config/discovery/workspaces.yml +1 -1
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-coverage.yml +26 -0
- package/src/config/gate-violation-baselines.json +3 -3
- package/src/scripts/_cli/cmd_brand_status.ts +174 -0
- package/src/scripts/_cli/cmd_conformance.ts +14 -42
- package/src/scripts/_cli/cmd_doctor.ts +65 -48
- package/src/scripts/_cli/cmd_mcp_available.ts +266 -0
- package/src/scripts/_cli/cmd_packs_active.ts +225 -0
- package/src/scripts/_cli/cmd_settings_get.ts +324 -0
- package/src/scripts/_dispatch.bash +57 -0
- package/src/scripts/_lib/agent_settings.ts +140 -16
- 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/carrier_divergence.ts +135 -0
- package/src/scripts/_lib/host_capability.ts +144 -4
- package/src/scripts/_lib/judgment_ladder.ts +382 -0
- package/src/scripts/_lib/orchestration_gate.ts +8 -0
- package/src/scripts/_lib/orchestration_record.ts +37 -0
- package/src/scripts/_lib/orchestration_savings.ts +1 -1
- package/src/scripts/_lib/prompt_shape.ts +88 -0
- package/src/scripts/_lib/review_skipped_record.ts +102 -0
- package/src/scripts/_lib/rule_layer_overlap.ts +254 -0
- package/src/scripts/_lib/self_repair.ts +489 -0
- package/src/scripts/_lib/self_repair_store.ts +128 -0
- package/src/scripts/_lib/subagent_capsule.ts +182 -0
- package/src/scripts/_lib/subagent_response.ts +19 -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/transcript_entry.ts +80 -0
- package/src/scripts/_lib/worker_budget.ts +37 -2
- package/src/scripts/ai_council/clients.ts +76 -54
- 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/bench_critic_protocol.ts +396 -0
- package/src/scripts/check_branch_freshness.ts +255 -0
- package/src/scripts/check_council_config_location.ts +125 -1
- package/src/scripts/check_detector_corpus.ts +202 -0
- package/src/scripts/check_hook_bundle_freshness.ts +4 -1
- package/src/scripts/check_standing_rule_delivery.ts +314 -0
- package/src/scripts/condense.ts +162 -2
- package/src/scripts/config/profiles.ts +13 -3
- package/src/scripts/conformance_scan.ts +272 -24
- package/src/scripts/council_availability_hook.ts +188 -0
- package/src/scripts/council_cli.ts +527 -32
- package/src/scripts/forensics_report.ts +333 -0
- package/src/scripts/hook_manifest.yaml +107 -26
- package/src/scripts/hooks/block_no_verify.ts +39 -2
- package/src/scripts/hooks/block_unauthorized_git.ts +156 -7
- package/src/scripts/hooks/concern_registry.ts +10 -0
- package/src/scripts/hooks/delegation_nudge_hook.ts +484 -0
- package/src/scripts/hooks/end_review_nudge_hook.ts +726 -0
- package/src/scripts/hooks/orchestration_record_hook.ts +342 -0
- package/src/scripts/install.ts +151 -0
- package/src/scripts/language_mirror_hook.ts +30 -37
- package/src/scripts/lint_discovery_vocabulary.ts +2 -0
- package/src/scripts/lint_never_silent.ts +276 -0
- package/src/scripts/lint_no_activation_gates.ts +271 -0
- package/src/scripts/measure_scope_dedup.ts +19 -13
- package/src/scripts/onboarding_gate_hook.ts +40 -1
- package/src/scripts/orchestration_record.ts +12 -1
- package/src/scripts/preamble_byte_census.ts +9 -20
- package/src/scripts/rebuild_hook_bundle.ts +177 -0
- package/src/scripts/release.ts +74 -2
- package/src/scripts/report_carrier_divergence.ts +364 -0
- package/src/scripts/report_skill_activation.ts +257 -0
- package/src/scripts/report_skill_obligation_violations.ts +579 -0
- package/src/scripts/routing_doctor.ts +95 -39
- package/src/scripts/score_skill_selection.ts +19 -5
- package/src/scripts/self_repair_cli.ts +470 -0
- package/src/scripts/self_repair_hook.ts +241 -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 +10 -17
- package/src/shared/settingsCarveOut.ts +0 -10
- 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
|
+
}
|