@event4u/agent-config 9.26.0 → 9.28.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 +12 -11
- package/CHANGELOG.md +134 -207
- package/README.md +1 -1
- package/dist/agent-src/commands/analyze/conformance.md +43 -0
- package/dist/agent-src/commands/roadmap/next.md +65 -3
- package/dist/agent-src/contexts/execution/orchestration-telemetry.md +10 -3
- package/dist/agent-src/rules/active-remediation.md +2 -0
- package/dist/agent-src/rules/analysis-skill-routing.md +2 -0
- package/dist/agent-src/rules/architecture.md +2 -0
- package/dist/agent-src/rules/artifact-drafting-protocol.md +2 -0
- package/dist/agent-src/rules/artifact-engagement-recording.md +6 -1
- package/dist/agent-src/rules/augment-edit-discipline.md +2 -0
- package/dist/agent-src/rules/autonomous-execution.md +3 -1
- package/dist/agent-src/rules/brand-consistency.md +2 -0
- package/dist/agent-src/rules/brand-source-of-truth.md +35 -6
- package/dist/agent-src/rules/broken-access-control.md +2 -0
- package/dist/agent-src/rules/cli-output-handling.md +2 -0
- package/dist/agent-src/rules/code-comment-discipline.md +2 -0
- package/dist/agent-src/rules/code-provenance.md +2 -0
- package/dist/agent-src/rules/command-suggestion-policy.md +2 -0
- package/dist/agent-src/rules/commit-conventions.md +2 -0
- package/dist/agent-src/rules/communication-through-line.md +2 -0
- package/dist/agent-src/rules/content-quoting-floor.md +2 -0
- package/dist/agent-src/rules/context-hygiene.md +13 -1
- package/dist/agent-src/rules/copilot-routing.md +2 -0
- package/dist/agent-src/rules/council-availability.md +93 -0
- package/dist/agent-src/rules/cross-source-consistency.md +5 -0
- package/dist/agent-src/rules/decision-revisit-gate.md +2 -0
- package/dist/agent-src/rules/delegation-policy.md +31 -5
- package/dist/agent-src/rules/design-fidelity.md +5 -0
- package/dist/agent-src/rules/design-review-after-ui-write.md +4 -0
- package/dist/agent-src/rules/devcontainer-routing.md +2 -0
- package/dist/agent-src/rules/doc-screenshot-hygiene.md +2 -0
- package/dist/agent-src/rules/docker-commands.md +2 -0
- package/dist/agent-src/rules/domain-adoption-policy.md +2 -0
- package/dist/agent-src/rules/domain-safety-disclaimer.md +2 -0
- package/dist/agent-src/rules/domain-safety-pii.md +2 -0
- package/dist/agent-src/rules/domain-safety-retention.md +2 -0
- package/dist/agent-src/rules/downstream-changes.md +2 -0
- package/dist/agent-src/rules/engineering-safety-floor.md +2 -0
- package/dist/agent-src/rules/evaluator-independence.md +14 -3
- package/dist/agent-src/rules/external-code-graph-interop.md +4 -1
- package/dist/agent-src/rules/external-reference-deep-dive.md +2 -0
- package/dist/agent-src/rules/fast-path-marker-visibility.md +2 -0
- package/dist/agent-src/rules/finance-safety-floor.md +3 -1
- package/dist/agent-src/rules/framework-neutrality-in-generic-skills.md +2 -0
- package/dist/agent-src/rules/git-history-discipline.md +3 -1
- package/dist/agent-src/rules/guidelines.md +2 -0
- package/dist/agent-src/rules/history-discipline.md +5 -1
- package/dist/agent-src/rules/icon-consistency.md +2 -0
- package/dist/agent-src/rules/image-likeness-and-rights.md +2 -0
- package/dist/agent-src/rules/improve-before-implement.md +2 -0
- package/dist/agent-src/rules/invite-challenge.md +2 -0
- package/dist/agent-src/rules/laravel-routing.md +2 -0
- package/dist/agent-src/rules/laravel-translations.md +2 -0
- package/dist/agent-src/rules/legal-safety-floor.md +3 -1
- package/dist/agent-src/rules/lethal-trifecta-guard.md +2 -0
- package/dist/agent-src/rules/linked-projects-onboarding-gate.md +2 -0
- package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +2 -0
- package/dist/agent-src/rules/markdown-safe-codeblocks.md +2 -0
- package/dist/agent-src/rules/media-governance-routing.md +2 -0
- package/dist/agent-src/rules/media-sync-ground-truth.md +2 -0
- package/dist/agent-src/rules/minimal-safe-diff.md +2 -0
- package/dist/agent-src/rules/missing-tool-handling.md +2 -0
- package/dist/agent-src/rules/model-recommendation.md +2 -0
- package/dist/agent-src/rules/no-attribution-footers.md +2 -0
- package/dist/agent-src/rules/no-decorative-emojis-in-git-surfaces.md +2 -0
- package/dist/agent-src/rules/no-pr-progress-comments.md +5 -1
- package/dist/agent-src/rules/no-roadmap-references.md +2 -0
- package/dist/agent-src/rules/notes-first-reasoning.md +2 -0
- package/dist/agent-src/rules/onboarding-gate.md +5 -1
- package/dist/agent-src/rules/output-discipline.md +2 -0
- package/dist/agent-src/rules/package-ci-checks.md +2 -0
- package/dist/agent-src/rules/persona-governance.md +2 -0
- package/dist/agent-src/rules/php-coding.md +2 -0
- package/dist/agent-src/rules/prefer-enums-over-literals.md +2 -0
- package/dist/agent-src/rules/preservation-guard.md +2 -0
- package/dist/agent-src/rules/provider-lifecycle-discipline.md +2 -0
- package/dist/agent-src/rules/question-not-instruction.md +2 -0
- package/dist/agent-src/rules/reviewer-awareness.md +2 -0
- package/dist/agent-src/rules/roadmap-ci-steps-policy.md +6 -1
- package/dist/agent-src/rules/roadmap-progress-sync.md +2 -0
- package/dist/agent-src/rules/role-mode-adherence.md +3 -1
- package/dist/agent-src/rules/rule-type-governance.md +2 -0
- package/dist/agent-src/rules/runtime-safety.md +2 -0
- package/dist/agent-src/rules/scale-discipline.md +5 -1
- package/dist/agent-src/rules/secret-vcs-guard.md +2 -0
- package/dist/agent-src/rules/security-sensitive-stop.md +2 -0
- package/dist/agent-src/rules/self-repair-loop.md +103 -0
- package/dist/agent-src/rules/senior-engineering-discipline.md +2 -0
- package/dist/agent-src/rules/session-canary.md +62 -19
- package/dist/agent-src/rules/settings-ask-protocol.md +4 -0
- package/dist/agent-src/rules/size-enforcement.md +2 -0
- package/dist/agent-src/rules/skill-improvement-trigger.md +2 -0
- package/dist/agent-src/rules/skill-quality.md +2 -0
- package/dist/agent-src/rules/slash-command-routing-policy.md +2 -0
- package/dist/agent-src/rules/source-confidentiality.md +2 -0
- package/dist/agent-src/rules/source-discovery-gate.md +2 -0
- package/dist/agent-src/rules/source-of-truth.md +2 -0
- package/dist/agent-src/rules/spreadsheet-source-quality.md +2 -0
- package/dist/agent-src/rules/strategy-safety-floor.md +3 -1
- package/dist/agent-src/rules/symfony-routing.md +2 -0
- package/dist/agent-src/rules/think-before-action.md +2 -0
- package/dist/agent-src/rules/token-budget-discipline.md +5 -1
- package/dist/agent-src/rules/token-efficiency.md +2 -0
- package/dist/agent-src/rules/token-optimizer-maintenance.md +2 -0
- package/dist/agent-src/rules/tool-safety.md +7 -1
- package/dist/agent-src/rules/ui-audit-gate.md +4 -0
- package/dist/agent-src/rules/untrusted-input-defense.md +2 -0
- package/dist/agent-src/rules/upstream-proposal.md +2 -0
- package/dist/agent-src/rules/user-interaction.md +2 -0
- package/dist/agent-src/rules/user-interrupt-priority.md +2 -0
- 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/performance-analysis/SKILL.md +1 -1
- package/dist/agent-src/skills/persona-improvement/SKILL.md +1 -1
- package/dist/agent-src/skills/project-analyzer/SKILL.md +1 -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/universal-project-analysis/SKILL.md +1 -1
- package/dist/agent-src/templates/AGENTS.md +6 -10
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli/registry.js +9 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/chunk-22GCIJ2O.js +270 -0
- package/dist/cli-delegate/{chunk-BSLUHV2F.js → chunk-2BE5I2MN.js} +156 -141
- package/dist/cli-delegate/{chunk-AIIUU73N.js → chunk-7OMQCH4V.js} +1 -1
- package/dist/cli-delegate/{chunk-HJ6DAJHR.js → chunk-GB2FSUEE.js} +57 -7
- package/dist/cli-delegate/{chunk-WR6GTRHJ.js → chunk-JXARCGVN.js} +3 -3
- package/dist/cli-delegate/{chunk-SJQMJI7M.js → chunk-PQSAQKHW.js} +467 -305
- 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 +7 -7
- package/dist/cli-delegate/cmd_converge.js +3 -3
- package/dist/cli-delegate/cmd_doctor.js +4 -4
- package/dist/cli-delegate/cmd_explain.js +80 -330
- package/dist/cli-delegate/cmd_export.js +5 -5
- package/dist/cli-delegate/cmd_fleet.js +3 -3
- package/dist/cli-delegate/cmd_handoff.js +2 -2
- 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 +5 -5
- 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 +272 -0
- package/dist/cli-delegate/cmd_settings_migrate.js +5 -5
- package/dist/cli-delegate/cmd_settings_set.js +7 -2967
- package/dist/cli-delegate/cmd_sync.js +5 -5
- package/dist/cli-delegate/cmd_uninstall.js +5 -5
- 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 +5 -5
- package/dist/cli-delegate/cmd_versions.js +4 -4
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +184 -124
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -3
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +7 -5
- package/dist/discovery/trust-report.md +11 -11
- package/dist/discovery/workspaces.json +6 -4
- package/dist/hooks/dispatch.js +9080 -5701
- package/dist/install/install.mjs +698 -508
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +698 -437
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/agent_settings.js +80 -15
- package/dist/scripts/_lib/agent_settings.js.map +1 -1
- package/dist/scripts/condense.js +33 -2
- package/dist/scripts/condense.js.map +1 -1
- package/dist/server/io/yamlIO.js +85 -0
- package/dist/server/io/yamlIO.js.map +1 -1
- package/dist/server/routes/settings.js +1 -12
- package/dist/server/routes/settings.js.map +1 -1
- package/dist/server/routes/wizard.js +52 -2
- package/dist/server/routes/wizard.js.map +1 -1
- package/dist/shared/settingsCarveOut.js +118 -0
- package/dist/shared/settingsCarveOut.js.map +1 -0
- package/dist/shared/settingsSurface.js +7 -1
- package/dist/shared/settingsSurface.js.map +1 -1
- package/dist/ui/assets/index-DmR0mbk4.js.map +1 -1
- package/docs/contracts/settings-classes.md +29 -8
- package/docs/guidelines/agent-infra/memory-access.md +14 -3
- package/package.json +1 -1
- package/src/agent-src/templates/AGENTS.md +6 -10
- package/src/config/budgets.yml +29 -0
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-coverage.yml +26 -0
- package/src/scripts/_cli/cmd_brand_status.ts +174 -0
- package/src/scripts/_cli/cmd_doctor.ts +6 -21
- 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 +75 -0
- package/src/scripts/_lib/agent_settings.ts +82 -16
- package/src/scripts/_lib/carrier_divergence.ts +135 -0
- package/src/scripts/_lib/git_common_dir.ts +128 -0
- package/src/scripts/_lib/host_capability.ts +84 -1
- package/src/scripts/_lib/obligation_frequency.ts +391 -0
- 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 +342 -0
- package/src/scripts/_lib/self_repair_store.ts +100 -0
- package/src/scripts/_lib/session_register.ts +361 -0
- package/src/scripts/_lib/transcript_entry.ts +80 -0
- package/src/scripts/check_branch_freshness.ts +255 -0
- package/src/scripts/check_council_config_location.ts +125 -1
- package/src/scripts/check_enforcement_coverage.ts +192 -7
- package/src/scripts/check_rule_projection_integrity.ts +364 -0
- package/src/scripts/check_standing_rule_delivery.ts +314 -0
- package/src/scripts/condense.ts +36 -2
- package/src/scripts/config/profiles.ts +13 -3
- package/src/scripts/conformance_scan.ts +307 -24
- package/src/scripts/council_availability_hook.ts +188 -0
- package/src/scripts/council_cli.ts +126 -7
- package/src/scripts/generate_settings_reference.ts +232 -0
- package/src/scripts/hook_manifest.yaml +155 -33
- package/src/scripts/hooks/block_no_verify.ts +95 -3
- package/src/scripts/hooks/block_unauthorized_git.ts +217 -2
- package/src/scripts/hooks/concern_registry.ts +12 -0
- package/src/scripts/hooks/delegation_nudge_hook.ts +450 -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 +49 -6
- package/src/scripts/measure_scope_dedup.ts +19 -13
- package/src/scripts/onboarding_gate_hook.ts +40 -1
- package/src/scripts/orchestration_backfill.ts +354 -0
- package/src/scripts/orchestration_record.ts +4 -1
- package/src/scripts/preamble_byte_census.ts +9 -20
- package/src/scripts/release.ts +142 -36
- 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 +77 -6
- package/src/scripts/schemas/rule.schema.json +5 -0
- package/src/scripts/self_repair_cli.ts +240 -0
- package/src/scripts/self_repair_hook.ts +241 -0
- package/src/scripts/session_canary_hook.ts +35 -3
- package/src/scripts/session_register_hook.ts +288 -0
- package/src/scripts/sessions_cli.ts +140 -0
- package/src/scripts/validate_frontmatter.ts +68 -0
- package/src/server/io/yamlIO.ts +87 -0
- package/src/shared/settingsCarveOut.ts +143 -0
- package/src/shared/settingsSurface.ts +7 -1
|
@@ -53,7 +53,7 @@ import {
|
|
|
53
53
|
import * as hooks_status from "./hooks_status.js";
|
|
54
54
|
import { load_agent_settings } from "./_lib/agent_settings.js";
|
|
55
55
|
import {
|
|
56
|
-
|
|
56
|
+
resolveHostCapabilities,
|
|
57
57
|
type HostCapabilityManifest,
|
|
58
58
|
} from "./_lib/host_capability.js";
|
|
59
59
|
import {
|
|
@@ -106,6 +106,19 @@ export interface OrchestrationReport {
|
|
|
106
106
|
/** cost.budgets.per_tier ceilings + live cool-down state per tier. */
|
|
107
107
|
tier_budgets: Record<string, { ceiling_usd: number | null; cooldown_until_ms: number }>;
|
|
108
108
|
host_manifest: HostCapabilityManifest;
|
|
109
|
+
/**
|
|
110
|
+
* The host identifier the capability registry was keyed on, and whether it
|
|
111
|
+
* was OBSERVED (given via `--platform`) or merely ASSUMED (the CLI default).
|
|
112
|
+
*
|
|
113
|
+
* The doctor has no host detection: `main()` defaults to `claude`. Once
|
|
114
|
+
* capability resolution keys on that string, an assumed platform produces a
|
|
115
|
+
* confident-looking `subagent_spawn` for a host the user may not be on —
|
|
116
|
+
* so the assumption is reported next to the value it decided, and a reader
|
|
117
|
+
* can falsify it. Without this pair the misdiagnosis is invisible in the
|
|
118
|
+
* output, which is worse than the pre-registry bug it replaced.
|
|
119
|
+
*/
|
|
120
|
+
host_platform: string;
|
|
121
|
+
host_platform_assumed: boolean;
|
|
109
122
|
/** Verdict of the real activation gate for a canonical delegable probe. */
|
|
110
123
|
activation: { action: string; reason: string };
|
|
111
124
|
cost_budgets: Record<string, unknown>;
|
|
@@ -194,18 +207,40 @@ export interface DoctorReport {
|
|
|
194
207
|
/**
|
|
195
208
|
* Orchestration-routing state: settings gates, host capability, budget
|
|
196
209
|
* inputs, and (optionally) a dry-run classification for a sample prompt.
|
|
197
|
-
* Read-only — mirrors
|
|
210
|
+
* Read-only — it mirrors what the delegation layer resolves FOR `platform`.
|
|
211
|
+
* That qualifier is load-bearing: the hook reads its platform off a real
|
|
212
|
+
* envelope, this function is told one, and the two agree only when the
|
|
213
|
+
* caller was told the truth (see `platform_assumed`).
|
|
214
|
+
*
|
|
215
|
+
* `platform` is the host identifier the committed capability registry is
|
|
216
|
+
* keyed on — the same string a hook envelope's `platform` field carries.
|
|
217
|
+
* It is REQUIRED, not optional: resolving through `normalizeHostManifest`
|
|
218
|
+
* alone (as this function did until the registry landed) skips the registry
|
|
219
|
+
* row entirely, so on a fresh clone with no `subagents.host_capabilities`
|
|
220
|
+
* override the doctor reported `subagent_spawn: false` while
|
|
221
|
+
* `delegation_nudge_hook` — which does call `resolveHostCapabilities` —
|
|
222
|
+
* reported `true`. Two readers of one fact disagreeing is worse than either
|
|
223
|
+
* answer, and the diagnostic is the one a user runs precisely to check the
|
|
224
|
+
* other. A required parameter makes that regression uncompilable; an
|
|
225
|
+
* optional one only discourages it in a comment, and a comment has never
|
|
226
|
+
* failed a build.
|
|
227
|
+
*
|
|
228
|
+
* `platform_assumed` says whether the caller OBSERVED the host or guessed
|
|
229
|
+
* it. It is not decoration: keying the registry on a guessed host is how
|
|
230
|
+
* this function would trade the bug it fixed for a mirrored one.
|
|
198
231
|
*/
|
|
199
232
|
export function collect_orchestration(
|
|
200
233
|
workspace_root: string,
|
|
201
234
|
sample_prompt: string | null,
|
|
235
|
+
platform: string,
|
|
236
|
+
platform_assumed: boolean,
|
|
202
237
|
): OrchestrationReport {
|
|
203
238
|
const settings = load_agent_settings({ cwd: workspace_root });
|
|
204
239
|
const sub = (settings["subagents"] ?? {}) as Record<string, unknown>;
|
|
205
240
|
const enabled = sub["enabled"] !== false;
|
|
206
241
|
const auto = typeof sub["auto"] === "string" ? (sub["auto"] as string) : "ask";
|
|
207
242
|
const downshift = sub["downshift"] !== false;
|
|
208
|
-
const host_manifest =
|
|
243
|
+
const host_manifest = resolveHostCapabilities(platform, sub["host_capabilities"]);
|
|
209
244
|
const activationInputs: ActivationInputs = {
|
|
210
245
|
enabled,
|
|
211
246
|
auto: (auto === "on" || auto === "off" ? auto : "ask") as ActivationInputs["auto"],
|
|
@@ -260,6 +295,8 @@ export function collect_orchestration(
|
|
|
260
295
|
budget_routing,
|
|
261
296
|
tier_budgets,
|
|
262
297
|
host_manifest,
|
|
298
|
+
host_platform: platform,
|
|
299
|
+
host_platform_assumed: platform_assumed,
|
|
263
300
|
activation: { action: probe.action, reason: probe.reason },
|
|
264
301
|
cost_budgets,
|
|
265
302
|
ledger_present,
|
|
@@ -410,6 +447,15 @@ function _freshness(no_freshness: boolean): FreshnessReport {
|
|
|
410
447
|
|
|
411
448
|
export function collect_report(options: {
|
|
412
449
|
platform: string;
|
|
450
|
+
/**
|
|
451
|
+
* True when `platform` is the CLI's fallback rather than a value the caller
|
|
452
|
+
* observed or passed. Optional so existing callers keep compiling, and
|
|
453
|
+
* defaulting to `true` on purpose: a caller that did not say where the
|
|
454
|
+
* platform came from has not established that it observed one, and the
|
|
455
|
+
* conservative reading is the one that shows the caveat rather than the one
|
|
456
|
+
* that hides it.
|
|
457
|
+
*/
|
|
458
|
+
platform_assumed?: boolean;
|
|
413
459
|
workspace_root: string;
|
|
414
460
|
no_freshness: boolean;
|
|
415
461
|
classify?: string | null;
|
|
@@ -440,7 +486,12 @@ export function collect_report(options: {
|
|
|
440
486
|
chain: chain.map((c) => c.name),
|
|
441
487
|
gates,
|
|
442
488
|
freshness: _freshness(options.no_freshness),
|
|
443
|
-
orchestration: collect_orchestration(
|
|
489
|
+
orchestration: collect_orchestration(
|
|
490
|
+
options.workspace_root,
|
|
491
|
+
options.classify ?? null,
|
|
492
|
+
options.platform,
|
|
493
|
+
options.platform_assumed ?? true,
|
|
494
|
+
),
|
|
444
495
|
bridge_table,
|
|
445
496
|
};
|
|
446
497
|
}
|
|
@@ -466,9 +517,18 @@ function _render(report: DoctorReport): string {
|
|
|
466
517
|
const o = report.orchestration;
|
|
467
518
|
lines.push(
|
|
468
519
|
`Orchestration: enabled=${o.enabled} · auto=${o.auto} · downshift=${o.downshift} · ` +
|
|
469
|
-
`budget_routing=${o.budget_routing} · subagent_spawn=${o.host_manifest.subagent_spawn}
|
|
520
|
+
`budget_routing=${o.budget_routing} · subagent_spawn=${o.host_manifest.subagent_spawn} ` +
|
|
521
|
+
`(host=${o.host_platform}${o.host_platform_assumed ? ", ASSUMED" : ", observed"}) · ` +
|
|
470
522
|
`ledger=${o.ledger_present ? "present" : "absent"}`,
|
|
471
523
|
);
|
|
524
|
+
if (o.host_platform_assumed) {
|
|
525
|
+
// Without this line the reader cannot tell a measured capability from one
|
|
526
|
+
// the registry produced for a host nobody checked they were on.
|
|
527
|
+
lines.push(
|
|
528
|
+
` ⚠️ host not detected — '${o.host_platform}' is this command's default, ` +
|
|
529
|
+
`and the capability registry is keyed on it. Pass --platform <host> to check another.`,
|
|
530
|
+
);
|
|
531
|
+
}
|
|
472
532
|
const tierBits = Object.entries(o.tier_budgets).map(([t, s]) => {
|
|
473
533
|
const cap = s.ceiling_usd === null ? "no cap" : `$${s.ceiling_usd}`;
|
|
474
534
|
const cool = s.cooldown_until_ms > 0 ? " COOLING" : "";
|
|
@@ -499,7 +559,11 @@ function _render(report: DoctorReport): string {
|
|
|
499
559
|
|
|
500
560
|
export function main(argv?: string[]): number {
|
|
501
561
|
const args = argv ?? process.argv.slice(2);
|
|
562
|
+
// The default is a guess, and since the capability registry is keyed on it
|
|
563
|
+
// the guess now decides `subagent_spawn`. Track where the value came from so
|
|
564
|
+
// the report can say so — there is no host detection here to replace it with.
|
|
502
565
|
let platform = "claude";
|
|
566
|
+
let platform_assumed = true;
|
|
503
567
|
let workspace_root = process.cwd();
|
|
504
568
|
let json = false;
|
|
505
569
|
let strict = false;
|
|
@@ -509,6 +573,7 @@ export function main(argv?: string[]): number {
|
|
|
509
573
|
const a = args[i];
|
|
510
574
|
if (a === "--platform" && args[i + 1]) {
|
|
511
575
|
platform = String(args[(i += 1)]);
|
|
576
|
+
platform_assumed = false;
|
|
512
577
|
} else if (a === "--workspace" && args[i + 1]) {
|
|
513
578
|
workspace_root = path.resolve(String(args[(i += 1)]));
|
|
514
579
|
} else if (a === "--classify" && args[i + 1]) {
|
|
@@ -521,7 +586,13 @@ export function main(argv?: string[]): number {
|
|
|
521
586
|
no_freshness = true;
|
|
522
587
|
}
|
|
523
588
|
}
|
|
524
|
-
const report = collect_report({
|
|
589
|
+
const report = collect_report({
|
|
590
|
+
platform,
|
|
591
|
+
platform_assumed,
|
|
592
|
+
workspace_root,
|
|
593
|
+
no_freshness,
|
|
594
|
+
classify,
|
|
595
|
+
});
|
|
525
596
|
if (json) {
|
|
526
597
|
process.stdout.write(`${JSON.stringify(report as unknown as JsonValue, null, 2)}\n`);
|
|
527
598
|
} else {
|
|
@@ -154,6 +154,11 @@
|
|
|
154
154
|
},
|
|
155
155
|
"description": "What mechanically enforces this rule. `validator:<script>` and `test:<path>` must resolve on disk AND be wired into a taskfile, workflow, or the hook manifest — a script that exists but runs nowhere resolves to unwired, not covered. `hook:<name>` must be registered in hook_manifest.yaml with fail_closed: true; a hook that never blocks is `observer:`, not `validator:`. `none` is legal and counted — an honest gap beats a false claim. Resolved, not trusted, by check_enforcement_coverage.ts."
|
|
156
156
|
},
|
|
157
|
+
"obligation_frequency": {
|
|
158
|
+
"type": "string",
|
|
159
|
+
"enum": ["per-edit", "per-turn", "per-task", "per-session", "per-event", "per-commit", "per-file-write", "none"],
|
|
160
|
+
"description": "How often this rule's obligation comes due — the question `enforced_by` does not answer. `enforced_by` asks *does something carry this*; a carrier can be wired, reachable and firing and still fire in the wrong slot (session-canary: a per-task obligation carried by a session_start hook, measured dropped on ~13 of 15 task starts while counting as enforced). Joined against the carrier's per-slot-and-platform firing frequency by check_enforcement_coverage.ts. The value set is a FOREST, not a chain — three roots that never cover one another: lifecycle (`per-turn` > `per-task` > `per-session`), repository-write (`per-commit`, `per-file-write`), and external-event (`per-event`). `per-edit` is a fourth, incomparable-to-`per-turn` case: pre/post_tool_use fire per tool call, so a conversational turn with no tool call fires them zero times. `none` = no recurring obligation (a pointer or reference rule). Required on every non-kernel rule by validate_frontmatter.ts; the nine kernel rules are exempt because block_kernel_rule_writes.ts denies agent writes to them, and that exemption is derived from _lib/kernel_rules.ts so it closes itself the moment a rule leaves the kernel."
|
|
161
|
+
},
|
|
157
162
|
"trust": {
|
|
158
163
|
"type": "object",
|
|
159
164
|
"additionalProperties": false,
|
|
@@ -0,0 +1,240 @@
|
|
|
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, an issue when it cannot, and a local record when nothing
|
|
16
|
+
* can leave the machine. `--dry-run` prints the resolved route and the rendered
|
|
17
|
+
* body and touches no network — that is the path the tests drive.
|
|
18
|
+
*
|
|
19
|
+
* Fail-closed on privacy: the record's own text goes through the audited
|
|
20
|
+
* privacy floor before anything leaves the machine, and a refusal downgrades
|
|
21
|
+
* the record to local-only rather than scrubbing it into publishable shape.
|
|
22
|
+
*/
|
|
23
|
+
import { spawnSync } from 'node:child_process';
|
|
24
|
+
import * as fs from 'node:fs';
|
|
25
|
+
import * as path from 'node:path';
|
|
26
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
27
|
+
|
|
28
|
+
import {
|
|
29
|
+
chooseEgressRoute,
|
|
30
|
+
type DefectRecord,
|
|
31
|
+
type EgressCapability,
|
|
32
|
+
type EgressRoute,
|
|
33
|
+
egressBlockedReason,
|
|
34
|
+
renderReport,
|
|
35
|
+
} from './_lib/self_repair.js';
|
|
36
|
+
import { listRecords, markReleased, openRecords, readRecord } from './_lib/self_repair_store.js';
|
|
37
|
+
|
|
38
|
+
export const UPSTREAM_REPO = 'event4u-app/agent-config';
|
|
39
|
+
|
|
40
|
+
const EXIT_OK = 0;
|
|
41
|
+
const EXIT_FAIL = 1;
|
|
42
|
+
|
|
43
|
+
/** Probe result for one machine — injectable so tests never shell out. */
|
|
44
|
+
export interface Probe {
|
|
45
|
+
agentConfigCheckout: string | null;
|
|
46
|
+
ghAuthenticated: boolean;
|
|
47
|
+
canPush: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function run(cmd: string, args: string[], cwd?: string): { ok: boolean; out: string } {
|
|
51
|
+
const r = spawnSync(cmd, args, { cwd, encoding: 'utf-8' });
|
|
52
|
+
return { ok: r.status === 0, out: `${r.stdout ?? ''}${r.stderr ?? ''}` };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Where the fix can be authored. `AGENT_CONFIG_SOURCE` wins; otherwise the CWD
|
|
57
|
+
* when it IS the package checkout. A consumer project has neither — that is the
|
|
58
|
+
* normal case, and the reason the issue fallback exists.
|
|
59
|
+
*/
|
|
60
|
+
export function findAgentConfigCheckout(env: NodeJS.ProcessEnv, cwd: string): string | null {
|
|
61
|
+
const explicit = env['AGENT_CONFIG_SOURCE'];
|
|
62
|
+
if (explicit && fs.existsSync(path.join(explicit, 'src', 'rules'))) {
|
|
63
|
+
return explicit;
|
|
64
|
+
}
|
|
65
|
+
if (
|
|
66
|
+
fs.existsSync(path.join(cwd, 'src', 'rules')) &&
|
|
67
|
+
fs.existsSync(path.join(cwd, 'src', 'scripts', 'hook_manifest.yaml'))
|
|
68
|
+
) {
|
|
69
|
+
return cwd;
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function probeMachine(env: NodeJS.ProcessEnv, cwd: string): Probe {
|
|
75
|
+
const checkout = findAgentConfigCheckout(env, cwd);
|
|
76
|
+
const gh = run('gh', ['auth', 'status']);
|
|
77
|
+
const canPush = checkout !== null && run('git', ['remote'], checkout).out.trim().length > 0;
|
|
78
|
+
return { agentConfigCheckout: checkout, ghAuthenticated: gh.ok, canPush };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function capabilityOf(probe: Probe): EgressCapability {
|
|
82
|
+
return {
|
|
83
|
+
hasAgentConfigCheckout: probe.agentConfigCheckout !== null,
|
|
84
|
+
ghAuthenticated: probe.ghAuthenticated,
|
|
85
|
+
canPush: probe.canPush,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Title line for the PR / issue — one per defect class, stable across reruns. */
|
|
90
|
+
export function titleFor(record: DefectRecord): string {
|
|
91
|
+
return `fix(self-repair): ${record.defect_class} — ${record.occurrences}x observed`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface ReleasePlan {
|
|
95
|
+
route: EgressRoute;
|
|
96
|
+
blocked: string | null;
|
|
97
|
+
title: string;
|
|
98
|
+
body: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Pure: everything `release` decides before it touches the network. */
|
|
102
|
+
export function planRelease(record: DefectRecord, probe: Probe, repoRoot: string): ReleasePlan {
|
|
103
|
+
const blocked = egressBlockedReason(record, repoRoot);
|
|
104
|
+
const route: EgressRoute =
|
|
105
|
+
blocked !== null ? 'local-only' : chooseEgressRoute(capabilityOf(probe));
|
|
106
|
+
return { route, blocked, title: titleFor(record), body: renderReport(record, route) };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function statusCmd(root: string): number {
|
|
110
|
+
const open = openRecords(root);
|
|
111
|
+
const all = listRecords(root);
|
|
112
|
+
if (all.length === 0) {
|
|
113
|
+
process.stdout.write('self-repair: no records.\n');
|
|
114
|
+
return EXIT_OK;
|
|
115
|
+
}
|
|
116
|
+
process.stdout.write(`self-repair: ${open.length} open of ${all.length} record(s)\n\n`);
|
|
117
|
+
for (const r of all) {
|
|
118
|
+
const mark = r.status === 'open' ? 'o' : '.';
|
|
119
|
+
process.stdout.write(
|
|
120
|
+
` ${mark} ${r.fingerprint} ${r.defect_class} ${r.occurrences}x ${r.last_seen}\n` +
|
|
121
|
+
` ${r.evidence}\n`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
process.stdout.write('\nRelease one: agent-config self-repair:release <fingerprint>\n');
|
|
125
|
+
return EXIT_OK;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function releaseCmd(root: string, argv: string[]): number {
|
|
129
|
+
const fp = argv.find((a) => !a.startsWith('-'));
|
|
130
|
+
if (!fp) {
|
|
131
|
+
process.stderr.write('self-repair:release needs a fingerprint (see self-repair:status).\n');
|
|
132
|
+
return EXIT_FAIL;
|
|
133
|
+
}
|
|
134
|
+
const record = readRecord(root, fp);
|
|
135
|
+
if (record === null) {
|
|
136
|
+
process.stderr.write(`self-repair:release: no record ${fp}.\n`);
|
|
137
|
+
return EXIT_FAIL;
|
|
138
|
+
}
|
|
139
|
+
const dryRun = argv.includes('--dry-run');
|
|
140
|
+
const repoArg = argv.indexOf('--repo');
|
|
141
|
+
const repo = repoArg >= 0 ? (argv[repoArg + 1] ?? UPSTREAM_REPO) : UPSTREAM_REPO;
|
|
142
|
+
const probe = probeMachine(process.env, process.cwd());
|
|
143
|
+
const plan = planRelease(record, probe, root);
|
|
144
|
+
|
|
145
|
+
process.stdout.write(`self-repair:release ${fp} -> route=${plan.route}\n`);
|
|
146
|
+
if (plan.blocked !== null) {
|
|
147
|
+
process.stdout.write(` WARN ${plan.blocked}\n`);
|
|
148
|
+
}
|
|
149
|
+
if (dryRun) {
|
|
150
|
+
process.stdout.write(`\n--- ${plan.title} ---\n${plan.body}\n`);
|
|
151
|
+
return EXIT_OK;
|
|
152
|
+
}
|
|
153
|
+
if (plan.route === 'local-only') {
|
|
154
|
+
process.stdout.write(
|
|
155
|
+
' Nothing published. The record stays in agents/runtime/self-repair/.\n',
|
|
156
|
+
);
|
|
157
|
+
return EXIT_OK;
|
|
158
|
+
}
|
|
159
|
+
if (plan.route === 'issue') {
|
|
160
|
+
const r = run('gh', [
|
|
161
|
+
'issue',
|
|
162
|
+
'create',
|
|
163
|
+
'--repo',
|
|
164
|
+
repo,
|
|
165
|
+
'--title',
|
|
166
|
+
plan.title,
|
|
167
|
+
'--body',
|
|
168
|
+
plan.body,
|
|
169
|
+
]);
|
|
170
|
+
process.stdout.write(`${r.out}\n`);
|
|
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,
|
|
193
|
+
);
|
|
194
|
+
process.stdout.write(`${pr.out}\n`);
|
|
195
|
+
if (!pr.ok) {
|
|
196
|
+
return EXIT_FAIL;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
markReleased(root, fp, new Date().toISOString());
|
|
200
|
+
return EXIT_OK;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function main(argv: readonly string[]): number {
|
|
204
|
+
const root = process.cwd();
|
|
205
|
+
const sub = argv[0] ?? 'status';
|
|
206
|
+
if (sub === 'status') {
|
|
207
|
+
return statusCmd(root);
|
|
208
|
+
}
|
|
209
|
+
if (sub === 'release') {
|
|
210
|
+
return releaseCmd(root, [...argv.slice(1)]);
|
|
211
|
+
}
|
|
212
|
+
process.stderr.write(`self-repair: unknown subcommand '${sub}' (status | release).\n`);
|
|
213
|
+
return EXIT_FAIL;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
declare const __AGENT_CONFIG_BUNDLE__: boolean | undefined;
|
|
217
|
+
function isCliEntry(): boolean {
|
|
218
|
+
if (typeof __AGENT_CONFIG_BUNDLE__ !== 'undefined' && __AGENT_CONFIG_BUNDLE__) {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
if (process.argv[1] === undefined) {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
|
|
225
|
+
if (import.meta.url === argvUrl) {
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
try {
|
|
229
|
+
return (
|
|
230
|
+
fs.realpathSync(fileURLToPath(import.meta.url)) ===
|
|
231
|
+
fs.realpathSync(path.resolve(process.argv[1]))
|
|
232
|
+
);
|
|
233
|
+
} catch {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (isCliEntry()) {
|
|
239
|
+
process.exit(main(process.argv.slice(2)));
|
|
240
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Self-repair hook — turns an observed agent defect into a queued, fixable
|
|
4
|
+
* record without the user having to file anything.
|
|
5
|
+
*
|
|
6
|
+
* Three slots, one record shape:
|
|
7
|
+
*
|
|
8
|
+
* - `user_prompt_submit` — the user states the agent worked wrongly. Opens a
|
|
9
|
+
* `user-reported` record and injects the open-queue line so the agent picks
|
|
10
|
+
* the work up in the same turn.
|
|
11
|
+
* - `stop` — the finished turn is run past the deterministic detectors. A hit
|
|
12
|
+
* opens a `self-detected` record. Nothing is injected here: a reminder that
|
|
13
|
+
* fires after the reply cannot shape it, and the queue line at the next
|
|
14
|
+
* prompt reaches the same agent one turn later at a fraction of the tokens.
|
|
15
|
+
* - every other slot — clean no-op.
|
|
16
|
+
*
|
|
17
|
+
* ADVISORY BY CONSTRUCTION (`fail_closed: false`, always exit 0). Blocking a
|
|
18
|
+
* turn-end on a heuristic is the failure mode the enforcement-projection and
|
|
19
|
+
* recursive-verification nulls both warn about; this hook records, it does not
|
|
20
|
+
* gate. The one outward step (push + PR, or an issue) stays behind the
|
|
21
|
+
* `non-destructive-by-default` Hard Floor and lives in `self_repair_cli.ts`.
|
|
22
|
+
*
|
|
23
|
+
* Honest scope: the detector set is small and deterministic on purpose. It
|
|
24
|
+
* covers the two defect classes the conformance audits actually measured, plus
|
|
25
|
+
* the user-report path. It does NOT "check all rules were followed" — most
|
|
26
|
+
* obligations are model-carried and unobservable from a transcript, and a
|
|
27
|
+
* detector that guessed at them would manufacture defects and flood the queue.
|
|
28
|
+
*/
|
|
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
|
+
type DefectFinding,
|
|
35
|
+
detectUserReport,
|
|
36
|
+
runDetectors,
|
|
37
|
+
type TurnSnapshot,
|
|
38
|
+
} from './_lib/self_repair.js';
|
|
39
|
+
import { openRecords, upsertFinding } from './_lib/self_repair_store.js';
|
|
40
|
+
import { readHookStdin } from './hooks/hook_stdin.js';
|
|
41
|
+
|
|
42
|
+
const EXIT_ALLOW = 0;
|
|
43
|
+
|
|
44
|
+
type JsonValue = string | number | boolean | null | JsonValue[] | { [k: string]: JsonValue };
|
|
45
|
+
type JsonObject = { [k: string]: JsonValue };
|
|
46
|
+
|
|
47
|
+
function isObject(v: unknown): v is JsonObject {
|
|
48
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function str(v: JsonValue | undefined): string {
|
|
52
|
+
return typeof v === 'string' ? v : '';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function workspaceRoot(env: JsonObject): string {
|
|
56
|
+
const v = env['workspace_root'];
|
|
57
|
+
return typeof v === 'string' && v ? v : process.cwd();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Last assistant reply + the tool commands of the turn, read from the host's
|
|
62
|
+
* JSONL transcript. Absent or unreadable transcript → an empty snapshot, which
|
|
63
|
+
* every detector declines.
|
|
64
|
+
*/
|
|
65
|
+
export function readTurn(transcriptPath: string, pinned: 'de' | 'en' | null): TurnSnapshot {
|
|
66
|
+
const empty: TurnSnapshot = { prompt: '', reply: '', toolCommands: [], pinnedLanguage: pinned };
|
|
67
|
+
if (!transcriptPath) {
|
|
68
|
+
return empty;
|
|
69
|
+
}
|
|
70
|
+
let lines: string[];
|
|
71
|
+
try {
|
|
72
|
+
lines = fs.readFileSync(transcriptPath, 'utf-8').split('\n');
|
|
73
|
+
} catch {
|
|
74
|
+
return empty;
|
|
75
|
+
}
|
|
76
|
+
let reply = '';
|
|
77
|
+
let prompt = '';
|
|
78
|
+
const toolCommands: string[] = [];
|
|
79
|
+
for (const raw of lines) {
|
|
80
|
+
const line = raw.trim();
|
|
81
|
+
if (!line) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
let obj: unknown;
|
|
85
|
+
try {
|
|
86
|
+
obj = JSON.parse(line);
|
|
87
|
+
} catch {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (!isObject(obj)) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const msg = obj['message'];
|
|
94
|
+
if (!isObject(msg)) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const content = msg['content'];
|
|
98
|
+
if (obj['type'] === 'user' && typeof content === 'string') {
|
|
99
|
+
prompt = content;
|
|
100
|
+
}
|
|
101
|
+
if (obj['type'] !== 'assistant') {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (typeof content === 'string') {
|
|
105
|
+
reply = content;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (!Array.isArray(content)) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
const parts: string[] = [];
|
|
112
|
+
for (const blk of content) {
|
|
113
|
+
if (!isObject(blk)) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (blk['type'] === 'text' && typeof blk['text'] === 'string') {
|
|
117
|
+
parts.push(blk['text']);
|
|
118
|
+
}
|
|
119
|
+
if (blk['type'] === 'tool_use') {
|
|
120
|
+
const input = blk['input'];
|
|
121
|
+
const name = str(blk['name']);
|
|
122
|
+
const cmd = isObject(input) ? str(input['command']) : '';
|
|
123
|
+
toolCommands.push(`${name} ${cmd}`.trim());
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (parts.length > 0) {
|
|
127
|
+
reply = parts.join('\n');
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return { prompt, reply, toolCommands, pinnedLanguage: pinned };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** The queue line injected at prompt time when records are waiting. */
|
|
134
|
+
export function buildQueueLine(count: number, newest: string): string {
|
|
135
|
+
return (
|
|
136
|
+
`<self-repair-queue>${count} open agent-config defect record(s) ` +
|
|
137
|
+
`(newest: ${newest}). These are defects in the AGENT CONFIG, not in this ` +
|
|
138
|
+
`project. Per the self-repair rule: analyse the newest record, author the ` +
|
|
139
|
+
`fix against agent-config, and prepare the release — the outward step ` +
|
|
140
|
+
`(push + PR, or an issue) still needs the user's word this turn. ` +
|
|
141
|
+
`Records: agents/runtime/self-repair/</self-repair-queue>`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function pinnedLanguage(env: JsonObject): 'de' | 'en' | null {
|
|
146
|
+
const v = str(env['reply_language'] ?? env['language']).toLowerCase();
|
|
147
|
+
if (v.startsWith('de')) {
|
|
148
|
+
return 'de';
|
|
149
|
+
}
|
|
150
|
+
if (v.startsWith('en')) {
|
|
151
|
+
return 'en';
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function main(): number {
|
|
157
|
+
let env: JsonObject = {};
|
|
158
|
+
try {
|
|
159
|
+
const raw = readHookStdin();
|
|
160
|
+
const parsed: unknown = raw.trim() ? JSON.parse(raw) : {};
|
|
161
|
+
env = isObject(parsed) ? parsed : {};
|
|
162
|
+
} catch {
|
|
163
|
+
// malformed stdin — never block, just no-op below
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const slot = str(env['event']) || 'session_start';
|
|
167
|
+
if (slot !== 'user_prompt_submit' && slot !== 'stop') {
|
|
168
|
+
return EXIT_ALLOW;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const root = workspaceRoot(env);
|
|
172
|
+
const now = new Date().toISOString();
|
|
173
|
+
const findings: DefectFinding[] = [];
|
|
174
|
+
|
|
175
|
+
if (slot === 'user_prompt_submit') {
|
|
176
|
+
const prompt = str(env['prompt'] ?? env['user_prompt'] ?? env['text']);
|
|
177
|
+
const f = detectUserReport(prompt);
|
|
178
|
+
if (f !== null) {
|
|
179
|
+
findings.push(f);
|
|
180
|
+
}
|
|
181
|
+
} else {
|
|
182
|
+
const tp = str(env['transcript_path'] ?? env['transcriptPath']);
|
|
183
|
+
findings.push(...runDetectors(readTurn(tp, pinnedLanguage(env))));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
for (const f of findings) {
|
|
187
|
+
try {
|
|
188
|
+
upsertFinding(root, f, now);
|
|
189
|
+
} catch {
|
|
190
|
+
// an unwritable store must never cost the user their turn
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Injection happens at prompt time only — see the header note on `stop`.
|
|
195
|
+
if (slot !== 'user_prompt_submit') {
|
|
196
|
+
return EXIT_ALLOW;
|
|
197
|
+
}
|
|
198
|
+
let open: ReturnType<typeof openRecords>;
|
|
199
|
+
try {
|
|
200
|
+
open = openRecords(root);
|
|
201
|
+
} catch {
|
|
202
|
+
return EXIT_ALLOW;
|
|
203
|
+
}
|
|
204
|
+
if (open.length === 0) {
|
|
205
|
+
return EXIT_ALLOW;
|
|
206
|
+
}
|
|
207
|
+
process.stdout.write(
|
|
208
|
+
`${JSON.stringify({
|
|
209
|
+
decision: 'allow',
|
|
210
|
+
reason: `self-repair: ${open.length} open record(s)`,
|
|
211
|
+
context: buildQueueLine(open.length, open[0]!.defect_class),
|
|
212
|
+
})}\n`,
|
|
213
|
+
);
|
|
214
|
+
return EXIT_ALLOW;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
declare const __AGENT_CONFIG_BUNDLE__: boolean | undefined;
|
|
218
|
+
function isCliEntry(): boolean {
|
|
219
|
+
if (typeof __AGENT_CONFIG_BUNDLE__ !== 'undefined' && __AGENT_CONFIG_BUNDLE__) {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
if (process.argv[1] === undefined) {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
|
|
226
|
+
if (import.meta.url === argvUrl) {
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
try {
|
|
230
|
+
return (
|
|
231
|
+
fs.realpathSync(fileURLToPath(import.meta.url)) ===
|
|
232
|
+
fs.realpathSync(path.resolve(process.argv[1]))
|
|
233
|
+
);
|
|
234
|
+
} catch {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (isCliEntry()) {
|
|
240
|
+
process.exit(main());
|
|
241
|
+
}
|